Linux PipeWire深度解析之pw_core_get_registry调用流程与实战(九十一)

简介: CSDN博客专家、《Android系统多媒体进阶实战》作者

博主新书推荐:《Android系统多媒体进阶实战》🚀

Android Audio工程师专栏地址:Audio工程师进阶系列原创干货持续更新中...... 】🚀

Android多媒体专栏地址:多媒体系统工程师系列原创干货持续更新中...... 】🚀

专题一 二:AAOS车载系统+AOSP14系统攻城狮入门视频实战课 🚀

专题三:Android14 Binder之HIDL与AIDL通信实战课 🚀

专题四:Android15快速自定义与集成音效实战课 🚀

专题五:Android15音频策略实战课 🚀

专题六:Android15音频性能实战课(无声/杂音/断音/爆音实战案例) 🚀

人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.
更多原创,欢迎关注:Android系统攻城狮

🍉🍉🍉文章目录🍉🍉🍉

🌻1.前言

本篇目的:

Linux PipeWire深度解析之pw_core_get_registry调用流程与实战。

要点概括

  • 核心功能:从已连接的pw_core对象中获取Registry代理对象,用于发现PipeWire服务端已经注册的全局对象。

  • 工作机制:客户端通过Core接口创建pw_registry代理,再给Registry注册监听器,由服务端异步回调global和global_remove事件。

  • 典型用途:枚举Node、Device、Port、Link、Factory等全局对象,并根据对象id按需调用pw_registry_bind绑定成具体代理对象。

pw_core_get_registry的本质不是"直接获取所有对象列表",而是"创建一个Registry代理对象"。真正的对象枚举结果不会作为函数返回值同步返回,而是通过Registry事件异步通知给客户端。

它位于客户端连接PipeWire服务端之后。应用先通过pw_context_connect得到pw_core,然后通过pw_core_get_registry得到pw_registry,再通过pw_registry_add_listener接收服务端全局对象变化。

它和pw_registry_bind的职责不同。pw_core_get_registry负责获取"对象目录入口",pw_registry_bind负责根据Registry事件中的全局对象id绑定具体对象。它和pw_core_sync也不同,pw_core_sync用于做客户端和服务端的往返同步,不负责发现对象。

🌻2.应用场景与用法

pw_core_get_registry

是PipeWireCore API中用于获取Registry代理对象的接口。

它处于PipeWire客户端发现服务端对象的入口位置。客户端连接PipeWire服务端后,并不知道服务端当前有哪些Node、Device、Factory、Link等对象。应用需要先获取Registry,再监听Registry事件,才能拿到这些全局对象的id、type、version、permissions和properties。

pw_core_get_registry用于从pw_core获取pw_registry代理对象。

函数原型

c 复制代码
struct pw_registry *pw_core_get_registry(struct pw_core *core,
                                         uint32_t version,
                                         size_t user_data_size);

参数说明

c 复制代码
struct pw_core *core;

core表示已经连接成功的PipeWireCore代理对象。

该对象通常由pw_context_connect返回。只有拿到有效core之后,客户端才具备和PipeWire服务端通信的基础能力。pw_core_get_registry依赖这个core向服务端发起get_registry请求。

c 复制代码
uint32_t version;

version表示客户端希望使用的Registry接口版本。

实际开发中通常传入:

c 复制代码
PW_VERSION_REGISTRY

它用于声明客户端期望的pw_registry接口版本,便于协议层做版本匹配。

c 复制代码
size_t user_data_size;

user_data_size表示为返回的Registry代理对象附加的用户数据大小。

如果不需要给Registry代理对象附加私有数据,可以传入0。它不是Buffer大小,也不是对象枚举结果大小,只是代理对象私有数据区域大小。

返回值

成功时返回:

c 复制代码
struct pw_registry *

表示创建成功的Registry代理对象。

失败时返回NULL。

需要注意,返回非NULL只表示Registry代理对象创建成功,并不表示已经拿到了全部全局对象。全局对象信息需要通过pw_registry_add_listener注册监听器后,由global事件异步返回。

