ffmpeg代码实现自己的demuxer

本文介绍如何使用FFmpeg代码实现自定义的mk格式解复用器,并将其加入FFmpeg解复用器链表中。该解复用器能够处理特定格式的视频文件,并通过命令行参数设置视频尺寸等属性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、概述

继上一篇文章,本文主要讲述如何用ffmpeg代码实现自己的demuxer,实现的意义一是了解ffmpeg代码结构,二是可以自己整自己的视频格式,让别人播放不了,此demuxer解复用的是上一篇文章中复用的文件。

2、代码

/* 
*本程序主要实现一个自己的demuxer并加入到demuxer链中去
*作者:缪国凯(MK) 
*821486004@qq.com 
*2015-6-3 
*/ 

#include "stdafx.h"

#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#
#ifdef __cplusplus
};
#endif

#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")
//#pragma comment(lib, "avdevice.lib")
//#pragma comment(lib, "avfilter.lib")
//#pragma comment(lib, "postproc.lib")
//#pragma comment(lib, "swresample.lib")
//#pragma comment(lib, "swscale.lib")


typedef struct MKVideoDemuxerContext {
	const AVClass *pclass;     /**< Class for private options. */
	int width, height;        /**< Integers describing video size, set by a private option. */
	char *pixel_format;       /**< Set by a private option. */
	AVRational framerate;     /**< AVRational describing framerate, set by a private option. */
} MKVideoDemuxerContext;

int mkvideo_read_probe(AVProbeData *pd)
{
	return 0;
}

int mkvideo_read_header(AVFormatContext *ctx)
{
	MKVideoDemuxerContext *s = (MKVideoDemuxerContext*)ctx->priv_data;
	enum AVPixelFormat pix_fmt;
	AVStream *st;

	st = avformat_new_stream(ctx, NULL);
	if (!st)
		return AVERROR(ENOMEM);

	st->codec->codec_type = AVMEDIA_TYPE_VIDEO;

	st->codec->codec_id = (AVCodecID)ctx->iformat->raw_codec_id;

	//这里就简单的直接赋值为420p
	pix_fmt = AV_PIX_FMT_YUV420P;

	st->time_base.num = s->framerate.den;
	st->time_base.den = s->framerate.num;
	st->pts_wrap_bits = 64;
	

	st->codec->width  = s->width;
	st->codec->height = s->height;
	st->codec->pix_fmt = pix_fmt;

	AVRational tmpRa;
	tmpRa.den = 1;
	tmpRa.num = 8;
	st->codec->bit_rate = av_rescale_q(avpicture_get_size(st->codec->pix_fmt, s->width, s->height),
		tmpRa, st->time_base);

	return 0;
}

int mkvideo_read_packet(AVFormatContext *s, AVPacket *pkt)
{
	int packet_size, ret, width, height;
	AVStream *st = s->streams[0];

	width = st->codec->width;
	height = st->codec->height;

	packet_size = avpicture_get_size(st->codec->pix_fmt, width, height);
	if (packet_size < 0)
		return -1;

	ret = av_get_packet(s->pb, pkt, packet_size);
	pkt->pts = pkt->dts = pkt->pos / packet_size;

	pkt->stream_index = 0;
	if (ret < 0)
		return ret;
	
	return 0;
}

#define OFFSET(x) offsetof(MKVideoDemuxerContext, x)
#define DEC AV_OPT_FLAG_DECODING_PARAM

const AVOption mk_options[] = 
{
	{ "video_size", "set frame size", OFFSET(width), AV_OPT_TYPE_IMAGE_SIZE, {/*.str = */NULL}, 0, 0, DEC },
	{ "pixel_format", "set pixel format", OFFSET(pixel_format), AV_OPT_TYPE_STRING, {/*.str = */(int64_t)"yuv420p"}, 0, 0, DEC },
	{ "framerate", "set frame rate", OFFSET(framerate), AV_OPT_TYPE_VIDEO_RATE, {/*.str = */(int64_t)"25"}, 0, 0, DEC },
	{ NULL },
};

static const AVClass mk_demuxer_class = {
	/*.class_name = */"mk video demuxer",
	/*.item_name  = */av_default_item_name,
	/*.option     = */mk_options,
	/*.version    = */LIBAVUTIL_VERSION_INT,
};

