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
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
|
// Copyright (c) 2011 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 "media/audio/pulse/pulse_output.h"
#include "base/bind.h"
#include "base/message_loop.h"
#include "media/audio/audio_parameters.h"
#include "media/audio/audio_util.h"
#if defined(OS_LINUX)
#include "media/audio/linux/audio_manager_linux.h"
#elif defined(OS_OPENBSD)
#include "media/audio/openbsd/audio_manager_openbsd.h"
#endif
#include "media/base/data_buffer.h"
#include "media/base/seekable_buffer.h"
static pa_sample_format_t BitsToPASampleFormat(int bits_per_sample) {
switch (bits_per_sample) {
// Unsupported sample formats shown for reference. I am assuming we want
// signed and little endian because that is what we gave to ALSA.
case 8:
return PA_SAMPLE_U8;
// Also 8-bits: PA_SAMPLE_ALAW and PA_SAMPLE_ULAW
case 16:
return PA_SAMPLE_S16LE;
// Also 16-bits: PA_SAMPLE_S16BE (big endian).
case 24:
return PA_SAMPLE_S24LE;
// Also 24-bits: PA_SAMPLE_S24BE (big endian).
// Other cases: PA_SAMPLE_24_32LE (in LSB of 32-bit field, little endian),
// and PA_SAMPLE_24_32BE (in LSB of 32-bit field, big endian),
case 32:
return PA_SAMPLE_S32LE;
// Also 32-bits: PA_SAMPLE_S32BE (big endian),
// PA_SAMPLE_FLOAT32LE (floating point little endian),
// and PA_SAMPLE_FLOAT32BE (floating point big endian).
default:
return PA_SAMPLE_INVALID;
}
}
static pa_channel_position ChromiumToPAChannelPosition(Channels channel) {
switch (channel) {
// PulseAudio does not differentiate between left/right and
// stereo-left/stereo-right, both translate to front-left/front-right.
case LEFT:
case STEREO_LEFT:
return PA_CHANNEL_POSITION_FRONT_LEFT;
case RIGHT:
case STEREO_RIGHT:
return PA_CHANNEL_POSITION_FRONT_RIGHT;
case CENTER:
return PA_CHANNEL_POSITION_FRONT_CENTER;
case LFE:
return PA_CHANNEL_POSITION_LFE;
case BACK_LEFT:
return PA_CHANNEL_POSITION_REAR_LEFT;
case BACK_RIGHT:
return PA_CHANNEL_POSITION_REAR_RIGHT;
case LEFT_OF_CENTER:
return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
case RIGHT_OF_CENTER:
return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
case BACK_CENTER:
return PA_CHANNEL_POSITION_REAR_CENTER;
case SIDE_LEFT:
return PA_CHANNEL_POSITION_SIDE_LEFT;
case SIDE_RIGHT:
return PA_CHANNEL_POSITION_SIDE_RIGHT;
case CHANNELS_MAX:
return PA_CHANNEL_POSITION_INVALID;
}
NOTREACHED() << "Invalid channel " << channel;
return PA_CHANNEL_POSITION_INVALID;
}
static pa_channel_map ChannelLayoutToPAChannelMap(
ChannelLayout channel_layout) {
// Initialize channel map.
pa_channel_map channel_map;
pa_channel_map_init(&channel_map);
channel_map.channels = ChannelLayoutToChannelCount(channel_layout);
// All channel maps have the same size array of channel positions.
for (unsigned int channel = 0; channel != CHANNELS_MAX; ++channel) {
int channel_position = kChannelOrderings[channel_layout][channel];
if (channel_position > -1) {
channel_map.map[channel_position] = ChromiumToPAChannelPosition(
static_cast<Channels>(channel));
} else {
// PulseAudio expects unused channels in channel maps to be filled with
// PA_CHANNEL_POSITION_MONO.
channel_map.map[channel_position] = PA_CHANNEL_POSITION_MONO;
}
}
// Fill in the rest of the unused channels.
for (unsigned int channel = CHANNELS_MAX; channel != PA_CHANNELS_MAX;
++channel) {
channel_map.map[channel] = PA_CHANNEL_POSITION_MONO;
}
return channel_map;
}
static size_t MicrosecondsToBytes(
uint32 microseconds, uint32 sample_rate, size_t bytes_per_frame) {
return microseconds * sample_rate * bytes_per_frame /
base::Time::kMicrosecondsPerSecond;
}
void PulseAudioOutputStream::ContextStateCallback(pa_context* context,
void* state_addr) {
pa_context_state_t* state = static_cast<pa_context_state_t*>(state_addr);
*state = pa_context_get_state(context);
}
void PulseAudioOutputStream::WriteRequestCallback(
pa_stream* playback_handle, size_t length, void* stream_addr) {
PulseAudioOutputStream* stream =
static_cast<PulseAudioOutputStream*>(stream_addr);
DCHECK_EQ(stream->message_loop_, MessageLoop::current());
stream->write_callback_handled_ = true;
// Fulfill write request.
stream->FulfillWriteRequest(length);
}
PulseAudioOutputStream::PulseAudioOutputStream(const AudioParameters& params,
AudioManagerPulse* manager,
MessageLoop* message_loop)
: channel_layout_(params.channel_layout),
channel_count_(ChannelLayoutToChannelCount(channel_layout_)),
sample_format_(BitsToPASampleFormat(params.bits_per_sample)),
sample_rate_(params.sample_rate),
bytes_per_frame_(params.channels * params.bits_per_sample / 8),
manager_(manager),
pa_context_(NULL),
pa_mainloop_(NULL),
playback_handle_(NULL),
packet_size_(params.GetPacketSize()),
frames_per_packet_(packet_size_ / bytes_per_frame_),
client_buffer_(NULL),
volume_(1.0f),
stream_stopped_(true),
write_callback_handled_(false),
message_loop_(message_loop),
ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
source_callback_(NULL) {
DCHECK_EQ(message_loop_, MessageLoop::current());
DCHECK(manager_);
// TODO(slock): Sanity check input values.
}
PulseAudioOutputStream::~PulseAudioOutputStream() {
// All internal structures should already have been freed in Close(),
// which calls AudioManagerPulse::Release which deletes this object.
DCHECK(!playback_handle_);
DCHECK(!pa_context_);
DCHECK(!pa_mainloop_);
}
bool PulseAudioOutputStream::Open() {
DCHECK_EQ(message_loop_, MessageLoop::current());
// TODO(slock): Possibly move most of this to an OpenPlaybackDevice function
// in a new class 'pulse_util', like alsa_util.
// Create a mainloop API and connect to the default server.
pa_mainloop_ = pa_mainloop_new();
pa_mainloop_api* pa_mainloop_api = pa_mainloop_get_api(pa_mainloop_);
pa_context_ = pa_context_new(pa_mainloop_api, "Chromium");
pa_context_state_t pa_context_state = PA_CONTEXT_UNCONNECTED;
pa_context_connect(pa_context_, NULL, PA_CONTEXT_NOFLAGS, NULL);
// Wait until PulseAudio is ready.
pa_context_set_state_callback(pa_context_, &ContextStateCallback,
&pa_context_state);
while (pa_context_state != PA_CONTEXT_READY) {
pa_mainloop_iterate(pa_mainloop_, 1, NULL);
if (pa_context_state == PA_CONTEXT_FAILED ||
pa_context_state == PA_CONTEXT_TERMINATED) {
Reset();
return false;
}
}
// Set sample specifications.
pa_sample_spec pa_sample_specifications;
pa_sample_specifications.format = sample_format_;
pa_sample_specifications.rate = sample_rate_;
pa_sample_specifications.channels = channel_count_;
// Get channel mapping and open playback stream.
pa_channel_map* map = NULL;
pa_channel_map source_channel_map = ChannelLayoutToPAChannelMap(
channel_layout_);
if (source_channel_map.channels != 0) {
// The source data uses a supported channel map so we will use it rather
// than the default channel map (NULL).
map = &source_channel_map;
}
playback_handle_ = pa_stream_new(pa_context_, "Playback",
&pa_sample_specifications, map);
// Initialize client buffer.
uint32 output_packet_size = frames_per_packet_ * bytes_per_frame_;
client_buffer_.reset(new media::SeekableBuffer(0, output_packet_size));
// Set write callback.
pa_stream_set_write_callback(playback_handle_, &WriteRequestCallback, this);
// Set server-side buffer attributes.
// (uint32_t)-1 is the default and recommended value from PulseAudio's
// documentation, found at:
// http://freedesktop.org/software/pulseaudio/doxygen/structpa__buffer__attr.html.
pa_buffer_attr pa_buffer_attributes;
pa_buffer_attributes.maxlength = static_cast<uint32_t>(-1);
pa_buffer_attributes.tlength = output_packet_size;
pa_buffer_attributes.prebuf = static_cast<uint32_t>(-1);
pa_buffer_attributes.minreq = static_cast<uint32_t>(-1);
pa_buffer_attributes.fragsize = static_cast<uint32_t>(-1);
// Connect playback stream.
pa_stream_connect_playback(playback_handle_, NULL,
&pa_buffer_attributes,
(pa_stream_flags_t)
(PA_STREAM_INTERPOLATE_TIMING |
PA_STREAM_ADJUST_LATENCY |
PA_STREAM_AUTO_TIMING_UPDATE),
NULL, NULL);
if (!playback_handle_) {
Reset();
return false;
}
return true;
}
void PulseAudioOutputStream::Reset() {
stream_stopped_ = true;
// Close the stream.
if (playback_handle_) {
pa_stream_flush(playback_handle_, NULL, NULL);
pa_stream_disconnect(playback_handle_);
// Release PulseAudio structures.
pa_stream_unref(playback_handle_);
playback_handle_ = NULL;
}
if (pa_context_) {
pa_context_unref(pa_context_);
pa_context_ = NULL;
}
if (pa_mainloop_) {
pa_mainloop_free(pa_mainloop_);
pa_mainloop_ = NULL;
}
// Release internal buffer.
client_buffer_.reset();
}
void PulseAudioOutputStream::Close() {
DCHECK_EQ(message_loop_, MessageLoop::current());
Reset();
// Signal to the manager that we're closed and can be removed.
// This should be the last call in the function as it deletes "this".
manager_->ReleaseOutputStream(this);
}
void PulseAudioOutputStream::WaitForWriteRequest() {
DCHECK_EQ(message_loop_, MessageLoop::current());
if (stream_stopped_)
return;
// Iterate the PulseAudio mainloop. If PulseAudio doesn't request a write,
// post a task to iterate the mainloop again.
write_callback_handled_ = false;
pa_mainloop_iterate(pa_mainloop_, 1, NULL);
if (!write_callback_handled_) {
message_loop_->PostTask(FROM_HERE, base::Bind(
&PulseAudioOutputStream::WaitForWriteRequest,
weak_factory_.GetWeakPtr()));
}
}
bool PulseAudioOutputStream::BufferPacketFromSource() {
uint32 buffer_delay = client_buffer_->forward_bytes();
pa_usec_t pa_latency_micros;
int negative;
pa_stream_get_latency(playback_handle_, &pa_latency_micros, &negative);
uint32 hardware_delay = MicrosecondsToBytes(pa_latency_micros,
sample_rate_,
bytes_per_frame_);
// TODO(slock): Deal with negative latency (negative == 1). This has yet
// to happen in practice though.
scoped_refptr<media::DataBuffer> packet =
new media::DataBuffer(packet_size_);
size_t packet_size = RunDataCallback(packet->GetWritableData(),
packet->GetBufferSize(),
AudioBuffersState(buffer_delay,
hardware_delay));
if (packet_size == 0)
return false;
media::AdjustVolume(packet->GetWritableData(),
packet_size,
channel_count_,
bytes_per_frame_ / channel_count_,
volume_);
packet->SetDataSize(packet_size);
// Add the packet to the buffer.
client_buffer_->Append(packet);
return true;
}
void PulseAudioOutputStream::FulfillWriteRequest(size_t requested_bytes) {
// If we have enough data to fulfill the request, we can finish the write.
if (stream_stopped_)
return;
// Request more data from the source until we can fulfill the request or
// fail to receive anymore data.
bool buffering_successful = true;
while (client_buffer_->forward_bytes() < requested_bytes &&
buffering_successful) {
buffering_successful = BufferPacketFromSource();
}
size_t bytes_written = 0;
if (client_buffer_->forward_bytes() > 0) {
// Try to fulfill the request by writing as many of the requested bytes to
// the stream as we can.
WriteToStream(requested_bytes, &bytes_written);
}
if (bytes_written < requested_bytes) {
// We weren't able to buffer enough data to fulfill the request. Try to
// fulfill the rest of the request later.
message_loop_->PostTask(FROM_HERE, base::Bind(
&PulseAudioOutputStream::FulfillWriteRequest,
weak_factory_.GetWeakPtr(),
requested_bytes - bytes_written));
} else {
// Continue playback.
message_loop_->PostTask(FROM_HERE, base::Bind(
&PulseAudioOutputStream::WaitForWriteRequest,
weak_factory_.GetWeakPtr()));
}
}
void PulseAudioOutputStream::WriteToStream(size_t bytes_to_write,
size_t* bytes_written) {
*bytes_written = 0;
while (*bytes_written < bytes_to_write) {
const uint8* chunk;
size_t chunk_size;
// Stop writing if there is no more data available.
if (!client_buffer_->GetCurrentChunk(&chunk, &chunk_size))
break;
// Write data to stream.
pa_stream_write(playback_handle_, chunk, chunk_size,
NULL, 0LL, PA_SEEK_RELATIVE);
client_buffer_->Seek(chunk_size);
*bytes_written += chunk_size;
}
}
void PulseAudioOutputStream::Start(AudioSourceCallback* callback) {
DCHECK_EQ(message_loop_, MessageLoop::current());
CHECK(callback);
DLOG_IF(ERROR, !playback_handle_)
<< "Open() has not been called successfully";
if (!playback_handle_)
return;
source_callback_ = callback;
// Clear buffer, it might still have data in it.
client_buffer_->Clear();
stream_stopped_ = false;
// Start playback.
message_loop_->PostTask(FROM_HERE, base::Bind(
&PulseAudioOutputStream::WaitForWriteRequest,
weak_factory_.GetWeakPtr()));
}
void PulseAudioOutputStream::Stop() {
DCHECK_EQ(message_loop_, MessageLoop::current());
stream_stopped_ = true;
}
void PulseAudioOutputStream::SetVolume(double volume) {
DCHECK_EQ(message_loop_, MessageLoop::current());
volume_ = static_cast<float>(volume);
}
void PulseAudioOutputStream::GetVolume(double* volume) {
DCHECK_EQ(message_loop_, MessageLoop::current());
*volume = volume_;
}
uint32 PulseAudioOutputStream::RunDataCallback(
uint8* dest, uint32 max_size, AudioBuffersState buffers_state) {
if (source_callback_)
return source_callback_->OnMoreData(this, dest, max_size, buffers_state);
return 0;
}
|