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

Source

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

博主新书推荐:《Android系统多媒体进阶实战》🚀
Android Audio工程师专栏地址: Audio工程师进阶系列原创干货持续更新中……】🚀
Android多媒体专栏地址: 多媒体系统工程师系列原创干货持续更新中……】🚀
专题一 二:AAOS车载系统+AOSP14系统攻城狮入门视频实战课 🚀
专题三:Android14 Binder之HIDL与AIDL通信实战课 🚀
专题四:Android15快速自定义与集成音效实战课 🚀
专题五:Android15音频策略实战课 🚀
专题六:Android15音频性能实战课(无声/杂音/断音/爆音实战案例) 🚀

人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.

更多原创,欢迎关注:Android系统攻城狮

欢迎关注Android系统攻城狮

🌻1.前言

本篇目的:理解pw_stream_connect()如何让一个刚创建的pw_stream进入PipeWire Graph,并为后续格式协商、路由连接和Buffer传输建立基础。

应用调用:

pw_stream_new()

创建pw_stream以后,此时得到的只是一个:

Stream对象

它还没有真正进入PipeWire的数据处理链路。

如果是音乐播放应用,还需要告诉PipeWire:

这是一个输出流
支持什么音频格式
是否需要自动选择播放设备
Buffer如何映射

这些工作由:

pw_stream_connect()

启动。

PipeWire官方将Stream定义为用于客户端与PipeWire交换数据的高级封装,并指出Stream初始处于UNCONNECTED状态,需要调用pw_stream_connect()连接;对于普通播放流,方向使用PW_DIRECTION_OUTPUT

整体调用流程:

① 创建
pw_stream

② pw_stream_connect()

③ 导出
Stream Node

④ 格式与Buffer
协商

⑤ 接入Graph
传输数据

简单理解:

pw_stream_new()
=
造出一辆汽车

pw_stream_connect()
=
把汽车开上PipeWire这条公路

🌻2.应用场景和用法

🌻2.1 应用场景

pw_stream主要用于应用和PipeWire之间传输媒体数据。

例如播放器:

音乐播放器
    ↓
pw_stream
    ↓
PipeWire
    ↓
Audio Sink

录音应用则相反:

Audio Source
    ↓
PipeWire
    ↓
pw_stream
    ↓
录音应用

因此pw_stream_connect()最常见的两个应用场景是:

音频播放
        ↓
PW_DIRECTION_OUTPUT

音频录音
        ↓
PW_DIRECTION_INPUT

这里的方向是站在Stream自身的数据方向看:

🎵 播放应用

PW_DIRECTION_OUTPUT
pw_stream

🔊 Audio Sink

🎤 Audio Source

PW_DIRECTION_INPUT
pw_stream

🎙 录音应用

PipeWire官方对此有一个非常重要的定义:

PW_DIRECTION_OUTPUT
=
Stream生产数据

PW_DIRECTION_INPUT
=
Stream消费数据

所以普通播放器使用PW_DIRECTION_OUTPUT,普通录音程序使用PW_DIRECTION_INPUT

另外,PW_STREAM_FLAG_AUTOCONNECT表示希望Session Manager自动把Stream连接到合适的目标Node;真正决定连接哪个设备以及创建Graph Link,属于Session Manager的策略职责,而不是pw_stream_connect()单独完成。

可以简单理解为:

pw_stream_connect()
=
“我要进入PipeWire”

WirePlumber
=
“我决定把你接到哪个设备”
🌻2.2 函数用法

pw_stream_connect()函数原型:

int pw_stream_connect(struct pw_stream *stream,
        enum pw_direction direction,
        uint32_t target_id,
        enum pw_stream_flags flags,
        const struct spa_pod **params,
        uint32_t n_params);

参数作用:

参数 作用
stream 已创建的pw_stream对象
direction Stream的数据方向
target_id 目标对象ID,现代用法通常使用PW_ID_ANY
flags Stream连接行为
params Stream支持的SPA参数,例如音频格式
n_params 参数数量

官方当前文档建议target_id使用:

PW_ID_ANY

如果需要指定目标设备,推荐在Stream属性中使用:

PW_KEY_TARGET_OBJECT

并设置目标Node的object.serialnode.name;直接通过target_id指定Node ID已经属于废弃用法。

播放器常见调用:

pw_stream_connect(stream,
        PW_DIRECTION_OUTPUT,
        PW_ID_ANY,
        PW_STREAM_FLAG_AUTOCONNECT |
        PW_STREAM_FLAG_MAP_BUFFERS |
        PW_STREAM_FLAG_RT_PROCESS,
        params,
        n_params);

几个常用Flag:

Flag 作用
PW_STREAM_FLAG_AUTOCONNECT 请求自动连接合适的目标
PW_STREAM_FLAG_MAP_BUFFERS 将可映射Buffer映射到客户端地址空间
PW_STREAM_FLAG_RT_PROCESS 在Realtime线程中调用process回调
PW_STREAM_FLAG_INACTIVE 创建后先保持Inactive
PW_STREAM_FLAG_NO_CONVERT 不使用Stream内部格式转换

PipeWire官方播放示例同样采用PW_DIRECTION_OUTPUT + PW_ID_ANY + AUTOCONNECT + MAP_BUFFERS + RT_PROCESS的组合。

params通常不是PCM数据,而是告诉PipeWire:

“我的Stream支持什么媒体格式?”

例如:

params[n_params++] =
    spa_format_audio_raw_build(
        &b,
        SPA_PARAM_EnumFormat,
        &SPA_AUDIO_INFO_RAW_INIT(
            .format = SPA_AUDIO_FORMAT_F32,
            .channels = 2,
            .rate = 48000));

也就是告诉PipeWire:

Format   = F32
Channels = 2
Rate     = 48000

🌻3.调用流程剖析

🌻3.1 客户端Stream连接流程

调用pw_stream_new()以后,Stream最开始处于:

PW_STREAM_STATE_UNCONNECTED

调用:

pw_stream_connect()

以后,Stream开始进入连接过程。

PipeWire的Stream并不是一个孤立的应用对象。官方架构说明中,Stream实际上封装了一个带Adapter的pw_client_node Proxy,用这种方式把客户端中的媒体处理能力表示成PipeWire服务端能够看到的Node。

因此可以把客户端过程理解为:

pw_stream
UNCONNECTED

pw_stream_connect()

设置direction
flags / params

建立ClientNode
Proxy

向服务端导出
Stream Node

这里最核心的变化是:

调用前:

应用
 └── pw_stream


调用后:

应用
 └── pw_stream
       ↓
   ClientNode Proxy
       ↕ IPC
   PipeWire服务端Node

Client Node机制本身通过Native Protocol在客户端Proxy和服务端Resource之间同步Node状态。官方Native Protocol文档说明,pw-streampw-filter正是利用Client Node机制实现客户端媒体处理Node;底层通过client-nodeFactory创建ClientNode Proxy和服务端Resource。

需要特别注意:

pw_stream_connect()成功返回,只代表连接请求成功启动,并不表示此刻已经完成格式协商、建立设备Link并开始播放。

Stream状态后面还可能经历:

UNCONNECTED
    ↓
CONNECTING
    ↓
PAUSED
    ↓
STREAMING

PipeWire官方Stream API定义了这几个状态,用于描述连接和运行过程。

🌻3.2 服务端协商与Graph连接流程

Stream Node进入PipeWire以后,服务端开始处理Node和Port。

客户端在pw_stream_connect()中传入:

direction
flags
params

其中params中的:

SPA_PARAM_EnumFormat

表示Stream支持哪些格式。

PipeWire随后通过参数协商确定真正使用的格式,并通过:

pw_stream_events.param_changed

通知客户端。

官方Stream文档明确说明,连接之后服务端会配置Stream参数;发生格式参数变化时会产生param_changed事件,随后客户端可以通过pw_stream_update_params()继续完成Buffer数量、大小、Meta等协商。

整体过程:

Stream Node
进入PipeWire

Format
协商

Buffer
协商

WirePlumber
选择目标

建立Link
进入Graph

对于一个普通音乐播放器:

应用pw_stream
      ↓
Stream Node
      ↓
输出Port
      ↓
Link
      ↓
Audio Sink Node

这里需要区分两件事情:

pw_stream_connect()
=
把Stream接入PipeWire连接流程

Link
=
把Stream Node的Port连接到设备Node的Port

如果设置:

PW_STREAM_FLAG_AUTOCONNECT

含义是:

“请Session Manager帮我自动寻找合适目标”

并不是pw_stream_connect()内部自己直接创建与ALSA Sink之间的Link。官方教程也明确把AUTOCONNECT描述为请求Session Manager将Stream连接到合适的Consumer。


🌻4.实战案例

实战目标:创建一个48kHz、双声道、F32格式的播放Stream,通过pw_stream_connect()接入PipeWire,并在process回调中持续写入音频数据。

整体流程:

① 创建
pw_stream

② pw_stream_connect()
接入PipeWire

③ process()
写入Buffer

🌻4.1 创建pw_stream

首先定义Stream事件:

static void on_process(void *userdata);

static void on_state_changed(
        void *userdata,
        enum pw_stream_state old,
        enum pw_stream_state state,
        const char *error)
{
    
      
    printf("stream state: %s\n",
           pw_stream_state_as_string(state));
}

static const struct pw_stream_events stream_events = {
    
      
    PW_VERSION_STREAM_EVENTS,
    .state_changed = on_state_changed,
    .process = on_process,
};

然后创建播放Stream:

struct pw_properties *props;

props = pw_properties_new(
        PW_KEY_MEDIA_TYPE, "Audio",
        PW_KEY_MEDIA_CATEGORY, "Playback",
        PW_KEY_MEDIA_ROLE, "Music",
        NULL);

stream = pw_stream_new_simple(
        pw_main_loop_get_loop(loop),
        "audio-playback",
        props,
        &stream_events,
        NULL);

这里创建出来的是:

pw_stream

此时它还没有进入PipeWire Graph。


🌻4.2 调用pw_stream_connect

首先构造Stream支持的音频格式:

uint8_t buffer[1024];
struct spa_pod_builder b =
        SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));

const struct spa_pod *params[1];

params[0] = spa_format_audio_raw_build(
        &b,
        SPA_PARAM_EnumFormat,
        &SPA_AUDIO_INFO_RAW_INIT(
            .format = SPA_AUDIO_FORMAT_F32,
            .channels = 2,
            .rate = 48000));

然后执行:

int res;

res = pw_stream_connect(
        stream,
        PW_DIRECTION_OUTPUT,
        PW_ID_ANY,
        PW_STREAM_FLAG_AUTOCONNECT |
        PW_STREAM_FLAG_MAP_BUFFERS |
        PW_STREAM_FLAG_RT_PROCESS,
        params,
        1);

if (res < 0) {
    
      
    fprintf(stderr,
            "pw_stream_connect failed: %s\n",
            spa_strerror(res));
}

这里相当于告诉PipeWire:

我是播放流
      ↓
PW_DIRECTION_OUTPUT

请自动选择目标
      ↓
PW_STREAM_FLAG_AUTOCONNECT

请映射Buffer
      ↓
PW_STREAM_FLAG_MAP_BUFFERS

支持格式
      ↓
F32 / 2ch / 48000Hz

核心变化:

pw_stream
UNCONNECTED

pw_stream_connect()

Stream Node

格式/Buffer协商

等待接入Graph

官方音频播放示例采用的就是这种基本结构:创建pw_stream、构造SPA_PARAM_EnumFormat,然后以PW_DIRECTION_OUTPUT和AUTOCONNECT等Flag执行pw_stream_connect()


🌻4.3 通过process回调验证数据传输

Stream真正开始处理数据以后,PipeWire通过:

process()

回调通知应用。

应用调用:

pw_stream_dequeue_buffer()

取出Buffer。

例如:

static void on_process(void *userdata)
{
    
      
    struct pw_buffer *b;
    struct spa_buffer *buf;
    float *dst;
    uint32_t n_frames;
    uint32_t i;

    b = pw_stream_dequeue_buffer(stream);
    if (b == NULL)
        return;

    buf = b->buffer;
    dst = buf->datas[0].data;

    if (dst == NULL)
        goto done;

    n_frames =
        buf->datas[0].maxsize /
        (sizeof(float) * 2);

    for (i = 0; i < n_frames; i++) {
    
      
        dst[i * 2] = 0.0f;
        dst[i * 2 + 1] = 0.0f;
    }

    buf->datas[0].chunk->offset = 0;
    buf->datas[0].chunk->stride =
        sizeof(float) * 2;
    buf->datas[0].chunk->size =
        n_frames * sizeof(float) * 2;

done:
    pw_stream_queue_buffer(stream, b);
}

这里写入的是静音PCM。

真正的数据循环是:

process()

pw_stream_dequeue_buffer()

应用写PCM

pw_stream_queue_buffer()

PipeWire Graph

当状态变化能够看到:

CONNECTING
    ↓
PAUSED
    ↓
STREAMING

并且开始持续收到:

process()

就说明:

pw_stream
      ↓
pw_stream_connect()
      ↓
Stream Node
      ↓
格式/Buffer协商
      ↓
Graph
      ↓
process()数据传输

这条播放链路已经建立。PipeWire官方也要求应用监听process事件,并通过pw_stream_dequeue_buffer()pw_stream_queue_buffer()完成播放或采集Buffer循环。


🌻5.总结

pw_stream_connect()就是把已经创建的pw_stream按照指定方向、连接Flag和媒体参数接入PipeWire连接流程,为Stream Node导出、格式与Buffer协商以及后续Graph连接建立基础。