From ed05166348936867f0689c939ebd916202face89 Mon Sep 17 00:00:00 2001 From: Ali Chraghi <63465728+alichraghi@users.noreply.github.com> Date: Sun, 30 Apr 2023 16:36:39 -0700 Subject: [PATCH] sysaudio: improve backend selection; remove i8 sample support (#767) * sysaudio: fix compilation errors * re-order backend selection * remove i8 samples support from backends and disable signedToSigned convertion for now * update sine-wave example --- libs/sysaudio/examples/sine-wave.zig | 11 +++--- libs/sysaudio/sdk.zig | 4 ++- libs/sysaudio/src/alsa.zig | 35 ++++++++----------- libs/sysaudio/src/backends.zig | 20 +++++------ libs/sysaudio/src/coreaudio.zig | 4 +-- libs/sysaudio/src/main.zig | 18 +++------- libs/sysaudio/src/pulseaudio.zig | 51 ++++++++++++---------------- libs/sysaudio/src/wasapi.zig | 10 +++--- 8 files changed, 64 insertions(+), 89 deletions(-) diff --git a/libs/sysaudio/examples/sine-wave.zig b/libs/sysaudio/examples/sine-wave.zig index 445a8e7d..30c97985 100644 --- a/libs/sysaudio/examples/sine-wave.zig +++ b/libs/sysaudio/examples/sine-wave.zig @@ -44,14 +44,13 @@ pub fn main() !void { const pitch = 440.0; const radians_per_second = pitch * 2.0 * std.math.pi; var seconds_offset: f32 = 0.0; -fn writeCallback(_: ?*anyopaque, n_frame: usize) void { +fn writeCallback(_: ?*anyopaque, frames: usize) void { const seconds_per_frame = 1.0 / @intToFloat(f32, player.sampleRate()); - var frame: usize = 0; - while (frame < n_frame) : (frame += 1) { - const sample = std.math.sin((seconds_offset + @intToFloat(f32, frame) * seconds_per_frame) * radians_per_second); - player.writeAll(frame, sample); + for (0..frames) |fi| { + const sample = std.math.sin((seconds_offset + @intToFloat(f32, fi) * seconds_per_frame) * radians_per_second); + player.writeAll(fi, sample); } - seconds_offset = @mod(seconds_offset + seconds_per_frame * @intToFloat(f32, n_frame), 1.0); + seconds_offset = @mod(seconds_offset + seconds_per_frame * @intToFloat(f32, frames), 1.0); } fn deviceChange(_: ?*anyopaque) void { diff --git a/libs/sysaudio/sdk.zig b/libs/sysaudio/sdk.zig index 7686f204..3ff93a3c 100644 --- a/libs/sysaudio/sdk.zig +++ b/libs/sysaudio/sdk.zig @@ -16,7 +16,9 @@ pub fn Sdk(comptime deps: anytype) type { _module = b.createModule(.{ .source_file = .{ .path = sdkPath("/src/main.zig") }, .dependencies = &.{ - .{ .name = "sysjs", .module = deps.sysjs.module(b) }, + .{ .name = "sysjs", .module = b.createModule(.{ + .source_file = .{ .path = "libs/mach-sysjs/src/main.zig" }, + }) }, }, }); return _module.?; diff --git a/libs/sysaudio/src/alsa.zig b/libs/sysaudio/src/alsa.zig index 1d3b370f..4dccb7f6 100644 --- a/libs/sysaudio/src/alsa.zig +++ b/libs/sysaudio/src/alsa.zig @@ -17,7 +17,7 @@ const lib = struct { var snd_pcm_state: *const fn (?*c.snd_pcm_t) callconv(.C) c.snd_pcm_state_t = undefined; var snd_pcm_pause: *const fn (?*c.snd_pcm_t, c_int) callconv(.C) c_int = undefined; var snd_pcm_writei: *const fn (?*c.snd_pcm_t, ?*const anyopaque, c.snd_pcm_uframes_t) callconv(.C) c.snd_pcm_sframes_t = undefined; - var snd_pcm_recover: *const fn (?*c.snd_pcm_t, c_int, c_int) callconv(.C) c_int = undefined; + var snd_pcm_prepare: *const fn (?*c.snd_pcm_t) callconv(.C) c_int = undefined; var snd_pcm_info_set_device: *const fn (?*c.snd_pcm_info_t, c_uint) callconv(.C) void = undefined; var snd_pcm_info_set_subdevice: *const fn (?*c.snd_pcm_info_t, c_uint) callconv(.C) void = undefined; var snd_pcm_info_get_name: *const fn (?*const c.snd_pcm_info_t) callconv(.C) [*c]const u8 = undefined; @@ -71,7 +71,7 @@ const lib = struct { snd_pcm_state = handle.lookup(@TypeOf(snd_pcm_state), "snd_pcm_state") orelse return error.SymbolLookup; snd_pcm_pause = handle.lookup(@TypeOf(snd_pcm_pause), "snd_pcm_pause") orelse return error.SymbolLookup; snd_pcm_writei = handle.lookup(@TypeOf(snd_pcm_writei), "snd_pcm_writei") orelse return error.SymbolLookup; - snd_pcm_recover = handle.lookup(@TypeOf(snd_pcm_recover), "snd_pcm_recover") orelse return error.SymbolLookup; + snd_pcm_prepare = handle.lookup(@TypeOf(snd_pcm_prepare), "snd_pcm_prepare") orelse return error.SymbolLookup; snd_pcm_info_set_device = handle.lookup(@TypeOf(snd_pcm_info_set_device), "snd_pcm_info_set_device") orelse return error.SymbolLookup; snd_pcm_info_set_subdevice = handle.lookup(@TypeOf(snd_pcm_info_set_subdevice), "snd_pcm_info_set_subdevice") orelse return error.SymbolLookup; snd_pcm_info_get_name = handle.lookup(@TypeOf(snd_pcm_info_get_name), "snd_pcm_info_get_name") orelse return error.SymbolLookup; @@ -402,10 +402,7 @@ pub const Context = struct { var fmt_arr = std.ArrayList(main.Format).init(self.allocator); inline for (std.meta.tags(main.Format)) |format| { - if (lib.snd_pcm_format_mask_test( - fmt_mask, - toAlsaFormat(format) catch unreachable, - ) != 0) { + if (lib.snd_pcm_format_mask_test(fmt_mask, toAlsaFormat(format)) != 0) { try fmt_arr.append(format); } } @@ -465,7 +462,7 @@ pub const Context = struct { if ((lib.snd_pcm_set_params( pcm, - toAlsaFormat(format) catch unreachable, + toAlsaFormat(format), c.SND_PCM_ACCESS_RW_INTERLEAVED, @intCast(c_uint, device.channels.len), sample_rate, @@ -584,19 +581,16 @@ pub const Player = struct { ch.*.ptr = self.sample_buffer.ptr + self.format.frameSize(i); } + var underrun = false; while (!self.aborted.load(.Unordered)) { - var frames_left = self.period_size; - while (frames_left > 0) { - self.writeFn(self.user_data, frames_left); - const n = lib.snd_pcm_writei(self.pcm, self.sample_buffer.ptr, frames_left); - if (n < 0) { - if (lib.snd_pcm_recover(self.pcm, @intCast(c_int, n), 1) < 0) { - if (std.debug.runtime_safety) unreachable; - return; - } - return; - } - frames_left -= @intCast(c_uint, n); + if (!underrun) { + self.writeFn(self.user_data, self.period_size); + } + underrun = false; + const n = lib.snd_pcm_writei(self.pcm, self.sample_buffer.ptr, self.period_size); + if (n < 0) { + _ = lib.snd_pcm_prepare(self.pcm); + underrun = true; } } } @@ -676,10 +670,9 @@ pub fn modeToStream(mode: main.Device.Mode) c_uint { }; } -pub fn toAlsaFormat(format: main.Format) !c.snd_pcm_format_t { +pub fn toAlsaFormat(format: main.Format) c.snd_pcm_format_t { return switch (format) { .u8 => c.SND_PCM_FORMAT_U8, - .i8 => c.SND_PCM_FORMAT_S8, .i16 => if (is_little) c.SND_PCM_FORMAT_S16_LE else c.SND_PCM_FORMAT_S16_BE, .i24 => if (is_little) c.SND_PCM_FORMAT_S24_3LE else c.SND_PCM_FORMAT_S24_3BE, .i24_4b => if (is_little) c.SND_PCM_FORMAT_S24_LE else c.SND_PCM_FORMAT_S24_BE, diff --git a/libs/sysaudio/src/backends.zig b/libs/sysaudio/src/backends.zig index b5036fd4..cc6f15e1 100644 --- a/libs/sysaudio/src/backends.zig +++ b/libs/sysaudio/src/backends.zig @@ -4,16 +4,16 @@ const std = @import("std"); pub const Backend = std.meta.Tag(BackendContext); pub const BackendContext = switch (builtin.os.tag) { .linux => union(enum) { - pulseaudio: *@import("pulseaudio.zig").Context, - jack: *@import("jack.zig").Context, - pipewire: *@import("pipewire.zig").Context, alsa: *@import("alsa.zig").Context, + pipewire: *@import("pipewire.zig").Context, + jack: *@import("jack.zig").Context, + pulseaudio: *@import("pulseaudio.zig").Context, dummy: *@import("dummy.zig").Context, }, .freebsd, .netbsd, .openbsd, .solaris => union(enum) { - pulseaudio: *@import("pulseaudio.zig").Context, - jack: *@import("jack.zig").Context, pipewire: *@import("pipewire.zig").Context, + jack: *@import("jack.zig").Context, + pulseaudio: *@import("pulseaudio.zig").Context, dummy: *@import("dummy.zig").Context, }, .macos, .ios, .watchos, .tvos => union(enum) { @@ -39,16 +39,16 @@ pub const BackendContext = switch (builtin.os.tag) { }; pub const BackendPlayer = switch (builtin.os.tag) { .linux => union(enum) { - pulseaudio: *@import("pulseaudio.zig").Player, - jack: *@import("jack.zig").Player, - pipewire: *@import("pipewire.zig").Player, alsa: *@import("alsa.zig").Player, + pipewire: *@import("pipewire.zig").Player, + jack: *@import("jack.zig").Player, + pulseaudio: *@import("pulseaudio.zig").Player, dummy: *@import("dummy.zig").Player, }, .freebsd, .netbsd, .openbsd, .solaris => union(enum) { - pulseaudio: *@import("pulseaudio.zig").Player, - jack: *@import("jack.zig").Player, pipewire: *@import("pipewire.zig").Player, + jack: *@import("jack.zig").Player, + pulseaudio: *@import("pulseaudio.zig").Player, dummy: *@import("dummy.zig").Player, }, .macos, .ios, .watchos, .tvos => union(enum) { diff --git a/libs/sysaudio/src/coreaudio.zig b/libs/sysaudio/src/coreaudio.zig index 912db47d..034d3c22 100644 --- a/libs/sysaudio/src/coreaudio.zig +++ b/libs/sysaudio/src/coreaudio.zig @@ -337,7 +337,7 @@ pub const Context = struct { return error.OpeningDevice; } - const stream_desc = createStreamDesc(options.format, options.sample_rate, device.channels.len); + const stream_desc = try createStreamDesc(options.format, options.sample_rate, device.channels.len); if (c.AudioUnitSetProperty( audio_unit, c.kAudioUnitProperty_StreamFormat, @@ -487,7 +487,6 @@ fn createStreamDesc(format: main.Format, sample_rate: u24, ch_count: usize) !c.A .mSampleRate = @intToFloat(f64, sample_rate), .mFormatID = c.kAudioFormatLinearPCM, .mFormatFlags = switch (format) { - .i8 => c.kAudioFormatFlagIsSignedInteger, .i16 => c.kAudioFormatFlagIsSignedInteger, .i24 => c.kAudioFormatFlagIsSignedInteger, .i32 => c.kAudioFormatFlagIsSignedInteger, @@ -500,7 +499,6 @@ fn createStreamDesc(format: main.Format, sample_rate: u24, ch_count: usize) !c.A .mBytesPerFrame = format.frameSize(ch_count), .mChannelsPerFrame = @intCast(c_uint, ch_count), .mBitsPerChannel = switch (format) { - .i8 => 8, .i16 => 16, .i24 => 24, .i32 => 32, diff --git a/libs/sysaudio/src/main.zig b/libs/sysaudio/src/main.zig index 96cfff17..302a2e45 100644 --- a/libs/sysaudio/src/main.zig +++ b/libs/sysaudio/src/main.zig @@ -213,24 +213,17 @@ pub const Player = struct { f32 => floatToUnsigned(u8, sample), else => unreachable, }, - .i8 => std.mem.bytesAsValue(i8, ptr[0..@sizeOf(i8)]).* = switch (@TypeOf(sample)) { - i8 => sample, - u8 => unsignedToSigned(i8, sample), - i16, i24, i32 => signedToSigned(i8, sample), - f32 => floatToSigned(i8, sample), - else => unreachable, - }, .i16 => std.mem.bytesAsValue(i16, ptr[0..@sizeOf(i16)]).* = switch (@TypeOf(sample)) { i16 => sample, u8 => unsignedToSigned(i16, sample), - i8, i24, i32 => signedToSigned(i16, sample), + // i8, i24, i32 => signedToSigned(i16, sample), f32 => floatToSigned(i16, sample), else => unreachable, }, .i24 => std.mem.bytesAsValue(i24, ptr[0..@sizeOf(i24)]).* = switch (@TypeOf(sample)) { i24 => sample, u8 => unsignedToSigned(i24, sample), - i8, i16, i32 => signedToSigned(i24, sample), + // i8, i16, i32 => signedToSigned(i24, sample), f32 => floatToSigned(i24, sample), else => unreachable, }, @@ -238,7 +231,7 @@ pub const Player = struct { .i32 => std.mem.bytesAsValue(i32, ptr[0..@sizeOf(i32)]).* = switch (@TypeOf(sample)) { i32 => sample, u8 => unsignedToSigned(i32, sample), - i8, i16, i24 => signedToSigned(i32, sample), + // i8, i16, i24 => signedToSigned(i32, sample), f32 => floatToSigned(i32, sample), else => unreachable, }, @@ -377,7 +370,6 @@ pub const Channel = struct { pub const Format = enum { u8, - i8, i16, i24, i24_4b, @@ -386,7 +378,7 @@ pub const Format = enum { pub fn size(self: Format) u8 { return switch (self) { - .u8, .i8 => 1, + .u8 => 1, .i16 => 2, .i24 => 3, .i24_4b, .i32, .f32 => 4, @@ -395,7 +387,7 @@ pub const Format = enum { pub fn validSize(self: Format) u8 { return switch (self) { - .u8, .i8 => 1, + .u8 => 1, .i16 => 2, .i24, .i24_4b => 3, .i32, .f32 => 4, diff --git a/libs/sysaudio/src/pulseaudio.zig b/libs/sysaudio/src/pulseaudio.zig index beae37c9..d3322699 100644 --- a/libs/sysaudio/src/pulseaudio.zig +++ b/libs/sysaudio/src/pulseaudio.zig @@ -327,7 +327,7 @@ pub const Context = struct { const sample_rate = device.sample_rate.clamp(options.sample_rate); const sample_spec = c.pa_sample_spec{ - .format = toPAFormat(format) catch unreachable, + .format = toPAFormat(format), .rate = sample_rate, .channels = @intCast(u5, device.channels.len), }; @@ -459,33 +459,28 @@ pub const Player = struct { var self = @ptrCast(*Player, @alignCast(@alignOf(*Player), user_data.?)); var frames_left = nbytes; - while (frames_left > 0) { - var chunk_size = frames_left; - if (lib.pa_stream_begin_write( - self.stream, - @ptrCast( - [*c]?*anyopaque, - @alignCast(@alignOf([*c]?*anyopaque), &self.write_ptr), - ), - &chunk_size, - ) != 0) { - if (std.debug.runtime_safety) unreachable; - return; - } + if (lib.pa_stream_begin_write( + self.stream, + @ptrCast( + [*c]?*anyopaque, + @alignCast(@alignOf([*c]?*anyopaque), &self.write_ptr), + ), + &frames_left, + ) != 0) { + if (std.debug.runtime_safety) unreachable; + return; + } - for (self.channels, 0..) |*ch, i| { - ch.*.ptr = self.write_ptr + self.format.frameSize(i); - } + for (self.channels, 0..) |*ch, i| { + ch.*.ptr = self.write_ptr + self.format.frameSize(i); + } - const frames = chunk_size / self.format.frameSize(self.channels.len); - self.writeFn(self.user_data, frames); + const frames = frames_left / self.format.frameSize(self.channels.len); + self.writeFn(self.user_data, frames); - if (lib.pa_stream_write(self.stream, self.write_ptr, chunk_size, null, 0, c.PA_SEEK_RELATIVE) != 0) { - if (std.debug.runtime_safety) unreachable; - return; - } - - frames_left -= chunk_size; + if (lib.pa_stream_write(self.stream, self.write_ptr, frames_left, null, 0, c.PA_SEEK_RELATIVE) != 0) { + if (std.debug.runtime_safety) unreachable; + return; } } @@ -616,7 +611,7 @@ pub fn fromPAChannelPos(pos: c.pa_channel_position_t) !main.Channel.Id { c.PA_CHANNEL_POSITION_SIDE_RIGHT => .side_right, // TODO: .front_center? - c.PA_CHANNEL_POSITION_AUX0...c.PA_CHANNEL_POSITION_AUX31 => error.Invalid, + c.PA_CHANNEL_POSITION_AUX0...c.PA_CHANNEL_POSITION_AUX31 => .front_center, c.PA_CHANNEL_POSITION_TOP_CENTER => .top_center, c.PA_CHANNEL_POSITION_TOP_FRONT_LEFT => .top_front_left, @@ -630,7 +625,7 @@ pub fn fromPAChannelPos(pos: c.pa_channel_position_t) !main.Channel.Id { }; } -pub fn toPAFormat(format: main.Format) !c.pa_sample_format_t { +pub fn toPAFormat(format: main.Format) c.pa_sample_format_t { return switch (format) { .u8 => c.PA_SAMPLE_U8, .i16 => if (is_little) c.PA_SAMPLE_S16LE else c.PA_SAMPLE_S16BE, @@ -638,8 +633,6 @@ pub fn toPAFormat(format: main.Format) !c.pa_sample_format_t { .i24_4b => if (is_little) c.PA_SAMPLE_S24_32LE else c.PA_SAMPLE_S24_32BE, .i32 => if (is_little) c.PA_SAMPLE_S32LE else c.PA_SAMPLE_S32BE, .f32 => if (is_little) c.PA_SAMPLE_FLOAT32LE else c.PA_SAMPLE_FLOAT32BE, - - .i8 => error.Invalid, }; } diff --git a/libs/sysaudio/src/wasapi.zig b/libs/sysaudio/src/wasapi.zig index 07e14a01..1e496f9c 100644 --- a/libs/sysaudio/src/wasapi.zig +++ b/libs/sysaudio/src/wasapi.zig @@ -327,7 +327,7 @@ pub const Context = struct { var fmt_arr = std.ArrayList(main.Format).init(self.allocator); var closest_match: ?*win32.WAVEFORMATEX = null; for (std.meta.tags(main.Format)) |format| { - setWaveFormatFormat(wf, format) catch continue; + setWaveFormatFormat(wf, format); if (audio_client.?.IsFormatSupported( .SHARED, @ptrCast(?*const win32.WAVEFORMATEX, @alignCast(@alignOf(*win32.WAVEFORMATEX), wf)), @@ -417,7 +417,7 @@ pub const Context = struct { }; } - fn setWaveFormatFormat(wf: *win32.WAVEFORMATEXTENSIBLE, format: main.Format) !void { + fn setWaveFormatFormat(wf: *win32.WAVEFORMATEXTENSIBLE, format: main.Format) void { switch (format) { .u8, .i16, .i24, .i24_4b, .i32 => { wf.SubFormat = win32.CLSID_KSDATAFORMAT_SUBTYPE_PCM.*; @@ -425,7 +425,6 @@ pub const Context = struct { .f32 => { wf.SubFormat = win32.CLSID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT.*; }, - .i8 => return error.Invalid, } wf.Format.wBitsPerSample = format.sizeBits(); wf.Samples.wValidBitsPerSample = format.validSizeBits(); @@ -488,7 +487,7 @@ pub const Context = struct { .wValidBitsPerSample = format.validSizeBits(), }, .dwChannelMask = toChannelMask(device.channels), - .SubFormat = toSubFormat(format) catch return error.OpeningDevice, + .SubFormat = toSubFormat(format), }; if (!self.is_wine and audio_client3 != null) { @@ -608,7 +607,7 @@ pub const Context = struct { return .{ .wasapi = player }; } - fn toSubFormat(format: main.Format) !win32.Guid { + fn toSubFormat(format: main.Format) win32.Guid { return switch (format) { .u8, .i16, @@ -617,7 +616,6 @@ pub const Context = struct { .i32, => win32.CLSID_KSDATAFORMAT_SUBTYPE_PCM.*, .f32 => win32.CLSID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT.*, - else => error.Invalid, }; }