IOS音频播放(三)

第三部分转载自:

码农人生

前言

本来说好是要在第三篇中讲AudioFileStream和AudioQueue,但写着写着发现光AudioFileStream就好多内容,最后还是决定分篇介绍,这篇先来说一下AudioFileStream,下一篇计划说一下和AudioFileStream类似的AudioFile,下下篇再来说AudioQueue。

 

本篇将会提到计算音频时长duration和音频seek的方法,这些方法对于CBR编码形式的音频文件可以做到比较精确,而对于VBR编码形式的会存在较大的误差(关于CBR和VBR,请看本系列的第一篇),具体讲到duration和seek时会再进行说明。

 

AudioFileStream介绍

第一篇中说到AudioFileStreamer时提到它的作用是用来读取采样率、码率、时长等基本信息以及分离音频帧。那么在官方文档中Apple是这样描述的:

 To play streamed audio content, such as from a network connection, use Audio File Stream Services in concert with Audio
Queue Services. Audio File Stream Services parses audio packets and metadata from common audio file container formats in a network bitstream. You can also use it to parse packets and metadata from on-disk files

根据Apple的描述,AudioFileStreamer用在流播放中,当然不仅限于网络流,本地文件同样可以用它来读取信息和分离音频帧。AudioFileStreamer的主要数据是文件数据而不是文件路径,所以数据的读取需要使用者自行实现,支持的文件格式有:

 

* MPEG-1 Audio Layer 3, used for .mp3 files

* MPEG-2 ADTS, used for the .aac audio data format

* AIFC

* AIFF

* CAF

* MPEG-4, used for .m4a, .mp4, and .3gp files

* NeXT

* WAVE

 

上述格式是iOS、MacOSX所支持的音频格式,这类格式可以被系统提供的API解码,如果想要解码其他的音频格式(如OGG、APE、FLAC)就需要自己实现解码器了。

 

初始化AudioFileStream

第一步,自然是要生成一个AudioFileStream的实例:


  1. extern OSStatus AudioFileStreamOpen (void * inClientData, 
  2.                                      AudioFileStream_PropertyListenerProc inPropertyListenerProc, 
  3.                                      AudioFileStream_PacketsProc inPacketsProc, 
  4.                                      AudioFileTypeID inFileTypeHint, 
  5.                                      AudioFileStreamID * outAudioFileStream); 

第一个参数和之前的AudioSession的初始化方法一样是一个上下文对象;

 

第二个参数AudioFileStream_PropertyListenerProc是歌曲信息解析的回调,每解析出一个歌曲信息都会进行一次回调;

 

第三个参数AudioFileStream_PacketsProc是分离帧的回调,每解析出一部分帧就会进行一次回调;

 

第四个参数AudioFileTypeID是文件类型的提示,这个参数来帮助AudioFileStream对文件格式进行解析。这个参数在文件信息不完整(例如信息有缺陷)时尤其有用,它可以给与AudioFileStream一定的提示,帮助其绕过文件中的错误或者缺失从而成功解析文件。所以在确定文件类型的情况下建议各位还是填上这个参数,如果无法确定可以传入0(原理上应该和这篇博文近似);


  1. //AudioFileTypeID枚举 
  2. enum { 
  3.         kAudioFileAIFFType             = 'AIFF', 
  4.         kAudioFileAIFCType             = 'AIFC', 
  5.         kAudioFileWAVEType             = 'WAVE', 
  6.         kAudioFileSoundDesigner2Type   = 'Sd2f', 
  7.         kAudioFileNextType             = 'NeXT', 
  8.         kAudioFileMP3Type              = 'MPG3',    // mpeg layer 3 
  9.         kAudioFileMP2Type              = 'MPG2',    // mpeg layer 2 
  10.         kAudioFileMP1Type              = 'MPG1',    // mpeg layer 1 
  11.         kAudioFileAC3Type              = 'ac-3', 
  12.         kAudioFileAAC_ADTSType         = 'adts', 
  13.         kAudioFileMPEG4Type            = 'mp4f', 
  14.         kAudioFileM4AType              = 'm4af', 
  15.         kAudioFileM4BType              = 'm4bf', 
  16.         kAudioFileCAFType              = 'caff', 
  17.         kAudioFile3GPType              = '3gpp', 
  18.         kAudioFile3GP2Type             = '3gp2',         
  19.         kAudioFileAMRType              = 'amrf'         
  20. }; 

