当前位置 博文首页 > wanggao的专栏:ffmpeg学习 pcm文件转wav文件
前面有详细介绍PCM文件数据格式,本文简单说明PCM文件如何转换成WAV文件,并通过代码实现转换过程。
WAV是微软公司专门为Windows开发的一种标准数字音频文件,符合资源互换文件格式(RIFF)规范,支持多种音频位数、采样频率和声道。基于PCM编码的WAV格式的音频数据无压缩,能被生声卡直接支持,文件体积大。
类似视频封装格式,WAV文件可以认为是保存音频采样数据的容器封装,仅在PCM数据前增加一些wav文件信息。基于PCM编码的标准WAV协议如下图
整个wav文件只有一个RIFF块,默认id为“RIFF”,其chunksize为不包含前两个字段文件大小(filesize - 8),format默认为”WAVE”。子块“fmt ”(注意有一个空字符)描述了音频采样数据格式,块长度固定为16字节。子块“data”承载pcm数据。
根据pcm文件信息,首先创建WAV文件格式文件头信息,再读取pcm数据,之后写出到文件即可。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
// PCM WAV Format (44 bytes)
typedef struct WavHeader {
uint32_t chunk_id; // "RIFF"
uint32_t chunk_size; // (44-8) + pcm_data_len
uint32_t format; // "WAVE"
uint32_t sub_chunk_1_id; // "fmt "
uint32_t sub_chunk_1_size; // 16 (PCM)
uint16_t audio_format; // 1 - PCM
uint16_t num_channels; // 1-mono, 2-stero
uint32_t sample_rate;
uint32_t byte_rate; // sample_rate * block_align
uint16_t block_align; // num_channels * bit_per_sample
uint16_t bit_per_sample;
uint32_t sub_chunk_2_id; // "data"
uint32_t sub_chunk_2_size; // pcm_data_len
uint8_t data[0]; // data pointer (不占内存)
}WavHeader;
int main()
{
// pcm 数据
const char *pcm_file_name = "../files/Titanic_8000_s16_stero.pcm";
FILE *pcm_file = fopen(pcm_file_name,"rb");
if(!pcm_file) {
printf("can not read pcm file %s\n", pcm_file_name);
return 0;
}
int32_t pos_s = ftell(pcm_file);
fseek(pcm_file, 0, SEEK_END);
int32_t pos_e = ftell(pcm_file);
int32_t pcm_data_len = pos_e - pos_s;
rewind(pcm_file);
// wav 数据
const char *wav_file_name = "Titanic_8000_s16_stero.wav";
FILE *wav_file = fopen(wav_file_name, "wb");
if(!wav_file) {
printf("can not read pcm file %s\n", wav_file_name);
return 0;
}
void *wav_buff = malloc(sizeof(WavHeader) + pcm_data_len);
WavHeader *wavHeader = (WavHeader*)wav_buff;
memcpy(&wavHeader->chunk_id,"RIFF",4);
wavHeader->chunk_size = 36 + pcm_data_len;
memcpy(&wavHeader->format, "WAVE", 4);
memcpy(&wavHeader->sub_chunk_1_id, "fmt ", 4);
wavHeader->sub_chunk_1_size = 16;
wavHeader->audio_format = 1;
wavHeader->num_channels = 2;
wavHeader->sample_rate = 8000;
wavHeader->bit_per_sample = 16;
wavHeader->block_align = wavHeader->num_channels * wavHeader->bit_per_sample / 8;
wavHeader->byte_rate = wavHeader->sample_rate * wavHeader->block_align;
memcpy(&wavHeader->sub_chunk_2_id, "data", 4);
wavHeader->sub_chunk_2_size = pcm_data_len;
// read pcm data
fread(wavHeader->data, 1, pcm_data_len, pcm_file);
// 写wav头、PCM数据
fwrite(wavHeader, 1, sizeof(WavHeader) + pcm_data_len, wav_file);
// 关闭文件
fclose(pcm_file);
fclose(wav_file);
free(wav_buff);
return 0;
}
注意WAV文件结构体中存在一个数组长度为0的uint8_t data[0]成员,该指针data不占用结构体大小,通常这种方用于不定长读的内存分配。
程序运行保存的文件大小如下图所示,wav文件也确实比pcm文件大了44个字节。
使用MediaInfo查看wav文件信息如下图,文件可以正常播放。