-
Notifications
You must be signed in to change notification settings - Fork 45
/
flac.go
426 lines (372 loc) · 12.3 KB
/
flac.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
// TODO(u): Evaluate storing the samples (and residuals) during frame audio
// decoding in a buffer allocated for the stream. This buffer would be allocated
// using BlockSize and NChannels from the StreamInfo block, and it could be
// reused in between calls to Next and ParseNext. This should reduce GC
// pressure.
// TODO: Remove note about encoder API.
// Package flac provides access to FLAC (Free Lossless Audio Codec) streams.
//
// A brief introduction of the FLAC stream format [1] follows. Each FLAC stream
// starts with a 32-bit signature ("fLaC"), followed by one or more metadata
// blocks, and then one or more audio frames. The first metadata block
// (StreamInfo) describes the basic properties of the audio stream and it is the
// only mandatory metadata block. Subsequent metadata blocks may appear in an
// arbitrary order.
//
// Please refer to the documentation of the meta [2] and the frame [3] packages
// for a brief introduction of their respective formats.
//
// [1]: https://www.xiph.org/flac/format.html#stream
// [2]: https://godoc.org/github.com/mewkiz/flac/meta
// [3]: https://godoc.org/github.com/mewkiz/flac/frame
//
// Note: the Encoder API is experimental until the 1.1.x release. As such, it's
// API is expected to change.
package flac
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"os"
"github.com/mewkiz/flac/frame"
"github.com/mewkiz/flac/internal/bufseekio"
"github.com/mewkiz/flac/meta"
)
// A Stream contains the metadata blocks and provides access to the audio frames
// of a FLAC stream.
//
// ref: https://www.xiph.org/flac/format.html#stream
type Stream struct {
// The StreamInfo metadata block describes the basic properties of the FLAC
// audio stream.
Info *meta.StreamInfo
// Zero or more metadata blocks.
Blocks []*meta.Block
// seekTable contains one or more pre-calculated audio frame seek points of
// the stream; nil if uninitialized.
seekTable *meta.SeekTable
// seekTableSize determines how many seek points the seekTable should have if
// the flac file does not include one in the metadata.
seekTableSize int
// dataStart is the offset of the first frame header since SeekPoint.Offset
// is relative to this position.
dataStart int64
// Underlying io.Reader, or io.ReadCloser.
r io.Reader
}
// New creates a new Stream for accessing the audio samples of r. It reads and
// parses the FLAC signature and the StreamInfo metadata block, but skips all
// other metadata blocks.
//
// Call Stream.Next to parse the frame header of the next audio frame, and call
// Stream.ParseNext to parse the entire next frame including audio samples.
func New(r io.Reader) (stream *Stream, err error) {
// Verify FLAC signature and parse the StreamInfo metadata block.
br := bufio.NewReader(r)
stream = &Stream{r: br}
block, err := stream.parseStreamInfo()
if err != nil {
return nil, err
}
// Skip the remaining metadata blocks.
for !block.IsLast {
block, err = meta.New(br)
if err != nil && err != meta.ErrReservedType {
return stream, err
}
if err = block.Skip(); err != nil {
return stream, err
}
}
return stream, nil
}
// NewSeek returns a Stream that has seeking enabled. The incoming io.ReadSeeker
// will not be buffered, which might result in performance issues. Using an
// in-memory buffer like *bytes.Reader should work well.
func NewSeek(rs io.ReadSeeker) (stream *Stream, err error) {
br := bufseekio.NewReadSeeker(rs)
stream = &Stream{r: br, seekTableSize: defaultSeekTableSize}
// Verify FLAC signature and parse the StreamInfo metadata block.
block, err := stream.parseStreamInfo()
if err != nil {
return stream, err
}
for !block.IsLast {
block, err = meta.Parse(stream.r)
if err != nil {
if err != meta.ErrReservedType {
return stream, err
}
if err = block.Skip(); err != nil {
return stream, err
}
}
if block.Header.Type == meta.TypeSeekTable {
stream.seekTable = block.Body.(*meta.SeekTable)
}
}
// Record file offset of the first frame header.
stream.dataStart, err = br.Seek(0, io.SeekCurrent)
return stream, err
}
var (
// flacSignature marks the beginning of a FLAC stream.
flacSignature = []byte("fLaC")
// id3Signature marks the beginning of an ID3 stream, used to skip over ID3
// data.
id3Signature = []byte("ID3")
// ErrNoSeeker reports that flac.NewSeek was called with an io.Reader not
// implementing io.Seeker, and thus does not allow for seeking.
ErrNoSeeker = errors.New("stream.Seek: reader does not implement io.Seeker")
// ErrNoSeektable reports that no seektable has been generated. Therefore,
// it is not possible to seek in the stream.
ErrNoSeektable = errors.New("stream.searchFromStart: no seektable exists")
)
const (
defaultSeekTableSize = 100
)
// parseStreamInfo verifies the signature which marks the beginning of a FLAC
// stream, and parses the StreamInfo metadata block. It returns a boolean value
// which specifies if the StreamInfo block was the last metadata block of the
// FLAC stream.
func (stream *Stream) parseStreamInfo() (block *meta.Block, err error) {
// Verify FLAC signature.
r := stream.r
var buf [4]byte
if _, err = io.ReadFull(r, buf[:]); err != nil {
return block, err
}
// Skip prepended ID3v2 data.
if bytes.Equal(buf[:3], id3Signature) {
if err := stream.skipID3v2(); err != nil {
return block, err
}
// Second attempt at verifying signature.
if _, err = io.ReadFull(r, buf[:]); err != nil {
return block, err
}
}
if !bytes.Equal(buf[:], flacSignature) {
return block, fmt.Errorf("flac.parseStreamInfo: invalid FLAC signature; expected %q, got %q", flacSignature, buf)
}
// Parse StreamInfo metadata block.
block, err = meta.Parse(r)
if err != nil {
return block, err
}
si, ok := block.Body.(*meta.StreamInfo)
if !ok {
return block, fmt.Errorf("flac.parseStreamInfo: incorrect type of first metadata block; expected *meta.StreamInfo, got %T", block.Body)
}
stream.Info = si
return block, nil
}
// skipID3v2 skips ID3v2 data prepended to flac files.
func (stream *Stream) skipID3v2() error {
r := bufio.NewReader(stream.r)
// Discard unnecessary data from the ID3v2 header.
if _, err := r.Discard(2); err != nil {
return err
}
// Read the size from the ID3v2 header.
var sizeBuf [4]byte
if _, err := r.Read(sizeBuf[:]); err != nil {
return err
}
// The size is encoded as a synchsafe integer.
size := int(sizeBuf[0])<<21 | int(sizeBuf[1])<<14 | int(sizeBuf[2])<<7 | int(sizeBuf[3])
_, err := r.Discard(size)
return err
}
// Parse creates a new Stream for accessing the metadata blocks and audio
// samples of r. It reads and parses the FLAC signature and all metadata blocks.
//
// Call Stream.Next to parse the frame header of the next audio frame, and call
// Stream.ParseNext to parse the entire next frame including audio samples.
func Parse(r io.Reader) (stream *Stream, err error) {
// Verify FLAC signature and parse the StreamInfo metadata block.
br := bufio.NewReader(r)
stream = &Stream{r: br}
block, err := stream.parseStreamInfo()
if err != nil {
return nil, err
}
// Parse the remaining metadata blocks.
for !block.IsLast {
block, err = meta.Parse(br)
if err != nil {
if err != meta.ErrReservedType {
return stream, err
}
// Skip the body of unknown (reserved) metadata blocks, as stated by
// the specification.
//
// ref: https://www.xiph.org/flac/format.html#format_overview
if err = block.Skip(); err != nil {
return stream, err
}
}
stream.Blocks = append(stream.Blocks, block)
}
return stream, nil
}
// Open creates a new Stream for accessing the audio samples of path. It reads
// and parses the FLAC signature and the StreamInfo metadata block, but skips
// all other metadata blocks.
//
// Call Stream.Next to parse the frame header of the next audio frame, and call
// Stream.ParseNext to parse the entire next frame including audio samples.
//
// Note: The Close method of the stream must be called when finished using it.
func Open(path string) (stream *Stream, err error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
stream, err = New(f)
if err != nil {
return nil, err
}
return stream, err
}
// ParseFile creates a new Stream for accessing the metadata blocks and audio
// samples of path. It reads and parses the FLAC signature and all metadata
// blocks.
//
// Call Stream.Next to parse the frame header of the next audio frame, and call
// Stream.ParseNext to parse the entire next frame including audio samples.
//
// Note: The Close method of the stream must be called when finished using it.
func ParseFile(path string) (stream *Stream, err error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
stream, err = Parse(f)
if err != nil {
return nil, err
}
return stream, err
}
// Close closes the stream gracefully if the underlying io.Reader also implements the io.Closer interface.
func (stream *Stream) Close() error {
if closer, ok := stream.r.(io.Closer); ok {
return closer.Close()
}
return nil
}
// Next parses the frame header of the next audio frame. It returns io.EOF to
// signal a graceful end of FLAC stream.
//
// Call Frame.Parse to parse the audio samples of its subframes.
func (stream *Stream) Next() (f *frame.Frame, err error) {
return frame.New(stream.r)
}
// ParseNext parses the entire next frame including audio samples. It returns
// io.EOF to signal a graceful end of FLAC stream.
func (stream *Stream) ParseNext() (f *frame.Frame, err error) {
return frame.Parse(stream.r)
}
// Seek seeks to the frame containing the given absolute sample number. The
// return value specifies the first sample number of the frame containing
// sampleNum.
func (stream *Stream) Seek(sampleNum uint64) (uint64, error) {
if stream.seekTable == nil && stream.seekTableSize > 0 {
if err := stream.makeSeekTable(); err != nil {
return 0, err
}
}
rs := stream.r.(io.ReadSeeker)
isBiggerThanStream := stream.Info.NSamples != 0 && sampleNum >= stream.Info.NSamples
if isBiggerThanStream || sampleNum < 0 {
return 0, fmt.Errorf("unable to seek to sample number %d", sampleNum)
}
point, err := stream.searchFromStart(sampleNum)
if err != nil {
return 0, err
}
if _, err := rs.Seek(stream.dataStart+int64(point.Offset), io.SeekStart); err != nil {
return 0, err
}
for {
// Record seek offset to start of frame.
offset, err := rs.Seek(0, io.SeekCurrent)
if err != nil {
return 0, err
}
frame, err := stream.ParseNext()
if err != nil {
return 0, err
}
if frame.SampleNumber()+uint64(frame.BlockSize) > sampleNum {
// Restore seek offset to the start of the frame containing the
// specified sample number.
_, err := rs.Seek(offset, io.SeekStart)
return frame.SampleNumber(), err
}
}
}
// TODO(_): Utilize binary search in searchFromStart.
// searchFromStart searches for the given sample number from the start of the
// seek table and returns the last seek point containing the sample number. If
// no seek point contains the sample number, the last seek point preceding the
// sample number is returned. If the sample number is lower than the first seek
// point, the first seek point is returned.
func (stream *Stream) searchFromStart(sampleNum uint64) (meta.SeekPoint, error) {
if len(stream.seekTable.Points) == 0 {
return meta.SeekPoint{}, ErrNoSeektable
}
prev := stream.seekTable.Points[0]
for _, p := range stream.seekTable.Points {
if p.SampleNum+uint64(p.NSamples) >= sampleNum {
return prev, nil
}
prev = p
}
return prev, nil
}
// makeSeekTable creates a seek table with seek points to each frame of the FLAC
// stream.
func (stream *Stream) makeSeekTable() (err error) {
rs, ok := stream.r.(io.ReadSeeker)
if !ok {
return ErrNoSeeker
}
pos, err := rs.Seek(0, io.SeekCurrent)
if err != nil {
return err
}
_, err = rs.Seek(stream.dataStart, io.SeekStart)
if err != nil {
return err
}
var i int
var sampleNum uint64
var points []meta.SeekPoint
for {
// Record seek offset to start of frame.
off, err := rs.Seek(0, io.SeekCurrent)
if err != nil {
return err
}
f, err := stream.ParseNext()
if err != nil {
if err == io.EOF {
break
}
return err
}
points = append(points, meta.SeekPoint{
SampleNum: sampleNum,
Offset: uint64(off - stream.dataStart),
NSamples: f.BlockSize,
})
sampleNum += uint64(f.BlockSize)
i++
}
stream.seekTable = &meta.SeekTable{Points: points}
_, err = rs.Seek(pos, io.SeekStart)
return err
}