在研究android音频架,音频驱动等的时候,就有涉及到dump音频数据debug,重采样,downmixer,位深转换的处理,那这些的操作原理以及相关算法是如何实现的呢?
带着这个问题,开始探讨音频数据的如下几个问题
- 音频数据的特征,与存储等基本概念
- 音频数据的dump方法
- 重采样
- downmix
- 存储位深转换
- 参考文章列表
位深的装换
声道数的装换
1. 音频数据的特征,与存储等基本概念
PCM的一些计算
一个PCM文件的大小(dataSize),存储位深(byteCount*8),采样率,PCM时长(duration)之间的关系:
dataSize=channalCount *byteCount *sampleRate *duration
如果考虑帧数帧的大小,文件帧数(frameCount),帧长度(frameSize) 计算关系如下:
frameCount = sampleRate * duration
frameCount = dataSize / (channelCount*byteCount)
frameSize = dataSize/frameCount = channelCount*byteCount
不过以上考虑帧长度的时候,需要注意是考量的PCM文件,如果是WAVE等格式,帧长度还需要考虑每一帧的填充帧头信息(headerInfo)大小
PCM的存储格式
属于左右声道交叉存储
常用的存储位深,就是8,16, 32位,分别对应的样本空间大小如下:
样本大小 —— 数据格式 —— 最小值 ——最大值
8位PCM —— unsigned int ——0—— 225
16位PCM —— int—— -32767 —— 32767
存储方式
大端存储
一种是将高序列字节存储在起始地址,这称为小端(little-endian)字节序
小端存储
一种是将低序字节存储在起始地址(小地址),这称为小端(little-endian)字节序
代码检查:
short test = 0x1234;
printf("test: %x, base: %x, high:%x \n",test,*(char*)(&test),*((char*)(&test)+1));
//test: 1234, base: 34, high:12
printf("test addr:%p ,base:%p,high :%p \n",&test,(char*)(&test),((char*)(&test)+1));
//test addr:0x7fff6934b52e ,base:0x7fff6934b52e,
// high :0x7fff6934b52f
//可见高位存储在高地址中间,低位存储在小地址中间,则是LE小端存储
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
2,PCM文件的转换
测试所用的音源 16位,双声道,采样率44.1KHZ,小端存储
将音源的左右声道分离,分别保存左右声道数据
这个是参考博文:
http://blog.csdn.net/leixiaohua1020/article/details/50534316
/**
* Split Left and Right channel of 16LE PCM file.
* @param url Location of PCM file.
*
*/
int simplest_pcm16le_split(char *url,char *l_url,char *r_url){
FILE *fp=fopen(url,"rb+");
FILE *fp1=fopen(l_url,"wb+");
FILE *fp2=fopen(r_url,"wb+");
unsigned char *sample=(unsigned char *)malloc(4);//每次读4个字节,也就是16位 L+R声道数据,可以理解为一帧
while(!feof(fp)){
fread(sample,1,4,fp);//读4次一个字节
//L
fwrite(sample,2,1,fp1);
//R
fwrite(sample+2,2,1,fp2);
}
free(sample);
fclose(fp);
fclose(fp1);
fclose(fp2);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
从上面的测试用例获取了左右声道的PCM数据,尝试使用http://blog.csdn.net/leixiaohua1020/article/details/50534316
中间的方法,将文件从16位转8位(质量变差,文件大小缩减一半)
int pcm16to8bit(char *source_16_url,char *dest_8_url ){
FILE *source_fp=fopen(source_16_url,"rb+");
FILE *dest_fp=fopen(dest_8_url,"wb+");
unsigned char *sample=(unsigned char *)malloc(4); //读数据的每一帧,还是16*2/8 四个字节
int cnt =0;
while(!feof(source_fp))
{
short *sampleNum16 =NULL;//short是两个字节
unsigned char sample_dest8 =0; //char是一个字节,8位是无符号存储,所以需要无符号的char
char sample_temp8 = 0; //需要先转位数,再转符号,因而需要一个temp
fread(sample,1,4,source_fp);//读一帧
//L channel write
sampleNum16=(short *)sample;//char* 转 short *,LE
sample_temp8=(*sampleNum16)>>8;//16转8,左移去除低字节
sample_dest8 = sample_temp8 +128;//转符号
fwrite(&sample_dest8,1,1,dest_fp);//写入文件
//R channel write
sampleNum16 = (short *)(sample+2);
sample_temp8=(*sampleNum16)>>8;
sample_dest8 = sample_temp8+128;
fwrite(&sample_dest8,1,1,dest_fp);
cnt++;
}
free(sample);
fclose(source_fp);
fclose(dest_fp);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
接下来思考,八位转16位怎么转呢?
int pcm8to16bit(char *source_8_url,char *dest_16_url ){
FILE *source_fp=fopen(source_8_url,"rb+");
FILE *dest_fp=fopen(dest_16_url,"wb+");
unsigned char *sample=(unsigned char *)malloc(2);//八位,每个采样数就2个字节
int cnt =0;
while(!feof(source_fp))
{
char* sampleNum8 =NULL;
short sample_temp16 =0;
unsigned short sample_dest16 = 0;
fread(sample,1,2,source_fp);//每次读两个byte
//L channel write
sampleNum8=sample;
//sample_temp16=(sampleNum8)<<8;
sample_dest16 = (*sampleNum8 +128)<<8;
//sample_dest16=(sample_dest16>>8)&0x00ff;
fwrite(&sample_dest16,2,1,dest_fp);
//R channel write
sampleNum8=(sample+1);
/*sample_temp16=(sampleNum8)<<8;
sample_dest16 = sample_temp16 -(2<<15);*/
sample_dest16 = (*sampleNum8 +128)<<8;
//sample_dest16=(sample_dest16>>8)&0x00ff;
fwrite(&sample_dest16,2,1,dest_fp);
cnt++;
}
free(sample);
fclose(source_fp);
fclose(dest_fp);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
声道数的转换
尝试把单声道转换成双声道
思路很简单,就是把左声道的数据和右声道的数据分别用原始数据填充一遍,当然这个操作是没什么意义的。
尝试将双声道转化为单声道:
void pcm_16bit_stereo_to_mono(char* source,char* dest)
{
FILE *source_fp=fopen(source,"rb+");
FILE *dest_fp=fopen(dest,"wb+");
unsigned char * sample=(unsigned char*)malloc(4);
while(!feof(source_fp))
{
short dest_sample = 0;
fread(sample,1,4,source_fp);
dest_sample= ((((*(sample+1)) << 8) | ((*(sample)) ))
+ ((*(sample+3)) << 8) | ((*(sample+2)) ));// >> 1;
fwrite(&dest_sample,2,1,dest_fp);
}
free(sample);
fclose(source_fp);
fclose(dest_fp);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
以上的计算,在Android源码里面可以找到音效处理这块涉及到很多转换操作,参考代码路径:
sourcecode/frameworks/av/media/libeffects/lvm/lib/Common/src/
重采样
在之前的一个博客里面介绍了重采样的概念
参考:
http://blog.csdn.net/joymine/article/details/74530111
重采样就是做插值/抽值算法