该网站详细描述了WAV文件的头部信息网址
尝试过其他人开源的代码,但是实际运行有问题,并且代码注释不全,于是尝试自己写
运行平台:嵌入式32位linux操作系统
首先要知道一些基础知识:
-
一帧数据大小(单位是字节)
=声道数量
*数据量化比特数
/8
-
采集频率
=1秒采集的帧数
-
文件1秒采集数据大小(单位是字节)
=一帧数据大小(单位是字节)
*采集频率
pcm文件+wav文件的头部信息 = wav文件
WAV文件的头部信息数据结构如下图,图片来自开头提到的网址
代码注释自认为是很全面了,有疑问可以看开头提到的网址
/* 参考网址:http://soundfile.sapp.org/doc/WaveFormat/ */
/* 该网站说明了WAV文件头部信息的所有参数含义以及参数所占空间大小 */
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <linux/types.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
static struct sampParam{
__u8 channels; //声道数
__u16 sample_rate; //采样率
__u8 bitsPerSample; //量化位数
}sampParam;
/**
* Convert PCM raw data to WAVE format
* @param pcmpath Input PCM file.
* @param sampParam 采样参数.
* @param wavepath Output WAVE file.
*/
int transform_pcm_to_wave(const char *pcmpath, struct sampParam sampParam, const char *wavepath)
{
/*-------------------------------------------------------------------------------*/
/* --------------------------- 创建并填充wav头部信息 -------------------------------*/
/*-------------------------------------------------------------------------------*/
int fd_pcm, fd_wav; //pcm,wav文件描述符
__u8 WAV_HEAD_SIZE = 44; //RIFF_HEADER + FMT_SUBCHUNK + DATA_SUBCHUNK = WAV_HEAD_SIZE(单位字节)
off_t offset; //文件读写偏移位置
__u8 buf[1024]; //fd_pcm读取到buf,然后写到fd_wav
ssize_t len; //实际从fd_pcm读取到的字节数,作为read函数的返回值
typedef struct RIFF_HEADER{
__u8 ChunkID[4]; //内容为"RIFF"
__u32 ChunkSize; //最后填写,WAVE格式音频的大小
__u8 Format[4]; //内容为"WAVE"
}RIFF_HEADER;
typedef struct FMT_SUBCHUNK{
__u8 Subchunk1ID[4]; //内容为"fmt "
__u32 Subchunk1Size; //内容为WAVE_FMT占的字节数,为16
__u16 AudioFormat; //如果为PCM,改值为 1
__u16 NumChannels; //通道数,单通道=1,双通道=2
__u32 SampleRate; //采样频率
__u32 ByteRate; /* ==dwSamplesPerSec*wChannels*uiBitsPerSample/8 */
__u16 BlockAlign; //==wChannels*uiBitsPerSample/8
__u16 BitsPerSample; //每个采样点的bit数,8bits=8, 16bits=16
}FMT_SUBCHUNK;
typedef struct DATA_SUBCHUNK{
__u8 Subchunk2ID[4]; //内容为"data"
__u32 Subchunk2Size; //==NumSamples*wChannels*uiBitsPerSample/8
}DATA_SUBCHUNK;
typedef struct WAV_HEAD{
RIFF_HEADER pcmHeader;
FMT_SUBCHUNK pcmFmt;
DATA_SUBCHUNK pcmData;
}WAV_HEAD;
WAV_HEAD* wavHead; //整个wav文件的头部
//只读
fd_pcm = open(pcmpath, O_RDONLY);
if(0 > fd_pcm){
perror("open pcm function error");
goto err1;
}
//可读可写,创建新文件,允许文件所属者读、写、执行文件
fd_wav = open(wavepath, O_RDWR | O_CREAT | O_EXCL, S_IRWXU | S_IRWXG | S_IRWXO);
if (0 > fd_wav){
perror("open wav function error");
goto err2;
}
/* WAVE_HEADER -------------------------------------------------------------------*/
memcpy(wavHead->pcmHeader.ChunkID, "RIFF", 4); //规定填RIFF
memcpy(wavHead->pcmHeader.Format, "WAVE", 4); //规定填WAVE
/* WAVE_FMT -------------------------------------------------------------------*/
//规定填fmt,不要忘记fmt后面的空格,凑齐4字节
memcpy(wavHead->pcmFmt.Subchunk1ID, "fmt ", 4);
//一般都是填16, 这个数字说明WAVE_FMT子块中Subchunk1Size后面剩下的字节空间大小
wavHead->pcmFmt.Subchunk1Size = 16;
//这个数字取决于音频数据的编码格式,如果是PCM则填1。其余如MP3、ADPCM等编码格式我也不知道。
wavHead->pcmFmt.AudioFormat = 0x0001;
//声道数量,由函数参数提供
wavHead->pcmFmt.NumChannels = sampParam.channels;
//采样频率,由函数参数提供
wavHead->pcmFmt.SampleRate = sampParam.sample_rate;
//表示音频数据每秒的字节数
wavHead->pcmFmt.ByteRate = sampParam.sample_rate *
sampParam.channels *
sampParam.bitsPerSample / 8;
/* 一帧数据大小(单位字节) */
wavHead->pcmFmt.BlockAlign = sampParam.channels * sampParam.bitsPerSample / 8;
/* 量化位数 */
wavHead->pcmFmt.BitsPerSample = sampParam.bitsPerSample;
/* WAVE_DATA -------------------------------------------------------------------*/
memcpy(wavHead->pcmData.Subchunk2ID, "data", 4);
/* 获取PCM文件大小,写给Subchunk2Size */
offset = lseek(fd_pcm, 0, SEEK_END); //fd_pcm文件的读写偏移量移动至文件最后
if(offset == -1){
perror("lseek SEEK_END function error");
goto err2;
}
/* PCM文件大小(单位字节) */
wavHead->pcmData.Subchunk2Size = (__u32)offset;
/* 不要忘记 WAVE_HEADER 部分信息还有一个 ChunkSize 还没计算---------------------------*/
wavHead->pcmHeader.ChunkSize = 36 + wavHead->pcmData.Subchunk2Size;
/*-------------------------------------------------------------------------------*/
/* ----------------- wav头部信息创建结束,开始写入fd_wav文件中 -----------------------*/
/*------------------------------------------------------------------------------*/
/* fd_pcm文件的读写偏移量移动回0位置 */
offset = lseek(fd_pcm, 0, SEEK_SET);
if(offset == -1){
perror("lseek SEEK_SET function error");
goto err2;
}
/* 先向fd_wav写入头部信息 */
if( 0 > write(fd_wav, wavHead, sizeof(WAV_HEAD))){
perror("write wavHead function error");
}
while(( len = read(fd_pcm, buf, sizeof(buf)) ) > 0 ) {
// 每读一次数据,就写入到拷贝文件中
if(0 > write(fd_wav, buf, len)){
perror("write fd_wav function error");
goto err2;
}
}
if(0 > len){
perror("read fd_pcm function error");
goto err2;
}
return 0;
err2:
close(fd_wav);
err1:
close(fd_pcm);
return -1;
}
int main(int argc, char* argv[])
{
sampParam.bitsPerSample = 16;
sampParam.channels = 2;
sampParam.sample_rate = 44100;
if(0 > transform_pcm_to_wave(argv[1], sampParam, argv[2])){
perror("transform_pcm_to_wave function error");
return -1;
}
return 0;
}
最后,提供一份PCM音频文件,来尝试代码的运行效果,文件如下
提取码: ajqj
百度网盘连接这份PCM格式:44100hz,16位,双通道