mirror of https://github.com/mgba-emu/mgba.git
Feature: Switch from ImageMagick to FFmpeg for GIF generation
This commit is contained in:
parent
8219b70c2e
commit
8708a0db52
1
CHANGES
1
CHANGES
|
@ -89,6 +89,7 @@ Misc:
|
|||
- 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")
|
||||
|
@ -462,12 +461,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")
|
||||
|
@ -513,18 +511,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})
|
||||
|
@ -544,29 +544,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)
|
||||
|
@ -1240,8 +1217,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}")
|
||||
|
|
|
@ -145,7 +145,7 @@ This will build and install mGBA into `/usr/bin` and `/usr/lib`. Dependencies th
|
|||
|
||||
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` ..
|
||||
|
@ -159,11 +159,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:
|
||||
|
||||
|
|
|
@ -95,10 +95,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>
|
||||
|
@ -38,7 +41,9 @@ enum {
|
|||
};
|
||||
|
||||
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;
|
||||
|
@ -49,11 +54,27 @@ 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->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;
|
||||
|
@ -66,6 +87,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) {
|
||||
|
@ -130,7 +160,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;
|
||||
|
@ -149,7 +179,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) {
|
||||
|
@ -179,6 +210,7 @@ bool FFmpegEncoderSetVideo(struct FFmpegEncoder* encoder, const char* vcodec, un
|
|||
}
|
||||
encoder->videoCodec = vcodec;
|
||||
encoder->videoBitrate = vbr;
|
||||
encoder->frameskip = frameskip + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -226,6 +258,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
|
||||
|
@ -325,8 +358,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) { VIDEO_TOTAL_LENGTH, GBA_ARM7TDMI_FREQUENCY };
|
||||
encoder->video->framerate = (AVRational) { GBA_ARM7TDMI_FREQUENCY, VIDEO_TOTAL_LENGTH };
|
||||
encoder->video->time_base = (AVRational) { VIDEO_TOTAL_LENGTH * encoder->frameskip, GBA_ARM7TDMI_FREQUENCY };
|
||||
encoder->video->framerate = (AVRational) { GBA_ARM7TDMI_FREQUENCY, VIDEO_TOTAL_LENGTH * encoder->frameskip };
|
||||
encoder->video->pix_fmt = encoder->pixFormat;
|
||||
encoder->video->gop_size = 60;
|
||||
encoder->video->max_b_frames = 3;
|
||||
|
@ -365,6 +398,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;
|
||||
|
@ -374,12 +455,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
|
||||
|
@ -401,6 +482,18 @@ void FFmpegEncoderClose(struct FFmpegEncoder* encoder) {
|
|||
}
|
||||
}
|
||||
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;
|
||||
|
@ -460,6 +553,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;
|
||||
|
@ -470,6 +572,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;
|
||||
|
@ -596,6 +710,10 @@ 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;
|
||||
|
||||
#if LIBAVCODEC_VERSION_MAJOR >= 55
|
||||
|
@ -606,7 +724,21 @@ 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);
|
||||
|
||||
_ffmpegWriteVideoFrame(encoder, encoder->videoFrame);
|
||||
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) {
|
||||
|
@ -651,20 +783,7 @@ 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,19 +72,28 @@ struct FFmpegEncoder {
|
|||
|
||||
struct AVCodecContext* video;
|
||||
enum AVPixelFormat pixFormat;
|
||||
enum AVPixelFormat ipixFormat;
|
||||
struct AVFrame* videoFrame;
|
||||
int width;
|
||||
int height;
|
||||
int iwidth;
|
||||
int iheight;
|
||||
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,107 +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);
|
||||
|
||||
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->frameskip = 2;
|
||||
encoder->delayMs = -1;
|
||||
|
||||
encoder->iwidth = GBA_VIDEO_HORIZONTAL_PIXELS;
|
||||
encoder->iheight = GBA_VIDEO_VERTICAL_PIXELS;
|
||||
}
|
||||
|
||||
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 *= VIDEO_TOTAL_LENGTH * 100;
|
||||
nts *= VIDEO_TOTAL_LENGTH * 100;
|
||||
ts /= GBA_ARM7TDMI_FREQUENCY;
|
||||
nts /= GBA_ARM7TDMI_FREQUENCY;
|
||||
}
|
||||
MagickSetImageDelay(encoder->wand, nts - ts);
|
||||
++encoder->currentFrame;
|
||||
}
|
||||
|
||||
static void _magickVideoDimensionsChanged(struct mAVStream* stream, unsigned width, unsigned height) {
|
||||
struct ImageMagickGIFEncoder* encoder = (struct ImageMagickGIFEncoder*) stream;
|
||||
if (width * height > encoder->iwidth * encoder->iheight) {
|
||||
free(encoder->frame);
|
||||
encoder->frame = malloc(width * height * 4);
|
||||
}
|
||||
encoder->iwidth = width;
|
||||
encoder->iheight = height;
|
||||
}
|
|
@ -1,43 +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;
|
||||
};
|
||||
|
||||
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
|
|
@ -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>
|
||||
|
|
|
@ -280,7 +280,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();
|
||||
|
@ -317,7 +317,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();
|
||||
|
|
|
@ -165,9 +165,6 @@ Window::~Window() {
|
|||
|
||||
#ifdef USE_FFMPEG
|
||||
delete m_videoView;
|
||||
#endif
|
||||
|
||||
#ifdef USE_MAGICK
|
||||
delete m_gifView;
|
||||
#endif
|
||||
|
||||
|
@ -507,9 +504,7 @@ void Window::openVideoWindow() {
|
|||
}
|
||||
m_videoView->show();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_MAGICK
|
||||
void Window::openGIFWindow() {
|
||||
if (!m_gifView) {
|
||||
m_gifView = new GIFView();
|
||||
|
@ -1442,9 +1437,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
|
||||
|
||||
|
@ -1874,13 +1866,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);
|
||||
}
|
||||
|
|
|
@ -101,9 +101,6 @@ public slots:
|
|||
|
||||
#ifdef USE_FFMPEG
|
||||
void openVideoWindow();
|
||||
#endif
|
||||
|
||||
#ifdef USE_MAGICK
|
||||
void openGIFWindow();
|
||||
#endif
|
||||
|
||||
|
@ -224,9 +221,6 @@ private:
|
|||
|
||||
#ifdef USE_FFMPEG
|
||||
VideoView* m_videoView = nullptr;
|
||||
#endif
|
||||
|
||||
#ifdef USE_MAGICK
|
||||
GIFView* m_gifView = nullptr;
|
||||
#endif
|
||||
|
||||
|
|
Loading…
Reference in New Issue