PCM (Pulse Code Modulation) 音频数据是一种未经压缩的原始音频数据格式,各个音频样本都由固定大小且有符号/无符号的整数值组成。每个 PCM 帧包含一个或多个 PCM 音频样本,通常表示为 16 比特或 24 比特或 32 比特的整数值。
PCM 格式音频的数据结构是轻松理解和实现的。每个音频帧包含一个或多个 PCM 音频样本(例如,对于单声道,每个PCM帧只包含一个音频样本,而对于立体声,则有两个样本),每个样本由一个固定数量的比特位(通常为 16 比特)表示,可以是有符号整数或无符号整数。
以下是用于描述基本 PCM 数据结构示例:
Byte 1 Byte 2 Byte 3
+--------------+--------------+
| Left Sample 1| Right Sample 1|
+--------------+--------------+
Byte 4 Byte 5 Byte6
+--------------+--------------+
| Left Sample 2| Right Sample 2|
+--------------+--------------+
... ... ...
上面的示例展示了一种记录“立体声 PCM”音频样本的方法,其中每个音频帧都包含左右两个音频样本,每个样本是由16位宽度的整数值来表示。
AMR-WB (Adaptive Multi Rate Wideband) 是一种语音编码格式,它使用抽样率为 16 kHz,量化位数为 16 比特,并且允许比较低的码率(通常在 6 到 12.2 kbps 范围内)来压缩语音内容。
AMR-WB 的数据结构与 PCM 数据有所不同。PCM 格式的音频是原始音频数据,而 AMR-WB 音频则是压缩后的数据。每个 AMR-WB 编码帧包含多个采样点意味着一个 AMR-WB 帧可以代表长度不定的时间范围内的音频。因此,解码时需要逐帧对 AMR-WB 压缩后的音频数据进行处理。
以下是用于描述基本AMR-WB数据结构示例:
Byte 1 Byte 2 ... Byte N
+--------------+--------------+----------------+
| | Header | |
| Frame Type | | Payload |
| | | |
+---------------+-------------+----------------+
上图展示 AMR-WB 数据的基本结构。其中,音频帧分为一个头部和一个有效载荷部分。帧头描述了该帧音频的类型、比特率等元数据,而有效载荷则含有压缩过的音频数据。
实例代码:
// 初始化 MediaFormat 将来自PCM音频数据源的每个音频样本映射到16位压缩表中的一个编码帧中(单位为20毫秒)
MediaFormat inputFormat = new MediaFormat();
inputFormat.setString(MediaFormat.KEY_MIME, "audio/raw"); // PCM 音频格式为 "audio/raw"
inputFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); // 单声道
inputFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 16000); // 采样率为 16kHz
inputFormat.setInteger(MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_16BIT); // PCM 数据,比特深度为 16
// MediaFormat outputFormat = new MediaFormat();
// outputFormat.setString(MediaFormat.KEY_MIME, "audio/amr-wb"); // 编码目标格式为 AMR-WB
// outputFormat.setInteger(MediaFormat.KEY_BIT_RATE, 128000); // 比特率为 128Kbps
// 创建和配置编码器
MediaCodec encoder = MediaCodec.createEncoderByType("audio/amr-wb");
encoder.configure(inputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
encoder.start();
// 获取输入和输出缓冲区
ByteBuffer[] inputBuffers = encoder.getInputBuffers();
ByteBuffer[] outputBuffers = encoder.getOutputBuffers();
// 准备编码器输入数据
byte[] pcmData = … // 来自 PCM 音频的原始数据
int inputBufferIndex = encoder.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(pcmData);
encoder.queueInputBuffer(inputBufferIndex, 0, pcmData.length, 0, 0);
}
// 处理编码器输出数据
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = encoder.dequeueOutputBuffer(bufferInfo, 0);
while (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
byte[] amrData = new byte[bufferInfo.size]; // 输出缓冲区大小即为 AMR 数据大小
outputBuffer.get(amrData);
// TODO: 处理编码后的AMR数据,保存到文件、上传到服务器等
// 释放输出缓冲区并请求下一个输出缓冲区操作,直到结束所有编解码过程
encoder.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = encoder.dequeueOutputBuffer(bufferInfo, 0);
}
// 停止编解码器并释放相关资源
encoder.stop();
encoder.release();
当你使用 MediaCodec API 将 PCM 音频数据转换为 AMR-WB 格式时,出现杂音的原因可能是多种多样的。以下是一些可能导致杂音问题的情况:
-
原始数据质量差:如果输入 PCM 音频的采样率或比特率低于 AMR-WB 数据格式所需要的最佳规格,那么编码器将不得不将原始数据采样再定向到更高的比特率来达到要求的音频表现;这会引起杂音和其他失真。
-
丢失数据:在传输和存储过程中,可能会导致信号中的部分数据丢失。这将导致输出的 AMR-WB 文件存在杂音、断续和其它混杂的声音。
-
搅乱顺序:当音频数据的顺序被搅乱,频率特性可被分布在额外的频段内,这也会产生杂音和声质损坏。
-
噪声手动对齐:处理音频数据时如果手动误操作导致插入额外的静音区域,则可能在解码过程中出现杂音。
-
不匹配音频格式: 如果在编码前调用
MediaFormat.setOutputFormat()
函数时与实际的原始音频格式进行错误匹配,在编码时可能会造成杂音等无法预期的编码问题。 -
编码器配置错误: 配置参数,如比特率、采样率和通道数等设置不当, 也可能导致杂音出现在编码后的音频文件中。
要确保使用合适的输入PCM数据文件,合理配置正确的MediaCodec参数以及仔细管理编解码器以避免出现杂音问题