OpenHarmony播放音乐start请求从app到media_service的调用流程01
1)文章由移远通信技术股份有限公司提供
2)以下内容包含了个人理解,仅供参考,如有不合理处,请联系笔者修改/删除
文章目录
- 一、环境与版本信息
- 二、MiniMusic.hap:最小化播放器Demo
- 三、ArkTS 入口
- 四、createAVPlayer() 到 AVPlayerNapi
- 五、play() 进入 native AVPlayer
- 六、native Player 到 PlayerService
- 七、PlayerServer 状态机
- 八、PlayerEngine 到 HiPlayerImpl
- 九、pipeline_->Start() 进入音频 sink
- 十、AudioServerSinkPlugin 创建 AudioRenderer
- 十一、GDB 实证:从 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
- 软件版本:见下方版本信息
"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调试,我们将:
- 追踪跨进程调用:分析应用进程如何通过IPC与media_service通信
- 理解状态机转换:解析播放器状态机的状态转换逻辑
- 验证技术实现:通过GDB调试验证理论分析与实际执行路径的一致性
- 建立完整认知:构建从应用层到服务层的完整技术栈理解
本文聚焦于分析 MiniMusic app 调用 play() 后,Start 请求如何从应用进程传递到 media_service,并在 media_service 中启动播放器 pipeline 的音频 sink。通过源码分析和GDB调试,我们将完整追踪这一跨进程调用链路。
1.5 核心流程架构图
1.5.1 核心调用链
// 应用进程侧
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 技术要点
- 跨进程架构:应用与media_service分离,通过IPC通信
- 异步任务模型:播放操作通过任务队列异步执行
- 状态机管理:PlayerServer维护播放状态,确保状态转换正确性
- 插件化设计:AudioSinkFilter作为插件接入pipeline架构
MiniMusic.hap
→ ArkTS 页面点击 Play
→ @ohos.multimedia.media.AVPlayer
→ AVPlayerNapi
→ PlayerImpl
→ PlayerService IPC
→ PlayerServer
→ HiPlayerImpl
→ pipeline_->Start()
核心目标:将"app中点击Play"这一用户操作,真正落地到 media_service 的播放器服务线程中执行。
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() 从 app 到 framework 再到 service 的完整过程。
2.1 目录与构成
Demo 位于:
applications/standard/hap/minimusic
核心文件主要包括:
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,固定播放系统文件:
/system/etc/dynamic.wav
基本流程是:
打开音频文件
-> createAVPlayer()
-> 设置 fdSrc
-> 等待 initialized
-> prepare()
-> 等待 prepared
-> play()
对应的核心代码是:
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 入口
入口文件:
applications/standard/hap/minimusic/entry/src/main/ets/pages/Index.ets
关键播放逻辑:
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();
这段代码做了三件事:
1. 打开系统音频文件。
2. 创建 AVPlayer。
3. 调用 prepare() 和 play()。
这里 fdSrc 比 url 更稳,因为当前版本对 fd://... 的 URL 形式校验比较严格,fdSrc 能直接走文件描述符路径。
四、createAVPlayer() 到 AVPlayerNapi
media.createAVPlayer() 只是 ArkTS 层入口,真正会进入 NAPI。
foundation/multimedia/player_framework/frameworks/js/avplayer/avplayer_napi.cpp
关键位置:
napi_value AVPlayerNapi::JsPlay(napi_env env, napi_callback_info info)
它的职责是:
1. 取出 JS 对象对应的 AVPlayerNapi 实例。
2. 做状态检查。
3. 创建异步播放任务。
play() 并不是同步把播放做完,而是进入一个任务对象:
promiseCtx->asyncTask = jsPlayer->PlayTask();
五、play() 进入 native AVPlayer
AVPlayerNapi::PlayTask() 的关键调用是:
int32_t ret = player_->Play();
这里的 player_ 是 native player 对象,继续进入播放器实现层。
Index.ets
-> media.createAVPlayer()
-> player.prepare()
-> player.play()
-> AVPlayerNapi::JsPlay()
-> AVPlayerNapi::PlayTask()
-> player_->Play()
六、native Player 到 PlayerService
native player 并不直接控制媒体线程,它会通过 PlayerService 继续进入 media_service。
关键链路可以整理为:
PlayerImpl::Play()
-> PlayerClient::Play()
-> PlayerServiceProxy::Play()
-> IPC PLAY
-> PlayerServiceStub::Play()
-> PlayerServer::Play()
这意味着:
应用进程里的 AVPlayer 只是客户端;
真正的播放器服务在 media_service。
6.1 PlayerImpl::Play()
PlayerImpl::Play() 的核心动作是调用服务端代理:
ret = playerService_->Play();
6.2 PlayerClient::Play()
PlayerClient 继续通过 IPC proxy 发送请求。
6.3 PlayerServiceProxy::Play()
这里会写 interface token,并通过 remote->SendRequest(PLAY, ...) 把请求发送给服务端。
6.4 PlayerServiceStub::Play()
服务端收到 IPC 后,进入:
playerServer_->Play()
七、PlayerServer 状态机
PlayerServer::Play() 并不是直接播放,它会先检查状态。
典型逻辑是:
if (lastOpStatus_ == PLAYER_PREPARED ||
lastOpStatus_ == PLAYER_PLAYBACK_COMPLETE ||
lastOpStatus_ == PLAYER_PAUSED) {
return OnPlay();
}
含义:
只有准备好、播完、暂停等状态才允许进入播放。
7.1 PlayerServer::OnPlay()
OnPlay() 会投递一个任务,而不是把所有逻辑都同步执行完:
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()
真正进入播放器引擎的关键调用:
int32_t ret = playerEngine_->Play();
到这里,控制链路已经完全进入 media_service 的播放器引擎层。
八、PlayerEngine 到 HiPlayerImpl
仓库里有两套 HiPlayerImpl 路径,当前文档只需要记住它们都会通向同一个结果:
HiPlayerImpl::Play()
-> pipeline_->Start()
8.1 histreamer 路径
关键调用:
syncManager_->Resume();
ret = TransStatus(pipeline_->Start());
8.2 media_foundation standard player 路径
关键调用:
syncManager_->Resume();
auto ret = pipeline_->Start();
不管是哪个 engine 实现,最终都要启动媒体 pipeline。
九、pipeline_->Start() 进入音频 sink
当前实测走的是 histreamer 的 Pipeline::Start()。它的模式很清楚:
先通过 SubmitJobOnce 提交启动任务,然后在任务里遍历 pipeline 中的 filters_。
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是:
AudioSinkFilter
Filter::Start() 会把具体 filter 的启动动作投递到 pipeline 线程里执行,
最终进入 AudioSinkFilter::DoStart()。这一步之后,控制权才交给具体的音频输出插件。
十、AudioServerSinkPlugin 创建 AudioRenderer
AudioServerSinkPlugin 是 media_service 中连接播放器和 AudioRenderer 的关键插件。
它会创建:
audioRenderer_ = AudioStandard::AudioRenderer::Create(rendererOptions_, appInfo);
然后在 Prepare() 里设置参数:
audioRenderer_->SetParams(rendererParams_);
最后在 Start() 里调用:
ret = audioRenderer_->Start();
这一步之后,就进入下一篇要分析的 AudioRendererPrivate::Start()
和 RendererInClientInner::StartAudioStream()。
十一、GDB 实证:从 app 进程到 media_service
这一段建议分两个进程看。
11.1 attach MiniMusic / HAP 进程
先找到 HAP 进程:
ps -A | grep -E "MiniMusic|minimusic|com.example.minimusic"
然后 attach 到应用进程。
在 app 进程里,先抓 ArkTS 进入 NAPI 的点:
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

这一步能证明:
1. `play()` 先进入 AVPlayerNapi。
2. AVPlayerNapi 会创建异步 PlayTask。
3. PlayTask 继续进入 native PlayerImpl。
4. PlayerImpl 通过 PlayerServiceProxy 发送 IPC。
11.1.1 证明 PlayerServiceProxy 的远端对象属于 media_service
继续在 PlayerServiceProxy::Play() 里看 Remote() 返回的对象:


关键结果是:
handle_ = 21
remoteDescriptor_ = "IStandardPlayerService"
这说明 PlayerServiceProxy 持有的是一个 Binder 远端代理对象,而且接口名就是播放器服务的标准接口。
再结合内核 Binder 状态查同一个句柄。
cat /sys/kernel/debug/binder/proc/5927 | grep "desc 21"
实测:
ref 165107: desc 21 node 165106 s 1 w 0 d 0000000000000000
这表示 MiniMusic app 进程 5927 中的 desc 21 指向 Binder node 165106。
再到 media_service 的 binder proc 中查同一个 node:
cat /sys/kernel/debug/binder/proc/$(pidof media_service) | grep 165106
实测:
node 165106: u0000007f0b3af490 c0000007f0b3b09a0 hs 1 hw 1 ls 0 lw 0 is 1 iw 1 tr 1 proc 5927
这条 node 165106 出现在 media_service 的 binder proc 文件中,
尾部的 proc 5927 表示当前引用者包含 MiniMusic app 进程。
再确认 app pid:
ps -A | grep minimusic
实测:
5927 ? 00:00:02 ample.minimusic
所以这条证据链是:
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
再 attach 到 media_service,抓 PlayerService stub 和 PlayerServer:
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 后,栈如下:
#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):
OHOS::Media::PlayerServiceStub::Play
at player_service_stub.cpp:443
再继续命中 PlayerServer::Play:
#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

这一步证明 app 的 Play IPC 已经进入 media_service 的 PlayerService 服务端对象,
并转到 PlayerServer。
11.2.2 第二阶段
PlayerServer::Play() 把真正的播放动作投递到 PlayerEngine 线程。
命中 HandlePlay 和 HiPlayerImpl::Play 后,栈如下:

#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:
syncManager_->Resume();
ret = TransStatus(pipeline_->Start());
在 942 行下断点后,GDB 实测:

11.2.3 第三阶段
单步进入 pipeline_->Start(),确认目标是 Pipeline::Start():

Pipeline::Start() 的关键源码:
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:

#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 侧合起来:
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 的完整链路
把这一篇的链路整理成一句话,就是:
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 的起点说明清楚。
后面三篇继续往下走:
02: media_service -> audio_server
03: audio_server -> audio_host
04: audio_host -> ALSA
这样四篇拼起来,就是一条完整的播放 Start 链路。
更多推荐


所有评论(0)