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()
|
||||
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})
|
||||
list(APPEND FEATURE_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/feature/ffmpeg/ffmpeg-encoder.c")
|
||||
list(APPEND DEPENDENCY_LIB ${FFMPEG_LIBRARIES} ${LIBAVCODEC_LIBRARIES} ${LIBAVFILTER_LIBRARIES} ${LIBAVFORMAT_LIBRARIES} ${LIBAVRESAMPLE_LIBRARIES} ${LIBAVUTIL_LIBRARIES} ${LIBSWSCALE_LIBRARIES} ${LIBSWRESAMPLE_LIBRARIES})
|
||||
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})
|
||||
if(WIN32 AND NOT DEFINED VCPKG_TARGET_TRIPLET)
|
||||
list(APPEND DEPENDENCY_LIB bcrypt)
|
||||
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/gba/interface.h>
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
#include <mgba-util/math.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
|
||||
encoder->video->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||
}
|
||||
#if LIBAVCODEC_VERSION_MAJOR >= 57
|
||||
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_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
|
||||
|
||||
if (strcmp(vcodec->name, "libx264") == 0) {
|
||||
// Try to adaptively figure out when you can use a slower encoder
|
||||
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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (strcmp(vcodec->name, "libvpx-vp9") == 0 && encoder->videoBitrate == 0) {
|
||||
av_opt_set(encoder->video->priv_data, "lossless", "1", 0);
|
||||
encoder->video->pix_fmt = AV_PIX_FMT_YUV444P;
|
||||
av_opt_set_int(encoder->video->priv_data, "lossless", 1, 0);
|
||||
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) {
|
||||
av_opt_set(encoder->video->priv_data, "lossless", "1", 0);
|
||||
|
|
|
@ -10,29 +10,9 @@
|
|||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
#include <mgba/core/interface.h>
|
||||
|
||||
#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
|
||||
#include "feature/ffmpeg/ffmpeg-common.h"
|
||||
|
||||
#define FFMPEG_FILTERS_MAX 4
|
||||
|
||||
|
@ -73,7 +53,7 @@ struct FFmpegEncoder {
|
|||
struct AVCodecContext* video;
|
||||
enum AVPixelFormat pixFormat;
|
||||
enum AVPixelFormat ipixFormat;
|
||||
struct AVFrame* videoFrame;
|
||||
AVFrame* videoFrame;
|
||||
int width;
|
||||
int height;
|
||||
int iwidth;
|
||||
|
|
|
@ -128,7 +128,7 @@ static bool _parsePacket(struct mVideoLogger* logger, const struct mVideoLoggerD
|
|||
break;
|
||||
case DIRTY_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);
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -11,10 +11,16 @@
|
|||
#include <mgba/feature/video-logger.h>
|
||||
|
||||
#include <mgba-util/png-io.h>
|
||||
#include <mgba-util/string.h>
|
||||
#include <mgba-util/table.h>
|
||||
#include <mgba-util/vector.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
|
||||
#include <mgba-util/platform/windows/getopt.h>
|
||||
#else
|
||||
|
@ -127,7 +133,7 @@ static bool parseCInemaArgs(int argc, char* const* argv) {
|
|||
}
|
||||
break;
|
||||
case 'b':
|
||||
strncpy(base, optarg, sizeof(base));
|
||||
strlcpy(base, optarg, sizeof(base));
|
||||
// TODO: Verify path exists
|
||||
break;
|
||||
case 'd':
|
||||
|
@ -140,7 +146,7 @@ static bool parseCInemaArgs(int argc, char* const* argv) {
|
|||
dryRun = true;
|
||||
break;
|
||||
case 'o':
|
||||
strncpy(outdir, optarg, sizeof(outdir));
|
||||
strlcpy(outdir, optarg, sizeof(outdir));
|
||||
// TODO: Make directory
|
||||
break;
|
||||
case 'q':
|
||||
|
@ -244,7 +250,7 @@ static void reduceTestList(struct CInemaTestList* tests) {
|
|||
}
|
||||
|
||||
static void testToPath(const char* testName, char* path) {
|
||||
strncpy(path, base, PATH_MAX);
|
||||
strlcpy(path, base, PATH_MAX);
|
||||
|
||||
bool dotSeen = true;
|
||||
size_t i;
|
||||
|
@ -253,7 +259,7 @@ static void testToPath(const char* testName, char* path) {
|
|||
dotSeen = true;
|
||||
} else {
|
||||
if (dotSeen) {
|
||||
strncpy(&path[i], PATH_SEP, PATH_MAX - i);
|
||||
strlcpy(&path[i], PATH_SEP, PATH_MAX - i);
|
||||
i += strlen(PATH_SEP);
|
||||
dotSeen = false;
|
||||
if (!i) {
|
||||
|
@ -268,7 +274,7 @@ static void testToPath(const char* testName, char* path) {
|
|||
|
||||
static void _loadConfigTree(struct Table* configTree, const char* testName) {
|
||||
char key[MAX_TEST];
|
||||
strncpy(key, testName, sizeof(key) - 1);
|
||||
strlcpy(key, testName, sizeof(key));
|
||||
|
||||
struct mCoreConfig* config;
|
||||
while (!(config = HashTableLookup(configTree, key))) {
|
||||
|
@ -301,7 +307,7 @@ static const char* _lookupValue(struct Table* configTree, const char* testName,
|
|||
_loadConfigTree(configTree, testName);
|
||||
|
||||
char testKey[MAX_TEST];
|
||||
strncpy(testKey, testName, sizeof(testKey) - 1);
|
||||
strlcpy(testKey, testName, sizeof(testKey));
|
||||
|
||||
struct mCoreConfig* config;
|
||||
while (true) {
|
||||
|
@ -378,10 +384,10 @@ bool CInemaTestInit(struct CInemaTest* test, const char* directory, const char*
|
|||
return false;
|
||||
}
|
||||
memset(test, 0, sizeof(*test));
|
||||
strncpy(test->directory, directory, sizeof(test->directory) - 1);
|
||||
strncpy(test->filename, filename, sizeof(test->filename) - 1);
|
||||
strlcpy(test->directory, directory, sizeof(test->directory));
|
||||
strlcpy(test->filename, filename, sizeof(test->filename));
|
||||
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);
|
||||
while (str) {
|
||||
str[0] = '.';
|
||||
|
@ -390,7 +396,7 @@ bool CInemaTestInit(struct CInemaTest* test, const char* directory, const char*
|
|||
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];
|
||||
snprintf(baselineName, sizeof(baselineName), "baseline_%04" PRIz "u.png", frame);
|
||||
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;
|
||||
}
|
||||
|
||||
#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) {
|
||||
char path[PATH_MAX] = {0};
|
||||
strncpy(path, outdir, sizeof(path) - 1);
|
||||
strlcpy(path, outdir, sizeof(path));
|
||||
char* pathEnd = path + strlen(path);
|
||||
const char* pos;
|
||||
while (true) {
|
||||
|
@ -565,13 +597,19 @@ void CInemaTestRun(struct CInemaTest* test, struct Table* configTree) {
|
|||
unsigned limit = 9999;
|
||||
unsigned skip = 0;
|
||||
unsigned fail = 0;
|
||||
unsigned video = 0;
|
||||
|
||||
CInemaConfigGetUInt(configTree, test->name, "frames", &limit);
|
||||
CInemaConfigGetUInt(configTree, test->name, "skip", &skip);
|
||||
CInemaConfigGetUInt(configTree, test->name, "fail", &fail);
|
||||
CInemaConfigGetUInt(configTree, test->name, "video", &video);
|
||||
CInemaConfigLoad(configTree, test->name, core);
|
||||
|
||||
struct VFile* save = VFileMemChunk(NULL, 0);
|
||||
core->loadROM(core, rom);
|
||||
if (!core->loadSave(core, save)) {
|
||||
save->close(save);
|
||||
}
|
||||
core->rtc.override = RTC_FAKE_EPOCH;
|
||||
core->rtc.value = 1200000000;
|
||||
core->reset(core);
|
||||
|
@ -583,6 +621,45 @@ void CInemaTestRun(struct CInemaTest* test, struct Table* configTree) {
|
|||
for (frame = 0; frame < skip; ++frame) {
|
||||
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) {
|
||||
core->runFrame(core);
|
||||
++test->totalFrames;
|
||||
|
@ -599,7 +676,26 @@ void CInemaTestRun(struct CInemaTest* test, struct Table* configTree) {
|
|||
.height = image.height,
|
||||
.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* expectPixels = expected.data;
|
||||
size_t x;
|
||||
|
@ -622,7 +718,7 @@ void CInemaTestRun(struct CInemaTest* test, struct Table* configTree) {
|
|||
if (r | g | b) {
|
||||
failed = true;
|
||||
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",
|
||||
frameCounter, x, y, r, g, b,
|
||||
|
@ -670,9 +766,9 @@ void CInemaTestRun(struct CInemaTest* test, struct Table* configTree) {
|
|||
if (failed) {
|
||||
struct CInemaImage outdiff = {
|
||||
.data = diff,
|
||||
.width = image.width,
|
||||
.height = image.height,
|
||||
.stride = image.width,
|
||||
.width = expected.width,
|
||||
.height = expected.height,
|
||||
.stride = expected.stride,
|
||||
};
|
||||
|
||||
_writeDiff(test->name, &image, frame, "result");
|
||||
|
@ -694,8 +790,10 @@ void CInemaTestRun(struct CInemaTest* test, struct Table* configTree) {
|
|||
free(expected.data);
|
||||
} else if (test->status == CI_ERROR) {
|
||||
break;
|
||||
} else if (rebaseline) {
|
||||
} else if (rebaseline && !video) {
|
||||
_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);
|
||||
mCoreConfigDeinit(&core->config);
|
||||
core->deinit(core);
|
||||
|
@ -768,7 +876,7 @@ int main(int argc, char** argv) {
|
|||
#ifndef _WIN32
|
||||
char* rbase = realpath(base, NULL);
|
||||
if (rbase) {
|
||||
strncpy(base, rbase, PATH_MAX);
|
||||
strlcpy(base, rbase, sizeof(base));
|
||||
free(rbase);
|
||||
}
|
||||
#endif
|
||||
|
@ -778,6 +886,11 @@ int main(int argc, char** argv) {
|
|||
|
||||
struct mLogger logger = { .log = _log };
|
||||
mLogSetDefaultLogger(&logger);
|
||||
#ifdef USE_FFMPEG
|
||||
if (verbosity < 2) {
|
||||
av_log_set_level(AV_LOG_ERROR);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (argc > 0) {
|
||||
size_t i;
|
||||
|
|
Loading…
Reference in New Issue