Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2579
  • 博文数量: 4
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 30
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-17 01:12
文章分类
文章存档

2021年(4)

我的朋友
最近访客

分类: C/C++

2021-10-12 10:01:17

声音与音频

声音是波,成为声波,而声波的三要素是频率、振幅和波形。频率代表音阶的高低(女高音、男低音)单位赫兹(Hz),人耳能听到的声波范围:频率在20Hz~20kHz之间;振幅代表响度(音量);波形代表音色。而我们音频处理就是对声波采集成数字信号后进行处理。

音频采集与关键名词

音频采集的过程主要是通过设备设置采样率、采样数,将音频信号采集为pcm(Pulse-code modulation,脉冲编码调制)编码的原始数据(无损压缩),然后编码压缩成mp3、aac等封装格式的数据。音频关键知识:

  • 采样率: 一段音频数据中单位时间内(每秒)采样的个数。
  • 位宽: 一次最大能传递数据的宽度,可以理解成放单个采集数据的内存。常有8位和16位,而8位:代表着每个采集点的数据都使用8位(1字节)来存储;16位:代表着每个采集点的数据都使用16位(2字节)来存储。
  • 声道数: 扬声器的个数,单声道、双声道等。每一个声道都占一个位宽。

来一张图来描述一下:



一段时间内的数据大小如何计算?

采样率 x (位宽 / 8) x 声道数 x 时间 = 数据大小(单位:字节)

比如 2分钟的CD(采样率为:44100,位宽:16,声道数:2)的数据大小:44100 x (16 / 8) x 2 x 120 = 20671.875 Byte 约为 20.18M。

PCM数据的基本使用

我们采集到的pcm原始数据要怎么玩?首先得知道怎么这些数据都代表啥意思,然后才能入手处理。

1、pcm数据时如何组成(存储)?

举个例子,分别使用不同的方式存储一段采集数据 0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88 总共8个字节。

  • 8位单声道: 按照数据采集时间顺序存储,即:0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88

  • 8位双声道: L声道-R声道-L声道-R声道形式存储,即:0x11(L) 0x22? 0x33(L) 0x44? ……

  • 16位单声道: 首先从 了解到位宽大于8位时,字节的排序方式是有差别的,描述如下:

    When more than one byte is used to represent a PCM sample, the byte order (big endian vs. little endian) must be known. Due to the widespread use of little-endian Intel CPUs, little-endian PCM tends to be the most common byte orientation.

    当使用一个以上的字节表示PCM样本时,必须知道字节顺序(大端与小端)。由于低端字节Intel CPU的广泛使用,低端字节PCM往往是最常见的字节方向。

    举个栗子:当位宽为16位(2字节)存储一个采集数据时,如:0x12ab,大端和小端分别是:

    big-endian: 0x12 0xab;
    little-endian: 0xab 0x12。

    所以:
    big-endian存储方式:0x1122 0x3344 0x5566 0x7788;
    little-endian存储方式:0x2211 0x4433 0x6655 0x8877。

  • 16位双声道: L声道-R声道-L声道-R声道形式存储:

    big-endian:0x1122(L) 0x3344? 0x5566(L) 0x7788?

    little-endian: 0x2211(L) 0x4433? 0x66550(L) 0x8877?

2、pcm原始数据可以怎么玩?

  • 将little-endian_2_44100_16.pcm采样数据进行切割,只保留后面5秒的数据


点击(此处)折叠或打开

  1. /**
  2.  * 将little-endian_2_44100_16.pcm采样数据进行切割,只保留后面5秒的数据
  3.  * 1、该类型数据5秒有多长?
  4.  * 2、从哪里开始截取?
  5.  */
  6. int cut5second(const char *url){
  7.     FILE *in = fopen(url, "rb+");
  8.     FILE *out = fopen("./output/spit5second.pcm", "wb+");
  9.     long long data5Length = 44100 * (16/8) * 2 * 5;
  10.     struct stat statbuf;
  11.     stat(url,&statbuf);
  12.     long long fileLength = statbuf.st_size;
  13.     long long start = fileLength - data5Length;
  14.     char *simple = (char *)malloc(data5Length);
  15.     //把指针位置移动到start位置开始读取
  16.     fseek(in,start,1);
  17.     //每次从in文件中读取1组data5Length个长度数据的到simple中
  18.     fread(simple,data5Length,1,in);
  19.     fwrite(simple,data5Length,1,out);
  20.     fclose(in);
  21.     fclose(out);
  22.     return 0;
  23. }
分离各声道的数据:把各个声道的采集点数据分开存储。

