Skip to content

Commit b279167

Browse files
committed
remove mach-opus
Signed-off-by: Emi <emi@hexops.com>
1 parent 097dbc4 commit b279167

4 files changed

Lines changed: 278 additions & 8 deletions

File tree

build.zig

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,14 @@ pub fn build(b: *std.Build) !void {
131131
.optimize = optimize,
132132
.enable_freetype = true,
133133
})) |dep| module.linkLibrary(dep.artifact("harfbuzz"));
134-
if (b.lazyDependency("mach_opus", .{
134+
if (b.lazyDependency("opusfile", .{
135135
.target = target,
136136
.optimize = .ReleaseFast,
137-
})) |dep| {
138-
module.addImport("mach-opus", dep.module("mach-opus"));
139-
}
137+
})) |dep| module.linkLibrary(dep.artifact("opusfile"));
138+
if (b.lazyDependency("opusenc", .{
139+
.target = target,
140+
.optimize = .ReleaseFast,
141+
})) |dep| module.linkLibrary(dep.artifact("opusenc"));
140142
}
141143

142144
if (want_examples) {

build.zig.zon

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,14 @@
6868
.hash = "12201b874ac217853e59f55477b96cb7a66d43a5930ed9b4f0cfc7113efc8f5a6449",
6969
.lazy = true,
7070
},
71-
.mach_opus = .{
72-
.url = "https://pkg.machengine.org/mach-opus/32712fd091636037959720ee00036a060816a4f0.tar.gz",
73-
.hash = "12202e4cc38985a37bcf4804f29ec2fcba162410eee777d96ecdbb2c4260b14b7e68",
71+
.opusfile = .{
72+
.url = "https://pkg.machengine.org/opusfile/3eb6f231cb7bfed63d68e5b6bfdd5b08adb64223.tar.gz",
73+
.hash = "12202b922b88245c86d7a28ce31f43c62b994aebec5dbaec0ec8d703093e07bdc334",
74+
.lazy = true,
75+
},
76+
.opusenc = .{
77+
.url = "https://pkg.machengine.org/opusenc/456cbba13168cc5b999e19256bc78e977ce18fc8.tar.gz",
78+
.hash = "1220a330daee6fe10a02a6b981903877b2fab74afb64068a66f1f8a8135096a8e08a",
7479
.lazy = true,
7580
},
7681
.mach_example_assets = .{

src/Audio.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const mach = @import("main.zig");
44
const sysaudio = mach.sysaudio;
55
const testing = mach.testing;
66

7-
pub const Opus = @import("mach-opus");
7+
pub const Opus = @import("Opus.zig");
88

99
const Audio = @This();
1010

src/Opus.zig

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
const std = @import("std");
2+
const c = @cImport({
3+
@cInclude("opusfile.h");
4+
@cInclude("opusenc.h");
5+
});
6+
7+
const Opus = @This();
8+
9+
channels: u8,
10+
sample_rate: u24,
11+
samples: []align(alignment) f32,
12+
13+
/// The length of a @Vector(len, f32) used for SIMD audio buffers.
14+
pub const simd_vector_length = std.simd.suggestVectorLength(f32) orelse 1;
15+
16+
pub const alignment = simd_vector_length * @sizeOf(f32);
17+
18+
pub const DecodeError = error{
19+
OutOfMemory,
20+
InvalidData,
21+
Internal,
22+
Reading,
23+
Seeking,
24+
Unknown,
25+
};
26+
27+
pub fn decodeStream(
28+
allocator: std.mem.Allocator,
29+
stream: std.io.StreamSource,
30+
) (DecodeError || std.io.StreamSource.ReadError)!Opus {
31+
var decoder = Decoder{ .allocator = allocator, .stream = stream };
32+
var err: c_int = 0;
33+
const opus_file = c.op_open_callbacks(
34+
&decoder,
35+
&c.OpusFileCallbacks{
36+
.read = Decoder.readCallback,
37+
.seek = Decoder.seekCallback,
38+
.tell = Decoder.tellCallback,
39+
.close = Decoder.closeCallback,
40+
},
41+
null,
42+
0,
43+
&err,
44+
);
45+
switch (err) {
46+
0 => {},
47+
// An underlying read operation failed. This may signal a truncation attack from an <https:> source.
48+
c.OP_EREAD => return error.Reading,
49+
// An internal memory allocation failed.
50+
c.OP_EFAULT => return error.OutOfMemory,
51+
// An unseekable stream encountered a new link that used a feature that is not implemented, such as an unsupported channel family.
52+
c.OP_EIMPL => return error.Internal,
53+
// The stream was only partially open.
54+
c.OP_EINVAL => return error.InvalidData,
55+
// An unseekable stream encountered a new link that did not have any logical Opus streams in it.
56+
c.OP_ENOTFORMAT => return error.InvalidData,
57+
// An unseekable stream encountered a new link with a required header packet that was not properly formatted, contained illegal values, or was missing altogether.
58+
c.OP_EBADHEADER => return error.InvalidData,
59+
// An unseekable stream encountered a new link with an ID header that contained an unrecognized version number.
60+
c.OP_EVERSION => return error.InvalidData,
61+
// We failed to find data we had seen before.
62+
c.OP_EBADLINK => return error.Seeking,
63+
// An unseekable stream encountered a new link with a starting timestamp that failed basic validity checks.
64+
c.OP_EBADTIMESTAMP => return error.InvalidData,
65+
else => return error.Unknown,
66+
}
67+
defer c.op_free(opus_file);
68+
69+
const header = c.op_head(opus_file, 0);
70+
const channels: u8 = @intCast(header.*.channel_count);
71+
const sample_rate: u24 = @intCast(header.*.input_sample_rate);
72+
const total_samples: usize = @intCast(c.op_pcm_total(opus_file, -1));
73+
var samples = try allocator.alignedAlloc(f32, alignment, total_samples * channels);
74+
errdefer allocator.free(samples);
75+
76+
var i: usize = 0;
77+
while (i < samples.len) {
78+
const read = c.op_read_float(opus_file, samples[i..].ptr, @intCast(samples.len - i), null);
79+
if (read == 0) break else if (read < 0) return error.InvalidData;
80+
i += @intCast(read * channels);
81+
}
82+
83+
return .{
84+
.channels = channels,
85+
.sample_rate = sample_rate,
86+
.samples = samples,
87+
};
88+
}
89+
90+
const Decoder = struct {
91+
allocator: std.mem.Allocator,
92+
stream: std.io.StreamSource,
93+
samples: []f32 = &.{},
94+
sample_index: usize = 0,
95+
96+
fn readCallback(decoder_opaque: ?*anyopaque, ptr: [*c]u8, nbytes: c_int) callconv(.C) c_int {
97+
const decoder: *Decoder = @ptrCast(@alignCast(decoder_opaque));
98+
const read = decoder.stream.read(ptr[0..@intCast(nbytes)]) catch return -1;
99+
return @intCast(read);
100+
}
101+
102+
fn seekCallback(decoder_opaque: ?*anyopaque, offset: i64, whence: c_int) callconv(.C) c_int {
103+
const decoder: *Decoder = @ptrCast(@alignCast(decoder_opaque));
104+
switch (whence) {
105+
c.SEEK_SET => decoder.stream.seekTo(@intCast(offset)) catch return -1,
106+
c.SEEK_CUR => decoder.stream.seekBy(offset) catch return -1,
107+
c.SEEK_END => decoder.stream.seekTo(decoder.stream.getEndPos() catch return -1) catch return -1,
108+
else => unreachable,
109+
}
110+
return 0;
111+
}
112+
113+
fn tellCallback(decoder_opaque: ?*anyopaque) callconv(.C) i64 {
114+
const decoder: *Decoder = @ptrCast(@alignCast(decoder_opaque));
115+
const pos = decoder.stream.getPos() catch unreachable;
116+
return @intCast(pos);
117+
}
118+
119+
fn closeCallback(decoder_opaque: ?*anyopaque) callconv(.C) c_int {
120+
_ = decoder_opaque;
121+
return 0;
122+
}
123+
};
124+
125+
pub const Comments = struct {
126+
opus_comments: *c.OggOpusComments,
127+
128+
pub fn init() error{OutOfMemory}!Comments {
129+
const comments = c.ope_comments_create() orelse return error.OutOfMemory;
130+
return .{ .opus_comments = comments };
131+
}
132+
133+
pub fn deinit(comments: Comments) void {
134+
c.ope_comments_destroy(comments.opus_comments);
135+
}
136+
137+
pub fn addString(comments: Comments, tag: [*:0]const u8, value: [*:0]const u8) error{OutOfMemory}!void {
138+
const err = c.ope_comments_add(comments.opus_comments, tag, value);
139+
if (err != c.OPE_OK) return error.OutOfMemory;
140+
}
141+
142+
pub const PictureType = enum(u5) {
143+
other = 0,
144+
/// PNG Only
145+
icon_32x32 = 1,
146+
icon_other = 2,
147+
cover_front = 3,
148+
cover_back = 4,
149+
leaflet_page = 5,
150+
/// (e.g. label side of CD)
151+
media = 6,
152+
/// Lead performer/soloist
153+
lead_artist = 7,
154+
/// Artist/Performer
155+
artist = 8,
156+
conductor = 9,
157+
/// Band/Orchestra
158+
band = 10,
159+
composer = 11,
160+
lyricist = 12,
161+
recording_location = 13,
162+
during_recording = 14,
163+
during_performance = 15,
164+
video_screen_capture = 16,
165+
a_bright_colored_fish = 17,
166+
illustration = 18,
167+
artist_logotype = 19,
168+
/// Publisher/Studio logoType
169+
publisher_logotype = 20,
170+
};
171+
172+
pub fn addPicture(
173+
comments: Comments,
174+
image: []const u8,
175+
picture_type: PictureType,
176+
description: [*:0]const u8,
177+
) error{OutOfMemory}!void {
178+
const err = c.ope_comments_add_picture_from_memory(
179+
comments.opus_comments,
180+
image.ptr,
181+
image.len,
182+
@intFromEnum(picture_type),
183+
description,
184+
);
185+
if (err != c.OPE_OK) return error.OutOfMemory;
186+
}
187+
};
188+
189+
pub const EncodeError = error{
190+
OutOfMemory,
191+
InvalidPicture,
192+
InvalidIcon,
193+
Writing,
194+
Internal,
195+
Unknown,
196+
};
197+
198+
pub const ChannelMapping = enum(u1) {
199+
mono_stereo = 0,
200+
surround = 1,
201+
};
202+
203+
pub fn encodeStream(
204+
stream: std.io.StreamSource,
205+
comments: Comments,
206+
sample_rate: u24,
207+
channels: u24,
208+
channel_mapping: ChannelMapping,
209+
samples: []const f32,
210+
) (EncodeError || std.io.StreamSource.ReadError)!void {
211+
var encoder = Encoder{ .stream = stream };
212+
var err: c_int = 0;
213+
const ope_encoder = c.ope_encoder_create_callbacks(
214+
&c.OpusEncCallbacks{
215+
.write = Encoder.writeCallback,
216+
.close = Encoder.closeCallback,
217+
},
218+
&encoder,
219+
comments.opus_comments,
220+
sample_rate,
221+
channels,
222+
@intFromEnum(channel_mapping),
223+
&err,
224+
);
225+
try checkEncoderErr(err);
226+
defer c.ope_encoder_destroy(ope_encoder);
227+
228+
try checkEncoderErr(c.ope_encoder_flush_header(ope_encoder));
229+
try checkEncoderErr(c.ope_encoder_write_float(ope_encoder, samples.ptr, @intCast(samples.len / channels)));
230+
try checkEncoderErr(c.ope_encoder_drain(ope_encoder));
231+
}
232+
233+
const Encoder = struct {
234+
stream: std.io.StreamSource,
235+
236+
fn writeCallback(encoder_opaque: ?*anyopaque, ptr: [*c]const u8, len: i32) callconv(.C) c_int {
237+
const encoder: *Encoder = @ptrCast(@alignCast(encoder_opaque));
238+
_ = encoder.stream.write(ptr[0..@intCast(len)]) catch return 1;
239+
return 0;
240+
}
241+
242+
fn closeCallback(encoder_opaque: ?*anyopaque) callconv(.C) c_int {
243+
_ = encoder_opaque;
244+
return 0;
245+
}
246+
};
247+
248+
fn checkEncoderErr(err: c_int) EncodeError!void {
249+
return switch (err) {
250+
c.OPE_OK => {},
251+
c.OPE_BAD_ARG => unreachable,
252+
c.OPE_INTERNAL_ERROR => error.Internal,
253+
c.OPE_UNIMPLEMENTED => error.Internal,
254+
c.OPE_ALLOC_FAIL => error.OutOfMemory,
255+
c.OPE_CANNOT_OPEN => unreachable,
256+
c.OPE_TOO_LATE => unreachable,
257+
c.OPE_INVALID_PICTURE => error.InvalidPicture,
258+
c.OPE_INVALID_ICON => error.InvalidIcon,
259+
c.OPE_WRITE_FAIL => error.Writing,
260+
c.OPE_CLOSE_FAIL => unreachable,
261+
else => error.Unknown,
262+
};
263+
}

0 commit comments

Comments
 (0)