5.1声道WAVE音频文件信息的写入及生成

来源:互联网 发布:昌泰祥淘宝店可信吗 编辑:程序博客网 时间:2024/06/06 11:41

5.1声道WAVE音频文件信息的写入及生成


环境

C程序编写环境:VS2010

数据分析环境:Matlab R2010a

音频测试环境:Adobe Audition

 

关于wave格式音频文件的声道简介

wave格式的音频文件多为单声道和立体声(左右双声道),多声道的音频文件常见的为5.1声道,即6个扬声器通道。

单声道:左右扬声器中的信号完全一致,左右声道完全不存在差别。如下图1:用Adobe Audition打开一个单声道音频文件。

 

立体声:左右扬声器中的信号有差异,主观感受上更有空间感。如下图2:用Adobe Audition打开一个立体声音频文件。

 

5.1声道:具有6个通道,六个扬声器中的信号各不相同。如下图3:用Adobe Audition打开一个5.1声道音频文件。前两个为左声道和右声道,后面四个信号分别分配到其他扬声器上。

 

 

实验过程

要完成上述试验,我们必须准备六个单声道的wave格式的音频文件,这个可以通过Adobe Audition动手制作,例如输入6首歌,可以分别选择另存为实验需要的参数的音频文件。Adobe Audition很容易学习,根据自己需要的参数另存为更简单,大家不妨试试。这里,我将电脑中的六个立体声文件另存为如下格式的单声道文件:采样率44.1Khz,采样值(量化比特数)16bit,单声道。

获得了实验原始材料,还要要学会分析数据,了解不同声道音频文件的构成,找到他们之间的内在联系便不难实现从单声道文件到5.1声道的合成。这里我们尤其要重视MatlabAdobe Audition的使用,实验内容用以上两款软件非常容易实现,这里只能帮我们分析数据,最后的C代码实现还是要靠自己用VS2010亲自动手编写。在动手之前我们先熟悉一下wave文件的构造,可以参看如下两个博客:http://www.cnblogs.com/liyiwen/archive/2010/04/19/1715715.html

http://syp0316.blog.163.com/blog/static/49178801200793133424635/

首先先用Matlab画出单声道文件的波形,观察单声道文件的PCM数据。

 

Matlab代码:

clc

clear

[s01,f1,nbit]=wavread('E:\Audio\Mono\es01_single.wav');

[s02,f2,nbit]=wavread('E:\Audio\Mono\es02_single.wav');

[es03,f3,nbit]=wavread('E:\Audio\Mono\es03_single.wav');

[s04,f4,nbit]=wavread('E:\Audio\Mono\sc01_single.wav');

[s05,f5,nbit]=wavread('E:\Audio\Mono\sc02_single.wav');

[s06,f6,nbit]=wavread('E:\Audio\Mono\sc03_single.wav');

 

%画图

subplot(3,2,1)

plot(s01),title('s01');

subplot(3,2,2)

plot(s02),title('s02');

subplot(3,2,3)

plot(s03),title('s03');

subplot(3,2,4)

plot(s04),title('s04');

subplot(3,2,5)

plot(s05),title('s05');

subplot(3,2,6)

plot(s06),title('s06');

6个单声道文件的PCM数据存放在s01-s06中,他们均为列向量(一维数组)。读取立体声文件我们不难发现,其PCM数据存放在一个M*2的二维数组中,再根据博客中介绍的不同声道文件PCM数据的摆放方式,不难联想到5,1声道文件的PCM数据的存放方式,显然是放在一个M*6的多维数组中。接着写matlab代码,用以上的单声道文件合成一个5.1声道的wave文件。

 

%补零

Len=[length(s01),length(s02),length(s03),length(s04),length(s05),length(s06)];

LEN_MAX=max(Len);

s01(Len(1)+1:LEN_MAX,1)=0;

s02(Len(2)+1:LEN_MAX,1)=0;

s03(Len(3)+1:LEN_MAX,1)=0;

s04(Len(4)+1:LEN_MAX,1)=0;

s05(Len(5)+1:LEN_MAX,1)=0;

s06(Len(6)+1:LEN_MAX,1)=0;

 

%合并

s=[];

s(:,1)=s01;

s(:,2)=s02;

s(:,3)=s03;

s(:,4)=s04;

s(:,5)=s05;

s(:,6)=s06;

 

%写文件

wavwrite(s,f1,nbit,'E:\Audio\Result\Channel_6.WAV');

figure

plot(s),title('5.1声道音频文件')

 

用Adobe Audition打开该文件,如上图3,并且能够播放,分别点击右侧L和R分别播放的是第一个文件es01_single.wav和第二个文件es02_single.wav.其他声道听不见是因为我们的耳机是左右声道的,需要并接其他四个扬声器才能听见另外四个声道。可见我们生成的多声道文件是正确的,即在C语言编写时可以按照类似思路进行。同时,在MATLAB观察5.1声道文件数据包可以发现S是一个M*6的多维数组,有6列,每一列代表一个单声道文件的PCM数据。

完成了单声道,多声道文件的制作并观察数据之后,就需要思考单声道wave文件中的信息和多声道中的信息有什么不一样,哪些地方需要改变,哪些地方不变。

大家可以阅读我前几篇关于wave文件信息的读取C代码和以上两个博客的内容,可以通过C语言代码对任何wave音频文件信息的读取。阅读过程中大家会发现,可以把wave文件信息宏观上分为两类,头部参数信息和PCM数据(真正的声音数据)。

头部信息需要做哪些变化:

根据已有的C代码,读取6个单声道文件的头部参数和用matlab合成的5.1声道的文件的头部参数。

单声道文件的头部参数如下如:

 

 

5.1声道文件的头部参数如下如:

 

 

大家自己拷贝程序运行观察比较结果会发现,6个的单声道文件合称为一个5,1声道的文件,文件大小上肯定是单声道的6倍,哪些参数需要扩大六倍记录下来,如声道数,文件大小等,便于在C代码中改写文件信息。

接下来我就通过C代码分析实验的过程。

工程包含三个文件:

WAVE_Struct.h  用于定义wave文件结构体和声明一些函数

Read_H_Data.cpp 编写函数体;

Write_WAVE_H.cpp 写新的文件。

WAVE_Struct.h 根据wave文件的描述定义结构体

#include <iostream>

using namespace std;

 

#define Num 6//声道数

 

/***************RIFF WAVE Chunk***********/

typedef struct RIFF_Header 

{

char szRiff[4];//RIFF

int dwszRiff;                  // *6

char szRiffFormat[4];//WAVE

}RIEF_H;

 

/***************Format Chunk***********/

typedef struct WAVE_FORMAT

{

char szFmtID[4];//fmt

int dwFmtSize;        // 18-16

short wFormatTag;

short wChannels;      // *6

int dwSamplesPerSec;   

int dwAvgBytePerSec;   //*6

short wBlockAlign;    //  *6

short wBitsPerSample;

 

}WAVE_F;

 

/***************Fact Chunk***********/

typedef struct FACT_BLOCK

{

char szFactID[4];

int dwFactSize;       

}FACT_B;

 

/***************Data Chunk***********/

typedef struct DATA_BLOCK

{

char szDataID[4];

int dwDataSize;//*6

}DATA_B;

 

int open(FILE *fp_result);

int READ_H_DATA(RIEF_H &f_riff, WAVE_F &f_format,DATA_B &f_data);

float *Read_P_Data(FILE *fp_speech,int front_info);

int Write_PCM_DATA(FILE *fp_result,RIEF_H f_riff, WAVE_F f_format,DATA_B f_data,short p[]);

 

 

Read_H_Data.cpp

#include "WAVE_Struct.h"

 

int open(FILE *fp_result)//打开文件函数

{

if((fp_result=fopen("E:\\Audio\\Result\\result2.wav","wb"))==NULL)

{

printf("can't open this file\n");

exit(0);

}

return 0;

}

//读取文件数据函数(很重要,细心体会)

