一 什么是音频的采样率和采样大小
自然界中的声音非常复杂,波形极其复杂,通常我们采用的是脉冲代码调制编码。即PCM编码。PCM通过抽样、量化、编码三个步骤将连续变化的模拟信号转换为数字编码。
抽样:在音频采集中叫做采样率。
由于声音其实是一种能量波,因此也有频率和振幅的特征,频率对应于时间轴线,振幅对应于电平轴线。波是无限光滑的,弦线可以看成由无数点组成,由于存储空间是相对有限的,数字编码过程中,必须对弦线的点进行采样。采样的过程就是抽取某点的频率值,很显然,在一秒中内抽取的点越多,获取得频率信息更丰富,为了复原波形,一次振动中,必须有2个点的采样,人耳能够感觉到的最高频率为20kHz,因此要满足人耳的听觉要求,则需要至少每秒进行40k次采样,用40kHz表达,这个40kHz就是采样率。我们常见的CD,采样率为44.1kHz。
量化:我们这里的采样大小就是量化的过程,将该频率的能量值并量化,用于表示信号强度。量化电平数为 2的整数次幂,我们常见的CD位16bit的采样大小,即2的16次方。
编码:
根据采样率和采样大小可以得知,相对自然界的信号,音频编码最多只能做到无限接近,至少目前的技术只能这样了,相对自然界的信号,任何数字音频编码方案都是有损的,因为无法完全还原。在计算机应用中,能够达到最高保真水平的就是PCM编码,被广泛用于素材保存及音乐欣赏,CD、DVD以及我们常见的WAV文件中均有应用。因此,PCM约定俗成了无损编码,因为PCM代表了数字音频中最佳的保真水准,并不意味着PCM就能够确保信号绝对保真,PCM也只能做到最大程度的无限接近。我们而习惯性的把MP3列入有损音频编码范畴,是相对PCM编码的。强调编码的相对性的有损和无损,是为了告诉大家,要做到真正的无损是困难的,就像用数字去表达圆周率,不管精度多高,也只是无限接近,而不是真正等于圆周率的值
为什么要使用音频压缩技术
要算一个PCM音频流的码率是一件很轻松的事情,采样率值×采样大小值×声道数bps。一个采样率为44.1KHz,采样大小为16bit,双声道的PCM编码的WAV文件,它的数据速率则为 44.1K×16×2 =1411.2 Kbps。我们常说128K的MP3,对应的WAV的参数,就是这个1411.2 Kbps,这个参数也被称为数据带宽,它和ADSL中的带宽是一个概念。将码率除以8,就可以得到这个WAV的数据速率,即176.4KB/s。这表示存储一秒钟采样率为44.1KHz,采样大小为16bit,双声道的PCM编码的音频信号,需要176.4KB的空间,1分钟则约为10.34M,这对大部分用户是不可接受的,尤其是喜欢在电脑上听音乐的朋友,要降低磁盘占用,只有2种方法,降低采样指标或者压缩。降低指标是不可取的,因此专家们研发了各种压缩方案。由于用途和针对的目标市场不一样,各种音频压缩编码所达到的音质和压缩比都不一样,在后面的文章中我们都会一一提到。有一点是可以肯定的,他们都压缩过。
频率与采样率的关系
采样率表示了每秒对原始信号采样的次数,我们常见到的音频文件采样率多为44.1KHz,这意味着什么呢?假设我们有2段正弦波信号,分别为20Hz和20KHz,长度均为一秒钟,以对应我们能听到的最低频和最高频,分别对这两段信号进行 40KHz的采样,我们可以得到一个什么样的结果呢?结果是:20Hz的信号每次振动被采样了40K/20=2000次,而20K的信号每次振动只有2次采样。显然,在相同的采样率下,记录低频的信息远比高频的详细。这也是为什么有些音响发烧友指责CD有数码声不够真实的原因,CD的44.1KHz采样也无法保证高频信号被较好记录。要较好的记录高频信号,看来需要更高的采样率,于是有些朋友在捕捉CD音轨的时候使用48KHz的采样率,这是不可取的!这其实对音质没有任何好处,对抓轨软件来说,保持和CD提供的44.1KHz一样的采样率才是最佳音质的保证之一,而不是去提高它。较高的采样率只有相对模拟信号的时候才有用,如果被采样的信号是数字的,请不要去尝试提高采样率。
流特征
随着网络的发展,人们对在线收听音乐提出了要求,因此也要求音频文件能够一边读一边播放,而不需要把这个文件全部读出后然后回放,这样就可以做到不用下载就可以实现收听了。也可以做到一边编码一边播放,正是这种特征,可以实现在线的直播,架设自己的数字广播电台成为了现实。
二 android中AudioRecord采集音频的参数说明
在android中采集音频的api是android.media.AudioRecord类
其中构造器的几个参数就是标准的声音采集参数
以下是参数的含义解释
public AudioRecord (int audioSource, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes)
Since: API Level 3
Class constructor.
Parameters
audioSource
|
the recording source. See MediaRecorder.AudioSource for recording source definitions.
音频源:指的是从哪里采集音频。这里我们当然是从麦克风采集音频,所以此参数的值为MIC
|
sampleRateInHz
|
the sample rate expressed in Hertz. Examples of rates are (but not limited to) 44100, 22050 and 11025.
采样率:音频的采样频率,每秒钟能够采样的次数,采样率越高,音质越高。给出的实例是44100、22050、11025但不限于这几个参数。例如要采集低质量的音频就可以使用4000、8000等低采样率。
|
channelConfig
|
describes the configuration of the audio channels. SeeCHANNEL_IN_MONO and CHANNEL_IN_STEREO
声道设置:android支持双声道立体声和单声道。MONO单声道,STEREO立体声
|
audioFormat
|
the format in which the audio data is represented. SeeENCODING_PCM_16BIT and ENCODING_PCM_8BIT
编码制式和采样大小:采集来的数据当然使用PCM编码(脉冲代码调制编码,即PCM编码。PCM通过抽样、量化、编码三个步骤将连续变化的模拟信号转换为数字编码。)
android支持的采样大小16bit 或者8bit。当然采样大小越大,那么信息量越多,音质也越高,现在主流的采样大小都是16bit,在低质量的语音传输的时候8bit 足够了。
|
bufferSizeInBytes
|
the total size (in bytes) of the buffer where audio data is written to during the recording. New audio data can be read from this buffer in smaller chunks than this size. SeegetMinBufferSize(int,
int, int) to determine the minimum required buffer size for the successful creation of an AudioRecord instance. Using values smaller than getMinBufferSize() will result in an initialization failure.
采集数据需要的缓冲区的大小,如果不知道最小需要的大小可以在getMinBufferSize()查看。
|
采集到的数据保存在一个byteBuffer中,可以使用流将其读出。亦可保存成为文件的形式
三 Android 使用AudioRecord录音相关和音频文件的封装
在Android中录音可以用MediaRecord录音,操作比较简单。但是不够专业,就是不能对音频进行处理。如果要进行音频的实时的处理或者音频的一些封装
就可以用AudioRecord来进行录音了。
这里给出一段代码。实现了AudioRecord的录音和WAV格式音频的封装。
用AudioTrack和AudioTrack类可以进行边录边播,可以参考:http://blog.sina.com.cn/s/blog_6309e1ed0100j1rw.html
我们这里的代码没有播放。但是有封装和详解,如下:
-
package com.ppmeet;
-
-
import java.io.File;
-
import java.io.FileInputStream;
-
import java.io.FileNotFoundException;
-
import java.io.FileOutputStream;
-
import java.io.IOException;
-
import android.app.Activity;
-
import android.graphics.PixelFormat;
-
import android.media.AudioFormat;
-
import android.media.AudioRecord;
-
import android.media.MediaRecorder;
-
import android.os.Bundle;
-
import android.view.View;
-
import android.view.View.OnClickListener;
-
import android.view.Window;
-
import android.view.WindowManager;
-
import android.widget.Button;
-
-
-
-
-
-
-
-
-
-
public class TestAudioRecord extends Activity {
-
-
private int audioSource = MediaRecorder.AudioSource.MIC;
-
-
private static int sampleRateInHz = 44100;
-
-
private static int channelConfig = AudioFormat.CHANNEL_IN_STEREO;
-
-
private static int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
-
-
private int bufferSizeInBytes = 0;
-
private Button Start;
-
private Button Stop;
-
private AudioRecord audioRecord;
-
private boolean isRecord = false;
-
-
private static final String AudioName = "/sdcard/love.raw";//不推荐这么写,可以用Enviroment.
-
-
private static final String NewAudioName = "/sdcard/new.wav";
-
-
public void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
getWindow().setFormat(PixelFormat.TRANSLUCENT);
-
requestWindowFeature(Window.FEATURE_NO_TITLE);
-
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
-
WindowManager.LayoutParams.FLAG_FULLSCREEN);
-
-
setContentView(R.layout.main);
-
init();
-
}
-
-
private void init() {
-
Start = (Button) this.findViewById(R.id.start);
-
Stop = (Button) this.findViewById(R.id.stop);
-
Start.setOnClickListener(new TestAudioListener());
-
Stop.setOnClickListener(new TestAudioListener());
-
creatAudioRecord();
-
}
-
-
private void creatAudioRecord() {
-
-
bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz,
-
channelConfig, audioFormat);
-
-
audioRecord = new AudioRecord(audioSource, sampleRateInHz,
-
channelConfig, audioFormat, bufferSizeInBytes);
-
}
-
-
class TestAudioListener implements OnClickListener {
-
-
@Override
-
public void onClick(View v) {
-
if (v == Start) {
-
startRecord();
-
}
-
if (v == Stop) {
-
stopRecord();
-
}
-
-
}
-
-
}
-
-
private void startRecord() {
-
audioRecord.startRecording();
-
-
isRecord = true;
-
-
new Thread(new AudioRecordThread()).start();
-
}
-
-
private void stopRecord() {
-
close();
-
}
-
-
private void close() {
-
if (audioRecord != null) {
-
System.out.println("stopRecord");
-
isRecord = false;
-
audioRecord.stop();
-
audioRecord.release();
-
audioRecord = null;
-
}
-
}
-
-
class AudioRecordThread implements Runnable {
-
@Override
-
public void run() {
-
writeDateTOFile();
-
copyWaveFile(AudioName, NewAudioName);
-
}
-
}
-
-
-
-
-
-
-
private void writeDateTOFile() {
-
-
byte[] audiodata = new byte[bufferSizeInBytes];
-
FileOutputStream fos = null;
-
int readsize = 0;
-
try {
-
File file = new File(AudioName);
-
if (file.exists()) {
-
file.delete();
-
}
-
fos = new FileOutputStream(file);
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
while (isRecord == true) {
-
readsize = audioRecord.read(audiodata, 0, bufferSizeInBytes);
-
if (AudioRecord.ERROR_INVALID_OPERATION != readsize) {
-
try {
-
fos.write(audiodata);
-
} catch (IOException e) {
-
e.printStackTrace();
-
}
-
}
-
}
-
try {
-
fos.close();
-
} catch (IOException e) {
-
e.printStackTrace();
-
}
-
}
-
-
-
private void copyWaveFile(String inFilename, String outFilename) {
-
FileInputStream in = null;
-
FileOutputStream out = null;
-
long totalAudioLen = 0;
-
long totalDataLen = totalAudioLen + 36;
-
long longSampleRate = sampleRateInHz;
-
int channels = 2;
-
long byteRate = 16 * sampleRateInHz * channels / 8;
-
byte[] data = new byte[bufferSizeInBytes];
-
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 (FileNotFoundException e) {
-
e.printStackTrace();
-
} catch (IOException e) {
-
e.printStackTrace();
-
}
-
}
-
-
-
-
-
-
-
-
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';
-
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';
-
header[9] = 'A';
-
header[10] = 'V';
-
header[11] = 'E';
-
header[12] = 'f';
-
header[13] = 'm';
-
header[14] = 't';
-
header[15] = ' ';
-
header[16] = 16;
-
header[17] = 0;
-
header[18] = 0;
-
header[19] = 0;
-
header[20] = 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);
-
header[33] = 0;
-
header[34] = 16;
-
header[35] = 0;
-
header[36] = 'd';
-
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);
-
}
-
-
@Override
-
protected void onDestroy() {
-
close();
-
super.onDestroy();
-
}
-
}
|
声音的数字信息可以转化成波形图:貌似是很简单的一些算大
byte[] buffer = new byte[bs]; isRun = true; while (isRun) {
int r = ar.read(buffer, 0, bs);
int v = 0;
// 将 buffer 内容取出,进行平方和运算
for (int i = 0; i < buffer.length; i++) {
// 这里没有做运算的优化,为了更加清晰的展示代码
v += buffer[i] * buffer[i];
}
// 平方和除以数据总长度,得到音量大小。可以获取白噪声值,然后对实际采样进行标准化。
// 如果想利用这个数值进行操作,建议用 sendMessage 将其抛出,在 Handler 里进行处理。
Log.d("spl", String.valueOf(v / (float) r));
原帖地址:
点击打开