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、解释
简单解释下原理:
首先自己实现一个AVIn
putFormat,然后把这个de
muxer加入到链表中去,其他的根本不用自己管了,需要注意的是,我们解析的其实是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。