音视频iOS音视频处理音视频

IOS使用AudioToolbox实现音频编解码

2020-08-22  本文已影响0人  HelloBinary

音频信息是如何捕捉的呢?主要通过图一的过程:


图一

自然界中的声音非常复杂,波形极其复杂,通常我们采用的是脉冲代码调制编码,即PCM编码。PCM通过抽样、量化、编码三个步骤将连续变化的模拟信号转换为数字编码。

采样后的数据大小 = 采样率值×采样大小值×声道数 bps。一个采样率为44.1KHz,采样大小为16bit,双声道的PCM编码的WAV文件,它的数据速率=44.1K×16×2 bps=1411.2 Kbps= 176.4 KB/s。

AAC高级音频编码

AAC(Advanced Audio Coding),中文名:高级音频编码,出现于1997年,基于MPEG-2的音频编码技术。由Fraunhofer IIS、杜比实验室、AT&T、Sony等公司共同开发,目的是取代MP3格式。

AAC音频格式

AAC音频格式有ADIF和ADTS:

iOS上把PCM音频编码成AAC音频流

  1. 设置编码器(codec),并开始录制;
  2. 收集到PCM数据,传给编码器;
  3. 编码完成回调callback,写入文件。


    image.png

音频压缩原理

AudioToolbox 的具体代码实现

首先定义一个类来做配置,我们要了解下几个概念
采样频率(sampleRate):也称为采样速度或者采样率,定义了每秒从连续信号中提取并组成离散信号的采样个数,它用赫兹(Hz)来表示。采样频率的倒数是采样周期,它是采样之间的时间间隔。通俗的讲采样频率是指计算机每秒钟采集多少个信号样本。采样频率越高声音的还原就越真实越自然。

8,000 Hz是电话所用采样率, 对于人的说话已经足够
11,025 Hz是AM调幅广播所用采样率
22,050 Hz和24,000 Hz- FM是调频广播所用采样率
32,000 Hz是miniDV 数码视频 camcorder、DAT (LP mode)所用采样率
44,100 Hz是音频 CD, 也常用于 MPEG-1 音频(VCD, SVCD, MP3)所用采样率 (超过该采样率,人耳很难分辨)
47,250 Hz是商用 PCM 录音机所用采样率
48,000 Hz是miniDV、数字电视、DVD、DAT、电影和专业音频所用的数字声音所用采样率
50,000 Hz是商用数字录音机所用采样率
96,000 或者 192,000 Hz - DVD-Audio、一些 LPCM DVD 音轨、BD-ROM(蓝光盘)音轨、和 HD-DVD (高清晰度 DVD)音轨所用所用采样率
2.8224 MHz是Direct Stream Digital 的 1 位 sigma-delta modulation 过程所用采样率。

音频比特率

音频的比特率公式: 比特率=采样率 * 单个的周期音频数据长度 (sampleSize)。
如16bit 单声道(channelCount) 48KHz音频的比特率:
48KHz * (16 * 1) = 1536kbps = 192 kBps


@interface SQAudioConfig  : NSObject
@property(nonatomic,assign)NSInteger bitrate;
@property(nonatomic,assign)NSInteger channelCount;
/**采样率*/
@property (nonatomic, assign) NSInteger sampleRate;//(默认44100)
/**采样点量化*/
@property (nonatomic, assign) NSInteger sampleSize;//(16)

@implementation SQAudioConfig
+ (instancetype)defaultConifg {
    return  [[SQAudioConfig alloc] init];
}
- (instancetype)init
{
    self = [super init];
    if (self) {
        self.bitrate = 96000;
        self.channelCount = 1;
        self.sampleSize = 16;
        self.sampleRate = 44100;
//比特率 = 44100*1*16;
    }
    return self;
}
@end

接下来我们要配置我们的编码器,通过AudioConverterNewSpecific来创建一个新专用编码器,根据输入音频数据描述参数和输出音频数据描述,用AudioStreamBasicDescription封装描述信息。

//配置音频编码参数
-(void)setupEncoderWithSampleBuffer: (CMSampleBufferRef)sampleBuffer{
//通过输入的sampleBuffer来获取输入描述信息
    AudioStreamBasicDescription inputAduioDes =* CMAudioFormatDescriptionGetStreamBasicDescription(CMSampleBufferGetFormatDescription(sampleBuffer));
    //设置输出参数
    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.mReserved =  0;                                  //对其方式 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(@"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(@"Error!:硬编码AAC 设置比特率失败");
    }
}
/**
 获取编码器类型描述
 参数1:类型
 */
