我正在考虑在C中创建WAV file
并看到一个示例here。
这看起来不错,但我有兴趣添加两个缓冲区来使音频立体声(每只耳朵可能有不同的声音)。如果我将通道数设置为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);
但这不起作用。理论上这应该是交错的。
答案 0 :(得分:7)
所以我决定试一试,这是写一个.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", "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;
}
答案 1 :(得分:0)
我认为问题在于&#34; write_little_endian&#34;。您不应该在笔记本电脑上使用它。
Endianness是特定于体系结构的。最初的例子可能是Arduino微控制器板。 Arduino板使用大端的Atmel微控制器。这就是你引用的代码明确需要将16位整数转换为little-endian格式的原因。
另一方面,您的笔记本电脑使用的x86处理器已经是小端,因此无需转换。如果您想要强大的可移植代码来转换字节顺序,可以在Linux中使用函数htole16
。查看手册页以了解有关此功能的更多信息。
对于快速但不可移植的修复,我会说只写出整个16位值。
另外,我认为你不需要将振幅减半,从单声道变为立体声。