播放器其实最主要的就是一个解码器,之前的那个最简单的播放器的代码里边也会发现一件事,那就是调用了ffmpeg中的
libavcodec以及libavformat两个库来完成视频解码的工作。后者主要就是完成封装格式的解析,而前者完成的才是解码工作。所以这回就来详细分析下只使用libavcodec这个库来进行解码的流程。
有一点需要注意,之前的那个播放器里边有一个
Flush Decoder
,这个其实就是为了
输出编码器中剩余的码流数据。
****************************************************************************************************************************************************
好了,接下来就直接来分析吧。
在主函数中,首先就是定义各种变量和结构体。而其中主要的结构体就是
AVCodec、
AVCodecContext、
AVCodecParserContext、
AVFrame和
AVPacket
。
****************************************************************************************************************************************************
好了,之后就是吊用注册函数了,但是不是之前的那个
av_register_all() 而是
avcodec_register_all()。这个我们之前就分析过,前者其实会调用后者的,这也就可以理解了。
这里再来温习一次这个函数
avcodec_register_all()这个函数其实和
av_register_all() 差不多
是在libavcodec目录下的allcodecs.c实现的
void avcodec_register_all(void)
{
static AVOnce control = AV_ONCE_INIT;
ff_thread_once(&control, register_all);
}
函数通过
ff_thread_once()
调用
register_all() 而
register_all就
在
libavcodec目录下的allcodecs.c
(其实就在上边)
register_all就会调用各种硬件加速器、编码器、解码器以及解析器
#define REGISTER_HWACCEL(X, x) \
{ \
extern AVHWAccel ff_##x##_hwaccel; \
if (CONFIG_##X##_HWACCEL) \
av_register_hwaccel(&ff_##x##_hwaccel); \
}
#define REGISTER_ENCODER(X, x) \
{ \
extern AVCodec ff_##x##_encoder; \
if (CONFIG_##X##_ENCODER) \
avcodec_register(&ff_##x##_encoder); \
}
#define REGISTER_DECODER(X, x) \
{ \
extern AVCodec ff_##x##_decoder; \
if (CONFIG_##X##_DECODER) \
avcodec_register(&ff_##x##_decoder); \
}
#define REGISTER_ENCDEC(X, x) REGISTER_ENCODER(X, x); REGISTER_DECODER(X, x)
#define REGISTER_PARSER(X, x) \
{ \
extern AVCodecParser ff_##x##_parser; \
if (CONFIG_##X##_PARSER) \
av_register_codec_parser(&ff_##x##_parser); \
}
这里边又迁出了几个函数
av_register_hwaccel(&ff_##x##_hwaccel);
avcodec_register(&ff_##x##_encoder);
avcodec_register(&ff_##x##_decoder);
av_register_codec_parser(&ff_##x##_parser);
****************************************************************************************************************************************************
在注册好了以后就调用
avcodec_find_decoder()
这个函数之前也有介绍过,这边先调用了。这个调用的顺序其实还是比较灵活的,其实也可以说在调用
avcodec_open2()之前调用就好。
而参数中的AVCodecID 这个就需要想办法获取到了,之前的那个播放器里边,应该是调用了
avformat_find_stream_info()才获取到的。这个我没有去仔细去看代码,之后再来证实——————————需要证实
我们可以在
libavformat目录下的utils.c
找到
AVCodec *avcodec_find_decoder(enum AVCodecID id)
{
return find_encdec(id, 0);
****************************************************************************************************************************************************
之后就是一个分配空间的函数了
avcodec_alloc_context3()
在之前的那个播放器里边,是调用了
avformat_alloc_context()来进行内存空间的分配以及赋初值
在前面的
avcodec_open2()函数的定义里边也有说过
在使用此函数之前,必须使用avcodec_alloc_context3()分配context
其实在
avformat_alloc_context()函数中,就有调用了
avcodec_alloc_context3()。毕竟后面没有看到任何调用————————————需要验证
函数的定义位于libavcodec目录下的avcodec.h
/**
* Allocate an AVCodecContext and set its fields to default values. The
* resulting struct should be freed with avcodec_free_context().
* 分配avcodectext并将其字段设置为默认值。由此产生的结构应该用avcodec_free_text()释放。
*
* @param codec if non-NULL, allocate private data and initialize defaults
* for the given codec. It is illegal to then call avcodec_open2()
* with a different codec.
* If NULL, then the codec-specific defaults won't be initialized,
* which may result in suboptimal default settings (this is
* important mainly for encoders, e.g. libx264).
*
* @return An AVCodecContext filled with default values or NULL on failure.
*/
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
函数的实现是在libavcodec目录下的options.c
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec)
{
AVCodecContext *avctx= av_malloc(sizeof(AVCodecContext));
if (!avctx)
return NULL;
if (init_context_defaults(avctx, codec) < 0) {
av_free(avctx);
return NULL;
}
return avctx;
****************************************************************************************************************************************************
在这之后,就调用
av_parser_init()
这个函数的作用看名字也知道是一个初始化函数,主要是对AVCodecContext的初始化
。
parser的意思就是一个解析器,它的作用其实也是可以猜得到的。
好了,来看下他的定义,位于libavcodec目录下的avcodec.h
AVCodecParserContext *av_parser_init(int codec_id);
就是这么的简单,甚至没有任何说明
函数的实现是在libavcodec目录下的parser.c
AVCodecParserContext *av_parser_init(int codec_id)
{
AVCodecParserContext *s = NULL;
AVCodecParser *parser;
int ret;
if (codec_id == AV_CODEC_ID_NONE)
return NULL;
for (parser = av_first_parser; parser; parser = parser->next) {
if (parser->codec_ids[0] == codec_id ||
parser->codec_ids[1] == codec_id ||
parser->codec_ids[2] == codec_id ||
parser->codec_ids[3] == codec_id ||
parser->codec_ids[4] == codec_id)
goto found;
}
return NULL;
found:
s = av_mallocz(sizeof(AVCodecParserContext));
if (!s)
goto err_out;
s->parser = parser;
s->priv_data = av_mallocz(parser->priv_data_size);
if (!s->priv_data)
goto err_out;
s->fetch_timestamp=1;
s->pict_type = AV_PICTURE_TYPE_I;
if (parser->parser_init) {
ret = parser->parser_init(s);
if (ret != 0)
goto err_out;
}
s->key_frame = -1;
#if FF_API_CONVERGENCE_DURATION
FF_DISABLE_DEPRECATION_WARNINGS
s->convergence_duration = 0;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
s->dts_sync_point = INT_MIN;
s->dts_ref_dts_delta = INT_MIN;
s->pts_dts_delta = INT_MIN;
s->format = -1;
return s;
err_out:
if (s)
av_freep(&s->priv_data);
av_free(s);
return NULL;
这一块也没有什么需要注意的。
****************************************************************************************************************************************************
之后就是
avcodec_open2
这个函数在之前也出现过,那就再温习一下
函数是在libavcodec目录下的avcodec.h里边定义的
/**
* Initialize the AVCodecContext to use the given AVCodec. Prior to using this
* function the context has to be allocated with avcodec_alloc_context3().
* 初始化avcodectext以使用给定的avcodece。在使用此函数之前,必须使用avcodec_alloc_context3()分配上下文。
*
* The functions avcodec_find_decoder_by_name(), avcodec_find_encoder_by_name(),
* avcodec_find_decoder() and avcodec_find_encoder() provide an easy way for
* retrieving a codec.
* avcodec_find_declder_by_name()、avcodec_find_encoder_by_name()、avcodec_find_decoder() 和 avcodec_find_encoder()
* 函数为检索编解码器提供了一种简单的方法。
*
* @warning This function is not thread safe!这个函数不是线程安全的!
*
* @note Always call this function before using decoding routines (such as
* @ref avcodec_receive_frame()).
* 总是调用这个函数之前使用解码例程(如 avcodec_receive_frame())。
*
* @code
* avcodec_register_all();
* av_dict_set(&opts, "b", "2.5M", 0);
* codec = avcodec_find_decoder(AV_CODEC_ID_H264);
* if (!codec)
* exit(1);
*
* context = avcodec_alloc_context3(codec);
*
* if (avcodec_open2(context, codec, opts) < 0)
* exit(1);
* @endcode
*
* @param avctx The context to initialize.
* @param codec The codec to open this context for. If a non-NULL codec has been
* previously passed to avcodec_alloc_context3() or
* for this context, then this parameter MUST be either NULL or
* equal to the previously passed codec.
* @param options A dictionary filled with AVCodecContext and codec-private options.
* On return this object will be filled with options that were not found.
*
* @return zero on success, a negative value on error
* @see avcodec_alloc_context3(), avcodec_find_decoder(), avcodec_find_encoder(),
* av_dict_set(), av_opt_find().
*/
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
函数是在libavcodec目录下的utils.c实现的
代码有点长 就不贴上来了
avcodec_open2()中最关键的一步就是调用AVCodec的init()方法初始化具体的编码器。AVCodec的init()是一个函数指针,指向具体编解码器中的初始化函数。
avcodec_open2()所做的工作,如下所列:
(1)为各种结构体分配内存(通过各种av_malloc()实现)。
(2)将输入的AVDictionary形式的选项设置到AVCodecContext。
(3)其他一些零零碎碎的检查,比如说检查编解码器是否处于“实验”阶段。
(4)如果是编码器,检查输入参数是否符合编码器的要求
(5)调用AVCodec的init()初始化具体的解码器。
****************************************************************************************************************************************************
主要的作用就是解析一个数据包,就是一个packet
来看下他的定义,位于libavcodec目录下的avcodec.h
/**
* Parse a packet.解析一个数据包。
*
* @param s parser context.
* @param avctx codec context.
* @param poutbuf set to pointer to parsed buffer or NULL if not yet finished.
* @param poutbuf_size set to size of parsed buffer or zero if not yet finished.
* @param buf input buffer.
* @param buf_size buffer size in bytes without the padding. I.e. the full buffer
size is assumed to be buf_size + AV_INPUT_BUFFER_PADDING_SIZE.
To signal EOF, this should be 0 (so that the last frame
can be output).
* @param pts input presentation timestamp.
* @param dts input decoding timestamp.
* @param pos input byte position in stream.
* @return the number of bytes of the input bitstream used.
*
* Example:
* @code
* while(in_len){
* len = av_parser_parse2(myparser, AVCodecContext, &data, &size,
* in_data, in_len,
* pts, dts, pos);
* in_data += len;
* in_len -= len;
*
* if(size)
* decode_frame(data, size);
* }
* @endcode
*/
int av_parser_parse2(AVCodecParserContext *s,
AVCodecContext *avctx,
uint8_t **poutbuf, int *poutbuf_size,
const uint8_t *buf, int buf_size,
int64_t pts, int64_t dts,
函数的实现是在libavcodec目录下的parser.c
int av_parser_parse2(AVCodecParserContext *s, AVCodecContext *avctx,
uint8_t **poutbuf, int *poutbuf_size,
const uint8_t *buf, int buf_size,
int64_t pts, int64_t dts, int64_t pos)
{
int index, i;
uint8_t dummy_buf[AV_INPUT_BUFFER_PADDING_SIZE];
av_assert1(avctx->codec_id != AV_CODEC_ID_NONE);
/* Parsers only work for the specified codec ids. */
av_assert1(avctx->codec_id == s->parser->codec_ids[0] ||
avctx->codec_id == s->parser->codec_ids[1] ||
avctx->codec_id == s->parser->codec_ids[2] ||
avctx->codec_id == s->parser->codec_ids[3] ||
avctx->codec_id == s->parser->codec_ids[4]);
if (!(s->flags & PARSER_FLAG_FETCHED_OFFSET)) {
s->next_frame_offset =
s->cur_offset = pos;
s->flags |= PARSER_FLAG_FETCHED_OFFSET;
}
if (buf_size == 0) {
/* padding is always necessary even if EOF, so we add it here */
memset(dummy_buf, 0, sizeof(dummy_buf));
buf = dummy_buf;
} else if (s->cur_offset + buf_size != s->cur_frame_end[s->cur_frame_start_index]) { /* skip remainder packets */
/* add a new packet descriptor */
i = (s->cur_frame_start_index + 1) & (AV_PARSER_PTS_NB - 1);
s->cur_frame_start_index = i;
s->cur_frame_offset[i] = s->cur_offset;
s->cur_frame_end[i] = s->cur_offset + buf_size;
s->cur_frame_pts[i] = pts;
s->cur_frame_dts[i] = dts;
s->cur_frame_pos[i] = pos;
}
if (s->fetch_timestamp) {
s->fetch_timestamp = 0;
s->last_pts = s->pts;
s->last_dts = s->dts;
s->last_pos = s->pos;
ff_fetch_timestamp(s, 0, 0, 0);
}
/* WARNING: the returned index can be negative */
index = s->parser->parser_parse(s, avctx, (const uint8_t **) poutbuf,
poutbuf_size, buf, buf_size);
av_assert0(index > -0x20000000); // The API does not allow returning AVERROR codes
#define FILL(name) if(s->name > 0 && avctx->name <= 0) avctx->name = s->name
if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {
FILL(field_order);
}
/* update the file pointer */
if (*poutbuf_size) {
/* fill the data for the current frame */
s->frame_offset = s->next_frame_offset;
/* offset of the next frame */
s->next_frame_offset = s->cur_offset + index;
s->fetch_timestamp = 1;
}
if (index < 0)
index = 0;
s->cur_offset += index;
return index;
****************************************************************************************************************************************************
这个函数之前也有说过,真正干活的人
该函数的声明位于libavcodec\avcodec.h
源代码位于libavcodec\utils.c
主要的工作就是:
(1)对输入的字段进行了一系列的检查工作:例如宽高是否正确,输入是否为视频等等。
(2)通过ret = avctx->codec->decode(avctx, picture, got_picture_ptr,&tmp)这句代码,调用了相应AVCodec的decode()函数,完成了解码操作。
(3)对得到的AVFrame的一些字段进行了赋值,例如宽高、像素格式等等。
其中第二部是关键的一步,它调用了AVCodec的decode()方法完成了解码。AVCodec的decode()方法是一个函数指针,指向了具体解码器的解码函数。
****************************************************************************************************************************************************
这个是对之前介绍的
av_parser_init()申请的资源进行释放的函数
函数的定义位于libavcodec目录下的avcodec.h下
void av_parser_close(AVCodecParserContext *s);
也是很简单的,只是需要注意下参数
函数的实现位于libavcodec目录下的parser.c
void av_parser_close(AVCodecParserContext *s)
{
if (s) {
if (s->parser->parser_close)
s->parser->parser_close(s);
av_freep(&s->priv_data);
av_free(s);
}
****************************************************************************************************************************************************
之后
av_frame_free、
avcodec_close、
av_free
这些函数就是为了释放资源的
****************************************************************************************************************************************************
avcodec_close()
该函数用于关闭编码器。avcodec_close()函数的声明位于libavcodec\avcodec.h,
/**
* Close a given AVCodecContext and free all the data associated with it
* (but not the AVCodecContext itself).
* 关闭给定的avcodectext并释放与其相关的所有数据(但不释放avcodectext本身)。
*
* Calling this function on an AVCodecContext that hasn't been opened will free
* the codec-specific data allocated in avcodec_alloc_context3() with a non-NULL
* codec. Subsequent calls will do nothing.
* 在尚未打开的avcodectext上调用此函数将释放在avcodec_alloc_context 3()中分配的与编解码
* 器特定的数据,其中包含一个非空codecc。随后的调用将不会起任何作用。
*
* @note Do not use this function. Use avcodec_free_context() to destroy a
* codec context (either open or closed). Opening and closing a codec context
* multiple times is not supported anymore -- use multiple codec contexts
* instead.
*/
int avcodec_close(AVCodecContext *avctx);
函数的实现
位于libavcodec\utils.c
该函数释放AVCodecContext中有关的变量,并且调用了AVCodec的close()关闭了解码器。
****************************************************************************************************************************************************
差不多就是这些了