音视频系列--音频基本理论

一、何为声音


中学物理中我们知道,声音是物体振动产生的声波。声音通过介质(空气、固体、液体)传入到人耳中,带动听小骨振动,经过一系列的神经信号传递后,被人所感知。

声音是一种波。物体振动时会使介质(如空气)产生疏密变化,从而形成疏密相见的纵波。

既然声音是波,那么我们就可以用图的形式来表示它。

给定空间中某一点,该点的空气疏密随时间的变化如下:

img波形图
下图是一个正弦波,其周期为0.002s,频率为500HZ。

img

频率(音调):声音1秒内周期性变化的次数

人耳的听觉范围在20Hz-20kHz。 低频的声音沉闷厚重,高频的声音尖锐刺耳。 高于 20kHz的声音为超声波。

振幅(响度):声音的大小

有的时候,我们用分贝(dB)形容声音大小。值得注意的是,dB是一个比值,是一个数值,没有任何单位标注。(功率强度之比的对数的10倍)

img
音调:声音频率的高低叫做音调(Pitch),是声音的三个主要的主观属性,即音量(响度)、音调、音色(也称音品) 之一。表示人的听觉分辨一个声音的调子高低的程度。音调主要由声音的频率决定,同时也与声音强度有关

同等条件下,波长(频率)是决定音调高低的因素。

音量:人主观上感觉声音的大小(俗称音量),由“振幅”(amplitude)和人离声源的距离决定,振幅越大响度越大,人和声源的距离越小,响度越大。(单位:分贝dB)

音色:又称声音的品质,波形决定了声音的音色。声音因不同物体材料的特性而具有不同特性,音色本身是一种抽象的东西,但波形是把这个抽象直观的表现。音色不同,波形则不同。典型的音色波形有方波,锯齿波,正弦波,脉冲波等。不同的音色,通过波形,完全可以分辨的。

图片

同等条件下,振幅是决定音量高低的因素。

图片

同等条件下,波纹是决定音色因素。

图片
通过上面简单的分析,我们已经知道声音的音量实际上就是由声波的振幅决定的,我们需要调整声波的振幅。播放一个视频,需要经历下面几步:

  • 输入视频url
  • 确定视频的封装格式
  • 开始解封装
  • 识别视频的轨道数据
  • 分离轨道数据,音频轨道、视频轨道
  • 解码视频数据为原始数据,解码音频数据为原始数据
  • 做好音视频同步
  • 渲染视频原始数据,播放音频原始数据

上面加黑标红的部分就是我们改变声音振幅的地方,只有将声音数据解码为原始数据,我们加工原始数据的音频流,然后送到AudioTrack或者OpenSL ES内部播放即可。

二、音频合成与分解


波的合成
QQ20150403-2

波的分解

File:Fourier series and transform.gif

三、声音采集和存储


为了将模拟信号数字化,主要分为三个部分,采样,量化和编码

采样:就是在时间轴上对信号进行数字化。根据奈奎斯特定律(也称为采样定理),按比声音最高频率2倍以上的频率对声音进行采样(也称为AD转换),对于高频率的音频信号,其频率范围(人耳能够听到的频率范围)是20Hz~20kHz,所以采样频率一般为44.1kHz,这样就可以保证采样声音达到20kHz也能被数字化,从而使得经过数字化处理后,人耳听到声音质量不会被降低。而所谓的44.1kHz就是代表1秒会采样44100次。

量化:量化是指在幅度轴上对信号进行数字化,比如用16比特的二进制信号来表示声音的一个采样,而16比特(一个short)所表示的范围是[-32768, 32767],共有65536个可能取值,因此最终模拟的音频信号在幅度上也分为了65536层。

编码:所谓编码,就是按照一定的格式记录采样和量化后的数字数据,比如顺序或压缩存储,等等。

四、PCM存储


4.1、什么是PCM

PCM(Pulse Code Modulation,脉冲编码调制)音频数据是未经压缩的音频采样数据裸流,它是由模拟信号经过采样、量化、编码转换成的标准数字音频数据。

