2 * block as much as possible.
3 * handle only async "usual" sigs, with sync signalfd.
4 * allow some signals to go thru though. many sync signal can only
5 * be handles with an async handler.
6 * always presume the process "controlling terminal" is different than the
7 * terminal connected on standard input and standard output
9 STATIC
void sigs_init_once(void)
14 r
= sigfillset(&sset
);
16 fatal("unable to get a full signal mask\n");
18 /* the "controlling terminal" line asks for a core dump, leave it be */
19 r
= sigdelset(&sset
, SIGQUIT
);
21 fatal("unable to remove SIGQUIT from our signal mask\n");
23 r
= pthread_sigmask(SIG_SETMASK
, &sset
, 0);
25 fatal("unable to \"block\" \"all\" signals\n");
27 /* from here, we "steal" signals with signalfd */
29 r
= sigemptyset(&sset
);
31 fatal("unable to get an empty signal mask\n");
32 /* we are asked nicely to terminate */
33 r
= sigaddset(&sset
, SIGTERM
);
35 fatal("unable to add SIGTERM to our signal mask\n");
36 /* the "controlling terminal" line (^c) asks nicely to terminate */
37 r
= sigaddset(&sset
, SIGINT
);
39 fatal("unable to add SIGINT to our signal mask\n");
41 r
= signalfd(-1, &sset
, SFD_NONBLOCK
);
43 fatal("unable to get a signalfd file descriptor\n");
46 STATIC
void prefill_wait(void) { loop
48 struct timespec wanted
;
53 npv_pipeline_limits_lock();
54 prefill_audio
= npv_pipeline_limits_p
.pkts
.prefill
.audio_bytes_rem
;
55 prefill_video
= npv_pipeline_limits_p
.pkts
.prefill
.video_bytes_rem
;
56 npv_pipeline_limits_unlock();
57 if (prefill_audio
<= 0 && prefill_video
<= 0)
59 memset(&wanted
, 0, sizeof(wanted
));
60 memset(&rem
, 0, sizeof(rem
));
61 wanted
.tv_nsec
= 100000000; /* 100 ms */
65 /* linux bug: cannot specify CLOCK_MONOTONIC_RAW */
66 r
= clock_nanosleep(CLOCK_MONOTONIC
, 0, &wanted
, &rem
);
70 fatal("prefill wait timer failed:%d\n", r
);
72 memcpy(&wanted
, &rem
, sizeof(wanted
));
73 memset(&rem
, 0, sizeof(rem
));
76 STATIC
void predecode_wait(void)
78 struct timespec wanted
;
81 memset(&wanted
, 0, sizeof(wanted
));
82 memset(&rem
, 0, sizeof(rem
));
83 /* we target ~double audio buf, namely (0.25s * 2 ~ 500ms) */
84 wanted
.tv_nsec
= 500000000;
88 /* linux bug: cannot specify CLOCK_MONOTONIC_RAW */
89 r
= clock_nanosleep(CLOCK_MONOTONIC
, 0, &wanted
, &rem
);
93 fatal("predecoding wait timer failed:%d\n", r
);
95 memcpy(&wanted
, &rem
, sizeof(wanted
));
96 memset(&rem
, 0, sizeof(rem
));
99 STATIC
void evt_init_once(void)
101 ep_fd_p
= epoll_create1(0);
103 fatal("unable to create the epoll file descriptor\n");
105 STATIC
void evt_add_all_fds(void)
109 struct epoll_event evt
;
112 evt
.events
= EPOLLIN
;
113 evt
.data
.fd
= sig_fd_l
;
114 r
= epoll_ctl(ep_fd_p
, EPOLL_CTL_ADD
, sig_fd_l
, &evt
);
116 fatal("unable to add the signalfd file descriptior to the epoll file descriptor\n");
117 /*--------------------------------------------------------------------*/
118 /* the video timer */
119 evt
.events
= EPOLLIN
;
120 evt
.data
.fd
= npv_video_timer_fd_p
;
121 r
= epoll_ctl(ep_fd_p
, EPOLL_CTL_ADD
, npv_video_timer_fd_p
, &evt
);
123 fatal("unable to add the video timer file descriptor\n");
124 /*--------------------------------------------------------------------*/
125 /* the x11 xcb file descriptor */
126 evt
.events
= EPOLLIN
;
127 evt
.data
.fd
= npv_xcb_p
.fd
;
128 r
= epoll_ctl(ep_fd_p
, EPOLL_CTL_ADD
, npv_xcb_p
.fd
, &evt
);
130 fatal("unable to add the x11 xcb file descriptor\n");
131 /*--------------------------------------------------------------------*/
132 /* alsa pcm poll file descriptors */
135 if (i
== npv_audio_pcm_pollfds_n_p
)
137 evt
.events
= npv_audio_pcm_pollfds_p
[i
].events
;
138 evt
.data
.fd
= npv_audio_pcm_pollfds_p
[i
].fd
;
139 r
= epoll_ctl(ep_fd_p
, EPOLL_CTL_ADD
,
140 npv_audio_pcm_pollfds_p
[i
].fd
, &evt
);
142 fatal("unable to add alsa poll file descriptor index %d to epoll file descriptor\n", i
);
145 /*--------------------------------------------------------------------*/
146 /* the draining timer */
147 evt
.events
= EPOLLIN
;
148 evt
.data
.fd
= npv_audio_draining_timer_fd_p
;
149 r
= epoll_ctl(ep_fd_p
, EPOLL_CTL_ADD
, npv_audio_draining_timer_fd_p
,
152 fatal("unable to add the draining timer file descriptor\n");
153 /*--------------------------------------------------------------------*/
154 /* the xcb screensaver heartbeat timer */
155 evt
.events
= EPOLLIN
;
156 evt
.data
.fd
= npv_xcb_p
.screensaver_heartbeat_timer_fd
;
157 r
= epoll_ctl(ep_fd_p
, EPOLL_CTL_ADD
,
158 npv_xcb_p
.screensaver_heartbeat_timer_fd
, &evt
);
160 fatal("unable to add the xcb screensaver heartbeat timer file descriptor\n");
161 /*--------------------------------------------------------------------*/
162 /* the xcb mouse visibility timer */
163 evt
.events
= EPOLLIN
;
164 evt
.data
.fd
= npv_xcb_p
.mouse_visibility_timer_fd
;
165 r
= epoll_ctl(ep_fd_p
, EPOLL_CTL_ADD
,
166 npv_xcb_p
.mouse_visibility_timer_fd
, &evt
);
168 fatal("unable to add the xcb screensaver heartbeat timer file descriptor\n");
170 STATIC
void evt_sigs(void)
173 struct signalfd_siginfo siginfo
;
176 r
= read(sig_fd_l
, &siginfo
, sizeof(siginfo
));
177 if (r
!= sizeof(siginfo
))
178 fatal("unable to read signal information\n");
180 switch (siginfo
.ssi_signo
) {
182 exit_ok("received SIGTERM\n");
184 exit_ok("received SIGINT\n");
186 warning("signal handle:unwanted signal %d received, discarding\n", siginfo
.ssi_signo
);
189 STATIC
void evt_accumulate(struct epoll_event
*evt
, bool *have_evt_sigs
,
190 bool *have_evt_pcm
, bool *have_evt_video
, bool *have_evt_pcm_draining
,
191 bool *have_evt_x11
, bool *have_evt_xcb_screensaver_heartbeat
,
192 bool *have_evt_xcb_mouse_visibility
)
196 if (evt
->data
.fd
== sig_fd_l
) {
197 if ((evt
->events
& EPOLLIN
) != 0) {
198 *have_evt_sigs
= true;
201 fatal("event loop wait:signal:unexpected event\n");
203 /*-------------------------------------------------------------------*/
204 /* only update alsa fds */
207 if (i
== npv_audio_pcm_pollfds_n_p
)
209 if (evt
->data
.fd
== npv_audio_pcm_pollfds_p
[i
].fd
) {
210 npv_audio_pcm_pollfds_p
[i
].revents
= evt
->events
;
211 *have_evt_pcm
= true;
216 /*-------------------------------------------------------------------*/
217 if (evt
->data
.fd
== npv_xcb_p
.fd
) {
218 if ((evt
->events
& EPOLLIN
) != 0)
219 *have_evt_x11
= true;
222 /*-------------------------------------------------------------------*/
223 if (evt
->data
.fd
== npv_video_timer_fd_p
) {
224 if ((evt
->events
& EPOLLIN
) != 0) {
225 *have_evt_video
= true;
228 fatal("event loop wait:video:unexpected event\n");
230 /*-------------------------------------------------------------------*/
231 if (evt
->data
.fd
== npv_audio_draining_timer_fd_p
) {
232 if ((evt
->events
& EPOLLIN
) != 0) {
233 *have_evt_pcm_draining
= true;
236 fatal("event loop wait:audio draining timer:unexpected event\n");
238 /*-------------------------------------------------------------------*/
239 if (evt
->data
.fd
== npv_xcb_p
.screensaver_heartbeat_timer_fd
) {
240 if ((evt
->events
& EPOLLIN
) != 0) {
241 *have_evt_xcb_screensaver_heartbeat
= true;
244 fatal("event loop wait:xcb screensaver heartbeat timer:unexpected event\n");
246 /*-------------------------------------------------------------------*/
247 if (evt
->data
.fd
== npv_xcb_p
.mouse_visibility_timer_fd
) {
248 if ((evt
->events
& EPOLLIN
) != 0) {
249 *have_evt_xcb_mouse_visibility
= true;
252 fatal("event loop wait:xcb mouse visibility timer:unexpected event\n");
256 * XXX: remember that all heavy lifting should be done in other threads.
257 * this thread should not "block" or perform "expensive" work.
258 * "blocking", "expensive" work should be offloaded to other threads.
260 #define EPOLL_EVTS_N 32 /* why not */
261 STATIC
void evts_loop(void)
265 struct epoll_event evts
[EPOLL_EVTS_N
];
269 bool have_evt_pcm_draining
;
271 bool have_evt_xcb_screensaver_heartbeat
;
272 bool have_evt_xcb_mouse_visibility
;
277 memset(evts
, 0, sizeof(evts
));
278 fds_n
= epoll_wait(ep_fd_p
, evts
, EPOLL_EVTS_N
, -1);
280 if (errno
== EINTR
) {
281 warning("event loop wait:was interrupted by a signal\n");
284 fatal("event loop wait:an error occured\n");
287 have_evt_sigs
= false;
288 have_evt_pcm
= false;
289 have_evt_video
= false;
290 have_evt_pcm_draining
= false;
291 have_evt_x11
= false;
292 have_evt_xcb_screensaver_heartbeat
= false;
293 have_evt_xcb_mouse_visibility
= false;
299 evt_accumulate(&evts
[fd_idx
], &have_evt_sigs
, &have_evt_pcm
,
300 &have_evt_video
, &have_evt_pcm_draining
,
301 &have_evt_x11
, &have_evt_xcb_screensaver_heartbeat
,
302 &have_evt_xcb_mouse_visibility
);
306 /* once we have our evts, we use a sort of priority order */
308 /* process any q-ed and we-handle sigs before anything */
312 * XXX: it may be more appropriate to break this in 2 steps: key inputs
313 * (light processing), wins resizing (heavy processing)
315 /* key input and win resizing */
318 /* XXX: once in audio draining mode, this should not really happen */
319 /* we are more sensitive to audio issues than video issues */
322 * since alsa could use several file descriptors, only once the
323 * pollfds were properly updated we can actually know we got
324 * something from alsa
326 r
= snd_pcm_poll_descriptors_revents(npv_audio_pcm_p
,
327 npv_audio_pcm_pollfds_p
, npv_audio_pcm_pollfds_n_p
,
330 fatal("alsa:error processing the poll file descriptors\n");
331 if ((pcm_evts
& ~POLLOUT
) != 0)
332 fatal("alsa:unexpected events\n");
333 if ((pcm_evts
& POLLOUT
) != 0)
334 npv_audio_evt_pcm_write();
337 npv_video_timer_evt();
338 /* while audio is draining, video fr may need to be displayed */
339 if (have_evt_pcm_draining
)
340 npv_audio_draining_state_evt();
341 if (have_evt_xcb_screensaver_heartbeat
)
342 npv_xcb_screensaver_heartbeat_timer_evt();
343 if (have_evt_xcb_mouse_visibility
)
344 npv_xcb_mouse_visibility_timer_evt();
347 STATIC
void ff_log_stdout(void *a
, int b
, const char *fmt
, va_list ap
)
351 STATIC
void usage(void)
354 npv [-f send a fullscreen message to the window manager at start] [-p alsa pcm]\n\
355 [-v volume(0..100)] [-h window height in pixels] [-w window width in pixels]\n\
356 [-b packet buffer prefill wait(0..100)] [-help] url\n");
358 STATIC
void opts_parse(int argc
, u8
**args
, u8
**url
, bool* start_fullscreen
,
359 u16
*w
, u16
*h
, u8
**pcm_str
, double *vol
, u8
*pkts_prefill_percent
)
369 if (strcmp("-f", args
[i
]) == 0) {
370 *start_fullscreen
= true;
372 } else if (strcmp("-p", args
[i
]) == 0) {
373 *pcm_str
= args
[i
+ 1];
374 pout("-p:alsa pcm \"%s\"\n", *pcm_str
);
376 } else if (strcmp("-v", args
[i
]) == 0) {
377 unsigned long vol_ul
;
380 fatal("-v:initial volume is missing\n");
381 vol_ul
= strtoul(args
[i
+ 1], 0, 10);
382 if (vol_ul
< 0 || 100 < vol_ul
)
383 fatal("-v:invalid volume value %lu (0..100)\n", vol_ul
);
384 *vol
= (double)vol_ul
/ 100.;
385 pout("-v:using initial volume %f\n", *vol
);
387 } else if (strcmp("-h", args
[i
]) == 0) {
391 fatal("-h:initial window pixel height is missing\n");
392 h_ul
= strtoul(args
[i
+ 1], 0, 10);
393 if (h_ul
== 0 || h_ul
> U16_MAX
)
394 fatal("-h:invalid window pixel height %lu (1..%lu)\n", h_ul
, U16_MAX
);
396 pout("-h:using initial window height %lu pixels\n", h_ul
);
398 } else if (strcmp("-w", args
[i
]) == 0) {
402 fatal("-h:initial window pixel width is missing\n");
403 w_ul
= strtoul(args
[i
+ 1], 0, 10);
404 if (w_ul
== 0 || w_ul
> U16_MAX
)
405 fatal("-w:invalid window pixel width %lu (1..%lu)\n", w_ul
, U16_MAX
);
407 pout("-w:using initial window width %lu pixels\n", w_ul
);
409 } else if (strcmp("-b", args
[i
]) == 0) {
411 fatal("-b:percent value for prefill of buffer of packet queues is missing\n");
412 *pkts_prefill_percent
= (u8
)strtoul(args
[i
+ 1], 0, 10);
413 pout("-v:using a %u prefilled buffer of packet queues\n", *pkts_prefill_percent
);
415 } else if (strcmp("-help", args
[i
]) == 0) {
424 fatal("missing url\n");
425 *url
= args
[url_idx
];
426 pout("url-->####%s####\n", *url
);
428 #define WIDTH_NOT_DEFINED 0
429 #define HEIGHT_NOT_DEFINED 0
430 STATIC
void init_once(u8
*url
, bool start_fullscreen
, double initial_vol
,
431 u16 win_width
, u16 win_height
, u8
*pcm_str
,
432 avcodec_params_t
**audio_codec_params
,
433 avcodec_params_t
**video_codec_params
)
435 avutil_rational_t
*audio_st_tb
;
436 avutil_rational_t
*video_st_tb
;
440 npv_vk_init_once(); /* generic plumbing */
441 npv_audio_filt_init_once(initial_vol
);
442 /* before audio_st_idx_p is actually used */
443 npv_audio_init_once(pcm_str
);
444 npv_video_init_once(); /* before video_st_idx_p is actually used */
446 npv_pipeline_init_once();
448 npv_fmt_init_once(url
);
449 /* we need something to start with */
450 npv_fmt_probe_best_sts(
451 &npv_video_st_p
.idx
, &npv_video_st_p
.id
, &video_st_tb
,
452 &npv_video_st_p
.start_time
, video_codec_params
,
453 &npv_audio_st_p
.idx
, &npv_audio_st_p
.id
, &audio_st_tb
,
454 &npv_audio_st_p
.start_time
, audio_codec_params
);
455 memcpy(&npv_audio_st_p
.tb
, audio_st_tb
, sizeof(*audio_st_tb
));
456 memcpy(&npv_video_st_p
.tb
, video_st_tb
, sizeof(*video_st_tb
));
457 if (win_width
== WIDTH_NOT_DEFINED
)
458 win_width
= (*video_codec_params
)->width
;
459 if (win_height
== HEIGHT_NOT_DEFINED
)
460 win_height
= (*video_codec_params
)->height
;
461 npv_xcb_init_once(win_width
, win_height
, start_fullscreen
);
462 npv_vk_surf_init_once(npv_xcb_p
.c
, npv_xcb_p
.win_id
);
464 #undef WIDTH_NOT_DEFINED
465 #undef HEIGHT_NOT_DEFINED
466 #define PRINT_INFO true
467 STATIC
void prepare(double initial_vol
, u8 pkts_prefill_percent
,
468 avcodec_params_t
*audio_codec_params
,
469 avcodec_params_t
*video_codec_params
)
471 enum avutil_audio_fr_fmt_t dst_fmt
;
474 uint64_t dst_chans_layout
;
476 npv_pipeline_limits_reset();
477 npv_pipeline_prefill_reset(pkts_prefill_percent
);
479 npv_audio_dec_ctx_cfg(audio_codec_params
);
480 npv_video_dec_ctx_cfg(video_codec_params
);
482 * do our best to match the pcm cfg to audio ff dec output, BUT we don't
483 * expect to match it "exactly": see right below why
485 npv_audio_pcm_cfg(npv_audio_pcm_p
, npv_audio_dec_ctx_p
->chans_n
,
486 npv_audio_dec_ctx_p
->fr_rate
, npv_audio_dec_ctx_p
->fr_fmt
);
487 /* use a ff filt to fill in the gap between the pcm and audio ff dec */
488 npv_audio_pcm2ff(npv_audio_pcm_p
, &dst_fmt
, &dst_rate
, &dst_chans_n
,
489 &dst_chans_layout
, PRINT_INFO
);
491 npv_audio_dec_ctx_p
->fr_fmt
, npv_audio_dec_ctx_p
->fr_rate
,
492 npv_audio_dec_ctx_p
->chans_n
, npv_audio_dec_ctx_p
->chans_layout
,
494 dst_fmt
, dst_rate
, dst_chans_n
, dst_chans_layout
,
496 npv_audio_pcm_silence_bufs_cfg(npv_audio_pcm_p
, PRINT_INFO
);
500 /*==== while waiting for the osd -- start ====================================*/
501 STATIC u8
*duration_estimate_to_str(enum AVDurationEstimationMethod m
)
504 case AVFMT_DURATION_FROM_PTS
:
505 return "from PTS(Presentation TimeStamp)";
506 case AVFMT_DURATION_FROM_STREAM
:
507 return "from stream";
508 case AVFMT_DURATION_FROM_BITRATE
:
509 return "from bitrate";
514 STATIC u8
*ts_to_str(int64_t ts
, avutil_rational_t time_base
,
517 static u8 str
[sizeof("~S00:00:00.000 remains S9223372036854775807 time base units")];
523 int64_t one_hour
; /* in ffmpeg time_base units */
524 int64_t one_min
; /* in ffmpeg time_base units */
525 int64_t one_sec
; /* in ffmpeg time_base units */
526 int64_t one_msec
; /* in ffmpeg time_base units */
534 one_hour
= INT64_C(3600) * (int64_t)time_base
.den
535 / (int64_t)time_base
.num
;
536 one_min
= INT64_C(60) * (int64_t)time_base
.den
537 / (int64_t)time_base
.num
;
538 one_sec
= (int64_t)time_base
.den
/ (int64_t)time_base
.num
;
539 one_msec
= one_sec
/ INT64_C(1000);
541 hours_n
= ts
/ one_hour
;
543 *remaining
= ts
% one_hour
;
544 mins_n
= *remaining
/ one_min
;
546 *remaining
= *remaining
% one_min
;
547 secs_n
= *remaining
/ one_sec
;
549 *remaining
= *remaining
% one_sec
;
550 msecs_n
= *remaining
/ one_msec
;
552 /* account for all rounding errors */
553 *remaining
= ts
- (hours_n
* one_hour
+ mins_n
* one_min
554 + secs_n
* one_sec
+ msecs_n
* one_msec
);
556 snprintf(str
, sizeof(str
), "%02"PRId64
":%02"PRId64
":%02"PRId64
".%03"PRId64
, hours_n
, mins_n
, secs_n
, msecs_n
);
559 snprintf(str
+ 1, sizeof(str
) - 1, "%02"PRId64
":%02"PRId64
":%02"PRId64
".%03"PRId64
, hours_n
, mins_n
, secs_n
, msecs_n
);
563 #define RED if (stdout_is_tty) pout("\x1b[38;2;255;0;0m")
564 #define GREEN if (stdout_is_tty) pout("\x1b[38;2;0;255;0m")
565 #define BLUE if (stdout_is_tty) pout("\x1b[38;2;0;0;255m")
566 #define PURPLE if (stdout_is_tty) pout("\x1b[38;2;255;0;255m")
567 #define RESTORE if (stdout_is_tty) pout("\x1b[39;49m");
568 #define TS_FROM_CLK_OK 0
569 /* we don't lock anything as an act of faith */
570 STATIC
void npv_cmd_info(void)
578 u8 duration_str
[sizeof("S9223372036854775807")];
582 stdout_is_tty
= false;
584 stdout_is_tty
= true;
587 GREEN
;pout("================================================================================\n");RESTORE
;
588 PURPLE
;pout("%s\n", npv_fmt_ctx_p
->url
);RESTORE
;
590 r8
= npv_clk_get_audio_st_ts(&audio_now
);
591 if (r8
!= TS_FROM_CLK_OK
) {
592 pout("information unavailable at the time of the command due to an unavailable audio clock\n");
595 ts_str
= ts_to_str(audio_now
, npv_audio_st_p
.tb
, &remaining
);
596 RED
;pout("%s", ts_str
);RESTORE
;
598 pout(" remaining %"PRId64
" time base units", remaining
);
601 pout("\t%"PRId64
" stream time base units (%d/%d seconds)\n",
602 audio_now
, npv_audio_st_p
.tb
.num
, npv_audio_st_p
.tb
.den
);
603 BLUE
;pout("--------------------------------------------------------------------------------\n");RESTORE
;
605 if (npv_fmt_ctx_p
->duration
== AV_NOPTS_VALUE
) {
606 pout("duration is not provided\n");
608 snprintf(duration_str
, sizeof(duration_str
), "%"PRId64
,
609 npv_fmt_ctx_p
->duration
);
610 ts_str
= ts_to_str(npv_fmt_ctx_p
->duration
, AV_TIME_BASE_Q
,
612 pout("duration=");RED
;pout("%s", ts_str
);RESTORE
;
614 pout(" remaining %"PRId64
" av_time_base units\n",
618 pout("\t%s av_time_base units (1/%d seconds)\n\testimation method is %s\n",
619 duration_str
, AV_TIME_BASE
, duration_estimate_to_str(
620 npv_fmt_ctx_p
->duration_estimation_method
));
622 BLUE
;pout("--------------------------------------------------------------------------------\n");RESTORE
;
623 pout("stream:audio_id=%d", npv_audio_st_p
.id
);
624 if (npv_fmt_ctx_p
->streams
[npv_audio_st_p
.idx
]->duration
== AV_NOPTS_VALUE
) {
625 pout(";duration is not provided\n");
627 snprintf(duration_str
, sizeof(duration_str
), "%"PRId64
,
628 npv_fmt_ctx_p
->streams
[npv_audio_st_p
.idx
]->duration
);
630 npv_fmt_ctx_p
->streams
[npv_audio_st_p
.idx
]->duration
,
631 npv_audio_st_p
.tb
, &remaining
);
632 pout(";duration=");RED
;pout("%s\n", ts_str
);RESTORE
;
634 pout(" remaining %"PRId64
" stream time base units\n",
638 pout("\t%s stream time base units (%d/%d seconds)\n",
639 duration_str
, npv_audio_st_p
.tb
.num
,
640 npv_audio_st_p
.tb
.den
);
642 BLUE
;pout("--------------------------------------------------------------------------------\n");RESTORE
;
643 GREEN
;pout("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n");RESTORE
;
645 #undef TS_FROM_CLK_OK
651 /*==== while waiting for the osd -- end ======================================*/
652 STATIC
void npv_cmd_quit(void)
654 exit_ok("quit command received\n");
656 STATIC
void seek_lock(void)
661 npv_pkt_q_lock(npv_video_pkt_q_p
);
662 npv_video_dec_frs_lock();
663 npv_video_dec_ctx_lock();
664 /* audio (useless from the main thd though) */
665 npv_pkt_q_lock(npv_audio_pkt_q_p
);
666 npv_audio_dec_sets_lock();
667 npv_audio_dec_ctx_lock();
669 npv_pipeline_limits_lock();
671 STATIC
void seek_unlock(void)
673 npv_pipeline_limits_unlock();
676 npv_audio_dec_ctx_unlock();
677 npv_audio_dec_sets_unlock();
678 npv_pkt_q_unlock(npv_audio_pkt_q_p
);
680 npv_video_dec_ctx_unlock();
681 npv_video_dec_frs_unlock();
682 npv_pkt_q_unlock(npv_video_pkt_q_p
);
684 npv_fmt_ctx_unlock();
686 #define TS_FROM_CLK_OK 0
687 STATIC
void seek_x(s64 delta
)
696 if (npv_audio_draining_p
) {
697 warning("seek:audio is draining, seeking disable\n");
700 if (paused_p
) /* we don't seek in pause */
703 npv_thdsws_wait_for_idle(npv_video_scaler_p
.ctx
);
707 r
= npv_clk_get_audio_st_ts(&audio_now
);
708 if (r
!= TS_FROM_CLK_OK
) {
709 warning("seek:audio:clock timestamp unavailable, ignoring command\n");
713 r
= npv_clk_get_video_st_ts(&video_now
);
714 if (r
!= TS_FROM_CLK_OK
) {
715 warning("seek:video:clock timestamp unavailable, ignoring command\n");
719 (void)snd_pcm_drop(npv_audio_pcm_p
);
721 * XXX: a set of sts can share the same id for seeking. if they share
722 * the same id then the tbs should be the same.
724 /*--------------------------------------------------------------------*/
725 new_audio_ts
= audio_now
+ delta
* npv_audio_st_p
.tb
.den
726 / npv_audio_st_p
.tb
.num
;
727 /* rewind capping if possible */
728 if (npv_audio_st_p
.start_time
!= AV_NOPTS_VALUE
)
729 if (new_audio_ts
< npv_audio_st_p
.start_time
)
730 new_audio_ts
= npv_audio_st_p
.start_time
;
731 pout("trying to seek to %"PRId64
" audio stream time base units\n", new_audio_ts
);
732 a
= avformat_seek_pkt(npv_fmt_ctx_p
, npv_audio_st_p
.id
, new_audio_ts
,
735 pout("unable to seek to %"PRId64
" audio stream time base units\n", new_audio_ts
);
736 goto try_restore_audio
;
738 pout("audio seek to %"PRId64
" audio stream time base units\n", new_audio_ts
);
739 /*--------------------------------------------------------------------*/
740 new_video_ts
= video_now
+ delta
* npv_video_st_p
.tb
.den
741 / npv_video_st_p
.tb
.num
;
742 /* rewind capping if possible */
743 if (npv_video_st_p
.start_time
!= AV_NOPTS_VALUE
)
744 if (new_video_ts
< npv_video_st_p
.start_time
)
745 new_video_ts
= npv_video_st_p
.start_time
;
746 pout("trying to seek to %"PRId64
" video stream time base units\n", new_video_ts
);
747 a
= avformat_seek_pkt(npv_fmt_ctx_p
, npv_video_st_p
.id
, new_video_ts
,
750 pout("unable to seek to %"PRId64
" video stream time base units but audio was seeked)\n", new_video_ts
);
751 goto try_restore_video
;
753 pout("video seek to %"PRId64
" video stream time base units\n", new_video_ts
);
755 npv_video_dec_flush();
756 npv_audio_dec_flush();
757 npv_audio_filt_flush();
759 npv_clk_invalidate();
760 npv_pipeline_limits_reset();
761 npv_pipeline_prefill_reset(npv_pipeline_limits_p
.pkts
.prefill
.percent
);
764 pout("prefilling audio and video buffers...");
765 prefill_wait(); /* use the lock on the pipeline limits */
767 pout("predecoding audio and video...");
768 predecode_wait(); /* use the lock on the pipeline limits */
770 (void)snd_pcm_prepare(npv_audio_pcm_p
);
774 a
= avformat_seek_pkt(npv_fmt_ctx_p
, npv_video_st_p
.id
, video_now
, 0);
775 if (a
< 0) /* we don't send an application error */
776 exit_ok("unable to restore video to %"PRId64
" video stream time base units\n", video_now
);
778 a
= avformat_seek_pkt(npv_fmt_ctx_p
, npv_audio_st_p
.id
, audio_now
, 0);
779 if (a
< 0) /* we don't send an application error */
780 exit_ok("unable to restore audio to %"PRId64
" audio stream time base units\n", audio_now
);
783 #undef TS_FROM_CLK_OK
784 STATIC
void npv_cmd_rewind(void)
786 pout("COMMAND:rewind\n");
790 STATIC
void npv_cmd_rewind_big(void)
792 pout("COMMAND:rewind big\n");
793 seek_x(-SEEK_DELTA_BIG
);
796 STATIC
void npv_cmd_fastforward(void)
798 pout("COMMAND:fastforward\n");
802 STATIC
void npv_cmd_fastforward_big(void)
804 pout("COMMAND:fastforward big\n");
805 seek_x(SEEK_DELTA_BIG
);
808 STATIC
void npv_cmd_pause(void)
810 if (npv_audio_draining_p
) {
811 warning("pause:audio is draining, toggling pause is disable\n");
817 pout("COMMAND:unpause\n");
820 avformat_read_play(npv_fmt_ctx_p
);
821 npv_fmt_ctx_unlock();
823 npv_video_timer_start();
827 pout("COMMAND:pause\n");
830 avformat_read_pause(npv_fmt_ctx_p
);
831 npv_fmt_ctx_unlock();
833 npv_video_timer_slow_start();
836 STATIC
void npv_cmd_vol_up(void)
838 npv_audio_filt_cmd_vol_up();
840 STATIC
void npv_cmd_vol_down(void)
842 npv_audio_filt_cmd_vol_down();
844 STATIC
void npv_cmd_mute(void)
846 npv_audio_filt_cmd_mute();
848 #define WIDTH_NOT_DEFINED 0
849 #define HEIGHT_NOT_DEFINED 0
850 int main(int argc
, u8
**args
)
852 bool start_fullscreen
;
858 avcodec_params_t
*audio_codec_params
;
859 avcodec_params_t
*video_codec_params
;
860 /* "turn on utf8" processing in used libs if any *AND* locale system */
861 setlocale(LC_ALL
, "");
862 /* av_log_set_level(AV_LOG_VERBOSE); */
863 /* av_log_set_level(AV_LOG_DEBUG); */
864 start_fullscreen
= false;
865 win_width
= WIDTH_NOT_DEFINED
;
866 win_height
= HEIGHT_NOT_DEFINED
;
871 npv_pipeline_limits_p
.pkts
.prefill
.percent
= 0;
872 opts_parse(argc
, args
, &url
, &start_fullscreen
, &win_width
, &win_height
,
873 &pcm_str
, &initial_vol
,
874 &npv_pipeline_limits_p
.pkts
.prefill
.percent
);
875 init_once(url
, start_fullscreen
, initial_vol
, win_width
, win_height
,
876 pcm_str
, &audio_codec_params
, &video_codec_params
);
877 prepare(initial_vol
, npv_pipeline_limits_p
.pkts
.prefill
.percent
,
878 audio_codec_params
, video_codec_params
);
880 /* switch the ffmpeg log to stdout for metadata/etc dump */
881 avutil_log_set_callback(ff_log_stdout
);
882 avformat_dump_fmt(npv_fmt_ctx_p
, 0, url
, 0);
883 avutil_log_set_callback(avutil_log_default_callback
);
885 npv_pipeline_read_thd_start();
886 pout("prefilling audio and video buffers...");
889 npv_pipeline_audio_thd_start();
890 npv_pipeline_video_thd_start();
891 pout("predecoding audio and video...");
894 npv_video_timer_start();
895 npv_xcb_screensaver_heartbeat_timer_start();
900 #undef WIDTH_NOT_DEFINED
901 #undef HEIGHT_NOT_DEFINED