第五个参数是返回的AudioFileStream实例对应的AudioFileStreamID,这个ID需要保存起来作为后续一些方法的参数使用;

 

返回值用来判断是否成功初始化(OSSStatus == noErr)。

 

解析数据

在初始化完成之后,只要拿到文件数据就可以进行解析了。解析时调用方法:


  1. extern OSStatus AudioFileStreamParseBytes(AudioFileStreamID inAudioFileStream, 
  2.                                           UInt32 inDataByteSize, 
  3.                                           const void* inData, 
  4.                                           UInt32 inFlags); 

第一个参数AudioFileStreamID,即初始化时返回的ID;

 

第二个参数inDataByteSize,本次解析的数据长度;

 

第三个参数inData,本次解析的数据;

 

第四个参数是说本次的解析和上一次解析是否是连续的关系,如果是连续的传入0,否则传入kAudioFileStreamParseFlag_Discontinuity。

 

这里需要插入解释一下何谓“连续”。在第一篇中我们提到过形如MP3的数据都以帧的形式存在的,解析时也需要以帧为单位解析。但在解码之前我们不可能知道每个帧的边界在第几个字节,所以就会出现这样的情况:我们传给AudioFileStreamParseBytes的数据在解析完成之后会有一部分数据余下来,这部分数据是接下去那一帧的前半部分,如果再次有数据输入需要继续解析时就必须要用到前一次解析余下来的数据才能保证帧数据完整,所以在正常播放的情况下传入0即可。目前知道的需要传入kAudioFileStreamParseFlag_Discontinuity的情况有两个,一个是在seek完毕之后显然seek后的数据和之前的数据完全无关;另一个是开源播放器AudioStreamer的作者@Matt
Gallagher曾在自己的blog中提到过的:

 the Audio File Stream Services hit me with a nasty bug:AudioFileStreamParseBytes will crash when trying to parse a streaming
MP3.In this case, if we pass the kAudioFileStreamParseFlag_Discontinuity flag to AudioFileStreamParseBytes on every invocation between receiving kAudioFileStreamProperty_ReadyToProducePackets and the first successful call to MyPacketsProc, then AudioFileStreamParseBytes
will be extra cautious in its approach and won't crash.

Matt发布这篇blog是在2008年,这个Bug年代相当久远了,而且原因未知,究竟是否修复也不得而知,而且由于环境不同(比如测试用的mp3文件和所处的iOS系统)无法重现这个问题,所以我个人觉得还是按照Matt的work around在回调得到kAudioFileStreamProperty_ReadyToProducePackets之后,在正常解析第一帧之前都传入kAudioFileStreamParseFlag_Discontinuity比较好。

 

回到之前的内容,AudioFileStreamParseBytes方法的返回值表示当前的数据是否被正常解析,如果OSStatus的值不是noErr则表示解析不成功,其中错误码包括:


  1. enum 
  2.   kAudioFileStreamError_UnsupportedFileType        = 'typ?', 
  3.   kAudioFileStreamError_UnsupportedDataFormat      = 'fmt?', 
  4.   kAudioFileStreamError_UnsupportedProperty        = 'pty?', 
  5.   kAudioFileStreamError_BadPropertySize            = '!siz', 
  6.   kAudioFileStreamError_NotOptimized               = 'optm', 
  7.   kAudioFileStreamError_InvalidPacketOffset        = 'pck?', 
  8.   kAudioFileStreamError_InvalidFile                = 'dta?', 
  9.   kAudioFileStreamError_ValueUnknown               = 'unk?', 
  10.   kAudioFileStreamError_DataUnavailable            = 'more', 
  11.   kAudioFileStreamError_IllegalOperation           = 'nope', 
  12.   kAudioFileStreamError_UnspecifiedError           = 'wht?', 
  13.   kAudioFileStreamError_DiscontinuityCantRecover   = 'dsc!' 
  14. }; 

