我们原始的音频数据就是PCM格式,通过咪头采样会来的数据也是PCM格式,所以有必要多普及一下音频的基础知识。我基础也不好,这是转载了一篇写的比较不错的博客,大家一起学习。

这是一篇转载的文章,转载的链接:PCM数据格式介绍

11.1 音频基础

声音是由物体的振动产生的,这种振动引起了周围空气压强的震荡,我们称这种震荡的函数表现形式为波形。

声音的频率就是周期的倒数,表示声音在1秒钟内的周期数,单位是hz,48Khz,就是表示1秒钟震荡了18k次。

现实生活中我们听到的声音是连续的,但是在计算机中只能处理数字信号,不能处理模拟信号,所以我们只能通过采样的方式,把模拟信号转化成数字信号,

假设 44100HZ 16bit stereo: 每秒钟有 44100 次采样, 采样数据用 16 位(2字节)记录, 双声道(立体声),采集4分钟的数据:44100162460=338688000bit=40M,这个数据就是没经过压缩的音频数据,我们叫做PCM数据。

11.2 PCM介绍

PCM全称Pulse-Code Modulation,翻译一下是脉冲调制编码。

其实大可以不用关心英文释义,之所以这么命名是因为一些历史原因。

在音视频中,PCM是一种用数字表示采样模拟信号的方法。

要将一段音频模拟信号转换为数字表示,包含如下三个步骤:

  1. Sampling(采样)
  2. Quantization(量化)
  3. Coding(编码)

通常,我们可以通过一条曲线在坐标中显示连续的模拟信号,如下图所示:

为了更容易理解PCM,取其中一段来说明。

假设这表示一段一秒的音频模拟信号。

11.2.1 Sampling(采样)

Sampling(采样)处理,实际上就是让采样数据能够完全表示原始信号,且采样数据能够通过重构还原成原始信号的过程,如上图。

