mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' into medusa
This commit is contained in:
commit
3eb6a92265
|
@ -553,8 +553,8 @@ if(USE_FFMPEG)
|
||||||
endif()
|
endif()
|
||||||
include_directories(AFTER ${FFMPEG_INCLUDE_DIRS} ${LIBAVCODEC_INCLUDE_DIRS} ${LIBAVFILTER_INCLUDE_DIRS} ${LIBAVFORMAT_INCLUDE_DIRS} ${LIBAVRESAMPLE_INCLUDE_DIRS} ${LIBAVUTIL_INCLUDE_DIRS} ${LIBSWRESAMPLE_INCLUDE_DIRS} ${LIBSWSCALE_INCLUDE_DIRS})
|
include_directories(AFTER ${FFMPEG_INCLUDE_DIRS} ${LIBAVCODEC_INCLUDE_DIRS} ${LIBAVFILTER_INCLUDE_DIRS} ${LIBAVFORMAT_INCLUDE_DIRS} ${LIBAVRESAMPLE_INCLUDE_DIRS} ${LIBAVUTIL_INCLUDE_DIRS} ${LIBSWRESAMPLE_INCLUDE_DIRS} ${LIBSWSCALE_INCLUDE_DIRS})
|
||||||
link_directories(${FFMPEG_LIBRARY_DIRS} ${LIBAVCODEC_LIBRARY_DIRS} ${LIBAVFILTER_LIBRARY_DIRS} ${LIBAVFORMAT_LIBRARY_DIRS} ${LIBAVRESAMPLE_LIBRARY_DIRS} ${LIBAVUTIL_LIBRARY_DIRS} ${LIBSWRESAMPLE_LIBRARY_DIRS} ${LIBSWSCALE_LIBRARY_DIRS})
|
link_directories(${FFMPEG_LIBRARY_DIRS} ${LIBAVCODEC_LIBRARY_DIRS} ${LIBAVFILTER_LIBRARY_DIRS} ${LIBAVFORMAT_LIBRARY_DIRS} ${LIBAVRESAMPLE_LIBRARY_DIRS} ${LIBAVUTIL_LIBRARY_DIRS} ${LIBSWRESAMPLE_LIBRARY_DIRS} ${LIBSWSCALE_LIBRARY_DIRS})
|
||||||
list(APPEND FEATURE_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/feature/ffmpeg/ffmpeg-encoder.c")
|
list(APPEND FEATURE_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/feature/ffmpeg/ffmpeg-encoder.c" "${CMAKE_CURRENT_SOURCE_DIR}/src/feature/ffmpeg/ffmpeg-decoder.c")
|
||||||
list(APPEND DEPENDENCY_LIB ${FFMPEG_LIBRARIES} ${LIBAVCODEC_LIBRARIES} ${LIBAVFILTER_LIBRARIES} ${LIBAVFORMAT_LIBRARIES} ${LIBAVRESAMPLE_LIBRARIES} ${LIBAVUTIL_LIBRARIES} ${LIBSWSCALE_LIBRARIES} ${LIBSWRESAMPLE_LIBRARIES})
|
list(APPEND DEPENDENCY_LIB ${FFMPEG_LIBRARIES} ${LIBAVCODEC_LIBRARIES} ${LIBAVFILTER_LIBRARIES} ${LIBAVFORMAT_LIBRARIES} ${LIBAVRESAMPLE_LIBRARIES} ${LIBAVUTIL_LIBRARIES} ${LIBSWSCALE_LIBRARIES} ${LIBSWRESAMPLE_LIBRARIES})
|
||||||
if(WIN32 AND NOT DEFINED VCPKG_TARGET_TRIPLET)
|
if(WIN32 AND NOT DEFINED VCPKG_TARGET_TRIPLET)
|
||||||
list(APPEND DEPENDENCY_LIB bcrypt)
|
list(APPEND DEPENDENCY_LIB bcrypt)
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
/* Copyright (c) 2013-2020 Jeffrey Pfau
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
#ifndef FFMPEG_COMMON
|
||||||
|
#define FFMPEG_COMMON
|
||||||
|
|
||||||
|
#include <mgba-util/common.h>
|
||||||
|
|
||||||
|
CXX_GUARD_START
|
||||||
|
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
#include <libavcodec/version.h>
|
||||||
|
|
||||||
|
// Version 57.16 in FFmpeg
|
||||||
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 100)
|
||||||
|
#define FFMPEG_USE_PACKETS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Version 57.15 in libav
|
||||||
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 35, 0)
|
||||||
|
#define FFMPEG_USE_NEW_BSF
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Version 57.14 in libav
|
||||||
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 0)
|
||||||
|
#define FFMPEG_USE_CODECPAR
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 8, 0)
|
||||||
|
#define FFMPEG_USE_PACKET_UNREF
|
||||||
|
#endif
|
||||||
|
|
||||||
|
CXX_GUARD_END
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,219 @@
|
||||||
|
/* Copyright (c) 2013-2020 Jeffrey Pfau
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
#include "ffmpeg-decoder.h"
|
||||||
|
|
||||||
|
#include <libswscale/swscale.h>
|
||||||
|
|
||||||
|
void FFmpegDecoderInit(struct FFmpegDecoder* decoder) {
|
||||||
|
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
|
||||||
|
av_register_all();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
memset(decoder, 0, sizeof(*decoder));
|
||||||
|
decoder->audioStream = -1;
|
||||||
|
decoder->videoStream = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FFmpegDecoderOpen(struct FFmpegDecoder* decoder, const char* infile) {
|
||||||
|
if (FFmpegDecoderIsOpen(decoder)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (avformat_open_input(&decoder->context, infile, NULL, NULL) < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (avformat_find_stream_info(decoder->context, NULL) < 0) {
|
||||||
|
FFmpegDecoderClose(decoder);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned i;
|
||||||
|
for (i = 0; i < decoder->context->nb_streams; ++i) {
|
||||||
|
#ifdef FFMPEG_USE_CODECPAR
|
||||||
|
enum AVMediaType type = decoder->context->streams[i]->codecpar->codec_type;
|
||||||
|
#else
|
||||||
|
enum AVMediaType type = decoder->context->streams[i]->codec->codec_type;
|
||||||
|
#endif
|
||||||
|
struct AVCodec* codec;
|
||||||
|
struct AVCodecContext* context = NULL;
|
||||||
|
if (type == AVMEDIA_TYPE_VIDEO && decoder->videoStream < 0) {
|
||||||
|
decoder->video = avcodec_alloc_context3(NULL);
|
||||||
|
if (!decoder->video) {
|
||||||
|
FFmpegDecoderClose(decoder);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
context = decoder->video;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == AVMEDIA_TYPE_AUDIO && decoder->audioStream < 0) {
|
||||||
|
decoder->audio = avcodec_alloc_context3(NULL);
|
||||||
|
if (!decoder->audio) {
|
||||||
|
FFmpegDecoderClose(decoder);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
context = decoder->audio;
|
||||||
|
}
|
||||||
|
if (!context) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef FFMPEG_USE_CODECPAR
|
||||||
|
if (avcodec_parameters_to_context(context, decoder->context->streams[i]->codecpar) < 0) {
|
||||||
|
FFmpegDecoderClose(decoder);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
codec = avcodec_find_decoder(context->codec_id);
|
||||||
|
if (!codec) {
|
||||||
|
FFmpegDecoderClose(decoder);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (avcodec_open2(context, codec, NULL) < 0) {
|
||||||
|
FFmpegDecoderClose(decoder);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == AVMEDIA_TYPE_VIDEO) {
|
||||||
|
decoder->videoStream = i;
|
||||||
|
decoder->width = -1;
|
||||||
|
decoder->height = -1;
|
||||||
|
#if LIBAVCODEC_VERSION_MAJOR >= 55
|
||||||
|
decoder->videoFrame = av_frame_alloc();
|
||||||
|
#else
|
||||||
|
decoder->videoFrame = avcodec_alloc_frame();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == AVMEDIA_TYPE_AUDIO) {
|
||||||
|
decoder->audioStream = i;
|
||||||
|
#if LIBAVCODEC_VERSION_MAJOR >= 55
|
||||||
|
decoder->audioFrame = av_frame_alloc();
|
||||||
|
#else
|
||||||
|
decoder->audioFrame = avcodec_alloc_frame();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FFmpegDecoderClose(struct FFmpegDecoder* decoder) {
|
||||||
|
if (decoder->audioFrame) {
|
||||||
|
#if LIBAVCODEC_VERSION_MAJOR >= 55
|
||||||
|
av_frame_free(&decoder->audioFrame);
|
||||||
|
#else
|
||||||
|
avcodec_free_frame(&decoder->audioFrame);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decoder->audio) {
|
||||||
|
#ifdef FFMPEG_USE_CODECPAR
|
||||||
|
avcodec_free_context(&decoder->audio);
|
||||||
|
#else
|
||||||
|
avcodec_close(decoder->audio);
|
||||||
|
decoder->audio = NULL;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decoder->scaleContext) {
|
||||||
|
sws_freeContext(decoder->scaleContext);
|
||||||
|
decoder->scaleContext = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decoder->videoFrame) {
|
||||||
|
#if LIBAVCODEC_VERSION_MAJOR >= 55
|
||||||
|
av_frame_free(&decoder->videoFrame);
|
||||||
|
#else
|
||||||
|
avcodec_free_frame(&decoder->videoFrame);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decoder->pixels) {
|
||||||
|
free(decoder->pixels);
|
||||||
|
decoder->pixels = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decoder->video) {
|
||||||
|
#ifdef FFMPEG_USE_CODECPAR
|
||||||
|
avcodec_free_context(&decoder->video);
|
||||||
|
#else
|
||||||
|
avcodec_close(decoder->video);
|
||||||
|
decoder->video = NULL;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decoder->context) {
|
||||||
|
avformat_close_input(&decoder->context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FFmpegDecoderIsOpen(struct FFmpegDecoder* decoder) {
|
||||||
|
return !!decoder->context;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FFmpegDecoderRead(struct FFmpegDecoder* decoder) {
|
||||||
|
bool readPacket = false;
|
||||||
|
while (!readPacket) {
|
||||||
|
AVPacket packet = {
|
||||||
|
.stream_index = -2
|
||||||
|
};
|
||||||
|
if (av_read_frame(decoder->context, &packet) < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
readPacket = true;
|
||||||
|
if (packet.stream_index == decoder->audioStream) {
|
||||||
|
// TODO
|
||||||
|
} else if (packet.stream_index == decoder->videoStream) {
|
||||||
|
#ifdef FFMPEG_USE_CODECPAR
|
||||||
|
if (avcodec_send_packet(decoder->video, &packet) < 0) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
if (avcodec_receive_frame(decoder->video, decoder->videoFrame) < 0) {
|
||||||
|
readPacket = false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
int gotData;
|
||||||
|
if (avcodec_decode_video2(decoder->video, decoder->videoFrame, &gotData, &packet) < 0 || !gotData) {
|
||||||
|
readPacket = false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (readPacket) {
|
||||||
|
if (decoder->width != decoder->videoFrame->width || decoder->height != decoder->videoFrame->height) {
|
||||||
|
decoder->width = decoder->videoFrame->width;
|
||||||
|
decoder->height = decoder->videoFrame->height;
|
||||||
|
if (decoder->out->videoDimensionsChanged) {
|
||||||
|
decoder->out->videoDimensionsChanged(decoder->out, decoder->width, decoder->height);
|
||||||
|
}
|
||||||
|
if (decoder->pixels) {
|
||||||
|
free(decoder->pixels);
|
||||||
|
}
|
||||||
|
decoder->pixels = calloc(decoder->width * decoder->height, BYTES_PER_PIXEL);
|
||||||
|
if (decoder->scaleContext) {
|
||||||
|
sws_freeContext(decoder->scaleContext);
|
||||||
|
decoder->scaleContext = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (decoder->out->postVideoFrame) {
|
||||||
|
if (!decoder->scaleContext) {
|
||||||
|
decoder->scaleContext = sws_getContext(decoder->width, decoder->height, decoder->videoFrame->format,
|
||||||
|
decoder->width, decoder->height, AV_PIX_FMT_BGR32,
|
||||||
|
SWS_POINT, 0, 0, 0);
|
||||||
|
}
|
||||||
|
int stride = decoder->width * BYTES_PER_PIXEL;
|
||||||
|
sws_scale(decoder->scaleContext, (const uint8_t* const*) decoder->videoFrame->data, decoder->videoFrame->linesize, 0, decoder->videoFrame->height, &decoder->pixels, &stride);
|
||||||
|
decoder->out->postVideoFrame(decoder->out, (const color_t*) decoder->pixels, decoder->width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifdef FFMPEG_USE_PACKET_UNREF
|
||||||
|
av_packet_unref(&packet);
|
||||||
|
#else
|
||||||
|
av_free_packet(&packet);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return readPacket;
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/* Copyright (c) 2013-2020 Jeffrey Pfau
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
#ifndef FFMPEG_DECODER
|
||||||
|
#define FFMPEG_DECODER
|
||||||
|
|
||||||
|
#include <mgba-util/common.h>
|
||||||
|
|
||||||
|
CXX_GUARD_START
|
||||||
|
|
||||||
|
#include <mgba/core/interface.h>
|
||||||
|
|
||||||
|
#include "feature/ffmpeg/ffmpeg-common.h"
|
||||||
|
|
||||||
|
#define FFMPEG_DECODER_BUFSIZE 4096
|
||||||
|
|
||||||
|
struct FFmpegDecoder {
|
||||||
|
struct mAVStream* out;
|
||||||
|
struct AVFormatContext* context;
|
||||||
|
|
||||||
|
int audioStream;
|
||||||
|
AVFrame* audioFrame;
|
||||||
|
struct AVCodecContext* audio;
|
||||||
|
|
||||||
|
int videoStream;
|
||||||
|
AVFrame* videoFrame;
|
||||||
|
struct AVCodecContext* video;
|
||||||
|
struct SwsContext* scaleContext;
|
||||||
|
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
uint8_t* pixels;
|
||||||
|
};
|
||||||
|
|
||||||
|
void FFmpegDecoderInit(struct FFmpegDecoder*);
|
||||||
|
bool FFmpegDecoderOpen(struct FFmpegDecoder*, const char* infile);
|
||||||
|
void FFmpegDecoderClose(struct FFmpegDecoder*);
|
||||||
|
bool FFmpegDecoderIsOpen(struct FFmpegDecoder*);
|
||||||
|
bool FFmpegDecoderRead(struct FFmpegDecoder*);
|
||||||
|
|
||||||
|
CXX_GUARD_END
|
||||||
|
|
||||||
|
#endif
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include <mgba/core/core.h>
|
#include <mgba/core/core.h>
|
||||||
#include <mgba/gba/interface.h>
|
#include <mgba/gba/interface.h>
|
||||||
|
#include <mgba/internal/gba/gba.h>
|
||||||
#include <mgba-util/math.h>
|
#include <mgba-util/math.h>
|
||||||
|
|
||||||
#include <libavcodec/version.h>
|
#include <libavcodec/version.h>
|
||||||
|
@ -393,12 +394,24 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
|
||||||
// QuickTime and a few other things require YUV420
|
// QuickTime and a few other things require YUV420
|
||||||
encoder->video->pix_fmt = AV_PIX_FMT_YUV420P;
|
encoder->video->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||||
}
|
}
|
||||||
#if LIBAVCODEC_VERSION_MAJOR >= 57
|
|
||||||
if (encoder->video->codec->id == AV_CODEC_ID_FFV1) {
|
if (encoder->video->codec->id == AV_CODEC_ID_FFV1) {
|
||||||
|
#if LIBAVCODEC_VERSION_MAJOR >= 57
|
||||||
av_opt_set(encoder->video->priv_data, "coder", "range_tab", 0);
|
av_opt_set(encoder->video->priv_data, "coder", "range_tab", 0);
|
||||||
|
av_opt_set_int(encoder->video->priv_data, "context", 1, 0);
|
||||||
|
#endif
|
||||||
|
encoder->video->gop_size = 128;
|
||||||
|
encoder->video->level = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (encoder->video->codec->id == AV_CODEC_ID_PNG) {
|
||||||
|
encoder->video->compression_level = 8;
|
||||||
|
}
|
||||||
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(58, 48, 100)
|
||||||
|
if (encoder->video->codec->id == AV_CODEC_ID_ZMBV) {
|
||||||
|
encoder->video->compression_level = 5;
|
||||||
|
encoder->video->pix_fmt = AV_PIX_FMT_BGR0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (strcmp(vcodec->name, "libx264") == 0) {
|
if (strcmp(vcodec->name, "libx264") == 0) {
|
||||||
// Try to adaptively figure out when you can use a slower encoder
|
// Try to adaptively figure out when you can use a slower encoder
|
||||||
if (encoder->width * encoder->height > 1000000) {
|
if (encoder->width * encoder->height > 1000000) {
|
||||||
|
@ -409,13 +422,15 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
|
||||||
av_opt_set(encoder->video->priv_data, "preset", "faster", 0);
|
av_opt_set(encoder->video->priv_data, "preset", "faster", 0);
|
||||||
}
|
}
|
||||||
if (encoder->videoBitrate == 0) {
|
if (encoder->videoBitrate == 0) {
|
||||||
av_opt_set(encoder->video->priv_data, "crf", "0", 0);
|
av_opt_set(encoder->video->priv_data, "qp", "0", 0);
|
||||||
encoder->video->pix_fmt = AV_PIX_FMT_YUV444P;
|
encoder->video->pix_fmt = AV_PIX_FMT_YUV444P;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (strcmp(vcodec->name, "libvpx-vp9") == 0 && encoder->videoBitrate == 0) {
|
if (strcmp(vcodec->name, "libvpx-vp9") == 0 && encoder->videoBitrate == 0) {
|
||||||
av_opt_set(encoder->video->priv_data, "lossless", "1", 0);
|
av_opt_set_int(encoder->video->priv_data, "lossless", 1, 0);
|
||||||
encoder->video->pix_fmt = AV_PIX_FMT_YUV444P;
|
av_opt_set_int(encoder->video->priv_data, "crf", 0, 0);
|
||||||
|
encoder->video->gop_size = 120;
|
||||||
|
encoder->video->pix_fmt = AV_PIX_FMT_GBRP;
|
||||||
}
|
}
|
||||||
if (strcmp(vcodec->name, "libwebp_anim") == 0 && encoder->videoBitrate == 0) {
|
if (strcmp(vcodec->name, "libwebp_anim") == 0 && encoder->videoBitrate == 0) {
|
||||||
av_opt_set(encoder->video->priv_data, "lossless", "1", 0);
|
av_opt_set(encoder->video->priv_data, "lossless", "1", 0);
|
||||||
|
|
|
@ -10,29 +10,9 @@
|
||||||
|
|
||||||
CXX_GUARD_START
|
CXX_GUARD_START
|
||||||
|
|
||||||
#include <mgba/internal/gba/gba.h>
|
#include <mgba/core/interface.h>
|
||||||
|
|
||||||
#include <libavformat/avformat.h>
|
#include "feature/ffmpeg/ffmpeg-common.h"
|
||||||
#include <libavcodec/version.h>
|
|
||||||
|
|
||||||
// Version 57.16 in FFmpeg
|
|
||||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 100)
|
|
||||||
#define FFMPEG_USE_PACKETS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Version 57.15 in libav
|
|
||||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 35, 0)
|
|
||||||
#define FFMPEG_USE_NEW_BSF
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Version 57.14 in libav
|
|
||||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 0)
|
|
||||||
#define FFMPEG_USE_CODECPAR
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 8, 0)
|
|
||||||
#define FFMPEG_USE_PACKET_UNREF
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define FFMPEG_FILTERS_MAX 4
|
#define FFMPEG_FILTERS_MAX 4
|
||||||
|
|
||||||
|
@ -73,7 +53,7 @@ struct FFmpegEncoder {
|
||||||
struct AVCodecContext* video;
|
struct AVCodecContext* video;
|
||||||
enum AVPixelFormat pixFormat;
|
enum AVPixelFormat pixFormat;
|
||||||
enum AVPixelFormat ipixFormat;
|
enum AVPixelFormat ipixFormat;
|
||||||
struct AVFrame* videoFrame;
|
AVFrame* videoFrame;
|
||||||
int width;
|
int width;
|
||||||
int height;
|
int height;
|
||||||
int iwidth;
|
int iwidth;
|
||||||
|
|
|
@ -128,7 +128,7 @@ static bool _parsePacket(struct mVideoLogger* logger, const struct mVideoLoggerD
|
||||||
break;
|
break;
|
||||||
case DIRTY_OAM:
|
case DIRTY_OAM:
|
||||||
if (item->address < GB_SIZE_OAM) {
|
if (item->address < GB_SIZE_OAM) {
|
||||||
logger->oam[item->address] = item->value;
|
((uint8_t*) logger->oam)[item->address] = item->value;
|
||||||
proxyRenderer->backend->writeOAM(proxyRenderer->backend, item->address);
|
proxyRenderer->backend->writeOAM(proxyRenderer->backend, item->address);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -11,10 +11,16 @@
|
||||||
#include <mgba/feature/video-logger.h>
|
#include <mgba/feature/video-logger.h>
|
||||||
|
|
||||||
#include <mgba-util/png-io.h>
|
#include <mgba-util/png-io.h>
|
||||||
|
#include <mgba-util/string.h>
|
||||||
#include <mgba-util/table.h>
|
#include <mgba-util/table.h>
|
||||||
#include <mgba-util/vector.h>
|
#include <mgba-util/vector.h>
|
||||||
#include <mgba-util/vfs.h>
|
#include <mgba-util/vfs.h>
|
||||||
|
|
||||||
|
#ifdef USE_FFMPEG
|
||||||
|
#include "feature/ffmpeg/ffmpeg-decoder.h"
|
||||||
|
#include "feature/ffmpeg/ffmpeg-encoder.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
#include <mgba-util/platform/windows/getopt.h>
|
#include <mgba-util/platform/windows/getopt.h>
|
||||||
#else
|
#else
|
||||||
|
@ -127,7 +133,7 @@ static bool parseCInemaArgs(int argc, char* const* argv) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'b':
|
case 'b':
|
||||||
strncpy(base, optarg, sizeof(base));
|
strlcpy(base, optarg, sizeof(base));
|
||||||
// TODO: Verify path exists
|
// TODO: Verify path exists
|
||||||
break;
|
break;
|
||||||
case 'd':
|
case 'd':
|
||||||
|
@ -140,7 +146,7 @@ static bool parseCInemaArgs(int argc, char* const* argv) {
|
||||||
dryRun = true;
|
dryRun = true;
|
||||||
break;
|
break;
|
||||||
case 'o':
|
case 'o':
|
||||||
strncpy(outdir, optarg, sizeof(outdir));
|
strlcpy(outdir, optarg, sizeof(outdir));
|
||||||
// TODO: Make directory
|
// TODO: Make directory
|
||||||
break;
|
break;
|
||||||
case 'q':
|
case 'q':
|
||||||
|
@ -244,7 +250,7 @@ static void reduceTestList(struct CInemaTestList* tests) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void testToPath(const char* testName, char* path) {
|
static void testToPath(const char* testName, char* path) {
|
||||||
strncpy(path, base, PATH_MAX);
|
strlcpy(path, base, PATH_MAX);
|
||||||
|
|
||||||
bool dotSeen = true;
|
bool dotSeen = true;
|
||||||
size_t i;
|
size_t i;
|
||||||
|
@ -253,7 +259,7 @@ static void testToPath(const char* testName, char* path) {
|
||||||
dotSeen = true;
|
dotSeen = true;
|
||||||
} else {
|
} else {
|
||||||
if (dotSeen) {
|
if (dotSeen) {
|
||||||
strncpy(&path[i], PATH_SEP, PATH_MAX - i);
|
strlcpy(&path[i], PATH_SEP, PATH_MAX - i);
|
||||||
i += strlen(PATH_SEP);
|
i += strlen(PATH_SEP);
|
||||||
dotSeen = false;
|
dotSeen = false;
|
||||||
if (!i) {
|
if (!i) {
|
||||||
|
@ -268,7 +274,7 @@ static void testToPath(const char* testName, char* path) {
|
||||||
|
|
||||||
static void _loadConfigTree(struct Table* configTree, const char* testName) {
|
static void _loadConfigTree(struct Table* configTree, const char* testName) {
|
||||||
char key[MAX_TEST];
|
char key[MAX_TEST];
|
||||||
strncpy(key, testName, sizeof(key) - 1);
|
strlcpy(key, testName, sizeof(key));
|
||||||
|
|
||||||
struct mCoreConfig* config;
|
struct mCoreConfig* config;
|
||||||
while (!(config = HashTableLookup(configTree, key))) {
|
while (!(config = HashTableLookup(configTree, key))) {
|
||||||
|
@ -301,7 +307,7 @@ static const char* _lookupValue(struct Table* configTree, const char* testName,
|
||||||
_loadConfigTree(configTree, testName);
|
_loadConfigTree(configTree, testName);
|
||||||
|
|
||||||
char testKey[MAX_TEST];
|
char testKey[MAX_TEST];
|
||||||
strncpy(testKey, testName, sizeof(testKey) - 1);
|
strlcpy(testKey, testName, sizeof(testKey));
|
||||||
|
|
||||||
struct mCoreConfig* config;
|
struct mCoreConfig* config;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -378,10 +384,10 @@ bool CInemaTestInit(struct CInemaTest* test, const char* directory, const char*
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
memset(test, 0, sizeof(*test));
|
memset(test, 0, sizeof(*test));
|
||||||
strncpy(test->directory, directory, sizeof(test->directory) - 1);
|
strlcpy(test->directory, directory, sizeof(test->directory));
|
||||||
strncpy(test->filename, filename, sizeof(test->filename) - 1);
|
strlcpy(test->filename, filename, sizeof(test->filename));
|
||||||
directory += strlen(base) + 1;
|
directory += strlen(base) + 1;
|
||||||
strncpy(test->name, directory, sizeof(test->name) - 1);
|
strlcpy(test->name, directory, sizeof(test->name));
|
||||||
char* str = strstr(test->name, PATH_SEP);
|
char* str = strstr(test->name, PATH_SEP);
|
||||||
while (str) {
|
while (str) {
|
||||||
str[0] = '.';
|
str[0] = '.';
|
||||||
|
@ -390,7 +396,7 @@ bool CInemaTestInit(struct CInemaTest* test, const char* directory, const char*
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _loadBaseline(struct VDir* dir, struct CInemaImage* image, size_t frame, enum CInemaStatus* status) {
|
static bool _loadBaselinePNG(struct VDir* dir, struct CInemaImage* image, size_t frame, enum CInemaStatus* status) {
|
||||||
char baselineName[32];
|
char baselineName[32];
|
||||||
snprintf(baselineName, sizeof(baselineName), "baseline_%04" PRIz "u.png", frame);
|
snprintf(baselineName, sizeof(baselineName), "baseline_%04" PRIz "u.png", frame);
|
||||||
struct VFile* baselineVF = dir->openFile(dir, baselineName, O_RDONLY);
|
struct VFile* baselineVF = dir->openFile(dir, baselineName, O_RDONLY);
|
||||||
|
@ -444,9 +450,35 @@ static bool _loadBaseline(struct VDir* dir, struct CInemaImage* image, size_t fr
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_FFMPEG
|
||||||
|
struct CInemaStream {
|
||||||
|
struct mAVStream d;
|
||||||
|
struct CInemaImage* image;
|
||||||
|
enum CInemaStatus* status;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void _cinemaDimensionsChanged(struct mAVStream* stream, unsigned width, unsigned height) {
|
||||||
|
struct CInemaStream* cistream = (struct CInemaStream*) stream;
|
||||||
|
if (height != cistream->image->height || width != cistream->image->width) {
|
||||||
|
CIlog(1, "Size mismatch for video, expected %ux%u, got %ux%u\n", width, height, cistream->image->width, cistream->image->height);
|
||||||
|
if (*cistream->status == CI_PASS) {
|
||||||
|
*cistream->status = CI_FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _cinemaVideoFrame(struct mAVStream* stream, const color_t* pixels, size_t stride) {
|
||||||
|
struct CInemaStream* cistream = (struct CInemaStream*) stream;
|
||||||
|
cistream->image->stride = stride;
|
||||||
|
size_t bufferSize = cistream->image->stride * cistream->image->height * BYTES_PER_PIXEL;
|
||||||
|
cistream->image->data = malloc(bufferSize);
|
||||||
|
memcpy(cistream->image->data, pixels, bufferSize);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static struct VDir* _makeOutDir(const char* testName) {
|
static struct VDir* _makeOutDir(const char* testName) {
|
||||||
char path[PATH_MAX] = {0};
|
char path[PATH_MAX] = {0};
|
||||||
strncpy(path, outdir, sizeof(path) - 1);
|
strlcpy(path, outdir, sizeof(path));
|
||||||
char* pathEnd = path + strlen(path);
|
char* pathEnd = path + strlen(path);
|
||||||
const char* pos;
|
const char* pos;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -565,13 +597,19 @@ void CInemaTestRun(struct CInemaTest* test, struct Table* configTree) {
|
||||||
unsigned limit = 9999;
|
unsigned limit = 9999;
|
||||||
unsigned skip = 0;
|
unsigned skip = 0;
|
||||||
unsigned fail = 0;
|
unsigned fail = 0;
|
||||||
|
unsigned video = 0;
|
||||||
|
|
||||||
CInemaConfigGetUInt(configTree, test->name, "frames", &limit);
|
CInemaConfigGetUInt(configTree, test->name, "frames", &limit);
|
||||||
CInemaConfigGetUInt(configTree, test->name, "skip", &skip);
|
CInemaConfigGetUInt(configTree, test->name, "skip", &skip);
|
||||||
CInemaConfigGetUInt(configTree, test->name, "fail", &fail);
|
CInemaConfigGetUInt(configTree, test->name, "fail", &fail);
|
||||||
|
CInemaConfigGetUInt(configTree, test->name, "video", &video);
|
||||||
CInemaConfigLoad(configTree, test->name, core);
|
CInemaConfigLoad(configTree, test->name, core);
|
||||||
|
|
||||||
|
struct VFile* save = VFileMemChunk(NULL, 0);
|
||||||
core->loadROM(core, rom);
|
core->loadROM(core, rom);
|
||||||
|
if (!core->loadSave(core, save)) {
|
||||||
|
save->close(save);
|
||||||
|
}
|
||||||
core->rtc.override = RTC_FAKE_EPOCH;
|
core->rtc.override = RTC_FAKE_EPOCH;
|
||||||
core->rtc.value = 1200000000;
|
core->rtc.value = 1200000000;
|
||||||
core->reset(core);
|
core->reset(core);
|
||||||
|
@ -583,6 +621,45 @@ void CInemaTestRun(struct CInemaTest* test, struct Table* configTree) {
|
||||||
for (frame = 0; frame < skip; ++frame) {
|
for (frame = 0; frame < skip; ++frame) {
|
||||||
core->runFrame(core);
|
core->runFrame(core);
|
||||||
}
|
}
|
||||||
|
core->desiredVideoDimensions(core, &image.width, &image.height);
|
||||||
|
|
||||||
|
#ifdef USE_FFMPEG
|
||||||
|
struct FFmpegDecoder decoder;
|
||||||
|
struct FFmpegEncoder encoder;
|
||||||
|
struct CInemaStream stream = {0};
|
||||||
|
if (video) {
|
||||||
|
char fname[PATH_MAX];
|
||||||
|
snprintf(fname, sizeof(fname), "%s" PATH_SEP "baseline.mkv", test->directory);
|
||||||
|
if (rebaseline) {
|
||||||
|
FFmpegEncoderInit(&encoder);
|
||||||
|
FFmpegEncoderSetAudio(&encoder, NULL, 0);
|
||||||
|
FFmpegEncoderSetVideo(&encoder, "png", 0, 0);
|
||||||
|
FFmpegEncoderSetContainer(&encoder, "mkv");
|
||||||
|
FFmpegEncoderSetDimensions(&encoder, image.width, image.height);
|
||||||
|
if (!FFmpegEncoderOpen(&encoder, fname)) {
|
||||||
|
CIerr(1, "Failed to save baseline video\n");
|
||||||
|
} else {
|
||||||
|
core->setAVStream(core, &encoder.d);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FFmpegDecoderInit(&decoder);
|
||||||
|
stream.d.postVideoFrame = _cinemaVideoFrame;
|
||||||
|
stream.d.videoDimensionsChanged = _cinemaDimensionsChanged;
|
||||||
|
stream.status = &test->status;
|
||||||
|
decoder.out = &stream.d;
|
||||||
|
|
||||||
|
if (!FFmpegDecoderOpen(&decoder, fname)) {
|
||||||
|
CIerr(1, "Failed to load baseline video\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (video) {
|
||||||
|
CIerr(0, "Failed to run video test without ffmpeg linked in\n");
|
||||||
|
test->status = CI_ERROR;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
for (frame = 0; limit; ++frame, --limit) {
|
for (frame = 0; limit; ++frame, --limit) {
|
||||||
core->runFrame(core);
|
core->runFrame(core);
|
||||||
++test->totalFrames;
|
++test->totalFrames;
|
||||||
|
@ -599,7 +676,26 @@ void CInemaTestRun(struct CInemaTest* test, struct Table* configTree) {
|
||||||
.height = image.height,
|
.height = image.height,
|
||||||
.stride = image.width,
|
.stride = image.width,
|
||||||
};
|
};
|
||||||
if (_loadBaseline(dir, &expected, frame, &test->status)) {
|
bool baselineFound;
|
||||||
|
if (video) {
|
||||||
|
baselineFound = false;
|
||||||
|
#ifdef USE_FFMPEG
|
||||||
|
if (!rebaseline && FFmpegDecoderIsOpen(&decoder)) {
|
||||||
|
stream.image = &expected;
|
||||||
|
while (!expected.data) {
|
||||||
|
if (!FFmpegDecoderRead(&decoder)) {
|
||||||
|
CIlog(1, "Failed to read more frames. EOF?\n");
|
||||||
|
test->status = CI_FAIL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
baselineFound = expected.data;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
baselineFound = _loadBaselinePNG(dir, &expected, frame, &test->status);
|
||||||
|
}
|
||||||
|
if (baselineFound) {
|
||||||
uint8_t* testPixels = image.data;
|
uint8_t* testPixels = image.data;
|
||||||
uint8_t* expectPixels = expected.data;
|
uint8_t* expectPixels = expected.data;
|
||||||
size_t x;
|
size_t x;
|
||||||
|
@ -622,7 +718,7 @@ void CInemaTestRun(struct CInemaTest* test, struct Table* configTree) {
|
||||||
if (r | g | b) {
|
if (r | g | b) {
|
||||||
failed = true;
|
failed = true;
|
||||||
if (diffs && !diff) {
|
if (diffs && !diff) {
|
||||||
diff = calloc(expected.width * expected.height, BYTES_PER_PIXEL);
|
diff = calloc(expected.stride * expected.height, BYTES_PER_PIXEL);
|
||||||
}
|
}
|
||||||
CIlog(3, "Frame %u failed at pixel %" PRIz "ux%" PRIz "u with diff %i,%i,%i (expected %02x%02x%02x, got %02x%02x%02x)\n",
|
CIlog(3, "Frame %u failed at pixel %" PRIz "ux%" PRIz "u with diff %i,%i,%i (expected %02x%02x%02x, got %02x%02x%02x)\n",
|
||||||
frameCounter, x, y, r, g, b,
|
frameCounter, x, y, r, g, b,
|
||||||
|
@ -670,9 +766,9 @@ void CInemaTestRun(struct CInemaTest* test, struct Table* configTree) {
|
||||||
if (failed) {
|
if (failed) {
|
||||||
struct CInemaImage outdiff = {
|
struct CInemaImage outdiff = {
|
||||||
.data = diff,
|
.data = diff,
|
||||||
.width = image.width,
|
.width = expected.width,
|
||||||
.height = image.height,
|
.height = expected.height,
|
||||||
.stride = image.width,
|
.stride = expected.stride,
|
||||||
};
|
};
|
||||||
|
|
||||||
_writeDiff(test->name, &image, frame, "result");
|
_writeDiff(test->name, &image, frame, "result");
|
||||||
|
@ -694,8 +790,10 @@ void CInemaTestRun(struct CInemaTest* test, struct Table* configTree) {
|
||||||
free(expected.data);
|
free(expected.data);
|
||||||
} else if (test->status == CI_ERROR) {
|
} else if (test->status == CI_ERROR) {
|
||||||
break;
|
break;
|
||||||
} else if (rebaseline) {
|
} else if (rebaseline && !video) {
|
||||||
_writeBaseline(dir, &image, frame);
|
_writeBaseline(dir, &image, frame);
|
||||||
|
} else if (!rebaseline) {
|
||||||
|
test->status = CI_FAIL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -707,6 +805,16 @@ void CInemaTestRun(struct CInemaTest* test, struct Table* configTree) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_FFMPEG
|
||||||
|
if (video) {
|
||||||
|
if (rebaseline) {
|
||||||
|
FFmpegEncoderClose(&encoder);
|
||||||
|
} else {
|
||||||
|
FFmpegDecoderClose(&decoder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
free(image.data);
|
free(image.data);
|
||||||
mCoreConfigDeinit(&core->config);
|
mCoreConfigDeinit(&core->config);
|
||||||
core->deinit(core);
|
core->deinit(core);
|
||||||
|
@ -768,7 +876,7 @@ int main(int argc, char** argv) {
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
char* rbase = realpath(base, NULL);
|
char* rbase = realpath(base, NULL);
|
||||||
if (rbase) {
|
if (rbase) {
|
||||||
strncpy(base, rbase, PATH_MAX);
|
strlcpy(base, rbase, sizeof(base));
|
||||||
free(rbase);
|
free(rbase);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -778,6 +886,11 @@ int main(int argc, char** argv) {
|
||||||
|
|
||||||
struct mLogger logger = { .log = _log };
|
struct mLogger logger = { .log = _log };
|
||||||
mLogSetDefaultLogger(&logger);
|
mLogSetDefaultLogger(&logger);
|
||||||
|
#ifdef USE_FFMPEG
|
||||||
|
if (verbosity < 2) {
|
||||||
|
av_log_set_level(AV_LOG_ERROR);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (argc > 0) {
|
if (argc > 0) {
|
||||||
size_t i;
|
size_t i;
|
||||||
|
|
Loading…
Reference in New Issue