sysaudio: libsoundio backend now functional
Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
052be9a684
commit
22f14ee1ed
1 changed files with 183 additions and 44 deletions
|
|
@ -7,6 +7,7 @@ const SoundIo = @import("soundio").SoundIo;
|
|||
const SoundIoDevice = @import("soundio").Device;
|
||||
const SoundIoInStream = @import("soundio").InStream;
|
||||
const SoundIoOutStream = @import("soundio").OutStream;
|
||||
|
||||
const SoundIoStream = union(Mode) {
|
||||
input: SoundIoInStream,
|
||||
output: SoundIoOutStream,
|
||||
|
|
@ -15,27 +16,125 @@ const SoundIoStream = union(Mode) {
|
|||
const Audio = @This();
|
||||
|
||||
pub const DataCallback = if (@import("builtin").zig_backend == .stage1)
|
||||
fn (device: Device, frame_count: u32) void
|
||||
fn (device: *Device, user_data: ?*anyopaque, buffer: []u8) void
|
||||
else
|
||||
*const fn (device: Device, frame_count: u32) void;
|
||||
*const fn (device: *Device, user_data: ?*anyopaque, buffer: []u8) void;
|
||||
|
||||
pub const Device = struct {
|
||||
descriptor: DeviceDescriptor,
|
||||
|
||||
// Internal fields.
|
||||
handle: SoundIoStream,
|
||||
data_callback: ?DataCallback = null,
|
||||
user_data: ?*anyopaque = null,
|
||||
planar_buffer: [512000]u8 = undefined,
|
||||
|
||||
pub fn setCallback(self: Device, callback: DataCallback, data: *anyopaque) void {
|
||||
self.data_callback = callback;
|
||||
self.user_data = data;
|
||||
}
|
||||
|
||||
pub fn deinit(self: Device) void {
|
||||
return switch (self.handle) {
|
||||
pub fn deinit(self: *Device, allocator: std.mem.Allocator) void {
|
||||
switch (self.handle) {
|
||||
.input => |d| d.deinit(),
|
||||
.output => |d| d.deinit(),
|
||||
}
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn setCallback(self: *Device, callback: DataCallback, data: *anyopaque) void {
|
||||
self.data_callback = callback;
|
||||
self.user_data = data;
|
||||
switch (self.handle) {
|
||||
.input => |_| @panic("input not supported yet"),
|
||||
.output => |d| {
|
||||
// TODO(sysaudio): support other formats
|
||||
d.setFormat(.float32LE);
|
||||
|
||||
d.setWriteCallback((struct {
|
||||
fn cCallback(
|
||||
c_outstream: ?[*]c.SoundIoOutStream,
|
||||
frame_count_min: c_int,
|
||||
frame_count_max: c_int,
|
||||
) callconv(.C) void {
|
||||
_ = frame_count_min;
|
||||
const outstream = SoundIoOutStream{ .handle = @ptrCast(*c.SoundIoOutStream, c_outstream) };
|
||||
const device = @ptrCast(*Device, @alignCast(@alignOf(Device), outstream.handle.userdata));
|
||||
|
||||
// TODO(sysaudio): provide callback with outstream.sampleRate()
|
||||
|
||||
// TODO(sysaudio): according to issue tracker and PR from mason (did we include it?)
|
||||
// there may be issues with frame_count_max being way too large on Windows. May need
|
||||
// to artificially limit it or use Mason's PR.
|
||||
|
||||
// The data callback gives us planar data, e.g. in AAAABBBB format for channels
|
||||
// A and B. WebAudio similarly requires data in planar format. libsoundio however
|
||||
// does not guarantee planar data format, it may be in interleaved format ABABABAB.
|
||||
// Invoke our data callback with a temporary buffer, this involves one copy later
|
||||
// but it's such a small amount of memory it is entirely negligible.
|
||||
const layout = outstream.layout();
|
||||
const total_frame_count = @intCast(usize, frame_count_max);
|
||||
const buffer_size: usize = @sizeOf(f32) * total_frame_count * @intCast(usize, layout.channelCount());
|
||||
const addr = @ptrToInt(&device.planar_buffer);
|
||||
const aligned_addr = std.mem.alignForward(addr, @alignOf(f32));
|
||||
const padding = aligned_addr - addr;
|
||||
const planar_buffer = device.planar_buffer[padding..buffer_size];
|
||||
device.data_callback.?(device, device.user_data.?, planar_buffer);
|
||||
|
||||
var frames_left = total_frame_count;
|
||||
while (frames_left > 0) {
|
||||
var frame_count: i32 = @intCast(i32, frames_left);
|
||||
|
||||
var areas: [*]c.SoundIoChannelArea = undefined;
|
||||
// TODO(sysaudio): improve error handling
|
||||
outstream.beginWrite(
|
||||
@ptrCast([*]?[*]c.SoundIoChannelArea, &areas),
|
||||
&frame_count,
|
||||
) catch |err| std.debug.panic("write failed: {s}", .{@errorName(err)});
|
||||
|
||||
if (frame_count == 0) break;
|
||||
|
||||
var channel: usize = 0;
|
||||
while (channel < @intCast(usize, layout.channelCount())) : (channel += 1) {
|
||||
const channel_ptr = areas[channel].ptr;
|
||||
var frame: c_int = 0;
|
||||
while (frame < frame_count) : (frame += 1) {
|
||||
const sample_start = (channel * total_frame_count * @sizeOf(f32)) + (@intCast(usize, frame) * @sizeOf(f32));
|
||||
const src = @ptrCast(*f32, @alignCast(@alignOf(f32), &planar_buffer[sample_start]));
|
||||
const dst = &channel_ptr[@intCast(usize, areas[channel].step * frame)];
|
||||
@ptrCast(*f32, @alignCast(@alignOf(f32), dst)).* = src.*;
|
||||
}
|
||||
}
|
||||
// TODO(sysaudio): improve error handling
|
||||
outstream.endWrite() catch |err| std.debug.panic("end write failed: {s}", .{@errorName(err)});
|
||||
frames_left -= @intCast(usize, frame_count);
|
||||
}
|
||||
}
|
||||
}).cCallback);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pause(device: *Device) Error!void {
|
||||
return (switch (device.handle) {
|
||||
.input => |d| d.pause(),
|
||||
.output => |d| d.pause(),
|
||||
}) catch |err| {
|
||||
return switch (err) {
|
||||
error.OutOfMemory => error.OutOfMemory,
|
||||
else => @panic(@errorName(err)),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
pub fn start(device: *Device) Error!void {
|
||||
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)),
|
||||
};
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const DeviceIterator = struct {
|
||||
ctx: Audio,
|
||||
mode: Mode,
|
||||
|
|
@ -63,6 +162,7 @@ pub const DeviceIterator = struct {
|
|||
}
|
||||
};
|
||||
|
||||
// TODO(sysaudio): standardize errors across backends
|
||||
pub const IteratorError = error{OutOfMemory};
|
||||
pub const Error = error{
|
||||
OutOfMemory,
|
||||
|
|
@ -74,6 +174,20 @@ pub const Error = error{
|
|||
UnsupportedOS,
|
||||
UnsupportedBackend,
|
||||
DeviceUnavailable,
|
||||
Invalid,
|
||||
OpeningDevice,
|
||||
BackendDisconnected,
|
||||
SystemResources,
|
||||
NoSuchClient,
|
||||
IncompatibleBackend,
|
||||
IncompatibleDevice,
|
||||
InitAudioBackend,
|
||||
NoSuchDevice,
|
||||
BackendUnavailable,
|
||||
Streaming,
|
||||
Interrupted,
|
||||
Underflow,
|
||||
EncodingString,
|
||||
};
|
||||
|
||||
handle: SoundIo,
|
||||
|
|
@ -102,9 +216,7 @@ pub fn waitEvents(self: Audio) void {
|
|||
self.handle.waitEvents();
|
||||
}
|
||||
|
||||
pub fn requestDevice(self: Audio, config: DeviceDescriptor) Error!Device {
|
||||
return Device{
|
||||
.handle = blk: {
|
||||
pub fn requestDevice(self: Audio, allocator: std.mem.Allocator, config: DeviceDescriptor) Error!*Device {
|
||||
var sio_device: SoundIoDevice = undefined;
|
||||
|
||||
if (config.id) |id| {
|
||||
|
|
@ -136,12 +248,39 @@ pub fn requestDevice(self: Audio, config: DeviceDescriptor) Error!Device {
|
|||
} orelse return error.DeviceUnavailable;
|
||||
}
|
||||
|
||||
break :blk switch (config.mode.?) {
|
||||
const handle = switch (config.mode.?) {
|
||||
.input => SoundIoStream{ .input = try sio_device.createInStream() },
|
||||
.output => SoundIoStream{ .output = try sio_device.createOutStream() },
|
||||
};
|
||||
},
|
||||
|
||||
switch (handle) {
|
||||
.input => |d| try d.open(),
|
||||
.output => |d| try d.open(),
|
||||
}
|
||||
|
||||
const device = try allocator.create(Device);
|
||||
switch (handle) {
|
||||
.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.?});
|
||||
|
||||
device.* = .{
|
||||
.descriptor = descriptor,
|
||||
.handle = handle,
|
||||
};
|
||||
return device;
|
||||
}
|
||||
|
||||
pub fn outputDeviceIterator(self: Audio) DeviceIterator {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue