在 Android 多媒体开发领域,音频处理是许多应用的基础需求,从录音应用到视频流媒体平台皆是如此。AVSample 仓库通过 Android 的 MediaCodec API 提供了音频编码和解码的全面实现,该 API 提供硬件加速处理以实现最佳性能。
1、理解音频 MediaCodec
MediaCodec 是 Android 的低级媒体编解码器 API,可访问设备的硬件和软件编解码器。对于音频处理,它能够高效地将原始 PCM(脉冲编码调制)数据编码为 AAC 等压缩格式,也能将压缩音频解码回 PCM 格式。
AVSample 项目为 MediaCodec 音频处理实现了清晰的架构,将关注点分离为处理配置、编解码操作和数据流管理的逻辑组件。
2、核心组件
2.1、AudioConfiguration
音频处理管道的核心是 AudioConfiguration 类,它定义了音频处理所需的所有参数:
val audioConfig = AudioConfiguration.Builder()
.setFrequency(44100) // 采样率(Hz)
.setChannelCount(1) // 单声道音频
.setBps(32, 64) // 比特率范围(kbps)
.setCodecType(AudioConfiguration.CodeType.ENCODE)
.build()
这种构建器模式允许您配置基本音频参数,包括:
- 采样率:默认为 44100 Hz(CD 品质)
- 声道数:1 为单声道,2 为立体声
- 比特率:最小和最大 kbps 之间的范围
- 编码格式:默认为 PCM 16 位
- 编解码器类型:编码或解码操作
- AAC 配置文件:默认为 AAC 低复杂度(LC)
2.2、AudioProcessor
AudioProcessor 类管理音频捕获和编码工作流。它继承 Thread 以在后台处理连续的音频处理:
class AudioProcessor(
privateval mAudioRecord: AudioRecord?,
audioConfiguration: AudioConfiguration?
) : Thread() {
privatevar mAudioEncoder: AACEncoder? = null
privateval mRecordBuffer: ByteArray
privateval mRecordBufferSize: Int
init {
mRecordBufferSize = AudioUtils.getMinBufferSize(
audioConfiguration!!.frequency,
audioConfiguration.channelCount
)
mRecordBuffer = ByteArray(mRecordBufferSize)
mAudioEncoder = AACEncoder(audioConfiguration)
mAudioEncoder!!.prepareCoder()
}
}
处理器持续从 AudioRecord 实例读取音频数据并馈送给编码器。它还提供暂停、停止和静音音频流的控制方法。
3、MediaCodec 配置
实际的 MediaCodec 设置发生在 AudioMediaCodec 伴生对象中,它提供了为编码和解码创建和配置编解码器的方法:
fun getAudioMediaCodec(configuration: AudioConfiguration): MediaCodec? {
val format = MediaFormat.createAudioFormat(
configuration.mime,
configuration.frequency,
configuration.channelCount
)
if (configuration.mime.equals(AudioConfiguration.DEFAULT_MIME)) {
format.setInteger(MediaFormat.KEY_AAC_PROFILE, configuration.aacProfile)
}
format.setInteger(MediaFormat.KEY_BIT_RATE, configuration.maxBps * 1024)
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, configuration.frequency)
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, configuration.channelCount)
val maxInputSize = AudioUtils.getMinBufferSize(
configuration.frequency,
configuration.channelCount,
configuration.encoding
)
format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize)
var mediaCodec: MediaCodec? = null
try {
if (configuration.codeType == AudioConfiguration.CodeType.ENCODE) {
mediaCodec = MediaCodec.createEncoderByType(configuration.mime)
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
} elseif (configuration.codeType == AudioConfiguration.CodeType.DECODE) {
format.setInteger(MediaFormat.KEY_IS_ADTS, configuration.adts)
mediaCodec = MediaCodec.createDecoderByType(configuration.mime)
mediaCodec.configure(format, null, null, 0)
}
} catch (e: Exception) {
e.printStackTrace()
// 出错时清理
}
return mediaCodec
}
此方法创建一个正确配置的 MediaCodec 实例,包含编码或解码操作所需的所有必要参数。
4、编码过程
实际的编码过程发生在 BaseAudioCodec 类中,它实现了核心的 MediaCodec 工作流:
编码过程遵循以下关键步骤:
- 输入缓冲区管理:编解码器请求输入缓冲区,原始 PCM 数据被放置其中
- 编码:MediaCodec 处理输入数据并生成编码输出
- 输出缓冲区管理:从输出缓冲区检索编码数据
- 回调:通过回调接口将编码数据传递给应用程序
以下是核心编码循环:
override fun enqueueCodec(input: ByteArray?) {
if (mMediaCodec == null) return
val inputBuffers = mMediaCodec!!.inputBuffers
val outputBuffers = mMediaCodec!!.outputBuffers
val inputBufferIndex = mMediaCodec!!.dequeueInputBuffer(12000)
if (inputBufferIndex >= 0) {
val inputBuffer = inputBuffers[inputBufferIndex]
inputBuffer.clear()
inputBuffer.put(input)
mMediaCodec!!.queueInputBuffer(
inputBufferIndex,
0,
input!!.size,
0,
0
)
}
var outputBufferIndex = mMediaCodec!!.dequeueOutputBuffer(mBufferInfo, 12000)
if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
onAudioOutformat(mMediaCodec?.outputFormat)
}
while (outputBufferIndex >= 0) {
val outputBuffer = outputBuffers[outputBufferIndex]
mBufferInfo!!.presentationTimeUs = System.nanoTime() / 1000 - mPts
onAudioData(outputBuffer, mBufferInfo)
mMediaCodec!!.releaseOutputBuffer(outputBufferIndex, false)
outputBufferIndex = mMediaCodec!!.dequeueOutputBuffer(mBufferInfo, 0)
}
}
此实现处理完整的 MediaCodec 工作流,包括缓冲区管理、时间戳计算和适当的资源清理。
5、ADTS 标头处理
AAC 音频文件通常需要 ADTS(音频数据传输流)标头才能正常播放。AVSample 项目包含原生代码,将这些标头添加到编码的 AAC 数据中:
override fun onAudioAACData(bb: ByteBuffer, bi: MediaCodec.BufferInfo) {
mAudio = ByteArray(bi.size + 7)
bb.position(bi.offset)
bb.limit(bi.offset + bi.size)
bb.get(mAudio, 7, bi.size)
ADTSUtils.addADTStoPacket(mAudio!!, mAudio!!.size, 2, 44100, 1)
mDecode?.enqueueCodec(mAudio!!)
mFileOutputStream?.write(mAudio, 0, mAudio!!.size)
}
原生实现高效地构建 ADTS 标头,包含正确的采样率、声道配置和配置文件信息:
void addADTStoPacket(
uint8_t *packet,
int packetLen,
int sampleInHz,
int chanCfgCounts,
int profile
) {
int freqIdx = 4; // 默认为44100 Hz
switch (sampleInHz) {
case8000: freqIdx = 11; break;
case16000: freqIdx = 8; break;
case32000: freqIdx = 5; break;
case44100: freqIdx = 4; break;
case48000: freqIdx = 3; break;
case96000: freqIdx = 0; break;
default: break;
}
// 填充ADTS数据
packet[0] = 0xFF;
packet[1] = 0xF1;
packet[2] = (((profile - 1) << 6) + (freqIdx << 2) + (chanCfgCounts >> 2));
packet[3] = (((chanCfgCounts & 3) << 6) + (packetLen >> 11));
packet[4] = ((packetLen & 0x7FF) >> 3);
packet[5] = (((packetLen & 7) << 5) + 0x1F);
packet[6] = 0xFC;
}
ADTS 标头长度为 7 字节,必须添加到每个 AAC 帧才能在大多数媒体播放器中正常播放。原生实现确保此操作的最佳性能。
6、实际实现
AudioMediaCodecActivity 展示了音频编码和解码的完整实现:
fun startEncode(view: View) {
init()
mStreamController?.start()
}
fun stopEncode(view: View) {
mStreamController?.stop()
mDecode?.stop()
mFileOutputStream?.close()
mFileOutputStream_PCM?.close()
}
该活动捕获音频,将其编码为 AAC,保存到文件,并同时解码回 PCM 进行验证。这种端到端实现展示了如何将所有组件集成到工作应用程序中。
展示的关键功能:
- 实时音频捕获和编码
- 编码 AAC 数据的文件输出
- 同时解码进行验证
- 适当的资源管理和清理
7、最佳实践和注意事项
实现 MediaCodec 音频处理时,请考虑这些重要因素:
7.1、缓冲区管理
- 在访问输入/输出缓冲区之前始终检查缓冲区索引
- 及时释放输出缓冲区以防止内存泄漏
- 适当处理
INFO_OUTPUT_FORMAT_CHANGED回调
7.2、性能优化
- 根据采样率和声道数使用适当的缓冲区大小
- 考虑在生产环境中使用异步模式以获得更好的性能
- 监控时间戳以确保正确同步
7.3、错误处理
- 为 MediaCodec 操作实现适当的异常处理
- 在错误条件下清理资源
- 优雅地处理设备特定的编解码器变化
7.4、线程管理
- 音频处理在单独的线程上运行,以避免阻塞 UI
- 使用
volatile标志进行线程安全的状态管理 - 在需要时实现适当的线程同步
注意:始终在多个设备上测试您的音频实现,因为编解码器支持和行为在不同 Android 制造商和操作系统版本之间可能有显著差异。
8、结论
AVSample 仓库提供了 MediaCodec 音频处理的强大实现,展示了 Android 多媒体开发的最佳实践。通过将关注点分离为逻辑组件并提供清晰的 API,它为构建音频录制、流媒体和处理应用程序提供了坚实的基础。
无论您是在构建录音应用、音频流媒体应用还是实时通信平台,理解和正确实现 MediaCodec 音频处理对于在 Android 上提供高质量、高性能的多媒体体验都至关重要。
学习和提升音视频开发技术,推荐你加入我们的知识星球:【关键帧的音视频开发圈】

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。