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
649
libs/sysaudio/src/alsa.zig
Normal file
649
libs/sysaudio/src/alsa.zig
Normal file
|
|
@ -0,0 +1,649 @@
|
|||
const std = @import("std");
|
||||
const c = @cImport(@cInclude("alsa/asoundlib.h"));
|
||||
const main = @import("main.zig");
|
||||
const backends = @import("backends.zig");
|
||||
const util = @import("util.zig");
|
||||
const inotify_event = std.os.linux.inotify_event;
|
||||
const is_little = @import("builtin").cpu.arch.endian() == .Little;
|
||||
|
||||
pub const Context = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
devices_info: util.DevicesInfo,
|
||||
watcher: ?Watcher,
|
||||
|
||||
const Watcher = struct {
|
||||
deviceChangeFn: main.DeviceChangeFn,
|
||||
userdata: ?*anyopaque,
|
||||
thread: std.Thread,
|
||||
aborted: std.atomic.Atomic(bool),
|
||||
notify_fd: std.os.fd_t,
|
||||
notify_wd: std.os.fd_t,
|
||||
notify_pipe_fd: [2]std.os.fd_t,
|
||||
};
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, options: main.Context.Options) !backends.BackendContext {
|
||||
_ = c.snd_lib_error_set_handler(@ptrCast(c.snd_lib_error_handler_t, &util.doNothing));
|
||||
|
||||
var self = try allocator.create(Context);
|
||||
errdefer allocator.destroy(self);
|
||||
self.* = .{
|
||||
.allocator = allocator,
|
||||
.devices_info = util.DevicesInfo.init(),
|
||||
.watcher = blk: {
|
||||
if (options.deviceChangeFn) |deviceChangeFn| {
|
||||
const notify_fd = std.os.inotify_init1(std.os.linux.IN.NONBLOCK) catch |err| switch (err) {
|
||||
error.ProcessFdQuotaExceeded,
|
||||
error.SystemFdQuotaExceeded,
|
||||
error.SystemResources,
|
||||
=> return error.SystemResources,
|
||||
error.Unexpected => unreachable,
|
||||
};
|
||||
errdefer std.os.close(notify_fd);
|
||||
|
||||
const notify_wd = std.os.inotify_add_watch(
|
||||
notify_fd,
|
||||
"/dev/snd",
|
||||
std.os.linux.IN.CREATE | std.os.linux.IN.DELETE,
|
||||
) catch |err| switch (err) {
|
||||
error.AccessDenied => return error.AccessDenied,
|
||||
error.UserResourceLimitReached,
|
||||
error.NotDir,
|
||||
error.FileNotFound,
|
||||
error.SystemResources,
|
||||
=> return error.SystemResources,
|
||||
error.NameTooLong,
|
||||
error.WatchAlreadyExists,
|
||||
error.Unexpected,
|
||||
=> unreachable,
|
||||
};
|
||||
errdefer std.os.inotify_rm_watch(notify_fd, notify_wd);
|
||||
|
||||
const notify_pipe_fd = std.os.pipe2(std.os.O.NONBLOCK) catch |err| switch (err) {
|
||||
error.ProcessFdQuotaExceeded,
|
||||
error.SystemFdQuotaExceeded,
|
||||
=> return error.SystemResources,
|
||||
error.Unexpected => unreachable,
|
||||
};
|
||||
errdefer {
|
||||
std.os.close(notify_pipe_fd[0]);
|
||||
std.os.close(notify_pipe_fd[1]);
|
||||
}
|
||||
|
||||
break :blk .{
|
||||
.deviceChangeFn = deviceChangeFn,
|
||||
.userdata = options.userdata,
|
||||
.aborted = .{ .value = false },
|
||||
.notify_fd = notify_fd,
|
||||
.notify_wd = notify_wd,
|
||||
.notify_pipe_fd = notify_pipe_fd,
|
||||
.thread = std.Thread.spawn(.{}, deviceEventsLoop, .{self}) catch |err| switch (err) {
|
||||
error.ThreadQuotaExceeded,
|
||||
error.SystemResources,
|
||||
error.LockedMemoryLimitExceeded,
|
||||
=> return error.SystemResources,
|
||||
error.OutOfMemory => return error.OutOfMemory,
|
||||
error.Unexpected => unreachable,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
break :blk null;
|
||||
},
|
||||
};
|
||||
|
||||
return .{ .alsa = self };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Context) void {
|
||||
if (self.watcher) |*watcher| {
|
||||
watcher.aborted.store(true, .Unordered);
|
||||
_ = std.os.write(watcher.notify_pipe_fd[1], "a") catch {};
|
||||
watcher.thread.join();
|
||||
|
||||
std.os.close(watcher.notify_pipe_fd[0]);
|
||||
std.os.close(watcher.notify_pipe_fd[1]);
|
||||
std.os.inotify_rm_watch(watcher.notify_fd, watcher.notify_wd);
|
||||
std.os.close(watcher.notify_fd);
|
||||
}
|
||||
|
||||
for (self.devices_info.list.items) |d|
|
||||
freeDevice(self.allocator, d);
|
||||
self.devices_info.list.deinit(self.allocator);
|
||||
self.allocator.destroy(self);
|
||||
}
|
||||
|
||||
fn deviceEventsLoop(self: *Context) void {
|
||||
var watcher = self.watcher.?;
|
||||
var scan = false;
|
||||
var last_crash: ?i64 = null;
|
||||
var buf: [2048]u8 = undefined;
|
||||
var fds = [2]std.os.pollfd{
|
||||
.{
|
||||
.fd = watcher.notify_fd,
|
||||
.events = std.os.POLL.IN,
|
||||
.revents = 0,
|
||||
},
|
||||
.{
|
||||
.fd = watcher.notify_pipe_fd[0],
|
||||
.events = std.os.POLL.IN,
|
||||
.revents = 0,
|
||||
},
|
||||
};
|
||||
|
||||
while (!watcher.aborted.load(.Unordered)) {
|
||||
_ = std.os.poll(&fds, -1) catch |err| switch (err) {
|
||||
error.NetworkSubsystemFailed,
|
||||
error.SystemResources,
|
||||
=> {
|
||||
const ts = std.time.milliTimestamp();
|
||||
if (last_crash) |lc| {
|
||||
if (ts - lc < 500) return;
|
||||
}
|
||||
last_crash = ts;
|
||||
continue;
|
||||
},
|
||||
error.Unexpected => unreachable,
|
||||
};
|
||||
if (watcher.notify_fd & std.os.POLL.IN != 0) {
|
||||
while (true) {
|
||||
const len = std.os.read(watcher.notify_fd, &buf) catch |err| {
|
||||
if (err == error.WouldBlock) break;
|
||||
const ts = std.time.milliTimestamp();
|
||||
if (last_crash) |lc| {
|
||||
if (ts - lc < 500) return;
|
||||
}
|
||||
last_crash = ts;
|
||||
break;
|
||||
};
|
||||
if (len == 0) break;
|
||||
|
||||
var i: usize = 0;
|
||||
var evt: *inotify_event = undefined;
|
||||
while (i < buf.len) : (i += @sizeOf(inotify_event) + evt.len) {
|
||||
evt = @ptrCast(*inotify_event, @alignCast(4, buf[i..]));
|
||||
const evt_name = @ptrCast([*]u8, buf[i..])[@sizeOf(inotify_event) .. @sizeOf(inotify_event) + 8];
|
||||
|
||||
if (evt.mask & std.os.linux.IN.ISDIR != 0 or !std.mem.startsWith(u8, evt_name, "pcm"))
|
||||
continue;
|
||||
|
||||
scan = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (scan) {
|
||||
watcher.deviceChangeFn(self.watcher.?.userdata);
|
||||
scan = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh(self: *Context) !void {
|
||||
for (self.devices_info.list.items) |d|
|
||||
freeDevice(self.allocator, d);
|
||||
self.devices_info.clear(self.allocator);
|
||||
|
||||
var pcm_info: ?*c.snd_pcm_info_t = null;
|
||||
_ = c.snd_pcm_info_malloc(&pcm_info);
|
||||
defer c.snd_pcm_info_free(pcm_info);
|
||||
|
||||
var card_idx: c_int = -1;
|
||||
if (c.snd_card_next(&card_idx) < 0)
|
||||
return error.SystemResources;
|
||||
|
||||
while (card_idx >= 0) {
|
||||
var card_id_buf: [8]u8 = undefined;
|
||||
const card_id = std.fmt.bufPrintZ(&card_id_buf, "hw:{d}", .{card_idx}) catch break;
|
||||
|
||||
var ctl: ?*c.snd_ctl_t = undefined;
|
||||
_ = switch (-c.snd_ctl_open(&ctl, card_id.ptr, 0)) {
|
||||
0 => {},
|
||||
@enumToInt(std.os.E.NOENT) => break,
|
||||
else => return error.OpeningDevice,
|
||||
};
|
||||
defer _ = c.snd_ctl_close(ctl);
|
||||
|
||||
var dev_idx: c_int = -1;
|
||||
if (c.snd_ctl_pcm_next_device(ctl, &dev_idx) < 0)
|
||||
return error.SystemResources;
|
||||
|
||||
c.snd_pcm_info_set_device(pcm_info, @intCast(c_uint, dev_idx));
|
||||
c.snd_pcm_info_set_subdevice(pcm_info, 0);
|
||||
const name = std.mem.span(c.snd_pcm_info_get_name(pcm_info) orelse continue);
|
||||
|
||||
for (&[_]main.Device.Mode{ .playback, .capture }) |mode| {
|
||||
const snd_stream = modeToStream(mode);
|
||||
c.snd_pcm_info_set_stream(pcm_info, snd_stream);
|
||||
const err = c.snd_ctl_pcm_info(ctl, pcm_info);
|
||||
switch (@intToEnum(std.os.E, -err)) {
|
||||
.SUCCESS => {},
|
||||
.NOENT,
|
||||
.NXIO,
|
||||
.NODEV,
|
||||
=> break,
|
||||
else => return error.SystemResources,
|
||||
}
|
||||
|
||||
var buf: [9]u8 = undefined; // 'hw' + max(card|device) * 2 + ':' + \0
|
||||
const id = std.fmt.bufPrintZ(&buf, "hw:{d},{d}", .{ card_idx, dev_idx }) catch continue;
|
||||
|
||||
var pcm: ?*c.snd_pcm_t = null;
|
||||
if (c.snd_pcm_open(&pcm, id.ptr, snd_stream, 0) < 0)
|
||||
continue;
|
||||
defer _ = c.snd_pcm_close(pcm);
|
||||
|
||||
var params: ?*c.snd_pcm_hw_params_t = null;
|
||||
_ = c.snd_pcm_hw_params_malloc(¶ms);
|
||||
defer c.snd_pcm_hw_params_free(params);
|
||||
if (c.snd_pcm_hw_params_any(pcm, params) < 0)
|
||||
continue;
|
||||
|
||||
if (c.snd_pcm_hw_params_can_pause(params) == 0)
|
||||
continue;
|
||||
|
||||
const device = main.Device{
|
||||
.mode = mode,
|
||||
.channels = blk: {
|
||||
const chmap = c.snd_pcm_query_chmaps(pcm);
|
||||
if (chmap) |_| {
|
||||
defer c.snd_pcm_free_chmaps(chmap);
|
||||
|
||||
if (chmap[0] == null) continue;
|
||||
|
||||
var channels = try self.allocator.alloc(main.Channel, chmap.*.*.map.channels);
|
||||
for (channels) |*ch, i|
|
||||
ch.*.id = fromAlsaChannel(chmap[0][0].map.pos()[i]) catch return error.OpeningDevice;
|
||||
break :blk channels;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
},
|
||||
.formats = blk: {
|
||||
var fmt_mask: ?*c.snd_pcm_format_mask_t = null;
|
||||
_ = c.snd_pcm_format_mask_malloc(&fmt_mask);
|
||||
defer c.snd_pcm_format_mask_free(fmt_mask);
|
||||
c.snd_pcm_format_mask_none(fmt_mask);
|
||||
c.snd_pcm_format_mask_set(fmt_mask, c.SND_PCM_FORMAT_S8);
|
||||
c.snd_pcm_format_mask_set(fmt_mask, c.SND_PCM_FORMAT_U8);
|
||||
c.snd_pcm_format_mask_set(fmt_mask, c.SND_PCM_FORMAT_S16_LE);
|
||||
c.snd_pcm_format_mask_set(fmt_mask, c.SND_PCM_FORMAT_S16_BE);
|
||||
c.snd_pcm_format_mask_set(fmt_mask, c.SND_PCM_FORMAT_U16_LE);
|
||||
c.snd_pcm_format_mask_set(fmt_mask, c.SND_PCM_FORMAT_U16_BE);
|
||||
c.snd_pcm_format_mask_set(fmt_mask, c.SND_PCM_FORMAT_S24_3LE);
|
||||
c.snd_pcm_format_mask_set(fmt_mask, c.SND_PCM_FORMAT_S24_3BE);
|
||||
c.snd_pcm_format_mask_set(fmt_mask, c.SND_PCM_FORMAT_U24_3LE);
|
||||
c.snd_pcm_format_mask_set(fmt_mask, c.SND_PCM_FORMAT_U24_3BE);
|
||||
c.snd_pcm_format_mask_set(fmt_mask, c.SND_PCM_FORMAT_S24_LE);
|
||||
c.snd_pcm_format_mask_set(fmt_mask, c.SND_PCM_FORMAT_S24_BE);
|
||||
c.snd_pcm_format_mask_set(fmt_mask, c.SND_PCM_FORMAT_U24_LE);
|
||||
c.snd_pcm_format_mask_set(fmt_mask, c.SND_PCM_FORMAT_U24_BE);
|
||||
c.snd_pcm_format_mask_set(fmt_mask, c.SND_PCM_FORMAT_S32_LE);
|
||||
c.snd_pcm_format_mask_set(fmt_mask, c.SND_PCM_FORMAT_S32_BE);
|
||||
c.snd_pcm_format_mask_set(fmt_mask, c.SND_PCM_FORMAT_U32_LE);
|
||||
c.snd_pcm_format_mask_set(fmt_mask, c.SND_PCM_FORMAT_U32_BE);
|
||||
c.snd_pcm_format_mask_set(fmt_mask, c.SND_PCM_FORMAT_FLOAT_LE);
|
||||
c.snd_pcm_format_mask_set(fmt_mask, c.SND_PCM_FORMAT_FLOAT_BE);
|
||||
c.snd_pcm_format_mask_set(fmt_mask, c.SND_PCM_FORMAT_FLOAT64_LE);
|
||||
c.snd_pcm_format_mask_set(fmt_mask, c.SND_PCM_FORMAT_FLOAT64_BE);
|
||||
c.snd_pcm_hw_params_get_format_mask(params, fmt_mask);
|
||||
|
||||
var fmt_arr = std.ArrayList(main.Format).init(self.allocator);
|
||||
inline for (std.meta.tags(main.Format)) |format| {
|
||||
if (c.snd_pcm_format_mask_test(
|
||||
fmt_mask,
|
||||
toAlsaFormat(format) catch unreachable,
|
||||
) != 0) {
|
||||
try fmt_arr.append(format);
|
||||
}
|
||||
}
|
||||
|
||||
break :blk try fmt_arr.toOwnedSlice();
|
||||
},
|
||||
.sample_rate = blk: {
|
||||
var rate_min: c_uint = 0;
|
||||
var rate_max: c_uint = 0;
|
||||
if (c.snd_pcm_hw_params_get_rate_min(params, &rate_min, null) < 0)
|
||||
continue;
|
||||
if (c.snd_pcm_hw_params_get_rate_max(params, &rate_max, null) < 0)
|
||||
continue;
|
||||
break :blk .{
|
||||
.min = @intCast(u24, rate_min),
|
||||
.max = @intCast(u24, rate_max),
|
||||
};
|
||||
},
|
||||
.id = try self.allocator.dupeZ(u8, id),
|
||||
.name = try self.allocator.dupeZ(u8, name),
|
||||
};
|
||||
|
||||
try self.devices_info.list.append(self.allocator, device);
|
||||
|
||||
if (self.devices_info.default(mode) == null and dev_idx == 0) {
|
||||
self.devices_info.setDefault(mode, self.devices_info.list.items.len - 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (c.snd_card_next(&card_idx) < 0)
|
||||
return error.SystemResources;
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
const format = device.preferredFormat(options.format);
|
||||
const sample_rate = device.sample_rate.clamp(options.sample_rate);
|
||||
var pcm: ?*c.snd_pcm_t = null;
|
||||
var mixer: ?*c.snd_mixer_t = null;
|
||||
var selem: ?*c.snd_mixer_selem_id_t = null;
|
||||
var mixer_elm: ?*c.snd_mixer_elem_t = null;
|
||||
var period_size: c_ulong = 0;
|
||||
|
||||
if (c.snd_pcm_open(&pcm, device.id.ptr, modeToStream(device.mode), 0) < 0)
|
||||
return error.OpeningDevice;
|
||||
errdefer _ = c.snd_pcm_close(pcm);
|
||||
{
|
||||
var hw_params: ?*c.snd_pcm_hw_params_t = null;
|
||||
|
||||
if ((c.snd_pcm_set_params(
|
||||
pcm,
|
||||
toAlsaFormat(format) catch unreachable,
|
||||
c.SND_PCM_ACCESS_RW_INTERLEAVED,
|
||||
@intCast(c_uint, device.channels.len),
|
||||
sample_rate,
|
||||
1,
|
||||
main.default_latency,
|
||||
)) < 0)
|
||||
return error.OpeningDevice;
|
||||
errdefer _ = c.snd_pcm_hw_free(pcm);
|
||||
|
||||
if (c.snd_pcm_hw_params_malloc(&hw_params) < 0)
|
||||
return error.OpeningDevice;
|
||||
defer c.snd_pcm_hw_params_free(hw_params);
|
||||
|
||||
if (c.snd_pcm_hw_params_current(pcm, hw_params) < 0)
|
||||
return error.OpeningDevice;
|
||||
|
||||
if (c.snd_pcm_hw_params_get_period_size(hw_params, &period_size, null) < 0)
|
||||
return error.OpeningDevice;
|
||||
}
|
||||
|
||||
{
|
||||
var chmap: c.snd_pcm_chmap_t = .{ .channels = @intCast(c_uint, device.channels.len) };
|
||||
|
||||
for (device.channels) |ch, i|
|
||||
chmap.pos()[i] = toCHMAP(ch.id);
|
||||
|
||||
if (c.snd_pcm_set_chmap(pcm, &chmap) < 0)
|
||||
return error.IncompatibleDevice;
|
||||
}
|
||||
|
||||
{
|
||||
if (c.snd_mixer_open(&mixer, 0) < 0)
|
||||
return error.OutOfMemory;
|
||||
|
||||
const card_id = try self.allocator.dupeZ(u8, std.mem.sliceTo(device.id, ','));
|
||||
defer self.allocator.free(card_id);
|
||||
|
||||
if (c.snd_mixer_attach(mixer, card_id.ptr) < 0)
|
||||
return error.IncompatibleDevice;
|
||||
|
||||
if (c.snd_mixer_selem_register(mixer, null, null) < 0)
|
||||
return error.OpeningDevice;
|
||||
|
||||
if (c.snd_mixer_load(mixer) < 0)
|
||||
return error.OpeningDevice;
|
||||
|
||||
if (c.snd_mixer_selem_id_malloc(&selem) < 0)
|
||||
return error.OutOfMemory;
|
||||
errdefer c.snd_mixer_selem_id_free(selem);
|
||||
|
||||
c.snd_mixer_selem_id_set_index(selem, 0);
|
||||
c.snd_mixer_selem_id_set_name(selem, "Master");
|
||||
|
||||
mixer_elm = c.snd_mixer_find_selem(mixer, selem) orelse
|
||||
return error.IncompatibleDevice;
|
||||
}
|
||||
|
||||
return .{
|
||||
.alsa = .{
|
||||
.allocator = self.allocator,
|
||||
.thread = undefined,
|
||||
.mutex = .{},
|
||||
._channels = device.channels,
|
||||
._format = format,
|
||||
.sample_rate = sample_rate,
|
||||
.writeFn = writeFn,
|
||||
.aborted = .{ .value = false },
|
||||
.sample_buffer = try self.allocator.alloc(u8, period_size * format.frameSize(device.channels.len)),
|
||||
.period_size = period_size,
|
||||
.pcm = pcm.?,
|
||||
.mixer = mixer.?,
|
||||
.selem = selem.?,
|
||||
.mixer_elm = mixer_elm.?,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Player = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
thread: std.Thread,
|
||||
mutex: std.Thread.Mutex,
|
||||
_channels: []main.Channel,
|
||||
_format: main.Format,
|
||||
sample_rate: u24,
|
||||
writeFn: main.WriteFn,
|
||||
aborted: std.atomic.Atomic(bool),
|
||||
sample_buffer: []u8,
|
||||
period_size: c_ulong,
|
||||
pcm: *c.snd_pcm_t,
|
||||
mixer: *c.snd_mixer_t,
|
||||
selem: *c.snd_mixer_selem_id_t,
|
||||
mixer_elm: *c.snd_mixer_elem_t,
|
||||
|
||||
pub fn deinit(self: *Player) void {
|
||||
self.aborted.store(true, .Unordered);
|
||||
self.thread.join();
|
||||
|
||||
_ = c.snd_mixer_close(self.mixer);
|
||||
c.snd_mixer_selem_id_free(self.selem);
|
||||
_ = c.snd_pcm_close(self.pcm);
|
||||
_ = c.snd_pcm_hw_free(self.pcm);
|
||||
|
||||
self.allocator.free(self.sample_buffer);
|
||||
}
|
||||
|
||||
pub fn start(self: *Player) !void {
|
||||
self.thread = std.Thread.spawn(.{}, writeLoop, .{self}) catch |err| switch (err) {
|
||||
error.ThreadQuotaExceeded,
|
||||
error.SystemResources,
|
||||
error.LockedMemoryLimitExceeded,
|
||||
=> return error.SystemResources,
|
||||
error.OutOfMemory => return error.OutOfMemory,
|
||||
error.Unexpected => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
fn writeLoop(self: *Player) void {
|
||||
var parent = @fieldParentPtr(main.Player, "data", @ptrCast(*backends.BackendPlayer, self));
|
||||
|
||||
for (self.channels()) |*ch, i| {
|
||||
ch.*.ptr = self.sample_buffer.ptr + self.format().frameSize(i);
|
||||
}
|
||||
|
||||
while (!self.aborted.load(.Unordered)) {
|
||||
var frames_left = self.period_size;
|
||||
while (frames_left > 0) {
|
||||
self.writeFn(parent, frames_left);
|
||||
const n = c.snd_pcm_writei(self.pcm, self.sample_buffer.ptr, frames_left);
|
||||
if (n < 0) {
|
||||
if (c.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play(self: Player) !void {
|
||||
if (c.snd_pcm_state(self.pcm) == c.SND_PCM_STATE_PAUSED) {
|
||||
if (c.snd_pcm_pause(self.pcm, 0) < 0)
|
||||
return error.CannotPlay;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pause(self: Player) !void {
|
||||
if (c.snd_pcm_state(self.pcm) != c.SND_PCM_STATE_PAUSED) {
|
||||
if (c.snd_pcm_pause(self.pcm, 1) < 0)
|
||||
return error.CannotPause;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paused(self: Player) bool {
|
||||
return c.snd_pcm_state(self.pcm) == c.SND_PCM_STATE_PAUSED;
|
||||
}
|
||||
|
||||
pub fn setVolume(self: *Player, vol: f32) !void {
|
||||
self.mutex.lock();
|
||||
defer self.mutex.unlock();
|
||||
|
||||
var min_vol: c_long = 0;
|
||||
var max_vol: c_long = 0;
|
||||
if (c.snd_mixer_selem_get_playback_volume_range(self.mixer_elm, &min_vol, &max_vol) < 0)
|
||||
return error.CannotSetVolume;
|
||||
|
||||
const dist = @intToFloat(f32, max_vol - min_vol);
|
||||
if (c.snd_mixer_selem_set_playback_volume_all(
|
||||
self.mixer_elm,
|
||||
@floatToInt(c_long, dist * vol) + min_vol,
|
||||
) < 0)
|
||||
return error.CannotSetVolume;
|
||||
}
|
||||
|
||||
pub fn volume(self: *Player) !f32 {
|
||||
self.mutex.lock();
|
||||
defer self.mutex.unlock();
|
||||
|
||||
var vol: c_long = 0;
|
||||
var channel: c_int = 0;
|
||||
|
||||
while (channel < c.SND_MIXER_SCHN_LAST) : (channel += 1) {
|
||||
if (c.snd_mixer_selem_has_playback_channel(self.mixer_elm, channel) == 1) {
|
||||
if (c.snd_mixer_selem_get_playback_volume(self.mixer_elm, channel, &vol) == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (channel == c.SND_MIXER_SCHN_LAST)
|
||||
return error.CannotGetVolume;
|
||||
|
||||
var min_vol: c_long = 0;
|
||||
var max_vol: c_long = 0;
|
||||
if (c.snd_mixer_selem_get_playback_volume_range(self.mixer_elm, &min_vol, &max_vol) < 0)
|
||||
return error.CannotGetVolume;
|
||||
|
||||
return @intToFloat(f32, vol) / @intToFloat(f32, max_vol - min_vol);
|
||||
}
|
||||
|
||||
pub fn writeRaw(self: *Player, channel: main.Channel, frame: usize, sample: anytype) void {
|
||||
var ptr = channel.ptr + frame * self.format().frameSize(self.channels().len);
|
||||
std.mem.bytesAsValue(@TypeOf(sample), ptr[0..@sizeOf(@TypeOf(sample))]).* = sample;
|
||||
}
|
||||
|
||||
pub fn channels(self: Player) []main.Channel {
|
||||
return self._channels;
|
||||
}
|
||||
|
||||
pub fn format(self: Player) main.Format {
|
||||
return self._format;
|
||||
}
|
||||
|
||||
pub fn sampleRate(self: Player) u24 {
|
||||
return self.sample_rate;
|
||||
}
|
||||
};
|
||||
|
||||
fn freeDevice(allocator: std.mem.Allocator, device: main.Device) void {
|
||||
allocator.free(device.id);
|
||||
allocator.free(device.name);
|
||||
allocator.free(device.formats);
|
||||
allocator.free(device.channels);
|
||||
}
|
||||
|
||||
pub fn modeToStream(mode: main.Device.Mode) c_uint {
|
||||
return switch (mode) {
|
||||
.playback => c.SND_PCM_STREAM_PLAYBACK,
|
||||
.capture => c.SND_PCM_STREAM_CAPTURE,
|
||||
};
|
||||
}
|
||||
|
||||
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,
|
||||
.i32 => if (is_little) c.SND_PCM_FORMAT_S32_LE else c.SND_PCM_FORMAT_S32_BE,
|
||||
.f32 => if (is_little) c.SND_PCM_FORMAT_FLOAT_LE else c.SND_PCM_FORMAT_FLOAT_BE,
|
||||
.f64 => if (is_little) c.SND_PCM_FORMAT_FLOAT64_LE else c.SND_PCM_FORMAT_FLOAT64_BE,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn fromAlsaChannel(pos: c_uint) !main.Channel.Id {
|
||||
return switch (pos) {
|
||||
c.SND_CHMAP_UNKNOWN, c.SND_CHMAP_NA => return error.Invalid,
|
||||
c.SND_CHMAP_MONO, c.SND_CHMAP_FC => .front_center,
|
||||
c.SND_CHMAP_FL => .front_left,
|
||||
c.SND_CHMAP_FR => .front_right,
|
||||
c.SND_CHMAP_LFE => .lfe,
|
||||
c.SND_CHMAP_SL => .side_left,
|
||||
c.SND_CHMAP_SR => .side_right,
|
||||
c.SND_CHMAP_RC => .back_center,
|
||||
c.SND_CHMAP_FLC => .front_left_center,
|
||||
c.SND_CHMAP_FRC => .front_right_center,
|
||||
c.SND_CHMAP_TC => .top_center,
|
||||
c.SND_CHMAP_TFL => .top_front_left,
|
||||
c.SND_CHMAP_TFR => .top_front_right,
|
||||
c.SND_CHMAP_TFC => .top_front_center,
|
||||
c.SND_CHMAP_TRL => .top_back_left,
|
||||
c.SND_CHMAP_TRR => .top_back_right,
|
||||
c.SND_CHMAP_TRC => .top_back_center,
|
||||
|
||||
else => return error.Invalid,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toCHMAP(pos: main.Channel.Id) c_uint {
|
||||
return switch (pos) {
|
||||
.front_center => c.SND_CHMAP_FC,
|
||||
.front_left => c.SND_CHMAP_FL,
|
||||
.front_right => c.SND_CHMAP_FR,
|
||||
.lfe => c.SND_CHMAP_LFE,
|
||||
.side_left => c.SND_CHMAP_SL,
|
||||
.side_right => c.SND_CHMAP_SR,
|
||||
.back_center => c.SND_CHMAP_RC,
|
||||
.front_left_center => c.SND_CHMAP_FLC,
|
||||
.front_right_center => c.SND_CHMAP_FRC,
|
||||
.top_center => c.SND_CHMAP_TC,
|
||||
.top_front_left => c.SND_CHMAP_TFL,
|
||||
.top_front_right => c.SND_CHMAP_TFR,
|
||||
.top_front_center => c.SND_CHMAP_TFC,
|
||||
.top_back_left => c.SND_CHMAP_TRL,
|
||||
.top_back_right => c.SND_CHMAP_TRR,
|
||||
.top_back_center => c.SND_CHMAP_TRC,
|
||||
};
|
||||
}
|
||||
|
||||
test {
|
||||
std.testing.refAllDeclsRecursive(@This());
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue