sysaudio: rewrite in zig
removes libsoundio dependency
This commit is contained in:
parent
8aa2c97079
commit
0f3e28bc2a
27 changed files with 4714 additions and 1344 deletions
274
libs/sysaudio/src/jack.zig
Normal file
274
libs/sysaudio/src/jack.zig
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
const std = @import("std");
|
||||
const c = @cImport(@cInclude("jack/jack.h"));
|
||||
const main = @import("main.zig");
|
||||
const backends = @import("backends.zig");
|
||||
const util = @import("util.zig");
|
||||
|
||||
pub const Context = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
devices_info: util.DevicesInfo,
|
||||
client: *c.jack_client_t,
|
||||
watcher: ?Watcher,
|
||||
|
||||
const Watcher = struct {
|
||||
deviceChangeFn: main.DeviceChangeFn,
|
||||
userdata: ?*anyopaque,
|
||||
};
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, options: main.Context.Options) !backends.BackendContext {
|
||||
c.jack_set_error_function(@ptrCast(?*const fn ([*c]const u8) callconv(.C) void, &util.doNothing));
|
||||
c.jack_set_info_function(@ptrCast(?*const fn ([*c]const u8) callconv(.C) void, &util.doNothing));
|
||||
|
||||
var status: c.jack_status_t = 0;
|
||||
var self = try allocator.create(Context);
|
||||
errdefer allocator.destroy(self);
|
||||
self.* = .{
|
||||
.allocator = allocator,
|
||||
.devices_info = util.DevicesInfo.init(),
|
||||
.client = c.jack_client_open(options.app_name.ptr, c.JackNoStartServer, &status) orelse {
|
||||
std.debug.assert(status & c.JackInvalidOption == 0);
|
||||
return if (status & c.JackShmFailure != 0)
|
||||
error.SystemResources
|
||||
else
|
||||
error.ConnectionRefused;
|
||||
},
|
||||
.watcher = if (options.deviceChangeFn) |deviceChangeFn| .{
|
||||
.deviceChangeFn = deviceChangeFn,
|
||||
.userdata = options.userdata,
|
||||
} else null,
|
||||
};
|
||||
|
||||
if (options.deviceChangeFn) |_| {
|
||||
if (c.jack_set_sample_rate_callback(self.client, sampleRateCallback, self) != 0 or
|
||||
c.jack_set_port_registration_callback(self.client, portRegistrationCallback, self) != 0 or
|
||||
c.jack_set_port_rename_callback(self.client, portRenameCalllback, self) != 0)
|
||||
return error.ConnectionRefused;
|
||||
}
|
||||
|
||||
return .{ .jack = self };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Context) void {
|
||||
for (self.devices_info.list.items) |device|
|
||||
freeDevice(self.allocator, device);
|
||||
self.devices_info.list.deinit(self.allocator);
|
||||
self.allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn refresh(self: *Context) !void {
|
||||
for (self.devices_info.list.items) |d|
|
||||
freeDevice(self.allocator, d);
|
||||
self.devices_info.clear(self.allocator);
|
||||
|
||||
const sample_rate = @intCast(u24, c.jack_get_sample_rate(self.client));
|
||||
|
||||
const port_names = c.jack_get_ports(self.client, null, null, 0) orelse
|
||||
return error.OutOfMemory;
|
||||
defer c.jack_free(@ptrCast(?*anyopaque, port_names));
|
||||
|
||||
var i: usize = 0;
|
||||
outer: while (port_names[i] != null) : (i += 1) {
|
||||
const port = c.jack_port_by_name(self.client, port_names[i]) orelse break;
|
||||
const port_type = c.jack_port_type(port)[0..@intCast(usize, c.jack_port_type_size())];
|
||||
if (!std.mem.startsWith(u8, port_type, c.JACK_DEFAULT_AUDIO_TYPE))
|
||||
continue;
|
||||
|
||||
const flags = c.jack_port_flags(port);
|
||||
const mode: main.Device.Mode = if (flags & c.JackPortIsInput != 0) .capture else .playback;
|
||||
|
||||
const name = std.mem.span(port_names[i]);
|
||||
const id = std.mem.sliceTo(name, ':');
|
||||
|
||||
for (self.devices_info.list.items) |*dev| {
|
||||
if (std.mem.eql(u8, dev.id, id) and mode == dev.mode) {
|
||||
const new_ch = main.Channel{
|
||||
.id = @intToEnum(main.Channel.Id, dev.channels.len),
|
||||
};
|
||||
dev.channels = try self.allocator.realloc(dev.channels, dev.channels.len + 1);
|
||||
dev.channels[dev.channels.len - 1] = new_ch;
|
||||
break :outer;
|
||||
}
|
||||
}
|
||||
|
||||
var device = main.Device{
|
||||
.id = try self.allocator.dupeZ(u8, id),
|
||||
.name = name,
|
||||
.mode = mode,
|
||||
.channels = blk: {
|
||||
var channels = try self.allocator.alloc(main.Channel, 1);
|
||||
channels[0] = .{ .id = @intToEnum(main.Channel.Id, 0) };
|
||||
break :blk channels;
|
||||
},
|
||||
.formats = &.{.f32},
|
||||
.sample_rate = .{
|
||||
.min = sample_rate,
|
||||
.max = sample_rate,
|
||||
},
|
||||
};
|
||||
|
||||
try self.devices_info.list.append(self.allocator, device);
|
||||
if (std.mem.eql(u8, "system", id)) {
|
||||
self.devices_info.setDefault(device.mode, self.devices_info.list.items.len - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sampleRateCallback(_: c.jack_nframes_t, arg: ?*anyopaque) callconv(.C) c_int {
|
||||
var self = @ptrCast(*Context, @alignCast(@alignOf(*Context), arg.?));
|
||||
self.watcher.?.deviceChangeFn(self.watcher.?.userdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn portRegistrationCallback(_: c.jack_port_id_t, _: c_int, arg: ?*anyopaque) callconv(.C) void {
|
||||
var self = @ptrCast(*Context, @alignCast(@alignOf(*Context), arg.?));
|
||||
self.watcher.?.deviceChangeFn(self.watcher.?.userdata);
|
||||
}
|
||||
|
||||
fn portRenameCalllback(_: c.jack_port_id_t, _: [*c]const u8, _: [*c]const u8, arg: ?*anyopaque) callconv(.C) void {
|
||||
var self = @ptrCast(*Context, @alignCast(@alignOf(*Context), arg.?));
|
||||
self.watcher.?.deviceChangeFn(self.watcher.?.userdata);
|
||||
}
|
||||
|
||||
pub fn devices(self: *Context) []const main.Device {
|
||||
return self.devices_info.list.items;
|
||||
}
|
||||
|
||||
pub fn defaultDevice(self: *Context, mode: main.Device.Mode) ?main.Device {
|
||||
return self.devices_info.default(mode);
|
||||
}
|
||||
|
||||
pub fn createPlayer(self: *Context, device: main.Device, writeFn: main.WriteFn, options: main.Player.Options) !backends.BackendPlayer {
|
||||
_ = options;
|
||||
|
||||
var ports = try self.allocator.alloc(*c.jack_port_t, device.channels.len);
|
||||
var dest_ports = try self.allocator.alloc([:0]const u8, ports.len);
|
||||
var buf: [64]u8 = undefined;
|
||||
for (device.channels) |_, i| {
|
||||
const port_name = std.fmt.bufPrintZ(&buf, "playback_{d}", .{i + 1}) catch unreachable;
|
||||
const dest_name = try std.fmt.allocPrintZ(self.allocator, "{s}:{s}", .{ device.id, port_name });
|
||||
ports[i] = c.jack_port_register(self.client, port_name.ptr, c.JACK_DEFAULT_AUDIO_TYPE, c.JackPortIsOutput, 0) orelse
|
||||
return error.OpeningDevice;
|
||||
dest_ports[i] = dest_name;
|
||||
}
|
||||
|
||||
return .{
|
||||
.jack = .{
|
||||
.allocator = self.allocator,
|
||||
.mutex = .{},
|
||||
.cond = .{},
|
||||
.device = device,
|
||||
.writeFn = writeFn,
|
||||
.client = self.client,
|
||||
.ports = ports,
|
||||
.dest_ports = dest_ports,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Player = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
mutex: std.Thread.Mutex,
|
||||
cond: std.Thread.Condition,
|
||||
device: main.Device,
|
||||
writeFn: main.WriteFn,
|
||||
client: *c.jack_client_t,
|
||||
ports: []const *c.jack_port_t,
|
||||
dest_ports: []const [:0]const u8,
|
||||
|
||||
pub fn deinit(self: *Player) void {
|
||||
self.allocator.free(self.ports);
|
||||
for (self.dest_ports) |d|
|
||||
self.allocator.free(d);
|
||||
self.allocator.free(self.dest_ports);
|
||||
}
|
||||
|
||||
pub fn start(self: *Player) !void {
|
||||
if (c.jack_set_process_callback(self.client, processCallback, self) != 0)
|
||||
return error.CannotPlay;
|
||||
|
||||
if (c.jack_activate(self.client) != 0)
|
||||
return error.CannotPlay;
|
||||
|
||||
for (self.ports) |port, i| {
|
||||
if (c.jack_connect(self.client, c.jack_port_name(port), self.dest_ports[i].ptr) != 0)
|
||||
return error.CannotPlay;
|
||||
}
|
||||
}
|
||||
|
||||
fn processCallback(n_frames: c.jack_nframes_t, self_opaque: ?*anyopaque) callconv(.C) c_int {
|
||||
const self = @ptrCast(*Player, @alignCast(@alignOf(*Player), self_opaque.?));
|
||||
var parent = @fieldParentPtr(main.Player, "data", @ptrCast(*backends.BackendPlayer, self));
|
||||
for (self.channels()) |*ch, i| {
|
||||
ch.*.ptr = @ptrCast([*]u8, c.jack_port_get_buffer(self.ports[i], n_frames));
|
||||
}
|
||||
self.writeFn(parent, n_frames);
|
||||
return 0;
|
||||
}
|
||||
|
||||
pub fn play(self: *Player) !void {
|
||||
self.mutex.lock();
|
||||
defer self.mutex.unlock();
|
||||
for (self.ports) |port, i| {
|
||||
if (c.jack_connect(self.client, c.jack_port_name(port), self.dest_ports[i].ptr) != 0)
|
||||
return error.CannotPlay;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pause(self: *Player) !void {
|
||||
self.mutex.lock();
|
||||
defer self.mutex.unlock();
|
||||
for (self.ports) |port, i| {
|
||||
if (c.jack_disconnect(self.client, c.jack_port_name(port), self.dest_ports[i].ptr) != 0)
|
||||
return error.CannotPause;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paused(self: *Player) bool {
|
||||
self.mutex.lock();
|
||||
defer self.mutex.unlock();
|
||||
for (self.ports) |port, i| {
|
||||
if (c.jack_port_connected_to(port, self.dest_ports[i].ptr) == 1)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn setVolume(self: *Player, vol: f32) !void {
|
||||
_ = self;
|
||||
_ = vol;
|
||||
@panic("incompatible backend");
|
||||
}
|
||||
|
||||
pub fn volume(self: *Player) !f32 {
|
||||
_ = self;
|
||||
@panic("incompatible backend");
|
||||
}
|
||||
|
||||
pub fn writeRaw(self: *Player, channel: main.Channel, frame: usize, sample: anytype) void {
|
||||
var ptr = channel.ptr + frame * self.format().size();
|
||||
std.mem.bytesAsValue(@TypeOf(sample), ptr[0..@sizeOf(@TypeOf(sample))]).* = sample;
|
||||
}
|
||||
|
||||
pub fn channels(self: Player) []main.Channel {
|
||||
return self.device.channels;
|
||||
}
|
||||
|
||||
pub fn format(self: Player) main.Format {
|
||||
_ = self;
|
||||
return .f32;
|
||||
}
|
||||
|
||||
pub fn sampleRate(self: Player) u24 {
|
||||
return @intCast(u24, c.jack_get_sample_rate(self.client));
|
||||
}
|
||||
};
|
||||
|
||||
pub fn freeDevice(allocator: std.mem.Allocator, device: main.Device) void {
|
||||
allocator.free(device.id);
|
||||
allocator.free(device.channels);
|
||||
}
|
||||
|
||||
test {
|
||||
std.testing.refAllDeclsRecursive(@This());
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue