C 中的音频录制和播放:音频增益问题

Audio Recording and Playback in C : problem with audio gain

问题本质上是如何正确地将增益应用于音频样本?

我在 FreeBSD 和 OSS 上编程,但在音频示例中控制音量对于其他 OS 和应用程序可能是相同的。

我正在研究其他人的应用程序内部结构,例如 ecasound (in C++) and SoX(在 C 中),但是当我阅读样本并对其应用增益时,我不知道出了什么问题:它变得失真和嘈杂。 我的意思是理解为什么调低音量不起作用(增益小于 1)。

我正在处理立体声 16 位 LE 样本。不应用增益,它完美地工作(录音和播放)。

我认为我应该将整数样本转换为浮点数;乘以增益因子并将其恢复为整数。但它不起作用。它似乎与函数 static int flow.

SoX in src/vol.c 的方法完全相同

下面是我的代码(没有使用额外的库)。函数回放是我应用增益的地方。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include "/usr/include/sys/soundcard.h"
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/stat.h> //man 2 chmod
#include <signal.h>

#define DEBUG 1
#define log(msg) if (DEBUG) printf("[LOG] %s\n",msg)
#define err(msg)  {printf("[ERR] %s\n",msg); exit(1); }


const char *device = "/dev/dsp3.1";     //Audio device
char *rawFile = "/tmp/raw-file.wav";    //Raw file to record and playback
int fragmentSize = 256;
int b_continue = 1;


void signalHandler(int sigNum){
    log("Signal captured");
    b_continue = 0;
}


void configDevice(int fdDsp){
    int ossCapabilities = 0;

    if(fdDsp == -1)
        err("can't open device");

    if( ioctl(fdDsp, SNDCTL_DSP_GETCAPS, &ossCapabilities) == -1)
        err("unsupported: SNDCTL_DSP_GETCAPS");

    /*
     * http://www.opensound.com/pguide/audio2.html
     */

    if(ossCapabilities & DSP_CAP_TRIGGER != DSP_CAP_TRIGGER){
        err("Triggering of recording/playback is not possible with this OSS device.");

    }

    if(ossCapabilities & DSP_CAP_REALTIME != DSP_CAP_REALTIME){
        err("No DSP_CAP_REALTIME.");

    }

    if(ioctl(fdDsp, SNDCTL_DSP_SETDUPLEX, &ossCapabilities) == -1)
        err("can't SNDCTL_DSP_SETDUPLEX");

    if(ossCapabilities & DSP_CAP_DUPLEX != DSP_CAP_DUPLEX)
        err("can't DSP_CAP_DUPLEX");

    int format = AFMT_S16_LE;   //set format
    if(ioctl(fdDsp, SNDCTL_DSP_SETFMT, &format ) == -1){
        err("Error setting format.");

    }

    int channels = 1; //mono=0 stereo=1
    if(ioctl(fdDsp, SNDCTL_DSP_STEREO, &channels ) == -1){
        err("Error setting channels." );

    }
    // FREQUENCY RATE
    int speed = 44100;
    if(ioctl(fdDsp, SNDCTL_DSP_SPEED, &speed ) == -1){
        err("Error setting speed.");

    }

    // FRAGMENT SIZE
    if(ioctl(fdDsp, SNDCTL_DSP_SETBLKSIZE, &fragmentSize) == -1){ //normalmente 2048 bits
        err("Cannot SNDCTL_DSP_SETBLKSIZE.");

    }


}

void record(){
    int fdDsp = open(device, O_RDONLY);
    configDevice(fdDsp);
    //create file for writing
    const int fdOutput = open(rawFile, O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR);

    if(fdOutput ==-1)
        err("can't open file to write");
    log("Recording...");

    do{
         // Triggers recording
         int enableBits = PCM_ENABLE_INPUT;
         if(ioctl(fdDsp, SNDCTL_DSP_SETTRIGGER, &enableBits) == -1)
             err("Can't record: SNDCTL_DSP_SETTRIGGER");

         int *buf[fragmentSize];
         read(fdDsp, buf, fragmentSize);
         write(fdOutput, buf, fragmentSize);

     } while(b_continue == 1);

     close(fdOutput);
     close(fdDsp);
}