int READ_H_DATA(RIEF_H &f_riff, WAVE_F &f_format,DATA_B &f_data)

{

FILE *fp;

fp=fopen("E:\\Audio\\Result\\sc02_single.wav","rb+");//为读,打开一个wav文件

if((fp=fopen("E:\\Audio\\Result\\sc02_single.wav","rb+"))==NULL) //若打开文件失骸败,退出

{

printf("can't open this file\n");

exit(0);

}

/*********RIFF WAVE Chunk的输出*********/

 

cout<<endl<<"RIFF WAVE Chunk的输出: "<<endl;

//读取ID=RIFF  4字节

fseek(fp,0L,0);

fread(f_riff.szRiff,sizeof(f_riff.szRiff),1,fp);

cout<<"RIFF标识:"<<f_riff.szRiff[0]<<f_riff.szRiff[1]<<f_riff.szRiff[2]<<f_riff.szRiff[3]<<endl;

 

//读取文件大小 4字节

fseek(fp,4L,0);

fread(&f_riff.dwszRiff,sizeof(f_riff.dwszRiff),1,fp);

cout<<"文件t大洙小:阰 "<<f_riff.dwszRiff<<endl;

 

//读取WAVE标识 4字节

fseek(fp,8L,0);

fread(f_riff.szRiffFormat,sizeof(f_riff.szRiffFormat),1,fp);

cout<<"WAVE标识:"<<f_riff.szRiffFormat[0]<<f_riff.szRiffFormat[1]<<f_riff.szRiffFormat[2]<<f_riff.szRiffFormat[3]<<endl;

 

///*******Format Chunk的输出*******/

 

cout<<endl<<"Format Chunk的输出: "<<endl;

//读取fam标识 4字节

fseek(fp,12L,0);

fread(f_format.szFmtID,sizeof(int),1,fp);

cout<<"fmt标识:"<<f_format.szFmtID[0]<<f_format.szFmtID[1]<<f_format.szFmtID[2]<<endl;

 

//读取size 4字节

fseek(fp,16L,0);//size_FORMAT=16,则最后没有那两个用来存放附加信息的字节,若为18,则有那两个字节,注意这会影响头信息的整体长度

fread(&f_format.dwFmtSize,sizeof(int),1,fp);  //查看是否有附加信息,size_FORMAT=18,则有附加信息

cout<<"size大小: "<<f_format.dwFmtSize<<endl;

 

//编括码方式 2字节

fseek(fp,20L,0);

fread(&f_format.wFormatTag,sizeof(short),1,fp);

cout<<"编码方式:"<<f_format.wFormatTag<<endl;

 

//声道数 2字节

fseek(fp,22L,0);

fread(&f_format.wChannels,sizeof(short),1,fp);

cout<<"声道数目:"<<f_format.wChannels<<endl;

 

//采样频率 4字节

fseek(fp,24L,0);

fread(&f_format.dwSamplesPerSec,sizeof(int),1,fp);

cout<<"采样频率:"<<f_format.dwSamplesPerSec<<endl;

 

//每秒所需字节数 4字节

fseek(fp,28L,0);

fread(&f_format.dwAvgBytePerSec,sizeof(int),1,fp);

cout<<"每秒所需字节数:"<<f_format.dwAvgBytePerSec<<endl;

 

//数据块对齐单位 2字节

fseek(fp,32L,0);

fread(&f_format.wBlockAlign,sizeof(short),1,fp);

cout<<"数据块对齐单位:"<<f_format.wBlockAlign<<endl;

 

//每个采样需要的bit数 2字节

fseek(fp,34L,0);

fread(&f_format.wBitsPerSample,sizeof(short),1,fp);

cout<<"每个采样需要的bit数:"<<f_format.wBitsPerSample<<endl;

 

 

/*********************Data Chunk的输出************************/

cout<<endl<<"Data Chunk的输出:"<<endl;

//DATA标识 4字节

fseek(fp,38L,0);

fread(f_data.szDataID,sizeof(int),1,fp);

cout<<"DATA标识: "<<f_data.szDataID[0]<<f_data.szDataID[1]<<f_data.szDataID[2]<<f_data.szDataID[3]<<endl;

 

//PCM数据长度 4字节

fseek(fp,42L,0);

fread(&f_data.dwDataSize,sizeof(int),1,fp);

cout<<"PCM数据长度:"<<f_data.dwDataSize<<endl;

 

return 0;

}

 

