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

一、背景与目标

1.1 环境信息

  • 硬件平台: RK3576
  • 内核版本: Linux 6.6
  • OpenHarmony版本: 6.1.0.31 (API 23)
// build/version.gni 关键配置
declare_args() {
  sdk_version = "6.1.0.31"
  api_version = "23"
  release_type = "Release"
  meta_version = "3.0.0"
  platform_version = "4.0.0"
}

1.2 前置知识回顾

上一篇已证明的链路:

media_service
  -> IpcStreamProxy::Start
  -> IIpcStreamIpcCode::COMMAND_START
  -> audio_server::IpcStreamInServer::Start

1.3 本文追踪目标

完整调用链路如下所示:

media_service
发送COMMAND_START

audio_server
IpcStreamInServer::Start

RendererInServer::Start

HpaeRendererStreamImpl::Start

HPAE线程切换

AudioRenderSink::Start

AudioRenderProxyStart

AudioRenderProxyCall
id=33

HdfRemoteAdapterOptionalDispatch
code=33

Binder IPC
handle=15

audio_host
HdfRemoteServiceStub::OnRemoteRequest

本文需要证明的三件事

  1. audio_server确实接收到了来自media_serviceCOMMAND_START
  2. audio_server内部最终调用了HDI proxy的AudioRenderProxyStart
  3. HDI proxy的Binder对端确实是audio_host

二、调试环境准备

2.1 调试配置

attach到audio_server进程后,首先配置GDB信号处理:

# 忽略SIG38信号(OpenHarmony内部信号)
handle SIG38 nostop noprint pass

2.2 关键断点设置

# 1. audio_server入口断点
b OHOS::AudioStandard::IpcStreamInServer::Start
b OHOS::AudioStandard::RendererInServer::Start
b OHOS::AudioStandard::HpaeRendererStreamImpl::Start
b OHOS::AudioStandard::AudioRenderSink::Start

# 2. HDI proxy层断点
b AudioRenderProxyStart
b AudioRenderProxyCall if id == 33  # 条件断点,只捕获START命令
b HdfRemoteAdapterOptionalDispatch
condition <断点号> code == 33       # 条件断点,只捕获code=33

调试技巧

  • AudioRenderProxyCall是通用helper函数,会被多个IAudioRender方法调用,必须加条件id == 33
  • HdfRemoteAdapterOptionalDispatch也会被多个HDF请求复用,同样需要条件断点

三、audio_server接收COMMAND_START

3.1 断点命中确认

IpcStreamInServer::Start被命中时,GDB堆栈如下:

#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

IpcStreamInServer断点命中

3.2 验证code值

在GDB中反推code=4对应的枚举值:

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

输出结果:

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

结论:当前audio_server接收到的正是来自media_serviceCOMMAND_START请求。

四、RendererInServer到HpaeRendererStreamImpl

4.1 调用链传递

继续执行,命中HpaeRendererStreamImpl::Start

HpaeRendererStreamImpl断点命中

#0  OHOS::AudioStandard::HpaeRendererStreamImpl::Start
    at hpae_renderer_stream_impl.cpp:158
#1  OHOS::AudioStandard::RendererInServer::StartInner
    at renderer_in_server.cpp:1205
#2  OHOS::AudioStandard::RendererInServer::Start
    at renderer_in_server.cpp:1129
#3  OHOS::AudioStandard::IpcStreamStub::OnRemoteRequest(code=4)
    at ipc_stream_stub.cpp:591
#4  OHOS::IPCObjectStub::SendRequestInner(code=4)

4.2 业务链路梳理

IpcStreamStub::OnRemoteRequest(COMMAND_START)
  -> IpcStreamInServer::Start
  -> RendererInServer::Start
  -> RendererInServer::StartInner
  -> HpaeRendererStreamImpl::Start

4.3 关键代码分析

HpaeRendererStreamImpl::Start()的核心逻辑:

int32_t HpaeRendererStreamImpl::Start()
{
    preBufDone_.store(false);
    ClockTime::GetAllTimeStamp(timestamp_);
    
    // 将播放流Start请求交给HPAE manager处理
    int32_t ret = IHpaeManager::GetHpaeManager().Start(
        HPAE_STREAM_CLASS_TYPE_PLAY, processConfig_.originalSessionId);
    
    if (ret != SUCCESS) {
        AUDIO_ERR_LOG("HpaeRendererStreamImpl::Start failed, ret:%{public}d", ret);
    }
    
    return ret;
}

关键点:这里只是将请求转发给HPAE(High Performance Audio Engine)管理器,真正的硬件操作在后面。

五、HPAE线程切换机制

5.1 多线程处理现象

继续执行会发现HpaeManager::StartHpaeRendererManager::Start在不同线程中被命中:

Thread "OS_IPC_*" hit HpaeManager::Start
Thread "HpaeManager" hit HpaeManager::Start::$_30::operator()
Thread "Speaker" hit HpaeRendererManager::Start::$_7::operator()

5.2 标准库包装帧

GDB堆栈中会出现大量标准库帧,这是HPAE内部任务调度的包装:

std::__h::__invoke
std::__h::function<void ()>::operator()
HpaeNoLockQueue::ProcessRequests
HpaeSignalProcessThread::Run

这些帧属于任务调度机制,不是业务逻辑核心。过滤后得到核心链路:

HpaeRendererStreamImpl::Start
  -> HPAE::HpaeManagerImpl::Start
  -> HPAE::HpaeManager::Start
  -> HPAE::HpaeRendererManager::Start
  -> HPAE::HpaeRendererManager::ConnectInputSession
  -> HPAE::HpaeSinkOutputNode::RenderSinkStart
  -> AudioRenderSink::Start

5.3 AudioRenderSink入口

命中AudioRenderSink::Start时的GDB堆栈:

#0  OHOS::AudioStandard::AudioRenderSink::Start
    at audio_render_sink.cpp:112
#1  OHOS::AudioStandard::HPAE::HpaeSinkOutputNode::RenderSinkStart
    at hpae_sink_output_node.cpp:381
#2  OHOS::AudioStandard::HPAE::HpaeRendererManager::ConnectInputSession
    at hpae_renderer_manager.cpp:472
#3  OHOS::AudioStandard::HPAE::HpaeRendererManager::Start(unsigned int)::$_7::operator()()
    at hpae_renderer_manager.cpp:675
#10 OHOS::AudioStandard::HPAE::HpaeNoLockQueue::ProcessRequests
#11 OHOS::AudioStandard::HPAE::HpaeSignalProcessThread::Run

六、AudioRenderSink调用HDI Proxy

6.1 关键代码分析

AudioRenderSink::Start()的实现:

int32_t AudioRenderSink::Start(void)
{
    std::lock_guard<std::mutex> lock(sinkMutex_);
    
    // 检查是否已启动
    if (started_) {
        return SUCCESS;
    }
    
    // 验证audioRender_指针
    CHECK_AND_RETURN_RET_LOG(audioRender_ != nullptr, 
        ERR_INVALID_HANDLE, "render is nullptr");
    
    // 调用HDI proxy的Start方法
    int32_t ret = audioRender_->Start(audioRender_);
    
    if (ret == SUCCESS) {
        started_ = true;
        AUDIO_INFO_LOG("AudioRenderSink::Start success");
    } else {
        AUDIO_ERR_LOG("AudioRenderSink::Start failed, ret:%{public}d", ret);
    }
    
    return ret;
}

6.2 函数指针验证

在GDB中验证audioRender_->Start的实际函数:

# 打印audioRender_指针
p audioRender_
# 打印Start函数指针
p audioRender_->Start
# 查看函数符号信息
info symbol audioRender_->Start

实测输出:

p audioRender_
$5 = (IAudioRender *) 0x7fbe862800

p audioRender_->Start
$6 = (int32_t (*)(IAudioRender *)) 0x7f3bbd6e70 <AudioRenderProxyStart>

info symbol audioRender_->Start
AudioRenderProxyStart in section .text of .../libaudio_proxy_6.0.z.so

6.3 调用链确认

AudioRenderSink::Start
  -> audioRender_->Start(audioRender_)    // 函数指针调用
  -> AudioRenderProxyStart                 // 实际调用的HDI proxy函数

重要发现audio_server并没有直接调用audio_host中的service实现,而是调用了libaudio_proxy_6.0.z.so中的HDI proxy函数。

七、AudioRenderProxyStart发送CMD_AUDIO_RENDER_START

7.1 生成代码分析

AudioRenderProxyStart()是HDF工具自动生成的代理代码:

static int32_t AudioRenderProxyStart(struct IAudioRender *self)
{
    struct HdfSBuf *audioRenderData = HdfSbufTypedObtain(SBUF_IPC);
    struct HdfSBuf *audioRenderReply = HdfSbufTypedObtain(SBUF_IPC);
    
    // 写入接口token
    if (!HdfRemoteServiceWriteInterfaceToken(self->AsObject(self), audioRenderData)) {
        HDF_LOGE("%{public}s: write interface token failed", __func__);
        HdfSbufRecycle(audioRenderData);
        HdfSbufRecycle(audioRenderReply);
        return HDF_ERR_INVALID_PARAM;
    }
    
    // 调用代理函数,传递CMD_AUDIO_RENDER_START
    int32_t audioRenderRet = AudioRenderProxyCall(
        self, CMD_AUDIO_RENDER_START, audioRenderData, audioRenderReply, false);
    
    HdfSbufRecycle(audioRenderData);
    HdfSbufRecycle(audioRenderReply);
    
    return audioRenderRet;
}

AudioRenderProxyStart调用栈

7.2 验证命令值

在GDB中验证CMD_AUDIO_RENDER_START的值:

CMD_AUDIO_RENDER_START验证

(gdb) frame 1
#1  0x0000007f983d4cc0 in HdfRemoteServiceStub::OnRemoteRequest (this=0x7f989d67c0, code=33, data=..., reply=..., option=...)
    at ../../drivers/hdf_core/adapter/uhdf2/ipc/src/hdf_remote_adapter.cpp:60