点击(此处)折叠或打开

  1. /**
  2.  * 将little-endian_2_44100_16.pcm 分离各声道的数据,即把各个声道的采集点数据分开存储。
  3.  */
  4. int separateLR(const char *url){
  5.     FILE *in = fopen(url, "rb+");
  6.     FILE *outL = fopen("./output/l.pcm", "wb+");
  7.     FILE *outR = fopen("./output/r.pcm", "wb+");
  8.     int simpleLength = 16 / 8 * 2;
  9.     char *simple = (char *)malloc(simpleLength);
  10.     while (1){
  11.         //每次从in文件中读取1组4个长度数据的到simple中
  12.         fread(simple,4,1,in);
  13.         if(feof(in)){
  14.             break;
  15.         }
  16.         //l(0声道):1-2
  17.         fwrite(simple,2,1,outL);
  18.         //r(1声道):3-4
  19.         fwrite(simple+2,2,1,outR);
  20.     }
  21.     fclose(in);
  22.     fclose(outL);
  23.     fclose(outR);
  24.     return 0;
  25. }
音量调节:把每个采集点数据的值 x 调节比例。注意:需要注意的是可调节范围,如:8位有无符号时最大是多少。

点击(此处)折叠或打开

  1. /**
  2.  * 将little-endian_2_44100_16.pcm 调节音量 比例
  3.  * 1、little-endian排序的值是如何排序的?真正的值是多少?
  4.  * 2、little-endian转成真正的值之后再进行计算,得到的结果再反转little-endian。
  5.  * 如:原始pcm数据:0xaa 0x01(左声道采样点数据),当scale=2:
  6.  * -> 值:0x01aa * 2 = 0x0354
  7.  * -> 转回little-endian再进行存储:0x5403(缩放后的值)
  8.  */
  9. int volumeAdjustment(const char *url, float scale){
  10.     FILE *in = fopen(url, "rb+");
  11.     FILE *out = fopen("./output/volume_adjustment.pcm", "wb+");
  12.     char *simple = (char *)malloc(4);
  13.     while (1){
  14.         //每次从in文件中读取1组4个长度数据的到simple中
  15.         fread(simple, 4, 1, in);
  16.         if(feof(in)){
  17.             break;
  18.         }
  19.         short *simple8bitTemp = (short *)malloc(2);

  20.         //l(0声道)
  21.         simple8bitTemp[0] = (simple[0] + (simple[1] << 8)) * scale;
  22.         //r(1声道)
  23.         simple8bitTemp[1] = (simple[2] + (simple[3] << 8)) * scale;

  24.         simple[0] = simple8bitTemp[0] & 0x00FF;
  25.         simple[1] = simple8bitTemp[0] >> 8;

  26.         simple[2] = simple8bitTemp[1] & 0x00FF;
  27.         simple[3] = simple8bitTemp[1] >> 8;
  28.         for(int i=0; i<4; i++){
  29.             printf("simple[%d]=%d\n",i,simple[i]);
  30.         }
  31.         fwrite(simple, 4, 1, out);
  32.     }
  33.     fclose(in);
  34.     fclose(out);
  35.     return 0;
  36. }
  • 播放速度:按照比例丢弃(或插入0)采集点的数据即可。

  • Android终端音频采样介绍

    • 1、关于采集的主要api介绍

    点击(此处)折叠或打开

    1. /**
    2. * @param audioSource 音频来源{@link MediaRecorder.AudioSource};如指定麦克风:MediaRecorder.AudioSource.MIC
    3. * @param sampleRateInHz 采样率{@link AudioFormat#SAMPLE_RATE_UNSPECIFIED},单位Hz;安卓支持所有的设备是:44100Hz
    4. * @param channelConfig 声道数{@link AudioFormat#CHANNEL_IN_MONO};
    5. * @param audioFormat 位宽{@link AudioFormat#ENCODING_PCM_8BIT}
    6. * @param bufferSizeInBytes 采集期间缓存区的大小
    7. */
    8. public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,
    9.             int bufferSizeInBytes)
    10.   
    11. //获取最小缓存区,参数跟AudioRecord保持一致
    12. int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) {}
    2、实现采集的伪代码

    点击(此处)折叠或打开

    1. //1、申请权限

    2. //2、获取最小缓存大小(根据api介绍,应该取要比预期大的缓冲区大小),这个大小其实也可以取①和②计算得来的大小
    3. int minBufferSize = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT) * 2;

    4. //3、初始化AudioRecord对象
    5. AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, 44100, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT, minBufferSize);

    6. //4、开始在子线程中进行采集数据
    7. new Thread(new Runnable() {
    8.   @Override
    9.   public void run() {
    10.     audioRecord.startRecording();
    11.     while(isRunning){
    12.       byte[] bytes = new byte[minBufferSize];
    13.         int len = audioRecord.read(bytes, 0, bytes.length);
    14.       //这里就可以把数据直接写入到sdcard了,如:xxx.pcm;输出排序方式为:little endian。
    15.     }
    16.      //5、停止录音机
    17.         audioRecord.stop();
    18.   }
    19. }).start();


    20. //6、最后释放资源
    21. audioRecord.release();

    播放pcm原始数据

    • ffmpeg:

    ffplay -f s16le -sample_rate 44100 -channels 2 -i xxx.pcm

    • 其他:

    Adobe Audition





    阅读(273) | 评论(0) | 转发(0) |
    给主人留下些什么吧!~~