blob: ee5fdd09ecf138c6884a619ea2c2fa440e3eaa28 [file] [log] [blame]
// Copyright 2018 The Chromium OS 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_perception/cras_client_impl.h"
#include <syslog.h>
namespace {
// Max number of devices to probe.
const size_t MAX_IODEVS = 10;
// Max number of nodes to probe.
const size_t MAX_IONODES = 20;
// Callback when getting a new audio frame.
int OnAudioSamples(struct cras_client* client,
cras_stream_id_t stream_id,
uint8_t* captured_samples,
uint8_t* playback_samples,
unsigned int num_samples,
const struct timespec* captured_time,
const struct timespec* playback_time,
void* user_arg) {
mri::CrasClientImpl* cras_client = (mri::CrasClientImpl*)user_arg;
cras_client->ProcessAudioSamples(captured_samples, num_samples);
return num_samples;
}
// Callback when getting audio capture error.
int OnAudioCaptureError(struct cras_client* client,
cras_stream_id_t stream_id,
int err,
void* arg) {
syslog(LOG_ERR, "Audio capture error with code: %d.", err);
return err;
}
} // namespace
namespace mri {
CrasClientImpl::~CrasClientImpl() {
Disconnect();
}
bool CrasClientImpl::Connect() {
if (IsConnected()) {
syslog(LOG_WARNING, "Client is already connected to CrAS server.");
return true;
}
// Create and connect a client to the CrAS server. cras_helper_create_connect
// returns 0 on success, or a negative error code on failure.
int rc = cras_helper_create_connect(&client_);
if (rc < 0) {
syslog(LOG_ERR, "Failed to connect to CrAS server with error code: %d.",
rc);
DestroyClient();
return false;
}
return true;
}
void CrasClientImpl::Disconnect() {
StopAudioCapture();
DestroyParams();
DestroyClient();
}
bool CrasClientImpl::IsConnected() const {
return client_ != nullptr;
}
bool CrasClientImpl::SetParams(const std::string& device_name,
unsigned int num_channels,
unsigned int block_size,
unsigned int frame_rate,
snd_pcm_format_t format) {
DestroyParams();
audio_format_ = cras_audio_format_create(format, frame_rate, num_channels);
if (!audio_format_) {
syslog(LOG_ERR, "Failed to create CrAS audio format.");
DestroyParams();
return false;
}
audio_capture_params_ = cras_client_unified_params_create(
CRAS_STREAM_INPUT, block_size, CRAS_STREAM_TYPE_DEFAULT, 0, this,
OnAudioSamples, OnAudioCaptureError, audio_format_);
if (!audio_capture_params_) {
syslog(LOG_ERR, "Failed to create CrAS audio capture format.");
DestroyParams();
return false;
}
device_name_ = device_name;
return true;
}
bool CrasClientImpl::StartAudioCapture() {
if (!IsConnected()) {
return false;
}
if (HasAudioCaptureStarted()) {
return true;
}
struct cras_iodev_info devs[MAX_IODEVS];
struct cras_ionode_info nodes[MAX_IONODES];
size_t num_devs = MAX_IODEVS;
size_t num_nodes = MAX_IONODES;
// Get the current list of input devices. Return 0 on success, -EINVAL if the
// client isn't valid or isn't running.
int rc = cras_client_get_input_devices(client_, devs, nodes, &num_devs,
&num_nodes);
if (rc != 0) {
syslog(LOG_ERR, "Failed to query audio input devices.");
return false;
}
// Search for the device that matches the given name.
int dev_idx = NO_DEVICE;
bool found_matched_device = false;
for (int i = 0; !device_name_.empty() && i < num_devs; i++) {
if (std::string(devs[i].name).find(device_name_) != std::string::npos) {
dev_idx = devs[i].idx;
syslog(LOG_INFO, "Found audio device: ID=%u, name=%s.", devs[i].idx,
devs[i].name);
found_matched_device = true;
break;
}
}
if (!device_name_.empty() && !found_matched_device) {
syslog(LOG_ERR, "Failed to find matched audio input device: %s.",
device_name_.c_str());
return false;
}
// Create a pinned stream. Return 0 on success, or negative error code on
// failure.
cras_stream_id_t stream_id;
rc = cras_client_add_pinned_stream(client_, dev_idx, &stream_id,
audio_capture_params_);
if (rc != 0) {
syslog(LOG_ERR, "Failed to add pinned stream with error code: %d.", rc);
return false;
}
has_audio_capture_started_ = true;
return true;
}
void CrasClientImpl::StopAudioCapture() {
if (IsConnected() && HasAudioCaptureStarted()) {
cras_client_rm_stream(client_, stream_id_);
has_audio_capture_started_ = false;
}
}
bool CrasClientImpl::HasAudioCaptureStarted() const {
return has_audio_capture_started_;
}
void CrasClientImpl::ProcessAudioSamples(const uint8_t* samples,
unsigned int num_frames) {
if (!audio_input_handler_) {
return;
}
const int bytes_per_frame = cras_client_format_bytes_per_frame(audio_format_);
const int total_bytes = bytes_per_frame * num_frames;
audio_input_handler_(samples, total_bytes);
}
void CrasClientImpl::DestroyParams() {
if (audio_format_) {
cras_audio_format_destroy(audio_format_);
audio_format_ = nullptr;
}
if (audio_capture_params_) {
cras_client_stream_params_destroy(audio_capture_params_);
audio_capture_params_ = nullptr;
}
}
void CrasClientImpl::DestroyClient() {
if (client_) {
cras_client_destroy(client_);
client_ = nullptr;
}
}
} // namespace mri