音频

iOS音频学习二之AudioFile

2018-02-27  本文已影响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:

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

初始化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需要数据时会调用回调方法,需要数据的时间点有两个

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;
}
读取音频格式信息

像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);  

到此基本完结了,小结

上一篇下一篇

猜你喜欢

热点阅读