使用 C 创建立体声 WAV 文件

Posted

技术标签:

【中文标题】使用 C 创建立体声 WAV 文件【英文标题】:Creating a stereo WAV file using C 【发布时间】:2014-04-12 13:24:30 【问题描述】:

我正在考虑在 C 中创建一个 WAV file,并看到了一个示例 here。

这看起来不错,但我有兴趣添加两个缓冲区以制作音频立体声(每只耳朵都有不同声音的可能性)。如果我将声道数设置为两个,则音频仅从左声道播放(显然是右声道,因为左声道是第一个声道)。我读过我必须将它与正确的频道交错。

很遗憾,我没有在网上找到很多可以帮助创建立体声 WAV 的内容。

write_little_endian((unsigned int)(data[i]),bytes_per_sample, wav_file); 

我尝试创建第二个缓冲区,幅度减半,看看是否可以交错。

for (j=0; i<BUF_SIZE; i++) 
    phase +=freq_radians_per_sample;
    buffertwo[i] = (int)((amplitude/2) * sin(phase));;
    

write_wav("test.wav", BUF_SIZE, buffer, buffertwo, S_RATE);

(将函数更改为采用两个短整数缓冲区)

只是在做

    write_little_endian((unsigned int)(data[i]),bytes_per_sample, wav_file);
    write_little_endian((unsigned int)(datatwo[i]),bytes_per_sample, wav_file);

但这不起作用。理论上应该是交错的。

【问题讨论】:

您是在 Arduino 上执行此操作,还是仅在您自己的笔记本电脑或台式机上执行此操作? 我现在在笔记本电脑上执行此操作,计划在小型 Linux 设备上运行它。谢谢 您是否分配了变量num_channels = 2;?您在编写 wav 文件的标题时需要它,以便它知道每帧有两个通道。 是的,我做了,我也重置了 phase=0;在调用第二个迭代器之前。 MediaInfo 确认我现在正在制作一个 2 通道音频文件,并且文件大小与比特率相匹配,所以我知道两个流都被复制进来了。 我知道我不需要将幅度减半来做立体声,这更多是为了看看左右是否产生不同的音调,我会尝试不使用 write_little_endian 并告诉你。 【参考方案1】:

所以我决定试一试,这里是另一种编写 .wav 文件的方法。它会生成一个名为sawtooth_test.wav 的文件。播放时,您应该听到左右两个不同的频率。 (不要放得太大声。它很烦人。)

/*Compiles with gcc -Wall -O2 -o wavwrite wavwrite.c*/

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <limits.h>

/*
The header of a wav file Based on:
https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
*/
typedef struct wavfile_header_s

    char    ChunkID[4];     /*  4   */
    int32_t ChunkSize;      /*  4   */
    char    Format[4];      /*  4   */
    
    char    Subchunk1ID[4]; /*  4   */
    int32_t Subchunk1Size;  /*  4   */
    int16_t AudioFormat;    /*  2   */
    int16_t NumChannels;    /*  2   */
    int32_t SampleRate;     /*  4   */
    int32_t ByteRate;       /*  4   */
    int16_t BlockAlign;     /*  2   */
    int16_t BitsPerSample;  /*  2   */
    
    char    Subchunk2ID[4];
    int32_t Subchunk2Size;
 wavfile_header_t;

/*Standard values for CD-quality audio*/
#define SUBCHUNK1SIZE   (16)
#define AUDIO_FORMAT    (1) /*For PCM*/
#define NUM_CHANNELS    (2)
#define SAMPLE_RATE     (44100)

#define BITS_PER_SAMPLE (16)

#define BYTE_RATE       (SAMPLE_RATE * NUM_CHANNELS * BITS_PER_SAMPLE / 8)
#define BLOCK_ALIGN     (NUM_CHANNELS * BITS_PER_SAMPLE / 8)

