使用FFmpeg+SDL2读取本地摄像头并渲染
核心流程分为设备初始化、数据捕获、解码、格式转换和渲染五个阶段。
一、整体流程
二、代码实现
1. 枚举并打开摄像头设备
#include <libavformat/avformat.h>
#include <libavdevice/avdevice.h>// 枚举设备(Windows)
avdevice_register_all();
AVDictionary *options = NULL;
av_dict_set(&options, "list_devices", "true", 0);
AVFormatContext *fmt_ctx = NULL;
avformat_open_input(&fmt_ctx, "video=dummy", av_find_input_format("dshow"), &options);// 打开摄像头(Linux V4L2)
avformat_open_input(&fmt_ctx, "/dev/video0", av_find_input_format("v4l2"), NULL);
注:Windows 设备名可能乱码,需通过
ffmpeg -list_devices true -f dshow -i dummy
获取准确名称7。
2. 配置视频流与解码器
// 查找视频流索引
int video_stream_index = -1;
for (int i = 0; i < fmt_ctx->nb_streams; i++) {if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {video_stream_index = i;break;}
}// 初始化解码器(H.264/MJPEG)
AVCodecParameters *codec_par = fmt_ctx->streams[video_stream_index]->codecpar;
AVCodec *codec = avcodec_find_decoder(codec_par->codec_id);
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(codec_ctx, codec_par);
avcodec_open2(codec_ctx, codec, NULL);
3. 初始化 SDL2 渲染窗口
使用SDL窗口
SDL_Init(SDL_INIT_VIDEO);
SDL_Window *window = SDL_CreateWindow("Camera", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, codec_ctx->width, codec_ctx->height, SDL_WINDOW_SHOWN);
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING,codec_ctx->width, codec_ctx->height);
获取 Qt 窗口句柄
// 在 Qt 窗口中创建一个容器(如 QLabel)
QLabel *sdlContainer = new QLabel(this);
sdlContainer->resize(640, 480);// 将 Qt 窗口句柄传递给 SDL2
SDL_Window *sdlWindow = SDL_CreateWindowFrom((void*)sdlContainer->winId());
4. 帧处理循环(解码 → 转换 → 渲染)
AVPacket *pkt = av_packet_alloc();
AVFrame *frame = av_frame_alloc();
SwsContext *sws_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P,SWS_BILINEAR, NULL, NULL, NULL);while (1) {av_read_frame(fmt_ctx, pkt); // 读取数据包if (pkt->stream_index == video_stream_index) {avcodec_send_packet(codec_ctx, pkt);if (avcodec_receive_frame(codec_ctx, frame) == 0) {// YUV 格式转换(例如 MJPEG → YUV420)AVFrame *yuv_frame = av_frame_alloc();sws_scale(sws_ctx, frame->data, frame->linesize, 0, frame->height,yuv_frame->data, yuv_frame->linesize);// SDL 渲染SDL_UpdateYUVTexture(texture, NULL,yuv_frame->data[0], yuv_frame->linesize[0], // Yyuv_frame->data[1], yuv_frame->linesize[1], // Uyuv_frame->data[2], yuv_frame->linesize[2]); // VSDL_RenderClear(renderer);SDL_RenderCopy(renderer, texture, NULL, NULL);SDL_RenderPresent(renderer);}}av_packet_unref(pkt);
}