您的位置:首页 > 编程语言 > PHP开发

ffmpeg源码简析(五)编码——avformat_alloc_output_context2(),avcodec_encode_video2()

2017-04-24 19:22 513 查看

1.avformat_alloc_output_context2()

在基于FFmpeg的视音频编码器程序中,该函数通常是第一个调用的函数(除了组件注册函数av_register_all())。avformat_alloc_output_context2()函数可以初始化一个用于输出的AVFormatContext结构体。它的声明位于libavformat\avformat.h,如下所示。

int avformat_alloc_output_context2(AVFormatContext **ctx, AVOutputFormat *oformat,
const char *format_name, const char *filename);


ctx:函数调用成功之后创建的AVFormatContext结构体。

oformat:指定AVFormatContext中的AVOutputFormat,用于确定输出格式。如果指定为NULL,可以设定后两个参数(format_name或者filename)由FFmpeg猜测输出格式。

PS:使用该参数需要自己手动获取AVOutputFormat,相对于使用后两个参数来说要麻烦一些。

format_name:指定输出格式的名称。根据格式名称,FFmpeg会推测输出格式。输出格式可以是“flv”,“mkv”等等。

filename:指定输出文件的名称。根据文件名称,FFmpeg会推测输出格式。文件名称可以是“xx.flv”,“yy.mkv”等等。

函数执行成功的话,其返回值大于等于0。

下面看一下avformat_alloc_output_context2()的函数定义。该函数的定义位于libavformat\mux.c中,如下所示。

int avformat_alloc_output_context2(AVFormatContext **avctx, AVOutputFormat *oformat,
const char *format, const char *filename)
{
AVFormatContext *s = avformat_alloc_context();
int ret = 0;

*avctx = NULL;
if (!s)
goto nomem;

if (!oformat) {
if (format) {
oformat = av_guess_format(format, NULL, NULL);
if (!oformat) {
av_log(s, AV_LOG_ERROR, "Requested output format '%s' is not a suitable output format\n", format);
ret = AVERROR(EINVAL);
goto error;
}
} else {
oformat = av_guess_format(NULL, filename, NULL);
if (!oformat) {
ret = AVERROR(EINVAL);
av_log(s, AV_LOG_ERROR, "Unable to find a suitable output format for '%s'\n",
filename);
goto error;
}
}
}

s->oformat = oformat;
if (s->oformat->priv_data_size > 0) {
s->priv_data = av_mallocz(s->oformat->priv_data_size);
if (!s->priv_data)
goto nomem;
if (s->oformat->priv_class) {
*(const AVClass**)s->priv_data= s->oformat->priv_class;
av_opt_set_defaults(s->priv_data);
}
} else
s->priv_data = NULL;

if (filename)
av_strlcpy(s->filename, filename, sizeof(s->filename));
*avctx = s;
return 0;
nomem:
av_log(s, AV_LOG_ERROR, "Out of memory\n");
ret = AVERROR(ENOMEM);
error:
avformat_free_context(s);
return ret;
}


从代码中可以看出,avformat_alloc_output_context2()的流程如要包含以下2步:

1)  调用avformat_alloc_context()初始化一个默认的AVFormatContext。

2)  如果指定了输入的AVOutputFormat,则直接将输入的AVOutputFormat赋值给AVOutputFormat的oformat。如果没有指定输入的AVOutputFormat,就需要根据文件格式名称或者文件名推测输出的AVOutputFormat。无论是通过文件格式名称还是文件名推测输出格式,都会调用一个函数av_guess_format()。


下面我们分别看看上文步骤中提到的两个重要的函数:avformat_alloc_context()和av_guess_format()。

avformat_alloc_context()

AVFormatContext *avformat_alloc_context(void)
{
AVFormatContext *ic;
ic = av_malloc(sizeof(AVFormatContext));
if (!ic) return ic;
avformat_get_context_defaults(ic);

ic->internal = av_mallocz(sizeof(*ic->internal));
if (!ic->internal) {
avformat_free_context(ic);
return NULL;
}

return ic;
}


从代码中可以看出,avformat_alloc_context()首先调用av_malloc()为AVFormatContext分配一块内存。然后调用了一个函数avformat_get_context_defaults()用于给AVFormatContext设置默认值。avformat_get_context_defaults()的定义如下。

static void avformat_get_context_defaults(AVFormatContext *s)
{
memset(s, 0, sizeof(AVFormatContext));

s->av_class = &av_format_context_class;

av_opt_set_defaults(s);
}


从代码中可以看出,avformat_alloc_context()首先调用memset()将AVFormatContext的内存置零;然后指定它的AVClass(指定了AVClass之后,该结构体就支持和AVOption相关的功能);最后调用av_opt_set_defaults()给AVFormatContext的成员变量设置默认值(av_opt_set_defaults()就是和AVOption有关的一个函数,专门用于给指定的结构体设定默认值,此处暂不分析)。

av_guess_format()

AVOutputFormat *av_guess_format(const char *short_name,
const char *filename,
const char *mime_type);


short_name:格式的名称。
filename:文件的名称。
mime_type:MIME类型。


返回最匹配的AVOutputFormat。如果没有很匹配的AVOutputFormat,则返回NULL。

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;
}


从代码中可以看出,av_guess_format()中使用一个整型变量score记录每种输出格式的匹配程度。函数中包含了一个while()循环,该循环利用函数av_oformat_next()遍历FFmpeg中所有的AVOutputFormat,并逐一计算每个输出格式的score。具体的计算过程分成如下几步:

1)  如果封装格式名称匹配,score增加100。匹配中使用了函数av_match_name()。
2)  如果mime类型匹配,score增加10。匹配直接使用字符串比较函数strcmp()。
3)  如果文件名称的后缀匹配,score增加5。匹配中使用了函数av_match_ext()。


while()循环结束后,得到得分最高的格式,就是最匹配的格式

av_guess_format()最终可以得到最合适的AVOutputFormat并且返回给avformat_alloc_output_context2()。avformat_alloc_output_context2()接下来将获得的AVOutputFormat赋值给刚刚新建的AVFormatContext,即可完成初始化工作。

2.avformat_write_header()

其中av_write_frame()用于写视频数据,avformat_write_header()用于写视频文件头,而av_write_trailer()用于写视频文件尾。

avformat_write_header()的声明位于libavformat\avformat.h,如下所示

int avformat_write_header(AVFormatContext *s, AVDictionary **options);


简单解释一下它的参数的含义:

s:用于输出的AVFormatContext。
options:额外的选项,目前没有深入研究过,一般为NULL。


函数正常执行后返回值等于0。

avformat_write_header()的定义位于libavformat\mux.c,如下所示。

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;
}


从源代码可以看出,avformat_write_header()完成了以下工作:

(1)调用init_muxer()初始化复用器
(2)调用AVOutputFormat的write_header()


init_muxer()

init_muxer()代码很长,但是它所做的工作比较简单,可以概括成两个字:检查。函数的流程可以概括成以下几步:

(1)将传入的AVDictionary形式的选项设置到AVFormatContext
(2)遍历AVFormatContext中的每个AVStream,并作如下检查:

a)AVStream的time_base是否正确设置。如果发现AVStream的time_base没有设置,则会调用avpriv_set_pts_info()进行设置。

b)对于音频,检查采样率设置是否正确;对于视频,检查宽、高、宽高比。

c)其他一些检查,不再详述。


AVOutputFormat->write_header()

avformat_write_header()中最关键的地方就是调用了AVOutputFormat的write_header()。write_header()是AVOutputFormat中的一个函数指针,指向写文件头的函数。不同的AVOutputFormat有不同的write_header()的实现方法。

3.avcodec_encode_video2()

该函数用于编码一帧视频数据。avcodec_encode_video2()函数的声明位于libavcodec\avcodec.h,如下所示。

int avcodec_encode_video2(AVCodecContext *avctx, AVPacket *avpkt,
const AVFrame *frame, int *got_packet_ptr);