大多数都可以从字面上理解,需要提一下的是kAudioFileStreamError_NotOptimized,文档上是这么说的:

 It is not possible to produce output packets because the file's packet table or other defining info is either not present
or is after the audio data.

它的含义是说这个音频文件的文件头不存在或者说文件头可能在文件的末尾,当前无法正常Parse,换句话说就是这个文件需要全部下载完才能播放,无法流播。

 

注意:AudioFileStreamParseBytes方法每一次调用都应该注意返回值,一旦出现错误就可以不必继续Parse了。

 

解析文件格式信息

在调用AudioFileStreamParseBytes方法进行解析时会首先读取格式信息,并同步的进入AudioFileStream_PropertyListenerProc回调方法

 

来看一下这个回调方法的定义


  1. typedef void (*AudioFileStream_PropertyListenerProc)(void * inClientData, 
  2.                                                      AudioFileStreamID inAudioFileStream, 
  3.                                                      AudioFileStreamPropertyID inPropertyID, 
  4.                                                      UInt32 * ioFlags); 

回调的第一个参数是Open方法中的上下文对象;

 

第二个参数inAudioFileStream是和Open方法中第四个返回参数AudioFileStreamID一样,表示当前FileStream的ID;

 

第三个参数是此次回调解析的信息ID。表示当前PropertyID对应的信息已经解析完成信息(例如数据格式、音频数据的偏移量等等),使用者可以通过AudioFileStreamGetProperty接口获取PropertyID对应的值或者数据结构;


  1. extern OSStatus AudioFileStreamGetProperty(AudioFileStreamID inAudioFileStream, 
  2.                                            AudioFileStreamPropertyID inPropertyID, 
  3.                                            UInt32 * ioPropertyDataSize, 
  4.                                            void * outPropertyData); 

第四个参数ioFlags是一个返回参数,表示这个property是否需要被缓存,如果需要赋值kAudioFileStreamPropertyFlag_PropertyIsCached否则不赋值(这个参数我也不知道应该在啥场景下使用。。一直都没去理他);

 

这个回调会进来多次,但并不是每一次都需要进行处理,可以根据需求处理需要的PropertyID进行处理(PropertyID列表如下)。


  1. //AudioFileStreamProperty枚举 
  2. enum 
  3.   kAudioFileStreamProperty_ReadyToProducePackets           =    'redy', 
  4.   kAudioFileStreamProperty_FileFormat                      =    'ffmt', 
  5.   kAudioFileStreamProperty_DataFormat                      =    'dfmt', 
  6.   kAudioFileStreamProperty_FormatList                      =    'flst', 
  7.   kAudioFileStreamProperty_MagicCookieData                 =    'mgic', 
  8.   kAudioFileStreamProperty_AudioDataByteCount              =    'bcnt', 
  9.   kAudioFileStreamProperty_AudioDataPacketCount            =    'pcnt', 
  10.   kAudioFileStreamProperty_MaximumPacketSize               =    'psze', 
  11.   kAudioFileStreamProperty_DataOffset                      =    'doff', 
  12.   kAudioFileStreamProperty_ChannelLayout                   =    'cmap', 
  13.   kAudioFileStreamProperty_PacketToFrame                   =    'pkfr', 
  14.   kAudioFileStreamProperty_FrameToPacket                   =    'frpk', 
  15.   kAudioFileStreamProperty_PacketToByte                    =    'pkby', 
  16.   kAudioFileStreamProperty_ByteToPacket                    =    'bypk', 
  17.   kAudioFileStreamProperty_PacketTableInfo                 =    'pnfo', 
  18.   kAudioFileStreamProperty_PacketSizeUpperBound            =    'pkub', 
  19.   kAudioFileStreamProperty_AverageBytesPerPacket           =    'abpp', 
  20.   kAudioFileStreamProperty_BitRate                         =    'brat', 
  21.   kAudioFileStreamProperty_InfoDictionary                  =    'info' 
  22. }; 

这里列几个我认为比较重要的PropertyID:

 

1、kAudioFileStreamProperty_BitRate:

表示音频数据的码率,获取这个Property是为了计算音频的总时长Duration(因为AudioFileStream没有这样的接口。。)。


  1. UInt32 bitRate; 
  2. UInt32 bitRateSize = sizeof(bitRate); 
  3. OSStatus status = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_BitRate, &bitRateSize, &bitRate); 
  4. if (status != noErr) 
  5.     //错误处理 

 

2、kAudioFileStreamProperty_DataOffset:

表示音频数据在整个音频文件中的offset(因为大多数音频文件都会有一个文件头之后才使真正的音频数据),这个值在seek时会发挥比较大的作用,音频的seek并不是直接seek文件位置而seek时间(比如seek到2分10秒的位置),seek时会根据时间计算出音频数据的字节offset然后需要再加上音频数据的offset才能得到在文件中的真正offset。


  1. SInt64 dataOffset; 
  2. UInt32 offsetSize = sizeof(dataOffset); 
  3. OSStatus status = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataOffset, &offsetSize, &dataOffset); 
  4. if (status != noErr) 
  5.     //错误处理 

 

3、kAudioFileStreamProperty_DataFormat

表示音频文件结构信息,是一个AudioStreamBasicDescription的结构


  1. struct AudioStreamBasicDescription 
  2.     Float64 mSampleRate; 
  3.     UInt32  mFormatID; 
  4.     UInt32  mFormatFlags; 
  5.     UInt32  mBytesPerPacket; 
  6.     UInt32  mFramesPerPacket; 
  7.     UInt32  mBytesPerFrame; 
  8.     UInt32  mChannelsPerFrame; 
  9.     UInt32  mBitsPerChannel; 
  10.     UInt32  mReserved; 
  11. }; 
  12.  
  13. AudioStreamBasicDescription asbd; 
  14. UInt32 asbdSize = sizeof(asbd); 
  15. OSStatus status = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataFormat, &asbdSize, &asbd); 
  16. if (status != noErr) 
  17.     //错误处理 
  18. }   

 

4、kAudioFileStreamProperty_FormatList

作用和kAudioFileStreamProperty_DataFormat是一样的,区别在于用这个PropertyID获取到是一个AudioStreamBasicDescription的数组,这个参数是用来支持AAC SBR这样的包含多个文件类型的音频格式。由于到底有多少个format我们并不知晓,所以需要先获取一下总数据大小:


  1. //获取数据大小 
  2. Boolean outWriteable; 
  3. UInt32 formatListSize; 
  4. OSStatus status = AudioFileStreamGetPropertyInfo(inAudioFileStream, kAudioFileStreamProperty_FormatList, &formatListSize, &outWriteable); 
  5. if (status != noErr) 
  6.     //错误处理 
  7.  
  8. //获取formatlist 
  9. AudioFormatListItem *formatList = malloc(formatListSize); 
  10. OSStatus status = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_FormatList, &formatListSize, formatList); 
  11. if (status != noErr) 
  12.     //错误处理 
  13.  
  14. //选择需要的格式 
  15. for (int i = 0; i * sizeof(AudioFormatListItem) < formatListSize; i += sizeof(AudioFormatListItem)) 
  16.     AudioStreamBasicDescription pasbd = formatList[i].mASBD; 
  17.     //选择需要的格式。。                              
  18. free(formatList); 

 

5、kAudioFileStreamProperty_AudioDataByteCount

顾名思义,音频文件中音频数据的总量。这个Property的作用一是用来计算音频的总时长,二是可以在seek时用来计算时间对应的字节offset。


  1. UInt64 audioDataByteCount; 
  2. UInt32 byteCountSize = sizeof(audioDataByteCount); 
  3. OSStatus status = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_AudioDataByteCount, &byteCountSize, &audioDataByteCount); 
  4. if (status != noErr) 
  5.     //错误处理 

 

6、kAudioFileStreamProperty_ReadyToProducePackets

这个PropertyID可以不必获取对应的值,一旦回调中这个PropertyID出现就代表解析完成,接下来可以对音频数据进行帧分离了。

 

计算时长Duration

获取时长的最佳方法是从ID3信息中去读取,那样是最准确的。如果ID3信息中没有存,那就依赖于文件头中的信息去计算了。

 

计算duration的公式如下:


  1. double duration = (audioDataByteCount * 8) / bitRate 

音频数据的字节总量audioDataByteCount可以通过kAudioFileStreamProperty_AudioDataByteCount获取,码率bitRate可以通过kAudioFileStreamProperty_BitRate获取也可以通过Parse一部分数据后计算平均码率来得到。

 

对于CBR数据来说用这样的计算方法的duration会比较准确,对于VBR数据就不好说了。所以对于VBR数据来说,最好是能够从ID3信息中获取到duration,获取不到再想办法通过计算平均码率的途径来计算duration。

 

分离音频帧

读取格式信息完成之后继续调用AudioFileStreamParseBytes方法可以对帧进行分离,并同步的进入AudioFileStream_PacketsProc回调方法。

 

回调的定义:


  1. typedef void (*AudioFileStream_PacketsProc)(void * inClientData, 
  2.                                             UInt32 inNumberBytes, 
  3.                                             UInt32 inNumberPackets, 
  4.                                             const void * inInputData, 
  5.                                             AudioStreamPacketDescription * inPacketDescriptions); 

第一个参数,一如既往的上下文对象;

 

第二个参数,本次处理的数据大小;

 

第三个参数,本次总共处理了多少帧(即代码里的Packet);

 

第四个参数,本次处理的所有数据;

 

第五个参数,AudioStreamPacketDescription数组,存储了每一帧数据是从第几个字节开始的,这一帧总共多少字节。


  1. //AudioStreamPacketDescription结构 
  2. //这里的mVariableFramesInPacket是指实际的数据帧只有VBR的数据才能用到(像MP3这样的压缩数据一个帧里会有好几个数据帧) 
  3. struct  AudioStreamPacketDescription 
  4.     SInt64  mStartOffset; 
  5.     UInt32  mVariableFramesInPacket; 
  6.     UInt32  mDataByteSize; 
  7. }; 

下面是我按照自己的理解实现的回调方法片段:


  1. static void MyAudioFileStreamPacketsCallBack(void *inClientData, 
  2.                                              UInt32 inNumberBytes, 
  3.                                              UInt32 inNumberPackets, 
  4.                                              const void *inInputData, 
  5.                                              AudioStreamPacketDescription  *inPacketDescriptions) 
  6.     //处理discontinuous.. 
  7.  
  8.     if (numberOfBytes == 0 || numberOfPackets == 0) 
  9.     { 
  10.         return; 
  11.     } 
  12.  
  13.     BOOL deletePackDesc = NO; 
  14.     if (packetDescriptioins == NULL) 
  15.     { 
  16.         //如果packetDescriptioins不存在,就按照CBR处理,平均每一帧的数据后生成packetDescriptioins 
  17.         deletePackDesc = YES; 
  18.         UInt32 packetSize = numberOfBytes / numberOfPackets; 
  19.         packetDescriptioins = (AudioStreamPacketDescription *)malloc(sizeof(AudioStreamPacketDescription) * numberOfPackets); 
  20.  
  21.         for (int i = 0; i < numberOfPackets; i++) 
  22.         { 
  23.             UInt32 packetOffset = packetSize * i; 
  24.             descriptions[i].mStartOffset = packetOffset; 
  25.             descriptions[i].mVariableFramesInPacket = 0; 
  26.             if (i == numberOfPackets - 1) 
  27.             { 
  28.                 packetDescriptioins[i].mDataByteSize = numberOfBytes - packetOffset; 
  29.             } 
  30.             else 
  31.             { 
  32.                 packetDescriptioins[i].mDataByteSize = packetSize; 
  33.             } 
  34.         } 
  35.     } 
  36.  
  37.     for (int i = 0; i < numberOfPackets; ++i) 
  38.     { 
  39.         SInt64 packetOffset = packetDescriptioins[i].mStartOffset; 
  40.         UInt32 packetSize   = packetDescriptioins[i].mDataByteSize; 
  41.  
  42.         //把解析出来的帧数据放进自己的buffer中 
  43.         ... 
  44.     } 
  45.  
  46.     if (deletePackDesc) 
  47.     { 
  48.         free(packetDescriptioins); 
  49.     } 

