最简单的解码流程(只涉及到libavcodec)

播放器其实最主要的就是一个解码器,之前的那个最简单的播放器的代码里边也会发现一件事,那就是调用了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()初始化具体的解码器。
****************************************************************************************************************************************************
之后 av_parser_parse2
主要的作用就是解析一个数据包,就是一个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,
                     int64_t pos);

函数的实现是在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;
}
****************************************************************************************************************************************************
之后 avcodec_decode_video2
这个函数之前也有说过,真正干活的人
该函数的声明位于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_close
这个是对之前介绍的 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()关闭了解码器。
****************************************************************************************************************************************************

差不多就是这些了




要使用libavcodec解码pcm格式的音频数据,可以按照以下步骤进行: 1. 打开音频文件并获取音频流信息 ```c AVFormatContext *format_ctx = NULL; avformat_open_input(&format_ctx, input_filename, NULL, NULL); avformat_find_stream_info(format_ctx, NULL); int audio_stream_index = av_find_best_stream(format_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); AVStream *audio_stream = format_ctx->streams[audio_stream_index]; ``` 2. 获取音频解码器并打开解码器上下文 ```c AVCodec *codec = avcodec_find_decoder(audio_stream->codecpar->codec_id); AVCodecContext *codec_ctx = avcodec_alloc_context3(codec); avcodec_parameters_to_context(codec_ctx, audio_stream->codecpar); avcodec_open2(codec_ctx, codec, NULL); ``` 3. 读取音频数据并解码 ```c AVPacket packet; AVFrame *frame = av_frame_alloc(); while (av_read_frame(format_ctx, &packet) >= 0) { if (packet.stream_index == audio_stream_index) { avcodec_send_packet(codec_ctx, &packet); while (avcodec_receive_frame(codec_ctx, frame) == 0) { // 解码后的音频数据在frame->data[0]中 // 根据音频参数获取每个样本的字节数 int bytes_per_sample = av_get_bytes_per_sample(codec_ctx->sample_fmt); // 计算音频数据的总字节数 int data_size = frame->nb_samples * codec_ctx->channels * bytes_per_sample; // 对音频数据进行处理 // ... } } av_packet_unref(&packet); } ``` 4. 关闭解码器上下文和音频文件 ```c avcodec_free_context(&codec_ctx); avformat_close_input(&format_ctx); ``` 注意,解码后的音频数据是以PCM格式存储的,需要根据音频参数(采样率、采样位数、声道数等)来解析每个样本的值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值