Changeset View
Changeset View
Standalone View
Standalone View
source/blender/imbuf/intern/anim_movie.c
| Show First 20 Lines • Show All 73 Lines • ▼ Show 20 Lines | |||||
| #include "IMB_indexer.h" | #include "IMB_indexer.h" | ||||
| #include "IMB_metadata.h" | #include "IMB_metadata.h" | ||||
| #ifdef WITH_FFMPEG | #ifdef WITH_FFMPEG | ||||
| # include "BKE_global.h" /* ENDIAN_ORDER */ | # include "BKE_global.h" /* ENDIAN_ORDER */ | ||||
| # include <libavcodec/avcodec.h> | # include <libavcodec/avcodec.h> | ||||
| # include <libavformat/avformat.h> | # include <libavformat/avformat.h> | ||||
| # include <libavutil/imgutils.h> | |||||
| # include <libavutil/rational.h> | # include <libavutil/rational.h> | ||||
| # include <libswscale/swscale.h> | # include <libswscale/swscale.h> | ||||
| # include "ffmpeg_compat.h" | # include "ffmpeg_compat.h" | ||||
| #endif /* WITH_FFMPEG */ | #endif /* WITH_FFMPEG */ | ||||
| int ismovie(const char *UNUSED(filepath)) | int ismovie(const char *UNUSED(filepath)) | ||||
| { | { | ||||
| ▲ Show 20 Lines • Show All 329 Lines • ▼ Show 20 Lines | # endif | ||||
| if (avierror != AVI_ERROR_NONE) { | if (avierror != AVI_ERROR_NONE) { | ||||
| AVI_print_error(avierror); | AVI_print_error(avierror); | ||||
| printf("Error loading avi: %s\n", anim->name); | printf("Error loading avi: %s\n", anim->name); | ||||
| free_anim_avi(anim); | free_anim_avi(anim); | ||||
| return -1; | return -1; | ||||
| } | } | ||||
| anim->duration_in_frames = anim->avi->header->TotalFrames; | anim->duration_in_frames = anim->avi->header->TotalFrames; | ||||
| anim->start_offset = 0.0f; | |||||
| anim->params = NULL; | anim->params = NULL; | ||||
| anim->x = anim->avi->header->Width; | anim->x = anim->avi->header->Width; | ||||
| anim->y = anim->avi->header->Height; | anim->y = anim->avi->header->Height; | ||||
| anim->interlacing = 0; | anim->interlacing = 0; | ||||
| anim->orientation = 0; | anim->orientation = 0; | ||||
| anim->framesize = anim->x * anim->y * 4; | anim->framesize = anim->x * anim->y * 4; | ||||
| anim->curposition = 0; | anim->cur_position = 0; | ||||
| anim->preseek = 0; | |||||
| # if 0 | # if 0 | ||||
| printf("x:%d y:%d size:%d interl:%d dur:%d\n", | printf("x:%d y:%d size:%d interl:%d dur:%d\n", | ||||
| anim->x, | anim->x, | ||||
| anim->y, | anim->y, | ||||
| anim->framesize, | anim->framesize, | ||||
| anim->interlacing, | anim->interlacing, | ||||
| anim->duration_in_frames); | anim->duration_in_frames); | ||||
| ▲ Show 20 Lines • Show All 69 Lines • ▼ Show 20 Lines | static int startffmpeg(struct anim *anim) | ||||
| AVFormatContext *pFormatCtx = NULL; | AVFormatContext *pFormatCtx = NULL; | ||||
| AVCodecContext *pCodecCtx; | AVCodecContext *pCodecCtx; | ||||
| AVRational frame_rate; | AVRational frame_rate; | ||||
| AVStream *video_stream; | AVStream *video_stream; | ||||
| int frs_num; | int frs_num; | ||||
| double frs_den; | double frs_den; | ||||
| int streamcount; | int streamcount; | ||||
| # ifdef FFMPEG_SWSCALE_COLOR_SPACE_SUPPORT | |||||
| /* The following for color space determination */ | /* The following for color space determination */ | ||||
| int srcRange, dstRange, brightness, contrast, saturation; | int srcRange, dstRange, brightness, contrast, saturation; | ||||
| int *table; | int *table; | ||||
| const int *inv_table; | const int *inv_table; | ||||
| # endif | |||||
| if (anim == NULL) { | if (anim == NULL) { | ||||
| return (-1); | return (-1); | ||||
| } | } | ||||
| streamcount = anim->streamindex; | streamcount = anim->streamindex; | ||||
| if (avformat_open_input(&pFormatCtx, anim->name, NULL, NULL) != 0) { | if (avformat_open_input(&pFormatCtx, anim->name, NULL, NULL) != 0) { | ||||
| return -1; | return -1; | ||||
| } | } | ||||
| if (avformat_find_stream_info(pFormatCtx, NULL) < 0) { | if (avformat_find_stream_info(pFormatCtx, NULL) < 0) { | ||||
| avformat_close_input(&pFormatCtx); | avformat_close_input(&pFormatCtx); | ||||
| return -1; | return -1; | ||||
| } | } | ||||
| av_dump_format(pFormatCtx, 0, anim->name, 0); | av_dump_format(pFormatCtx, 0, anim->name, 0); | ||||
| /* Find the video stream */ | /* Find the video stream */ | ||||
| video_stream_index = -1; | video_stream_index = -1; | ||||
| for (i = 0; i < pFormatCtx->nb_streams; i++) { | for (i = 0; i < pFormatCtx->nb_streams; i++) { | ||||
| if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { | if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { | ||||
| if (streamcount > 0) { | if (streamcount > 0) { | ||||
| streamcount--; | streamcount--; | ||||
| continue; | continue; | ||||
| } | } | ||||
| video_stream_index = i; | video_stream_index = i; | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| if (video_stream_index == -1) { | if (video_stream_index == -1) { | ||||
| avformat_close_input(&pFormatCtx); | avformat_close_input(&pFormatCtx); | ||||
| return -1; | return -1; | ||||
| } | } | ||||
| video_stream = pFormatCtx->streams[video_stream_index]; | video_stream = pFormatCtx->streams[video_stream_index]; | ||||
| pCodecCtx = video_stream->codec; | |||||
| /* Find the decoder for the video stream */ | /* Find the decoder for the video stream */ | ||||
| pCodec = avcodec_find_decoder(pCodecCtx->codec_id); | pCodec = avcodec_find_decoder(video_stream->codecpar->codec_id); | ||||
| if (pCodec == NULL) { | if (pCodec == NULL) { | ||||
| avformat_close_input(&pFormatCtx); | avformat_close_input(&pFormatCtx); | ||||
| return -1; | return -1; | ||||
| } | } | ||||
| pCodecCtx->workaround_bugs = 1; | pCodecCtx = avcodec_alloc_context3(NULL); | ||||
| avcodec_parameters_to_context(pCodecCtx, video_stream->codecpar); | |||||
| pCodecCtx->workaround_bugs = FF_BUG_AUTODETECT; | |||||
| if (pCodec->capabilities & AV_CODEC_CAP_AUTO_THREADS) { | if (pCodec->capabilities & AV_CODEC_CAP_AUTO_THREADS) { | ||||
| pCodecCtx->thread_count = 0; | pCodecCtx->thread_count = 0; | ||||
| } | } | ||||
| else { | else { | ||||
| pCodecCtx->thread_count = BLI_system_thread_count(); | pCodecCtx->thread_count = BLI_system_thread_count(); | ||||
| } | } | ||||
| if (pCodec->capabilities & AV_CODEC_CAP_FRAME_THREADS) { | if (pCodec->capabilities & AV_CODEC_CAP_FRAME_THREADS) { | ||||
| pCodecCtx->thread_type = FF_THREAD_FRAME; | pCodecCtx->thread_type = FF_THREAD_FRAME; | ||||
| } | } | ||||
| else if (pCodec->capabilities & AV_CODEC_CAP_SLICE_THREADS) { | else if (pCodec->capabilities & AV_CODEC_CAP_SLICE_THREADS) { | ||||
| pCodecCtx->thread_type = FF_THREAD_SLICE; | pCodecCtx->thread_type = FF_THREAD_SLICE; | ||||
| } | } | ||||
| if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { | if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { | ||||
| avformat_close_input(&pFormatCtx); | avformat_close_input(&pFormatCtx); | ||||
| return -1; | return -1; | ||||
| } | } | ||||
| if (pCodecCtx->pix_fmt == AV_PIX_FMT_NONE) { | if (pCodecCtx->pix_fmt == AV_PIX_FMT_NONE) { | ||||
| avcodec_close(anim->pCodecCtx); | avcodec_free_context(&anim->pCodecCtx); | ||||
| avformat_close_input(&pFormatCtx); | avformat_close_input(&pFormatCtx); | ||||
| return -1; | return -1; | ||||
| } | } | ||||
| double video_start = 0; | |||||
| double pts_time_base = av_q2d(video_stream->time_base); | |||||
| if (video_stream->start_time != AV_NOPTS_VALUE) { | |||||
| video_start = video_stream->start_time * pts_time_base; | |||||
| } | |||||
| frame_rate = av_guess_frame_rate(pFormatCtx, video_stream, NULL); | frame_rate = av_guess_frame_rate(pFormatCtx, video_stream, NULL); | ||||
| anim->duration_in_frames = 0; | anim->duration_in_frames = 0; | ||||
| /* Take from the stream if we can. */ | /* Take from the stream if we can. */ | ||||
| if (video_stream->nb_frames != 0) { | if (video_stream->nb_frames != 0) { | ||||
| anim->duration_in_frames = video_stream->nb_frames; | anim->duration_in_frames = video_stream->nb_frames; | ||||
| /* Sanity check on the detected duration. This is to work around corruption like reported in | /* Sanity check on the detected duration. This is to work around corruption like reported in | ||||
| * T68091. */ | * T68091. */ | ||||
| if (frame_rate.den != 0 && pFormatCtx->duration > 0) { | if (frame_rate.den != 0 && pFormatCtx->duration > 0) { | ||||
| double stream_sec = anim->duration_in_frames * av_q2d(frame_rate); | double stream_sec = anim->duration_in_frames * av_q2d(frame_rate); | ||||
| double container_sec = pFormatCtx->duration / (double)AV_TIME_BASE; | double container_sec = pFormatCtx->duration / (double)AV_TIME_BASE; | ||||
| if (stream_sec > 4.0 * container_sec) { | if (stream_sec > 4.0 * container_sec) { | ||||
| /* The stream is significantly longer than the container duration, which is | /* The stream is significantly longer than the container duration, which is | ||||
| * suspicious. */ | * suspicious. */ | ||||
| anim->duration_in_frames = 0; | anim->duration_in_frames = 0; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| /* Fall back to the container. */ | /* Fall back to manually estimating the video stream duration. | ||||
| * This is because the video stream duration can be shorter than the pFormatCtx->duration. | |||||
| */ | |||||
| if (anim->duration_in_frames == 0) { | if (anim->duration_in_frames == 0) { | ||||
| anim->duration_in_frames = (int)(pFormatCtx->duration * av_q2d(frame_rate) / AV_TIME_BASE + | double stream_dur; | ||||
| 0.5f); | |||||
| if (video_stream->duration != AV_NOPTS_VALUE) { | |||||
| stream_dur = video_stream->duration * pts_time_base; | |||||
| } | |||||
| else { | |||||
| double audio_start = 0; | |||||
| /* Find audio stream to guess the duration of the video. | |||||
| * Sometimes the audio AND the video stream have a start offset. | |||||
| * The difference between these is the offset we want to use to | |||||
| * calculate the video duration. | |||||
| */ | |||||
| for (i = 0; i < pFormatCtx->nb_streams; i++) { | |||||
| if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { | |||||
| AVStream *audio_stream = pFormatCtx->streams[i]; | |||||
| if (audio_stream->start_time != AV_NOPTS_VALUE) { | |||||
| audio_start = audio_stream->start_time * av_q2d(audio_stream->time_base); | |||||
| } | |||||
| break; | |||||
| } | |||||
| } | |||||
| if (video_start > audio_start) { | |||||
| stream_dur = (double)pFormatCtx->duration / AV_TIME_BASE - (video_start - audio_start); | |||||
| } | |||||
| else { | |||||
| /* The video stream starts before or at the same time as the audio stream! | |||||
| * We have to assume that the video stream is as long as the full pFormatCtx->duration. | |||||
| */ | |||||
| stream_dur = (double)pFormatCtx->duration / AV_TIME_BASE; | |||||
| } | |||||
| } | |||||
| anim->duration_in_frames = (int)(stream_dur * av_q2d(frame_rate) + 0.5f); | |||||
| } | |||||
| double ctx_start = 0; | |||||
| if (pFormatCtx->start_time != AV_NOPTS_VALUE) { | |||||
| ctx_start = (double)pFormatCtx->start_time / AV_TIME_BASE; | |||||
| } | } | ||||
| frs_num = frame_rate.num; | frs_num = frame_rate.num; | ||||
| frs_den = frame_rate.den; | frs_den = frame_rate.den; | ||||
| frs_den *= AV_TIME_BASE; | frs_den *= AV_TIME_BASE; | ||||
| while (frs_num % 10 == 0 && frs_den >= 2.0 && frs_num > 10) { | while (frs_num % 10 == 0 && frs_den >= 2.0 && frs_num > 10) { | ||||
| frs_num /= 10; | frs_num /= 10; | ||||
| frs_den /= 10; | frs_den /= 10; | ||||
| } | } | ||||
| anim->frs_sec = frs_num; | anim->frs_sec = frs_num; | ||||
| anim->frs_sec_base = frs_den; | anim->frs_sec_base = frs_den; | ||||
| /* Save the relative start time for the video. IE the start time in relation to where playback | |||||
| * starts. */ | |||||
| anim->start_offset = video_start - ctx_start; | |||||
| anim->params = 0; | anim->params = 0; | ||||
| anim->x = pCodecCtx->width; | anim->x = pCodecCtx->width; | ||||
| anim->y = av_get_cropped_height_from_codec(pCodecCtx); | anim->y = pCodecCtx->height; | ||||
| anim->pFormatCtx = pFormatCtx; | anim->pFormatCtx = pFormatCtx; | ||||
| anim->pCodecCtx = pCodecCtx; | anim->pCodecCtx = pCodecCtx; | ||||
| anim->pCodec = pCodec; | anim->pCodec = pCodec; | ||||
| anim->videoStream = video_stream_index; | anim->videoStream = video_stream_index; | ||||
| anim->interlacing = 0; | anim->interlacing = 0; | ||||
| anim->orientation = 0; | anim->orientation = 0; | ||||
| anim->framesize = anim->x * anim->y * 4; | anim->framesize = anim->x * anim->y * 4; | ||||
| anim->curposition = -1; | anim->cur_position = -1; | ||||
| anim->last_frame = 0; | anim->cur_frame_final = 0; | ||||
| anim->last_pts = -1; | anim->cur_pts = -1; | ||||
| anim->next_pts = -1; | anim->cur_key_frame_pts = -1; | ||||
| anim->next_packet.stream_index = -1; | anim->cur_packet = av_packet_alloc(); | ||||
| anim->cur_packet->stream_index = -1; | |||||
| anim->pFrame = av_frame_alloc(); | anim->pFrame = av_frame_alloc(); | ||||
| anim->pFrameComplete = false; | anim->pFrameComplete = false; | ||||
| anim->pFrameDeinterlaced = av_frame_alloc(); | anim->pFrameDeinterlaced = av_frame_alloc(); | ||||
| anim->pFrameRGB = av_frame_alloc(); | anim->pFrameRGB = av_frame_alloc(); | ||||
| if (need_aligned_ffmpeg_buffer(anim)) { | if (need_aligned_ffmpeg_buffer(anim)) { | ||||
| anim->pFrameRGB->format = AV_PIX_FMT_RGBA; | anim->pFrameRGB->format = AV_PIX_FMT_RGBA; | ||||
| anim->pFrameRGB->width = anim->x; | anim->pFrameRGB->width = anim->x; | ||||
| anim->pFrameRGB->height = anim->y; | anim->pFrameRGB->height = anim->y; | ||||
| if (av_frame_get_buffer(anim->pFrameRGB, 32) < 0) { | if (av_frame_get_buffer(anim->pFrameRGB, 32) < 0) { | ||||
| fprintf(stderr, "Could not allocate frame data.\n"); | fprintf(stderr, "Could not allocate frame data.\n"); | ||||
| avcodec_close(anim->pCodecCtx); | avcodec_free_context(&anim->pCodecCtx); | ||||
| avformat_close_input(&anim->pFormatCtx); | avformat_close_input(&anim->pFormatCtx); | ||||
| av_packet_free(&anim->cur_packet); | |||||
| av_frame_free(&anim->pFrameRGB); | av_frame_free(&anim->pFrameRGB); | ||||
| av_frame_free(&anim->pFrameDeinterlaced); | av_frame_free(&anim->pFrameDeinterlaced); | ||||
| av_frame_free(&anim->pFrame); | av_frame_free(&anim->pFrame); | ||||
| anim->pCodecCtx = NULL; | anim->pCodecCtx = NULL; | ||||
| return -1; | return -1; | ||||
| } | } | ||||
| } | } | ||||
| if (avpicture_get_size(AV_PIX_FMT_RGBA, anim->x, anim->y) != anim->x * anim->y * 4) { | if (av_image_get_buffer_size(AV_PIX_FMT_RGBA, anim->x, anim->y, 1) != anim->x * anim->y * 4) { | ||||
| fprintf(stderr, "ffmpeg has changed alloc scheme ... ARGHHH!\n"); | fprintf(stderr, "ffmpeg has changed alloc scheme ... ARGHHH!\n"); | ||||
| avcodec_close(anim->pCodecCtx); | avcodec_free_context(&anim->pCodecCtx); | ||||
| avformat_close_input(&anim->pFormatCtx); | avformat_close_input(&anim->pFormatCtx); | ||||
| av_packet_free(&anim->cur_packet); | |||||
| av_frame_free(&anim->pFrameRGB); | av_frame_free(&anim->pFrameRGB); | ||||
| av_frame_free(&anim->pFrameDeinterlaced); | av_frame_free(&anim->pFrameDeinterlaced); | ||||
| av_frame_free(&anim->pFrame); | av_frame_free(&anim->pFrame); | ||||
| anim->pCodecCtx = NULL; | anim->pCodecCtx = NULL; | ||||
| return -1; | return -1; | ||||
| } | } | ||||
| if (anim->ib_flags & IB_animdeinterlace) { | if (anim->ib_flags & IB_animdeinterlace) { | ||||
| avpicture_fill((AVPicture *)anim->pFrameDeinterlaced, | av_image_fill_arrays(anim->pFrameDeinterlaced->data, | ||||
| MEM_callocN(avpicture_get_size(anim->pCodecCtx->pix_fmt, | anim->pFrameDeinterlaced->linesize, | ||||
| MEM_callocN(av_image_get_buffer_size(anim->pCodecCtx->pix_fmt, | |||||
| anim->pCodecCtx->width, | anim->pCodecCtx->width, | ||||
| anim->pCodecCtx->height), | anim->pCodecCtx->height, | ||||
| 1), | |||||
| "ffmpeg deinterlace"), | "ffmpeg deinterlace"), | ||||
| anim->pCodecCtx->pix_fmt, | anim->pCodecCtx->pix_fmt, | ||||
| anim->pCodecCtx->width, | anim->pCodecCtx->width, | ||||
| anim->pCodecCtx->height); | anim->pCodecCtx->height, | ||||
| } | 1); | ||||
| if (pCodecCtx->has_b_frames) { | |||||
| anim->preseek = 25; /* FIXME: detect gopsize ... */ | |||||
| } | |||||
| else { | |||||
| anim->preseek = 0; | |||||
| } | } | ||||
| anim->img_convert_ctx = sws_getContext(anim->x, | anim->img_convert_ctx = sws_getContext(anim->x, | ||||
| anim->y, | anim->y, | ||||
| anim->pCodecCtx->pix_fmt, | anim->pCodecCtx->pix_fmt, | ||||
| anim->x, | anim->x, | ||||
| anim->y, | anim->y, | ||||
| AV_PIX_FMT_RGBA, | AV_PIX_FMT_RGBA, | ||||
| SWS_FAST_BILINEAR | SWS_PRINT_INFO | SWS_FULL_CHR_H_INT, | SWS_BILINEAR | SWS_PRINT_INFO | SWS_FULL_CHR_H_INT, | ||||
| NULL, | NULL, | ||||
| NULL, | NULL, | ||||
| NULL); | NULL); | ||||
| if (!anim->img_convert_ctx) { | if (!anim->img_convert_ctx) { | ||||
| fprintf(stderr, "Can't transform color space??? Bailing out...\n"); | fprintf(stderr, "Can't transform color space??? Bailing out...\n"); | ||||
| avcodec_close(anim->pCodecCtx); | avcodec_free_context(&anim->pCodecCtx); | ||||
| avformat_close_input(&anim->pFormatCtx); | avformat_close_input(&anim->pFormatCtx); | ||||
| av_packet_free(&anim->cur_packet); | |||||
| av_frame_free(&anim->pFrameRGB); | av_frame_free(&anim->pFrameRGB); | ||||
| av_frame_free(&anim->pFrameDeinterlaced); | av_frame_free(&anim->pFrameDeinterlaced); | ||||
| av_frame_free(&anim->pFrame); | av_frame_free(&anim->pFrame); | ||||
| anim->pCodecCtx = NULL; | anim->pCodecCtx = NULL; | ||||
| return -1; | return -1; | ||||
| } | } | ||||
| # ifdef FFMPEG_SWSCALE_COLOR_SPACE_SUPPORT | |||||
| /* Try do detect if input has 0-255 YCbCR range (JFIF Jpeg MotionJpeg) */ | /* Try do detect if input has 0-255 YCbCR range (JFIF Jpeg MotionJpeg) */ | ||||
| if (!sws_getColorspaceDetails(anim->img_convert_ctx, | if (!sws_getColorspaceDetails(anim->img_convert_ctx, | ||||
| (int **)&inv_table, | (int **)&inv_table, | ||||
| &srcRange, | &srcRange, | ||||
| &table, | &table, | ||||
| &dstRange, | &dstRange, | ||||
| &brightness, | &brightness, | ||||
| &contrast, | &contrast, | ||||
| Show All 10 Lines | if (sws_setColorspaceDetails(anim->img_convert_ctx, | ||||
| contrast, | contrast, | ||||
| saturation)) { | saturation)) { | ||||
| fprintf(stderr, "Warning: Could not set libswscale colorspace details.\n"); | fprintf(stderr, "Warning: Could not set libswscale colorspace details.\n"); | ||||
| } | } | ||||
| } | } | ||||
| else { | else { | ||||
| fprintf(stderr, "Warning: Could not set libswscale colorspace details.\n"); | fprintf(stderr, "Warning: Could not set libswscale colorspace details.\n"); | ||||
| } | } | ||||
| # endif | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| /* postprocess the image in anim->pFrame and do color conversion | /* postprocess the image in anim->pFrame and do color conversion | ||||
| * and deinterlacing stuff. | * and deinterlacing stuff. | ||||
| * | * | ||||
| * Output is anim->last_frame | * Output is anim->cur_frame_final | ||||
| */ | */ | ||||
| static void ffmpeg_postprocess(struct anim *anim) | static void ffmpeg_postprocess(struct anim *anim) | ||||
| { | { | ||||
| AVFrame *input = anim->pFrame; | AVFrame *input = anim->pFrame; | ||||
| ImBuf *ibuf = anim->last_frame; | ImBuf *ibuf = anim->cur_frame_final; | ||||
| int filter_y = 0; | int filter_y = 0; | ||||
| if (!anim->pFrameComplete) { | if (!anim->pFrameComplete) { | ||||
| return; | return; | ||||
| } | } | ||||
| /* This means the data wasn't read properly, | /* This means the data wasn't read properly, | ||||
| * this check stops crashing */ | * this check stops crashing */ | ||||
| if (input->data[0] == 0 && input->data[1] == 0 && input->data[2] == 0 && input->data[3] == 0) { | if (input->data[0] == 0 && input->data[1] == 0 && input->data[2] == 0 && input->data[3] == 0) { | ||||
| fprintf(stderr, | fprintf(stderr, | ||||
| "ffmpeg_fetchibuf: " | "ffmpeg_fetchibuf: " | ||||
| "data not read properly...\n"); | "data not read properly...\n"); | ||||
| return; | return; | ||||
| } | } | ||||
| av_log(anim->pFormatCtx, | av_log(anim->pFormatCtx, | ||||
| AV_LOG_DEBUG, | AV_LOG_DEBUG, | ||||
| " POSTPROC: anim->pFrame planes: %p %p %p %p\n", | " POSTPROC: anim->pFrame planes: %p %p %p %p\n", | ||||
| input->data[0], | input->data[0], | ||||
| input->data[1], | input->data[1], | ||||
| input->data[2], | input->data[2], | ||||
| input->data[3]); | input->data[3]); | ||||
| if (anim->ib_flags & IB_animdeinterlace) { | if (anim->ib_flags & IB_animdeinterlace) { | ||||
| if (avpicture_deinterlace((AVPicture *)anim->pFrameDeinterlaced, | if (av_image_deinterlace(anim->pFrameDeinterlaced, | ||||
| (const AVPicture *)anim->pFrame, | anim->pFrame, | ||||
| anim->pCodecCtx->pix_fmt, | anim->pCodecCtx->pix_fmt, | ||||
| anim->pCodecCtx->width, | anim->pCodecCtx->width, | ||||
| anim->pCodecCtx->height) < 0) { | anim->pCodecCtx->height) < 0) { | ||||
| filter_y = true; | filter_y = true; | ||||
| } | } | ||||
| else { | else { | ||||
| input = anim->pFrameDeinterlaced; | input = anim->pFrameDeinterlaced; | ||||
| } | } | ||||
| } | } | ||||
| if (!need_aligned_ffmpeg_buffer(anim)) { | if (!need_aligned_ffmpeg_buffer(anim)) { | ||||
| avpicture_fill((AVPicture *)anim->pFrameRGB, | av_image_fill_arrays(anim->pFrameRGB->data, | ||||
| anim->pFrameRGB->linesize, | |||||
| (unsigned char *)ibuf->rect, | (unsigned char *)ibuf->rect, | ||||
| AV_PIX_FMT_RGBA, | AV_PIX_FMT_RGBA, | ||||
| anim->x, | anim->x, | ||||
| anim->y); | anim->y, | ||||
| 1); | |||||
| } | } | ||||
| # if defined(__x86_64__) || defined(_M_X64) | # if defined(__x86_64__) || defined(_M_X64) | ||||
| /* Scale and flip image over Y axis in one go, using negative strides. | /* Scale and flip image over Y axis in one go, using negative strides. | ||||
| * This doesn't work with arm/powerpc though and may be misusing the API. | * This doesn't work with arm/powerpc though and may be misusing the API. | ||||
| * Limit it x86_64 where it appears to work. | * Limit it x86_64 where it appears to work. | ||||
| * http://trac.ffmpeg.org/ticket/9060 */ | * http://trac.ffmpeg.org/ticket/9060 */ | ||||
| int *dstStride = anim->pFrameRGB->linesize; | int *dstStride = anim->pFrameRGB->linesize; | ||||
| ▲ Show 20 Lines • Show All 66 Lines • ▼ Show 20 Lines | if (need_aligned_ffmpeg_buffer(anim)) { | ||||
| } | } | ||||
| } | } | ||||
| if (filter_y) { | if (filter_y) { | ||||
| IMB_filtery(ibuf); | IMB_filtery(ibuf); | ||||
| } | } | ||||
| } | } | ||||
| /* decode one video frame also considering the packet read into next_packet */ | /* decode one video frame also considering the packet read into cur_packet */ | ||||
| static int ffmpeg_decode_video_frame(struct anim *anim) | static int ffmpeg_decode_video_frame(struct anim *anim) | ||||
| { | { | ||||
| int rval = 0; | int rval = 0; | ||||
| av_log(anim->pFormatCtx, AV_LOG_DEBUG, " DECODE VIDEO FRAME\n"); | av_log(anim->pFormatCtx, AV_LOG_DEBUG, " DECODE VIDEO FRAME\n"); | ||||
| if (anim->next_packet.stream_index == anim->videoStream) { | if (anim->cur_packet->stream_index == anim->videoStream) { | ||||
| av_free_packet(&anim->next_packet); | av_packet_unref(anim->cur_packet); | ||||
| anim->next_packet.stream_index = -1; | anim->cur_packet->stream_index = -1; | ||||
| } | } | ||||
| while ((rval = av_read_frame(anim->pFormatCtx, &anim->next_packet)) >= 0) { | while ((rval = av_read_frame(anim->pFormatCtx, anim->cur_packet)) >= 0) { | ||||
| av_log(anim->pFormatCtx, | av_log(anim->pFormatCtx, | ||||
| AV_LOG_DEBUG, | AV_LOG_DEBUG, | ||||
| "%sREAD: strID=%d (VID: %d) dts=%" PRId64 " pts=%" PRId64 " %s\n", | "%sREAD: strID=%d (VID: %d) dts=%" PRId64 " pts=%" PRId64 " %s\n", | ||||
| (anim->next_packet.stream_index == anim->videoStream) ? "->" : " ", | (anim->cur_packet->stream_index == anim->videoStream) ? "->" : " ", | ||||
| anim->next_packet.stream_index, | anim->cur_packet->stream_index, | ||||
| anim->videoStream, | anim->videoStream, | ||||
| (anim->next_packet.dts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->next_packet.dts, | (anim->cur_packet->dts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->cur_packet->dts, | ||||
| (anim->next_packet.pts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->next_packet.pts, | (anim->cur_packet->pts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->cur_packet->pts, | ||||
| (anim->next_packet.flags & AV_PKT_FLAG_KEY) ? " KEY" : ""); | (anim->cur_packet->flags & AV_PKT_FLAG_KEY) ? " KEY" : ""); | ||||
| if (anim->next_packet.stream_index == anim->videoStream) { | if (anim->cur_packet->stream_index == anim->videoStream) { | ||||
| anim->pFrameComplete = 0; | anim->pFrameComplete = 0; | ||||
| avcodec_decode_video2( | avcodec_send_packet(anim->pCodecCtx, anim->cur_packet); | ||||
| anim->pCodecCtx, anim->pFrame, &anim->pFrameComplete, &anim->next_packet); | anim->pFrameComplete = avcodec_receive_frame(anim->pCodecCtx, anim->pFrame) == 0; | ||||
| if (anim->pFrameComplete) { | if (anim->pFrameComplete) { | ||||
| anim->next_pts = av_get_pts_from_frame(anim->pFormatCtx, anim->pFrame); | anim->cur_pts = av_get_pts_from_frame(anim->pFrame); | ||||
| if (anim->pFrame->key_frame) { | |||||
| anim->cur_key_frame_pts = anim->cur_pts; | |||||
| } | |||||
| av_log(anim->pFormatCtx, | av_log(anim->pFormatCtx, | ||||
| AV_LOG_DEBUG, | AV_LOG_DEBUG, | ||||
| " FRAME DONE: next_pts=%" PRId64 " pkt_pts=%" PRId64 ", guessed_pts=%" PRId64 "\n", | " FRAME DONE: cur_pts=%" PRId64 ", guessed_pts=%" PRId64 "\n", | ||||
| (anim->pFrame->pts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->pFrame->pts, | (anim->pFrame->pts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->pFrame->pts, | ||||
| (anim->pFrame->pkt_pts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->pFrame->pkt_pts, | (int64_t)anim->cur_pts); | ||||
| (int64_t)anim->next_pts); | |||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| av_free_packet(&anim->next_packet); | av_packet_unref(anim->cur_packet); | ||||
| anim->next_packet.stream_index = -1; | anim->cur_packet->stream_index = -1; | ||||
| } | } | ||||
| if (rval == AVERROR_EOF) { | if (rval == AVERROR_EOF) { | ||||
| /* this sets size and data fields to zero, | /* Flush any remaining frames out of the decoder. */ | ||||
| * which is necessary to decode the remaining data | |||||
| * in the decoder engine after EOF. It also prevents a memory | |||||
| * leak, since av_read_frame spills out a full size packet even | |||||
| * on EOF... (and: it's safe to call on NULL packets) */ | |||||
| av_free_packet(&anim->next_packet); | |||||
| anim->next_packet.size = 0; | |||||
| anim->next_packet.data = 0; | |||||
| anim->pFrameComplete = 0; | anim->pFrameComplete = 0; | ||||
| avcodec_decode_video2( | avcodec_send_packet(anim->pCodecCtx, NULL); | ||||
| anim->pCodecCtx, anim->pFrame, &anim->pFrameComplete, &anim->next_packet); | anim->pFrameComplete = avcodec_receive_frame(anim->pCodecCtx, anim->pFrame) == 0; | ||||
| if (anim->pFrameComplete) { | if (anim->pFrameComplete) { | ||||
| anim->next_pts = av_get_pts_from_frame(anim->pFormatCtx, anim->pFrame); | anim->cur_pts = av_get_pts_from_frame(anim->pFrame); | ||||
| if (anim->pFrame->key_frame) { | |||||
| anim->cur_key_frame_pts = anim->cur_pts; | |||||
| } | |||||
| av_log(anim->pFormatCtx, | av_log(anim->pFormatCtx, | ||||
| AV_LOG_DEBUG, | AV_LOG_DEBUG, | ||||
| " FRAME DONE (after EOF): next_pts=%" PRId64 " pkt_pts=%" PRId64 | " FRAME DONE (after EOF): cur_pts=%" PRId64 ", guessed_pts=%" PRId64 "\n", | ||||
| ", guessed_pts=%" PRId64 "\n", | |||||
| (anim->pFrame->pts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->pFrame->pts, | (anim->pFrame->pts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->pFrame->pts, | ||||
| (anim->pFrame->pkt_pts == AV_NOPTS_VALUE) ? -1 : (int64_t)anim->pFrame->pkt_pts, | (int64_t)anim->cur_pts); | ||||
| (int64_t)anim->next_pts); | |||||
| rval = 0; | rval = 0; | ||||
| } | } | ||||
| } | } | ||||
| if (rval < 0) { | if (rval < 0) { | ||||
| anim->next_packet.stream_index = -1; | av_packet_unref(anim->cur_packet); | ||||
| anim->cur_packet->stream_index = -1; | |||||
| av_log(anim->pFormatCtx, | av_log(anim->pFormatCtx, | ||||
| AV_LOG_ERROR, | AV_LOG_ERROR, | ||||
| " DECODE READ FAILED: av_read_frame() " | " DECODE READ FAILED: av_read_frame() " | ||||
| "returned error: %d\n", | "returned error: %s\n", | ||||
| rval); | av_err2str(rval)); | ||||
| } | } | ||||
| return (rval >= 0); | return (rval >= 0); | ||||
| } | } | ||||
| static int match_format(const char *name, AVFormatContext *pFormatCtx) | static int match_format(const char *name, AVFormatContext *pFormatCtx) | ||||
| { | { | ||||
| const char *p; | const char *p; | ||||
| Show All 31 Lines | while (*p) { | ||||
| if (match_format(*p++, pFormatCtx)) { | if (match_format(*p++, pFormatCtx)) { | ||||
| return true; | return true; | ||||
| } | } | ||||
| } | } | ||||
| return false; | return false; | ||||
| } | } | ||||
| static int64_t ffmpeg_get_seek_pts(struct anim *anim, int64_t pts_to_search) | |||||
| { | |||||
| AVStream *v_st = anim->pFormatCtx->streams[anim->videoStream]; | |||||
| AVRational frame_rate = v_st->r_frame_rate; | |||||
| AVRational time_base = v_st->time_base; | |||||
| double steps_per_frame = (double)(frame_rate.den * time_base.den) / | |||||
| (double)(frame_rate.num * time_base.num); | |||||
| /* Step back half a frame position to make sure that we get the requested | |||||
| * frame and not the one after it. This is a workaround as ffmpeg will | |||||
| * sometimes not seek to a frame after the requested pts even if | |||||
| * AVSEEK_FLAG_BACKWARD is specified. | |||||
| */ | |||||
| int64_t pts = pts_to_search - (steps_per_frame / 2); | |||||
| return pts; | |||||
| } | |||||
| /* This gives us an estimate of which pts our requested frame will have. | |||||
| * Note that this might be off a bit in certain video files, but it should still be close enough. | |||||
| */ | |||||
| static int64_t ffmpeg_get_pts_to_search(struct anim *anim, | static int64_t ffmpeg_get_pts_to_search(struct anim *anim, | ||||
| struct anim_index *tc_index, | struct anim_index *tc_index, | ||||
| int position) | int position) | ||||
| { | { | ||||
| int64_t pts_to_search; | int64_t pts_to_search; | ||||
| int64_t st_time = anim->pFormatCtx->start_time; | |||||
| 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); | |||||
| if (tc_index) { | if (tc_index) { | ||||
| int new_frame_index = IMB_indexer_get_frame_index(tc_index, position); | int new_frame_index = IMB_indexer_get_frame_index(tc_index, position); | ||||
| 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); | AVStream *v_st = anim->pFormatCtx->streams[anim->videoStream]; | ||||
| int64_t start_pts = v_st->start_time; | |||||
| AVRational frame_rate = v_st->r_frame_rate; | |||||
| AVRational time_base = v_st->time_base; | |||||
| double steps_per_frame = (double)(frame_rate.den * time_base.den) / | |||||
| (double)(frame_rate.num * time_base.num); | |||||
| if (st_time != AV_NOPTS_VALUE) { | pts_to_search = round(position * steps_per_frame); | ||||
| pts_to_search += st_time / pts_time_base / AV_TIME_BASE; | |||||
| if (start_pts != AV_NOPTS_VALUE) { | |||||
| pts_to_search += start_pts; | |||||
| } | } | ||||
| } | } | ||||
| return pts_to_search; | return pts_to_search; | ||||
| } | } | ||||
| /* Check if the pts will get us the same frame that we already have in memory from last decode. */ | |||||
| static bool ffmpeg_pts_matches_last_frame(struct anim *anim, int64_t pts_to_search) | static bool ffmpeg_pts_matches_last_frame(struct anim *anim, int64_t pts_to_search) | ||||
| { | { | ||||
| return anim->last_frame && anim->last_pts <= pts_to_search && anim->next_pts > pts_to_search; | if (anim->pFrame && anim->cur_frame_final) { | ||||
| int64_t diff = pts_to_search - anim->cur_pts; | |||||
| return diff >= 0 && diff < anim->pFrame->pkt_duration; | |||||
| } | } | ||||
| /* Requested video frame is expected to be found within different GOP as last decoded frame. | |||||
| * Seeking to new position and scanning is fastest way to get requested frame. | |||||
| * Check whether ffmpeg_can_scan() and ffmpeg_pts_matches_last_frame() is false before using this | |||||
| * function. */ | |||||
| static bool ffmpeg_can_seek(struct anim *anim, int position) | |||||
| { | |||||
| return position != anim->curposition + 1; | |||||
| } | |||||
| /* 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 && | |||||
| position - (anim->curposition + 1) < anim->preseek) { | |||||
| return true; | |||||
| } | |||||
| if (tc_index == NULL) { | |||||
| return false; | 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); | |||||
| } | |||||
| static bool ffmpeg_is_first_frame_decode(struct anim *anim, int position) | static bool ffmpeg_is_first_frame_decode(struct anim *anim, int position) | ||||
| { | { | ||||
| return position == 0 && anim->curposition == -1; | return position == 0 && anim->cur_position == -1; | ||||
| } | } | ||||
| /* Decode frames one by one until its PTS matches pts_to_search. */ | /* 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) | 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"); | av_log(anim->pFormatCtx, AV_LOG_DEBUG, "FETCH: within current GOP\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, | ||||
| "SCAN start: considering pts=%" PRId64 " in search of %" PRId64 "\n", | "SCAN start: considering pts=%" PRId64 " in search of %" PRId64 "\n", | ||||
| (int64_t)anim->next_pts, | (int64_t)anim->cur_pts, | ||||
| (int64_t)pts_to_search); | (int64_t)pts_to_search); | ||||
| while (count > 0 && anim->next_pts < pts_to_search) { | int64_t start_gop_frame = anim->cur_key_frame_pts; | ||||
| bool scan_fuzzy = false; | |||||
| while (anim->cur_pts < pts_to_search) { | |||||
| av_log(anim->pFormatCtx, | av_log(anim->pFormatCtx, | ||||
| AV_LOG_DEBUG, | AV_LOG_DEBUG, | ||||
| " WHILE: pts=%" PRId64 " in search of %" PRId64 "\n", | " WHILE: pts=%" PRId64 " in search of %" PRId64 "\n", | ||||
| (int64_t)anim->next_pts, | (int64_t)anim->cur_pts, | ||||
| (int64_t)pts_to_search); | (int64_t)pts_to_search); | ||||
| if (!ffmpeg_decode_video_frame(anim)) { | if (!ffmpeg_decode_video_frame(anim)) { | ||||
| break; | break; | ||||
| } | } | ||||
| count--; | |||||
| if (start_gop_frame != anim->cur_key_frame_pts) { | |||||
| break; | |||||
| } | |||||
| if (anim->cur_pts < pts_to_search && | |||||
| anim->cur_pts + anim->pFrame->pkt_duration > pts_to_search) { | |||||
| /* Our estimate of the pts was a bit off, but we have the frame we want. */ | |||||
| av_log(anim->pFormatCtx, AV_LOG_DEBUG, "SCAN fuzzy frame match\n"); | |||||
| scan_fuzzy = true; | |||||
| break; | |||||
| } | |||||
| } | } | ||||
| if (count == 0) { | |||||
| if (start_gop_frame != anim->cur_key_frame_pts) { | |||||
| /* We went into an other GOP frame. This should never happen as we should have positioned us | |||||
| * correctly by seeking into the GOP frame that contains the frame we want. */ | |||||
| av_log(anim->pFormatCtx, | av_log(anim->pFormatCtx, | ||||
| AV_LOG_ERROR, | AV_LOG_ERROR, | ||||
| "SCAN failed: completely lost in stream, " | "SCAN failed: completely lost in stream, " | ||||
| "bailing out at PTS=%" PRId64 ", searching for PTS=%" PRId64 "\n", | "bailing out at PTS=%" PRId64 ", searching for PTS=%" PRId64 "\n", | ||||
| (int64_t)anim->next_pts, | (int64_t)anim->cur_pts, | ||||
| (int64_t)pts_to_search); | (int64_t)pts_to_search); | ||||
| } | } | ||||
| if (anim->next_pts == pts_to_search) { | |||||
| if (scan_fuzzy || anim->cur_pts == pts_to_search) { | |||||
| av_log(anim->pFormatCtx, AV_LOG_DEBUG, "SCAN HAPPY: we found our PTS!\n"); | av_log(anim->pFormatCtx, AV_LOG_DEBUG, "SCAN HAPPY: we found our PTS!\n"); | ||||
| } | } | ||||
| else { | else { | ||||
| av_log(anim->pFormatCtx, AV_LOG_ERROR, "SCAN UNHAPPY: PTS not matched!\n"); | av_log(anim->pFormatCtx, AV_LOG_ERROR, "SCAN UNHAPPY: PTS not matched!\n"); | ||||
| } | } | ||||
| } | } | ||||
| /* Wrapper over av_seek_frame(), for formats that doesn't have it's own read_seek() or read_seek2() | /* Wrapper over av_seek_frame(), for formats that doesn't have its own read_seek() or | ||||
| * functions defined. When seeking in these formats, rule to seek to last necessary I-frame is not | * read_seek2() functions defined. When seeking in these formats, rule to seek to last | ||||
| * honored. It is not even guaranteed that I-frame, that must be decoded will be read. See | * necessary I-frame is not honored. It is not even guaranteed that I-frame, that must be | ||||
| * https://trac.ffmpeg.org/ticket/1607 and https://developer.blender.org/T86944. */ | * decoded will be read. See https://trac.ffmpeg.org/ticket/1607 and | ||||
| static int ffmpeg_generic_seek_workaround(struct anim *anim, int64_t requested_pos) | * https://developer.blender.org/T86944. */ | ||||
| static int ffmpeg_generic_seek_workaround(struct anim *anim, | |||||
| int64_t *requested_pts, | |||||
| int64_t pts_to_search) | |||||
| { | { | ||||
| AVStream *v_st = anim->pFormatCtx->streams[anim->videoStream]; | AVStream *v_st = anim->pFormatCtx->streams[anim->videoStream]; | ||||
| double frame_rate = av_q2d(av_guess_frame_rate(anim->pFormatCtx, v_st, NULL)); | AVRational frame_rate = v_st->r_frame_rate; | ||||
| int64_t current_pos = requested_pos; | AVRational time_base = v_st->time_base; | ||||
| double steps_per_frame = (double)(frame_rate.den * time_base.den) / | |||||
| (double)(frame_rate.num * time_base.num); | |||||
| int64_t current_pts = *requested_pts; | |||||
| int64_t offset = 0; | |||||
| /* This time offset maximum limit is arbitrary. If some files fails to decode it may be | int64_t cur_pts, prev_pts = -1; | ||||
| * increased. Seek performance will be negatively affected. Small initial offset is necessary | |||||
| * because encoder can re-arrange frames as it needs but within it's delay, which is usually | /* Step backward frame by frame until we find the key frame we are looking for. */ | ||||
| * small. */ | while (current_pts != 0) { | ||||
| for (int offset = 5; offset < 25; offset++) { | current_pts = *requested_pts - (int64_t)round(offset * steps_per_frame); | ||||
| current_pos = requested_pos - ((int64_t)(offset)*AV_TIME_BASE / frame_rate); | current_pts = MAX2(current_pts, 0); | ||||
| current_pos = max_ii(current_pos, 0); | |||||
| /* Seek to timestamp. */ | /* Seek to timestamp. */ | ||||
| if (av_seek_frame(anim->pFormatCtx, -1, current_pos, AVSEEK_FLAG_BACKWARD) < 0) { | if (av_seek_frame(anim->pFormatCtx, anim->videoStream, current_pts, AVSEEK_FLAG_BACKWARD) < | ||||
| 0) { | |||||
| break; | break; | ||||
| } | } | ||||
| /* Read first video stream packet. */ | /* Read first video stream packet. */ | ||||
| AVPacket read_packet = {0}; | AVPacket *read_packet = av_packet_alloc(); | ||||
| while (av_read_frame(anim->pFormatCtx, &read_packet) >= 0) { | while (av_read_frame(anim->pFormatCtx, read_packet) >= 0) { | ||||
| if (anim->next_packet.stream_index == anim->videoStream) { | if (read_packet->stream_index == anim->videoStream) { | ||||
| break; | break; | ||||
| } | } | ||||
| av_packet_unref(read_packet); | |||||
| } | } | ||||
| /* If this packet contains I-frame, exit loop. This should be the frame that we need. */ | /* If this packet contains an I-frame, this could be the frame that we need. */ | ||||
| if (read_packet.flags & AV_PKT_FLAG_KEY) { | bool is_key_frame = read_packet->flags & AV_PKT_FLAG_KEY; | ||||
| /* We need to check the packet timestamp as the key frame could be for a GOP forward in the the | |||||
| * video stream. So if it has a larger timestamp than the frame we want, ignore it. | |||||
| */ | |||||
| cur_pts = timestamp_from_pts_or_dts(read_packet->pts, read_packet->dts); | |||||
| av_packet_free(&read_packet); | |||||
| if (is_key_frame) { | |||||
| if (cur_pts <= pts_to_search) { | |||||
| /* We found the I-frame we were looking for! */ | |||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| /* Re-seek to timestamp that gave I-frame, so it can be read by decode function. */ | if (cur_pts == prev_pts) { | ||||
| return av_seek_frame(anim->pFormatCtx, -1, current_pos, AVSEEK_FLAG_BACKWARD); | /* We got the same key frame packet twice. | ||||
| * This probably means that we have hit the beginning of the stream. */ | |||||
| break; | |||||
| } | } | ||||
| /* Seek to last necessary I-frame and scan-decode until requested frame is found. */ | prev_pts = cur_pts; | ||||
| static void ffmpeg_seek_and_decode(struct anim *anim, int position, struct anim_index *tc_index) | offset++; | ||||
| { | } | ||||
| 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); | *requested_pts = current_pts; | ||||
| /* Re-seek to timestamp that gave I-frame, so it can be read by decode function. */ | |||||
| return av_seek_frame(anim->pFormatCtx, anim->videoStream, current_pts, AVSEEK_FLAG_BACKWARD); | |||||
| } | |||||
| /* Seek to last necessary key frame. */ | |||||
| static int ffmpeg_seek_to_key_frame(struct anim *anim, | |||||
| int position, | |||||
| struct anim_index *tc_index, | |||||
| int64_t pts_to_search) | |||||
| { | |||||
| int64_t pos; | int64_t pos; | ||||
| int ret; | int ret; | ||||
| if (tc_index) { | if (tc_index) { | ||||
| /* We can use timestamps generated from our indexer to seek. */ | |||||
| int new_frame_index = IMB_indexer_get_frame_index(tc_index, position); | int new_frame_index = IMB_indexer_get_frame_index(tc_index, position); | ||||
| int old_frame_index = IMB_indexer_get_frame_index(tc_index, anim->cur_position); | |||||
| if (IMB_indexer_can_scan(tc_index, old_frame_index, new_frame_index)) { | |||||
| /* No need to seek, return early. */ | |||||
| return 0; | |||||
| } | |||||
| uint64_t pts; | |||||
| 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); | ||||
| pts = IMB_indexer_get_seek_pos_pts(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); | ||||
| anim->cur_key_frame_pts = timestamp_from_pts_or_dts(pts, dts); | |||||
| 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 pts = %" PRIu64 "\n", pts); | |||||
| 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); | |||||
| } | } | ||||
| else { | else { | ||||
| av_log(anim->pFormatCtx, AV_LOG_DEBUG, "... using DTS pos\n"); | av_log(anim->pFormatCtx, AV_LOG_DEBUG, "... using PTS pos\n"); | ||||
| ret = av_seek_frame(anim->pFormatCtx, anim->videoStream, dts, AVSEEK_FLAG_BACKWARD); | ret = av_seek_frame( | ||||
| anim->pFormatCtx, anim->videoStream, anim->cur_key_frame_pts, AVSEEK_FLAG_BACKWARD); | |||||
| } | } | ||||
| } | } | ||||
| else { | else { | ||||
| pos = (int64_t)(position)*AV_TIME_BASE / frame_rate; | /* We have to manually seek with ffmpeg to get to the key frame we want to start decoding from. | ||||
| */ | |||||
| pos = ffmpeg_get_seek_pts(anim, pts_to_search); | |||||
| av_log(anim->pFormatCtx, AV_LOG_DEBUG, "NO INDEX final seek pos = %" PRId64 "\n", pos); | |||||
| av_log(anim->pFormatCtx, | AVFormatContext *format_ctx = anim->pFormatCtx; | ||||
| AV_LOG_DEBUG, | |||||
| "NO INDEX seek pos = %" PRId64 ", st_time = %" PRId64 "\n", | |||||
| pos, | |||||
| (st_time != AV_NOPTS_VALUE) ? st_time : 0); | |||||
| if (pos < 0) { | if (format_ctx->iformat->read_seek2 || format_ctx->iformat->read_seek) { | ||||
| pos = 0; | ret = av_seek_frame(anim->pFormatCtx, anim->videoStream, pos, AVSEEK_FLAG_BACKWARD); | ||||
| } | |||||
| else { | |||||
| ret = ffmpeg_generic_seek_workaround(anim, &pos, pts_to_search); | |||||
| av_log(anim->pFormatCtx, AV_LOG_DEBUG, "Adjusted final seek pos = %" PRId64 "\n", pos); | |||||
| } | } | ||||
| if (st_time != AV_NOPTS_VALUE) { | if (ret >= 0) { | ||||
| pos += st_time; | /* Double check if we need to seek and decode all packets. */ | ||||
| AVPacket *current_gop_start_packet = av_packet_alloc(); | |||||
| while (av_read_frame(anim->pFormatCtx, current_gop_start_packet) >= 0) { | |||||
| if (current_gop_start_packet->stream_index == anim->videoStream) { | |||||
| break; | |||||
| } | } | ||||
| av_packet_unref(current_gop_start_packet); | |||||
| } | |||||
| int64_t gop_pts = timestamp_from_pts_or_dts(current_gop_start_packet->pts, | |||||
| current_gop_start_packet->dts); | |||||
| av_log(anim->pFormatCtx, AV_LOG_DEBUG, "NO INDEX final seek pos = %" PRId64 "\n", pos); | av_packet_free(¤t_gop_start_packet); | ||||
| bool same_gop = gop_pts == anim->cur_key_frame_pts; | |||||
| AVFormatContext *format_ctx = anim->pFormatCtx; | if (same_gop && position > anim->cur_position) { | ||||
| /* Change back to our old frame position so we can simply continue decoding from there. */ | |||||
| int64_t cur_pts = timestamp_from_pts_or_dts(anim->cur_packet->pts, anim->cur_packet->dts); | |||||
| if (format_ctx->iformat->read_seek2 || format_ctx->iformat->read_seek) { | if (cur_pts == gop_pts) { | ||||
| ret = av_seek_frame(anim->pFormatCtx, -1, pos, AVSEEK_FLAG_BACKWARD); | /* We are already at the correct position. */ | ||||
| return 0; | |||||
| } | } | ||||
| else { | AVPacket *temp = av_packet_alloc(); | ||||
| ret = ffmpeg_generic_seek_workaround(anim, pos); | |||||
| while (av_read_frame(anim->pFormatCtx, temp) >= 0) { | |||||
| int64_t temp_pts = timestamp_from_pts_or_dts(temp->pts, temp->dts); | |||||
| if (temp->stream_index == anim->videoStream && temp_pts == cur_pts) { | |||||
| break; | |||||
| } | |||||
| av_packet_unref(temp); | |||||
| } | |||||
| av_packet_free(&temp); | |||||
| return 0; | |||||
| } | |||||
| anim->cur_key_frame_pts = gop_pts; | |||||
| /* Seek back so we are at the correct position after we decoded a frame. */ | |||||
| av_seek_frame(anim->pFormatCtx, anim->videoStream, 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, | pts_to_search, | ||||
| ret); | ret); | ||||
| } | } | ||||
| /* Flush the internal buffers of ffmpeg. This needs to be done after seeking to avoid decoding | |||||
| * errors. */ | |||||
| avcodec_flush_buffers(anim->pCodecCtx); | avcodec_flush_buffers(anim->pCodecCtx); | ||||
| anim->next_pts = -1; | anim->cur_pts = -1; | ||||
| if (anim->next_packet.stream_index == anim->videoStream) { | if (anim->cur_packet->stream_index == anim->videoStream) { | ||||
| av_free_packet(&anim->next_packet); | av_packet_unref(anim->cur_packet); | ||||
| anim->next_packet.stream_index = -1; | anim->cur_packet->stream_index = -1; | ||||
| } | } | ||||
| /* memset(anim->pFrame, ...) ?? */ | return ret; | ||||
| if (ret < 0) { | |||||
| /* Seek failed. */ | |||||
| return; | |||||
| } | |||||
| ffmpeg_decode_video_frame_scan(anim, pts_to_search); | |||||
| } | } | ||||
| static ImBuf *ffmpeg_fetchibuf(struct anim *anim, int position, IMB_Timecode_Type tc) | static ImBuf *ffmpeg_fetchibuf(struct anim *anim, int position, IMB_Timecode_Type tc) | ||||
| { | { | ||||
| if (anim == NULL) { | if (anim == NULL) { | ||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| av_log(anim->pFormatCtx, AV_LOG_DEBUG, "FETCH: pos=%d\n", position); | av_log(anim->pFormatCtx, AV_LOG_DEBUG, "FETCH: pos=%d\n", position); | ||||
| struct anim_index *tc_index = IMB_anim_open_index(anim, tc); | 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); | int64_t pts_to_search = ffmpeg_get_pts_to_search(anim, tc_index, position); | ||||
| AVStream *v_st = anim->pFormatCtx->streams[anim->videoStream]; | AVStream *v_st = anim->pFormatCtx->streams[anim->videoStream]; | ||||
| double frame_rate = av_q2d(av_guess_frame_rate(anim->pFormatCtx, v_st, NULL)); | double frame_rate = av_q2d(v_st->r_frame_rate); | ||||
| double pts_time_base = av_q2d(v_st->time_base); | double pts_time_base = av_q2d(v_st->time_base); | ||||
| int64_t st_time = anim->pFormatCtx->start_time; | int64_t start_pts = v_st->start_time; | ||||
| av_log(anim->pFormatCtx, | av_log(anim->pFormatCtx, | ||||
| AV_LOG_DEBUG, | AV_LOG_DEBUG, | ||||
| "FETCH: looking for PTS=%" PRId64 " (pts_timebase=%g, frame_rate=%g, st_time=%" PRId64 | "FETCH: looking for PTS=%" PRId64 " (pts_timebase=%g, frame_rate=%g, start_pts=%" PRId64 | ||||
| ")\n", | ")\n", | ||||
| (int64_t)pts_to_search, | (int64_t)pts_to_search, | ||||
| pts_time_base, | pts_time_base, | ||||
| frame_rate, | frame_rate, | ||||
| st_time); | start_pts); | ||||
| if (ffmpeg_pts_matches_last_frame(anim, pts_to_search)) { | if (ffmpeg_pts_matches_last_frame(anim, pts_to_search)) { | ||||
| av_log(anim->pFormatCtx, | av_log(anim->pFormatCtx, | ||||
| AV_LOG_DEBUG, | AV_LOG_DEBUG, | ||||
| "FETCH: frame repeat: last: %" PRId64 " next: %" PRId64 "\n", | "FETCH: frame repeat: pts: %" PRId64 "\n", | ||||
| (int64_t)anim->last_pts, | (int64_t)anim->cur_pts); | ||||
| (int64_t)anim->next_pts); | IMB_refImBuf(anim->cur_frame_final); | ||||
| IMB_refImBuf(anim->last_frame); | anim->cur_position = position; | ||||
| anim->curposition = position; | return anim->cur_frame_final; | ||||
| return anim->last_frame; | |||||
| } | } | ||||
| if (ffmpeg_can_scan(anim, position, tc_index) || ffmpeg_is_first_frame_decode(anim, position)) { | if (position == anim->cur_position + 1 || 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 { | |||||
| 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"); | ||||
| ffmpeg_decode_video_frame(anim); | |||||
| } | |||||
| else if (ffmpeg_seek_to_key_frame(anim, position, tc_index, pts_to_search) >= 0) { | |||||
| ffmpeg_decode_video_frame_scan(anim, pts_to_search); | |||||
| } | } | ||||
| IMB_freeImBuf(anim->last_frame); | IMB_freeImBuf(anim->cur_frame_final); | ||||
| /* 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 | ||||
| * when destination buffer is not properly aligned. For example, this happens | * when destination buffer is not properly aligned. For example, this happens | ||||
| * in FFmpeg 4.3.1. It got fixed later on, but for compatibility reasons is | * in FFmpeg 4.3.1. It got fixed later on, but for compatibility reasons is | ||||
| * still best to avoid crash. | * still best to avoid crash. | ||||
| * | * | ||||
| * This is achieved by using own allocation call rather than relying on | * This is achieved by using own allocation call rather than relying on | ||||
| * IMB_allocImBuf() to do so since the IMB_allocImBuf() is not guaranteed | * IMB_allocImBuf() to do so since the IMB_allocImBuf() is not guaranteed | ||||
| * to perform aligned allocation. | * to perform aligned allocation. | ||||
| * | * | ||||
| * In theory this could give better performance, since SIMD operations on | * In theory this could give better performance, since SIMD operations on | ||||
| * aligned data are usually faster. | * aligned data are usually faster. | ||||
| * | * | ||||
| * Note that even though sometimes vertical flip is required it does not | * Note that even though sometimes vertical flip is required it does not | ||||
| * affect on alignment of data passed to sws_scale because if the X dimension | * affect on alignment of data passed to sws_scale because if the X dimension | ||||
| * is not 32 byte aligned special intermediate buffer is allocated. | * is not 32 byte aligned special intermediate buffer is allocated. | ||||
| * | * | ||||
| * The issue was reported to FFmpeg under ticket #8747 in the FFmpeg tracker | * The issue was reported to FFmpeg under ticket #8747 in the FFmpeg tracker | ||||
| * and is fixed in the newer versions than 4.3.1. */ | * and is fixed in the newer versions than 4.3.1. */ | ||||
| anim->last_frame = IMB_allocImBuf(anim->x, anim->y, 32, 0); | anim->cur_frame_final = IMB_allocImBuf(anim->x, anim->y, 32, 0); | ||||
| anim->last_frame->rect = MEM_mallocN_aligned((size_t)4 * anim->x * anim->y, 32, "ffmpeg ibuf"); | anim->cur_frame_final->rect = MEM_mallocN_aligned( | ||||
| anim->last_frame->mall |= IB_rect; | (size_t)4 * anim->x * anim->y, 32, "ffmpeg ibuf"); | ||||
| anim->cur_frame_final->mall |= IB_rect; | |||||
| anim->last_frame->rect_colorspace = colormanage_colorspace_get_named(anim->colorspace); | anim->cur_frame_final->rect_colorspace = colormanage_colorspace_get_named(anim->colorspace); | ||||
| ffmpeg_postprocess(anim); | ffmpeg_postprocess(anim); | ||||
| anim->last_pts = anim->next_pts; | anim->cur_position = position; | ||||
| ffmpeg_decode_video_frame(anim); | |||||
| anim->curposition = position; | |||||
| IMB_refImBuf(anim->last_frame); | IMB_refImBuf(anim->cur_frame_final); | ||||
| return anim->last_frame; | return anim->cur_frame_final; | ||||
| } | } | ||||
| static void free_anim_ffmpeg(struct anim *anim) | static void free_anim_ffmpeg(struct anim *anim) | ||||
| { | { | ||||
| if (anim == NULL) { | if (anim == NULL) { | ||||
| return; | return; | ||||
| } | } | ||||
| if (anim->pCodecCtx) { | if (anim->pCodecCtx) { | ||||
| avcodec_close(anim->pCodecCtx); | avcodec_free_context(&anim->pCodecCtx); | ||||
| avformat_close_input(&anim->pFormatCtx); | avformat_close_input(&anim->pFormatCtx); | ||||
| av_packet_free(&anim->cur_packet); | |||||
| /* Special case here: pFrame could share pointers with codec, | av_frame_free(&anim->pFrame); | ||||
| * so in order to avoid double-free we don't use av_frame_free() | |||||
| * to free the frame. | |||||
| * | |||||
| * Could it be a bug in FFmpeg? | |||||
| */ | |||||
| av_free(anim->pFrame); | |||||
| if (!need_aligned_ffmpeg_buffer(anim)) { | if (!need_aligned_ffmpeg_buffer(anim)) { | ||||
| /* If there's no need for own aligned buffer it means that FFmpeg's | /* If there's no need for own aligned buffer it means that FFmpeg's | ||||
| * frame shares the same buffer as temporary ImBuf. In this case we | * frame shares the same buffer as temporary ImBuf. In this case we | ||||
| * should not free the buffer when freeing the FFmpeg buffer. | * should not free the buffer when freeing the FFmpeg buffer. | ||||
| */ | */ | ||||
| avpicture_fill((AVPicture *)anim->pFrameRGB, NULL, AV_PIX_FMT_RGBA, anim->x, anim->y); | av_image_fill_arrays(anim->pFrameRGB->data, | ||||
| anim->pFrameRGB->linesize, | |||||
| NULL, | |||||
| AV_PIX_FMT_RGBA, | |||||
| anim->x, | |||||
| anim->y, | |||||
| 1); | |||||
| } | } | ||||
| av_frame_free(&anim->pFrameRGB); | av_frame_free(&anim->pFrameRGB); | ||||
| av_frame_free(&anim->pFrameDeinterlaced); | av_frame_free(&anim->pFrameDeinterlaced); | ||||
| sws_freeContext(anim->img_convert_ctx); | sws_freeContext(anim->img_convert_ctx); | ||||
| IMB_freeImBuf(anim->last_frame); | IMB_freeImBuf(anim->cur_frame_final); | ||||
| if (anim->next_packet.stream_index != -1) { | |||||
| av_free_packet(&anim->next_packet); | |||||
| } | |||||
| } | } | ||||
| anim->duration_in_frames = 0; | anim->duration_in_frames = 0; | ||||
| } | } | ||||
| #endif | #endif | ||||
| /* Try next picture to read */ | /* Try next picture to read */ | ||||
| /* No picture, try to open next animation */ | /* No picture, try to open next animation */ | ||||
| ▲ Show 20 Lines • Show All 117 Lines • ▼ Show 20 Lines | struct ImBuf *IMB_anim_absolute(struct anim *anim, | ||||
| switch (anim->curtype) { | switch (anim->curtype) { | ||||
| case ANIM_SEQUENCE: | case ANIM_SEQUENCE: | ||||
| pic = an_stringdec(anim->first, head, tail, &digits); | pic = an_stringdec(anim->first, head, tail, &digits); | ||||
| pic += position; | pic += position; | ||||
| an_stringenc(anim->name, head, tail, digits, pic); | an_stringenc(anim->name, head, tail, digits, pic); | ||||
| ibuf = IMB_loadiffname(anim->name, IB_rect, anim->colorspace); | ibuf = IMB_loadiffname(anim->name, IB_rect, anim->colorspace); | ||||
| if (ibuf) { | if (ibuf) { | ||||
| anim->curposition = position; | anim->cur_position = position; | ||||
| } | } | ||||
| break; | break; | ||||
| case ANIM_MOVIE: | case ANIM_MOVIE: | ||||
| ibuf = movie_fetchibuf(anim, position); | ibuf = movie_fetchibuf(anim, position); | ||||
| if (ibuf) { | if (ibuf) { | ||||
| anim->curposition = position; | anim->cur_position = position; | ||||
| IMB_convert_rgba_to_abgr(ibuf); | IMB_convert_rgba_to_abgr(ibuf); | ||||
| } | } | ||||
| break; | break; | ||||
| #ifdef WITH_AVI | #ifdef WITH_AVI | ||||
| case ANIM_AVI: | case ANIM_AVI: | ||||
| ibuf = avi_fetchibuf(anim, position); | ibuf = avi_fetchibuf(anim, position); | ||||
| if (ibuf) { | if (ibuf) { | ||||
| anim->curposition = position; | anim->cur_position = position; | ||||
| } | } | ||||
| break; | break; | ||||
| #endif | #endif | ||||
| #ifdef WITH_FFMPEG | #ifdef WITH_FFMPEG | ||||
| case ANIM_FFMPEG: | case ANIM_FFMPEG: | ||||
| ibuf = ffmpeg_fetchibuf(anim, position, tc); | ibuf = ffmpeg_fetchibuf(anim, position, tc); | ||||
| if (ibuf) { | if (ibuf) { | ||||
| anim->curposition = position; | anim->cur_position = position; | ||||
| } | } | ||||
| filter_y = 0; /* done internally */ | filter_y = 0; /* done internally */ | ||||
| break; | break; | ||||
| #endif | #endif | ||||
| } | } | ||||
| if (ibuf) { | if (ibuf) { | ||||
| if (filter_y) { | if (filter_y) { | ||||
| IMB_filtery(ibuf); | IMB_filtery(ibuf); | ||||
| } | } | ||||
| BLI_snprintf(ibuf->name, sizeof(ibuf->name), "%s.%04d", anim->name, anim->curposition + 1); | BLI_snprintf(ibuf->name, sizeof(ibuf->name), "%s.%04d", anim->name, anim->cur_position + 1); | ||||
| } | } | ||||
| return ibuf; | return ibuf; | ||||
| } | } | ||||
| /***/ | /***/ | ||||
| int IMB_anim_get_duration(struct anim *anim, IMB_Timecode_Type tc) | int IMB_anim_get_duration(struct anim *anim, IMB_Timecode_Type tc) | ||||
| { | { | ||||
| struct anim_index *idx; | struct anim_index *idx; | ||||
| if (tc == IMB_TC_NONE) { | if (tc == IMB_TC_NONE) { | ||||
| return anim->duration_in_frames; | return anim->duration_in_frames; | ||||
| } | } | ||||
| idx = IMB_anim_open_index(anim, tc); | idx = IMB_anim_open_index(anim, tc); | ||||
| if (!idx) { | if (!idx) { | ||||
| return anim->duration_in_frames; | return anim->duration_in_frames; | ||||
| } | } | ||||
| return IMB_indexer_get_duration(idx); | return IMB_indexer_get_duration(idx); | ||||
| } | } | ||||
| double IMD_anim_get_offset(struct anim *anim) | |||||
| { | |||||
| return anim->start_offset; | |||||
| } | |||||
| bool IMB_anim_get_fps(struct anim *anim, short *frs_sec, float *frs_sec_base, bool no_av_base) | bool IMB_anim_get_fps(struct anim *anim, short *frs_sec, float *frs_sec_base, bool no_av_base) | ||||
| { | { | ||||
| double frs_sec_base_double; | double frs_sec_base_double; | ||||
| if (anim->frs_sec) { | if (anim->frs_sec) { | ||||
| if (anim->frs_sec > SHRT_MAX) { | if (anim->frs_sec > SHRT_MAX) { | ||||
| /* We cannot store original rational in our short/float format, | /* We cannot store original rational in our short/float format, | ||||
| * we need to approximate it as best as we can... */ | * we need to approximate it as best as we can... */ | ||||
| *frs_sec = SHRT_MAX; | *frs_sec = SHRT_MAX; | ||||
| Show All 17 Lines | #endif | ||||
| BLI_assert(*frs_sec > 0); | BLI_assert(*frs_sec > 0); | ||||
| BLI_assert(*frs_sec_base > 0.0f); | BLI_assert(*frs_sec_base > 0.0f); | ||||
| return true; | return true; | ||||
| } | } | ||||
| return false; | return false; | ||||
| } | } | ||||
| void IMB_anim_set_preseek(struct anim *anim, int preseek) | |||||
| { | |||||
| anim->preseek = preseek; | |||||
| } | |||||
| int IMB_anim_get_preseek(struct anim *anim) | |||||
| { | |||||
| return anim->preseek; | |||||
| } | |||||
| int IMB_anim_get_image_width(struct anim *anim) | int IMB_anim_get_image_width(struct anim *anim) | ||||
| { | { | ||||
| return anim->x; | return anim->x; | ||||
| } | } | ||||
| int IMB_anim_get_image_height(struct anim *anim) | int IMB_anim_get_image_height(struct anim *anim) | ||||
| { | { | ||||
| return anim->y; | return anim->y; | ||||
| } | } | ||||