您的当前位置:首页正文

ALSA子系统(十五)------PCM转WAV音频格式

2024-11-27 来源:个人技术集锦
你好!这里是风筝的博客,
欢迎和我一起交流。

所有的WAV都有一个文件头,这个文件头记录着音频流的编码参数。数据块的记录方式是little-endian字节顺序。

WAV格式全称为WAVE,只需要在PCM文件的前面添加WAV文件头,就可以生成WAV格式文件。
WAVE文件格式是微软RIFF规范的一部分,用于存储多媒体文件。RIFF文件以文件头开始,后跟一系列数据块(Chunk)。WAVE文件通常只是具有单个“ WAVE”块的RIFF文件,该“ WAVE”块由两个子块组成-“ fmt”块指定数据格式,而“ data”块包含实际样本数据。将此表格称为“规范表格”。

  • ChunkID:大小为4个字节数据,内容为“RIFF”,表示资源交换文件标识
  • ChunkSize:大小为4个字节数据,内容为一个整数,表示从下个地址开始到文件尾的总字节数
  • Format:大小为4个字节数据,内容为“WAVE”,表示WAV文件标识
  • Subchunkl ID:大小为4个字节数据,内容为“fmt ”,表示波形格式标识(fmt ),最后一位空格。
  • Subchunkl Size:大小为4个字节数据,内容为一个整数,表示PCMWAVEFORMAT的长度。
  • AudioFormat:大小为2个字节数据,内容为一个短整数,表示格式种类(值为1时,表示数据为线性PCM编码)
  • NumChannels:大小为2个字节数据,内容为一个短整数,表示通道数,单声道为1,双声道为2
  • SampleRate:大小为4个字节数据,内容为一个整数,表示采样率,比如44100
  • ByteRate:大小为4个字节数据,内容为一个整数,表示波形数据传输速率(每秒平均字节数),大小为 采样率 * 通道数 * 采样位数
  • BlockAlign:大小为2字节数据,内容为一个短整数,表示DATA数据块长度,大小为 通道数 * 采样位数
  • BitsPerSample:大小为2个字节数据,内容为一个短整数,表示采样位数,即PCM位宽,通常为8位或16bit
  • Subchunk2ID:大小为4个字节数据,内容为“data”,表示数据标记符
  • Subchunk2 Size:大小为4个字节数据,内容为一个整数,表示接下来声音数据的总大小,需要减去头部的44个字节。
  • 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)];
	}
}

参考:

显示全文