1 /******************************************************************************
2     QtAV:  Multimedia framework based on Qt and FFmpeg
3     Copyright (C) 2012-2019 Wang Bin <wbsecg1@gmail.com>
4 
5 *   This file is part of QtAV
6 
7     This library is free software; you can redistribute it and/or
8     modify it under the terms of the GNU Lesser General Public
9     License as published by the Free Software Foundation; either
10     version 2.1 of the License, or (at your option) any later version.
11 
12     This library is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15     Lesser General Public License for more details.
16 
17     You should have received a copy of the GNU Lesser General Public
18     License along with this library; if not, write to the Free Software
19     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20 ******************************************************************************/
21 
22 #include "QtAV/QtAV_Global.h"
23 #include <QtCore/QLibraryInfo>
24 #include <QtCore/QObject>
25 #include <QtCore/QRegExp>
26 #include "QtAV/version.h"
27 #include "QtAV/private/AVCompat.h"
28 #include "utils/internal.h"
29 #include "utils/Logger.h"
30 
QtAV_Version()31 unsigned QtAV_Version()
32 {
33     return QTAV_VERSION;
34 }
35 
QtAV_Version_String()36 QString QtAV_Version_String()
37 {
38     // vs<2015: C2308: concatenating mismatched strings for QStringLiteral("a" "b")
39     return QString::fromLatin1(QTAV_VERSION_STR);
40 }
41 
42 #define QTAV_VERSION_STR_LONG   QTAV_VERSION_STR "(" __DATE__ ", " __TIME__ ")"
43 
QtAV_Version_String_Long()44 QString QtAV_Version_String_Long()
45 {
46     return QString::fromLatin1(QTAV_VERSION_STR_LONG);
47 }
48 
49 namespace QtAV {
50 
51 namespace Internal {
52 // disable logging for release. you can manually enable it.
53 #if defined(QT_NO_DEBUG)// && !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(Q_OS_WINRT)
54 static QtAV::LogLevel gLogLevel = QtAV::LogOff;
55 #else
56 static QtAV::LogLevel gLogLevel = QtAV::LogAll;
57 #endif
58 static bool gLogLevelSet = false;
isLogLevelSet()59 bool isLogLevelSet() { return gLogLevelSet;}
60 static int gAVLogLevel = AV_LOG_INFO;
61 } //namespace Internal
62 
63 //TODO: auto add new depend libraries information
aboutFFmpeg_PlainText()64 QString aboutFFmpeg_PlainText()
65 {
66     return aboutFFmpeg_HTML().remove(QRegExp(QStringLiteral("<[^>]*>")));
67 }
68 
69 namespace Internal {
70 typedef struct depend_component {
71     const char* lib;
72     unsigned build_version;
73     unsigned rt_version;
74     const char *config;
75     const char *license;
76 } depend_component;
77 
get_qt_version()78 static unsigned get_qt_version() {
79     int major = 0, minor = 0, patch = 0;
80     if (sscanf(qVersion(), "%d.%d.%d", &major, &minor, &patch) != 3)
81         qWarning("Can not recognize Qt runtime version");
82     return QT_VERSION_CHECK(major, minor, patch);
83 }
84 
get_depend_component(const depend_component * info=0)85 static const depend_component* get_depend_component(const depend_component* info = 0)
86 {
87     // DO NOT use QStringLiteral here because the install script use strings to search "Qt-" in the library. QStringLiteral will place it in .ro and strings can not find it
88     static const QByteArray qt_license(QLibraryInfo::licensee().prepend(QLatin1String("Qt-" QT_VERSION_STR " licensee: ")).toUtf8());
89 #if QT_VERSION >= QT_VERSION_CHECK(5, 3, 0)
90     static const char* qt_build_info = get_qt_version() >= QT_VERSION_CHECK(5, 3, 0) ? QLibraryInfo::build() : "";
91 #else
92     static const char* qt_build_info = "";
93 #endif
94     static const depend_component components[] = {
95         {  "Qt", QT_VERSION, get_qt_version(), qt_build_info, qt_license.constData() },
96         //TODO: auto check loaded libraries
97 #define FF_COMPONENT(name, NAME) #name, LIB##NAME##_VERSION_INT, name##_version(), name##_configuration(), name##_license()
98         { FF_COMPONENT(avutil, AVUTIL) },
99         { FF_COMPONENT(avcodec, AVCODEC) },
100         { FF_COMPONENT(avformat, AVFORMAT) },
101 #if QTAV_HAVE(AVFILTER)
102         { FF_COMPONENT(avfilter, AVFILTER) },
103 #endif //QTAV_HAVE(AVFILTER)
104 #if QTAV_HAVE(AVDEVICE)
105         { FF_COMPONENT(avdevice, AVDEVICE) },
106 #endif //QTAV_HAVE(AVDEVICE)
107 #if QTAV_HAVE(AVRESAMPLE)
108         { FF_COMPONENT(avresample, AVRESAMPLE) },
109 #endif //QTAV_HAVE(AVRESAMPLE)
110 #if QTAV_HAVE(SWRESAMPLE)
111         { FF_COMPONENT(swresample, SWRESAMPLE) },
112 #endif //QTAV_HAVE(SWRESAMPLE)
113         { FF_COMPONENT(swscale, SWSCALE) },
114 #undef FF_COMPONENT
115         { 0, 0, 0, 0, 0 }
116     };
117     if (!info)
118         return &components[0];
119     // invalid input ptr
120     if (((ptrdiff_t)info - (ptrdiff_t)(&components[0]))%sizeof(depend_component))
121         return 0;
122     const depend_component *next = info;
123     next++;
124     if (!next->lib)
125         return 0;
126     return next;
127 }
128 
print_library_info()129 void print_library_info()
130 {
131     qDebug() << aboutQtAV_PlainText().toUtf8().constData();
132     const depend_component* info = Internal::get_depend_component(0);
133     while (info) {
134         if (!qstrcmp(info->lib, "avutil"))
135             qDebug("FFmpeg/Libav configuration: %s", info->config);
136         qDebug("Build with %s-%u.%u.%u"
137                , info->lib
138                , QTAV_VERSION_MAJOR(info->build_version)
139                , QTAV_VERSION_MINOR(info->build_version)
140                , QTAV_VERSION_PATCH(info->build_version)
141                );
142         unsigned rt_version = info->rt_version;
143         if (info->build_version != rt_version) {
144             qWarning("Warning: %s runtime version %u.%u.%u mismatch!"
145                     , info->lib
146                     , QTAV_VERSION_MAJOR(rt_version)
147                     , QTAV_VERSION_MINOR(rt_version)
148                     , QTAV_VERSION_PATCH(rt_version)
149                     );
150         }
151         info = Internal::get_depend_component(info);
152     }
153 }
154 
155 } //namespace Internal
156 
aboutFFmpeg_HTML()157 QString aboutFFmpeg_HTML()
158 {
159     QString text = QStringLiteral("<h3>FFmpeg/Libav</h3>\n");
160     const Internal::depend_component* info = Internal::get_depend_component(0);
161     while (info) {
162         text += QStringLiteral("<h4>%1: %2-%3.%4.%5</h4>\n")
163                 .arg(QObject::tr("Build version"))
164                 .arg(QLatin1String(info->lib))
165                 .arg(QTAV_VERSION_MAJOR(info->build_version))
166                 .arg(QTAV_VERSION_MINOR(info->build_version))
167                 .arg(QTAV_VERSION_PATCH(info->build_version))
168                 ;
169         unsigned rt_version = info->rt_version;
170         if (info->build_version != rt_version) {
171             text += QStringLiteral("<h4 style='color:#ff0000;'>%1: %2.%3.%4</h4>\n")
172                     .arg(QObject::tr("Runtime version"))
173                     .arg(QTAV_VERSION_MAJOR(rt_version))
174                     .arg(QTAV_VERSION_MINOR(rt_version))
175                     .arg(QTAV_VERSION_PATCH(rt_version))
176                     ;
177         }
178         text += QStringLiteral("<p>%1</p>\n<p>%2</p>\n").arg(QString::fromUtf8(info->config)).arg(QString::fromUtf8(info->license));
179         info = Internal::get_depend_component(info);
180     }
181     return text;
182 }
183 
aboutQtAV_PlainText()184 QString aboutQtAV_PlainText()
185 {
186     return aboutQtAV_HTML().remove(QRegExp(QStringLiteral("<[^>]*>")));
187 }
188 
aboutQtAV_HTML()189 QString aboutQtAV_HTML()
190 {
191     static QString about = QString::fromLatin1("<img src='qrc:/QtAV.svg'><h3>QtAV " QTAV_VERSION_STR_LONG "</h3>\n"
192             "<p>%1</p><p>%2</p><p>%3 </p>"
193             "<p>Copyright (C) 2012-2019 Wang Bin (aka. Lucas Wang) <a href='mailto:wbsecg1@gmail.com'>wbsecg1@gmail.com</a></p>\n"
194             "<p>%4: <a href='http://qtav.org/donate.html'>http://qtav.org/donate.html</a></p>\n"
195             "<p>%5: <a href='https://github.com/wang-bin/QtAV'>https://github.com/wang-bin/QtAV</a></p>\n"
196             "<p>%6: <a href='http://qtav.org'>http://qtav.org</a></p>"
197            ).arg(QObject::tr("Multimedia framework base on Qt and FFmpeg.\n"))
198             .arg(QObject::tr("Distributed under the terms of LGPLv2.1 or later.\n"))
199             .arg(QObject::tr("Shanghai, China"))
200             .arg(QObject::tr("Donate"))
201             .arg(QObject::tr("Source"))
202             .arg(QObject::tr("Home page"));
203     return about;
204 }
205 
setLogLevel(LogLevel value)206 void setLogLevel(LogLevel value)
207 {
208     Internal::gLogLevelSet = true;
209     Internal::gLogLevel = value;
210 }
211 
logLevel()212 LogLevel logLevel()
213 {
214     return (LogLevel)Internal::gLogLevel;
215 }
216 
setFFmpegLogHandler(void (* callback)(void *,int,const char *,va_list))217 void setFFmpegLogHandler(void (*callback)(void *, int, const char *, va_list))
218 {
219     // libav does not check null callback
220     if (!callback)
221         callback = av_log_default_callback;
222     av_log_set_callback(callback);
223 }
224 
setFFmpegLogLevel(const QByteArray & level)225 void setFFmpegLogLevel(const QByteArray &level)
226 {
227     if (level.isEmpty())
228         return;
229     bool ok = false;
230     const int value = level.toInt(&ok);
231     if ((ok && value == 0) || level == "off" || level == "quiet")
232         Internal::gAVLogLevel = AV_LOG_QUIET;
233     else if (level == "panic")
234         Internal::gAVLogLevel = AV_LOG_PANIC;
235     else if (level == "fatal")
236         Internal::gAVLogLevel = AV_LOG_FATAL;
237     else if (level == "error")
238         Internal::gAVLogLevel = AV_LOG_ERROR;
239     else if (level.startsWith("warn"))
240         Internal::gAVLogLevel = AV_LOG_WARNING;
241     else if (level == "info")
242         Internal::gAVLogLevel = AV_LOG_INFO;
243     else if (level == "verbose")
244         Internal::gAVLogLevel = AV_LOG_VERBOSE;
245     else if (level == "debug")
246         Internal::gAVLogLevel = AV_LOG_DEBUG;
247 #ifdef AV_LOG_TRACE
248     else if (level == "trace")
249         Internal::gAVLogLevel = AV_LOG_TRACE;
250 #endif
251     else
252         Internal::gAVLogLevel = AV_LOG_INFO;
253     av_log_set_level(Internal::gAVLogLevel);
254 }
255 
qtav_ffmpeg_log_callback(void * ctx,int level,const char * fmt,va_list vl)256 static void qtav_ffmpeg_log_callback(void* ctx, int level,const char* fmt, va_list vl)
257 {
258     // AV_LOG_DEBUG is used by ffmpeg developers
259     if (level > Internal::gAVLogLevel)
260         return;
261     AVClass *c = ctx ? *(AVClass**)ctx : 0;
262     QString qmsg = QString().sprintf("[FFmpeg:%s] ", c ? c->item_name(ctx) : "?") + QString().vsprintf(fmt, vl);
263     qmsg = qmsg.trimmed();
264     if (level > AV_LOG_WARNING)
265         qDebug() << qPrintable(qmsg);
266     else if (level > AV_LOG_PANIC)
267         qWarning() << qPrintable(qmsg);
268 }
269 
avformatOptions()270 QString avformatOptions()
271 {
272     static QString opts;
273     if (!opts.isEmpty())
274         return opts;
275     void* obj =  const_cast<void*>(reinterpret_cast<const void*>(avformat_get_class()));
276     opts = Internal::optionsToString((void*)&obj);
277     opts.append(ushort('\n'));
278 #if AVFORMAT_STATIC_REGISTER
279     const AVInputFormat *i = NULL;
280     void* it = NULL;
281     while ((i = av_demuxer_iterate(&it))) {
282 #else
283     AVInputFormat *i = NULL;
284     av_register_all(); // MUST register all input/output formats
285     while ((i = av_iformat_next(i))) {
286 #endif
287         QString opt(Internal::optionsToString((void*)&i->priv_class).trimmed());
288         if (opt.isEmpty())
289             continue;
290         opts.append(QStringLiteral("options for input format %1:\n%2\n\n")
291                     .arg(QLatin1String(i->name))
292                     .arg(opt));
293     }
294 #if AVFORMAT_STATIC_REGISTER
295     const AVOutputFormat *o = NULL;
296     it = NULL;
297     while ((o = av_muxer_iterate(&it))) {
298 #else
299     av_register_all(); // MUST register all input/output formats
300     AVOutputFormat *o = NULL;
301     while ((o = av_oformat_next(o))) {
302 #endif
303         QString opt(Internal::optionsToString((void*)&o->priv_class).trimmed());
304         if (opt.isEmpty())
305             continue;
306         opts.append(QStringLiteral("options for output format %1:\n%2\n\n")
307                     .arg(QLatin1String(o->name))
308                     .arg(opt));
309     }
310     return opts;
311 }
312 
313 QString avcodecOptions()
314 {
315     static QString opts;
316     if (!opts.isEmpty())
317         return opts;
318     void* obj = const_cast<void*>(reinterpret_cast<const void*>(avcodec_get_class()));
319     opts = Internal::optionsToString((void*)&obj);
320     opts.append(ushort('\n'));
321     const AVCodec* c = NULL;
322 #if AVCODEC_STATIC_REGISTER
323     void* it = NULL;
324     while ((c = av_codec_iterate(&it))) {
325 #else
326     avcodec_register_all();
327     while ((c = av_codec_next(c))) {
328 #endif
329         QString opt(Internal::optionsToString((void*)&c->priv_class).trimmed());
330         if (opt.isEmpty())
331             continue;
332         opts.append(QStringLiteral("Options for codec %1:\n%2\n\n").arg(QLatin1String(c->name)).arg(opt));
333     }
334     return opts;
335 }
336 
337 #if 0
338 const QStringList& supportedInputMimeTypes()
339 {
340     static QStringList mimes;
341     if (!mimes.isEmpty())
342         return mimes;
343     av_register_all(); // MUST register all input/output formats
344     AVOutputFormat *i = av_oformat_next(NULL);
345     QStringList list;
346     while (i) {
347         list << QString(i->mime_type).split(QLatin1Char(','), QString::SkipEmptyParts);
348         i = av_oformat_next(i);
349     }
350     foreach (const QString& v, list) {
351         mimes.append(v.trimmed());
352     }
353     mimes.removeDuplicates();
354     return mimes;
355 }
356 
357 static QStringList s_audio_mimes, s_video_mimes, s_subtitle_mimes;
358 static void init_supported_codec_info() {
359     const AVCodecDescriptor* cd = avcodec_descriptor_next(NULL);
360     while (cd) {
361         QStringList list;
362         if (cd->mime_types) {
363             for (int i = 0; cd->mime_types[i]; ++i) {
364                 list.append(QString(cd->mime_types[i]).trimmed());
365             }
366         }
367         switch (cd->type) {
368         case AVMEDIA_TYPE_AUDIO:
369             s_audio_mimes << list;
370             break;
371         case AVMEDIA_TYPE_VIDEO:
372             s_video_mimes << list;
373         case AVMEDIA_TYPE_SUBTITLE:
374             s_subtitle_mimes << list;
375         default:
376             break;
377         }
378         cd = avcodec_descriptor_next(cd);
379     }
380     s_audio_mimes.removeDuplicates();
381     s_video_mimes.removeDuplicates();
382     s_subtitle_mimes.removeDuplicates();
383 }
384 const QStringList& supportedAudioMimeTypes()
385 {
386     if (s_audio_mimes.isEmpty())
387         init_supported_codec_info();
388     return s_audio_mimes;
389 }
390 
391 const QStringList& supportedVideoMimeTypes()
392 {
393     if (s_video_mimes.isEmpty())
394         init_supported_codec_info();
395     return s_video_mimes;
396 }
397 // TODO: subtitleprocessor support
398 const QStringList& supportedSubtitleMimeTypes()
399 {
400     if (s_subtitle_mimes.isEmpty())
401         init_supported_codec_info();
402     return s_subtitle_mimes;
403 }
404 #endif
405 
406 
407 /*
408  * AVColorSpace:
409  * libav11 libavutil54.3.0 pixfmt.h, ffmpeg2.1*libavutil52.48.101 frame.h
410  * ffmpeg2.5 pixfmt.h. AVFrame.colorspace
411  * earlier versions: avcodec.h, avctx.colorspace
412  */
413 ColorSpace colorSpaceFromFFmpeg(AVColorSpace cs)
414 {
415     switch (cs) {
416     // from ffmpeg: order of coefficients is actually GBR
417     case AVCOL_SPC_RGB: return ColorSpace_GBR;
418     case AVCOL_SPC_BT709: return ColorSpace_BT709;
419     case AVCOL_SPC_BT470BG: return ColorSpace_BT601;
420     case AVCOL_SPC_SMPTE170M: return ColorSpace_BT601;
421     default: return ColorSpace_Unknown;
422     }
423 }
424 
425 ColorRange colorRangeFromFFmpeg(AVColorRange cr)
426 {
427     switch (cr) {
428     case AVCOL_RANGE_MPEG: return ColorRange_Limited;
429     case AVCOL_RANGE_JPEG: return ColorRange_Full;
430     default: return ColorRange_Unknown;
431     }
432 }
433 
434 namespace
435 {
436 static const struct RegisterMetaTypes {
437     RegisterMetaTypes() {
438         qRegisterMetaType<QtAV::MediaStatus>("QtAV::MediaStatus");
439     }
440 } _registerMetaTypes;
441 }
442 
443 // TODO: static link. move all into 1
444 namespace {
445 class InitFFmpegLog {
446 public:
447     InitFFmpegLog() {
448         setFFmpegLogHandler(qtav_ffmpeg_log_callback);
449         QtAV::setFFmpegLogLevel(qgetenv("QTAV_FFMPEG_LOG").toLower());
450     }
451 };
452 InitFFmpegLog fflog;
453 }
454 }
455 
456 // Initialize Qt Resource System when the library is built
457 // statically
458 static void initResources() {
459     Q_INIT_RESOURCE(shaders);
460     Q_INIT_RESOURCE(QtAV);
461 }
462 
463 namespace {
464     class ResourceLoader {
465     public:
466         ResourceLoader() { initResources(); }
467     };
468 
469     ResourceLoader QtAV_QRCLoader;
470 }
471