音视频开发29 FFmpeg 音频编码- 流程以及重要API,该章节使用AAC编码说明

此章节的一些参数,需要先掌握aac的一些基本知识:​​​​​​aac音视频开发13 FFmpeg 音频 --- 常用音频格式AAC,AAC编码器, AAC ADTS格式 。_ffmpeg aac data数据格式-CSDN博客

目的:

从本地⽂件读取PCM数据进⾏AAC格式编码,然后将编码后的AAC数据存储到本地⽂件。

流程:

关键函数说明:

avcodec_find_encoder:根据指定的AVCodecID查找注册的编码器。
avcodec_alloc_context3:为AVCodecContext分配内存。
avcodec_open2:打开编码器。
avcodec_send_frame:将AVFrame⾮压缩数据给编码器。
avcodec_receive_packet:获取到编码后的AVPacket数据,收到的packet需要⾃⼰释放内存。
av_frame_get_buffer: 为⾳频或视频帧分配新的buffer。在调⽤这个函数之前,必须在AVFame上设
置好以下属性:format(视频为像素格式,⾳频为样本格式)、nb_samples(样本个数,针对⾳频)、
channel_layout(通道类型,针对⾳频)、width/height(宽⾼,针对视频)。
av_frame_make_writable :确保AVFrame是可写的,使⽤av_frame_make_writable()的问题是,在最坏的情况下,它会在您使⽤encode再次更改整个输⼊frame之前复制它. 如果frame不可写,
av_frame_make_writable()将分配新的缓冲区,并复制这个输⼊input frame数据,避免和编码器需
要缓存该帧时造成冲突。
av_samples_fill_arrays 填充⾳频帧

对于 flush encoder的操作:
编码器通常的冲洗⽅法:调⽤⼀次 avcodec_send_frame(NULL)(返回成功),然后不停调⽤
avcodec_receive_packet() 直到其返回 AVERROR_EOF,取出所有缓存帧, avcodec_receive_packet() 返回 AVERROR_EOF 这⼀次是没有有效数据的,仅仅获取到⼀
个结束标志

PCM样本格式

PCM(Pulse Code Modulation,脉冲编码调制)⾳频数据是未经压缩的⾳频采样数据裸流,它是由模拟信 号经过采样、量化、编码转换成的标准数字⾳频数据。
描述PCM数据的6个参数:

  1. Sample Rate : 采样频率。8kHz(电话)、44.1kHz(CD)、48kHz(DVD)。

  2. Sample Size : 量化位数。通常该值为16-bit。

  3. Number of Channels : 通道个数。常⻅的⾳频有⽴体声(stereo)和单声道(mono)两种类型,⽴体声包 含左声道和右声道。另外还有环绕⽴体声等其它不太常⽤的类型。

  4. Sign : 表示样本数据是否是有符号位,⽐如⽤⼀字节表示的样本数据,有符号的话表示范围为-128 ~ 127,⽆符号是0 ~ 255。有符号位16bits数据取值范围为-32768~32767。

  5. Byte Ordering : 字节序。字节序是little-endian还是big-endian。通常均为little-endian。字节序说
    明⻅第4节。

  6. Integer Or Floating Point : 整形或浮点型。⼤多数格式的PCM样本数据使⽤整形表示,⽽在⼀些对 精度要求⾼的应⽤⽅⾯,使⽤浮点类型表示PCM样本数据(浮点数 float值域为 [-1.0, 1.0])。

    //播放格式为f32le,双声道,采样频率48000Hz的PCM数据
    ffplay -f f32le -ac 2 -ar 48000 pcm_audio

如何知道FFmpeg⽀持的PCM数据格式

使⽤ffmpeg -formats命令,获取ffmpeg⽀持的⾳视频格式,其中我们可以找到⽀持的PCM格式。

ffmpeg -formats | findstr PCM

 DE alaw            PCM A-law
 DE f32be           PCM 32-bit floating-point big-endian
 DE f32le           PCM 32-bit floating-point little-endian
 DE f64be           PCM 64-bit floating-point big-endian
 DE f64le           PCM 64-bit floating-point little-endian
 DE mulaw           PCM mu-law
 DE s16be           PCM signed 16-bit big-endian
 DE s16le           PCM signed 16-bit little-endian
 DE s24be           PCM signed 24-bit big-endian
 DE s24le           PCM signed 24-bit little-endian
 DE s32be           PCM signed 32-bit big-endian
 DE s32le           PCM signed 32-bit little-endian
 DE s8              PCM signed 8-bit
 DE u16be           PCM unsigned 16-bit big-endian
 DE u16le           PCM unsigned 16-bit little-endian
 DE u24be           PCM unsigned 24-bit big-endian
 DE u24le           PCM unsigned 24-bit little-endian
 DE u32be           PCM unsigned 32-bit big-endian
 DE u32le           PCM unsigned 32-bit little-endian
 DE u8              PCM unsigned 8-bit
 DE vidc            PCM Archimedes VIDC

s是有符号,u是⽆符号,f是浮点数。
be是⼤端,le是⼩端。

FFmpeg中Packed和Planar的PCM数据区别

FFmpeg中⾳视频数据基本上都有Packed和Planar两种存储⽅式,对于双声道⾳频来说,
Packed⽅式为两个声道的数据交错存储;Planar⽅式为两个声道分开存储。 假设⼀个L/R为⼀
个采样点,数据存储的⽅式如下所示:
Packed: L R L R L R L R
Planar: L L L L ... R R R R...

packed格式

