主页 > IT业界  > 

Qt中使用ffmpeg获取采集卡数据录制视频

Qt中使用ffmpeg获取采集卡数据录制视频

作者:billy 版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处

前言

之前做了一个功能,从采集卡获取数据然后录制成视频,结果发现录制的视频内存占用非常大,1分钟的视频大概有 800MB 内存。在帧率和分辨率已确定的情况下,只能通过调整比特率来减少内存占用,但是设置比特率在不同编码器和平台支持情况都有所不同,有些编码器甚至不支持直接设置比特率,所以博主想起了 ffmpeg 这个神器。

这里先介绍一下视频的相关参数:

在用户视角: 清晰度 = 比特率(码率) / 分辨率 流畅度 = 帧率 在开发者视角: 影响内存的:主要是分辨率 影响 CPU 的:码率和编码格式 影响 GPU 的:分辨率和编码格式 影响体积大小和带宽:码率 ffmpeg 库功能测试

首先在网上找到了 ffmpeg 库的 windows 安装包来做下测试 百度网盘下载链接:ffmpeg(windows安装包) 提取码:fkmn

下载完成之后可以直接使用 bin\ffmpeg.exe 在命令行做测试,把 ffmpeg\bin 路径添加到环境变量中

确认 ffmpeg 版本和配置:ffmpeg -version

列举所有设备:ffmpeg -list_devices true -f dshow -i dummy

对已录制的内存占用较大的视频进行压缩:

设置码率:ffmpeg -i input.mp4 -b:v 1000k output.mp4设置分辨率:ffmpeg -i input.mp4 -s 640x360 output.mp4设置帧率:ffmpeg -i input.mp4 -r 30 output.mp4综合调整:ffmpeg -i input.mp4 -b:v 800k -s 640x360 -r 30 output.mp4

也可以用 python 跑脚本:

import subprocess def compress_video(input_file, output_file, bitrate='1000k'): command = [ 'ffmpeg', '-i', input_file, '-b:v', bitrate, output_file ] try: subprocess.run(command, check=True) print(f"视频压缩成功,输出文件为: {output_file}") except subprocess.CalledProcessError as e: print(f"视频压缩失败: {e}") # 使用示例 input_file = 'input.mp4' output_file = 'output.mp4' compress_video(input_file, output_file, bitrate='800k')

再测试一下直接打开设备录制视频

ffmpeg -f dshow -i video="@device_pnp_\\?\usb#vid_2b89&pid_5647&mi_00#7&223c07ce&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global" -c:v libx264 -max_delay 10000 -b:v 1000k -bufsize 200k -r 30 output.mp4 -y 命令解释 -c:v libx264: 指定使用libx264编码器进行视频编码。 -max_delay 10000: 设置编码器的最大延迟为10000毫秒(10秒)。这有助于控制视频编码的缓冲延迟。 -b:v 1000k: 设置视频比特率为1000 kbps(千比特每秒)。 -bufsize 200k: 设置编码器的缓冲区大小为200 kbps。这个参数用于控制编码器在遇到高负载或低负载时的比特率变化。 -r 30: 设置帧率为30帧每秒。 output.mp4: 输出文件名。 -y: 覆盖输出文件,如果文件已存在。

测试结果为用 ffmpeg 压缩过的视频内存占用率非常小,码率越小内存就越小。但是对于客户来说他不会使用命令行去做压缩,所以最终方案还是直接使用 ffmpeg 库来录制视频。

Qt 中集成 ffmpeg 库来录制视频

首先在网上下载编译完成的 ffmpeg 库,博主用的是 Qt 5.15.2 和 vs2019 百度网盘下载链接:ffmpeg(库文件) 提取码: cqwx

把库集成到 Qt 中:

INCLUDEPATH += $$PWD/ffmpeg/include LIBS += -L$$PWD/ffmpeg/lib/ -lavcodec LIBS += -L$$PWD/ffmpeg/lib/ -lavdevice LIBS += -L$$PWD/ffmpeg/lib/ -lavfilter LIBS += -L$$PWD/ffmpeg/lib/ -lavformat LIBS += -L$$PWD/ffmpeg/lib/ -lavutil LIBS += -L$$PWD/ffmpeg/lib/ -lpostproc LIBS += -L$$PWD/ffmpeg/lib/ -lswresample LIBS += -L$$PWD/ffmpeg/lib/ -lswscale

实现拍照和录屏的功能:

extern "C" { #include <libavformat/avformat.h> #include <libavcodec/avcodec.h> #include <libavutil/avutil.h> #include <libavutil/imgutils.h> #include <libswscale/swscale.h> #include <libavdevice/avdevice.h> } void showErrorInfo(int ret) { static char errbuf[AV_ERROR_MAX_STRING_SIZE]; memset(errbuf, 0, AV_ERROR_MAX_STRING_SIZE); av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); qDebug() << "Error info:" << errbuf; } void videoRecord() { avformat_network_init(); avdevice_register_all(); // 遍历所有设备类型 // const AVInputFormat *iformat = nullptr; // while ((iformat = av_input_audio_device_next(iformat))) { // qDebug() << "Audio input device: " << iformat->name << " - " << iformat->long_name; // } // iformat = nullptr; // while ((iformat = av_input_video_device_next(iformat))) { // qDebug() << "Video input device: " << iformat->name << " - " << iformat->long_name; // } const AVInputFormat *iformat_dshow = av_find_input_format("dshow"); if (!iformat_dshow) { qDebug() << "Could not find input format !"; return; } // 用于存储设备列表的上下文 AVDeviceInfoList *deviceList = nullptr; int ret = avdevice_list_input_sources(iformat_dshow, nullptr, nullptr, &deviceList); if (ret < 0) { qDebug() << "Could not list input sources !"; showErrorInfo(ret); return; } // 设备名称 std::string deviceName = ""; // 遍历设备列表 // qDebug() << "Available DirectShow devices:"; for (int i = 0; i < deviceList->nb_devices; ++i) { AVDeviceInfo *device = deviceList->devices[i]; // qDebug() << "Device" << i << ":" << device->device_description << "(" << device->device_name << ")"; // 获取绿联采集卡的设备名称 QString description = QString(device->device_description); if ( description.contains("UGREEN") ) { deviceName = "video=" + description.toStdString(); break; } } // 释放设备列表 avdevice_free_list_devices(&deviceList); //------------------------------ // 创建 AVFormatContext AVFormatContext *inputFormatContext = avformat_alloc_context(); if (!inputFormatContext) { qDebug() << "Could not allocate AVFormatContext !"; return; } // 设置附加参数 AVDictionary *options = nullptr; int framerate = 30; av_dict_set(&options, "rtbufsize", "100M", 0); // 设置缓冲区大小 av_dict_set(&options, "framerate", "30", 0); // 设置帧率 // 打开输入设备 ret = avformat_open_input(&inputFormatContext, deviceName.c_str(), iformat_dshow, &options); if (ret < 0) { qDebug() << "Could not open input device !"; showErrorInfo(ret); return; } // 打印输入设备的信息 // av_dump_format(inputFormatContext, 0, deviceName.c_str(), 0); // 查找输入流信息 ret = avformat_find_stream_info(inputFormatContext, nullptr); if (ret < 0) { qDebug() << "Could not find stream information !"; showErrorInfo(ret); return; } // 查找视频流 int videoStreamIndex = -1; for (unsigned int i = 0; i < inputFormatContext->nb_streams; i++) { if (inputFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { videoStreamIndex = i; break; } } if (videoStreamIndex == -1) { qDebug() << "Could not find video stream !"; return; } // 获取视频流参数 AVCodecParameters *inputCodecParameters = inputFormatContext->streams[videoStreamIndex]->codecpar; // 查找输入解码器 const AVCodec *inputCodec = avcodec_find_decoder(inputCodecParameters->codec_id); if (!inputCodec) { qDebug() << "Could not find codec !"; return ; } // 创建输入解码器上下文 AVCodecContext *inputCodecContext = avcodec_alloc_context3(inputCodec); if (!inputCodecContext) { qDebug() << "Could not allocate codec context !"; return; } // 将视频流参数复制到输入解码器上下文 ret = avcodec_parameters_to_context(inputCodecContext, inputCodecParameters); if (ret < 0) { qDebug() << "Could not copy codec parameters to context !"; showErrorInfo(ret); return; } // 打开输入解码器 ret = avcodec_open2(inputCodecContext, inputCodec, nullptr); if (ret < 0) { qDebug() << "Could not open codec !"; showErrorInfo(ret); return; } //------------------------------ // 分配帧和数据包 AVFrame *frame = av_frame_alloc(); AVPacket *packet = av_packet_alloc(); if (!frame || !packet) { qDebug() << "Could not alloc frame and packet !"; return; } // 分配图像转换上下文 SwsContext *swsContext = sws_getContext(inputCodecContext->width, inputCodecContext->height, inputCodecContext->pix_fmt, inputCodecContext->width, inputCodecContext->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, nullptr, nullptr, nullptr); if (!swsContext) { qDebug() << "Could not initialize SwsContext !"; return; } // 分配 RGB 帧 AVFrame *rgbFrame = av_frame_alloc(); int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, inputCodecContext->width, inputCodecContext->height, 1); uint8_t *buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t)); av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, AV_PIX_FMT_RGB24, inputCodecContext->width, inputCodecContext->height, 1); // 读取帧数据(保存10张图片) int frameCount = 0; while (av_read_frame(inputFormatContext, packet) >= 0 && frameCount < 11) { // 确保帧的数据类型和解码器的数据类型一致 if (packet->stream_index == videoStreamIndex) { // 发送数据包到解码器 ret = avcodec_send_packet(inputCodecContext, packet); if (ret < 0) { qDebug() << "Error sending packet to decoder !"; showErrorInfo(ret); continue; } // 接收解码后的帧 while (avcodec_receive_frame(inputCodecContext, frame) == 0) { // 舍弃第一帧 if ( frameCount == 0 ) { frameCount++; continue; } // 转换图像格式 sws_scale(swsContext, frame->data, frame->linesize, 0, inputCodecContext->height, rgbFrame->data, rgbFrame->linesize); // 创建 QImage QImage image(rgbFrame->data[0], inputCodecContext->width, inputCodecContext->height, QImage::Format_RGB888); // 保存图像 QString fileName = QString("frame_%1.jpg").arg(frameCount++); if (!image.save(fileName)) { qDebug() << "Error saving image:" << fileName; } else { qDebug() << "Image saved:" << fileName; } } } av_packet_unref(packet); } //------------------------------ // 输出格式上下文 AVFormatContext *outputFormatContext = nullptr; const char *outputFileName = "output.mp4"; // 创建输出格式上下文 ret = avformat_alloc_output_context2(&outputFormatContext, nullptr, nullptr, outputFileName); if (!outputFormatContext) { qDebug() << "Could not create output context !"; showErrorInfo(ret); return; } // 查找输出编码器 const AVCodec *outputCodec = avcodec_find_encoder(AV_CODEC_ID_H264); if (!outputCodec) { qDebug() << "Could not find output codec !"; return; } // 创建输出流 AVStream *outputStream = avformat_new_stream(outputFormatContext, outputCodec); if (!outputStream) { qDebug() << "Could not create output stream !"; return; } // 打开输出编码器上下文 AVCodecContext *outputCodecContext = avcodec_alloc_context3(outputCodec); if (!outputCodecContext) { qDebug() << "Could not allocate output codec context !"; return; } // 设置输出编码器参数 outputCodecContext->codec_id = outputCodec->id; outputCodecContext->codec_type = AVMEDIA_TYPE_VIDEO; outputCodecContext->pix_fmt = AV_PIX_FMT_YUV420P; outputCodecContext->width = 1920; // 设置分辨率 outputCodecContext->height = 1080; outputCodecContext->time_base = {1, framerate}; // 设置帧率 outputCodecContext->framerate = {framerate, 1}; outputCodecContext->bit_rate = 2000000; // 设置比特率 outputCodecContext->rc_buffer_size = 2 * outputCodecContext->bit_rate; // 设置缓冲区大小为码率的两倍 outputCodecContext->gop_size = 10; // 设置关键帧间隔 outputCodecContext->max_b_frames = 1; if (outputFormatContext->oformat->flags & AVFMT_GLOBALHEADER) { outputCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } // 打开输出编码器 ret = avcodec_open2(outputCodecContext, outputCodec, nullptr); if (ret < 0) { qDebug() << "Could not open output codec !"; showErrorInfo(ret); return; } // 复制输出编码器参数到输出流 avcodec_parameters_from_context(outputStream->codecpar, outputCodecContext); outputStream->time_base = outputCodecContext->time_base; //------------------------------ // 打开输出文件 if (!(outputFormatContext->oformat->flags & AVFMT_NOFILE)) { ret = avio_open(&outputFormatContext->pb, outputFileName, AVIO_FLAG_WRITE); if (ret < 0) { qDebug() << "Could not open output file !"; showErrorInfo(ret); return; } } // 写入文件头 ret = avformat_write_header(outputFormatContext, nullptr); if (ret < 0) { qDebug() << "Could not write header !"; showErrorInfo(ret); return; } // 分配帧和数据包 AVFrame *inputFrame = av_frame_alloc(); AVPacket *inputPacket = av_packet_alloc(); AVFrame *outputFrame = av_frame_alloc(); AVPacket *outputPacket = av_packet_alloc(); if (!inputFrame || !inputPacket || !outputFrame || !outputPacket) { qDebug() << "Could not alloc frame and packet !"; return; } // 分配图像转换上下文 SwsContext *swsContext2 = sws_getContext(inputCodecContext->width, inputCodecContext->height, inputCodecContext->pix_fmt, outputCodecContext->width, outputCodecContext->height, outputCodecContext->pix_fmt, SWS_BILINEAR, nullptr, nullptr, nullptr); if (!swsContext2) { qDebug() << "Could not initialize SwsContext !"; return; } // 分配输出帧数据 outputFrame->format = AV_PIX_FMT_YUV420P; outputFrame->width = inputCodecContext->width; outputFrame->height = inputCodecContext->height; ret = av_frame_get_buffer(outputFrame, 0); if (ret < 0) { qDebug() << "Could not get frame buffer !"; showErrorInfo(ret); return; } // 录屏60秒,帧率30,保存1800帧数据 int frameCount2 = 0; while (av_read_frame(inputFormatContext, inputPacket) >= 0 && frameCount2 < 1800) { // 确保帧的数据类型和解码器的数据类型一致 if (inputPacket->stream_index == videoStreamIndex) { // 发送输入数据包到解码器 ret = avcodec_send_packet(inputCodecContext, inputPacket); if (ret < 0) { qDebug() << "Error sending packet to decoder !"; showErrorInfo(ret); continue; } // 接收解码后的帧 while (avcodec_receive_frame(inputCodecContext, inputFrame) == 0) { // 转换图像格式 sws_scale(swsContext2, inputFrame->data, inputFrame->linesize, 0, inputCodecContext->height, outputFrame->data, outputFrame->linesize); outputFrame->pts = frameCount2++; qDebug() << "frameCount2: " << frameCount2; // 发送输出帧到输出编码器 ret = avcodec_send_frame(outputCodecContext, outputFrame); if (ret < 0) { qDebug() << "Error sending frame to encoder !"; showErrorInfo(ret); continue; } // 接收编码后的数据包 while (avcodec_receive_packet(outputCodecContext, outputPacket) == 0) { av_packet_rescale_ts(outputPacket, outputCodecContext->time_base, outputStream->time_base); outputPacket->stream_index = outputStream->index; // 写入数据包到输出文件 ret = av_interleaved_write_frame(outputFormatContext, outputPacket); if (ret < 0) { qDebug() << "Error writing packet to output file"; showErrorInfo(ret); continue; } av_packet_unref(outputPacket); } } } av_packet_unref(inputPacket); } // 刷新编码器 avcodec_send_frame(outputCodecContext, nullptr); while (avcodec_receive_packet(outputCodecContext, outputPacket) == 0) { av_packet_rescale_ts(outputPacket, outputCodecContext->time_base, outputStream->time_base); outputPacket->stream_index = outputStream->index; ret = av_interleaved_write_frame(outputFormatContext, outputPacket); if (ret < 0) { qDebug() << "Error writing packet to output file"; showErrorInfo(ret); } av_packet_unref(outputPacket); } // 写入文件尾 av_write_trailer(outputFormatContext); // 释放资源 av_frame_free(&inputFrame); av_frame_free(&outputFrame); av_packet_free(&inputPacket); av_packet_free(&outputPacket); sws_freeContext(swsContext2); avcodec_free_context(&outputCodecContext); avformat_free_context(outputFormatContext); if (outputFormatContext && !(outputFormatContext->oformat->flags & AVFMT_NOFILE)) { avio_closep(&outputFormatContext->pb); } av_free(buffer); av_frame_free(&rgbFrame); av_frame_free(&frame); av_packet_free(&packet); sws_freeContext(swsContext); avcodec_free_context(&inputCodecContext); avformat_close_input(&inputFormatContext); avformat_free_context(inputFormatContext); av_dict_free(&options); avformat_network_deinit(); }
标签:

Qt中使用ffmpeg获取采集卡数据录制视频由讯客互联IT业界栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“Qt中使用ffmpeg获取采集卡数据录制视频