/*Return 0 on success and -1 on failure*/
int write_PCM16_stereo_header(FILE *file_p,
                                int32_t SampleRate,
                                int32_t FrameCount)

    int ret;
    
    wavfile_header_t wav_header;
    int32_t subchunk2_size;
    int32_t chunk_size;
    
    size_t write_count;
    
    subchunk2_size  = FrameCount * NUM_CHANNELS * BITS_PER_SAMPLE / 8;
    chunk_size      = 4 + (8 + SUBCHUNK1SIZE) + (8 + subchunk2_size);
    
    wav_header.ChunkID[0] = 'R';
    wav_header.ChunkID[1] = 'I';
    wav_header.ChunkID[2] = 'F';
    wav_header.ChunkID[3] = 'F';
    
    wav_header.ChunkSize = chunk_size;
    
    wav_header.Format[0] = 'W';
    wav_header.Format[1] = 'A';
    wav_header.Format[2] = 'V';
    wav_header.Format[3] = 'E';
    
    wav_header.Subchunk1ID[0] = 'f';
    wav_header.Subchunk1ID[1] = 'm';
    wav_header.Subchunk1ID[2] = 't';
    wav_header.Subchunk1ID[3] = ' ';
    
    wav_header.Subchunk1Size = SUBCHUNK1SIZE;
    wav_header.AudioFormat = AUDIO_FORMAT;
    wav_header.NumChannels = NUM_CHANNELS;
    wav_header.SampleRate = SampleRate;
    wav_header.ByteRate = BYTE_RATE;
    wav_header.BlockAlign = BLOCK_ALIGN;
    wav_header.BitsPerSample = BITS_PER_SAMPLE;
    
    wav_header.Subchunk2ID[0] = 'd';
    wav_header.Subchunk2ID[1] = 'a';
    wav_header.Subchunk2ID[2] = 't';
    wav_header.Subchunk2ID[3] = 'a';
    wav_header.Subchunk2Size = subchunk2_size;
    
    write_count = fwrite(   &wav_header, 
                            sizeof(wavfile_header_t), 1,
                            file_p);
                    
    ret = (1 != write_count)? -1 : 0;
    
    return ret;


/*Data structure to hold a single frame with two channels*/
typedef struct PCM16_stereo_s

    int16_t left;
    int16_t right;
 PCM16_stereo_t;

PCM16_stereo_t *allocate_PCM16_stereo_buffer(int32_t FrameCount)

    return (PCM16_stereo_t *)malloc(sizeof(PCM16_stereo_t) * FrameCount);



/*Return the number of audio frames sucessfully written*/
size_t  write_PCM16wav_data(FILE*           file_p,
                            int32_t         FrameCount,
                            PCM16_stereo_t  *buffer_p)

    size_t ret;
    
    ret = fwrite(   buffer_p, 
                    sizeof(PCM16_stereo_t), FrameCount,
                    file_p);
                    
    return ret;


/*Generate two saw-tooth signals at two frequencies and amplitudes*/
int generate_dual_sawtooth( double frequency1,
                            double amplitude1,
                            double frequency2,
                            double amplitude2,
                            int32_t SampleRate,
                            int32_t FrameCount,
                            PCM16_stereo_t  *buffer_p)

    int ret = 0;
    double SampleRate_d = (double)SampleRate;
    double SamplePeriod = 1.0 / SampleRate_d;
    
    double Period1, Period2;
    double phase1, phase2;
    double Slope1, Slope2;
    
    int32_t k;
    
    /*Check for the violation of the Nyquist limit*/
    if( (frequency1*2 >= SampleRate_d) || (frequency2*2 >= SampleRate_d) )
    
        ret = -1;
        goto error0;
    
    
    /*Compute the period*/
    Period1 = 1.0 / frequency1;
    Period2 = 1.0 / frequency2;
    
    /*Compute the slope*/
    Slope1  = amplitude1 / Period1;
    Slope2  = amplitude2 / Period2;
    
    for(k = 0, phase1 = 0.0, phase2 = 0.0; 
        k < FrameCount; 
        k++)
    
        phase1 += SamplePeriod;
        phase1 = (phase1 > Period1)? (phase1 - Period1) : phase1;
        
        phase2 += SamplePeriod;
        phase2 = (phase2 > Period2)? (phase2 - Period2) : phase2;
        
        buffer_p[k].left    = (int16_t)(phase1 * Slope1);
        buffer_p[k].right   = (int16_t)(phase2 * Slope2);
    
    
error0:
    return ret;


int main(void)

    int ret;
    FILE* file_p;

    double frequency1 = 493.9; /*B4*/
    double amplitude1 = 0.65 * (double)SHRT_MAX;
    double frequency2 = 392.0; /*G4*/
    double amplitude2 = 0.75 * (double)SHRT_MAX;
    
    double duration = 10; /*seconds*/
    int32_t FrameCount = duration * SAMPLE_RATE;
    
    PCM16_stereo_t  *buffer_p = NULL;
    
    size_t written;
    
    /*Open the wav file*/
    file_p = fopen("./sawtooth_test.wav", "wb");
    if(NULL == file_p)
    
        perror("fopen failed in main");
        ret = -1;
        goto error0;
    
    
    /*Allocate the data buffer*/
    buffer_p = allocate_PCM16_stereo_buffer(FrameCount);
    if(NULL == buffer_p)
    
        perror("fopen failed in main");
        ret = -1;
        goto error1;        
    

    /*Fill the buffer*/
    ret = generate_dual_sawtooth(   frequency1,
                                    amplitude1,
                                    frequency2,
                                    amplitude2,
                                    SAMPLE_RATE,
                                    FrameCount,
                                    buffer_p);
    if(ret < 0)
    
        fprintf(stderr, "generate_dual_sawtooth failed in main\n");
        ret = -1;
        goto error2;
    
    
    /*Write the wav file header*/
    ret = write_PCM16_stereo_header(file_p,
                                    SAMPLE_RATE,
                                    FrameCount);
    if(ret < 0)
    
        perror("write_PCM16_stereo_header failed in main");
        ret = -1;
        goto error2;
    
    
    /*Write the data out to file*/
    written = write_PCM16wav_data(  file_p,
                                    FrameCount,
                                    buffer_p);
    if(written < FrameCount)
    
        perror("write_PCM16wav_data failed in main");
        ret = -1;
        goto error2;
    

    /*Free and close everything*/    
error2:
    free(buffer_p);
error1:
    fclose(file_p);
error0:
    return ret;    

【讨论】:

嗨,这是一段很好的代码。但是当我生成波形文件并听到它时,我发现了两个问题。它很吵,我听不到大三度(G4 到 B4)。当我检查二进制文件时,我发现 0x0A 被 0x0D、0x0A 替换。因此,解决方案是使用选项“wb”打开文件以打开二进制文件进行写入。也许你想纠正这个。另一个改进是减去幅度/2 的直流偏移。小心!迈克尔 谢谢你,@MiCo 和 Evil。好建议。刚刚接受了修改。 没问题。我找到了你的答案,试图解决我自己的问题:***.com/questions/68625243/… 但无法将 plst、slnt 块放在一起。【参考方案2】:

我认为问题在于函数“write_little_endian”。您不需要在笔记本电脑上使用它。

字节顺序是特定于架构的。最初的示例可能是针对 Arduino 微控制器板的。 Arduino 板使用大端的 Atmel 微控制器。这就是为什么您明确引用的代码需要将 16 位整数转换为 little-endian 格式的原因。

另一方面,您的笔记本电脑使用 x86 处理器,这些处理器已经是小端,因此无需转换。如果你想要健壮的可移植代码来转换字节顺序,你可以在 Linux 中使用函数htole16。查找手册页以了解有关此功能的更多信息。

为了快速但不便携的修复,我想说写出整个 16 位值。

另外,我认为从单声道到立体声不需要将幅度减半。

【讨论】:

以上是关于使用 C 创建立体声 WAV 文件的主要内容,如果未能解决你的问题,请参考以下文章

写入立体声后 WAV 写入功能无法同步

在 Python 中用 24 位数据从立体声 wav 文件 wav 中读取单个通道的数据

如何在 Python 中将 WAV 从立体声转换为单声道?

解交织 PCM (*.wav) 立体声音频数据

使用音频工具 sox,我如何确定立体声录音是不是实际上是单声道?

将wav文件转换为单声道