FFmpeg例子分析—decode_audio

FFmpeg流程

avformat_find_stream_info 阻塞_c++

音频解码流程

avformat_find_stream_info 阻塞_c++_02

音频解码详细步骤

avformat_find_stream_info 阻塞_图像处理_03

代码

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <thread>

extern "C" {
    #include "libavformat/avformat.h"
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    #include <libavutil/frame.h>
    #include <libavutil/mem.h>

    #include <libavcodec/avcodec.h>
}

using namespace std;

#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"avutil.lib")
#pragma comment(lib,"avcodec.lib")


 /**
  * @file
  * audio decoding with libavcodec API example
  * libavcodec API音频解码示例
  *
  * @example decode_audio.c
  */


#define AUDIO_INBUF_SIZE 20480
#define AUDIO_REFILL_THRESH 4096

static void decode(AVCodecContext* dec_ctx, AVPacket* pkt, AVFrame* frame,
    FILE* outfile)
{
    int i, ch;
    int ret, data_size;

    /* send the packet with the compressed data to the decoder */
    //将AVPacket压缩数据给解码器
    ret = avcodec_send_packet(dec_ctx, pkt);
    if (ret < 0) {
        fprintf(stderr, "Error submitting the packet to the decoder\n");
        exit(1);
    }

    /* read all the output frames (in general there may be any number of them */
    while (ret >= 0) {
        //获取到解码后的AVFrame数据
        ret = avcodec_receive_frame(dec_ctx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0) {
            fprintf(stderr, "Error during decoding\n");
            exit(1);
        }
        // 获取每个sample中的字节数
        data_size = av_get_bytes_per_sample(dec_ctx->sample_fmt);
        if (data_size < 0) {
            /* This should not occur, checking just for paranoia */
            fprintf(stderr, "Failed to calculate data size\n");
            exit(1);
        }
        for (i = 0; i < frame->nb_samples; i++)
            for (ch = 0; ch < dec_ctx->channels; ch++)
                fwrite(frame->data[ch] + data_size * i, 1, data_size, outfile);
    }
}

int main(int argc, char** argv)
{
    const char* outfilename, * filename;
    const AVCodec* codec;
    AVCodecContext* c = NULL;
    AVCodecParserContext* parser = NULL;
    int len, ret;
    FILE* f, * outfile;
    uint8_t inbuf[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
    uint8_t* data;
    size_t   data_size;
    AVPacket* pkt;
    AVFrame* decoded_frame = NULL;

    //if (argc <= 2) {
    //    fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);
    //    exit(0);
    //}
    //filename = argv[1];
    //outfilename = argv[2];

    filename = "C:\\Music\\yicijiuhao.mp3";
    outfilename = "D:\\FFmpeg\\yicijiuhao.pcm";


    pkt = av_packet_alloc();

    /* find the MPEG audio decoder */
    //根据指定的AVCodecID查找注册的解码器
    codec = avcodec_find_decoder(AV_CODEC_ID_MP2);
    if (!codec) {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }

    //初始化AVCodecParserContext
    parser = av_parser_init(codec->id);
    if (!parser) {
        fprintf(stderr, "Parser not found\n");
        exit(1);
    }

    //AVCodecContext* c = NULL;
    //为AVCodecContext分配内存
    c = avcodec_alloc_context3(codec);
    if (!c) {
        fprintf(stderr, "Could not allocate audio codec context\n");
        exit(1);
    }

    /* open it */
    //打开解码器
    if (avcodec_open2(c, codec, NULL) < 0) {
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    }

    f = fopen(filename, "rb");
    if (!f) {
        fprintf(stderr, "Could not open %s\n", filename);
        exit(1);
    }
    outfile = fopen(outfilename, "wb");
    if (!outfile) {
        av_free(c);
        exit(1);
    }

    /* decode until eof */
    data = inbuf;
    //AUDIO_INBUF_SIZE = 1024 * 20
    data_size = fread(inbuf, 1, AUDIO_INBUF_SIZE, f);

    while (data_size > 0) {
        if (!decoded_frame) {
            if (!(decoded_frame = av_frame_alloc())) {
                fprintf(stderr, "Could not allocate audio frame\n");
                exit(1);
            }
        }

        //解析获得一个Packet
        ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size,
            data, data_size,
            AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
        if (ret < 0) {
            fprintf(stderr, "Error while parsing\n");
            exit(1);
        }
        data += ret;
        data_size -= ret;

        if (pkt->size) {
            //解码
            //static void decode(AVCodecContext* dec_ctx, AVPacket* pkt, AVFrame* frame,FILE* outfile)
            decode(c, pkt, decoded_frame, outfile);
        }
            

        if (data_size < AUDIO_REFILL_THRESH) {
            memmove(inbuf, data, data_size);
            data = inbuf;
            len = fread(data + data_size, 1,
                AUDIO_INBUF_SIZE - data_size, f);
            if (len > 0)
                data_size += len;
        }
    }

    /* flush the decoder */
    pkt->data = NULL;
    pkt->size = 0;
    decode(c, pkt, decoded_frame, outfile);

    fclose(outfile);
    fclose(f);

    avcodec_free_context(&c);
    av_parser_close(parser);
    av_frame_free(&decoded_frame);
    av_packet_free(&pkt);

    return 0;
}

//参考资料
//https://www.jianshu.com/p/fb8ce81a7cf4

大概就是这样

avformat_find_stream_info 阻塞_c++_04

音频重新填充阈值,代码分析

avformat_find_stream_info 阻塞_ffmpeg_05

当inbuf里面的数据量小于阈值里,把剩下的数据移动到缓冲区前面,重置data指针。再从音视频文件中读取数据,放入缓冲区,增加data_size。

函数解读

fread函数

fread函数用于从文件流中读取数据,其函数原型为:

size_t fread(void* buffer, size_t size, size_t count, FILE*stream);

参数

参数

说明

buffer

指向要读取的数组中首个对象的指针

size

每个对象的大小(单位是字节)

count

要读取的对象个数

stream

输入流

av_frame_alloc函数

Allocate an AVFrame and set its fields to default values.  The resulting
 struct must be freed using av_frame_free().
分配AVFrame并将其字段设置为默认值。必须使用av_frame_free释放生成的结构

 @return An AVFrame filled with default values or NULL on failure.
 AVFrame填充了默认值或失败时为NULL

 @note this only allocates the AVFrame itself, not the data buffers. Those
 must be allocated through other means, e.g. with av_frame_get_buffer() or
 manually.
 这只分配AVFrame本身,而不是数据缓冲区。必须通过其他方式分配

AVFrame结构体分析看这里。

av_parser_parse2函数—解析数据包

函数原型

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

参数说明

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.

使用方法

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

函数代码

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

memmove函数

memmove用于拷贝字节,如果目标区域和源区域有重叠的话,memmove能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中,但复制后源内容会被更改。但是当目标区域与源区域没有重叠则和memcpy函数功能相同。

原型:

void *memmove( void* dest, const void* src, size_t count );

头文件:

<string.h>

功能:

由src所指内存区域复制count个字节到dest所指内存区域。

相关函数:

memset、memcpy

inbuf为什么要加上64

// 20480 + 64
    uint8_t inbuf[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];

首先我们创建了一个AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE大小的数据缓存区,加上AV_INPUT_BUFFER_PADDING_SIZE是为了防止某些优化过的reader一次性读取过多导致越界。然后调用fread函数从本地文件中每次读取AUDIO_INBUF_SIZE大小的数据到缓存区中。

参考

ffmpeg_sample解读_decode_audio关于FFmpeg的AAC解码实战