iOS音频播放学习(1)

原博:http://msching.github.io/blog/categories/ios-audio/


相关知识


脉冲编码调制(Pulse Code Modulation):对声音采样和量化过程,简称PCM

MP3:MP3中的码率(BitRate)代表了MP3的数据压缩质量,码率越高质量越好。有固定码率(Constant birate,CBR)和可变码率(Variable bitrate, VBR)。数据有两部分:一部分为ID3来存储歌名等信息,另一部分为音频数据,以帧(frame)为单位存储。CBR中每个帧中包含的PCM是固定的,VBR则是可变的


音频播放概述

iOS播放流程:
  1. 读取文件
  2. 解析采样率、码率等信息,分离音频帧
  3. 对分离出来的音频帧解码得到PCM数据
  4. 对PCM数据进行音效处理
  5. 把PCM数据解码成音频信号
  6. 音频信号传递至硬件播放
  7. 重复1-6

iOS接口:
Audio FIle Services: 读写音频数据,完成第2步
Audio File Stream Services: 解码音频,完成第2步
Audio Converter Services: 音频数据转换,完成第3步
Audio Processing Graph Services: 音效处理模块,完成第4步
Audio Unit Services: 播放音频数据,完成第5.6步
Extended Audio File Services: Audio Fuke Services和Audio Converter Services的结合体
AVAudioPlayer/AVPlayer(AVFoundation):高级接口,可完成整个音频播放流程
Audio Queue Services:高级接口,可完成3、5、6步

使用:
音频播放:AVFoundation
音频流播放:AudioFileStreamer + AudioQueue


AudioSession

功能:确定音频的使用方式(播放还是录音);选择合适的输入输出设备;协调系统和其他app的行为
一般使用的是AVFoundation中的AVAudioSession(AudioToolbox中的AudioSession已经depracated)

初始化
extern OSStatus AudioSessionInitialize(CFRunLoopRef inRunLoop,
                                       CFStringRef inRunLoopMode, // 前两个参数为NULL,表示AudioSession运行在主线程
                                       AudioSessionInterruptionListener inInterruptionListener, // AudioSession被打断时的回调
                                       void *inClientData); // 代表打断回调时的附带对象,可以理解为上下文

typedef void (*AudioSessionInterruptionListener)(void * inClientData, UInt32 inInterruptionState);

监听RouteChange(诸如拔掉耳机就暂停音乐的功能)
extern OSStatus AudioSessionAddPropertyListener(AudioSessionPropertyID inID, // 传入kAudioSessionProperty_AudioRouteChange
                                                AudioSessionPropertyListener inProc, // 传入回调方法
                                                void *inClientData);
                                              
typedef void (*AudioSessionPropertyListener)(void * inClientData, // 接到回调时可以把该参数换成CFDictionaryRef并获取kAudioSession_AudioRouteChangeKey_Reason对应的value
                                             AudioSessionPropertyID inID,
                                             UInt32 inDataSize,
                                             const void * inData);
//AVAudioSession的AudioRouteChangeReason枚举
typedef NS_ENUM(NSUInteger, AVAudioSessionRouteChangeReason)
{
  AVAudioSessionRouteChangeReasonUnknown = 0,
  AVAudioSessionRouteChangeReasonNewDeviceAvailable = 1,
  AVAudioSessionRouteChangeReasonOldDeviceUnavailable = 2,
  AVAudioSessionRouteChangeReasonCategoryChange = 3,
  AVAudioSessionRouteChangeReasonOverride = 4,
  AVAudioSessionRouteChangeReasonWakeFromSleep = 6,
  AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory = 7,
  AVAudioSessionRouteChangeReasonRouteConfigurationChange NS_ENUM_AVAILABLE_IOS(7_0) = 8
}

设置类别
extern OSStatus AudioSessionSetProperty(AudioSessionPropertyID inID,
                                        UInt32 inDataSize,
                                        const void *inData);
使用AVAudioSession时调用下面的接口
/* set session category */
- (BOOL)setCategory:(NSString *)category error:(NSError **)outError;
/* set session category with options */
- (BOOL)setCategory:(NSString *)category withOptions: (AVAudioSessionCategoryOptions)options error:

启动方法
//AVAudioSession的启动方法
-(BOOL)setActive:(BOOL)activeerror:(NSError**)outError;
-(BOOL)setActive:(BOOL)activewithFlags:(NSInteger)flagserror:(NSError**)outErrorNS_DEPRECATED_IOS(4_0,6_0);
-(BOOL)setActive:(BOOL)activewithOptions:(AVAudioSessionSetActiveOptions)optionserror:(NSError**)outErrorNS_AVAILABLE_IOS(6_0);


AudioFileStream

初始化
extern OSStatus AudioFileStreamOpen (void * inClientData,
                                     AudioFileStream_PropertyListenerProc inPropertyListenerProc, // 歌曲信息解析回调
                                     AudioFileStream_PacketsProc inPacketsProc, // 分离帧的回调
                                     AudioFileTypeID inFileTypeHint, // 文件类型的提示
                                     AudioFileStreamID * outAudioFileStream); // 实例对应的ID,需要保存起来以便使用

//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'        
};
返回值用于判断是否初始化成功(OSStatus == noErr)

解析数据
extern OSStatus AudioFileStreamParseBytes(AudioFileStreamID inAudioFileStream, // 初始化时返回的ID
                                          UInt32 inDataByteSize, // 本次解析的数据长度
                                          const void* inData, // 本次解析的数据
                                          UInt32 inFlags); // 本次解析和上一次是否连续关系,连续传入0,否则传入kAudioFileStreamParseFlag_Discontinuity
// 返回值表示当前数据是否被正常解析,错误码包括
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!'
};

解析文件格式
调用上述方法解析时,会首先读取格式信息,并同步调用AudioFileStream_PropertyListenerProc回调方法
typedef void (*AudioFileStream_PropertyListenerProc)(void * inClientData,
                                                     AudioFileStreamID inAudioFileStream,
                                                     AudioFileStreamPropertyID inPropertyID, // 此次回调的信息ID,使用下面接口获取对应值或者数据结构
                                                     UInt32 * ioFlags); // property是否需要被缓存,需要则赋值kAudioFileStreamPropertyFlag_PropertyIsCached
extern OSStatus AudioFileStreamGetProperty(AudioFileStreamID inAudioFileStream,
                                           AudioFileStreamPropertyID inPropertyID,
                                           UInt32 * ioPropertyDataSize,
                                           void * outPropertyData);

// PropertyID列表
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', // 音频数据在整个文件中的offset
  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'
};



计算时长
从ID3中获取或者计算
double duration = (audioDataByteCount * 8) / bitRate

分离音频帧
读取格式信息完成之后调用AudioFileStreamParseBytes进行帧分离,同步进入AudioFileStream_PacketsProc回调方法
typedef void (*AudioFileStream_PacketsProc)(void * inClientData,
                                            UInt32 inNumberBytes, // 本次处理的数据大小
                                            UInt32 inNumberPackets, // 本次处理了多少帧(Packet)
                                            const void * inInputData, // 本次处理的所有数据
                                            AudioStreamPacketDescription * inPacketDescriptions); // 数组,存储了每一帧数据是从第几个字节开始,这一帧总共多少字节

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

Seek
计算Seek的字节
double seekToTime = ...; //需要seek到哪个时间,秒为单位
UInt64 audioDataByteCount = ...; //通过kAudioFileStreamProperty_AudioDataByteCount获取的值
SInt64 dataOffset = ...; //通过kAudioFileStreamProperty_DataOffset获取的值
double durtion = ...; //通过公式(AudioDataByteCount * 8) / BitRate计算得到的时长

//近似seekOffset = 数据偏移 + seekToTime对应的近似字节数
SInt64 approximateSeekOffset = dataOffset + (seekToTime / duration) * audioDataByteCount;

seekToTime对应的帧(Packet)
//首先需要计算每个packet对应的时长
AudioStreamBasicDescription asbd = ...; ////通过kAudioFileStreamProperty_DataFormat或者kAudioFileStreamProperty_FormatList获取的值
double packetDuration = asbd.mFramesPerPacket / asbd.mSampleRate

//然后计算packet位置
SInt64 seekToPacket = floor(seekToTime / packetDuration);

使用AudioFilesStreamSeek计算精确的字节偏移和时间
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;
}

