1 // Copyright (c) 2012 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.
7 // AlsaPcmOutputStream object is *not* thread-safe and should only be used
8 // from the audio thread. We DCHECK on this assumption whenever we can.
10 // SEMANTICS OF Close()
12 // Close() is responsible for cleaning up any resources that were acquired after
13 // a successful Open(). Close() will nullify any scheduled outstanding runnable
17 // SEMANTICS OF ERROR STATES
19 // The object has two distinct error states: |state_| == kInError
20 // and |stop_stream_|. The |stop_stream_| variable is used to indicate
21 // that the playback_handle should no longer be used either because of a
22 // hardware/low-level event.
24 // When |state_| == kInError, all public API functions will fail with an error
25 // (Start() will call the OnError() function on the callback immediately), or
26 // no-op themselves with the exception of Close(). Even if an error state has
27 // been entered, if Open() has previously returned successfully, Close() must be
28 // called to cleanup the ALSA devices and release resources.
30 // When |stop_stream_| is set, no more commands will be made against the
31 // ALSA device, and playback will effectively stop. From the client's point of
32 // view, it will seem that the device has just clogged and stopped requesting
35 #include "media/audio/linux/alsa_output.h"
39 #include "base/bind.h"
40 #include "base/debug/trace_event.h"
41 #include "base/logging.h"
42 #include "base/message_loop/message_loop.h"
43 #include "base/stl_util.h"
44 #include "base/time/time.h"
45 #include "media/audio/linux/alsa_util.h"
46 #include "media/audio/linux/alsa_wrapper.h"
47 #include "media/audio/linux/audio_manager_linux.h"
48 #include "media/base/channel_mixer.h"
49 #include "media/base/data_buffer.h"
50 #include "media/base/seekable_buffer.h"
54 // Set to 0 during debugging if you want error messages due to underrun
55 // events or other recoverable errors.
57 static const int kPcmRecoverIsSilent
= 1;
59 static const int kPcmRecoverIsSilent
= 0;
62 // While the "default" device may support multi-channel audio, in Alsa, only
63 // the device names surround40, surround41, surround50, etc, have a defined
64 // channel mapping according to Lennart:
66 // http://0pointer.de/blog/projects/guide-to-sound-apis.html
68 // This function makes a best guess at the specific > 2 channel device name
69 // based on the number of channels requested. NULL is returned if no device
70 // can be found to match the channel numbers. In this case, using
71 // kDefaultDevice is probably the best bet.
73 // A five channel source is assumed to be surround50 instead of surround41
74 // (which is also 5 channels).
76 // TODO(ajwong): The source data should have enough info to tell us if we want
77 // surround41 versus surround51, etc., instead of needing us to guess based on
78 // channel number. Fix API to pass that data down.
79 static const char* GuessSpecificDeviceName(uint32 channels
) {
101 std::ostream
& operator<<(std::ostream
& os
,
102 AlsaPcmOutputStream::InternalState state
) {
104 case AlsaPcmOutputStream::kInError
:
107 case AlsaPcmOutputStream::kCreated
:
110 case AlsaPcmOutputStream::kIsOpened
:
113 case AlsaPcmOutputStream::kIsPlaying
:
116 case AlsaPcmOutputStream::kIsStopped
:
119 case AlsaPcmOutputStream::kIsClosed
:
126 const char AlsaPcmOutputStream::kDefaultDevice
[] = "default";
127 const char AlsaPcmOutputStream::kAutoSelectDevice
[] = "";
128 const char AlsaPcmOutputStream::kPlugPrefix
[] = "plug:";
130 // We use 40ms as our minimum required latency. If it is needed, we may be able
131 // to get it down to 20ms.
132 const uint32
AlsaPcmOutputStream::kMinLatencyMicros
= 40 * 1000;
134 AlsaPcmOutputStream::AlsaPcmOutputStream(const std::string
& device_name
,
135 const AudioParameters
& params
,
136 AlsaWrapper
* wrapper
,
137 AudioManagerLinux
* manager
)
138 : requested_device_name_(device_name
),
139 pcm_format_(alsa_util::BitsToFormat(params
.bits_per_sample())),
140 channels_(params
.channels()),
141 channel_layout_(params
.channel_layout()),
142 sample_rate_(params
.sample_rate()),
143 bytes_per_sample_(params
.bits_per_sample() / 8),
144 bytes_per_frame_(params
.GetBytesPerFrame()),
145 packet_size_(params
.GetBytesPerBuffer()),
147 base::TimeDelta::FromMicroseconds(kMinLatencyMicros
),
148 FramesToTimeDelta(params
.frames_per_buffer() * 2, sample_rate_
))),
149 bytes_per_output_frame_(bytes_per_frame_
),
150 alsa_buffer_frames_(0),
154 message_loop_(base::MessageLoop::current()),
155 playback_handle_(NULL
),
156 frames_per_packet_(packet_size_
/ bytes_per_frame_
),
160 source_callback_(NULL
),
161 audio_bus_(AudioBus::Create(params
)) {
162 DCHECK(manager_
->GetMessageLoop()->BelongsToCurrentThread());
163 DCHECK_EQ(audio_bus_
->frames() * bytes_per_frame_
, packet_size_
);
165 // Sanity check input values.
166 if (!params
.IsValid()) {
167 LOG(WARNING
) << "Unsupported audio parameters.";
168 TransitionTo(kInError
);
171 if (pcm_format_
== SND_PCM_FORMAT_UNKNOWN
) {
172 LOG(WARNING
) << "Unsupported bits per sample: " << params
.bits_per_sample();
173 TransitionTo(kInError
);
177 AlsaPcmOutputStream::~AlsaPcmOutputStream() {
178 InternalState current_state
= state();
179 DCHECK(current_state
== kCreated
||
180 current_state
== kIsClosed
||
181 current_state
== kInError
);
182 DCHECK(!playback_handle_
);
185 bool AlsaPcmOutputStream::Open() {
186 DCHECK(IsOnAudioThread());
188 if (state() == kInError
)
191 if (!CanTransitionTo(kIsOpened
)) {
192 NOTREACHED() << "Invalid state: " << state();
196 // We do not need to check if the transition was successful because
197 // CanTransitionTo() was checked above, and it is assumed that this
198 // object's public API is only called on one thread so the state cannot
199 // transition out from under us.
200 TransitionTo(kIsOpened
);
202 // Try to open the device.
203 if (requested_device_name_
== kAutoSelectDevice
) {
204 playback_handle_
= AutoSelectDevice(latency_
.InMicroseconds());
205 if (playback_handle_
)
206 DVLOG(1) << "Auto-selected device: " << device_name_
;
208 device_name_
= requested_device_name_
;
209 playback_handle_
= alsa_util::OpenPlaybackDevice(
210 wrapper_
, device_name_
.c_str(), channels_
, sample_rate_
,
211 pcm_format_
, latency_
.InMicroseconds());
214 // Finish initializing the stream if the device was opened successfully.
215 if (playback_handle_
== NULL
) {
217 TransitionTo(kInError
);
220 bytes_per_output_frame_
= channel_mixer_
?
221 mixed_audio_bus_
->channels() * bytes_per_sample_
: bytes_per_frame_
;
222 uint32 output_packet_size
= frames_per_packet_
* bytes_per_output_frame_
;
223 buffer_
.reset(new media::SeekableBuffer(0, output_packet_size
));
225 // Get alsa buffer size.
226 snd_pcm_uframes_t buffer_size
;
227 snd_pcm_uframes_t period_size
;
228 int error
= wrapper_
->PcmGetParams(playback_handle_
, &buffer_size
,
231 LOG(ERROR
) << "Failed to get playback buffer size from ALSA: "
232 << wrapper_
->StrError(error
);
233 // Buffer size is at least twice of packet size.
234 alsa_buffer_frames_
= frames_per_packet_
* 2;
236 alsa_buffer_frames_
= buffer_size
;
243 void AlsaPcmOutputStream::Close() {
244 DCHECK(IsOnAudioThread());
246 if (state() != kIsClosed
)
247 TransitionTo(kIsClosed
);
249 // Shutdown the audio device.
250 if (playback_handle_
) {
251 if (alsa_util::CloseDevice(wrapper_
, playback_handle_
) < 0) {
252 LOG(WARNING
) << "Unable to close audio device. Leaking handle.";
254 playback_handle_
= NULL
;
256 // Release the buffer.
259 // Signal anything that might already be scheduled to stop.
260 stop_stream_
= true; // Not necessary in production, but unit tests
261 // uses the flag to verify that stream was closed.
264 weak_factory_
.InvalidateWeakPtrs();
266 // Signal to the manager that we're closed and can be removed.
267 // Should be last call in the method as it deletes "this".
268 manager_
->ReleaseOutputStream(this);
271 void AlsaPcmOutputStream::Start(AudioSourceCallback
* callback
) {
272 DCHECK(IsOnAudioThread());
279 // Only post the task if we can enter the playing state.
280 if (TransitionTo(kIsPlaying
) != kIsPlaying
)
283 // Before starting, the buffer might have audio from previous user of this
287 // When starting again, drop all packets in the device and prepare it again
288 // in case we are restarting from a pause state and need to flush old data.
289 int error
= wrapper_
->PcmDrop(playback_handle_
);
290 if (error
< 0 && error
!= -EAGAIN
) {
291 LOG(ERROR
) << "Failure clearing playback device ("
292 << wrapper_
->PcmName(playback_handle_
) << "): "
293 << wrapper_
->StrError(error
);
298 error
= wrapper_
->PcmPrepare(playback_handle_
);
299 if (error
< 0 && error
!= -EAGAIN
) {
300 LOG(ERROR
) << "Failure preparing stream ("
301 << wrapper_
->PcmName(playback_handle_
) << "): "
302 << wrapper_
->StrError(error
);
307 // Ensure the first buffer is silence to avoid startup glitches.
308 int buffer_size
= GetAvailableFrames() * bytes_per_output_frame_
;
309 scoped_refptr
<DataBuffer
> silent_packet
= new DataBuffer(buffer_size
);
310 silent_packet
->set_data_size(buffer_size
);
311 memset(silent_packet
->writable_data(), 0, silent_packet
->data_size());
312 buffer_
->Append(silent_packet
);
315 // Start the callback chain.
316 set_source_callback(callback
);
320 void AlsaPcmOutputStream::Stop() {
321 DCHECK(IsOnAudioThread());
323 // Reset the callback, so that it is not called anymore.
324 set_source_callback(NULL
);
325 weak_factory_
.InvalidateWeakPtrs();
327 TransitionTo(kIsStopped
);
330 void AlsaPcmOutputStream::SetVolume(double volume
) {
331 DCHECK(IsOnAudioThread());
333 volume_
= static_cast<float>(volume
);
336 void AlsaPcmOutputStream::GetVolume(double* volume
) {
337 DCHECK(IsOnAudioThread());
342 void AlsaPcmOutputStream::BufferPacket(bool* source_exhausted
) {
343 DCHECK(IsOnAudioThread());
345 // If stopped, simulate a 0-length packet.
348 *source_exhausted
= true;
352 *source_exhausted
= false;
354 // Request more data only when we run out of data in the buffer, because
355 // WritePacket() comsumes only the current chunk of data.
356 if (!buffer_
->forward_bytes()) {
357 // Before making a request to source for data we need to determine the
358 // delay (in bytes) for the requested data to be played.
359 const uint32 hardware_delay
= GetCurrentDelay() * bytes_per_frame_
;
361 scoped_refptr
<media::DataBuffer
> packet
=
362 new media::DataBuffer(packet_size_
);
363 int frames_filled
= RunDataCallback(
364 audio_bus_
.get(), AudioBuffersState(0, hardware_delay
));
366 size_t packet_size
= frames_filled
* bytes_per_frame_
;
367 DCHECK_LE(packet_size
, packet_size_
);
369 // TODO(dalecurtis): Channel downmixing, upmixing, should be done in mixer;
370 // volume adjust should use SSE optimized vector_fmul() prior to interleave.
371 AudioBus
* output_bus
= audio_bus_
.get();
372 if (channel_mixer_
) {
373 output_bus
= mixed_audio_bus_
.get();
374 channel_mixer_
->Transform(audio_bus_
.get(), output_bus
);
375 // Adjust packet size for downmix.
376 packet_size
= packet_size
/ bytes_per_frame_
* bytes_per_output_frame_
;
379 // Note: If this ever changes to output raw float the data must be clipped
380 // and sanitized since it may come from an untrusted source such as NaCl.
381 output_bus
->Scale(volume_
);
382 output_bus
->ToInterleaved(
383 frames_filled
, bytes_per_sample_
, packet
->writable_data());
385 if (packet_size
> 0) {
386 packet
->set_data_size(packet_size
);
387 // Add the packet to the buffer.
388 buffer_
->Append(packet
);
390 *source_exhausted
= true;
395 void AlsaPcmOutputStream::WritePacket() {
396 DCHECK(IsOnAudioThread());
398 // If the device is in error, just eat the bytes.
404 if (state() != kIsPlaying
)
407 CHECK_EQ(buffer_
->forward_bytes() % bytes_per_output_frame_
, 0u);
409 const uint8
* buffer_data
;
411 if (buffer_
->GetCurrentChunk(&buffer_data
, &buffer_size
)) {
412 buffer_size
= buffer_size
- (buffer_size
% bytes_per_output_frame_
);
413 snd_pcm_sframes_t frames
= std::min(
414 static_cast<snd_pcm_sframes_t
>(buffer_size
/ bytes_per_output_frame_
),
415 GetAvailableFrames());
420 snd_pcm_sframes_t frames_written
=
421 wrapper_
->PcmWritei(playback_handle_
, buffer_data
, frames
);
422 if (frames_written
< 0) {
423 // Attempt once to immediately recover from EINTR,
424 // EPIPE (overrun/underrun), ESTRPIPE (stream suspended). WritePacket
425 // will eventually be called again, so eventual recovery will happen if
426 // muliple retries are required.
427 frames_written
= wrapper_
->PcmRecover(playback_handle_
,
429 kPcmRecoverIsSilent
);
430 if (frames_written
< 0) {
431 if (frames_written
!= -EAGAIN
) {
432 LOG(ERROR
) << "Failed to write to pcm device: "
433 << wrapper_
->StrError(frames_written
);
434 RunErrorCallback(frames_written
);
439 DCHECK_EQ(frames_written
, frames
);
441 // Seek forward in the buffer after we've written some data to ALSA.
442 buffer_
->Seek(frames_written
* bytes_per_output_frame_
);
445 // If nothing left to write and playback hasn't started yet, start it now.
446 // This ensures that shorter sounds will still play.
447 if (playback_handle_
&&
448 (wrapper_
->PcmState(playback_handle_
) == SND_PCM_STATE_PREPARED
) &&
449 GetCurrentDelay() > 0) {
450 wrapper_
->PcmStart(playback_handle_
);
455 void AlsaPcmOutputStream::WriteTask() {
456 DCHECK(IsOnAudioThread());
461 if (state() == kIsStopped
)
464 bool source_exhausted
;
465 BufferPacket(&source_exhausted
);
468 ScheduleNextWrite(source_exhausted
);
471 void AlsaPcmOutputStream::ScheduleNextWrite(bool source_exhausted
) {
472 DCHECK(IsOnAudioThread());
474 if (stop_stream_
|| state() != kIsPlaying
)
477 const uint32 kTargetFramesAvailable
= alsa_buffer_frames_
/ 2;
478 uint32 available_frames
= GetAvailableFrames();
480 base::TimeDelta next_fill_time
;
481 if (buffer_
->forward_bytes() && available_frames
) {
482 // If we've got data available and ALSA has room, deliver it immediately.
483 next_fill_time
= base::TimeDelta();
484 } else if (buffer_
->forward_bytes()) {
485 // If we've got data available and no room, poll until room is available.
486 // Polling in this manner allows us to ensure a more consistent callback
487 // schedule. In testing this yields a variance of +/- 5ms versus the non-
488 // polling strategy which is around +/- 30ms and bimodal.
489 next_fill_time
= base::TimeDelta::FromMilliseconds(5);
490 } else if (available_frames
< kTargetFramesAvailable
) {
491 // Schedule the next write for the moment when the available buffer of the
492 // sound card hits |kTargetFramesAvailable|.
493 next_fill_time
= FramesToTimeDelta(
494 kTargetFramesAvailable
- available_frames
, sample_rate_
);
495 } else if (!source_exhausted
) {
496 // The sound card has |kTargetFramesAvailable| or more frames available.
497 // Invoke the next write immediately to avoid underrun.
498 next_fill_time
= base::TimeDelta();
500 // The sound card has frames available, but our source is exhausted, so
501 // avoid busy looping by delaying a bit.
502 next_fill_time
= base::TimeDelta::FromMilliseconds(10);
505 message_loop_
->PostDelayedTask(FROM_HERE
, base::Bind(
506 &AlsaPcmOutputStream::WriteTask
, weak_factory_
.GetWeakPtr()),
511 base::TimeDelta
AlsaPcmOutputStream::FramesToTimeDelta(int frames
,
512 double sample_rate
) {
513 return base::TimeDelta::FromMicroseconds(
514 frames
* base::Time::kMicrosecondsPerSecond
/ sample_rate
);
517 std::string
AlsaPcmOutputStream::FindDeviceForChannels(uint32 channels
) {
518 // Constants specified by the ALSA API for device hints.
519 static const int kGetAllDevices
= -1;
520 static const char kPcmInterfaceName
[] = "pcm";
521 static const char kIoHintName
[] = "IOID";
522 static const char kNameHintName
[] = "NAME";
524 const char* wanted_device
= GuessSpecificDeviceName(channels
);
526 return std::string();
528 std::string guessed_device
;
530 int error
= wrapper_
->DeviceNameHint(kGetAllDevices
,
534 // NOTE: Do not early return from inside this if statement. The
535 // hints above need to be freed.
536 for (void** hint_iter
= hints
; *hint_iter
!= NULL
; hint_iter
++) {
537 // Only examine devices that are output capable.. Valid values are
538 // "Input", "Output", and NULL which means both input and output.
539 scoped_ptr_malloc
<char> io(
540 wrapper_
->DeviceNameGetHint(*hint_iter
, kIoHintName
));
541 if (io
!= NULL
&& strcmp(io
.get(), "Input") == 0)
544 // Attempt to select the closest device for number of channels.
545 scoped_ptr_malloc
<char> name(
546 wrapper_
->DeviceNameGetHint(*hint_iter
, kNameHintName
));
547 if (strncmp(wanted_device
, name
.get(), strlen(wanted_device
)) == 0) {
548 guessed_device
= name
.get();
553 // Destroy the hint now that we're done with it.
554 wrapper_
->DeviceNameFreeHint(hints
);
557 LOG(ERROR
) << "Unable to get hints for devices: "
558 << wrapper_
->StrError(error
);
561 return guessed_device
;
564 snd_pcm_sframes_t
AlsaPcmOutputStream::GetCurrentDelay() {
565 snd_pcm_sframes_t delay
= -1;
566 // Don't query ALSA's delay if we have underrun since it'll be jammed at some
567 // non-zero value and potentially even negative!
569 // Also, if we're in the prepared state, don't query because that seems to
570 // cause an I/O error when we do query the delay.
571 snd_pcm_state_t pcm_state
= wrapper_
->PcmState(playback_handle_
);
572 if (pcm_state
!= SND_PCM_STATE_XRUN
&&
573 pcm_state
!= SND_PCM_STATE_PREPARED
) {
574 int error
= wrapper_
->PcmDelay(playback_handle_
, &delay
);
576 // Assume a delay of zero and attempt to recover the device.
578 error
= wrapper_
->PcmRecover(playback_handle_
,
580 kPcmRecoverIsSilent
);
582 LOG(ERROR
) << "Failed querying delay: " << wrapper_
->StrError(error
);
587 // snd_pcm_delay() sometimes returns crazy values. In this case return delay
588 // of data we know currently is in ALSA's buffer. Note: When the underlying
589 // driver is PulseAudio based, certain configuration settings (e.g., tsched=1)
590 // will generate much larger delay values than |alsa_buffer_frames_|, so only
591 // clip if delay is truly crazy (> 10x expected).
592 if (static_cast<snd_pcm_uframes_t
>(delay
) > alsa_buffer_frames_
* 10) {
593 delay
= alsa_buffer_frames_
- GetAvailableFrames();
603 snd_pcm_sframes_t
AlsaPcmOutputStream::GetAvailableFrames() {
604 DCHECK(IsOnAudioThread());
609 // Find the number of frames queued in the sound device.
610 snd_pcm_sframes_t available_frames
=
611 wrapper_
->PcmAvailUpdate(playback_handle_
);
612 if (available_frames
< 0) {
613 available_frames
= wrapper_
->PcmRecover(playback_handle_
,
615 kPcmRecoverIsSilent
);
617 if (available_frames
< 0) {
618 LOG(ERROR
) << "Failed querying available frames. Assuming 0: "
619 << wrapper_
->StrError(available_frames
);
622 if (static_cast<uint32
>(available_frames
) > alsa_buffer_frames_
* 2) {
623 LOG(ERROR
) << "ALSA returned " << available_frames
<< " of "
624 << alsa_buffer_frames_
<< " frames available.";
625 return alsa_buffer_frames_
;
628 return available_frames
;
631 snd_pcm_t
* AlsaPcmOutputStream::AutoSelectDevice(unsigned int latency
) {
632 // For auto-selection:
633 // 1) Attempt to open a device that best matches the number of channels
635 // 2) If that fails, attempt the "plug:" version of it in case ALSA can
636 // remap do some software conversion to make it work.
637 // 3) Fallback to kDefaultDevice.
638 // 4) If that fails too, try the "plug:" version of kDefaultDevice.
640 snd_pcm_t
* handle
= NULL
;
641 device_name_
= FindDeviceForChannels(channels_
);
644 if (!device_name_
.empty()) {
645 if ((handle
= alsa_util::OpenPlaybackDevice(wrapper_
, device_name_
.c_str(),
646 channels_
, sample_rate_
,
653 device_name_
= kPlugPrefix
+ device_name_
;
654 if ((handle
= alsa_util::OpenPlaybackDevice(wrapper_
, device_name_
.c_str(),
655 channels_
, sample_rate_
,
662 // For the kDefaultDevice device, we can only reliably depend on 2-channel
663 // output to have the correct ordering according to Lennart. For the channel
664 // formats that we know how to downmix from (3 channel to 8 channel), setup
666 uint32 default_channels
= channels_
;
667 if (default_channels
> 2) {
668 channel_mixer_
.reset(new ChannelMixer(
669 channel_layout_
, CHANNEL_LAYOUT_STEREO
));
670 default_channels
= 2;
671 mixed_audio_bus_
= AudioBus::Create(
672 default_channels
, audio_bus_
->frames());
676 device_name_
= kDefaultDevice
;
677 if ((handle
= alsa_util::OpenPlaybackDevice(
678 wrapper_
, device_name_
.c_str(), default_channels
, sample_rate_
,
679 pcm_format_
, latency
)) != NULL
) {
684 device_name_
= kPlugPrefix
+ device_name_
;
685 if ((handle
= alsa_util::OpenPlaybackDevice(
686 wrapper_
, device_name_
.c_str(), default_channels
, sample_rate_
,
687 pcm_format_
, latency
)) != NULL
) {
691 // Unable to open any device.
692 device_name_
.clear();
696 bool AlsaPcmOutputStream::CanTransitionTo(InternalState to
) {
699 return to
== kIsOpened
|| to
== kIsClosed
|| to
== kInError
;
702 return to
== kIsPlaying
|| to
== kIsStopped
||
703 to
== kIsClosed
|| to
== kInError
;
706 return to
== kIsPlaying
|| to
== kIsStopped
||
707 to
== kIsClosed
|| to
== kInError
;
710 return to
== kIsPlaying
|| to
== kIsStopped
||
711 to
== kIsClosed
|| to
== kInError
;
714 return to
== kIsClosed
|| to
== kInError
;
722 AlsaPcmOutputStream::InternalState
723 AlsaPcmOutputStream::TransitionTo(InternalState to
) {
724 DCHECK(IsOnAudioThread());
726 if (!CanTransitionTo(to
)) {
727 NOTREACHED() << "Cannot transition from: " << state_
<< " to: " << to
;
735 AlsaPcmOutputStream::InternalState
AlsaPcmOutputStream::state() {
739 bool AlsaPcmOutputStream::IsOnAudioThread() const {
740 return message_loop_
&& message_loop_
== base::MessageLoop::current();
743 int AlsaPcmOutputStream::RunDataCallback(AudioBus
* audio_bus
,
744 AudioBuffersState buffers_state
) {
745 TRACE_EVENT0("audio", "AlsaPcmOutputStream::RunDataCallback");
747 if (source_callback_
)
748 return source_callback_
->OnMoreData(audio_bus
, buffers_state
);
753 void AlsaPcmOutputStream::RunErrorCallback(int code
) {
754 if (source_callback_
)
755 source_callback_
->OnError(this);
758 // Changes the AudioSourceCallback to proxy calls to. Pass in NULL to
759 // release ownership of the currently registered callback.
760 void AlsaPcmOutputStream::set_source_callback(AudioSourceCallback
* callback
) {
761 DCHECK(IsOnAudioThread());
762 source_callback_
= callback
;