前言
這是一篇關于在線音頻播放的文章,參考自蘋果OS X的demo。
在移植到iOS后,可以通過iphone播放Mac上面的音頻,實現在線播放音頻的功能。
本文可以學習到socket編程、AudioFileStream轉換音頻流、AudioQueue播放音頻、信號量的使用。
正文
demo有兩個工程,分別是servers
和client
。
servers是OS X的應用,作為服務端,負責發送音頻流數據;
client是iOS的應用,作為客戶端,負責接收音頻流數據;
音頻數據通過AudioFileStream轉換后,調用AudioQueue進行播放,中間會用到信號量進行等待和同步。
1、socket編程
bind方法用于綁定接口,然后用listen監聽tcp連接請求,accept用于接受tcp連接;
fopen打開音頻文件,fread讀取音頻數據,send對建立的連接發送音頻流;
對已經失效的socket,send兩次數據就會觸發SIGPIPE信號,默認的處理是關閉進程。
// 打開文件
FILE* file = fopen([[[NSBundle mainBundle] pathForResource:@"chenli" ofType:@"mp3"] UTF8String], "r");
// 創建socket
int listener_socket = socket(AF_INET, SOCK_STREAM, 0);
// 綁定socket
bind(listener_socket, (struct sockaddr*)&server_sockaddr, sizeof(server_sockaddr));
// 監聽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);
// 發送音頻流
ssize_t bytesSent = send(connection_socket, buf, bytesRead, 0);
// 關閉socket
close(connection_socket);
2、AudioQueue播放音頻
AudioQueue的播放時,需要先給audioBuffer填充數據,并把audioBuffer放入AudioQueue,然后通知AudioQueue開始播放;
AudioQueue從已經填充的audioBuffer里面開始播放數據,實時把播放完畢的audioBuffer回調給業務層,業務繼續填充播放完畢的audioBuffer,重復流程直到音頻播放完畢。
前文使用AudioToolbox播放AAC有對AudioQueue更詳細的介紹以及更簡化的demo。
配置AudioQueue
// 添加AudioQueue的回調函數和添加參數,MyAudioQueueOutputCallback是播完結束的回調
AudioQueueNewOutput(&asbd, MyAudioQueueOutputCallback, myData, NULL, NULL, 0, &audioQueue);
// AudioBuffer分配buffer
AudioQueueAllocateBuffer(audioQueue, kAQBufSize, &audioQueueBuffer[i]);
// 添加AudioQueue的屬性監聽
AudioQueueAddPropertyListener(audioQueue, kAudioQueueProperty_IsRunning, MyAudioQueueIsRunningCallback, myData);
開始播放
// 開始AudioQueue播放
AudioQueueStart(myData->audioQueue, NULL);
// 向AudioQueue傳入buffer
AudioQueueEnqueueBuffer(audioQueue, fillBuf, (UInt32)myData->packetsFilled, packetDescs);
播放結束
// 傳入最后的音頻數據后需要調用,否則buffer里面的數據可能會影響下次播放
AudioQueueFlush(audioQueue);
// 如果需要停止播放,可以調用這個函數,第二個參數表示同步/異步
AudioQueueStop(audioQueue, false);
// 播放完畢,銷毀隊列
AudioQueueDispose(audioQueue, false);
3、互斥鎖
普通鎖
pthread_mutex_lock(mutex) 加鎖,可能會阻塞;
pthread_mutex_unlock(mutex) 解鎖;
條件鎖(pthread_cond_wait)
調用pthread_cond_wait時,條件不成立則阻塞,直到條件成立;
調用pthread_cond_wait前,要先調用pthread_mutex_lock(mutex)加鎖,pthread_cond_wait會在調用結束解鎖mutex;
pthread_cond_wait條件滿足后(pthread_cond_signal被調用),會對mutex加鎖,當我們執行完程序時需要對mutex解鎖;
調用pthread_cond_wait時,為了防止并發放入阻塞隊列,所以需要提前對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轉換音頻流
AudioFileStream可以用來讀取音頻流信息和分離音頻幀,與之類似的API簇還有AudioFile和ExtAudioFile。
AudioFileStream可以用在線音頻流,也可以使用本地文件。
// 打開一個音頻流轉換器,需要設置AudioFileStream_PropertyListenerProc 和 AudioFileStream_PacketsProc 回調函數;
AudioFileStreamOpen(myData, MyPropertyListenerProc, MyPacketsProc, kAudioFileAAC_ADTSType, &audioFileStream);
// AudioFileStreamParseBytes 解析數據,會調用之前設置好的AudioFileStream_PropertyListenerProc 和 AudioFileStream_PacketsProc 回調函數;
AudioFileStreamParseBytes(myData->audioFileStream, (UInt32)bytesRecvd, buf, 0);
// 獲取特定的屬性
AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataFormat, &asbdSize, &asbd);
// 關閉音頻流
AudioFileStreamClose(audioFileStream);
附錄
demo中用到用到的一些方法:
AudioFileStreamParseBytes
解析數據,會調用之前設置好的AudioFileStream_PropertyListenerProc
和 AudioFileStream_PacketsProc
回調函數;
AudioFileStreamOpen
打開一個音頻流轉換器,需要設置AudioFileStream_PropertyListenerProc
和 AudioFileStream_PacketsProc
回調函數;
MyPropertyListenerProc
音頻屬性回調函數;
MyPacketsProc
數據回調函數;
MyEnqueueBuffer
把buffer里面的數據傳入AudioQueue;
WaitForFreeBuffer
當前所有buffer已經占用滿,等待AudioQueue播放完釋放buffer;
MyAudioQueueOutputCallback
AudioQueue釋放buffer的回調函數;
MyAudioQueueIsRunningCallback
AudioQueue是否在播放的回調函數;
MyConnectSocket
建立socket鏈接
demo 的代碼地址在這里傳送門。
demo的打開方式:
server是服務端,運行在OS X
有binary和app兩種方式
- binary需要編譯完之后,找到二進制所在的目錄,在其目錄下放對應的音頻文件;
- app打開,保持運行;
client是客戶端,運行在iOS
- 1、在getHostName處需要修改為OS X的ip地址;
- 2、iOS和OS X需要處于同一局域網;
- 3、clietn未播放完結束,會導致server關閉;
總結
這個demo很有意思:用到很多知識點,而且很簡單,非常適合學習。
最近越來越忙,如果有問題可以評論或者簡信聯系,盡量清楚點描述問題還有問題的上下文。
前文系列,或許會有興趣。
使用VideoToolbox硬編碼H.264
使用VideoToolbox硬解碼H.264
使用AudioToolbox編碼AAC
使用AudioToolbox播放AAC
HLS點播實現(H.264和AAC碼流)
HLS推流的實現(iOS和OS X系統)