60              ret = dispatcher->Dispatch(reinterpret_cast<HdfRemoteService *>(service_->target), code, dataSbuf, replySbuf);
(gdb) p this
$5 = (HdfRemoteServiceStub *) 0x7f989d67c0
(gdb) p this->service_
$6 = (HdfRemoteService *) 0x7f989dde50
(gdb) p this->service_->dispatcher
$7 = (HdfRemoteDispatcher *) 0x7f98a169e0
(gdb) p this->service_->dispatcher->Dispatch
$8 = (int (*)(HdfRemoteService *, int, HdfSBuf *, HdfSBuf *)) 0x7f17291270 <AudioRenderOnRemoteRequest>
(gdb) list *this->service_->dispatcher->Dispatch
0x7f17291270 is in AudioRenderOnRemoteRequest (gen/drivers/interface/audio/v6_0/audio_render_stub.c:1620).
1615        struct AudioRenderStub *stub = CONTAINER_OF(self, struct AudioRenderStub, interface);
1616        return stub->remote;
1617    }
1618
1619    static int32_t AudioRenderOnRemoteRequest(struct HdfRemoteService *remote, int code, struct HdfSBuf *data, struct HdfSBuf *reply)
1620    {
1621        struct AudioRenderStub *stub = (struct AudioRenderStub*)remote;
1622        if (stub == NULL || stub->remote == NULL || stub->interface == NULL) {
1623            HDF_LOGE("%{public}s: invalid stub object", __func__);
1624            return HDF_ERR_INVALID_OBJECT;
1625        }
1626        if (!HdfRemoteServiceCheckInterfaceToken(stub->remote, data)) {
1627            HDF_LOGE("%{public}s: interface token check failed", __func__);
1628            return HDF_ERR_INVALID_PARAM;
1629        }
1630
1631        switch (code) {
1632            case CMD_AUDIO_RENDER_GET_LATENCY:
1633                return SerStubGetLatency(stub->interface, data, reply);
1634            case CMD_AUDIO_RENDER_RENDER_FRAME:
# 直接打印值
p/d CMD_AUDIO_RENDER_START
# 使用typeof技巧查看枚举名称
p (typeof(CMD_AUDIO_RENDER_GET_LATENCY))CMD_AUDIO_RENDER_START
# 验证33对应的枚举
p (typeof(CMD_AUDIO_RENDER_GET_LATENCY))33

实测输出:

$2 = 33
$3 = CMD_AUDIO_RENDER_START

7.3 GDB调试技巧

CMD_AUDIO_RENDER_*是匿名枚举,没有直接的枚举类型名。GDB不能直接使用:

# 错误:类型名不存在
p (AudioRenderInterfaceCode)33

正确做法是借用同一枚举组中的任意常量类型:

# 正确:使用typeof获取枚举类型
p (typeof(CMD_AUDIO_RENDER_GET_LATENCY))33

这样就可以从整数值33反推出对应的枚举常量CMD_AUDIO_RENDER_START

八、AudioRenderProxyCall到HDF Remote Dispatch

8.1 条件断点命中

使用条件断点捕获AudioRenderProxyCall

b AudioRenderProxyCall if id == 33

命中时的GDB堆栈:

AudioRenderProxyCall调用栈

#0  AudioRenderProxyCall
    (self=0x7fbc899b20, id=33, data=0x7f3b0aaac0, 
     reply=0x7f3b0aaae0, isOneWay=false)
    at gen/drivers/interface/audio/v6_0/audio_render_proxy.c:80
#1  AudioRenderProxyStart
    at gen/drivers/interface/audio/v6_0/audio_render_proxy.c:1723
#2  OHOS::AudioStandard::AudioRenderSink::Start
    at audio_render_sink.cpp:144
#3  OHOS::AudioStandard::HPAE::HpaeSinkOutputNode::RenderSinkStart
    at hpae_sink_output_node.cpp:381
#4  OHOS::AudioStandard::HPAE::HpaeRendererManager::ConnectInputSession
    at hpae_renderer_manager.cpp:472
#5  OHOS::AudioStandard::HPAE::HpaeRendererManager::Start(unsigned int)::$_7::operator()()
    at hpae_renderer_manager.cpp:675

8.2 代理调用实现

AudioRenderProxyCall()的实现:

static int32_t AudioRenderProxyCall(struct IAudioRender *self, int32_t id,
    struct HdfSBuf *data, struct HdfSBuf *reply, bool isOneWay)
{
    struct HdfRemoteService *remote = self->AsObject(self);
    
    // 参数检查
    if (remote == NULL ||
        remote->dispatcher == NULL ||
        remote->dispatcher->Dispatch == NULL ||
        remote->dispatcher->DispatchAsync == NULL) {
        return HDF_ERR_INVALID_OBJECT;
    }
    
    // 根据isOneWay选择同步或异步调用
    if (isOneWay) {
        return remote->dispatcher->DispatchAsync(remote, id, data, reply);
    } else {
        return remote->dispatcher->Dispatch(remote, id, data, reply);
    }
}

由于isOneWay=false,实际走的是同步调用路径:

remote->dispatcher->Dispatch(remote, id, data, reply)

这里的id=33就是CMD_AUDIO_RENDER_START

九、HdfRemoteAdapterOptionalDispatch解析Binder Remote

9.1 断点命中

继续命中HdfRemoteAdapterOptionalDispatch

HdfRemoteAdapterOptionalDispatch调用栈

#0  HdfRemoteAdapterOptionalDispatch
    (service=0x7fbbef4bf0, code=33, data=0x7f3b0aaac0, reply=0x7f3b0aaae0, sync=true)
    at hdf_remote_adapter.cpp:111
#1  AudioRenderProxyCall
    (self=0x7fbc899b20, id=33, ...)
    at audio_render_proxy.c:91
#2  AudioRenderProxyStart
    at audio_render_proxy.c:1723
#3  AudioRenderSink::Start
    at audio_render_sink.cpp:144

9.2 关键源码分析

HdfRemoteAdapterOptionalDispatch的关键源码:

static int HdfRemoteAdapterOptionalDispatch(struct HdfRemoteService *service, int code,
    HdfSBuf *data, HdfSBuf *reply, bool sync)
{
    ...
    int flag = sync ? OHOS::MessageOption::TF_SYNC : OHOS::MessageOption::TF_ASYNC;
    OHOS::MessageOption option(flag);
    struct HdfRemoteServiceHolder *holder = reinterpret_cast<struct HdfRemoteServiceHolder *>(service);
    if (dataParcel != nullptr) {
        OHOS::sptr<OHOS::IRemoteObject> remote = holder->remote_;
        if (remote != nullptr) {
            return remote->SendRequest(code, *dataParcel, *replyParcel, option);
        }
    }
    return HDF_FAILURE;
}

关键点

  • HdfRemoteService *service 实际上是 HdfRemoteServiceHolder 的起始地址
  • holder->remote_ 才是真正的 Binder IRemoteObject proxy
  • 不要看 service->target,client proxy 侧的 service->target 可能是 0

9.3 GDB验证Binder对象

在GDB中验证Binder对象:

# 查看HdfRemoteServiceHolder结构
ptype HdfRemoteServiceHolder

# 转换指针类型
p (struct HdfRemoteServiceHolder*)service

# 获取remote_成员
p ((struct HdfRemoteServiceHolder*)service)->remote_

# 查看引用计数
p ((struct HdfRemoteServiceHolder*)service)->remote_.refs_

实测输出:

type = struct HdfRemoteServiceHolder {
    HdfRemoteService service_;
    OHOS::sptr<OHOS::IRemoteObject> remote_;
    OHOS::sptr<OHOS::IRemoteObject::DeathRecipient> deathRecipient_;
    std::__h::u16string descriptor_;
}

$10 = (HdfRemoteServiceHolder *) 0x7fbbef4bf0
$11 = {refs_ = 0x7fbc899970}
$12 = (OHOS::IRemoteObject *) 0x7fbc899970

9.4 获取Binder Handle

继续把 remote_.refs_ 转成 IPCObjectProxy,取 binder handle:

p/d ((OHOS::IPCObjectProxy*)0x7fbc899970)->handle_

实测输出:

$17 = 15

结论audio_server 中 HDF AudioRender proxy 的 Binder handle 是 15。

十、证明Binder对端是audio_host

10.1 查看audio_server的Binder描述符

在板端查看 audio_server 的 binder proc:

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

实测输出:

ref 75509: desc 15 node 75508 s 1 w 0 d 0000000000000000

这表示:audio_server 的 binder desc 15 指向 node 75508。

10.2 查找对应的Binder节点

再去 audio_host 的 binder proc 查同一个 node:

cat /sys/kernel/debug/binder/proc/$(pidof audio_host) | grep "node 75508"

实测输出:

node 75508: u0000007fa96a04f0 c0000007fa96607c0 hs 1 hw 1 ls 0 lw 0 is 1 iw 1 tr 1 proc 4995

10.3 验证进程ID

确认 proc 4995 对应的进程:

ps -ef | grep audio_server

实测输出:

audio 4995 1 0 16:16:49 ? 00:00:02 audio_server

10.4 链路确认

这说明:

audio_server 中的 Binder desc 15
  -> node 75508
  -> node 75508 位于 audio_host
  -> 当前引用进程 proc 4995 是 audio_server

因此,audio_serverAudioRenderProxyCall(id=33) 最终发送到了 audio_host

十一、结论

11.1 完整调用链路

整个调用链路可以整理为:

audio_server:
IpcStreamStub::OnRemoteRequest(IIpcStreamIpcCode::COMMAND_START)
  -> IpcStreamInServer::Start
  -> RendererInServer::Start
  -> RendererInServer::StartInner
  -> HpaeRendererStreamImpl::Start
  -> IHpaeManager::GetHpaeManager().Start(...)
  -> HPAE::HpaeManagerImpl::Start
  -> HPAE::HpaeManager::Start
  -> HPAE::HpaeRendererManager::Start
  -> HPAE::HpaeRendererManager::ConnectInputSession
  -> HPAE::HpaeSinkOutputNode::RenderSinkStart
  -> AudioRenderSink::Start
  -> audioRender_->Start(audioRender_)
  -> AudioRenderProxyStart
  -> AudioRenderProxyCall(id=33)
  -> HdfRemoteAdapterOptionalDispatch(code=33)
  -> remote->SendRequest(33, ...)

audio_host:
HdfRemoteServiceStub::OnRemoteRequest(code=33)

11.2 核心证明点

通过GDB调试和代码分析,我们证明了以下关键点:

1. audio_server 入口 code=4,可反推为 IIpcStreamIpcCode::COMMAND_START
2. HpaeRendererStreamImpl::Start 进入 HPAE
3. HPAE 在线程切换后进入 AudioRenderSink::Start
4. AudioRenderSink::Start 调用 audioRender_->Start
5. audioRender_->Start 的实际函数指针是 AudioRenderProxyStart
6. AudioRenderProxyStart 调用 AudioRenderProxyCall(..., CMD_AUDIO_RENDER_START, ...)
7. CMD_AUDIO_RENDER_START 的整数值是 33
8. HdfRemoteAdapterOptionalDispatch(code=33) 中的 holder->remote_ 是 IPCObjectProxy
9. 该 IPCObjectProxy 的 handle 是 15
10. binder debugfs 证明 audio_server 的 desc 15 指向 audio_host 中的 node

11.3 最终结论

audio_server 收到 media_service 的流 Start 后,
经过 RendererInServer 和 HPAE 启动本地 render sink,
最终通过 HDF AudioRender proxy 向 audio_host 发送 CMD_AUDIO_RENDER_START。
Logo

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

更多推荐