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