summaryrefslogtreecommitdiffstats
path: root/media/audio/linux/alsa_output.h
blob: f2999c188f42d693ebc28d6c7303454d5bc69385 (plain)
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
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Creates an output stream based on the ALSA PCM interface.
//
// On device write failure, the stream will move itself to an invalid state.
// No more data will be pulled from the data source, or written to the device.
// All calls to public API functions will either no-op themselves, or return an
// error if possible.  Specifically, If the stream is in an error state, Open()
// will return false, and Start() will call OnError() immediately on the
// provided callback.
//
// TODO(ajwong): The OnClose() and OnError() calling needing fixing.
//
// If the stream is successfully opened, Close() must be called before the
// stream is deleted as Close() is responsible for ensuring resource cleanup
// occurs.
//
// This object's thread-safety is a little tricky.  This object's public API
// can only be called from the thread that created the object.  Calling the
// public APIs in any method that may cause concurrent execution will result in
// a race condition.  When modifying the code in this class, please read the
// threading assumptions at the top of the implementation file to avoid
// introducing race conditions between tasks posted to the internal
// message_loop, and the thread calling the public APIs.
//
// TODO(sergeyu): AlsaPcmOutputStream is always created and used from the
// audio thread (i.e. |client_thread_loop_| and |message_loop_| always point
// to the same thread), so it doesn't need to be thread-safe anymore.
//
// TODO(sergeyu): Remove refcounter from AlsaPcmOutputStream and use
// ScopedRunnableMethodFactory to create tasks.

#ifndef MEDIA_AUDIO_LINUX_ALSA_OUTPUT_H_
#define MEDIA_AUDIO_LINUX_ALSA_OUTPUT_H_

#include <alsa/asoundlib.h>

#include <string>

#include "base/gtest_prod_util.h"
#include "base/ref_counted.h"
#include "base/scoped_ptr.h"
#include "base/synchronization/lock.h"
#include "media/audio/audio_io.h"
#include "media/audio/audio_parameters.h"

namespace media {
class SeekableBuffer;
};  // namespace media

class AlsaWrapper;
class AudioManagerLinux;
class MessageLoop;

class AlsaPcmOutputStream :
    public AudioOutputStream,
    public base::RefCountedThreadSafe<AlsaPcmOutputStream> {
 public:
  // String for the generic "default" ALSA device that has the highest
  // compatibility and chance of working.
  static const char kDefaultDevice[];

  // Pass this to the AlsaPcmOutputStream if you want to attempt auto-selection
  // of the audio device.
  static const char kAutoSelectDevice[];

  // Prefix for device names to enable ALSA library resampling.
  static const char kPlugPrefix[];

  // The minimum latency that is accepted by the device.
  static const uint32 kMinLatencyMicros;

  // Create a PCM Output stream for the ALSA device identified by
  // |device_name|.  The AlsaPcmOutputStream uses |wrapper| to communicate with
  // the alsa libraries, allowing for dependency injection during testing.  All
  // requesting of data, and writing to the alsa device will be done on
  // |message_loop|.
  //
  // If unsure of what to use for |device_name|, use |kAutoSelectDevice|.
  AlsaPcmOutputStream(const std::string& device_name,
                      AudioParameters params,
                      AlsaWrapper* wrapper,
                      AudioManagerLinux* manager,
                      MessageLoop* message_loop);

  // Implementation of AudioOutputStream.
  virtual bool Open();
  virtual void Close();
  virtual void Start(AudioSourceCallback* callback);
  virtual void Stop();
  virtual void SetVolume(double volume);
  virtual void GetVolume(double* volume);

 private:
  friend class base::RefCountedThreadSafe<AlsaPcmOutputStream>;
  friend class AlsaPcmOutputStreamTest;
  FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest,
                           AutoSelectDevice_DeviceSelect);
  FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest,
                           AutoSelectDevice_FallbackDevices);
  FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, AutoSelectDevice_HintFail);
  FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket);
  FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket_Negative);
  FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket_StopStream);
  FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket_Underrun);
  FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, BufferPacket_FullBuffer);
  FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, ConstructedState);
  FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, LatencyFloor);
  FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, OpenClose);
  FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, PcmOpenFailed);
  FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, PcmSetParamsFailed);
  FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, ScheduleNextWrite);
  FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest,
                           ScheduleNextWrite_StopStream);
  FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, StartStop);
  FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, WritePacket_FinishedPacket);
  FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, WritePacket_NormalPacket);
  FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, WritePacket_StopStream);
  FRIEND_TEST_ALL_PREFIXES(AlsaPcmOutputStreamTest, WritePacket_WriteFails);

  virtual ~AlsaPcmOutputStream();

  // Flags indicating the state of the stream.
  enum InternalState {
    kInError = 0,
    kCreated,
    kIsOpened,
    kIsPlaying,
    kIsStopped,
    kIsClosed
  };
  friend std::ostream& operator<<(std::ostream& os, InternalState);

  // Various tasks that complete actions started in the public API.
  void OpenTask();
  void StartTask();
  void CloseTask();

  // Functions to get another packet from the data source and write it into the
  // ALSA device.
  void BufferPacket(bool* source_exhausted);
  void WritePacket();
  void WriteTask();
  void ScheduleNextWrite(bool source_exhausted);

  // Utility functions for talking with the ALSA API.
  static uint32 FramesToMicros(uint32 frames, uint32 sample_rate);
  static uint32 FramesToMillis(uint32 frames, uint32 sample_rate);
  std::string FindDeviceForChannels(uint32 channels);
  snd_pcm_sframes_t GetAvailableFrames();
  snd_pcm_sframes_t GetCurrentDelay();

  // Attempts to find the best matching linux audio device for the given number
  // of channels.  This function will set |device_name_| and |should_downmix_|.
  snd_pcm_t* AutoSelectDevice(uint32 latency);

  // Thread-asserting accessors for member variables.
  AudioManagerLinux* manager();

  // Struct holding all mutable the data that must be shared by the
  // message_loop() and the thread that created the object.
  class SharedData {
   public:
    explicit SharedData(MessageLoop* state_transition_loop);

    // Functions to safeguard state transitions and ensure that transitions are
    // only allowed occuring on the thread that created the object.  All
    // changes to the object state should go through these functions.
    bool CanTransitionTo(InternalState to);
    bool CanTransitionTo_Locked(InternalState to);
    InternalState TransitionTo(InternalState to);
    InternalState state();

    float volume();
    void set_volume(float v);

    // API for Proxying calls to the AudioSourceCallback provided during
    // Start().  These APIs are threadsafe.
    //
    // TODO(ajwong): This is necessary because the ownership semantics for the
    // |source_callback_| object are incorrect in AudioRenderHost. The callback
    // is passed into the output stream, but ownership is not transfered which
    // requires a synchronization on access of the |source_callback_| to avoid
    // using a deleted callback.
    uint32 OnMoreData(AudioOutputStream* stream, uint8* dest,
                      uint32 max_size, AudioBuffersState buffers_state);
    void OnError(AudioOutputStream* stream, int code);

    // Changes the AudioSourceCallback to proxy calls to.  Pass in NULL to
    // release ownership of the currently registered callback.
    void set_source_callback(AudioSourceCallback* callback);

   private:
    base::Lock lock_;

    InternalState state_;
    float volume_;  // Volume level from 0.0 to 1.0.

    AudioSourceCallback* source_callback_;

    MessageLoop* const state_transition_loop_;

    DISALLOW_COPY_AND_ASSIGN(SharedData);
  } shared_data_;

  // Configuration constants from the constructor.  Referenceable by all threads
  // since they are constants.
  const std::string requested_device_name_;
  const snd_pcm_format_t pcm_format_;
  const uint32 channels_;
  const uint32 sample_rate_;
  const uint32 bytes_per_sample_;
  const uint32 bytes_per_frame_;

  // Device configuration data. Populated after OpenTask() completes.
  std::string device_name_;
  bool should_downmix_;
  uint32 packet_size_;
  uint32 micros_per_packet_;
  uint32 latency_micros_;
  uint32 bytes_per_output_frame_;
  uint32 alsa_buffer_frames_;

  // Flag indicating the code should stop reading from the data source or
  // writing to the ALSA device.  This is set because the device has entered
  // an unrecoverable error state, or the ClosedTask() has executed.
  bool stop_stream_;

  // Wrapper class to invoke all the ALSA functions.
  AlsaWrapper* wrapper_;

  // Audio manager that created us.  Used to report that we've been closed.
  // This should only be used on the |client_thread_loop_|.  Access via
  // the manager() function.
  AudioManagerLinux* manager_;

  // Handle to the actual PCM playback device.
  snd_pcm_t* playback_handle_;

  scoped_ptr<media::SeekableBuffer> buffer_;
  uint32 frames_per_packet_;

  // Used to check which message loop is allowed to call the public APIs.
  MessageLoop* client_thread_loop_;

  // The message loop responsible for querying the data source, and writing to
  // the output device.
  MessageLoop* message_loop_;

  DISALLOW_COPY_AND_ASSIGN(AlsaPcmOutputStream);
};

#endif  // MEDIA_AUDIO_LINUX_ALSA_OUTPUT_H_