inPacketDescriptions这个字段为空时需要按CBR的数据处理。但其实在解析CBR数据时inPacketDescriptions一般也会有返回,因为即使是CBR数据帧的大小也不是恒定不变的,例如CBR的MP3会在每一帧的数据后放1 byte的填充位,这个填充位也并非时时刻刻存在,所以帧的大小会有1 byte的浮动。(比如采样率44.1KHZ,码率160kbps的CBR MP3文件每一帧的大小在522字节和523字节浮动。所以不能因为有inPacketDescriptions没有返回NULL而判定音频数据就是VBR编码的)。

 

Seek

就音频的角度来seek功能描述为“我要拖到xx分xx秒”,而实际操作时我们需要操作的是文件,所以我们需要知道的是“我要拖到xx分xx秒”这个操作对应到文件上是要从第几个字节开始读取音频数据。

 

对于原始的PCM数据来说每一个PCM帧都是固定长度的,对应的播放时长也是固定的,但一旦转换成压缩后的音频数据就会因为编码形式的不同而不同了。对于CBR而言每个帧中所包含的PCM数据帧是恒定的,所以每一帧对应的播放时长也是恒定的;而VBR则不同,为了保证数据最优并且文件大小最小,VBR的每一帧中所包含的PCM数据帧是不固定的,这就导致在流播放的情况下VBR的数据想要做seek并不容易。这里我们也只讨论CBR下的seek。

 

CBR数据的seek一般是这样实现的(参考并修改自matt的blog):

 

1、近似地计算应该seek到哪个字节


  1. double seekToTime = ...; //需要seek到哪个时间,秒为单位 
  2. UInt64 audioDataByteCount = ...; //通过kAudioFileStreamProperty_AudioDataByteCount获取的值 
  3. SInt64 dataOffset = ...; //通过kAudioFileStreamProperty_DataOffset获取的值 
  4. double durtion = ...; //通过公式(AudioDataByteCount * 8) / BitRate计算得到的时长 
  5.  
  6. //近似seekOffset = 数据偏移 + seekToTime对应的近似字节数 
  7. SInt64 approximateSeekOffset = dataOffset + (seekToTime / duration) * audioDataByteCount; 

 

2、计算seekToTime对应的是第几个帧(Packet)

我们可以利用之前Parse得到的音频格式信息来计算PacketDuration。audioItem.fileFormat.mFramesPerPacket / audioItem.fileFormat.mSampleRate;


  1. //首先需要计算每个packet对应的时长 
  2. AudioStreamBasicDescription asbd = ...; ////通过kAudioFileStreamProperty_DataFormat或者kAudioFileStreamProperty_FormatList获取的值 
  3. double packetDuration = asbd.mFramesPerPacket / asbd.mSampleRate 
  4.  
  5. //然后计算packet位置 
  6. SInt64 seekToPacket = floor(seekToTime / packetDuration); 

 

3、使用AudioFileStreamSeek计算精确的字节偏移和时间

AudioFileStreamSeek可以用来寻找某一个帧(Packet)对应的字节偏移(byte offset):

 

* 如果找到了就会把ioFlags加上kAudioFileStreamSeekFlag_OffsetIsEstimated,并且给outDataByteOffset赋值,outDataByteOffset就是输入的seekToPacket对应的字节偏移量,我们可以根据outDataByteOffset来计算出精确的seekOffset和seekToTime;

* 如果没找到那么还是应该用第1步计算出来的approximateSeekOffset来做seek;


  1. SInt64 seekByteOffset; 
  2. UInt32 ioFlags = 0; 
  3. SInt64 outDataByteOffset; 
  4. OSStatus status = AudioFileStreamSeek(audioFileStreamID, seekToPacket, &outDataByteOffset, &ioFlags); 
  5. if (status == noErr && !(ioFlags & kAudioFileStreamSeekFlag_OffsetIsEstimated)) 
  6.   //如果AudioFileStreamSeek方法找到了帧的字节偏移,需要修正一下时间 
  7.   seekToTime -= ((seekByteOffset - dataOffset) - outDataByteOffset) * 8.0 / bitRate; 
  8.   seekByteOffset = outDataByteOffset + dataOffset; 
  9. else 
  10.   seekByteOffset = approximateSeekOffset; 

 

