前言
這是一篇關(guān)于在線音頻播放的文章柜候,參考自蘋果OS X的demo搞动。
在移植到iOS后,可以通過iphone播放Mac上面的音頻渣刷,實現(xiàn)在線播放音頻的功能鹦肿。
本文可以學(xué)習(xí)到socket編程、AudioFileStream轉(zhuǎn)換音頻流辅柴、AudioQueue播放音頻箩溃、信號量的使用瞭吃。
正文
demo有兩個工程,分別是servers
和client
涣旨。
servers是OS X的應(yīng)用歪架,作為服務(wù)端,負(fù)責(zé)發(fā)送音頻流數(shù)據(jù)霹陡;
client是iOS的應(yīng)用和蚪,作為客戶端,負(fù)責(zé)接收音頻流數(shù)據(jù)烹棉;
音頻數(shù)據(jù)通過AudioFileStream轉(zhuǎn)換后攒霹,調(diào)用AudioQueue進(jìn)行播放,中間會用到信號量進(jìn)行等待和同步浆洗。
1催束、socket編程
bind方法用于綁定接口,然后用listen監(jiān)聽tcp連接請求伏社,accept用于接受tcp連接泣崩;
fopen打開音頻文件,fread讀取音頻數(shù)據(jù)洛口,send對建立的連接發(fā)送音頻流矫付;
對已經(jīng)失效的socket,send兩次數(shù)據(jù)就會觸發(fā)SIGPIPE信號第焰,默認(rèn)的處理是關(guān)閉進(jìn)程买优。
// 打開文件
FILE* file = fopen([[[NSBundle mainBundle] pathForResource:@"chenli" ofType:@"mp3"] UTF8String], "r");
// 創(chuàng)建socket
int listener_socket = socket(AF_INET, SOCK_STREAM, 0);
// 綁定socket
bind(listener_socket, (struct sockaddr*)&server_sockaddr, sizeof(server_sockaddr));
// 監(jiān)聽tcp連接
listen(listener_socket, 4);
// 接收tcp連接,注意挺举!這里并不是三次握手杀赢。
int connection_socket = accept(listener_socket, (struct sockaddr*)&client_sockaddr, &client_sockaddr_size);
// 讀取文件
size_t bytesRead = fread(buf, 1, 32768, file);
// 發(fā)送音頻流
ssize_t bytesSent = send(connection_socket, buf, bytesRead, 0);
// 關(guān)閉socket
close(connection_socket);
2、AudioQueue播放音頻
AudioQueue的播放時湘纵,需要先給audioBuffer填充數(shù)據(jù)脂崔,并把a(bǔ)udioBuffer放入AudioQueue,然后通知AudioQueue開始播放梧喷;
AudioQueue從已經(jīng)填充的audioBuffer里面開始播放數(shù)據(jù)砌左,實時把播放完畢的audioBuffer回調(diào)給業(yè)務(wù)層,業(yè)務(wù)繼續(xù)填充播放完畢的audioBuffer铺敌,重復(fù)流程直到音頻播放完畢汇歹。
前文使用AudioToolbox播放AAC有對AudioQueue更詳細(xì)的介紹以及更簡化的demo。
配置AudioQueue
// 添加AudioQueue的回調(diào)函數(shù)和添加參數(shù)偿凭,MyAudioQueueOutputCallback是播完結(jié)束的回調(diào)
AudioQueueNewOutput(&asbd, MyAudioQueueOutputCallback, myData, NULL, NULL, 0, &audioQueue);
// AudioBuffer分配buffer
AudioQueueAllocateBuffer(audioQueue, kAQBufSize, &audioQueueBuffer[i]);
// 添加AudioQueue的屬性監(jiān)聽
AudioQueueAddPropertyListener(audioQueue, kAudioQueueProperty_IsRunning, MyAudioQueueIsRunningCallback, myData);
開始播放
// 開始AudioQueue播放
AudioQueueStart(myData->audioQueue, NULL);
// 向AudioQueue傳入buffer
AudioQueueEnqueueBuffer(audioQueue, fillBuf, (UInt32)myData->packetsFilled, packetDescs);
播放結(jié)束
// 傳入最后的音頻數(shù)據(jù)后需要調(diào)用产弹,否則buffer里面的數(shù)據(jù)可能會影響下次播放
AudioQueueFlush(audioQueue);
// 如果需要停止播放,可以調(diào)用這個函數(shù)弯囊,第二個參數(shù)表示同步/異步
AudioQueueStop(audioQueue, false);
// 播放完畢痰哨,銷毀隊列
AudioQueueDispose(audioQueue, false);
3胶果、互斥鎖
普通鎖
pthread_mutex_lock(mutex) 加鎖,可能會阻塞斤斧;
pthread_mutex_unlock(mutex) 解鎖稽物;
條件鎖(pthread_cond_wait)
調(diào)用pthread_cond_wait時,條件不成立則阻塞折欠,直到條件成立贝或;
調(diào)用pthread_cond_wait前,要先調(diào)用pthread_mutex_lock(mutex)加鎖锐秦,pthread_cond_wait會在調(diào)用結(jié)束解鎖mutex咪奖;
pthread_cond_wait條件滿足后(pthread_cond_signal被調(diào)用),會對mutex加鎖酱床,當(dāng)我們執(zhí)行完程序時需要對mutex解鎖羊赵;
調(diào)用pthread_cond_wait時,為了防止并發(fā)放入阻塞隊列扇谣,所以需要提前對mutex加鎖昧捷;
申請條件鎖
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);
釋放條件鎖
pthread_mutex_lock(&mutex);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
4、AudioFileStream轉(zhuǎn)換音頻流
AudioFileStream可以用來讀取音頻流信息和分離音頻幀罐寨,與之類似的API簇還有AudioFile和ExtAudioFile靡挥。
AudioFileStream可以用在線音頻流,也可以使用本地文件鸯绿。
// 打開一個音頻流轉(zhuǎn)換器跋破,需要設(shè)置AudioFileStream_PropertyListenerProc 和 AudioFileStream_PacketsProc 回調(diào)函數(shù);
AudioFileStreamOpen(myData, MyPropertyListenerProc, MyPacketsProc, kAudioFileAAC_ADTSType, &audioFileStream);
// AudioFileStreamParseBytes 解析數(shù)據(jù)瓶蝴,會調(diào)用之前設(shè)置好的AudioFileStream_PropertyListenerProc 和 AudioFileStream_PacketsProc 回調(diào)函數(shù)毒返;
AudioFileStreamParseBytes(myData->audioFileStream, (UInt32)bytesRecvd, buf, 0);
// 獲取特定的屬性
AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataFormat, &asbdSize, &asbd);
// 關(guān)閉音頻流
AudioFileStreamClose(audioFileStream);
附錄
demo中用到用到的一些方法:
AudioFileStreamParseBytes
解析數(shù)據(jù),會調(diào)用之前設(shè)置好的AudioFileStream_PropertyListenerProc
和 AudioFileStream_PacketsProc
回調(diào)函數(shù)舷手;
AudioFileStreamOpen
打開一個音頻流轉(zhuǎn)換器拧簸,需要設(shè)置AudioFileStream_PropertyListenerProc
和 AudioFileStream_PacketsProc
回調(diào)函數(shù);
MyPropertyListenerProc
音頻屬性回調(diào)函數(shù)男窟;
MyPacketsProc
數(shù)據(jù)回調(diào)函數(shù)盆赤;
MyEnqueueBuffer
把buffer里面的數(shù)據(jù)傳入AudioQueue;
WaitForFreeBuffer
當(dāng)前所有buffer已經(jīng)占用滿蝎宇,等待AudioQueue播放完釋放buffer弟劲;
MyAudioQueueOutputCallback
AudioQueue釋放buffer的回調(diào)函數(shù);
MyAudioQueueIsRunningCallback
AudioQueue是否在播放的回調(diào)函數(shù)姥芥;
MyConnectSocket
建立socket鏈接
demo 的代碼地址在這里傳送門。
demo的打開方式:
server是服務(wù)端汇鞭,運行在OS X
有binary和app兩種方式
- binary需要編譯完之后凉唐,找到二進(jìn)制所在的目錄庸追,在其目錄下放對應(yīng)的音頻文件;
- app打開台囱,保持運行淡溯;
client是客戶端,運行在iOS
- 1簿训、在getHostName處需要修改為OS X的ip地址咱娶;
- 2、iOS和OS X需要處于同一局域網(wǎng)强品;
- 3膘侮、clietn未播放完結(jié)束,會導(dǎo)致server關(guān)閉的榛;
總結(jié)
這個demo很有意思:用到很多知識點琼了,而且很簡單,非常適合學(xué)習(xí)夫晌。
最近越來越忙雕薪,如果有問題可以評論或者簡信聯(lián)系,盡量清楚點描述問題還有問題的上下文晓淀。
前文系列所袁,或許會有興趣。
使用VideoToolbox硬編碼H.264
使用VideoToolbox硬解碼H.264
使用AudioToolbox編碼AAC
使用AudioToolbox播放AAC
HLS點播實現(xiàn)(H.264和AAC碼流)
HLS推流的實現(xiàn)(iOS和OS X系統(tǒng))