| 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..e88a06962a0764781171e81fc86098807a6940b9
|
| --- /dev/null
|
| +++ b/media/audio/cras/cras_unified.cc
|
| @@ -0,0 +1,369 @@
|
| +// 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 <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),
|
| + params_(params),
|
| + bytes_per_frame_(0),
|
| + is_playing_(false),
|
| + volume_(1.0),
|
| + manager_(manager),
|
| + source_callback_(NULL),
|
| + input_bus_(NULL),
|
| + output_bus_(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 (alsa_util::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);
|
| + source_callback_ = callback;
|
| +
|
| + // Only start if we can enter the playing state.
|
| + if (is_playing_)
|
| + return;
|
| +
|
| + LOG(ERROR) << "Unified Start";
|
| + // Prepare |audio_format| and |stream_params| for the stream we
|
| + // will create.
|
| + cras_audio_format* audio_format = cras_audio_format_create(
|
| + alsa_util::BitsToFormat(params_.bits_per_sample()),
|
| + params_.sample_rate(),
|
| + params_.channels());
|
| + if (!audio_format) {
|
| + LOG(WARNING) << "Error setting up audio parameters.";
|
| + 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);
|
| + }
|
| +
|
| + 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_->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) {
|
| + 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
|
|
|