ffmpeg用代码实现自己的muxer

本文介绍如何自定义muxer并将其加入ffmpeg的AVOutputFormat链表,实现将视频转换为特定格式的功能。通过理解ffmpeg的格式匹配机制和流程,作者实现了自己的muxer,使得ffmpeg能够直接调用并进行视频格式转换。本文详细解释了基本原理、代码实现、格式匹配过程以及关键函数的调用,最后验证了自定义muxer的有效性。

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

1、概述

本代码实现了一个muxer并加入到ffmpeg的AVOutputFormat 链表中去,使代码能直接调用。实现的意义一是了解ffmpeg代码结构,二是可以自己整自己的视频格式,让别人播放不了。

2、代码

简单看下代码:

/*
*本程序主要实现一个自己的muxer并加入到muxer链中去,使其可用,只有视频
*作者:缪国凯(MK)
*821486004@qq.com
*2015-6-2
*/

#include "stdafx.h"

#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.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")

int mk_write_header(struct AVFormatContext *fmt)
{
	//这些地方就可以加入自己的格式定义
	return 0;
}

int mk_write_packet(struct AVFormatContext *fmt, AVPacket *pkt)
{
	avio_write(fmt->pb, pkt->data, pkt->size);//简单的用file协议写文件
	return 0;
}

int mk_write_trailer(struct AVFormatContext *fmt)
{
	//这些地方就可以加入自己的格式定义
	return 0;
}

AVOutputFormat ff_mk_muxer = 
{
	/*.name			= */"mk",
	/*.long_name		= */"mk (MK Video Container)",
	/*.mime_type		= */"mkvideo/x-msvideo",
	/*.extensions		= */"mk",

	/*.audio_codec		= */AV_CODEC_ID_NONE,
	/*.video_codec		= */AV_CODEC_ID_RAWVIDEO,//这里先用ffmpeg自带的yuv编码器,以后改成自己的
	/*.subtitle_codec	= */AV_CODEC_ID_NONE,

	/*.flags		= */AVFMT_NOTIMESTAMPS,
	/*.codec_tag		= */NULL,
	/*.priv_class		= */NULL,
	/*.next				= */NULL,
	/*.priv_data_size	= */0,	

	/*.write_header		= */mk_write_header,
	/*.write_packet		= */mk_write_packet,
	/*.write_trailer	= */mk_write_trailer,
};

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

