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