從FFmpeg 4. 2源碼中提取dshow mjpeg code步驟

之前在https://blog.csdn.net/fengbingchun/article/details/103735560 中介紹過在Windows上通過vs2017編譯FFmpeg源碼進行單步調試的步驟,爲了進一步熟悉FFmpeg這裏以提取FFmpeg dshow mjpeg源碼爲例介紹其實現過程及注意事項:

FFmpeg是用C實現的,爲了加速,個別模塊也有對應的彙編實現。之前在https://blog.csdn.net/fengbingchun/article/details/102641967中介紹過從OpenCV中提取dshow mjpeg的步驟,但是OpenCV中只能拿到解碼後的數據不能拿到解碼前即編碼的數據,而FFmpeg可以獲取到編碼數據。

這裏僅提取與獲取dshow mjpeg編碼數據僅包括視頻不包括音頻相關的code,涉及到的對外C接口包括11個: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。主要代碼實現在libavdevice模塊,這裏按照測試code的調用順序依次說明:

1. avdevice_register_all:初始化avdevice模塊,並註冊所有輸入輸出設備。

因爲我們只是獲取dshow mjpeg的編碼數據,不會用到AVOutputFormat相關內容,因此對於outdev_list數組不需要提取ff_opengl_muxer和ff_sdl2_muxer的實現,僅需令outdev_list[]={NULL};即可。

對於indev_list數組僅需要提取ff_dshow_demuxer的code,因此僅需令indev_list[]={&ff_dshow_demuxer, NULL};即可。ff_dshow_demuxer的實現在libavdevice模塊的前綴爲dshow的那些文件中,那些文件基本上都需要提取出來。

在av_format_init_next函數中用到的數組變量muxer_list和demuxer_list全部賦值爲NULL即可。好像函數av_format_init_next什麼都沒做,也可直接去掉此函數。

2. avformat_alloc_context:分配AVFormatContext。對AVFormatContext執行malloc、memset(0)操作,以及一些默認設置。其中內部的回調函數io_open和io_close無需提取。並顯式將編解碼格式設置爲mjpeg。

3. av_find_input_format:根據輸入格式的名字即”dshow”查找AVInputFormat。其實就是將ff_dshow_demuxer賦值給了外部的AVInputFormat指針對象。

4. av_dict_set:設置一個AVDictionary項。這裏主要是設置video size。其實就是將key即“video_size”和value即”1280x720”賦值給AVDictionary。

5. avformat_open_input:打開輸入流並讀取頭信息。之前在上面第2步中對AVFormatContext作了memset全賦值爲0操作,因此avformat_open_input函數中一些if判斷始終會爲false如pb,這些code可註釋掉。這個函數裏面最重要的是AVInputFormat中的回調函數read_header,它會開啓攝像頭。

6. av_malloc:爲一個AVPacket分配內存塊。以64位對齊方式調用系統_aligned_malloc函數。

7. av_read_frame:獲取幀數據,每調用一次獲取一幀mjpeg編碼數據。這裏的主要實現函數是read_frame_internal,調用完此函數後就會jump到return_packet處,其它部分code不會走到如for內code,因此這部分code可全註釋掉。read_frame_internal實現有些複雜,不過裏面的一些if判斷會始終爲false,可以註釋掉那些code。

8. av_packet_unref:釋放數據緩衝區並將AVPacket字段重置爲默認值。每正常調用一次av_read_frame函數就應該對應調用一次av_packet_unref函數。

9. av_freep:釋放申請的內存塊,調用系統_aligned_free函數,如果AVPacket是有效的,在調用此函數前需要先調用av_packet_unref。

10. avformat_close_input:關閉打開的AVFormatContext並釋放。

11. av_dict_free:釋放爲AVDictionary分配的內存,內部調用的是av_freep函數。

注意事項:

1. C語言語法與C++的差異:

(1).C中從void*到一個具體的結構體可以直接賦值,但C++需要使用static_cast。

(2).C中從int到enum可以直接賦值,但C++需要使用static_cast。

(3).C中對結構體賦值支持”.結構體成員變量名=value”,而且成員變量名的順序可以任意,但C++需要完全按照變量名順序依次直接給出各個變量名的value,不支持”.變量名=value”。

(4).C中會將class、this、new等當作普通變量名使用,但在C++中這些屬於關鍵字,需要修改成其它名字。

(5).C中對union支持”.union成員變量名=value”,C++不支持,這裏將union修改成了struct,如AVOption中的default_val。

2. 在移植dshow.c文件到c++文件時,發現需要define宏COBJMACROS和CINTERFACE,定義完這兩個宏後,include文件ObjIdl.h和Shlwapi.h時總會有各種問題,好像定義那兩個宏後,這兩個include文件不能同時在同一個.cpp文件中,臨時解決方法是將dshow.c拆分成幾個.cpp文件。

3. 移植code中,有些分支或函數感覺執行不到,臨時使用一個宏ERROR_POS佔位,此宏定義如下,發現問題可快速定位:

#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中用到了一些系統接口,需要依賴兩個系統庫:strmiids.lib、shlwapi.lib。

5. 提取的全部code存放在:https://github.com/fengbingchun/OpenCV_Test/tree/master/src/fbc_cv

測試代碼如下:將函數中的命名空間fbc去掉即可在原始ffmpeg中運行

#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_VER
	fbc::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;
#else
	fprintf(stderr, "Error: only support windows platform\n");
	return -1;
#endif
}

生成的test.mjpeg文件可使用ffplay直接播放,執行結果如下:

GitHubhttps://github.com/fengbingchun/OpenCV_Test

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章