Linux PipeWire深度解析之pw_stream_new调用流程与实战(七十八)

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

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

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

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

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

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

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

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

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

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

🍉🍉🍉文章目录🍉🍉🍉

🌻1.前言

本篇目的:

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

要点概括

  • 核心功能:创建一个PipeWireStream对象,为后续播放、录音、视频采集或视频输出建立客户端侧控制入口。

  • 工作机制:基于已有pw_core创建pw_stream结构,保存Stream名称、属性、Core关联关系、状态管理和事件基础设施。

  • 典型用途:应用已经连接PipeWire服务端后,需要创建一个可连接到Graph的媒体Stream。

pw_stream_new的本质是"创建Stream控制对象",不是"开始传输媒体数据"。它只让应用得到一个struct pw_stream*,后续还需要通过pw_stream_add_listener注册事件,通过pw_stream_connect接入PipeWireGraph,才能进入真正的数据处理链路。

它和pw_stream_connect的边界很清楚。pw_stream_new负责创建Stream对象;pw_stream_connect负责把Stream连接到PipeWireGraph。没有connect之前,Stream还没有远端Node、Port、Link和Buffer。

它和pw_stream_dequeue_buffer也不同。pw_stream_dequeue_buffer用于process回调中的Buffer取出,属于数据路径;pw_stream_new属于对象创建路径。前者处理媒体数据,后者建立Stream生命周期起点。

它和pw_stream_new_simple也不同。pw_stream_new适合应用已经持有pw_core的场景;pw_stream_new_simple更适合简单示例或轻量客户端,它把部分Core和事件循环处理做了封装。

🌻2.应用场景与用法

pw_stream_new

是PipeWireStream API中用于创建Stream对象的接口。

它位于PipeWire客户端接入链路的起点。应用连接到PipeWire服务端并获得pw_core之后,就可以调用pw_stream_new创建一个Stream对象。这个Stream对象后续可以配置事件、连接目标、协商格式、接收Buffer事件,并进入process回调处理媒体数据。

pw_stream_new用于基于已有pw_core创建一个PipeWireStream对象。

函数原型

c 复制代码
struct pw_stream *pw_stream_new(struct pw_core *core,
                                const char *name,
                                struct pw_properties *props);

参数说明

c 复制代码
struct pw_core *core;

core表示应用已经连接到PipeWire服务端后得到的Core对象。

它是Stream连接PipeWire服务端的基础。pw_stream_new创建出来的Stream会绑定到这个Core,后续pw_stream_connect会通过该Core与服务端交互。

c 复制代码
const char *name;

name表示Stream名称。

这个名称通常用于调试、对象标识、日志显示和会话管理策略识别。例如播放器可以设置为"music-playback",录音程序可以设置为"audio-capture"

c 复制代码
struct pw_properties *props;

props表示Stream属性集合。

常见属性包括媒体类型、媒体分类、应用名称、节点名称、Stream角色等。它会影响PipeWire和WirePlumber对这个Stream的识别、路由和策略处理。

工程上通常通过pw_properties_new创建props,然后传给pw_stream_new。传入后不要再把同一个props当作普通外部对象随意释放或复用。

返回值

成功时返回:

c 复制代码
struct pw_stream *

表示新创建的Stream对象。

失败时返回NULL。常见原因包括core无效、内存分配失败或内部初始化失败。

返回成功并不表示Stream已经连接成功,也不表示已经有Buffer可用。此时只是创建了客户端侧Stream对象。要进入媒体处理链路,还需要继续调用pw_stream_connect。

应用场景

第一类场景是播放器创建播放Stream。

播放器先连接PipeWire服务端,得到pw_core,然后调用pw_stream_new创建播放Stream,再设置媒体属性并连接到默认Sink或指定Node。

第二类场景是录音程序创建采集Stream。

录音程序同样先创建Stream对象,再通过pw_stream_connect以输入方向连接到Source或录音设备Node,之后在process回调中读取采集数据。

第三类场景是复杂客户端统一管理多个Stream。

