Linux 上带符号的 16 位 ALSA PCM 数据到 U8 的转换
Posted
技术标签:
【中文标题】Linux 上带符号的 16 位 ALSA PCM 数据到 U8 的转换【英文标题】:Signed 16-bit ALSA PCM data to U8 Conversion on Linux 【发布时间】:2015-04-16 11:59:03 【问题描述】:我正在尝试将 16 位 ALSA PCM 样本转换为无符号 8 位 PCM 样本,以便在 Linux 上进行无线传输。接收机器正在成功播放传输的数据,并且录制的语音在那里并且可以识别,但是质量很糟糕并且很嘈杂。我已经在两端尝试了 ALSA 混音器来调整流,但它似乎并没有变得更好。我相信我将样本转换为 8 位 PCM 有问题,但这只是一个简单的转换,所以我不确定可能是什么错误。有人对我的转换代码有任何建议或发现有什么问题吗?谢谢。
转换代码:
// This byte array needs to be the packet size we wish to send
QByteArray prepareToSend;
prepareToSend.clear();
// Keep reading from ALSA until we fill one full frame
int frames = 1;
while ( prepareToSend.size() < TARGET_TX_BUFFER_SIZE )
// Create a ByteArray
QByteArray readBytes;
readBytes.resize(size);
// Read with ALSA
short sample[1]; // Data is signed 16-bit
int rc = snd_pcm_readi(m_PlaybackHandle, sample, frames);
if (rc == -EPIPE)
/* EPIPE means overrun */
fprintf(stderr, "Overrun occurred\n");
snd_pcm_prepare(m_PlaybackHandle);
else if (rc < 0)
fprintf(stderr,
"Error from read: %s\n",
snd_strerror(rc));
else if (rc != (int)frames)
fprintf(stderr, "Short read, read %d frames\n", rc);
else
// Copy bytes to the prepare to send buffer
//qDebug() << "Bytes for sample buffer: " << sizeof(sample);
prepareToSend.append((qint16)(sample[0]) >> 8); // signed 16-bit becomes u8
ALSA 配置:
// Setup parameters
int size;
snd_pcm_t *m_PlaybackHandle;
snd_pcm_hw_params_t *m_HwParams;
char *buffer;
qDebug() << "Desire to Transmit Data - Setting up ALSA Now....";
// Error handling
int err;
// Device to Write to
const char *snd_device_in = "hw:1,0";
if ((err = snd_pcm_open (&m_PlaybackHandle, snd_device_in, SND_PCM_STREAM_CAPTURE, 0)) < 0)
fprintf (stderr, "Cannot open audio device %s (%s)\n",
snd_device_in,
snd_strerror (err));
exit (1);
/* Allocate a hardware parameters object. */
snd_pcm_hw_params_alloca(&m_HwParams);
if ((err = snd_pcm_hw_params_malloc (&m_HwParams)) < 0)
fprintf (stderr, "Cannot allocate hardware parameter structure (%s)\n",
snd_strerror (err));
exit (1);
if ((err = snd_pcm_hw_params_any (m_PlaybackHandle, m_HwParams)) < 0)
fprintf (stderr, "Cannot initialize hardware parameter structure (%s)\n",
snd_strerror (err));
exit (1);
if ((err = snd_pcm_hw_params_set_access (m_PlaybackHandle, m_HwParams, 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(m_PlaybackHandle, m_HwParams, SND_PCM_FORMAT_S16)) < 0) // Has to be 16 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 (m_PlaybackHandle, m_HwParams, 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 (m_PlaybackHandle, m_HwParams, 1)) < 0) // 1 Channel Mono
fprintf (stderr, "Cannot set channel count (%s)\n",
snd_strerror (err));
exit (1);
/*
Frames: samples x channels (i.e: stereo frames are composed of two samples, mono frames are composed of 1 sample,...)
Period: Number of samples tranferred after which the device acknowledges the transfer to the apllication (usually via an interrupt).
*/
/* Submit params to device */
if ((err = snd_pcm_hw_params(m_PlaybackHandle, m_HwParams)) < 0)
fprintf (stderr, "Cannot set parameters (%s)\n",
snd_strerror (err));
exit (1);
/* Free the Struct */
snd_pcm_hw_params_free(m_HwParams);
// Flush handle prepare for record
snd_pcm_drop(m_PlaybackHandle);
if ((err = snd_pcm_prepare (m_PlaybackHandle)) < 0)
fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
snd_strerror (err));
exit (1);
qDebug() << "Done Setting up ALSA....";
// Prepare the device
if ((err = snd_pcm_prepare (m_PlaybackHandle)) < 0)
fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
snd_strerror (err));
exit (1);
【问题讨论】:
8000 采样率听起来非常低...您确定您没有听到该采样率的确切含义吗? 是的,我在发送端用相同的帧速率使用 PulseAudio 进行了同样的尝试,听起来非常清晰。一点也不吵。我还在 PulseAudio 配置中关闭了重采样。这是在小型嵌入式 Linux 设备上运行的,因此 PulseAudio 在获取数据时导致了很长(10 秒)的延迟。我确定它正在过滤,但没有重新混合。 数据向右移动 8 位将消除几乎所有的声音细节。建议使用某种压缩算法将 16 位解析为 8 位。或许通过从 16 位中提取成对的位,使用多数规则,然后使用高位规则将数据压缩为 8 位。 【参考方案1】:(qint16)(sample[0]) >> 8
将有符号线性 16 位 PCM 转换为有符号线性 8 位 PCM。如果您想要无符号线性 8 位,那么它将是 ((quint16)sample[0] ^ 0x8000) >> 8
。
虽然 16 位 PCM 几乎总是采用线性标度,但 8 位 PCM 更常见的是对数标度(μ-law 或 A-law),并且通常使用查找表进行转换。如果您确实想要线性 8 位,那么您可能需要首先调整增益,使峰值为 0 dBFS,然后使用音频压缩来减小动态范围,使其适合 8 位。
【讨论】:
【参考方案2】:如果您使用plughw:1,0
而不是hw:1,0
,您只需告诉设备您需要SND_PCM_FORMAT_U8
,样本就会自动转换。
(这也适用于 µ-Law 和 A-Law。)
【讨论】:
以上是关于Linux 上带符号的 16 位 ALSA PCM 数据到 U8 的转换的主要内容,如果未能解决你的问题,请参考以下文章
Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)
Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)