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;
- }
再来看看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;
- }
再看 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。
链接:http://blog.csdn.net/dancing_night/article/details/46328837