4、按照seekByteOffset读取对应的数据继续使用AudioFileStreamParseByte进行解析

如果是网络流可以通过设置range头来获取字节,本地文件的话直接seek就好了。调用AudioFileStreamParseByte时注意刚seek完第一次Parse数据需要加参数kAudioFileStreamParseFlag_Discontinuity。

 

关闭AudioFileStream

AudioFileStream使用完毕后需要调用AudioFileStreamClose进行关闭,没啥特别需要注意的。


  1. extern OSStatus AudioFileStreamClose(AudioFileStreamID inAudioFileStream);  

 

小结

本篇关于AudioFileStream做了详细介绍,小结一下:

 

* 使用AudioFileStream首先需要调用AudioFileStreamOpen,需要注意的是尽量提供inFileTypeHint参数帮助AudioFileStream解析数据,调用完成后记录AudioFileStreamID;

 

* 当有数据时调用AudioFileStreamParseBytes进行解析,每一次解析都需要注意返回值,返回值一旦出现noErr以外的值就代表Parse出错,其中kAudioFileStreamError_NotOptimized代表该文件缺少头信息或者其头信息在文件尾部不适合流播放;

 

* 使用AudioFileStreamParseBytes需要注意第四个参数在需要合适的时候传入kAudioFileStreamParseFlag_Discontinuity;

 

* 调用AudioFileStreamParseBytes后会首先同步进入AudioFileStream_PropertyListenerProc回调来解析文件格式信息,如果回调得到kAudioFileStreamProperty_ReadyToProducePackets表示解析格式信息完成;

 

* 解析格式信息完成后继续调用AudioFileStreamParseBytes会进入MyAudioFileStreamPacketsCallBack回调来分离音频帧,在回调中应该将分离出来的帧信息保存到自己的buffer中; 

 

* seek时需要先近似的计算seekTime对应的seekByteOffset,然后利用AudioFileStreamSeek计算精确的offset,如果能得到精确的offset就修正一下seektime,如果无法得到精确的offset就用之前的近似结果

 

* AudioFileStream使用完毕后需要调用AudioFileStreamClose进行关闭;

 

示例代码

AudioStreamerFreeStreamer这两个优秀的开源播放器都用到AudioFileStream大家可以借鉴。我自己也写了一个简单的AudioFileStream封装

 

下一篇将讲述如何使用AudioFile。

 

参考资料

 

Using
Audio

 

Streaming and
playing an MP3 stream

 

Streaming MP3/AAC audio
again

 

时间: 2024-08-09 13:20:46

IOS音频播放(三)的相关文章

IOS音频播放(二)

