美文网首页音频
AudioToolBox音频编解码(八)

AudioToolBox音频编解码(八)

作者: 仙人掌__ | 来源:发表于2019-06-07 17:08 被阅读0次

    前言

    AAC音频数据格式是一个很常见的音频压缩编码数据格式,经常会有这样的需求,通过麦克风采集到的PCM格式音频数据,编码为AAC格式音频数据,然后通过网络发给另一端,网络的另一端接收到AAC格式音频数据后,解码为PCM格式音频数据。本文将介绍用AudioToobox框架实现音频的编解码

    AAC数据格式封装格式ADTS解析

    原始音频编码后形成的裸的AAC数据是无法直接解码的,一般会在前面添加能够描述音频信息(如采样率,采样格式,声道数)的头部信息,常见的有ADTS封装格式,如下:
    (ADTS头部)(压缩的AAC音频数据)
    ADTS头部
    它描述了音频的采样率,采样格式,声道数等信息;具体格式参考如下网站:
    http://wiki.multimedia.cx/index.php?title=ADTS
    http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Channel_Configurations
    下面是由压缩的AAC数据长度/声道数,采样率为44.1kHz生成ADTS头部的代码:

    - (NSData *)getADTSDataWithPacketLength:(NSInteger)packetLength channel:(int)channel
    {
        
        int adtsLength = 7;
        char *packet = malloc(sizeof(char) * adtsLength);
        // Variables Recycled by addADTStoPacket
        int profile = 2;  //AAC LC      编码压缩级别
        int freqIdx = 4;  //44.1KHz     // 采样率
        int chanCfg = channel;          // 声道数
        NSUInteger fullLength = adtsLength + packetLength;
        // fill in ADTS data
        packet[0] = (char)0xFF; // 11111111     = syncword
        packet[1] = (char)0xF9; // 1111 1 00 1  = syncword MPEG-2 Layer CRC
    //    packet[1] = (char)0xF1; // 1111 0 00 1  = syncword MPEG-4 Layer CRC
        packet[2] = (char)(((profile-1)<<6) + (freqIdx<<2) +(chanCfg>>2));
        packet[3] = (char)(((chanCfg&3)<<6) + (fullLength>>11));
        packet[4] = (char)((fullLength&0x7FF) >> 3);
        packet[5] = (char)(((fullLength&7)<<5) + 0x1F);
        packet[6] = (char)0xFC;
        NSData *data = [NSData dataWithBytesNoCopy:packet length:adtsLength freeWhenDone:YES];
        return data;
    }
    

    下面是由一段ADTS数据解析出声道数,采样率的代码

    // 解析ADTS 头部的采样率,声道数等信息
    - (ADAudioFormat)getADTSInfo:(NSData *)adtsData
    {
        const unsigned char buff[10];
        [adtsData getBytes:(void*)buff length:adtsData.length];
        
        unsigned long long adts = 0;
        const unsigned char *p = buff;
        adts |= *p ++; adts <<= 8;
        adts |= *p ++; adts <<= 8;
        adts |= *p ++; adts <<= 8;
        adts |= *p ++; adts <<= 8;
        adts |= *p ++; adts <<= 8;
        adts |= *p ++; adts <<= 8;
        adts |= *p ++;
        
        ADAudioFormat format;
        // 获取声道数
        format.channels = (adts >> 30) & 0x07;
        // 获取采样率
        format.samplerate = (adts >> 34) & 0x0f;
        return format;
    }
    

    AudioToolbox编码PCM音频数据为AAC音频数据

    1、获取原始PCM音频数据
    这里用AVCaptureSession实现。代码如下

    // 1、初始化AVCaptureSession
    self.captureSession = [[AVCaptureSession alloc] init];
    
    // 2、获取音频设备对象
    AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
    
    // 3、根据音频设备对象生成对应的音频输入对象,并将该音频输入对象添加到AVCaptureSession中
    self.audioCaptureInput = [[AVCaptureDeviceInput alloc] initWithDevice:audioDevice error:nil];
    if ([self.captureSession canAddInput:self.audioCaptureInput]) {
        NSLog(@"添加了输入");
        [self.captureSession addInput:self.audioCaptureInput];
    }
    
    // 4、创建音频数据输出对象并将该输出对象添加到AVCaptureSession中
    self.audioCaptureOutput = [[AVCaptureAudioDataOutput alloc] init];
    if ([self.captureSession canAddOutput:self.audioCaptureOutput]) {
        NSLog(@"添加了输出");
        [self.captureSession addOutput:self.audioCaptureOutput];
    }
    
    // 5、设置音频输出对象的回调
    [self.audioCaptureOutput setSampleBufferDelegate:self queue:_audioQueue];
    
    // 6、启动运行
    [self.captureSession startRunning];
    

    2、在回调中获取原始音频数据和数据格式

    - (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
    {
        if (self.audioCaptureOutput == output) {    // 音频输出
        }
    }
    

    这里解释下CMSampleBufferRef,它是一个描述音视频数据对象的结构体,它既可以用于描述压缩的音/视频,也可以用于描述原始的音/视频;位于CoreMedia下的CMSampleBuffer.h头文件下,一般是AVCaptureOutput生成的对象
    1、它包含了音/视频的数据格式
    2、它包含了具体的音/视频数据(压缩或者未压缩的)
    然后将CMSampleBufferRef中的原始音频数据的格式及具体的PCM音频数据提取出来

    // 提取音频数据
    CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
    CFRetain(blockBuffer);
    size_t dataSize=0;
    char *buffer = (char*)malloc(1024*10*4);
    OSStatus status = CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &dataSize, &buffer);
    if (status != kCMBlockBufferNoErr) {
        NSLog(@"没有出错");
    } else {
        NSLog(@"获取的数据大小 %ld",dataSize);
    }
    

    // 提取音频数据格式

    // 初始化
    AudioStreamBasicDescription inASBD = *CMAudioFormatDescriptionGetStreamBasicDescription(CMSampleBufferGetFormatDescription(sampleBuffer));
    

    3、初始化编码器
    首先定义压缩后的aac格式描述对象

    AudioStreamBasicDescription destASBD = {0};
    destASBD.mFormatID = kAudioFormatMPEG4AAC;      // 表示AAC编码
    destASBD.mFormatFlags = kMPEG4Object_AAC_LC;    // aac 编码的 profile
    destASBD.mSampleRate = sourceASBD.mSampleRate;  // 采样率不变,与原始PCM数据保持一致
    destASBD.mChannelsPerFrame = sourceASBD.mChannelsPerFrame;  // 声道数不变,与原始PCM数据保持一致
    destASBD.mFramesPerPacket = 1024;   // 对于aac的固定码率方式 值为1024
    destASBD.mBitsPerChannel = 0;       // 填0就好
    destASBD.mBytesPerFrame = 0;        // 填0就好
    destASBD.mBytesPerPacket = 0;       // 填0就好
    destASBD.mReserved = 0;
    

    然后根据输入PCM音频数据数据格式和这里定义的压缩后的AAC编码的数据格式创建编码转换器对象AudioConverterRef
    这里解释下AudioConverterRef
    位于AudioToolbox下的AudioConvert.h中定义,它的作用如下:
    1、编码
    2、解码
    3、采样率/声道数/采样格式的转换

    - (id)initWithSourceASBD:(AudioStreamBasicDescription)sourceASBD destASBD:(AudioStreamBasicDescription)destASBD
    {
        if (self = [super init]) {
            
            // 创建用于格式转化的缓冲区,避免重复创建内存
            _buffer = (uint8_t*)malloc(max_buffer_size);
            _bufferSize = max_buffer_size;
            memset(_buffer, 0, _bufferSize);
            _sourceASBD = sourceASBD;
            _destASBD = destASBD;
            
            // 编码器参数
            AudioClassDescription classspecific = [self classDesWithFormatPropertyId:kAudioFormatProperty_Encoders subType:kAudioFormatMPEG4AAC manufacturer:kAppleHardwareAudioCodecManufacturer];
            // 根据指定的格式参数创建格式转换器
            CheckStatusReturn(AudioConverterNewSpecific(&sourceASBD, &destASBD, 1, &classspecific, &_audioConverter),@"AudioConverterNewSpecific fail");
    //        //也可以用下面这种方式
    //        const OSType subtype = kAudioFormatMPEG4AAC;
    //        AudioClassDescription classspecific[2] = {
    //            {
    //                kAudioEncoderComponentType,
    //                subtype,
    //                kAppleSoftwareAudioCodecManufacturer
    //            },
    //        };
    //        CheckStatusReturn(AudioConverterNewSpecific(&sourceASBD, &destASBD, 2, classspecific, &_audioConverter),@"AudioConverterNewSpecific fail");
        }
        return self;
    }
    

    先看一下classDesWithFormatPropertyId方法,它定义了编码相关的参数,比如是使用硬编码还是软编码等等

    /** 创建格式转换器时的参数
     */
    - (AudioClassDescription)classDesWithFormatPropertyId:(AudioFormatPropertyID)formatPropertyId
                                                  subType:(AudioFormatID)type
                                             manufacturer:(UInt32)manufacturer
    {
        AudioClassDescription returnClassDes = {0};
        
        OSStatus status = noErr;
        // 获取指定的AudioFormatPropertyID的格式参数下inSpecifier类型为type的有多少个AudioClassDescription表示的属性
        UInt32 size;
        status = AudioFormatGetPropertyInfo(formatPropertyId, sizeof(type), &type, &size);
        if (status != noErr) {
            NSLog(@"AudioFormatGetPropertyInfo fail %d",status);
        }
        
        // 获取指定的AudioFormatPropertyID的格式参数下inSpecifier类型为type的指定数目的AudioClassDescription表示的属性
        UInt32 count = size / sizeof(AudioClassDescription);
        AudioClassDescription descriptions[count];
        status = AudioFormatGetProperty(formatPropertyId,
                                    sizeof(type),
                                    &type,
                                    &size,
                                    descriptions);
        if (status) {
            NSLog(@"error getting audio format propery: %d", (int)(status));
        }
        
        // 匹配指定的属性,然后拷贝过去
        for (unsigned int i = 0; i < count; i++) {
            if ((type == descriptions[i].mSubType) &&
                (manufacturer == descriptions[i].mManufacturer)) {
                memcpy(&returnClassDes, &(descriptions[i]), sizeof(returnClassDes));
            }
        }
        
        return returnClassDes;
    }
    

    然后使用AudioConverterNewSpecific()方法创建编码器
    4、传入原始音频数据进行编码
    对于第二步的回调函数提供的音频数据是CMSampleBufferRef类型的数据,我们可以将其中的音频数据提取出来组装成AudioUnitBuffList格式,如下

    // 输入音频的数据格式
    size_t size;
    const AudioChannelLayout *layout =  CMAudioFormatDescriptionGetChannelLayout(CMSampleBufferGetFormatDescription(sampleBuffer), &size);
    AudioBufferList inputBufferlist;
    inputBufferlist.mNumberBuffers = 1;
    inputBufferlist.mBuffers[0].mNumberChannels = AudioChannelLayoutTag_GetNumberOfChannels(layout->mChannelLayoutTag);
    inputBufferlist.mBuffers[0].mData = malloc(dataSize);
    inputBufferlist.mBuffers[0].mDataByteSize = (UInt32)dataSize;
    memcpy(inputBufferlist.mBuffers[0].mData, buffer, dataSize);
    
    // 将PCM数据编码为ADTS封装的AAC数据格式
    - (BOOL)doEncodeBufferList:(AudioBufferList)fromBufferList toADTSData:(NSData**)todata
    {
        if (!_audioConverter) {
            NSLog(@"转换器还没有创建");
            return NO;
        }
        int size = fromBufferList.mBuffers[0].mDataByteSize;
        
        if (size<= 0) {
            NSLog(@"fromBufferList 中没有数据");
            return NO;
        }
        
        // 对于编码数据来说 没有planner的概念
        UInt32  channel = fromBufferList.mBuffers[0].mNumberChannels;
        AudioBufferList outAudioBufferList = {0};
        outAudioBufferList.mNumberBuffers = 1;
        outAudioBufferList.mBuffers[0].mNumberChannels = channel;
        outAudioBufferList.mBuffers[0].mDataByteSize = _bufferSize;
        outAudioBufferList.mBuffers[0].mData = _buffer;
        UInt32 ioOutputDataPacketSize = 1;
        
        OSStatus status = AudioConverterFillComplexBuffer(_audioConverter,inInputDataProc, &fromBufferList, &ioOutputDataPacketSize,&outAudioBufferList, NULL);
        
        if (status == 0){
            NSData *rawAAC = [NSData dataWithBytes:outAudioBufferList.mBuffers[0].mData length:outAudioBufferList.mBuffers[0].mDataByteSize];
            NSData *adtsHeader = [self getADTSDataWithPacketLength:rawAAC.length channel:channel];
            NSMutableData *fullData = [NSMutableData dataWithData:adtsHeader];
            [fullData appendData:rawAAC];
            *todata = fullData;
        }else{
            NSLog(@"音频编码失败");
            return NO;
        }
        
        return YES;
    }
    

    请看NSData *adtsHeader = [self getADTSDataWithPacketLength:rawAAC.length channel:channel];这一行
    ,他的作用是调用前面介绍的ADTS头部生成函数生成7个字节的ADTS头部
    最后将ADTS头部与原始的AAC裸数据合并组成ADTS封装格式的数据然后返回

    以上就是音频编码为AAC并封装为ADTS格式的全过程,最后我们可以将最后一步获取到的adts格式数据直接写入到.aac后缀结尾的文件中,生成的文件可以直接用PC或者手机播放

    AAC数据解码为PCM音频数据

    前面我们知道,要解码AAC格式的压缩数据,必须要有头部信息才行,比如ADTS封装格式的AAC数据,接下来我们就简单点,直接解码前面第四步获取到的ADTS数据
    1、首先创建解码器
    解码器和编码器一样,都是由AudioConverterRef实现的,他们是个逆过程,也就是前面第3步中代码行的AudioConverterNewSpecific(&sourceASBD,&destASBD, 1, &classspecific, &_audioConverter))中的参数sourceASBD和destASBD调换一下即可
    其它调用
    2、AAC解码为PCM
    解码之前首先得将ADTS头部信息剥离出来,并且解析出采样率,采样格式等等信息

    Byte crcFlag;
        [fromData getBytes:&crcFlag range:NSMakeRange(1, 1)];
        // 先去掉头部
        NSData *realyData = nil;
        NSData *adtsData = nil;
        if (crcFlag & 0x08) {   // 说明ADTS头部占用7个字节
            realyData = [fromData subdataWithRange:NSMakeRange(7, fromData.length-7)];
            adtsData = [fromData subdataWithRange:NSMakeRange(0,7)];
        } else {                // 说明ADTS头部占用9个字节
            realyData = [fromData subdataWithRange:NSMakeRange(9, fromData.length-9)];
            adtsData = [fromData subdataWithRange:NSMakeRange(0,9)];
        }
        
        ADAudioFormat format = [self getADTSInfo:adtsData];
        
        AudioBufferList fromBufferlist;
        fromBufferlist.mNumberBuffers = 1;
        fromBufferlist.mBuffers[0].mNumberChannels = format.channels;
        fromBufferlist.mBuffers[0].mData = (void*)malloc(realyData.length);
        fromBufferlist.mBuffers[0].mDataByteSize = (UInt32)realyData.length;
    

    其它代码与编码一模一样

    参考Demo

    Demo

    相关文章

      网友评论

        本文标题:AudioToolBox音频编解码(八)

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