From fc8040d695644aaca4596adebeca4ea1369ef630 Mon Sep 17 00:00:00 2001 From: Fox Caminiti Date: Fri, 22 Jul 2022 20:45:08 -0400 Subject: first --- video.cpp | 336 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 336 insertions(+) create mode 100644 video.cpp (limited to 'video.cpp') diff --git a/video.cpp b/video.cpp new file mode 100644 index 0000000..d39719e --- /dev/null +++ b/video.cpp @@ -0,0 +1,336 @@ +// 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 + +internal bool32 +AV_TryFrame(av_info *AV, int32 *err) +{ + *err = av_read_frame(AV->FileFormatContext, AV->VideoPacket); + if (*err >= 0 && AV->VideoPacket->stream_index != AV->StreamIndex) { + 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; +} + +internal bool32 +TestAV(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; +} + +internal void +InitAV(char *filename, av_info *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]; + AV->StreamIndex = 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)); + } + + AV->FPS = (real32)AV->VideoStream->r_frame_rate.num / AV->VideoStream->r_frame_rate.den; + AV->IntFPS = (int32)(AV->FPS + 0.5f); + AV->LastPTS = -1; + + + // 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; + + int16 i = 0; + for (;;) { + if (AV_TryFrame(AV, &err)) { + if (i >= AV->FPS * TestAmount) { + AV->AvgPTSPerSecond = (real32)AV->VideoFrame->pts / TestAmount; + printf("frame: %i, pts: %li\n", i, AV->VideoFrame->pts); + break; + } + i++; + av_frame_unref(AV->VideoFrame); + } + } + + AV->AvgPTSPerFrame = (real32)AV->AvgPTSPerSecond / AV->IntFPS; + printf("Avg PTS per sec: %.06f, Avg PTS per frame: %.06f\n", AV->AvgPTSPerSecond, AV->AvgPTSPerFrame); + + av_seek_frame(AV->FileFormatContext, -1, 0, AVSEEK_FLAG_BACKWARD); +}; + +#if PACKEDRGB +internal void +Store4x4Chunk(pixel_buffer *Raster); +internal void +SSE_ClearBuffer(pixel_buffer *Raster, uint16); +#else +internal void +Libav_GBRAToRGBA(pixel_buffer *Raster); +#endif + +internal int16 +LoadVideoFrame(video_source *Source, memory *Memory, int32 TimelineFrame) +{ + av_info *AV = &Source->AV; + pixel_buffer *Buffer = &Source->Raster; + int32 *CurrentlyRenderedFrame = &Source->VideoCurrentFrame; + + int32 err = 0; + + int p = 0; + int i = 0; + + int32 FrameToSeek = TimelineFrame - Source->VideoFrameOffset; + 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 / AV->IntFPS * 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, &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) < AV->AvgPTSPerFrame) + { + if (AV->LastPTS == -1) { + AV->LastPTS = 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->LastPTS); + AV->LastPTS = AV->VideoFrame->pts; + } + + uint32 PixelCount = AV->VideoFrame->width*AV->VideoFrame->height; + int out_linesize[4] = { Buffer->Pitch, Buffer->Pitch, Buffer->Pitch, Buffer->Pitch }; + uint8 *dst_data[4] = { (uint8 *)Buffer->OriginalBuffer, (uint8 *)Buffer->OriginalBuffer + PixelCount, + (uint8 *)Buffer->OriginalBuffer + PixelCount*2, (uint8 *)Buffer->OriginalBuffer + PixelCount*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, +#if PACKEDRGB + AV->VideoFrame->width, AV->VideoFrame->height, AV_PIX_FMT_RGBA, SWS_BILINEAR, +#else + AV->VideoFrame->width, AV->VideoFrame->height, AV_PIX_FMT_GBRAP, SWS_BILINEAR, +#endif + 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); + +#if PACKEDRGB + Store4x4Chunk(Buffer); + SSE_CopyToBuffer(Buffer, 1); + SSE_ClearBuffer(Buffer, 1); +#else + Libav_GBRAToRGBA(Buffer); +#endif + + return 0; + } + 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; +} -- cgit v1.2.3