一、前言
用ffmpeg來處理USB攝像頭,是前段時間研究視頻監控ffmpeg內核的時候搞定的,既然ffmpeg這么牛逼的庫可以解析各種音視頻,我想處理個本地USB攝像頭應該也不是什么難事,果真搜索也是一大堆,當然主要也是因為有個項目的應用需要用到ffmpeg來處理本地USB攝像頭,需要拿到每張圖片做智能分析,用Qt自帶的camera類不大好處理,剛好將ffmpeg的處理流程都搞清楚了,索性直接用ffmpeg來直接處理好了,用上這么強大的解碼庫,理論上支持各種USB攝像頭。本地USB攝像機不需要硬解碼,視頻流編碼類型為 AV_CODEC_ID_RAWVIDEO 像素格式為 AV_PIX_FMT_YUYV422 不經過解碼操作直接就可顯示。
ffmpeg方案處理流程:
- 引入avdevice.h頭文件,調用avdevice_register_all();注冊本地設備處理。
- 調用av_dict_set設置分辨率(video_size)、幀率(framerate)等參數。
- 調用av_find_input_format設置輸入格式。
- 調用avformat_open_input打開文件。
- 調用av_find_best_stream找到視頻流地址。
- 調用avcodec_find_decoder設置視頻解碼器。
- 調用av_read_frame循環解碼讀取幀數據。
- 調用avcodec_send_packet avcodec_receive_frame解碼數據。
- 處理完以后調用av_frame_free avcodec_close等釋放資源。
二、功能特點
- 同時支持windows、linux、嵌入式linux上的USB攝像頭實時采集。
- 支持多路USB攝像頭多線程實時采集。
- 在嵌入式linux設備上,自動查找USB設備文件並加載。
- 可手動設置設備文件名稱,手動設置后按照手動設置的設備文件加載。
- 在嵌入式linux設備上支持人臉識別接口,實時繪制人臉框。
- 具有打開、暫停、繼續、關閉、截圖等常規功能。
- 可設置兩路OSD標簽,分別設置文本、顏色、字號、位置等。
- 可作為視頻監控系統使用。
三、效果圖
四、相關站點
- 國內站點:https://gitee.com/feiyangqingyun/QWidgetDemo
- 國際站點:https://github.com/feiyangqingyun/QWidgetDemo
- 個人主頁:https://blog.csdn.net/feiyangqingyun
- 知乎主頁:https://www.zhihu.com/people/feiyangqingyun/
- 體驗地址:https://blog.csdn.net/feiyangqingyun/article/details/97565652
五、核心代碼
void FFmpegThread::initOption()
{
//在打開碼流前指定各種參數比如:探測時間/超時時間/最大延時等
//設置緩存大小,1080p可將值調大
av_dict_set(&options, "buffer_size", "8192000", 0);
//以tcp方式打開,如果以udp方式打開將tcp替換為udp
av_dict_set(&options, "rtsp_transport", transport.toUtf8().constData(), 0);
//設置超時斷開連接時間,單位微秒,3000000表示3秒
av_dict_set(&options, "stimeout", "3000000", 0);
//設置最大時延,單位微秒,1000000表示1秒
av_dict_set(&options, "max_delay", "1000000", 0);
//自動開啟線程數
av_dict_set(&options, "threads", "auto", 0);
//單獨對USB攝像機設置參數
if (isUsbCamera) {
//設置輸入格式
//av_dict_set(&options, "input_format", "mjpeg", 0);
//設置分辨率
QString size = QString("%1x%2").arg(videoWidth).arg(videoHeight);
av_dict_set(&options, "video_size", size.toUtf8().constData(), 0);
//設置幀率
av_dict_set(&options, "framerate", "25", 0);
}
//本地USB攝像機不需要硬解碼,強制改成回調運行和無硬解碼
//視頻流編碼類型為 AV_CODEC_ID_RAWVIDEO 像素格式為 AV_PIX_FMT_YUYV422 不經過解碼操作直接就可顯示
if (isUsbCamera) {
callback = true;
hardware = "none";
}
//沒有啟用opengl則強制改為回調
#ifndef opengl
callback = true;
#endif
//rtmp視頻流強制改成存儲成h264裸流,目前存儲成mp4還有問題
if (url.startsWith("rtmp", Qt::CaseInsensitive)) {
saveMp4 = false;
}
}
bool FFmpegThread::initInput()
{
//實例化格式處理上下文
formatCtx = avformat_alloc_context();
//設置超時回調,有些不存在的地址或者網絡不好的情況下要卡很久
formatCtx->interrupt_callback.callback = AVInterruptCallBackFun;
formatCtx->interrupt_callback.opaque = this;
//必須要有tryOpen標志位來控制超時回調,由他來控制是否繼續阻塞等待打開
tryOpen = false;
//先判斷是否是本地設備(video=設備名字符串),打開的方式不一樣
QByteArray urlData = url.toUtf8();
AVInputFormat *ifmt = NULL;
if (isUsbCamera) {
#if defined(Q_OS_WIN)
ifmt = av_find_input_format("dshow");
#elif defined(Q_OS_LINUX)
//ifmt = av_find_input_format("v4l2");
ifmt = av_find_input_format("video4linux2");
#elif defined(Q_OS_MAC)
ifmt = av_find_input_format("avfoundation");
#endif
}
int result = avformat_open_input(&formatCtx, urlData.data(), ifmt, &options);
tryOpen = true;
if (result < 0) {
qDebug() << TIMEMS << "open input error" << url;
return false;
}
//釋放設置參數
if (options != NULL) {
av_dict_free(&options);
}
//獲取流信息
result = avformat_find_stream_info(formatCtx, NULL);
if (result < 0) {
qDebug() << TIMEMS << "find stream info error";
return false;
}
return true;
}