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