all repos — mgba @ c2564c77a9902fa0d05fd8fe0f6a1650959d3546

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