UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
pipewire.cpp
Go to the documentation of this file.
1
5/*
6 * Copyright (c) 2023 CESNET z.s.p.o.
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, is permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 *
20 * 3. Neither the name of CESNET nor the names of its contributors may be
21 * used to endorse or promote products derived from this software without
22 * specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
26 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
27 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
28 * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
29 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
30 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
34 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
35 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 */
37
38#ifdef HAVE_CONFIG_H
39#include "config.h"
40#include "config_unix.h"
41#endif
42
43#include <memory>
44
46#include "audio/types.h"
47#include "pipewire_common.hpp"
48#include "debug.h"
49#include "lib_common.h"
50#include "utils/color_out.h"
51#include "utils/ring_buffer.h"
53
54#define MOD_NAME "[PW aplay] "
55
69
70static void audio_play_pw_probe(struct device_info **available_devices, int *count, void (**deleter)(void *))
71{
72 *deleter = free;
73 *available_devices = static_cast<device_info *>(calloc(1, sizeof(device_info)));
74 strcpy((*available_devices)[0].dev, "");
75 strcpy((*available_devices)[0].name, "Default pipewire output");
76 *count = 1;
77}
78
79static void audio_play_pw_help(){
80 color_printf("Pipewire audio output.\n");
81 color_printf("Usage\n");
82 color_printf(TERM_BOLD TERM_FG_RED "\t-r pipewire" TERM_FG_RESET "[:target=<device>][:buffer-len=<millis>][:quant=<samples>]\n" TERM_RESET);
83 color_printf("\n");
84
85 color_printf("Devices:\n");
86 print_devices("Audio/Sink");
87}
88
89/* This function can only use realtime-safe calls (no locking, allocating, etc.)
90*/
91static void on_process(void *userdata) noexcept{
92 auto s = static_cast<state_pipewire_play *>(userdata);
93
94 auto avail = ring_get_current_size(s->ring_buf.get());
95
96 while(avail > 0){
97 struct pw_buffer *b = pw_stream_dequeue_buffer(s->stream.get());
98 if (!b) {
99 pw_log_warn("out of buffers: %m");
100 return;
101 }
102 struct spa_buffer *buf = b->buffer;
103
104 char *dst = static_cast<char *>(buf->datas[0].data);
105 if (!dst)
106 return;
107
108 int to_write = std::min<int>(buf->datas[0].maxsize, avail);
109
110 ring_buffer_read(s->ring_buf.get(), dst, to_write);
111 avail -= to_write;
112
113 buf->datas[0].chunk->offset = 0;
114 buf->datas[0].chunk->stride = s->desc.ch_count * s->desc.bps;
115 buf->datas[0].chunk->size = to_write;
116
117 pw_stream_queue_buffer(s->stream.get(), b);
118 }
119}
120
121static void * audio_play_pw_init(const char *cfg){
122 auto s = std::make_unique<state_pipewire_play>();
123
124 std::string_view cfg_sv(cfg);
125 while(!cfg_sv.empty()){
126 auto tok = tokenize(cfg_sv, ':', '"');
127 auto key = tokenize(tok, '=');
128 auto val = tokenize(tok, '=');
129
130 if(key == "help"){
132 return INIT_NOERR;
133 } else if(key == "target"){
134 s->target = val;
135 } else if(key == "buffer-len"){
136 parse_num(val, s->buf_len_ms);
137 } else if(key == "quant"){
138 parse_num(val, s->quant);
139 }
140 }
141
143
144 log_msg(LOG_LEVEL_INFO, MOD_NAME "Compiled with libpipewire %s\n", pw_get_headers_version());
145 log_msg(LOG_LEVEL_INFO, MOD_NAME "Linked with libpipewire %s\n", pw_get_library_version());
146
147 return s.release();
148}
149
150static void audio_play_pw_put_frame(void *state, const struct audio_frame *frame){
151 auto s = static_cast<state_pipewire_play *>(state);
152
153 auto avail = ring_get_available_write_size(s->ring_buf.get());
154 auto to_write = frame->data_len;
155 if(to_write > avail){
156 log_msg(LOG_LEVEL_WARNING, MOD_NAME "Got frame of len %d, but ring has only %d free\n", frame->data_len, ring_get_available_write_size(s->ring_buf.get()));
157 to_write = avail;
158 }
159
160 ring_buffer_write(s->ring_buf.get(), frame->data, to_write);
161}
162
163static bool is_format_supported(void *data, size_t *len){
164 struct audio_desc desc;
165 if (*len < sizeof(desc)) {
166 return false;
167 } else {
168 memcpy(&desc, data, sizeof(desc));
169 }
170
171 return desc.codec == AC_PCM && desc.bps >= 1 && desc.bps <= 4;
172}
173
174static bool audio_play_pw_ctl(void *state, int request, void *data, size_t *len){
175 UNUSED(state);
176
177 switch (request) {
179 return is_format_supported(data, len);
180 default:
181 return false;
182
183 }
184}
185
186static void on_state_changed(void *state, enum pw_stream_state old, enum pw_stream_state new_state, const char *error)
187{
188 auto s = static_cast<state_pipewire_play *>(state);
189 log_msg(LOG_LEVEL_NOTICE, MOD_NAME "Stream state change: %s -> %s\n",
190 pw_stream_state_as_string(old),
191 pw_stream_state_as_string(new_state));
192
193 if(error){
194 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Stream error: %s\n", error);
195 }
196
197 pw_thread_loop_signal(s->pw.pipewire_loop.get(), false);
198}
199
200static void on_param_changed(void *state, uint32_t id, const struct spa_pod *param){
201 auto s = static_cast<state_pipewire_play *>(state);
202
203 if(!param || id != SPA_PARAM_Format)
204 return;
205
206 spa_audio_info audio_params;
207 int res = spa_format_parse(param, &audio_params.media_type, &audio_params.media_subtype);
208 if(res < 0
209 || audio_params.media_type != SPA_MEDIA_TYPE_audio
210 || audio_params.media_subtype != SPA_MEDIA_SUBTYPE_raw)
211 {
212 return;
213 }
214
215 spa_format_audio_raw_parse(param, &audio_params.info.raw);
216
217 log_msg(LOG_LEVEL_NOTICE, MOD_NAME "Pipewire format change: %s, %u ch, %uHz\n",
218 spa_debug_type_find_short_name(spa_type_audio_format, audio_params.info.raw.format),
219 audio_params.info.raw.channels,
220 audio_params.info.raw.rate);
221
222 assert(audio_params.info.raw.rate == (unsigned) s->desc.sample_rate);
223 assert(audio_params.info.raw.channels == (unsigned) s->desc.ch_count);
224 assert(audio_params.info.raw.format == get_pw_format_from_bps(s->desc.bps));
225
226 std::byte buffer[1024];
227 auto pod_builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
228
229 unsigned buffer_size = (s->buf_len_ms * s->desc.sample_rate / 1000) * s->desc.ch_count * s->desc.bps;
230
231 log_msg(LOG_LEVEL_VERBOSE, MOD_NAME "Requesting buffer size %u\n", buffer_size);
232
233 spa_pod *new_params = (spa_pod *) spa_pod_builder_add_object(&pod_builder,
234 SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
235 SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
236 SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(buffer_size, 0, INT32_MAX),
237 SPA_PARAM_BUFFERS_stride, SPA_POD_Int(s->desc.ch_count * s->desc.bps));
238
239 if(!new_params){
240 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to build pw buffer params pod\n");
241 return;
242 }
243
244 if (pw_stream_update_params(s->stream.get(), const_cast<const spa_pod **>(&new_params), 1) < 0) {
245 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to set stream params\n");
246 }
247}
248
249const static pw_stream_events stream_events = {
250 .version = PW_VERSION_STREAM_EVENTS,
251 .destroy = nullptr,
252 .state_changed = on_state_changed,
253 .control_info = nullptr,
254 .io_changed = nullptr,
255 .param_changed = on_param_changed,
256 .add_buffer = nullptr,
257 .remove_buffer = nullptr,
258 .process = on_process,
259 .drained = nullptr,
260#if PW_MAJOR > 0 || PW_MINOR > 3 || (PW_MINOR == 3 && PW_MICRO > 39)
261 .command = nullptr,
262 .trigger_done = nullptr,
263#endif
264};
265
267 auto s = static_cast<state_pipewire_play *>(state);
268
269 unsigned rate = desc.sample_rate;
270 spa_audio_format format = get_pw_format_from_bps(desc.bps);
271
272 auto props = pw_properties_new(
273 PW_KEY_MEDIA_TYPE, "Audio",
274 PW_KEY_MEDIA_CATEGORY, "Playback",
275 PW_KEY_MEDIA_ROLE, "Communication",
276 PW_KEY_APP_NAME, "UltraGrid",
277 PW_KEY_APP_ICON_NAME, "ultragrid",
278 PW_KEY_NODE_NAME, "ug play",
279 STREAM_TARGET_PROPERTY_KEY, s->target.c_str(),
280 nullptr);
281
282 pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", rate);
283 pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", s->quant, rate);
284
285 std::byte buffer[1024];
286 auto pod_builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
287
288 auto audio_info = SPA_AUDIO_INFO_RAW_INIT(
289 .format = format,
290 .rate = rate,
291 .channels = static_cast<unsigned>(desc.ch_count),
292 );
293 const spa_pod *params = spa_format_audio_raw_build(&pod_builder, SPA_PARAM_EnumFormat,
294 &audio_info);
295
296 s->desc = desc;
297
298 /*
299 * Pipewire thread loop lock
300 */
301 pipewire_thread_loop_lock_guard lock(s->pw.pipewire_loop.get());
302
303 s->stream.reset(pw_stream_new(
304 s->pw.pipewire_core.get(),
305 "UltraGrid playback",
306 props));
307
308 pw_stream_add_listener(
309 s->stream.get(),
310 &s->stream_listener.get(),
312 s);
313
314 unsigned ring_size = (s->buf_len_ms * desc.sample_rate / 1000) * desc.ch_count * desc.bps * 2;
315 s->ring_buf.reset(ring_buffer_init(ring_size));
316
317 pw_stream_connect(s->stream.get(),
318 PW_DIRECTION_OUTPUT,
319 PW_ID_ANY,
320 static_cast<pw_stream_flags>(
321 PW_STREAM_FLAG_AUTOCONNECT |
322 PW_STREAM_FLAG_MAP_BUFFERS |
323 PW_STREAM_FLAG_RT_PROCESS),
324 &params, 1);
325
326 while(true){
327 pw_thread_loop_wait(s->pw.pipewire_loop.get());
328 const char *error = nullptr;
329 auto stream_state = pw_stream_get_state(s->stream.get(), &error);
330 if(stream_state == PW_STREAM_STATE_STREAMING){
331 return true;
332 }
333 if(stream_state == PW_STREAM_STATE_ERROR){
334 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to initialize stream: %s\n", error ? error : "(no err msg)");
335 return false;
336 }
337 }
338
339 return true;
340}
341
342static void audio_play_pw_done(void *state){
343 auto s = static_cast<state_pipewire_play *>(state);
344
345 pw_thread_loop_stop(s->pw.pipewire_loop.get());
346 delete s;
347}
348
357
359
#define UNUSED(x)
void ** state
int void * val
struct video_desc desc
int int channels
struct video_frame * frame
int void size_t * len
int void(** deleter)(void *))
static void on_state_changed(void *state, enum pw_stream_state old, enum pw_stream_state new_state, const char *error)
Definition pipewire.cpp:162
static const pw_stream_events stream_events
Definition pipewire.cpp:177
static void on_process(void *state)
Definition pipewire.cpp:83
static void on_param_changed(void *state, uint32_t id, const struct spa_pod *param)
Definition pipewire.cpp:103
#define MOD_NAME
Definition pipewire.cpp:57
static bool audio_play_pw_ctl(void *state, int request, void *data, size_t *len)
Definition pipewire.cpp:174
static bool audio_play_pw_reconfigure(void *state, struct audio_desc desc)
Definition pipewire.cpp:266
static bool is_format_supported(void *data, size_t *len)
Definition pipewire.cpp:163
static void audio_play_pw_probe(struct device_info **available_devices, int *count, void(**deleter)(void *))
Definition pipewire.cpp:70
static const struct audio_playback_info aplay_pw_info
Definition pipewire.cpp:349
static void * audio_play_pw_init(const char *cfg)
Definition pipewire.cpp:121
static void audio_play_pw_done(void *state)
Definition pipewire.cpp:342
static void audio_play_pw_help()
Definition pipewire.cpp:79
static void audio_play_pw_put_frame(void *state, const struct audio_frame *frame)
Definition pipewire.cpp:150
This file contains definition of some common audio types.
@ AC_PCM
Definition types.h:53
#define AUDIO_PLAYBACK_CTL_QUERY_FORMAT
#define AUDIO_PLAYBACK_ABI_VERSION
int color_printf(const char *format,...)
#define TERM_BOLD
Definition color_out.h:48
#define TERM_FG_RED
Definition color_out.h:50
#define TERM_RESET
Definition color_out.h:47
#define TERM_FG_RESET
Definition color_out.h:59
void log_msg(int level, const char *format,...)
Definition debug.cpp:130
#define LOG_LEVEL_NOTICE
information that may be interesting
Definition debug.h:61
#define LOG_LEVEL_INFO
normal reporting
Definition debug.h:62
#define LOG_LEVEL_ERROR
general errors
Definition debug.h:59
#define LOG_LEVEL_VERBOSE
Definition debug.h:63
#define LOG_LEVEL_WARNING
less severe errors
Definition debug.h:60
int count
Definition hd-rum.c:44
static int buffer_size(int sock, int optname, int size)
Definition hd-rum.c:64
#define INIT_NOERR
Definition host.h:73
@ LIBRARY_CLASS_AUDIO_PLAYBACK
Definition lib_common.h:77
#define REGISTER_MODULE(name, info, lclass, abi)
Registers module to global modules' registry.
Definition lib_common.h:144
void print_devices(std::string_view media_class)
bool initialize_pw_common(pipewire_state_common &s, int fd)
#define PW_KEY_NODE_RATE
raii_uniq_handle< pw_stream, pw_stream_destroy > pw_stream_uniq
spa_audio_format get_pw_format_from_bps(unsigned bps)
#define STREAM_TARGET_PROPERTY_KEY
int ring_get_current_size(struct ring_buffer *ring)
struct ring_buffer * ring_buffer_init(int size)
int ring_buffer_read(struct ring_buffer *ring, char *out, int max_len)
void ring_buffer_write(struct ring_buffer *ring, const char *in, int len)
int ring_get_available_write_size(struct ring_buffer *ring)
std::unique_ptr< ring_buffer, ring_buf_deleter > ring_buffer_uniq
const char * param
Definition settings.cpp:195
const char * name
Definition settings.cpp:193
std::string_view tokenize(std::string_view &str, char delim, char quot)
Tokenizer for string_view.
bool parse_num(std::string_view sv, T &res, int base=10)
struct used to comunicate with audio capture/playback device
Definition types.h:99
spa_hook_uniq stream_listener
Definition pipewire.cpp:60
ring_buffer_uniq ring_buf
Definition pipewire.cpp:65
pw_stream_uniq stream
Definition pipewire.cpp:59
pipewire_state_common pw
Definition pipewire.cpp:57
std::string target
Definition pipewire.cpp:62
const char * key
Definition cineform.cpp:125