source: mainline/uspace/app/wavplay/main.c

Last change on this file was 485281e, checked in by Jiri Svoboda <jiri@…>, 7 years ago

Allow specifying non-default audio target

Needed when there is more than one audio device. It would be nice
complementary functionality if we could actually configure the
default audio target.

  • Property mode set to 100644
File size: 9.4 KB
RevLine 
[e677b08]1/*
2 * Copyright (c) 2012 Jan Vesely
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup wavplay
30 * @{
31 */
32/**
33 * @file PCM playback audio devices
34 */
35
36#include <assert.h>
[508b0df1]37#include <stdatomic.h>
[e677b08]38#include <errno.h>
39#include <fibril_synch.h>
[38d150e]40#include <stdlib.h>
[5cd5079]41#include <str_error.h>
[e677b08]42#include <stdio.h>
[5cd5079]43#include <hound/client.h>
[ea6c838]44#include <pcm/sample_format.h>
[aef1799]45#include <getopt.h>
[e677b08]46
[aef1799]47#include "dplay.h"
[0a4ad7d]48#include "drec.h"
[e677b08]49#include "wave.h"
50
[d26233c]51#define READ_SIZE (32 * 1024)
52#define STREAM_BUFFER_SIZE (64 * 1024)
[abaef81]53
[a8e87da]54/**
55 * Play audio file using a new stream on provided context.
56 * @param ctx Provided context.
57 * @param filename File to play.
58 * @return Error code.
59 */
[b7fd2a0]60static errno_t hplay_ctx(hound_context_t *ctx, const char *filename)
[d26233c]61{
62 printf("Hound context playback: %s\n", filename);
63 FILE *source = fopen(filename, "rb");
64 if (!source) {
65 printf("Failed to open file %s\n", filename);
66 return EINVAL;
67 }
[a8e87da]68
69 /* Read and parse WAV header */
[d26233c]70 wave_header_t header;
71 size_t read = fread(&header, sizeof(header), 1, source);
72 if (read != 1) {
73 printf("Failed to read WAV header: %zu\n", read);
74 fclose(source);
75 return EIO;
76 }
77 pcm_format_t format;
78 const char *error;
[b7fd2a0]79 errno_t ret = wav_parse_header(&header, NULL, NULL, &format.channels,
[d26233c]80 &format.sampling_rate, &format.sample_format, &error);
81 if (ret != EOK) {
[60b1076]82 printf("Error parsing `%s' wav header: %s.\n", filename, error);
[d26233c]83 fclose(source);
84 return EINVAL;
85 }
86
[60b1076]87 printf("File `%s' format: %u channel(s), %uHz, %s.\n", filename,
88 format.channels, format.sampling_rate,
89 pcm_sample_format_str(format.sample_format));
90
[a8e87da]91 /* Allocate buffer and create new context */
[1433ecda]92 char *buffer = malloc(READ_SIZE);
[d26233c]93 if (!buffer) {
94 fclose(source);
95 return ENOMEM;
96 }
[a8e87da]97 hound_stream_t *stream = hound_stream_create(ctx,
98 HOUND_STREAM_DRAIN_ON_EXIT, format, STREAM_BUFFER_SIZE);
99
100 /* Read and play */
[d26233c]101 while ((read = fread(buffer, sizeof(char), READ_SIZE, source)) > 0) {
102 ret = hound_stream_write(stream, buffer, read);
103 if (ret != EOK) {
104 printf("Failed to write to hound stream: %s\n",
105 str_error(ret));
106 break;
107 }
108 }
[a8e87da]109
110 /* Cleanup */
[d26233c]111 free(buffer);
112 fclose(source);
113 return ret;
114}
[abaef81]115
[a8e87da]116/**
117 * Play audio file via hound server.
118 * @param filename File to play.
119 * @return Error code
120 */
[485281e]121static errno_t hplay(const char *filename, const char *target)
[abaef81]122{
[60e5696d]123 printf("Hound playback: %s\n", filename);
[abaef81]124 FILE *source = fopen(filename, "rb");
[bd5860f]125 if (!source) {
126 printf("Failed to open file %s\n", filename);
[abaef81]127 return EINVAL;
[bd5860f]128 }
[a8e87da]129
130 /* Read and parse WAV header */
[abaef81]131 wave_header_t header;
132 size_t read = fread(&header, sizeof(header), 1, source);
[bd5860f]133 if (read != 1) {
134 printf("Failed to read WAV header: %zu\n", read);
[abaef81]135 fclose(source);
136 return EIO;
137 }
[9e1800c]138 pcm_format_t format;
[abaef81]139 const char *error;
[b7fd2a0]140 errno_t ret = wav_parse_header(&header, NULL, NULL, &format.channels,
[9e1800c]141 &format.sampling_rate, &format.sample_format, &error);
[abaef81]142 if (ret != EOK) {
[60b1076]143 printf("Error parsing `%s' wav header: %s.\n", filename, error);
[abaef81]144 fclose(source);
145 return EINVAL;
146 }
[60b1076]147 printf("File `%s' format: %u channel(s), %uHz, %s.\n", filename,
148 format.channels, format.sampling_rate,
149 pcm_sample_format_str(format.sample_format));
[a8e87da]150
151 /* Connect new playback context */
[abaef81]152 hound_context_t *hound = hound_context_create_playback(filename,
[d26233c]153 format, STREAM_BUFFER_SIZE);
[abaef81]154 if (!hound) {
155 printf("Failed to create HOUND context\n");
156 fclose(source);
157 return ENOMEM;
158 }
159
[485281e]160 ret = hound_context_connect_target(hound, target);
[abaef81]161 if (ret != EOK) {
[485281e]162 printf("Failed to connect to target '%s': %s\n", target,
[abaef81]163 str_error(ret));
[485281e]164
165 char **names = NULL;
166 size_t count = 0;
167 ret = hound_context_get_available_targets(hound, &names, &count);
168 if (ret == EOK) {
169 printf("Available targets:\n");
170 for (size_t i = 0; i < count; i++)
171 printf(" - %s\n", names[i]);
172 }
173
[62beb4b]174 hound_context_destroy(hound);
[abaef81]175 fclose(source);
[62beb4b]176 return ret;
[abaef81]177 }
[a8e87da]178
179 /* Read and play */
[d26233c]180 static char buffer[READ_SIZE];
181 while ((read = fread(buffer, sizeof(char), READ_SIZE, source)) > 0) {
[bd5860f]182 ret = hound_write_main_stream(hound, buffer, read);
[abaef81]183 if (ret != EOK) {
[bd5860f]184 printf("Failed to write to main context stream: %s\n",
[abaef81]185 str_error(ret));
186 break;
187 }
188 }
[a8e87da]189
190 /* Cleanup */
[03c2d5f]191 hound_context_destroy(hound);
[abaef81]192 fclose(source);
193 return ret;
194}
195
[a8e87da]196/**
197 * Helper structure for playback in separate fibrils
198 */
[d26233c]199typedef struct {
200 hound_context_t *ctx;
[508b0df1]201 atomic_int *count;
[d26233c]202 const char *file;
203} fib_play_t;
204
[a8e87da]205/**
206 * Fibril playback wrapper.
207 * @param arg Argument, pointer to playback helper structure.
208 * @return Error code.
209 */
[b7fd2a0]210static errno_t play_wrapper(void *arg)
[d26233c]211{
212 assert(arg);
213 fib_play_t *p = arg;
[b7fd2a0]214 const errno_t ret = hplay_ctx(p->ctx, p->file);
[508b0df1]215 atomic_fetch_sub(p->count, 1);
[d26233c]216 free(arg);
217 return ret;
218}
219
[a8e87da]220/**
221 * Array of supported commandline options
222 */
[aef1799]223static const struct option opts[] = {
[1433ecda]224 { "device", required_argument, 0, 'd' },
225 { "parallel", no_argument, 0, 'p' },
226 { "record", no_argument, 0, 'r' },
[485281e]227 { "target", required_argument, 0, 't' },
[1433ecda]228 { "help", no_argument, 0, 'h' },
229 { 0, 0, 0, 0 }
[aef1799]230};
231
[a8e87da]232/**
233 * Print usage help.
234 * @param name Name of the program.
235 */
[1433ecda]236static void print_help(const char *name)
[86fe9d1]237{
[19f6ea5b]238 printf("Usage: %s [options] file [files...]\n", name);
[86fe9d1]239 printf("supported options:\n");
240 printf("\t -h, --help\t Print this help.\n");
[60e5696d]241 printf("\t -r, --record\t Start recording instead of playback. "
242 "(Not implemented)\n");
[485281e]243 printf("\t -d, --device\t Direct output to specified device instead of "
244 "the sound service. Use location path or a special device `default'\n");
245 printf("\t -t, --target\t Output to the specified audio target.\n");
[d26233c]246 printf("\t -p, --parallel\t Play given files in parallel instead of "
247 "sequentially (does not work with -d).\n");
[86fe9d1]248}
[aef1799]249
[e677b08]250int main(int argc, char *argv[])
251{
[aef1799]252 const char *device = "default";
[485281e]253 const char *target = HOUND_DEFAULT_TARGET;
[aef1799]254 int idx = 0;
[d26233c]255 bool direct = false, record = false, parallel = false;
[aef1799]256 optind = 0;
257 int ret = 0;
[a8e87da]258
259 /* Parse command line options */
[aef1799]260 while (ret != -1) {
[485281e]261 ret = getopt_long(argc, argv, "d:prt:h", opts, &idx);
[aef1799]262 switch (ret) {
263 case 'd':
264 direct = true;
265 device = optarg;
266 break;
267 case 'r':
268 record = true;
269 break;
[d26233c]270 case 'p':
271 parallel = true;
272 break;
[485281e]273 case 't':
274 target = optarg;
275 break;
[86fe9d1]276 case 'h':
277 print_help(*argv);
278 return 0;
[850fd32]279 }
[aef1799]280 }
281
[d26233c]282 if (parallel && direct) {
283 printf("Parallel playback is available only if using sound "
284 "server (no -d)\n");
285 print_help(*argv);
286 return 1;
287 }
288
[aef1799]289 if (optind == argc) {
290 printf("Not enough arguments.\n");
[86fe9d1]291 print_help(*argv);
[e677b08]292 return 1;
[aef1799]293 }
294
[a8e87da]295 /* Init parallel playback variables */
[d26233c]296 hound_context_t *hound_ctx = NULL;
[508b0df1]297 atomic_int playcount = 0;
[a8e87da]298
299 /* Init parallel playback context if necessary */
[d26233c]300 if (parallel) {
301 hound_ctx = hound_context_create_playback("wavplay",
302 AUDIO_FORMAT_DEFAULT, STREAM_BUFFER_SIZE);
303 if (!hound_ctx) {
304 printf("Failed to create global hound context\n");
305 return 1;
306 }
[b7fd2a0]307 const errno_t ret = hound_context_connect_target(hound_ctx,
[d26233c]308 HOUND_DEFAULT_TARGET);
309 if (ret != EOK) {
310 printf("Failed to connect hound context to default "
[1433ecda]311 "target.\n");
[d26233c]312 hound_context_destroy(hound_ctx);
313 return 1;
314 }
315 }
316
[a8e87da]317 /* play or record all files */
[19f6ea5b]318 for (int i = optind; i < argc; ++i) {
319 const char *file = argv[i];
320
321 printf("%s (%d/%d) %s\n", record ? "Recording" : "Playing",
322 i - optind + 1, argc - optind, file);
323 if (record) {
[0a4ad7d]324 if (direct) {
325 drecord(device, file);
[0e4c5f0]326 continue;
[0a4ad7d]327 } else {
328 printf("Indirect recording is not supported "
329 "yet.\n");
330 break;
331 }
[19f6ea5b]332 }
[a8e87da]333
[19f6ea5b]334 if (direct) {
335 dplay(device, file);
336 } else {
[d26233c]337 if (parallel) {
[a8e87da]338 /* Start new fibril for parallel playback */
[d26233c]339 fib_play_t *data = malloc(sizeof(fib_play_t));
340 if (!data) {
341 printf("Playback of %s failed.\n",
[1433ecda]342 file);
[d26233c]343 continue;
344 }
345 data->file = file;
346 data->count = &playcount;
347 data->ctx = hound_ctx;
348 fid_t fid = fibril_create(play_wrapper, data);
[508b0df1]349 atomic_fetch_add(&playcount, 1);
[d26233c]350 fibril_add_ready(fid);
351 } else {
[485281e]352 hplay(file, target);
[d26233c]353 }
[19f6ea5b]354 }
[4389076]355 }
[d26233c]356
[a8e87da]357 /* Wait for all fibrils to finish */
[508b0df1]358 while (atomic_load(&playcount) > 0)
[5f97ef44]359 fibril_usleep(1000000);
[d26233c]360
[a8e87da]361 /* Destroy parallel playback context, if initialized */
[d26233c]362 if (hound_ctx)
363 hound_context_destroy(hound_ctx);
[19f6ea5b]364 return 0;
[e677b08]365}
366/**
367 * @}
368 */
Note: See TracBrowser for help on using the repository browser.