UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
pipewire.cpp File Reference
#include <memory>
#include "audio/audio_playback.h"
#include "audio/types.h"
#include "pipewire_common.hpp"
#include "debug.h"
#include "lib_common.h"
#include "utils/color_out.h"
#include "utils/ring_buffer.h"
#include "utils/string_view_utils.hpp"
Include dependency graph for pipewire.cpp:

Go to the source code of this file.

Classes

struct  state_pipewire_play
 

Macros

#define MOD_NAME   "[PW aplay] "
 

Functions

static bool audio_play_pw_ctl (void *state, int request, void *data, size_t *len)
 
static void audio_play_pw_done (void *state)
 
static void audio_play_pw_help ()
 
static void * audio_play_pw_init (const char *cfg)
 
static void audio_play_pw_probe (struct device_info **available_devices, int *count, void(**deleter)(void *))
 
static void audio_play_pw_put_frame (void *state, const struct audio_frame *frame)
 
static bool audio_play_pw_reconfigure (void *state, struct audio_desc desc)
 
static bool is_format_supported (void *data, size_t *len)
 
static void on_param_changed (void *state, uint32_t id, const struct spa_pod *param)
 
static void on_process (void *userdata) noexcept
 
static void on_state_changed (void *state, enum pw_stream_state old, enum pw_stream_state new_state, const char *error)
 
 REGISTER_MODULE (pipewire, &aplay_pw_info, LIBRARY_CLASS_AUDIO_PLAYBACK, AUDIO_PLAYBACK_ABI_VERSION)
 

Variables

static const struct audio_playback_info aplay_pw_info
 
static const pw_stream_events stream_events
 

Detailed Description

Author
Martin Piatka piatk.nosp@m.a@ce.nosp@m.snet..nosp@m.cz

Definition in file pipewire.cpp.

Macro Definition Documentation

◆ MOD_NAME

#define MOD_NAME   "[PW aplay] "

Definition at line 54 of file pipewire.cpp.

Function Documentation

◆ audio_play_pw_ctl()

static bool audio_play_pw_ctl ( void * state,
int request,
void * data,
size_t * len )
static

Definition at line 174 of file pipewire.cpp.

174 {
175 UNUSED(state);
176
177 switch (request) {
179 return is_format_supported(data, len);
180 default:
181 return false;
182
183 }
184}
#define UNUSED(x)
void ** state
int void size_t * len
static bool is_format_supported(void *data, size_t *len)
Definition pipewire.cpp:163
#define AUDIO_PLAYBACK_CTL_QUERY_FORMAT

References AUDIO_PLAYBACK_CTL_QUERY_FORMAT, is_format_supported(), len, state, and UNUSED.

Here is the call graph for this function:

◆ audio_play_pw_done()

static void audio_play_pw_done ( void * state)
static

Definition at line 342 of file pipewire.cpp.

342 {
343 auto s = static_cast<state_pipewire_play *>(state);
344
345 pw_thread_loop_stop(s->pw.pipewire_loop.get());
346 delete s;
347}

References state.

◆ audio_play_pw_help()

static void audio_play_pw_help ( )
static

Definition at line 79 of file pipewire.cpp.

79 {
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}
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 print_devices(std::string_view media_class)

References color_printf(), print_devices(), TERM_BOLD, TERM_FG_RED, TERM_FG_RESET, and TERM_RESET.

Referenced by audio_play_pw_init().

Here is the call graph for this function:

◆ audio_play_pw_init()

static void * audio_play_pw_init ( const char * cfg)
static

Definition at line 121 of file pipewire.cpp.

121 {
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}
int void * val
#define MOD_NAME
Definition pipewire.cpp:57
static void audio_play_pw_help()
Definition pipewire.cpp:79
void log_msg(int level, const char *format,...)
Definition debug.cpp:130
#define LOG_LEVEL_INFO
normal reporting
Definition debug.h:62
#define INIT_NOERR
Definition host.h:73
bool initialize_pw_common(pipewire_state_common &s, int fd)
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)
const char * key
Definition cineform.cpp:125

References audio_play_pw_help(), INIT_NOERR, initialize_pw_common(), key, LOG_LEVEL_INFO, log_msg(), MOD_NAME, parse_num(), tokenize(), and val.

Here is the call graph for this function:

◆ audio_play_pw_probe()

static void audio_play_pw_probe ( struct device_info ** available_devices,
int * count,
void(** deleter )(void *) )
static

Definition at line 70 of file pipewire.cpp.

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}
int void(** deleter)(void *))
int count
Definition hd-rum.c:44
const char * name
Definition settings.cpp:193

