diff --git a/base/CMakeLists.txt b/base/CMakeLists.txt index 6d42cd043..61cff6fc5 100755 --- a/base/CMakeLists.txt +++ b/base/CMakeLists.txt @@ -156,15 +156,16 @@ SET(CORE_FILES src/Mp4WriterSinkUtils.cpp src/MultimediaQueueXform.cpp src/Mp4ReaderSource.cpp - src/Mp4ReaderSourceUtils.cpp src/RTSPClientSrc.cpp src/RTSPClientSrc.cpp src/MotionVectorExtractor.cpp src/OverlayModule.cpp + src/OrderedCacheOfFiles.cpp ) SET(CORE_FILES_H include/BufferMaker.h + include/Mp4ErrorFrame.h include/FramesMuxer.h include/FrameMetadata.h include/FrameMetadataFactory.h @@ -215,11 +216,11 @@ SET(CORE_FILES_H include/RTSPClientSrc.h include/H264Metadata.h include/Mp4ReaderSource.h - include/Mp4ReaderSourceUtils.h include/RTSPClientSrc.h include/H264Metadata.h include/MotionVectorExtractor.h include/OverlayModule.h + include/OrderedCacheOfFiles.h ) IF(ENABLE_WINDOWS) @@ -544,6 +545,12 @@ SET(UT_FILES test/rtsp_client_tests.cpp test/rtsp_client_tests.cpp test/motionvector_extractor_and_overlay_tests.cpp + test/mp4_reverse_play_tests.cpp + test/ordered_cache_of_files_tests.cpp + test/mp4_seek_tests.cpp + test/mp4_simul_read_write_tests.cpp + test/mp4_getlivevideots_tests.cpp + test/mp4_dts_strategy_tests.cpp test/overlaymodule_tests.cpp ${ARM64_UT_FILES} ${CUDA_UT_FILES} diff --git a/base/include/AIPExceptions.h b/base/include/AIPExceptions.h index 307216dff..d89596f66 100755 --- a/base/include/AIPExceptions.h +++ b/base/include/AIPExceptions.h @@ -19,9 +19,25 @@ #define AIP_NOTIMPLEMENTED 7723 #define AIP_NOTSET 7725 #define AIP_NOTEXEPCTED 7726 +#define MP4_OCOF_END 7726 // Fatal errors #define AIP_FATAL 7811 +#define MP4_NAME_PARSE_FAILED 7812 +#define MP4_FILE_CLOSE_FAILED 7813 +#define MP4_RELOAD_RESUME_FAILED 7814 +#define MP4_OPEN_FILE_FAILED 7815 +#define MP4_MISSING_VIDEOTRACK 7816 +#define MP4_MISSING_START_TS 7817 +#define MP4_TIME_RANGE_FETCH_FAILED 7818 +#define MP4_SET_POINTER_END_FAILED 7819 +#define MP4_SEEK_INSIDE_FILE_FAILED 7820 +#define MP4_BUFFER_TOO_SMALL 7821 +#define MP4_OCOF_EMPTY 7721 +#define MP4_OCOF_MISSING_FILE 7822 +#define MP4_OCOF_INVALID_DUR 7823 +#define MP4_UNEXPECTED_STATE 7824 + #define AIPException_LOG_SEV(severity,type) for(std::ostringstream stream; Logger::getLogger()->push(severity, stream);) Logger::getLogger()->aipexceptionPre(stream, severity,type) @@ -67,4 +83,28 @@ class AIP_Exception : public std::runtime_error std::string message; }; +class Mp4_Exception : public AIP_Exception +{ +public: + explicit Mp4_Exception(int type, const std::string file, int line, const std::string logMessage) : + AIP_Exception(type, file, line, logMessage) + { + } + + explicit Mp4_Exception(int type, const std::string file, int line, int _openFileErrorCode, const std::string logMessage) : + AIP_Exception(type, file, line, logMessage) + { + openFileErrorCode = _openFileErrorCode; + } + + int getOpenFileErrorCode() + { + return openFileErrorCode; + } + +private: + int openFileErrorCode = 0; +}; + #define AIPException(_type,_message) AIP_Exception(_type,__FILE__,__LINE__,_message) +#define Mp4Exception(_type,_message) Mp4_Exception(_type,__FILE__,__LINE__,_message) diff --git a/base/include/Command.h b/base/include/Command.h index cd55313fd..78908e949 100755 --- a/base/include/Command.h +++ b/base/include/Command.h @@ -15,7 +15,8 @@ class Command MultimediaQueueXform, Seek, DeleteWindow, - CreateWindow + CreateWindow, + PlayPause }; Command() @@ -279,19 +280,19 @@ class Mp4SeekCommand : public Command } - Mp4SeekCommand(uint64_t _seekStartTS,uint64_t _seekEndTS) : Command(CommandType::Seek) + Mp4SeekCommand(uint64_t _skipTS, bool _forceReopen = false) : Command(CommandType::Seek) { - seekStartTS = _seekStartTS; - seekEndTS = _seekEndTS; + seekStartTS = _skipTS; + forceReopen = _forceReopen; } size_t getSerializeSize() { - return 128 + sizeof(Mp4SeekCommand) + sizeof(seekStartTS) + sizeof(seekEndTS) + Command::getSerializeSize(); + return 128 + sizeof(Mp4SeekCommand) + sizeof(seekStartTS) +sizeof(forceReopen) + Command::getSerializeSize(); } uint64_t seekStartTS = 0; - uint64_t seekEndTS = 0; + bool forceReopen = false; private: friend class boost::serialization::access; @@ -300,6 +301,45 @@ class Mp4SeekCommand : public Command { ar& boost::serialization::base_object(*this); ar& seekStartTS; - ar& seekEndTS; + ar& forceReopen; + } +}; + +class PlayPauseCommand : public Command +{ +public: + PlayPauseCommand() : Command(CommandType::PlayPause) + { + } + + PlayPauseCommand(float _speed, bool _direction) : Command(CommandType::PlayPause) + { + + if (_speed != 0 && _speed != 1) + { + LOG_ERROR << "Fractional speed is not yet supported."; + throw AIPException(AIP_FATAL, "Fractional speed is not yet supported."); + } + speed = _speed; + direction = _direction; + } + + size_t getSerializeSize() + { + return sizeof(PlayPauseCommand) + sizeof(speed) + sizeof(direction) + Command::getSerializeSize(); + } + + // play speed of the module at any given fps + float speed = 1; + // fwd = 1, bwd = 0 + bool direction = 1; +private: + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int) + { + ar& boost::serialization::base_object(*this); + ar& speed; + ar& direction; } }; \ No newline at end of file diff --git a/base/include/Frame.h b/base/include/Frame.h index 7b486822a..b8004104e 100755 --- a/base/include/Frame.h +++ b/base/include/Frame.h @@ -20,6 +20,7 @@ class Frame :public boost::asio::mutable_buffer { int m_num, m_den; virtual bool isEoP() { return false; } virtual bool isEOS() { return false; } + virtual bool isMp4ErrorFrame() { return false; } virtual bool isEmpty() { return false; } virtual bool isPropsChange(); virtual bool isPausePlay(); @@ -51,9 +52,20 @@ class EoPFrame : public Frame class EoSFrame : public Frame { public: + enum EoSFrameType + { + GENERAL = 0, + MP4_PLYB_EOS, + MP4_SEEK_EOS, + }; EoSFrame(); virtual ~EoSFrame() {} virtual bool isEOS(); + EoSFrameType getEoSFrameType(); + EoSFrame(EoSFrameType eosType, uint64_t mp4TS); +private: + EoSFrameType type; + uint64_t mp4TS; }; class EmptyFrame :public Frame { diff --git a/base/include/Module.h b/base/include/Module.h index 46bce82da..31163050c 100644 --- a/base/include/Module.h +++ b/base/include/Module.h @@ -162,6 +162,7 @@ class Module { virtual bool init(); void operator()(); //to support boost::thread virtual bool run(); + bool play(float speed, bool direction = true); bool play(bool play); bool queueStep(); virtual bool step(); @@ -176,6 +177,7 @@ class Module { boost::shared_ptr getPacer() { return pacer; } static frame_sp getFrameByType(frame_container& frames, int frameType); virtual void flushQue(); + bool getPlayDirection() { return mDirection; } virtual void flushQueRecursive(); protected: virtual boost_deque getFrames(frame_container& frames); @@ -240,6 +242,7 @@ class Module { Utils::deSerialize(cmd, frame->data(), frame->size()); } + bool queuePlayPauseCommand(PlayPauseCommand ppCmd); frame_sp makeCommandFrame(size_t size, framemetadata_sp& metadata); frame_sp makeFrame(size_t size, string& pinId); frame_sp makeFrame(size_t size); // use only if 1 output pin is there @@ -252,6 +255,8 @@ class Module { virtual bool send(frame_container& frames, bool forceBlockingPush=false); virtual void sendEOS(); + virtual void sendEOS(frame_sp& frame); + virtual void sendMp4ErrorFrame(frame_sp& frame); virtual void sendEoPFrame(); boost::function onStepFail; @@ -318,6 +323,7 @@ class Module { bool processSourceQue(); bool handlePausePlay(bool play); + virtual bool handlePausePlay(float speed = 1, bool direction = true); virtual void notifyPlay(bool play) {} //makes buffers from frameFactory @@ -370,6 +376,8 @@ class Module { bool isFeedbackEnabled(std::string& moduleId); // get pins and call bool mPlay; + bool mDirection; + float mSpeed; uint32_t mForceStepCount; int mSkipIndex; Kind myNature; diff --git a/base/include/Mp4ErrorFrame.h b/base/include/Mp4ErrorFrame.h new file mode 100644 index 000000000..4539ae05a --- /dev/null +++ b/base/include/Mp4ErrorFrame.h @@ -0,0 +1,40 @@ +#pragma once +#include "Frame.h" +#include "Utils.h" + +class Mp4ErrorFrame : public Frame { +public: + enum Mp4ErrorFrameType + { + MP4_SEEK, + MP4_STEP + }; + + Mp4ErrorFrame() {} + Mp4ErrorFrame(int _errorType, int _errorCode, std::string &_errorMsg) + { + errorType = _errorType; + errorCode = _errorCode; + errorMsg = _errorMsg; + } + + Mp4ErrorFrame(int _errorType, int _errorCode, std::string &_errorMsg, int _openErrorCode, uint64_t& _errorMp4TS) + { + errorType = _errorType; + errorCode = _errorCode; + errorMsg = _errorMsg; + openErrorCode = _openErrorCode; + errorMp4TS = _errorMp4TS; + } + + bool isMp4ErrorFrame() + { + return true; + } + + int errorType; // SEEK/STEP + int errorCode; // defined in AIPExceptions.h + uint64_t errorMp4TS = 0; // skipTS in randomSeek, lastFrameTS in step + int openErrorCode = 0; // expose some libmp4 error codes + std::string errorMsg; // keep chars < 500 +}; \ No newline at end of file diff --git a/base/include/Mp4ReaderSource.h b/base/include/Mp4ReaderSource.h index 658febab4..675d53689 100644 --- a/base/include/Mp4ReaderSource.h +++ b/base/include/Mp4ReaderSource.h @@ -1,9 +1,10 @@ #pragma once #include "Module.h" -class Mp4readerDetailAbs; -class Mp4readerDetailJpeg; -class Mp4readerDetailH264; +#include +class Mp4ReaderDetailAbs; +class Mp4ReaderDetailJpeg; +class Mp4ReaderDetailH264; class Mp4ReaderSourceProps : public ModuleProps { @@ -13,27 +14,43 @@ class Mp4ReaderSourceProps : public ModuleProps } - Mp4ReaderSourceProps(std::string _videoPath, bool _parseFS, size_t _biggerFrameSize, size_t _biggerMetadataFrameSize, int _parseFSTimeoutDuration = 15, bool _bFramesEnabled = false) : ModuleProps() + Mp4ReaderSourceProps(std::string _videoPath, bool _parseFS, uint16_t _reInitInterval, bool _direction, bool _readLoop, bool _giveLiveTS, int _parseFSTimeoutDuration = 15, bool _bFramesEnabled = false) : ModuleProps() { - biggerFrameSize = _biggerFrameSize; - biggerMetadataFrameSize = _biggerMetadataFrameSize; - videoPath = _videoPath; - parseFS = _parseFS; - bFramesEnabled = _bFramesEnabled; - parseFSTimeoutDuration = _parseFSTimeoutDuration; - if (parseFS) + /* About props: + - videoPath - Path of a video from where the reading will start. + - reInitInterval - Live Mode if reInitInterval > 0 i.e. reading a file as it gets written. We recheck the last file every reInitInterval time to see if it has been updated. + - direction - Playback direction (fwd/bwd). + - parseFS - Read the NVR format till infinity, if true. Else we read only one file. + - readLoop - Read a single video in loop. It can not be used in conjuction with live mode (reInitInterval > 0) or NVR mode (parseFS = true) mode. + - giveLiveTS - If enabled, gives live timestamps instead of recorded timestamps in the video files. + */ + + if (reInitInterval < 0) { - skipDir = boost::filesystem::path(videoPath).parent_path().parent_path().parent_path().string(); + auto errMsg = "incorrect prop reInitInterval <" + std::to_string(reInitInterval) + ">"; + throw AIPException(AIP_FATAL, errMsg); } - } - - Mp4ReaderSourceProps(std::string _videoPath, bool _parseFS, int _parseFSTimeoutDuration = 15, bool _bFramesEnabled = false) : ModuleProps() - { - videoPath = _videoPath; + if (_readLoop && (_reInitInterval || _parseFS)) + { + auto errMsg = "Incorrect parameters. Looping can not be coupled with retry feature or disk parsing. loop <" + std::to_string(_readLoop) + + "> reInitInterval <" + std::to_string(reInitInterval) + "> parseFS <" + std::to_string(_parseFS) + ">"; + throw AIPException(AIP_FATAL, errMsg); + } + auto canonicalVideoPath = boost::filesystem::canonical(_videoPath); + videoPath = canonicalVideoPath.string(); parseFS = _parseFS; + skipDir = boost::filesystem::path(canonicalVideoPath).parent_path().parent_path().parent_path().string(); bFramesEnabled = _bFramesEnabled; + direction = _direction; + giveLiveTS = _giveLiveTS; + if (_reInitInterval < 0) + { + throw AIPException(AIP_FATAL, "reInitInterval must be 0 or more seconds"); + } parseFSTimeoutDuration = _parseFSTimeoutDuration; + readLoop = _readLoop; + reInitInterval = _reInitInterval; if (parseFS) { skipDir = boost::filesystem::path(videoPath).parent_path().parent_path().parent_path().string(); @@ -41,19 +58,28 @@ class Mp4ReaderSourceProps : public ModuleProps } + void setMaxFrameSizes(size_t _maxImgFrameSize, size_t _maxMetadataFrameSize) + { + biggerFrameSize = _maxImgFrameSize; + biggerMetadataFrameSize = _maxMetadataFrameSize; + } size_t getSerializeSize() { - return ModuleProps::getSerializeSize() + sizeof(videoPath) + sizeof(parseFS) + sizeof(skipDir) + + sizeof(parseFSTimeoutDuration); + return ModuleProps::getSerializeSize() + sizeof(videoPath) + sizeof(parseFS) + sizeof(skipDir) + sizeof(direction) + sizeof(parseFSTimeoutDuration) + sizeof(biggerFrameSize) + sizeof(biggerMetadataFrameSize) + sizeof(bFramesEnabled); } - std::string skipDir = "./data/mp4_videos"; + std::string skipDir = "./data/Mp4_videos"; std::string videoPath = ""; size_t biggerFrameSize = 600000; size_t biggerMetadataFrameSize = 60000; bool parseFS = true; + bool direction = true; bool bFramesEnabled = false; + uint16_t reInitInterval = 0; int parseFSTimeoutDuration = 15; + bool readLoop = false; + bool giveLiveTS = false; private: friend class boost::serialization::access; @@ -68,6 +94,9 @@ class Mp4ReaderSourceProps : public ModuleProps ar& biggerMetadataFrameSize; ar& bFramesEnabled; ar& parseFSTimeoutDuration; + ar& direction; + ar& readLoop; + ar& giveLiveTS; } }; @@ -80,20 +109,34 @@ class Mp4ReaderSource : public Module bool term(); Mp4ReaderSourceProps getProps(); void setProps(Mp4ReaderSourceProps& props); + std::string getOpenVideoPath(); + void setImageMetadata(std::string& pinId, framemetadata_sp& metadata); std::string addOutPutPin(framemetadata_sp& metadata); - bool randomSeek(uint64_t seekStartTS, uint64_t seekEndTS); + bool changePlayback(float speed, bool direction); + bool getVideoRangeFromCache(std::string videoPath, uint64_t& start_ts, uint64_t& end_ts); + bool randomSeek(uint64_t skipTS, bool forceReopen = false); + bool refreshCache(); + std::map> getCacheSnapShot(); // to be used for debugging only + double getOpenVideoFPS(); + double getOpenVideoDurationInSecs(); + int32_t getOpenVideoFrameCount(); + void getResolution(uint32_t& width, uint32_t& height) + { + width = mWidth; + height = mHeight; + } protected: bool produce(); bool validateOutputPins(); bool handleCommand(Command::CommandType type, frame_sp& fame); bool handlePropsChange(frame_sp& frame); + bool handlePausePlay(float speed, bool direction) override; private: std::string h264ImagePinId; std::string encodedImagePinId; - std::string mp4FramePinId; - int outImageFrameType; - boost::shared_ptr mDetail; + uint32_t mWidth = 0; + uint32_t mHeight = 0; + std::string metadataFramePinId; + boost::shared_ptr mDetail; Mp4ReaderSourceProps props; - std::function _makeFrame; - std::function _getOutputMetadataByType; }; \ No newline at end of file diff --git a/base/include/Mp4ReaderSourceUtils.h b/base/include/Mp4ReaderSourceUtils.h deleted file mode 100644 index e3b1b56e5..000000000 --- a/base/include/Mp4ReaderSourceUtils.h +++ /dev/null @@ -1,151 +0,0 @@ -#pragma once - -#include -#include -#include - -class FileStructureParser -{ -public: - enum ParseStatus - { - NOT_FOUND = -3, - END_OF_FILE, - END_OF_RECORDINGS, - FOUND, - FOUND_NEXT - }; - - FileStructureParser(); - bool init(std::string& _seedVideoFilePath, std::vector& parsedVideoFiles, bool includeStarting, bool parseDir = true); - bool parse(std::string& startingVideoFile, std::vector& parsedVideoFiles, bool includeStarting); - int randomSeek(uint64_t& skipTS, std::string& skipDir, std::string& videoFile, uint64_t& skipMsecsInFile); - bool setParseLimit(uint32_t _nParseFiles); - bool parseDir(boost::filesystem::path dirPath, std::string& videoName); - int getNextToVideoFileFlag() - { - return nextToVideoFileFlag; - } - std::string getNextVideoFile() - { - return nextToVideoFile; - } -private: - void parseFilesInDirectory(boost::filesystem::path& folderPath, - std::vector& files); - void parseFilesInDirectories(std::vector& dirs, - std::vector& files); - void parseDirectoriesInDirectory(boost::filesystem::path& folderPath, - std::vector& hourDirectories); - void filterRelevantPaths(std::vector& allPaths, - boost::filesystem::path& startingPath, std::vector& revelantPaths); - void parseDayDirectories(std::vector& relevantDayDirectories, - std::vector& relevantVideoFiles); - void boostPathToStrUtil(std::vector& boost_paths, - std::vector& paths); - template - int greater(std::vector& list, T& elem) - { - int st = 0; - int listSize = list.size(); - int end = listSize - 1; - while (st < end) - { - int mid = (st + end) / 2; - if (list[mid] <= elem) - { - st = mid + 1; - } - else - { - end = mid; - } - } - if (st == listSize - 1 && list[st] <= elem) - { - return -1; - } - return st; - } - - template - int greaterOrEqual(std::vector& list, T& elem) - { - int st = 0; - int listSize = list.size(); - int end = listSize - 1; - while (st < end) - { - int mid = (st + end) / 2; - if (list[mid] < elem) - { - st = mid + 1; - } - else - { - end = mid; - } - } - if (st == listSize - 1 && list[st] < elem) - { - return -1; - } - return st; - } - - template - int lesserOrEqual(std::vector& list, T& elem) - { - int st = 0; - int listSize = list.size(); - int end = listSize - 1; - if (list[end] < elem) - { - return end; - } - while (st <= end) - { - int mid = (st + end) / 2; - if (list[mid] == elem) - { - return mid; - } - if (mid > 0 && list[mid - 1] <= elem && list[mid] > elem) - { - return mid - 1; - } - - if (list[mid] > elem) - { - end = mid - 1; - } - else - { - st = mid + 1; - } - } - if (!st && list[st] > elem) - { - return -1; - } - return st; - } - - // random seek - int firstOfNextDay(boost::filesystem::path& baseFolder, std::string& yyyymmdd, std::string& videoFile); - int firstOfNextHour(boost::filesystem::path& yyyymmddDir, std::string& hr, std::string& videoFile); - int findFileWithMinute(boost::filesystem::path& hrDir, std::string& min, std::string& videoFile); - - bool filePatternCheck(const boost::filesystem::path& path); - bool datePatternCheck(const boost::filesystem::path& path); - bool hourPatternCheck(const boost::filesystem::path& path); - - std::string format_2(int& num); - std::string format_hrs(int& hr); - - /* this limit is checked after min one yymmdd folder parsed */ - uint32_t nParseFiles = 24 * 60; - boost::filesystem::path latestParsedVideoPath; - std::string nextToVideoFile = ""; - int nextToVideoFileFlag; -}; \ No newline at end of file diff --git a/base/include/Mp4WriterSink.h b/base/include/Mp4WriterSink.h index 8bcff4dba..340cbba16 100644 --- a/base/include/Mp4WriterSink.h +++ b/base/include/Mp4WriterSink.h @@ -35,7 +35,7 @@ class Mp4WriterSinkProps : public ModuleProps Mp4WriterSinkProps() : ModuleProps() { - baseFolder = "./data/mp4_videos/"; + baseFolder = "./data/Mp4_videos/"; chunkTime = 1; //minutes syncTimeInSecs = 1; fps = 30; diff --git a/base/include/OrderedCacheOfFiles.h b/base/include/OrderedCacheOfFiles.h new file mode 100644 index 000000000..44d368f64 --- /dev/null +++ b/base/include/OrderedCacheOfFiles.h @@ -0,0 +1,134 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#define batch_size 1440 + +namespace fs = boost::filesystem; +namespace bmi = boost::multi_index; + + +class OrderedCacheOfFiles +{ +public: + OrderedCacheOfFiles(std::string& video_folder, uint32_t initial_batch_size = 1440, uint32_t _lowerWaterMark = 1441, uint32_t _upperWaterMark = 2880); + ~OrderedCacheOfFiles() + { + if (!cleanCacheOnMainThread && mThread) + { + mThread->join(); + } + } + void updateBatchSize(uint32_t _batchSize) + { + batchSize = _batchSize; + } + void cleanCacheOnSeperateThread(bool flag) + { + cleanCacheOnMainThread = !flag; + } + void deleteLostEntry(std::string& filePath); + uint64_t getFileDuration(std::string& filename); + // Note - getFileAt() is an unreliable method. Use ONLY if you know what you are doing. + std::string getFileAt(uint64_t timestamp, bool direction); + bool isTimeStampInFile(std::string& filename, uint64_t timestamp); + std::string getNextFileAfter(std::string& currentFile, bool direction); + std::vector parseAndSortDateDir(const std::string& rootDir); + std::vector parseAndSortHourDir(const std::string& rootDir); + std::vector parseAndSortMp4Files(const std::string& rootDir); + bool parseFiles(uint64_t start_ts, bool direction, bool includeFloorFile = false, bool disableBatchSizeCheck = false, uint64_t skipTS = 0); + bool getRandomSeekFile(uint64_t ts, bool direction, uint64_t& skipMsecs, std::string& fileName); + bool getFileFromCache(uint64_t timestamp, bool direction, std::string& fileName); + size_t getCacheSize() + { + return videoCache.size(); + } + bool fetchAndUpdateFromDisk(std::string videoFile, uint64_t& start_ts, uint64_t& end_ts); + bool fetchFromCache(std::string& videoFile, uint64_t& start_ts, uint64_t& end_ts); + void readVideoStartEnd(std::string& filePath, uint64_t& start_ts, uint64_t& end_ts); + void clearCache(); + bool refreshCache(); + std::string getLastVideoInCache() { return videoCache.rbegin()->path; } + void updateCache(std::string& filePath, uint64_t& start_ts, uint64_t& end_ts); // allow updates from playback + std::map> getSnapShot(); // too costly, use for debugging only + bool probe(boost::filesystem::path dirPath, std::string& videoName); +private: + bool lastKnownPlaybackDir = true; // sync with mp4 playback + boost::mutex m_mutex; + boost::shared_ptr mThread = nullptr; + int cacheSize = 1440; + int batchSize = 1440; + std::string rootDir = ""; + uint32_t lowerWaterMark; + uint32_t upperWaterMark; + bool cleanCacheOnMainThread = true; + /* Util methods */ + bool filePatternCheck(const fs::path& path); + bool datePatternCheck(const boost::filesystem::path& path); + bool hourPatternCheck(const boost::filesystem::path& path); + //bool openFileUpdateCacheByIter(iter); + //bool openFileUpdateCacheByFilename(filePath); + + /* ---------Cache Stuff ------------*/ + struct CacheIteratorState + { + std::string END_ITER = "END_ITERATOR"; + } cacheIteratorState; + + // cache entry format + struct Video + { + uint64_t start_ts, end_ts; + std::string path; + + Video(std::string& _path, uint64_t _start_ts) : path(_path), start_ts(_start_ts) + { + end_ts = 0; + } + + Video(const std::string _path, uint64_t _start_ts) : path(_path), start_ts(_start_ts) + { + end_ts = 0; + } + + Video(std::string _path, uint64_t _start_ts, uint64_t _end_ts) : path(_path), start_ts(_start_ts), end_ts(_end_ts) {} + + void updateEndTS(uint64_t ts) + { + end_ts = ts; + } + }; + + // cache index tags + struct videoPath {}; + struct videoTS {}; + + // VideoCache data type + typedef boost::multi_index_container< + Video, + bmi::indexed_by< + // sort by less on path + bmi::ordered_unique, bmi::member >, + + // sort by less on videoTS + bmi::ordered_non_unique, bmi::member > + > + > VideoCache; + + // Cache object and index + VideoCache videoCache; + VideoCache::index::type& videoCacheStartTSIndex = videoCache.get(); + + // Cache methods + void insertInVideoCache(Video vid); + + // Cache cleanup strategy + void retireOldFiles(uint64_t startTSofRelevantFile); + void dropFarthestFromTS(uint64_t startTS); + +}; \ No newline at end of file diff --git a/base/src/Frame.cpp b/base/src/Frame.cpp index 87e2eb77e..0cad6e359 100755 --- a/base/src/Frame.cpp +++ b/base/src/Frame.cpp @@ -66,6 +66,19 @@ bool EoSFrame::isEOS() { return true; } EmptyFrame::EmptyFrame() : Frame() {} bool EmptyFrame::isEmpty() { return true; } +EoSFrame::EoSFrameType EoSFrame::getEoSFrameType() +{ + return type; +} + +EoSFrame::EoSFrame(EoSFrame::EoSFrameType _type, uint64_t _mp4TS) : Frame() +{ + type = _type; + mp4TS = _mp4TS; + timestamp = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); +} + + ExternalFrame::ExternalFrame(ApraData* data) : Frame() { data->locked.fetch_add(1, memory_order_seq_cst); diff --git a/base/src/Module.cpp b/base/src/Module.cpp index 5fff6140f..7c00ab596 100644 --- a/base/src/Module.cpp +++ b/base/src/Module.cpp @@ -121,7 +121,7 @@ class Module::Profiler double mPipelineFps; }; -Module::Module(Kind nature, string name, ModuleProps _props) : mRunning(false), mPlay(true), mForceStepCount(0), mStopCount(0), mForwardPins(0), myNature(nature), myName(name), mSkipIndex(0) +Module::Module(Kind nature, string name, ModuleProps _props) : mRunning(false), mPlay(true), mDirection(true), mForceStepCount(0), mStopCount(0), mForwardPins(0), myNature(nature), myName(name), mSkipIndex(0) { static int moduleCounter = 0; moduleCounter += 1; @@ -1028,20 +1028,11 @@ bool Module::shouldTriggerSOS() return false; } -bool Module::play(bool play) +bool Module::queuePlayPauseCommand(PlayPauseCommand ppCmd) { - if (!mRunning) - { - // comes here if module is not running in a thread - // comes here when pipeline is started with run_all_threaded_withpause - return handlePausePlay(play); - } - auto metadata = framemetadata_sp(new PausePlayMetadata()); - auto frame = makeCommandFrame(metadata->getDataSize(), metadata); - - auto buffer = (unsigned char *)frame->data(); - memset(frame->data(), play, 1); + auto frame = makeCommandFrame(ppCmd.getSerializeSize(), metadata); + Utils::serialize(ppCmd, frame->data(), ppCmd.getSerializeSize()); // add to que frame_container frames; @@ -1049,11 +1040,33 @@ bool Module::play(bool play) if (!Module::try_push(frames)) { LOG_ERROR << "failed to push play command to the que"; + return false; } - return true; } +bool Module::play(bool _play) +{ + if (_play) + { + return play(1, mDirection); + } + + return play(0, mDirection); +} + +bool Module::play(float speed, bool direction) +{ + if (!mRunning) + { + // comes here if module is not running in a thread + // comes here when pipeline is started with run_all_threaded_withpause + return handlePausePlay(speed, direction); + } + PlayPauseCommand ppCmd(speed, direction); + return queuePlayPauseCommand(ppCmd); +} + bool Module::queueStep() { auto cmd = StepCommand(); @@ -1104,9 +1117,9 @@ bool Module::processSourceQue() if (frame->isPausePlay()) { - auto buffer = (unsigned char *)frame->data(); - auto play = buffer[0] ? true : false; - handlePausePlay(play); + PlayPauseCommand ppCmd; + getCommand(ppCmd, frame); + handlePausePlay(ppCmd.speed, ppCmd.direction); } else if (frame->isPropsChange()) { @@ -1132,11 +1145,17 @@ bool Module::processSourceQue() return true; } +bool Module::handlePausePlay(float speed, bool direction) +{ + mDirection = direction; + return handlePausePlay(speed > 0); +} + bool Module::handlePausePlay(bool play) { mPlay = play; notifyPlay(mPlay); - + mSpeed = mPlay ? 1 : 0; return true; } @@ -1205,6 +1224,38 @@ void Module::sendEOS() send(frames, true); } +void Module::sendEOS(frame_sp& frame) +{ + if (myNature == SINK) + { + return; + } + frame_container frames; + pair me; // map element + BOOST_FOREACH(me, mOutputPinIdFrameFactoryMap) + { + frames.insert(make_pair(me.first, frame)); + } + + send(frames, true); +} + +void Module::sendMp4ErrorFrame(frame_sp& frame) +{ + if (myNature == SINK) + { + return; + } + + frame_container frames; + pair me; // map element + BOOST_FOREACH(me, mOutputPinIdFrameFactoryMap) { + frames.insert(make_pair(me.first, frame)); + } + + send(frames, true); +} + bool Module::preProcessNonSource(frame_container &frames) { auto bTriggerSOS = shouldTriggerSOS(); // donot calculate every time - store the state when condition changes diff --git a/base/src/Mp4ReaderSource.cpp b/base/src/Mp4ReaderSource.cpp index 4acffe008..f5471f058 100644 --- a/base/src/Mp4ReaderSource.cpp +++ b/base/src/Mp4ReaderSource.cpp @@ -1,40 +1,170 @@ #include "Mp4ReaderSource.h" #include "FrameMetadata.h" #include "Mp4VideoMetadata.h" -#include "Mp4ReaderSourceUtils.h" #include "EncodedImageMetadata.h" #include "H264Metadata.h" #include "Frame.h" #include "Command.h" #include "libmp4.h" #include "H264Utils.h" +#include "OrderedCacheOfFiles.h" +#include "AIPExceptions.h" +#include "Mp4ErrorFrame.h" +#include "Module.h" -class Mp4readerDetailAbs +class Mp4ReaderDetailAbs { public: - Mp4readerDetailAbs(Mp4ReaderSourceProps& props, std::function _makeFrame, - std::function _makeframe, std::function _sendEOS) + Mp4ReaderDetailAbs(Mp4ReaderSourceProps& props, std::function _makeFrame, + std::function _makeFrameTrim, std::function _sendEOS, + std::function _setMetadata, std::function _sendMp4ErrorFrame) { setProps(props); makeFrame = _makeFrame; - makeframe = _makeframe; + makeFrameTrim = _makeFrameTrim; sendEOS = _sendEOS; - mFSParser = boost::shared_ptr(new FileStructureParser()); + mSetMetadata = _setMetadata; + sendMp4ErrorFrame = _sendMp4ErrorFrame; + cof = boost::shared_ptr(new OrderedCacheOfFiles(mProps.skipDir)); } - ~Mp4readerDetailAbs() + ~Mp4ReaderDetailAbs() { } - virtual void setMetadata() = 0; + virtual void setMetadata() + { + auto mp4FrameMetadata = framemetadata_sp(new Mp4VideoMetadata("v_1_0")); + auto serFormatVersion = getSerFormatVersion(); + auto mp4VideoMetadata = FrameMetadataFactory::downcast(mp4FrameMetadata); + mp4VideoMetadata->setData(serFormatVersion); + } + virtual void sendEndOfStream() = 0; virtual bool produceFrames(frame_container& frames) = 0; - virtual void prependSpsPps(boost::asio::mutable_buffer& iFrameBuffer) = 0; - virtual int mp4Seek(mp4_demux* demux,uint64_t time_offset_usec, mp4_seek_method syncType) = 0; + virtual int mp4Seek(mp4_demux* demux, uint64_t time_offset_usec, mp4_seek_method syncType, int& seekedToFrame) = 0; - void setProps(Mp4ReaderSourceProps& props) + bool Init() { + sentEOSSignal = false; + + if (mProps.parseFS) + { + auto boostVideoTS = boost::filesystem::path(mState.mVideoPath).stem().string(); + uint64_t start_parsing_ts = 0; + try + { + start_parsing_ts = std::stoull(boostVideoTS); + } + catch (std::invalid_argument) + { + auto msg = "Video File name not in proper format.Check the filename sent as props. \ + If you want to read a file with custom name instead, please disable parseFS flag."; + LOG_ERROR << msg; + throw AIPException(AIP_FATAL, msg); + } + cof->parseFiles(start_parsing_ts, mState.direction, true, false); // enable exactMatch, dont disable disableBatchSizeCheck + } + return initNewVideo(true); // enable firstOpenAfterinit + } + + void updateMstate(Mp4ReaderSourceProps& props, std::string videoPath) + { + mState.direction = props.direction; + mState.mVideoPath = videoPath; mProps = props; - mState.mVideoPath = mProps.videoPath; + } + + void setProps(Mp4ReaderSourceProps& props) + { + std::string tempVideoPath; + std::chrono::time_point t = std::chrono::system_clock::now(); + auto dur = std::chrono::duration_cast(t.time_since_epoch()); + uint64_t nowTS = dur.count(); + reloadFileAfter = calcReloadFileAfter(); + // To check if the video file path is correct + try + { + auto canonicalVideoPath = boost::filesystem::canonical(props.videoPath); + tempVideoPath = canonicalVideoPath.string(); + } + catch (...) + { + auto msg = "Video File name not in proper format.Check the filename sent as props. \ + If you want to read a file with custom name instead, please disable parseFS flag."; + LOG_ERROR << msg; + return; + } + + // If the video path is a custom file - don't parse just open the video, cof check says that not to open video if setProps if called from module init(). + if (!props.parseFS && cof) + { + cof->clearCache(); + updateMstate(props, tempVideoPath); + initNewVideo(); + return; + } + + auto tempSkipDir = boost::filesystem::path(tempVideoPath).parent_path().parent_path().parent_path().string(); + if (props.parseFS && mProps.skipDir == tempSkipDir && mState.mVideoPath != "") + { + if (mProps.videoPath == props.videoPath) + mProps = props; + LOG_ERROR << "The root dir is same and only file path has changed, Please use SEEK functionality instead for this use case!, cannot change props"; + return; + } + + if (props.parseFS && mProps.skipDir != tempSkipDir && mState.mVideoPath != "") + { + sentEOSSignal = false; + + auto boostVideoTS = boost::filesystem::path(tempVideoPath).stem().string(); + uint64_t start_parsing_ts = 0; + try + { + start_parsing_ts = std::stoull(boostVideoTS); + } + catch (std::invalid_argument) + { + auto msg = "Video File name not in proper format.Check the filename sent as props. \ + If you want to read a file with custom name instead, please disable parseFS flag."; + LOG_ERROR << msg; + throw AIPException(AIP_FATAL, msg); + } + + //check if root has changed + cof = boost::shared_ptr(new OrderedCacheOfFiles(tempSkipDir)); + cof->clearCache(); + cof->parseFiles(start_parsing_ts, props.direction, true, false); + //parse successful - update mState and skipDir with current root dir + updateMstate(props, tempVideoPath); + mProps.skipDir = tempSkipDir; + initNewVideo(true); + + return; + } + + // It comes here when setProps is called during Module startup + updateMstate(props, tempVideoPath); + } + + std::string getOpenVideoPath() + { + return mState.mVideoPath; + } + + int32_t getOpenVideoFrameCount() + { + return mState.mFramesInVideo; + } + + std::map> getSnapShot() + { + return cof->getSnapShot(); + } + + bool refreshCache() + { + return cof->refreshCache(); } std::string getSerFormatVersion() @@ -42,91 +172,248 @@ class Mp4readerDetailAbs return mState.mSerFormatVersion; } - void Init() + void setPlayback(float _speed, bool _direction) + { + if (_speed != mState.speed) + { + mState.speed = _speed; + } + // only if direction changes + if (mState.direction != _direction) + { + mState.direction = _direction; + /* using the new direction, check if new video has to be opened + setting proper mFrameCounterIdx will ensure new video is init at right time.*/ + if (mState.direction) + { + // bwd -> fwd + if (mState.mFrameCounterIdx > mState.mFramesInVideo - 1) + { + // before direction change: we were going to read last frame + mState.mFrameCounterIdx = mState.mFramesInVideo; + } + else + { + // if we were at EOF before dir change - so end/wait/sendEOS flags need to be reset + if (mState.mFrameCounterIdx == -1) + { + mState.end = false; + waitFlag = false; + sentEOSSignal = false; + } + mState.mFrameCounterIdx++; + } + } + else if (!mState.direction) + { + // fwd -> bwd + if (mState.mFrameCounterIdx <= 0) + { + // before direction change: we were going to read first frame + mState.mFrameCounterIdx = -1; + } + else + { + // if we were at EOF before dir change - so end/wait/sendEOS flags need to be reset + if (mState.mFrameCounterIdx == mState.mFramesInVideo) + { + mState.end = false; + waitFlag = false; + sentEOSSignal = false; + } + mState.mFrameCounterIdx--; + } + } + LOG_TRACE << "changed direction frameIdx <" << mState.mFrameCounterIdx << "> totalFrames <" << mState.mFramesInVideo << ">"; + mp4_demux_toggle_playback(mState.demux, mState.video.id); + } + } + + bool getVideoRangeFromCache(std::string& videoPath, uint64_t& start_ts, uint64_t& end_ts) + { + return cof->fetchFromCache(videoPath, start_ts, end_ts); + } + + bool attemptFileClose() { - initNewVideo(); + LOG_INFO << "attemptFileClose called"; + try + { + if (mState.demux) + { + mp4_demux_close(mState.demux); + mState.demux = nullptr; + } + } + catch (...) + { + auto msg = "Error occured while closing the video file <" + mState.mVideoPath + ">"; + LOG_ERROR << msg; + throw Mp4Exception(MP4_FILE_CLOSE_FAILED, msg); + } + return true; } bool parseFS() { /* - raise error if init fails - return 0 if no relevant files left on disk - relevant - files with timestamp after the mVideoPath + parseFS() asks OrderedCacheOfFiles to parse disk with timestamp of current file and playback direction. + return 0, if no relevant files left on disk (end state i.e. EOF) + else return 1 */ - bool includeStarting = (mState.mVideoCounter ? false : true) | (mState.randomSeekParseFlag); - mState.mParsedVideoFiles.clear(); - bool flag = mFSParser->init(mState.mVideoPath, mState.mParsedVideoFiles, includeStarting, mProps.parseFS); - if (!flag) - { - LOG_ERROR << "File Structure Parsing Failed. Check logs."; - throw AIPException(AIP_FATAL, "Parsing File Structure failed"); - } - mState.mVideoCounter = 0; - mState.randomSeekParseFlag = false; - mState.mParsedFilesCount = mState.mParsedVideoFiles.size(); - return mState.mParsedFilesCount == 0; + bool foundRelevantFilesOnDisk = cof->parseFiles(mState.resolvedStartingTS, mState.direction); + return foundRelevantFilesOnDisk; } /* initNewVideo responsible for setting demux-ing files in correct order Opens the mp4 file at mVideoCounter index in mParsedVideoFiles If mParsedVideoFiles are exhausted - it performs a fresh fs parse for mp4 files */ - bool initNewVideo() + bool initNewVideo(bool firstOpenAfterInit = false) { /* parseFS() is called: only if parseFS is set AND (it is the first time OR if parse file limit is reached) returns false if no relevant mp4 file left on disk. */ - auto videoPath = boost::filesystem::path(mState.mVideoPath); - if (videoPath.extension() != ".mp4") + // in case race conditions happen between writer and reader (videotrack not found etc) - use code will retry + auto filePath = boost::filesystem::path(mState.mVideoPath); + if (filePath.extension() != ".mp4") { - if (!mFSParser->parseDir(videoPath, mState.mVideoPath)) + if (!cof->probe(filePath, mState.mVideoPath)) { LOG_DEBUG << "Mp4 file is not present" << ">"; isVideoFileFound = false; - return false; + return true; } isVideoFileFound = true; } - if (mProps.parseFS && (mState.mParsedVideoFiles.empty() || mState.mVideoCounter == mState.mParsedFilesCount)) + auto nextFilePath = mState.mVideoPath; + if (mProps.parseFS) { - mState.end = parseFS(); + if (!firstOpenAfterInit) + { + try + { + nextFilePath = cof->getNextFileAfter(mState.mVideoPath, mState.direction); + } + catch (AIP_Exception& exception) + { + if (exception.getCode() == MP4_OCOF_END) // EOC + { + LOG_INFO << "End OF Cache. Parsing disk again."; + nextFilePath = ""; + } + else + { + auto msg = "Failed to find next file to <" + mState.mVideoPath + ">"; + LOG_ERROR << msg; + throw Mp4Exception(MP4_UNEXPECTED_STATE, msg); + } + } + } + if (nextFilePath.empty()) // we had reached EOC + { + mState.end = !parseFS(); + if (!mState.end) + { + try + { + nextFilePath = cof->getNextFileAfter(mState.mVideoPath, mState.direction); // from updated cache + } + catch (Mp4_Exception& ex) + { + if (ex.getCode() == MP4_OCOF_END) + { + LOG_ERROR << "parse found new files but getNextFileAfter hit EOC while looking for a potential file."; + mState.end = true; + } + else + { + auto msg = "unexpected state while getting next file after successful parse <" + ex.getError() + ">"; + LOG_ERROR << msg; + throw Mp4Exception(MP4_UNEXPECTED_STATE, msg); + } + } + } + } } - // no files left to read + // no files left to read OR no new files even after fresh parse OR empty folder if (mState.end) { - mState.mVideoPath = ""; - return false; - } - - if (mState.mVideoCounter < mState.mParsedFilesCount) //just for safety - { - mState.mVideoPath = mState.mParsedVideoFiles[mState.mVideoCounter]; - ++mState.mVideoCounter; + LOG_INFO << "Reached EOF end state in playback."; + if (mProps.readLoop) + { + openVideoSetPointer(mState.mVideoPath); + mState.end = false; + return true; + } + // reload the current file + if (waitFlag) + { + uint64_t tstart_ts, tend_ts; + cof->readVideoStartEnd(mState.mVideoPath, tstart_ts, tend_ts); + if (mProps.parseFS) // update cache + { + cof->fetchAndUpdateFromDisk(mState.mVideoPath, tstart_ts, tend_ts); + } + // verify if file is updated + LOG_TRACE << "old endTS <" << mState.endTS << "> new endTS <" << tend_ts << ">"; + if (mState.endTS >= tend_ts) + { + return true; + } + // open the video in reader and set pointer at correct place + LOG_TRACE << "REOPEN THE FILE < " << mState.mVideoPath << ">"; + openVideoSetPointer(mState.mVideoPath); + auto seekTS = mState.direction ? mState.frameTSInMsecs + 1 : mState.frameTSInMsecs - 1; + auto ret = randomSeekInternal(seekTS); // also resets end + if (!ret) + { + auto msg = "Unexpected issue occured while resuming playback after reloading the file <" + + mState.mVideoPath + "> @seekTS <" + std::to_string(seekTS) + ">"; + LOG_ERROR << msg; + throw Mp4Exception(MP4_RELOAD_RESUME_FAILED, msg); + } + // disable reloading now + waitFlag = false; + // allow sending EOS frame again + sentEOSSignal = false; + } + return true; } + LOG_INFO << "Opening New Video <" << nextFilePath << ">"; + /* in case we had reached EOR -> waiting state -> but after reInitInterval new file has been written + the waitFlag will be reset in openVideoSetPointer + */ + openVideoSetPointer(nextFilePath); + return true; + } - LOG_INFO << "InitNewVideo <" << mState.mVideoPath << ">"; - - /* libmp4 stuff */ - // open the mp4 file here + /* + throws MP4_OPEN_FAILED_EXCEPTION, MP4_MISSING_VIDEOTRACK, MP4_MISSING_START_TS, + MP4_TIME_RANGE_FETCH_FAILED, MP4_SET_POINTER_END_FAILED + */ + void openVideoSetPointer(std::string& filePath) + { if (mState.demux) { - mp4_demux_close(mState.demux); - mState.videotrack = -1; - mState.metatrack = -1; - mState.mFrameCounter = 0; + termOpenVideo(); } - ret = mp4_demux_open(mState.mVideoPath.c_str(), &mState.demux); + LOG_INFO << "opening video <" << filePath << ">"; + ret = mp4_demux_open(filePath.c_str(), &mState.demux); if (ret < 0) { - throw AIPException(AIP_FATAL, "Failed to open the file <" + mState.mVideoPath + ">"); + //TODO: Behaviour yet to be decided in case a file is deleted while it is cached, generating a hole in the cache. + auto msg = "Failed to open the file <" + filePath + "> libmp4 errorcode<" + std::to_string(ret) + ">"; + LOG_ERROR << msg; + throw Mp4Exception(MP4_OPEN_FILE_FAILED, msg); } + /* read metadata string to get serializer format version & starting timestamp from the header */ unsigned int count = 0; char** keys = NULL; char** values = NULL; @@ -136,31 +423,30 @@ class Mp4readerDetailAbs LOG_ERROR << "mp4_demux_get_metadata_strings <" << -ret; } - auto boostVideoTS = boost::filesystem::path(mState.mVideoPath).stem().string(); - if (count > 0) { - LOG_DEBUG << "Reading User Metadata Key-Values\n"; - for (auto i = 0; i < count; i++) { + LOG_INFO << "Reading User Metadata Key-Values\n"; + for (unsigned int i = 0; i < count; i++) { if ((keys[i]) && (values[i])) { if (!strcmp(keys[i], "\251too")) { - LOG_DEBUG << "key <" << keys[i] << ",<" << values[i] << ">"; + LOG_INFO << "key <" << keys[i] << ",<" << values[i] << ">"; mState.mSerFormatVersion.assign(values[i]); } if (!strcmp(keys[i], "\251sts")) { - LOG_DEBUG << "key <" << keys[i] << ",<" << values[i] << ">"; - mState.startTimeStamp = std::stoull(values[i]); + LOG_INFO << "key <" << keys[i] << ",<" << values[i] << ">"; + mState.startTimeStampFromFile = std::stoull(values[i]); } } } } + /* Update mState with relevant information */ + mState.mVideoPath = filePath; mState.ntracks = mp4_demux_get_track_count(mState.demux); for (auto i = 0; i < mState.ntracks; i++) { - ret = mp4_demux_get_track_info(mState.demux, i, &mState.info); if (ret < 0) { LOG_ERROR << "mp4 track info fetch failed <" << i << "> ret<" << ret << ">"; @@ -173,154 +459,293 @@ class Mp4readerDetailAbs mState.has_more_video = mState.info.sample_count > 0; mState.videotrack = 1; mState.mFramesInVideo = mState.info.sample_count; - width = mState.info.video_width; - height = mState.info.video_height; + mWidth = mState.info.video_width; + mHeight = mState.info.video_height; + mDurationInSecs = mState.info.duration / mState.info.timescale; + mFPS = mState.mFramesInVideo / mDurationInSecs; } } if (mState.videotrack == -1) { - LOG_ERROR << "No Videotrack found in the video <" << mState.mVideoPath << " Stopping."; - throw AIPException(AIP_FATAL, "No video track found"); + auto msg = "No Videotrack found in the video <" + mState.mVideoPath + ">"; + LOG_ERROR << msg; + throw Mp4Exception(MP4_MISSING_VIDEOTRACK, msg); } - try + // starting timestamp of the video will either come from the video name or the header + if (mState.startTimeStampFromFile) { - openVideoStartingTS = std::stoull(boostVideoTS); - mState.startTimeStamp = std::stoull(boostVideoTS); + mState.resolvedStartingTS = mState.startTimeStampFromFile; } - catch (std::invalid_argument) + else { - if (!mState.startTimeStamp) + auto boostVideoTS = boost::filesystem::path(mState.mVideoPath).stem().string(); + try { - throw AIPException(AIP_FATAL, "unexpected state - starting ts not found in video name or metadata"); + mState.resolvedStartingTS = std::stoull(boostVideoTS); + } + catch (std::invalid_argument) + { + auto msg = "unexpected: starting ts not found in video name or metadata"; + LOG_ERROR << msg; + throw Mp4Exception(MP4_MISSING_START_TS, msg); } - openVideoStartingTS = mState.startTimeStamp; } + // update metadata setMetadata(); - return true; + + // get the end_ts of the video and update the cache + uint64_t dummy_start_ts, duration; + try + { + mp4_demux_time_range(mState.demux, &dummy_start_ts, &duration); + } + catch (...) + { + auto msg = "Failed to get time range of the video."; + LOG_ERROR << msg; + throw Mp4Exception(MP4_TIME_RANGE_FETCH_FAILED, msg); + } + mState.endTS = mState.resolvedStartingTS + duration; + if (mProps.parseFS) + { + cof->updateCache(mState.mVideoPath, mState.resolvedStartingTS, mState.endTS); + } + + // if playback direction is reverse, move the pointer to the last frame of the video + if (!mState.direction) + { + try + { + mp4_set_reader_pos_lastframe(mState.demux, mState.videotrack, mState.direction); + } + catch (...) + { + auto msg = "Unexpected error while moving the read pointer to last frame of <" + mState.mVideoPath + ">"; + LOG_ERROR << msg; + throw Mp4Exception(MP4_SET_POINTER_END_FAILED, msg); + } + mState.mFrameCounterIdx = mState.mFramesInVideo - 1; + } + // reset flags + waitFlag = false; + sentEOSSignal = false; } - bool randomSeek(uint64_t& skipTS, uint64_t _seekEndTS) + bool randomSeekInternal(uint64_t& skipTS, bool forceReopen = false) { - /* Takes a timestamp and sets proper mVideoFile and mParsedFilesCount (in case new parse is required) and initNewVideo(). - * Also, seeks to correct frame in the mVideoFile. If seek within in the videoFile fails, moving to next available video is attempted. - * If all ways to seek fails, the read state is reset. - */ - seekEndTS = _seekEndTS; - + /* Seeking in custom file i.e. parseFS is disabled */ if (!mProps.parseFS) { int seekedToFrame = -1; uint64_t skipMsecsInFile = 0; - if (!mState.startTimeStamp) + if (!mState.startTimeStampFromFile) { LOG_ERROR << "Start timestamp is not saved in the file. Can't support seeking with timestamps."; return false; } - if (skipTS < mState.startTimeStamp) + if (skipTS < mState.startTimeStampFromFile) { LOG_INFO << "seek time outside range. Seeking to start of video."; skipMsecsInFile = 0; } else { - skipMsecsInFile = skipTS - mState.startTimeStamp; + skipMsecsInFile = skipTS - mState.startTimeStampFromFile; } - LOG_DEBUG << "Attempting seek <" << mState.mVideoPath << "> @skipMsecsInFile <" << skipMsecsInFile << ">"; - + LOG_INFO << "Attempting seek <" << mState.mVideoPath << "> @skipMsecsInFile <" << skipMsecsInFile << ">"; uint64_t time_offset_usec = skipMsecsInFile * 1000; - int returnCode = mp4Seek(mState.demux, time_offset_usec, mp4_seek_method::MP4_SEEK_METHOD_NEAREST_SYNC); - mState.mFrameCounter = seekedToFrame; + int returnCode = mp4Seek(mState.demux, time_offset_usec, mp4_seek_method::MP4_SEEK_METHOD_NEXT_SYNC, seekedToFrame); + mState.mFrameCounterIdx = seekedToFrame; if (returnCode == -ENFILE) { LOG_INFO << "Query time beyond the EOF. Resuming..."; + auto frame = frame_sp(new EoSFrame(EoSFrame::EoSFrameType::MP4_SEEK_EOS, skipTS)); + sendEOS(frame); + seekReachedEOF = true; return true; } if (returnCode < 0) { LOG_ERROR << "Seek failed. Unexpected error."; - return false; + auto msg = "Unexpected error happened whie seeking inside the video file."; + LOG_ERROR << msg; + throw Mp4Exception(MP4_SEEK_INSIDE_FILE_FAILED, msg); } + // continue reading file if seek is successful + mState.has_more_video = 1; + mState.end = false; // enable seeking after eof + // reset flags + waitFlag = false; + sentEOSSignal = false; return true; } - DemuxAndParserState tempState = mState; + /* Regular seek + 1. bool cof.getRandomSeekFile(skipTS, skipVideoFile, skipMsec) + 2. That method returns false if randomSeek has failed. Then, do nothing. + 3. On success, we know which file to open and how many secs to skip in it. + 4. Do openVideoFor() this file & seek in this file & set the mFrameCountIdx to the seekedToFrame ? + */ std::string skipVideoFile; uint64_t skipMsecsInFile; - int ret = mFSParser->randomSeek(skipTS, mProps.skipDir, skipVideoFile, skipMsecsInFile); - LOG_INFO << "Attempting seek <" << skipVideoFile << "> @skipMsecsInFile <" << skipMsecsInFile << ">"; - if (ret < 0) + bool ret = cof->getRandomSeekFile(skipTS, mState.direction, skipMsecsInFile, skipVideoFile); + if (!ret) { - LOG_ERROR << "Skip to ts <" << skipTS << "> failed. Please check skip dir <" << mProps.skipDir << ">"; + // send EOS signal + auto frame = frame_sp(new EoSFrame(EoSFrame::EoSFrameType::MP4_SEEK_EOS, skipTS)); + sendEOS(frame); + // skip the frame in the readNextFrame that happens in the same step + seekReachedEOF = true; + LOG_INFO << "Seek to skipTS <" << skipTS << "> failed. Resuming playback..."; return false; } - bool found = false; - for (auto i = 0; i < mState.mParsedVideoFiles.size(); ++i) + // check if the skipTS is in already opened file (if mState.end has not reached) + bool skipTSInOpenFile = false; + if (!mState.end) { - if (mState.mParsedVideoFiles[i] == skipVideoFile) - { - mState.mVideoCounter = i; - found = true; - break; - } + skipTSInOpenFile = cof->isTimeStampInFile(mState.mVideoPath, skipTS); } - if (!found) + // force reopen the video file if skipVideo is the last file in cache + auto lastVideoInCache = boost::filesystem::canonical(cof->getLastVideoInCache()); + bool skipFileIsLastInCache = boost::filesystem::equivalent(lastVideoInCache, boost::filesystem::canonical(skipVideoFile)); + if (!skipTSInOpenFile || skipFileIsLastInCache) { - // do fresh fs parse - mState.mVideoPath = skipVideoFile; - mState.mVideoCounter = mState.mParsedFilesCount; - mState.randomSeekParseFlag = true; + // open skipVideoFile if mState.end has reached or skipTS not in currently open video + openVideoSetPointer(skipVideoFile); // it is possible that this file has been deleted but not removed from cache } - initNewVideo(); + LOG_INFO << "Attempting seek <" << skipVideoFile << "> @skipMsecsInFile <" << skipMsecsInFile << ">"; if (skipMsecsInFile) { uint64_t time_offset_usec = skipMsecsInFile * 1000; - int returnCode = mp4Seek(mState.demux, time_offset_usec, mp4_seek_method::MP4_SEEK_METHOD_NEAREST_SYNC); - // to determine the end of video - mState.mFrameCounter = seekedToFrame; - + int seekedToFrame = 0; + mp4_seek_method seekDirectionStrategy = mState.direction ? mp4_seek_method::MP4_SEEK_METHOD_NEXT_SYNC : mp4_seek_method::MP4_SEEK_METHOD_PREVIOUS_SYNC; + int returnCode = mp4Seek(mState.demux, time_offset_usec, seekDirectionStrategy, seekedToFrame); if (returnCode < 0) { - LOG_ERROR << "Error while skipping to ts <" << skipTS << "> failed. File <" << skipVideoFile << "> @ time <" << skipMsecsInFile << "> ms errorCode <" << returnCode << ">"; - LOG_ERROR << "Trying to seek to next available file..."; - auto nextFlag = mFSParser->getNextToVideoFileFlag(); - if (nextFlag < 0) - { - // reset the state of mux to before seek op - LOG_ERROR << "No next file found <" << nextFlag << ">" << "Resetting the reader state to before seek op"; - mState = tempState; - LOG_ERROR << "Switching back to video <" << mState.mVideoPath << ">"; - // hot fix to avoid demux close attempt - mState.demux = nullptr; - mState.mVideoCounter -= 1; - initNewVideo(); - return false; - } - mState.mVideoPath = mFSParser->getNextVideoFile(); - mState.mVideoCounter = mState.mParsedFilesCount; - mState.randomSeekParseFlag = true; - initNewVideo(); + auto msg = "Unexpected error happened whie seeking inside the video file."; + LOG_ERROR << msg; + throw Mp4Exception(MP4_SEEK_INSIDE_FILE_FAILED, msg); } + mState.mFrameCounterIdx = seekedToFrame; + LOG_TRACE << "Time offset usec <" << time_offset_usec << ">, seekedToFrame <" << seekedToFrame << ">"; } + + // seek successful + mState.end = false; // enable seeking after eof + // reset sentEOFSignal + sentEOSSignal = false; + // reset waitFlag + waitFlag = false; + // prependSpsPps + mState.shouldPrependSpsPps = true; return true; } - void readNextFrame(uint8_t* sampleFrame, uint8_t* sampleMetadataFrame, size_t& imageFrameSize, size_t& metadataFrameSize) + uint64_t calcReloadFileAfter() { + std::chrono::time_point t = std::chrono::system_clock::now(); + auto dur = std::chrono::duration_cast(t.time_since_epoch()); + uint64_t val = dur.count(); + uint64_t reloadTSVal = val + mProps.reInitInterval * 1000; + LOG_INFO << "nowTS <" << val << "> reloadFileAfter <" << reloadTSVal << ">"; + return reloadTSVal; + } - // isVideoFileFound is false when there is no video file in the given dir and now we check if the video file has been created. + void termOpenVideo() + { + try + { + mp4_demux_close(mState.demux); + mState.demux = nullptr; + } + catch (...) + { + auto msg = "Error occured while closing the video file <" + mState.mVideoPath + ">"; + LOG_ERROR << msg; + throw Mp4Exception(MP4_FILE_CLOSE_FAILED, msg); + } + mState.videotrack = -1; + mState.metatrack = -1; + mState.mFrameCounterIdx = 0; + } + + bool randomSeek(uint64_t& skipTS, bool forceReopen = false) noexcept + { + try + { + randomSeekInternal(skipTS, forceReopen); + } + catch (Mp4_Exception& ex) + { + makeAndSendMp4Error(Mp4ErrorFrame::MP4_SEEK, ex.getCode(), ex.getError(), ex.getOpenFileErrorCode(), skipTS); + return false; + } + catch (...) + { + std::string msg = "unknown error while seeking"; + makeAndSendMp4Error(Mp4ErrorFrame::MP4_SEEK, MP4_UNEXPECTED_STATE, msg, 0, skipTS); + return false; + } + return true; + } + + void makeAndSendMp4Error(int errorType, int errorCode, std::string errorMsg, int openErrorCode, uint64_t _errorMp4TS) + { + LOG_ERROR << "makeAndSendMp4Error <" << errorType << "," << errorCode << "," << errorMsg << "," << openErrorCode << "," << _errorMp4TS << ">"; + frame_sp errorFrame = boost::shared_ptr(new Mp4ErrorFrame(errorType, errorCode, errorMsg, openErrorCode, _errorMp4TS)); + sendMp4ErrorFrame(errorFrame); + } + + bool isOpenVideoFinished() + { + if (mState.direction && (mState.mFrameCounterIdx >= mState.mFramesInVideo)) + { + return true; + } + if (!mState.direction && mState.mFrameCounterIdx <= -1) + { + return true; + } + return false; + } + + void readNextFrame(frame_sp& imgFrame, frame_sp& metadetaFrame, size_t& imgSize, size_t& metadataSize, uint64_t& frameTSInMsecs, int32_t& mp4FIndex) noexcept + { + try + { + readNextFrameInternal(imgFrame, metadetaFrame, imgSize, metadataSize, frameTSInMsecs, mp4FIndex); + } + catch (Mp4_Exception& ex) + { + imgSize = 0; + // send the last frame timestamp + makeAndSendMp4Error(Mp4ErrorFrame::MP4_STEP, ex.getCode(), ex.getError(), ex.getOpenFileErrorCode(), mState.frameTSInMsecs); + return; + } + catch (...) + { + imgSize = 0; + std::string msg = "uknown error in readNextFrame"; + makeAndSendMp4Error(Mp4ErrorFrame::MP4_STEP, MP4_UNEXPECTED_STATE, msg, 0, mState.frameTSInMsecs); + return; + } + } + + void readNextFrameInternal(frame_sp& imgFrame, frame_sp& metadetaFrame, size_t& imageFrameSize, size_t& metadataFrameSize, uint64_t& frameTSInMsecs, int32_t& mp4FIndex) + { if (!isVideoFileFound) { currentTS = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); if (currentTS >= recheckDiskTS) { - if (!initNewVideo()) + if (!cof->probe(boost::filesystem::path(mState.mVideoPath), mState.mVideoPath)); { - sampleFrame = nullptr; + imgFrame = nullptr; imageFrameSize = 0; recheckDiskTS = currentTS + mProps.parseFSTimeoutDuration; return; @@ -328,56 +753,147 @@ class Mp4readerDetailAbs } else { - sampleFrame = nullptr; + imgFrame = nullptr; imageFrameSize = 0; return; } } - // all frames of the open video are already read and end has not reached - if (mState.mFrameCounter == mState.mFramesInVideo && !mState.end) + if (waitFlag) + { + LOG_TRACE << "readNextFrame: waitFlag <" << waitFlag << ">"; + std::chrono::time_point t = std::chrono::system_clock::now(); + auto dur = std::chrono::duration_cast(t.time_since_epoch()); + uint64_t nowTS = dur.count(); + LOG_INFO << "readNextFrameInternal: nowTS <" << nowTS << "> reloadFileAfter <" << reloadFileAfter << ">"; + if (reloadFileAfter > nowTS) + { + LOG_TRACE << "waiting...."; + return; + } + else // no new data on reload (wait state continues) so, re-calc a new reloadFileAfter + { + reloadFileAfter = calcReloadFileAfter(); + LOG_INFO << "New reloadFileAfter <" << reloadFileAfter << "> WaitFlag <" << waitFlag << ">"; + } + } + + // video is finished + if (isOpenVideoFinished()) { - sendEndOfStream(); - // if parseFS is unset, it is the end - LOG_ERROR << "frames number" << mState.mFrameCounter; mState.end = !mProps.parseFS; - initNewVideo(); + initNewVideo(); // new video is init or mState.end is reached. + } + else + { + // if video is not finished, end and wait states are not possible. + // This can happen due to direction change after EOF. + mState.end = false; + waitFlag = false; + sentEOSSignal = false; } if (mState.end) // no files left to be parsed { - sampleFrame = nullptr; - imageFrameSize = 0; + if (mProps.reInitInterval && !waitFlag) // ONLY the first time after EOR + { + reloadFileAfter = calcReloadFileAfter(); + waitFlag = true; // will be reset by openVideoSetPointer or randomSeek or setPlayback + LOG_TRACE << "EOR reached in readNextFrame: waitFlag <" << waitFlag << ">"; + LOG_INFO << "Reload File After reloadFileAfter <" << reloadFileAfter << ">"; + //return nullptr; + } + if (!sentEOSSignal) + { + + auto frame = frame_sp(new EoSFrame(EoSFrame::EoSFrameType::MP4_PLYB_EOS, mState.frameTSInMsecs)); + sendEOS(frame); // just send once + sentEOSSignal = true; + } + return; + } + + // skip sending one frame in same step when seek reaches EOS + if (seekReachedEOF) + { + seekReachedEOF = false; return; } if (mState.has_more_video) { - ret = mp4_demux_get_track_sample(mState.demux, - mState.video.id, - 1, - sampleFrame, - mProps.biggerFrameSize, - sampleMetadataFrame, - mProps.biggerMetadataFrameSize, - &mState.sample); - imageFrameSize = imageFrameSize + mState.sample.size; - metadataFrameSize = mState.sample.metadata_size; + uint8_t* sampleFrame = static_cast(imgFrame->data()); + uint8_t* sampleMetadataFrame = static_cast(metadetaFrame->data()); + + uint32_t imageSize = mProps.biggerFrameSize; + uint32_t metadataSize = mProps.biggerMetadataFrameSize; + if (mState.direction) + { + ret = mp4_demux_get_track_sample(mState.demux, + mState.video.id, + 1, + sampleFrame, + imageSize, + sampleMetadataFrame, + metadataSize, + &mState.sample); + mp4FIndex = mState.mFrameCounterIdx++; + } + else + { + ret = mp4_demux_get_track_sample_rev(mState.demux, + mState.video.id, + 1, + sampleFrame, + imageSize, + sampleMetadataFrame, + metadataSize, + &mState.sample); + mp4FIndex = mState.mFrameCounterIdx--; + } + /* To get only info about the frames ret = mp4_demux_get_track_sample( demux, id, 1, NULL, 0, NULL, 0, &sample); */ + /* check the buffer size props */ + if (mState.sample.size > mProps.biggerFrameSize) + { + std::string msg = "Buffer size too small. Please check maxImgFrameSize property. maxImgFrameSize <" + std::to_string(mProps.biggerFrameSize) + "> frame size <" + std::to_string(mState.sample.size) + ">"; + throw Mp4Exception(MP4_BUFFER_TOO_SMALL, msg); + } + if (mState.sample.metadata_size > mProps.biggerMetadataFrameSize) + { + std::string msg = "Buffer size too small. Please check maxMetadataFrameSize property. maxMetadataFrameSize <" + std::to_string(mProps.biggerMetadataFrameSize) + "> frame size <" + std::to_string(mState.sample.metadata_size) + ">"; + throw Mp4Exception(MP4_BUFFER_TOO_SMALL, msg); + } + if (ret != 0 || mState.sample.size == 0) { LOG_INFO << "<" << ret << "," << mState.sample.size << "," << mState.sample.metadata_size << ">"; mState.has_more_video = 0; - sampleFrame = nullptr; - imageFrameSize = 0; + if (!mProps.parseFS) + { + if (!sentEOSSignal) + { + auto frame = frame_sp(new EoSFrame(EoSFrame::EoSFrameType::MP4_PLYB_EOS, mState.frameTSInMsecs)); + sendEOS(frame); // just send once + sentEOSSignal = true; + } + } return; } - ++mState.mFrameCounter; + + // get the frame timestamp + uint64_t sample_ts_usec = mp4_sample_time_to_usec(mState.sample.dts, mState.video.timescale); + frameTSInMsecs = mState.resolvedStartingTS + (sample_ts_usec / 1000); + mState.frameTSInMsecs = frameTSInMsecs; + LOG_TRACE << "readNextFrame frameTS <" << frameTSInMsecs << ">"; + imageFrameSize += static_cast(mState.sample.size); + metadataFrameSize = static_cast(mState.sample.metadata_size); + // for metadata to be ignored - we will have metadata_buffer = nullptr and size = 0 + return; } - return; } Mp4ReaderSourceProps mProps; @@ -385,6 +901,30 @@ class Mp4readerDetailAbs struct DemuxAndParserState { + DemuxAndParserState() + { + resetState(); + } + + void resetState() + { + demux = nullptr; + videotrack = -1; + metatrack = -1; + ntracks = -1; + startTimeStampFromFile = 0; + resolvedStartingTS = 0; + endTS = 0; + frameTSInMsecs = 0; + mFrameCounterIdx = 0; + mFramesInVideo = 0; + mVideoPath = ""; + mSerFormatVersion = ""; + speed = 1; + direction = true; + end = false; + } + mp4_demux* demux = nullptr; mp4_track_info info, video; mp4_track_sample sample; @@ -393,79 +933,94 @@ class Mp4readerDetailAbs int videotrack = -1; int metatrack = -1; int ntracks = -1; + uint64_t endTS; uint64_t startTimeStamp = 0; uint32_t mParsedFilesCount = 0; uint32_t mVideoCounter = 0; uint32_t mFrameCounter = 0; - uint32_t mFramesInVideo = 0; + int32_t mFramesInVideo = 0; + uint64_t resolvedStartingTS; + uint64_t frameTSInMsecs; std::vector mParsedVideoFiles; + uint64_t startTimeStampFromFile; std::string mVideoPath = ""; - bool randomSeekParseFlag = false; + int32_t mFrameCounterIdx; + bool shouldPrependSpsPps = false; bool end = false; Mp4ReaderSourceProps props; + float speed; + bool direction; + //bool end; } mState; uint64_t openVideoStartingTS = 0; - uint64_t seekEndTS = 9999999999999; + uint64_t reloadFileAfter = 0; int seekedToFrame = -1; bool isVideoFileFound = true; uint64_t currentTS = 0; + bool sentEOSSignal = false; + bool seekReachedEOF = false; + bool waitFlag = false; uint64_t recheckDiskTS = 0; + boost::shared_ptr cof; + framemetadata_sp updatedEncodedImgMetadata; /* mState.end = true is possible only in two cases: - if parseFS found no more relevant files on the disk - parseFS is disabled and intial video has finished playing */ public: - int width = 0; - int height = 0; + int mWidth = 0; + int mHeight = 0; int ret; + double mFPS = 0; + double mDurationInSecs = 0; std::function makeFrame; - std::function sendEOS; - std::function makeframe; - boost::shared_ptr mFSParser; + std::function sendEOS; + std::function makeFrameTrim; + std::function sendMp4ErrorFrame; + std::function mSetMetadata; std::string h264ImagePinId; std::string encodedImagePinId; - std::string mp4FramePinId; + std::string metadataFramePinId; }; -class Mp4readerDetailJpeg : public Mp4readerDetailAbs +class Mp4ReaderDetailJpeg : public Mp4ReaderDetailAbs { public: - Mp4readerDetailJpeg(Mp4ReaderSourceProps& props, std::function _makeFrame, - std::function _makeframe, std::function _sendEOS) : Mp4readerDetailAbs(props, _makeFrame, _makeframe, _sendEOS) + Mp4ReaderDetailJpeg(Mp4ReaderSourceProps& props, std::function _makeFrame, + std::function _makeFrameTrim, std::function _sendEOS, std::function _setMetadata, std::function _sendMp4ErrorFrame) : Mp4ReaderDetailAbs(props, _makeFrame, _makeFrameTrim, _sendEOS, _setMetadata, _sendMp4ErrorFrame) {} - ~Mp4readerDetailJpeg() { mp4_demux_close(mState.demux); } + ~Mp4ReaderDetailJpeg() {} void setMetadata(); bool produceFrames(frame_container& frames); - void prependSpsPps(boost::asio::mutable_buffer& iFrameBuffer) {} void sendEndOfStream() {} - int mp4Seek(mp4_demux* demux, uint64_t time_offset_usec, mp4_seek_method syncType); + int mp4Seek(mp4_demux* demux, uint64_t time_offset_usec, mp4_seek_method syncType, int& seekedToFrame); }; -class Mp4readerDetailH264 : public Mp4readerDetailAbs +class Mp4ReaderDetailH264 : public Mp4ReaderDetailAbs { public: - Mp4readerDetailH264(Mp4ReaderSourceProps& props, std::function _makeFrame, - std::function _makeframe, std::function _sendEOS) : Mp4readerDetailAbs(props, _makeFrame, _makeframe, _sendEOS) + Mp4ReaderDetailH264(Mp4ReaderSourceProps& props, std::function _makeFrame, + std::function _makeFrameTrim, std::function _sendEOS, std::function _setMetadata, std::function _sendMp4ErrorFrame) : Mp4ReaderDetailAbs(props, _makeFrame, _makeFrameTrim, _sendEOS, _setMetadata, _sendMp4ErrorFrame) {} - ~Mp4readerDetailH264() { mp4_demux_close(mState.demux); } + ~Mp4ReaderDetailH264() {} void setMetadata(); + void readSPSPPS(); bool produceFrames(frame_container& frames); - void prependSpsPps(boost::asio::mutable_buffer& iFrameBuffer); + void prependSpsPps(uint8_t* iFrameBuffer); void sendEndOfStream(); - int mp4Seek(mp4_demux* demux, uint64_t time_offset_usec, mp4_seek_method syncType); + int mp4Seek(mp4_demux* demux, uint64_t time_offset_usec, mp4_seek_method syncType, int& seekedToFrame); private: uint8_t* sps = nullptr; uint8_t* pps = nullptr; size_t spsSize = 0; size_t ppsSize = 0; bool seekedToEndTS = false; - bool isRandomSeek = true; }; -void Mp4readerDetailJpeg::setMetadata() +void Mp4ReaderDetailJpeg::setMetadata() { - auto metadata = framemetadata_sp(new EncodedImageMetadata(width, height)); + auto metadata = framemetadata_sp(new EncodedImageMetadata(mWidth, mHeight)); if (!metadata->isSet()) { return; @@ -473,60 +1028,90 @@ void Mp4readerDetailJpeg::setMetadata() auto encodedMetadata = FrameMetadataFactory::downcast(metadata); encodedMetadata->setData(*encodedMetadata); - auto mp4FrameMetadata = framemetadata_sp(new Mp4VideoMetadata("v_1")); - //// set proto version in mp4videometadata + auto mp4FrameMetadata = framemetadata_sp(new Mp4VideoMetadata("v_1_0")); + // set proto version in mp4videometadata auto serFormatVersion = getSerFormatVersion(); auto mp4VideoMetadata = FrameMetadataFactory::downcast(mp4FrameMetadata); mp4VideoMetadata->setData(serFormatVersion); - return; + Mp4ReaderDetailAbs::setMetadata(); + // set at Module level + mSetMetadata(encodedImagePinId, metadata); } -int Mp4readerDetailJpeg::mp4Seek(mp4_demux* demux, uint64_t time_offset_usec, mp4_seek_method syncType) +int Mp4ReaderDetailJpeg::mp4Seek(mp4_demux* demux, uint64_t time_offset_usec, mp4_seek_method syncType, int& seekedToFrame) { auto ret = mp4_demux_seek_jpeg(demux, time_offset_usec, syncType, &seekedToFrame); return ret; } -bool Mp4readerDetailJpeg::produceFrames(frame_container& frames) +bool Mp4ReaderDetailJpeg::produceFrames(frame_container& frames) { frame_sp imgFrame = makeFrame(mProps.biggerFrameSize, encodedImagePinId); - frame_sp metadataFrame = makeFrame(mProps.biggerMetadataFrameSize, mp4FramePinId); - uint8_t* sampleFrame = reinterpret_cast(imgFrame->data()); - uint8_t* sampleMetadataFrame = reinterpret_cast(metadataFrame->data()); - size_t imageActualSize = 0; - size_t metadataActualSize = 0; - readNextFrame(sampleFrame, sampleMetadataFrame, imageActualSize, metadataActualSize); + frame_sp metadataFrame = makeFrame(mProps.biggerMetadataFrameSize, metadataFramePinId); + size_t imgSize = 0; + size_t metadataSize = 0; + int32_t mp4FIndex = 0; + uint64_t frameTSInMsecs; - if (!imageActualSize || !sampleFrame) + try { - return true; + readNextFrame(imgFrame, metadataFrame, imgSize, metadataSize, frameTSInMsecs, mp4FIndex); + } + catch (const std::exception& e) + { + LOG_ERROR << e.what(); + attemptFileClose(); } - - auto trimmedImgFrame = makeframe(imgFrame, imageActualSize, encodedImagePinId); - uint64_t sample_ts_usec = mp4_sample_time_to_usec(mState.sample.dts, mState.video.timescale); - auto frameTSInMsecs = openVideoStartingTS + (sample_ts_usec / 1000); + if (!imgSize) + { + return true; + } + auto trimmedImgFrame = makeFrameTrim(imgFrame, imgSize, encodedImagePinId); trimmedImgFrame->timestamp = frameTSInMsecs; + trimmedImgFrame->fIndex = mp4FIndex; - if (seekEndTS <= frameTSInMsecs) + // give recorded timestamps + if (!mProps.giveLiveTS) { - return true; + /* recordedTS mode */ + trimmedImgFrame->timestamp = frameTSInMsecs; + } + else + { + /* getLiveTS mode */ + // get local epoch timestamp in milliseconds + std::chrono::time_point t = std::chrono::system_clock::now(); + auto dur = std::chrono::duration_cast(t.time_since_epoch()); + auto nowTS = dur.count(); + trimmedImgFrame->timestamp = nowTS; } frames.insert(make_pair(encodedImagePinId, trimmedImgFrame)); - if (metadataActualSize) + if (metadataSize) { - auto metadataSizeFrame = makeframe(metadataFrame, metadataActualSize, mp4FramePinId); - metadataSizeFrame->timestamp = frameTSInMsecs; - frames.insert(make_pair(mp4FramePinId, metadataSizeFrame)); + auto trimmedMetadataFrame = makeFrameTrim(metadataFrame, metadataSize, metadataFramePinId); + trimmedMetadataFrame->timestamp = frameTSInMsecs; + trimmedMetadataFrame->fIndex = mp4FIndex; + if (!mProps.giveLiveTS) + { + /* recordedTS mode */ + trimmedMetadataFrame->timestamp = frameTSInMsecs; + } + else + { + trimmedMetadataFrame->timestamp = trimmedImgFrame->timestamp; + } + + frames.insert(make_pair(metadataFramePinId, trimmedMetadataFrame)); } return true; } -void Mp4readerDetailH264::setMetadata() +void Mp4ReaderDetailH264::setMetadata() { - auto metadata = framemetadata_sp(new H264Metadata(width, height)); + auto metadata = framemetadata_sp(new H264Metadata(mWidth, mHeight)); if (!metadata->isSet()) { return; @@ -534,12 +1119,14 @@ void Mp4readerDetailH264::setMetadata() auto h264Metadata = FrameMetadataFactory::downcast(metadata); h264Metadata->setData(*h264Metadata); - auto mp4FrameMetadata = framemetadata_sp(new Mp4VideoMetadata("v_1")); - //// set proto version in mp4videometadata - auto serFormatVersion = getSerFormatVersion(); - auto mp4VideoMetadata = FrameMetadataFactory::downcast(mp4FrameMetadata); - mp4VideoMetadata->setData(serFormatVersion); + readSPSPPS(); + Mp4ReaderDetailAbs::setMetadata(); + mSetMetadata(h264ImagePinId, metadata); + return; +} +void Mp4ReaderDetailH264::readSPSPPS() +{ struct mp4_video_decoder_config* vdc = (mp4_video_decoder_config*)malloc( sizeof(mp4_video_decoder_config)); @@ -550,21 +1137,26 @@ void Mp4readerDetailH264::setMetadata() pps = vdc->avc.pps; spsSize = vdc->avc.sps_size; ppsSize = vdc->avc.pps_size; - return; } -int Mp4readerDetailH264::mp4Seek(mp4_demux* demux, uint64_t time_offset_usec, mp4_seek_method syncType) +int Mp4ReaderDetailH264::mp4Seek(mp4_demux* demux, uint64_t time_offset_usec, mp4_seek_method syncType, int& seekedToFrame) { auto ret = mp4_demux_seek(demux, time_offset_usec, syncType, &seekedToFrame); + if (ret == -2) + { + seekedToFrame = mState.mFramesInVideo; + ret = 0; + } return ret; } -void Mp4readerDetailH264::sendEndOfStream() +void Mp4ReaderDetailH264::sendEndOfStream() { - sendEOS(); + auto frame = frame_sp(new EoSFrame(EoSFrame::EoSFrameType::MP4_SEEK_EOS, 0)); + sendEOS(frame); } -void Mp4readerDetailH264::prependSpsPps(boost::asio::mutable_buffer& iFrameBuffer) +void Mp4ReaderDetailH264::prependSpsPps(uint8_t* iFrameBuffer) { //1a write sps on tmpBuffer.data() //1b tmpBuffer+=sizeof_sps @@ -575,77 +1167,109 @@ void Mp4readerDetailH264::prependSpsPps(boost::asio::mutable_buffer& iFrameBuffe // Now pass tmpBuffer.data() and tmpBuffer.size() to libmp4 char NaluSeprator[4] = { 00 ,00, 00 ,01 }; auto nalu = reinterpret_cast(NaluSeprator); - memcpy(iFrameBuffer.data(), nalu, 4); + memcpy(iFrameBuffer, nalu, 4); iFrameBuffer += 4; - memcpy(iFrameBuffer.data(), sps, spsSize); + memcpy(iFrameBuffer, sps, spsSize); iFrameBuffer += spsSize; - memcpy(iFrameBuffer.data(), nalu, 4); + memcpy(iFrameBuffer, nalu, 4); iFrameBuffer += 4; - memcpy(iFrameBuffer.data(), pps, ppsSize); + memcpy(iFrameBuffer, pps, ppsSize); iFrameBuffer += ppsSize; } -bool Mp4readerDetailH264::produceFrames(frame_container& frames) +bool Mp4ReaderDetailH264::produceFrames(frame_container& frames) { frame_sp imgFrame = makeFrame(mProps.biggerFrameSize, h264ImagePinId); - boost::asio::mutable_buffer tmpBuffer(imgFrame->data(), imgFrame->size()); - size_t imageActualSize = 0; + size_t imgSize = 0; + frame_sp metadataFrame = makeFrame(mProps.biggerMetadataFrameSize, metadataFramePinId); + size_t metadataSize = 0; + uint64_t frameTSInMsecs; + int32_t mp4FIndex = 0; - if (mState.randomSeekParseFlag && isRandomSeek) + try + { + readNextFrame(imgFrame, metadataFrame, imgSize, metadataSize, frameTSInMsecs, mp4FIndex); + } + catch (const std::exception& e) { - prependSpsPps(tmpBuffer); - imageActualSize = spsSize + ppsSize + 8; - isRandomSeek = false; + LOG_ERROR << e.what(); + attemptFileClose(); } - frame_sp metadataFrame = makeFrame(mProps.biggerMetadataFrameSize,mp4FramePinId); - uint8_t* sampleFrame = reinterpret_cast(tmpBuffer.data()); - uint8_t* sampleMetadataFrame = reinterpret_cast(metadataFrame->data()); - size_t metadataActualSize = 0; - readNextFrame(sampleFrame, sampleMetadataFrame, imageActualSize, metadataActualSize); - if (!imageActualSize || !sampleFrame) + if (!imgSize) { return true; } - auto trimmedImgFrame = makeframe(imgFrame, imageActualSize, h264ImagePinId); - auto tempBuffer = const_buffer(trimmedImgFrame->data(), trimmedImgFrame->size()); - auto ret = H264Utils::parseNalu(tempBuffer); - short typeFound; - const_buffer spsBuff, ppsBuff; - tie(typeFound, spsBuff, ppsBuff) = ret; - - uint64_t sample_ts_usec = mp4_sample_time_to_usec(mState.sample.dts, mState.video.timescale); - auto frameTSInMsecs = openVideoStartingTS + (sample_ts_usec / 1000); + if (mState.shouldPrependSpsPps) + { + boost::asio::mutable_buffer tmpBuffer(imgFrame->data(), imgFrame->size()); + auto type = H264Utils::getNALUType((char*)tmpBuffer.data()); + if (type != H264Utils::H264_NAL_TYPE_END_OF_SEQ) + { + auto tempFrame = makeFrame(imgSize + spsSize + ppsSize + 8, h264ImagePinId); + uint8_t* tempFrameBuffer = reinterpret_cast(tempFrame->data()); + prependSpsPps(tempFrameBuffer); + tempFrameBuffer += spsSize + ppsSize + 8; + memcpy(tempFrameBuffer, imgFrame->data(), imgSize); + imgSize += spsSize + ppsSize + 8; + imgFrame = tempFrame; + } + mState.shouldPrependSpsPps = false; + } - trimmedImgFrame->timestamp = frameTSInMsecs; + auto trimmedImgFrame = makeFrameTrim(imgFrame, imgSize, h264ImagePinId); - if (seekedToEndTS) + uint8_t* frameData = reinterpret_cast(trimmedImgFrame->data()); + short nalType = H264Utils::getNALUType((char*)trimmedImgFrame->data()); + if (nalType == H264Utils::H264_NAL_TYPE_SEQ_PARAM) { - return true; + frameData[3] = 0x1; + frameData[spsSize + 7] = 0x1; + frameData[spsSize + ppsSize + 10] = 0x0; + frameData[spsSize + ppsSize + 11] = 0x1; } - - if (seekEndTS <= frameTSInMsecs && !mProps.bFramesEnabled) + else { - return true; + frameData[2] = 0x0; + frameData[3] = 0x1; } - if (seekEndTS <= frameTSInMsecs && mProps.bFramesEnabled) + trimmedImgFrame->timestamp = frameTSInMsecs; + trimmedImgFrame->fIndex = mp4FIndex; + + // give recorded timestamps + if (!mProps.giveLiveTS) { - if (typeFound == H264Utils::H264_NAL_TYPE::H264_NAL_TYPE_IDR_SLICE) - { - frames.insert(make_pair(h264ImagePinId, trimmedImgFrame)); - seekedToEndTS = true; - return true; - } + /* recordedTS mode */ + trimmedImgFrame->timestamp = frameTSInMsecs; + } + else + { + /* getLiveTS mode */ + // get local epoch timestamp in milliseconds + std::chrono::time_point t = std::chrono::system_clock::now(); + auto dur = std::chrono::duration_cast(t.time_since_epoch()); + auto nowTS = dur.count(); + trimmedImgFrame->timestamp = nowTS; } frames.insert(make_pair(h264ImagePinId, trimmedImgFrame)); - if (metadataActualSize) + if (metadataSize) { - auto metadataSizeFrame = makeframe(metadataFrame, metadataActualSize, mp4FramePinId); - metadataSizeFrame->timestamp = frameTSInMsecs; - frames.insert(make_pair(mp4FramePinId, metadataSizeFrame)); + auto trimmedMetadataFrame = makeFrameTrim(metadataFrame, metadataSize, metadataFramePinId); + trimmedMetadataFrame->timestamp = frameTSInMsecs; + trimmedMetadataFrame->fIndex = mp4FIndex; + if (!mProps.giveLiveTS) + { + /* recordedTS mode */ + trimmedMetadataFrame->timestamp = frameTSInMsecs; + } + else + { + trimmedMetadataFrame->timestamp = trimmedImgFrame->timestamp; + } + frames.insert(make_pair(metadataFramePinId, trimmedMetadataFrame)); } return true; } @@ -663,42 +1287,103 @@ bool Mp4ReaderSource::init() { return false; } - auto inputPinIdMetadataMap = getFirstOutputMetadata(); - auto mFrameType = inputPinIdMetadataMap->getFrameType(); + auto outMetadata = getFirstOutputMetadata(); + auto mFrameType = outMetadata->getFrameType(); if (mFrameType == FrameMetadata::FrameType::ENCODED_IMAGE) { - mDetail.reset(new Mp4readerDetailJpeg( + mDetail.reset(new Mp4ReaderDetailJpeg( props, [&](size_t size, string& pinId) { return makeFrame(size, pinId); }, [&](frame_sp& frame, size_t& size, string& pinId) { return makeFrame(frame, size, pinId); }, - [&](void) - {return sendEOS(); })); + [&](frame_sp frame) + {return Module::sendEOS(frame); }, + [&](std::string& pinId, framemetadata_sp& metadata) + { return setImageMetadata(pinId, metadata); }, + [&](frame_sp& frame) {return Module::sendMp4ErrorFrame(frame); })); } else if (mFrameType == FrameMetadata::FrameType::H264_DATA) { - mDetail.reset(new Mp4readerDetailH264(props, + mDetail.reset(new Mp4ReaderDetailH264(props, [&](size_t size, string& pinId) { return makeFrame(size, pinId); }, [&](frame_sp& frame, size_t& size, string& pinId) { return makeFrame(frame, size, pinId); }, - [&](void) - {return sendEOS(); })); + [&](frame_sp frame) + {return Module::sendEOS(frame); }, + [&](std::string& pinId, framemetadata_sp& metadata) + { return setImageMetadata(pinId, metadata); }, + [&](frame_sp& frame) {return Module::sendMp4ErrorFrame(frame); })); } - mDetail->encodedImagePinId = encodedImagePinId; mDetail->h264ImagePinId = h264ImagePinId; - mDetail->mp4FramePinId = mp4FramePinId; + mDetail->metadataFramePinId = metadataFramePinId; + return mDetail->Init(); +} - mDetail->Init(); - return true; +void Mp4ReaderSource::setImageMetadata(std::string& pinId, framemetadata_sp& metadata) +{ + Module::setMetadata(pinId, metadata); + mWidth = mDetail->mWidth; + mHeight = mDetail->mHeight; +} + +std::map> Mp4ReaderSource::getCacheSnapShot() +{ + return mDetail->getSnapShot(); +} + +bool Mp4ReaderSource::refreshCache() +{ + return mDetail->refreshCache(); +} + +std::string Mp4ReaderSource::getOpenVideoPath() +{ + if (mDetail) + { + return mDetail->getOpenVideoPath(); + } + return ""; +} + +int32_t Mp4ReaderSource::getOpenVideoFrameCount() +{ + if (mDetail) + { + return mDetail->getOpenVideoFrameCount(); + } + return -1; +} + +double Mp4ReaderSource::getOpenVideoFPS() +{ + if (mDetail) + { + return mDetail->mFPS; + } + return -1; +} + +double Mp4ReaderSource::getOpenVideoDurationInSecs() +{ + if (mDetail) + { + return mDetail->mDurationInSecs; + } + return -1; +} + +bool Mp4ReaderSource::getVideoRangeFromCache(std::string videoFile, uint64_t& start_ts, uint64_t& end_ts) +{ + return mDetail->getVideoRangeFromCache(videoFile, start_ts, end_ts); } bool Mp4ReaderSource::term() { auto moduleRet = Module::term(); - + mDetail->attemptFileClose(); return moduleRet; } @@ -718,8 +1403,8 @@ std::string Mp4ReaderSource::addOutPutPin(framemetadata_sp& metadata) } else { - mp4FramePinId = Module::addOutputPin(metadata); - return mp4FramePinId; + metadataFramePinId = Module::addOutputPin(metadata); + return metadataFramePinId; } } @@ -765,10 +1450,10 @@ Mp4ReaderSourceProps Mp4ReaderSource::getProps() bool Mp4ReaderSource::handlePropsChange(frame_sp& frame) { - Mp4ReaderSourceProps props(mDetail->mProps.videoPath, mDetail->mProps.parseFS, mDetail->mProps.biggerFrameSize, mDetail->mProps.biggerMetadataFrameSize); + bool direction = getPlayDirection(); + Mp4ReaderSourceProps props(mDetail->mProps.videoPath, mDetail->mProps.parseFS, mDetail->mProps.reInitInterval, direction, mDetail->mProps.readLoop, mDetail->mProps.giveLiveTS, mDetail->mProps.parseFSTimeoutDuration, mDetail->mProps.bFramesEnabled); bool ret = Module::handlePropsChange(frame, props); mDetail->setProps(props); - mDetail->Init(); return ret; } @@ -777,13 +1462,19 @@ void Mp4ReaderSource::setProps(Mp4ReaderSourceProps& props) Module::addPropsToQueue(props); } +bool Mp4ReaderSource::changePlayback(float speed, bool direction) +{ + PlayPauseCommand ppc(speed, direction); + return queuePlayPauseCommand(ppc); +} + bool Mp4ReaderSource::handleCommand(Command::CommandType type, frame_sp& frame) { if (type == Command::CommandType::Seek) { Mp4SeekCommand seekCmd; getCommand(seekCmd, frame); - return mDetail->randomSeek(seekCmd.seekStartTS,seekCmd.seekEndTS); + return mDetail->randomSeek(seekCmd.seekStartTS, seekCmd.forceReopen); } else { @@ -791,8 +1482,14 @@ bool Mp4ReaderSource::handleCommand(Command::CommandType type, frame_sp& frame) } } -bool Mp4ReaderSource::randomSeek(uint64_t seekStartTS, uint64_t seekEndTS) +bool Mp4ReaderSource::handlePausePlay(float speed, bool direction) +{ + mDetail->setPlayback(speed, direction); + return Module::handlePausePlay(speed, direction); +} + +bool Mp4ReaderSource::randomSeek(uint64_t skipTS, bool forceReopen) { - Mp4SeekCommand cmd(seekStartTS, seekEndTS); + Mp4SeekCommand cmd(skipTS, forceReopen); return queueCommand(cmd); } diff --git a/base/src/Mp4ReaderSourceUtils.cpp b/base/src/Mp4ReaderSourceUtils.cpp deleted file mode 100644 index 96504bb80..000000000 --- a/base/src/Mp4ReaderSourceUtils.cpp +++ /dev/null @@ -1,458 +0,0 @@ -#include "Mp4ReaderSourceUtils.h" -#include "Logger.h" - -void FileStructureParser::boostPathToStrUtil(std::vector& boost_paths, std::vector& paths) -{ - for (auto i = 0; i < boost_paths.size(); ++i) - { - paths.push_back(boost_paths[i].string()); - } -} - -bool FileStructureParser::setParseLimit(uint32_t _nParseFiles) -{ - /* Note - this limit is checked after min one yyyymmdd folder is parsed */ - nParseFiles = _nParseFiles; - return true; -} - -std::string FileStructureParser::format_2(int& num) -{ - if (num < 10) - { - return "0" + std::to_string(num); - } - else - { - return std::to_string(num); - } -} - -std::string FileStructureParser::format_hrs(int& hr) -{ - if (hr < 10) - { - return "000" + std::to_string(hr); - } - else - { - return "00" + std::to_string(hr); - } -} - -FileStructureParser::FileStructureParser() -{ -} - -void FileStructureParser::parseFilesInDirectory(boost::filesystem::path& folderPath, std::vector& files) -{ - for (auto&& itr : boost::filesystem::directory_iterator(folderPath)) - { - auto dirPath = itr.path(); - if (boost::filesystem::is_regular_file(dirPath) && boost::filesystem::extension(dirPath) == ".mp4") - { - files.push_back(dirPath); - } - } -} - -void FileStructureParser::parseFilesInDirectories(std::vector& dirs, std::vector& files) -{ - /* updates the files with the files found in dirs - non-recursive */ - for (auto itr = dirs.begin(); itr != dirs.end(); ++itr) - { - parseFilesInDirectory(*itr, files); - } - -} - -void FileStructureParser::parseDirectoriesInDirectory(boost::filesystem::path& folderPath, std::vector& hourDirectories) -{ - for (auto&& itr : boost::filesystem::directory_iterator(folderPath)) - { - auto dirPath = itr.path(); - if (boost::filesystem::is_directory(dirPath)) - { - hourDirectories.push_back(dirPath); - } - } -} - -void FileStructureParser::filterRelevantPaths(std::vector& allPaths, boost::filesystem::path& startingPath, std::vector& revelantPaths) -{ - /* - assumes allPaths is sorted - sets relevantPaths = paths after the startingPath in allPaths - */ - bool relevant = false; - for (int i = 0; i < allPaths.size(); ++i) - { - if (allPaths[i] == startingPath) - { - relevant = true; - continue; - } - if (relevant) - { - revelantPaths.push_back(allPaths[i]); - } - } -} - -void FileStructureParser::parseDayDirectories(std::vector& relevantDayDirectories, std::vector& relevantVideoFiles) -{ - /* - Parses the all day/hour dirs for given arg - 2 level deep only - Parses files hour by hour till the nParseFiles limit is reached. - */ - for (auto i = 0; i < relevantDayDirectories.size(); ++i) - { - for (auto&& hrDiritr : boost::filesystem::directory_iterator(relevantDayDirectories[i])) - { - auto hrDirPath = hrDiritr.path(); - if (boost::filesystem::is_directory(hrDirPath)) // hr directory - { - parseFilesInDirectory(hrDirPath, relevantVideoFiles); - if (relevantVideoFiles.size() >= 24 * 60) - { - // save the last parsed file - latestParsedVideoPath = relevantVideoFiles[relevantVideoFiles.size() - 1]; - return; - } - } - } - } -} - -bool FileStructureParser::parse(std::string& startingVideoFile, std::vector& parsedVideoFiles, bool includeStarting) -{ - auto startingVideoPath = boost::filesystem::path(startingVideoFile); - auto startingHourDirPath = startingVideoPath.parent_path(); - auto startingDayDirPath = startingHourDirPath.parent_path(); - auto rootDirPath = startingDayDirPath.parent_path(); - - // return if file does not exist - if (!boost::filesystem::exists(startingVideoPath)) - { - LOG_ERROR << "file does not exist"; - return false; - } - - // parse the relevant mp4 files from dir of the startingVideoPath file - std::vector videoFiles; - std::vector relevantVideoFiles; - if (includeStarting) - { - relevantVideoFiles.push_back(startingVideoPath); - } - parseFilesInDirectory(startingHourDirPath, videoFiles); - filterRelevantPaths(videoFiles, startingVideoPath, relevantVideoFiles); - - // parse the relevant hour directory names of this day - std::vector hourDirectories; - std::vector relevantHourDirectories; - parseDirectoriesInDirectory(startingDayDirPath, hourDirectories); - filterRelevantPaths(hourDirectories, startingHourDirPath, relevantHourDirectories); - parseFilesInDirectories(relevantHourDirectories, relevantVideoFiles); - - if (relevantVideoFiles.size() >= nParseFiles) - { - LOG_DEBUG << "number of video files <" << relevantVideoFiles.size() << ">"; - boostPathToStrUtil(relevantVideoFiles, parsedVideoFiles); - return true; - } - - // parse the relevant day directory names - std::vector dayDirectories; - std::vector relevantDayDirectories; - parseDirectoriesInDirectory(rootDirPath, dayDirectories); - filterRelevantPaths(dayDirectories, startingDayDirPath, relevantDayDirectories); - - // parse all the relevant day directories for files - parseDayDirectories(relevantDayDirectories, relevantVideoFiles); - - if (relevantVideoFiles.empty()) - { - LOG_ERROR << "no video file found"; - } - - LOG_DEBUG << "number of video files <" << relevantVideoFiles.size() << ">"; - boostPathToStrUtil(relevantVideoFiles, parsedVideoFiles); - return true; -} - -bool FileStructureParser::init(std::string& startingVideoFile, std::vector& parsedVideoFiles, bool includeStarting, bool parseDirs) -{ - /* - parse the dir stucture format of mp4WriterSink if parseDirs is set - otherwise read the individual mp4 file - */ - if (parseDirs) - { - if (!latestParsedVideoPath.empty()) - { - includeStarting = false; - } - parse(startingVideoFile, parsedVideoFiles, includeStarting); - } - else - { - parsedVideoFiles.push_back(startingVideoFile); - } - return true; -} - -int FileStructureParser::firstOfNextDay(boost::filesystem::path& baseFolder, std::string& yyyymmdd, std::string& videoFile) -{ - std::vector dates; - for (auto&& itr : boost::filesystem::directory_iterator(baseFolder)) - { - auto filePath = itr.path(); - if (boost::filesystem::is_directory(filePath)) - { - dates.push_back(filePath.filename().string()); - } - } - std::sort(dates.begin(), dates.end()); // is this required ? - int idx = greater(dates, yyyymmdd); - // no further dates present - if (idx < 0) - { - LOG_ERROR << "end of recordings <" << ParseStatus::END_OF_RECORDINGS << ">"; - return ParseStatus::END_OF_RECORDINGS; - } - auto yyyymmddDir = baseFolder / dates[idx]; - std::string hr = "0"; // hint: "00" > "0" - int ret = firstOfNextHour(yyyymmddDir, hr, videoFile); - if (ret < 0) - { - LOG_ERROR << "end of recordings <" << ParseStatus::END_OF_RECORDINGS << ">"; - return ParseStatus::END_OF_RECORDINGS; //should never come here if empty directories are not present - } - return ParseStatus::FOUND_NEXT; -} - -int FileStructureParser::firstOfNextHour(boost::filesystem::path& yyyymmddDir, std::string& hr, std::string& videoFile) -{ - std::vector hrs; - for (auto&& itr : boost::filesystem::directory_iterator(yyyymmddDir)) - { - auto filePath = itr.path(); - if (boost::filesystem::is_directory(filePath)) - { - hrs.push_back(filePath.filename().string()); - } - } - std::sort(hrs.begin(), hrs.end()); - int idx = greater(hrs, hr); - // no further hrs present - if (idx < 0) - { - return idx; - } - auto hrDir = yyyymmddDir / hrs[idx]; - std::string min = "00.mp4"; - return findFileWithMinute(hrDir, min, videoFile); -} - -int FileStructureParser::findFileWithMinute(boost::filesystem::path& hrDir, std::string& min, std::string& videoFile) -{ - // returns the possible file with the given minute. - // whether the file actually has the minute depends on the video file. - std::vector mins; - for (auto&& itr : boost::filesystem::directory_iterator(hrDir)) - { - auto filePath = itr.path(); - if (boost::filesystem::is_regular_file(filePath) && boost::filesystem::extension(filePath) == ".mp4") - { - mins.push_back(filePath.filename().string()); - } - } - - std::sort(mins.begin(), mins.end()); // is this required ? - // find exact file (lesserOrEqual), if its not available, find the next available file (greaterOrEqual) - int idx = lesserOrEqual(mins, min); - if (idx < 0) - { - idx = greaterOrEqual(mins, min); - if (idx < 0) - { - return ParseStatus::NOT_FOUND; - } - } - videoFile = (hrDir / mins[idx]).string(); - // set the nextToVideoFile in case the current file is not long enough to skip - if (mins[idx] <= min && nextToVideoFile.empty()) - { // save nextToVideoFileInfo only once - only when exact match is found - if (idx < mins.size() - 1) - { - nextToVideoFile = (hrDir / mins[idx + 1]).string(); - nextToVideoFileFlag = ParseStatus::FOUND_NEXT; - } - else - { // parse for next file in next folders - auto hrDirStr = hrDir.filename().string(); - auto hrDirParentPath = hrDir.parent_path(); - auto ret = firstOfNextHour(hrDirParentPath, hrDirStr, nextToVideoFile); - nextToVideoFileFlag = ret; - if (ret < 0) - { - auto yyyymmddDir = hrDir.parent_path().filename().string(); - auto heDirParentPath = hrDir.parent_path().parent_path(); - nextToVideoFileFlag = firstOfNextDay(heDirParentPath, yyyymmddDir, nextToVideoFile); - } - } - return ParseStatus::FOUND; - } - return ParseStatus::FOUND_NEXT; -} - -bool FileStructureParser::filePatternCheck(const boost::filesystem::path& path) -{ - if (boost::filesystem::is_regular_file(path) && boost::filesystem::extension(path) == ".mp4" && - path.stem().string().find_first_not_of("0123456789") == std::string::npos) - { - return true; - } - return false; -} - -bool FileStructureParser::datePatternCheck(const boost::filesystem::path& path) -{ - auto pathStr = path.filename().string(); - return (pathStr.find_first_not_of("0123456789") == std::string::npos && pathStr.size() == 8); - return false; -} - -bool FileStructureParser::hourPatternCheck(const boost::filesystem::path& path) -{ - auto parentPath = path.parent_path(); - if (!datePatternCheck(parentPath)) - { - return false; - } - auto pathStr = path.filename().string(); - return (pathStr.find_first_not_of("0123456789") == std::string::npos && pathStr.size() == 4); - return false; -} - -bool FileStructureParser::parseDir(boost::filesystem::path rootDir, std::string& videoName) -{ - try - { - boost::filesystem::is_empty(rootDir); - } - catch (...) - { - return false; - } - - boost::filesystem::recursive_directory_iterator dir(rootDir), end; - - LOG_INFO << "parsing files from dir <" << *dir << ">"; - - for (dir; dir != end; ++dir) - { - LOG_ERROR << "Checking dir <" << dir->path() << ">"; - auto path = dir->path(); - if (boost::filesystem::is_directory(dir->path())) - { - auto parentPath = dir->path().parent_path(); - - // potential date folder - if (boost::filesystem::equivalent(parentPath, rootDir)) - { - if (!datePatternCheck(dir->path())) - { - // skip going inside - dir.no_push(); - } - } - // potential hour folder - else - { - if (!hourPatternCheck(dir->path())) - { - // skip going inside - dir.no_push(); - } - } - // dont add folder paths to relevant files - continue; - } - - // potential video file - if (!filePatternCheck(dir->path())) - { - continue; - } - - videoName = dir->path().string(); - return true; - } - return false; -} - -int FileStructureParser::randomSeek(uint64_t& skipTS, std::string& skipDir, std::string& videoFile, uint64_t& skipMsecsInFile) -{ - skipMsecsInFile = 0; - nextToVideoFile = ""; - std::chrono::milliseconds duration(skipTS); - std::chrono::seconds secondsInDuration = std::chrono::duration_cast(duration); - std::chrono::milliseconds msecsInDuration = std::chrono::duration_cast(duration - secondsInDuration); - - std::chrono::time_point timePointInSeconds(secondsInDuration); - std::time_t t = std::chrono::system_clock::to_time_t(timePointInSeconds); - std::tm tm = *std::localtime(&t); - uint16_t msecs = msecsInDuration.count(); - - auto baseFolder = boost::filesystem::path(skipDir); - std::string yyyymmdd = std::to_string(1900 + tm.tm_year) + format_2(tm.tm_mon) + format_2(tm.tm_mday); - auto yyyymmddDir = baseFolder / yyyymmdd; - if (!boost::filesystem::is_directory(yyyymmddDir)) - { - return firstOfNextDay(baseFolder, yyyymmdd, videoFile); - } - - boost::filesystem::path hrDir = baseFolder / yyyymmdd / format_hrs(tm.tm_hour); - if (!boost::filesystem::is_directory(hrDir)) - { - auto formatHrs = format_hrs(tm.tm_hour); - auto retHr = firstOfNextHour(yyyymmddDir, formatHrs, videoFile); - if (retHr < 0) - { - return firstOfNextDay(baseFolder, yyyymmdd, videoFile); // if no next hour found on this day, move to next day - } - return retHr; - } - - std::tm tm2 = tm; - tm2.tm_sec = 0; - std::time_t tNew = std::mktime(&tm2); - auto tNewTimePoint = std::chrono::system_clock::from_time_t(tNew); - std::chrono::system_clock::duration d = tNewTimePoint.time_since_epoch(); - uint64_t tsTillMinInMsecs = std::chrono::duration_cast(d).count(); - - std::string fileToSearch = std::to_string(tsTillMinInMsecs) + ".mp4"; - auto mReturn = findFileWithMinute(hrDir, fileToSearch, videoFile); - auto ret = mReturn; - if (ret < 0) - { - auto formatHrs = format_hrs(tm.tm_hour); - auto retHr = firstOfNextHour(yyyymmddDir, formatHrs, videoFile); - if (retHr < 0) - { - return firstOfNextDay(baseFolder, yyyymmdd, videoFile); - } - return retHr; - } - - // we need frame at this msec in the file videoFile - std::string videoFileName = boost::filesystem::path(videoFile).filename().string(); - uint64_t tsOfFileFound = std::stoull(videoFileName.substr(0, videoFileName.find("."))); - if (tsOfFileFound < skipTS) - { - skipMsecsInFile = skipTS - tsOfFileFound; - } - return ret; -} \ No newline at end of file diff --git a/base/src/OrderedCacheOfFiles.cpp b/base/src/OrderedCacheOfFiles.cpp new file mode 100644 index 000000000..6296e7699 --- /dev/null +++ b/base/src/OrderedCacheOfFiles.cpp @@ -0,0 +1,882 @@ +#include +#include "Logger.h" +#include "libmp4.h" +#include "OrderedCacheOfFiles.h" +#include "AIPExceptions.h" + +/* implies that upon dir change the shared pointer needs to be reset with new params */ +OrderedCacheOfFiles::OrderedCacheOfFiles(std::string& video_folder, uint32_t initial_batch_size, uint32_t _lowerWaterMark, uint32_t _upperWaterMark) +{ + rootDir = video_folder; + batchSize = initial_batch_size; + lowerWaterMark = _lowerWaterMark; + upperWaterMark = _upperWaterMark; + cleanCacheOnMainThread = true; +} + +uint64_t OrderedCacheOfFiles::getFileDuration(std::string& filename) +{ + auto videoIter = videoCache.find(filename); + if (videoIter == videoCache.end()) + { + return 0; + } + if (videoIter->end_ts != 0) + { + uint64_t diff = videoIter->end_ts - videoIter->start_ts; + return diff; + } + return 0; +} + +bool OrderedCacheOfFiles::fetchFromCache(std::string& videoFile, uint64_t& start_ts, uint64_t& end_ts) +{ + LOG_TRACE << "fetchFromCache called..."; + start_ts = 0; + end_ts = 0; + if (videoCache.empty()) + { + return false; + } + + try + { + videoFile = boost::filesystem::canonical(videoFile).string(); + } + catch (...) + { + LOG_ERROR << "File not found on disk: " + videoFile; + return false; + } + + auto iter = videoCache.find(videoFile); + if (iter == videoCache.end()) + { + return false; + } + + start_ts = iter->start_ts; + end_ts = iter->end_ts; + + return true; +} + +bool OrderedCacheOfFiles::fetchAndUpdateFromDisk(std::string videoFile, uint64_t& start_ts, uint64_t& end_ts) +{ + LOG_TRACE << "getFileStartAndEnd called..."; + start_ts = 0; + end_ts = 0; + if (videoCache.empty()) + { + return false; + } + + try + { + videoFile = boost::filesystem::canonical(videoFile).string(); + } + catch (...) + { + LOG_ERROR << "File not found on disk: " + videoFile; + return false; + } + + auto iter = videoCache.find(videoFile); + if (iter == videoCache.end()) + { + return false; + } + + start_ts = iter->start_ts; + + // force update regardless of end_ts != 0 + uint64_t tstart_ts, tend_ts; + readVideoStartEnd(videoFile, tstart_ts, tend_ts); + updateCache(videoFile, tstart_ts, tend_ts); + + end_ts = iter->end_ts; + + return true; +} + +/* +* This method takes and returns a snapshot of the cache data. It is costly and should be used only rarely. +*/ +std::map> OrderedCacheOfFiles::getSnapShot() +{ + std::map> snap; + for (auto it = videoCache.begin(); it != videoCache.end(); ++it) + { + std::pair> elem; + elem.first = it->path; + elem.second.first = it->start_ts; + elem.second.second = it->end_ts; + snap.insert(elem); + } + return snap; +} + +bool OrderedCacheOfFiles::probe(boost::filesystem::path potentialMp4File, std::string& videoName) +{ + try + { + boost::filesystem::is_empty(potentialMp4File); + } + catch (...) + { + return false; + } + + auto dateDir = parseAndSortDateDir(potentialMp4File.string()); + + for (auto& dateDirPath : dateDir) + { + auto hourDir = parseAndSortHourDir(dateDirPath.string()); + + for (auto& hourDirPath : hourDir) + { + + auto mp4Files = parseAndSortMp4Files(hourDirPath.string()); + + if (mp4Files.size()) + { + videoName = mp4Files.begin()->string(); + return true; + } + else + { + return false; + } + } + } + return false; +} + +/* +Important Note: +**UNRELIABLE METHOD - Use ONLY if you know what you are doing.** +It is used to get the potential file at the timestamp. +It does not gurantee that the file will actually contain the timestamp +NOR does it guratee that the file is logically correct. +It has to be used in conjungtion with other methods i.e. +Use isTimeStampInFile() to confirm. +If ts is not present in the file returned by isTimeStampInFile(), +then use the getNextFileAfter() to conclude. +*/ +std::string OrderedCacheOfFiles::getFileAt(uint64_t timestamp, bool direction) +{ + if (videoCache.empty()) + { + auto msg = "Calling getFileAt() on empty cache. Call parseFiles first."; + LOG_ERROR << msg; + throw AIPException(MP4_OCOF_EMPTY, msg); + } + // Note: start_ts will never have cache miss + auto lowerBoundIter = videoCacheStartTSIndex.lower_bound(timestamp); + + /* + Idea1: If we find exact match with lower_bound(), simply return it. + If not exact match, that means the sts of file > ts queried. For these cases: + Idea2 - For bwd play, we return the lower_bound() iterator itself. I know this is wrong. It is by design to maintain abstraction. + Idea3 - See Usage Note above to understand the abstraction we are maintaining. + Idea4 - For fwd play, we return the previous iterator to the lower_bound() + Corner cases: + 1. bwd play + invalid iterator i.e. end - we need to break Idea2 + 2. fwd play + first iterator - we need to break Idea4 + 3. bwd play + lower_bound is end() - give empty string - so that isTimeStampInFile returns false + Detection of End Of Files: + 1. BWD Play: bwd play + lower_bound is the first value + 2. FWD Play: we cant detect EOF boundary in fwd play, bcz the last video can be of any length. Use getNextFileAfter() to confirm. + */ + + // lower bound itself returns end iterator, cacheIteratorState.END_ITER represents the same in string + if (!direction && lowerBoundIter == videoCacheStartTSIndex.end()) + { + return cacheIteratorState.END_ITER; // no correct file - isTimeStampInFile will return false for empty string + } + + // exact match + if (lowerBoundIter->start_ts == timestamp) + { + return lowerBoundIter->path; + } + + // greater than timestamp + if (lowerBoundIter == videoCacheStartTSIndex.begin()) + { + if (!direction) + { + LOG_ERROR << "this exception will be caught!!"; + throw Mp4Exception(MP4_OCOF_END, "Reached end of cache in bwd play"); // EOF in bwd play + } + + if (direction) + { + return lowerBoundIter->path; // corner case in fwd direction + } + } + + // Note: We are intentionally returning the next file in bwd case + if (direction) + { + --lowerBoundIter; + } + + // Note: this method will always return last file in case of ts >= last file's sts + return lowerBoundIter->path; +} + +std::string OrderedCacheOfFiles::getNextFileAfter(std::string& currentFile, bool direction) +{ + // corner case of END_ITER + if (currentFile == cacheIteratorState.END_ITER) + { + auto iter = videoCache.end(); + --iter; + return iter->path; + } + /* Assumption from here: currentFile will always be present in the cache */ + auto iter = videoCache.find(currentFile); + if (iter == videoCache.end()) + { + auto msg = "currentFile <" + currentFile + "> missing in the cache."; + LOG_ERROR << msg; + throw Mp4Exception(MP4_OCOF_MISSING_FILE, msg); + } + + if (direction) + { + // increment then check + ++iter; + if (iter != videoCache.end()) + { + return iter->path; + } + else + { + LOG_ERROR << "this exception will be caught!!"; + throw Mp4Exception(MP4_OCOF_END, "Reached End of Cache in fwd play."); + } + } + else + { + // check then decrement + if (iter != videoCache.begin()) + { + --iter; + return iter->path; + } + else + { + LOG_ERROR << "this exception will be caught!!"; + throw Mp4Exception(MP4_OCOF_END, "Reached End of Cache in bwd play."); + } + } + + // no valid next file + return ""; +} + +bool OrderedCacheOfFiles::isTimeStampInFile(std::string& filePath, uint64_t timestamp) +{ + // corner case of END_ITER + if (filePath == cacheIteratorState.END_ITER) + { + return false; + } + auto videoIter = videoCache.find(filePath); + if (videoIter == videoCache.end()) + { + LOG_INFO << "Unexpected: File not present in the parsed files. Please recheck."; + return false; + } + + if (!videoIter->end_ts) // we havent opened the video yet. + { + uint64_t tstart_ts, tend_ts; + readVideoStartEnd(filePath, tstart_ts, tend_ts); + updateCache(filePath, tstart_ts, tend_ts); + } + + if (timestamp >= videoIter->start_ts && timestamp <= videoIter->end_ts) + { + return true; + } + return false; +} + +void OrderedCacheOfFiles::readVideoStartEnd(std::string& filePath, uint64_t& start_ts, uint64_t& end_ts) +{ + // open the file + uint64_t duration = 0; + start_ts = 0; + end_ts = 0; + + struct mp4_demux* demux; + auto ret = mp4_demux_open(filePath.c_str(), &demux); + if (ret < 0) + { + auto msg = "Error opening the file <" + filePath + "> libmp4 errorcode<" + std::to_string(ret) + ">"; + LOG_ERROR << msg; + throw Mp4Exception(MP4_OPEN_FILE_FAILED, msg); + } + // get the video span from the file + try + { + mp4_demux_time_range(demux, &start_ts, &duration); + } + catch (...) + { + auto msg = "Unexpected error occured getting time range of the video <" + filePath + ">"; + LOG_ERROR << msg; + throw AIPException(MP4_TIME_RANGE_FETCH_FAILED, msg); + } + // if timestamp not present in header (returns 0) - try reading from the filename + if (!start_ts) + { + try + { + auto fileNameTS = boost::filesystem::path(filePath).stem().string(); + start_ts = std::stoull(fileNameTS); + } + catch (std::invalid_argument) + { + auto msg = "unexpected state - starting ts not found in video name or metadata"; + LOG_ERROR << msg; + throw Mp4Exception(MP4_UNEXPECTED_STATE, msg); + } + } + // parsed start_ts and duration of the video + end_ts = start_ts + duration; + if (end_ts < start_ts) + { + auto msg = "Invalid values: end ts < start ts in videoCache entry"; + LOG_ERROR << msg; + throw Mp4Exception(MP4_UNEXPECTED_STATE, msg); + } + // close the file + mp4_demux_close(demux); + +} + +void OrderedCacheOfFiles::updateCache(std::string& filePath, uint64_t& start_ts, uint64_t& end_ts) +{ + auto videoIter = videoCache.find(filePath); + if (videoIter == videoCache.end()) + { + auto msg = "Trying to update non existing video data"; + LOG_ERROR << msg; + throw Mp4Exception(MP4_OCOF_MISSING_FILE, msg); + } + if (end_ts == videoIter->end_ts) + { + return; + } + boost::mutex::scoped_lock lock(m_mutex); + videoCache.modify(videoIter, [start_ts, end_ts](auto& entry) {entry.start_ts = start_ts; entry.end_ts = end_ts; }); +} + +/* throws MP4_UNEXPECTED_STATE, MP4_OCOF_EMPTY */ +bool OrderedCacheOfFiles::getRandomSeekFile(uint64_t skipTS, bool direction, uint64_t& skipMsecs, std::string& skipVideoFile) +{ + skipMsecs = 0; + skipVideoFile = ""; + uint64_t freshParseStartTS = skipTS; + bool queryBeforeCacheStart = false; + + // track playback dir + if (lastKnownPlaybackDir != direction) + { + lastKnownPlaybackDir = direction; + } + + /* Perform a fresh disk parse in way to ensure that no holes are created in the cache. */ + if (direction) + { + if (skipTS < videoCache.begin()->start_ts) + { + // parse - green + queryBeforeCacheStart = true; + freshParseStartTS = skipTS; + } + else + { + queryBeforeCacheStart = false; + freshParseStartTS = videoCache.rbegin()->start_ts; // start parsing from EOC in fwd dir + } + } + else if (!direction) + { + if (skipTS > videoCache.rbegin()->start_ts) + { + // check if skipTS lies in the last file, then no need to do fresh Disk parse + std::string lastFileInCache = videoCache.rbegin()->path; + if (!isTimeStampInFile(lastFileInCache, skipTS)) + { + // parse - green + queryBeforeCacheStart = true; + freshParseStartTS = skipTS; + } + } + else + { + queryBeforeCacheStart = false; + freshParseStartTS = videoCache.begin()->start_ts; // in bwd dir, files till freshParseStartTS will get parsed + } + } + + if (!queryBeforeCacheStart) + { + bool isSkipFileInCache = getFileFromCache(skipTS, direction, skipVideoFile); + if (isSkipFileInCache) + { + bool isTsInFile = isTimeStampInFile(skipVideoFile, skipTS); + if (isTsInFile) + { + auto cachedFile = videoCache.find(skipVideoFile); + uint64_t startTS = cachedFile->start_ts; + skipMsecs = skipTS - startTS; + } + return true; + } + } + + /* fresh parse is required - cache will be updated according to the skipTS */ + bool foundRelevantFiles = parseFiles(freshParseStartTS, direction, true, true, skipTS); // enable includeExactMatch, disableBatchSizeCheck, drop farthest from skipTS + if (!foundRelevantFiles) + { + // seek fails + return false; + } + + // recheck bounds with updated cache + if (direction && skipTS < videoCache.begin()->start_ts) + { + // green + skipVideoFile = videoCache.begin()->path; + // skipMsecs = 0; + return true; + } + else if (!direction && skipTS > videoCache.rbegin()->start_ts) + { + // green + skipVideoFile = videoCache.rbegin()->path; + // set correct skipMsecs if skipTS is indeed inside the skipVideoFile before returning + bool isTSInFile = isTimeStampInFile(skipVideoFile, skipTS); + if (isTSInFile) + { + auto videoEntry = videoCache.find(skipVideoFile); + uint64_t startTS = videoEntry->start_ts; + skipMsecs = skipTS - startTS; + } + return true; + } + + // check in updated cache data + bool isSkipFileInCache = getFileFromCache(skipTS, direction, skipVideoFile); + if (!isSkipFileInCache) + { + /*case: in case of fwd parse, cache might include last file on disk as relevant file, + but, if the skipTS is not in that file, the seek fails here. */ + return false; // seek fails + //throw Mp4Exception(MP4_UNEXPECTED_STATE, "unexpected error happened while searching for file in cache."); + } + // skipMsecs = 0, if the skipTS doesn't lie inside any file. Else, calc based on start_ts of skipVideoFile. + bool isTSInFile = isTimeStampInFile(skipVideoFile, skipTS); + if (isTSInFile) + { + auto videoEntry = videoCache.find(skipVideoFile); + uint64_t startTS = videoEntry->start_ts; + skipMsecs = skipTS - startTS; + } + return true; +} + +/* throws MP4_OCOF_EMPTY*/ +bool OrderedCacheOfFiles::getFileFromCache(uint64_t timestamp, bool direction, std::string& fileName) +{ + try + { + fileName = getFileAt(timestamp, direction); + bool tsInFileFlag = isTimeStampInFile(fileName, timestamp); + + if (tsInFileFlag) + { + return true; + } + else + { + /*if the file is the last file in cache - + and there is a possibility that file may get updated (eg. while writing) - + then reload the file */ + if (direction && fileName == videoCache.rbegin()->path) //boost::filesystem::equivalent(boost::filesystem::canonical(fileName), boost::filesystem::canonical(videoCache.rbegin()->path)) + { + uint64_t tstart_ts, tend_ts; + readVideoStartEnd(fileName, tstart_ts, tend_ts); + updateCache(fileName, tstart_ts, tend_ts); + if (timestamp >= tstart_ts && timestamp <= tend_ts) + { + return true; + } + } + fileName = getNextFileAfter(fileName, direction); + return true; + } + } + catch (Mp4_Exception& exception) + { + if (exception.getCode() == MP4_OCOF_EMPTY) + { + fileName = ""; + auto msg = "Unexpected error happened in OrderedCacheOfFiles while getting file from it"; + LOG_ERROR << msg; + throw Mp4Exception(MP4_OCOF_EMPTY, msg); + } + if (exception.getCode() == MP4_OCOF_END) + { + fileName = ""; + } + return false; + } +} + +void OrderedCacheOfFiles::insertInVideoCache(Video vid) +{ + boost::mutex::scoped_lock lock(m_mutex); + videoCache.insert(vid); +} + + +/* directory parsing: + 1. Iterate over whole root directory recursively + 2. Discard files in the root folder + 3. Dont parse the folders with other format than yyyymmdd + 4. For all the mp4 files check if they are relevant based on start_ts + direction (strictly increasing/dec) + 5. For the first batchSize number of relevant files - add them in the videoCache with the starting timestamp (name) + 6. If no relevant file found in the parsing - return false to indicate no more relevant file on disk. + 7. If the higher watermark is breached in the videoCache, trigger a cleanup in seperate thread ? + */ + +std::vector OrderedCacheOfFiles::parseAndSortDateDir(const std::string& rootDir) +{ + std::vector dateDir; + fs::directory_iterator dateDirIter(rootDir), dateDirEndIter; + LOG_INFO << "parsing files from dir <" << *dateDirIter << ">"; + + for (dateDirIter; dateDirIter != dateDirEndIter; ++dateDirIter) + { + if (fs::is_directory(dateDirIter->path())) + { + auto parentPath = dateDirIter->path().parent_path(); + + // potential date folder + if (fs::equivalent(parentPath, rootDir)) + { + if (datePatternCheck(dateDirIter->path())) + { + dateDir.push_back(dateDirIter->path()); + } + } + } + + } + + if (dateDir.size()) + { + std::sort(dateDir.begin(), dateDir.end()); + } + return dateDir; +} + +std::vector OrderedCacheOfFiles::parseAndSortHourDir(const std::string& dateDirPath) +{ + std::vector hourDir; + + fs::directory_iterator hourDirIter(dateDirPath), hourDirEndIter; + for (hourDirIter; hourDirIter != hourDirEndIter; ++hourDirIter) + { + if (fs::is_directory(hourDirIter->path())) + { + // potential hour folder + if (hourPatternCheck(hourDirIter->path())) + { + hourDir.push_back(hourDirIter->path()); + } + } + } + + if (hourDir.size()) + { + std::sort(hourDir.begin(), hourDir.end()); + } + return hourDir; +} + +std::vector OrderedCacheOfFiles::parseAndSortMp4Files(const std::string& hourDirPath) +{ + std::vector mp4Files; + fs::directory_iterator mp4FileIter(hourDirPath), mp4FileEndIter; + // potential video file + for (mp4FileIter; mp4FileIter != mp4FileEndIter; ++mp4FileIter) + { + if (filePatternCheck(mp4FileIter->path())) + { + mp4Files.push_back(mp4FileIter->path()); + } + } + if (mp4Files.size()) + { + std::sort(mp4Files.begin(), mp4Files.end()); + } + return mp4Files; +} + +bool OrderedCacheOfFiles::parseFiles(uint64_t start_ts, bool direction, bool includeFloorFile, bool disableBatchSizeCheck, uint64_t skipTS) +{ + // Note- direction: synced with playback direction + int parsedFilesCount = 0; + lastKnownPlaybackDir = direction; + bool exactMatchFound = false; + uint64_t startTSofRelevantFile = 0; + uint64_t startTSofPrevFileOnDisk = 0; + boost::filesystem::path previousFileOnDisk = ""; + boost::filesystem::path exactMatchFile = ""; + + auto dateDir = parseAndSortDateDir(rootDir); + + for (auto& dateDirPath : dateDir) + { + auto hourDir = parseAndSortHourDir(dateDirPath.string()); + + for (auto& hourDirPath : hourDir) + { + if (!disableBatchSizeCheck && parsedFilesCount >= batchSize) + { + break; // stop parsing + } + + auto mp4Files = parseAndSortMp4Files(hourDirPath.string()); + + for (auto& mp4File : mp4Files) + { + // time based filtering + // force batchSize check at hour folder level + + uint64_t fileTS = 0; + try + { + fileTS = std::stoull(mp4File.stem().string()); + } + catch (...) + { + LOG_TRACE << "OrderedCacheOfFiles: Ignoring File <" << mp4File.string() << "> due to timestamp parsing failure."; + continue; + } + if (direction && fileTS < start_ts) + { + // keep track of prev mp4 file on disk + previousFileOnDisk = mp4File.string(); + startTSofPrevFileOnDisk = fileTS; + continue; + } + else if (!direction && fileTS > start_ts) + { + continue; + } + else if (!includeFloorFile && fileTS == start_ts) + { + exactMatchFound = true; + exactMatchFile = mp4File.string(); + continue; + } + + // cache insertion + LOG_INFO << "cache insert: " << mp4File << "\n"; + Video vid(mp4File.string(), fileTS); + + /* ----- first relevant file found ----- */ + if (!startTSofRelevantFile) + { + startTSofRelevantFile = vid.start_ts; + + if (includeFloorFile && !exactMatchFound) + { + // add prev file to cache - handles start_ts lies in middle of prevFileOnDisk in fwd parse. + if (!previousFileOnDisk.empty()) + { + startTSofRelevantFile = startTSofPrevFileOnDisk; + Video prevVid(previousFileOnDisk.string(), startTSofPrevFileOnDisk); + insertInVideoCache(prevVid); + ++parsedFilesCount; + } + } + } + /* ----- first relevant file found end ----- */ + + insertInVideoCache(vid); + ++parsedFilesCount; + } + } + } + /* corner case: first relevant file was never found --- */ + if (!startTSofRelevantFile) // no file with ts > start_ts was found + { + if (exactMatchFound) // only 1 file with exactmatch was found + { + startTSofRelevantFile = start_ts; + Video exactMatchVid(exactMatchFile.string(), start_ts); + insertInVideoCache(exactMatchVid); + ++parsedFilesCount; + } + else if (includeFloorFile && startTSofPrevFileOnDisk) // in case start_ts is present in prevFileOnDisk + { + startTSofRelevantFile = startTSofPrevFileOnDisk; + Video prevVid(previousFileOnDisk.string(), startTSofPrevFileOnDisk); + insertInVideoCache(prevVid); + ++parsedFilesCount; + } + } + /* trigger the drop strategy + if seek triggered parse - drop from farthest side of seekTS */ + auto startDropFromTS = skipTS ? skipTS : startTSofRelevantFile; + retireOldFiles(startDropFromTS); + + bool foundRelevantFilesOnDisk = parsedFilesCount > 0; + return foundRelevantFilesOnDisk; + +} + +void OrderedCacheOfFiles::retireOldFiles(uint64_t ts) +{ + /* cant blindly delete from the cache end which is opposite to direction -- + eg. seek queryBeforeCache will drop new files as well */ + if (cleanCacheOnMainThread) + { + dropFarthestFromTS(ts); + return; + } + if (mThread) + { + mThread->join(); + } + mThread = boost::shared_ptr(new boost::thread(boost::bind(&OrderedCacheOfFiles::dropFarthestFromTS, this, ts))); +} + +void OrderedCacheOfFiles::dropFarthestFromTS(uint64_t ts) +{ + if (videoCache.empty()) + { + return; + } + + /* dropping algo */ + int64_t begDistTS = ts - videoCache.begin()->start_ts; + auto absBeginDistance = abs(begDistTS); + int64_t endDistTS = ts - videoCache.rbegin()->start_ts; + auto absEndDistance = abs(endDistTS); + if (videoCache.size() >= upperWaterMark) + { + if (absEndDistance <= absBeginDistance) + { + auto itr = videoCache.begin(); + while (itr != videoCache.end()) + { + auto path = itr->path; + if (videoCache.size() >= lowerWaterMark) + { + boost::mutex::scoped_lock(m_mutex); + // Note - erase returns the iterator of next element after deletion. + itr = videoCache.erase(itr); + } + else + { + return; + } + } + } + else + { + // delete from end using the fwd iterator. + auto itr = videoCache.end(); + --itr; + while (itr != videoCache.begin()) + { + auto path = itr->path; + if (videoCache.size() >= lowerWaterMark) + { + boost::mutex::scoped_lock(m_mutex); + // Note - erase returns the iterator of next element after deletion. + itr = videoCache.erase(itr); + --itr; + } + else + { + return; + } + } + } + } +} + +void OrderedCacheOfFiles::deleteLostEntry(std::string& filePath) +{ + auto itr = videoCache.find(filePath); + if (itr == videoCache.end()) + { + return; + } + + boost::mutex::scoped_lock(m_mutex); + itr = videoCache.erase(itr); // erase gives updated itr from cache + + return; +} + +void OrderedCacheOfFiles::clearCache() +{ + if (videoCache.size()) + { + boost::mutex::scoped_lock(m_mutex); + videoCache.clear(); + } +} + +bool OrderedCacheOfFiles::refreshCache() +{ + auto direction = lastKnownPlaybackDir; + auto startParsingFrom = direction ? videoCache.begin()->start_ts : videoCache.rbegin()->end_ts; + return parseFiles(startParsingFrom, direction, true, true); +} + +/* Utils methods */ +bool OrderedCacheOfFiles::filePatternCheck(const fs::path& path) +{ + if (fs::is_regular_file(path) && fs::extension(path) == ".mp4" && + path.stem().string().find_first_not_of("0123456789") == std::string::npos) + { + return true; + } + return false; +} + +bool OrderedCacheOfFiles::datePatternCheck(const boost::filesystem::path& path) +{ + /*auto parentPath = path.parent_path(); + if (!boost::filesystem::equivalent(parentPath, rootDir)) + { + return false; + }*/ + auto pathStr = path.filename().string(); + return (pathStr.find_first_not_of("0123456789") == std::string::npos && pathStr.size() == 8); +} + +bool OrderedCacheOfFiles::hourPatternCheck(const boost::filesystem::path& path) +{ + auto parentPath = path.parent_path(); + if (!datePatternCheck(parentPath)) + { + return false; + } + auto pathStr = path.filename().string(); + return (pathStr.find_first_not_of("0123456789") == std::string::npos && pathStr.size() == 4); +} diff --git a/base/test/h264decoder_tests.cpp b/base/test/h264decoder_tests.cpp index c950f5f80..cc643e00f 100644 --- a/base/test/h264decoder_tests.cpp +++ b/base/test/h264decoder_tests.cpp @@ -29,7 +29,7 @@ BOOST_AUTO_TEST_CASE(mp4reader_decoder_eglrenderer,* boost::unit_test::disabled( // metadata is known std::string videoPath = "./data/Mp4_videos/h264_video/20221010/0012/1668064027062.mp4"; - auto mp4ReaderProps = Mp4ReaderSourceProps(videoPath, false); + auto mp4ReaderProps = Mp4ReaderSourceProps(videoPath, false, 0, true, false, false); auto mp4Reader = boost::shared_ptr(new Mp4ReaderSource(mp4ReaderProps)); auto h264ImageMetadata = framemetadata_sp(new H264Metadata(0, 0)); mp4Reader->addOutPutPin(h264ImageMetadata); @@ -70,7 +70,7 @@ BOOST_AUTO_TEST_CASE(mp4reader_decoder_extsink) // metadata is known std::string videoPath = "./data/Mp4_videos/h264_video/20221010/0012/1668064027062.mp4"; - auto mp4ReaderProps = Mp4ReaderSourceProps(videoPath, false); + auto mp4ReaderProps = Mp4ReaderSourceProps(videoPath, false, 0, true, false, false); auto mp4Reader = boost::shared_ptr(new Mp4ReaderSource(mp4ReaderProps)); auto h264ImageMetadata = framemetadata_sp(new H264Metadata(0, 0)); mp4Reader->addOutPutPin(h264ImageMetadata); @@ -225,7 +225,7 @@ BOOST_AUTO_TEST_CASE(mp4reader_to_decoder_extSink, *utf::precondition(if_h264_en Logger::setLogLevel("info"); std::string startingVideoPath_2 = "./data/Mp4_videos/h264_video/20221010/0012/1668064027062.mp4"; - auto mp4ReaderProps_2 = Mp4ReaderSourceProps(startingVideoPath_2, false); + auto mp4ReaderProps_2 = Mp4ReaderSourceProps(startingVideoPath_2, false, 0, true, false, false); mp4ReaderProps_2.logHealth = true; mp4ReaderProps_2.logHealthFrequency = 100; mp4ReaderProps_2.fps = 30; diff --git a/base/test/mp4_dts_strategy_tests.cpp b/base/test/mp4_dts_strategy_tests.cpp new file mode 100644 index 000000000..42cdff35a --- /dev/null +++ b/base/test/mp4_dts_strategy_tests.cpp @@ -0,0 +1,203 @@ +#include +#include +#include +#include +#include "Logger.h" + +#include "Mp4ReaderSource.h" +#include "Mp4WriterSink.h" +#include "StatSink.h" +#include "EncodedImageMetadata.h" +#include "Mp4VideoMetadata.h" +#include "FileWriterModule.h" +#include "H264Metadata.h" +#include "PipeLine.h" +#include "test_utils.h" + +BOOST_AUTO_TEST_SUITE(mp4_dts_strategy) + +void read_write(std::string videoPath, std::string outPath, + bool recordedTSBasedDTS, bool parseFS = true, int chunkTime=1) +{ + LoggerProps loggerProps; + loggerProps.logLevel = boost::log::trivial::severity_level::info; + Logger::setLogLevel(boost::log::trivial::severity_level::info); + Logger::initLogger(loggerProps); + + bool readLoop = false; + auto mp4ReaderProps = Mp4ReaderSourceProps(videoPath, parseFS, 0, true, readLoop, false); + mp4ReaderProps.logHealth = true; + mp4ReaderProps.logHealthFrequency = 300; + auto mp4Reader = boost::shared_ptr(new Mp4ReaderSource(mp4ReaderProps)); + auto h264ImageMetadata = framemetadata_sp(new H264Metadata(0, 0)); + mp4Reader->addOutPutPin(h264ImageMetadata); + auto mp4Metadata = framemetadata_sp(new Mp4VideoMetadata("v_1")); + mp4Reader->addOutPutPin(mp4Metadata); + + std::vector mImagePin; + mImagePin = mp4Reader->getAllOutputPinsByType(FrameMetadata::H264_DATA); + auto mp4WriterSinkProps = Mp4WriterSinkProps(chunkTime, 1, 30, outPath, recordedTSBasedDTS); + mp4WriterSinkProps.logHealth = true; + mp4WriterSinkProps.logHealthFrequency = 300; + auto mp4WriterSink = boost::shared_ptr(new Mp4WriterSink(mp4WriterSinkProps)); + mp4Reader->setNext(mp4WriterSink, mImagePin); + + boost::shared_ptr p; + p = boost::shared_ptr(new PipeLine("test")); + p->appendModule(mp4Reader); + + if (!p->init()) + { + throw AIPException(AIP_FATAL, "Engine Pipeline init failed. Check IPEngine Logs for more details."); + } + p->run_all_threaded(); + + boost::this_thread::sleep_for(boost::chrono::seconds(15)); + + p->stop(); + p->term(); + + p->wait_for_all(); + + p.reset(); +} + +BOOST_AUTO_TEST_CASE(read_mul_write_one_as_recorded) +{ + // two videos (9sec, 9 sec) with 17hr time gap + // writes a 17hr 8mins 27sec sec video , in which first and last 9 secs is playable + std::string videoPath = "data/Mp4_videos/h264_videos_dts_test/20221010/0012/1668001826042.mp4"; + std::string outPath = "data/testOutput/mp4_videos/outFrames/file_as_rec.mp4"; + bool parseFS = true; + + // write a fixed rate video with no gaps + bool recordedTSBasedDTS = true; + read_write(videoPath, outPath, recordedTSBasedDTS, parseFS, UINT32_MAX); + + Test_Utils::deleteFolder(outPath); +} + +BOOST_AUTO_TEST_CASE(read_mul_write_one_fixed_rate) +{ + // two videos (9sec, 9 sec) with 17hr time gap + // writes a 15 secs video playable video + std::string videoPath = "data/Mp4_videos/h264_videos_dts_test/20221010/0012/1668001826042.mp4"; + std::string outPath = "data/testOutput/mp4_videos/outFrames/file_fixed_rate.mp4"; + bool parseFS = true; + + // write both videos as recorded i.e. including the gap + bool recordedTSBasedDTS = false; + read_write(videoPath, outPath, recordedTSBasedDTS, parseFS, UINT32_MAX); + + Test_Utils::deleteFolder(outPath); +} + +struct SetupSeekTest { + SetupSeekTest(std::string videoPath, bool parseFS) + { + LoggerProps loggerProps; + loggerProps.logLevel = boost::log::trivial::severity_level::info; + Logger::setLogLevel(boost::log::trivial::severity_level::info); + Logger::initLogger(loggerProps); + + bool readLoop = false; + auto mp4ReaderProps = Mp4ReaderSourceProps(videoPath, parseFS, 0, true, readLoop, false); + mp4ReaderProps.logHealth = true; + mp4ReaderProps.logHealthFrequency = 300; + mp4Reader = boost::shared_ptr(new Mp4ReaderSource(mp4ReaderProps)); + auto h264ImageMetadata = framemetadata_sp(new H264Metadata(0, 0)); + mp4Reader->addOutPutPin(h264ImageMetadata); + auto mp4Metadata = framemetadata_sp(new Mp4VideoMetadata("v_1")); + mp4Reader->addOutPutPin(mp4Metadata); + + std::vector mImagePin; + mImagePin = mp4Reader->getAllOutputPinsByType(FrameMetadata::H264_DATA); + + auto sinkProps = ExternalSinkProps();; + sink = boost::shared_ptr(new ExternalSink(sinkProps)); + mp4Reader->setNext(sink, mImagePin); + } + + class ExternalSinkProps : public ModuleProps + { + public: + ExternalSinkProps() : ModuleProps() + { + } + }; + class ExternalSink : public Module + { + public: + ExternalSink(ExternalSinkProps props) : Module(SINK, "ExternalSink", props) + { + } + + frame_container pop() + { + return Module::pop(); + } + + ~ExternalSink() + { + } + + protected: + bool process(frame_container &frames) + { + //LOG_ERROR << "ExternalSinkProcess <>"; + for (const auto &pair : frames) + { + LOG_TRACE << pair.first << "," << pair.second; + } + + auto frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + if (frame) + LOG_INFO << "Timestamp <" << frame->timestamp << ">"; + + return true; + } + + bool validateInputPins() + { + return true; + } + + bool validateInputOutputPins() + { + return true; + } + + }; // ExternalSink + boost::shared_ptr mp4Reader; + boost::shared_ptr sink; +}; + +BOOST_AUTO_TEST_CASE(eof_seek_step) +{ + /* tests the issue - unable to read the file after randomSeek@same file once it reaches EOF */ + // reads the output video of read_mul_write_one_fixed_rate test above + std::string videoPath = "data/Mp4_videos/file_fixed_rate.mp4"; + bool parseFS = false; + + SetupSeekTest f(videoPath, parseFS); + BOOST_TEST(f.mp4Reader->init()); + BOOST_TEST(f.sink->init()); + + f.mp4Reader->randomSeek(1668001826042); + f.mp4Reader->step(); + + f.mp4Reader->step(); + auto frames = f.sink->pop(); + auto frame = Module::getFrameByType(frames, FrameMetadata::FrameType::H264_DATA); + BOOST_TEST(frame->timestamp == 1668001826042); + + f.mp4Reader->randomSeek(1668001829042); + f.mp4Reader->step(); + + f.mp4Reader->step(); + frames = f.sink->pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::H264_DATA); + BOOST_TEST(frame->timestamp == 1668001826075); +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/base/test/mp4_getlivevideots_tests.cpp b/base/test/mp4_getlivevideots_tests.cpp new file mode 100644 index 000000000..de5e54d5e --- /dev/null +++ b/base/test/mp4_getlivevideots_tests.cpp @@ -0,0 +1,182 @@ +#include +#include "test_utils.h" +#include "FrameMetadata.h" +#include "FrameMetadataFactory.h" +#include "Frame.h" +#include "Logger.h" +#include "AIPExceptions.h" +#include "PipeLine.h" + +#include "FileReaderModule.h" +#include "Mp4ReaderSource.h" +#include "Mp4VideoMetadata.h" +#include "EncodedImageMetadata.h" +#include "FrameContainerQueue.h" + +BOOST_AUTO_TEST_SUITE(mp4_getlivevideots_tests) + +struct SetupSeekTests +{ + SetupSeekTests(std::string &startingVideoPath, int width, int height, int reInitInterval, bool parseFS, bool readLoop, bool getLiveTS = true) + { + LoggerProps loggerProps; + loggerProps.logLevel = boost::log::trivial::severity_level::info; + Logger::initLogger(loggerProps); + + auto mp4ReaderProps = Mp4ReaderSourceProps(startingVideoPath, parseFS, reInitInterval, true, readLoop, getLiveTS); + mp4Reader = boost::shared_ptr(new Mp4ReaderSource(mp4ReaderProps)); + auto encodedImageMetadata = framemetadata_sp(new EncodedImageMetadata(width, height)); + auto encodedImagePin = mp4Reader->addOutPutPin(encodedImageMetadata); + auto mp4Metadata = framemetadata_sp(new Mp4VideoMetadata("v_3_0")); + auto mp4MetadataPin = mp4Reader->addOutPutPin(mp4Metadata); + + + std::vector mImagePin; + mImagePin = mp4Reader->getAllOutputPinsByType(FrameMetadata::ENCODED_IMAGE); + + auto sinkProps = ExternalSinkProps(); + sinkProps.logHealth = true; + sinkProps.logHealthFrequency = 1000; + sink = boost::shared_ptr(new ExternalSink(sinkProps)); + mp4Reader->setNext(sink, mImagePin); + + auto p = boost::shared_ptr(new PipeLine("mp4reader")); + p->appendModule(mp4Reader); + + BOOST_TEST(mp4Reader->init()); + BOOST_TEST(sink->init()); + } + + ~SetupSeekTests() + { + mp4Reader->term(); + sink->term(); + } + + uint64_t getTSFromFileName(std::string videoPath) + { + std::string videoFileName = boost::filesystem::path(videoPath).filename().string(); + uint64_t ts = std::stoull(videoFileName.substr(0, videoFileName.find("."))); + return ts; + } + + class ExternalSinkProps : public ModuleProps + { + public: + ExternalSinkProps() : ModuleProps() + { + } + }; + class ExternalSink : public Module + { + public: + ExternalSink(ExternalSinkProps props) : Module(SINK, "ExternalSink", props) + { + } + + frame_container pop() + { + return Module::pop(); + } + + boost::shared_ptr getQue() + { + return Module::getQue(); + } + + ~ExternalSink() + { + } + + protected: + bool process(frame_container &frames) + { + //LOG_ERROR << "ExternalSinkProcess <>"; + for (const auto &pair : frames) + { + LOG_INFO << pair.first << "," << pair.second; + } + + auto frame = Module::getFrameByType(frames, FrameMetadata::FrameType::RAW_IMAGE); + if (frame) + LOG_INFO << "Timestamp <" << frame->timestamp << ">"; + // raise an event for the view + //(*mDataHandler)(); + + return true; + } + + bool validateInputPins() + { + return true; + } + + bool validateInputOutputPins() + { + return true; + } + + }; // ExternalSink + boost::shared_ptr p = nullptr; + boost::shared_ptr mp4Reader; + boost::shared_ptr sink; +}; + +uint64_t getCurrentTS() +{ + std::chrono::time_point t = std::chrono::system_clock::now(); + auto dur = std::chrono::duration_cast(t.time_since_epoch()); + uint64_t currentTime = dur.count(); + return currentTime; +} + +BOOST_AUTO_TEST_CASE(seek_read_loop) +{ + std::string startingVideoPath = "data/Mp4_videos/mp4_seek_tests/apra.mp4"; + int width = 22, height = 30; + bool parseFS = false; + bool readLoop = true; + SetupSeekTests s(startingVideoPath, width, height, 0, parseFS, readLoop); + + auto currentTS = getCurrentTS(); + + /* process one frame */ + s.mp4Reader->step(); + auto frames = s.sink->pop(); + + auto imgFrame = frames.begin()->second; + // first frame in the video is 1673855454254 + BOOST_TEST(imgFrame->timestamp >= currentTS); + + currentTS = getCurrentTS(); + uint64_t skipTS = 1673855454000; + bool ret = s.mp4Reader->randomSeek(skipTS); + BOOST_TEST(ret == true); + s.mp4Reader->step(); + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp >= currentTS); + + // seek to last frame + currentTS = getCurrentTS(); + skipTS = 1673855456243; + ret = s.mp4Reader->randomSeek(skipTS); + BOOST_TEST(ret == true); + s.mp4Reader->step(); + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp >= currentTS); + + currentTS = getCurrentTS(); + // read loop should not give EOS - it should give first frame again + s.mp4Reader->step(); + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + // first frame in the video is 1673855454254 + BOOST_TEST(imgFrame->timestamp >= currentTS); +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/base/test/mp4_reverse_play_tests.cpp b/base/test/mp4_reverse_play_tests.cpp new file mode 100644 index 000000000..48aff5cd7 --- /dev/null +++ b/base/test/mp4_reverse_play_tests.cpp @@ -0,0 +1,543 @@ +#include +#include "test_utils.h" +#include "FrameMetadata.h" +#include "FrameMetadataFactory.h" +#include "Frame.h" +#include "Logger.h" +#include "AIPExceptions.h" +#include "PipeLine.h" + +#include "Mp4ReaderSource.h" +#include "Mp4VideoMetadata.h" +#include "EncodedImageMetadata.h" +#include "StatSink.h" + +#include "FrameContainerQueue.h" + +BOOST_AUTO_TEST_SUITE(mp4_reverse_play) + +class TestModule : public Module +{ +public: + TestModule(Kind nature, string name, ModuleProps props) : Module(nature, name, props) + { + + } + + virtual ~TestModule() {} + + size_t getNumberOfOutputPins() { return Module::getNumberOfOutputPins(); } + size_t getNumberOfInputPins() { return Module::getNumberOfInputPins(); } + framemetadata_sp getFirstInputMetadata() { return Module::getFirstInputMetadata(); } + framemetadata_sp getFirstOutputMetadata() { return Module::getFirstOutputMetadata(); } + metadata_by_pin& getInputMetadata() { return Module::getInputMetadata(); } + framemetadata_sp getInputMetadataByType(int type) { return Module::getInputMetadataByType(type); } + framemetadata_sp getOutputMetadataByType(int type) { return Module::getOutputMetadataByType(type); } + int getNumberOfInputsByType(int type) { return Module::getNumberOfInputsByType(type); } + int getNumberOfOutputsByType(int type) { return Module::getNumberOfOutputsByType(type); } + bool isMetadataEmpty(framemetadata_sp& metadata) { return Module::isMetadataEmpty(metadata); } + bool isFrameEmpty(frame_sp& frame) { return Module::isFrameEmpty(frame); } + string getInputPinIdByType(int type) { return Module::getInputPinIdByType(type); } + string getOutputPinIdByType(int type) { return Module::getOutputPinIdByType(type); } + + void addInputPin(framemetadata_sp& metadata, string& pinId) { return Module::addInputPin(metadata, pinId); } // throws exception if validation fails + Connections getConnections() { return Module::getConnections(); } + + boost_deque getFrames(frame_container& frames) { return Module::getFrames(frames); } + + frame_sp makeFrame(size_t size, string pinId) { return Module::makeFrame(size, pinId); } + frame_sp makeFrame(size_t size) { return Module::makeFrame(size); } + + bool send(frame_container& frames) { return Module::send(frames); } + + boost::shared_ptr getQue() { return Module::getQue(); } + frame_sp getFrameByType(frame_container& frames, int frameType) { return Module::getFrameByType(frames, frameType); } + + ModuleProps getProps() { return Module::getProps(); } + void setProps(ModuleProps& props) { return Module::setProps(props); } + void fillProps(ModuleProps& props) { return Module::fillProps(props); } + + bool processSourceQue() { return Module::processSourceQue(); } + bool handlePausePlay(bool play) { return Module::handlePausePlay(play); } + bool getPlayState() { return Module::getPlayState(); } +}; + +class TestModuleProps : public ModuleProps +{ +public: + TestModuleProps() :ModuleProps() + { + } + TestModuleProps(int fps, size_t qlen, bool logHealth) : ModuleProps(fps, qlen, logHealth) + { + } + ~TestModuleProps() + {} +}; +class TestModule1 : public TestModule +{ +public: + TestModule1(TestModuleProps _props) : TestModule(SINK, "TestModule1", _props) + { + + } + + virtual ~TestModule1() {} + +protected: + bool validateInputPins() { return true; } +}; + +struct SetupPlaybackTests +{ + SetupPlaybackTests(std::string videoPath, + bool reInitInterval, bool direction, bool parseFS) + { + LoggerProps loggerProps; + loggerProps.logLevel = boost::log::trivial::severity_level::info; + Logger::initLogger(loggerProps); + + bool readLoop = false; + auto mp4ReaderProps = Mp4ReaderSourceProps(videoPath, parseFS, reInitInterval, direction, readLoop, false); + mp4ReaderProps.logHealth = true; + mp4ReaderProps.logHealthFrequency = 1000; + mp4ReaderProps.fps = 100; + mp4Reader = boost::shared_ptr(new Mp4ReaderSource(mp4ReaderProps)); + auto encodedImageMetadata = framemetadata_sp(new EncodedImageMetadata(0, 0)); + mp4Reader->addOutPutPin(encodedImageMetadata); + auto mp4Metadata = framemetadata_sp(new Mp4VideoMetadata("v_2_0")); + mp4Reader->addOutPutPin(mp4Metadata); + + TestModuleProps sinkProps;// (30, 100, true); + //sinkProps.logHealth = false; + sinkProps.logHealthFrequency = 1; + sink = boost::shared_ptr(new TestModule1(sinkProps)); + mp4Reader->setNext(sink); + + BOOST_TEST(mp4Reader->init()); + BOOST_TEST(sink->init()); + } + + boost::shared_ptr mp4Reader; + boost::shared_ptr sink = nullptr; +}; + +BOOST_AUTO_TEST_CASE(fwd) +{ + std::string videoPath = "data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895288956.mp4"; + SetupPlaybackTests f(videoPath, 0, true, true); + + int ct = 0, total = 601; + while (ct < total - 1) + { + f.mp4Reader->step(); + auto sinkQ = f.sink->getQue(); + auto frames = sinkQ->try_pop(); + auto frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + LOG_INFO << "frame->timestamp <" << frame->timestamp << ">"; + ct++; + } + f.mp4Reader->step(); + auto sinkQ = f.sink->getQue(); + auto frames = sinkQ->try_pop(); + auto frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1655895298961); + LOG_INFO << "frame->timestamp <" << frame->timestamp << ">"; + + // new video open + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1655919060000); +} + +BOOST_AUTO_TEST_CASE(switch_playback) +{ + std::string videoPath = "data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895288956.mp4"; + SetupPlaybackTests f(videoPath, 0, true, true); + + f.mp4Reader->step(); + auto sinkQ = f.sink->getQue(); + auto frames = sinkQ->try_pop(); + auto frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1655895288956); + + LOG_INFO << "changing playback bwd>"; + f.mp4Reader->changePlayback(1, false); + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1655895288956); + + // new video open + new file parse happens + LOG_INFO << "new video opens"; + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1655895165230); + + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1655895165215); + + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1655895165200); + + LOG_INFO << "chaning playback fwd>"; + f.mp4Reader->changePlayback(1, true); + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1655895165200); + + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1655895165215); + + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1655895165230); + + // new video open + LOG_INFO << "new video opens<><>"; + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1655895288956); + LOG_INFO << "1 frame->timestamp <" << frame->timestamp << ">"; + + int nFramesInOpenVideo = 601, count = 1; + while (count < nFramesInOpenVideo - 1) + { + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + LOG_TRACE << "frameIdx/total <" << count << "/" << nFramesInOpenVideo << ">"; + LOG_TRACE << "frame->timestamp <" << frame->timestamp << ">"; + ++count; + } + // last frame of open video + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1655895298961); + + // new video open + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1655919060000); +} + +BOOST_AUTO_TEST_CASE(video_coverage) +{ + std::string videoPath = "data/Mp4_videos/mp4_seek_tests/20220522/0023/1655919060000.mp4"; + SetupPlaybackTests f(videoPath, 0, true, true); + + /* forward playback verification */ + f.mp4Reader->step(); + auto sinkQ = f.sink->getQue(); + auto frames = sinkQ->try_pop(); + auto frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1655919060000); + + int nFramesInOpenVideo = 1270, count = 1; + while (count < nFramesInOpenVideo - 1) + { + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + LOG_TRACE << "frameIdx/total <" << count << "/" << nFramesInOpenVideo << ">"; + LOG_TRACE << "frame->timestamp <" << frame->timestamp << ">"; + ++count; + } + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == (1655919060000 + 21136)); + + /* backward playback verification */ + LOG_INFO << "changing playback bwd>"; + f.mp4Reader->changePlayback(1, false); + f.mp4Reader->step(); + sinkQ = f.sink->getQue(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == (1655919060000 + 21136)); + + f.mp4Reader->step(); + sinkQ = f.sink->getQue(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == (1655919060000 + 21120)); + + nFramesInOpenVideo = 1270, count = 2; + while (count < nFramesInOpenVideo - 1) + { + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + LOG_TRACE << "frameIdx/total <" << count << "/" << nFramesInOpenVideo << ">"; + LOG_TRACE << "frame->timestamp <" << frame->timestamp << ">"; + ++count; + } + // first frame + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1655919060000); + + // new (prev) video open + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1655895298961); +} + +BOOST_AUTO_TEST_CASE(seek_in_revPlayback_prev_hr) +{ + std::string videoPath = "data/Mp4_videos/mp4_seek_tests/20220522/0023/1655919060000.mp4"; + bool direction = false; + + SetupPlaybackTests f(videoPath, 0, direction, true); + auto sinkQ = f.sink->getQue(); + + // last frame + f.mp4Reader->step(); + auto frames = sinkQ->try_pop(); + auto frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1655919060000 + 21136); + + // 2nd frame is at 1655919060015 + f.mp4Reader->randomSeek(1655919060009, false); + f.mp4Reader->step(); + + // first frame + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1655919060000); + + // new (prev) video open + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1655895298961); +} + +BOOST_AUTO_TEST_CASE(seek_in_revPlayback_prev_day) +{ + std::string videoPath = "data/Mp4_videos/mp4_seek_tests/20220523/0001/1655926320000.mp4"; + bool direction = false; + + SetupPlaybackTests f(videoPath, 0, direction, true); + auto sinkQ = f.sink->getQue(); + + // last frame + f.mp4Reader->step(); + auto frames = sinkQ->try_pop(); + auto frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1655926320000 + 59980); + + f.mp4Reader->randomSeek(1655926320009, false); + f.mp4Reader->step(); + + // first frame + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1655926320000); + + // last frame of the new (prev) video open + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1655919060000 + 21136); +} + +BOOST_AUTO_TEST_CASE(seek_in_revPlay_prev_hr) +{ + std::string videoPath = "data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895162221.mp4"; + bool direction = false; + + SetupPlaybackTests f(videoPath, 0, direction, true); + auto sinkQ = f.sink->getQue(); + + // last frame + f.mp4Reader->step(); + auto frames = sinkQ->try_pop(); + auto frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame != nullptr); + //BOOST_TEST(frame->timestamp == 1655919060000 + 21136); + + f.mp4Reader->randomSeek(1655895299961, false); + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame != nullptr); + BOOST_TEST(frame->timestamp == 1655895298961); +} + +BOOST_AUTO_TEST_CASE(seek_in_revPlay_fail_to_seek_infile_restore) +{ + std::string videoPath = "data/Mp4_videos/mp4_seek_tests/20220522/0023/1655919060000.mp4"; + bool direction = false; + + SetupPlaybackTests f(videoPath, 0, direction, true); + auto sinkQ = f.sink->getQue(); + + // last frame + f.mp4Reader->step(); + auto frames = sinkQ->try_pop(); + auto frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame != nullptr); + BOOST_TEST(frame->timestamp == 1655919060000 + 21136); + + // nothing further on disk + f.mp4Reader->randomSeek(1655895162000, false); + f.mp4Reader->step(); + frames = sinkQ->pop(); + BOOST_TEST(frames.begin()->second->isEOS()); + auto eosFrame = dynamic_cast(frames.begin()->second.get()); + auto type = eosFrame->getEoSFrameType(); + BOOST_TEST(type == EoSFrame::EoSFrameType::MP4_SEEK_EOS); + + // last frame of the new (prev) video open + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1655919060000 + 21120); +} + +void printCache(std::map >& snap) +{ + LOG_INFO << "============printing cache=============="; + for (auto it = snap.begin(); it != snap.end(); ++it) + { + LOG_INFO << it->first << ": <" << it->second.first << "> <" << it->second.second << ">"; + } + LOG_INFO << "============printing cache FIN=============="; +} + +BOOST_AUTO_TEST_CASE(seek_dir_change_trig_fresh_parse) +{ + std::string videoPath = "data/Mp4_videos/mp4_seek_tests/20220523/0001/1655926320000.mp4"; + bool direction = true; + + SetupPlaybackTests f(videoPath, 0, direction, true); + auto sinkQ = f.sink->getQue(); + + // last frame // first + f.mp4Reader->step(); + auto frames = sinkQ->try_pop(); + auto frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame != nullptr); + //BOOST_TEST(frame->timestamp == 1655919060000 + 21136); + + // fourth/last + f.mp4Reader->randomSeek(1655926320000 + 5, false); + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame != nullptr); + BOOST_TEST(frame->timestamp == 1655926320016); + + auto snap = f.mp4Reader->getCacheSnapShot(); + printCache(snap); + + // bwd seek -- first + f.mp4Reader->play(1, false); + f.mp4Reader->randomSeek(1655895162221 + 2, false); + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame != nullptr); + BOOST_TEST(frame->timestamp == 1655895162221); + + snap = f.mp4Reader->getCacheSnapShot(); + printCache(snap); + + // change direction - fwd - seek into --second + f.mp4Reader->play(1, true); + f.mp4Reader->randomSeek(1655895288956, false); // use play + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame != nullptr); + BOOST_TEST(frame->timestamp == 1655895288956); + + snap = f.mp4Reader->getCacheSnapShot(); + printCache(snap); + + // change direction - bwd - seek into --second + f.mp4Reader->play(1, false); + f.mp4Reader->randomSeek(1655895288956 + 10, false); // use play + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame != nullptr); + BOOST_TEST(frame->timestamp == 1655895288956); +} + +BOOST_AUTO_TEST_CASE(step_only_parse_disabled_video_cov_with_reinitInterval) +{ + /* + video coverage test i.e. [st -> end (eof) | direction change | end -> st (eof)] + parse disabled + using only step + with reinitInterval + */ + std::string videoPath = "data/Mp4_videos/mp4_seek_tests/apra.mp4"; + SetupPlaybackTests f(videoPath, 10, true, false); + + /* forward playback verification */ + f.mp4Reader->step(); + auto sinkQ = f.sink->getQue(); + auto frames = sinkQ->try_pop(); + auto frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + BOOST_TEST(frame->timestamp == 1673855454254); + + while (1) + { + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = frames.begin()->second; + if (frame->isEOS()) + { + auto eosFrame = dynamic_cast(frame.get()); + BOOST_TEST(eosFrame->getEoSFrameType() == EoSFrame::EoSFrameType::MP4_PLYB_EOS); + break; + } + } + LOG_INFO << "Reached EOF !"; + + /* backward playback verification */ + LOG_INFO << "changing playback bwd>"; + uint64_t lastFrameTS = 0; + f.mp4Reader->changePlayback(1, false); + while (1) + { + f.mp4Reader->step(); + frames = sinkQ->try_pop(); + frame = frames.begin()->second; + if (frame->isEOS()) + { + auto eosFrame = dynamic_cast(frame.get()); + BOOST_TEST(eosFrame->getEoSFrameType() == EoSFrame::EoSFrameType::MP4_PLYB_EOS); + break; + } + else + { + lastFrameTS = frame->timestamp; + } + } + LOG_INFO << "Reached EOF !"; + BOOST_TEST(lastFrameTS == 1673855454254); +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/base/test/mp4_seek_tests.cpp b/base/test/mp4_seek_tests.cpp new file mode 100644 index 000000000..0ac46db14 --- /dev/null +++ b/base/test/mp4_seek_tests.cpp @@ -0,0 +1,1299 @@ + +#include +#include "test_utils.h" +#include "FrameMetadata.h" +#include "FrameMetadataFactory.h" +#include "Frame.h" +#include "Logger.h" +#include "AIPExceptions.h" +#include "PipeLine.h" + +#include "Mp4ReaderSource.h" +#include "Mp4VideoMetadata.h" +#include "EncodedImageMetadata.h" +#include "FrameContainerQueue.h" +#include "H264Metadata.h" + +BOOST_AUTO_TEST_SUITE(mp4_seek_tests) + +struct SetupSeekTests +{ + SetupSeekTests(std::string& startingVideoPath, int reInitInterval, bool parseFS, bool readLoop, FrameMetadata::FrameType frameType) + { + framemetadata_sp imageMetadata; + LoggerProps loggerProps; + loggerProps.logLevel = boost::log::trivial::severity_level::info; + Logger::initLogger(loggerProps); + + auto mp4ReaderProps = Mp4ReaderSourceProps(startingVideoPath, parseFS, reInitInterval, true, readLoop, false); + mp4Reader = boost::shared_ptr(new Mp4ReaderSource(mp4ReaderProps)); + if (frameType == FrameMetadata::FrameType::ENCODED_IMAGE) + { + imageMetadata = framemetadata_sp(new EncodedImageMetadata(0, 0)); + } + else if(frameType == FrameMetadata::FrameType::H264_DATA) + { + imageMetadata = framemetadata_sp(new H264Metadata(0, 0)); + } + auto encodedImagePin = mp4Reader->addOutPutPin(imageMetadata); + auto mp4Metadata = framemetadata_sp(new Mp4VideoMetadata("v_2_0")); + auto mp4MetadataPin = mp4Reader->addOutPutPin(mp4Metadata); + + auto sinkProps = ExternalSinkProps(); + sinkProps.logHealth = true; + sinkProps.logHealthFrequency = 1000; + sink = boost::shared_ptr(new ExternalSink(sinkProps)); + mp4Reader->setNext(sink, true, false); + + auto p = boost::shared_ptr(new PipeLine("mp4reader")); + p->appendModule(mp4Reader); + + BOOST_TEST(mp4Reader->init()); + BOOST_TEST(sink->init()); + } + + ~SetupSeekTests() + { + mp4Reader->term(); + sink->term(); + } + + uint64_t getTSFromFileName(std::string videoPath) + { + std::string videoFileName = boost::filesystem::path(videoPath).filename().string(); + uint64_t ts = std::stoull(videoFileName.substr(0, videoFileName.find("."))); + return ts; + } + + class ExternalSinkProps : public ModuleProps + { + public: + ExternalSinkProps() : ModuleProps() + { + } + }; + class ExternalSink : public Module + { + public: + ExternalSink(ExternalSinkProps props) : Module(SINK, "ExternalSink", props) + { + } + + frame_container pop() + { + return Module::pop(); + } + + boost::shared_ptr getQue() + { + return Module::getQue(); + } + + ~ExternalSink() + { + } + + protected: + bool process(frame_container& frames) + { + //LOG_ERROR << "ExternalSinkProcess <>"; + for (const auto& pair : frames) + { + LOG_INFO << pair.first << "," << pair.second; + } + + auto frame = Module::getFrameByType(frames, FrameMetadata::FrameType::RAW_IMAGE); + if (frame) + LOG_INFO << "Timestamp <" << frame->timestamp << ">"; + // raise an event for the view + //(*mDataHandler)(); + + return true; + } + + bool validateInputPins() + { + return true; + } + + bool validateInputOutputPins() + { + return true; + } + + }; // ExternalSink + boost::shared_ptr p = nullptr; + boost::shared_ptr mp4Reader; + boost::shared_ptr sink; +}; + +BOOST_AUTO_TEST_CASE(no_seek) +{ + std::string startingVideoPath = "data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895162221.mp4"; + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::ENCODED_IMAGE); + + s.mp4Reader->step(); + auto frames = s.sink->pop(); + auto imgFrame = frames.begin()->second; + // first frame ts should be same as filename + auto ts = s.getTSFromFileName(startingVideoPath); + BOOST_TEST(imgFrame->timestamp == ts); +} + +BOOST_AUTO_TEST_CASE(seek_in_current_file) +{ + /* video length is 3 seconds */ + std::string startingVideoPath = "data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895162221.mp4"; + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::ENCODED_IMAGE); + + s.mp4Reader->step(); + + auto frames = s.sink->pop(); + auto imgFrame = frames.begin()->second; + + // seek 1 sec inside the file currently being read by mp4Reader + uint64_t skipTS = 1655895163221; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + + frames = s.sink->pop(); + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1655895163229); + LOG_INFO << "Found next available frame " << imgFrame->timestamp - skipTS << " msecs later from skipTS"; + + // lets check the next frame also - used in seek_eof_reset_state + s.mp4Reader->step(); + frames = s.sink->pop(); + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1655895163245); + LOG_INFO << "Next frame in sequence " << imgFrame->timestamp - 1655895163229 << " msecs later from last frame"; +} + +BOOST_AUTO_TEST_CASE(seek_in_next_file) +{ + std::string startingVideoPath = "data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895162221.mp4"; + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::ENCODED_IMAGE); + + /* process one frame */ + s.mp4Reader->step(); + + auto frames = s.sink->pop(); + + auto imgFrame = frames.begin()->second; + + /* ts of first frame of next file is 1655895288956 - video length 10secs. + Seek 5 sec inside the file which is next to the currently open file. */ + uint64_t skipTS = 1655895293956; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1655895293966); + LOG_INFO << "Found next available frame " << imgFrame->timestamp - skipTS << " msecs later from skipTS"; +} + +BOOST_AUTO_TEST_CASE(seek_in_file_in_next_hr) +{ + /* seek to frame inside the file in next hr */ + std::string startingVideoPath = "data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895162221.mp4"; + + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::ENCODED_IMAGE); + + /* process one frame */ + s.mp4Reader->step(); + + auto frames = s.sink->pop(); + auto imgFrame = frames.begin()->second; + + /* ts of first frame of seeked file is 1655919060000, video length 21secs. + Seek 20 sec inside this file. */ + uint64_t skipTS = 1655919080000; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1655919080010); + LOG_INFO << "Found next available frame " << imgFrame->timestamp - skipTS << " msecs later from skipTS"; +} + +BOOST_AUTO_TEST_CASE(seek_in_file_in_next_day) +{ + /* seek to frame inside the file in next day */ + std::string startingVideoPath = "data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895162221.mp4"; + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::ENCODED_IMAGE); + + /* process one frame */ + s.mp4Reader->step(); + + auto frames = s.sink->pop(); + + auto imgFrame = frames.begin()->second; + + /* ts of first frame of seeked file is 1655926320000, video length 60secs. + Seek 57 sec inside this file. */ + uint64_t skipTS = 1655926377000; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1655926377014); + LOG_INFO << "Found next available frame " << imgFrame->timestamp - skipTS << " msecs later from skipTS"; +} + +BOOST_AUTO_TEST_CASE(seek_fails_no_reset) +{ + /* the last video of the same hour as the skipTS is not long enough + expectation is that seek will fail and continue to find the next available frame since its not EOF */ + std::string startingVideoPath = "data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895162221.mp4"; + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::ENCODED_IMAGE); + + /* process one frame */ + s.mp4Reader->step(); + + auto frames = s.sink->pop(); + auto imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == s.getTSFromFileName(startingVideoPath)); + + uint64_t skipTS = 1655895400956; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + // expectation - the seeked to ts is the ts of the first frame of next video + BOOST_TEST(imgFrame->timestamp == 1655919060000); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; +} + +BOOST_AUTO_TEST_CASE(hr_missing_next_avl_hr) +{ + /* no recording for the hour exists - move to next available hour */ + std::string startingVideoPath = "data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895162221.mp4"; + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::ENCODED_IMAGE); + + LOG_INFO << "current open video before seek <" << s.mp4Reader->getOpenVideoPath() << ">"; + /* process one frame */ + s.mp4Reader->step(); + LOG_INFO << "current open video after step but before seek <" << s.mp4Reader->getOpenVideoPath() << ">"; + + auto frames = s.sink->pop(); + + auto imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == s.getTSFromFileName(startingVideoPath)); + + uint64_t skipTS = 1655898288000; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + LOG_INFO << "current video after seek <" << s.mp4Reader->getOpenVideoPath() << ">"; + frames = s.sink->pop(); + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1655919060000); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; +} + +BOOST_AUTO_TEST_CASE(hr_missing_next_avl_day) +{ + /* no recording for the day exists - move to next available day */ + std::string startingVideoPath = "data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895162221.mp4"; + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::ENCODED_IMAGE); + + /* process one frame */ + s.mp4Reader->step(); + auto frames = s.sink->pop(); + + auto imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == s.getTSFromFileName(startingVideoPath)); + + uint64_t skipTS = 1655898288000; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1655919060000); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; +} + +BOOST_AUTO_TEST_CASE(missing_past_day_seek) +{ + /* no recording for the past day exists - move to next available day i.e. first frame in recordings */ + std::string startingVideoPath = "data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895162221.mp4"; + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::ENCODED_IMAGE); + + /* process one frame */ + s.mp4Reader->step(); + auto frames = s.sink->pop(); + + auto imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == s.getTSFromFileName(startingVideoPath)); + + uint64_t skipTS = 1655805162000; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == s.getTSFromFileName(startingVideoPath)); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; +} + +BOOST_AUTO_TEST_CASE(seek_fail_eof_reset_state) +{ + /* seek beyond the last frame of of the last video - expectation is that + seek will fail and reset the state to pre-seek state.*/ + std::string startingVideoPath = "data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895162221.mp4"; + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::ENCODED_IMAGE); + + /* process one frame */ + s.mp4Reader->step(); + + auto frames = s.sink->pop(); + auto imgFrame = frames.begin()->second; + + // first seek 1 sec inside current file + uint64_t skipTS = 1655895163221; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1655895163229); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + + // then seek beyond eof + skipTS = 1655926444000; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); // preprocess command + produce + + frames = s.sink->pop(); + auto frame = frames.begin(); + BOOST_TEST(frame->second->isEOS()); + auto eosFrame = dynamic_cast(frame->second.get()); + auto type = eosFrame->getEoSFrameType(); + BOOST_TEST(type == EoSFrame::EoSFrameType::MP4_SEEK_EOS); + + // next step should give us the frame from resumed mp4Reader state + s.mp4Reader->step(); + + frames = s.sink->pop(); + imgFrame = frames.begin()->second; + // expectation - the seeked to ts is equal to original state before seek i.e. read the next frame in the old sequence + BOOST_TEST(imgFrame->timestamp == 1655895163245); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + + // one more seek + s.mp4Reader->step(); + + frames = s.sink->pop(); + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1655895163260); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; +} + +BOOST_AUTO_TEST_CASE(seek_to_last_frame) +{ + /* seek to the exact last frame - next and exact match both */ + std::string startingVideoPath = "data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895162221.mp4"; + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::ENCODED_IMAGE); + + /* process one frame */ + s.mp4Reader->step(); + + auto frames = s.sink->pop(); + + auto imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == s.getTSFromFileName(startingVideoPath)); + + // seek to the last frame in the file + uint64_t skipTS = 1655895165228; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + + frames = s.sink->pop(); + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1655895165230); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + + // seek to the last frame in the file -- exact timestamp + skipTS = 1655895165230; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + + frames = s.sink->pop(); + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1655895165230); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; +} + +BOOST_AUTO_TEST_CASE(reach_eof_do_eos_then_seek) +{ + /* seek to last frame of the recordings, after processing it, we should reach EOF */ + std::string startingVideoPath = "data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895162221.mp4"; + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::ENCODED_IMAGE); + + /* process one frame */ + s.mp4Reader->step(); + + auto frames = s.sink->pop(); + + auto imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == s.getTSFromFileName(startingVideoPath)); + + uint64_t skipTS = 1655926379960; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1655926379980); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + // reached eof - next step should get us EOS frame + bool ret = s.mp4Reader->step(); + + frames = s.sink->pop(); + auto frame = frames.begin(); + BOOST_TEST(frame->second->isEOS()); + auto eosFrame = dynamic_cast(frame->second.get()); + auto type = eosFrame->getEoSFrameType(); + BOOST_TEST(type == EoSFrame::EoSFrameType::MP4_PLYB_EOS); + + // mp4Reader should be allowed to seek though + // seek should work even after reaching EOF + skipTS = 1655898288000; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + + frames = s.sink->pop(); + frame = frames.begin(); + BOOST_TEST(frame->second->isEOS() == false); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1655919060000); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; +} + +BOOST_AUTO_TEST_CASE(refresh_last_file_on_seek) +{ + /* seek to the exact last video twice - make sure video is reopened every time we seek to last video in cache (fwd only) */ + std::string startingVideoPath = "data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895162221.mp4"; + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::ENCODED_IMAGE); + + /* process one frame */ + s.mp4Reader->step(); + + auto frames = s.sink->pop(); + + auto imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == s.getTSFromFileName(startingVideoPath)); + + // seek to the last video in cache + uint64_t skipTS = 1655926320000; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + + frames = s.sink->pop(); + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1655926320000); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + + // seek to the last video in cache again & make sure it refreshes + skipTS = 1655926320000 + 10; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + + frames = s.sink->pop(); + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1655926320016); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + + // seek to second last video and make sure file is not reopened + skipTS = 1655919060000; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + + frames = s.sink->pop(); + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1655919060000); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; +} + +BOOST_AUTO_TEST_CASE(seek_with_parseFS_disabled) +{ + std::string startingVideoPath = "data/Mp4_videos/mp4_seek_tests/apra.mp4"; + bool parseFS = false; + SetupSeekTests s(startingVideoPath, 0, parseFS, false, FrameMetadata::ENCODED_IMAGE); + + /* process one frame */ + s.mp4Reader->step(); + + auto frames = s.sink->pop(); + auto imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1673855454254); + + /* before first frame - go to first frame */ + uint64_t skipTS = 1673855454000; + bool ret = s.mp4Reader->randomSeek(skipTS, false); + BOOST_TEST(ret == true); + s.mp4Reader->step(); + + frames = s.sink->pop(); + imgFrame = frames.begin()->second; + // first frame is 1673855454254 + BOOST_TEST(imgFrame->timestamp == 1673855454254); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + + /* basic case - bw start and end */ + skipTS = 1673855454900; + ret = s.mp4Reader->randomSeek(skipTS, false); + BOOST_TEST(ret == true); + s.mp4Reader->step(); + + frames = s.sink->pop(); + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1673855454900); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + + /* beyond EOF - returns true - should resume from before seek state */ + skipTS = 1673855454254 + 5000; + ret = s.mp4Reader->randomSeek(skipTS, false); + BOOST_TEST(ret == true); + s.mp4Reader->step(); + + frames = s.sink->pop(); + BOOST_TEST(frames.begin()->second->isEOS()); + auto eosFrame = dynamic_cast(frames.begin()->second.get()); + auto type = eosFrame->getEoSFrameType(); + BOOST_TEST(type == EoSFrame::EoSFrameType::MP4_SEEK_EOS); + + //step to get next frame from resumed state + s.mp4Reader->step(); + + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1673855454901); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + + skipTS = 1673855454254 + 200; + ret = s.mp4Reader->randomSeek(skipTS, false); + BOOST_TEST(ret == true); + s.mp4Reader->step(); + + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1673855454462); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + + /* reach eof */ + uint64_t lastTS = 0; + while (true) + { + s.mp4Reader->step(); + + + frames = s.sink->pop(); + if (frames.begin()->second->isEOS()) + { + auto eosFrame = dynamic_cast(frames.begin()->second.get()); + auto type = eosFrame->getEoSFrameType(); + BOOST_TEST(type == EoSFrame::EoSFrameType::MP4_PLYB_EOS); + break; + } + imgFrame = frames.begin()->second; + lastTS = imgFrame->timestamp; + } + BOOST_TEST((lastTS == 1673855456243)); + LOG_INFO << "Reached EOF!"; + + // important: seeking inside this file should allow us to step through it again + LOG_INFO << "Seeking after reaching EOF!!"; + skipTS = 1673855454462; + ret = s.mp4Reader->randomSeek(skipTS, false); + BOOST_TEST(ret == true); + s.mp4Reader->step(); + + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1673855454462); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + + /* reach eof again */ + lastTS = 0; + while (true) + { + s.mp4Reader->step(); + + + frames = s.sink->pop(); + if (frames.begin()->second->isEOS()) + { + auto eosFrame = dynamic_cast(frames.begin()->second.get()); + auto type = eosFrame->getEoSFrameType(); + BOOST_TEST(type == EoSFrame::EoSFrameType::MP4_PLYB_EOS); + break; + } + imgFrame = frames.begin()->second; + lastTS = imgFrame->timestamp; + } + BOOST_TEST((lastTS == 1673855456243)); + LOG_INFO << "Reached EOF!"; +} + +BOOST_AUTO_TEST_CASE(read_loop) +{ + std::string startingVideoPath = "data/Mp4_videos/mp4_seek_tests/apra.mp4"; + bool parseFS = false; + bool readLoop = true; + SetupSeekTests s(startingVideoPath, 0, parseFS, readLoop, FrameMetadata::ENCODED_IMAGE); + + /* process one frame */ + s.mp4Reader->step(); + + + auto frames = s.sink->pop(); + + + auto imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1673855454254); + + /* before first frame - go to first frame */ + uint64_t skipTS = 1673855454000; + bool ret = s.mp4Reader->randomSeek(skipTS, false); + BOOST_TEST(ret == true); + s.mp4Reader->step(); + + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + // first frame is 1673855454254 + BOOST_TEST(imgFrame->timestamp == 1673855454254); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + + // seek to last frame + skipTS = 1673855456243; + ret = s.mp4Reader->randomSeek(skipTS, false); + BOOST_TEST(ret == true); + s.mp4Reader->step(); + + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1673855456243); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + + // read loop should not give EOS - it should give first frame again + s.mp4Reader->step(); + + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + // first frame is 1673855454254 + BOOST_TEST(imgFrame->timestamp == 1673855454254); + + // read till end again + while (1) + { + s.mp4Reader->step(); + + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + if (imgFrame->timestamp == 1673855456243) + { + break; + } + } + + // read loop should not give EOS - it should give first frame again + s.mp4Reader->step(); + + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + // first frame is 1673855454254 + BOOST_TEST(imgFrame->timestamp == 1673855454254); +} + +// H264 seek test + +BOOST_AUTO_TEST_CASE(seek_in_current_file_h264) +{ + /* video length is 3 seconds */ + std::string startingVideoPath = "data/Mp4_videos/mp4_seeks_tests_h264/20230501/0013/1685604896179.mp4"; + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::H264_DATA); + + s.mp4Reader->step(); + + auto frames = s.sink->pop(); + auto imgFrame = frames.begin()->second; + + // seek 1 sec inside the file currently being read by mp4Reader + uint64_t skipTS = 1685604897179; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + + frames = s.sink->pop(); + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1685604897188); + LOG_INFO << "Found next available frame " << imgFrame->timestamp - skipTS << " msecs later from skipTS"; + + // lets check the next frame also - used in seek_eof_reset_state + s.mp4Reader->step(); + frames = s.sink->pop(); + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1685604897218); + LOG_INFO << "Next frame in sequence " << imgFrame->timestamp - 1685604897188 << " msecs later from last frame"; +} + +BOOST_AUTO_TEST_CASE(seek_in_next_file_h264) +{ + std::string startingVideoPath = "data/Mp4_videos/mp4_seeks_tests_h264/20230501/0012/1685604318680.mp4"; + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::H264_DATA); + + /* process one frame */ + s.mp4Reader->step(); + + auto frames = s.sink->pop(); + + auto imgFrame = frames.begin()->second; + + /* ts of first frame of next file is 1655895288956 - video length 5secs. + Seek 3 sec inside the file which is next to the currently open file. */ + uint64_t skipTS = 1685604364723; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1685604365527); + LOG_INFO << "Found next available frame " << imgFrame->timestamp - skipTS << " msecs later from skipTS"; +} + +BOOST_AUTO_TEST_CASE(seek_in_file_in_next_hr_h264) +{ + /* seek to frame inside the file in next hr */ + std::string startingVideoPath = "data/Mp4_videos/mp4_seeks_tests_h264/20230501/0012/1685604318680.mp4"; + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::H264_DATA); + + /* process one frame */ + s.mp4Reader->step(); + + auto frames = s.sink->pop(); + auto imgFrame = frames.begin()->second; + + /* ts of first frame of seeked file is 1655919060000, video length 3secs. + Seek 1 sec inside this file. */ + uint64_t skipTS = 1685604897179; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1685604897188); + LOG_INFO << "Found next available frame " << imgFrame->timestamp - skipTS << " msecs later from skipTS"; +} + +BOOST_AUTO_TEST_CASE(seek_in_file_in_next_day_h264) +{ + /* seek to frame inside the file in next day */ + std::string startingVideoPath = "data/Mp4_videos/mp4_seeks_tests_h264/20230111/0012/1673420640350.mp4"; + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::H264_DATA); + + /* process one frame */ + s.mp4Reader->step(); + + auto frames = s.sink->pop(); + + auto imgFrame = frames.begin()->second; + + /* ts of first frame of seeked file is 1685604318680, video length 60secs. + Seek 3 sec inside this file. */ + uint64_t skipTS = 1685604321680; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1685604322484); + LOG_INFO << "Found next available frame " << imgFrame->timestamp - skipTS << " msecs later from skipTS"; +} + +BOOST_AUTO_TEST_CASE(seek_fails_no_reset_h264) +{ + /* the last video of the same hour as the skipTS is not long enough + expectation is that seek will fail and continue to find the next available frame since its not EOF */ + std::string startingVideoPath = "data/Mp4_videos/mp4_seeks_tests_h264/20230501/0012/1685604318680.mp4"; + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::H264_DATA); + + /* process one frame */ + s.mp4Reader->step(); + + auto frames = s.sink->pop(); + auto imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == s.getTSFromFileName(startingVideoPath)); + + uint64_t skipTS = 1685604391723; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + // expectation - the seeked to ts is the ts of the first frame of next video + BOOST_TEST(imgFrame->timestamp == 1685604896179); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; +} + +BOOST_AUTO_TEST_CASE(hr_missing_next_avl_hr_h264) +{ + /* no recording for the hour exists - move to next available hour */ + std::string startingVideoPath = "data/Mp4_videos/mp4_seeks_tests_h264/20230501/0012/1685604318680.mp4"; + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::H264_DATA); + + LOG_INFO << "current open video before seek <" << s.mp4Reader->getOpenVideoPath() << ">"; + /* process one frame */ + s.mp4Reader->step(); + LOG_INFO << "current open video after step but before seek <" << s.mp4Reader->getOpenVideoPath() << ">"; + + auto frames = s.sink->pop(); + + auto imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == s.getTSFromFileName(startingVideoPath)); + + uint64_t skipTS = 1685604395723; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + LOG_INFO << "current video after seek <" << s.mp4Reader->getOpenVideoPath() << ">"; + frames = s.sink->pop(); + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1685604896179); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; +} + +BOOST_AUTO_TEST_CASE(missing_past_day_seek_h264) +{ + /* no recording for the past day exists - move to next available day i.e. first frame in recordings */ + std::string startingVideoPath = "data/Mp4_videos/mp4_seeks_tests_h264/20230111/0012/1673420640350.mp4"; + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::H264_DATA); + + /* process one frame */ + s.mp4Reader->step(); + auto frames = s.sink->pop(); + + auto imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == s.getTSFromFileName(startingVideoPath)); + + uint64_t skipTS = 1673350540350; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == s.getTSFromFileName(startingVideoPath)); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; +} + +BOOST_AUTO_TEST_CASE(seek_fail_eof_reset_state_h264) +{ + /* seek beyond the last frame of of the last video - expectation is that + seek will fail and reset the state to pre-seek state.*/ + std::string startingVideoPath = "data/Mp4_videos/mp4_seeks_tests_h264/20230501/0012/1685604318680.mp4"; + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::H264_DATA); + + /* process one frame */ + s.mp4Reader->step(); + + auto frames = s.sink->pop(); + auto imgFrame = frames.begin()->second; + + // first seek 1 sec inside current file + uint64_t skipTS = 1685604319680; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1685604319692); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + + // then seek beyond eof + skipTS = 1685605896179; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); // preprocess command + produce + + frames = s.sink->pop(); + auto frame = frames.begin(); + BOOST_TEST(frame->second->isEOS()); + auto eosFrame = dynamic_cast(frame->second.get()); + auto type = eosFrame->getEoSFrameType(); + BOOST_TEST(type == EoSFrame::EoSFrameType::MP4_SEEK_EOS); + + // next step should give us the frame from resumed mp4Reader state + s.mp4Reader->step(); + + frames = s.sink->pop(); + imgFrame = frames.begin()->second; + // expectation - the seeked to ts is equal to original state before seek i.e. read the next frame in the old sequence + BOOST_TEST(imgFrame->timestamp == 1685604319721); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + + // one more seek + s.mp4Reader->step(); + + frames = s.sink->pop(); + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1685604319753); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; +} + +BOOST_AUTO_TEST_CASE(seek_to_last_frame_h264) +{ + /* seek to the exact last frame - next and exact match both */ + std::string startingVideoPath = "data/Mp4_videos/mp4_seeks_tests_h264/20230501/0012/1685604361723.mp4"; + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::H264_DATA); + + /* process one frame */ + s.mp4Reader->step(); + + auto frames = s.sink->pop(); + + auto imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == s.getTSFromFileName(startingVideoPath)); + + // seek to the last frame in the file , as the last frame in the file is P frame , it opens the next posisble video + uint64_t skipTS = 1685604368100; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + + frames = s.sink->pop(); + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1685604896179); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; +} + +BOOST_AUTO_TEST_CASE(reach_eof_do_eos_then_seek_h264) +{ + /* seek to last frame of the recordings, after processing it, we should reach EOF */ + std::string startingVideoPath = "data/Mp4_videos/mp4_seeks_tests_h264/20230501/0012/1685604318680.mp4"; + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::H264_DATA); + + /* process one frame */ + s.mp4Reader->step(); + + auto frames = s.sink->pop(); + + auto imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == s.getTSFromFileName(startingVideoPath)); + + uint64_t skipTS = 1685604898878; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1685604898979); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + for(int i = 0 ; i < 7; i++) + { + s.mp4Reader->step(); + + frames = s.sink->pop(); + } + + // reached eof - next step should get us EOS frame + auto frame = frames.begin(); + BOOST_TEST(frame->second->isEOS()); + auto eosFrame = dynamic_cast(frame->second.get()); + auto type = eosFrame->getEoSFrameType(); + BOOST_TEST(type == EoSFrame::EoSFrameType::MP4_PLYB_EOS); + + // mp4Reader should be allowed to seek though + // seek should work even after reaching EOF + skipTS = 1685604898878; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + + frames = s.sink->pop(); + frame = frames.begin(); + BOOST_TEST(frame->second->isEOS() == false); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1685604898979); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; +} + +BOOST_AUTO_TEST_CASE(refresh_last_file_on_seek_h264) +{ + /* seek to the exact last video twice - make sure video is reopened every time we seek to last video in cache (fwd only) */ + std::string startingVideoPath = "data/Mp4_videos/mp4_seeks_tests_h264/20230111/0012/1673420640350.mp4"; + SetupSeekTests s(startingVideoPath, 0, true, false, FrameMetadata::H264_DATA); + + /* process one frame */ + s.mp4Reader->step(); + + auto frames = s.sink->pop(); + + auto imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == s.getTSFromFileName(startingVideoPath)); + + // seek to the last video in cache + uint64_t skipTS = 1685604896179; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + + frames = s.sink->pop(); + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1685604896179); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + + // seek to the last video in cache again & make sure it refreshes + skipTS = 1685604896179 + 100; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + + frames = s.sink->pop(); + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1685604897188); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + + // seek to second last video and make sure file is not reopened + skipTS = 1685604899000; + s.mp4Reader->randomSeek(skipTS, false); + s.mp4Reader->step(); + + frames = s.sink->pop(); + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1685604898979); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; +} + +BOOST_AUTO_TEST_CASE(seek_with_parseFS_disabled_h264) +{ + std::string startingVideoPath = "data/Mp4_videos/mp4_seeks_tests_h264/apraH264.mp4"; + bool parseFS = false; + SetupSeekTests s(startingVideoPath, 0, parseFS, false, FrameMetadata::H264_DATA); + + /* process one frame */ + s.mp4Reader->step(); + + auto frames = s.sink->pop(); + auto imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1673420640350); + + /* before first frame - go to first frame */ + uint64_t skipTS = 1673420640000; + bool ret = s.mp4Reader->randomSeek(skipTS, false); + BOOST_TEST(ret == true); + s.mp4Reader->step(); + + frames = s.sink->pop(); + imgFrame = frames.begin()->second; + // first frame is 1673420640350 + BOOST_TEST(imgFrame->timestamp == 1673420640350); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + + /* basic case - bw start and end */ + skipTS = 1673420640950; + ret = s.mp4Reader->randomSeek(skipTS, false); + BOOST_TEST(ret == true); + s.mp4Reader->step(); + + frames = s.sink->pop(); + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1673420642668); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + + /* beyond EOF - returns true - should resume from before seek state */ + skipTS = 1673420640350 + 6000; + ret = s.mp4Reader->randomSeek(skipTS, false); + BOOST_TEST(ret == true); + s.mp4Reader->step(); + + frames = s.sink->pop(); + BOOST_TEST(frames.begin()->second->isEOS()); + auto eosFrame = dynamic_cast(frames.begin()->second.get()); + auto type = eosFrame->getEoSFrameType(); + BOOST_TEST(type == EoSFrame::EoSFrameType::MP4_SEEK_EOS); + + //step to get next frame from resumed state + s.mp4Reader->step(); + + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1673420642684); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + + skipTS = 1673420640350 + 200; + ret = s.mp4Reader->randomSeek(skipTS, false); + BOOST_TEST(ret == true); + s.mp4Reader->step(); + + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1673420642668); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + + /* reach eof */ + uint64_t lastTS = 0; + while (true) + { + s.mp4Reader->step(); + + + frames = s.sink->pop(); + if (frames.begin()->second->isEOS()) + { + auto eosFrame = dynamic_cast(frames.begin()->second.get()); + auto type = eosFrame->getEoSFrameType(); + BOOST_TEST(type == EoSFrame::EoSFrameType::MP4_PLYB_EOS); + break; + } + imgFrame = frames.begin()->second; + lastTS = imgFrame->timestamp; + } + BOOST_TEST((lastTS == 1673420645353)); + LOG_INFO << "Reached EOF!"; + + // important: seeking inside this file should allow us to step through it again + LOG_INFO << "Seeking after reaching EOF!!"; + skipTS = 1673420640550 ; + ret = s.mp4Reader->randomSeek(skipTS, false); + BOOST_TEST(ret == true); + s.mp4Reader->step(); + + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1673420642668); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + + /* reach eof again */ + lastTS = 0; + while (true) + { + s.mp4Reader->step(); + + + frames = s.sink->pop(); + if (frames.begin()->second->isEOS()) + { + auto eosFrame = dynamic_cast(frames.begin()->second.get()); + auto type = eosFrame->getEoSFrameType(); + BOOST_TEST(type == EoSFrame::EoSFrameType::MP4_PLYB_EOS); + break; + } + imgFrame = frames.begin()->second; + lastTS = imgFrame->timestamp; + } + BOOST_TEST((lastTS == 1673420645353)); + LOG_INFO << "Reached EOF!"; +} + +BOOST_AUTO_TEST_CASE(read_loop_h264) +{ + std::string startingVideoPath = "data/Mp4_videos/mp4_seeks_tests_h264/apraH264.mp4"; + bool parseFS = false; + bool readLoop = true; + SetupSeekTests s(startingVideoPath, 0, parseFS, readLoop, FrameMetadata::H264_DATA); + + /* process one frame */ + s.mp4Reader->step(); + + + auto frames = s.sink->pop(); + + + auto imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1673420640350); + + /* before first frame - go to first frame */ + uint64_t skipTS = 1673420640000; + bool ret = s.mp4Reader->randomSeek(skipTS, false); + BOOST_TEST(ret == true); + s.mp4Reader->step(); + + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + // first frame is 1673420640350 + BOOST_TEST(imgFrame->timestamp == 1673420640350); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + + // seek to last frame + skipTS = 1673420644850; + ret = s.mp4Reader->randomSeek(skipTS, false); + BOOST_TEST(ret == true); + s.mp4Reader->step(); + + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1673420644975); + LOG_INFO << "Found next available frame " << (int)(imgFrame->timestamp - skipTS) << " msecs later from skipTS"; + + s.mp4Reader->step(); + + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + // first I Frame is 1673420644990 + BOOST_TEST(imgFrame->timestamp == 1673420644990); + + // read till end again + while (1) + { + s.mp4Reader->step(); + + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + // last frame is 1673420645353 + if (imgFrame->timestamp == 1673420645353) + { + break; + } + } + + // read loop should not give EOS - it should give first frame again + s.mp4Reader->step(); + + + frames = s.sink->pop(); + + imgFrame = frames.begin()->second; + // first frame is 1673420640350 + BOOST_TEST(imgFrame->timestamp == 1673420640350); +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/base/test/mp4_simul_read_write_tests.cpp b/base/test/mp4_simul_read_write_tests.cpp new file mode 100644 index 000000000..f1b4a2691 --- /dev/null +++ b/base/test/mp4_simul_read_write_tests.cpp @@ -0,0 +1,1176 @@ +#include +#include +#include +#include +#include "Logger.h" + +#include "Mp4ReaderSource.h" +#include "Mp4WriterSink.h" +#include "StatSink.h" +#include "EncodedImageMetadata.h" +#include "Mp4VideoMetadata.h" +#include "FileReaderModule.h" +#include "FrameContainerQueue.h" +#include "PipeLine.h" +#include "Mp4ErrorFrame.h" +BOOST_AUTO_TEST_SUITE(mp4_simul_read_write_tests) + +class ExternalSinkProps : public ModuleProps +{ +public: + ExternalSinkProps() : ModuleProps() + { + } +}; +class ExternalSink : public Module +{ +public: + ExternalSink(ExternalSinkProps props) : Module(SINK, "ExternalSink", props) + { + } + + frame_container pop() + { + return Module::pop(); + } + + boost::shared_ptr getQue() + { + return Module::getQue(); + } + + ~ExternalSink() + { + } + +protected: + bool process(frame_container &frames) + { + //LOG_ERROR << "ExternalSinkProcess <>"; + for (const auto &pair : frames) + { + LOG_TRACE << pair.first << "," << pair.second; + } + + auto frame = Module::getFrameByType(frames, FrameMetadata::FrameType::ENCODED_IMAGE); + if (frame) + LOG_INFO << "Timestamp <" << frame->timestamp << ">"; + + return true; + } + + bool validateInputPins() + { + return true; + } + + bool validateInputOutputPins() + { + return true; + } + +}; // ExternalSink + +struct WritePipeline { + WritePipeline(std::string readFolderPath, int readfps, int width, int height, + std::string _writeOutPath, int writeChunkTime, int writeSyncTimeInSecs, int writefps) + { + LoggerProps loggerProps; + loggerProps.logLevel = boost::log::trivial::severity_level::info; + Logger::setLogLevel(boost::log::trivial::severity_level::info); + Logger::initLogger(loggerProps); + + writeOutPath = _writeOutPath; + auto fileReaderProps = FileReaderModuleProps(readFolderPath, 0, -1); + fileReaderProps.fps = readfps; + fileReaderProps.readLoop = true; + fileReader = boost::shared_ptr(new FileReaderModule(fileReaderProps)); + auto encodedImageMetadata = framemetadata_sp(new EncodedImageMetadata(width, height)); + fileReader->addOutputPin(encodedImageMetadata); + + auto mp4WriterSinkProps = Mp4WriterSinkProps(writeChunkTime, writeSyncTimeInSecs, writefps, _writeOutPath); + mp4WriterSink = boost::shared_ptr(new Mp4WriterSink(mp4WriterSinkProps)); + fileReader->setNext(mp4WriterSink); + + BOOST_TEST(fileReader->init()); + BOOST_TEST(mp4WriterSink->init()); + } + + ~WritePipeline() + { + //termPipeline(); + // delete any existing stuff in directory + if (!boost::filesystem::is_empty(writeOutPath)) + { + for (auto &&itr : boost::filesystem::recursive_directory_iterator(writeOutPath)) + { + auto dirPath = itr.path(); + boost::filesystem::remove_all(dirPath); + } + } + } + + void termPipeline() + { + fileReader->term(); + mp4WriterSink->term(); + } + + std::string writeOutPath; + boost::shared_ptr mp4WriterSink = nullptr; + boost::shared_ptr fileReader = nullptr; +}; + +struct WritePipelineIndependent { + WritePipelineIndependent(std::string readFolderPath, int readfps, int width, int height, + std::string _writeOutPath, int writeChunkTime, int writeSyncTimeInSecs, int writefps) + { + LoggerProps loggerProps; + loggerProps.logLevel = boost::log::trivial::severity_level::info; + Logger::setLogLevel(boost::log::trivial::severity_level::info); + Logger::initLogger(loggerProps); + + writeOutPath = _writeOutPath; + auto fileReaderProps = FileReaderModuleProps(readFolderPath, 0, -1); + fileReaderProps.fps = readfps; + fileReaderProps.readLoop = true; + fileReader = boost::shared_ptr(new FileReaderModule(fileReaderProps)); + auto encodedImageMetadata = framemetadata_sp(new EncodedImageMetadata(width, height)); + fileReader->addOutputPin(encodedImageMetadata); + + auto mp4WriterSinkProps = Mp4WriterSinkProps(writeChunkTime, writeSyncTimeInSecs, writefps, _writeOutPath); + mp4WriterSink = boost::shared_ptr(new Mp4WriterSink(mp4WriterSinkProps)); + fileReader->setNext(mp4WriterSink); + + p = boost::shared_ptr(new PipeLine("test")); + p->appendModule(fileReader); + + p->init(); + p->run_all_threaded(); + } + + ~WritePipelineIndependent() + { + p->stop(); + p->term(); + p->wait_for_all(); + p.reset(); + + // delete any existing stuff in directory + if (!boost::filesystem::is_empty(writeOutPath)) + { + for (auto &&itr : boost::filesystem::recursive_directory_iterator(writeOutPath)) + { + auto dirPath = itr.path(); + boost::filesystem::remove_all(dirPath); + break; + } + } + } + + void termPipeline() + { + fileReader->term(); + mp4WriterSink->term(); + } + + std::string writeOutPath; + boost::shared_ptr p; + boost::shared_ptr mp4WriterSink = nullptr; + boost::shared_ptr fileReader = nullptr; +}; + +struct ReadPipeline { + ReadPipeline(std::string videoPath, uint16_t reInitInterval, bool direction, bool parseFS, int fps = 30) + { + LoggerProps loggerProps; + loggerProps.logLevel = boost::log::trivial::severity_level::info; + Logger::setLogLevel(boost::log::trivial::severity_level::info); + Logger::initLogger(loggerProps); + + bool readLoop = false; + auto mp4ReaderProps = Mp4ReaderSourceProps(videoPath, parseFS, reInitInterval, direction, readLoop, false); + mp4ReaderProps.fps = fps; + mp4ReaderProps.logHealth = true; + mp4ReaderProps.logHealthFrequency = 100; + mp4Reader = boost::shared_ptr(new Mp4ReaderSource(mp4ReaderProps)); + auto encodedImageMetadata = framemetadata_sp(new EncodedImageMetadata(0,0)); + mp4Reader->addOutPutPin(encodedImageMetadata); + auto mp4Metadata = framemetadata_sp(new Mp4VideoMetadata("v_1_0")); + mp4Reader->addOutPutPin(mp4Metadata); + + std::vector mImagePin; + mImagePin = mp4Reader->getAllOutputPinsByType(FrameMetadata::ENCODED_IMAGE); + + auto sinkProps = ExternalSinkProps();; + sink = boost::shared_ptr(new ExternalSink(sinkProps)); + mp4Reader->setNext(sink, mImagePin); + + BOOST_TEST(mp4Reader->init()); + BOOST_TEST(sink->init()); + } + + ~ReadPipeline() + { + termPipeline(); + } + + void termPipeline() + { + mp4Reader->term(); + sink->term(); + } + + boost::shared_ptr mp4Reader = nullptr; + boost::shared_ptr sink = nullptr; +}; + +BOOST_AUTO_TEST_CASE(basic) +{ + /* write pipeline params */ + std::string readFolderPath = "data/resized_mono_jpg/"; + int fileReaderFPS = 10; + int height = 160; + int width = 80; + std::string writeFolderPath = "data/Mp4_videos/mp4_read_write_tests/"; + int chunkTimeMins = UINT32_MAX; + int syncTimeInSecs = 1; + int writeFPS = 10; + WritePipeline w(readFolderPath, fileReaderFPS, width, height, writeFolderPath, chunkTimeMins, syncTimeInSecs, writeFPS); + + uint64_t lastFrameTS = 0; + // write 4 frames + for (auto i = 0; i < 3; ++i) + { + w.fileReader->step(); + w.mp4WriterSink->step(); + } + // sync the mp4 with next step + boost::this_thread::sleep_for(boost::chrono::seconds(syncTimeInSecs)); + LOG_INFO << "WRITING 4th FRAME"; + w.fileReader->step(); + w.mp4WriterSink->step(); + LOG_INFO << "FIFTH FRAME WRITTEN"; + + /* read Pipeline params */ + std::string readPath, rootPath; + int reInitIntervalSecs = 5; + bool direction = true; + bool parseFS = true; + + // read the first and only file in the directory + if (!boost::filesystem::is_directory(writeFolderPath)) + { + boost::filesystem::create_directories(writeFolderPath); + } + for (auto &&itr : boost::filesystem::recursive_directory_iterator(writeFolderPath)) + { + auto dirPath = itr.path(); + if (boost::filesystem::is_regular_file(dirPath) && boost::filesystem::extension(dirPath) == ".mp4") + { + readPath = dirPath.string(); + rootPath = boost::filesystem::path(readPath).parent_path().parent_path().string(); + break; + } + } + auto boostVideoTS = std::stoull(boost::filesystem::path(readPath).stem().string()); + ReadPipeline r(readPath, reInitIntervalSecs, direction, parseFS); + + // read 3 frames + for (auto i = 0; i < 3; ++i) + { + r.mp4Reader->step(); + auto frame = r.sink->pop().begin()->second; + LOG_INFO << "reading frame < " << i + 1 << ">"; + LOG_INFO << "frame->timestamp <" << frame->timestamp << ">"; + BOOST_TEST(!frame->isEOS()); + if (lastFrameTS) + BOOST_TEST((frame->timestamp - lastFrameTS) < 20000); + lastFrameTS = frame->timestamp; + if (!i) + { + BOOST_TEST(boostVideoTS == lastFrameTS); + } + } + + // EOS + r.mp4Reader->step(); + auto frame = r.sink->pop().begin()->second; + LOG_INFO << "frame is EOS <" << frame->isEOS() << ">"; + BOOST_TEST(frame->isEOS()); + //lastFrameTS = frame->timestamp; + + // force sync with new frame + boost::this_thread::sleep_for(boost::chrono::seconds(syncTimeInSecs)); + LOG_INFO << "WRITING 5th FRAME"; + w.fileReader->step(); + w.mp4WriterSink->step(); + + // reader should be able to get a frame now + LOG_INFO << "attempt reading frame after reInitInterval"; + auto sinkQ = r.sink->getQue(); + while (1) + { + r.mp4Reader->step(); + if (sinkQ->size()) + { + frame = r.sink->pop().begin()->second; + BOOST_TEST(!frame->isEOS()); + BOOST_TEST(frame->timestamp > lastFrameTS); + break; + } + boost::this_thread::sleep_for(boost::chrono::seconds(syncTimeInSecs)); + } + LOG_INFO << "frame after reInitInterval < " << frame->timestamp << ">"; + + // test cleanup + w.termPipeline(); + r.termPipeline(); + boost::filesystem::remove_all(rootPath); + boost::this_thread::sleep_for(boost::chrono::seconds(1)); +} + +BOOST_AUTO_TEST_CASE(basic_parseFS_disabled, *boost::unit_test::disabled()) +{ + /* write pipeline params */ + std::string readFolderPath = "data/resized_mono_jpg/"; + int fileReaderFPS = 10; + int height = 160; + int width = 80; + std::string writeFolderPath = "data/Mp4_videos/mp4_read_write_tests"; + int chunkTimeMins = UINT32_MAX; + int syncTimeInSecs = 1; + int writeFPS = 10; + WritePipeline w(readFolderPath, fileReaderFPS, width, height, writeFolderPath, chunkTimeMins, syncTimeInSecs, writeFPS); + + uint64_t lastFrameTS = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + // write 4 frames + for (auto i = 0; i < 3; ++i) + { + w.fileReader->step(); + w.mp4WriterSink->step(); + } + // sync the mp4 with next step + boost::this_thread::sleep_for(boost::chrono::seconds(syncTimeInSecs)); + LOG_INFO << "WRITING 4th FRAME"; + w.fileReader->step(); + w.mp4WriterSink->step(); + LOG_INFO << "FIFTH FRAME WRITTEN"; + + /* read Pipeline params */ + std::string readPath, rootPath; + int reInitIntervalSecs = 5; + bool direction = true; + bool parseFS = false; + + // read the first and only file in the directory + if (!boost::filesystem::is_directory(writeFolderPath)) + { + boost::filesystem::create_directories(writeFolderPath); + } + for (auto &&itr : boost::filesystem::recursive_directory_iterator(writeFolderPath)) + { + auto dirPath = itr.path(); + if (boost::filesystem::is_regular_file(dirPath) && boost::filesystem::extension(dirPath) == ".mp4") + { + readPath = dirPath.string(); + rootPath = boost::filesystem::path(readPath).parent_path().parent_path().string(); + break; + } + } + auto boostVideoTS = std::stoull(boost::filesystem::path(readPath).stem().string()); + ReadPipeline r(readPath, reInitIntervalSecs, direction, parseFS); + + // read 4 frames + for (auto i = 0; i < 3; ++i) + { + r.mp4Reader->step(); + auto frame = r.sink->pop().begin()->second; + LOG_INFO << "reading frame < " << i + 1 << ">"; + LOG_INFO << "frame->timestamp <" << frame->timestamp << ">"; + BOOST_TEST(!frame->isEOS()); + BOOST_TEST((frame->timestamp - lastFrameTS) < 20000); + lastFrameTS = frame->timestamp; + if (!i) + { + BOOST_TEST(boostVideoTS == lastFrameTS); + } + } + + // EOS + r.mp4Reader->step(); + auto frame = r.sink->pop().begin()->second; + LOG_INFO << "frame is EOS <" << frame->isEOS() << ">"; + BOOST_TEST(frame->isEOS()); + //lastFrameTS = frame->timestamp; + + // force sync with new frame + boost::this_thread::sleep_for(boost::chrono::seconds(syncTimeInSecs)); + LOG_INFO << "WRITING 5th FRAME"; + w.fileReader->step(); + w.mp4WriterSink->step(); + + // reader should be able to get a frame now + LOG_INFO << "attempt reading frame after reInitInterval"; + auto sinkQ = r.sink->getQue(); + while (1) + { + r.mp4Reader->step(); + if (sinkQ->size()) + { + frame = r.sink->pop().begin()->second; + BOOST_TEST(!frame->isEOS()); + BOOST_TEST(frame->timestamp > lastFrameTS); + break; + } + boost::this_thread::sleep_for(boost::chrono::seconds(syncTimeInSecs)); + } + LOG_INFO << "frame after reInitInterval < " << frame->timestamp << ">"; + + // test cleanup + w.termPipeline(); + r.termPipeline(); + boost::filesystem::remove_all(rootPath); + boost::this_thread::sleep_for(boost::chrono::seconds(2)); +} + +BOOST_AUTO_TEST_CASE(loop_no_chunking, *boost::unit_test::disabled()) +{ + /* write pipeline params */ + std::string readFolderPath = "data/resized_mono_jpg/"; + int fileReaderFPS = 30; + int height = 180; + int width = 80; + std::string writeFolderPath = "data/mp4_videos/mp4_read_write_tests/"; + int chunkTimeMins = UINT32_MAX; + int syncTimeInSecs = 1; + int writeFPS = 30; + WritePipeline w(readFolderPath, fileReaderFPS, width, height, writeFolderPath, chunkTimeMins, syncTimeInSecs, writeFPS); + + uint64_t lastFrameTS = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + // write 4 frames + for (auto i = 0; i < 3; ++i) + { + w.fileReader->step(); + w.mp4WriterSink->step(); + } + // sync the mp4 with next step + boost::this_thread::sleep_for(boost::chrono::seconds(syncTimeInSecs)); + LOG_INFO << "WRITING 4th FRAME"; + w.fileReader->step(); + w.mp4WriterSink->step(); + LOG_INFO << "FIFTH FRAME WRITTEN"; + + /* read Pipeline params */ + std::string readPath, rootPath; + int reInitIntervalSecs = 5; + bool direction = true; + bool parseFS = true; + + // read the first and only file in the directory + if (!boost::filesystem::is_directory(writeFolderPath)) + { + boost::filesystem::create_directories(writeFolderPath); + } + for (auto &&itr : boost::filesystem::recursive_directory_iterator(writeFolderPath)) + { + auto dirPath = itr.path(); + if (boost::filesystem::is_regular_file(dirPath) && boost::filesystem::extension(dirPath) == ".mp4") + { + readPath = dirPath.string(); + rootPath = boost::filesystem::path(readPath).parent_path().parent_path().string(); + break; + } + } + auto boostVideoTS = std::stoull(boost::filesystem::path(readPath).stem().string()); + ReadPipeline r(readPath, reInitIntervalSecs, direction, parseFS); + + // read 4 frames + for (auto i = 0; i < 3; ++i) + { + r.mp4Reader->step(); + auto frame = r.sink->pop().begin()->second; + LOG_INFO << "reading frame < " << i + 1 << ">"; + LOG_INFO << "frame->timestamp <" << frame->timestamp << ">"; + BOOST_TEST(!frame->isEOS()); + BOOST_TEST((frame->timestamp - lastFrameTS) < 20000); + lastFrameTS = frame->timestamp; + if (!i) + { + BOOST_TEST(boostVideoTS == lastFrameTS); + } + } + + // EOS + r.mp4Reader->step(); + auto frame = r.sink->pop().begin()->second; + LOG_INFO << "frame is EOS <" << frame->isEOS() << ">"; + BOOST_TEST(frame->isEOS()); + //lastFrameTS = frame->timestamp; + + LOG_INFO << "=============================Starting loop testing=========================="; + int count = 0; + auto sinkQ = r.sink->getQue(); + while (count < 10) + { + LOG_INFO << "=======Round " << count + 1 << "=========="; + for (auto i = 0; i < 10; i++) + { + if (i == 9) // force sync all 10 frames in next step + { + boost::this_thread::sleep_for(boost::chrono::seconds(syncTimeInSecs)); + } + LOG_INFO << "===>Writing new frames after EOS <" << i+1 << ">"; + w.fileReader->step(); + w.mp4WriterSink->step(); + } + + LOG_INFO << "====10 frames written on the file==="; + auto frameCount = 0; + while (1) + { + r.mp4Reader->step(); + if (!sinkQ->size()) + { + boost::this_thread::sleep_for(boost::chrono::seconds(syncTimeInSecs)); + continue; + } + frame = r.sink->pop().begin()->second; + BOOST_TEST(frame->timestamp > lastFrameTS); + LOG_INFO << "===>reading frame after EOS <" << frame->timestamp << "> isEOS <" << frame->isEOS() << ">"; + ++frameCount; + if (frame->isEOS()) + { + --frameCount; + break; + } + else + { + lastFrameTS = frame->timestamp; // we care about ts of image frame + } + } + BOOST_TEST(frameCount == 10); + ++count; + } + // test cleanup + w.termPipeline(); + r.termPipeline(); + boost::filesystem::remove_all(rootPath); + boost::this_thread::sleep_for(boost::chrono::seconds(2)); +} + +BOOST_AUTO_TEST_CASE(basic_chunking, *boost::unit_test::disabled()) +{ + /* write pipeline params */ + std::string readFolderPath = "data/resized_mono_jpg/"; + int fileReaderFPS = 10; + int height = 160; + int width = 80; + std::string writeFolderPath = "data/mp4_videos/mp4_read_write_tests/"; + int chunkTimeMins = 1; + int syncTimeInSecs = 1; + int writeFPS = 10; + WritePipeline w(readFolderPath, fileReaderFPS, width, height, writeFolderPath, chunkTimeMins, syncTimeInSecs, writeFPS); + + /* timing */ + auto nowTime = std::chrono::system_clock::now().time_since_epoch(); + uint64_t lastFrameTS = std::chrono::duration_cast(nowTime).count(); + std::chrono::time_point timePointInSeconds(std::chrono::duration_cast(nowTime)); + std::time_t t = std::chrono::system_clock::to_time_t(timePointInSeconds); + std::tm tm = *std::localtime(&t); + + // write 4 frames + for (auto i = 0; i < 3; ++i) + { + w.fileReader->step(); + w.mp4WriterSink->step(); + } + // sync the mp4 with next step + boost::this_thread::sleep_for(boost::chrono::seconds(syncTimeInSecs)); + w.fileReader->step(); + w.mp4WriterSink->step(); + + /* read Pipeline params */ + std::string readPath, rootPath; + int reInitIntervalSecs = 5; + bool direction = true; + bool parseFS = true; + + // read the first and only file in the directory + if (!boost::filesystem::is_directory(writeFolderPath)) + { + boost::filesystem::create_directories(writeFolderPath); + } + for (auto &&itr : boost::filesystem::recursive_directory_iterator(writeFolderPath)) + { + auto dirPath = itr.path(); + if (boost::filesystem::is_regular_file(dirPath) && boost::filesystem::extension(dirPath) == ".mp4") + { + readPath = dirPath.string(); + rootPath = boost::filesystem::path(readPath).parent_path().parent_path().string(); + break; + } + } + auto boostVideoTS = std::stoull(boost::filesystem::path(readPath).stem().string()); + ReadPipeline r(readPath, reInitIntervalSecs, direction, parseFS); + + // read 4 frames + for (auto i = 0; i < 3; ++i) + { + r.mp4Reader->step(); + auto frame = r.sink->pop().begin()->second; + LOG_INFO << "reading frame < " << i + 1 << ">"; + LOG_INFO << "frame->timestamp <" << frame->timestamp << ">"; + BOOST_TEST(!frame->isEOS()); + BOOST_TEST((frame->timestamp - lastFrameTS) < 20000); + lastFrameTS = frame->timestamp; + if (!i) + { + BOOST_TEST(boostVideoTS == lastFrameTS); + } + } + + // EOS + r.mp4Reader->step(); + auto frame = r.sink->pop().begin()->second; + LOG_INFO << "frame is EOS <" << frame->isEOS() << ">"; + BOOST_TEST(frame->isEOS()); + //lastFrameTS = frame->timestamp; + + /* write a new video now - dirty minute clock change logic start */ + auto nowTime2 = std::chrono::system_clock::now().time_since_epoch(); + std::chrono::time_point timePointInSeconds2(std::chrono::duration_cast(nowTime2)); + std::time_t t2 = std::chrono::system_clock::to_time_t(timePointInSeconds2); + std::tm tm2 = *std::localtime(&t2); + + while (tm2.tm_min != tm.tm_min + 1) + { + auto nowTime2 = std::chrono::system_clock::now().time_since_epoch(); + std::chrono::time_point timePointInSeconds2(std::chrono::duration_cast(nowTime2)); + std::time_t t2 = std::chrono::system_clock::to_time_t(timePointInSeconds2); + tm2 = *std::localtime(&t2); + } + /* dirty minute clock change logic end */ + LOG_INFO << "OldVideoMin < " << tm.tm_min << "> newVideoMin <" << tm2.tm_min << ">"; + auto lowerLimitNewVideo = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + LOG_INFO << "Writing 2 frames in new video!! @ <" << lowerLimitNewVideo << ">"; + for (auto i = 0; i < 2; ++i) + { + if (i == 1) + { + boost::this_thread::sleep_for(boost::chrono::seconds(syncTimeInSecs)); + } + w.fileReader->step(); + w.mp4WriterSink->step(); + } + + // reader should get the frame from the new video now + LOG_INFO << "=================================read from new video==================================="; + auto sinkQ = r.sink->getQue(); + auto count = 0; + while (1) + { + r.mp4Reader->step(); + LOG_INFO << "sinkQ size <" << sinkQ->size() << ">"; + if (sinkQ->size()) + { + frame = r.sink->pop().begin()->second; + LOG_INFO << "===>reading frame after EOS <" << frame->timestamp << "> lowerLimitNewVideo <" << lowerLimitNewVideo << "> isEOS<" << frame->isEOS() << "> "; + if (frame->isEOS()) + { + LOG_INFO << "====EndOfStream===="; + break; + } + BOOST_TEST(!frame->isEOS()); + BOOST_TEST(frame->timestamp >= lowerLimitNewVideo); + } + boost::this_thread::sleep_for(boost::chrono::seconds(syncTimeInSecs)); + } + + // now write and read for 10 frames + for (auto i = 0; i < 10; i++) + { + if (i == 9) // force sync all 10 frames in next step + { + boost::this_thread::sleep_for(boost::chrono::seconds(syncTimeInSecs)); + } + LOG_INFO << "===>Writing new frames after EOS in new file <" << i + 1 << ">"; + w.fileReader->step(); + w.mp4WriterSink->step(); + } + + LOG_INFO << "====10 frames written on the new file==="; + auto frameCount = 0; + while (1) + { + r.mp4Reader->step(); + if (!sinkQ->size()) + { + boost::this_thread::sleep_for(boost::chrono::seconds(syncTimeInSecs)); + continue; + } + frame = r.sink->pop().begin()->second; + BOOST_TEST(frame->timestamp > lastFrameTS); + LOG_INFO << "===>reading frame after EOS in the new file<" << frame->timestamp << "> isEOS <" << frame->isEOS() << ">"; + ++frameCount; + if (frame->isEOS()) + { + --frameCount; + break; + } + else + { + lastFrameTS = frame->timestamp; + } + } + LOG_INFO << "total Frames read in the new file after EOS<" << frameCount << ">"; + BOOST_TEST(frameCount == 10); + boost::this_thread::sleep_for(boost::chrono::seconds(syncTimeInSecs+2)); + + // test cleanup + w.termPipeline(); + r.termPipeline(); + boost::filesystem::remove_all(rootPath); + boost::this_thread::sleep_for(boost::chrono::seconds(2)); +} + +BOOST_AUTO_TEST_CASE(seek_in_wait_state) +{ + /* write pipeline params */ + std::string readFolderPath = "data/resized_mono_jpg/"; + int fileReaderFPS = 10; + int height = 30; + int width = 22; + std::string writeFolderPath = "data/mp4_videos/mp4_read_write_tests/"; + int chunkTimeMins = UINT32_MAX; + int syncTimeInSecs = 1; + int writeFPS = 10; + WritePipeline w(readFolderPath, fileReaderFPS, width, height, writeFolderPath, chunkTimeMins, syncTimeInSecs, writeFPS); + + uint64_t lastFrameTS = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + // write 4 frames + for (auto i = 0; i < 3; ++i) + { + w.fileReader->step(); + w.mp4WriterSink->step(); + } + // sync the mp4 with next step + boost::this_thread::sleep_for(boost::chrono::seconds(syncTimeInSecs)); + LOG_INFO << "WRITING 4th FRAME"; + w.fileReader->step(); + w.mp4WriterSink->step(); + LOG_INFO << "FIFTH FRAME WRITTEN"; + + /* read Pipeline params */ + std::string readPath, rootPath; + int reInitIntervalSecs = 5; + bool direction = true; + bool parseFS = true; + + // read the first and only file in the directory + if (!boost::filesystem::is_directory(writeFolderPath)) + { + boost::filesystem::create_directories(writeFolderPath); + } + for (auto &&itr : boost::filesystem::recursive_directory_iterator(writeFolderPath)) + { + auto dirPath = itr.path(); + if (boost::filesystem::is_regular_file(dirPath) && boost::filesystem::extension(dirPath) == ".mp4") + { + readPath = dirPath.string(); + rootPath = boost::filesystem::path(readPath).parent_path().parent_path().string(); + break; + } + } + auto boostVideoTS = std::stoull(boost::filesystem::path(readPath).stem().string()); + ReadPipeline r(readPath, reInitIntervalSecs, direction, parseFS); + + // read 4 frames + for (auto i = 0; i < 3; ++i) + { + r.mp4Reader->step(); + auto frame = r.sink->pop().begin()->second; + LOG_INFO << "reading frame < " << i + 1 << ">"; + LOG_INFO << "frame->timestamp <" << frame->timestamp << "> lastFrameTS <" << lastFrameTS << ">"; + BOOST_TEST(!frame->isEOS()); + BOOST_TEST((frame->timestamp - lastFrameTS) < 20000); + lastFrameTS = frame->timestamp; + if (!i) + { + BOOST_TEST(boostVideoTS == lastFrameTS); + } + } + + // EOS + r.mp4Reader->step(); + auto frame = r.sink->pop().begin()->second; + LOG_INFO << "frame is EOS <" << frame->isEOS() << ">"; + BOOST_TEST(frame->isEOS()); + lastFrameTS = frame->timestamp; + + // wait state + LOG_INFO << "reader should be in waiting state"; + auto sinkQ = r.sink->getQue(); + for (auto i = 0; i < 2; ++i) + { + r.mp4Reader->step(); + if (sinkQ->size()) + { + BOOST_TEST(false); + LOG_ERROR << "mp4Reader should be in waiting state."; + break; + } + boost::this_thread::sleep_for(boost::chrono::seconds(syncTimeInSecs)); + } + + // seek in wait state + LOG_INFO << "seeking in wait state..."; + r.mp4Reader->randomSeek(boostVideoTS); + r.mp4Reader->step(); + BOOST_TEST(sinkQ->size()); + frame = r.sink->pop().begin()->second; + BOOST_TEST(frame->timestamp == boostVideoTS); + LOG_INFO << "seeked frameTS <" << frame->timestamp << "> videoTS <" << boostVideoTS << ">"; + + // test cleanup + w.termPipeline(); + r.termPipeline(); + boost::filesystem::remove_all(rootPath); + boost::this_thread::sleep_for(boost::chrono::seconds(2)); +} + +BOOST_AUTO_TEST_CASE(seek_in_wait_parseFS_disabled) +{ + /* write pipeline params */ + std::string readFolderPath = "data/resized_mono_jpg/"; + int fileReaderFPS = 10; + int height = 30; + int width = 22; + std::string writeFolderPath = "data/mp4_videos/mp4_read_write_tests/"; + int chunkTimeMins = UINT32_MAX; + int syncTimeInSecs = 1; + int writeFPS = 10; + WritePipeline w(readFolderPath, fileReaderFPS, width, height, writeFolderPath, chunkTimeMins, syncTimeInSecs, writeFPS); + + uint64_t lastFrameTS = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + // write 4 frames + for (auto i = 0; i < 3; ++i) + { + w.fileReader->step(); + w.mp4WriterSink->step(); + } + // sync the mp4 with next step + boost::this_thread::sleep_for(boost::chrono::seconds(syncTimeInSecs)); + LOG_INFO << "WRITING 4th FRAME"; + w.fileReader->step(); + w.mp4WriterSink->step(); + LOG_INFO << "FOURTH FRAME WRITTEN"; + + /* read Pipeline params */ + std::string readPath, rootPath; + int reInitIntervalSecs = 5; + bool direction = true; + bool parseFS = false; + + // read the first and only file in the directory + if (!boost::filesystem::is_directory(writeFolderPath)) + { + boost::filesystem::create_directories(writeFolderPath); + } + for (auto &&itr : boost::filesystem::recursive_directory_iterator(writeFolderPath)) + { + auto dirPath = itr.path(); + if (boost::filesystem::is_regular_file(dirPath) && boost::filesystem::extension(dirPath) == ".mp4") + { + readPath = dirPath.string(); + rootPath = boost::filesystem::path(readPath).parent_path().parent_path().string(); + break; + } + } + auto boostVideoTS = std::stoull(boost::filesystem::path(readPath).stem().string()); + ReadPipeline r(readPath, reInitIntervalSecs, direction, parseFS); + uint64_t lastVideoTS = 0; + // read 4 frames + for (auto i = 0; i < 3; ++i) + { + r.mp4Reader->step(); + auto frame = r.sink->pop().begin()->second; + LOG_INFO << "reading frame < " << i + 1 << ">"; + LOG_INFO << "frame->timestamp <" << frame->timestamp << "> lastFrameTS <" << lastFrameTS << ">"; + BOOST_TEST(!frame->isEOS()); + BOOST_TEST((frame->timestamp - lastFrameTS) < 20000); + lastFrameTS = frame->timestamp; + lastVideoTS = lastFrameTS; + if (!i) + { + BOOST_TEST(boostVideoTS == lastFrameTS); + } + } + + // EOS + r.mp4Reader->step(); + auto frame = r.sink->pop().begin()->second; + LOG_INFO << "frame is EOS <" << frame->isEOS() << ">"; + BOOST_TEST(frame->isEOS()); + lastFrameTS = frame->timestamp; + + // wait state + LOG_ERROR << "reader should be in waiting state"; + auto sinkQ = r.sink->getQue(); + for (auto i = 0; i < 2; ++i) + { + r.mp4Reader->step(); + if (sinkQ->size()) + { + BOOST_TEST(false); + LOG_ERROR << "mp4Reader should be in waiting state."; + break; + } + boost::this_thread::sleep_for(boost::chrono::seconds(syncTimeInSecs)); + } + + // seek in wait state and read whole video again + LOG_INFO << "seeking in wait state..."; + r.mp4Reader->randomSeek(boostVideoTS); + r.mp4Reader->step(); + BOOST_TEST(sinkQ->size()); + frame = r.sink->pop().begin()->second; + BOOST_TEST(frame->timestamp == boostVideoTS); + LOG_INFO << "seeked frameTS <" << frame->timestamp << "> videoTS <" << boostVideoTS << ">"; + + for (auto i = 0; i < 2; ++i) + { + r.mp4Reader->step(); + auto frame = r.sink->pop().begin()->second; + LOG_INFO << "reading frame < " << i + 1 << ">"; + LOG_INFO << "frame->timestamp <" << frame->timestamp << "> lastFrameTS <" << lastFrameTS << ">"; + lastFrameTS = frame->timestamp; + } + + // EOS + r.mp4Reader->step(); + frame = r.sink->pop().begin()->second; + LOG_INFO << "frame is EOS <" << frame->isEOS() << ">"; + BOOST_TEST(frame->isEOS()); + // no updates to the video + BOOST_TEST(lastFrameTS == lastVideoTS); + + // test cleanup + w.termPipeline(); + r.termPipeline(); + boost::filesystem::remove_all(rootPath); +} + +BOOST_AUTO_TEST_CASE(writer_only, *boost::unit_test::disabled()) +{ + /* write pipeline params */ + std::string readFolderPath = "data/bigjpeg/"; + int fileReaderFPS = 180; + int height = 3619; + int width = 3619; + std::string writeFolderPath = "data/mp4_videos/mp4_read_write_tests/"; + int chunkTimeMins = 10; + int syncTimeInSecs = 1; + int writeFPS = 180; + WritePipelineIndependent write(readFolderPath, fileReaderFPS, width, height, writeFolderPath, chunkTimeMins, syncTimeInSecs, writeFPS); +} + +BOOST_AUTO_TEST_CASE(reader_only, *boost::unit_test::disabled()) +{ + std::string writeFolderPath = ""; + std::string readPath = "", rootPath; + int reInitIntervalSecs = 1; + bool direction = true; + bool parseFS = true; + int readFps = 60; + + while (readPath.empty()) + { + // read the first and only file in the directory + if (!boost::filesystem::is_directory(writeFolderPath)) + { + boost::filesystem::create_directories(writeFolderPath); + } + for (auto &&itr : boost::filesystem::recursive_directory_iterator(writeFolderPath)) + { + auto dirPath = itr.path(); + if (boost::filesystem::is_regular_file(dirPath) && boost::filesystem::extension(dirPath) == ".mp4") + { + readPath = dirPath.string(); + rootPath = boost::filesystem::path(readPath).parent_path().parent_path().string(); + break; + } + } + } + LOG_INFO << "Waiting for first video"; + boost::this_thread::sleep_for(boost::chrono::seconds(1)); + LOG_INFO << "Resuming....."; + auto boostVideoTS = std::stoull(boost::filesystem::path(readPath).stem().string()); + ReadPipeline r(readPath, reInitIntervalSecs, direction, parseFS, readFps); + + // read 4 frames + auto i = 0; + uint64_t lastFrameTS = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + bool skipStep = false; + while (1) + { + r.mp4Reader->step(); + auto frame = r.sink->pop().begin()->second; + if (frame->isMp4ErrorFrame()) + { + auto errorFrame = dynamic_cast(frame.get()); + auto type = errorFrame->errorCode; + LOG_ERROR << "**********************************************************************************"; + LOG_ERROR << "Error occured in mp4Reader <" << type << "> msg <" << errorFrame->errorMsg << ">"; + LOG_ERROR << "**********************************************************************************"; + if (type == MP4_MISSING_VIDEOTRACK) + { + boost::this_thread::sleep_for(boost::chrono::seconds(1)); + r.mp4Reader->randomSeek(lastFrameTS + 1); + continue; + } + else if (type == MP4_OPEN_FILE_FAILED) + { + boost::this_thread::sleep_for(boost::chrono::seconds(1)); + r.mp4Reader->randomSeek(lastFrameTS + 1); + continue; + } + } + if (frame->isEOS()) + { + auto q = r.sink->getQue(); + while (!q->size()) + { + LOG_INFO << "Waiting for more data on disk ....................."; + boost::this_thread::sleep_for(boost::chrono::seconds(1)); + r.mp4Reader->step(); + skipStep = true; + } + continue; + } + LOG_INFO << "read image frame < " << i + 1 << ">"; + LOG_INFO << "frame->timestamp <" << frame->timestamp << ">"; + //BOOST_TEST((frame->timestamp - lastFrameTS) < 20000); + lastFrameTS = frame->timestamp; + ++i; + + if (i == 10000) + { + break; + } + } + LOG_INFO << "Total Frames Read <" << i << ">"; + boost::this_thread::sleep_for(boost::chrono::milliseconds(300)); +} + +// most important +BOOST_AUTO_TEST_CASE(ultimate) +{ + /* write pipeline params */ + std::string readFolderPath = "data/resized_mono_jpg/"; + int fileReaderFPS = 30; + int height = 80; + int width = 160; + std::string writeFolderPath = "data/Mp4_videos/mp4_read_write_tests/"; + int chunkTimeMins = 10; + int syncTimeInSecs = 1; + int writeFPS = 30; + WritePipelineIndependent write(readFolderPath, fileReaderFPS, width, height, writeFolderPath, chunkTimeMins, syncTimeInSecs, writeFPS); + + std::string readPath = "", rootPath; + int reInitIntervalSecs = 1; + bool direction = true; + bool parseFS = true; + int readFps = 60; + + while (readPath.empty()) + { + // read the first and only file in the directory + if (!boost::filesystem::is_directory(writeFolderPath)) + { + boost::filesystem::create_directories(writeFolderPath); + } + auto cannonicalWriteFolderPath = boost::filesystem::canonical(writeFolderPath); + for (auto &&itr : boost::filesystem::recursive_directory_iterator(cannonicalWriteFolderPath)) + { + auto dirPath = itr.path(); + if (boost::filesystem::is_regular_file(dirPath) && boost::filesystem::extension(dirPath) == ".mp4") + { + readPath = dirPath.string(); + rootPath = boost::filesystem::path(readPath).parent_path().parent_path().string(); + break; + } + } + } + LOG_INFO << "Waiting for first video"; + boost::this_thread::sleep_for(boost::chrono::seconds(1)); + LOG_INFO << "Resuming....."; + auto boostVideoTS = std::stoull(boost::filesystem::path(readPath).stem().string()); + ReadPipeline r(readPath, reInitIntervalSecs, direction, parseFS, readFps); + + // read 4 frames + auto i = 0; + uint64_t lastFrameTS = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + lastFrameTS -= 2000; + bool skipStep = false; + + while(1) + { + r.mp4Reader->step(); + auto frame = r.sink->pop().begin()->second; + if (frame->isMp4ErrorFrame()) + { + auto errorFrame = dynamic_cast(frame.get()); + auto type = errorFrame->errorCode; + LOG_ERROR << "**********************************************************************************"; + LOG_ERROR << "Error occured in mp4Reader <" << type << "> msg <" << errorFrame->errorMsg << ">"; + LOG_ERROR << "**********************************************************************************"; + if (type == MP4_MISSING_VIDEOTRACK) + { + boost::this_thread::sleep_for(boost::chrono::seconds(1)); + r.mp4Reader->randomSeek(lastFrameTS + 1); + continue; + } + else if (type == MP4_OPEN_FILE_FAILED) + { + boost::this_thread::sleep_for(boost::chrono::seconds(1)); + r.mp4Reader->randomSeek(lastFrameTS + 1); + continue; + } + } + if (frame->isEOS()) + { + auto q = r.sink->getQue(); + + while (!q->size()) + { + auto queSize = q->size(); + LOG_INFO << "Waiting for more data on disk ....................."; + boost::this_thread::sleep_for(boost::chrono::seconds(1)); + r.mp4Reader->step(); + skipStep = true; + } + auto queSize = q->size(); + continue; + } + LOG_INFO << "read image frame < " << i + 1 << ">"; + LOG_INFO << "frame->timestamp <" << frame->timestamp << ">"; + BOOST_TEST((frame->timestamp - lastFrameTS) < 20000); + + if (!((frame->timestamp - lastFrameTS) < 20000)) + { + LOG_INFO << "whats wrong"; + } + lastFrameTS = frame->timestamp; + ++i; + + if (i == 2000) + { + break; + } + } + LOG_INFO << "Total Frames Read <" << i << ">"; + boost::this_thread::sleep_for(boost::chrono::milliseconds(90)); +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/base/test/mp4readersource_tests.cpp b/base/test/mp4readersource_tests.cpp index 43f8257e7..7aa1d9347 100644 --- a/base/test/mp4readersource_tests.cpp +++ b/base/test/mp4readersource_tests.cpp @@ -4,15 +4,17 @@ #include "PipeLine.h" #include "FileReaderModule.h" #include "Mp4ReaderSource.h" -#include "FileWriterModule.h" #include "StatSink.h" #include "FrameMetadata.h" #include "EncodedImageMetadata.h" #include "H264Metadata.h" #include "Mp4VideoMetadata.h" #include "Mp4WriterSink.h" +#include "ExternalSinkModule.h" +#include "test_utils.h" +#include "Mp4ErrorFrame.h" -BOOST_AUTO_TEST_SUITE(Mp4ReaderSource_tests) +BOOST_AUTO_TEST_SUITE(mp4readersource_tests) class MetadataSinkProps : public ModuleProps { @@ -40,7 +42,7 @@ class MetadataSink : public Module { if (frame->fIndex < 100) { - metadata.assign(reinterpret_cast(frame->data()), frame->size()); + metadata.assign(reinterpret_cast(frame->data()), 11); LOG_INFO << "Metadata\n frame_numer <" << frame->fIndex + 1 << "><" << metadata << ">"; if (!mProps.uniqMetadata) @@ -70,377 +72,390 @@ class MetadataSink : public Module MetadataSinkProps mProps; }; -void read_video_extract_frames(std::string videoPath, std::string outPath, boost::filesystem::path file, framemetadata_sp inputMetadata, FrameMetadata::FrameType frameType, bool parseFS, int uniqMetadata = 0) +struct SetupMp4ReaderTest { - LoggerProps loggerProps; - loggerProps.logLevel = boost::log::trivial::severity_level::info; - Logger::setLogLevel(boost::log::trivial::severity_level::info); - Logger::initLogger(loggerProps); - boost::filesystem::path dir(outPath); - - auto mp4ReaderProps = Mp4ReaderSourceProps(videoPath, parseFS); - auto mp4Reader = boost::shared_ptr(new Mp4ReaderSource(mp4ReaderProps)); - - mp4Reader->addOutPutPin(inputMetadata); - auto mp4Metadata = framemetadata_sp(new Mp4VideoMetadata("v_1")); - mp4Reader->addOutPutPin(mp4Metadata); - - boost::filesystem::path full_path = dir / file; - LOG_INFO << full_path; - auto fileWriterProps = FileWriterModuleProps(full_path.string()); - auto fileWriter = boost::shared_ptr(new FileWriterModule(fileWriterProps)); - std::vector mImagePin; - mImagePin = mp4Reader->getAllOutputPinsByType(frameType); - mp4Reader->setNext(fileWriter, mImagePin); - - StatSinkProps statSinkProps; - statSinkProps.logHealth = true; - statSinkProps.logHealthFrequency = 10; - auto statSink = boost::shared_ptr(new StatSink(statSinkProps)); - mp4Reader->setNext(statSink); - - auto metaSinkProps = MetadataSinkProps(uniqMetadata); - metaSinkProps.logHealth = true; - metaSinkProps.logHealthFrequency = 10; - auto metaSink = boost::shared_ptr(new MetadataSink(metaSinkProps)); - mp4Reader->setNext(metaSink); - - boost::shared_ptr p; - p = boost::shared_ptr(new PipeLine("test")); - p->appendModule(mp4Reader); - - if (!p->init()) + SetupMp4ReaderTest(std::string videoPath, framemetadata_sp inputMetadata, FrameMetadata::FrameType frameType, bool parseFS, bool isMetadata, int uniqMetadata = 0) { - throw AIPException(AIP_FATAL, "Engine Pipeline init failed. Check IPEngine Logs for more details."); - } + isVideoMetada = isMetadata; + LoggerProps loggerProps; + loggerProps.logLevel = boost::log::trivial::severity_level::info; + Logger::setLogLevel(boost::log::trivial::severity_level::info); + Logger::initLogger(loggerProps); - p->run_all_threaded(); + auto mp4ReaderProps = Mp4ReaderSourceProps(videoPath, parseFS, 0, true, false, false); + mp4Reader = boost::shared_ptr(new Mp4ReaderSource(mp4ReaderProps)); - boost::this_thread::sleep_for(boost::chrono::seconds(10)); + mp4Reader->addOutPutPin(inputMetadata); - p->stop(); - p->term(); - p->wait_for_all(); - p.reset(); -} + auto mp4Metadata = framemetadata_sp(new Mp4VideoMetadata("v_1")); + mp4Reader->addOutPutPin(mp4Metadata); -void random_seek_video(std::string skipDir, uint64_t seekStartTS, uint64_t seekEndTS, std::string startingVideoPath, std::string outPath, framemetadata_sp inputMetadata, FrameMetadata::FrameType frameType, boost::filesystem::path file) -{ - LoggerProps loggerProps; - loggerProps.logLevel = boost::log::trivial::severity_level::info; - Logger::setLogLevel(boost::log::trivial::severity_level::info); - Logger::initLogger(loggerProps); + std::vector mImagePin; + mImagePin = mp4Reader->getAllOutputPinsByType(frameType); - boost::filesystem::path dir(outPath); + sink = boost::shared_ptr(new ExternalSinkModule()); - auto mp4ReaderProps = Mp4ReaderSourceProps(startingVideoPath, false,true); - auto mp4Reader = boost::shared_ptr(new Mp4ReaderSource(mp4ReaderProps)); - mp4Reader->addOutPutPin(inputMetadata); - auto mp4Metadata = framemetadata_sp(new Mp4VideoMetadata("v_1")); - mp4Reader->addOutPutPin(mp4Metadata); + mp4Reader->setNext(sink, mImagePin); - mp4ReaderProps.skipDir = skipDir; + if (isMetadata) + { + auto metaSinkProps = MetadataSinkProps(uniqMetadata); + metaSinkProps.logHealth = true; + metaSinkProps.logHealthFrequency = 10; + metaSink = boost::shared_ptr(new MetadataSink(metaSinkProps)); + mp4Reader->setNext(metaSink); + } - boost::filesystem::path full_path = dir / file; - LOG_INFO << full_path; - auto fileWriterProps = FileWriterModuleProps(full_path.string()); - auto fileWriter = boost::shared_ptr(new FileWriterModule(fileWriterProps)); - std::vector mImagePin; - mImagePin = mp4Reader->getAllOutputPinsByType(frameType); - mp4Reader->setNext(fileWriter, mImagePin); + BOOST_TEST(mp4Reader->init()); + BOOST_TEST(sink->init()); - boost::shared_ptr p; - p = boost::shared_ptr(new PipeLine("test")); - p->appendModule(mp4Reader); + } - if (!p->init()) + ~SetupMp4ReaderTest() { - throw AIPException(AIP_FATAL, "Engine Pipeline init failed. Check IPEngine Logs for more details."); + mp4Reader->term(); + if(isVideoMetada) + metaSink->term(); + sink->term(); } - mp4Reader->setProps(mp4ReaderProps); - mp4Reader->randomSeek(seekStartTS,seekEndTS); - - p->run_all_threaded(); - - boost::this_thread::sleep_for(boost::chrono::seconds(10)); - - p->stop(); - p->term(); - p->wait_for_all(); - p.reset(); -} - -BOOST_AUTO_TEST_CASE(mp4v_to_rgb_24_jpg) -{ - std::string videoPath = "./data/Mp4_videos/jpg_video/20220928/0013/10.mp4"; - std::string outPath = "data/testOutput/outFrames"; - boost::filesystem::path file("frame_??????.jpg"); - auto frameType = FrameMetadata::FrameType::ENCODED_IMAGE; - auto encodedImageMetadata = framemetadata_sp(new EncodedImageMetadata(0, 0)); - bool parseFS = false; - read_video_extract_frames(videoPath, outPath, file, encodedImageMetadata, frameType, parseFS); -} - -BOOST_AUTO_TEST_CASE(mp4v_to_mono_8_jpg) -{ - std::string videoPath = "./data/Mp4_videos/jpg_video/20220928/0013/1666943213667.mp4"; - std::string outPath = "data/testOutput/outFrames"; - boost::filesystem::path file("frame_??????.jpg"); - auto frameType = FrameMetadata::FrameType::ENCODED_IMAGE; - auto encodedImageMetadata = framemetadata_sp(new EncodedImageMetadata(0, 0)); - bool parseFS = false; - read_video_extract_frames(videoPath, outPath, file, encodedImageMetadata, frameType, parseFS); -} + bool isVideoMetada; + boost::shared_ptr p = nullptr; + boost::shared_ptr mp4Reader; + boost::shared_ptr sink; + boost::shared_ptr metaSink; +}; -BOOST_AUTO_TEST_CASE(mp4v_read_metadata_jpg) +BOOST_AUTO_TEST_CASE(mp4v_to_jpg_frames_metadata) { - std::string videoPath = "data/Mp4_videos/jpg_video_metada/20220928/0014/1666949168743.mp4"; - std::string outPath = "./data/testOutput/outFrames"; - boost::filesystem::path file("frame_??????.jpg"); + std::string videoPath = "./data/Mp4_videos/jpg_video_metadata/20230513/0019/1686666193885.mp4"; + std::string outPath = "data/mp4Reader_saveOrCompare/jpeg/frame_000"; auto frameType = FrameMetadata::FrameType::ENCODED_IMAGE; auto encodedImageMetadata = framemetadata_sp(new EncodedImageMetadata(0, 0)); bool parseFS = false; - read_video_extract_frames(videoPath, outPath, file, encodedImageMetadata, frameType, parseFS); -} + SetupMp4ReaderTest s(videoPath, encodedImageMetadata, frameType, parseFS, true); -BOOST_AUTO_TEST_CASE(fs_parsing_jpg) -{ - /* file structure parsing test */ - std::string videoPath = "data/Mp4_videos/jpg_video/20220928/0013/1666943213667.mp4"; - std::string outPath = "data/testOutput/outFrames"; - boost::filesystem::path file("frame_??????.jpg"); - auto frameType = FrameMetadata::FrameType::ENCODED_IMAGE; - auto encodedImageMetadata = framemetadata_sp(new EncodedImageMetadata(0, 0)); - bool parseFS = true; - read_video_extract_frames(videoPath, outPath, file, encodedImageMetadata, frameType, parseFS, 5); -} + for (int i = 0; i < 180; i++) + { + s.mp4Reader->step(); + s.metaSink->step(); + auto frames = s.sink->pop(); + auto outputFrame = frames.begin()->second; + std::string fileName; + if (i % 10 == 0) + { + if (i < 10) + { + fileName = outPath + "00" + to_string(i) + ".jpg"; + } + else if (i >= 10 && i < 100) + { + fileName = outPath + "0" + to_string(i) + ".jpg"; + } + else + { + fileName = outPath + to_string(i) + ".jpg"; + } + Test_Utils::saveOrCompare(fileName.c_str(), (const uint8_t*)outputFrame->data(), outputFrame->size(), 0); + } + } -BOOST_AUTO_TEST_CASE(random_seek_jpg) -{ - std::string skipDir = "data/Mp4_videos/jpg_video_metada/"; - std::string startingVideoPath = "data/Mp4_videos/jpg_video_metada/20220928/0014/1666949168743.mp4"; - std::string outPath = "data/testOutput/outFrames"; - uint64_t seekStartTS = 1666949171743; - uint64_t seekEndTS = 1666949175743; - boost::filesystem::path file("frame_??????.jpg"); - auto frameType = FrameMetadata::FrameType::ENCODED_IMAGE; - auto encodedImageMetadata = framemetadata_sp(new EncodedImageMetadata(0, 0)); - random_seek_video(skipDir, seekStartTS, seekEndTS, startingVideoPath, outPath, encodedImageMetadata, frameType, file); } -BOOST_AUTO_TEST_CASE(mp4v_to_h264frames_metadata) +BOOST_AUTO_TEST_CASE(mp4v_to_h264_frames_metadata) { - std::string videoPath = "./data/Mp4_videos/h264_video_metadata/20221009/0019/1668001826042.mp4"; - std::string outPath = "data/testOutput/outFrames"; + std::string videoPath = "./data/Mp4_videos/h264_video_metadata/20230514/0011/1686723796848.mp4"; + std::string outPath = "data/mp4Reader_saveOrCompare/h264/frame_000"; bool parseFS = false; auto h264ImageMetadata = framemetadata_sp(new H264Metadata(0, 0)); - boost::filesystem::path file("frame_??????.h264"); auto frameType = FrameMetadata::FrameType::H264_DATA; - read_video_extract_frames(videoPath, outPath, file, h264ImageMetadata, frameType, parseFS); -} + SetupMp4ReaderTest s(videoPath, h264ImageMetadata, frameType, parseFS, true); -BOOST_AUTO_TEST_CASE(mp4v_to_h264frames) -{ - std::string videoPath = "./data/Mp4_videos/h264_video/20221010/0012/1668064027062.mp4"; - std::string outPath = "data/testOutput/outFrames"; - bool parseFS = false; - auto h264ImageMetadata = framemetadata_sp(new H264Metadata(0, 0)); - boost::filesystem::path file("frame_??????.h264"); - auto frameType = FrameMetadata::FrameType::H264_DATA; - read_video_extract_frames(videoPath, outPath, file, h264ImageMetadata, frameType, parseFS); + for (int i = 0; i < 180; i++) + { + s.mp4Reader->step(); + s.metaSink->step(); + auto frames = s.sink->pop(); + auto outputFrame = frames.begin()->second; + std::string fileName; + if (i % 10 == 0) + { + if (i < 10) + { + fileName = outPath + "00" + to_string(i) + ".h264"; + } + else if (i >= 10 && i < 100) + { + fileName = outPath + "0" + to_string(i) + ".h264"; + } + else + { + fileName = outPath + to_string(i) + ".h264"; + } + Test_Utils::saveOrCompare(fileName.c_str(), (const uint8_t*)outputFrame->data(), outputFrame->size(), 0); + } + } } -BOOST_AUTO_TEST_CASE(random_seek_h264) +BOOST_AUTO_TEST_CASE(read_timeStamp_from_custom_fileName) { - std::string skipDir = "data/Mp4_videos/h264_video/"; - std::string startingVideoPath = "./data/Mp4_videos/h264_video/20221010/0012/1668064027062.mp4"; + /* file structure parsing test */ + std::string videoPath = "./data/Mp4_videos/h264_video/apraH264.mp4"; std::string outPath = "data/testOutput/outFrames"; - uint64_t seekStartTS = 1668064030062; - uint64_t seekEndTS = 1668064032062; - boost::filesystem::path file("frame_??????.h264"); auto frameType = FrameMetadata::FrameType::H264_DATA; auto h264ImageMetadata = framemetadata_sp(new H264Metadata(0, 0)); + bool parseFS = false; + SetupMp4ReaderTest s(videoPath, h264ImageMetadata, frameType, parseFS, false); - random_seek_video(skipDir, seekStartTS, seekEndTS, startingVideoPath, outPath, h264ImageMetadata, frameType, file); + s.mp4Reader->step(); + auto frames = s.sink->pop(); + auto frame = frames.begin()->second; + BOOST_TEST(frame->timestamp == 1673420640350); } -BOOST_AUTO_TEST_CASE(fs_parsing_h264, *boost::unit_test::disabled()) +BOOST_AUTO_TEST_CASE(getSetProps_change_root_folder) { - /* file structure parsing test */ - std::string videoPath = "./data/Mp4_videos/h264_video/20221010/0012/1668064027062.mp4"; - std::string outPath = "data/testOutput/outFrames"; - boost::filesystem::path file("frame_??????.h264"); + std::string videoPath = "./data/Mp4_videos/h264_video_metadata/20230514/0011/1686723796848.mp4"; + std::string outPath = "./data/testOutput/outFrames/"; + bool parseFS = true; auto frameType = FrameMetadata::FrameType::H264_DATA; auto h264ImageMetadata = framemetadata_sp(new H264Metadata(0, 0)); - bool parseFS = true; - read_video_extract_frames(videoPath, outPath, file, h264ImageMetadata, frameType, 5, parseFS); + + SetupMp4ReaderTest s(videoPath, h264ImageMetadata, frameType, parseFS, false); + + frame_container frames; + // go till the second last frame + for (int i = 0; i < 230; i++) + { + s.mp4Reader->step(); + frames = s.sink->pop(); + } + + // read the last frame of the open video + s.mp4Reader->step(); + frames = s.sink->pop(); + auto lastFrame = frames.begin()->second; + BOOST_TEST(lastFrame->timestamp == 1686723806278); + + //change the video file path , Now read first frame new video of changed root dir instead of last frame of open video + auto propsChange = s.mp4Reader->getProps(); + propsChange.videoPath = "./data/Mp4_videos/mp4_seeks_tests_h264/20230111/0012/1673420640350.mp4"; + s.mp4Reader->setProps(propsChange); + s.mp4Reader->step(); + frames = s.sink->pop(); + auto frame = frames.begin()->second; + BOOST_TEST(frame->timestamp == 1673420640350); } -BOOST_AUTO_TEST_CASE(read_timeStamp_from_custom_fileName) +BOOST_AUTO_TEST_CASE(getSetProps_change_root_folder_with_custom_file_name) { - /* file structure parsing test */ - std::string videoPath = "./data/Mp4_videos/h264_video/apraH264.mp4"; - std::string outPath = "data/testOutput/outFrames"; - boost::filesystem::path file("frame_??????.h264"); + std::string videoPath = "./data/Mp4_videos/h264_video_metadata/20230514/0011/1686723796848.mp4"; + std::string outPath = "./data/testOutput/outFrames/"; + bool parseFS = true; auto frameType = FrameMetadata::FrameType::H264_DATA; auto h264ImageMetadata = framemetadata_sp(new H264Metadata(0, 0)); - bool parseFS = true; - read_video_extract_frames(videoPath, outPath, file, h264ImageMetadata, frameType, 5, parseFS); -} + SetupMp4ReaderTest s(videoPath, h264ImageMetadata, frameType, parseFS, false); + frame_container frames; + // go till the second last frame + for (int i = 0; i < 230; i++) + { + s.mp4Reader->step(); + frames = s.sink->pop(); + } -BOOST_AUTO_TEST_CASE(getSetProps) + // read the last frame of the open video + s.mp4Reader->step(); + frames = s.sink->pop(); + auto lastFrame = frames.begin()->second; + BOOST_TEST(lastFrame->timestamp == 1686723806278); + + //change the video file path , Now read first frame new video of changed root dir instead of last frame of open video + auto propsChange = s.mp4Reader->getProps(); + // To read custom file name parseFS needs to be disabled + propsChange.parseFS = false; + propsChange.videoPath = "./data/Mp4_videos/h264_video/apraH264.mp4"; + s.mp4Reader->setProps(propsChange); + s.mp4Reader->step(); + frames = s.sink->pop(); + auto frame = frames.begin()->second; + BOOST_TEST(frame->timestamp == 1673420640350); +} + +BOOST_AUTO_TEST_CASE(NotParseFs_to_parseFS) { - std::string videoPath = "./data/Mp4_videos/jpg_video/20220928/0013/1666943213667.mp4"; + std::string videoPath = "./data/Mp4_videos/mp4_seeks_tests_h264/apraH264.mp4"; std::string outPath = "./data/testOutput/outFrames/"; - std::string changedVideoPath = "./data/Mp4_videos/jpg_video_metada/20220928/0014/1666949168743.mp4"; - bool parseFS = true; - int uniqMetadata = 0; + bool parseFS = false; + auto frameType = FrameMetadata::FrameType::H264_DATA; + auto h264ImageMetadata = framemetadata_sp(new H264Metadata(0, 0)); - LoggerProps loggerProps; - loggerProps.logLevel = boost::log::trivial::severity_level::info; - Logger::setLogLevel(boost::log::trivial::severity_level::info); - Logger::initLogger(loggerProps); + SetupMp4ReaderTest s(videoPath, h264ImageMetadata, frameType, parseFS, false); + frame_container frames; - boost::filesystem::path dir(outPath); + s.mp4Reader->step(); + frames = s.sink->pop(); + auto frame = frames.begin()->second; + BOOST_TEST(frame->timestamp == 1673420640350); - auto mp4ReaderProps = Mp4ReaderSourceProps(videoPath, parseFS); - auto mp4Reader = boost::shared_ptr(new Mp4ReaderSource(mp4ReaderProps)); - auto encodedImageMetadata = framemetadata_sp(new EncodedImageMetadata(0, 0)); - mp4Reader->addOutPutPin(encodedImageMetadata); - auto mp4Metadata = framemetadata_sp(new Mp4VideoMetadata("v_1")); - mp4Reader->addOutPutPin(mp4Metadata); - - boost::filesystem::path file("frame_??????.jpg"); - boost::filesystem::path full_path = dir / file; - LOG_INFO << full_path; - auto fileWriterProps = FileWriterModuleProps(full_path.string()); - auto fileWriter = boost::shared_ptr(new FileWriterModule(fileWriterProps)); - std::vector encodedImagePin; - encodedImagePin = mp4Reader->getAllOutputPinsByType(FrameMetadata::ENCODED_IMAGE); - mp4Reader->setNext(fileWriter, encodedImagePin); - - StatSinkProps statSinkProps; - statSinkProps.logHealth = true; - statSinkProps.logHealthFrequency = 10; - auto statSink = boost::shared_ptr(new StatSink(statSinkProps)); - mp4Reader->setNext(statSink); - - auto metaSinkProps = MetadataSinkProps(uniqMetadata); - metaSinkProps.logHealth = true; - metaSinkProps.logHealthFrequency = 10; - auto metaSink = boost::shared_ptr(new MetadataSink(metaSinkProps)); - mp4Reader->setNext(metaSink); - - boost::shared_ptr p; - p = boost::shared_ptr(new PipeLine("test")); - p->appendModule(mp4Reader); - - if (!p->init()) + // go till the second last frame + for (int i = 0; i < 50; i++) { - throw AIPException(AIP_FATAL, "Engine Pipeline init failed. Check IPEngine Logs for more details."); + s.mp4Reader->step(); + frames = s.sink->pop(); } - p->run_all_threaded(); - boost::this_thread::sleep_for(boost::chrono::seconds(10)); + //change the video file path , Now read first frame new video of changed root dir instead of last frame of open video + auto propsChange = s.mp4Reader->getProps(); + // To read custom file name parseFS needs to be disabled + propsChange.parseFS = true; + propsChange.videoPath = "./data/Mp4_videos/mp4_seeks_tests_h264/20230501/0012/1685604318680.mp4"; + s.mp4Reader->setProps(propsChange); + s.mp4Reader->step(); + frames = s.sink->pop(); + frame = frames.begin()->second; + BOOST_TEST(frame->timestamp == 1685604318680); +} - Mp4ReaderSourceProps propsChange(changedVideoPath, true); - mp4Reader->setProps(propsChange); +BOOST_AUTO_TEST_CASE(getSetProps_change_root_folder_fail) +{ + std::string videoPath = "./data/Mp4_videos/h264_video_metadata/20230514/0011/1686723796848.mp4"; + std::string outPath = "./data/testOutput/outFrames/"; + bool parseFS = true; + auto frameType = FrameMetadata::FrameType::H264_DATA; + auto h264ImageMetadata = framemetadata_sp(new H264Metadata(0, 0)); - boost::this_thread::sleep_for(boost::chrono::seconds(10)); + SetupMp4ReaderTest s(videoPath, h264ImageMetadata, frameType, parseFS, false); + frame_container frames; + // go till the second last frame + for (int i = 0; i < 229; i++) + { + s.mp4Reader->step(); + frames = s.sink->pop(); + } - p->stop(); - p->term(); - p->wait_for_all(); - p.reset(); + // read the secoond last frame of the open video + s.mp4Reader->step(); + frames = s.sink->pop(); + auto lastFrame = frames.begin()->second; + BOOST_TEST(lastFrame->timestamp == 1686723806237); + + //change the video file path , Now read first frame new video of changed root dir instead of last frame of open video + auto propsChange = s.mp4Reader->getProps(); + propsChange.parseFS = false; + //this path dosen't exist on disk - so cannoical path call will fail it, hence continue reading the open video + propsChange.videoPath = "./data/Mp4_videos/videos/apraH264.mp4"; + s.mp4Reader->setProps(propsChange); + s.mp4Reader->step(); + frames = s.sink->pop(); + auto frame = frames.begin()->second; + // read the last frame of the open video + BOOST_TEST(frame->timestamp == 1686723806278); } BOOST_AUTO_TEST_CASE(parse_root_dir_and_find_the_video) { std::string videoPath = "./data/Mp4_videos/jpg_video"; - std::string outPath = "data/testOutput/outFrames"; boost::filesystem::path file("frame_??????.jpg"); auto frameType = FrameMetadata::FrameType::ENCODED_IMAGE; auto encodedImageMetadata = framemetadata_sp(new EncodedImageMetadata(0, 0)); bool parseFS = false; - read_video_extract_frames(videoPath, outPath, file, encodedImageMetadata, frameType, parseFS); + SetupMp4ReaderTest s(videoPath, encodedImageMetadata, frameType, parseFS, false); + + BOOST_TEST(s.mp4Reader->step()); + auto frames = s.sink->pop(); } -//Note: We still have to implement the feature to read and write video and simultaneously -BOOST_AUTO_TEST_CASE(mp4reader_waits_when_no_video_and_reads_whenever_video_is_written, *boost::unit_test::disabled()) +BOOST_AUTO_TEST_CASE(check_exposed_params) { - int width = 1280; - int height = 720; + std::string startingVideoPath = "data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895162221.mp4"; + bool parseFS = true; + + auto mp4ReaderProps = Mp4ReaderSourceProps(startingVideoPath, parseFS, 0, true, false, false); + auto mp4Reader = boost::shared_ptr(new Mp4ReaderSource(mp4ReaderProps)); - LoggerProps loggerProps; - loggerProps.logLevel = boost::log::trivial::severity_level::info; - Logger::setLogLevel(boost::log::trivial::severity_level::info); - Logger::initLogger(loggerProps); + auto encodedImageMetadata = framemetadata_sp(new EncodedImageMetadata(0, 0)); - auto fileReaderProps = FileReaderModuleProps("./data/re3_filtered", 0, -1); - fileReaderProps.fps = 24; - fileReaderProps.readLoop = false; + mp4Reader->addOutPutPin(encodedImageMetadata); + auto mp4Metadata = framemetadata_sp(new Mp4VideoMetadata("v_1")); + mp4Reader->addOutPutPin(mp4Metadata); - auto fileReader = boost::shared_ptr(new FileReaderModule(fileReaderProps)); - auto encodedImageMetadata = framemetadata_sp(new EncodedImageMetadata(width, height)); - fileReader->addOutputPin(encodedImageMetadata); + std::vector mImagePin; + mImagePin = mp4Reader->getAllOutputPinsByType(FrameMetadata::ENCODED_IMAGE); + + auto sink = boost::shared_ptr(new ExternalSinkModule()); + mp4Reader->setNext(sink); + + mp4Reader->init(); + sink->init(); + /* process one frame */ + mp4Reader->step(); + auto frames = sink->pop(); + + auto imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1655895162221); + + BOOST_TEST(mp4Reader->getOpenVideoFPS() == 60, boost::test_tools::tolerance(0.99)); + BOOST_TEST(mp4Reader->getOpenVideoDurationInSecs() == 3); + BOOST_TEST(mp4Reader->getOpenVideoFrameCount() == 181); +} - auto mp4WriterSinkProps = Mp4WriterSinkProps(1, 10, 24, "./data/testOutput/mp4_videos/"); - mp4WriterSinkProps.logHealth = true; - mp4WriterSinkProps.logHealthFrequency = 100; - auto mp4WriterSink = boost::shared_ptr(new Mp4WriterSink(mp4WriterSinkProps)); - fileReader->setNext(mp4WriterSink); - boost::filesystem::path dir("./data/testOutput/outFrames/" ); +BOOST_AUTO_TEST_CASE(max_buffer_size_change_props) +{ + std::string startingVideoPath = "data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895162221.mp4"; + int width = 22, height = 30; + bool parseFS = true; - auto mp4ReaderProps = Mp4ReaderSourceProps("./data/testOutput/mp4_videos/", false,10); + auto mp4ReaderProps = Mp4ReaderSourceProps(startingVideoPath, parseFS, 0, true, false, false); auto mp4Reader = boost::shared_ptr(new Mp4ReaderSource(mp4ReaderProps)); - auto imageMetadata = framemetadata_sp(new EncodedImageMetadata(0, 0)); - mp4Reader->addOutPutPin(imageMetadata); - auto mp4Metadata = framemetadata_sp(new Mp4VideoMetadata("v_1")); - mp4Reader->addOutPutPin(mp4Metadata); - - boost::filesystem::path file("frame_??????.jpg"); - boost::filesystem::path full_path = dir / file; - LOG_INFO << full_path; - auto fileWriterProps = FileWriterModuleProps(full_path.string()); - auto fileWriter = boost::shared_ptr(new FileWriterModule(fileWriterProps)); - std::vector encodedImagePin; - encodedImagePin = mp4Reader->getAllOutputPinsByType(FrameMetadata::ENCODED_IMAGE); - mp4Reader->setNext(fileWriter, encodedImagePin); - - StatSinkProps statSinkProps; - statSinkProps.logHealth = true; - statSinkProps.logHealthFrequency = 10; - auto statSink = boost::shared_ptr(new StatSink(statSinkProps)); - mp4Reader->setNext(statSink); - - auto metaSinkProps = MetadataSinkProps(0); - metaSinkProps.logHealth = true; - metaSinkProps.logHealthFrequency = 10; - auto metaSink = boost::shared_ptr(new MetadataSink(metaSinkProps)); - mp4Reader->setNext(metaSink); - - boost::shared_ptr p; - p = boost::shared_ptr(new PipeLine("test")); - p->appendModule(fileReader); - p->appendModule(mp4Reader); - - if (!p->init()) - { - throw AIPException(AIP_FATAL, "Engine Pipeline init failed. Check IPEngine Logs for more details."); - } - p->run_all_threaded(); - boost::this_thread::sleep_for(boost::chrono::seconds(15)); + auto encodedImageMetadata = framemetadata_sp(new EncodedImageMetadata(0, 0)); - p->stop(); - p->term(); - p->wait_for_all(); - p.reset(); + auto pinId = mp4Reader->addOutPutPin(encodedImageMetadata); + auto mp4Metadata = framemetadata_sp(new Mp4VideoMetadata("v_1")); + mp4Reader->addOutPutPin(mp4Metadata); + std::vector mImagePin; + mImagePin = mp4Reader->getAllOutputPinsByType(FrameMetadata::ENCODED_IMAGE); + + auto sink = boost::shared_ptr(new ExternalSinkModule()); + + mp4Reader->setNext(sink); + + mp4Reader->init(); + sink->init(); + /* process one frame */ + mp4Reader->step(); + auto frames = sink->pop(); + auto imgFrame = frames.begin()->second; + BOOST_TEST(imgFrame->timestamp == 1655895162221); + + // change prop + auto mp4Props = mp4Reader->getProps(); + mp4Props.biggerFrameSize = 100; + mp4Reader->setProps(mp4Props); + + // process next frame + mp4Reader->step(); + frames = sink->pop(); + auto frame = frames.begin()->second; + BOOST_TEST(frame->isMp4ErrorFrame()); + auto errorFrame = dynamic_cast(frame.get()); + auto type = errorFrame->errorCode; + BOOST_TEST(type == MP4_BUFFER_TOO_SMALL); + + // change prop + mp4Props = mp4Reader->getProps(); + mp4Props.biggerFrameSize = 300; + mp4Reader->setProps(mp4Props); + mp4Reader->step(); + + // process next frame (size is > 100 i.e. 284 Bytes) + mp4Reader->step(); + frames = sink->pop(); + BOOST_TEST((frames.find(pinId) != frames.end())); } BOOST_AUTO_TEST_SUITE_END() diff --git a/base/test/mp4writersink_tests.cpp b/base/test/mp4writersink_tests.cpp index 16c9d59b9..9594841eb 100644 --- a/base/test/mp4writersink_tests.cpp +++ b/base/test/mp4writersink_tests.cpp @@ -180,7 +180,7 @@ void read_write(std::string videoPath, std::string outPath, int width, int heigh boost::filesystem::path dir(outPath); - auto mp4ReaderProps = Mp4ReaderSourceProps(videoPath, parseFS); + auto mp4ReaderProps = Mp4ReaderSourceProps(videoPath, parseFS,0,true,false,false); mp4ReaderProps.logHealth = true; mp4ReaderProps.logHealthFrequency = 300; auto mp4Reader = boost::shared_ptr(new Mp4ReaderSource(mp4ReaderProps)); @@ -205,7 +205,7 @@ void read_write(std::string videoPath, std::string outPath, int width, int heigh } p->run_all_threaded(); - boost::this_thread::sleep_for(boost::chrono::seconds(45)); + boost::this_thread::sleep_for(boost::chrono::seconds(8)); p->stop(); p->term(); @@ -542,20 +542,6 @@ BOOST_AUTO_TEST_CASE(single_file_given_name_h264) writeH264(true,10,outFolderPath, UINT32_MAX); } -BOOST_AUTO_TEST_CASE(read_mul_write_one_as_recorded) -{ - // two videos (19sec, 60 sec) with 101sec time gap - // writes a 79 sec video - std::string videoPath = "data/mp4_videos/h264_videos_dts_test/20221010/0012/1668001826042.mp4"; - std::string outPath = "data/testOutput/file_as_rec.mp4"; - bool parseFS = true; - - // write a fixed rate video with no gaps - bool recordedTSBasedDTS = true; - auto h264ImageMetadata = framemetadata_sp(new H264Metadata(0,0)); - read_write(videoPath, outPath, 704, 576, h264ImageMetadata, recordedTSBasedDTS, parseFS, UINT32_MAX); -} - BOOST_AUTO_TEST_CASE(write_mp4video_h264_step) { std::string inFolderPath = "./data/h264_data"; diff --git a/base/test/ordered_cache_of_files_tests.cpp b/base/test/ordered_cache_of_files_tests.cpp new file mode 100644 index 000000000..6c594d059 --- /dev/null +++ b/base/test/ordered_cache_of_files_tests.cpp @@ -0,0 +1,812 @@ +#include +#include + +#include +#include +#include +#include + +#include "Logger.h" +#include "OrderedCacheOfFiles.h" +#include "AIPExceptions.h" + +BOOST_AUTO_TEST_SUITE(ordered_file_cache) + +struct LoggerSetup +{ + LoggerSetup() + { + LoggerProps loggerProps; + loggerProps.logLevel = boost::log::trivial::severity_level::info; + Logger::setLogLevel(boost::log::trivial::severity_level::info); + Logger::initLogger(loggerProps); + } + + ~LoggerSetup() + { + } +}; + +struct DiskFiles +{ + std::map files = + { + {1655895162221, "data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895162221.mp4"}, + {1655895288956, "data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895288956.mp4"}, + {1655919060000, "data/Mp4_videos/mp4_seek_tests/20220522/0023/1655919060000.mp4"}, + {1655926320000, "data/Mp4_videos/mp4_seek_tests/20220523/0001/1655926320000.mp4"} + + }; + + /* sts -> duration (msec) [imprecise] */ + std::map fileDurations = + { + {1655895162221, 4000}, + {1655895288956, 10005}, + {1655919060000, 22000}, + {1655926320000, 60000} + + }; +}; + +/* -----------Functionality Tests---------------- */ + +BOOST_AUTO_TEST_CASE(fsParseExactMatch) +{ + LoggerSetup setup; + DiskFiles diskFiles; + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + OrderedCacheOfFiles cof(dir, 100, 100, 1000); + cof.parseFiles(102, true); + std::string fileName; + // check the map + bool direction = true; + for (auto it = diskFiles.files.begin(); it != diskFiles.files.end(); ++it) + { + auto timestamp = it->first; + auto isFilefound = cof.getFileFromCache(timestamp, direction, fileName); + BOOST_TEST(isFilefound); + LOG_INFO << "Checking: fsParseExactMatch" << it->second << "<>" << fileName; + BOOST_TEST(boost::filesystem::equivalent(boost::filesystem::path(it->second), boost::filesystem::path(fileName))); + } + // reverse + direction = false; + for (auto it = diskFiles.files.begin(); it != diskFiles.files.end(); ++it) + { + auto timestamp = it->first; + auto isFilefound = cof.getFileFromCache(timestamp, direction, fileName); + BOOST_TEST(isFilefound); + LOG_INFO << "Checking: fsParseExactMatch" << it->second << "<>" << fileName; + BOOST_TEST(boost::filesystem::equivalent(boost::filesystem::path(it->second), boost::filesystem::path(fileName))); + } +} + +BOOST_AUTO_TEST_CASE(fwdBasic_FileOpen_QueryHole) +{ + LoggerSetup setup; + DiskFiles diskFiles; + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + OrderedCacheOfFiles cof(dir, 100, 100, 1000); + cof.parseFiles(102, true); + std::string fileName; + + // check the map + bool direction = true; + for (auto it = diskFiles.files.begin(); it != diskFiles.files.end(); ++it) + { + auto ts = it->first + diskFiles.fileDurations[it->first] + 5; + auto isFileFound = cof.getFileFromCache(ts, direction, fileName); + if (isFileFound) + { + BOOST_TEST(isFileFound); + auto nextDiskFile = it; + nextDiskFile++; + LOG_INFO << "Checking bwdBasicOpen_NoCacheUse: ts " << it->first << "> " << fileName << "<>" << nextDiskFile->second; + BOOST_TEST(boost::filesystem::equivalent(boost::filesystem::path(fileName), boost::filesystem::path(nextDiskFile->second))); + } + else + { + BOOST_TEST(fileName == ""); + } + } + + +} + +BOOST_AUTO_TEST_CASE(bwd_BasicFileOpen_QueryHole) +{ + LoggerSetup setup; + DiskFiles diskFiles; + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + OrderedCacheOfFiles cof(dir, 100, 100, 1000); + cof.parseFiles(102, true); + std::string fileName; + // check the map + bool direction = false; + for (auto it = diskFiles.files.begin(); it != diskFiles.files.end(); ++it) + { + auto ts = it->first + diskFiles.fileDurations[it->first]; + + auto isFileFound = cof.getFileFromCache(ts, direction, fileName); + BOOST_TEST(isFileFound); + LOG_INFO << "Checking bwdBasicOpen_NoCacheUse: ts " << it->first << "> " << fileName << "<>" << it->second; + BOOST_TEST(boost::filesystem::equivalent(boost::filesystem::path(fileName), boost::filesystem::path(it->second))); + + } +} + +BOOST_AUTO_TEST_CASE(biDirectional_BasicFileOpen_QueryMidFile) +{ + LoggerSetup setup; + DiskFiles diskFiles; + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + OrderedCacheOfFiles cof(dir, 100, 100, 1000); + cof.parseFiles(102, true); + std::string fileName; + // check the map + bool direction = true; + for (auto it = diskFiles.files.begin(); it != diskFiles.files.end(); ++it) + { + // As the queryTS is present in the file , getFileFromCache() will give the actual fileName and return true. + auto ts = it->first + diskFiles.fileDurations[it->first] - 1200; + auto isFileFound = cof.getFileFromCache(ts, direction, fileName); + BOOST_TEST(isFileFound); + LOG_INFO << "Checking bwdBasicOpen_NoCacheUse: ts " << it->first << "> " << fileName << "<>" << it->second; + BOOST_TEST(boost::filesystem::equivalent(boost::filesystem::path(fileName), boost::filesystem::path(it->second))); + } + + direction = false; + for (auto it = diskFiles.files.begin(); it != diskFiles.files.end(); ++it) + { + auto ts = it->first + diskFiles.fileDurations[it->first] - 1200; + auto isFileFound = cof.getFileFromCache(ts, direction, fileName); + BOOST_TEST(isFileFound); + LOG_INFO << "Checking bwdBasicOpen_NoCacheUse: ts " << it->first << "> " << fileName << "<>" << it->second; + BOOST_TEST(boost::filesystem::equivalent(boost::filesystem::path(fileName), boost::filesystem::path(it->second))); + } +} + + +BOOST_AUTO_TEST_CASE(random_seek_fwd_queryBeforeCache) +{ + LoggerSetup setup; + DiskFiles diskFiles; + + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + OrderedCacheOfFiles cof(dir, 100, 100, 100); + uint64_t skipMsecs = 0; + // parse all files + cof.parseFiles(102, true); + // beyond EOF + auto timeStamp = 1655895162221 - 10000; + bool direction = true; + std::string fileName; + //EOF returns empty string for the fileName and isFilePresent becomes false + auto isFilePresent = cof.getRandomSeekFile(timeStamp, direction, skipMsecs, fileName); + + BOOST_TEST(skipMsecs == 0); + BOOST_TEST(isFilePresent); + BOOST_TEST(boost::filesystem::equivalent(diskFiles.files[1655895162221], fileName)); +} + +/* ----------End Of Files Detection Tests----------- */ +BOOST_AUTO_TEST_CASE(fwdEOFDetection) +{ + LoggerSetup setup; + DiskFiles diskFiles; + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + OrderedCacheOfFiles cof(dir, 100, 100, 1000); + cof.parseFiles(102, true); + // check the map + bool direction = true; + + auto diskFileIter = diskFiles.files.begin(); + auto currentFile = boost::filesystem::canonical(diskFileIter->second).string(); + try + { + while (true) + { + + currentFile = cof.getNextFileAfter(currentFile, direction); + ++diskFileIter; + BOOST_TEST(boost::filesystem::equivalent(boost::filesystem::path(currentFile), boost::filesystem::path(diskFileIter->second))); + } + } + catch (AIP_Exception& exception) + { + BOOST_TEST(exception.getCode() == MP4_OCOF_END); + } + catch (...) + { + BOOST_TEST(false); + } +} + +BOOST_AUTO_TEST_CASE(bwdEOFDetection_getNextFileAfter) +{ + LoggerSetup setup; + DiskFiles diskFiles; + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + OrderedCacheOfFiles cof(dir, 100, 100, 1000); + cof.parseFiles(102, true); + // check the map + bool direction = false; + + auto diskFileIter = diskFiles.files.rbegin(); + auto currentFile = boost::filesystem::canonical(diskFileIter->second).string(); + try + { + while (true) + { + + currentFile = cof.getNextFileAfter(currentFile, direction); + ++diskFileIter; + BOOST_TEST(boost::filesystem::equivalent(boost::filesystem::path(currentFile), boost::filesystem::path(diskFileIter->second))); + } + } + catch (AIP_Exception& exception) + { + BOOST_TEST(exception.getCode() == MP4_OCOF_END); + } +} + +// skipVideoFile = +// skipMsecs = ts - start_ts of skipVideoFile +// return 1 - if success +// return 0 - if fails +// return -1 -> EOF -> catch(..) => return -1 + +BOOST_AUTO_TEST_CASE(random_seek_fwd_query_mid_file) +{ + LoggerSetup setup; + DiskFiles diskFiles; + + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + OrderedCacheOfFiles cof(dir, 2, 100, 1000); + bool direction = true; + cof.parseFiles(102, direction); + uint64_t skipMsecs = 0; + auto iter = diskFiles.files.begin(); + ++iter; + auto timeStamp = iter->first + 200; + std::string fileName; + auto ret = cof.getRandomSeekFile(timeStamp, direction, skipMsecs, fileName); + BOOST_TEST(boost::filesystem::equivalent(iter->second, fileName)); + BOOST_TEST(ret); +} + +BOOST_AUTO_TEST_CASE(random_seek_bwd_query_mid_file) +{ + LoggerSetup setup; + DiskFiles diskFiles; + + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + OrderedCacheOfFiles cof(dir, 2, 100, 1000); + bool direction = false; + cof.parseFiles(102, true); + uint64_t skipMsecs = 0; + std::string fileName; + + auto iter = diskFiles.files.begin(); + ++iter; + auto timeStamp = iter->first + 200; + + auto ret = cof.getRandomSeekFile(timeStamp, direction, skipMsecs, fileName); + + BOOST_TEST(boost::filesystem::equivalent(iter->second, fileName)); + BOOST_TEST(ret); +} + +BOOST_AUTO_TEST_CASE(random_seek_bwd_queryhole) +{ + LoggerSetup setup; + DiskFiles diskFiles; + + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + OrderedCacheOfFiles cof(dir, 100, 100, 1000); + cof.parseFiles(102, true); + uint64_t skipMsecs = 0; + auto timeStamp = diskFiles.fileDurations.begin()->first + diskFiles.fileDurations.begin()->second + 300; + bool direction = false; + std::string fileName; + auto ret = cof.getRandomSeekFile(timeStamp, direction, skipMsecs, fileName); + BOOST_TEST(skipMsecs == 0); + BOOST_TEST(ret); + BOOST_TEST(boost::filesystem::equivalent(fileName, diskFiles.files.begin()->second)); +} + +BOOST_AUTO_TEST_CASE(random_seek_fwd_EOC) +{ + LoggerSetup setup; + DiskFiles diskFiles; + + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + OrderedCacheOfFiles cof(dir, 2, 100, 1000); + cof.parseFiles(102, true); // only first two files are now in cache + uint64_t skipMsecs = 0; + + // ts of third + uint64_t timeStamp = 1655895288956 + 25000; + bool direction = true; + std::string fileName; + + auto isFileInCache = cof.getFileFromCache(timeStamp, direction, fileName); + BOOST_TEST(!isFileInCache); + + auto isFilePresent = cof.getRandomSeekFile(timeStamp, direction, skipMsecs, fileName); + BOOST_TEST(isFilePresent); +} + +BOOST_AUTO_TEST_CASE(random_seek_fwd_EOC_green) +{ + // green => cache has [3,4] - seek happens in file [1,2] + LoggerSetup setup; + DiskFiles diskFiles; + + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + + OrderedCacheOfFiles cof(dir, 2, 100, 1000); + // last one files in cache + bool direction = true; + cof.parseFiles(1655919060000, direction); + + // skip is in middle of second file + uint64_t skipTS = 1655895288956 + 2000; + std::string fileName; + uint64_t skipMsecs; + + auto isFilePresent = cof.getRandomSeekFile(skipTS, direction, skipMsecs, fileName); + BOOST_TEST(boost::filesystem::equivalent(fileName, diskFiles.files[1655895288956])); + BOOST_TEST(skipMsecs == 2000); +} + +BOOST_AUTO_TEST_CASE(random_seek_bwd_EOC_green) +{ + // green => cache has files [1,2] - seek happens in file [3,4] + LoggerSetup setup; + DiskFiles diskFiles; + + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + + OrderedCacheOfFiles cof(dir, 2, 100, 1000); + // first files in cache + bool direction = false; + bool includeExactMatch = true; + cof.parseFiles(1655895288956, direction, includeExactMatch); + + // skip is in middle of third file + uint64_t skipTS = 1655919060000 + 2000; + std::string fileName; + uint64_t skipMsecs; + + auto isFilePresent = cof.getRandomSeekFile(skipTS, direction, skipMsecs, fileName); + BOOST_TEST(boost::filesystem::equivalent(fileName, diskFiles.files[1655919060000])); + BOOST_TEST(skipMsecs == 2000); +} + +BOOST_AUTO_TEST_CASE(random_seek_bwd_EOC) +{ + LoggerSetup setup; + DiskFiles diskFiles; + + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + OrderedCacheOfFiles cof(dir, 2, 3, 3); + cof.parseFiles(102, true); + uint64_t skipMsecs = 0; + auto timeStamp = 1655895162221 + 100; + bool direction = false; + std::string fileName; + + cof.parseFiles(1655919060000 - 1, true); + + auto isFileInCache = cof.getFileFromCache(timeStamp, direction, fileName); + BOOST_TEST(!isFileInCache); + + auto isFilePresent = cof.getRandomSeekFile(timeStamp, direction, skipMsecs, fileName); + BOOST_TEST(isFilePresent); +} + +BOOST_AUTO_TEST_CASE(random_seek_bwd_EOF) +{ + LoggerSetup setup; + DiskFiles diskFiles; + + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + OrderedCacheOfFiles cof(dir, 3, 100, 1000); + cof.parseFiles(102, true); + uint64_t skipMsecs = 0; + auto timeStamp = diskFiles.fileDurations.begin()->first - 1000; + bool direction = false; + std::string fileName; + //EOF returns empty string for the fileName and isFilePresent becomes false + auto isFilePresent = cof.getRandomSeekFile(timeStamp, direction, skipMsecs, fileName); + + BOOST_TEST(skipMsecs == 0); + BOOST_TEST(!isFilePresent); + BOOST_TEST(fileName == ""); +} + +BOOST_AUTO_TEST_CASE(random_seek_fwd_EOF) +{ + LoggerSetup setup; + DiskFiles diskFiles; + + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + OrderedCacheOfFiles cof(dir, 100, 100, 100); + uint64_t skipMsecs = 0; + // parse all files + cof.parseFiles(102, true); + // beyond EOF + auto timeStamp = 1655926320000 + 90000; + bool direction = true; + std::string fileName; + //EOF returns empty string for the fileName and isFilePresent becomes false + auto isFilePresent = cof.getRandomSeekFile(timeStamp, direction, skipMsecs, fileName); + + BOOST_TEST(skipMsecs == 0); + BOOST_TEST(!isFilePresent); + BOOST_TEST(fileName == ""); +} + +BOOST_AUTO_TEST_CASE(dropstrategy_fwd) +{ + LoggerSetup setup; + DiskFiles diskFiles; + + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + uint32_t cacheSize = 2; + uint32_t lowerWaterMark = 3; + uint32_t upperWaterMark = 3; + + OrderedCacheOfFiles cof(dir, cacheSize, lowerWaterMark, upperWaterMark); + + cof.parseFiles(102, true); + bool direction = true; + std::string fileName; + std::string secondFileNameBeforeDeleting; + std::string secondFileNameAfterDeleting; + cof.getFileFromCache(0, direction, secondFileNameBeforeDeleting); + + cof.parseFiles(1655919060000 - 100, true); + + cof.getFileFromCache(0, direction, secondFileNameAfterDeleting); + + BOOST_TEST(secondFileNameBeforeDeleting != secondFileNameAfterDeleting); + + auto videoCacheSize = cof.getCacheSize(); + + BOOST_TEST(videoCacheSize == 2); +} + +BOOST_AUTO_TEST_CASE(dropstrategy_bwd) +{ + LoggerSetup setup; + DiskFiles diskFiles; + + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + uint32_t cacheSize = 2; + uint32_t lowerWaterMark = 3; + uint32_t upperWaterMark = 3; + + OrderedCacheOfFiles cof(dir, cacheSize, lowerWaterMark, upperWaterMark); + + cof.parseFiles(102, true); + bool direction = true; + std::string fileName; + std::string secondFileNameBeforeDeleting; + std::string secondFileNameAfterDeleting; + cof.getFileFromCache(0, direction, secondFileNameBeforeDeleting); + + auto videoCacheSize = cof.getCacheSize(); + + BOOST_TEST(videoCacheSize == 2); + + cof.parseFiles(1655919060000 - 100, true); + + cof.getFileFromCache(0, direction, secondFileNameAfterDeleting); + + BOOST_TEST(secondFileNameBeforeDeleting != secondFileNameAfterDeleting); + + cof.parseFiles(UINT64_MAX, false); + + std::string firstFileNameAfterDeletingInBwd; + cof.getFileFromCache(UINT64_MAX, false, firstFileNameAfterDeletingInBwd); + + videoCacheSize = cof.getCacheSize(); + + BOOST_TEST(videoCacheSize == 2); + + BOOST_TEST(boost::filesystem::equivalent(firstFileNameAfterDeletingInBwd, secondFileNameBeforeDeleting)); +} + +BOOST_AUTO_TEST_CASE(skipTS_exact_match_with_fileName) +{ + LoggerSetup setup; + DiskFiles diskFiles; + + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + uint32_t cacheSize = 2; + uint32_t lowerWaterMark = 3; + uint32_t upperWaterMark = 3; + std::string fileName; + + OrderedCacheOfFiles cof(dir, cacheSize, lowerWaterMark, upperWaterMark); + uint64_t skipMsecs = 0; + + // 3rd and 4th file will come in cache + cof.parseFiles(1655919060000 - 100, true); + + auto isFilePresent = cof.getRandomSeekFile(diskFiles.files.begin()->first, true, skipMsecs, fileName); + BOOST_TEST(isFilePresent); + BOOST_TEST(boost::filesystem::equivalent(fileName, diskFiles.files.begin()->second)); +} + +BOOST_AUTO_TEST_CASE(get_start_end) +{ + LoggerSetup setup; + DiskFiles diskFiles; + + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + uint32_t cacheSize = 8; + uint32_t lowerWaterMark = 10; + uint32_t upperWaterMark = 20; + std::string fileName; + + OrderedCacheOfFiles cof(dir, cacheSize, lowerWaterMark, upperWaterMark); + cof.parseFiles(0, true); + + std::string videoFile = dir + "/20220522/0016/1655895288956.mp4"; + uint64_t start_ts, end_ts; + bool isFileInCache = cof.fetchFromCache(videoFile, start_ts, end_ts); + BOOST_TEST(isFileInCache == true); + BOOST_TEST(start_ts == 1655895288956); + BOOST_TEST(end_ts == 0); + + // force update from disk + isFileInCache = cof.fetchAndUpdateFromDisk(videoFile, start_ts, end_ts); + BOOST_TEST(isFileInCache == true); + BOOST_TEST(start_ts == 1655895288956); + BOOST_TEST(end_ts == 1655895298961); + + // not in cache + not on disk + videoFile = dir + "\\20220630\\0012\\1659163533000.mp4"; + isFileInCache = cof.fetchFromCache(videoFile, start_ts, end_ts); + BOOST_TEST(isFileInCache == false); + BOOST_TEST(start_ts == 0); + BOOST_TEST(end_ts == 0); +} + +BOOST_AUTO_TEST_CASE(parse_noFirstRelevantFileFound_prevFile) +{ + // tests the case where random seek forces a fresh disk parse + // and exactly 1 file is being added to cache that has start_ts < seekTS + LoggerSetup setup; + DiskFiles diskFiles; + + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + uint32_t cacheSize = 3; + uint32_t lowerWaterMark = 4; + uint32_t upperWaterMark = 4; + std::string fileName; + + OrderedCacheOfFiles cof(dir, cacheSize, lowerWaterMark, upperWaterMark); + uint64_t skipMsecs = 0; + + // 1st file will come in cache + cof.parseFiles(1655895162221 - 10, true); + + // seek to ts that is inside the last file + auto isFilePresent = cof.getRandomSeekFile(1655926320000 + 10, true, skipMsecs, fileName); + BOOST_TEST(isFilePresent); + BOOST_TEST(boost::filesystem::equivalent(fileName, diskFiles.files[1655926320000])); +} + +BOOST_AUTO_TEST_CASE(parse_noFirstRelevantFileFound_exactMatch) +{ + LoggerSetup setup; + DiskFiles diskFiles; + + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + uint32_t cacheSize = 1; + uint32_t lowerWaterMark = 4; + uint32_t upperWaterMark = 4; + std::string fileName; + + OrderedCacheOfFiles cof(dir, cacheSize, lowerWaterMark, upperWaterMark); + uint64_t skipMsecs = 0; + + // 1st file will come in cache + cof.parseFiles(1655895162221 - 10, true); + + // seek to ts that is inside the last file + auto isFilePresent = cof.getRandomSeekFile(1655926320000, true, skipMsecs, fileName); + BOOST_TEST(isFilePresent); + BOOST_TEST(boost::filesystem::equivalent(fileName, diskFiles.files[1655926320000])); +} + +void printCache(std::map > &snap) +{ + LOG_INFO << "===printing cache==="; + for (auto it = snap.begin(); it != snap.end(); ++it) + { + LOG_INFO << it->first << ": <" << it->second.first << "> <" << it->second.second << ">"; + } +} + +BOOST_AUTO_TEST_CASE(fwd_seek_trig_parse_hole_check) +{ + /* checks that no holes should be created in cache on fresh parse while seeking + fwd dir */ + LoggerSetup setup; + DiskFiles diskFiles; + + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + uint32_t cacheSize = 1; + uint32_t lowerWaterMark = 5; + uint32_t upperWaterMark = 6; + std::string fileName; + + OrderedCacheOfFiles cof(dir, cacheSize, lowerWaterMark, upperWaterMark); + uint64_t skipMsecs = 0; + + // only 2 file in cache (first hours) even though size is 1 + cof.parseFiles(1655895162221 - 10, true); + auto snap = cof.getSnapShot(); + printCache(snap); + BOOST_TEST((snap.find(boost::filesystem::canonical(diskFiles.files[1655895162221]).string()) != snap.end())); + BOOST_TEST((snap.find(boost::filesystem::canonical(diskFiles.files[1655895288956]).string()) != snap.end())); + + // seek to end + auto isFilePresent = cof.getRandomSeekFile(1655926320000 + 5, true, skipMsecs, fileName); + BOOST_TEST(isFilePresent); + BOOST_TEST(boost::filesystem::equivalent(fileName, diskFiles.files[1655926320000])); + + // all four file will come in cache + snap = cof.getSnapShot(); + printCache(snap); + + for (auto it = diskFiles.files.begin(); it != diskFiles.files.end(); ++it) + { + auto fileItr = snap.find(boost::filesystem::canonical(it->second).string()); + BOOST_TEST((fileItr != snap.end())); + } +} + +BOOST_AUTO_TEST_CASE(bwd_seek_trig_parse_hole_check) +{ + LoggerSetup setup; + DiskFiles diskFiles; + + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + uint32_t cacheSize = 1; + uint32_t lowerWaterMark = 5; + uint32_t upperWaterMark = 6; + std::string fileName; + + OrderedCacheOfFiles cof(dir, cacheSize, lowerWaterMark, upperWaterMark); + uint64_t skipMsecs = 0; + + // only 1 file in cache + cof.parseFiles(1655926320000 - 10, true); + LOG_INFO << "first parse files"; + auto snap = cof.getSnapShot(); + printCache(snap); + + // seek to first in bwd dir + auto isFilePresent = cof.getRandomSeekFile(1655895162221 + 5, false, skipMsecs, fileName); + BOOST_TEST(isFilePresent); + BOOST_TEST(boost::filesystem::equivalent(fileName, diskFiles.files[1655895162221])); + + // all four file will come in cache + snap = cof.getSnapShot(); + printCache(snap); + for (auto it = diskFiles.files.begin(); it != diskFiles.files.end(); ++it) + { + auto fileItr = snap.find(boost::filesystem::canonical(it->second).string()); + BOOST_TEST((fileItr != snap.end())); + } +} + +BOOST_AUTO_TEST_CASE(randomSeek_trig_drop) +{ + LoggerSetup setup; + DiskFiles diskFiles; + + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + uint32_t cacheSize = 1; + uint32_t lowerWaterMark = 3; + uint32_t upperWaterMark = 3; + std::string fileName; + + OrderedCacheOfFiles cof(dir, cacheSize, lowerWaterMark, upperWaterMark); + uint64_t skipMsecs = 0; + + // only 1 file in cache + cof.parseFiles(1655926320000 - 10, true); + auto snap = cof.getSnapShot(); + printCache(snap); + + // seek to first in bwd dir + auto isFilePresent = cof.getRandomSeekFile(1655895162221 + 5, false, skipMsecs, fileName); + BOOST_TEST(isFilePresent); + BOOST_TEST(boost::filesystem::equivalent(fileName, diskFiles.files[1655895162221])); + + // all four file should come in cache - but last two will be dropped due to drop logic + snap = cof.getSnapShot(); + printCache(snap); + BOOST_TEST((snap.find(boost::filesystem::canonical(diskFiles.files[1655895162221]).string()) != snap.end())); + BOOST_TEST((snap.find(boost::filesystem::canonical(diskFiles.files[1655895288956]).string()) != snap.end())); + + // seek in fwd dir to last file again + isFilePresent = cof.getRandomSeekFile(1655926320000 + 5, true, skipMsecs, fileName); + BOOST_TEST(isFilePresent); + BOOST_TEST(boost::filesystem::equivalent(fileName, diskFiles.files[1655926320000])); + + // now only last two files will be present in cache + snap = cof.getSnapShot(); + printCache(snap); + BOOST_TEST((snap.find(boost::filesystem::canonical(diskFiles.files[1655919060000]).string()) != snap.end())); + BOOST_TEST((snap.find(boost::filesystem::canonical(diskFiles.files[1655926320000]).string()) != snap.end())); +} + +BOOST_AUTO_TEST_CASE(cache_refresh) +{ + LoggerSetup setup; + DiskFiles diskFiles; + + std::string dir = "data/Mp4_videos/mp4_seek_tests"; + dir = boost::filesystem::canonical(dir).string(); + uint32_t cacheSize = 2; + uint32_t lowerWaterMark = 3; + uint32_t upperWaterMark = 6; + std::string fileName; + + OrderedCacheOfFiles cof(dir, cacheSize, lowerWaterMark, upperWaterMark); + + // only 3 file in cache + cof.parseFiles(1655895162221 - 10, true); + + auto snap = cof.getSnapShot(); + printCache(snap); + + BOOST_TEST((snap.find(diskFiles.files[1655926320000]) == snap.end())); + + cof.refreshCache(); + + // all four file will come in cache + snap = cof.getSnapShot(); + printCache(snap); + + for (auto it = diskFiles.files.begin(); it != diskFiles.files.end(); ++it) + { + auto fileItr = snap.find(boost::filesystem::canonical(it->second).string()); + BOOST_TEST((fileItr != snap.end())); + } +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/data/Mp4_videos/file_fixed_rate.mp4 b/data/Mp4_videos/file_fixed_rate.mp4 new file mode 100644 index 000000000..d63ea9b76 Binary files /dev/null and b/data/Mp4_videos/file_fixed_rate.mp4 differ diff --git a/data/Mp4_videos/h264_video_metadata/20221009/0019/1668001826042.mp4 b/data/Mp4_videos/h264_video_metadata/20230514/0011/1686723796848.mp4 similarity index 98% rename from data/Mp4_videos/h264_video_metadata/20221009/0019/1668001826042.mp4 rename to data/Mp4_videos/h264_video_metadata/20230514/0011/1686723796848.mp4 index 20bfc4c3c..fd32352a6 100644 Binary files a/data/Mp4_videos/h264_video_metadata/20221009/0019/1668001826042.mp4 and b/data/Mp4_videos/h264_video_metadata/20230514/0011/1686723796848.mp4 differ diff --git a/data/Mp4_videos/jpg_video_metada/20220928/0014/1666949168743.mp4 b/data/Mp4_videos/jpg_video_metadata/20230513/0019/1686666193885.mp4 similarity index 99% rename from data/Mp4_videos/jpg_video_metada/20220928/0014/1666949168743.mp4 rename to data/Mp4_videos/jpg_video_metadata/20230513/0019/1686666193885.mp4 index aeac34db9..6b5d89a48 100644 Binary files a/data/Mp4_videos/jpg_video_metada/20220928/0014/1666949168743.mp4 and b/data/Mp4_videos/jpg_video_metadata/20230513/0019/1686666193885.mp4 differ diff --git a/data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895162221.mp4 b/data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895162221.mp4 new file mode 100644 index 000000000..81abaf21d Binary files /dev/null and b/data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895162221.mp4 differ diff --git a/data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895288956.mp4 b/data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895288956.mp4 new file mode 100644 index 000000000..6fc233ef6 Binary files /dev/null and b/data/Mp4_videos/mp4_seek_tests/20220522/0016/1655895288956.mp4 differ diff --git a/data/Mp4_videos/mp4_seek_tests/20220522/0023/1655919060000.mp4 b/data/Mp4_videos/mp4_seek_tests/20220522/0023/1655919060000.mp4 new file mode 100644 index 000000000..aaedf3c3c Binary files /dev/null and b/data/Mp4_videos/mp4_seek_tests/20220522/0023/1655919060000.mp4 differ diff --git a/data/Mp4_videos/mp4_seek_tests/20220523/0001/1655926320000.mp4 b/data/Mp4_videos/mp4_seek_tests/20220523/0001/1655926320000.mp4 new file mode 100644 index 000000000..37c781776 Binary files /dev/null and b/data/Mp4_videos/mp4_seek_tests/20220523/0001/1655926320000.mp4 differ diff --git a/data/Mp4_videos/mp4_seek_tests/apra.mp4 b/data/Mp4_videos/mp4_seek_tests/apra.mp4 new file mode 100644 index 000000000..53fbc1900 Binary files /dev/null and b/data/Mp4_videos/mp4_seek_tests/apra.mp4 differ diff --git a/data/Mp4_videos/mp4_seeks_tests_h264/20230111/0012/1673420640350.mp4 b/data/Mp4_videos/mp4_seeks_tests_h264/20230111/0012/1673420640350.mp4 new file mode 100644 index 000000000..d6706db06 Binary files /dev/null and b/data/Mp4_videos/mp4_seeks_tests_h264/20230111/0012/1673420640350.mp4 differ diff --git a/data/Mp4_videos/mp4_seeks_tests_h264/20230501/0012/1685604318680.mp4 b/data/Mp4_videos/mp4_seeks_tests_h264/20230501/0012/1685604318680.mp4 new file mode 100644 index 000000000..169790bdb Binary files /dev/null and b/data/Mp4_videos/mp4_seeks_tests_h264/20230501/0012/1685604318680.mp4 differ diff --git a/data/Mp4_videos/mp4_seeks_tests_h264/20230501/0012/1685604361723.mp4 b/data/Mp4_videos/mp4_seeks_tests_h264/20230501/0012/1685604361723.mp4 new file mode 100644 index 000000000..6b88ca398 Binary files /dev/null and b/data/Mp4_videos/mp4_seeks_tests_h264/20230501/0012/1685604361723.mp4 differ diff --git a/data/Mp4_videos/mp4_seeks_tests_h264/20230501/0013/1685604896179.mp4 b/data/Mp4_videos/mp4_seeks_tests_h264/20230501/0013/1685604896179.mp4 new file mode 100644 index 000000000..85bc7a075 Binary files /dev/null and b/data/Mp4_videos/mp4_seeks_tests_h264/20230501/0013/1685604896179.mp4 differ diff --git a/data/Mp4_videos/mp4_seeks_tests_h264/apraH264.mp4 b/data/Mp4_videos/mp4_seeks_tests_h264/apraH264.mp4 new file mode 100644 index 000000000..d6706db06 Binary files /dev/null and b/data/Mp4_videos/mp4_seeks_tests_h264/apraH264.mp4 differ diff --git a/data/resized_mono_jpg/video001_output_000536.jpg b/data/resized_mono_jpg/video001_output_000536.jpg new file mode 100644 index 000000000..a49d0e79e Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000536.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000537.jpg b/data/resized_mono_jpg/video001_output_000537.jpg new file mode 100644 index 000000000..c890d1b6f Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000537.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000538.jpg b/data/resized_mono_jpg/video001_output_000538.jpg new file mode 100644 index 000000000..6219e56f1 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000538.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000539.jpg b/data/resized_mono_jpg/video001_output_000539.jpg new file mode 100644 index 000000000..d3d063ebe Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000539.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000540.jpg b/data/resized_mono_jpg/video001_output_000540.jpg new file mode 100644 index 000000000..390452a04 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000540.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000541.jpg b/data/resized_mono_jpg/video001_output_000541.jpg new file mode 100644 index 000000000..6d4a55ee9 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000541.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000543.jpg b/data/resized_mono_jpg/video001_output_000543.jpg new file mode 100644 index 000000000..8872aaac5 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000543.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000546.jpg b/data/resized_mono_jpg/video001_output_000546.jpg new file mode 100644 index 000000000..a671488c4 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000546.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000554.jpg b/data/resized_mono_jpg/video001_output_000554.jpg new file mode 100644 index 000000000..82e3b8914 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000554.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000555.jpg b/data/resized_mono_jpg/video001_output_000555.jpg new file mode 100644 index 000000000..ec5b9387a Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000555.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000556.jpg b/data/resized_mono_jpg/video001_output_000556.jpg new file mode 100644 index 000000000..017c2f171 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000556.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000557.jpg b/data/resized_mono_jpg/video001_output_000557.jpg new file mode 100644 index 000000000..e2245d235 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000557.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000558.jpg b/data/resized_mono_jpg/video001_output_000558.jpg new file mode 100644 index 000000000..1ac22d207 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000558.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000559.jpg b/data/resized_mono_jpg/video001_output_000559.jpg new file mode 100644 index 000000000..7bc362aef Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000559.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000560.jpg b/data/resized_mono_jpg/video001_output_000560.jpg new file mode 100644 index 000000000..c01c4665b Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000560.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000561.jpg b/data/resized_mono_jpg/video001_output_000561.jpg new file mode 100644 index 000000000..f4f9e4bec Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000561.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000562.jpg b/data/resized_mono_jpg/video001_output_000562.jpg new file mode 100644 index 000000000..3f05b02ab Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000562.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000563.jpg b/data/resized_mono_jpg/video001_output_000563.jpg new file mode 100644 index 000000000..0ee4721c4 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000563.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000564.jpg b/data/resized_mono_jpg/video001_output_000564.jpg new file mode 100644 index 000000000..fefcfa947 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000564.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000565.jpg b/data/resized_mono_jpg/video001_output_000565.jpg new file mode 100644 index 000000000..522d45b7c Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000565.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000566.jpg b/data/resized_mono_jpg/video001_output_000566.jpg new file mode 100644 index 000000000..459380e38 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000566.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000567.jpg b/data/resized_mono_jpg/video001_output_000567.jpg new file mode 100644 index 000000000..3d46b0f54 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000567.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000568.jpg b/data/resized_mono_jpg/video001_output_000568.jpg new file mode 100644 index 000000000..bc6333e69 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000568.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000569.jpg b/data/resized_mono_jpg/video001_output_000569.jpg new file mode 100644 index 000000000..bd9cbe41d Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000569.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000570.jpg b/data/resized_mono_jpg/video001_output_000570.jpg new file mode 100644 index 000000000..4b349d232 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000570.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000571.jpg b/data/resized_mono_jpg/video001_output_000571.jpg new file mode 100644 index 000000000..f3a434100 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000571.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000572.jpg b/data/resized_mono_jpg/video001_output_000572.jpg new file mode 100644 index 000000000..5e11b0b8e Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000572.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000573.jpg b/data/resized_mono_jpg/video001_output_000573.jpg new file mode 100644 index 000000000..3dbff4432 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000573.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000574.jpg b/data/resized_mono_jpg/video001_output_000574.jpg new file mode 100644 index 000000000..02adb148b Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000574.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000575.jpg b/data/resized_mono_jpg/video001_output_000575.jpg new file mode 100644 index 000000000..83c06e5ec Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000575.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000576.jpg b/data/resized_mono_jpg/video001_output_000576.jpg new file mode 100644 index 000000000..85bc4a71d Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000576.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000577.jpg b/data/resized_mono_jpg/video001_output_000577.jpg new file mode 100644 index 000000000..0ba767509 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000577.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000578.jpg b/data/resized_mono_jpg/video001_output_000578.jpg new file mode 100644 index 000000000..b56d09442 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000578.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000579.jpg b/data/resized_mono_jpg/video001_output_000579.jpg new file mode 100644 index 000000000..0ee427cb5 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000579.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000580.jpg b/data/resized_mono_jpg/video001_output_000580.jpg new file mode 100644 index 000000000..86d78085e Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000580.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000581.jpg b/data/resized_mono_jpg/video001_output_000581.jpg new file mode 100644 index 000000000..1df5e2eff Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000581.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000582.jpg b/data/resized_mono_jpg/video001_output_000582.jpg new file mode 100644 index 000000000..84cf00496 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000582.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000583.jpg b/data/resized_mono_jpg/video001_output_000583.jpg new file mode 100644 index 000000000..291b7883f Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000583.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000584.jpg b/data/resized_mono_jpg/video001_output_000584.jpg new file mode 100644 index 000000000..1eb7fa14b Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000584.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000585.jpg b/data/resized_mono_jpg/video001_output_000585.jpg new file mode 100644 index 000000000..3ea6af4fa Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000585.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000586.jpg b/data/resized_mono_jpg/video001_output_000586.jpg new file mode 100644 index 000000000..a42d9bd22 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000586.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000587.jpg b/data/resized_mono_jpg/video001_output_000587.jpg new file mode 100644 index 000000000..f21f96ed8 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000587.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000588.jpg b/data/resized_mono_jpg/video001_output_000588.jpg new file mode 100644 index 000000000..c887dcd1a Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000588.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000589.jpg b/data/resized_mono_jpg/video001_output_000589.jpg new file mode 100644 index 000000000..3f4dd40b1 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000589.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000590.jpg b/data/resized_mono_jpg/video001_output_000590.jpg new file mode 100644 index 000000000..d2e245560 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000590.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000591.jpg b/data/resized_mono_jpg/video001_output_000591.jpg new file mode 100644 index 000000000..bf0849e38 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000591.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000592.jpg b/data/resized_mono_jpg/video001_output_000592.jpg new file mode 100644 index 000000000..bec64f538 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000592.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000593.jpg b/data/resized_mono_jpg/video001_output_000593.jpg new file mode 100644 index 000000000..33f5d421a Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000593.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000594.jpg b/data/resized_mono_jpg/video001_output_000594.jpg new file mode 100644 index 000000000..a93f4a88a Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000594.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000595.jpg b/data/resized_mono_jpg/video001_output_000595.jpg new file mode 100644 index 000000000..0b8896940 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000595.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000596.jpg b/data/resized_mono_jpg/video001_output_000596.jpg new file mode 100644 index 000000000..058c97205 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000596.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000597.jpg b/data/resized_mono_jpg/video001_output_000597.jpg new file mode 100644 index 000000000..98f42e891 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000597.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000598.jpg b/data/resized_mono_jpg/video001_output_000598.jpg new file mode 100644 index 000000000..99ed1fab6 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000598.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000599.jpg b/data/resized_mono_jpg/video001_output_000599.jpg new file mode 100644 index 000000000..d239b692b Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000599.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000600.jpg b/data/resized_mono_jpg/video001_output_000600.jpg new file mode 100644 index 000000000..3de1265d9 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000600.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000601.jpg b/data/resized_mono_jpg/video001_output_000601.jpg new file mode 100644 index 000000000..fac579e3b Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000601.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000602.jpg b/data/resized_mono_jpg/video001_output_000602.jpg new file mode 100644 index 000000000..c6739a509 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000602.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000603.jpg b/data/resized_mono_jpg/video001_output_000603.jpg new file mode 100644 index 000000000..f24d3aebb Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000603.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000604.jpg b/data/resized_mono_jpg/video001_output_000604.jpg new file mode 100644 index 000000000..7218d0c88 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000604.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000605.jpg b/data/resized_mono_jpg/video001_output_000605.jpg new file mode 100644 index 000000000..263f3b39a Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000605.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000606.jpg b/data/resized_mono_jpg/video001_output_000606.jpg new file mode 100644 index 000000000..e217121b1 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000606.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000607.jpg b/data/resized_mono_jpg/video001_output_000607.jpg new file mode 100644 index 000000000..2673afca0 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000607.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000608.jpg b/data/resized_mono_jpg/video001_output_000608.jpg new file mode 100644 index 000000000..ef7958e48 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000608.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000609.jpg b/data/resized_mono_jpg/video001_output_000609.jpg new file mode 100644 index 000000000..9dcb71e85 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000609.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000610.jpg b/data/resized_mono_jpg/video001_output_000610.jpg new file mode 100644 index 000000000..9761a0359 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000610.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000611.jpg b/data/resized_mono_jpg/video001_output_000611.jpg new file mode 100644 index 000000000..d41406bfc Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000611.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000612.jpg b/data/resized_mono_jpg/video001_output_000612.jpg new file mode 100644 index 000000000..258b361b1 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000612.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000613.jpg b/data/resized_mono_jpg/video001_output_000613.jpg new file mode 100644 index 000000000..90a51b2ea Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000613.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000614.jpg b/data/resized_mono_jpg/video001_output_000614.jpg new file mode 100644 index 000000000..f53f48b5e Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000614.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000615.jpg b/data/resized_mono_jpg/video001_output_000615.jpg new file mode 100644 index 000000000..e17a7b040 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000615.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000616.jpg b/data/resized_mono_jpg/video001_output_000616.jpg new file mode 100644 index 000000000..90af8cd3c Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000616.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000617.jpg b/data/resized_mono_jpg/video001_output_000617.jpg new file mode 100644 index 000000000..3e6ed632e Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000617.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000618.jpg b/data/resized_mono_jpg/video001_output_000618.jpg new file mode 100644 index 000000000..3801d2784 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000618.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000619.jpg b/data/resized_mono_jpg/video001_output_000619.jpg new file mode 100644 index 000000000..d032a4960 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000619.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000620.jpg b/data/resized_mono_jpg/video001_output_000620.jpg new file mode 100644 index 000000000..12f29d1ad Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000620.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000621.jpg b/data/resized_mono_jpg/video001_output_000621.jpg new file mode 100644 index 000000000..3d02bdd47 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000621.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000622.jpg b/data/resized_mono_jpg/video001_output_000622.jpg new file mode 100644 index 000000000..44ace0359 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000622.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000623.jpg b/data/resized_mono_jpg/video001_output_000623.jpg new file mode 100644 index 000000000..f2fdfc075 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000623.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000624.jpg b/data/resized_mono_jpg/video001_output_000624.jpg new file mode 100644 index 000000000..7ceef5e9f Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000624.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000625.jpg b/data/resized_mono_jpg/video001_output_000625.jpg new file mode 100644 index 000000000..83cc14798 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000625.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000626.jpg b/data/resized_mono_jpg/video001_output_000626.jpg new file mode 100644 index 000000000..c3206b952 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000626.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000627.jpg b/data/resized_mono_jpg/video001_output_000627.jpg new file mode 100644 index 000000000..d9323f644 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000627.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000628.jpg b/data/resized_mono_jpg/video001_output_000628.jpg new file mode 100644 index 000000000..d14b4fe9f Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000628.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000629.jpg b/data/resized_mono_jpg/video001_output_000629.jpg new file mode 100644 index 000000000..0a1b1059a Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000629.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000630.jpg b/data/resized_mono_jpg/video001_output_000630.jpg new file mode 100644 index 000000000..45486c24e Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000630.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000631.jpg b/data/resized_mono_jpg/video001_output_000631.jpg new file mode 100644 index 000000000..790b1d360 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000631.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000632.jpg b/data/resized_mono_jpg/video001_output_000632.jpg new file mode 100644 index 000000000..56b703378 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000632.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000633.jpg b/data/resized_mono_jpg/video001_output_000633.jpg new file mode 100644 index 000000000..c7604e3f4 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000633.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000634.jpg b/data/resized_mono_jpg/video001_output_000634.jpg new file mode 100644 index 000000000..d3cb382fc Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000634.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000635.jpg b/data/resized_mono_jpg/video001_output_000635.jpg new file mode 100644 index 000000000..ae6254654 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000635.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000636.jpg b/data/resized_mono_jpg/video001_output_000636.jpg new file mode 100644 index 000000000..2f89e5053 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000636.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000637.jpg b/data/resized_mono_jpg/video001_output_000637.jpg new file mode 100644 index 000000000..e783720df Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000637.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000638.jpg b/data/resized_mono_jpg/video001_output_000638.jpg new file mode 100644 index 000000000..6455431e2 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000638.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000639.jpg b/data/resized_mono_jpg/video001_output_000639.jpg new file mode 100644 index 000000000..599711dc0 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000639.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000640.jpg b/data/resized_mono_jpg/video001_output_000640.jpg new file mode 100644 index 000000000..e543df0be Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000640.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000641.jpg b/data/resized_mono_jpg/video001_output_000641.jpg new file mode 100644 index 000000000..4dee01f82 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000641.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000642.jpg b/data/resized_mono_jpg/video001_output_000642.jpg new file mode 100644 index 000000000..7fe5f2343 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000642.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000643.jpg b/data/resized_mono_jpg/video001_output_000643.jpg new file mode 100644 index 000000000..aba85827d Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000643.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000644.jpg b/data/resized_mono_jpg/video001_output_000644.jpg new file mode 100644 index 000000000..a551f8c6c Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000644.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000645.jpg b/data/resized_mono_jpg/video001_output_000645.jpg new file mode 100644 index 000000000..930894405 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000645.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000646.jpg b/data/resized_mono_jpg/video001_output_000646.jpg new file mode 100644 index 000000000..740837983 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000646.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000647.jpg b/data/resized_mono_jpg/video001_output_000647.jpg new file mode 100644 index 000000000..1781e2f04 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000647.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000648.jpg b/data/resized_mono_jpg/video001_output_000648.jpg new file mode 100644 index 000000000..f0ba72ac2 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000648.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000649.jpg b/data/resized_mono_jpg/video001_output_000649.jpg new file mode 100644 index 000000000..26e6e9129 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000649.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000650.jpg b/data/resized_mono_jpg/video001_output_000650.jpg new file mode 100644 index 000000000..e0be176df Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000650.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000651.jpg b/data/resized_mono_jpg/video001_output_000651.jpg new file mode 100644 index 000000000..12f206203 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000651.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000652.jpg b/data/resized_mono_jpg/video001_output_000652.jpg new file mode 100644 index 000000000..68dd1abce Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000652.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000653.jpg b/data/resized_mono_jpg/video001_output_000653.jpg new file mode 100644 index 000000000..d3266830c Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000653.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000654.jpg b/data/resized_mono_jpg/video001_output_000654.jpg new file mode 100644 index 000000000..ee17c51f8 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000654.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000655.jpg b/data/resized_mono_jpg/video001_output_000655.jpg new file mode 100644 index 000000000..f0fe7872c Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000655.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000656.jpg b/data/resized_mono_jpg/video001_output_000656.jpg new file mode 100644 index 000000000..d907611a5 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000656.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000657.jpg b/data/resized_mono_jpg/video001_output_000657.jpg new file mode 100644 index 000000000..a19e1c643 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000657.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000658.jpg b/data/resized_mono_jpg/video001_output_000658.jpg new file mode 100644 index 000000000..0ecde95a9 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000658.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000659.jpg b/data/resized_mono_jpg/video001_output_000659.jpg new file mode 100644 index 000000000..8804ec048 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000659.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000660.jpg b/data/resized_mono_jpg/video001_output_000660.jpg new file mode 100644 index 000000000..7836d92f1 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000660.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000661.jpg b/data/resized_mono_jpg/video001_output_000661.jpg new file mode 100644 index 000000000..bba956fa2 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000661.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000662.jpg b/data/resized_mono_jpg/video001_output_000662.jpg new file mode 100644 index 000000000..5e806b71a Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000662.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000663.jpg b/data/resized_mono_jpg/video001_output_000663.jpg new file mode 100644 index 000000000..6f988da41 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000663.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000664.jpg b/data/resized_mono_jpg/video001_output_000664.jpg new file mode 100644 index 000000000..c90ab8935 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000664.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000665.jpg b/data/resized_mono_jpg/video001_output_000665.jpg new file mode 100644 index 000000000..8cd75eb54 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000665.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000666.jpg b/data/resized_mono_jpg/video001_output_000666.jpg new file mode 100644 index 000000000..796ad48f1 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000666.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000667.jpg b/data/resized_mono_jpg/video001_output_000667.jpg new file mode 100644 index 000000000..05b820a2b Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000667.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000668.jpg b/data/resized_mono_jpg/video001_output_000668.jpg new file mode 100644 index 000000000..0e9f1ffc6 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000668.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000669.jpg b/data/resized_mono_jpg/video001_output_000669.jpg new file mode 100644 index 000000000..002d1d7bf Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000669.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000670.jpg b/data/resized_mono_jpg/video001_output_000670.jpg new file mode 100644 index 000000000..d9107ebc1 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000670.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000671.jpg b/data/resized_mono_jpg/video001_output_000671.jpg new file mode 100644 index 000000000..1aea78bad Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000671.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000672.jpg b/data/resized_mono_jpg/video001_output_000672.jpg new file mode 100644 index 000000000..632439cab Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000672.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000673.jpg b/data/resized_mono_jpg/video001_output_000673.jpg new file mode 100644 index 000000000..f3e5390e6 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000673.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000674.jpg b/data/resized_mono_jpg/video001_output_000674.jpg new file mode 100644 index 000000000..a4bdf224a Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000674.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000675.jpg b/data/resized_mono_jpg/video001_output_000675.jpg new file mode 100644 index 000000000..e03b78e7e Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000675.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000676.jpg b/data/resized_mono_jpg/video001_output_000676.jpg new file mode 100644 index 000000000..55cb9e020 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000676.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000677.jpg b/data/resized_mono_jpg/video001_output_000677.jpg new file mode 100644 index 000000000..fb61c8bfd Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000677.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000678.jpg b/data/resized_mono_jpg/video001_output_000678.jpg new file mode 100644 index 000000000..86b0be049 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000678.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000679.jpg b/data/resized_mono_jpg/video001_output_000679.jpg new file mode 100644 index 000000000..41465d14d Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000679.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000680.jpg b/data/resized_mono_jpg/video001_output_000680.jpg new file mode 100644 index 000000000..388a1cb86 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000680.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000681.jpg b/data/resized_mono_jpg/video001_output_000681.jpg new file mode 100644 index 000000000..271210cfe Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000681.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000682.jpg b/data/resized_mono_jpg/video001_output_000682.jpg new file mode 100644 index 000000000..7cf6ad95e Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000682.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000683.jpg b/data/resized_mono_jpg/video001_output_000683.jpg new file mode 100644 index 000000000..0ecdff8e2 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000683.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000684.jpg b/data/resized_mono_jpg/video001_output_000684.jpg new file mode 100644 index 000000000..af2ccfbaf Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000684.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000685.jpg b/data/resized_mono_jpg/video001_output_000685.jpg new file mode 100644 index 000000000..70fb460f8 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000685.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000686.jpg b/data/resized_mono_jpg/video001_output_000686.jpg new file mode 100644 index 000000000..2c5e1aade Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000686.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000687.jpg b/data/resized_mono_jpg/video001_output_000687.jpg new file mode 100644 index 000000000..07b45c556 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000687.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000688.jpg b/data/resized_mono_jpg/video001_output_000688.jpg new file mode 100644 index 000000000..0813afed8 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000688.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000689.jpg b/data/resized_mono_jpg/video001_output_000689.jpg new file mode 100644 index 000000000..0e61b791d Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000689.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000690.jpg b/data/resized_mono_jpg/video001_output_000690.jpg new file mode 100644 index 000000000..3ffdc84c9 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000690.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000691.jpg b/data/resized_mono_jpg/video001_output_000691.jpg new file mode 100644 index 000000000..30719f0b5 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000691.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000692.jpg b/data/resized_mono_jpg/video001_output_000692.jpg new file mode 100644 index 000000000..7c9c7f0e7 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000692.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000693.jpg b/data/resized_mono_jpg/video001_output_000693.jpg new file mode 100644 index 000000000..8518d45a2 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000693.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000694.jpg b/data/resized_mono_jpg/video001_output_000694.jpg new file mode 100644 index 000000000..4d37fe16c Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000694.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000695.jpg b/data/resized_mono_jpg/video001_output_000695.jpg new file mode 100644 index 000000000..b96285728 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000695.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000696.jpg b/data/resized_mono_jpg/video001_output_000696.jpg new file mode 100644 index 000000000..8e81e07d7 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000696.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000697.jpg b/data/resized_mono_jpg/video001_output_000697.jpg new file mode 100644 index 000000000..ea26a31b1 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000697.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000698.jpg b/data/resized_mono_jpg/video001_output_000698.jpg new file mode 100644 index 000000000..3d181db39 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000698.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000699.jpg b/data/resized_mono_jpg/video001_output_000699.jpg new file mode 100644 index 000000000..b9ae57a0e Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000699.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000700.jpg b/data/resized_mono_jpg/video001_output_000700.jpg new file mode 100644 index 000000000..b65b42dbc Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000700.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000701.jpg b/data/resized_mono_jpg/video001_output_000701.jpg new file mode 100644 index 000000000..13881a336 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000701.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000702.jpg b/data/resized_mono_jpg/video001_output_000702.jpg new file mode 100644 index 000000000..80e847b5c Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000702.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000703.jpg b/data/resized_mono_jpg/video001_output_000703.jpg new file mode 100644 index 000000000..7083b2946 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000703.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000704.jpg b/data/resized_mono_jpg/video001_output_000704.jpg new file mode 100644 index 000000000..6f2826de0 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000704.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000705.jpg b/data/resized_mono_jpg/video001_output_000705.jpg new file mode 100644 index 000000000..85b0fd459 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000705.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000706.jpg b/data/resized_mono_jpg/video001_output_000706.jpg new file mode 100644 index 000000000..65ead6a27 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000706.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000707.jpg b/data/resized_mono_jpg/video001_output_000707.jpg new file mode 100644 index 000000000..b69482971 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000707.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000708.jpg b/data/resized_mono_jpg/video001_output_000708.jpg new file mode 100644 index 000000000..d2991d02a Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000708.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000709.jpg b/data/resized_mono_jpg/video001_output_000709.jpg new file mode 100644 index 000000000..97c997787 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000709.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000710.jpg b/data/resized_mono_jpg/video001_output_000710.jpg new file mode 100644 index 000000000..1b6710551 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000710.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000711.jpg b/data/resized_mono_jpg/video001_output_000711.jpg new file mode 100644 index 000000000..47c52bc8a Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000711.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000712.jpg b/data/resized_mono_jpg/video001_output_000712.jpg new file mode 100644 index 000000000..31cd7284d Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000712.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000713.jpg b/data/resized_mono_jpg/video001_output_000713.jpg new file mode 100644 index 000000000..52434c841 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000713.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000714.jpg b/data/resized_mono_jpg/video001_output_000714.jpg new file mode 100644 index 000000000..c60f6a7b7 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000714.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000715.jpg b/data/resized_mono_jpg/video001_output_000715.jpg new file mode 100644 index 000000000..f97e99bf8 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000715.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000716.jpg b/data/resized_mono_jpg/video001_output_000716.jpg new file mode 100644 index 000000000..4aab3dcab Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000716.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000717.jpg b/data/resized_mono_jpg/video001_output_000717.jpg new file mode 100644 index 000000000..57d2695ea Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000717.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000718.jpg b/data/resized_mono_jpg/video001_output_000718.jpg new file mode 100644 index 000000000..2ebb08788 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000718.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000719.jpg b/data/resized_mono_jpg/video001_output_000719.jpg new file mode 100644 index 000000000..da8c4e09f Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000719.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000720.jpg b/data/resized_mono_jpg/video001_output_000720.jpg new file mode 100644 index 000000000..3212789a5 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000720.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000721.jpg b/data/resized_mono_jpg/video001_output_000721.jpg new file mode 100644 index 000000000..f0a172841 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000721.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000722.jpg b/data/resized_mono_jpg/video001_output_000722.jpg new file mode 100644 index 000000000..877c115c7 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000722.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000723.jpg b/data/resized_mono_jpg/video001_output_000723.jpg new file mode 100644 index 000000000..143e5e46f Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000723.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000724.jpg b/data/resized_mono_jpg/video001_output_000724.jpg new file mode 100644 index 000000000..efd75876f Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000724.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000725.jpg b/data/resized_mono_jpg/video001_output_000725.jpg new file mode 100644 index 000000000..99db2d7fe Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000725.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000726.jpg b/data/resized_mono_jpg/video001_output_000726.jpg new file mode 100644 index 000000000..c13ecb344 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000726.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000735.jpg b/data/resized_mono_jpg/video001_output_000735.jpg new file mode 100644 index 000000000..00655ef98 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000735.jpg differ diff --git a/data/resized_mono_jpg/video001_output_000737.jpg b/data/resized_mono_jpg/video001_output_000737.jpg new file mode 100644 index 000000000..95ebc53e5 Binary files /dev/null and b/data/resized_mono_jpg/video001_output_000737.jpg differ