diff --git a/YUViewLib/YUViewLib.pro b/YUViewLib/YUViewLib.pro index 2ac10025a..761bcd7d0 100644 --- a/YUViewLib/YUViewLib.pro +++ b/YUViewLib/YUViewLib.pro @@ -12,6 +12,7 @@ HEADERS += $$files(src/*.h, true) FORMS += $$files(ui/*.ui, false) INCLUDEPATH += src/ +INCLUDEPATH += $$top_srcdir/submodules RESOURCES += \ images/images.qrc \ diff --git a/YUViewLib/src/common/FileInfo.h b/YUViewLib/src/common/InfoItemAndData.h similarity index 75% rename from YUViewLib/src/common/FileInfo.h rename to YUViewLib/src/common/InfoItemAndData.h index 49a7e924b..78fc533af 100644 --- a/YUViewLib/src/common/FileInfo.h +++ b/YUViewLib/src/common/InfoItemAndData.h @@ -37,26 +37,14 @@ #include /* - * An info item has a name, a text and an optional toolTip. These are used to show them in the - * fileInfoWidget. For example: ["File Name", "file.yuv"] or ["Number Frames", "123"] Another option - * is to show a button. If the user clicks on it, the callback function infoListButtonPressed() for - * the corresponding playlist item is called. + * An info item has a name, a text and an optional description. These are used to show them in the + * fileInfoWidget. For example: ["File Name", "file.yuv"] or ["Number Frames", "123"]. */ struct InfoItem { - InfoItem(const QString &name, - const QString &text, - const QString &toolTip = QString(), - bool button = false, - int buttonID = -1) - : name(name), text(text), button(button), buttonID(buttonID), toolTip(toolTip) - { - } - QString name{}; - QString text{}; - bool button{}; - int buttonID{}; - QString toolTip{}; + std::string name{}; + std::string text{}; + std::string description{}; }; struct InfoData diff --git a/YUViewLib/src/filesource/DataSourceLocalFile.cpp b/YUViewLib/src/filesource/DataSourceLocalFile.cpp new file mode 100644 index 000000000..794dc35ac --- /dev/null +++ b/YUViewLib/src/filesource/DataSourceLocalFile.cpp @@ -0,0 +1,102 @@ +/* This file is part of YUView - The YUV player with advanced analytics toolset + * + * Copyright (C) 2015 Institut für Nachrichtentechnik, RWTH Aachen University, GERMANY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations including + * the two. + * + * You must obey the GNU General Public License in all respects for all + * of the code used other than OpenSSL. If you modify file(s) with this + * exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do + * so, delete this exception statement from your version. If you delete + * this exception statement from all source files in the program, then + * also delete it here. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "DataSourceLocalFile.h" + +DataSourceLocalFile::DataSourceLocalFile(const std::filesystem::path &filePath) +{ + this->file.open(filePath.string(), std::ios_base::in | std::ios_base::binary); +} + +std::vector DataSourceLocalFile::getInfoList() const +{ + if (!this->isReady()) + return {}; + + std::vector infoList; + infoList.push_back( + InfoItem({"File path", this->filePath.string(), "The absolute path of the local file"})); + if (const auto size = this->fileSize()) + infoList.push_back(InfoItem({"File Size", std::to_string(*size)})); + + return infoList; +} + +bool DataSourceLocalFile::atEnd() const +{ + return this->file.eof(); +} + +bool DataSourceLocalFile::isReady() const +{ + return !this->file.fail(); +} + +std::int64_t DataSourceLocalFile::position() const +{ + return this->filePosition; +} + +std::optional DataSourceLocalFile::fileSize() const +{ + if (!this->isFileOpened) + return {}; + + const auto size = std::filesystem::file_size(this->filePath); + return static_cast(size); +} + +bool DataSourceLocalFile::seek(const std::int64_t pos) +{ + if (!this->isReady()) + return false; + + this->file.seekg(static_cast(pos)); + return this->isReady(); +} + +std::int64_t DataSourceLocalFile::read(ByteVector &buffer, const std::int64_t nrBytes) +{ + if (!this->isReady()) + return 0; + + const auto usize = static_cast(nrBytes); + if (buffer.size() < nrBytes) + buffer.resize(usize); + + this->file.read(reinterpret_cast(buffer.data()), usize); + + const auto bytesRead = this->file.gcount(); + + this->filePosition += bytesRead; + return static_cast(bytesRead); +} diff --git a/YUViewLib/src/filesource/DataSourceLocalFile.h b/YUViewLib/src/filesource/DataSourceLocalFile.h new file mode 100644 index 000000000..ba5382c69 --- /dev/null +++ b/YUViewLib/src/filesource/DataSourceLocalFile.h @@ -0,0 +1,60 @@ +/* This file is part of YUView - The YUV player with advanced analytics toolset + * + * Copyright (C) 2015 Institut für Nachrichtentechnik, RWTH Aachen University, GERMANY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations including + * the two. + * + * You must obey the GNU General Public License in all respects for all + * of the code used other than OpenSSL. If you modify file(s) with this + * exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do + * so, delete this exception statement from your version. If you delete + * this exception statement from all source files in the program, then + * also delete it here. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "IDataSource.h" + +#include +#include + +class DataSourceLocalFile : public IDataSource +{ +public: + DataSourceLocalFile(const std::filesystem::path &filePath); + + virtual [[nodiscard]] std::vector getInfoList() const override; + virtual [[nodiscard]] bool atEnd() const override; + virtual [[nodiscard]] bool isReady() const override; + virtual [[nodiscard]] std::int64_t position() const override; + virtual [[nodiscard]] std::optional fileSize() const; + + virtual [[nodiscard]] bool seek(const std::int64_t pos) override; + virtual [[nodiscard]] std::int64_t read(ByteVector &buffer, const std::int64_t nrBytes) override; + +protected: + std::filesystem::path filePath{}; + bool isFileOpened{}; + + std::ifstream file{}; + std::int64_t filePosition{}; +}; diff --git a/YUViewLib/src/filesource/FileSource.cpp b/YUViewLib/src/filesource/FileSource.cpp index 7e5daf3de..f34dde1a2 100644 --- a/YUViewLib/src/filesource/FileSource.cpp +++ b/YUViewLib/src/filesource/FileSource.cpp @@ -34,11 +34,9 @@ #include -#include -#include -#include -#include -#include +#include +#include + #ifdef Q_OS_WIN #include #endif @@ -48,40 +46,51 @@ #include #endif -FileSource::FileSource() +FileSource::FileSource() : FileSourceWithLocalFile() { - connect(&fileWatcher, - &QFileSystemWatcher::fileChanged, - this, - &FileSource::fileSystemWatcherFileChanged); } bool FileSource::openFile(const QString &filePath) { - // Check if the file exists - this->fileInfo.setFile(filePath); - if (!this->fileInfo.exists() || !this->fileInfo.isFile()) + QFileInfo fileInfo(filePath); + if (!fileInfo.exists() || !fileInfo.isFile()) return false; - if (this->isFileOpened && this->srcFile.isOpen()) + if (this->srcFile.isOpen()) this->srcFile.close(); - // open file for reading this->srcFile.setFileName(filePath); this->isFileOpened = this->srcFile.open(QIODevice::ReadOnly); if (!this->isFileOpened) return false; - // Save the full file path this->fullFilePath = filePath; - // Install a watcher for the file (if file watching is active) this->updateFileWatchSetting(); - this->fileChanged = false; return true; } +bool FileSource::atEnd() const +{ + return !this->isFileOpened ? true : this->srcFile.atEnd(); +} + +QByteArray FileSource::readLine() +{ + return !this->isFileOpened ? QByteArray() : this->srcFile.readLine(); +} + +bool FileSource::seek(int64_t pos) +{ + return !this->isFileOpened ? false : this->srcFile.seek(pos); +} + +int64_t FileSource::pos() +{ + return !this->isFileOpened ? 0 : this->srcFile.pos(); +} + #if SSE_CONVERSION // Resize the target array if necessary and read the given number of bytes to the data array void FileSource::readBytes(byteArrayAligned &targetBuffer, int64_t startPos, int64_t nrBytes) @@ -97,10 +106,9 @@ void FileSource::readBytes(byteArrayAligned &targetBuffer, int64_t startPos, int } #endif -// Resize the target array if necessary and read the given number of bytes to the data array int64_t FileSource::readBytes(QByteArray &targetBuffer, int64_t startPos, int64_t nrBytes) { - if (!this->isOk()) + if (!this->isOpened()) return 0; if (targetBuffer.size() < nrBytes) @@ -116,233 +124,6 @@ int64_t FileSource::readBytes(QByteArray &targetBuffer, int64_t startPos, int64_ return this->srcFile.read(targetBuffer.data(), nrBytes); } -QList FileSource::getFileInfoList() const -{ - QList infoList; - - if (!this->isFileOpened) - return infoList; - - infoList.append(InfoItem("File Path", this->fileInfo.absoluteFilePath())); -#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) - auto createdtime = this->fileInfo.created().toString("yyyy-MM-dd hh:mm:ss"); -#else - auto createdtime = this->fileInfo.birthTime().toString("yyyy-MM-dd hh:mm:ss"); -#endif - infoList.append(InfoItem("Time Created", createdtime)); - infoList.append( - InfoItem("Time Modified", this->fileInfo.lastModified().toString("yyyy-MM-dd hh:mm:ss"))); - infoList.append(InfoItem("Nr Bytes", QString("%1").arg(this->fileInfo.size()))); - - return infoList; -} - -QString FileSource::getAbsoluteFilePath() const -{ - return this->isFileOpened ? this->fileInfo.absoluteFilePath() : QString(); -} - -FileSource::fileFormat_t FileSource::guessFormatFromFilename() const -{ - FileSource::fileFormat_t format; - - // We are going to check two strings (one after the other) for indicators on the frames size, fps - // and bit depth. 1: The file name, 2: The folder name that the file is contained in. - - auto dirName = this->fileInfo.absoluteDir().dirName(); - auto fileName = this->fileInfo.fileName(); - if (fileName.isEmpty()) - return format; - - for (auto const &name : {fileName, dirName}) - { - // First, we will try to get a frame size from the name - if (!format.frameSize.isValid()) - { - // The regular expressions to match. They are sorted from most detailed to least so that the - // most detailed ones are tested first. - auto regExprList = - QStringList() - << "([0-9]+)(?:x|X|\\*)([0-9]+)_([0-9]+)(?:Hz)?_([0-9]+)b?[\\._]" // Something_2160x1440_60_8_more.yuv - // or - // Something_2160x1440_60_8b.yuv - // or - // Something_2160x1440_60Hz_8_more.yuv - << "([0-9]+)(?:x|X|\\*)([0-9]+)_([0-9]+)(?:Hz)?[\\._]" // Something_2160x1440_60_more.yuv - // or Something_2160x1440_60.yuv - << "([0-9]+)(?:x|X|\\*)([0-9]+)[\\._]"; // Something_2160x1440_more.yuv or - // Something_2160x1440.yuv - - for (auto regExpStr : regExprList) - { - QRegularExpression exp(regExpStr); - auto match = exp.match(name); - if (match.hasMatch()) - { - auto widthString = match.captured(1); - auto heightString = match.captured(2); - format.frameSize = Size(widthString.toInt(), heightString.toInt()); - - auto rateString = match.captured(3); - if (!rateString.isEmpty()) - format.frameRate = rateString.toDouble(); - - auto bitDepthString = match.captured(4); - if (!bitDepthString.isEmpty()) - format.bitDepth = bitDepthString.toUInt(); - - break; // Don't check the following expressions - } - } - } - - // try resolution indicators with framerate: "1080p50", "720p24" ... - if (!format.frameSize.isValid()) - { - QRegularExpression rx1080p("1080p([0-9]+)"); - auto matchIt = rx1080p.globalMatch(name); - if (matchIt.hasNext()) - { - auto match = matchIt.next(); - format.frameSize = Size(1920, 1080); - auto frameRateString = match.captured(1); - format.frameRate = frameRateString.toInt(); - } - } - if (!format.frameSize.isValid()) - { - QRegularExpression rx720p("720p([0-9]+)"); - auto matchIt = rx720p.globalMatch(name); - if (matchIt.hasNext()) - { - auto match = matchIt.next(); - format.frameSize = Size(1280, 720); - auto frameRateString = match.captured(1); - format.frameRate = frameRateString.toInt(); - } - } - - if (!format.frameSize.isValid()) - { - // try to find resolution indicators (e.g. 'cif', 'hd') in file name - if (name.contains("_cif", Qt::CaseInsensitive)) - format.frameSize = Size(352, 288); - else if (name.contains("_qcif", Qt::CaseInsensitive)) - format.frameSize = Size(176, 144); - else if (name.contains("_4cif", Qt::CaseInsensitive)) - format.frameSize = Size(704, 576); - else if (name.contains("UHD", Qt::CaseSensitive)) - format.frameSize = Size(3840, 2160); - else if (name.contains("HD", Qt::CaseSensitive)) - format.frameSize = Size(1920, 1080); - else if (name.contains("1080p", Qt::CaseSensitive)) - format.frameSize = Size(1920, 1080); - else if (name.contains("720p", Qt::CaseSensitive)) - format.frameSize = Size(1280, 720); - } - - // Second, if we were able to get a frame size but no frame rate, we will try to get a frame - // rate. - if (format.frameSize.isValid() && format.frameRate == -1) - { - // Look for: 24fps, 50fps, 24FPS, 50FPS - QRegularExpression rxFPS("([0-9]+)fps", QRegularExpression::CaseInsensitiveOption); - auto match = rxFPS.match(name); - if (match.hasMatch()) - { - auto frameRateString = match.captured(1); - format.frameRate = frameRateString.toInt(); - } - } - if (format.frameSize.isValid() && format.frameRate == -1) - { - // Look for: 24Hz, 50Hz, 24HZ, 50HZ - QRegularExpression rxHZ("([0-9]+)HZ", QRegularExpression::CaseInsensitiveOption); - auto match = rxHZ.match(name); - if (match.hasMatch()) - { - QString frameRateString = match.captured(1); - format.frameRate = frameRateString.toInt(); - } - } - - // Third, if we were able to get a frame size but no bit depth, we try to get a bit depth. - if (format.frameSize.isValid() && format.bitDepth == 0) - { - for (unsigned bd : {8, 9, 10, 12, 16}) - { - // Look for: 10bit, 10BIT, 10-bit, 10-BIT - if (name.contains(QString("%1bit").arg(bd), Qt::CaseInsensitive) || - name.contains(QString("%1-bit").arg(bd), Qt::CaseInsensitive)) - { - format.bitDepth = bd; - break; - } - // Look for bit depths like: _16b_ .8b. -12b- - QRegularExpression exp(QString("(?:_|\\.|-)%1b(?:_|\\.|-)").arg(bd)); - auto match = exp.match(name); - if (match.hasMatch()) - { - format.bitDepth = bd; - break; - } - } - } - - // If we were able to get a frame size, try to get an indicator for packed formats - if (format.frameSize.isValid()) - { - QRegularExpression exp("(?:_|\\.|-)packed(?:_|\\.|-)"); - auto match = exp.match(name); - if (match.hasMatch()) - format.packed = true; - } - } - - return format; -} - -// If you are loading a playlist and you have an absolute path and a relative path, this function -// will return the absolute path (if a file with that absolute path exists) or convert the relative -// path to an absolute one and return that (if that file exists). If neither exists the empty string -// is returned. -QString FileSource::getAbsPathFromAbsAndRel(const QString ¤tPath, - const QString &absolutePath, - const QString &relativePath) -{ - QFileInfo checkAbsoluteFile(absolutePath); - if (checkAbsoluteFile.exists()) - return absolutePath; - - QFileInfo plFileInfo(currentPath); - auto combinePath = QDir(plFileInfo.path()).filePath(relativePath); - QFileInfo checkRelativeFile(combinePath); - if (checkRelativeFile.exists() && checkRelativeFile.isFile()) - { - return QDir::cleanPath(combinePath); - } - - return {}; -} - -bool FileSource::getAndResetFileChangedFlag() -{ - bool b = this->fileChanged; - this->fileChanged = false; - return b; -} - -void FileSource::updateFileWatchSetting() -{ - // Install a file watcher if file watching is active in the settings. - // The addPath/removePath functions will do nothing if called twice for the same file. - QSettings settings; - if (settings.value("WatchFiles", true).toBool()) - fileWatcher.addPath(this->fullFilePath); - else - fileWatcher.removePath(this->fullFilePath); -} - void FileSource::clearFileCache() { if (!this->isFileOpened) diff --git a/YUViewLib/src/filesource/FileSource.h b/YUViewLib/src/filesource/FileSource.h index 6aa3f19c0..8b0ac1f44 100644 --- a/YUViewLib/src/filesource/FileSource.h +++ b/YUViewLib/src/filesource/FileSource.h @@ -32,70 +32,31 @@ #pragma once +#include "FileSourceWithLocalFile.h" + #include -#include -#include #include -#include #include -#include -#include #include -enum class InputFormat -{ - Invalid = -1, - AnnexBHEVC, // Raw HEVC annex B file - AnnexBAVC, // Raw AVC annex B file - AnnexBVVC, // Raw VVC annex B file - Libav // This is some sort of container file which we will read using libavformat -}; - -const auto InputFormatMapper = EnumMapper({{InputFormat::Invalid, "Invalid"}, - {InputFormat::AnnexBHEVC, "AnnexBHEVC"}, - {InputFormat::AnnexBAVC, "AnnexBAVC"}, - {InputFormat::AnnexBVVC, "AnnexBVVC"}, - {InputFormat::Libav, "Libav"}}); - /* The FileSource class provides functions for accessing files. Besides the reading of * certain blocks of the file, it also directly provides information on the file for the * fileInfoWidget. It also adds functions for guessing the format from the filename. */ -class FileSource : public QObject +class FileSource : public FileSourceWithLocalFile { Q_OBJECT public: FileSource(); - virtual bool openFile(const QString &filePath); - - virtual QList getFileInfoList() const; - int64_t getFileSize() const { return !isFileOpened ? -1 : fileInfo.size(); } - QString getAbsoluteFilePath() const; - QFileInfo getFileInfo() const { return this->fileInfo; } - QFile * getQFile() { return &this->srcFile; } - bool getAndResetFileChangedFlag(); + virtual [[nodiscard]] bool openFile(const QString &filePath) override; - // Return true if the file could be opened and is ready for use. - bool isOk() const { return this->isFileOpened; } - - virtual bool atEnd() const { return !this->isFileOpened ? true : this->srcFile.atEnd(); } - QByteArray readLine() { return !this->isFileOpened ? QByteArray() : this->srcFile.readLine(); } - virtual bool seek(int64_t pos) { return !this->isFileOpened ? false : this->srcFile.seek(pos); } - int64_t pos() { return !this->isFileOpened ? 0 : this->srcFile.pos(); } - - struct fileFormat_t - { - Size frameSize; - int frameRate{-1}; - unsigned bitDepth{}; - bool packed{false}; - }; - fileFormat_t guessFormatFromFilename() const; - - // Get the file size in bytes + [[nodiscard]] virtual bool atEnd() const; + [[nodiscard]] QByteArray readLine(); + [[nodiscard]] virtual bool seek(int64_t pos); + [[nodiscard]] int64_t pos(); // Read the given number of bytes starting at startPos into the QByteArray out // Resize the QByteArray if necessary. Return how many bytes were read. @@ -104,25 +65,12 @@ class FileSource : public QObject void readBytes(byteArrayAligned &data, int64_t startPos, int64_t nrBytes); #endif - static QString getAbsPathFromAbsAndRel(const QString ¤tPath, - const QString &absolutePath, - const QString &relativePath); - - void updateFileWatchSetting(); void clearFileCache(); -private slots: - void fileSystemWatcherFileChanged(const QString &) { fileChanged = true; } - protected: - QString fullFilePath{}; - QFileInfo fileInfo; - QFile srcFile; - bool isFileOpened{}; + QFile srcFile; + bool isFileOpened{}; private: - QFileSystemWatcher fileWatcher{}; - bool fileChanged{}; - QMutex readMutex; }; diff --git a/YUViewLib/src/filesource/FileSourceAnnexBFile.cpp b/YUViewLib/src/filesource/FileSourceAnnexBFile.cpp index cc22edf33..b00895048 100644 --- a/YUViewLib/src/filesource/FileSourceAnnexBFile.cpp +++ b/YUViewLib/src/filesource/FileSourceAnnexBFile.cpp @@ -1,34 +1,34 @@ /* This file is part of YUView - The YUV player with advanced analytics toolset -* -* Copyright (C) 2015 Institut für Nachrichtentechnik, RWTH Aachen University, GERMANY -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 3 of the License, or -* (at your option) any later version. -* -* In addition, as a special exception, the copyright holders give -* permission to link the code of portions of this program with the -* OpenSSL library under certain conditions as described in each -* individual source file, and distribute linked combinations including -* the two. -* -* You must obey the GNU General Public License in all respects for all -* of the code used other than OpenSSL. If you modify file(s) with this -* exception, you may extend this exception to your version of the -* file(s), but you are not obligated to do so. If you do not wish to do -* so, delete this exception statement from your version. If you delete -* this exception statement from all source files in the program, then -* also delete it here. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -*/ + * + * Copyright (C) 2015 Institut für Nachrichtentechnik, RWTH Aachen University, GERMANY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations including + * the two. + * + * You must obey the GNU General Public License in all respects for all + * of the code used other than OpenSSL. If you modify file(s) with this + * exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do + * so, delete this exception statement from your version. If you delete + * this exception statement from all source files in the program, then + * also delete it here. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #include "FileSourceAnnexBFile.h" @@ -41,14 +41,14 @@ #endif const auto BUFFERSIZE = 500000; -const auto STARTCODE = QByteArrayLiteral("\x00\x00\x01"); +const auto STARTCODE = QByteArrayLiteral("\x00\x00\x01"); FileSourceAnnexBFile::FileSourceAnnexBFile() { this->fileBuffer.resize(BUFFERSIZE); } -// Open the file and fill the read buffer. +// Open the file and fill the read buffer. bool FileSourceAnnexBFile::openFile(const QString &fileName) { DEBUG_ANNEXBFILE("FileSourceAnnexBFile::openFile fileName " << fileName); @@ -68,7 +68,7 @@ bool FileSourceAnnexBFile::openFile(const QString &fileName) } bool FileSourceAnnexBFile::atEnd() const -{ +{ return this->fileBufferSize < BUFFERSIZE && this->posInBuffer >= int64_t(this->fileBufferSize); } @@ -76,12 +76,13 @@ void FileSourceAnnexBFile::seekToFirstNAL() { auto nextStartCodePos = this->fileBuffer.indexOf(STARTCODE); if (nextStartCodePos < 0) - // The first buffer does not contain a start code. This is very unusual. Use the normal getNextNALUnit to seek + // The first buffer does not contain a start code. This is very unusual. Use the normal + // getNextNALUnit to seek this->getNextNALUnit(); else { // For 0001 or 001 point to the first 0 byte - if (nextStartCodePos > 0 && this->fileBuffer.at(nextStartCodePos-1) ==(char)0) + if (nextStartCodePos > 0 && this->fileBuffer.at(nextStartCodePos - 1) == (char)0) this->posInBuffer = nextStartCodePos - 1; else this->posInBuffer = nextStartCodePos; @@ -91,7 +92,8 @@ void FileSourceAnnexBFile::seekToFirstNAL() this->nrBytesBeforeFirstNAL = this->bufferStartPosInFile + uint64_t(this->posInBuffer); } -QByteArray FileSourceAnnexBFile::getNextNALUnit(bool getLastDataAgain, pairUint64 *startEndPosInFile) +QByteArray FileSourceAnnexBFile::getNextNALUnit(bool getLastDataAgain, + pairUint64 *startEndPosInFile) { if (getLastDataAgain) return this->lastReturnArray; @@ -101,14 +103,15 @@ QByteArray FileSourceAnnexBFile::getNextNALUnit(bool getLastDataAgain, pairUint6 if (startEndPosInFile) startEndPosInFile->first = this->bufferStartPosInFile + uint64_t(this->posInBuffer); - int nextStartCodePos = -1; - int searchOffset = 3; - bool startCodeFound = false; + int nextStartCodePos = -1; + int searchOffset = 3; + bool startCodeFound = false; while (!startCodeFound) { if (this->posInBuffer < 0) { - // Part of the start code was in the last buffer (see special boundary cases below). Add those parts. + // Part of the start code was in the last buffer (see special boundary cases below). Add those + // parts. const auto nrZeroBytesMissing = std::abs(this->posInBuffer); this->lastReturnArray.append(nrZeroBytesMissing, char(0)); } @@ -117,8 +120,10 @@ QByteArray FileSourceAnnexBFile::getNextNALUnit(bool getLastDataAgain, pairUint6 if (nextStartCodePos < 0 || (uint64_t)nextStartCodePos > this->fileBufferSize) { // No start code found ... append all data in the current buffer. - this->lastReturnArray += this->fileBuffer.mid(this->posInBuffer, this->fileBufferSize - this->posInBuffer); - DEBUG_ANNEXBFILE("FileSourceHEVCAnnexBFile::getNextNALUnit no start code found - ret size " << this->lastReturnArray.size()); + this->lastReturnArray += + this->fileBuffer.mid(this->posInBuffer, this->fileBufferSize - this->posInBuffer); + DEBUG_ANNEXBFILE("FileSourceHEVCAnnexBFile::getNextNALUnit no start code found - ret size " + << this->lastReturnArray.size()); if (this->fileBufferSize < BUFFERSIZE) { @@ -129,35 +134,40 @@ QByteArray FileSourceAnnexBFile::getNextNALUnit(bool getLastDataAgain, pairUint6 return this->lastReturnArray; } - // Before we load the next bytes: The start code might be located at the boundary to the next buffer + // Before we load the next bytes: The start code might be located at the boundary to the next + // buffer const auto lastByteZero0 = this->fileBuffer.at(this->fileBufferSize - 3) == (char)0; const auto lastByteZero1 = this->fileBuffer.at(this->fileBufferSize - 2) == (char)0; const auto lastByteZero2 = this->fileBuffer.at(this->fileBufferSize - 1) == (char)0; // We have to continue searching - get the next buffer updateBuffer(); - + if (this->fileBufferSize > 2) { // Now look for the special boundary case: if (this->fileBuffer.at(0) == (char)1 && lastByteZero2 && lastByteZero1) { - // Found a start code - the 1 byte is here and the two (or three) 0 bytes were in the last buffer - startCodeFound = true; + // Found a start code - the 1 byte is here and the two (or three) 0 bytes were in the last + // buffer + startCodeFound = true; nextStartCodePos = lastByteZero0 ? -3 : -2; this->lastReturnArray.chop(lastByteZero0 ? 3 : 2); } - else if (this->fileBuffer.at(0) == (char)0 && this->fileBuffer.at(1) == (char)1 && lastByteZero2) + else if (this->fileBuffer.at(0) == (char)0 && this->fileBuffer.at(1) == (char)1 && + lastByteZero2) { - // Found a start code - the 01 bytes are here and the one (or two) 0 bytes were in the last buffer - startCodeFound = true; + // Found a start code - the 01 bytes are here and the one (or two) 0 bytes were in the + // last buffer + startCodeFound = true; nextStartCodePos = lastByteZero1 ? -2 : -1; this->lastReturnArray.chop(lastByteZero0 ? 1 : 1); } - else if (this->fileBuffer.at(0) == (char)0 && this->fileBuffer.at(1) == (char)0 && this->fileBuffer.at(2) == (char)1) + else if (this->fileBuffer.at(0) == (char)0 && this->fileBuffer.at(1) == (char)0 && + this->fileBuffer.at(2) == (char)1) { // Found a start code - the 001 bytes are here. Check the last byte of the last buffer - startCodeFound = true; + startCodeFound = true; nextStartCodePos = lastByteZero2 ? -1 : 0; if (lastByteZero2) this->lastReturnArray.chop(1); @@ -179,21 +189,24 @@ QByteArray FileSourceAnnexBFile::getNextNALUnit(bool getLastDataAgain, pairUint6 if (startEndPosInFile) startEndPosInFile->second = this->bufferStartPosInFile + nextStartCodePos; if (nextStartCodePos > int(this->posInBuffer)) - this->lastReturnArray += this->fileBuffer.mid(this->posInBuffer, nextStartCodePos - this->posInBuffer); + this->lastReturnArray += + this->fileBuffer.mid(this->posInBuffer, nextStartCodePos - this->posInBuffer); this->posInBuffer = nextStartCodePos; - DEBUG_ANNEXBFILE("FileSourceAnnexBFile::getNextNALUnit start code found - ret size " << this->lastReturnArray.size()); + DEBUG_ANNEXBFILE("FileSourceAnnexBFile::getNextNALUnit start code found - ret size " + << this->lastReturnArray.size()); return this->lastReturnArray; } QByteArray FileSourceAnnexBFile::getFrameData(pairUint64 startEndFilePos) { // Get all data for the frame (all NAL units in the raw format with start codes). - // We don't need to convert the format to the mp4 ISO format. The ffmpeg decoder can also accept raw NAL units. - // When the extradata is set as raw NAL units, the AVPackets must also be raw NAL units. + // We don't need to convert the format to the mp4 ISO format. The ffmpeg decoder can also accept + // raw NAL units. When the extradata is set as raw NAL units, the AVPackets must also be raw NAL + // units. QByteArray retArray; - + auto start = startEndFilePos.first; - auto end = startEndFilePos.second; + auto end = startEndFilePos.second; // Seek the source file to the start position this->seek(start); @@ -228,9 +241,10 @@ bool FileSourceAnnexBFile::updateBuffer() this->bufferStartPosInFile += this->fileBufferSize; this->fileBufferSize = srcFile.read(this->fileBuffer.data(), BUFFERSIZE); - this->posInBuffer = 0; + this->posInBuffer = 0; - DEBUG_ANNEXBFILE("FileSourceAnnexBFile::updateBuffer this->fileBufferSize " << this->fileBufferSize); + DEBUG_ANNEXBFILE("FileSourceAnnexBFile::updateBuffer this->fileBufferSize " + << this->fileBufferSize); return (this->fileBufferSize > 0); } @@ -247,16 +261,18 @@ bool FileSourceAnnexBFile::seek(int64_t pos) // The file is empty of there was an error reading from the file. return false; this->bufferStartPosInFile = pos; - this->posInBuffer = 0; + this->posInBuffer = 0; if (pos == 0) this->seekToFirstNAL(); else { // Check if we are at a start code position (001 or 0001) - if (this->fileBuffer.at(0) == (char)0 && this->fileBuffer.at(1) == (char)0 && this->fileBuffer.at(2) == (char)0 && this->fileBuffer.at(3) == (char)1) + if (this->fileBuffer.at(0) == (char)0 && this->fileBuffer.at(1) == (char)0 && + this->fileBuffer.at(2) == (char)0 && this->fileBuffer.at(3) == (char)1) return true; - if (this->fileBuffer.at(0) == (char)0 && this->fileBuffer.at(1) == (char)0 && this->fileBuffer.at(2) == (char)1) + if (this->fileBuffer.at(0) == (char)0 && this->fileBuffer.at(1) == (char)0 && + this->fileBuffer.at(2) == (char)1) return true; DEBUG_ANNEXBFILE("FileSourceAnnexBFile::seek could not find start code at seek position"); diff --git a/YUViewLib/src/filesource/FileSourceAnnexBFile.h b/YUViewLib/src/filesource/FileSourceAnnexBFile.h index a2d486cb0..5b45cd06f 100644 --- a/YUViewLib/src/filesource/FileSourceAnnexBFile.h +++ b/YUViewLib/src/filesource/FileSourceAnnexBFile.h @@ -47,12 +47,9 @@ class FileSourceAnnexBFile : public FileSource public: FileSourceAnnexBFile(); - FileSourceAnnexBFile(const QString &filePath) : FileSourceAnnexBFile() { openFile(filePath); } - ~FileSourceAnnexBFile(){}; bool openFile(const QString &filePath) override; - // Is the file at the end? bool atEnd() const override; // --- Retrieving of data from the file --- @@ -64,17 +61,18 @@ class FileSourceAnnexBFile : public FileSource // Also return the start and end position of the NAL unit in the file so you can seek to it. // startEndPosInFile: The file positions of the first byte in the NAL header and the end position // of the last byte - QByteArray getNextNALUnit(bool getLastDataAgain = false, pairUint64 *startEndPosInFile = nullptr); + [[nodiscard]] QByteArray getNextNALUnit(bool getLastDataAgain = false, + pairUint64 *startEndPosInFile = nullptr); // Get all bytes that are needed to decode the next frame (from the given start to the given end // position) The data will be returned in the ISO/IEC 14496-15 format (4 bytes size followed by // the payload). - QByteArray getFrameData(pairUint64 startEndFilePos); + [[nodiscard]] QByteArray getFrameData(pairUint64 startEndFilePos); // Seek the file to the given byte position. Update the buffer. - bool seek(int64_t pos) override; + [[nodiscard]] bool seek(int64_t pos) override; - uint64_t getNrBytesBeforeFirstNAL() const { return this->nrBytesBeforeFirstNAL; } + [[nodiscard]] uint64_t getNrBytesBeforeFirstNAL() const { return this->nrBytesBeforeFirstNAL; } protected: QByteArray fileBuffer; @@ -88,13 +86,9 @@ class FileSourceAnnexBFile : public FileSource // update the buffer and the start of the start code was in the previous buffer int64_t posInBuffer{0}; - // load the next buffer bool updateBuffer(); - - // Seek to the first NAL header in the bitstream void seekToFirstNAL(); - // We will keep the last buffer in case the reader wants to get it again QByteArray lastReturnArray; uint64_t nrBytesBeforeFirstNAL{0}; diff --git a/YUViewLib/src/filesource/FileSourceFFmpegFile.cpp b/YUViewLib/src/filesource/FileSourceFFmpegFile.cpp index 6776e8d92..d7e505068 100644 --- a/YUViewLib/src/filesource/FileSourceFFmpegFile.cpp +++ b/YUViewLib/src/filesource/FileSourceFFmpegFile.cpp @@ -32,737 +32,6 @@ #include "FileSourceFFmpegFile.h" -#include -#include -#include - -#include -#include -#include -#include - -#define FILESOURCEFFMPEGFILE_DEBUG_OUTPUT 0 -#if FILESOURCEFFMPEGFILE_DEBUG_OUTPUT && !NDEBUG -#include -#define DEBUG_FFMPEG qDebug -#else -#define DEBUG_FFMPEG(fmt, ...) ((void)0) -#endif - -using SubByteReaderLogging = parser::reader::SubByteReaderLogging; -using namespace FFmpeg; - -namespace -{ - -auto startCode = QByteArrayLiteral("\x00\x00\x01"); - -uint64_t getBoxSize(ByteVector::const_iterator iterator) -{ - uint64_t size = 0; - size += static_cast(*(iterator++)) << (8 * 3); - size += static_cast(*(iterator++)) << (8 * 2); - size += static_cast(*(iterator++)) << (8 * 1); - size += static_cast(*iterator); - return size; -} - -} // namespace - -FileSourceFFmpegFile::FileSourceFFmpegFile() -{ - connect(&this->fileWatcher, - &QFileSystemWatcher::fileChanged, - this, - &FileSourceFFmpegFile::fileSystemWatcherFileChanged); -} - -AVPacketWrapper FileSourceFFmpegFile::getNextPacket(bool getLastPackage, bool videoPacket) -{ - if (getLastPackage) - return this->currentPacket; - - // Load the next packet - if (!this->goToNextPacket(videoPacket)) - { - this->posInFile = -1; - return {}; - } - - return this->currentPacket; -} - -QByteArray FileSourceFFmpegFile::getNextUnit(bool getLastDataAgain) -{ - if (getLastDataAgain) - return this->lastReturnArray; - - // Is a packet loaded? - if (this->currentPacketData.isEmpty()) - { - if (!this->goToNextPacket(true)) - { - this->posInFile = -1; - return {}; - } - - this->currentPacketData = QByteArray::fromRawData((const char *)(this->currentPacket.getData()), - this->currentPacket.getDataSize()); - this->posInData = 0; - } - - // AVPacket data can be in one of two formats: - // 1: The raw annexB format with start codes (0x00000001 or 0x000001) - // 2: ISO/IEC 14496-15 mp4 format: The first 4 bytes determine the size of the NAL unit followed - // by the payload - if (this->packetDataFormat == PacketDataFormat::RawNAL) - { - const auto firstBytes = this->currentPacketData.mid(posInData, 4); - int offset; - if (firstBytes.at(0) == (char)0 && firstBytes.at(1) == (char)0 && firstBytes.at(2) == (char)0 && - firstBytes.at(3) == (char)1) - offset = 4; - else if (firstBytes.at(0) == (char)0 && firstBytes.at(1) == (char)0 && - firstBytes.at(2) == (char)1) - offset = 3; - else - { - // The start code could not be found ... - this->currentPacketData.clear(); - return {}; - } - - // Look for the next start code (or the end of the file) - auto nextStartCodePos = this->currentPacketData.indexOf(startCode, posInData + 3); - - if (nextStartCodePos == -1) - { - // Return the remainder of the buffer and clear it so that the next packet is loaded on the - // next call - this->lastReturnArray = currentPacketData.mid(this->posInData + offset); - this->currentPacketData.clear(); - } - else - { - auto size = nextStartCodePos - this->posInData - offset; - lastReturnArray = this->currentPacketData.mid(this->posInData + offset, size); - this->posInData += 3 + size; - } - } - else if (this->packetDataFormat == PacketDataFormat::MP4) - { - auto sizePart = this->currentPacketData.mid(posInData, 4); - int size = (unsigned char)sizePart.at(3); - size += (unsigned char)sizePart.at(2) << 8; - size += (unsigned char)sizePart.at(1) << 16; - size += (unsigned char)sizePart.at(0) << 24; - - if (size < 0) - { - // The int did overflow. This means that the NAL unit is > 2GB in size. This is probably an - // error - this->currentPacketData.clear(); - return {}; - } - if (size > this->currentPacketData.length() - this->posInData) - { - // The indicated size is bigger than the buffer - this->currentPacketData.clear(); - return {}; - } - - this->lastReturnArray = this->currentPacketData.mid(posInData + 4, size); - this->posInData += 4 + size; - if (this->posInData >= this->currentPacketData.size()) - this->currentPacketData.clear(); - } - else if (this->packetDataFormat == PacketDataFormat::OBU) - { - SubByteReaderLogging reader( - SubByteReaderLogging::convertToByteVector(currentPacketData), nullptr, "", posInData); - - try - { - parser::av1::obu_header header; - header.parse(reader); - - if (header.obu_has_size_field) - { - auto completeSize = header.obu_size + reader.nrBytesRead(); - this->lastReturnArray = currentPacketData.mid(posInData, completeSize); - this->posInData += completeSize; - if (this->posInData >= currentPacketData.size()) - this->currentPacketData.clear(); - } - else - { - // The OBU is the remainder of the input - this->lastReturnArray = currentPacketData.mid(posInData); - this->posInData = currentPacketData.size(); - this->currentPacketData.clear(); - } - } - catch (...) - { - // The reader threw an exception - this->currentPacketData.clear(); - return {}; - } - } - - return this->lastReturnArray; -} - -QByteArray FileSourceFFmpegFile::getExtradata() -{ - // Get the video stream - if (!this->video_stream) - return {}; - return this->video_stream.getExtradata(); -} - -StringPairVec FileSourceFFmpegFile::getMetadata() +bool FileSourceFFmpegFile::openFile(const QString &filePath) { - if (!this->formatCtx) - return {}; - return ff.getDictionaryEntries(this->formatCtx.getMetadata(), "", 0); -} - -ByteVector FileSourceFFmpegFile::getLhvCData() -{ - const auto inputFormat = this->formatCtx.getInputFormat(); - const auto isMp4 = inputFormat.getName().contains("mp4"); - if (!this->getVideoStreamCodecID().isHEVC() || !isMp4) - return {}; - - // This is a bit of a hack. The problem is that FFmpeg can currently not extract this for us. - // Maybe this will be added in the future. So the only option we have here is to manually extract - // the lhvC data from the mp4 file. In mp4, the boxes can be at the beginning or at the end of the - // file. - enum class SearchPosition - { - Beginning, - End - }; - - std::ifstream inputFile(this->fileName.toStdString(), std::ios::binary); - for (const auto searchPosition : {SearchPosition::Beginning, SearchPosition::End}) - { - constexpr auto NR_SEARCH_BYTES = 5120; - - if (searchPosition == SearchPosition::End) - inputFile.seekg(-NR_SEARCH_BYTES, std::ios_base::end); - - const auto rawFileData = functions::readData(inputFile, NR_SEARCH_BYTES); - if (rawFileData.empty()) - continue; - - const std::string searchString = "lhvC"; - auto lhvcPos = std::search( - rawFileData.begin(), rawFileData.end(), searchString.begin(), searchString.end()); - if (lhvcPos == rawFileData.end()) - continue; - - if (std::distance(rawFileData.begin(), lhvcPos) < 4) - continue; - - const auto boxSize = getBoxSize(lhvcPos - 4); - if (boxSize == 0 || boxSize > std::distance(lhvcPos, rawFileData.end())) - continue; - - // We just return the payload without the box size or the "lhvC" tag - return ByteVector(lhvcPos + 4, lhvcPos + boxSize - 4); - } - - return {}; -} - -QList FileSourceFFmpegFile::getParameterSets() -{ - if (!this->isFileOpened) - return {}; - - /* The SPS/PPS are somewhere else in containers: - * In mp4-container (mkv also) PPS/SPS are stored separate from frame data in global headers. - * To access them from libav* APIs you need to look for extradata field in AVCodecContext of - * AVStream which relate to needed video stream. Also extradata can have different format from - * standard H.264 NALs so look in MP4-container specs for format description. */ - const auto extradata = this->getExtradata(); - if (extradata.isEmpty()) - { - DEBUG_FFMPEG("Error no extradata could be found."); - return {}; - } - - QList retArray; - - // Since the FFmpeg developers don't want to make it too easy, the extradata is organized - // differently depending on the codec. - auto codecID = this->ff.getCodecIDWrapper(video_stream.getCodecID()); - if (codecID.isHEVC()) - { - if (extradata.at(0) == 1) - { - // Internally, ffmpeg uses a custom format for the parameter sets (hvcC). - // The hvcC parameters come first, and afterwards, the "normal" parameter sets are sent. - - // The first 22 bytes are fixed hvcC parameter set (see hvcc_write in libavformat hevc.c) - auto numOfArrays = int(extradata.at(22)); - - int pos = 23; - for (int i = 0; i < numOfArrays; i++) - { - // The first byte contains the NAL unit type (which we don't use here). - pos++; - // int byte = (unsigned char)(extradata.at(pos++)); - // bool array_completeness = byte & (1 << 7); - // int nalUnitType = byte & 0x3f; - - // Two bytes numNalus - int numNalus = (unsigned char)(extradata.at(pos++)) << 7; - numNalus += (unsigned char)(extradata.at(pos++)); - - for (int j = 0; j < numNalus; j++) - { - // Two bytes nalUnitLength - int nalUnitLength = (unsigned char)(extradata.at(pos++)) << 7; - nalUnitLength += (unsigned char)(extradata.at(pos++)); - - // nalUnitLength bytes payload of the NAL unit - // This payload includes the NAL unit header - auto rawNAL = extradata.mid(pos, nalUnitLength); - retArray.append(rawNAL); - pos += nalUnitLength; - } - } - } - } - else if (codecID.isAVC()) - { - // Note: Actually we would only need this if we would feed the AVC bitstream to a different - // decoder then ffmpeg. - // So this function is so far not called (and not tested). - - // First byte is 1, length must be at least 7 bytes - if (extradata.at(0) == 1 && extradata.length() >= 7) - { - int nrSPS = extradata.at(5) & 0x1f; - int pos = 6; - for (int i = 0; i < nrSPS; i++) - { - int nalUnitLength = (unsigned char)(extradata.at(pos++)) << 7; - nalUnitLength += (unsigned char)(extradata.at(pos++)); - - auto rawNAL = extradata.mid(pos, nalUnitLength); - retArray.append(rawNAL); - pos += nalUnitLength; - } - - int nrPPS = extradata.at(pos++); - for (int i = 0; i < nrPPS; i++) - { - int nalUnitLength = (unsigned char)(extradata.at(pos++)) << 7; - nalUnitLength += (unsigned char)(extradata.at(pos++)); - - auto rawNAL = extradata.mid(pos, nalUnitLength); - retArray.append(rawNAL); - pos += nalUnitLength; - } - } - } - else if (codecID.isAV1()) - { - // This should be a normal OBU for the seuqence header starting with the OBU header - SubByteReaderLogging reader(SubByteReaderLogging::convertToByteVector(extradata), nullptr); - parser::av1::obu_header header; - try - { - header.parse(reader); - } - catch (const std::exception &e) - { - (void)e; - DEBUG_FFMPEG("Error parsing OBU header " + e.what()); - return retArray; - } - - if (header.obu_type == parser::av1::ObuType::OBU_SEQUENCE_HEADER) - retArray.append(extradata); - } - - return retArray; -} - -FileSourceFFmpegFile::~FileSourceFFmpegFile() -{ - if (this->currentPacket) - this->ff.freePacket(this->currentPacket); -} - -bool FileSourceFFmpegFile::openFile(const QString & filePath, - QWidget * mainWindow, - FileSourceFFmpegFile *other, - bool parseFile) -{ - // Check if the file exists - this->fileInfo.setFile(filePath); - if (!this->fileInfo.exists() || !this->fileInfo.isFile()) - return false; - - if (this->isFileOpened) - { - // Close the file? - // TODO - } - - this->openFileAndFindVideoStream(filePath); - if (!this->isFileOpened) - return false; - - // Save the full file path - this->fullFilePath = filePath; - - // Install a watcher for the file (if file watching is active) - this->updateFileWatchSetting(); - this->fileChanged = false; - - // If another (already opened) bitstream is given, copy bitstream info from there; Otherwise scan - // the bitstream. - if (other && other->isFileOpened) - { - this->nrFrames = other->nrFrames; - this->keyFrameList = other->keyFrameList; - } - else if (parseFile) - { - if (!this->scanBitstream(mainWindow)) - return false; - - this->seekFileToBeginning(); - } - - return true; -} - -// Check if we are supposed to watch the file for changes. If no, remove the file watcher. If yes, -// install one. -void FileSourceFFmpegFile::updateFileWatchSetting() -{ - // Install a file watcher if file watching is active in the settings. - // The addPath/removePath functions will do nothing if called twice for the same file. - QSettings settings; - if (settings.value("WatchFiles", true).toBool()) - this->fileWatcher.addPath(this->fullFilePath); - else - this->fileWatcher.removePath(this->fullFilePath); -} - -std::pair FileSourceFFmpegFile::getClosestSeekableFrameBefore(int frameIdx) const -{ - // We are always be able to seek to the beginning of the file - auto bestSeekDTS = this->keyFrameList[0].dts; - auto seekToFrameIdx = this->keyFrameList[0].frame; - - for (const auto &pic : this->keyFrameList) - { - if (frameIdx > 0 && pic.frame <= unsigned(frameIdx)) - { - // We could seek here - bestSeekDTS = pic.dts; - seekToFrameIdx = pic.frame; - } - else - break; - } - - return {bestSeekDTS, seekToFrameIdx}; -} - -bool FileSourceFFmpegFile::scanBitstream(QWidget *mainWindow) -{ - if (!this->isFileOpened) - return false; - - // Create the dialog (if the given pointer is not null) - auto maxPTS = this->getMaxTS(); - // Updating the dialog (setValue) is quite slow. Only do this if the percent value changes. - int curPercentValue = 0; - std::unique_ptr progress; - if (mainWindow != nullptr) - { - progress.reset( - new QProgressDialog("Parsing (indexing) bitstream...", "Cancel", 0, 100, mainWindow)); - progress->setMinimumDuration(1000); // Show after 1s - progress->setAutoClose(false); - progress->setAutoReset(false); - progress->setWindowModality(Qt::WindowModal); - } - - this->nrFrames = 0; - while (this->goToNextPacket(true)) - { - DEBUG_FFMPEG("FileSourceFFmpegFile::scanBitstream: frame %d pts %d dts %d%s", - this->nrFrames, - (int)this->currentPacket.getPTS(), - (int)this->currentPacket.getDTS(), - this->currentPacket.getFlagKeyframe() ? " - keyframe" : ""); - - if (this->currentPacket.getFlagKeyframe()) - this->keyFrameList.append(pictureIdx(this->nrFrames, this->currentPacket.getDTS())); - - if (progress && progress->wasCanceled()) - return false; - - int newPercentValue = 0; - if (maxPTS != 0) - newPercentValue = functions::clip(int(this->currentPacket.getPTS() * 100 / maxPTS), 0, 100); - if (newPercentValue != curPercentValue) - { - if (progress) - progress->setValue(newPercentValue); - curPercentValue = newPercentValue; - } - - this->nrFrames++; - } - - DEBUG_FFMPEG("FileSourceFFmpegFile::scanBitstream: Scan done. Found %d frames and %d keyframes.", - this->nrFrames, - this->keyFrameList.length()); - return !progress->wasCanceled(); -} - -void FileSourceFFmpegFile::openFileAndFindVideoStream(QString fileName) -{ - this->isFileOpened = false; - - this->ff.loadFFmpegLibraries(); - if (!this->ff.loadingSuccessfull()) - return; - - // Open the input file - if (!this->ff.openInput(this->formatCtx, fileName)) - return; - - this->fileName = fileName; - this->formatCtx.getInputFormat(); - - for (unsigned idx = 0; idx < this->formatCtx.getNbStreams(); idx++) - { - auto stream = this->formatCtx.getStream(idx); - auto streamType = stream.getCodecType(); - auto codeID = this->ff.getCodecIDWrapper(stream.getCodecID()); - if (streamType == AVMEDIA_TYPE_VIDEO) - { - this->video_stream = stream; - this->streamIndices.video = idx; - } - else if (streamType == AVMEDIA_TYPE_AUDIO) - this->streamIndices.audio.append(idx); - else if (streamType == AVMEDIA_TYPE_SUBTITLE) - { - if (codeID.getCodecName() == "dvb_subtitle") - this->streamIndices.subtitle.dvb.append(idx); - else if (codeID.getCodecName() == "eia_608") - this->streamIndices.subtitle.eia608.append(idx); - else - this->streamIndices.subtitle.other.append(idx); - } - } - if (!this->video_stream) - return; - - this->currentPacket = this->ff.allocatePacket(); - - // Get the frame rate, picture size and color conversion mode - auto avgFrameRate = this->video_stream.getAvgFrameRate(); - if (avgFrameRate.den == 0) - this->frameRate = -1.0; - else - this->frameRate = avgFrameRate.num / double(avgFrameRate.den); - - const auto ffmpegPixFormat = - this->ff.getAvPixFmtDescriptionFromAvPixelFormat(this->video_stream.getPixelFormat()); - this->rawFormat = ffmpegPixFormat.getRawFormat(); - if (this->rawFormat == video::RawFormat::YUV) - this->pixelFormat_yuv = ffmpegPixFormat.getPixelFormatYUV(); - else if (this->rawFormat == video::RawFormat::RGB) - this->pixelFormat_rgb = ffmpegPixFormat.getRGBPixelFormat(); - - this->duration = this->formatCtx.getDuration(); - this->timeBase = this->video_stream.getTimeBase(); - - auto colSpace = this->video_stream.getColorspace(); - this->frameSize = this->video_stream.getFrameSize(); - - if (colSpace == AVCOL_SPC_BT2020_NCL || colSpace == AVCOL_SPC_BT2020_CL) - this->colorConversionType = video::yuv::ColorConversion::BT2020_LimitedRange; - else if (colSpace == AVCOL_SPC_BT470BG || colSpace == AVCOL_SPC_SMPTE170M) - this->colorConversionType = video::yuv::ColorConversion::BT601_LimitedRange; - else - this->colorConversionType = video::yuv::ColorConversion::BT709_LimitedRange; - - this->isFileOpened = true; -} - -bool FileSourceFFmpegFile::goToNextPacket(bool videoPacketsOnly) -{ - // Load the next video stream packet into the packet buffer - int ret = 0; - do - { - auto &pkt = this->currentPacket; - - if (this->currentPacket) - this->ff.unrefPacket(this->currentPacket); - - { - auto ctx = this->formatCtx.getFormatCtx(); - if (!ctx || !pkt) - ret = -1; - else - ret = ff.lib.avformat.av_read_frame(ctx, pkt.getPacket()); - } - - if (pkt.getStreamIndex() == this->streamIndices.video) - pkt.setPacketType(PacketType::VIDEO); - else if (this->streamIndices.audio.contains(pkt.getStreamIndex())) - pkt.setPacketType(PacketType::AUDIO); - else if (this->streamIndices.subtitle.dvb.contains(pkt.getStreamIndex())) - pkt.setPacketType(PacketType::SUBTITLE_DVB); - else if (this->streamIndices.subtitle.eia608.contains(pkt.getStreamIndex())) - pkt.setPacketType(PacketType::SUBTITLE_608); - else - pkt.setPacketType(PacketType::OTHER); - } while (ret == 0 && videoPacketsOnly && - this->currentPacket.getPacketType() != PacketType::VIDEO); - - if (ret < 0) - { - this->endOfFile = true; - return false; - } - - DEBUG_FFMPEG("FileSourceFFmpegFile::goToNextPacket: Return: stream %d pts %d dts %d%s", - (int)this->currentPacket.getStreamIndex(), - (int)this->currentPacket.getPTS(), - (int)this->currentPacket.getDTS(), - this->currentPacket.getFlagKeyframe() ? " - keyframe" : ""); - - if (this->currentPacket.getPacketType() == PacketType::VIDEO && - this->packetDataFormat == PacketDataFormat::Unknown) - // This is the first video package that we find and we don't know what the format of the packet - // data is. Guess the format from the data in the first package This format should not change - // from packet to packet - this->packetDataFormat = this->currentPacket.guessDataFormatFromData(); - - return true; -} - -bool FileSourceFFmpegFile::seekToDTS(int64_t dts) -{ - if (!this->isFileOpened) - return false; - - int ret = this->ff.seekFrame(this->formatCtx, this->video_stream.getIndex(), dts); - if (ret != 0) - { - DEBUG_FFMPEG("FFmpegLibraries::seekToDTS Error DTS %ld. Return Code %d", dts, ret); - return false; - } - - // We seeked somewhere, so we are not at the end of the file anymore. - this->endOfFile = false; - - DEBUG_FFMPEG("FFmpegLibraries::seekToDTS Successfully seeked to DTS %d", (int)dts); - return true; -} - -bool FileSourceFFmpegFile::seekFileToBeginning() -{ - if (!this->isFileOpened) - return false; - - int ret = this->ff.seekBeginning(this->formatCtx); - if (ret != 0) - { - DEBUG_FFMPEG("FFmpegLibraries::seekToBeginning Error. Return Code %d", ret); - return false; - } - - // We seeked somewhere, so we are not at the end of the file anymore. - this->endOfFile = false; - - DEBUG_FFMPEG("FFmpegLibraries::seekToBeginning Successfull."); - return true; -} - -int64_t FileSourceFFmpegFile::getMaxTS() -{ - if (!this->isFileOpened) - return -1; - - // duration / AV_TIME_BASE is the duration in seconds - // pts * timeBase is also in seconds - return this->duration / AV_TIME_BASE * this->timeBase.den / this->timeBase.num; -} - -indexRange FileSourceFFmpegFile::getDecodableFrameLimits() const -{ - if (this->keyFrameList.isEmpty() || this->nrFrames == 0) - return {}; - - indexRange range; - range.first = int(this->keyFrameList.at(0).frame); - range.second = int(this->nrFrames); - return range; -} - -QList FileSourceFFmpegFile::getFileInfoForAllStreams() -{ - QList info; - - info += this->formatCtx.getInfoText(); - for (unsigned i = 0; i < this->formatCtx.getNbStreams(); i++) - { - auto stream = this->formatCtx.getStream(i); - auto codecIdWrapper = this->ff.getCodecIDWrapper(stream.getCodecID()); - info += stream.getInfoText(codecIdWrapper); - } - - return info; -} - -QList FileSourceFFmpegFile::getTimeBaseAllStreams() -{ - QList timeBaseList; - - for (unsigned i = 0; i < this->formatCtx.getNbStreams(); i++) - { - auto stream = this->formatCtx.getStream(i); - timeBaseList.append(stream.getTimeBase()); - } - - return timeBaseList; -} - -StringVec FileSourceFFmpegFile::getShortStreamDescriptionAllStreams() -{ - StringVec descriptions; - - for (unsigned i = 0; i < this->formatCtx.getNbStreams(); i++) - { - std::ostringstream description; - auto stream = this->formatCtx.getStream(i); - description << stream.getCodecTypeName().toStdString(); - - auto codecID = this->ff.getCodecIDWrapper(stream.getCodecID()); - description << " " << codecID.getCodecName().toStdString() << " "; - - description << std::pair{stream.getFrameSize().width, stream.getFrameSize().height}; - - descriptions.push_back(description.str()); - } - - return descriptions; } diff --git a/YUViewLib/src/filesource/FileSourceFFmpegFile.h b/YUViewLib/src/filesource/FileSourceFFmpegFile.h index acc51d4cd..f97cc91db 100644 --- a/YUViewLib/src/filesource/FileSourceFFmpegFile.h +++ b/YUViewLib/src/filesource/FileSourceFFmpegFile.h @@ -32,176 +32,19 @@ #pragma once -#include "FileSource.h" -#include -#include -#include -#include -#include -#include