| // 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. |
| // |
| // The object has one error state: |state_| == kInError. When |state_| == |
| // kInError, all public API functions will fail with an error (Start() will call |
| // the OnError() function on the callback immediately), or no-op themselves with |
| // the exception of Close(). Even if an error state has been entered, if Open() |
| // has previously returned successfully, Close() must be called. |
| |
| #include "media/audio/linux/cras_output.h" |
| |
| #include <cras_client.h> |
| |
| #include "base/logging.h" |
| #include "media/audio/audio_util.h" |
| #include "media/audio/linux/alsa_util.h" |
| #include "media/audio/linux/audio_manager_linux.h" |
| |
| namespace media { |
| |
| // Helps make log messages readable. |
| std::ostream& operator<<(std::ostream& os, |
| CrasOutputStream::InternalState state) { |
| switch (state) { |
| case CrasOutputStream::kInError: |
| os << "kInError"; |
| break; |
| case CrasOutputStream::kCreated: |
| os << "kCreated"; |
| break; |
| case CrasOutputStream::kIsOpened: |
| os << "kIsOpened"; |
| break; |
| case CrasOutputStream::kIsPlaying: |
| os << "kIsPlaying"; |
| break; |
| case CrasOutputStream::kIsStopped: |
| os << "kIsStopped"; |
| break; |
| case CrasOutputStream::kIsClosed: |
| os << "kIsClosed"; |
| break; |
| default: |
| os << "UnknownState"; |
| break; |
| }; |
| return os; |
| } |
| |
| // Overview of operation: |
| // 1) An object of CrasOutputStream is created by the AudioManager |
| // factory: audio_man->MakeAudioStream(). |
| // 2) Next some thread will call Open(), at that point a client is created and |
| // configured for the correct format and sample rate. |
| // 3) Then Start(source) is called and a stream is added to the CRAS client |
| // which will create its own thread that periodically calls the source for more |
| // data as buffers are being consumed. |
| // 4) When finished Stop() is called, which is handled by stopping the stream. |
| // 5) Finally Close() is called. It cleans up and notifies the audio manager, |
| // which likely will destroy this object. |
| |
| CrasOutputStream::CrasOutputStream(const AudioParameters& params, |
| AudioManagerLinux* manager) |
| : client_(NULL), |
| stream_id_(0), |
| samples_per_packet_(params.frames_per_buffer()), |
| bytes_per_frame_(0), |
| frame_rate_(params.sample_rate()), |
| num_channels_(params.channels()), |
| pcm_format_(alsa_util::BitsToFormat(params.bits_per_sample())), |
| state_(kCreated), |
| volume_(1.0), |
| manager_(manager), |
| source_callback_(NULL), |
| audio_bus_(AudioBus::Create(params)) { |
| // We must have a manager. |
| DCHECK(manager_); |
| |
| // Sanity check input values. |
| if (params.sample_rate() <= 0) { |
| LOG(WARNING) << "Unsupported audio frequency."; |
| TransitionTo(kInError); |
| return; |
| } |
| |
| if (AudioParameters::AUDIO_PCM_LINEAR != params.format() && |
| AudioParameters::AUDIO_PCM_LOW_LATENCY != params.format()) { |
| LOG(WARNING) << "Unsupported audio format."; |
| TransitionTo(kInError); |
| return; |
| } |
| |
| if (pcm_format_ == SND_PCM_FORMAT_UNKNOWN) { |
| LOG(WARNING) << "Unsupported bits per sample: " << params.bits_per_sample(); |
| TransitionTo(kInError); |
| return; |
| } |
| } |
| |
| CrasOutputStream::~CrasOutputStream() { |
| InternalState current_state = state(); |
| DCHECK(current_state == kCreated || |
| current_state == kIsClosed || |
| current_state == kInError); |
| } |
| |
| bool CrasOutputStream::Open() { |
| if (!CanTransitionTo(kIsOpened)) { |
| NOTREACHED() << "Invalid state: " << state(); |
| return false; |
| } |
| |
| // We do not need to check if the transition was successful because |
| // CanTransitionTo() was checked above, and it is assumed that this |
| // object's public API is only called on one thread so the state cannot |
| // transition out from under us. |
| TransitionTo(kIsOpened); |
| |
| // Create the client and connect to the CRAS server. |
| int err = cras_client_create(&client_); |
| if (err < 0) { |
| LOG(WARNING) << "Couldn't create CRAS client.\n"; |
| client_ = NULL; |
| TransitionTo(kInError); |
| return false; |
| } |
| err = cras_client_connect(client_); |
| if (err) { |
| LOG(WARNING) << "Couldn't connect CRAS client.\n"; |
| cras_client_destroy(client_); |
| client_ = NULL; |
| TransitionTo(kInError); |
| return false; |
| } |
| // Then start running the client. |
| err = cras_client_run_thread(client_); |
| if (err) { |
| LOG(WARNING) << "Couldn't run CRAS client.\n"; |
| cras_client_destroy(client_); |
| client_ = NULL; |
| TransitionTo(kInError); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void CrasOutputStream::Close() { |
| // Sanity Check that we can transition to closed. |
| if (TransitionTo(kIsClosed) != kIsClosed) { |
| NOTREACHED() << "Unable to transition Closed."; |
| return; |
| } |
| |
| if (client_) { |
| cras_client_stop(client_); |
| cras_client_destroy(client_); |
| client_ = NULL; |
| } |
| |
| // Signal to the manager that we're closed and can be removed. |
| // Should be last call in the method as it deletes "this". |
| manager_->ReleaseOutputStream(this); |
| } |
| |
| void CrasOutputStream::Start(AudioSourceCallback* callback) { |
| CHECK(callback); |
| source_callback_ = callback; |
| |
| // Only start if we can enter the playing state. |
| if (TransitionTo(kIsPlaying) != kIsPlaying) |
| return; |
| |
| // Prepare |audio_format| and |stream_params| for the stream we |
| // will create. |
| cras_audio_format* audio_format = cras_audio_format_create( |
| pcm_format_, |
| frame_rate_, |
| num_channels_); |
| if (audio_format == NULL) { |
| LOG(WARNING) << "Error setting up audio parameters."; |
| TransitionTo(kInError); |
| callback->OnError(this, -ENOMEM); |
| return; |
| } |
| cras_stream_params* stream_params = cras_client_stream_params_create( |
| CRAS_STREAM_OUTPUT, |
| samples_per_packet_ * 2, // Total latency. |
| samples_per_packet_ / 2, // Call back when this many left. |
| samples_per_packet_, // Call back with at least this much space. |
| CRAS_STREAM_TYPE_DEFAULT, |
| 0, |
| this, |
| CrasOutputStream::PutSamples, |
| CrasOutputStream::StreamError, |
| audio_format); |
| if (stream_params == NULL) { |
| LOG(WARNING) << "Error setting up stream parameters."; |
| TransitionTo(kInError); |
| callback->OnError(this, -ENOMEM); |
| cras_audio_format_destroy(audio_format); |
| return; |
| } |
| |
| // Before starting the stream, save the number of bytes in a frame for use in |
| // the callback. |
| bytes_per_frame_ = cras_client_format_bytes_per_frame(audio_format); |
| |
| // Adding the stream will start the audio callbacks requesting data. |
| int err = cras_client_add_stream(client_, &stream_id_, stream_params); |
| if (err < 0) { |
| LOG(WARNING) << "Failed to add the stream"; |
| TransitionTo(kInError); |
| callback->OnError(this, err); |
| cras_audio_format_destroy(audio_format); |
| cras_client_stream_params_destroy(stream_params); |
| return; |
| } |
| |
| // Set initial volume. |
| cras_client_set_stream_volume(client_, stream_id_, volume_); |
| |
| // Done with config params. |
| cras_audio_format_destroy(audio_format); |
| cras_client_stream_params_destroy(stream_params); |
| } |
| |
| void CrasOutputStream::Stop() { |
| if (!client_) |
| return; |
| // Removing the stream from the client stops audio. |
| cras_client_rm_stream(client_, stream_id_); |
| TransitionTo(kIsStopped); |
| } |
| |
| void CrasOutputStream::SetVolume(double volume) { |
| if (!client_) |
| return; |
| volume_ = static_cast<float>(volume); |
| cras_client_set_stream_volume(client_, stream_id_, volume_); |
| } |
| |
| void CrasOutputStream::GetVolume(double* volume) { |
| *volume = volume_; |
| } |
| |
| // Static callback asking for samples. |
| int CrasOutputStream::PutSamples(cras_client* client, |
| cras_stream_id_t stream_id, |
| uint8* samples, |
| size_t frames, |
| const timespec* sample_ts, |
| void* arg) { |
| CrasOutputStream* me = static_cast<CrasOutputStream*>(arg); |
| return me->Render(frames, samples, sample_ts); |
| } |
| |
| // Static callback for stream errors. |
| int CrasOutputStream::StreamError(cras_client* client, |
| cras_stream_id_t stream_id, |
| int err, |
| void* arg) { |
| CrasOutputStream* me = static_cast<CrasOutputStream*>(arg); |
| me->NotifyStreamError(err); |
| return 0; |
| } |
| |
| // Note this is run from a real time thread, so don't waste cycles here. |
| uint32 CrasOutputStream::Render(size_t frames, |
| uint8* buffer, |
| const timespec* sample_ts) { |
| timespec latency_ts = {0, 0}; |
| |
| // Determine latency and pass that on to the source. |
| cras_client_calc_playback_latency(sample_ts, &latency_ts); |
| |
| // Treat negative latency (if we are too slow to render) as 0. |
| uint32 latency_usec; |
| if (latency_ts.tv_sec < 0 || latency_ts.tv_nsec < 0) { |
| latency_usec = 0; |
| } else { |
| latency_usec = (latency_ts.tv_sec * 1000000) + |
| latency_ts.tv_nsec / 1000; |
| } |
| |
| uint32 frames_latency = latency_usec * frame_rate_ / 1000000; |
| uint32 bytes_latency = frames_latency * bytes_per_frame_; |
| DCHECK_EQ(frames, static_cast<size_t>(audio_bus_->frames())); |
| int frames_filled = source_callback_->OnMoreData( |
| audio_bus_.get(), AudioBuffersState(0, bytes_latency)); |
| // Note: If this ever changes to output raw float the data must be clipped and |
| // sanitized since it may come from an untrusted source such as NaCl. |
| audio_bus_->ToInterleaved( |
| frames_filled, bytes_per_frame_ / num_channels_, buffer); |
| return frames_filled; |
| } |
| |
| void CrasOutputStream::NotifyStreamError(int err) { |
| // This will remove the stream from the client. |
| if (state_ == kIsClosed || state_ == kInError) |
| return; // Don't care about error if we aren't using it. |
| TransitionTo(kInError); |
| if (source_callback_) |
| source_callback_->OnError(this, err); |
| } |
| |
| bool CrasOutputStream::CanTransitionTo(InternalState to) { |
| switch (state_) { |
| case kCreated: |
| return to == kIsOpened || to == kIsClosed || to == kInError; |
| |
| case kIsOpened: |
| return to == kIsPlaying || to == kIsStopped || |
| to == kIsClosed || to == kInError; |
| |
| case kIsPlaying: |
| return to == kIsPlaying || to == kIsStopped || |
| to == kIsClosed || to == kInError; |
| |
| case kIsStopped: |
| return to == kIsPlaying || to == kIsStopped || |
| to == kIsClosed || to == kInError; |
| |
| case kInError: |
| return to == kIsClosed || to == kInError; |
| |
| case kIsClosed: |
| return false; |
| } |
| return false; |
| } |
| |
| CrasOutputStream::InternalState |
| CrasOutputStream::TransitionTo(InternalState to) { |
| if (!CanTransitionTo(to)) { |
| state_ = kInError; |
| } else { |
| state_ = to; |
| } |
| return state_; |
| } |
| |
| CrasOutputStream::InternalState CrasOutputStream::state() { |
| return state_; |
| } |
| |
| } // namespace media |