From 06c702ec5258bcdd5a5fbf7c879bfc7e2705cbf8 Mon Sep 17 00:00:00 2001 From: Uoti Urpala Date: Fri, 16 Mar 2012 00:57:52 +0200 Subject: terminal output: if audio/video pts is missing, show "???" Change the terminal status line to show "???" instead of a huge negative number if audio or video pts is missing (there was a partial workaround for audio before, but not video or A-V difference). --- mplayer.c | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) (limited to 'mplayer.c') diff --git a/mplayer.c b/mplayer.c index 19d93bf2d8..c228f98d42 100644 --- a/mplayer.c +++ b/mplayer.c @@ -1228,6 +1228,8 @@ static void print_status(struct MPContext *mpctx, double a_pos, bool at_frame) if (mpctx->time_frame > 0) mpctx->last_av_difference += mpctx->time_frame * opts->playback_speed; + if (a_pos == MP_NOPTS_VALUE || mpctx->video_pts == MP_NOPTS_VALUE) + mpctx->last_av_difference = MP_NOPTS_VALUE; if (mpctx->last_av_difference > 0.5 && drop_frame_cnt > 50 && !mpctx->drop_message_shown) { mp_tmsg(MSGT_AVSYNC, MSGL_WARN, SystemTooSlow); @@ -1237,9 +1239,6 @@ static void print_status(struct MPContext *mpctx, double a_pos, bool at_frame) if (opts->quiet) return; - if (a_pos == MP_NOPTS_VALUE) - a_pos = -9; // don't print a huge negative number - int width; char *line; unsigned pos = 0; @@ -1257,8 +1256,11 @@ static void print_status(struct MPContext *mpctx, double a_pos, bool at_frame) // Audio time if (mpctx->sh_audio) { - saddf(line, &pos, width, "A:%6.1f ", a_pos); - if (!sh_video) { + if (a_pos != MP_NOPTS_VALUE) + saddf(line, &pos, width, "A:%6.1f ", a_pos); + else + saddf(line, &pos, width, "A: ??? "); + if (!sh_video && a_pos != MP_NOPTS_VALUE) { float len = get_time_length(mpctx); saddf(line, &pos, width, "("); sadd_hhmmssf(line, &pos, width, a_pos); @@ -1269,13 +1271,22 @@ static void print_status(struct MPContext *mpctx, double a_pos, bool at_frame) } // Video time - if (sh_video) - saddf(line, &pos, width, "V:%6.1f ", mpctx->video_pts); + if (sh_video) { + if (mpctx->video_pts != MP_NOPTS_VALUE) + saddf(line, &pos, width, "V:%6.1f ", mpctx->video_pts); + else + saddf(line, &pos, width, "V: ??? ", mpctx->video_pts); + } // A-V sync - if (mpctx->sh_audio && sh_video) - saddf(line, &pos, width, "A-V:%7.3f ct:%7.3f ", - mpctx->last_av_difference, mpctx->total_avsync_change); + if (mpctx->sh_audio && sh_video) { + if (mpctx->last_av_difference != MP_NOPTS_VALUE) + saddf(line, &pos, width, "A-V:%7.3f ct:%7.3f ", + mpctx->last_av_difference, mpctx->total_avsync_change); + else + saddf(line, &pos, width, "A-V: ??? ct:%7.3f ", + mpctx->total_avsync_change); + } // Video stats if (sh_video) @@ -3358,9 +3369,11 @@ double get_current_time(struct MPContext *mpctx) struct demuxer *demuxer = mpctx->demuxer; if (demuxer->stream_pts != MP_NOPTS_VALUE) return demuxer->stream_pts; - struct sh_video *sh_video = demuxer->video->sh; - if (sh_video) - return mpctx->video_pts; + if (mpctx->sh_video) { + double pts = mpctx->video_pts; + if (pts != MP_NOPTS_VALUE) + return pts; + } double apts = playing_audio_pts(mpctx); if (apts != MP_NOPTS_VALUE) return apts; -- cgit v1.2.3 From 7040968be41cab1a9aa6129afe7c76129c74c2f8 Mon Sep 17 00:00:00 2001 From: Uoti Urpala Date: Thu, 15 Mar 2012 01:24:52 +0200 Subject: core: restructure main play loop, continue audio after video Restructure parts of the code in the main play loop. The main functionality difference is that if a video track ends first, now audio will continue to be played until it ends too. Now the process also wakes up less often if there's no need to update video or audio. This will reduce unnecessary wakeups especially when paused, but may make handling of input events laggier when fd-based notifications are not supported (like most input on Windows). --- mplayer.c | 529 +++++++++++++++++++++++++++++--------------------------------- 1 file changed, 246 insertions(+), 283 deletions(-) (limited to 'mplayer.c') diff --git a/mplayer.c b/mplayer.c index c228f98d42..76c2001c69 100644 --- a/mplayer.c +++ b/mplayer.c @@ -2504,7 +2504,7 @@ static int audio_start_sync(struct MPContext *mpctx, int playsize) return decode_audio(sh_audio, &ao->buffer, playsize); } -static int fill_audio_out_buffers(struct MPContext *mpctx) +static int fill_audio_out_buffers(struct MPContext *mpctx, double endpts) { struct MPOpts *opts = &mpctx->opts; struct ao *ao = mpctx->ao; @@ -2566,8 +2566,7 @@ static int fill_audio_out_buffers(struct MPContext *mpctx) t = GetTimer() - t; tt = t * 0.000001f; audio_time_usage += tt; - if (mpctx->timeline && modifiable_audio_format) { - double endpts = mpctx->timeline[mpctx->timeline_part + 1].start; + if (endpts != MP_NOPTS_VALUE && modifiable_audio_format) { double bytes = (endpts - written_audio_pts(mpctx) + audio_delay) * ao->bps / opts->playback_speed; if (playsize > bytes) { @@ -2609,65 +2608,6 @@ static int fill_audio_out_buffers(struct MPContext *mpctx) return -partial_fill; } -static int sleep_until_near_frame(struct MPContext *mpctx, float *time_frame, - bool sync_to_audio, float *aq_sleep_time) -{ - struct MPOpts *opts = &mpctx->opts; - double audio_limit = 2; - current_module = "calc_sleep_time"; - - if (mpctx->restart_playback) - return 0; - - *time_frame -= get_relative_time(mpctx); // reset timer - - if (sync_to_audio) { - float delay = ao_get_delay(mpctx->ao); - mp_dbg(MSGT_AVSYNC, MSGL_DBG2, "delay=%f\n", delay); - - if (opts->autosync) { - /* - * Adjust this raw delay value by calculating the expected - * delay for this frame and generating a new value which is - * weighted between the two. The higher autosync is, the - * closer to the delay value gets to that which "-nosound" - * would have used, and the longer it will take for A/V - * sync to settle at the right value (but it eventually will.) - * This settling time is very short for values below 100. - */ - float predicted = mpctx->delay / opts->playback_speed + *time_frame; - float difference = delay - predicted; - delay = predicted + difference / (float)opts->autosync; - } - - *time_frame = delay - mpctx->delay / opts->playback_speed; - - // delay = amount of audio buffered in soundcard/driver - delay = FFMIN(delay, 0.5); - delay = FFMAX(delay, 0.1); - audio_limit = delay; - } else { - // If we're lagging more than 200 ms behind the right playback rate, - // don't try to "catch up". - // If benchmark is set always output frames as fast as possible - // without sleeping. - if (*time_frame < -0.2 || opts->benchmark) - *time_frame = 0; - } - - double t = *time_frame - mpctx->video_out->flip_queue_offset; - - if (t <= 0.05) - return 0; - - t -= 0.05; - if (t > audio_limit * 0.6) - t = audio_limit * 0.5; - *aq_sleep_time += t; - mp_input_get_cmd(mpctx->input, t * 1000 + 1, 1); - return 1; -} - int reinit_video_chain(struct MPContext *mpctx) { struct MPOpts *opts = &mpctx->opts; @@ -3087,31 +3027,6 @@ void add_step_frame(struct MPContext *mpctx) unpause_player(mpctx); } -static void pause_loop(struct MPContext *mpctx) -{ - mp_cmd_t *cmd; - - update_pause_message(mpctx); - - while ((cmd = mp_input_get_cmd(mpctx->input, 20, 1)) == NULL - || cmd->id == MP_CMD_SET_MOUSE_POS || cmd->pausing == 4) { - if (cmd) { - cmd = mp_input_get_cmd(mpctx->input, 0, 0); - run_command(mpctx, cmd); - mp_cmd_free(cmd); - continue; - } - if (mpctx->sh_video && mpctx->video_out) - vo_check_events(mpctx->video_out); - update_osd_msg(mpctx); - int hack = vo_osd_changed(0); - vo_osd_changed(hack); - if (hack || mpctx->sh_video && mpctx->video_out->want_redraw) - break; - update_pause_message(mpctx); - } -} - static void reinit_decoders(struct MPContext *mpctx) { reinit_video_chain(mpctx); @@ -3131,7 +3046,6 @@ static void seek_reset(struct MPContext *mpctx, bool reset_ao) mpctx->sh_video->last_pts = MP_NOPTS_VALUE; mpctx->delay = 0; mpctx->time_frame = 0; - mpctx->restart_playback = true; // Not all demuxers set d_video->pts during seek, so this value // (which is used by at least vobsub code below) may be completely // wrong (probably 0). @@ -3159,6 +3073,7 @@ static void seek_reset(struct MPContext *mpctx, bool reset_ao) vobsub_seek(vo_vobsub, mpctx->sh_video->pts); } + mpctx->restart_playback = true; mpctx->hrseek_active = false; mpctx->hrseek_framedrop = false; mpctx->total_avsync_change = 0; @@ -3489,8 +3404,20 @@ int seek_chapter(struct MPContext *mpctx, int chapter, double *seek_pts) static void run_playloop(struct MPContext *mpctx) { struct MPOpts *opts = &mpctx->opts; - float aq_sleep_time = 0; bool full_audio_buffers = false; + bool audio_left = false, video_left = false; + double endpts = end_at.type == END_AT_TIME ? end_at.pos : MP_NOPTS_VALUE; + bool end_is_chapter = false; + double sleeptime = 0.5; + bool was_restart = mpctx->restart_playback; + + if (mpctx->timeline) { + double end = mpctx->timeline[mpctx->timeline_part + 1].start; + if (endpts == MP_NOPTS_VALUE || end < endpts) { + endpts = end; + end_is_chapter = true; + } + } if (opts->chapterrange[1] > 0) { int cur_chapter = get_current_chapter(mpctx); @@ -3504,79 +3431,27 @@ static void run_playloop(struct MPContext *mpctx) reinit_audio_chain(mpctx); } - /*========================== PLAY AUDIO ============================*/ - - if (!mpctx->sh_video) - mpctx->restart_playback = false; + if (mpctx->step_frames && !mpctx->sh_video) { + mpctx->step_frames = 0; + pause_player(mpctx); + } if (mpctx->sh_audio && !mpctx->restart_playback) { - int status = fill_audio_out_buffers(mpctx); + int status = fill_audio_out_buffers(mpctx, endpts); full_audio_buffers = status >= 0 && !mpctx->ao->untimed; - if (status == -2) - // at eof, all audio at least written to ao - if (!mpctx->sh_video) - mpctx->stop_play = AT_END_OF_FILE; + // Not at audio stream EOF yet + audio_left = status > -2; } - - if (!mpctx->sh_video) { - if (mpctx->step_frames) { - mpctx->step_frames = 0; - pause_player(mpctx); - } - // handle audio-only case: - double a_pos = 0, a_buf = 0; - // sh_audio can be NULL due to video stream switching - // TODO: handle this better - if (mpctx->sh_audio) { - a_buf = ao_get_delay(mpctx->ao); - a_pos = written_audio_pts(mpctx) - mpctx->opts.playback_speed * - a_buf; - } - - print_status(mpctx, a_pos, false); - - update_subtitles(mpctx, a_pos, mpctx->video_offset, false); - update_osd_msg(mpctx); - if (end_at.type == END_AT_TIME && end_at.pos < a_pos) - mpctx->stop_play = AT_END_OF_FILE; - else if (mpctx->timeline && mpctx->stop_play == AT_END_OF_FILE - && mpctx->timeline_part + 1 < mpctx->num_timeline_parts - && mpctx->sh_audio) { - struct timeline_part *p = mpctx->timeline + mpctx->timeline_part; - if (!opts->gapless_audio && p->source != (p + 1)->source - && a_buf > 0.05) { - mpctx->stop_play = KEEP_PLAYING; - mp_input_get_cmd(mpctx->input, (a_buf - .05) * 1000, true); - } else { - seek(mpctx, (struct seek_params){ .type = MPSEEK_ABSOLUTE, - .amount = (p + 1)->start }, - true); - } - } else if (!mpctx->stop_play) { - int sleep_time = 100; - if (mpctx->sh_audio) { - if (mpctx->ao->untimed) - sleep_time = 0; - else if (full_audio_buffers) - sleep_time = FFMAX(20, a_buf * 1000 - 50); - else - sleep_time = 20; - sleep_time = FFMIN(sleep_time, 100); - } - mp_input_get_cmd(mpctx->input, sleep_time, true); - } - } else { - - /*========================== PLAY VIDEO ============================*/ - + double buffered_audio = -1; + while (mpctx->sh_video) { // never loops, for "break;" only vo_pts = mpctx->sh_video->timer * 90000.0; vo_fps = mpctx->sh_video->fps; - bool blit_frame = mpctx->video_out->frame_loaded; - if (!blit_frame) { + video_left = mpctx->video_out->hasframe; + if (!mpctx->video_out->frame_loaded + && (!mpctx->paused || mpctx->restart_playback)) { double frame_time = update_video(mpctx); - blit_frame = mpctx->video_out->frame_loaded; mp_dbg(MSGT_AVSYNC, MSGL_DBG2, "*** ftime=%5.3f ***\n", frame_time); if (mpctx->sh_video->vf_initialized < 0) { mp_tmsg(MSGT_CPLAYER, MSGL_FATAL, @@ -3585,25 +3460,14 @@ static void run_playloop(struct MPContext *mpctx) mpctx->stop_play = PT_NEXT_ENTRY; return; } - if (frame_time < 0) - mpctx->stop_play = AT_END_OF_FILE; - else if (!mpctx->restart_playback) { + video_left = frame_time >= 0; + if (endpts != MP_NOPTS_VALUE) + video_left &= mpctx->sh_video->pts < endpts; + if (video_left && !mpctx->restart_playback) { mpctx->time_frame += frame_time / opts->playback_speed; adjust_sync(mpctx, frame_time); } } - if (mpctx->timeline) { - struct timeline_part *next = - mpctx->timeline + mpctx->timeline_part + 1; - if (mpctx->sh_video->pts >= next->start - || mpctx->stop_play == AT_END_OF_FILE - && mpctx->timeline_part + 1 < mpctx->num_timeline_parts) { - seek(mpctx, (struct seek_params){ .type = MPSEEK_ABSOLUTE, - .amount = next->start }, - true); - return; - } - } // ================================================================ @@ -3625,78 +3489,116 @@ static void run_playloop(struct MPContext *mpctx) } } - bool frame_time_remaining = sleep_until_near_frame(mpctx, - &mpctx->time_frame, - full_audio_buffers, - &aq_sleep_time); + if (!video_left || (mpctx->paused && !mpctx->restart_playback)) + break; + if (!mpctx->video_out->frame_loaded) { + sleeptime = 0; + break; + } + + mpctx->time_frame -= get_relative_time(mpctx); + if (full_audio_buffers && !mpctx->restart_playback) { + buffered_audio = ao_get_delay(mpctx->ao); + mp_dbg(MSGT_AVSYNC, MSGL_DBG2, "delay=%f\n", buffered_audio); + + if (opts->autosync) { + /* Smooth reported playback position from AO by averaging + * it with the value expected based on previus value and + * time elapsed since then. May help smooth video timing + * with audio output that have inaccurate position reporting. + * This is badly implemented; the behavior of the smoothing + * now undesirably depends on how often this code runs + * (mainly depends on video frame rate). */ + float predicted = (mpctx->delay / opts->playback_speed + + mpctx->time_frame); + float difference = buffered_audio - predicted; + buffered_audio = predicted + difference / opts->autosync; + } + + mpctx->time_frame = (buffered_audio - + mpctx->delay / opts->playback_speed); + } else { + /* If we're more than 200 ms behind the right playback + * position, don't try to speed up display of following + * frames to catch up; continue with default speed from + * the current frame instead. + * If benchmark is set always output frames immediately + * without sleeping. + */ + if (mpctx->time_frame < -0.2 || opts->benchmark) + mpctx->time_frame = 0; + } + + double vsleep = mpctx->time_frame - mpctx->video_out->flip_queue_offset; + if (vsleep > 0.050) { + sleeptime = FFMIN(sleeptime, vsleep - 0.040); + break; + } + sleeptime = 0; //=================== FLIP PAGE (VIDEO BLT): ====================== current_module = "flip_page"; - if (!frame_time_remaining && blit_frame) { - vo_new_frame_imminent(mpctx->video_out); - struct sh_video *sh_video = mpctx->sh_video; - mpctx->video_pts = sh_video->pts; - update_subtitles(mpctx, sh_video->pts, mpctx->video_offset, false); - update_teletext(sh_video, mpctx->demuxer, 0); - update_osd_msg(mpctx); - struct vf_instance *vf = sh_video->vfilter; - mpctx->osd->pts = mpctx->video_pts; - vf->control(vf, VFCTRL_DRAW_EOSD, mpctx->osd); - vf->control(vf, VFCTRL_DRAW_OSD, mpctx->osd); - vo_osd_changed(0); - - mpctx->time_frame -= mpctx->video_out->flip_queue_offset; - aq_sleep_time += mpctx->time_frame; - // flag 256 means: libvo driver does its timing (dvb card) - if (mpctx->time_frame > 0.001 - && !(mpctx->sh_video->output_flags & VFCAP_TIMER)) - mpctx->time_frame = timing_sleep(mpctx, mpctx->time_frame); - mpctx->time_frame += mpctx->video_out->flip_queue_offset; - - unsigned int t2 = GetTimer(); - /* Playing with playback speed it's possible to get pathological - * cases with mpctx->time_frame negative enough to cause an - * overflow in pts_us calculation, thus the FFMAX. */ - double time_frame = FFMAX(mpctx->time_frame, -1); - unsigned int pts_us = mpctx->last_time + time_frame * 1e6; - int duration = -1; - double pts2 = mpctx->video_out->next_pts2; - if (pts2 != MP_NOPTS_VALUE && opts->correct_pts - && !mpctx->restart_playback) { - // expected A/V sync correction is ignored - double diff = (pts2 - mpctx->video_pts); - diff /= opts->playback_speed; - if (mpctx->time_frame < 0) - diff += mpctx->time_frame; - if (diff < 0) - diff = 0; - if (diff > 10) - diff = 10; - duration = diff * 1e6; - } - vo_flip_page(mpctx->video_out, pts_us | 1, duration); - - mpctx->last_vo_flip_duration = (GetTimer() - t2) * 0.000001; - vout_time_usage += mpctx->last_vo_flip_duration; - if (mpctx->video_out->driver->flip_page_timed) { - // No need to adjust sync based on flip speed - mpctx->last_vo_flip_duration = 0; - // For print_status - VO call finishing early is OK for sync - mpctx->time_frame -= get_relative_time(mpctx); - } - if (mpctx->restart_playback) { - mpctx->syncing_audio = true; - if (mpctx->sh_audio) - fill_audio_out_buffers(mpctx); - mpctx->restart_playback = false; - mpctx->time_frame = 0; - get_relative_time(mpctx); - } - print_status(mpctx, MP_NOPTS_VALUE, true); - screenshot_flip(mpctx); - } else - print_status(mpctx, MP_NOPTS_VALUE, false); + vo_new_frame_imminent(mpctx->video_out); + struct sh_video *sh_video = mpctx->sh_video; + mpctx->video_pts = sh_video->pts; + update_subtitles(mpctx, sh_video->pts, mpctx->video_offset, false); + update_teletext(sh_video, mpctx->demuxer, 0); + update_osd_msg(mpctx); + struct vf_instance *vf = sh_video->vfilter; + mpctx->osd->pts = mpctx->video_pts; + vf->control(vf, VFCTRL_DRAW_EOSD, mpctx->osd); + vf->control(vf, VFCTRL_DRAW_OSD, mpctx->osd); + vo_osd_changed(0); + + mpctx->time_frame -= mpctx->video_out->flip_queue_offset; + float aq_sleep_time = mpctx->time_frame; + if (mpctx->time_frame > 0.001 + && !(mpctx->sh_video->output_flags & VFCAP_TIMER)) + mpctx->time_frame = timing_sleep(mpctx, mpctx->time_frame); + mpctx->time_frame += mpctx->video_out->flip_queue_offset; + + unsigned int t2 = GetTimer(); + /* Playing with playback speed it's possible to get pathological + * cases with mpctx->time_frame negative enough to cause an + * overflow in pts_us calculation, thus the FFMAX. */ + double time_frame = FFMAX(mpctx->time_frame, -1); + unsigned int pts_us = mpctx->last_time + time_frame * 1e6; + int duration = -1; + double pts2 = mpctx->video_out->next_pts2; + if (pts2 != MP_NOPTS_VALUE && opts->correct_pts && + !mpctx->restart_playback) { + // expected A/V sync correction is ignored + double diff = (pts2 - mpctx->video_pts); + diff /= opts->playback_speed; + if (mpctx->time_frame < 0) + diff += mpctx->time_frame; + if (diff < 0) + diff = 0; + if (diff > 10) + diff = 10; + duration = diff * 1e6; + } + vo_flip_page(mpctx->video_out, pts_us | 1, duration); + + mpctx->last_vo_flip_duration = (GetTimer() - t2) * 0.000001; + vout_time_usage += mpctx->last_vo_flip_duration; + if (mpctx->video_out->driver->flip_page_timed) { + // No need to adjust sync based on flip speed + mpctx->last_vo_flip_duration = 0; + // For print_status - VO call finishing early is OK for sync + mpctx->time_frame -= get_relative_time(mpctx); + } + if (mpctx->restart_playback) { + mpctx->syncing_audio = true; + if (mpctx->sh_audio) + fill_audio_out_buffers(mpctx, endpts); + mpctx->restart_playback = false; + mpctx->time_frame = 0; + get_relative_time(mpctx); + } + print_status(mpctx, MP_NOPTS_VALUE, true); + screenshot_flip(mpctx); if (opts->auto_quality > 0) { current_module = "autoq"; @@ -3709,25 +3611,18 @@ static void run_playloop(struct MPContext *mpctx) set_video_quality(mpctx->sh_video, output_quality); } - if (!frame_time_remaining && blit_frame) { - if (play_n_frames >= 0) { - --play_n_frames; - if (play_n_frames <= 0) - mpctx->stop_play = PT_NEXT_ENTRY; - } - if (mpctx->step_frames > 0) { - mpctx->step_frames--; - if (mpctx->step_frames == 0) - pause_player(mpctx); - } + if (play_n_frames >= 0) { + --play_n_frames; + if (play_n_frames <= 0) + mpctx->stop_play = PT_NEXT_ENTRY; } - - // FIXME: add size based support for -endpos - if (end_at.type == END_AT_TIME && - !frame_time_remaining && end_at.pos <= mpctx->sh_video->pts) - mpctx->stop_play = PT_NEXT_ENTRY; - - } // end if(mpctx->sh_video) + if (mpctx->step_frames > 0) { + mpctx->step_frames--; + if (mpctx->step_frames == 0) + pause_player(mpctx); + } + break; + } // video #ifdef CONFIG_DVDNAV if (mpctx->stream->type == STREAMTYPE_DVDNAV) { @@ -3753,48 +3648,116 @@ static void run_playloop(struct MPContext *mpctx) } #endif - //================= Keyboard events, SEEKing ==================== + if (mpctx->restart_playback && !video_left) { + if (mpctx->sh_audio) { + int status = fill_audio_out_buffers(mpctx, endpts); + full_audio_buffers = status >= 0 && !mpctx->ao->untimed; + // Not at audio stream EOF yet + audio_left = status > -2; + } + mpctx->restart_playback = false; + } + if (mpctx->sh_audio && buffered_audio == -1) + buffered_audio = mpctx->paused ? 0 : ao_get_delay(mpctx->ao); - current_module = "key_events"; + update_osd_msg(mpctx); + if (mpctx->paused) + update_pause_message(mpctx); + if (!video_left && (!mpctx->paused || was_restart)) { + double a_pos = 0; + if (mpctx->sh_audio) { + a_pos = (written_audio_pts(mpctx) - + mpctx->opts.playback_speed * buffered_audio); + } + print_status(mpctx, a_pos, false); - while (1) { - mp_cmd_t *cmd; - while ((cmd = mp_input_get_cmd(mpctx->input, 0, 1)) != NULL) { - /* Allow running consecutive seek commands to combine them, - * but execute the seek before running other commands. - * If the user seeks continuously (keeps arrow key down) - * try to finish showing a frame from one location before doing - * another seek (which could lead to unchanging display). */ - if (mpctx->seek.type && cmd->id != MP_CMD_SEEK - || mpctx->restart_playback && cmd->id == MP_CMD_SEEK - && GetTimerMS() - mpctx->start_timestamp < 300) - break; - cmd = mp_input_get_cmd(mpctx->input, 0, 0); - run_command(mpctx, cmd); - mp_cmd_free(cmd); - if (mpctx->stop_play) - break; + if (!mpctx->sh_video) + update_subtitles(mpctx, a_pos, mpctx->video_offset, false); + } + + /* It's possible for the user to simultaneously switch both audio + * and video streams to "disabled" at runtime. Handle this by waiting + * rather than immediately stopping playback due to EOF. + * + * When all audio has been written to output driver, stay in the + * main loop handling commands until it has been mostly consumed, + * except in the gapless case, where the next file will be started + * while audio from the current one still remains to be played. + * + * We want this check to trigger if we seeked to this position, + * but not if we paused at it with audio possibly still buffered in + * the AO. There's currently no working way to check buffered audio + * inside AO while paused. Thus the "was_restart" check below, which + * should trigger after seek only, when we know there's no audio + * buffered. + */ + if ((mpctx->sh_audio || mpctx->sh_video) && !audio_left && !video_left + && (opts->gapless_audio || buffered_audio < 0.05) + && (!mpctx->paused || was_restart)) { + if (end_is_chapter) { + seek(mpctx, (struct seek_params){ + .type = MPSEEK_ABSOLUTE, + .amount = mpctx->timeline[mpctx->timeline_part+1].start + }, true); + } else + mpctx->stop_play = AT_END_OF_FILE; + } else if (!mpctx->stop_play) { + double audio_sleep = 9; + if (mpctx->sh_audio && !mpctx->paused) { + if (mpctx->ao->untimed) { + if (!mpctx->sh_video) + audio_sleep = 0; + } else if (full_audio_buffers) { + audio_sleep = buffered_audio - 0.050; + // Keep extra safety margin if the buffers are large + if (audio_sleep > 0.100) + audio_sleep = FFMAX(audio_sleep - 0.200, 0.100); + else + audio_sleep = FFMAX(audio_sleep, 0.020); + } else + audio_sleep = 0.020; } - bool slow_video = mpctx->sh_video && mpctx->video_out->frame_loaded; - if (!(mpctx->paused || slow_video) || mpctx->stop_play - || mpctx->seek.type || mpctx->restart_playback) - break; - if (mpctx->sh_video) { - update_osd_msg(mpctx); + sleeptime = FFMIN(sleeptime, audio_sleep); + if (sleeptime > 0) { + if (!mpctx->sh_video) + goto novideo; int hack = vo_osd_changed(0); vo_osd_changed(hack); if (hack || mpctx->video_out->want_redraw) { if (redraw_osd(mpctx) < 0) { - if (mpctx->paused) + if (mpctx->paused && video_left) add_step_frame(mpctx); - break; + else + goto novideo; } else vo_osd_changed(0); + } else { + novideo: + mp_input_get_cmd(mpctx->input, sleeptime * 1000, true); } } - if (!mpctx->paused) + } + + //================= Keyboard events, SEEKing ==================== + + current_module = "key_events"; + + mp_cmd_t *cmd; + while ((cmd = mp_input_get_cmd(mpctx->input, 0, 1)) != NULL) { + /* Allow running consecutive seek commands to combine them, + * but execute the seek before running other commands. + * If the user seeks continuously (keeps arrow key down) + * try to finish showing a frame from one location before doing + * another seek (which could lead to unchanging display). */ + if (mpctx->seek.type && cmd->id != MP_CMD_SEEK + || mpctx->restart_playback && cmd->id == MP_CMD_SEEK + && GetTimerMS() - mpctx->start_timestamp < 300) + break; + cmd = mp_input_get_cmd(mpctx->input, 0, 0); + run_command(mpctx, cmd); + mp_cmd_free(cmd); + if (mpctx->stop_play) break; - pause_loop(mpctx); } // handle -sstep -- cgit v1.2.3 From ec58e5a3848f82681839eaa15d2c6912d92e74ed Mon Sep 17 00:00:00 2001 From: Uoti Urpala Date: Sat, 21 Jan 2012 09:28:07 +0200 Subject: options: move mixer.h options to struct --- mplayer.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'mplayer.c') diff --git a/mplayer.c b/mplayer.c index 76c2001c69..7ca2d0303f 100644 --- a/mplayer.c +++ b/mplayer.c @@ -1776,6 +1776,7 @@ void reinit_audio_chain(struct MPContext *mpctx) if (!(mpctx->initialized_flags & INITIALIZED_AO)) { mpctx->initialized_flags |= INITIALIZED_AO; mpctx->ao = ao_create(); + mpctx->ao->opts = opts; mpctx->ao->samplerate = force_srate; mpctx->ao->format = opts->audio_output_format; } @@ -1823,6 +1824,8 @@ void reinit_audio_chain(struct MPContext *mpctx) } mpctx->mixer.ao = ao; mpctx->mixer.volstep = volstep; + mpctx->mixer.softvol = opts->softvol; + mpctx->mixer.softvol_max = opts->softvol_max; mpctx->syncing_audio = true; return; -- cgit v1.2.3 From fd50478659e56db84e8564434702cdc8291ac854 Mon Sep 17 00:00:00 2001 From: Uoti Urpala Date: Fri, 16 Mar 2012 08:04:24 +0200 Subject: core: improve sub and audio start after timeline part switch When switching to a timeline part from another file, decoders were reinitialized after doing the demuxer-level seek. This is necessary for audio because some decoders read from the demuxer stream during initialization and the previous stream position before seek could have been at EOF. However, this initialization sequence could lose first subtitles or first part of audio. The problem for subtitles was that the seek itself or audio initialization could already have buffered subtitle packets from the new position, and the way subtitles are reinitialized flushes packet buffers. Thus early subtitles could be lost (even if they were demuxed - unfortunately demuxers may not know about still active subtitles earlier in the file, but that's another issue). Fix this by moving subtitle and video reinitialization before the demuxer seek; they don't have the problems which prevent that for audio. Audio initialization can already decode and buffer some output. However, the seek_reset() call done last would then throw away this buffered output. Work around this by adding an extra flag to seek_reset(). --- mplayer.c | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) (limited to 'mplayer.c') diff --git a/mplayer.c b/mplayer.c index 7ca2d0303f..3803dffe79 100644 --- a/mplayer.c +++ b/mplayer.c @@ -3030,14 +3030,7 @@ void add_step_frame(struct MPContext *mpctx) unpause_player(mpctx); } -static void reinit_decoders(struct MPContext *mpctx) -{ - reinit_video_chain(mpctx); - reinit_audio_chain(mpctx); - mp_property_do("sub", M_PROPERTY_SET, &(int){mpctx->global_sub_pos}, mpctx); -} - -static void seek_reset(struct MPContext *mpctx, bool reset_ao) +static void seek_reset(struct MPContext *mpctx, bool reset_ao, bool reset_ac) { if (mpctx->sh_video) { current_module = "seek_video_reset"; @@ -3059,7 +3052,7 @@ static void seek_reset(struct MPContext *mpctx, bool reset_ao) update_teletext(mpctx->sh_video, mpctx->demuxer, 1); } - if (mpctx->sh_audio) { + if (mpctx->sh_audio && reset_ac) { current_module = "seek_audio_reset"; resync_audio_stream(mpctx->sh_audio); if (reset_ao) @@ -3168,13 +3161,19 @@ static int seek(MPContext *mpctx, struct seek_params seek, if (demuxer_amount == -1) { mpctx->stop_play = AT_END_OF_FILE; // Clear audio from current position - if (mpctx->sh_audio) { + if (mpctx->sh_audio && !timeline_fallthrough) { ao_reset(mpctx->ao); mpctx->sh_audio->a_buffer_len = 0; } return -1; } } + if (need_reset) { + reinit_video_chain(mpctx); + mp_property_do("sub", M_PROPERTY_SET, &(int){mpctx->global_sub_pos}, + mpctx); + } + int demuxer_style = 0; switch (seek.type) { case MPSEEK_FACTOR: @@ -3191,12 +3190,19 @@ static int seek(MPContext *mpctx, struct seek_params seek, demuxer_amount -= opts->hr_seek_demuxer_offset; int seekresult = demux_seek(mpctx->demuxer, demuxer_amount, audio_delay, demuxer_style); - if (need_reset) - reinit_decoders(mpctx); - if (seekresult == 0) + if (seekresult == 0) { + if (need_reset) { + reinit_audio_chain(mpctx); + seek_reset(mpctx, !timeline_fallthrough, false); + } return -1; + } - seek_reset(mpctx, !timeline_fallthrough); + if (need_reset) + reinit_audio_chain(mpctx); + /* If we just reinitialized audio it doesn't need to be reset, + * and resetting could lose audio some decoders produce during init. */ + seek_reset(mpctx, !timeline_fallthrough, !need_reset); /* Use the target time as "current position" for further relative * seeks etc until a new video frame has been decoded */ @@ -3384,7 +3390,7 @@ int seek_chapter(struct MPContext *mpctx, int chapter, double *seek_pts) int res = demuxer_seek_chapter(mpctx->demuxer, chapter, seek_pts); if (res >= 0) { if (*seek_pts == -1) - seek_reset(mpctx, true); + seek_reset(mpctx, true, true); else { mpctx->last_chapter_seek = res; mpctx->last_chapter_pts = *seek_pts; -- cgit v1.2.3 From 06e3dc8eba756778e0cca6f55e24f0ae39d4489f Mon Sep 17 00:00:00 2001 From: Uoti Urpala Date: Tue, 20 Mar 2012 02:54:19 +0200 Subject: timeline: subs: keep subtitle tracks in source time Timeline handling converted the pts values from demuxed subtitles to timeline scale. Change the code to do most subtitle handling in original subtitle source pts, and instead convert current playback timeline pts to those units when deciding which subtitle to show. The main functionality changes are that now demuxed subtitles which overlap chapter boundaries are handled correctly (at least for libass subtitles), and external subtitles are assumed to use same pts scale as current source (this needs improvements later). Before, a video subtitle that had a duration continuing past the end of the chapter would continue to be shown for the original duration, even if the chapter ended and playback switched to a position in the source where the subtitle shouldn't exist. Now, the subtitle will correctly end. Before, external subtitle files were interpreted as specifying pts values in timeline scale. Now, they're interpreted as specifying pts values in source file time scale, for _every_ source file. This is probably more likely to be what the user wants for the "main" source file in case there is one, but almost certainly not quite right for multiple source files where the same subs could be shown over different scenes. If the user wants them to match some main source file, it's probably still better to have incorrect extra subs for video from some files than to have every subtitle appearing at the wrong time. The new code makes it easier to change the interpretation of the subtitle times, and some configurability should be added in the future. --- mplayer.c | 59 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 29 insertions(+), 30 deletions(-) (limited to 'mplayer.c') diff --git a/mplayer.c b/mplayer.c index 3803dffe79..4f7027ec79 100644 --- a/mplayer.c +++ b/mplayer.c @@ -1905,13 +1905,14 @@ static bool is_av_sub(int type) return type == 'b' || type == 'p' || type == 'x'; } -void update_subtitles(struct MPContext *mpctx, double refpts, - double sub_offset, bool reset) +void update_subtitles(struct MPContext *mpctx, double refpts_tl, bool reset) { + mpctx->osd->sub_offset = mpctx->video_offset; struct MPOpts *opts = &mpctx->opts; struct sh_video *sh_video = mpctx->sh_video; struct demux_stream *d_sub = mpctx->d_sub; - double curpts = refpts + sub_delay; + double refpts_s = refpts_tl - mpctx->osd->sub_offset; + double curpts_s = refpts_s + sub_delay; unsigned char *packet = NULL; int len; struct sh_sub *sh_sub = d_sub->sh; @@ -1936,7 +1937,7 @@ void update_subtitles(struct MPContext *mpctx, double refpts, if (sub_fps == 0) sub_fps = sh_video ? sh_video->fps : 25; current_module = "find_sub"; - find_sub(mpctx, mpctx->subdata, curpts * + find_sub(mpctx, mpctx->subdata, curpts_s * (mpctx->subdata->sub_uses_time ? 100. : sub_fps)); if (vo_sub) mpctx->vo_sub_last = vo_sub; @@ -1951,13 +1952,13 @@ void update_subtitles(struct MPContext *mpctx, double refpts, // Vobsub len = 0; if (vo_vobsub) { - if (curpts >= 0) { - len = vobsub_get_packet(vo_vobsub, curpts, + if (curpts_s >= 0) { + len = vobsub_get_packet(vo_vobsub, curpts_s, (void **)&packet, ×tamp); if (len > 0) mp_dbg(MSGT_CPLAYER, MSGL_V, "\rVOB sub: len=%d " "v_pts=%5.3f v_timer=%5.3f sub=%5.3f ts=%d \n", - len, refpts, sh_video->timer, + len, refpts_s, sh_video->timer, timestamp / 90000.0, timestamp); } } else { @@ -1969,14 +1970,14 @@ void update_subtitles(struct MPContext *mpctx, double refpts, // d_video->pts which would have been the simplest // improvement doesn't work because mpeg specific hacks // in video.c set d_video->pts to 0. - float x = d_sub->pts - refpts; + float x = d_sub->pts - refpts_s; if (x > -20 && x < 20) // prevent missing subs on pts reset timestamp = 90000 * d_sub->pts; else - timestamp = 90000 * curpts; + timestamp = 90000 * curpts_s; mp_dbg(MSGT_CPLAYER, MSGL_V, "\rDVD sub: len=%d " "v_pts=%5.3f s_pts=%5.3f ts=%d \n", len, - refpts, d_sub->pts, timestamp); + refpts_s, d_sub->pts, timestamp); } } if (len <= 0 || !packet) @@ -2002,19 +2003,19 @@ void update_subtitles(struct MPContext *mpctx, double refpts, ds_get_next_pts(d_sub); while (d_sub->first) { - double subpts = ds_get_next_pts(d_sub) + sub_offset; - if (subpts > curpts) { + double subpts_s = ds_get_next_pts(d_sub); + if (subpts_s > curpts_s) { // Libass handled subs can be fed to it in advance if (!opts->ass_enabled || !is_text_sub(type)) break; // Try to avoid demuxing whole file at once - if (d_sub->non_interleaved && subpts > curpts + 1) + if (d_sub->non_interleaved && subpts_s > curpts_s + 1) break; } double duration = d_sub->first->duration; len = ds_get_packet_sub(d_sub, &packet); if (is_av_sub(type)) { - int ret = decode_avsub(sh_sub, packet, len, subpts, duration); + int ret = decode_avsub(sh_sub, packet, len, subpts_s, duration); if (ret < 0) mp_msg(MSGT_SPUDEC, MSGL_WARN, "lavc failed decoding " "subtitle\n"); @@ -2043,10 +2044,10 @@ void update_subtitles(struct MPContext *mpctx, double refpts, continue; } if (sh_sub && sh_sub->active) { - sub_decode(sh_sub, mpctx->osd, packet, len, subpts, duration); + sub_decode(sh_sub, mpctx->osd, packet, len, subpts_s, duration); continue; } - if (subpts != MP_NOPTS_VALUE) { + if (subpts_s != MP_NOPTS_VALUE) { if (duration < 0) sub_clear_text(&subs, MP_NOPTS_VALUE); if (type == 'a') { // ssa/ass subs without libass => convert to plaintext @@ -2060,21 +2061,21 @@ void update_subtitles(struct MPContext *mpctx, double refpts, len -= p - packet; packet = p; } - double endpts = MP_NOPTS_VALUE; - if (subpts != MP_NOPTS_VALUE && duration >= 0) - endpts = subpts + duration; - sub_add_text(&subs, packet, len, endpts); + double endpts_s = MP_NOPTS_VALUE; + if (subpts_s != MP_NOPTS_VALUE && duration >= 0) + endpts_s = subpts_s + duration; + sub_add_text(&subs, packet, len, endpts_s); set_osd_subtitle(mpctx, &subs); } if (d_sub->non_interleaved) ds_get_next_pts(d_sub); } if (!opts->ass_enabled) - if (sub_clear_text(&subs, curpts)) + if (sub_clear_text(&subs, curpts_s)) set_osd_subtitle(mpctx, &subs); } if (vo_spudec) { - spudec_heartbeat(vo_spudec, 90000 * curpts); + spudec_heartbeat(vo_spudec, 90000 * curpts_s); if (spudec_changed(vo_spudec)) vo_osd_changed(OSDTYPE_SPU); } @@ -3014,7 +3015,7 @@ static int redraw_osd(struct MPContext *mpctx) return -1; if (vo_redraw_frame(mpctx->video_out) < 0) return -1; - mpctx->osd->pts = mpctx->video_pts; + mpctx->osd->pts = mpctx->video_pts - mpctx->osd->sub_offset; if (!(sh_video->output_flags & VFCAP_EOSD_FILTER)) vf->control(vf, VFCTRL_DRAW_EOSD, mpctx->osd); vf->control(vf, VFCTRL_DRAW_OSD, mpctx->osd); @@ -3047,8 +3048,7 @@ static void seek_reset(struct MPContext *mpctx, bool reset_ao, bool reset_ac) // wrong (probably 0). mpctx->sh_video->pts = mpctx->d_video->pts + mpctx->video_offset; mpctx->video_pts = mpctx->sh_video->pts; - update_subtitles(mpctx, mpctx->sh_video->pts, mpctx->video_offset, - true); + update_subtitles(mpctx, mpctx->sh_video->pts, true); update_teletext(mpctx->sh_video, mpctx->demuxer, 1); } @@ -3060,8 +3060,7 @@ static void seek_reset(struct MPContext *mpctx, bool reset_ao, bool reset_ac) mpctx->ao->buffer.len = mpctx->ao->buffer_playable_size; mpctx->sh_audio->a_buffer_len = 0; if (!mpctx->sh_video) - update_subtitles(mpctx, mpctx->sh_audio->pts, - mpctx->video_offset, true); + update_subtitles(mpctx, mpctx->sh_audio->pts, true); } if (vo_vobsub && mpctx->sh_video) { @@ -3551,11 +3550,11 @@ static void run_playloop(struct MPContext *mpctx) vo_new_frame_imminent(mpctx->video_out); struct sh_video *sh_video = mpctx->sh_video; mpctx->video_pts = sh_video->pts; - update_subtitles(mpctx, sh_video->pts, mpctx->video_offset, false); + update_subtitles(mpctx, sh_video->pts, false); update_teletext(sh_video, mpctx->demuxer, 0); update_osd_msg(mpctx); struct vf_instance *vf = sh_video->vfilter; - mpctx->osd->pts = mpctx->video_pts; + mpctx->osd->pts = mpctx->video_pts - mpctx->osd->sub_offset; vf->control(vf, VFCTRL_DRAW_EOSD, mpctx->osd); vf->control(vf, VFCTRL_DRAW_OSD, mpctx->osd); vo_osd_changed(0); @@ -3681,7 +3680,7 @@ static void run_playloop(struct MPContext *mpctx) print_status(mpctx, a_pos, false); if (!mpctx->sh_video) - update_subtitles(mpctx, a_pos, mpctx->video_offset, false); + update_subtitles(mpctx, a_pos, false); } /* It's possible for the user to simultaneously switch both audio -- cgit v1.2.3 From a0de4bc5000823c058aab6b02a7fdfb48275cb32 Mon Sep 17 00:00:00 2001 From: Uoti Urpala Date: Sun, 25 Mar 2012 22:58:48 +0300 Subject: ao_pulse, core: make pulse thread wake up core for more data For ao_pulse, the current latency is not a good indicator of how soon the AO requires new data to avoid underflow. Add an internal pipe that can be used to wake up the input loop from select(), and make the pulseaudio main loop (which runs in a separate thread) use this mechanism when pulse requests more data. The wakeup signal currently contains no information about the reason for the wakup, but audio buffers are always filled when the event loop wakes up. Also, request a latency of 1 second from the Pulseaudio server. The default is normally significantly higher. We don't need low latency, while higher latency helps prevent underflows reduces need for wakeups. --- mplayer.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'mplayer.c') diff --git a/mplayer.c b/mplayer.c index 4f7027ec79..eb278353e3 100644 --- a/mplayer.c +++ b/mplayer.c @@ -1775,8 +1775,7 @@ void reinit_audio_chain(struct MPContext *mpctx) current_module = "af_preinit"; if (!(mpctx->initialized_flags & INITIALIZED_AO)) { mpctx->initialized_flags |= INITIALIZED_AO; - mpctx->ao = ao_create(); - mpctx->ao->opts = opts; + mpctx->ao = ao_create(opts, mpctx->input); mpctx->ao->samplerate = force_srate; mpctx->ao->format = opts->audio_output_format; } -- cgit v1.2.3