iOS音視頻開發(fā)-音頻硬編碼(AudioToolbox-PCM To AAC)

之前幾篇文章記錄了視頻的軟、硬編碼過程,接下來將記錄下音頻的軟、硬編碼過程,學習、工作之余,以免忘記。
視頻編碼地址:
iOS音視頻開發(fā)-視頻會話捕捉
iOS音視頻開發(fā)-視頻硬編碼(H264)
iOS音視頻開發(fā)-視頻軟編碼(x264編碼H.264文件)
iOS音視頻開發(fā)-視頻軟編碼(FFmpeg+x264編碼H.264文件)


PCM數(shù)據(jù)

PCM(Pulse Code Modulation)也被稱為脈沖編碼調制。PCM音頻數(shù)據(jù)是未經壓縮的音頻采樣數(shù)據(jù)裸流,它是由模擬信號經過采樣、量化、編碼轉換成的標準的數(shù)字音頻數(shù)據(jù)。
移動端對音頻的實時采集編碼傳輸,一般為將采集的音頻數(shù)據(jù)設置為PCM格式數(shù)據(jù),然后將PCM編碼為AAC格式數(shù)據(jù),以便后續(xù)傳輸。
PCM的數(shù)據(jù)格式,這里有篇文章介紹的很好,后續(xù)代碼中的采樣率、聲道等均參考此文章設置。
PCM數(shù)據(jù)格式文章地址:點這里

ADTS

ADTS全稱是(Audio Data Transport Stream),是AAC的一種十分常見的傳輸格式。
將PCM數(shù)據(jù)編碼為AAC的時候需要將每幀的AAC數(shù)據(jù)添加ADTS header,否則將無法解碼播放。
ADTS數(shù)據(jù)格式分為兩部分:
固定頭部:adts_fixed_header
可變頭部:adts_variable_header
詳見Wiki,地址在文章末尾。

主要代碼

1、音頻捕獲代碼

#import "BBAudioCapture.h"
#import <AVFoundation/AVFoundation.h>
#import "BBAudioConfig.h"
#import "BBAudioHardEncoder.h"
#import "BBAudioHardEncoder.h"

@interface BBAudioCapture ()
{
     AudioComponentInstance _outInstance;
}
@property (nonatomic, assign) AudioComponent        component;
@property (nonatomic, strong) AVAudioSession        *session;
@property (nonatomic, strong) BBAudioHardEncoder    *encoder;
@property (nonatomic, strong) NSFileHandle          *handle;
@end

@implementation BBAudioCapture

#pragma mark -- 對象銷毀方法
- (void)dealloc{
    AudioComponentInstanceDispose(_outInstance);
}

#pragma mark -- 對外API(控制是否捕捉音頻數(shù)據(jù))
- (void)startRunning{
    AudioOutputUnitStart(_outInstance);
}

-(void)stopRunning{
    AudioOutputUnitStop(_outInstance);
}

#pragma mark -- 對外API(設置捕獲音頻數(shù)據(jù)配置項)
- (void)setConfig:(BBAudioConfig *)config{
    _config = config;
    [self private_setupAudioSession];
}

#pragma mark -- 私有API(初始化音頻會話)
- (void)private_setupAudioSession{
    
    //0.初始化編碼器
    self.encoder = [[BBAudioHardEncoder alloc] init];
    self.encoder.config = self.config;
    
    //1.獲取音頻會話實例
    self.session = [AVAudioSession sharedInstance];
    
    NSError *error = nil;
    [self.session setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionMixWithOthers | AVAudioSessionCategoryOptionDefaultToSpeaker error:&error];
    
    if (error) {
        NSLog(@"AVAudioSession setupError");
        error = nil;
        return;
    }
    
    //2.激活會話
    [self.session setActive:YES error:&error];
    
    if (error) {
        NSLog(@"AVAudioSession setActiveError");
        error = nil;
        return;
    }
    
    //3.設置模式
    [self.session setMode:AVAudioSessionModeVideoRecording error:&error];
    
    if (error) {
        NSLog(@"AVAudioSession setModeError");
        error = nil;
        return;
    }
    
    //4.設置音頻單元
    AudioComponentDescription acd = {
        .componentType = kAudioUnitType_Output,
        .componentSubType = kAudioUnitSubType_RemoteIO,
        .componentManufacturer = kAudioUnitManufacturer_Apple,
        .componentFlags = 0,
        .componentFlagsMask = 0,
    };
    
    //5.查找音頻單元
    self.component = AudioComponentFindNext(NULL, &acd);
    
    //6.獲取音頻單元實例
    OSStatus status = AudioComponentInstanceNew(self.component, &_outInstance);
    
    if (status != noErr) {
        NSLog(@"AudioSource new AudioComponent error");
        status = noErr;
        return;
    }
    
    //7.設置音頻單元屬性-->可讀寫 0-->不可讀寫 1-->可讀寫
    UInt32 flagOne = 1;
    AudioUnitSetProperty(_outInstance, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &flagOne, sizeof(flagOne));
    
    //8.設置音頻單元屬性-->音頻流
    AudioStreamBasicDescription asbd = {0};
    asbd.mSampleRate = self.config.sampleRate;//采樣率
    asbd.mFormatID = kAudioFormatLinearPCM;//原始數(shù)據(jù)為PCM格式
    asbd.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
    asbd.mChannelsPerFrame = (UInt32)self.config.channels;//每幀的聲道數(shù)量
    asbd.mFramesPerPacket = 1;//每個數(shù)據(jù)包多少幀
    asbd.mBitsPerChannel = 16;//16位
    asbd.mBytesPerFrame = asbd.mChannelsPerFrame * asbd.mBitsPerChannel / 8;//每幀多少字節(jié) bytes -> bit / 8
    asbd.mBytesPerPacket = asbd.mFramesPerPacket * asbd.mBytesPerFrame;//每個包多少字節(jié)
    
    status = AudioUnitSetProperty(_outInstance, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &asbd, sizeof(asbd));
    
    if (status != noErr) {
        NSLog(@"AudioUnitSetProperty StreamFormat error");
        status = noErr;
        return;
    }
    
    //9.設置回調函數(shù)
    AURenderCallbackStruct cb;
    cb.inputProcRefCon = (__bridge void *)self;
    cb.inputProc = audioBufferCallBack;
    
    status = AudioUnitSetProperty(_outInstance, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 1, &cb, sizeof(cb));
    
    if(status != noErr){
        NSLog(@"AudioUnitSetProperty StreamFormat InputCallback error");
        status = noErr;
        return;
    }
    
    //10.初始化音頻單元
    status = AudioUnitInitialize(_outInstance);
    
    if (status != noErr) {
        NSLog(@"AudioUnitInitialize error");
        status = noErr;
        return;
    }
    
    //11.設置優(yōu)先采樣率
    [self.session setPreferredSampleRate:self.config.sampleRate error:&error];
    
    if (error) {
        NSLog(@"AudioSource setPreferredSampleRate error");
        error = nil;
        return;
    }
    
    //12.aac文件夾地址
    NSString *audioPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"test.aac"];
    [[NSFileManager defaultManager] removeItemAtPath:audioPath error:nil];
    [[NSFileManager defaultManager] createFileAtPath:audioPath contents:nil attributes:nil];
    self.handle = [NSFileHandle fileHandleForWritingAtPath:audioPath];
    
}

#pragma mark -- 音頻流回調函數(shù)
static OSStatus audioBufferCallBack(void *inRefCon,
                                    AudioUnitRenderActionFlags *ioActionFlags,
                                    const AudioTimeStamp *inTimeStamp,
                                    UInt32 inBusNumber,
                                    UInt32 inNumberFrames,
                                    AudioBufferList *ioData) {
    @autoreleasepool {
        BBAudioCapture *capture = (__bridge BBAudioCapture *)inRefCon;
        if(!capture) return -1;
        
        AudioBuffer buffer;
        buffer.mData = NULL;
        buffer.mDataByteSize = 0;
        buffer.mNumberChannels = 1;
        
        AudioBufferList buffers;
        buffers.mNumberBuffers = 1;
        buffers.mBuffers[0] = buffer;
        
        OSStatus status = AudioUnitRender(capture->_outInstance,
                                          ioActionFlags,
                                          inTimeStamp,
                                          inBusNumber,
                                          inNumberFrames,
                                          &buffers);
        
        if(status == noErr) {
            [capture.encoder encodeWithBufferList:buffers completianBlock:^(NSData *encodedData, NSError *error) {
                if (error) {
                    NSLog(@"error:%@",error);
                    return;
                }
                
                NSLog(@"write to file!");
                [capture.handle writeData:encodedData];
            }];
        }
        
        return status;
    }
}

@end

2、編碼代碼

#import "BBAudioHardEncoder.h"
#import "BBAudioConfig.h"

