This commit is contained in:
Dario48true 2025-06-14 22:27:21 +02:00
parent fec3a8b16a
commit 366b327b35
2 changed files with 177 additions and 79 deletions

View file

@ -15,19 +15,6 @@ pub fn build(b: *std.Build) void {
// set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{});
// This creates a "module", which represents a collection of source files alongside
// some compilation options, such as optimization mode and linked system libraries.
// Every executable or library we compile will be based on one or more modules.
const lib_mod = b.createModule(.{
// `root_source_file` is the Zig "entry point" of the module. If a module
// only contains e.g. external object files, you can make this `null`.
// In this case the main source file is merely a path, however, in more
// complicated build scripts, this could be a generated file.
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
});
// We will also create a module for our other entry point, 'main.zig'.
const exe_mod = b.createModule(.{
// `root_source_file` is the Zig "entry point" of the module. If a module
@ -37,31 +24,17 @@ pub fn build(b: *std.Build) void {
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
});
// Modules can depend on one another using the `std.Build.Module.addImport` function.
// This is what allows Zig source code to use `@import("foo")` where 'foo' is not a
// file path. In this case, we set up `exe_mod` to import `lib_mod`.
exe_mod.addImport("zig_lib", lib_mod);
// Now, we will create a static library based on the module we created above.
// This creates a `std.Build.Step.Compile`, which is the build step responsible
// for actually invoking the compiler.
const lib = b.addLibrary(.{
.linkage = .static,
.name = "zig",
.root_module = lib_mod,
exe_mod.linkSystemLibrary("libpipewire-0.3", .{
.needed = true,
});
// This declares intent for the library to be installed into the standard
// location when the user invokes the "install" step (the default step when
// running `zig build`).
b.installArtifact(lib);
// This creates another `std.Build.Step.Compile`, but this one builds an executable
// rather than a static library.
const exe = b.addExecutable(.{
.name = "zig",
.name = "recorder",
.root_module = exe_mod,
});
@ -93,14 +66,6 @@ pub fn build(b: *std.Build) void {
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
// Creates a step for unit testing. This only builds the test executable
// but does not run it.
const lib_unit_tests = b.addTest(.{
.root_module = lib_mod,
});
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
const exe_unit_tests = b.addTest(.{
.root_module = exe_mod,
});
@ -111,6 +76,5 @@ pub fn build(b: *std.Build) void {
// the `zig build --help` menu, providing a way for the user to request
// running the unit tests.
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_lib_unit_tests.step);
test_step.dependOn(&run_exe_unit_tests.step);
}

View file

@ -1,46 +1,180 @@
//! By convention, main.zig is where your main function lives in the case that
//! you are building an executable. If you are making a library, the convention
//! is to delete this file and start with root.zig instead.
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 {
// Prints to stderr (it's a shortcut based on `std.io.getStdErr()`)
std.debug.print("All your {s} are belong to us.\n", .{"codebase"});
// stdout is for the actual output of your application, for example if you
// are implementing gzip, then only the compressed bytes should be sent to
// stdout, not any debugging messages.
const stdout_file = std.io.getStdOut().writer();
var bw = std.io.bufferedWriter(stdout_file);
const stdout = bw.writer();
try stdout.print("Run `zig build test` to run the tests.\n", .{});
try bw.flush(); // Don't forget to flush!
}
test "simple test" {
var list = std.ArrayList(i32).init(std.testing.allocator);
defer list.deinit(); // Try commenting this out and see if zig detects the memory leak!
try list.append(42);
try std.testing.expectEqual(@as(i32, 42), list.pop());
}
test "use other module" {
try std.testing.expectEqual(@as(i32, 150), lib.add(100, 50));
}
test "fuzz example" {
const Context = struct {
fn testOne(context: @This(), input: []const u8) anyerror!void {
_ = context;
// Try passing `--fuzz` to `zig build test` and see if it manages to fail this test case!
try std.testing.expect(!std.mem.eql(u8, "canyoufindme", input));
}
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,
},
};
try std.testing.fuzz(Context{}, Context.testOne, .{});
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");
/// This imports the separate module containing `root.zig`. Take a look in `build.zig` for details.
const lib = @import("zig_lib");
const pipewire = @cImport({
@cInclude("pipewire/pipewire.h");
@cInclude("spa/param/audio/format-utils.h");
});