应用场景

第一类场景是枚举PipeWire服务端对象。

调试工具、图形化监控工具、设备管理程序通常需要获取服务端当前有哪些Node、Device、Port、Link和Factory。它们会先调用pw_core_get_registry获取Registry,再监听global事件构建设备和Graph视图。

第二类场景是查找目标播放或录音Node。

客户端可以通过Registry事件遍历所有Node,根据media.class、node.name、object.path等属性判断目标对象,再调用pw_registry_bind绑定该Node。

第三类场景是实现设备热插拔监听。

设备新增、删除、Link变化、Node变化都会通过Registry事件体现。客户端不需要轮询服务端,而是通过global和global_remove事件维护本地对象缓存。

第四类场景是开发PipeWire策略或诊断工具。

WirePlumber、调试程序、测试工具、对象分析工具都需要依赖Registry理解当前PipeWire服务端状态。Registry相当于服务端全局对象目录,是客户端理解PipeWire运行状态的入口。

🌻3.调用流程剖析

🌻3.1核心步骤

1.应用调用pw_init初始化PipeWire运行环境。

2.应用创建MainLoop和Context。

3.应用调用pw_context_connect连接PipeWire服务端,成功后得到pw_core对象。

4.应用调用pw_core_get_registry,从pw_core创建pw_registry代理对象。

5.应用调用pw_registry_add_listener,为Registry注册事件监听器。

6.PipeWire服务端通过Registry向客户端发送global事件,通知当前已经存在的全局对象。

7.应用在global回调中读取id、permissions、type、version和props。

8.应用根据type和props判断对象类型,例如Node、Device、Factory、Port、Link。

9.如果应用需要访问具体对象,则调用pw_registry_bind绑定目标id。

10.绑定成功后,客户端得到对应类型的代理对象,例如pw_node、pw_device或pw_proxy。

11.当服务端对象被移除时,Registry触发global_remove事件,应用同步清理本地缓存。

12.应用退出时销毁Registry代理对象,最后断开Core连接并释放Context。

🌻3.2调用流程图

🌻3.3生命周期图

🌻4.实战应用案例

下面以"枚举PipeWire服务端全局对象"为例,说明pw_core_get_registry在真实开发中的用法。

该案例的目标是:客户端连接PipeWire服务端后,通过Registry监听服务端对象,并筛选出Node对象。Node可能表示播放流、录音流、ALSA设备Node、蓝牙音频Node、视频Node等。

c 复制代码
#include <pipewire/pipewire.h>

struct app_data {
    struct pw_main_loop *loop;
    struct pw_context *context;
    struct pw_core *core;
    struct pw_registry *registry;

    struct spa_hook registry_listener;
};

static void on_registry_global(void *data,
                               uint32_t id,
                               uint32_t permissions,
                               const char *type,
                               uint32_t version,
                               const struct spa_dict *props)
{
    const char *name;
    const char *media_class;

    if (props == NULL)
        return;

    name = spa_dict_lookup(props, "node.name");
    media_class = spa_dict_lookup(props, "media.class");

    if (type != NULL &&
        spa_streq(type, PW_TYPE_INTERFACE_Node)) {
        /*
         * 这里可以记录Node对象:
         *
         * id          : 服务端全局对象id
         * permissions : 当前客户端权限
         * type        : PipeWire接口类型
         * version     : 对象接口版本
         * props       : 对象属性
         *
         * 如果后续需要真正访问该Node,
         * 可以使用pw_registry_bind绑定这个id。
         */
        (void)id;
        (void)permissions;
        (void)version;
        (void)name;
        (void)media_class;
    }
}

static void on_registry_global_remove(void *data, uint32_t id)
{
    /*
     * 服务端全局对象被移除。
     * 如果本地缓存了该id,需要在这里删除。
     */
    (void)data;
    (void)id;
}

