Linux上有符号的16位ALSA PCM数据到U8的转换

Signed 16-bit ALSA PCM data to U8 Conversion on Linux

本文关键字:数据 U8 转换 PCM 16位 符号 Linux ALSA      更新时间:2023-10-16

我正试图将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 occurredn");
                    snd_pcm_prepare(m_PlaybackHandle);
                } else if (rc < 0) {
                    fprintf(stderr,
                            "Error from read: %sn",
                            snd_strerror(rc));
                } else if (rc != (int)frames) {
                    fprintf(stderr, "Short read, read %d framesn", 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);
        }

(qint16)(sample[0]) >> 8将有符号线性16位PCM转换为有符号线性8位PCM。如果您想要无符号线性8位,那么它将是((quint16)sample[0] ^ 0x8000) >> 8

尽管16位PCM几乎总是在线性标度上,但8位PCM更常见的是在对数标度上(µ-律或a-律),并且通常使用查找表进行转换。如果你真的想要线性8位,那么你可能需要首先调整增益,使峰值在0 dBFS,并使用音频压缩来减少动态范围,使其适合8位。

如果使用plughw:1,0而不是hw:1,0,您只需告诉设备您想要SND_PCM_FORMAT_U8,样本就会自动转换。(这也适用于µ-定律和A-定律。)