180 lines
5.9 KiB
Zig
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");
|
|
});
|