例如一个音视频会议程序可能同时创建麦克风采集Stream、扬声器播放Stream、摄像头采集Stream和屏幕共享Stream。使用pw_stream_new可以让这些Stream共享同一个pw_core和事件循环上下文。

第四类场景是需要明确控制Core生命周期的程序。

如果应用已经自己管理pw_context、pw_core、pw_loop和权限逻辑,就更适合使用pw_stream_new,而不是使用封装更高的pw_stream_new_simple。

🌻3.调用流程剖析

🌻3.1核心步骤

1.应用初始化PipeWire运行环境,并连接PipeWire服务端。

2.应用获得pw_core对象。

3.应用准备Stream名称和属性props。

4.应用调用pw_stream_new(core,name,props)。

5.pw_stream_new检查core参数,并为Stream分配客户端侧结构。

6.Stream保存Core引用、Stream名称和属性集合。

7.Stream初始化内部状态、事件Hook、Buffer列表和监听结构。

8.Stream进入未连接状态,此时还没有接入PipeWireGraph。

9.pw_stream_new返回struct pw_stream*给应用。

10.应用调用pw_stream_add_listener注册Stream事件。

11.应用调用pw_stream_connect设置方向、目标ID、连接标志和格式参数。

12.连接完成后,Stream才进入格式协商、Buffer管理和process数据处理阶段。

🌻3.2调用流程图

🌻3.3生命周期图

🌻4.实战应用案例

下面以"创建一个播放Stream对象"为例,说明pw_stream_new的典型使用方式。

这个案例只展示pw_stream_new在主链路中的位置,不展示编译命令和程序运行结果。

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

struct app_data {
    struct pw_core *core;
    struct pw_stream *stream;
    struct spa_hook stream_listener;
};

首先定义应用数据结构。

core表示已经连接到PipeWire服务端的Core对象。

stream表示通过pw_stream_new创建出来的Stream对象。

stream_listener用于保存事件监听Hook,避免监听器生命周期过早结束。

c 复制代码
static void on_stream_process(void *userdata)
{
    struct app_data *data = userdata;
    struct pw_buffer *b;

    b = pw_stream_dequeue_buffer(data->stream);
    if (b == NULL)
        return;

    /*
     * 播放方向:
     * 这里通常向Buffer写入PCM数据。
     *
     * 采集方向:
     * 这里通常从Buffer读取PCM数据。
     */

    pw_stream_queue_buffer(data->stream, b);
}

process回调不是由pw_stream_new直接触发的。它只有在Stream完成connect、进入Graph调度,并且有可用Buffer之后才会被触发。

pw_stream_new只创建Stream对象,不负责取Buffer,也不负责提交Buffer。

c 复制代码
static const struct pw_stream_events stream_events = {
    PW_VERSION_STREAM_EVENTS,
    .process = on_stream_process,
};

这里定义Stream事件表。

对于播放或录音程序,process事件最重要。它代表PipeWireGraph已经调度到该Stream,应用需要在这个回调中生产或消费媒体数据。

c 复制代码
static struct pw_stream *create_playback_stream(struct app_data *data)
{
    struct pw_properties *props;

    props = pw_properties_new(
        PW_KEY_MEDIA_TYPE, "Audio",
        PW_KEY_MEDIA_CATEGORY, "Playback",
        PW_KEY_MEDIA_ROLE, "Music",
        PW_KEY_APP_NAME, "pipewire-playback-demo",
        NULL);

    data->stream = pw_stream_new(data->core,
                                 "playback-stream",
                                 props);
    if (data->stream == NULL)
        return NULL;

    pw_stream_add_listener(data->stream,
                           &data->stream_listener,
                           &stream_events,
                           data);

    return data->stream;
}

这段代码体现了pw_stream_new的核心用法。

第一步,准备Stream属性。

c 复制代码
props = pw_properties_new(
    PW_KEY_MEDIA_TYPE, "Audio",
    PW_KEY_MEDIA_CATEGORY, "Playback",
    PW_KEY_MEDIA_ROLE, "Music",
    PW_KEY_APP_NAME, "pipewire-playback-demo",
    NULL);

