Android MediaPlayer 播放流程:解码与 AudioTrack 调用时机
- MediaPlayer 整体播放流程
应用层请求播放 → 状态机准备 → 解码器初始化 → 解码线程启动 →
PCM数据 → AudioTrack写入 → 硬件播放
- 解码时机详解
2.1 初始化阶段的解码准备
// MediaPlayer.java 中解码相关初始化
// 设置数据源后,prepareAsync() 或 prepare() 会触发:
// 1. 创建解码器
// 2. 配置解码参数
// 3. 启动解码线程
public void prepareAsync() throws IllegalStateException {
// ...
_prepareAsync();
// 内部调用 native 层,启动解码器初始化
}
private native void _prepareAsync();
2.2 实际解码时机
解码开始时间:
• 立即解码:调用 start() 后立即开始解码
• 缓冲控制:有足够数据缓冲时开始解码
• 流媒体特殊处理:网络流需缓冲到一定量才开始解码
关键源码位置:
// frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
void NuPlayer::onStart() {
// 触发解码器开始工作
mSource->start();
mDecoder->start();
}
- AudioTrack 调用时机
3.1 AudioTrack 创建时间
// 创建阶段
public void start() throws IllegalStateException {
// ...
_start(); // 调用 native 方法
}
// native 层实现
static void android_media_MediaPlayer_start(JNIEnv *env, jobject thiz) {
sp mp = getMediaPlayer(env, thiz);
// ...
mp->start(); // 触发 AudioTrack 创建
}
3.2 AudioTrack 实际调用流程
// frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
void NuPlayer::Decoder::onRequestInputBuffers() {
// 1. 从数据源读取压缩数据
// 2. 解码器解码
// 3. 解码后得到 PCM 数据
// 4. 将 PCM 数据送入 AudioTrack
}
具体时序:
解码线程运行 → 解码一帧音频 → 检查AudioTrack缓冲区 →
如果缓冲区有空位 → 写入AudioTrack → 硬件播放
- 解码与播放的详细时序图
// 伪代码表示时序
时序图:
-
MediaPlayer.start()
↓
-
Native层启动解码线程
↓
-
解码器开始工作(解码第一帧)
↓
-
检查是否创建AudioTrack
↓
-
如果未创建,创建AudioTrack并打开
↓
-
将解码后的PCM写入AudioTrack缓冲区
↓
-
AudioTrack开始播放(首次写入后)
↓
-
解码线程持续解码并填充缓冲区
↓
-
播放完成或停止时释放资源
-
缓冲机制与流畅播放
5.1 解码缓冲区
// Audio解码器缓冲策略
class AudioDecoder {
private:
enum {
kMinBufferCount = 4, // 最小缓冲帧数
kTargetBufferCount = 8, // 目标缓冲帧数
kMaxBufferCount = 16 // 最大缓冲帧数
};
// 解码到一定数量才开始播放
bool shouldStartPlayback() {
return mDecodedFrames > kMinBufferCount;
}
};
5.2 AudioTrack 缓冲区管理
// AudioTrack 的缓冲区配置
// 创建 AudioTrack 时计算缓冲区大小
int bufferSizeInBytes = AudioTrack.getMinBufferSize(
sampleRate,
channelConfig,
audioFormat
);
// 通常设置为2-3倍的minBufferSize以保证流畅
bufferSizeInBytes *= 3;
AudioTrack audioTrack = new AudioTrack(
streamType,
sampleRate,
channelConfig,
audioFormat,
bufferSizeInBytes,
MODE_STREAM
);
- 源码关键调用点
6.1 解码器启动源码
// frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
status_t NuPlayer::Decoder::init() {
// 创建解码器组件
mCodec = MediaCodec::CreateByType(
mSurface, mime, false, false);
// 配置解码器
mCodec->configure(format, surface, crypto, 0);
// 启动解码器
mCodec->start();
// 启动输入/输出缓冲区处理线程
mCodecLooper = new ALooper;
mCodecLooper->start();
return OK;
}
6.2 PCM 数据流向 AudioTrack
// frameworks/av/media/libstagefright/AudioPlayer.cpp
void AudioPlayer::onBufferAvailable(size_t index, size_t offset, ...) {
// 从解码器获取 PCM 数据
sp buffer;
mSource->read(&buffer, options);
if (buffer != NULL) {
// 写入 AudioTrack
ssize_t n = mAudioTrack->write(
buffer->data(),
buffer->size()
);
if (n < 0) {
// 处理写入错误
notifyAudioEOS();
}
}
}
6.3 AudioTrack 的创建时机
// frameworks/av/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
void NuPlayer::Renderer::onAudioOutputReady() {
// AudioTrack 在需要时创建
if (mAudioTrack == NULL) {
// 创建 AudioTrack
mAudioTrack = new AudioTrack(
mStreamType,
mSampleRate,
mChannelMask,
mFormat,
mFrameCount,
mFlags,
audioCallback,
this,
0, // notification frames
mSessionId,
AudioTrack::TRANSFER_DEFAULT,
NULL, // offload info
-1, // uid
-1, // pid
mAudioAttributes
);
// 启动播放
mAudioTrack->start();
}
}
- 流畅播放的关键控制点
7.1 缓冲区水位控制
// 解码速度控制策略
void NuPlayer::Decoder::onBufferAvailable() {
// 计算当前缓冲区状态
size_t filledBuffers = getFilledBufferCount();
size_t totalBuffers = getTotalBufferCount();
// 控制解码速度
if (filledBuffers > totalBuffers * 0.8) {
// 缓冲区快满了,暂停解码
pauseDecoding();
} else if (filledBuffers < totalBuffers * 0.2) {
// 缓冲区快空了,加速解码
resumeDecoding();
}
}
7.2 音视频同步
// AudioTrack 播放时间控制
void AudioPlayer::adjustPlaybackTime(int64_t timeUs) {
// 计算音频播放时间
int64_t positionUs;
mAudioTrack->getTimestamp(&positionUs);
// 与视频同步
int64_t driftUs = positionUs - mVideoTimeUs;
if (abs(driftUs) > AUDIO_VIDEO_THRESHOLD_US) {
// 需要调整
if (driftUs > 0) {
// 音频超前,轻微减速
mAudioTrack->setPlaybackRate(mPlaybackRate * 0.99);
} else {
// 音频滞后,轻微加速
mAudioTrack->setPlaybackRate(mPlaybackRate * 1.01);
}
}
}
- 状态机与生命周期
// MediaPlayer 状态机中的关键节点
状态转换:
IDLE → INITIALIZED → PREPARING → PREPARED → STARTED → PAUSED → STOPPED
// 关键节点:
-
prepare()/prepareAsync(): 初始化解码器
-
start():
- 创建 AudioTrack
- 开始解码
- 开始播放
-
pause(): 暂停解码和播放
-
stop(): 停止解码,释放 AudioTrack
-
release(): 释放所有资源
-
性能优化策略
9.1 低延迟播放
// 配置低延迟 AudioTrack
AudioAttributes audioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.setFlags(AudioAttributes.FLAG_LOW_LATENCY)
.build();
AudioFormat audioFormat = new AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setSampleRate(44100)
.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
.build();
AudioTrack audioTrack = new AudioTrack.Builder()
.setAudioAttributes(audioAttributes)
.setAudioFormat(audioFormat)
.setBufferSizeInBytes(minBufferSize)
.setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY)
.build();
9.2 大缓冲区策略
// 针对网络流的缓冲优化
class StreamingAudioPlayer {
// 增加预解码缓冲区
static const size_t kPreDecodeBufferSize = 10 * 1024 * 1024; // 10MB
// 动态调整缓冲区
void adjustBufferBasedOnNetwork() {
if (networkSpeed < THRESHOLD_SLOW) {
increaseBufferSize();
} else {
decreaseBufferSize();
}
}
};
- 故障处理与恢复
// AudioTrack 写入失败处理
ssize_t AudioPlayer::writeToAudioTrack(const void* buffer, size_t size) {
ssize_t written = mAudioTrack->write(buffer, size);
if (written < 0) {
switch (written) {
case WOULD_BLOCK:
// 缓冲区满,等待后重试
usleep(10000); // 10ms
return writeToAudioTrack(buffer, size);
case DEAD_OBJECT:
// AudioTrack 已死,重新创建
recreateAudioTrack();
return writeToAudioTrack(buffer, size);
default:
// 其他错误,通知上层
notifyError(written);
return written;
}
}
return written;
}
关键总结:
- 解码时机:start() 调用后立即开始,受缓冲策略控制
- AudioTrack 创建:首次有 PCM 数据需要播放时创建
- 数据流向:解码器 → PCM 缓冲区 → AudioTrack → 硬件
- 流畅保证:通过双缓冲机制和预解码实现
- 同步机制:AudioTrack 的时间戳用于音视频同步
- 内存管理:及时释放解码缓冲区和 AudioTrack 资源