第二部分转载自: 码农人生 前言 本篇为<iOS音频播放>系列的第二篇. 在实施前一篇中所述的7个步骤之前还必须面对一个麻烦的问题,AudioSession. AudioSession简介 AudioSession这个玩意的主要功能包括以下几点(图片来自官方文档): 确定你的app如何使用音频(是播放?还是录音?) 为你的app选择合适的输入输出设备(比如输入用的麦克风,输出是耳机.手机功放或者airplay) 协调你的app的音频播放和系统以及其他app行为(例如有电话时需要打断,电话结束时

IOS音频播放(一)

第一部分转载自: 码农人生 iOS音频播放 (一):概述 Audio Playback in iOS (Part 1) : Introduction 前言 从事音乐相关的app开发也已经有一段时日了,在这过程中app的播放器几经修改我也因此对于iOS下的音频播放实现有了一定的研究.写这个系列的博客目的一方面希望能够抛砖引玉,另一方面也是希望能帮助国内其他的iOS开发者和爱好者少走弯路(我自己就遇到了不少的坑=.=). 本篇为<iOS音频播放>系列的第一篇,主要将对iOS下实现音频播放的方法进行

iOS开发系列--音频播放、录音、视频播放、拍照、视频录制

概览 随着移动互联网的发展,如今的手机早已不是打电话.发短信那么简单了,播放音乐.视频.录音.拍照等都是很常用的功能.在iOS中对于多媒体的支持是非常强大的,无论是音视频播放.录制,还是对麦克风.摄像头的操作都提供了多套API.在今天的文章中将会对这些内容进行一一介绍: 音频 音效 音乐 音频会话 录音 音频队列服务 视频 MPMoviePlayerController MPMoviePlayerViewController AVPlayer 摄像头 UIImagePickerControlle

iOS后台音频播放及锁屏界面显示音频信息

iOS后台播放音乐及用户交互处理 后台播放是任何一个音频软件都支持的功能,在上一篇博客中,详细介绍了使用AVAudioPlayer播放音频的方法,这篇博客将对后台的处理做介绍,关于播放与设置音频的博客地址:http://my.oschina.net/u/2340880/blog/420129. 一.设置后台播放 iOS设置后台音频播放的步骤非常简单,首先需要在系统设置的plist文件中添加一个键Required background modes,值为App plays audio or stre

代码-ios音频后台播放,怎么实现(在线)

问题描述 ios音频后台播放,怎么实现(在线) ios音频后台播放,怎么实现,在线播放的最好有直接执行的代码 解决方案 IOS后台运行 之 后台播放音乐 iOS后台播放音乐 解决方案二: 使用服务,即Service就可以满足你的要求.给你我以前自己写的示例: import java.util.List;import android.annotation.SuppressLint;import android.app.Service;import android.content.Broadcast

iOS开发音频播放基础——AVAudioPlayer的应用

iOS音频开发--AVAudioPlayer应用 AVAudioPlayer是系统提供给我们的一个音频播放类,在AVFoundation框架下,通过它,我们可以实现一个功能强大的音乐播放器.首先,在项目中我们需要导入AVFoundation这个框架. 一.AVAudioPlayer方法与属性详解 初始化方法有两种,通过音频的路径或者音频data数据初始化player对象 ? 1 2 - (instancetype)initWithContentsOfURL:(NSURL *)url error:

IOS开发之简单音频播放器

        今天第一次接触IOS开发的UI部分,之前学OC的时候一直在模拟的使用Target-Action回调模式,今天算是真正的用了一次.为了熟悉一下基本控件的使用方法,和UI部分的回调,下面开发了一个特别简易的音频播放器,来犒劳一下自己这一天的UI学习成果.在用到UI的控件时如果很好的理解之前博客在OC中的Target-Action回调模式,感觉控件的用法会很顺手.下面的简易播放器没有用到多高深的技术,只是一些基本控件和View的使用.         话不多说简单的介绍一下今天的音频播

iOS音频的后台播放总结(后台网络请求歌曲,Remote控制,锁屏封面,各种打断)

在没有网络的情况下,音频的后台播放比较简单,google一下可以搜到很多资料,但是如果每次歌曲的请求都是通过网络,就不成了,有时可以也扛不了几首,这里总结下实现方法,可以实现像电台一样的功能,后台播放,网络请求歌曲,Remote控制,锁屏有封面,电话和听歌打断处理等.     初始化AudioSession和基本配置 音频播放器采用的AVPlayer ,自己进行了功能封装,暂且不谈,在程序启动的时候需要配置AudioSession,AudioSession负责应用音频的设置,比如支不支持后台,打

iOS实现播放远程网络音乐的核心技术点总结_IOS

一.前言 这两天做了个小项目涉及到了远程音乐播放,因为第一次做这种音乐项目,边查资料边做,其中涉及到主要技术点有: 如何播放远程网络音乐 如何切换当前正在播放中的音乐资源 如何监听音乐播放的各种状态(播放器状态.播放的进度.缓冲的进度,播放完成) 如何手动操控播放进度 如何在后台模式或者锁屏情况下正常播放音乐 如何在锁屏模式下显示音乐播放信息和远程操控音乐 如果您对一块技术点有兴趣或者正在寻找相关资料,那么本篇或许能提供一些参考或启发. 二. 网络音乐播放的核心技术点 根据自己的经验和查了一些音