github.com/c-darwin/mobile@v0.0.0-20160313183840-ff625c46f7c9/exp/audio/audio.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // +build darwin linux 6 7 // Package audio provides a basic audio player. 8 // 9 // In order to use this package on Linux desktop distros, 10 // you will need OpenAL library as an external dependency. 11 // On Ubuntu 14.04 'Trusty', you may have to install this library 12 // by running the command below. 13 // 14 // sudo apt-get install libopenal-dev 15 // 16 // When compiled for Android, this package uses OpenAL Soft as a backend. 17 // Please add its license file to the open source notices of your 18 // application. 19 // OpenAL Soft's license file could be found at 20 // http://repo.or.cz/w/openal-soft.git/blob/HEAD:/COPYING. 21 package audio // import "github.com/c-darwin/mobile/exp/audio" 22 23 import ( 24 "bytes" 25 "errors" 26 "fmt" 27 "io" 28 "sync" 29 "time" 30 31 "github.com/c-darwin/mobile/exp/audio/al" 32 ) 33 34 // ReadSeekCloser is an io.ReadSeeker and io.Closer. 35 type ReadSeekCloser interface { 36 io.ReadSeeker 37 io.Closer 38 } 39 40 // Format represents a PCM data format. 41 type Format int 42 43 const ( 44 Mono8 Format = iota + 1 45 Mono16 46 Stereo8 47 Stereo16 48 ) 49 50 func (f Format) String() string { return formatStrings[f] } 51 52 // formatBytes is the product of bytes per sample and number of channels. 53 var formatBytes = [...]int64{ 54 Mono8: 1, 55 Mono16: 2, 56 Stereo8: 2, 57 Stereo16: 4, 58 } 59 60 var formatCodes = [...]uint32{ 61 Mono8: al.FormatMono8, 62 Mono16: al.FormatMono16, 63 Stereo8: al.FormatStereo8, 64 Stereo16: al.FormatStereo16, 65 } 66 67 var formatStrings = [...]string{ 68 0: "unknown", 69 Mono8: "mono8", 70 Mono16: "mono16", 71 Stereo8: "stereo8", 72 Stereo16: "stereo16", 73 } 74 75 // State indicates the current playing state of the player. 76 type State int 77 78 const ( 79 Unknown State = iota 80 Initial 81 Playing 82 Paused 83 Stopped 84 ) 85 86 func (s State) String() string { return stateStrings[s] } 87 88 var stateStrings = [...]string{ 89 Unknown: "unknown", 90 Initial: "initial", 91 Playing: "playing", 92 Paused: "paused", 93 Stopped: "stopped", 94 } 95 96 var codeToState = map[int32]State{ 97 0: Unknown, 98 al.Initial: Initial, 99 al.Playing: Playing, 100 al.Paused: Paused, 101 al.Stopped: Stopped, 102 } 103 104 type track struct { 105 format Format 106 samplesPerSecond int64 107 src ReadSeekCloser 108 109 // hasHeader represents whether the audio source contains 110 // a PCM header. If true, the audio data starts 44 bytes 111 // later in the source. 112 hasHeader bool 113 } 114 115 // Player is a basic audio player that plays PCM data. 116 // Operations on a nil *Player are no-op, a nil *Player can 117 // be used for testing purposes. 118 type Player struct { 119 t *track 120 source al.Source 121 122 mu sync.Mutex 123 prep bool 124 bufs []al.Buffer // buffers are created and queued to source during prepare. 125 sizeBytes int64 // size of the audio source 126 } 127 128 // NewPlayer returns a new Player. 129 // It initializes the underlying audio devices and the related resources. 130 // If zero values are provided for format and sample rate values, the player 131 // determines them from the source's WAV header. 132 // An error is returned if the format and sample rate can't be determined. 133 func NewPlayer(src ReadSeekCloser, format Format, samplesPerSecond int64) (*Player, error) { 134 if err := al.OpenDevice(); err != nil { 135 return nil, err 136 } 137 s := al.GenSources(1) 138 if code := al.Error(); code != 0 { 139 return nil, fmt.Errorf("audio: cannot generate an audio source [err=%x]", code) 140 } 141 p := &Player{ 142 t: &track{format: format, src: src, samplesPerSecond: samplesPerSecond}, 143 source: s[0], 144 } 145 if err := p.discoverHeader(); err != nil { 146 return nil, err 147 } 148 if p.t.format == 0 { 149 return nil, errors.New("audio: cannot determine the format") 150 } 151 if p.t.samplesPerSecond == 0 { 152 return nil, errors.New("audio: cannot determine the sample rate") 153 } 154 return p, nil 155 } 156 157 // headerSize is the size of WAV headers. 158 // See http://www.topherlee.com/software/pcm-tut-wavformat.html. 159 const headerSize = 44 160 161 var ( 162 riffHeader = []byte("RIFF") 163 waveHeader = []byte("WAVE") 164 ) 165 166 func (p *Player) discoverHeader() error { 167 buf := make([]byte, headerSize) 168 if n, _ := io.ReadFull(p.t.src, buf); n != headerSize { 169 // No header present or read error. 170 return nil 171 } 172 if !(bytes.Equal(buf[0:4], riffHeader) && bytes.Equal(buf[8:12], waveHeader)) { 173 return nil 174 } 175 p.t.hasHeader = true 176 var format Format 177 switch channels, depth := buf[22], buf[34]; { 178 case channels == 1 && depth == 8: 179 format = Mono8 180 case channels == 1 && depth == 16: 181 format = Mono16 182 case channels == 2 && depth == 8: 183 format = Stereo8 184 case channels == 2 && depth == 16: 185 format = Stereo16 186 default: 187 return fmt.Errorf("audio: unsupported format; num of channels=%d, bit rate=%d", channels, depth) 188 } 189 if p.t.format == 0 { 190 p.t.format = format 191 } 192 if p.t.format != format { 193 return fmt.Errorf("audio: given format %v does not match header %v", p.t.format, format) 194 } 195 sampleRate := int64(buf[24]) | int64(buf[25])<<8 | int64(buf[26])<<16 | int64(buf[27]<<24) 196 if p.t.samplesPerSecond == 0 { 197 p.t.samplesPerSecond = sampleRate 198 } 199 if p.t.samplesPerSecond != sampleRate { 200 return fmt.Errorf("audio: given sample rate %v does not match header", p.t.samplesPerSecond, sampleRate) 201 } 202 return nil 203 } 204 205 func (p *Player) prepare(offset int64, force bool) error { 206 p.mu.Lock() 207 if !force && p.prep { 208 p.mu.Unlock() 209 return nil 210 } 211 p.mu.Unlock() 212 213 if p.t.hasHeader { 214 offset += headerSize 215 } 216 if _, err := p.t.src.Seek(offset, 0); err != nil { 217 return err 218 } 219 var bufs []al.Buffer 220 // TODO(jbd): Limit the number of buffers in use, unqueue and reuse 221 // the existing buffers as buffers are processed. 222 buf := make([]byte, 128*1024) 223 size := offset 224 for { 225 n, err := p.t.src.Read(buf) 226 if n > 0 { 227 size += int64(n) 228 b := al.GenBuffers(1) 229 b[0].BufferData(formatCodes[p.t.format], buf[:n], int32(p.t.samplesPerSecond)) 230 bufs = append(bufs, b[0]) 231 } 232 if err == io.EOF { 233 break 234 } 235 if err != nil { 236 return err 237 } 238 } 239 240 p.mu.Lock() 241 if len(p.bufs) > 0 { 242 p.source.UnqueueBuffers(p.bufs) 243 al.DeleteBuffers(p.bufs) 244 } 245 p.sizeBytes = size 246 p.bufs = bufs 247 p.prep = true 248 if len(bufs) > 0 { 249 p.source.QueueBuffers(bufs) 250 } 251 p.mu.Unlock() 252 return nil 253 } 254 255 // Play buffers the source audio to the audio device and starts 256 // to play the source. 257 // If the player paused or stopped, it reuses the previously buffered 258 // resources to keep playing from the time it has paused or stopped. 259 func (p *Player) Play() error { 260 if p == nil { 261 return nil 262 } 263 // Prepares if the track hasn't been buffered before. 264 if err := p.prepare(0, false); err != nil { 265 return err 266 } 267 al.PlaySources(p.source) 268 return lastErr() 269 } 270 271 // Pause pauses the player. 272 func (p *Player) Pause() error { 273 if p == nil { 274 return nil 275 } 276 al.PauseSources(p.source) 277 return lastErr() 278 } 279 280 // Stop stops the player. 281 func (p *Player) Stop() error { 282 if p == nil { 283 return nil 284 } 285 al.StopSources(p.source) 286 return lastErr() 287 } 288 289 // Seek moves the play head to the given offset relative to the start of the source. 290 func (p *Player) Seek(offset time.Duration) error { 291 if p == nil { 292 return nil 293 } 294 if err := p.Stop(); err != nil { 295 return err 296 } 297 size := durToByteOffset(p.t, offset) 298 if err := p.prepare(size, true); err != nil { 299 return err 300 } 301 al.PlaySources(p.source) 302 return lastErr() 303 } 304 305 // Current returns the current playback position of the audio that is being played. 306 func (p *Player) Current() time.Duration { 307 if p == nil { 308 return 0 309 } 310 // TODO(jbd): Current never returns the Total when the playing is finished. 311 // OpenAL may be returning the last buffer's start point as an OffsetByte. 312 return byteOffsetToDur(p.t, int64(p.source.OffsetByte())) 313 } 314 315 // Total returns the total duration of the audio source. 316 func (p *Player) Total() time.Duration { 317 if p == nil { 318 return 0 319 } 320 // Prepare is required to determine the length of the source. 321 // We need to read the entire source to calculate the length. 322 p.prepare(0, false) 323 return byteOffsetToDur(p.t, p.sizeBytes) 324 } 325 326 // Volume returns the current player volume. The range of the volume is [0, 1]. 327 func (p *Player) Volume() float64 { 328 if p == nil { 329 return 0 330 } 331 return float64(p.source.Gain()) 332 } 333 334 // SetVolume sets the volume of the player. The range of the volume is [0, 1]. 335 func (p *Player) SetVolume(vol float64) { 336 if p == nil { 337 return 338 } 339 p.source.SetGain(float32(vol)) 340 } 341 342 // State returns the player's current state. 343 func (p *Player) State() State { 344 if p == nil { 345 return Unknown 346 } 347 return codeToState[p.source.State()] 348 } 349 350 // Close closes the device and frees the underlying resources 351 // used by the player. 352 // It should be called as soon as the player is not in-use anymore. 353 func (p *Player) Close() error { 354 if p == nil { 355 return nil 356 } 357 if p.source != 0 { 358 al.DeleteSources(p.source) 359 } 360 p.mu.Lock() 361 if len(p.bufs) > 0 { 362 al.DeleteBuffers(p.bufs) 363 } 364 p.mu.Unlock() 365 p.t.src.Close() 366 return nil 367 } 368 369 func byteOffsetToDur(t *track, offset int64) time.Duration { 370 return time.Duration(offset * formatBytes[t.format] * int64(time.Second) / t.samplesPerSecond) 371 } 372 373 func durToByteOffset(t *track, dur time.Duration) int64 { 374 return int64(dur) * t.samplesPerSecond / (formatBytes[t.format] * int64(time.Second)) 375 } 376 377 // lastErr returns the last error or nil if the last operation 378 // has been succesful. 379 func lastErr() error { 380 if code := al.Error(); code != 0 { 381 return fmt.Errorf("audio: openal failed with %x", code) 382 } 383 return nil 384 } 385 386 // TODO(jbd): Close the device.