Requirement: Use Audio File to record Audio files in iOS.


Implementation principle: The API in Audio File can be used to record the Audio data we collected into Audio files, including Audio data directly collected from the Audio Queue/Audio Unit or Audio Converter indirect conversion of Audio data.


Prerequisites for reading:

  • This article needs to use three data sources to achieve Audio data recording: Audio Queue, Audio Converter
  • Fundamentals of Core Audio: Simple books, nuggets, blogs
  • Audio Collection: Audio Queue Brief books, Nuggets, blogs
  • Simple books, nuggets, blogs
  • Basic knowledge of C and C++

This article is directly for the actual combat, if you need to understand the theoretical basis refer to the above links, this article focuses on the actual combat attention points.

This project requires the collection of Audio Queue and Audio Unit to achieve recording. So here are two demos.


GitHub address (with code)Audio recording Queue.Audio recording Unit

Letter Address:Audio File Record

Address of nuggets:Audio File Record

Blog Address:Audio File Record


The specific implementation

1. Create an audio file

This uses the current formatting time as the file name, naming conflicts.

The main code below is to create an audio file to store the sound, mainly in the sandbox to create a directory (named Voice) to store the audio file. Note that we must create the folder first, otherwise we will get an error when calling the AudioFileCreateWithURL function behind it.

- (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"]; // Create a subdirectory first. 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;
}
Copy the code

2. Create Audio File

Using the URL created above, the type of file we want to create (CAF files in iOS can hold any type of audio data), the ASBD format of the audio stream, and the flag of the file feature, setting kAudioFileFlags_EraseFile indicates that the CreateURL call will empty the existing file The CreateURL call will fail if the file already exists.

- (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;
}
Copy the code

3. Set magic cookie

Magic Cookie: Can be understood as the header information of a file, containing some necessary information for audio file playback. Magic Cookie blocks contain supplementary data required by some audio data formats (such as MPEG-4 AAC) for decoding audio data. If the audio data format contained in the CAF file requires Magic cookie data, the file must have this block.

Here is divided into two cases, if recorded file data CBR(uncompressed data format :PCM…) , do not need to set magic cookie, if the record file data VBR(compressed data format :AAC…) , you need to set magic cookie.

Note: The way of setting magic cookie is different for the audio collected by different technologies.

  • Audio Queue Sets the Magic cookie

First use the kAudioQueueProperty_MagicCookie property to find if the current Audio queue contains a magic cookie, if so, return magic Cookie length, and then allocate some memory to it so you can call kAudioQueueProperty_MagicCookie to get the magic cookie in the Audio queue, and finally, magic Cookie by kAudioFilePropertyMagicCookieData attribute is set to the audio file.

- (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."); }}Copy the code
  • Audio Converter set Magic cookie

When using Audio Unit to collect Audio data, we cannot directly collect data of AAC type, so we need to use Audio Converter. The principle is the same as above, that is, obtain Magic cookie from Audio Converter and set it to 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); }}Copy the code

4. Write data to a file.

Audio data can be written to a file by AudioFileWritePackets.

- (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; // Start position}else {
        NSLog(@"%@:%s - write file status = %d \n",kModuleName,__func__,(int)status); }}Copy the code

The function is defined as follows.

  • InUseCache: specifies whether to cache data when writing data
  • InNumBytes: indicates the size of the data to be written
  • InPacketDescriptions: Description of audio packets in VBR format
  • InStartingPacket: Indicates the number of packets that are written from each time
  • IoNumPackets: indicates the number of packets to be written at the current time
  • InBuffer: Indicates the written audio data
extern OSStatus	
AudioFileWritePackets (	AudioFileID							inAudioFile,  
                        Boolean								inUseCache,
                        UInt32								inNumBytes,
                        const AudioStreamPacketDescription * __nullable inPacketDescriptions,
                        SInt64								inStartingPacket, 
                        UInt32								*ioNumPackets, 
                        const void							*inAPI_AVAILABLE(MacOS (10.2), ios(2.0), Watchos (2.0), TVOs (9.0));Copy the code

5. Stop recording

Note: Magic cookie writing operation is required when recording is turned on or off. It is done at the beginning to make magic cookie available to the file, and it is called at the end to update and correct magic cookie information.

-(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;
}
Copy the code