Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(99)

Unified Diff: media/audio/cras/cras_unified.cc

Issue 11959018: Add a unified audio I/O backend for ChromeOS. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Save params, other cleanups suggested by Dale Created 7 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « media/audio/cras/cras_unified.h ('k') | media/audio/cras/cras_unified_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « media/audio/cras/cras_unified.h ('k') | media/audio/cras/cras_unified_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698