Chromium Code Reviews| Index: media/audio/cras/cras_unified.cc |
| diff --git a/media/audio/cras/cras_unified.cc b/media/audio/cras/cras_unified.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..575eab27428b069a89fa902dfd729ee272573a56 |
| --- /dev/null |
| +++ b/media/audio/cras/cras_unified.cc |
| @@ -0,0 +1,380 @@ |
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
|
no longer working on chromium
2013/03/21 15:38:27
ditto
dgreid
2013/03/21 17:18:44
Done.
|
| +// 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 <cras_client.h> |
| + |
| +#include "base/command_line.h" |
| +#include "base/logging.h" |
| +#include "media/audio/audio_util.h" |
| +#include "media/audio/cras/audio_manager_cras.h" |
| +#include "media/audio/linux/alsa_util.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), |
| + samples_per_packet_(params.frames_per_buffer()), |
| + bytes_per_frame_(0), |
| + frame_rate_(params.sample_rate()), |
| + output_channels_(params.channels()), |
| + pcm_format_(alsa_util::BitsToFormat(params.bits_per_sample())), |
| + is_playing_(false), |
| + volume_(1.0), |
| + manager_(manager), |
| + source_callback_(NULL), |
| + input_bus_(NULL), |
| + output_bus_(NULL) { |
| + unsigned int input_channels = params.input_channels(); |
|
no longer working on chromium
2013/03/21 15:38:27
Curiously, why do we need output_channels to be a
dgreid
2013/03/21 17:18:44
If there are input channels they will always be th
dgreid
2013/03/21 17:18:44
If there are input channels there will be the same
|
| + |
| + // We must have a manager. |
|
no longer working on chromium
2013/03/21 15:38:27
nit, the comment tells nothing, please remove
dgreid
2013/03/21 17:18:44
Done.
|
| + DCHECK(manager_); |
|
no longer working on chromium
2013/03/21 15:38:27
move the DCHECK to the top of the function.
dgreid
2013/03/21 17:18:44
Done.
|
| + // Must have at least one input or output. If there are both they must be the |
| + // same. |
| + DCHECK(output_channels_ > 0 || input_channels > 0); |
| + if (output_channels_ > 0 && input_channels > 0) |
| + DCHECK(output_channels_ == input_channels); |
| + |
| + if (input_channels) |
| + input_bus_ = AudioBus::Create(input_channels, |
| + params.frames_per_buffer()); |
| + if (output_channels_) |
| + output_bus_ = AudioBus::Create(params); |
| + |
| + if (output_channels_ && input_channels) |
| + stream_direction_ = CRAS_STREAM_UNIFIED; |
| + else if (output_channels_) |
| + stream_direction_ = CRAS_STREAM_OUTPUT; |
| + else |
| + NOTREACHED() << "Input only streams not supported by CrasUnifiedStream."; |
|
no longer working on chromium
2013/03/21 15:38:27
move this section up to line 101, and combine with
dgreid
2013/03/21 17:18:44
I got rid of it and simlified the checks above, th
|
| +} |
| + |
| +CrasUnifiedStream::~CrasUnifiedStream() { |
| + DCHECK(!is_playing_); |
| +} |
| + |
| +bool CrasUnifiedStream::Open() { |
| + // Sanity check input values. |
| + if (frame_rate_ <= 0) { |
| + LOG(WARNING) << "Unsupported audio frequency."; |
| + return false; |
| + } |
| + |
| + if (pcm_format_ == SND_PCM_FORMAT_UNKNOWN) { |
| + LOG(WARNING) << "Unsupported pcm format"; |
| + return false; |
| + } |
| + |
| + // Create the client and connect to the CRAS server. |
| + int err = cras_client_create(&client_); |
|
no longer working on chromium
2013/03/21 15:38:27
why do you need variable err?
You can simply do
i
dgreid
2013/03/21 17:18:44
Done.
|
| + if (err < 0) { |
| + LOG(WARNING) << "Couldn't create CRAS client.\n"; |
| + client_ = NULL; |
| + return false; |
| + } |
| + |
| + err = cras_client_connect(client_); |
| + if (err) { |
| + LOG(WARNING) << "Couldn't connect CRAS client.\n"; |
| + cras_client_destroy(client_); |
| + client_ = NULL; |
| + 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; |
| + 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); |
| + 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( |
| + pcm_format_, |
| + frame_rate_, |
| + output_channels_); |
| + if (audio_format == NULL) { |
| + LOG(WARNING) << "Error setting up audio parameters."; |
| + callback->OnError(this, -ENOMEM); |
| + return; |
| + } |
| + |
| + cras_stream_params* stream_params = cras_client_unified_params_create( |
| + stream_direction_, |
| + samples_per_packet_, |
| + CRAS_STREAM_TYPE_DEFAULT, |
| + 0, |
| + this, |
| + CrasUnifiedStream::UnifiedCallback, |
| + CrasUnifiedStream::StreamError, |
| + audio_format); |
| + if (stream_params == NULL) { |
| + LOG(WARNING) << "Error setting up stream parameters."; |
| + 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"; |
| + 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); |
| + |
| + 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_; |
| +} |
| + |
| +unsigned int 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) |
|
no longer working on chromium
2013/03/21 15:38:27
nit, add {} since else case has more than 1 line.
dgreid
2013/03/21 17:18:44
Done.
|
| + 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 * frame_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->DispatchCalback(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::DispatchCalback(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); |
| + } |
| + |
| + 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) { |
| + unsigned int bytes_per_sample = bytes_per_frame_ / output_channels_; |
| + |
| + DCHECK_EQ(frames, static_cast<size_t>(output_bus_->frames())); |
| + DCHECK(source_callback_); |
| + |
| + 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. |
| + unsigned int 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_->OnMoreIOData( |
| + input_bus_.get(), |
| + 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) { |
| + timespec latency_ts = {0, 0}; |
|
no longer working on chromium
2013/03/21 15:38:27
nit, move the declaration to the place where it is
dgreid
2013/03/21 17:18:44
Done.
dgreid
2013/03/21 17:18:44
Done.
|
| + |
| + DCHECK_EQ(frames, static_cast<size_t>(output_bus_->frames())); |
| + |
| + // Determine latency and pass that on to the source. |
| + 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_ / output_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, err); |
| +} |
| + |
| +} // namespace media |