• iOS音频播放 (三):AudioFileStream 转


    原文出处 :http://msching.github.io/blog/2014/07/09/audio-in-ios-3/


    前言

    本来说好是要在第三篇中讲AudioFileStreamAudioQueue,但写着写着发现光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
    2
    3
    4
    5
    
    extern OSStatus AudioFileStreamOpen (void * inClientData,
                                         AudioFileStream_PropertyListenerProc inPropertyListenerProc,
                                         AudioFileStream_PacketsProc inPacketsProc,
                                         AudioFileTypeID inFileTypeHint,
                                         AudioFileStreamID * outAudioFileStream);
    

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

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

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

    第四个參数AudioFileTypeID是文件类型的提示,这个參数来帮助AudioFileStream对文件格式进行解析。

    这个參数在文件信息不完整(比如信息有缺陷)时尤事实上用,它能够给与AudioFileStream一定的提示,帮助其绕过文件里的错误或者缺失从而成功解析文件。所以在确定文件类型的情况下建议各位还是填上这个參数。假设无法确定能够传入0(原理上应该和这篇博文近似);

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

    第五个參数是返回的AudioFileStream实例相应的AudioFileStreamID,这个ID须要保存起来作为兴许一些方法的參数使用。

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


    解析数据

    在初始化完毕之后,仅仅要拿到文件数据就能够进行解析了。解析时调用方法:

    1
    2
    3
    4
    
    extern OSStatus AudioFileStreamParseBytes(AudioFileStreamID inAudioFileStream,
                                              UInt32 inDataByteSize,
                                              const void* inData,
                                              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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    enum
    {
      kAudioFileStreamError_UnsupportedFileType        = 'typ?

    ', kAudioFileStreamError_UnsupportedDataFormat = 'fmt?

    ', kAudioFileStreamError_UnsupportedProperty = 'pty?', kAudioFileStreamError_BadPropertySize = '!siz', kAudioFileStreamError_NotOptimized = 'optm', kAudioFileStreamError_InvalidPacketOffset = 'pck?

    ', kAudioFileStreamError_InvalidFile = 'dta?

    ', kAudioFileStreamError_ValueUnknown = 'unk?', kAudioFileStreamError_DataUnavailable = 'more', kAudioFileStreamError_IllegalOperation = 'nope', kAudioFileStreamError_UnspecifiedError = 'wht?', kAudioFileStreamError_DiscontinuityCantRecover = 'dsc!' };

    大多数都能够从字面上理解,须要提一下的是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
    2
    3
    4
    
    typedef void (*AudioFileStream_PropertyListenerProc)(void * inClientData,
                                                         AudioFileStreamID inAudioFileStream,
                                                         AudioFileStreamPropertyID inPropertyID,
                                                         UInt32 * ioFlags);
    

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

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

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

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

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

    这个回调会进来多次。但并非每一次都须要进行处理,能够依据需求处理须要的PropertyID进行处理(PropertyID列表例如以下)。

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

    这里列几个我觉得比較重要的PropertyID:

    1、kAudioFileStreamProperty_BitRate

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

    。)。

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

    2014.8.2 补充: 发如今流播放的情况下。有时数据流量比較小时会出现ReadyToProducePackets还是没有获取到bitRate的情况。这时就须要分离一些拼音帧然后计算平均bitRate,计算公式例如以下:

    1
    
    UInt32 averageBitRate = totalPackectByteCount / totalPacketCout;
    

    2、kAudioFileStreamProperty_DataOffset

    表示音频数据在整个音频文件里的offset(由于大多数音频文件都会有一个文件头之后才使真正的音频数据),这个值在seek时会发挥比較大的作用。音频的seek并非直接seek文件位置而seek时间(比方seek到2分10秒的位置),seek时会依据时间计算出音频数据的字节offset然后须要再加上音频数据的offset才干得到在文件里的真正offset。

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

    3、kAudioFileStreamProperty_DataFormat

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

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

    4、kAudioFileStreamProperty_FormatList

    作用和kAudioFileStreamProperty_DataFormat是一样的,差别在于用这个PropertyID获取到是一个AudioStreamBasicDescription的数组,这个參数是用来支持AAC SBR这种包括多个文件类型的音频格式。

    因为究竟有多少个format我们并不知晓,所以须要先获取一下总数据大小:

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

    } free(formatList);

    5、kAudioFileStreamProperty_AudioDataByteCount

    顾名思义,音频文件里音频数据的总量。

    这个Property的作用一是用来计算音频的总时长,二是能够在seek时用来计算时间相应的字节offset。

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

    2014.8.2 补充: 发如今流播放的情况下,有时数据流量比較小时会出现ReadyToProducePackets还是没有获取到audioDataByteCount的情况,这时就须要近似计算audioDataByteCount。一般来说音频文件的总大小一定是能够得到的(利用文件系统或者Http请求中的contentLength),那么计算方法例如以下:

    1
    2
    3
    
    UInt32 dataOffset = ...; //kAudioFileStreamProperty_DataOffset
    UInt32 fileLength = ...; //音频文件大小
    UInt32 audioDataByteCount = fileLength - dataOffset;
    

    5、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
    2
    3
    4
    5
    
    typedef void (*AudioFileStream_PacketsProc)(void * inClientData,
                                                UInt32 inNumberBytes,
                                                UInt32 inNumberPackets,
                                                const void * inInputData,
                                                AudioStreamPacketDescription * inPacketDescriptions);
    

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

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

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

    第四个參数。本次处理的全部数据。

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

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

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

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

    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并不easy。

    这里我们也仅仅讨论CBR下的seek。

    CBR数据的seek通常是这样实现的(參考并改动自matt的blog):

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

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

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

    我们能够利用之前Parse得到的音频格式信息来计算PacketDuration。

    audioItem.fileFormat.mFramesPerPacket /audioItem.fileFormat.mSampleRate;

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

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

    AudioFileStreamSeek能够用来寻找某一个帧(Packet)相应的字节偏移(byte offset):

    • 假设找到了就会把ioFlags加上kAudioFileStreamSeekFlag_OffsetIsEstimated,而且给outDataByteOffset赋值。outDataByteOffset就是输入的seekToPacket相应的字节偏移量,我们能够依据outDataByteOffset来计算出精确的seekOffset和seekToTime。
    • 假设没找到那么还是应该用第1步计算出来的approximateSeekOffset来做seek;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    SInt64 seekByteOffset;
    UInt32 ioFlags = 0;
    SInt64 outDataByteOffset;
    OSStatus status = AudioFileStreamSeek(audioFileStreamID, seekToPacket, &outDataByteOffset, &ioFlags);
    if (status == noErr && !(ioFlags & kAudioFileStreamSeekFlag_OffsetIsEstimated))
    {
      //假设AudioFileStreamSeek方法找到了帧的字节偏移,须要修正一下时间
      seekToTime -= ((seekByteOffset - dataOffset) - outDataByteOffset) * 8.0 / bitRate;
      seekByteOffset = outDataByteOffset + dataOffset;
    }
    else
    {
      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


  • 相关阅读:
    SQL2012远程连接到SQL2008时的问题:已成功与服务器建立连接,但在登陆过程中发生错误。
    常用正则表达式
    ASP.NET不拖控件教程(1)-认识JSON
    ASP.NET服务器控件对应的HTML标签
    Apache Ranger 1.1.0源码导入IDEA并运行调试security-admin web模块
    【JDK源码分析】线程池ThreadPoolExecutor原理解析
    【JDK源码分析】同步工具Exchanger,它的内部实现原理你看懂了吗?
    【JDK源码分析】并发包同步工具Semaphore
    【JDK源码分析】并发包同步工具CountDownLatch
    【JDK源码分析】并发包同步工具CyclicBarrier
  • 原文地址:https://www.cnblogs.com/tlnshuju/p/7289554.html
Copyright © 2020-2023  润新知