1)文章由移远通信技术股份有限公司提供
2)以下内容包含了个人理解,仅供参考,如有不合理处,请联系笔者修改/删除

一、背景与目标

1.1 环境信息

  • 操作系统:OpenHarmony 6.1
  • 硬件平台:RK3576
  • 内核版本:Linux 6.6
  • 软件版本:SDK 6.1.0.31,API 23

1.2 问题背景

在OpenHarmony音频播放架构中,播放器启动音频输出的完整链路涉及多个进程间的协作。当应用层(如MiniMusic)发起播放请求后,该请求首先通过Binder IPC传递到media_service进程。然而,真正的音频硬件驱动操作并不在media_service中完成,而是需要进一步将音频流控制命令发送到专门的audio_server进程。

本文聚焦于分析播放器管线启动音频输出后,Start请求如何从media_service通过AudioRenderer IPC下发到audio_server。这是音频播放链路中的关键跨进程通信环节,理解这一过程对于调试音频播放问题和深入理解OpenHarmony音频架构至关重要。

1.3 分析目标

本文的目标是通过源码分析和GDB调试,完整证明以下调用链路的正确性:

// media_service 侧调用链
AudioSinkFilter::DoStart
  -> AudioSink::Start
  -> AudioServerSinkPlugin::Start
  -> AudioRendererPrivate::Start
  -> AudioRendererPrivate::GetStartStreamResult
  -> RendererInClientInner::StartAudioStream
  -> IpcStreamProxy::Start
  -> remote->SendRequest(COMMAND_START)

// audio_server 侧处理链
IPCObjectStub::SendRequestInner(code=4)
  -> IpcStreamStub::OnRemoteRequest(code=4)
  -> IpcStreamInServer::Start
  -> RendererInServer::Start

具体需要证明两个核心点:

  1. media_service中确实调用了IpcStreamProxy::Start
  2. IpcStreamProxy的Binder对端确实是audio_server

1.4 前置知识

在上一篇分析中,我们已经证明了从应用层到media_service的调用链:

MiniMusic app
  -> PlayerServiceProxy::Play()
  -> Binder handle
  -> media_service中的IStandardPlayerService服务端对象
  -> PlayerServer::HandlePlay()
  -> HiPlayerImpl::Play()
  -> pipeline_->Start()
  -> Pipeline::Start()
  -> Filter::Start()
  -> AudioSinkFilter::DoStart()

因此,本文的起点不再是app进程,而是media_service中已经被pipeline启动起来的AudioSinkFilter::DoStart()。从这里开始,音频输出分支继续调用AudioSink::Start()AudioServerSinkPlugin::Start()AudioRendererPrivate::Start(),最终通过IpcStreamProxy::Start()发送到audio_server

二、调试环境准备

2.1 为什么Start会出现在media_service

在OpenHarmony的音频架构设计中,播放管线通常不直接运行在应用进程中。对于系统播放器、媒体框架播放器或ohos.samples.distributedmusicplayer这类应用,播放管线运行在media_serviceHiPlayer线程中。

这种设计带来了一个重要影响:调试播放器Start链路时,如果只attach应用进程,很可能无法观察到AudioRendererPrivate::Start()的调用。正确的调试目标应该是media_service进程。

2.2 调试步骤

步骤1:附加到media_service进程

# 获取media_service进程ID
pidof media_service

# 使用GDB附加到media_service进程
gdb-multiarch -q <media_service可执行文件>

步骤2:忽略常见实时信号

进入GDB后,为了避免调试过程中被实时信号干扰,建议先忽略SIG38信号:

handle SIG38 nostop noprint pass

三、关键断点设置

3.1 客户端IPC断点

要捕获framework client发起IPC的关键节点,建议设置以下断点:

# 客户端音频渲染器启动断点
b OHOS::AudioStandard::AudioRendererPrivate::Start
b OHOS::AudioStandard::AudioRendererPrivate::GetStartStreamResult
b OHOS::AudioStandard::RendererInClientInner::StartAudioStream
b OHOS::AudioStandard::IpcStreamProxy::Start

