iOS: Audio File 音頻文件錄製(支持VBR,CBR)

需求:iOS中使用Audio File 實現音頻文件錄製.


實現原理: 使用Audio File中的API能夠將咱們採集到的音頻數據錄製成音頻文件,這裏採集到的數據包括從Audio Queue/Audio Unit直接採集或Audio Converter間接轉換獲得的音頻數據.


閱讀前提:

本文直接爲實戰篇,如需瞭解理論基礎參考上述連接中的內容,本文側重於實戰中注意點.

本項目須要藉助Audio Queue, Audio Unit的採集,才能實現錄製.因此提供如下兩個Demo.


GitHub地址(附代碼) : Audio Queue錄製, Audio Unit錄製

簡書地址 : Audio File Record

掘金地址 : Audio File Record

博客地址 : Audio File Record


具體實現

1. 建立音頻文件

這裏使用當前格式化時間做爲文件名,命名衝突.ios

下面主要代碼爲建立一個用於存放聲音的音頻文件,主要是在沙盒中建立一個目錄(名爲Voice)存放音頻文件.注意,咱們必定要先將文件夾建立出來,不然在調用後面AudioFileCreateWithURL函數時將報錯.git

- (NSString *)createFilePath {
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    dateFormatter.dateFormat = @"yyyy_MM_dd__HH_mm_ss";
    NSString *date = [dateFormatter stringFromDate:[NSDate date]];
    
    NSArray *searchPaths    = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                                                  NSUserDomainMask,
                                                                  YES);
    
    NSString *documentPath  = [[searchPaths objectAtIndex:0] stringByAppendingPathComponent:@"Voice"];
    
    // 先建立子目錄. 注意,若果直接調用AudioFileCreateWithURL建立一個不存在的目錄建立文件會失敗
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:documentPath]) {
        [fileManager createDirectoryAtPath:documentPath withIntermediateDirectories:YES attributes:nil error:nil];
    }
    
    NSString *fullFileName  = [NSString stringWithFormat:@"%@.caf",date];
    NSString *filePath      = [documentPath stringByAppendingPathComponent:fullFileName];
    return filePath;
}
複製代碼

2. 建立Audio File

經過上面建立的url,再加上咱們要建立的文件類型(iOS中CAF格式文件能夠存聽任意類型音頻數據),音頻流的ASBD格式,文件特性的flag,這裏設置kAudioFileFlags_EraseFile代表CreateURL調用將清空現有文件的內容,若是未設置,則若是文件已存在則CreateURL調用將失敗.github

- (AudioFileID)createAudioFileWithFilePath:(NSString *)filePath AudioDesc:(AudioStreamBasicDescription)audioDesc {
    CFURLRef url            = CFURLCreateWithString(kCFAllocatorDefault, (CFStringRef)filePath, NULL);
    NSLog(@"Audio Recorder: record file path:%@",filePath);
    
    AudioFileID audioFile;
    // create the audio file
    OSStatus status = AudioFileCreateWithURL(url,
                                             kAudioFileCAFType,
                                             &audioDesc,
                                             kAudioFileFlags_EraseFile,
                                             &audioFile);
    if (status != noErr) {
        NSLog(@"Audio Recorder: AudioFileCreateWithURL Failed, status:%d",(int)status);
    }
    
    CFRelease(url);
    
    return audioFile;
}
複製代碼

3. 設置magic cookie

magic cookie: 能夠理解成是文件的頭信息,包含音頻文件播放須要的一些必要信息, magic cookie塊包含某些音頻數據格式(例如MPEG-4 AAC)所需的補充數據,用於解碼音頻數據。若是CAF文件中包含的音頻數據格式須要magic cookie數據,則該文件必須具備此塊。macos

在這裏分爲兩種狀況,若是錄製文件數據CBR(未壓縮數據格式:PCM...),則不須要設置magic cookie, 若是錄製文件數據VBR(壓縮數據格式:AAC...),則須要設置magic cookie.緩存

注意: 採用不一樣技術採集到的音頻,設置magic cookie的方式是不一樣的.bash

  • Audio Queue 設置magic cookie

首先使用kAudioQueueProperty_MagicCookie屬性獲取當前audio queue是否含有magic cookie,若是有,返回magic cookie長度,而後爲它分配一段內存就能夠調用kAudioQueueProperty_MagicCookie獲取audio queue中的magic cookie,最後,將magic cookie經過kAudioFilePropertyMagicCookieData屬性設置到audio file中便可.cookie

- (void)copyEncoderCookieToFileByAudioQueue:(AudioQueueRef)inQueue inFile:(AudioFileID)inFile {
    OSStatus result = noErr;
    UInt32 cookieSize;
    
    result = AudioQueueGetPropertySize (
                                        inQueue,
                                        kAudioQueueProperty_MagicCookie,
                                        &cookieSize
                                        );
    if (result == noErr) {
        char* magicCookie = (char *) malloc (cookieSize);
        result =AudioQueueGetProperty (
                                       inQueue,
                                       kAudioQueueProperty_MagicCookie,
                                       magicCookie,
                                       &cookieSize
                                       );
        if (result == noErr) {
            result = AudioFileSetProperty (
                                           inFile,
                                           kAudioFilePropertyMagicCookieData,
                                           cookieSize,
                                           magicCookie
                                           );
            if (result == noErr) {
                NSLog(@"set Magic cookie successful.");
            }else {
                NSLog(@"set Magic cookie failed.");
            }
        }else {
            NSLog(@"get Magic cookie failed.");
        }
        free (magicCookie);
            
    }else {
        NSLog(@"Magic cookie: get size failed.");
    }

}
複製代碼
  • Audio Converter 設置magic cookie