1 AV_SAMPLE_FMT_U8, ///< unsigned 8 bits
2 AV_SAMPLE_FMT_S16, ///< signed 16 bits
3 AV_SAMPLE_FMT_S32, ///< signed 32 bits
4 AV_SAMPLE_FMT_FLT, ///< float
5 AV_SAMPLE_FMT_DBL, ///< double

只能保存在AVFrame的uint8_t *data[0]

⾳频保持格式如下:

LRLRLR ...

planar格式

planar为FFmpeg内部存储⾳频使⽤的采样格式,所有的Planar格式后⾯都有字⺟P标识。

1 AV_SAMPLE_FMT_U8P, ///< unsigned 8 bits, planar
2 AV_SAMPLE_FMT_S16P, ///< signed 16 bits, planar
3 AV_SAMPLE_FMT_S32P, ///< signed 32 bits, planar
4 AV_SAMPLE_FMT_FLTP, ///< float, planar
5 AV_SAMPLE_FMT_DBLP, ///< double, planar
6 AV_SAMPLE_FMT_S64, ///< signed 64 bits
7 AV_SAMPLE_FMT_S64P, ///< signed 64 bits, planar

plane 0: LLLLLLLLLLLLLLLLLLLLLLLLLL...
plane 1: RRRRRRRRRRRRRRRRRRRR....
plane 0对于uint8_t *data[0];
plane 1对于uint8_t *data[1];

FFMPEG 默认的AAC编码器行为:

FFmpeg默认的AAC编码器不⽀持AV_SAMPLE_FMT_S16格式的编码,
只⽀持 AV_SAMPLE_FMT_FLTP,
AV_SAMPLE_FMT_FLTP 这种格式是按平⾯存储,样点是float类型,所谓平⾯也就是 每个声道单独存储,⽐如左声道存储到data[0]中,右声道存储到data[1]中。

FFmpeg⾳频 解码后编码前 的数据是存放在AVFrame结构中的。
Packed格式,frame.data[0]或frame.extended_data[0]包含所有的⾳频数据中。
Planar格式,frame.data[i]或者frame.extended_data[i]表示第i个声道的数据(假设声道0是第⼀
个),
AVFrame.data数组⼤⼩固定为8,如果声道数超过8,需要从frame.extended_data获取声道数据。

补充说明

Planar模式ffmpeg内部存储模式 ,我们 实际使⽤的⾳频⽂件 都是 Packed模式 的。
FFmpeg解码不同格式的⾳频输出的⾳频采样格式不是⼀样。
测试发现,
AAC解码输出的数据为浮点型的 AV_SAMPLE_FMT_FLTP 格式,
MP3解码输出的数据为 AV_SAMPLE_FMT_S16P 格式(使 ⽤的mp3⽂件为16位深)。
具体采样格式可以查看解码后的AVFrame中的 format成员 或编解码器的 AVCodecContext中的 sample_fmt 成员。
Planar或者Packed模式直接影响到保存⽂件时写⽂件的操作,操作数据的时候⼀定要先检测⾳频采样 格式。

示例代码:

//编码 代码。
//音频的格式,
//从本地⽂件读取PCM数据进⾏AAC格式编码,然后将编码后的AAC数据存储到本地⽂件。

#include <iostream>

extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/fifo.h"
#include "libavutil/audio_fifo.h"
#include "libswresample/swresample.h"
#include "libavutil/opt.h"

}
using namespace std;

static void get_adts_header(AVCodecContext *ctx, uint8_t *adts_header, int aac_length)
{
    uint8_t freq_idx = 0;    //0: 96000 Hz  3: 48000 Hz 4: 44100 Hz
    switch (ctx->sample_rate) {
        case 96000: freq_idx = 0; break;
        case 88200: freq_idx = 1; break;
        case 64000: freq_idx = 2; break;
        case 48000: freq_idx = 3; break;
        case 44100: freq_idx = 4; break;
        case 32000: freq_idx = 5; break;
        case 24000: freq_idx = 6; break;
        case 22050: freq_idx = 7; break;
        case 16000: freq_idx = 8; break;
        case 12000: freq_idx = 9; break;
        case 11025: freq_idx = 10; break;
        case 8000: freq_idx = 11; break;
        case 7350: freq_idx = 12; break;
        default: freq_idx = 4; break;
    }
    uint8_t chanCfg = ctx->channels;
    uint32_t frame_length = aac_length + 7;
    adts_header[0] = 0xFF;
    adts_header[1] = 0xF1;
    adts_header[2] = ((ctx->profile) << 6) + (freq_idx << 2) + (chanCfg >> 2);
    adts_header[3] = (((chanCfg & 3) << 6) + (frame_length  >> 11));
    adts_header[4] = ((frame_length & 0x7FF) >> 3);
    adts_header[5] = (((frame_length & 7) << 5) + 0x1F);
    adts_header[6] = 0xFC;
}

