all repos — mgba @ 1a09f93b2795d2d2a0281c9cb3c3ccf9f875751b

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