3 static u8
*ts_to_str(int64_t ts
, avutil_rational_t time_base
,
6 static u8 str
[sizeof("~S00:00:00.000 remains S9223372036854775807 time base units")];
12 int64_t one_hour
; /* in ffmpeg time_base units */
13 int64_t one_min
; /* in ffmpeg time_base units */
14 int64_t one_sec
; /* in ffmpeg time_base units */
15 int64_t one_msec
; /* in ffmpeg time_base units */
23 one_hour
= INT64_C(3600) * (int64_t)time_base
.den
24 / (int64_t)time_base
.num
;
25 one_min
= INT64_C(60) * (int64_t)time_base
.den
26 / (int64_t)time_base
.num
;
27 one_sec
= (int64_t)time_base
.den
/ (int64_t)time_base
.num
;
28 one_msec
= one_sec
/ INT64_C(1000);
30 hours_n
= ts
/ one_hour
;
32 *remaining
= ts
% one_hour
;
33 mins_n
= *remaining
/ one_min
;
35 *remaining
= *remaining
% one_min
;
36 secs_n
= *remaining
/ one_sec
;
38 *remaining
= *remaining
% one_sec
;
39 msecs_n
= *remaining
/ one_msec
;
41 /* account for all rounding errors */
42 *remaining
= ts
- (hours_n
* one_hour
+ mins_n
* one_min
43 + secs_n
* one_sec
+ msecs_n
* one_msec
);
45 snprintf(str
, sizeof(str
), "%02"PRId64
":%02"PRId64
":%02"PRId64
".%03"PRId64
, hours_n
, mins_n
, secs_n
, msecs_n
);
48 snprintf(str
+ 1, sizeof(str
) - 1, "%02"PRId64
":%02"PRId64
":%02"PRId64
".%03"PRId64
, hours_n
, mins_n
, secs_n
, msecs_n
);
52 /*--------------------------------------------------------------------------*/
54 * block as much as possible.
55 * handle only async "usual" sigs, with sync signalfd.
56 * allow some signals to go thru though. many sync signal can only
57 * be handles with an async handler.
58 * always presume the process "controlling terminal" is different than the
59 * terminal connected on standard input and standard output
62 static void sigs_init_once(void)
67 r
= sigfillset(&sset
);
69 FATAL("unable to get a full signal mask\n");
71 /* the "controlling terminal" line asks for a core dump, leave it be */
72 r
= sigdelset(&sset
, SIGQUIT
);
74 FATAL("unable to remove SIGQUIT from our signal mask\n");
76 r
= pthread_sigmask(SIG_SETMASK
, &sset
, 0);
78 FATAL("unable to \"block\" \"all\" signals\n");
80 /* from here, we "steal" signals with signalfd */
82 r
= sigemptyset(&sset
);
84 FATAL("unable to get an empty signal mask\n");
85 /* we are asked nicely to terminate */
86 r
= sigaddset(&sset
, SIGTERM
);
88 FATAL("unable to add SIGTERM to our signal mask\n");
89 /* the "controlling terminal" line (^c) asks nicely to terminate */
90 r
= sigaddset(&sset
, SIGINT
);
92 FATAL("unable to add SIGINT to our signal mask\n");
94 r
= signalfd(-1, &sset
, SFD_NONBLOCK
);
96 FATAL("unable to get a signalfd file descriptor\n");
100 static void evt_init_once(void)
102 ep_fd_p
= epoll_create1(0);
104 FATAL("unable to create the epoll file descriptor\n");
107 static void evt_add_all_fds(void)
111 struct epoll_event evt
;
114 evt
.events
= EPOLLIN
;
115 evt
.data
.fd
= sig_fd_l
;
116 r
= epoll_ctl(ep_fd_p
, EPOLL_CTL_ADD
, sig_fd_l
, &evt
);
118 FATAL("unable to add the signalfd file descriptior to the epoll file descriptor\n");
119 /*--------------------------------------------------------------------*/
120 ///* the input timer */
121 //evt.events = EPOLLIN;
122 //evt.data.fd = input_timer_fd_p;
123 //r = epoll_ctl(ep_fd_p, EPOLL_CTL_ADD, input_timer_fd_p, &evt);
125 // FATAL("unable to add the input timer file descriptor\n");
126 /*--------------------------------------------------------------------*/
127 /* the video timer */
128 evt
.events
= EPOLLIN
;
129 evt
.data
.fd
= video_timer_fd_p
;
130 r
= epoll_ctl(ep_fd_p
, EPOLL_CTL_ADD
, video_timer_fd_p
, &evt
);
132 FATAL("unable to add the video timer file descriptor\n");
133 /*--------------------------------------------------------------------*/
134 /* the x11 xcb file descriptor */
135 evt
.events
= EPOLLIN
;
136 evt
.data
.fd
= npv_xcb_p
.fd
;
137 r
= epoll_ctl(ep_fd_p
, EPOLL_CTL_ADD
, npv_xcb_p
.fd
, &evt
);
139 FATAL("unable to add the x11 xcb file descriptor\n");
140 /*--------------------------------------------------------------------*/
141 /* alsa pcm poll file descriptors */
144 if (i
== audio_pcm_pollfds_n_p
)
146 evt
.events
= audio_pcm_pollfds_p
[i
].events
;
147 evt
.data
.fd
= audio_pcm_pollfds_p
[i
].fd
;
148 r
= epoll_ctl(ep_fd_p
, EPOLL_CTL_ADD
, audio_pcm_pollfds_p
[i
].fd
,
151 FATAL("unable to add alsa poll file descriptor index %d to epoll file descriptor\n", i
);
154 /*--------------------------------------------------------------------*/
155 /* the draining timer */
156 evt
.events
= EPOLLIN
;
157 evt
.data
.fd
= audio_draining_timer_fd_p
;
158 r
= epoll_ctl(ep_fd_p
, EPOLL_CTL_ADD
, audio_draining_timer_fd_p
, &evt
);
160 FATAL("unable to add the draining timer file descriptor\n");
163 static void evt_sigs(void)
166 struct signalfd_siginfo siginfo
;
169 r
= read(sig_fd_l
, &siginfo
, sizeof(siginfo
));
170 if (r
!= sizeof(siginfo
))
171 FATAL("unable to read signal information\n");
173 switch (siginfo
.ssi_signo
) {
175 EXIT("received SIGTERM\n");
177 EXIT("received SIGINT\n");
179 WARNING("signal handle:unwanted signal %d received, discarding\n", siginfo
.ssi_signo
);
183 static void evt_accumulate(struct epoll_event
*evt
, bool *have_evt_sigs
,
184 bool *have_evt_pcm
, bool *have_evt_video
, bool *have_evt_pcm_draining
,
185 bool *have_evt_x11
) //bool *have_evt_input)
189 if (evt
->data
.fd
== sig_fd_l
) {
190 if ((evt
->events
& EPOLLIN
) != 0) {
191 *have_evt_sigs
= true;
194 FATAL("event loop wait:signal:unexpected event\n");
196 /*-------------------------------------------------------------------*/
197 /* only update alsa fds */
200 if (i
== audio_pcm_pollfds_n_p
)
202 if (evt
->data
.fd
== audio_pcm_pollfds_p
[i
].fd
) {
203 audio_pcm_pollfds_p
[i
].revents
= evt
->events
;
204 *have_evt_pcm
= true;
209 /*-------------------------------------------------------------------*/
210 if (evt
->data
.fd
== npv_xcb_p
.fd
) {
211 if ((evt
->events
& EPOLLIN
) != 0)
212 *have_evt_x11
= true;
215 /*-------------------------------------------------------------------*/
216 if (evt
->data
.fd
== video_timer_fd_p
) {
217 if ((evt
->events
& EPOLLIN
) != 0) {
218 *have_evt_video
= true;
221 FATAL("event loop wait:video:unexpected event\n");
223 /*-------------------------------------------------------------------*/
224 //if (evt->data.fd == input_timer_fd_p) {
225 // if ((evt->events & EPOLLIN) != 0) {
226 // *have_evt_input = true;
229 // FATAL("event loop wait:input:unexpected event\n");
231 /*-------------------------------------------------------------------*/
232 if (evt
->data
.fd
== audio_draining_timer_fd_p
) {
233 if ((evt
->events
& EPOLLIN
) != 0) {
234 *have_evt_pcm_draining
= true;
237 FATAL("event loop wait:audio draining timer:unexpected event\n");
241 * XXX: remember that all heavy lifting should be done in other threads.
242 * this thread should not "block" or perform "expensive" work.
243 * "blocking", "expensive" work should be offloaded to other threads.
245 #define EPOLL_EVTS_N 32 /* why not */
247 static void evts_loop(void)
251 struct epoll_event evts
[EPOLL_EVTS_N
];
255 bool have_evt_pcm_draining
;
257 //bool have_evt_input;
262 memset(evts
, 0, sizeof(evts
));
263 fds_n
= epoll_wait(ep_fd_p
, evts
, EPOLL_EVTS_N
, -1);
265 if (errno
== EINTR
) {
266 WARNING("event loop wait:was interrupted by a signal\n");
269 FATAL("event loop wait:an error occured\n");
272 have_evt_sigs
= false;
273 have_evt_pcm
= false;
274 have_evt_video
= false;
275 have_evt_pcm_draining
= false;
276 have_evt_x11
= false;
277 //have_evt_input = false;
283 evt_accumulate(&evts
[fd_idx
], &have_evt_sigs
, &have_evt_pcm
,
284 &have_evt_video
, &have_evt_pcm_draining
,
290 /* once we have our evts, we use a sort of priority order */
292 /* process any q-ed and we-handle sigs before anything */
296 * XXX: it may be more appropriate to break this in 2 steps: key inputs
297 * (light processing), wins resizing (heavy processing)
299 /* key input and win resizing */
302 /* XXX: once in audio draining mode, this should not really happen */
303 /* we are more sensitive to audio issues than video issues */
306 * since alsa could use several file descriptors, only once the
307 * pollfds were properly updated we can actually know we got
308 * something from alsa
310 r
= snd_pcm_poll_descriptors_revents(audio_pcm_p
,
311 audio_pcm_pollfds_p
, audio_pcm_pollfds_n_p
, &pcm_evts
);
313 FATAL("alsa:error processing the poll file descriptors\n");
315 if ((pcm_evts
& ~POLLOUT
) != 0)
316 FATAL("alsa:unexpected events\n");
318 if ((pcm_evts
& POLLOUT
) != 0)
319 audio_evt_pcm_write();
323 //if (have_evt_input)
324 // input_timer_evt();
325 /* while audio is draining, video fr may need to be displayed */
326 if (have_evt_pcm_draining
)
327 audio_draining_state_evt();
331 static void ff_log_stdout(void *a
, int b
, const char *fmt
, va_list ap
)
336 static void usage(void)
339 npv [-p alsa pcm] [-v volume(0..100)] [-h height in pixels] [-w width in pixels]\
340 [-b packet buffer prefill wait(0..100)] [-help] url\n");
343 static void opts_parse(int argc
, u8
**args
, u8
**url
, u16
*w
, u16
*h
,
344 u8
**pcm_str
, double *vol
, u8
*pkts_prefill_percent
)
355 if (strcmp("-p", args
[i
]) == 0) {
356 unsigned long vol_ul
;
359 FATAL("-p:alsa pcm is missing\n");
361 *pcm_str
= args
[i
+ 1];
362 POUT("-p:alsa pcm \"%f\"\n", *pcm_str
);
364 } else if (strcmp("-v", args
[i
]) == 0) {
365 unsigned long vol_ul
;
368 FATAL("-v:initial volume is missing\n");
370 vol_ul
= strtoul(args
[i
+ 1], 0, 10);
371 if (vol_ul
< 0 || 100 < vol_ul
)
372 FATAL("-v:invalid volume value %lu (0..100)\n", vol_ul
);
374 *vol
= (double)vol_ul
/ 100.;
375 POUT("-v:using initial volume %f\n", *vol
);
377 } else if (strcmp("-h", args
[i
]) == 0) {
381 FATAL("-h:initial window pixel height is missing\n");
383 h_ul
= strtoul(args
[i
+ 1], 0, 10);
384 if (h_ul
== 0 || h_ul
> U16_MAX
)
385 FATAL("-h:invalid window pixel height %lu (1..%lu)\n", h_ul
, U16_MAX
);
387 POUT("-h:using initial window height %lu pixels\n", h_ul
);
389 } else if (strcmp("-w", args
[i
]) == 0) {
393 FATAL("-h:initial window pixel width is missing\n");
395 w_ul
= strtoul(args
[i
+ 1], 0, 10);
396 if (w_ul
== 0 || w_ul
> U16_MAX
)
397 FATAL("-w:invalid window pixel width %lu (1..%lu)\n", w_ul
, U16_MAX
);
399 POUT("-w:using initial window width %lu pixels\n", w_ul
);
401 } else if (strcmp("-b", args
[i
]) == 0) {
403 FATAL("-b:percent value for prefill of buffer of packet queues is missing\n");
405 *pkts_prefill_percent
= (u8
)strtoul(args
[i
+ 1], 0, 10);
406 POUT("-v:using a %u prefilled buffer of packet queues\n", *pkts_prefill_percent
);
408 } else if (strcmp("-help", args
[i
]) == 0) {
417 FATAL("missing url\n");
418 *url
= args
[url_idx
];
419 POUT("url-->####%s####\n", *url
);
422 #define WIDTH_NOT_DEFINED 0
423 #define HEIGHT_NOT_DEFINED 0
424 static void init_once(u8
*url
, u16 win_width
, u16 win_height
, u8
*pcm_str
,
425 u8 pkts_prefill_percent
, avcodec_params_t
**audio_codec_params
,
426 avcodec_params_t
**video_codec_params
)
428 avutil_rational_t
*audio_st_tb
;
429 avutil_rational_t
*video_st_tb
;
433 npv_vk_init_once(); /* generic plumbing */
434 audio_filt_init_once();
435 audio_init_once(pcm_str
); /* before audio_st_idx_p is actually used */
436 video_init_once(); /* before video_st_idx_p is actually used */
438 pipeline_init_once(pkts_prefill_percent
);
441 /* we need something to start with */
443 &video_st_p
.idx
, &video_st_p
.id
, &video_st_tb
,
444 &video_st_p
.start_time
, video_codec_params
,
445 &audio_st_p
.idx
, &audio_st_p
.id
, &audio_st_tb
,
446 &audio_st_p
.start_time
, audio_codec_params
);
447 memcpy(&audio_st_p
.tb
, audio_st_tb
, sizeof(*audio_st_tb
));
448 memcpy(&video_st_p
.tb
, video_st_tb
, sizeof(*video_st_tb
));
449 if (win_width
== WIDTH_NOT_DEFINED
)
450 win_width
= (*video_codec_params
)->width
;
451 if (win_height
== HEIGHT_NOT_DEFINED
)
452 win_height
= (*video_codec_params
)->height
;
453 npv_xcb_init_once(win_width
, win_height
);
454 npv_vk_surf_init_once(npv_xcb_p
.c
, npv_xcb_p
.win_id
);
456 #undef WIDTH_NOT_DEFINED
457 #undef HEIGHT_NOT_DEFINED
459 #define PRINT_INFO true
460 static void prepare(double initial_vol
, avcodec_params_t
*audio_codec_params
,
461 avcodec_params_t
*video_codec_params
)
463 enum avutil_audio_fr_fmt_t dst_fmt
;
466 uint64_t dst_chans_layout
;
468 audio_dec_ctx_cfg(audio_codec_params
);
469 video_dec_ctx_cfg(video_codec_params
);
471 * do our best to match the pcm cfg to audio ff dec output, BUT we don't
472 * expect to match it "exactly": see right below why
474 audio_pcm_cfg(audio_pcm_p
, audio_dec_ctx_p
->chans_n
,
475 audio_dec_ctx_p
->fr_rate
, audio_dec_ctx_p
->fr_fmt
);
476 /* use a ff filt to fill in the gap between the pcm and audio ff dec */
477 audio_pcm2ff(audio_pcm_p
, &dst_fmt
, &dst_rate
, &dst_chans_n
,
478 &dst_chans_layout
, PRINT_INFO
);
480 audio_dec_ctx_p
->fr_fmt
, audio_dec_ctx_p
->fr_rate
,
481 audio_dec_ctx_p
->chans_n
, audio_dec_ctx_p
->chans_layout
,
483 dst_fmt
, dst_rate
, dst_chans_n
, dst_chans_layout
,
485 audio_pcm_silence_bufs_cfg(PRINT_INFO
);
490 static void cmd_info(void)
492 //TODO: info OSD toggle
495 static void cmd_quit(void)
497 EXIT("quit command received\n");
500 static void seek_lock(void)
505 pkt_q_lock(video_pkt_q_p
);
506 video_dec_frs_lock();
507 video_dec_ctx_lock();
509 pkt_q_lock(audio_pkt_q_p
);
510 audio_dec_sets_lock();
511 audio_dec_ctx_lock();
514 static void seek_unlock(void)
517 audio_dec_ctx_unlock();
518 audio_dec_sets_unlock();
519 pkt_q_unlock(audio_pkt_q_p
);
521 video_dec_ctx_unlock();
522 video_dec_frs_unlock();
523 pkt_q_unlock(video_pkt_q_p
);
528 #define TS_FROM_CLK_OK 0
529 static void seek_x(s64 delta
)
538 if (audio_draining_p
) {
539 WARNING("seek:audio is draining, seeking disable\n");
543 thdsws_wait_for_idle(video_scaler_p
.ctx
);
547 r
= clk_get_audio_st_ts(&audio_now
);
548 if (r
!= TS_FROM_CLK_OK
) {
549 WARNING("seek:audio:clock timestamp unavailable, ignoring command\n");
553 r
= clk_get_video_st_ts(&video_now
);
554 if (r
!= TS_FROM_CLK_OK
) {
555 WARNING("seek:video:clock timestamp unavailable, ignoring command\n");
560 * XXX: a set of sts can share the same id for seeking. if they share
561 * the same id then the tbs should be the same.
563 /*--------------------------------------------------------------------*/
564 new_audio_ts
= audio_now
+ delta
* audio_st_p
.tb
.den
566 /* rewind capping if possible */
567 if (audio_st_p
.start_time
!= AV_NOPTS_VALUE
)
568 if (new_audio_ts
< audio_st_p
.start_time
)
569 new_audio_ts
= audio_st_p
.start_time
;
570 POUT("trying to seek to %"PRId64
" audio stream time base units\n", new_audio_ts
);
571 a
= avformat_seek_pkt(fmt_ctx_p
, audio_st_p
.id
, new_audio_ts
, 0);
573 WARNING("unable to seek to %"PRId64
" audio stream time base units\n", new_audio_ts
);
574 goto try_restore_audio
;
576 POUT("audio seek to %"PRId64
" audio stream time base units\n", new_audio_ts
);
577 /*--------------------------------------------------------------------*/
578 new_video_ts
= video_now
+ delta
* video_st_p
.tb
.den
580 /* rewind capping if possible */
581 if (video_st_p
.start_time
!= AV_NOPTS_VALUE
)
582 if (new_video_ts
< video_st_p
.start_time
)
583 new_video_ts
= video_st_p
.start_time
;
584 POUT("trying to seek to %"PRId64
" video stream time base units\n", new_video_ts
);
585 a
= avformat_seek_pkt(fmt_ctx_p
, video_st_p
.id
, new_video_ts
, 0);
587 WARNING("unable to seek to %"PRId64
" video stream time base units but audio was seeked)\n", new_video_ts
);
588 goto try_restore_video
;
590 POUT("video seek to %"PRId64
" video stream time base units\n", new_video_ts
);
597 //input_bootstrap_audio_video();
602 a
= avformat_seek_pkt(fmt_ctx_p
, video_st_p
.id
, video_now
, 0);
603 if (a
< 0) /* we don't send an application error */
604 EXIT("unable to restore video to %"PRId64
" video stream time base units\n", video_now
);
606 a
= avformat_seek_pkt(fmt_ctx_p
, audio_st_p
.id
, audio_now
, 0);
607 if (a
< 0) /* we don't send an application error */
608 EXIT("unable to restore audio to %"PRId64
" audio stream time base units\n", audio_now
);
611 #undef TS_FROM_CLK_OK
612 static void cmd_rewind(void)
614 POUT("COMMAND:rewind\n");
618 static void cmd_rewind_big(void)
620 POUT("COMMAND:rewind big\n");
621 seek_x(-SEEK_DELTA_BIG
);
624 static void cmd_fastforward(void)
626 POUT("COMMAND:fastforward\n");
630 static void cmd_fastforward_big(void)
632 POUT("COMMAND:fastforward big\n");
633 seek_x(SEEK_DELTA_BIG
);
636 static void cmd_pause(void)
638 if (audio_draining_p
) {
639 WARNING("pause:audio is draining, toggling pause is disable\n");
645 POUT("COMMAND:unpause\n");
648 avformat_read_play(fmt_ctx_p
);
653 POUT("COMMAND:pause\n");
656 avformat_read_pause(fmt_ctx_p
);
661 static void cmd_vol_up(void)
663 audio_filt_cmd_vol_up();
666 static void cmd_vol_down(void)
668 audio_filt_cmd_vol_down();
671 static void cmd_mute(void)
673 audio_filt_cmd_mute();
676 static void prefill_wait(void) { loop
678 struct timespec wanted
;
683 pipeline_limits_lock();
684 prefill_audio
= pipeline_limits_p
.pkts
.prefill
.audio_bytes_rem
;
685 prefill_video
= pipeline_limits_p
.pkts
.prefill
.video_bytes_rem
;
686 pipeline_limits_unlock();
687 if (prefill_audio
<= 0 && prefill_video
<= 0)
689 memset(&wanted
, 0, sizeof(wanted
));
690 memset(&rem
, 0, sizeof(rem
));
691 wanted
.tv_nsec
= 100000000; /* 100 ms */
695 /* linux bug: cannot specify CLOCK_MONOTONIC_RAW */
696 r
= clock_nanosleep(CLOCK_MONOTONIC
, 0, &wanted
, &rem
);
700 FATAL("prefill wait timer failed:%d\n", r
);
702 memcpy(&wanted
, &rem
, sizeof(wanted
));
703 memset(&rem
, 0, sizeof(rem
));
707 static void predecoding_wait(void)
709 struct timespec wanted
;
712 memset(&wanted
, 0, sizeof(wanted
));
713 memset(&rem
, 0, sizeof(rem
));
714 /* we target ~double audio buf, namely (0.25s * 2 ~ 500ms) */
715 wanted
.tv_nsec
= 500000000;
719 /* linux bug: cannot specify CLOCK_MONOTONIC_RAW */
720 r
= clock_nanosleep(CLOCK_MONOTONIC
, 0, &wanted
, &rem
);
724 FATAL("predecoding wait timer failed:%d\n", r
);
726 memcpy(&wanted
, &rem
, sizeof(wanted
));
727 memset(&rem
, 0, sizeof(rem
));
730 #define WIDTH_NOT_DEFINED 0
731 #define HEIGHT_NOT_DEFINED 0
732 int main(int argc
, u8
**args
)
739 u8 pkts_prefill_percent
;
740 avcodec_params_t
*audio_codec_params
;
741 avcodec_params_t
*video_codec_params
;
743 /* "turn on utf8" processing in used libs if any *AND* locale system */
744 setlocale(LC_ALL
, "");
745 /* av_log_set_level(AV_LOG_VERBOSE); */
746 /* av_log_set_level(AV_LOG_DEBUG); */
748 win_width
= WIDTH_NOT_DEFINED
;
749 win_height
= HEIGHT_NOT_DEFINED
;
754 pkts_prefill_percent
= 0;
755 opts_parse(argc
, args
, &url
, &win_width
, &win_height
, &pcm_str
,
756 &initial_vol
, &pkts_prefill_percent
);
757 init_once(url
, win_width
, win_height
, pcm_str
, pkts_prefill_percent
,
758 &audio_codec_params
, &video_codec_params
);
759 prepare(initial_vol
, audio_codec_params
, video_codec_params
);
761 /* switch the ffmpeg log to stdout for metadata/etc dump */
762 avutil_log_set_callback(ff_log_stdout
);
763 avformat_dump_fmt(fmt_ctx_p
, 0, url
, 0);
764 avutil_log_set_callback(avutil_log_default_callback
);
766 pipeline_read_thd_start();
767 POUT("prefilling audio and video buffers...");
770 pipeline_audio_thd_start();
771 pipeline_video_thd_start();
772 POUT("predecoding audio and video...");
780 #undef WIDTH_NOT_DEFINED
781 #undef HEIGHT_NOT_DEFINED