关闭AudioFileStream
extern OSStatus AudioFileStreamClose(AudioFileStreamID inAudioFileStream);


AudioFile

2个打开方法
// 读取本地文件的
enum {
  kAudioFileReadPermission      = 0x01,
  kAudioFileWritePermission     = 0x02,
  kAudioFileReadWritePermission = 0x03
};

extern OSStatus AudioFileOpenURL (CFURLRef inFileRef, // 文件路径
                                  SInt8 inPermissions,  // 文件的允许使用方式,读/写/读写
                                  AudioFileTypeID inFileTypeHint, // 文件类型提示
                                  AudioFileID * outAudioFile); 

2.
extern OSStatus AudioFileOpenWithCallbacks (void * inClientData,
                                            AudioFile_ReadProc inReadFunc, // AudioFile需要读音频时进行的回调
                                            AudioFile_WriteProc inWriteFunc, // 需要写音频时进行的回调
                                            AudioFile_GetSizeProc inGetSizeFunc, // 需用用到文件总大小时的回调
                                            AudioFile_SetSizeProc inSetSizeFunc, // 需要设置文件时的回调
                                            AudioFileTypeID inFileTypeHint,
                                            AudioFileID * outAudioFile);

typedef SInt64 (*AudioFile_GetSizeProc)(void * inClientData); // 文件总长度

typedef OSStatus (*AudioFile_ReadProc)(void * inClientData,
                                       SInt64 inPosition, // 需要读取的第几个字节开始的数据
                                       UInt32 requestCount, // 需要读取的数据长度
                                       void * buffer, // 返回参数
                                       UInt32 * actualCount); // 实际提供的数据长度

读取音频格式信息
extern OSStatus AudioFileGetPropertyInfo(AudioFileID inAudioFile,
                                         AudioFilePropertyID inPropertyID,
                                         UInt32 * outDataSize,
                                         UInt32 * isWritable); // 用于获取某个属性对应的数据的大小以及该属性是否可以被write
                                      
extern OSStatus AudioFileGetProperty(AudioFileID inAudioFile,
                                     AudioFilePropertyID inPropertyID,
                                     UInt32 * ioDataSize,
                                     void * outPropertyData);  // 用来获取属性对应的数据
// 可以获取的属性
enum
{
  kAudioFilePropertyFileFormat             =    'ffmt',
  kAudioFilePropertyDataFormat             =    'dfmt',
  kAudioFilePropertyIsOptimized            =    'optm',
  kAudioFilePropertyMagicCookieData        =    'mgic',
  kAudioFilePropertyAudioDataByteCount     =    'bcnt',
  kAudioFilePropertyAudioDataPacketCount   =    'pcnt',
  kAudioFilePropertyMaximumPacketSize      =    'psze',
  kAudioFilePropertyDataOffset             =    'doff',
  kAudioFilePropertyChannelLayout          =    'cmap',
  kAudioFilePropertyDeferSizeUpdates       =    'dszu',
  kAudioFilePropertyMarkerList             =    'mkls',
  kAudioFilePropertyRegionList             =    'rgls',
  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'
}; 


直接读取音频数据
extern OSStatus AudioFileReadBytes (AudioFileID inAudioFile,
                                    Boolean inUseCache, // 是否需要cache,一般为false
                                    SInt64 inStartingByte, // 从第几个字节开始读取数据
                                    UInt32 * ioNumBytes, // 在调用时作为输入参数表示需要读取多少数据,调用完成后作为输出参数表示实际读取了多少数据
                                    void * outBuffer); // buffer指针
按帧读取音频数据
extern OSStatus AudioFileReadPacketData (AudioFileID inAudioFile,
                                         Boolean inUseCache,
                                         UInt32 * ioNumBytes, // 在输入时表示outBuffer的size,输出时表示实际读取了多少数据
                                         AudioStreamPacketDescription * outPacketDescriptions,
                                         SInt64 inStartingPacket,
                                         UInt32 * ioNumPackets,
                                         void * outBuffer);
                                      
