From d3df5d07a13cb18be498db4f25b77265ee307d35 Mon Sep 17 00:00:00 2001 From: Frank Schmirler Date: Tue, 1 Oct 2013 23:47:25 +0200 Subject: [PATCH] Redesigned pos= parameter patch for streaming recordings and added missing bits like HEAD and resume.# support --- server/connectionHTTP.c | 144 ++++++++++++++++++++++++---------------- server/connectionHTTP.h | 8 +-- server/connectionVTP.c | 2 +- server/recplayer.c | 70 +++++++++---------- server/recplayer.h | 9 +-- server/recstreamer.c | 75 ++------------------- server/recstreamer.h | 10 ++- 7 files changed, 137 insertions(+), 181 deletions(-) diff --git a/server/connectionHTTP.c b/server/connectionHTTP.c index 73029d7..02e5170 100644 --- a/server/connectionHTTP.c +++ b/server/connectionHTTP.c @@ -24,7 +24,9 @@ cConnectionHTTP::cConnectionHTTP(void): m_Streamer(NULL), m_StreamType((eStreamType)StreamdevServerSetup.HTTPStreamType), m_Channel(NULL), - m_Recording(NULL), + m_RecPlayer(NULL), + m_ReplayPos(0), + m_ReplayFakeRange(false), m_MenuList(NULL) { Dprintf("constructor hsRequest\n"); @@ -35,7 +37,7 @@ cConnectionHTTP::cConnectionHTTP(void): cConnectionHTTP::~cConnectionHTTP() { delete m_Streamer; - delete m_Recording; + delete m_RecPlayer; } bool cConnectionHTTP::CanAuthenticate(void) @@ -197,53 +199,32 @@ bool cConnectionHTTP::ProcessRequest(void) } return HttpResponse(503, true); } - else if (m_Recording != NULL) { + else if (m_RecPlayer != NULL) { Dprintf("GET recording\n"); - cStreamdevRecStreamer* recStreamer = new cStreamdevRecStreamer(m_Recording, this, m_ReplayPos); - m_Streamer = recStreamer; int64_t from, to; + bool hasRange = ParseRange(from, to); + + cStreamdevRecStreamer* recStreamer; + if (from == 0 && hasRange && m_ReplayFakeRange) { + recStreamer = new cStreamdevRecStreamer(m_RecPlayer, this); + from += m_ReplayPos; + if (to >= 0) + to += m_ReplayPos; + } + else + recStreamer = new cStreamdevRecStreamer(m_RecPlayer, this, m_ReplayPos); + m_Streamer = recStreamer; uint64_t total = recStreamer->GetLength(); - if (ParseRange(from, to)) { - Dprintf("parsed from-to: %lld - %lld\n", (long long)from, (long long)to); + if (hasRange) { int64_t length = recStreamer->SetRange(from, to); - int64_t fromByPos = recStreamer->GetFromByPos(); - if (fromByPos > 0) { - if (from == 0) { - from = fromByPos; - to = total - 1; - Dprintf("from byte: %lld\n", (long long)from); - } - else if (m_ReplayPos.find("full_") != 0) { - from += fromByPos; - to += fromByPos; - } - Dprintf("part of recording: %lld-%lld/%lld\n", (long long)from, (long long)to, (long long)total); - length = recStreamer->SetRange(from, to); - Dprintf("part of recording: %lld-%lld/%lld, len %lld\n", (long long)from, (long long)to, (long long)total, (long long)length); - } - if (m_ReplayPos.find("full_") != 0 && fromByPos > 0) { - from -= fromByPos; - to -= fromByPos; - total -= fromByPos; - } Dprintf("range response: %lld-%lld/%lld, len %lld\n", (long long)from, (long long)to, (long long)total, (long long)length); if (length < 0L) return HttpResponse(416, true, "video/mpeg", "Accept-Ranges: bytes\r\nContent-Range: bytes */%llu", (unsigned long long) total); else return HttpResponse(206, false, "video/mpeg", "Accept-Ranges: bytes\r\nContent-Range: bytes %lld-%lld/%llu\r\nContent-Length: %lld", (long long) from, (long long) to, (unsigned long long) total, (long long) length); } - else { - int64_t fromByPos = recStreamer->GetFromByPos(); - if (fromByPos > 0) { - from = fromByPos; - to = total - 1; - Dprintf("from byte: %lld\n", (long long)from); - int64_t length = recStreamer->SetRange(from, to); - Dprintf("part of recording: %lld-%lld/%lld, len %lld\n", (long long)from, (long long)to, (long long)total, (long long)length); - } - + else return HttpResponse(200, false, "video/mpeg", "Accept-Ranges: bytes"); - } } else { return HttpResponse(404, true); @@ -270,13 +251,23 @@ bool cConnectionHTTP::ProcessRequest(void) } return HttpResponse(503, true); } - else if (m_Recording != NULL) { + else if (m_RecPlayer != NULL) { Dprintf("HEAD recording\n"); - cStreamdevRecStreamer *recStreamer = new cStreamdevRecStreamer(m_Recording, this, m_ReplayPos); - m_Streamer = recStreamer; int64_t from, to; + bool hasRange = ParseRange(from, to); + + cStreamdevRecStreamer* recStreamer; + if (from == 0 && hasRange && m_ReplayFakeRange) { + recStreamer = new cStreamdevRecStreamer(m_RecPlayer, this, m_ReplayPos); + from += m_ReplayPos; + if (to >= 0) + to += m_ReplayPos; + } + else + recStreamer = new cStreamdevRecStreamer(m_RecPlayer, this, m_ReplayPos); + m_Streamer = recStreamer; uint64_t total = recStreamer->GetLength(); - if (ParseRange(from, to)) { + if (hasRange) { int64_t length = recStreamer->SetRange(from, to); if (length < 0L) return HttpResponse(416, true, "video/mpeg", "Accept-Ranges: bytes\r\nContent-Range: bytes */%llu", (unsigned long long) total); @@ -362,7 +353,6 @@ bool cConnectionHTTP::ParseRange(int64_t &From, int64_t &To) const From = To = 0L; tStrStrMap::const_iterator it = Headers().find(RANGE); if (it != Headers().end()) { - Dprintf("%s: %s\n", it->first.c_str(), it->second.c_str()); size_t b = it->second.find("bytes="); if (b != std::string::npos) { char* e = NULL; @@ -482,8 +472,10 @@ cMenuList* cConnectionHTTP::MenuListFromString(const std::string& Path, const st return NULL; } -cRecording* cConnectionHTTP::RecordingFromString(const char *FileBase, const char *FileExt) const +RecPlayer* cConnectionHTTP::RecPlayerFromString(const char *FileBase, const char *FileExt) { + RecPlayer *recPlayer = NULL; + if (strcasecmp(FileExt, ".rec") != 0) return NULL; @@ -498,19 +490,63 @@ cRecording* cConnectionHTTP::RecordingFromString(const char *FileBase, const cha cThreadLock RecordingsLock(&Recordings); for (cRecording *rec = Recordings.First(); rec; rec = Recordings.Next(rec)) { if (stat(rec->FileName(), &st) == 0 && st.st_dev == (dev_t) l && st.st_ino == inode) - return new cRecording(rec->FileName()); + recPlayer = new RecPlayer(rec->FileName()); } } } else if (*p == 0) { // get recording by index cThreadLock RecordingsLock(&Recordings); - cRecording* rec = Recordings.Get((int) l - 1); + cRecording *rec = Recordings.Get((int) l - 1); if (rec) - return new cRecording(rec->FileName()); + recPlayer = new RecPlayer(rec->FileName()); + } + + if (recPlayer) { + const char *pos = NULL; + tStrStrMap::const_iterator it = m_Params.begin(); + while (it != m_Params.end()) { + if (it->first == "pos") { + pos = it->second.c_str(); + break; + } + ++it; + } + if (pos) { + // With prefix "full_" we try to fool players + // by replying with a content range starting + // at the requested position instead of 0. + // This is a heavy violation of standards. + // Use at your own risk! + if (strncasecmp(pos, "full_", 5) == 0) { + m_ReplayFakeRange = true; + pos += 5; + } + if (strncasecmp(pos, "resume", 6) == 0) { + int id = pos[6] == '.' ? atoi(pos + 7) : 0; + m_ReplayPos = recPlayer->positionFromResume(id); + } + else if (strncasecmp(pos, "mark.", 5) == 0) { + int index = atoi(pos + 5); + m_ReplayPos = recPlayer->positionFromMark(index); + } + else if (strncasecmp(pos, "time.", 5) == 0) { + int seconds = atoi(pos + 5); + m_ReplayPos = recPlayer->positionFromTime(seconds); + } + else if (strncasecmp(pos, "frame.", 6) == 0) { + int frame = atoi(pos + 6); + m_ReplayPos = recPlayer->positionFromFrameNumber(frame); + } + else { + m_ReplayPos = atol(pos); + if (m_ReplayPos > 0L && m_ReplayPos < 100L) + m_ReplayPos = recPlayer->positionFromPercent((int) m_ReplayPos); + } + } } } - return NULL; + return recPlayer; } bool cConnectionHTTP::ProcessURI(const std::string& PathInfo) @@ -552,16 +588,8 @@ bool cConnectionHTTP::ProcessURI(const std::string& PathInfo) if ((m_MenuList = MenuListFromString(PathInfo.substr(1, file_pos), filespec.c_str(), fileext.c_str())) != NULL) { Dprintf("Channel list requested\n"); return true; - } else if ((m_Recording = RecordingFromString(filespec.c_str(), fileext.c_str())) != NULL) { - Dprintf("Recording %s found\n", m_Recording->Name()); - tStrStrMap::iterator it = m_Params.begin(); - while (it != m_Params.end()) { - if (it->first == "pos") { - m_ReplayPos = it->second; - Dprintf("pos: %s\n", m_ReplayPos.c_str()); - break; - } - } + } else if ((m_RecPlayer = RecPlayerFromString(filespec.c_str(), fileext.c_str())) != NULL) { + Dprintf("Recording %s found\n", m_RecPlayer->getCurrentRecording()->Name()); return true; } else if ((m_Channel = ChannelFromString(filespec.c_str(), &m_Apid[0], &m_Dpid[0])) != NULL) { Dprintf("Channel found. Apid/Dpid is %d/%d\n", m_Apid[0], m_Dpid[0]); diff --git a/server/connectionHTTP.h b/server/connectionHTTP.h index 3161cfc..e02f221 100644 --- a/server/connectionHTTP.h +++ b/server/connectionHTTP.h @@ -34,13 +34,14 @@ private: int m_Apid[2]; int m_Dpid[2]; // job: replay - cRecording *m_Recording; - std::string m_ReplayPos; + RecPlayer *m_RecPlayer; + int64_t m_ReplayPos; + bool m_ReplayFakeRange; // job: listing cMenuList *m_MenuList; cMenuList* MenuListFromString(const std::string &PathInfo, const std::string &Filebase, const std::string &Fileext) const; - cRecording* RecordingFromString(const char* FileBase, const char* FileExt) const; + RecPlayer* RecPlayerFromString(const char* FileBase, const char* FileExt); bool ProcessURI(const std::string &PathInfo); bool HttpResponse(int Code, bool Last, const char* ContentType = NULL, const char* Headers = "", ...); @@ -70,7 +71,6 @@ public: virtual bool Abort(void) const; virtual void Flushed(void); - inline std::string GetReplayPos() { return m_ReplayPos; } }; inline bool cConnectionHTTP::Abort(void) const diff --git a/server/connectionVTP.c b/server/connectionVTP.c index 57afafa..55c92de 100644 --- a/server/connectionVTP.c +++ b/server/connectionVTP.c @@ -1150,7 +1150,7 @@ bool cConnectionVTP::CmdPLAY(char *Opts) if (m_RecPlayer) { delete m_RecPlayer; } - m_RecPlayer = new RecPlayer(recording); + m_RecPlayer = new RecPlayer(recording->FileName()); return Respond(220, "%llu (Bytes), %u (Frames)", (long long unsigned int) m_RecPlayer->getLengthBytes(), (unsigned int) m_RecPlayer->getLengthFrames()); } else { diff --git a/server/recplayer.c b/server/recplayer.c index f85ed45..3885b4c 100644 --- a/server/recplayer.c +++ b/server/recplayer.c @@ -19,10 +19,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include -#include -#include - #include "recplayer.h" // for TSPLAY patch detection @@ -32,17 +28,17 @@ #define _XOPEN_SOURCE 600 #include -RecPlayer::RecPlayer(cRecording* rec) +RecPlayer::RecPlayer(const char* FileName) { file = NULL; fileOpen = 0; lastPosition = 0; - recording = rec; + recording = new cRecording(FileName); for(int i = 1; i < 1000; i++) segments[i] = NULL; // FIXME find out max file path / name lengths - indexFile = new cIndexFile(recording->FileName(), false, rec->IsPesRecording()); + indexFile = new cIndexFile(recording->FileName(), false, recording->IsPesRecording()); if (!indexFile) esyslog("ERROR: Streamdev: Failed to create indexfile!"); scan(); @@ -89,6 +85,8 @@ RecPlayer::~RecPlayer() int i = 1; while(segments[i++]) delete segments[i]; if (file) fclose(file); + delete indexFile; + delete recording; } int RecPlayer::openFile(int index) @@ -214,45 +212,39 @@ cRecording* RecPlayer::getCurrentRecording() return recording; } -int RecPlayer::frameFromResume() +#if VDRVERSNUM < 10732 +#define ALIGNED_POS(x) (positionFromFrameNumber(indexFile->GetNextIFrame(x, 1))) +#else +#define ALIGNED_POS(x) (positionFromFrameNumber(indexFile->GetClosestIFrame(x))) +#endif +uint64_t RecPlayer::positionFromResume(int ResumeID) { - int frame = 0; - char fileName[2048]; - snprintf(fileName, 2047, "%s/resume", recording->FileName()); - std::ifstream ifs; - ifs.open(fileName); - if (!ifs.is_open()) return 0; - std::string sFrame; - getline(ifs, sFrame); - ifs.close(); - sFrame=sFrame.substr(2); - frame=atoi(sFrame.c_str()); - return frame; + int resumeBackup = Setup.ResumeID; + Setup.ResumeID = ResumeID; + cResumeFile resume(recording->FileName(), recording->IsPesRecording()); + Setup.ResumeID = resumeBackup; + return ALIGNED_POS(resume.Read()); } -int RecPlayer::frameFromMark(int index) +uint64_t RecPlayer::positionFromMark(int MarkIndex) { - char fileName[2048]; - snprintf(fileName, 2047, "%s/marks", recording->FileName()); - std::ifstream ifs; - ifs.open(fileName); - if (!ifs.is_open()) return 0; - std::string sTime; - for (int i=0; i<=index; i++) { - getline(ifs, sTime); - if (ifs.eof()) break; - } - ifs.close(); - int seconds = 0, minutes = 0, hours = 0; - hours = atoi(sTime.substr(0, sTime.find(":")).c_str()); - minutes = atoi(sTime.substr(sTime.find(":") + 1, 2).c_str()); - seconds = atoi(sTime.substr(sTime.rfind(":") + 1, 2).c_str()); - return frameFromSeconds(seconds + minutes * 60 + hours * 3600); + cMarks marks; + if (marks.Load(recording->FileName(), recording->FramesPerSecond(), recording->IsPesRecording()) && marks.Count()) { + cMark *mark = marks.cConfig::Get(MarkIndex); + if (mark) + return ALIGNED_POS(mark->Position()); + } + return 0; } -int RecPlayer::frameFromSeconds(int seconds) +uint64_t RecPlayer::positionFromTime(int Seconds) { - return 25 * seconds; // 25fps + return ALIGNED_POS(SecondsToFrames(Seconds, recording->FramesPerSecond())); +} + +uint64_t RecPlayer::positionFromPercent(int Percent) +{ + return ALIGNED_POS(getLengthFrames() * Percent / 100L); } uint64_t RecPlayer::positionFromFrameNumber(uint32_t frameNumber) diff --git a/server/recplayer.h b/server/recplayer.h index d0a3ff9..e56c2cd 100644 --- a/server/recplayer.h +++ b/server/recplayer.h @@ -36,7 +36,7 @@ class Segment class RecPlayer { public: - RecPlayer(cRecording* rec); + RecPlayer(const char* FileName); ~RecPlayer(); uint64_t getLengthBytes(); uint32_t getLengthFrames(); @@ -45,10 +45,11 @@ class RecPlayer uint64_t getLastPosition(); cRecording* getCurrentRecording(); void scan(); + uint64_t positionFromResume(int ResumeID); + uint64_t positionFromMark(int MarkIndex); + uint64_t positionFromTime(int Seconds); + uint64_t positionFromPercent(int Percent); uint64_t positionFromFrameNumber(uint32_t frameNumber); - int frameFromResume(); - int frameFromMark(int index); - int frameFromSeconds(int seconds); uint32_t frameNumberFromPosition(uint64_t position); bool getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength); diff --git a/server/recstreamer.c b/server/recstreamer.c index 8a5a7ad..bd01b96 100644 --- a/server/recstreamer.c +++ b/server/recstreamer.c @@ -12,14 +12,14 @@ using namespace Streamdev; // --- cStreamdevRecStreamer ------------------------------------------------- -cStreamdevRecStreamer::cStreamdevRecStreamer(cRecording *Rec, const cServerConnection *Connection, std::string pos): +cStreamdevRecStreamer::cStreamdevRecStreamer(RecPlayer *RecPlayer, const cServerConnection *Connection, int64_t StartOffset): cStreamdevStreamer("streamdev-recstreaming", Connection), - m_RecPlayer(Rec), + m_RecPlayer(RecPlayer), + m_StartOffset(StartOffset), m_From(0L) { Dprintf("New rec streamer\n"); - m_To = (int64_t) m_RecPlayer.getLengthBytes() - 1; - m_Pos = pos; + m_To = (int64_t) m_RecPlayer->getLengthBytes() - StartOffset - 1; } cStreamdevRecStreamer::~cStreamdevRecStreamer() @@ -30,7 +30,7 @@ cStreamdevRecStreamer::~cStreamdevRecStreamer() int64_t cStreamdevRecStreamer::SetRange(int64_t &From, int64_t &To) { - int64_t l = (int64_t) m_RecPlayer.getLengthBytes(); + int64_t l = (int64_t) GetLength(); if (From < 0L) { From += l; if (From < 0L) @@ -53,73 +53,10 @@ int64_t cStreamdevRecStreamer::SetRange(int64_t &From, int64_t &To) return m_To - m_From + 1; } -int32_t cStreamdevRecStreamer::getIFrameBeforeFrame(int32_t frame) -{ - uint32_t iframe, len; - uint64_t pos; - m_RecPlayer.getNextIFrame(frame + 1, 0, &pos, &iframe, &len); - Dprintf("pos: frame %i -> start at iFrame %i\n", frame, iframe); - return iframe; -} - -int64_t cStreamdevRecStreamer::GetFromByPos() -{ - if (m_Pos.empty()) return 0; - - std::string pos = m_Pos; - - // cut prefix (if any) - if (pos.find('_') != std::string::npos) { - pos = pos.substr(pos.find('_') + 1); - } - - // resume file - if (pos == "resume") { - int frame = getIFrameBeforeFrame(m_RecPlayer.frameFromResume()); - Dprintf("pos: frame from resume: %i\n", frame); - return m_RecPlayer.positionFromFrameNumber(frame); - } - - // mark - if (pos.find("mark.") == 0) { - int index = atoi(pos.substr(5).c_str()); - int frame = getIFrameBeforeFrame(m_RecPlayer.frameFromMark(index)); - Dprintf("pos: mark %i - frame %i\n", index, frame); - return m_RecPlayer.positionFromFrameNumber(frame); - } - - // time - if (pos.find("time.") == 0) { - int seconds = atoi(pos.substr(5).c_str()); - int frame = getIFrameBeforeFrame(m_RecPlayer.frameFromSeconds(seconds)); - Dprintf("pos: %i seconds - frame %i\n", seconds, frame); - return m_RecPlayer.positionFromFrameNumber(frame); - } - - // frame number - if (pos.find("frame.") == 0) { - int frame = getIFrameBeforeFrame(atoi(pos.substr(6).c_str())); - Dprintf("pos: frame %i\n", frame); - return m_RecPlayer.positionFromFrameNumber(frame); - } - - // default: byte index or percent - // as "%" is the url escape character, interpret <100 as percent - // if (pos.find("%") != std::string::npos) { - // int percent = atoi(pos.substr(0, pos.find("%")).c_str()); - int64_t number = atol(pos.c_str()); - if (number < 100) { - Dprintf("pos: %lld percent\n", (long long)number); - int64_t offset = m_RecPlayer.getLengthBytes() * number / 100; - return offset; - } - return number; -} - uchar* cStreamdevRecStreamer::GetFromReceiver(int &Count) { if (m_From <= m_To) { - Count = (int) m_RecPlayer.getBlock(m_Buffer, m_From, sizeof(m_Buffer)); + Count = (int) m_RecPlayer->getBlock(m_Buffer, m_StartOffset + m_From, sizeof(m_Buffer)); return m_Buffer; } return NULL; diff --git a/server/recstreamer.h b/server/recstreamer.h index 6c53a61..ee4b120 100644 --- a/server/recstreamer.h +++ b/server/recstreamer.h @@ -11,10 +11,10 @@ class cStreamdevRecStreamer: public cStreamdevStreamer { private: //Streamdev::cTSRemux *m_Remux; - RecPlayer m_RecPlayer; + RecPlayer *m_RecPlayer; + int64_t m_StartOffset; int64_t m_From; int64_t m_To; - std::string m_Pos; uchar m_Buffer[RECBUFSIZE]; protected: @@ -23,12 +23,10 @@ protected: public: virtual bool IsReceiving(void) const { return m_From <= m_To; }; - inline uint64_t GetLength() { return m_RecPlayer.getLengthBytes(); } + uint64_t GetLength() { return m_RecPlayer->getLengthBytes() - m_StartOffset; } int64_t SetRange(int64_t &From, int64_t &To); virtual cString ToText() const; - int64_t GetFromByPos(); - int32_t getIFrameBeforeFrame(int32_t frame); - cStreamdevRecStreamer(cRecording *Recording, const cServerConnection *Connection, std::string pos); + cStreamdevRecStreamer(RecPlayer *RecPlayer, const cServerConnection *Connection, int64_t StartOffset = 0L); virtual ~cStreamdevRecStreamer(); };