Compare commits

..
7 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
Illusion 1142d83adb finally understood why the use-after-free bug happened 2024-09-25 04:13:37 +02:00
Illusion f444efc5af improve the linux example 2024-09-24 23:07:49 +02:00
6 changed files with 277 additions and 158 deletions
+9 -10
View File
@@ -6,23 +6,19 @@ Files:-
Playback issues in the example program that may relate to the decoder:- Playback issues in the example program that may relate to the decoder:-
23 - Crunchy loud noise mixed with the actual audio data 23 - Crunchy loud noise mixed with the actual audio data
uncommon/02 -> 6 channel audio might be incorrect (not a decoder issue afaik)
uncommon/04 -> We get a bits per sample mismatch uncommon/04 -> We get a bits per sample mismatch
uncommon/10 -> We error with Invalid Signature. I assume for these two I should look for the frame sync bits. But maybe my decoder should reject these. idk. uncommon/10 -> We error with Invalid Signature. I assume for these two I should look for the frame sync bits. But maybe my decoder should reject these. idk.
uncommon/11 -> We error with Invalid Signature uncommon/11 -> We error with Invalid Signature
faulty/01 -> This one works when it shouldn't (Wrong max blocksize) 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/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:- 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 [ ] Options
- [x] Vorbis comment data - [x] Vorbis comment data
- [ ] Cuesheet ? - [ ] Cuesheet ?
@@ -32,10 +28,13 @@ Things:-
[ ] Lots of checks (look at the TODOs in flac.odin) [ ] Lots of checks (look at the TODOs in flac.odin)
[x] Memory optimizations [x] Memory optimizations
[ ] Speed 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 [ ] Seeking
[ ] Fuzzing to test the stability of the code [ ] Fuzzing to test the stability of the code
[ ] Check if the use-after-free bug happens on Windows, and if so, if the address sanitizer will show us the full stacktrace [x] Check if the use-after-free bug happens on Windows, and if so, if the address sanitizer will show us the full stacktrace
instead of skipping some procs. instead of skipping some procs.
This was caused by `read_data()` and the tee reader writing into the buffer and us using some memory that was freed while the buffer
resized. Solution is to use buffer data BEFORE calling `read_data()`
[ ] Encoder (low priority for now) [ ] Encoder (low priority for now)
+49 -24
View File
@@ -1,4 +1,4 @@
package flac_example package flac_linux_alsa_example
import "core:fmt" import "core:fmt"
import "core:mem" import "core:mem"
@@ -33,15 +33,19 @@ main :: proc() {
fmt.eprintln("Failed to alloc memory") fmt.eprintln("Failed to alloc memory")
return 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() 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 { if err != nil {
fmt.println("Error while reading flac file:", err) fmt.println("Error while reading flac file:", err)
os.exit(1) os.exit(1)
} }
defer flac.destroy(flac_data, reader, allocator)
fmt.println(flac_data) fmt.println(flac_data)
@@ -51,13 +55,13 @@ main :: proc() {
os.exit(1) os.exit(1)
} }
sample_rate := flac_data.metadata.sample_rate // TODO: channel mapping is incorrect for anything with more than 2 channels.
channels := flac_data.metadata.channels channels := flac_data.metadata.channels
md5_ctx: md5.Context md5_ctx: md5.Context
md5.init(&md5_ctx) md5.init(&md5_ctx)
for { for {
frame, err := flac.read_next_frame(reader, flac_data) frame, err := flac.decode_next_frame(reader, flac_data, context.allocator)
if err != nil { if err != nil {
if err == .EOF { if err == .EOF {
break break
@@ -67,20 +71,24 @@ main :: proc() {
} }
} }
//if frame.sample_rate != sample_rate || frame.channels != channels { if frame.channels != channels {
// sample_rate = frame.sample_rate // Finish playing the previous frame before changing the hw params.
// channels = frame.channels alsa.pcm_drain(handle)
// alsa_err = alsa.pcm_set_params(handle, .S16_LE, .RW_INTERLEAVED, u32(channels), sample_rate, 1, 0)
// if alsa_err < 0 { channels = frame.channels
// fmt.eprintln("Playback open error:", alsa.strerror(alsa_err)) alsa_err = alsa.pcm_set_params(handle, .S16_LE, .RW_INTERLEAVED, u32(channels), flac_data.metadata.sample_rate, 1, 500000)
// os.exit(1) if alsa_err < 0 {
// } fmt.eprintln("Playback open error:", alsa.strerror(alsa_err))
//} os.exit(1)
}
}
// TODO: resampling
// 8 bps audio // 8 bps audio
//converted_samples := make([]u8, len(samples)) //converted_samples := make([]u8, len(frame.samples))
//defer delete(converted_samples) //defer delete(converted_samples)
//for sample, i in samples { //for sample, i in frame.samples {
// converted_samples[i] = u8(sample & 0xFF) // converted_samples[i] = u8(sample & 0xFF)
//} //}
@@ -102,13 +110,6 @@ main :: proc() {
flac.md5hash(&md5_ctx, flac_data.metadata.bits_per_sample, frame.samples) flac.md5hash(&md5_ctx, flac_data.metadata.bits_per_sample, frame.samples)
} }
end := time.now()
err = flac.md5sum(&md5_ctx, flac_data)
if err == .MD5_Mismatch {
fmt.println("Decoded audio data is not correct.")
os.exit(1)
}
alsa_err = alsa.pcm_drain(handle) alsa_err = alsa.pcm_drain(handle)
if alsa_err < 0 { if alsa_err < 0 {
@@ -116,6 +117,30 @@ main :: proc() {
} }
alsa.pcm_close(handle) alsa.pcm_close(handle)
end := time.now()
err = flac.md5sum(&md5_ctx, flac_data)
if err == .MD5_Mismatch {
fmt.printfln("Decoded audio data is not correct. Expected MD5: %v, Got: %v", flac_data.metadata.expected_md5, flac_data.metadata.calculated_md5)
os.exit(1)
}
elapsed := time.diff(start, end) elapsed := time.diff(start, end)
fmt.println("Decoding took", elapsed) 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)
} }
+118 -53
View File
@@ -1,13 +1,11 @@
package flac package flac
import "core:bytes"
import "core:bufio" import "core:bufio"
import "core:crypto/legacy/md5" import "core:crypto/legacy/md5"
import "core:fmt" import "core:fmt"
import "core:io" import "core:io"
import "core:mem" import "core:mem"
import "core:os" import "core:os"
import "core:time"
Error :: union { Error :: union {
io.Error, io.Error,
@@ -69,8 +67,8 @@ PictureType :: enum u32 {
Flac :: struct { Flac :: struct {
metadata: FlacMetadata, metadata: FlacMetadata,
// This holds the samples up to the currently decoded frame. If all frames have // This holds the samples of the current frame.
// been decoded then this will hold all the decoded samples. // TODO: Should this be deleted? every frame already holds its own samples data
samples: [dynamic]i32, samples: [dynamic]i32,
} }
@@ -94,14 +92,15 @@ Picture :: struct {
height: u32, height: u32,
depth: u32, depth: u32,
num_colors: u32, num_colors: u32,
data: []byte, data: []byte `fmt:"-"`,
} }
Frame :: struct { Frame :: struct {
channels: u8, channels: u8,
sample_rate: u32, sample_rate: u32,
// A slice of `Flac`'s `samples` field. // A slice view of `Flac`'s `samples` field.
// Contains the frame's decoded samples. // Contains the frame's decoded samples.
// DO NOT FREE.
samples: []i32, samples: []i32,
} }
@@ -430,12 +429,27 @@ correlate :: proc(subframes: [][]i32, channel_assignment: ChannelAssignment) {
} }
@(private) @(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 header := read_data(r, FlacHeader) or_return
if header.magic != FLAC_MAGIC { if header.magic != FLAC_MAGIC {
return nil, .Invalid_Signature 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 sample_rate := header.streaminfo.sr_chan_bps_ts >> 44
num_channel_minus_one := (header.streaminfo.sr_chan_bps_ts >> 41) & 7 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 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 { if streaminfo_header.type != .STREAMINFO {
return flac, .Missing_StreamInfo return nil, .Missing_StreamInfo
} }
if flac == nil { if flac == nil {
@@ -462,6 +476,30 @@ read_metadata :: proc(r: ^Reader, allocator := context.allocator) -> (flac: ^Fla
flac.metadata.expected_md5 = header.streaminfo.md5 flac.metadata.expected_md5 = header.streaminfo.md5
pictures := make([dynamic]Picture, 0, 2, allocator) 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 last_block := streaminfo_header.last_block
for !last_block { for !last_block {
md_block_hdr := read_data(r, u32be) or_return 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) fmt.eprintfln("Expected size: %d, Got %d", metadata_block_header.length, actual_size)
} }
case .VORBIS_COMMENT: 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_len := read_data(r, u32le) or_return
vendor_name_slice := make([]byte, vendor_name_len) vendor_name_slice := make([]byte, vendor_name_len)
flac.metadata.vendor_name = string(vendor_name_slice)
read_slice(r, vendor_name_slice) or_return read_slice(r, vendor_name_slice) or_return
vendor_name := string(vendor_name_slice)
user_comment_list_len := read_data(r, u32le) or_return user_comment_list_len := read_data(r, u32le) or_return
actual_size := 4 + vendor_name_len + 4 actual_size := 4 + vendor_name_len + 4
comments := make([]string, user_comment_list_len) comments := make([]string, user_comment_list_len)
flac.metadata.vorbis_comments = comments
for i in 0..<user_comment_list_len { for i in 0..<user_comment_list_len {
comment_len := read_data(r, u32le) or_return comment_len := read_data(r, u32le) or_return
comment_slice := make([]byte, comment_len) comment_slice := make([]byte, comment_len)
comments[i] = string(comment_slice)
read_slice(r, comment_slice) or_return read_slice(r, comment_slice) or_return
comment := string(comment_slice)
actual_size += 4 + comment_len 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) { if metadata_block_header.length != u32(actual_size) {
fmt.eprintln("Vorbis metadata header length mismatch!") fmt.eprintln("Vorbis metadata header length mismatch!")
fmt.eprintfln("Expected size: %d, Got %d", metadata_block_header.length, actual_size) 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 cuesheet := read_data(r, CueSheet) or_return
if cuesheet.num_tracks < 1 { if cuesheet.num_tracks < 1 {
// TODO: should this be a warning instead of an error that halts the decoding? // 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) //fmt.println(cuesheet)
for i in 0..<cuesheet.num_tracks { for i in 0..<cuesheet.num_tracks {
@@ -546,15 +591,20 @@ read_metadata :: proc(r: ^Reader, allocator := context.allocator) -> (flac: ^Fla
case .PICTURE: case .PICTURE:
pic_type := PictureType(read_data(r, u32be) or_return) pic_type := PictureType(read_data(r, u32be) or_return)
mime_len := 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) mimetype_slice := make([]byte, mime_len)
defer if err != nil {delete(mimetype_slice)}
read_slice(r, mimetype_slice) or_return read_slice(r, mimetype_slice) or_return
mimetype := string(mimetype_slice) mimetype := string(mimetype_slice)
desc_len := read_data(r, u32be) or_return desc_len := read_data(r, u32be) or_return
desc_slice := make([]byte, desc_len) desc_slice := make([]byte, desc_len)
defer if err != nil {delete(desc_slice)}
read_slice(r, desc_slice) or_return read_slice(r, desc_slice) or_return
desc := string(desc_slice) desc := string(desc_slice)
metadata := read_data(r, PictureMetadata) or_return metadata := read_data(r, PictureMetadata) or_return
data := make([]byte, metadata.data_len) data := make([]byte, metadata.data_len)
defer if err != nil {delete(data)}
read_slice(r, data) or_return read_slice(r, data) or_return
picture := Picture{ picture := Picture{
type = pic_type, type = pic_type,
@@ -583,7 +633,7 @@ read_metadata :: proc(r: ^Reader, allocator := context.allocator) -> (flac: ^Fla
return flac, nil 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 // Frame Header
// //
@@ -591,10 +641,6 @@ read_next_frame :: proc(r: ^Reader, flac: ^Flac, allocator := context.allocator)
tee_r: io.Tee_Reader tee_r: io.Tee_Reader
crc_buffer: Buffer crc_buffer: Buffer
// NOTE: using the default heap allocator here causes a use-after-free bug on files subset/05, subset/06, and subset/25
// No idea what the cause of the bug is.
//
// Resolved without understanding the root issue. See below code for a note on this.
buffer_init_allocator(&crc_buffer, 0, 128) buffer_init_allocator(&crc_buffer, 0, 128)
defer buffer_destroy(&crc_buffer) defer buffer_destroy(&crc_buffer)
@@ -656,6 +702,14 @@ read_next_frame :: proc(r: ^Reader, flac: ^Flac, allocator := context.allocator)
return {}, .Invalid_Block_Size 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 { //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. // TODO: block sizes less than 16 are only valid for the last frame and MUST NOT be used for any other frame.
//} //}
@@ -784,7 +838,7 @@ read_next_frame :: proc(r: ^Reader, flac: ^Flac, allocator := context.allocator)
correlate(subframes, channel_assignment) correlate(subframes, channel_assignment)
start := len(flac.samples) clear(&flac.samples)
// Write the samples interleaved. // Write the samples interleaved.
for i in 0..<len(subframes[0]) { for i in 0..<len(subframes[0]) {
@@ -798,7 +852,7 @@ read_next_frame :: proc(r: ^Reader, flac: ^Flac, allocator := context.allocator)
delete(subframe) delete(subframe)
} }
frame.samples = flac.samples[start:] frame.samples = flac.samples[:]
frame.channels = frame_channels frame.channels = frame_channels
frame.sample_rate = actual_sample_rate_in_Hz frame.sample_rate = actual_sample_rate_in_Hz
@@ -806,13 +860,6 @@ read_next_frame :: proc(r: ^Reader, flac: ^Flac, allocator := context.allocator)
// Frame Footer // Frame Footer
// //
crc16_bytes := buffer_to_bytes(&crc_buffer) crc16_bytes := buffer_to_bytes(&crc_buffer)
// NOTE: The order of these two calls is important. If we're using the default heap allocator
// for crc_buffer and we call read_data before calculate_crc16 then we run into a memory bug.
// I'm still not sure the root cause of the bug but it disappears when they're in this order.
//
// In the below cases the bug doesn't happen and the order doesn't matter.
// When using the temp allocator or an arena allocator.
// When using the reader from the arguments instead of the tee reader.
calculated_frame_crc := calculate_crc16(crc16_bytes) calculated_frame_crc := calculate_crc16(crc16_bytes)
frame_crc := u16(read_data(r, u16be) or_return) frame_crc := u16(read_data(r, u16be) or_return)
@@ -867,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. // `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. // 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) { load_from_file :: proc(filename: string, allocator := context.allocator) -> (flac: ^Flac, r: ^Reader, err: Error) {
context.allocator = allocator context.allocator = allocator
file, open_err := os.open(filename) file, open_err := os.open(filename)
if open_err != os.ERROR_NONE { if open_err != nil {
fmt.eprintln(open_err) fmt.eprintln(open_err)
return nil, {}, .Unable_To_Read_File return nil, nil, .Unable_To_Read_File
} }
br := new(bufio.Reader) br := new(bufio.Reader)
bufio.reader_init(br, os.stream_from_handle(file)) bufio.reader_init(br, os.to_stream(file))
r = new(Reader) reader := new(Reader)
r.r = bufio.reader_to_stream(br) reader.r = bufio.reader_to_stream(br)
r.buf = 0 reader.buf = 0
r.x = 0 reader.x = 0
r.n = 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. /* `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. 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 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. the memory, otherwise a bad free may occur and crash the program.
@@ -901,21 +962,25 @@ load_from_file :: proc(filename: string, allocator := context.allocator) -> (fla
destroy :: proc(flac: ^Flac, r: ^Reader = nil, allocator := context.allocator) { destroy :: proc(flac: ^Flac, r: ^Reader = nil, allocator := context.allocator) {
context.allocator = allocator context.allocator = allocator
delete(flac.samples) // Tolerates a nil `flac` so a caller can `defer destroy(...)` straight after
for picture in flac.metadata.pictures { // a load without first checking whether the load succeeded.
delete(picture.mimetype) if flac != nil {
delete(picture.data) delete(flac.samples)
delete(picture.description) for picture in flac.metadata.pictures {
} delete(picture.mimetype)
delete(flac.metadata.pictures) delete(picture.data)
delete(picture.description)
}
delete(flac.metadata.pictures)
for comment in flac.metadata.vorbis_comments { for comment in flac.metadata.vorbis_comments {
delete(comment) delete(comment)
} }
delete(flac.metadata.vorbis_comments) delete(flac.metadata.vorbis_comments)
delete(flac.metadata.vendor_name) delete(flac.metadata.vendor_name)
free(flac) free(flac)
}
if r != nil { if r != nil {
br := cast(^bufio.Reader)r.data br := cast(^bufio.Reader)r.data
+7 -4
View File
@@ -6,6 +6,7 @@ import "core:crypto/legacy/md5"
FLAC_MAGIC :: 'f' << 24 | 'L' << 16 | 'a' << 8 | 'C' FLAC_MAGIC :: 'f' << 24 | 'L' << 16 | 'a' << 8 | 'C'
//odinfmt: disable //odinfmt: disable
@(rodata)
crc8_lookup := [256]u8 { crc8_lookup := [256]u8 {
0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15,
0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
@@ -41,6 +42,7 @@ crc8_lookup := [256]u8 {
0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3,
} }
@(rodata)
crc16_lookup := [256]u16 { crc16_lookup := [256]u16 {
0x0000, 0x8005, 0x800F, 0x000A, 0x801B, 0x001E, 0x0014, 0x8011, 0x0000, 0x8005, 0x800F, 0x000A, 0x801B, 0x001E, 0x0014, 0x8011,
0x8033, 0x0036, 0x003C, 0x8039, 0x0028, 0x802D, 0x8027, 0x0022, 0x8033, 0x0036, 0x003C, 0x8039, 0x0028, 0x802D, 0x8027, 0x0022,
@@ -210,10 +212,10 @@ CueSheet :: struct #packed {
media_catalog_num: [128]byte, media_catalog_num: [128]byte,
num_lead_in_samples: u64be, num_lead_in_samples: u64be,
using _: bit_field u8 { using _: bit_field u8 {
reserved: u8 | 7, reserved_1: u8 | 7,
compact_disc: bool | 1, compact_disc: bool | 1,
}, },
reserved: [258]byte, reserved_2: [258]byte,
num_tracks: u8, num_tracks: u8,
} }
@@ -223,11 +225,11 @@ CueSheetTrack :: struct #packed {
track_num: u8, track_num: u8,
ISRC: [12]byte, ISRC: [12]byte,
using _: bit_field u8 { using _: bit_field u8 {
reserved: u8 | 6, reserved_1: u8 | 6,
pre_emphasis: bool | 1, pre_emphasis: bool | 1,
track_type: TrackType | 1, track_type: TrackType | 1,
}, },
reserved: [13]byte, reserved_2: [13]byte,
num_track_index_points: u8, num_track_index_points: u8,
} }
@@ -238,6 +240,7 @@ CueSheetTrackIndex :: struct #packed {
reserved: [3]byte, reserved: [3]byte,
} }
@(optimization_mode="favor_size")
decode_extended_utf8 :: proc(r: ^Reader) -> (decoded_num: u64, err: Error) { decode_extended_utf8 :: proc(r: ^Reader) -> (decoded_num: u64, err: Error) {
MASKX :: 0b0011_1111 MASKX :: 0b0011_1111
MASK2 :: 0b0001_1111 MASK2 :: 0b0001_1111
+4 -1
View File
@@ -1,4 +1,3 @@
#+private
package flac package flac
import "core:io" import "core:io"
@@ -6,6 +5,8 @@ import "core:io"
/* Basic byte and bit reader. /* Basic byte and bit reader.
Uses core:bytes reader under the hood with a few useful procedures for reading structs and other types. 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 { Reader :: struct {
using r: io.Stream, using r: io.Stream,
// Temporary read buffer. // 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 // Read reads and returns the next n bits, at most 64. It buffers bits up to the
// next byte boundary. // next byte boundary.
// Borrowed from https://github.com/mewkiz/flac // Borrowed from https://github.com/mewkiz/flac
@(private)
read_bits :: proc(r: ^Reader, n: uint) -> (res: u64, err: io.Error) #no_bounds_check { read_bits :: proc(r: ^Reader, n: uint) -> (res: u64, err: io.Error) #no_bounds_check {
if n == 0 { if n == 0 {
return 0, nil 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 return res, nil
} }
@(private)
align_to_byte :: proc(r: ^Reader) { align_to_byte :: proc(r: ^Reader) {
r.x = 0 r.x = 0
r.n = 0 r.n = 0
+90 -66
View File
@@ -3,6 +3,7 @@ package flac_test
import "core:fmt" import "core:fmt"
import "core:os" import "core:os"
import "core:testing" import "core:testing"
import "core:crypto/legacy/md5"
import "shared:flac" import "shared:flac"
@(test) @(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)) testing.expect(t, err == nil, fmt.tprint("Read Blocksize 4096 metadata Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("Blocksize 4608 Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("Blocksize 16 Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("Blocksize 192 Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("Blocksize 254 Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("Blocksize 512 Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("Blocksize 725 Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("Blocksize 1000 Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("Blocksize 1937 Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("Blocksize 2304 Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("Partition order 8 Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("qlp precision 15 bit Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("qlp precision 2 bit Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -207,7 +208,7 @@ wasted_bits :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Wasted bits Failed with error:", err)) testing.expect(t, err == nil, fmt.tprint("Wasted bits Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("Only verbatim subframes Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -241,7 +242,7 @@ partition_order_8_containing_escaped_partitions :: proc(t: ^testing.T) {
) )
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("All fixed orders Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -271,7 +272,7 @@ precision_search :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Precision search Failed with error:", err)) testing.expect(t, err == nil, fmt.tprint("Precision search Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -286,7 +287,7 @@ samplerate_35467Hz :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Samplerate 35467Hz Failed with error:", err)) testing.expect(t, err == nil, fmt.tprint("Samplerate 35467Hz Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -301,7 +302,7 @@ samplerate_39kHz :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Samplerate 39kHz Failed with error:", err)) testing.expect(t, err == nil, fmt.tprint("Samplerate 39kHz Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -316,7 +317,7 @@ samplerate_22050Hz :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Samplerate 22050Hz Failed with error:", err)) testing.expect(t, err == nil, fmt.tprint("Samplerate 22050Hz Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("12 bit per sample Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("8 bit per sample Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -367,7 +368,7 @@ variable_blocksize_file_created_with_flake_revision_264 :: proc(t: ^testing.T) {
) )
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -391,7 +392,7 @@ variable_blocksize_file_created_with_flake_revision_264_modified_to_create_small
) )
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -412,7 +413,7 @@ variable_blocksize_file_created_with_CUETools_Flake_2_1_6 :: proc(t: ^testing.T)
) )
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -433,7 +434,7 @@ old_format_variable_blocksize_file_created_with_Flake_0_11 :: proc(t: ^testing.T
) )
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("High resolution audio, default settings Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("High resolution audio, blocksize 16384 Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("High resolution audio, blocksize 13456 Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -499,7 +500,7 @@ high_resolution_audio_using_only_32nd_order_predictors :: proc(t: ^testing.T) {
) )
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -520,7 +521,7 @@ high_resolution_audio_partition_order_8_containing_escaped_partitions :: proc(t:
) )
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -535,7 +536,7 @@ samplerate_192kHz :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Samplerate 192kHz Failed with error:", err)) testing.expect(t, err == nil, fmt.tprint("Samplerate 192kHz Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -554,7 +555,7 @@ samplerate_192kHz_using_only_32nd_order_predictors :: proc(t: ^testing.T) {
) )
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -569,7 +570,7 @@ samplerate_134560Hz :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Samplerate 134560Hz Failed with error:", err)) testing.expect(t, err == nil, fmt.tprint("Samplerate 134560Hz Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -584,7 +585,7 @@ samplerate_384kHz :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Samplerate 384kHz Failed with error:", err)) testing.expect(t, err == nil, fmt.tprint("Samplerate 384kHz Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("20 bit per sample Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -614,7 +615,7 @@ _3_channels :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("3 channels Failed with error:", err)) testing.expect(t, err == nil, fmt.tprint("3 channels Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -629,7 +630,7 @@ _4_channels :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("4 channels Failed with error:", err)) testing.expect(t, err == nil, fmt.tprint("4 channels Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -644,7 +645,7 @@ _5_channels :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("5 channels Failed with error:", err)) testing.expect(t, err == nil, fmt.tprint("5 channels Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -659,7 +660,7 @@ _6_channels :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("6 channels Failed with error:", err)) testing.expect(t, err == nil, fmt.tprint("6 channels Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -674,7 +675,7 @@ _7_channels :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("7 channels Failed with error:", err)) testing.expect(t, err == nil, fmt.tprint("7 channels Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -689,7 +690,7 @@ _8_channels :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("8 channels Failed with error:", err)) testing.expect(t, err == nil, fmt.tprint("8 channels Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -710,7 +711,7 @@ _8_channel_surround_192kHz_24_bit_using_only_32nd_order_predictors :: proc(t: ^t
) )
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("No total number of samples Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("No min-max framesize Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -755,7 +756,7 @@ only_streaminfo :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Only STREAMINFO Failed with error:", err)) testing.expect(t, err == nil, fmt.tprint("Only STREAMINFO Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("Extremely large SEEKTABLE Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("Extremely large PADDING Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("Extremely large PICTURE Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("Extremely large VORBISCOMMENT Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("Extremely large APPLICATION Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("CUESHEET with very many indexes Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("1000x repeating VORBSICOMMENT Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("File 48-53 combined Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -890,7 +891,7 @@ jpg_picture :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("JPG PICTURE Failed with error:", err)) testing.expect(t, err == nil, fmt.tprint("JPG PICTURE Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -905,7 +906,7 @@ png_picture :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("PNG PICTURE Failed with error:", err)) testing.expect(t, err == nil, fmt.tprint("PNG PICTURE Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -920,7 +921,7 @@ gif_picture :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("GIF PICTURE Failed with error:", err)) testing.expect(t, err == nil, fmt.tprint("GIF PICTURE Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -935,7 +936,7 @@ avif_picture :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("AVIF PICTURE Failed with error:", err)) testing.expect(t, err == nil, fmt.tprint("AVIF PICTURE Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }
@@ -950,7 +951,7 @@ mono_audio :: proc(t: ^testing.T) {
testing.expect(t, err == nil, fmt.tprint("Mono audio Failed with error:", err)) testing.expect(t, err == nil, fmt.tprint("Mono audio Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("Predictor overflow check, 16-bit Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("Predictor overflow check, 20-bit Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("Predictor overflow check, 24-bit Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("Rice partitions with escape code zero Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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) @(test)
uncommon_32_bit_per_sample :: proc(t: ^testing.T) { uncommon_32_bit_per_sample :: proc(t: ^testing.T) {
data, r, err := flac.load_from_file("flac-test-files/uncommon/05 - 32bps audio.flac") 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)) testing.expect(t, err == nil, fmt.tprint("Uncommon 32bps audio Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break 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)) testing.expect(t, err == nil, fmt.tprint("Uncommon samplerate 768kHz Failed with error:", err))
for { for {
frame, f_err := flac.read_next_frame(r, data) frame, f_err := flac.decode_next_frame(r, data)
if f_err == .EOF { if f_err == .EOF {
break break
} }