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