float *Read_P_Data(FILE *fp_speech,int front_info)

{

short data;            //为什么用short参看文献中的相应wav文件的pcm数据存放格式。16位单声道为两个字节,故为short

float static a[N];

fseek(fp_speech,front_info,0);//跳过头,经-过分析该文件头部信息占46个字节,之后的存放PCM数据

for(int i=0;i<N;i++)

{

fread(&data,sizeof(short),1,fp_speech);

a[i]=(float)data/(float)(1024*32);

}

rewind(fp_speech);

return a;

}

 

int Write_PCM_DATA(FILE *fp_result,RIEF_H f_riff, WAVE_F f_format,DATA_B f_data,short p[])

{

fseek(fp_result,0L,0);

//写入RIFF Chunk段

fseek(fp_result,0L,0);

if (fwrite(&f_riff,sizeof(f_riff),1,fp_result)!=1)

{

cout<<"error"<<endl;

}

 

//写Fromat Chunk段

if (fwrite(&f_format,sizeof(f_format),1,fp_result)!=1)

{

cout<<"error"<<endl;

}

 

//写′Data Chunk段  这里我们不写附加段

if (fwrite(&f_data,sizeof(f_data),1,fp_result)!=1)

{

cout<<"error"<<endl;

}

 

//写Pcm Data Chunk段

for (int k=0;k<6*1122984;k++)

{

if (fwrite(&p[k],sizeof(short),1,fp_result)!=1)

{

cout<<"error"<<endl;

}

}

fclose(fp_result);

return 0;

}

 

Write_WAVE_H.cpp

 

#include "WAVE_Struct.h"

 

char str[Num][30]={ //存放的是单声道文件的路径,大家根据自己的情况更改

"E:\\Audio\\Mono\\es01_single.wav",

"E:\\Audio\\Mono\\es02_single.wav",

"E:\\Audio\\Mono\\es03_single.wav",

"E:\\Audio\\Mono\\sc01_single.wav",

"E:\\Audio\\Mono\\sc02_single.wav",

"E:\\Audio\\Mono\\sc03_single.wav"

};

 

int main()

{

RIEF_H f_Riff; f_Riff.dwszRiff=0;//初始化ˉ

WAVE_F f_format; f_format.dwAvgBytePerSec=0;

DATA_B f_data;f_data.dwDataSize=0;

FILE *fp_Mono[Num]; //存放六个单蹋声道文件的文件指针

int data_size[Num]; //记录六个单蹋声道文件PCM数据的长度

int max_len=0; //因为最后数据合并,所以找出data_size[]中的最大值为基准,否则最长序列信息会丢失,长度不足的序列自动补零

 

/**************获取各单声道文件PCM数据的长度**************/

for (int i=0;i<Num;i++)

{

fp_Mono[i]=fopen(str[i],"rb+");//为读,打开一个wav文件

if((fp_Mono[i]=fopen(str[i],"rb+"))==NULL) //若打开a文件失败,退出

{

printf("can't open this file\n");

exit(0);

}

fseek(fp_Mono[i],42L,0);//跳转到存放PCM数据长度的地方{研究wave文件头的结构),读取长度以便后面分配内存空间

fread(&data_size[i],sizeof(int),1,fp_Mono[i]);//读取各个文件中PCM数据的长度放入缓存中

if (data_size[i]>=max_len)//找最大值过程,max_len存放最大长度

{

max_len=data_size[i];

}

cout<<"序ò列的PCM数簓据Y的样ù本点数簓:阰 "<<data_size[i]<<endl;

}

cout<<max_len<<endl;//显示看是否读取正确

 

/**************按长度获取各单声道文件PCM数据**************/

//储存六个指针,指针用来存储pCM数据

//这里采用动态分配,因为文件长度未知,且为了防止溢出

short *wav_data[Num];

for (int j=0;j<6;j++)

{

wav_data[j]=(short*)malloc(max_len*sizeof(short));

if(wav_data==NULL)

{

printf("Can not get menory for that many value..\n");

exit(EXIT_FAILURE);

}

}

 

for (int i=0;i<Num;i++)

{

fseek(fp_Mono[i],46L,0);//跳转到存放PCM数据的地方,按恪各自文已知长度(data_size[i])读取PCM数据

if (fread(wav_data[i],data_size[i],1,fp_Mono[i])!=1)

{

cout<<"read error !"<<endl;//数据读取正确

}

fclose(fp_Mono[i]);  // 依次关闭文件流

}

 

/**************合并数据,各个单声道pCM数据依次读取**************/

short *M_C_data;

M_C_data=(short*)malloc(Num*max_len*sizeof(short));

if(M_C_data==NULL)

{

printf("Can not get menory for that many value..\n");

exit(EXIT_FAILURE);

}

for (int k=0,j=0;j<max_len;k=k+6,j++)//合并数据

{

 

M_C_data[k+0]=wav_data[0][j];

M_C_data[k+1]=wav_data[1][j];

M_C_data[k+2]=wav_data[2][j];

M_C_data[k+3]=wav_data[3][j];

M_C_data[k+4]=wav_data[4][j];

M_C_data[k+5]=wav_data[5][j];

}

//可以多显示几组,看是否全部读取

cout<<M_C_data[0]<<" "<<M_C_data[1]<<" "<<M_C_data[2]<<" "<<M_C_data[3]<<" "<<M_C_data[4]<<" "<<M_C_data[5]<<endl;

 

/**************获取PCM数据长度最大单声道文件的头部信息**************/

READ_H_DATA(f_Riff,f_format,f_data);//读取之后显示出基准文件的头信息

 

/**************改写数据,存入新的wave头*************/

f_Riff.dwszRiff=f_Riff.dwszRiff*6;//整个文件的大小,不包含RIFF的ID所占内存和自身内存大小这里粗略表括示,精确的是将字节数加起来即可。

f_format.dwFmtSize=f_format.dwFmtSize-2;

f_format.wChannels=f_format.wChannels*6;

f_format.dwAvgBytePerSec=f_format.dwAvgBytePerSec*6;

f_format.wBlockAlign=f_format.wBlockAlign*6;

f_data.dwDataSize=max_len*6;

 

    /**************为a读á写′建¨立ⅰ一个新的二t进制文件t**************/

FILE *fp_result=fopen("E:\\Audio\\Result\\result2.wav","wb+");//"wb+"  为读写建立一个新的二进制文件

open(fp_result);

//数据的写入都是整块整块的写入,避免出错

Write_PCM_DATA(fp_result,f_Riff,f_format,f_data,M_C_data);

//释放动态分配的内存

free(wav_data[0]);free(wav_data[1]);free(wav_data[2]);

free(wav_data[3]);free(wav_data[4]);free(wav_data[5]);

free(M_C_data);

return 0;

}

 

/*到这里,新的5.1声道的wave文件就合成完毕,其中wave头中没有添加附加信息段和fact chunk段,感兴趣的同学可以进一步完善*/

 

 

代码复制完毕。

写程序的过程中需要注意一下几点:

1,善于运用动态分配的方式分配内存,读取各个单声道文件的PCM数据时注意读取的长度。别忘了释放内存。

2.运用文件处理函数,fread(),fseek(),fwrite()等要注意,自己好好探究。并注意在什么时候关闭文件最合适,fwrite()最容易导致出错。

3.别忘了用C生成的wave文件用matlab和Adobe Audition验证,看是否正确。

4.READ_H_DATA(RIEF_H &f_riff, WAVE_F &f_format,DATA_B &f_data),运用这个函数读取我们生成的新文件的信息,看是否正确。思考,如果形参不是引用的形式会出现什么情况?

0 0
原创粉丝点击