FFmpeg: Allow framerate to be adjusted
jump to
@@ -48,6 +48,7 @@ void (*videoDimensionsChanged)(struct mAVStream*, unsigned width, unsigned height);
void (*postVideoFrame)(struct mAVStream*, const color_t* buffer, size_t stride); void (*postAudioFrame)(struct mAVStream*, int16_t left, int16_t right); void (*postAudioBuffer)(struct mAVStream*, struct blip_t* left, struct blip_t* right); + void (*videoFrameRateChanged)(struct mAVStream*, unsigned numerator, unsigned denominator); }; struct mKeyCallback {
@@ -215,6 +215,9 @@ ds->stream = stream;
if (stream && stream->videoDimensionsChanged) { stream->videoDimensionsChanged(stream, DS_VIDEO_HORIZONTAL_PIXELS, DS_VIDEO_VERTICAL_PIXELS * 2); } + if (stream && stream->videoFrameRateChanged) { + stream->videoFrameRateChanged(stream, core->frameCycles(core), core->frequency(core)); + } } static bool _DSCoreLoadROM(struct mCore* core, struct VFile* vf) {
@@ -25,6 +25,7 @@
static void _ffmpegPostVideoFrame(struct mAVStream*, const color_t* pixels, size_t stride); 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); enum { PREFERRED_SAMPLE_RATE = 0x8000@@ -37,9 +38,12 @@ encoder->d.videoDimensionsChanged = _ffmpegSetVideoDimensions;
encoder->d.postVideoFrame = _ffmpegPostVideoFrame; encoder->d.postAudioFrame = _ffmpegPostAudioFrame; encoder->d.postAudioBuffer = 0; + encoder->d.videoFrameRateChanged = _ffmpegSetVideoFrameRate; encoder->audioCodec = 0; encoder->videoCodec = 0; + encoder->audio = NULL; + encoder->video = NULL; encoder->containerFormat = 0; FFmpegEncoderSetAudio(encoder, "flac", 0); FFmpegEncoderSetVideo(encoder, "png", 0);@@ -47,6 +51,8 @@ FFmpegEncoderSetContainer(encoder, "matroska");
FFmpegEncoderSetDimensions(encoder, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); encoder->iwidth = VIDEO_HORIZONTAL_PIXELS; encoder->iheight = VIDEO_VERTICAL_PIXELS; + encoder->frameCycles = VIDEO_TOTAL_LENGTH; + encoder->cycles = GBA_ARM7TDMI_FREQUENCY; encoder->resampleContext = 0; encoder->absf = 0; encoder->context = 0;@@ -286,7 +292,8 @@ #endif
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->videoStream->time_base = (AVRational) { encoder->frameCycles, encoder->cycles }; + encoder->video->time_base = encoder->videoStream->time_base; encoder->video->pix_fmt = encoder->pixFormat; encoder->video->gop_size = 60; encoder->video->max_b_frames = 3;@@ -352,6 +359,7 @@ #else
avcodec_free_frame(&encoder->audioFrame); #endif avcodec_close(encoder->audio); + encoder->audio = NULL; if (encoder->resampleContext) { avresample_close(encoder->resampleContext);@@ -373,6 +381,7 @@ #else
avcodec_free_frame(&encoder->videoFrame); #endif avcodec_close(encoder->video); + encoder->video = NULL; sws_freeContext(encoder->scaleContext); encoder->scaleContext = NULL;@@ -525,23 +534,38 @@ static void _ffmpegSetVideoDimensions(struct mAVStream* stream, unsigned width, unsigned height) {
struct FFmpegEncoder* encoder = (struct FFmpegEncoder*) stream; encoder->iwidth = width; encoder->iheight = height; - if (encoder->scaleContext) { - sws_freeContext(encoder->scaleContext); - } - encoder->scaleContext = sws_getContext(encoder->iwidth, encoder->iheight, + if (encoder->video) { + 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, + AV_PIX_FMT_RGB565, #else - AV_PIX_FMT_BGR555, + AV_PIX_FMT_BGR555, #endif #else #ifndef USE_LIBAV - AV_PIX_FMT_0BGR32, + AV_PIX_FMT_0BGR32, #else - AV_PIX_FMT_BGR32, + AV_PIX_FMT_BGR32, #endif #endif - encoder->videoFrame->width, encoder->videoFrame->height, encoder->video->pix_fmt, - SWS_POINT, 0, 0, 0); + encoder->videoFrame->width, encoder->videoFrame->height, encoder->video->pix_fmt, + SWS_POINT, 0, 0, 0); + } +} + +static void _ffmpegSetVideoFrameRate(struct mAVStream* stream, unsigned numerator, unsigned denominator) { + struct FFmpegEncoder* encoder = (struct FFmpegEncoder*) stream; + FFmpegEncoderSetInputFrameRate(encoder, numerator, denominator); +} + +void FFmpegEncoderSetInputFrameRate(struct FFmpegEncoder* encoder, unsigned numerator, unsigned denominator) { + encoder->frameCycles = numerator; + encoder->cycles = denominator; + if (encoder->video) { + encoder->video->time_base = (AVRational) { numerator, denominator }; + } }
@@ -72,6 +72,8 @@ int width;
int height; int iwidth; int iheight; + unsigned frameCycles; + unsigned cycles; int64_t currentVideoFrame; struct SwsContext* scaleContext; struct AVStream* videoStream;@@ -86,6 +88,8 @@ bool FFmpegEncoderVerifyContainer(struct FFmpegEncoder*);
bool FFmpegEncoderOpen(struct FFmpegEncoder*, const char* outfile); void FFmpegEncoderClose(struct FFmpegEncoder*); bool FFmpegEncoderIsOpen(struct FFmpegEncoder*); + +void FFmpegEncoderSetInputFrameRate(struct FFmpegEncoder*, unsigned numerator, unsigned denominator); CXX_GUARD_END
@@ -11,6 +11,7 @@ #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;@@ -19,12 +20,15 @@ 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 = VIDEO_HORIZONTAL_PIXELS; encoder->iheight = VIDEO_VERTICAL_PIXELS; + encoder->numerator = VIDEO_TOTAL_LENGTH; + encoder->denominator = GBA_ARM7TDMI_FREQUENCY; } void ImageMagickGIFEncoderSetParams(struct ImageMagickGIFEncoder* encoder, int frameskip, int delayMs) {@@ -87,10 +91,10 @@ 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; + ts *= encoder->numerator * 100; + nts *= encoder->numerator * 100; + ts /= encoder->denominator; + nts /= encoder->denominator; } MagickSetImageDelay(encoder->wand, nts - ts); ++encoder->currentFrame;@@ -101,3 +105,9 @@ struct ImageMagickGIFEncoder* encoder = (struct ImageMagickGIFEncoder*) stream;
encoder->iwidth = width; encoder->iheight = height; } + +static void _magickVideoFrameRateChanged(struct mAVStream* stream, unsigned numerator, unsigned denominator) { + struct ImageMagickGIFEncoder* encoder = (struct ImageMagickGIFEncoder*) stream; + encoder->numerator = numerator; + encoder->denominator = denominator; +}
@@ -33,6 +33,9 @@ int delayMs;
unsigned iwidth; unsigned iheight; + + unsigned numerator; + unsigned denominator; }; void ImageMagickGIFEncoderInit(struct ImageMagickGIFEncoder*);
@@ -211,6 +211,9 @@ gb->stream = stream;
if (stream && stream->videoDimensionsChanged) { stream->videoDimensionsChanged(stream, GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS); } + if (stream && stream->videoFrameRateChanged) { + stream->videoFrameRateChanged(stream, core->frameCycles(core), core->frequency(core)); + } } static bool _GBCoreLoadROM(struct mCore* core, struct VFile* vf) {
@@ -229,6 +229,9 @@ gba->stream = stream;
if (stream && stream->videoDimensionsChanged) { stream->videoDimensionsChanged(stream, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); } + if (stream && stream->videoFrameRateChanged) { + stream->videoFrameRateChanged(stream, core->frameCycles(core), core->frequency(core)); + } } static bool _GBACoreLoadROM(struct mCore* core, struct VFile* vf) {
@@ -669,6 +669,7 @@ stream.videoDimensionsChanged = 0;
stream.postVideoFrame = 0; stream.postAudioFrame = 0; stream.postAudioBuffer = _postAudioBuffer; + stream.videoFrameRateChanged = 0; if (!allocateRomBuffer()) { return 1;
@@ -211,6 +211,7 @@ stream.videoDimensionsChanged = 0;
stream.postAudioFrame = 0; stream.postAudioBuffer = _postAudioBuffer; stream.postVideoFrame = 0; + stream.videoFrameRateChanged = 0; } void retro_deinit(void) {
@@ -679,6 +679,13 @@
return QSize(width, height); } +QPair<unsigned, unsigned> GameController::frameRate() const { + if (!m_gameOpen) { + return qMakePair(1U, 60U); + } + return qMakePair(m_threadContext.core->frameCycles(m_threadContext.core), m_threadContext.core->frequency(m_threadContext.core)); +} + void GameController::setPaused(bool paused) { if (!isLoaded() || paused == mCoreThreadIsPaused(&m_threadContext)) { return;
@@ -71,6 +71,7 @@
bool audioSync() const { return m_audioSync; } bool videoSync() const { return m_videoSync; } QSize screenDimensions() const; + QPair<unsigned, unsigned> frameRate() const; void setInputController(InputController* controller) { m_inputController = controller; }
@@ -239,6 +239,10 @@ }
} } +void VideoView::setNativeFrameRate(const QPair<unsigned, unsigned>& ratio) { + FFmpegEncoderSetInputFrameRate(&m_encoder, ratio.first, ratio.second); +} + void VideoView::selectFile() { QString filename = GBAApp::app()->getSaveFileName(this, tr("Select output file")); if (!filename.isEmpty()) {
@@ -29,6 +29,7 @@ public slots:
void startRecording(); void stopRecording(); void setNativeResolution(const QSize&); + void setNativeFrameRate(const QPair<unsigned, unsigned>& ratio); signals: void recordingStarted(mAVStream*);
@@ -545,15 +545,17 @@ #ifdef USE_FFMPEG
void Window::openVideoWindow() { if (!m_videoView) { m_videoView = new VideoView(); - connect(m_videoView, SIGNAL(recordingStarted(mAVStream*)), m_controller, SLOT(setAVStream(mAVStream*))); + connect(m_videoView, SIGNAL(recordingStarted(mAVStream*)), m_controller, SLOT(setAVStream(mAVStream*)), Qt::DirectConnection); connect(m_videoView, SIGNAL(recordingStopped()), m_controller, SLOT(clearAVStream()), Qt::DirectConnection); connect(m_controller, SIGNAL(gameStopped(mCoreThread*)), m_videoView, SLOT(stopRecording())); connect(m_controller, SIGNAL(gameStopped(mCoreThread*)), m_videoView, SLOT(close())); connect(m_controller, &GameController::gameStarted, [this]() { m_videoView->setNativeResolution(m_controller->screenDimensions()); + m_videoView->setNativeFrameRate(m_controller->frameRate()); }); if (m_controller->isLoaded()) { m_videoView->setNativeResolution(m_controller->screenDimensions()); + m_videoView->setNativeFrameRate(m_controller->frameRate()); } connect(this, SIGNAL(shutdown()), m_videoView, SLOT(close())); }