使用MediaCodec将PCM音频编码为AMR-WB格式

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 格式时,出现杂音的原因可能是多种多样的。以下是一些可能导致杂音问题的情况:

  1. 原始数据质量差:如果输入 PCM 音频的采样率或比特率低于 AMR-WB 数据格式所需要的最佳规格,那么编码器将不得不将原始数据采样再定向到更高的比特率来达到要求的音频表现;这会引起杂音和其他失真。

  2. 丢失数据:在传输和存储过程中,可能会导致信号中的部分数据丢失。这将导致输出的 AMR-WB 文件存在杂音、断续和其它混杂的声音。

  3. 搅乱顺序:当音频数据的顺序被搅乱,频率特性可被分布在额外的频段内,这也会产生杂音和声质损坏。

  4. 噪声手动对齐:处理音频数据时如果手动误操作导致插入额外的静音区域,则可能在解码过程中出现杂音。

  5. 不匹配音频格式: 如果在编码前调用 MediaFormat.setOutputFormat() 函数时与实际的原始音频格式进行错误匹配,在编码时可能会造成杂音等无法预期的编码问题。

  6. 编码器配置错误: 配置参数,如比特率、采样率和通道数等设置不当, 也可能导致杂音出现在编码后的音频文件中。

要确保使用合适的输入PCM数据文件,合理配置正确的MediaCodec参数以及仔细管理编解码器以避免出现杂音问题

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
MediaCodec API 可以用于解码和编码音频数据。如果您需要将一个音频文件从一个编码格式换为另一个编码格式,可以使用 MediaCodec API 来实现。 以下是一个简单的示例代码,用于将一个 MP3 文件换为 AAC 格式: ```java MediaExtractor extractor = new MediaExtractor(); extractor.setDataSource(inputFilePath); int trackCount = extractor.getTrackCount(); for (int i = 0; i < trackCount; i++) { MediaFormat format = extractor.getTrackFormat(i); String mime = format.getString(MediaFormat.KEY_MIME); if (mime.startsWith("audio/")) { extractor.selectTrack(i); MediaCodec codec = MediaCodec.createEncoderByType("audio/mp4a-latm"); codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); codec.start(); ByteBuffer[] inputBuffers = codec.getInputBuffers(); ByteBuffer[] outputBuffers = codec.getOutputBuffers(); MediaMuxer muxer = new MediaMuxer(outputFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); int trackIndex = muxer.addTrack(codec.getOutputFormat()); muxer.start(); MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); boolean sawInputEOS = false; boolean sawOutputEOS = false; while (!sawOutputEOS) { if (!sawInputEOS) { int inputBufferIndex = codec.dequeueInputBuffer(-1); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; int sampleSize = extractor.readSampleData(inputBuffer, 0); if (sampleSize < 0) { sawInputEOS = true; codec.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); } else { long presentationTimeUs = extractor.getSampleTime(); codec.queueInputBuffer(inputBufferIndex, 0, sampleSize, presentationTimeUs, 0); extractor.advance(); } } } int outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, -1); if (outputBufferIndex >= 0) { ByteBuffer outputBuffer = outputBuffers[outputBufferIndex]; outputBuffer.position(bufferInfo.offset); outputBuffer.limit(bufferInfo.offset + bufferInfo.size); muxer.writeSampleData(trackIndex, outputBuffer, bufferInfo); codec.releaseOutputBuffer(outputBufferIndex, false); if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { sawOutputEOS = true; } } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { MediaFormat newFormat = codec.getOutputFormat(); trackIndex = muxer.addTrack(newFormat); muxer.start(); } } muxer.stop(); muxer.release(); codec.stop(); codec.release(); extractor.unselectTrack(i); } } extractor.release(); ``` 上述代码中,首先使用 MediaExtractor 打开一个 MP3 文件,并获取其中的音频轨道。然后使用 MediaCodec 创建一个 AAC 编码器,并将音频轨道的格式配置给编码器。接着循环读取音频数据,将其输入到编码器中进行编码,然后将编码后的数据写入到输出文件中。最后释放资源。 需要注意的是,上述代码仅用于演示如何使用 MediaCodec API 进行音频编码格式换。实际使用时,还需要考虑到性能和兼容性等问题,并进行相应的优化和调试。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你好,工程师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值