mirror of
https://github.com/IllusionMan1212/flac-odin.git
synced 2026-10-24 18:12:29 +00:00
Compare commits
1
Commits
44b4e52063
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a0741320d
|
@@ -429,7 +429,16 @@ 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
|
||||||
@@ -453,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 {
|
||||||
@@ -467,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
|
||||||
@@ -506,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)
|
||||||
@@ -537,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 {
|
||||||
@@ -551,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,
|
||||||
@@ -877,25 +922,39 @@ load_from_file :: proc(filename: string, allocator := context.allocator) -> (fla
|
|||||||
file, open_err := os.open(filename)
|
file, open_err := os.open(filename)
|
||||||
if open_err != nil {
|
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.to_stream(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.
|
||||||
@@ -903,6 +962,9 @@ 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
|
||||||
|
|
||||||
|
// Tolerates a nil `flac` so a caller can `defer destroy(...)` straight after
|
||||||
|
// a load without first checking whether the load succeeded.
|
||||||
|
if flac != nil {
|
||||||
delete(flac.samples)
|
delete(flac.samples)
|
||||||
for picture in flac.metadata.pictures {
|
for picture in flac.metadata.pictures {
|
||||||
delete(picture.mimetype)
|
delete(picture.mimetype)
|
||||||
@@ -918,6 +980,7 @@ destroy :: proc(flac: ^Flac, r: ^Reader = nil, allocator := context.allocator) {
|
|||||||
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
|
||||||
|
|||||||
+4
-1
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user