commit
This commit is contained in:
parent
fec3a8b16a
commit
366b327b35
2 changed files with 177 additions and 79 deletions
44
build.zig
44
build.zig
|
@ -15,19 +15,6 @@ pub fn build(b: *std.Build) void {
|
||||||
// set a preferred release mode, allowing the user to decide how to optimize.
|
// set a preferred release mode, allowing the user to decide how to optimize.
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
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'.
|
// We will also create a module for our other entry point, 'main.zig'.
|
||||||
const exe_mod = b.createModule(.{
|
const exe_mod = b.createModule(.{
|
||||||
// `root_source_file` is the Zig "entry point" of the module. If a module
|
// `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"),
|
.root_source_file = b.path("src/main.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
|
.link_libc = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Modules can depend on one another using the `std.Build.Module.addImport` function.
|
exe_mod.linkSystemLibrary("libpipewire-0.3", .{
|
||||||
// This is what allows Zig source code to use `@import("foo")` where 'foo' is not a
|
.needed = true,
|
||||||
// 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,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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
|
// This creates another `std.Build.Step.Compile`, but this one builds an executable
|
||||||
// rather than a static library.
|
// rather than a static library.
|
||||||
const exe = b.addExecutable(.{
|
const exe = b.addExecutable(.{
|
||||||
.name = "zig",
|
.name = "recorder",
|
||||||
.root_module = exe_mod,
|
.root_module = exe_mod,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -93,14 +66,6 @@ pub fn build(b: *std.Build) void {
|
||||||
const run_step = b.step("run", "Run the app");
|
const run_step = b.step("run", "Run the app");
|
||||||
run_step.dependOn(&run_cmd.step);
|
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(.{
|
const exe_unit_tests = b.addTest(.{
|
||||||
.root_module = exe_mod,
|
.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
|
// the `zig build --help` menu, providing a way for the user to request
|
||||||
// running the unit tests.
|
// running the unit tests.
|
||||||
const test_step = b.step("test", "Run 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);
|
test_step.dependOn(&run_exe_unit_tests.step);
|
||||||
}
|
}
|
||||||
|
|
212
src/main.zig
212
src/main.zig
|
@ -1,46 +1,180 @@
|
||||||
//! By convention, main.zig is where your main function lives in the case that
|
const data_struct = struct {
|
||||||
//! you are building an executable. If you are making a library, the convention
|
loop: ?*pipewire.pw_main_loop,
|
||||||
//! is to delete this file and start with root.zig instead.
|
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 {
|
pub fn main() !void {
|
||||||
// Prints to stderr (it's a shortcut based on `std.io.getStdErr()`)
|
var data: data_struct = undefined;
|
||||||
std.debug.print("All your {s} are belong to us.\n", .{"codebase"});
|
var params: [1][*c]const pipewire.spa_pod = undefined;
|
||||||
|
var buffer: [1024]u8 = undefined;
|
||||||
// stdout is for the actual output of your application, for example if you
|
var props: *pipewire.pw_properties = undefined;
|
||||||
// are implementing gzip, then only the compressed bytes should be sent to
|
var b: pipewire.spa_pod_builder = pipewire.spa_pod_builder{
|
||||||
// stdout, not any debugging messages.
|
.data = &buffer,
|
||||||
const stdout_file = std.io.getStdOut().writer();
|
.size = @sizeOf([1024]u8),
|
||||||
var bw = std.io.bufferedWriter(stdout_file);
|
._padding = 0,
|
||||||
const stdout = bw.writer();
|
.state = .{
|
||||||
|
.flags = 0,
|
||||||
try stdout.print("Run `zig build test` to run the tests.\n", .{});
|
.offset = 0,
|
||||||
|
.frame = null,
|
||||||
try bw.flush(); // Don't forget to flush!
|
},
|
||||||
}
|
.callbacks = .{
|
||||||
|
.data = null,
|
||||||
test "simple test" {
|
.funcs = null,
|
||||||
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));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
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");
|
const std = @import("std");
|
||||||
|
|
||||||
/// This imports the separate module containing `root.zig`. Take a look in `build.zig` for details.
|
const pipewire = @cImport({
|
||||||
const lib = @import("zig_lib");
|
@cInclude("pipewire/pipewire.h");
|
||||||
|
@cInclude("spa/param/audio/format-utils.h");
|
||||||
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue