sylware / nyanmp (public) (License: AGPLv3) (since 2020-02-12) (hash sha1)
intended to become a collection of media players for gnu/linux based on ffmpeg, alsa, vulkan, x11, wayland, etc.
List of commits:
Subject Hash Author Date (UTC)
here we go d5a2b11f4d73ec5365a340163f451fa8a123d728 Sylvain BERTRAND 2020-02-12 18:50:57
Commit d5a2b11f4d73ec5365a340163f451fa8a123d728 - here we go
Author: Sylvain BERTRAND
Author date (UTC): 2020-02-12 18:50
Committer name: Sylvain BERTRAND
Committer date (UTC): 2020-02-12 18:50
Parent(s):
Signer:
Signing key:
Signing status: N
Tree: a396e09281908e5d4b114b6784da298b34d52da2
File Lines added Lines deleted
README 16 0
npaf.c 2188 0
npaf_config.h 36 0
File README added (mode: 100644) (index 0000000..c817b71)
1 nyan collection of wip (Work-In-Progress) media players
2
3 those are _not_ ffplay, which is built upon libSDL and experimental ffmpeg
4 features/apis (test bed for ffmpeg developers)
5
6 ====
7 npaf: Nyan Player Audio File, directly wired to alsa and ffmpeg. you can
8 configure the key bindings in the user configuration header file.
9 use case: no dynamic codec/filter reconfiguration
10 ====
11
12 BUILD:
13 compile and link with your locally configured ffmpeg libraries and libasound
14 from alsa-lib project.
15 for an audio only player, you could link it to an audio only ffmpeg set of
16 libraries
File npaf.c added (mode: 100644) (index 0000000..dcdf8d9)
1 #ifndef NYANPAF_C
2 #define NYANPAF_C
3 /*
4 * code protected with a GNU affero GPLv3 license
5 * copyright (C) 2020 Sylvain BERTRAND
6 */
7 /*
8 * ABBREVIATIONS
9 *
10 * abuf : Audio BUFfer
11 * async : ASYNChronous
12 * b(s) : Byte(S) (often)
13 * buf(s) : BUFfer(S)
14 * chan(s) : CHANnel(S)
15 * cmd(s) : CoMmanD(S)
16 * cp(s) : Code Point(s)
17 * conf : CONFiguration
18 * ctx : ConTeX
19 * dec : DECoder/DECoded
20 * dev : DEVice
21 * e : End (usually a pointer on the byte past the last valid byte)
22 * eof : End Of File
23 * esc : ESCape
24 * ep : EPoll
25 * evt(s) : EVenT(S)
26 * err : ERRor
27 * fd : File Descriptor
28 * filt : FILTer
29 * fmt : ForMaT
30 * inc : INCrement
31 * idx(s) : InDeX(S)
32 * l10n : LocalizatioN (10 chars between L and N)
33 * min(s) : MINute(S)
34 * msec(s) : MilliSECond(S)
35 * n : couNt
36 * nr : NumbeR
37 * out : OUTput
38 * pkt : PacKeT
39 * s : Start
40 * sec(s) : SECond(S)
41 * seq : SEQuence
42 * sig(s) : SIGnal(S)
43 * st : STream
44 * str : STRing
45 * sync : SYNChronous
46 * sz : SiZe
47 * ts : TimeStamp
48 * vol : VOLume
49 */
50 /*
51 * this is not a library, then we could not care less about memory management
52 * and/or similar cleanup: we have a virtual machine with a garbage collector,
53 * it is linux.
54 *
55 * we do presume we won't play more than 8 chans and ffmpeg chans layout will
56 * fit alsa chans map
57 *
58 * XXX: we don't know how the alsa silence machinery works, then we use brutal
59 * silence bufs
60 */
61 /*---------------------------------------------------------------------------*/
62 /* C/posix */
63 #include <stdbool.h>
64 #include <locale.h>
65 #include <stdarg.h>
66 #include <stdio.h>
67 #include <stdlib.h>
68 #include <string.h>
69 #include <errno.h>
70 #include <signal.h>
71 #include <pthread.h>
72 #include <unistd.h>
73 #include <termios.h>
74 #include <stdint.h>
75 #include <fcntl.h>
76 /* linux and compatible */
77 #include <sys/epoll.h>
78 #include <sys/signalfd.h>
79 #include <sys/timerfd.h>
80 /* ffmpeg */
81 #include <libavformat/avformat.h>
82 #include <libavcodec/avcodec.h>
83 #include <libavfilter/avfilter.h>
84 #include <libavfilter/buffersrc.h>
85 #include <libavfilter/buffersink.h>
86 #include <libavutil/opt.h>
87 /* alsa */
88 #include <alsa/asoundlib.h>
89 /* fix C */
90 #define u8 char
91 #define u32 uint32_t
92 #define u16 uint16_t
93 #define f32 float
94 #define loop for(;;)
95
96 #define ARRAY_N(x) (sizeof(x) / sizeof((x)[0]))
97
98 #define POUT(fmt, ...) fprintf(stdout, fmt, ##__VA_ARGS__)
99 #define PERR(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)
100 #define WARNING(fmt, ...) fprintf(stderr, "WARNING:" fmt, ##__VA_ARGS__)
101 #define FATAL(fmt, ...) ({PERR("FATAL:" fmt, ##__VA_ARGS__);stdin_flags_restore();stdin_tty_conf_restore();exit(1);})
102 #define EXIT(fmt, ...) ({POUT("EXITING:" fmt, ##__VA_ARGS__);stdin_flags_restore();stdin_tty_conf_restore();exit(0);})
103 #define STR_SZ 255 /* sz and idx fit in 1 byte */
104 /*---------------------------------------------------------------------------*/
105 static u8 *file_path;
106 static f32 initial_vol;
107 /*---------------------------------------------------------------------------*/
108 /* linux and compatible */
109 static int ep_fd;
110 static int sig_fd;
111 /*---------------------------------------------------------------------------*/
112 /* linux alsa */
113 static snd_pcm_t *pcm;
114 static u8 *pcm_name = "default";
115 static snd_output_t *pcm_pout;
116 static snd_output_t *pcm_perr;
117 #define PCM_POLLFDS_N_MAX 16 /* banzai */
118 static struct pollfd pcm_pollfds[PCM_POLLFDS_N_MAX];
119 static u8 pcm_pollfds_n;
120 static snd_pcm_hw_params_t *pcm_hw_params;
121 /*---------------------------------------------------------------------------*/
122 /* ffmpeg dec */
123 /*
124 * XXX: a ffmpeg frame is not 1 audio frame but a collection of frames.
125 * ffmpeg dec frame "nb_samples" is actually the number of audio frames
126 * av_read_frame is actually av_read_pkt, like av_seek*
127 * we personally did hate the resulting confusion.
128 * we purposely "fix" the namespace even if some others will hate it.
129 * hate for hate ? xD
130 */
131 #define AVFrames AVFrame
132 #define frames_n nb_samples
133 #define av_frames_alloc av_frame_alloc
134 #define av_read_pkt av_read_frame
135 #define av_seek_pkt av_seek_frame
136 #define avcodec_receive_frames avcodec_receive_frame
137 #define AVFrameFormat AVSampleFormat
138 #define av_frames_set_silence av_samples_set_silence
139 #define av_get_frame_fmt_name av_get_sample_fmt_name
140 #define av_get_frame_fmt_string av_get_sample_fmt_string
141 #define frame_rate sample_rate
142 #define av_frame_fmt_is_planar av_sample_fmt_is_planar
143 #define frame_fmt sample_fmt
144 static u8 *file_url;
145 static AVFormatContext *fmt_ctx;
146 static int st_idx;
147 static AVStream *st;
148 static AVCodec *dec;
149 static AVCodecContext *dec_ctx;
150 static AVPacket *pkt;
151 /*---------------------------------------------------------------------------*/
152 /* ffmpeg filter */
153 static AVFilterGraph *filter_graph;
154 static AVFilterContext *abufsrc_ctx;
155 static const AVFilter *abufsrc;
156 static AVFilterContext *vol_ctx;
157 static const AVFilter *vol_filt;
158 static u8 float_zero_l10n_str[sizeof("xxx.xx")];
159 static AVFilterContext *afmt_ctx;
160 static const AVFilter *afmt;
161 static AVFilterContext *abufsink_ctx;
162 static const AVFilter *abufsink;
163 /*---------------------------------------------------------------------------*/
164 /* running state */
165 struct dec_frames_t {
166 AVFrames *av;
167 bool have_some;
168 bool have_rejected_pkt;
169 int64_t most_recent_ts;
170 };
171 static struct dec_frames_t dec_frames;
172
173 struct filt_frames_t {
174 AVFrames *av;
175 bool no_more_dec_frames;
176 float vol;
177 bool muted;
178 snd_pcm_uframes_t pcm_written_uframes_n;
179 };
180 static struct filt_frames_t filt_frames;
181 /* we will inject silence frames while paused */
182 bool paused;
183 void *silence_bufs[AV_NUM_DATA_POINTERS];
184 /*---------------------------------------------------------------------------*/
185 /* tty */
186 static bool stdin_tty_conf_modified;
187 static struct termios stdin_tio_save;
188 static int stdin_flags_save;
189 static bool stdout_is_tty;
190 /*---------------------------------------------------------------------------*/
191 static void cmd_quit(void);
192 static void cmd_rewind(void);
193 static void cmd_fastforward(void);
194 static void cmd_rewind_big(void);
195 static void cmd_fastforward_big(void);
196 static void cmd_vol_up(void);
197 static void cmd_vol_down(void);
198 static void cmd_mute(void);
199 static void cmd_info(void);
200 static void cmd_pause(void);
201 /*--------------------------------------------------------------------------*/
202 #include "npaf_config.h"
203 /*----------------------------------------------------------------------------*/
204 /* input "state" machine (2 major states: "utf8" and "esc seq"*/
205 static int input_timer_fd;
206 static bool input_esc_seq_mode;
207 static u8 input_b; /* input byte */
208
209 static u8 utf8_cp[4];
210 static u8 utf8_cp_next_byte; /* idx in utf8_cp */
211 static u8 utf8_cp_sz;
212
213 static u8 esc_seq[STR_SZ];
214 static u8 esc_seq_next_byte; /* idx in esc_seq */
215 #define esc_seq_sz esc_seq_next_byte /* the idx of the next byte is its sz */
216 /*----------------------------------------------------------------------------*/
217 static u8 *apf(u8 *fmt, ...) /* asprintf... meh... */
218 {
219 u8 *r;
220 int sz;
221 va_list ap;
222
223 va_start(ap, fmt);
224 sz = vsnprintf(0, 0, fmt, ap);
225 va_end(ap);
226
227 r = malloc(sz + 1);
228
229 va_start(ap, fmt);
230 vsnprintf(r, sz + 1 , fmt, ap);
231 va_end(ap);
232 return r;
233 }
234
235 static void stdin_tty_conf_restore(void)
236 {
237 int r;
238 struct termios tio_chk;
239
240 if (!stdin_tty_conf_modified)
241 return;
242
243 r = tcsetattr(0, TCSANOW, &stdin_tio_save);
244 if (r == -1) {
245 WARNING("input:unable to restore the terminal line attributes\t");
246 return;
247 }
248
249 memset(&tio_chk, 0, sizeof(tio_chk));
250 r = tcgetattr(0, &tio_chk);
251 if (r == -1) {
252 WARNING("input:unable to get the current terminal line attributes for restoration checking\n");
253 return;
254 }
255
256 r = memcmp(&tio_chk, &stdin_tio_save, sizeof(tio_chk));
257 if (r != 0)
258 WARNING("input:only partial restoration of the terminal line attributes\n");
259 }
260
261 static void stdin_flags_restore(void)
262 {
263 int r;
264
265 r = fcntl(0, F_SETFL, stdin_flags_save);
266 if (r == -1)
267 WARNING("input:unable to restore the file flags of the standard input\n");
268 }
269
270 static u8 *duration_estimate_to_str(enum AVDurationEstimationMethod m)
271 {
272 switch (m) {
273 case AVFMT_DURATION_FROM_PTS:
274 return "from PTS(Presentation TimeStamp)";
275 case AVFMT_DURATION_FROM_STREAM:
276 return "from stream";
277 case AVFMT_DURATION_FROM_BITRATE:
278 return "from bitrate";
279 default:
280 return "unkwown";
281 }
282 }
283 /* meh... */
284 static u8 *ts_to_str(int64_t ts, AVRational time_base, int64_t *remaining)
285 {
286 static u8 str[sizeof("~S00:00:00.000 remains S9223372036854775807 time base units")];
287 bool is_neg;
288 int64_t hours_n;
289 int64_t mins_n;
290 int64_t secs_n;
291 int64_t msecs_n;
292 int64_t one_hour; /* in ffmpeg time_base units */
293 int64_t one_min; /* in ffmpeg time_base units */
294 int64_t one_sec; /* in ffmpeg time_base units */
295 int64_t one_msec; /* in ffmpeg time_base units */
296
297 if (ts < 0) {
298 ts = -ts;
299 is_neg = true;
300 } else
301 is_neg = false;
302
303 one_hour = INT64_C(3600) * (int64_t)time_base.den
304 / (int64_t)time_base.num;
305 one_min = INT64_C(60) * (int64_t)time_base.den
306 / (int64_t)time_base.num;
307 one_sec = (int64_t)time_base.den / (int64_t)time_base.num;
308 one_msec = one_sec / INT64_C(1000);
309
310 hours_n = ts / one_hour;
311
312 *remaining = ts % one_hour;
313 mins_n = *remaining / one_min;
314
315 *remaining = *remaining % one_min;
316 secs_n = *remaining / one_sec;
317
318 *remaining = *remaining % one_sec;
319 msecs_n = *remaining / one_msec;
320
321 /* account for all rounding errors */
322 *remaining = ts - (hours_n * one_hour + mins_n * one_min
323 + secs_n * one_sec + msecs_n * one_msec);
324 if (!is_neg)
325 snprintf(str, sizeof(str), "%02"PRId64":%02"PRId64":%02"PRId64".%03"PRId64, hours_n, mins_n, secs_n, msecs_n);
326 else {
327 str[0] = '-';
328 snprintf(str + 1, sizeof(str) - 1, "%02"PRId64":%02"PRId64":%02"PRId64".%03"PRId64, hours_n, mins_n, secs_n, msecs_n);
329 }
330 return str;
331 }
332 #define RED if (stdout_is_tty) POUT("\x1b[38;2;255;0;0m")
333 #define GREEN if (stdout_is_tty) POUT("\x1b[38;2;0;255;0m")
334 #define BLUE if (stdout_is_tty) POUT("\x1b[38;2;0;0;255m")
335 #define PURPLE if (stdout_is_tty) POUT("\x1b[38;2;255;0;255m")
336 #define RESTORE if (stdout_is_tty) POUT("\x1b[39;49m");
337 static void cmd_info(void)
338 {
339 u8 *ts_str;
340 int64_t remaining;
341 u8 duration_str[sizeof("S9223372036854775807")];
342
343 RESTORE;
344 GREEN;POUT("================================================================================\n");RESTORE;
345 PURPLE;POUT("%s\n", file_url);RESTORE;
346 ts_str = ts_to_str(dec_frames.most_recent_ts, st->time_base,
347 &remaining);
348 RED;POUT("%s", ts_str);RESTORE;
349 if (remaining != 0)
350 POUT(" remaining %"PRId64" time base units", remaining);
351 else
352 POUT("\n");
353 POUT("\t%"PRId64" stream time base units (%d/%d seconds)\n", dec_frames.most_recent_ts, st->time_base.num, st->time_base.den);
354 BLUE;POUT("--------------------------------------------------------------------------------\n");RESTORE;
355 POUT("format:");
356 if (fmt_ctx->duration == AV_NOPTS_VALUE) {
357 POUT("duration is not provided\n");
358 } else {
359 snprintf(duration_str, sizeof(duration_str), "%"PRId64, fmt_ctx->duration);
360 ts_str = ts_to_str(fmt_ctx->duration, AV_TIME_BASE_Q,
361 &remaining);
362 POUT("duration=");RED;POUT("%s", ts_str);RESTORE;
363 if (remaining != 0)
364 POUT(" remaining %"PRId64" av_time_base units\n", remaining);
365 else
366 POUT("\n");
367 POUT("\t%s av_time_base units (1/%d seconds)\n\testimation method is %s\n", duration_str, AV_TIME_BASE, duration_estimate_to_str(fmt_ctx->duration_estimation_method));
368 }
369
370 POUT("stream:id=%d", st->id);
371 if (st->duration == AV_NOPTS_VALUE) {
372 POUT(";duration is not provided\n");
373 } else {
374 snprintf(duration_str, sizeof(duration_str), "%"PRId64, st->duration);
375 ts_str = ts_to_str(st->duration, st->time_base, &remaining);
376 POUT(";duration=");RED;POUT("%s\n", ts_str);RESTORE;
377 if (remaining != 0)
378 POUT(" remaining %"PRId64" stream time base units\n", remaining);
379 else
380 POUT("\n");
381 POUT("\t%s stream time base units (%d/%d seconds)\n", duration_str, st->time_base.num, st->time_base.den);
382 }
383 BLUE;POUT("--------------------------------------------------------------------------------\n");RESTORE;
384 if (paused) {
385 RED;POUT("paused\n");RESTORE;
386 }
387 if (filt_frames.muted) {
388 RED;POUT("muted\n");RESTORE;
389 }
390 GREEN;POUT("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n");RESTORE;
391 }
392 #undef RED
393 #undef GREEN
394 #undef BLUE
395 #undef PURPLE
396 #undef RESTORE
397 static void cmd_quit(void)
398 {
399 EXIT("quit command received\n");
400 }
401
402 static void seek_x(int64_t delta)
403 {
404 int r;
405 int64_t new_ts;
406
407 new_ts = dec_frames.most_recent_ts + delta
408 * st->time_base.den / st->time_base.num;
409 /* rewind capping */
410 if (new_ts < st->start_time)
411 new_ts = st->start_time;
412 POUT("trying to seek to %"PRId64" stream time base units\n", new_ts);
413
414 r = av_seek_pkt(fmt_ctx, st->id, new_ts, 0);
415 if (r < 0)
416 WARNING("unable to seek to "PRId64" stream time base units\n", new_ts);
417 }
418
419 static void cmd_rewind(void)
420 {
421 POUT("COMMAND:rewind\n");
422 seek_x(-SEEK_DELTA);
423 }
424
425 static void cmd_rewind_big(void)
426 {
427 POUT("COMMAND:rewind big\n");
428 seek_x(-SEEK_DELTA_BIG);
429 }
430
431 static void cmd_fastforward(void)
432 {
433 POUT("COMMAND:fastforward\n");
434 seek_x(SEEK_DELTA);
435 }
436
437 static void cmd_fastforward_big(void)
438 {
439 POUT("COMMAND:fastforward big\n");
440 seek_x(SEEK_DELTA_BIG);
441 }
442
443 static void cmd_pause(void)
444 {
445 if (paused) {
446 POUT("COMMAND:unpause\n");
447 paused = false;
448 } else {
449 POUT("COMMAND:pause\n");
450 paused = true;
451 }
452 }
453
454 static void cmd_vol_up(void)
455 {
456 int r;
457 u8 vol_l10n_str[sizeof("xxx.xx")]; /* should be overkill */
458 u8 response[STR_SZ];
459
460 filt_frames.vol += VOL_DELTA;
461 if (filt_frames.vol > 1.0)
462 filt_frames.vol = 1.0;
463 snprintf(vol_l10n_str, sizeof(vol_l10n_str), "%f", filt_frames.vol);
464
465 POUT("COMMAND:volume up to value %s\n", vol_l10n_str);
466
467 r = avfilter_graph_send_command(filter_graph, "vol", "volume",
468 vol_l10n_str, response, sizeof(response),
469 AVFILTER_CMD_FLAG_ONE);
470 if (r < 0)
471 WARNING("ffmpeg:volume context:unable to set the volume up to \"%s\":response from volume filter:\"%s\"\n", response);
472 }
473
474 static void cmd_vol_down(void)
475 {
476 int r;
477 u8 vol_l10n_str[sizeof("xxx.xx")]; /* should be overkill */
478 u8 response[STR_SZ];
479
480 filt_frames.vol -= VOL_DELTA;
481 if (filt_frames.vol < 0.0)
482 filt_frames.vol = 0.0;
483 snprintf(vol_l10n_str, sizeof(vol_l10n_str), "%f", filt_frames.vol);
484
485 POUT("COMMAND:volume down to value %s\n", vol_l10n_str);
486
487 r = avfilter_graph_send_command(filter_graph, "vol", "volume",
488 vol_l10n_str, response, sizeof(response), 0);
489 if (r < 0)
490 WARNING("ffmpeg:volume context:unable to set the volume down to \"%s\":response from volume filter:\"%s\"\n", response);
491 }
492
493 static void cmd_mute(void)
494 {
495 int r;
496 u8 vol_l10n_str[sizeof("xxx.xx")]; /* should be overkill */
497 u8 response[STR_SZ];
498
499 if (filt_frames.muted) {
500 POUT("COMMAND:unmuting\n");
501
502 snprintf(vol_l10n_str, sizeof(vol_l10n_str), "%f",
503 filt_frames.vol);
504 r = avfilter_graph_send_command(filter_graph, "vol", "volume",
505 vol_l10n_str, response, sizeof(response), 0);
506 if (r < 0) {
507 WARNING("ffmpeg:volume context:unable to mute the volume to 0:response from volume filter:%s\n", response);
508 } else {
509 filt_frames.muted = false;
510 }
511 } else {
512 POUT("COMMAND:muting\n");
513
514 r = avfilter_graph_send_command(filter_graph, "vol", "volume",
515 float_zero_l10n_str, response, sizeof(response), 0);
516 if (r < 0) {
517 WARNING("ffmpeg:volume context:unable to mute the volume to 0:response from volume filter:%s\n", response);
518 } else {
519 filt_frames.muted = true;
520 }
521 }
522 }
523
524 static void init_stdin_tty(void)
525 {
526 int r;
527 struct termios tio_new;
528 struct termios tio_chk;
529
530 r = isatty(0);
531 if (r == 0) {
532 POUT("input:standard input is not a terminal\n");
533 return;
534 }
535
536 memset(&stdin_tio_save, 0, sizeof(stdin_tio_save));
537 r = tcgetattr(0, &stdin_tio_save);
538 if (r == -1)
539 FATAL("input:unable to get the current standard input terminal line attributes\n");
540
541 tio_new = stdin_tio_save;
542 tio_new.c_lflag &= ~(ICANON|ECHO);
543 tio_new.c_cc[VMIN] = 1; /* 1 "char", could be bytes from a utf8 code point */
544 tio_new.c_cc[VTIME] = 0;
545
546 r = tcsetattr(0, TCSANOW, &tio_new);
547 stdin_tty_conf_modified = true;
548 if (r == -1)
549 FATAL("input:unable to set all standard input terminal line\n");
550
551 r = tcgetattr(0, &tio_chk);
552 if (r == -1)
553 FATAL("input:unable to get the current standard input terminal line attributes for checking\n");
554
555 r = memcmp(&tio_chk, &tio_new, sizeof(tio_chk));
556 if (r != 0)
557 FATAL("input:setting the wanted terminal line attributes failed\n");
558 }
559
560 static void init_stdin_flags(void)
561 {
562 int r;
563 /* switch the standard input to non-blocking */
564 r = fcntl(0, F_GETFL);
565 if (r == -1)
566 FATAL("input:unable to get the file flags of the standard input\n");
567
568 stdin_flags_save = r;
569
570 r |= O_NONBLOCK;
571 r = fcntl(0, F_SETFL, r);
572 if (r == -1)
573 FATAL("input:unable to set non-blocking operations on the standard input\n");
574 }
575
576 static void init_stdout(void)
577 {
578 int r;
579
580 r = isatty(1);
581 if (r == 0) {
582 POUT("output:standard output not is not a terminal\n");
583 stdout_is_tty = false;
584 return;
585 }
586 stdout_is_tty = true;
587 }
588
589 static void init_ffmpeg_dec(void)
590 {
591 int r;
592
593 memset(&dec_frames, 0, sizeof(dec_frames));
594
595 fmt_ctx = 0;
596 r = avformat_open_input(&fmt_ctx, file_url, NULL, NULL);
597 if (r < 0)
598 FATAL("ffmpeg:unable to open\n");
599
600 /* probe beyond the header, if any */
601 r = avformat_find_stream_info(fmt_ctx, 0);
602 if (r < 0)
603 FATAL("ffmpeg:unable to probe\n");
604
605 r = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, 0, 0);
606 if (r < 0)
607 FATAL("ffmpeg:no audio stream found\n");
608
609 st_idx = r;
610 POUT("ffmpeg:audio stream index %d is selected to be played\n", st_idx);
611 st = fmt_ctx->streams[st_idx];
612 dec_frames.most_recent_ts = st->start_time;
613
614 dec = avcodec_find_decoder(st->codecpar->codec_id);
615 if (dec == 0)
616 FATAL("ffmpeg:unable to find a proper audio decoder\n");
617
618 dec_ctx = avcodec_alloc_context3(dec);
619 if (dec_ctx == 0)
620 FATAL("ffmpeg:unable to allocate an audio decoder context\n");
621
622 /* XXX: useless ? */
623 r = avcodec_parameters_to_context(dec_ctx, st->codecpar);
624 if (r < 0)
625 FATAL("ffmpeg:unable to apply stream codec parameters in codec context\n");
626
627 r = avcodec_open2(dec_ctx, dec, 0);
628 if (r < 0)
629 FATAL("ffmpeg:unable to open the audio decoder context\n");
630
631 pkt = av_packet_alloc();
632 if (pkt == 0)
633 FATAL("ffmpeg:unable to allocate a packet\n");
634
635 dec_frames.av = av_frames_alloc();
636 if (dec_frames.av == 0)
637 FATAL("ffmpeg:unable to allocate a decoded frames structure\n");
638 }
639
640 static void init_alsa(void)
641 {
642 int r;
643
644 r = snd_output_stdio_attach(&pcm_pout, stdout, 0);
645 if (r < 0)
646 FATAL("alsa:unable to attach stdout\n");
647
648 r = snd_output_stdio_attach(&pcm_perr, stderr, 0);
649 if (r < 0)
650 FATAL("alsa:unable to attach stderr\n");
651
652 r = snd_pcm_open(&pcm, pcm_name, SND_PCM_STREAM_PLAYBACK,
653 SND_PCM_NONBLOCK);
654 if (r < 0) {
655 if (r == -EAGAIN)
656 FATAL("alsa:\"%s\" pcm is already in use\n", pcm_name);
657 else
658 FATAL("alsa:unable to open \"%s\" pcm for playback\n", pcm_name);
659 }
660
661 POUT("ALSA PCM DUMP START-------------------------------------------------------------\n");
662 snd_pcm_dump(pcm, pcm_pout);
663 POUT("ALSA PCM DUMP END---------------------------------------------------------------\n");
664 }
665
666 static bool ffmpeg_fmt2pcm_layout_best_effort(enum AVFrameFormat ffmpeg_fmt,
667 snd_pcm_format_t *alsa_fmt, snd_pcm_access_t *alsa_access)
668 {
669 static u8 ffmpeg_fmt_str[STR_SZ];
670
671 av_get_frame_fmt_string(ffmpeg_fmt_str, STR_SZ, ffmpeg_fmt);
672
673 /* XXX: only classic non-mmap ones */
674 switch (ffmpeg_fmt) {
675 case AV_SAMPLE_FMT_U8:
676 *alsa_fmt = SND_PCM_FORMAT_U8;
677 *alsa_access = SND_PCM_ACCESS_RW_INTERLEAVED;
678 break;
679 case AV_SAMPLE_FMT_S16:
680 *alsa_fmt = SND_PCM_FORMAT_S16;
681 *alsa_access = SND_PCM_ACCESS_RW_INTERLEAVED;
682 break;
683 case AV_SAMPLE_FMT_S32:
684 *alsa_fmt = SND_PCM_FORMAT_S32;
685 *alsa_access = SND_PCM_ACCESS_RW_INTERLEAVED;
686 break;
687 case AV_SAMPLE_FMT_FLT:
688 *alsa_fmt = SND_PCM_FORMAT_FLOAT;
689 *alsa_access = SND_PCM_ACCESS_RW_INTERLEAVED;
690 break;
691 /* ffmpeg "planar" fmts are actually non interleaved fmts */
692 case AV_SAMPLE_FMT_U8P:
693 *alsa_fmt = SND_PCM_FORMAT_U8;
694 *alsa_access = SND_PCM_ACCESS_RW_NONINTERLEAVED;
695 break;
696 case AV_SAMPLE_FMT_S16P:
697 *alsa_fmt = SND_PCM_FORMAT_S16;
698 *alsa_access = SND_PCM_ACCESS_RW_NONINTERLEAVED;
699 break;
700 case AV_SAMPLE_FMT_S32P:
701 *alsa_fmt = SND_PCM_FORMAT_S32;
702 *alsa_access = SND_PCM_ACCESS_RW_NONINTERLEAVED;
703 break;
704 case AV_SAMPLE_FMT_FLTP:
705 *alsa_fmt = SND_PCM_FORMAT_FLOAT;
706 *alsa_access = SND_PCM_ACCESS_RW_NONINTERLEAVED;
707 break;
708 default:
709 POUT("best effort:unable to wire ffmpeg sample format \"%sbits\" to alsa sample format, \n,", ffmpeg_fmt_str);
710 return false;
711 }
712 POUT("best effort:ffmpeg format \"%sbits\" (%u bytes) to alsa layout \"%s\" and access \"%s\"\n", ffmpeg_fmt_str, av_get_bytes_per_sample(ffmpeg_fmt), snd_pcm_format_description(*alsa_fmt), snd_pcm_access_name(*alsa_access));
713 return true;
714 }
715
716 /* fatal if the wiring cannot be done */
717 static void pcm_layout2ffmpeg_fmt_strict(snd_pcm_format_t alsa_fmt,
718 snd_pcm_access_t alsa_access, enum AVFrameFormat *ffmpeg_fmt)
719 {
720 static u8 ffmpeg_fmt_str[STR_SZ];
721 /*
722 * ffmpeg fmt byte order is always native.
723 * here we handle little endian only
724 */
725 switch (alsa_fmt) {
726 case SND_PCM_FORMAT_FLOAT:
727 if (alsa_access == SND_PCM_ACCESS_RW_INTERLEAVED)
728 *ffmpeg_fmt = AV_SAMPLE_FMT_FLT;
729 else
730 *ffmpeg_fmt = AV_SAMPLE_FMT_FLTP;
731 break;
732 case SND_PCM_FORMAT_S32:
733 if (alsa_access == SND_PCM_ACCESS_RW_INTERLEAVED)
734 *ffmpeg_fmt = AV_SAMPLE_FMT_S32;
735 else
736 *ffmpeg_fmt = AV_SAMPLE_FMT_S32P;
737 break;
738 case SND_PCM_FORMAT_S16:
739 if (alsa_access == SND_PCM_ACCESS_RW_INTERLEAVED)
740 *ffmpeg_fmt = AV_SAMPLE_FMT_S16;
741 else
742 *ffmpeg_fmt = AV_SAMPLE_FMT_S16P;
743 break;
744 case SND_PCM_FORMAT_U8:
745 if (alsa_access == SND_PCM_ACCESS_RW_INTERLEAVED)
746 *ffmpeg_fmt = AV_SAMPLE_FMT_U8;
747 else
748 *ffmpeg_fmt = AV_SAMPLE_FMT_U8P;
749 break;
750 default:
751 FATAL("unable to wire strictly alsa layout \"%s\"/\"%s\" to a ffmpeg format\n", snd_pcm_format_description(alsa_fmt), snd_pcm_access_name(alsa_access));
752 }
753 av_get_frame_fmt_string(ffmpeg_fmt_str, STR_SZ, *ffmpeg_fmt);
754 POUT("alsa pcm layout \"%s\"/\"%s\" wired strictly to ffmpeg format \"%sbits\"\n", snd_pcm_format_description(alsa_fmt), snd_pcm_access_name(alsa_access), ffmpeg_fmt_str);
755 }
756
757 static void pcm_hw_channels_n_decide(void)
758 {
759 int r;
760 unsigned int channels_n_dec;
761 unsigned int channels_n_max;
762 unsigned int channels_n_min;
763
764 channels_n_dec = (unsigned int)dec_ctx->channels;
765
766 r = snd_pcm_hw_params_test_channels(pcm, pcm_hw_params, channels_n_dec);
767 if (r == 0) {
768 r = snd_pcm_hw_params_set_channels(pcm, pcm_hw_params,
769 channels_n_dec);
770 if (r != 0)
771 FATAL("alsa:unable to restrict pcm device to %u channels, count which was successfully tested\n", channels_n_dec);
772 POUT("alsa:using ffmpeg decoder %u channels\n", channels_n_dec);
773 return;
774 }
775 POUT("alsa:unable to use ffmpeg decoder %u channels\n", channels_n_dec);
776
777 /* try to use the maximum channels n the pcm can */
778
779 r = snd_pcm_hw_params_get_channels_max(pcm_hw_params, &channels_n_max);
780 if (r != 0)
781 FATAL("alsa:unable to get the maximum count of pcm device channels\n");
782
783 r = snd_pcm_hw_params_test_channels(pcm, pcm_hw_params, channels_n_max);
784 if (r == 0) {
785 r = snd_pcm_hw_params_set_channels(pcm, pcm_hw_params,
786 channels_n_max);
787 if (r != 0)
788 FATAL("alsa:unable to restrict pcm device to %u channels, count which was successfully tested\n", channels_n_max);
789 POUT("alsa:using pcm maximum %u channels\n", channels_n_max);
790 return;
791 }
792
793 /* ok... last try, the pcm dev minimum channels */
794
795 r = snd_pcm_hw_params_get_channels_min(pcm_hw_params, &channels_n_min);
796 if (r != 0)
797 FATAL("alsa:unable to get the minimum count of pcm device channels\n");
798
799 r = snd_pcm_hw_params_test_channels(pcm, pcm_hw_params, channels_n_min);
800 if (r == 0) {
801 r = snd_pcm_hw_params_set_channels(pcm, pcm_hw_params,
802 channels_n_min);
803 if (r != 0)
804 FATAL("alsa:unable to restrict pcm device to %u channels, count which was successfully tested\n", channels_n_min);
805 POUT("alsa:using pcm device minimum %u channels\n", channels_n_min);
806 return;
807 }
808 FATAL("alsa:unable to find a suitable count of channels\n");
809 }
810
811 static void pcm_hw_rate_decide(void)
812 {
813 int r;
814 unsigned int rate_dec;
815 unsigned int rate_max;
816 unsigned int rate_near;
817 unsigned int rate_min;
818
819 rate_dec = (unsigned int)dec_ctx->frame_rate;
820
821 r = snd_pcm_hw_params_test_rate(pcm, pcm_hw_params, rate_dec,
822 SND_PCM_STREAM_PLAYBACK);
823 if (r == 0) {
824 r = snd_pcm_hw_params_set_rate(pcm, pcm_hw_params, rate_dec,
825 SND_PCM_STREAM_PLAYBACK);
826 if (r != 0)
827 FATAL("alsa:unable to restrict pcm device to %uHz, which was successfully tested\n", rate_dec);
828 POUT("alsa:using ffmpeg decoder %uHz\n", rate_dec);
829 return;
830 }
831 POUT("alsa:unable to use ffmpeg decodeur %uHz\n", rate_dec);
832
833 /* try to use the maximum rate the pcm can */
834
835 r = snd_pcm_hw_params_get_rate_max(pcm_hw_params, &rate_max,
836 SND_PCM_STREAM_PLAYBACK);
837 if (r != 0)
838 FATAL("alsa:unable to get the maximum rate of pcm device\n");
839
840 r = snd_pcm_hw_params_test_rate(pcm, pcm_hw_params, rate_max,
841 SND_PCM_STREAM_PLAYBACK);
842 if (r == 0) {
843 r = snd_pcm_hw_params_set_rate(pcm, pcm_hw_params, rate_max,
844 SND_PCM_STREAM_PLAYBACK);
845 if (r != 0)
846 FATAL("alsa:unable to restrict pcm device to %uHz, which was successfully tested\n", rate_max);
847 POUT("alsa:using pcm device %uHz\n", rate_max);
848 return;
849 }
850
851 /* try to use a rate "near" of what the pcm dev can */
852
853 rate_near = rate_dec;
854 r = snd_pcm_hw_params_set_rate_near(pcm, pcm_hw_params, &rate_near,
855 SND_PCM_STREAM_PLAYBACK);
856 if (r == 0) {
857 POUT("alsa:using pcm device %uHz\n", rate_near);
858 return;
859 }
860
861 /* even a "near" rate did failed... try the minimum */
862
863 r = snd_pcm_hw_params_get_rate_min(pcm_hw_params, &rate_min,
864 SND_PCM_STREAM_PLAYBACK);
865 if (r != 0)
866 FATAL("alsa:unable to get the minimum rate of pcm device\n");
867
868 r = snd_pcm_hw_params_test_rate(pcm, pcm_hw_params, rate_min,
869 SND_PCM_STREAM_PLAYBACK);
870 if (r == 0) {
871 r = snd_pcm_hw_params_set_rate(pcm, pcm_hw_params, rate_min,
872 SND_PCM_STREAM_PLAYBACK);
873 if (r != 0)
874 FATAL("alsa:unable to restrict pcm device to %uHz, which was successfully tested\n", rate_min);
875 POUT("alsa:using pcm device %uHz\n", rate_min);
876 return;
877 }
878 FATAL("alsa:unable to find a suitable rate\n");
879 }
880
881 static bool pcm_hw_fmt_decide_x(snd_pcm_format_t fmt)
882 {
883 int r;
884
885 r = snd_pcm_hw_params_test_format(pcm, pcm_hw_params, fmt);
886 if (r != 0)
887 return false;
888
889 r = snd_pcm_hw_params_set_format(pcm, pcm_hw_params, fmt);
890 if (r != 0)
891 FATAL("alsa:unable to restrict pcm device to \"%s\", which was successfully tested\n", snd_pcm_format_description(fmt));
892 POUT("alsa:using \"%s\" format\n", snd_pcm_format_description(fmt));
893 return true;
894 }
895
896 static void pcm_hw_fmt_decide(bool use_fmt_dec, snd_pcm_format_t fmt_dec)
897 {
898 int r;
899 snd_pcm_format_t *fmt;
900
901
902 if (use_fmt_dec) {
903 r = snd_pcm_hw_params_test_format(pcm, pcm_hw_params, fmt_dec);
904 if (r == 0) {
905 r = snd_pcm_hw_params_set_format(pcm, pcm_hw_params,
906 fmt_dec);
907 if (r != 0)
908 FATAL("alsa:unable to restrict pcm device to \"%s\", which was successfully tested\n", snd_pcm_format_description(fmt_dec));
909 POUT("alsa:using ffmpeg decoder \"%s\" format\n",
910 snd_pcm_format_description(fmt_dec));
911 return;
912 }
913 }
914
915 /* then we try to select from the reasonable "best" to the lowest */
916
917 /* prefer fmts we know supported by ffmpeg */
918 if (pcm_hw_fmt_decide_x(SND_PCM_FORMAT_FLOAT))
919 return;
920 if (pcm_hw_fmt_decide_x(SND_PCM_FORMAT_S32))
921 return;
922 if (pcm_hw_fmt_decide_x(SND_PCM_FORMAT_S16))
923 return;
924 if (pcm_hw_fmt_decide_x(SND_PCM_FORMAT_U8))
925 return;
926 /*
927 * from here, at the time of writting, those fmts have no ffmpeg
928 * wiring, but we are alsa centric here, validate that later
929 */
930 if (pcm_hw_fmt_decide_x(SND_PCM_FORMAT_U32))
931 return;
932 if (pcm_hw_fmt_decide_x(SND_PCM_FORMAT_S24))
933 return;
934 if (pcm_hw_fmt_decide_x(SND_PCM_FORMAT_U24))
935 return;
936 if (pcm_hw_fmt_decide_x(SND_PCM_FORMAT_U16))
937 return;
938 if (pcm_hw_fmt_decide_x(SND_PCM_FORMAT_S8))
939 return;
940 FATAL("alsa:unable to find a suitable format\n");
941 }
942
943 static bool pcm_hw_access_decide_x(snd_pcm_access_t access)
944 {
945 int r;
946
947 r = snd_pcm_hw_params_test_access(pcm, pcm_hw_params, access);
948 if (r != 0)
949 return false;
950
951 r = snd_pcm_hw_params_set_access(pcm, pcm_hw_params, access);
952 if (r != 0)
953 FATAL("alsa:unable to restrict pcm device to \"%s\", which was successfully tested\n", snd_pcm_access_name(access));
954 POUT("alsa:using \"%s\" access\n", snd_pcm_access_name(access));
955 return true;
956 }
957
958 /* XXX: only classic non-mmap ones */
959 static void pcm_hw_access_decide(bool use_access_dec,
960 snd_pcm_access_t access_dec)
961 {
962 int r;
963 snd_pcm_access_t access;
964
965 if (use_access_dec) {
966 r = snd_pcm_hw_params_test_access(pcm, pcm_hw_params, access_dec);
967 if (r == 0) {
968 r = snd_pcm_hw_params_set_access(pcm, pcm_hw_params,
969 access_dec);
970 if (r != 0)
971 FATAL("alsa:unable to restrict pcm device to \"%s\", which was successfully tested\n", snd_pcm_access_name(access_dec));
972 POUT("alsa:using ffmpeg decoder \"%s\" access\n", snd_pcm_access_name(access_dec));
973 return;
974 }
975 }
976 /* brute force */
977 if (pcm_hw_access_decide_x(SND_PCM_ACCESS_RW_INTERLEAVED))
978 return;
979 if (pcm_hw_access_decide_x(SND_PCM_ACCESS_RW_NONINTERLEAVED))
980 return;
981 FATAL("alsa:unable to find a suitable access\n");
982 }
983 /*
984 * this function will "decide" the pcm dev conf:
985 * the goal is to be the "closest" to the dec output,
986 * the "gap" will be "filled" with ffmpeg fiters
987 *
988 * the "strategy" is a "fall-thru" (channels n then ... then ...) which
989 * will "restrict" the pcm dev conf further at each step.
990 *
991 * we try to use a sensible restrict order regarding audio properties.
992 */
993 static void pcm_hw_conf(void)
994 {
995 int r;
996 bool best_effort_wiring_success;
997 snd_pcm_format_t fmt_from_dec_best_effort;
998 snd_pcm_access_t access_from_dec_best_effort;
999 snd_pcm_uframes_t latency_control_target_buf_uframes_n;
1000 snd_pcm_uframes_t latency_control_buf_uframes_n;
1001 unsigned int rate;
1002
1003 /* the return value is from a first refine of the raw hw params */
1004 r = snd_pcm_hw_params_any(pcm, pcm_hw_params);
1005 if (r < 0)
1006 FATAL("alsa:unable to populate the hardware parameters context\n");
1007
1008 pcm_hw_channels_n_decide();
1009 pcm_hw_rate_decide();
1010
1011 /* try our best */
1012 best_effort_wiring_success = ffmpeg_fmt2pcm_layout_best_effort(
1013 dec_ctx->frame_fmt, &fmt_from_dec_best_effort,
1014 &access_from_dec_best_effort);
1015 pcm_hw_fmt_decide(best_effort_wiring_success, fmt_from_dec_best_effort);
1016 pcm_hw_access_decide(best_effort_wiring_success,
1017 access_from_dec_best_effort);
1018 /*--------------------------------------------------------------------*/
1019 /*
1020 * lantency control: some audio buf can be huge (tested on a
1021 * pulseaudio with 10 seconds audio buf). if we are careless, we
1022 * will quickly fill this buf which is worth a significant amount of
1023 * time, hence will add huge latency to interactive audio filtering
1024 * (volume...). in the case of the 10 secondes pulseaudio buf, it
1025 * means if you want to mute the audio, it will happen 10 seconds
1026 * later. we add lantency control by limiting the size of the device
1027 * audio buf, in periods count.
1028 * we choose roughly 0.25 second, or roughly (rate / 4) frames.
1029 */
1030 r = snd_pcm_hw_params_get_rate(pcm_hw_params, &rate, 0);
1031 if (r < 0) {
1032 WARNING("alsa:latency control:DISABLING LATENCY CONTROL:unable to get the rate for the current device parameters\n");
1033 return;
1034 }
1035 latency_control_target_buf_uframes_n = (snd_pcm_uframes_t)rate;
1036
1037 latency_control_target_buf_uframes_n /= 4;
1038 latency_control_buf_uframes_n = latency_control_target_buf_uframes_n;
1039
1040 r = snd_pcm_hw_params_set_buffer_size_near(pcm, pcm_hw_params,
1041 &latency_control_buf_uframes_n);
1042 if (r < 0) {
1043 WARNING("alsa:latency control:DISABLING_LATENCY_CONTROL:unable to set the audio buffer size (count of frames) to %u periods for the current device parameters\n", latency_control_buf_uframes_n);
1044 return;
1045 }
1046 POUT("alsa:latency control:target buffer frame count is %u (~0.25 sec), got an audio buffer size set to %u frames\n", latency_control_target_buf_uframes_n, latency_control_buf_uframes_n);
1047 }
1048
1049 static void pcm_conf(void)
1050 {
1051 int r;
1052 snd_pcm_format_t fmt;
1053 snd_pcm_access_t access;
1054 snd_pcm_sw_params_t *sw_params;
1055
1056 r = snd_pcm_hw_params_malloc(&pcm_hw_params);
1057 if (r < 0)
1058 FATAL("alsa:unable to allocate hardware parameters context\n");
1059
1060 pcm_hw_conf();
1061
1062 r = snd_pcm_hw_params(pcm, pcm_hw_params);
1063 if (r != 0)
1064 FATAL("alsa:unable to apply the hardware parameters\n");
1065 POUT("ALSA:HW_PARAMS START------------------------------------------------------------\n");
1066 r = snd_pcm_hw_params_current(pcm, pcm_hw_params);
1067 if (r != 0)
1068 FATAL("alsa:unable to get current hardware parameters\n");
1069 snd_pcm_hw_params_dump(pcm_hw_params, pcm_pout);
1070 POUT("ALSA:HW_PARAMS END--------------------------------------------------------------\n");
1071
1072 POUT("ALSA:SW_PARAMS START------------------------------------------------------------\n");
1073 r = snd_pcm_sw_params_malloc(&sw_params);
1074 if (r != 0)
1075 FATAL("alsa:unable to allocate software parameters structure\n");
1076
1077 r = snd_pcm_sw_params_current(pcm, sw_params);
1078 if (r != 0)
1079 FATAL("alsa:unable to get current software parameters\n");
1080
1081 r = snd_pcm_sw_params_set_period_event(pcm, sw_params, 1);
1082 if (r != 0)
1083 FATAL("alsa:unable to enable period event\n");
1084
1085 r = snd_pcm_sw_params(pcm, sw_params);
1086 if (r != 0)
1087 FATAL("alsa:unable to apply sotfware parameters\n");
1088
1089 snd_pcm_sw_params_dump(sw_params, pcm_pout);
1090 POUT("ALSA:SW_PARAMS END--------------------------------------------------------------\n");
1091
1092 r = snd_pcm_poll_descriptors_count(pcm);
1093 POUT("alsa:have %d poll file descriptors\n", r);
1094
1095 if ((r <= 0) || (r > PCM_POLLFDS_N_MAX))
1096 FATAL("alsa:invalid number of alsa poll file descriptors\n");
1097 pcm_pollfds_n =(u8)r;
1098
1099 memset(pcm_pollfds, 0, sizeof(pcm_pollfds));
1100 snd_pcm_poll_descriptors(pcm, pcm_pollfds, PCM_POLLFDS_N_MAX);
1101 }
1102 /*
1103 * block as much as possible.
1104 * handle only async "usual" sigs, with sync signalfd.
1105 * allow some signals to go thru though.
1106 * always presume the process "controlling terminal" is different than the
1107 * terminal connected on standard input and standard output
1108 */
1109 static void init_sigs(void)
1110 {
1111 int r;
1112 sigset_t sset;
1113
1114 r = sigfillset(&sset);
1115 if (r == -1)
1116 FATAL("unable to get a full signal mask\n");
1117
1118 /* the "controlling terminal" line asks for a core dump, leave it be */
1119 r = sigdelset(&sset, SIGQUIT);
1120 if (r == -1)
1121 FATAL("unable to remove SIGQUIT from our signal mask\n");
1122
1123 r = pthread_sigmask(SIG_SETMASK, &sset, 0);
1124 if (r != 0)
1125 FATAL("unable to \"block\" \"all\" signals\n");
1126
1127 /* from here, we "steal" signals with signalfd */
1128
1129 r = sigemptyset(&sset);
1130 if (r == -1)
1131 FATAL("unable to get an empty signal mask\n");
1132 /* we are asked nicely to terminate */
1133 r = sigaddset(&sset, SIGTERM);
1134 if (r == -1)
1135 FATAL("unable to add SIGTERM to our signal mask\n");
1136 /* the "controlling terminal" line (^c) asks nicely to terminate */
1137 r = sigaddset(&sset, SIGINT);
1138 if (r == -1)
1139 FATAL("unable to add SIGINT to our signal mask\n");
1140
1141 r = signalfd(-1, &sset, SFD_NONBLOCK);
1142 if (r == -1)
1143 FATAL("unable to get a signalfd file descriptor\n");
1144 sig_fd = r;
1145 }
1146
1147 static void init_epoll(void)
1148 {
1149 int r;
1150 u8 i;
1151 struct epoll_event evt;
1152
1153 ep_fd = epoll_create1(0);
1154 if (ep_fd == -1)
1155 FATAL("unable to create the epoll file descriptor\n");
1156 /*--------------------------------------------------------------------*/
1157 /* signals */
1158 evt.events = EPOLLIN;
1159 evt.data.fd = sig_fd;
1160 r = epoll_ctl(ep_fd, EPOLL_CTL_ADD, sig_fd, &evt);
1161 if (r == -1)
1162 FATAL("unable to add the signalfd file descriptior to the epoll file descriptor\n");
1163 /*--------------------------------------------------------------------*/
1164 /* standard input/terminal */
1165 evt.events = EPOLLIN;
1166 evt.data.fd = 0;
1167 r = epoll_ctl(ep_fd, EPOLL_CTL_ADD, 0, &evt);
1168 if (r == -1)
1169 FATAL("unable to add the standard input to the epoll file descriptor\n");
1170 /*--------------------------------------------------------------------*/
1171 /* the timer in charge of accounting unknown esc seq */
1172 evt.events = EPOLLIN;
1173 evt.data.fd = input_timer_fd;
1174 r = epoll_ctl(ep_fd, EPOLL_CTL_ADD, input_timer_fd, &evt);
1175 if (r == -1)
1176 FATAL("unable to add the timer file descriptor accounting for unknown escape sequences to epoll file descriptor\n");
1177 /*--------------------------------------------------------------------*/
1178 /* alsa poll file descriptors */
1179 i = 0;
1180 loop {
1181 if (i == pcm_pollfds_n)
1182 break;
1183 evt.events = pcm_pollfds[i].events;
1184 evt.data.fd = pcm_pollfds[i].fd;
1185 r = epoll_ctl(ep_fd, EPOLL_CTL_ADD, pcm_pollfds[i].fd, &evt);
1186 if (r == -1)
1187 FATAL("unable to add alsa poll file descriptor index %d to epoll file descriptor\n", i);
1188 ++i;
1189 }
1190 }
1191
1192 static void init_input_state(void)
1193 {
1194 input_timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
1195 if (input_timer_fd == -1)
1196 FATAL("unable to get a timer file descriptor\n");
1197
1198 input_esc_seq_mode = false;
1199
1200 esc_seq_next_byte = 0;
1201 utf8_cp_next_byte = 0;
1202 }
1203
1204 static void input_utf8_cp_bind_cmd(void)
1205 {
1206 u16 i;
1207
1208 i = 0;
1209 loop {
1210 struct tty_bind_t *bind;
1211
1212 if (i == ARRAY_N(tty_binds))
1213 break;
1214 bind = tty_binds + i;
1215
1216 /* exclude esc seq binds */
1217 if ((bind->c[0] != 0x1b) && (utf8_cp_sz == bind->sz)) {
1218 int r;
1219
1220 r = memcmp(utf8_cp, bind->c, utf8_cp_sz);
1221 if (r == 0) {
1222 bind->cmd();
1223 return;
1224 }
1225 }
1226 ++i;
1227 }
1228 }
1229 /*
1230 * to exit the esc seq input mode, we could implement a smart tree in order to
1231 * know if it is not possible to get any defined esc seq, or we could already
1232 * use the maximum bind esc seq sz
1233 */
1234 static bool input_esc_seq_bind_cmd(void)
1235 {
1236 u16 i;
1237
1238 i = 0;
1239 loop {
1240 struct tty_bind_t *bind;
1241
1242 if (i == ARRAY_N(tty_binds))
1243 break;
1244 bind = tty_binds + i;
1245
1246 /* only esc seq binds */
1247 if ((bind->c[0] == 0x1b) && (esc_seq_sz == bind->sz)) {
1248 int r;
1249
1250 r = memcmp(esc_seq, bind->c, esc_seq_sz);
1251 if (r == 0) {
1252 bind->cmd();
1253 return true;
1254 }
1255 }
1256 ++i;
1257 }
1258 return false;
1259 }
1260
1261 static void input_byte_esc_seq(void)
1262 {
1263 int r;
1264 struct itimerspec t;
1265
1266 if (input_b == 0x1b) {
1267 esc_seq[0] = 0x1b;
1268 esc_seq_next_byte = 1;
1269
1270 memset(&t, 0, sizeof(t));
1271 t.it_value.tv_sec = INPUT_ESC_SEQ_TIMEOUT_SECS_N;
1272
1273 r = timerfd_settime(input_timer_fd, 0, &t, 0);
1274 if (r == -1)
1275 FATAL("unable to arm the timer to account for unknown input escape sequence from the terminal\n");
1276 return;
1277 }
1278
1279 esc_seq[esc_seq_next_byte] = input_b;
1280 ++esc_seq_next_byte;
1281
1282 if (!input_esc_seq_bind_cmd() && (esc_seq_next_byte != STR_SZ))
1283 return;
1284
1285 memset(&t, 0, sizeof(t));
1286 r = timerfd_settime(input_timer_fd, 0, &t, 0);
1287 if (r == -1)
1288 FATAL("unable to disarm the timer used to account for unknown input escape sequence from the terminal\n");
1289
1290 esc_seq_next_byte = 0;
1291 input_esc_seq_mode = false;
1292
1293 utf8_cp_next_byte = 0;
1294 }
1295
1296 static void input_byte_utf8_cp(void)
1297 {
1298 if ((input_b & 0x80) != 0) { /* utf8 cp > 0x7f */
1299 if ((input_b & 0x40) != 0) { /* utf8 cp start byte */
1300 utf8_cp[0] = input_b;
1301 utf8_cp_next_byte = 1;
1302
1303 if ((input_b & 0x20) == 0)
1304 utf8_cp_sz = 2;
1305 else if ((input_b & 0x10) == 0)
1306 utf8_cp_sz = 3;
1307 else /* if ((input_b & 0x08) == 0) */
1308 utf8_cp_sz = 4; /* must be 4 */
1309 return;
1310 }
1311
1312 /* (b & 0x40) == 0, utf8 cp continuation byte */
1313
1314 /*
1315 * no start byte, discard (but should skip all following
1316 * continuation bytes and send the utf8 "invalid" cp)
1317 */
1318 if (utf8_cp_next_byte == 0)
1319 return;
1320
1321 utf8_cp[utf8_cp_next_byte] = input_b;
1322 ++utf8_cp_next_byte;
1323
1324 if (utf8_cp_next_byte != utf8_cp_sz)
1325 return;
1326
1327 } else {/* ascii 0x00 - 0x7f */
1328 if (input_b == 0x1b) {/* esc */
1329 /* change state and process the esc byte */
1330 input_esc_seq_mode = true;
1331 input_byte_esc_seq();
1332 return;
1333 }
1334 utf8_cp[0] = input_b;
1335 utf8_cp_sz = 1;
1336 }
1337
1338 input_utf8_cp_bind_cmd();
1339 }
1340 /* we are either reading an utf8 cp or an esc seq */
1341 static void evt_input_drain(void) { loop
1342 {
1343 ssize_t r;
1344
1345 errno = 0;
1346 r = read(0, &input_b, 1);
1347 if (r == -1) {
1348 if (errno == EAGAIN) /* no more input data */
1349 break;
1350 if (errno == EINTR) /* restart manually the call */
1351 continue;
1352 FATAL("an error occured while reading the input\n");
1353 }
1354 if (r == 0)
1355 FATAL("input end of file\n");
1356
1357 if (!input_esc_seq_mode)
1358 input_byte_utf8_cp();
1359 else
1360 input_byte_esc_seq();
1361 }}
1362
1363 static void evt_sigs(void)
1364 {
1365 int r;
1366 struct signalfd_siginfo siginfo;
1367
1368 /* no short reads */
1369 r = read(sig_fd, &siginfo, sizeof(siginfo));
1370 if (r != sizeof(siginfo))
1371 FATAL("unable to read signal information\n");
1372
1373 switch (siginfo.ssi_signo) {
1374 case SIGTERM:
1375 EXIT("received SIGTERM\n");
1376 case SIGINT:
1377 EXIT("received SIGINT\n");
1378 default:
1379 WARNING("signal handle:unwanted signal %d received, discarding\n", siginfo.ssi_signo);
1380 }
1381 }
1382
1383 static void evt_timer(void)
1384 {
1385 int r;
1386 struct itimerspec t;
1387
1388 memset(&t, 0, sizeof(t));
1389 r = timerfd_settime(input_timer_fd, 0, &t, 0);
1390 if (r == -1)
1391 FATAL("unable to disarm the timer used to account for unknown input escape sequences from the terminal\n");
1392
1393 esc_seq_next_byte = 0;
1394 input_esc_seq_mode = false;
1395
1396 utf8_cp_next_byte = 0;
1397 }
1398
1399 static void evt_handle(struct epoll_event *evt, bool *pcm_evt)
1400 {
1401 if (evt->data.fd == 0) {
1402 if ((evt->events & EPOLLIN) != 0)
1403 evt_input_drain();
1404 else
1405 FATAL("event loop wait:input:unexpected event\n");
1406 } else if (evt->data.fd == sig_fd) {
1407 if ((evt->events & EPOLLIN) != 0)
1408 evt_sigs();
1409 else
1410 FATAL("event loop wait:signal:unexpected event\n");
1411 } else if (evt->data.fd == input_timer_fd) {
1412 if ((evt->events & EPOLLIN) != 0)
1413 evt_timer();
1414 else
1415 FATAL("event loop wait:timer:unexpected event\n");
1416 } else { /* only update alsa fds */
1417 u8 i;
1418
1419 i = 0;
1420 loop {
1421 if (i == pcm_pollfds_n)
1422 break;
1423
1424 if (evt->data.fd == pcm_pollfds[i].fd) {
1425 pcm_pollfds[i].revents = evt->events;
1426 *pcm_evt = true;
1427 }
1428
1429 ++i;
1430 }
1431 }
1432 }
1433
1434 static void pkt_read(void) { loop
1435 {
1436 int r;
1437
1438 r = av_read_pkt(fmt_ctx, pkt);
1439 if (r == 0) {
1440 if (pkt->stream_index == st_idx)
1441 break;
1442 av_packet_unref(pkt);
1443 continue;
1444 } else if (r == AVERROR_EOF) {
1445 /* switch decoder(s) to draining/finishing mode */
1446 av_packet_free(&pkt);
1447 break;
1448 }
1449 FATAL("ffmpeg:error while reading coded/compressed data into packets\n");
1450 }}
1451
1452 /* fill the dec with pkts */
1453 static void dec_fill(void) { loop
1454 {
1455 int r;
1456
1457 if (!dec_frames.have_rejected_pkt) /* already have a pkt to send */
1458 pkt_read();
1459
1460 r = avcodec_send_packet(dec_ctx, pkt);
1461 if (r == AVERROR(EAGAIN)) { /* dec is full and the pkt is rejected */
1462 dec_frames.have_rejected_pkt = true;
1463 break;
1464 } else if (r == 0) {
1465 dec_frames.have_rejected_pkt = false;
1466 if (pkt == 0) /* last pkt */
1467 break;
1468 av_packet_unref(pkt);
1469 continue;
1470 }
1471 FATAL("ffmpeg:error while sending the packet to the decoder\n");
1472 }}
1473 /*
1474 * return true if we have 1 dec_frames.av, false if no more dec_frames.av
1475 * will come
1476 */
1477 static bool dec_frames_get(void) { loop
1478 {
1479 int r;
1480
1481 if (!dec_frames.have_some)
1482 dec_fill();
1483
1484 /* will unref the dec_frames.av bufs for us */
1485 r = avcodec_receive_frames(dec_ctx, dec_frames.av);
1486 if (r == AVERROR(EAGAIN)) {
1487 dec_frames.have_some = false;
1488 continue;
1489 } else if (r == 0) {
1490 dec_frames.have_some = true;
1491 if (dec_frames.av->pts != AV_NOPTS_VALUE)
1492 dec_frames.most_recent_ts = dec_frames.av->pts;
1493 else
1494 dec_frames.most_recent_ts = dec_frames.av->pkt_dts;
1495 return true; /* "return" the current dec_frames.av */
1496 } else if (r == AVERROR_EOF) {
1497 POUT("ffmpeg:last decoder frames reached (receiving)\n");
1498 return false;
1499 }
1500 FATAL("ffmpeg:error while receiving frames from the decoder\n");
1501 }}
1502
1503 static bool filt_frames_get(void) { loop
1504 {
1505 int r;
1506
1507 if (!filt_frames.no_more_dec_frames) {
1508 if(!dec_frames_get()) {
1509 filt_frames.no_more_dec_frames = true;
1510
1511 r = av_buffersrc_add_frame_flags(abufsrc_ctx,
1512 0 , AV_BUFFERSRC_FLAG_PUSH
1513 | AV_BUFFERSRC_FLAG_KEEP_REF);
1514 if (r < 0)
1515 FATAL("ffmpeg:unable to notify the end of data to the filter source audio buffer context\n");
1516 } else {
1517 /*
1518 * the dec_frames bufs will be unref in
1519 * avcodec_receive_frames
1520 */
1521 r = av_buffersrc_add_frame_flags(abufsrc_ctx,
1522 dec_frames.av, AV_BUFFERSRC_FLAG_PUSH
1523 | AV_BUFFERSRC_FLAG_KEEP_REF);
1524 if (r < 0)
1525 FATAL("ffmpeg:unable to submit the decoder frames to the filter source audio buffer context\n");
1526 }
1527 }
1528 /*
1529 * the last dec_frames should switch the filter in draining mode,
1530 * and filt_frames.av won't matter.
1531 */
1532 r = av_buffersink_get_frame(abufsink_ctx, filt_frames.av);
1533 if (r == AVERROR(EAGAIN)) {
1534 /*
1535 * won't happen if no more dec frames are available thanks
1536 * to eof propagation along the processing graph, in theory
1537 */
1538 continue;
1539 } else if (r >= 0) {
1540 filt_frames.pcm_written_uframes_n = 0;
1541 return true;
1542 } else if (r == AVERROR_EOF) {
1543 POUT("ffmpeg:last filter frames reached (getting)\n");
1544 return false;
1545 }
1546 FATAL("ffmpeg:error while getting frames from the filter\n");
1547 }}
1548 #define NO 0
1549 static void chans_buf_init(u8 **chans_buf, int start_frame_idx)
1550 {
1551 int is_planar_fmt;
1552 int sample_bytes_n;
1553
1554 sample_bytes_n = av_get_bytes_per_sample(filt_frames.av->format);
1555
1556 is_planar_fmt = av_frame_fmt_is_planar(filt_frames.av->format);
1557 if (is_planar_fmt == NO) { /* or is pcm interleaved */
1558 int frame_bytes_n;
1559
1560 frame_bytes_n = sample_bytes_n * filt_frames.av->channels;
1561 chans_buf[0] = (u8*)filt_frames.av->data[0] + start_frame_idx
1562 * frame_bytes_n;
1563 } else { /* ffmpeg planar or pcm noninterleaved */
1564 int p;
1565
1566 p = 0;
1567 loop {
1568 if (p == filt_frames.av->channels)
1569 break;
1570 chans_buf[p] = (u8*)filt_frames.av->data[p]
1571 + start_frame_idx * sample_bytes_n;
1572 ++p;
1573 }
1574 }
1575 }
1576 #undef NO
1577 #define NO 0
1578 static void chans_buf_inc(u8 **chans_buf, int frames_inc)
1579 {
1580 int is_planar_fmt;
1581 int sample_bytes_n;
1582
1583 sample_bytes_n = av_get_bytes_per_sample(filt_frames.av->format);
1584
1585 is_planar_fmt = av_frame_fmt_is_planar(filt_frames.av->format);
1586 if (is_planar_fmt == NO) { /* or is pcm interleaved */
1587 int frame_bytes_n;
1588
1589 frame_bytes_n = sample_bytes_n * filt_frames.av->channels;
1590 chans_buf[0] += frames_inc * frame_bytes_n;
1591 } else { /* ffmpeg planar or pcm noninterleaved */
1592 u8 p;
1593
1594 p = 0;
1595 loop {
1596 if (p == filt_frames.av->channels)
1597 break;
1598 chans_buf[p] += frames_inc * sample_bytes_n;
1599 ++p;
1600 }
1601 }
1602 }
1603 #undef NO
1604 #define NO 0
1605 static void pcm_silence_frames_write(snd_pcm_uframes_t uframes_n)
1606 {
1607 int is_planar_fmt;
1608
1609 if (uframes_n == 0)
1610 return;
1611
1612 is_planar_fmt = av_frame_fmt_is_planar(filt_frames.av->format);
1613 if (is_planar_fmt == NO)
1614 (void)snd_pcm_writei(pcm, silence_bufs[0], uframes_n);
1615 else
1616 (void)snd_pcm_writen(pcm, silence_bufs, uframes_n);
1617 }
1618 #undef NO
1619 #define NO 0
1620 static void pcm_filt_frames_write(snd_pcm_uframes_t uframes_n) { loop
1621 {
1622 u8 chan_buf;
1623 u8 *chans_buf[AV_NUM_DATA_POINTERS];
1624 snd_pcm_sframes_t r0;
1625 snd_pcm_uframes_t uframes_to_write_n;
1626 snd_pcm_uframes_t filt_frames_remaining_uframes_n; /* for clarity */
1627
1628 if (uframes_n == 0)
1629 return;
1630
1631 /* create the chan buf pointers */
1632 chans_buf_init(chans_buf, (int)filt_frames.pcm_written_uframes_n);
1633
1634 filt_frames_remaining_uframes_n =
1635 (snd_pcm_uframes_t)filt_frames.av->frames_n
1636 - filt_frames.pcm_written_uframes_n;
1637 if (filt_frames_remaining_uframes_n > uframes_n)
1638 uframes_to_write_n = uframes_n;
1639 else
1640 uframes_to_write_n = filt_frames_remaining_uframes_n;
1641
1642 loop {
1643 int is_planar_fmt;
1644 snd_pcm_uframes_t written_uframes_n;
1645
1646 is_planar_fmt = av_frame_fmt_is_planar(filt_frames.av->format);
1647 if (is_planar_fmt == NO)
1648 r0 = snd_pcm_writei(pcm, chans_buf[0],
1649 uframes_to_write_n);
1650 else
1651 r0 = snd_pcm_writen(pcm, (void**)chans_buf,
1652 uframes_to_write_n);
1653 if (r0 < 0) {
1654 if (r0 == -EAGAIN) /* return to epoll */
1655 return;
1656 else if (r0 == -EPIPE || r0 == -ESTRPIPE) {
1657 /* underrun or suspended */
1658 int r1;
1659
1660 r1 = snd_pcm_recover(pcm, (int)r0, 0);
1661 if (r1 == 0) {
1662 WARNING("alsa:pcm recovered going back to epoll\n");
1663 return; /* recovered, go back to epoll */
1664 }
1665 FATAL("alsa:unable to recover from suspend/underrun\n");
1666 }
1667 FATAL("alsa:fatal/unhandled error while writing the frames\n");
1668 }
1669
1670 /* r0 >= 0 */
1671
1672 written_uframes_n = (snd_pcm_uframes_t)r0;
1673 filt_frames.pcm_written_uframes_n += written_uframes_n;
1674 uframes_n -= written_uframes_n;
1675 uframes_to_write_n -= written_uframes_n;
1676
1677 chans_buf_inc(chans_buf, (int)written_uframes_n);
1678
1679 if (uframes_to_write_n == 0)
1680 break;
1681 }
1682
1683 if (filt_frames.pcm_written_uframes_n
1684 == (snd_pcm_uframes_t)filt_frames.av->frames_n) {
1685 av_frame_unref(filt_frames.av);
1686
1687 if (!filt_frames_get()) { /* no more filt frames ? */
1688 loop { /* spinning */
1689 int r1;
1690
1691 r1 = snd_pcm_drain(pcm);
1692 if (r1 == 0)
1693 break;
1694 if (r1 != -EAGAIN)
1695 FATAL("alsa:an error occured while draining the pcm\n");
1696 }
1697 /**********************************************/
1698 EXIT("finished playing\n");
1699 /**********************************************/
1700 }
1701 }
1702 }}
1703 #undef NO
1704 static void init_ffmpeg_filter_abufsrc(void)
1705 {
1706 int r;
1707 AVRational time_base;
1708 u8 chans_layout[STR_SZ]; /* should be overkill */
1709
1710 abufsrc = avfilter_get_by_name("abuffer");
1711 if (abufsrc == 0)
1712 FATAL("ffmpeg:input audio buffer:could not find the filter\n");
1713
1714 abufsrc_ctx = avfilter_graph_alloc_filter(filter_graph, abufsrc,
1715 "src_abuf");
1716 if (abufsrc_ctx == 0)
1717 FATAL("ffmpeg:input audio buffer:could not allocate the instance in the filter graph\n");
1718
1719 r = av_opt_set(abufsrc_ctx, "sample_fmt",
1720 av_get_frame_fmt_name(dec_ctx->frame_fmt),
1721 AV_OPT_SEARCH_CHILDREN);
1722 if (r < 0)
1723 FATAL("ffmpeg:input audio buffer context:unable to set the decoder frame format option\n");
1724
1725 time_base.num = 1;
1726 time_base.den = dec_ctx->frame_rate;
1727 r = av_opt_set_q(abufsrc_ctx, "time_base", time_base,
1728 AV_OPT_SEARCH_CHILDREN);
1729 if (r < 0)
1730 FATAL("ffmpeg:input audio buffer context:unable to set the decoder time base (1 / rate) option\n");
1731
1732 r = av_opt_set_int(abufsrc_ctx, "sample_rate", dec_ctx->frame_rate,
1733 AV_OPT_SEARCH_CHILDREN);
1734 if (r < 0)
1735 FATAL("ffmpeg:input audio buffer context:unable to set the decoder rate option\n");
1736 /*
1737 * XXX: at the time of coding, bug for 1 channel layout... or I did miss
1738 * some valuable information
1739 */
1740 av_get_channel_layout_string(chans_layout, sizeof(chans_layout), dec_ctx->channels,
1741 dec_ctx->channel_layout);
1742 POUT("ffmpeg:input audio buffer:using channels layout \"%s\" (%d pcm channels)\n", chans_layout, dec_ctx->channels);
1743 r = av_opt_set(abufsrc_ctx, "channel_layout", chans_layout,
1744 AV_OPT_SEARCH_CHILDREN);
1745 if (r < 0)
1746 FATAL("ffmpeg:input audio buffer context:unable to set the decoder channel layout option\n");
1747
1748 r = avfilter_init_str(abufsrc_ctx, 0);
1749 if (r < 0)
1750 FATAL("ffmpeg:input audio buffer context:unable to initialize\n");
1751 }
1752
1753 static void init_ffmpeg_filter_vol(void)
1754 {
1755 int r;
1756 u8 vol_l10n_str[sizeof("xxx.xx")]; /* should be overkill */
1757
1758 vol_filt = avfilter_get_by_name("volume");
1759 if (vol_filt == 0)
1760 FATAL("ffmpeg:volume:could not find the filter\n");
1761
1762 vol_ctx = avfilter_graph_alloc_filter(filter_graph, vol_filt, "vol");
1763 if (vol_ctx == 0)
1764 FATAL("ffmpeg:volume context:could not allocate the instance in the filter graph\n");
1765
1766 if (initial_vol == -1.0)
1767 filt_frames.vol = 1.0;
1768 else
1769 filt_frames.vol = initial_vol;
1770 snprintf(vol_l10n_str, sizeof(vol_l10n_str), "%f", filt_frames.vol);
1771 r = av_opt_set(vol_ctx, "volume", vol_l10n_str, AV_OPT_SEARCH_CHILDREN);
1772 if (r < 0)
1773 FATAL("ffmpeg:volume context:unable to set the volume option\n");
1774
1775 r = avfilter_init_str(vol_ctx, 0);
1776 if (r < 0)
1777 FATAL("ffmpeg:volume buffer context:unable to initialize\n");
1778 }
1779 /*
1780 * XXX: if it is ever used significantly, a fine granularity wiring strategy
1781 * will be implemented instead of using the default wiring
1782 */
1783 static uint64_t pcm_chmaps2ffmpeg_chans_layout(u8 *str, u8 str_sz)
1784 {
1785 int r;
1786 uint64_t ffmpeg_chans_layout;
1787 snd_pcm_chmap_t *pcm_map;
1788 int pcm_chans_n;
1789
1790 r = snd_pcm_hw_params_get_channels(pcm_hw_params, &pcm_chans_n);
1791 if (r < 0)
1792 FATAL("alsa:unable to get the pcm count of channels for ffmpeg channel layout mapping\n");
1793
1794 pcm_map = snd_pcm_get_chmap(pcm);
1795 if (pcm_map == 0) {
1796 POUT("alsa:no pcm channel map available, wiring to default ffmpeg channel layout\n");
1797 } else {
1798 POUT("alsa:your pcm device support channel maps, but fine granularity wiring strategy is not implemented\n");
1799 free(pcm_map);
1800 }
1801 ffmpeg_chans_layout = av_get_default_channel_layout(pcm_chans_n);
1802 av_get_channel_layout_string(str, str_sz, pcm_chans_n,
1803 ffmpeg_chans_layout);
1804 POUT("alsa channel map wired to ffmpeg channel layout:\"%s\" (%d pcm channels)\n", str, pcm_chans_n);
1805 }
1806
1807 static void init_ffmpeg_filter_afmt(void)
1808 {
1809 int r;
1810 snd_pcm_format_t pcm_fmt;
1811 snd_pcm_access_t pcm_access;
1812 enum AVFrameFormat ffmpeg_fmt;
1813 int rate;
1814 u8 rate_str[sizeof("dddddd")];
1815 u8 ffmpeg_chans_layout_str[STR_SZ]; /* should be overkill */
1816
1817 afmt = avfilter_get_by_name("aformat");
1818 if (afmt == 0)
1819 FATAL("ffmpeg:audio format:could not find the filter");
1820
1821 afmt_ctx = avfilter_graph_alloc_filter(filter_graph, afmt, "afmt");
1822 if (afmt_ctx == 0)
1823 FATAL("ffmpeg:audio format:could not allocate the instance in the filter graph\n");
1824 /*--------------------------------------------------------------------*/
1825 r = snd_pcm_hw_params_get_access(pcm_hw_params, &pcm_access);
1826 if (r < 0)
1827 FATAL("alsa:unable to get the pcm access for ffmpeg filter wiring\n");
1828
1829 r = snd_pcm_hw_params_get_format(pcm_hw_params, &pcm_fmt);
1830 if (r < 0)
1831 FATAL("alsa:unable to get the pcm format for ffmpeg filter wiring\n");
1832 /**********************************************************************/
1833 pcm_layout2ffmpeg_fmt_strict(pcm_fmt, pcm_access, &ffmpeg_fmt);
1834 /**********************************************************************/
1835 r = av_opt_set(afmt_ctx, "sample_fmts",
1836 av_get_frame_fmt_name(ffmpeg_fmt), AV_OPT_SEARCH_CHILDREN);
1837 if (r < 0)
1838 FATAL("ffmpeg:audio format context:could to set the pcm sample format\n");
1839 /*--------------------------------------------------------------------*/
1840 r = snd_pcm_hw_params_get_rate(pcm_hw_params, &rate,
1841 SND_PCM_STREAM_PLAYBACK);
1842 if (r < 0)
1843 FATAL("alsa:unable to get the pcm rate for ffmpeg filter wiring\n");
1844
1845 snprintf(rate_str, sizeof(rate_str), "%d", rate);
1846 r = av_opt_set(afmt_ctx, "sample_rates", rate_str,
1847 AV_OPT_SEARCH_CHILDREN);
1848 if (r < 0)
1849 FATAL("ffmpeg:audio format context:could not set the pcm rate\n");
1850 /**********************************************************************/
1851 pcm_chmaps2ffmpeg_chans_layout(ffmpeg_chans_layout_str,
1852 sizeof(ffmpeg_chans_layout_str));
1853 /**********************************************************************/
1854 r = av_opt_set(afmt_ctx, "channel_layouts", ffmpeg_chans_layout_str,
1855 AV_OPT_SEARCH_CHILDREN);
1856 if (r < 0)
1857 FATAL("ffmpeg:audio format context:could not set the layout of channels\n");
1858 POUT("ffmpeg:audio format context:channel layout is \"%s\"\n", ffmpeg_chans_layout_str);
1859
1860 r = avfilter_init_str(afmt_ctx, 0);
1861 if (r < 0)
1862 FATAL("ffmpeg:audio format context:unable to initialize\n");
1863 }
1864
1865 static void init_ffmpeg_filter_abufsink(void)
1866 {
1867 int r;
1868
1869 abufsink = avfilter_get_by_name("abuffersink");
1870 if (abufsink == 0)
1871 FATAL("ffmpeg:abufsink:could not find the filter\n");
1872
1873 abufsink_ctx = avfilter_graph_alloc_filter(filter_graph, abufsink, "sink_abuf");
1874 if (abufsink_ctx == 0)
1875 FATAL("ffmpeg:abufsink:could not allocate the instance in the filter graph\n");
1876
1877 r = avfilter_init_str(abufsink_ctx, 0);
1878 if (r < 0)
1879 FATAL("ffmpeg:audio format context:unable to initialize\n");
1880 }
1881 /*
1882 * see ffmpeg doc/examples/filter_audio.c, here we are looking for interactive
1883 * latency
1884 */
1885 static void init_ffmpeg_filter_graph(void)
1886 {
1887 int r;
1888 char *dump_str;
1889
1890 memset(&filt_frames, 0, sizeof(filt_frames));
1891
1892 filter_graph = avfilter_graph_alloc();
1893 if (filter_graph == 0)
1894 FATAL("ffmpeg:unable to create filter graph\n");
1895
1896 init_ffmpeg_filter_abufsrc();
1897 init_ffmpeg_filter_vol();
1898 init_ffmpeg_filter_afmt();
1899 init_ffmpeg_filter_abufsink();
1900 /* connections of filters */
1901 r = avfilter_link(abufsrc_ctx, 0, vol_ctx, 0);
1902 if (r < 0)
1903 FATAL("ffmpeg:unable to connect the audio buffer filter to the volume filter\n");
1904 r = avfilter_link(vol_ctx, 0, afmt_ctx, 0);
1905 if (r < 0)
1906 FATAL("ffmpeg:unable to connect the volume filter to the audio format filter\n");
1907 r = avfilter_link(afmt_ctx, 0, abufsink_ctx, 0);
1908 if (r < 0)
1909 FATAL("ffmpeg:unable to connect the audio format filter to the sink audio buffer\n");
1910 /* configure the graph */
1911 r = avfilter_graph_config(filter_graph, 0);
1912 if (r < 0)
1913 FATAL("ffmpeg:unable to configure the filter graph\n");
1914
1915 dump_str = avfilter_graph_dump(filter_graph, 0);
1916 if (dump_str == 0) {
1917 WARNING("ffmpeg:unable to get a filter graph descriptions\n");
1918 return;
1919 }
1920 POUT("FFMPEG FILTER GRAPH START-------------------------------------------------------\n");
1921 POUT(dump_str);
1922 POUT("FFMPEG FILTER GRAPH END---------------------------------------------------------\n");
1923
1924 filt_frames.av = av_frames_alloc();
1925 if (filt_frames.av == 0)
1926 FATAL("ffmpeg:unable to allocate a filtered frames structure\n");
1927 /* floating point strs are localized... */
1928 snprintf(float_zero_l10n_str, sizeof(float_zero_l10n_str), "%f", 0.0);
1929 }
1930
1931 static void evt_pcm_write(void)
1932 {
1933 snd_pcm_sframes_t r0;
1934 /* try only 2 times */
1935 r0 = snd_pcm_avail(pcm);
1936 if (r0 < 0) {
1937 if (r0 == -EPIPE || r0 == -ESTRPIPE) {
1938 /* underrun or suspended */
1939 int r1;
1940
1941 r1 = snd_pcm_recover(pcm, (int)r0, 0);
1942 if (r1 == 0) {
1943 WARNING("alsa:pcm recovered retrying to get some available frames\n");
1944 r0 = snd_pcm_avail(pcm);
1945 if (r0 < 0)
1946 FATAL("alsa:unable to get some available frames after recovery\n");
1947 } else
1948 FATAL("alsa:unable to recover from suspend/underrun\n");
1949 } else
1950 FATAL("alsa:error getting some available frames\n");
1951 }
1952
1953 if (paused)
1954 pcm_silence_frames_write((snd_pcm_uframes_t)r0);
1955 else
1956 pcm_filt_frames_write((snd_pcm_uframes_t)r0);
1957 }
1958 #define EPOLL_EVTS_N 8 /* why not */
1959 static void evts_loop(void)
1960 {
1961 int fds_n;
1962 int fd_idx;
1963 struct epoll_event evts[EPOLL_EVTS_N];
1964 bool pcm_evt;
1965 int r;
1966 short pcm_evts;
1967
1968 errno = 0;
1969 memset(evts, 0, sizeof(evts));
1970 fds_n = epoll_wait(ep_fd, evts, EPOLL_EVTS_N, -1);
1971 if (fds_n == -1) {
1972 if (errno == EINTR) {
1973 WARNING("event loop wait:was interrupted by a signal\n");
1974 return;
1975 }
1976 FATAL("event loop wait:an error occured\n");
1977 }
1978
1979 pcm_evt = false;
1980 fd_idx = 0;
1981 loop {
1982 if (fd_idx == fds_n)
1983 break;
1984 evt_handle(&evts[fd_idx], &pcm_evt);
1985 ++fd_idx;
1986 }
1987
1988 if (!pcm_evt)
1989 return;
1990 /*
1991 * since alsa could use several file descriptors, only once the
1992 * pollfds were properly updated we can actually know we got
1993 * something from alsa
1994 */
1995 r = snd_pcm_poll_descriptors_revents(pcm, pcm_pollfds, pcm_pollfds_n,
1996 &pcm_evts);
1997 if (r != 0)
1998 FATAL("alsa:error processing the poll file descriptors\n");
1999
2000 if ((pcm_evts & ~POLLOUT) != 0)
2001 FATAL("alsa:unexpected events\n");
2002
2003 if ((pcm_evts & POLLOUT) != 0)
2004 evt_pcm_write();
2005 }
2006 #undef EPOLL_EVTS_N
2007 /* use the strict filter ffmpeg format<->pcm layout wiring */
2008 #define NO 0
2009 static void init_silence_bufs(void)
2010 {
2011 int r;
2012 snd_pcm_uframes_t pcm_buf_uframes_n_max;
2013 uint64_t chans_layout;
2014 int chans_n;
2015 enum AVFrameFormat fmt;
2016 int sample_bytes_n;
2017 int is_planar_fmt;
2018
2019 r = snd_pcm_hw_params_get_buffer_size(pcm_hw_params,
2020 &pcm_buf_uframes_n_max);
2021 if (r < 0)
2022 FATAL("silence:unable to get the pcm audio buffer size\n");
2023 /*
2024 * from here, we switch to ffmpeg since we will use the ffmpeg
2025 * silence creation function
2026 */
2027 chans_layout = av_buffersink_get_channel_layout(abufsink_ctx);
2028 chans_n = av_get_channel_layout_nb_channels(chans_layout);
2029 fmt = (enum AVFrameFormat)av_buffersink_get_format(abufsink_ctx);
2030
2031 sample_bytes_n = av_get_bytes_per_sample(fmt);
2032
2033 is_planar_fmt = av_frame_fmt_is_planar(fmt);
2034 if (is_planar_fmt == NO) { /* or is pcm interleaved */
2035 int frame_bytes_n;
2036
2037 frame_bytes_n = sample_bytes_n * chans_n;
2038
2039 silence_bufs[0] = malloc(pcm_buf_uframes_n_max * frame_bytes_n);
2040 if (silence_bufs[0] == 0)
2041 FATAL("silence:non planar format:unable to allocate the silence buffer of %u bytes\n", pcm_buf_uframes_n_max * frame_bytes_n);
2042 POUT("silence:non planar format:buffer of %u bytes is allocated\n", pcm_buf_uframes_n_max * frame_bytes_n);
2043 (void)av_frames_set_silence((uint8_t**)silence_bufs, 0,
2044 (int)pcm_buf_uframes_n_max, chans_n, fmt);
2045 POUT("silence:non planar format:silence buffer filled with %u silence frames\n", pcm_buf_uframes_n_max);
2046 } else { /* ffmpeg planar or pcm noninterleaved */
2047 u8 p;
2048
2049 p = 0;
2050 loop {
2051 if (p == chans_n)
2052 break;
2053
2054 silence_bufs[p] = malloc(pcm_buf_uframes_n_max
2055 * sample_bytes_n);
2056 if (silence_bufs[p] == 0)
2057 FATAL("silence:planar format:unable to allocate silence buffer %u of %u bytes\n", p, pcm_buf_uframes_n_max * sample_bytes_n);
2058 POUT("silence:planar format:buffer[%u] of %u bytes is allocated\n", p, pcm_buf_uframes_n_max * sample_bytes_n);
2059 ++p;
2060 }
2061 (void)av_frames_set_silence((uint8_t**)silence_bufs, 0,
2062 (int)pcm_buf_uframes_n_max, chans_n, fmt);
2063 POUT("silence:planar format:allocated %u silence buffers for %u frames\n", chans_n, pcm_buf_uframes_n_max);
2064 }
2065 }
2066 #undef NO
2067 static void ffmpeg_log_stdout(void *a, int b, const char *fmt, va_list ap)
2068 {
2069 vprintf(fmt, ap);
2070 }
2071
2072 static void usage(void)
2073 {
2074 POUT("npaf [-v volume(0..100)] [-h] file_path\n");
2075 }
2076
2077 static void opts_parse(int argc, u8 **args)
2078 {
2079 int i;
2080 int file_path_idx;
2081
2082 i = 1;
2083 file_path_idx = -1;
2084 initial_vol = -1.0;
2085 loop {
2086 if (i == argc)
2087 break;
2088
2089 if (strcmp("-v", args[i]) == 0) {
2090 unsigned long vol;
2091
2092 if ((i + 1) == argc)
2093 FATAL("-v:initial volume option is missing\n");
2094
2095 vol = strtoul(args[i + 1], 0, 10);
2096 if (vol < 0 || 100 < vol)
2097 FATAL("-v:invalid volume value %lu (0..100)\n", vol);
2098
2099 initial_vol = (f32)vol / 100.0;
2100 POUT("-v:using initial volume %f\n", initial_vol);
2101 i += 2;
2102 } else if (strcmp("-h", args[i]) == 0) {
2103 usage();
2104 exit(0);
2105 } else {
2106 file_path_idx = i;
2107 ++i;
2108 }
2109 }
2110
2111 if (file_path_idx == -1)
2112 FATAL("missing file path\n");
2113
2114 file_path = args[file_path_idx];
2115 file_url = apf("file:%s", file_path);
2116
2117 POUT("playing ####%s####\n", file_url);
2118 }
2119
2120 static void init(int argc, u8 **args)
2121 {
2122 stdin_tty_conf_modified = false;
2123
2124 opts_parse(argc, args);
2125 init_sigs();
2126 /* av_log_set_level(AV_LOG_VERBOSE); */
2127 /* av_log_set_level(AV_LOG_DEBUG); */
2128 init_ffmpeg_dec();
2129 init_alsa();
2130 init_stdin_tty();
2131 init_stdin_flags();
2132 init_stdout();
2133 init_input_state();
2134 pcm_conf();
2135 init_ffmpeg_filter_graph();
2136 init_silence_bufs();
2137 if (!filt_frames_get())
2138 FATAL("ffmpeg:unable to get first frames from the filter\n");
2139 init_epoll();
2140
2141 /* switch the ffmpeg log to stdout for metadata/etc dump */
2142 av_log_set_callback(ffmpeg_log_stdout);
2143 av_dump_format(fmt_ctx, 0, file_url, 0);
2144 av_log_set_callback(av_log_default_callback);
2145 }
2146
2147 int main(int argc, u8 **args)
2148 {
2149 /* "turn on utf8" processing in used libs if any *AND* locale system */
2150 setlocale(LC_ALL, "");
2151
2152 init(argc, args);
2153 loop evts_loop();
2154 /* unreachable */
2155 }
2156 /*----------------------------------------------------------------------------*/
2157 #undef ARRAY_N
2158 #undef av_frames_alloc
2159 #undef av_frame_fmt_is_planar
2160 #undef av_frames_set_silence
2161 #undef av_get_frame_fmt_name
2162 #undef av_get_frame_fmt_string
2163 #undef av_read_pkt
2164 #undef av_seek_pkt
2165 #undef avcodec_receive_frames
2166 #undef AVFrameFormat
2167 #undef AVFrames
2168 #undef esc_seq_sz
2169 #undef EXIT
2170 #undef f32
2171 #undef FATAL
2172 #undef frame_fmt
2173 #undef frame_rate
2174 #undef frames_n
2175 #undef INPUT_ESC_SEQ_TIMEOUT_SECS_N
2176 #undef loop
2177 #undef PCM_POLLFDS_N_MAX
2178 #undef PERR
2179 #undef POUT
2180 #undef SEEK_DELTA
2181 #undef SEEK_DELTA_BIG
2182 #undef STR_SZ
2183 #undef u16
2184 #undef u32
2185 #undef u8
2186 #undef VOL_DELTA
2187 #undef WARNING
2188 #endif
File npaf_config.h added (mode: 100644) (index 0000000..3cb5d0b)
1 /*
2 * only basic esc seq soup for keyboard input, "ansi/ecma-48 + VTXXX + xterm"
3 * utf8 terminal configured for 2 bytes c1 codes, hence the csi is the esc
4 * ascii byte followed by the '[' ascii char. to account for any unknown esc
5 * seq, we have a global timeout.
6 * you have very good documentation on VTXXX (52/100/5xx) on the web, that
7 * to understand well what is a terminal and "where" all that comes
8 * from.
9 *
10 * or 1 utf8 char (up to 4 bytes and not 6 like in legacy iso)
11 */
12 struct tty_bind_t {
13 u8 *c; /* utf8 char, up to 4 bytes, or esc seq */
14 u8 sz; /* size of whatever is c */
15 u8 *name; /* friendly name */
16 void (*cmd)(void); /* bound cmd */
17 };
18 #define INPUT_ESC_SEQ_TIMEOUT_SECS_N 1
19 #define TTY_BIND(x, y, z) {.c = x, .sz = (sizeof(x) - 1), .name = y, .cmd = z}
20 struct tty_bind_t tty_binds[] = {
21 TTY_BIND("q", "letter q", cmd_quit),
22 TTY_BIND("i", "letter i", cmd_info),
23 TTY_BIND("\x1b[D", "arrow left", cmd_rewind),
24 TTY_BIND("\x1b[C", "arrow right", cmd_fastforward),
25 TTY_BIND("\x1b[A", "arrow up", cmd_vol_up),
26 TTY_BIND("\x1b[B", "arrow down", cmd_vol_down),
27 TTY_BIND("\x1b[H", "home", cmd_rewind_big),
28 TTY_BIND("\x1b[4~", "end", cmd_fastforward_big),
29 TTY_BIND("m", "letter m", cmd_mute),
30 TTY_BIND(" ", "space", cmd_pause)
31 };
32 #undef TTY_BIND
33
34 #define VOL_DELTA 0.02 /* from 0.0 to 1.0 */
35 #define SEEK_DELTA INT64_C(10) /* 10 seconds */
36 #define SEEK_DELTA_BIG (INT64_C(4) * INT64_C(60)) /* 4 minutes */
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/sylware/nyanmp

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/sylware/nyanmp

Clone this repository using git:
git clone git://git.rocketgit.com/user/sylware/nyanmp

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main