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; }