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 资源
相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone3 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui