OpenHarmony播放音乐start请求从audio_host到ALSA库的调用流程04
1)文章由移远通信技术股份有限公司提供
2)以下内容包含了个人理解,仅供参考,如有不合理处,请联系笔者修改/删除
文章目录
- 一、背景与目标
- 二、建议断点
- 三、注意 SerStubStart 和 RenderStartImpl 有多个位置
- 四、audio_host 入口:SerStubStart
- 五、在 GDB 中反推 code=33 的枚举含义
- 六、SerStubStart 调用 AudioRenderStart
- 七、audio_host 内部调用到 RK3576 RenderStartImpl
- 八、AudioRenderStart 到 AudioOutputRenderStart
- 九、AudioOutputRenderStart 调用 renderIns->Start
- 十、RK3576 RenderStartImpl 做什么
- 十一、串到 ALSA 库:snd_ctl_elem_write
- 十二、SndElementWriteInt 如何调用 ALSA control
- 十三、SndElementWriteSwitch 也会进入 snd_ctl_elem_write
- 十四、Start 路径不是 PCM 写数据路径
- 十五、结论
一、背景与目标
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 前置知识回顾
前面的文章已经证明,播放 Start 请求已经从 media_service
下发到 audio_server,再通过 HDI proxy 到达 audio_host:
// 前文已证明的链路
media_service
-> audio_server
-> AudioRenderProxyCall(id=33)
-> audio_host
audio_host 是用户态音频 HDI service 所在进程,负责处理来自 audio_server 的音频硬件操作请求。本文将继续分析最后一段关键路径:audio_host 收到 CMD_AUDIO_RENDER_START 后,如何通过 HDI service、ALSA adapter、RK3576 板级实现,最终调用到 ALSA 库。
1.3 本文追踪目标
本文聚焦于 audio_host 进程收到 CMD_AUDIO_RENDER_START 命令后的完整调用链路分析,涵盖以下关键路径:
- HDI Service层:
audio_host中的 HDF IPC 请求处理 - ALSA Adapter层:OpenHarmony 音频适配器实现
- 板级驱动层:RK3576 特定的音频驱动实现
- ALSA库层:最终调用到 ALSA 用户态库
通过 GDB 调试验证以下完整调用链路:
// 本文要证明的完整链路
audio_host
HdfRemoteServiceStub::OnRemoteRequest(code=33)
-> SerStubStart
-> AudioRenderStart
-> AudioOutputRenderStart
-> RenderStartImpl
-> SndElementWriteInt / SndElementWriteSwitch
-> snd_ctl_elem_write
需要证明的两个核心问题:
audio_host收到的code=33与audio_server发出的CMD_AUDIO_RENDER_START是同一个请求- RK3576 的 Start 流程最终进入 ALSA control/mixer API,而非 PCM 数据写入路径
二、建议断点
attach audio_host 后:
handle SIG38 nostop noprint pass
最小断点组合:
b SerStubStart
b RenderStartImpl
这两个断点的作用:
SerStubStart:
证明 audio_host 收到了 audio_server 发来的 Start 请求。
RenderStartImpl:
通过底层栈一次性补全 audio_host 内部调用关系。
为了把 ALSA 库也串上,再加一个 ALSA API 断点:
b snd_ctl_elem_write
如果符号还没有加载:
set breakpoint pending on
b snd_ctl_elem_write
三、注意 SerStubStart 和 RenderStartImpl 有多个位置
实际下断点时,GDB 可能显示多个位置:
Breakpoint 1 at 0x7f27f0fe5c: SerStubStart. (2 locations)
1.1 audio_capture_stub.c:1069
1.2 audio_render_stub.c:1331
Breakpoint 2 at 0x7f27cccf40: RenderStartImpl. (2 locations)
2.1 alsa_snd_render.c:778
2.2 device/board/rockchip/rk3576/audio_alsa/vendor_render.c:165
原因是 capture 和 render 生成代码里都有 SerStubStart,
通用 ALSA adapter 和 RK3576 板级代码里也都有 RenderStartImpl。
本文关注播放链路,所以关键位置是:
gen/drivers/interface/audio/v6_0/audio_render_stub.c:1331
device/board/rockchip/rk3576/audio_alsa/vendor_render.c:165
如果想减少干扰,可以删除不关心的位置:
delete 1.1
delete 2.1
也可以保留多个位置,命中后通过文件路径判断当前是不是 render 路径。
四、audio_host 入口:SerStubStart
命中 SerStubStart:

Thread 2 "OS_IPC_0_4996" hit Breakpoint 1,
SerStubStart(serviceImpl=0x7fa968a830, audioRenderData=<optimized out>,
audioRenderReply=<optimized out>)
at gen/drivers/interface/audio/v6_0/audio_render_stub.c:1331
GDB 栈:
#0 SerStubStart
at gen/drivers/interface/audio/v6_0/audio_render_stub.c:1331
#1 HdfRemoteServiceStub::OnRemoteRequest
(this=0x7fa96607c0, code=33, data=..., reply=..., option=...)
at drivers/hdf_core/adapter/uhdf2/ipc/src/hdf_remote_adapter.cpp:60
#2 OHOS::IPCObjectStub::SendRequestInner
(this=0x7fa96607c0, code=33, data=..., reply=..., option=...)
at ipc_object_stub.cpp:409
#3 OHOS::BinderInvoker::GeneralServiceSendRequest
#4 OHOS::BinderInvoker::TargetStubSendRequest
#5 OHOS::BinderInvoker::Transaction
这里的关键是:
HdfRemoteServiceStub::OnRemoteRequest(code=33)
-> SerStubStart
这正好对应上一篇 audio_server 侧的:
AudioRenderProxyCall(id=33)
-> HdfRemoteAdapterOptionalDispatch(code=33)
-> remote->SendRequest(33, ...)
五、在 GDB 中反推 code=33 的枚举含义
在 audio_host 的 GDB 中,可以直接反推:
p (typeof(CMD_AUDIO_RENDER_GET_LATENCY))33
实测:
$1 = CMD_AUDIO_RENDER_START
如果想使用当前栈里的变量,而不是手写 33:
frame 1
p code
p (typeof(CMD_AUDIO_RENDER_GET_LATENCY))code
预期:
$ = 33
$ = CMD_AUDIO_RENDER_START
这里使用 typeof(CMD_AUDIO_RENDER_GET_LATENCY),是因为 CMD_AUDIO_RENDER_* 来自匿名 enum。没有一个稳定可用的枚举类型名时,可以借同组任意枚举常量的类型,把整数还原成枚举项。
因此,audio_host 入口可以写成:
audio_host 收到 HDF IPC 请求:
code=33 == CMD_AUDIO_RENDER_START
随后分发到 audio_render_stub.c 的 SerStubStart。
六、SerStubStart 调用 AudioRenderStart
SerStubStart 生成代码:
static int32_t SerStubStart(struct IAudioRender *serviceImpl,
struct HdfSBuf *audioRenderData, struct HdfSBuf *audioRenderReply)
{
int32_t audioRenderRet = HDF_FAILURE;
if (serviceImpl == NULL) {
audioRenderRet = HDF_ERR_INVALID_OBJECT;
goto FINISHED;
}
if (serviceImpl->Start == NULL) {
audioRenderRet = HDF_ERR_NOT_SUPPORT;
goto FINISHED;
}
audioRenderRet = serviceImpl->Start(serviceImpl);
...
}
也就是说,SerStubStart 只是 HDI stub 层,它会继续调用 IAudioRender 服务端对象上的函数指针:
serviceImpl->Start(serviceImpl)
从后续栈可知,该函数指针进入:
AudioRenderStart
七、audio_host 内部调用到 RK3576 RenderStartImpl
继续执行,命中 RenderStartImpl:
Thread 2 "OS_IPC_0_4996" hit Breakpoint 2,
RenderStartImpl(renderIns=0x7fa9674040)
at device/board/rockchip/rk3576/audio_alsa/vendor_render.c:165
GDB 栈:

#0 RenderStartImpl
at device/board/rockchip/rk3576/audio_alsa/vendor_render.c:165
#1 AudioOutputRenderStart
at drivers/peripheral/audio/supportlibs/alsa_adapter/src/alsa_lib_render.c:407
#2 AudioRenderStart
at drivers/peripheral/audio/hdi_service/primary_impl/src/audio_render.c:107
#3 SerStubStart
at gen/drivers/interface/audio/v6_0/audio_render_stub.c:1343
#4 HdfRemoteServiceStub::OnRemoteRequest(code=33)
at drivers/hdf_core/adapter/uhdf2/ipc/src/hdf_remote_adapter.cpp:60
#5 OHOS::IPCObjectStub::SendRequestInner(code=33)
at ipc_object_stub.cpp:409
这张栈已经把 audio_host 内部主体链路补齐:
HdfRemoteServiceStub::OnRemoteRequest(code=33)
-> SerStubStart
-> AudioRenderStart
-> AudioOutputRenderStart
-> RenderStartImpl
八、AudioRenderStart 到 AudioOutputRenderStart
AudioRenderStart 位于:
drivers/peripheral/audio/hdi_service/primary_impl/src/audio_render.c
从栈看:
AudioRenderStart(handle=0x7fa968a830)
-> AudioOutputRenderStart(...)
这一层属于 HDI service primary 实现。它把 IAudioRender::Start 这个 HDI 接口调用,
继续下发到 ALSA adapter 的 render start 操作。
九、AudioOutputRenderStart 调用 renderIns->Start
AudioOutputRenderStart 位于:
drivers/peripheral/audio/supportlibs/alsa_adapter/src/alsa_lib_render.c
关键代码:
int32_t AudioOutputRenderStart(
const struct DevHandle *handle, int cmdId, const struct AudioHwRenderParam *handleData)
{
int32_t ret;
struct AlsaRender *renderIns = NULL;
CHECK_NULL_PTR_RETURN_DEFAULT(handleData);
renderIns = RenderGetInstance(handleData->renderMode.hwInfo.adapterName);
CHECK_NULL_PTR_RETURN_DEFAULT(renderIns);
ret = renderIns->Start(renderIns);
if (ret != HDF_SUCCESS) {
return ret;
}
return HDF_SUCCESS;
}
这里的关键是:
renderIns->Start(renderIns)
对 RK3576 primary render 来说,这个函数指针被板级代码覆盖为:
device/board/rockchip/rk3576/audio_alsa/vendor_render.c
RenderStartImpl
所以调用进入:
AudioOutputRenderStart
-> RenderStartImpl
十、RK3576 RenderStartImpl 做什么
RenderStartImpl 代码:

static int32_t RenderStartImpl(struct AlsaRender *renderIns)
{
int32_t ret;
struct AlsaMixerCtlElement elem;
struct AlsaSoundCard *cardIns = (struct AlsaSoundCard *)renderIns;
SndElementItemInit(&elem);
long volume = SND_DEFAULT_PLAYBACK_VOL;
elem.numid = SND_NUMID_DACL_PLAYBACK_VOL;
elem.name = SND_ELEM_DACL_PLAYBACK_VOL;
ret = SndElementWriteInt(cardIns, &elem, volume);
...
elem.numid = SND_NUMID_DACR_PLAYBACK_VOL;
elem.name = SND_ELEM_DACR_PLAYBACK_VOL;
ret = SndElementWriteInt(cardIns, &elem, volume);
...
bool speakerOn = (renderIns->descPins != PIN_OUT_HEADSET);
bool headphoneOn = (renderIns->descPins == PIN_OUT_HEADSET);
elem.numid = SND_NUMID_SPEAKER_SWITCH;
elem.name = SND_ELEM_SPEAKER_SWITCH;
ret = SndElementWriteSwitch(cardIns, &elem, speakerOn);
...
elem.numid = SND_NUMID_SPK_SWITCH;
elem.name = SND_ELEM_SPK_SWITCH;
ret = SndElementWriteSwitch(cardIns, &elem, speakerOn);
...
elem.numid = SND_NUMID_HEADPHONE_SWITCH;
elem.name = SND_ELEM_HEADPHONE_SWITCH;
ret = SndElementWriteSwitch(cardIns, &elem, headphoneOn);
...
elem.numid = SND_NUMID_HP_SWITCH;
elem.name = SND_ELEM_HP_SWITCH;
ret = SndElementWriteSwitch(cardIns, &elem, headphoneOn);
...
return HDF_SUCCESS;
}
它主要做两类事情:
1. 设置 DAC 左右声道播放音量:
SND_NUMID_DACL_PLAYBACK_VOL
SND_NUMID_DACR_PLAYBACK_VOL
2. 根据当前输出设备打开 speaker/headphone 相关 switch:
SND_NUMID_SPEAKER_SWITCH
SND_NUMID_SPK_SWITCH
SND_NUMID_HEADPHONE_SWITCH
SND_NUMID_HP_SWITCH
所以,RK3576 的 Start 阶段重点不是写 PCM 数据,而是配置 codec/mixer 输出通路。
十一、串到 ALSA 库:snd_ctl_elem_write
为了证明已经进入 ALSA 库,在 GDB 中对 snd_ctl_elem_write 下断点:
b snd_ctl_elem_write
c
bt
实测命中:
Thread 2 "OS_IPC_0_4996" hit Breakpoint 3,
snd_ctl_elem_write(ctl=0x7fa9669c30, data=0x7fa96fcc90)
at third_party/alsa-lib/src/control/control.c:1037
GDB 栈:

#0 snd_ctl_elem_write
at third_party/alsa-lib/src/control/control.c:1037
#1 SndElementWriteInt
at drivers/peripheral/audio/supportlibs/alsa_adapter/src/alsa_soundcard.c:1117
#2 RenderStartImpl
at device/board/rockchip/rk3576/audio_alsa/vendor_render.c:172
#3 AudioOutputRenderStart
at drivers/peripheral/audio/supportlibs/alsa_adapter/src/alsa_lib_render.c:407
#4 AudioRenderStart
at drivers/peripheral/audio/hdi_service/primary_impl/src/audio_render.c:107
#5 SerStubStart
at gen/drivers/interface/audio/v6_0/audio_render_stub.c:1343
#6 HdfRemoteServiceStub::OnRemoteRequest(code=33)
at drivers/hdf_core/adapter/uhdf2/ipc/src/hdf_remote_adapter.cpp:60
#7 OHOS::IPCObjectStub::SendRequestInner(code=33)
at ipc_object_stub.cpp:409
这张栈把最终链路闭合:
HdfRemoteServiceStub::OnRemoteRequest(code=33)
-> SerStubStart
-> AudioRenderStart
-> AudioOutputRenderStart
-> RenderStartImpl
-> SndElementWriteInt
-> snd_ctl_elem_write
其中 snd_ctl_elem_write 位于:
third_party/alsa-lib/src/control/control.c
这就是 ALSA 用户态库的 control API。
十二、SndElementWriteInt 如何调用 ALSA control
SndElementWriteInt 位于:
drivers/peripheral/audio/supportlibs/alsa_adapter/src/alsa_soundcard.c
关键代码:
int32_t SndElementWriteInt(
struct AlsaSoundCard *cardIns, const struct AlsaMixerCtlElement *ctlElem, long value)
{
snd_ctl_t *alsaHandle = NULL;
snd_ctl_elem_id_t *elem_id = NULL;
snd_ctl_elem_info_t *elem_info = NULL;
snd_ctl_elem_value_t *elem_value = NULL;
int ret = snd_ctl_open(&alsaHandle, cardIns->ctrlName, 0);
...
snd_ctl_elem_id_alloca(&elem_id);
snd_ctl_elem_info_alloca(&elem_info);
snd_ctl_elem_value_alloca(&elem_value);
ret = SetElementInfo(alsaHandle, ctlElem, elem_info, elem_id);
...
if (!snd_ctl_elem_info_is_writable(elem_info)) {
return HDF_FAILURE;
}
snd_ctl_elem_value_set_id(elem_value, elem_id);
snd_ctl_elem_type_t type = snd_ctl_elem_info_get_type(elem_info);
if (type == SND_CTL_ELEM_TYPE_INTEGER) {
snd_ctl_elem_value_set_integer(elem_value, 0, value);
} else if (type == SND_CTL_ELEM_TYPE_INTEGER64) {
snd_ctl_elem_value_set_integer64(elem_value, 0, (long long)value);
} else {
return HDF_FAILURE;
}
ret = snd_ctl_elem_write(alsaHandle, elem_value);
...
}
这说明 RenderStartImpl 中的:
SndElementWriteInt(cardIns, &elem, volume)
最终转成 ALSA control 操作:
snd_ctl_open
SetElementInfo
snd_ctl_elem_value_set_integer
snd_ctl_elem_write
十三、SndElementWriteSwitch 也会进入 snd_ctl_elem_write
RenderStartImpl 后面还有多次:
SndElementWriteSwitch(cardIns, &elem, speakerOn);
SndElementWriteSwitch(cardIns, &elem, headphoneOn);
SndElementWriteSwitch 的结构和 SndElementWriteInt 类似,只是写入的是 boolean 类型:
if (type == SND_CTL_ELEM_TYPE_BOOLEAN) {
int value = on ? 1 : 0;
snd_ctl_elem_value_set_boolean(elem_value, 0, value);
}
ret = snd_ctl_elem_write(alsaHandle, elem_value);
如果继续执行:
c
bt
后续还会再次命中 snd_ctl_elem_write,上层会变成:
#0 snd_ctl_elem_write
#1 SndElementWriteSwitch
#2 RenderStartImpl at vendor_render.c:190/198/206/214
这证明 speaker/headphone switch 同样是通过 ALSA control API 写入的。
十四、Start 路径不是 PCM 写数据路径
这里需要特别说明:Start 这条路径串到的是 ALSA control/mixer,不是 ALSA PCM 写数据。
也就是说,本文证明的是:
CMD_AUDIO_RENDER_START
-> 打开或配置 RK3576 播放输出通路
-> 调用 snd_ctl_elem_write 写 mixer/control
而不是:
CMD_AUDIO_RENDER_START
-> snd_pcm_writei
真正把音频 PCM 数据写进 ALSA 的路径属于 Write / RenderFrame 流程,通常会走:
IAudioRender::RenderFrame
-> AudioRenderRenderFrame
-> AudioOutputRenderWrite
-> RenderWriteImpl
-> RenderWritei
-> snd_pcm_writei
audio_host 的 Start 请求已经进入 ALSA 用户态库,并对 codec/mixer 控件进行了配置。
十五、结论
链路可以整理为:
audio_host:
HdfRemoteServiceStub::OnRemoteRequest(code=33)
-> SerStubStart
-> serviceImpl->Start(serviceImpl)
-> AudioRenderStart
-> AudioOutputRenderStart
-> renderIns->Start(renderIns)
-> RenderStartImpl
-> SndElementWriteInt
-> snd_ctl_elem_write
其中:
code=33 == CMD_AUDIO_RENDER_START
对 RK3576 来说,RenderStartImpl 做的是:
1. 设置 DAC 左右声道播放音量。
2. 根据输出设备打开 speaker/headphone switch。
3. 通过 SndElementWriteInt / SndElementWriteSwitch 写 ALSA mixer/control。
4. 最终进入 alsa-lib 的 snd_ctl_elem_write。
完整四篇文章合起来,Start 请求从 app 到底层 ALSA control/mixer 的完整链路就是:
app:
MiniMusic.hap
-> Index.ets 点击 Play
-> AVPlayerNapi::JsPlay
-> AVPlayerNapi::PlayTask
-> PlayerImpl::Play
-> PlayerServiceProxy::Play
-> SendRequest(PLAY)
media_service:
PlayerServiceStub::Play
-> PlayerServer::Play
-> PlayerServer::OnPlay
-> PlayerServer::HandlePlay
-> HiPlayerImpl::Play
-> pipeline_->Start()
-> Pipeline::Start()
-> Filter::Start()
-> AudioSinkFilter::DoStart
-> AudioSink::Start
-> AudioServerSinkPlugin::Start
-> AudioRendererPrivate::Start
-> RendererInClientInner::StartAudioStream
-> IpcStreamProxy::Start
-> SendRequest(IIpcStreamIpcCode::COMMAND_START)
audio_server:
IpcStreamStub::OnRemoteRequest(COMMAND_START)
-> RendererInServer::Start
-> HpaeRendererStreamImpl::Start
-> HPAE::HpaeRendererManager::Start
-> AudioRenderSink::Start
-> AudioRenderProxyStart
-> AudioRenderProxyCall(CMD_AUDIO_RENDER_START)
audio_host:
HdfRemoteServiceStub::OnRemoteRequest(CMD_AUDIO_RENDER_START)
-> SerStubStart
-> AudioRenderStart
-> AudioOutputRenderStart
-> RenderStartImpl
-> SndElementWriteInt / SndElementWriteSwitch
-> snd_ctl_elem_write
这就是 OpenHarmony 6.1 在 RK3576 上,播放 Start 请求从 app 一路到 ALSA control/mixer 的完整实测链路。
更多推荐

所有评论(0)