这些属性会告诉PipeWire和会话管理器:这是一个音频播放流,角色是音乐播放。WirePlumber这类会话管理器可以根据这些属性进行路由、权限和策略处理。

第二步,创建Stream对象。

c 复制代码
data->stream = pw_stream_new(data->core,
                             "playback-stream",
                             props);

这里的核心点是:pw_stream_new只需要已有core、Stream名称和属性集合。调用成功后,应用得到一个Stream控制对象。

第三步,注册Stream事件。

c 复制代码
pw_stream_add_listener(data->stream,
                       &data->stream_listener,
                       &stream_events,
                       data);

pw_stream_new本身不接收事件表。事件需要通过pw_stream_add_listener单独注册。这样process、state_changed、param_changed、add_buffer、remove_buffer等事件才能通知到应用。

接下来,Stream还需要connect。

c 复制代码
static int connect_playback_stream(struct app_data *data,
                                   const struct spa_pod **params,
                                   uint32_t n_params)
{
    return pw_stream_connect(data->stream,
                             PW_DIRECTION_OUTPUT,
                             PW_ID_ANY,
                             PW_STREAM_FLAG_AUTOCONNECT |
                             PW_STREAM_FLAG_MAP_BUFFERS |
                             PW_STREAM_FLAG_RT_PROCESS,
                             params,
                             n_params);
}

这一步才是Stream进入PipeWireGraph的关键。

PW_DIRECTION_OUTPUT表示应用向Graph输出数据,也就是播放方向。

PW_ID_ANY表示不指定固定目标,由会话管理器选择合适的播放设备。

PW_STREAM_FLAG_AUTOCONNECT表示允许自动连接目标Node。

PW_STREAM_FLAG_MAP_BUFFERS表示希望PipeWire帮应用映射Buffer内存,便于直接读写。

PW_STREAM_FLAG_RT_PROCESS表示process回调会进入实时处理路径,应用必须避免阻塞操作。

从工程视角看,pw_stream_new和pw_stream_connect要分开理解。

pw_stream_new阶段:

c 复制代码
data->stream = pw_stream_new(data->core,
                             "playback-stream",
                             props);

此时应用只是拿到了Stream对象。

pw_stream_connect阶段:

c 复制代码
pw_stream_connect(data->stream,
                  PW_DIRECTION_OUTPUT,
                  PW_ID_ANY,
                  flags,
                  params,
                  n_params);

此时Stream才开始和PipeWire服务端交互,进入格式协商、Node创建、Port连接和Buffer准备流程。

如果把pw_stream_new误解成"创建播放节点并开始播放",后面分析PipeWire链路就会混乱。更准确的说法是:

pw_stream_new创建客户端侧Stream对象。

pw_stream_connect让Stream进入Graph连接流程。

pw_stream_dequeue_buffer和pw_stream_queue_buffer负责process回调中的Buffer流转。

🌻5.一句话总结

pw_stream_new是PipeWireStream生命周期的创建入口:它基于已有pw_core创建客户端侧Stream对象,但不连接Graph、不创建Buffer、不处理媒体数据,真正的数据链路要从pw_stream_connect之后才开始。

相关推荐
换个号退隐江湖i2 小时前
LLM 调试日志刷屏怎么办?用 tee 边看边存
linux
DYWorker0013 小时前
搭建嵌入式Linux虚拟机开发环境
linux
weipt3 小时前
从ollama到llama.cpp
linux·服务器·llama
加油码3 小时前
动静态库的链接底层原理(进阶版)
linux·c++
东莞和裕包装3 小时前
如何管控广州定制纸箱生产中的啤切精度与印刷色差质量问题
大数据·运维·网络·人工智能
s_w.h3 小时前
【 计网 】计算机网络基础
服务器·计算机网络
小飞侠在吗4 小时前
Windows 下 Nginx 访问 127.0.0.1 报 50x 错误:根因是路径里 \t 被当成 Tab
运维·windows·nginx
H_oRIZoN_4 小时前
Linux入门DAY23(C语言树形结构)
linux·c语言·数据结构
kdxiaojie4 小时前
Linux USB驱动阅读笔记(1)
linux·笔记·学习·usb