summaryrefslogtreecommitdiff
path: root/ffmpeg_backend.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ffmpeg_backend.cpp')
-rw-r--r--ffmpeg_backend.cpp366
1 files changed, 366 insertions, 0 deletions
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 <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libavformat/avio.h>
+#include <libavutil/avutil.h>
+#include <libswscale/swscale.h>
+}
+
+// workaround to make libav error printing work
+#ifdef av_err2str
+#undef av_err2str
+#include <string>
+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;
+}