diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/connectionHTTP.c | 6 | ||||
-rw-r--r-- | server/connectionVTP.c | 840 | ||||
-rw-r--r-- | server/connectionVTP.h | 21 | ||||
-rw-r--r-- | server/livestreamer.c | 226 | ||||
-rw-r--r-- | server/livestreamer.h | 17 | ||||
-rw-r--r-- | server/menuHTTP.c | 4 | ||||
-rw-r--r-- | server/recplayer.c | 288 | ||||
-rw-r--r-- | server/recplayer.h | 63 | ||||
-rw-r--r-- | server/streamer.c | 22 | ||||
-rw-r--r-- | server/streamer.h | 43 |
10 files changed, 1259 insertions, 271 deletions
diff --git a/server/connectionHTTP.c b/server/connectionHTTP.c index fc10bfc..83e568d 100644 --- a/server/connectionHTTP.c +++ b/server/connectionHTTP.c @@ -1,5 +1,5 @@ /* - * $Id: connectionHTTP.c,v 1.16 2009/02/13 07:02:19 schmirl Exp $ + * $Id: connectionHTTP.c,v 1.17 2009/06/19 06:32:45 schmirl Exp $ */ #include <ctype.h> @@ -211,10 +211,8 @@ bool cConnectionHTTP::CmdGET(const std::string &Opts) const char* pType = type.c_str(); if (strcasecmp(pType, "PS") == 0) { m_StreamType = stPS; -#if APIVERSNUM < 10703 } else if (strcasecmp(pType, "PES") == 0) { m_StreamType = stPES; -#endif } else if (strcasecmp(pType, "TS") == 0) { m_StreamType = stTS; } else if (strcasecmp(pType, "ES") == 0) { @@ -266,9 +264,7 @@ bool cConnectionHTTP::CmdGET(const std::string &Opts) { case stTS: base += "TS/"; break; case stPS: base += "PS/"; break; -#if APIVERSNUM < 10703 case stPES: base += "PES/"; break; -#endif case stES: base += "ES/"; break; case stExtern: base += "Extern/"; break; default: break; diff --git a/server/connectionVTP.c b/server/connectionVTP.c index e0edb6e..579cff2 100644 --- a/server/connectionVTP.c +++ b/server/connectionVTP.c @@ -1,5 +1,5 @@ /* - * $Id: connectionVTP.c,v 1.19 2009/01/16 11:35:44 schmirl Exp $ + * $Id: connectionVTP.c,v 1.21 2009/07/01 10:46:16 schmirl Exp $ */ #include "server/connectionVTP.h" @@ -8,6 +8,8 @@ #include "setup.h" #include <vdr/tools.h> +#include <vdr/videodir.h> +#include <vdr/menu.h> #include <tools/select.h> #include <string.h> #include <ctype.h> @@ -28,13 +30,20 @@ 563: Recording not available (currently?) */ +enum eDumpModeStreamdev { dmsdAll, dmsdPresent, dmsdFollowing, dmsdAtTime, dmsdFromToTime }; + // --- cLSTEHandler ----------------------------------------------------------- class cLSTEHandler { private: +#ifdef USE_PARENTALRATING + enum eStates { Channel, Event, Title, Subtitle, Description, Vps, Content, + EndEvent, EndChannel, EndEPG }; +#else enum eStates { Channel, Event, Title, Subtitle, Description, Vps, EndEvent, EndChannel, EndEPG }; +#endif /* PARENTALRATING */ cConnectionVTP *m_Client; cSchedulesLock *m_SchedulesLock; const cSchedules *m_Schedules; @@ -44,6 +53,7 @@ private: char *m_Error; eStates m_State; bool m_Traverse; + time_t m_ToTime; public: cLSTEHandler(cConnectionVTP *Client, const char *Option); ~cLSTEHandler(); @@ -59,10 +69,12 @@ cLSTEHandler::cLSTEHandler(cConnectionVTP *Client, const char *Option): m_Errno(0), m_Error(NULL), m_State(Channel), - m_Traverse(false) + m_Traverse(false), + m_ToTime(0) { - eDumpMode dumpmode = dmAll; + eDumpModeStreamdev dumpmode = dmsdAll; time_t attime = 0; + time_t fromtime = 0; if (m_Schedules != NULL && *Option) { char buf[strlen(Option) + 1]; @@ -70,13 +82,13 @@ cLSTEHandler::cLSTEHandler(cConnectionVTP *Client, const char *Option): const char *delim = " \t"; char *strtok_next; char *p = strtok_r(buf, delim, &strtok_next); - while (p && dumpmode == dmAll) { + while (p && dumpmode == dmsdAll) { if (strcasecmp(p, "NOW") == 0) - dumpmode = dmPresent; + dumpmode = dmsdPresent; else if (strcasecmp(p, "NEXT") == 0) - dumpmode = dmFollowing; + dumpmode = dmsdFollowing; else if (strcasecmp(p, "AT") == 0) { - dumpmode = dmAtTime; + dumpmode = dmsdAtTime; if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) { if (isnumber(p)) attime = strtol(p, NULL, 10); @@ -90,6 +102,39 @@ cLSTEHandler::cLSTEHandler(cConnectionVTP *Client, const char *Option): m_Error = strdup("Missing time"); break; } + } + else if (strcasecmp(p, "FROM") == 0) { + dumpmode = dmsdFromToTime; + if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) { + if (isnumber(p)) + fromtime = strtol(p, NULL, 10); + else { + m_Errno = 501; + m_Error = strdup("Invalid time"); + break; + } + if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) { + if (strcasecmp(p, "TO") == 0) { + if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) { + if (isnumber(p)) + m_ToTime = strtol(p, NULL, 10); + else { + m_Errno = 501; + m_Error = strdup("Invalid time"); + break; + } + } else { + m_Errno = 501; + m_Error = strdup("Missing time"); + break; + } + } + } + } else { + m_Errno = 501; + m_Error = strdup("Missing time"); + break; + } } else if (!m_Schedule) { cChannel* Channel = NULL; if (isnumber(p)) @@ -129,16 +174,29 @@ cLSTEHandler::cLSTEHandler(cConnectionVTP *Client, const char *Option): if (m_Schedule != NULL && m_Schedule->Events() != NULL) { switch (dumpmode) { - case dmAll: m_Event = m_Schedule->Events()->First(); - m_Traverse = true; - break; - case dmPresent: m_Event = m_Schedule->GetPresentEvent(); - break; - case dmFollowing: m_Event = m_Schedule->GetFollowingEvent(); - break; - case dmAtTime: m_Event = m_Schedule->GetEventAround(attime); - break; - + case dmsdAll: m_Event = m_Schedule->Events()->First(); + m_Traverse = true; + break; + case dmsdPresent: m_Event = m_Schedule->GetPresentEvent(); + break; + case dmsdFollowing: m_Event = m_Schedule->GetFollowingEvent(); + break; + case dmsdAtTime: m_Event = m_Schedule->GetEventAround(attime); + break; + case dmsdFromToTime: + if (m_Schedule->Events()->Count() <= 1) { + m_Event = m_Schedule->Events()->First(); + break; + } + if (fromtime < m_Schedule->Events()->First()->StartTime()) { + fromtime = m_Schedule->Events()->First()->StartTime(); + } + if (m_ToTime > m_Schedule->Events()->Last()->EndTime()) { + m_ToTime = m_Schedule->Events()->Last()->EndTime(); + } + m_Event = m_Schedule->GetEventAround(fromtime); + m_Traverse = true; + break; } } } @@ -227,7 +285,11 @@ bool cLSTEHandler::Next(bool &Last) break; case Vps: +#ifdef USE_PARENTALRATING + m_State = Content; +#else m_State = EndEvent; +#endif /* PARENTALRATING */ if (m_Event->Vps()) #ifdef __FreeBSD__ return m_Client->Respond(-215, "V %d", m_Event->Vps()); @@ -238,9 +300,26 @@ bool cLSTEHandler::Next(bool &Last) return Next(Last); break; +#ifdef USE_PARENTALRATING + case Content: + m_State = EndEvent; + if (!isempty(m_Event->GetContentsString())) { + char *copy = strdup(m_Event->GetContentsString()); + cString cpy(copy, true); + strreplace(copy, '\n', '|'); + return m_Client->Respond(-215, "G %i %i %s", m_Event->Contents() & 0xF0, m_Event->Contents() & 0x0F, copy); + } else + return Next(Last); + break; +#endif + case EndEvent: - if (m_Traverse) + if (m_Traverse) { m_Event = m_Schedule->Events()->Next(m_Event); + if ((m_Event != NULL) && (m_ToTime != 0) && (m_Event->StartTime() > m_ToTime)) { + m_Event = NULL; + } + } else m_Event = NULL; @@ -377,7 +456,7 @@ bool cLSTCHandler::Next(bool &Last) } } - if (i < Channels.MaxNumber()) + if (i < Channels.MaxNumber() + 1) Last = false; } @@ -468,6 +547,181 @@ bool cLSTTHandler::Next(bool &Last) return result; } +// --- cLSTRHandler ----------------------------------------------------------- + +class cLSTRHandler +{ +private: + enum eStates { Recording, Event, Title, Subtitle, Description, Components, Vps, + EndRecording }; + cConnectionVTP *m_Client; + cRecording *m_Recording; + const cEvent *m_Event; + int m_Index; + int m_Errno; + char *m_Error; + bool m_Traverse; + bool m_Info; + eStates m_State; + int m_CurrentComponent; +public: + cLSTRHandler(cConnectionVTP *Client, const char *Option); + ~cLSTRHandler(); + bool Next(bool &Last); +}; + +cLSTRHandler::cLSTRHandler(cConnectionVTP *Client, const char *Option): + m_Client(Client), + m_Recording(NULL), + m_Event(NULL), + m_Index(0), + m_Errno(0), + m_Error(NULL), + m_Traverse(false), + m_Info(false), + m_State(Recording), + m_CurrentComponent(0) +{ + if (*Option) { + if (isnumber(Option)) { + m_Recording = Recordings.Get(strtol(Option, NULL, 10) - 1); +#if defined(USE_STREAMDEVEXT) || APIVERSNUM >= 10705 + m_Event = m_Recording->Info()->GetEvent(); +#endif + m_Info = true; + if (m_Recording == NULL) { + m_Errno = 501; + asprintf(&m_Error, "Recording \"%s\" not found", Option); + } + } + else { + m_Errno = 501; + asprintf(&m_Error, "Error in Recording number \"%s\"", Option); + } + } + else if (Recordings.Count()) { + m_Traverse = true; + m_Index = 0; + m_Recording = Recordings.Get(m_Index); + if (m_Recording == NULL) { + m_Errno = 501; + asprintf(&m_Error, "Recording \"%d\" not found", m_Index + 1); + } + } + else { + m_Errno = 550; + m_Error = strdup("No recordings available"); + } +} + +cLSTRHandler::~cLSTRHandler() +{ + if (m_Error != NULL) + free(m_Error); +} + +bool cLSTRHandler::Next(bool &Last) +{ + if (m_Error != NULL) { + Last = true; + cString str(m_Error, true); + m_Error = NULL; + return m_Client->Respond(m_Errno, *str); + } + + if (m_Info) { + Last = false; + switch (m_State) { + case Recording: + if (m_Recording != NULL) { + m_State = Event; + return m_Client->Respond(-215, "C %s%s%s", + *m_Recording->Info()->ChannelID().ToString(), + m_Recording->Info()->ChannelName() ? " " : "", + m_Recording->Info()->ChannelName() ? m_Recording->Info()->ChannelName() : ""); + } + else { + m_State = EndRecording; + return Next(Last); + } + break; + + case Event: + m_State = Title; + if (m_Event != NULL) { + return m_Client->Respond(-215, "E %u %ld %d %X %X", (unsigned int) m_Event->EventID(), + m_Event->StartTime(), m_Event->Duration(), + m_Event->TableID(), m_Event->Version()); + } + return Next(Last); + + case Title: + m_State = Subtitle; + return m_Client->Respond(-215, "T %s", m_Recording->Info()->Title()); + + case Subtitle: + m_State = Description; + if (!isempty(m_Recording->Info()->ShortText())) { + return m_Client->Respond(-215, "S %s", m_Recording->Info()->ShortText()); + } + return Next(Last); + + case Description: + m_State = Components; + if (!isempty(m_Recording->Info()->Description())) { + m_State = Components; + char *copy = strdup(m_Recording->Info()->Description()); + cString cpy(copy, true); + strreplace(copy, '\n', '|'); + return m_Client->Respond(-215, "D %s", copy); + } + return Next(Last); + + case Components: + if (m_Recording->Info()->Components()) { + if (m_CurrentComponent < m_Recording->Info()->Components()->NumComponents()) { + tComponent *p = m_Recording->Info()->Components()->Component(m_CurrentComponent); + m_CurrentComponent++; + if (!Setup.UseDolbyDigital && p->stream == 0x02 && p->type == 0x05) + return Next(Last); + + return m_Client->Respond(-215, "X %s", *p->ToString()); + } + } + m_State = Vps; + return Next(Last); + + case Vps: + m_State = EndRecording; + if (m_Event != NULL) { + if (m_Event->Vps()) { + return m_Client->Respond(-215, "V %ld", m_Event->Vps()); + } + } + return Next(Last); + + case EndRecording: + Last = true; + return m_Client->Respond(215, "End of recording information"); + } + } + else { + bool result; + Last = !m_Traverse || m_Index >= Recordings.Count() - 1; + result = m_Client->Respond(Last ? 250 : -250, "%d %s", m_Recording->Index() + 1, m_Recording->Title(' ', true)); + + if (m_Traverse && !Last) { + m_Recording = Recordings.Get(++m_Index); + if (m_Recording == NULL) { + m_Errno = 501; + asprintf(&m_Error, "Recording \"%d\" not found", m_Index + 1); + } + } + return result; + } + return false; +} + // --- cConnectionVTP --------------------------------------------------------- cConnectionVTP::cConnectionVTP(void): @@ -476,12 +730,16 @@ cConnectionVTP::cConnectionVTP(void): m_LiveStreamer(NULL), m_FilterSocket(NULL), m_FilterStreamer(NULL), + m_RecSocket(NULL), + m_DataSocket(NULL), m_LastCommand(NULL), m_StreamType(stTSPIDS), m_FiltersSupport(false), + m_RecPlayer(NULL), m_LSTEHandler(NULL), m_LSTCHandler(NULL), - m_LSTTHandler(NULL) + m_LSTTHandler(NULL), + m_LSTRHandler(NULL) { } @@ -491,11 +749,15 @@ cConnectionVTP::~cConnectionVTP() free(m_LastCommand); delete m_LiveStreamer; delete m_LiveSocket; + delete m_RecSocket; delete m_FilterStreamer; delete m_FilterSocket; + delete m_DataSocket; delete m_LSTTHandler; delete m_LSTCHandler; delete m_LSTEHandler; + delete m_LSTRHandler; + delete m_RecPlayer; } inline bool cConnectionVTP::Abort(void) const @@ -548,7 +810,7 @@ bool cConnectionVTP::Command(char *Cmd) } if (strcasecmp(Cmd, "LSTE") == 0) return CmdLSTE(param); - //else if (strcasecmp(Cmd, "LSTR") == 0) return CmdLSTR(param); + else if (strcasecmp(Cmd, "LSTR") == 0) return CmdLSTR(param); else if (strcasecmp(Cmd, "LSTT") == 0) return CmdLSTT(param); else if (strcasecmp(Cmd, "LSTC") == 0) return CmdLSTC(param); @@ -561,7 +823,9 @@ bool cConnectionVTP::Command(char *Cmd) if (strcasecmp(Cmd, "CAPS") == 0) return CmdCAPS(param); else if (strcasecmp(Cmd, "PROV") == 0) return CmdPROV(param); else if (strcasecmp(Cmd, "PORT") == 0) return CmdPORT(param); + else if (strcasecmp(Cmd, "READ") == 0) return CmdREAD(param); else if (strcasecmp(Cmd, "TUNE") == 0) return CmdTUNE(param); + else if (strcasecmp(Cmd, "PLAY") == 0) return CmdPLAY(param); else if (strcasecmp(Cmd, "ADDP") == 0) return CmdADDP(param); else if (strcasecmp(Cmd, "DELP") == 0) return CmdDELP(param); else if (strcasecmp(Cmd, "ADDF") == 0) return CmdADDF(param); @@ -570,10 +834,17 @@ bool cConnectionVTP::Command(char *Cmd) else if (strcasecmp(Cmd, "QUIT") == 0) return CmdQUIT(); else if (strcasecmp(Cmd, "SUSP") == 0) return CmdSUSP(); // Commands adopted from SVDRP - //else if (strcasecmp(Cmd, "DELR") == 0) return CmdDELR(param); + else if (strcasecmp(Cmd, "STAT") == 0) return CmdSTAT(param); else if (strcasecmp(Cmd, "MODT") == 0) return CmdMODT(param); else if (strcasecmp(Cmd, "NEWT") == 0) return CmdNEWT(param); else if (strcasecmp(Cmd, "DELT") == 0) return CmdDELT(param); + else if (strcasecmp(Cmd, "NEXT") == 0) return CmdNEXT(param); + else if (strcasecmp(Cmd, "NEWC") == 0) return CmdNEWC(param); + else if (strcasecmp(Cmd, "MODC") == 0) return CmdMODC(param); + else if (strcasecmp(Cmd, "MOVC") == 0) return CmdMOVC(param); + else if (strcasecmp(Cmd, "DELC") == 0) return CmdDELC(param); + else if (strcasecmp(Cmd, "DELR") == 0) return CmdDELR(param); + else if (strcasecmp(Cmd, "RENR") == 0) return CmdRENR(param); else return Respond(500, "Unknown Command \"%s\"", Cmd); } @@ -595,12 +866,10 @@ bool cConnectionVTP::CmdCAPS(char *Opts) return Respond(220, "Capability \"%s\" accepted", Opts); } -#if APIVERSNUM < 10703 if (strcasecmp(Opts, "PES") == 0) { m_StreamType = stPES; return Respond(220, "Capability \"%s\" accepted", Opts); } -#endif if (strcasecmp(Opts, "EXTERN") == 0) { m_StreamType = stExtern; @@ -648,7 +917,7 @@ bool cConnectionVTP::CmdPORT(char *Opts) if (ep == Opts || !isspace(*ep)) return Respond(500, "Use: PORT Id Destination"); - if (id != siLive && id != siLiveFilter) + if (id >= si_Count) return Respond(501, "Wrong connection id %d", id); Opts = skipspace(ep); @@ -676,7 +945,8 @@ bool cConnectionVTP::CmdPORT(char *Opts) isyslog("Streamdev: Setting data connection to %s:%d", dataip, dataport); - if (id == siLiveFilter) { + switch (id) { + case siLiveFilter: m_FiltersSupport = true; if(m_FilterStreamer) m_FilterStreamer->Stop(); @@ -696,26 +966,91 @@ bool cConnectionVTP::CmdPORT(char *Opts) m_FilterStreamer->Activate(true); return Respond(220, "Port command ok, data connection opened"); - } + break; - if(m_LiveSocket && m_LiveStreamer) - m_LiveStreamer->Stop(); - delete m_LiveSocket; + case siLive: + if(m_LiveSocket && m_LiveStreamer) + m_LiveStreamer->Stop(); + delete m_LiveSocket; - m_LiveSocket = new cTBSocket(SOCK_STREAM); - if (!m_LiveSocket->Connect(dataip, dataport)) { - esyslog("ERROR: Streamdev: Couldn't open data connection to %s:%d: %s", - dataip, dataport, strerror(errno)); - DELETENULL(m_LiveSocket); - return Respond(551, "Couldn't open data connection"); - } + m_LiveSocket = new cTBSocket(SOCK_STREAM); + if (!m_LiveSocket->Connect(dataip, dataport)) { + esyslog("ERROR: Streamdev: Couldn't open data connection to %s:%d: %s", + dataip, dataport, strerror(errno)); + DELETENULL(m_LiveSocket); + return Respond(551, "Couldn't open data connection"); + } - if (!m_LiveSocket->SetDSCP()) - LOG_ERROR_STR("unable to set DSCP sockopt"); - if (m_LiveStreamer) - m_LiveStreamer->Start(m_LiveSocket); + if (!m_LiveSocket->SetDSCP()) + LOG_ERROR_STR("unable to set DSCP sockopt"); + if (m_LiveStreamer) + m_LiveStreamer->Start(m_LiveSocket); - return Respond(220, "Port command ok, data connection opened"); + return Respond(220, "Port command ok, data connection opened"); + break; + + case siReplay: + delete m_RecSocket; + + m_RecSocket = new cTBSocket(SOCK_STREAM); + if (!m_RecSocket->Connect(dataip, dataport)) { + esyslog("ERROR: Streamdev: Couldn't open data connection to %s:%d: %s", + dataip, dataport, strerror(errno)); + DELETENULL(m_RecSocket); + return Respond(551, "Couldn't open data connection"); + } + + if (!m_RecSocket->SetDSCP()) + LOG_ERROR_STR("unable to set DSCP sockopt"); + + return Respond(220, "Port command ok, data connection opened"); + break; + + case siDataRespond: + delete m_DataSocket; + + m_DataSocket = new cTBSocket(SOCK_STREAM); + if (!m_DataSocket->Connect(dataip, dataport)) { + esyslog("ERROR: Streamdev: Couldn't open data connection to %s:%d: %s", + dataip, dataport, strerror(errno)); + DELETENULL(m_DataSocket); + return Respond(551, "Couldn't open data connection"); + } + return Respond(220, "Port command ok, data connection opened"); + break; + + default: + return Respond(501, "No handler for id %u", id); + } +} + +bool cConnectionVTP::CmdREAD(char *Opts) +{ + if (*Opts) { + char *tail; + uint64_t position = strtoll(Opts, &tail, 10); + if (tail && tail != Opts) { + tail = skipspace(tail); + if (tail && tail != Opts) { + int size = strtol(tail, NULL, 10); + uint8_t* data = (uint8_t*)malloc(size+4); + unsigned long count_readed = m_RecPlayer->getBlock(data, position, size); + unsigned long count_written = m_RecSocket->SysWrite(data, count_readed); + + free(data); + return Respond(220, "%lu Bytes submitted", count_written); + } + else { + return Respond(501, "Missing position"); + } + } + else { + return Respond(501, "Missing size"); + } + } + else { + return Respond(501, "Missing position"); + } } bool cConnectionVTP::CmdTUNE(char *Opts) @@ -749,6 +1084,32 @@ bool cConnectionVTP::CmdTUNE(char *Opts) return Respond(220, "Channel tuned"); } +bool cConnectionVTP::CmdPLAY(char *Opts) +{ + Recordings.Update(true); + if (*Opts) { + if (isnumber(Opts)) { + cRecording *recording = Recordings.Get(strtol(Opts, NULL, 10) - 1); + if (recording) { + if (m_RecPlayer) { + delete m_RecPlayer; + } + m_RecPlayer = new RecPlayer(recording); + return Respond(220, "%llu (Bytes), %u (Frames)", (long long unsigned int) m_RecPlayer->getLengthBytes(), (unsigned int) m_RecPlayer->getLengthFrames()); + } + else { + return Respond(550, "Recording \"%s\" not found", Opts); + } + } + else { + return Respond(500, "Use: PLAY record"); + } + } + else { + return Respond(500, "Use: PLAY record"); + } +} + bool cConnectionVTP::CmdADDP(char *Opts) { int pid; @@ -844,6 +1205,13 @@ bool cConnectionVTP::CmdABRT(char *Opts) DELETENULL(m_FilterStreamer); DELETENULL(m_FilterSocket); break; + case siReplay: + DELETENULL(m_RecPlayer); + DELETENULL(m_RecSocket); + break; + case siDataRespond: + DELETENULL(m_DataSocket); + break; default: return Respond(501, "Wrong connection id %d", id); break; @@ -881,7 +1249,8 @@ bool cConnectionVTP::CmdLSTX(cHandler *&Handler, char *Option) Handler = new cHandler(this, Option); } - bool last, result = false; + bool last = false; + bool result = false; if (Handler != NULL) result = Handler->Next(last); else @@ -907,11 +1276,66 @@ bool cConnectionVTP::CmdLSTT(char *Option) return CmdLSTX(m_LSTTHandler, Option); } +bool cConnectionVTP::CmdLSTR(char *Option) +{ + return CmdLSTX(m_LSTRHandler, Option); +} + // Functions adopted from SVDRP #define INIT_WRAPPER() bool _res #define Reply(c,m...) _res = Respond(c,m) #define EXIT_WRAPPER() return _res +bool cConnectionVTP::CmdSTAT(const char *Option) +{ + INIT_WRAPPER(); + if (*Option) { + if (strcasecmp(Option, "DISK") == 0) { + int FreeMB, UsedMB; + int Percent = VideoDiskSpace(&FreeMB, &UsedMB); + Reply(250, "%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent); + } + else if (strcasecmp(Option, "NAME") == 0) { + Reply(250, "vdr - The Video Disk Recorder with Streamdev-Server"); + } + else if (strcasecmp(Option, "VERSION") == 0) { + Reply(250, "VDR: %s | Streamdev: %s", VDRVERSION, VERSION); + } + else if (strcasecmp(Option, "RECORDS") == 0) { + bool recordings = Recordings.Load(); + Recordings.Sort(); + if (recordings) { + cRecording *recording = Recordings.Last(); + Reply(250, "%d", recording->Index() + 1); + } + else { + Reply(250, "0"); + } + } + else if (strcasecmp(Option, "CHANNELS") == 0) { + Reply(250, "%d", Channels.MaxNumber()); + } + else if (strcasecmp(Option, "TIMERS") == 0) { + Reply(250, "%d", Timers.Count()); + } + else if (strcasecmp(Option, "CHARSET") == 0) { + Reply(250, "%s", cCharSetConv::SystemCharacterTable()); + } + else if (strcasecmp(Option, "TIME") == 0) { + time_t timeNow = time(NULL); + struct tm* timeStruct = localtime(&timeNow); + int timeOffset = timeStruct->tm_gmtoff; + + Reply(250, "%lu %i", (unsigned long) timeNow, timeOffset); + } + else + Reply(501, "Invalid Option \"%s\"", Option); + } + else + Reply(501, "No option given"); + EXIT_WRAPPER(); +} + bool cConnectionVTP::CmdMODT(const char *Option) { INIT_WRAPPER(); @@ -992,61 +1416,293 @@ bool cConnectionVTP::CmdDELT(const char *Option) EXIT_WRAPPER(); } -/*bool cConnectionVTP::CmdLSTR(char *Option) { +bool cConnectionVTP::CmdNEXT(const char *Option) +{ INIT_WRAPPER(); - bool recordings = Recordings.Load(); - Recordings.Sort(); - if (*Option) { - if (isnumber(Option)) { - cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1); - if (recording) { - if (recording->Summary()) { - char *summary = strdup(recording->Summary()); - Reply(250, "%s", strreplace(summary,'\n','|')); - free(summary); - } - else - Reply(550, "No summary availabe"); - } - else - Reply(550, "Recording \"%s\" not found", Option); - } - else - Reply(501, "Error in recording number \"%s\"", Option); - } - else if (recordings) { - cRecording *recording = Recordings.First(); - while (recording) { - Reply(recording == Recordings.Last() ? 250 : -250, "%d %s", recording->Index() + 1, recording->Title(' ', true)); - recording = Recordings.Next(recording); - } - } - else - Reply(550, "No recordings available"); + cTimer *t = Timers.GetNextActiveTimer(); + if (t) { + time_t Start = t->StartTime(); + int Number = t->Index() + 1; + if (!*Option) + Reply(250, "%d %s", Number, *TimeToString(Start)); + else if (strcasecmp(Option, "ABS") == 0) + Reply(250, "%d %ld", Number, Start); + else if (strcasecmp(Option, "REL") == 0) + Reply(250, "%d %ld", Number, Start - time(NULL)); + else + Reply(501, "Unknown option: \"%s\"", Option); + } + else + Reply(550, "No active timers"); EXIT_WRAPPER(); } -bool cConnectionVTP::CmdDELR(char *Option) { +bool cConnectionVTP::CmdNEWC(const char *Option) +{ INIT_WRAPPER(); - if (*Option) { - if (isnumber(Option)) { - cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1); - if (recording) { - if (recording->Delete()) - Reply(250, "Recording \"%s\" deleted", Option); - else - Reply(554, "Error while deleting recording!"); - } - else - Reply(550, "Recording \"%s\" not found%s", Option, Recordings.Count() ? "" : " (use LSTR before deleting)"); - } - else - Reply(501, "Error in recording number \"%s\"", Option); - } - else - Reply(501, "Missing recording number"); + if (*Option) { + cChannel ch; + if (ch.Parse(Option)) { + if (Channels.HasUniqueChannelID(&ch)) { + cChannel *channel = new cChannel; + *channel = ch; + Channels.Add(channel); + Channels.ReNumber(); + Channels.SetModified(true); + isyslog("new channel %d %s", channel->Number(), *channel->ToText()); + Reply(250, "%d %s", channel->Number(), *channel->ToText()); + } + else { + Reply(501, "Channel settings are not unique"); + } + } + else { + Reply(501, "Error in channel settings"); + } + } + else { + Reply(501, "Missing channel settings"); + } EXIT_WRAPPER(); -}*/ +} + +bool cConnectionVTP::CmdMODC(const char *Option) +{ + INIT_WRAPPER(); + if (*Option) { + char *tail; + int n = strtol(Option, &tail, 10); + if (tail && tail != Option) { + tail = skipspace(tail); + if (!Channels.BeingEdited()) { + cChannel *channel = Channels.GetByNumber(n); + if (channel) { + cChannel ch; + if (ch.Parse(tail)) { + if (Channels.HasUniqueChannelID(&ch, channel)) { + *channel = ch; + Channels.ReNumber(); + Channels.SetModified(true); + isyslog("modifed channel %d %s", channel->Number(), *channel->ToText()); + Reply(250, "%d %s", channel->Number(), *channel->ToText()); + } + else { + Reply(501, "Channel settings are not unique"); + } + } + else { + Reply(501, "Error in channel settings"); + } + } + else { + Reply(501, "Channel \"%d\" not defined", n); + } + } + else { + Reply(550, "Channels are being edited - try again later"); + } + } + else { + Reply(501, "Error in channel number"); + } + } + else { + Reply(501, "Missing channel settings"); + } + EXIT_WRAPPER(); +} + +bool cConnectionVTP::CmdMOVC(const char *Option) +{ + INIT_WRAPPER(); + if (*Option) { + if (!Channels.BeingEdited() && !Timers.BeingEdited()) { + char *tail; + int From = strtol(Option, &tail, 10); + if (tail && tail != Option) { + tail = skipspace(tail); + if (tail && tail != Option) { + int To = strtol(tail, NULL, 10); + int CurrentChannelNr = cDevice::CurrentChannel(); + cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr); + cChannel *FromChannel = Channels.GetByNumber(From); + if (FromChannel) { + cChannel *ToChannel = Channels.GetByNumber(To); + if (ToChannel) { + int FromNumber = FromChannel->Number(); + int ToNumber = ToChannel->Number(); + if (FromNumber != ToNumber) { + Channels.Move(FromChannel, ToChannel); + Channels.ReNumber(); + Channels.SetModified(true); + if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) { + if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) { + Channels.SwitchTo(CurrentChannel->Number()); + } + else { + cDevice::SetCurrentChannel(CurrentChannel); + } + } + isyslog("channel %d moved to %d", FromNumber, ToNumber); + Reply(250,"Channel \"%d\" moved to \"%d\"", From, To); + } + else { + Reply(501, "Can't move channel to same postion"); + } + } + else { + Reply(501, "Channel \"%d\" not defined", To); + } + } + else { + Reply(501, "Channel \"%d\" not defined", From); + } + } + else { + Reply(501, "Error in channel number"); + } + } + else { + Reply(501, "Error in channel number"); + } + } + else { + Reply(550, "Channels or timers are being edited - try again later"); + } + } + else { + Reply(501, "Missing channel number"); + } + EXIT_WRAPPER(); +} + +bool cConnectionVTP::CmdDELC(const char *Option) +{ + INIT_WRAPPER(); + if (*Option) { + if (isnumber(Option)) { + if (!Channels.BeingEdited()) { + cChannel *channel = Channels.GetByNumber(strtol(Option, NULL, 10)); + if (channel) { + for (cTimer *timer = Timers.First(); timer; timer = Timers.Next(timer)) { + if (timer->Channel() == channel) { + Reply(550, "Channel \"%s\" is in use by timer %d", Option, timer->Index() + 1); + return false; + } + } + int CurrentChannelNr = cDevice::CurrentChannel(); + cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr); + if (CurrentChannel && channel == CurrentChannel) { + int n = Channels.GetNextNormal(CurrentChannel->Index()); + if (n < 0) + n = Channels.GetPrevNormal(CurrentChannel->Index()); + CurrentChannel = Channels.Get(n); + CurrentChannelNr = 0; // triggers channel switch below + } + Channels.Del(channel); + Channels.ReNumber(); + Channels.SetModified(true); + isyslog("channel %s deleted", Option); + if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) { + if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) + Channels.SwitchTo(CurrentChannel->Number()); + else + cDevice::SetCurrentChannel(CurrentChannel); + } + Reply(250, "Channel \"%s\" deleted", Option); + } + else + Reply(501, "Channel \"%s\" not defined", Option); + } + else + Reply(550, "Channels are being edited - try again later"); + } + else + Reply(501, "Error in channel number \"%s\"", Option); + } + else { + Reply(501, "Missing channel number"); + } + EXIT_WRAPPER(); +} + +bool cConnectionVTP::CmdDELR(const char *Option) +{ + INIT_WRAPPER(); + if (*Option) { + if (isnumber(Option)) { + cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1); + if (recording) { + cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName()); + if (!rc) { + if (recording->Delete()) { + Reply(250, "Recording \"%s\" deleted", Option); + ::Recordings.DelByName(recording->FileName()); + } + else + Reply(554, "Error while deleting recording!"); + } + else + Reply(550, "Recording \"%s\" is in use by timer %d", Option, rc->Timer()->Index() + 1); + } + else + Reply(550, "Recording \"%s\" not found%s", Option, Recordings.Count() ? "" : " (use LSTR before deleting)"); + } + else + Reply(501, "Error in recording number \"%s\"", Option); + } + else + Reply(501, "Missing recording number"); + EXIT_WRAPPER(); +} + +bool cConnectionVTP::CmdRENR(const char *Option) +{ + INIT_WRAPPER(); +#if defined(LIEMIKUUTIO) + bool recordings = Recordings.Update(true); + if (recordings) { + if (*Option) { + char *tail; + int n = strtol(Option, &tail, 10); + cRecording *recording = Recordings.Get(n - 1); + if (recording && tail && tail != Option) { +#if APIVERSNUM < 10704 + int priority = recording->priority; + int lifetime = recording->lifetime; +#endif + char *oldName = strdup(recording->Name()); + tail = skipspace(tail); +#if APIVERSNUM < 10704 + if (recording->Rename(tail, &priority, &lifetime)) { +#else + if (recording->Rename(tail)) { +#endif + Reply(250, "Renamed \"%s\" to \"%s\"", oldName, recording->Name()); + Recordings.ChangeState(); + Recordings.TouchUpdate(); + } + else { + Reply(501, "Renaming \"%s\" to \"%s\" failed", oldName, tail); + } + free(oldName); + } + else { + Reply(501, "Recording not found or wrong syntax"); + } + } + else { + Reply(501, "Missing Input settings"); + } + } + else { + Reply(550, "No recordings available"); + } +#else + Reply(501, "Rename not supported, please use LIEMIEXT"); +#endif /* LIEMIKUUTIO */ + EXIT_WRAPPER(); +} bool cConnectionVTP::Respond(int Code, const char *Message, ...) { diff --git a/server/connectionVTP.h b/server/connectionVTP.h index 452f3ae..b938fe6 100644 --- a/server/connectionVTP.h +++ b/server/connectionVTP.h @@ -2,6 +2,7 @@ #define VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H #include "server/connection.h" +#include "server/recplayer.h" class cTBSocket; class cStreamdevLiveStreamer; @@ -9,6 +10,7 @@ class cStreamdevFilterStreamer; class cLSTEHandler; class cLSTCHandler; class cLSTTHandler; +class cLSTRHandler; class cConnectionVTP: public cServerConnection { friend class cLSTEHandler; @@ -21,16 +23,19 @@ private: cStreamdevLiveStreamer *m_LiveStreamer; cTBSocket *m_FilterSocket; cStreamdevFilterStreamer *m_FilterStreamer; + cTBSocket *m_RecSocket; + cTBSocket *m_DataSocket; char *m_LastCommand; eStreamType m_StreamType; bool m_FiltersSupport; + RecPlayer *m_RecPlayer; // Members adopted for SVDRP - cRecordings Recordings; cLSTEHandler *m_LSTEHandler; cLSTCHandler *m_LSTCHandler; cLSTTHandler *m_LSTTHandler; + cLSTRHandler *m_LSTRHandler; protected: template<class cHandler> @@ -51,7 +56,9 @@ public: bool CmdCAPS(char *Opts); bool CmdPROV(char *Opts); bool CmdPORT(char *Opts); + bool CmdREAD(char *Opts); bool CmdTUNE(char *Opts); + bool CmdPLAY(char *Opts); bool CmdADDP(char *Opts); bool CmdDELP(char *Opts); bool CmdADDF(char *Opts); @@ -64,14 +71,20 @@ public: bool CmdLSTE(char *Opts); bool CmdLSTC(char *Opts); bool CmdLSTT(char *Opts); + bool CmdLSTR(char *Opts); // Commands adopted from SVDRP + bool CmdSTAT(const char *Option); bool CmdMODT(const char *Option); bool CmdNEWT(const char *Option); bool CmdDELT(const char *Option); - - //bool CmdLSTR(char *Opts); - //bool CmdDELR(char *Opts); + bool CmdNEXT(const char *Option); + bool CmdNEWC(const char *Option); + bool CmdMODC(const char *Option); + bool CmdMOVC(const char *Option); + bool CmdDELC(const char *Option); + bool CmdDELR(const char *Option); + bool CmdRENR(const char *Option); bool Respond(int Code, const char *Message, ...) __attribute__ ((format (printf, 3, 4))); diff --git a/server/livestreamer.c b/server/livestreamer.c index 97dffd7..71a3565 100644 --- a/server/livestreamer.c +++ b/server/livestreamer.c @@ -4,6 +4,7 @@ #include <libsi/descriptor.h> #include "remux/ts2ps.h" +#include "remux/ts2pes.h" #include "remux/ts2es.h" #include "remux/extern.h" @@ -13,7 +14,7 @@ #include "server/livefilter.h" #include "common.h" -#define TSPATREPACKER +using namespace Streamdev; // --- cStreamdevLiveReceiver ------------------------------------------------- @@ -64,6 +65,8 @@ private: int pmtPid; int pmtSid; int pmtVersion; + uchar tspat_buf[TS_SIZE]; + cStreamdevBuffer siBuffer; const cChannel *m_Channel; cStreamdevLiveStreamer *m_Streamer; @@ -73,9 +76,11 @@ private: int GetPid(SI::PMT::Stream& stream); public: cStreamdevPatFilter(cStreamdevLiveStreamer *Streamer, const cChannel *Channel); + uchar* Get(int &Count) { return siBuffer.Get(Count); } + void Del(int Count) { return siBuffer.Del(Count); } }; -cStreamdevPatFilter::cStreamdevPatFilter(cStreamdevLiveStreamer *Streamer, const cChannel *Channel) +cStreamdevPatFilter::cStreamdevPatFilter(cStreamdevLiveStreamer *Streamer, const cChannel *Channel): siBuffer(10 * TS_SIZE, TS_SIZE) { Dprintf("cStreamdevPatFilter(\"%s\")\n", Channel->Name()); assert(Streamer); @@ -85,6 +90,29 @@ cStreamdevPatFilter::cStreamdevPatFilter(cStreamdevLiveStreamer *Streamer, const pmtSid = 0; pmtVersion = -1; Set(0x00, 0x00); // PAT + // initialize PAT buffer. Only some values are dynamic (see comments) + memset(tspat_buf, 0xff, TS_SIZE); + tspat_buf[0] = TS_SYNC_BYTE; // Transport packet header sunchronization byte (1000011 = 0x47h) + tspat_buf[1] = 0x40; // Set payload unit start indicator bit + tspat_buf[2] = 0x0; // PID + tspat_buf[3] = 0x10; // Set payload flag, DYNAMIC: Continuity counter + tspat_buf[4] = 0x0; // SI pointer field + tspat_buf[5] = 0x0; // PAT table id + tspat_buf[6] = 0xb0; // Section syntax indicator bit and reserved bits set + tspat_buf[7] = 12 + 1; // Section length (12 bit): PAT_TABLE_LEN + 1 + tspat_buf[8] = 0; // DYNAMIC: Transport stream ID (bits 8-15) + tspat_buf[9] = 0; // DYNAMIC: Transport stream ID (bits 0-7) + tspat_buf[10] = 0xc0; // Reserved, DYNAMIC: Version number, DYNAMIC: Current next indicator + tspat_buf[11] = 0x0; // Section number + tspat_buf[12] = 0x0; // Last section number + tspat_buf[13] = 0; // DYNAMIC: Program number (bits 8-15) + tspat_buf[14] = 0; // DYNAMIC: Program number (bits 0-7) + tspat_buf[15] = 0xe0; // Reserved, DYNAMIC: Network ID (bits 8-12) + tspat_buf[16] = 0; // DYNAMIC: Network ID (bits 0-7) + tspat_buf[17] = 0; // DYNAMIC: Checksum + tspat_buf[18] = 0; // DYNAMIC: Checksum + tspat_buf[19] = 0; // DYNAMIC: Checksum + tspat_buf[20] = 0; // DYNAMIC: Checksum } static const char * const psStreamTypes[] = { @@ -224,54 +252,37 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i if (0 != (pmtPid = assoc.getPid())) { Dprintf("cStreamdevPatFilter: PMT pid for channel %s: %d\n", Channel->Name(), pmtPid); pmtSid = assoc.getServiceId(); - if (Length < TS_SIZE-5) { - // repack PAT to TS frame and send to client -#ifndef TSPATREPACKER - uint8_t pat_ts[TS_SIZE] = {TS_SYNC_BYTE, 0x40 /* pusi=1 */, 0 /* pid=0 */, 0x10 /* adaption=1 */, 0 /* pointer */}; - memcpy(pat_ts + 5, Data, Length); - m_Streamer->Put(pat_ts, TS_SIZE); -#else - int ts_id; - unsigned int crc, i, len; - uint8_t *tmp, tspat_buf[TS_SIZE]; - static uint8_t ccounter = 0; - ccounter = (ccounter + 1) % 16; - memset(tspat_buf, 0xff, TS_SIZE); - ts_id = Channel->Tid(); // Get transport stream id of the channel - tspat_buf[0] = TS_SYNC_BYTE; // Transport packet header sunchronization byte (1000011 = 0x47h) - tspat_buf[1] = 0x40; // Set payload unit start indicator bit - tspat_buf[2] = 0x0; // PID - tspat_buf[3] = 0x10 | ccounter; // Set payload flag, Continuity counter - tspat_buf[4] = 0x0; // SI pointer field - tspat_buf[5] = 0x0; // PAT table id - tspat_buf[6] = 0xb0; // Section syntax indicator bit and reserved bits set - tspat_buf[7] = 12 + 1; // Section length (12 bit): PAT_TABLE_LEN + 1 - tspat_buf[8] = (ts_id >> 8); // Transport stream ID (bits 8-15) - tspat_buf[9] = (ts_id & 0xff); // Transport stream ID (bits 0-7) - tspat_buf[10] = 0xc0 | ((pat.getVersionNumber() << 1) & 0x3e) | - pat.getCurrentNextIndicator();// Version number, Current next indicator - tspat_buf[11] = 0x0; // Section number - tspat_buf[12] = 0x0; // Last section number - tspat_buf[13] = (pmtSid >> 8); // Program number (bits 8-15) - tspat_buf[14] = (pmtSid & 0xff); // Program number (bits 0-7) - tspat_buf[15] = 0xe0 | (pmtPid >> 8); // Network ID (bits 8-12) - tspat_buf[16] = (pmtPid & 0xff); // Network ID (bits 0-7) - crc = 0xffffffff; - len = 12; // PAT_TABLE_LEN - tmp = &tspat_buf[4 + 1]; // TS_HDR_LEN + 1 - while (len--) { - crc ^= *tmp++ << 24; - for (i = 0; i < 8; i++) - crc = (crc << 1) ^ ((crc & 0x80000000) ? 0x04c11db7 : 0); // CRC32POLY - } - tspat_buf[17] = crc >> 24 & 0xff; // Checksum - tspat_buf[18] = crc >> 16 & 0xff; // Checksum - tspat_buf[19] = crc >> 8 & 0xff; // Checksum - tspat_buf[20] = crc & 0xff; // Checksum - m_Streamer->Put(tspat_buf, TS_SIZE); -#endif - } else - isyslog("cStreamdevPatFilter: PAT size %d too large to fit in one TS", Length); + // repack PAT to TS frame and send to client + int ts_id; + unsigned int crc, i, len; + uint8_t *tmp; + static uint8_t ccounter = 0; + ccounter = (ccounter + 1) % 16; + ts_id = Channel->Tid(); // Get transport stream id of the channel + tspat_buf[3] = 0x10 | ccounter; // Set payload flag, Continuity counter + tspat_buf[8] = (ts_id >> 8); // Transport stream ID (bits 8-15) + tspat_buf[9] = (ts_id & 0xff); // Transport stream ID (bits 0-7) + tspat_buf[10] = 0xc0 | ((pat.getVersionNumber() << 1) & 0x3e) | + pat.getCurrentNextIndicator();// Version number, Current next indicator + tspat_buf[13] = (pmtSid >> 8); // Program number (bits 8-15) + tspat_buf[14] = (pmtSid & 0xff); // Program number (bits 0-7) + tspat_buf[15] = 0xe0 | (pmtPid >> 8); // Network ID (bits 8-12) + tspat_buf[16] = (pmtPid & 0xff); // Network ID (bits 0-7) + crc = 0xffffffff; + len = 12; // PAT_TABLE_LEN + tmp = &tspat_buf[4 + 1]; // TS_HDR_LEN + 1 + while (len--) { + crc ^= *tmp++ << 24; + for (i = 0; i < 8; i++) + crc = (crc << 1) ^ ((crc & 0x80000000) ? 0x04c11db7 : 0); // CRC32POLY + } + tspat_buf[17] = crc >> 24 & 0xff; // Checksum + tspat_buf[18] = crc >> 16 & 0xff; // Checksum + tspat_buf[19] = crc >> 8 & 0xff; // Checksum + tspat_buf[20] = crc & 0xff; // Checksum + int written = siBuffer.PutTS(tspat_buf, TS_SIZE); + if (written != TS_SIZE) + siBuffer.ReportOverflow(TS_SIZE - written); if (pmtPid != prevPmtPid) { m_Streamer->SetPids(pmtPid); Add(pmtPid, 0x02); @@ -292,7 +303,7 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i if (pmtVersion != -1) { if (pmtVersion != pmt.getVersionNumber()) { Dprintf("cStreamdevPatFilter: PMT version changed, detaching all pids\n"); - Del(pmtPid, 0x02); + cFilter::Del(pmtPid, 0x02); pmtPid = 0; // this triggers PAT scan } return; @@ -329,12 +340,7 @@ cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority, std::string Paramet m_Device(NULL), m_Receiver(NULL), m_PatFilter(NULL), -#if APIVERSNUM < 10703 - m_PESRemux(NULL), -#endif - m_ESRemux(NULL), - m_PSRemux(NULL), - m_ExtRemux(NULL) + m_Remux(NULL) { } @@ -347,12 +353,7 @@ cStreamdevLiveStreamer::~cStreamdevLiveStreamer() DELETENULL(m_PatFilter); } DELETENULL(m_Receiver); -#if APIVERSNUM < 10703 - delete m_PESRemux; -#endif - delete m_ESRemux; - delete m_PSRemux; - delete m_ExtRemux; + delete m_Remux; } bool cStreamdevLiveStreamer::HasPid(int Pid) @@ -459,19 +460,17 @@ bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType Str int pid = ISRADIO(m_Channel) ? m_Channel->Apid(0) : m_Channel->Vpid(); if (Apid != 0) pid = Apid; - m_ESRemux = new cTS2ESRemux(pid); + m_Remux = new cTS2ESRemux(pid); return SetPids(pid); } -#if APIVERSNUM < 10703 case stPES: - m_PESRemux = new cRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(), - m_Channel->Spids(), false); + m_Remux = new cTS2PESRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(), + m_Channel->Spids()); return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids()); -#endif case stPS: - m_PSRemux = new cTS2PSRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(), + m_Remux = new cTS2PSRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(), m_Channel->Spids()); return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids()); @@ -490,7 +489,7 @@ bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType Str return true; case stExtern: - m_ExtRemux = new cExternRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(), + m_Remux = new cExternRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(), m_Channel->Spids(), m_Parameter); return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids()); @@ -503,82 +502,39 @@ bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType Str int cStreamdevLiveStreamer::Put(const uchar *Data, int Count) { - switch (m_StreamType) { - case stTS: - case stTSPIDS: - return cStreamdevStreamer::Put(Data, Count); - -#if APIVERSNUM < 10703 - case stPES: - return m_PESRemux->Put(Data, Count); -#endif - - case stES: - return m_ESRemux->Put(Data, Count); - - case stPS: - return m_PSRemux->Put(Data, Count); - - case stExtern: - return m_ExtRemux->Put(Data, Count); - - default: // shouldn't happen??? - return 0; + // insert si data + if (m_PatFilter) { + int siCount; + uchar *siData = m_PatFilter->Get(siCount); + if (siData) { + if (m_Remux) + siCount = m_Remux->Put(siData, siCount); + else + siCount = cStreamdevStreamer::Put(siData, siCount); + if (siCount) + m_PatFilter->Del(siCount); + } } + if (m_Remux) + return m_Remux->Put(Data, Count); + else + return cStreamdevStreamer::Put(Data, Count); } uchar *cStreamdevLiveStreamer::Get(int &Count) { - switch (m_StreamType) { - case stTS: - case stTSPIDS: + if (m_Remux) + return m_Remux->Get(Count); + else return cStreamdevStreamer::Get(Count); - -#if APIVERSNUM < 10703 - case stPES: - return m_PESRemux->Get(Count); -#endif - - case stES: - return m_ESRemux->Get(Count); - - case stPS: - return m_PSRemux->Get(Count); - - case stExtern: - return m_ExtRemux->Get(Count); - - default: // shouldn't happen??? - return 0; - } } void cStreamdevLiveStreamer::Del(int Count) { - switch (m_StreamType) { - case stTS: - case stTSPIDS: + if (m_Remux) + m_Remux->Del(Count); + else cStreamdevStreamer::Del(Count); - break; - -#if APIVERSNUM < 10703 - case stPES: - m_PESRemux->Del(Count); - break; -#endif - - case stES: - m_ESRemux->Del(Count); - break; - - case stPS: - m_PSRemux->Del(Count); - break; - - case stExtern: - m_ExtRemux->Del(Count); - break; - } } void cStreamdevLiveStreamer::Attach(void) diff --git a/server/livestreamer.h b/server/livestreamer.h index 5c4ae8f..7f442ba 100644 --- a/server/livestreamer.h +++ b/server/livestreamer.h @@ -5,14 +5,12 @@ #include <vdr/receiver.h> #include "server/streamer.h" +#include "remux/tsremux.h" #include "common.h" -class cTS2PSRemux; -class cTS2ESRemux; -class cExternRemux; -#if APIVERSNUM < 10703 -class cRemux; -#endif +namespace Streamdev { + class cTSRemux; +} class cStreamdevPatFilter; class cStreamdevLiveReceiver; @@ -29,12 +27,7 @@ private: cDevice *m_Device; cStreamdevLiveReceiver *m_Receiver; cStreamdevPatFilter *m_PatFilter; -#if APIVERSNUM < 10703 - cRemux *m_PESRemux; -#endif - cTS2ESRemux *m_ESRemux; - cTS2PSRemux *m_PSRemux; - cExternRemux *m_ExtRemux; + Streamdev::cTSRemux *m_Remux; void StartReceiver(void); bool HasPid(int Pid); diff --git a/server/menuHTTP.c b/server/menuHTTP.c index 41b1f10..8d3e404 100644 --- a/server/menuHTTP.c +++ b/server/menuHTTP.c @@ -201,10 +201,8 @@ std::string cHtmlChannelList::StreamTypeMenu() (std::string) "[<a href=\"/TS/" + self + "\">TS</a>] "); typeMenu += (streamType == stPS ? (std::string) "[PS] " : (std::string) "[<a href=\"/PS/" + self + "\">PS</a>] "); -#if APIVERSNUM < 10703 typeMenu += (streamType == stPES ? (std::string) "[PES] " : (std::string) "[<a href=\"/PES/" + self + "\">PES</a>] "); -#endif typeMenu += (streamType == stES ? (std::string) "[ES] " : (std::string) "[<a href=\"/ES/" + self + "\">ES</a>] "); typeMenu += (streamType == stExtern ? (std::string) "[Extern] " : @@ -343,10 +341,8 @@ std::string cHtmlChannelList::ItemText() switch (streamType) { case stTS: suffix = (std::string) ".ts"; break; case stPS: suffix = (std::string) ".vob"; break; -#if APIVERSNUM < 10703 // for Network Media Tank case stPES: suffix = (std::string) ".vdr"; break; -#endif default: suffix = ""; } line += (std::string) "<li value=\"" + (const char*) itoa(current->Number()) + "\">"; diff --git a/server/recplayer.c b/server/recplayer.c new file mode 100644 index 0000000..f45d8c3 --- /dev/null +++ b/server/recplayer.c @@ -0,0 +1,288 @@ +/* + Copyright 2004-2005 Chris Tallon + + This file is part of VOMP. + and adopted for streamdev to play recordings + + VOMP is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + VOMP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with VOMP; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "recplayer.h" + +#define _XOPEN_SOURCE 600 +#include <fcntl.h> + +RecPlayer::RecPlayer(cRecording* rec) +{ + file = NULL; + fileOpen = 0; + lastPosition = 0; + recording = rec; + for(int i = 1; i < 1000; i++) segments[i] = NULL; + + // FIXME find out max file path / name lengths + +#if VDRVERSNUM >= 10703 + indexFile = new cIndexFile(recording->FileName(), false, rec->IsPesRecording()); +#else + indexFile = new cIndexFile(recording->FileName(), false); +#endif + if (!indexFile) esyslog("ERROR: Streamdev: Failed to create indexfile!"); + + scan(); +} + +void RecPlayer::scan() +{ + if (file) fclose(file); + totalLength = 0; + fileOpen = 0; + totalFrames = 0; + + int i = 1; + while(segments[i++]) delete segments[i]; + + char fileName[2048]; + for(i = 1; i < 1000; i++) + { + +#if APIVERSNUM < 10703 + snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), i); + //log->log("RecPlayer", Log::DEBUG, "FILENAME: %s", fileName); + file = fopen(fileName, "r"); +#else + snprintf(fileName, 2047, "%s/%05i.ts", recording->FileName(), i); + file = fopen(fileName, "r"); + if (!file) { + snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), i); + file = fopen(fileName, "r"); + } +#endif + if (!file) break; + + segments[i] = new Segment(); + segments[i]->start = totalLength; + fseek(file, 0, SEEK_END); + totalLength += ftell(file); + totalFrames = indexFile->Last(); + //log->log("RecPlayer", Log::DEBUG, "File %i found, totalLength now %llu, numFrames = %lu", i, totalLength, totalFrames); + segments[i]->end = totalLength; + fclose(file); + } + + file = NULL; +} + +RecPlayer::~RecPlayer() +{ + //log->log("RecPlayer", Log::DEBUG, "destructor"); + int i = 1; + while(segments[i++]) delete segments[i]; + if (file) fclose(file); +} + +int RecPlayer::openFile(int index) +{ + if (file) fclose(file); + + char fileName[2048]; + +#if APIVERSNUM >= 10703 + snprintf(fileName, 2047, "%s/%05i.ts", recording->FileName(), index); + isyslog("openFile called for index %i string:%s", index, fileName); + + file = fopen(fileName, "r"); + if (file) + { + fileOpen = index; + return 1; + } +#endif + + snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), index); + isyslog("openFile called for index %i string:%s", index, fileName); + //log->log("RecPlayer", Log::DEBUG, "openFile called for index %i string:%s", index, fileName); + + file = fopen(fileName, "r"); + if (file) + { + fileOpen = index; + return 1; + } + + //log->log("RecPlayer", Log::DEBUG, "file failed to open"); + fileOpen = 0; + return 0; +} + +uint64_t RecPlayer::getLengthBytes() +{ + return totalLength; +} + +uint32_t RecPlayer::getLengthFrames() +{ + return totalFrames; +} + +unsigned long RecPlayer::getBlock(unsigned char* buffer, uint64_t position, unsigned long amount) +{ + if ((amount > totalLength) || (amount > 500000)) + { + //log->log("RecPlayer", Log::DEBUG, "Amount %lu requested and rejected", amount); + return 0; + } + + if (position >= totalLength) + { + //log->log("RecPlayer", Log::DEBUG, "Client asked for data starting past end of recording!"); + return 0; + } + + if ((position + amount) > totalLength) + { + //log->log("RecPlayer", Log::DEBUG, "Client asked for some data past the end of recording, adjusting amount"); + amount = totalLength - position; + } + + // work out what block position is in + int segmentNumber; + for(segmentNumber = 1; segmentNumber < 1000; segmentNumber++) + { + if ((position >= segments[segmentNumber]->start) && (position < segments[segmentNumber]->end)) break; + // position is in this block + } + + // we could be seeking around + if (segmentNumber != fileOpen) + { + if (!openFile(segmentNumber)) return 0; + } + + uint64_t currentPosition = position; + uint32_t yetToGet = amount; + uint32_t got = 0; + uint32_t getFromThisSegment = 0; + uint32_t filePosition; + + while(got < amount) + { + if (got) + { + // if(got) then we have already got some and we are back around + // advance the file pointer to the next file + if (!openFile(++segmentNumber)) return 0; + } + + // is the request completely in this block? + if ((currentPosition + yetToGet) <= segments[segmentNumber]->end) + getFromThisSegment = yetToGet; + else + getFromThisSegment = segments[segmentNumber]->end - currentPosition; + + filePosition = currentPosition - segments[segmentNumber]->start; + fseek(file, filePosition, SEEK_SET); + if (fread(&buffer[got], getFromThisSegment, 1, file) != 1) return 0; // umm, big problem. + + // Tell linux not to bother keeping the data in the FS cache + posix_fadvise(file->_fileno, filePosition, getFromThisSegment, POSIX_FADV_DONTNEED); + + got += getFromThisSegment; + currentPosition += getFromThisSegment; + yetToGet -= getFromThisSegment; + } + + lastPosition = position; + return got; +} + +uint64_t RecPlayer::getLastPosition() +{ + return lastPosition; +} + +cRecording* RecPlayer::getCurrentRecording() +{ + return recording; +} + +uint64_t RecPlayer::positionFromFrameNumber(uint32_t frameNumber) +{ + if (!indexFile) return 0; + +#if VDRVERSNUM >= 10703 + uint16_t retFileNumber; + off_t retFileOffset; +#else + uchar retFileNumber; + int retFileOffset; +#endif + + if (!indexFile->Get((int)frameNumber, &retFileNumber, &retFileOffset)) + { + return 0; + } + +// log->log("RecPlayer", Log::DEBUG, "FN: %u FO: %i", retFileNumber, retFileOffset); + if (!segments[retFileNumber]) return 0; + uint64_t position = segments[retFileNumber]->start + retFileOffset; +// log->log("RecPlayer", Log::DEBUG, "Pos: %llu", position); + + return position; +} + +uint32_t RecPlayer::frameNumberFromPosition(uint64_t position) +{ + if (!indexFile) return 0; + + if (position >= totalLength) + { + //log->log("RecPlayer", Log::DEBUG, "Client asked for data starting past end of recording!"); + return 0; + } + + uint8_t segmentNumber; + for(segmentNumber = 1; segmentNumber < 255; segmentNumber++) + { + if ((position >= segments[segmentNumber]->start) && (position < segments[segmentNumber]->end)) break; + // position is in this block + } + uint32_t askposition = position - segments[segmentNumber]->start; + return indexFile->Get((int)segmentNumber, askposition); + +} + + +bool RecPlayer::getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength) +{ + // 0 = backwards + // 1 = forwards + + if (!indexFile) return false; + + int iframeLength; + int indexReturnFrameNumber; + + indexReturnFrameNumber = (uint32_t)indexFile->GetNextIFrame(frameNumber, (direction==1 ? true : false), NULL, NULL, &iframeLength); + //log->log("RecPlayer", Log::DEBUG, "GNIF input framenumber:%lu, direction=%lu, output:framenumber=%i, framelength=%i", frameNumber, direction, indexReturnFrameNumber, iframeLength); + + if (indexReturnFrameNumber == -1) return false; + + *rfilePosition = positionFromFrameNumber(indexReturnFrameNumber); + *rframeNumber = (uint32_t)indexReturnFrameNumber; + *rframeLength = (uint32_t)iframeLength; + + return true; +} diff --git a/server/recplayer.h b/server/recplayer.h new file mode 100644 index 0000000..3da6c89 --- /dev/null +++ b/server/recplayer.h @@ -0,0 +1,63 @@ +/* + Copyright 2004-2005 Chris Tallon + + This file is part of VOMP. + + VOMP is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + VOMP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with VOMP; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef RECPLAYER_H +#define RECPLAYER_H + +#include <stdio.h> +#include <vdr/recording.h> + +#include "server/streamer.h" + +class Segment +{ + public: + uint64_t start; + uint64_t end; +}; + +class RecPlayer +{ + public: + RecPlayer(cRecording* rec); + ~RecPlayer(); + uint64_t getLengthBytes(); + uint32_t getLengthFrames(); + unsigned long getBlock(unsigned char* buffer, uint64_t position, unsigned long amount); + int openFile(int index); + uint64_t getLastPosition(); + cRecording* getCurrentRecording(); + void scan(); + uint64_t positionFromFrameNumber(uint32_t frameNumber); + uint32_t frameNumberFromPosition(uint64_t position); + bool getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength); + + private: + cRecording* recording; + cIndexFile* indexFile; + FILE* file; + int fileOpen; + Segment* segments[1000]; + uint64_t totalLength; + uint64_t lastPosition; + uint32_t totalFrames; +}; + +#endif diff --git a/server/streamer.c b/server/streamer.c index 9795cc6..42e7efa 100644 --- a/server/streamer.c +++ b/server/streamer.c @@ -1,5 +1,5 @@ /* - * $Id: streamer.c,v 1.18 2009/02/13 10:39:22 schmirl Exp $ + * $Id: streamer.c,v 1.19 2009/06/19 06:32:45 schmirl Exp $ */ #include <vdr/ringbuffer.h> @@ -14,6 +14,13 @@ #include "tools/select.h" #include "common.h" +// --- cStreamdevBuffer ------------------------------------------------------- + +cStreamdevBuffer::cStreamdevBuffer(int Size, int Margin, bool Statistics, const char *Description): + cRingBufferLinear(Size, Margin, Statistics, Description) +{ +} + // --- cStreamdevWriter ------------------------------------------------------- cStreamdevWriter::cStreamdevWriter(cTBSocket *Socket, @@ -95,14 +102,13 @@ void cStreamdevWriter::Action(void) cStreamdevStreamer::cStreamdevStreamer(const char *Name): cThread(Name), - m_Running(false), m_Writer(NULL), - m_RingBuffer(new cRingBufferLinear(STREAMERBUFSIZE, TS_SIZE * 2, + m_RingBuffer(new cStreamdevBuffer(STREAMERBUFSIZE, TS_SIZE * 2, true, "streamdev-streamer")), - m_SendBuffer(new cRingBufferLinear(WRITERBUFSIZE, TS_SIZE * 2)) + m_SendBuffer(new cStreamdevBuffer(WRITERBUFSIZE, TS_SIZE * 2)) { m_RingBuffer->SetTimeouts(0, 100); - m_SendBuffer->SetTimeouts(0, 100); + m_SendBuffer->SetTimeouts(100, 100); } cStreamdevStreamer::~cStreamdevStreamer() @@ -116,7 +122,6 @@ void cStreamdevStreamer::Start(cTBSocket *Socket) { Dprintf("start streamer\n"); m_Writer = new cStreamdevWriter(Socket, this); - m_Running = true; Attach(); } @@ -135,9 +140,8 @@ void cStreamdevStreamer::Stop(void) Dprintf("stopping streamer\n"); Cancel(3); } - if (m_Running) { + if (m_Writer) { Detach(); - m_Running = false; DELETENULL(m_Writer); } } @@ -152,8 +156,6 @@ void cStreamdevStreamer::Action(void) int count = Put(block, got); if (count) m_RingBuffer->Del(count); - else - cCondWait::SleepMs(100); } } } diff --git a/server/streamer.h b/server/streamer.h index 20323b7..6561bc2 100644 --- a/server/streamer.h +++ b/server/streamer.h @@ -1,5 +1,5 @@ /* - * $Id: streamer.h,v 1.10 2009/02/13 10:39:22 schmirl Exp $ + * $Id: streamer.h,v 1.11 2009/06/19 06:32:45 schmirl Exp $ */ #ifndef VDR_STREAMDEV_STREAMER_H @@ -16,8 +16,34 @@ class cStreamdevStreamer; #define TS_SIZE 188 #endif -#define STREAMERBUFSIZE MEGABYTE(4) -#define WRITERBUFSIZE KILOBYTE(256) +#define STREAMERBUFSIZE (20000 * TS_SIZE) +#define WRITERBUFSIZE (5000 * TS_SIZE) + +// --- cStreamdevBuffer ------------------------------------------------------- + +class cStreamdevBuffer: public cRingBufferLinear { +public: + // make public + void WaitForPut(void) { cRingBuffer::WaitForPut(); } + // Always write complete TS packets + // (assumes Count is a multiple of TS_SIZE) + int PutTS(const uchar *Data, int Count); + cStreamdevBuffer(int Size, int Margin = 0, bool Statistics = false, const char *Description = NULL); +}; + +inline int cStreamdevBuffer::PutTS(const uchar *Data, int Count) +{ + int free = Free(); + if (free < Count) + Count = free; + + Count -= Count % TS_SIZE; + if (Count) + Count = Put(Data, Count); + else + WaitForPut(); + return Count; +} // --- cStreamdevWriter ------------------------------------------------------- @@ -38,15 +64,14 @@ public: class cStreamdevStreamer: public cThread { private: - bool m_Running; cStreamdevWriter *m_Writer; - cRingBufferLinear *m_RingBuffer; - cRingBufferLinear *m_SendBuffer; + cStreamdevBuffer *m_RingBuffer; + cStreamdevBuffer *m_SendBuffer; protected: virtual void Action(void); - bool IsRunning(void) const { return m_Running; } + bool IsRunning(void) const { return m_Writer; } public: cStreamdevStreamer(const char *Name); @@ -57,10 +82,10 @@ public: bool Abort(void); void Activate(bool On); - int Receive(uchar *Data, int Length) { return m_RingBuffer->Put(Data, Length); } + int Receive(uchar *Data, int Length) { return m_RingBuffer->PutTS(Data, Length); } void ReportOverflow(int Bytes) { m_RingBuffer->ReportOverflow(Bytes); } - virtual int Put(const uchar *Data, int Count) { return m_SendBuffer->Put(Data, Count); } + virtual int Put(const uchar *Data, int Count) { return m_SendBuffer->PutTS(Data, Count); } virtual uchar *Get(int &Count) { return m_SendBuffer->Get(Count); } virtual void Del(int Count) { m_SendBuffer->Del(Count); } |