From 57655f1b21e77c69f7309e8ddc1d6a933f75a068 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sat, 20 Apr 2024 02:11:57 +0200 Subject: [PATCH] Upgrade segmentreader to support (f)MP4 segments, add such support to HLS input --- lib/segmentreader.cpp | 159 +++++++++++++++++++++++++++++++++++----- lib/segmentreader.h | 3 + src/input/input_hls.cpp | 52 ++++--------- src/input/input_hls.h | 2 +- 4 files changed, 159 insertions(+), 57 deletions(-) diff --git a/lib/segmentreader.cpp b/lib/segmentreader.cpp index 8f0e95fa..6b8a5c51 100644 --- a/lib/segmentreader.cpp +++ b/lib/segmentreader.cpp @@ -29,6 +29,8 @@ namespace Mist{ #endif currBuf = 0; packetPtr = 0; + mp4PacksLeft = 0; + lastMoof = 0; } void SegmentReader::onProgress(bool (*callback)(uint8_t)){ @@ -49,6 +51,9 @@ namespace Mist{ // Buffered? Just return false - we can't download more. if (buffered){return false;} + // Past end of file? Always return false. + if (_offset > currBuf->rsize()){return false;} + #ifdef SSL // Encrypted? Round up to nearest multiple of 16 if (encrypted && _offset % 16){ @@ -86,7 +91,34 @@ namespace Mist{ } void SegmentReader::initializeMetadata(DTSC::Meta &meta, size_t tid, size_t mappingId){ - tsStream.initializeMetadata(meta, tid, mappingId); + if (parser == STRM_TS){ + tsStream.initializeMetadata(meta, tid, mappingId); + return; + } + + if (parser == STRM_MP4){ + for (std::deque::iterator it = mp4Headers.begin(); it != mp4Headers.end(); ++it){ + if (it->trackId != tid){continue;} + size_t tNumber = meta.addTrack(); + INFO_MSG("Found track %zu of type %s -> %s", tNumber, it->sType.c_str(), it->codec.c_str()); + meta.setID(tNumber, mappingId); + meta.setCodec(tNumber, it->codec); + meta.setInit(tNumber, it->initData); + meta.setLang(tNumber, it->lang); + if (it->trackType == "video"){ + meta.setType(tNumber, "video"); + meta.setWidth(tNumber, it->vidWidth); + meta.setHeight(tNumber, it->vidHeight); + } + if (it->trackType == "audio"){ + meta.setType(tNumber, "audio"); + meta.setChannels(tNumber, it->audChannels); + meta.setRate(tNumber, it->audRate); + meta.setSize(tNumber, it->audSize); + } + } + return; + } } /// Attempts to read a single TS packet from the current segment, setting packetPtr on success @@ -101,7 +133,7 @@ namespace Mist{ parser = STRM_TS; continue; } - if (!memcmp(*currBuf + 4, "ftyp", 4) || !memcmp(*currBuf + 4, "moof", 4) || !memcmp(*currBuf + 4, "moov", 4)){ + if (!memcmp(*currBuf + 4, "ftyp", 4) || !memcmp(*currBuf + 4, "styp", 4) || !memcmp(*currBuf + 4, "moof", 4) || !memcmp(*currBuf + 4, "moov", 4)){ parser = STRM_MP4; continue; } @@ -122,34 +154,118 @@ namespace Mist{ } if (parser == STRM_MP4){ - /// \TODO Implement parsing MP4 data + if (mp4PacksLeft){ + std::deque::iterator pIt = mp4PackNo.begin(); + for (std::deque::iterator it = mp4Headers.begin(); it != mp4Headers.end(); ++it){ + if (*pIt < it->size()){ + uint64_t prtBpos = 0, prtTime = 0; + uint32_t prtBlen = 0; + int32_t prtTimeOff = 0; + bool prtKey = false; + it->getPart(*pIt, &prtBpos, &prtBlen, &prtTime, &prtTimeOff, &prtKey, lastMoof); + // Increase/decrease counters + --mp4PacksLeft; + ++(*pIt); + // Abort reading if we cannot read this part, try the next part + if (!readTo(prtBpos + prtBlen)){continue;} + // Fill the packet and return true + thisPacket.genericFill(prtTime, prtTimeOff, it->trackId, *currBuf + prtBpos, prtBlen, bytePos, prtKey); + return true; + } + ++pIt; + } + } + if (!mp4PacksLeft){ + // Read more boxes! + if (offset >= currBuf->rsize()){return false;} + if (!readTo(offset + 12)){ + INFO_MSG("Could not read next MP4 box!"); + return false; + } + std::string boxType = std::string(*currBuf+offset+4, 4); + uint64_t boxSize = MP4::calcBoxSize(*currBuf+offset); + if (!readTo(offset + boxSize)){ + INFO_MSG("Could not read next MP4 box!"); + return false; + } + if (boxType == "moov"){ + mp4PacksLeft = 0; + mp4Headers.clear(); + mp4PackNo.clear(); + MP4::Box moovBox(*currBuf+offset, false); + std::deque trak = ((MP4::MOOV*)&moovBox)->getChildren(); + for (std::deque::iterator trakIt = trak.begin(); trakIt != trak.end(); trakIt++){ + mp4Headers.push_back(MP4::TrackHeader()); + mp4PackNo.push_back(0); + mp4Headers.rbegin()->read(*trakIt); + mp4PacksLeft += mp4Headers.rbegin()->size(); + } + MEDIUM_MSG("Read moov box"); + } + if (boxType == "moof"){ + if (!mp4Headers.size()){ + FAIL_MSG("Attempting to read moof box without reading moov box first!"); + return false; + } + lastMoof = offset; + MP4::Box moofBox(*currBuf+offset, false); + // Indicate that we're reading the next moof box to all track headers + for (std::deque::iterator it = mp4Headers.begin(); it != mp4Headers.end(); ++it){ + it->nextMoof(); + } + // Loop over traf boxes inside the moof box, but them in our header parser + std::deque trafs = ((MP4::MOOF*)&moofBox)->getChildren(); + for (std::deque::iterator t = trafs.begin(); t != trafs.end(); ++t){ + if (!(t->getChild())){ + WARN_MSG("Could not find thfd box inside traf box!"); + continue; + } + uint32_t trackId = t->getChild().getTrackID(); + for (std::deque::iterator it = mp4Headers.begin(); it != mp4Headers.end(); ++it){ + if (it->trackId == trackId){ + it->read(*t); + break; + } + } + } + mp4PacksLeft = 0; + std::deque::iterator pIt = mp4PackNo.begin(); + for (std::deque::iterator it = mp4Headers.begin(); it != mp4Headers.end(); ++it){ + mp4PacksLeft += it->size(); + (*pIt) = 0; + ++pIt; + } + } + offset += boxSize; + } } } return false; } void SegmentReader::setInit(const std::string & data){ - /// \TODO Implement detecting/parsing MP4 init data - /* - std::string boxType = std::string(readBuffer+4, 4); - uint64_t boxSize = MP4::calcBoxSize(readBuffer); + char * ptr = (char *)data.data(); + size_t len = data.size(); + size_t offset = 0; + while (offset + 8 <= len){ + std::string boxType = std::string(ptr+offset+4, 4); + uint64_t boxSize = MP4::calcBoxSize(ptr+offset); if (boxType == "moov"){ - while (readBuffer.size() < boxSize && inFile && keepRunning()){inFile.readSome(boxSize-readBuffer.size(), *this);} - if (readBuffer.size() < boxSize){ - Util::logExitReason(ER_FORMAT_SPECIFIC, "Could not read entire MOOV box into memory"); - break; - } - MP4::Box moovBox(readBuffer, false); - - // for all box in moov + mp4PacksLeft = 0; + mp4Headers.clear(); + mp4PackNo.clear(); + MP4::Box moovBox(ptr+offset, false); std::deque trak = ((MP4::MOOV*)&moovBox)->getChildren(); for (std::deque::iterator trakIt = trak.begin(); trakIt != trak.end(); trakIt++){ - trackHeaders.push_back(MP4::TrackHeader()); - trackHeaders.rbegin()->read(*trakIt); + mp4Headers.push_back(MP4::TrackHeader()); + mp4PackNo.push_back(0); + mp4Headers.rbegin()->read(*trakIt); + mp4PacksLeft += mp4Headers.rbegin()->size(); } - hasMoov = true; + MEDIUM_MSG("Read moov box"); } - */ + offset += boxSize; + } } /// Stores data in currBuf, decodes if/as necessary, in whole 16-byte blocks @@ -212,6 +328,11 @@ namespace Mist{ /// Loads the given segment URL into the segment buffer. bool SegmentReader::load(const std::string &path, uint64_t startAt, uint64_t stopAt, const char * ivec, const char * keyAES, Util::ResizeablePointer * bufPtr){ tsStream.partialClear(); + lastMoof = 0; + for (std::deque::iterator it = mp4Headers.begin(); it != mp4Headers.end(); ++it){ + it->nextMoof(); + } + isOpen = false; parser = STRM_UNKN; if (ivec && keyAES && memcmp(keyAES, "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", 16)){ diff --git a/lib/segmentreader.h b/lib/segmentreader.h index cb4c8c40..74be25f7 100644 --- a/lib/segmentreader.h +++ b/lib/segmentreader.h @@ -42,6 +42,9 @@ namespace Mist{ streamType parser; TS::Stream tsStream; std::deque mp4Headers; + std::deque mp4PackNo; + size_t mp4PacksLeft; + uint64_t lastMoof; #ifdef SSL diff --git a/src/input/input_hls.cpp b/src/input/input_hls.cpp index 7f39c957..275d7a7a 100644 --- a/src/input/input_hls.cpp +++ b/src/input/input_hls.cpp @@ -1,21 +1,5 @@ #include "input_hls.h" -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include #define SEM_TS_CLAIM "/MstTSIN%s" @@ -217,7 +201,7 @@ namespace Mist{ } pls.reload(); - playlistMapping[plsTotalCount] = pls; + playlistMapping[pls.id] = pls; plsInitCount++; if (initOnly){ return; @@ -275,14 +259,6 @@ namespace Mist{ } } - void flipKey(char *d){ - for (size_t i = 0; i < 8; i++){ - char tmp = d[i]; - d[i] = d[15 - i]; - d[15 - i] = tmp; - } - } - /// Handles both initial load and future reloads. /// Returns how many segments were added to the internal segment list. bool Playlist::reload(){ @@ -411,8 +387,8 @@ namespace Mist{ if (key == "MAP"){ size_t mapLen = 0, mapOffset = 0; size_t tmpPos = val.find("BYTERANGE=\""); - size_t tmpPos2 = val.substr(tmpPos).find('"'); if (tmpPos != std::string::npos){ + size_t tmpPos2 = val.substr(tmpPos).find('"'); mapRange = val.substr(tmpPos + 11, tmpPos2 - tmpPos - 11); size_t atSign = mapRange.find('@'); @@ -427,8 +403,8 @@ namespace Mist{ } tmpPos = val.find("URI=\""); - tmpPos2 = val.substr(tmpPos + 5).find('"'); if (tmpPos != std::string::npos){ + size_t tmpPos2 = val.substr(tmpPos + 5).find('"'); mapUri = val.substr(tmpPos + 5, tmpPos2); } @@ -450,12 +426,13 @@ namespace Mist{ mapPLen = 0; } } + if (!mapLen){mapLen = mapPLen;} if (mapLen < mapPLen){mapPLen = mapLen;} if (!mapPLen){ FAIL_MSG("Could not retrieve map from '%s'", root.link(mapUri).getUrl().c_str()); continue; } - maps.insert(std::pair(keyUri, std::string(mapPtr, mapPLen))); + maps.insert(std::pair(mapUri+mapRange, std::string(mapPtr, mapPLen))); } continue; } @@ -665,9 +642,6 @@ namespace Mist{ } - InputHLS::~InputHLS(){ - } - bool InputHLS::checkArguments(){ config->is_active = true; if (config->getString("input") == "-"){ @@ -751,6 +725,9 @@ namespace Mist{ if (thisEntry.size() >= 11){ newEntry.startAtByte = thisEntry[9u].asInt(); newEntry.stopAtByte = thisEntry[10u].asInt(); + if (thisEntry.size() >= 12){ + newEntry.mapName = thisEntry[11u].asStringRef(); + } } newList.push_back(newEntry); } @@ -904,9 +881,12 @@ namespace Mist{ thisEntries.append(entryIt->wait); thisEntries.append(entryIt->ivec); thisEntries.append(entryIt->keyAES); - if (entryIt->startAtByte || entryIt->stopAtByte){ + if (entryIt->startAtByte || entryIt->stopAtByte || entryIt->mapName.size()){ thisEntries.append(entryIt->startAtByte); thisEntries.append(entryIt->stopAtByte); + if (entryIt->mapName.size()){ + thisEntries.append(entryIt->mapName); + } } thisPlaylist.append(thisEntries); } @@ -1166,7 +1146,6 @@ namespace Mist{ } } - // Note: bpos is overloaded here for playlist entry! void InputHLS::seek(uint64_t seekTime, size_t idx){ if (idx == INVALID_TRACK_ID){return;} plsTimeOffset.clear(); @@ -1174,10 +1153,10 @@ namespace Mist{ plsInterval.clear(); segDowner.reset(); uint64_t trackId = M.getID(idx); + currentPlaylist = getMappedTrackPlaylist(trackId); unsigned long plistEntry = 0; - - DTSC::Keys keys(M.keys(idx)); + DTSC::Keys keys = M.getKeys(idx); for (size_t i = keys.getFirstValid(); i < keys.getEndValid(); i++){ if (keys.getTime(i) > seekTime){ VERYHIGH_MSG("Found elapsed key with a time of %" PRIu64 " ms. Using playlist index %zu to match requested time %lu", keys.getTime(i), plistEntry, seekTime); @@ -1191,9 +1170,8 @@ namespace Mist{ plistEntry = keys.getBpos(i) - 1 - playlistMapping[currentPlaylist].firstIndex; INSANE_MSG("Found valid key with a time of %" PRIu64 " ms at playlist index %zu while seeking", keys.getTime(i), plistEntry); } - currentIndex = plistEntry; - currentPlaylist = getMappedTrackPlaylist(trackId); + VERYHIGH_MSG("Seeking to index %zu on playlist %" PRIu64, currentIndex, currentPlaylist); {// Lock mutex for listEntries diff --git a/src/input/input_hls.h b/src/input/input_hls.h index abcf349f..4603cd75 100644 --- a/src/input/input_hls.h +++ b/src/input/input_hls.h @@ -100,7 +100,7 @@ namespace Mist{ class InputHLS : public Input{ public: InputHLS(Util::Config *cfg); - ~InputHLS(); + ~InputHLS(){} bool needsLock(){return !config->getBool("realtime");} bool openStreamSource(); bool callback();