3.2 备用断点策略

如果IpcStreamProxy::Start符号暂时没有加载,可以采用以下备用方案:

方案1:在生成文件行号下断点

当命中RendererInClientInner::StartAudioStream后,可以对生成的IPC代理文件设置断点:

b gen/foundation/multimedia/audio_framework/services/audio_service/idl/ipc_stream_proxy.cpp:179

方案2:直接在源码调用行下断点

b ../../foundation/multimedia/audio_framework/services/audio_service/client/src/renderer_in_client_public.cpp:1065

3.3 服务端断点(audio_server侧)

# 附加到audio_server进程后设置
b OHOS::AudioStandard::IpcStreamInServer::Start
b OHOS::AudioStandard::RendererInServer::Start

四、media_service中的播放器管线调用栈分析

4.1 GDB调用栈捕获

当命中RendererInClientInner::StartAudioStream断点时,GDB显示的调用栈如下:

播放器管线调用栈

#0  OHOS::AudioStandard::RendererInClientInner::StartAudioStream
    at renderer_in_client_public.cpp:1057
#1  OHOS::AudioStandard::AudioRendererPrivate::GetStartStreamResult
    at audio_renderer.cpp:1025
#2  OHOS::AudioStandard::AudioRendererPrivate::Start
    at audio_renderer.cpp:1255
#3  OHOS::Media::Plugins::AudioServerSinkPlugin::Start
    at audio_server_sink_plugin.cpp:476
#4  OHOS::Media::AudioSink::Start
    at audio_sink.cpp:326
#5  OHOS::Media::Pipeline::AudioSinkFilter::DoStart
    at audio_sink_filter.cpp:160
#6  OHOS::Media::Pipeline::Filter::StartDone
    at filter.cpp:165
#7  OHOS::Media::Pipeline::Filter::Start()::$_2::operator()()
    at filter.cpp:143
#14 OHOS::Media::TaskInner::HandleJob
    at taskInner.cpp:331
#15 OHOS::Media::PipeLineThread::Run
    at pipeline_threadpool.cpp:207
#18 OHOS::Media::Thread::Run
    at thread.cpp:161

4.2 业务链路提取

去除标准库std::function和线程封装帧后,核心业务调用链清晰可见:

AudioSinkFilter::DoStart
  -> AudioSink::Start
  -> AudioServerSinkPlugin::Start
  -> AudioRendererPrivate::Start
  -> AudioRendererPrivate::GetStartStreamResult
  -> RendererInClientInner::StartAudioStream

关键发现AudioRendererPrivate::Start()确实是由media_service内部的播放器管线触发的,这验证了我们的第一个假设。

五、StartAudioStream 真正发起 ipcStream_->Start

RendererInClientInner::StartAudioStream() 的关键源码:

// foundation/multimedia/audio_framework/services/audio_service/client/src/renderer_in_client_public.cpp
bool RendererInClientInner::StartAudioStream(StateChangeCmdType cmdType,
    AudioStreamDeviceChangeReasonExt reason)
{
    ...
    CHECK_AND_CALL_FUNC_RETURN_RET(ipcStream_ != nullptr, false,
        HILOG_COMM_ERROR("[StartAudioStream]ipcStream is not inited!"));
    int32_t ret = ipcStream_->Start();
    CHECK_AND_CALL_FUNC_RETURN_RET(ret == SUCCESS, false,
        HILOG_COMM_ERROR("[StartAudioStream]Start call server failed:%{public}u", ret));
    ...
}

在这里插入图片描述

在 GDB 中停到 1065 行:

b ../../foundation/multimedia/audio_framework/services/audio_service/client/src/renderer_in_client_public.cpp:1065
c

命中后可以看到:

在这里插入图片描述

1065        int32_t ret = ipcStream_->Start();

继续进入后,调用目标是 IpcStreamProxy::Start