int _tmain(int argc, _TCHAR* argv[])
{
	if(argc < 3 || (!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_output_format(&ff_mk_muxer);//把自己的muxer加入链表

	if (avformat_open_input(&in_fxt, argv[1], NULL, NULL) < 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.num = in_fxt->streams[i]->avg_frame_rate.den;
			out_stream->codec->time_base.den = in_fxt->streams[i]->avg_frame_rate.num;

			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;
			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、解释

简单说下基本原理。

首先自己实现一个AVOutputFormat,然后把这个muxer加入到链表中去,其他的根本不用自己管了,ffmpeg这套框架做的的确很漂亮,会一层层的掉用到你的muxer来,函数avformat_alloc_output_context2会根据输入的格式或后缀来在AVOutputFormat链中查找最适合的muxer,并把这个muxer的指针用oformat保存,下面看看格式匹配的代码:

AVOutputFormat *av_guess_format(const char *short_name, const char *filename,
                                const char *mime_type)
{
    AVOutputFormat *fmt = NULL, *fmt_found;
    int score_max, score;

    /* specific test for image sequences */
#if CONFIG_IMAGE2_MUXER
    if (!short_name && filename &&
        av_filename_number_test(filename) &&
        ff_guess_image2_codec(filename) != AV_CODEC_ID_NONE) {
        return av_guess_format("image2", NULL, NULL);
    }
#endif
    /* Find the proper file type. */
    fmt_found = NULL;
    score_max = 0;
    while ((fmt = av_oformat_next(fmt))) {
        score = 0;
        if (fmt->name && short_name && av_match_name(short_name, fmt->name))
            score += 100;
        if (fmt->mime_type && mime_type && !strcmp(fmt->mime_type, mime_type))
            score += 10;
        if (filename && fmt->extensions &&
            av_match_ext(filename, fmt->extensions)) {
            score += 5;
        }
        if (score > score_max) {
            score_max = score;
            fmt_found = fmt;
        }
    }
    return fmt_found;
}
遍历整个链表,得分最高的就是最匹配的格式,name和传入的shotname(就是传入的格式)匹配得分100,mime_type匹配得分10,extensions和传入的文件名后缀匹配得分5,是不是很有意思。

再来看看avformat_write_header的代码:

int avformat_write_header(AVFormatContext *s, AVDictionary **options)
{
    int ret = 0;

    if (ret = init_muxer(s, options))
        return ret;

    if (s->oformat->write_header) {
        ret = s->oformat->write_header(s);
        if (ret >= 0 && s->pb && s->pb->error < 0)
            ret = s->pb->error;
        if (ret < 0)
            return ret;
        if (s->flush_packets && s->pb && s->pb->error >= 0 && s->flags & AVFMT_FLAG_FLUSH_PACKETS)
            avio_flush(s->pb);
    }

    if ((ret = init_pts(s)) < 0)
        return ret;

    if (s->avoid_negative_ts < 0) {
        av_assert2(s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_AUTO);
        if (s->oformat->flags & (AVFMT_TS_NEGATIVE | AVFMT_NOTIMESTAMPS)) {
            s->avoid_negative_ts = 0;
        } else
            s->avoid_negative_ts = AVFMT_AVOID_NEG_TS_MAKE_NON_NEGATIVE;
    }

    return 0;
}
会调用oformat的write_header。

再看av_write_frame的代码:

int av_write_frame(AVFormatContext *s, AVPacket *pkt)
{
    int ret;

    ret = check_packet(s, pkt);
    if (ret < 0)
        return ret;

    if (!pkt) {
        if (s->oformat->flags & AVFMT_ALLOW_FLUSH) {
            ret = s->oformat->write_packet(s, NULL);
            if (s->flush_packets && s->pb && s->pb->error >= 0 && s->flags & AVFMT_FLAG_FLUSH_PACKETS)
                avio_flush(s->pb);
            if (ret >= 0 && s->pb && s->pb->error < 0)
                ret = s->pb->error;
            return ret;
        }
        return 1;
    }

    ret = compute_pkt_fields2(s, s->streams[pkt->stream_index], pkt);

    if (ret < 0 && !(s->oformat->flags & AVFMT_NOTIMESTAMPS))
        return ret;

    ret = write_packet(s, pkt);
    if (ret >= 0 && s->pb && s->pb->error < 0)
        ret = s->pb->error;

    if (ret >= 0)
        s->streams[pkt->stream_index]->nb_frames++;
    return ret;
}

会调用oformat的write_packet。

最后看看av_write_trailer的代码:

int av_write_trailer(AVFormatContext *s)
{
    int ret, i;

    for (;; ) {
        AVPacket pkt;
        ret = interleave_packet(s, &pkt, NULL, 1);
        if (ret < 0)
            goto fail;
        if (!ret)
            break;

        ret = write_packet(s, &pkt);
        if (ret >= 0)
            s->streams[pkt.stream_index]->nb_frames++;

        av_free_packet(&pkt);

        if (ret < 0)
            goto fail;
        if(s->pb && s->pb->error)
            goto fail;
    }

fail:
    if (s->oformat->write_trailer)
        if (ret >= 0) {
        ret = s->oformat->write_trailer(s);
        } else {
            s->oformat->write_trailer(s);
        }

    if (s->pb)
       avio_flush(s->pb);
    if (ret == 0)
       ret = s->pb ? s->pb->error : 0;
    for (i = 0; i < s->nb_streams; i++) {
        av_freep(&s->streams[i]->priv_data);
        av_freep(&s->streams[i]->index_entries);
    }
    if (s->oformat->priv_class)
        av_opt_free(s->priv_data);
    av_freep(&s->priv_data);
    return ret;
}

会调用oformat的write_trailer。


所以说,只要你按照ffmpeg的格式去定义muxer并加入链表,其他的都不用管了。

 最后验证一下你的结果,运行程序随便转一个结果出来,把后缀.mk改为.yuv,用yuv播放器放一下,ok了,大功告成。下一篇介绍如何把这个muxer加入ffmpeg源码,使ffmpeg命令行可以用这个muxer。


<think>好的,用户想要了解如何使用FFmpeg API实现RTP推流的完整代码。我需要先理清楚FFmpeg中与推流相关的关键步骤,并确保覆盖所有必要的API调用。 首先,用户可能已经对FFmpeg有一定了解,但可能不熟悉具体的API使用。我需要从基本流程入手,比如初始化、编解码器设置、输出格式配置等。需要分步骤讲解,确保代码结构清晰。 接下来,考虑RTP推流的特殊性。RTP通常需要特定的传输参数,比如sdp文件的生成,可能需要在代码中处理这些细节。用户可能希望知道如何正确设置sdp信息,或者是否需要手动创建sdp文件。 然后,检查FFmpeg的API变化。不同版本的FFmpeg可能有不同的函数或参数,需要确保提供的代码示例是兼容当前主流版本的。比如,avcodec_encode_video2已经被弃用,现在推荐使用avcodec_send_frame和avcodec_receive_packet。 另外,用户可能需要完整的代码示例,而不仅仅是片段。因此,我需要提供一个完整的C程序结构,包括头文件引入、变量声明、错误处理等。同时,要确保代码中的每一步都有注释,方便用户理解。 还要考虑输入文件的格式和编码问题。用户提供的输入文件必须是FFmpeg支持的格式,并且需要正确设置编码器参数,比如像素格式、码率、GOP大小等。如果用户输入的文件格式不匹配,可能会导致错误,所以需要在代码中进行检查和处理。 另外,网络传输方面,RTP推流的URL格式可能需要特定协议,比如使用rtp://后面跟IP和端口。需要确保输出格式正确,并且使用正确的muxer,比如RTP需要指定fifo_size以避免延迟问题。 错误处理也是关键。每个FFmpeg函数调用后都应该检查返回值,并给出有意义的错误信息,帮助用户调试可能的问题,比如文件打开失败、编码器找不到等。 最后,需要提醒用户编译时的链接选项,确保正确链接FFmpeg的库文件。同时,提供运行时的命令行示例,包括生成SDP文件的方法,这样用户可以直接测试推流效果。 总结一下,我需要构建一个结构清晰、步骤分明的代码示例,涵盖初始化、打开输入、输出设置、编码、封包、发送等所有关键步骤,并辅以必要的注释和错误处理。同时,指出可能遇到的陷阱和注意事项,确保用户能够顺利实现RTP推流功能。</think>以下是一个基于FFmpeg API实现RTP推流的完整C语言代码示例,包含关键步骤的中文注释: ```c #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavutil/opt.h> int main() { AVFormatContext *ifmt_ctx = NULL; // 输入文件上下文 AVFormatContext *ofmt_ctx = NULL; // 输出流上下文 AVPacket pkt; int ret; // 注册所有组件 av_register_all(); avformat_network_init(); // 1. 打开输入文件 if ((ret = avformat_open_input(&ifmt_ctx, "input.mp4", NULL, NULL)) < 0) { fprintf(stderr, "无法打开输入文件: %s\n", av_err2str(ret)); return -1; } // 获取流信息 if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0) { fprintf(stderr, "无法获取流信息: %s\n", av_err2str(ret)); return -1; } // 2. 创建输出上下文 avformat_alloc_output_context2(&ofmt_ctx, NULL, "rtp", "rtp://127.0.0.1:5004"); if (!ofmt_ctx) { fprintf(stderr, "无法创建输出上下文\n"); return -1; } // 3. 复制流配置 for (int i = 0; i < ifmt_ctx->nb_streams; i++) { AVStream *in_stream = ifmt_ctx->streams[i]; AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec); if (!out_stream) { fprintf(stderr, "创建输出流失败\n"); return -1; } avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar); out_stream->codecpar->codec_tag = 0; } // 4. 打开输出流 if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) { ret = avio_open(&ofmt_ctx->pb, ofmt_ctx->filename, AVIO_FLAG_WRITE); if (ret < 0) { fprintf(stderr, "无法打开输出流: %s\n", av_err2str(ret)); return -1; } } // 5. 写文件头 ret = avformat_write_header(ofmt_ctx, NULL); if (ret < 0) { fprintf(stderr, "写文件头失败: %s\n", av_err2str(ret)); return -1; } // 6. 推流循环 while (1) { if ((ret = av_read_frame(ifmt_ctx, &pkt)) < 0) break; // 转换时间基 AVStream *in_stream = ifmt_ctx->streams[pkt.stream_index]; AVStream *out_stream = ofmt_ctx->streams[pkt.stream_index]; av_packet_rescale_ts(&pkt, in_stream->time_base, out_stream->time_base); // 发送数据包 ret = av_interleaved_write_frame(ofmt_ctx, &pkt); if (ret < 0) { fprintf(stderr, "发送数据包失败: %s\n", av_err2str(ret)); break; } av_packet_unref(&pkt); } // 7. 写文件尾 av_write_trailer(ofmt_ctx); // 清理资源 avformat_close_input(&ifmt_ctx); if (ofmt_ctx && !(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) avio_closep(&ofmt_ctx->pb); avformat_free_context(ofmt_ctx); return 0; } ``` 关键步骤说明: 1. **输入处理**:打开输入文件并解析媒体流信息 2. **输出配置**:创建RTP输出上下文,指定目标地址(IP+端口) 3. **流映射**:复制输入流的编码参数到输出流 4. **网络连接**:通过avio_open建立网络连接 5. **封装协议头**:写入RTP协议头信息 6. **数据转发**:循环读取数据包并进行时间基转换后发送 7. **资源释放**:正确关闭所有上下文和网络连接 编译命令示例: ```bash gcc -o rtp_streamer rtp_streamer.c -lavformat -lavcodec -lavutil -lswscale ``` 运行命令: ```bash ./rtp_streamer ``` 注意事项: 1. 需要先生成SDP文件用于播放端: ```bash ffmpeg -protocol_whitelist file,rtp -i input.sdp ``` 2. 输入文件需要预先转码为RTP支持的格式(如H264+AAC) 3. 推荐设置`fifo_size`参数避免网络延迟: ```c av_opt_set(ofmt_ctx->priv_data, "fifo_size", "500000", 0); ``` 该代码实现了基本的RTP推流功能,实际应用中需要根据具体需求添加错误处理、重连机制和日志记录等功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

dancing_night

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

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

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

打赏作者

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

抵扣说明:

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

余额充值