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.