void playback(){
    log("Opening file:");
    log(rawFile);
    log("On device:");
    log(device);

    int fdDsp = open(device, O_WRONLY);
    configDevice(fdDsp);

    const int fdInput = open(rawFile, O_RDONLY);

    if(fdInput ==-1)
        err("can't open file");
    log("Playing...");

    int eof = 0;

    do{
        // TRIGGERs PLAYBACK
        int enableBits = PCM_ENABLE_OUTPUT;
        if(ioctl(fdDsp, SNDCTL_DSP_SETTRIGGER, &enableBits) == -1){
            err("Cannot SNDCTL_DSP_SETTRIGGER.");

        }

        int buf[fragmentSize];
        eof = read(fdInput, buf, fragmentSize); //bytes read or -1 if EOF

        // audio processing:
        for(int i=0;i<fragmentSize;i++){
            // learning how to get left and right channels from buffer
            int l = (buf)[i] & 0xffff;
            int r = ((buf)[i] >> 16)  & 0xffff ;

            // FIXME: it is causing distortion:
            float fl = l;
            float fr = r;
            fl *= 1.0;
            fr *= 0.3; //if different than 1, sounds distorted and noisy
            l = fl;
            r = fr;


            // OK: unite Left and Right channels again
            int lr = (l ) | (r << 16);
            // OK: other options to mix these two channels:
            int lleft = l;              //Just the left channel
            int rright = (r << 16);     //Just the right channel
            int lmono = (l << 16) | l;  //Left ch. on both channels
            int rmono = (r << 16) | r;  //Right ch. on both channels

            // the output:
            (buf)[i] = lr;

        }

        write(fdDsp, buf, fragmentSize);
        if(b_continue == 0) break;
    } while(eof > 0);

    close(fdInput);
    close(fdDsp);
}

int main(int argc, char *argv[])
{

    signal(SIGINT, signalHandler);
    log("Ctrl^C to stop recording/playback");
    record();
    b_continue = 1; playback();
    log("Stopped.");
    return 0;
}

更新:

正如CL指出的那样,我使用了错误的类型,read()/write() 的最后一个参数大于缓冲区的大小。

因此,在 FreeBSD 中,我将缓冲区类型更改为 int16_t(短),定义在 #include <stdint.h> 中。

现在我可以根据需要正确应用增益:

        float fl = l;
        float fr = r;
        fl *= 1.0f;
        fr *= 1.5f;
        l = fl;
        r = fr;

我会接受 CL's 回答。

现在音频处理循环每次处理一个样本(左右交错)。

更新代码:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include "/usr/include/sys/soundcard.h"
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/stat.h> //man 2 chmod
#include <signal.h>
#include <stdint.h> //has type int16_t (short)

#define DEBUG 1
#define log(msg) if (DEBUG) printf("[LOG] %s\n",msg)
#define err(msg)  {printf("[ERR] %s\n",msg); exit(1); }


const char *device = "/dev/dsp3.1";     //Audio device
char *rawFile = "/tmp/stereo.wav";    //Raw file to record and playback
int fragmentSize = 256;
int b_continue = 1;


void signalHandler(int sigNum){
    log("Signal captured");
    b_continue = 0;
}


void configDevice(int fdDsp){
    int ossCapabilities = 0;

    if(fdDsp == -1)
        err("can't open device");

    if( ioctl(fdDsp, SNDCTL_DSP_GETCAPS, &ossCapabilities) == -1)
        err("unsupported: SNDCTL_DSP_GETCAPS");

    /*
     * http://www.opensound.com/pguide/audio2.html
     */

    if(ossCapabilities & DSP_CAP_TRIGGER != DSP_CAP_TRIGGER){
        err("Triggering of recording/playback is not possible with this OSS device.");

    }

    if(ossCapabilities & DSP_CAP_REALTIME != DSP_CAP_REALTIME){
        err("No DSP_CAP_REALTIME.");

    }

    if(ioctl(fdDsp, SNDCTL_DSP_SETDUPLEX, &ossCapabilities) == -1)
        err("can't SNDCTL_DSP_SETDUPLEX");

    if(ossCapabilities & DSP_CAP_DUPLEX != DSP_CAP_DUPLEX)
        err("can't DSP_CAP_DUPLEX");

    int format = AFMT_S16_LE;   //set format
    if(ioctl(fdDsp, SNDCTL_DSP_SETFMT, &format ) == -1){
        err("Error setting format.");

    }

    int channels = 1; //mono=0 stereo=1
    if(ioctl(fdDsp, SNDCTL_DSP_STEREO, &channels ) == -1){
        err("Error setting channels." );

    }
    // FREQUENCY RATE
    int speed = 44100;
    if(ioctl(fdDsp, SNDCTL_DSP_SPEED, &speed ) == -1){
        err("Error setting speed.");

    }

    // FRAGMENT SIZE
    if(ioctl(fdDsp, SNDCTL_DSP_SETBLKSIZE, &fragmentSize) == -1){ //normalmente 2048 bits
        err("Cannot SNDCTL_DSP_SETBLKSIZE.");

    }


}

