Simplify channel logic for coreaudio

This commit is contained in:
Haze Booth 2023-06-02 16:17:46 -04:00 committed by Stephen Gutekanst
parent 81bcce0c48
commit 28c119888b
3 changed files with 51 additions and 114 deletions

View file

@ -4,23 +4,31 @@ const sysaudio = @import("sysaudio");
var player: sysaudio.Player = undefined;
pub fn main() !void {
var timer = try std.time.Timer.start();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var ctx = try sysaudio.Context.init(null, allocator, .{ .deviceChangeFn = deviceChange });
std.log.info("Took {} to initialize the context...", .{std.fmt.fmtDuration(timer.lap())});
defer ctx.deinit();
try ctx.refresh();
std.log.info("Took {} to refresh the context...", .{std.fmt.fmtDuration(timer.lap())});
const device = ctx.defaultDevice(.playback) orelse return error.NoDevice;
std.log.info("Took {} to get the default playback device...", .{std.fmt.fmtDuration(timer.lap())});
player = try ctx.createPlayer(device, writeCallback, .{});
std.log.info("Took {} to create a player...", .{std.fmt.fmtDuration(timer.lap())});
defer player.deinit();
try player.start();
std.log.info("Took {} to start the player...", .{std.fmt.fmtDuration(timer.lap())});
try player.setVolume(0.85);
std.log.info("Took {} to set the volume...", .{std.fmt.fmtDuration(timer.lap())});
var buf: [16]u8 = undefined;
std.log.info("player created & entering i/o loop...", .{});
while (true) {
std.debug.print("( paused = {}, volume = {d} )\n> ", .{ player.paused(), try player.volume() });
const line = (try std.io.getStdIn().reader().readUntilDelimiterOrEof(&buf, '\n')) orelse break;

View file

@ -57,7 +57,7 @@ pub const Context = struct {
0,
null,
&io_size,
) != 0) {
) != c.noErr) {
return error.OpeningDevice;
}
@ -73,7 +73,7 @@ pub const Context = struct {
null,
&io_size,
@ptrCast(*anyopaque, devs),
) != 0) {
) != c.noErr) {
return error.OpeningDevice;
}
@ -85,7 +85,7 @@ pub const Context = struct {
c.kAudioHardwarePropertyDefaultInputDevice,
&io_size,
&default_input_id,
) != 0) {
) != c.noErr) {
return error.OpeningDevice;
}
@ -94,7 +94,7 @@ pub const Context = struct {
c.kAudioHardwarePropertyDefaultOutputDevice,
&io_size,
&default_output_id,
) != 0) {
) != c.noErr) {
return error.OpeningDevice;
}
@ -117,7 +117,7 @@ pub const Context = struct {
0,
null,
&io_size,
) != 0) {
) != c.noErr) {
continue;
}
@ -129,7 +129,7 @@ pub const Context = struct {
null,
&io_size,
@ptrCast(*anyopaque, buf_list),
) != 0) {
) != c.noErr) {
return error.OpeningDevice;
}
@ -138,28 +138,29 @@ pub const Context = struct {
}
}
prop_address.mSelector = if (is_darling) c.kAudioDevicePropertyPreferredChannelsForStereo else c.kAudioDevicePropertyPreferredChannelLayout;
prop_address.mScope = if (mode == .playback) c.kAudioObjectPropertyScopeOutput else c.kAudioObjectPropertyScopeInput;
if (c.AudioObjectGetPropertyDataSize(id, &prop_address, 0, null, &io_size) != 0) {
return error.OpeningDevice;
}
var channel_layout: c.AudioChannelLayout = undefined;
if (c.AudioObjectGetPropertyData(
id,
&prop_address,
0,
null,
&io_size,
&channel_layout,
) != 0) {
return error.OpeningDevice;
}
const channels = self.fromCoreAudioChannelLayout(channel_layout) catch |err| switch (err) {
error.IncompatibleDevice => continue,
error.OutOfMemory => return error.OutOfMemory,
// for now, only set `channels` to the output channels
const audio_buffer_list_property_address = c.AudioObjectPropertyAddress{
.mSelector = c.kAudioDevicePropertyStreamConfiguration,
.mScope = c.kAudioDevicePropertyScopeOutput,
.mElement = c.kAudioObjectPropertyElementMain,
};
var output_audio_buffer_list: c.AudioBufferList = undefined;
var audio_buffer_list_size: c_uint = undefined;
if (c.AudioObjectGetPropertyDataSize(id, &audio_buffer_list_property_address, 0, null, &audio_buffer_list_size) != c.noErr) {
return error.OpeningDevice;
}
if (c.AudioObjectGetPropertyData(id, &prop_address, 0, null, &audio_buffer_list_size, @ptrCast(*anyopaque, &output_audio_buffer_list)) != c.noErr) {
return error.OpeningDevice;
}
var output_channel_count: usize = 0;
for (0..output_audio_buffer_list.mNumberBuffers) |mBufferIndex| {
output_channel_count += output_audio_buffer_list.mBuffers[mBufferIndex].mNumberChannels;
}
var channels = try self.allocator.alloc(main.Channel, output_channel_count);
prop_address.mSelector = c.kAudioDevicePropertyNominalSampleRate;
io_size = @sizeOf(f64);
@ -171,7 +172,7 @@ pub const Context = struct {
null,
&io_size,
&sample_rate,
) != 0) {
) != c.noErr) {
return error.OpeningDevice;
}
@ -183,7 +184,7 @@ pub const Context = struct {
c.kAudioDevicePropertyDeviceName,
&io_size,
null,
) != 0) {
) != c.noErr) {
return error.OpeningDevice;
}
@ -196,7 +197,7 @@ pub const Context = struct {
c.kAudioDevicePropertyDeviceName,
&io_size,
name.ptr,
) != 0) {
) != c.noErr) {
return error.OpeningDevice;
}
const id_str = try std.fmt.allocPrintZ(self.allocator, "{d}", .{id});
@ -218,86 +219,13 @@ pub const Context = struct {
if (id == default_output_id) {
self.devices_info.default_output = self.devices_info.list.items.len - 1;
}
if (id == default_input_id) {
self.devices_info.default_input = self.devices_info.list.items.len - 1;
}
}
}
fn fromCoreAudioChannelLayout(self: Context, chan_layout: c.AudioChannelLayout) ![]main.Channel {
var channels: []main.Channel = undefined;
switch (chan_layout.mChannelLayoutTag) {
c.kAudioChannelLayoutTag_UseChannelDescriptions => {
const num_channels = if (is_darling) 1 else chan_layout.mNumberChannelDescriptions;
channels = try self.allocator.alloc(main.Channel, num_channels);
for (channels) |*ch| {
ch.id = .front_center;
} // TODO
},
c.kAudioChannelLayoutTag_Mono => {
channels = try self.allocator.alloc(main.Channel, 1);
channels[0].id = .front_center;
},
c.kAudioChannelLayoutTag_Stereo,
c.kAudioChannelLayoutTag_StereoHeadphones,
c.kAudioChannelLayoutTag_MatrixStereo,
c.kAudioChannelLayoutTag_Binaural,
=> {
channels = try self.allocator.alloc(main.Channel, 2);
channels[0].id = .front_left;
channels[1].id = .front_right;
},
c.kAudioChannelLayoutTag_Quadraphonic => {
channels = try self.allocator.alloc(main.Channel, 4);
channels[0].id = .front_left;
channels[1].id = .front_right;
channels[2].id = .back_left;
channels[3].id = .back_right;
},
c.kAudioChannelLayoutTag_Pentagonal => {
channels = try self.allocator.alloc(main.Channel, 5);
channels[0].id = .front_center;
channels[1].id = .side_left;
channels[2].id = .side_right;
channels[3].id = .back_left;
channels[4].id = .back_right;
},
c.kAudioChannelLayoutTag_Hexagonal => {
channels = try self.allocator.alloc(main.Channel, 6);
channels[0].id = .front_center;
channels[1].id = .side_left;
channels[2].id = .side_right;
channels[4].id = .back_center;
channels[5].id = .back_left;
channels[6].id = .back_right;
},
c.kAudioChannelLayoutTag_Octagonal => {
channels = try self.allocator.alloc(main.Channel, 8);
channels[0].id = .front_center;
channels[1].id = .back_center;
channels[2].id = .front_left;
channels[3].id = .front_right;
channels[4].id = .side_left;
channels[5].id = .side_right;
channels[6].id = .back_left;
channels[7].id = .back_right;
},
c.kAudioChannelLayoutTag_Cube => {
channels = try self.allocator.alloc(main.Channel, 8);
channels[0].id = .front_left;
channels[1].id = .front_right;
channels[2].id = .back_left;
channels[3].id = .back_right;
channels[4].id = .top_front_left;
channels[5].id = .top_front_right;
channels[6].id = .top_back_left;
channels[7].id = .top_back_right;
},
else => return error.IncompatibleDevice,
}
return channels;
}
pub fn devices(self: Context) []const main.Device {
return self.devices_info.list.items;
}
@ -320,9 +248,9 @@ pub const Context = struct {
if (component == null) return error.OpeningDevice;
var audio_unit: c.AudioComponentInstance = undefined;
if (c.AudioComponentInstanceNew(component, &audio_unit) != 0) return error.OpeningDevice;
if (c.AudioComponentInstanceNew(component, &audio_unit) != c.noErr) return error.OpeningDevice;
if (c.AudioUnitInitialize(audio_unit) != 0) return error.OpeningDevice;
if (c.AudioUnitInitialize(audio_unit) != c.noErr) return error.OpeningDevice;
errdefer _ = c.AudioUnitUninitialize(audio_unit);
const device_id = std.fmt.parseInt(c.AudioDeviceID, device.id, 10) catch unreachable;
@ -333,7 +261,7 @@ pub const Context = struct {
0,
&device_id,
@sizeOf(c.AudioDeviceID),
) != 0) {
) != c.noErr) {
return error.OpeningDevice;
}
@ -345,7 +273,7 @@ pub const Context = struct {
0,
&stream_desc,
@sizeOf(c.AudioStreamBasicDescription),
) != 0) {
) != c.noErr) {
return error.OpeningDevice;
}
@ -360,7 +288,7 @@ pub const Context = struct {
0,
&render_callback,
@sizeOf(c.AURenderCallbackStruct),
) != 0) {
) != c.noErr) {
return error.OpeningDevice;
}
@ -409,7 +337,7 @@ pub const Player = struct {
const self = @ptrCast(*Player, @alignCast(@alignOf(*Player), self_opaque.?));
for (self.channels, 0..) |*ch, i| {
ch.*.ptr = @ptrCast([*]u8, buf.*.mBuffers[0].mData.?) + self.format.frameSize(i);
ch.ptr = @ptrCast([*]u8, buf.*.mBuffers[0].mData.?) + self.format.frameSize(i);
}
const frames = buf.*.mBuffers[0].mDataByteSize / self.format.frameSize(self.channels.len);
self.writeFn(self.user_data, frames);
@ -429,14 +357,14 @@ pub const Player = struct {
}
pub fn play(self: *Player) !void {
if (c.AudioOutputUnitStart(self.audio_unit) != 0) {
if (c.AudioOutputUnitStart(self.audio_unit) != c.noErr) {
return error.CannotPlay;
}
self.is_paused = false;
}
pub fn pause(self: *Player) !void {
if (c.AudioOutputUnitStop(self.audio_unit) != 0) {
if (c.AudioOutputUnitStop(self.audio_unit) != c.noErr) {
return error.CannotPause;
}
self.is_paused = true;
@ -454,7 +382,7 @@ pub const Player = struct {
0,
vol,
0,
) != 0) {
) != c.noErr) {
if (is_darling) return;
return error.CannotSetVolume;
}
@ -468,7 +396,7 @@ pub const Player = struct {
c.kAudioUnitScope_Global,
0,
&vol,
) != 0) {
) != c.noErr) {
if (is_darling) return 1;
return error.CannotGetVolume;
}

View file

@ -312,6 +312,7 @@ pub const Device = struct {
id: [:0]const u8,
name: [:0]const u8,
mode: Mode,
// NOTE(haze): we should elaborate on the `channels` field and seperate them into input and output channels
channels: []Channel,
formats: []const Format,
sample_rate: util.Range(u24),