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

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

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

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

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

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

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

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

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

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

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

🍉🍉🍉文章目录🍉🍉🍉

  • [🌻1. 前言](#🌻1. 前言)
  • [🌻2. 应用场景与用法](#🌻2. 应用场景与用法)
  • 应用场景
  • [🌻3. 调用流程剖析](#🌻3. 调用流程剖析)
    • [🌻3.1 核心步骤](#🌻3.1 核心步骤)
      • [1. PipeWire 环境已经初始化](#1. PipeWire 环境已经初始化)
      • [2. 创建或获取 Core 对象](#2. 创建或获取 Core 对象)
      • [3. 构造 Stream 属性](#3. 构造 Stream 属性)
      • [4. 调用 pw_stream_new](#4. 调用 pw_stream_new)
      • [5. 创建内部 Stream 对象](#5. 创建内部 Stream 对象)
      • [6. 设置 Stream 初始状态](#6. 设置 Stream 初始状态)
      • [7. 返回 Stream 句柄](#7. 返回 Stream 句柄)
      • [8. 后续连接 Stream](#8. 后续连接 Stream)
    • [🌻3.2 调用流程图](#🌻3.2 调用流程图)
    • [🌻3.3 生命周期图](#🌻3.3 生命周期图)
  • [🌻4. 实战应用案例](#🌻4. 实战应用案例)
  • [🌻5. 一句话总结](#🌻5. 一句话总结)

🌻1. 前言

本篇目的:

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

要点概括

  • 核心功能:创建一个 PipeWire 客户端侧的 Stream 对象,用于后续建立音频、视频或其他媒体数据流。
  • 工作机制pw_stream_new() 基于 pw_core 创建 struct pw_stream,初始化 Stream 属性、事件列表、内部状态和用户数据区,但不会立即连接设备,也不会开始数据传输。
  • 典型用途 :创建播放流、创建录音流、创建自定义音视频处理流,并为后续 pw_stream_connect()、Buffer 分配和 process 回调建立基础对象。

🌻2. 应用场景与用法

pw_stream_new() 是 PipeWire Stream 生命周期管理中的创建接口。

应用在连接 PipeWire Core 后,可以调用:

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

而该接口用于:

基于已有 pw_core 创建一个客户端侧 Stream 对象,并为后续连接、格式协商、Buffer 管理和数据处理回调准备基础状态。

需要注意:

pw_stream_new() 只负责创建 Stream 对象,不会自动连接 Sink、Source、Node 或设备,也不会启动音频数据流。

真正让 Stream 进入 PipeWire 图、参与路由和数据传输的接口是:

c 复制代码
pw_stream_connect(
        stream,
        direction,
        target_id,
        flags,
        params,
        n_params);

函数原型

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

参数说明

c 复制代码
core:
由 pw_context_connect() 或相关接口获得的 PipeWire Core 对象

name:
Stream 名称,用于标识这条媒体流

props:
Stream 属性集合,用于描述媒体角色、节点名称、应用名称、目标设备等信息

常见属性包括:

c 复制代码
PW_KEY_MEDIA_TYPE
PW_KEY_MEDIA_CATEGORY
PW_KEY_MEDIA_ROLE
PW_KEY_NODE_NAME
PW_KEY_NODE_DESCRIPTION
PW_KEY_APP_NAME

例如创建音乐播放流时,可以设置:

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

返回值

c 复制代码
创建成功:
返回 struct pw_stream *

创建失败:
返回 NULL

返回的 struct pw_stream * 是后续 Stream 操作的核心句柄。

调用者需要在不再使用时调用:

c 复制代码
pw_stream_destroy(
        stream);

释放 Stream 对象。


应用场景

pw_stream_new() 常见应用场景主要有三类。

第一类是创建音频播放流。播放器、提示音程序、车载导航播报模块可以通过 pw_stream_new() 创建播放 Stream,再通过 pw_stream_connect() 指定 PW_DIRECTION_OUTPUT,最终把应用侧音频数据送入 PipeWire 图中。

第二类是创建音频录音流。录音程序、语音识别前端、通话采集模块可以通过 pw_stream_new() 创建录音 Stream,再使用 PW_DIRECTION_INPUT 连接到 Source 或 Capture 节点,从 PipeWire 获取音频采集数据。

第三类是创建自定义音视频处理流。PipeWire 不只处理普通音频播放和录音,也可以用于视频、相机、屏幕采集和自定义媒体节点。应用可以通过 Stream 抽象连接图中的媒体节点,并在 process 回调中处理 Buffer。


🌻3. 调用流程剖析

🌻3.1 核心步骤

1. PipeWire 环境已经初始化

应用首先初始化 PipeWire 环境:

c 复制代码
pw_init(
        &argc,
        &argv);

随后创建 Main Loop 和 Context:

c 复制代码
main_loop =
        pw_main_loop_new(
                NULL);

context =
        pw_context_new(
                pw_main_loop_get_loop(
                        main_loop),
                NULL,
                0);

2. 创建或获取 Core 对象

pw_stream_new() 依赖 pw_core

应用通常通过:

c 复制代码
core =
        pw_context_connect(
                context,
                NULL,
                0);

连接 PipeWire Core。

core 表示当前客户端与 PipeWire Server 之间的核心连接对象,也是后续创建 Stream、Registry、Proxy 等对象的基础。


3. 构造 Stream 属性

调用 pw_stream_new() 前,应用可以构造 pw_properties

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

这些属性不会直接启动数据流,但会影响后续 Stream 暴露给 PipeWire 图时的身份信息、路由策略和会话管理行为。


4. 调用 pw_stream_new

应用执行:

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

此时进入 PipeWire Stream API。

该函数的核心任务是创建客户端侧 struct pw_stream,并把它和当前 pw_core 关联起来。


5. 创建内部 Stream 对象

pw_stream_new() 内部会分配并初始化 Stream 对象。

逻辑上可以理解为:

text 复制代码
分配 struct pw_stream
初始化属性
初始化事件列表
初始化状态
保存 core
保存 name
准备 user_data 区域

此时 Stream 只是一个本地对象,还没有真正进入 PipeWire 图。


6. 设置 Stream 初始状态

Stream 创建完成后,会处于可配置状态。

此阶段适合继续调用:

c 复制代码
pw_stream_add_listener(
        stream,
        &listener,
        &stream_events,
        userdata);

注册状态变化、参数变化、Buffer 处理等事件回调。


7. 返回 Stream 句柄

创建成功后,pw_stream_new() 返回:

c 复制代码
struct pw_stream *

如果内存分配失败、参数非法或 Core 不可用,则返回:

c 复制代码
NULL

调用者需要检查返回值:

c 复制代码
if (!stream) {
    goto finish;
}

8. 后续连接 Stream

pw_stream_new() 本身不连接设备。

应用还需要调用:

c 复制代码
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);

之后 Stream 才会进入 PipeWire 图,参与格式协商、路由选择、Buffer 分配和 process 回调。

🌻3.2 调用流程图


🌻3.3 生命周期图

🌻4. 实战应用案例

c 复制代码
#include <pipewire/pipewire.h>       // PipeWire核心API

#include <spa/param/audio/format-utils.h>
#include <spa/param/props.h>
#include <spa/pod/builder.h>

#include <stdlib.h>
#include <string.h>

struct app_data {
    struct pw_main_loop *main_loop;
    struct pw_context *context;
    struct pw_core *core;
    struct pw_stream *stream;
    int result;
};

static void stream_state_changed_cb(
        void *userdata,
        enum pw_stream_state old,
        enum pw_stream_state state,
        const char *error) {

    struct app_data *data =
            userdata;

    if (state == PW_STREAM_STATE_ERROR ||
        state == PW_STREAM_STATE_UNCONNECTED) {

        data->result =
                EXIT_FAILURE;

        pw_main_loop_quit(
                data->main_loop);
    }
}

static void stream_process_cb(
        void *userdata) {

    struct app_data *data =
            userdata;

    struct pw_buffer *buffer =
            pw_stream_dequeue_buffer(
                    data->stream);

    if (!buffer) {
        return;
    }

    struct spa_buffer *spa_buffer =
            buffer->buffer;

    if (spa_buffer->datas[0].data &&
        spa_buffer->datas[0].chunk) {

        uint32_t size =
                spa_buffer->datas[0].maxsize;

        memset(
                spa_buffer->datas[0].data,
                0,
                size);

        spa_buffer->datas[0].chunk->offset =
                0;

        spa_buffer->datas[0].chunk->size =
                size;

        spa_buffer->datas[0].chunk->stride =
                sizeof(float) * 2;
    }

    pw_stream_queue_buffer(
            data->stream,
            buffer);
}

static const struct pw_stream_events stream_events = {
    PW_VERSION_STREAM_EVENTS,
    .state_changed =
            stream_state_changed_cb,
    .process =
            stream_process_cb,
};

int main(
        int argc,
        char *argv[]) {

    struct app_data data = {0};

    uint8_t buffer[1024];

    struct spa_pod_builder builder =
            SPA_POD_BUILDER_INIT(
                    buffer,
                    sizeof(buffer));

    const struct spa_pod *params[1];

    struct spa_audio_info_raw audio_info = {
        .format =
                SPA_AUDIO_FORMAT_F32,
        .rate =
                48000,
        .channels =
                2,
        .position = {
                SPA_AUDIO_CHANNEL_FL,
                SPA_AUDIO_CHANNEL_FR,
        },
    };

    data.result =
            EXIT_SUCCESS;

    pw_init(
            &argc,
            &argv);   // 初始化PipeWire环境

    data.main_loop =
            pw_main_loop_new(
                    NULL);   // 创建Main Loop

    if (!data.main_loop) {
        data.result =
                EXIT_FAILURE;
        goto finish_deinit;
    }

    data.context =
            pw_context_new(
                    pw_main_loop_get_loop(
                            data.main_loop),
                    NULL,
                    0);   // 创建Context

    if (!data.context) {
        data.result =
                EXIT_FAILURE;
        goto finish_main_loop;
    }

    data.core =
            pw_context_connect(
                    data.context,
                    NULL,
                    0);   // 连接PipeWire Core

    if (!data.core) {
        data.result =
                EXIT_FAILURE;
        goto finish_context;
    }

    data.stream =
            pw_stream_new(
                    data.core,
                    "pw-stream-new-demo",
                    pw_properties_new(
                            PW_KEY_MEDIA_TYPE,
                            "Audio",
                            PW_KEY_MEDIA_CATEGORY,
                            "Playback",
                            PW_KEY_MEDIA_ROLE,
                            "Music",
                            PW_KEY_NODE_NAME,
                            "pw_stream_new_demo",
                            NULL));   // 创建Stream对象

    if (!data.stream) {
        data.result =
                EXIT_FAILURE;
        goto finish_core;
    }

    pw_stream_add_listener(
            data.stream,
            NULL,
            &stream_events,
            &data);   // 注册Stream事件回调

    params[0] =
            spa_format_audio_raw_build(
                    &builder,
                    SPA_PARAM_EnumFormat,
                    &audio_info);   // 构造音频格式参数

    if (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,
            1) < 0) {

        data.result =
                EXIT_FAILURE;
        goto finish_stream;
    }

    pw_main_loop_run(
            data.main_loop);   // 进入事件循环

finish_stream:
    pw_stream_destroy(
            data.stream);   // 销毁Stream

finish_core:
    pw_core_disconnect(
            data.core);   // 断开Core

finish_context:
    pw_context_destroy(
            data.context);   // 销毁Context

finish_main_loop:
    pw_main_loop_destroy(
            data.main_loop);   // 销毁Main Loop

finish_deinit:
    pw_deinit();   // 释放PipeWire环境资源

    return data.result;
}

🌻5. 一句话总结

pw_stream_new() 本质上是:

"基于 PipeWire Core 创建一个客户端侧 Stream 对象,为后续连接、格式协商、Buffer 管理和数据处理回调建立基础句柄"。

它负责创建 Stream 对象、保存属性、关联 Core 并准备事件回调体系,但不负责连接设备、不负责启动数据传输、不负责完成路由选择。

它是 PipeWire 播放流、录音流和自定义媒体流创建流程中的基础接口。

相关推荐
Ivan CloudBay1 小时前
CDN 能完全隐藏源服务器 IP 吗?
运维·服务器·tcp/ip
蜉蝣fuyou1 小时前
VMware多版本安装包
linux·vmware·虚拟机
不会代码的小猴1 小时前
Linux note2
linux·笔记
AAA@峥1 小时前
OpenStack Keystone 认证服务完整学习指南
服务器·云计算·openstack
雾里0不看花2 小时前
【App Service Linux】在Linux App Service中安装 tcpdump 并抓取网络包
linux·网络·tcpdump
沉迷学习 日益消瘦2 小时前
13-Ingress 生产实战
运维·kubernetes
Dawn-bit3 小时前
Linux日志处理三剑客之基础篇:(基础正则+扩展正则)
linux·运维·服务器·正则表达式·云计算·运维开发
暗影凋落3 小时前
docker-image 工具展示更详细镜像层内容
运维·docker·容器
深圳市爱派派智能科技有限公司3 小时前
“进迭时空 RISC-V 集群服务器 CSB1-N10SPK3:10 节点 K3,600 TOPS 绿色算力“
运维·服务器·risc-v·k3·进迭时空·集群服务器