static const struct pw_registry_events registry_events = {
    PW_VERSION_REGISTRY_EVENTS,
    .global = on_registry_global,
    .global_remove = on_registry_global_remove,
};

static int init_registry(struct app_data *app)
{
    app->registry = pw_core_get_registry(app->core,
                                         PW_VERSION_REGISTRY,
                                         0);
    if (app->registry == NULL)
        return -1;

    pw_registry_add_listener(app->registry,
                             &app->registry_listener,
                             &registry_events,
                             app);

    return 0;
}

这个案例只完成对象发现,不直接绑定对象。这样做的好处是边界清晰:Registry负责发现对象,bind负责访问对象。实际项目中可以先把global事件中的对象缓存到本地表,再按业务需求绑定目标对象。

如果要绑定Node对象,可以在global回调中进一步处理:

c 复制代码
static void on_registry_global(void *data,
                               uint32_t id,
                               uint32_t permissions,
                               const char *type,
                               uint32_t version,
                               const struct spa_dict *props)
{
    struct app_data *app = data;
    struct pw_proxy *proxy;
    const char *media_class;

    if (props == NULL)
        return;

    if (!spa_streq(type, PW_TYPE_INTERFACE_Node))
        return;

    media_class = spa_dict_lookup(props, "media.class");
    if (media_class == NULL)
        return;

    if (spa_streq(media_class, "Audio/Sink")) {
        proxy = pw_registry_bind(app->registry,
                                 id,
                                 type,
                                 version,
                                 0);

        /*
         * proxy表示绑定后的Node代理对象。
         * 后续可以根据需要注册Node事件监听器,
         * 或者保存该proxy用于后续管理。
         */
        (void)proxy;
    }
}

这段代码体现了pw_core_get_registry和pw_registry_bind的典型配合方式。

pw_core_get_registry只创建Registry代理,不负责筛选对象,也不负责创建Node代理。global回调负责发现对象,业务逻辑负责判断对象是否符合条件,pw_registry_bind负责把目标id绑定成客户端可操作的代理对象。

工程使用中要注意三点。

第一,不能把pw_core_get_registry理解成同步枚举接口。它返回的是Registry代理对象,不是对象列表。对象列表来自后续global事件。

第二,global事件中的id是服务端全局对象id。这个id适合传给pw_registry_bind,但不能直接当成本地指针使用。

第三,global_remove事件必须处理。PipeWire服务端对象是动态变化的,设备拔出、流退出、Link销毁都会导致对象移除。如果本地缓存不清理,后续很容易访问过期对象。

🌻5.一句话总结

pw_core_get_registry是PipeWire客户端发现服务端全局对象的入口接口:它从pw_core创建pw_registry代理,通过Registry事件异步获得Node、Device、Port、Link等对象,再按需使用pw_registry_bind绑定具体对象。

相关推荐
其实防守也摸鱼11 分钟前
智能体推荐:精选 AI Agent 工具与实战指南
运维·开发语言·人工智能·学习·web安全·自动化
小义_16 分钟前
JDK 深度解析
java·linux·开发语言·python·面试
邪修king17 分钟前
Re:Linux 系统编程篇 (十二):进程篇(一)基础与 fork 系统调用全解
linux·运维·服务器
牢姐与蒯21 分钟前
Linux进程(五).之环境变量
linux·运维·服务器·ubuntu
ZGi.ai21 分钟前
ZGI 文件产物:生成报告后怎样交付
大数据·运维·workflow·权限管理·zgi·智能体交付·文件产物
楠楠子呀31 分钟前
号码状态核验:从数据清洗到合规处理的实现思路
运维·自动化
raindayinrain37 分钟前
深入理解Linux内核--系统调用,性能优化
linux·性能优化·系统调用·返回值·入参·内核态用户态地址访问
小周学学学41 分钟前
GLPI 项目解读:开源 IT 资产管理与 ITSM 服务台平台
运维·开源·自动化·资产管理
码上有光43 分钟前
Linux:进程控制和进程替换
java·linux·服务器·进程控制·进程替换