mirror of https://github.com/mgba-emu/mgba.git
Hook up and finish up video recorder
This commit is contained in:
parent
61569c0559
commit
b51e72fcab
|
@ -311,6 +311,26 @@ void GameController::setTurbo(bool set, bool forced) {
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -73,6 +73,8 @@ public slots:
|
|||
void setAudioSync(bool);
|
||||
void setFrameskip(int);
|
||||
void setTurbo(bool, bool forced = true);
|
||||
void setAVStream(GBAAVStream*);
|
||||
void clearAVStream();
|
||||
|
||||
#ifdef BUILD_SDL
|
||||
private slots:
|
||||
|
|
|
@ -2,14 +2,183 @@
|
|||
|
||||
#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
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
|
||||
#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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -68,12 +68,7 @@
|
|||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Xvid</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Uncompressed</string>
|
||||
<string>FFV1</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
|
|
|
@ -148,6 +148,8 @@ void Window::selectPatch() {
|
|||
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();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue