声音的模拟数字转换

声波从模拟信号转化为数字信号,要经过采样、量化、编码三个步骤

采样

采样就是在时间轴上对信号离散化,如下图所示

3b2bb5860d98

image

人耳只能听到频率20Hz~20kHz的声音,根据耐奎斯特采样理论,采样频率必须是信号最高频率的两倍,这样才能保证质量不失真,所以采样率一般为44.1kHz,即每秒采样44100个点,这样能保证每个声波至少有两个采样点。

量化

量化是指在幅度轴上对信号离散化,一般用16bit或8bit来表示声音的一个采样,以16bit为例,取值范围为[-32768,32767],因此在幅度轴上分了65536层,如下图所示

3b2bb5860d98

image

编码

编码就是按照一定的格式记录采样和量化后的数字数据,比如顺序存储(PCM)或压缩存储(AAC、MP3)等等。

PCM

PCM:脉冲编码调制(Pulse Code Modulation)数据,是未经压缩的原始音频数据。PCM数据一 般有以下几个指标:采样率(sampleRate)、量化格式(sampleFormat)、声道数(channel)。

以CD的音质为例:量化格式为16比特(2字节),采样率为44100,声道数为2,如果采样PCM格式存储,那么存储一个1分钟的音频要占用44100 * 2 * 2 * 60=10.09MB。

声音格式还有一个重要指标:比特率,即1秒时间内的比特数,一般比特率越高音质越好。还是以上面的CD音质为例,比特率为: 44100 * 16 * 2 = 1378.125kbps

示例

下面我们通过一个例子来加深理解:

AudioRecord录制PCM

public class PCMConfig {

public static final int SAMPLE_RATE_IN_HZ = 44100;//采样率44.1kHz

public static final int CHANNEL = AudioFormat.CHANNEL_IN_STEREO;//双声道(左右声道)

public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;//每个采样点16bit,即2字节

}

public class PCMRecorder {

private AudioRecord mAudioRecord;

private int mBufferSize;

public PCMRecorder() {

mBufferSize = AudioRecord.getMinBufferSize(PCMConfig.SAMPLE_RATE_IN_HZ,

PCMConfig.CHANNEL,

PCMConfig.AUDIO_FORMAT);

mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,

PCMConfig.SAMPLE_RATE_IN_HZ,

PCMConfig.CHANNEL,

PCMConfig.AUDIO_FORMAT,

mBufferSize);

}

public void startRecord(String outputPath) {

fos = new FileOutputStream(outputPath);

mAudioRecord.startRecording();

byte[] byteBuffer = new byte[mBufferSize];

while (mRecording) {

int len = mAudioRecord.read(byteBuffer, 0, byteBuffer.length);

fos.write(byteBuffer, 0, len);

}

fos.flush();

mAudioRecord.stop();

}

}

以上代码,我们先设置AudioRecorder的采样率、声道、量化指标,然后通过AudioRecorder的read方法循环将麦克风采集的音频数据写入到文件里,直到用户主动结束录制,这样我们就得到一个pcm原始音频文件。下面我们看一下如何查看和播放pcm文件

查看和播放PCM

PC上有专门的软件浏览pcm文件,windows上有收费的专业音频编辑软件Adobe Audition,免费开源的音频编辑软件Audacity。我们用Audacity打开刚才录制的pcm文件看一下,对音频数据有个直观的认识。

3b2bb5860d98

image

可以看到这段音频的采样率为44100Hz,两个音轨,每个采样点32bit(16bit * 2),这跟我们的配置是一致的。

我们还可以用AudioTrack直接在Android设备上直接播放pcm

public class PCMPlayer {

public void play(File pcmFile) {

AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,

PCMConfig.SAMPLE_RATE_IN_HZ,

PCMConfig.CHANNEL,

PCMConfig.AUDIO_FORMAT,

(int) pcmFile.length(),

AudioTrack.MODE_STATIC);

FileInputStream fis = new FileInputStream(pcmFile);

byte[] buffer = new byte[1024 * 1024];

int len = 0;

while ( (len = fis.read(buffer)) != -1) {

audioTrack.write(buffer, 0, len);

}

audioTrack.play();

}

}

将PCM音量减半

public static boolean halfVolume(File pcmFile) {

byte[] audioData = FileUtils.toByteArray(pcmFile);

FileOutputStream fos = null;

try {

File output = new File(pcmFile.getParentFile(), pcmFile.getName() + ".half");

fos = new FileOutputStream(output);

int length = audioData.length;

for (int i = 0; i < length - 2; i += 2) {

// 每次取2个字节表示音量

int volume = Util.toUnsignedInt16(audioData[i], audioData[i + 1]);

// 音量减半

volume = volume / 2;

byte[] bytes = Util.unsignedInt16ToByteArray(volume);

audioData[i] = bytes[0];

audioData[i + 1] = bytes[1];

}

fos.write(audioData);

fos.flush();

return true;

} catch (IOException e) {

e.printStackTrace();

} finally {

IOUtils.closeQuietly(fos);

}

return false;

}

效果如下图

3b2bb5860d98

image

分离PCM左右声道

public static boolean splitChannel(File pcmFile) {

byte[] audioData = FileUtils.toByteArray(pcmFile);

if (audioData == null) {

return false;

}

FileOutputStream fosLeft = null;

FileOutputStream fosRight = null;

try {

File leftOutput = new File(pcmFile.getParentFile(), pcmFile.getName() + ".left");

fosLeft = new FileOutputStream(leftOutput);

File rightOutput = new File(pcmFile.getParentFile(), pcmFile.getName() + ".right");

fosRight =new FileOutputStream(rightOutput);

ByteArrayOutputStream bosLeft = new ByteArrayOutputStream((int) (pcmFile.length() / 2));

ByteArrayOutputStream bosRight = new ByteArrayOutputStream((int) (pcmFile.length() / 2));

int len = audioData.length;

for (int i = 0; i < len - 4; i += 4) {

// 写入左声道采样点

bosLeft.write(audioData, i, 2);

// 写入右声道采样点

bosRight.write(audioData, i + 2, 2);

}

fosLeft.write(bosLeft.toByteArray());

fosRight.write(bosRight.toByteArray());

return true;

} catch (IOException e) {

e.printStackTrace();

} finally {

IOUtils.closeQuietly(fosLeft);

IOUtils.closeQuietly(fosRight);

}

return false;

}

从上面代码可以知道左右声道数据是按依次排序的

| 16bit | 16bit | 16bit | 16bit |……

|左声道|右声道|左声道|右声道|……

3b2bb5860d98

image

PCM速度提升一倍

public static boolean doubleSpeed(File pcmFile) {

FileInputStream fis = null;

FileOutputStream fos = null;

try {

fis = new FileInputStream(pcmFile);

File output = new File(pcmFile.getParentFile(), pcmFile.getName() + ".speed");

fos = new FileOutputStream(output);

ByteArrayOutputStream bos = new ByteArrayOutputStream((int) pcmFile.length() / 2);

byte[] buffer = new byte[1024 * 1024];

int len = 0;

while ( (len = fis.read(buffer)) != -1) {

for (int i = 0; i < len - 4; i += 4) {

int index = i / 4;

//只取时间轴偶数位的采样数据

if (index % 2 == 0) {

bos.write(buffer, i, 4);

}

}

}

fos.write(bos.toByteArray());

fos.flush();

return true;

} catch (IOException e) {

e.printStackTrace();

} finally {

IOUtils.closeQuietly(fis);

IOUtils.closeQuietly(fos);

}

return false;

}

3b2bb5860d98

image

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