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