socket TCP 从0实现音频传输 ALSA 播放

Posted ningci

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了socket TCP 从0实现音频传输 ALSA 播放相关的知识,希望对你有一定的参考价值。

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 
", 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 
", 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 播放的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Raspberry Pi 上使用 C++ 将接收到的 UDP 音频数据正确写入 ALSA

[Linux_音频]_0_0_使用alsa的API,设置和获得声音

从 ALSA Raspberry Pi 获取音频幅度

ALSA音频工具amixer,aplay,arecord

TCP是如何实现可靠传输的?

Android:使用 AudioTrack 和 Socket 手动有效地流式传输音频