| // Copyright 2013 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/cras/cras_unified.h" |
| |
| #include <algorithm> |
| |
| #include "base/cxx17_backports.h" |
| #include "base/logging.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "media/audio/cras/audio_manager_cras_base.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| int GetDevicePin(AudioManagerCrasBase* manager, const std::string& device_id) { |
| if (!manager->IsDefault(device_id, false)) { |
| uint64_t cras_node_id; |
| base::StringToUint64(device_id, &cras_node_id); |
| return dev_index_of(cras_node_id); |
| } |
| return NO_DEVICE; |
| } |
| |
| } // namespace |
| |
| // Overview of operation: |
| // 1) An object of CrasUnifiedStream 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. |
| // |
| // Simplified data flow for output only streams: |
| // |
| // +-------------+ +------------------+ |
| // | CRAS Server | | Chrome Client | |
| // +------+------+ Add Stream +---------+--------+ |
| // |<----------------------------------| |
| // | | |
| // | Near out of samples, request more | |
| // |---------------------------------->| |
| // | | UnifiedCallback() |
| // | | WriteAudio() |
| // | | |
| // | buffer_frames written to shm | |
| // |<----------------------------------| |
| // | | |
| // ... Repeats for each block. ... |
| // | | |
| // | | |
| // | Remove stream | |
| // |<----------------------------------| |
| // | | |
| // |
| // For Unified streams the Chrome client is notified whenever buffer_frames have |
| // been captured. For Output streams the client is notified a few milliseconds |
| // before the hardware buffer underruns and fills the buffer with another block |
| // of audio. |
| |
| CrasUnifiedStream::CrasUnifiedStream(const AudioParameters& params, |
| AudioManagerCrasBase* manager, |
| const std::string& device_id) |
| : client_(NULL), |
| stream_id_(0), |
| params_(params), |
| is_playing_(false), |
| volume_(1.0), |
| manager_(manager), |
| source_callback_(NULL), |
| output_bus_(AudioBus::Create(params)), |
| stream_direction_(CRAS_STREAM_OUTPUT), |
| pin_device_(GetDevicePin(manager, device_id)) { |
| DCHECK(manager_); |
| DCHECK_GT(params_.channels(), 0); |
| } |
| |
| CrasUnifiedStream::~CrasUnifiedStream() { |
| DCHECK(!is_playing_); |
| } |
| |
| bool CrasUnifiedStream::Open() { |
| // Sanity check input values. |
| if (params_.sample_rate() <= 0) { |
| LOG(WARNING) << "Unsupported audio frequency."; |
| return false; |
| } |
| |
| // Create the client and connect to the CRAS server. |
| client_ = libcras_client_create(); |
| if (!client_) { |
| LOG(WARNING) << "Couldn't create CRAS client.\n"; |
| client_ = NULL; |
| return false; |
| } |
| |
| if (libcras_client_connect(client_)) { |
| LOG(WARNING) << "Couldn't connect CRAS client.\n"; |
| libcras_client_destroy(client_); |
| client_ = NULL; |
| return false; |
| } |
| |
| // Then start running the client. |
| if (libcras_client_run_thread(client_)) { |
| LOG(WARNING) << "Couldn't run CRAS client.\n"; |
| libcras_client_destroy(client_); |
| client_ = NULL; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void CrasUnifiedStream::Close() { |
| if (client_) { |
| libcras_client_stop(client_); |
| libcras_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); |
| } |
| |
| // This stream is always used with sub second buffer sizes, where it's |
| // sufficient to simply always flush upon Start(). |
| void CrasUnifiedStream::Flush() {} |
| |
| void CrasUnifiedStream::Start(AudioSourceCallback* callback) { |
| CHECK(callback); |
| |
| // Channel map to CRAS_CHANNEL, values in the same order of |
| // corresponding source in Chromium defined Channels. |
| static const int kChannelMap[] = { |
| CRAS_CH_FL, |
| CRAS_CH_FR, |
| CRAS_CH_FC, |
| CRAS_CH_LFE, |
| CRAS_CH_RL, |
| CRAS_CH_RR, |
| CRAS_CH_FLC, |
| CRAS_CH_FRC, |
| CRAS_CH_RC, |
| CRAS_CH_SL, |
| CRAS_CH_SR |
| }; |
| |
| source_callback_ = callback; |
| |
| // Only start if we can enter the playing state. |
| if (is_playing_) |
| return; |
| |
| struct libcras_stream_params* stream_params = libcras_stream_params_create(); |
| if (!stream_params) { |
| DLOG(ERROR) << "Error creating stream params."; |
| callback->OnError(AudioSourceCallback::ErrorType::kUnknown); |
| } |
| |
| unsigned int frames_per_packet = params_.frames_per_buffer(); |
| int rc = libcras_stream_params_set( |
| stream_params, stream_direction_, frames_per_packet * 2, |
| frames_per_packet, CRAS_STREAM_TYPE_DEFAULT, manager_->GetClientType(), 0, |
| this, CrasUnifiedStream::UnifiedCallback, CrasUnifiedStream::StreamError, |
| params_.sample_rate(), SND_PCM_FORMAT_S16, params_.channels()); |
| |
| if (rc) { |
| LOG(WARNING) << "Error setting up stream parameters."; |
| callback->OnError(AudioSourceCallback::ErrorType::kUnknown); |
| libcras_stream_params_destroy(stream_params); |
| return; |
| } |
| |
| // Initialize channel layout to all -1 to indicate that none of |
| // the channels is set in the layout. |
| int8_t layout[CRAS_CH_MAX] = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; |
| |
| // Converts to CRAS defined channels. ChannelOrder will return -1 |
| // for channels that does not present in params_.channel_layout(). |
| for (size_t i = 0; i < base::size(kChannelMap); ++i) |
| layout[kChannelMap[i]] = ChannelOrder(params_.channel_layout(), |
| static_cast<Channels>(i)); |
| |
| rc = libcras_stream_params_set_channel_layout(stream_params, CRAS_CH_MAX, |
| layout); |
| if (rc) { |
| DLOG(WARNING) << "Error setting up the channel layout."; |
| callback->OnError(AudioSourceCallback::ErrorType::kUnknown); |
| libcras_stream_params_destroy(stream_params); |
| return; |
| } |
| |
| // Adding the stream will start the audio callbacks requesting data. |
| if (libcras_client_add_pinned_stream(client_, pin_device_, &stream_id_, |
| stream_params)) { |
| LOG(WARNING) << "Failed to add the stream."; |
| callback->OnError(AudioSourceCallback::ErrorType::kUnknown); |
| libcras_stream_params_destroy(stream_params); |
| return; |
| } |
| |
| // Set initial volume. |
| libcras_client_set_stream_volume(client_, stream_id_, volume_); |
| |
| // Done with config params. |
| libcras_stream_params_destroy(stream_params); |
| |
| is_playing_ = true; |
| } |
| |
| void CrasUnifiedStream::Stop() { |
| if (!client_) |
| return; |
| |
| // Removing the stream from the client stops audio. |
| libcras_client_rm_stream(client_, stream_id_); |
| |
| is_playing_ = false; |
| } |
| |
| void CrasUnifiedStream::SetVolume(double volume) { |
| if (!client_) |
| return; |
| volume_ = static_cast<float>(volume); |
| libcras_client_set_stream_volume(client_, stream_id_, volume_); |
| } |
| |
| void CrasUnifiedStream::GetVolume(double* volume) { |
| *volume = volume_; |
| } |
| |
| // Static callback asking for samples. |
| int CrasUnifiedStream::UnifiedCallback(struct libcras_stream_cb_data* data) { |
| unsigned int frames; |
| uint8_t* buf; |
| struct timespec latency; |
| void* usr_arg; |
| libcras_stream_cb_data_get_frames(data, &frames); |
| libcras_stream_cb_data_get_buf(data, &buf); |
| libcras_stream_cb_data_get_latency(data, &latency); |
| libcras_stream_cb_data_get_usr_arg(data, &usr_arg); |
| CrasUnifiedStream* me = static_cast<CrasUnifiedStream*>(usr_arg); |
| return me->WriteAudio(frames, buf, &latency); |
| } |
| |
| // Static callback for stream errors. |
| int CrasUnifiedStream::StreamError(cras_client* client, |
| cras_stream_id_t stream_id, |
| int err, |
| void* arg) { |
| CrasUnifiedStream* me = static_cast<CrasUnifiedStream*>(arg); |
| me->NotifyStreamError(err); |
| return 0; |
| } |
| |
| uint32_t CrasUnifiedStream::WriteAudio(size_t frames, |
| uint8_t* buffer, |
| const timespec* latency_ts) { |
| DCHECK_EQ(frames, static_cast<size_t>(output_bus_->frames())); |
| |
| // Treat negative latency (if we are too slow to render) as 0. |
| const base::TimeDelta delay = |
| std::max(base::TimeDelta::FromTimeSpec(*latency_ts), base::TimeDelta()); |
| |
| int frames_filled = source_callback_->OnMoreData( |
| delay, base::TimeTicks::Now(), 0, output_bus_.get()); |
| |
| // 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. |
| output_bus_->ToInterleaved<SignedInt16SampleTypeTraits>( |
| frames_filled, reinterpret_cast<int16_t*>(buffer)); |
| |
| return frames_filled; |
| } |
| |
| void CrasUnifiedStream::NotifyStreamError(int err) { |
| // This will remove the stream from the client. |
| // TODO(dalecurtis): Consider sending a translated |err| code. |
| if (source_callback_) |
| source_callback_->OnError(AudioSourceCallback::ErrorType::kUnknown); |
| } |
| |
| } // namespace media |