-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathaudio_al.go
408 lines (357 loc) · 9.71 KB
/
audio_al.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
// +build !windows,!netgo
package audio
// Taken from golang.org/x/mobile/exp/audio
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"sync"
"time"
"golang.org/x/mobile/exp/audio/al"
)
// Format represents a PCM data format.
type Format int
const (
Mono8 Format = iota + 1
Mono16
Stereo8
Stereo16
)
func (f Format) String() string { return formatStrings[f] }
// formatBytes is the product of bytes per sample and number of channels.
var formatBytes = [...]int64{
Mono8: 1,
Mono16: 2,
Stereo8: 2,
Stereo16: 4,
}
var formatCodes = [...]uint32{
Mono8: al.FormatMono8,
Mono16: al.FormatMono16,
Stereo8: al.FormatStereo8,
Stereo16: al.FormatStereo16,
}
var formatStrings = [...]string{
0: "unknown",
Mono8: "mono8",
Mono16: "mono16",
Stereo8: "stereo8",
Stereo16: "stereo16",
}
var codeToState = map[int32]State{
0: Unknown,
al.Initial: Initial,
al.Playing: Playing,
al.Paused: Paused,
al.Stopped: Stopped,
}
type track struct {
format Format
samplesPerSecond int64
src ReadSeekCloser
// hasHeader represents whether the audio source contains
// a PCM header. If true, the audio data starts 44 bytes
// later in the source.
hasHeader bool
}
// Player is a basic audio player that plays PCM data.
// Operations on a nil *Player are no-op, a nil *Player can
// be used for testing purposes.
type Player struct {
t *track
source al.Source
mu sync.Mutex
prep bool
bufs []al.Buffer // buffers are created and queued to source during prepare.
sizeBytes int64 // size of the audio source
background bool
}
// readSeekCloserBuffer is a wrapper to create a ReadSeekCloser
type readSeekCloserBuffer struct {
inner *bytes.Reader
}
func (r *readSeekCloserBuffer) Close() error {
return nil
}
func (r *readSeekCloserBuffer) Read(p []byte) (n int, err error) {
return r.inner.Read(p)
}
func (r *readSeekCloserBuffer) Seek(offset int64, whence int) (int64, error) {
return r.inner.Seek(offset, whence)
}
// NewPlayer returns a new Player.
// It initializes the underlying audio devices and the related resources.
// If zero values are provided for format and sample rate values, the player
// determines them from the source's WAV header.
// An error is returned if the format and sample rate can't be determined.
//
// The audio package is only designed for small audio sources.
func NewPlayer(src ReadSeekCloser, format Format, samplesPerSecond int64) (*Player, error) {
s := al.GenSources(1)
if code := al.Error(); code != 0 {
return nil, fmt.Errorf("audio: cannot generate an audio source [err=%x]", code)
}
p := &Player{
t: &track{format: format, src: src, samplesPerSecond: samplesPerSecond},
source: s[0],
}
if err := p.discoverHeader(); err != nil {
return nil, err
}
if p.t.format == 0 {
return nil, errors.New("audio: cannot determine the format")
}
if p.t.samplesPerSecond == 0 {
return nil, errors.New("audio: cannot determine the sample rate")
}
return p, nil
}
func NewSimplePlayer(src string) (*Player, error) {
err := Preload()
if err != nil {
return &Player{}, nil
}
al.SetListenerPosition(al.Vector{0, 0, 0})
b, err := ioutil.ReadFile(src)
if err != nil {
return &Player{}, err
}
buf := bytes.NewReader(b)
return NewPlayer(&readSeekCloserBuffer{buf}, 0, 0)
}
// headerSize is the size of WAV headers.
// See http://www.topherlee.com/software/pcm-tut-wavformat.html.
const headerSize = 44
var (
riffHeader = []byte("RIFF")
waveHeader = []byte("WAVE")
)
func (p *Player) discoverHeader() error {
buf := make([]byte, headerSize)
if n, _ := io.ReadFull(p.t.src, buf); n != headerSize {
// No header present or read error.
return nil
}
if !(bytes.Equal(buf[0:4], riffHeader) && bytes.Equal(buf[8:12], waveHeader)) {
return nil
}
p.t.hasHeader = true
var format Format
switch channels, depth := buf[22], buf[34]; {
case channels == 1 && depth == 8:
format = Mono8
case channels == 1 && depth == 16:
format = Mono16
case channels == 2 && depth == 8:
format = Stereo8
case channels == 2 && depth == 16:
format = Stereo16
default:
return fmt.Errorf("audio: unsupported format; num of channels=%d, bit rate=%d", channels, depth)
}
if p.t.format == 0 {
p.t.format = format
}
if p.t.format != format {
return fmt.Errorf("audio: given format %v does not match header %v", p.t.format, format)
}
sampleRate := int64(buf[24]) | int64(buf[25])<<8 | int64(buf[26])<<16 | int64(buf[27])<<24
if p.t.samplesPerSecond == 0 {
p.t.samplesPerSecond = sampleRate
}
if p.t.samplesPerSecond != sampleRate {
return fmt.Errorf("audio: given sample rate %v does not match header %v", p.t.samplesPerSecond, sampleRate)
}
return nil
}
func (p *Player) Prepare(background bool, offset int64, force bool) error {
return p.prepare(background, offset, force)
}
func (p *Player) prepare(background bool, offset int64, force bool) error {
p.mu.Lock()
if !force && p.prep {
p.mu.Unlock()
return nil
}
p.mu.Unlock()
if p.t.hasHeader {
offset += headerSize
}
if _, err := p.t.src.Seek(offset, 0); err != nil {
return err
}
var bufs []al.Buffer
// TODO(jbd): Limit the number of buffers in use, unqueue and reuse
// the existing buffers as buffers are processed.
buf := make([]byte, 128*1024)
size := offset
for {
n, err := p.t.src.Read(buf)
if n > 0 {
size += int64(n)
b := al.GenBuffers(1)
if !background && p.t.format == Stereo16 {
inputBuffer := bytes.NewBuffer(buf)
outputBuffer := new(bytes.Buffer)
var left, right int16
// TODO: this might not be the best way to convert Stereo16 to Mono16, but it's something
for converted := 0; converted < n; converted += 4 {
binary.Read(inputBuffer, binary.LittleEndian, &left)
binary.Read(inputBuffer, binary.LittleEndian, &right)
binary.Write(outputBuffer, binary.LittleEndian, int16((int32(left)+int32(right))/2))
}
b[0].BufferData(formatCodes[Mono16], outputBuffer.Bytes(), int32(p.t.samplesPerSecond/2))
} else {
b[0].BufferData(formatCodes[p.t.format], buf[:n], int32(p.t.samplesPerSecond))
}
bufs = append(bufs, b[0])
}
if err == io.EOF {
break
}
if err != nil {
return err
}
}
p.mu.Lock()
if len(p.bufs) > 0 {
p.source.UnqueueBuffers(p.bufs...)
al.DeleteBuffers(p.bufs...)
}
p.sizeBytes = size
p.bufs = bufs
p.prep = true
if len(bufs) > 0 {
p.source.QueueBuffers(bufs...)
}
p.mu.Unlock()
return nil
}
// Play buffers the source audio to the audio device and starts
// to play the source.
// If the player paused or stopped, it reuses the previously buffered
// resources to keep playing from the time it has paused or stopped.
func (p *Player) Play() error {
if p == nil {
return nil
}
// Prepares if the track hasn't been buffered before.
if err := p.prepare(p.background, 0, false); err != nil {
return err
}
al.PlaySources(p.source)
return lastErr()
}
// Pause pauses the player.
func (p *Player) Pause() error {
if p == nil {
return nil
}
al.PauseSources(p.source)
return lastErr()
}
// Stop stops the player.
func (p *Player) Stop() error {
if p == nil {
return nil
}
al.StopSources(p.source)
return lastErr()
}
// Seek moves the play head to the given offset relative to the start of the source.
func (p *Player) Seek(background bool, offset time.Duration) error {
if p == nil {
return nil
}
if err := p.Stop(); err != nil {
return err
}
size := durToByteOffset(p.t, offset)
if err := p.prepare(background, size, true); err != nil {
return err
}
al.PlaySources(p.source)
return lastErr()
}
// Current returns the current playback position of the audio that is being played.
func (p *Player) Current() time.Duration {
if p == nil {
return 0
}
// TODO(jbd): Current never returns the Total when the playing is finished.
// OpenAL may be returning the last buffer's start point as an OffsetByte.
return byteOffsetToDur(p.t, int64(p.source.OffsetByte()))
}
// Total returns the total duration of the audio source.
func (p *Player) Total(background bool) time.Duration {
if p == nil {
return 0
}
// Prepare is required to determine the length of the source.
// We need to read the entire source to calculate the length.
p.prepare(background, 0, false)
return byteOffsetToDur(p.t, p.sizeBytes)
}
// Volume returns the current player volume. The range of the volume is [0, 1].
func (p *Player) Volume() float64 {
if p == nil {
return 0
}
return float64(p.source.Gain())
}
// SetVolume sets the volume of the player. The range of the volume is [0, 1].
func (p *Player) SetVolume(vol float64) {
if p == nil {
return
}
p.source.SetGain(float32(vol))
}
// State returns the player's current state.
func (p *Player) State() State {
if p == nil {
return Unknown
}
return codeToState[p.source.State()]
}
// Close closes the device and frees the underlying resources
// used by the player.
// It should be called as soon as the player is not in-use anymore.
func (p *Player) Close() error {
if p == nil {
return nil
}
if p.source != 0 {
al.DeleteSources(p.source)
}
p.mu.Lock()
if len(p.bufs) > 0 {
al.DeleteBuffers(p.bufs...)
}
p.mu.Unlock()
p.t.src.Close()
return nil
}
func (p *Player) Source() al.Source {
return p.source
}
func byteOffsetToDur(t *track, offset int64) time.Duration {
return time.Duration(offset * formatBytes[t.format] * int64(time.Second) / t.samplesPerSecond)
}
func durToByteOffset(t *track, dur time.Duration) int64 {
return int64(dur) * t.samplesPerSecond / (formatBytes[t.format] * int64(time.Second))
}
// lastErr returns the last error or nil if the last operation
// has been succesful.
func lastErr() error {
if code := al.Error(); code != 0 {
return fmt.Errorf("audio: openal failed with %x", code)
}
return nil
}
func Preload() error {
return al.OpenDevice()
}
// TODO(jbd): Close the device.