Mac OS使用FFmpeg进行音频AAC编码

一.概述

iOS 使用FFmpeg 实现音视频软编码

上一篇文章中写到的AAC音频编码,因缺少真机测试,有挺多问题,编码后的音频全是噪声。这篇文章使用Mac OS环境,将逐一解决这些问题且编码成功。本文使用的是FFmpeg4.2版本。

二.编码器打开失败问题

FFmpeg编码器不支持AV_SAMPLE_FMT_S16格式。此为packet格式,声道的pcm数据全部放在AVFramedata[0]中,交替存储。

FFmpeg编码器支持AV_SAMPLE_FMT_FLTP格式。此为planar格式,声道的pcm数据会单独保存。例如双声道,左右声道pcm数据会分别保存在AVFramedata[0]data[1]中。

    //找到aac编码器
    pCodec =  avcodec_find_encoder(AV_CODEC_ID_AAC);
    if (!pCodec) {
        printf("Codec not found\n");
        return;
    }
    pCodecContext = avcodec_alloc_context3(pCodec);
    pCodecContext->codec_id = AV_CODEC_ID_AAC;
    pCodecContext->codec_type = AVMEDIA_TYPE_AUDIO;
    //ffmpeg目前只支持FLTP/FLT格式,否则编码器无法打开
    pCodecContext->sample_fmt = AV_SAMPLE_FMT_FLTP;
    pCodecContext->sample_rate = 44100;
    pCodecContext->channel_layout = AV_CH_LAYOUT_STEREO;
    pCodecContext->channels = av_get_channel_layout_nb_channels(pCodecContext->channel_layout);
    pCodecContext->bit_rate = 64000;
    pCodecContext->profile = FF_PROFILE_AAC_LOW ;
    pCodecContext->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;

三、AVFrame初始化内存问题

之前使用的初始化内存方法:

    int size = av_samples_get_buffer_size(NULL, _pCodecContext->channels, _pCodecContext->frame_size, _pCodecContext->sample_fmt, 1);
    uint8_t *buffer = av_malloc(size);
    avcodec_fill_audio_frame(_pFrame, _pCodecContext->channels, _pCodecContext->sample_fmt, buffer, size, 1);

实际上可以用一句代码:

av_frame_get_buffer(pFrame, 0);

有个疑惑的地方是,当选择双声道时,初始化后data[0]data[1]正常,对应的linesize[0] = 4096,而linesize[1]=0,但是编码后双声道的声音都正常。

四、重采样问题

demo中使用的是Audio Unit进行采样,配置AudioStreamBasicDescription中的是:

dataFormat.mFormatFlags     = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;

即样本格式为AV_SAMPLE_FMT_S16,而编码器格式是AV_SAMPLE_FMT_FLTP,而且如果采样率,位元深度,声道数不一致,也需要进行重采样:

    //根据asbd参数设置重采样参数
    SwrContext *swr = swr_alloc();
    uint64_t in_channel_layout = av_get_default_channel_layout(audioDescription.mChannelsPerFrame);
    av_opt_set_int(swr, "in_channel_layout",  in_channel_layout, 0);
    av_opt_set_int(swr, "out_channel_layout", pCodecContext->channel_layout,  0);
    av_opt_set_int(swr, "in_channel_count", audioDescription.mChannelsPerFrame,  0);
    av_opt_set_int(swr, "out_channel_count", pCodecContext->channels,  0);
    av_opt_set_int(swr, "in_sample_rate",     audioDescription.mSampleRate,0);
    av_opt_set_int(swr, "out_sample_rate",    pCodecContext->sample_rate,0);
    av_opt_set_sample_fmt(swr, "in_sample_fmt",  AV_SAMPLE_FMT_S16, 0);
    av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_FLTP,  0);
    swr_init(swr);
    
    //根据Audio Unit传递的数据量,计算样本量
    int in_nb_samples = (int)pcm_size/(audioDescription.mChannelsPerFrame * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16));
    
    //初始化输入数据内存
    uint8_t **input = NULL;
    int src_linesize;
    av_samples_alloc_array_and_samples(&input, &src_linesize,audioDescription.mChannelsPerFrame, in_nb_samples,AV_SAMPLE_FMT_S16, 0);
    *input = pcm_data;
    
    //初始化输出数据内存
    uint8_t *output = NULL;
    //计算实际的样本量,因为不同分辨率转换后,会出现样本量不一致情况
    int out_samples = (int)av_rescale_rnd(swr_get_delay(swr, audioDescription.mSampleRate) + in_nb_samples, pCodecContext->sample_rate, audioDescription.mSampleRate, AV_ROUND_UP);
    av_samples_alloc(&output, NULL, pCodecContext->channels, out_samples, pCodecContext->sample_fmt, 0);
    
    //转换音频格式
    int out_nb_samples = swr_convert(swr,&output,out_samples, (const uint8_t **)input, in_nb_samples);
    if (out_nb_samples < 0) {
        fprintf(stderr, "Could not convert input samples (error )\n");
        return;
    }

五、缓冲问题

采集到的pcm数据一次是1024个字节,经过转换后输出的samples是512个。此demo中AAC编码器需凑齐1024个samples才能进行编码,所以需要使用AVAudioFifo音频缓冲队列:

//根据实际情况realloc队列内存
    ret = av_audio_fifo_realloc(audioFifo, av_audio_fifo_size(audioFifo)+ (int)out_nb_samples);
    if(ret <0){
        printf("av_audio_fifo_realloc == %s\n", av_err2str(ret));
        return;
    }
    //写入队列
    ret = av_audio_fifo_write(audioFifo, (void **)&output,out_nb_samples);
    if(ret <0){
        printf("av_audio_fifo_write == %s\n", av_err2str(ret));
        return;
    }
    //判断达到aac的1024个样本量后,再提取出来编码
    while(av_audio_fifo_size(audioFifo) >= pCodecContext->frame_size)
    {
        int frame_size = FFMIN(av_audio_fifo_size(audioFifo), pCodecContext->frame_size);
        //从队列中提取1024个样本量数据,写入avframe中
        av_audio_fifo_read(audioFifo, (void **)pFrame->data, frame_size);

      (省略)
    }

六、AAC的ADTS头问题

demo中使用的是4.2版FFmpeg,编码中不会自动添加ADTS头,需在每一包编码后的数据前手动添加ADTS头:

void addADTSHead(uint8_t *head,int length,int channels){
    length &= 0x1FFF;
    int sample_index = 4;//此处为44100采样率对应的index
    head[0] = (char)0xff;
    head[1] = (char)0xf1;
    head[2] = (char)(0x40 | (sample_index << 2) | (channels >> 2));
    head[3] = (char)((channels & 0x3) << 6 | (length >> 11));
    head[4] = (char)(length >> 3) & 0xff;
    head[5] = (char)(((length & 0x7) << 5) & 0xff) | 0x1f;
    head[6] = (char)0xfc;
}

七、变速,音量问题(未解决)

当仅仅是音频采集和编码,CPU的占用率在20%~30%,生成的aac文件就很正常。


正常的aac文件波形图

当开启RTMP,推流到本地的nginx服务器,此过程CPU的占用率在100%~200%,此时生成的aac文件就会有变速,音量问题。此问题暂未解决,有了解的同学望不吝赐教。

异常的aac文件波形图

你可能感兴趣的:(Mac OS使用FFmpeg进行音频AAC编码)