| /* |
| * Copyright (c) 2011 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 <stdio.h> |
| #include <signal.h> |
| #include <pthread.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include "libaudiodev.h" |
| |
| typedef struct { |
| unsigned char *data; |
| } audio_buffer; |
| |
| static int verbose = 0; |
| |
| static int buffer_count; // Total number of buffer |
| static pthread_mutex_t buf_mutex; // This protects the variables below |
| audio_buffer *buffers; |
| static int write_index; // buffer should be written next |
| static int read_index; // buffer should be read next |
| static int write_available; // number of buffers can be write |
| static int read_available; // number of buffers can be read |
| static pthread_cond_t has_data; |
| static struct timespec cap_start_time, play_start_time; |
| static int total_cap_frames, total_play_frames; |
| |
| /* Termination variable. */ |
| static int terminate; |
| |
| static void set_current_time(struct timespec *ts) { |
| clock_gettime(CLOCK_MONOTONIC, ts); |
| } |
| |
| // Returns the time since the given time in nanoseconds. |
| static long long since(struct timespec *ts) { |
| struct timespec now; |
| clock_gettime(CLOCK_MONOTONIC, &now); |
| long long t = now.tv_sec - ts->tv_sec; |
| t *= 1000000000; |
| t += (now.tv_nsec - ts->tv_nsec); |
| return t; |
| } |
| |
| static void update_stat() { |
| if (verbose) { |
| double cap_rate = total_cap_frames * 1e9 / since(&cap_start_time); |
| double play_rate = total_play_frames * 1e9 / since(&play_start_time); |
| printf("Buffer: %d/%d, Capture: %d, Play: %d \r", |
| read_available, buffer_count, |
| (int) cap_rate, (int) play_rate); |
| } |
| } |
| |
| static void *play_loop(void *arg) { |
| audio_device_t *device = (audio_device_t *)arg; |
| int buf_play; |
| |
| pthread_mutex_lock(&buf_mutex); |
| // Wait until half of the buffers are filled. |
| while (!terminate && read_available < buffer_count / 2) { |
| pthread_cond_wait(&has_data, &buf_mutex); |
| } |
| |
| // Now start playing |
| set_current_time(&play_start_time); |
| total_play_frames = 0; |
| while (!terminate) { |
| while (read_available == 0) { |
| pthread_cond_wait(&has_data, &buf_mutex); |
| } |
| buf_play = read_index; |
| read_index = (read_index + 1) % buffer_count; |
| read_available--; |
| |
| pthread_mutex_unlock(&buf_mutex); |
| pcm_io(device, buffers[buf_play].data, chunk_size); |
| pthread_mutex_lock(&buf_mutex); |
| |
| total_play_frames += chunk_size; |
| write_available++; |
| update_stat(); |
| } |
| pthread_mutex_unlock(&buf_mutex); |
| |
| return NULL; |
| } |
| |
| static void *cap_loop(void *arg) { |
| audio_device_t *device = (audio_device_t *)arg; |
| int buf_cap; |
| |
| pthread_mutex_lock(&buf_mutex); |
| total_cap_frames = 0; |
| set_current_time(&cap_start_time); |
| while (!terminate) { |
| // If we have no more buffer to write, drop the oldest one |
| if (write_available == 0) { |
| read_index = (read_index + 1) % buffer_count; |
| read_available--; |
| } else { |
| write_available--; |
| } |
| buf_cap = write_index; |
| write_index = (write_index + 1) % buffer_count; |
| |
| pthread_mutex_unlock(&buf_mutex); |
| pcm_io(device, buffers[buf_cap].data, chunk_size); |
| pthread_mutex_lock(&buf_mutex); |
| |
| total_cap_frames += chunk_size; |
| read_available++; |
| pthread_cond_signal(&has_data); |
| update_stat(); |
| } |
| pthread_mutex_unlock(&buf_mutex); |
| |
| return NULL; |
| } |
| |
| static void signal_handler(int signal) { |
| printf("Signal Caught.\n"); |
| |
| terminate = 1; |
| } |
| |
| static void dump_line(FILE *fp) { |
| int ch; |
| while ((ch = fgetc(fp)) != EOF && ch != '\n') {} |
| } |
| |
| static void get_choice(char *direction_name, audio_device_info_list_t *list, |
| int *choice) { |
| int i; |
| while (1) { |
| printf("%s devices:\n", direction_name); |
| if (list->count == 0) { |
| printf("No devices :(\n"); |
| exit(EXIT_FAILURE); |
| } |
| |
| for (i = 0; i < list->count; i++) { |
| printf("(%d)\nCard %d: %s, %s\n Device %d: %s [%s], %s", i + 1, |
| list->devs[i].card, list->devs[i].dev_id, |
| list->devs[i].dev_name, list->devs[i].dev_no, |
| list->devs[i].pcm_id, list->devs[i].pcm_name, |
| list->devs[i].audio_device.hwdevname); |
| printf("\n"); |
| } |
| printf("\nChoose one(1 - %d): ", list->count); |
| |
| if (scanf("%d", choice) == 0) { |
| dump_line(stdin); |
| printf("\nThat was an invalid choice.\n"); |
| } else if (*choice > 0 && *choice <= list->count) { |
| break; |
| } else { |
| printf("\nThat was an invalid choice.\n"); |
| } |
| } |
| } |
| |
| static void init_buffers(int size) { |
| int i; |
| buffers = (audio_buffer *)malloc(buffer_count * sizeof(audio_buffer)); |
| if (!buffers) { |
| fprintf(stderr, "Error: Could not create audio buffer array.\n"); |
| exit(EXIT_FAILURE); |
| } |
| pthread_mutex_init(&buf_mutex, NULL); |
| pthread_cond_init(&has_data, NULL); |
| for (i = 0; i < buffer_count; i++) { |
| buffers[i].data = (unsigned char *)malloc(size); |
| if (!buffers[i].data) { |
| fprintf(stderr, "Error: Could not create audio buffers.\n"); |
| exit(EXIT_FAILURE); |
| } |
| } |
| read_index = write_index = 0; |
| read_available = 0; |
| write_available = buffer_count; |
| } |
| |
| void test(int buffer_size, unsigned int ct, char *pdev_name, char *cdev_name) { |
| pthread_t capture_thread; |
| pthread_t playback_thread; |
| buffer_count = ct; |
| |
| audio_device_info_list_t *playback_list = NULL; |
| audio_device_info_list_t *capture_list = NULL; |
| |
| // Actual playback and capture devices we use to loop. Their |
| // pcm handle will be closed in close_sound_handle. |
| audio_device_t playback_device; |
| audio_device_t capture_device; |
| |
| if (pdev_name) { |
| playback_device.direction = SND_PCM_STREAM_PLAYBACK; |
| playback_device.handle = NULL; |
| strcpy(playback_device.hwdevname, pdev_name); |
| } else { |
| playback_list = get_device_list(SND_PCM_STREAM_PLAYBACK); |
| int pdev; |
| get_choice("playback", playback_list, &pdev); |
| playback_device = playback_list->devs[pdev - 1].audio_device; |
| } |
| |
| if (cdev_name) { |
| capture_device.direction = SND_PCM_STREAM_CAPTURE; |
| capture_device.handle = NULL; |
| strcpy(capture_device.hwdevname, cdev_name); |
| } else { |
| capture_list = get_device_list(SND_PCM_STREAM_CAPTURE); |
| int cdev; |
| get_choice("capture", capture_list, &cdev); |
| capture_device = capture_list->devs[cdev - 1].audio_device; |
| } |
| |
| init_buffers(buffer_size); |
| terminate = 0; |
| |
| signal(SIGINT, signal_handler); |
| signal(SIGTERM, signal_handler); |
| signal(SIGABRT, signal_handler); |
| |
| if (create_sound_handle(&playback_device, buffer_size) || |
| create_sound_handle(&capture_device, buffer_size)) |
| exit(EXIT_FAILURE); |
| |
| pthread_create(&playback_thread, NULL, play_loop, &playback_device); |
| pthread_create(&capture_thread, NULL, cap_loop, &capture_device); |
| |
| pthread_join(capture_thread, NULL); |
| pthread_join(playback_thread, NULL); |
| |
| close_sound_handle(&playback_device); |
| close_sound_handle(&capture_device); |
| |
| if (playback_list) |
| free_device_list(playback_list); |
| if (capture_list) |
| free_device_list(capture_list); |
| |
| printf("Exiting.\n"); |
| } |
| |
| int main(int argc, char **argv) { |
| char *play_dev = NULL; |
| char *cap_dev = NULL; |
| int count = 100; |
| int size = 1024; |
| int arg; |
| |
| while ((arg = getopt(argc, argv, "i:o:c:s:v")) != -1) { |
| switch(arg) { |
| case 'i': |
| cap_dev = optarg; |
| break; |
| case 'o': |
| play_dev = optarg; |
| break; |
| case 'c': |
| count = atoi(optarg); |
| break; |
| case 's': |
| size = atoi(optarg); |
| break; |
| case 'v': |
| verbose = 1; |
| break; |
| case '?': |
| if (optopt == 'i' || optopt == 'o' || optopt == 'c' || optopt == 's') { |
| fprintf(stderr, "Option -%c requires an argument.\n", optopt); |
| } else { |
| fprintf(stderr, "Unknown Option -%c.\n", optopt); |
| } |
| default: |
| return 1; |
| } |
| } |
| |
| test(size, count, play_dev, cap_dev); |
| return 0; |
| } |