OSDN Git Service

78139009ba2a68ddb23af69e5918d1dd210b9ba7
[x264-launcher/x264-launcher.git] / src / encoder_x265.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Simple x264 Launcher
3 // Copyright (C) 2004-2014 LoRd_MuldeR <MuldeR2@GMX.de>
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License along
16 // with this program; if not, write to the Free Software Foundation, Inc.,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 //
19 // http://www.gnu.org/licenses/gpl-2.0.txt
20 ///////////////////////////////////////////////////////////////////////////////
21
22 #include "encoder_x265.h"
23
24 #include "global.h"
25 #include "model_options.h"
26 #include "model_status.h"
27 #include "binaries.h"
28 #include "mediainfo.h"
29
30 #include <QStringList>
31 #include <QDir>
32 #include <QRegExp>
33
34 //x265 version info
35 static const unsigned int VERSION_X265_MINIMUM_VER = 14;
36 static const unsigned int VERSION_X265_MINIMUM_REV =  0;
37
38 // ------------------------------------------------------------
39 // Helper Macros
40 // ------------------------------------------------------------
41
42 #define X265_UPDATE_PROGRESS(X) do \
43 { \
44         bool ok[2] = { false, false }; \
45         unsigned int progressInt = (X)->cap(1).toUInt(&ok[0]); \
46         unsigned int progressFrc = (X)->cap(2).toUInt(&ok[1]); \
47         setStatus((pass == 2) ? JobStatus_Running_Pass2 : ((pass == 1) ? JobStatus_Running_Pass1 : JobStatus_Running)); \
48         if(ok[0] && ok[1]) \
49         { \
50                 const double progress = (double(progressInt) / 100.0) + (double(progressFrc) / 1000.0); \
51                 if(!qFuzzyCompare(progress, last_progress)) \
52                 { \
53                         setProgress(floor(progress * 100.0)); \
54                         size_estimate = qFuzzyIsNull(size_estimate) ? estimateSize(m_outputFile, progress) : ((0.667 * size_estimate) + (0.333 * estimateSize(m_outputFile, progress))); \
55                         last_progress = progress; \
56                 } \
57         } \
58         setDetails(tr("%1, est. file size %2").arg(line.mid(offset).trimmed(), sizeToString(qRound64(size_estimate)))); \
59 } \
60 while(0)
61
62 #define REMOVE_CUSTOM_ARG(LIST, ITER, FLAG, PARAM) do \
63 { \
64         if(ITER != LIST.end()) \
65         { \
66                 if((*ITER).compare(PARAM, Qt::CaseInsensitive) == 0) \
67                 { \
68                         log(tr("WARNING: Custom parameter \"" PARAM "\" will be ignored in Pipe'd mode!\n")); \
69                         ITER = LIST.erase(ITER); \
70                         if(ITER != LIST.end()) \
71                         { \
72                                 if(!((*ITER).startsWith("--", Qt::CaseInsensitive))) ITER = LIST.erase(ITER); \
73                         } \
74                         FLAG = true; \
75                 } \
76         } \
77 } \
78 while(0)
79
80 static QString MAKE_NAME(const char *baseName, const OptionsModel *options)
81 {
82         const QString arch = (options->encArch() == OptionsModel::EncArch_x64) ? "x64" : "x86";
83         const QString vari = (options->encVariant() == OptionsModel::EncVariant_HiBit ) ? "16-Bit" : "8-Bit";
84         return QString("%1, %2, %3").arg(QString::fromLatin1(baseName), arch, vari);
85 }
86
87 // ------------------------------------------------------------
88 // Encoder Info
89 // ------------------------------------------------------------
90
91 class X265EncoderInfo : public AbstractEncoderInfo
92 {
93 public:
94         virtual QString getVariantId(const int &variant) const
95         {
96                 switch(variant)
97                 {
98                 case OptionsModel::EncVariant_LoBit:
99                         return QString::fromLatin1("8-Bit");
100                 case OptionsModel::EncVariant_HiBit:
101                         return QString::fromLatin1("16-Bit");
102                 default:
103                         return QString::fromLatin1("N/A");
104                 }
105         }
106
107         virtual QStringList getTunings(void) const
108         {
109                 QStringList tunings;
110                 tunings << "Grain" << "PSNR" << "SSIM" << "FastDecode" << "ZeroLatency";
111                 return tunings;
112         }
113
114         virtual QStringList getProfiles(const int &variant) const
115         {
116                 return QStringList();
117         }
118
119         virtual QStringList supportedOutputFormats(void) const
120         {
121                 QStringList extLst;
122                 extLst << "hevc";
123                 return extLst;
124         }
125
126         virtual bool isRCModeSupported(const int rcMode) const
127         {
128                 switch(rcMode)
129                 {
130                 case OptionsModel::RCMode_CRF:
131                 case OptionsModel::RCMode_CQ:
132                 case OptionsModel::RCMode_2Pass:
133                 case OptionsModel::RCMode_ABR:
134                         return true;
135                 default:
136                         return false;
137                 }
138         }
139
140         virtual bool isInputTypeSupported(const int format) const
141         {
142                 switch(format)
143                 {
144                 case MediaInfo::FILETYPE_YUV4MPEG2:
145                         return true;
146                 default:
147                         return false;
148                 }
149         }
150 };
151
152 static const X265EncoderInfo s_x265EncoderInfo;
153
154 const AbstractEncoderInfo &X265Encoder::getEncoderInfo(void)
155 {
156         return s_x265EncoderInfo;
157 }
158
159 // ------------------------------------------------------------
160 // Constructor & Destructor
161 // ------------------------------------------------------------
162
163 X265Encoder::X265Encoder(JobObject *jobObject, const OptionsModel *options, const SysinfoModel *const sysinfo, const PreferencesModel *const preferences, JobStatus &jobStatus, volatile bool *abort, volatile bool *pause, QSemaphore *semaphorePause, const QString &sourceFile, const QString &outputFile)
164 :
165         AbstractEncoder(jobObject, options, sysinfo, preferences, jobStatus, abort, pause, semaphorePause, sourceFile, outputFile),
166         m_encoderName(MAKE_NAME("x265 (H.265/HEVC)", m_options)),
167         m_binaryFile(ENC_BINARY(sysinfo, options))
168 {
169         if(options->encType() != OptionsModel::EncType_X265)
170         {
171                 THROW("Invalid encoder type!");
172         }
173 }
174
175 X265Encoder::~X265Encoder(void)
176 {
177         /*Nothing to do here*/
178 }
179
180 const QString &X265Encoder::getName(void)
181 {
182         return m_encoderName;
183 }
184
185 // ------------------------------------------------------------
186 // Check Version
187 // ------------------------------------------------------------
188
189 void X265Encoder::checkVersion_init(QList<QRegExp*> &patterns, QStringList &cmdLine)
190 {
191         cmdLine << "--version";
192         patterns << new QRegExp("\\bHEVC\\s+encoder\\s+version\\s+(\\d)\\.(\\d+)\\+(\\d+)\\b", Qt::CaseInsensitive);
193         patterns << new QRegExp("\\bHEVC\\s+encoder\\s+version\\s+(\\d)\\.(\\d+)\\b", Qt::CaseInsensitive);
194 }
195
196 void X265Encoder::checkVersion_parseLine(const QString &line, QList<QRegExp*> &patterns, unsigned int &core, unsigned int &build, bool &modified)
197 {
198         int offset = -1;
199
200         if((offset = patterns[0]->lastIndexIn(line)) >= 0)
201         {
202                 bool ok[3] = { false, false, false };
203                 unsigned int temp[3];
204                 temp[0] = patterns[0]->cap(1).toUInt(&ok[0]);
205                 temp[1] = patterns[0]->cap(2).toUInt(&ok[1]);
206                 temp[2] = patterns[0]->cap(3).toUInt(&ok[2]);
207                 if(ok[0] && ok[1])
208                 {
209                         core = (10 * temp[0]) + temp[1];
210                 }
211                 if(ok[2]) build = temp[2];
212         }
213         else if((offset = patterns[1]->lastIndexIn(line)) >= 0)
214         {
215                 bool ok[2] = { false, false };
216                 unsigned int temp[2];
217                 temp[0] = patterns[1]->cap(1).toUInt(&ok[0]);
218                 temp[1] = patterns[1]->cap(2).toUInt(&ok[1]);
219                 if(ok[0] && ok[1])
220                 {
221                         core = (10 * temp[0]) + temp[1];
222                 }
223                 build = 0;
224         }
225
226         if(!line.isEmpty())
227         {
228                 log(line);
229         }
230 }
231
232 QString X265Encoder::printVersion(const unsigned int &revision, const bool &modified)
233 {
234         unsigned int core, build;
235         splitRevision(revision, core, build);
236
237         return tr("x265 version: %1.%2+%3").arg(QString::number(core / 10), QString::number(core % 10), QString::number(build));
238 }
239
240 bool X265Encoder::isVersionSupported(const unsigned int &revision, const bool &modified)
241 {
242         unsigned int core, build;
243         splitRevision(revision, core, build);
244
245         if((core < VERSION_X265_MINIMUM_VER) || ((core == VERSION_X265_MINIMUM_VER) && (build < VERSION_X265_MINIMUM_REV)))
246         {
247                 log(tr("\nERROR: Your version of x265 is too old! (Minimum required revision is 0.%1+%2)").arg(QString::number(VERSION_X265_MINIMUM_VER), QString::number(VERSION_X265_MINIMUM_REV)));
248                 return false;
249         }
250         else if(core > VERSION_X265_MINIMUM_VER)
251         {
252                 log(tr("\nWARNING: Your version of x265 is newer than the latest tested version, take care!"));
253                 log(tr("This application works best with x265 version %1.%2. Newer versions may work or not.").arg(QString::number(VERSION_X265_MINIMUM_VER / 10), QString::number(VERSION_X265_MINIMUM_VER % 10)));
254         }
255         
256         return true;
257 }
258
259 // ------------------------------------------------------------
260 // Encoding Functions
261 // ------------------------------------------------------------
262
263 void X265Encoder::buildCommandLine(QStringList &cmdLine, const bool &usePipe, const unsigned int &frames, const QString &indexFile, const int &pass, const QString &passLogFile)
264 {
265         double crf_int = 0.0, crf_frc = 0.0;
266
267         switch(m_options->rcMode())
268         {
269         case OptionsModel::RCMode_CQ:
270                 cmdLine << "--qp" << QString::number(qRound(m_options->quantizer()));
271                 break;
272         case OptionsModel::RCMode_CRF:
273                 crf_frc = modf(m_options->quantizer(), &crf_int);
274                 cmdLine << "--crf" << QString("%1.%2").arg(QString::number(qRound(crf_int)), QString::number(qRound(crf_frc * 10.0)));
275                 break;
276         case OptionsModel::RCMode_2Pass:
277         case OptionsModel::RCMode_ABR:
278                 cmdLine << "--bitrate" << QString::number(m_options->bitrate());
279                 break;
280         default:
281                 THROW("Bad rate-control mode !!!");
282                 break;
283         }
284         
285         if((pass == 1) || (pass == 2))
286         {
287                 cmdLine << "--pass" << QString::number(pass);
288                 cmdLine << "--stats" << QDir::toNativeSeparators(passLogFile);
289         }
290
291         cmdLine << "--preset" << m_options->preset().toLower();
292
293         if(!m_options->tune().simplified().isEmpty())
294         {
295                 if(m_options->tune().simplified().compare(QString::fromLatin1(OptionsModel::TUNING_UNSPECIFIED), Qt::CaseInsensitive) != 0)
296                 {
297                         cmdLine << "--tune" << m_options->tune().simplified().toLower();
298                 }
299         }
300
301         if(m_options->profile().compare("auto", Qt::CaseInsensitive) != 0)
302         {
303                 if((m_options->encType() == OptionsModel::EncType_X264) && (m_options->encVariant() == OptionsModel::EncVariant_LoBit))
304                 {
305                         cmdLine << "--profile" << m_options->profile().toLower();
306                 }
307         }
308
309         if(!m_options->customEncParams().isEmpty())
310         {
311                 QStringList customArgs = splitParams(m_options->customEncParams(), m_sourceFile, m_outputFile);
312                 if(usePipe)
313                 {
314                         QStringList::iterator i = customArgs.begin();
315                         while(i != customArgs.end())
316                         {
317                                 bool bModified = false;
318                                 REMOVE_CUSTOM_ARG(customArgs, i, bModified, "--fps");
319                                 REMOVE_CUSTOM_ARG(customArgs, i, bModified, "--frames");
320                                 if(!bModified) i++;
321                         }
322                 }
323                 cmdLine.append(customArgs);
324         }
325
326         cmdLine << "--output" << QDir::toNativeSeparators(m_outputFile);
327         
328         if(usePipe)
329         {
330                 if(frames < 1) THROW("Frames not set!");
331                 cmdLine << "--frames" << QString::number(frames);
332                 cmdLine << "--y4m" << "-";
333         }
334         else
335         {
336                 cmdLine << QDir::toNativeSeparators(m_sourceFile);
337         }
338 }
339
340 void X265Encoder::runEncodingPass_init(QList<QRegExp*> &patterns)
341 {
342         patterns << new QRegExp("\\[(\\d+)\\.(\\d+)%\\].+frames");   //regExpProgress
343         patterns << new QRegExp("indexing.+\\[(\\d+)\\.(\\d+)%\\]"); //regExpIndexing
344         patterns << new QRegExp("^(\\d+) frames:"); //regExpFrameCnt
345         patterns << new QRegExp("\\[\\s*(\\d+)\\.(\\d+)%\\]\\s+(\\d+)/(\\d+)\\s(\\d+).(\\d+)\\s(\\d+).(\\d+)\\s+(\\d+):(\\d+):(\\d+)\\s+(\\d+):(\\d+):(\\d+)"); //regExpModified
346 }
347
348 void X265Encoder::runEncodingPass_parseLine(const QString &line, QList<QRegExp*> &patterns, const int &pass, double &last_progress, double &size_estimate)
349 {
350         int offset = -1;
351         if((offset = patterns[0]->lastIndexIn(line)) >= 0)
352         {
353                 X265_UPDATE_PROGRESS(patterns[0]);
354         }
355         else if((offset = patterns[1]->lastIndexIn(line)) >= 0)
356         {
357                 bool ok = false;
358                 unsigned int progress = patterns[1]->cap(1).toUInt(&ok);
359                 setStatus(JobStatus_Indexing);
360                 if(ok)
361                 {
362                         setProgress(progress);
363                 }
364                 setDetails(line.mid(offset).trimmed());
365         }
366         else if((offset = patterns[2]->lastIndexIn(line)) >= 0)
367         {
368                 setStatus((pass == 2) ? JobStatus_Running_Pass2 : ((pass == 1) ? JobStatus_Running_Pass1 : JobStatus_Running));
369                 setDetails(line.mid(offset).trimmed());
370         }
371         else if((offset = patterns[3]->lastIndexIn(line)) >= 0)
372         {
373                 X265_UPDATE_PROGRESS(patterns[3]);
374         }
375         else if(!line.isEmpty())
376         {
377                 log(line);
378         }
379 }