1. 程式人生 > >socket TCP 從0實現音頻傳輸 ALSA 播放

socket TCP 從0實現音頻傳輸 ALSA 播放

隱藏 loopback 音頻 休眠 發送 break 實現 wire client

RTP標準是采用 UDP 發送,有不少現成的開源庫,但不在本文討論的範圍內。
UDP 用戶數據報,不提供流程,安全傳輸的功能,但速度快,能提供多播,廣播,沒有序列號 SEQ ,有 MTU 限制,1500。
TCP 傳輸控制協議,提供流控,SEQ ,重傳功能,沒有數據長度限制,可以發幾 M 。

但在使用中還是有很多地方需要註意,否則聲音不好聽,斷斷續續或是延時嚴重。

雖然 TCP 在使用上沒有 MTU 限制,但是在真實的2個PC 之音使用 TCP 發送數據,也是被切片的,每次包不能超過 1448 字節,本機對本機,數據包沒有 MTU 限制,因為走的是 LOOPBACK ,裝個 wireshark 一看就懂了。

為什麽是 1448 ,MTU 是1500 減去 包頭 20 TCP 頭 20 時間戳 12 。

ALSA 的播放有2種打開模式,阻塞 非阻塞 snd_pcm_nonblock(playback_handle, 1);

發送接收流控問題,TCP 發送的數據如果大於接收的速度,就會被緩存起來,一直到接收完成以後。

下面只貼出關鍵代碼,來看這一個問題。
send.c 通過 讀取本地的一個 wav 的文件,直接讀取 一塊內容直接發送到本地端口。
這裏隱藏了,2個問題
1,用 TCP 來發送,RTP是使用 UDP 發送,而 UDP 不提供 SEQ ,也就是說,接收到的數據順序會亂,所以,RTP 在使用 UDP 承載時都會自己加一個頭信息
自行維護,SEQ ,時間戳,數據校驗
2,UDP 是一發就很快返回,不需要等待接收方的 ACK ,所以用 TCP 發送會慢一點。

play.c

 1 struct sockaddr_in client_addr;
 2 socklen_t client_addr_len;
 3 client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
 4 
 5 if(0 > client_fd)
 6 {
 7     printf("client connect error");
 8     continue;
 9 }
10 printf("client connect addr:%s \n", inet_ntoa(client_addr.sin_addr));
11 while(1) 12 { 13 int read_len; 14 int ret; 15 unsigned char buffer[4096]; 16 read_len = recv(client_fd, buffer, 4096, 0); 17 if(0 >= read_len) break; 18 ret = snd_pcm_writei(playback_handle, buffer, read_len/4); 19 if(-EPIPE == ret) 20 { 21 snd_pcm_prepare(playback_handle); 22 } 23 printf("read_len:%d \n", read_len); 24 } 25 close(client_fd);

send.c

1 audio_p = audio_buf;
2 send_size = 4096;
3 while((audio_p - audio_buf) <= stat.st_size)
4 {
5     if(-1 != client_fd) send(client_fd, audio_p, send_size, 0);
6     audio_p += send_size;
7 }

以上2個程序,可以運行,也可以播放WAV ,存在的問題是,send 一下子把所有數據都發過去了,當按 ctrl+c 退出時,play 還有數據,還能播放一段時間。

理想的情況是,就像打電話一樣,掛斷後馬上停止播放。

send 中是直接發送的 WAV 中的 PCM 數據,所以要添加合適的 delay 來模擬真實的音頻直播。

delay 時長的計算:4096*1000000/(44100*16*2/8)=23219 ,每次發送的長度是 4096 ,采樣率44k 2通道 16位,算出來,休眠時長是 23219us 。

問題僅僅是這樣嗎?天真。

如果直接在 while 發送 數據包時直接添加 usleep(23219) ,就會發現,播放出來非常難聽,這是因為 TCP 發送還是需要時間的,這也是RTP 用 UDP 的原因,UDP 發送非常快。

所以,這裏需要另開一個線程,專門做 發送 TCP 數據包的功能。

同理,play 的裏面也需要把 TCP 接收的單獨放到線程中。

原理講明白了,代碼就不貼了。

socket TCP 從0實現音頻傳輸 ALSA 播放