| // 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 "base/logging.h" |
| #include "media/audio/cras/audio_manager_cras.h" |
| |
| namespace media { |
| |
| // 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. |
| // |
| // For output-only streams, a unified stream is created with 0 input channels. |
| // |
| // Simplified data flow for unified streams: |
| // |
| // +-------------+ +------------------+ |
| // | CRAS Server | | Chrome Client | |
| // +------+------+ Add Stream +---------+--------+ |
| // |<----------------------------------| |
| // | | |
| // | buffer_frames captured to shm | |
| // |---------------------------------->| |
| // | | UnifiedCallback() |
| // | | ReadWriteAudio() |
| // | | |
| // | buffer_frames written to shm | |
| // |<----------------------------------| |
| // | | |
| // ... Repeats for each block. ... |
| // | | |
| // | | |
| // | Remove stream | |
| // |<----------------------------------| |
| // | | |
| // |
| // 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, |
| AudioManagerCras* manager) |
| : client_(NULL), |
| stream_id_(0), |
| params_(params), |
| bytes_per_frame_(0), |
| is_playing_(false), |
| volume_(1.0), |
| manager_(manager), |
| source_callback_(NULL), |
| stream_direction_(CRAS_STREAM_OUTPUT) { |
| DCHECK(manager_); |
| DCHECK(params_.channels() > 0); |
| |
| // Must have at least one input or output. If there are both they must be the |
| // same. |
| int input_channels = params_.input_channels(); |
| |
| if (input_channels) { |
| // A unified stream for input and output. |
| DCHECK(params_.channels() == input_channels); |
| stream_direction_ = CRAS_STREAM_UNIFIED; |
| input_bus_ = AudioBus::Create(input_channels, |
| params_.frames_per_buffer()); |
| } |
| |
| output_bus_ = AudioBus::Create(params); |
| } |
| |
| CrasUnifiedStream::~CrasUnifiedStream() { |
| DCHECK(!is_playing_); |
| } |
| |
| bool CrasUnifiedStream::Open() { |
| // Sanity check input values. |
| if (params_.sample_rate() <= 0) { |
| LOG(WARNING) << "Unsupported audio frequency."; |
| return false; |
| } |
| |
| if (AudioManagerCras::BitsToFormat(params_.bits_per_sample()) == |
| SND_PCM_FORMAT_UNKNOWN) { |
| LOG(WARNING) << "Unsupported pcm format"; |
| return false; |
| } |
| |
| // Create the client and connect to the CRAS server. |
| if (cras_client_create(&client_)) { |
| LOG(WARNING) << "Couldn't create CRAS client.\n"; |
| client_ = NULL; |
| return false; |
| } |
| |
| if (cras_client_connect(client_)) { |
| LOG(WARNING) << "Couldn't connect CRAS client.\n"; |
| cras_client_destroy(client_); |
| client_ = NULL; |
| return false; |
| } |
| |
| // Then start running the client. |
| if (cras_client_run_thread(client_)) { |
| LOG(WARNING) << "Couldn't run CRAS client.\n"; |
| cras_client_destroy(client_); |
| client_ = NULL; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void CrasUnifiedStream::Close() { |
| 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 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; |
| |
| // Prepare |audio_format| and |stream_params| for the stream we |
| // will create. |
| cras_audio_format* audio_format = cras_audio_format_create( |
| AudioManagerCras::BitsToFormat(params_.bits_per_sample()), |
| params_.sample_rate(), |
| params_.channels()); |
| if (!audio_format) { |
| LOG(WARNING) << "Error setting up audio parameters."; |
| callback->OnError(this); |
| return; |
| } |
| |
| // Initialize channel layout to all -1 to indicate that none of |
| // the channels is set in the layout. |
| int8 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 < arraysize(kChannelMap); ++i) |
| layout[kChannelMap[i]] = ChannelOrder(params_.channel_layout(), |
| static_cast<Channels>(i)); |
| |
| if (cras_audio_format_set_channel_layout(audio_format, layout)) { |
| LOG(WARNING) << "Error setting channel layout."; |
| callback->OnError(this); |
| return; |
| } |
| |
| cras_stream_params* stream_params = cras_client_unified_params_create( |
| stream_direction_, |
| params_.frames_per_buffer(), |
| CRAS_STREAM_TYPE_DEFAULT, |
| 0, |
| this, |
| CrasUnifiedStream::UnifiedCallback, |
| CrasUnifiedStream::StreamError, |
| audio_format); |
| if (!stream_params) { |
| LOG(WARNING) << "Error setting up stream parameters."; |
| callback->OnError(this); |
| 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. |
| if (cras_client_add_stream(client_, &stream_id_, stream_params) < 0) { |
| LOG(WARNING) << "Failed to add the stream"; |
| callback->OnError(this); |
| 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); |
| |
| is_playing_ = true; |
| } |
| |
| void CrasUnifiedStream::Stop() { |
| if (!client_) |
| return; |
| |
| // Removing the stream from the client stops audio. |
| cras_client_rm_stream(client_, stream_id_); |
| |
| is_playing_ = false; |
| } |
| |
| void CrasUnifiedStream::SetVolume(double volume) { |
| if (!client_) |
| return; |
| volume_ = static_cast<float>(volume); |
| cras_client_set_stream_volume(client_, stream_id_, volume_); |
| } |
| |
| void CrasUnifiedStream::GetVolume(double* volume) { |
| *volume = volume_; |
| } |
| |
| uint32 CrasUnifiedStream::GetBytesLatency( |
| const struct timespec& latency_ts) { |
| uint32 latency_usec; |
| |
| // Treat negative latency (if we are too slow to render) as 0. |
| if (latency_ts.tv_sec < 0 || latency_ts.tv_nsec < 0) { |
| latency_usec = 0; |
| } else { |
| latency_usec = (latency_ts.tv_sec * base::Time::kMicrosecondsPerSecond) + |
| latency_ts.tv_nsec / base::Time::kNanosecondsPerMicrosecond; |
| } |
| |
| double frames_latency = |
| latency_usec * params_.sample_rate() / base::Time::kMicrosecondsPerSecond; |
| |
| return static_cast<unsigned int>(frames_latency * bytes_per_frame_); |
| } |
| |
| // Static callback asking for samples. |
| int CrasUnifiedStream::UnifiedCallback(cras_client* client, |
| cras_stream_id_t stream_id, |
| uint8* input_samples, |
| uint8* output_samples, |
| unsigned int frames, |
| const timespec* input_ts, |
| const timespec* output_ts, |
| void* arg) { |
| CrasUnifiedStream* me = static_cast<CrasUnifiedStream*>(arg); |
| return me->DispatchCallback(frames, |
| input_samples, |
| output_samples, |
| input_ts, |
| output_ts); |
| } |
| |
| // 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; |
| } |
| |
| // Calls the appropriate rendering function for this type of stream. |
| uint32 CrasUnifiedStream::DispatchCallback(size_t frames, |
| uint8* input_samples, |
| uint8* output_samples, |
| const timespec* input_ts, |
| const timespec* output_ts) { |
| switch (stream_direction_) { |
| case CRAS_STREAM_OUTPUT: |
| return WriteAudio(frames, output_samples, output_ts); |
| case CRAS_STREAM_INPUT: |
| NOTREACHED() << "CrasUnifiedStream doesn't support input streams."; |
| return 0; |
| case CRAS_STREAM_UNIFIED: |
| return ReadWriteAudio(frames, input_samples, output_samples, |
| input_ts, output_ts); |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| // Note these are run from a real time thread, so don't waste cycles here. |
| uint32 CrasUnifiedStream::ReadWriteAudio(size_t frames, |
| uint8* input_samples, |
| uint8* output_samples, |
| const timespec* input_ts, |
| const timespec* output_ts) { |
| DCHECK_EQ(frames, static_cast<size_t>(output_bus_->frames())); |
| DCHECK(source_callback_); |
| |
| uint32 bytes_per_sample = bytes_per_frame_ / params_.channels(); |
| input_bus_->FromInterleaved(input_samples, frames, bytes_per_sample); |
| |
| // Determine latency and pass that on to the source. We have the capture time |
| // of the first input sample and the playback time of the next audio sample |
| // passed from the audio server, add them together for total latency. |
| uint32 total_delay_bytes; |
| timespec latency_ts = {0, 0}; |
| cras_client_calc_capture_latency(input_ts, &latency_ts); |
| total_delay_bytes = GetBytesLatency(latency_ts); |
| cras_client_calc_playback_latency(output_ts, &latency_ts); |
| total_delay_bytes += GetBytesLatency(latency_ts); |
| |
| int frames_filled = source_callback_->OnMoreData( |
| output_bus_.get(), |
| AudioBuffersState(0, total_delay_bytes)); |
| |
| output_bus_->ToInterleaved(frames_filled, bytes_per_sample, output_samples); |
| |
| return frames_filled; |
| } |
| |
| uint32 CrasUnifiedStream::WriteAudio(size_t frames, |
| uint8* buffer, |
| const timespec* sample_ts) { |
| DCHECK_EQ(frames, static_cast<size_t>(output_bus_->frames())); |
| |
| // Determine latency and pass that on to the source. |
| timespec latency_ts = {0, 0}; |
| cras_client_calc_playback_latency(sample_ts, &latency_ts); |
| |
| int frames_filled = source_callback_->OnMoreData( |
| output_bus_.get(), AudioBuffersState(0, GetBytesLatency(latency_ts))); |
| |
| // 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( |
| frames_filled, bytes_per_frame_ / params_.channels(), buffer); |
| |
| return frames_filled; |
| } |
| |
| void CrasUnifiedStream::NotifyStreamError(int err) { |
| // This will remove the stream from the client. |
| if (source_callback_) |
| source_callback_->OnError(this); |
| } |
| |
| } // namespace media |