References count, deleter, and name.

◆ audio_play_pw_put_frame()

static void audio_play_pw_put_frame ( void * state,
const struct audio_frame * frame )
static

Definition at line 150 of file pipewire.cpp.

150 {
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}
struct video_frame * frame
#define LOG_LEVEL_WARNING
less severe errors
Definition debug.h:60
void ring_buffer_write(struct ring_buffer *ring, const char *in, int len)
int ring_get_available_write_size(struct ring_buffer *ring)

References frame, LOG_LEVEL_WARNING, log_msg(), MOD_NAME, ring_buffer_write(), ring_get_available_write_size(), and state.

Here is the call graph for this function:

◆ audio_play_pw_reconfigure()

static bool audio_play_pw_reconfigure ( void * state,
struct audio_desc desc )
static

Definition at line 266 of file pipewire.cpp.

266 {
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}
struct video_desc desc
int int channels
static const pw_stream_events stream_events
Definition pipewire.cpp:177
#define LOG_LEVEL_ERROR
general errors
Definition debug.h:59
#define PW_KEY_NODE_RATE
spa_audio_format get_pw_format_from_bps(unsigned bps)
#define STREAM_TARGET_PROPERTY_KEY
struct ring_buffer * ring_buffer_init(int size)

References channels, desc, get_pw_format_from_bps(), LOG_LEVEL_ERROR, log_msg(), MOD_NAME, PW_KEY_NODE_RATE, ring_buffer_init(), state, stream_events, and STREAM_TARGET_PROPERTY_KEY.

Here is the call graph for this function:

◆ is_format_supported()

static bool is_format_supported ( void * data,
size_t * len )
static

Definition at line 163 of file pipewire.cpp.

163 {
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}
@ AC_PCM
Definition types.h:53

References AC_PCM, desc, and len.

Referenced by audio_play_pw_ctl(), and vulkan_display::VulkanDisplay::is_image_description_supported().

◆ on_param_changed()

static void on_param_changed ( void * state,
uint32_t id,
const struct spa_pod * param )
static

Definition at line 200 of file pipewire.cpp.

200 {
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}
#define LOG_LEVEL_NOTICE
information that may be interesting
Definition debug.h:61
#define LOG_LEVEL_VERBOSE
Definition debug.h:63
static int buffer_size(int sock, int optname, int size)
Definition hd-rum.c:64
const char * param
Definition settings.cpp:195

References buffer_size(), get_pw_format_from_bps(), LOG_LEVEL_ERROR, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, log_msg(), MOD_NAME, param, and state.

Here is the call graph for this function:

◆ on_process()

static void on_process ( void * userdata)
staticnoexcept

Definition at line 91 of file pipewire.cpp.

91 {
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}
int ring_get_current_size(struct ring_buffer *ring)
int ring_buffer_read(struct ring_buffer *ring, char *out, int max_len)

References ring_buffer_read(), and ring_get_current_size().

Here is the call graph for this function:

◆ on_state_changed()

static void on_state_changed ( void * state,
enum pw_stream_state old,
enum pw_stream_state new_state,
const char * error )
static

Definition at line 186 of file pipewire.cpp.

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}

References LOG_LEVEL_ERROR, LOG_LEVEL_NOTICE, log_msg(), MOD_NAME, and state.

Here is the call graph for this function:

◆ REGISTER_MODULE()

REGISTER_MODULE ( pipewire ,
& aplay_pw_info,
LIBRARY_CLASS_AUDIO_PLAYBACK ,
AUDIO_PLAYBACK_ABI_VERSION  )

Variable Documentation

◆ aplay_pw_info

const struct audio_playback_info aplay_pw_info
static
Initial value:
= {
}
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 void audio_play_pw_probe(struct device_info **available_devices, int *count, void(**deleter)(void *))
Definition pipewire.cpp:70
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_put_frame(void *state, const struct audio_frame *frame)
Definition pipewire.cpp:150

Definition at line 349 of file pipewire.cpp.

◆ stream_events

const pw_stream_events stream_events
static
Initial value:
= {
.version = PW_VERSION_STREAM_EVENTS,
.destroy = nullptr,
.state_changed = on_state_changed,
.control_info = nullptr,
.io_changed = nullptr,
.param_changed = on_param_changed,
.add_buffer = nullptr,
.remove_buffer = nullptr,
.process = on_process,
.drained = nullptr,
}
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 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

Definition at line 249 of file pipewire.cpp.

249 {
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};