- (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(@"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);
   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数据进行处理,AVFoudation捕捉到的数据是封装在CMSampleBufferRef这个结构体里。通过CMSampleBufferRef可以获取数据所在结构体CMBlockBufferRef,然后调用CMBlockBufferGetDataPointer来获取数据内存地址。然后通过AudioConverterFillComplexBuffer来实现编码,需要注意的是这个函数需要将pcm数据再进行一次封装(有点绕),封装成AudioBufferList.然后编写我们的回调函数AudioConverterFillComplexBuffer,这个函数主要用于向提供输入数据,也就是你要转换的数据,在这里封装成AudioBufferList。误区:
这外面定义的outAudioBufferList和里面的参数ioData是不同的AudioBufferList一个是输出一个是输入。

/**编码*/
- (void)encodeAudioSamepleBuffer: (CMSampleBufferRef)sampleBuffer{
    CFRetain(sampleBuffer);
    if(!_audioConverter){
         [self setupEncoderWithSampleBuffer:sampleBuffer];
    }
    __weak typeof(self) weakSelf=self;
    dispatch_async(_encoderQueue, ^{
        //3.获取CMBlockBuffer, 这里面保存了PCM数据
        CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
        CFRetain(blockBuffer);
         OSStatus status = CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &_pcmBufferSize, &_pcmBuffer);
        //5.判断status状态
        NSError *error = nil;
        if (status != kCMBlockBufferNoErr) {
            error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
            NSLog(@"Error: ACC encode get data point error: %@",error);
            return;
        }
        uint8_t *pcmBuffer = malloc(weakSelf.pcmBufferSize);
        memset(pcmBuffer, 0, weakSelf.pcmBufferSize);
        //3.输出buffer
        /*
         typedef struct AudioBufferList {
         UInt32 mNumberBuffers;
         AudioBuffer mBuffers[1];
         } AudioBufferList;
         
         struct AudioBuffer
         {
         UInt32              mNumberChannels;
         UInt32              mDataByteSize;
         void* __nullable    mData;
         };
         typedef struct AudioBuffer  AudioBuffer;
         */
        //将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
        UInt32 outputDataPacketSize = 1;
        //配置填充函数,获取输出数据
        //转换由输入回调函数提供的数据
        /*
         参数1: inAudioConverter 音频转换器
         参数2: inInputDataProc 回调函数.提供要转换的音频数据的回调函数。当转换器准备好接受新的输入数据时,会重复调用此回调.
         参数3: inInputDataProcUserData
         参数4: inInputDataProcUserData,self
         参数5: ioOutputDataPacketSize,输出缓冲区的大小
         参数6: outOutputData,需要转换的音频数据
         参数7: outPacketDescription,输出包信息
         */
        status = AudioConverterFillComplexBuffer(_audioConverter, aacEncodeInputDataProc, (__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);
            //添加ADTS头,想要获取裸流时,请忽略添加ADTS头,写入文件时,必须添加
            //            NSData *adtsHeader = [self adtsDataForPacketLength:rawAAC.length];
            //            NSMutableData *fullData = [NSMutableData dataWithCapacity:adtsHeader.length + rawAAC.length];;
            //            [fullData appendData:adtsHeader];
            //            [fullData appendData:rawAAC];
            //将数据传递到回调队列中
            dispatch_async(weakSelf.callbackQueue, ^{
                [_delegate audioEncodeCallBack:rawAAC];
            });
        } else {
            error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
        }
        
        CFRelease(blockBuffer);
        CFRelease(sampleBuffer);
        if (error) {
            NSLog(@"error: AAC编码失败 %@",error);
        }
    });
}
static OSStatus aacEncodeInputDataProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) {
    //获取self
    SQAudioEncoder *aacEncoder = (__bridge SQAudioEncoder *)(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;
}

完整源码

//
//  SQAudioEncoder.h
//  CPDemo
//
//  Created by Sem on 2020/8/13.
//  Copyright © 2020 SEM. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import "SQAVConfig.h"
NS_ASSUME_NONNULL_BEGIN

@protocol SQAudioEncoderDelegate<NSObject>
-(void)audioEncodeCallBack:(NSData *)aacData;
@end
@interface SQAudioEncoder : NSObject
/**编码器配置*/
@property (nonatomic, strong) SQAudioConfig *config;
@property (nonatomic, weak) id<SQAudioEncoderDelegate> delegate;

/**初始化传入编码器配置*/
- (instancetype)initWithConfig:(SQAudioConfig*)config;

/**编码*/
- (void)encodeAudioSamepleBuffer: (CMSampleBufferRef)sampleBuffer;
@end

NS_ASSUME_NONNULL_END

//
//  SQAudioEncoder.m
//  CPDemo
//
//  Created by Sem on 2020/8/13.
//  Copyright © 2020 SEM. All rights reserved.
//

#import "SQAudioEncoder.h"
#import "SQAVConfig.h"
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>
@interface SQAudioEncoder()

@property (nonatomic, strong) dispatch_queue_t encoderQueue;
@property (nonatomic, strong) dispatch_queue_t callbackQueue;

//对音频转换器对象
@property (nonatomic, unsafe_unretained) AudioConverterRef audioConverter;
//PCM缓存区
@property (nonatomic) char *pcmBuffer;
//PCM缓存区大小
@property (nonatomic) size_t pcmBufferSize;

@end
@implementation SQAudioEncoder
- (instancetype)initWithConfig:(SQAudioConfig*)config{
    self = [super init];
    if(self){
        //音频编码队列
        _encoderQueue = dispatch_queue_create("aac hard encoder queue", DISPATCH_QUEUE_SERIAL);
        //音频回调队列
        _callbackQueue = dispatch_queue_create("aac hard encoder callback queue", DISPATCH_QUEUE_SERIAL);
        //音频转换器
        _audioConverter = NULL;
        _pcmBufferSize = 0;
        _pcmBuffer = NULL;
        _config = config;
        if (config == nil) {
            _config = [[SQAudioConfig alloc] init];
        }
    }
    return self;
}
//配置音频编码参数
-(void)setupEncoderWithSampleBuffer: (CMSampleBufferRef)sampleBuffer{
    AudioStreamBasicDescription inputAduioDes =* CMAudioFormatDescriptionGetStreamBasicDescription(CMSampleBufferGetFormatDescription(sampleBuffer));
    //设置输出参数
    AudioStreamBasicDescription outputAudioDes ={0};
    outputAudioDes.mSampleRate = (Float64)_config.sampleRate;   //采样率
    outputAudioDes.mFormatID = kAudioFormatMPEG4AAC;                //输出格式
    outputAudioDes.mFormatFlags = kMPEG4Object_AAC_LC;              // 如果设为0 代表无损编码
    outputAudioDes.mBytesPerPacket = 0;                             //压缩的时候设置0
    outputAudioDes.mFramesPerPacket = 1024;                         //每一个packet帧数 AAC-1024;
    outputAudioDes.mBytesPerFrame = 0;                              //压缩的时候设置0
    outputAudioDes.mChannelsPerFrame = (uint32_t)_config.channelCount; //输出声道数
    outputAudioDes.mBitsPerChannel = 0;                             //数据帧中每个通道的采样位数。压缩的时候设置0
    outputAudioDes.mReserved =  0;                                  //对其方式 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(@"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(@"Error!:硬编码AAC 设置比特率失败");
    }
}
/**
 获取编码器类型描述
 参数1:类型
 */