當使用Audio Unit採集音頻數據時,咱們沒法直接採集AAC類型的數據,須要藉助Audio Converter,原理同上,即從Audio Converter中獲取Magic cookie並設置給audio file.函數

-(void)copyEncoderCookieToFileByAudioConverter:(AudioConverterRef)audioConverter inFile:(AudioFileID)inFile {
    // Grab the cookie from the converter and write it to the destination file.
    UInt32 cookieSize = 0;
    OSStatus error = AudioConverterGetPropertyInfo(audioConverter, kAudioConverterCompressionMagicCookie, &cookieSize, NULL);
    
    if (error == noErr && cookieSize != 0) {
        char *cookie = (char *)malloc(cookieSize * sizeof(char));
        error        = AudioConverterGetProperty(audioConverter, kAudioConverterCompressionMagicCookie, &cookieSize, cookie);
        
        if (error == noErr) {
            error = AudioFileSetProperty(inFile, kAudioFilePropertyMagicCookieData, cookieSize, cookie);
            if (error == noErr) {
                UInt32 willEatTheCookie = false;
                error = AudioFileGetPropertyInfo(inFile, kAudioFilePropertyMagicCookieData, NULL, &willEatTheCookie);
                if (error == noErr) {
                    NSLog(@"%@:%s - Writing magic cookie to destination file: %u cookie:%d \n",kModuleName,__func__, (unsigned int)cookieSize, willEatTheCookie);
                }else {
                    NSLog(@"%@:%s - Could not Writing magic cookie to destination file status:%d \n",kModuleName,__func__,(int)error);
                }
            } else {
                NSLog(@"%@:%s - Even though some formats have cookies, some files don't take them and that's OK,set cookie status:%d \n",kModuleName,__func__,(int)error);
            }
        } else {
            NSLog(@"%@:%s - Could not Get kAudioConverterCompressionMagicCookie from Audio Converter!\n status:%d ",kModuleName,__func__,(int)error);
        }
        
        free(cookie);
    }else {
        // If there is an error here, then the format doesn't have a cookie - this is perfectly fine as som formats do not. NSLog(@"%@:%s - cookie status:%d, %d \n",kModuleName,__func__,(int)error, cookieSize); } } 複製代碼

4. 將數據寫入文件.

經過AudioFileWritePackets能夠將音頻數據寫入文件.post

- (void)writeFileWithInNumBytes:(UInt32)inNumBytes ioNumPackets:(UInt32 )ioNumPackets inBuffer:(const void *)inBuffer inPacketDesc:(const AudioStreamPacketDescription*)inPacketDesc {
    if (!m_recordFile) {
        return;
    }
    
//    AudioStreamPacketDescription outputPacketDescriptions;
    OSStatus status = AudioFileWritePackets(m_recordFile,
                                            false,
                                            inNumBytes,
                                            inPacketDesc,
                                            m_recordCurrentPacket,
                                            &ioNumPackets,
                                            inBuffer);
    
    if (status == noErr) {
        m_recordCurrentPacket += ioNumPackets;  // 用於記錄起始位置
    }else {
        NSLog(@"%@:%s - write file status = %d \n",kModuleName,__func__,(int)status);
    }
    
}
複製代碼

該函數定義以下.ui

  • inUseCache: 寫入數據時是否緩存數據
  • inNumBytes: 寫入數據的大小
  • inPacketDescriptions: VBR格式下音頻數據包的描述信息
  • inStartingPacket: 每次從第多少個包開始寫入,累加過程,因此須要記錄
  • ioNumPackets:當前此次寫入多少個數據包
  • inBuffer: 寫入的音頻數據
extern OSStatus	
AudioFileWritePackets (	AudioFileID							inAudioFile,  
                        Boolean								inUseCache,
                        UInt32								inNumBytes,
                        const AudioStreamPacketDescription * __nullable inPacketDescriptions,
                        SInt64								inStartingPacket, 
                        UInt32								*ioNumPackets, 
                        const void							*inBuffer)			API_AVAILABLE(macos(10.2), ios(2.0), watchos(2.0), tvos(9.0));
複製代碼

5. 中止錄製

注意: 在開啓與關閉錄製時都須要作一次寫magic cookie操做,開始時作是爲了使文件具有magic cookie可用,結束時調用是爲了更新與校訂magic cookie信息.

-(void)stopVoiceRecordAudioConverter:(AudioConverterRef)audioConverter needMagicCookie:(BOOL)isNeedMagicCookie {
    if (isNeedMagicCookie) {
        // reconfirm magic cookie at the end.
        [self copyEncoderCookieToFileByAudioConverter:audioConverter
                                               inFile:m_recordFile];
    }
    
    AudioFileClose(m_recordFile);
    m_recordCurrentPacket = 0;
}
複製代碼
相關文章
相關標籤/搜索