Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(246)

Side by Side Diff: media/audio/linux/pulse_output.cc

Issue 7473021: PulseAudio Sound Playback on Linux (Closed) Base URL: http://git.chromium.org/git/chromium.git@trunk
Patch Set: "vrk comments response" Created 9 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "media/audio/linux/pulse_output.h"
6
7 #include "base/message_loop.h"
8 #include "media/audio/audio_util.h"
9 #include "media/audio/audio_parameters.h"
vrk (LEFT CHROMIUM) 2011/08/18 18:22:22 nit: abc order of includes
slock 2011/08/18 18:54:16 Done.
10 #include "media/audio/linux/audio_manager_linux.h"
11 #include "media/base/data_buffer.h"
12 #include "media/base/seekable_buffer.h"
13
14 static pa_sample_format_t BitsToFormat(int bits_per_sample) {
15 switch (bits_per_sample) {
16 // Unsupported sample formats shown for reference. I am assuming we want
17 // signed and little endian because that is what we gave to ALSA.
18 case 8:
19 return PA_SAMPLE_U8;
20 // Also 8-bits: PA_SAMPLE_ALAW and PA_SAMPLE_ULAW
21 case 16:
22 return PA_SAMPLE_S16LE;
23 // Also 16-bits: PA_SAMPLE_S16BE (big endian).
24 case 24:
25 return PA_SAMPLE_S24LE;
26 // Also 24-bits: PA_SAMPLE_S24BE (big endian).
27 // Other cases: PA_SAMPLE_24_32LE (in LSB of 32-bit field, little endian),
28 // and PA_SAMPLE_24_32BE (in LSB of 32-bit field, big endian),
29 case 32:
30 return PA_SAMPLE_S32LE;
31 // Also 32-bits: PA_SAMPLE_S32BE (big endian),
32 // PA_SAMPLE_FLOAT32LE (floating point little endian),
33 // and PA_SAMPLE_FLOAT32BE (floating point big endian).
34 default:
35 return PA_SAMPLE_INVALID;
36 }
37 }
38
39 static size_t MicrosecondsToBytes(
40 uint32 microseconds, uint32 sample_rate, size_t bytes_per_frame) {
41 return microseconds * sample_rate * bytes_per_frame /
42 base::Time::kMicrosecondsPerSecond;
43 }
44
45 void PulseAudioOutputStream::ContextStateCallback(pa_context* context,
46 void* state_addr) {
47 pa_context_state_t* state = static_cast<pa_context_state_t*>(state_addr);
48 *state = pa_context_get_state(context);
49 }
50
51 void PulseAudioOutputStream::WriteRequestCallback(
52 pa_stream* playback_handle, size_t length, void* stream_addr) {
53 PulseAudioOutputStream* stream =
54 static_cast<PulseAudioOutputStream*>(stream_addr);
55
56 DCHECK_EQ(stream->message_loop_, MessageLoop::current());
57
58 stream->write_callback_handled_ = true;
59
60 // Fulfill write request.
61 stream->FulfillWriteRequest(length);
62
63 // Continue playback.
64 stream->message_loop_->PostTask(
vrk (LEFT CHROMIUM) 2011/08/18 18:22:22 I believe this should be in the "else" block for i
slock 2011/08/18 18:54:16 Done.
65 FROM_HERE,
66 stream->method_factory_.NewRunnableMethod(
67 &PulseAudioOutputStream::WaitForWriteRequest));
68 }
69
70 PulseAudioOutputStream::PulseAudioOutputStream(const AudioParameters& params,
71 AudioManagerLinux* manager,
72 MessageLoop* message_loop)
73 : channel_layout_(params.channel_layout),
74 channel_count_(ChannelLayoutToChannelCount(channel_layout_)),
75 sample_format_(BitsToFormat(params.bits_per_sample)),
76 sample_rate_(params.sample_rate),
77 bytes_per_frame_(params.channels * params.bits_per_sample / 8),
78 manager_(manager),
79 pa_context_(NULL),
80 pa_mainloop_(NULL),
81 playback_handle_(NULL),
82 packet_size_(params.GetPacketSize()),
83 frames_per_packet_(packet_size_ / bytes_per_frame_),
84 client_buffer_(NULL),
85 volume_(1.0f),
86 stream_stopped_(true),
87 write_callback_handled_(false),
88 message_loop_(message_loop),
89 ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
90 source_callback_(NULL) {
91 DCHECK_EQ(message_loop_, MessageLoop::current());
92 DCHECK(manager_);
93
94 // TODO(slock): Sanity check input values.
95 }
96
97 PulseAudioOutputStream::~PulseAudioOutputStream() {
98 // All internal structures should already have been freed in Close(),
99 // which calls AudioManagerLinux::Release which deletes this object.
100 DCHECK(!playback_handle_);
101 DCHECK(!pa_context_);
102 DCHECK(!pa_mainloop_);
103 }
104
105 bool PulseAudioOutputStream::Open() {
106 DCHECK_EQ(message_loop_, MessageLoop::current());
107
108 // TODO(slock): Possibly move most of this to a OpenPlaybackDevice function in
vrk (LEFT CHROMIUM) 2011/08/18 18:22:22 nit: "to an"
slock 2011/08/18 18:54:16 Done.
109 // a new class 'pulse_util', like alsa_util.
110
111 // Create a mainloop API and connect to the default server.
112 pa_mainloop_ = pa_mainloop_new();
113 pa_mainloop_api* pa_mainloop_api = pa_mainloop_get_api(pa_mainloop_);
114 pa_context_ = pa_context_new(pa_mainloop_api, "Chromium");
115 pa_context_state_t pa_context_state = PA_CONTEXT_UNCONNECTED;
116 pa_context_connect(pa_context_, NULL, PA_CONTEXT_NOFLAGS, NULL);
117
118 // Wait until PulseAudio is ready.
119 pa_context_set_state_callback(pa_context_, &ContextStateCallback,
120 &pa_context_state);
121 while (pa_context_state != PA_CONTEXT_READY) {
122 pa_mainloop_iterate(pa_mainloop_, 1, NULL);
123 if (pa_context_state == PA_CONTEXT_FAILED ||
124 pa_context_state == PA_CONTEXT_TERMINATED) {
125 Reset();
126 return false;
127 }
128 }
129
130 // Set sample specifications and open playback stream.
131 pa_sample_spec pa_sample_specifications;
132 pa_sample_specifications.format = sample_format_;
133 pa_sample_specifications.rate = sample_rate_;
134 pa_sample_specifications.channels = channel_count_;
135 playback_handle_ = pa_stream_new(pa_context_, "Playback",
136 &pa_sample_specifications, NULL);
137
138 // Initialize client buffer.
139 uint32 output_packet_size = frames_per_packet_ * bytes_per_frame_;
140 client_buffer_.reset(new media::SeekableBuffer(0, output_packet_size));
141
142 // Set write callback.
143 pa_stream_set_write_callback(playback_handle_, &WriteRequestCallback,
144 this);
vrk (LEFT CHROMIUM) 2011/08/18 18:22:22 nit: move this to line above?
slock 2011/08/18 18:54:16 Done.
145
146 // Set server-side buffer attributes.
147 // (uint32_t)-1 is the default and recommended value from PulseAudio's
148 // documentation, found at:
149 // http://freedesktop.org/software/pulseaudio/doxygen/structpa__buffer__attr.h tml.
150 pa_buffer_attr pa_buffer_attributes;
151 pa_buffer_attributes.maxlength = static_cast<uint32_t>(-1);
152 pa_buffer_attributes.tlength = output_packet_size;
153 pa_buffer_attributes.prebuf = static_cast<uint32_t>(-1);
154 pa_buffer_attributes.minreq = static_cast<uint32_t>(-1);
155 pa_buffer_attributes.fragsize = static_cast<uint32_t>(-1);
156
157 // Connect playback stream.
158 pa_stream_connect_playback(playback_handle_, NULL,
159 &pa_buffer_attributes,
160 (pa_stream_flags_t)
161 (PA_STREAM_INTERPOLATE_TIMING |
162 PA_STREAM_ADJUST_LATENCY |
163 PA_STREAM_AUTO_TIMING_UPDATE),
164 NULL, NULL);
165
166 if (!playback_handle_) {
167 Reset();
168 return false;
169 }
170
171 return true;
172 }
173
174 void PulseAudioOutputStream::Reset() {
175 stream_stopped_ = true;
176
177 // Close the stream.
178 if (playback_handle_) {
179 pa_stream_flush(playback_handle_, NULL, NULL);
180 pa_stream_disconnect(playback_handle_);
181
182 // Release PulseAudio structures.
183 pa_stream_unref(playback_handle_);
184 playback_handle_ = NULL;
185 }
186 if (pa_context_) {
187 pa_context_unref(pa_context_);
188 pa_context_ = NULL;
189 }
190 if (pa_mainloop_) {
191 pa_mainloop_free(pa_mainloop_);
192 pa_mainloop_ = NULL;
193 }
194
195 // Release internal buffer.
196 client_buffer_.reset();
197 }
198
199 void PulseAudioOutputStream::Close() {
200 DCHECK_EQ(message_loop_, MessageLoop::current());
201
202 Reset();
203
204 // Signal to the manager that we're closed and can be removed.
205 // This should be the last call in the function as it deletes "this".
206 manager_->ReleaseOutputStream(this);
207 }
208
209 void PulseAudioOutputStream::WaitForWriteRequest() {
210 DCHECK_EQ(message_loop_, MessageLoop::current());
211
212 // Iterate the PulseAudio mainloop. If the stream isn't stopped or PulseAudio
213 // doesn't request a write, post a task to iterate the mainloop again.
214 write_callback_handled_ = false;
215 pa_mainloop_iterate(pa_mainloop_, 1, NULL);
216 if (!write_callback_handled_ && !stream_stopped_) {
217 message_loop_->PostTask(
218 FROM_HERE,
219 method_factory_.NewRunnableMethod(
220 &PulseAudioOutputStream::WaitForWriteRequest));
221 }
222 }
223
224 bool PulseAudioOutputStream::BufferPacketFromSource() {
225 uint32 buffer_delay = client_buffer_->forward_bytes();
226 pa_usec_t pa_latency_micros;
227 int negative;
228 pa_stream_get_latency(playback_handle_, &pa_latency_micros, &negative);
229 uint32 hardware_delay = MicrosecondsToBytes(pa_latency_micros,
230 sample_rate_,
231 bytes_per_frame_);
232 // TODO(slock): Deal with negative latency (negative == 1). This has yet
233 // to happen in practice though.
234 scoped_refptr<media::DataBuffer> packet =
235 new media::DataBuffer(packet_size_);
236 size_t packet_size = RunDataCallback(packet->GetWritableData(),
237 packet->GetBufferSize(),
238 AudioBuffersState(buffer_delay,
239 hardware_delay));
240
241 if (packet_size == 0)
242 return false;
243
244 // TODO(slock): Swizzling and downmixing.
245 media::AdjustVolume(packet->GetWritableData(),
246 packet_size,
247 channel_count_,
248 bytes_per_frame_ / channel_count_,
249 volume_);
250 packet->SetDataSize(packet_size);
251 // Add the packet to the buffer.
252 client_buffer_->Append(packet);
253 return true;
254 }
255
256 void PulseAudioOutputStream::FulfillWriteRequest(size_t requested_bytes) {
257 // If we have enough data to fulfill the request, we can finish the write.
258 if (stream_stopped_)
259 return;
260
261 // Request more data from the source until we can fulfill the request or
262 // fail to receive anymore data.
263 bool buffering_successful = true;
264 while (client_buffer_->forward_bytes() < requested_bytes &&
265 buffering_successful) {
266 buffering_successful = BufferPacketFromSource();
267 }
268
269 size_t bytes_written = 0;
270 if (client_buffer_->forward_bytes() > 0)
271 // Try to fulfill the request by writing as many of the requested bytes to
vrk (LEFT CHROMIUM) 2011/08/18 18:22:22 nit: Put the comment above the if statement, or pu
slock 2011/08/18 18:54:16 Done. I didn't if 'multi-line' included comments
272 // the stream as we can.
273 WriteToStream(requested_bytes, &bytes_written);
274
275 if (bytes_written < requested_bytes)
vrk (LEFT CHROMIUM) 2011/08/18 18:22:22 nit: curly braces around multi-line blocks
slock 2011/08/18 18:54:16 Done.
276 // We weren't able to buffer enough data to fulfill the request. Try to
277 // fulfill the rest of the request later.
278 message_loop_->PostTask(
279 FROM_HERE,
280 method_factory_.NewRunnableMethod(
281 &PulseAudioOutputStream::FulfillWriteRequest,
282 requested_bytes - bytes_written));
283 }
284
285 void PulseAudioOutputStream::WriteToStream(size_t bytes_to_write,
286 size_t* bytes_written) {
287 *bytes_written = 0;
288 const uint8* chunk;
289 size_t chunk_size;
290
291 // Try to get data to write.
292 bool buffer_exhausted = !client_buffer_->GetCurrentChunk(&chunk, &chunk_size);
293 while (*bytes_written < bytes_to_write && !buffer_exhausted) {
294 // Write data to stream.
295 pa_stream_write(playback_handle_, chunk, chunk_size,
296 NULL, 0LL, PA_SEEK_RELATIVE);
297 client_buffer_->Seek(chunk_size);
298 *bytes_written += chunk_size;
299
300 // Get more data.
301 buffer_exhausted = client_buffer_->GetCurrentChunk(&chunk, &chunk_size);
vrk (LEFT CHROMIUM) 2011/08/18 18:22:22 logic is incorrect: should be buffer_exhausted =
slock 2011/08/18 18:54:16 Done.
302 }
303 }
304
305 void PulseAudioOutputStream::Start(AudioSourceCallback* callback) {
306 DCHECK_EQ(message_loop_, MessageLoop::current());
307
308 CHECK(callback);
309 source_callback_ = callback;
310
311 // Clear buffer, it might still have data in it.
312 client_buffer_->Clear();
313 stream_stopped_ = false;
314
315 // Start playback.
316 message_loop_->PostTask(
317 FROM_HERE,
318 method_factory_.NewRunnableMethod(
319 &PulseAudioOutputStream::WaitForWriteRequest));
320 }
321
322 void PulseAudioOutputStream::Stop() {
323 DCHECK_EQ(message_loop_, MessageLoop::current());
324
325 stream_stopped_ = true;
326 }
327
328 void PulseAudioOutputStream::SetVolume(double volume) {
329 DCHECK_EQ(message_loop_, MessageLoop::current());
330
331 volume_ = static_cast<float>(volume);
332 }
333
334 void PulseAudioOutputStream::GetVolume(double* volume) {
335 DCHECK_EQ(message_loop_, MessageLoop::current());
336
337 *volume = volume_;
338 }
339
340 uint32 PulseAudioOutputStream::RunDataCallback(
341 uint8* dest, uint32 max_size, AudioBuffersState buffers_state) {
342 if (source_callback_)
343 return source_callback_->OnMoreData(this, dest, max_size, buffers_state);
344
345 return 0;
346 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698