Compare commits

..
5 Commits
Author SHA1 Message Date
Illusion 7a0741320d fix: don't leak memory on the error path 2026-09-11 15:12:53 +02:00
Illusion 44b4e52063 fix: redeclaration of reserved field with using 2026-09-11 01:44:58 +02:00
Illusion ba691f7470 fix: compile under latest odin (maybe). other small fixes 2026-09-11 01:25:54 +02:00
Illusion c8b4c93276 todo 2025-01-30 01:07:14 +02:00
Illusion 81aa8d0f68 fix: don't hold onto previously decoded frames' samples to reduce mem usage
- migrate to os2
- check if the streaminfo min and max block size are within range
- use a tracking allocator for the example program
2025-01-27 23:57:57 +02:00
6 changed files with 247 additions and 127 deletions
+5 -9
View File
@@ -13,17 +13,12 @@ uncommon/11 -> We error with Invalid Signature
faulty/01 -> This one works when it shouldn't (Wrong max blocksize)
faulty/02 -> This one works when it shouldn't (Wrong max framesize)
faulty/04 -> This one works when it shouldn't. This one has a wrong number of channels (whatever that means)
Each frame has to be played by itself, otherwise plays sped up.
faulty/05 -> This one works when it shouldn't. (Wrong number of samples)
This should be fine to decode and play.
faulty/08 -> This one works when it shouldn't. (Blocksize 65536, exceeds the allowed max of 65535)
We should error here. We segfault currently.
faulty/09 -> This one works when it shouldn't. (Blocksize 1, below the allowed min of 16 (only the last frame is allowed to have less than 16))
We should error here. We play correctly currently.
faulty/11 -> We return an error now, but we should just warn and decode the audio fine.
Things:-
[ ] Seeking to arbitrary samples
- bufio `Reader` doesn't implement `.Seek` but the underlying `io.Stream` which is just a file DOES implement
`.Seek` so I think bufio `Reader` should have Seeking capabilities too as long as the underlying Reader supports it.
I could hack something together to (dis)prove this.
[ ] Options
- [x] Vorbis comment data
- [ ] Cuesheet ?
@@ -33,6 +28,7 @@ Things:-
[ ] Lots of checks (look at the TODOs in flac.odin)
[x] Memory optimizations
[ ] Speed optimizations
- Does this matter? So far I haven't found a FLAC file that lags while playing or stalls the audio device even without `-o:speed`
[ ] Seeking
[ ] Fuzzing to test the stability of the code
[x] Check if the use-after-free bug happens on Windows, and if so, if the address sanitizer will show us the full stacktrace
+25 -5
View File
@@ -1,4 +1,4 @@
package flac_linux_example
package flac_linux_alsa_example
import "core:fmt"
import "core:mem"
@@ -33,15 +33,19 @@ main :: proc() {
fmt.eprintln("Failed to alloc memory")
return
}
allocator := virtual.arena_allocator(&arena)
//allocator := virtual.arena_allocator(&arena)
track: mem.Tracking_Allocator
mem.tracking_allocator_init(&track, context.allocator)
defer mem.tracking_allocator_destroy(&track)
context.allocator = mem.tracking_allocator(&track)
start := time.now()
flac_data, reader, err := flac.load_from_file(os.args[1], allocator)
flac_data, reader, err := flac.load_from_file(os.args[1], context.allocator)
if err != nil {
fmt.println("Error while reading flac file:", err)
os.exit(1)
}
defer flac.destroy(flac_data, reader, allocator)
fmt.println(flac_data)
@@ -57,7 +61,7 @@ main :: proc() {
md5_ctx: md5.Context
md5.init(&md5_ctx)
for {
frame, err := flac.read_next_frame(reader, flac_data, allocator)
frame, err := flac.decode_next_frame(reader, flac_data, context.allocator)
if err != nil {
if err == .EOF {
break
@@ -123,4 +127,20 @@ main :: proc() {
elapsed := time.diff(start, end)
fmt.println("Decoding took", elapsed)
flac.destroy(flac_data, reader, context.allocator)
for _, leak in track.allocation_map {
fmt.printfln("%v leaked %m", leak.location, leak.size)
}
for bad_free in track.bad_free_array {
fmt.printfln("%v allocation %p was freed badly", bad_free.location, bad_free.memory)
}
fmt.printfln("Peak Mem: %m", track.peak_memory_allocated)
fmt.printfln("Total Mem: %m", track.total_memory_allocated)
fmt.printfln("Current Mem: %m", track.current_memory_allocated)
fmt.printfln("Total Allocs: %v, Total Frees: %v:", track.total_allocation_count, track.total_free_count)
fmt.printfln("Total Mem Free'd: %m", track.total_memory_freed)
}
+105 -29
View File
@@ -1,13 +1,11 @@
package flac
import "core:bytes"
import "core:bufio"
import "core:crypto/legacy/md5"
import "core:fmt"
import "core:io"
import "core:mem"
import "core:os"
import "core:time"
Error :: union {
io.Error,
@@ -69,8 +67,8 @@ PictureType :: enum u32 {
Flac :: struct {
metadata: FlacMetadata,
// This holds the samples up to the currently decoded frame. If all frames have
// been decoded then this will hold all the decoded samples.
// This holds the samples of the current frame.
// TODO: Should this be deleted? every frame already holds its own samples data
samples: [dynamic]i32,
}
@@ -94,14 +92,15 @@ Picture :: struct {
height: u32,
depth: u32,
num_colors: u32,
data: []byte,
data: []byte `fmt:"-"`,
}
Frame :: struct {
channels: u8,
sample_rate: u32,
// A slice of `Flac`'s `samples` field.
// A slice view of `Flac`'s `samples` field.
// Contains the frame's decoded samples.
// DO NOT FREE.
samples: []i32,
}
@@ -430,12 +429,27 @@ correlate :: proc(subframes: [][]i32, channel_assignment: ChannelAssignment) {
}
@(private)
read_metadata :: proc(r: ^Reader, allocator := context.allocator) -> (flac: ^Flac, err: Error) {
read_metadata :: proc(r: ^Reader, allocator := context.allocator) -> (out_flac: ^Flac, err: Error) {
context.allocator = allocator
/* Held in a local rather than in the named result: an Odin `defer` cannot
change a named result, so the unwind below is able to free this but not to
nil it out for the caller. Assigning it only on the successful return means
every error path hands back nil instead of a freed pointer.
*/
flac: ^Flac
header := read_data(r, FlacHeader) or_return
if header.magic != FLAC_MAGIC {
return nil, .Invalid_Signature
}
if (header.streaminfo.min_block_size < 16 || header.streaminfo.min_block_size > 65535) ||
(header.streaminfo.max_block_size < 16 || header.streaminfo.max_block_size > 65535) ||
(header.streaminfo.min_block_size > header.streaminfo.max_block_size) {
return nil, .Invalid_Block_Size
}
sample_rate := header.streaminfo.sr_chan_bps_ts >> 44
num_channel_minus_one := (header.streaminfo.sr_chan_bps_ts >> 41) & 7
bits_per_sample_minus_one := (header.streaminfo.sr_chan_bps_ts >> 36) & 0x1F
@@ -448,7 +462,7 @@ read_metadata :: proc(r: ^Reader, allocator := context.allocator) -> (flac: ^Fla
}
if streaminfo_header.type != .STREAMINFO {
return flac, .Missing_StreamInfo
return nil, .Missing_StreamInfo
}
if flac == nil {
@@ -462,6 +476,30 @@ read_metadata :: proc(r: ^Reader, allocator := context.allocator) -> (flac: ^Fla
flac.metadata.expected_md5 = header.streaminfo.md5
pictures := make([dynamic]Picture, 0, 2, allocator)
/* Everything allocated from here on hangs off `flac`, and a caller that gets
an error back is handed a nil `flac` -- so it has nothing to pass to
`destroy` and cannot clean any of this up itself. Unwind it here instead.
Each block below therefore hands its allocations to `flac` or to `pictures`
as soon as they exist, before the next read that could fail.
*/
defer if err != nil {
for picture in pictures {
delete(picture.mimetype)
delete(picture.description)
delete(picture.data)
}
delete(pictures)
for comment in flac.metadata.vorbis_comments {
delete(comment)
}
delete(flac.metadata.vorbis_comments)
delete(flac.metadata.vendor_name)
free(flac)
}
last_block := streaminfo_header.last_block
for !last_block {
md_block_hdr := read_data(r, u32be) or_return
@@ -501,26 +539,33 @@ read_metadata :: proc(r: ^Reader, allocator := context.allocator) -> (flac: ^Fla
fmt.eprintfln("Expected size: %d, Got %d", metadata_block_header.length, actual_size)
}
case .VORBIS_COMMENT:
// A stream may carry more than one of these; the last one wins,
// so release the previous block rather than orphaning it.
for comment in flac.metadata.vorbis_comments {
delete(comment)
}
delete(flac.metadata.vorbis_comments)
delete(flac.metadata.vendor_name)
flac.metadata.vorbis_comments = nil
flac.metadata.vendor_name = ""
vendor_name_len := read_data(r, u32le) or_return
vendor_name_slice := make([]byte, vendor_name_len)
flac.metadata.vendor_name = string(vendor_name_slice)
read_slice(r, vendor_name_slice) or_return
vendor_name := string(vendor_name_slice)
user_comment_list_len := read_data(r, u32le) or_return
actual_size := 4 + vendor_name_len + 4
comments := make([]string, user_comment_list_len)
flac.metadata.vorbis_comments = comments
for i in 0..<user_comment_list_len {
comment_len := read_data(r, u32le) or_return
comment_slice := make([]byte, comment_len)
comments[i] = string(comment_slice)
read_slice(r, comment_slice) or_return
comment := string(comment_slice)
actual_size += 4 + comment_len
comments[i] = comment
}
flac.metadata.vorbis_comments = comments
flac.metadata.vendor_name = vendor_name
if metadata_block_header.length != u32(actual_size) {
fmt.eprintln("Vorbis metadata header length mismatch!")
fmt.eprintfln("Expected size: %d, Got %d", metadata_block_header.length, actual_size)
@@ -532,7 +577,7 @@ read_metadata :: proc(r: ^Reader, allocator := context.allocator) -> (flac: ^Fla
cuesheet := read_data(r, CueSheet) or_return
if cuesheet.num_tracks < 1 {
// TODO: should this be a warning instead of an error that halts the decoding?
return flac, .Missing_Lead_Out_Track
return nil, .Missing_Lead_Out_Track
}
//fmt.println(cuesheet)
for i in 0..<cuesheet.num_tracks {
@@ -546,15 +591,20 @@ read_metadata :: proc(r: ^Reader, allocator := context.allocator) -> (flac: ^Fla
case .PICTURE:
pic_type := PictureType(read_data(r, u32be) or_return)
mime_len := read_data(r, u32be) or_return
// These three are only reachable through `pictures` once the
// append below runs, so they need their own unwind until then.
mimetype_slice := make([]byte, mime_len)
defer if err != nil {delete(mimetype_slice)}
read_slice(r, mimetype_slice) or_return
mimetype := string(mimetype_slice)
desc_len := read_data(r, u32be) or_return
desc_slice := make([]byte, desc_len)
defer if err != nil {delete(desc_slice)}
read_slice(r, desc_slice) or_return
desc := string(desc_slice)
metadata := read_data(r, PictureMetadata) or_return
data := make([]byte, metadata.data_len)
defer if err != nil {delete(data)}
read_slice(r, data) or_return
picture := Picture{
type = pic_type,
@@ -583,7 +633,7 @@ read_metadata :: proc(r: ^Reader, allocator := context.allocator) -> (flac: ^Fla
return flac, nil
}
read_next_frame :: proc(r: ^Reader, flac: ^Flac, allocator := context.allocator) -> (frame: Frame, err: Error) {
decode_next_frame :: proc(r: ^Reader, flac: ^Flac, allocator := context.allocator) -> (frame: Frame, err: Error) {
//
// Frame Header
//
@@ -652,6 +702,14 @@ read_next_frame :: proc(r: ^Reader, flac: ^Flac, allocator := context.allocator)
return {}, .Invalid_Block_Size
}
// TODO: Any frame but the last one MUST have a block size equal to or greater
// than the minimum block size and MUST have a block size equal to or
// less than the maximum block size. The last frame MUST have a block
// size equal to or less than the maximum block size; it does not have
// to comply to the minimum block size because the block size of that
// frame must be able to accommodate the length of the audio data the
// stream contains
//
//if actual_block_size < 15 && !last_frame {
// TODO: block sizes less than 16 are only valid for the last frame and MUST NOT be used for any other frame.
//}
@@ -780,7 +838,7 @@ read_next_frame :: proc(r: ^Reader, flac: ^Flac, allocator := context.allocator)
correlate(subframes, channel_assignment)
start := len(flac.samples)
clear(&flac.samples)
// Write the samples interleaved.
for i in 0..<len(subframes[0]) {
@@ -794,7 +852,7 @@ read_next_frame :: proc(r: ^Reader, flac: ^Flac, allocator := context.allocator)
delete(subframe)
}
frame.samples = flac.samples[start:]
frame.samples = flac.samples[:]
frame.channels = frame_channels
frame.sample_rate = actual_sample_rate_in_Hz
@@ -856,33 +914,47 @@ md5sum :: proc(md5_ctx: ^md5.Context, flac: ^Flac) -> Error {
}
// `load_from_file` initializes a buffered reader for the file and reads all the metadata blocks.
// Calls to `read_next_frame()` MUST be made to decode the audio data.
// Calls to `decode_next_frame()` MUST be made to decode the audio data.
// This is the recommended way to read most FLAC files as it has a minimal memory overhead.
load_from_file :: proc(filename: string, allocator := context.allocator) -> (flac: ^Flac, r: ^Reader, err: Error) {
context.allocator = allocator
file, open_err := os.open(filename)
if open_err != os.ERROR_NONE {
if open_err != nil {
fmt.eprintln(open_err)
return nil, {}, .Unable_To_Read_File
return nil, nil, .Unable_To_Read_File
}
br := new(bufio.Reader)
bufio.reader_init(br, os.stream_from_handle(file))
bufio.reader_init(br, os.to_stream(file))
r = new(Reader)
r.r = bufio.reader_to_stream(br)
r.buf = 0
r.x = 0
r.n = 0
reader := new(Reader)
reader.r = bufio.reader_to_stream(br)
reader.buf = 0
reader.x = 0
reader.n = 0
flac = read_metadata(r) or_return
/* Without this an unreadable file in a scanned library burns an fd and the
reader's 4 KB buffer on every attempt -- the caller is handed nil for both
results and so has no handle to close the file with.
`reader` is a local for the same reason `read_metadata` keeps one: a defer
cannot nil out the named result, so `r` is only assigned on success.
*/
defer if err != nil {
bufio.reader_destroy(br)
free(br)
free(reader)
os.close(file)
}
return flac, r, nil
flac = read_metadata(reader) or_return
return flac, reader, nil
}
/* `destroy` cleans up and frees the memory allocated by all 3 loading functions.
The returned reader from `load_from_file` MUST be passed to `destroy` in order to close the file handle.
Both arguments may be nil, so it is safe to defer immediately after a load.
---
The allocator that was used when loading the flac file MUST be passed to `destroy` in order to properly free
the memory, otherwise a bad free may occur and crash the program.
@@ -890,6 +962,9 @@ load_from_file :: proc(filename: string, allocator := context.allocator) -> (fla
destroy :: proc(flac: ^Flac, r: ^Reader = nil, allocator := context.allocator) {
context.allocator = allocator
// Tolerates a nil `flac` so a caller can `defer destroy(...)` straight after
// a load without first checking whether the load succeeded.
if flac != nil {
delete(flac.samples)
for picture in flac.metadata.pictures {
delete(picture.mimetype)
@@ -905,6 +980,7 @@ destroy :: proc(flac: ^Flac, r: ^Reader = nil, allocator := context.allocator) {
delete(flac.metadata.vendor_name)
free(flac)
}
if r != nil {
br := cast(^bufio.Reader)r.data
+5 -4
View File
@@ -212,10 +212,10 @@ CueSheet :: struct #packed {
media_catalog_num: [128]byte,
num_lead_in_samples: u64be,
using _: bit_field u8 {
reserved: u8 | 7,
reserved_1: u8 | 7,
compact_disc: bool | 1,
},
reserved: [258]byte,
reserved_2: [258]byte,
num_tracks: u8,
}
@@ -225,11 +225,11 @@ CueSheetTrack :: struct #packed {
track_num: u8,
ISRC: [12]byte,
using _: bit_field u8 {
reserved: u8 | 6,
reserved_1: u8 | 6,
pre_emphasis: bool | 1,
track_type: TrackType | 1,
},
reserved: [13]byte,
reserved_2: [13]byte,
num_track_index_points: u8,
}
@@ -240,6 +240,7 @@ CueSheetTrackIndex :: struct #packed {
reserved: [3]byte,
}
@(optimization_mode="favor_size")
decode_extended_utf8 :: proc(r: ^Reader) -> (decoded_num: u64, err: Error) {
MASKX :: 0b0011_1111
MASK2 :: 0b0001_1111
+4 -1
View File
@@ -1,4 +1,3 @@
#+private
package flac
import "core:io"
@@ -6,6 +5,8 @@ import "core:io"
/* Basic byte and bit reader.
Uses core:bytes reader under the hood with a few useful procedures for reading structs and other types.
*/
// Public because load_from_file hands one back; everything else in this file
// stays package-private.
Reader :: struct {
using r: io.Stream,
// Temporary read buffer.
@@ -33,6 +34,7 @@ read_data :: #force_inline proc(r: ^Reader, $T: typeid) -> (res: T, err: io.Erro
// Read reads and returns the next n bits, at most 64. It buffers bits up to the
// next byte boundary.
// Borrowed from https://github.com/mewkiz/flac
@(private)
read_bits :: proc(r: ^Reader, n: uint) -> (res: u64, err: io.Error) #no_bounds_check {
if n == 0 {
return 0, nil
@@ -86,6 +88,7 @@ read_bits :: proc(r: ^Reader, n: uint) -> (res: u64, err: io.Error) #no_bounds_c
return res, nil
}
@(private)
align_to_byte :: proc(r: ^Reader) {
r.x = 0
r.n = 0
+90 -66
View File
@@ -3,6 +3,7 @@ package flac_test
import "core:fmt"
import "core:os"
import "core:testing"
import "core:crypto/legacy/md5"
import "shared:flac"
@(test)
@@ -12,7 +13,7 @@ test_blocksize_4096 :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Read Blocksize 4096 metadata Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -27,7 +28,7 @@ test_blocksize_4608 :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Blocksize 4608 Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -42,7 +43,7 @@ test_blocksize_16 :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Blocksize 16 Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -57,7 +58,7 @@ test_blocksize_192 :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Blocksize 192 Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -72,7 +73,7 @@ test_blocksize_254 :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Blocksize 254 Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -87,7 +88,7 @@ test_blocksize_512 :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Blocksize 512 Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -102,7 +103,7 @@ test_blocksize_725 :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Blocksize 725 Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -117,7 +118,7 @@ test_blocksize_1000 :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Blocksize 1000 Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -132,7 +133,7 @@ test_blocksize_1937 :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Blocksize 1937 Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -147,7 +148,7 @@ test_blocksize_2304 :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Blocksize 2304 Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -162,7 +163,7 @@ test_partition_order_8 :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Partition order 8 Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -177,7 +178,7 @@ test_qlp_precision_15_bit :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("qlp precision 15 bit Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -192,7 +193,7 @@ test_qlp_precision_2_bit :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("qlp precision 2 bit Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -207,7 +208,7 @@ wasted_bits :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Wasted bits Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -222,7 +223,7 @@ only_verbatim_subframes :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Only verbatim subframes Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -241,7 +242,7 @@ partition_order_8_containing_escaped_partitions :: proc(t: ^testing.T) {
)
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -256,7 +257,7 @@ all_fixed_orders :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("All fixed orders Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -271,7 +272,7 @@ precision_search :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Precision search Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -286,7 +287,7 @@ samplerate_35467Hz :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Samplerate 35467Hz Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -301,7 +302,7 @@ samplerate_39kHz :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Samplerate 39kHz Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -316,7 +317,7 @@ samplerate_22050Hz :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Samplerate 22050Hz Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -331,7 +332,7 @@ _12_bit_per_sample :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("12 bit per sample Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -346,7 +347,7 @@ _8_bit_per_sample :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("8 bit per sample Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -367,7 +368,7 @@ variable_blocksize_file_created_with_flake_revision_264 :: proc(t: ^testing.T) {
)
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -391,7 +392,7 @@ variable_blocksize_file_created_with_flake_revision_264_modified_to_create_small
)
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -412,7 +413,7 @@ variable_blocksize_file_created_with_CUETools_Flake_2_1_6 :: proc(t: ^testing.T)
)
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -433,7 +434,7 @@ old_format_variable_blocksize_file_created_with_Flake_0_11 :: proc(t: ^testing.T
)
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -448,7 +449,7 @@ high_resolution_audio_default_settings :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("High resolution audio, default settings Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -463,7 +464,7 @@ high_resolution_audio_blocksize_16384 :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("High resolution audio, blocksize 16384 Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -478,7 +479,7 @@ high_resolution_audio_blocksize_13456 :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("High resolution audio, blocksize 13456 Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -499,7 +500,7 @@ high_resolution_audio_using_only_32nd_order_predictors :: proc(t: ^testing.T) {
)
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -520,7 +521,7 @@ high_resolution_audio_partition_order_8_containing_escaped_partitions :: proc(t:
)
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -535,7 +536,7 @@ samplerate_192kHz :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Samplerate 192kHz Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -554,7 +555,7 @@ samplerate_192kHz_using_only_32nd_order_predictors :: proc(t: ^testing.T) {
)
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -569,7 +570,7 @@ samplerate_134560Hz :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Samplerate 134560Hz Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -584,7 +585,7 @@ samplerate_384kHz :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Samplerate 384kHz Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -599,7 +600,7 @@ _20_bit_per_sample :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("20 bit per sample Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -614,7 +615,7 @@ _3_channels :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("3 channels Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -629,7 +630,7 @@ _4_channels :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("4 channels Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -644,7 +645,7 @@ _5_channels :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("5 channels Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -659,7 +660,7 @@ _6_channels :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("6 channels Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -674,7 +675,7 @@ _7_channels :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("7 channels Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -689,7 +690,7 @@ _8_channels :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("8 channels Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -710,7 +711,7 @@ _8_channel_surround_192kHz_24_bit_using_only_32nd_order_predictors :: proc(t: ^t
)
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -725,7 +726,7 @@ no_total_number_of_samples :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("No total number of samples Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -740,7 +741,7 @@ no_min_max_framesize :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("No min-max framesize Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -755,7 +756,7 @@ only_streaminfo :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Only STREAMINFO Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -770,7 +771,7 @@ extreme_large_seektable :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Extremely large SEEKTABLE Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -785,7 +786,7 @@ extreme_large_padding :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Extremely large PADDING Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -800,7 +801,7 @@ extreme_large_picture :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Extremely large PICTURE Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -815,7 +816,7 @@ extreme_large_vorbiscomment :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Extremely large VORBISCOMMENT Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -830,7 +831,7 @@ extreme_large_application :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Extremely large APPLICATION Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -845,7 +846,7 @@ cuesheet_with_very_many_indexes :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("CUESHEET with very many indexes Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -860,7 +861,7 @@ _1000x_repeating_vorbiscomment :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("1000x repeating VORBSICOMMENT Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -875,7 +876,7 @@ file_48_53_combined :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("File 48-53 combined Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -890,7 +891,7 @@ jpg_picture :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("JPG PICTURE Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -905,7 +906,7 @@ png_picture :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("PNG PICTURE Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -920,7 +921,7 @@ gif_picture :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("GIF PICTURE Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -935,7 +936,7 @@ avif_picture :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("AVIF PICTURE Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -950,7 +951,7 @@ mono_audio :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Mono audio Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -965,7 +966,7 @@ predictor_overflow_check_16_bit :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Predictor overflow check, 16-bit Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -980,7 +981,7 @@ predictor_overflow_check_20_bit :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Predictor overflow check, 20-bit Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -995,7 +996,7 @@ predictor_overflow_check_24_bit :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Predictor overflow check, 24-bit Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -1010,7 +1011,7 @@ rice_partitions_with_escape_code_zero :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Rice partitions with escape code zero Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -1018,6 +1019,29 @@ rice_partitions_with_escape_code_zero :: proc(t: ^testing.T) {
}
}
@(test)
uncommon_changing_samplerate :: proc(t: ^testing.T) {
data, r, err := flac.load_from_file("flac-test-files/uncommon/01 - changing samplerate.flac")
defer flac.destroy(data, r)
testing.expect(t, err == nil, fmt.tprint("Uncommon changing samplerate Failed with error:", err))
md5_ctx: md5.Context
md5.init(&md5_ctx)
for {
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
flac.md5hash(&md5_ctx, data.metadata.bits_per_sample, frame.samples)
testing.expect(t, f_err == nil, fmt.tprintf("Decoding Uncommon changing samplerate frame Failed with error:", f_err))
}
if data.metadata.expected_md5 != 0 {
testing.expect(t, flac.md5sum(&md5_ctx, data) == nil, fmt.tprintf("Incorrect MD5 hash. Expected %v. Got %v", data.metadata.expected_md5, data.metadata.calculated_md5))
}
}
@(test)
uncommon_32_bit_per_sample :: proc(t: ^testing.T) {
data, r, err := flac.load_from_file("flac-test-files/uncommon/05 - 32bps audio.flac")
@@ -1025,7 +1049,7 @@ uncommon_32_bit_per_sample :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Uncommon 32bps audio Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}
@@ -1040,7 +1064,7 @@ uncommon_samplerate_768kHz :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Uncommon samplerate 768kHz Failed with error:", err))
for {
frame, f_err := flac.read_next_frame(r, data)
frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF {
break
}