all repos — mgba @ 5dcf3a5d43ef5c0c6945a7db8e117c4f7d7bfb5e

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