summaryrefslogtreecommitdiffstats
path: root/media/audio/win/waveout_output_win.cc
blob: 77b2d36b6f21b856e6c991a4e9a60b09d9d1ee9b (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
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
// Copyright (c) 2006-2008 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.

#include <windows.h>
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")

#include "media/audio/win/waveout_output_win.h"

#include "base/basictypes.h"
#include "base/logging.h"
#include "media/audio/audio_output.h"
#include "media/audio/win/audio_manager_win.h"

// Some general thoughts about the waveOut API which is badly documented :
// - We use CALLBACK_FUNCTION mode in which XP secretly creates two threads
//   named _MixerCallbackThread and _waveThread which have real-time priority.
//   The callbacks occur in _waveThread.
// - Windows does not provide a way to query if the device is playing or paused
//   thus it forces you to maintain state, which naturally is not exactly
//   synchronized to the actual device state.
// - Some functions, like waveOutReset cannot be called in the callback thread
//   or called in any random state because they deadlock. This results in a
//   non- instantaneous Stop() method. waveOutPrepareHeader seems to be in the
//   same boat.
// - waveOutReset() will forcefully kill the _waveThread so it is important
//   to make sure we are not executing inside the audio source's OnMoreData()
//   or that we take locks inside WaveCallback() or QueueNextPacket().

namespace {

// We settled for a double buffering scheme. It seems to strike a good balance
// between how fast data needs to be provided versus memory usage.
const size_t kNumBuffers = 2;

// The maximum volume for a PCM device on windows. Volume is logarithmic so the
// perceived volume increase sounds linear.
const double kMaxVolumeLevel = 65535.0;

// Sixty four MB is the maximum buffer size per AudioOutputStream.
const size_t kMaxOpenBufferSize = 1024 * 1024 * 64;

// Our sound buffers are allocated once and kept in a linked list using the
// the WAVEHDR::dwUser variable. The last buffer points to the first buffer.
WAVEHDR* GetNextBuffer(WAVEHDR* current) {
  return reinterpret_cast<WAVEHDR*>(current->dwUser);
}

}  // namespace

PCMWaveOutAudioOutputStream::PCMWaveOutAudioOutputStream(
    AudioManagerWin* manager, int channels, int sampling_rate,
    char bits_per_sample, UINT device_id)
    : state_(PCMA_BRAND_NEW),
      manager_(manager),
      device_id_(device_id),
      waveout_(NULL),
      callback_(NULL),
      buffer_(NULL),
      buffer_size_(0) {
  format_.wFormatTag = WAVE_FORMAT_PCM;
  format_.nChannels = channels;
  format_.nSamplesPerSec = sampling_rate;
  format_.wBitsPerSample = bits_per_sample;
  format_.cbSize = 0;
  // The next are computed from above.
  format_.nBlockAlign = (format_.nChannels * format_.wBitsPerSample) / 8;
  format_.nAvgBytesPerSec = format_.nBlockAlign * format_.nSamplesPerSec;
  // The event is auto-reset.
  stopped_event_.Set(::CreateEventW(NULL, FALSE, FALSE, NULL));
}

PCMWaveOutAudioOutputStream::~PCMWaveOutAudioOutputStream() {
  DCHECK(NULL == waveout_);
}

bool PCMWaveOutAudioOutputStream::Open(size_t buffer_size) {
  if (state_ != PCMA_BRAND_NEW)
    return false;
  if (buffer_size > kMaxOpenBufferSize)
    return false;
  // Open the device. We'll be getting callback in WaveCallback function. They
  // occur in a magic, time-critical thread that windows creates.
  MMRESULT result = ::waveOutOpen(&waveout_, device_id_, &format_,
                                  reinterpret_cast<DWORD_PTR>(WaveCallback),
                                  reinterpret_cast<DWORD_PTR>(this),
                                  CALLBACK_FUNCTION);
  if (result != MMSYSERR_NOERROR)
    return false;
  // If we don't have a packet size we use 100ms.
  if (!buffer_size)
    buffer_size = format_.nAvgBytesPerSec / 10;

  SetupBuffers(buffer_size);
  buffer_size_ = buffer_size;
  state_ = PCMA_READY;
  return true;
}

void PCMWaveOutAudioOutputStream::SetupBuffers(size_t rq_size) {
  WAVEHDR* last = NULL;
  WAVEHDR* first = NULL;
  for (int ix = 0; ix != kNumBuffers; ++ix) {
    size_t sz = sizeof(WAVEHDR) + rq_size;
    buffer_ =  reinterpret_cast<WAVEHDR*>(new char[sz]);
    buffer_->lpData = reinterpret_cast<char*>(buffer_) + sizeof(WAVEHDR);
    buffer_->dwBufferLength = rq_size;
    buffer_->dwBytesRecorded = 0;
    buffer_->dwUser = reinterpret_cast<DWORD_PTR>(last);
    buffer_->dwFlags = WHDR_DONE;
    buffer_->dwLoops = 0;
    if (ix == 0)
      first = buffer_;
    last = buffer_;
    // Tell windows sound drivers about our buffers. Not documented what
    // this does but we can guess that causes the OS to keep a reference to
    // the memory pages so the driver can use them without worries.
    ::waveOutPrepareHeader(waveout_, buffer_, sizeof(WAVEHDR));
  }
  // Fix the first buffer to point to the last one.
  first->dwUser = reinterpret_cast<DWORD_PTR>(last);
}

void PCMWaveOutAudioOutputStream::FreeBuffers() {
  WAVEHDR* current = buffer_;
  for (int ix = 0; ix != kNumBuffers; ++ix) {
    WAVEHDR* next = GetNextBuffer(current);
    ::waveOutUnprepareHeader(waveout_, current, sizeof(WAVEHDR));
    delete[] reinterpret_cast<char*>(current);
    current = next;
  }
  buffer_ = NULL;
}

// Initially we ask the source to fill up both audio buffers. If we don't do
// this then we would always get the driver callback when it is about to run
// samples and that would leave too little time to react.
void PCMWaveOutAudioOutputStream::Start(AudioSourceCallback* callback) {
  if (state_ != PCMA_READY)
    return;
  callback_ = callback;
  state_ = PCMA_PLAYING;
  WAVEHDR* buffer = buffer_;
  for (int ix = 0; ix != kNumBuffers; ++ix) {
    QueueNextPacket(buffer);
    buffer = GetNextBuffer(buffer);
  }
  buffer = buffer_;
  // Send the buffers to the audio driver.
  for (int ix = 0; ix != kNumBuffers; ++ix) {
    MMRESULT result = ::waveOutWrite(waveout_, buffer, sizeof(WAVEHDR));
    if (result != MMSYSERR_NOERROR) {
      HandleError(result);
      break;
    }
    buffer = GetNextBuffer(buffer);
  }
}

// Stopping is tricky. First, no buffer should be locked by the audio driver
// or else the waveOutReset will deadlock and secondly, the callback should not
// be inside the AudioSource's OnMoreData because waveOutReset() forcefully
// kills the callback thread.
void PCMWaveOutAudioOutputStream::Stop() {
  if (state_ != PCMA_PLAYING)
    return;
  state_ = PCMA_STOPPING;
  // Wait for the callback to finish, it will signal us when ready to be reset.
  if (WAIT_OBJECT_0 != ::WaitForSingleObject(stopped_event_, INFINITE)) {
    HandleError(::GetLastError());
    return;
  }
  state_ = PCMA_STOPPED;
  MMRESULT res = ::waveOutReset(waveout_);
  if (res != MMSYSERR_NOERROR) {
    state_ = PCMA_PLAYING;
    HandleError(res);
    return;
  }
  state_ = PCMA_READY;
}

// We can Close in any state except that trying to close a stream that is
// playing Windows generates an error, which we propagate to the source.
void PCMWaveOutAudioOutputStream::Close() {
  if (waveout_) {
    // waveOutClose generates a callback with WOM_CLOSE id in the same thread.
    MMRESULT res = ::waveOutClose(waveout_);
    if (res != MMSYSERR_NOERROR) {
      HandleError(res);
      return;
    }
    state_ = PCMA_CLOSED;
    waveout_ = NULL;
    FreeBuffers();
  }
  // Tell the audio manager that we have been released. This can result in
  // the manager destroying us in-place so this needs to be the last thing
  // we do on this function.
  manager_->ReleaseStream(this);
}

void PCMWaveOutAudioOutputStream::SetVolume(double left_level,
                                            double right_level) {
  if (!waveout_)
    return;
  uint16 left = static_cast<uint16>(left_level * kMaxVolumeLevel);
  uint16 right = static_cast<uint16>(right_level * kMaxVolumeLevel);
  DWORD volume_packed = MAKELONG(left, right);
  MMRESULT res = ::waveOutSetVolume(waveout_, volume_packed);
  if (res != MMSYSERR_NOERROR) {
    HandleError(res);
    return;
  }
}

void PCMWaveOutAudioOutputStream::GetVolume(double* left_level,
                                            double* right_level) {
  if (!waveout_)
    return;
  DWORD volume_packed = 0;
  MMRESULT res = ::waveOutGetVolume(waveout_, &volume_packed);
  if (res != MMSYSERR_NOERROR) {
    HandleError(res);
    return;
  }
  *left_level = static_cast<double>(LOWORD(volume_packed)) / kMaxVolumeLevel;
  *right_level = static_cast<double>(HIWORD(volume_packed)) / kMaxVolumeLevel;
}

size_t PCMWaveOutAudioOutputStream::GetNumBuffers() {
  return kNumBuffers;
}

void PCMWaveOutAudioOutputStream::HandleError(MMRESULT error) {
  DLOG(WARNING) << "PCMWaveOutAudio error " << error;
  callback_->OnError(this, error);
}

void PCMWaveOutAudioOutputStream::QueueNextPacket(WAVEHDR *buffer) {
  // Call the source which will fill our buffer with pleasant sounds and
  // return to us how many bytes were used.
  // TODO(fbarchard): Handle used 0 by queueing more.
  size_t used = callback_->OnMoreData(this, buffer->lpData, buffer_size_);
  if (used <= buffer_size_) {
    buffer->dwBufferLength = used;
  } else {
    HandleError(0);
    return;
  }
  buffer->dwFlags = WHDR_PREPARED;
}

// Windows call us back in this function when some events happen. Most notably
// when it is done playing a buffer. Since we use double buffering it is
// convenient to think of |buffer| as free and GetNextBuffer(buffer) as in
// use by the driver.
void PCMWaveOutAudioOutputStream::WaveCallback(HWAVEOUT hwo, UINT msg,
                                               DWORD_PTR instance,
                                               DWORD_PTR param1, DWORD_PTR) {
  PCMWaveOutAudioOutputStream* obj =
      reinterpret_cast<PCMWaveOutAudioOutputStream*>(instance);

  if (msg == WOM_DONE) {
    // WOM_DONE indicates that the driver is done with our buffer, we can
    // either ask the source for more data or check if we need to stop playing.
    WAVEHDR* buffer = reinterpret_cast<WAVEHDR*>(param1);
    buffer->dwFlags = WHDR_DONE;

    if (obj->state_ == PCMA_STOPPING) {
      // The main thread has called Stop() and is waiting to issue waveOutReset
      // which will kill this thread. We should not enter AudioSourceCallback
      // code anymore.
      ::SetEvent(obj->stopped_event_);
      return;
    } else if (obj->state_ == PCMA_STOPPED) {
      // Not sure if ever hit this but just in case.
      return;
    }
    obj->QueueNextPacket(buffer);
    // Time to send the buffer to the audio driver. Since we are reusing
    // the same buffers we can get away without calling waveOutPrepareHeader.
    MMRESULT result = ::waveOutWrite(hwo, buffer, sizeof(WAVEHDR));
    if (result != MMSYSERR_NOERROR)
      obj->HandleError(result);

  } else if (msg == WOM_CLOSE) {
    // We can be closed before calling Start, so it is possible to have a
    // null callback at this point.
    if (obj->callback_)
      obj->callback_->OnClose(obj);
  }
}