在这里插入图片描述
在这里插入图片描述

#0  OHOS::AudioStandard::IpcStreamProxy::Start
    at gen/foundation/multimedia/audio_framework/services/audio_service/idl/ipc_stream_proxy.cpp:179
#1  OHOS::AudioStandard::RendererInClientInner::StartAudioStream
    at renderer_in_client_public.cpp:1065
#2  OHOS::AudioStandard::AudioRendererPrivate::GetStartStreamResult
    at audio_renderer.cpp:1025
#3  OHOS::AudioStandard::AudioRendererPrivate::Start
    at audio_renderer.cpp:1255
#4  OHOS::Media::Plugins::AudioServerSinkPlugin::Start
    at audio_server_sink_plugin.cpp:476

六、IpcStreamProxy::Start 发送 COMMAND_START

生成代码 IpcStreamProxy::Start() 的关键逻辑:
在这里插入图片描述

ErrCode IpcStreamProxy::Start()
{
    MessageParcel data;
    MessageParcel reply;
    MessageOption option(MessageOption::TF_SYNC);

    if (!data.WriteInterfaceToken(GetDescriptor())) {
        return ERR_INVALID_VALUE;
    }

    sptr<IRemoteObject> remote = Remote();
    if (!remote) {
        return ERR_INVALID_DATA;
    }

    int32_t result = remote->SendRequest(
        static_cast<uint32_t>(IIpcStreamIpcCode::COMMAND_START), data, reply, option);
    ...
}

这里有三个关键信息:

1. Remote() 返回远端 Binder 对象代理。
2. SendRequest 使用同步调用 TF_SYNC。
3. 请求码是 IIpcStreamIpcCode::COMMAND_START。

在 GDB 中可以直接反查 COMMAND_START 的数值:

p/d (uint32_t)OHOS::AudioStandard::IIpcStreamIpcCode::COMMAND_START

也可以在服务端拿到整数 code=4 后反推:

p (OHOS::AudioStandard::IIpcStreamIpcCode)4

输出:

$ = OHOS::AudioStandard::IIpcStreamIpcCode::COMMAND_START

所以第一段 IPC 的语义是:

IpcStreamProxy::Start
  -> remote->SendRequest(4, ...)
  -> IIpcStreamIpcCode::COMMAND_START

七、证明 remote 对端是 audio_server

只看到 remote->SendRequest() 还不够。客户端一定知道自己要给哪个 Binder 对象发送请求,
但这个信息不是从函数名直接看出来的,需要从 Remote() 返回的 IPCObjectProxy 里取 Binder handle,
再用 binder debugfs 反查。

在这里插入图片描述
IpcStreamProxy::Start() 中,执行到:

189         sptr<IRemoteObject> remote = Remote();
190         if (!remote) {

GDB:

p remote

示例输出:

$10 = {refs_ = 0x7f29ee75f0}

remoteOHOS::sptr<OHOS::IRemoteObject>,真正指针保存在 refs_

ptype remote
p ((OHOS::IPCObjectProxy*)remote.refs_)->handle_
p ((OHOS::IPCObjectProxy*)remote.refs_)->GetInterfaceDescriptor()

实测结果:

handle_ = 15
GetInterfaceDescriptor() = u"OHOS.AudioStandard.IIpcStream"

这说明:

media_service 当前持有一个 IIpcStream 远端对象代理。
该代理在 media_service 进程中的 Binder handle 是 15。

接着在板端查 binder debugfs。
在这里插入图片描述

先在 media_service 的 binder proc 中找到 desc 15:

cat /sys/kernel/debug/binder/proc/$(pidof media_service) | grep "desc 15"

实测:

ref 426743: desc 15 node 426742 s 1 w 0 d 0000000000000000

这表示:

media_service 的 binder desc 15 指向 binder node 426742。

再去 audio_server 的 binder proc 中查同一个 node:

cat /sys/kernel/debug/binder/proc/$(pidof audio_server) | grep "node 426742"

实测:

node 426742: u0000007f3d356760 c0000007f3d0afc90 hs 1 hw 1 ls 0 lw 0 is 1 iw 1 tr 1 proc 578

再确认:

pidof media_service

实测:

578

这就证明:

media_service 中的 IPCObjectProxy(handle=15)
  -> binder desc 15
  -> binder node 426742
  -> node 426742 位于 audio_server
  -> 引用者 proc 578 正是 media_service

因此,IpcStreamProxy::Start() 的请求对端确实是 audio_server

八、audio_server 侧接收 COMMAND_START

attach audio_server 后,可以对服务端入口下断点:

handle SIG38 nostop noprint pass
b OHOS::AudioStandard::IpcStreamInServer::Start
b OHOS::AudioStandard::RendererInServer::Start

命中 IpcStreamInServer::Start 后,栈如下:

在这里插入图片描述

#0  OHOS::AudioStandard::IpcStreamInServer::Start
    at ipc_stream_in_server.cpp:195
#1  OHOS::AudioStandard::IpcStreamStub::OnRemoteRequest
    (this=<optimized out>, code=4, data=..., reply=..., option=...)
    at gen/foundation/multimedia/audio_framework/services/audio_service/idl/ipc_stream_stub.cpp:591
#2  OHOS::IPCObjectStub::SendRequestInner
    (this=0x7fa0cb25f0, code=4, data=..., reply=..., option=...)
    at ipc_object_stub.cpp:409
#3  OHOS::BinderInvoker::GeneralServiceSendRequest
#4  OHOS::BinderInvoker::TargetStubSendRequest
#5  OHOS::BinderInvoker::Transaction

frame 1 反查 code:

frame 1
p code
p (OHOS::AudioStandard::IIpcStreamIpcCode)code

输出:

$1 = OHOS::AudioStandard::IIpcStreamIpcCode::COMMAND_START

这和客户端 IpcStreamProxy::Start() 中的 COMMAND_START 对上。

需要注意:有时候 GDB 在 IpcStreamStub::OnRemoteRequest 里显示的当前源码行可能落在附近其他 case 的返回行,
比如 ResetStaticPlayPosition()。这通常是优化或行号映射导致的显示迷惑。判断请求类型时,应以函数参数 code 和枚举反推为准。

九、结论

链路可以整理为:

media_service / HiPlayer_*:
AudioSinkFilter::DoStart
  -> AudioSink::Start
  -> AudioServerSinkPlugin::Start
  -> AudioRendererPrivate::Start
  -> AudioRendererPrivate::GetStartStreamResult
  -> RendererInClientInner::StartAudioStream
  -> IpcStreamProxy::Start
  -> remote->SendRequest(IIpcStreamIpcCode::COMMAND_START)

audio_server / OS_IPC_*:
IPCObjectStub::SendRequestInner(code=4)
  -> IpcStreamStub::OnRemoteRequest(code=4)
  -> IpcStreamInServer::Start
  -> RendererInServer::Start

核心证明点:

1. GDB 栈证明播放器管线在 media_service 中进入 AudioRendererPrivate::Start。
2. StartAudioStream 的关键调用是 ipcStream_->Start。
3. 动态调用目标是 IpcStreamProxy::Start。
4. IpcStreamProxy::Start 发送 IIpcStreamIpcCode::COMMAND_START。
5. COMMAND_START 的运行时 code 是 4。
6. media_service 中 remote IPCObjectProxy 的 handle 是 15。
7. binder debugfs 证明 media_service 的 desc 15 对应 audio_server 中的 node。
8. audio_server 中 OnRemoteRequest(code=4) 进入 IpcStreamInServer::Start。

因此,可以得出结论:

播放器 Start 请求首先由 media_service 中的 AudioRenderer client 发起,
通过 IIpcStream 的 Binder IPC 发送到 audio_server,
audio_server 侧收到的请求码是 COMMAND_START。
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