使用C创建立体声WAV文件

我正在寻找用C创建一个WAV file ,并在这里看到了一个例子。

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

不幸的是,我没有找到很多在线帮助创建立体声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); 

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

所以我决定给它一个有趣的镜头,这是另一种编写.wav文件的方法。 它生成一个名为sawtooth_test.wav的文件。 当你播放它时,你应该听到左右两个不同的频率。 (不要回放太大声。它太烦人了。)

 /*Compiles with gcc -Wall -O2 -o wavwrite wavwrite.c*/ #include  #include  #include  #include  /* 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", "w"); 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; } 

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

Endianness是特定于体系结构的。 最初的例子可能是Arduino微控制器板。 Arduino板使用大端的Atmel微控制器。 这就是你引用的代码明确需要将16位整数转换为little-endian格式的原因。

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

对于快速但不可移植的修复,我会说只写出整个16位值。

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