1*02e95f1aSMarcin Radomski /* tinyplay.c
2*02e95f1aSMarcin Radomski **
3*02e95f1aSMarcin Radomski ** Copyright 2011, The Android Open Source Project
4*02e95f1aSMarcin Radomski **
5*02e95f1aSMarcin Radomski ** Redistribution and use in source and binary forms, with or without
6*02e95f1aSMarcin Radomski ** modification, are permitted provided that the following conditions are met:
7*02e95f1aSMarcin Radomski ** * Redistributions of source code must retain the above copyright
8*02e95f1aSMarcin Radomski ** notice, this list of conditions and the following disclaimer.
9*02e95f1aSMarcin Radomski ** * Redistributions in binary form must reproduce the above copyright
10*02e95f1aSMarcin Radomski ** notice, this list of conditions and the following disclaimer in the
11*02e95f1aSMarcin Radomski ** documentation and/or other materials provided with the distribution.
12*02e95f1aSMarcin Radomski ** * Neither the name of The Android Open Source Project nor the names of
13*02e95f1aSMarcin Radomski ** its contributors may be used to endorse or promote products derived
14*02e95f1aSMarcin Radomski ** from this software without specific prior written permission.
15*02e95f1aSMarcin Radomski **
16*02e95f1aSMarcin Radomski ** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
17*02e95f1aSMarcin Radomski ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18*02e95f1aSMarcin Radomski ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19*02e95f1aSMarcin Radomski ** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
20*02e95f1aSMarcin Radomski ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21*02e95f1aSMarcin Radomski ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22*02e95f1aSMarcin Radomski ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23*02e95f1aSMarcin Radomski ** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24*02e95f1aSMarcin Radomski ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25*02e95f1aSMarcin Radomski ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
26*02e95f1aSMarcin Radomski ** DAMAGE.
27*02e95f1aSMarcin Radomski */
28*02e95f1aSMarcin Radomski
29*02e95f1aSMarcin Radomski #include <tinyalsa/asoundlib.h>
30*02e95f1aSMarcin Radomski #include <signal.h>
31*02e95f1aSMarcin Radomski #include <stdbool.h>
32*02e95f1aSMarcin Radomski #include <stdint.h>
33*02e95f1aSMarcin Radomski #include <stdio.h>
34*02e95f1aSMarcin Radomski #include <stdlib.h>
35*02e95f1aSMarcin Radomski #include <string.h>
36*02e95f1aSMarcin Radomski
37*02e95f1aSMarcin Radomski #define OPTPARSE_IMPLEMENTATION
38*02e95f1aSMarcin Radomski #include "optparse.h"
39*02e95f1aSMarcin Radomski
40*02e95f1aSMarcin Radomski struct cmd {
41*02e95f1aSMarcin Radomski const char *filename;
42*02e95f1aSMarcin Radomski const char *filetype;
43*02e95f1aSMarcin Radomski unsigned int card;
44*02e95f1aSMarcin Radomski unsigned int device;
45*02e95f1aSMarcin Radomski int flags;
46*02e95f1aSMarcin Radomski struct pcm_config config;
47*02e95f1aSMarcin Radomski unsigned int bits;
48*02e95f1aSMarcin Radomski bool is_float;
49*02e95f1aSMarcin Radomski };
50*02e95f1aSMarcin Radomski
cmd_init(struct cmd * cmd)51*02e95f1aSMarcin Radomski void cmd_init(struct cmd *cmd)
52*02e95f1aSMarcin Radomski {
53*02e95f1aSMarcin Radomski cmd->filename = NULL;
54*02e95f1aSMarcin Radomski cmd->filetype = NULL;
55*02e95f1aSMarcin Radomski cmd->card = 0;
56*02e95f1aSMarcin Radomski cmd->device = 0;
57*02e95f1aSMarcin Radomski cmd->flags = PCM_OUT;
58*02e95f1aSMarcin Radomski cmd->config.period_size = 1024;
59*02e95f1aSMarcin Radomski cmd->config.period_count = 2;
60*02e95f1aSMarcin Radomski cmd->config.channels = 2;
61*02e95f1aSMarcin Radomski cmd->config.rate = 48000;
62*02e95f1aSMarcin Radomski cmd->config.format = PCM_FORMAT_S16_LE;
63*02e95f1aSMarcin Radomski cmd->config.silence_threshold = cmd->config.period_size * cmd->config.period_count;
64*02e95f1aSMarcin Radomski cmd->config.silence_size = 0;
65*02e95f1aSMarcin Radomski cmd->config.stop_threshold = cmd->config.period_size * cmd->config.period_count;
66*02e95f1aSMarcin Radomski cmd->config.start_threshold = cmd->config.period_size;
67*02e95f1aSMarcin Radomski cmd->bits = 16;
68*02e95f1aSMarcin Radomski cmd->is_float = false;
69*02e95f1aSMarcin Radomski }
70*02e95f1aSMarcin Radomski
71*02e95f1aSMarcin Radomski #define ID_RIFF 0x46464952
72*02e95f1aSMarcin Radomski #define ID_WAVE 0x45564157
73*02e95f1aSMarcin Radomski #define ID_FMT 0x20746d66
74*02e95f1aSMarcin Radomski #define ID_DATA 0x61746164
75*02e95f1aSMarcin Radomski
76*02e95f1aSMarcin Radomski #define WAVE_FORMAT_PCM 0x0001
77*02e95f1aSMarcin Radomski #define WAVE_FORMAT_IEEE_FLOAT 0x0003
78*02e95f1aSMarcin Radomski
79*02e95f1aSMarcin Radomski struct riff_wave_header {
80*02e95f1aSMarcin Radomski uint32_t riff_id;
81*02e95f1aSMarcin Radomski uint32_t riff_sz;
82*02e95f1aSMarcin Radomski uint32_t wave_id;
83*02e95f1aSMarcin Radomski };
84*02e95f1aSMarcin Radomski
85*02e95f1aSMarcin Radomski struct chunk_header {
86*02e95f1aSMarcin Radomski uint32_t id;
87*02e95f1aSMarcin Radomski uint32_t sz;
88*02e95f1aSMarcin Radomski };
89*02e95f1aSMarcin Radomski
90*02e95f1aSMarcin Radomski struct chunk_fmt {
91*02e95f1aSMarcin Radomski uint16_t audio_format;
92*02e95f1aSMarcin Radomski uint16_t num_channels;
93*02e95f1aSMarcin Radomski uint32_t sample_rate;
94*02e95f1aSMarcin Radomski uint32_t byte_rate;
95*02e95f1aSMarcin Radomski uint16_t block_align;
96*02e95f1aSMarcin Radomski uint16_t bits_per_sample;
97*02e95f1aSMarcin Radomski };
98*02e95f1aSMarcin Radomski
99*02e95f1aSMarcin Radomski struct ctx {
100*02e95f1aSMarcin Radomski struct pcm *pcm;
101*02e95f1aSMarcin Radomski
102*02e95f1aSMarcin Radomski struct riff_wave_header wave_header;
103*02e95f1aSMarcin Radomski struct chunk_header chunk_header;
104*02e95f1aSMarcin Radomski struct chunk_fmt chunk_fmt;
105*02e95f1aSMarcin Radomski
106*02e95f1aSMarcin Radomski FILE *file;
107*02e95f1aSMarcin Radomski size_t file_size;
108*02e95f1aSMarcin Radomski };
109*02e95f1aSMarcin Radomski
is_wave_file(const char * filetype)110*02e95f1aSMarcin Radomski static bool is_wave_file(const char *filetype)
111*02e95f1aSMarcin Radomski {
112*02e95f1aSMarcin Radomski return filetype != NULL && strcmp(filetype, "wav") == 0;
113*02e95f1aSMarcin Radomski }
114*02e95f1aSMarcin Radomski
signed_pcm_bits_to_format(int bits)115*02e95f1aSMarcin Radomski static enum pcm_format signed_pcm_bits_to_format(int bits)
116*02e95f1aSMarcin Radomski {
117*02e95f1aSMarcin Radomski switch (bits) {
118*02e95f1aSMarcin Radomski case 8:
119*02e95f1aSMarcin Radomski return PCM_FORMAT_S8;
120*02e95f1aSMarcin Radomski case 16:
121*02e95f1aSMarcin Radomski return PCM_FORMAT_S16_LE;
122*02e95f1aSMarcin Radomski case 24:
123*02e95f1aSMarcin Radomski return PCM_FORMAT_S24_3LE;
124*02e95f1aSMarcin Radomski case 32:
125*02e95f1aSMarcin Radomski return PCM_FORMAT_S32_LE;
126*02e95f1aSMarcin Radomski default:
127*02e95f1aSMarcin Radomski return -1;
128*02e95f1aSMarcin Radomski }
129*02e95f1aSMarcin Radomski }
130*02e95f1aSMarcin Radomski
parse_wave_file(struct ctx * ctx,const char * filename)131*02e95f1aSMarcin Radomski static int parse_wave_file(struct ctx *ctx, const char *filename)
132*02e95f1aSMarcin Radomski {
133*02e95f1aSMarcin Radomski if (fread(&ctx->wave_header, sizeof(ctx->wave_header), 1, ctx->file) != 1){
134*02e95f1aSMarcin Radomski fprintf(stderr, "error: '%s' does not contain a riff/wave header\n", filename);
135*02e95f1aSMarcin Radomski return -1;
136*02e95f1aSMarcin Radomski }
137*02e95f1aSMarcin Radomski
138*02e95f1aSMarcin Radomski if (ctx->wave_header.riff_id != ID_RIFF || ctx->wave_header.wave_id != ID_WAVE) {
139*02e95f1aSMarcin Radomski fprintf(stderr, "error: '%s' is not a riff/wave file\n", filename);
140*02e95f1aSMarcin Radomski return -1;
141*02e95f1aSMarcin Radomski }
142*02e95f1aSMarcin Radomski
143*02e95f1aSMarcin Radomski bool more_chunks = true;
144*02e95f1aSMarcin Radomski do {
145*02e95f1aSMarcin Radomski if (fread(&ctx->chunk_header, sizeof(ctx->chunk_header), 1, ctx->file) != 1) {
146*02e95f1aSMarcin Radomski fprintf(stderr, "error: '%s' does not contain a data chunk\n", filename);
147*02e95f1aSMarcin Radomski return -1;
148*02e95f1aSMarcin Radomski }
149*02e95f1aSMarcin Radomski switch (ctx->chunk_header.id) {
150*02e95f1aSMarcin Radomski case ID_FMT:
151*02e95f1aSMarcin Radomski if (fread(&ctx->chunk_fmt, sizeof(ctx->chunk_fmt), 1, ctx->file) != 1) {
152*02e95f1aSMarcin Radomski fprintf(stderr, "error: '%s' has incomplete format chunk\n", filename);
153*02e95f1aSMarcin Radomski return -1;
154*02e95f1aSMarcin Radomski }
155*02e95f1aSMarcin Radomski /* If the format header is larger, skip the rest */
156*02e95f1aSMarcin Radomski if (ctx->chunk_header.sz > sizeof(ctx->chunk_fmt)) {
157*02e95f1aSMarcin Radomski fseek(ctx->file, ctx->chunk_header.sz - sizeof(ctx->chunk_fmt), SEEK_CUR);
158*02e95f1aSMarcin Radomski }
159*02e95f1aSMarcin Radomski break;
160*02e95f1aSMarcin Radomski case ID_DATA:
161*02e95f1aSMarcin Radomski /* Stop looking for chunks */
162*02e95f1aSMarcin Radomski more_chunks = false;
163*02e95f1aSMarcin Radomski break;
164*02e95f1aSMarcin Radomski default:
165*02e95f1aSMarcin Radomski /* Unknown chunk, skip bytes */
166*02e95f1aSMarcin Radomski fseek(ctx->file, ctx->chunk_header.sz, SEEK_CUR);
167*02e95f1aSMarcin Radomski }
168*02e95f1aSMarcin Radomski } while (more_chunks);
169*02e95f1aSMarcin Radomski
170*02e95f1aSMarcin Radomski return 0;
171*02e95f1aSMarcin Radomski }
172*02e95f1aSMarcin Radomski
ctx_init(struct ctx * ctx,struct cmd * cmd)173*02e95f1aSMarcin Radomski static int ctx_init(struct ctx* ctx, struct cmd *cmd)
174*02e95f1aSMarcin Radomski {
175*02e95f1aSMarcin Radomski unsigned int bits = cmd->bits;
176*02e95f1aSMarcin Radomski struct pcm_config *config = &cmd->config;
177*02e95f1aSMarcin Radomski bool is_float = cmd->is_float;
178*02e95f1aSMarcin Radomski
179*02e95f1aSMarcin Radomski if (cmd->filename == NULL) {
180*02e95f1aSMarcin Radomski fprintf(stderr, "filename not specified\n");
181*02e95f1aSMarcin Radomski return -1;
182*02e95f1aSMarcin Radomski }
183*02e95f1aSMarcin Radomski if (strcmp(cmd->filename, "-") == 0) {
184*02e95f1aSMarcin Radomski ctx->file = stdin;
185*02e95f1aSMarcin Radomski } else {
186*02e95f1aSMarcin Radomski ctx->file = fopen(cmd->filename, "rb");
187*02e95f1aSMarcin Radomski fseek(ctx->file, 0L, SEEK_END);
188*02e95f1aSMarcin Radomski ctx->file_size = ftell(ctx->file);
189*02e95f1aSMarcin Radomski fseek(ctx->file, 0L, SEEK_SET);
190*02e95f1aSMarcin Radomski }
191*02e95f1aSMarcin Radomski
192*02e95f1aSMarcin Radomski if (ctx->file == NULL) {
193*02e95f1aSMarcin Radomski fprintf(stderr, "failed to open '%s'\n", cmd->filename);
194*02e95f1aSMarcin Radomski return -1;
195*02e95f1aSMarcin Radomski }
196*02e95f1aSMarcin Radomski
197*02e95f1aSMarcin Radomski if (is_wave_file(cmd->filetype)) {
198*02e95f1aSMarcin Radomski if (parse_wave_file(ctx, cmd->filename) != 0) {
199*02e95f1aSMarcin Radomski fclose(ctx->file);
200*02e95f1aSMarcin Radomski return -1;
201*02e95f1aSMarcin Radomski }
202*02e95f1aSMarcin Radomski config->channels = ctx->chunk_fmt.num_channels;
203*02e95f1aSMarcin Radomski config->rate = ctx->chunk_fmt.sample_rate;
204*02e95f1aSMarcin Radomski bits = ctx->chunk_fmt.bits_per_sample;
205*02e95f1aSMarcin Radomski is_float = ctx->chunk_fmt.audio_format == WAVE_FORMAT_IEEE_FLOAT;
206*02e95f1aSMarcin Radomski ctx->file_size = (size_t) ctx->chunk_header.sz;
207*02e95f1aSMarcin Radomski }
208*02e95f1aSMarcin Radomski
209*02e95f1aSMarcin Radomski if (is_float) {
210*02e95f1aSMarcin Radomski config->format = PCM_FORMAT_FLOAT_LE;
211*02e95f1aSMarcin Radomski } else {
212*02e95f1aSMarcin Radomski config->format = signed_pcm_bits_to_format(bits);
213*02e95f1aSMarcin Radomski if (config->format == -1) {
214*02e95f1aSMarcin Radomski fprintf(stderr, "bit count '%u' not supported\n", bits);
215*02e95f1aSMarcin Radomski fclose(ctx->file);
216*02e95f1aSMarcin Radomski return -1;
217*02e95f1aSMarcin Radomski }
218*02e95f1aSMarcin Radomski }
219*02e95f1aSMarcin Radomski
220*02e95f1aSMarcin Radomski ctx->pcm = pcm_open(cmd->card,
221*02e95f1aSMarcin Radomski cmd->device,
222*02e95f1aSMarcin Radomski cmd->flags,
223*02e95f1aSMarcin Radomski config);
224*02e95f1aSMarcin Radomski if (!pcm_is_ready(ctx->pcm)) {
225*02e95f1aSMarcin Radomski fprintf(stderr, "failed to open for pcm %u,%u. %s\n",
226*02e95f1aSMarcin Radomski cmd->card, cmd->device,
227*02e95f1aSMarcin Radomski pcm_get_error(ctx->pcm));
228*02e95f1aSMarcin Radomski fclose(ctx->file);
229*02e95f1aSMarcin Radomski pcm_close(ctx->pcm);
230*02e95f1aSMarcin Radomski return -1;
231*02e95f1aSMarcin Radomski }
232*02e95f1aSMarcin Radomski
233*02e95f1aSMarcin Radomski return 0;
234*02e95f1aSMarcin Radomski }
235*02e95f1aSMarcin Radomski
ctx_free(struct ctx * ctx)236*02e95f1aSMarcin Radomski void ctx_free(struct ctx *ctx)
237*02e95f1aSMarcin Radomski {
238*02e95f1aSMarcin Radomski if (ctx == NULL) {
239*02e95f1aSMarcin Radomski return;
240*02e95f1aSMarcin Radomski }
241*02e95f1aSMarcin Radomski if (ctx->pcm != NULL) {
242*02e95f1aSMarcin Radomski pcm_close(ctx->pcm);
243*02e95f1aSMarcin Radomski }
244*02e95f1aSMarcin Radomski if (ctx->file != NULL) {
245*02e95f1aSMarcin Radomski fclose(ctx->file);
246*02e95f1aSMarcin Radomski }
247*02e95f1aSMarcin Radomski }
248*02e95f1aSMarcin Radomski
249*02e95f1aSMarcin Radomski static int close = 0;
250*02e95f1aSMarcin Radomski
251*02e95f1aSMarcin Radomski int play_sample(struct ctx *ctx);
252*02e95f1aSMarcin Radomski
stream_close(int sig)253*02e95f1aSMarcin Radomski void stream_close(int sig)
254*02e95f1aSMarcin Radomski {
255*02e95f1aSMarcin Radomski /* allow the stream to be closed gracefully */
256*02e95f1aSMarcin Radomski signal(sig, SIG_IGN);
257*02e95f1aSMarcin Radomski close = 1;
258*02e95f1aSMarcin Radomski }
259*02e95f1aSMarcin Radomski
print_usage(const char * argv0)260*02e95f1aSMarcin Radomski void print_usage(const char *argv0)
261*02e95f1aSMarcin Radomski {
262*02e95f1aSMarcin Radomski fprintf(stderr, "usage: %s file.wav [options]\n", argv0);
263*02e95f1aSMarcin Radomski fprintf(stderr, "options:\n");
264*02e95f1aSMarcin Radomski fprintf(stderr, "-D | --card <card number> The card to receive the audio\n");
265*02e95f1aSMarcin Radomski fprintf(stderr, "-d | --device <device number> The device to receive the audio\n");
266*02e95f1aSMarcin Radomski fprintf(stderr, "-p | --period-size <size> The size of the PCM's period\n");
267*02e95f1aSMarcin Radomski fprintf(stderr, "-n | --period-count <count> The number of PCM periods\n");
268*02e95f1aSMarcin Radomski fprintf(stderr, "-i | --file-type <file-type> The type of file to read (raw or wav)\n");
269*02e95f1aSMarcin Radomski fprintf(stderr, "-c | --channels <count> The amount of channels per frame\n");
270*02e95f1aSMarcin Radomski fprintf(stderr, "-r | --rate <rate> The amount of frames per second\n");
271*02e95f1aSMarcin Radomski fprintf(stderr, "-b | --bits <bit-count> The number of bits in one sample\n");
272*02e95f1aSMarcin Radomski fprintf(stderr, "-f | --float The frames are in floating-point PCM\n");
273*02e95f1aSMarcin Radomski fprintf(stderr, "-M | --mmap Use memory mapped IO to play audio\n");
274*02e95f1aSMarcin Radomski fprintf(stderr, "-s | --silence-threshold <size> The minimum number of frames to silence the PCM\n");
275*02e95f1aSMarcin Radomski fprintf(stderr, "-t | --start-threshold <size> The minimum number of frames required to start the PCM\n");
276*02e95f1aSMarcin Radomski fprintf(stderr, "-T | --stop-threshold <size> The minimum number of frames required to stop the PCM\n");
277*02e95f1aSMarcin Radomski }
278*02e95f1aSMarcin Radomski
main(int argc,char ** argv)279*02e95f1aSMarcin Radomski int main(int argc, char **argv)
280*02e95f1aSMarcin Radomski {
281*02e95f1aSMarcin Radomski int c;
282*02e95f1aSMarcin Radomski struct cmd cmd;
283*02e95f1aSMarcin Radomski struct ctx ctx;
284*02e95f1aSMarcin Radomski struct optparse opts;
285*02e95f1aSMarcin Radomski struct optparse_long long_options[] = {
286*02e95f1aSMarcin Radomski { "card", 'D', OPTPARSE_REQUIRED },
287*02e95f1aSMarcin Radomski { "device", 'd', OPTPARSE_REQUIRED },
288*02e95f1aSMarcin Radomski { "period-size", 'p', OPTPARSE_REQUIRED },
289*02e95f1aSMarcin Radomski { "period-count", 'n', OPTPARSE_REQUIRED },
290*02e95f1aSMarcin Radomski { "file-type", 'i', OPTPARSE_REQUIRED },
291*02e95f1aSMarcin Radomski { "channels", 'c', OPTPARSE_REQUIRED },
292*02e95f1aSMarcin Radomski { "rate", 'r', OPTPARSE_REQUIRED },
293*02e95f1aSMarcin Radomski { "bits", 'b', OPTPARSE_REQUIRED },
294*02e95f1aSMarcin Radomski { "float", 'f', OPTPARSE_NONE },
295*02e95f1aSMarcin Radomski { "mmap", 'M', OPTPARSE_NONE },
296*02e95f1aSMarcin Radomski { "help", 'h', OPTPARSE_NONE },
297*02e95f1aSMarcin Radomski { "silence-threshold", 's', OPTPARSE_REQUIRED },
298*02e95f1aSMarcin Radomski { "start-threshold", 't', OPTPARSE_REQUIRED },
299*02e95f1aSMarcin Radomski { "stop-threshold", 'T', OPTPARSE_REQUIRED },
300*02e95f1aSMarcin Radomski { 0, 0, 0 }
301*02e95f1aSMarcin Radomski };
302*02e95f1aSMarcin Radomski
303*02e95f1aSMarcin Radomski if (argc < 2) {
304*02e95f1aSMarcin Radomski print_usage(argv[0]);
305*02e95f1aSMarcin Radomski return EXIT_FAILURE;
306*02e95f1aSMarcin Radomski }
307*02e95f1aSMarcin Radomski
308*02e95f1aSMarcin Radomski cmd_init(&cmd);
309*02e95f1aSMarcin Radomski optparse_init(&opts, argv);
310*02e95f1aSMarcin Radomski unsigned silence_threshold = 0;
311*02e95f1aSMarcin Radomski unsigned start_threshold = 0;
312*02e95f1aSMarcin Radomski unsigned stop_threshold = 0;
313*02e95f1aSMarcin Radomski while ((c = optparse_long(&opts, long_options, NULL)) != -1) {
314*02e95f1aSMarcin Radomski switch (c) {
315*02e95f1aSMarcin Radomski case 'D':
316*02e95f1aSMarcin Radomski if (sscanf(opts.optarg, "%u", &cmd.card) != 1) {
317*02e95f1aSMarcin Radomski fprintf(stderr, "failed parsing card number '%s'\n", argv[1]);
318*02e95f1aSMarcin Radomski return EXIT_FAILURE;
319*02e95f1aSMarcin Radomski }
320*02e95f1aSMarcin Radomski break;
321*02e95f1aSMarcin Radomski case 'd':
322*02e95f1aSMarcin Radomski if (sscanf(opts.optarg, "%u", &cmd.device) != 1) {
323*02e95f1aSMarcin Radomski fprintf(stderr, "failed parsing device number '%s'\n", argv[1]);
324*02e95f1aSMarcin Radomski return EXIT_FAILURE;
325*02e95f1aSMarcin Radomski }
326*02e95f1aSMarcin Radomski break;
327*02e95f1aSMarcin Radomski case 'p':
328*02e95f1aSMarcin Radomski if (sscanf(opts.optarg, "%u", &cmd.config.period_size) != 1) {
329*02e95f1aSMarcin Radomski fprintf(stderr, "failed parsing period size '%s'\n", argv[1]);
330*02e95f1aSMarcin Radomski return EXIT_FAILURE;
331*02e95f1aSMarcin Radomski }
332*02e95f1aSMarcin Radomski break;
333*02e95f1aSMarcin Radomski case 'n':
334*02e95f1aSMarcin Radomski if (sscanf(opts.optarg, "%u", &cmd.config.period_count) != 1) {
335*02e95f1aSMarcin Radomski fprintf(stderr, "failed parsing period count '%s'\n", argv[1]);
336*02e95f1aSMarcin Radomski return EXIT_FAILURE;
337*02e95f1aSMarcin Radomski }
338*02e95f1aSMarcin Radomski break;
339*02e95f1aSMarcin Radomski case 'c':
340*02e95f1aSMarcin Radomski if (sscanf(opts.optarg, "%u", &cmd.config.channels) != 1) {
341*02e95f1aSMarcin Radomski fprintf(stderr, "failed parsing channel count '%s'\n", argv[1]);
342*02e95f1aSMarcin Radomski return EXIT_FAILURE;
343*02e95f1aSMarcin Radomski }
344*02e95f1aSMarcin Radomski break;
345*02e95f1aSMarcin Radomski case 'r':
346*02e95f1aSMarcin Radomski if (sscanf(opts.optarg, "%u", &cmd.config.rate) != 1) {
347*02e95f1aSMarcin Radomski fprintf(stderr, "failed parsing rate '%s'\n", argv[1]);
348*02e95f1aSMarcin Radomski return EXIT_FAILURE;
349*02e95f1aSMarcin Radomski }
350*02e95f1aSMarcin Radomski break;
351*02e95f1aSMarcin Radomski case 'i':
352*02e95f1aSMarcin Radomski cmd.filetype = opts.optarg;
353*02e95f1aSMarcin Radomski break;
354*02e95f1aSMarcin Radomski case 'b':
355*02e95f1aSMarcin Radomski if (sscanf(opts.optarg, "%u", &cmd.bits) != 1) {
356*02e95f1aSMarcin Radomski fprintf(stderr, "failed parsing bits per one sample '%s'\n", argv[1]);
357*02e95f1aSMarcin Radomski return EXIT_FAILURE;
358*02e95f1aSMarcin Radomski }
359*02e95f1aSMarcin Radomski break;
360*02e95f1aSMarcin Radomski case 'f':
361*02e95f1aSMarcin Radomski cmd.is_float = true;
362*02e95f1aSMarcin Radomski break;
363*02e95f1aSMarcin Radomski case 'M':
364*02e95f1aSMarcin Radomski cmd.flags |= PCM_MMAP;
365*02e95f1aSMarcin Radomski break;
366*02e95f1aSMarcin Radomski case 's':
367*02e95f1aSMarcin Radomski if (sscanf(opts.optarg, "%u", &silence_threshold) != 1) {
368*02e95f1aSMarcin Radomski fprintf(stderr, "failed parsing silence threshold '%s'\n", argv[1]);
369*02e95f1aSMarcin Radomski return EXIT_FAILURE;
370*02e95f1aSMarcin Radomski }
371*02e95f1aSMarcin Radomski break;
372*02e95f1aSMarcin Radomski case 't':
373*02e95f1aSMarcin Radomski if (sscanf(opts.optarg, "%u", &start_threshold) != 1) {
374*02e95f1aSMarcin Radomski fprintf(stderr, "failed parsing start threshold '%s'\n", argv[1]);
375*02e95f1aSMarcin Radomski return EXIT_FAILURE;
376*02e95f1aSMarcin Radomski }
377*02e95f1aSMarcin Radomski break;
378*02e95f1aSMarcin Radomski case 'T':
379*02e95f1aSMarcin Radomski if (sscanf(opts.optarg, "%u", &stop_threshold) != 1) {
380*02e95f1aSMarcin Radomski fprintf(stderr, "failed parsing stop threshold '%s'\n", argv[1]);
381*02e95f1aSMarcin Radomski return EXIT_FAILURE;
382*02e95f1aSMarcin Radomski }
383*02e95f1aSMarcin Radomski break;
384*02e95f1aSMarcin Radomski case 'h':
385*02e95f1aSMarcin Radomski print_usage(argv[0]);
386*02e95f1aSMarcin Radomski return EXIT_SUCCESS;
387*02e95f1aSMarcin Radomski case '?':
388*02e95f1aSMarcin Radomski fprintf(stderr, "%s\n", opts.errmsg);
389*02e95f1aSMarcin Radomski return EXIT_FAILURE;
390*02e95f1aSMarcin Radomski }
391*02e95f1aSMarcin Radomski }
392*02e95f1aSMarcin Radomski cmd.filename = optparse_arg(&opts);
393*02e95f1aSMarcin Radomski
394*02e95f1aSMarcin Radomski if (cmd.filename != NULL && cmd.filetype == NULL &&
395*02e95f1aSMarcin Radomski (cmd.filetype = strrchr(cmd.filename, '.')) != NULL) {
396*02e95f1aSMarcin Radomski cmd.filetype++;
397*02e95f1aSMarcin Radomski }
398*02e95f1aSMarcin Radomski
399*02e95f1aSMarcin Radomski cmd.config.silence_threshold = cmd.config.period_size * cmd.config.period_count;
400*02e95f1aSMarcin Radomski cmd.config.stop_threshold = cmd.config.period_size * cmd.config.period_count;
401*02e95f1aSMarcin Radomski cmd.config.start_threshold = cmd.config.period_size;
402*02e95f1aSMarcin Radomski
403*02e95f1aSMarcin Radomski if (silence_threshold != 0) {
404*02e95f1aSMarcin Radomski cmd.config.silence_threshold = silence_threshold;
405*02e95f1aSMarcin Radomski }
406*02e95f1aSMarcin Radomski if (start_threshold != 0) {
407*02e95f1aSMarcin Radomski cmd.config.start_threshold = start_threshold;
408*02e95f1aSMarcin Radomski }
409*02e95f1aSMarcin Radomski if (stop_threshold != 0) {
410*02e95f1aSMarcin Radomski cmd.config.stop_threshold = stop_threshold;
411*02e95f1aSMarcin Radomski }
412*02e95f1aSMarcin Radomski
413*02e95f1aSMarcin Radomski if (ctx_init(&ctx, &cmd) < 0) {
414*02e95f1aSMarcin Radomski return EXIT_FAILURE;
415*02e95f1aSMarcin Radomski }
416*02e95f1aSMarcin Radomski
417*02e95f1aSMarcin Radomski printf("playing '%s': %u ch, %u hz, %u-bit ", cmd.filename, cmd.config.channels,
418*02e95f1aSMarcin Radomski cmd.config.rate, pcm_format_to_bits(cmd.config.format));
419*02e95f1aSMarcin Radomski if (cmd.config.format == PCM_FORMAT_FLOAT_LE) {
420*02e95f1aSMarcin Radomski printf("floating-point PCM\n");
421*02e95f1aSMarcin Radomski } else {
422*02e95f1aSMarcin Radomski printf("signed PCM\n");
423*02e95f1aSMarcin Radomski }
424*02e95f1aSMarcin Radomski
425*02e95f1aSMarcin Radomski if (play_sample(&ctx) < 0) {
426*02e95f1aSMarcin Radomski ctx_free(&ctx);
427*02e95f1aSMarcin Radomski return EXIT_FAILURE;
428*02e95f1aSMarcin Radomski }
429*02e95f1aSMarcin Radomski
430*02e95f1aSMarcin Radomski ctx_free(&ctx);
431*02e95f1aSMarcin Radomski return EXIT_SUCCESS;
432*02e95f1aSMarcin Radomski }
433*02e95f1aSMarcin Radomski
check_param(struct pcm_params * params,unsigned int param,unsigned int value,char * param_name,char * param_unit)434*02e95f1aSMarcin Radomski int check_param(struct pcm_params *params, unsigned int param, unsigned int value,
435*02e95f1aSMarcin Radomski char *param_name, char *param_unit)
436*02e95f1aSMarcin Radomski {
437*02e95f1aSMarcin Radomski unsigned int min;
438*02e95f1aSMarcin Radomski unsigned int max;
439*02e95f1aSMarcin Radomski bool is_within_bounds = true;
440*02e95f1aSMarcin Radomski
441*02e95f1aSMarcin Radomski min = pcm_params_get_min(params, param);
442*02e95f1aSMarcin Radomski if (value < min) {
443*02e95f1aSMarcin Radomski fprintf(stderr, "%s is %u%s, device only supports >= %u%s\n", param_name, value,
444*02e95f1aSMarcin Radomski param_unit, min, param_unit);
445*02e95f1aSMarcin Radomski is_within_bounds = false;
446*02e95f1aSMarcin Radomski }
447*02e95f1aSMarcin Radomski
448*02e95f1aSMarcin Radomski max = pcm_params_get_max(params, param);
449*02e95f1aSMarcin Radomski if (value > max) {
450*02e95f1aSMarcin Radomski fprintf(stderr, "%s is %u%s, device only supports <= %u%s\n", param_name, value,
451*02e95f1aSMarcin Radomski param_unit, max, param_unit);
452*02e95f1aSMarcin Radomski is_within_bounds = false;
453*02e95f1aSMarcin Radomski }
454*02e95f1aSMarcin Radomski
455*02e95f1aSMarcin Radomski return is_within_bounds;
456*02e95f1aSMarcin Radomski }
457*02e95f1aSMarcin Radomski
sample_is_playable(const struct cmd * cmd)458*02e95f1aSMarcin Radomski int sample_is_playable(const struct cmd *cmd)
459*02e95f1aSMarcin Radomski {
460*02e95f1aSMarcin Radomski struct pcm_params *params;
461*02e95f1aSMarcin Radomski int can_play;
462*02e95f1aSMarcin Radomski
463*02e95f1aSMarcin Radomski params = pcm_params_get(cmd->card, cmd->device, PCM_OUT);
464*02e95f1aSMarcin Radomski if (params == NULL) {
465*02e95f1aSMarcin Radomski fprintf(stderr, "unable to open PCM %u,%u\n", cmd->card, cmd->device);
466*02e95f1aSMarcin Radomski return 0;
467*02e95f1aSMarcin Radomski }
468*02e95f1aSMarcin Radomski
469*02e95f1aSMarcin Radomski can_play = check_param(params, PCM_PARAM_RATE, cmd->config.rate, "sample rate", "hz");
470*02e95f1aSMarcin Radomski can_play &= check_param(params, PCM_PARAM_CHANNELS, cmd->config.channels, "sample",
471*02e95f1aSMarcin Radomski " channels");
472*02e95f1aSMarcin Radomski can_play &= check_param(params, PCM_PARAM_SAMPLE_BITS, cmd->bits, "bits", " bits");
473*02e95f1aSMarcin Radomski can_play &= check_param(params, PCM_PARAM_PERIOD_SIZE, cmd->config.period_size, "period size",
474*02e95f1aSMarcin Radomski " frames");
475*02e95f1aSMarcin Radomski can_play &= check_param(params, PCM_PARAM_PERIODS, cmd->config.period_count, "period count",
476*02e95f1aSMarcin Radomski "");
477*02e95f1aSMarcin Radomski
478*02e95f1aSMarcin Radomski pcm_params_free(params);
479*02e95f1aSMarcin Radomski
480*02e95f1aSMarcin Radomski return can_play;
481*02e95f1aSMarcin Radomski }
482*02e95f1aSMarcin Radomski
play_sample(struct ctx * ctx)483*02e95f1aSMarcin Radomski int play_sample(struct ctx *ctx)
484*02e95f1aSMarcin Radomski {
485*02e95f1aSMarcin Radomski char *buffer;
486*02e95f1aSMarcin Radomski bool is_stdin_source = ctx->file == stdin;
487*02e95f1aSMarcin Radomski size_t buffer_size = 0;
488*02e95f1aSMarcin Radomski size_t num_read = 0;
489*02e95f1aSMarcin Radomski size_t remaining_data_size = is_stdin_source ? SIZE_MAX : ctx->file_size;
490*02e95f1aSMarcin Radomski size_t played_data_size = 0;
491*02e95f1aSMarcin Radomski size_t read_size = 0;
492*02e95f1aSMarcin Radomski const struct pcm_config *config = pcm_get_config(ctx->pcm);
493*02e95f1aSMarcin Radomski
494*02e95f1aSMarcin Radomski if (config == NULL) {
495*02e95f1aSMarcin Radomski fprintf(stderr, "unable to get pcm config\n");
496*02e95f1aSMarcin Radomski return -1;
497*02e95f1aSMarcin Radomski }
498*02e95f1aSMarcin Radomski
499*02e95f1aSMarcin Radomski buffer_size = pcm_frames_to_bytes(ctx->pcm, config->period_size);
500*02e95f1aSMarcin Radomski buffer = malloc(buffer_size);
501*02e95f1aSMarcin Radomski if (!buffer) {
502*02e95f1aSMarcin Radomski fprintf(stderr, "unable to allocate %zu bytes\n", buffer_size);
503*02e95f1aSMarcin Radomski return -1;
504*02e95f1aSMarcin Radomski }
505*02e95f1aSMarcin Radomski
506*02e95f1aSMarcin Radomski /* catch ctrl-c to shutdown cleanly */
507*02e95f1aSMarcin Radomski signal(SIGINT, stream_close);
508*02e95f1aSMarcin Radomski
509*02e95f1aSMarcin Radomski do {
510*02e95f1aSMarcin Radomski read_size = remaining_data_size > buffer_size ? buffer_size : remaining_data_size;
511*02e95f1aSMarcin Radomski num_read = fread(buffer, 1, read_size, ctx->file);
512*02e95f1aSMarcin Radomski if (num_read > 0) {
513*02e95f1aSMarcin Radomski int written_frames = pcm_writei(ctx->pcm, buffer,
514*02e95f1aSMarcin Radomski pcm_bytes_to_frames(ctx->pcm, num_read));
515*02e95f1aSMarcin Radomski if (written_frames < 0) {
516*02e95f1aSMarcin Radomski fprintf(stderr, "error playing sample. %s\n", pcm_get_error(ctx->pcm));
517*02e95f1aSMarcin Radomski break;
518*02e95f1aSMarcin Radomski }
519*02e95f1aSMarcin Radomski
520*02e95f1aSMarcin Radomski if (!is_stdin_source) {
521*02e95f1aSMarcin Radomski remaining_data_size -= num_read;
522*02e95f1aSMarcin Radomski }
523*02e95f1aSMarcin Radomski played_data_size += pcm_frames_to_bytes(ctx->pcm, written_frames);
524*02e95f1aSMarcin Radomski }
525*02e95f1aSMarcin Radomski } while (!close && num_read > 0 && remaining_data_size > 0);
526*02e95f1aSMarcin Radomski
527*02e95f1aSMarcin Radomski printf("Played %zu bytes. ", played_data_size);
528*02e95f1aSMarcin Radomski if (is_stdin_source) {
529*02e95f1aSMarcin Radomski printf("\n");
530*02e95f1aSMarcin Radomski } else {
531*02e95f1aSMarcin Radomski printf("Remains %zu bytes.\n", remaining_data_size);
532*02e95f1aSMarcin Radomski }
533*02e95f1aSMarcin Radomski
534*02e95f1aSMarcin Radomski pcm_wait(ctx->pcm, -1);
535*02e95f1aSMarcin Radomski
536*02e95f1aSMarcin Radomski free(buffer);
537*02e95f1aSMarcin Radomski return 0;
538*02e95f1aSMarcin Radomski }
539*02e95f1aSMarcin Radomski
540