static double get_device_delay(struct wasapi_state *state) { UINT64 sample_count = atomic_load(&state->sample_count); UINT64 position, qpc_position; HRESULT hr; switch (hr = IAudioClock_GetPosition(state->pAudioClock, &position, &qpc_position)) { case S_OK: case S_FALSE: break; default: MP_ERR(state, "IAudioClock::GetPosition returned %s\n", wasapi_explain_err(hr)); } LARGE_INTEGER qpc_count; QueryPerformanceCounter(&qpc_count); double qpc_diff = (qpc_count.QuadPart * 1e7 / state->qpc_frequency.QuadPart) - qpc_position; position += state->clock_frequency * (uint64_t)(qpc_diff / 1e7); /* convert position to the same base as sample_count */ position = position * state->format.Format.nSamplesPerSec / state->clock_frequency; double diff = sample_count - position; double delay = diff / state->format.Format.nSamplesPerSec; MP_TRACE(state, "device delay: %g samples (%g ms)\n", diff, delay * 1000); return delay; }
static HRESULT get_device_delay(struct wasapi_state *state, double *delay) { UINT64 sample_count = atomic_load(&state->sample_count); UINT64 position, qpc_position; HRESULT hr; hr = IAudioClock_GetPosition(state->pAudioClock, &position, &qpc_position); /* GetPosition succeeded, but the result may be inaccurate due to the length of the call */ /* http://msdn.microsoft.com/en-us/library/windows/desktop/dd370889%28v=vs.85%29.aspx */ if (hr == S_FALSE) { MP_DBG(state, "Possibly inaccurate device position.\n"); hr = S_OK; } EXIT_ON_ERROR(hr); LARGE_INTEGER qpc_count; QueryPerformanceCounter(&qpc_count); double qpc_diff = (qpc_count.QuadPart * 1e7 / state->qpc_frequency.QuadPart) - qpc_position; position += state->clock_frequency * (uint64_t) (qpc_diff / 1e7); /* convert position to the same base as sample_count */ position = position * state->format.Format.nSamplesPerSec / state->clock_frequency; double diff = sample_count - position; *delay = diff / state->format.Format.nSamplesPerSec; MP_TRACE(state, "Device delay: %g samples (%g ms)\n", diff, *delay * 1000); return S_OK; exit_label: MP_ERR(state, "Error getting device delay: %s\n", mp_HRESULT_to_str(hr)); return hr; }
static int dvb_streaming_read(stream_t *stream, char *buffer, int size) { struct pollfd pfds[1]; int pos = 0, tries, rk, fd; dvb_priv_t *priv = (dvb_priv_t *) stream->priv; dvb_state_t* state = priv->state; MP_TRACE(stream, "dvb_streaming_read(%d)\n", size); tries = state->retry + 1; fd = state->dvr_fd; while (pos < size) { pfds[0].fd = fd; pfds[0].events = POLLIN | POLLPRI; rk = size - pos; if (poll(pfds, 1, 500) <= 0) { MP_ERR(stream, "dvb_streaming_read, attempt N. %d failed with " "errno %d when reading %d bytes\n", tries, errno, size - pos); errno = 0; if (--tries > 0) continue; break; } if ((rk = read(fd, &buffer[pos], rk)) > 0) { pos += rk; MP_TRACE(stream, "ret (%d) bytes\n", pos); } else { MP_ERR(stream, "dvb_streaming_read, poll ok but read failed with " "errno %d when reading %d bytes, size: %d, pos: %d\n", errno, size - pos, size, pos); } } if (!pos) MP_ERR(stream, "dvb_streaming_read, return %d bytes\n", pos); return pos; }
static void thread_feed(struct ao *ao) { struct wasapi_state *state = ao->priv; HRESULT hr; UINT32 frame_count = state->bufferFrameCount; if (state->share_mode == AUDCLNT_SHAREMODE_SHARED) { UINT32 padding = 0; hr = IAudioClient_GetCurrentPadding(state->pAudioClient, &padding); EXIT_ON_ERROR(hr); frame_count -= padding; MP_TRACE(ao, "Frame to fill: %"PRIu32". Padding: %"PRIu32"\n", frame_count, padding); } double delay_us; hr = get_device_delay(state, &delay_us); EXIT_ON_ERROR(hr); // add the buffer delay delay_us += frame_count * 1e6 / state->format.Format.nSamplesPerSec; BYTE *pData; hr = IAudioRenderClient_GetBuffer(state->pRenderClient, frame_count, &pData); EXIT_ON_ERROR(hr); BYTE *data[1] = {pData}; ao_read_data(ao, (void **)data, frame_count, mp_time_us() + (int64_t)llrint(delay_us)); // note, we can't use ao_read_data return value here since we already // commited to frame_count above in the GetBuffer call hr = IAudioRenderClient_ReleaseBuffer(state->pRenderClient, frame_count, 0); EXIT_ON_ERROR(hr); atomic_fetch_add(&state->sample_count, frame_count); return; exit_label: MP_ERR(state, "Error feeding audio: %s\n", mp_HRESULT_to_str(hr)); MP_VERBOSE(ao, "Requesting ao reload\n"); ao_request_reload(ao); return; }
static int pvr_stream_read (stream_t *stream, char *buffer, int size) { struct pollfd pfds[1]; struct pvr_t *pvr; int rk, fd, pos; if (!stream || !buffer) return 0; pvr = (struct pvr_t *) stream->priv; fd = pvr->dev_fd; pos = 0; if (fd < 0) return 0; while (pos < size) { pfds[0].fd = fd; pfds[0].events = POLLIN | POLLPRI; rk = size - pos; if (poll (pfds, 1, 500) <= 0) { MP_ERR(pvr, "%s failed with errno %d when reading %d bytes\n", LOG_LEVEL_PVR, errno, size-pos); break; } rk = read (fd, &buffer[pos], rk); if (rk > 0) { pos += rk; MP_TRACE(pvr, "%s read (%d) bytes\n", LOG_LEVEL_PVR, pos); } } if (!pos) MP_ERR(pvr, "%s read %d bytes\n", LOG_LEVEL_PVR, pos); return pos; }
static HRESULT get_device_delay(struct wasapi_state *state, double *delay_us) { UINT64 sample_count = atomic_load(&state->sample_count); UINT64 position, qpc_position; HRESULT hr; hr = IAudioClock_GetPosition(state->pAudioClock, &position, &qpc_position); // GetPosition succeeded, but the result may be // inaccurate due to the length of the call // http://msdn.microsoft.com/en-us/library/windows/desktop/dd370889%28v=vs.85%29.aspx if (hr == S_FALSE) { MP_VERBOSE(state, "Possibly inaccurate device position.\n"); hr = S_OK; } EXIT_ON_ERROR(hr); // convert position to number of samples careful to avoid overflow UINT64 sample_position = uint64_scale(position, state->format.Format.nSamplesPerSec, state->clock_frequency); INT64 diff = sample_count - sample_position; *delay_us = diff * 1e6 / state->format.Format.nSamplesPerSec; // Correct for any delay in IAudioClock_GetPosition above. // This should normally be very small (<1 us), but just in case. . . LARGE_INTEGER qpc; QueryPerformanceCounter(&qpc); INT64 qpc_diff = av_rescale(qpc.QuadPart, 10000000, state->qpc_frequency.QuadPart) - qpc_position; // ignore the above calculation if it yeilds more than 10 seconds (due to // possible overflow inside IAudioClock_GetPosition) if (qpc_diff < 10 * 10000000) { *delay_us -= qpc_diff / 10.0; // convert to us } else { MP_VERBOSE(state, "Insane qpc delay correction of %g seconds. " "Ignoring it.\n", qpc_diff / 10000000.0); } MP_TRACE(state, "Device delay: %g us\n", *delay_us); return S_OK; exit_label: MP_ERR(state, "Error getting device delay: %s\n", mp_HRESULT_to_str(hr)); return hr; }
/* Update avsync before a new video frame is displayed. Actually, this can be * called arbitrarily often before the actual display. * This adjusts the time of the next video frame */ static void update_avsync_before_frame(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; struct vo *vo = mpctx->video_out; if (!mpctx->sync_audio_to_video || mpctx->video_status < STATUS_READY) { mpctx->time_frame = 0; } else if (mpctx->audio_status == STATUS_PLAYING && mpctx->video_status == STATUS_PLAYING && !ao_untimed(mpctx->ao)) { double buffered_audio = ao_get_delay(mpctx->ao); MP_TRACE(mpctx, "audio 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 untimed is set always output frames immediately * without sleeping. */ if (mpctx->time_frame < -0.2 || opts->untimed || vo->driver->untimed) mpctx->time_frame = 0; } }
// Initialization and runtime control static int control(struct af_instance* af, int cmd, void* arg) { af_delay_t* s = af->priv; switch(cmd) { case AF_CONTROL_REINIT: { int i; struct mp_audio *in = arg; if (in->bps != 1 && in->bps != 2 && in->bps != 4) { MP_FATAL(af, "Sample format not supported\n"); return AF_ERROR; } // Free prevous delay queues for(i=0; i<af->data->nch; i++) free(s->q[i]); mp_audio_force_interleaved_format(in); mp_audio_copy_config(af->data, in); // Allocate new delay queues for(i=0; i<af->data->nch; i++) { s->q[i] = calloc(L,af->data->bps); if(NULL == s->q[i]) MP_FATAL(af, "Out of memory\n"); } if(AF_OK != af_from_ms(AF_NCH, s->d, s->wi, af->data->rate, 0.0, 1000.0)) return AF_ERROR; s->ri = 0; for(i=0; i<AF_NCH; i++) { MP_DBG(af, "Channel %i delayed by %0.3fms\n", i,MPCLAMP(s->d[i],0.0,1000.0)); MP_TRACE(af, "Channel %i delayed by %i samples\n", i,s->wi[i]); } return AF_OK; } } return AF_UNKNOWN; }
// Move the frame in next_frame[1] to next_frame[0]. This makes the frame // "known" to the playback logic. A frame in next_frame[0] is either "known" or // NULL, so the moving must always be done by this function. static void shift_new_frame(struct MPContext *mpctx) { if (mpctx->next_frame[0] || !mpctx->next_frame[1]) return; mpctx->next_frame[0] = mpctx->next_frame[1]; mpctx->next_frame[1] = NULL; double frame_time = 0; double pts = mpctx->next_frame[0]->pts; if (mpctx->video_pts != MP_NOPTS_VALUE) { frame_time = pts - mpctx->video_pts; double tolerance = 15; if (mpctx->demuxer->ts_resets_possible) { // Fortunately no real framerate is likely to go below this. It // still could be that the file is VFR, but the demuxer reports a // higher rate, so account for the case of e.g. 60hz demuxer fps // but 23hz actual fps. double fps = 23.976; if (mpctx->d_video->fps > 0 && mpctx->d_video->fps < fps) fps = mpctx->d_video->fps; tolerance = 3 * 1.0 / fps; } if (frame_time <= 0 || frame_time >= tolerance) { // Assume a discontinuity. MP_WARN(mpctx, "Invalid video timestamp: %f -> %f\n", mpctx->video_pts, pts); frame_time = 0; mpctx->audio_status = STATUS_SYNCING; } } mpctx->video_next_pts = pts; mpctx->delay -= frame_time; if (mpctx->video_status >= STATUS_PLAYING) { mpctx->time_frame += frame_time / mpctx->opts->playback_speed; adjust_sync(mpctx, pts, frame_time); } mpctx->dropped_frames = 0; MP_TRACE(mpctx, "frametime=%5.3f\n", frame_time); }
// Return true if progress was made. static bool locked_read_output(struct vf_instance *vf) { struct vf_priv_s *p = vf->priv; bool r = false; // Move finished frames from the request slots to the vf output queue. while (p->requested[0] && p->requested[0] != &dummy_img) { struct mp_image *out = p->requested[0]; if (out->pts != MP_NOPTS_VALUE) { double duration = out->pts; out->pts = p->out_pts; p->out_pts += duration; } vf_add_output_frame(vf, out); for (int n = 0; n < p->max_requests - 1; n++) p->requested[n] = p->requested[n + 1]; p->requested[p->max_requests - 1] = NULL; p->out_frameno++; r = true; } // Don't request frames if we haven't sent any input yet. if (p->num_buffered + p->in_frameno == 0) return r; // Request new future frames as far as possible. for (int n = 0; n < p->max_requests; n++) { if (!p->requested[n]) { // Note: this assumes getFrameAsync() will never call // infiltGetFrame (if it does, we would deadlock) p->requested[n] = (struct mp_image *)&dummy_img; p->failed = false; MP_TRACE(vf, "requesting frame %d (%d)\n", p->out_frameno + n, n); p->vsapi->getFrameAsync(p->out_frameno + n, p->out_node, vs_frame_done, vf); } } return r; }
static void VS_CC vs_frame_done(void *userData, const VSFrameRef *f, int n, VSNodeRef *node, const char *errorMsg) { struct vf_instance *vf = userData; struct vf_priv_s *p = vf->priv; pthread_mutex_lock(&p->lock); // If these assertions fail, n is an unrequested frame (or filtered twice). assert(n >= p->out_frameno && n < p->out_frameno + p->max_requests); int index = n - p->out_frameno; MP_TRACE(vf, "filtered frame %d (%d)\n", n, index); assert(p->requested[index] == &dummy_img); struct mp_image *res = NULL; if (f) { struct mp_image img = map_vs_frame(p, f, false); img.pts = MP_NOPTS_VALUE; const VSMap *map = p->vsapi->getFramePropsRO(f); if (map) { int err1, err2; int num = p->vsapi->propGetInt(map, "_DurationNum", 0, &err1); int den = p->vsapi->propGetInt(map, "_DurationDen", 0, &err2); if (!err1 && !err2) img.pts = num / (double)den; // abusing pts for frame length } if (img.pts == MP_NOPTS_VALUE) MP_ERR(vf, "No PTS after filter at frame %d!\n", n); res = mp_image_new_copy(&img); p->vsapi->freeFrame(f); } if (!res) { p->failed = true; MP_ERR(vf, "Filter error at frame %d: %s\n", n, errorMsg); } p->requested[index] = res; pthread_cond_broadcast(&p->wakeup); pthread_mutex_unlock(&p->lock); }
// Fill mpctx->next_frame[] with a newly filtered or decoded image. // returns VD_* code static int video_output_image(struct MPContext *mpctx, double endpts) { bool hrseek = mpctx->hrseek_active && mpctx->video_status == STATUS_SYNCING; if (mpctx->d_video->header->attached_picture) { if (vo_has_frame(mpctx->video_out)) return VD_EOF; if (mpctx->next_frame[0]) return VD_NEW_FRAME; int r = video_decode_and_filter(mpctx); video_filter(mpctx, true); // force EOF filtering (avoid decoding more) mpctx->next_frame[0] = vf_read_output_frame(mpctx->d_video->vfilter); if (mpctx->next_frame[0]) mpctx->next_frame[0]->pts = MP_NOPTS_VALUE; return r <= 0 ? VD_EOF : VD_PROGRESS; } if (have_new_frame(mpctx)) return VD_NEW_FRAME; if (!mpctx->next_frame[0] && mpctx->next_frame[1]) { mpctx->next_frame[0] = mpctx->next_frame[1]; mpctx->next_frame[1] = NULL; double pts = mpctx->next_frame[0]->pts; double last_pts = mpctx->video_pts; if (last_pts == MP_NOPTS_VALUE) last_pts = pts; double frame_time = pts - last_pts; if (frame_time < 0 || frame_time >= 60) { // Assume a PTS difference >= 60 seconds is a discontinuity. MP_WARN(mpctx, "Jump in video pts: %f -> %f\n", last_pts, pts); frame_time = 0; } mpctx->video_next_pts = pts; if (mpctx->d_audio) mpctx->delay -= frame_time; if (mpctx->video_status >= STATUS_READY) { mpctx->time_frame += frame_time / mpctx->opts->playback_speed; adjust_sync(mpctx, pts, frame_time); } mpctx->dropped_frames = 0; MP_TRACE(mpctx, "frametime=%5.3f\n", frame_time); } if (have_new_frame(mpctx)) return VD_NEW_FRAME; // Get a new frame if we need one. int r = VD_PROGRESS; if (!mpctx->next_frame[1]) { // Filter a new frame. r = video_decode_and_filter(mpctx); if (r < 0) return r; // error struct mp_image *img = vf_read_output_frame(mpctx->d_video->vfilter); if (img) { // Always add these; they make backstepping after seeking faster. add_frame_pts(mpctx, img->pts); bool drop = false; if ((endpts != MP_NOPTS_VALUE && img->pts >= endpts) || mpctx->max_frames == 0) { drop = true; r = VD_EOF; } if (!drop && hrseek && mpctx->hrseek_lastframe) { mp_image_setrefp(&mpctx->saved_frame, img); drop = true; } if (hrseek && img->pts < mpctx->hrseek_pts - .005) drop = true; if (drop) { talloc_free(img); } else { mpctx->next_frame[1] = img; } } } // On EOF, always allow the playloop to use the remaining frame. if (have_new_frame(mpctx) || (r <= 0 && mpctx->next_frame[0])) return VD_NEW_FRAME; // Last-frame seek if (r <= 0 && hrseek && mpctx->hrseek_lastframe && mpctx->saved_frame) { mpctx->next_frame[1] = mpctx->saved_frame; mpctx->saved_frame = NULL; return VD_PROGRESS; } return r; }
// Runs in the cache thread. // Returns true if reading was attempted, and the mutex was shortly unlocked. static bool cache_fill(struct priv *s) { int64_t read = s->read_filepos; int len = 0; // drop cache contents only if seeking backward or too much fwd. // This is also done for on-disk files, since it loses the backseek cache. // That in turn can cause major bandwidth increase and performance // issues with e.g. mov or badly interleaved files if (read < s->min_filepos || read > s->max_filepos + s->seek_limit) { MP_VERBOSE(s, "Dropping cache at pos %"PRId64", " "cached range: %"PRId64"-%"PRId64".\n", read, s->min_filepos, s->max_filepos); cache_drop_contents(s); } if (stream_tell(s->stream) != s->max_filepos && s->seekable) { MP_VERBOSE(s, "Seeking underlying stream: %"PRId64" -> %"PRId64"\n", stream_tell(s->stream), s->max_filepos); stream_seek(s->stream, s->max_filepos); if (stream_tell(s->stream) != s->max_filepos) goto done; } if (mp_cancel_test(s->cache->cancel)) goto done; // number of buffer bytes which should be preserved in backwards direction int64_t back = MPCLAMP(read - s->min_filepos, 0, s->back_size); // limit maximum readahead so that the backbuffer space is reserved, even // if the backbuffer is not used. limit it to ensure that we don't stall the // network when starting a file, or we wouldn't download new data until we // get new free space again. (unless everything fits in the cache.) if (s->stream_size > s->buffer_size) back = MPMAX(back, s->back_size); // number of buffer bytes that are valid and can be read int64_t newb = FFMAX(s->max_filepos - read, 0); // max. number of bytes that can be written (starting from max_filepos) int64_t space = s->buffer_size - (newb + back); // offset into the buffer that maps to max_filepos int64_t pos = s->max_filepos - s->offset; if (pos >= s->buffer_size) pos -= s->buffer_size; // wrap-around if (space < FILL_LIMIT) { s->idle = true; s->reads++; // don't stuck main thread return false; } // limit to end of buffer (without wrapping) if (pos + space >= s->buffer_size) space = s->buffer_size - pos; // limit read size (or else would block and read the entire buffer in 1 call) space = FFMIN(space, s->stream->read_chunk); // back+newb+space <= buffer_size int64_t back2 = s->buffer_size - (space + newb); // max back size if (s->min_filepos < (read - back2)) s->min_filepos = read - back2; // The read call might take a long time and block, so drop the lock. pthread_mutex_unlock(&s->mutex); len = stream_read_partial(s->stream, &s->buffer[pos], space); pthread_mutex_lock(&s->mutex); // Do this after reading a block, because at least libdvdnav updates the // stream position only after actually reading something after a seek. if (s->start_pts == MP_NOPTS_VALUE) { double pts; if (stream_control(s->stream, STREAM_CTRL_GET_CURRENT_TIME, &pts) > 0) s->start_pts = pts; } s->max_filepos += len; if (pos + len == s->buffer_size) s->offset += s->buffer_size; // wrap... done: s->eof = len <= 0; s->idle = s->eof; s->reads++; if (s->eof) { s->eof_pos = stream_tell(s->stream); MP_TRACE(s, "EOF reached.\n"); } pthread_cond_signal(&s->wakeup); return true; }
static int dvd_read_sector(stream_t *stream, dvd_priv_t *d, unsigned char *data) { int len; if(d->packs_left==0) { /** * If we're not at the end of this cell, we can determine the next * VOBU to display using the VOBU_SRI information section of the * DSI. Using this value correctly follows the current angle, * avoiding the doubled scenes in The Matrix, and makes our life * really happy. * * Otherwise, we set our next address past the end of this cell to * force the code above to go to the next cell in the program. */ if(d->dsi_pack.vobu_sri.next_vobu != SRI_END_OF_CELL) { d->cur_pack= d->dsi_pack.dsi_gi.nv_pck_lbn + ( d->dsi_pack.vobu_sri.next_vobu & 0x7fffffff ); MP_DBG(stream, "Navi new pos=0x%X \n",d->cur_pack); } else { // end of cell! find next cell! MP_VERBOSE(stream, "--- END OF CELL !!! ---\n"); d->cur_pack=d->cell_last_pack+1; } } read_next: if(d->cur_pack>d->cell_last_pack) { // end of cell! int next=dvd_next_cell(stream, d); if(next>=0) { d->cur_cell=next; // if( d->cur_pgc->cell_playback[d->cur_cell].block_type // == BLOCK_TYPE_ANGLE_BLOCK ) d->cur_cell+=dvd_angle-1; d->cur_pack = d->cur_pgc->cell_playback[ d->cur_cell ].first_sector; d->cell_last_pack=d->cur_pgc->cell_playback[ d->cur_cell ].last_sector; MP_VERBOSE(stream, "DVD next cell: %d pack: 0x%X-0x%X \n",d->cur_cell,d->cur_pack,d->cell_last_pack); } else return -1; // EOF } len = DVDReadBlocks(d->title, d->cur_pack, 1, data); // only == 0 should indicate an error, but some dvdread version are buggy when used with dvdcss if(len <= 0) return -1; //error if(data[38]==0 && data[39]==0 && data[40]==1 && data[41]==0xBF && data[1024]==0 && data[1025]==0 && data[1026]==1 && data[1027]==0xBF) { // found a Navi packet!!! #if DVDREAD_VERSION >= LIBDVDREAD_VERSION(0,9,0) navRead_DSI(&d->dsi_pack, &(data[ DSI_START_BYTE ])); #else navRead_DSI(&d->dsi_pack, &(data[ DSI_START_BYTE ]), sizeof(dsi_t)); #endif if(d->cur_pack != d->dsi_pack.dsi_gi.nv_pck_lbn ) { MP_VERBOSE(stream, "Invalid NAVI packet! lba=0x%X navi=0x%X \n", d->cur_pack,d->dsi_pack.dsi_gi.nv_pck_lbn); } else { // process! d->packs_left = d->dsi_pack.dsi_gi.vobu_ea; MP_DBG(stream, "Found NAVI packet! lba=0x%X len=%d \n",d->cur_pack,d->packs_left); //navPrint_DSI(&d->dsi_pack); MP_TRACE(stream, "\r### CELL %d: Navi: %d/%d IFO: %d/%d \n",d->cur_cell, d->dsi_pack.dsi_gi.vobu_c_idn,d->dsi_pack.dsi_gi.vobu_vob_idn, d->cur_pgc->cell_position[d->cur_cell].cell_nr, d->cur_pgc->cell_position[d->cur_cell].vob_id_nr); if(d->angle_seek) { int i,skip=0; for(i=0;i<9;i++) // check if all values zero: if((skip=d->dsi_pack.sml_agli.data[i].address)!=0) break; if(skip && skip!=0x7fffffff) { // sml_agli table has valid data (at least one non-zero): d->cur_pack=d->dsi_pack.dsi_gi.nv_pck_lbn+ d->dsi_pack.sml_agli.data[d->dvd_angle-1].address; d->angle_seek=0; d->cur_pack--; MP_VERBOSE(stream, "Angle-seek synced using sml_agli map! new_lba=0x%X \n",d->cur_pack); } else { // check if we're in the right cell, jump otherwise: if( (d->dsi_pack.dsi_gi.vobu_c_idn==d->cur_pgc->cell_position[d->cur_cell].cell_nr) && (d->dsi_pack.dsi_gi.vobu_vob_idn==d->cur_pgc->cell_position[d->cur_cell].vob_id_nr) ){ d->angle_seek=0; MP_VERBOSE(stream, "Angle-seek synced by cell/vob IDN search! \n"); } else { // wrong angle, skip this vobu: d->cur_pack=d->dsi_pack.dsi_gi.nv_pck_lbn+ d->dsi_pack.dsi_gi.vobu_ea; d->angle_seek=2; // DEBUG } } } } ++d->cur_pack; goto read_next; } ++d->cur_pack; if(d->packs_left>=0) --d->packs_left; if(d->angle_seek) { if(d->angle_seek==2) MP_VERBOSE(stream, "!!! warning! reading packet while angle_seek !!!\n"); goto read_next; // searching for Navi packet } return d->cur_pack-1; }
static void get_disc_lang(struct stream *stream, struct sh_stream *sh) { struct stream_lang_req req = {.type = sh->type, .id = sh->demuxer_id}; if (stream->uncached_type == STREAMTYPE_DVD && sh->type == STREAM_SUB) req.id = req.id & 0x1F; // mpeg ID to index stream_control(stream, STREAM_CTRL_GET_LANG, &req); if (req.name[0]) sh->lang = talloc_strdup(sh, req.name); } static void add_dvd_streams(demuxer_t *demuxer) { struct priv *p = demuxer->priv; struct stream *stream = demuxer->stream; if (stream->uncached_type != STREAMTYPE_DVD) return; struct stream_dvd_info_req info; if (stream_control(stream, STREAM_CTRL_GET_DVD_INFO, &info) > 0) { for (int n = 0; n < MPMIN(32, info.num_subs); n++) { struct sh_stream *sh = demux_alloc_sh_stream(STREAM_SUB); sh->demuxer_id = n + 0x20; sh->codec->codec = "dvd_subtitle"; get_disc_lang(stream, sh); // p->streams _must_ match with p->slave->streams, so we can't add // it yet - it has to be done when the real stream appears, which // could be right on start, or any time later. p->dvd_subs[n] = sh; // emulate the extradata struct mp_csp_params csp = MP_CSP_PARAMS_DEFAULTS; struct mp_cmat cmatrix; mp_get_csp_matrix(&csp, &cmatrix); char *s = talloc_strdup(sh, ""); s = talloc_asprintf_append(s, "palette: "); for (int i = 0; i < 16; i++) { int color = info.palette[i]; int y[3] = {(color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff}; int c[3]; mp_map_fixp_color(&cmatrix, 8, y, 8, c); color = (c[2] << 16) | (c[1] << 8) | c[0]; if (i != 0) s = talloc_asprintf_append(s, ", "); s = talloc_asprintf_append(s, "%06x", color); } s = talloc_asprintf_append(s, "\n"); sh->codec->extradata = s; sh->codec->extradata_size = strlen(s); demux_add_sh_stream(demuxer, sh); } } } static void add_streams(demuxer_t *demuxer) { struct priv *p = demuxer->priv; for (int n = p->num_streams; n < demux_get_num_stream(p->slave); n++) { struct sh_stream *src = demux_get_stream(p->slave, n); if (src->type == STREAM_SUB) { struct sh_stream *sub = NULL; if (src->demuxer_id >= 0x20 && src->demuxer_id <= 0x3F) sub = p->dvd_subs[src->demuxer_id - 0x20]; if (sub) { assert(p->num_streams == n); // directly mapped MP_TARRAY_APPEND(p, p->streams, p->num_streams, sub); continue; } } struct sh_stream *sh = demux_alloc_sh_stream(src->type); assert(p->num_streams == n); // directly mapped MP_TARRAY_APPEND(p, p->streams, p->num_streams, sh); // Copy all stream fields that might be relevant *sh->codec = *src->codec; sh->demuxer_id = src->demuxer_id; if (src->type == STREAM_VIDEO) { double ar; if (stream_control(demuxer->stream, STREAM_CTRL_GET_ASPECT_RATIO, &ar) == STREAM_OK) { struct mp_image_params f = {.w = src->codec->disp_w, .h = src->codec->disp_h}; mp_image_params_set_dsize(&f, 1728 * ar, 1728); sh->codec->par_w = f.p_w; sh->codec->par_h = f.p_h; } } get_disc_lang(demuxer->stream, sh); demux_add_sh_stream(demuxer, sh); } reselect_streams(demuxer); } static void d_seek(demuxer_t *demuxer, double rel_seek_secs, int flags) { struct priv *p = demuxer->priv; if (demuxer->stream->uncached_type == STREAMTYPE_CDDA) { demux_seek(p->slave, rel_seek_secs, flags); return; } double pts = p->seek_pts; if (flags & SEEK_ABSOLUTE) pts = 0.0f; double base_pts = pts; // to what pts is relative if (flags & SEEK_FACTOR) { double tmp = 0; stream_control(demuxer->stream, STREAM_CTRL_GET_TIME_LENGTH, &tmp); pts += tmp * rel_seek_secs; } else { pts += rel_seek_secs; } MP_VERBOSE(demuxer, "seek to: %f\n", pts); double seek_arg[] = {pts, base_pts, flags}; stream_control(demuxer->stream, STREAM_CTRL_SEEK_TO_TIME, seek_arg); demux_control(p->slave, DEMUXER_CTRL_RESYNC, NULL); p->seek_pts = pts; p->seek_reinit = true; } static void reset_pts(demuxer_t *demuxer) { struct priv *p = demuxer->priv; double base; if (stream_control(demuxer->stream, STREAM_CTRL_GET_CURRENT_TIME, &base) < 1) base = 0; MP_VERBOSE(demuxer, "reset to time: %f\n", base); p->base_dts = p->last_dts = MP_NOPTS_VALUE; p->base_time = base; p->seek_reinit = false; } static int d_fill_buffer(demuxer_t *demuxer) { struct priv *p = demuxer->priv; struct demux_packet *pkt = demux_read_any_packet(p->slave); if (!pkt) return 0; demux_update(p->slave); if (p->seek_reinit) reset_pts(demuxer); add_streams(demuxer); if (pkt->stream >= p->num_streams) { // out of memory? talloc_free(pkt); return 0; } struct sh_stream *sh = p->streams[pkt->stream]; if (!demux_stream_is_selected(sh)) { talloc_free(pkt); return 1; } if (demuxer->stream->uncached_type == STREAMTYPE_CDDA) { demux_add_packet(sh, pkt); return 1; } MP_TRACE(demuxer, "ipts: %d %f %f\n", sh->type, pkt->pts, pkt->dts); if (sh->type == STREAM_SUB) { if (p->base_dts == MP_NOPTS_VALUE) MP_WARN(demuxer, "subtitle packet along PTS reset\n"); } else if (pkt->dts != MP_NOPTS_VALUE) { // Use the very first DTS to rebase the start time of the MPEG stream // to the playback time. if (p->base_dts == MP_NOPTS_VALUE) p->base_dts = pkt->dts; if (p->last_dts == MP_NOPTS_VALUE) p->last_dts = pkt->dts; if (fabs(p->last_dts - pkt->dts) >= DTS_RESET_THRESHOLD) { MP_WARN(demuxer, "PTS discontinuity: %f->%f\n", p->last_dts, pkt->dts); p->base_time += p->last_dts - p->base_dts; p->base_dts = pkt->dts - pkt->duration; } p->last_dts = pkt->dts; } if (p->base_dts != MP_NOPTS_VALUE) { double delta = -p->base_dts + p->base_time; if (pkt->pts != MP_NOPTS_VALUE) pkt->pts += delta; if (pkt->dts != MP_NOPTS_VALUE) pkt->dts += delta; } MP_TRACE(demuxer, "opts: %d %f %f\n", sh->type, pkt->pts, pkt->dts); if (pkt->pts != MP_NOPTS_VALUE) p->seek_pts = pkt->pts; demux_add_packet(sh, pkt); return 1; } static void add_stream_chapters(struct demuxer *demuxer) { int num = 0; if (stream_control(demuxer->stream, STREAM_CTRL_GET_NUM_CHAPTERS, &num) < 1) return; for (int n = 0; n < num; n++) { double p = n; if (stream_control(demuxer->stream, STREAM_CTRL_GET_CHAPTER_TIME, &p) < 1) continue; demuxer_add_chapter(demuxer, "", p, 0); } }
void write_video(struct MPContext *mpctx, double endpts) { struct MPOpts *opts = mpctx->opts; struct vo *vo = mpctx->video_out; if (!mpctx->d_video) return; update_fps(mpctx); // Whether there's still at least 1 video frame that can be shown. // If false, it means we can reconfig the VO if needed (normally, this // would disrupt playback, so only do it on !still_playing). bool still_playing = vo_has_next_frame(vo, true); // For the last frame case (frame is being displayed). still_playing |= mpctx->playing_last_frame; still_playing |= mpctx->last_frame_duration > 0; double frame_time = 0; int r = update_video(mpctx, endpts, !still_playing, &frame_time); MP_TRACE(mpctx, "update_video: %d (still_playing=%d)\n", r, still_playing); if (r == VD_WAIT) // Demuxer will wake us up for more packets to decode. return; if (r < 0) { MP_FATAL(mpctx, "Could not initialize video chain.\n"); int uninit = INITIALIZED_VCODEC; if (!opts->force_vo) uninit |= INITIALIZED_VO; uninit_player(mpctx, uninit); if (!mpctx->current_track[STREAM_AUDIO]) mpctx->stop_play = PT_NEXT_ENTRY; mpctx->error_playing = true; handle_force_window(mpctx, true); return; // restart loop } if (r == VD_EOF) { if (!mpctx->playing_last_frame && mpctx->last_frame_duration > 0) { mpctx->time_frame += mpctx->last_frame_duration; mpctx->last_frame_duration = 0; mpctx->playing_last_frame = true; MP_VERBOSE(mpctx, "showing last frame\n"); } } if (r == VD_NEW_FRAME) { MP_TRACE(mpctx, "frametime=%5.3f\n", frame_time); if (mpctx->video_status > STATUS_PLAYING) mpctx->video_status = STATUS_PLAYING; if (mpctx->video_status >= STATUS_READY) { mpctx->time_frame += frame_time / opts->playback_speed; adjust_sync(mpctx, frame_time); } } else if (r == VD_EOF && mpctx->playing_last_frame) { // Let video timing code continue displaying. mpctx->video_status = STATUS_DRAINING; MP_VERBOSE(mpctx, "still showing last frame\n"); } else if (r <= 0) { // EOF or error mpctx->delay = 0; mpctx->last_av_difference = 0; mpctx->video_status = STATUS_EOF; MP_VERBOSE(mpctx, "video EOF\n"); return; } else { if (mpctx->video_status > STATUS_PLAYING) mpctx->video_status = STATUS_PLAYING; // Decode more in next iteration. mpctx->sleeptime = 0; MP_TRACE(mpctx, "filtering more video\n"); } // Actual playback starts when both audio and video are ready. if (mpctx->video_status == STATUS_READY) return; if (mpctx->paused && mpctx->video_status >= STATUS_READY) return; mpctx->time_frame -= get_relative_time(mpctx); double audio_pts = playing_audio_pts(mpctx); if (!mpctx->sync_audio_to_video || mpctx->video_status < STATUS_READY) { mpctx->time_frame = 0; } else if (mpctx->audio_status == STATUS_PLAYING && mpctx->video_status == STATUS_PLAYING) { double buffered_audio = ao_get_delay(mpctx->ao); MP_TRACE(mpctx, "audio 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 untimed is set always output frames immediately * without sleeping. */ if (mpctx->time_frame < -0.2 || opts->untimed || vo->untimed) mpctx->time_frame = 0; } double vsleep = mpctx->time_frame - vo->flip_queue_offset; if (vsleep > 0.050) { mpctx->sleeptime = MPMIN(mpctx->sleeptime, vsleep - 0.040); return; } mpctx->sleeptime = 0; mpctx->playing_last_frame = false; // last frame case if (r != VD_NEW_FRAME) return; //=================== FLIP PAGE (VIDEO BLT): ====================== mpctx->video_pts = mpctx->video_next_pts; mpctx->last_vo_pts = mpctx->video_pts; mpctx->playback_pts = mpctx->video_pts; update_subtitles(mpctx); update_osd_msg(mpctx); MP_STATS(mpctx, "vo draw frame"); vo_new_frame_imminent(vo); MP_STATS(mpctx, "vo sleep"); mpctx->time_frame -= get_relative_time(mpctx); mpctx->time_frame -= vo->flip_queue_offset; if (mpctx->time_frame > 0.001) mpctx->time_frame = timing_sleep(mpctx, mpctx->time_frame); mpctx->time_frame += vo->flip_queue_offset; int64_t t2 = mp_time_us(); /* 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 MPMAX. */ double time_frame = MPMAX(mpctx->time_frame, -1); int64_t pts_us = mpctx->last_time + time_frame * 1e6; int duration = -1; double pts2 = vo_get_next_pts(vo, 0); // this is the next frame PTS if (mpctx->video_pts != MP_NOPTS_VALUE && pts2 == MP_NOPTS_VALUE) { // Make up a frame duration. Using the frame rate is not a good // choice, since the frame rate could be unset/broken/random. float fps = mpctx->d_video->fps; double frame_duration = fps > 0 ? 1.0 / fps : 0; pts2 = mpctx->video_pts + MPCLAMP(frame_duration, 0.0, 5.0); } if (pts2 != MP_NOPTS_VALUE) { // 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; mpctx->last_frame_duration = diff; } if (mpctx->video_status != STATUS_PLAYING) duration = -1; MP_STATS(mpctx, "start flip"); vo_flip_page(vo, pts_us | 1, duration); MP_STATS(mpctx, "end flip"); if (audio_pts != MP_NOPTS_VALUE) MP_STATS(mpctx, "value %f ptsdiff", mpctx->video_pts - audio_pts); mpctx->last_vo_flip_duration = (mp_time_us() - t2) * 0.000001; if (vo->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); } mpctx->shown_vframes++; if (mpctx->video_status < STATUS_PLAYING) mpctx->video_status = STATUS_READY; update_avsync(mpctx); screenshot_flip(mpctx); mp_notify(mpctx, MPV_EVENT_TICK, NULL); if (!mpctx->sync_audio_to_video) mpctx->video_status = STATUS_EOF; }
static void VS_CC infiltInit(VSMap *in, VSMap *out, void **instanceData, VSNode *node, VSCore *core, const VSAPI *vsapi) { struct vf_instance *vf = *instanceData; struct vf_priv_s *p = vf->priv; // The number of frames of our input node is obviously unknown. The user // could for example seek any time, randomly "ending" the clip. // This specific value was suggested by the VapourSynth developer. int enough_for_everyone = INT_MAX / 16; // Note: this is called from createFilter, so no need for locking. VSVideoInfo fmt = { .format = p->vsapi->getFormatPreset(mp_to_vs(p->fmt_in.imgfmt), p->vscore), .width = p->fmt_in.w, .height = p->fmt_in.h, .numFrames = enough_for_everyone, }; if (!fmt.format) { p->vsapi->setError(out, "Unsupported input format.\n"); return; } p->vsapi->setVideoInfo(&fmt, 1, node); p->in_node_active = true; } static const VSFrameRef *VS_CC infiltGetFrame(int frameno, int activationReason, void **instanceData, void **frameData, VSFrameContext *frameCtx, VSCore *core, const VSAPI *vsapi) { struct vf_instance *vf = *instanceData; struct vf_priv_s *p = vf->priv; VSFrameRef *ret = NULL; pthread_mutex_lock(&p->lock); MP_TRACE(vf, "VS asking for frame %d (at %d)\n", frameno, p->in_frameno); while (1) { if (p->shutdown) { p->vsapi->setFilterError("EOF or filter reinit/uninit", frameCtx); MP_DBG(vf, "returning error on EOF/reset\n"); break; } if (p->initializing) { MP_WARN(vf, "Frame requested during init! This is unsupported.\n" "Returning black dummy frame with 0 duration.\n"); ret = alloc_vs_frame(p, &vf->fmt_in); if (!ret) { p->vsapi->setFilterError("Could not allocate VS frame", frameCtx); break; } struct mp_image vsframe = map_vs_frame(p, ret, true); mp_image_clear(&vsframe, 0, 0, vf->fmt_in.w, vf->fmt_in.h); struct mp_image dummy = {0}; mp_image_set_params(&dummy, &vf->fmt_in); set_vs_frame_props(p, ret, &dummy, 0, 1); break; } if (frameno < p->in_frameno) { char msg[180]; snprintf(msg, sizeof(msg), "Frame %d requested, but only have frames starting from %d. " "Try increasing the buffered-frames suboption.", frameno, p->in_frameno); MP_FATAL(vf, "%s\n", msg); p->vsapi->setFilterError(msg, frameCtx); break; } if (frameno >= p->in_frameno + MP_TALLOC_AVAIL(p->buffered)) { // Too far in the future. Remove frames, so that the main thread can // queue new frames. if (p->num_buffered) { drain_oldest_buffered_frame(p); pthread_cond_broadcast(&p->wakeup); if (vf->chain->wakeup_callback) vf->chain->wakeup_callback(vf->chain->wakeup_callback_ctx); continue; } } if (frameno >= p->in_frameno + p->num_buffered) { // If we think EOF was reached, don't wait for new input, and assume // the VS filter has reached EOF. if (p->eof) { p->shutdown = true; continue; } } if (frameno < p->in_frameno + p->num_buffered) { struct mp_image *img = p->buffered[frameno - p->in_frameno]; ret = alloc_vs_frame(p, &img->params); if (!ret) { p->vsapi->setFilterError("Could not allocate VS frame", frameCtx); break; } struct mp_image vsframe = map_vs_frame(p, ret, true); mp_image_copy(&vsframe, img); int res = 1e6; int dur = img->pts * res + 0.5; set_vs_frame_props(p, ret, img, dur, res); break; } pthread_cond_wait(&p->wakeup, &p->lock); } pthread_cond_broadcast(&p->wakeup); pthread_mutex_unlock(&p->lock); return ret; } static void VS_CC infiltFree(void *instanceData, VSCore *core, const VSAPI *vsapi) { struct vf_instance *vf = instanceData; struct vf_priv_s *p = vf->priv; pthread_mutex_lock(&p->lock); p->in_node_active = false; pthread_cond_broadcast(&p->wakeup); pthread_mutex_unlock(&p->lock); } // number of getAsyncFrame calls in progress // must be called with p->lock held static int num_requested(struct vf_priv_s *p) { int r = 0; for (int n = 0; n < p->max_requests; n++) r += p->requested[n] == &dummy_img; return r; }
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 (!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; MP_VERBOSE(d_video, "Container reported FPS: %f\n", sh->video->fps); 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"); } #if HAVE_ENCODING if (mpctx->encode_lavc_ctx && d_video) encode_lavc_set_video_fps(mpctx->encode_lavc_ctx, d_video->fps); #endif 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, MPSEEK_VERY_EXACT, 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 d_video->waiting_decoded_mpi ? VD_EOF : VD_PROGRESS; } 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->dts != MP_NOPTS_VALUE) pkt->dts += 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->opts->frame_dropping & 2)) { 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) + 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; } // Move the frame in next_frame[1] to next_frame[0]. This makes the frame // "known" to the playback logic. A frame in next_frame[0] is either "known" or // NULL, so the moving must always be done by this function. static void shift_new_frame(struct MPContext *mpctx) { if (mpctx->next_frame[0] || !mpctx->next_frame[1]) return; mpctx->next_frame[0] = mpctx->next_frame[1]; mpctx->next_frame[1] = NULL; double frame_time = 0; double pts = mpctx->next_frame[0]->pts; if (mpctx->video_pts != MP_NOPTS_VALUE) { frame_time = pts - mpctx->video_pts; double tolerance = 15; if (mpctx->demuxer->ts_resets_possible) { // Fortunately no real framerate is likely to go below this. It // still could be that the file is VFR, but the demuxer reports a // higher rate, so account for the case of e.g. 60hz demuxer fps // but 23hz actual fps. double fps = 23.976; if (mpctx->d_video->fps > 0 && mpctx->d_video->fps < fps) fps = mpctx->d_video->fps; tolerance = 3 * 1.0 / fps; } if (frame_time <= 0 || frame_time >= tolerance) { // Assume a discontinuity. MP_WARN(mpctx, "Invalid video timestamp: %f -> %f\n", mpctx->video_pts, pts); frame_time = 0; mpctx->audio_status = STATUS_SYNCING; } } mpctx->video_next_pts = pts; mpctx->delay -= frame_time; if (mpctx->video_status >= STATUS_PLAYING) { mpctx->time_frame += frame_time / mpctx->opts->playback_speed; adjust_sync(mpctx, pts, frame_time); } mpctx->dropped_frames = 0; MP_TRACE(mpctx, "frametime=%5.3f\n", frame_time); } // Whether it's fine to call add_new_frame() now. static bool needs_new_frame(struct MPContext *mpctx) { return !mpctx->next_frame[1]; } // Queue a frame to mpctx->next_frame[]. Call only if needs_new_frame() signals ok. static void add_new_frame(struct MPContext *mpctx, struct mp_image *frame) { assert(needs_new_frame(mpctx)); assert(frame); mpctx->next_frame[1] = frame; shift_new_frame(mpctx); }
void write_video(struct MPContext *mpctx, double endpts) { struct MPOpts *opts = mpctx->opts; struct vo *vo = mpctx->video_out; if (!mpctx->d_video) return; // Actual playback starts when both audio and video are ready. if (mpctx->video_status == STATUS_READY) return; if (mpctx->paused && mpctx->video_status >= STATUS_READY) return; int r = video_output_image(mpctx, endpts); MP_TRACE(mpctx, "video_output_image: %d\n", r); if (r < 0) goto error; if (r == VD_WAIT) // Demuxer will wake us up for more packets to decode. return; if (r == VD_EOF) { mpctx->video_status = vo_still_displaying(vo) ? STATUS_DRAINING : STATUS_EOF; mpctx->delay = 0; mpctx->last_av_difference = 0; MP_DBG(mpctx, "video EOF (status=%d)\n", mpctx->video_status); return; } if (mpctx->video_status > STATUS_PLAYING) mpctx->video_status = STATUS_PLAYING; if (r != VD_NEW_FRAME) { mpctx->sleeptime = 0; // Decode more in next iteration. return; } // Filter output is different from VO input? struct mp_image_params p = mpctx->next_frames[0]->params; if (!vo->params || !mp_image_params_equal(&p, vo->params)) { // Changing config deletes the current frame; wait until it's finished. if (vo_still_displaying(vo)) return; const struct vo_driver *info = mpctx->video_out->driver; char extra[20] = {0}; if (p.w != p.d_w || p.h != p.d_h) snprintf(extra, sizeof(extra), " => %dx%d", p.d_w, p.d_h); MP_INFO(mpctx, "VO: [%s] %dx%d%s %s\n", info->name, p.w, p.h, extra, vo_format_name(p.imgfmt)); MP_VERBOSE(mpctx, "VO: Description: %s\n", info->description); int vo_r = vo_reconfig(vo, &p, 0); if (vo_r < 0) { mpctx->error_playing = MPV_ERROR_VO_INIT_FAILED; goto error; } init_vo(mpctx); } mpctx->time_frame -= get_relative_time(mpctx); update_avsync_before_frame(mpctx); double time_frame = MPMAX(mpctx->time_frame, -1); int64_t pts = mp_time_us() + (int64_t)(time_frame * 1e6); // wait until VO wakes us up to get more frames if (!vo_is_ready_for_frame(vo, pts)) { if (video_feed_async_filter(mpctx) < 0) goto error; return; } assert(mpctx->num_next_frames >= 1); struct vo_frame dummy = { .pts = pts, .duration = -1, .num_frames = mpctx->num_next_frames, }; for (int n = 0; n < dummy.num_frames; n++) dummy.frames[n] = mpctx->next_frames[n]; struct vo_frame *frame = vo_frame_ref(&dummy); double diff = -1; double vpts0 = mpctx->next_frames[0]->pts; double vpts1 = MP_NOPTS_VALUE; if (mpctx->num_next_frames >= 2) vpts1 = mpctx->next_frames[1]->pts; if (vpts0 != MP_NOPTS_VALUE && vpts1 != MP_NOPTS_VALUE) diff = vpts1 - vpts0; if (diff < 0 && mpctx->d_video->fps > 0) diff = 1.0 / mpctx->d_video->fps; // fallback to demuxer-reported fps if (opts->untimed || vo->driver->untimed) diff = -1; // disable frame dropping and aspects of frame timing if (diff >= 0) { // expected A/V sync correction is ignored diff /= opts->playback_speed; if (mpctx->time_frame < 0) diff += mpctx->time_frame; frame->duration = MPCLAMP(diff, 0, 10) * 1e6; } mpctx->video_pts = mpctx->next_frames[0]->pts; mpctx->last_vo_pts = mpctx->video_pts; mpctx->playback_pts = mpctx->video_pts; update_avsync_after_frame(mpctx); mpctx->osd_force_update = true; update_osd_msg(mpctx); update_subtitles(mpctx); vo_queue_frame(vo, frame); shift_frames(mpctx); // The frames were shifted down; "initialize" the new first entry. if (mpctx->num_next_frames >= 1) handle_new_frame(mpctx); mpctx->shown_vframes++; if (mpctx->video_status < STATUS_PLAYING) { mpctx->video_status = STATUS_READY; // After a seek, make sure to wait until the first frame is visible. vo_wait_frame(vo); MP_VERBOSE(mpctx, "first video frame after restart shown\n"); } screenshot_flip(mpctx); mp_notify(mpctx, MPV_EVENT_TICK, NULL); if (!mpctx->sync_audio_to_video) mpctx->video_status = STATUS_EOF; if (mpctx->video_status != STATUS_EOF) { if (mpctx->step_frames > 0) { mpctx->step_frames--; if (!mpctx->step_frames && !opts->pause) pause_player(mpctx); } if (mpctx->max_frames == 0 && !mpctx->stop_play) mpctx->stop_play = AT_END_OF_FILE; if (mpctx->max_frames > 0) mpctx->max_frames--; } mpctx->sleeptime = 0; return; error: MP_FATAL(mpctx, "Could not initialize video chain.\n"); uninit_video_chain(mpctx); error_on_track(mpctx, mpctx->current_track[STREAM_VIDEO][0]); handle_force_window(mpctx, true); mpctx->sleeptime = 0; }
void write_video(struct MPContext *mpctx, double endpts) { struct MPOpts *opts = mpctx->opts; struct vo *vo = mpctx->video_out; if (!mpctx->d_video) return; // Actual playback starts when both audio and video are ready. if (mpctx->video_status == STATUS_READY) return; if (mpctx->paused && mpctx->video_status >= STATUS_READY) return; update_fps(mpctx); int r = video_output_image(mpctx, endpts); MP_TRACE(mpctx, "video_output_image: %d\n", r); if (r < 0) goto error; if (r == VD_WAIT) // Demuxer will wake us up for more packets to decode. return; if (r == VD_EOF) { mpctx->video_status = vo_still_displaying(vo) ? STATUS_DRAINING : STATUS_EOF; mpctx->delay = 0; mpctx->last_av_difference = 0; MP_VERBOSE(mpctx, "video EOF (status=%d)\n", mpctx->video_status); return; } if (mpctx->video_status > STATUS_PLAYING) mpctx->video_status = STATUS_PLAYING; mpctx->time_frame -= get_relative_time(mpctx); update_avsync_before_frame(mpctx); if (r != VD_NEW_FRAME) { mpctx->sleeptime = 0; // Decode more in next iteration. return; } // Filter output is different from VO input? struct mp_image_params p = mpctx->next_frame[0]->params; if (!vo->params || !mp_image_params_equal(&p, vo->params)) { // Changing config deletes the current frame; wait until it's finished. if (vo_still_displaying(vo)) return; const struct vo_driver *info = mpctx->video_out->driver; MP_INFO(mpctx, "VO: [%s] %dx%d => %dx%d %s\n", info->name, p.w, p.h, p.d_w, p.d_h, vo_format_name(p.imgfmt)); MP_VERBOSE(mpctx, "VO: Description: %s\n", info->description); int vo_r = vo_reconfig(vo, &p, 0); if (vo_r < 0) goto error; init_vo(mpctx); mpctx->time_frame = 0; // display immediately } double time_frame = MPMAX(mpctx->time_frame, -1); int64_t pts = mp_time_us() + (int64_t)(time_frame * 1e6); if (!vo_is_ready_for_frame(vo, pts)) return; // wait until VO wakes us up to get more frames int64_t duration = -1; double diff = -1; double vpts0 = mpctx->next_frame[0] ? mpctx->next_frame[0]->pts : MP_NOPTS_VALUE; double vpts1 = mpctx->next_frame[1] ? mpctx->next_frame[1]->pts : MP_NOPTS_VALUE; if (vpts0 != MP_NOPTS_VALUE && vpts1 != MP_NOPTS_VALUE) diff = vpts1 - vpts0; if (diff < 0 && mpctx->d_video->fps > 0) diff = 1.0 / mpctx->d_video->fps; // fallback to demuxer-reported fps if (diff >= 0) { // expected A/V sync correction is ignored diff /= opts->playback_speed; if (mpctx->time_frame < 0) diff += mpctx->time_frame; duration = MPCLAMP(diff, 0, 10) * 1e6; } mpctx->video_pts = mpctx->next_frame[0]->pts; mpctx->last_vo_pts = mpctx->video_pts; mpctx->playback_pts = mpctx->video_pts; mpctx->osd_force_update = true; update_osd_msg(mpctx); update_subtitles(mpctx); vo_queue_frame(vo, mpctx->next_frame[0], pts, duration); mpctx->next_frame[0] = NULL; mpctx->shown_vframes++; if (mpctx->video_status < STATUS_PLAYING) { mpctx->video_status = STATUS_READY; // After a seek, make sure to wait until the first frame is visible. vo_wait_frame(vo); } update_avsync_after_frame(mpctx); screenshot_flip(mpctx); mp_notify(mpctx, MPV_EVENT_TICK, NULL); if (!mpctx->sync_audio_to_video) mpctx->video_status = STATUS_EOF; if (mpctx->video_status != STATUS_EOF) { if (mpctx->step_frames > 0) { mpctx->step_frames--; if (!mpctx->step_frames && !opts->pause) pause_player(mpctx); } if (mpctx->max_frames == 0) mpctx->stop_play = PT_NEXT_ENTRY; if (mpctx->max_frames > 0) mpctx->max_frames--; } mpctx->sleeptime = 0; return; error: MP_FATAL(mpctx, "Could not initialize video chain.\n"); int uninit = INITIALIZED_VCODEC; if (!opts->force_vo) uninit |= INITIALIZED_VO; uninit_player(mpctx, uninit); if (!mpctx->current_track[STREAM_AUDIO]) mpctx->stop_play = PT_NEXT_ENTRY; mpctx->error_playing = true; handle_force_window(mpctx, true); mpctx->sleeptime = 0; }