Earl Ou | edb7158 | 2016-11-29 15:59:57 +0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| 3 | * Use of this source code is governed by a BSD-style license that can be |
| 4 | * found in the LICENSE file. |
| 5 | */ |
| 6 | |
| 7 | #include <stdio.h> |
| 8 | #include <signal.h> |
| 9 | #include <pthread.h> |
| 10 | #include <time.h> |
| 11 | #include <unistd.h> |
| 12 | |
Earl Ou | 7ae955b | 2016-11-30 16:41:22 +0800 | [diff] [blame] | 13 | #include "include/libaudiodev.h" |
Earl Ou | edb7158 | 2016-11-29 15:59:57 +0800 | [diff] [blame] | 14 | |
| 15 | typedef struct { |
| 16 | unsigned char *data; |
| 17 | } audio_buffer; |
| 18 | |
| 19 | static int verbose = 0; |
| 20 | |
| 21 | static int buffer_count; // Total number of buffer |
| 22 | static pthread_mutex_t buf_mutex; // This protects the variables below |
| 23 | audio_buffer *buffers; |
| 24 | static int write_index; // buffer should be written next |
| 25 | static int read_index; // buffer should be read next |
| 26 | static int write_available; // number of buffers can be write |
| 27 | static int read_available; // number of buffers can be read |
| 28 | static pthread_cond_t has_data; |
| 29 | static struct timespec cap_start_time, play_start_time; |
| 30 | static int total_cap_frames, total_play_frames; |
| 31 | |
| 32 | /* Termination variable. */ |
| 33 | static int terminate; |
| 34 | |
| 35 | static void set_current_time(struct timespec *ts) { |
| 36 | clock_gettime(CLOCK_MONOTONIC, ts); |
| 37 | } |
| 38 | |
| 39 | // Returns the time since the given time in nanoseconds. |
| 40 | static long long since(struct timespec *ts) { |
| 41 | struct timespec now; |
| 42 | clock_gettime(CLOCK_MONOTONIC, &now); |
| 43 | long long t = now.tv_sec - ts->tv_sec; |
| 44 | t *= 1000000000; |
| 45 | t += (now.tv_nsec - ts->tv_nsec); |
| 46 | return t; |
| 47 | } |
| 48 | |
| 49 | static void update_stat() { |
| 50 | if (verbose) { |
| 51 | double cap_rate = total_cap_frames * 1e9 / since(&cap_start_time); |
| 52 | double play_rate = total_play_frames * 1e9 / since(&play_start_time); |
| 53 | printf("Buffer: %d/%d, Capture: %d, Play: %d \r", |
| 54 | read_available, buffer_count, |
| 55 | (int) cap_rate, (int) play_rate); |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | static void *play_loop(void *arg) { |
| 60 | audio_device_t *device = (audio_device_t *)arg; |
| 61 | int buf_play; |
| 62 | |
| 63 | pthread_mutex_lock(&buf_mutex); |
| 64 | // Wait until half of the buffers are filled. |
| 65 | while (!terminate && read_available < buffer_count / 2) { |
| 66 | pthread_cond_wait(&has_data, &buf_mutex); |
| 67 | } |
| 68 | |
| 69 | // Now start playing |
| 70 | set_current_time(&play_start_time); |
| 71 | total_play_frames = 0; |
| 72 | while (!terminate) { |
| 73 | while (read_available == 0) { |
| 74 | pthread_cond_wait(&has_data, &buf_mutex); |
| 75 | } |
| 76 | buf_play = read_index; |
| 77 | read_index = (read_index + 1) % buffer_count; |
| 78 | read_available--; |
| 79 | |
| 80 | pthread_mutex_unlock(&buf_mutex); |
| 81 | pcm_io(device, buffers[buf_play].data, chunk_size); |
| 82 | pthread_mutex_lock(&buf_mutex); |
| 83 | |
| 84 | total_play_frames += chunk_size; |
| 85 | write_available++; |
| 86 | update_stat(); |
| 87 | } |
| 88 | pthread_mutex_unlock(&buf_mutex); |
| 89 | |
| 90 | return NULL; |
| 91 | } |
| 92 | |
| 93 | static void *cap_loop(void *arg) { |
| 94 | audio_device_t *device = (audio_device_t *)arg; |
| 95 | int buf_cap; |
| 96 | |
| 97 | pthread_mutex_lock(&buf_mutex); |
| 98 | total_cap_frames = 0; |
| 99 | set_current_time(&cap_start_time); |
| 100 | while (!terminate) { |
| 101 | // If we have no more buffer to write, drop the oldest one |
| 102 | if (write_available == 0) { |
| 103 | read_index = (read_index + 1) % buffer_count; |
| 104 | read_available--; |
| 105 | } else { |
| 106 | write_available--; |
| 107 | } |
| 108 | buf_cap = write_index; |
| 109 | write_index = (write_index + 1) % buffer_count; |
| 110 | |
| 111 | pthread_mutex_unlock(&buf_mutex); |
| 112 | pcm_io(device, buffers[buf_cap].data, chunk_size); |
| 113 | pthread_mutex_lock(&buf_mutex); |
| 114 | |
| 115 | total_cap_frames += chunk_size; |
| 116 | read_available++; |
| 117 | pthread_cond_signal(&has_data); |
| 118 | update_stat(); |
| 119 | } |
| 120 | pthread_mutex_unlock(&buf_mutex); |
| 121 | |
| 122 | return NULL; |
| 123 | } |
| 124 | |
| 125 | static void signal_handler(int signal) { |
| 126 | printf("Signal Caught.\n"); |
| 127 | |
| 128 | terminate = 1; |
| 129 | } |
| 130 | |
| 131 | static void dump_line(FILE *fp) { |
| 132 | int ch; |
| 133 | while ((ch = fgetc(fp)) != EOF && ch != '\n') {} |
| 134 | } |
| 135 | |
| 136 | static void get_choice(char *direction_name, audio_device_info_list_t *list, |
| 137 | int *choice) { |
| 138 | int i; |
| 139 | while (1) { |
| 140 | printf("%s devices:\n", direction_name); |
| 141 | if (list->count == 0) { |
| 142 | printf("No devices :(\n"); |
| 143 | exit(EXIT_FAILURE); |
| 144 | } |
| 145 | |
| 146 | for (i = 0; i < list->count; i++) { |
| 147 | printf("(%d)\nCard %d: %s, %s\n Device %d: %s [%s], %s", i + 1, |
| 148 | list->devs[i].card, list->devs[i].dev_id, |
| 149 | list->devs[i].dev_name, list->devs[i].dev_no, |
| 150 | list->devs[i].pcm_id, list->devs[i].pcm_name, |
| 151 | list->devs[i].audio_device.hwdevname); |
| 152 | printf("\n"); |
| 153 | } |
| 154 | printf("\nChoose one(1 - %d): ", list->count); |
| 155 | |
| 156 | if (scanf("%d", choice) == 0) { |
| 157 | dump_line(stdin); |
| 158 | printf("\nThat was an invalid choice.\n"); |
| 159 | } else if (*choice > 0 && *choice <= list->count) { |
| 160 | break; |
| 161 | } else { |
| 162 | printf("\nThat was an invalid choice.\n"); |
| 163 | } |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | static void init_buffers(int size) { |
| 168 | int i; |
| 169 | buffers = (audio_buffer *)malloc(buffer_count * sizeof(audio_buffer)); |
| 170 | if (!buffers) { |
| 171 | fprintf(stderr, "Error: Could not create audio buffer array.\n"); |
| 172 | exit(EXIT_FAILURE); |
| 173 | } |
| 174 | pthread_mutex_init(&buf_mutex, NULL); |
| 175 | pthread_cond_init(&has_data, NULL); |
| 176 | for (i = 0; i < buffer_count; i++) { |
| 177 | buffers[i].data = (unsigned char *)malloc(size); |
| 178 | if (!buffers[i].data) { |
| 179 | fprintf(stderr, "Error: Could not create audio buffers.\n"); |
| 180 | exit(EXIT_FAILURE); |
| 181 | } |
| 182 | } |
| 183 | read_index = write_index = 0; |
| 184 | read_available = 0; |
| 185 | write_available = buffer_count; |
| 186 | } |
| 187 | |
| 188 | void test(int buffer_size, unsigned int ct, char *pdev_name, char *cdev_name) { |
| 189 | pthread_t capture_thread; |
| 190 | pthread_t playback_thread; |
| 191 | buffer_count = ct; |
| 192 | |
| 193 | audio_device_info_list_t *playback_list = NULL; |
| 194 | audio_device_info_list_t *capture_list = NULL; |
| 195 | |
| 196 | // Actual playback and capture devices we use to loop. Their |
| 197 | // pcm handle will be closed in close_sound_handle. |
| 198 | audio_device_t playback_device; |
| 199 | audio_device_t capture_device; |
| 200 | |
| 201 | if (pdev_name) { |
| 202 | playback_device.direction = SND_PCM_STREAM_PLAYBACK; |
| 203 | playback_device.handle = NULL; |
| 204 | strcpy(playback_device.hwdevname, pdev_name); |
| 205 | } else { |
| 206 | playback_list = get_device_list(SND_PCM_STREAM_PLAYBACK); |
| 207 | int pdev; |
| 208 | get_choice("playback", playback_list, &pdev); |
| 209 | playback_device = playback_list->devs[pdev - 1].audio_device; |
| 210 | } |
| 211 | |
| 212 | if (cdev_name) { |
| 213 | capture_device.direction = SND_PCM_STREAM_CAPTURE; |
| 214 | capture_device.handle = NULL; |
| 215 | strcpy(capture_device.hwdevname, cdev_name); |
| 216 | } else { |
| 217 | capture_list = get_device_list(SND_PCM_STREAM_CAPTURE); |
| 218 | int cdev; |
| 219 | get_choice("capture", capture_list, &cdev); |
| 220 | capture_device = capture_list->devs[cdev - 1].audio_device; |
| 221 | } |
| 222 | |
| 223 | init_buffers(buffer_size); |
| 224 | terminate = 0; |
| 225 | |
| 226 | signal(SIGINT, signal_handler); |
| 227 | signal(SIGTERM, signal_handler); |
| 228 | signal(SIGABRT, signal_handler); |
| 229 | |
| 230 | if (create_sound_handle(&playback_device, buffer_size) || |
| 231 | create_sound_handle(&capture_device, buffer_size)) |
| 232 | exit(EXIT_FAILURE); |
| 233 | |
| 234 | pthread_create(&playback_thread, NULL, play_loop, &playback_device); |
| 235 | pthread_create(&capture_thread, NULL, cap_loop, &capture_device); |
| 236 | |
| 237 | pthread_join(capture_thread, NULL); |
| 238 | pthread_join(playback_thread, NULL); |
| 239 | |
| 240 | close_sound_handle(&playback_device); |
| 241 | close_sound_handle(&capture_device); |
| 242 | |
| 243 | if (playback_list) |
| 244 | free_device_list(playback_list); |
| 245 | if (capture_list) |
| 246 | free_device_list(capture_list); |
| 247 | |
| 248 | printf("Exiting.\n"); |
| 249 | } |
| 250 | |
| 251 | int main(int argc, char **argv) { |
| 252 | char *play_dev = NULL; |
| 253 | char *cap_dev = NULL; |
| 254 | int count = 100; |
| 255 | int size = 1024; |
| 256 | int arg; |
| 257 | |
| 258 | while ((arg = getopt(argc, argv, "i:o:c:s:v")) != -1) { |
| 259 | switch(arg) { |
| 260 | case 'i': |
| 261 | cap_dev = optarg; |
| 262 | break; |
| 263 | case 'o': |
| 264 | play_dev = optarg; |
| 265 | break; |
| 266 | case 'c': |
| 267 | count = atoi(optarg); |
| 268 | break; |
| 269 | case 's': |
| 270 | size = atoi(optarg); |
| 271 | break; |
| 272 | case 'v': |
| 273 | verbose = 1; |
| 274 | break; |
| 275 | case '?': |
| 276 | if (optopt == 'i' || optopt == 'o' || optopt == 'c' || optopt == 's') { |
| 277 | fprintf(stderr, "Option -%c requires an argument.\n", optopt); |
| 278 | } else { |
| 279 | fprintf(stderr, "Unknown Option -%c.\n", optopt); |
| 280 | } |
| 281 | default: |
| 282 | return 1; |
| 283 | } |
| 284 | } |
| 285 | |
| 286 | test(size, count, play_dev, cap_dev); |
| 287 | return 0; |
| 288 | } |