static int encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt, FILE *output)
{
    int ret;

    /* send the frame for encoding */
    ret = avcodec_send_frame(ctx, frame);
    if (ret < 0) {
        fprintf(stderr, "Error sending the frame to the encoder\n");
        return -1;
    }

    /* read all the available output packets (in general there may be any number of them */
    // 编码和解码都是一样的,都是send 1次,然后receive多次, 直到AVERROR(EAGAIN)或者AVERROR_EOF
    while (ret >= 0) {
        ret = avcodec_receive_packet(ctx, pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            return 0;
        } else if (ret < 0) {
            fprintf(stderr, "Error encoding audio frame\n");
            return -1;
        }

        size_t len = 0;
        printf("ctx->flags:0x%x & AV_CODEC_FLAG_GLOBAL_HEADER:0x%x, name:%s\n",ctx->flags, ctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER, ctx->codec->name);
        if((ctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER)) {
            // 需要额外的adts header写入
            uint8_t aac_header[7];
            get_adts_header(ctx, aac_header, pkt->size);
            len = fwrite(aac_header, 1, 7, output);
            if(len != 7) {
                fprintf(stderr, "fwrite aac_header failed\n");
                return -1;
            }
        }
        len = fwrite(pkt->data, 1, pkt->size, output);
        if(len != pkt->size) {
            fprintf(stderr, "fwrite aac data failed\n");
            return -1;
        }
        /* 是否需要释放数据? avcodec_receive_packet第一个调用的就是 av_packet_unref
        * 所以我们不用手动去释放,这里有个问题,不能将pkt直接插入到队列,因为编码器会释放数据
        * 可以新分配一个pkt, 然后使用av_packet_move_ref转移pkt对应的buffer
        */
         av_packet_unref(pkt);
    }
    av_frame_unref(frame);
    return -1;
}



/*
 * 这里只支持2通道的转换
*/
void f32le_convert_to_fltp(float *f32le, float *fltp, int nb_samples) {
    float *fltp_l = fltp;   // 左通道
    float *fltp_r = fltp + nb_samples;   // 右通道
    for(int i = 0; i < nb_samples; i++) {
        fltp_l[i] = f32le[i*2];     // 0 1   - 2 3
        fltp_r[i] = f32le[i*2+1];   // 可以尝试注释左声道或者右声道听听声音
    }
}

/* 检测该编码器是否支持该采样格式 */
static int check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_fmt)
{
    const enum AVSampleFormat *p = codec->sample_fmts;

    while (*p != AV_SAMPLE_FMT_NONE) { // 通过AV_SAMPLE_FMT_NONE(-1)作为结束符
        printf("%s sample_fmt support %d\n",codec->name,*p);
        if (*p == sample_fmt)
            return 1;
        p++;
    }
    return 0;
}

/* 检测该编码器是否支持该采样率 */
static int check_sample_rate(const AVCodec *codec, const int sample_rate)
{
    const int *p = codec->supported_samplerates;
    while (*p != 0)  {// 0作为退出条件,比如libfdk-aacenc.c的aac_sample_rates
        printf("%s support %dhz\n", codec->name, *p);
        if (*p == sample_rate)
            return 1;
        p++;
    }
    return 0;
}

/* 检测该编码器是否支持该采样率, 该函数只是作参考 */
static int check_channel_layout(const AVCodec *codec,  AVChannelLayout ch_layout)
{
    // 不是每个codec都给出支持的 ch_layout,实验测试,ffmpeg自带的aac 编码器中的 ch_layout 就是nullptr
    const AVChannelLayout * avchannelLayout=  codec->ch_layouts;
    if(!avchannelLayout) {
        printf("the codec %s no set channel_layouts\n", codec->name);
        return 1;
    }
    while ((*avchannelLayout).nb_channels != 0) { // 0作为退出条件,比如libfdk-aacenc.c的aac_channel_layout
        printf("%s support channel_layout %d\n", codec->name, (*avchannelLayout).nb_channels);
        if ((*avchannelLayout).nb_channels == ch_layout.nb_channels)
            return 1;
        avchannelLayout++;
    }
    return 0;
}

