extern "C" { #include #include #include #include #include } // workaround to make libav error printing work #ifdef av_err2str #undef av_err2str #include av_always_inline std::string av_err2string(int errnum) { char str[AV_ERROR_MAX_STRING_SIZE]; return av_make_error_string(str, AV_ERROR_MAX_STRING_SIZE, errnum); } #define av_err2str(err) av_err2string(err).c_str() #endif // av_err2str #include "ffmpeg_backend.h" bool32 AV_TryFrame(av_info *AV, int32 *err) { *err = av_read_frame(AV->FileFormatContext, AV->VideoPacket); if (*err >= 0 && AV->VideoPacket->stream_index != AV->VideoStream->index) { av_packet_unref(AV->VideoPacket); return 0; } if (*err < 0) *err = avcodec_send_packet(AV->VideoCodecContext, AV->VideoPacket); else { *err = avcodec_send_packet(AV->VideoCodecContext, AV->VideoPacket); } av_packet_unref(AV->VideoPacket); if (*err < 0) { fprintf(stderr, "Libav *error: (%s)\n", av_err2str(*err)); Assert(0); } while (*err >= 0) { *err = avcodec_receive_frame(AV->VideoCodecContext, AV->VideoFrame); if (*err == AVERROR_EOF) { av_seek_frame(AV->FileFormatContext, -1, 0, AVSEEK_FLAG_BACKWARD); *err = 0; break; } else if (*err == AVERROR(EAGAIN)) { *err = 0; break; } else if (*err < 0) { Assert(0); } return 1; } return 0; } bool32 AV_IsFileSupported(char *filename, bool32 *IsVideo) { int32 err = 0; // enum AVHWDeviceType type; // while((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE) // printf("%s\n", av_hwdevice_get_type_name(type)); AVFormatContext *temp = avformat_alloc_context(); err = avformat_open_input(&temp, filename, NULL, NULL);; if (err < 0) { fprintf(stderr, "Libav error: (%s)\n", av_err2str(err)); avformat_free_context(temp); return 0; } err = avformat_find_stream_info(temp, NULL); if (err < 0) { fprintf(stderr, "Libav error: (%s)\n", av_err2str(err)); avformat_free_context(temp); return 0; } for (uint32 i = 0; i < temp->nb_streams; i++) { AVCodecParameters *LocalCodecParameters = NULL; LocalCodecParameters = temp->streams[i]->codecpar; if (LocalCodecParameters->codec_type == AVMEDIA_TYPE_VIDEO) { if (temp->streams[i]->duration > 1) { *IsVideo = true; } avformat_free_context(temp); return 1; } } printf("Libav error: No video track found."); return 0; } // Note that we can't get away with not having to keep track of the AV pointer // for undos by clearing/reallocing these contexts every frame since the state // of the PTS value is stored inside FileFormatContext. void AV_Dealloc(av_info *AV) { Assert(AV); avformat_free_context(AV->FileFormatContext); avcodec_free_context(&AV->VideoCodecContext); av_packet_free(&AV->VideoPacket); av_frame_free(&AV->VideoFrame); }; void AV_Init(block_source *Source, av_info *AV, memory *Memory) { *AV = {}; int32 err = 0; // enum AVHWDeviceType type; // while((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE) // printf("%s\n", av_hwdevice_get_type_name(type)); // The two calls below theoretically shouldn't fail since we already tested them in IsFileSupported. AV->FileFormatContext = avformat_alloc_context(); char *Path = (char *)Memory_Block_AddressAtIndex(Memory, F_Strings, Source->Path_String_Index); err = avformat_open_input(&AV->FileFormatContext, Path, NULL, NULL);; if (err < 0) { fprintf(stderr, "Libav error: (%s)\n", av_err2str(err)); Assert(0); } err = avformat_find_stream_info(AV->FileFormatContext, NULL); if (err < 0) { fprintf(stderr, "Libav error: (%s)\n", av_err2str(err)); Assert(0); } for (uint32 i = 0; i < AV->FileFormatContext->nb_streams; i++) { AVCodecParameters *LocalCodecParameters = NULL; LocalCodecParameters = AV->FileFormatContext->streams[i]->codecpar; if (LocalCodecParameters->codec_type == AVMEDIA_TYPE_VIDEO) { AV->VideoCodecParameters = LocalCodecParameters; AV->VideoStream = AV->FileFormatContext->streams[i]; break; } } if (!AV->VideoCodecParameters) { printf("Libav error: No video track found."); } AV->VideoCodec = avcodec_find_decoder(AV->VideoCodecParameters->codec_id); if (!AV->VideoCodec) { printf("Libav error: Suitable decoder could not be identified for codec:\n"); } /* int16 codecs = 0; for (;;) { AV->VideoHWConfig = avcodec_get_hw_config(AV->VideoCodec, codecs); if (!AV->VideoHWConfig) { printf("Libav error: Hardware acceleration not found for decoder %s.", AV->VideoCodec->name); break; } AV->HWPixFormat = AV->VideoHWConfig->pix_fmt; break; // if (AV->VideoHWConfig->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX && // AV->VideoHWConfig->device_type == type) { // } codecs++; } */ AV->VideoCodecContext = avcodec_alloc_context3(AV->VideoCodec); if (!AV->VideoCodecContext) { printf("Libav error: Decoder context allocation failed."); } err = avcodec_parameters_to_context(AV->VideoCodecContext, AV->VideoCodecParameters); if (err < 0) { fprintf(stderr, "Libav error: (%s)\n", av_err2str(err)); } avcodec_open2(AV->VideoCodecContext, AV->VideoCodec, NULL); if (err < 0) { fprintf(stderr, "Libav error: (%s)\n", av_err2str(err)); } AV->VideoPacket = av_packet_alloc(); if (err < 0) { fprintf(stderr, "Libav error: (%s)\n", av_err2str(err)); } AV->VideoFrame = av_frame_alloc(); if (err < 0) { fprintf(stderr, "Libav error: (%s)\n", av_err2str(err)); } Source->BytesPerPixel = 4; Source->FPS = (real32)AV->VideoStream->r_frame_rate.num / AV->VideoStream->r_frame_rate.den; Source->Width = AV->VideoCodecContext->width; Source->Height = AV->VideoCodecContext->height; }; void AV_GetPTSAverage(block_source *Source, av_info *AV, int32 *err) { // TODO(fox): This PTS average isn't exact and causes an occasional // frame skip. See libav remarks in forum for more details. if (AV->VideoStream->duration == 1) { Source->AvgPTSPerFrame = 1; return; } // TODO(fox): Handle footage under five seconds. int16 TestAmount = 5; real32 FPS = (real32)AV->VideoStream->r_frame_rate.num / AV->VideoStream->r_frame_rate.den; int16 i = 0; real32 AvgPTSPerSecond = 0; for (;;) { if (AV_TryFrame(AV, err)) { if (i >= FPS * TestAmount) { AvgPTSPerSecond = (real32)AV->VideoFrame->pts / TestAmount; printf("frame: %i, pts: %li\n", i, AV->VideoFrame->pts); break; } i++; av_frame_unref(AV->VideoFrame); } } Source->AvgPTSPerFrame = (real32)AvgPTSPerSecond / (int32)(FPS + 0.5f); printf("Avg PTS per sec: %.06f, Avg PTS per frame: %.06f\n", AvgPTSPerSecond, Source->AvgPTSPerFrame); av_seek_frame(AV->FileFormatContext, -1, 0, AVSEEK_FLAG_BACKWARD); } #if 0 cached_bitmap * AV_LoadStill(block_source *Source, layer_bitmap_info *BitmapInfo, memory *Memory) { av_info *AV = (av_info *)BitmapInfo->AVInfo; int32 *CurrentlyRenderedFrame = &BitmapInfo->CurrentFrame; int32 err = 0; *CurrentlyRenderedFrame = 0; av_seek_frame(AV->FileFormatContext, -1, 0, AVSEEK_FLAG_FRAME); while (err >= 0) { if (AV_TryFrame(AV, &err)) { uint16 Width = Source->Width; uint16 Height = Source->Height; uint16 BytesPerPixel = Source->BytesPerPixel; int32 Pitch = Width*BytesPerPixel; cached_bitmap *Bitmap = Memory_RollingBitmap(Memory, Source, 0); void *Buffer = Bitmap->Data; int out_linesize[4] = { Pitch, Pitch, Pitch, Pitch }; uint8 *dst_data[4] = { (uint8 *)Buffer, (uint8 *)Buffer + Width*Height*BytesPerPixel, (uint8 *)Buffer + Width*Height*BytesPerPixel*2, (uint8 *)Buffer + Width*Height*BytesPerPixel*3 }; // NOTE(fox): This function will be replaced in the future. AV->RGBContext = sws_getContext(AV->VideoFrame->width, AV->VideoFrame->height, (AVPixelFormat)AV->VideoFrame->format, AV->VideoFrame->width, AV->VideoFrame->height, AV_PIX_FMT_RGBA, SWS_BILINEAR, NULL, NULL, NULL); if(!AV->RGBContext) { printf("Libav error: SwsContext creation failed."); } sws_scale(AV->RGBContext, AV->VideoFrame->data, AV->VideoFrame->linesize, 0, AV->VideoFrame->height, dst_data, out_linesize); av_frame_unref(AV->VideoFrame); Assert(BitmapInfo->BitmapBuffer); return Bitmap; } av_frame_unref(AV->VideoFrame); } return 0; } cached_bitmap * AV_LoadVideoFrame(source *Source, layer_bitmap_info *BitmapInfo, memory *Memory, int32 TimelineFrame) { av_info *AV = (av_info *)BitmapInfo->AVInfo; int32 *CurrentlyRenderedFrame = &BitmapInfo->CurrentFrame; int32 err = 0; if (!Source->AvgPTSPerFrame) { AV_GetPTSAverage(Source, AV, &err); } Assert(Source->AvgPTSPerFrame); int32 FrameToSeek = TimelineFrame - BitmapInfo->FrameOffset; if (*CurrentlyRenderedFrame == FrameToSeek || FrameToSeek < 0) return 0; // NOTE(fox): The decoder automatically advances to the next frame, so we // don't need to call av_seek_frame under normal playback. // This function only seeks to the nearest "keyframe." if (*CurrentlyRenderedFrame != FrameToSeek - 1) { int64 SeekSeconds = (int64)(FrameToSeek / (int32)(Source->FPS + 0.5f) * AV_TIME_BASE); av_seek_frame(AV->FileFormatContext, -1, SeekSeconds, AVSEEK_FLAG_BACKWARD); printf("Seek activated\n"); } else if (*CurrentlyRenderedFrame < 0) { av_seek_frame(AV->FileFormatContext, -1, 0, AVSEEK_FLAG_BACKWARD); } *CurrentlyRenderedFrame = FrameToSeek; int64 SeekPTS = (int64)(Source->AvgPTSPerFrame*FrameToSeek + 0.5f); while (err >= 0) { if (AV_TryFrame(AV, &err)) { // The first frame that gets loaded isn't always the actual // first frame, so we need to check until it's correct. if (FrameToSeek == 0 && AV->VideoFrame->pts != AV->VideoStream->start_time) { av_frame_unref(AV->VideoFrame); // printf("NON-START: avg: %li, real pts: %li", SeekPTS, AV->VideoFrame->pts); continue; } int64 Difference = AV->VideoFrame->pts - SeekPTS; if (abs(Difference) < Source->AvgPTSPerFrame) { if (AV->PreviousPTS == -1) { AV->PreviousPTS = AV->VideoFrame->pts; // printf("avg: %li, real pts: %li, difference: %li\n", SeekPTS, AV->VideoFrame->pts, Difference); } else { // printf("avg: %li, real pts: %li, difference: %li difference from last pts: %li\n", SeekPTS, AV->VideoFrame->pts, AV->VideoFrame->pts - SeekPTS, AV->VideoFrame->pts - AV->PreviousPTS); AV->PreviousPTS = AV->VideoFrame->pts; } uint16 Width = Source->Width; uint16 Height = Source->Height; uint16 BytesPerPixel = Source->BytesPerPixel; int32 Pitch = Width*BytesPerPixel; cached_bitmap *Bitmap = Memory_RollingBitmap(Memory, Source, FrameToSeek); void *Buffer = Bitmap->Data; int out_linesize[4] = { Pitch, Pitch, Pitch, Pitch }; uint8 *dst_data[4] = { (uint8 *)Buffer, (uint8 *)Buffer + Width*Height*BytesPerPixel, (uint8 *)Buffer + Width*Height*BytesPerPixel*2, (uint8 *)Buffer + Width*Height*BytesPerPixel*3 }; // NOTE(fox): This function will be replaced in the future. AV->RGBContext = sws_getContext(AV->VideoFrame->width, AV->VideoFrame->height, (AVPixelFormat)AV->VideoFrame->format, AV->VideoFrame->width, AV->VideoFrame->height, AV_PIX_FMT_RGBA, SWS_BILINEAR, NULL, NULL, NULL); if(!AV->RGBContext) { printf("Libav error: SwsContext creation failed."); } sws_scale(AV->RGBContext, AV->VideoFrame->data, AV->VideoFrame->linesize, 0, AV->VideoFrame->height, dst_data, out_linesize); av_frame_unref(AV->VideoFrame); Assert(BitmapInfo->BitmapBuffer); return Bitmap; } else { // If this gets printed when not seeking, a frame has been skipped! // printf("FRAME SKIP: avg: %li, real pts: %li, difference: %li\n", SeekPTS, AV->VideoFrame->pts, Difference); } av_frame_unref(AV->VideoFrame); } } return 0; } #endif