Skip to content

Add optional FLAC compression #76

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 11, 2025
Merged

Add optional FLAC compression #76

merged 3 commits into from
Jul 11, 2025

Conversation

braheezy
Copy link
Contributor

@braheezy braheezy commented Jul 8, 2025

Hi! I added the ability for the encoder to optionally choose a prediction method for better compression. I'm not sure the API is great but it seems to work and allows this library to produce files smaller than their input, see the Results at the end.

Bonus: The change in frame.go may be inappropriate for this PR but it fixes an out-of-index error I was getting in wav2flac on all files.

How

Added an opt-in prediction analysis pass.

  • Encoder now has a boolean field AnalysisEnabled (helper EnablePredictionAnalysis(true)).
  • When the flag is off (default) the encoder behaves as before: it writes Constant / Verbatim frames exactly as provided by the caller. This guarantees the round-trip tests that decode an existing FLAC and immediately re-encode it still pass.
  • When the flag is on the encoder examines any sub-frame that is still marked PredVerbatim and chooses the smallest of:
    1. Constant predictor (all samples equal)
    2. Verbatim (no prediction)
    3. Fixed prediction. The new analyseFixed() picks the best order and Rice-k.

libFLAC exposes a range of parameters to tune the encoding process. This PR introduces one knob to try to pick the best prediction method.

Results

Here's what I got when enabling prediction analysis on various audio files:

File Original Size w/out analysis w/ analysis Reduction
forest_of_illusion.wav 6.7 MB 7.9 MB 4.1 MB 48%
yoshis_island.wav 4.8 MB 5.3 MB 2.6 MB 51%
vanilla_dome.wav 8.5 MB 9.8 MB 5.9 MB 40%

There's an expected increase in encoding resources:

Metric Before After Change
Time 1547070 ns/op 6727643 ns/op 4.35x slower
Memory 1101783 B/op 3449681 B/op 3.13x more
Allocations 244 allocs/op 704 allocs/op 2.89x more

The new enc_benchmark_test.go created these results.

Review

I'm open to feedback or questions, or if more tests are needed. I have no personal need for this, just thought it would be fun to try to enhance things a bit.

@mewmew
Copy link
Member

mewmew commented Jul 8, 2025

I'm open to feedback or questions, or if more tests are needed. I have no personal need for this, just thought it would be fun to try to enhance things a bit.

A labour born out of love and passion is the best <3

Really happy to see you implementing the compression for FLAC @braheezy! This has been a long awaited feature.

My brother and I are on summer holidays, going to the Swedish summer house today to have a Sauna : )

So, while we deeply appreciate the PR. Just giving a heads up that the review may take a while, as we are in vacation-mode.

Joyous summer wishes,
Henry & Robin

@mewmew mewmew merged commit 2290fe3 into mewkiz:master Jul 11, 2025
@mewmew
Copy link
Member

mewmew commented Jul 11, 2025

Really wonderful job with implementing encoding using Fixed prediction of samples!

I've merged the PR to main and done some stylistic changes.

The only issue I've identified is related to when trying to analyze FLAC files that have fewer than 2 samples in a subframe.

In such cases, the sf.RiceSubframe field is never initialized, since analyzeFixed has an early return before allocating the &frame.RiceSubframe{} struct.

Edit: fixed in d7eb6c1.

$ go test ./...
--- FAIL: TestEncode (0.09s)
    --- FAIL: TestEncode/testdata/191885.flac (0.03s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
	panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x5342fc]

goroutine 18 [running]:
testing.tRunner.func1.2({0x56f280, 0x6deb00})
	/usr/lib/go/src/testing/testing.go:1734 +0x21c
testing.tRunner.func1()
	/usr/lib/go/src/testing/testing.go:1737 +0x35e
panic({0x56f280?, 0x6deb00?})
	/usr/lib/go/src/runtime/panic.go:792 +0x132
github.com/mewkiz/flac.analyzeSubframe(0xc000070620, 0x10)
	/home/u/life/projects/github.com/mewkiz/flac/analysis_fixed.go:175 +0xbc
github.com/mewkiz/flac.(*Encoder).WriteFrame(0xc00014e0f0, 0xc0000705b0)
	/home/u/life/projects/github.com/mewkiz/flac/encode_frame.go:93 +0x8e6
github.com/mewkiz/flac_test.TestEncode.func1(0xc000082380)
	/home/u/life/projects/github.com/mewkiz/flac/enc_test.go:150 +0x331
testing.tRunner(0xc000082380, 0xc000110198)
	/usr/lib/go/src/testing/testing.go:1792 +0xf4
created by testing.(*T).Run in goroutine 5
	/usr/lib/go/src/testing/testing.go:1851 +0x413
FAIL	github.com/mewkiz/flac	0.097s
ok  	github.com/mewkiz/flac/frame	(cached)
ok  	github.com/mewkiz/flac/internal/bits	(cached)
ok  	github.com/mewkiz/flac/internal/bufseekio	(cached)
?   	github.com/mewkiz/flac/internal/hashutil	[no test files]
ok  	github.com/mewkiz/flac/internal/hashutil/crc16	(cached)
ok  	github.com/mewkiz/flac/internal/hashutil/crc8	(cached)
?   	github.com/mewkiz/flac/internal/ioutilx	[no test files]
?   	github.com/mewkiz/flac/internal/utf8	[no test files]
ok  	github.com/mewkiz/flac/meta	(cached)
FAIL

mewmew added a commit that referenced this pull request Jul 11, 2025
@mewmew mewmew mentioned this pull request Jul 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants