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