【iOS音视频学习】AudioToolBox音频硬编码AAC

1,076 阅读6分钟

「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战」。 这篇文章讲解使用AudioToolBox硬编码音频,关于视频的硬编解码,可以看我之前的文章

AudioToolBox

  • 和VideoToolBox一样,一个纯C偏底层的框架
  • 在AAC编码场景下,能通过采集的PCM数据,拿到AAC格式数据

音频相关基础知识

  • 声音三要素
    • 音调(音频)。声音的尖锐浑厚
    • 音量(振幅)。影响声音的大小
    • 音色(谐波),和材质有关
  • 心理声学模型
    • 人类的听觉范围 20HZ-20KHZ
    • 20HZ以下次声波 20KHZ以上 超声波
    • 音频的编码可以将超过人类听觉范围的数据剔除,达到压缩的目的
  • PCM(脉冲编码调制)
    • 模拟信号转为数字信号的数据,可以直接播放
    • 1 抽样
    • 2 量化
    • 3 编码

音频质量

数字音频的质量取决于:采样频率和量化位数这2个参数,为了了保证在时间变化方向上取样点尽量密,取样频率要高;在幅度取值上尽量细,量化⽐比特率要高。直接的结果就是存储容 量及传输信道容量要求的压⼒。

  • 音频信号的传输率 = 取样频率 * 样本量量化⽐比特数 * 通道数,例如
    • 取样频率 = 44.1kHz
    • 样本值的量量化⽐比特数 = 16
    • 普通⽴立体声的信号通道数 = 2
    • 数字信号传输码流⼤大约 1.4M bit/s
    • 一秒钟的数据量量为 1.4Mbit / (8/Byte)
    • 达176.4Byte(字节),等于88200个汉字的数据量量

音频压缩原理

  • 无损压缩(除人耳听不到的声音压缩之外,其他的声音数据都保留,压缩后的数据可以完全复原,短码高频,长码低频)

  • 有损压缩(消除冗余数据),例如丢弃人耳无法听到的那部分

    数字⾳音频信号包含的对⼈人们感受信息影响可以忽略的成分称为冗余,包括

    • 遮蔽效应(较弱的声音被较强的覆盖)
    • 时域遮蔽或频域遮蔽
    • 听觉冗余(听觉范围以外的)

AAC

  • AAC音频格式有ADIF & ADTS
  • ADIF 必须从头解码,无法从中间解码
  • ADTS 可从任意位置解码,适用于网络传输

一 实时接收外界buffer

  • 拿到buffer之后才能创建音频转换器
  • 如果要写成文件,注意需要ADTS头,关于ADTS参考这里
  // 实时编码
- (void)audioEncodeWithSampleBuffer:(CMSampleBufferRef)sampleBuffer {
    CFRetain(sampleBuffer);
    // 判断音频转换器是否创建成功.如果未创建成功.则配置音频编码参数且创建转码器
    if (!_audioConverter) {
        [self setupAudioConverterWithSampleBuffer:sampleBuffer];
    }
    dispatch_async(_encodeQueue, ^{
        // 从sampleBuffer获取CMBlockBuffer, 这里面保存了PCM数据
        CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
        CFRetain(blockBuffer);
        // 获取BlockBuffer中音频PCM数据大小以及PCM音频数据地址
        OSStatus status = CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &_pcmBufferSize, &_pcmBuffer);
        NSError *error = nil;
        if (status != kCMBlockBufferNoErr) {
            error = [**NSError** errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
            NSLog(@"CQAudioEncoder - Error: ACC encode get data point error: %@",error);
            return;
        }
        // PCM->AAC
        // 开辟_pcmBuffsize大小的pcm内存空间
        uint8_t *pcmBuffer = malloc(_pcmBufferSize);
        // 将_pcmBufferSize数据set到pcmBuffer中.
        memset(pcmBuffer, 0, _pcmBufferSize);

        // 将pcmBuffer数据填充到outAudioBufferList 对象中
        AudioBufferList outAudioBufferList = {0};
        outAudioBufferList.mNumberBuffers = 1;
        outAudioBufferList.mBuffers[0].mNumberChannels = (uint32_t)_config.channelCount;
        outAudioBufferList.mBuffers[0].mDataByteSize = (UInt32)_pcmBufferSize;
        outAudioBufferList.mBuffers[0].mData = pcmBuffer;
        
        // 配置填充函数,获取输出数据*

        // 转换由输入回调函数提供的数据*
        /**
        参数1: inAudioConverter 音频转换器*
        参数2: inInputDataProc 回调函数.提供要转换的音频数据的回调函数。当转换器准备好接受新的输入数据时,会重复调用此回调.*
        参数3: inInputDataProcUserData,self*
        参数4: ioOutputDataPacketSize,输出缓冲区的大小*
        参数5: outOutputData,需要转换的音频数据*
        参数6: outPacketDescription,输出包信息 NULL*
        // 输出包大小为1
        UInt32 outputDataPacketSize = 1;
        status = AudioConverterFillComplexBuffer(_audioConverter, audioEncodeCallBack, (__bridge void * _Nullable)(self), &outputDataPacketSize, &outAudioBufferList, NULL);

        if (status == noErr) {
            // 获取数据
            NSData *rawAAC = [NSData dataWithBytes: outAudioBufferList.mBuffers[0].mData length:outAudioBufferList.mBuffers[0].mDataByteSize];
            // 释放pcmBuffer
            free(pcmBuffer);
            NSMutableData *fullData = NSMutableData.data;
            // 添加ADTS头,想要获取裸流时,请忽略添加ADTS头,写入文件时,必须添加*
            // 和AudioToolBox无关,任何平台下,编码AAC都需要遵循的文件规则*
            if (!self.isHaveHeader) {
                NSData *adtsHeader = [self adtsDataForPacketLength:rawAAC.length];
                [fullData appendData:adtsHeader];
                // 码流是实时获取的,只需要拼接一次
                self.isHaveHeader = YES;
            }
            [fullData appendData:rawAAC];
            // 回调数据
            dispatch_async(_callBackQueue, ^{
                if (self.delegate && [self.delegate respondsToSelector:@selector(audioEncoder:didEncodeSuccessWithAACData:)]) {
                    [self.delegate audioEncoder:self didEncodeSuccessWithAACData:fullData];
                }
            });
        } else {
            error = [**NSError** errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
        }
        CFRelease(blockBuffer);
        CFRelease(sampleBuffer);
        if (error) {
            NSLog(@"CQAudioEncoder - Error: AAC编码失败 %@",error);
        }
    });
}

二 创建转换器

  • mSampleRate 采样率
  • mChannelsPerFrame 输出声道数
  • mFormatID 输出格式
  • mFormatFlags 编码格式
  • mFramesPerPacket 每一个packet帧数
  • mBitsPerChannel 数据中每个通道的采样位数
  • mBytesPerFrame 每一帧大小
  • mBytesPerPacket 每个packet大小帧大小 * 帧数)
  • mReserved 对其方式 0(标识8字节对其)
/// *创建音频转换器*
- (void)setupAudioConverterWithSampleBuffer:(CMSampleBufferRef)sampleBuffer {
    // 1 获取输入参数
    AudioStreamBasicDescription inputAduioDes = *CMAudioFormatDescriptionGetStreamBasicDescription( CMSampleBufferGetFormatDescription(sampleBuffer));
    // 2 设置输出参数*
    AudioStreamBasicDescription outputAudioDes = {0};
    outputAudioDes.mSampleRate = (Float64)_config.sampleRate;       *// 采样率*
    outputAudioDes.mFormatID = kAudioFormatMPEG4AAC;                *// 输出格式*
    outputAudioDes.mFormatFlags = kMPEG4Object_AAC_LC;              *// 如果设为0 代表无损编码*
    outputAudioDes.mBytesPerPacket = 0;                             *// 自己确定每个packet 大小*
    outputAudioDes.mFramesPerPacket = 1024;                         *// 每一个packet帧数 AAC-1024;*
    outputAudioDes.mBytesPerFrame = 0;                              *// 每一帧大小*
    outputAudioDes.mChannelsPerFrame = (uint32_t)_config.channelCount; *// 输出声道数*
    outputAudioDes.mBitsPerChannel = 0;                             *// 数据帧中每个通道的采样位数。*
    outputAudioDes.mReserved0;                                  *// 对其方式 0(8字节对齐)*
    // 填充输出相关信息*
    UInt32 outDesSize = sizeof(outputAudioDes);
    AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &outDesSize, &outputAudioDes);
    
    // 获取编码器的描述信息(只能传入software)*
    AudioClassDescription *audioClassDesc = [self getAudioCalssDescriptionWithType:outputAudioDes.mFormatID fromManufacture:kAppleSoftwareAudioCodecManufacturer];
    

    /** *创建converter*

     *参数1:输入音频格式描述*

     *参数2:输出音频格式描述*

     *参数3:class desc的数量*

     *参数4:class desc*

     *参数5:创建的解码器*

     */
    OSStatus status = AudioConverterNewSpecific(&inputAduioDes, &outputAudioDes, 1, audioClassDesc, &_audioConverter);
    if (status != noErr) {
        NSLog(@"CQAudioEncoder -Error!:硬编码AAC创建失败, status= %d", (int)status);
        return;
    }

    // 设置编解码质量*

    /**

*kAudioConverterQuality_Max                              = 0x7F,*

*kAudioConverterQuality_High                             = 0x60,*

*kAudioConverterQuality_Medium                           = 0x40,*

*kAudioConverterQuality_Low                              = 0x20,*

*kAudioConverterQuality_Min                              = 0*

**/*

    UInt32 temp = kAudioConverterQuality_High;

    *// 编解码器的呈现质量*

    AudioConverterSetProperty(_audioConverter, kAudioConverterCodecQuality, sizeof(temp), &temp);

    

    *// 设置比特率*

    uint32_t audioBitrate = (uint32_t)self.config.bitrate;

    uint32_t audioBitrateSize = sizeof(audioBitrate);

    status = AudioConverterSetProperty(_audioConverter, kAudioConverterEncodeBitRate, audioBitrateSize, &audioBitrate);

    if (status != noErr) {

        NSLog(@"AudioAudioEncoder - Error!:硬编码AAC 设置比特率失败");

    }

}

\


/**

 获取编码器类型描述*
  @param* type *类型*
  @param* manufacture *制造商,填Apple*
 */
- (AudioClassDescription *)getAudioCalssDescriptionWithType:(AudioFormatID)type fromManufacture:(uint32_t)manufacture {

    static AudioClassDescription desc;

    UInt32 encoderSpecific = type;

    // 获取满足AAC编码器的总大小*
    UInt32 size;
    
    /**
     *参数1:编码器类型*
     *参数2:类型描述大小*
     *参数3:类型描述*
     *参数4:大小*
     */
    OSStatus status = AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders, sizeof(encoderSpecific), &encoderSpecific, &size);
    if (status != noErr) {
        NSLog(@"CQAudioEncoder - Error!:硬编码AAC get info 失败, status= %d", (int)status);
        return nil;
    }
    // 计算aac编码器的个数*
    unsigned int count = size / sizeof(AudioClassDescription);
    // 创建一个包含count个编码器的数组*
    AudioClassDescription description[count];
    // 将满足aac编码的编码器的信息写入数组*
    status = AudioFormatGetProperty(kAudioFormatProperty_Encoders, sizeof(encoderSpecific), &encoderSpecific, &size, &description);
    if (status != noErr) {
        NSLog(@"CQAudioEncoder - Error!:硬编码AAC get propery 失败, status= %d", (int)status);
        return nil;
    }
    for (unsigned int i = 0; i < count; i++) {
        if (type == description[i].mSubType && manufacture == description[i].mManufacturer) {
            desc = description[i];
            return &desc;
        }
    }
    return nil;

}

三 在回调里填充数据

// 编码器回调函数(不断填充PCM数据)*
static OSStatus audioEncodeCallBack(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) {
    CQAudioEncoder *aacEncoder = (__bridge **CQAudioEncoder** *)(inUserData);
    // 判断pcmBuffsize大小
    if (!aacEncoder.pcmBufferSize) {
        *ioNumberDataPackets = 0;
        return  - 1;
    }
    // 填充
    ioData->mBuffers[0].mData = aacEncoder.pcmBuffer;
    ioData->mBuffers[0].mDataByteSize = (uint32_t)aacEncoder.pcmBufferSize;
    ioData->mBuffers[0].mNumberChannels = (uint32_t)aacEncoder.config.channelCount;
    // 填充完毕,则清空数据
    aacEncoder.pcmBufferSize = 0;
    ioNumberDataPackets = 1;
    return noErr;

}