Merge branch 'master' into medusa

This commit is contained in:
Vicki Pfau 2020-07-26 17:01:32 -07:00
commit 3eb6a92265
8 changed files with 458 additions and 49 deletions

View File

@ -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()

View File

@ -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

View File

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

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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;