// Copyright (c) 2012 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 #include #include "base/compiler_specific.h" #include "base/single_thread_task_runner.h" #include "base/strings/stringprintf.h" #include "base/time/time.h" #include "media/audio/audio_device_description.h" #include "media/audio/audio_manager_base.h" #include "media/audio/pulse/pulse_util.h" #include "media/base/audio_sample_types.h" namespace media { using pulse::AutoPulseLock; using pulse::WaitForOperationCompletion; // static, pa_stream_notify_cb void PulseAudioOutputStream::StreamNotifyCallback(pa_stream* s, void* p_this) { PulseAudioOutputStream* stream = static_cast(p_this); // Forward unexpected failures to the AudioSourceCallback if available. All // these variables are only modified under pa_threaded_mainloop_lock() so this // should be thread safe. if (s && stream->source_callback_ && pa_stream_get_state(s) == PA_STREAM_FAILED) { stream->source_callback_->OnError(AudioSourceCallback::ErrorType::kUnknown); } pa_threaded_mainloop_signal(stream->pa_mainloop_, 0); } // static, pa_stream_request_cb_t void PulseAudioOutputStream::StreamRequestCallback(pa_stream* s, size_t len, void* p_this) { // Fulfill write request; must always result in a pa_stream_write() call. static_cast(p_this)->FulfillWriteRequest(len); } PulseAudioOutputStream::PulseAudioOutputStream( const AudioParameters& params, const std::string& device_id, AudioManagerBase* manager, AudioManager::LogCallback log_callback) : params_(AudioParameters(params.format(), params.channel_layout(), params.sample_rate(), params.frames_per_buffer())), device_id_(device_id), manager_(manager), log_callback_(std::move(log_callback)), pa_context_(nullptr), pa_mainloop_(nullptr), pa_stream_(nullptr), volume_(1.0f), source_callback_(nullptr), buffer_size_(params_.GetBytesPerBuffer(kSampleFormatF32)) { CHECK(params_.IsValid()); SendLogMessage("%s({device_id=%s}, {params=[%s]})", __func__, device_id.c_str(), params.AsHumanReadableString().c_str()); audio_bus_ = AudioBus::Create(params_); } PulseAudioOutputStream::~PulseAudioOutputStream() { // All internal structures should already have been freed in Close(), which // calls AudioManagerBase::ReleaseOutputStream() which deletes this object. DCHECK(!pa_stream_); DCHECK(!pa_context_); DCHECK(!pa_mainloop_); } bool PulseAudioOutputStream::Open() { DCHECK(thread_checker_.CalledOnValidThread()); SendLogMessage("%s()", __func__); bool result = pulse::CreateOutputStream( &pa_mainloop_, &pa_context_, &pa_stream_, params_, device_id_, AudioManager::GetGlobalAppName(), &StreamNotifyCallback, &StreamRequestCallback, this); if (!result) { SendLogMessage("%s => (ERROR: failed to open PA stream)", __func__); } return result; } void PulseAudioOutputStream::Reset() { if (!pa_mainloop_) { DCHECK(!pa_stream_); DCHECK(!pa_context_); return; } { AutoPulseLock auto_lock(pa_mainloop_); // Close the stream. if (pa_stream_) { // Ensure all samples are played out before shutdown. pa_operation* operation = pa_stream_flush( pa_stream_, &pulse::StreamSuccessCallback, pa_mainloop_); WaitForOperationCompletion(pa_mainloop_, operation, pa_context_, pa_stream_); // Release PulseAudio structures. pa_stream_disconnect(pa_stream_); pa_stream_set_write_callback(pa_stream_, nullptr, nullptr); pa_stream_set_state_callback(pa_stream_, nullptr, nullptr); pa_stream_unref(pa_stream_); pa_stream_ = nullptr; } if (pa_context_) { pa_context_disconnect(pa_context_); pa_context_set_state_callback(pa_context_, nullptr, nullptr); pa_context_unref(pa_context_); pa_context_ = nullptr; } } pa_threaded_mainloop_stop(pa_mainloop_); pa_threaded_mainloop_free(pa_mainloop_); pa_mainloop_ = nullptr; } void PulseAudioOutputStream::Close() { DCHECK(thread_checker_.CalledOnValidThread()); SendLogMessage("%s()", __func__); 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); } // This stream is always used with sub second buffer sizes, where it's // sufficient to simply always flush upon Start(). void PulseAudioOutputStream::Flush() {} void PulseAudioOutputStream::SendLogMessage(const char* format, ...) { if (log_callback_.is_null()) return; va_list args; va_start(args, format); log_callback_.Run("PAOS::" + base::StringPrintV(format, args) + base::StringPrintf(" [this=%p]", this)); va_end(args); } void PulseAudioOutputStream::FulfillWriteRequest(size_t requested_bytes) { int bytes_remaining = requested_bytes; while (bytes_remaining > 0) { void* pa_buffer = nullptr; size_t pa_buffer_size = buffer_size_; CHECK_GE(pa_stream_begin_write(pa_stream_, &pa_buffer, &pa_buffer_size), 0); if (!source_callback_) { memset(pa_buffer, 0, pa_buffer_size); pa_stream_write(pa_stream_, pa_buffer, pa_buffer_size, nullptr, 0LL, PA_SEEK_RELATIVE); bytes_remaining -= pa_buffer_size; continue; } size_t unwritten_frames_in_bus = audio_bus_->frames(); size_t frames_filled = source_callback_->OnMoreData( pulse::GetHardwareLatency(pa_stream_), base::TimeTicks::Now(), 0, audio_bus_.get()); // Zero any unfilled data so it plays back as silence. if (frames_filled < unwritten_frames_in_bus) { audio_bus_->ZeroFramesPartial(frames_filled, unwritten_frames_in_bus - frames_filled); } audio_bus_->Scale(volume_); size_t frame_size = buffer_size_ / unwritten_frames_in_bus; size_t frames_to_copy = pa_buffer_size / frame_size; size_t frame_offset_in_bus = 0; do { // Grab frames and get the count. frames_to_copy = std::min(audio_bus_->frames() - frame_offset_in_bus, frames_to_copy); // We skip clipping since that occurs at the shared memory boundary. audio_bus_->ToInterleavedPartial( frame_offset_in_bus, frames_to_copy, reinterpret_cast(pa_buffer)); frame_offset_in_bus += frames_to_copy; unwritten_frames_in_bus -= frames_to_copy; if (pa_stream_write(pa_stream_, pa_buffer, pa_buffer_size, nullptr, 0LL, PA_SEEK_RELATIVE) < 0) { source_callback_->OnError(AudioSourceCallback::ErrorType::kUnknown); return; } bytes_remaining -= pa_buffer_size; if (unwritten_frames_in_bus) { // Reset the buffer and the size: // - If pa_buffer isn't nulled out, then it will get re-used, and // there will be a race between PA reading and us writing. // - If we don't shrink the pa_buffer_size to a small value, we get // stuttering as the memory allocation can take far too long. This // also means that we will never get more than we want, and we // dont need to memset. pa_buffer = nullptr; pa_buffer_size = unwritten_frames_in_bus * frame_size; CHECK_GE(pa_stream_begin_write(pa_stream_, &pa_buffer, &pa_buffer_size), 0); frames_to_copy = pa_buffer_size / frame_size; } } while (unwritten_frames_in_bus); } } void PulseAudioOutputStream::Start(AudioSourceCallback* callback) { DCHECK(thread_checker_.CalledOnValidThread()); CHECK(callback); CHECK(pa_stream_); SendLogMessage("%s()", __func__); AutoPulseLock auto_lock(pa_mainloop_); // Ensure the context and stream are ready. if (pa_context_get_state(pa_context_) != PA_CONTEXT_READY && pa_stream_get_state(pa_stream_) != PA_STREAM_READY) { callback->OnError(AudioSourceCallback::ErrorType::kUnknown); return; } source_callback_ = callback; // Uncork (resume) the stream. pa_operation* operation = pa_stream_cork( pa_stream_, 0, &pulse::StreamSuccessCallback, pa_mainloop_); if (!WaitForOperationCompletion(pa_mainloop_, operation, pa_context_, pa_stream_)) { callback->OnError(AudioSourceCallback::ErrorType::kUnknown); } } void PulseAudioOutputStream::Stop() { DCHECK(thread_checker_.CalledOnValidThread()); SendLogMessage("%s()", __func__); // Cork (pause) the stream. Waiting for the main loop lock will ensure // outstanding callbacks have completed. AutoPulseLock auto_lock(pa_mainloop_); if (!source_callback_) return; // Set |source_callback_| to nullptr so all FulfillWriteRequest() calls which // may occur while waiting on the flush and cork exit immediately. auto* callback = source_callback_; source_callback_ = nullptr; // Flush the stream prior to cork, doing so after will cause hangs. Write // callbacks are suspended while inside pa_threaded_mainloop_lock() so this // is all thread safe. pa_operation* operation = pa_stream_flush(pa_stream_, &pulse::StreamSuccessCallback, pa_mainloop_); if (!WaitForOperationCompletion(pa_mainloop_, operation, pa_context_, pa_stream_)) { callback->OnError(AudioSourceCallback::ErrorType::kUnknown); } operation = pa_stream_cork(pa_stream_, 1, &pulse::StreamSuccessCallback, pa_mainloop_); if (!WaitForOperationCompletion(pa_mainloop_, operation, pa_context_, pa_stream_)) { callback->OnError(AudioSourceCallback::ErrorType::kUnknown); } } void PulseAudioOutputStream::SetVolume(double volume) { DCHECK(thread_checker_.CalledOnValidThread()); // Waiting for the main loop lock will ensure outstanding callbacks have // completed and |volume_| is not accessed from them. AutoPulseLock auto_lock(pa_mainloop_); volume_ = static_cast(volume); } void PulseAudioOutputStream::GetVolume(double* volume) { DCHECK(thread_checker_.CalledOnValidThread()); *volume = volume_; } } // namespace media