音频数据的处理

时间:2024-03-24 17:08:08

在研究android音频架,音频驱动等的时候,就有涉及到dump音频数据debug,重采样,downmixer,位深转换的处理,那这些的操作原理以及相关算法是如何实现的呢?

带着这个问题,开始探讨音频数据的如下几个问题

  1. 音频数据的特征,与存储等基本概念
  2. 音频数据的dump方法
  3. 重采样
  4. downmix
  5. 存储位深转换
  6. 参考文章列表

位深的装换
声道数的装换

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

重采样就是做插值/抽值算法