OpenHarmony播放音乐start请求从app到media_service的调用流程01

1)文章由移远通信技术股份有限公司提供

2)以下内容包含了个人理解,仅供参考,如有不合理处,请联系笔者修改/删除

文章目录

  • 一、环境与版本信息
    • [1.1 硬件平台](#1.1 硬件平台)
    • [1.2 软件环境](#1.2 软件环境)
    • [1.3 版本确认](#1.3 版本确认)
    • [1.4 本文目标](#1.4 本文目标)
    • [1.5 核心流程架构图](#1.5 核心流程架构图)
      • [1.5.1 核心调用链](#1.5.1 核心调用链)
      • [1.5.2 技术要点](#1.5.2 技术要点)
  • 二、MiniMusic.hap:最小化播放器Demo
    • [2.1 目录与构成](#2.1 目录与构成)
    • [2.2 播放实现](#2.2 播放实现)
    • [2.3 为什么使用 `fdSrc`](#2.3 为什么使用 fdSrc)
    • [2.4 为什么选 MiniMusic.hap](#2.4 为什么选 MiniMusic.hap)
    • [2.5 实现效果](#2.5 实现效果)
  • [三、ArkTS 入口](#三、ArkTS 入口)
  • [四、createAVPlayer() 到 AVPlayerNapi](#四、createAVPlayer() 到 AVPlayerNapi)
  • [五、play() 进入 native AVPlayer](#五、play() 进入 native AVPlayer)
  • [六、native Player 到 PlayerService](#六、native Player 到 PlayerService)
    • [6.1 PlayerImpl::Play()](#6.1 PlayerImpl::Play())
    • [6.2 PlayerClient::Play()](#6.2 PlayerClient::Play())
    • [6.3 PlayerServiceProxy::Play()](#6.3 PlayerServiceProxy::Play())
    • [6.4 PlayerServiceStub::Play()](#6.4 PlayerServiceStub::Play())
  • [七、PlayerServer 状态机](#七、PlayerServer 状态机)
    • [7.1 `PlayerServer::OnPlay()`](#7.1 PlayerServer::OnPlay())
    • [7.2 `PlayerServer::HandlePlay()`](#7.2 PlayerServer::HandlePlay())
  • [八、PlayerEngine 到 HiPlayerImpl](#八、PlayerEngine 到 HiPlayerImpl)
    • [8.1 histreamer 路径](#8.1 histreamer 路径)
    • [8.2 media_foundation standard player 路径](#8.2 media_foundation standard player 路径)
  • [九、pipeline_->Start() 进入音频 sink](#九、pipeline_->Start() 进入音频 sink)
  • [十、AudioServerSinkPlugin 创建 AudioRenderer](#十、AudioServerSinkPlugin 创建 AudioRenderer)
  • [十一、GDB 实证:从 app 进程到 media_service](#十一、GDB 实证:从 app 进程到 media_service)
    • [11.1 attach MiniMusic / HAP 进程](#11.1 attach MiniMusic / HAP 进程)
      • [11.1.1 证明 PlayerServiceProxy 的远端对象属于 media_service](#11.1.1 证明 PlayerServiceProxy 的远端对象属于 media_service)
    • [11.2 attach media_service](#11.2 attach media_service)
      • [11.2.1 第一阶段](#11.2.1 第一阶段)
      • [11.2.2 第二阶段](#11.2.2 第二阶段)
      • [11.2.3 第三阶段](#11.2.3 第三阶段)
      • [11.2.4 第四阶段](#11.2.4 第四阶段)
  • [十二、从 app 到 media_service 的完整链路](#十二、从 app 到 media_service 的完整链路)
  • 十三、这一篇的定位

一、环境与版本信息

1.1 硬件平台

  • 开发板:RK3576
  • CPU架构:ARM64

1.2 软件环境

  • 操作系统:OpenHarmony 6.1
  • 内核版本:Linux 6.6
  • SDK版本:6.1.0.31
  • API版本:23
  • 构建类型:Release

1.3 版本确认

通过构建配置文件确认版本信息:

  • 硬件平台:RK3576
  • 操作系统:OpenHarmony 6.1
  • 内核版本:Linux 6.6
  • 软件版本:见下方版本信息
c 复制代码
"build/version.gni"

# Copyright (c) 2021 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# OHOS version
declare_args() {
  sdk_version = "6.1.0.31"
  api_version = "23"

  # Release type, optional values: Betax, RCx...
  release_type = "Release"
  meta_version = "3.0.0"
  platform_version = "4.0.0"
}

# ohos SDK version
declare_args() {
  current_sdk_version = sdk_version
}

# ohos NDK version
declare_args() {
  current_ndk_version = current_sdk_version
}

1.4 本文目标

本文旨在深度解析OpenHarmony音频播放链路中,从应用层点击Play按钮到media_service服务启动音频pipeline的完整调用路径。

通过源码分析结合GDB调试,我们将:

  1. 追踪跨进程调用:分析应用进程如何通过IPC与media_service通信
  2. 理解状态机转换:解析播放器状态机的状态转换逻辑
  3. 验证技术实现:通过GDB调试验证理论分析与实际执行路径的一致性
  4. 建立完整认知:构建从应用层到服务层的完整技术栈理解

本文聚焦于分析 MiniMusic app 调用 play() 后,Start 请求如何从应用进程传递到 media_service,并在 media_service 中启动播放器 pipeline 的音频 sink。通过源码分析和GDB调试,我们将完整追踪这一跨进程调用链路。

1.5 核心流程架构图

#mermaid-svg-PijOXmLOto5SSIyS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PijOXmLOto5SSIyS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PijOXmLOto5SSIyS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PijOXmLOto5SSIyS .error-icon{fill:#552222;}#mermaid-svg-PijOXmLOto5SSIyS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PijOXmLOto5SSIyS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PijOXmLOto5SSIyS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PijOXmLOto5SSIyS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PijOXmLOto5SSIyS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PijOXmLOto5SSIyS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PijOXmLOto5SSIyS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PijOXmLOto5SSIyS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PijOXmLOto5SSIyS .marker.cross{stroke:#333333;}#mermaid-svg-PijOXmLOto5SSIyS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PijOXmLOto5SSIyS p{margin:0;}#mermaid-svg-PijOXmLOto5SSIyS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PijOXmLOto5SSIyS .cluster-label text{fill:#333;}#mermaid-svg-PijOXmLOto5SSIyS .cluster-label span{color:#333;}#mermaid-svg-PijOXmLOto5SSIyS .cluster-label span p{background-color:transparent;}#mermaid-svg-PijOXmLOto5SSIyS .label text,#mermaid-svg-PijOXmLOto5SSIyS span{fill:#333;color:#333;}#mermaid-svg-PijOXmLOto5SSIyS .node rect,#mermaid-svg-PijOXmLOto5SSIyS .node circle,#mermaid-svg-PijOXmLOto5SSIyS .node ellipse,#mermaid-svg-PijOXmLOto5SSIyS .node polygon,#mermaid-svg-PijOXmLOto5SSIyS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PijOXmLOto5SSIyS .rough-node .label text,#mermaid-svg-PijOXmLOto5SSIyS .node .label text,#mermaid-svg-PijOXmLOto5SSIyS .image-shape .label,#mermaid-svg-PijOXmLOto5SSIyS .icon-shape .label{text-anchor:middle;}#mermaid-svg-PijOXmLOto5SSIyS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PijOXmLOto5SSIyS .rough-node .label,#mermaid-svg-PijOXmLOto5SSIyS .node .label,#mermaid-svg-PijOXmLOto5SSIyS .image-shape .label,#mermaid-svg-PijOXmLOto5SSIyS .icon-shape .label{text-align:center;}#mermaid-svg-PijOXmLOto5SSIyS .node.clickable{cursor:pointer;}#mermaid-svg-PijOXmLOto5SSIyS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PijOXmLOto5SSIyS .arrowheadPath{fill:#333333;}#mermaid-svg-PijOXmLOto5SSIyS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PijOXmLOto5SSIyS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PijOXmLOto5SSIyS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PijOXmLOto5SSIyS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PijOXmLOto5SSIyS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PijOXmLOto5SSIyS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PijOXmLOto5SSIyS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PijOXmLOto5SSIyS .cluster text{fill:#333;}#mermaid-svg-PijOXmLOto5SSIyS .cluster span{color:#333;}#mermaid-svg-PijOXmLOto5SSIyS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PijOXmLOto5SSIyS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PijOXmLOto5SSIyS rect.text{fill:none;stroke-width:0;}#mermaid-svg-PijOXmLOto5SSIyS .icon-shape,#mermaid-svg-PijOXmLOto5SSIyS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PijOXmLOto5SSIyS .icon-shape p,#mermaid-svg-PijOXmLOto5SSIyS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PijOXmLOto5SSIyS .icon-shape .label rect,#mermaid-svg-PijOXmLOto5SSIyS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PijOXmLOto5SSIyS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PijOXmLOto5SSIyS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PijOXmLOto5SSIyS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 后续链路
media_service进程
应用进程
🎵 MiniMusic.hap

应用层
📱 ArkTS UI点击Play
🔧 @ohos.multimedia.media.AVPlayer
🌉 AVPlayerNapi

JS/Native桥接
⚙️ PlayerImpl

Native播放器实现
📡 PlayerService IPC

跨进程通信
🏗️ PlayerServer

服务端状态机
🚀 HiPlayerImpl

播放引擎
🔌 pipeline_->Start()

启动媒体管道
🎧 AudioSinkFilter

音频输出过滤器
🔊 AudioRenderer

音频渲染器

1.5.1 核心调用链

c 复制代码
// 应用进程侧
MiniMusic.hap → ArkTS UI点击Play → @ohos.multimedia.media.AVPlayer
→ AVPlayerNapi → PlayerImpl → PlayerService IPC

// media_service进程侧
PlayerServiceStub::Play → PlayerServer::Play → PlayerServer::OnPlay
→ PlayerServer::HandlePlay → HiPlayerImpl::Play → pipeline_->Start()
→ Filter::Start() → AudioSinkFilter::DoStart()

1.5.2 技术要点

  1. 跨进程架构:应用与media_service分离,通过IPC通信
  2. 异步任务模型:播放操作通过任务队列异步执行
  3. 状态机管理:PlayerServer维护播放状态,确保状态转换正确性
  4. 插件化设计:AudioSinkFilter作为插件接入pipeline架构
c 复制代码
MiniMusic.hap
  → ArkTS 页面点击 Play
  → @ohos.multimedia.media.AVPlayer
  → AVPlayerNapi
  → PlayerImpl
  → PlayerService IPC
  → PlayerServer
  → HiPlayerImpl
  → pipeline_->Start()

核心目标 :将"app中点击Play"这一用户操作,真正落地到 media_service 的播放器服务线程中执行。

c 复制代码
MiniMusic.hap
  -> ArkTS 页面点击 Play
  -> @ohos.multimedia.media.AVPlayer
  -> AVPlayerNapi
  -> PlayerImpl
  -> PlayerService IPC
  -> PlayerServer
  -> HiPlayerImpl
  -> pipeline_->Start()

这里的核心是把"app 里点了 Play"这件事,真正落到 media_service 的播放器服务线程里。

二、MiniMusic.hap:最小化播放器Demo

MiniMusic.hap 是我自己写的一个最小音乐播放器 Demo,主要用于验证播放功能和梳理播放链路。

它的特点是代码量小、路径固定、干扰少,适合拿来观察 play()appframework 再到 service 的完整过程。

2.1 目录与构成

Demo 位于:

text 复制代码
applications/standard/hap/minimusic

核心文件主要包括:

text 复制代码
applications/standard/hap/minimusic/AppScope/app.json
applications/standard/hap/minimusic/entry/src/main/module.json
applications/standard/hap/minimusic/entry/src/main/ets/mainAbility/MainAbility.ts
applications/standard/hap/minimusic/entry/src/main/ets/pages/Index.ets
applications/standard/hap/minimusic/signature/minimusic_release.p7b
applications/standard/hap/minimusic/signature/minimusic_release_profile.json

2.2 播放实现

播放逻辑主要在 Index.ets,固定播放系统文件:

text 复制代码
/system/etc/dynamic.wav

基本流程是:

text 复制代码
打开音频文件
  -> createAVPlayer()
  -> 设置 fdSrc
  -> 等待 initialized
  -> prepare()
  -> 等待 prepared
  -> play()

对应的核心代码是:

ts 复制代码
this.audioFile = fs.openSync(MUSIC_PATH, fs.OpenMode.READ_ONLY);
this.player = await media.createAVPlayer();

const fileStat = fs.statSync(MUSIC_PATH);
this.player.fdSrc = {
  fd: this.audioFile.fd,
  offset: 0,
  length: fileStat.size,
};

await this.waitForState(this.player, 'initialized');
await this.player.prepare();
await this.waitForState(this.player, 'prepared');
await this.player.play();

2.3 为什么使用 fdSrc

这个 Demo 不直接把路径字符串丢给播放器,而是用 fdSrc 传文件描述符和长度。这样做的原因很直接:

  • 避免路径权限、沙箱和 URI 解析差异。
  • 让播放器走 AVFileDescriptor 这条更稳定的输入路径。
  • 对应到 native 层时,NAPI 会把它转成 SetSource(fd, offset, length)

所以本文后面的 GDB 和源码分析,起点都是这个最小 Demo 发起的 play(),不是一个复杂播放器工程。

2.4 为什么选 MiniMusic.hap

因为系统自带的音乐播放器代码对于我来说还是太多了

我只是想搞清楚流程不关心应用的复杂逻辑

所以我让AI帮写了一个核心逻辑只有100多行的demo用来配合我梳理播放的一些流程

2.5 实现效果

三、ArkTS 入口

入口文件:

text 复制代码
applications/standard/hap/minimusic/entry/src/main/ets/pages/Index.ets

关键播放逻辑:

ts 复制代码
const MUSIC_PATH: string = '/system/etc/dynamic.wav';

this.audioFile = fs.openSync(MUSIC_PATH, fs.OpenMode.READ_ONLY);
this.player = await media.createAVPlayer();

const fileStat = fs.statSync(MUSIC_PATH);
this.player.fdSrc = {
  fd: this.audioFile.fd,
  offset: 0,
  length: fileStat.size,
};

await this.waitForState(this.player, 'initialized');
await this.player.prepare();
await this.waitForState(this.player, 'prepared');
await this.player.play();

这段代码做了三件事:

text 复制代码
1. 打开系统音频文件。
2. 创建 AVPlayer。
3. 调用 prepare() 和 play()。

这里 fdSrcurl 更稳,因为当前版本对 fd://... 的 URL 形式校验比较严格,

fdSrc 能直接走文件描述符路径。

四、createAVPlayer() 到 AVPlayerNapi

media.createAVPlayer() 只是 ArkTS 层入口,真正会进入 NAPI

text 复制代码
foundation/multimedia/player_framework/frameworks/js/avplayer/avplayer_napi.cpp

关键位置:

cpp 复制代码
napi_value AVPlayerNapi::JsPlay(napi_env env, napi_callback_info info)

它的职责是:

text 复制代码
1. 取出 JS 对象对应的 AVPlayerNapi 实例。
2. 做状态检查。
3. 创建异步播放任务。

play() 并不是同步把播放做完,而是进入一个任务对象:

cpp 复制代码
promiseCtx->asyncTask = jsPlayer->PlayTask();

五、play() 进入 native AVPlayer

AVPlayerNapi::PlayTask() 的关键调用是:

cpp 复制代码
int32_t ret = player_->Play();

这里的 player_native player 对象,继续进入播放器实现层。

c 复制代码
Index.ets
  -> media.createAVPlayer()
  -> player.prepare()
  -> player.play()
  -> AVPlayerNapi::JsPlay()
  -> AVPlayerNapi::PlayTask()
  -> player_->Play()

六、native Player 到 PlayerService

native player 并不直接控制媒体线程,它会通过 PlayerService 继续进入 media_service

关键链路可以整理为:

c 复制代码
PlayerImpl::Play()
  -> PlayerClient::Play()
  -> PlayerServiceProxy::Play()
  -> IPC PLAY
  -> PlayerServiceStub::Play()
  -> PlayerServer::Play()

这意味着:

text 复制代码
应用进程里的 AVPlayer 只是客户端;
真正的播放器服务在 media_service。

6.1 PlayerImpl::Play()

PlayerImpl::Play() 的核心动作是调用服务端代理:

cpp 复制代码
ret = playerService_->Play();

6.2 PlayerClient::Play()

PlayerClient 继续通过 IPC proxy 发送请求。

6.3 PlayerServiceProxy::Play()

这里会写 interface token,并通过 remote->SendRequest(PLAY, ...) 把请求发送给服务端。

6.4 PlayerServiceStub::Play()

服务端收到 IPC 后,进入:

cpp 复制代码
playerServer_->Play()

七、PlayerServer 状态机

PlayerServer::Play() 并不是直接播放,它会先检查状态。

典型逻辑是:

cpp 复制代码
if (lastOpStatus_ == PLAYER_PREPARED ||
    lastOpStatus_ == PLAYER_PLAYBACK_COMPLETE ||
    lastOpStatus_ == PLAYER_PAUSED) {
    return OnPlay();
}

含义:

text 复制代码
只有准备好、播完、暂停等状态才允许进入播放。

7.1 PlayerServer::OnPlay()

OnPlay() 会投递一个任务,而不是把所有逻辑都同步执行完:

cpp 复制代码
auto playingTask = std::make_shared<TaskHandler<void>>([this]() {
    auto currState = std::static_pointer_cast<BaseState>(GetCurrState());
    (void)currState->Play();
});
int ret = taskMgr_.LaunchTask(playingTask, PlayerServerTaskType::STATE_CHANGE, "play");
lastOpStatus_ = PLAYER_STARTED;

7.2 PlayerServer::HandlePlay()

真正进入播放器引擎的关键调用:

cpp 复制代码
int32_t ret = playerEngine_->Play();

到这里,控制链路已经完全进入 media_service 的播放器引擎层。

八、PlayerEngine 到 HiPlayerImpl

仓库里有两套 HiPlayerImpl 路径,当前文档只需要记住它们都会通向同一个结果:

text 复制代码
HiPlayerImpl::Play()
  -> pipeline_->Start()

8.1 histreamer 路径

关键调用:

cpp 复制代码
syncManager_->Resume();
ret = TransStatus(pipeline_->Start());

8.2 media_foundation standard player 路径

关键调用:

cpp 复制代码
syncManager_->Resume();
auto ret = pipeline_->Start();

不管是哪个 engine 实现,最终都要启动媒体 pipeline。

九、pipeline_->Start() 进入音频 sink

当前实测走的是 histreamerPipeline::Start()。它的模式很清楚:

先通过 SubmitJobOnce 提交启动任务,然后在任务里遍历 pipeline 中的 filters_

cpp 复制代码
Status Pipeline::Start()
{
    Status ret = Status::OK;
    SubmitJobOnce([&] {
        AutoLock lock(mutex_);
        for (auto it = filters_.begin(); it != filters_.end(); ++it) {
            ret = (*it)->Start();
            if (ret != Status::OK) {
                return;
            }
        }
        ...
    });
    return ret;
}

也就是说,pipeline 启动时会启动每个 filter。音频输出相关 filte是:

text 复制代码
AudioSinkFilter

Filter::Start() 会把具体 filter 的启动动作投递到 pipeline 线程里执行,

最终进入 AudioSinkFilter::DoStart()。这一步之后,控制权才交给具体的音频输出插件。

十、AudioServerSinkPlugin 创建 AudioRenderer

AudioServerSinkPluginmedia_service 中连接播放器和 AudioRenderer 的关键插件。

它会创建:

cpp 复制代码
audioRenderer_ = AudioStandard::AudioRenderer::Create(rendererOptions_, appInfo);

然后在 Prepare() 里设置参数:

cpp 复制代码
audioRenderer_->SetParams(rendererParams_);

最后在 Start() 里调用:

cpp 复制代码
ret = audioRenderer_->Start();

这一步之后,就进入下一篇要分析的 AudioRendererPrivate::Start()

RendererInClientInner::StartAudioStream()

十一、GDB 实证:从 app 进程到 media_service

这一段建议分两个进程看。

11.1 attach MiniMusic / HAP 进程

先找到 HAP 进程:

sh 复制代码
ps -A | grep -E "MiniMusic|minimusic|com.example.minimusic"

然后 attach 到应用进程。

在 app 进程里,先抓 ArkTS 进入 NAPI 的点:

gdb 复制代码
handle SIG38 nostop noprint pass
b OHOS::Media::AVPlayerNapi::JsPlay
b OHOS::Media::AVPlayerNapi::PlayTask
b OHOS::Media::PlayerImpl::Play
b OHOS::Media::PlayerServiceProxy::Play

这一步能证明:

text 复制代码
1. `play()` 先进入 AVPlayerNapi。
2. AVPlayerNapi 会创建异步 PlayTask。
3. PlayTask 继续进入 native PlayerImpl。
4. PlayerImpl 通过 PlayerServiceProxy 发送 IPC。

11.1.1 证明 PlayerServiceProxy 的远端对象属于 media_service

继续在 PlayerServiceProxy::Play() 里看 Remote() 返回的对象:

关键结果是:

text 复制代码
handle_ = 21
remoteDescriptor_ = "IStandardPlayerService"

这说明 PlayerServiceProxy 持有的是一个 Binder 远端代理对象,而且接口名就是播放器服务的标准接口。

再结合内核 Binder 状态查同一个句柄。

sh 复制代码
cat /sys/kernel/debug/binder/proc/5927 | grep "desc 21"

实测:

text 复制代码
ref 165107: desc 21 node 165106 s 1 w 0 d 0000000000000000

这表示 MiniMusic app 进程 5927 中的 desc 21 指向 Binder node 165106

再到 media_servicebinder proc 中查同一个 node

sh 复制代码
cat /sys/kernel/debug/binder/proc/$(pidof media_service) | grep 165106

实测:

text 复制代码
node 165106: u0000007f0b3af490 c0000007f0b3b09a0 hs 1 hw 1 ls 0 lw 0 is 1 iw 1 tr 1 proc 5927

这条 node 165106 出现在 media_servicebinder proc 文件中,

尾部的 proc 5927 表示当前引用者包含 MiniMusic app 进程。

再确认 app pid

sh 复制代码
ps -A | grep minimusic

实测:

text 复制代码
5927 ? 00:00:02 ample.minimusic

所以这条证据链是:

c 复制代码
ample.minimusic(pid 5927) 中的 IPCObjectProxy(handle=21)
  -> binder desc 21
  -> binder node 165106
  -> node 165106 位于 media_service
  -> 引用者 proc 5927 正是 ample.minimusic

这就证明 PlayerServiceProxy::Play()Binder

请求对端是 media_service 中的 IStandardPlayerService 服务端对象。

11.2 attach media_service

attachmedia_service,抓 PlayerService stubPlayerServer

gdb 复制代码
handle SIG38 nostop noprint pass
b OHOS::Media::PlayerServiceStub::Play
b OHOS::Media::PlayerServer::Play
b OHOS::Media::PlayerServer::OnPlay
b OHOS::Media::PlayerServer::HandlePlay
b OHOS::Media::HiPlayerImpl::Play
b OHOS::Media::Pipeline::Pipeline::Start
b OHOS::Media::Pipeline::Filter::Start
b OHOS::Media::Pipeline::AudioSinkFilter::DoStart

media_service 侧不是一条同步栈跑到底,而是分成几个阶段。

11.2.1 第一阶段

PlayerRequest 线程收到 app 发来的 Play IPC。命中 PlayerServiceStub::Play 后,栈如下:

c 复制代码
#0  OHOS::Media::PlayerServiceStub::Play(data, reply)@plt
#1  OHOS::Media::PlayerServiceStub::FillPlayerFuncPart1()::$_4::operator()
    at player_service_stub.cpp:141
#6  std::__h::function<int ()>::operator()()
#7  OHOS::Media::TaskHandler<int>::Execute
    at task_queue.h:137
#8  OHOS::Media::TaskQueue::TaskProcessor
    at task_queue.cpp:196

继续后命中真正的 PlayerServiceStub::Play(this)

c 复制代码
OHOS::Media::PlayerServiceStub::Play
    at player_service_stub.cpp:443

再继续命中 PlayerServer::Play

c 复制代码
#0  OHOS::Media::PlayerServer::Play
    at player_server.cpp:544
#1  OHOS::Media::PlayerServerMem::Play
    at player_server_mem.cpp:310
#2  OHOS::Media::PlayerServiceStub::Play
    at player_service_stub.cpp:449
#3  OHOS::Media::PlayerServiceStub::Play(data, reply)
    at player_service_stub.cpp:889

这一步证明 appPlay IPC 已经进入 media_servicePlayerService 服务端对象,

并转到 PlayerServer

11.2.2 第二阶段

PlayerServer::Play() 把真正的播放动作投递到 PlayerEngine 线程。

命中 HandlePlayHiPlayerImpl::Play 后,栈如下:

c 复制代码
#0  OHOS::Media::HiPlayerImpl::Play
    at hiplayer_impl.cpp:913
#1  OHOS::Media::PlayerServer::HandlePlay
    at player_server.cpp:587
#2  OHOS::Media::PlayerServer::OnPlay()::$_6::operator()()
    at player_server.cpp:573
#8  OHOS::Media::TaskHandler<void>::Execute
    at task_queue.h:132
#9  OHOS::Media::TaskQueue::TaskProcessor
    at task_queue.cpp:196

源码里 HiPlayerImpl::Play() 在 942 行启动 pipeline

cpp 复制代码
syncManager_->Resume();
ret = TransStatus(pipeline_->Start());

在 942 行下断点后,GDB 实测:

11.2.3 第三阶段

单步进入 pipeline_->Start(),确认目标是 Pipeline::Start()

Pipeline::Start() 的关键源码:

cpp 复制代码
Status Pipeline::Start()
{
    Status ret = Status::OK;
    SubmitJobOnce([&] {
        AutoLock lock(mutex_);
        for (auto it = filters_.begin(); it != filters_.end(); ++it) {
            ret = (*it)->Start();
            if (ret != Status::OK) {
                return;
            }
        }
        ...
    });
    return ret;
}

继续在 Filter::Start 下断点,命中时的栈是:

这一步证明 HiPlayerImpl::Play() 不是直接调用某个固定的 audio sink

而是通过 pipeline_->Start() 遍历 pipeline 中的 filters_,逐个调用 Filter::Start()

11.2.4 第四阶段

Filter::Start() 内部再把具体 filter 的启动动作投递到 pipeline 线程。

随后命中 AudioSinkFilter::DoStart

c 复制代码
#0  OHOS::Media::Pipeline::AudioSinkFilter::DoStart
    at audio_sink_filter.cpp:151
#1  OHOS::Media::Pipeline::Filter::StartDone
    at filter.cpp:165
#2  OHOS::Media::Pipeline::Filter::Start()::$_2::operator()()
    at filter.cpp:143
#9  OHOS::Media::TaskInner::HandleJob
    at taskInner.cpp:331
#10 OHOS::Media::PipeLineThread::Run
    at pipeline_threadpool.cpp:207

这一步能把 app 侧和 media_service 侧合起来:

c 复制代码
app 进程:
AVPlayerNapi::JsPlay
  -> AVPlayerNapi::PlayTask
  -> PlayerImpl::Play
  -> PlayerServiceProxy::Play
  -> IPC PLAY

media_service:
PlayerServiceStub::Play
  -> PlayerServer::Play
  -> PlayerServer::OnPlay
  -> PlayerServer::HandlePlay
  -> HiPlayerImpl::Play
  -> pipeline_->Start()
  -> Pipeline::Start()
  -> Filter::Start()
  -> AudioSinkFilter::DoStart()

十二、从 app 到 media_service 的完整链路

把这一篇的链路整理成一句话,就是:

c 复制代码
MiniMusic.hap
  -> Index.ets 点击 Play
  -> media.createAVPlayer()
  -> AVPlayerNapi::JsPlay()
  -> AVPlayerNapi::PlayTask()
  -> PlayerImpl::Play()
  -> PlayerClient::Play()
  -> PlayerServiceProxy::Play()
  -> PlayerServiceStub::Play()
  -> PlayerServer::Play()
  -> PlayerServer::OnPlay()
  -> PlayerServer::HandlePlay()
  -> HiPlayerImpl::Play()
  -> pipeline_->Start()
  -> Pipeline::Start()
  -> Filter::Start()
  -> AudioSinkFilter::DoStart()

十三、这一篇的定位

这一篇只负责把 app -> media_service 的入口打通,

并把播放器引擎如何进入 pipeline 的起点说明清楚。

后面三篇继续往下走:

c 复制代码
02: media_service -> audio_server
03: audio_server -> audio_host
04: audio_host -> ALSA

这样四篇拼起来,就是一条完整的播放 Start 链路。

相关推荐
moyh-blog11 天前
OpenHarmony播放音乐start请求从audio_server到audio_host的调用流程03
audio·openahrmony
moyh-blog11 天前
OpenHarmony播放音乐start请求从media_service到audio_server的调用流程02
openharmony·audio
moyh-blog11 天前
OpenHarmony播放音乐start请求从audio_host到ALSA库的调用流程04
audio·openahrmony
一颗小行星!18 天前
在线音频分轨网站推荐:5 款人声、伴奏与乐器分离工具对比
audio
山顶夕景1 个月前
【Audio】Audio encoder相关Benchmark
大模型·音视频·多模态·audio·检索·全模态
CheungChunChiu4 个月前
Linux 音频子系统完整梳理:ALSA、ASoC、DAPM、Codec、Machine、es8389 与 rk‑multicodecs 全解析
linux·运维·音视频·codec·audio·asla·dapm
summerkissyou19875 个月前
Android-Audio-根据音频焦点控制播放
android·audio
ameyume5 个月前
基于原生Android 16设置音量调用流程
android·audio
千里马学框架5 个月前
干货分享:车载音频audio调试开发之dumpsys CarAudioService剖析
android·音视频·面试题·audio·系统开发·车载audio·framework工程师