普通文本  |  401行  |  13.65 KB

// 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/alsa/alsa_util.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 (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);

  // 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(
      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;
  }

  // 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_->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