all repos — mgba @ f4f6ff902ca4d5d5b5b8ec092ef4317aaa6de9b9

mGBA Game Boy Advance Emulator

src/platform/qt/VideoView.cpp (view raw)

  1#include "VideoView.h"
  2
  3#ifdef USE_FFMPEG
  4
  5#include <QFileDialog>
  6#include <QMap>
  7
  8using namespace QGBA;
  9
 10QMap<QString, QString> VideoView::s_acodecMap;
 11QMap<QString, QString> VideoView::s_vcodecMap;
 12QMap<QString, QString> VideoView::s_containerMap;
 13
 14VideoView::VideoView(QWidget* parent)
 15	: QWidget(parent)
 16	, m_audioCodecCstr(nullptr)
 17	, m_videoCodecCstr(nullptr)
 18	, m_containerCstr(nullptr)
 19{
 20	m_ui.setupUi(this);
 21
 22	if (s_acodecMap.empty()) {
 23		s_acodecMap["aac"] = "libfaac";
 24		s_acodecMap["mp3"] = "libmp3lame";
 25		s_acodecMap["uncompressed"] = "pcm_s16le";
 26	}
 27	if (s_vcodecMap.empty()) {
 28		s_vcodecMap["h264"] = "libx264rgb";
 29	}
 30	if (s_containerMap.empty()) {
 31		s_containerMap["mkv"] = "matroska";
 32	}
 33
 34	connect(m_ui.buttonBox, SIGNAL(rejected()), this, SLOT(close()));
 35	connect(m_ui.start, SIGNAL(clicked()), this, SLOT(startRecording()));
 36	connect(m_ui.stop, SIGNAL(clicked()), this, SLOT(stopRecording()));
 37
 38	connect(m_ui.selectFile, SIGNAL(clicked()), this, SLOT(selectFile()));
 39	connect(m_ui.filename, SIGNAL(textChanged(const QString&)), this, SLOT(setFilename(const QString&)));
 40
 41	connect(m_ui.audio, SIGNAL(activated(const QString&)), this, SLOT(setAudioCodec(const QString&)));
 42	connect(m_ui.video, SIGNAL(activated(const QString&)), this, SLOT(setVideoCodec(const QString&)));
 43	connect(m_ui.container, SIGNAL(activated(const QString&)), this, SLOT(setContainer(const QString&)));
 44
 45	connect(m_ui.abr, SIGNAL(valueChanged(int)), this, SLOT(setAudioBitrate(int)));
 46	connect(m_ui.vbr, SIGNAL(valueChanged(int)), this, SLOT(setVideoBitrate(int)));
 47
 48	FFmpegEncoderInit(&m_encoder);
 49
 50	setAudioCodec(m_ui.audio->currentText());
 51	setVideoCodec(m_ui.video->currentText());
 52	setAudioBitrate(m_ui.abr->value());
 53	setVideoBitrate(m_ui.vbr->value());
 54	setContainer(m_ui.container->currentText());
 55}
 56
 57VideoView::~VideoView() {
 58	stopRecording();
 59	free(m_audioCodecCstr);
 60	free(m_videoCodecCstr);
 61	free(m_containerCstr);
 62}
 63
 64void VideoView::startRecording() {
 65	if (!validateSettings()) {
 66		return;
 67	}
 68	if (!FFmpegEncoderOpen(&m_encoder, m_filename.toLocal8Bit().constData())) {
 69		return;
 70	}
 71	m_ui.start->setEnabled(false);
 72	m_ui.stop->setEnabled(true);
 73	emit recordingStarted(&m_encoder.d);
 74}
 75
 76void VideoView::stopRecording() {
 77	emit recordingStopped();
 78	FFmpegEncoderClose(&m_encoder);
 79	m_ui.stop->setEnabled(false);
 80	validateSettings();
 81}
 82
 83void VideoView::selectFile() {
 84	QString filename = QFileDialog::getSaveFileName(this, tr("Select output file"));
 85	if (!filename.isEmpty()) {
 86		m_ui.filename->setText(filename);
 87	}
 88}
 89
 90void VideoView::setFilename(const QString& fname) {
 91	m_filename = fname;
 92	validateSettings();
 93}
 94
 95void VideoView::setAudioCodec(const QString& codec) {
 96	free(m_audioCodecCstr);
 97	m_audioCodec = sanitizeCodec(codec);
 98	if (s_acodecMap.contains(m_audioCodec)) {
 99		m_audioCodec = s_acodecMap[m_audioCodec];
100	}
101	m_audioCodecCstr = strdup(m_audioCodec.toLocal8Bit().constData());
102	if (!FFmpegEncoderSetAudio(&m_encoder, m_audioCodecCstr, m_abr)) {
103		free(m_audioCodecCstr);
104		m_audioCodecCstr = nullptr;
105	}
106	validateSettings();
107}
108
109void VideoView::setVideoCodec(const QString& codec) {
110	free(m_videoCodecCstr);
111	m_videoCodec = sanitizeCodec(codec);
112	if (s_vcodecMap.contains(m_videoCodec)) {
113		m_videoCodec = s_vcodecMap[m_videoCodec];
114	}
115	m_videoCodecCstr = strdup(m_videoCodec.toLocal8Bit().constData());
116	if (!FFmpegEncoderSetVideo(&m_encoder, m_videoCodecCstr, m_vbr)) {
117		free(m_videoCodecCstr);
118		m_videoCodecCstr = nullptr;
119	}
120	validateSettings();
121}
122
123void VideoView::setContainer(const QString& container) {
124	free(m_containerCstr);
125	m_container = sanitizeCodec(container);
126	if (s_containerMap.contains(m_container)) {
127		m_container = s_containerMap[m_container];
128	}
129	m_containerCstr = strdup(m_container.toLocal8Bit().constData());
130	if (!FFmpegEncoderSetContainer(&m_encoder, m_containerCstr)) {
131		free(m_containerCstr);
132		m_containerCstr = nullptr;
133	}
134	validateSettings();
135}
136
137void VideoView::setAudioBitrate(int br) {
138	m_abr = br * 1000;
139	FFmpegEncoderSetAudio(&m_encoder, m_audioCodecCstr, m_abr);
140	validateSettings();
141}
142
143void VideoView::setVideoBitrate(int br) {
144	m_abr = br * 1000;
145	FFmpegEncoderSetVideo(&m_encoder, m_videoCodecCstr, m_vbr);
146	validateSettings();
147}
148
149bool VideoView::validateSettings() {
150	bool valid = true;
151	if (!m_audioCodecCstr) {
152		valid = false;
153		m_ui.audio->setStyleSheet("QComboBox { color: red; }");
154	} else {
155		m_ui.audio->setStyleSheet("");
156	}
157
158	if (!m_videoCodecCstr) {
159		valid = false;
160		m_ui.video->setStyleSheet("QComboBox { color: red; }");
161	} else {
162		m_ui.video->setStyleSheet("");
163	}
164
165	if (!m_containerCstr) {
166		valid = false;
167		m_ui.container->setStyleSheet("QComboBox { color: red; }");
168	} else {
169		m_ui.container->setStyleSheet("");
170	}
171
172	// This |valid| check is necessary as if one of the cstrs
173	// is null, the encoder likely has a dangling pointer
174	if (valid && !FFmpegEncoderVerifyContainer(&m_encoder)) {
175		valid = false;
176	}
177
178	m_ui.start->setEnabled(valid && !m_filename.isNull());
179	return valid;
180}
181
182QString VideoView::sanitizeCodec(const QString& codec) {
183	QString sanitized = codec.toLower();
184	return sanitized.remove(QChar('.'));
185}
186
187#endif