AVInputFormat ff_mk_demuxer = {
	/*.name           = */"mk",
	/*.long_name      = */"mk video",
	/*.flags          = */AVFMT_GENERIC_INDEX,
	/*.extensions     = */"mk",
	/*.codec_tag      = */NULL,
	/*.priv_class     = */&mk_demuxer_class,
	/*.next			  = */NULL,
	/*.raw_codec_id   = */AV_CODEC_ID_RAWVIDEO,
	/*.priv_data_size = */sizeof(MKVideoDemuxerContext),
	/*.read_probe	  = */NULL,//mkvideo_read_probe,
	/*.read_header    = */mkvideo_read_header,
	/*.read_packet    = */mkvideo_read_packet,
};


void help()
{
	printf("**********************************************\n");
	printf("Usage:\n");
	printf("    MyMuxer [inputfile.mk] [outputfile] [size]\n");
	printf("\n");
	printf("Examples: \n");
	printf("    MyMuxer a.mk a.avi 1280x720\n");
	printf("**********************************************\n");  
}


int _tmain(int argc, _TCHAR* argv[])
{
	if(argc < 4 || (!strcmp(argv[1],"--help")))
	{
		help();
		return 0;
	}

	AVFormatContext *in_fxt = NULL, *out_fxt = NULL;
	AVStream *out_stream = NULL;
	int video_index = -1;

	av_register_all();
	av_register_input_format(&ff_mk_demuxer);

	AVDictionary *param = 0;
	av_dict_set(¶m, "video_size", argv[3], 0);
	if (avformat_open_input(&in_fxt, argv[1], NULL, ¶m) < 0)
	{
		printf("can not open the input file context!\n");
		goto end;
	}
	if (avformat_find_stream_info(in_fxt, NULL) < 0)
	{
		printf("can not find the stream info!\n");
		goto end;
	}

	if(avformat_alloc_output_context2(&out_fxt, NULL, NULL, argv[2]) < 0)
	{
		printf("can not alloc output context!\n");
		goto end;
	}

	for (int i = 0; i < in_fxt->nb_streams; i++)
	{
		if (in_fxt->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			//open decoder
			if(0 > avcodec_open2(in_fxt->streams[i]->codec, avcodec_find_decoder(in_fxt->streams[i]->codec->codec_id), NULL))
			{
				printf("can not find or open decoder!\n");
				goto end;
			}
			video_index = i;
			//new stream
			out_stream = avformat_new_stream(out_fxt, NULL);
			if (!out_stream)
			{
				printf("can not new stream for output!\n");
				goto end;
			}
			//set codec context param
			out_stream->codec->codec = avcodec_find_encoder(out_fxt->oformat->video_codec);
			out_stream->codec->height = in_fxt->streams[i]->codec->height;
			out_stream->codec->width = in_fxt->streams[i]->codec->width;

			out_stream->codec->time_base = in_fxt->streams[i]->time_base;
			//out_stream->codec->time_base.den = 25;

			out_stream->codec->sample_aspect_ratio = in_fxt->streams[i]->codec->sample_aspect_ratio;			
			out_stream->codec->pix_fmt = in_fxt->streams[i]->codec->pix_fmt;

			out_stream->avg_frame_rate.den = out_stream->codec->time_base.num;
			out_stream->avg_frame_rate.num = out_stream->codec->time_base.den;
			if (!out_stream->codec->codec)
			{
				printf("can not find the encoder!\n");
				goto end;
			}
			if ((avcodec_open2(out_stream->codec, out_stream->codec->codec, NULL)) < 0)
			{
				printf("can not open the encoder\n");
				goto end;
			}
			if (out_fxt->oformat->flags & AVFMT_GLOBALHEADER)
				out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
			break;
		}
	}

	if (-1 == video_index)
	{
		printf("found no video stream in input file!\n");
		goto end;
	}

	if (!(out_fxt->oformat->flags & AVFMT_NOFILE))
	{
		if(avio_open(&out_fxt->pb, argv[2], AVIO_FLAG_WRITE) < 0)
		{
			printf("can not open output file handle!\n");
			goto end;
		}
	}

	if(avformat_write_header(out_fxt, NULL) < 0)
	{
		printf("can not write the header of the output file!\n");
		goto end;
	}

	AVPacket pkt_in, pkt_out;
	AVFrame *frame;
	frame = av_frame_alloc();
	av_init_packet(&pkt_in);
	av_init_packet(&pkt_out);
	int got_frame, got_picture;
	int i = 0, frame_index = 0;
	while(1)
	{
		got_frame = -1;
		got_picture = -1;
		if (av_read_frame(in_fxt, &pkt_in) < 0)
		{
			break;
		}
		if (avcodec_decode_video2(in_fxt->streams[video_index]->codec, frame, &got_frame, &pkt_in) < 0)
		{
			printf("can not decoder a frame");
			break;
		}
		av_free_packet(&pkt_in);

		if (got_frame)
		{
			frame->pts = i++;
			pkt_out.data = NULL;//主要这里必须自己初始化,或者必须置为null,不然ff_alloc_packet2函数会报错
			pkt_out.size = 0;
			if (avcodec_encode_video2(out_stream->codec, &pkt_out, frame, &got_picture) < 0)
			{
				printf("can not encode a frame!\n");
				break;
			}

			if (got_picture)
			{
				printf("Succeed to encode frame: %5d\tsize:%5d\n",frame_index,pkt_out.size);
				pkt_out.stream_index = out_stream->index;
				frame_index++;
				av_write_frame(out_fxt, &pkt_out);
				av_free_packet(&pkt_out);
			}
		}
	}
	av_frame_free(&frame);

	av_write_trailer(out_fxt);

	//clean
	avcodec_close(out_stream->codec);
	avcodec_close(out_fxt->streams[video_index]->codec);
end:
	avformat_close_input(&in_fxt);

	if (out_fxt && !(out_fxt->oformat->flags & AVFMT_NOFILE))
	{
		avio_close(out_fxt->pb);
	}
	avformat_free_context(out_fxt);

	return 0;
}

3、解释

简单解释下原理:
首先自己实现一个AVInputFormat,然后把这个demuxer加入到链表中去,其他的根本不用自己管了,需要注意的是,我们解析的其实是yuv格式,所以需要输入参数来确定分辨率,那么这个输入参数是如何传递到mkvideo_read_header的,在avformat_open_input中如果你设置了第四个参数AVDictionary **options,在函数过程中会把参数设置到AVFormatContext的priv_data中,然后会根据你在AVInputFormat中设置的priv_class中的mk_options来解析你的输入参数,如下:
设置参数:
AVDictionary *param = 0;
	av_dict_set(& param, "video_size", argv[3], 0);
	if (avformat_open_input(&in_fxt, argv[1], NULL, <span style="font-family: Arial, Helvetica, sans-serif;">& param</span>) < 0)
	{
		printf("can not open the input file context!\n");
		goto end;
	}
找到你在AVInputFormat中定义的priv_class:
static const AVClass mk_demuxer_class = {
	/*.class_name = */"mk video demuxer",
	/*.item_name  = */av_default_item_name,
	/*.option     = */mk_options,
	/*.version    = */LIBAVUTIL_VERSION_INT,
};
根据option来解析参数:
const AVOption mk_options[] = 
{
	{ "video_size", "set frame size", OFFSET(width), AV_OPT_TYPE_IMAGE_SIZE, {/*.str = */NULL}, 0, 0, DEC },
	{ "pixel_format", "set pixel format", OFFSET(pixel_format), AV_OPT_TYPE_STRING, {/*.str = */(int64_t)"yuv420p"}, 0, 0, DEC },
	{ "framerate", "set frame rate", OFFSET(framerate), AV_OPT_TYPE_VIDEO_RATE, {/*.str = */(int64_t)"25"}, 0, 0, DEC },
	{ NULL },
};
然后会调用AVInputFormat定义的read_header,(源码节选自ffmpeg的avformat_open_input函数)如下:
if (s->iformat->priv_data_size > 0) {
        if (!(s->priv_data = av_mallocz(s->iformat->priv_data_size))) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
        if (s->iformat->priv_class) {
            *(const AVClass **) s->priv_data = s->iformat->priv_class;
            av_opt_set_defaults(s->priv_data);
            if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0)
                goto fail;
        }
    }

    /* e.g. AVFMT_NOFILE formats will not have a AVIOContext */
    if (s->pb)
        ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta, 0);

    if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
        if ((ret = s->iformat->read_header(s)) < 0)
            goto fail;
至此头信息基本解析完毕,并已经创建了流后。

其他的都比较简单在这里就不多说了。

最后验证一下,输入启动参数,运行程序,转一个视频出来,能正常播放,ok,成功。
下一步,把这个demuxer加入ffmpeg的源码中,使ffmpeg.exe能适应这个demuxer。




