iOS在線音頻流播放

前言

這是一篇關于在線音頻播放的文章,參考自蘋果OS X的demo。
在移植到iOS后,可以通過iphone播放Mac上面的音頻,實現在線播放音頻的功能。
本文可以學習到socket編程、AudioFileStream轉換音頻流AudioQueue播放音頻、信號量的使用。

正文

demo有兩個工程,分別是serversclient。
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_PropertyListenerProcAudioFileStream_PacketsProc 回調函數;
AudioFileStreamOpen 打開一個音頻流轉換器,需要設置AudioFileStream_PropertyListenerProcAudioFileStream_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系統)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容