avctx:编码器的AVCodecContext。
avpkt:编码输出的AVPacket。
frame:编码输入的AVFrame。
got_packet_ptr:成功编码一个AVPacket的时候设置为1。

函数返回0代表编码成功。


avcodec_encode_video2()的定义位于libavcodec\utils.c,如下所示。

int attribute_align_arg avcodec_encode_video2(AVCodecContext *avctx,
AVPacket *avpkt,
const AVFrame *frame,
int *got_packet_ptr)
{
int ret;
AVPacket user_pkt = *avpkt;
int needs_realloc = !user_pkt.data;

*got_packet_ptr = 0;

if(CONFIG_FRAME_THREAD_ENCODER &&
avctx->internal->frame_thread_encoder && (avctx->active_thread_type&FF_THREAD_FRAME))
return ff_thread_video_encode_frame(avctx, avpkt, frame, got_packet_ptr);

if ((avctx->flags&CODEC_FLAG_PASS1) && avctx->stats_out)
avctx->stats_out[0] = '\0';

if (!(avctx->codec->capabilities & CODEC_CAP_DELAY) && !frame) {
av_free_packet(avpkt);
av_init_packet(avpkt);
avpkt->size = 0;
return 0;
}
//检查输入
if (av_image_check_size(avctx->width, avctx->height, 0, avctx))
return AVERROR(EINVAL);

av_assert0(avctx->codec->encode2);
//编码
ret = avctx->codec->encode2(avctx, avpkt, frame, got_packet_ptr);
av_assert0(ret <= 0);

if (avpkt->data && avpkt->data == avctx->internal->byte_buffer) {
needs_realloc = 0;
if (user_pkt.data) {
if (user_pkt.size >= avpkt->size) {
memcpy(user_pkt.data, avpkt->data, avpkt->size);
} else {
av_log(avctx, AV_LOG_ERROR, "Provided packet is too small, needs to be %d\n", avpkt->size);
avpkt->size = user_pkt.size;
ret = -1;
}
avpkt->buf      = user_pkt.buf;
avpkt->data     = user_pkt.data;
#if FF_API_DESTRUCT_PACKET
FF_DISABLE_DEPRECATION_WARNINGS
avpkt->destruct = user_pkt.destruct;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
} else {
if (av_dup_packet(avpkt) < 0) {
ret = AVERROR(ENOMEM);
}
}
}

if (!ret) {
if (!*got_packet_ptr)
avpkt->size = 0;
else if (!(avctx->codec->capabilities & CODEC_CAP_DELAY))
avpkt->pts = avpkt->dts = frame->pts;

if (needs_realloc && avpkt->data) {
ret = av_buffer_realloc(&avpkt->buf, avpkt->size + FF_INPUT_BUFFER_PADDING_SIZE);
if (ret >= 0)
avpkt->data = avpkt->buf->data;
}

avctx->frame_number++;
}

if (ret < 0 || !*got_packet_ptr)
av_free_packet(avpkt);
else
av_packet_merge_side_data(avpkt);

emms_c();
return ret;
}


从函数的定义可以看出,avcodec_encode_video2()首先调用了av_image_check_size()检查设置的宽高参数是否合理,然后调用了AVCodec的encode2()调用具体的解码器。

av_image_check_size()

av_image_check_size()是一个很简单的函数,用于检查图像宽高是否正常,它的定义如下所示。

int av_image_check_size(unsigned int w, unsigned int h, int log_offset, void *log_ctx)
{
ImgUtils imgutils = { &imgutils_class, log_offset, log_ctx };

if ((int)w>0 && (int)h>0 && (w+128)*(uint64_t)(h+128) < INT_MAX/8)
return 0;

av_log(&imgutils, AV_LOG_ERROR, "Picture size %ux%u is invalid\n", w, h);
return AVERROR(EINVAL);
}


从代码中可以看出,av_image_check_size()主要是要求图像宽高必须为正数,而且取值不能太大。

AVCodec->encode2()

AVCodec的encode2()是一个函数指针,指向特定编码器的编码函数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: