| /* Copyright (c) 2013 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 <pthread.h> |
| #include <sys/param.h> |
| #include <syslog.h> |
| |
| #include "audio_thread_log.h" |
| #include "byte_buffer.h" |
| #include "cras_audio_area.h" |
| #include "cras_config.h" |
| #include "cras_iodev.h" |
| #include "cras_iodev_list.h" |
| #include "cras_types.h" |
| #include "cras_util.h" |
| #include "sfh.h" |
| #include "utlist.h" |
| |
| #define LOOPBACK_BUFFER_SIZE 8192 |
| |
| static const char *loopdev_names[LOOPBACK_NUM_TYPES] = { |
| "Post Mix Pre DSP Loopback", |
| "Post DSP Loopback", |
| }; |
| |
| static size_t loopback_supported_rates[] = { 48000, 0 }; |
| |
| static size_t loopback_supported_channel_counts[] = { 2, 0 }; |
| |
| static snd_pcm_format_t loopback_supported_formats[] = { |
| SND_PCM_FORMAT_S16_LE, |
| 0, |
| }; |
| |
| /* loopack iodev. Keep state of a loopback device. |
| * loopback_type - Pre-dsp or post-dsp. |
| * read_frames - Frames of audio data read since last dev start. |
| * started - True to indicate the target device is running, otherwise false. |
| * dev_start_time - The timestamp of the last call to configure_dev. |
| * sample_buffer - Pointer to sample buffer. |
| * sender_idx - Index of the output device to read loopback audio. |
| */ |
| struct loopback_iodev { |
| struct cras_iodev base; |
| enum CRAS_LOOPBACK_TYPE loopback_type; |
| uint64_t read_frames; |
| bool started; |
| struct timespec dev_start_time; |
| struct byte_buffer *sample_buffer; |
| unsigned int sender_idx; |
| }; |
| |
| static int sample_hook_start(bool start, void *cb_data) |
| { |
| struct loopback_iodev *loopdev = (struct loopback_iodev *)cb_data; |
| loopdev->started = start; |
| return 0; |
| } |
| |
| /* |
| * Called in the put buffer function of the sender that hooked to. |
| * |
| * Returns: |
| * Number of frames copied to the sample buffer in the hook. |
| */ |
| static int sample_hook(const uint8_t *frames, unsigned int nframes, |
| const struct cras_audio_format *fmt, void *cb_data) |
| { |
| struct loopback_iodev *loopdev = (struct loopback_iodev *)cb_data; |
| struct byte_buffer *sbuf = loopdev->sample_buffer; |
| unsigned int frame_bytes = cras_get_format_bytes(fmt); |
| unsigned int frames_to_copy, bytes_to_copy, frames_copied = 0; |
| int i; |
| |
| for (i = 0; i < 2; i++) { |
| frames_to_copy = MIN(buf_writable(sbuf) / frame_bytes, nframes); |
| if (!frames_to_copy) |
| break; |
| |
| bytes_to_copy = frames_to_copy * frame_bytes; |
| memcpy(buf_write_pointer(sbuf), frames, bytes_to_copy); |
| buf_increment_write(sbuf, bytes_to_copy); |
| frames += bytes_to_copy; |
| nframes -= frames_to_copy; |
| frames_copied += frames_to_copy; |
| } |
| |
| ATLOG(atlog, AUDIO_THREAD_LOOPBACK_SAMPLE_HOOK, nframes + frames_copied, |
| frames_copied, 0); |
| |
| return frames_copied; |
| } |
| |
| static void update_first_output_to_loopback(struct loopback_iodev *loopdev) |
| { |
| struct cras_iodev *edev; |
| |
| /* Register loopback hook onto first enabled iodev. */ |
| edev = cras_iodev_list_get_first_enabled_iodev(CRAS_STREAM_OUTPUT); |
| if (edev) { |
| loopdev->sender_idx = edev->info.idx; |
| cras_iodev_list_register_loopback( |
| loopdev->loopback_type, loopdev->sender_idx, |
| sample_hook, sample_hook_start, loopdev->base.info.idx); |
| } |
| } |
| |
| static void device_enabled_hook(struct cras_iodev *iodev, void *cb_data) |
| { |
| struct loopback_iodev *loopdev = (struct loopback_iodev *)cb_data; |
| |
| if (iodev->direction != CRAS_STREAM_OUTPUT) |
| return; |
| |
| update_first_output_to_loopback(loopdev); |
| } |
| |
| static void device_disabled_hook(struct cras_iodev *iodev, void *cb_data) |
| { |
| struct loopback_iodev *loopdev = (struct loopback_iodev *)cb_data; |
| |
| if (loopdev->sender_idx != iodev->info.idx) |
| return; |
| |
| /* Unregister loopback hook from disabled iodev. */ |
| cras_iodev_list_unregister_loopback(loopdev->loopback_type, |
| loopdev->sender_idx, |
| loopdev->base.info.idx); |
| update_first_output_to_loopback(loopdev); |
| } |
| |
| /* |
| * iodev callbacks. |
| */ |
| |
| static int frames_queued(const struct cras_iodev *iodev, |
| struct timespec *hw_tstamp) |
| { |
| struct loopback_iodev *loopdev = (struct loopback_iodev *)iodev; |
| struct byte_buffer *sbuf = loopdev->sample_buffer; |
| unsigned int frame_bytes = cras_get_format_bytes(iodev->format); |
| |
| if (!loopdev->started) { |
| unsigned int frames_since_start, frames_to_fill, bytes_to_fill; |
| |
| frames_since_start = cras_frames_since_time( |
| &loopdev->dev_start_time, iodev->format->frame_rate); |
| frames_to_fill = |
| frames_since_start > loopdev->read_frames ? |
| frames_since_start - loopdev->read_frames : |
| 0; |
| frames_to_fill = |
| MIN(buf_writable(sbuf) / frame_bytes, frames_to_fill); |
| if (frames_to_fill > 0) { |
| bytes_to_fill = frames_to_fill * frame_bytes; |
| memset(buf_write_pointer(sbuf), 0, bytes_to_fill); |
| buf_increment_write(sbuf, bytes_to_fill); |
| } |
| } |
| clock_gettime(CLOCK_MONOTONIC_RAW, hw_tstamp); |
| return buf_queued(sbuf) / frame_bytes; |
| } |
| |
| static int delay_frames(const struct cras_iodev *iodev) |
| { |
| struct timespec tstamp; |
| |
| return frames_queued(iodev, &tstamp); |
| } |
| |
| static int close_record_dev(struct cras_iodev *iodev) |
| { |
| struct loopback_iodev *loopdev = (struct loopback_iodev *)iodev; |
| struct byte_buffer *sbuf = loopdev->sample_buffer; |
| |
| cras_iodev_free_format(iodev); |
| cras_iodev_free_audio_area(iodev); |
| buf_reset(sbuf); |
| |
| cras_iodev_list_unregister_loopback(loopdev->loopback_type, |
| loopdev->sender_idx, |
| loopdev->base.info.idx); |
| loopdev->sender_idx = NO_DEVICE; |
| cras_iodev_list_set_device_enabled_callback(NULL, NULL, (void *)iodev); |
| |
| return 0; |
| } |
| |
| static int configure_record_dev(struct cras_iodev *iodev) |
| { |
| struct loopback_iodev *loopdev = (struct loopback_iodev *)iodev; |
| struct cras_iodev *edev; |
| |
| cras_iodev_init_audio_area(iodev, iodev->format->num_channels); |
| clock_gettime(CLOCK_MONOTONIC_RAW, &loopdev->dev_start_time); |
| loopdev->read_frames = 0; |
| loopdev->started = 0; |
| |
| edev = cras_iodev_list_get_first_enabled_iodev(CRAS_STREAM_OUTPUT); |
| if (edev) { |
| loopdev->sender_idx = edev->info.idx; |
| cras_iodev_list_register_loopback( |
| loopdev->loopback_type, loopdev->sender_idx, |
| sample_hook, sample_hook_start, iodev->info.idx); |
| } |
| cras_iodev_list_set_device_enabled_callback( |
| device_enabled_hook, device_disabled_hook, (void *)iodev); |
| |
| return 0; |
| } |
| |
| static int get_record_buffer(struct cras_iodev *iodev, |
| struct cras_audio_area **area, unsigned *frames) |
| { |
| struct loopback_iodev *loopdev = (struct loopback_iodev *)iodev; |
| struct byte_buffer *sbuf = loopdev->sample_buffer; |
| unsigned int frame_bytes = cras_get_format_bytes(iodev->format); |
| unsigned int avail_frames = buf_readable(sbuf) / frame_bytes; |
| |
| ATLOG(atlog, AUDIO_THREAD_LOOPBACK_GET, *frames, avail_frames, 0); |
| |
| *frames = MIN(avail_frames, *frames); |
| iodev->area->frames = *frames; |
| cras_audio_area_config_buf_pointers(iodev->area, iodev->format, |
| buf_read_pointer(sbuf)); |
| *area = iodev->area; |
| |
| return 0; |
| } |
| |
| static int put_record_buffer(struct cras_iodev *iodev, unsigned nframes) |
| { |
| struct loopback_iodev *loopdev = (struct loopback_iodev *)iodev; |
| struct byte_buffer *sbuf = loopdev->sample_buffer; |
| unsigned int frame_bytes = cras_get_format_bytes(iodev->format); |
| |
| buf_increment_read(sbuf, (size_t)nframes * (size_t)frame_bytes); |
| loopdev->read_frames += nframes; |
| ATLOG(atlog, AUDIO_THREAD_LOOPBACK_PUT, nframes, 0, 0); |
| return 0; |
| } |
| |
| static int flush_record_buffer(struct cras_iodev *iodev) |
| { |
| struct loopback_iodev *loopdev = (struct loopback_iodev *)iodev; |
| struct byte_buffer *sbuf = loopdev->sample_buffer; |
| unsigned int queued_bytes = buf_queued(sbuf); |
| buf_increment_read(sbuf, queued_bytes); |
| loopdev->read_frames = 0; |
| return 0; |
| } |
| |
| static void update_active_node(struct cras_iodev *iodev, unsigned node_idx, |
| unsigned dev_enabled) |
| { |
| } |
| |
| static struct cras_iodev *create_loopback_iodev(enum CRAS_LOOPBACK_TYPE type) |
| { |
| struct loopback_iodev *loopback_iodev; |
| struct cras_iodev *iodev; |
| |
| loopback_iodev = calloc(1, sizeof(*loopback_iodev)); |
| if (loopback_iodev == NULL) |
| return NULL; |
| |
| loopback_iodev->sample_buffer = byte_buffer_create(1024 * 16 * 4); |
| if (loopback_iodev->sample_buffer == NULL) { |
| free(loopback_iodev); |
| return NULL; |
| } |
| |
| loopback_iodev->loopback_type = type; |
| |
| iodev = &loopback_iodev->base; |
| iodev->direction = CRAS_STREAM_INPUT; |
| snprintf(iodev->info.name, ARRAY_SIZE(iodev->info.name), "%s", |
| loopdev_names[type]); |
| iodev->info.name[ARRAY_SIZE(iodev->info.name) - 1] = '\0'; |
| iodev->info.stable_id = |
| SuperFastHash(iodev->info.name, strlen(iodev->info.name), |
| strlen(iodev->info.name)); |
| |
| iodev->supported_rates = loopback_supported_rates; |
| iodev->supported_channel_counts = loopback_supported_channel_counts; |
| iodev->supported_formats = loopback_supported_formats; |
| iodev->buffer_size = LOOPBACK_BUFFER_SIZE; |
| |
| iodev->frames_queued = frames_queued; |
| iodev->delay_frames = delay_frames; |
| iodev->update_active_node = update_active_node; |
| iodev->configure_dev = configure_record_dev; |
| iodev->close_dev = close_record_dev; |
| iodev->get_buffer = get_record_buffer; |
| iodev->put_buffer = put_record_buffer; |
| iodev->flush_buffer = flush_record_buffer; |
| |
| /* |
| * Record max supported channels into cras_iodev_info. |
| * The value is the max of loopback_supported_channel_counts. |
| */ |
| iodev->info.max_supported_channels = 2; |
| |
| return iodev; |
| } |
| |
| /* |
| * Exported Interface. |
| */ |
| |
| struct cras_iodev *loopback_iodev_create(enum CRAS_LOOPBACK_TYPE type) |
| { |
| struct cras_iodev *iodev; |
| struct cras_ionode *node; |
| enum CRAS_NODE_TYPE node_type; |
| |
| switch (type) { |
| case LOOPBACK_POST_MIX_PRE_DSP: |
| node_type = CRAS_NODE_TYPE_POST_MIX_PRE_DSP; |
| break; |
| case LOOPBACK_POST_DSP: |
| node_type = CRAS_NODE_TYPE_POST_DSP; |
| break; |
| default: |
| return NULL; |
| } |
| |
| iodev = create_loopback_iodev(type); |
| if (iodev == NULL) |
| return NULL; |
| |
| /* Create an empty ionode */ |
| node = (struct cras_ionode *)calloc(1, sizeof(*node)); |
| node->dev = iodev; |
| node->type = node_type; |
| node->plugged = 1; |
| node->volume = 100; |
| node->ui_gain_scaler = 1.0f; |
| node->stable_id = iodev->info.stable_id; |
| node->software_volume_needed = 0; |
| strcpy(node->name, loopdev_names[type]); |
| cras_iodev_add_node(iodev, node); |
| cras_iodev_set_active_node(iodev, node); |
| |
| cras_iodev_list_add_input(iodev); |
| |
| return iodev; |
| } |
| |
| void loopback_iodev_destroy(struct cras_iodev *iodev) |
| { |
| struct loopback_iodev *loopdev = (struct loopback_iodev *)iodev; |
| struct byte_buffer *sbuf = loopdev->sample_buffer; |
| |
| cras_iodev_list_rm_input(iodev); |
| free(iodev->nodes); |
| |
| byte_buffer_destroy(&sbuf); |
| free(loopdev); |
| } |