From a6f37c1ac0b78cbfb60ff93f3f788db89cb301de Mon Sep 17 00:00:00 2001 From: mohammedzakikochargi <97148049+mohammedzakikochargi@users.noreply.github.com> Date: Tue, 18 Jul 2023 00:05:31 +0530 Subject: [PATCH] Enhanced playback compatibility: Mp4Writer-generated videos now compatible with all media players (#250) * Bug fix: Videos now playable on all players written using mp4Writer * changed function name --- base/include/Mp4WriterSink.h | 11 +- base/include/Mp4WriterSinkUtils.h | 2 +- base/src/Mp4WriterSink.cpp | 172 +++++++++++++++++++++++++----- base/src/Mp4WriterSinkUtils.cpp | 22 ++-- base/test/mp4writersink_tests.cpp | 155 +++++++++++++++++++++++++-- 5 files changed, 319 insertions(+), 43 deletions(-) diff --git a/base/include/Mp4WriterSink.h b/base/include/Mp4WriterSink.h index ed6e47f0a..8bcff4dba 100644 --- a/base/include/Mp4WriterSink.h +++ b/base/include/Mp4WriterSink.h @@ -9,11 +9,12 @@ class DetailH264; class Mp4WriterSinkProps : public ModuleProps { public: - Mp4WriterSinkProps(uint32_t _chunkTime, uint32_t _syncTimeInSecs, uint16_t _fps, std::string _baseFolder, bool _recordedTSBasedDTS = true) : ModuleProps() + Mp4WriterSinkProps(uint32_t _chunkTime, uint32_t _syncTimeInSecs, uint16_t _fps, std::string _baseFolder, bool _recordedTSBasedDTS = true, bool _enableMetadata = true) : ModuleProps() { baseFolder = _baseFolder; fps = _fps; recordedTSBasedDTS = _recordedTSBasedDTS; + enableMetadata = _enableMetadata; if ((_chunkTime >= 1 && _chunkTime <= 60) || (_chunkTime == UINT32_MAX)) { chunkTime = _chunkTime; @@ -39,6 +40,7 @@ class Mp4WriterSinkProps : public ModuleProps syncTimeInSecs = 1; fps = 30; recordedTSBasedDTS = true; + enableMetadata = true; } size_t getSerializeSize() @@ -48,7 +50,8 @@ class Mp4WriterSinkProps : public ModuleProps sizeof(baseFolder) + sizeof(chunkTime) + sizeof(syncTimeInSecs) + - sizeof(fps); + sizeof(fps) + + sizeof(enableMetadata);; } std::string baseFolder; @@ -56,6 +59,7 @@ class Mp4WriterSinkProps : public ModuleProps uint32_t syncTimeInSecs = 1; uint16_t fps = 30; bool recordedTSBasedDTS = true; + bool enableMetadata = true; private: friend class boost::serialization::access; @@ -68,6 +72,7 @@ class Mp4WriterSinkProps : public ModuleProps ar &chunkTime; ar &syncTimeInSecs; ar &fps; + ar &enableMetadata; } }; @@ -89,6 +94,8 @@ class Mp4WriterSink : public Module bool setMetadata(framemetadata_sp &inputMetadata); bool handlePropsChange(frame_sp &frame); bool shouldTriggerSOS(); + void addInputPin(framemetadata_sp& metadata, string& pinId); + bool enableMp4Metadata(framemetadata_sp &inputMetadata); boost::shared_ptr mDetail; Mp4WriterSinkProps mProp; diff --git a/base/include/Mp4WriterSinkUtils.h b/base/include/Mp4WriterSinkUtils.h index 32507e85f..55b253a71 100644 --- a/base/include/Mp4WriterSinkUtils.h +++ b/base/include/Mp4WriterSinkUtils.h @@ -13,7 +13,7 @@ class Mp4WriterSinkUtils bool customNamedFileDirCheck(std::string baseFolder, uint32_t chunkTimeInMinutes, boost::filesystem::path relPath, std::string& nextFrameFileName); std::string format_hrs(int &hr); std::string format_2(int &min); - std::string filePath(boost::filesystem::path relPath, std::string mp4FileName, std::string baseFolder); + std::string filePath(boost::filesystem::path relPath, std::string mp4FileName, std::string baseFolder, uint64_t chunkTimeInMins); ~Mp4WriterSinkUtils(); private: int lastVideoMinute=0; diff --git a/base/src/Mp4WriterSink.cpp b/base/src/Mp4WriterSink.cpp index ac932cdc9..23abe46fc 100644 --- a/base/src/Mp4WriterSink.cpp +++ b/base/src/Mp4WriterSink.cpp @@ -78,7 +78,7 @@ class DetailAbs mNextFrameFileName = ""; mux = nullptr; mMetadataEnabled = false; - + /* DTS should be based on recorded timestamps of frames or on the fps prop entirely */ if (_props.recordedTSBasedDTS) { mDTSCalc.reset(new DTSPassThroughStrategy); @@ -91,7 +91,7 @@ class DetailAbs void setProps(Mp4WriterSinkProps& _props) { - mProps.reset(new Mp4WriterSinkProps(_props.chunkTime, _props.syncTimeInSecs, _props.fps, _props.baseFolder)); + mProps.reset(new Mp4WriterSinkProps(_props.chunkTime, _props.syncTimeInSecs, _props.fps, _props.baseFolder, _props.recordedTSBasedDTS, _props.enableMetadata)); } ~DetailAbs() @@ -172,20 +172,21 @@ class DetailAbs if (metatrack < 0) { LOG_ERROR << "Failed to add metadata track"; - // #Dec_24_Review - should we throw exception here ? This means that user sends metadata to this module but we don't write ? } // https://www.rfc-editor.org/rfc/rfc4337.txt std::string content_encoding = "base64"; std::string mime_format = "video/mp4"; - // #Dec_24_Review - use return code from the below function call - mp4_mux_track_set_metadata_mime_type( + auto ret = mp4_mux_track_set_metadata_mime_type( mux, metatrack, content_encoding.c_str(), mime_format.c_str()); - + if(ret != 0) + { + LOG_ERROR << "Failed to add metadata track mime type"; + } /* Add track reference */ if (metatrack > 0) { @@ -194,7 +195,6 @@ class DetailAbs if (ret != 0) { LOG_ERROR << "Failed to add metadata track as reference"; - // #Dec_24_Review - should we throw exception here ? This means that user sends metadata to this module but we don't write ? } } } @@ -280,6 +280,7 @@ class DetailH264 : public DetailAbs { } bool write(frame_container& frames); + uint8_t* AppendSizeInNaluSeprator(short naluType, frame_sp frame, size_t& frameSize); bool set_video_decoder_config() { @@ -323,7 +324,8 @@ bool DetailJpeg::write(frame_container& frames) if (syncFlag) { - mp4_mux_sync(mux); + LOG_TRACE << "attempting to sync <" << mNextFrameFileName << ">"; + auto ret = mp4_mux_sync(mux); syncFlag = false; } @@ -348,15 +350,78 @@ bool DetailJpeg::write(frame_container& frames) mp4_mux_track_add_sample(mux, videotrack, &mux_sample); - if (metatrack != -1 && mMetadataEnabled && inMp4MetaFrame.get()) - { - mux_sample.buffer = static_cast(inMp4MetaFrame->data()); - mux_sample.len = inMp4MetaFrame->size(); - mp4_mux_track_add_sample(mux, metatrack, &mux_sample); - } + if (metatrack != -1 && mMetadataEnabled) + { + if (inMp4MetaFrame.get() && inMp4MetaFrame->fIndex == 0) + { + mux_sample.buffer = static_cast(inMp4MetaFrame->data()); + mux_sample.len = inMp4MetaFrame->size(); + } + else + { + mux_sample.buffer = nullptr; + mux_sample.len = 0; + } + + mp4_mux_track_add_sample(mux, metatrack, &mux_sample); + } return true; } +uint8_t* DetailH264::AppendSizeInNaluSeprator(short naluType, frame_sp inH264ImageFrame, size_t& frameSize) +{ + char NaluSeprator[3] = { 00 ,00, 00 }; + char nalusepratorIframe[2] = { 00, 00 }; + auto nalu = reinterpret_cast(NaluSeprator); + uint spsPpsSize = spsBuffer.size() + ppsBuffer.size() + 8; + if (naluType == H264Utils::H264_NAL_TYPE_SEQ_PARAM) + { + frameSize = inH264ImageFrame->size(); + } + //Add the size of sps and pps to I frame - (First frame of the video) + else if (naluType == H264Utils::H264_NAL_TYPE_IDR_SLICE) + { + frameSize = inH264ImageFrame->size() + spsPpsSize; + } + uint8_t* newBuffer = new uint8_t[frameSize]; + //add the size of sps to the 4th byte of sps's nalu seprator (00 00 00 SpsSize 67) + memcpy(newBuffer, nalu, 3); + newBuffer += 3; + newBuffer[0] = spsBuffer.size(); + newBuffer += 1; + memcpy(newBuffer, spsBuffer.data(), spsBuffer.size()); + newBuffer += spsBuffer.size(); + + //add the size of sps to the 4th byte of pps's nalu seprator (00 00 00 PpsSize 68) + memcpy(newBuffer, nalu, 3); + newBuffer += 3; + newBuffer[0] = ppsBuffer.size(); + newBuffer += 1; + memcpy(newBuffer, ppsBuffer.data(), ppsBuffer.size()); + newBuffer += ppsBuffer.size(); + memcpy(newBuffer, nalusepratorIframe, 2); + newBuffer += 2; + + //add the size of I frame to the 3rd and 4th byte of I frame's nalu seprator (00 00 frameSize frameSize 66) + newBuffer[0] = (frameSize - spsPpsSize - 4 >> 8) & 0xFF; + newBuffer[1] = frameSize - spsPpsSize - 4 & 0xFF; + newBuffer += 2; + uint8_t* tempBuffer = reinterpret_cast(inH264ImageFrame->data()); + if (naluType == H264Utils::H264_NAL_TYPE_SEQ_PARAM) + { + tempBuffer = tempBuffer + spsPpsSize + 4; + } + else if (naluType == H264Utils::H264_NAL_TYPE_IDR_SLICE) + { + tempBuffer = tempBuffer + 4; + } + //copy I frame data to the buffer + memcpy(newBuffer, tempBuffer, frameSize - spsPpsSize - 4); + //set the pointer to the starting of frame + newBuffer -= spsPpsSize + 4; + return newBuffer; +} + bool DetailH264::write(frame_container& frames) { auto inH264ImageFrame = Module::getFrameByType(frames, FrameMetadata::FrameType::H264_DATA); @@ -388,15 +453,41 @@ bool DetailH264::write(frame_container& frames) return false; } + uint8_t* frameData = reinterpret_cast(inH264ImageFrame->data()); + // assign size of the frame to the last two bytes of the NALU seperator for playability in default players + frameData[2] = (inH264ImageFrame->size() - 4 >> 8) & 0xFF; + frameData[3] = inH264ImageFrame->size() - 4 & 0xFF; + + mux_sample.buffer = frameData; + mux_sample.len = inH264ImageFrame->size(); + auto naluType = H264Utils::getNALUType((char*)mFrameBuffer.data()); + size_t frameSize; if (mNextFrameFileName != _nextFrameFileName) { mNextFrameFileName = _nextFrameFileName; initNewMp4File(mNextFrameFileName); + if (naluType == H264Utils::H264_NAL_TYPE_IDR_SLICE) + { + //add sps and pps to I-frame and change the Nalu seprator according to Mp4 format + auto newBuffer = AppendSizeInNaluSeprator(naluType, inH264ImageFrame, frameSize); + mux_sample.buffer = newBuffer; + mux_sample.len = frameSize; + } + } + + if (naluType == H264Utils::H264_NAL_TYPE_SEQ_PARAM) + { + //change the Nalu seprator according to Mp4 format + auto newBuffer = AppendSizeInNaluSeprator(naluType, inH264ImageFrame, frameSize); + mux_sample.buffer = newBuffer; + mux_sample.len = frameSize; } + if (syncFlag) { - mp4_mux_sync(mux); + LOG_TRACE << "attempting to sync <" << mNextFrameFileName << ">"; + auto ret = mp4_mux_sync(mux); syncFlag = false; } @@ -411,8 +502,6 @@ bool DetailH264::write(frame_container& frames) addMetadataInVideoHeader(inH264ImageFrame); - mux_sample.buffer = static_cast(inH264ImageFrame->data()); - mux_sample.len = inH264ImageFrame->size(); mux_sample.sync = isKeyFrame ? 1 : 0; int64_t diffInMsecs = 0; @@ -432,8 +521,16 @@ bool DetailH264::write(frame_container& frames) if (metatrack != -1 && mMetadataEnabled && inMp4MetaFrame.get()) { - mux_sample.buffer = static_cast(inMp4MetaFrame->data()); - mux_sample.len = inMp4MetaFrame->size(); + if (inMp4MetaFrame->size()) + { + mux_sample.buffer = static_cast(inMp4MetaFrame->data()); + mux_sample.len = inMp4MetaFrame->size(); + } + else + { + mux_sample.buffer = nullptr; + mux_sample.len = 0; + } mp4_mux_track_add_sample(mux, metatrack, &mux_sample); } return true; @@ -448,6 +545,8 @@ Mp4WriterSink::~Mp4WriterSink() {} bool Mp4WriterSink::init() { + bool enableVideoMetadata = false; + framemetadata_sp mp4VideoMetadata; if (!Module::init()) { return false; @@ -456,7 +555,7 @@ bool Mp4WriterSink::init() for (auto const& element : inputPinIdMetadataMap) { - auto& metadata = element.second; + auto metadata = element.second; auto mFrameType = metadata->getFrameType(); if (mFrameType == FrameMetadata::FrameType::ENCODED_IMAGE) { @@ -467,6 +566,16 @@ bool Mp4WriterSink::init() { mDetail.reset(new DetailH264(mProp)); } + + else if (mFrameType == FrameMetadata::FrameType::MP4_VIDEO_METADATA && mProp.enableMetadata) + { + enableVideoMetadata = true; + mp4VideoMetadata = metadata; + } + } + if (enableVideoMetadata) + { + enableMp4Metadata(mp4VideoMetadata); } return Module::init(); } @@ -511,11 +620,28 @@ bool Mp4WriterSink::validateInputPins() } bool Mp4WriterSink::setMetadata(framemetadata_sp& inputMetadata) { - // #Dec_24_Review - this function seems to do nothing mDetail->setImageMetadata(inputMetadata); return true; } +bool Mp4WriterSink::enableMp4Metadata(framemetadata_sp &inputMetadata) +{ + auto mp4VideoMetadata = FrameMetadataFactory::downcast(inputMetadata); + std::string formatVersion = mp4VideoMetadata->getVersion(); + if (formatVersion.empty()) + { + LOG_ERROR << "Serialization Format Information missing from the metadata. Metadata writing will be disabled"; + return false; + } + mDetail->enableMetadata(formatVersion); + return true; +} + +void Mp4WriterSink::addInputPin(framemetadata_sp& metadata, string& pinId) +{ + Module::addInputPin(metadata, pinId); +} + bool Mp4WriterSink::processSOS(frame_sp& frame) { auto inputMetadata = frame->getMetadata(); @@ -571,10 +697,6 @@ bool Mp4WriterSink::process(frame_container& frames) bool Mp4WriterSink::processEOS(string& pinId) { - // #Dec_24_Review - generally you do opposite of what you do on SOS, so that after EOS, SOS is triggered - // in current state after EOS, SOS is not triggered - is it by design ? - // Example EOS can be triggered if there is some resolution change in upstream module - // so you want to do mDetail->mInputMetadata.reset() - so that SOS gets triggered return true; } diff --git a/base/src/Mp4WriterSinkUtils.cpp b/base/src/Mp4WriterSinkUtils.cpp index be3fa5bb8..2430b31f9 100644 --- a/base/src/Mp4WriterSinkUtils.cpp +++ b/base/src/Mp4WriterSinkUtils.cpp @@ -36,9 +36,15 @@ std::string Mp4WriterSinkUtils::format_2(int &num) } } -std::string Mp4WriterSinkUtils::filePath(boost::filesystem::path relPath, std::string mp4FileName, std::string baseFolder) +std::string Mp4WriterSinkUtils::filePath(boost::filesystem::path relPath, std::string mp4FileName, std::string baseFolder, uint64_t chunkTimeInMinutes) { boost::filesystem::path finalPath; + std::string mp4VideoPath; + if (customNamedFileDirCheck(baseFolder, chunkTimeInMinutes, relPath, mp4VideoPath)) + { + return mp4VideoPath; + } + auto folderPath = boost::filesystem::path(baseFolder) / relPath; if (boost::filesystem::is_directory(folderPath)) { @@ -121,11 +127,11 @@ void Mp4WriterSinkUtils::parseTSJpeg(uint64_t &ts, uint32_t &chunkTimeInMinutes, // used cached values if the difference in ts is less than chunkTime uint32_t chunkTimeInSecs = 60 * chunkTimeInMinutes; - if ((t - lastVideoTS) < chunkTimeInSecs && currentFolder == baseFolder && chunkTimeInMinutes != UINT32_MAX) + if ((t - lastVideoTS) < chunkTimeInSecs && currentFolder == baseFolder) { relPath = lastVideoFolderPath; mp4FileName = lastVideoName; - nextFrameFileName = filePath(relPath, mp4FileName, baseFolder); + nextFrameFileName = filePath(relPath, mp4FileName, baseFolder, chunkTimeInMinutes); return; } @@ -144,7 +150,7 @@ void Mp4WriterSinkUtils::parseTSJpeg(uint64_t &ts, uint32_t &chunkTimeInMinutes, lastVideoName = mp4FileName; - nextFrameFileName = filePath(relPath, mp4FileName, baseFolder); + nextFrameFileName = filePath(relPath, mp4FileName, baseFolder, chunkTimeInMinutes); } void Mp4WriterSinkUtils::parseTSH264(uint64_t& ts, uint32_t& chunkTimeInMinutes, uint32_t& syncTimeInSeconds,boost::filesystem::path& relPath, std::string& mp4FileName, bool& syncFlag, short frameType, short naluType, std::string baseFolder, std::string& nextFrameFileName) @@ -169,15 +175,15 @@ void Mp4WriterSinkUtils::parseTSH264(uint64_t& ts, uint32_t& chunkTimeInMinutes, } // used cached values if the difference in ts is less than chunkTime uint32_t chunkTimeInSecs = 60 * chunkTimeInMinutes; - if ((t - lastVideoTS) < chunkTimeInSecs && currentFolder == baseFolder && chunkTimeInMinutes != UINT32_MAX) + if ((t - lastVideoTS) < chunkTimeInSecs && currentFolder == baseFolder)// && chunkTimeInMinutes != UINT32_MAX { relPath = lastVideoFolderPath; mp4FileName = lastVideoName; - nextFrameFileName = filePath(relPath, mp4FileName, baseFolder); + nextFrameFileName = filePath(relPath, mp4FileName, baseFolder, chunkTimeInMinutes); return; } // cannot be merged with if condition above. - if (naluType != H264Utils::H264_NAL_TYPE::H264_NAL_TYPE_IDR_SLICE && chunkTimeInMinutes != UINT32_MAX) + if (naluType != H264Utils::H264_NAL_TYPE::H264_NAL_TYPE_IDR_SLICE && naluType != H264Utils::H264_NAL_TYPE_SEQ_PARAM) { relPath = lastVideoFolderPath; mp4FileName = lastVideoName; @@ -200,7 +206,7 @@ void Mp4WriterSinkUtils::parseTSH264(uint64_t& ts, uint32_t& chunkTimeInMinutes, lastVideoName = mp4FileName; lastVideoFolderPath = relPath; - nextFrameFileName = filePath(relPath, mp4FileName, baseFolder); + nextFrameFileName = filePath(relPath, mp4FileName, baseFolder, chunkTimeInMinutes); tempNextFrameFileName = nextFrameFileName; } diff --git a/base/test/mp4writersink_tests.cpp b/base/test/mp4writersink_tests.cpp index e85497b31..16c9d59b9 100644 --- a/base/test/mp4writersink_tests.cpp +++ b/base/test/mp4writersink_tests.cpp @@ -13,6 +13,8 @@ #include "H264Utils.h" #include "ExternalSinkModule.h" #include "Mp4ReaderSource.h" +#include "ExternalSourceModule.h" +#include BOOST_AUTO_TEST_SUITE(mp4WriterSink_tests) @@ -29,7 +31,7 @@ void writeH264(bool readLoop, int sleepSeconds, std::string outFolderPath, int c Logger::initLogger(loggerProps); auto fileReaderProps = FileReaderModuleProps(inFolderPath, 0, -1); - fileReaderProps.fps = 100; + fileReaderProps.fps = 24; fileReaderProps.readLoop = readLoop; auto fileReader = boost::shared_ptr(new FileReaderModule(fileReaderProps)); @@ -62,6 +64,8 @@ void writeH264(bool readLoop, int sleepSeconds, std::string outFolderPath, int c p->term(); p->wait_for_all(); p.reset(); + + Test_Utils::deleteFolder(outFolderPath); } void write(std::string inFolderPath, std::string outFolderPath, int width, int height, int chunkTime = 1) { @@ -104,6 +108,8 @@ void write(std::string inFolderPath, std::string outFolderPath, int width, int h p->term(); p->wait_for_all(); p.reset(); + + Test_Utils::deleteFolder(outFolderPath); } void write_metadata(std::string inFolderPath, std::string outFolderPath, std::string metadataPath, int width, int height, int fps) @@ -160,6 +166,8 @@ void write_metadata(std::string inFolderPath, std::string outFolderPath, std::st p->term(); p->wait_for_all(); p.reset(); + + Test_Utils::deleteFolder(outFolderPath); } void read_write(std::string videoPath, std::string outPath, int width, int height,framemetadata_sp metadata, @@ -201,10 +209,10 @@ void read_write(std::string videoPath, std::string outPath, int width, int heigh p->stop(); p->term(); - p->wait_for_all(); - p.reset(); + + Test_Utils::deleteFolder(outPath); } BOOST_AUTO_TEST_CASE(jpg_rgb_24_to_mp4v) @@ -295,19 +303,22 @@ BOOST_AUTO_TEST_CASE(setgetprops_jpeg) LOG_ERROR << "processing folder <" << inFolderPath << ">"; p->run_all_threaded(); - Test_Utils::sleep_for_seconds(20); + Test_Utils::sleep_for_seconds(10); Mp4WriterSinkProps propschange = mp4WriterSink->getProps(); propschange.chunkTime = 2; propschange.baseFolder = changedOutFolderPath; mp4WriterSink->setProps(propschange); - Test_Utils::sleep_for_seconds(70); + Test_Utils::sleep_for_seconds(10); p->stop(); p->term(); p->wait_for_all(); p.reset(); + + Test_Utils::deleteFolder(outFolderPath); + Test_Utils::deleteFolder(changedOutFolderPath); } BOOST_AUTO_TEST_CASE(h264_to_mp4v) @@ -383,6 +394,8 @@ BOOST_AUTO_TEST_CASE(h264_metadata, *boost::unit_test::disabled()) p->term(); p->wait_for_all(); p.reset(); + + Test_Utils::deleteFolder(outFolderPath); } BOOST_AUTO_TEST_CASE(parsenalu) @@ -498,12 +511,15 @@ BOOST_AUTO_TEST_CASE(setgetprops_h264) propschange.baseFolder = changedOutFolderPath; mp4WriterSink->setProps(propschange); - Test_Utils::sleep_for_seconds(60); + Test_Utils::sleep_for_seconds(20); p->stop(); p->term(); p->wait_for_all(); p.reset(); + + Test_Utils::deleteFolder(outFolderPath); + Test_Utils::deleteFolder(changedOutFolderPath); } BOOST_AUTO_TEST_CASE(single_file_given_name_jpeg) @@ -523,7 +539,7 @@ BOOST_AUTO_TEST_CASE(single_file_given_name_h264) // custom name is only supported while writing to single video file (chunktime = UINT32_MAX). std::string outFolderPath = "./data/testOutput/mp4_videos/h264_videos/apraH264.mp4"; - writeH264(true,80,outFolderPath, UINT32_MAX); + writeH264(true,10,outFolderPath, UINT32_MAX); } BOOST_AUTO_TEST_CASE(read_mul_write_one_as_recorded) @@ -540,4 +556,129 @@ BOOST_AUTO_TEST_CASE(read_mul_write_one_as_recorded) 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"; + std::string metadataPath = "./data/Metadata/"; + std::string outPath = "data/testOutput/mp4_videos/h264/step/stepvideo.mp4"; + int width = 704; + int height = 576; + + LoggerProps loggerProps; + loggerProps.logLevel = boost::log::trivial::severity_level::info; + Logger::setLogLevel(boost::log::trivial::severity_level::info); + Logger::initLogger(loggerProps); + + auto fileReaderProps = FileReaderModuleProps(inFolderPath, 0, -1); + fileReaderProps.fps = 24; + fileReaderProps.readLoop = false; + + auto fileReader = boost::shared_ptr(new FileReaderModule(fileReaderProps)); + auto h264ImageMetadata = framemetadata_sp(new H264Metadata(width, height)); + fileReader->addOutputPin(h264ImageMetadata); + + auto mp4WriterSinkProps = Mp4WriterSinkProps(UINT32_MAX, 10, 100, outPath); + mp4WriterSinkProps.logHealth = true; + mp4WriterSinkProps.logHealthFrequency = 100; + auto mp4WriterSink = boost::shared_ptr(new Mp4WriterSink(mp4WriterSinkProps)); + fileReader->setNext(mp4WriterSink); + + fileReader->play(true); + + BOOST_TEST(fileReader->init()); + BOOST_TEST(mp4WriterSink->init()); + + for (int i = 0; i < 230; i++) + { + fileReader->step(); + mp4WriterSink->step(); + } + fileReader->term(); + mp4WriterSink->term(); + boost::filesystem::path mp4fileName = "data/testOutput/mp4_videos/h264/step/stepvideo.mp4"; + auto fileSize = boost::filesystem::file_size(mp4fileName); + //checking the size of mp4 file + BOOST_TEST(fileSize, 4270314); + + Test_Utils::deleteFolder(mp4fileName.string()); + +} + +BOOST_AUTO_TEST_CASE(write_mp4video_metadata_h264_step) +{ + std::string videoMetadata = "aprapipes"; + std::string inFolderPath = "./data/h264_data"; + std::string outPath = "data/testOutput/mp4_videos/h264_metadata/step/stepvideo.mp4"; + int width = 704; + int height = 576; + + LoggerProps loggerProps; + loggerProps.logLevel = boost::log::trivial::severity_level::info; + Logger::setLogLevel(boost::log::trivial::severity_level::info); + Logger::initLogger(loggerProps); + + auto fileReaderProps = FileReaderModuleProps(inFolderPath, 0, -1); + fileReaderProps.fps = 24; + fileReaderProps.readLoop = true; + + auto fileReader = boost::shared_ptr(new FileReaderModule(fileReaderProps)); + auto encodedImageMetadata = framemetadata_sp(new H264Metadata(width, height)); + fileReader->addOutputPin(encodedImageMetadata); + + + auto metadataSource = boost::shared_ptr(new ExternalSourceModule()); + + auto mp4Metadata = framemetadata_sp(new Mp4VideoMetadata("v_1_0")); + auto metadataPinId = metadataSource->addOutputPin(mp4Metadata); + + FramesMuxerProps muxerProps; + muxerProps.strategy = FramesMuxerProps::MAX_DELAY_ANY; + muxerProps.maxDelay = 0; + auto readerMuxer = boost::shared_ptr(new FramesMuxer(muxerProps)); + fileReader->setNext(readerMuxer); + metadataSource->setNext(readerMuxer); + + auto mp4WriterSinkProps = Mp4WriterSinkProps(UINT32_MAX, 10, fileReaderProps.fps, outPath); + mp4WriterSinkProps.logHealth = true; + mp4WriterSinkProps.logHealthFrequency = 100; + auto mp4WriterSink = boost::shared_ptr(new Mp4WriterSink(mp4WriterSinkProps)); + readerMuxer->setNext(mp4WriterSink); + + fileReader->play(true); + + BOOST_TEST(fileReader->init()); + BOOST_TEST(metadataSource->init()); + BOOST_TEST(readerMuxer->init()); + BOOST_TEST(mp4WriterSink->init()); + for (int i = 0; i < 230; i++) + { + fileReader->step(); + auto metaFrame = metadataSource->makeFrame(0, metadataPinId); + if (i % 6 == 0) + { + //write metadata to only every 6th frame . For every other frame the metadata size will be 0. All the 230 frames with 230 entries for metadata as well + metaFrame = metadataSource->makeFrame(videoMetadata.size(), metadataPinId); + memcpy(metaFrame->data(), videoMetadata.data(), videoMetadata.size()); + } + //writing the current timestamp to the file . + std::chrono::time_point t = std::chrono::system_clock::now(); + auto dur = std::chrono::duration_cast(t.time_since_epoch()); + metaFrame->timestamp = dur.count(); + frame_container frames; + frames.insert(make_pair(metadataPinId, metaFrame)); + metadataSource->send(frames); + readerMuxer->step(); + readerMuxer->step(); + mp4WriterSink->step(); + } + fileReader->term(); + metadataSource->term(); + readerMuxer->term(); + mp4WriterSink->term(); + boost::filesystem::path mp4FileName = "data/testOutput/mp4_videos/h264_metadata/step/stepvideo.mp4"; + auto fileSize = boost::filesystem::file_size(mp4FileName); + BOOST_TEST(fileSize == 4270665); + + Test_Utils::deleteFolder(mp4FileName.string()); +} BOOST_AUTO_TEST_SUITE_END()