void record(){
    int fdDsp = open(device, O_RDONLY);
    configDevice(fdDsp);
    //create file for writing
    const int fdOutput = open(rawFile, O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR);

    if(fdOutput ==-1)
        err("can't open file to write");
    log("Recording...");

    do{
         // Triggers recording
         int enableBits = PCM_ENABLE_INPUT;
         if(ioctl(fdDsp, SNDCTL_DSP_SETTRIGGER, &enableBits) == -1)
             err("Can't record: SNDCTL_DSP_SETTRIGGER");

         // Wrong:
//         int *buf[fragmentSize];
//         read(fdDsp, buf, fragmentSize);
//         write(fdOutput, buf, fragmentSize);
         int16_t *buf[fragmentSize/sizeof (int16_t)];
         read(fdDsp, buf, fragmentSize/sizeof (int16_t));
         write(fdOutput, buf, fragmentSize/sizeof (int16_t));

     } while(b_continue == 1);

     close(fdOutput);
     close(fdDsp);
}

void playback(){
    log("Opening file:");
    log(rawFile);
    log("On device:");
    log(device);

    int fdDsp = open(device, O_WRONLY);
    configDevice(fdDsp);

    const int fdInput = open(rawFile, O_RDONLY);

    if(fdInput ==-1)
        err("can't open file");
    log("Playing...");

    int eof = 0;

    do{
        // TRIGGERs PLAYBACK
        int enableBits = PCM_ENABLE_OUTPUT;
        if(ioctl(fdDsp, SNDCTL_DSP_SETTRIGGER, &enableBits) == -1){
            err("Cannot SNDCTL_DSP_SETTRIGGER.");

        }

        //Wrong buffer type (too large) and wrong last parameter for read():
//        int buf[fragmentSize];
//        eof = read(fdInput, buf, fragmentSize);
        int16_t buf[fragmentSize/sizeof (int16_t)];
        eof = read(fdInput, buf, fragmentSize/sizeof (int16_t));

        // audio processing:
        for(int i=0;i<fragmentSize/sizeof (int16_t);i++){
            int16_t l = buf[i];
            int16_t r = buf[i+1];

            // Using int16_t (short) buffer, gain works but stereo is inverted with factor >= 1.4f
            float fl = l;
            float fr = r;
            fl *= 2.0f;
            fr *= 3.0f;
            l = fl;
            r = fr;

            // the output:
            (buf)[i] = l;
            i++;
            (buf)[i] = r;

        }

//        write(fdDsp, buf, fragmentSize); //wrong
        write(fdDsp, buf, fragmentSize/sizeof (int16_t));
        if(b_continue == 0) break;
    } while(eof > 0);

    close(fdInput);
    close(fdDsp);
}

int main(int argc, char *argv[])
{

    signal(SIGINT, signalHandler);
    log("Ctrl^C to stop recording/playback");
    record();
    b_continue = 1; playback();
    log("Stopped.");
    return 0;
}

谢谢,

read()/write()的最后一个参数是字节数,但是buf[]中的一个条目多了一个字节

在二进制数的补码表示中,对负值进行(或必须)符号扩展,即最高位为1。在此代码中,提取 L/R 个通道和组合它们都不能正确处理负样本。

处理负样本的最简单方法是每个样本使用一个数组条目,即 short int.