美文网首页ios developers音视频从入门到放弃
Mac OS使用FFmpeg进行音频AAC编码

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

作者: 陆离o | 来源:发表于2020-03-27 22:21 被阅读0次

    一.概述

    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编码

        本文链接:https://www.haomeiwen.com/subject/jbnruhtx.html