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) { } else if (*err == AVERROR(EAGAIN)) { *err = 0; break; } else if (*err < 0) { Assert(0); } return 1; } return 0; } // TODO(fox): Could be combined into AV_Init once we have dealloc functions for // the AVInfo allocation. 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_Init(source *Source, layer_bitmap_info *BitmapInfo, memory *Memory) { BitmapInfo->AVInfo = AllocateMemory(Memory, sizeof(av_info), P_AVInfo); av_info *AV = (av_info *)BitmapInfo->AVInfo; *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(); err = avformat_open_input(&AV->FileFormatContext, Source->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 (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)); } 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->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(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. // 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->Info.AvgPTSPerFrame = (real32)AvgPTSPerSecond / (int32)(FPS + 0.5f); printf("Avg PTS per sec: %.06f, Avg PTS per frame: %.06f\n", AvgPTSPerSecond, Source->Info.AvgPTSPerFrame); av_seek_frame(AV->FileFormatContext, -1, 0, AVSEEK_FLAG_BACKWARD); } bool32 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->Info.AvgPTSPerFrame) { AV_GetPTSAverage(Source, AV, &err); } Assert(Source->Info.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)(Source->Info.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->Info.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->Info.Width; uint16 Height = Source->Info.Height; uint16 BytesPerPixel = Source->Info.BytesPerPixel; int32 Pitch = Width*BytesPerPixel; memory_table *Table = &Memory->Slot[B_LoadedBitmaps]; // First check whether we'd run over the buffer at the current // position, and reset it if so. uint64 Size = Bitmap_CalcTotalBytes(Width, Height, BytesPerPixel); if (Table->CurrentPosition + Size > Table->Size) { Table->CurrentPosition = 0; Table->PointerIndex = 0; } cached_bitmap *Bitmap = &Memory->Bitmap[Table->PointerIndex]; // Next, if there's a pointer in front of the current position, // check whether it's far away enough so that the size fits. bool32 BS = true; if (Bitmap->Data) { uint64 BytesBetween = (uint8 *)Bitmap->Data - ((uint8 *)Table->Address + Table->CurrentPosition); if (BytesBetween > Size) { int16 StopAt = Table->NumberOfPointers - 1; while (StopAt > Table->PointerIndex - 1) { Memory->Bitmap[StopAt + 1] = Memory->Bitmap[StopAt]; StopAt--; } Table->NumberOfPointers++; BS = false; } } // If it doesn't fit, then we need to dereference the pointers // until we have enough space. if ((Table->PointerIndex < Table->NumberOfPointers) && BS) { bool32 Avail = false; void *AddressStart = (void *)((uint8 *)Table->Address + Table->CurrentPosition); uint32 Amount = 0; while(!Avail) { // Bail out if we're on the last index, as we don't need to do anything else. if (Table->PointerIndex != Table->NumberOfPointers - 1) { void *Data2 = Memory->Bitmap[Table->PointerIndex+1].Data; uint64 BytesBetween = (uint8 *)Data2 - (uint8 *)AddressStart; if (BytesBetween < Size) { int16 StopAt = Table->PointerIndex; while (StopAt < Table->NumberOfPointers - 1) { Memory->Bitmap[StopAt] = Memory->Bitmap[StopAt + 1]; StopAt++; } Amount++; Table->NumberOfPointers--; if (Amount > 2) { Amount += 0; } } else { Avail = true; } } else { Avail = true; } } } // Increment the total number of pointers if the bitmap is // empty, i.e. we're at the first iteration of the buffer loop. if (!Bitmap->Data) { Table->NumberOfPointers++; } Bitmap->Data = AllocateMemory(Memory, Size, B_LoadedBitmaps); if (!Bitmap->Data) { Assert(0); } Bitmap->SourceOwner = Source; Bitmap->Frame = FrameToSeek; Table->PointerIndex++; void *Buffer = Bitmap->Data; // No two pointers on the table should hold the same data // address or be empty. Debug_Memory_Assert_Cohesion(Memory, Table); 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); void *DestBuffer = BitmapInfo->BitmapBuffer; Bitmap_ConvertPacking(Buffer, DestBuffer, Width, Height, BytesPerPixel, 0); // void *Buffer2 = AllocateMemory(Memory, Bitmap_CalcTotalBytes(Width, Height, BytesPerPixel), B_LoadedBitmaps); // uint64 test = &Buffer2 - &Buffer; // 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, AV->VideoFrame->pts, Difference); } av_frame_unref(AV->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; }