// Return pts value corresponding to currently playing audio. double playing_audio_pts(struct MPContext *mpctx) { double pts = written_audio_pts(mpctx); if (pts == MP_NOPTS_VALUE) return pts; return pts - mpctx->opts->playback_speed * ao_get_delay(mpctx->ao); }
/* Modify video timing to match the audio timeline. There are two main * reasons this is needed. First, video and audio can start from different * positions at beginning of file or after a seek (MPlayer starts both * immediately even if they have different pts). Second, the file can have * audio timestamps that are inconsistent with the duration of the audio * packets, for example two consecutive timestamp values differing by * one second but only a packet with enough samples for half a second * of playback between them. */ static void adjust_sync(struct MPContext *mpctx, double v_pts, double frame_time) { struct MPOpts *opts = mpctx->opts; if (mpctx->audio_status != STATUS_PLAYING) return; double a_pts = written_audio_pts(mpctx) + opts->audio_delay - mpctx->delay; double av_delay = a_pts - v_pts; double change = av_delay * 0.1; double max_change = opts->default_max_pts_correction >= 0 ? opts->default_max_pts_correction : frame_time * 0.1; if (change < -max_change) change = -max_change; else if (change > max_change) change = max_change; mpctx->delay += change; mpctx->total_avsync_change += change; }
/* Modify video timing to match the audio timeline. There are two main * reasons this is needed. First, video and audio can start from different * positions at beginning of file or after a seek (MPlayer starts both * immediately even if they have different pts). Second, the file can have * audio timestamps that are inconsistent with the duration of the audio * packets, for example two consecutive timestamp values differing by * one second but only a packet with enough samples for half a second * of playback between them. */ static void adjust_sync(struct MPContext *mpctx, double frame_time) { struct MPOpts *opts = mpctx->opts; if (mpctx->audio_status != STATUS_PLAYING) return; double a_pts = written_audio_pts(mpctx) - mpctx->delay; double v_pts = mpctx->video_next_pts; double av_delay = a_pts - v_pts; // Try to sync vo_flip() so it will *finish* at given time av_delay += mpctx->last_vo_flip_duration; av_delay += mpctx->audio_delay; // This much pts difference is desired double change = av_delay * 0.1; double max_change = opts->default_max_pts_correction >= 0 ? opts->default_max_pts_correction : frame_time * 0.1; if (change < -max_change) change = -max_change; else if (change > max_change) change = max_change; mpctx->delay += change; mpctx->total_avsync_change += change; }
int reinit_video_chain(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; assert(!mpctx->d_video); struct track *track = mpctx->current_track[0][STREAM_VIDEO]; struct sh_stream *sh = track ? track->stream : NULL; if (!sh) goto no_video; MP_VERBOSE(mpctx, "[V] fourcc:0x%X size:%dx%d fps:%5.3f\n", sh->format, sh->video->disp_w, sh->video->disp_h, sh->video->fps); //================== Init VIDEO (codec & libvo) ========================== if (!opts->fixed_vo || !mpctx->video_out) { struct vo_extra ex = { .input_ctx = mpctx->input, .osd = mpctx->osd, .encode_lavc_ctx = mpctx->encode_lavc_ctx, .opengl_cb_context = mpctx->gl_cb_ctx, }; mpctx->video_out = init_best_video_out(mpctx->global, &ex); if (!mpctx->video_out) { MP_FATAL(mpctx, "Error opening/initializing " "the selected video_out (-vo) device.\n"); mpctx->error_playing = MPV_ERROR_VO_INIT_FAILED; goto err_out; } mpctx->mouse_cursor_visible = true; } update_window_title(mpctx, true); struct dec_video *d_video = talloc_zero(NULL, struct dec_video); mpctx->d_video = d_video; d_video->global = mpctx->global; d_video->log = mp_log_new(d_video, mpctx->log, "!vd"); d_video->opts = mpctx->opts; d_video->header = sh; d_video->fps = sh->video->fps; d_video->vo = mpctx->video_out; if (opts->force_fps) { d_video->fps = opts->force_fps; MP_INFO(mpctx, "FPS forced to %5.3f.\n", d_video->fps); MP_INFO(mpctx, "Use --no-correct-pts to force FPS based timing.\n"); } update_fps(mpctx); vo_control(mpctx->video_out, VOCTRL_GET_HWDEC_INFO, &d_video->hwdec_info); recreate_video_filters(mpctx); if (!video_init_best_codec(d_video, opts->video_decoders)) goto err_out; bool saver_state = opts->pause || !opts->stop_screensaver; vo_control(mpctx->video_out, saver_state ? VOCTRL_RESTORE_SCREENSAVER : VOCTRL_KILL_SCREENSAVER, NULL); vo_set_paused(mpctx->video_out, mpctx->paused); mpctx->sync_audio_to_video = !sh->attached_picture; mpctx->vo_pts_history_seek_ts++; // If we switch on video again, ensure audio position matches up. if (mpctx->d_audio) mpctx->audio_status = STATUS_SYNCING; reset_video_state(mpctx); reset_subtitle_state(mpctx); return 1; err_out: no_video: uninit_video_chain(mpctx); if (track) error_on_track(mpctx, track); handle_force_window(mpctx, true); return 0; } // Try to refresh the video by doing a precise seek to the currently displayed // frame. This can go wrong in all sorts of ways, so use sparingly. void mp_force_video_refresh(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; struct dec_video *d_video = mpctx->d_video; if (!d_video || !d_video->decoder_output.imgfmt) return; // If not paused, the next frame should come soon enough. if (opts->pause && mpctx->last_vo_pts != MP_NOPTS_VALUE) queue_seek(mpctx, MPSEEK_ABSOLUTE, mpctx->last_vo_pts, 2, true); } static int check_framedrop(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; // check for frame-drop: if (mpctx->video_status == STATUS_PLAYING && !mpctx->paused && mpctx->audio_status == STATUS_PLAYING && !ao_untimed(mpctx->ao)) { float fps = mpctx->d_video->fps; double frame_time = fps > 0 ? 1.0 / fps : 0; // we should avoid dropping too many frames in sequence unless we // are too late. and we allow 100ms A-V delay here: if (mpctx->last_av_difference - 0.100 > mpctx->dropped_frames * frame_time) return !!(opts->frame_dropping & 2); } return 0; } // Read a packet, store decoded image into d_video->waiting_decoded_mpi // returns VD_* code static int decode_image(struct MPContext *mpctx) { struct dec_video *d_video = mpctx->d_video; if (d_video->header->attached_picture) { d_video->waiting_decoded_mpi = video_decode(d_video, d_video->header->attached_picture, 0); return VD_EOF; } struct demux_packet *pkt; if (demux_read_packet_async(d_video->header, &pkt) == 0) return VD_WAIT; if (pkt && pkt->pts != MP_NOPTS_VALUE) pkt->pts += mpctx->video_offset; if ((pkt && pkt->pts >= mpctx->hrseek_pts - .005) || d_video->has_broken_packet_pts || !mpctx->opts->hr_seek_framedrop) { mpctx->hrseek_framedrop = false; } bool hrseek = mpctx->hrseek_active && mpctx->video_status == STATUS_SYNCING; int framedrop_type = hrseek && mpctx->hrseek_framedrop ? 2 : check_framedrop(mpctx); d_video->waiting_decoded_mpi = video_decode(d_video, pkt, framedrop_type); bool had_packet = !!pkt; talloc_free(pkt); if (had_packet && !d_video->waiting_decoded_mpi && mpctx->video_status == STATUS_PLAYING) { mpctx->dropped_frames_total++; mpctx->dropped_frames++; } return had_packet ? VD_PROGRESS : VD_EOF; } // Called after video reinit. This can be generally used to try to insert more // filters using the filter chain edit functionality in command.c. static void init_filter_params(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; // Note that the filter chain is already initialized. This code might // recreate the chain a second time, which is not very elegant, but allows // us to test whether enabling deinterlacing works with the current video // format and other filters. if (opts->deinterlace >= 0) mp_property_do("deinterlace", M_PROPERTY_SET, &opts->deinterlace, mpctx); } // Feed newly decoded frames to the filter, take care of format changes. // If eof=true, drain the filter chain, and return VD_EOF if empty. static int video_filter(struct MPContext *mpctx, bool eof) { struct dec_video *d_video = mpctx->d_video; struct vf_chain *vf = d_video->vfilter; if (vf->initialized < 0) return VD_ERROR; // There is already a filtered frame available. // If vf_needs_input() returns > 0, the filter wants input anyway. if (vf_output_frame(vf, eof) > 0 && vf_needs_input(vf) < 1) return VD_PROGRESS; // Decoder output is different from filter input? bool need_vf_reconfig = !vf->input_params.imgfmt || vf->initialized < 1 || !mp_image_params_equal(&d_video->decoder_output, &vf->input_params); // (If imgfmt==0, nothing was decoded yet, and the format is unknown.) if (need_vf_reconfig && d_video->decoder_output.imgfmt) { // Drain the filter chain. if (vf_output_frame(vf, true) > 0) return VD_PROGRESS; // The filter chain is drained; execute the filter format change. filter_reconfig(mpctx, false); if (vf->initialized == 0) return VD_PROGRESS; // hw decoding fallback; try again if (vf->initialized < 1) return VD_ERROR; init_filter_params(mpctx); return VD_RECONFIG; } // If something was decoded, and the filter chain is ready, filter it. if (!need_vf_reconfig && d_video->waiting_decoded_mpi) { vf_filter_frame(vf, d_video->waiting_decoded_mpi); d_video->waiting_decoded_mpi = NULL; return VD_PROGRESS; } return eof ? VD_EOF : VD_PROGRESS; } // Make sure at least 1 filtered image is available, decode new video if needed. // returns VD_* code // A return value of VD_PROGRESS doesn't necessarily output a frame, but makes // the promise that calling this function again will eventually do something. static int video_decode_and_filter(struct MPContext *mpctx) { struct dec_video *d_video = mpctx->d_video; int r = video_filter(mpctx, false); if (r < 0) return r; if (!d_video->waiting_decoded_mpi) { // Decode a new image, or at least feed the decoder a packet. r = decode_image(mpctx); if (r == VD_WAIT) return r; if (d_video->waiting_decoded_mpi) d_video->decoder_output = d_video->waiting_decoded_mpi->params; } bool eof = !d_video->waiting_decoded_mpi && (r == VD_EOF || r < 0); r = video_filter(mpctx, eof); if (r == VD_RECONFIG) // retry feeding decoded image r = video_filter(mpctx, eof); return r; } static int video_feed_async_filter(struct MPContext *mpctx) { struct dec_video *d_video = mpctx->d_video; struct vf_chain *vf = d_video->vfilter; if (vf->initialized < 0) return VD_ERROR; if (vf_needs_input(vf) < 1) return 0; mpctx->sleeptime = 0; // retry until done return video_decode_and_filter(mpctx); } /* Modify video timing to match the audio timeline. There are two main * reasons this is needed. First, video and audio can start from different * positions at beginning of file or after a seek (MPlayer starts both * immediately even if they have different pts). Second, the file can have * audio timestamps that are inconsistent with the duration of the audio * packets, for example two consecutive timestamp values differing by * one second but only a packet with enough samples for half a second * of playback between them. */ static void adjust_sync(struct MPContext *mpctx, double v_pts, double frame_time) { struct MPOpts *opts = mpctx->opts; if (mpctx->audio_status != STATUS_PLAYING) return; double a_pts = written_audio_pts(mpctx) + mpctx->audio_delay - mpctx->delay; double av_delay = a_pts - v_pts; double change = av_delay * 0.1; double max_change = opts->default_max_pts_correction >= 0 ? opts->default_max_pts_correction : frame_time * 0.1; if (change < -max_change) change = -max_change; else if (change > max_change) change = max_change; mpctx->delay += change; mpctx->total_avsync_change += change; } // Enough video filtered already to push one frame to the VO? static bool have_new_frame(struct MPContext *mpctx) { bool need_2nd = !!(mpctx->opts->frame_dropping & 1) // we need the duration && mpctx->video_pts != MP_NOPTS_VALUE; // ...except for the 1st frame return mpctx->next_frame[0] && (!need_2nd || mpctx->next_frame[1]); }
int fill_audio_out_buffers(struct MPContext *mpctx, double endpts) { struct MPOpts *opts = mpctx->opts; struct ao *ao = mpctx->ao; int playsize; int playflags = 0; bool audio_eof = false; bool signal_eof = false; bool partial_fill = false; sh_audio_t * const sh_audio = mpctx->sh_audio; bool modifiable_audio_format = !(ao->format & AF_FORMAT_SPECIAL_MASK); int unitsize = ao->channels.num * af_fmt2bits(ao->format) / 8; if (mpctx->paused) playsize = 1; // just initialize things (audio pts at least) else playsize = ao_get_space(ao); // Coming here with hrseek_active still set means audio-only if (!mpctx->sh_video || !mpctx->sync_audio_to_video) mpctx->syncing_audio = false; if (!opts->initial_audio_sync || !modifiable_audio_format) { mpctx->syncing_audio = false; mpctx->hrseek_active = false; } int res; if (mpctx->syncing_audio || mpctx->hrseek_active) res = audio_start_sync(mpctx, playsize); else res = decode_audio(sh_audio, &ao->buffer, playsize); if (res < 0) { // EOF, error or format change if (res == -2) { /* The format change isn't handled too gracefully. A more precise * implementation would require draining buffered old-format audio * while displaying video, then doing the output format switch. */ if (!mpctx->opts->gapless_audio) uninit_player(mpctx, INITIALIZED_AO); reinit_audio_chain(mpctx); return -1; } else if (res == ASYNC_PLAY_DONE) return 0; else if (demux_stream_eof(mpctx->sh_audio->gsh)) audio_eof = true; } if (endpts != MP_NOPTS_VALUE && modifiable_audio_format) { double bytes = (endpts - written_audio_pts(mpctx) + mpctx->audio_delay) * ao->bps / opts->playback_speed; if (playsize > bytes) { playsize = MPMAX(bytes, 0); audio_eof = true; partial_fill = true; } } assert(ao->buffer.len % unitsize == 0); if (playsize > ao->buffer.len) { partial_fill = true; playsize = ao->buffer.len; } playsize -= playsize % unitsize; if (!playsize) return partial_fill && audio_eof ? -2 : -partial_fill; if (audio_eof && partial_fill) { if (opts->gapless_audio) { // With gapless audio, delay this to ao_uninit. There must be only // 1 final chunk, and that is handled when calling ao_uninit(). signal_eof = true; } else { playflags |= AOPLAY_FINAL_CHUNK; } } assert(ao->buffer_playable_size <= ao->buffer.len); int played = write_to_ao(mpctx, ao->buffer.start, playsize, playflags, written_audio_pts(mpctx)); ao->buffer_playable_size = playsize - played; if (played > 0) { ao->buffer.len -= played; memmove(ao->buffer.start, ao->buffer.start + played, ao->buffer.len); } else if (!mpctx->paused && audio_eof && ao_get_delay(ao) < .04) { // Sanity check to avoid hanging in case current ao doesn't output // partial chunks and doesn't check for AOPLAY_FINAL_CHUNK signal_eof = true; } return signal_eof ? -2 : -partial_fill; }
static int audio_start_sync(struct MPContext *mpctx, int playsize) { struct ao *ao = mpctx->ao; struct MPOpts *opts = mpctx->opts; sh_audio_t * const sh_audio = mpctx->sh_audio; int res; // Timing info may not be set without res = decode_audio(sh_audio, &ao->buffer, 1); if (res < 0) return res; int bytes; bool did_retry = false; double written_pts; double bps = ao->bps / opts->playback_speed; bool hrseek = mpctx->hrseek_active; // audio only hrseek mpctx->hrseek_active = false; while (1) { written_pts = written_audio_pts(mpctx); double ptsdiff; if (hrseek) ptsdiff = written_pts - mpctx->hrseek_pts; else ptsdiff = written_pts - mpctx->sh_video->pts - mpctx->delay - mpctx->audio_delay; bytes = ptsdiff * bps; bytes -= bytes % (ao->channels.num * af_fmt2bits(ao->format) / 8); // ogg demuxers give packets without timing if (written_pts <= 1 && sh_audio->pts == MP_NOPTS_VALUE) { if (!did_retry) { // Try to read more data to see packets that have pts res = decode_audio(sh_audio, &ao->buffer, ao->bps); if (res < 0) return res; did_retry = true; continue; } bytes = 0; } if (fabs(ptsdiff) > 300 || isnan(ptsdiff)) // pts reset or just broken? bytes = 0; if (bytes > 0) break; mpctx->syncing_audio = false; int a = MPMIN(-bytes, MPMAX(playsize, 20000)); res = decode_audio(sh_audio, &ao->buffer, a); bytes += ao->buffer.len; if (bytes >= 0) { memmove(ao->buffer.start, ao->buffer.start + ao->buffer.len - bytes, bytes); ao->buffer.len = bytes; if (res < 0) return res; return decode_audio(sh_audio, &ao->buffer, playsize); } ao->buffer.len = 0; if (res < 0) return res; } if (hrseek) // Don't add silence in audio-only case even if position is too late return 0; int fillbyte = 0; if ((ao->format & AF_FORMAT_SIGN_MASK) == AF_FORMAT_US) fillbyte = 0x80; if (bytes >= playsize) { /* This case could fall back to the one below with * bytes = playsize, but then silence would keep accumulating * in a_out_buffer if the AO accepts less data than it asks for * in playsize. */ char *p = malloc(playsize); memset(p, fillbyte, playsize); write_to_ao(mpctx, p, playsize, 0, written_pts - bytes / bps); free(p); return ASYNC_PLAY_DONE; } mpctx->syncing_audio = false; decode_audio_prepend_bytes(&ao->buffer, bytes, fillbyte); return decode_audio(sh_audio, &ao->buffer, playsize); }