// position pointer in file, position in second AVFrame *VideoFFmpeg::grabFrame(long position) { AVPacket packet; int frameFinished; int posFound = 1; bool frameLoaded = false; int64_t targetTs = 0; CacheFrame *frame; int64_t dts = 0; if (m_cacheStarted) { // when cache is active, we must not read the file directly do { pthread_mutex_lock(&m_cacheMutex); frame = (CacheFrame *)m_frameCacheBase.first; pthread_mutex_unlock(&m_cacheMutex); // no need to remove the frame from the queue: the cache thread does not touch the head, only the tail if (frame == NULL) { // no frame in cache, in case of file it is an abnormal situation if (m_isFile) { // go back to no threaded reading stopCache(); break; } return NULL; } if (frame->framePosition == -1) { // this frame mark the end of the file (only used for file) // leave in cache to make sure we don't miss it m_eof = true; return NULL; } // for streaming, always return the next frame, // that's what grabFrame does in non cache mode anyway. if (m_isStreaming || frame->framePosition == position) { return frame->frame; } // for cam, skip old frames to keep image realtime. // There should be no risk of clock drift since it all happens on the same CPU if (frame->framePosition > position) { // this can happen after rewind if the seek didn't find the first frame // the frame in the buffer is ahead of time, just leave it there return NULL; } // this frame is not useful, release it pthread_mutex_lock(&m_cacheMutex); BLI_remlink(&m_frameCacheBase, frame); BLI_addtail(&m_frameCacheFree, frame); pthread_mutex_unlock(&m_cacheMutex); } while (true); } double timeBase = av_q2d(m_formatCtx->streams[m_videoStream]->time_base); int64_t startTs = m_formatCtx->streams[m_videoStream]->start_time; if (startTs == AV_NOPTS_VALUE) startTs = 0; // come here when there is no cache or cache has been stopped // locate the frame, by seeking if necessary (seeking is only possible for files) if (m_isFile) { // first check if the position that we are looking for is in the preseek range // if so, just read the frame until we get there if (position > m_curPosition + 1 && m_preseek && position - (m_curPosition + 1) < m_preseek) { while (av_read_frame(m_formatCtx, &packet)>=0) { if (packet.stream_index == m_videoStream) { avcodec_decode_video2( m_codecCtx, m_frame, &frameFinished, &packet); if (frameFinished) { m_curPosition = (long)((packet.dts-startTs) * (m_baseFrameRate*timeBase) + 0.5); } } av_free_packet(&packet); if (position == m_curPosition+1) break; } } // if the position is not in preseek, do a direct jump if (position != m_curPosition + 1) { int64_t pos = (int64_t)((position - m_preseek) / (m_baseFrameRate*timeBase)); if (pos < 0) pos = 0; pos += startTs; if (position <= m_curPosition || !m_eof) { #if 0 // Tried to make this work but couldn't: seeking on byte is ignored by the // format plugin and it will generally continue to read from last timestamp. // Too bad because frame seek is not always able to get the first frame // of the file. if (position <= m_preseek) { // we can safely go the beginning of the file if (av_seek_frame(m_formatCtx, m_videoStream, 0, AVSEEK_FLAG_BYTE) >= 0) { // binary seek does not reset the timestamp, must do it now av_update_cur_dts(m_formatCtx, m_formatCtx->streams[m_videoStream], startTs); m_curPosition = 0; } } else #endif { // current position is now lost, guess a value. if (av_seek_frame(m_formatCtx, m_videoStream, pos, AVSEEK_FLAG_BACKWARD) >= 0) { // current position is now lost, guess a value. // It's not important because it will be set at this end of this function m_curPosition = position - m_preseek - 1; } } } // this is the timestamp of the frame we're looking for targetTs = (int64_t)(position / (m_baseFrameRate * timeBase)) + startTs; posFound = 0; avcodec_flush_buffers(m_codecCtx); } } else if (m_isThreaded) { // cache is not started but threading is possible // better not read the stream => make take some time, better start caching if (startCache()) return NULL; // Abnormal!!! could not start cache, fall back on direct read m_isThreaded = false; } // find the correct frame, in case of streaming and no cache, it means just // return the next frame. This is not quite correct, may need more work while (av_read_frame(m_formatCtx, &packet) >= 0) { if (packet.stream_index == m_videoStream) { AVFrame *input = m_frame; short counter = 0; /* If m_isImage, while the data is not read properly (png, tiffs, etc formats may need several pass), else don't need while loop*/ do { avcodec_decode_video2(m_codecCtx, m_frame, &frameFinished, &packet); counter++; } while ((input->data[0] == 0 && input->data[1] == 0 && input->data[2] == 0 && input->data[3] == 0) && counter < 10 && m_isImage); // remember dts to compute exact frame number dts = packet.dts; if (frameFinished && !posFound) { if (dts >= targetTs) { posFound = 1; } } if (frameFinished && posFound == 1) { AVFrame * input = m_frame; /* This means the data wasnt read properly, * this check stops crashing */ if ( input->data[0]==0 && input->data[1]==0 && input->data[2]==0 && input->data[3]==0) { av_free_packet(&packet); break; } if (m_deinterlace) { if (avpicture_deinterlace( (AVPicture*) m_frameDeinterlaced, (const AVPicture*) m_frame, m_codecCtx->pix_fmt, m_codecCtx->width, m_codecCtx->height) >= 0) { input = m_frameDeinterlaced; } } // convert to RGB24 sws_scale(m_imgConvertCtx, input->data, input->linesize, 0, m_codecCtx->height, m_frameRGB->data, m_frameRGB->linesize); av_free_packet(&packet); frameLoaded = true; break; } } av_free_packet(&packet); } m_eof = m_isFile && !frameLoaded; if (frameLoaded) { m_curPosition = (long)((dts-startTs) * (m_baseFrameRate*timeBase) + 0.5); if (m_isThreaded) { // normal case for file: first locate, then start cache if (!startCache()) { // Abnormal!! could not start cache, return to non-cache mode m_isThreaded = false; } } return m_frameRGB; } return NULL; }
static ImBuf *ffmpeg_fetchibuf(struct anim *anim, int position, IMB_Timecode_Type tc) { int64_t pts_to_search = 0; double frame_rate; double pts_time_base; long long st_time; struct anim_index *tc_index = 0; AVStream *v_st; int new_frame_index = 0; /* To quiet gcc barking... */ int old_frame_index = 0; /* To quiet gcc barking... */ if (anim == 0) return (0); av_log(anim->pFormatCtx, AV_LOG_DEBUG, "FETCH: pos=%d\n", position); if (tc != IMB_TC_NONE) { tc_index = IMB_anim_open_index(anim, tc); } v_st = anim->pFormatCtx->streams[anim->videoStream]; frame_rate = av_q2d(v_st->r_frame_rate); st_time = anim->pFormatCtx->start_time; pts_time_base = av_q2d(v_st->time_base); if (tc_index) { new_frame_index = IMB_indexer_get_frame_index( tc_index, position); old_frame_index = IMB_indexer_get_frame_index( tc_index, anim->curposition); pts_to_search = IMB_indexer_get_pts( tc_index, new_frame_index); } else { pts_to_search = (long long) floor(((double) position) / pts_time_base / frame_rate + 0.5); if (st_time != AV_NOPTS_VALUE) { pts_to_search += st_time / pts_time_base / AV_TIME_BASE; } } av_log(anim->pFormatCtx, AV_LOG_DEBUG, "FETCH: looking for PTS=%lld " "(pts_timebase=%g, frame_rate=%g, st_time=%lld)\n", (long long int)pts_to_search, pts_time_base, frame_rate, st_time); if (anim->last_frame && anim->last_pts <= pts_to_search && anim->next_pts > pts_to_search) { av_log(anim->pFormatCtx, AV_LOG_DEBUG, "FETCH: frame repeat: last: %lld next: %lld\n", (long long int)anim->last_pts, (long long int)anim->next_pts); IMB_refImBuf(anim->last_frame); anim->curposition = position; return anim->last_frame; } if (position > anim->curposition + 1 && anim->preseek && !tc_index && position - (anim->curposition + 1) < anim->preseek) { av_log(anim->pFormatCtx, AV_LOG_DEBUG, "FETCH: within preseek interval (no index)\n"); ffmpeg_decode_video_frame_scan(anim, pts_to_search); } else if (tc_index && IMB_indexer_can_scan(tc_index, old_frame_index, new_frame_index)) { av_log(anim->pFormatCtx, AV_LOG_DEBUG, "FETCH: within preseek interval " "(index tells us)\n"); ffmpeg_decode_video_frame_scan(anim, pts_to_search); } else if (position != anim->curposition + 1) { long long pos; int ret; if (tc_index) { unsigned long long dts; pos = IMB_indexer_get_seek_pos( tc_index, new_frame_index); dts = IMB_indexer_get_seek_pos_dts( tc_index, new_frame_index); av_log(anim->pFormatCtx, AV_LOG_DEBUG, "TC INDEX seek pos = %lld\n", pos); av_log(anim->pFormatCtx, AV_LOG_DEBUG, "TC INDEX seek dts = %lld\n", dts); if (ffmpeg_seek_by_byte(anim->pFormatCtx)) { av_log(anim->pFormatCtx, AV_LOG_DEBUG, "... using BYTE pos\n"); ret = av_seek_frame(anim->pFormatCtx, -1, pos, AVSEEK_FLAG_BYTE); av_update_cur_dts(anim->pFormatCtx, v_st, dts); } else { av_log(anim->pFormatCtx, AV_LOG_DEBUG, "... using DTS pos\n"); ret = av_seek_frame(anim->pFormatCtx, anim->videoStream, dts, AVSEEK_FLAG_BACKWARD); } } else { pos = (long long) (position - anim->preseek) * AV_TIME_BASE / frame_rate; av_log(anim->pFormatCtx, AV_LOG_DEBUG, "NO INDEX seek pos = %lld, st_time = %lld\n", pos, (st_time != AV_NOPTS_VALUE) ? st_time : 0); if (pos < 0) { pos = 0; } if (st_time != AV_NOPTS_VALUE) { pos += st_time; } av_log(anim->pFormatCtx, AV_LOG_DEBUG, "NO INDEX final seek pos = %lld\n", pos); ret = av_seek_frame(anim->pFormatCtx, -1, pos, AVSEEK_FLAG_BACKWARD); } if (ret < 0) { av_log(anim->pFormatCtx, AV_LOG_ERROR, "FETCH: " "error while seeking to DTS = %lld " "(frameno = %d, PTS = %lld): errcode = %d\n", pos, position, (long long int)pts_to_search, ret); } avcodec_flush_buffers(anim->pCodecCtx); anim->next_pts = -1; if (anim->next_packet.stream_index == anim->videoStream) { av_free_packet(&anim->next_packet); anim->next_packet.stream_index = -1; } /* memset(anim->pFrame, ...) ?? */ if (ret >= 0) { ffmpeg_decode_video_frame_scan(anim, pts_to_search); } } else if (position == 0 && anim->curposition == -1) { /* first frame without seeking special case... */ ffmpeg_decode_video_frame(anim); } else { av_log(anim->pFormatCtx, AV_LOG_DEBUG, "FETCH: no seek necessary, just continue...\n"); } IMB_freeImBuf(anim->last_frame); anim->last_frame = IMB_allocImBuf(anim->x, anim->y, 32, IB_rect); anim->last_frame->rect_colorspace = colormanage_colorspace_get_named(anim->colorspace); ffmpeg_postprocess(anim); anim->last_pts = anim->next_pts; ffmpeg_decode_video_frame(anim); anim->curposition = position; IMB_refImBuf(anim->last_frame); return anim->last_frame; }