/* 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 #include #include #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); }