前言
這兩天禁(晉)煙(嫣)的秀恩愛氓仲,身為程序員的我們又被默默的送了一把狗糧酌儒,這段時間一直在忙公司項目,兩個多月都沒有寫過文章了,今天閑來無事想把iOS中播放音樂(包括段音效)的部分拿出來總結(jié)一下愉舔。
主要部分:
1.音效的播放
2.音樂的播放(本地, 網(wǎng)絡(luò))
3.音頻隊列服務(wù)
1.音效播放(AudioToolbox/AudioToolbox.h)
音頻文件必須打包成.caf伙菜、.aif轩缤、.wav中的一種(注意這是官方文檔的說法,實際測試發(fā)現(xiàn)一些.mp3也可以播放)
這個段音效播放不能大于30s贩绕,這個30s不是我說的火的,是蘋果的API說的
創(chuàng)建音效的ID,音效的播放和銷毀都靠這個ID來執(zhí)行
AudioServicesCreateSystemSoundID(CFURLRef inFileURL, SystemSoundID* outSystemSoundID)
播放音效
AudioServicesPlaySystemSound(SystemSoundID inSystemSoundID)
iOS9以后可以用的淑倾,帶有block回調(diào)的播放
AudioServicesPlaySystemSoundWithCompletion(SystemSoundID inSystemSoundID, void (^__nullable inCompletionBlock)(void))
帶有震動的播放
AudioServicesPlayAlertSound(SystemSoundID inSystemSoundID)
iOS9以后可以用的馏鹤,帶有block回調(diào)的播放
AudioServicesPlayAlertSoundWithCompletion( SystemSoundID inSystemSoundID,void (^__nullable inCompletionBlock)(void))
在iOS9之前,如何判斷一個音效是否播放完成呢娇哆?(利用下面的方法)
AudioServicesAddSystemSoundCompletion(SystemSoundID inSystemSoundID,CFRunLoopRef __nullable inRunLoop, CFStringRef __nullable inRunLoopMode,AudioServicesSystemSoundCompletionProc inCompletionRoutine,void * __nullable inClientData)
銷毀音效的播放
AudioServicesDisposeSystemSoundID(SystemSoundID inSystemSoundID)
下面對上面的方法的演示湃累,播放一些音效, 播放48s的mp3時會報錯
static SystemSoundID soundID = 0;
- (IBAction)play:(id)sender {
// NSString *str = [[NSBundle mainBundle] pathForResource:@"vcyber_waiting" ofType:@"wav"];
NSString *str = [[NSBundle mainBundle] pathForResource:@"28s" ofType:@"mp3"];
// NSString *str = [[NSBundle mainBundle] pathForResource:@"48s" ofType:@"mp3"];
NSURL *url = [NSURL fileURLWithPath:str];
AudioServicesCreateSystemSoundID((__bridge CFURLRef _Nonnull)(url), &soundID);
//
// AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, soundCompleteCallBack, NULL);
//
// //AudioServicesPlaySystemSound(soundID);
//
// AudioServicesPlayAlertSound(soundID);
// AudioServicesPlaySystemSoundWithCompletion(soundID, ^{
// NSLog(@"播放完成");
// AudioServicesDisposeSystemSoundID(soundID);
// });
AudioServicesPlayAlertSoundWithCompletion(soundID, ^{
NSLog(@"播放完成");
});
}
void soundCompleteCallBack(SystemSoundID soundID, void * clientDate) {
NSLog(@"播放完成");
AudioServicesDisposeSystemSoundID(soundID);
}
- (IBAction)stop:(id)sender {
AudioServicesDisposeSystemSoundID(soundID);
}
2.本地音樂播放
AVAudioPlayer
AVAudioPlayer是播放本地音樂最常到的勃救,這個類對于大多數(shù)人來說應(yīng)該很常用,這里不多說治力,說一下它的基本用法和代理的用法蒙秒,直接上代碼,代碼注釋很詳細(xì)
@interface LocalMusicViewController ()<AVAudioPlayerDelegate>
/**
播放器
*/
@property (nonatomic, strong) AVAudioPlayer *player;
/**
播放進(jìn)度條
*/
@property (weak, nonatomic) IBOutlet UIProgressView *progress;
/**
改變播放進(jìn)度滑塊
*/
@property (weak, nonatomic) IBOutlet UISlider *progressSlide;
/**
改變聲音滑塊
*/
@property (weak, nonatomic) IBOutlet UISlider *volum;
/**
改變進(jìn)度條滑塊顯示的定時器
*/
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation LocalMusicViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSError *err;
NSURL *url = [[NSBundle mainBundle] URLForResource:@"1" withExtension:@"mp3"];
// 初始化播放器
_player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&err];
self.volum.value = 0.5;
// 設(shè)置播放器聲音
_player.volume = self.volum.value;
// 設(shè)置代理
_player.delegate = self;
// 設(shè)置播放速率
_player.rate = 1.0;
// 設(shè)置播放次數(shù) 負(fù)數(shù)代表無限循環(huán)
_player.numberOfLoops = -1;
// 準(zhǔn)備播放
[_player prepareToPlay];
self.progress.progress = 0;
self.progressSlide.value = 0;
_timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(change) userInfo:nil repeats:YES];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
}
- (void)change {
self.progress.progress = _player.currentTime / _player.duration;
}
- (IBAction)progressChange:(UISlider *)sender {
// 改變當(dāng)前的播放進(jìn)度
_player.currentTime = sender.value * _player.duration;
self.progress.progress = sender.value;
}
- (IBAction)volumChange:(UISlider *)sender {
// 改變聲音大小
_player.volume = sender.value;
}
- (IBAction)player:(id)sender {
// 開始播放
[_player play];
}
- (IBAction)stop:(id)sender {
// 暫停播放
[_player stop];
}
#pragma mark --AVAudioPlayerDelegate
/**
完成播放宵统, 但是在打斷播放和暫停晕讲、停止不會調(diào)用
*/
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
}
/**
播放過程中解碼錯誤時會調(diào)用
*/
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError * __nullable)error {
}
/**
播放過程被打斷
*/
- (void)audioPlayerBeginInterruption:(AVAudioPlayer *)player NS_DEPRECATED_IOS(2_2, 8_0) {
}
/**
打斷結(jié)束
*/
- (void)audioPlayerEndInterruption:(AVAudioPlayer *)player withOptions:(NSUInteger)flags NS_DEPRECATED_IOS(6_0, 8_0) {
}
/**
打斷結(jié)束
*/
- (void)audioPlayerEndInterruption:(AVAudioPlayer *)player withFlags:(NSUInteger)flags NS_DEPRECATED_IOS(4_0, 6_0) {
}
/**
這個方法被上面的方法代替了
*/
- (void)audioPlayerEndInterruption:(AVAudioPlayer *)player NS_DEPRECATED_IOS(2_2, 6_0) {
}
網(wǎng)絡(luò)音樂播放(AVPlayer)
AVPlayer是播放網(wǎng)絡(luò)音樂和網(wǎng)絡(luò)視頻最常用到的,它可以自己緩存網(wǎng)絡(luò)數(shù)據(jù)马澈,然后播放瓢省,AVPlayer在播放視頻時必須創(chuàng)建一個AVPlayerLayer用來展示視頻,如果播放音樂箭券,聲音就不用創(chuàng)建這個對象净捅。這里簡單演示一下網(wǎng)絡(luò)播放音樂
1. 通過網(wǎng)絡(luò)鏈接創(chuàng)建AVPlayerItem
AVPlayerItem的初始化方法很多疑枯,我這里直接用initWithURL:
這個方法創(chuàng)建
- (AVPlayerItem *)getItemWithIndex:(NSInteger)index {
NSURL *url = [NSURL URLWithString:self.musicArray[index]];
AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:url];
//KVO監(jiān)聽播放狀態(tài)
[item addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
//KVO監(jiān)聽緩存大小
[item addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
//通知監(jiān)聽item播放完畢
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playOver:) name:AVPlayerItemDidPlayToEndTimeNotification object:item];
return item;
}
2.實現(xiàn)KVO的方法辩块,根據(jù)keyPath來判斷觀察的屬性是哪一個
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
AVPlayerItem *item = object;
if ([keyPath isEqualToString:@"status"]) {
switch (self.player.status) {
case AVPlayerStatusUnknown:
NSLog(@"未知狀態(tài),不能播放");
break;
case AVPlayerStatusReadyToPlay:
NSLog(@"準(zhǔn)備完畢荆永,可以播放");
break;
case AVPlayerStatusFailed:
NSLog(@"加載失敗, 網(wǎng)絡(luò)相關(guān)問題");
break;
default:
break;
}
}
if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
NSArray *array = item.loadedTimeRanges;
//本次緩存的時間
CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];
NSTimeInterval totalBufferTime = CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration); //緩存的總長度
self.bufferProgress.progress = totalBufferTime / CMTimeGetSeconds(item.duration);
}
}
3.懶加載AVPlayer
- (AVPlayer *)player {
if (!_player) {
// 根據(jù)鏈接數(shù)組獲取第一個播放的item废亭, 用這個item來初始化AVPlayer
AVPlayerItem *item = [self getItemWithIndex:self.currentIndex];
// 初始化AVPlayer
_player = [[AVPlayer alloc] initWithPlayerItem:item];
__weak typeof(self)weakSelf = self;
// 監(jiān)聽播放的進(jìn)度的方法,addPeriodicTime: ObserverForInterval: usingBlock:
/*
DMTime 每到一定的時間會回調(diào)一次具钥,包括開始和結(jié)束播放
block回調(diào)豆村,用來獲取當(dāng)前播放時長
return 返回一個觀察對象,當(dāng)播放完畢時需要骂删,移除這個觀察
*/
_timeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
float current = CMTimeGetSeconds(time);
if (current) {
[weakSelf.progressView setProgress:current / CMTimeGetSeconds(item.duration) animated:YES];
weakSelf.progressSlide.value = current / CMTimeGetSeconds(item.duration);
}
}];
}
return _player;
}
4.播放和暫停
// 播放
- (IBAction)play:(id)sender {
[self.player play];
}
//暫停
- (IBAction)pause:(id)sender {
[self.player pause];
}
5.下一首和上一首
- (IBAction)next:(UIButton *)sender {
[self removeObserver];
self.currentIndex ++;
if (self.currentIndex >= self.musicArray.count) {
self.currentIndex = 0;
}
// 這個方法是用一個item取代當(dāng)前的item
[self.player replaceCurrentItemWithPlayerItem:[self getItemWithIndex:self.currentIndex]];
[self.player play];
}
- (IBAction)last:(UIButton *)sender {
[self removeObserver];
self.currentIndex --;
if (self.currentIndex < 0) {
self.currentIndex = 0;
}
// 這個方法是用一個item取代當(dāng)前的item
[self.player replaceCurrentItemWithPlayerItem:[self getItemWithIndex:self.currentIndex]];
[self.player play];
}
// 在播放另一個時掌动,要移除當(dāng)前item的觀察者,還要移除item播放完成的通知
- (void)removeObserver {
[self.player.currentItem removeObserver:self forKeyPath:@"status"];
[self.player.currentItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
6.控制播放進(jìn)度宁玫,這個也有很多的方法粗恢,如果不是太精確,用- (void)seekToTime:(CMTime)time:
這個方法就行欧瘪,如果要精確的用這個- (void)seekToTime:(CMTime)time toleranceBefore:(CMTime)toleranceBefore toleranceAfter:(CMTime)toleranceAfter
- (IBAction)changeProgress:(UISlider *)sender {
if (self.player.status == AVPlayerStatusReadyToPlay) {
[self.player seekToTime:CMTimeMake(CMTimeGetSeconds(self.player.currentItem.duration) * sender.value, 1)];
}
}
音頻隊列服務(wù)(Audio Queue Services)
在AudioToolbox框架中的音頻隊列服務(wù)眷射,是用來播放網(wǎng)絡(luò)流媒體的一個框架,它完全可以做到音頻播放和錄制佛掖,一個音頻服務(wù)隊列有三個部分組成:
1.三個緩沖器Buffers:沒個緩沖器都是一個存儲音頻數(shù)據(jù)的臨時倉庫妖碉。
2.一個緩沖隊列Buffer Queue:一個包含音頻緩沖器的有序隊列。
3.一個回調(diào)CallBack:一個自定義的隊列回調(diào)函數(shù)芥被。
在音頻播放緩沖隊列中欧宜,將音頻讀取到緩沖器中,一旦一個緩沖器填充滿之后就放到緩沖隊列中拴魄,然后繼續(xù)填充其他緩沖器冗茸;當(dāng)開始播放時猛拴,則從第一個緩沖器中讀取音頻進(jìn)行播放;一旦播放完之后就會觸發(fā)回調(diào)函數(shù)蚀狰,開始播放下一個緩沖器中的音頻愉昆,同時填充第一個緩沖器放;填充滿之后再次放回到緩沖隊列麻蹋。下面是官方詳細(xì)的流程:
AudioQueue的工作大致流程:
1.創(chuàng)建
AudioQueue
,創(chuàng)建BufferArray
數(shù)組跛溉,用于存放AudioQueueBufferRef
2.通過
AudioQueueAllocateBuffer
創(chuàng)建AudioQueueBufferRef
一般2-3個,放入到BufferArray
數(shù)組中3.有數(shù)據(jù)時從
buffer
數(shù)組取出一個buffer
扮授,memcpy
數(shù)據(jù)后用AudioQueueEnqueueBuffer
方法把buffer
插入AudioQueue
中4.
AudioQueue
中存在Buffer
后芳室,調(diào)用AudioQueueStart
播放。(具體等到填入多少buffer
后再播放可以自己控制刹勃,只要能保證播放不間斷即可)5.
AudioQueue
播放音樂后消耗了某個buffer
堪侯,在另一個線程回調(diào)并送出該buffe
r,把buffer
放回BufferArray
供下一次使用6.返回步驟3繼續(xù)循環(huán)直到播放結(jié)束
常用API
創(chuàng)建AudioQueue
第一個參數(shù)表示需要播放的音頻數(shù)據(jù)格式類型荔仁,是一個AudioStreamBasicDescription對象伍宦,是使用AudioFileStream或者AudioFile解析出來的數(shù)據(jù)格式信息;
第二個參數(shù)AudioQueueOutputCallback是某塊Buffer被使用之后的回調(diào)乏梁;
第三個參數(shù)為上下文對象次洼;
第四個參數(shù)inCallbackRunLoop為AudioQueueOutputCallback需要在的哪個RunLoop上被回調(diào),如果傳入NULL的話就會再AudioQueue的內(nèi)部RunLoop中被回調(diào)遇骑,所以一般傳NULL就可以了卖毁;
第五個參數(shù)inCallbackRunLoopMode為RunLoop模式,如果傳入NULL就相當(dāng)于kCFRunLoopCommonModes落萎,也傳NULL就可以了亥啦;
第六個參數(shù)inFlags是保留字段,目前沒作用练链,傳0翔脱;
第七個參數(shù),返回生成的AudioQueue實例兑宇;
返回值用來判斷是否成功創(chuàng)建(OSStatus == noErr)碍侦。
extern OSStatus
AudioQueueNewOutput( const AudioStreamBasicDescription *inFormat,
AudioQueueOutputCallback inCallbackProc,
void * __nullable inUserData,
CFRunLoopRef __nullable inCallbackRunLoop,
CFStringRef __nullable inCallbackRunLoopMode,
UInt32 inFlags,
AudioQueueRef __nullable * __nonnull outAQ)
參數(shù)和上面基本相同,只是把RunLoop換成了dispatch queue
AudioQueueNewOutputWithDispatchQueue(AudioQueueRef __nullable * __nonnull outAQ,
const AudioStreamBasicDescription *inFormat,
UInt32 inFlags,
dispatch_queue_t inCallbackDispatchQueue,
AudioQueueOutputCallbackBlock inCallbackBlock)
創(chuàng)建Buffer
第一個參數(shù)方法傳入AudioQueue實例
第二個參數(shù)Buffer大小
第三個傳出的BufferArray實例隶糕;
extern OSStatus
AudioQueueAllocateBuffer(AudioQueueRef inAQ,
UInt32 inBufferByteSize,
AudioQueueBufferRef __nullable * __nonnull outBuffer)
比上面的方法多了一個inNumberPacketDescriptions瓷产,這個參數(shù)可以指定生成的Buffer中PacketDescriptions的個數(shù)
extern OSStatus
AudioQueueAllocateBufferWithPacketDescriptions(
AudioQueueRef inAQ,
UInt32 inBufferByteSize,
UInt32 inNumberPacketDescriptions,
AudioQueueBufferRef __nullable * __nonnull outBuffer)
釋放buffer
第一個參數(shù)AudioQueue實例
第二個參數(shù)指定的buffer
extern OSStatus
AudioQueueFreeBuffer( AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer)
插入buffer
第一個參數(shù)AudioQueue實例
第二個參數(shù)指定的Buffer
第三個參數(shù)數(shù)據(jù)包的個數(shù)
第四個參數(shù)數(shù)據(jù)包描述
extern OSStatus
AudioQueueEnqueueBuffer( AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
UInt32 inNumPacketDescs,
const AudioStreamPacketDescription * __nullable inPacketDescs)
上面的方法基本滿足要求,這個方法對插入的buffer進(jìn)行額外的更多的操作
extern OSStatus
AudioQueueEnqueueBufferWithParameters(
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
UInt32 inNumPacketDescs,
const AudioStreamPacketDescription * __nullable inPacketDescs,
UInt32 inTrimFramesAtStart,
UInt32 inTrimFramesAtEnd,
UInt32 inNumParamValues,
const AudioQueueParameterEvent * __nullable inParamValues,
const AudioTimeStamp * __nullable inStartTime,
AudioTimeStamp * __nullable outActualStartTime) __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
開始播放
第一個參數(shù)AudioQueue實例
第二個參數(shù)播放時間枚驻,如果直接開始播放 傳NULL
extern OSStatus
AudioQueueStart( AudioQueueRef inAQ,
const AudioTimeStamp * __nullable inStartTime)
解碼數(shù)據(jù)濒旦,不常用,調(diào)用開始播放會自動解碼
extern OSStatus
AudioQueuePrime( AudioQueueRef inAQ,
UInt32 inNumberOfFramesToPrepare,
UInt32 * __nullable outNumberOfFramesPrepared)
停止播放
第二個參數(shù)Bool值再登,控制是否立即停止尔邓,如果傳false晾剖,會把Enqueue的所有buffer播放完成再停止
extern OSStatus
AudioQueueStop( AudioQueueRef inAQ,
Boolean inImmediate) __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
暫停播放
extern OSStatus
AudioQueuePause( AudioQueueRef inAQ) __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
重置解碼器
這個方法會播放完隊列中的buffer后重置解碼器,防止當(dāng)前的解碼器影響下一段音頻梯嗽,比如切換歌曲的時候齿尽,如果和AudioQueueStop(AQ,false)
一起使用并不會起效,因為Stop方法的false參數(shù)也會做同樣的事情灯节。
extern OSStatus
AudioQueueFlush( AudioQueueRef inAQ) __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
重置AudioQueue
重置AudioQueue會清除所有已經(jīng)Enqueue的buffer循头,并觸發(fā)AudioQueueOutputCallback,調(diào)用AudioQueueStop方法時同樣會觸發(fā)該方法。這個方法的直接調(diào)用一般在seek時使用炎疆,用來清除殘留的buffer(seek時還有一種做法是先AudioQueueStop
卡骂,等seek完成后重新start)。
extern OSStatus
AudioQueueReset( AudioQueueRef inAQ) __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
獲取播放時間
調(diào)用時傳入AudioTimeStamp形入,從這個結(jié)構(gòu)體當(dāng)中獲取播放時間
extern OSStatus
AudioQueueGetCurrentTime( AudioQueueRef inAQ,
AudioQueueTimelineRef __nullable inTimeline,
AudioTimeStamp * __nullable outTimeStamp,
Boolean * __nullable outTimelineDiscontinuity) __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
銷毀AudioQueue
參數(shù)的意義基本和AudioQueueStop一樣
extern OSStatus
AudioQueueDispose( AudioQueueRef inAQ,
Boolean inImmediate) __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
AudioQueue參數(shù)
AudioQueueGetParameter
AudioQueueSetParameter
參數(shù)列表
CF_ENUM(AudioQueueParameterID)
{
kAudioQueueParam_Volume = 1,
kAudioQueueParam_PlayRate = 2,
kAudioQueueParam_Pitch = 3,
kAudioQueueParam_VolumeRampTime = 4,
kAudioQueueParam_Pan = 13
};
AudioQueue屬性
AudioQueueGetPropertySize
AudioQueueGetProperty
AudioQueueSetProperty
屬性列表
CF_ENUM(AudioQueuePropertyID) {
kAudioQueueProperty_IsRunning = 'aqrn', // value is UInt32
kAudioQueueDeviceProperty_SampleRate = 'aqsr', // value is Float64
kAudioQueueDeviceProperty_NumberChannels = 'aqdc', // value is UInt32
kAudioQueueProperty_CurrentDevice = 'aqcd', // value is CFStringRef
kAudioQueueProperty_MagicCookie = 'aqmc', // value is void*
kAudioQueueProperty_MaximumOutputPacketSize = 'xops', // value is UInt32
kAudioQueueProperty_StreamDescription = 'aqft', // value is AudioStreamBasicDescription
kAudioQueueProperty_ChannelLayout = 'aqcl', // value is AudioChannelLayout
kAudioQueueProperty_EnableLevelMetering = 'aqme', // value is UInt32
kAudioQueueProperty_CurrentLevelMeter = 'aqmv', // value is array of AudioQueueLevelMeterState, 1 per channel
kAudioQueueProperty_CurrentLevelMeterDB = 'aqmd', // value is array of AudioQueueLevelMeterState, 1 per channel
kAudioQueueProperty_DecodeBufferSizeFrames = 'dcbf', // value is UInt32
kAudioQueueProperty_ConverterError = 'qcve', // value is UInt32
kAudioQueueProperty_EnableTimePitch = 'q_tp', // value is UInt32, 0/1
kAudioQueueProperty_TimePitchAlgorithm = 'qtpa', // value is UInt32. See values below.
kAudioQueueProperty_TimePitchBypass = 'qtpb', // value is UInt32, 1=bypassed
};
監(jiān)聽屬相變化相關(guān)方法
AudioQueueAddPropertyListener
AudioQueueRemovePropertyListener
總結(jié):
這里說的東西都比(能)較(力)基(有)礎(chǔ)(限)全跨,其實AudioQueue的功能還有很多,如果大家想去研究比較細(xì)致的AudioQueue的使用亿遂,這里給大家推薦兩個github地址浓若,一個是AudioStreamer,一個是FreeStreamer崩掘,這里的兩個播放都是使用AudioQueue實現(xiàn)的七嫌。