从FFmpeg 4. 2源码中提取dshow mjpeg code步骤
之前在https://blog.csdn.net/fengbingchun/article/details/103735560 中介紹過在Windows上通過vs2017編譯FFmpeg源碼進(jìn)行單步調(diào)試的步驟,為了進(jìn)一步熟悉FFmpeg這里以提取FFmpeg dshow mjpeg源碼為例介紹其實(shí)現(xiàn)過程及注意事項(xiàng):
FFmpeg是用C實(shí)現(xiàn)的,為了加速,個(gè)別模塊也有對應(yīng)的匯編實(shí)現(xiàn)。之前在https://blog.csdn.net/fengbingchun/article/details/102641967中介紹過從OpenCV中提取dshow mjpeg的步驟,但是OpenCV中只能拿到解碼后的數(shù)據(jù)不能拿到解碼前即編碼的數(shù)據(jù),而FFmpeg可以獲取到編碼數(shù)據(jù)。
這里僅提取與獲取dshow mjpeg編碼數(shù)據(jù)僅包括視頻不包括音頻相關(guān)的code,涉及到的對外C接口包括11個(gè):avdevice_register_all、avformat_alloc_context、av_find_input_format、av_dict_set、avformat_open_input、av_malloc、av_read_frame、av_packet_unref、av_freep、avformat_close_input、av_dict_free。主要代碼實(shí)現(xiàn)在libavdevice模塊,這里按照測試code的調(diào)用順序依次說明:
1. avdevice_register_all:初始化avdevice模塊,并注冊所有輸入輸出設(shè)備。
因?yàn)槲覀冎皇谦@取dshow mjpeg的編碼數(shù)據(jù),不會用到AVOutputFormat相關(guān)內(nèi)容,因此對于outdev_list數(shù)組不需要提取ff_opengl_muxer和ff_sdl2_muxer的實(shí)現(xiàn),僅需令outdev_list[]={NULL};即可。
對于indev_list數(shù)組僅需要提取ff_dshow_demuxer的code,因此僅需令indev_list[]={&ff_dshow_demuxer, NULL};即可。ff_dshow_demuxer的實(shí)現(xiàn)在libavdevice模塊的前綴為dshow的那些文件中,那些文件基本上都需要提取出來。
在av_format_init_next函數(shù)中用到的數(shù)組變量muxer_list和demuxer_list全部賦值為NULL即可。好像函數(shù)av_format_init_next什么都沒做,也可直接去掉此函數(shù)。
2. avformat_alloc_context:分配AVFormatContext。對AVFormatContext執(zhí)行malloc、memset(0)操作,以及一些默認(rèn)設(shè)置。其中內(nèi)部的回調(diào)函數(shù)io_open和io_close無需提取。并顯式將編解碼格式設(shè)置為mjpeg。
3. av_find_input_format:根據(jù)輸入格式的名字即”dshow”查找AVInputFormat。其實(shí)就是將ff_dshow_demuxer賦值給了外部的AVInputFormat指針對象。
4. av_dict_set:設(shè)置一個(gè)AVDictionary項(xiàng)。這里主要是設(shè)置video size。其實(shí)就是將key即“video_size”和value即”1280x720”賦值給AVDictionary。
5. avformat_open_input:打開輸入流并讀取頭信息。之前在上面第2步中對AVFormatContext作了memset全賦值為0操作,因此avformat_open_input函數(shù)中一些if判斷始終會為false如pb,這些code可注釋掉。這個(gè)函數(shù)里面最重要的是AVInputFormat中的回調(diào)函數(shù)read_header,它會開啟攝像頭。
6. av_malloc:為一個(gè)AVPacket分配內(nèi)存塊。以64位對齊方式調(diào)用系統(tǒng)_aligned_malloc函數(shù)。
7. av_read_frame:獲取幀數(shù)據(jù),每調(diào)用一次獲取一幀mjpeg編碼數(shù)據(jù)。這里的主要實(shí)現(xiàn)函數(shù)是read_frame_internal,調(diào)用完此函數(shù)后就會jump到return_packet處,其它部分code不會走到如for內(nèi)code,因此這部分code可全注釋掉。read_frame_internal實(shí)現(xiàn)有些復(fù)雜,不過里面的一些if判斷會始終為false,可以注釋掉那些code。
8. av_packet_unref:釋放數(shù)據(jù)緩沖區(qū)并將AVPacket字段重置為默認(rèn)值。每正常調(diào)用一次av_read_frame函數(shù)就應(yīng)該對應(yīng)調(diào)用一次av_packet_unref函數(shù)。
9. av_freep:釋放申請的內(nèi)存塊,調(diào)用系統(tǒng)_aligned_free函數(shù),如果AVPacket是有效的,在調(diào)用此函數(shù)前需要先調(diào)用av_packet_unref。
10. avformat_close_input:關(guān)閉打開的AVFormatContext并釋放。
11. av_dict_free:釋放為AVDictionary分配的內(nèi)存,內(nèi)部調(diào)用的是av_freep函數(shù)。
注意事項(xiàng):
1. C語言語法與C++的差異:
(1).C中從void*到一個(gè)具體的結(jié)構(gòu)體可以直接賦值,但C++需要使用static_cast。
(2).C中從int到enum可以直接賦值,但C++需要使用static_cast。
(3).C中對結(jié)構(gòu)體賦值支持”.結(jié)構(gòu)體成員變量名=value”,而且成員變量名的順序可以任意,但C++需要完全按照變量名順序依次直接給出各個(gè)變量名的value,不支持”.變量名=value”。
(4).C中會將class、this、new等當(dāng)作普通變量名使用,但在C++中這些屬于關(guān)鍵字,需要修改成其它名字。
(5).C中對union支持”.union成員變量名=value”,C++不支持,這里將union修改成了struct,如AVOption中的default_val。
2. 在移植dshow.c文件到c++文件時(shí),發(fā)現(xiàn)需要define宏COBJMACROS和CINTERFACE,定義完這兩個(gè)宏后,include文件ObjIdl.h和Shlwapi.h時(shí)總會有各種問題,好像定義那兩個(gè)宏后,這兩個(gè)include文件不能同時(shí)在同一個(gè).cpp文件中,臨時(shí)解決方法是將dshow.c拆分成幾個(gè).cpp文件。
3. 移植code中,有些分支或函數(shù)感覺執(zhí)行不到,臨時(shí)使用一個(gè)宏ERROR_POS占位,此宏定義如下,發(fā)現(xiàn)問題可快速定位:
#define ERROR_POS \fprintf(stderr, "Error, It should not execute to this position: file: %s, func: %s, line: %d\n", __FILE__, __FUNCTION__, __LINE__); \abort();
4. code中用到了一些系統(tǒng)接口,需要依賴兩個(gè)系統(tǒng)庫:strmiids.lib、shlwapi.lib。
5. 提取的全部code存放在:https://github.com/fengbingchun/OpenCV_Test/tree/master/src/fbc_cv
測試代碼如下:將函數(shù)中的命名空間fbc去掉即可在原始ffmpeg中運(yùn)行
#include <fstream>
#include <iostream>
#include "fbc_cv_funset.hpp"
#include <videocapture.hpp>
#include <opencv2/opencv.hpp>
#include <avdevice.hpp>
#include <avformat.hpp>
#include <avutil.hpp>
#include <avmem.hpp>int test_ffmpeg_dshow_mjpeg()
{
#ifdef _MSC_VERfbc::avdevice_register_all();fbc::AVFormatContext* format_context = fbc::avformat_alloc_context();fbc::AVCodecID id = fbc::AV_CODEC_ID_MJPEG;format_context->video_codec_id = id;fbc::AVInputFormat* input_format = fbc::av_find_input_format("dshow");if (!input_format) {fprintf(stderr, "Error: input format is not supported\n");return -1;}fbc::AVDictionary* dict = nullptr;int ret = fbc::av_dict_set(&dict, "video_size", "1280x720", 0);if (ret < 0) {fprintf(stderr, "Error: fail to av_dict_set: %d\n", ret);return -1;}ret = fbc::avformat_open_input(&format_context, "video=Integrated Webcam", input_format, &dict);if (ret != 0) {fprintf(stderr, "Error: fail to avformat_open_input: %d\n", ret);return -1;}int video_stream_index = -1;for (unsigned int i = 0; i < format_context->nb_streams; ++i) {const fbc::AVStream* stream = format_context->streams[i];if (stream->codecpar->codec_type == fbc::AVMEDIA_TYPE_VIDEO) {video_stream_index = i;fprintf(stdout, "type of the encoded data: %d, dimensions of the video frame in pixels: width: %d, height: %d, pixel format: %d\n",stream->codecpar->codec_id, stream->codecpar->width, stream->codecpar->height, stream->codecpar->format);//break;}}if (video_stream_index == -1) {fprintf(stderr, "Error: no video stream\n");return -1;}fbc::AVCodecParameters* codecpar = format_context->streams[video_stream_index]->codecpar;if (codecpar->codec_id != id) {fprintf(stderr, "Error: this test code only support mjpeg encode: %d\n", codecpar->codec_id);return -1;}fbc::AVPacket* packet = (fbc::AVPacket*)fbc::av_malloc(sizeof(fbc::AVPacket));if (!packet) {fprintf(stderr, "Error: fail to alloc\n");return -1;}std::ofstream out("E:/GitCode/OpenCV_Test/test_images/test.mjpeg", std::ios::binary | std::ios::out);if (!out.is_open()) {fprintf(stderr, "Error, fail to open file\n");return -1;}int count = 0;while (count++ < 100) {ret = fbc::av_read_frame(format_context, packet);if (ret >= 0 && packet->stream_index == video_stream_index && packet->size > 0) {fprintf(stdout, "packet size: %d\n", packet->size);out.write((char*)packet->data, packet->size);}else if (ret < 0 || packet->size <= 0) {fprintf(stderr, "Warnint: fail to av_read_frame: %d, packet size: %d\n", ret, packet->size);continue;}fbc::av_packet_unref(packet);}fbc::av_freep(packet);fbc::avformat_close_input(&format_context);fbc::av_dict_free(&dict);out.close();fprintf(stdout, "test finish\n");return 0;
#elsefprintf(stderr, "Error: only support windows platform\n");return -1;
#endif
}
生成的test.mjpeg文件可使用ffplay直接播放,執(zhí)行結(jié)果如下:
GitHub:https://github.com/fengbingchun/OpenCV_Test
總結(jié)
以上是生活随笔為你收集整理的从FFmpeg 4. 2源码中提取dshow mjpeg code步骤的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++11中std::async的使用
- 下一篇: Doxygen使用介绍