将采样后的图拿出来单独解释:

  1. 红色曲线:表示原始信号。
  2. 蓝色垂直线段:表示当前时间点对原始信号的一次采样。采样是一系列基于振幅(amplitude和相同时间间隔的样本。这也是为什么采样过程被称为PAM的原因。
  3. PAM:(Pulse Amplitude Modulation)是一系列离散样本之的结果。

11.2.2 采样率(Sample rate)

每秒钟的样本数也被称之为采样率(Sample rate)。在Sampling图示案例中,采样率为每秒34次。意味着在一秒的时间内,原始信号被采样了34次(也就是蓝色垂直线段的数量)。

通常,采样率的单位用Hz表示,例如1Hz表示每秒钟对原始信号采样一次,1KHz表示每秒钟采样1000次。1MHz表示每秒钟采样1百万次。

根据场景的不同,采样率也有所不同,采样率越高,声音的还原程度越高,质量就越好,同时占用空间会变大。

例如:通话时的采样率为8KHz,常用的媒体采样率有44KHz,对于一些蓝光影片采样率高达1MHz。

11.2.3 Quantization(量化)

原始信号采样后,需要通过量化来描述采样数据的大小。如图:

量化处理过程,就是将时间连续的信号,处理成时间离散的信号,并用实数表示。这些实数将被转换为二进制数用于模拟信号的存储和传输。

在图例中,如果说采样是画垂直线段的话,那么量化就是画水平线,用于衡量每次采样的数字指标。如图:

图中,每条横线表示一个等级(level)。

为了更好的描述量化过程,先来介绍一下bit-depth(位深):用来描述存储数字信号值的bit数。较常用的模拟信号位深有:

8-bit:2^8 = 256 levels,有256个等级可以用于衡量真实的模拟信号。
16-bit:2^16 = 65,536 levels,有65,536个等级可以用于衡量真实的模拟信号。
24-bit:2^24 = 16,666,216 levels,有16,666,216个等级可以用于衡量真实的模拟信号。
显而易见,位深越大,对模拟信号的描述将越真实,对声音的描述更加准确。

在当前例子中,如果用为8-bit位深来描述的话,就如下图所示:

量化的过程就是将一个平顶样本四舍五入到一个可用最近level描述的过程。如图中黑色加粗梯形折线。量化过程中,我们将尽量让每个采样和一个level匹配,因为每个level都是表示一个bit值。

图中,第9次采样的平顶样本对应的level用十进制表示为255,也就是二进制的1111 1111。

11.2.4 Encoding(编码)


在编码这一步,我们会将时间线上的每个sample数据转化为对应的二进制数据。

采样数据经过编码后产生的二进制数据,就是PCM数据。PCM数据可以直接存储在介质上,也可以在经过编解码处理后进行存储或传输。

11.2.5 PCM数据常用量化指标

采样率(Sample rate):每秒钟采样多少次,以Hz为单位。详见:**采样率(Sample rate)**一节。

位深度(Bit-depth):表示用多少个二进制位来描述采样数据,一般为16bit。详见:**Quantization(量化)**一节。

字节序:表示音频PCM数据存储的字节序是大端存储(big-endian)还是小端存储(little-endian),为了数据处理效率的高效,通常为小端存储。

声道数(channel number):当前PCM文件中包含的声道数,是单声道(mono)、双声道(stereo)?此外还有5.1声道等。

声道布局:立体声,低音炮(2.1声道),5.1环绕立体声

采样数据是否有符号(Sign):要表达的就是字面上的意思,需要注意的是,使用有符号的采样数据不能用无符号的方式播放。

以FFmpeg中常见的PCM数据格式s16le为例:它描述的是有符号16位小端PCM数据。

s表示有符号,16表示位深,le表示小端存储。

比特率:没有压缩的音频数据的比特率 = 采样频率 * 采样精度 * 通道数。

码率:压缩后的音频数据的比特率,常见的码率有:

:音频的帧的概念没有视频帧那么清晰,几乎所有视频编码格式都可以简单的认为一帧就是编码后的一张图像。
帧长:
(1)可以指每帧采样数播放的时间,mp3 48K,1152个采样点,每帧则为24ms;aac则是每帧是1024个采样点。攒够一帧的数据才送去做编码。
(2)也可以指压缩后每帧的数据长度,所以需要注意。

每帧持续时间(秒)=每帧采样点数/采样频率(hz)

11.2.6 PCM数据流

如果是单声道的音频文件,采样数据按时间的先后顺序依次存入(有的时候也会采用LRLRLR方式存储,只是另一个声道的数据为0),如果是双声道的话就按照LRLRLR的方式存储,存储的时候还和机器的大小端有关。大端模式如下图所示:

如果我们有一个PCM文件,在代码中,我们可以通过以下方式来读取这样的PCM数据流(Stream)。

FILE *file
int8_t *buffer;
file = fopen("PCM file path");
buffer = malloc(fileSize);
fread(buffer, sizeof(int8_t), fileSize / sizeof(int8_t), file);

伪代码仅仅表示一种加载方式。但在代码中,一开始就将整个文件加载到了内存中,这是不对的。因为我们的音频数据量往往会比较大,一次性全部加载增加了内存负担,而且并不必要。

通常我们会为buffer分配一个固定的长度,例如2048字节,通过循环的方式一边从文件中加载PCM数据,一边播放。

加载好PCM数据后,需要送到音频设备驱动程序中播放,这时我们应该能听到声音。与PCM数数据一同到达驱动程序的通常还有采样率(sample rate),用来告诉驱动每秒钟应该播放多少个采样数据。如果传递给驱动程序的采样率大于PCM实际采样率,那么声音的播放速度将比实际速度快,反之亦然。

存储方式可能还有交错和非交错的方式:

不同的驱动程序对于多声道数据的排列方式可能稍有区别,下面是常用的声道排列地图:

2:  FL FR                       (stereo)
3:  FL FR LFE                   (2.1 surround)
4:  FL FR BL BR                 (quad)
5:  FL FR FC BL BR              (quad + center)
6:  FL FR FC LFE SL SR          (5.1 surround - last two can also be BL BR)
7:  FL FR FC LFE BC SL SR       (6.1 surround)
8:  FL FR FC LFE BL BR SL SR    (7.1 surround)

11.2.7 音量控制

音量的表示实际上就是量化过程中每个采样数据的level值,只要适当的增大或者缩小采样的level就可以达到更改音量的目的。

但需要说明的是,并是不将level值*2就能得到两倍于原声音的音量。

因为如下两个原因:

  1. 数据溢出:
    我们都知道每个采样数据的取值范围是有限制的,例如一个signed 8-bit样本,取值范围为-128~128,值为125时,放大两倍后的值为250,超过了可描述的范围,此时发生了数据溢出。这个时候就需要我们做策略性的裁剪处理,使放大后的值符合当前格式的取值区间。

如下伪代码描述了signed 8-bit格式的声音放大两倍的裁剪处理:

int16_t pcm[1024] = read in some pcm data;
int32_t pcmval;
for (ctr = 0; ctr < 1024; ctr++) {pcmval = pcm[ctr] * 2;if (pcmval < 128 && pcmval > -128) {pcm[ctr] = pcmval} else if (pcmval > 128) {pcm[ctr] = 128;} else if (pcmval < -128) {pcm[ctr] = -128;}
}
  1. 对数描述:
    平时表示声音强度我们都是用分贝(db)作单位的,声学领域中,分贝的定义是声源功率与基准声功率比值的对数乘以10的数值。根据人耳的心理声学模型,人耳对声音感知程度是对数关系,而不是线性关系。人类的听觉反应是基于声音的相对变化而非绝对的变化。对数标度正好能模仿人类耳朵对声音的反应。所以用分贝作单位描述声音强度更符合人类对声音强度的感知。前面我们直接将声音乘以某个值,也就是线性调节,调节音量时会感觉到刚开始音量变化很快,后面调的话好像都没啥变化,使用对数关系调节音量的话声音听起来就会均匀增大。

如下图所示,横轴表示音量调节滑块,纵坐标表示人耳感知到的音量,图中取了两块横轴变化相同的区域,音量滑块滑动变化一样,但是人耳感觉到的音量变化是不一样的,在左侧也就是较安静的地方,感觉到音量变化大,在右侧声音较大区域人耳感觉到的音量变化较小。

这就需要对音量值的乘数系数合理取值。具体如何取值,请参考非常专业的一篇文章:PCM音量控制

11.2.8 采样率调整

采样率的定义为:每秒钟采样次数。而降低增加采样率只需要以固定的频率复制或者丢弃采样数据即可。

如10Hz表示每秒钟采样10次,我们只需要将2*n(n为从0开始的值)处的采样数据舍弃,就可以得到10/2 = 5Hz的采样数据。

音视频学习(十一、PCM格式介绍)相关推荐

  1. Android音视频学习系列(五) — 掌握音频基础知识并使用AudioTrack、OpenSL ES渲染PCM数据

    系列文章 Android音视频学习系列(一) - JNI从入门到精通 Android音视频学习系列(二) - 交叉编译动态库.静态库的入门 Android音视频学习系列(三) - Shell脚本入门 ...

  2. 音视频基础(三)WAV格式与PCM简介

    最近的工作涉及到语音识别相关的研究,因此先简单的梳理一下WAV格式和PCM.以前用c++实现了mp3 player,这个时候再来回顾下代码实现,将WAV的播放 看了下. 什么是PCM 直接上百度百科的 ...

  3. Android音视频学习系列(七) — 从0~1开发一款Android端播放器(支持多协议网络拉流本地文件)

    系列文章 Android音视频学习系列(一) - JNI从入门到精通 Android音视频学习系列(二) - 交叉编译动态库.静态库的入门 Android音视频学习系列(三) - Shell脚本入门 ...

  4. 音视频学习路线及学习资料推荐

    前言 前面的文章学习了一些关于C/C++和NDK相关的知识点,那么现在就来简单的入门一些关于音视频的相关概念,万层高楼平地起,还是从最基础开始. 正文 查看音视频文件的封装格式 这里要明确一个概念,就 ...

  5. Android音视频学习系列(八) — 基于Nginx搭建(rtmp、http)直播服务器

    系列文章 Android音视频学习系列(一) - JNI从入门到精通 Android音视频学习系列(二) - 交叉编译动态库.静态库的入门 Android音视频学习系列(三) - Shell脚本入门 ...

  6. Android音视频学习系列(九) — Android端实现rtmp推流

    系列文章 Android音视频学习系列(一) - JNI从入门到精通 Android音视频学习系列(二) - 交叉编译动态库.静态库的入门 Android音视频学习系列(三) - Shell脚本入门 ...

  7. Android音视频学习系列(六) — 掌握视频基础知识并使用OpenGL ES 2.0渲染YUV数据

    系列文章 Android音视频学习系列(一) - JNI从入门到精通 Android音视频学习系列(二) - 交叉编译动态库.静态库的入门 Android音视频学习系列(三) - Shell脚本入门 ...

  8. Android音视频学习系列(十) — 基于FFmpeg + OpenSL ES实现音频万能播放器

    系列文章 Android音视频学习系列(一) - JNI从入门到精通 Android音视频学习系列(二) - 交叉编译动态库.静态库的入门 Android音视频学习系列(三) - Shell脚本入门 ...

  9. android 键编译,Android 音视频学习系列 (四) 一键编译 32/64 位 FFmpeg 4.2.2

    前言 2020/5/20 增加了硬件解码编译脚本 编译环境 Centos + NDK20b + FFmpeg4.2.2 + Android-21/16 2020/4/26 更新了编译 64 位脚本 编 ...

最新文章

  1. 魅族建立基于M8的Android开源项目
  2. 计算机英语基础课程论文,计算机专业英语课程教学论文
  3. 利用 fdisk进行分区
  4. ES6 异步编程之二:Promise
  5. mysql spj_解决Electron启动出现短暂的白屏 - SegmentFault 思否
  6. [yii2] 实现所有action方法之前执行一段代码或者方法
  7. 【bzoj1911】 Apio2010—特别行动队
  8. 三种查看SqlServer中数据物理pge页的方法
  9. fastapi 响应模型 / 响应状态码 / 表单参数
  10. HTML二刺螈网址导航模板
  11. L2-010. 排座位-PAT团体程序设计天梯赛GPLT(并查集)
  12. 设置windows服务依赖项
  13. 理解JESD204B链路参数 Understanding JESD204B Link Parameters
  14. vba字典重复key_VBA字典技术整理
  15. Django cache redis 最全介绍
  16. js 中文加密解密
  17. python numpy 获得数组的行和列(三种方法)
  18. 关于echarts图中的柱状图添加横线问题
  19. Visio镜像翻转图形
  20. 微课-面向对象程序设计(5课82分钟)

热门文章

  1. 从Devcon5大会看以太坊生态的发展
  2. Go 语言从入门到实战
  3. 安装2021b报错-找不到 ‘D:\MATLAB2021b\bin\win64\hg.dll‘ 所需的资源文件 ‘toolbox/matlab/graphics/hg/hgrc.m‘
  4. hadoop组件概况
  5. 第一章项目学习活动记录表
  6. 为什么delphi编译生成的exe文件这么大
  7. html纵向字幕无缝滚动,html文字无缝滚动代码
  8. matlab画气象要素,Matlab怎样在中国地图背景上显示气象数据?详见内容
  9. 【WebGIS】 矢量栅格可视化网页实践——基于cesium、geoserver、postgis
  10. #、##、__VA_ARGS__和##__VA_ARGS__的作用