- (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(@"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);
   for (unsigned int i = 0; i < count; i++) {
        if (type == description[i].mSubType && manufacture == description[i].mManufacturer) {
            desc = description[i];
            return &desc;
        }
    }
    return nil;
}
/**编码*/
- (void)encodeAudioSamepleBuffer: (CMSampleBufferRef)sampleBuffer{
    CFRetain(sampleBuffer);
    if(!_audioConverter){
         [self setupEncoderWithSampleBuffer:sampleBuffer];
    }
    __weak typeof(self) weakSelf=self;
    dispatch_async(_encoderQueue, ^{
        //3.获取CMBlockBuffer, 这里面保存了PCM数据
        CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
        CFRetain(blockBuffer);
         OSStatus status = CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &_pcmBufferSize, &_pcmBuffer);
        //5.判断status状态
        NSError *error = nil;
        if (status != kCMBlockBufferNoErr) {
            error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
            NSLog(@"Error: ACC encode get data point error: %@",error);
            return;
        }
        uint8_t *pcmBuffer = malloc(weakSelf.pcmBufferSize);
        memset(pcmBuffer, 0, weakSelf.pcmBufferSize);
        //3.输出buffer
        /*
         typedef struct AudioBufferList {
         UInt32 mNumberBuffers;
         AudioBuffer mBuffers[1];
         } AudioBufferList;
         
         struct AudioBuffer
         {
         UInt32              mNumberChannels;
         UInt32              mDataByteSize;
         void* __nullable    mData;
         };
         typedef struct AudioBuffer  AudioBuffer;
         */
        //将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
        UInt32 outputDataPacketSize = 1;
        //配置填充函数,获取输出数据
        //转换由输入回调函数提供的数据
        /*
         参数1: inAudioConverter 音频转换器
         参数2: inInputDataProc 回调函数.提供要转换的音频数据的回调函数。当转换器准备好接受新的输入数据时,会重复调用此回调.
         参数3: inInputDataProcUserData
         参数4: inInputDataProcUserData,self
         参数5: ioOutputDataPacketSize,输出缓冲区的大小
         参数6: outOutputData,需要转换的音频数据
         参数7: outPacketDescription,输出包信息
         */
        status = AudioConverterFillComplexBuffer(_audioConverter, aacEncodeInputDataProc, (__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);
            //添加ADTS头,想要获取裸流时,请忽略添加ADTS头,写入文件时,必须添加
            //            NSData *adtsHeader = [self adtsDataForPacketLength:rawAAC.length];
            //            NSMutableData *fullData = [NSMutableData dataWithCapacity:adtsHeader.length + rawAAC.length];;
            //            [fullData appendData:adtsHeader];
            //            [fullData appendData:rawAAC];
            //将数据传递到回调队列中
            dispatch_async(weakSelf.callbackQueue, ^{
                [_delegate audioEncodeCallBack:rawAAC];
            });
        } else {
            error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
        }
        
        CFRelease(blockBuffer);
        CFRelease(sampleBuffer);
        if (error) {
            NSLog(@"error: AAC编码失败 %@",error);
        }
    });
}

