{sysaudio,examples}: get sysaudio running on linux, separate audio configuration and descriptor (#518)
* Get sysaudio working on linux * Separate audio configuration and descriptor * Config/Descriptor -> Options/Properties - Rename sysaudio DeviceConfig and DeviceDescriptor to Device.Options and Device.Properties - example: Convert buffer before passing to renderWithType * make Device.start() idempotent
This commit is contained in:
parent
f807c85232
commit
0e71daf504
4 changed files with 193 additions and 98 deletions
|
|
@ -27,8 +27,8 @@ pub const Format = enum {
|
|||
F32,
|
||||
};
|
||||
|
||||
pub const DeviceDescriptor = struct {
|
||||
mode: ?Mode = null,
|
||||
pub const DeviceOptions = struct {
|
||||
mode: Mode = .output,
|
||||
format: ?Format = null,
|
||||
is_raw: ?bool = null,
|
||||
channels: ?u8 = null,
|
||||
|
|
@ -37,6 +37,28 @@ pub const DeviceDescriptor = struct {
|
|||
name: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
pub const DeviceProperties = struct {
|
||||
mode: Mode,
|
||||
format: Format,
|
||||
is_raw: bool,
|
||||
channels: u8,
|
||||
sample_rate: u32,
|
||||
id: [:0]const u8,
|
||||
name: []const u8,
|
||||
|
||||
pub fn intoConfig(properties: DeviceProperties) DeviceOptions {
|
||||
return .{
|
||||
.mode = properties.mode,
|
||||
.format = properties.format,
|
||||
.is_raw = properties.is_raw,
|
||||
.channels = properties.channels,
|
||||
.sample_rate = properties.sample_rate,
|
||||
.id = properties.id,
|
||||
.name = properties.name,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const Audio = @This();
|
||||
|
||||
backend: Backend,
|
||||
|
|
@ -55,7 +77,7 @@ pub fn waitEvents(self: Audio) void {
|
|||
self.backend.waitEvents();
|
||||
}
|
||||
|
||||
pub fn requestDevice(self: Audio, allocator: std.mem.Allocator, config: DeviceDescriptor) Error!*Device {
|
||||
pub fn requestDevice(self: Audio, allocator: std.mem.Allocator, config: Device.Options) Error!*Device {
|
||||
return self.backend.requestDevice(allocator, config);
|
||||
}
|
||||
|
||||
|
|
@ -88,9 +110,9 @@ test "connect to device from descriptor" {
|
|||
defer a.deinit();
|
||||
|
||||
var iter = a.outputDeviceIterator();
|
||||
var device_desc = (try iter.next()) orelse return error.NoDeviceFound;
|
||||
var device_conf = (try iter.next()) orelse return error.NoDeviceFound;
|
||||
|
||||
const d = try a.requestDevice(std.testing.allocator, device_desc);
|
||||
const d = try a.requestDevice(std.testing.allocator, device_conf);
|
||||
defer d.deinit(std.testing.allocator);
|
||||
}
|
||||
|
||||
|
|
@ -99,29 +121,14 @@ test "requestDevice behavior: null is_raw" {
|
|||
defer a.deinit();
|
||||
|
||||
var iter = a.outputDeviceIterator();
|
||||
var device_desc = (try iter.next()) orelse return error.NoDeviceFound;
|
||||
var device_conf = (try iter.next()) orelse return error.NoDeviceFound;
|
||||
|
||||
const bad_desc = DeviceDescriptor{
|
||||
const bad_conf = Device.Options{
|
||||
.is_raw = null,
|
||||
.mode = device_desc.mode,
|
||||
.id = device_desc.id,
|
||||
.mode = device_conf.mode,
|
||||
.id = device_conf.id,
|
||||
};
|
||||
try testing.expectError(error.InvalidParameter, a.requestDevice(std.testing.allocator, bad_desc));
|
||||
}
|
||||
|
||||
test "requestDevice behavior: null mode" {
|
||||
const a = try init();
|
||||
defer a.deinit();
|
||||
|
||||
var iter = a.outputDeviceIterator();
|
||||
var device_desc = (try iter.next()) orelse return error.NoDeviceFound;
|
||||
|
||||
const bad_desc = DeviceDescriptor{
|
||||
.is_raw = device_desc.is_raw,
|
||||
.mode = null,
|
||||
.id = device_desc.id,
|
||||
};
|
||||
try testing.expectError(error.InvalidParameter, a.requestDevice(std.testing.allocator, bad_desc));
|
||||
try testing.expectError(error.InvalidParameter, a.requestDevice(std.testing.allocator, bad_conf));
|
||||
}
|
||||
|
||||
test "requestDevice behavior: invalid id" {
|
||||
|
|
@ -130,12 +137,12 @@ test "requestDevice behavior: invalid id" {
|
|||
// defer a.deinit();
|
||||
|
||||
// var iter = a.outputDeviceIterator();
|
||||
// var device_desc = (try iter.next()) orelse return error.NoDeviceFound;
|
||||
// var device_conf = (try iter.next()) orelse return error.NoDeviceFound;
|
||||
|
||||
// const bad_desc = DeviceDescriptor{
|
||||
// .is_raw = device_desc.is_raw,
|
||||
// .mode = device_desc.mode,
|
||||
// const bad_conf = Device.Options{
|
||||
// .is_raw = device_conf.is_raw,
|
||||
// .mode = device_conf.mode,
|
||||
// .id = "wrong-id",
|
||||
// };
|
||||
// try testing.expectError(error.DeviceUnavailable, a.requestDevice(bad_desc));
|
||||
// try testing.expectError(error.DeviceUnavailable, a.requestDevice(bad_conf));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
const std = @import("std");
|
||||
const Mode = @import("main.zig").Mode;
|
||||
const DeviceDescriptor = @import("main.zig").DeviceDescriptor;
|
||||
const DeviceOptions = @import("main.zig").DeviceOptions;
|
||||
const DeviceProperties = @import("main.zig").DeviceProperties;
|
||||
const Format = @import("main.zig").Format;
|
||||
const c = @import("soundio").c;
|
||||
const Aim = @import("soundio").Aim;
|
||||
const SoundIo = @import("soundio").SoundIo;
|
||||
const SoundIoFormat = @import("soundio").Format;
|
||||
const SoundIoDevice = @import("soundio").Device;
|
||||
const SoundIoInStream = @import("soundio").InStream;
|
||||
const SoundIoOutStream = @import("soundio").OutStream;
|
||||
|
|
@ -21,13 +24,17 @@ else
|
|||
*const fn (device: *Device, user_data: ?*anyopaque, buffer: []u8) void;
|
||||
|
||||
pub const Device = struct {
|
||||
descriptor: DeviceDescriptor,
|
||||
properties: Properties,
|
||||
|
||||
// Internal fields.
|
||||
handle: SoundIoStream,
|
||||
data_callback: ?DataCallback = null,
|
||||
user_data: ?*anyopaque = null,
|
||||
planar_buffer: [512000]u8 = undefined,
|
||||
started: bool = false,
|
||||
|
||||
pub const Options = DeviceOptions;
|
||||
pub const Properties = DeviceProperties;
|
||||
|
||||
pub fn deinit(self: *Device, allocator: std.mem.Allocator) void {
|
||||
switch (self.handle) {
|
||||
|
|
@ -111,6 +118,7 @@ pub const Device = struct {
|
|||
}
|
||||
|
||||
pub fn pause(device: *Device) Error!void {
|
||||
if (!device.started) return;
|
||||
return (switch (device.handle) {
|
||||
.input => |d| d.pause(true),
|
||||
.output => |d| d.pause(true),
|
||||
|
|
@ -124,15 +132,28 @@ pub const Device = struct {
|
|||
|
||||
pub fn start(device: *Device) Error!void {
|
||||
// TODO(sysaudio): after pause, may need to call d.pause(false) instead of d.start()?
|
||||
return (switch (device.handle) {
|
||||
.input => |d| d.start(),
|
||||
.output => |d| d.start(),
|
||||
}) catch |err| {
|
||||
return switch (err) {
|
||||
error.OutOfMemory => error.OutOfMemory,
|
||||
else => @panic(@errorName(err)),
|
||||
if (!device.started) {
|
||||
device.started = true;
|
||||
return (switch (device.handle) {
|
||||
.input => |d| d.start(),
|
||||
.output => |d| d.start(),
|
||||
}) catch |err| {
|
||||
return switch (err) {
|
||||
error.OutOfMemory => error.OutOfMemory,
|
||||
else => @panic(@errorName(err)),
|
||||
};
|
||||
};
|
||||
};
|
||||
} else {
|
||||
return (switch (device.handle) {
|
||||
.input => |d| d.pause(false),
|
||||
.output => |d| d.pause(false),
|
||||
}) catch |err| {
|
||||
return switch (err) {
|
||||
error.OutOfMemory => error.OutOfMemory,
|
||||
else => @panic(@errorName(err)),
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -142,14 +163,14 @@ pub const DeviceIterator = struct {
|
|||
device_len: u16,
|
||||
index: u16,
|
||||
|
||||
pub fn next(self: *DeviceIterator) IteratorError!?DeviceDescriptor {
|
||||
pub fn next(self: *DeviceIterator) IteratorError!?DeviceOptions {
|
||||
if (self.index < self.device_len) {
|
||||
const device_desc = switch (self.mode) {
|
||||
.input => self.ctx.handle.getInputDevice(self.index) orelse return null,
|
||||
.output => self.ctx.handle.getOutputDevice(self.index) orelse return null,
|
||||
};
|
||||
self.index += 1;
|
||||
return DeviceDescriptor{
|
||||
return DeviceOptions{
|
||||
.mode = switch (@intToEnum(Aim, device_desc.handle.aim)) {
|
||||
.input => .input,
|
||||
.output => .output,
|
||||
|
|
@ -217,18 +238,18 @@ pub fn waitEvents(self: Audio) void {
|
|||
self.handle.waitEvents();
|
||||
}
|
||||
|
||||
pub fn requestDevice(self: Audio, allocator: std.mem.Allocator, config: DeviceDescriptor) Error!*Device {
|
||||
pub fn requestDevice(self: Audio, allocator: std.mem.Allocator, options: DeviceOptions) Error!*Device {
|
||||
var sio_device: SoundIoDevice = undefined;
|
||||
|
||||
if (config.id) |id| {
|
||||
if (config.mode == null or config.is_raw == null)
|
||||
if (options.id) |id| {
|
||||
if (options.is_raw == null)
|
||||
return error.InvalidParameter;
|
||||
|
||||
sio_device = switch (config.mode.?) {
|
||||
.input => self.handle.getInputDeviceFromID(id, config.is_raw.?),
|
||||
.output => self.handle.getOutputDeviceFromID(id, config.is_raw.?),
|
||||
sio_device = switch (options.mode) {
|
||||
.input => self.handle.getInputDeviceFromID(id, options.is_raw.?),
|
||||
.output => self.handle.getOutputDeviceFromID(id, options.is_raw.?),
|
||||
} orelse {
|
||||
return if (switch (config.mode.?) {
|
||||
return if (switch (options.mode) {
|
||||
.input => self.handle.inputDeviceCount().?,
|
||||
.output => self.handle.outputDeviceCount().?,
|
||||
} == 0)
|
||||
|
|
@ -237,19 +258,17 @@ pub fn requestDevice(self: Audio, allocator: std.mem.Allocator, config: DeviceDe
|
|||
error.DeviceUnavailable;
|
||||
};
|
||||
} else {
|
||||
if (config.mode == null) return error.InvalidParameter;
|
||||
|
||||
const id = switch (config.mode.?) {
|
||||
const id = switch (options.mode) {
|
||||
.input => self.handle.defaultInputDeviceIndex(),
|
||||
.output => self.handle.defaultOutputDeviceIndex(),
|
||||
} orelse return error.NoDeviceFound;
|
||||
sio_device = switch (config.mode.?) {
|
||||
sio_device = switch (options.mode) {
|
||||
.input => self.handle.getInputDevice(id),
|
||||
.output => self.handle.getOutputDevice(id),
|
||||
} orelse return error.DeviceUnavailable;
|
||||
}
|
||||
|
||||
const handle = switch (config.mode.?) {
|
||||
const handle = switch (options.mode) {
|
||||
.input => SoundIoStream{ .input = try sio_device.createInStream() },
|
||||
.output => SoundIoStream{ .output = try sio_device.createOutStream() },
|
||||
};
|
||||
|
|
@ -264,21 +283,52 @@ pub fn requestDevice(self: Audio, allocator: std.mem.Allocator, config: DeviceDe
|
|||
.input => |d| d.handle.userdata = device,
|
||||
.output => |d| d.handle.userdata = device,
|
||||
}
|
||||
var descriptor = config;
|
||||
descriptor.mode = descriptor.mode orelse .output;
|
||||
descriptor.channels = @intCast(u8, switch (handle) {
|
||||
.input => |d| d.layout().channelCount(),
|
||||
.output => |d| d.layout().channelCount(),
|
||||
});
|
||||
descriptor.sample_rate = @intCast(u32, switch (handle) {
|
||||
.input => |d| d.sampleRate(),
|
||||
.output => |d| d.sampleRate(),
|
||||
});
|
||||
std.log.info("channels {}", .{descriptor.channels.?});
|
||||
std.log.info("sample_rate {}\n", .{descriptor.sample_rate.?});
|
||||
|
||||
// TODO(sysaudio): handle big endian architectures
|
||||
const format: Format = switch (handle) {
|
||||
.input => |d| switch (@intToEnum(SoundIoFormat, d.handle.format)) {
|
||||
.U8 => .U8,
|
||||
.S16LE => .S16,
|
||||
.S24LE => .S24,
|
||||
.S32LE => .S32,
|
||||
.float32LE => .F32,
|
||||
else => return error.InvalidParameter,
|
||||
},
|
||||
.output => |d| switch (@intToEnum(SoundIoFormat, d.handle.format)) {
|
||||
.U8 => .U8,
|
||||
.S16LE => .S16,
|
||||
.S24LE => .S24,
|
||||
.S32LE => .S32,
|
||||
.float32LE => .F32,
|
||||
else => return error.InvalidParameter,
|
||||
},
|
||||
};
|
||||
|
||||
// TODO(sysaudio): Get the device name. Calling span or sliceTo on the name is causing segfaults on NixOS
|
||||
// const name_ptr = switch(handle) {
|
||||
// .input => |d| d.handle.name,
|
||||
// .output => |d| d.handle.name,
|
||||
// };
|
||||
// const name = std.mem.sliceTo(name_ptr, 0);
|
||||
|
||||
var properties = DeviceProperties{
|
||||
.is_raw = options.is_raw orelse false,
|
||||
.format = format,
|
||||
.mode = options.mode,
|
||||
.id = std.mem.span(sio_device.handle.id),
|
||||
.name = "",
|
||||
.channels = @intCast(u8, switch (handle) {
|
||||
.input => |d| d.layout().channelCount(),
|
||||
.output => |d| d.layout().channelCount(),
|
||||
}),
|
||||
.sample_rate = @intCast(u32, switch (handle) {
|
||||
.input => |d| d.sampleRate(),
|
||||
.output => |d| d.sampleRate(),
|
||||
}),
|
||||
};
|
||||
|
||||
device.* = .{
|
||||
.descriptor = descriptor,
|
||||
.properties = properties,
|
||||
.handle = handle,
|
||||
};
|
||||
return device;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
const std = @import("std");
|
||||
const Mode = @import("main.zig").Mode;
|
||||
const DeviceDescriptor = @import("main.zig").DeviceDescriptor;
|
||||
const DeviceOptions = @import("main.zig").DeviceOptions;
|
||||
const DeviceProperties = @import("main.zig").DeviceProperties;
|
||||
const js = @import("sysjs");
|
||||
|
||||
const Audio = @This();
|
||||
|
|
@ -11,11 +12,14 @@ else
|
|||
*const fn (device: *Device, user_data: ?*anyopaque, buffer: []u8) void;
|
||||
|
||||
pub const Device = struct {
|
||||
descriptor: DeviceDescriptor,
|
||||
properties: DeviceProperties,
|
||||
|
||||
// Internal fields.
|
||||
context: js.Object,
|
||||
|
||||
pub const Options = DeviceOptions;
|
||||
pub const Properties = DeviceProperties;
|
||||
|
||||
pub fn deinit(device: *Device, allocator: std.mem.Allocator) void {
|
||||
device.context.deinit();
|
||||
allocator.destroy(device);
|
||||
|
|
@ -41,7 +45,7 @@ pub const DeviceIterator = struct {
|
|||
ctx: *Audio,
|
||||
mode: Mode,
|
||||
|
||||
pub fn next(_: DeviceIterator) IteratorError!?DeviceDescriptor {
|
||||
pub fn next(_: DeviceIterator) IteratorError!?DeviceProperties {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
|
@ -67,18 +71,18 @@ pub fn deinit(audio: Audio) void {
|
|||
audio.context_constructor.deinit();
|
||||
}
|
||||
|
||||
// TODO)sysaudio): implement waitEvents for WebAudio, will a WASM process terminate without this?
|
||||
// TODO(sysaudio): implement waitEvents for WebAudio, will a WASM process terminate without this?
|
||||
pub fn waitEvents(_: Audio) void {}
|
||||
|
||||
const default_channel_count = 2;
|
||||
const default_sample_rate = 48000;
|
||||
const default_buffer_size_per_channel = 1024; // 21.33ms
|
||||
|
||||
pub fn requestDevice(audio: Audio, allocator: std.mem.Allocator, config: DeviceDescriptor) Error!*Device {
|
||||
// NOTE: WebAudio only supports F32 audio format, so config.format is unused
|
||||
const mode = config.mode orelse .output;
|
||||
const channels = config.channels orelse default_channel_count;
|
||||
const sample_rate = config.sample_rate orelse default_sample_rate;
|
||||
pub fn requestDevice(audio: Audio, allocator: std.mem.Allocator, options: DeviceOptions) Error!*Device {
|
||||
// NOTE: WebAudio only supports F32 audio format, so options.format is unused
|
||||
const mode = options.mode;
|
||||
const channels = options.channels orelse default_channel_count;
|
||||
const sample_rate = options.sample_rate orelse default_sample_rate;
|
||||
|
||||
const context_options = js.createMap();
|
||||
defer context_options.deinit();
|
||||
|
|
@ -113,15 +117,16 @@ pub fn requestDevice(audio: Audio, allocator: std.mem.Allocator, config: DeviceD
|
|||
_ = node.call("connect", &.{destination.toValue()});
|
||||
}
|
||||
|
||||
// TODO(sysaudio): introduce a descriptor type that has non-optional fields.
|
||||
var descriptor = config;
|
||||
descriptor.mode = descriptor.mode orelse .output;
|
||||
descriptor.channels = descriptor.channels orelse default_channel_count;
|
||||
descriptor.sample_rate = descriptor.sample_rate orelse default_sample_rate;
|
||||
var properties = DeviceProperties {
|
||||
.format = .F32,
|
||||
.mode = options.mode orelse .output,
|
||||
.channels = options.channels orelse default_channel_count,
|
||||
.sample_rate = options.sample_rate orelse default_sample_rate,
|
||||
};
|
||||
|
||||
const device = try allocator.create(Device);
|
||||
device.* = .{
|
||||
.descriptor = descriptor,
|
||||
.properties = properties,
|
||||
.context = context,
|
||||
};
|
||||
return device;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue