const data_struct = struct { loop: ?*pipewire.pw_main_loop, stream: ?*pipewire.pw_stream, format: pipewire.spa_audio_info, move: u1, }; /// our data processing function is in general: /// /// struct pw_buffer *b; /// b = pw_stream_dequeue_buffer(stream); /// /// .. consume stuff in the buffer ... /// /// pw_stream_queue_buffer(stream, b); export fn on_process(userdata: ?*anyopaque) void { const data: *data_struct = @ptrCast(@alignCast(userdata)); const b_optional: ?*pipewire.pw_buffer = pipewire.pw_stream_dequeue_buffer(data.stream); var buf: *pipewire.spa_buffer = undefined; var samples_optional: ?[*]f32 = undefined; var max: f32 = undefined; var c: u32 = 0; var n: u32 = undefined; var n_channels: u32 = undefined; var n_samples: u32 = undefined; var peak: u32 = undefined; if (b_optional) |b| { buf = b.buffer; samples_optional = @ptrCast(@alignCast(buf.datas[0].data)); if (samples_optional) |samples| { n_channels = data.format.info.raw.channels; n_samples = buf.datas[0].chunk.*.size / @sizeOf(f32); if (data.move != @as(u1, 0)) _ = pipewire.fprintf(pipewire.stdout, "%c[%dA", @as(u8, 0x1b), n_channels + 1); _ = pipewire.fprintf(pipewire.stdout, "captured %d samples\n", n_samples / n_channels); while (c < data.format.info.raw.channels) : (c += 0) { max = 0.0; n = c; while (n < n_samples) : (n += n_channels) max = @max(max, @abs(samples[n])); peak = @bitCast(@min(@max(max * 30.0, 0.0), 39.0)); _ = pipewire.fprintf( pipewire.stdout, "channel %d: |%*s%*s| peak:%f\n", c, peak + 1, "*", 40 - peak, "", max, ); } data.move = @intFromBool(true); _ = pipewire.fflush(pipewire.stdout); _ = pipewire.pw_stream_queue_buffer(data.stream, b); } } else { pw_log_warn("out of buffers: %m"); } } /// Be notified when the stream param changes. We're only looking at the format changes. export fn on_stream_param_changed(_data: ?*anyopaque, id: u32, param_optional: ?*const pipewire.spa_pod) void { const data: *data_struct = @ptrCast(@alignCast(_data)); // NULL means to clear the format if (param_optional) |param| { if (id != pipewire.SPA_PARAM_Format) { if (pipewire.spa_format_parse(param, &data.format.media_type, &data.format.media_subtype) >= 0) { if (data.format.media_type == pipewire.SPA_MEDIA_TYPE_audio and data.format.media_subtype == pipewire.SPA_MEDIA_SUBTYPE_raw) { _ = pipewire.spa_format_audio_raw_parse(param, &data.format.info.raw); _ = pipewire.fprintf(pipewire.stdout, "capturing rate:%d channels:%d\n", data.format.info.raw.rate, data.format.info.raw.channels); } } } } } const stream_events = pipewire.pw_stream_events{ .version = pipewire.PW_VERSION_STREAM_EVENTS, .param_changed = on_stream_param_changed, .process = on_process, }; export fn do_quit(userdata: ?*anyopaque, signalnumber: c_int) void { const data: *data_struct = @ptrCast(@alignCast(userdata)); _ = pipewire.pw_main_loop_quit(data.loop); _ = signalnumber; } pub fn main() !void { var data: data_struct = undefined; var params: [1][*c]const pipewire.spa_pod = undefined; var buffer: [1024]u8 = undefined; var props: *pipewire.pw_properties = undefined; var b: pipewire.spa_pod_builder = pipewire.spa_pod_builder{ .data = &buffer, .size = @sizeOf([1024]u8), ._padding = 0, .state = .{ .flags = 0, .offset = 0, .frame = null, }, .callbacks = .{ .data = null, .funcs = null, }, }; const argv = std.os.argv; var c_ptr: [*c][*c]u8 = @ptrCast(argv.ptr); var argc: c_int = @intCast(std.os.argv.len); pipewire.pw_init(&argc, &c_ptr); std.log.info( \\ Compiled with libpipewire {s} \\ Linked with libpipewire {s}, , .{ pipewire.pw_get_headers_version(), pipewire.pw_get_library_version(), }); data.loop = pipewire.pw_main_loop_new(null); _ = pipewire.pw_loop_add_signal(pipewire.pw_main_loop_get_loop(data.loop), pipewire.SIGINT, do_quit, &data); _ = pipewire.pw_loop_add_signal(pipewire.pw_main_loop_get_loop(data.loop), pipewire.SIGTERM, do_quit, &data); props = pipewire.pw_properties_new( pipewire.PW_KEY_MEDIA_TYPE, "Audio", pipewire.PW_KEY_MEDIA_CATEGORY, "Capture", pipewire.PW_KEY_MEDIA_ROLE, "Music", @as(?*anyopaque, @ptrFromInt(0)), ); if (argc > 1) _ = pipewire.pw_properties_set(props, pipewire.PW_KEY_TARGET_OBJECT, argv[1]); data.stream = pipewire.pw_stream_new_simple( pipewire.pw_main_loop_get_loop(data.loop), "audio-capture", props, &stream_events, &data, ); params[0] = pipewire.spa_format_audio_raw_build( &b, pipewire.SPA_PARAM_EnumFormat, &pipewire.spa_audio_info_raw{ .format = pipewire.SPA_AUDIO_FORMAT_F32 }, ); _ = pipewire.pw_stream_connect(data.stream, pipewire.PW_DIRECTION_INPUT, pipewire.PW_ID_ANY, pipewire.PW_STREAM_FLAG_AUTOCONNECT | pipewire.PW_STREAM_FLAG_MAP_BUFFERS | pipewire.PW_STREAM_FLAG_RT_PROCESS, params[0..].ptr, @as(u32, 1)); _ = pipewire.pw_main_loop_run(data.loop); pipewire.pw_stream_destroy(data.stream); pipewire.pw_main_loop_destroy(data.loop); pipewire.pw_deinit(); } extern "c" fn pw_log_warn(...) void; const std = @import("std"); const pipewire = @cImport({ @cInclude("pipewire/pipewire.h"); @cInclude("spa/param/audio/format-utils.h"); });