static OSStatus aacEncodeInputDataProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) {
    //获取self
    SQAudioEncoder *aacEncoder = (__bridge SQAudioEncoder *)(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;
}

- (void)dealloc {
    if (_audioConverter) {
        AudioConverterDispose(_audioConverter);
        _audioConverter = NULL;
    }
    
}
@end

解码AAC

解码是相反的,我们从AAC输入 ,然后输出PCM,其他和编码差不多,还是通过AudioConverterNewSpecific函数创建音频转换器

 AudioStreamBasicDescription outputAudioDes = {0};
    outputAudioDes.mSampleRate = (Float64)_config.sampleRate;       //采样率
    outputAudioDes.mChannelsPerFrame = (UInt32)_config.channelCount; //输出声道数
    outputAudioDes.mFormatID = kAudioFormatLinearPCM;                //输出格式
    outputAudioDes.mFormatFlags = (kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked); //编码 12
    outputAudioDes.mFramesPerPacket = 1;                            //每一个packet帧数 ;
    outputAudioDes.mBitsPerChannel = 16;                             //数据帧中每个通道的采样位数。
    outputAudioDes.mBytesPerFrame = outputAudioDes.mBitsPerChannel / 8 *outputAudioDes.mChannelsPerFrame;                              //每一帧大小(采样位数 / 8 *声道数)
    outputAudioDes.mBytesPerPacket = outputAudioDes.mBytesPerFrame * outputAudioDes.mFramesPerPacket;                             //每个packet大小(帧大小 * 帧数)
    outputAudioDes.mReserved =  0;                                  //对其方式 0(8字节对齐)

    //输入参数
    AudioStreamBasicDescription inputAduioDes = {0};
    inputAduioDes.mSampleRate = (Float64)_config.sampleRate;
    inputAduioDes.mFormatID = kAudioFormatMPEG4AAC;
    inputAduioDes.mFormatFlags = kMPEG4Object_AAC_LC;
    inputAduioDes.mFramesPerPacket = 1024;
    inputAduioDes.mChannelsPerFrame = (UInt32)_config.channelCount;

解码数据的时候我们自己定义一个结构体来封装ACC数据

typedef struct {
    char * data;
    UInt32 size;
    UInt32 channelCount;
    AudioStreamPacketDescription packetDesc;
} SQAudioUserData;

接下来基本和编码一样,解码源码

//
//  SQAudioDecode.h
//  CPDemo
//
//  Created by Sem on 2020/8/13.
//  Copyright © 2020 SEM. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
@class SQAudioConfig;

NS_ASSUME_NONNULL_BEGIN
/**AAC解码回调代理*/
@protocol SQAudioDecoderDelegate <NSObject>
- (void)audioDecodeCallback:(NSData *)pcmData;
@end
@interface SQAudioDecode : NSObject
@property (nonatomic, strong) SQAudioConfig *config;
@property (nonatomic, weak) id<SQAudioDecoderDelegate> delegate;

//初始化 传入解码配置
- (instancetype)initWithConfig:(SQAudioConfig *)config;

/**解码aac*/
- (void)decodeAudioAACData: (NSData *)aacData;
@end

NS_ASSUME_NONNULL_END

//
//  SQAudioDecode.m
//  CPDemo
//
//  Created by Sem on 2020/8/13.
//  Copyright © 2020 SEM. All rights reserved.
//

#import "SQAudioDecode.h"
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>
#import "SQAVConfig.h"
typedef struct {
    char * data;
    UInt32 size;
    UInt32 channelCount;
    AudioStreamPacketDescription packetDesc;
} SQAudioUserData;
@interface SQAudioDecode()
@property (strong, nonatomic) NSCondition *converterCond;
@property (nonatomic, strong) dispatch_queue_t decoderQueue;
@property (nonatomic, strong) dispatch_queue_t callbackQueue;

@property (nonatomic) AudioConverterRef audioConverter;
@property (nonatomic) char *aacBuffer;
@property (nonatomic) UInt32 aacBufferSize;
@property (nonatomic) AudioStreamPacketDescription *packetDesc;

@end
@implementation SQAudioDecode
//初始化 传入解码配置
- (instancetype)initWithConfig:(SQAudioConfig *)config{
    self =[super init];
    if(self){
        _decoderQueue = dispatch_queue_create("aac hard decoder queue", DISPATCH_QUEUE_SERIAL);
        _callbackQueue = dispatch_queue_create("aac hard decoder callback queue", DISPATCH_QUEUE_SERIAL);
        _audioConverter = NULL;
        _aacBufferSize = 0;
        _aacBuffer = NULL;
        _config = config;
        if (_config == nil) {
            _config = [[SQAudioConfig alloc] init];
        }
        AudioStreamPacketDescription desc = {0};
        _packetDesc = &desc;
        [self setupEncoder];
    }
    return self;
}

- (void)setupEncoder {
    AudioStreamBasicDescription outputAudioDes = {0};
    outputAudioDes.mSampleRate = (Float64)_config.sampleRate;       //采样率
    outputAudioDes.mChannelsPerFrame = (UInt32)_config.channelCount; //输出声道数
    outputAudioDes.mFormatID = kAudioFormatLinearPCM;                //输出格式
    outputAudioDes.mFormatFlags = (kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked); //编码 12
    outputAudioDes.mFramesPerPacket = 1;                            //每一个packet帧数 ;
    outputAudioDes.mBitsPerChannel = 16;                             //数据帧中每个通道的采样位数。
    outputAudioDes.mBytesPerFrame = outputAudioDes.mBitsPerChannel / 8 *outputAudioDes.mChannelsPerFrame;                              //每一帧大小(采样位数 / 8 *声道数)一般是1或者2
    outputAudioDes.mBytesPerPacket = outputAudioDes.mBytesPerFrame * outputAudioDes.mFramesPerPacket;                             //每个packet大小(帧大小 * 帧数)
    outputAudioDes.mReserved =  0;                                  //对其方式 0(8字节对齐)

    //输入参数
    AudioStreamBasicDescription inputAduioDes = {0};
    inputAduioDes.mSampleRate = (Float64)_config.sampleRate;
    inputAduioDes.mFormatID = kAudioFormatMPEG4AAC;
    inputAduioDes.mFormatFlags = kMPEG4Object_AAC_LC;
    inputAduioDes.mFramesPerPacket = 1024;
    inputAduioDes.mChannelsPerFrame = (UInt32)_config.channelCount;
    //填充输出相关信息
    UInt32 inDesSize = sizeof(inputAduioDes);
    AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &inDesSize, &inputAduioDes);
    
    //获取解码器的描述信息(只能传入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(@"Error!:硬解码AAC创建失败, status= %d", (int)status);
        return;
    }
}

