Android-Audio-MediaPlayer-播放-流程

Android MediaPlayer 播放流程:解码与 AudioTrack 调用时机

  1. MediaPlayer 整体播放流程

应用层请求播放 → 状态机准备 → 解码器初始化 → 解码线程启动 →

PCM数据 → AudioTrack写入 → 硬件播放

  1. 解码时机详解

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();

}

  1. 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 → 硬件播放

  1. 解码与播放的详细时序图

// 伪代码表示时序

时序图:

  1. MediaPlayer.start()

  2. Native层启动解码线程

  3. 解码器开始工作(解码第一帧)

  4. 检查是否创建AudioTrack

  5. 如果未创建,创建AudioTrack并打开

  6. 将解码后的PCM写入AudioTrack缓冲区

  7. AudioTrack开始播放(首次写入后)

  8. 解码线程持续解码并填充缓冲区

  9. 播放完成或停止时释放资源

  10. 缓冲机制与流畅播放

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

);

  1. 源码关键调用点

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();
}

}

  1. 流畅播放的关键控制点

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);
    }
}

}

  1. 状态机与生命周期

// MediaPlayer 状态机中的关键节点

状态转换:

IDLE → INITIALIZED → PREPARING → PREPARED → STARTED → PAUSED → STOPPED

// 关键节点:

  1. prepare()/prepareAsync(): 初始化解码器

  2. start():

    • 创建 AudioTrack
    • 开始解码
    • 开始播放
  3. pause(): 暂停解码和播放

  4. stop(): 停止解码,释放 AudioTrack

  5. release(): 释放所有资源

  6. 性能优化策略

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();
    }
}

};

  1. 故障处理与恢复

// 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;

}

关键总结:

  1. 解码时机:start() 调用后立即开始,受缓冲策略控制
  2. AudioTrack 创建:首次有 PCM 数据需要播放时创建
  3. 数据流向:解码器 → PCM 缓冲区 → AudioTrack → 硬件
  4. 流畅保证:通过双缓冲机制和预解码实现
  5. 同步机制:AudioTrack 的时间戳用于音视频同步
  6. 内存管理:及时释放解码缓冲区和 AudioTrack 资源
相关推荐
mjhcsp2 小时前
C++ 后缀平衡树解析
android·java·c++
没有bug.的程序员3 小时前
Gradle 构建优化深度探秘:从 Java 核心到底层 Android 物理性能压榨实战指南
android·java·开发语言·分布式·缓存·gradle
lljss20209 小时前
MediaPad 10 Link S10-201wa(安卓4.1.2) 安装vlc
android
黄昏晓x9 小时前
C++----异常
android·java·c++
aningxiaoxixi9 小时前
Android Audio 广播之 ACTION_AUDIO_BECOMING_NOISY
android·java·python
Chengbei1111 小时前
内网渗透过程中搜寻指定文件内容Everything小工具
android·安全·网络安全·系统安全·密码学·网络攻击模型·安全架构
coding者在努力11 小时前
LangChain之Prompt核心组件.2026年新版讲解,超详细
android·langchain·prompt
城东米粉儿11 小时前
Kotlin suspendCancellCoroutine 笔记
android
Jomurphys11 小时前
Android 优化 - R8 混淆
android