void printfAVCodec(const AVCodec *avcodec){
    if(avcodec==nullptr){
        cout<<"printfAVCodec error avcodec==nullptr"<<endl;
        return ;
    }
    cout << "avcodec->name = " << avcodec->name << endl;
    cout << "avcodec->long_name = " << avcodec->long_name << endl;

    //该编解码器的 类型,enum AVMediaType type; 类似 AVMEDIA_TYPE_VIDEO,AVMEDIA_TYPE_AUDIO
    cout << "avcodec->type = " << avcodec->type << endl;

    //enum AVCodecID id; 类似 AV_CODEC_ID_AAC,AV_CODEC_ID_H264 , AAC 对应的值是十进制是86018,十六进制是15002
    cout << "avcodec->id = " << avcodec->id << endl;


    /**
     * Codec capabilities.
     * see AV_CODEC_CAP_*
     * int capabilities;
     * AAC 作为 encoder 的值是98,对应的二进制是 0110 0010,对应的如下的三个值 或 起来
     *      #define AV_CODEC_CAP_DR1                 (1 <<  1)
            #define AV_CODEC_CAP_DELAY               (1 <<  5)
            #define AV_CODEC_CAP_SMALL_LAST_FRAME    (1 <<  6)
     */
    cout << "avcodec->capabilities = " << avcodec->capabilities << endl;

    ///< maximum value for lowres supported by the decoder
    /// 视频专用 解码器支持的低分辨率的最大值
    // uint8_t max_lowres;
    cout << "avcodec->max_lowres = " << avcodec->max_lowres << endl;



    ///< array of supported framerates, or NULL if any, array is terminated by {0,0}
    ///const AVRational *supported_framerates;
    ///支持的帧率(仅视频), 类似 每秒25张图片
    if(avcodec->supported_framerates == nullptr){
        cout<<"avcodec->supported_framerates = nullptr"<<endl;
    } else {
        cout<<"avcodec->supported_framerates != nullptr"<<endl;
        const AVRational * avr = avcodec->supported_framerates;
        int i =0;
        while(1){
            if(avr[i].den ==0 && avr[i].num == 0){
                break;
            }
            cout<<"avr["<<i <<"].den = "<< avr[i].den << " avr[" << i << "].num = " << avr[i].num << endl;
            ++i;
        }
    }


    ///< array of supported pixel formats, or NULL if unknown, array is terminated by -1
    ///const enum AVPixelFormat *pix_fmts;
    /// 支持的像素格式(仅视频),类似 AV_PIX_FMT_YUV420P
    if(avcodec->pix_fmts == nullptr){
        cout<<"avcodec->pix_fmts = nullptr"<<endl;
    }else{
        cout<<"avcodec->pix_fmts != nullptr"<<endl;
        const enum AVPixelFormat * avpixelformat = avcodec->pix_fmts;
        int i =0;
        while(1){
            if(avpixelformat[i] == -1){
                break;
            }else{
                cout<<"avpixelformat["<<i<<"] = avpixelformat[i] " <<endl;
                ++i;
            }
        }
    }



    ///< array of supported audio samplerates, or NULL if unknown, array is terminated by 0
    ///     const int *supported_samplerates;
    /// 支持的采样率(仅音频)41000,48000
    if(avcodec->supported_samplerates == nullptr){
        cout<<"avcodec->supported_samplerates = nullptr"<<endl;
    }else{
        cout<<"avcodec->supported_samplerates != nullptr"<<endl;
        const int  * support_samplerates = avcodec->supported_samplerates;
        int i =0;
        while(1){
            if(support_samplerates[i] == 0){
                break;
            }else{
                cout<<"support_samplerates["<<i<<"] = " << support_samplerates[i]<<endl;
                ++i;
            }
        }
    }


    ///支持的采样格式(仅音频),类似 AV_SAMPLE_FMT_S16,FFMpeg 自带的AAC 只支持  AV_SAMPLE_FMT_FLTP,对应的十进制的值是8
    ///< array of supported sample formats, or NULL if unknown, array is terminated by -1
    ///     const enum AVSampleFormat *sample_fmts;

    if(avcodec->sample_fmts == nullptr){
        cout<<"avcodec->sample_fmts = nullptr"<<endl;
    }else{
        cout<<"avcodec->sample_fmts != nullptr"<<endl;
        const enum AVSampleFormat *sample_fmts = avcodec->sample_fmts;
        int i =0;
        while(1){
            if(sample_fmts[i] == -1){
                break;
            }else{
                cout<<"sample_fmts["<<i<<"] = " << sample_fmts[i]<<endl;
                ++i;
            }
        }
    }



    //#if FF_API_OLD_CHANNEL_LAYOUT
    //    /**
    //     * @deprecated use ch_layouts instead
    //     */
    //    attribute_deprecated
    //    const uint64_t *channel_layouts;         ///< array of support channel layouts, or NULL if unknown. array is terminated by 0
    //#endif

    //该编码器支持的声道数量。已经不再使用。可以使用 const AVChannelLayout *ch_layouts; 替代
    const uint64_t *channel_layouts = avcodec->channel_layouts;

    if(channel_layouts == nullptr){
        cout<<"channel_layouts =  nullptr"<<endl; //在ffmepg 6.0的时候,使用编码器ffmpeg自带的AAC,这块为nullptr。这不合理。这里baidu了一下,发现 如下的说法:    // 不是每个codec都给出支持的channel_layout
    }else{
        cout<<"channel_layouts !=  nullptr"<<endl;
        int i = 0;
        while(1){
            if(channel_layouts[i] == 0){
                break;
            }
            cout<<"channel_layouts[<<" << i << "] = " << channel_layouts[i] << endl;
            ++i;
        }
    }

    ///这里为了方便测试 ,将已经废弃的const uint64_t *channel_layouts; 和 const AVChannelLayout *ch_layouts;对比
    /**
     * Array of supported channel layouts, terminated with a zeroed layout.
     */
    ///const AVChannelLayout *ch_layouts;
    const AVChannelLayout *ch_layouts = avcodec->ch_layouts;
    if(ch_layouts == nullptr){
        cout<<"ch_layouts =  nullptr"<<endl; //在ffmepg 6.0的时候,使用编码器ffmpeg自带的AAC,这块为nullptr。这不合理。  baidu说明如下:  // 不是每个codec都给出支持的channel_layout

    }else{
        cout<<"ch_layouts !=  nullptr"<<endl;
        int i = 0;
        while(1){
            if(ch_layouts[i].nb_channels == 0 ){
                break;
            }
            cout<<"ch_layouts[<<" << i << "].nb_channels = " << ch_layouts[i].nb_channels << endl;
            ++i;
        }
    }




    ///< AVClass for the private context
    /// const AVClass *priv_class;
    /// AVClass最主要的作用就是给结构体(例如AVFormatContext等)增加AVOption功能的支持。换句话说AVClass就是AVOption和目标结构体之间的"桥梁"。AVClass要求必须声明为目标结构体的第一个变量。
    const AVClass *priv_class = avcodec->priv_class;
    if(priv_class==nullptr){
        cout << "priv_class =  nullptr" << endl;
    }else {
        cout << "priv_class != nullptr" << endl;
        cout << "priv_class->option->name = " << priv_class->option->name << endl;

    }


    ///< array of recognized profiles, or NULL if unknown, array is terminated by {AV_PROFILE_UNKNOWN}
    ///     const AVProfile *profiles;
    /// 如果非NULL,则为此编解码器识别的配置文件数组。
    /// 该结构描述了由AVCodecID描述的单个编解码器的属性。

    const AVProfile *profiles = avcodec->profiles;
    if(profiles == nullptr){
        cout<<"profiles =  nullptr"<<endl;
    }else{
        cout<<"profiles !=  nullptr"<<endl;
        int i = 0;
        while(1){

            if(profiles[i].profile == AV_PROFILE_UNKNOWN ){
                break;
            }
            cout<<"profiles[<<" << i << "].profiles = " << profiles[i].profile <<  " profiles.name = " << profiles[i].name << endl;
            ++i;
        }
    }


    /**
     * Group name of the codec implementation.
     * This is a short symbolic name of the wrapper backing this codec.
     A wrapper uses some kind of external implementation for the codec,
      such as an external library, or a codec implementation provided by the OS or the hardware.
     * If this field is NULL, this is a builtin, libavcodec native codec.
     * If non-NULL, this will be the suffix in AVCodec.name in most cases (usually AVCodec.name will be of the form "<codec_name>_<wrapper_name>").
       const char *wrapper_name;
    */

    /**
    *编解码器实现的组名称。
    *这是支持此编解码器的包装器的简短符号名称。
    包装器对编解码器使用某种外部实现,
    诸如外部库或由OS或硬件提供的编解码器实现。
    *如果此字段为NULL,则这是一个内置的libavcodec本机编解码器。
    *如果非NULL,在大多数情况下,这将是AVCodec.name中的后缀(通常AVCodec.name的形式为"<codec_name>_<wrapper_name>")。
    */
    const char*wrapper_name = avcodec->wrapper_name;

    if(wrapper_name == nullptr){
        cout<<"wrapper_name = nullptr"<<endl;
    }else{
        cout<<"wrapper_name = " <<wrapper_name <<endl;
    }

    cout<<"debug point "<<endl;

}


