原博: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播放流程:
- 读取文件
- 解析采样率、码率等信息,分离音频帧
- 对分离出来的音频帧解码得到PCM数据
- 对PCM数据进行音效处理
- 把PCM数据解码成音频信号
- 音频信号传递至硬件播放
- 重复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);