描述PCM数据的6个参数:

1、Sample Rate : 采样频率。8kHz(电话)、44.1kHz(CD)、48kHz(DVD)。
2、Sample Size : 量化位数。通常该值为16bit。量化位数是音频文件的另一个参数。量化位数越大,声音的质量越高。常用的量化位数有8位、16位和32位。量化位数指用几位二进制数来存储采样获得的数据。量化位数为8,即指用8位二进制数来存储数据,如00010111。
3、Number of Channels : 通道个数。常见的音频有立体声(stereo)和单声道(mono)两种类型,立体声包含左声道和右声道。另外还有环绕立体声等其它不太常用的类型。
4、Sign : 表示样本数据是否是有符号位,比如用一字节表示的样本数据,有符号的话表示范围为-128 ~ 127,无符号是0 ~ 255。
5、Byte Ordering : 字节序。字节序是little-endian还是big-endian。通常均为little-endian。字节序说明见第4节。
6、Integer Or Floating Point : 整形或浮点型。大多数格式的PCM样本数据使用整形表示,而在一些对精度要求高的应用方面,使用浮点类型表示PCM样本数据。

以CD的音质为例:量化格式(有的地点描述为位深度)为16比特(2字节),采样率为44100,声道数为2,这些信息就表述了CD的音质。而对于声音格式,还有一个概念用来描述它的大小,称为数据比特率,即1s内时间内的比特数目,它用于衡量音频数据单位时间内的容量大小。而对于CD音质的数据,比特率为多少了?计算如下:

44100 * 16 * 2 = 1378.125kbps

那么在1分钟内,这类CD音质的数据需要占据多大的存储空间?计算如下:

1378.125 * 60 / 8 / 1024 = 10.09MB

推荐的PCM数据播放工具:

1、ffplay, 使用示例如下:

//播放格式为f32le,单声道,采样频率48000Hz的PCM数据
ffplay -f f32le -ac 1 -ar 48000 pcm_audio

2、Audacity:一款免费开源的跨平台音频处理软件。

4.2、PCM数据格式

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

img

4.3、声道数

声道分为单声道与双声道。

单声道即为左右耳听到的声音相同。

双声道两耳听到的信息不同。相同的声音时间、采样频率和比特率的情况下,双声道文件的存储空间是单声道的两倍。但其会给人空间感,游戏和电影中常采用双声道,可达到“听声辨位”的效果。

示例声音如下:

img

4.4、PCM转WAV

public class PcmToWavUtil {
    private int mBufferSize;  //缓存的音频大小
    private int mSampleRate = 8000;// 8000|16000
    private int mChannelConfig = AudioFormat.CHANNEL_IN_STEREO;   //立体声
    private int mChannelCount = 2;
    private int mEncoding = AudioFormat.ENCODING_PCM_16BIT;

    public PcmToWavUtil() {
        this.mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannelConfig, mEncoding);
    }

    /**
     * @param sampleRate sample rate、采样率
     * @param channelConfig    channel、声道
     * @param encoding   Audio data format、音频格式
     */
    public PcmToWavUtil(int sampleRate, int channelConfig, int channelCount, int encoding) {
        this.mSampleRate = sampleRate;
        this.mChannelConfig = channelConfig;
        this.mChannelCount = channelCount;
        this.mEncoding = encoding;
        this.mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannelConfig, mEncoding);
    }

    /**
     * pcm文件转wav文件
     *
     * @param inFilename  源文件路径
     * @param outFilename 目标文件路径
     */
    public void pcmToWav(String inFilename, String outFilename) {
        FileInputStream in;
        FileOutputStream out;
        long totalAudioLen;
        long totalDataLen;
        long longSampleRate = mSampleRate;
        int channels = mChannelCount;
        long byteRate = 16 * mSampleRate * channels / 8;
        byte[] data = new byte[mBufferSize];
        try {
            in = new FileInputStream(inFilename);
            out = new FileOutputStream(outFilename);
            totalAudioLen = in.getChannel().size();
            totalDataLen = totalAudioLen + 36;

            writeWaveFileHeader(out, totalAudioLen, totalDataLen,
                    longSampleRate, channels, byteRate);
            while (in.read(data) != -1) {
                out.write(data);
            }
            in.close();
            out.close();
        }  catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 加入wav文件头
     */
    private void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
                                     long totalDataLen, long longSampleRate, int channels, long byteRate)
            throws IOException {
        byte[] header = new byte[44];
        header[0] = 'R'; // RIFF/WAVE header
        header[1] = 'I';
        header[2] = 'F';
        header[3] = 'F';
        header[4] = (byte) (totalDataLen & 0xff);
        header[5] = (byte) ((totalDataLen >> 8) & 0xff);
        header[6] = (byte) ((totalDataLen >> 16) & 0xff);
        header[7] = (byte) ((totalDataLen >> 24) & 0xff);
        header[8] = 'W';  //WAVE
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        header[12] = 'f'; // 'fmt ' chunk
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';
        header[16] = 16;  // 4 bytes: size of 'fmt ' chunk
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        header[20] = 1;   // format = 1
        header[21] = 0;
        header[22] = (byte) channels;
        header[23] = 0;
        header[24] = (byte) (longSampleRate & 0xff);
        header[25] = (byte) ((longSampleRate >> 8) & 0xff);
        header[26] = (byte) ((longSampleRate >> 16) & 0xff);
        header[27] = (byte) ((longSampleRate >> 24) & 0xff);
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
        header[32] = (byte) (2 * 16 / 8); // block align
        header[33] = 0;
        header[34] = 16;  // bits per sample
        header[35] = 0;
        header[36] = 'd'; //data
        header[37] = 'a';
        header[38] = 't';
        header[39] = 'a';
        header[40] = (byte) (totalAudioLen & 0xff);
        header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
        header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
        header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
        out.write(header, 0, 44);
    }
}

五、音频编码


几种常用的压缩编码格式

1、WAV编码

WAV编码就是在PCM(Pulse Code Modulation,脉冲编码调制)前面加了44字节,分别用来描述PCM的采样率、声道数、数据格式等信息。

特点:音质非常好,大量软件都支持
适用场景:多媒体开发的中间文件、保存音乐和音效素材

2、MP3编码

MP3具有不错的压缩比,使用LAME编码(MP3编码格式的一种实现)的中高码率的MP3文件,听感上非常接近源WAV文件,当然在不同的应用场景下,应该调整合适的参数以达到最好的效果。

特点:音质在128Kbit/s以上表现还不错,压缩比比较高,大量软件和硬件都支持,兼容性好。
适用场景:高比特率下对兼容性有要求的音乐。

3、AAC编码

AAC是新一代的音频有损压缩技术,它通过一些附加的编码技术(比如PS,SBR),衍生出了LC-AAC,HE-AAC,HE-AAC v2三种主要的编码格式。

特点:在小于128Kbit/s的码率下表现优异,并且多用于视频中的音频编码。
适用场合:128Kbit/s以下的音频编码,多用于视频中音频轨的编码。

4、Ogg编码

Ogg是一种非常有潜力的编码,在各种码率下都有比较优秀的表现,尤其是在低码率场景下。Ogg除了音质好之外,还是完全免费的,这为Ogg获得更多的支持打好了基础。Ogg有着非常出色的算法,可以用更小的码率达到更好的音质,128kbit/s的Ogg比192Kbit/s甚至更高码率的MP3还要出色。但目前还没有媒体服务软件的支持,因此基于Ogg的数字广播还无法实现。Ogg目前受支持的情况还不够好,无论是软件上还是硬件上的支持,都无法和MP3相提并论。

特点:可以用比MP3更小的码率实现比MP3更好的音质,高中低码率下均有良好的表现,兼容性不够好,流媒体特性不支持。
适用场合:语音聊天的音频消息场景。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值