void printfAVCodecContext(AVCodecContext *encoderAVCodecContext){

}



int main()
{
    cout << "Hello World!" << endl;
    int ret = 0;

    //1.编码器
    const AVCodec * encoderAVCodec = nullptr;
    //2.编码器上下文
    AVCodecContext * encoderAVCodecContext = nullptr;

    //7.打开输入和输出文件
    const char *in_pcm_file = "D:/AllInformation/qtworkspacenew/08encoder_01_pcmToAAC/48000_2_f32le.pcm";
    FILE *infile = nullptr;

    const char *out_aac_file = "D:/AllInformation/qtworkspacenew/08encoder_01_pcmToAAC/48000_2_f32le.acc";;
    FILE *outfile = nullptr;

    //8.有了文件,就需要从文件读写,创建avframe,用于将pcm文件中的数据读取到avframe中。
    AVFrame *frame = nullptr;

    //9.有了文件,就需要从文件读写,创建 avpacket, 用于将编码后的数据存储到avpacket中。
    AVPacket *pkt = nullptr;

    //12.
    // 12.1 avframe 中的每一个frame 帧中的大小。
    int frame_bytes = 0;
    // 12.2 frame_buf 的目的是从 pcm file 中读取数据到 frame_buf,然后 通过 av_samples_fill_arrays方法读取到 真正的frame中
    uint8_t *frame_buf = nullptr;
    // 12.3 frame_buf_trans 的目的是,如果 frame_buf 的格式不符合条件,需要将 frame_buf中的数据转换,然后存储到 frame_buf_trans 中
    const uint8_t *frame_buf_trans = nullptr;

    //13. 设置pts,在编码阶段设置
    int64_t pts = 0;

    //1.找到编码器
    encoderAVCodec = avcodec_find_encoder(AV_CODEC_ID_AAC);
    if(encoderAVCodec == nullptr){
        ret = -1;
        cout << " avcodec_find_encoder(AV_CODEC_ID_AAC) error " << endl;
        goto pcmtoaacend;
    }
    //1.1 打印编码器信息
    printfAVCodec(encoderAVCodec);

    //2.编码器上下文
    encoderAVCodecContext = avcodec_alloc_context3(encoderAVCodec);
    if(encoderAVCodec == nullptr){
        cout << " avcodec_alloc_context3(AV_CODEC_ID_AAC) error " << endl;
        ret = -2;
        goto pcmtoaacend;
    }

    printfAVCodecContext(encoderAVCodecContext);


    //3.设置 编码器上下文 参数,这一步的原因:要告诉编码器,我要将什么格式的pcm进行编码,pcm数据是纯音频数据,没有头部,如果不告知编码器,编码器就不知道参数,就无从编码
    //3.1 设置哪些参数呢? 告知我们的pcm 是 -ar 48000 -ac 2 -f f32le 的,由于我们使用的是ffmpeg自带的aac编码器,因此 sample_rate必须是48000,sample_fmt必须是AV_SAMPLE_FMT_FLTP
    //那么如果不是,就应该使用音频重采样处理成ffmpeg 自带的可以识别的 采样率,采样格式。
    //那么如果我们使用的lib-fdk,由于lib-fdk可以识别 采样率是44100,采样格式是 AV_SAMPLE_FMT_S16,如果给定的pcm不是AV_SAMPLE_FMT_S16的,也不是44100的,那么就要进行 音频重采样。

    encoderAVCodecContext->sample_rate    = 48000; //48000;  一定要设置,因为在 编码器 中 只是说了 当前编码器支持的 采样率数组,我们要在编码器上下文中设置真正的采样率到底是多少
    encoderAVCodecContext->ch_layout = AV_CHANNEL_LAYOUT_STEREO;  //2  在编码器中只是说了当前编码器支持的 声道数 数组(有些编码器还不说),因此要手动的设定。也是因为有些编码器并不设定支持的 声道数 数组,因此声道数这个值不用判断 是否合理
    if(strcmp(encoderAVCodec->name, "aac") == 0) {
        encoderAVCodecContext->sample_fmt = AV_SAMPLE_FMT_FLTP; //f32le  //我们使用的是ffmpeg自带的aac编码器,ffmpeg自带的aac编码器要求,pcm的格式必须是 AV_SAMPLE_FMT_FLTP
    } else if(strcmp(encoderAVCodec->name, "libfdk_aac") == 0) {
        encoderAVCodecContext->sample_fmt = AV_SAMPLE_FMT_S16;  //s16le
    } else {
        encoderAVCodecContext->sample_fmt = AV_SAMPLE_FMT_FLTP; //f32le
    }
    encoderAVCodecContext->codec_id = encoderAVCodec->id; //设置编码器id,从FFMPEG自带的AAC编码器来看,在通过 第二步,分配编码器上下文的时候,就已经将编码器的 id传递过去了,这里查看avcodec_alloc_context3方法源码,就可以看到encoderAVCodecContext 中的 值是将 encoderAVCodec 的id 和type 拷贝过去的。因此可以不赋值。但是为了保险起见,还是赋值一下比较好
    encoderAVCodecContext->codec_type = encoderAVCodec->type; //设置编码器类型 同上
    encoderAVCodecContext->bit_rate = 128*1024; //设置平均比特率,FFMEPG AAC中有说明,不设置的话,默认也会是128kbps,ffmpeg网站 参考 https://ffmpeg.org/ffmpeg-all.html#Options-11
    encoderAVCodecContext->profile = FF_PROFILE_AAC_LOW;    //设置aac 的规格,这个值也可以不设置,默认也会是 FF_PROFILE_AAC_LOW, ffmpeg网站 参考 https://ffmpeg.org/ffmpeg-all.html#Options-11

    //4. 检测我们给定的pcm的这些值是否支持,只要有一个不支持就需要 音频重采样。
    if (!check_sample_fmt(encoderAVCodec, encoderAVCodecContext->sample_fmt)) {
        fprintf(stderr, "Encoder does not support sample format %s",
                av_get_sample_fmt_name(encoderAVCodecContext->sample_fmt));
        exit(1);
    }
    if (!check_sample_rate(encoderAVCodec, encoderAVCodecContext->sample_rate)) {
        fprintf(stderr, "Encoder does not support sample rate %d", encoderAVCodecContext->sample_rate);
        exit(1);
    }
    if (!check_channel_layout(encoderAVCodec, encoderAVCodecContext->ch_layout)) {
        fprintf(stderr, "Encoder does not support channel layout %lu", encoderAVCodecContext->ch_layout.nb_channels);
        exit(1);
    }

    //5.如果走到这里,说明上述参数都可以使用,那么我们打印一下这些参数
    printf("\n\nAudio encode config\n");
    printf("bit_rate:%ldkbps\n", encoderAVCodecContext->bit_rate/1024);
    printf("sample_rate:%d\n", encoderAVCodecContext->sample_rate);
    printf("sample_fmt:%s\n", av_get_sample_fmt_name(encoderAVCodecContext->sample_fmt));
    printf("channels:%d\n", encoderAVCodecContext->ch_layout.nb_channels);
    // frame_size是在avcodec_open2后进行关联
    printf("1 frame_size:%d\n", encoderAVCodecContext->frame_size); //在没有 "将编码器上下文和编码器进行关联"前,这个值是0,当调用了avcodec_open2方法 -- "将编码器上下文和编码器进行关联"后,aac会将这个值变成 1024
    encoderAVCodecContext->flags = AV_CODEC_FLAG_GLOBAL_HEADER;  //ffmpeg默认的aac是不带adts,而fdk_aac默认带adts,这里我们强制不带

    //6. /* 将编码器上下文和编码器进行关联 从avcodec_open2源码来看,会设置很多的参数  */
    if (avcodec_open2(encoderAVCodecContext, encoderAVCodec, NULL) < 0) {
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    }

    printfAVCodecContext(encoderAVCodecContext);

    printf("2 frame_size:%d\n\n", encoderAVCodecContext->frame_size); // 决定每次到底送多少个采样点,当 avcodec_open2 调用后,这个值是1024

    //7.打开输入和输出文件
    infile = fopen(in_pcm_file, "rb");
    if (!infile) {
        fprintf(stderr, "Could not open %s\n", in_pcm_file);
        goto pcmtoaacend;
    }
    outfile = fopen(out_aac_file, "wb");
    if (!outfile) {
        fprintf(stderr, "Could not open %s\n", out_aac_file);
         goto pcmtoaacend;
    }

    //8.有了文件,就需要从文件读写,创建avframe,用于将pcm文件中的数据读取到avframe中。
    /* frame containing input raw audio */
    frame = av_frame_alloc(); //av_frame_alloc,会分配出来frame的内存,但是不会给frame的data分配空间。在后面的 av_frame_get_buffer方法中,会真正的给 frame的data分配空间。参考AVFrame 结构体中的data。
    if (!frame)
    {
        fprintf(stderr, "Could not allocate audio frame\n");
        goto pcmtoaacend;
    }


    //9.有了文件,就需要从文件读写,创建 avpacket, 用于将编码后的数据存储到avpacket中。
    /* packet for holding encoded output */
    pkt = av_packet_alloc();
    if (!pkt)
    {
        fprintf(stderr, "could not allocate the packet\n");
        goto pcmtoaacend;
    }

    //10.设置 avframe的参数,因为读取的pcm数据 要都是要放在avframe中,因此要告知给 avframe 中读取多少数据。
    /* 每次送多少数据给编码器由:
     *  (1)frame_size(每帧单个通道的采样点数);
     *  (2)sample_fmt(采样点格式);
     *  (3)channel_layout(通道布局情况);
     * 3要素决定
     */
    frame->nb_samples     = encoderAVCodecContext->frame_size;
    frame->format         = encoderAVCodecContext->sample_fmt;
    frame->ch_layout = encoderAVCodecContext->ch_layout;

    printf("frame nb_samples:%d\n", frame->nb_samples);
    printf("frame sample_fmt:%d\n", frame->format);
    printf("frame channel_layout:%lu\n\n", frame->ch_layout);

    //11. 为frame分配 buffer,实际上就是给frame的data分配真正的空间,参考 av_frame_get_buffer 的源码。
    //参考av_frame_get_buffer方法的说明如下
    /*** The following fields must be set on frame before calling this function:
        * - format (pixel format for video, sample format for audio)
        * - width and height for video
        * - nb_samples and ch_layout for audio
        * 第一个参数 frame,会给这个frame分配 data和 extradata的空间
        * 如果这个frame已经分配过了data,那么可能造成 内存泄漏。
        * 第二个参数为0,表示和当前的CPU对齐。而且强烈要求是0,除非您知道你要干啥?
      ***/
    ret = av_frame_get_buffer(frame, 0);
    if (ret < 0)
    {
        fprintf(stderr, "Could not allocate audio data buffers\n");
        goto pcmtoaacend;
    }

    //12. 下来就要将 pcm 的数据 读取到frame中,最终使用的方法是 av_samples_fill_arrays,
    //看一下av_samples_fill_arrays方法的参数,第一个参数表示给 frame->data中写,第二个参数给frame->linesize 中写,
    // 第二个参数表示读取 buf中数据,那么这个buf的大小应该是多少呢?很显然,应该是一个帧 的大小,那么一个帧的大小是多少呢?对于aac,是1024
    //第四个参数,表示 给 frame 读取的数据有多少个 声道
    //第五个参数,表示 给frame中的 样本数多大
    //第六个参数,表示,给frame中的 pcm的格式是啥
    //第七个参数,表示是否字节对齐,0表示字节对齐,0也是默认值。
//    int av_samples_fill_arrays(uint8_t **audio_data, int *linesize,
//                               const uint8_t *buf,
//                               int nb_channels, int nb_samples,
//                               enum AVSampleFormat sample_fmt, int align);

    //12 从av_samples_fill_arrays方法中,我们需要 自己弄一个第三个参数出来,并且告知第三个参数是多大。弄一个const uint8_t * 很容易,那么怎么知道这个buffer的大小呢?
    //我们是给av_samples_fill_arrays方法的第一个参数中写,那么就要知道frame一帧的数据是多大,由于aac的一帧是需要1024个样本,那么大小就是  = 每一个样本的大小 * 1024个样本 * 声道数量
    // 计算出每一帧的数据 单个采样点的字节 * 通道数目 * 每帧采样点数量
    frame_bytes = av_get_bytes_per_sample((AVSampleFormat)frame->format) * frame->ch_layout.nb_channels  * frame->nb_samples;

    //这里我们可以想一下 av_samples_fill_arrays 方法为什么需要这些参数。
    //第一个参数,第二个参数都是给 哪里写,第三个参数要写的buf是啥, 那么4,5,6,7参数是干啥的呢?是告诉ffmpeg我们的内存要怎么分配。

    frame_buf = (uint8_t *)malloc(frame_bytes);
    if(!frame_buf) {
        printf("frame_buf malloc failed\n");
        goto pcmtoaacend;
    }

    frame_buf_trans =  (uint8_t *)malloc(frame_bytes);
    if(!frame_buf_trans) {
        printf("frame_buf_trans malloc failed\n");
        goto pcmtoaacend;
    }

    //13.真正的开始读取数据
    printf("start enode\n");
    while(1){
        //13.1 每次开始从pcm file 读取的时候,先要将 frame_buf的数据清空
        memset((void *)frame_buf, 0, frame_bytes);
        //13.2 当我们将数据清除了后,就应该给frame_buf中写入数据了。返回值read_bytes表示真正读取的数据是多少
        size_t read_bytes = fread(frame_buf, 1, frame_bytes, infile);
        if(read_bytes <= 0){ //注意的是:fread 的返回值正常情况下为真正的读取的数据是多少;如果返回0,表示第二个参数或者第三个参数为0;如果读取到文件尾或者出现错误,则返回负值;fread 不区别文件尾和错误,而调用者必须用 feof 和 ferror 鉴别出现者为何。
            if (feof(infile)) //feof 检查是否已抵达给定文件流的结尾。说明是正常读取文件结束。
               printf("Error reading infile: unexpected end of file\n");
            else if (ferror(infile)) {
                perror("Error reading infile");
            }
            printf("read file finish\n");
            break;

        }
        //如果走到这一步,说明读取的数据是有的,那么开始对这部分数据进行处理
        //那么现在这个数据都早frame_buf中了,这个frame_buf是我们自定义的一块内存,这块内存的中的数据是从pcm中获取的,
        //注意的是:能存储在文件中的pcm数据都是交错模式的,但是ffmpeg 的AAC编码器是不能处理 交错模式的pcm的,因此我们还需要将交错模式的pcm转换成 planar 模式的pcm

        //av_frame_make_writable:确保AVFrame是可写的,尽可能避免数据的复制。 如果AVFrame不是是可写的,将分配新的buffer和复制数据。
        //确保该frame可写, 如果编码器内部保持了内存参考计数,则需要重新拷贝一个备份.目的是新写入的数据和编码器保存的数据不能产生冲突
        ret = av_frame_make_writable(frame);
        if(ret != 0)
            printf("av_frame_make_writable failed, ret = %d\n", ret);

        //注意我们这时候读取到的frame_buf中的数据是 infile文件中的数据。也就是pcm文件,那么这个pcm一般情况下都是 交错模式的(因为只有交错模式的才能播放,一般存放在本地文件的pcm都是交错模式的)

        //ffmpeg自带的aac 只能处理 AV_SAMPLE_FMT_FLTP ; fdk-aac只能处理的是 AV_SAMPLE_FMT_S16模式

        //这里判断,如果使用的fdk-libaac,且我们给编码器设置的就是 AV_SAMPLE_FMT_S16 ,就可以直接处理,因为fdk-libaac是可以直接处理 AV_SAMPLE_FMT_S16
        if((strcmp(encoderAVCodec->name, "libfdk_aac")==0) && AV_SAMPLE_FMT_S16 == frame->format) {
            // 将读取到的PCM数据填充到frame去,但要注意格式的匹配, 是planar还是packed都要区分清楚
            ret = av_samples_fill_arrays(frame->data, frame->linesize,
                                   frame_buf, frame->ch_layout.nb_channels,
                                   frame->nb_samples, (AVSampleFormat)frame->format, 0);
            if(ret <0 ){
                printf("av_samples_fill_arrays  failed AV_SAMPLE_FMT_S16 \n");
                goto pcmtoaacend;
            }
        } else {
            // 走到这里,就是我们读取的数据 是从pcm 的infile中获得的,大部分也 是交错模式的 ,那么这里就有需要将这个交错模式的pcm,转成ffmpeg 内部AAC支持的 AV_SAMPLE_FMT_FLTP planar 模式的,才能处理
            // 这里为了简单,本地测试是直接用的 48000 的 f32le的pcm,因此要将本地的f32le packed模式的数据转为float palanar(通过 f32le_convert_to_fltp),
            memset((void *)frame_buf_trans, 0, frame_bytes);
            f32le_convert_to_fltp((float *)frame_buf, (float *)frame_buf_trans, frame->nb_samples);
            ret = av_samples_fill_arrays(frame->data, frame->linesize,
                                   frame_buf_trans, frame->ch_layout.nb_channels,
                                   frame->nb_samples, (AVSampleFormat)frame->format, 0);
            if(ret <0 ){
                printf("av_samples_fill_arrays  failed AV_SAMPLE_FMT_S16 \n");
                goto pcmtoaacend;
            }
        }

        //走到这里数据就已经在 frame中了,那么下来就要对这个frame进行编码了
        // 设置pts
        pts += frame->nb_samples;
        frame->pts = pts;       // 使用采样率作为pts的单位,具体换算成秒 pts*1/采样率
        ret = encode(encoderAVCodecContext, frame, pkt, outfile);
        if(ret < 0) {
            printf("encode failed\n");
            break;
        }

    }

    //14/* 冲刷编码器 */
    encode(encoderAVCodecContext, NULL, pkt, outfile);



pcmtoaacend:
    // 关闭文件
    fclose(infile);
    fclose(outfile);

    if(frame_buf){
        free((void *)frame_buf);
    }
    if(frame_buf_trans){
        free((void *)frame_buf_trans);
    }
    av_packet_free(&pkt);//av_packet_free是安全的,即使pkt是null,参考源码可知
    av_frame_free(&frame);//av_frame_free是安全的,即使frame是null,参考源码可知
    if(!infile){
        fclose(infile);
    }

    if(!outfile){
        fclose(outfile);
    }

    if(encoderAVCodecContext){
        avcodec_free_context(&encoderAVCodecContext);
    }

    return ret;
}