@interface BBAudioHardEncoder ()
@property (nonatomic, assign) AudioConverterRef converterRef;
@end
@implementation BBAudioHardEncoder

- (AudioConverterRef)converterRef{
    if (_converterRef == nil) {
        [self private_setupAudioConvert];
    }
    return _converterRef;
}

- (void)dealloc {
    AudioConverterDispose(_converterRef);
}

- (void)private_setupAudioConvert{
    
    //1.輸入流
    AudioStreamBasicDescription inputFormat = {0};
    inputFormat.mSampleRate = self.config.sampleRate;//采樣率
    inputFormat.mFormatID = kAudioFormatLinearPCM;//PCM采樣
    inputFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
    inputFormat.mChannelsPerFrame = (UInt32)self.config.channels;//每幀聲道數(shù)
    inputFormat.mFramesPerPacket = 1;//每包幀數(shù)
    inputFormat.mBitsPerChannel = 16;//每聲道位數(shù)
    inputFormat.mBytesPerFrame = inputFormat.mBitsPerChannel / 8 * inputFormat.mChannelsPerFrame;//每幀的字節(jié)數(shù)
    inputFormat.mBytesPerPacket = inputFormat.mBytesPerFrame * inputFormat.mFramesPerPacket;//每包字節(jié)數(shù)
    
    //2.輸出流
    AudioStreamBasicDescription outputFormat;
    //2.1初始清零
    memset(&outputFormat, 0, sizeof(outputFormat));
    //2.2音頻流,在正常播放情況下的幀率。如果是壓縮的格式,這個屬性表示解壓縮后的幀率。幀率不能為0。
    outputFormat.mSampleRate       = inputFormat.mSampleRate;
    //2.3AAC編碼 kAudioFormatMPEG4AAC kAudioFormatMPEG4AAC_HE_V2
    outputFormat.mFormatID         = kAudioFormatMPEG4AAC;
    //2.4無損編碼,0則無
    outputFormat.mFormatFlags      = kMPEG4Object_AAC_LC;
    //2.5每一個packet的音頻數(shù)據(jù)大小。如果的動態(tài)大小設置為0。動態(tài)大小的格式需要用AudioStreamPacketDescription來確定每個packet的大小。
    outputFormat.mBytesPerPacket   = 0;
    //2.6每幀的聲道數(shù)
    outputFormat.mChannelsPerFrame = (UInt32)self.config.channels;
    //2.7每個packet的幀數(shù)。如果是未壓縮的音頻數(shù)據(jù),值是1。動態(tài)幀率格式,這個值是一個較大的固定數(shù)字,比如說AAC的1024。如果是動態(tài)大小幀數(shù)(比如Ogg格式)設置為0。
    outputFormat.mFramesPerPacket  = 1024;
    //2.8每幀的bytes數(shù),每幀的大小。每一幀的起始點到下一幀的起始點。如果是壓縮格式,設置為0 。
    outputFormat.mBytesPerFrame = 0;
    //2.9語音每采樣點占用位數(shù) 壓縮格式設置為0
    outputFormat.mBitsPerChannel = 0;
    //2.10字節(jié)對齊,填0.
    outputFormat.mReserved = 0;
    
    //3.編碼器參數(shù)
    const OSType subtype = kAudioFormatMPEG4AAC;
    AudioClassDescription requestedCodecs[2] = {
        {
            kAudioEncoderComponentType,
            subtype,
            kAppleSoftwareAudioCodecManufacturer
        },
        {
            kAudioEncoderComponentType,
            subtype,
            kAppleHardwareAudioCodecManufacturer
        }
    };
    
    //4.編碼器
    OSStatus result = AudioConverterNewSpecific(&inputFormat, &outputFormat, 2, requestedCodecs, &_converterRef);
    
    if (result == noErr) {
        NSLog(@"creat convert success!");
    }else{
        NSLog(@"creat convert error!");
        _converterRef = nil;
    }
    
}

