QTfrontend/util/LibavInteraction.cpp
branchhedgeroid
changeset 15515 7030706266df
parent 14636 92ebe33c5eb6
equal deleted inserted replaced
7861:bc7b6aa5d67a 15515:7030706266df
       
     1 /*
       
     2  * Hedgewars, a free turn based strategy game
       
     3  * Copyright (c) 2004-2015 Andrey Korotaev <unC0Rr@gmail.com>
       
     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; version 2 of the License
       
     8  *
       
     9  * This program is distributed in the hope that it will be useful,
       
    10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       
    12  * GNU General Public License for more details.
       
    13  *
       
    14  * You should have received a copy of the GNU General Public License
       
    15  * along with this program; if not, write to the Free Software
       
    16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
       
    17  */
       
    18 
       
    19 #include "LibavInteraction.h"
       
    20 
       
    21 #ifdef VIDEOREC
       
    22 extern "C"
       
    23 {
       
    24 #include "libavcodec/avcodec.h"
       
    25 #include "libavformat/avformat.h"
       
    26 #include "libavutil/avutil.h"
       
    27 }
       
    28 
       
    29 #include <QVector>
       
    30 #include <QList>
       
    31 #include <QComboBox>
       
    32 
       
    33 #include "HWApplication.h"
       
    34 
       
    35 // compatibility section
       
    36 #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54, 8, 0)
       
    37 #define av_codec_is_encoder(x)          x->encode
       
    38 #endif
       
    39 
       
    40 #if LIBAVCODEC_VERSION_MAJOR < 55
       
    41 #define AVCodecID                       CodecID
       
    42 #endif
       
    43 
       
    44 #if LIBAVFORMAT_VERSION_MAJOR < 54
       
    45 #define avformat_find_stream_info(x, y) av_find_stream_info(x)
       
    46 #define avformat_close_input(x)         av_close_input_file(*(x))
       
    47 #endif
       
    48 
       
    49 #if LIBAVUTIL_VERSION_MAJOR < 54
       
    50 #define AVPixelFormat                   PixelFormat
       
    51 #define AV_PIX_FMT_YUV420P              PIX_FMT_YUV420P
       
    52 #endif
       
    53 
       
    54 struct Codec
       
    55 {
       
    56     AVCodecID id;
       
    57     bool isAudio;
       
    58     QString shortName; // used for identification
       
    59     QString longName; // used for displaying to user
       
    60     bool isRecommended;
       
    61 };
       
    62 
       
    63 struct Format
       
    64 {
       
    65     QString shortName;
       
    66     QString longName;
       
    67     bool isRecommended;
       
    68     QString extension;
       
    69     QVector<Codec*> codecs;
       
    70 };
       
    71 
       
    72 QList<Codec> codecs;
       
    73 QMap<QString,Format> formats;
       
    74 
       
    75 // test if given format supports given codec
       
    76 bool FormatQueryCodec(AVOutputFormat *ofmt, enum AVCodecID codec_id)
       
    77 {
       
    78 #if LIBAVFORMAT_VERSION_MAJOR >= 54
       
    79     return avformat_query_codec(ofmt, codec_id, FF_COMPLIANCE_NORMAL) == 1;
       
    80 #else
       
    81     if (ofmt->codec_tag)
       
    82         return !!av_codec_get_tag(ofmt->codec_tag, codec_id);
       
    83     return codec_id == ofmt->video_codec || codec_id == ofmt->audio_codec;
       
    84 #endif
       
    85 }
       
    86 
       
    87 LibavInteraction::LibavInteraction() : QObject()
       
    88 {
       
    89     // initialize libav and register all codecs and formats
       
    90     av_register_all();
       
    91 
       
    92     // get list of all codecs
       
    93     AVCodec* pCodec = NULL;
       
    94     while ((pCodec = av_codec_next(pCodec)))
       
    95     {
       
    96         if (!av_codec_is_encoder(pCodec))
       
    97             continue;
       
    98 
       
    99         if (pCodec->type != AVMEDIA_TYPE_VIDEO && pCodec->type != AVMEDIA_TYPE_AUDIO)
       
   100             continue;
       
   101 
       
   102         // this encoders seems to be buggy
       
   103         if (strcmp(pCodec->name, "rv10") == 0 || strcmp(pCodec->name, "rv20") == 0)
       
   104             continue;
       
   105 
       
   106         // this encoder is experimental (as of Jan 17, 2019)
       
   107         if (strcmp(pCodec->name, "libaom-av1") == 0)
       
   108             continue;
       
   109 
       
   110         // doesn't support stereo sound
       
   111         if (strcmp(pCodec->name, "real_144") == 0)
       
   112             continue;
       
   113 
       
   114         if (!pCodec->long_name || strlen(pCodec->long_name) == 0)
       
   115             continue;
       
   116 
       
   117         if (pCodec->type == AVMEDIA_TYPE_VIDEO)
       
   118         {
       
   119             if (pCodec->supported_framerates != NULL)
       
   120                 continue;
       
   121 
       
   122             // check if codec supports yuv 4:2:0 format
       
   123             if (!pCodec->pix_fmts)
       
   124                 continue;
       
   125             bool yuv420Supported = false;
       
   126             for (const enum AVPixelFormat* pfmt = pCodec->pix_fmts; *pfmt != -1; pfmt++)
       
   127                 if (*pfmt == AV_PIX_FMT_YUV420P)
       
   128                 {
       
   129                     yuv420Supported = true;
       
   130                     break;
       
   131                 }
       
   132             if (!yuv420Supported)
       
   133                 continue;
       
   134         }
       
   135         if (pCodec->type == AVMEDIA_TYPE_AUDIO)
       
   136         {
       
   137             // check if codec supports signed 16-bit format
       
   138             if (!pCodec->sample_fmts)
       
   139                 continue;
       
   140             bool s16Supported = false;
       
   141             for (const enum AVSampleFormat* pfmt = pCodec->sample_fmts; *pfmt != -1; pfmt++)
       
   142                 if (*pfmt == AV_SAMPLE_FMT_S16)
       
   143                 {
       
   144                     s16Supported = true;
       
   145                     break;
       
   146                 }
       
   147             if (!s16Supported)
       
   148                 continue;
       
   149         }
       
   150         // add codec to list of codecs
       
   151         codecs.push_back(Codec());
       
   152         Codec & codec = codecs.back();
       
   153         codec.id = pCodec->id;
       
   154         codec.isAudio = pCodec->type == AVMEDIA_TYPE_AUDIO;
       
   155         codec.shortName = pCodec->name;
       
   156         codec.longName = pCodec->long_name;
       
   157 
       
   158         codec.isRecommended = false;
       
   159         if (strcmp(pCodec->name, "libx264") == 0)
       
   160         {
       
   161             codec.longName = "H.264/MPEG-4 Part 10 AVC (x264)";
       
   162             codec.isRecommended = true;
       
   163         }
       
   164         else if (strcmp(pCodec->name, "libxvid") == 0)
       
   165         {
       
   166             codec.longName = "MPEG-4 Part 2 (Xvid)";
       
   167             codec.isRecommended = true;
       
   168         }
       
   169         else if (strcmp(pCodec->name, "libmp3lame") == 0)
       
   170         {
       
   171             codec.longName = "MP3 (MPEG audio layer 3) (LAME)";
       
   172             codec.isRecommended = true;
       
   173         }
       
   174         else
       
   175             codec.longName = pCodec->long_name;
       
   176 
       
   177         if (strcmp(pCodec->name, "mpeg4") == 0 || strcmp(pCodec->name, "ac3_fixed") == 0)
       
   178             codec.isRecommended = true;
       
   179     }
       
   180 
       
   181     // get list of all formats
       
   182     AVOutputFormat* pFormat = NULL;
       
   183     while ((pFormat = av_oformat_next(pFormat)))
       
   184     {
       
   185         if (!pFormat->extensions)
       
   186             continue;
       
   187 
       
   188         // skip some strange formats to not confuse users
       
   189         if (strstr(pFormat->long_name, "raw"))
       
   190             continue;
       
   191 
       
   192         Format format;
       
   193         bool hasVideoCodec = false;
       
   194         for (QList<Codec>::iterator codec = codecs.begin(); codec != codecs.end(); ++codec)
       
   195         {
       
   196             if (!FormatQueryCodec(pFormat, codec->id))
       
   197                 continue;
       
   198             format.codecs.push_back(&*codec);
       
   199             if (!codec->isAudio)
       
   200                 hasVideoCodec = true;
       
   201         }
       
   202         if (!hasVideoCodec)
       
   203             continue;
       
   204 
       
   205         QString ext(pFormat->extensions);
       
   206         ext.truncate(strcspn(pFormat->extensions, ","));
       
   207         format.extension = ext;
       
   208         format.shortName = pFormat->name;
       
   209         format.longName = QString("%1 (*.%2)").arg(pFormat->long_name).arg(ext);
       
   210 
       
   211         format.isRecommended = strcmp(pFormat->name, "mp4") == 0 || strcmp(pFormat->name, "avi") == 0;
       
   212 
       
   213         formats[pFormat->name] = format;
       
   214     }
       
   215 }
       
   216 
       
   217 void LibavInteraction::fillFormats(QComboBox * pFormats)
       
   218 {
       
   219     // first insert recomended formats
       
   220     foreach(const Format & format, formats)
       
   221         if (format.isRecommended)
       
   222             pFormats->addItem(format.longName, format.shortName);
       
   223 
       
   224     // remember where to place separator between recomended and other formats
       
   225     int sep = pFormats->count();
       
   226 
       
   227     // insert remaining formats
       
   228     foreach(const Format & format, formats)
       
   229         if (!format.isRecommended)
       
   230             pFormats->addItem(format.longName, format.shortName);
       
   231 
       
   232     // insert separator if necessary
       
   233     if (sep != 0 && sep != pFormats->count())
       
   234         pFormats->insertSeparator(sep);
       
   235 }
       
   236 
       
   237 void LibavInteraction::fillCodecs(const QString & fmt, QComboBox * pVCodecs, QComboBox * pACodecs)
       
   238 {
       
   239     Format & format = formats[fmt];
       
   240 
       
   241     // first insert recomended codecs
       
   242     foreach(Codec * codec, format.codecs)
       
   243     {
       
   244         if (codec->isRecommended)
       
   245         {
       
   246             if (codec->isAudio)
       
   247                 pACodecs->addItem(codec->longName, codec->shortName);
       
   248             else
       
   249                 pVCodecs->addItem(codec->longName, codec->shortName);
       
   250         }
       
   251     }
       
   252 
       
   253     // remember where to place separators between recomended and other codecs
       
   254     int vsep = pVCodecs->count();
       
   255     int asep = pACodecs->count();
       
   256 
       
   257     // insert remaining codecs
       
   258     foreach(Codec * codec, format.codecs)
       
   259     {
       
   260         if (!codec->isRecommended)
       
   261         {
       
   262             if (codec->isAudio)
       
   263                 pACodecs->addItem(codec->longName, codec->shortName);
       
   264             else
       
   265                 pVCodecs->addItem(codec->longName, codec->shortName);
       
   266         }
       
   267     }
       
   268 
       
   269     // insert separators if necessary
       
   270     if (vsep != 0 && vsep != pVCodecs->count())
       
   271         pVCodecs->insertSeparator(vsep);
       
   272     if (asep != 0 && asep != pACodecs->count())
       
   273         pACodecs->insertSeparator(asep);
       
   274 }
       
   275 
       
   276 QString LibavInteraction::getExtension(const QString & format)
       
   277 {
       
   278     return formats[format].extension;
       
   279 }
       
   280 
       
   281 // get information abaout file (duration, resolution etc) in multiline string
       
   282 QString LibavInteraction::getFileInfo(const QString & filepath)
       
   283 {
       
   284     AVFormatContext* pContext = NULL;
       
   285     QByteArray utf8path = filepath.toUtf8();
       
   286     if (avformat_open_input(&pContext, utf8path.data(), NULL, NULL) < 0)
       
   287         return "";
       
   288     if (avformat_find_stream_info(pContext, NULL) < 0)
       
   289         return "";
       
   290 
       
   291     int s = float(pContext->duration)/AV_TIME_BASE;
       
   292     //: Duration in minutes and seconds (SI units)
       
   293     QString desc = tr("Duration: %1min %2s").arg(s/60).arg(s%60) + "\n";
       
   294     for (int i = 0; i < (int)pContext->nb_streams; i++)
       
   295     {
       
   296         AVStream* pStream = pContext->streams[i];
       
   297         if (!pStream)
       
   298             continue;
       
   299         AVCodecContext* pCodec = pContext->streams[i]->codec;
       
   300         if (!pCodec)
       
   301             continue;
       
   302 
       
   303 
       
   304         AVCodec* pDecoder = avcodec_find_decoder(pCodec->codec_id);
       
   305         QString decoderName = pDecoder ? pDecoder->name : tr("unknown");
       
   306         if (pCodec->codec_type == AVMEDIA_TYPE_VIDEO)
       
   307         {
       
   308             if (pStream->avg_frame_rate.den)
       
   309             {
       
   310                 float fps = float(pStream->avg_frame_rate.num)/pStream->avg_frame_rate.den;
       
   311                 //: Video metadata. %1 = video width, %2 = video height, %3 = frames per second = %4 = decoder name
       
   312                 desc += QString(tr("Video: %1x%2, %3 FPS, %4")).arg(pCodec->width).arg(pCodec->height).arg(QLocale().toString(fps, 'f', 2)).arg(decoderName);
       
   313             }
       
   314             else
       
   315             {
       
   316                 //: Video metadata. %1 = video width, %2 = video height, %3 = decoder name
       
   317                 desc += QString(tr("Video: %1x%2, %3")).arg(pCodec->width).arg(pCodec->height).arg(decoderName);
       
   318             }
       
   319         }
       
   320         else if (pCodec->codec_type == AVMEDIA_TYPE_AUDIO)
       
   321         {
       
   322             desc += tr("Audio: ");
       
   323             desc += decoderName;
       
   324         }
       
   325         else
       
   326             continue;
       
   327         desc += "\n";
       
   328     }
       
   329     AVDictionaryEntry* pComment = av_dict_get(pContext->metadata, "comment", NULL, 0);
       
   330     if (pComment)
       
   331     {
       
   332         // Video comment. We expect a simple key value storage in a particular format
       
   333         // and parse it here so the key names can be localized.
       
   334         desc += QString("\n");
       
   335         QStringList strings = QString(pComment->value).split('\n');
       
   336         QString sPlayer, sTheme, sMap, sRecord;
       
   337         for(int i=0; i < strings.count(); i++)
       
   338         {
       
   339             QString s = strings.at(i);
       
   340             // Original key names are in English, like:
       
   341             //     Key: Value
       
   342             if (s.startsWith("Player: "))
       
   343                 sPlayer = QString(s.mid(8));
       
   344             else if (s.startsWith("Theme: "))
       
   345                 sTheme = QString(s.mid(7));
       
   346             else if (s.startsWith("Map: "))
       
   347                 sMap = QString(s.mid(5));
       
   348             else if (s.startsWith("Record: "))
       
   349                 sRecord = QString(s.mid(8));
       
   350         }
       
   351         if(!sPlayer.isNull())
       
   352             desc += QString(tr("Player: %1")).arg(sPlayer) + "\n";
       
   353         if(!sTheme.isNull())
       
   354             desc += QString(tr("Theme: %1")).arg(sTheme) + "\n";
       
   355         if(!sMap.isNull())
       
   356             desc += QString(tr("Map: %1")).arg(sMap) + "\n";
       
   357         if(!sRecord.isNull())
       
   358             //: As in ‘recording’
       
   359             desc += QString(tr("Record: %1")).arg(sRecord);
       
   360     }
       
   361     avformat_close_input(&pContext);
       
   362     return desc;
       
   363 }
       
   364 
       
   365 #else
       
   366 LibavInteraction::LibavInteraction() : QObject()
       
   367 {
       
   368 
       
   369 }
       
   370 
       
   371 void LibavInteraction::fillFormats(QComboBox * pFormats)
       
   372 {
       
   373     Q_UNUSED(pFormats);
       
   374 }
       
   375 
       
   376 void LibavInteraction::fillCodecs(const QString & format, QComboBox * pVCodecs, QComboBox * pACodecs)
       
   377 {
       
   378     Q_UNUSED(format);
       
   379     Q_UNUSED(pVCodecs);
       
   380     Q_UNUSED(pACodecs);
       
   381 }
       
   382 
       
   383 QString LibavInteraction::getExtension(const QString & format)
       
   384 {
       
   385     Q_UNUSED(format);
       
   386 
       
   387     return QString();
       
   388 }
       
   389 
       
   390 QString LibavInteraction::getFileInfo(const QString & filepath)
       
   391 {
       
   392     Q_UNUSED(filepath);
       
   393 
       
   394     return QString();
       
   395 }
       
   396 #endif
       
   397 
       
   398 LibavInteraction & LibavInteraction::instance()
       
   399 {
       
   400     static LibavInteraction instance;
       
   401     return instance;
       
   402 }