美文网首页音频
iOS音频学习二之AudioFile

iOS音频学习二之AudioFile

作者: 程序狗 | 来源:发表于2018-02-27 17:23 被阅读85次

    上一篇我介绍了AudioFileStream,这一篇我来介绍一下AudioFile。
    AudioFile跟AudioFileStream一样,也能读取音频格式信息和进行帧的分离,但是功能比AudioFileStream强大

    AudioFile官方文档介绍:
    a C programming interface that enables you to read or write a wide variety of audio data to or from disk or a memory buffer.With Audio File Services you can:

    • Create, initialize, open, and close audio files
    • Read and write audio files
    • Optimize audio files
    • Work with user data and global information

    由文档我们知道,这个类可以用来创建、初始化音频文件;读写音频数据,对音频进行优化;读取和写入音频信息。所以它不止可以用来支持音频播放,还可以生成音频文件(本篇暂不涉及)

    初始化AudioFile

    AudioFile有两个创建方式
    1.AudioFileOpenURL

    extern OSStatus 
    AudioFileOpenURL (  CFURLRef                            inFileRef,
                        AudioFilePermissions                inPermissions,
                        AudioFileTypeID                     inFileTypeHint,
                        AudioFileID __nullable * __nonnull  outAudioFile)                   __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
    

    第一个参数是文件路径
    第二个参数是文件的允许使用方式,一共三种,读、写和读写,如果打开文件后使用了权限外的操作,就会报错

    typedef CF_ENUM(SInt8, AudioFilePermissions) {
        kAudioFileReadPermission      = 0x01,
        kAudioFileWritePermission     = 0x02,
        kAudioFileReadWritePermission = 0x03
    };
    

    第三个参数跟AudioFileStream一样,传入一个参数帮助解析文件
    第四个参数返回AudioFile实例对应的一个ID,需要保存起来作为一些参数的查询
    返回值返回noErr则成功

    2.AudioFileOpenWithCallbacks

    extern OSStatus 
    AudioFileOpenWithCallbacks (
                    void *                              inClientData,
                    AudioFile_ReadProc                  inReadFunc,
                    AudioFile_WriteProc __nullable      inWriteFunc,
                    AudioFile_GetSizeProc               inGetSizeFunc,
                    AudioFile_SetSizeProc __nullable    inSetSizeFunc,
                    AudioFileTypeID                     inFileTypeHint,
                    AudioFileID __nullable * __nonnull  outAudioFile)               __OSX_AVAILABLE_STARTING(__MAC_10_3,__IPHONE_2_0);
    

    第一个参数,上下文对象,一般为AudioFile实例
    第二个参数,当AudioFile需要读音频数据时进行的回调(同步回调)
    第三个参数,当AudioFile需要写音频数据时进行的回调(暂不讨论,传Null)
    第四个参数,当AudioFile需要用到文件的总大小时的回调(同步回调)
    第五个参数,当AudioFile需要设置文件大小时的回调(写音频文件功能时使用,传Null,暂不讨论)
    第六,第七同AudioFileOpenURL方法
    这个方法的重点在于AudioFile_ReadProc这个回调。这个方法比第一个自由度更高,AudioFile只需要一个数据源,无论是磁盘或者内存的数据甚至是网络流只要能在AudioFile需要时即open和read时,通过AudioFile_ReadProc回调给AudioFile提供合适的数据就可以了,即可以读取网络流和本地文件
    我们来看这两个回调

    typedef OSStatus (*AudioFile_ReadProc)(
                                    void *      inClientData,
                                    SInt64      inPosition, 
                                    UInt32      requestCount,
                                    void *      buffer, 
                                    UInt32 *    actualCount);
    typedef SInt64 (*AudioFile_GetSizeProc)(
                                    void *      inClientData);
    

    AudioFile_GetSizeProc这个回调比较简单,就是返回文件总长度
    AudioFile_ReadProc
    第一个参数,上下文对象,一般为AudioFile实例
    第二个参数,需要读取第几个字节开始的数据
    第三个参数,需要读取的数据长度
    第四个参数,返回参数,是一个数据指针且其空间已经被分配了,我们需要做的是把数据memcpy到buffer中
    第五个参数,实际提供的数据长度,即memcpy到buffer的数据长度
    返回值,如果没有异常的话就直接返回noErr。
    AudioFile需要数据时会调用回调方法,需要数据的时间点有两个

    • Open方法调用时,由于AudioFile的Open方法调用过程中就会对音频格式信息进行解析,只有复合要求的音频格式才能被成功打开否则Open方法就会返回码
    • Read相关方法调用时;
      这个回调函数需要注意inPosition和requestCount参数,这两个参数指明了本次回调需要提供的数据范围是从inPosition和requestCount个字节的数据。这里就有两种情况
    • 有充足的数据:那么我们需要把这个范围内的数据拷贝到buffer中,并且给actualCount赋值给requestCount,最后返回noErr
    • 数据不足: 没有充足数据的话就只能把手头的数据拷贝到buffer中,拷贝的数据必须是从inPosition开始的连续数据,拷贝完成后给actualCount赋值实际拷贝进buffer中的数据长度,然后返回noErr,如下代码
    static OSStatus ZJAudioFileReadCallBack(void *inclientData,SInt64 inPosition, UInt32 requestCount, void *buffer, UInt32 *actualCount){
        ZJAudioFile *audioFile = (__bridge ZJAudioFile *)inclientData;
        *actualCount = [audioFile availableDataLengthAtOffset:inPosition maxLength:requestCount];
        if (*actualCount>0) {
            NSData *data = [audioFile dataAtOffset:inPosition length:*actualCount];
            memcpy(buffer, [data bytes], [data length]);
            
        }
        return noErr;
    }
    
    • 当Open方法调用时的回调数据不足:AudioFile的Open方法会根据文件格式类型分几步进行数据读取以解析确定是否是一个合法的文件格式,其中每一步的inPosition和requestCount都不一样,如果某一步不成会直接进行下一步,如果几步下来都失败了,那么Open方法就会失败。即在调用Open方法之前需要保证音频文件的格式信息完整,所以AudioFile并不能独立应用于音频流的读取,在流播放的时候需要使用AudioFileStream得到ReadyToProducePackets标志位来保证信息完整;
    • Read方法调用时回调数据不足:这种情况下inPosition和requestCount的数值与Read方法调用时传入的参数有关,数据不足对于Read方法本身没有影响,只要回调返回noErr,Read就成功,只是实际交给Read方法的调用方的数据会不足,所以需要去处理;
    读取音频格式信息

    像AudioFileStream一样,AudioFile会有两个getProperty的方法去获取音频格式信息

    extern OSStatus
    AudioFileGetPropertyInfo(       AudioFileID             inAudioFile,
                                    AudioFilePropertyID     inPropertyID,
                                    UInt32 * __nullable     outDataSize,
                                    UInt32 * __nullable     isWritable)         __OSX_AVAILABLE_STARTING(__MAC_10_2,__IPHONE_2_0);
                                          
    extern OSStatus
    AudioFileGetProperty(   AudioFileID             inAudioFile,
                            AudioFilePropertyID     inPropertyID,
                            UInt32                  *ioDataSize,
                            void                    *outPropertyData)           __OSX_AVAILABLE_STARTING(__MAC_10_2,__IPHONE_2_0);
    

    AudioFileGetPropertyInfo方法用来获取某个属性对应的数据大小(outDataSize)以及该属性能否被write(isWritable),而AudioFileGetProperty则用来获取属性对应的数据。对于一些大小可变的属性需要先使用AudioFileGetPropertyInfo获取数据大小才能获取数据(例如formatList),而有些确定类型单个则不必先调用AudioFileGetPropertyInfo直接调用AudioFileGetProperty则可(比如bitRate)

      UInt32 formatListSize;
        OSStatus status = AudioFileGetPropertyInfo(_audioFileID, kAudioFilePropertyFormatList, &formatListSize, NULL);
        if (status == noErr) {
            BOOL found = NO;
            AudioFormatListItem *formatList = malloc(formatListSize);
            OSStatus status = AudioFileGetProperty(_audioFileID, kAudioFilePropertyFormatList, &formatListSize, formatList);
            if (status == noErr) {
                UInt32 supportedFormatsSize;
                status = AudioFormatGetPropertyInfo(kAudioFormatProperty_DecodeFormatIDs, 0, NULL, &supportedFormatsSize);
                if (status != noErr) {
                    free(formatList);
                    [self _closeAudioFile];
                    return;
                }
                
                UInt32 supportedFormatCount = supportedFormatsSize/sizeof(OSType);
                OSType *supportedFormats = (OSType *)malloc(supportedFormatsSize);
                status = AudioFormatGetProperty(kAudioFormatProperty_DecodeFormatIDs, 0, NULL, &supportedFormatsSize, supportedFormats);
                if (status != noErr) {
                    free(formatList);
                    free(supportedFormats);
                    [self _closeAudioFile];
                    return;
                }
                
                for (int i = 0; i*sizeof(AudioFormatListItem)<formatListSize; i++) {
                    AudioStreamBasicDescription format = formatList[i].mASBD;
                    for (UInt32 j = 0; j < supportedFormatCount; j++) {
                        if (format.mFormatID == supportedFormats[j]) {
                            NSLog(@"i -- %u j -- %u",i,j);
                            _format = format;
                            found = YES;
                            break;
                        }
                    }
                }
                free(supportedFormats);
            }
            free(formatList);
            
            if (!found) {
                [self _closeAudioFile];
                return;
            }else{
                [self _calculatePacketsDuration];
            }
        }
        
        UInt32 size = sizeof(_bitRate);
        status = AudioFileGetProperty(_audioFileID, kAudioFilePropertyBitRate, &size, &_bitRate);
        
        if (status != noErr) {
            [self _closeAudioFile];
            return;
        }
        
        size = sizeof(_dataOffset);
        status = AudioFileGetProperty(_audioFileID, kAudioFilePropertyDataOffset, &size, &_dataOffset);
        if (status != noErr) {
            [self _closeAudioFile];
            return;
        }
        
        _audioDataByteCount = _fileSize-_dataOffset;
        size = sizeof(_duration);
        status = AudioFileGetProperty(_audioFileID, kAudioFilePropertyEstimatedDuration, &size, &_duration);
        if (status != noErr) {
            [self _calculateDuration];
        }
        
        size = sizeof(_maxPacketSize);
        status = AudioFileGetProperty(_audioFileID, kAudioFilePropertyPacketSizeUpperBound, &size, &_maxPacketSize);
        if (status != noErr || _maxPacketSize == 0) {
            status = AudioFileGetProperty(_audioFileID, kAudioFilePropertyMaximumPacketSize, &size, &_maxPacketSize);
            if (status != noErr) {
                [self _closeAudioFile];
                return;
            }
        }
    

    以下是可以获取到的属性

    CF_ENUM(AudioFilePropertyID)
    {
        kAudioFilePropertyFileFormat            =   'ffmt',
        kAudioFilePropertyDataFormat            =   'dfmt',
        kAudioFilePropertyIsOptimized           =   'optm',
        kAudioFilePropertyMagicCookieData       =   'mgic',
        kAudioFilePropertyAudioDataByteCount    =   'bcnt',
        kAudioFilePropertyAudioDataPacketCount  =   'pcnt',
        kAudioFilePropertyMaximumPacketSize     =   'psze',
        kAudioFilePropertyDataOffset            =   'doff',
        kAudioFilePropertyChannelLayout         =   'cmap',
        kAudioFilePropertyDeferSizeUpdates      =   'dszu',
        kAudioFilePropertyDataFormatName        =   'fnme',
        kAudioFilePropertyMarkerList            =   'mkls',
        kAudioFilePropertyRegionList            =   'rgls',
        kAudioFilePropertyPacketToFrame         =   'pkfr',
        kAudioFilePropertyFrameToPacket         =   'frpk',
        kAudioFilePropertyPacketToByte          =   'pkby',
        kAudioFilePropertyByteToPacket          =   'bypk',
        kAudioFilePropertyChunkIDs              =   'chid',
        kAudioFilePropertyInfoDictionary        =   'info',
        kAudioFilePropertyPacketTableInfo       =   'pnfo',
        kAudioFilePropertyFormatList            =   'flst',
        kAudioFilePropertyPacketSizeUpperBound  =   'pkub',
        kAudioFilePropertyReserveDuration       =   'rsrv',
        kAudioFilePropertyEstimatedDuration     =   'edur',
        kAudioFilePropertyBitRate               =   'brat',
        kAudioFilePropertyID3Tag                =   'id3t',
        kAudioFilePropertySourceBitDepth        =   'sbtd',
        kAudioFilePropertyAlbumArtwork          =   'aart',
        kAudioFilePropertyAudioTrackCount       =   'atct',
        kAudioFilePropertyUseAudioTrack         =   'uatk'
    };
    
    

    里面的有EstimatedDuration和bitRate,可以直接获取duration和bitRate了

    读取音频数据

    读取音频数据分为直接读取音频数据和按帧(packet)读取音频数据
    先来看直接读取音频数据

    extern OSStatus AudioFileReadBytes (AudioFileID inAudioFile,
                                        Boolean inUseCache,
                                        SInt64 inStartingByte,
                                        UInt32 * ioNumBytes,
                                        void * outBuffer);
    

    第一个参数fileID,即Open方法里面得到的实例id;
    第二个参数,是否需要cache,一般都传false;
    第三个参数,从第几个bytes开始读取数据;
    第四个参数,这个参数在调用时作为输入参数表示需要读取多少数据,调用完成后作为输出参数表示实际读取了多少数据(Read回调中的requestCount和actualCount);
    第五个参数,buffer指针,需要事先分配好足够大的内存(ioNumBytes大,即Read回调中的buffer,所以Read回调不需要再分配内存);
    返回值表示是否读取成功,如果失败则返回kAudioFileEndOfFileError;
    这个方法得到的数据都是没有帧分离的数据,如果想要用来播放或者解码还必须通过AudioFileStream进行帧分离;

    我们继续看按帧(packet)读取音频数据

    extern OSStatus 
    AudioFileReadPackets (  AudioFileID                     inAudioFile, 
                            Boolean                         inUseCache,
                            UInt32 *                        outNumBytes,
                            AudioStreamPacketDescription * __nullable outPacketDescriptions,
                            SInt64                          inStartingPacket, 
                            UInt32 *                        ioNumPackets,
                            void * __nullable               outBuffer)          __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_2,__MAC_10_10, __IPHONE_2_0,__IPHONE_8_0);//iOS8已经deprecated
    
    extern OSStatus 
    AudioFileReadPacketData (   AudioFileID                     inAudioFile, 
                                Boolean                         inUseCache,
                                UInt32 *                        ioNumBytes,
                                AudioStreamPacketDescription * __nullable outPacketDescriptions,
                                SInt64                          inStartingPacket, 
                                UInt32 *                        ioNumPackets,
                                void * __nullable               outBuffer)          __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_2_2);
    

    第一个回调函数在iOS8以后已经deprecated了,其实两个函数差别不大,只是前者之前更多用来读取固定时长音频或者非压缩音频时,现在一般采用第二种,且效率更高并且省内存
    我们来看参数
    第一,二个参数跟AudioFileReadBytes一致
    第三个参数,AudioFileReadPacketData中,ioNumBytes这个参数在输入输出中都要用到,在输入表示outBuffer的size,输出时表示实际读取了多少size的数据。
    第四个参数,帧信息数组指针,在输入前需要分配内存,大小必须足够存在ioNumPackets个帧(sizeof(AudioStreamPacketDescription)*ioNumPackets);
    第五个参数,从第几帧开始读取数据
    第六个参数,在输入时表示需要读取多少个帧,在输出时表示实际读取了多少帧
    第七个参数,outBuffer数据指针,在输入前就需要分配好空间,这个参数看上去两个方法一样但其实并非这样。对于AudioFileReadPacketData来说只要分配近似帧*帧数的内存空间即可,方法本身会对给定的内存空间大小来决定最后输出多少个帧,如果空间不够会适当减少出的帧数;而对于AudioFileReadPackets来说则需分配最大帧大小(或帧大小上界)*帧数的内存空间才行;第三个参数后者是输入输出双向使用的,而前者只是作为输出使用,这也是后者省内存的原因
    返回值,同AudioFileReadBytes
    这两个方法读取后的数据为帧分离后的数据,可以用来直接播放或者解码

    - (NSArray *)parseData:(BOOL *)isErr
    {
        UInt32 ioNumPackets = packetPerRead;//要读取多少个packet
        UInt32 ioNumBytes = ioNumPackets * _maxPacketSize;获取输出输入的size
        void *outBuffer = (void *)malloc(ioNumBytes);
        
        AudioStreamPacketDescription *outPacketDescriptions = NULL;
        OSStatus status = noErr;
        
        UInt32 descSize = sizeof(AudioStreamPacketDescription) *ioNumPackets;
        outPacketDescriptions = (AudioStreamPacketDescription *)malloc(descSize);
        status = AudioFileReadPacketData(_audioFileID, false, &ioNumBytes, outPacketDescriptions, _packetOffset, &ioNumPackets, outBuffer);
        
        if (status != noErr) {
            *isErr = status == kAudioFileEndOfFileError;
            free(outBuffer);
            return nil;
        }
        
        if (ioNumBytes == 0) {
            *isErr = YES;
        }
        
        _packetOffset += ioNumPackets;
        
        if (ioNumPackets > 0) {
            
            NSMutableArray *parsedDataArray = [[NSMutableArray alloc]init];
            for (int i = 0; i < ioNumPackets; i++) {
                AudioStreamPacketDescription packetDescription;
                if (outPacketDescriptions) {
                    packetDescription = outPacketDescriptions[i];
                }else{
                    packetDescription.mStartOffset = i*_format.mBytesPerPacket;
                    packetDescription.mDataByteSize = _format.mBytesPerPacket;
                    packetDescription.mVariableFramesInPacket = _format.mFramesPerPacket;
                }
                
                ZJParsedAudioData *parsedData = [ZJParsedAudioData parsedAudioDataWithBytes:outBuffer+packetDescription.mStartOffset packetDescription:packetDescription];
                
                if (parsedData) {
                    [parsedDataArray addObject:parsedData];
                }
            }
            return parsedDataArray;
        }
        return nil;
    }
    

    同样AudioFile也需要关闭

    extern OSStatus AudioFileClose (AudioFileID inAudioFile);  
    

    到此基本完结了,小结

    • AudioFile有两种初始化方法,一种只能打开本地文件,一种则使用场景更多
    • 必须保证音频信息的完整,才能使用AudioFile的Open方法,AudioFile需要配合AudioFileStream来判断文件格式可读再调用AudioFile的Open方法
    • 使用AudioFileGetProperty读取格式信息时需要判断所读取的信息是否需要先调用AudioFileGetPropertyInfo获得数据大小后再进行读取
      最后demo地址奉上https://github.com/chanbendong/ZJAudioFile

    相关文章

      网友评论

        本文标题:iOS音频学习二之AudioFile

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