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
This commit is contained in:
2025-01-27 23:57:57 +02:00
parent 1142d83adb
commit 81aa8d0f68
4 changed files with 51 additions and 24 deletions
+1 -9
View File
@@ -13,15 +13,6 @@ 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:-
[ ] Options [ ] Options
@@ -33,6 +24,7 @@ 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
[x] 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
+24 -4
View File
@@ -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)
@@ -57,7 +61,7 @@ main :: proc() {
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, allocator) 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
@@ -123,4 +127,20 @@ main :: proc() {
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)
} }
+25 -11
View File
@@ -6,7 +6,7 @@ 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/os2"
import "core:time" import "core:time"
Error :: union { Error :: union {
@@ -69,8 +69,7 @@ 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.
samples: [dynamic]i32, samples: [dynamic]i32,
} }
@@ -94,14 +93,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,
} }
@@ -436,6 +436,12 @@ read_metadata :: proc(r: ^Reader, allocator := context.allocator) -> (flac: ^Fla
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
@@ -583,7 +589,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
// //
@@ -652,6 +658,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.
//} //}
@@ -780,7 +794,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]) {
@@ -794,7 +808,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
@@ -861,14 +875,14 @@ md5sum :: proc(md5_ctx: ^md5.Context, flac: ^Flac) -> Error {
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 := os2.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, {}, .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, os2.to_stream(file))
r = new(Reader) r = new(Reader)
r.r = bufio.reader_to_stream(br) r.r = bufio.reader_to_stream(br)
+1
View File
@@ -240,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