Hook up and finish up video recorder
Jeffrey Pfau jeffrey@endrift.com
Sun, 26 Oct 2014 23:49:25 -0700
6 files changed,
237 insertions(+),
8 deletions(-)
M
src/platform/qt/GameController.cpp
→
src/platform/qt/GameController.cpp
@@ -311,6 +311,26 @@ GBAThreadContinue(&m_threadContext);
} } +void GameController::setAVStream(GBAAVStream* stream) { + if (m_gameOpen) { + GBAThreadInterrupt(&m_threadContext); + m_threadContext.stream = stream; + GBAThreadContinue(&m_threadContext); + } else { + m_threadContext.stream = stream; + } +} + +void GameController::clearAVStream() { + if (m_gameOpen) { + GBAThreadInterrupt(&m_threadContext); + m_threadContext.stream = nullptr; + GBAThreadContinue(&m_threadContext); + } else { + m_threadContext.stream = nullptr; + } +} + void GameController::updateKeys() { int activeKeys = m_activeKeys; #ifdef BUILD_SDL
M
src/platform/qt/GameController.h
→
src/platform/qt/GameController.h
@@ -73,6 +73,8 @@ void setVideoSync(bool);
void setAudioSync(bool); void setFrameskip(int); void setTurbo(bool, bool forced = true); + void setAVStream(GBAAVStream*); + void clearAVStream(); #ifdef BUILD_SDL private slots:
M
src/platform/qt/VideoView.cpp
→
src/platform/qt/VideoView.cpp
@@ -2,14 +2,183 @@ #include "VideoView.h"
#ifdef USE_FFMPEG +#include <QFileDialog> +#include <QMap> + using namespace QGBA; +QMap<QString, QString> VideoView::s_acodecMap; +QMap<QString, QString> VideoView::s_vcodecMap; +QMap<QString, QString> VideoView::s_containerMap; + VideoView::VideoView(QWidget* parent) : QWidget(parent) + , m_audioCodecCstr(nullptr) + , m_videoCodecCstr(nullptr) + , m_containerCstr(nullptr) { m_ui.setupUi(this); + if (s_acodecMap.empty()) { + s_acodecMap["mp3"] = "libmp3lame"; + s_acodecMap["uncompressed"] = "pcm_s16le"; + } + if (s_vcodecMap.empty()) { + s_vcodecMap["h264"] = "libx264rgb"; + } + if (s_containerMap.empty()) { + s_containerMap["mkv"] = "matroska"; + } + connect(m_ui.buttonBox, SIGNAL(rejected()), this, SLOT(close())); + connect(m_ui.start, SIGNAL(clicked()), this, SLOT(startRecording())); + connect(m_ui.stop, SIGNAL(clicked()), this, SLOT(stopRecording())); + + connect(m_ui.selectFile, SIGNAL(clicked()), this, SLOT(selectFile())); + connect(m_ui.filename, SIGNAL(textChanged(const QString&)), this, SLOT(setFilename(const QString&))); + + connect(m_ui.audio, SIGNAL(activated(const QString&)), this, SLOT(setAudioCodec(const QString&))); + connect(m_ui.video, SIGNAL(activated(const QString&)), this, SLOT(setVideoCodec(const QString&))); + connect(m_ui.container, SIGNAL(activated(const QString&)), this, SLOT(setContainer(const QString&))); + + connect(m_ui.abr, SIGNAL(valueChanged(int)), this, SLOT(setAudioBitrate(int))); + connect(m_ui.vbr, SIGNAL(valueChanged(int)), this, SLOT(setVideoBitrate(int))); + + FFmpegEncoderInit(&m_encoder); + + setAudioCodec(m_ui.audio->currentText()); + setVideoCodec(m_ui.video->currentText()); + setContainer(m_ui.container->currentText()); +} + +VideoView::~VideoView() { + stopRecording(); + free(m_audioCodecCstr); + free(m_videoCodecCstr); + free(m_containerCstr); +} + +void VideoView::startRecording() { + if (!validateSettings()) { + return; + } + if (!FFmpegEncoderOpen(&m_encoder, m_filename.toLocal8Bit().constData())) { + return; + } + m_ui.start->setEnabled(false); + m_ui.stop->setEnabled(true); + emit recordingStarted(&m_encoder.d); +} + +void VideoView::stopRecording() { + emit recordingStopped(); + FFmpegEncoderClose(&m_encoder); + m_ui.stop->setEnabled(false); + validateSettings(); +} + +void VideoView::selectFile() { + QString filename = QFileDialog::getSaveFileName(this, tr("Select output file")); + if (!filename.isEmpty()) { + m_ui.filename->setText(filename); + } +} + +void VideoView::setFilename(const QString& fname) { + m_filename = fname; + validateSettings(); +} + +void VideoView::setAudioCodec(const QString& codec) { + free(m_audioCodecCstr); + m_audioCodec = sanitizeCodec(codec); + if (s_acodecMap.contains(m_audioCodec)) { + m_audioCodec = s_acodecMap[m_audioCodec]; + } + m_audioCodecCstr = strdup(m_audioCodec.toLocal8Bit().constData()); + if (!FFmpegEncoderSetAudio(&m_encoder, m_audioCodecCstr, m_abr)) { + free(m_audioCodecCstr); + m_audioCodecCstr = nullptr; + } + validateSettings(); +} + +void VideoView::setVideoCodec(const QString& codec) { + free(m_videoCodecCstr); + m_videoCodec = sanitizeCodec(codec); + if (s_vcodecMap.contains(m_videoCodec)) { + m_videoCodec = s_vcodecMap[m_videoCodec]; + } + m_videoCodecCstr = strdup(m_videoCodec.toLocal8Bit().constData()); + if (!FFmpegEncoderSetVideo(&m_encoder, m_videoCodecCstr, m_vbr)) { + free(m_videoCodecCstr); + m_videoCodecCstr = nullptr; + } + validateSettings(); +} + +void VideoView::setContainer(const QString& container) { + free(m_containerCstr); + m_container = sanitizeCodec(container); + if (s_containerMap.contains(m_container)) { + m_container = s_containerMap[m_container]; + } + m_containerCstr = strdup(m_container.toLocal8Bit().constData()); + if (!FFmpegEncoderSetContainer(&m_encoder, m_containerCstr)) { + free(m_containerCstr); + m_containerCstr = nullptr; + } + validateSettings(); +} + +void VideoView::setAudioBitrate(int br) { + m_abr = br; + FFmpegEncoderSetAudio(&m_encoder, m_audioCodecCstr, m_abr); + validateSettings(); +} + +void VideoView::setVideoBitrate(int br) { + m_abr = br; + FFmpegEncoderSetVideo(&m_encoder, m_videoCodecCstr, m_vbr); + validateSettings(); +} + +bool VideoView::validateSettings() { + bool valid = true; + if (!m_audioCodecCstr) { + valid = false; + m_ui.audio->setStyleSheet("QComboBox { color: red; }"); + } else { + m_ui.audio->setStyleSheet(""); + } + + if (!m_videoCodecCstr) { + valid = false; + m_ui.video->setStyleSheet("QComboBox { color: red; }"); + } else { + m_ui.video->setStyleSheet(""); + } + + if (!m_containerCstr) { + valid = false; + m_ui.container->setStyleSheet("QComboBox { color: red; }"); + } else { + m_ui.container->setStyleSheet(""); + } + + // This |valid| check is necessary as if one of the cstrs + // is null, the encoder likely has a dangling pointer + if (valid && !FFmpegEncoderVerifyContainer(&m_encoder)) { + valid = false; + } + + m_ui.start->setEnabled(valid && !m_filename.isNull()); + return valid; +} + +QString VideoView::sanitizeCodec(const QString& codec) { + QString sanitized = codec.toLower(); + return sanitized.remove(QChar('.')); } #endif
M
src/platform/qt/VideoView.h
→
src/platform/qt/VideoView.h
@@ -7,7 +7,9 @@ #include <QWidget>
#include "ui_VideoView.h" -struct FFmpegEncoder; +extern "C" { +#include "platform/ffmpeg/ffmpeg-encoder.h" +} namespace QGBA {@@ -16,11 +18,50 @@ Q_OBJECT
public: VideoView(QWidget* parent = nullptr); + virtual ~VideoView(); + + GBAAVStream* getStream() { return &m_encoder.d; } + +public slots: + void startRecording(); + void stopRecording(); + +signals: + void recordingStarted(GBAAVStream*); + void recordingStopped(); + +private slots: + void selectFile(); + void setFilename(const QString&); + void setAudioCodec(const QString&); + void setVideoCodec(const QString&); + void setContainer(const QString&); + + void setAudioBitrate(int); + void setVideoBitrate(int); private: + bool validateSettings(); + static QString sanitizeCodec(const QString&); + Ui::VideoView m_ui; - FFmpegEncoder* m_encoder; + FFmpegEncoder m_encoder; + + QString m_filename; + QString m_audioCodec; + QString m_videoCodec; + QString m_container; + char* m_audioCodecCstr; + char* m_videoCodecCstr; + char* m_containerCstr; + + int m_abr; + int m_vbr; + + static QMap<QString, QString> s_acodecMap; + static QMap<QString, QString> s_vcodecMap; + static QMap<QString, QString> s_containerMap; }; }
M
src/platform/qt/VideoView.ui
→
src/platform/qt/VideoView.ui
@@ -68,12 +68,7 @@ </property>
</item> <item> <property name="text"> - <string>Xvid</string> - </property> - </item> - <item> - <property name="text"> - <string>Uncompressed</string> + <string>FFV1</string> </property> </item> </widget>
M
src/platform/qt/Window.cpp
→
src/platform/qt/Window.cpp
@@ -148,6 +148,8 @@ #ifdef USE_FFMPEG
void Window::openVideoWindow() { if (!m_videoView) { m_videoView = new VideoView(); + connect(m_videoView, SIGNAL(recordingStarted(GBAAVStream*)), m_controller, SLOT(setAVStream(GBAAVStream*))); + connect(m_videoView, SIGNAL(recordingStopped()), m_controller, SLOT(clearAVStream()), Qt::DirectConnection); } m_videoView->show(); }