- (void)encodeWithBufferList:(AudioBufferList)bufferList completianBlock:(void (^)(NSData *encodedData, NSError *error))completionBlock{
    if (!self.converterRef) {
        return;
    }
    int size = bufferList.mBuffers[0].mDataByteSize;
    
    if (size <= 0) {
        return;
    }
    
    char *aacBuf = malloc(size);
    
    //1.初始化一個輸出緩沖列表
    AudioBufferList outBufferList;
    outBufferList.mNumberBuffers              = 1;
    outBufferList.mBuffers[0].mNumberChannels = bufferList.mBuffers[0].mNumberChannels;
    outBufferList.mBuffers[0].mDataByteSize   = bufferList.mBuffers[0].mDataByteSize; // 設置緩沖區(qū)大小
    outBufferList.mBuffers[0].mData           = aacBuf; // 設置AAC緩沖區(qū)
    UInt32 outputDataPacketSize               = 1;
    
    NSData *data = nil;
    NSError *error = nil;
    OSStatus status = AudioConverterFillComplexBuffer(_converterRef, inputDataProc, &bufferList, &outputDataPacketSize, &outBufferList, NULL);
    if (status == 0){
        NSData *rawAAC = [NSData dataWithBytes:outBufferList.mBuffers[0].mData length:outBufferList.mBuffers[0].mDataByteSize];
        NSData *adtsHeader = [self getADTSDataWithPacketLength:rawAAC.length];
        NSMutableData *fullData = [NSMutableData dataWithData:adtsHeader];
        [fullData appendData:rawAAC];
        data = fullData;
    }else{
        error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
        NSLog(@"音頻編碼失敗");
        return;
    }
    
    if (completionBlock) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            completionBlock(data, error);
        });
    }
    free(aacBuf);
}

#pragma mark -- AudioCallBack
OSStatus inputDataProc(AudioConverterRef inConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData,AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) {
    //填充PCM到緩沖區(qū)
    AudioBufferList bufferList = *(AudioBufferList*)inUserData;
    ioData->mBuffers[0].mNumberChannels = 1;
    ioData->mBuffers[0].mData           = bufferList.mBuffers[0].mData;
    ioData->mBuffers[0].mDataByteSize   = bufferList.mBuffers[0].mDataByteSize;
    ioData->mNumberBuffers              = 1;
    return noErr;
}

/**
 *  Add ADTS header at the beginning of each and every AAC packet.
 *  This is needed as MediaCodec encoder generates a packet of raw
 *  AAC data.
 *
 *  Note the packetLen must count in the ADTS header itself.
 *  See: http://wiki.multimedia.cx/index.php?title=ADTS
 *  Also: http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Channel_Configurations
 **/
- (NSData *)getADTSDataWithPacketLength:(NSInteger)packetLength {
    
    int adtsLength = 7;
    char *packet = malloc(sizeof(char) * adtsLength);
    // Variables Recycled by addADTStoPacket
    int profile = 2;  //AAC LC
    //39=MediaCodecInfo.CodecProfileLevel.AACObjectELD;
    int freqIdx = 4;  //44.1KHz
    int chanCfg = 1;  //MPEG-4 Audio Channel Configuration. 1 Channel front-center
    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[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;
}
@end

以上代碼為主要代碼,配置代碼無非就是采樣率:44.1kHz,聲道:雙聲道。
完整代碼地址:https://github.com/ibabyblue/PCMHardEncodeToAAC
將編碼的AAC數(shù)據(jù)寫入本地文件,利用VLC播放器可以直接播放.aac格式文件,測試很方便。

寫在最后,學習過程中非常感謝Jovins、iossigner分享的干活,感恩!
參考地址:
http://blog.csdn.net/ownwell/article/details/8114121/
https://wiki.multimedia.cx/index.php?title=ADTS
https://developer.apple.com/documentation/audiotoolbox/audio_converter_services?language=objc

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 視頻 視頻實質:純粹的視頻(不包括音頻)實質上就是一組幀圖片,經過視頻編碼成為視頻(video)文件再把音頻(au...
    勇敢的_心_閱讀 3,197評論 1 30
  • H264中的sps pps iOS仿微信小視頻功能開發(fā)優(yōu)化記錄【如何快速的開發(fā)一個完整的iOS直播app】(原理篇...
    CharlyZheng閱讀 1,524評論 0 2
  • 前言 本篇開始講解在Android平臺上進行的音頻編輯開發(fā),首先需要對音頻相關概念有基礎的認識。所以本篇要講解以下...
    Ihesong閱讀 8,047評論 2 18
  • 那一年我二十一歲,在我一生的黃金時代,我有好多奢望。我想愛,想吃,還想在一瞬間變成天上半明半暗的云。后來我才知道,...
    靜聽晨鐘暮鼓閱讀 170評論 0 0
  • 楊帛翰閱讀 166評論 0 0

友情鏈接更多精彩內容