所有的WAV都有一个文件头,这个文件头记录着音频流的编码参数。数据块的记录方式是little-endian字节顺序。
WAV格式全称为WAVE,只需要在PCM文件的前面添加WAV文件头,就可以生成WAV格式文件。
WAVE文件格式是微软RIFF规范的一部分,用于存储多媒体文件。RIFF文件以文件头开始,后跟一系列数据块(Chunk)。WAVE文件通常只是具有单个“ WAVE”块的RIFF文件,该“ WAVE”块由两个子块组成-“ fmt”块指定数据格式,而“ data”块包含实际样本数据。将此表格称为“规范表格”。
例如,以下是WAVE文件的开头72个字节,其中的字节显示为十六进制数字:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <getopt.h>
#include <sys/signal.h>
#pragma pack(push)
#pragma pack(1) //1字节对齐
typedef struct {
char RIFFNAME[4];
unsigned int nRIFFSize;
char WAVNAME[4];
char FMTNAME[4];
unsigned int nFMTSize;
unsigned short nAudioFormat;
unsigned short nChannels;
unsigned int nSampleRate; /*采样频率*/
unsigned int nBytesPerSecond; /*每秒所需字节数*/
unsigned short nBlockAlign; /*数据块对齐单位,每个采样需要的字节数*/
unsigned short wBitsPerSample; /*每个采样需要的bit数*/
char DATANAME[4];
unsigned int nDataSize;
} WAVFLIEHEAD;
#pragma pack(pop) /* 恢复先前的pack设置 */
WAVFLIEHEAD FileHeader;
void wav_head_print(void)
{
printf("RIFFNAME:%c%c%c%c\n", FileHeader.RIFFNAME[0], FileHeader.RIFFNAME[1], FileHeader.RIFFNAME[2], FileHeader.RIFFNAME[3]);
printf("RIFFSize:%d\n", FileHeader.nRIFFSize);
printf("WAVNAME:%c%c%c%c\n", FileHeader.WAVNAME[0], FileHeader.WAVNAME[1], FileHeader.WAVNAME[2], FileHeader.WAVNAME[3]);
printf("FMTNAME:%c%c%c%c\n", FileHeader.FMTNAME[0], FileHeader.FMTNAME[1], FileHeader.FMTNAME[2], FileHeader.FMTNAME[3]);
printf("FMTSize:%d\n", FileHeader.nFMTSize);
printf("AudioFormat:%d\n", FileHeader.nAudioFormat);
printf("Channels:%d\n", FileHeader.nChannels);
printf("SampleRate:%d\n", FileHeader.nSampleRate);
printf("BytesPerSecond:%d\n", FileHeader.nBytesPerSecond);
printf("BlockAlign:%d\n", FileHeader.nBlockAlign);
printf("BitsPerSample:%d\n", FileHeader.wBitsPerSample);
printf("DATANAME:%c%c%c%c\n", FileHeader.DATANAME[0], FileHeader.DATANAME[1], FileHeader.DATANAME[2], FileHeader.DATANAME[3]);
printf("DataSize:%d\n", FileHeader.nDataSize);
}
int main(int argc, char const *argv[])
{
unsigned int rate = 48000;
unsigned short channels = 2, bits = 16;
int option_index, c;
static const char short_options[] = "hs:d:r:v:c:b:p:";
static const struct option long_options[] = {
{"help",no_argument,0,'h'},
{"source pcm file name",required_argument,0,'s'},
{"destination wav file name",required_argument,0,'d'},
{"rate",required_argument,0,'r'},
{"channels",required_argument,0,'r'},
{0, 0, 0, 0}
};
char file_name_wav[100]={0,};
char file_name_pcm[100]={0,};
while ((c = getopt_long(argc, argv, short_options, long_options, &option_index)) != -1) {
switch(c) {
case 'h':
printf("help\n");
return 0;
case 'd':
strcpy(file_name_wav,optarg);
break;
case 's':
strcpy(file_name_pcm,optarg);
break;
case 'r':
rate = strtol(optarg, NULL, 0);
break;
case 'c':
channels = strtol(optarg, NULL, 0);
break;
case 'b':
bits = strtol(optarg, NULL, 0);
break;
default:
printf("unsupport cmd,try -h for help\n");
return 1;
}
}
FileHeader.RIFFNAME[0] = 'R';
FileHeader.RIFFNAME[1] = 'I';
FileHeader.RIFFNAME[2] = 'F';
FileHeader.RIFFNAME[3] = 'F';
FileHeader.WAVNAME[0] = 'W';
FileHeader.WAVNAME[1] = 'A';
FileHeader.WAVNAME[2] = 'V';
FileHeader.WAVNAME[3] = 'E';
FileHeader.FMTNAME[0] = 'f';
FileHeader.FMTNAME[1] = 'm';
FileHeader.FMTNAME[2] = 't';
FileHeader.FMTNAME[3] = 0x20;
FileHeader.nFMTSize =16;//SND_PCM_FORMAT_S16_LE
FileHeader.nAudioFormat = 1;
FileHeader.DATANAME[0] = 'd';
FileHeader.DATANAME[1] = 'a';
FileHeader.DATANAME[2] = 't';
FileHeader.DATANAME[3] = 'a';
FileHeader.wBitsPerSample = bits;
FileHeader.nBlockAlign =2;
FileHeader.nSampleRate =rate;
FileHeader.nBytesPerSecond = rate * FileHeader.nBlockAlign;
FileHeader.nChannels = channels;
wav_head_print();
int nFileLen = 0;
int nSize = sizeof(FileHeader);
FILE *fp_s = NULL;
FILE *fp_d = NULL;
fp_s = fopen(file_name_pcm, "rb");
if (fp_s == NULL)
return -1;
fp_d = fopen(file_name_wav, "wb+");
if (fp_d == NULL)
return -2;
int nWrite =fwrite(&FileHeader, 1, nSize, fp_d);
if (nWrite != nSize)
{
fclose(fp_s);
fclose(fp_d);
return -3;
}
while( !feof(fp_s))
{
char readBuf[4096];
int nRead = fread(readBuf, 1,4096, fp_s);
if (nRead >0)
{
fwrite(readBuf,1, nRead, fp_d);
}
nFileLen += nRead;
}
fseek(fp_d, 0L, SEEK_SET);
FileHeader.nRIFFSize = nFileLen - 8 +nSize;
FileHeader.nDataSize = nFileLen;
nWrite =fwrite(&FileHeader, 1, nSize, fp_d);
if (nWrite != nSize)
{
fclose(fp_s);
fclose(fp_d);
return -4;
}
fclose(fp_s);
fclose(fp_d);
return nFileLen;
}
编译:gcc pcm2wav.c -o pcm2wav
如果要想 test.pcm 转换为test.wav
./pcm2wav -s test.pcm -d test.wav -c 2 -r 48000 -b 16
后续,提供一个单声道转多声道的算法:
void MonoToMulti(char *src_buf, char *dst_buf, int len, int bits, int channels)
{
int i, j;
for (int i = 0; i < len / (bits/8); i++)
{
for (int j = 0; j < channels * bits/8; j++)
dst_buf[i * channels * bits/8 + j] = src_buf[i * bits/8 + j%(bits/8)];
}
}
参考: