79 lines
2.9 KiB
Plaintext
79 lines
2.9 KiB
Plaintext
//
|
|
// OGG Vorbis decoder. Jellyfin always serves us /universal as OGG, so this
|
|
// is the single decode path. Returns Sound_Data with type LINEAR_SAMPLE_ARRAY
|
|
// — same buffer the visualizer reads via sound_data.samples (no parallel
|
|
// decoder, no cursor desync).
|
|
//
|
|
// We do NOT use stb_vorbis_decode_memory: that malloc()s the output buffer
|
|
// with libc's allocator, and Sound_Player's release_asset path frees it
|
|
// with Jai's allocator → SEGV at song end. Instead we open the stream,
|
|
// allocate the PCM buffer ourselves, and decode into it. Same allocator on
|
|
// both ends.
|
|
//
|
|
|
|
decode_ogg :: (bytes: string, name: string) -> Sound.Sound_Data, bool {
|
|
err: s32;
|
|
v := Stb_Vorbis.stb_vorbis_open_memory(bytes.data, cast(s32) bytes.count, *err, null);
|
|
if !v {
|
|
log_error("decode_ogg: open failed for '%' (err=%)", name, err);
|
|
return .{}, false;
|
|
}
|
|
defer Stb_Vorbis.stb_vorbis_close(v);
|
|
|
|
info := Stb_Vorbis.stb_vorbis_get_info(v);
|
|
nch := cast(s64) info.channels;
|
|
if nch <= 0 || info.sample_rate == 0 {
|
|
log_error("decode_ogg: bad info for '%' (ch=%, rate=%)", name, info.channels, info.sample_rate);
|
|
return .{}, false;
|
|
}
|
|
|
|
total_frames := cast(s64) Stb_Vorbis.stb_vorbis_stream_length_in_samples(v);
|
|
if total_frames <= 0 {
|
|
log_error("decode_ogg: zero-length stream for '%'", name);
|
|
return .{}, false;
|
|
}
|
|
|
|
total_samples := total_frames * nch;
|
|
samples := cast(*s16) alloc(total_samples * size_of(s16));
|
|
if !samples {
|
|
log_error("decode_ogg: alloc failed for '%'", name);
|
|
return .{}, false;
|
|
}
|
|
|
|
// stb_vorbis sometimes hands back fewer frames than the header advertised
|
|
// (chained / corrupt streams), so loop until it returns 0 and use what
|
|
// we got.
|
|
decoded_frames: s64 = 0;
|
|
while decoded_frames < total_frames {
|
|
remaining_shorts := cast(s32) ((total_frames - decoded_frames) * nch);
|
|
n := Stb_Vorbis.stb_vorbis_get_samples_short_interleaved(
|
|
v, cast(s32) nch,
|
|
samples + decoded_frames * nch,
|
|
remaining_shorts);
|
|
if n <= 0 break;
|
|
decoded_frames += n;
|
|
}
|
|
|
|
if decoded_frames == 0 {
|
|
log_error("decode_ogg: no frames decoded for '%'", name);
|
|
free(samples);
|
|
return .{}, false;
|
|
}
|
|
|
|
actual_samples := decoded_frames * nch;
|
|
|
|
sd: Sound.Sound_Data;
|
|
sd.name = copy_string(name);
|
|
sd.loaded = true;
|
|
sd.type = .LINEAR_SAMPLE_ARRAY;
|
|
sd.nchannels = cast(u16) nch;
|
|
sd.sampling_rate = cast(u32) info.sample_rate;
|
|
sd.nsamples_times_nchannels = actual_samples;
|
|
sd.samples = samples;
|
|
sd.buffer.count = actual_samples * size_of(s16);
|
|
sd.buffer.data = cast(*u8) samples;
|
|
|
|
log_info("decode_ogg: '%' ch=%, rate=%, frames=%", name, nch, info.sample_rate, decoded_frames);
|
|
return sd, true;
|
|
}
|