From b26f27d9e3fd44ec5775accdc3666a339684be4c Mon Sep 17 00:00:00 2001 From: Fox Caminiti Date: Mon, 1 Aug 2022 20:03:12 -0400 Subject: large changes to bitmap structure --- ffmpeg_backend.cpp | 366 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 366 insertions(+) create mode 100644 ffmpeg_backend.cpp (limited to 'ffmpeg_backend.cpp') diff --git a/ffmpeg_backend.cpp b/ffmpeg_backend.cpp new file mode 100644 index 0000000..e97b4da --- /dev/null +++ b/ffmpeg_backend.cpp @@ -0,0 +1,366 @@ +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_codec_info *AV, av_packet_info *AVLayer, int32 *err) +{ + *err = av_read_frame(AV->FileFormatContext, AVLayer->VideoPacket); + if (*err >= 0 && AVLayer->VideoPacket->stream_index != AV->VideoStream->index) { + av_packet_unref(AVLayer->VideoPacket); + return 0; + } + + if (*err < 0) + *err = avcodec_send_packet(AV->VideoCodecContext, AVLayer->VideoPacket); + else { + *err = avcodec_send_packet(AV->VideoCodecContext, AVLayer->VideoPacket); + } + av_packet_unref(AVLayer->VideoPacket); + + if (*err < 0) + { + fprintf(stderr, "Libav *error: (%s)\n", av_err2str(*err)); + Assert(0); + } + + while (*err >= 0) { + *err = avcodec_receive_frame(AV->VideoCodecContext, AVLayer->VideoFrame); + if (*err == AVERROR_EOF) { + } else if (*err == AVERROR(EAGAIN)) { + *err = 0; + break; + } else if (*err < 0) { + Assert(0); + } + return 1; + } + return 0; +} + +bool32 AV_IsFileSupported(char *filename) +{ + 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; + } + + avformat_free_context(temp); + + return 1; +} + +void AV_CodecInfo_Init(char *filename, source *Source, memory *Memory) +{ + Source->Info.AVCodecInfo = AllocateMemory(Memory, sizeof(av_codec_info), P_AVInfo); + av_codec_info *AV = (av_codec_info *)Source->Info.AVCodecInfo; + *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)); + + AV->FileFormatContext = avformat_alloc_context(); + err = avformat_open_input(&AV->FileFormatContext, filename, NULL, NULL);; + + if (err < 0) { + fprintf(stderr, "Libav error: (%s)\n", av_err2str(err)); + } + + err = avformat_find_stream_info(AV->FileFormatContext, NULL); + + if (err < 0) { + fprintf(stderr, "Libav error: (%s)\n", av_err2str(err)); + } + + for (int 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: Video codec could not be identified."); + } +/* + 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)); + } + + Source->Info.FPS = (real32)AV->VideoStream->r_frame_rate.num / AV->VideoStream->r_frame_rate.den; + Source->Info.Width = AV->VideoCodecContext->width; + Source->Info.Height = AV->VideoCodecContext->height; +}; + + + +void AV_GetPTSAverage(av_codec_info *AV, av_packet_info *AVLayer, int32 *err) +{ + // TODO(fox): This PTS average isn't exact and causes an occasional + // frame skip. See libav remarks in forum for more details. + + // 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, AVLayer, err)) { + if (i >= FPS * TestAmount) { + AvgPTSPerSecond = (real32)AVLayer->VideoFrame->pts / TestAmount; + printf("frame: %i, pts: %li\n", i, AVLayer->VideoFrame->pts); + break; + } + i++; + av_frame_unref(AVLayer->VideoFrame); + } + } + + AV->AvgPTSPerFrame = (real32)AvgPTSPerSecond / (int32)(FPS + 0.5f); + printf("Avg PTS per sec: %.06f, Avg PTS per frame: %.06f\n", AvgPTSPerSecond, AV->AvgPTSPerFrame); + + av_seek_frame(AV->FileFormatContext, -1, 0, AVSEEK_FLAG_BACKWARD); +} + + +void AV_PacketInfo_Init(layer_bitmap_info *BitmapInfo, memory *Memory) +{ + BitmapInfo->AVPacketInfo = AllocateMemory(Memory, sizeof(av_packet_info), P_AVInfo); + av_packet_info *AV = (av_packet_info *)BitmapInfo->AVPacketInfo; + *AV = {}; + printf("%li", AV->PreviousPTS); + + int32 err = 0; + + 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)); + } +} + + +static void +Convert4x4Chunk(pixel_buffer *Raster, uint8); +static void +ClearBuffer(pixel_buffer *Raster, void *); + +bool32 AV_LoadVideoFrame(source *Source, layer_bitmap_info *BitmapInfo, memory *Memory, int32 TimelineFrame) +{ + av_codec_info *AV = (av_codec_info *)Source->Info.AVCodecInfo; + av_packet_info *AVLayer = (av_packet_info *)BitmapInfo->AVPacketInfo; + + int32 *CurrentlyRenderedFrame = &BitmapInfo->CurrentFrame; + + int32 err = 0; + + if (!AV->AvgPTSPerFrame) { + AV_GetPTSAverage(AV, AVLayer, &err); + } + Assert(AV->AvgPTSPerFrame); + + int p = 0; + int i = 0; + + 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->Info.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)(AV->AvgPTSPerFrame*FrameToSeek + 0.5f); + + while (err >= 0) { + if (AV_TryFrame(AV, AVLayer, &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 && AVLayer->VideoFrame->pts != AV->VideoStream->start_time) { + av_frame_unref(AVLayer->VideoFrame); + printf("NON-START: avg: %li, real pts: %li", SeekPTS, AVLayer->VideoFrame->pts); + continue; + } + + int64 Difference = AVLayer->VideoFrame->pts - SeekPTS; + if (abs(Difference) < AV->AvgPTSPerFrame) + { + if (AVLayer->PreviousPTS == -1) { + AVLayer->PreviousPTS = AVLayer->VideoFrame->pts; + printf("avg: %li, real pts: %li, difference: %li\n", SeekPTS, AVLayer->VideoFrame->pts, Difference); + } else { + printf("avg: %li, real pts: %li, difference: %li difference from last pts: %li\n", SeekPTS, AVLayer->VideoFrame->pts, AVLayer->VideoFrame->pts - SeekPTS, AVLayer->VideoFrame->pts - AVLayer->PreviousPTS); + AVLayer->PreviousPTS = AVLayer->VideoFrame->pts; + } + + uint16 Width = Source->Info.Width; + uint16 Height = Source->Info.Height; + uint16 BytesPerPixel = Source->Info.BytesPerPixel; + int32 Pitch = Width*BytesPerPixel; + + void *Buffer = AllocateMemory(Memory, Bitmap_CalcTotalBytes(Width, Height, BytesPerPixel), B_LoadedBitmaps); + + 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. + AVLayer->RGBContext = sws_getContext(AVLayer->VideoFrame->width, AVLayer->VideoFrame->height, (AVPixelFormat)AVLayer->VideoFrame->format, + AVLayer->VideoFrame->width, AVLayer->VideoFrame->height, AV_PIX_FMT_RGBA, SWS_BILINEAR, + NULL, NULL, NULL); + + if(!AVLayer->RGBContext) { + printf("Libav error: SwsContext creation failed."); + } + + sws_scale(AVLayer->RGBContext, AVLayer->VideoFrame->data, AVLayer->VideoFrame->linesize, 0, AVLayer->VideoFrame->height, + dst_data, out_linesize); + + av_frame_unref(AVLayer->VideoFrame); + + if (!BitmapInfo->BitmapBuffer) { + BitmapInfo->BitmapBuffer = AllocateMemory(Memory, Bitmap_CalcTotalBytes(Width, Height, BytesPerPixel), B_LayerBitmaps); + } + void *DestBuffer = BitmapInfo->BitmapBuffer; + Bitmap_ConvertPacking(Buffer, DestBuffer, Width, Height, BytesPerPixel, 0); + // CopyToBuffer(Buffer, 1); + // Bitmap_Clear(Buffer, Source->Info.Width, Source->Info.Height, Source->Info.BytesPerPixel); + + return 1; + } + 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, AVLayer->VideoFrame->pts, Difference); + } + av_frame_unref(AVLayer->VideoFrame); + } + } + /* + for (int p = 0; p < 8000; p++) { + av_packet_unref(AV->VideoPacket); + int i = 0; + while (i < 5) + { + err = avcodec_send_packet(AV->VideoCodecContext, AV->VideoPacket); + if (err < 0) + { + fprintf(stderr, "Libav error: (%s)\n", av_err2str(err)); + Assert(0); + } + err = avcodec_receive_frame(AV->VideoCodecContext, AV->VideoFrame); + if (err >= 0) { + break; + } else if (err < 0) { + fprintf(stderr, "Libav error: (%s)\n", av_err2str(err)); + } + i++; + } + } + */ + + + /* + uint8 *Test = pFrame->data[0]; + + for (int16 Y = 0; Y < Buffer.Height; Y++) { + for (int16 X = 0; X < Buffer.Width; X++) { + uint8 *Row = (uint8 *)Buffer.OriginalBuffer + Buffer.Pitch*Y; + uint32 *Pixel = (uint32 *)Row + X; + *Pixel = (uint32)((0xFF << 24) | *Test); + Test++; + } + } + */ + + return 0; +} -- cgit v1.2.3