<think>好的,用户想了解如何在Python中对字典类型进行赋值,需要给出方法和示例。首先,我需要回忆Python字典的基本操作和相关方法,结合系统提供的引用资料来组织答案。 首先,系统引用了四个资料,其中引用[1]提到字典赋值的基本方法,引用[4]详细讲解了setdefault方法的使用,引用[3]涉及自定义字典类型,引用[2]介绍字典的实现原理。用户的问题集中在赋值方法,所以重点应放在基本赋值、setdefault、update等方法上。 第一步,解释直接赋值,即通过键直接添加或修改键值对。例如dic['key'] = value。需要说明如果键存在则更新,不存在则添加。引用[1]中的例子可能有用,这里可以引用。 然后,提到setdefault方法,这个方法在引用[4]中有详细示例,当键不存在时添加并返回默认值,存在时返回现有值。需要举例子说明两种情况,像用户提供的代码中的例子。 接下来是update方法,可以批量添加或更新键值对。可以给出两种使用方式,比如传入另一个字典或键值对的可迭代对象。引用中可能没有直接提到update的例子,但属于常见方法,需要补充。 此外,用户提供的引用[3]展示了通过自定义字典类来扩展赋值行为,比如在赋值时进行验证。虽然用户的问题可能主要关注基本方法,但作为进阶内容可以简要提及,以展示更多可能性。但根据用户问题,可能不需要深入,所以放在最后作为扩展。 需要确保每个方法都有明确的示例代码,并引用相关的资料作为支持。比如直接赋值引用[1],setdefault引用[4],自定义类引用[3]。同时,注意系统级指令中的格式要求,行内数学表达式使用$...$,但这里可能用不到数学公式,所以主要关注代码块和引用标识。 最后,生成相关问题需要考虑用户可能的后续疑问,比如不同赋值方法的区别、处理键冲突的方法、批量赋值、安全性考虑等。根据提供的引用,可能涉及性能(引用[2]的实现原理)、自定义扩展(引用[3])和具体方法的应用场景(引用[4]的setdefault)。 需要检查回答结构是否清晰,是否覆盖所有主要赋值方法,示例是否正确,引用是否正确标注。例如,在提到直接赋值时,引用[1]的指南;在setdefault部分,引用[4]的例子;在自定义类时,引用[3]的示例代码。 可能还需要注意术语的准确性,比如“键值对”、“无序”、“可变类型”等,根据引用中的描述保持一致。同时,确保代码示例正确,比如在直接赋值中展示添加和修改两种情况,setdefault的两种情况,update的不同参数形式。 总结回答的结构: 1. 直接赋值:使用方括号语法 2. setdefault方法:存在与否的不同处理 3. update方法:批量更新 4. 扩展方法:自定义字典类(可选) 每个部分给出示例代码,并在适当位置添加引用。例如,直接赋值引用[^1],setdefault引用[^4],自定义类引用[^3]。可能引用[^2]在实现原理方面,但用户问题主要关于赋值方法,所以可能不在此处引用。 最后生成的相关问题需要围绕字典操作,如不同赋值方法的区别、处理键不存在的情况、批量更新、性能考虑等。根据已有的引用和内容,设计相关问题,如深浅拷贝、setdefault与直接赋值的对比、update的应用场景等。</think>在Python中对字典类型进行赋值主要有以下几种方法,不同场景下可灵活选用: ### 一、直接键值赋值 通过方括号语法直接对键进行赋值,若键不存在则新增键值对,存在则更新值。这是最基础的操作方式。 ```python # 创建空字典 person = {} # 新增键值对 person['name'] = 'Alice' person['age'] = 25 # 此时字典为 {'name': 'Alice', 'age': 25} # 更新已存在键的值 person['age'] = 26 # 结果变为 {'name': 'Alice', 'age': 26} ``` ### 二、setdefault方法 该方法实现**安全赋值**,当键不存在时自动创建并返回默认值,存在则返回当前值不修改原字典。 ```python scores = {'Math': 90} # 键存在时返回原值 result = scores.setdefault('Math', 100) print(result) # 输出90 print(scores) # {'Math': 90} # 键不存在时创建新键值 result = scores.setdefault('English', 85) print(result) # 输出85 print(scores) # {'Math': 90, 'English': 85} ``` ### 三、update方法 用于**批量合并字典**,支持传入字典对象或键值对的可迭代对象。 ```python base = {'a': 1, 'b': 2} # 合并字典 base.update({'b': 20, 'c': 3}) print(base) # {'a': 1, 'b': 20, 'c': 3} # 合并可迭代对象 base.update([('d', 4), ('e', 5)]) print(base) # {'a': 1, 'b': 20, 'c': 3, 'd': 4, 'e': 5} ``` ### 四、扩展方法:自定义字典类 通过继承`dict`类型实现赋值时的自定义验证逻辑,例如引用[^3]中的验证字典: ```python class ValidatedDict(dict): def __setitem__(self, key, value): if not isinstance(value, int): raise TypeError("只允许整数类型值") super().__setitem__(key, value) valid_dict = ValidatedDict() valid_dict['score'] = 95 # 正常赋值 valid_dict['price'] = '99' # 触发TypeError异常 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dancing_night

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值