mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' (early part) into medusa
This commit is contained in:
commit
6b84383d1a
5
CHANGES
5
CHANGES
|
@ -60,6 +60,8 @@ Emulation fixes:
|
|||
- GB Memory: Better emulate 0xFEA0 region on DMG, MGB and AGB
|
||||
- GB Printer: Reset printer buffer index after printing
|
||||
- GB Video: Fix mode 0 window edge case (fixes mgba.io/i/1519)
|
||||
- GBA Audio: Fix channel 4 aliasing (fixes mgba.io/i/1265)
|
||||
- GB Audio: Improve channel 4 supersampling
|
||||
Other fixes:
|
||||
- Qt: Fix some Qt display driver race conditions
|
||||
- Core: Improved lockstep driver reliability (Le Hoang Quyen)
|
||||
|
@ -78,6 +80,7 @@ Other fixes:
|
|||
- Qt: Only show emulator restart warning once per settings saving
|
||||
- Qt: Improve cheat view UX
|
||||
- GB: Fix SGB controller incrementing (fixes mgba.io/i/1104)
|
||||
- FFmpeg: Drain recording buffers
|
||||
Misc:
|
||||
- GBA Savedata: EEPROM performance fixes
|
||||
- GBA Savedata: Automatically map 1Mbit Flash files as 1Mbit Flash
|
||||
|
@ -105,6 +108,8 @@ Misc:
|
|||
- Switch: Support file associations
|
||||
- Qt: Show error message if file failed to load
|
||||
- Qt: Scale pixel color values to full range (fixes mgba.io/i/1511)
|
||||
- Qt, OpenGL: Disable integer scaling for dimensions that don't fit
|
||||
- Feature: Switch from ImageMagick to FFmpeg for GIF generation
|
||||
|
||||
0.7.2: (2019-05-25)
|
||||
Emulation fixes:
|
||||
|
|
|
@ -36,7 +36,6 @@ if(NOT LIBMGBA_ONLY)
|
|||
set(USE_MINIZIP ON CACHE BOOL "Whether or not to enable external minizip support")
|
||||
set(USE_PNG ON CACHE BOOL "Whether or not to enable PNG support")
|
||||
set(USE_LIBZIP ON CACHE BOOL "Whether or not to enable LIBZIP support")
|
||||
set(USE_MAGICK ON CACHE BOOL "Whether or not to enable ImageMagick support")
|
||||
set(USE_SQLITE3 ON CACHE BOOL "Whether or not to enable SQLite3 support")
|
||||
set(USE_ELF ON CACHE BOOL "Whether or not to enable ELF support")
|
||||
set(M_CORE_GBA ON CACHE BOOL "Build Game Boy Advance core")
|
||||
|
@ -467,12 +466,11 @@ set(WANT_PNG ${USE_PNG})
|
|||
set(WANT_SQLITE3 ${USE_SQLITE3})
|
||||
set(USE_CMOCKA ${BUILD_SUITE})
|
||||
|
||||
find_feature(USE_FFMPEG "libavcodec;libavformat;libavutil;libswscale")
|
||||
find_feature(USE_FFMPEG "libavcodec;libavfilter;libavformat;libavutil;libswscale")
|
||||
find_feature(USE_ZLIB "ZLIB")
|
||||
find_feature(USE_MINIZIP "minizip")
|
||||
find_feature(USE_PNG "PNG")
|
||||
find_feature(USE_LIBZIP "libzip")
|
||||
find_feature(USE_MAGICK "MagickWand")
|
||||
find_feature(USE_EPOXY "epoxy")
|
||||
find_feature(USE_CMOCKA "cmocka")
|
||||
find_feature(USE_SQLITE3 "sqlite3")
|
||||
|
@ -531,15 +529,20 @@ if(USE_FFMPEG)
|
|||
list(APPEND FEATURES LIBAVRESAMPLE)
|
||||
list(APPEND FEATURES LIBAV)
|
||||
endif()
|
||||
include_directories(AFTER ${LIBAVCODEC_INCLUDE_DIRS} ${LIBAVFORMAT_INCLUDE_DIRS} ${LIBAVRESAMPLE_INCLUDE_DIRS} ${LIBAVUTIL_INCLUDE_DIRS} ${LIBSWRESAMPLE_INCLUDE_DIRS} ${LIBSWSCALE_INCLUDE_DIRS})
|
||||
link_directories(${LIBAVCODEC_LIBRARY_DIRS} ${LIBAVFORMAT_LIBRARY_DIRS} ${LIBAVRESAMPLE_LIBRARY_DIRS} ${LIBAVUTIL_LIBRARY_DIRS} ${LIBSWRESAMPLE_LIBRARY_DIRS} ${LIBSWSCALE_LIBRARY_DIRS})
|
||||
include_directories(AFTER ${LIBAVCODEC_INCLUDE_DIRS} ${LIBAVFILTER_INCLUDE_DIRS} ${LIBAVFORMAT_INCLUDE_DIRS} ${LIBAVRESAMPLE_INCLUDE_DIRS} ${LIBAVUTIL_INCLUDE_DIRS} ${LIBSWRESAMPLE_INCLUDE_DIRS} ${LIBSWSCALE_INCLUDE_DIRS})
|
||||
link_directories(${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")
|
||||
string(REGEX MATCH "^[0-9]+" LIBAVCODEC_VERSION_MAJOR ${libavcodec_VERSION})
|
||||
string(REGEX MATCH "^[0-9]+" LIBAVFILTER_VERSION_MAJOR ${libavfilter_VERSION})
|
||||
string(REGEX MATCH "^[0-9]+" LIBAVFORMAT_VERSION_MAJOR ${libavformat_VERSION})
|
||||
string(REGEX MATCH "^[0-9]+" LIBAVUTIL_VERSION_MAJOR ${libavutil_VERSION})
|
||||
string(REGEX MATCH "^[0-9]+" LIBSWSCALE_VERSION_MAJOR ${libswscale_VERSION})
|
||||
list(APPEND DEPENDENCY_LIB ${LIBAVCODEC_LIBRARIES} ${LIBAVFORMAT_LIBRARIES} ${LIBAVRESAMPLE_LIBRARIES} ${LIBAVUTIL_LIBRARIES} ${LIBSWSCALE_LIBRARIES} ${LIBSWRESAMPLE_LIBRARIES})
|
||||
list(APPEND DEPENDENCY_LIB ${LIBAVCODEC_LIBRARIES} ${LIBAVFILTER_LIBRARIES} ${LIBAVFORMAT_LIBRARIES} ${LIBAVRESAMPLE_LIBRARIES} ${LIBAVUTIL_LIBRARIES} ${LIBSWSCALE_LIBRARIES} ${LIBSWRESAMPLE_LIBRARIES})
|
||||
if(WIN32)
|
||||
list(APPEND DEPENDENCY_LIB bcrypt)
|
||||
endif()
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavcodec${LIBAVCODEC_VERSION_MAJOR}|libavcodec-extra-${LIBAVCODEC_VERSION_MAJOR}|libavcodec-ffmpeg${LIBAVCODEC_VERSION_MAJOR}|libavcodec-ffmpeg-extra${LIBAVCODEC_VERSION_MAJOR}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavfilter${LIBAVFILTER_VERSION_MAJOR}|libavfilter-ffmpeg${LIBAVFILTER_VERSION_MAJOR}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavformat${LIBAVFORMAT_VERSION_MAJOR}|libavformat-ffmpeg${LIBAVFORMAT_VERSION_MAJOR}")
|
||||
if(USE_LIBSWRESAMPLE)
|
||||
string(REGEX MATCH "^[0-9]+" LIBSWRESAMPLE_VERSION_MAJOR ${libswresample_VERSION})
|
||||
|
@ -559,29 +562,6 @@ endif()
|
|||
|
||||
list(APPEND THIRD_PARTY_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/blip_buf/blip_buf.c")
|
||||
|
||||
if(USE_MAGICK)
|
||||
list(APPEND FEATURES MAGICK)
|
||||
include_directories(AFTER ${MAGICKWAND_INCLUDE_DIRS})
|
||||
link_directories(${MAGICKWAND_LIBRARY_DIRS})
|
||||
list(APPEND FEATURE_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/feature/imagemagick/imagemagick-gif-encoder.c")
|
||||
list(APPEND DEPENDENCY_LIB ${MAGICKWAND_LIBRARIES})
|
||||
string(REGEX MATCH "^[0-9]+\\.[0-9]+" MAGICKWAND_VERSION_PARTIAL ${MagickWand_VERSION})
|
||||
string(REGEX MATCH "^[0-9]+" MAGICKWAND_VERSION_MAJOR ${MagickWand_VERSION})
|
||||
if(${MAGICKWAND_VERSION_PARTIAL} STREQUAL "6.7")
|
||||
set(MAGICKWAND_DEB_VERSION "5")
|
||||
elseif(${MagickWand_VERSION} STREQUAL "6.9.10")
|
||||
set(MAGICKWAND_DEB_VERSION "-6.q16-6")
|
||||
elseif(${MagickWand_VERSION} STREQUAL "6.9.7")
|
||||
set(MAGICKWAND_DEB_VERSION "-6.q16-3")
|
||||
else()
|
||||
set(MAGICKWAND_DEB_VERSION "-6.q16-2")
|
||||
endif()
|
||||
list(APPEND FEATURE_DEFINES MAGICKWAND_VERSION_MAJOR=${MAGICKWAND_VERSION_MAJOR})
|
||||
list(APPEND FEATURE_FLAGS ${MAGICKWAND_CFLAGS_OTHER})
|
||||
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libmagickwand${MAGICKWAND_DEB_VERSION}")
|
||||
endif()
|
||||
|
||||
if(WANT_ZLIB AND NOT USE_ZLIB)
|
||||
set(SKIP_INSTALL_ALL ON)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/zlib zlib EXCLUDE_FROM_ALL)
|
||||
|
@ -1142,9 +1122,10 @@ if(DISTBUILD)
|
|||
set(CPACK_ARCHIVE_COMPONENT_INSTALL ON)
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo" AND BUILD_SHARED)
|
||||
if(NOT APPLE)
|
||||
add_custom_command(TARGET ${BINARY_NAME} POST_BUILD COMMAND "${OBJCOPY}" --only-keep-debug "$<TARGET_FILE:${BINARY_NAME}>" "$<TARGET_FILE:${BINARY_NAME}>.dSYM")
|
||||
add_custom_command(TARGET ${BINARY_NAME} POST_BUILD COMMAND "${STRIP}" -S "$<TARGET_FILE:${BINARY_NAME}>")
|
||||
install(FILES "$<TARGET_FILE:${BINARY_NAME}>.dSYM" DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME}-dbg)
|
||||
add_custom_command(TARGET ${BINARY_NAME} POST_BUILD COMMAND "${OBJCOPY}" --only-keep-debug "$<TARGET_FILE:${BINARY_NAME}>" "$<TARGET_FILE:${BINARY_NAME}>.debug")
|
||||
add_custom_command(TARGET ${BINARY_NAME} POST_BUILD COMMAND "${STRIP}" "$<TARGET_FILE:${BINARY_NAME}>")
|
||||
add_custom_command(TARGET ${BINARY_NAME} POST_BUILD COMMAND "${OBJCOPY}" --add-gnu-debuglink "$<TARGET_FILE:${BINARY_NAME}>.debug" "$<TARGET_FILE:${BINARY_NAME}>")
|
||||
install(FILES "$<TARGET_FILE:${BINARY_NAME}>.debug" DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME}-dbg)
|
||||
endif()
|
||||
endif()
|
||||
if(APPLE)
|
||||
|
@ -1265,8 +1246,7 @@ if(NOT QUIET AND NOT LIBMGBA_ONLY)
|
|||
message(STATUS " CLI debugger: ${USE_EDITLINE}")
|
||||
endif()
|
||||
message(STATUS " GDB stub: ${USE_GDB_STUB}")
|
||||
message(STATUS " Video recording: ${USE_FFMPEG}")
|
||||
message(STATUS " GIF recording: ${USE_MAGICK}")
|
||||
message(STATUS " GIF/Video recording: ${USE_FFMPEG}")
|
||||
message(STATUS " Screenshot/advanced savestate support: ${USE_PNG}")
|
||||
message(STATUS " ZIP support: ${SUMMARY_ZIP}")
|
||||
message(STATUS " 7-Zip support: ${USE_LZMA}")
|
||||
|
|
|
@ -163,7 +163,7 @@ This will build and install medusa into `/usr/bin` and `/usr/lib`. Dependencies
|
|||
|
||||
If you are on macOS, the steps are a little different. Assuming you are using the homebrew package manager, the recommended commands to obtain the dependencies and build are:
|
||||
|
||||
brew install cmake ffmpeg imagemagick libzip qt5 sdl2 libedit pkg-config
|
||||
brew install cmake ffmpeg libzip qt5 sdl2 libedit pkg-config
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_PREFIX_PATH=`brew --prefix qt5` ..
|
||||
|
@ -177,11 +177,11 @@ To build on Windows for development, using MSYS2 is recommended. Follow the inst
|
|||
|
||||
For x86 (32 bit) builds:
|
||||
|
||||
pacman -Sy --needed base-devel git mingw-w64-i686-{cmake,ffmpeg,gcc,gdb,imagemagick,libelf,libepoxy,libzip,pkg-config,qt5,SDL2,ntldd-git}
|
||||
pacman -Sy --needed base-devel git mingw-w64-i686-{cmake,ffmpeg,gcc,gdb,libelf,libepoxy,libzip,pkg-config,qt5,SDL2,ntldd-git}
|
||||
|
||||
For x86_64 (64 bit) builds:
|
||||
|
||||
pacman -Sy --needed base-devel git mingw-w64-x86_64-{cmake,ffmpeg,gcc,gdb,imagemagick,libelf,libepoxy,libzip,pkg-config,qt5,SDL2,ntldd-git}
|
||||
pacman -Sy --needed base-devel git mingw-w64-x86_64-{cmake,ffmpeg,gcc,gdb,libelf,libepoxy,libzip,pkg-config,qt5,SDL2,ntldd-git}
|
||||
|
||||
Check out the source code by running this command:
|
||||
|
||||
|
|
|
@ -99,10 +99,6 @@
|
|||
#cmakedefine USE_LZMA
|
||||
#endif
|
||||
|
||||
#ifndef USE_MAGICK
|
||||
#cmakedefine USE_MAGICK
|
||||
#endif
|
||||
|
||||
#ifndef USE_MINIZIP
|
||||
#cmakedefine USE_MINIZIP
|
||||
#endif
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
#include <libavcodec/version.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
|
||||
#include <libavfilter/buffersink.h>
|
||||
#include <libavfilter/buffersrc.h>
|
||||
|
||||
#include <libavutil/version.h>
|
||||
#if LIBAVUTIL_VERSION_MAJOR >= 53
|
||||
#include <libavutil/buffer.h>
|
||||
|
@ -31,12 +34,17 @@ static void _ffmpegPostAudioFrame(struct mAVStream*, int16_t left, int16_t right
|
|||
static void _ffmpegSetVideoDimensions(struct mAVStream*, unsigned width, unsigned height);
|
||||
static void _ffmpegSetVideoFrameRate(struct mAVStream*, unsigned numerator, unsigned denominator);
|
||||
|
||||
static bool _ffmpegWriteAudioFrame(struct FFmpegEncoder* encoder, struct AVFrame* audioFrame);
|
||||
static bool _ffmpegWriteVideoFrame(struct FFmpegEncoder* encoder, struct AVFrame* videoFrame);
|
||||
|
||||
enum {
|
||||
PREFERRED_SAMPLE_RATE = 0x8000
|
||||
};
|
||||
|
||||
void FFmpegEncoderInit(struct FFmpegEncoder* encoder) {
|
||||
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
|
||||
av_register_all();
|
||||
#endif
|
||||
|
||||
encoder->d.videoDimensionsChanged = _ffmpegSetVideoDimensions;
|
||||
encoder->d.postVideoFrame = _ffmpegPostVideoFrame;
|
||||
|
@ -48,13 +56,29 @@ void FFmpegEncoderInit(struct FFmpegEncoder* encoder) {
|
|||
encoder->videoCodec = NULL;
|
||||
encoder->containerFormat = NULL;
|
||||
FFmpegEncoderSetAudio(encoder, "flac", 0);
|
||||
FFmpegEncoderSetVideo(encoder, "png", 0);
|
||||
FFmpegEncoderSetVideo(encoder, "libx264", 0, 0);
|
||||
FFmpegEncoderSetContainer(encoder, "matroska");
|
||||
FFmpegEncoderSetDimensions(encoder, GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS);
|
||||
encoder->iwidth = GBA_VIDEO_HORIZONTAL_PIXELS;
|
||||
encoder->iheight = GBA_VIDEO_VERTICAL_PIXELS;
|
||||
encoder->frameCycles = VIDEO_TOTAL_LENGTH;
|
||||
encoder->cycles = GBA_ARM7TDMI_FREQUENCY;
|
||||
encoder->frameskip = 1;
|
||||
encoder->skipResidue = 0;
|
||||
encoder->ipixFormat =
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
AV_PIX_FMT_RGB565;
|
||||
#else
|
||||
AV_PIX_FMT_BGR555;
|
||||
#endif
|
||||
#else
|
||||
#ifndef USE_LIBAV
|
||||
AV_PIX_FMT_0BGR32;
|
||||
#else
|
||||
AV_PIX_FMT_BGR32;
|
||||
#endif
|
||||
#endif
|
||||
encoder->resampleContext = NULL;
|
||||
encoder->absf = NULL;
|
||||
encoder->context = NULL;
|
||||
|
@ -67,6 +91,15 @@ void FFmpegEncoderInit(struct FFmpegEncoder* encoder) {
|
|||
encoder->video = NULL;
|
||||
encoder->videoStream = NULL;
|
||||
encoder->videoFrame = NULL;
|
||||
encoder->graph = NULL;
|
||||
encoder->source = NULL;
|
||||
encoder->sink = NULL;
|
||||
encoder->sinkFrame = NULL;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < FFMPEG_FILTERS_MAX; ++i) {
|
||||
encoder->filters[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool FFmpegEncoderSetAudio(struct FFmpegEncoder* encoder, const char* acodec, unsigned abr) {
|
||||
|
@ -131,7 +164,7 @@ bool FFmpegEncoderSetAudio(struct FFmpegEncoder* encoder, const char* acodec, un
|
|||
return true;
|
||||
}
|
||||
|
||||
bool FFmpegEncoderSetVideo(struct FFmpegEncoder* encoder, const char* vcodec, unsigned vbr) {
|
||||
bool FFmpegEncoderSetVideo(struct FFmpegEncoder* encoder, const char* vcodec, unsigned vbr, int frameskip) {
|
||||
static const struct {
|
||||
enum AVPixelFormat format;
|
||||
int priority;
|
||||
|
@ -150,7 +183,8 @@ bool FFmpegEncoderSetVideo(struct FFmpegEncoder* encoder, const char* vcodec, un
|
|||
#endif
|
||||
{ AV_PIX_FMT_YUV422P, 4 },
|
||||
{ AV_PIX_FMT_YUV444P, 5 },
|
||||
{ AV_PIX_FMT_YUV420P, 6 }
|
||||
{ AV_PIX_FMT_YUV420P, 6 },
|
||||
{ AV_PIX_FMT_PAL8, 7 },
|
||||
};
|
||||
|
||||
if (!vcodec) {
|
||||
|
@ -180,6 +214,7 @@ bool FFmpegEncoderSetVideo(struct FFmpegEncoder* encoder, const char* vcodec, un
|
|||
}
|
||||
encoder->videoCodec = vcodec;
|
||||
encoder->videoBitrate = vbr;
|
||||
encoder->frameskip = frameskip + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -227,6 +262,7 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
|
|||
encoder->currentAudioSample = 0;
|
||||
encoder->currentAudioFrame = 0;
|
||||
encoder->currentVideoFrame = 0;
|
||||
encoder->skipResidue = 0;
|
||||
|
||||
AVOutputFormat* oformat = av_guess_format(encoder->containerFormat, 0, 0);
|
||||
#ifndef USE_LIBAV
|
||||
|
@ -326,8 +362,8 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
|
|||
encoder->video->bit_rate = encoder->videoBitrate;
|
||||
encoder->video->width = encoder->width;
|
||||
encoder->video->height = encoder->height;
|
||||
encoder->video->time_base = (AVRational) { encoder->frameCycles, encoder->cycles };
|
||||
encoder->video->framerate = (AVRational) { encoder->cycles, encoder->frameCycles };
|
||||
encoder->video->time_base = (AVRational) { encoder->frameCycles * encoder->frameskip, encoder->cycles };
|
||||
encoder->video->framerate = (AVRational) { encoder->cycles, encoder->frameCycles * encoder->frameskip };
|
||||
encoder->video->pix_fmt = encoder->pixFormat;
|
||||
encoder->video->gop_size = 60;
|
||||
encoder->video->max_b_frames = 3;
|
||||
|
@ -366,6 +402,54 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
|
|||
encoder->video->pix_fmt = AV_PIX_FMT_YUV444P;
|
||||
}
|
||||
|
||||
if (encoder->pixFormat == AV_PIX_FMT_PAL8) {
|
||||
encoder->graph = avfilter_graph_alloc();
|
||||
|
||||
const struct AVFilter* source = avfilter_get_by_name("buffer");
|
||||
const struct AVFilter* sink = avfilter_get_by_name("buffersink");
|
||||
const struct AVFilter* split = avfilter_get_by_name("split");
|
||||
const struct AVFilter* palettegen = avfilter_get_by_name("palettegen");
|
||||
const struct AVFilter* paletteuse = avfilter_get_by_name("paletteuse");
|
||||
|
||||
if (!source || !sink || !split || !palettegen || !paletteuse || !encoder->graph) {
|
||||
FFmpegEncoderClose(encoder);
|
||||
return false;
|
||||
}
|
||||
|
||||
char args[256];
|
||||
snprintf(args, sizeof(args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d",
|
||||
encoder->video->width, encoder->video->height, encoder->ipixFormat,
|
||||
encoder->video->time_base.num, encoder->video->time_base.den);
|
||||
|
||||
int res = 0;
|
||||
res |= avfilter_graph_create_filter(&encoder->source, source, NULL, args, NULL, encoder->graph);
|
||||
res |= avfilter_graph_create_filter(&encoder->sink, sink, NULL, NULL, NULL, encoder->graph);
|
||||
res |= avfilter_graph_create_filter(&encoder->filters[0], split, NULL, NULL, NULL, encoder->graph);
|
||||
res |= avfilter_graph_create_filter(&encoder->filters[1], palettegen, NULL, "reserve_transparent=off", NULL, encoder->graph);
|
||||
res |= avfilter_graph_create_filter(&encoder->filters[2], paletteuse, NULL, "dither=none", NULL, encoder->graph);
|
||||
if (res < 0) {
|
||||
FFmpegEncoderClose(encoder);
|
||||
return false;
|
||||
}
|
||||
|
||||
res = 0;
|
||||
res |= avfilter_link(encoder->source, 0, encoder->filters[0], 0);
|
||||
res |= avfilter_link(encoder->filters[0], 0, encoder->filters[1], 0);
|
||||
res |= avfilter_link(encoder->filters[0], 1, encoder->filters[2], 0);
|
||||
res |= avfilter_link(encoder->filters[1], 0, encoder->filters[2], 1);
|
||||
res |= avfilter_link(encoder->filters[2], 0, encoder->sink, 0);
|
||||
if (res < 0 || avfilter_graph_config(encoder->graph, NULL) < 0) {
|
||||
FFmpegEncoderClose(encoder);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if LIBAVCODEC_VERSION_MAJOR >= 55
|
||||
encoder->sinkFrame = av_frame_alloc();
|
||||
#else
|
||||
encoder->sinkFrame = avcodec_alloc_frame();
|
||||
#endif
|
||||
}
|
||||
|
||||
if (avcodec_open2(encoder->video, vcodec, 0) < 0) {
|
||||
FFmpegEncoderClose(encoder);
|
||||
return false;
|
||||
|
@ -375,12 +459,12 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
|
|||
#else
|
||||
encoder->videoFrame = avcodec_alloc_frame();
|
||||
#endif
|
||||
encoder->videoFrame->format = encoder->video->pix_fmt;
|
||||
encoder->videoFrame->format = encoder->video->pix_fmt != AV_PIX_FMT_PAL8 ? encoder->video->pix_fmt : encoder->ipixFormat;
|
||||
encoder->videoFrame->width = encoder->video->width;
|
||||
encoder->videoFrame->height = encoder->video->height;
|
||||
encoder->videoFrame->pts = 0;
|
||||
_ffmpegSetVideoDimensions(&encoder->d, encoder->iwidth, encoder->iheight);
|
||||
av_image_alloc(encoder->videoFrame->data, encoder->videoFrame->linesize, encoder->video->width, encoder->video->height, encoder->video->pix_fmt, 32);
|
||||
av_image_alloc(encoder->videoFrame->data, encoder->videoFrame->linesize, encoder->videoFrame->width, encoder->videoFrame->height, encoder->videoFrame->format, 32);
|
||||
#ifdef FFMPEG_USE_CODECPAR
|
||||
avcodec_parameters_from_context(encoder->videoStream->codecpar, encoder->video);
|
||||
#endif
|
||||
|
@ -394,6 +478,33 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
|
|||
}
|
||||
|
||||
void FFmpegEncoderClose(struct FFmpegEncoder* encoder) {
|
||||
if (encoder->audio) {
|
||||
while (true) {
|
||||
if (!_ffmpegWriteAudioFrame(encoder, NULL)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (encoder->video) {
|
||||
if (encoder->graph) {
|
||||
if (av_buffersrc_add_frame(encoder->source, NULL) >= 0) {
|
||||
while (true) {
|
||||
int res = av_buffersink_get_frame(encoder->sink, encoder->sinkFrame);
|
||||
if (res < 0) {
|
||||
break;
|
||||
}
|
||||
_ffmpegWriteVideoFrame(encoder, encoder->sinkFrame);
|
||||
av_frame_unref(encoder->sinkFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (true) {
|
||||
if (!_ffmpegWriteVideoFrame(encoder, NULL)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (encoder->context && encoder->context->pb) {
|
||||
av_write_trailer(encoder->context);
|
||||
avio_close(encoder->context->pb);
|
||||
|
@ -446,6 +557,15 @@ void FFmpegEncoderClose(struct FFmpegEncoder* encoder) {
|
|||
#endif
|
||||
}
|
||||
|
||||
if (encoder->sinkFrame) {
|
||||
#if LIBAVCODEC_VERSION_MAJOR >= 55
|
||||
av_frame_free(&encoder->sinkFrame);
|
||||
#else
|
||||
avcodec_free_frame(&encoder->sinkFrame);
|
||||
#endif
|
||||
encoder->sinkFrame = NULL;
|
||||
}
|
||||
|
||||
if (encoder->video) {
|
||||
avcodec_close(encoder->video);
|
||||
encoder->video = NULL;
|
||||
|
@ -456,6 +576,18 @@ void FFmpegEncoderClose(struct FFmpegEncoder* encoder) {
|
|||
encoder->scaleContext = NULL;
|
||||
}
|
||||
|
||||
if (encoder->graph) {
|
||||
avfilter_graph_free(&encoder->graph);
|
||||
encoder->graph = NULL;
|
||||
encoder->source = NULL;
|
||||
encoder->sink = NULL;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < FFMPEG_FILTERS_MAX; ++i) {
|
||||
encoder->filters[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (encoder->context) {
|
||||
avformat_free_context(encoder->context);
|
||||
encoder->context = NULL;
|
||||
|
@ -514,6 +646,10 @@ void _ffmpegPostAudioFrame(struct mAVStream* stream, int16_t left, int16_t right
|
|||
encoder->audioFrame->pts = encoder->currentAudioFrame;
|
||||
encoder->currentAudioFrame += samples;
|
||||
|
||||
_ffmpegWriteAudioFrame(encoder, encoder->audioFrame);
|
||||
}
|
||||
|
||||
bool _ffmpegWriteAudioFrame(struct FFmpegEncoder* encoder, struct AVFrame* audioFrame) {
|
||||
AVPacket packet;
|
||||
av_init_packet(&packet);
|
||||
packet.data = 0;
|
||||
|
@ -521,13 +657,13 @@ void _ffmpegPostAudioFrame(struct mAVStream* stream, int16_t left, int16_t right
|
|||
|
||||
int gotData;
|
||||
#ifdef FFMPEG_USE_PACKETS
|
||||
avcodec_send_frame(encoder->audio, encoder->audioFrame);
|
||||
avcodec_send_frame(encoder->audio, audioFrame);
|
||||
gotData = avcodec_receive_packet(encoder->audio, &packet);
|
||||
gotData = (gotData == 0) && packet.size;
|
||||
#else
|
||||
avcodec_encode_audio2(encoder->audio, &packet, encoder->audioFrame, &gotData);
|
||||
avcodec_encode_audio2(encoder->audio, &packet, audioFrame, &gotData);
|
||||
#endif
|
||||
packet.pts = av_rescale_q(encoder->audioFrame->pts, encoder->audio->time_base, encoder->audioStream->time_base);
|
||||
packet.pts = av_rescale_q(packet.pts, encoder->audio->time_base, encoder->audioStream->time_base);
|
||||
packet.dts = packet.pts;
|
||||
|
||||
if (gotData) {
|
||||
|
@ -570,6 +706,7 @@ void _ffmpegPostAudioFrame(struct mAVStream* stream, int16_t left, int16_t right
|
|||
#else
|
||||
av_free_packet(&packet);
|
||||
#endif
|
||||
return gotData;
|
||||
}
|
||||
|
||||
void _ffmpegPostVideoFrame(struct mAVStream* stream, const color_t* pixels, size_t stride) {
|
||||
|
@ -577,13 +714,12 @@ void _ffmpegPostVideoFrame(struct mAVStream* stream, const color_t* pixels, size
|
|||
if (!encoder->context || !encoder->videoCodec) {
|
||||
return;
|
||||
}
|
||||
encoder->skipResidue = (encoder->skipResidue + 1) % encoder->frameskip;
|
||||
if (encoder->skipResidue) {
|
||||
return;
|
||||
}
|
||||
stride *= BYTES_PER_PIXEL;
|
||||
|
||||
AVPacket packet;
|
||||
|
||||
av_init_packet(&packet);
|
||||
packet.data = 0;
|
||||
packet.size = 0;
|
||||
#if LIBAVCODEC_VERSION_MAJOR >= 55
|
||||
av_frame_make_writable(encoder->videoFrame);
|
||||
#endif
|
||||
|
@ -592,14 +728,37 @@ void _ffmpegPostVideoFrame(struct mAVStream* stream, const color_t* pixels, size
|
|||
|
||||
sws_scale(encoder->scaleContext, (const uint8_t* const*) &pixels, (const int*) &stride, 0, encoder->iheight, encoder->videoFrame->data, encoder->videoFrame->linesize);
|
||||
|
||||
if (encoder->graph) {
|
||||
if (av_buffersrc_add_frame(encoder->source, encoder->videoFrame) < 0) {
|
||||
return;
|
||||
}
|
||||
while (true) {
|
||||
int res = av_buffersink_get_frame(encoder->sink, encoder->sinkFrame);
|
||||
if (res < 0) {
|
||||
break;
|
||||
}
|
||||
_ffmpegWriteVideoFrame(encoder, encoder->sinkFrame);
|
||||
av_frame_unref(encoder->sinkFrame);
|
||||
}
|
||||
} else {
|
||||
_ffmpegWriteVideoFrame(encoder, encoder->videoFrame);
|
||||
}
|
||||
}
|
||||
|
||||
bool _ffmpegWriteVideoFrame(struct FFmpegEncoder* encoder, struct AVFrame* videoFrame) {
|
||||
AVPacket packet;
|
||||
|
||||
av_init_packet(&packet);
|
||||
packet.data = 0;
|
||||
packet.size = 0;
|
||||
|
||||
int gotData;
|
||||
#ifdef FFMPEG_USE_PACKETS
|
||||
avcodec_send_frame(encoder->video, encoder->videoFrame);
|
||||
avcodec_send_frame(encoder->video, videoFrame);
|
||||
gotData = avcodec_receive_packet(encoder->video, &packet) == 0;
|
||||
#else
|
||||
avcodec_encode_video2(encoder->video, &packet, encoder->videoFrame, &gotData);
|
||||
avcodec_encode_video2(encoder->video, &packet, videoFrame, &gotData);
|
||||
#endif
|
||||
packet.pts = encoder->videoFrame->pts;
|
||||
if (gotData) {
|
||||
#ifndef FFMPEG_USE_PACKET_UNREF
|
||||
if (encoder->video->coded_frame->key_frame) {
|
||||
|
@ -614,6 +773,8 @@ void _ffmpegPostVideoFrame(struct mAVStream* stream, const color_t* pixels, size
|
|||
#else
|
||||
av_free_packet(&packet);
|
||||
#endif
|
||||
|
||||
return gotData;
|
||||
}
|
||||
|
||||
static void _ffmpegSetVideoDimensions(struct mAVStream* stream, unsigned width, unsigned height) {
|
||||
|
@ -626,21 +787,8 @@ static void _ffmpegSetVideoDimensions(struct mAVStream* stream, unsigned width,
|
|||
if (encoder->scaleContext) {
|
||||
sws_freeContext(encoder->scaleContext);
|
||||
}
|
||||
encoder->scaleContext = sws_getContext(encoder->iwidth, encoder->iheight,
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
AV_PIX_FMT_RGB565,
|
||||
#else
|
||||
AV_PIX_FMT_BGR555,
|
||||
#endif
|
||||
#else
|
||||
#ifndef USE_LIBAV
|
||||
AV_PIX_FMT_0BGR32,
|
||||
#else
|
||||
AV_PIX_FMT_BGR32,
|
||||
#endif
|
||||
#endif
|
||||
encoder->videoFrame->width, encoder->videoFrame->height, encoder->video->pix_fmt,
|
||||
encoder->scaleContext = sws_getContext(encoder->iwidth, encoder->iheight, encoder->ipixFormat,
|
||||
encoder->videoFrame->width, encoder->videoFrame->height, encoder->videoFrame->format,
|
||||
SWS_POINT, 0, 0, 0);
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,8 @@ CXX_GUARD_START
|
|||
#define FFMPEG_USE_PACKET_UNREF
|
||||
#endif
|
||||
|
||||
#define FFMPEG_FILTERS_MAX 4
|
||||
|
||||
struct FFmpegEncoder {
|
||||
struct mAVStream d;
|
||||
struct AVFormatContext* context;
|
||||
|
@ -70,6 +72,7 @@ struct FFmpegEncoder {
|
|||
|
||||
struct AVCodecContext* video;
|
||||
enum AVPixelFormat pixFormat;
|
||||
enum AVPixelFormat ipixFormat;
|
||||
struct AVFrame* videoFrame;
|
||||
int width;
|
||||
int height;
|
||||
|
@ -77,14 +80,22 @@ struct FFmpegEncoder {
|
|||
int iheight;
|
||||
unsigned frameCycles;
|
||||
unsigned cycles;
|
||||
int frameskip;
|
||||
int skipResidue;
|
||||
int64_t currentVideoFrame;
|
||||
struct SwsContext* scaleContext;
|
||||
struct AVStream* videoStream;
|
||||
|
||||
struct AVFilterGraph* graph;
|
||||
struct AVFilterContext* source;
|
||||
struct AVFilterContext* sink;
|
||||
struct AVFilterContext* filters[FFMPEG_FILTERS_MAX];
|
||||
struct AVFrame* sinkFrame;
|
||||
};
|
||||
|
||||
void FFmpegEncoderInit(struct FFmpegEncoder*);
|
||||
bool FFmpegEncoderSetAudio(struct FFmpegEncoder*, const char* acodec, unsigned abr);
|
||||
bool FFmpegEncoderSetVideo(struct FFmpegEncoder*, const char* vcodec, unsigned vbr);
|
||||
bool FFmpegEncoderSetVideo(struct FFmpegEncoder*, const char* vcodec, unsigned vbr, int frameskip);
|
||||
bool FFmpegEncoderSetContainer(struct FFmpegEncoder*, const char* container);
|
||||
void FFmpegEncoderSetDimensions(struct FFmpegEncoder*, int width, int height);
|
||||
bool FFmpegEncoderVerifyContainer(struct FFmpegEncoder*);
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
/* Copyright (c) 2013-2015 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 "imagemagick-gif-encoder.h"
|
||||
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
#include <mgba/gba/interface.h>
|
||||
#include <mgba-util/string.h>
|
||||
|
||||
static void _magickPostVideoFrame(struct mAVStream*, const color_t* pixels, size_t stride);
|
||||
static void _magickVideoDimensionsChanged(struct mAVStream*, unsigned width, unsigned height);
|
||||
static void _magickVideoFrameRateChanged(struct mAVStream*, unsigned numerator, unsigned denominator);
|
||||
|
||||
void ImageMagickGIFEncoderInit(struct ImageMagickGIFEncoder* encoder) {
|
||||
encoder->wand = 0;
|
||||
|
||||
encoder->d.videoDimensionsChanged = _magickVideoDimensionsChanged;
|
||||
encoder->d.postVideoFrame = _magickPostVideoFrame;
|
||||
encoder->d.postAudioFrame = 0;
|
||||
encoder->d.postAudioBuffer = 0;
|
||||
encoder->d.videoFrameRateChanged = _magickVideoFrameRateChanged;
|
||||
|
||||
encoder->frameskip = 2;
|
||||
encoder->delayMs = -1;
|
||||
|
||||
encoder->iwidth = GBA_VIDEO_HORIZONTAL_PIXELS;
|
||||
encoder->iheight = GBA_VIDEO_VERTICAL_PIXELS;
|
||||
encoder->numerator = VIDEO_TOTAL_LENGTH;
|
||||
encoder->denominator = GBA_ARM7TDMI_FREQUENCY;
|
||||
}
|
||||
|
||||
void ImageMagickGIFEncoderSetParams(struct ImageMagickGIFEncoder* encoder, int frameskip, int delayMs) {
|
||||
if (ImageMagickGIFEncoderIsOpen(encoder)) {
|
||||
return;
|
||||
}
|
||||
encoder->frameskip = frameskip;
|
||||
encoder->delayMs = delayMs;
|
||||
}
|
||||
|
||||
bool ImageMagickGIFEncoderOpen(struct ImageMagickGIFEncoder* encoder, const char* outfile) {
|
||||
MagickWandGenesis();
|
||||
encoder->wand = NewMagickWand();
|
||||
MagickSetImageFormat(encoder->wand, "GIF");
|
||||
MagickSetImageDispose(encoder->wand, PreviousDispose);
|
||||
encoder->outfile = strdup(outfile);
|
||||
encoder->frame = malloc(encoder->iwidth * encoder->iheight * 4);
|
||||
encoder->currentFrame = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ImageMagickGIFEncoderClose(struct ImageMagickGIFEncoder* encoder) {
|
||||
if (!encoder->wand) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MagickBooleanType success = MagickWriteImages(encoder->wand, encoder->outfile, MagickTrue);
|
||||
DestroyMagickWand(encoder->wand);
|
||||
encoder->wand = 0;
|
||||
free(encoder->outfile);
|
||||
free(encoder->frame);
|
||||
MagickWandTerminus();
|
||||
return success == MagickTrue;
|
||||
}
|
||||
|
||||
bool ImageMagickGIFEncoderIsOpen(struct ImageMagickGIFEncoder* encoder) {
|
||||
return !!encoder->wand;
|
||||
}
|
||||
|
||||
static void _magickPostVideoFrame(struct mAVStream* stream, const color_t* pixels, size_t stride) {
|
||||
struct ImageMagickGIFEncoder* encoder = (struct ImageMagickGIFEncoder*) stream;
|
||||
|
||||
if (encoder->currentFrame % (encoder->frameskip + 1)) {
|
||||
++encoder->currentFrame;
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t* p8 = (const uint8_t*) pixels;
|
||||
size_t row;
|
||||
for (row = 0; row < encoder->iheight; ++row) {
|
||||
memcpy(&encoder->frame[row * encoder->iwidth], &p8[row * 4 * stride], encoder->iwidth * 4);
|
||||
}
|
||||
|
||||
MagickConstituteImage(encoder->wand, encoder->iwidth, encoder->iheight, "RGBP", CharPixel, encoder->frame);
|
||||
uint64_t ts = encoder->currentFrame;
|
||||
uint64_t nts = encoder->currentFrame + encoder->frameskip + 1;
|
||||
if (encoder->delayMs >= 0) {
|
||||
ts *= encoder->delayMs;
|
||||
nts *= encoder->delayMs;
|
||||
ts /= 10;
|
||||
nts /= 10;
|
||||
} else {
|
||||
ts *= encoder->numerator * 100;
|
||||
nts *= encoder->numerator * 100;
|
||||
ts /= encoder->denominator;
|
||||
nts /= encoder->denominator;
|
||||
}
|
||||
MagickSetImageDelay(encoder->wand, nts - ts);
|
||||
++encoder->currentFrame;
|
||||
}
|
||||
|
||||
static void _magickVideoDimensionsChanged(struct mAVStream* stream, unsigned width, unsigned height) {
|
||||
struct ImageMagickGIFEncoder* encoder = (struct ImageMagickGIFEncoder*) stream;
|
||||
if (encoder->iwidth == width && encoder->iheight == height) {
|
||||
return;
|
||||
}
|
||||
if (width * height > encoder->iwidth * encoder->iheight) {
|
||||
free(encoder->frame);
|
||||
encoder->frame = malloc(width * height * 4);
|
||||
}
|
||||
encoder->iwidth = width;
|
||||
encoder->iheight = height;
|
||||
encoder->frame = malloc(encoder->iwidth * encoder->iheight * 4);
|
||||
}
|
||||
|
||||
static void _magickVideoFrameRateChanged(struct mAVStream* stream, unsigned numerator, unsigned denominator) {
|
||||
struct ImageMagickGIFEncoder* encoder = (struct ImageMagickGIFEncoder*) stream;
|
||||
encoder->numerator = numerator;
|
||||
encoder->denominator = denominator;
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
/* Copyright (c) 2013-2015 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 IMAGEMAGICK_GIF_ENCODER
|
||||
#define IMAGEMAGICK_GIF_ENCODER
|
||||
|
||||
#include <mgba-util/common.h>
|
||||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba/core/interface.h>
|
||||
|
||||
#if MAGICKWAND_VERSION_MAJOR >= 7
|
||||
#include <MagickWand/MagickWand.h>
|
||||
#else
|
||||
#include <wand/MagickWand.h>
|
||||
#endif
|
||||
|
||||
struct ImageMagickGIFEncoder {
|
||||
struct mAVStream d;
|
||||
MagickWand* wand;
|
||||
char* outfile;
|
||||
uint32_t* frame;
|
||||
|
||||
unsigned currentFrame;
|
||||
int frameskip;
|
||||
int delayMs;
|
||||
|
||||
unsigned iwidth;
|
||||
unsigned iheight;
|
||||
|
||||
unsigned numerator;
|
||||
unsigned denominator;
|
||||
};
|
||||
|
||||
void ImageMagickGIFEncoderInit(struct ImageMagickGIFEncoder*);
|
||||
void ImageMagickGIFEncoderSetParams(struct ImageMagickGIFEncoder* encoder, int frameskip, int delayMs);
|
||||
bool ImageMagickGIFEncoderOpen(struct ImageMagickGIFEncoder*, const char* outfile);
|
||||
bool ImageMagickGIFEncoderClose(struct ImageMagickGIFEncoder*);
|
||||
bool ImageMagickGIFEncoderIsOpen(struct ImageMagickGIFEncoder*);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
#endif
|
|
@ -37,7 +37,7 @@ static bool _updateSweep(struct GBAudioSquareChannel* sweep, bool initial);
|
|||
static void _updateSquareSample(struct GBAudioSquareChannel* ch);
|
||||
static int32_t _updateSquareChannel(struct GBAudioSquareChannel* ch);
|
||||
|
||||
static int8_t _coalesceNoiseChannel(struct GBAudioNoiseChannel* ch);
|
||||
static int16_t _coalesceNoiseChannel(struct GBAudioNoiseChannel* ch);
|
||||
|
||||
static void _updateFrame(struct mTiming* timing, void* user, uint32_t cyclesLate);
|
||||
static void _updateChannel1(struct mTiming* timing, void* user, uint32_t cyclesLate);
|
||||
|
@ -632,8 +632,11 @@ void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right) {
|
|||
}
|
||||
}
|
||||
|
||||
sampleLeft <<= 3;
|
||||
sampleRight <<= 3;
|
||||
|
||||
if (!audio->forceDisableCh[3]) {
|
||||
int8_t sample = _coalesceNoiseChannel(&audio->ch4);
|
||||
int16_t sample = audio->style == GB_AUDIO_GBA ? (audio->ch4.sample << 3) : _coalesceNoiseChannel(&audio->ch4);
|
||||
if (audio->ch4Left) {
|
||||
sampleLeft += sample;
|
||||
}
|
||||
|
@ -643,9 +646,6 @@ void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right) {
|
|||
}
|
||||
}
|
||||
|
||||
sampleLeft <<= 3;
|
||||
sampleRight <<= 3;
|
||||
|
||||
*left = sampleLeft * (1 + audio->volumeLeft);
|
||||
*right = sampleRight * (1 + audio->volumeRight);
|
||||
}
|
||||
|
@ -768,12 +768,12 @@ static int32_t _updateSquareChannel(struct GBAudioSquareChannel* ch) {
|
|||
}
|
||||
}
|
||||
|
||||
static int8_t _coalesceNoiseChannel(struct GBAudioNoiseChannel* ch) {
|
||||
static int16_t _coalesceNoiseChannel(struct GBAudioNoiseChannel* ch) {
|
||||
if (!ch->nSamples) {
|
||||
return ch->sample;
|
||||
}
|
||||
// TODO keep track of timing
|
||||
int8_t sample = ch->samples / ch->nSamples;
|
||||
int16_t sample = (ch->samples << 3) / ch->nSamples;
|
||||
ch->nSamples = 0;
|
||||
ch->samples = 0;
|
||||
return sample;
|
||||
|
|
|
@ -90,9 +90,13 @@ static void mGLContextResized(struct VideoBackend* v, unsigned w, unsigned h) {
|
|||
}
|
||||
}
|
||||
if (v->lockIntegerScaling) {
|
||||
if (drawW >= v->width) {
|
||||
drawW -= drawW % v->width;
|
||||
}
|
||||
if (drawH >= v->height) {
|
||||
drawH -= drawH % v->height;
|
||||
}
|
||||
}
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
glClearColor(0, 0, 0, 0);
|
||||
|
|
|
@ -201,9 +201,13 @@ static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h)
|
|||
}
|
||||
}
|
||||
if (v->lockIntegerScaling) {
|
||||
if (drawW >= v->width) {
|
||||
drawW -= drawW % v->width;
|
||||
}
|
||||
if (drawH >= v->height) {
|
||||
drawH -= drawH % v->height;
|
||||
}
|
||||
}
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH);
|
||||
}
|
||||
|
|
|
@ -368,8 +368,9 @@ endif()
|
|||
|
||||
if(DISTBUILD AND CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
|
||||
if(NOT APPLE)
|
||||
add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND "${OBJCOPY}" --only-keep-debug "$<TARGET_FILE:${BINARY_NAME}-qt>" "$<TARGET_FILE:${BINARY_NAME}-qt>.dSYM")
|
||||
add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND "${STRIP}" -S "$<TARGET_FILE:${BINARY_NAME}-qt>")
|
||||
install(FILES "$<TARGET_FILE:${BINARY_NAME}-qt>.dSYM" DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-qt-dbg)
|
||||
add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND "${OBJCOPY}" --only-keep-debug "$<TARGET_FILE:${BINARY_NAME}-qt>" "$<TARGET_FILE:${BINARY_NAME}-qt>.debug")
|
||||
add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND "${STRIP}" "$<TARGET_FILE:${BINARY_NAME}-qt>")
|
||||
add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND "${OBJCOPY}" --add-gnu-debuglink "$<TARGET_FILE:${BINARY_NAME}-qt>.debug" "$<TARGET_FILE:${BINARY_NAME}-qt>")
|
||||
install(FILES "$<TARGET_FILE:${BINARY_NAME}-qt>.debug" DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-qt-dbg)
|
||||
endif()
|
||||
endif()
|
||||
|
|
|
@ -221,12 +221,13 @@ QImage CoreController::getPixels() {
|
|||
const void* pixels;
|
||||
m_threadContext.core->getPixels(m_threadContext.core, &pixels, &stride);
|
||||
stride *= BYTES_PER_PIXEL;
|
||||
buffer.resize(stride * size.height());
|
||||
memcpy(buffer.data(), pixels, buffer.size());
|
||||
buffer = QByteArray::fromRawData(static_cast<const char*>(pixels), stride * size.height());
|
||||
}
|
||||
|
||||
return QImage(reinterpret_cast<const uchar*>(buffer.constData()),
|
||||
QImage image(reinterpret_cast<const uchar*>(buffer.constData()),
|
||||
size.width(), size.height(), stride, QImage::Format_RGBX8888);
|
||||
image.bits(); // Cause QImage to detach
|
||||
return image;
|
||||
}
|
||||
|
||||
bool CoreController::isPaused() {
|
||||
|
|
|
@ -12,7 +12,9 @@
|
|||
#include <QApplication>
|
||||
#include <QOpenGLContext>
|
||||
#include <QOpenGLPaintDevice>
|
||||
#include <QMutexLocker>
|
||||
#include <QResizeEvent>
|
||||
#include <QScreen>
|
||||
#include <QTimer>
|
||||
#include <QWindow>
|
||||
|
||||
|
@ -119,6 +121,7 @@ void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
|
|||
messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio());
|
||||
#endif
|
||||
resizePainter();
|
||||
setUpdatesEnabled(false);
|
||||
}
|
||||
|
||||
void DisplayGL::stopDrawing() {
|
||||
|
@ -127,12 +130,14 @@ void DisplayGL::stopDrawing() {
|
|||
CoreController::Interrupter interrupter(m_context);
|
||||
QMetaObject::invokeMethod(m_painter, "stop", Qt::BlockingQueuedConnection);
|
||||
m_drawThread->exit();
|
||||
m_drawThread->wait();
|
||||
m_drawThread = nullptr;
|
||||
|
||||
m_gl->makeCurrent(windowHandle());
|
||||
#if defined(_WIN32) && defined(USE_EPOXY)
|
||||
epoxy_handle_external_wglMakeCurrent();
|
||||
#endif
|
||||
setUpdatesEnabled(true);
|
||||
}
|
||||
m_context.reset();
|
||||
}
|
||||
|
@ -142,6 +147,7 @@ void DisplayGL::pauseDrawing() {
|
|||
m_isDrawing = false;
|
||||
CoreController::Interrupter interrupter(m_context);
|
||||
QMetaObject::invokeMethod(m_painter, "pause", Qt::BlockingQueuedConnection);
|
||||
setUpdatesEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,6 +156,7 @@ void DisplayGL::unpauseDrawing() {
|
|||
m_isDrawing = true;
|
||||
CoreController::Interrupter interrupter(m_context);
|
||||
QMetaObject::invokeMethod(m_painter, "unpause", Qt::BlockingQueuedConnection);
|
||||
setUpdatesEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -276,9 +283,14 @@ PainterGL::PainterGL(QWindow* surface, QOpenGLContext* parent, int forceVersion)
|
|||
#endif
|
||||
m_backend->swap = [](VideoBackend* v) {
|
||||
PainterGL* painter = static_cast<PainterGL*>(v->user);
|
||||
if (!painter->m_swapTimer.isActive()) {
|
||||
QMetaObject::invokeMethod(&painter->m_swapTimer, "start");
|
||||
if (!painter->m_gl->isValid()) {
|
||||
return;
|
||||
}
|
||||
painter->m_gl->swapBuffers(painter->m_surface);
|
||||
painter->m_gl->makeCurrent(painter->m_surface);
|
||||
#if defined(_WIN32) && defined(USE_EPOXY)
|
||||
epoxy_handle_external_wglMakeCurrent();
|
||||
#endif
|
||||
};
|
||||
|
||||
m_backend->init(m_backend, 0);
|
||||
|
@ -296,10 +308,6 @@ PainterGL::PainterGL(QWindow* surface, QOpenGLContext* parent, int forceVersion)
|
|||
for (int i = 0; i < 2; ++i) {
|
||||
m_free.append(new uint32_t[256 * 512]);
|
||||
}
|
||||
|
||||
m_swapTimer.setInterval(16);
|
||||
m_swapTimer.setSingleShot(true);
|
||||
connect(&m_swapTimer, &QTimer::timeout, this, &PainterGL::swap);
|
||||
}
|
||||
|
||||
PainterGL::~PainterGL() {
|
||||
|
@ -388,32 +396,35 @@ void PainterGL::start() {
|
|||
}
|
||||
|
||||
void PainterGL::draw() {
|
||||
if (m_queue.isEmpty()) {
|
||||
if (!m_active || m_queue.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_needsUnlock) {
|
||||
QTimer::singleShot(0, this, &PainterGL::draw);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mCoreSyncWaitFrameStart(&m_context->thread()->impl->sync) || !m_queue.isEmpty()) {
|
||||
mCoreSync* sync = &m_context->thread()->impl->sync;
|
||||
mCoreSyncWaitFrameStart(sync);
|
||||
dequeue();
|
||||
if (!m_delayTimer.isValid()) {
|
||||
m_delayTimer.start();
|
||||
} else if (sync->audioWait || sync->videoFrameWait) {
|
||||
while (m_delayTimer.nsecsElapsed() + 2000000 < 1000000000 / sync->fpsTarget) {
|
||||
QThread::usleep(500);
|
||||
}
|
||||
m_delayTimer.restart();
|
||||
}
|
||||
mCoreSyncWaitFrameEnd(sync);
|
||||
|
||||
forceDraw();
|
||||
if (m_context->thread()->impl->sync.videoFrameWait) {
|
||||
m_needsUnlock = true;
|
||||
} else {
|
||||
mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync);
|
||||
}
|
||||
} else {
|
||||
mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync);
|
||||
}
|
||||
}
|
||||
|
||||
void PainterGL::forceDraw() {
|
||||
m_painter.begin(m_window);
|
||||
performDraw();
|
||||
m_painter.end();
|
||||
if (!m_context->thread()->impl->sync.audioWait && !m_context->thread()->impl->sync.videoFrameWait) {
|
||||
if (m_delayTimer.elapsed() < 1000 / m_surface->screen()->refreshRate()) {
|
||||
return;
|
||||
}
|
||||
m_delayTimer.restart();
|
||||
}
|
||||
m_backend->swap(m_backend);
|
||||
}
|
||||
|
||||
|
@ -423,10 +434,6 @@ void PainterGL::stop() {
|
|||
dequeueAll();
|
||||
m_backend->clear(m_backend);
|
||||
m_backend->swap(m_backend);
|
||||
if (m_swapTimer.isActive()) {
|
||||
swap();
|
||||
m_swapTimer.stop();
|
||||
}
|
||||
if (m_videoProxy) {
|
||||
m_videoProxy->reset();
|
||||
}
|
||||
|
@ -456,28 +463,6 @@ void PainterGL::performDraw() {
|
|||
if (m_messagePainter) {
|
||||
m_messagePainter->paint(&m_painter);
|
||||
}
|
||||
m_frameReady = true;
|
||||
}
|
||||
|
||||
void PainterGL::swap() {
|
||||
if (!m_gl->isValid()) {
|
||||
return;
|
||||
}
|
||||
if (m_frameReady) {
|
||||
m_gl->swapBuffers(m_surface);
|
||||
m_gl->makeCurrent(m_surface);
|
||||
#if defined(_WIN32) && defined(USE_EPOXY)
|
||||
epoxy_handle_external_wglMakeCurrent();
|
||||
#endif
|
||||
m_frameReady = false;
|
||||
}
|
||||
if (m_needsUnlock) {
|
||||
mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync);
|
||||
m_needsUnlock = false;
|
||||
}
|
||||
if (!m_queue.isEmpty()) {
|
||||
QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void PainterGL::enqueue(const uint32_t* backing) {
|
||||
|
@ -534,6 +519,12 @@ void PainterGL::setShaders(struct VDir* dir) {
|
|||
return;
|
||||
}
|
||||
#ifdef BUILD_GLES2
|
||||
if (!m_started) {
|
||||
m_gl->makeCurrent(m_surface);
|
||||
#if defined(_WIN32) && defined(USE_EPOXY)
|
||||
epoxy_handle_external_wglMakeCurrent();
|
||||
#endif
|
||||
}
|
||||
if (m_shader.passes) {
|
||||
mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
|
||||
mGLES2ShaderFree(&m_shader);
|
||||
|
@ -541,6 +532,8 @@ void PainterGL::setShaders(struct VDir* dir) {
|
|||
mGLES2ShaderLoad(&m_shader, dir);
|
||||
if (m_started) {
|
||||
mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
|
||||
} else {
|
||||
m_gl->doneCurrent();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#include <QElapsedTimer>
|
||||
#include <QOpenGLContext>
|
||||
#include <QList>
|
||||
#include <QMouseEvent>
|
||||
|
@ -108,9 +109,6 @@ public slots:
|
|||
|
||||
int glTex();
|
||||
|
||||
private slots:
|
||||
void swap();
|
||||
|
||||
private:
|
||||
void performDraw();
|
||||
void dequeue();
|
||||
|
@ -131,9 +129,7 @@ private:
|
|||
VideoBackend* m_backend = nullptr;
|
||||
QSize m_size;
|
||||
MessagePainter* m_messagePainter = nullptr;
|
||||
QTimer m_swapTimer{this};
|
||||
bool m_needsUnlock = false;
|
||||
bool m_frameReady = false;
|
||||
QElapsedTimer m_delayTimer;
|
||||
std::shared_ptr<VideoProxy> m_videoProxy;
|
||||
};
|
||||
|
||||
|
|
|
@ -99,6 +99,21 @@ void DisplayQt::paintEvent(QPaintEvent*) {
|
|||
}
|
||||
QSize s = size();
|
||||
QSize ds = viewportSize();
|
||||
if (isAspectRatioLocked()) {
|
||||
if (s.width() * m_height > s.height() * m_width) {
|
||||
ds.setWidth(s.height() * m_width / m_height);
|
||||
} else if (s.width() * m_height < s.height() * m_width) {
|
||||
ds.setHeight(s.width() * m_height / m_width);
|
||||
}
|
||||
}
|
||||
if (isIntegerScalingLocked()) {
|
||||
if (ds.width() >= m_width) {
|
||||
ds.setWidth(ds.width() - ds.width() % m_width);
|
||||
}
|
||||
if (ds.height() >= m_height) {
|
||||
ds.setHeight(ds.height() - ds.height() % m_height);
|
||||
}
|
||||
}
|
||||
QPoint origin = QPoint((s.width() - ds.width()) / 2, (s.height() - ds.height()) / 2);
|
||||
QRect full(origin, ds);
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "GIFView.h"
|
||||
|
||||
#ifdef USE_MAGICK
|
||||
#ifdef USE_FFMPEG
|
||||
|
||||
#include "CoreController.h"
|
||||
#include "GBAApp.h"
|
||||
|
@ -13,9 +13,6 @@
|
|||
|
||||
#include <QMap>
|
||||
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
#include <mgba/internal/gba/video.h>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
GIFView::GIFView(QWidget* parent)
|
||||
|
@ -29,11 +26,9 @@ GIFView::GIFView(QWidget* parent)
|
|||
connect(m_ui.selectFile, &QAbstractButton::clicked, this, &GIFView::selectFile);
|
||||
connect(m_ui.filename, &QLineEdit::textChanged, this, &GIFView::setFilename);
|
||||
|
||||
connect(m_ui.frameskip, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
|
||||
this, &GIFView::updateDelay);
|
||||
connect(m_ui.delayAuto, &QAbstractButton::clicked, this, &GIFView::updateDelay);
|
||||
|
||||
ImageMagickGIFEncoderInit(&m_encoder);
|
||||
FFmpegEncoderInit(&m_encoder);
|
||||
FFmpegEncoderSetAudio(&m_encoder, nullptr, 0);
|
||||
FFmpegEncoderSetContainer(&m_encoder, "gif");
|
||||
}
|
||||
|
||||
GIFView::~GIFView() {
|
||||
|
@ -44,34 +39,35 @@ void GIFView::setController(std::shared_ptr<CoreController> controller) {
|
|||
connect(controller.get(), &CoreController::stopping, this, &GIFView::stopRecording);
|
||||
connect(this, &GIFView::recordingStarted, controller.get(), &CoreController::setAVStream);
|
||||
connect(this, &GIFView::recordingStopped, controller.get(), &CoreController::clearAVStream, Qt::DirectConnection);
|
||||
QSize size(controller->screenDimensions());
|
||||
FFmpegEncoderSetDimensions(&m_encoder, size.width(), size.height());
|
||||
}
|
||||
|
||||
void GIFView::startRecording() {
|
||||
int delayMs = m_ui.delayAuto->isChecked() ? -1 : m_ui.delayMs->value();
|
||||
ImageMagickGIFEncoderSetParams(&m_encoder, m_ui.frameskip->value(), delayMs);
|
||||
if (!ImageMagickGIFEncoderOpen(&m_encoder, m_filename.toUtf8().constData())) {
|
||||
FFmpegEncoderSetVideo(&m_encoder, "gif", 0, m_ui.frameskip->value());
|
||||
if (!FFmpegEncoderOpen(&m_encoder, m_filename.toUtf8().constData())) {
|
||||
LOG(QT, ERROR) << tr("Failed to open output GIF file: %1").arg(m_filename);
|
||||
return;
|
||||
}
|
||||
m_ui.start->setEnabled(false);
|
||||
m_ui.stop->setEnabled(true);
|
||||
m_ui.groupBox->setEnabled(false);
|
||||
m_ui.frameskip->setEnabled(false);
|
||||
emit recordingStarted(&m_encoder.d);
|
||||
}
|
||||
|
||||
void GIFView::stopRecording() {
|
||||
emit recordingStopped();
|
||||
ImageMagickGIFEncoderClose(&m_encoder);
|
||||
FFmpegEncoderClose(&m_encoder);
|
||||
m_ui.stop->setEnabled(false);
|
||||
m_ui.start->setEnabled(true);
|
||||
m_ui.groupBox->setEnabled(true);
|
||||
m_ui.frameskip->setEnabled(true);
|
||||
}
|
||||
|
||||
void GIFView::selectFile() {
|
||||
QString filename = GBAApp::app()->getSaveFileName(this, tr("Select output file"), tr("Graphics Interchange Format (*.gif)"));
|
||||
if (!filename.isEmpty()) {
|
||||
m_ui.filename->setText(filename);
|
||||
if (!ImageMagickGIFEncoderIsOpen(&m_encoder)) {
|
||||
if (!FFmpegEncoderIsOpen(&m_encoder)) {
|
||||
m_ui.start->setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
@ -81,15 +77,4 @@ void GIFView::setFilename(const QString& fname) {
|
|||
m_filename = fname;
|
||||
}
|
||||
|
||||
void GIFView::updateDelay() {
|
||||
if (!m_ui.delayAuto->isChecked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t s = (m_ui.frameskip->value() + 1);
|
||||
s *= VIDEO_TOTAL_LENGTH * 1000;
|
||||
s /= GBA_ARM7TDMI_FREQUENCY;
|
||||
m_ui.delayMs->setValue(s);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_MAGICK
|
||||
#ifdef USE_FFMPEG
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
|||
|
||||
#include "ui_GIFView.h"
|
||||
|
||||
#include "feature/imagemagick/imagemagick-gif-encoder.h"
|
||||
#include "feature/ffmpeg/ffmpeg-encoder.h"
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
|
@ -41,12 +41,11 @@ signals:
|
|||
private slots:
|
||||
void selectFile();
|
||||
void setFilename(const QString&);
|
||||
void updateDelay();
|
||||
|
||||
private:
|
||||
Ui::GIFView m_ui;
|
||||
|
||||
ImageMagickGIFEncoder m_encoder;
|
||||
FFmpegEncoder m_encoder;
|
||||
|
||||
QString m_filename;
|
||||
};
|
||||
|
|
|
@ -6,18 +6,52 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>278</width>
|
||||
<height>247</height>
|
||||
<width>392</width>
|
||||
<height>220</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Record GIF</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetFixedSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<item row="1" column="0">
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Frameskip</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QSpinBox" name="frameskip">
|
||||
<property name="value">
|
||||
<number>2</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="3">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Close</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="3">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="0">
|
||||
<widget class="QPushButton" name="start">
|
||||
|
@ -51,6 +85,19 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="selectFile">
|
||||
<property name="sizePolicy">
|
||||
|
@ -74,81 +121,8 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string/>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Frameskip</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="frameskip">
|
||||
<property name="value">
|
||||
<number>2</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Frame delay (ms)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="delayAuto">
|
||||
<property name="text">
|
||||
<string>Automatic</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="delayMs">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>5000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>50</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Close</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
@ -169,21 +143,5 @@
|
|||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>delayAuto</sender>
|
||||
<signal>clicked(bool)</signal>
|
||||
<receiver>delayMs</receiver>
|
||||
<slot>setDisabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>202</x>
|
||||
<y>177</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>192</x>
|
||||
<y>148</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
|
|
|
@ -284,7 +284,7 @@ void VideoView::setVideoCodec(const QString& codec, bool manual) {
|
|||
} else {
|
||||
m_videoCodecCstr = strdup(m_videoCodec.toUtf8().constData());
|
||||
}
|
||||
if (!FFmpegEncoderSetVideo(&m_encoder, m_videoCodecCstr, m_vbr)) {
|
||||
if (!FFmpegEncoderSetVideo(&m_encoder, m_videoCodecCstr, m_vbr, 0)) {
|
||||
free(m_videoCodecCstr);
|
||||
m_videoCodecCstr = nullptr;
|
||||
m_videoCodec = QString();
|
||||
|
@ -321,7 +321,7 @@ void VideoView::setAudioBitrate(int br, bool manual) {
|
|||
|
||||
void VideoView::setVideoBitrate(int br, bool manual) {
|
||||
m_vbr = br >= 0 ? br * 1000 : 0;
|
||||
FFmpegEncoderSetVideo(&m_encoder, m_videoCodecCstr, m_vbr);
|
||||
FFmpegEncoderSetVideo(&m_encoder, m_videoCodecCstr, m_vbr, 0);
|
||||
validateSettings();
|
||||
if (manual) {
|
||||
uncheckIncompatible();
|
||||
|
|
|
@ -198,9 +198,6 @@ Window::~Window() {
|
|||
|
||||
#ifdef USE_FFMPEG
|
||||
delete m_videoView;
|
||||
#endif
|
||||
|
||||
#ifdef USE_MAGICK
|
||||
delete m_gifView;
|
||||
#endif
|
||||
|
||||
|
@ -556,9 +553,7 @@ void Window::openVideoWindow() {
|
|||
}
|
||||
m_videoView->show();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_MAGICK
|
||||
void Window::openGIFWindow() {
|
||||
if (!m_gifView) {
|
||||
m_gifView = new GIFView();
|
||||
|
@ -1534,9 +1529,6 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
|
||||
#ifdef USE_FFMPEG
|
||||
addGameAction(tr("Record A/V..."), "recordOutput", this, &Window::openVideoWindow, "av");
|
||||
#endif
|
||||
|
||||
#ifdef USE_MAGICK
|
||||
addGameAction(tr("Record GIF..."), "recordGIF", this, &Window::openGIFWindow, "av");
|
||||
#endif
|
||||
|
||||
|
@ -1914,13 +1906,11 @@ void Window::setController(CoreController* controller, const QString& fname) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_MAGICK
|
||||
#ifdef USE_FFMPEG
|
||||
if (m_gifView) {
|
||||
m_gifView->setController(m_controller);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_FFMPEG
|
||||
if (m_videoView) {
|
||||
m_videoView->setController(m_controller);
|
||||
}
|
||||
|
@ -2015,9 +2005,13 @@ void WindowBackground::paintEvent(QPaintEvent* event) {
|
|||
}
|
||||
}
|
||||
if (m_lockIntegerScaling) {
|
||||
if (ds.width() >= m_aspectWidth) {
|
||||
ds.setWidth(ds.width() - ds.width() % m_aspectWidth);
|
||||
}
|
||||
if (ds.height() >= m_aspectHeight) {
|
||||
ds.setHeight(ds.height() - ds.height() % m_aspectHeight);
|
||||
}
|
||||
}
|
||||
QPoint origin = QPoint((s.width() - ds.width()) / 2, (s.height() - ds.height()) / 2);
|
||||
QRect full(origin, ds);
|
||||
painter.drawPixmap(full, logo);
|
||||
|
|
|
@ -101,9 +101,6 @@ public slots:
|
|||
|
||||
#ifdef USE_FFMPEG
|
||||
void openVideoWindow();
|
||||
#endif
|
||||
|
||||
#ifdef USE_MAGICK
|
||||
void openGIFWindow();
|
||||
#endif
|
||||
|
||||
|
@ -222,9 +219,6 @@ private:
|
|||
|
||||
#ifdef USE_FFMPEG
|
||||
VideoView* m_videoView = nullptr;
|
||||
#endif
|
||||
|
||||
#ifdef USE_MAGICK
|
||||
GIFView* m_gifView = nullptr;
|
||||
#endif
|
||||
|
||||
|
|
|
@ -125,8 +125,9 @@ endif()
|
|||
|
||||
if(DISTBUILD AND CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
|
||||
if(NOT APPLE)
|
||||
add_custom_command(TARGET ${BINARY_NAME}-sdl POST_BUILD COMMAND "${OBJCOPY}" --only-keep-debug "$<TARGET_FILE:${BINARY_NAME}-sdl>" "$<TARGET_FILE:${BINARY_NAME}-sdl>.dSYM")
|
||||
add_custom_command(TARGET ${BINARY_NAME}-sdl POST_BUILD COMMAND "${STRIP}" -S "$<TARGET_FILE:${BINARY_NAME}-sdl>")
|
||||
install(FILES "$<TARGET_FILE:${BINARY_NAME}-sdl>.dSYM" DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-sdl-dbg)
|
||||
add_custom_command(TARGET ${BINARY_NAME}-sdl POST_BUILD COMMAND "${OBJCOPY}" --only-keep-debug "$<TARGET_FILE:${BINARY_NAME}-sdl>" "$<TARGET_FILE:${BINARY_NAME}-sdl>.debug")
|
||||
add_custom_command(TARGET ${BINARY_NAME}-sdl POST_BUILD COMMAND "${STRIP}" "$<TARGET_FILE:${BINARY_NAME}-sdl>")
|
||||
add_custom_command(TARGET ${BINARY_NAME}-sdl POST_BUILD COMMAND "${OBJCOPY}" --add-gnu-debuglink "$<TARGET_FILE:${BINARY_NAME}-sdl>.debug" "$<TARGET_FILE:${BINARY_NAME}-sdl>")
|
||||
install(FILES "$<TARGET_FILE:${BINARY_NAME}-sdl>.debug" DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-sdl-dbg)
|
||||
endif()
|
||||
endif()
|
||||
|
|
Loading…
Reference in New Issue