Skip to content

Commit 863451f

Browse files
committed
feat: add simple FLAC decoder
1 parent 0dd19a1 commit 863451f

File tree

9 files changed

+443
-28
lines changed

9 files changed

+443
-28
lines changed

.github/workflows/build_test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ jobs:
2222

2323
- name: Install Linux dependencies
2424
if: runner.os == 'Linux'
25-
run: sudo apt-get update && sudo apt-get install -y libasound2-dev libvorbis-dev libogg-dev
25+
run: sudo apt-get update && sudo apt-get install -y libasound2-dev libvorbis-dev libogg-dev libflac-dev
2626

2727
- name: Install macOS dependencies
2828
if: runner.os == 'macOS'
29-
run: brew install libogg libvorbis
29+
run: brew install libogg libvorbis flac
3030

3131
- name: Build daemon
3232
run: go build -v ./cmd/daemon

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
FROM alpine:3.20 AS build
22

3-
RUN apk -U --no-cache add go alsa-lib-dev libogg-dev libvorbis-dev gcc musl-dev
3+
RUN apk -U --no-cache add go alsa-lib-dev libogg-dev libvorbis-dev flac-dev gcc musl-dev
44

55
WORKDIR /src
66

cmd/daemon/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ func (app *App) newAppPlayer(ctx context.Context, creds any) (_ *AppPlayer, err
146146
Events: appPlayer.sess.Events(),
147147
Log: app.log,
148148

149+
FlacEnabled: app.cfg.FlacEnabled,
150+
149151
NormalisationEnabled: !app.cfg.NormalisationDisabled,
150152
NormalisationUseAlbumGain: app.cfg.NormalisationUseAlbumGain,
151153
NormalisationPregain: app.cfg.NormalisationPregain,
@@ -401,6 +403,7 @@ type Config struct {
401403
DisableAutoplay bool `koanf:"disable_autoplay"`
402404
ZeroconfInterfacesToAdvertise []string `koanf:"zeroconf_interfaces_to_advertise"`
403405
MprisEnabled bool `koanf:"mpris_enabled"`
406+
FlacEnabled bool `koanf:"flac_enabled"`
404407
Server struct {
405408
Enabled bool `koanf:"enabled"`
406409
Address string `koanf:"address"`

config_schema.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,11 @@
271271
"type": "boolean",
272272
"description": "Enables MPRIS communication with D-Bus",
273273
"default": false
274+
},
275+
"flac_enabled": {
276+
"type": "boolean",
277+
"description": "Whether FLAC files should be preferred over other formats when available",
278+
"default": false
274279
}
275280
},
276281
"required": [

flac/decoder.go

Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
package flac
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"unsafe"
8+
9+
librespot "github.com/devgianlu/go-librespot"
10+
)
11+
12+
// #cgo pkg-config: flac
13+
// #include <FLAC/stream_decoder.h>
14+
// #include <stdlib.h>
15+
//
16+
// typedef struct {
17+
// void* decoder;
18+
// } ClientData;
19+
//
20+
// static ClientData* allocateClientData() {
21+
// return (ClientData*)malloc(sizeof(ClientData));
22+
// }
23+
//
24+
// static void freeClientData(ClientData *data) {
25+
// free(data);
26+
// }
27+
//
28+
// static const char* getStreamDecoderErrorStatusString(FLAC__StreamDecoderErrorStatus status) {
29+
// return FLAC__StreamDecoderErrorStatusString[status];
30+
// }
31+
//
32+
// extern FLAC__StreamDecoderReadStatus callbackRead(
33+
// FLAC__StreamDecoder *decoder,
34+
// FLAC__byte *buffer,
35+
// size_t *bytes,
36+
// void *clientData
37+
// );
38+
// extern FLAC__StreamDecoderSeekStatus callbackSeek(
39+
// FLAC__StreamDecoder *decoder,
40+
// FLAC__uint64 absoluteByteOffset,
41+
// void *clientData
42+
// );
43+
// extern FLAC__StreamDecoderTellStatus callbackTell(
44+
// FLAC__StreamDecoder *decoder,
45+
// FLAC__uint64 *absoluteByteOffset,
46+
// void *clientData
47+
// );
48+
// extern FLAC__StreamDecoderLengthStatus callbackLength(
49+
// FLAC__StreamDecoder *decoder,
50+
// FLAC__uint64 *streamLength,
51+
// void *clientData
52+
// );
53+
// extern FLAC__bool callbackEof(
54+
// FLAC__StreamDecoder *decoder,
55+
// void *clientData
56+
// );
57+
// extern FLAC__StreamDecoderWriteStatus callbackWrite(
58+
// FLAC__StreamDecoder *decoder,
59+
// FLAC__Frame *frame,
60+
// FLAC__int32 *buffer[],
61+
// void *clientData
62+
// );
63+
// extern void callbackMetadata(
64+
// FLAC__StreamDecoder *decoder,
65+
// FLAC__StreamMetadata *metadata,
66+
// void *client
67+
// );
68+
// extern void callbackError(
69+
// FLAC__StreamDecoder *decoder,
70+
// FLAC__StreamDecoderErrorStatus status,
71+
// void *clientData
72+
// );
73+
import "C"
74+
75+
// Decoder implements an FLAC decoder.
76+
type Decoder struct {
77+
log librespot.Logger
78+
79+
SampleRate int32
80+
Channels int32
81+
82+
gain float32
83+
84+
input librespot.SizedReadAtSeeker
85+
decoder *C.FLAC__StreamDecoder
86+
clientData *C.ClientData
87+
buffer []float32
88+
}
89+
90+
func New(log librespot.Logger, r librespot.SizedReadAtSeeker, gain float32) (*Decoder, error) {
91+
d := &Decoder{log: log, input: r, gain: gain}
92+
93+
d.clientData = C.allocateClientData()
94+
if d.clientData == nil {
95+
return nil, fmt.Errorf("could not allocate FLAC client data")
96+
}
97+
d.clientData.decoder = unsafe.Pointer(d)
98+
99+
d.decoder = C.FLAC__stream_decoder_new()
100+
if d.decoder == nil {
101+
C.freeClientData(d.clientData)
102+
return nil, fmt.Errorf("could not create FLAC decoder")
103+
}
104+
105+
if res := C.FLAC__stream_decoder_init_stream(
106+
d.decoder,
107+
(C.FLAC__StreamDecoderReadCallback)(C.callbackRead),
108+
(C.FLAC__StreamDecoderSeekCallback)(C.callbackSeek),
109+
(C.FLAC__StreamDecoderTellCallback)(C.callbackTell),
110+
(C.FLAC__StreamDecoderLengthCallback)(C.callbackLength),
111+
(C.FLAC__StreamDecoderEofCallback)(C.callbackEof),
112+
(C.FLAC__StreamDecoderWriteCallback)(C.callbackWrite),
113+
(C.FLAC__StreamDecoderMetadataCallback)(C.callbackMetadata),
114+
(C.FLAC__StreamDecoderErrorCallback)(C.callbackError),
115+
unsafe.Pointer(d.clientData),
116+
); res != C.FLAC__STREAM_DECODER_INIT_STATUS_OK {
117+
C.FLAC__stream_decoder_delete(d.decoder)
118+
C.freeClientData(d.clientData)
119+
120+
err := C.FLAC__StreamDecoderInitStatusString[res]
121+
return nil, fmt.Errorf("could not initialize FLAC decoder: %s", C.GoString(err))
122+
}
123+
124+
if C.FLAC__stream_decoder_process_until_end_of_metadata(d.decoder) == 0 {
125+
C.FLAC__stream_decoder_delete(d.decoder)
126+
C.freeClientData(d.clientData)
127+
128+
return nil, fmt.Errorf("could not process FLAC metadata")
129+
}
130+
131+
return d, nil
132+
}
133+
134+
func callbackGetDecoder(clientData *C.void) *Decoder {
135+
return (*Decoder)(unsafe.Pointer((*C.ClientData)(unsafe.Pointer(clientData)).decoder))
136+
}
137+
138+
//export callbackRead
139+
func callbackRead(
140+
_ *C.FLAC__StreamDecoder,
141+
buffer *C.FLAC__byte,
142+
bytes *C.size_t,
143+
clientData *C.void,
144+
) C.FLAC__StreamDecoderReadStatus {
145+
d := callbackGetDecoder(clientData)
146+
147+
b := unsafe.Slice((*byte)(buffer), *bytes)
148+
n, err := d.input.Read(b)
149+
*bytes = C.size_t(n)
150+
151+
if errors.Is(err, io.EOF) {
152+
if n == 0 {
153+
return C.FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM
154+
}
155+
156+
return C.FLAC__STREAM_DECODER_READ_STATUS_CONTINUE
157+
} else if err != nil {
158+
d.log.WithError(err).Errorf("failed to read from input")
159+
return C.FLAC__STREAM_DECODER_READ_STATUS_ABORT
160+
}
161+
162+
return C.FLAC__STREAM_DECODER_READ_STATUS_CONTINUE
163+
}
164+
165+
//export callbackSeek
166+
func callbackSeek(
167+
_ *C.FLAC__StreamDecoder,
168+
absoluteByteOffset C.FLAC__uint64,
169+
clientData *C.void,
170+
) C.FLAC__StreamDecoderSeekStatus {
171+
d := callbackGetDecoder(clientData)
172+
_, err := d.input.Seek(int64(absoluteByteOffset), io.SeekStart)
173+
if err != nil {
174+
d.log.WithError(err).Errorf("failed to seek in input")
175+
return C.FLAC__STREAM_DECODER_SEEK_STATUS_ERROR
176+
}
177+
return C.FLAC__STREAM_DECODER_SEEK_STATUS_OK
178+
}
179+
180+
//export callbackTell
181+
func callbackTell(
182+
_ *C.FLAC__StreamDecoder,
183+
absoluteByteOffset *C.FLAC__uint64,
184+
clientData *C.void,
185+
) C.FLAC__StreamDecoderTellStatus {
186+
d := callbackGetDecoder(clientData)
187+
pos, err := d.input.Seek(0, io.SeekCurrent)
188+
if err != nil {
189+
d.log.WithError(err).Errorf("failed to tell position in input")
190+
return C.FLAC__STREAM_DECODER_TELL_STATUS_ERROR
191+
}
192+
*absoluteByteOffset = C.FLAC__uint64(pos)
193+
return C.FLAC__STREAM_DECODER_TELL_STATUS_OK
194+
}
195+
196+
//export callbackLength
197+
func callbackLength(
198+
_ *C.FLAC__StreamDecoder,
199+
streamLength *C.FLAC__uint64,
200+
clientData *C.void,
201+
) C.FLAC__StreamDecoderLengthStatus {
202+
d := callbackGetDecoder(clientData)
203+
size := d.input.Size()
204+
*streamLength = C.FLAC__uint64(size)
205+
return C.FLAC__STREAM_DECODER_LENGTH_STATUS_OK
206+
}
207+
208+
//export callbackEof
209+
func callbackEof(
210+
_ *C.FLAC__StreamDecoder,
211+
clientData *C.void,
212+
) C.FLAC__bool {
213+
d := callbackGetDecoder(clientData)
214+
pos, err := d.input.Seek(0, io.SeekCurrent)
215+
if err != nil {
216+
d.log.WithError(err).Errorf("failed to tell position in input")
217+
return C.FLAC__bool(1)
218+
}
219+
size := d.input.Size()
220+
if pos >= size {
221+
return C.FLAC__bool(1)
222+
}
223+
return C.FLAC__bool(0)
224+
}
225+
226+
//export callbackWrite
227+
func callbackWrite(
228+
_ *C.FLAC__StreamDecoder,
229+
frame *C.FLAC__Frame,
230+
buffer **C.FLAC__int32,
231+
clientData *C.void,
232+
) C.FLAC__StreamDecoderWriteStatus {
233+
d := callbackGetDecoder(clientData)
234+
235+
s := unsafe.Slice(buffer, d.Channels)
236+
norm := float32(uint32(1) << uint32(frame.header.bits_per_sample))
237+
238+
// Copy samples to the temporary buffer interleaving channels
239+
for i := 0; i < int(frame.header.blocksize); i++ {
240+
for ch := 0; ch < int(d.Channels); ch++ {
241+
ss := unsafe.Slice(s[ch], frame.header.blocksize)
242+
d.buffer = append(d.buffer, float32(ss[i])/norm*d.gain)
243+
}
244+
}
245+
246+
return C.FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE
247+
}
248+
249+
//export callbackMetadata
250+
func callbackMetadata(
251+
_ *C.FLAC__StreamDecoder,
252+
metadata *C.FLAC__StreamMetadata,
253+
clientData *C.void,
254+
) {
255+
d := callbackGetDecoder(clientData)
256+
257+
if metadata._type == C.FLAC__METADATA_TYPE_STREAMINFO {
258+
streamInfo := (*C.FLAC__StreamMetadata_StreamInfo)(unsafe.Pointer(&metadata.data[0]))
259+
d.SampleRate = int32(streamInfo.sample_rate)
260+
d.Channels = int32(streamInfo.channels)
261+
d.log.Infof("FLAC stream info: sample rate = %d, channels = %d", d.SampleRate, d.Channels)
262+
} else {
263+
d.log.Debugf("seen FLAC metadata type: %d", metadata._type)
264+
}
265+
}
266+
267+
//export callbackError
268+
func callbackError(
269+
_ *C.FLAC__StreamDecoder,
270+
status C.FLAC__StreamDecoderErrorStatus,
271+
clientData *C.void,
272+
) {
273+
d := callbackGetDecoder(clientData)
274+
275+
err := C.getStreamDecoderErrorStatusString(status)
276+
d.log.Errorf("FLAC decoder error: %s", C.GoString(err))
277+
}
278+
279+
func (d *Decoder) Read(p []float32) (n int, err error) {
280+
for {
281+
nn := copy(p, d.buffer)
282+
p = p[nn:]
283+
n += nn
284+
d.buffer = d.buffer[nn:]
285+
286+
if len(p) == 0 {
287+
return n, nil
288+
}
289+
290+
if C.FLAC__stream_decoder_process_single(d.decoder) == 0 {
291+
return 0, fmt.Errorf("error while decoding FLAC frame")
292+
}
293+
294+
if C.FLAC__stream_decoder_get_state(d.decoder) == C.FLAC__STREAM_DECODER_END_OF_STREAM {
295+
if len(d.buffer) == 0 {
296+
return n, io.EOF
297+
}
298+
return n, nil
299+
}
300+
}
301+
}
302+
303+
func (d *Decoder) SetPositionMs(pos int64) error {
304+
posSamples := pos * int64(d.SampleRate) / 1000
305+
if C.FLAC__stream_decoder_seek_absolute(d.decoder, C.FLAC__uint64(posSamples)) == 0 {
306+
return fmt.Errorf("could not seek to position")
307+
}
308+
309+
return nil
310+
}
311+
312+
func (d *Decoder) PositionMs() int64 {
313+
var samplePosition C.FLAC__uint64
314+
if C.FLAC__stream_decoder_get_decode_position(d.decoder, &samplePosition) == 0 {
315+
d.log.Errorf("could not get decode position")
316+
return 0
317+
}
318+
319+
return int64(samplePosition) * 1000 / int64(d.SampleRate)
320+
}
321+
322+
func (d *Decoder) Close() error {
323+
C.FLAC__stream_decoder_delete(d.decoder)
324+
C.freeClientData(d.clientData)
325+
return nil
326+
}

0 commit comments

Comments
 (0)