Changeset View
Changeset View
Standalone View
Standalone View
source/blender/imbuf/intern/anim_movie.c
| Show First 20 Lines • Show All 963 Lines • ▼ Show 20 Lines | av_log(anim->pFormatCtx, | ||||
| " DECODE READ FAILED: av_read_frame() " | " DECODE READ FAILED: av_read_frame() " | ||||
| "returned error: %d\n", | "returned error: %d\n", | ||||
| rval); | rval); | ||||
| } | } | ||||
| return (rval >= 0); | return (rval >= 0); | ||||
| } | } | ||||
| static void ffmpeg_decode_video_frame_scan(struct anim *anim, int64_t pts_to_search) | |||||
| { | |||||
| /* there seem to exist *very* silly GOP lengths out in the wild... */ | |||||
| int count = 1000; | |||||
| av_log(anim->pFormatCtx, | |||||
| AV_LOG_DEBUG, | |||||
| "SCAN start: considering pts=%" PRId64 " in search of %" PRId64 "\n", | |||||
| (int64_t)anim->next_pts, | |||||
| (int64_t)pts_to_search); | |||||
| while (count > 0 && anim->next_pts < pts_to_search) { | |||||
| av_log(anim->pFormatCtx, | |||||
| AV_LOG_DEBUG, | |||||
| " WHILE: pts=%" PRId64 " in search of %" PRId64 "\n", | |||||
| (int64_t)anim->next_pts, | |||||
| (int64_t)pts_to_search); | |||||
| if (!ffmpeg_decode_video_frame(anim)) { | |||||
| break; | |||||
| } | |||||
| count--; | |||||
| } | |||||
| if (count == 0) { | |||||
| av_log(anim->pFormatCtx, | |||||
| AV_LOG_ERROR, | |||||
| "SCAN failed: completely lost in stream, " | |||||
| "bailing out at PTS=%" PRId64 ", searching for PTS=%" PRId64 "\n", | |||||
| (int64_t)anim->next_pts, | |||||
| (int64_t)pts_to_search); | |||||
| } | |||||
| if (anim->next_pts == pts_to_search) { | |||||
| av_log(anim->pFormatCtx, AV_LOG_DEBUG, "SCAN HAPPY: we found our PTS!\n"); | |||||
| } | |||||
| else { | |||||
| av_log(anim->pFormatCtx, AV_LOG_ERROR, "SCAN UNHAPPY: PTS not matched!\n"); | |||||
| } | |||||
| } | |||||
| static int match_format(const char *name, AVFormatContext *pFormatCtx) | static int match_format(const char *name, AVFormatContext *pFormatCtx) | ||||
| { | { | ||||
| const char *p; | const char *p; | ||||
| int len, namelen; | int len, namelen; | ||||
| const char *names = pFormatCtx->iformat->name; | const char *names = pFormatCtx->iformat->name; | ||||
| if (!name || !names) { | if (!name || !names) { | ||||
| Show All 26 Lines | while (*p) { | ||||
| if (match_format(*p++, pFormatCtx)) { | if (match_format(*p++, pFormatCtx)) { | ||||
| return true; | return true; | ||||
| } | } | ||||
| } | } | ||||
| return false; | return false; | ||||
| } | } | ||||
| static ImBuf *ffmpeg_fetchibuf(struct anim *anim, int position, IMB_Timecode_Type tc) | static int64_t ffmpeg_get_pts_to_search(struct anim *anim, | ||||
| { | struct anim_index *tc_index, | ||||
| int64_t pts_to_search = 0; | int position) | ||||
| double frame_rate; | { | ||||
| double pts_time_base; | int64_t pts_to_search; | ||||
| int64_t st_time; | int64_t st_time = anim->pFormatCtx->start_time; | ||||
| struct anim_index *tc_index = 0; | AVStream *v_st = anim->pFormatCtx->streams[anim->videoStream]; | ||||
| AVStream *v_st; | double frame_rate = av_q2d(av_guess_frame_rate(anim->pFormatCtx, v_st, NULL)); | ||||
| int new_frame_index = 0; /* To quiet gcc barking... */ | double pts_time_base = av_q2d(v_st->time_base); | ||||
| int old_frame_index = 0; /* To quiet gcc barking... */ | |||||
| if (anim == NULL) { | |||||
| 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(av_guess_frame_rate(anim->pFormatCtx, v_st, NULL)); | |||||
| st_time = anim->pFormatCtx->start_time; | |||||
| pts_time_base = av_q2d(v_st->time_base); | |||||
| if (tc_index) { | if (tc_index) { | ||||
| new_frame_index = IMB_indexer_get_frame_index(tc_index, position); | int 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); | pts_to_search = IMB_indexer_get_pts(tc_index, new_frame_index); | ||||
| } | } | ||||
| else { | else { | ||||
| pts_to_search = (long long)floor(((double)position) / pts_time_base / frame_rate + 0.5); | pts_to_search = (long long)floor(((double)position) / pts_time_base / frame_rate + 0.5); | ||||
| if (st_time != AV_NOPTS_VALUE) { | if (st_time != AV_NOPTS_VALUE) { | ||||
| pts_to_search += st_time / pts_time_base / AV_TIME_BASE; | pts_to_search += st_time / pts_time_base / AV_TIME_BASE; | ||||
| } | } | ||||
| } | } | ||||
| return pts_to_search; | |||||
| } | |||||
| av_log(anim->pFormatCtx, | static bool ffmpeg_pts_matches_last_frame(struct anim *anim, int64_t pts_to_search) | ||||
| AV_LOG_DEBUG, | { | ||||
| "FETCH: looking for PTS=%" PRId64 " (pts_timebase=%g, frame_rate=%g, st_time=%" PRId64 | return anim->last_frame && anim->last_pts <= pts_to_search && anim->next_pts > pts_to_search; | ||||
| ")\n", | } | ||||
| (int64_t)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) { | /* Requested video frame is expected to be found within different GOP as last decoded frame. | ||||
| av_log(anim->pFormatCtx, | * Seeking to new position and scanning is fastest way to get requested frame. | ||||
| AV_LOG_DEBUG, | * Check whether ffmpeg_can_scan() and ffmpeg_pts_matches_last_frame() is false before using this | ||||
| "FETCH: frame repeat: last: %" PRId64 " next: %" PRId64 "\n", | * function. */ | ||||
| (int64_t)anim->last_pts, | static bool ffmpeg_can_seek(struct anim *anim, int position) | ||||
| (int64_t)anim->next_pts); | { | ||||
| IMB_refImBuf(anim->last_frame); | return position != anim->curposition + 1; | ||||
| anim->curposition = position; | |||||
| return anim->last_frame; | |||||
| } | } | ||||
| /* Requested video frame is expected to be found within same GOP as last decoded frame. | |||||
| * Decoding frames in sequence until frame matches requested one is fastest way to get it. */ | |||||
| static bool ffmpeg_can_scan(struct anim *anim, int position, struct anim_index *tc_index) | |||||
| { | |||||
| if (position > anim->curposition + 1 && anim->preseek && !tc_index && | if (position > anim->curposition + 1 && anim->preseek && !tc_index && | ||||
| position - (anim->curposition + 1) < anim->preseek) { | position - (anim->curposition + 1) < anim->preseek) { | ||||
| av_log(anim->pFormatCtx, AV_LOG_DEBUG, "FETCH: within preseek interval (no index)\n"); | return true; | ||||
| } | |||||
| ffmpeg_decode_video_frame_scan(anim, pts_to_search); | if (tc_index == NULL) { | ||||
| return false; | |||||
| } | |||||
| int new_frame_index = IMB_indexer_get_frame_index(tc_index, position); | |||||
| int old_frame_index = IMB_indexer_get_frame_index(tc_index, anim->curposition); | |||||
| return IMB_indexer_can_scan(tc_index, old_frame_index, new_frame_index); | |||||
| } | } | ||||
| else if (tc_index && IMB_indexer_can_scan(tc_index, old_frame_index, new_frame_index)) { | |||||
| static bool ffmpeg_is_first_frame_decode(struct anim *anim, int position) | |||||
| { | |||||
| return position == 0 && anim->curposition == -1; | |||||
| } | |||||
| /* Decode frames one by one until its PTS matches pts_to_search. */ | |||||
| static void ffmpeg_decode_video_frame_scan(struct anim *anim, int64_t pts_to_search) | |||||
| { | |||||
| av_log(anim->pFormatCtx, AV_LOG_DEBUG, "FETCH: within preseek interval\n"); | |||||
| /* there seem to exist *very* silly GOP lengths out in the wild... */ | |||||
| int count = 1000; | |||||
| av_log(anim->pFormatCtx, | av_log(anim->pFormatCtx, | ||||
| AV_LOG_DEBUG, | AV_LOG_DEBUG, | ||||
| "FETCH: within preseek interval " | "SCAN start: considering pts=%" PRId64 " in search of %" PRId64 "\n", | ||||
| "(index tells us)\n"); | (int64_t)anim->next_pts, | ||||
| (int64_t)pts_to_search); | |||||
| ffmpeg_decode_video_frame_scan(anim, pts_to_search); | while (count > 0 && anim->next_pts < pts_to_search) { | ||||
| av_log(anim->pFormatCtx, | |||||
| AV_LOG_DEBUG, | |||||
| " WHILE: pts=%" PRId64 " in search of %" PRId64 "\n", | |||||
| (int64_t)anim->next_pts, | |||||
| (int64_t)pts_to_search); | |||||
| if (!ffmpeg_decode_video_frame(anim)) { | |||||
| break; | |||||
| } | |||||
| count--; | |||||
| } | |||||
| if (count == 0) { | |||||
| av_log(anim->pFormatCtx, | |||||
| AV_LOG_ERROR, | |||||
| "SCAN failed: completely lost in stream, " | |||||
| "bailing out at PTS=%" PRId64 ", searching for PTS=%" PRId64 "\n", | |||||
| (int64_t)anim->next_pts, | |||||
| (int64_t)pts_to_search); | |||||
| } | |||||
| if (anim->next_pts == pts_to_search) { | |||||
| av_log(anim->pFormatCtx, AV_LOG_DEBUG, "SCAN HAPPY: we found our PTS!\n"); | |||||
| } | |||||
| else { | |||||
| av_log(anim->pFormatCtx, AV_LOG_ERROR, "SCAN UNHAPPY: PTS not matched!\n"); | |||||
| } | |||||
| } | } | ||||
| else if (position != anim->curposition + 1) { | |||||
| /* Seek to last necessary I-frame and scan-decode until requested frame is found. */ | |||||
| static void ffmpeg_seek_and_decode(struct anim *anim, int position, struct anim_index *tc_index) | |||||
| { | |||||
| AVStream *v_st = anim->pFormatCtx->streams[anim->videoStream]; | |||||
| double frame_rate = av_q2d(av_guess_frame_rate(anim->pFormatCtx, v_st, NULL)); | |||||
| int64_t st_time = anim->pFormatCtx->start_time; | |||||
| int64_t pts_to_search = ffmpeg_get_pts_to_search(anim, tc_index, position); | |||||
| int64_t pos; | int64_t pos; | ||||
| int ret; | int ret; | ||||
| if (tc_index) { | if (tc_index) { | ||||
| int new_frame_index = IMB_indexer_get_frame_index(tc_index, position); | |||||
| uint64_t dts; | uint64_t dts; | ||||
| pos = IMB_indexer_get_seek_pos(tc_index, new_frame_index); | pos = IMB_indexer_get_seek_pos(tc_index, new_frame_index); | ||||
| dts = IMB_indexer_get_seek_pos_dts(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 = %" PRId64 "\n", pos); | av_log(anim->pFormatCtx, AV_LOG_DEBUG, "TC INDEX seek pos = %" PRId64 "\n", pos); | ||||
| av_log(anim->pFormatCtx, AV_LOG_DEBUG, "TC INDEX seek dts = %" PRIu64 "\n", dts); | av_log(anim->pFormatCtx, AV_LOG_DEBUG, "TC INDEX seek dts = %" PRIu64 "\n", dts); | ||||
| if (ffmpeg_seek_by_byte(anim->pFormatCtx)) { | if (ffmpeg_seek_by_byte(anim->pFormatCtx)) { | ||||
| av_log(anim->pFormatCtx, AV_LOG_DEBUG, "... using BYTE pos\n"); | av_log(anim->pFormatCtx, AV_LOG_DEBUG, "... using BYTE pos\n"); | ||||
| ret = av_seek_frame(anim->pFormatCtx, -1, pos, AVSEEK_FLAG_BYTE); | ret = av_seek_frame(anim->pFormatCtx, -1, pos, AVSEEK_FLAG_BYTE); | ||||
| av_update_cur_dts(anim->pFormatCtx, v_st, dts); | av_update_cur_dts(anim->pFormatCtx, v_st, dts); | ||||
| } | } | ||||
| else { | else { | ||||
| av_log(anim->pFormatCtx, AV_LOG_DEBUG, "... using DTS pos\n"); | av_log(anim->pFormatCtx, AV_LOG_DEBUG, "... using DTS pos\n"); | ||||
| ret = av_seek_frame(anim->pFormatCtx, anim->videoStream, dts, AVSEEK_FLAG_BACKWARD); | ret = av_seek_frame(anim->pFormatCtx, anim->videoStream, dts, AVSEEK_FLAG_BACKWARD); | ||||
| } | } | ||||
| } | } | ||||
| else { | else { | ||||
| pos = (int64_t)position * AV_TIME_BASE / frame_rate; | pos = (int64_t)(position)*AV_TIME_BASE / frame_rate; | ||||
| av_log(anim->pFormatCtx, | av_log(anim->pFormatCtx, | ||||
| AV_LOG_DEBUG, | AV_LOG_DEBUG, | ||||
| "NO INDEX seek pos = %" PRId64 ", st_time = %" PRId64 "\n", | "NO INDEX seek pos = %" PRId64 ", st_time = %" PRId64 "\n", | ||||
| pos, | pos, | ||||
| (st_time != AV_NOPTS_VALUE) ? st_time : 0); | (st_time != AV_NOPTS_VALUE) ? st_time : 0); | ||||
| if (pos < 0) { | if (pos < 0) { | ||||
| pos = 0; | pos = 0; | ||||
| } | } | ||||
| if (st_time != AV_NOPTS_VALUE) { | if (st_time != AV_NOPTS_VALUE) { | ||||
| pos += st_time; | pos += st_time; | ||||
| } | } | ||||
| av_log(anim->pFormatCtx, AV_LOG_DEBUG, "NO INDEX final seek pos = %" PRId64 "\n", pos); | av_log(anim->pFormatCtx, AV_LOG_DEBUG, "NO INDEX final seek pos = %" PRId64 "\n", pos); | ||||
| ret = av_seek_frame(anim->pFormatCtx, -1, pos, AVSEEK_FLAG_BACKWARD); | ret = av_seek_frame(anim->pFormatCtx, -1, pos, AVSEEK_FLAG_BACKWARD); | ||||
| } | } | ||||
| if (ret < 0) { | if (ret < 0) { | ||||
| av_log(anim->pFormatCtx, | av_log(anim->pFormatCtx, | ||||
| AV_LOG_ERROR, | AV_LOG_ERROR, | ||||
| "FETCH: " | "FETCH: " | ||||
| "error while seeking to DTS = %" PRId64 " (frameno = %d, PTS = %" PRId64 | "error while seeking to DTS = %" PRId64 " (frameno = %d, PTS = %" PRId64 | ||||
| "): errcode = %d\n", | "): errcode = %d\n", | ||||
| pos, | pos, | ||||
| position, | position, | ||||
| (int64_t)pts_to_search, | (int64_t)pts_to_search, | ||||
| ret); | ret); | ||||
| } | } | ||||
| avcodec_flush_buffers(anim->pCodecCtx); | avcodec_flush_buffers(anim->pCodecCtx); | ||||
| anim->next_pts = -1; | anim->next_pts = -1; | ||||
| if (anim->next_packet.stream_index == anim->videoStream) { | if (anim->next_packet.stream_index == anim->videoStream) { | ||||
| av_free_packet(&anim->next_packet); | av_free_packet(&anim->next_packet); | ||||
| anim->next_packet.stream_index = -1; | anim->next_packet.stream_index = -1; | ||||
| } | } | ||||
| /* memset(anim->pFrame, ...) ?? */ | /* memset(anim->pFrame, ...) ?? */ | ||||
| if (ret < 0) { | |||||
| /* Seek failed. */ | |||||
| return; | |||||
| } | |||||
| if (ret >= 0) { | |||||
| ffmpeg_decode_video_frame_scan(anim, pts_to_search); | ffmpeg_decode_video_frame_scan(anim, pts_to_search); | ||||
| } | } | ||||
| static ImBuf *ffmpeg_fetchibuf(struct anim *anim, int position, IMB_Timecode_Type tc) | |||||
| { | |||||
| if (anim == NULL) { | |||||
| return NULL; | |||||
| } | } | ||||
| else if (position == 0 && anim->curposition == -1) { | |||||
| /* first frame without seeking special case... */ | av_log(anim->pFormatCtx, AV_LOG_DEBUG, "FETCH: pos=%d\n", position); | ||||
| ffmpeg_decode_video_frame(anim); | |||||
| struct anim_index *tc_index = IMB_anim_open_index(anim, tc); | |||||
| int64_t pts_to_search = ffmpeg_get_pts_to_search(anim, tc_index, position); | |||||
| AVStream *v_st = anim->pFormatCtx->streams[anim->videoStream]; | |||||
| double frame_rate = av_q2d(av_guess_frame_rate(anim->pFormatCtx, v_st, NULL)); | |||||
| double pts_time_base = av_q2d(v_st->time_base); | |||||
| int64_t st_time = anim->pFormatCtx->start_time; | |||||
| av_log(anim->pFormatCtx, | |||||
| AV_LOG_DEBUG, | |||||
| "FETCH: looking for PTS=%" PRId64 " (pts_timebase=%g, frame_rate=%g, st_time=%" PRId64 | |||||
| ")\n", | |||||
| (int64_t)pts_to_search, | |||||
| pts_time_base, | |||||
| frame_rate, | |||||
| st_time); | |||||
| if (ffmpeg_pts_matches_last_frame(anim, pts_to_search)) { | |||||
| av_log(anim->pFormatCtx, | |||||
| AV_LOG_DEBUG, | |||||
| "FETCH: frame repeat: last: %" PRId64 " next: %" PRId64 "\n", | |||||
| (int64_t)anim->last_pts, | |||||
| (int64_t)anim->next_pts); | |||||
| IMB_refImBuf(anim->last_frame); | |||||
| anim->curposition = position; | |||||
| return anim->last_frame; | |||||
| } | |||||
| if (ffmpeg_can_scan(anim, position, tc_index) || ffmpeg_is_first_frame_decode(anim, position)) { | |||||
| ffmpeg_decode_video_frame_scan(anim, pts_to_search); | |||||
| } | |||||
| else if (ffmpeg_can_seek(anim, position)) { | |||||
| ffmpeg_seek_and_decode(anim, position, tc_index); | |||||
| } | } | ||||
| else { | else { | ||||
| av_log(anim->pFormatCtx, AV_LOG_DEBUG, "FETCH: no seek necessary, just continue...\n"); | av_log(anim->pFormatCtx, AV_LOG_DEBUG, "FETCH: no seek necessary, just continue...\n"); | ||||
| } | } | ||||
| IMB_freeImBuf(anim->last_frame); | IMB_freeImBuf(anim->last_frame); | ||||
| /* Certain versions of FFmpeg have a bug in libswscale which ends up in crash | /* Certain versions of FFmpeg have a bug in libswscale which ends up in crash | ||||
| ▲ Show 20 Lines • Show All 309 Lines • Show Last 20 Lines | |||||