feat: buffered decoding API

This commit is contained in:
2024-06-19 04:08:54 +02:00
parent ae6ad428ab
commit c7a634eeb3
10 changed files with 1985 additions and 305 deletions
+2 -5
View File
@@ -1,7 +1,4 @@
.vscode/
ols.json ols.json
odinfmt.json odinfmt.json
example/ tests/*.flac
example/!*.odin
tests/test-files/LPC_example.flac
tests/test-files/fixed_example.flac
tests/test-files/verbatim_example.flac
+16 -8
View File
@@ -2,11 +2,14 @@ Files:-
27 -> This one uses the old format of specifying a variable block strategy. Look into it. 27 -> This one uses the old format of specifying a variable block strategy. Look into it.
The way to know the blocking strategy for this old format FLACs is to see if min_block_size and max_block_size are different, The way to know the blocking strategy for this old format FLACs is to see if min_block_size and max_block_size are different,
if they are, then the blocking strategy is VARIABLE, if not, then it's FIXED. if they are, then the blocking strategy is VARIABLE, if not, then it's FIXED.
44 -> High memory and CPU usage. needs optimization.
uncommon/01 -> This one decodes fine but the expected MD5 is 0x00 * 16 which doesn't match the calculated MD5. not sure what to do about this. Playback issues in the example program that may relate to the decoder:-
uncommon/02 -> No MD5 05 - CRC fails for a frame. The gotten CRC is different each time, expected CRC is also different sometimes.
uncommon/03 -> No MD5 06 - Same as above
23 - Crunchy loud noise mixed with the actual audio data
25 - Same CRC problem
It's worth noting that if we get rid of the CRC checks the files play just fine.
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
@@ -14,19 +17,24 @@ 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) 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) 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) faulty/08 -> This one works when it shouldn't. (Blocksize 65536, exceeds the allowed max of 65535)
faulty/09 -> This one works when it shouldn't. (Blocksize 1, below the allowed min of 16 (last frame(subframe?) is allowed to have less than 16)) 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
- [ ] Vorbis comment data - [x] Vorbis comment data
- [ ] Cuesheet ? - [ ] Cuesheet ?
- [ ] Seektable ? - [ ] Seektable ?
- [x] Picture data - [x] Picture data
[ ] File streaming API? io.Stream?? [x] File streaming API? io.Stream??
[ ] Lots of checks (look at the TODOs in flac.odin) [ ] Lots of checks (look at the TODOs in flac.odin)
[ ] Memory optimizations [x] Memory optimizations
[ ] Speed optimizations [ ] Speed optimizations
[ ] Seeking [ ] Seeking
[ ] Fuzzing to test the stability of the code [ ] Fuzzing to test the stability of the code
-36
View File
@@ -1,36 +0,0 @@
package flac_example
import "core:fmt"
import "core:mem"
import "core:mem/virtual"
import "core:os"
import "core:time"
import "../"
main :: proc() {
if len(os.args) < 2 {
fmt.eprintln("Please provide a path to a flac file.")
os.exit(1)
}
arena: virtual.Arena
arena_err := virtual.arena_init_static(&arena, mem.Megabyte * 8)
if arena_err != nil {
fmt.eprintln("Failed to alloc memory")
return
}
allocator := virtual.arena_allocator(&arena)
start := time.now()
flac_data, err := flac.load_from_file(os.args[1], allocator = allocator)
if err != nil {
fmt.eprintln(err)
os.exit(1)
}
end := time.now()
elapsed := time.diff(start, end)
fmt.println("Decoding took", elapsed)
}
+189
View File
@@ -0,0 +1,189 @@
package flac_example
import "core:fmt"
import "core:mem"
import "core:mem/virtual"
import "core:crypto/legacy/md5"
import "core:os"
import "core:time"
import "shared:flac"
import "shared:alsa"
DEVICE :: "default"
main :: proc() {
if len(os.args) < 2 {
fmt.eprintln("Please provide a path to a flac file.")
os.exit(1)
}
arena: virtual.Arena
handle: ^alsa.pcm_t
alsa_err := alsa.pcm_open(&handle, DEVICE, .PLAYBACK, .BLOCK)
if alsa_err < 0 {
fmt.eprintln("Failed to initialize audio device: ", alsa.strerror(alsa_err))
os.exit(1)
}
arena_err := virtual.arena_init_growing(&arena, mem.Megabyte * 8)
if arena_err != nil {
fmt.eprintln("Failed to alloc memory")
return
}
allocator := virtual.arena_allocator(&arena)
start := time.now()
flac_data, reader, err := flac.load_from_file_buffered(os.args[1], 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)
alsa_err = alsa.pcm_set_params(handle, .S16_LE, .RW_INTERLEAVED, u32(flac_data.metadata.channels), flac_data.metadata.sample_rate, 1, 500000)
if alsa_err < 0 {
fmt.eprintln("Playback open error:", alsa.strerror(alsa_err))
os.exit(1)
}
sample_rate := flac_data.metadata.sample_rate
channels := flac_data.metadata.channels
//md5_ctx: md5.Context
//md5.init(&md5_ctx)
for {
frame, err := flac.read_next_frame(reader, flac_data)
defer delete(frame.samples)
if err != nil {
if err == .EOF {
break
} else {
fmt.println("Error while decoding frame:", err)
os.exit(1)
}
}
//if frame.sample_rate != sample_rate || frame.channels != channels {
// sample_rate = frame.sample_rate
// channels = frame.channels
// alsa_err = alsa.pcm_set_params(handle, .S16_LE, .RW_INTERLEAVED, u32(channels), sample_rate, 1, 0)
// if alsa_err < 0 {
// fmt.eprintln("Playback open error:", alsa.strerror(alsa_err))
// os.exit(1)
// }
//}
//converted_samples := make([]u8, len(samples))
//defer delete(converted_samples)
//for sample, i in samples {
// converted_samples[i] = u8(sample & 0xFF)
//}
converted_samples := make([]i16, len(frame.samples))
defer delete(converted_samples)
for sample, i in frame.samples {
new_sample := sample & 0xFF
new_sample |= ((sample >> 8) & 0xFF) << 8
converted_samples[i] = i16(new_sample)
}
frames := alsa.pcm_writei(handle, raw_data(converted_samples), u64(len(frame.samples)) / u64(frame.channels))
if frames < 0 {
fmt.eprintln("Error writing to PCM device:", alsa.strerror(auto_cast frames))
break
}
fmt.printfln("Played %d frames", frames)
//flac.md5hash(&md5_ctx, flac_data.metadata.bits_per_sample, 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)
if alsa_err < 0 {
fmt.eprintln("pcm_drain failed with:", alsa.strerror(alsa_err))
}
alsa.pcm_close(handle)
elapsed := time.diff(start, end)
fmt.println("Decoding took", elapsed)
}
old_main :: proc() {
if len(os.args) < 2 {
fmt.eprintln("Please provide a path to a flac file.")
os.exit(1)
}
handle: ^alsa.pcm_t
arena: virtual.Arena
arena_err := virtual.arena_init_growing(&arena, mem.Megabyte * 8)
if arena_err != nil {
fmt.eprintln("Failed to alloc memory")
return
}
allocator := virtual.arena_allocator(&arena)
alsa_err := alsa.pcm_open(&handle, DEVICE, .PLAYBACK, .BLOCK)
if alsa_err < 0 {
fmt.eprintln("Failed to initialize audio device: ", alsa.strerror(alsa_err))
os.exit(1)
}
start := time.now()
flac_data, err := flac.load_from_file(os.args[1], allocator = allocator)
if err != nil {
fmt.eprintln(err)
if err == .MD5_Mismatch {
fmt.printfln("Expected: %v, got: %v", flac_data.metadata.expected_md5, flac_data.metadata.calculated_md5)
}
os.exit(1)
}
defer flac.destroy(flac_data, allocator = allocator)
end := time.now()
fmt.println(flac_data.metadata)
alsa_err = alsa.pcm_set_params(handle, .S16_LE, .RW_INTERLEAVED, u32(flac_data.metadata.channels), flac_data.metadata.sample_rate, 1, 500000)
if alsa_err < 0 {
fmt.eprintln("Playback open error:", alsa.strerror(alsa_err))
os.exit(1)
}
converted_samples := make([]i16, len(flac_data.samples))
for sample, i in flac_data.samples {
new_sample := sample & 0xFF
new_sample |= ((sample >> 8) & 0xFF) << 8
converted_samples[i] = i16(new_sample)
}
for {
frames := alsa.pcm_writei(handle, raw_data(converted_samples), u64(len(flac_data.samples)) / u64(flac_data.metadata.channels))
if frames < 0 {
fmt.eprintln("Error writing to PCM device:", alsa.strerror(auto_cast frames))
break
}
fmt.printfln("Played %d frames", frames)
}
alsa_err = alsa.pcm_drain(handle)
if alsa_err < 0 {
fmt.eprintln("pcm_drain failed with:", alsa.strerror(alsa_err))
}
alsa.pcm_close(handle)
elapsed := time.diff(start, end)
fmt.println("Decoding took", elapsed)
}
+81
View File
@@ -0,0 +1,81 @@
package main
import "core:os"
import "core:fmt"
import "core:mem"
import "core:mem/virtual"
import "core:time"
import "shared:alsa"
import "shared:flac"
DEVICE :: "default"
main :: proc() {
if len(os.args) < 2 {
fmt.eprintln("Please provide a path to a flac file.")
os.exit(1)
}
handle: ^alsa.pcm_t
arena: virtual.Arena
arena_err := virtual.arena_init_growing(&arena, mem.Megabyte * 8)
if arena_err != nil {
fmt.eprintln("Failed to alloc memory")
return
}
allocator := virtual.arena_allocator(&arena)
alsa_err := alsa.pcm_open(&handle, DEVICE, .PLAYBACK, .BLOCK)
if alsa_err < 0 {
fmt.eprintln("Failed to initialize audio device: ", alsa.strerror(alsa_err))
os.exit(1)
}
start := time.now()
flac_data, err := flac.load_from_file(os.args[1], allocator = allocator)
if err != nil {
fmt.eprintln(err)
if err == .MD5_Mismatch {
fmt.printfln("Expected: %v, got: %v", flac_data.metadata.expected_md5, flac_data.metadata.calculated_md5)
}
os.exit(1)
}
defer flac.destroy(flac_data, allocator = allocator)
end := time.now()
fmt.println(flac_data.metadata)
alsa_err = alsa.pcm_set_params(handle, .S16_LE, .RW_INTERLEAVED, u32(flac_data.metadata.channels), flac_data.metadata.sample_rate, 1, 500000)
if alsa_err < 0 {
fmt.eprintln("Playback open error:", alsa.strerror(alsa_err))
os.exit(1)
}
converted_samples := make([]i16, len(flac_data.samples))
for sample, i in flac_data.samples {
new_sample := sample & 0xFF
new_sample |= ((sample >> 8) & 0xFF) << 8
converted_samples[i] = i16(new_sample)
}
for {
frames := alsa.pcm_writei(handle, raw_data(converted_samples), u64(len(flac_data.samples)) / u64(flac_data.metadata.channels))
if frames < 0 {
fmt.eprintln("Error writing to PCM device:", alsa.strerror(auto_cast frames))
break
}
fmt.printfln("Played %d frames", frames)
}
alsa_err = alsa.pcm_drain(handle)
if alsa_err < 0 {
fmt.eprintln("pcm_drain failed with:", alsa.strerror(alsa_err))
}
alsa.pcm_close(handle)
elapsed := time.diff(start, end)
fmt.println("Decoding took", elapsed)
}
+517 -154
View File
@@ -1,6 +1,7 @@
package flac package flac
import "core:bytes" import "core:bytes"
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"
@@ -20,7 +21,6 @@ FlacError :: enum {
Missing_StreamInfo, Missing_StreamInfo,
Missing_Lead_Out_Track, Missing_Lead_Out_Track,
Invalid_Coefficient_Precision, Invalid_Coefficient_Precision,
Metadata_Block_Length_Mismatch,
CRC_Mismatch, CRC_Mismatch,
MD5_Mismatch, MD5_Mismatch,
Unsupported_BPS, // Remove when implemented Unsupported_BPS, // Remove when implemented
@@ -36,17 +36,12 @@ Option:
`.skip_md5_check` `.skip_md5_check`
Skips checking the MD5 hash of the decoded sample data. Skips checking the MD5 hash of the decoded sample data.
Offers a slight performance increase but will not error if the data is incorrect. Offers a slight performance increase but will not error if the data is incorrect.
`.only_return_picture_data`
Returns the embedded picture metadata and encoded pixel data (if available)
`.only_return_metadata` includes this.
`.only_return_metadata` `.only_return_metadata`
Returns all metadata of a FLAC stream without decoding the audio data. Returns all metadata of a FLAC stream without decoding the audio data.
*/ */
// TODO: Implement these
Option :: enum { Option :: enum {
skip_md5_check, skip_md5_check,
only_return_picture_data,
only_return_metadata, only_return_metadata,
} }
@@ -77,9 +72,8 @@ PictureType :: enum u32 {
} }
Flac :: struct { Flac :: struct {
// TODO: Other stuff
metadata: FlacMetadata, metadata: FlacMetadata,
samples: []i32, samples: [dynamic]i32,
} }
FlacMetadata :: struct { FlacMetadata :: struct {
@@ -90,6 +84,8 @@ FlacMetadata :: struct {
expected_md5: [16]byte, expected_md5: [16]byte,
calculated_md5: [16]byte, calculated_md5: [16]byte,
pictures: []Picture, pictures: []Picture,
vendor_name: string,
vorbis_comments: []string,
} }
Picture :: struct { Picture :: struct {
@@ -103,14 +99,17 @@ Picture :: struct {
data: []byte, data: []byte,
} }
Frame :: struct {
channels: u8,
sample_rate: u32,
samples: []i32,
}
@(private)
decode_subframe :: proc(r: ^Reader, bps: u8, block_size: u16) -> (samples: []i32, err: Error) #no_bounds_check { decode_subframe :: proc(r: ^Reader, bps: u8, block_size: u16) -> (samples: []i32, err: Error) #no_bounds_check {
// TODO: temp allocator?? so we copy the slice of data into the _actual_ sample buffer
// Maybe even a static arena with a size of 32MB by default (and configurable using #config) or something
// that we then use to allocate from (better perf is the reason because allocating on the heap for every subframe
// is probably noticibly slower)
subframe_samples := make([]i32, block_size) subframe_samples := make([]i32, block_size)
data := read_byte(r) or_return data := read_bits(r, 8) or_return
subframe_type := (data & 0x7E) >> 1 subframe_type := (data & 0x7E) >> 1
low_3_bits := subframe_type & 7 low_3_bits := subframe_type & 7
@@ -118,7 +117,7 @@ decode_subframe :: proc(r: ^Reader, bps: u8, block_size: u16) -> (samples: []i32
wasted_bits := 0 wasted_bits := 0
if has_wasted_bits { if has_wasted_bits {
for (read_bit(r) or_return) != 1 { for (read_bits(r, 1) or_return) != 1 {
wasted_bits += 1 wasted_bits += 1
} }
wasted_bits += 1 wasted_bits += 1
@@ -251,7 +250,7 @@ decode_subframe :: proc(r: ^Reader, bps: u8, block_size: u16) -> (samples: []i32
for s in 0..<num_samples { for s in 0..<num_samples {
quotient := 0 quotient := 0
// Read bits until we encounter a 1 (also called unary encoding). That's our quotient. // Read bits until we encounter a 1 (also called unary encoding). That's our quotient.
for (read_bit(r) or_return) != 1 { for (read_bits(r, 1) or_return) != 1 {
quotient += 1 quotient += 1
} }
remainder := int(read_bits(r, uint(rice_parameter)) or_return) remainder := int(read_bits(r, uint(rice_parameter)) or_return)
@@ -352,7 +351,7 @@ decode_subframe :: proc(r: ^Reader, bps: u8, block_size: u16) -> (samples: []i32
for s in 0..<num_samples { for s in 0..<num_samples {
quotient := 0 quotient := 0
// Read bits until we encounter a 1 (also called unary encoding). That's our quotient. // Read bits until we encounter a 1 (also called unary encoding). That's our quotient.
for (read_bit(r) or_return) != 1 { for (read_bits(r, 1) or_return) != 1 {
quotient += 1 quotient += 1
} }
remainder := int(read_bits(r, uint(rice_parameter)) or_return) remainder := int(read_bits(r, uint(rice_parameter)) or_return)
@@ -395,22 +394,29 @@ decode_subframe :: proc(r: ^Reader, bps: u8, block_size: u16) -> (samples: []i32
return subframe_samples, nil return subframe_samples, nil
} }
// TODO: remove @(private)
frame_num := 0
decode_frame :: proc( decode_frame :: proc(
r: ^Reader, r: ^Reader,
flac: ^Flac, flac: ^Flac,
md5_ctx: ^md5.Context,
options: Options,
) -> ( ) -> (
err: Error, err: Error,
) #no_bounds_check { ) #no_bounds_check {
// //
// Frame Header // Frame Header
// //
frame_beginning := r.i tee_r: io.Tee_Reader
crc_buffer: bytes.Buffer
//fmt.println("STARTING FRAME", frame_num) bytes.buffer_init_allocator(&crc_buffer, 0, 128)
//fmt.println("FRAME START AT OFFSET:", r.i) defer bytes.buffer_destroy(&crc_buffer)
crc := bytes.buffer_to_stream(&crc_buffer)
r := &Reader{
r = io.tee_reader_init(&tee_r, r, crc),
buf = r.buf,
x = r.x,
n = r.n,
}
data := read_data(r, u16be) or_return data := read_data(r, u16be) or_return
@@ -451,7 +457,7 @@ decode_frame :: proc(
case 2..=5: case 2..=5:
actual_block_size = 576 * auto_cast (pow2(block_size - 2)) actual_block_size = 576 * auto_cast (pow2(block_size - 2))
case 6: case 6:
actual_block_size = auto_cast (read_byte(r) or_return) + 1 actual_block_size = auto_cast (read_bits(r, 8) or_return) + 1
case 7: case 7:
actual_block_size = (read_data(r, u16be) or_return) + 1 actual_block_size = (read_data(r, u16be) or_return) + 1
case 8..=15: case 8..=15:
@@ -491,7 +497,7 @@ decode_frame :: proc(
case ._96kHz: case ._96kHz:
actual_sample_rate_in_Hz = 96000 actual_sample_rate_in_Hz = 96000
case .USE_8_BITS_IN_kHz_FROM_HEADER_END: case .USE_8_BITS_IN_kHz_FROM_HEADER_END:
actual_sample_rate_in_Hz = auto_cast (read_byte(r) or_return) * 1000 actual_sample_rate_in_Hz = auto_cast (read_bits(r, 8) or_return) * 1000
case .USE_16_BITS_IN_Hz_FROM_HEADER_END: case .USE_16_BITS_IN_Hz_FROM_HEADER_END:
actual_sample_rate_in_Hz = auto_cast (read_data(r, u16be) or_return) actual_sample_rate_in_Hz = auto_cast (read_data(r, u16be) or_return)
case .USE_16_BITS_IN_TENS_OF_Hz_FROM_HEADER_END: case .USE_16_BITS_IN_TENS_OF_Hz_FROM_HEADER_END:
@@ -504,16 +510,9 @@ decode_frame :: proc(
// TODO: sample rate MUST NOT be 0 if the subframe contains audio. // TODO: sample rate MUST NOT be 0 if the subframe contains audio.
// a sample rate of 0 MAY be used when non-audio is represented. // a sample rate of 0 MAY be used when non-audio is represented.
frame_header_end := r.i crc8_bytes := bytes.buffer_to_bytes(&crc_buffer)
frame_header_crc := u8(read_bits(r, 8) or_return)
frame_header_crc := read_byte(r) or_return calculated_header_crc := calculate_crc8(crc8_bytes)
calculated_header_crc := calculate_crc8(r.s[frame_beginning:frame_header_end])
//fmt.printfln("block size bits: 0b%b", block_size)
//fmt.printfln("actual block size: %d sample(s)", actual_block_size)
//fmt.println("sample rate bits:", sample_rate)
//fmt.printfln("actual sample rate: %dHz", actual_sample_rate_in_Hz)
//fmt.println("channel assignment:", channel_assignment)
if frame_header_crc != calculated_header_crc { if frame_header_crc != calculated_header_crc {
fmt.printfln("Expected frame header CRC: 0x%X, got: 0x%X", frame_header_crc, calculated_header_crc) fmt.printfln("Expected frame header CRC: 0x%X, got: 0x%X", frame_header_crc, calculated_header_crc)
@@ -576,7 +575,6 @@ decode_frame :: proc(
// so many questions... // so many questions...
} }
// TODO: remember to clean this up
subframes := make([][]i32, frame_channels) subframes := make([][]i32, frame_channels)
defer delete(subframes) defer delete(subframes)
for i in 0..<frame_channels { for i in 0..<frame_channels {
@@ -594,6 +592,59 @@ decode_frame :: proc(
align_to_byte(r) align_to_byte(r)
correlate(subframes, channel_assignment)
// Write the samples interleaved.
for i in 0..<len(subframes[0]) {
for subframe, sf in subframes {
sample := subframe[i]
append(&flac.samples, sample)
if .skip_md5_check not_in options {
switch bps {
case 1..=8:
md5.update(md5_ctx, {u8(sample)})
case 9..=16:
first_byte := u8(sample & 0xFF)
second_byte := u8((sample >> 8) & 0xFF)
md5.update(md5_ctx, {first_byte, second_byte})
case 17..=24:
first_byte := u8(sample & 0xFF)
second_byte := u8((sample >> 8) & 0xFF)
third_byte := u8((sample >> 16) & 0xFF)
md5.update(md5_ctx, {first_byte, second_byte, third_byte})
case 25..=32:
data := transmute([4]u8)sample
md5.update(md5_ctx, data[:])
case:
fmt.println("INVALID BPS")
os.exit(1)
}
}
}
}
for subframe in subframes {
delete(subframe)
}
//
// Frame Footer
//
crc16_bytes := bytes.buffer_to_bytes(&crc_buffer)
frame_crc := u16(read_data(r, u16be) or_return)
calculated_frame_crc := calculate_crc16(crc16_bytes)
if frame_crc != calculated_frame_crc {
fmt.eprintfln("Expected frame CRC 0x%X, got 0x%X", frame_crc, calculated_frame_crc)
return .CRC_Mismatch
}
return nil
}
@(private)
correlate :: proc(subframes: [][]i32, channel_assignment: ChannelAssignment) {
// Undo Stereo Decorrelation // Undo Stereo Decorrelation
#partial switch channel_assignment { #partial switch channel_assignment {
case .STEREO_MID_SIDE: case .STEREO_MID_SIDE:
@@ -625,67 +676,11 @@ decode_frame :: proc(
subframes[1][i] = left - side subframes[1][i] = left - side
} }
} }
// TODO: Maybe this is slow if we have a lot of samples??
// The cases are ranges because streaminfo can define unusual bits per sample
for i in 0..<len(subframes[0]) {
for subframe in subframes {
sample := subframe[i]
switch bps {
case 1..=8:
md5.update(&md5_ctx, {u8(sample)})
case 9..=16:
first_byte := u8(sample & 0xFF)
second_byte := u8((sample >> 8) & 0xFF)
md5.update(&md5_ctx, {first_byte, second_byte})
case 17..=24:
first_byte := u8(sample & 0xFF)
second_byte := u8((sample >> 8) & 0xFF)
third_byte := u8((sample >> 16) & 0xFF)
md5.update(&md5_ctx, {first_byte, second_byte, third_byte})
case 25..=32:
data := transmute([4]u8)sample
md5.update(&md5_ctx, data[:])
case:
fmt.println("INVALID BPS")
os.exit(1)
}
}
}
// TODO: We need a way to copy (or better yet just point to) the decoded sample data
// We'll probably either copy each subframe's samples somehow, or have a dynamic array and append to that.
// Both are slow-ish but I can't think of a faster way that just works.
// This doesn't work btw, just for my future self to not think this is fine.
for subframe in subframes {
copy(flac.samples, subframe)
}
//
// Frame Footer
//
frame_end := r.i
frame_crc := read_data(r, u16be) or_return
calculated_frame_crc := calculate_crc16(r.s[frame_beginning:frame_end])
if auto_cast frame_crc != calculated_frame_crc {
fmt.eprintfln("Expected frame CRC 0x%X, got 0x%X", frame_crc, calculated_frame_crc)
return .CRC_Mismatch
}
frame_num += 1
return nil
} }
load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (flac: ^Flac, err: Error) { @(private)
r: Reader read_metadata :: proc(r: ^Reader, buffered := false, allocator := context.allocator) -> (flac: ^Flac, err: Error) {
bytes.reader_init(&r, data) header := read_data(r, FlacHeader) or_return
md5.init(&md5_ctx)
defer md5.reset(&md5_ctx)
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
} }
@@ -718,7 +713,7 @@ load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context
pictures := make([dynamic]Picture, 0, 2, allocator) pictures := make([dynamic]Picture, 0, 2, allocator)
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
metadata_block_header := MetadataBlockHeader { metadata_block_header := MetadataBlockHeader {
last_block = bool(md_block_hdr >> 31), last_block = bool(md_block_hdr >> 31),
type = BlockType((md_block_hdr >> 24) & 0x7F), type = BlockType((md_block_hdr >> 24) & 0x7F),
@@ -731,70 +726,95 @@ load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context
case .STREAMINFO: case .STREAMINFO:
// Do nothing, we already parse the mandatory streaminfo metadata // Do nothing, we already parse the mandatory streaminfo metadata
case .PADDING: case .PADDING:
//fmt.printfln("Skipping %d bytes of padding data", metadata_block_header.length) if buffered {
bytes.reader_seek(&r, auto_cast metadata_block_header.length, .Current) reader := cast(^bufio.Reader)r.data
skipped := bufio.reader_discard(reader, int(metadata_block_header.length)) or_return
} else {
reader := cast(^bytes.Reader)r.data
skipped := bytes.reader_seek(reader, i64(metadata_block_header.length), .Current) or_return
}
case .APPLICATION: case .APPLICATION:
//app_id := read_slice(&r, 4) or_return //app_id := read_slice(&r, 4) or_return
//fmt.println("Found application with ID:", string(app_id)) //fmt.println("Found application with ID:", string(app_id))
//fmt.printfln("Skipping %d bytes of application metadata block", metadata_block_header.length) //fmt.printfln("Skipping %d bytes of application metadata block", metadata_block_header.length)
bytes.reader_seek(&r, auto_cast metadata_block_header.length, .Current)
// TODO: handle the application data // TODO: handle the application data
if buffered {
reader := cast(^bufio.Reader)r.data
skipped := bufio.reader_discard(reader, int(metadata_block_header.length)) or_return
} else {
reader := cast(^bytes.Reader)r.data
skipped := bytes.reader_seek(reader, i64(metadata_block_header.length), .Current) or_return
}
case .SEEKTABLE: case .SEEKTABLE:
// TODO: check for size mismatch?
seekpoints := metadata_block_header.length / 18 seekpoints := metadata_block_header.length / 18
//fmt.printfln("we have %d seekpoints", seekpoints) //fmt.printfln("we have %d seekpoints", seekpoints)
actual_size := 0
for i in 0..<seekpoints { for i in 0..<seekpoints {
_ = read_data(&r, Seekpoint) or_return _ = read_data(r, Seekpoint) or_return
actual_size += size_of(Seekpoint)
//fmt.println(seekpoint) //fmt.println(seekpoint)
} }
if metadata_block_header.length != u32(actual_size) {
fmt.eprintln("Seektable metadata header length mismatch!")
fmt.eprintfln("Expected size: %d, Got %d", metadata_block_header.length, actual_size)
}
case .VORBIS_COMMENT: case .VORBIS_COMMENT:
// TODO: save this info to a struct 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 := string(read_slice(&r, auto_cast vendor_name_len) or_return) read_slice(r, vendor_name_slice) or_return
user_comment_list_len := read_data(&r, u32le) 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 actual_size := 4 + vendor_name_len + 4
comments := make([]string, user_comment_list_len)
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 := string(read_slice(&r, auto_cast comment_len) or_return) comment_slice := make([]byte, comment_len)
//fmt.println(comment) 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) {
// TODO: should this be a warning instead of an error that halts the decoding? fmt.eprintln("Vorbis metadata header length mismatch!")
// I think so, the comment isn't crucial to the decoding process. fmt.eprintfln("Expected size: %d, Got %d", metadata_block_header.length, actual_size)
//fmt.println("Metadata header length mismatch!")
//fmt.printfln("Expected size: %d, Got %d", metadata_block_header.length, actual_size)
return flac, .Metadata_Block_Length_Mismatch
} }
// TODO: framing bit?? // TODO: framing bit??
//fmt.println(vendor_name)
case .CUESHEET: case .CUESHEET:
// TODO: save this to a struct // TODO: save this to a struct
// + there's some CD-DA stuff that we need to consider I think // + there's some CD-DA stuff that we need to consider I think
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 flac, .Missing_Lead_Out_Track
} }
//fmt.println(cuesheet) //fmt.println(cuesheet)
for i in 0..<cuesheet.num_tracks { for i in 0..<cuesheet.num_tracks {
track := read_data(&r, CueSheetTrack) or_return track := read_data(r, CueSheetTrack) or_return
for i in 0..<track.num_track_index_points { for i in 0..<track.num_track_index_points {
_ = read_data(&r, CueSheetTrackIndex) or_return _ = read_data(r, CueSheetTrackIndex) or_return
//fmt.println(track_index) //fmt.println(track_index)
} }
//fmt.println(track) //fmt.println(track)
} }
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
mimetype := string(read_slice(&r, auto_cast mime_len) or_return) mimetype_slice := make([]byte, mime_len)
desc_len := read_data(&r, u32be) or_return read_slice(r, mimetype_slice) or_return
desc := string(read_slice(&r, auto_cast desc_len) or_return) mimetype := string(mimetype_slice)
metadata := read_data(&r, PictureMetadata) or_return desc_len := read_data(r, u32be) or_return
data := read_slice(&r, auto_cast metadata.data_len) or_return desc_slice := make([]byte, desc_len)
read_slice(r, desc_slice) or_return
desc := string(desc_slice)
metadata := read_data(r, PictureMetadata) or_return
data := make([]byte, metadata.data_len)
read_slice(r, data) or_return
picture := Picture{ picture := Picture{
type = pic_type, type = pic_type,
mimetype = mimetype, mimetype = mimetype,
@@ -808,40 +828,334 @@ load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context
append(&pictures, picture) append(&pictures, picture)
case .INVALID: case .INVALID:
fmt.eprintln("Invalid block type") fmt.eprintln("Invalid block type")
bytes.reader_seek(&r, auto_cast metadata_block_header.length, .Current) if buffered {
reader := cast(^bufio.Reader)r.data
skipped := bufio.reader_discard(reader, int(metadata_block_header.length)) or_return
} else {
reader := cast(^bytes.Reader)r.data
skipped := bytes.reader_seek(reader, i64(metadata_block_header.length), .Current) or_return
}
case: case:
fmt.printfln("Skipping %d bytes of Unknown metadata block", metadata_block_header.length) fmt.printfln("Skipping %d bytes of Unknown metadata block", metadata_block_header.length)
bytes.reader_seek(&r, auto_cast metadata_block_header.length, .Current) if buffered {
reader := cast(^bufio.Reader)r.data
skipped := bufio.reader_discard(reader, int(metadata_block_header.length)) or_return
} else {
reader := cast(^bytes.Reader)r.data
skipped := bytes.reader_seek(reader, i64(metadata_block_header.length), .Current) or_return
} }
} }
// Yucky
err = decode_frame(&r, flac)
for err == nil {
err = decode_frame(&r, flac)
} }
flac.metadata.pictures = pictures[:] flac.metadata.pictures = pictures[:]
return flac, nil
}
read_next_frame :: proc(r: ^Reader, flac: ^Flac) -> (frame: Frame, err: Error) {
//
// Frame Header
//
tee_r: io.Tee_Reader
crc_buffer: bytes.Buffer
bytes.buffer_init_allocator(&crc_buffer, 0, 128)
defer bytes.buffer_destroy(&crc_buffer)
crc := bytes.buffer_to_stream(&crc_buffer)
r := &Reader{
r = io.tee_reader_init(&tee_r, r, crc),
buf = r.buf,
x = r.x,
n = r.n,
}
data := read_data(r, u16be) or_return
sync_code := data >> 2
blocking_strategy := BlockingStrategy(data & 1)
data = read_data(r, u16be) or_return
block_size := data >> 12
sample_rate := SampleRate((data >> 8) & 0xF)
channel_assignment := ChannelAssignment((data >> 4) & 0xF)
sample_size := SampleSize((data >> 1) & 7)
coded_num := decode_extended_utf8(r) or_return
if blocking_strategy == .VARIABLE {
// Sample number of first sample
// MUST NOT be larger than 36 bits unencoded or 7 bytes encoded.
// MUST be equal to the number of samples preceding the current frame. Otherwise seeking is not possible.
// TODO: also write a check for the number of samples so we'll probably keep track of how many samples we decoded
// maybe the number of decoded samples can be inferred by the number of frames ?? or just use the length of
// the decoded_samples slice.
if coded_num > 0x1000000000 {
// TODO: Sample number can't be bigger than 36 bits
}
} else {
// Frame number
// MUST NOT be larger than 31 bits unencoded or 6 bytes encoded.
// MUST be equal to the number of frames preceding the current frame. Otherwise seeking is not possible.
// TODO: also write a check for the number of frames so we'll probably keep track of how many frames we decoded.
if coded_num > 0x80000000 {
// TODO: Frame number cannot be bigger than 31 bits
}
}
actual_block_size: u16be
switch block_size {
case 1:
actual_block_size = 192
case 2..=5:
actual_block_size = 576 * auto_cast (pow2(block_size - 2))
case 6:
actual_block_size = auto_cast (read_bits(r, 8) or_return) + 1
case 7:
actual_block_size = (read_data(r, u16be) or_return) + 1
case 8..=15:
actual_block_size = 256 * auto_cast (pow2(block_size - 8))
case:
return {}, .Invalid_Block_Size
}
//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.
//}
actual_sample_rate_in_Hz: u32
switch sample_rate {
case .USE_STREAMINFO:
actual_sample_rate_in_Hz = flac.metadata.sample_rate
case ._88_2kHz:
actual_sample_rate_in_Hz = 88200
case ._176_4kHz:
actual_sample_rate_in_Hz = 176400
case ._192kHz:
actual_sample_rate_in_Hz = 192000
case ._8kHz:
actual_sample_rate_in_Hz = 8000
case ._16kHz:
actual_sample_rate_in_Hz = 16000
case ._22_05kHz:
actual_sample_rate_in_Hz = 22005
case ._24kHz:
actual_sample_rate_in_Hz = 24000
case ._32kHz:
actual_sample_rate_in_Hz = 32000
case ._44_1kHz:
actual_sample_rate_in_Hz = 44100
case ._48kHz:
actual_sample_rate_in_Hz = 48000
case ._96kHz:
actual_sample_rate_in_Hz = 96000
case .USE_8_BITS_IN_kHz_FROM_HEADER_END:
actual_sample_rate_in_Hz = auto_cast (read_bits(r, 8) or_return) * 1000
case .USE_16_BITS_IN_Hz_FROM_HEADER_END:
actual_sample_rate_in_Hz = auto_cast (read_data(r, u16be) or_return)
case .USE_16_BITS_IN_TENS_OF_Hz_FROM_HEADER_END:
actual_sample_rate_in_Hz = auto_cast (read_data(r, u16be) or_return) * 10
case .INVALID: fallthrough
case:
return {}, .Invalid_Sample_Rate
}
// TODO: sample rate MUST NOT be 0 if the subframe contains audio.
// a sample rate of 0 MAY be used when non-audio is represented.
crc8_bytes := bytes.buffer_to_bytes(&crc_buffer)
frame_header_crc := u8(read_bits(r, 8) or_return)
calculated_header_crc := calculate_crc8(crc8_bytes)
if frame_header_crc != calculated_header_crc {
fmt.printfln("Expected frame header CRC: 0x%X, got: 0x%X", frame_header_crc, calculated_header_crc)
return {}, .CRC_Mismatch
}
//
// Subframes
//
bps: u8
switch sample_size {
case .USE_STREAMINFO:
bps = flac.metadata.bits_per_sample
case ._8BPS:
bps = 8
case ._12BPS:
bps = 12
case ._16BPS:
bps = 16
case ._20BPS:
bps = 20
case ._24BPS:
bps = 24
case ._32BPS:
bps = 32
}
if bps != flac.metadata.bits_per_sample {
return {}, .Bits_Per_Second_Mismatch
}
frame_channels := flac.metadata.channels
switch channel_assignment {
case .MONO:
frame_channels = 1
case ._3CHANNELS:
frame_channels = 3
case ._4CHANNELS:
frame_channels = 4
case ._5CHANNELS:
frame_channels = 5
case ._6CHANNELS:
frame_channels = 6
case ._7CHANNELS:
frame_channels = 7
case ._8CHANNELS:
frame_channels = 8
case ._2CHANNELS: fallthrough
case .STEREO_LEFT_SIDE: fallthrough
case .STEREO_SIDE_RIGHT: fallthrough
case .STEREO_MID_SIDE:
frame_channels = 2
}
if frame_channels != flac.metadata.channels {
// TODO: warning or error?
// we have a faulty file that says it has 5 channels but subframes all say they got 1
// we also have an two uncommon files with increasing and decreasing number of channels
// uncommon are supposed to be unusual but still valid FLAC files. why is that first one faulty then???
// so many questions...
}
subframes := make([][]i32, frame_channels)
defer delete(subframes)
for i in 0..<frame_channels {
frame_bps := bps
// For side channels we increase the bps by 1
// Left, mid and right channels don't need an extra bit.
// ref: https://github.com/ietf-wg-cellar/flac-specification/blob/master/rfc_backmatter.md#first-audio-frame
if (channel_assignment == .STEREO_SIDE_RIGHT && i == 0) ||
((channel_assignment == .STEREO_LEFT_SIDE || channel_assignment == .STEREO_MID_SIDE) && i == 1) {
frame_bps += 1
}
subframes[i] = decode_subframe(r, frame_bps, auto_cast actual_block_size) or_return
}
align_to_byte(r)
correlate(subframes, channel_assignment)
samples_arr := make([dynamic]i32, 0, block_size * 2)
// Write the samples interleaved.
for i in 0..<len(subframes[0]) {
for subframe, sf in subframes {
sample := subframe[i]
append(&samples_arr, sample)
}
}
for subframe in subframes {
delete(subframe)
}
frame.samples = samples_arr[:]
frame.channels = frame_channels
frame.sample_rate = actual_sample_rate_in_Hz
//
// Frame Footer
//
crc16_bytes := bytes.buffer_to_bytes(&crc_buffer)
frame_crc := u16(read_data(r, u16be) or_return)
calculated_frame_crc := calculate_crc16(crc16_bytes)
if frame_crc != calculated_frame_crc {
fmt.eprintfln("Expected frame CRC 0x%X, got 0x%X", frame_crc, calculated_frame_crc)
return {}, .CRC_Mismatch
}
return frame, nil
}
md5hash :: proc(md5_ctx: ^md5.Context, bps: u8, samples: []i32) {
for sample in samples {
switch bps {
case 1..=8:
md5.update(md5_ctx, {u8(sample)})
case 9..=16:
first_byte := u8(sample & 0xFF)
second_byte := u8((sample >> 8) & 0xFF)
md5.update(md5_ctx, {first_byte, second_byte})
case 17..=24:
first_byte := u8(sample & 0xFF)
second_byte := u8((sample >> 8) & 0xFF)
third_byte := u8((sample >> 16) & 0xFF)
md5.update(md5_ctx, {first_byte, second_byte, third_byte})
case 25..=32:
data := transmute([4]u8)sample
md5.update(md5_ctx, data[:])
case:
fmt.println("INVALID BPS")
os.exit(1)
}
}
}
md5sum :: proc(md5_ctx: ^md5.Context, flac: ^Flac) -> Error {
calculated_md5: [16]byte
md5.final(md5_ctx, calculated_md5[:])
flac.metadata.calculated_md5 = calculated_md5
if mem.compare(calculated_md5[:], flac.metadata.expected_md5[:]) != 0 {
return .MD5_Mismatch
}
return nil
}
// `load_from_bytes` takes a byte slice and initializes a bitreader for it.
// The data is expected to be a full flac stream.
load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (flac: ^Flac, err: Error) {
context.allocator = allocator
byte_r: bytes.Reader
bytes.reader_init(&byte_r, data)
r := Reader{bytes.reader_to_stream(&byte_r), 0, 0, 0}
flac = read_metadata(&r) or_return
if .only_return_metadata in options {
return flac, nil
}
md5_ctx: md5.Context
md5.init(&md5_ctx)
defer md5.reset(&md5_ctx)
for {
err = decode_frame(&r, flac, &md5_ctx, options)
if err != nil {
if err == .EOF {
break
} else {
return flac, err
}
}
}
if err != .EOF { if err != .EOF {
return flac, err return flac, err
} }
// if .skip_md5_check not_in options {
// Validate the decoded audio data's md5 md5sum(&md5_ctx, flac) or_return
//
calculated_md5: [16]byte
md5.final(&md5_ctx, calculated_md5[:])
flac.metadata.calculated_md5 = calculated_md5
if mem.compare(calculated_md5[:], header.streaminfo.md5[:]) != 0 {
return flac, .MD5_Mismatch
} }
return flac, nil return flac, nil
} }
// `load_from_file` reads the entire file to memory and operates on the returned byte slice.
// Only use this with small files.
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (flac: ^Flac, err: Error) { load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (flac: ^Flac, err: Error) {
context.allocator = allocator context.allocator = allocator
@@ -849,22 +1163,71 @@ load_from_file :: proc(filename: string, options := Options{}, allocator := cont
defer delete(data) defer delete(data)
if ok { if ok {
return load_from_bytes(data) return load_from_bytes(data, options)
} else { } else {
return nil, .Unable_To_Read_File return nil, .Unable_To_Read_File
} }
} }
destroy :: proc(flac: ^Flac) {
// TODO: clean up the allocations // `load_from_file_buffered` initializes a buffered reader for the file and reads all the metadata blocks.
delete(flac.samples) // Calls to `read_next_frame()` MUST be made to decode the audio data.
// TODO: I think this data gets deleted when the data from os.read_entire_file is deleted. // This is the recommended way to read most FLAC files as it has a minimal memory overhead.
// Double check that and if it's true then make sure to copy this data. load_from_file_buffered :: proc(filename: string, allocator := context.allocator) -> (flac: ^Flac, r: ^Reader, err: Error) {
// Copying small strings is fine but copying the image pixel data might be slow. context.allocator = allocator
//for picture in flac.metadata.pictures {
// picture.mimetype file, open_err := os.open(filename)
// picture.data if open_err != os.ERROR_NONE {
// picture.description fmt.eprintln(open_err)
//} return nil, {}, .Unable_To_Read_File
delete(flac.metadata.pictures) }
br := new(bufio.Reader)
bufio.reader_init(br, os.stream_from_handle(file))
r = new(Reader)
r.r = bufio.reader_to_stream(br)
r.buf = 0
r.x = 0
r.n = 0
flac = read_metadata(r, true) or_return
return flac, r, nil
}
/* `destroy` cleans up and frees the memory allocated by all 3 loading functions.
The returned reader from `load_from_file_buffered` MUST be passed to `destroy` in order to close the file handle.
`load_from_file` and `load_from_bytes` may pass nil as the reader.
---
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.
*/
destroy :: proc(flac: ^Flac, r: ^Reader = nil, allocator := context.allocator) {
context.allocator = allocator
delete(flac.samples)
for picture in flac.metadata.pictures {
delete(picture.mimetype)
delete(picture.data)
delete(picture.description)
}
delete(flac.metadata.pictures)
for comment in flac.metadata.vorbis_comments {
delete(comment)
}
delete(flac.metadata.vorbis_comments)
delete(flac.metadata.vendor_name)
free(flac)
if r != nil {
br := cast(^bufio.Reader)r.data
closer, ok := io.to_closer(br.rd)
io.close(closer)
bufio.reader_destroy(br)
free(br)
free(r)
}
} }
+2 -7
View File
@@ -79,11 +79,6 @@ crc16_lookup := [256]u16 {
fixed_coefficients := [4][]i32{{1}, {2, -1}, {3, -3, 1}, {4, -6, 4, -1}} fixed_coefficients := [4][]i32{{1}, {2, -1}, {3, -3, 1}, {4, -6, 4, -1}}
// TODO: no idea if I want to keep this global and a thread_local
// or if I should just init it in load_from_bytes
@(thread_local)
md5_ctx: md5.Context
BlockType :: enum u16be { BlockType :: enum u16be {
STREAMINFO, STREAMINFO,
PADDING, PADDING,
@@ -253,7 +248,7 @@ decode_extended_utf8 :: proc(r: ^Reader) -> (decoded_num: u64, err: Error) {
decoded_num = u64(0) decoded_num = u64(0)
first_byte := read_byte(r) or_return first_byte := read_bits(r, 8) or_return
utf8_sequence_len := 0 utf8_sequence_len := 0
for i: uint = 7; i > 0; i -= 1 { for i: uint = 7; i > 0; i -= 1 {
@@ -269,7 +264,7 @@ decode_extended_utf8 :: proc(r: ^Reader) -> (decoded_num: u64, err: Error) {
octets := make([dynamic]byte, 0, 8) octets := make([dynamic]byte, 0, 8)
defer delete(octets) defer delete(octets)
for i := utf8_sequence_len - 1; i > 0; i -= 1 { for i := utf8_sequence_len - 1; i > 0; i -= 1 {
append(&octets, read_byte(r) or_return) append(&octets, byte(read_bits(r, 8) or_return))
} }
switch utf8_sequence_len { switch utf8_sequence_len {
+58 -92
View File
@@ -8,119 +8,85 @@ import "core:io"
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.
*/ */
Reader :: struct { Reader :: struct {
using _: bytes.Reader, using r: io.Stream,
bits_read: u8, // Temporary read buffer.
buf: [8]u8,
// Between 0 and 7 buffered bits since previous read operations.
x: u8,
// The number of buffered bits in x.
n: uint,
} }
read_slice :: #force_inline proc(r: ^Reader, size: int) -> (res: []u8, err: io.Error) { read_slice :: #force_inline proc(r: ^Reader, buf: []u8) -> io.Error {
#no_bounds_check { io.read_full(r^, buf) or_return
if r.i + i64(size) <= i64(len(r.s)) {
res = r.s[r.i:r.i + i64(size)]
r.i += i64(size)
return res, .None
}
}
if i64(len(r.s)) == r.i { return nil
return []u8{}, .EOF
} else {
return []u8{}, .Short_Buffer
}
} }
read_data :: #force_inline proc(r: ^Reader, $T: typeid) -> (res: T, err: io.Error) { read_data :: #force_inline proc(r: ^Reader, $T: typeid) -> (res: T, err: io.Error) {
b := read_slice(r, size_of(T)) or_return b: [size_of(T)]byte
read_slice(r, b[:]) or_return
return (^T)(&b[0])^, nil return (^T)(&b[0])^, nil
} }
read_bit :: proc(r: ^Reader) -> (bit: byte, err: Error) #no_bounds_check { // Read reads and returns the next n bits, at most 64. It buffers bits up to the
if r.i >= i64(len(r.s)) { // next byte boundary.
return 0, .EOF // Borrowed from https://github.com/mewkiz/flac
read_bits :: proc(r: ^Reader, n: uint) -> (res: u64, err: io.Error) #no_bounds_check {
if n == 0 {
return 0, nil
} }
bits := 8 - (r.bits_read + 1) n := n
b := (r.s[r.i] >> bits) & 1
r.bits_read += 1
if r.bits_read == 8 { // Read buffered bits.
r.i += 1 if r.n > 0 {
r.bits_read = 0 switch {
} case r.n == n:
r.n = 0
return b, nil return u64(r.x), nil
} case r.n > n:
r.n -= n
read_bits :: proc(r: ^Reader, n: uint) -> (bits: u64, err: Error) #no_bounds_check { mask := ~u8(0) << r.n
res: u64 res = u64(r.x&mask) >> r.n
r.x &~= mask
// Fast path: if we can read all bits within the current byte.
if r.bits_read + u8(n) <= 8 {
res = (u64(r.s[r.i]) >> (8 - r.bits_read - u8(n))) & ((1 << n) - 1)
r.bits_read += u8(n)
if r.bits_read == 8 {
r.bits_read = 0
r.i += 1
}
return res, nil return res, nil
} }
n -= r.n
// General case: reading across multiple bytes. res = u64(r.x)
rem_bits := n r.n = 0
if r.bits_read > 0 {
// Read the remaining bits in the current byte.
bits_in_byte := 8 - r.bits_read
res = u64(r.s[r.i]) & ((1 << bits_in_byte) - 1)
rem_bits -= uint(bits_in_byte)
r.bits_read = 0
r.i += 1
} }
// Read full bytes. // Fill the temporary buffer.
for rem_bits >= 8 { bytes := n / 8
res = (res << 8) | u64(r.s[r.i]) bits := n % 8
rem_bits -= 8 if bits > 0 {
r.i += 1 bytes += 1
} }
io.read_full(r, r.buf[:bytes]) or_return
// Read remaining bits in the final byte. // Read bits from the temporary buffer.
if rem_bits > 0 { for b in r.buf[:bytes-1] {
res = (res << rem_bits) | (u64(r.s[r.i]) >> (8 - rem_bits)) res <<= 8
r.bits_read += u8(rem_bits) res |= u64(b)
} }
b := r.buf[bytes-1]
return res, nil if bits > 0 {
} res <<= bits
r.n = 8 - bits
read_byte :: proc(r: ^Reader) -> (b: byte, err: Error) #no_bounds_check { mask := ~u8(0) << r.n
if r.i >= i64(len(r.s)) { res |= u64(b&mask) >> r.n
return 0, .EOF r.x = b & ~mask
}
// Continue reading from the current byte if it has unread bits.
if r.bits_read != 0 {
cur_byte := r.s[r.i]
next_byte := r.s[r.i + 1]
rem_bits := 8 - r.bits_read
rem_bits_mask: u8 = (1 << rem_bits) - 1
b = ((cur_byte & rem_bits_mask) << r.bits_read) | (next_byte >> rem_bits)
r.i += 1
} else { } else {
b = r.s[r.i] res <<= 8
r.i += 1 res |= u64(b)
} }
return b, nil
return res, nil
} }
align_to_byte :: proc(r: ^Reader) -> Error { align_to_byte :: proc(r: ^Reader) {
if r.bits_read == 0 { r.x = 0
return nil r.n = 0
}
if r.i >= i64(len(r.s)) {
return .EOF
}
// Align by skipping the remaining bits in the byte
r.bits_read = 0
r.i += 1
return nil
} }
File diff suppressed because it is too large Load Diff