C/C++修改wav文件采样率实现
将一个采样率为44100的wav文件,改为11025的采样率。
为了实现以上目标,我们需要储备一些关于wav及PCM数据的相关知识,以下是笔者学习参考的网站地址:
PCM音频数据处理---降低采样率 PCM数据格式 C++标准库实现WAV文件读写
以下我基于项目需要写的代码,实现将采样率为44100的wav文件,改为11025的采样率,并重新生成同名文件。系统环境是centos7 x64。
1.wavEdit.h
#ifndef _WAVEDIT_H_
#define _WAVEDIT_H_
typedef unsigned char uint8_t;
typedef signed char int8_t;
typedef unsigned short int uint16_t;
typedef signed short int int16_t;
typedef unsigned int uint32_t;
typedef signed int int32_t;
#pragma pack(push, 1)
typedef struct
{
int8_t chunk_id[4] = { 0 }; // 文件标识,通常为"RIFF"
uint32_t chunk_size = 0; // 文件数据大小,该值 + 8 就是整个WAV文件的大小
int8_t format[4] = { 0 }; // 文件格式,“WAVE”
int8_t fmt_chunk_id[4] = { 0 }; // 子块标识,“fmt”
uint32_t fmt_chunk_size = 0; // 子块大小,其数值不确定,取决于编码格式。可以是16、18、20、40 等
uint16_t audio_fomat = 0; // 音频格式,1为PCM格式
uint16_t num_channels = 0; // 声道数,1:单声道,2:双声道(立体声)
uint32_t sample_rate = 0; // 采样率,常用的采样频率有 11025,22050和44100KHZ
uint32_t byte_rate = 0; // 每秒的字节数,值=声道数x采样率x每样本的数据位数/8。播放软件利用这个值可以估计缓冲区大小。
uint16_t block_align = 0; // 块对齐 采样帧大小。该数值为:声道数x位数/8。播放软件需要一次处理多个该值大小的字节数据,用该数值调整缓冲区
uint16_t bits_per_sample = 0; // 采样深度,即采样位数。常见是4、8、12、16、24、32
int8_t data_chunk_id[4] = { 0 }; // 子块标识,“data”
uint32_t data_chunk_size = 0; // 子块大小
int32_t num_frame = 0; // 文件总帧数
int32_t start_pos = 0; // 音频数据的开始位置
}wavHead;
#pragma pack(pop)
void wavFormate(char* filename,wavHead &wh);
#endif2.wavEdit.cpp
#include <stdio.h>
#include <stdlib.h>
#include "wavEdit.h"
char* read_File_rb(const char * filename,int &datalen)
{
int size=16;
int count = 1;
char * outdat = NULL;
//取文件大小
struct stat statbuf;
int ret = stat(filename,&statbuf); //调用stat函数
if(ret != 0) //获取失败。
{
return outdat;
}
datalen = statbuf.st_size; //返回文件大小。
FILE* fp = fopen(filename, "rb");
if (NULL != fp)
{
outdat = (char *)malloc(datalen+size);
memset(outdat,0,datalen+size);
fread((void*)outdat,(size_t)datalen,1,fp);
fclose(fp);
return outdat;
}
else
{
return outdat;
}
}
void wavHeadFormat(wavHead &wh,int nSampleRate,int ndata_chunk_size)
{
wh.sample_rate = nSampleRate;
wh.byte_rate = wh.num_channels*wh.sample_rate*wh.bits_per_sample/8;
wh.data_chunk_size = ndata_chunk_size; //子块大小,PCM数据大小
wh.chunk_size = ndata_chunk_size+wh.start_pos-8; // 文件数据大小,该值 + 8 就是整个WAV文件的大小
}
/*------------------------------------------
* filename 文件名称路径(含路径)
* wh: 用来保存文件头的结构体实例
--------------------------------------------*/
void wavFormate(char* filename,wavHead &wh)
{
//2.文件头分析
const int HEAD_LENGTH = 256 * 1024;//256kb
char buf[HEAD_LENGTH];
//memcpy(buf,fileBuf,HEAD_LENGTH);
FILE *stream = fopen(filename, "rb");
fread(buf, 1, HEAD_LENGTH, stream);
fclose (stream);
//记录文件读取位置
int pos = 0;
//寻找“RIFF”标记
while (pos < HEAD_LENGTH) {
if ((buf[pos] == 'R') && (buf[pos + 1] == 'I') && (buf[pos + 2] == 'F') && (buf[pos + 3] == 'F')) {
wh.chunk_id[0] = 'R';
wh.chunk_id[1] = 'I';
wh.chunk_id[2] = 'F';
wh.chunk_id[3] = 'F';
pos += 4;
break;
}
++pos;
}
//读取Header部分
wh.chunk_size = *(int *)&buf[pos];
pos += 4;
wh.format[0] = buf[pos];
wh.format[1] = buf[pos + 1];
wh.format[2] = buf[pos + 2];
wh.format[3] = buf[pos + 3];
pos += 4;
//寻找“fmt”标记
while (pos < HEAD_LENGTH) {
if ((buf[pos] == 'f') && (buf[pos + 1] == 'm') && (buf[pos + 2] == 't')) {
wh.fmt_chunk_id[0] = 'f';
wh.fmt_chunk_id[1] = 'm';
wh.fmt_chunk_id[2] = 't';
wh.fmt_chunk_id[3] = ' '; //这个可别漏了哦
pos += 4;
break;
}
++pos;
}
//读取Format Chunk部分
wh.fmt_chunk_size = *(int *)&buf[pos];
pos += 4;
wh.audio_fomat = *(short *)&buf[pos];
pos += 2;
wh.num_channels = *(short *)&buf[pos];
pos += 2;
wh.sample_rate = *(int *)&buf[pos];
pos += 4;
wh.byte_rate = *(int *)&buf[pos];
pos += 4;
wh.block_align = *(short *)&buf[pos];
pos += 2;
wh.bits_per_sample = *(short *)&buf[pos];
pos += 2;
//寻找“data”标记
while (pos < HEAD_LENGTH) {
if ((buf[pos] == 'd') && (buf[pos + 1] == 'a') && (buf[pos + 2] == 't') && (buf[pos + 3] == 'a'))
{
wh.data_chunk_id[0] = 'd';
wh.data_chunk_id[1] = 'a';
wh.data_chunk_id[2] = 't';
wh.data_chunk_id[3] = 'a';
pos += 4;
break;
}
++pos;
}
//读取Data Chunk的非data部分
wh.data_chunk_size = *(int *)&buf[pos];
pos += 4;
//记录真正音频数据的开始位置
wh.start_pos = pos;
//计算文件总帧数
wh.num_frame = wh.data_chunk_size / (wh.num_channels*(wh.bits_per_sample / 8));
printf("[0]sample_rate(%d),byte_rate(%d),data_chunk_size(%d),chunk_size(%d),fmt_chunk_size(%d),start_pos(%d) @ %s.",\
wh.sample_rate, \
wh.byte_rate, \
wh.data_chunk_size, \
wh.chunk_size,wh.fmt_chunk_size,wh.start_pos,\
filename);
//3.数据转写-
//https://www.cnblogs.com/wangguchangqing/p/5970516.html
if(wh.num_channels==2 && wh.sample_rate>11025 && wh.bits_per_sample==16)
{
int rate = wh.sample_rate/11025;
if(rate>1)
{
//读取文件原始数据
int fileLen;
char *fileBuf = read_File_rb(filename,fileLen);
FILE* fp = fopen(filename, "wb+");
if (NULL != fp)
{
//写文件头
fwrite(wh.chunk_id, sizeof(wavHead)-8, 1, fp);
int16_t temp;
int32_t tempSumL = 0;
int32_t tempSumR = 0;
int flag = 0;
int data_chunk_size = 0; //修改后PCM数据长度,单位字节
uint16_t *pdata = (uint16_t *)(fileBuf+wh.start_pos); //转成16位数据
int max = (wh.data_chunk_size/2)-rate*2;
for(int i=0;i<=max;i+=(rate*2))
{
flag = 0;
tempSumL = 0;
tempSumR = 0;
while(flag<rate)
{
if(flag%2==0)
{
temp = pdata[i+flag];
tempSumL += temp;
}
else
{
temp = pdata[i+flag+1];
tempSumR += temp;
}
flag++;
}
temp = (uint16_t)(tempSumL/rate);
fwrite(&temp, 2, 1, fp);
temp = (uint16_t)(tempSumR/rate);
fwrite(&temp, 2, 1, fp);
data_chunk_size+=4;
}
fseek(fp,0,SEEK_SET); //定位到文件起始位置
//重写文件头
wavHeadFormat(wh,11025,data_chunk_size);
fwrite(wh.chunk_id, sizeof(wavHead)-8, 1, fp);
fclose(fp);
printf("[1]sample_rate(%d),byte_rate(%d),data_chunk_size(%d),chunk_size(%d),fmt_chunk_size(%d),start_pos(%d) @ %s.",\
wh.sample_rate, \
wh.byte_rate, \
wh.data_chunk_size, \
wh.chunk_size,wh.fmt_chunk_size,wh.start_pos,\
filename);
}
//内存释放
free(fileBuf);
fileBuf=NULL;
}
}
}3.测试引用
#include "wavEdit.h"
int main(int argc, char* argv[])
{
char m_audioFile[256]="xx.wav";
wavHead wh;
// 执行完志后,m_audioFile 所指向的文件已经完成更新。
wavFormate(m_audioFile, wh);
return 0;
}以上是完整的实现过程。
凯特网版权声明:以上内容允许转载,但请注明出处,谢谢!