/**
 获取解码器类型描述
 参数1:类型
 */
- (AudioClassDescription *)getAudioCalssDescriptionWithType: (AudioFormatID)type fromManufacture: (uint32_t)manufacture {
    
    static AudioClassDescription desc;
    UInt32 decoderSpecific = type;
    //获取满足AAC解码器的总大小
    UInt32 size;
    /**
     参数1:编码器类型(解码)
     参数2:类型描述大小
     参数3:类型描述
     参数4:大小
     */
    OSStatus status = AudioFormatGetPropertyInfo(kAudioFormatProperty_Decoders, sizeof(decoderSpecific), &decoderSpecific, &size);
    if (status != noErr) {
        NSLog(@"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(decoderSpecific), &decoderSpecific, &size, &description);
    if (status != noErr) {
        NSLog(@"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;
}

- (void)dealloc {
    if (_audioConverter) {
        AudioConverterDispose(_audioConverter);
        _audioConverter = NULL;
    }
    
}
/**解码aac*/
- (void)decodeAudioAACData: (NSData *)aacData{
    if (!_audioConverter) { return; }
    __weak typeof(self) weakSelf=self;
    dispatch_async(_decoderQueue, ^{
        //记录aac 作为参数参入解码回调函数
        SQAudioUserData userData = {0};
        userData.channelCount = (UInt32)weakSelf.config.channelCount;
        userData.data = (char *)[aacData bytes];
        userData.size = (UInt32)aacData.length;
        userData.packetDesc.mDataByteSize = (UInt32)aacData.length;
        userData.packetDesc.mStartOffset = 0;
        userData.packetDesc.mVariableFramesInPacket = 0;
        
        //输出大小和packet个数
        UInt32 pcmBufferSize = (UInt32)(2048 * _config.channelCount);
        UInt32 pcmDataPacketSize = 1024;
        //创建临时容器pcm
        uint8_t *pcmBuffer = malloc(pcmBufferSize);
        memset(pcmBuffer, 0, pcmBufferSize);
        
        //输出buffer
        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;
        
        //输出描述
        AudioStreamPacketDescription outputPacketDesc = {0};
        
        //配置填充函数,获取输出数据
        OSStatus status = AudioConverterFillComplexBuffer(_audioConverter, &AudioDecoderConverterComplexInputDataProc, &userData, &pcmDataPacketSize, &outAudioBufferList, &outputPacketDesc);
        if (status != noErr) {
            NSLog(@"Error: AAC Decoder error, status=%d",(int)status);
            return;
        }
        //如果获取到数据
        if (outAudioBufferList.mBuffers[0].mDataByteSize > 0) {
            NSData *rawData = [NSData dataWithBytes:outAudioBufferList.mBuffers[0].mData length:outAudioBufferList.mBuffers[0].mDataByteSize];
            dispatch_async(weakSelf.callbackQueue, ^{
                [weakSelf.delegate audioDecodeCallback:rawData];
            });
        }
        free(pcmBuffer);
        
    });
}
//解码器回调函数
static OSStatus AudioDecoderConverterComplexInputDataProc(  AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData,  AudioStreamPacketDescription **outDataPacketDescription,  void *inUserData) {
    
    
    SQAudioUserData *audioDecoder = (SQAudioUserData *)(inUserData);
    if (audioDecoder->size <= 0) {
        ioNumberDataPackets = 0;
        return -1;
    }
   
    //填充数据
    *outDataPacketDescription = &audioDecoder->packetDesc;
    (*outDataPacketDescription)[0].mStartOffset = 0;
    (*outDataPacketDescription)[0].mDataByteSize = audioDecoder->size;
    (*outDataPacketDescription)[0].mVariableFramesInPacket = 0;
    
    ioData->mBuffers[0].mData = audioDecoder->data;
    ioData->mBuffers[0].mDataByteSize = audioDecoder->size;
    ioData->mBuffers[0].mNumberChannels = audioDecoder->channelCount;
    
    return noErr;
}
@end

上一篇下一篇

猜你喜欢

热点阅读