问题一:

avcodec_receive_packet 不同的返回值代表什么含义;读取的packet如果要放到队列⾥⾯那应该怎么放 到队列?

0代表成功。

如果返回值<0,代表错误,一般错误有三种,前两种不应该叫做错误

* @retval AVERROR(EAGAIN) output is not available in the current state - user must

* try to send input 输出 在当前状态下不可用,user需要重新发送一遍

* @retval AVERROR_EOF the encoder has been fully flushed, and there will be no

* more output packets 编码器已完全刷新,将不再有输出数据包

* @retval AVERROR(EINVAL) codec not opened, or it is a decoder 不是编码器,可能是解码器

/**
 * Read encoded data from the encoder.
 *
 * @param avctx codec context
 * @param avpkt This will be set to a reference-counted packet allocated by the
 *              encoder. Note that the function will always call
 *              av_packet_unref(avpkt) before doing anything else.
 * @retval 0               success
 * @retval AVERROR(EAGAIN) output is not available in the current state - user must
 *                         try to send input
 * @retval AVERROR_EOF     the encoder has been fully flushed, and there will be no
 *                         more output packets
 * @retval AVERROR(EINVAL) codec not opened, or it is a decoder
 * @retval "another negative error code" legitimate encoding errors
 */
int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);
相关推荐
余~~1853816280036 分钟前
NFC 碰一碰发视频源码搭建技术详解,支持OEM
开发语言·人工智能·python·音视频
却道天凉_好个秋1 小时前
音视频学习(二十八):websocket-flv
websocket·音视频·flv
红米饭配南瓜汤1 小时前
WebRTC服务质量(11)- Pacer机制(03) IntervalBudget
网络·网络协议·音视频·webrtc·媒体
0点51 胜2 小时前
[ffmpeg]编译 libx264
ffmpeg
畅联云平台2 小时前
美畅物联丨如何通过视频汇聚平台汇聚视频并推送至上级28181平台
服务器·网络·音视频
一只特立独行的猪6112 小时前
Java处理视频思路
音视频
Say-hai3 小时前
音视频入门知识(六):消息获取模式篇
音视频
Say-hai3 小时前
音视频入门知识(三):音频篇
音视频
Say-hai3 小时前
音视频入门知识(四):封装篇
音视频
Say-hai9 小时前
音视频入门知识(七):时间戳及其音视频播放原理
音视频