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