// 读取固定时长音频或者非压缩音频时使用
extern OSStatus AudioFileReadPackets (AudioFileID inAudioFile,
                                      Boolean inUseCache,
                                      UInt32 * outNumBytes, // 只在输出时使用
                                      AudioStreamPacketDescription * outPacketDescriptions,
                                      SInt64 inStartingPacket,
                                      UInt32 * ioNumPackets,
                                      void * outBuffer);

关闭AudioFile
extern OSStatus AudioFileClose (AudioFileID inAudioFile);


AudioQueue

创建
OSStatus AudioQueueNewOutput (const AudioStreamBasicDescription * inFormat, // 使用AudioFileStream或者AudioFile解析出来的数据格式信息
                              AudioQueueOutputCallback inCallbackProc, // 某块buffer被使用之后的回调
                              void * inUserData,
                              CFRunLoopRef inCallbackRunLoop, // AudioQueueOutputCallback需要在哪个RunLoop上被回调。一般传入NULL表示在AudioQueue内部的RunLoop中回调
                              CFStringRef inCallbackRunLoopMode, // 传NULL表示kCFRunLoopCommonModes
                              UInt32 inFlags, 传0
                              AudioQueueRef * outAQ); // 返回生成的AudioQueue实例
                              
OSStatus AudioQueueNewOutputWithDispatchQueue(AudioQueueRef * outAQ,
                                              const AudioStreamBasicDescription * inFormat,
                                              UInt32 inFlags,
                                              dispatch_queue_t inCallbackDispatchQueue,
                                              AudioQueueOutputCallbackBlock inCallbackBlock);

Buffer相关方法
创建buffer
// 传入AudioQueue实例和Buffer大小,传出Buffer实例
OSStatus AudioQueueAllocateBuffer(AudioQueueRef inAQ,
                                  UInt32 inBufferByteSize,
                                  AudioQueueBufferRef * outBuffer);
// 指定生成的Buffer中PacketDescriptions的个数                              
OSStatus AudioQueueAllocateBufferWithPacketDescriptions(AudioQueueRef inAQ,
                                                        UInt32 inBufferByteSize,
                                                        UInt32 inNumberPacketDescriptions,
                                                        AudioQueueBufferRef * outBuffer);
销毁buffer
OSStatus AudioQueueFreeBuffer(AudioQueueRef inAQ,AudioQueueBufferRef inBuffer); // 只能在AudioQueue不再处理数据时使用
插入buffer
OSStatus AudioQueueEnqueueBuffer(AudioQueueRef inAQ,
                                 AudioQueueBufferRef inBuffer,
                                 UInt32 inNumPacketDescs,
                                 const AudioStreamPacketDescription * inPacketDescs);

播放控制
开始播放
OSStatus AudioQueueStart(AudioQueueRef inAQ,const AudioTimeStamp * inStartTime); // 第二个参数为控制播放时间,一般情况下开始播放直接传入NULL
 解码数据(不常用)
OSStatus AudioQueuePrime(AudioQueueRef inAQ,
                          UInt32 inNumberOfFramesToPrepare,
                          UInt32 * outNumberOfFramesPrepared);
暂停播放
OSStatus AudioQueuePause(AudioQueueRef inAQ);
停止播放
OSStatus AudioQueueStop(AudioQueueRef inAQ, Boolean inImmediate); // 第二个参数如果传入true的话会立即停止播放(同步),传入false的话AudioQueue会播放完所有入列的buffer后再停止
Flush
OSStatus AudioQueueFlush(AudioQueueRef inAQ); // 播放完所有入列的buffer后重置解码器
重置
OSStatus AudioQueueReset(AudioQueueRef inAQ); // 清除所有入列的buffer,一般在seek时使用
获取播放时间
OSStatus AudioQueueGetCurrentTime(AudioQueueRef inAQ, // 可传NULL
                                  AudioQueueTimelineRef inTimeline,
                                  AudioTimeStamp * outTimeStamp,
                                  Boolean * outTimelineDiscontinuity); // 可传NULL
// 调用后返回AudioTimeStamp
AudioTimeStamp time = ...; //AudioQueueGetCurrentTime方法获取 这里指的是实际播放时间
NSTimeInterval playedTime = time.mSampleTime / _format.mSampleRate;


销毁AudioQueue
AudioQueueDispose(AudioQueueRef inAQ,  Boolean inImmediate);

你可能感兴趣的:(iOS,学习)