如何在 Raspberry Pi 上使用 C++ 将接收到的 UDP 音频数据正确写入 ALSA
Posted
技术标签:
【中文标题】如何在 Raspberry Pi 上使用 C++ 将接收到的 UDP 音频数据正确写入 ALSA【英文标题】:How to Properly Write Received UDP Audio Data to ALSA with C++ on Raspberry Pi 【发布时间】:2015-04-08 15:47:29 【问题描述】:我有 2 个 Raspberry Pi,其中 1 个将音频数据的 UDP 帧传输到另一个 Raspberry Pi。收到的 UDP 数据包每个是 160 字节。传输树莓派正在发送 8KHz 8 位单声道样本。接收树莓派使用带有 QUDPSocket 的 Qt 5.4.0 并尝试使用 ALSA 播放接收到的数据。代码如下。每次当字节到达接收树莓派时,“readyRead”信号被触发,缓冲区被写入 ALSA。我在接收 Pi 上的耳机插孔中发出非常不连贯和故障的声音 - 但它是可识别的。所以它正在工作,但听起来很糟糕。
-
我的以下 ALSA 配置有什么明显错误吗?
我应该如何使用 snd_pcm_writei 将接收到的 UDP 数据包写入 ALSA?
感谢您的任何建议。
UdpReceiver::UdpReceiver(QObject *parent) : QObject(parent)
// Debug
qDebug() << "Setting up a UDP Socket...";
// Create a socket
m_Socket = new QUdpSocket(this);
// Bind to the 2616 port
bool didBind = m_Socket->bind(QHostAddress::Any, 0x2616);
if ( !didBind )
qDebug() << "Error - could not bind to UDP Port!";
else
qDebug() << "Success binding to port 0x2616!";
// Get notified that data is incoming to the socket
connect(m_Socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
// Init to Zero
m_NumberUDPPacketsReceived = 0;
void UdpReceiver::readyRead()
// When data comes in
QByteArray buffer;
buffer.resize(m_Socket->pendingDatagramSize());
QHostAddress sender;
quint16 senderPort;
// Cap buffer size
int lenToRead = buffer.size();
if ( buffer.size() > NOMINAL_AUDIO_BUFFER_SIZE )
lenToRead = NOMINAL_AUDIO_BUFFER_SIZE;
// Read the data from the UDP Port
m_Socket->readDatagram(buffer.data(), lenToRead,
&sender, &senderPort);
// Kick off audio playback
if ( m_NumberUDPPacketsReceived == 0 )
qDebug() << "Received Data - Setting up ALSA Now....";
// Error handling
int err;
// Device to Write to
char *snd_device_out = "hw:0,0";
if ((err = snd_pcm_open (&playback_handle, snd_device_out, SND_PCM_STREAM_PLAYBACK, 0)) < 0)
fprintf (stderr, "cannot open audio device %s (%s)\n",
snd_device_out,
snd_strerror (err));
exit (1);
if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0)
fprintf (stderr, "cannot allocate hardware parameter structure (%s)\n",
snd_strerror (err));
exit (1);
if ((err = snd_pcm_hw_params_any (playback_handle, hw_params)) < 0)
fprintf (stderr, "cannot initialize hardware parameter structure (%s)\n",
snd_strerror (err));
exit (1);
if ((err = snd_pcm_hw_params_set_access (playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
fprintf (stderr, "cannot set access type (%s)\n",
snd_strerror (err));
exit (1);
if ((err = snd_pcm_hw_params_set_format (playback_handle, hw_params, SND_PCM_FORMAT_U8)) < 0) // Unsigned 8 bit
fprintf (stderr, "cannot set sample format (%s)\n",
snd_strerror (err));
exit (1);
uint sample_rate = 8000;
if ((err = snd_pcm_hw_params_set_rate (playback_handle, hw_params, sample_rate, 0)) < 0) // 8 KHz
fprintf (stderr, "cannot set sample rate (%s)\n",
snd_strerror (err));
exit (1);
if ((err = snd_pcm_hw_params_set_channels (playback_handle, hw_params, 1)) < 0) // 1 Channel Mono
fprintf (stderr, "cannot set channel count (%s)\n",
snd_strerror (err));
exit (1);
if ((err = snd_pcm_hw_params (playback_handle, hw_params)) < 0)
fprintf (stderr, "cannot set parameters (%s)\n",
snd_strerror (err));
exit (1);
snd_pcm_hw_params_free (hw_params);
// Flush handle prepare for playback
snd_pcm_drop(playback_handle);
if ((err = snd_pcm_prepare (playback_handle)) < 0)
fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
snd_strerror (err));
exit (1);
qDebug() << "Done Setting up ALSA....";
// Grab the buffer
m_Buffer = buffer.data();
// Write the data to the ALSA device
int error;
if ((error = snd_pcm_writei (playback_handle, m_Buffer, NOMINAL_AUDIO_BUFFER_SIZE)) != NOMINAL_AUDIO_BUFFER_SIZE)
fprintf (stderr, "write to audio interface failed (%s)\n",
snd_strerror (error));
exit (1);
// Count up
m_NumberUDPPacketsReceived++;
【问题讨论】:
在使用其他程序时可以播放更好的声音吗?某些(?)型号的插孔声音(但不是 HDMI)本身就很糟糕,与软件无关。 感谢您的意见。但如果我使用 ALSA 实用程序的“aplay”程序,那么音频输出听起来很棒。我很确定这是一个配置问题。 您仍然没有设置缓冲区/周期大小。 您应该在 SLOT 中处理传入的数据报一段时间,例如 here 以确保所有可用数据都使用当前信号处理。 设置您的音频缓冲区以收集 1 秒的完整时间,然后以 1 秒的缓冲区块播放。看看质量是否变得更好(应该)。将设计更改为有一个侦听线程读取数据并填充缓冲区,而另一个线程通过连续馈送数据以延迟播放它,例如 0.1 秒。您每秒有 8000 个样本。 5 条 UDP 消息意味着 800 个样本,即 0.1 秒。所以你可以从这个开始。通过在一次运行中输入 160 个样本,您一次可以播放 0.02 秒。然后你停止播放,并在同一个线程上阅读另一条 UDP 消息。 【参考方案1】:我不理解代码的“限制缓冲区大小”部分。如果传入数据大于您的任意 NOMINAL_AUDIO_BUFFER_SIZE,那么您将切断该传入数据。你试过删除那段代码吗?
【讨论】:
以上是关于如何在 Raspberry Pi 上使用 C++ 将接收到的 UDP 音频数据正确写入 ALSA的主要内容,如果未能解决你的问题,请参考以下文章
在 Raspberry PI 上使用 Python 和 OpenCV 进行图像处理 [关闭]
Opencv:无法打开显示:C++、Raspberry Pi 无头连接
Linux C++ 用户空间应用程序实时睡眠功能(POSIX、Raspberry Pi)
如何使用 Python 在 Raspberry Pi 上杀死 omxplayer 播放器