pipewire-recorder-zig/src/main.zig
Dario48true 366b327b35 commit
2025-06-14 22:27:21 +02:00

180 lines
5.9 KiB
Zig

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");
});