From e838c4fb0e1a585ec925d8d04d13ec33d09e55b3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 26 Mar 2017 12:24:45 -0700 Subject: [PATCH 01/16] Qt: Add HEVC and NVENC to video formats --- src/platform/qt/VideoView.cpp | 4 ++++ src/platform/qt/VideoView.ui | 9 +++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/platform/qt/VideoView.cpp b/src/platform/qt/VideoView.cpp index 6a6523b6e..681371217 100644 --- a/src/platform/qt/VideoView.cpp +++ b/src/platform/qt/VideoView.cpp @@ -63,7 +63,9 @@ VideoView::VideoView(QWidget* parent) if (s_vcodecMap.empty()) { s_vcodecMap["dirac"] = "libschroedinger"; s_vcodecMap["h264"] = "libx264"; + s_vcodecMap["h264 nvenc"] = "h264_nvenc"; s_vcodecMap["hevc"] = "libx265"; + s_vcodecMap["hevc nvenc"] = "hevc_nvenc"; s_vcodecMap["theora"] = "libtheora"; s_vcodecMap["vp8"] = "libvpx"; s_vcodecMap["vp9"] = "libvpx-vp9"; @@ -458,6 +460,8 @@ void VideoView::uncheckIncompatible() { QString VideoView::sanitizeCodec(const QString& codec, const QMap& mapping) { QString sanitized = codec.toLower(); sanitized = sanitized.remove(QChar('.')); + sanitized = sanitized.remove(QChar('(')); + sanitized = sanitized.remove(QChar(')')); if (mapping.contains(sanitized)) { sanitized = mapping[sanitized]; } diff --git a/src/platform/qt/VideoView.ui b/src/platform/qt/VideoView.ui index ff9dcaed1..4bdcefe75 100644 --- a/src/platform/qt/VideoView.ui +++ b/src/platform/qt/VideoView.ui @@ -266,12 +266,17 @@ - VP8 + h.264 (NVENC) - Xvid + HEVC + + + + + VP8 From fc67f3fbe4f805035b8d872fe0eb2fb4d7712f68 Mon Sep 17 00:00:00 2001 From: waddlesplash Date: Tue, 28 Mar 2017 20:16:57 -0400 Subject: [PATCH 02/16] version.cmake: Correctly set the working directory. Fixes out-of-tree builds. --- version.cmake | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/version.cmake b/version.cmake index 12e0d7c01..2c132db64 100644 --- a/version.cmake +++ b/version.cmake @@ -10,11 +10,11 @@ set(SUMMARY "${PROJECT_NAME} Game Boy Advance Emulator") find_program(GIT git) if(GIT AND NOT SKIP_GIT) - execute_process(COMMAND ${GIT} describe --always --abbrev=40 --dirty OUTPUT_VARIABLE GIT_COMMIT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND ${GIT} describe --always --dirty OUTPUT_VARIABLE GIT_COMMIT_SHORT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND ${GIT} symbolic-ref --short HEAD OUTPUT_VARIABLE GIT_BRANCH ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND ${GIT} rev-list HEAD --count OUTPUT_VARIABLE GIT_REV ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND ${GIT} describe --tag --exact-match OUTPUT_VARIABLE GIT_TAG ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${GIT} describe --always --abbrev=40 --dirty WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" OUTPUT_VARIABLE GIT_COMMIT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${GIT} describe --always --dirty WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" OUTPUT_VARIABLE GIT_COMMIT_SHORT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${GIT} symbolic-ref --short HEAD WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" OUTPUT_VARIABLE GIT_BRANCH ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${GIT} rev-list HEAD --count WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" OUTPUT_VARIABLE GIT_REV ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${GIT} describe --tag --exact-match WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" OUTPUT_VARIABLE GIT_TAG ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) endif() if(NOT GIT_REV) From 93dbae1e5fc50411fc229c799ce49d3b4ea97272 Mon Sep 17 00:00:00 2001 From: Jeremy Herbert Date: Mon, 27 Mar 2017 22:07:19 +1000 Subject: [PATCH 03/16] Fix GDB stub reporting of CPSR --- src/debugger/gdb-stub.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/debugger/gdb-stub.c b/src/debugger/gdb-stub.c index b497c24d7..7c4111c86 100644 --- a/src/debugger/gdb-stub.c +++ b/src/debugger/gdb-stub.c @@ -321,13 +321,31 @@ static void _readGPRs(struct GDBStub* stub, const char* message) { UNUSED(message); int r; int i = 0; + + // General purpose registers for (r = 0; r < ARM_PC; ++r) { _int2hex32(cpu->gprs[r], &stub->outgoing[i]); i += 8; } + + // Program counter _int2hex32(cpu->gprs[ARM_PC] - (cpu->cpsr.t ? WORD_SIZE_THUMB : WORD_SIZE_ARM), &stub->outgoing[i]); i += 8; + // Floating point registers, unused on the GBA (8 of them, 24 bits each) + for (r = 0; r < 8 * 3; ++r) { + _int2hex32(0, &stub->outgoing[i]); + i += 8; + } + + // Floating point status, unused on the GBA (32 bits) + _int2hex32(0, &stub->outgoing[i]); + i += 8; + + // CPU status + _int2hex32(cpu->cpsr.packed, &stub->outgoing[i]); + i += 8; + stub->outgoing[i] = 0; _sendMessage(stub); } From 874ad93f4dba11722ccfb02aa7a3533777d8e03e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 29 Mar 2017 18:46:36 -0700 Subject: [PATCH 04/16] GBA Memory: Fix copy-on-write for ROM when executing in ROM --- src/gba/memory.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gba/memory.c b/src/gba/memory.c index 69cdc8b28..a6280254e 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -1552,6 +1552,9 @@ void _pristineCow(struct GBA* gba) { void* newRom = anonymousMemoryMap(SIZE_CART0); memcpy(newRom, gba->memory.rom, gba->memory.romSize); memset(((uint8_t*) newRom) + gba->memory.romSize, 0xFF, SIZE_CART0 - gba->memory.romSize); + if (gba->cpu->memory.activeRegion == gba->memory.rom) { + gba->cpu->memory.activeRegion = newRom; + } if (gba->romVf) { #ifndef _3DS gba->romVf->unmap(gba->romVf, gba->memory.rom, gba->memory.romSize); From 40f87518be2d84bcfa650d55568ee39c2ac6294d Mon Sep 17 00:00:00 2001 From: waddlesplash Date: Wed, 29 Mar 2017 23:46:20 -0400 Subject: [PATCH 05/16] Qt: Don't link against SDLMAIN, Qt already handles that. (#551) --- src/platform/qt/CMakeLists.txt | 2 +- src/platform/qt/main.cpp | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index efaed3f05..11605a181 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -22,7 +22,7 @@ if(BUILD_SDL) if(SDL2_FOUND) link_directories(${SDL2_LIBDIR}) endif() - list(APPEND PLATFORM_LIBRARY ${SDL_LIBRARY} ${SDLMAIN_LIBRARY}) + list(APPEND PLATFORM_LIBRARY ${SDL_LIBRARY}) list(APPEND PLATFORM_SRC ${PLATFORM_SRC} ${CMAKE_SOURCE_DIR}/src/platform/sdl/sdl-events.c ${CMAKE_SOURCE_DIR}/src/platform/sdl/sdl-audio.c) include_directories(${SDL_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/src/platform/sdl) endif() diff --git a/src/platform/qt/main.cpp b/src/platform/qt/main.cpp index 29df3ea8d..631040d82 100644 --- a/src/platform/qt/main.cpp +++ b/src/platform/qt/main.cpp @@ -3,6 +3,10 @@ * 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/. */ + +// This must be defined before anything else is included. +#define SDL_MAIN_HANDLED + #include "GBAApp.h" #include "Window.h" @@ -22,6 +26,9 @@ Q_IMPORT_PLUGIN(QWindowsAudioPlugin); #endif int main(int argc, char* argv[]) { +#ifdef BUILD_SDL + SDL_SetMainReady(); +#endif QGBA::GBAApp application(argc, argv); QLocale locale = QLocale::system(); From 4449361f5c61f6c13829bb1f37a706f652592f03 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 1 Apr 2017 14:08:19 -0700 Subject: [PATCH 06/16] Qt: Fix setting audio and video sync without reloading the game --- src/platform/qt/GameController.cpp | 13 +++++++++++++ src/platform/qt/GameController.h | 2 ++ src/platform/qt/Window.cpp | 4 ++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index 66d8eb81e..e26ea13fe 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -345,6 +345,8 @@ void GameController::setConfig(const mCoreConfig* config) { if (isLoaded()) { Interrupter interrupter(this); mCoreLoadForeignConfig(m_threadContext.core, config); + m_audioSync = m_threadContext.sync.audioWait; + m_videoSync = m_threadContext.sync.videoFrameWait; m_audioProcessor->setInput(&m_threadContext); } } @@ -1098,6 +1100,17 @@ void GameController::setSync(bool enable) { } m_sync = enable; } + +void GameController::setAudioSync(bool enable) { + m_audioSync = enable; + m_threadContext.sync.audioWait = enable; +} + +void GameController::setVideoSync(bool enable) { + m_videoSync = enable; + m_threadContext.sync.videoFrameWait = enable; +} + void GameController::setAVStream(mAVStream* stream) { Interrupter interrupter(this); m_stream = stream; diff --git a/src/platform/qt/GameController.h b/src/platform/qt/GameController.h index 70f397c62..75b9a1137 100644 --- a/src/platform/qt/GameController.h +++ b/src/platform/qt/GameController.h @@ -147,6 +147,8 @@ public slots: void setTurbo(bool, bool forced = true); void setTurboSpeed(float ratio); void setSync(bool); + void setAudioSync(bool); + void setVideoSync(bool); void setAVStream(mAVStream*); void clearAVStream(); void reloadAudioDriver(); diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 6e86d9ed3..ba5e2dd58 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1157,14 +1157,14 @@ void Window::setupMenu(QMenuBar* menubar) { ConfigOption* videoSync = m_config->addOption("videoSync"); videoSync->addBoolean(tr("Sync to &video"), emulationMenu); videoSync->connect([this](const QVariant& value) { - reloadConfig(); + m_controller->setVideoSync(value.toBool()); }, this); m_config->updateOption("videoSync"); ConfigOption* audioSync = m_config->addOption("audioSync"); audioSync->addBoolean(tr("Sync to &audio"), emulationMenu); audioSync->connect([this](const QVariant& value) { - reloadConfig(); + m_controller->setAudioSync(value.toBool()); }, this); m_config->updateOption("audioSync"); From 5d13a00cf751daa51aae95a0d064a3070ca0ee72 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 2 Apr 2017 00:37:33 -0700 Subject: [PATCH 07/16] GB Video: Fix frame end callbacks not getting called while screen is off --- src/gb/video.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/gb/video.c b/src/gb/video.c index 465380fa9..038729b20 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -128,14 +128,6 @@ void _endMode0(struct mTiming* timing, void* context, uint32_t cyclesLate) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); } video->p->memory.io[REG_IF] |= (1 << GB_IRQ_VBLANK); - - size_t c; - for (c = 0; c < mCoreCallbacksListSize(&video->p->coreCallbacks); ++c) { - struct mCoreCallbacks* callbacks = mCoreCallbacksListGetPointer(&video->p->coreCallbacks, c); - if (callbacks->videoFrameEnded) { - callbacks->videoFrameEnded(callbacks->context); - } - } } if (!GBRegisterSTATIsHblankIRQ(video->stat) && GBRegisterSTATIsLYCIRQ(video->stat) && lyc == video->ly) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); @@ -231,6 +223,14 @@ void _updateFrameCount(struct mTiming* timing, void* context, uint32_t cyclesLat return; } + size_t c; + for (c = 0; c < mCoreCallbacksListSize(&video->p->coreCallbacks); ++c) { + struct mCoreCallbacks* callbacks = mCoreCallbacksListGetPointer(&video->p->coreCallbacks, c); + if (callbacks->videoFrameEnded) { + callbacks->videoFrameEnded(callbacks->context); + } + } + GBFrameEnded(video->p); --video->frameskipCounter; if (video->frameskipCounter < 0) { @@ -247,7 +247,6 @@ void _updateFrameCount(struct mTiming* timing, void* context, uint32_t cyclesLat video->p->stream->postVideoFrame(video->p->stream, pixels, stride); } - size_t c; for (c = 0; c < mCoreCallbacksListSize(&video->p->coreCallbacks); ++c) { struct mCoreCallbacks* callbacks = mCoreCallbacksListGetPointer(&video->p->coreCallbacks, c); if (callbacks->videoFrameStarted) { From 10fe4a743c1b01c496987b5cac2dfc479e89c8dc Mon Sep 17 00:00:00 2001 From: waddlesplash Date: Thu, 30 Mar 2017 11:11:29 -0400 Subject: [PATCH 08/16] Qt: Show native directory separators in the GUI. The core still gets '/'s always (it chokes on '\'s), but the Qt interface always uses the native separators. In the process of doing this, also removed the custom FileDialog subclass and made everything use GBAApp::get*FileDialog instead. Also fixes #552, because I had to change that code anyway. --- src/platform/qt/GBAApp.cpp | 36 +++--------------------------- src/platform/qt/GBAApp.h | 13 ----------- src/platform/qt/GameController.cpp | 4 ++-- src/platform/qt/ObjView.cpp | 8 ++----- src/platform/qt/PaletteView.cpp | 13 ++++------- src/platform/qt/Window.cpp | 2 +- 6 files changed, 12 insertions(+), 64 deletions(-) diff --git a/src/platform/qt/GBAApp.cpp b/src/platform/qt/GBAApp.cpp index b54ea49ee..088d490cc 100644 --- a/src/platform/qt/GBAApp.cpp +++ b/src/platform/qt/GBAApp.cpp @@ -163,7 +163,7 @@ QString GBAApp::getOpenFileName(QWidget* owner, const QString& title, const QStr QString filename = QFileDialog::getOpenFileName(owner, title, m_configController.getOption("lastDirectory"), filter); continueAll(paused); if (!filename.isEmpty()) { - m_configController.setOption("lastDirectory", QFileInfo(filename).dir().path()); + m_configController.setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath()); } return filename; } @@ -174,7 +174,7 @@ QString GBAApp::getSaveFileName(QWidget* owner, const QString& title, const QStr QString filename = QFileDialog::getSaveFileName(owner, title, m_configController.getOption("lastDirectory"), filter); continueAll(paused); if (!filename.isEmpty()) { - m_configController.setOption("lastDirectory", QFileInfo(filename).dir().path()); + m_configController.setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath()); } return filename; } @@ -185,23 +185,11 @@ QString GBAApp::getOpenDirectoryName(QWidget* owner, const QString& title) { QString filename = QFileDialog::getExistingDirectory(owner, title, m_configController.getOption("lastDirectory")); continueAll(paused); if (!filename.isEmpty()) { - m_configController.setOption("lastDirectory", QFileInfo(filename).dir().path()); + m_configController.setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath()); } return filename; } -QFileDialog* GBAApp::getOpenFileDialog(QWidget* owner, const QString& title, const QString& filter) { - FileDialog* dialog = new FileDialog(this, owner, title, filter); - dialog->setAcceptMode(QFileDialog::AcceptOpen); - return dialog; -} - -QFileDialog* GBAApp::getSaveFileDialog(QWidget* owner, const QString& title, const QString& filter) { - FileDialog* dialog = new FileDialog(this, owner, title, filter); - dialog->setAcceptMode(QFileDialog::AcceptSave); - return dialog; -} - QString GBAApp::dataDir() { #ifdef DATADIR QString path = QString::fromUtf8(DATADIR); @@ -241,24 +229,6 @@ bool GBAApp::reloadGameDB() { } #endif -GBAApp::FileDialog::FileDialog(GBAApp* app, QWidget* parent, const QString& caption, const QString& filter) - : QFileDialog(parent, caption, app->m_configController.getOption("lastDirectory"), filter) - , m_app(app) -{ -} - -int GBAApp::FileDialog::exec() { - QList paused; - m_app->pauseAll(&paused); - bool didAccept = QFileDialog::exec() == QDialog::Accepted; - QStringList filenames = selectedFiles(); - if (!filenames.isEmpty()) { - m_app->m_configController.setOption("lastDirectory", QFileInfo(filenames[0]).dir().path()); - } - m_app->continueAll(paused); - return didAccept; -} - #ifdef USE_SQLITE3 GameDBParser::GameDBParser(NoIntroDB* db, QObject* parent) : QObject(parent) diff --git a/src/platform/qt/GBAApp.h b/src/platform/qt/GBAApp.h index 8ade195e1..be0e620d4 100644 --- a/src/platform/qt/GBAApp.h +++ b/src/platform/qt/GBAApp.h @@ -55,9 +55,6 @@ public: QString getSaveFileName(QWidget* owner, const QString& title, const QString& filter = QString()); QString getOpenDirectoryName(QWidget* owner, const QString& title); - QFileDialog* getOpenFileDialog(QWidget* owner, const QString& title, const QString& filter = QString()); - QFileDialog* getSaveFileDialog(QWidget* owner, const QString& title, const QString& filter = QString()); - const NoIntroDB* gameDB() const { return m_db; } bool reloadGameDB(); @@ -65,16 +62,6 @@ protected: bool event(QEvent*); private: - class FileDialog : public QFileDialog { - public: - FileDialog(GBAApp* app, QWidget* parent = nullptr, const QString& caption = QString(), - const QString& filter = QString()); - virtual int exec() override; - - private: - GBAApp* m_app; - }; - Window* newWindowInternal(); void pauseAll(QList* paused); diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index e26ea13fe..bf81defd0 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -409,10 +409,10 @@ void GameController::loadGame(VFile* vf, const QString& path, const QString& bas closeGame(); QFileInfo info(base); if (info.isDir()) { - m_fname = base + QDir::separator() + path; + m_fname = QFileInfo(base + '/' + path).canonicalFilePath(); m_fsub = QString(); } else { - m_fname = base; + m_fname = info.canonicalFilePath(); m_fsub = path; } m_vf = vf; diff --git a/src/platform/qt/ObjView.cpp b/src/platform/qt/ObjView.cpp index 2749d7a50..dc74bebb6 100644 --- a/src/platform/qt/ObjView.cpp +++ b/src/platform/qt/ObjView.cpp @@ -244,12 +244,8 @@ void ObjView::updateTilesGB(bool force) { void ObjView::exportObj() { GameController::Interrupter interrupter(m_controller); - QFileDialog* dialog = GBAApp::app()->getSaveFileDialog(this, tr("Export sprite"), - tr("Portable Network Graphics (*.png)")); - if (!dialog->exec()) { - return; - } - QString filename = dialog->selectedFiles()[0]; + QString filename = GBAApp::app()->getSaveFileName(this, tr("Export sprite"), + tr("Portable Network Graphics (*.png)")); VFile* vf = VFileDevice::open(filename, O_WRONLY | O_CREAT | O_TRUNC); if (!vf) { LOG(QT, ERROR) << tr("Failed to open output PNG file: %1").arg(filename); diff --git a/src/platform/qt/PaletteView.cpp b/src/platform/qt/PaletteView.cpp index af55bdd4b..53a8aaa6e 100644 --- a/src/platform/qt/PaletteView.cpp +++ b/src/platform/qt/PaletteView.cpp @@ -134,21 +134,16 @@ void PaletteView::exportPalette(int start, int length) { } GameController::Interrupter interrupter(m_controller); - QFileDialog* dialog = GBAApp::app()->getSaveFileDialog(this, tr("Export palette"), - tr("Windows PAL (*.pal);;Adobe Color Table (*.act)")); - if (!dialog->exec()) { - return; - } - QString filename = dialog->selectedFiles()[0]; + QString filename = GBAApp::app()->getSaveFileName(this, tr("Export palette"), + tr("Windows PAL (*.pal);;Adobe Color Table (*.act)")); VFile* vf = VFileDevice::open(filename, O_WRONLY | O_CREAT | O_TRUNC); if (!vf) { LOG(QT, ERROR) << tr("Failed to open output palette file: %1").arg(filename); return; } - QString filter = dialog->selectedNameFilter(); - if (filter.contains("*.pal")) { + if (filename.endsWith(".pal", Qt::CaseInsensitive)) { exportPaletteRIFF(vf, length, &static_cast(m_controller->thread()->core->board)->video.palette[start]); - } else if (filter.contains("*.act")) { + } else if (filename.endsWith(".act", Qt::CaseInsensitive)) { exportPaletteACT(vf, length, &static_cast(m_controller->thread()->core->board)->video.palette[start]); } vf->close(vf); diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index ba5e2dd58..76756e628 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1580,7 +1580,7 @@ void Window::updateMRU() { m_mruMenu->clear(); int i = 0; for (const QString& file : m_mruFiles) { - QAction* item = new QAction(file, m_mruMenu); + QAction* item = new QAction(QDir::toNativeSeparators(file).replace("&", "&&"), m_mruMenu); item->setShortcut(QString("Ctrl+%1").arg(i)); connect(item, &QAction::triggered, [this, file]() { m_controller->loadGame(file); }); m_mruMenu->addAction(item); From 35b5626053dea8a4c53c63b6da23831fea6bb649 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 2 Apr 2017 16:47:46 -0700 Subject: [PATCH 09/16] SDL: Fix game crash check --- CHANGES | 1 + src/platform/sdl/main.c | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 656e7b80f..190d6a6d7 100644 --- a/CHANGES +++ b/CHANGES @@ -31,6 +31,7 @@ Bugfixes: - Qt: Fix linking after some windows have been closed - GBA Video: Fix wrong palette on 256-color sprites in OBJWIN - Windows: Fix VDir.rewind + - SDL: Fix game crash check Misc: - SDL: Remove scancode key input - GBA Video: Clean up unused timers diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c index a5d960b23..cf740f54f 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -194,6 +194,10 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) { if (mCoreThreadStart(&thread)) { renderer->runloop(renderer, &thread); mSDLPauseAudio(&renderer->audio); + if (mCoreThreadHasCrashed(&thread)) { + didFail = true; + printf("The game crashed!\n"); + } mCoreThreadJoin(&thread); } else { didFail = true; @@ -204,11 +208,6 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) { mSDLResumeScreensaver(&renderer->events); mSDLSetScreensaverSuspendable(&renderer->events, false); #endif - - if (mCoreThreadHasCrashed(&thread)) { - didFail = true; - printf("The game crashed!\n"); - } } renderer->core->unloadROM(renderer->core); return didFail; From 4a38f9b97998fb977f04d2b980830d3b697a6bd4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 3 Apr 2017 10:54:49 -0700 Subject: [PATCH 10/16] Qt: Simplify high-framerate fix (fixes #545) --- src/platform/qt/DisplayGL.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index da5781a3c..49d9ce5b1 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -318,15 +318,6 @@ void PainterGL::draw() { if (m_queue.isEmpty() || !mCoreThreadIsActive(m_context)) { return; } - if (!m_delayTimer.isValid()) { - m_delayTimer.start(); - } else if (m_delayTimer.elapsed() < 16) { - QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection); - QThread::usleep(500); - return; - } else { - m_delayTimer.restart(); - } if (mCoreSyncWaitFrameStart(&m_context->sync) || !m_queue.isEmpty()) { dequeue(); @@ -335,6 +326,14 @@ void PainterGL::draw() { performDraw(); m_painter.end(); m_backend->swap(m_backend); + if (!m_delayTimer.isValid()) { + m_delayTimer.start(); + } else { + while (m_delayTimer.elapsed() < 15) { + QThread::usleep(100); + } + m_delayTimer.restart(); + } } else { mCoreSyncWaitFrameEnd(&m_context->sync); } From 232e67f5291f2857dd92f9385e892adf14dab9af Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 3 Apr 2017 12:20:28 -0700 Subject: [PATCH 11/16] SDL: Fix race condition with audio thread when starting --- CHANGES | 1 + src/platform/sdl/main.c | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 190d6a6d7..a873c957d 100644 --- a/CHANGES +++ b/CHANGES @@ -32,6 +32,7 @@ Bugfixes: - GBA Video: Fix wrong palette on 256-color sprites in OBJWIN - Windows: Fix VDir.rewind - SDL: Fix game crash check + - SDL: Fix race condition with audio thread when starting Misc: - SDL: Remove scancode key input - GBA Video: Clean up unused timers diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c index cf740f54f..24e0353c2 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -185,29 +185,31 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) { renderer->audio.samples = renderer->core->opts.audioBuffers; renderer->audio.sampleRate = 44100; - bool didFail = !mSDLInitAudio(&renderer->audio, &thread); + bool didFail = !mCoreThreadStart(&thread); if (!didFail) { #if SDL_VERSION_ATLEAST(2, 0, 0) mSDLSetScreensaverSuspendable(&renderer->events, renderer->core->opts.suspendScreensaver); mSDLSuspendScreensaver(&renderer->events); #endif - if (mCoreThreadStart(&thread)) { + if (mSDLInitAudio(&renderer->audio, &thread)) { renderer->runloop(renderer, &thread); mSDLPauseAudio(&renderer->audio); if (mCoreThreadHasCrashed(&thread)) { didFail = true; printf("The game crashed!\n"); } - mCoreThreadJoin(&thread); } else { didFail = true; - printf("Could not run game. Are you sure the file exists and is a compatible game?\n"); + printf("Could not initialize audio.\n"); } - #if SDL_VERSION_ATLEAST(2, 0, 0) mSDLResumeScreensaver(&renderer->events); mSDLSetScreensaverSuspendable(&renderer->events, false); #endif + + mCoreThreadJoin(&thread); + } else { + printf("Could not run game. Are you sure the file exists and is a compatible game?\n"); } renderer->core->unloadROM(renderer->core); return didFail; From f73fd7f3da3e36b6a11fe5e15cafaca8c0fd733f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 3 Apr 2017 14:31:40 -0700 Subject: [PATCH 12/16] GB: Fix flickering when screen is strobed quickly --- CHANGES | 1 + include/mgba/internal/gb/video.h | 1 + src/gb/renderers/software.c | 25 +++++++--------------- src/gb/video.c | 36 +++++++++++++++++++------------- 4 files changed, 31 insertions(+), 32 deletions(-) diff --git a/CHANGES b/CHANGES index a873c957d..cdb0889eb 100644 --- a/CHANGES +++ b/CHANGES @@ -33,6 +33,7 @@ Bugfixes: - Windows: Fix VDir.rewind - SDL: Fix game crash check - SDL: Fix race condition with audio thread when starting + - GB: Fix flickering when screen is strobed quickly Misc: - SDL: Remove scancode key input - GBA Video: Clean up unused timers diff --git a/include/mgba/internal/gb/video.h b/include/mgba/internal/gb/video.h index 67bf96405..a7b143077 100644 --- a/include/mgba/internal/gb/video.h +++ b/include/mgba/internal/gb/video.h @@ -119,6 +119,7 @@ struct GBVideo { int ocpIndex; bool ocpIncrement; + uint16_t dmgPalette[4]; uint16_t palette[64]; int32_t frameCounter; diff --git a/src/gb/renderers/software.c b/src/gb/renderers/software.c index 61daa9f98..6c4740d76 100644 --- a/src/gb/renderers/software.c +++ b/src/gb/renderers/software.c @@ -24,26 +24,15 @@ static void GBVideoSoftwareRendererDrawBackground(struct GBVideoSoftwareRenderer static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* renderer, struct GBObj* obj, int startX, int endX, int y); static void _clearScreen(struct GBVideoSoftwareRenderer* renderer) { - // TODO: Dynamic from dmgPalette -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - color_t palette0 = 0xFFDF; -#else - color_t palette0 = 0x7FFF; -#endif -#else - color_t palette0 = 0xFFFFFF; -#endif - int y; for (y = 0; y < GB_VIDEO_VERTICAL_PIXELS; ++y) { color_t* row = &renderer->outputBuffer[renderer->outputBufferStride * y]; int x; for (x = 0; x < GB_VIDEO_HORIZONTAL_PIXELS; x += 4) { - row[x + 0] = palette0; - row[x + 1] = palette0; - row[x + 2] = palette0; - row[x + 3] = palette0; + row[x + 0] = renderer->palette[0]; + row[x + 1] = renderer->palette[0]; + row[x + 2] = renderer->palette[0]; + row[x + 3] = renderer->palette[0]; } } } @@ -85,9 +74,6 @@ static uint8_t GBVideoSoftwareRendererWriteVideoRegister(struct GBVideoRenderer* struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer; switch (address) { case REG_LCDC: - if (GBRegisterLCDCIsEnable(softwareRenderer->lcdc) && !GBRegisterLCDCIsEnable(value)) { - _clearScreen(softwareRenderer); - } softwareRenderer->lcdc = value; break; case REG_SCY: @@ -197,6 +183,9 @@ static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer) mappedMemoryFree(softwareRenderer->temporaryBuffer, GB_VIDEO_HORIZONTAL_PIXELS * GB_VIDEO_VERTICAL_PIXELS * 4); softwareRenderer->temporaryBuffer = 0; } + if (!GBRegisterLCDCIsEnable(softwareRenderer->lcdc)) { + _clearScreen(softwareRenderer); + } softwareRenderer->currentWy = 0; } diff --git a/src/gb/video.c b/src/gb/video.c index 038729b20..e1650acd9 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -61,6 +61,11 @@ void GBVideoInit(struct GBVideo* video) { video->frameEvent.name = "GB Video Frame"; video->frameEvent.callback = _updateFrameCount; video->frameEvent.priority = 9; + + video->dmgPalette[0] = 0x7FFF; + video->dmgPalette[1] = 0x56B5; + video->dmgPalette[2] = 0x294A; + video->dmgPalette[3] = 0x0000; } void GBVideoReset(struct GBVideo* video) { @@ -157,7 +162,6 @@ void _endMode1(struct mTiming* timing, void* context, uint32_t cyclesLate) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); GBUpdateIRQs(video->p); } - video->renderer->finishFrame(video->renderer); if (video->p->memory.mbcType == GB_MBC7 && video->p->memory.rotation && video->p->memory.rotation->sample) { video->p->memory.rotation->sample(video->p->memory.rotation); } @@ -234,6 +238,7 @@ void _updateFrameCount(struct mTiming* timing, void* context, uint32_t cyclesLat GBFrameEnded(video->p); --video->frameskipCounter; if (video->frameskipCounter < 0) { + video->renderer->finishFrame(video->renderer); mCoreSyncPostFrame(video->p->sync); video->frameskipCounter = video->frameskip; } @@ -318,6 +323,8 @@ void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value) { GBUpdateIRQs(video->p); } video->p->memory.io[REG_STAT] = video->stat; + video->renderer->writePalette(video->renderer, 0, video->palette[0]); + mTimingDeschedule(&video->p->timing, &video->frameEvent); } if (GBRegisterLCDCIsEnable(video->p->memory.io[REG_LCDC]) && !GBRegisterLCDCIsEnable(value)) { @@ -326,6 +333,8 @@ void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value) { video->p->memory.io[REG_STAT] = video->stat; video->ly = 0; video->p->memory.io[REG_LY] = 0; + video->renderer->writePalette(video->renderer, 0, video->dmgPalette[0]); + mTimingDeschedule(&video->p->timing, &video->modeEvent); mTimingSchedule(&video->p->timing, &video->frameEvent, GB_VIDEO_TOTAL_LENGTH); } @@ -351,34 +360,33 @@ void GBVideoWriteLYC(struct GBVideo* video, uint8_t value) { } void GBVideoWritePalette(struct GBVideo* video, uint16_t address, uint8_t value) { - static const uint16_t dmgPalette[4] = { 0x7FFF, 0x56B5, 0x294A, 0x0000}; if (video->p->model < GB_MODEL_CGB) { switch (address) { case REG_BGP: - video->palette[0] = dmgPalette[value & 3]; - video->palette[1] = dmgPalette[(value >> 2) & 3]; - video->palette[2] = dmgPalette[(value >> 4) & 3]; - video->palette[3] = dmgPalette[(value >> 6) & 3]; + video->palette[0] = video->dmgPalette[value & 3]; + video->palette[1] = video->dmgPalette[(value >> 2) & 3]; + video->palette[2] = video->dmgPalette[(value >> 4) & 3]; + video->palette[3] = video->dmgPalette[(value >> 6) & 3]; video->renderer->writePalette(video->renderer, 0, video->palette[0]); video->renderer->writePalette(video->renderer, 1, video->palette[1]); video->renderer->writePalette(video->renderer, 2, video->palette[2]); video->renderer->writePalette(video->renderer, 3, video->palette[3]); break; case REG_OBP0: - video->palette[8 * 4 + 0] = dmgPalette[value & 3]; - video->palette[8 * 4 + 1] = dmgPalette[(value >> 2) & 3]; - video->palette[8 * 4 + 2] = dmgPalette[(value >> 4) & 3]; - video->palette[8 * 4 + 3] = dmgPalette[(value >> 6) & 3]; + video->palette[8 * 4 + 0] = video->dmgPalette[value & 3]; + video->palette[8 * 4 + 1] = video->dmgPalette[(value >> 2) & 3]; + video->palette[8 * 4 + 2] = video->dmgPalette[(value >> 4) & 3]; + video->palette[8 * 4 + 3] = video->dmgPalette[(value >> 6) & 3]; video->renderer->writePalette(video->renderer, 8 * 4 + 0, video->palette[8 * 4 + 0]); video->renderer->writePalette(video->renderer, 8 * 4 + 1, video->palette[8 * 4 + 1]); video->renderer->writePalette(video->renderer, 8 * 4 + 2, video->palette[8 * 4 + 2]); video->renderer->writePalette(video->renderer, 8 * 4 + 3, video->palette[8 * 4 + 3]); break; case REG_OBP1: - video->palette[9 * 4 + 0] = dmgPalette[value & 3]; - video->palette[9 * 4 + 1] = dmgPalette[(value >> 2) & 3]; - video->palette[9 * 4 + 2] = dmgPalette[(value >> 4) & 3]; - video->palette[9 * 4 + 3] = dmgPalette[(value >> 6) & 3]; + video->palette[9 * 4 + 0] = video->dmgPalette[value & 3]; + video->palette[9 * 4 + 1] = video->dmgPalette[(value >> 2) & 3]; + video->palette[9 * 4 + 2] = video->dmgPalette[(value >> 4) & 3]; + video->palette[9 * 4 + 3] = video->dmgPalette[(value >> 6) & 3]; video->renderer->writePalette(video->renderer, 9 * 4 + 0, video->palette[9 * 4 + 0]); video->renderer->writePalette(video->renderer, 9 * 4 + 1, video->palette[9 * 4 + 1]); video->renderer->writePalette(video->renderer, 9 * 4 + 2, video->palette[9 * 4 + 2]); From ba65740b159a2ce3bceecf1088f859f326d69b34 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 3 Apr 2017 14:32:21 -0700 Subject: [PATCH 13/16] GB: Allow setting DMG palette --- include/mgba/internal/gb/video.h | 2 ++ src/gb/core.c | 15 +++++++++++++++ src/gb/video.c | 7 +++++++ 3 files changed, 24 insertions(+) diff --git a/include/mgba/internal/gb/video.h b/include/mgba/internal/gb/video.h index a7b143077..80899c723 100644 --- a/include/mgba/internal/gb/video.h +++ b/include/mgba/internal/gb/video.h @@ -139,6 +139,8 @@ void GBVideoWriteLYC(struct GBVideo* video, uint8_t value); void GBVideoWritePalette(struct GBVideo* video, uint16_t address, uint8_t value); void GBVideoSwitchBank(struct GBVideo* video, uint8_t value); +void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint16_t color); + struct GBSerializedState; void GBVideoSerialize(const struct GBVideo* video, struct GBSerializedState* state); void GBVideoDeserialize(struct GBVideo* video, const struct GBSerializedState* state); diff --git a/src/gb/core.c b/src/gb/core.c index f7f552914..8263696f1 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -105,6 +105,21 @@ static void _GBCoreLoadConfig(struct mCore* core, const struct mCoreConfig* conf gb->audio.masterVolume = core->opts.volume; } gb->video.frameskip = core->opts.frameskip; + + int color; + if (mCoreConfigGetIntValue(&core->config, "gb.pal[0]", &color)) { + GBVideoSetPalette(&gb->video, 0, color); + } + if (mCoreConfigGetIntValue(&core->config, "gb.pal[1]", &color)) { + GBVideoSetPalette(&gb->video, 1, color); + } + if (mCoreConfigGetIntValue(&core->config, "gb.pal[2]", &color)) { + GBVideoSetPalette(&gb->video, 2, color); + } + if (mCoreConfigGetIntValue(&core->config, "gb.pal[3]", &color)) { + GBVideoSetPalette(&gb->video, 3, color); + } + mCoreConfigCopyValue(&core->config, config, "gb.bios"); mCoreConfigCopyValue(&core->config, config, "gbc.bios"); diff --git a/src/gb/video.c b/src/gb/video.c index e1650acd9..cefe73cf7 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -439,6 +439,13 @@ void GBVideoSwitchBank(struct GBVideo* video, uint8_t value) { video->vramCurrentBank = value; } +void GBVideoSetPalette(struct GBVideo* video, unsigned index, uint16_t color) { + if (index >= 4) { + return; + } + video->dmgPalette[index] = color; +} + static void GBVideoDummyRendererInit(struct GBVideoRenderer* renderer, enum GBModel model) { UNUSED(renderer); UNUSED(model); From 133424bd4f6b262dccb7da55b3f38a69c9da826b Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 4 Apr 2017 02:00:10 -0700 Subject: [PATCH 14/16] FFmpeg: Fix overflow and general issues with audio encoding --- CHANGES | 1 + src/feature/ffmpeg/ffmpeg-encoder.c | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index cdb0889eb..f41c98a8c 100644 --- a/CHANGES +++ b/CHANGES @@ -34,6 +34,7 @@ Bugfixes: - SDL: Fix game crash check - SDL: Fix race condition with audio thread when starting - GB: Fix flickering when screen is strobed quickly + - FFmpeg: Fix overflow and general issues with audio encoding Misc: - SDL: Remove scancode key input - GBA Video: Clean up unused timers diff --git a/src/feature/ffmpeg/ffmpeg-encoder.c b/src/feature/ffmpeg/ffmpeg-encoder.c index a902d6abf..4f0afef53 100644 --- a/src/feature/ffmpeg/ffmpeg-encoder.c +++ b/src/feature/ffmpeg/ffmpeg-encoder.c @@ -389,27 +389,27 @@ void _ffmpegPostAudioFrame(struct mAVStream* stream, int16_t left, int16_t right encoder->audioBuffer[encoder->currentAudioSample * 2] = left; encoder->audioBuffer[encoder->currentAudioSample * 2 + 1] = right; - ++encoder->currentAudioFrame; ++encoder->currentAudioSample; - if ((encoder->currentAudioSample * 4) < encoder->audioBufferSize) { + if (encoder->currentAudioSample * 4 < encoder->audioBufferSize) { return; } int channelSize = 2 * av_get_bytes_per_sample(encoder->audio->sample_fmt); - avresample_convert(encoder->resampleContext, - 0, 0, 0, - (uint8_t**) &encoder->audioBuffer, 0, encoder->audioBufferSize / 4); + avresample_convert(encoder->resampleContext, 0, 0, 0, + (uint8_t**) &encoder->audioBuffer, 0, encoder->audioBufferSize / 4); + + encoder->currentAudioSample = 0; if (avresample_available(encoder->resampleContext) < encoder->audioFrame->nb_samples) { return; } #if LIBAVCODEC_VERSION_MAJOR >= 55 av_frame_make_writable(encoder->audioFrame); #endif - avresample_read(encoder->resampleContext, encoder->audioFrame->data, encoder->postaudioBufferSize / channelSize); + int samples = avresample_read(encoder->resampleContext, encoder->audioFrame->data, encoder->postaudioBufferSize / channelSize); - encoder->audioFrame->pts = av_rescale_q(encoder->currentAudioFrame - encoder->currentAudioSample, encoder->audio->time_base, encoder->audioStream->time_base); - encoder->currentAudioSample = 0; + encoder->audioFrame->pts = av_rescale_q(encoder->currentAudioFrame, encoder->audio->time_base, encoder->audioStream->time_base); + encoder->currentAudioFrame += samples; AVPacket packet; av_init_packet(&packet); From d9b645b2098cc4ccb5662d0705d3f3a3d37b7486 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 4 Apr 2017 02:01:06 -0700 Subject: [PATCH 15/16] FFmpeg: Return false if a file fails to open --- CHANGES | 1 + src/feature/ffmpeg/ffmpeg-encoder.c | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index f41c98a8c..ffdeb47c1 100644 --- a/CHANGES +++ b/CHANGES @@ -81,6 +81,7 @@ Misc: - Qt: Remove audio thread - Qt: Remove audio buffer sizing in AudioProcessorQt - Qt: Re-enable QtMultimedia on Windows + - FFmpeg: Return false if a file fails to open 0.5.2: (2016-12-31) Bugfixes: diff --git a/src/feature/ffmpeg/ffmpeg-encoder.c b/src/feature/ffmpeg/ffmpeg-encoder.c index 4f0afef53..41e5d0877 100644 --- a/src/feature/ffmpeg/ffmpeg-encoder.c +++ b/src/feature/ffmpeg/ffmpeg-encoder.c @@ -320,7 +320,9 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) { avcodec_parameters_from_context(encoder->videoStream->codecpar, encoder->video); #endif - avio_open(&encoder->context->pb, outfile, AVIO_FLAG_WRITE); + if (avio_open(&encoder->context->pb, outfile, AVIO_FLAG_WRITE) < 0) { + return false; + } return avformat_write_header(encoder->context, 0) >= 0; } From 6a77d2aaeafd999f2b7258db70f377d44305ef08 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 4 Apr 2017 02:02:29 -0700 Subject: [PATCH 16/16] FFmpeg: Force MP4 files to YUV420P --- CHANGES | 1 + src/feature/ffmpeg/ffmpeg-encoder.c | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGES b/CHANGES index ffdeb47c1..c7080c30d 100644 --- a/CHANGES +++ b/CHANGES @@ -82,6 +82,7 @@ Misc: - Qt: Remove audio buffer sizing in AudioProcessorQt - Qt: Re-enable QtMultimedia on Windows - FFmpeg: Return false if a file fails to open + - FFmpeg: Force MP4 files to YUV420P 0.5.2: (2016-12-31) Bugfixes: diff --git a/src/feature/ffmpeg/ffmpeg-encoder.c b/src/feature/ffmpeg/ffmpeg-encoder.c index 41e5d0877..a7a206788 100644 --- a/src/feature/ffmpeg/ffmpeg-encoder.c +++ b/src/feature/ffmpeg/ffmpeg-encoder.c @@ -304,6 +304,14 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) { } av_opt_set(encoder->video->priv_data, "tune", "zerolatency", 0); } + + if (encoder->video->codec->id == AV_CODEC_ID_H264 && + (strcasecmp(encoder->containerFormat, "mp4") || + strcasecmp(encoder->containerFormat, "m4v") || + strcasecmp(encoder->containerFormat, "mov"))) { + // QuickTime and a few other things require YUV420 + encoder->video->pix_fmt = AV_PIX_FMT_YUV420P; + } avcodec_open2(encoder->video, vcodec, 0); #if LIBAVCODEC_VERSION_MAJOR >= 55 encoder->videoFrame = av_frame_alloc();