diff options
-rw-r--r-- | HISTORY | 4 | ||||
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | decoder.h | 4 | ||||
-rw-r--r-- | osd.c | 195 | ||||
-rw-r--r-- | osd.h | 5 | ||||
-rw-r--r-- | osd_info.c | 160 | ||||
-rw-r--r-- | osd_info.h | 2 | ||||
-rw-r--r-- | osdpip.c | 2 | ||||
-rw-r--r-- | patches/osdpip-0.0.10a-fedora.diff | 49 | ||||
-rw-r--r-- | patches/osdpip__decoder_c.diff | 20 | ||||
-rw-r--r-- | po/ca_ES.po | 7 | ||||
-rw-r--r-- | po/cs_CZ.po | 7 | ||||
-rw-r--r-- | po/da_DK.po | 7 | ||||
-rw-r--r-- | po/de_DE.po | 9 | ||||
-rw-r--r-- | po/el_GR.po | 7 | ||||
-rw-r--r-- | po/es_ES.po | 7 | ||||
-rw-r--r-- | po/et_EE.po | 7 | ||||
-rw-r--r-- | po/fi_FI.po | 7 | ||||
-rw-r--r-- | po/fr_FR.po | 7 | ||||
-rw-r--r-- | po/hr_HR.po | 7 | ||||
-rw-r--r-- | po/hu_HU.po | 7 | ||||
-rwxr-xr-x[-rw-r--r--] | po/it_IT.po | 87 | ||||
-rw-r--r-- | po/nl_NL.po | 7 | ||||
-rw-r--r-- | po/nn_NO.po | 7 | ||||
-rw-r--r-- | po/pl_PL.po | 7 | ||||
-rw-r--r-- | po/pt_PT.po | 7 | ||||
-rw-r--r-- | po/ro_RO.po | 7 | ||||
-rw-r--r-- | po/ru_RU.po | 7 | ||||
-rw-r--r-- | po/sl_SI.po | 7 | ||||
-rw-r--r-- | po/sv_SE.po | 7 | ||||
-rw-r--r-- | po/tr_TR.po | 7 | ||||
-rw-r--r-- | receiver.c | 9 | ||||
-rw-r--r-- | receiver.h | 8 | ||||
-rw-r--r-- | remux.c | 2228 | ||||
-rw-r--r-- | remux.h | 85 |
35 files changed, 2856 insertions, 148 deletions
@@ -1,6 +1,10 @@ VDR Plugin 'osdpip' Revision History ------------------------------------ +2010-01-01: Version 0.0.10a +- added support for VDR 1.7.x +- added PIP while replaying + 2008-05-03: Version 0.0.10 (written by Andreas Regel) - support swscale functions of recent FFMPEG versions. Have a look at README to see how to deactivate it for older FFPMEG versions. @@ -13,7 +13,7 @@ # ### uncomment the following line, if you have a recent FFMPEG version that ### has a changed structure of its header files. -#WITH_NEW_FFMPEG_HEADERS=1 +WITH_NEW_FFMPEG_HEADERS=1 # The official name of this plugin. # This name will be used in the '-P...' option of VDR to load the plugin. @@ -60,7 +60,7 @@ DEFINES += -D_GNU_SOURCE ### The object files (add further files here): -OBJS = $(PLUGIN).o osd_info.o osd.o receiver.o setup.o i18n.o pes.o quantize.o decoder.o +OBJS = $(PLUGIN).o osd_info.o remux.o osd.o receiver.o setup.o i18n.o pes.o quantize.o decoder.o ifdef FFMPEG_STATIC DEFINES += -DHAVE_FFMPEG_STATIC @@ -20,9 +20,9 @@ extern "C" #include <libswscale/swscale.h> #endif #else - #include <ffmpeg/avcodec.h> + #include <libavcodec/avcodec.h> #ifdef USE_SWSCALE - #include <ffmpeg/swscale.h> + #include <libswscale/swscale.h> #endif #endif } @@ -13,11 +13,18 @@ #include "quantize.h" #include "receiver.h" #include "setup.h" +#if VDRVERSNUM > 10703 +#include "remux.h" +#endif #include <vdr/ringbuffer.h> +#if VDRVERSNUM <= 10703 #include <vdr/remux.h> +#endif #include <vdr/thread.h> +#include <vdr/menu.h> + cMutex Mutex; cOsdPipObject::cOsdPipObject(cDevice *Device, const cChannel *Channel) @@ -31,7 +38,7 @@ cOsdPipObject::cOsdPipObject(cDevice *Device, const cChannel *Channel) m_Active = false; m_Ready = false; m_Reset = true; - m_PipMode = pipModeNormal; + m_PipMode = (cDevice::PrimaryDevice()->Replaying() && cControl::Control()) ? pipModeReplay : pipModeNormal; m_Width = m_Height = -1; m_Bitmap = NULL; m_InfoWindow = NULL; @@ -109,11 +116,12 @@ void cOsdPipObject::SwitchOsdPipChan(int i) { m_Channel = pipChan; dev->SwitchChannel(m_Channel, false); + m_InfoWindow->SetMessage(tr("Zapping mode")); + m_InfoWindow->Show(true); m_Receiver = new cOsdPipReceiver(m_Channel, m_ESBuffer); dev->AttachReceiver(m_Receiver); } Start(); - m_InfoWindow->Hide(); } } @@ -210,6 +218,9 @@ void cOsdPipObject::ProcessImage(unsigned char * data, int length) int infoX = 0; int infoY = 0; int infoH = OsdPipSetup.ShowInfo * 30; + if (cReplayControl::NowReplaying() && OsdPipSetup.ShowInfo >= 2) { + infoH = 60; + } switch (OsdPipSetup.InfoPosition) { @@ -469,7 +480,17 @@ void cOsdPipObject::ProcessImage(unsigned char * data, int length) if (OsdPipSetup.ShowInfo) { m_InfoWindow->SetChannel(Channels.GetByNumber(cDevice::ActualDevice()->CurrentChannel())); - m_InfoWindow->Show(); + switch (m_PipMode) + { + case pipModeZapping: + m_InfoWindow->SetMessage(tr("Zapping mode")); + break; + case pipModeMoving: + m_InfoWindow->SetMessage(tr("Move mode")); + break; + default: ; + } + m_InfoWindow->Show(true); } m_Ready = true; } @@ -481,6 +502,11 @@ void cOsdPipObject::ProcessImage(unsigned char * data, int length) void cOsdPipObject::Action(void) { + int pos, end; + bool Play, Fwd; + int Spd; + static int pPos; + static time_t tme; m_Active = true; isyslog("osdpip: decoder thread started (pid = %d)", getpid()); @@ -543,6 +569,27 @@ void cOsdPipObject::Action(void) } } cCondWait::SleepMs(1); + if (cDevice::PrimaryDevice()->Replaying() && cControl::Control()) { + cControl::Control()->GetReplayMode(Play, Fwd, Spd); + cControl::Control()->GetIndex(pos, end); + if ((Play && Fwd && pos == pPos) || pos >= (end - 1)) + { + if (tme == 0) + { + tme = time(NULL) + 3; + } + if (tme < time(NULL)) + { + tme = 0; + StopReplay(); + } + } + if (!Play || !Fwd || pPos != pos) + { + tme = 0; + pPos = pos; + } + } } if (OsdPipSetup.ColorDepth == kDepthColor128var || @@ -558,8 +605,21 @@ void cOsdPipObject::Show(void) Start(); } +void cOsdPipObject::StopReplay(void) +{ + cControl::Control()->ProcessKey(kStop); + cDevice::PrimaryDevice()->StopReplay(); + if (m_InfoWindow->Shown()) + m_InfoWindow->Hide(); + Channels.SwitchTo(cDevice::CurrentChannel()); +} + eOSState cOsdPipObject::ProcessKey(eKeys Key) { + static cString pPos = ""; + int pos, end; + bool replay = (cReplayControl::NowReplaying() && cControl::Control()); + eOSState state = cOsdObject::ProcessKey(Key); if (state == osUnknown) { @@ -581,7 +641,7 @@ eOSState cOsdPipObject::ProcessKey(eKeys Key) if (m_Ready && m_InfoWindow) { m_InfoWindow->SetMessage(tr("Move mode")); - m_InfoWindow->Show(); + m_InfoWindow->Show(true); } break; case kYellow: @@ -589,13 +649,26 @@ eOSState cOsdPipObject::ProcessKey(eKeys Key) if (m_Ready && m_InfoWindow) { m_InfoWindow->SetMessage(tr("Normal mode")); - m_InfoWindow->Show(); + m_InfoWindow->Show(true); + } + break; + case kBlue: + if (replay) + { + m_PipMode = pipModeReplay; + if (m_Ready && m_InfoWindow) + { + m_InfoWindow->SetMessage(tr("Replay mode")); + m_InfoWindow->Show(true); + } } break; case kUp: + case kChanUp: SwitchOsdPipChan(1); break; case kDown: + case kChanDn: SwitchOsdPipChan(-1); break; default: @@ -616,7 +689,7 @@ eOSState cOsdPipObject::ProcessKey(eKeys Key) if (m_Ready && m_InfoWindow) { m_InfoWindow->SetMessage(tr("Normal mode")); - m_InfoWindow->Show(); + m_InfoWindow->Show(true); } break; case kYellow: @@ -624,7 +697,18 @@ eOSState cOsdPipObject::ProcessKey(eKeys Key) if (m_Ready && m_InfoWindow) { m_InfoWindow->SetMessage(tr("Zapping mode")); - m_InfoWindow->Show(); + m_InfoWindow->Show(true); + } + break; + case kBlue: + if (replay) + { + m_PipMode = pipModeReplay; + if (m_Ready && m_InfoWindow) + { + m_InfoWindow->SetMessage(tr("Replay mode")); + m_InfoWindow->Show(true); + } } break; case kUp: @@ -650,10 +734,86 @@ eOSState cOsdPipObject::ProcessKey(eKeys Key) } state = osContinue; } + else if (m_PipMode == pipModeReplay) + { + if (replay) + { + switch (Key & ~k_Repeat) + { + case kOk: + case kBack: + state = m_InfoWindow->ProcessKey(Key); + break; + case k1...k9: + case kRed: + state = osContinue; + break; + case kBlue: + m_PipMode = pipModeNormal; + if (m_Ready && m_InfoWindow) + { + m_InfoWindow->SetMessage(tr("Normal mode")); + m_InfoWindow->Show(replay); + } + state = osContinue; + break; + case k0: + StopReplay(); + state = osContinue; + break; + case kLeft: + case kRight: + case kYellow: + case kGreen: + default: + cControl::Control()->ProcessKey(Key); + state = osContinue; + } + if (cControl::Control()) + { + cControl::Control()->GetIndex(pos, end); + if (pPos == "") + { + pPos = IndexToHMSF(pos); + } + if (strcmp(IndexToHMSF(pos), pPos) != 0 && m_InfoWindow) + { + pPos = IndexToHMSF(pos); + if (m_InfoWindow->Shown()) + { + m_InfoWindow->Show(false); + } + } + } + } else if (m_Ready && m_InfoWindow) + { + state = m_InfoWindow->ProcessKey(Key); + } + } else { - if (m_Ready && m_InfoWindow) + if (replay) + { + switch (Key) + { + case kBack: + case k0: + StopReplay(); + state = osContinue; + break; + case k1...k9: + case kUp: + case kChanUp: + case kDown: + case kChanDn: + case kRed: + state = osContinue; + default: ; + } + } else if (m_Ready && m_InfoWindow) + { state = m_InfoWindow->ProcessKey(Key); + } } } if (state == osUnknown) @@ -674,7 +834,7 @@ eOSState cOsdPipObject::ProcessKey(eKeys Key) if (m_Ready && m_InfoWindow) { m_InfoWindow->SetMessage(tr("Move mode")); - m_InfoWindow->Show(); + m_InfoWindow->Show(true); } break; case kYellow: @@ -682,7 +842,18 @@ eOSState cOsdPipObject::ProcessKey(eKeys Key) if (m_Ready && m_InfoWindow) { m_InfoWindow->SetMessage(tr("Zapping mode")); - m_InfoWindow->Show(); + m_InfoWindow->Show(true); + } + break; + case kBlue: + if (replay) + { + m_PipMode = pipModeReplay; + if (m_Ready && m_InfoWindow) + { + m_InfoWindow->SetMessage(tr("Replay mode")); + m_InfoWindow->Show(replay); + } } break; case kUp: @@ -692,7 +863,7 @@ eOSState cOsdPipObject::ProcessKey(eKeys Key) case kOk: if (OsdPipSetup.ShowInfo) { - m_InfoWindow->Show(); + m_InfoWindow->Show(true); } break; default: @@ -716,6 +887,8 @@ void cOsdPipObject::ChannelSwitch(const cDevice * device, int channelNumber) if (OsdPipSetup.ShowInfo) { m_InfoWindow->SetChannel(Channels.GetByNumber(channelNumber)); + if (m_PipMode == pipModeZapping) + m_InfoWindow->SetMessage(tr("Zapping mode")); m_InfoWindow->Show(); } } @@ -21,7 +21,8 @@ typedef enum _ePipMode { pipModeNormal, pipModeMoving, - pipModeZapping + pipModeZapping, + pipModeReplay } ePipMode; class cRingBufferFrame; @@ -56,6 +57,7 @@ private: void Stop(void); void SwapChannels(void); void SwitchOsdPipChan(int i); + void StopReplay(); protected: virtual void Action(void); virtual void ChannelSwitch(const cDevice * device, int channelNumber); @@ -64,7 +66,6 @@ protected: public: cOsdPipObject(cDevice *Device, const cChannel *Channel); virtual ~cOsdPipObject(void); - virtual void Show(void); eOSState ProcessKey(eKeys k); }; @@ -5,6 +5,8 @@ #include <vdr/device.h> #include <vdr/thread.h> +#include <vdr/menu.h> + #define DIRECTCHANNELTIMEOUT 1 #define INFOTIMEOUT 5 @@ -44,74 +46,112 @@ void cOsdInfoWindow::SetChannel(const cChannel * channel) m_Group = -1; } -void cOsdInfoWindow::Show() +void cOsdInfoWindow::Show(bool Refresh) { char channel[101] = ""; char presentName[101] = ""; char presentTime[10] = ""; char followingName[101] = ""; char followingTime[10] = ""; - + double progress = 0.0; + int pos, end; + char *tmp = NULL; + static bool lastMsg; + if (m_Message) { snprintf(channel, 100, "%s", m_Message); - m_Message = NULL; } - else if (m_Channel) + else if (!(cReplayControl::NowReplaying() && cControl::Control())) { - if (m_Channel->GroupSep()) - snprintf(channel, 100, "%s", m_Channel->Name()); - else - snprintf(channel, 100, "%d%s %s", m_Channel->Number(), m_Number ? "-" : "", m_Channel->Name()); - if (m_WithInfo) + if (m_Channel) { - const cEvent * present = NULL; - const cEvent * following = NULL; - cSchedulesLock schedulesLock; - const cSchedules * schedules = cSchedules::Schedules(schedulesLock); - if (schedules) + if (m_Channel->GroupSep()) + snprintf(channel, 100, "%s", m_Channel->Name()); + else + snprintf(channel, 100, "%d%s %s", m_Channel->Number(), m_Number ? "-" : "", m_Channel->Name()); + if (m_WithInfo) { - const cSchedule * schedule = schedules->GetSchedule(m_Channel->GetChannelID()); - if (schedule) + const cEvent * present = NULL; + const cEvent * following = NULL; + cSchedulesLock schedulesLock; + const cSchedules * schedules = cSchedules::Schedules(schedulesLock); + if (schedules) { - if ((present = schedule->GetPresentEvent()) != NULL) + const cSchedule * schedule = schedules->GetSchedule(m_Channel->GetChannelID()); + if (schedule) { - const char * presentTitle = present->Title(); - if (!isempty(presentTitle)) + if ((present = schedule->GetPresentEvent()) != NULL) { - sprintf(presentTime, "%s", (const char *) present->GetTimeString()); - sprintf(presentName, "%s", (const char *) presentTitle); + const char * presentTitle = present->Title(); + if (!isempty(presentTitle)) + { + sprintf(presentTime, "%s", (const char *) present->GetTimeString()); + sprintf(presentName, "%s", (const char *) presentTitle); + progress = double(time(NULL) - present->StartTime()) / double(present->EndTime() - present->StartTime()); + } } - } - if ((following = schedule->GetFollowingEvent()) != NULL) - { - const char * followingTitle = following->Title(); - if (!isempty(followingTitle)) + if ((following = schedule->GetFollowingEvent()) != NULL) { - sprintf(followingTime, "%s", (const char *) following->GetTimeString()); - sprintf(followingName, "%s", (const char *) followingTitle); + const char * followingTitle = following->Title(); + if (!isempty(followingTitle)) + { + sprintf(followingTime, "%s", (const char *) following->GetTimeString()); + sprintf(followingName, "%s", (const char *) followingTitle); + } } } } } } + else if (m_Number) + { + snprintf(channel, 100, "%d-", m_Number); + } + else + snprintf(channel, 100, "%s", trVDR("*** Invalid Channel ***")); } - else if (m_Number) - snprintf(channel, 100, "%d-", m_Number); else - snprintf(channel, 100, "%s", trVDR("*** Invalid Channel ***")); + { + cControl::Control()->GetIndex(pos, end); + cRecording *rec = new cRecording(cReplayControl::NowReplaying()); + asprintf(&tmp, "%s (%s/%s)", rec->Name(), (const char *)IndexToHMSF(pos), (const char *)IndexToHMSF(end)); + while (strchr(tmp, '~')) + { + tmp = strchr(tmp, '~'); + tmp++; + } + snprintf(channel, 100, "%s", tmp); + delete(rec); + rec = NULL; + } Mutex.Lock(); if (OsdPipSetup.ColorDepth == kDepthGrey16) { - m_Bitmap->DrawRectangle(0, 0, m_Bitmap->Width() - 1, m_Bitmap->Height() - 1, clrBlack); + if (!m_Shown || Refresh || (!m_Message && lastMsg)) + m_Bitmap->DrawRectangle(0, 0, m_Bitmap->Width() - 1, m_Bitmap->Height() - 1, clrBlack); const cFont * font = cFont::GetFont(fontOsd); - m_Bitmap->DrawText(0, 0, channel, clrWhite, clrBlack, font, m_Bitmap->Width(), 29); + m_Bitmap->DrawText((cControl::Control() && cReplayControl::NowReplaying()) || m_Message ? 0 : 20, 0, channel, clrWhite, clrBlack, font, m_Bitmap->Width(), 29); if (m_Bitmap->Height() > 30) { - m_Bitmap->DrawText(0, 30, presentTime, clrWhite, clrBlack, font, 80, 29); - m_Bitmap->DrawText(80, 30, presentName, clrWhite, clrBlack, font, m_Bitmap->Width() - 80, 29); + if (cReplayControl::NowReplaying() && cControl::Control() && !m_Message) { + m_Bitmap->DrawRectangle(0, 30, m_Bitmap->Width() - 1, m_Bitmap->Height() - 1, clrBlack); + cControl::Control()->GetIndex(pos, end); + m_Bitmap->DrawRectangle(2, 36, m_Bitmap->Width() - 4, m_Bitmap->Height() - 8, clrWhite); + m_Bitmap->DrawRectangle(4, 38, (m_Bitmap->Width() - 6) * double(pos) / double(end), m_Bitmap->Height() - 10, clrGreen); + } else + { + m_Bitmap->DrawText(20, 30, presentTime, clrWhite, clrTransparent, font, 80, 29); + m_Bitmap->DrawText(100, 30, presentName, clrWhite, clrTransparent, font, m_Bitmap->Width() - 80, 29); + if (progress != 0) + { + m_Bitmap->DrawRectangle(6, 1, 14, m_Bitmap->Height() - 1, clrWhite); + m_Bitmap->DrawRectangle(7, 2, 13, (m_Bitmap->Height() - 1) * progress, clrGreen); + m_Bitmap->DrawRectangle(7, (m_Bitmap->Height() - 1) * progress, 13, m_Bitmap->Height() - 2, clrBlack); + } + } } if (m_Bitmap->Height() > 2*30) { @@ -122,29 +162,53 @@ void cOsdInfoWindow::Show() else { m_Palette[0] = 0xFF000000; - m_Palette[255] = 0x00FFFFFF; - m_Bitmap->DrawRectangle(0, 0, m_Bitmap->Width() - 1, m_Bitmap->Height() - 1, m_Palette[0]); + m_Palette[255] = 0xFFFFFFFF; + if (!m_Shown || Refresh || (!m_Message && lastMsg)) + { + m_Bitmap->DrawRectangle(0, 0, m_Bitmap->Width() - 1, m_Bitmap->Height() - 1, m_Palette[0]); + } for (int i = 0; i < 256; i++) m_Bitmap->SetColor(i, m_Palette[i]); - m_Osd->DrawBitmap(m_InfoX, m_InfoY, *m_Bitmap, 0, 0, true); - m_Osd->Flush(); - m_Palette[255] = 0xFFFFFFFF; - m_Bitmap->DrawRectangle(0, 0, m_Bitmap->Width() - 1, m_Bitmap->Height() - 1, m_Palette[0]); + if (!m_Shown || Refresh) + { + m_Osd->DrawBitmap(m_InfoX, m_InfoY, *m_Bitmap, 0, 0, true); + m_Osd->Flush(); + m_Palette[255] = 0xFFFFFFFF; + m_Bitmap->DrawRectangle(0, 0, m_Bitmap->Width() - 1, m_Bitmap->Height() - 1, m_Palette[0]); + } for (int i = 0; i < 256; i++) m_Bitmap->SetColor(i, m_Palette[i]); const cFont *font = cFont::GetFont(fontOsd); - m_Bitmap->DrawText(0, 0, channel, m_Palette[255], m_Palette[0], font, m_Bitmap->Width(), 29); + m_Bitmap->DrawText((cControl::Control() && cReplayControl::NowReplaying()) || m_Message ? 0 : 20, 0, channel, m_Palette[255], m_Palette[0], font, m_Bitmap->Width(), 29); if (m_Bitmap->Height() > 30) { - m_Bitmap->DrawText(0, 30, presentTime, m_Palette[255], m_Palette[0], font, 80, 29); - m_Bitmap->DrawText(80, 30, presentName, m_Palette[255], m_Palette[0], font, m_Bitmap->Width() - 80, 29); + if (cReplayControl::NowReplaying() && cControl::Control() && !m_Message) + { + m_Bitmap->DrawRectangle(0, 30, m_Bitmap->Width() - 1, m_Bitmap->Height() - 1, m_Palette[0]); + cControl::Control()->GetIndex(pos, end); + m_Bitmap->DrawRectangle(2, 36, m_Bitmap->Width() - 4, m_Bitmap->Height() - 8, m_Palette[255]); + m_Bitmap->DrawRectangle(4, 38, (m_Bitmap->Width() - 6) * double(pos) / double(end), m_Bitmap->Height() - 10, OsdPipSetup.ColorDepth == kDepthColor128var ? m_Palette[0] : clrGreen); + } else + { + m_Bitmap->DrawText(20, 30, presentTime, m_Palette[255], m_Palette[0], font, 80, 29); + m_Bitmap->DrawText(100, 30, presentName, m_Palette[255], m_Palette[0], font, m_Bitmap->Width() - 80, 29); + if (progress != 0) + { + m_Bitmap->DrawRectangle(6, 1, 14, m_Bitmap->Height() - 1, OsdPipSetup.ColorDepth == kDepthColor128var ? m_Palette[255] : m_Palette[0]); + m_Bitmap->DrawRectangle(7, 2, 13, (m_Bitmap->Height() - 1) * progress, OsdPipSetup.ColorDepth == kDepthColor128var ? m_Palette[255] : clrGreen); + m_Bitmap->DrawRectangle(7, (m_Bitmap->Height() - 1) * progress, 13, m_Bitmap->Height() - 2, OsdPipSetup.ColorDepth == kDepthColor128var ? m_Palette[0] : m_Palette[255]); + } + } } if (m_Bitmap->Height() > 2*30) { - m_Bitmap->DrawText(0, 2*30, followingTime, m_Palette[255], m_Palette[0], font, 80, 29); - m_Bitmap->DrawText(80, 2*30, followingName, m_Palette[255], m_Palette[0], font, m_Bitmap->Width() - 80, 29); + m_Bitmap->DrawText(20, 2*30, followingTime, m_Palette[255], m_Palette[0], font, 80, 29); + m_Bitmap->DrawText(100, 2*30, followingName, m_Palette[255], m_Palette[0], font, m_Bitmap->Width() - 80, 29); } } + tmp = NULL; + lastMsg = (m_Message != NULL); + m_Message = NULL; m_Osd->DrawBitmap(m_InfoX, m_InfoY, *m_Bitmap, 0, 0, true); m_Osd->Flush(); m_Shown = true; @@ -197,7 +261,7 @@ eOSState cOsdInfoWindow::ProcessKey(eKeys key) cChannel * channel = Channels.GetByNumber(m_Number); m_Channel = channel; m_WithInfo = false; - Show(); + Show(true); // Lets see if there can be any useful further input: int n = channel ? m_Number * 10 : 0; while (channel && (channel = Channels.Next(channel)) != NULL) @@ -311,7 +375,7 @@ eOSState cOsdInfoWindow::ProcessKey(eKeys key) { m_Group = -1; m_Number = 0; - m_Channel = Channels.Get(cDevice::CurrentChannel()); + m_Channel = Channels.GetByNumber(cDevice::CurrentChannel()); m_WithInfo = true; Hide(); } @@ -35,7 +35,7 @@ public: ~cOsdInfoWindow(); void SetMessage(const char * message); void SetChannel(const cChannel * channel); - void Show(); + void Show(bool Refresh = false); void Hide(); eOSState ProcessKey(eKeys key); @@ -13,7 +13,7 @@ #include <vdr/plugin.h> -static const char *VERSION = "0.0.10"; +static const char *VERSION = "0.0.10a"; static const char *DESCRIPTION = trNOOP("OSD Picture-in-Picture"); static const char *MAINMENUENTRY = trNOOP("Picture-in-Picture"); diff --git a/patches/osdpip-0.0.10a-fedora.diff b/patches/osdpip-0.0.10a-fedora.diff new file mode 100644 index 0000000..08672fb --- /dev/null +++ b/patches/osdpip-0.0.10a-fedora.diff @@ -0,0 +1,49 @@ +diff -Naur osdpip-0.0.10a/decoder.c osdpip-0.0.10a.new/decoder.c +--- osdpip-0.0.10a/decoder.c 2010-01-01 22:24:06.000000000 +0100 ++++ osdpip-0.0.10a.new/decoder.c 2010-01-02 14:11:31.914298872 +0100 +@@ -82,14 +82,14 @@ + context = sws_getContext(m_Context->width - (OsdPipSetup.CropLeft + OsdPipSetup.CropRight), + m_Context->height - (OsdPipSetup.CropTop + OsdPipSetup.CropBottom), + PIX_FMT_YUV420P, +- m_Width, m_Height, ConvertToRGB ? PIX_FMT_RGBA32 : PIX_FMT_YUV420P, ++ m_Width, m_Height, ConvertToRGB ? PIX_FMT_RGB32 : PIX_FMT_YUV420P, + SWS_LANCZOS, NULL, NULL, NULL); + if (!context) { + printf("Error initializing scale context.\n"); + return -1; + } + avpicture_fill((AVPicture *) m_PicResample, m_BufferResample, +- ConvertToRGB ? PIX_FMT_RGBA32 : PIX_FMT_YUV420P, ++ ConvertToRGB ? PIX_FMT_RGB32 : PIX_FMT_YUV420P, + m_Width, m_Height); + sws_scale(context, pic_crop.data, pic_crop.linesize, + 0, m_Context->height - (OsdPipSetup.CropTop + OsdPipSetup.CropBottom), +diff -Naur osdpip-0.0.10a/Makefile osdpip-0.0.10a.new/Makefile +--- osdpip-0.0.10a/Makefile 2010-01-01 22:24:06.000000000 +0100 ++++ osdpip-0.0.10a.new/Makefile 2010-01-02 14:12:36.561299250 +0100 +@@ -28,12 +28,12 @@ + ### The C++ compiler and options: + + CXX ?= g++ +-CXXFLAGS ?= -g -O2 -Wall -Woverloaded-virtual ++CXXFLAGS ?= -g -O2 -Wall -Woverloaded-virtual -fPIC + + ### The directory environment: + + DVBDIR = ../../../../DVB +-FFMDIR = ../../../../ffmpeg-0.4.8 ++FFMDIR = /usr/include/ffmpeg + VDRDIR = ../../.. + LIBDIR = ../../lib + TMPDIR = /tmp +@@ -53,8 +53,8 @@ + + ### Includes and Defines (add further entries here): + +-INCLUDES += -I$(VDRDIR)/include -I$(DVBDIR)/include -I. -I$(FFMDIR)/libavcodec +-LIBS = -L$(FFMDIR)/libavcodec -lavcodec ++INCLUDES += -I$(VDRDIR)/include -I$(DVBDIR)/include -I. -I$(FFMDIR) ++LIBS = -L$(FFMDIR) -lavcodec + DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"' + DEFINES += -D_GNU_SOURCE + diff --git a/patches/osdpip__decoder_c.diff b/patches/osdpip__decoder_c.diff new file mode 100644 index 0000000..f116978 --- /dev/null +++ b/patches/osdpip__decoder_c.diff @@ -0,0 +1,20 @@ +diff -Nur /usr/local/src/vdr-1.7.10/PLUGINS/src/osdpip-0.0.10a/decoder.c.orig /usr/local/src/vdr-1.7.10/PLUGINS/src/osdpip-0.0.10a/decoder.c +--- /usr/local/src/vdr-1.7.10/PLUGINS/src/osdpip-0.0.10a/decoder.c.orig 2010-01-02 11:36:19.000000000 +0100 ++++ /usr/local/src/vdr-1.7.10/PLUGINS/src/osdpip-0.0.10a/decoder.c 2010-01-02 11:37:12.000000000 +0100 +@@ -82,14 +82,14 @@ + context = sws_getContext(m_Context->width - (OsdPipSetup.CropLeft + OsdPipSetup.CropRight), + m_Context->height - (OsdPipSetup.CropTop + OsdPipSetup.CropBottom), + PIX_FMT_YUV420P, +- m_Width, m_Height, ConvertToRGB ? PIX_FMT_RGBA32 : PIX_FMT_YUV420P, ++ m_Width, m_Height, ConvertToRGB ? PIX_FMT_RGB32 : PIX_FMT_YUV420P, + SWS_LANCZOS, NULL, NULL, NULL); + if (!context) { + printf("Error initializing scale context.\n"); + return -1; + } + avpicture_fill((AVPicture *) m_PicResample, m_BufferResample, +- ConvertToRGB ? PIX_FMT_RGBA32 : PIX_FMT_YUV420P, ++ ConvertToRGB ? PIX_FMT_RGB32 : PIX_FMT_YUV420P, + m_Width, m_Height); + sws_scale(context, pic_crop.data, pic_crop.linesize, + 0, m_Context->height - (OsdPipSetup.CropTop + OsdPipSetup.CropBottom), diff --git a/po/ca_ES.po b/po/ca_ES.po index 1bdbf46..8316ad2 100644 --- a/po/ca_ES.po +++ b/po/ca_ES.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.5.7\n" "Report-Msgid-Bugs-To: <andreas.regel@powarman.de>\n" -"POT-Creation-Date: 2008-05-03 21:57+0200\n" +"POT-Creation-Date: 2009-01-02 19:39+0100\n" "PO-Revision-Date: 2008-04-27 18:00+0200\n" "Last-Translator: Jordi Vilà <jvila@tinet.org>\n" "Language-Team: <vdr@linuxtv.org>\n" @@ -17,13 +17,16 @@ msgstr "" "Content-Type: text/plain; charset=ISO-8859-1\n" "Content-Transfer-Encoding: 8bit\n" +msgid "Zapping mode" +msgstr "" + msgid "Move mode" msgstr "" msgid "Normal mode" msgstr "" -msgid "Zapping mode" +msgid "Replay mode" msgstr "" msgid "OSD Picture-in-Picture" diff --git a/po/cs_CZ.po b/po/cs_CZ.po index b2c3f40..f7f8525 100644 --- a/po/cs_CZ.po +++ b/po/cs_CZ.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.5.7\n" "Report-Msgid-Bugs-To: <andreas.regel@powarman.de>\n" -"POT-Creation-Date: 2008-05-03 21:57+0200\n" +"POT-Creation-Date: 2009-01-02 19:39+0100\n" "PO-Revision-Date: 2008-04-27 18:00+0200\n" "Last-Translator: Vladimír Bárta <vladimir.barta@k2atmitec.cz>\n" "Language-Team: <vdr@linuxtv.org>\n" @@ -15,13 +15,16 @@ msgstr "" "Content-Type: text/plain; charset=ISO-8859-2\n" "Content-Transfer-Encoding: 8bit\n" +msgid "Zapping mode" +msgstr "" + msgid "Move mode" msgstr "" msgid "Normal mode" msgstr "" -msgid "Zapping mode" +msgid "Replay mode" msgstr "" msgid "OSD Picture-in-Picture" diff --git a/po/da_DK.po b/po/da_DK.po index 79fb6df..b468406 100644 --- a/po/da_DK.po +++ b/po/da_DK.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.5.7\n" "Report-Msgid-Bugs-To: <andreas.regel@powarman.de>\n" -"POT-Creation-Date: 2008-05-03 21:57+0200\n" +"POT-Creation-Date: 2009-01-02 19:39+0100\n" "PO-Revision-Date: 2008-04-27 18:00+0200\n" "Last-Translator: Mogens Elneff <mogens@elneff.dk>\n" "Language-Team: <vdr@linuxtv.org>\n" @@ -15,13 +15,16 @@ msgstr "" "Content-Type: text/plain; charset=ISO-8859-15\n" "Content-Transfer-Encoding: 8bit\n" +msgid "Zapping mode" +msgstr "" + msgid "Move mode" msgstr "" msgid "Normal mode" msgstr "" -msgid "Zapping mode" +msgid "Replay mode" msgstr "" msgid "OSD Picture-in-Picture" diff --git a/po/de_DE.po b/po/de_DE.po index 1a9e1d3..a030355 100644 --- a/po/de_DE.po +++ b/po/de_DE.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.5.7\n" "Report-Msgid-Bugs-To: <andreas.regel@powarman.de>\n" -"POT-Creation-Date: 2008-05-03 21:57+0200\n" +"POT-Creation-Date: 2009-01-02 19:39+0100\n" "PO-Revision-Date: 2008-04-27 18:00+0200\n" "Last-Translator: Klaus Schmidinger <kls@cadsoft.de>\n" "Language-Team: <vdr@linuxtv.org>\n" @@ -15,14 +15,17 @@ msgstr "" "Content-Type: text/plain; charset=ISO-8859-15\n" "Content-Transfer-Encoding: 8bit\n" +msgid "Zapping mode" +msgstr "Umschaltmodus" + msgid "Move mode" msgstr "Bewegungsmodus" msgid "Normal mode" msgstr "Normaler Modus" -msgid "Zapping mode" -msgstr "Umschaltmodus" +msgid "Replay mode" +msgstr "Wiedergabemodus" msgid "OSD Picture-in-Picture" msgstr "OSD Bild-in-Bild" diff --git a/po/el_GR.po b/po/el_GR.po index 5205e12..72ffa64 100644 --- a/po/el_GR.po +++ b/po/el_GR.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.5.7\n" "Report-Msgid-Bugs-To: <andreas.regel@powarman.de>\n" -"POT-Creation-Date: 2008-05-03 21:57+0200\n" +"POT-Creation-Date: 2009-01-02 19:39+0100\n" "PO-Revision-Date: 2008-04-27 18:00+0200\n" "Last-Translator: Dimitrios Dimitrakos <mail@dimitrios.de>\n" "Language-Team: <vdr@linuxtv.org>\n" @@ -15,13 +15,16 @@ msgstr "" "Content-Type: text/plain; charset=ISO-8859-7\n" "Content-Transfer-Encoding: 8bit\n" +msgid "Zapping mode" +msgstr "" + msgid "Move mode" msgstr "" msgid "Normal mode" msgstr "" -msgid "Zapping mode" +msgid "Replay mode" msgstr "" msgid "OSD Picture-in-Picture" diff --git a/po/es_ES.po b/po/es_ES.po index 4a13bc0..f797abd 100644 --- a/po/es_ES.po +++ b/po/es_ES.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.5.7\n" "Report-Msgid-Bugs-To: <andreas.regel@powarman.de>\n" -"POT-Creation-Date: 2008-05-03 21:57+0200\n" +"POT-Creation-Date: 2009-01-02 19:39+0100\n" "PO-Revision-Date: 2008-04-27 18:00+0200\n" "Last-Translator: Ruben Nunez Francisco <ruben.nunez@tang-it.com>\n" "Language-Team: <vdr@linuxtv.org>\n" @@ -15,13 +15,16 @@ msgstr "" "Content-Type: text/plain; charset=ISO-8859-15\n" "Content-Transfer-Encoding: 8bit\n" +msgid "Zapping mode" +msgstr "" + msgid "Move mode" msgstr "" msgid "Normal mode" msgstr "" -msgid "Zapping mode" +msgid "Replay mode" msgstr "" msgid "OSD Picture-in-Picture" diff --git a/po/et_EE.po b/po/et_EE.po index 96d92ce..ad07902 100644 --- a/po/et_EE.po +++ b/po/et_EE.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.5.7\n" "Report-Msgid-Bugs-To: <andreas.regel@powarman.de>\n" -"POT-Creation-Date: 2008-05-03 21:57+0200\n" +"POT-Creation-Date: 2009-01-02 19:39+0100\n" "PO-Revision-Date: 2008-04-27 18:00+0200\n" "Last-Translator: Arthur Konovalov <kasjas@hot.ee>\n" "Language-Team: <vdr@linuxtv.org>\n" @@ -15,13 +15,16 @@ msgstr "" "Content-Type: text/plain; charset=ISO-8859-13\n" "Content-Transfer-Encoding: 8bit\n" +msgid "Zapping mode" +msgstr "" + msgid "Move mode" msgstr "" msgid "Normal mode" msgstr "" -msgid "Zapping mode" +msgid "Replay mode" msgstr "" msgid "OSD Picture-in-Picture" diff --git a/po/fi_FI.po b/po/fi_FI.po index 1cce199..45d39c7 100644 --- a/po/fi_FI.po +++ b/po/fi_FI.po @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.5.7\n" "Report-Msgid-Bugs-To: <andreas.regel@powarman.de>\n" -"POT-Creation-Date: 2008-05-03 21:57+0200\n" +"POT-Creation-Date: 2009-01-02 19:39+0100\n" "PO-Revision-Date: 2008-04-27 18:00+0200\n" "Last-Translator: Rolf Ahrenberg <rahrenbe@cc.hut.fi>\n" "Language-Team: <vdr@linuxtv.org>\n" @@ -18,13 +18,16 @@ msgstr "" "Content-Type: text/plain; charset=ISO-8859-15\n" "Content-Transfer-Encoding: 8bit\n" +msgid "Zapping mode" +msgstr "" + msgid "Move mode" msgstr "Siirtotoiminto" msgid "Normal mode" msgstr "Normaalitoiminto" -msgid "Zapping mode" +msgid "Replay mode" msgstr "" msgid "OSD Picture-in-Picture" diff --git a/po/fr_FR.po b/po/fr_FR.po index 638825e..0a44c48 100644 --- a/po/fr_FR.po +++ b/po/fr_FR.po @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.5.7\n" "Report-Msgid-Bugs-To: <andreas.regel@powarman.de>\n" -"POT-Creation-Date: 2008-05-03 21:57+0200\n" +"POT-Creation-Date: 2009-01-02 19:39+0100\n" "PO-Revision-Date: 2008-04-27 18:00+0200\n" "Last-Translator: Nicolas Huillard <nhuillard@e-dition.fr>\n" "Language-Team: <vdr@linuxtv.org>\n" @@ -18,13 +18,16 @@ msgstr "" "Content-Type: text/plain; charset=ISO-8859-1\n" "Content-Transfer-Encoding: 8bit\n" +msgid "Zapping mode" +msgstr "" + msgid "Move mode" msgstr "Mode mouvement" msgid "Normal mode" msgstr "Mode normale" -msgid "Zapping mode" +msgid "Replay mode" msgstr "" msgid "OSD Picture-in-Picture" diff --git a/po/hr_HR.po b/po/hr_HR.po index a8de102..6ed0cfc 100644 --- a/po/hr_HR.po +++ b/po/hr_HR.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.5.7\n" "Report-Msgid-Bugs-To: <andreas.regel@powarman.de>\n" -"POT-Creation-Date: 2008-05-03 21:57+0200\n" +"POT-Creation-Date: 2009-01-02 19:39+0100\n" "PO-Revision-Date: 2008-04-27 18:00+0200\n" "Last-Translator: Drazen Dupor <drazen.dupor@dupor.com>\n" "Language-Team: <vdr@linuxtv.org>\n" @@ -16,13 +16,16 @@ msgstr "" "Content-Type: text/plain; charset=ISO-8859-2\n" "Content-Transfer-Encoding: 8bit\n" +msgid "Zapping mode" +msgstr "" + msgid "Move mode" msgstr "" msgid "Normal mode" msgstr "" -msgid "Zapping mode" +msgid "Replay mode" msgstr "" msgid "OSD Picture-in-Picture" diff --git a/po/hu_HU.po b/po/hu_HU.po index d250094..04fca2a 100644 --- a/po/hu_HU.po +++ b/po/hu_HU.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.5.7\n" "Report-Msgid-Bugs-To: <andreas.regel@powarman.de>\n" -"POT-Creation-Date: 2008-05-03 21:57+0200\n" +"POT-Creation-Date: 2009-01-02 19:39+0100\n" "PO-Revision-Date: 2008-04-27 18:00+0200\n" "Last-Translator: Istvan Koenigsberger <istvnko@hotmail.com>, Guido Josten <guido.josten@t-online.de>\n" "Language-Team: <vdr@linuxtv.org>\n" @@ -16,13 +16,16 @@ msgstr "" "Content-Type: text/plain; charset=ISO-8859-2\n" "Content-Transfer-Encoding: 8bit\n" +msgid "Zapping mode" +msgstr "" + msgid "Move mode" msgstr "" msgid "Normal mode" msgstr "" -msgid "Zapping mode" +msgid "Replay mode" msgstr "" msgid "OSD Picture-in-Picture" diff --git a/po/it_IT.po b/po/it_IT.po index 9c8182b..d6ed295 100644..100755 --- a/po/it_IT.po +++ b/po/it_IT.po @@ -10,114 +10,121 @@ msgstr "" "Project-Id-Version: VDR 1.5.7\n" "Report-Msgid-Bugs-To: <andreas.regel@powarman.de>\n" "POT-Creation-Date: 2008-05-03 21:57+0200\n" -"PO-Revision-Date: 2008-04-27 18:00+0200\n" -"Last-Translator: Sean Carlos <seanc@libero.it>\n" -"Language-Team: <vdr@linuxtv.org>\n" +"PO-Revision-Date: 2010-01-13 00:15+0100\n" +"Last-Translator: Diego Pierotto <vdr-italian@tiscali.it>\n" +"Language-Team: <vdr@linuxtv.org>\n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=ISO-8859-15\n" +"Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Italian\n" +"X-Poedit-Country: ITALY\n" +"X-Poedit-SourceCharset: utf-8\n" + +msgid "Zapping mode" +msgstr "Modalità zapping" msgid "Move mode" -msgstr "" +msgstr "Modalità spostamento" msgid "Normal mode" -msgstr "" +msgstr "Modalità normale" -msgid "Zapping mode" -msgstr "" +msgid "Replay mode" +msgstr "Modalità  riproduzione" msgid "OSD Picture-in-Picture" -msgstr "" +msgstr "Picture-in-Picture con OSD" msgid "Picture-in-Picture" -msgstr "" +msgstr "Picture-in-Picture" msgid "Greyscale (16)" -msgstr "" +msgstr "Liv. grigio (16)" msgid "Greyscale (256)" -msgstr "" +msgstr "Liv. grigio (256)" msgid "Color (256, fixed)" -msgstr "" +msgstr "Colore (256, fisso)" msgid "Color (128, variable)" -msgstr "" +msgstr "Colore (128, variabile)" msgid "Color (256, dithered)" -msgstr "" +msgstr "Colore (256, distorto)" msgid "channel only" -msgstr "" +msgstr "solo canale" msgid "simple" -msgstr "" +msgstr "semplice" msgid "complete" -msgstr "" +msgstr "completa" msgid "top left" -msgstr "" +msgstr "in alto a sinistra" msgid "top right" -msgstr "" +msgstr "in alto a destra" msgid "bottom left" -msgstr "" +msgstr "in basso a sinistra" msgid "bottom right" -msgstr "" +msgstr "in basso a destra" msgid "automatic" -msgstr "" +msgstr "automatico" msgid "none" -msgstr "" +msgstr "nessuno" msgid "1 frame" -msgstr "" +msgstr "1 frame" msgid "2 frames" -msgstr "" +msgstr "2 frame" msgid "X Position" -msgstr "" +msgstr "Posizione X" msgid "Y Position" -msgstr "" +msgstr "Posizione Y" msgid "Crop left" -msgstr "" +msgstr "Ritaglio sinistro" msgid "Crop right" -msgstr "" +msgstr "Ritaglio destro" msgid "Crop at top" -msgstr "" +msgstr "Ritaglio superiore" msgid "Crop at bottom" -msgstr "" +msgstr "Ritaglio inferiore" msgid "Color depth" -msgstr "" +msgstr "Profondità  colore" msgid "Size" -msgstr "" +msgstr "Dimensione" msgid "Frames to display" -msgstr "" +msgstr "Frame da visualizzare" msgid "Drop frames" -msgstr "" +msgstr "Elimina frame" msgid "Swap FFMPEG output" -msgstr "" +msgstr "Cambia visualizz. FFMPEG" msgid "Show info window" -msgstr "" +msgstr "Mostra info finestre" msgid "Info window width" -msgstr "" +msgstr "Info larghezza finestra" msgid "Info window position" -msgstr "" +msgstr "Info posizione finestra" + diff --git a/po/nl_NL.po b/po/nl_NL.po index 3fef0f8..45b9aaa 100644 --- a/po/nl_NL.po +++ b/po/nl_NL.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.5.7\n" "Report-Msgid-Bugs-To: <andreas.regel@powarman.de>\n" -"POT-Creation-Date: 2008-05-03 21:57+0200\n" +"POT-Creation-Date: 2009-01-02 19:39+0100\n" "PO-Revision-Date: 2008-04-27 18:00+0200\n" "Last-Translator: Maarten Wisse <Maarten.Wisse@urz.uni-hd.de>\n" "Language-Team: <vdr@linuxtv.org>\n" @@ -17,13 +17,16 @@ msgstr "" "Content-Type: text/plain; charset=ISO-8859-15\n" "Content-Transfer-Encoding: 8bit\n" +msgid "Zapping mode" +msgstr "" + msgid "Move mode" msgstr "" msgid "Normal mode" msgstr "" -msgid "Zapping mode" +msgid "Replay mode" msgstr "" msgid "OSD Picture-in-Picture" diff --git a/po/nn_NO.po b/po/nn_NO.po index 60f33d5..3f69191 100644 --- a/po/nn_NO.po +++ b/po/nn_NO.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.5.7\n" "Report-Msgid-Bugs-To: <andreas.regel@powarman.de>\n" -"POT-Creation-Date: 2008-05-03 21:57+0200\n" +"POT-Creation-Date: 2009-01-02 19:39+0100\n" "PO-Revision-Date: 2008-04-27 18:00+0200\n" "Last-Translator: Truls Slevigen <truls@slevigen.no>\n" "Language-Team: <vdr@linuxtv.org>\n" @@ -16,13 +16,16 @@ msgstr "" "Content-Type: text/plain; charset=ISO-8859-1\n" "Content-Transfer-Encoding: 8bit\n" +msgid "Zapping mode" +msgstr "" + msgid "Move mode" msgstr "" msgid "Normal mode" msgstr "" -msgid "Zapping mode" +msgid "Replay mode" msgstr "" msgid "OSD Picture-in-Picture" diff --git a/po/pl_PL.po b/po/pl_PL.po index 61d7d1b..164e98d 100644 --- a/po/pl_PL.po +++ b/po/pl_PL.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.5.7\n" "Report-Msgid-Bugs-To: <andreas.regel@powarman.de>\n" -"POT-Creation-Date: 2008-05-03 21:57+0200\n" +"POT-Creation-Date: 2009-01-02 19:39+0100\n" "PO-Revision-Date: 2008-04-27 18:00+0200\n" "Last-Translator: Michael Rakowski <mrak@gmx.de>\n" "Language-Team: <vdr@linuxtv.org>\n" @@ -15,13 +15,16 @@ msgstr "" "Content-Type: text/plain; charset=ISO-8859-2\n" "Content-Transfer-Encoding: 8bit\n" +msgid "Zapping mode" +msgstr "" + msgid "Move mode" msgstr "" msgid "Normal mode" msgstr "" -msgid "Zapping mode" +msgid "Replay mode" msgstr "" msgid "OSD Picture-in-Picture" diff --git a/po/pt_PT.po b/po/pt_PT.po index 92522c7..a7e903a 100644 --- a/po/pt_PT.po +++ b/po/pt_PT.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.5.7\n" "Report-Msgid-Bugs-To: <andreas.regel@powarman.de>\n" -"POT-Creation-Date: 2008-05-03 21:57+0200\n" +"POT-Creation-Date: 2009-01-02 19:39+0100\n" "PO-Revision-Date: 2008-04-27 18:00+0200\n" "Last-Translator: Paulo Lopes <pmml@netvita.pt>\n" "Language-Team: <vdr@linuxtv.org>\n" @@ -15,13 +15,16 @@ msgstr "" "Content-Type: text/plain; charset=ISO-8859-1\n" "Content-Transfer-Encoding: 8bit\n" +msgid "Zapping mode" +msgstr "" + msgid "Move mode" msgstr "" msgid "Normal mode" msgstr "" -msgid "Zapping mode" +msgid "Replay mode" msgstr "" msgid "OSD Picture-in-Picture" diff --git a/po/ro_RO.po b/po/ro_RO.po index f74dd22..a9a0fa6 100644 --- a/po/ro_RO.po +++ b/po/ro_RO.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.5.7\n" "Report-Msgid-Bugs-To: <andreas.regel@powarman.de>\n" -"POT-Creation-Date: 2008-05-03 21:57+0200\n" +"POT-Creation-Date: 2009-01-02 19:39+0100\n" "PO-Revision-Date: 2008-04-27 18:00+0200\n" "Last-Translator: Lucian Muresan <lucianm@users.sourceforge.net>\n" "Language-Team: <vdr@linuxtv.org>\n" @@ -16,13 +16,16 @@ msgstr "" "Content-Type: text/plain; charset=ISO-8859-2\n" "Content-Transfer-Encoding: 8bit\n" +msgid "Zapping mode" +msgstr "" + msgid "Move mode" msgstr "" msgid "Normal mode" msgstr "" -msgid "Zapping mode" +msgid "Replay mode" msgstr "" msgid "OSD Picture-in-Picture" diff --git a/po/ru_RU.po b/po/ru_RU.po index 8723fd1..15cc597 100644 --- a/po/ru_RU.po +++ b/po/ru_RU.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.5.7\n" "Report-Msgid-Bugs-To: <andreas.regel@powarman.de>\n" -"POT-Creation-Date: 2008-05-03 21:57+0200\n" +"POT-Creation-Date: 2009-01-02 19:39+0100\n" "PO-Revision-Date: 2008-04-27 18:00+0200\n" "Last-Translator: Vyacheslav Dikonov <sdiconov@mail.ru>\n" "Language-Team: <vdr@linuxtv.org>\n" @@ -15,13 +15,16 @@ msgstr "" "Content-Type: text/plain; charset=ISO-8859-5\n" "Content-Transfer-Encoding: 8bit\n" +msgid "Zapping mode" +msgstr "" + msgid "Move mode" msgstr "" msgid "Normal mode" msgstr "" -msgid "Zapping mode" +msgid "Replay mode" msgstr "" msgid "OSD Picture-in-Picture" diff --git a/po/sl_SI.po b/po/sl_SI.po index 88d3894..35d6969 100644 --- a/po/sl_SI.po +++ b/po/sl_SI.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.5.7\n" "Report-Msgid-Bugs-To: <andreas.regel@powarman.de>\n" -"POT-Creation-Date: 2008-05-03 21:57+0200\n" +"POT-Creation-Date: 2009-01-02 19:39+0100\n" "PO-Revision-Date: 2008-04-27 18:00+0200\n" "Last-Translator: Matjaz Thaler <matjaz.thaler@guest.arnes.si>\n" "Language-Team: <vdr@linuxtv.org>\n" @@ -16,13 +16,16 @@ msgstr "" "Content-Type: text/plain; charset=ISO-8859-2\n" "Content-Transfer-Encoding: 8bit\n" +msgid "Zapping mode" +msgstr "" + msgid "Move mode" msgstr "" msgid "Normal mode" msgstr "" -msgid "Zapping mode" +msgid "Replay mode" msgstr "" msgid "OSD Picture-in-Picture" diff --git a/po/sv_SE.po b/po/sv_SE.po index 39b311f..5a4a5d5 100644 --- a/po/sv_SE.po +++ b/po/sv_SE.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.5.7\n" "Report-Msgid-Bugs-To: <andreas.regel@powarman.de>\n" -"POT-Creation-Date: 2008-05-03 21:57+0200\n" +"POT-Creation-Date: 2009-01-02 19:39+0100\n" "PO-Revision-Date: 2008-04-27 18:00+0200\n" "Last-Translator: Tomas Prybil <tomas@prybil.se>\n" "Language-Team: <vdr@linuxtv.org>\n" @@ -16,13 +16,16 @@ msgstr "" "Content-Type: text/plain; charset=ISO-8859-1\n" "Content-Transfer-Encoding: 8bit\n" +msgid "Zapping mode" +msgstr "" + msgid "Move mode" msgstr "" msgid "Normal mode" msgstr "" -msgid "Zapping mode" +msgid "Replay mode" msgstr "" msgid "OSD Picture-in-Picture" diff --git a/po/tr_TR.po b/po/tr_TR.po index 505e145..56f6cc3 100644 --- a/po/tr_TR.po +++ b/po/tr_TR.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: VDR 1.5.7\n" "Report-Msgid-Bugs-To: <andreas.regel@powarman.de>\n" -"POT-Creation-Date: 2008-05-03 21:57+0200\n" +"POT-Creation-Date: 2009-01-02 19:39+0100\n" "PO-Revision-Date: 2008-04-27 18:00+0200\n" "Last-Translator: Oktay Yolgeçen <oktay_73@yahoo.de>\n" "Language-Team: <vdr@linuxtv.org>\n" @@ -15,13 +15,16 @@ msgstr "" "Content-Type: text/plain; charset=ISO-8859-9\n" "Content-Transfer-Encoding: 8bit\n" +msgid "Zapping mode" +msgstr "" + msgid "Move mode" msgstr "" msgid "Normal mode" msgstr "" -msgid "Zapping mode" +msgid "Replay mode" msgstr "" msgid "OSD Picture-in-Picture" @@ -7,9 +7,14 @@ #include "receiver.h" #include "pes.h" #include "setup.h" +#if VDRVERSNUM > 10703 +#include "remux.h" +#endif #include <vdr/channels.h> +#if VDRVERSNUM <= 10703 #include <vdr/remux.h> +#endif #include <vdr/ringbuffer.h> cOsdPipReceiver::cOsdPipReceiver(const cChannel *Channel, @@ -24,7 +29,11 @@ cOsdPipReceiver::cOsdPipReceiver(const cChannel *Channel, m_TSBuffer = new cRingBufferLinear(MEGABYTE(3), TS_SIZE * 2, true); m_TSBuffer->SetTimeouts(0, 100); m_ESBuffer = ESBuffer; +#if VDRVERSNUM > 10703 + m_Remux = new cRemuxPIP(Channel->Vpid(), NULL, NULL, NULL, true); +#else m_Remux = new cRemux(Channel->Vpid(), NULL, NULL, NULL, true); +#endif m_Active = false; } @@ -12,13 +12,21 @@ class cRingBufferLinear; class cRingBufferFrame; +#if VDRVERSNUM > 10703 +class cRemuxPIP; +#else class cRemux; +#endif class cOsdPipReceiver: public cReceiver, public cThread { private: cRingBufferLinear *m_TSBuffer; cRingBufferFrame *m_ESBuffer; +#if VDRVERSNUM > 10703 + cRemuxPIP *m_Remux; +#else cRemux *m_Remux; +#endif bool m_Active; @@ -0,0 +1,2228 @@ +/* + * remux.c: A streaming MPEG2 remultiplexer + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * The parts of this code that implement cTS2PES have been taken from + * the Linux DVB driver's 'tuxplayer' example and were rewritten to suit + * VDR's needs. + * + * The cRepacker family's code was originally written by Reinhard Nissl <rnissl@gmx.de>, + * and adapted to the VDR coding style by Klaus.Schmidinger@cadsoft.de. + * + * $Id: remux.c 1.64 2007/11/25 13:56:03 kls Exp $ + */ + +#include "remux.h" +#include <stdlib.h> +#include <vdr/channels.h> +#include <vdr/shutdown.h> +#include <vdr/tools.h> + +ePesHeader AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader) +{ + if (Count < 7) + return phNeedMoreData; // too short + + if ((Data[6] & 0xC0) == 0x80) { // MPEG 2 + if (Count < 9) + return phNeedMoreData; // too short + + PesPayloadOffset = 6 + 3 + Data[8]; + if (Count < PesPayloadOffset) + return phNeedMoreData; // too short + + if (ContinuationHeader) + *ContinuationHeader = ((Data[6] == 0x80) && !Data[7] && !Data[8]); + + return phMPEG2; // MPEG 2 + } + + // check for MPEG 1 ... + PesPayloadOffset = 6; + + // skip up to 16 stuffing bytes + for (int i = 0; i < 16; i++) { + if (Data[PesPayloadOffset] != 0xFF) + break; + + if (Count <= ++PesPayloadOffset) + return phNeedMoreData; // too short + } + + // skip STD_buffer_scale/size + if ((Data[PesPayloadOffset] & 0xC0) == 0x40) { + PesPayloadOffset += 2; + + if (Count <= PesPayloadOffset) + return phNeedMoreData; // too short + } + + if (ContinuationHeader) + *ContinuationHeader = false; + + if ((Data[PesPayloadOffset] & 0xF0) == 0x20) { + // skip PTS only + PesPayloadOffset += 5; + } + else if ((Data[PesPayloadOffset] & 0xF0) == 0x30) { + // skip PTS and DTS + PesPayloadOffset += 10; + } + else if (Data[PesPayloadOffset] == 0x0F) { + // continuation header + PesPayloadOffset++; + + if (ContinuationHeader) + *ContinuationHeader = true; + } + else + return phInvalid; // unknown + + if (Count < PesPayloadOffset) + return phNeedMoreData; // too short + + return phMPEG1; // MPEG 1 +} + +// --- cRepacker ------------------------------------------------------------- + +#define MIN_LOG_INTERVAL 10 // min. # of seconds between two consecutive log messages of a cRepacker +#define LOG(a...) (LogAllowed() && (esyslog(a), true)) + +class cRepacker { +protected: + bool initiallySyncing; + int maxPacketSize; + uint8_t subStreamId; + time_t lastLog; + int suppressedLogMessages; + bool LogAllowed(void); + void DroppedData(const char *Reason, int Count) { LOG("%s (dropped %d bytes)", Reason, Count); } +public: + static int Put(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count, int CapacityNeeded); + cRepacker(void); + virtual ~cRepacker() {} + virtual void Reset(void) { initiallySyncing = true; } + virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) = 0; + virtual int BreakAt(const uchar *Data, int Count) = 0; + virtual int QuerySnoopSize(void) { return 0; } + void SetMaxPacketSize(int MaxPacketSize) { maxPacketSize = MaxPacketSize; } + void SetSubStreamId(uint8_t SubStreamId) { subStreamId = SubStreamId; } + }; + +cRepacker::cRepacker(void) +{ + initiallySyncing = true; + maxPacketSize = 6 + 65535; + subStreamId = 0; + suppressedLogMessages = 0;; + lastLog = 0; +} + +bool cRepacker::LogAllowed(void) +{ + bool Allowed = time(NULL) - lastLog >= MIN_LOG_INTERVAL; + lastLog = time(NULL); + if (Allowed) { + if (suppressedLogMessages) { + esyslog("%d cRepacker messages suppressed", suppressedLogMessages); + suppressedLogMessages = 0; + } + } + else + suppressedLogMessages++; + return Allowed; +} + +int cRepacker::Put(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count, int CapacityNeeded) +{ + if (CapacityNeeded >= Count && ResultBuffer->Free() < CapacityNeeded) { + esyslog("ERROR: possible result buffer overflow, dropped %d out of %d byte", CapacityNeeded, CapacityNeeded); + return 0; + } + int n = ResultBuffer->Put(Data, Count); + if (n != Count) + esyslog("ERROR: result buffer overflow, dropped %d out of %d byte", Count - n, Count); + return n; +} + +// --- cCommonRepacker ------------------------------------------------------- + +class cCommonRepacker : public cRepacker { +protected: + int skippedBytes; + int packetTodo; + uchar fragmentData[6 + 65535 + 3]; + int fragmentLen; + uchar pesHeader[6 + 3 + 255 + 3]; + int pesHeaderLen; + uchar pesHeaderBackup[6 + 3 + 255]; + int pesHeaderBackupLen; + uint32_t scanner; + uint32_t localScanner; + int localStart; + bool PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); + virtual int QuerySnoopSize() { return 4; } + virtual void Reset(void); + }; + +void cCommonRepacker::Reset(void) +{ + cRepacker::Reset(); + skippedBytes = 0; + packetTodo = 0; + fragmentLen = 0; + pesHeaderLen = 0; + pesHeaderBackupLen = 0; + localStart = -1; +} + +bool cCommonRepacker::PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) +{ + // enter packet length into PES header ... + if (fragmentLen > 0) { // ... which is contained in the fragment buffer + // determine PES packet payload + int PacketLen = fragmentLen + Count - 6; + fragmentData[ 4 ] = PacketLen >> 8; + fragmentData[ 5 ] = PacketLen & 0xFF; + // just skip packets with no payload + int PesPayloadOffset = 0; + if (AnalyzePesHeader(fragmentData, fragmentLen, PesPayloadOffset) <= phInvalid) + LOG("cCommonRepacker: invalid PES packet encountered in fragment buffer!"); + else if (6 + PacketLen <= PesPayloadOffset) { + fragmentLen = 0; + return true; // skip empty packet + } + // amount of data to put into result buffer: a negative Count value means + // to strip off any partially contained start code. + int Bite = fragmentLen + (Count >= 0 ? 0 : Count); + // put data into result buffer + int n = Put(ResultBuffer, fragmentData, Bite, 6 + PacketLen); + fragmentLen = 0; + if (n != Bite) + return false; + } + else if (pesHeaderLen > 0) { // ... which is contained in the PES header buffer + int PacketLen = pesHeaderLen + Count - 6; + pesHeader[ 4 ] = PacketLen >> 8; + pesHeader[ 5 ] = PacketLen & 0xFF; + // just skip packets with no payload + int PesPayloadOffset = 0; + if (AnalyzePesHeader(pesHeader, pesHeaderLen, PesPayloadOffset) <= phInvalid) + LOG("cCommonRepacker: invalid PES packet encountered in header buffer!"); + else if (6 + PacketLen <= PesPayloadOffset) { + pesHeaderLen = 0; + return true; // skip empty packet + } + // amount of data to put into result buffer: a negative Count value means + // to strip off any partially contained start code. + int Bite = pesHeaderLen + (Count >= 0 ? 0 : Count); + // put data into result buffer + int n = Put(ResultBuffer, pesHeader, Bite, 6 + PacketLen); + pesHeaderLen = 0; + if (n != Bite) + return false; + } + // append further payload + if (Count > 0) { + // amount of data to put into result buffer + int Bite = Count; + // put data into result buffer + int n = Put(ResultBuffer, Data, Bite, Bite); + if (n != Bite) + return false; + } + // we did it ;-) + return true; +} + +// --- cVideoRepacker -------------------------------------------------------- + +class cVideoRepacker : public cCommonRepacker { +private: + enum eState { + syncing, + findPicture, + scanPicture + }; + int state; + void HandleStartCode(const uchar *const Data, cRingBufferLinear *const ResultBuffer, const uchar *&Payload, const uchar StreamID, const ePesHeader MpegLevel); + inline bool ScanDataForStartCodeSlow(const uchar *const Data); + inline bool ScanDataForStartCodeFast(const uchar *&Data, const uchar *Limit); + inline bool ScanDataForStartCode(const uchar *&Data, int &Done, int &Todo); + inline void AdjustCounters(const int Delta, int &Done, int &Todo); + inline bool ScanForEndOfPictureSlow(const uchar *&Data); + inline bool ScanForEndOfPictureFast(const uchar *&Data, const uchar *Limit); + inline bool ScanForEndOfPicture(const uchar *&Data, const uchar *Limit); +public: + cVideoRepacker(void); + virtual void Reset(void); + virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); + virtual int BreakAt(const uchar *Data, int Count); + }; + +cVideoRepacker::cVideoRepacker(void) +{ + Reset(); +} + +void cVideoRepacker::Reset(void) +{ + cCommonRepacker::Reset(); + scanner = 0xFFFFFFFF; + state = syncing; +} + +void cVideoRepacker::HandleStartCode(const uchar *const Data, cRingBufferLinear *const ResultBuffer, const uchar *&Payload, const uchar StreamID, const ePesHeader MpegLevel) +{ + // synchronisation is detected some bytes after frame start. + const int SkippedBytesLimit = 4; + + // which kind of start code have we got? + switch (*Data) { + case 0xB9 ... 0xFF: // system start codes + LOG("cVideoRepacker: found system start code: stream seems to be scrambled or not demultiplexed"); + break; + case 0xB0 ... 0xB1: // reserved start codes + case 0xB6: + LOG("cVideoRepacker: found reserved start code: stream seems to be scrambled"); + break; + case 0xB4: // sequence error code + LOG("cVideoRepacker: found sequence error code: stream seems to be damaged"); + case 0xB2: // user data start code + case 0xB5: // extension start code + break; + case 0xB7: // sequence end code + case 0xB3: // sequence header code + case 0xB8: // group start code + case 0x00: // picture start code + if (state == scanPicture) { + // the above start codes indicate that the current picture is done. So + // push out the packet to start a new packet for the next picuture. If + // the byte count get's negative then the current buffer ends in a + // partitial start code that must be stripped off, as it shall be put + // in the next packet. + PushOutPacket(ResultBuffer, Payload, Data - 3 - Payload); + // go on with syncing to the next picture + state = syncing; + } + if (state == syncing) { + if (initiallySyncing) // omit report for the typical initial case + initiallySyncing = false; + else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes + LOG("cVideoRepacker: skipped %d bytes to sync on next picture", skippedBytes - SkippedBytesLimit); + skippedBytes = 0; + // if there is a PES header available, then use it ... + if (pesHeaderBackupLen > 0) { + // ISO 13818-1 says: + // In the case of video, if a PTS is present in a PES packet header + // it shall refer to the access unit containing the first picture start + // code that commences in this PES packet. A picture start code commences + // in PES packet if the first byte of the picture start code is present + // in the PES packet. + memcpy(pesHeader, pesHeaderBackup, pesHeaderBackupLen); + pesHeaderLen = pesHeaderBackupLen; + pesHeaderBackupLen = 0; + } + else { + // ... otherwise create a continuation PES header + pesHeaderLen = 0; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x01; + pesHeader[pesHeaderLen++] = StreamID; // video stream ID + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + + if (MpegLevel == phMPEG2) { + pesHeader[pesHeaderLen++] = 0x80; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + } + else + pesHeader[pesHeaderLen++] = 0x0F; + } + // append the first three bytes of the start code + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x01; + // the next packet's payload will begin with the fourth byte of + // the start code (= the actual code) + Payload = Data; + // as there is no length information available, assume the + // maximum we can hold in one PES packet + packetTodo = maxPacketSize - pesHeaderLen; + // go on with finding the picture data + state++; + } + break; + case 0x01 ... 0xAF: // slice start codes + if (state == findPicture) { + // go on with scanning the picture data + state++; + } + break; + } +} + +bool cVideoRepacker::ScanDataForStartCodeSlow(const uchar *const Data) +{ + scanner <<= 8; + bool FoundStartCode = (scanner == 0x00000100); + scanner |= *Data; + return FoundStartCode; +} + +bool cVideoRepacker::ScanDataForStartCodeFast(const uchar *&Data, const uchar *Limit) +{ + Limit--; + + while (Data < Limit && (Data = (const uchar *)memchr(Data, 0x01, Limit - Data))) { + if (Data[-2] || Data[-1]) + Data += 3; + else { + scanner = 0x00000100 | *++Data; + return true; + } + } + + Data = Limit; + uint32_t *Scanner = (uint32_t *)(Data - 3); + scanner = ntohl(*Scanner); + return false; +} + +bool cVideoRepacker::ScanDataForStartCode(const uchar *&Data, int &Done, int &Todo) +{ + const uchar *const DataOrig = Data; + const int MinDataSize = 4; + + if (Todo < MinDataSize || (state != syncing && packetTodo < MinDataSize)) + return ScanDataForStartCodeSlow(Data); + + int Limit = Todo; + if (state != syncing && Limit > packetTodo) + Limit = packetTodo; + + if (ScanDataForStartCodeSlow(Data)) + return true; + + if (ScanDataForStartCodeSlow(++Data)) { + AdjustCounters(1, Done, Todo); + return true; + } + ++Data; + + bool FoundStartCode = ScanDataForStartCodeFast(Data, DataOrig + Limit); + AdjustCounters(Data - DataOrig, Done, Todo); + return FoundStartCode; +} + +void cVideoRepacker::AdjustCounters(const int Delta, int &Done, int &Todo) +{ + Done += Delta; + Todo -= Delta; + + if (state <= syncing) + skippedBytes += Delta; + else + packetTodo -= Delta; +} + +void cVideoRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) +{ + // synchronisation is detected some bytes after frame start. + const int SkippedBytesLimit = 4; + + // reset local scanner + localStart = -1; + + int pesPayloadOffset = 0; + bool continuationHeader = false; + ePesHeader mpegLevel = AnalyzePesHeader(Data, Count, pesPayloadOffset, &continuationHeader); + if (mpegLevel <= phInvalid) { + DroppedData("cVideoRepacker: no valid PES packet header found", Count); + return; + } + if (!continuationHeader) { + // backup PES header + pesHeaderBackupLen = pesPayloadOffset; + memcpy(pesHeaderBackup, Data, pesHeaderBackupLen); + } + + // skip PES header + int done = pesPayloadOffset; + int todo = Count - done; + const uchar *data = Data + done; + // remember start of the data + const uchar *payload = data; + + while (todo > 0) { + // collect number of skipped bytes while syncing + if (state <= syncing) + skippedBytes++; + // did we reach a start code? + if (ScanDataForStartCode(data, done, todo)) + HandleStartCode(data, ResultBuffer, payload, Data[3], mpegLevel); + // move on + data++; + done++; + todo--; + // do we have to start a new packet as there is no more space left? + if (state != syncing && --packetTodo <= 0) { + // we connot start a new packet here if the current might end in a start + // code and this start code shall possibly be put in the next packet. So + // overfill the current packet until we can safely detect that we won't + // break a start code into pieces: + // + // A) the last four bytes were a start code. + // B) the current byte introduces a start code. + // C) the last three bytes begin a start code. + // + // Todo : Data : Rule : Result + // -----:-------------------------------:------:------- + // : XX 00 00 00 01 YY|YY YY YY YY : : + // 0 : ^^| : A : push + // -----:-------------------------------:------:------- + // : XX XX 00 00 00 01|YY YY YY YY : : + // 0 : ^^| : B : wait + // -1 : |^^ : A : push + // -----:-------------------------------:------:------- + // : XX XX XX 00 00 00|01 YY YY YY : : + // 0 : ^^| : C : wait + // -1 : |^^ : B : wait + // -2 : | ^^ : A : push + // -----:-------------------------------:------:------- + // : XX XX XX XX 00 00|00 01 YY YY : : + // 0 : ^^| : C : wait + // -1 : |^^ : C : wait + // -2 : | ^^ : B : wait + // -3 : | ^^ : A : push + // -----:-------------------------------:------:------- + // : XX XX XX XX XX 00|00 00 01 YY : : + // 0 : ^^| : C : wait + // -1 : |^^ : C : wait + // -2 : | ^^ : : push + // -----:-------------------------------:------:------- + bool A = ((scanner & 0xFFFFFF00) == 0x00000100); + bool B = ((scanner & 0xFFFFFF) == 0x000001); + bool C = ((scanner & 0xFF) == 0x00) && (packetTodo >= -1); + if (A || (!B && !C)) { + // actually we cannot push out an overfull packet. So we'll have to + // adjust the byte count and payload start as necessary. If the byte + // count get's negative we'll have to append the excess from fragment's + // tail to the next PES header. + int bite = data + packetTodo - payload; + const uchar *excessData = fragmentData + fragmentLen + bite; + // a negative byte count means to drop some bytes from the current + // fragment's tail, to not exceed the maximum packet size. + PushOutPacket(ResultBuffer, payload, bite); + // create a continuation PES header + pesHeaderLen = 0; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x01; + pesHeader[pesHeaderLen++] = Data[3]; // video stream ID + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + + if (mpegLevel == phMPEG2) { + pesHeader[pesHeaderLen++] = 0x80; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + } + else + pesHeader[pesHeaderLen++] = 0x0F; + + // copy any excess data + while (bite++ < 0) { + // append the excess data here + pesHeader[pesHeaderLen++] = *excessData++; + packetTodo++; + } + // the next packet's payload will begin here + payload = data + packetTodo; + // as there is no length information available, assume the + // maximum we can hold in one PES packet + packetTodo += maxPacketSize - pesHeaderLen; + } + } + } + // the packet is done. Now store any remaining data into fragment buffer + // if we are no longer syncing. + if (state != syncing) { + // append the PES header ... + int bite = pesHeaderLen; + pesHeaderLen = 0; + if (bite > 0) { + memcpy(fragmentData + fragmentLen, pesHeader, bite); + fragmentLen += bite; + } + // append payload. It may contain part of a start code at it's end, + // which will be removed when the next packet gets processed. + bite = data - payload; + if (bite > 0) { + memcpy(fragmentData + fragmentLen, payload, bite); + fragmentLen += bite; + } + } + // report that syncing dropped some bytes + if (skippedBytes > SkippedBytesLimit) { + if (!initiallySyncing) // omit report for the typical initial case + LOG("cVideoRepacker: skipped %d bytes while syncing on next picture", skippedBytes - SkippedBytesLimit); + skippedBytes = SkippedBytesLimit; + } +} + +bool cVideoRepacker::ScanForEndOfPictureSlow(const uchar *&Data) +{ + localScanner <<= 8; + localScanner |= *Data++; + // check start codes which follow picture data + switch (localScanner) { + case 0x00000100: // picture start code + case 0x000001B8: // group start code + case 0x000001B3: // sequence header code + case 0x000001B7: // sequence end code + return true; + } + return false; +} + +bool cVideoRepacker::ScanForEndOfPictureFast(const uchar *&Data, const uchar *Limit) +{ + Limit--; + + while (Data < Limit && (Data = (const uchar *)memchr(Data, 0x01, Limit - Data))) { + if (Data[-2] || Data[-1]) + Data += 3; + else { + localScanner = 0x00000100 | *++Data; + // check start codes which follow picture data + switch (localScanner) { + case 0x00000100: // picture start code + case 0x000001B8: // group start code + case 0x000001B3: // sequence header code + case 0x000001B7: // sequence end code + Data++; + return true; + default: + Data += 3; + } + } + } + + Data = Limit + 1; + uint32_t *LocalScanner = (uint32_t *)(Data - 4); + localScanner = ntohl(*LocalScanner); + return false; +} + +bool cVideoRepacker::ScanForEndOfPicture(const uchar *&Data, const uchar *Limit) +{ + const uchar *const DataOrig = Data; + const int MinDataSize = 4; + bool FoundEndOfPicture; + + if (Limit - Data <= MinDataSize) { + FoundEndOfPicture = false; + while (Data < Limit) { + if (ScanForEndOfPictureSlow(Data)) { + FoundEndOfPicture = true; + break; + } + } + } + else { + FoundEndOfPicture = true; + if (!ScanForEndOfPictureSlow(Data)) { + if (!ScanForEndOfPictureSlow(Data)) { + if (!ScanForEndOfPictureFast(Data, Limit)) + FoundEndOfPicture = false; + } + } + } + + localStart += (Data - DataOrig); + return FoundEndOfPicture; +} + +int cVideoRepacker::BreakAt(const uchar *Data, int Count) +{ + if (initiallySyncing) + return -1; // fill the packet buffer completely until we have synced once + + int PesPayloadOffset = 0; + + if (AnalyzePesHeader(Data, Count, PesPayloadOffset) <= phInvalid) + return -1; // not enough data for test + + // just detect end of picture + if (state == scanPicture) { + // setup local scanner + if (localStart < 0) { + localScanner = scanner; + localStart = 0; + } + // start where we've stopped at the last run + const uchar *data = Data + PesPayloadOffset + localStart; + const uchar *limit = Data + Count; + // scan data + if (ScanForEndOfPicture(data, limit)) + return data - Data; + } + // just fill up packet and append next start code + return PesPayloadOffset + packetTodo + 4; +} + +// --- cAudioRepacker -------------------------------------------------------- + +class cAudioRepacker : public cCommonRepacker { +private: + static int bitRates[2][3][16]; + enum eState { + syncing, + scanFrame + }; + int state; + int frameTodo; + int frameSize; + int cid; + static bool IsValidAudioHeader(uint32_t Header, bool Mpeg2, int *FrameSize = NULL); +public: + cAudioRepacker(int Cid); + virtual void Reset(void); + virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); + virtual int BreakAt(const uchar *Data, int Count); + }; + +int cAudioRepacker::bitRates[2][3][16] = { // all values are specified as kbits/s + { + { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1 }, // MPEG 1, Layer I + { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1 }, // MPEG 1, Layer II + { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1 } // MPEG 1, Layer III + }, + { + { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1 }, // MPEG 2, Layer I + { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 }, // MPEG 2, Layer II/III + { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 } // MPEG 2, Layer II/III + } + }; + +cAudioRepacker::cAudioRepacker(int Cid) +{ + cid = Cid; + Reset(); +} + +void cAudioRepacker::Reset(void) +{ + cCommonRepacker::Reset(); + scanner = 0; + state = syncing; + frameTodo = 0; + frameSize = 0; +} + +bool cAudioRepacker::IsValidAudioHeader(uint32_t Header, bool Mpeg2, int *FrameSize) +{ + int syncword = (Header & 0xFFF00000) >> 20; + int id = (Header & 0x00080000) >> 19; + int layer = (Header & 0x00060000) >> 17; +//int protection_bit = (Header & 0x00010000) >> 16; + int bitrate_index = (Header & 0x0000F000) >> 12; + int sampling_frequency = (Header & 0x00000C00) >> 10; + int padding_bit = (Header & 0x00000200) >> 9; +//int private_bit = (Header & 0x00000100) >> 8; +//int mode = (Header & 0x000000C0) >> 6; +//int mode_extension = (Header & 0x00000030) >> 4; +//int copyright = (Header & 0x00000008) >> 3; +//int orignal_copy = (Header & 0x00000004) >> 2; + int emphasis = (Header & 0x00000003); + + if (syncword != 0xFFF) + return false; + + if (id == 0 && !Mpeg2) // reserved in MPEG 1 + return false; + + if (layer == 0) // reserved + return false; + + if (bitrate_index == 0xF) // forbidden + return false; + + if (sampling_frequency == 3) // reserved + return false; + + if (emphasis == 2) // reserved + return false; + + if (FrameSize) { + if (bitrate_index == 0) + *FrameSize = 0; + else { + static int samplingFrequencies[2][4] = { // all values are specified in Hz + { 44100, 48000, 32000, -1 }, // MPEG 1 + { 22050, 24000, 16000, -1 } // MPEG 2 + }; + + static int slots_per_frame[2][3] = { + { 12, 144, 144 }, // MPEG 1, Layer I, II, III + { 12, 144, 72 } // MPEG 2, Layer I, II, III + }; + + int mpegIndex = 1 - id; + int layerIndex = 3 - layer; + + // Layer I (i. e., layerIndex == 0) has a larger slot size + int slotSize = (layerIndex == 0) ? 4 : 1; // bytes + + int br = 1000 * bitRates[mpegIndex][layerIndex][bitrate_index]; // bits/s + int sf = samplingFrequencies[mpegIndex][sampling_frequency]; + + int N = slots_per_frame[mpegIndex][layerIndex] * br / sf; // slots + + *FrameSize = (N + padding_bit) * slotSize; // bytes + } + } + + return true; +} + +void cAudioRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) +{ + // synchronisation is detected some bytes after frame start. + const int SkippedBytesLimit = 4; + + // reset local scanner + localStart = -1; + + int pesPayloadOffset = 0; + bool continuationHeader = false; + ePesHeader mpegLevel = AnalyzePesHeader(Data, Count, pesPayloadOffset, &continuationHeader); + if (mpegLevel <= phInvalid) { + DroppedData("cAudioRepacker: no valid PES packet header found", Count); + return; + } + if (!continuationHeader) { + // backup PES header + pesHeaderBackupLen = pesPayloadOffset; + memcpy(pesHeaderBackup, Data, pesHeaderBackupLen); + } + + // skip PES header + int done = pesPayloadOffset; + int todo = Count - done; + const uchar *data = Data + done; + // remember start of the data + const uchar *payload = data; + + while (todo > 0) { + // collect number of skipped bytes while syncing + if (state <= syncing) + skippedBytes++; + // did we reach an audio frame header? + scanner <<= 8; + scanner |= *data; + if ((scanner & 0xFFF00000) == 0xFFF00000) { + if (frameTodo <= 0 && (frameSize == 0 || skippedBytes >= 4) && IsValidAudioHeader(scanner, mpegLevel == phMPEG2, &frameSize)) { + if (state == scanFrame) { + // As a new audio frame starts here, the previous one is done. So push + // out the packet to start a new packet for the next audio frame. If + // the byte count gets negative then the current buffer ends in a + // partitial audio frame header that must be stripped off, as it shall + // be put in the next packet. + PushOutPacket(ResultBuffer, payload, data - 3 - payload); + // go on with syncing to the next audio frame + state = syncing; + } + if (state == syncing) { + if (initiallySyncing) // omit report for the typical initial case + initiallySyncing = false; + else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes + LOG("cAudioRepacker(0x%02X): skipped %d bytes to sync on next audio frame", cid, skippedBytes - SkippedBytesLimit); + skippedBytes = 0; + // if there is a PES header available, then use it ... + if (pesHeaderBackupLen > 0) { + // ISO 13818-1 says: + // In the case of audio, if a PTS is present in a PES packet header + // it shall refer to the access unit commencing in the PES packet. An + // audio access unit commences in a PES packet if the first byte of + // the audio access unit is present in the PES packet. + memcpy(pesHeader, pesHeaderBackup, pesHeaderBackupLen); + pesHeaderLen = pesHeaderBackupLen; + pesHeaderBackupLen = 0; + } + else { + // ... otherwise create a continuation PES header + pesHeaderLen = 0; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x01; + pesHeader[pesHeaderLen++] = Data[3]; // audio stream ID + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + + if (mpegLevel == phMPEG2) { + pesHeader[pesHeaderLen++] = 0x80; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + } + else + pesHeader[pesHeaderLen++] = 0x0F; + } + // append the first three bytes of the audio frame header + pesHeader[pesHeaderLen++] = 0xFF; + pesHeader[pesHeaderLen++] = (scanner >> 16) & 0xFF; + pesHeader[pesHeaderLen++] = (scanner >> 8) & 0xFF; + // the next packet's payload will begin with the fourth byte of + // the audio frame header (= the actual byte) + payload = data; + // maximum we can hold in one PES packet + packetTodo = maxPacketSize - pesHeaderLen; + // expected remainder of audio frame: so far we have read 3 bytes from the frame header + frameTodo = frameSize - 3; + // go on with collecting the frame's data + state++; + } + } + } + data++; + done++; + todo--; + // do we have to start a new packet as the current is done? + if (frameTodo > 0) { + if (--frameTodo == 0) { + // the current audio frame is is done now. So push out the packet to + // start a new packet for the next audio frame. + PushOutPacket(ResultBuffer, payload, data - payload); + // go on with syncing to the next audio frame + state = syncing; + } + } + // do we have to start a new packet as there is no more space left? + if (state != syncing && --packetTodo <= 0) { + // We connot start a new packet here if the current might end in an audio + // frame header and this header shall possibly be put in the next packet. So + // overfill the current packet until we can safely detect that we won't + // break an audio frame header into pieces: + // + // A) the last four bytes were an audio frame header. + // B) the last three bytes introduce an audio frame header. + // C) the last two bytes introduce an audio frame header. + // D) the last byte introduces an audio frame header. + // + // Todo : Data : Rule : Result + // -----:-------------------------------:------:------- + // : XX XX FF Fz zz zz|YY YY YY YY : : + // 0 : ^^| : A : push + // -----:-------------------------------:------:------- + // : XX XX XX FF Fz zz|zz YY YY YY : : + // 0 : ^^| : B : wait + // -1 : |^^ : A : push + // -----:-------------------------------:------:------- + // : XX XX XX XX FF Fz|zz zz YY YY : : + // 0 : ^^| : C : wait + // -1 : |^^ : B : wait + // -2 : | ^^ : A : push + // -----:-------------------------------:------:------- + // : XX XX XX XX XX FF|Fz zz zz YY : : + // 0 : ^^| : D : wait + // -1 : |^^ : C : wait + // -2 : | ^^ : B : wait + // -3 : | ^^ : A : push + // -----:-------------------------------:------:------- + bool A = ((scanner & 0xFFF00000) == 0xFFF00000); + bool B = ((scanner & 0xFFF000) == 0xFFF000); + bool C = ((scanner & 0xFFF0) == 0xFFF0); + bool D = ((scanner & 0xFF) == 0xFF); + if (A || (!B && !C && !D)) { + // Actually we cannot push out an overfull packet. So we'll have to + // adjust the byte count and payload start as necessary. If the byte + // count gets negative we'll have to append the excess from fragment's + // tail to the next PES header. + int bite = data + packetTodo - payload; + const uchar *excessData = fragmentData + fragmentLen + bite; + // A negative byte count means to drop some bytes from the current + // fragment's tail, to not exceed the maximum packet size. + PushOutPacket(ResultBuffer, payload, bite); + // create a continuation PES header + pesHeaderLen = 0; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x01; + pesHeader[pesHeaderLen++] = Data[3]; // audio stream ID + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + + if (mpegLevel == phMPEG2) { + pesHeader[pesHeaderLen++] = 0x80; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + } + else + pesHeader[pesHeaderLen++] = 0x0F; + + // copy any excess data + while (bite++ < 0) { + // append the excess data here + pesHeader[pesHeaderLen++] = *excessData++; + packetTodo++; + } + // the next packet's payload will begin here + payload = data + packetTodo; + // as there is no length information available, assume the + // maximum we can hold in one PES packet + packetTodo += maxPacketSize - pesHeaderLen; + } + } + } + // The packet is done. Now store any remaining data into fragment buffer + // if we are no longer syncing. + if (state != syncing) { + // append the PES header ... + int bite = pesHeaderLen; + pesHeaderLen = 0; + if (bite > 0) { + memcpy(fragmentData + fragmentLen, pesHeader, bite); + fragmentLen += bite; + } + // append payload. It may contain part of an audio frame header at it's + // end, which will be removed when the next packet gets processed. + bite = data - payload; + if (bite > 0) { + memcpy(fragmentData + fragmentLen, payload, bite); + fragmentLen += bite; + } + } + // report that syncing dropped some bytes + if (skippedBytes > SkippedBytesLimit) { + if (!initiallySyncing) // omit report for the typical initial case + LOG("cAudioRepacker(0x%02X): skipped %d bytes while syncing on next audio frame", cid, skippedBytes - SkippedBytesLimit); + skippedBytes = SkippedBytesLimit; + } +} + +int cAudioRepacker::BreakAt(const uchar *Data, int Count) +{ + if (initiallySyncing) + return -1; // fill the packet buffer completely until we have synced once + + int PesPayloadOffset = 0; + + ePesHeader MpegLevel = AnalyzePesHeader(Data, Count, PesPayloadOffset); + if (MpegLevel <= phInvalid) + return -1; // not enough data for test + + // determine amount of data to fill up packet and to append next audio frame header + int packetRemainder = PesPayloadOffset + packetTodo + 4; + + // just detect end of an audio frame + if (state == scanFrame) { + // when remaining audio frame size is known, then omit scanning + if (frameTodo > 0) { + // determine amount of data to fill up audio frame and to append next audio frame header + int remaining = PesPayloadOffset + frameTodo + 4; + if (remaining < packetRemainder) + return remaining; + return packetRemainder; + } + // setup local scanner + if (localStart < 0) { + localScanner = scanner; + localStart = 0; + } + // start where we've stopped at the last run + const uchar *data = Data + PesPayloadOffset + localStart; + const uchar *limit = Data + Count; + // scan data + while (data < limit) { + localStart++; + localScanner <<= 8; + localScanner |= *data++; + // check whether the next audio frame follows + if (((localScanner & 0xFFF00000) == 0xFFF00000) && IsValidAudioHeader(localScanner, MpegLevel == phMPEG2)) + return data - Data; + } + } + // just fill up packet and append next audio frame header + return packetRemainder; +} + +// --- cDolbyRepacker -------------------------------------------------------- + +class cDolbyRepacker : public cRepacker { +private: + static int frameSizes[]; + uchar fragmentData[6 + 65535]; + int fragmentLen; + int fragmentTodo; + uchar pesHeader[6 + 3 + 255 + 4 + 4]; + int pesHeaderLen; + uchar pesHeaderBackup[6 + 3 + 255]; + int pesHeaderBackupLen; + uchar chk1; + uchar chk2; + int ac3todo; + enum eState { + find_0b, + find_77, + store_chk1, + store_chk2, + get_length, + output_packet + }; + int state; + int skippedBytes; + void ResetPesHeader(bool ContinuationFrame = false); + void AppendSubStreamID(bool ContinuationFrame = false); + bool FinishRemainder(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite); + bool StartNewPacket(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite); +public: + cDolbyRepacker(void); + virtual void Reset(void); + virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); + virtual int BreakAt(const uchar *Data, int Count); + }; + +// frameSizes are in words, i. e. multiply them by 2 to get bytes +int cDolbyRepacker::frameSizes[] = { + // fs = 48 kHz + 64, 64, 80, 80, 96, 96, 112, 112, 128, 128, 160, 160, 192, 192, 224, 224, + 256, 256, 320, 320, 384, 384, 448, 448, 512, 512, 640, 640, 768, 768, 896, 896, + 1024, 1024, 1152, 1152, 1280, 1280, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // fs = 44.1 kHz + 69, 70, 87, 88, 104, 105, 121, 122, 139, 140, 174, 175, 208, 209, 243, 244, + 278, 279, 348, 349, 417, 418, 487, 488, 557, 558, 696, 697, 835, 836, 975, 976, + 1114, 1115, 1253, 1254, 1393, 1394, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // fs = 32 kHz + 96, 96, 120, 120, 144, 144, 168, 168, 192, 192, 240, 240, 288, 288, 336, 336, + 384, 384, 480, 480, 576, 576, 672, 672, 768, 768, 960, 960, 1152, 1152, 1344, 1344, + 1536, 1536, 1728, 1728, 1920, 1920, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + +cDolbyRepacker::cDolbyRepacker(void) +{ + pesHeader[0] = 0x00; + pesHeader[1] = 0x00; + pesHeader[2] = 0x01; + pesHeader[3] = 0xBD; + pesHeader[4] = 0x00; + pesHeader[5] = 0x00; + Reset(); +} + +void cDolbyRepacker::AppendSubStreamID(bool ContinuationFrame) +{ + if (subStreamId) { + pesHeader[pesHeaderLen++] = subStreamId; + // number of ac3 frames "starting" in this packet (1 by design). + pesHeader[pesHeaderLen++] = 0x01; + // offset to start of first ac3 frame (0 means "no ac3 frame starting" + // so 1 (by design) addresses the first byte after the next two bytes). + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = (ContinuationFrame ? 0x00 : 0x01); + } +} + +void cDolbyRepacker::ResetPesHeader(bool ContinuationFrame) +{ + pesHeader[6] = 0x80; + pesHeader[7] = 0x00; + pesHeader[8] = 0x00; + pesHeaderLen = 9; + AppendSubStreamID(ContinuationFrame); +} + +void cDolbyRepacker::Reset(void) +{ + cRepacker::Reset(); + ResetPesHeader(); + state = find_0b; + ac3todo = 0; + chk1 = 0; + chk2 = 0; + fragmentLen = 0; + fragmentTodo = 0; + pesHeaderBackupLen = 0; + skippedBytes = 0; +} + +bool cDolbyRepacker::FinishRemainder(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite) +{ + bool success = true; + // enough data available to put PES packet into buffer? + if (fragmentTodo <= Todo) { + // output a previous fragment first + if (fragmentLen > 0) { + Bite = fragmentLen; + int n = Put(ResultBuffer, fragmentData, Bite, fragmentLen + fragmentTodo); + if (Bite != n) + success = false; + fragmentLen = 0; + } + Bite = fragmentTodo; + if (success) { + int n = Put(ResultBuffer, Data, Bite, Bite); + if (Bite != n) + success = false; + } + fragmentTodo = 0; + // ac3 frame completely processed? + if (Bite >= ac3todo) + state = find_0b; // go on with finding start of next packet + } + else { + // copy the fragment into separate buffer for later processing + Bite = Todo; + memcpy(fragmentData + fragmentLen, Data, Bite); + fragmentLen += Bite; + fragmentTodo -= Bite; + } + return success; +} + +bool cDolbyRepacker::StartNewPacket(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite) +{ + bool success = true; + int packetLen = pesHeaderLen + ac3todo; + // limit packet to maximum size + if (packetLen > maxPacketSize) + packetLen = maxPacketSize; + pesHeader[4] = (packetLen - 6) >> 8; + pesHeader[5] = (packetLen - 6) & 0xFF; + Bite = pesHeaderLen; + // enough data available to put PES packet into buffer? + if (packetLen - pesHeaderLen <= Todo) { + int n = Put(ResultBuffer, pesHeader, Bite, packetLen); + if (Bite != n) + success = false; + Bite = packetLen - pesHeaderLen; + if (success) { + n = Put(ResultBuffer, Data, Bite, Bite); + if (Bite != n) + success = false; + } + // ac3 frame completely processed? + if (Bite >= ac3todo) + state = find_0b; // go on with finding start of next packet + } + else { + fragmentTodo = packetLen; + // copy the pesheader into separate buffer for later processing + memcpy(fragmentData + fragmentLen, pesHeader, Bite); + fragmentLen += Bite; + fragmentTodo -= Bite; + // copy the fragment into separate buffer for later processing + Bite = Todo; + memcpy(fragmentData + fragmentLen, Data, Bite); + fragmentLen += Bite; + fragmentTodo -= Bite; + } + return success; +} + +void cDolbyRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) +{ + // synchronisation is detected some bytes after frame start. + const int SkippedBytesLimit = 4; + + // check for MPEG 2 + if ((Data[6] & 0xC0) != 0x80) { + DroppedData("cDolbyRepacker: MPEG 2 PES header expected", Count); + return; + } + + // backup PES header + if (Data[6] != 0x80 || Data[7] != 0x00 || Data[8] != 0x00) { + pesHeaderBackupLen = 6 + 3 + Data[8]; + memcpy(pesHeaderBackup, Data, pesHeaderBackupLen); + } + + // skip PES header + int done = 6 + 3 + Data[8]; + int todo = Count - done; + const uchar *data = Data + done; + + // look for 0x0B 0x77 <chk1> <chk2> <frameSize> + while (todo > 0) { + switch (state) { + case find_0b: + if (*data == 0x0B) { + state++; + // copy header information once for later use + if (pesHeaderBackupLen > 0) { + pesHeaderLen = pesHeaderBackupLen; + pesHeaderBackupLen = 0; + memcpy(pesHeader, pesHeaderBackup, pesHeaderLen); + AppendSubStreamID(); + } + } + data++; + done++; + todo--; + skippedBytes++; // collect number of skipped bytes while syncing + continue; + case find_77: + if (*data != 0x77) { + state = find_0b; + continue; + } + data++; + done++; + todo--; + skippedBytes++; // collect number of skipped bytes while syncing + state++; + continue; + case store_chk1: + chk1 = *data++; + done++; + todo--; + skippedBytes++; // collect number of skipped bytes while syncing + state++; + continue; + case store_chk2: + chk2 = *data++; + done++; + todo--; + skippedBytes++; // collect number of skipped bytes while syncing + state++; + continue; + case get_length: + ac3todo = 2 * frameSizes[*data]; + // frameSizeCode was invalid => restart searching + if (ac3todo <= 0) { + // reset PES header instead of using a wrong one + ResetPesHeader(); + if (chk1 == 0x0B) { + if (chk2 == 0x77) { + state = store_chk1; + continue; + } + if (chk2 == 0x0B) { + state = find_77; + continue; + } + state = find_0b; + continue; + } + if (chk2 == 0x0B) { + state = find_77; + continue; + } + state = find_0b; + continue; + } + if (initiallySyncing) // omit report for the typical initial case + initiallySyncing = false; + else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes + LOG("cDolbyRepacker: skipped %d bytes to sync on next AC3 frame", skippedBytes - SkippedBytesLimit); + skippedBytes = 0; + // append read data to header for common output processing + pesHeader[pesHeaderLen++] = 0x0B; + pesHeader[pesHeaderLen++] = 0x77; + pesHeader[pesHeaderLen++] = chk1; + pesHeader[pesHeaderLen++] = chk2; + ac3todo -= 4; + state++; + // fall through to output + case output_packet: { + int bite = 0; + // finish remainder of ac3 frame? + if (fragmentTodo > 0) + FinishRemainder(ResultBuffer, data, todo, bite); + else { + // start a new packet + StartNewPacket(ResultBuffer, data, todo, bite); + // prepare for next (continuation) packet + ResetPesHeader(state == output_packet); + } + data += bite; + done += bite; + todo -= bite; + ac3todo -= bite; + } + } + } + // report that syncing dropped some bytes + if (skippedBytes > SkippedBytesLimit) { + if (!initiallySyncing) // omit report for the typical initial case + LOG("cDolbyRepacker: skipped %d bytes while syncing on next AC3 frame", skippedBytes - 4); + skippedBytes = SkippedBytesLimit; + } +} + +int cDolbyRepacker::BreakAt(const uchar *Data, int Count) +{ + if (initiallySyncing) + return -1; // fill the packet buffer completely until we have synced once + // enough data for test? + if (Count < 6 + 3) + return -1; + // check for MPEG 2 + if ((Data[6] & 0xC0) != 0x80) + return -1; + int headerLen = Data[8] + 6 + 3; + // break after fragment tail? + if (ac3todo > 0) + return headerLen + ac3todo; + // enough data for test? + if (Count < headerLen + 5) + return -1; + const uchar *data = Data + headerLen; + // break after ac3 frame? + if (data[0] == 0x0B && data[1] == 0x77 && frameSizes[data[4]] > 0) + return headerLen + 2 * frameSizes[data[4]]; + return -1; +} + +// --- cTS2PES --------------------------------------------------------------- + +#include <netinet/in.h> + +//XXX TODO: these should really be available in some driver header file! +#define PROG_STREAM_MAP 0xBC +#ifndef PRIVATE_STREAM1 +#define PRIVATE_STREAM1 0xBD +#endif +#define PADDING_STREAM 0xBE +#ifndef PRIVATE_STREAM2 +#define PRIVATE_STREAM2 0xBF +#endif +#define AUDIO_STREAM_S 0xC0 +#define AUDIO_STREAM_E 0xDF +#define VIDEO_STREAM_S 0xE0 +#define VIDEO_STREAM_E 0xEF +#define ECM_STREAM 0xF0 +#define EMM_STREAM 0xF1 +#define DSM_CC_STREAM 0xF2 +#define ISO13522_STREAM 0xF3 +#define PROG_STREAM_DIR 0xFF + +//pts_dts flags +#define PTS_ONLY 0x80 + +#define TS_SIZE 188 +#define PID_MASK_HI 0x1F +#define CONT_CNT_MASK 0x0F + +// Flags: +#define PAY_LOAD 0x10 +#define ADAPT_FIELD 0x20 +#define PAY_START 0x40 +#define TS_ERROR 0x80 + +#define MAX_PLENGTH 0xFFFF // the maximum PES packet length (theoretically) +#define MMAX_PLENGTH (64*MAX_PLENGTH) // some stations send PES packets that are extremely large, e.g. DVB-T in Finland or HDTV 1920x1080 + +#define IPACKS 2048 + +// Start codes: +#define SC_SEQUENCE 0xB3 // "sequence header code" +#define SC_GROUP 0xB8 // "group start code" +#define SC_PICTURE 0x00 // "picture start code" + +#define MAXNONUSEFULDATA (10*1024*1024) +#define MAXNUMUPTERRORS 10 + +class cTS2PES { +private: + int pid; + int size; + int found; + int count; + uint8_t *buf; + uint8_t cid; + uint8_t rewriteCid; + uint8_t subStreamId; + int plength; + uint8_t plen[2]; + uint8_t flag1; + uint8_t flag2; + uint8_t hlength; + int mpeg; + uint8_t check; + int mpeg1_required; + int mpeg1_stuffing; + bool done; + cRingBufferLinear *resultBuffer; + int tsErrors; + int ccErrors; + int ccCounter; + cRepacker *repacker; + static uint8_t headr[]; + void store(uint8_t *Data, int Count); + void reset_ipack(void); + void send_ipack(void); + void write_ipack(const uint8_t *Data, int Count); + void instant_repack(const uint8_t *Buf, int Count); +public: + cTS2PES(int Pid, cRingBufferLinear *ResultBuffer, int Size, uint8_t RewriteCid = 0x00, uint8_t SubStreamId = 0x00, cRepacker *Repacker = NULL); + ~cTS2PES(); + int Pid(void) { return pid; } + void ts_to_pes(const uint8_t *Buf); // don't need count (=188) + void Clear(void); + }; + +uint8_t cTS2PES::headr[] = { 0x00, 0x00, 0x01 }; + +cTS2PES::cTS2PES(int Pid, cRingBufferLinear *ResultBuffer, int Size, uint8_t RewriteCid, uint8_t SubStreamId, cRepacker *Repacker) +{ + pid = Pid; + resultBuffer = ResultBuffer; + size = Size; + rewriteCid = RewriteCid; + subStreamId = SubStreamId; + repacker = Repacker; + if (repacker) { + repacker->SetMaxPacketSize(size); + repacker->SetSubStreamId(subStreamId); + size += repacker->QuerySnoopSize(); + } + + tsErrors = 0; + ccErrors = 0; + ccCounter = -1; + + if (!(buf = MALLOC(uint8_t, size))) + esyslog("Not enough memory for ts_transform"); + + reset_ipack(); +} + +cTS2PES::~cTS2PES() +{ + if (tsErrors || ccErrors) + dsyslog("cTS2PES got %d TS errors, %d TS continuity errors", tsErrors, ccErrors); + free(buf); + delete repacker; +} + +void cTS2PES::Clear(void) +{ + reset_ipack(); + if (repacker) + repacker->Reset(); +} + +void cTS2PES::store(uint8_t *Data, int Count) +{ + if (repacker) + repacker->Repack(resultBuffer, Data, Count); + else + cRepacker::Put(resultBuffer, Data, Count, Count); +} + +void cTS2PES::reset_ipack(void) +{ + found = 0; + cid = 0; + plength = 0; + flag1 = 0; + flag2 = 0; + hlength = 0; + mpeg = 0; + check = 0; + mpeg1_required = 0; + mpeg1_stuffing = 0; + done = false; + count = 0; +} + +void cTS2PES::send_ipack(void) +{ + if (count <= ((mpeg == 2) ? 9 : 7)) // skip empty packets + return; + buf[3] = rewriteCid ? rewriteCid : cid; + buf[4] = (uint8_t)(((count - 6) & 0xFF00) >> 8); + buf[5] = (uint8_t)((count - 6) & 0x00FF); + store(buf, count); + + switch (mpeg) { + case 2: + buf[6] = 0x80; + buf[7] = 0x00; + buf[8] = 0x00; + count = 9; + if (!repacker && subStreamId) { + buf[9] = subStreamId; + buf[10] = 1; + buf[11] = 0; + buf[12] = 1; + count = 13; + } + break; + case 1: + buf[6] = 0x0F; + count = 7; + break; + } +} + +void cTS2PES::write_ipack(const uint8_t *Data, int Count) +{ + if (count < 6) { + memcpy(buf, headr, 3); + count = 6; + } + + // determine amount of data to process + int bite = Count; + if (count + bite > size) + bite = size - count; + if (repacker) { + int breakAt = repacker->BreakAt(buf, count); + // avoid memcpy of data after break location + if (0 <= breakAt && breakAt < count + bite) { + bite = breakAt - count; + if (bite < 0) // should never happen + bite = 0; + } + } + + memcpy(buf + count, Data, bite); + count += bite; + + if (repacker) { + // determine break location + int breakAt = repacker->BreakAt(buf, count); + if (breakAt > size) // won't fit into packet? + breakAt = -1; + if (breakAt > count) // not enough data? + breakAt = -1; + // push out data before break location + if (breakAt > 0) { + // adjust bite if above memcpy was to large + bite -= count - breakAt; + count = breakAt; + send_ipack(); + // recurse for data after break location + if (Count - bite > 0) + write_ipack(Data + bite, Count - bite); + } + } + + // push out data when buffer is full + if (count >= size) { + send_ipack(); + // recurse for remaining data + if (Count - bite > 0) + write_ipack(Data + bite, Count - bite); + } +} + +void cTS2PES::instant_repack(const uint8_t *Buf, int Count) +{ + int c = 0; + + while (c < Count && (mpeg == 0 || (mpeg == 1 && found < mpeg1_required) || (mpeg == 2 && found < 9)) && (found < 5 || !done)) { + switch (found ) { + case 0: + case 1: + if (Buf[c] == 0x00) + found++; + else + found = 0; + c++; + break; + case 2: + if (Buf[c] == 0x01) + found++; + else if (Buf[c] != 0) + found = 0; + c++; + break; + case 3: + cid = 0; + switch (Buf[c]) { + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + case PADDING_STREAM : + case DSM_CC_STREAM : + case ISO13522_STREAM: + done = true; + case PRIVATE_STREAM1: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + found++; + cid = Buf[c++]; + break; + default: + found = 0; + break; + } + break; + case 4: + if (Count - c > 1) { + unsigned short *pl = (unsigned short *)(Buf + c); + plength = ntohs(*pl); + c += 2; + found += 2; + mpeg1_stuffing = 0; + } + else { + plen[0] = Buf[c]; + found++; + return; + } + break; + case 5: { + plen[1] = Buf[c++]; + unsigned short *pl = (unsigned short *)plen; + plength = ntohs(*pl); + found++; + mpeg1_stuffing = 0; + } + break; + case 6: + if (!done) { + flag1 = Buf[c++]; + found++; + if (mpeg1_stuffing == 0) { // first stuffing iteration: determine MPEG level + if ((flag1 & 0xC0) == 0x80) + mpeg = 2; + else { + mpeg = 1; + mpeg1_required = 7; + } + } + if (mpeg == 1) { + if (flag1 == 0xFF) { // MPEG1 stuffing + if (++mpeg1_stuffing > 16) + found = 0; // invalid MPEG1 header + else { // ignore stuffing + found--; + if (plength > 0) + plength--; + } + } + else if ((flag1 & 0xC0) == 0x40) // STD_buffer_scale/size + mpeg1_required += 2; + else if (flag1 != 0x0F && (flag1 & 0xF0) != 0x20 && (flag1 & 0xF0) != 0x30) + found = 0; // invalid MPEG1 header + else { + flag2 = 0; + hlength = 0; + } + } + } + break; + case 7: + if (!done && (mpeg == 2 || mpeg1_required > 7)) { + flag2 = Buf[c++]; + found++; + } + break; + case 8: + if (!done && (mpeg == 2 || mpeg1_required > 7)) { + hlength = Buf[c++]; + found++; + if (mpeg == 1 && hlength != 0x0F && (hlength & 0xF0) != 0x20 && (hlength & 0xF0) != 0x30) + found = 0; // invalid MPEG1 header + } + break; + default: + break; + } + } + + if (!plength) + plength = MMAX_PLENGTH - 6; + + if (done || ((mpeg == 2 && found >= 9) || (mpeg == 1 && found >= mpeg1_required))) { + switch (cid) { + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + case PRIVATE_STREAM1: + + if (mpeg == 2 && found == 9 && count < found) { // make sure to not write the data twice by looking at count + write_ipack(&flag1, 1); + write_ipack(&flag2, 1); + write_ipack(&hlength, 1); + } + + if (mpeg == 1 && found == mpeg1_required && count < found) { // make sure to not write the data twice by looking at count + write_ipack(&flag1, 1); + if (mpeg1_required > 7) { + write_ipack(&flag2, 1); + write_ipack(&hlength, 1); + } + } + + if (mpeg == 2 && (flag2 & PTS_ONLY) && found < 14) { + while (c < Count && found < 14) { + write_ipack(Buf + c, 1); + c++; + found++; + } + if (c == Count) + return; + } + + if (!repacker && subStreamId) { + while (c < Count && found < (hlength + 9) && found < plength + 6) { + write_ipack(Buf + c, 1); + c++; + found++; + } + if (found == (hlength + 9)) { + uchar sbuf[] = { 0x01, 0x00, 0x00 }; + write_ipack(&subStreamId, 1); + write_ipack(sbuf, 3); + } + } + + while (c < Count && found < plength + 6) { + int l = Count - c; + if (l + found > plength + 6) + l = plength + 6 - found; + write_ipack(Buf + c, l); + found += l; + c += l; + } + + break; + } + + if (done) { + if (found + Count - c < plength + 6) { + found += Count - c; + c = Count; + } + else { + c += plength + 6 - found; + found = plength + 6; + } + } + + if (plength && found == plength + 6) { + if (plength == MMAX_PLENGTH - 6) + esyslog("ERROR: PES packet length overflow in remuxer (stream corruption)"); + send_ipack(); + reset_ipack(); + if (c < Count) + instant_repack(Buf + c, Count - c); + } + } + return; +} + +void cTS2PES::ts_to_pes(const uint8_t *Buf) // don't need count (=188) +{ + if (!Buf) + return; + + if (Buf[1] & TS_ERROR) + tsErrors++; + +#ifdef USE_DVBSETUP + if (!(Buf[3] & (ADAPT_FIELD | PAY_LOAD))) { + dsyslog("TS packet discarded due to invalid adaption_field_control"); + return; + } +#else + if (!(Buf[3] & (ADAPT_FIELD | PAY_LOAD))) + return; // discard TS packet with adaption_field_control set to '00'. +#endif /* DVBSETUP */ + + if ((Buf[3] & PAY_LOAD) && ((Buf[3] ^ ccCounter) & CONT_CNT_MASK)) { + // This should check duplicates and packets which do not increase the counter. + // But as the errors usually come in bursts this should be enough to + // show you there is something wrong with signal quality. + if (ccCounter != -1 && ((Buf[3] ^ (ccCounter + 1)) & CONT_CNT_MASK)) { + ccErrors++; + // Enable this if you are having problems with signal quality. + // These are the errors I used to get with Nova-T when antenna + // was not positioned correcly (not transport errors). //tvr + //dsyslog("TS continuity error (%d)", ccCounter); +#ifdef USE_DVBSETUP + dsyslog("TS continuity error (%d)", ccCounter); +#endif /* DVBSETUP */ + } + ccCounter = Buf[3] & CONT_CNT_MASK; + } + + if (Buf[1] & PAY_START) { + if (found > 6) { + if (plength != MMAX_PLENGTH - 6 && plength != found - 6) + dsyslog("PES packet shortened to %d bytes (expected: %d bytes)", found, plength + 6); + plength = found - 6; + send_ipack(); + reset_ipack(); + } + found = 0; + } + + uint8_t off = 0; + + if (Buf[3] & ADAPT_FIELD) { // adaptation field? + off = Buf[4] + 1; + if (off + 4 > 187) + return; + } + + if (Buf[3] & PAY_LOAD) + instant_repack(Buf + 4 + off, TS_SIZE - 4 - off); +#ifdef USE_DVBSETUP + else if (off + 4 < 188) + dsyslog("adaption_field zu short or PAY_LOAD not set"); +#endif /* DVBSETUP */ +} + +// --- cRingBufferLinearPes -------------------------------------------------- + +class cRingBufferLinearPes : public cRingBufferLinear { +protected: + virtual int DataReady(const uchar *Data, int Count); +public: + cRingBufferLinearPes(int Size, int Margin = 0, bool Statistics = false, const char *Description = NULL) + :cRingBufferLinear(Size, Margin, Statistics, Description) {} + }; + +int cRingBufferLinearPes::DataReady(const uchar *Data, int Count) +{ + int c = cRingBufferLinear::DataReady(Data, Count); + if (!c && Count >= 6) { + if (!Data[0] && !Data[1] && Data[2] == 0x01) { + int Length = 6 + Data[4] * 256 + Data[5]; + if (Length <= Count) + return Length; + } + } + return c; +} + +// --- cRemuxPIP ---------------------------------------------------------------- + +#define RESULTBUFFERSIZE KILOBYTE(256) + +#ifdef USE_SYNCEARLY +cRemuxPIP::cRemuxPIP(int VPid, const int *APids, const int *DPids, const int *SPids, bool ExitOnFailure, bool SyncEarly) +#else +cRemuxPIP::cRemuxPIP(int VPid, const int *APids, const int *DPids, const int *SPids, bool ExitOnFailure) +#endif /* SYNCEARLY */ +{ + exitOnFailure = ExitOnFailure; + noVideo = VPid == 0 || VPid == 1 || VPid == 0x1FFF; + numUPTerrors = 0; + synced = false; +#ifdef USE_SYNCEARLY + syncEarly = SyncEarly; +#endif /* SYNCEARLY */ + skipped = 0; + numTracks = 0; + resultSkipped = 0; + resultBuffer = new cRingBufferLinearPes(RESULTBUFFERSIZE, IPACKS, false, "Result"); + resultBuffer->SetTimeouts(0, 100); + if (VPid) +#define TEST_cVideoRepacker +#ifdef TEST_cVideoRepacker + ts2pes[numTracks++] = new cTS2PES(VPid, resultBuffer, IPACKS, 0xE0, 0x00, new cVideoRepacker); +#else + ts2pes[numTracks++] = new cTS2PES(VPid, resultBuffer, IPACKS, 0xE0); +#endif + if (APids) { + int n = 0; + while (*APids && numTracks < MAXTRACKS && n < MAXAPIDS) { +#define TEST_cAudioRepacker +#ifdef TEST_cAudioRepacker + ts2pes[numTracks++] = new cTS2PES(*APids++, resultBuffer, IPACKS, 0xC0 + n, 0x00, new cAudioRepacker(0xC0 + n)); + n++; +#else + ts2pes[numTracks++] = new cTS2PES(*APids++, resultBuffer, IPACKS, 0xC0 + n++); +#endif + } + } + if (DPids) { + int n = 0; + while (*DPids && numTracks < MAXTRACKS && n < MAXDPIDS) + ts2pes[numTracks++] = new cTS2PES(*DPids++, resultBuffer, IPACKS, 0x00, 0x80 + n++, new cDolbyRepacker); + } + if (SPids) { + int n = 0; + while (*SPids && numTracks < MAXTRACKS && n < MAXSPIDS) + ts2pes[numTracks++] = new cTS2PES(*SPids++, resultBuffer, IPACKS, 0x00, 0x20 + n++); + } +} + +cRemuxPIP::~cRemuxPIP() +{ + for (int t = 0; t < numTracks; t++) + delete ts2pes[t]; + delete resultBuffer; +} + +int cRemuxPIP::GetPid(const uchar *Data) +{ + return (((uint16_t)Data[0] & PID_MASK_HI) << 8) | (Data[1] & 0xFF); +} + +int cRemuxPIP::GetPacketLength(const uchar *Data, int Count, int Offset) +{ + // Returns the length of the packet starting at Offset, or -1 if Count is + // too small to contain the entire packet. + int Length = (Offset + 5 < Count) ? (Data[Offset + 4] << 8) + Data[Offset + 5] + 6 : -1; + if (Length > 0 && Offset + Length <= Count) + return Length; + return -1; +} + +int cRemuxPIP::ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &PictureType) +{ + // Scans the video packet starting at Offset and returns its length. + // If the return value is -1 the packet was not completely in the buffer. + int Length = GetPacketLength(Data, Count, Offset); + if (Length > 0) { + int PesPayloadOffset = 0; + if (AnalyzePesHeader(Data + Offset, Length, PesPayloadOffset) >= phMPEG1) { + const uchar *p = Data + Offset + PesPayloadOffset + 2; + const uchar *pLimit = Data + Offset + Length - 3; +#ifdef TEST_cVideoRepacker + // cVideoRepacker ensures that a new PES packet is started for a new sequence, + // group or picture which allows us to easily skip scanning through a huge + // amount of video data. + if (p < pLimit) { + if (p[-2] || p[-1] || p[0] != 0x01) + pLimit = 0; // skip scanning: packet doesn't start with 0x000001 + else { + switch (p[1]) { + case SC_SEQUENCE: + case SC_GROUP: + case SC_PICTURE: + break; + default: // skip scanning: packet doesn't start a new sequence, group or picture + pLimit = 0; + } + } + } +#endif + while (p < pLimit && (p = (const uchar *)memchr(p, 0x01, pLimit - p))) { + if (!p[-2] && !p[-1]) { // found 0x000001 + switch (p[1]) { + case SC_PICTURE: PictureType = (p[3] >> 3) & 0x07; + return Length; + } + p += 4; // continue scanning after 0x01ssxxyy + } + else + p += 3; // continue scanning after 0x01xxyy + } + } + PictureType = NO_PICTURE; + return Length; + } + return -1; +} + +#define TS_SYNC_BYTE 0x47 + +int cRemuxPIP::Put(const uchar *Data, int Count) +{ + int used = 0; + + // Make sure we are looking at a TS packet: + + while (Count > TS_SIZE) { + if (Data[0] == TS_SYNC_BYTE && Data[TS_SIZE] == TS_SYNC_BYTE) + break; + Data++; + Count--; + used++; + } + if (used) + esyslog("ERROR: skipped %d byte to sync on TS packet", used); + + // Convert incoming TS data into multiplexed PES: + + for (int i = 0; i < Count; i += TS_SIZE) { + if (Count - i < TS_SIZE) + break; + if (Data[i] != TS_SYNC_BYTE) + break; + if (resultBuffer->Free() < 2 * IPACKS) + break; // A cTS2PES might write one full packet and also a small rest + int pid = GetPid(Data + i + 1); + if (Data[i + 3] & 0x10) { // got payload + for (int t = 0; t < numTracks; t++) { + if (ts2pes[t]->Pid() == pid) { + ts2pes[t]->ts_to_pes(Data + i); + break; + } + } + } + used += TS_SIZE; + } + + // Check if we're getting anywhere here: + if (!synced && skipped >= 0) { + if (skipped > MAXNONUSEFULDATA) { + esyslog("ERROR: no useful data seen within %d byte of video stream", skipped); + skipped = -1; + if (exitOnFailure) + ShutdownHandler.RequestEmergencyExit(); + } + else + skipped += used; + } + + return used; +} + +uchar *cRemuxPIP::Get(int &Count, uchar *PictureType) +{ + // Remove any previously skipped data from the result buffer: + + if (resultSkipped > 0) { + resultBuffer->Del(resultSkipped); + resultSkipped = 0; + } + +#if 0 + // Test recording without determining the real frame borders: + if (PictureType) + *PictureType = I_FRAME; + return resultBuffer->Get(Count); +#endif + + // Check for frame borders: + + if (PictureType) + *PictureType = NO_PICTURE; + + Count = 0; + uchar *resultData = NULL; + int resultCount = 0; + uchar *data = resultBuffer->Get(resultCount); + if (data) { + for (int i = 0; i < resultCount - 3; i++) { + if (data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1) { + int l = 0; + uchar StreamType = data[i + 3]; + if (VIDEO_STREAM_S <= StreamType && StreamType <= VIDEO_STREAM_E) { + uchar pt = NO_PICTURE; + l = ScanVideoPacket(data, resultCount, i, pt); + if (l < 0) + return resultData; + if (pt != NO_PICTURE) { + if (pt < I_FRAME || B_FRAME < pt) { + esyslog("ERROR: unknown picture type '%d'", pt); + if (++numUPTerrors > MAXNUMUPTERRORS && exitOnFailure) { + ShutdownHandler.RequestEmergencyExit(); + numUPTerrors = 0; + } + } + else if (!synced) { +#ifdef USE_SYNCEARLY + if (pt == I_FRAME || syncEarly) { +#else + if (pt == I_FRAME) { +#endif /* SYNCEARLY */ + if (PictureType) + *PictureType = pt; + resultSkipped = i; // will drop everything before this position +#ifdef USE_SYNCEARLY + if (!syncEarly) +#endif /* SYNCEARLY */ + SetBrokenLink(data + i, l); + synced = true; +#ifdef USE_SYNCEARLY + if (syncEarly) { + if (pt == I_FRAME) // syncEarly: it's ok but there is no need to call SetBrokenLink() + SetBrokenLink(data + i, l); + else fprintf(stderr, "video: synced early\n"); + } +#endif /* SYNCEARLY */ + } + } + else if (Count) + return resultData; + else if (PictureType) + *PictureType = pt; + } + } + else { //if (AUDIO_STREAM_S <= StreamType && StreamType <= AUDIO_STREAM_E || StreamType == PRIVATE_STREAM1) { + l = GetPacketLength(data, resultCount, i); + if (l < 0) + return resultData; +#ifdef USE_SYNCEARLY + if (noVideo || !synced && syncEarly) { + if (!synced) { + if (PictureType && noVideo) +#else + if (noVideo) { + if (!synced) { + if (PictureType) +#endif /* SYNCEARLY */ + *PictureType = I_FRAME; + resultSkipped = i; // will drop everything before this position + synced = true; +#ifdef USE_SYNCEARLY + if (!noVideo && syncEarly) fprintf(stderr, "audio: synced early\n"); +#endif /* SYNCEARLY */ + } + else if (Count) + return resultData; + else if (PictureType) + *PictureType = I_FRAME; + } + } + if (synced) { + if (!Count) + resultData = data + i; + Count += l; + } + else + resultSkipped = i + l; + if (l > 0) + i += l - 1; // the loop increments, too + } + } + } + return resultData; +} + +void cRemuxPIP::Del(int Count) +{ + resultBuffer->Del(Count); +} + +void cRemuxPIP::Clear(void) +{ + for (int t = 0; t < numTracks; t++) + ts2pes[t]->Clear(); + resultBuffer->Clear(); + synced = false; + skipped = 0; + resultSkipped = 0; +} + +void cRemuxPIP::SetBrokenLink(uchar *Data, int Length) +{ + int PesPayloadOffset = 0; + if (AnalyzePesHeader(Data, Length, PesPayloadOffset) >= phMPEG1 && (Data[3] & 0xF0) == VIDEO_STREAM_S) { + for (int i = PesPayloadOffset; i < Length - 7; i++) { + if (Data[i] == 0 && Data[i + 1] == 0 && Data[i + 2] == 1 && Data[i + 3] == 0xB8) { + if (!(Data[i + 7] & 0x40)) // set flag only if GOP is not closed + Data[i + 7] |= 0x20; + return; + } + } + dsyslog("SetBrokenLink: no GOP header found in video packet"); + } + else + dsyslog("SetBrokenLink: no video packet in frame"); +} @@ -0,0 +1,85 @@ +/* + * remux.h: A streaming MPEG2 remultiplexer + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * $Id: remux.h 1.17 2007/09/02 10:19:06 kls Exp $ + */ + +#ifndef __REMUX_PIP_H +#define __REMUX_PIP_H + +#include <vdr/ringbuffer.h> +#include <vdr/tools.h> +#include <vdr/remux.h> + +//ePesHeader AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader = NULL); + +// Picture types: +#define NO_PICTURE 0 +#define I_FRAME 1 +#define P_FRAME 2 +#define B_FRAME 3 + +#define MAXTRACKS 64 + +class cTS2PES; + +class cRemuxPIP { +private: + bool exitOnFailure; + bool noVideo; + int numUPTerrors; + bool synced; +#ifdef USE_SYNCEARLY + bool syncEarly; +#endif /* SYNCEARLY */ + int skipped; + cTS2PES *ts2pes[MAXTRACKS]; + int numTracks; + cRingBufferLinear *resultBuffer; + int resultSkipped; + int GetPid(const uchar *Data); +public: +#ifdef USE_SYNCEARLY + cRemuxPIP(int VPid, const int *APids, const int *DPids, const int *SPids, bool ExitOnFailure = false, bool SyncEarly = false); +#else + cRemuxPIP(int VPid, const int *APids, const int *DPids, const int *SPids, bool ExitOnFailure = false); +#endif /* SYNCEARLY */ + ///< Creates a new remuxer for the given PIDs. VPid is the video PID, while + ///< APids, DPids and SPids are pointers to zero terminated lists of audio, + ///< dolby and subtitle PIDs (the pointers may be NULL if there is no such + ///< PID). If ExitOnFailure is true, the remuxer will initiate an "emergency + ///< exit" in case of problems with the data stream. + ///< If USE_SYNCEARLY is activated: SyncEarly causes cRemuxPIP to sync as soon + ///< as a video or audio frame is seen. + ~cRemuxPIP(); + void SetTimeouts(int PutTimeout, int GetTimeout) { resultBuffer->SetTimeouts(PutTimeout, GetTimeout); } + ///< By default cRemuxPIP assumes that Put() and Get() are called from different + ///< threads, and uses a timeout in the Get() function in case there is no + ///< data available. SetTimeouts() can be used to modify these timeouts. + ///< Especially if Put() and Get() are called from the same thread, setting + ///< both timeouts to 0 is recommended. + int Put(const uchar *Data, int Count); + ///< Puts at most Count bytes of Data into the remuxer. + ///< \return Returns the number of bytes actually consumed from Data. + uchar *Get(int &Count, uchar *PictureType = NULL); + ///< Gets all currently available data from the remuxer. + ///< \return Count contains the number of bytes the result points to, and + ///< PictureType (if not NULL) will contain one of NO_PICTURE, I_FRAME, P_FRAME + ///< or B_FRAME. + void Del(int Count); + ///< Deletes Count bytes from the remuxer. Count must be the number returned + ///< from a previous call to Get(). Several calls to Del() with fractions of + ///< a previously returned Count may be made, but the total sum of all Count + ///< values must be exactly what the previous Get() has returned. + void Clear(void); + ///< Clears the remuxer of all data it might still contain, keeping the PID + ///< settings as they are. + static void SetBrokenLink(uchar *Data, int Length); + static int GetPacketLength(const uchar *Data, int Count, int Offset); + static int ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &PictureType); + }; + +#endif // __REMUX_PIP_H |