From cd21e85b8756bf96a5d3a85b2800ef1ceb006db8 Mon Sep 17 00:00:00 2001 From: Christian Fetzer Date: Mon, 6 Jan 2014 15:03:12 +0100 Subject: [PATCH] [mythtv-cmyth] Release v1.9.14 --- addons/pvr.mythtv.cmyth/Makefile.am | 14 +- addons/pvr.mythtv.cmyth/addon/addon.xml.in | 2 +- addons/pvr.mythtv.cmyth/addon/changelog.txt | 13 + .../resources/language/English/strings.po | 48 + .../addon/resources/settings.xml | 4 + .../VS2010Express/pvr.mythtv.cmyth.vcxproj | 27 + .../pvr.mythtv.cmyth.vcxproj.filters | 80 ++ addons/pvr.mythtv.cmyth/src/client.cpp | 155 ++- addons/pvr.mythtv.cmyth/src/client.h | 12 + .../src/cppmyth/MythConnection.cpp | 4 + .../src/cppmyth/MythEventHandler.cpp | 10 +- .../src/cppmyth/MythEventHandler.h | 2 +- .../src/cppmyth/MythRecordingRule.cpp | 26 + .../src/cppmyth/MythRecordingRule.h | 9 +- .../src/cppmyth/MythScheduleManager.cpp | 406 ++++++- .../src/cppmyth/MythScheduleManager.h | 40 +- addons/pvr.mythtv.cmyth/src/demux.cpp | 584 ++++++++++ addons/pvr.mythtv.cmyth/src/demux.h | 95 ++ .../pvr.mythtv.cmyth/src/demuxer/ES_AAC.cpp | 253 +++++ addons/pvr.mythtv.cmyth/src/demuxer/ES_AAC.h | 56 + .../pvr.mythtv.cmyth/src/demuxer/ES_AC3.cpp | 251 ++++ addons/pvr.mythtv.cmyth/src/demuxer/ES_AC3.h | 47 + .../src/demuxer/ES_MPEGAudio.cpp | 144 +++ .../src/demuxer/ES_MPEGAudio.h | 46 + .../src/demuxer/ES_MPEGVideo.cpp | 278 +++++ .../src/demuxer/ES_MPEGVideo.h | 58 + .../src/demuxer/ES_Subtitle.cpp | 62 + .../src/demuxer/ES_Subtitle.h | 35 + .../src/demuxer/ES_Teletext.cpp | 57 + .../src/demuxer/ES_Teletext.h | 35 + .../pvr.mythtv.cmyth/src/demuxer/ES_h264.cpp | 583 ++++++++++ addons/pvr.mythtv.cmyth/src/demuxer/ES_h264.h | 111 ++ .../src/demuxer/bitstream.cpp | 143 +++ .../pvr.mythtv.cmyth/src/demuxer/bitstream.h | 50 + addons/pvr.mythtv.cmyth/src/demuxer/common.h | 43 + addons/pvr.mythtv.cmyth/src/demuxer/debug.cpp | 111 ++ addons/pvr.mythtv.cmyth/src/demuxer/debug.h | 44 + .../src/demuxer/elementaryStream.cpp | 279 +++++ .../src/demuxer/elementaryStream.h | 113 ++ .../src/demuxer/tsDemuxer.cpp | 1012 +++++++++++++++++ .../pvr.mythtv.cmyth/src/demuxer/tsDemuxer.h | 130 +++ .../pvr.mythtv.cmyth/src/demuxer/tsPacket.h | 77 ++ addons/pvr.mythtv.cmyth/src/demuxer/tsTable.h | 55 + addons/pvr.mythtv.cmyth/src/fileOps.cpp | 2 +- .../pvr.mythtv.cmyth/src/pvrclient-mythtv.cpp | 270 +++-- .../pvr.mythtv.cmyth/src/pvrclient-mythtv.h | 24 + lib/cmyth/include/cmyth/cmyth.h | 44 +- lib/cmyth/libcmyth/cmyth_local.h | 7 + lib/cmyth/libcmyth/file.c | 2 +- lib/cmyth/libcmyth/keyframe.c | 18 + lib/cmyth/libcmyth/mythtv_mysql.c | 45 +- lib/cmyth/libcmyth/posmap.c | 18 + lib/cmyth/libcmyth/recorder.c | 230 +++- lib/cmyth/libcmyth/recordingrule.c | 42 + lib/cmyth/libcmyth/socket.c | 116 +- 55 files changed, 6241 insertions(+), 181 deletions(-) create mode 100644 addons/pvr.mythtv.cmyth/src/demux.cpp create mode 100644 addons/pvr.mythtv.cmyth/src/demux.h create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/ES_AAC.cpp create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/ES_AAC.h create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/ES_AC3.cpp create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/ES_AC3.h create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/ES_MPEGAudio.cpp create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/ES_MPEGAudio.h create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/ES_MPEGVideo.cpp create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/ES_MPEGVideo.h create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/ES_Subtitle.cpp create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/ES_Subtitle.h create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/ES_Teletext.cpp create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/ES_Teletext.h create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/ES_h264.cpp create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/ES_h264.h create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/bitstream.cpp create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/bitstream.h create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/common.h create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/debug.cpp create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/debug.h create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/elementaryStream.cpp create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/elementaryStream.h create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/tsDemuxer.cpp create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/tsDemuxer.h create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/tsPacket.h create mode 100644 addons/pvr.mythtv.cmyth/src/demuxer/tsTable.h diff --git a/addons/pvr.mythtv.cmyth/Makefile.am b/addons/pvr.mythtv.cmyth/Makefile.am index dcc5901bf..0882dd51e 100644 --- a/addons/pvr.mythtv.cmyth/Makefile.am +++ b/addons/pvr.mythtv.cmyth/Makefile.am @@ -33,4 +33,16 @@ libmythtvcmyth_addon_la_SOURCES = src/client.cpp \ src/cppmyth/MythRecordingRule.cpp \ src/cppmyth/MythTimestamp.cpp \ src/cppmyth/MythEPGInfo.cpp \ - src/cppmyth/MythScheduleManager.cpp + src/cppmyth/MythScheduleManager.cpp \ + src/demux.cpp \ + src/demuxer/debug.cpp \ + src/demuxer/elementaryStream.cpp \ + src/demuxer/tsDemuxer.cpp \ + src/demuxer/bitstream.cpp \ + src/demuxer/ES_MPEGVideo.cpp \ + src/demuxer/ES_MPEGAudio.cpp \ + src/demuxer/ES_h264.cpp \ + src/demuxer/ES_AAC.cpp \ + src/demuxer/ES_AC3.cpp \ + src/demuxer/ES_Subtitle.cpp \ + src/demuxer/ES_Teletext.cpp diff --git a/addons/pvr.mythtv.cmyth/addon/addon.xml.in b/addons/pvr.mythtv.cmyth/addon/addon.xml.in index 16b701a3d..b0c582d89 100644 --- a/addons/pvr.mythtv.cmyth/addon/addon.xml.in +++ b/addons/pvr.mythtv.cmyth/addon/addon.xml.in @@ -1,7 +1,7 @@ diff --git a/addons/pvr.mythtv.cmyth/addon/changelog.txt b/addons/pvr.mythtv.cmyth/addon/changelog.txt index 532ba8733..7e19613d9 100644 --- a/addons/pvr.mythtv.cmyth/addon/changelog.txt +++ b/addons/pvr.mythtv.cmyth/addon/changelog.txt @@ -1,3 +1,16 @@ +v1.9.14 +- Added demuxer (optional) + - Improved timeshifting (GetPlayingTime, GetBufferStart/EndTime) + - Faster channel switching +- Added possibility to start backend using Wake-on-LAN +- Added client actions + - Toogle visibility of recordings that are in state 'Not recording' + - Create special recording rules (series recording) +- Fixed compatibility with MythTV 0.27 backend + - Fixed channel icon download + - Fixed schedule management +- Fixed recognizing merged channels + v1.9.13 - add timeshift buffer functions diff --git a/addons/pvr.mythtv.cmyth/addon/resources/language/English/strings.po b/addons/pvr.mythtv.cmyth/addon/resources/language/English/strings.po index c58912a7a..e59bf0e9d 100644 --- a/addons/pvr.mythtv.cmyth/addon/resources/language/English/strings.po +++ b/addons/pvr.mythtv.cmyth/addon/resources/language/English/strings.po @@ -66,6 +66,10 @@ msgctxt "#30011" msgid "Prefer Live TV and cancel conflicting recording" msgstr "" +msgctxt "#30012" +msgid "MythTV Backend Ethernet address (WOL)" +msgstr "" + msgctxt "#30019" msgid "General" msgstr "" @@ -126,6 +130,14 @@ msgctxt "#30049" msgid "Recording template" msgstr "" +msgctxt "#30050" +msgid "Advanced" +msgstr "" + +msgctxt "#30052" +msgid "Enable demuxing MPEG-TS" +msgstr "" + # Systeminformation labels msgctxt "#30100" msgid "Protocol version: %i - Database version: %i" @@ -172,6 +184,18 @@ msgctxt "#30309" msgid "Not recording" msgstr "" +msgctxt "#30310" +msgid "Enabled" +msgstr "" + +msgctxt "#30311" +msgid "Disabled" +msgstr "" + +msgctxt "#30312" +msgid "No broadcast found" +msgstr "" + # Menu Hooks msgctxt "#30411" msgid "Delete and re-record" @@ -180,3 +204,27 @@ msgstr "" msgctxt "#30412" msgid "Keep LiveTV recording" msgstr "" + +msgctxt "#30421" +msgid "Show/hide rules with status 'Not Recording'" +msgstr "" + +msgctxt "#30431" +msgid "Record all showings (this channel)" +msgstr "" + +msgctxt "#30432" +msgid "Record this showing every week" +msgstr "" + +msgctxt "#30433" +msgid "Record this showing every day" +msgstr "" + +msgctxt "#30434" +msgid "Record one showing (all channels)" +msgstr "" + +msgctxt "#30435" +msgid "Record all new episodes (this channel)" +msgstr "" diff --git a/addons/pvr.mythtv.cmyth/addon/resources/settings.xml b/addons/pvr.mythtv.cmyth/addon/resources/settings.xml index 2830126ea..03b5ca81e 100644 --- a/addons/pvr.mythtv.cmyth/addon/resources/settings.xml +++ b/addons/pvr.mythtv.cmyth/addon/resources/settings.xml @@ -8,6 +8,7 @@ + @@ -26,4 +27,7 @@ + + + diff --git a/addons/pvr.mythtv.cmyth/project/VS2010Express/pvr.mythtv.cmyth.vcxproj b/addons/pvr.mythtv.cmyth/project/VS2010Express/pvr.mythtv.cmyth.vcxproj index 14626f88b..40a7ccd69 100644 --- a/addons/pvr.mythtv.cmyth/project/VS2010Express/pvr.mythtv.cmyth.vcxproj +++ b/addons/pvr.mythtv.cmyth/project/VS2010Express/pvr.mythtv.cmyth.vcxproj @@ -26,6 +26,18 @@ + + + + + + + + + + + + @@ -47,6 +59,21 @@ + + + + + + + + + + + + + + + diff --git a/addons/pvr.mythtv.cmyth/project/VS2010Express/pvr.mythtv.cmyth.vcxproj.filters b/addons/pvr.mythtv.cmyth/project/VS2010Express/pvr.mythtv.cmyth.vcxproj.filters index e1641d7a9..3351009d0 100644 --- a/addons/pvr.mythtv.cmyth/project/VS2010Express/pvr.mythtv.cmyth.vcxproj.filters +++ b/addons/pvr.mythtv.cmyth/project/VS2010Express/pvr.mythtv.cmyth.vcxproj.filters @@ -44,6 +44,40 @@ cppmyth + + demuxer + + + demuxer + + + demuxer + + + demuxer + + + demuxer + + + demuxer + + + demuxer + + + demuxer + + + demuxer + + + demuxer + + + + demuxer + @@ -94,6 +128,49 @@ cppmyth + + demuxer + + + demuxer + + + demuxer + + + demuxer + + + demuxer + + + demuxer + + + demuxer + + + demuxer + + + demuxer + + + demuxer + + + demuxer + + + demuxer + + + demuxer + + + + demuxer + @@ -111,6 +188,9 @@ {3ba85d2f-45bc-4080-8895-55949f7f52a7} + + {af26b031-f1b0-4eed-bdb7-1e12feef27d0} + diff --git a/addons/pvr.mythtv.cmyth/src/client.cpp b/addons/pvr.mythtv.cmyth/src/client.cpp index 7976a66a5..b1bd92bc7 100644 --- a/addons/pvr.mythtv.cmyth/src/client.cpp +++ b/addons/pvr.mythtv.cmyth/src/client.cpp @@ -32,6 +32,7 @@ using namespace ADDON; * and exported to the other source files. */ CStdString g_szMythHostname = DEFAULT_HOST; ///< The Host name or IP of the mythtv server +CStdString g_szMythHostEther = ""; ///< The Host MAC address of the mythtv server int g_iMythPort = DEFAULT_PORT; ///< The mythtv Port (default is 6543) CStdString g_szDBUser = DEFAULT_DB_USER; ///< The mythtv sql username (default is mythtv) CStdString g_szDBPassword = DEFAULT_DB_PASSWORD; ///< The mythtv sql password (default is mythtv) @@ -52,6 +53,7 @@ bool g_bRecAutoRunJob3 = false; bool g_bRecAutoRunJob4 = false; bool g_bRecAutoExpire = false; int g_iRecTranscoder = 0; +bool g_bDemuxing = DEFAULT_HANDLE_DEMUXING; ///* Client member variables */ ADDON_STATUS m_CurStatus = ADDON_STATUS_UNKNOWN; @@ -65,6 +67,7 @@ PVRClientMythTV *g_client = NULL; CHelper_libXBMC_addon *XBMC = NULL; CHelper_libXBMC_pvr *PVR = NULL; CHelper_libXBMC_gui *GUI = NULL; +CHelper_libXBMC_codec *CODEC = NULL; extern "C" { @@ -117,6 +120,15 @@ ADDON_STATUS ADDON_Create(void *hdl, void *props) } XBMC->Log(LOG_DEBUG, "Register handle @ libXBMC_gui...done"); + CODEC = new CHelper_libXBMC_codec; + if (!CODEC->RegisterMe(hdl)) + { + SAFE_DELETE(PVR); + SAFE_DELETE(XBMC); + SAFE_DELETE(GUI); + return ADDON_STATUS_PERMANENT_FAILURE; + } + m_CurStatus = ADDON_STATUS_UNKNOWN; g_szUserPath = pvrprops->strUserPath; g_szClientPath = pvrprops->strClientPath; @@ -250,6 +262,24 @@ ADDON_STATUS ADDON_Create(void *hdl, void *props) if (!XBMC->GetSetting("rec_transcoder", &g_iRecTranscoder)) g_iRecTranscoder = 0; + /* Read setting "demuxing" from settings.xml */ + if (!XBMC->GetSetting("demuxing", &g_bDemuxing)) + { + /* If setting is unknown fallback to defaults */ + XBMC->Log(LOG_ERROR, "Couldn't get 'demuxing' setting, falling back to '%b' as default", DEFAULT_HANDLE_DEMUXING); + g_bDemuxing = DEFAULT_HANDLE_DEMUXING; + } + + /* Read setting "host_ether" from settings.xml */ + if (XBMC->GetSetting("host_ether", buffer)) + g_szMythHostEther = buffer; + else + { + /* If setting is unknown fallback to defaults */ + g_szMythHostEther = ""; + } + buffer[0] = 0; + free (buffer); // Create our addon @@ -259,6 +289,7 @@ ADDON_STATUS ADDON_Create(void *hdl, void *props) { XBMC->Log(LOG_ERROR, "Failed to connect to backend"); SAFE_DELETE(g_client); + SAFE_DELETE(CODEC); SAFE_DELETE(GUI); SAFE_DELETE(PVR); SAFE_DELETE(XBMC); @@ -290,6 +321,42 @@ ADDON_STATUS ADDON_Create(void *hdl, void *props) menuHookKeepLiveTVRec.iLocalizedStringId = 30412; PVR->AddMenuHook(&menuHookKeepLiveTVRec); + PVR_MENUHOOK menuhookSettingShowNR; + menuhookSettingShowNR.category = PVR_MENUHOOK_SETTING; + menuhookSettingShowNR.iHookId = MENUHOOK_SHOW_HIDE_NOT_RECORDING; + menuhookSettingShowNR.iLocalizedStringId = 30421; + PVR->AddMenuHook(&menuhookSettingShowNR); + + PVR_MENUHOOK menuhookEpgRec1; + menuhookEpgRec1.category = PVR_MENUHOOK_EPG; + menuhookEpgRec1.iHookId = MENUHOOK_EPG_REC_CHAN_ALL_SHOWINGS; + menuhookEpgRec1.iLocalizedStringId = 30431; + PVR->AddMenuHook(&menuhookEpgRec1); + + PVR_MENUHOOK menuhookEpgRec2; + menuhookEpgRec2.category = PVR_MENUHOOK_EPG; + menuhookEpgRec2.iHookId = MENUHOOK_EPG_REC_CHAN_WEEKLY; + menuhookEpgRec2.iLocalizedStringId = 30432; + PVR->AddMenuHook(&menuhookEpgRec2); + + PVR_MENUHOOK menuhookEpgRec3; + menuhookEpgRec3.category = PVR_MENUHOOK_EPG; + menuhookEpgRec3.iHookId = MENUHOOK_EPG_REC_CHAN_DAILY; + menuhookEpgRec3.iLocalizedStringId = 30433; + PVR->AddMenuHook(&menuhookEpgRec3); + + PVR_MENUHOOK menuhookEpgRec4; + menuhookEpgRec4.category = PVR_MENUHOOK_EPG; + menuhookEpgRec4.iHookId = MENUHOOK_EPG_REC_ONE_SHOWING; + menuhookEpgRec4.iLocalizedStringId = 30434; + PVR->AddMenuHook(&menuhookEpgRec4); + + PVR_MENUHOOK menuhookEpgRec5; + menuhookEpgRec5.category = PVR_MENUHOOK_EPG; + menuhookEpgRec5.iHookId = MENUHOOK_EPG_REC_NEW_EPISODES; + menuhookEpgRec5.iLocalizedStringId = 30435; + PVR->AddMenuHook(&menuhookEpgRec5); + XBMC->Log(LOG_DEBUG, "MythTV cmyth PVR-Client successfully created"); m_CurStatus = ADDON_STATUS_OK; g_bCreated = true; @@ -305,6 +372,12 @@ void ADDON_Destroy() g_bCreated = false; } + if (CODEC) + { + delete(CODEC); + CODEC = NULL; + } + if (PVR) { delete(PVR); @@ -413,6 +486,12 @@ ADDON_STATUS ADDON_SetSetting(const char *settingName, const void *settingValue) if (tmp_sDBName != g_szDBName) return ADDON_STATUS_NEED_RESTART; } + else if (str == "demuxing") + { + XBMC->Log(LOG_INFO, "Changed Setting 'demuxing' from %u to %u", g_bDemuxing, *(bool*)settingValue); + if (g_bDemuxing != *(bool*)settingValue) + return ADDON_STATUS_NEED_RESTART; + } else if (str == "extradebug") { XBMC->Log(LOG_INFO, "Changed Setting 'extra debug' from %u to %u", g_bExtraDebug, *(bool*)settingValue); @@ -550,7 +629,7 @@ PVR_ERROR GetAddonCapabilities(PVR_ADDON_CAPABILITIES *pCapabilities) pCapabilities->bSupportsTimers = true; pCapabilities->bHandlesInputStream = true; - pCapabilities->bHandlesDemuxing = false; + pCapabilities->bHandlesDemuxing = g_bDemuxing; pCapabilities->bSupportsRecordings = true; pCapabilities->bSupportsRecordingPlayCount = true; @@ -564,12 +643,6 @@ PVR_ERROR GetAddonCapabilities(PVR_ADDON_CAPABILITIES *pCapabilities) } } -PVR_ERROR GetStreamProperties(PVR_STREAM_PROPERTIES* props) -{ - (void)props; - return PVR_ERROR_NOT_IMPLEMENTED; -} - const char *GetBackendName() { return g_client->GetBackendName(); @@ -950,18 +1023,72 @@ long long LengthRecordedStream(void) return g_client->LengthRecordedStream(); } +/*******************************************/ +/** PVR Demux Functions **/ + +PVR_ERROR GetStreamProperties(PVR_STREAM_PROPERTIES* pProperties) +{ + if (g_client == NULL) + return PVR_ERROR_SERVER_ERROR; + + return g_client->GetStreamProperties(pProperties); +} + +void DemuxAbort(void) +{ + if (g_client != NULL) + g_client->DemuxAbort(); +} + +DemuxPacket* DemuxRead(void) +{ + if (g_client == NULL) + return NULL; + + return g_client->DemuxRead(); +} + +void DemuxFlush(void) +{ + if (g_client != NULL) + g_client->DemuxFlush(); +} + +bool SeekTime(int time, bool backwards, double *startpts) +{ + if (g_client != NULL) + return g_client->SeekTime(time, backwards, startpts); + return false; +} + +/*******************************************/ +/** PVR Timeshift Functions **/ + +time_t GetPlayingTime() +{ + if (g_client != NULL) + return g_client->GetPlayingTime(); + return 0; +} + +time_t GetBufferTimeStart() +{ + if (g_client != NULL) + return g_client->GetBufferTimeStart(); + return 0; +} + +time_t GetBufferTimeEnd() +{ + if (g_client != NULL) + return g_client->GetBufferTimeEnd(); + return 0; +} /*******************************************/ /** Unused API Functions **/ -DemuxPacket* DemuxRead() { return NULL; } -void DemuxAbort() {} void DemuxReset() {} -void DemuxFlush() {} const char * GetLiveStreamURL(const PVR_CHANNEL &) { return ""; } -bool SeekTime(int,bool,double*) { return false; } void SetSpeed(int) {}; -time_t GetPlayingTime() { return 0; } -time_t GetBufferTimeStart() { return 0; } -time_t GetBufferTimeEnd() { return 0; } } //end extern "C" diff --git a/addons/pvr.mythtv.cmyth/src/client.h b/addons/pvr.mythtv.cmyth/src/client.h index 214958417..222a2d67b 100644 --- a/addons/pvr.mythtv.cmyth/src/client.h +++ b/addons/pvr.mythtv.cmyth/src/client.h @@ -27,6 +27,7 @@ #include #include #include +#include extern "C" { #include @@ -74,6 +75,14 @@ static inline struct tm *localtime_r(const time_t * clock, struct tm *result) #define MENUHOOK_REC_DELETE_AND_RERECORD 1 #define MENUHOOK_KEEP_LIVETV_RECORDING 2 +#define MENUHOOK_SHOW_HIDE_NOT_RECORDING 3 +#define MENUHOOK_EPG_REC_CHAN_ALL_SHOWINGS 4 +#define MENUHOOK_EPG_REC_CHAN_WEEKLY 5 +#define MENUHOOK_EPG_REC_CHAN_DAILY 6 +#define MENUHOOK_EPG_REC_ONE_SHOWING 7 +#define MENUHOOK_EPG_REC_NEW_EPISODES 8 + +#define DEFAULT_HANDLE_DEMUXING false /*! * @brief PVR macros for string exchange @@ -92,6 +101,7 @@ extern CStdString g_szClientPath; ///< The Path where this driver i /* Client Settings */ extern CStdString g_szMythHostname; ///< The Host name or IP of the mythtv server +extern CStdString g_szMythHostEther; ///< The Host MAC address of the mythtv server extern int g_iMythPort; ///< The mythtv Port (default is 6543) extern CStdString g_szDBUser; ///< The mythtv sql username (default is mythtv) extern CStdString g_szDBPassword; ///< The mythtv sql password (default is mythtv) @@ -113,9 +123,11 @@ extern bool g_bRecAutoRunJob3; extern bool g_bRecAutoRunJob4; extern bool g_bRecAutoExpire; extern int g_iRecTranscoder; +extern bool g_bDemuxing; extern ADDON::CHelper_libXBMC_addon *XBMC; extern CHelper_libXBMC_pvr *PVR; extern CHelper_libXBMC_gui *GUI; +extern CHelper_libXBMC_codec *CODEC; #endif /* CLIENT_H */ diff --git a/addons/pvr.mythtv.cmyth/src/cppmyth/MythConnection.cpp b/addons/pvr.mythtv.cmyth/src/cppmyth/MythConnection.cpp index d681e8831..9a6914ec2 100644 --- a/addons/pvr.mythtv.cmyth/src/cppmyth/MythConnection.cpp +++ b/addons/pvr.mythtv.cmyth/src/cppmyth/MythConnection.cpp @@ -134,6 +134,10 @@ bool MythConnection::IsConnected() bool MythConnection::TryReconnect() { int retval; + + if (!g_szMythHostEther.IsEmpty()) + XBMC->WakeOnLan(g_szMythHostEther); + Lock(); if (m_playback) retval = cmyth_conn_reconnect_playback(*m_conn_t); diff --git a/addons/pvr.mythtv.cmyth/src/cppmyth/MythEventHandler.cpp b/addons/pvr.mythtv.cmyth/src/cppmyth/MythEventHandler.cpp index 5c2bb48df..a86719367 100644 --- a/addons/pvr.mythtv.cmyth/src/cppmyth/MythEventHandler.cpp +++ b/addons/pvr.mythtv.cmyth/src/cppmyth/MythEventHandler.cpp @@ -102,6 +102,7 @@ class MythEventHandler::MythEventHandlerPrivate : public CThread bool m_playback; bool m_hang; + bool m_sendWOL; CStdString m_currentRecordID; MythFile m_currentFile; @@ -122,6 +123,7 @@ MythEventHandler::MythEventHandlerPrivate::MythEventHandlerPrivate(const CStdStr , m_signal() , m_playback(false) , m_hang(false) + , m_sendWOL(false) , m_recordingChangeEventList() , m_recordingChangePinCount(0) { @@ -535,6 +537,10 @@ void MythEventHandler::MythEventHandlerPrivate::RetryConnect() m_hang = true; while (!IsStopped()) { + // wake up the backend sending magic packet + if (m_sendWOL && !g_szMythHostEther.IsEmpty()) + XBMC->WakeOnLan(g_szMythHostEther); + usleep(999999); ref_release(*m_conn_t); *m_conn_t = NULL; @@ -547,6 +553,7 @@ void MythEventHandler::MythEventHandlerPrivate::RetryConnect() XBMC->Log(LOG_NOTICE, "%s - Connected client to event socket", __FUNCTION__); XBMC->QueueNotification(QUEUE_INFO, XBMC->GetLocalizedString(30303)); // MythTV backend available m_hang = false; + m_sendWOL = false; HandleRecordingListChange(); // Reload all recordings break; } @@ -592,10 +599,11 @@ void MythEventHandler::Suspend() } } -void MythEventHandler::Resume() +void MythEventHandler::Resume(bool sendWOL) { if (m_imp->IsStopped()) { + m_imp->m_sendWOL = sendWOL; m_imp->m_lock.Clear(); m_imp->CreateThread(); } diff --git a/addons/pvr.mythtv.cmyth/src/cppmyth/MythEventHandler.h b/addons/pvr.mythtv.cmyth/src/cppmyth/MythEventHandler.h index f22c3ecdf..54d102142 100644 --- a/addons/pvr.mythtv.cmyth/src/cppmyth/MythEventHandler.h +++ b/addons/pvr.mythtv.cmyth/src/cppmyth/MythEventHandler.h @@ -54,7 +54,7 @@ class MythEventHandler void RegisterObserver(MythEventObserver *observer); void Suspend(); - void Resume(); + void Resume(bool sendWOL = false); void PreventLiveChainUpdate(); void AllowLiveChainUpdate(); diff --git a/addons/pvr.mythtv.cmyth/src/cppmyth/MythRecordingRule.cpp b/addons/pvr.mythtv.cmyth/src/cppmyth/MythRecordingRule.cpp index 55a6934bb..809b077f4 100644 --- a/addons/pvr.mythtv.cmyth/src/cppmyth/MythRecordingRule.cpp +++ b/addons/pvr.mythtv.cmyth/src/cppmyth/MythRecordingRule.cpp @@ -387,3 +387,29 @@ void MythRecordingRule::SetFilter(unsigned int filter) { cmyth_recordingrule_set_filter(*m_recordingrule_t, filter); } + +CStdString MythRecordingRule::ProgramID() const +{ + char *buf = cmyth_recordingrule_programid(*m_recordingrule_t); + CStdString retval(buf); + ref_release(buf); + return retval; +} + +void MythRecordingRule::SetProgramID(const CStdString &programid) +{ + cmyth_recordingrule_set_programid(*m_recordingrule_t, const_cast(programid.c_str())); +} + +CStdString MythRecordingRule::SeriesID() const +{ + char *buf = cmyth_recordingrule_seriesid(*m_recordingrule_t); + CStdString retval(buf); + ref_release(buf); + return retval; +} + +void MythRecordingRule::SetSeriesID(const CStdString &seriesid) +{ + cmyth_recordingrule_set_seriesid(*m_recordingrule_t, const_cast(seriesid.c_str())); +} diff --git a/addons/pvr.mythtv.cmyth/src/cppmyth/MythRecordingRule.h b/addons/pvr.mythtv.cmyth/src/cppmyth/MythRecordingRule.h index bff0f76cb..58f803622 100644 --- a/addons/pvr.mythtv.cmyth/src/cppmyth/MythRecordingRule.h +++ b/addons/pvr.mythtv.cmyth/src/cppmyth/MythRecordingRule.h @@ -88,7 +88,8 @@ class MythRecordingRule FM_ThisEpisode = 0x040, FM_ThisSeries = 0x080, FM_ThisTime = 0x100, - FM_ThisDayAndTime = 0x200 + FM_ThisDayAndTime = 0x200, + FM_ThisChannel = 0x400 }; MythRecordingRule(); @@ -191,6 +192,12 @@ class MythRecordingRule unsigned int Filter() const; void SetFilter(unsigned int filter); + CStdString ProgramID() const; + void SetProgramID(const CStdString &programid); + + CStdString SeriesID() const; + void SetSeriesID(const CStdString &seriesid); + private: boost::shared_ptr > m_recordingrule_t; }; diff --git a/addons/pvr.mythtv.cmyth/src/cppmyth/MythScheduleManager.cpp b/addons/pvr.mythtv.cmyth/src/cppmyth/MythScheduleManager.cpp index 343c66da9..a7218d74d 100644 --- a/addons/pvr.mythtv.cmyth/src/cppmyth/MythScheduleManager.cpp +++ b/addons/pvr.mythtv.cmyth/src/cppmyth/MythScheduleManager.cpp @@ -108,6 +108,7 @@ MythScheduleManager::MythScheduleManager() , m_db() , m_dbSchemaVersion(0) , m_versionHelper(new MythScheduleHelperNoHelper()) + , m_showNotRecording(false) { } @@ -115,6 +116,7 @@ MythScheduleManager::MythScheduleManager(MythConnection &con, MythDatabase &db) : m_con(con) , m_db(db) , m_dbSchemaVersion(0) + , m_showNotRecording(false) { this->Update(); } @@ -160,6 +162,10 @@ ScheduleList MythScheduleManager::GetUpcomingRecordings() MythScheduleManager::MSM_ERROR MythScheduleManager::ScheduleRecording(const MythRecordingRule &rule) { + // Don't schedule nil + if (rule.Type() == MythRecordingRule::RT_NotRecording) + return MSM_ERROR_FAILED; + if (!m_db.AddRecordingRule(rule)) return MSM_ERROR_FAILED; @@ -630,22 +636,25 @@ void MythScheduleManager::Update() } // Add missed programs (NOT RECORDING) to upcoming recordings. User could delete them as needed. - //ProgramInfoMap schedule = m_con.GetScheduledPrograms(); - //for (ProgramInfoMap::iterator it = schedule.begin(); it != schedule.end(); ++it) - //{ - // if (m_recordingIndexByRuleId.count(it->second.RecordID()) == 0) - // { - // NodeById::const_iterator itr = m_rulesById.find(it->second.RecordID()); - // if (itr != m_rulesById.end() && !itr->second->HasOverrideRules()) - // { - // boost::shared_ptr rec = boost::shared_ptr(new MythProgramInfo()); - // *rec = it->second; - // unsigned int index = MakeIndex(it->second); - // m_recordings.insert(RecordingList::value_type(index, rec)); - // m_recordingIndexByRuleId.insert(std::make_pair(it->second.RecordID(), index)); - // } - // } - //} + if (m_showNotRecording) + { + ProgramInfoMap schedule = m_con.GetScheduledPrograms(); + for (ProgramInfoMap::iterator it = schedule.begin(); it != schedule.end(); ++it) + { + if (m_recordingIndexByRuleId.count(it->second.RecordID()) == 0) + { + NodeById::const_iterator itr = m_rulesById.find(it->second.RecordID()); + if (itr != m_rulesById.end() && !itr->second->HasOverrideRules()) + { + boost::shared_ptr rec = boost::shared_ptr(new MythProgramInfo()); + *rec = it->second; + unsigned int index = MakeIndex(it->second); + m_recordings.insert(RecordingList::value_type(index, rec)); + m_recordingIndexByRuleId.insert(std::make_pair(it->second.RecordID(), index)); + } + } + } + } if (g_bExtraDebug) { @@ -656,6 +665,11 @@ void MythScheduleManager::Update() } } +RuleMetadata MythScheduleManager::GetMetadata(const MythRecordingRule &rule) const +{ + return m_versionHelper->GetMetadata(rule); +} + MythRecordingRule MythScheduleManager::NewFromTemplate(MythEPGInfo &epgInfo) { return m_versionHelper->NewFromTemplate(epgInfo); @@ -676,14 +690,20 @@ MythRecordingRule MythScheduleManager::NewWeeklyRecord(MythEPGInfo &epgInfo) return m_versionHelper->NewWeeklyRecord(epgInfo); } -MythRecordingRule MythScheduleManager::NewChannelRecord(const CStdString &searchTitle) +MythRecordingRule MythScheduleManager::NewChannelRecord(MythEPGInfo &epgInfo) { - return m_versionHelper->NewChannelRecord(searchTitle); + return m_versionHelper->NewChannelRecord(epgInfo); } -MythRecordingRule MythScheduleManager::NewOneRecord(const CStdString &searchTitle) +MythRecordingRule MythScheduleManager::NewOneRecord(MythEPGInfo &epgInfo) { - return m_versionHelper->NewOneRecord(searchTitle); + return m_versionHelper->NewOneRecord(epgInfo); +} + +bool MythScheduleManager::ToggleShowNotRecording() +{ + m_showNotRecording ^= true; + return m_showNotRecording; } /////////////////////////////////////////////////////////////////////////////// @@ -698,7 +718,17 @@ bool MythScheduleHelperNoHelper::SameTimeslot(MythRecordingRule &first, MythReco return false; } -MythRecordingRule MythScheduleHelperNoHelper::NewFromTemplate(MythEPGInfo& epgInfo) +RuleMetadata MythScheduleHelperNoHelper::GetMetadata(const MythRecordingRule &rule) const +{ + RuleMetadata meta; + (void)rule; + meta.isRepeating = false; + meta.weekDays = 0; + meta.marker = ""; + return meta; +} + +MythRecordingRule MythScheduleHelperNoHelper::NewFromTemplate(MythEPGInfo &epgInfo) { (void)epgInfo; return MythRecordingRule(); @@ -722,15 +752,15 @@ MythRecordingRule MythScheduleHelperNoHelper::NewWeeklyRecord(MythEPGInfo &epgIn return MythRecordingRule(); } -MythRecordingRule MythScheduleHelperNoHelper::NewChannelRecord(const CStdString &searchTitle) +MythRecordingRule MythScheduleHelperNoHelper::NewChannelRecord(MythEPGInfo &epgInfo) { - (void)searchTitle; + (void)epgInfo; return MythRecordingRule(); } -MythRecordingRule MythScheduleHelperNoHelper::NewOneRecord(const CStdString &searchTitle) +MythRecordingRule MythScheduleHelperNoHelper::NewOneRecord(MythEPGInfo &epgInfo) { - (void)searchTitle; + (void)epgInfo; return MythRecordingRule(); } @@ -799,6 +829,58 @@ bool MythScheduleHelper1226::SameTimeslot(MythRecordingRule &first, MythRecordin return false; } +RuleMetadata MythScheduleHelper1226::GetMetadata(const MythRecordingRule &rule) const +{ + RuleMetadata meta; + time_t st = rule.StartTime(); + meta.isRepeating = false; + meta.weekDays = 0; + meta.marker = ""; + switch (rule.Type()) + { + case MythRecordingRule::RT_DailyRecord: + case MythRecordingRule::RT_FindDailyRecord: + meta.isRepeating = true; + meta.weekDays = 0x7F; + meta.marker = "d"; + break; + case MythRecordingRule::RT_WeeklyRecord: + case MythRecordingRule::RT_FindWeeklyRecord: + meta.isRepeating = true; + meta.weekDays = 1 << ((weekday(&st) + 6) % 7); + meta.marker = "w"; + break; + case MythRecordingRule::RT_ChannelRecord: + meta.isRepeating = true; + meta.weekDays = 0x7F; + meta.marker = "C"; + break; + case MythRecordingRule::RT_AllRecord: + meta.isRepeating = true; + meta.weekDays = 0x7F; + meta.marker = "A"; + break; + case MythRecordingRule::RT_OneRecord: + meta.isRepeating = false; + meta.weekDays = 0; + meta.marker = "1"; + break; + case MythRecordingRule::RT_DontRecord: + meta.isRepeating = false; + meta.weekDays = 0; + meta.marker = "x"; + break; + case MythRecordingRule::RT_OverrideRecord: + meta.isRepeating = false; + meta.weekDays = 0; + meta.marker = "o"; + break; + default: + break; + } + return meta; +} + MythRecordingRule MythScheduleHelper1226::NewFromTemplate(MythEPGInfo &epgInfo) { MythRecordingRule rule; @@ -850,6 +932,8 @@ MythRecordingRule MythScheduleHelper1226::NewSingleRecord(MythEPGInfo &epgInfo) rule.SetCategory(epgInfo.Category()); rule.SetDescription(epgInfo.Description()); rule.SetCallsign(epgInfo.Callsign()); + rule.SetProgramID(epgInfo.ProgramID()); + rule.SetSeriesID(epgInfo.SeriesID()); } else { @@ -862,7 +946,7 @@ MythRecordingRule MythScheduleHelper1226::NewSingleRecord(MythEPGInfo &epgInfo) return rule; } -MythRecordingRule MythScheduleHelper1226::NewDailyRecord(MythEPGInfo& epgInfo) +MythRecordingRule MythScheduleHelper1226::NewDailyRecord(MythEPGInfo &epgInfo) { MythRecordingRule rule = this->NewFromTemplate(epgInfo); @@ -879,6 +963,8 @@ MythRecordingRule MythScheduleHelper1226::NewDailyRecord(MythEPGInfo& epgInfo) rule.SetCategory(epgInfo.Category()); rule.SetDescription(epgInfo.Description()); rule.SetCallsign(epgInfo.Callsign()); + rule.SetProgramID(epgInfo.ProgramID()); + rule.SetSeriesID(epgInfo.SeriesID()); } else { @@ -891,7 +977,7 @@ MythRecordingRule MythScheduleHelper1226::NewDailyRecord(MythEPGInfo& epgInfo) return rule; } -MythRecordingRule MythScheduleHelper1226::NewWeeklyRecord(MythEPGInfo& epgInfo) +MythRecordingRule MythScheduleHelper1226::NewWeeklyRecord(MythEPGInfo &epgInfo) { MythRecordingRule rule = this->NewFromTemplate(epgInfo); @@ -908,6 +994,8 @@ MythRecordingRule MythScheduleHelper1226::NewWeeklyRecord(MythEPGInfo& epgInfo) rule.SetCategory(epgInfo.Category()); rule.SetDescription(epgInfo.Description()); rule.SetCallsign(epgInfo.Callsign()); + rule.SetProgramID(epgInfo.ProgramID()); + rule.SetSeriesID(epgInfo.SeriesID()); } else { @@ -920,30 +1008,64 @@ MythRecordingRule MythScheduleHelper1226::NewWeeklyRecord(MythEPGInfo& epgInfo) return rule; } -MythRecordingRule MythScheduleHelper1226::NewChannelRecord(const CStdString &searchTitle) +MythRecordingRule MythScheduleHelper1226::NewChannelRecord(MythEPGInfo &epgInfo) { - // Backend use the description to find program by keywords or title - MythEPGInfo epgInfo; MythRecordingRule rule = this->NewFromTemplate(epgInfo); + rule.SetType(MythRecordingRule::RT_ChannelRecord); - rule.SetSearchType(MythRecordingRule::ST_TitleSearch); - rule.SetSubtitle(""); - rule.SetDescription(searchTitle); + + if (!epgInfo.IsNull()) + { + rule.SetSearchType(MythRecordingRule::ST_TitleSearch); + rule.SetChannelID(epgInfo.ChannelID()); + rule.SetStartTime(epgInfo.StartTime()); + rule.SetEndTime(epgInfo.EndTime()); + rule.SetTitle(epgInfo.Title()); + // Backend use the description to find program by keywords or title + rule.SetSubtitle(""); + rule.SetDescription(epgInfo.Title()); + rule.SetCategory(epgInfo.Category()); + rule.SetCallsign(epgInfo.Callsign()); + rule.SetProgramID(epgInfo.ProgramID()); + rule.SetSeriesID(epgInfo.SeriesID()); + } + else + { + // Not feasible + rule.SetType(MythRecordingRule::RT_NotRecording); + } rule.SetDuplicateControlMethod(MythRecordingRule::DM_CheckSubtitleAndDescription); rule.SetCheckDuplicatesInType(MythRecordingRule::DI_InAll); rule.SetInactive(false); return rule; } -MythRecordingRule MythScheduleHelper1226::NewOneRecord(const CStdString &searchTitle) +MythRecordingRule MythScheduleHelper1226::NewOneRecord(MythEPGInfo &epgInfo) { - // Backend use the description to find program by keywords or title - MythEPGInfo epgInfo; MythRecordingRule rule = this->NewFromTemplate(epgInfo); + rule.SetType(MythRecordingRule::RT_OneRecord); - rule.SetSearchType(MythRecordingRule::ST_TitleSearch); - rule.SetSubtitle(""); - rule.SetDescription(searchTitle); + + if (!epgInfo.IsNull()) + { + rule.SetSearchType(MythRecordingRule::ST_TitleSearch); + rule.SetChannelID(epgInfo.ChannelID()); + rule.SetStartTime(epgInfo.StartTime()); + rule.SetEndTime(epgInfo.EndTime()); + rule.SetTitle(epgInfo.Title()); + // Backend use the description to find program by keywords or title + rule.SetSubtitle(""); + rule.SetDescription(epgInfo.Title()); + rule.SetCategory(epgInfo.Category()); + rule.SetCallsign(epgInfo.Callsign()); + rule.SetProgramID(epgInfo.ProgramID()); + rule.SetSeriesID(epgInfo.SeriesID()); + } + else + { + // Not feasible + rule.SetType(MythRecordingRule::RT_NotRecording); + } rule.SetDuplicateControlMethod(MythRecordingRule::DM_CheckSubtitleAndDescription); rule.SetCheckDuplicatesInType(MythRecordingRule::DI_InAll); rule.SetInactive(false); @@ -1128,5 +1250,209 @@ MythRecordingRule MythScheduleHelper1302::NewFromTemplate(MythEPGInfo &epgInfo) //// types are automatically converted to the suggested alternatives. //// -// TODO +RuleMetadata MythScheduleHelper1309::GetMetadata(const MythRecordingRule &rule) const +{ + RuleMetadata meta; + time_t st = rule.StartTime(); + meta.isRepeating = false; + meta.weekDays = 0; + meta.marker = ""; + switch (rule.Type()) + { + case MythRecordingRule::RT_DailyRecord: + case MythRecordingRule::RT_FindDailyRecord: + meta.isRepeating = true; + meta.weekDays = 0x7F; + meta.marker = "d"; + break; + case MythRecordingRule::RT_WeeklyRecord: + case MythRecordingRule::RT_FindWeeklyRecord: + meta.isRepeating = true; + meta.weekDays = 1 << ((weekday(&st) + 6) % 7); + meta.marker = "w"; + break; + case MythRecordingRule::RT_ChannelRecord: + meta.isRepeating = true; + meta.weekDays = 0x7F; + meta.marker = "C"; + break; + case MythRecordingRule::RT_AllRecord: + meta.isRepeating = true; + if ((rule.Filter() & MythRecordingRule::FM_ThisDayAndTime)) + { + meta.weekDays = 1 << ((weekday(&st) + 6) % 7); + meta.marker = "w"; + } + else if ((rule.Filter() & MythRecordingRule::FM_ThisTime)) + { + meta.weekDays = 0x7F; + meta.marker = "d"; + } + else + { + meta.weekDays = 0x7F; + meta.marker = "A"; + } + break; + case MythRecordingRule::RT_OneRecord: + meta.isRepeating = false; + meta.weekDays = 0; + meta.marker = "1"; + break; + case MythRecordingRule::RT_DontRecord: + meta.isRepeating = false; + meta.weekDays = 0; + meta.marker = "x"; + break; + case MythRecordingRule::RT_OverrideRecord: + meta.isRepeating = false; + meta.weekDays = 0; + meta.marker = "o"; + break; + default: + break; + } + return meta; +} + +MythRecordingRule MythScheduleHelper1309::NewDailyRecord(MythEPGInfo &epgInfo) +{ + unsigned int filter; + MythRecordingRule rule = this->NewFromTemplate(epgInfo); + rule.SetType(MythRecordingRule::RT_AllRecord); + filter = MythRecordingRule::FM_ThisChannel + MythRecordingRule::FM_ThisTime; + rule.SetFilter(filter); + + if (!epgInfo.IsNull()) + { + rule.SetSearchType(MythRecordingRule::ST_NoSearch); + rule.SetChannelID(epgInfo.ChannelID()); + rule.SetStartTime(epgInfo.StartTime()); + rule.SetEndTime(epgInfo.EndTime()); + rule.SetTitle(epgInfo.Title()); + rule.SetSubtitle(epgInfo.Subtitle()); + rule.SetCategory(epgInfo.Category()); + rule.SetDescription(epgInfo.Description()); + rule.SetCallsign(epgInfo.Callsign()); + rule.SetProgramID(epgInfo.ProgramID()); + rule.SetSeriesID(epgInfo.SeriesID()); + } + else + { + // No EPG! Create custom daily for this channel + rule.SetType(MythRecordingRule::RT_DailyRecord); + rule.SetFilter(MythRecordingRule::FM_ThisChannel); + // kManualSearch = http://www.gossamer-threads.com/lists/mythtv/dev/155150?search_string=kManualSearch;#155150 + rule.SetSearchType(MythRecordingRule::ST_ManualSearch); + } + rule.SetDuplicateControlMethod(MythRecordingRule::DM_CheckSubtitleAndDescription); + rule.SetCheckDuplicatesInType(MythRecordingRule::DI_InAll); + rule.SetInactive(false); + return rule; +} + +MythRecordingRule MythScheduleHelper1309::NewWeeklyRecord(MythEPGInfo &epgInfo) +{ + unsigned int filter; + MythRecordingRule rule = this->NewFromTemplate(epgInfo); + + rule.SetType(MythRecordingRule::RT_AllRecord); + filter = MythRecordingRule::FM_ThisChannel + MythRecordingRule::FM_ThisDayAndTime; + rule.SetFilter(filter); + + if (!epgInfo.IsNull()) + { + rule.SetSearchType(MythRecordingRule::ST_NoSearch); + rule.SetChannelID(epgInfo.ChannelID()); + rule.SetStartTime(epgInfo.StartTime()); + rule.SetEndTime(epgInfo.EndTime()); + rule.SetTitle(epgInfo.Title()); + rule.SetSubtitle(epgInfo.Subtitle()); + rule.SetCategory(epgInfo.Category()); + rule.SetDescription(epgInfo.Description()); + rule.SetCallsign(epgInfo.Callsign()); + rule.SetProgramID(epgInfo.ProgramID()); + rule.SetSeriesID(epgInfo.SeriesID()); + } + else + { + // No EPG! Create custom weekly for this channel + rule.SetType(MythRecordingRule::RT_WeeklyRecord); + rule.SetFilter(MythRecordingRule::FM_ThisChannel); + // kManualSearch = http://www.gossamer-threads.com/lists/mythtv/dev/155150?search_string=kManualSearch;#155150 + rule.SetSearchType(MythRecordingRule::ST_ManualSearch); + } + rule.SetDuplicateControlMethod(MythRecordingRule::DM_CheckSubtitleAndDescription); + rule.SetCheckDuplicatesInType(MythRecordingRule::DI_InAll); + rule.SetInactive(false); + return rule; +} + +MythRecordingRule MythScheduleHelper1309::NewChannelRecord(MythEPGInfo &epgInfo) +{ + unsigned int filter; + MythRecordingRule rule = this->NewFromTemplate(epgInfo); + + rule.SetType(MythRecordingRule::RT_AllRecord); + filter = MythRecordingRule::FM_ThisChannel; + rule.SetFilter(filter); + + if (!epgInfo.IsNull()) + { + rule.SetSearchType(MythRecordingRule::ST_NoSearch); + rule.SetChannelID(epgInfo.ChannelID()); + rule.SetStartTime(epgInfo.StartTime()); + rule.SetEndTime(epgInfo.EndTime()); + rule.SetTitle(epgInfo.Title()); + rule.SetSubtitle(epgInfo.Subtitle()); + rule.SetCategory(epgInfo.Category()); + rule.SetDescription(epgInfo.Description()); + rule.SetCallsign(epgInfo.Callsign()); + rule.SetProgramID(epgInfo.ProgramID()); + rule.SetSeriesID(epgInfo.SeriesID()); + } + else + { + // Not feasible + rule.SetType(MythRecordingRule::RT_NotRecording); + } + rule.SetDuplicateControlMethod(MythRecordingRule::DM_CheckSubtitleAndDescription); + rule.SetCheckDuplicatesInType(MythRecordingRule::DI_InAll); + rule.SetInactive(false); + return rule; +} + +MythRecordingRule MythScheduleHelper1309::NewOneRecord(MythEPGInfo &epgInfo) +{ + unsigned int filter; + MythRecordingRule rule = this->NewFromTemplate(epgInfo); + + rule.SetType(MythRecordingRule::RT_OneRecord); + filter = MythRecordingRule::FM_ThisEpisode; + rule.SetFilter(filter); + + if (!epgInfo.IsNull()) + { + rule.SetSearchType(MythRecordingRule::ST_NoSearch); + rule.SetChannelID(epgInfo.ChannelID()); + rule.SetStartTime(epgInfo.StartTime()); + rule.SetEndTime(epgInfo.EndTime()); + rule.SetTitle(epgInfo.Title()); + rule.SetSubtitle(epgInfo.Subtitle()); + rule.SetCategory(epgInfo.Category()); + rule.SetDescription(epgInfo.Description()); + rule.SetCallsign(epgInfo.Callsign()); + rule.SetProgramID(epgInfo.ProgramID()); + rule.SetSeriesID(epgInfo.SeriesID()); + } + else + { + // Not feasible + rule.SetType(MythRecordingRule::RT_NotRecording); + } + rule.SetDuplicateControlMethod(MythRecordingRule::DM_CheckSubtitleAndDescription); + rule.SetCheckDuplicatesInType(MythRecordingRule::DI_InAll); + rule.SetInactive(false); + return rule; +} diff --git a/addons/pvr.mythtv.cmyth/src/cppmyth/MythScheduleManager.h b/addons/pvr.mythtv.cmyth/src/cppmyth/MythScheduleManager.h index 059d736ca..1d51100d9 100644 --- a/addons/pvr.mythtv.cmyth/src/cppmyth/MythScheduleManager.h +++ b/addons/pvr.mythtv.cmyth/src/cppmyth/MythScheduleManager.h @@ -36,6 +36,13 @@ typedef std::vector OverrideRuleList; // Schedule element is pair < index of schedule , program info of schedule > typedef std::vector > > ScheduleList; +typedef struct +{ + bool isRepeating; + int weekDays; + const char* marker; +} RuleMetadata; + class MythRecordingRuleNode { public: @@ -97,20 +104,24 @@ class MythScheduleManager VersionHelper() {} virtual ~VersionHelper(); virtual bool SameTimeslot(MythRecordingRule &first, MythRecordingRule &second) const = 0; + virtual RuleMetadata GetMetadata(const MythRecordingRule &rule) const = 0; virtual MythRecordingRule NewFromTemplate(MythEPGInfo &epgInfo) = 0; virtual MythRecordingRule NewSingleRecord(MythEPGInfo &epgInfo) = 0; virtual MythRecordingRule NewDailyRecord(MythEPGInfo &epgInfo) = 0; virtual MythRecordingRule NewWeeklyRecord(MythEPGInfo &epgInfo) = 0; - virtual MythRecordingRule NewChannelRecord(const CStdString &searchTitle) = 0; - virtual MythRecordingRule NewOneRecord(const CStdString &searchTitle) = 0; + virtual MythRecordingRule NewChannelRecord(MythEPGInfo &epgInfo) = 0; + virtual MythRecordingRule NewOneRecord(MythEPGInfo &epgInfo) = 0; }; + RuleMetadata GetMetadata(const MythRecordingRule &rule) const; MythRecordingRule NewFromTemplate(MythEPGInfo &epgInfo); MythRecordingRule NewSingleRecord(MythEPGInfo &epgInfo); MythRecordingRule NewDailyRecord(MythEPGInfo &epgInfo); MythRecordingRule NewWeeklyRecord(MythEPGInfo &epgInfo); - MythRecordingRule NewChannelRecord(const CStdString &searchTitle); - MythRecordingRule NewOneRecord(const CStdString &searchTitle); + MythRecordingRule NewChannelRecord(MythEPGInfo &epgInfo); + MythRecordingRule NewOneRecord(MythEPGInfo &epgInfo); + + bool ToggleShowNotRecording(); private: mutable PLATFORM::CMutex m_lock; @@ -136,6 +147,8 @@ class MythScheduleManager NodeById m_rulesById; RecordingList m_recordings; RecordingIndexByRuleId m_recordingIndexByRuleId; + + bool m_showNotRecording; }; @@ -152,12 +165,13 @@ inline MythScheduleManager::VersionHelper::~VersionHelper() { class MythScheduleHelperNoHelper : public MythScheduleManager::VersionHelper { public: virtual bool SameTimeslot(MythRecordingRule &first, MythRecordingRule &second) const; + virtual RuleMetadata GetMetadata(const MythRecordingRule &rule) const; virtual MythRecordingRule NewFromTemplate(MythEPGInfo &epgInfo); virtual MythRecordingRule NewSingleRecord(MythEPGInfo &epgInfo); virtual MythRecordingRule NewDailyRecord(MythEPGInfo &epgInfo); virtual MythRecordingRule NewWeeklyRecord(MythEPGInfo &epgInfo); - virtual MythRecordingRule NewChannelRecord(const CStdString &searchTitle); - virtual MythRecordingRule NewOneRecord(const CStdString &searchTitle); + virtual MythRecordingRule NewChannelRecord(MythEPGInfo &epgInfo); + virtual MythRecordingRule NewOneRecord(MythEPGInfo &epgInfo); }; // Base 0.24 @@ -168,12 +182,13 @@ class MythScheduleHelper1226 : public MythScheduleHelperNoHelper { MythScheduleHelper1226(MythDatabase &db) : m_db(db) { } virtual bool SameTimeslot(MythRecordingRule &first, MythRecordingRule &second) const; + virtual RuleMetadata GetMetadata(const MythRecordingRule &rule) const; virtual MythRecordingRule NewFromTemplate(MythEPGInfo &epgInfo); virtual MythRecordingRule NewSingleRecord(MythEPGInfo &epgInfo); virtual MythRecordingRule NewDailyRecord(MythEPGInfo &epgInfo); virtual MythRecordingRule NewWeeklyRecord(MythEPGInfo &epgInfo); - virtual MythRecordingRule NewChannelRecord(const CStdString &searchTitle); - virtual MythRecordingRule NewOneRecord(const CStdString &searchTitle); + virtual MythRecordingRule NewChannelRecord(MythEPGInfo &epgInfo); + virtual MythRecordingRule NewOneRecord(MythEPGInfo &epgInfo); protected: MythDatabase m_db; }; @@ -206,8 +221,9 @@ class MythScheduleHelper1309 : public MythScheduleHelper1302 { MythScheduleHelper1309(MythDatabase &db) : MythScheduleHelper1302(db) { } - //virtual bool SameTimeslot(MythRecordingRule &first, MythRecordingRule &second) const; - //virtual MythRecordingRule NewSingleRecord() const; - //virtual MythRecordingRule NewDailyRecord() const; - //virtual MythRecordingRule NewWeeklyRecord() const; + virtual RuleMetadata GetMetadata(const MythRecordingRule &rule) const; + virtual MythRecordingRule NewDailyRecord(MythEPGInfo &epgInfo); + virtual MythRecordingRule NewWeeklyRecord(MythEPGInfo &epgInfo); + virtual MythRecordingRule NewChannelRecord(MythEPGInfo &epgInfo); + virtual MythRecordingRule NewOneRecord(MythEPGInfo &epgInfo); }; diff --git a/addons/pvr.mythtv.cmyth/src/demux.cpp b/addons/pvr.mythtv.cmyth/src/demux.cpp new file mode 100644 index 000000000..871bd7f1c --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demux.cpp @@ -0,0 +1,584 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include "platform/os.h" + +#include "libXBMC_pvr.h" +#include "xbmc_codec_types.h" + +#include "demux.h" +#include "client.h" + +extern "C" { +#include +}; + +#define LOGTAG "[DEMUX] " +#define POSMAP_PTS_INTERVAL (PTS_TIME_BASE * 2) // 2 secs + +using namespace PLATFORM; +using namespace ADDON; + +void DemuxLog(int level, char *msg) +{ + if (msg && level != DEMUX_DBG_NONE) + { + bool doLog = g_bExtraDebug; + addon_log_t loglevel = LOG_DEBUG; + switch (level) + { + case DEMUX_DBG_ERROR: + loglevel = LOG_ERROR; + doLog = true; + break; + case DEMUX_DBG_WARN: + case DEMUX_DBG_INFO: + loglevel = LOG_INFO; + break; + case DEMUX_DBG_DEBUG: + case DEMUX_DBG_PARSE: + case DEMUX_DBG_ALL: + loglevel = LOG_DEBUG; + break; + } + if (XBMC && doLog) + XBMC->Log(loglevel, LOGTAG"%s", msg); + } +} + +Demux::Demux(MythRecorder &recorder) + : CThread() + , m_recorder(recorder) + , m_channel(1) + , m_av_buf_size(AV_BUFFER_SIZE) + , m_av_pos(0) + , m_av_buf(NULL) + , m_av_rbs(NULL) + , m_av_rbe(NULL) + , m_AVContext(NULL) + , m_mainStreamPID(0xffff) + , m_DTS(PTS_UNSET) + , m_PTS(PTS_UNSET) + , m_pinTime(0) + , m_curTime(0) + , m_endTime(0) + , m_isChangePlaced(false) +{ + m_av_buf = (unsigned char*)malloc(sizeof(*m_av_buf) * (m_av_buf_size + 1)); + if (m_av_buf) + { + m_av_rbs = m_av_buf; + m_av_rbe = m_av_buf; + + if (g_bExtraDebug) + demux_dbg_level(DEMUX_DBG_DEBUG); + else + demux_dbg_level(DEMUX_DBG_ERROR); + demux_set_dbg_msgcallback(DemuxLog); + + m_AVContext = new AVContext(this, m_av_pos, m_channel); + + CreateThread(true); + } + else + { + XBMC->Log(LOG_ERROR, LOGTAG"alloc AV buffer failed"); + } +} + +Demux::~Demux() +{ + StopThread(0); + Flush(); + + // Free AV context + if (m_AVContext) + SAFE_DELETE(m_AVContext); + // Free AV buffer + if (m_av_buf) + { + if (g_bExtraDebug) + XBMC->Log(LOG_DEBUG, LOGTAG"free AV buffer: allocated size was %zu", m_av_buf_size); + free(m_av_buf); + m_av_buf = NULL; + } +} + +/* + * Implement our AV reader + */ +const unsigned char* Demux::ReadAV(uint64_t pos, size_t n) +{ + // out of range + if (n > m_av_buf_size) + return NULL; + + // Already read ? + size_t sz = m_av_rbe - m_av_buf; + if (pos < m_av_pos || pos > (m_av_pos + sz)) + { + // seek and reset buffer + int64_t newpos = m_recorder.LiveTVSeek((int64_t)pos, WHENCE_SET); + if (newpos < 0) + return NULL; + m_av_pos = (uint64_t)newpos; + m_av_rbs = m_av_rbe = m_av_buf; + } + else + { + // move to the desired pos in buffer + m_av_rbs = m_av_buf + (size_t)(pos - m_av_pos); + } + + size_t dataread = m_av_rbe - m_av_rbs; + if (dataread >= n) + return m_av_rbs; + + memmove(m_av_buf, m_av_rbs, dataread); + m_av_rbs = m_av_buf; + m_av_rbe = m_av_rbs + dataread; + m_av_pos = pos; + unsigned int len = (unsigned int)(m_av_buf_size - dataread); + int wait = 5000; + while (wait > 0 && !IsStopped() ) + { + int ret = m_recorder.ReadLiveTV(m_av_rbe, len); + if (ret > 0) + { + m_av_rbe += ret; + dataread += ret; + len -= ret; + } + if (dataread >= n || ret < 0) + break; + wait -= 1000; + usleep(100000); + } + return dataread >= n ? m_av_rbs : NULL; +} + +void* Demux::Process() +{ + if (!m_AVContext) + { + XBMC->Log(LOG_ERROR, LOGTAG"%s: no AVContext", __FUNCTION__); + return NULL; + } + + int ret = 0; + + while (!IsStopped()) + { + { + CLockObject lock(m_mutex); + ret = m_AVContext->TSResync(); + } + if (ret != AVCONTEXT_CONTINUE) + break; + + ret = m_AVContext->ProcessTSPacket(); + + if (m_AVContext->HasPIDStreamData()) + { + ElementaryStream::STREAM_PKT pkt; + while (get_stream_data(&pkt)) + { + if (pkt.streamChange && update_pvr_stream(pkt.pid)) + push_stream_change(); + DemuxPacket* dxp = stream_pvr_data(&pkt); + if (dxp) + push_stream_data(dxp); + } + } + if (m_AVContext->HasPIDPayload()) + { + ret = m_AVContext->ProcessTSPayload(); + if (ret == AVCONTEXT_PROGRAM_CHANGE) + { + populate_pvr_streams(); + push_stream_change(); + } + } + + if (ret < 0) + XBMC->Log(LOG_NOTICE, LOGTAG"%s: error %d", __FUNCTION__, ret); + + if (ret == AVCONTEXT_TS_ERROR) + m_AVContext->Shift(); + else + m_AVContext->GoNext(); + } + + XBMC->Log(LOG_DEBUG, LOGTAG"%s: stopped with status %d", __FUNCTION__, ret); + return NULL; +} + +bool Demux::GetStreamProperties(PVR_STREAM_PROPERTIES* props) +{ + int wait = 0; + // Wait until setup is completed for all streams + while (IsRunning() && !m_nosetup.empty() && wait < 20) + { + if (g_bExtraDebug) + XBMC->Log(LOG_DEBUG, LOGTAG"%s: waiting until setup will be completed ...", __FUNCTION__); + usleep(100000); + wait++; + } + if (!m_nosetup.empty()) + XBMC->Log(LOG_ERROR, LOGTAG"%s: incomplete setup", __FUNCTION__); + + CLockObject lock(m_mutex); + m_isChangePlaced = false; + return m_streams.GetProperties(props); +} + +void Demux::Flush(void) +{ + CLockObject lock(m_mutex); + DemuxPacket* pkt(NULL); + while (m_demuxPacketBuffer.Pop(pkt)) + PVR->FreeDemuxPacket(pkt); +} + +void Demux::Abort() +{ + StopThread(0); +} + +DemuxPacket* Demux::Read() +{ + DemuxPacket* packet(NULL); + if (m_demuxPacketBuffer.Pop(packet, 100)) + return packet; + return PVR->AllocateDemuxPacket(0); +} + +bool Demux::SeekTime(int time, bool backwards, double* startpts) +{ + // Current PTS must be valid to estimate offset + if (m_PTS == PTS_UNSET) + return false; + // time is in MSEC not PTS_TIME_BASE. Rescale time to PTS (90Khz) + uint64_t pts = (uint64_t)time * PTS_TIME_BASE / 1000; + // Compute offset from current PTS + int64_t offset = (int64_t)(pts - m_PTS); + // Limit offset to deal with invalid request or PTS discontinuity + // Backwards : Limiting offset to +6 secs + // Forwards : Limiting offset to -6 secs + if (backwards) + offset = std::min(offset, (int64_t)(PTS_TIME_BASE * 6)); + else + offset = std::max(offset, (int64_t)(PTS_TIME_BASE * (-6))); + // Compute desired time position + int64_t desired = m_curTime + offset; + + CLockObject lock(m_mutex); + + std::map::const_iterator it; + if (offset < 0) + { + it = m_posmap.upper_bound(desired); + if (backwards && it != m_posmap.begin()) + --it; + } + else + { + it = m_posmap.upper_bound(desired); + // On end shift back if possible + if (it == m_posmap.end() && it != m_posmap.begin()) + --it; + } + + if (g_bExtraDebug) + XBMC->Log(LOG_DEBUG, LOGTAG"%s: bw:%d tm:%d tm_pts:%"PRIu64" c_pts:%"PRIu64" offset:%+6.3f c_tm:%+6.3f n_tm:%+6.3f", __FUNCTION__, + backwards, time, pts, m_PTS, (double)offset / PTS_TIME_BASE, (double)m_curTime / PTS_TIME_BASE, (double)desired / PTS_TIME_BASE); + + if (it != m_posmap.end()) + { + int64_t new_time = it->first; + uint64_t new_pos = it->second.av_pos; + uint64_t new_pts = it->second.av_pts; + XBMC->Log(LOG_DEBUG, LOGTAG"seek to %"PRId64" pts=%"PRIu64, new_time, new_pts); + + Flush(); + m_AVContext->GoPosition(new_pos); + m_AVContext->ResetPackets(); + m_curTime = m_pinTime = new_time; + m_DTS = m_PTS = new_pts; + } + + *startpts = (double)m_PTS * DVD_TIME_BASE / PTS_TIME_BASE; + + return true; +} + +int Demux::GetPlayingTime() +{ + double time_ms = (double)m_curTime * 1000 / PTS_TIME_BASE; + if (time_ms > INT_MAX) + return INT_MAX; + return (int)time_ms; +} + +bool Demux::get_stream_data(ElementaryStream::STREAM_PKT* pkt) +{ + ElementaryStream* es = m_AVContext->GetPIDStream(); + if (!es) + return false; + + if (!es->GetStreamPacket(pkt)) + return false; + + if (pkt->duration > 180000) + { + pkt->duration = 0; + } + else if (pkt->pid == m_mainStreamPID) + { + // Fill duration map for main stream + m_curTime += pkt->duration; + if (m_curTime >= m_pinTime) + { + m_pinTime += POSMAP_PTS_INTERVAL; + if (m_curTime > m_endTime) + { + AV_POSMAP_ITEM item; + item.av_pts = pkt->pts; + item.av_pos = m_AVContext->GetPosition(); + m_posmap.insert(std::make_pair(m_curTime, item)); + m_endTime = m_curTime; + } + } + // Sync main DTS & PTS + m_DTS = pkt->dts; + m_PTS = pkt->pts; + } + return true; +} + +void Demux::reset_posmap() +{ + if (m_posmap.empty()) + return; + + { + CLockObject lock(m_mutex); + m_posmap.clear(); + m_pinTime = m_curTime = m_endTime = 0; + } +} + +static inline int stream_identifier(int composition_id, int ancillary_id) +{ + return ((composition_id & 0xff00) >> 8) + | ((composition_id & 0xff) << 8) + | ((ancillary_id & 0xff00) << 16) + | ((ancillary_id & 0xff) << 24); +} + +static void recode_language(const char* muxLanguage, char* strLanguage) +{ + /* + * While XBMC does'nt support them. + * Fix unsupported language codes (EN 300 468 Annex F & J) + * 'qaa' : Original audio + * 'qad','NAR' : Audio Description + */ + if (strncmp(muxLanguage, "qaa", 3) == 0 || + strncmp(muxLanguage, "qad", 3) == 0 || + strncmp(muxLanguage, "NAR", 3) == 0) + { + strLanguage[0] = 0; + strLanguage[1] = 0; + strLanguage[2] = 0; + strLanguage[3] = 0; + } + else + { + strLanguage[0] = muxLanguage[0]; + strLanguage[1] = muxLanguage[1]; + strLanguage[2] = muxLanguage[2]; + strLanguage[3] = 0; + } +} + +void Demux::populate_pvr_streams() +{ + CLockObject Lock(m_mutex); + + uint16_t mainPid = 0xffff; + int mainType = XBMC_CODEC_TYPE_UNKNOWN; + std::vector new_streams; + const std::vector es_streams = m_AVContext->GetStreams(); + for (std::vector::const_iterator it = es_streams.begin(); it != es_streams.end(); it++) + { + const char* codec_name = (*it)->GetStreamCodecName(); + xbmc_codec_t codec = CODEC->GetCodecByName(codec_name); + if (codec.codec_type != XBMC_CODEC_TYPE_UNKNOWN) + { + // Find the main stream: + // The best candidate would be the first video. Else the first audio + switch (mainType) + { + case XBMC_CODEC_TYPE_VIDEO: + break; + case XBMC_CODEC_TYPE_AUDIO: + if (codec.codec_type != XBMC_CODEC_TYPE_VIDEO) + break; + default: + mainPid = (*it)->pid; + mainType = codec.codec_type; + } + + XbmcPvrStream new_stream; + m_streams.GetStreamData((*it)->pid, &new_stream); + + new_stream.iCodecId = codec.codec_id; + new_stream.iCodecType = codec.codec_type; + recode_language((*it)->stream_info.language, new_stream.strLanguage); + new_stream.iIdentifier = stream_identifier((*it)->stream_info.composition_id, (*it)->stream_info.ancillary_id); + new_stream.iFPSScale = (*it)->stream_info.fps_scale; + new_stream.iFPSRate = (*it)->stream_info.fps_rate; + new_stream.iHeight = (*it)->stream_info.height; + new_stream.iWidth = (*it)->stream_info.width; + new_stream.fAspect = (*it)->stream_info.aspect; + new_stream.iChannels = (*it)->stream_info.channels; + new_stream.iSampleRate = (*it)->stream_info.sample_rate; + new_stream.iBlockAlign = (*it)->stream_info.block_align; + new_stream.iBitRate = (*it)->stream_info.bit_rate; + new_stream.iBitsPerSample = (*it)->stream_info.bits_Per_sample; + + new_streams.push_back(new_stream); + m_AVContext->StartStreaming((*it)->pid); + + // Add stream to no setup set + if (!(*it)->has_stream_info) + m_nosetup.insert((*it)->pid); + + if (g_bExtraDebug) + XBMC->Log(LOG_DEBUG, LOGTAG"%s: register PES %.4x %s", __FUNCTION__, (*it)->pid, codec_name); + } + } + m_streams.UpdateStreams(new_streams); + // Renew main stream + m_mainStreamPID = mainPid; +} + +bool Demux::update_pvr_stream(uint16_t pid) +{ + ElementaryStream* es = m_AVContext->GetStream(pid); + if (!es) + return false; + + if (g_bExtraDebug) + XBMC->Log(LOG_DEBUG, LOGTAG"%s: update info PES %.4x %s", __FUNCTION__, es->pid, es->GetStreamCodecName()); + + CLockObject Lock(m_mutex); + + XbmcPvrStream* stream = m_streams.GetStreamById(es->pid); + if (stream) + { + recode_language(es->stream_info.language, stream->strLanguage); + stream->iIdentifier = stream_identifier(es->stream_info.composition_id, es->stream_info.ancillary_id); + stream->iFPSScale = es->stream_info.fps_scale; + stream->iFPSRate = es->stream_info.fps_rate; + stream->iHeight = es->stream_info.height; + stream->iWidth = es->stream_info.width; + stream->fAspect = es->stream_info.aspect; + stream->iChannels = es->stream_info.channels; + stream->iSampleRate = es->stream_info.sample_rate; + stream->iBlockAlign = es->stream_info.block_align; + stream->iBitRate = es->stream_info.bit_rate; + stream->iBitsPerSample = es->stream_info.bits_Per_sample; + + if (es->has_stream_info) + { + // Now stream is setup. Remove it from no setup set + std::set::iterator it = m_nosetup.find(es->pid); + if (it != m_nosetup.end()) + { + m_nosetup.erase(it); + if (m_nosetup.empty()) + XBMC->Log(LOG_DEBUG, LOGTAG"%s: setup is completed", __FUNCTION__); + } + } + return true; + } + return false; +} + +void Demux::push_stream_change() +{ + if (!m_isChangePlaced) + { + bool ret = false; + DemuxPacket* dxp = PVR->AllocateDemuxPacket(0); + dxp->iStreamId = DMX_SPECIALID_STREAMCHANGE; + + while (!IsStopped() && !(ret = m_demuxPacketBuffer.Push(dxp))) + usleep(100000); + if (!ret) + PVR->FreeDemuxPacket(dxp); + else + { + m_isChangePlaced = true; + XBMC->Log(LOG_DEBUG, LOGTAG"%s: done", __FUNCTION__); + } + } +} + +DemuxPacket* Demux::stream_pvr_data(ElementaryStream::STREAM_PKT* pkt) +{ + if (!pkt) + return NULL; + + DemuxPacket* dxp = PVR->AllocateDemuxPacket(pkt->size); + if (dxp) + { + if (pkt->size > 0 && pkt->data) + memcpy(dxp->pData, pkt->data, pkt->size); + + dxp->iSize = pkt->size; + dxp->duration = (double)pkt->duration * DVD_TIME_BASE / PTS_TIME_BASE; + if (pkt->dts != PTS_UNSET) + dxp->dts = (double)pkt->dts * DVD_TIME_BASE / PTS_TIME_BASE; + else + dxp->dts = DVD_NOPTS_VALUE; + if (pkt->pts != PTS_UNSET) + dxp->pts = (double)pkt->pts * DVD_TIME_BASE / PTS_TIME_BASE; + else + dxp->pts = DVD_NOPTS_VALUE; + + dxp->iStreamId = m_streams.GetStreamId((unsigned int)pkt->pid); + } + return dxp; +} + +void Demux::push_stream_data(DemuxPacket* dxp) +{ + if (dxp) + { + bool ret = false; + while (!IsStopped() && !(ret = m_demuxPacketBuffer.Push(dxp))) + usleep(100000); + if (!ret) + PVR->FreeDemuxPacket(dxp); + } +} diff --git a/addons/pvr.mythtv.cmyth/src/demux.h b/addons/pvr.mythtv.cmyth/src/demux.h new file mode 100644 index 000000000..7c1bc002b --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demux.h @@ -0,0 +1,95 @@ +#pragma once +/* + * Copyright (C) 2005-2013 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include "cppmyth/MythRecorder.h" +#include "demuxer/tsDemuxer.h" +#include "client.h" + +#include +#include +#include +#include + +#include +#include + +#define AV_BUFFER_SIZE 131072 + +class Demux : public TSDemuxer, PLATFORM::CThread +{ +public: + Demux(MythRecorder &recorder); + ~Demux(); + + const unsigned char* ReadAV(uint64_t pos, size_t n); + + void* Process(); + + bool GetStreamProperties(PVR_STREAM_PROPERTIES* props); + void Flush(); + void Abort(); + DemuxPacket* Read(); + bool SeekTime(int time, bool backwards, double* startpts); + + int GetPlayingTime(); + +private: + MythRecorder m_recorder; + uint16_t m_channel; + PLATFORM::SyncedBuffer m_demuxPacketBuffer; + PLATFORM::CMutex m_mutex; + ADDON::XbmcStreamProperties m_streams; + + bool get_stream_data(ElementaryStream::STREAM_PKT* pkt); + void reset_posmap(); + + // PVR interfaces + void populate_pvr_streams(); + bool update_pvr_stream(uint16_t pid); + void push_stream_change(); + DemuxPacket* stream_pvr_data(ElementaryStream::STREAM_PKT* pkt); + void push_stream_data(DemuxPacket* dxp); + + // AV raw buffer + size_t m_av_buf_size; ///< size of av buffer + uint64_t m_av_pos; ///< absolute position in av + unsigned char* m_av_buf; ///< buffer + unsigned char* m_av_rbs; ///< raw data start in buffer + unsigned char* m_av_rbe; ///< raw data end in buffer + + // Playback context + AVContext* m_AVContext; + uint16_t m_mainStreamPID; ///< PID of main stream + uint64_t m_DTS; ///< absolute decode time of main stream + uint64_t m_PTS; ///< absolute presentation time of main stream + int64_t m_pinTime; ///< pinned relative position (90Khz) + int64_t m_curTime; ///< current relative position (90Khz) + int64_t m_endTime; ///< last relative marked position (90Khz)) + typedef struct + { + uint64_t av_pts; + uint64_t av_pos; + } AV_POSMAP_ITEM; + std::map m_posmap; + + bool m_isChangePlaced; + std::set m_nosetup; +}; diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/ES_AAC.cpp b/addons/pvr.mythtv.cmyth/src/demuxer/ES_AAC.cpp new file mode 100644 index 000000000..bd632e207 --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/ES_AAC.cpp @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include + +#include "ES_AAC.h" +#include "bitstream.h" + +static int aac_sample_rates[16] = +{ + 96000, 88200, 64000, 48000, 44100, 32000, + 24000, 22050, 16000, 12000, 11025, 8000, 7350 +}; + + +ES_AAC::ES_AAC(uint16_t pes_pid) + : ElementaryStream(pes_pid) +{ + m_Configured = false; + m_FrameLengthType = 0; + m_PTS = 0; + m_DTS = 0; + m_FrameSize = 0; + m_SampleRate = 0; + m_Channels = 0; + m_BitRate = 0; + m_AudioMuxVersion_A = 0; + es_alloc_init = 1920*2; + Reset(); +} + +ES_AAC::~ES_AAC() +{ +} + +void ES_AAC::Parse(STREAM_PKT* pkt) +{ + int p = es_parsed; + int l; + while ((l = es_len - p) > 8) + { + if (FindHeaders(es_buf + p, l) < 0) + break; + p++; + } + es_parsed = p; + + if (es_found_frame && l >= m_FrameSize) + { + bool streamChange = SetAudioInformation(m_Channels, m_SampleRate, m_BitRate, 0, 0); + pkt->pid = pid; + pkt->data = &es_buf[p]; + pkt->size = m_FrameSize; + pkt->duration = 1024 * 90000 / m_SampleRate; + pkt->dts = m_DTS; + pkt->pts = m_PTS; + pkt->streamChange = streamChange; + + es_consumed = p + m_FrameSize; + es_parsed = es_consumed; + es_found_frame = false; + } +} + +int ES_AAC::FindHeaders(uint8_t *buf, int buf_size) +{ + if (es_found_frame) + return -1; + + uint8_t *buf_ptr = buf; + + // STREAM_TYPE_AUDIO_AACLATM + if ((buf_ptr[0] == 0x56 && (buf_ptr[1] & 0xE0) == 0xE0)) + { + // TODO + if (buf_size < 16) + return -1; + + cBitstream bs(buf_ptr, 16 * 8); + bs.skipBits(11); + m_FrameSize = bs.readBits(13) + 3; + if (!ParseLATMAudioMuxElement(&bs)) + return 0; + + es_found_frame = true; + m_DTS = c_pts; + m_PTS = c_pts; + c_pts += 90000 * 1024 / m_SampleRate; + return -1; + } + //STREAM_TYPE_AUDIO_AACADTS + else if(buf_ptr[0] == 0xFF && (buf_ptr[1] & 0xF0) == 0xF0) + { + // need at least 7 bytes for header + if (buf_size < 7) + return -1; + + cBitstream bs(buf_ptr, 9 * 8); + bs.skipBits(15); + + // check if CRC is present, means header is 9 byte long + int noCrc = bs.readBits(1); + if (!noCrc && (buf_size < 9)) + return -1; + + bs.skipBits(2); // profile + int SampleRateIndex = bs.readBits(4); + bs.skipBits(1); // private + m_Channels = bs.readBits(3); + bs.skipBits(4); + + m_FrameSize = bs.readBits(13); + m_SampleRate = aac_sample_rates[SampleRateIndex & 0x0E]; + + es_found_frame = true; + m_DTS = c_pts; + m_PTS = c_pts; + c_pts += 90000 * 1024 / m_SampleRate; + return -1; + } + return 0; +} + +bool ES_AAC::ParseLATMAudioMuxElement(cBitstream *bs) +{ + if (!bs->readBits1()) + ReadStreamMuxConfig(bs); + + if (!m_Configured) + return false; + + return true; +} + +void ES_AAC::ReadStreamMuxConfig(cBitstream *bs) +{ + int AudioMuxVersion = bs->readBits(1); + m_AudioMuxVersion_A = 0; + if (AudioMuxVersion) // audioMuxVersion + m_AudioMuxVersion_A = bs->readBits(1); + + if(m_AudioMuxVersion_A) + return; + + if (AudioMuxVersion) + LATMGetValue(bs); // taraFullness + + bs->skipBits(1); // allStreamSameTimeFraming = 1 + bs->skipBits(6); // numSubFrames = 0 + bs->skipBits(4); // numPrograms = 0 + + // for each program (which there is only on in DVB) + bs->skipBits(3); // numLayer = 0 + + // for each layer (which there is only on in DVB) + if (!AudioMuxVersion) + ReadAudioSpecificConfig(bs); + else + return; + + // these are not needed... perhaps + m_FrameLengthType = bs->readBits(3); + switch (m_FrameLengthType) + { + case 0: + bs->readBits(8); + break; + case 1: + bs->readBits(9); + break; + case 3: + case 4: + case 5: + bs->readBits(6); // celp_table_index + break; + case 6: + case 7: + bs->readBits(1); // hvxc_table_index + break; + } + + if (bs->readBits(1)) + { // other data? + int esc; + do + { + esc = bs->readBits(1); + bs->skipBits(8); + } while (esc); + } + + if (bs->readBits(1)) // crc present? + bs->skipBits(8); // config_crc + m_Configured = true; +} + +void ES_AAC::ReadAudioSpecificConfig(cBitstream *bs) +{ + int aot = bs->readBits(5); + if (aot == 31) + aot = 32 + bs->readBits(6); + + int SampleRateIndex = bs->readBits(4); + + if (SampleRateIndex == 0xf) + m_SampleRate = bs->readBits(24); + else + m_SampleRate = aac_sample_rates[SampleRateIndex & 0xf]; + + m_Channels = bs->readBits(4); + + if (aot == 5) { // AOT_SBR + if (bs->readBits(4) == 0xf) { // extensionSamplingFrequencyIndex + bs->skipBits(24); + } + aot = bs->readBits(5); // this is the main object type (i.e. non-extended) + if (aot == 31) + aot = 32 + bs->readBits(6); + } + + if(aot != 2) + return; + + bs->skipBits(1); //framelen_flag + if (bs->readBits1()) // depends_on_coder + bs->skipBits(14); + + if (bs->readBits(1)) // ext_flag + bs->skipBits(1); // ext3_flag +} + +void ES_AAC::Reset() +{ + ElementaryStream::Reset(); + m_Configured = false; +} diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/ES_AAC.h b/addons/pvr.mythtv.cmyth/src/demuxer/ES_AAC.h new file mode 100644 index 000000000..de18fe804 --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/ES_AAC.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#ifndef ES_AAC_H +#define ES_AAC_H + +#include "elementaryStream.h" +#include "bitstream.h" + +class ES_AAC : public ElementaryStream +{ +private: + int m_SampleRate; + int m_Channels; + int m_BitRate; + int m_FrameSize; + + int64_t m_PTS; /* pts of the current frame */ + int64_t m_DTS; /* dts of the current frame */ + + bool m_Configured; + int m_AudioMuxVersion_A; + int m_FrameLengthType; + + int FindHeaders(uint8_t *buf, int buf_size); + bool ParseLATMAudioMuxElement(cBitstream *bs); + void ReadStreamMuxConfig(cBitstream *bs); + void ReadAudioSpecificConfig(cBitstream *bs); + uint32_t LATMGetValue(cBitstream *bs) { return bs->readBits(bs->readBits(2) * 8); } + +public: + ES_AAC(uint16_t pes_pid); + virtual ~ES_AAC(); + + virtual void Parse(STREAM_PKT* pkt); + virtual void Reset(); +}; + +#endif /* ES_AAC_H */ diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/ES_AC3.cpp b/addons/pvr.mythtv.cmyth/src/demuxer/ES_AC3.cpp new file mode 100644 index 000000000..c8758e1a4 --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/ES_AC3.cpp @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include +#include + +#include "ES_AC3.h" +#include "bitstream.h" + +using namespace std; + +#define AC3_HEADER_SIZE 7 + +/** Channel mode (audio coding mode) */ +typedef enum +{ + AC3_CHMODE_DUALMONO = 0, + AC3_CHMODE_MONO, + AC3_CHMODE_STEREO, + AC3_CHMODE_3F, + AC3_CHMODE_2F1R, + AC3_CHMODE_3F1R, + AC3_CHMODE_2F2R, + AC3_CHMODE_3F2R +} AC3ChannelMode; + +/* possible frequencies */ +const uint16_t AC3SampleRateTable[3] = { 48000, 44100, 32000 }; + +/* possible bitrates */ +const uint16_t AC3BitrateTable[19] = { + 32, 40, 48, 56, 64, 80, 96, 112, 128, + 160, 192, 224, 256, 320, 384, 448, 512, 576, 640 +}; + +const uint8_t AC3ChannelsTable[8] = { + 2, 1, 2, 3, 3, 4, 4, 5 +}; + +const uint16_t AC3FrameSizeTable[38][3] = { + { 64, 69, 96 }, + { 64, 70, 96 }, + { 80, 87, 120 }, + { 80, 88, 120 }, + { 96, 104, 144 }, + { 96, 105, 144 }, + { 112, 121, 168 }, + { 112, 122, 168 }, + { 128, 139, 192 }, + { 128, 140, 192 }, + { 160, 174, 240 }, + { 160, 175, 240 }, + { 192, 208, 288 }, + { 192, 209, 288 }, + { 224, 243, 336 }, + { 224, 244, 336 }, + { 256, 278, 384 }, + { 256, 279, 384 }, + { 320, 348, 480 }, + { 320, 349, 480 }, + { 384, 417, 576 }, + { 384, 418, 576 }, + { 448, 487, 672 }, + { 448, 488, 672 }, + { 512, 557, 768 }, + { 512, 558, 768 }, + { 640, 696, 960 }, + { 640, 697, 960 }, + { 768, 835, 1152 }, + { 768, 836, 1152 }, + { 896, 975, 1344 }, + { 896, 976, 1344 }, + { 1024, 1114, 1536 }, + { 1024, 1115, 1536 }, + { 1152, 1253, 1728 }, + { 1152, 1254, 1728 }, + { 1280, 1393, 1920 }, + { 1280, 1394, 1920 }, +}; + +const uint8_t EAC3Blocks[4] = { + 1, 2, 3, 6 +}; + +typedef enum { + EAC3_FRAME_TYPE_INDEPENDENT = 0, + EAC3_FRAME_TYPE_DEPENDENT, + EAC3_FRAME_TYPE_AC3_CONVERT, + EAC3_FRAME_TYPE_RESERVED +} EAC3FrameType; + +ES_AC3::ES_AC3(uint16_t pid) + : ElementaryStream(pid) +{ + m_PTS = 0; + m_DTS = 0; + m_FrameSize = 0; + m_SampleRate = 0; + m_Channels = 0; + m_BitRate = 0; + es_alloc_init = 1920*2; +} + +ES_AC3::~ES_AC3() +{ +} + +void ES_AC3::Parse(STREAM_PKT* pkt) +{ + int p = es_parsed; + int l; + while ((l = es_len - p) > 8) + { + if (FindHeaders(es_buf + p, l) < 0) + break; + p++; + } + es_parsed = p; + + if (es_found_frame && l >= m_FrameSize) + { + bool streamChange = SetAudioInformation(m_Channels, m_SampleRate, m_BitRate, 0, 0); + pkt->pid = pid; + pkt->data = &es_buf[p]; + pkt->size = m_FrameSize; + pkt->duration = 90000 * 1536 / m_SampleRate; + pkt->dts = m_DTS; + pkt->pts = m_PTS; + pkt->streamChange = streamChange; + + es_consumed = p + m_FrameSize; + es_parsed = es_consumed; + es_found_frame = false; + } +} + +int ES_AC3::FindHeaders(uint8_t *buf, int buf_size) +{ + if (es_found_frame) + return -1; + + if (buf_size < 9) + return -1; + + uint8_t *buf_ptr = buf; + + if ((buf_ptr[0] == 0x0b && buf_ptr[1] == 0x77)) + { + cBitstream bs(buf_ptr + 2, AC3_HEADER_SIZE * 8); + + // read ahead to bsid to distinguish between AC-3 and E-AC-3 + int bsid = bs.showBits(29) & 0x1F; + if (bsid > 16) + return 0; + + if (bsid <= 10) + { + // Normal AC-3 + bs.skipBits(16); + int fscod = bs.readBits(2); + int frmsizecod = bs.readBits(6); + bs.skipBits(5); // skip bsid, already got it + bs.skipBits(3); // skip bitstream mode + int acmod = bs.readBits(3); + + if (fscod == 3 || frmsizecod > 37) + return 0; + + if (acmod == AC3_CHMODE_STEREO) + { + bs.skipBits(2); // skip dsurmod + } + else + { + if ((acmod & 1) && acmod != AC3_CHMODE_MONO) + bs.skipBits(2); + if (acmod & 4) + bs.skipBits(2); + } + int lfeon = bs.readBits(1); + + int srShift = max(bsid, 8) - 8; + m_SampleRate = AC3SampleRateTable[fscod] >> srShift; + m_BitRate = (AC3BitrateTable[frmsizecod>>1] * 1000) >> srShift; + m_Channels = AC3ChannelsTable[acmod] + lfeon; + m_FrameSize = AC3FrameSizeTable[frmsizecod][fscod] * 2; + } + else + { + // Enhanced AC-3 + int frametype = bs.readBits(2); + if (frametype == EAC3_FRAME_TYPE_RESERVED) + return 0; + + bs.readBits(3); // int substreamid + + m_FrameSize = (bs.readBits(11) + 1) << 1; + if (m_FrameSize < AC3_HEADER_SIZE) + return 0; + + int numBlocks = 6; + int sr_code = bs.readBits(2); + if (sr_code == 3) + { + int sr_code2 = bs.readBits(2); + if (sr_code2 == 3) + return 0; + m_SampleRate = AC3SampleRateTable[sr_code2] / 2; + } + else + { + numBlocks = EAC3Blocks[bs.readBits(2)]; + m_SampleRate = AC3SampleRateTable[sr_code]; + } + + int channelMode = bs.readBits(3); + int lfeon = bs.readBits(1); + + m_BitRate = (uint32_t)(8.0 * m_FrameSize * m_SampleRate / (numBlocks * 256.0)); + m_Channels = AC3ChannelsTable[channelMode] + lfeon; + } + es_found_frame = true; + m_DTS = c_pts; + m_PTS = c_pts; + c_pts += 90000 * 1536 / m_SampleRate; + return -1; + } + return 0; +} + +void ES_AC3::Reset() +{ + ElementaryStream::Reset(); +} diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/ES_AC3.h b/addons/pvr.mythtv.cmyth/src/demuxer/ES_AC3.h new file mode 100644 index 000000000..2c6acd6f3 --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/ES_AC3.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#ifndef ES_AC3_H +#define ES_AC3_H + +#include "elementaryStream.h" + +class ES_AC3 : public ElementaryStream +{ +private: + int m_SampleRate; + int m_Channels; + int m_BitRate; + int m_FrameSize; + + int64_t m_PTS; /* pts of the current frame */ + int64_t m_DTS; /* dts of the current frame */ + + int FindHeaders(uint8_t *buf, int buf_size); + +public: + ES_AC3(uint16_t pid); + virtual ~ES_AC3(); + + virtual void Parse(STREAM_PKT* pkt); + virtual void Reset(); +}; + +#endif /* ES_AC3_H */ diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/ES_MPEGAudio.cpp b/addons/pvr.mythtv.cmyth/src/demuxer/ES_MPEGAudio.cpp new file mode 100644 index 000000000..71c76b58e --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/ES_MPEGAudio.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include + +#include "ES_MPEGAudio.h" +#include "bitstream.h" + +const uint16_t FrequencyTable[3] = { 44100, 48000, 32000 }; +const uint16_t BitrateTable[2][3][15] = +{ + { + {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448 }, + {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384 }, + {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 } + }, + { + {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256}, + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}, + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160} + } +}; + +ES_MPEG2Audio::ES_MPEG2Audio(uint16_t pid) + : ElementaryStream(pid) +{ + m_PTS = 0; + m_DTS = 0; + m_FrameSize = 0; + m_SampleRate = 0; + m_Channels = 0; + m_BitRate = 0; + es_alloc_init = 2048; +} + +ES_MPEG2Audio::~ES_MPEG2Audio() +{ +} + +void ES_MPEG2Audio::Parse(STREAM_PKT* pkt) +{ + int p = es_parsed; + int l; + while ((l = es_len - p) > 3) + { + if (FindHeaders(es_buf + p, l) < 0) + break; + p++; + } + es_parsed = p; + + if (es_found_frame && l >= m_FrameSize) + { + bool streamChange = SetAudioInformation(m_Channels, m_SampleRate, m_BitRate, 0, 0); + pkt->pid = pid; + pkt->data = &es_buf[p]; + pkt->size = m_FrameSize; + pkt->duration = 90000 * 1152 / m_SampleRate; + pkt->dts = m_DTS; + pkt->pts = m_PTS; + pkt->streamChange = streamChange; + + es_consumed = p + m_FrameSize; + es_parsed = es_consumed; + es_found_frame = false; + } +} + +int ES_MPEG2Audio::FindHeaders(uint8_t *buf, int buf_size) +{ + if (es_found_frame) + return -1; + + if (buf_size < 4) + return -1; + + uint8_t *buf_ptr = buf; + + if ((buf_ptr[0] == 0xFF && (buf_ptr[1] & 0xE0) == 0xE0)) + { + cBitstream bs(buf_ptr, 4 * 8); + bs.skipBits(11); // syncword + + int audioVersion = bs.readBits(2); + if (audioVersion == 1) + return 0; + int mpeg2 = !(audioVersion & 1); + int mpeg25 = !(audioVersion & 3); + + int layer = bs.readBits(2); + if (layer == 0) + return 0; + layer = 4 - layer; + + bs.skipBits(1); // protetion bit + int bitrate_index = bs.readBits(4); + if (bitrate_index == 15 || bitrate_index == 0) + return 0; + m_BitRate = BitrateTable[mpeg2][layer - 1][bitrate_index] * 1000; + + int sample_rate_index = bs.readBits(2); + if (sample_rate_index == 3) + return 0; + m_SampleRate = FrequencyTable[sample_rate_index] >> (mpeg2 + mpeg25); + + int padding = bs.readBits1(); + bs.skipBits(1); // private bit + int channel_mode = bs.readBits(2); + + if (channel_mode == 11) + m_Channels = 1; + else + m_Channels = 2; + + if (layer == 1) + m_FrameSize = (12 * m_BitRate / m_SampleRate + padding) * 4; + else + m_FrameSize = 144 * m_BitRate / m_SampleRate + padding; + + es_found_frame = true; + m_DTS = c_pts; + m_PTS = c_pts; + c_pts += 90000 * 1152 / m_SampleRate; + return -1; + } + return 0; +} diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/ES_MPEGAudio.h b/addons/pvr.mythtv.cmyth/src/demuxer/ES_MPEGAudio.h new file mode 100644 index 000000000..7edaec4d2 --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/ES_MPEGAudio.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#ifndef ES_MPEGAUDIO_H +#define ES_MPEGAUDIO_H + +#include "elementaryStream.h" + +class ES_MPEG2Audio : public ElementaryStream +{ +private: + int m_SampleRate; + int m_Channels; + int m_BitRate; + int m_FrameSize; + + int64_t m_PTS; + int64_t m_DTS; + + int FindHeaders(uint8_t *buf, int buf_size); + +public: + ES_MPEG2Audio(uint16_t pid); + virtual ~ES_MPEG2Audio(); + + virtual void Parse(STREAM_PKT* pkt); +}; + +#endif /* ES_MPEGAUDIO_H */ diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/ES_MPEGVideo.cpp b/addons/pvr.mythtv.cmyth/src/demuxer/ES_MPEGVideo.cpp new file mode 100644 index 000000000..417907be5 --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/ES_MPEGVideo.cpp @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include + +#include "ES_MPEGVideo.h" +#include "bitstream.h" + +using namespace std; + +#define MPEG_PICTURE_START 0x00000100 +#define MPEG_SEQUENCE_START 0x000001b3 +#define MPEG_SEQUENCE_EXTENSION 0x000001b5 +#define MPEG_SLICE_S 0x00000101 +#define MPEG_SLICE_E 0x000001af + +#define PKT_I_FRAME 1 +#define PKT_P_FRAME 2 +#define PKT_B_FRAME 3 +#define PKT_NTYPES 4 + +/** + * MPEG2VIDEO frame duration table (in 90kHz clock domain) + */ +const unsigned int mpeg2video_framedurations[16] = { + 0, + 3753, + 3750, + 3600, + 3003, + 3000, + 1800, + 1501, + 1500, +}; + +ES_MPEG2Video::ES_MPEG2Video(uint16_t pid) + : ElementaryStream(pid) +{ + m_FrameDuration = 0; + m_vbvDelay = -1; + m_vbvSize = 0; + m_Height = 0; + m_Width = 0; + m_Dar = 0.0f; + m_DTS = 0; + m_PTS = 0; + m_AuDTS = 0; + m_AuPTS = 0; + m_AuPrevDTS = 0; + m_TemporalReference = 0; + m_TrLastTime = 0; + m_PicNumber = 0; + es_alloc_init = 80000; + Reset(); +} + +ES_MPEG2Video::~ES_MPEG2Video() +{ +} + +void ES_MPEG2Video::Parse(STREAM_PKT *pkt) +{ + int frame_ptr = es_consumed; + int p = es_parsed; + uint32_t startcode = m_StartCode; + bool frameComplete = false; + int l; + while ((l = es_len - p) > 3) + { + if ((startcode & 0xffffff00) == 0x00000100) + { + if (Parse_MPEG2Video(startcode, p, frameComplete) < 0) + { + break; + } + } + startcode = startcode << 8 | es_buf[p++]; + } + es_parsed = p; + m_StartCode = startcode; + + if (frameComplete) + { + if (!m_NeedSPS && !m_NeedIFrame) + { + int fpsScale = static_cast(Rescale(m_FrameDuration, RESCALE_TIME_BASE, PTS_TIME_BASE)); + bool streamChange = SetVideoInformation(fpsScale, RESCALE_TIME_BASE, m_Height, m_Width, m_Dar); + pkt->pid = pid; + pkt->size = es_consumed - frame_ptr; + pkt->data = &es_buf[frame_ptr]; + pkt->dts = m_DTS; + pkt->pts = m_PTS; + pkt->duration = m_FrameDuration; + pkt->streamChange = streamChange; + } + m_StartCode = 0xffffffff; + es_parsed = es_consumed; + es_found_frame = false; + } +} + +void ES_MPEG2Video::Reset() +{ + ElementaryStream::Reset(); + m_StartCode = 0xffffffff; + m_NeedIFrame = true; + m_NeedSPS = true; +} + +int ES_MPEG2Video::Parse_MPEG2Video(uint32_t startcode, int buf_ptr, bool &complete) +{ + int len = es_len - buf_ptr; + uint8_t *buf = es_buf + buf_ptr; + + switch (startcode & 0xFF) + { + case 0: // picture start + { + if (m_NeedSPS) + { + es_found_frame = true; + return 0; + } + if (es_found_frame) + { + complete = true; + es_consumed = buf_ptr - 4; + return -1; + } + if (len < 4) + return -1; + if (!Parse_MPEG2Video_PicStart(buf)) + return 0; + + if (!es_found_frame) + { + m_AuPrevDTS = m_AuDTS; + if (buf_ptr - 4 >= (int)es_pts_pointer) + { + m_AuDTS = c_dts; + m_AuPTS = c_pts; + } + else + { + m_AuDTS = p_dts; + m_AuPTS = p_pts; + } + } + if (m_AuPrevDTS == m_AuDTS) + { + m_DTS = m_AuDTS + m_PicNumber*m_FrameDuration; + m_PTS = m_AuPTS + (m_TemporalReference-m_TrLastTime)*m_FrameDuration; + } + else + { + m_PTS = m_AuPTS; + m_DTS = m_AuDTS; + m_PicNumber = 0; + m_TrLastTime = m_TemporalReference; + } + + m_PicNumber++; + es_found_frame = true; + break; + } + + case 0xb3: // Sequence start code + { + if (es_found_frame) + { + complete = true; + es_consumed = buf_ptr - 4; + return -1; + } + if (len < 8) + return -1; + if (!Parse_MPEG2Video_SeqStart(buf)) + return 0; + + break; + } + + case 0xb7: // sequence end + { + if (es_found_frame) + { + complete = true; + es_consumed = buf_ptr; + return -1; + } + break; + } + + default: + break; + } + + return 0; +} + +bool ES_MPEG2Video::Parse_MPEG2Video_SeqStart(uint8_t *buf) +{ + cBitstream bs(buf, 8 * 8); + + m_Width = bs.readBits(12); + m_Height = bs.readBits(12); + + // figure out Display Aspect Ratio + uint8_t aspect = bs.readBits(4); + + switch(aspect) + { + case 1: + m_Dar = 1.0f; + break; + case 2: + m_Dar = 4.0f/3.0f; + break; + case 3: + m_Dar = 16.0f/9.0f; + break; + case 4: + m_Dar = 2.21f; + break; + default: + demux_dbg(DEMUX_DBG_ERROR, "invalid / forbidden DAR in sequence header !\n"); + return false; + } + + m_FrameDuration = mpeg2video_framedurations[bs.readBits(4)]; + bs.skipBits(18); + bs.skipBits(1); + + m_vbvSize = bs.readBits(10) * 16 * 1024 / 8; + m_NeedSPS = false; + + return true; +} + +bool ES_MPEG2Video::Parse_MPEG2Video_PicStart(uint8_t *buf) +{ + cBitstream bs(buf, 4 * 8); + + m_TemporalReference = bs.readBits(10); /* temporal reference */ + + int pct = bs.readBits(3); + if (pct < PKT_I_FRAME || pct > PKT_B_FRAME) + return true; /* Illegal picture_coding_type */ + + if (pct == PKT_I_FRAME) + m_NeedIFrame = false; + + int vbvDelay = bs.readBits(16); /* vbv_delay */ + if (vbvDelay == 0xffff) + m_vbvDelay = -1; + else + m_vbvDelay = vbvDelay; + + return true; +} diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/ES_MPEGVideo.h b/addons/pvr.mythtv.cmyth/src/demuxer/ES_MPEGVideo.h new file mode 100644 index 000000000..d72640e2d --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/ES_MPEGVideo.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#ifndef ES_MPEGVIDEO_H +#define ES_MPEGVIDEO_H + +#include +#include "elementaryStream.h" + +class ES_MPEG2Video : public ElementaryStream +{ +private: + uint32_t m_StartCode; + bool m_NeedIFrame; + bool m_NeedSPS; + int m_FrameDuration; + int m_vbvDelay; /* -1 if CBR */ + int m_vbvSize; /* Video buffer size (in bytes) */ + int m_Width; + int m_Height; + float m_Dar; + int64_t m_DTS; + int64_t m_PTS; + int64_t m_AuDTS, m_AuPTS, m_AuPrevDTS; + int m_TemporalReference; + int m_TrLastTime; + int m_PicNumber; + + int Parse_MPEG2Video(uint32_t startcode, int buf_ptr, bool &complete); + bool Parse_MPEG2Video_SeqStart(uint8_t *buf); + bool Parse_MPEG2Video_PicStart(uint8_t *buf); + +public: + ES_MPEG2Video(uint16_t pid); + virtual ~ES_MPEG2Video(); + + virtual void Parse(STREAM_PKT* pkt); + virtual void Reset(); +}; + +#endif /* ES_MPEGVIDEO_H */ diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/ES_Subtitle.cpp b/addons/pvr.mythtv.cmyth/src/demuxer/ES_Subtitle.cpp new file mode 100644 index 000000000..d704374b8 --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/ES_Subtitle.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include + +#include "ES_Subtitle.h" + +ES_Subtitle::ES_Subtitle(uint16_t pid) + : ElementaryStream(pid) +{ + es_alloc_init = 4000; + has_stream_info = true; // doesn't provide stream info +} + +ES_Subtitle::~ES_Subtitle() +{ + +} + +void ES_Subtitle::Parse(STREAM_PKT* pkt) +{ + int l = es_len - es_parsed; + + if (l > 0) + { + if (l < 2 || es_buf[0] != 0x20 || es_buf[1] != 0x00) + { + Reset(); + return; + } + + if(es_buf[l-1] == 0xff) + { + pkt->pid = pid; + pkt->data = es_buf+2; + pkt->size = l-3; + pkt->duration = 0; + pkt->dts = c_dts; + pkt->pts = c_pts; + pkt->streamChange = false; + } + + es_parsed = es_consumed = es_len; + } +} diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/ES_Subtitle.h b/addons/pvr.mythtv.cmyth/src/demuxer/ES_Subtitle.h new file mode 100644 index 000000000..491d61f6c --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/ES_Subtitle.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#ifndef ES_SUBTITLE_H +#define ES_SUBTITLE_H + +#include "elementaryStream.h" + +class ES_Subtitle : public ElementaryStream +{ +public: + ES_Subtitle(uint16_t pid); + virtual ~ES_Subtitle(); + + virtual void Parse(STREAM_PKT* pkt); +}; + +#endif /* ES_SUBTITLE_H */ diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/ES_Teletext.cpp b/addons/pvr.mythtv.cmyth/src/demuxer/ES_Teletext.cpp new file mode 100644 index 000000000..ff55add77 --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/ES_Teletext.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include + +#include "ES_Teletext.h" + +ES_Teletext::ES_Teletext(uint16_t pid) + : ElementaryStream(pid) +{ + es_alloc_init = 4000; + has_stream_info = true; // doesn't provide stream info +} + +ES_Teletext::~ES_Teletext() +{ +} + +void ES_Teletext::Parse(STREAM_PKT* pkt) +{ + int l = es_len - es_parsed; + if (l < 1) + return; + + if (es_buf[0] < 0x10 || es_buf[0] > 0x1F) + { + Reset(); + return; + } + + pkt->pid = pid; + pkt->data = es_buf; + pkt->size = l; + pkt->duration = 0; + pkt->dts = c_dts; + pkt->pts = c_pts; + pkt->streamChange = false; + + es_parsed = es_consumed = es_len; +} diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/ES_Teletext.h b/addons/pvr.mythtv.cmyth/src/demuxer/ES_Teletext.h new file mode 100644 index 000000000..200b6a7b7 --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/ES_Teletext.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#ifndef ES_TELETEXT_H +#define ES_TELETEXT_H + +#include "elementaryStream.h" + +class ES_Teletext : public ElementaryStream +{ +public: + ES_Teletext(uint16_t pid); + virtual ~ES_Teletext(); + + virtual void Parse(STREAM_PKT* pkt); +}; + +#endif /* ES_TELETEXT_H */ diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/ES_h264.cpp b/addons/pvr.mythtv.cmyth/src/demuxer/ES_h264.cpp new file mode 100644 index 000000000..960a508e9 --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/ES_h264.cpp @@ -0,0 +1,583 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include + +#include "ES_h264.h" +#include "bitstream.h" + +static const int h264_lev2cpbsize[][2] = +{ + {10, 175}, + {11, 500}, + {12, 1000}, + {13, 2000}, + {20, 2000}, + {21, 4000}, + {22, 4000}, + {30, 10000}, + {31, 14000}, + {32, 20000}, + {40, 25000}, + {41, 62500}, + {42, 62500}, + {50, 135000}, + {51, 240000}, + {-1, -1}, +}; + +ES_h264::ES_h264(uint16_t pes_pid) + : ElementaryStream(pes_pid) +{ + m_Height = 0; + m_Width = 0; + m_FPS = 25; + m_FpsScale = 0; + m_FrameDuration = 0; + m_vbvDelay = -1; + m_vbvSize = 0; + m_PixelAspect.den = 1; + m_PixelAspect.num = 0; + m_DTS = 0; + m_PTS = 0; + es_alloc_init = 240000; + Reset(); +} + +ES_h264::~ES_h264() +{ +} + +void ES_h264::Parse(STREAM_PKT* pkt) +{ + int frame_ptr = es_consumed; + int p = es_parsed; + uint32_t startcode = m_StartCode; + bool frameComplete = false; + int l; + while ((l = es_len - p) > 3) + { + if ((startcode & 0xffffff00) == 0x00000100) + { + if (Parse_H264(startcode, p, frameComplete) < 0) + { + break; + } + } + startcode = startcode << 8 | es_buf[p++]; + } + es_parsed = p; + m_StartCode = startcode; + + if (frameComplete) + { + if (!m_NeedSPS && !m_NeedIFrame) + { + double PAR = (double)m_PixelAspect.num/(double)m_PixelAspect.den; + double DAR = (PAR * m_Width) / m_Height; + demux_dbg(DEMUX_DBG_PARSE, "H.264 SPS: PAR %i:%i\n", m_PixelAspect.num, m_PixelAspect.den); + demux_dbg(DEMUX_DBG_PARSE, "H.264 SPS: DAR %.2f\n", DAR); + if (m_FpsScale == 0) + { + m_FpsScale = static_cast(Rescale(c_dts - p_dts, RESCALE_TIME_BASE, PTS_TIME_BASE)); + } + bool streamChange = SetVideoInformation(m_FpsScale, RESCALE_TIME_BASE, m_Height, m_Width, static_cast(DAR)); + pkt->pid = pid; + pkt->size = es_consumed - frame_ptr; + pkt->data = &es_buf[frame_ptr]; + pkt->dts = m_DTS; + pkt->pts = m_PTS; + pkt->duration = c_dts - p_dts; + pkt->streamChange = streamChange; + } + m_StartCode = 0xffffffff; + es_parsed = es_consumed; + es_found_frame = false; + } +} + +void ES_h264::Reset() +{ + ElementaryStream::Reset(); + m_StartCode = 0xffffffff; + m_NeedIFrame = true; + m_NeedSPS = true; + m_NeedPPS = true; + memset(&m_streamData, 0, sizeof(m_streamData)); +} + +int ES_h264::Parse_H264(uint32_t startcode, int buf_ptr, bool &complete) +{ + int len = es_len - buf_ptr; + uint8_t *buf = es_buf + buf_ptr; + + switch(startcode & 0x9f) + { + case 1: + case 2: + case 3: + case 4: + case 5: + { + if (m_NeedSPS || m_NeedPPS) + { + es_found_frame = true; + return 0; + } + // need at least 32 bytes for parsing nal + if (len < 32) + return -1; + h264_private::VCL_NAL vcl; + memset(&vcl, 0, sizeof(h264_private::VCL_NAL)); + vcl.nal_ref_idc = startcode & 0x60; + vcl.nal_unit_type = startcode & 0x1F; + if (!Parse_SLH(buf, len, vcl)) + return 0; + + // check for the beginning of a new access unit + if (es_found_frame && IsFirstVclNal(vcl)) + { + complete = true; + es_consumed = buf_ptr - 4; + return -1; + } + + if (!es_found_frame) + { + if (buf_ptr - 4 >= (int)es_pts_pointer) + { + m_DTS = c_dts; + m_PTS = c_pts; + } + else + { + m_DTS = p_dts; + m_PTS = p_pts; + } + } + + m_streamData.vcl_nal = vcl; + es_found_frame = true; + break; + } + + case NAL_SEI: + if (es_found_frame) + { + complete = true; + es_consumed = buf_ptr - 4; + return -1; + } + break; + + case NAL_SPS: + { + if (es_found_frame) + { + complete = true; + es_consumed = buf_ptr - 4; + return -1; + } + // TODO: how big is SPS? + if (len < 256) + return -1; + if (!Parse_SPS(buf, len)) + return 0; + + m_NeedSPS = false; + break; + } + + case NAL_PPS: + { + if (es_found_frame) + { + complete = true; + es_consumed = buf_ptr - 4; + return -1; + } + // TODO: how big is PPS + if (len < 64) + return -1; + if (!Parse_PPS(buf, len)) + return 0; + m_NeedPPS = false; + break; + } + + case NAL_AUD: + if (es_found_frame && (p_dts != PTS_UNSET)) + { + complete = true; + es_consumed = buf_ptr - 4; + return -1; + } + break; + + case NAL_END_SEQ: + if (es_found_frame) + { + complete = true; + es_consumed = buf_ptr; + return -1; + } + break; + + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + if (es_found_frame) + { + complete = true; + es_consumed = buf_ptr - 4; + return -1; + } + break; + + default: + break; + } + + return 0; +} + +bool ES_h264::Parse_PPS(uint8_t *buf, int len) +{ + cBitstream bs(buf, len*8); + + int pps_id = bs.readGolombUE(); + int sps_id = bs.readGolombUE(); + m_streamData.pps[pps_id].sps = sps_id; + bs.readBits1(); + m_streamData.pps[pps_id].pic_order_present_flag = bs.readBits1(); + return true; +} + +bool ES_h264::Parse_SLH(uint8_t *buf, int len, h264_private::VCL_NAL &vcl) +{ + cBitstream bs(buf, len*8); + + bs.readGolombUE(); /* first_mb_in_slice */ + int slice_type = bs.readGolombUE(); + + if (slice_type > 4) + slice_type -= 5; /* Fixed slice type per frame */ + + switch (slice_type) + { + case 0: + break; + case 1: + break; + case 2: + m_NeedIFrame = false; + break; + default: + return false; + } + + int pps_id = bs.readGolombUE(); + int sps_id = m_streamData.pps[pps_id].sps; + if (m_streamData.sps[sps_id].cbpsize == 0) + return false; + + m_vbvSize = m_streamData.sps[sps_id].cbpsize; + m_vbvDelay = -1; + + vcl.pic_parameter_set_id = pps_id; + vcl.frame_num = bs.readBits(m_streamData.sps[sps_id].log2_max_frame_num); + if (!m_streamData.sps[sps_id].frame_mbs_only_flag) + { + vcl.field_pic_flag = bs.readBits1(); + // interlaced +// if (vcl.field_pic_flag) +// m_FPS *= 2; + } + if (vcl.field_pic_flag) + vcl.bottom_field_flag = bs.readBits1(); + + if (vcl.nal_unit_type == 5) + vcl.idr_pic_id = bs.readGolombUE(); + if (m_streamData.sps[sps_id].pic_order_cnt_type == 0) + { + vcl.pic_order_cnt_lsb = bs.readBits(m_streamData.sps[sps_id].log2_max_pic_order_cnt_lsb); + if(m_streamData.pps[pps_id].pic_order_present_flag && !vcl.field_pic_flag) + vcl.delta_pic_order_cnt_bottom = bs.readGolombSE(); + } + if(m_streamData.sps[sps_id].pic_order_cnt_type == 1 && + !m_streamData.sps[sps_id].delta_pic_order_always_zero_flag ) + { + vcl.delta_pic_order_cnt_0 = bs.readGolombSE(); + if(m_streamData.pps[pps_id].pic_order_present_flag && !vcl.field_pic_flag ) + vcl.delta_pic_order_cnt_1 = bs.readGolombSE(); + } + + vcl.pic_order_cnt_type = m_streamData.sps[sps_id].pic_order_cnt_type; + + return true; +} + +bool ES_h264::Parse_SPS(uint8_t *buf, int len) +{ + cBitstream bs(buf, len*8); + unsigned int tmp, frame_mbs_only; + int cbpsize = -1; + + int profile_idc = bs.readBits(8); + /* constraint_set0_flag = bs.readBits1(); */ + /* constraint_set1_flag = bs.readBits1(); */ + /* constraint_set2_flag = bs.readBits1(); */ + /* constraint_set3_flag = bs.readBits1(); */ + /* reserved = bs.readBits(4); */ + bs.skipBits(8); + int level_idc = bs.readBits(8); + unsigned int seq_parameter_set_id = bs.readGolombUE(9); + + unsigned int i = 0; + while (h264_lev2cpbsize[i][0] != -1) + { + if (h264_lev2cpbsize[i][0] >= level_idc) + { + cbpsize = h264_lev2cpbsize[i][1]; + break; + } + i++; + } + if (cbpsize < 0) + return false; + + memset(&m_streamData.sps[seq_parameter_set_id], 0, sizeof(h264_private::SPS)); + m_streamData.sps[seq_parameter_set_id].cbpsize = cbpsize * 125; /* Convert from kbit to bytes */ + + if( profile_idc == 100 || profile_idc == 110 || + profile_idc == 122 || profile_idc == 244 || profile_idc == 44 || + profile_idc == 83 || profile_idc == 86 || profile_idc == 118 || + profile_idc == 128 ) + { + int chroma_format_idc = bs.readGolombUE(9); /* chroma_format_idc */ + if(chroma_format_idc == 3) + bs.skipBits(1); /* residual_colour_transform_flag */ + bs.readGolombUE(); /* bit_depth_luma - 8 */ + bs.readGolombUE(); /* bit_depth_chroma - 8 */ + bs.skipBits(1); /* transform_bypass */ + if (bs.readBits1()) /* seq_scaling_matrix_present */ + { + for (int i = 0; i < ((chroma_format_idc != 3) ? 8 : 12); i++) + { + if (bs.readBits1()) /* seq_scaling_list_present */ + { + int last = 8, next = 8, size = (i<6) ? 16 : 64; + for (int j = 0; j < size; j++) + { + if (next) + next = (last + bs.readGolombSE()) & 0xff; + last = !next ? last: next; + } + } + } + } + } + + int log2_max_frame_num_minus4 = bs.readGolombUE(); /* log2_max_frame_num - 4 */ + m_streamData.sps[seq_parameter_set_id].log2_max_frame_num = log2_max_frame_num_minus4 + 4; + int pic_order_cnt_type = bs.readGolombUE(9); + m_streamData.sps[seq_parameter_set_id].pic_order_cnt_type = pic_order_cnt_type; + if (pic_order_cnt_type == 0) + { + int log2_max_pic_order_cnt_lsb_minus4 = bs.readGolombUE(); /* log2_max_poc_lsb - 4 */ + m_streamData.sps[seq_parameter_set_id].log2_max_pic_order_cnt_lsb = log2_max_pic_order_cnt_lsb_minus4 + 4; + } + else if (pic_order_cnt_type == 1) + { + m_streamData.sps[seq_parameter_set_id].delta_pic_order_always_zero_flag = bs.readBits1(); + bs.readGolombSE(); /* offset_for_non_ref_pic */ + bs.readGolombSE(); /* offset_for_top_to_bottom_field */ + tmp = bs.readGolombUE(); /* num_ref_frames_in_pic_order_cnt_cycle */ + for (unsigned int i = 0; i < tmp; i++) + bs.readGolombSE(); /* offset_for_ref_frame[i] */ + } + else if(pic_order_cnt_type != 2) + { + /* Illegal poc */ + return false; + } + + bs.readGolombUE(9); /* ref_frames */ + bs.skipBits(1); /* gaps_in_frame_num_allowed */ + m_Width /* mbs */ = bs.readGolombUE() + 1; + m_Height /* mbs */ = bs.readGolombUE() + 1; + frame_mbs_only = bs.readBits1(); + m_streamData.sps[seq_parameter_set_id].frame_mbs_only_flag = frame_mbs_only; + demux_dbg(DEMUX_DBG_PARSE, "H.264 SPS: pic_width: %u mbs\n", (unsigned) m_Width); + demux_dbg(DEMUX_DBG_PARSE, "H.264 SPS: pic_height: %u mbs\n", (unsigned) m_Height); + demux_dbg(DEMUX_DBG_PARSE, "H.264 SPS: frame only flag: %d\n", frame_mbs_only); + + m_Width *= 16; + m_Height *= 16 * (2-frame_mbs_only); + + if (!frame_mbs_only) + { + if (bs.readBits1()) /* mb_adaptive_frame_field_flag */ + demux_dbg(DEMUX_DBG_PARSE, "H.264 SPS: MBAFF\n"); + } + bs.skipBits(1); /* direct_8x8_inference_flag */ + if (bs.readBits1()) /* frame_cropping_flag */ + { + uint32_t crop_left = bs.readGolombUE(); + uint32_t crop_right = bs.readGolombUE(); + uint32_t crop_top = bs.readGolombUE(); + uint32_t crop_bottom = bs.readGolombUE(); + demux_dbg(DEMUX_DBG_PARSE, "H.264 SPS: cropping %d %d %d %d\n", crop_left, crop_top, crop_right, crop_bottom); + + m_Width -= 2*(crop_left + crop_right); + if (frame_mbs_only) + m_Height -= 2*(crop_top + crop_bottom); + else + m_Height -= 4*(crop_top + crop_bottom); + } + + /* VUI parameters */ + m_PixelAspect.num = 0; + if (bs.readBits1()) /* vui_parameters_present flag */ + { + if (bs.readBits1()) /* aspect_ratio_info_present */ + { + uint32_t aspect_ratio_idc = bs.readBits(8); + demux_dbg(DEMUX_DBG_PARSE, "H.264 SPS: aspect_ratio_idc %d\n", aspect_ratio_idc); + + if (aspect_ratio_idc == 255 /* Extended_SAR */) + { + m_PixelAspect.num = bs.readBits(16); /* sar_width */ + m_PixelAspect.den = bs.readBits(16); /* sar_height */ + demux_dbg(DEMUX_DBG_PARSE, "H.264 SPS: -> sar %dx%d\n", m_PixelAspect.num, m_PixelAspect.den); + } + else + { + static const mpeg_rational_t aspect_ratios[] = + { /* page 213: */ + /* 0: unknown */ + {0, 1}, + /* 1...16: */ + { 1, 1}, {12, 11}, {10, 11}, {16, 11}, { 40, 33}, {24, 11}, {20, 11}, {32, 11}, + {80, 33}, {18, 11}, {15, 11}, {64, 33}, {160, 99}, { 4, 3}, { 3, 2}, { 2, 1} + }; + + if (aspect_ratio_idc < sizeof(aspect_ratios)/sizeof(aspect_ratios[0])) + { + memcpy(&m_PixelAspect, &aspect_ratios[aspect_ratio_idc], sizeof(mpeg_rational_t)); + demux_dbg(DEMUX_DBG_PARSE, "H.264 SPS: PAR %d / %d\n", m_PixelAspect.num, m_PixelAspect.den); + } + else + { + demux_dbg(DEMUX_DBG_PARSE, "H.264 SPS: aspect_ratio_idc out of range !\n"); + } + } + } + if (bs.readBits1()) // overscan + { + bs.readBits1(); // overscan_appropriate_flag + } + if (bs.readBits1()) // video_signal_type_present_flag + { + bs.readBits(3); // video_format + bs.readBits1(); // video_full_range_flag + if (bs.readBits1()) // colour_description_present_flag + { + bs.readBits(8); // colour_primaries + bs.readBits(8); // transfer_characteristics + bs.readBits(8); // matrix_coefficients + } + } + + if (bs.readBits1()) // chroma_loc_info_present_flag + { + bs.readGolombUE(); // chroma_sample_loc_type_top_field + bs.readGolombUE(); // chroma_sample_loc_type_bottom_field + } + + if (bs.readBits1()) // timing_info_present_flag + { +// uint32_t num_units_in_tick = bs.readBits(32); +// uint32_t time_scale = bs.readBits(32); +// int fixed_frame_rate = bs.readBits1(); +// if (num_units_in_tick > 0) +// m_FPS = time_scale / (num_units_in_tick * 2); + } + } + + demux_dbg(DEMUX_DBG_PARSE, "H.264 SPS: -> video size %dx%d, aspect %d:%d\n", m_Width, m_Height, m_PixelAspect.num, m_PixelAspect.den); + return true; +} + +bool ES_h264::IsFirstVclNal(h264_private::VCL_NAL &vcl) +{ + if (m_streamData.vcl_nal.frame_num != vcl.frame_num) + return true; + + if (m_streamData.vcl_nal.pic_parameter_set_id != vcl.pic_parameter_set_id) + return true; + + if (m_streamData.vcl_nal.field_pic_flag != vcl.field_pic_flag) + return true; + + if (m_streamData.vcl_nal.field_pic_flag && vcl.field_pic_flag) + { + if (m_streamData.vcl_nal.bottom_field_flag != vcl.bottom_field_flag) + return true; + } + + if (m_streamData.vcl_nal.nal_ref_idc == 0 || vcl.nal_ref_idc == 0) + { + if (m_streamData.vcl_nal.nal_ref_idc != vcl.nal_ref_idc) + return true; + } + + if (m_streamData.vcl_nal.pic_order_cnt_type == 0 && vcl.pic_order_cnt_type == 0) + { + if (m_streamData.vcl_nal.pic_order_cnt_lsb != vcl.pic_order_cnt_lsb) + return true; + if (m_streamData.vcl_nal.delta_pic_order_cnt_bottom != vcl.delta_pic_order_cnt_bottom) + return true; + } + + if (m_streamData.vcl_nal.pic_order_cnt_type == 1 && vcl.pic_order_cnt_type == 1) + { + if (m_streamData.vcl_nal.delta_pic_order_cnt_0 != vcl.delta_pic_order_cnt_0) + return true; + if (m_streamData.vcl_nal.delta_pic_order_cnt_1 != vcl.delta_pic_order_cnt_1) + return true; + } + + if (m_streamData.vcl_nal.nal_unit_type == 5 || vcl.nal_unit_type == 5) + { + if (m_streamData.vcl_nal.nal_unit_type != vcl.nal_unit_type) + return true; + } + + if (m_streamData.vcl_nal.nal_unit_type == 5 && vcl.nal_unit_type == 5) + { + if (m_streamData.vcl_nal.idr_pic_id != vcl.idr_pic_id) + return true; + } + return false; +} diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/ES_h264.h b/addons/pvr.mythtv.cmyth/src/demuxer/ES_h264.h new file mode 100644 index 000000000..c93ac064c --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/ES_h264.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2005-2013 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#ifndef ES_H264_H +#define ES_H264_H + +#include "elementaryStream.h" + +class ES_h264 : public ElementaryStream +{ +private: + typedef struct h264_private + { + struct SPS + { + int frame_duration; + int cbpsize; + int pic_order_cnt_type; + int frame_mbs_only_flag; + int log2_max_frame_num; + int log2_max_pic_order_cnt_lsb; + int delta_pic_order_always_zero_flag; + } sps[256]; + + struct PPS + { + int sps; + int pic_order_present_flag; + } pps[256]; + + struct VCL_NAL + { + int frame_num; // slice + int pic_parameter_set_id; // slice + int field_pic_flag; // slice + int bottom_field_flag; // slice + int delta_pic_order_cnt_bottom; // slice + int delta_pic_order_cnt_0; // slice + int delta_pic_order_cnt_1; // slice + int pic_order_cnt_lsb; // slice + int idr_pic_id; // slice + int nal_unit_type; + int nal_ref_idc; // start code + int pic_order_cnt_type; // sps + } vcl_nal; + + } h264_private_t; + + typedef struct mpeg_rational_s { + int num; + int den; + } mpeg_rational_t; + + enum + { + NAL_SLH = 0x01, // Slice Header + NAL_SEI = 0x06, // Supplemental Enhancement Information + NAL_SPS = 0x07, // Sequence Parameter Set + NAL_PPS = 0x08, // Picture Parameter Set + NAL_AUD = 0x09, // Access Unit Delimiter + NAL_END_SEQ = 0x0A // End of Sequence + }; + + uint32_t m_StartCode; + bool m_NeedIFrame; + bool m_NeedSPS; + bool m_NeedPPS; + int m_Width; + int m_Height; + int m_FPS; + int m_FpsScale; + mpeg_rational_t m_PixelAspect; + int m_FrameDuration; + h264_private m_streamData; + int m_vbvDelay; /* -1 if CBR */ + int m_vbvSize; /* Video buffer size (in bytes) */ + int64_t m_DTS; + int64_t m_PTS; + + int Parse_H264(uint32_t startcode, int buf_ptr, bool &complete); + bool Parse_PPS(uint8_t *buf, int len); + bool Parse_SLH(uint8_t *buf, int len, h264_private::VCL_NAL &vcl); + bool Parse_SPS(uint8_t *buf, int len); + bool IsFirstVclNal(h264_private::VCL_NAL &vcl); + +public: + ES_h264(uint16_t pes_pid); + virtual ~ES_h264(); + + virtual void Parse(STREAM_PKT* pkt); + virtual void Reset(); +}; + +#endif /* ES_H264_H */ diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/bitstream.cpp b/addons/pvr.mythtv.cmyth/src/demuxer/bitstream.cpp new file mode 100644 index 000000000..76404a563 --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/bitstream.cpp @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2005-2012 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#include +#include +#include "bitstream.h" + +cBitstream::cBitstream(uint8_t *data, int bits) +{ + m_data = data; + m_offset = 0; + m_len = bits; + m_error = false; +} + +void cBitstream::setBitstream(uint8_t *data, int bits) +{ + m_data = data; + m_offset = 0; + m_len = bits; + m_error = false; +} + +void cBitstream::skipBits(int num) +{ + m_offset += num; +} + +unsigned int cBitstream::readBits(int num) +{ + int r = 0; + + while(num > 0) + { + if(m_offset >= m_len) + { + m_error = true; + return 0; + } + + num--; + + if(m_data[m_offset / 8] & (1 << (7 - (m_offset & 7)))) + r |= 1 << num; + + m_offset++; + } + return r; +} + +unsigned int cBitstream::showBits(int num) +{ + int r = 0; + int offs = m_offset; + + while(num > 0) + { + if(offs >= m_len) + { + m_error = true; + return 0; + } + + num--; + + if(m_data[offs / 8] & (1 << (7 - (offs & 7)))) + r |= 1 << num; + + offs++; + } + return r; +} + +unsigned int cBitstream::readGolombUE(int maxbits) +{ + int lzb = -1; + int bits = 0; + + for(int b = 0; !b; lzb++, bits++) + { + if (bits > maxbits) + return 0; + b = readBits1(); + } + + return (1 << lzb) - 1 + readBits(lzb); +} + +signed int cBitstream::readGolombSE() +{ + int v, pos; + v = readGolombUE(); + if(v == 0) + return 0; + + pos = (v & 1); + v = (v + 1) >> 1; + return pos ? v : -v; +} + + +unsigned int cBitstream::remainingBits() +{ + return m_len - m_offset; +} + + +void cBitstream::putBits(int val, int num) +{ + while(num > 0) { + if(m_offset >= m_len) + { + m_error = true; + return; + } + + num--; + + if(val & (1 << num)) + m_data[m_offset / 8] |= 1 << (7 - (m_offset & 7)); + else + m_data[m_offset / 8] &= ~(1 << (7 - (m_offset & 7))); + + m_offset++; + } +} diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/bitstream.h b/addons/pvr.mythtv.cmyth/src/demuxer/bitstream.h new file mode 100644 index 000000000..ed36216d0 --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/bitstream.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2005-2012 Team XBMC + * http://www.xbmc.org + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with XBMC; see the file COPYING. If not, see + * . + * + */ + +#ifndef BITSTREAM_H +#define BITSTREAM_H + +#include + +class cBitstream +{ +private: + uint8_t *m_data; + int m_offset; + int m_len; + bool m_error; + +public: + cBitstream(uint8_t *data, int bits); + + void setBitstream(uint8_t *data, int bits); + void skipBits(int num); + unsigned int readBits(int num); + unsigned int showBits(int num); + unsigned int readBits1() { return readBits(1); } + unsigned int readGolombUE(int maxbits = 32); + signed int readGolombSE(); + unsigned int remainingBits(); + void putBits(int val, int num); + int length() { return m_len; } + bool isError() { return m_error; } +}; + +#endif /* BITSTREAM_H */ diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/common.h b/addons/pvr.mythtv.cmyth/src/demuxer/common.h new file mode 100644 index 000000000..032a0853b --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/common.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2013 Jean-Luc Barriere + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef COMMON_H +#define COMMON_H + +#include +#include +#include +#include +#include +#include + +extern "C" { +#include "debug.h" +} + +#define ES_INIT_BUFFER_SIZE 64000 +#define ES_MAX_BUFFER_SIZE 1048576 +#define MAX_RESYNC_SIZE 65536 +#define PTS_MASK 0x1ffffffffLL +#define PTS_UNSET 0x1ffffffffLL +#define PTS_TIME_BASE 90000LL +#define RESCALE_TIME_BASE 1000000LL + +#endif /* COMMON_H */ diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/debug.cpp b/addons/pvr.mythtv.cmyth/src/demuxer/debug.cpp new file mode 100644 index 000000000..2cb704ba9 --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/debug.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2013 Jean-Luc Barriere + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "debug.h" + +extern "C" { + +#include +#include +#include +#include + +typedef struct { + const char *name; + int cur_level; + int (*selector)(int plevel, int slevel); + void (*msg_callback)(int level, char *msg); +} demux_debug_ctx_t; + +#define DEMUX_DEBUG_CTX_INIT(n,l,s) { n, l, s, NULL } + +static demux_debug_ctx_t demux_debug_ctx = DEMUX_DEBUG_CTX_INIT("demuxer", DEMUX_DBG_NONE, NULL); + +/** + * Set the debug level to be used for the subsystem + * \param ctx the subsystem debug context to use + * \param level the debug level for the subsystem + * \return an integer subsystem id used for future interaction + */ +static inline void +__demux_dbg_setlevel(demux_debug_ctx_t *ctx, int level) +{ + if (ctx != NULL) { + ctx->cur_level = level; + } +} + +/** + * Generate a debug message at a given debug level + * \param ctx the subsystem debug context to use + * \param level the debug level of the debug message + * \param fmt a printf style format string for the message + * \param ... arguments to the format + */ +static inline void +__demux_dbg(demux_debug_ctx_t *ctx, int level, const char *fmt, va_list ap) +{ + char msg[4096]; + int len; + if (!ctx) { + return; + } + if ((ctx->selector && ctx->selector(level, ctx->cur_level)) || + (!ctx->selector && (level <= ctx->cur_level))) { + len = snprintf(msg, sizeof(msg), "(%s)", ctx->name); + vsnprintf(msg + len, sizeof(msg)-len, fmt, ap); + if (ctx->msg_callback) { + ctx->msg_callback(level, msg); + } else { + fwrite(msg, strlen(msg), 1, stdout); + } + } +} + +void demux_dbg_level(int l) +{ + __demux_dbg_setlevel(&demux_debug_ctx, l); +} + +void demux_dbg_all() +{ + __demux_dbg_setlevel(&demux_debug_ctx, DEMUX_DBG_ALL); +} + +void demux_dbg_none() +{ + __demux_dbg_setlevel(&demux_debug_ctx, DEMUX_DBG_NONE); +} + +void demux_dbg(int level, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + __demux_dbg(&demux_debug_ctx, level, fmt, ap); + va_end(ap); +} + +void demux_set_dbg_msgcallback(void (*msgcb)(int level, char *)) +{ + demux_debug_ctx.msg_callback = msgcb; +} + +} diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/debug.h b/addons/pvr.mythtv.cmyth/src/demuxer/debug.h new file mode 100644 index 000000000..6470863d1 --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/debug.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2013 Jean-Luc Barriere + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef DEBUG_H +#define DEBUG_H + +#define DEMUX_DBG_NONE -1 +#define DEMUX_DBG_ERROR 0 +#define DEMUX_DBG_WARN 1 +#define DEMUX_DBG_INFO 2 +#define DEMUX_DBG_DEBUG 3 +#define DEMUX_DBG_PARSE 4 +#define DEMUX_DBG_ALL 6 + +#ifdef _MSC_VER +#define snprintf _snprintf +#endif + +extern "C" { +extern void demux_dbg_level(int l); +extern void demux_dbg_all(void); +extern void demux_dbg_none(void); +extern void demux_dbg(int level, const char *fmt, ...); +extern void demux_set_dbg_msgcallback(void (*msgcb)(int level,char *)); +} + +#endif /* DEBUG_H */ diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/elementaryStream.cpp b/addons/pvr.mythtv.cmyth/src/demuxer/elementaryStream.cpp new file mode 100644 index 000000000..4a65b32eb --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/elementaryStream.cpp @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2013 Jean-Luc Barriere + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "elementaryStream.h" + +ElementaryStream::ElementaryStream(uint16_t pes_pid) + : pid(pes_pid) + , stream_type(STREAM_TYPE_UNKNOWN) + , c_dts(PTS_UNSET) + , c_pts(PTS_UNSET) + , p_dts(PTS_UNSET) + , p_pts(PTS_UNSET) + , has_stream_info(false) + , es_alloc_init(ES_INIT_BUFFER_SIZE) + , es_buf(NULL) + , es_alloc(0) + , es_len(0) + , es_consumed(0) + , es_pts_pointer(0) + , es_parsed(0) + , es_found_frame(false) +{ + memset(&stream_info, 0, sizeof(ElementaryStream::STREAM_INFO)); +} + +ElementaryStream::~ElementaryStream(void) +{ + if (es_buf) + { + demux_dbg(DEMUX_DBG_DEBUG, "free stream buffer %.4x: allocated size was %zu\n", pid, es_alloc); + free(es_buf); + es_buf = NULL; + } +} + +void ElementaryStream::Reset(void) +{ + ClearBuffer(); + es_found_frame = false; +} + +void ElementaryStream::ClearBuffer() +{ + es_len = es_consumed = es_pts_pointer = es_parsed = 0; +} + +int ElementaryStream::Append(const unsigned char* buf, size_t len, bool new_pts) +{ + // Mark position where current pts become applicable + if (new_pts) + es_pts_pointer = es_len; + + if (es_buf && es_consumed) + { + if (es_consumed < es_len) + { + memmove(es_buf, es_buf + es_consumed, es_len - es_consumed); + es_len -= es_consumed; + es_parsed -= es_consumed; + if (es_pts_pointer > es_consumed) + es_pts_pointer -= es_consumed; + else + es_pts_pointer = 0; + + es_consumed = 0; + } + else + ClearBuffer(); + } + if (es_len + len > es_alloc) + { + if (es_alloc >= ES_MAX_BUFFER_SIZE) + return -ENOMEM; + + size_t n = (es_alloc ? (es_alloc + len) * 2 : es_alloc_init); + if (n > ES_MAX_BUFFER_SIZE) + n = ES_MAX_BUFFER_SIZE; + + demux_dbg(DEMUX_DBG_DEBUG, "realloc buffer size to %zu for stream %.4x\n", n, pid); + unsigned char* p = es_buf; + es_buf = (unsigned char*)realloc(es_buf, n * sizeof(*es_buf)); + if (es_buf) + { + es_alloc = n; + } + else + { + free(p); + es_alloc = 0; + es_len = 0; + return -ENOMEM; + } + } + + if (!es_buf) + return -ENOMEM; + + memcpy(es_buf + es_len, buf, len); + es_len += len; + + return 0; +} + +const char* ElementaryStream::GetStreamCodecName(STREAM_TYPE stream_type) +{ + switch (stream_type) + { + case STREAM_TYPE_VIDEO_MPEG1: + return "mpeg1video"; + case STREAM_TYPE_VIDEO_MPEG2: + return "mpeg2video"; + case STREAM_TYPE_AUDIO_MPEG1: + return "mp1"; + case STREAM_TYPE_AUDIO_MPEG2: + return "mp2"; + case STREAM_TYPE_AUDIO_AAC: + return "aac"; + case STREAM_TYPE_VIDEO_MPEG4: + return "mpeg4video"; + case STREAM_TYPE_VIDEO_H264: + return "h264"; + case STREAM_TYPE_VIDEO_VC1: + return "vc1"; + case STREAM_TYPE_AUDIO_LPCM: + return "lpcm"; + case STREAM_TYPE_AUDIO_AC3: + return "ac3"; + case STREAM_TYPE_AUDIO_EAC3: + return "eac3"; + case STREAM_TYPE_AUDIO_DTS: + return "dts"; + case STREAM_TYPE_DVB_TELETEXT: + return "teletext"; + case STREAM_TYPE_DVB_SUBTITLE: + return "dvbsub"; + default: + return "data"; + } +} + +const char* ElementaryStream::GetStreamCodecName() const +{ + return GetStreamCodecName(stream_type); +} + +bool ElementaryStream::GetStreamPacket(STREAM_PKT* pkt) +{ + ResetStreamPacket(pkt); + Parse(pkt); + if (pkt->data) + return true; + return false; +} + +void ElementaryStream::Parse(STREAM_PKT* pkt) +{ + // No parser: pass-through + if (es_consumed < es_len) + { + es_consumed = es_parsed = es_len; + pkt->pid = pid; + pkt->size = es_consumed; + pkt->data = es_buf; + pkt->dts = c_dts; + pkt->pts = c_pts; + if (c_dts == PTS_UNSET || p_dts == PTS_UNSET) + pkt->duration = 0; + else + pkt->duration = c_dts - p_dts; + pkt->streamChange = false; + } +} + +void ElementaryStream::ResetStreamPacket(STREAM_PKT* pkt) +{ + pkt->pid = 0xffff; + pkt->size = 0; + pkt->data = NULL; + pkt->dts = PTS_UNSET; + pkt->pts = PTS_UNSET; + pkt->duration = 0; + pkt->streamChange = false; +} + +uint64_t ElementaryStream::Rescale(uint64_t a, uint64_t b, uint64_t c) +{ + uint64_t r = c / 2; + + if (b <= INT_MAX && c <= INT_MAX) + { + if (a <= INT_MAX) + return (a * b + r) / c; + else + return a / c * b + (a % c * b + r) / c; + } + else + { + uint64_t a0 = a & 0xFFFFFFFF; + uint64_t a1 = a >> 32; + uint64_t b0 = b & 0xFFFFFFFF; + uint64_t b1 = b >> 32; + uint64_t t1 = a0 * b1 + a1 * b0; + uint64_t t1a = t1 << 32; + + a0 = a0 * b0 + t1a; + a1 = a1 * b1 + (t1 >> 32) + (a0 < t1a); + a0 += r; + a1 += a0 < r; + + for (int i = 63; i >= 0; i--) + { + a1 += a1 + ((a0 >> i) & 1); + t1 += t1; + if (c <= a1) + { + a1 -= c; + t1++; + } + } + return t1; + } +} + +bool ElementaryStream::SetVideoInformation(int FpsScale, int FpsRate, int Height, int Width, float Aspect) +{ + bool ret = false; + if ((stream_info.fps_scale != FpsScale) || + (stream_info.fps_rate != FpsRate) || + (stream_info.height != Height) || + (stream_info.width != Width) || + (stream_info.aspect != Aspect)) + ret = true; + + stream_info.fps_scale = FpsScale; + stream_info.fps_rate = FpsRate; + stream_info.height = Height; + stream_info.width = Width; + stream_info.aspect = Aspect; + + has_stream_info = true; + return ret; +} + +bool ElementaryStream::SetAudioInformation(int Channels, int SampleRate, int BitRate, int BitsPerSample, int BlockAlign) +{ + bool ret = false; + if ((stream_info.channels != Channels) || + (stream_info.sample_rate != SampleRate) || + (stream_info.block_align != BlockAlign) || + (stream_info.bit_rate != BitRate) || + (stream_info.bits_Per_sample != BitsPerSample)) + ret = true; + + stream_info.channels = Channels; + stream_info.sample_rate = SampleRate; + stream_info.block_align = BlockAlign; + stream_info.bit_rate = BitRate; + stream_info.bits_Per_sample = BitsPerSample; + + has_stream_info = true; + return ret; +} diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/elementaryStream.h b/addons/pvr.mythtv.cmyth/src/demuxer/elementaryStream.h new file mode 100644 index 000000000..886829874 --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/elementaryStream.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2013 Jean-Luc Barriere + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef ELEMENTARYSTREAM_H +#define ELEMENTARYSTREAM_H + +#include "common.h" + +enum STREAM_TYPE +{ + STREAM_TYPE_UNKNOWN = 0, + STREAM_TYPE_VIDEO_MPEG1, + STREAM_TYPE_VIDEO_MPEG2, + STREAM_TYPE_AUDIO_MPEG1, + STREAM_TYPE_AUDIO_MPEG2, + STREAM_TYPE_PRIVATE_DATA, + STREAM_TYPE_AUDIO_AAC, + STREAM_TYPE_VIDEO_MPEG4, + STREAM_TYPE_VIDEO_H264, + STREAM_TYPE_VIDEO_VC1, + STREAM_TYPE_AUDIO_LPCM, + STREAM_TYPE_AUDIO_AC3, + STREAM_TYPE_AUDIO_EAC3, + STREAM_TYPE_AUDIO_DTS, + STREAM_TYPE_DVB_TELETEXT, + STREAM_TYPE_DVB_SUBTITLE +}; + +class ElementaryStream +{ +public: + ElementaryStream(uint16_t pes_pid); + virtual ~ElementaryStream(); + virtual void Reset(); + void ClearBuffer(); + int Append(const unsigned char* buf, size_t len, bool new_pts = false); + const char* GetStreamCodecName() const; + static const char* GetStreamCodecName(STREAM_TYPE stream_type); + + uint16_t pid; + STREAM_TYPE stream_type; + uint64_t c_dts; ///< current MPEG stream DTS (decode time for video) + uint64_t c_pts; ///< current MPEG stream PTS (presentation time for audio and video) + uint64_t p_dts; ///< previous MPEG stream DTS (decode time for video) + uint64_t p_pts; ///< previous MPEG stream PTS (presentation time for audio and video) + + bool has_stream_info; ///< true if stream info is completed else it requires parsing of iframe + + struct STREAM_INFO + { + char language[4]; + int composition_id; + int ancillary_id; + int fps_scale; + int fps_rate; + int height; + int width; + float aspect; + int channels; + int sample_rate; + int block_align; + int bit_rate; + int bits_Per_sample; + } stream_info; + + typedef struct + { + uint16_t pid; + size_t size; + const unsigned char* data; + uint64_t dts; + uint64_t pts; + uint64_t duration; + bool streamChange; + } STREAM_PKT; + + bool GetStreamPacket(STREAM_PKT* pkt); + virtual void Parse(STREAM_PKT* pkt); + +protected: + void ResetStreamPacket(STREAM_PKT* pkt); + uint64_t Rescale(uint64_t a, uint64_t b, uint64_t c); + bool SetVideoInformation(int FpsScale, int FpsRate, int Height, int Width, float Aspect); + bool SetAudioInformation(int Channels, int SampleRate, int BitRate, int BitsPerSample, int BlockAlign); + + size_t es_alloc_init; ///< Initial allocation of memory for buffer + unsigned char* es_buf; ///< The Pointer to buffer + size_t es_alloc; ///< Allocated size of memory for buffer + size_t es_len; ///< Size of data in buffer + size_t es_consumed; ///< Consumed payload. Will be erased on next append + size_t es_pts_pointer; ///< Position in buffer where current PTS becomes applicable + size_t es_parsed; ///< Parser: Last processed position in buffer + bool es_found_frame; ///< Parser: Found frame +}; + +#endif /* ELEMENTARYSTREAM_H */ diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/tsDemuxer.cpp b/addons/pvr.mythtv.cmyth/src/demuxer/tsDemuxer.cpp new file mode 100644 index 000000000..a8f9f4517 --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/tsDemuxer.cpp @@ -0,0 +1,1012 @@ +/* + * Copyright (C) 2013 Jean-Luc Barriere + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#include "tsDemuxer.h" +#include "elementaryStream.h" +#include "ES_MPEGVideo.h" +#include "ES_MPEGAudio.h" +#include "ES_h264.h" +#include "ES_AAC.h" +#include "ES_AC3.h" +#include "ES_Subtitle.h" +#include "ES_Teletext.h" + +using namespace PLATFORM; + +AVContext::AVContext(TSDemuxer* const demux, uint64_t pos, uint16_t channel) + : av_pos(pos) + , av_data_len(FLUTS_NORMAL_TS_PACKETSIZE) + , av_pkt_size(0) + , is_configured(false) + , channel(channel) + , pid(0xffff) + , transport_error(false) + , has_payload(false) + , payload_unit_start(false) + , discontinuity(false) + , payload(NULL) + , payload_len(0) + , packet(NULL) +{ + m_demux = demux; + memset(av_buf, 0, sizeof(av_buf)); +}; + +void AVContext::Reset(void) +{ + CLockObject lock(mutex); + + pid = 0xffff; + transport_error = false; + has_payload = false; + payload_unit_start = false; + discontinuity = false; + payload = NULL; + payload_len = 0; + packet = NULL; +} + +uint16_t AVContext::GetPID() const +{ + return pid; +} + +PACKET_TYPE AVContext::GetPIDType() const +{ + CLockObject lock(mutex); + + if (packet) + return packet->packet_type; + return PACKET_TYPE_UNKNOWN; +} + +uint16_t AVContext::GetPIDChannel() const +{ + CLockObject lock(mutex); + + if (packet) + return packet->channel; + return 0xffff; +} + +bool AVContext::HasPIDStreamData() const +{ + CLockObject lock(mutex); + + // PES packets append frame buffer of elementary stream until next start of unit + // On new unit start, flag is held + if (packet && packet->has_stream_data) + return true; + return false; +} + +bool AVContext::HasPIDPayload() const +{ + return has_payload; +} + +ElementaryStream* AVContext::GetPIDStream() +{ + CLockObject lock(mutex); + + if (packet && packet->packet_type == PACKET_TYPE_PES) + return packet->stream; + return NULL; +} + +std::vector AVContext::GetStreams() +{ + CLockObject lock(mutex); + + std::vector v; + for (std::map::iterator it = packets.begin(); it != packets.end(); it++) + if (it->second.packet_type == PACKET_TYPE_PES && it->second.stream) + v.push_back(it->second.stream); + return v; +} + +void AVContext::StartStreaming(uint16_t pid) +{ + CLockObject lock(mutex); + + std::map::iterator it = packets.find(pid); + if (it != packets.end()) + it->second.streaming = true; +} + +void AVContext::StopStreaming(uint16_t pid) +{ + CLockObject lock(mutex); + + std::map::iterator it = packets.find(pid); + if (it != packets.end()) + it->second.streaming = false; +} + +ElementaryStream* AVContext::GetStream(uint16_t pid) const +{ + CLockObject lock(mutex); + + std::map::const_iterator it = packets.find(pid); + if (it != packets.end()) + return it->second.stream; + return NULL; +} + +uint16_t AVContext::GetChannel(uint16_t pid) const +{ + CLockObject lock(mutex); + + std::map::const_iterator it = packets.find(pid); + if (it != packets.end()) + return it->second.channel; + return 0xffff; +} + +void AVContext::ResetPackets() +{ + CLockObject lock(mutex); + + for (std::map::iterator it = packets.begin(); it != packets.end(); it++) + { + it->second.Reset(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +///// +///// MPEG-TS parser for the context +///// + +uint8_t AVContext::av_rb8(const unsigned char* p) +{ + uint8_t val = *(uint8_t*)p; + return val; +} + +uint16_t AVContext::av_rb16(const unsigned char* p) +{ + uint16_t val = av_rb8(p) << 8; + val |= av_rb8(p + 1); + return val; +} + +uint32_t AVContext::av_rb32(const unsigned char* p) +{ + uint32_t val = av_rb16(p) << 16; + val |= av_rb16(p + 2); + return val; +} + +uint64_t AVContext::decode_pts(const unsigned char* p) +{ + uint64_t pts = (uint64_t)(av_rb8(p) & 0x0e) << 29 | (av_rb16(p + 1) >> 1) << 15 | av_rb16(p + 3) >> 1; + return pts; +} + +STREAM_TYPE AVContext::get_stream_type(uint8_t pes_type) +{ + switch (pes_type) + { + case 0x01: + return STREAM_TYPE_VIDEO_MPEG1; + case 0x02: + return STREAM_TYPE_VIDEO_MPEG2; + case 0x03: + return STREAM_TYPE_AUDIO_MPEG1; + case 0x04: + return STREAM_TYPE_AUDIO_MPEG2; + case 0x06: + return STREAM_TYPE_PRIVATE_DATA; + case 0x0f: + case 0x11: + return STREAM_TYPE_AUDIO_AAC; + case 0x10: + return STREAM_TYPE_VIDEO_MPEG4; + case 0x1b: + return STREAM_TYPE_VIDEO_H264; + case 0xea: + return STREAM_TYPE_VIDEO_VC1; + case 0x80: + return STREAM_TYPE_AUDIO_LPCM; + case 0x81: + case 0x83: + case 0x84: + case 0x87: + return STREAM_TYPE_AUDIO_AC3; + case 0x82: + case 0x85: + case 0x8a: + return STREAM_TYPE_AUDIO_DTS; + } + return STREAM_TYPE_UNKNOWN; +} + +int AVContext::configure_ts() +{ + const unsigned char* data; + size_t data_size = AV_CONTEXT_PACKETSIZE; + uint64_t pos = av_pos; + int fluts[][2] = { + {FLUTS_NORMAL_TS_PACKETSIZE, 0}, + {FLUTS_M2TS_TS_PACKETSIZE, 0}, + {FLUTS_DVB_ASI_TS_PACKETSIZE, 0}, + {FLUTS_ATSC_TS_PACKETSIZE, 0} + }; + int nb = sizeof (fluts) / (2 * sizeof (int)); + int score = TS_CHECK_MIN_SCORE; + + for (int i = 0; i < MAX_RESYNC_SIZE; i++) + { + if (!(data = m_demux->ReadAV(pos, data_size))) + return AVCONTEXT_IO_ERROR; + if (data[0] == 0x47) + { + int count, found; + for (int t = 0; t < nb; t++) // for all fluts + { + const unsigned char* ndata; + uint64_t npos = pos; + int do_retry = score; // Reach for score + do + { + --do_retry; + npos += fluts[t][0]; + if (!(ndata = m_demux->ReadAV(npos, data_size))) + return AVCONTEXT_IO_ERROR; + } + while (ndata[0] == 0x47 && (++fluts[t][1]) && do_retry); + } + // Is score reached ? + count = found = 0; + for (int t = 0; t < nb; t++) + { + if (fluts[t][1] == score) + { + found = t; + ++count; + } + // Reset score for next retry + fluts[t][1] = 0; + } + // One and only one is eligible + if (count == 1) + { + demux_dbg(DEMUX_DBG_DEBUG, "%s: packet size is %d\n", __FUNCTION__, fluts[found][0]); + av_pkt_size = fluts[found][0]; + av_pos = pos; + return AVCONTEXT_CONTINUE; + } + // More one: Retry for highest score + else if (count > 1 && ++score > TS_CHECK_MAX_SCORE) + // Packet size remains undetermined + break; + // None: Bad sync. Shift and retry + else + pos++; + } + else + pos++; + } + + demux_dbg(DEMUX_DBG_ERROR, "%s: invalid stream\n", __FUNCTION__); + return AVCONTEXT_TS_NOSYNC; +} + +int AVContext::TSResync() +{ + const unsigned char* data; + if (!is_configured) + { + int ret = configure_ts(); + if (ret != AVCONTEXT_CONTINUE) + return ret; + is_configured = true; + } + for (int i = 0; i < MAX_RESYNC_SIZE; i++) + { + data = m_demux->ReadAV(av_pos, av_pkt_size); + if (!data) + return AVCONTEXT_IO_ERROR; + if (data[0] == 0x47) + { + memcpy(av_buf, data, av_pkt_size); + Reset(); + return AVCONTEXT_CONTINUE; + } + av_pos++; + } + + return AVCONTEXT_TS_NOSYNC; +} + +uint64_t AVContext::GoNext() +{ + av_pos += av_pkt_size; + Reset(); + return av_pos; +} + +uint64_t AVContext::Shift() +{ + av_pos++; + Reset(); + return av_pos; +} + +void AVContext::GoPosition(uint64_t pos) +{ + av_pos = pos; + Reset(); +} + +uint64_t AVContext::GetPosition() const +{ + return av_pos; +} + +/* + * Process TS packet + * + * returns: + * + * AVCONTEXT_CONTINUE + * Parse completed. If has payload, process it else Continue to next packet. + * + * AVCONTEXT_STREAM_PID_DATA + * Parse completed. A new PES unit starts and data of elementary stream for + * the PID must be picked before processing this payload. + * + * AVCONTEXT_DISCONTINUITY + * Discontinuity. PID will wait until next unit start. So continue to next + * packet. + * + * AVCONTEXT_TS_NOSYNC + * Bad sync byte. Should run TSResync(). + * + * AVCONTEXT_TS_ERROR + * Parsing error ! + */ +int AVContext::ProcessTSPacket() +{ + CLockObject lock(mutex); + + int ret = AVCONTEXT_CONTINUE; + std::map::iterator it; + + if (av_rb8(this->av_buf) != 0x47) // ts sync byte + return AVCONTEXT_TS_NOSYNC; + + uint16_t header = av_rb16(this->av_buf + 1); + this->pid = header & 0x1fff; + this->transport_error = (header & 0x8000) != 0; + this->payload_unit_start = (header & 0x4000) != 0; + // Cleaning context + this->discontinuity = false; + this->has_payload = false; + this->payload = NULL; + this->payload_len = 0; + + if (this->transport_error) + return AVCONTEXT_CONTINUE; + // Null packet + if (this->pid == 0x1fff) + return AVCONTEXT_CONTINUE; + + uint8_t flags = av_rb8(this->av_buf + 3); + bool has_payload = (flags & 0x10) != 0; + bool is_discontinuity = false; + uint8_t continuity_counter = flags & 0x0f; + bool has_adaptation = (flags & 0x20) != 0; + size_t n = 0; + if (has_adaptation) + { + size_t len = (size_t)av_rb8(this->av_buf + 4); + if (len > (this->av_data_len - 5)) + return AVCONTEXT_TS_ERROR; + n = len + 1; + if (len > 0) + { + is_discontinuity = (av_rb8(this->av_buf + 5) & 0x80) != 0; + } + } + if (has_payload) + { + // Payload start after adaptation fields + this->payload = this->av_buf + n + 4; + this->payload_len = this->av_data_len - n - 4; + } + + it = this->packets.find(this->pid); + if (it == this->packets.end()) + { + // Not registred PID + // We are waiting for unit start of PID 0 else next packet is required + if (this->pid == 0 && this->payload_unit_start) + { + // Registering PID 0 + Packet pid0; + pid0.pid = this->pid; + pid0.packet_type = PACKET_TYPE_PSI; + pid0.continuity = continuity_counter; + it = this->packets.insert(it, std::make_pair(this->pid, pid0)); + } + else + return AVCONTEXT_CONTINUE; + } + else + { + // PID is registred + // Checking unit start is required + if (it->second.wait_unit_start && !this->payload_unit_start) + { + // Not unit start. Save packet flow continuity... + it->second.continuity = continuity_counter; + this->discontinuity = true; + return AVCONTEXT_DISCONTINUITY; + } + // Checking continuity where possible + if (it->second.continuity != 0xff) + { + uint8_t expected_cc = has_payload ? (it->second.continuity + 1) & 0x0f : it->second.continuity; + if (!is_discontinuity && expected_cc != continuity_counter) + { + this->discontinuity = true; + // If unit is not start then reset PID and wait the next unit start + if (!this->payload_unit_start) + { + it->second.Reset(); + demux_dbg(DEMUX_DBG_WARN, "PID %.4x discontinuity detected: found %u, expected %u\n", this->pid, continuity_counter, expected_cc); + return AVCONTEXT_DISCONTINUITY; + } + } + } + it->second.continuity = continuity_counter; + } + + this->discontinuity |= is_discontinuity; + this->has_payload = has_payload; + this->packet = &(it->second); + + // It is time to stream data for PES + if (this->payload_unit_start && + this->packet->streaming && + this->packet->packet_type == PACKET_TYPE_PES && + !this->packet->wait_unit_start) + { + this->packet->has_stream_data = true; + ret = AVCONTEXT_STREAM_PID_DATA; + } + return ret; +} + +/* + * Process payload of packet depending of its type + * + * PACKET_TYPE_PSI -> parse_ts_psi() + * PACKET_TYPE_PES -> parse_ts_pes() + */ +int AVContext::ProcessTSPayload() +{ + CLockObject lock(mutex); + + if (!this->packet) + return AVCONTEXT_CONTINUE; + + int ret = 0; + switch (this->packet->packet_type) + { + case PACKET_TYPE_PSI: + ret = parse_ts_psi(); + break; + case PACKET_TYPE_PES: + ret = parse_ts_pes(); + break; + case PACKET_TYPE_UNKNOWN: + break; + } + + return ret; +} + +void AVContext::clear_pmt() +{ + demux_dbg(DEMUX_DBG_DEBUG, "%s\n", __FUNCTION__); + std::vector pid_list; + for (std::map::iterator it = this->packets.begin(); it != this->packets.end(); it++) + { + if (it->second.packet_type == PACKET_TYPE_PSI && it->second.packet_table.table_id == 0x02) + { + pid_list.push_back(it->first); + clear_pes(it->second.channel); + } + } + for (std::vector::iterator it = pid_list.begin(); it != pid_list.end(); it ++) + this->packets.erase(*it); +} + +void AVContext::clear_pes(uint16_t channel) +{ + demux_dbg(DEMUX_DBG_DEBUG, "%s(%u)\n", __FUNCTION__, channel); + std::vector pid_list; + for (std::map::iterator it = this->packets.begin(); it != this->packets.end(); it++) + { + if (it->second.packet_type == PACKET_TYPE_PES && it->second.channel == channel) + pid_list.push_back(it->first); + } + for (std::vector::iterator it = pid_list.begin(); it != pid_list.end(); it ++) + this->packets.erase(*it); +} + +/* + * Parse PSI payload + * + * returns: + * + * AVCONTEXT_CONTINUE + * Parse completed. Continue to next packet + * + * AVCONTEXT_PROGRAM_CHANGE + * Parse completed. The program has changed. All streams are resetted and + * streaming flag is set to false. Client must inspect streams MAP and enable + * streaming for those recognized. + * + * AVCONTEXT_TS_ERROR + * Parsing error ! + */ +int AVContext::parse_ts_psi() +{ + size_t len; + + if (!this->has_payload || !this->payload || !this->payload_len || !this->packet) + return AVCONTEXT_CONTINUE; + + if (this->payload_unit_start) + { + // Reset wait for unit start + this->packet->wait_unit_start = false; + // pointer field present + len = (size_t)av_rb8(this->payload); + if (len > this->payload_len) + return AVCONTEXT_TS_ERROR; + + // table ID + uint8_t table_id = av_rb8(this->payload + 1); + + // table length + len = (size_t)av_rb16(this->payload + 2); + if ((len & 0x3000) != 0x3000) + return AVCONTEXT_TS_ERROR; + + len &= 0x0fff; + if (len > TABLE_BUFFER_SIZE) + return AVCONTEXT_TS_ERROR; + + this->packet->packet_table.Reset(); + + size_t n = this->payload_len - 4; + memcpy(this->packet->packet_table.buf, this->payload + 4, n); + this->packet->packet_table.table_id = table_id; + this->packet->packet_table.offset = n; + this->packet->packet_table.len = len; + // check for incomplete section + if (this->packet->packet_table.offset < this->packet->packet_table.len) + return AVCONTEXT_CONTINUE; + } + else + { + // next part of PSI + if (this->packet->packet_table.offset == 0) + return AVCONTEXT_TS_ERROR; + + if ((this->payload_len + this->packet->packet_table.offset) > TABLE_BUFFER_SIZE) + return AVCONTEXT_TS_ERROR; + + memcpy(this->packet->packet_table.buf + this->packet->packet_table.offset, this->payload, this->payload_len); + this->packet->packet_table.offset += this->payload_len; + // check for incomplete section + if (this->packet->packet_table.offset < this->packet->packet_table.len) + return AVCONTEXT_CONTINUE; + } + + // now entire table is filled + const unsigned char* psi = this->packet->packet_table.buf; + const unsigned char* end_psi = psi + this->packet->packet_table.len; + + switch (this->packet->packet_table.table_id) + { + case 0x00: // parse PAT table + { + // check if version number changed + uint16_t id = av_rb16(psi); + // check if applicable + if ((av_rb8(psi + 2) & 0x01) == 0) + return AVCONTEXT_CONTINUE; + // check if version number changed + uint8_t version = (av_rb8(psi + 2) & 0x3e) >> 1; + if (id == this->packet->packet_table.id && version == this->packet->packet_table.version) + return AVCONTEXT_CONTINUE; + demux_dbg(DEMUX_DBG_DEBUG, "%s: new PAT version %u\n", __FUNCTION__, version); + + // clear old associated pmt + clear_pmt(); + + // parse new version of PAT + psi += 5; + + end_psi -= 4; // CRC32 + + if (psi >= end_psi) + return AVCONTEXT_TS_ERROR; + + len = end_psi - psi; + + if (len % 4) + return AVCONTEXT_TS_ERROR; + + size_t n = len / 4; + + for (size_t i = 0; i < n; i++, psi += 4) + { + uint16_t channel = av_rb16(psi); + uint16_t pmt_pid = av_rb16(psi + 2); + + if ((pmt_pid & 0xe000) != 0xe000) + return AVCONTEXT_TS_ERROR; + + pmt_pid &= 0x1fff; + + demux_dbg(DEMUX_DBG_DEBUG, "%s: PAT version %u: new PMT %.4x channel %u\n", __FUNCTION__, version, pmt_pid, channel); + if (this->channel == 0 || this->channel == channel) + { + Packet& pmt = this->packets[pmt_pid]; + pmt.pid = pmt_pid; + pmt.packet_type = PACKET_TYPE_PSI; + pmt.channel = channel; + demux_dbg(DEMUX_DBG_DEBUG, "%s: PAT version %u: register PMT %.4x channel %u\n", __FUNCTION__, version, pmt_pid, channel); + } + } + // PAT is processed. New version is available + this->packet->packet_table.id = id; + this->packet->packet_table.version = version; + break; + } + case 0x02: // parse PMT table + { + uint16_t id = av_rb16(psi); + // check if applicable + if ((av_rb8(psi + 2) & 0x01) == 0) + return AVCONTEXT_CONTINUE; + // check if version number changed + uint8_t version = (av_rb8(psi + 2) & 0x3e) >> 1; + if (id == this->packet->packet_table.id && version == this->packet->packet_table.version) + return AVCONTEXT_CONTINUE; + demux_dbg(DEMUX_DBG_DEBUG, "%s: PMT(%.4x) version %u\n", __FUNCTION__, this->packet->pid, version); + + // clear old pes + clear_pes(this->packet->channel); + + // parse new version of PMT + psi += 7; + + end_psi -= 4; // CRC32 + + if (psi >= end_psi) + return AVCONTEXT_TS_ERROR; + + len = (size_t)(av_rb16(psi) & 0x0fff); + psi += 2 + len; + + while (psi < end_psi) + { + if (end_psi - psi < 5) + return AVCONTEXT_TS_ERROR; + + uint8_t pes_type = av_rb8(psi); + uint16_t pes_pid = av_rb16(psi + 1); + + if ((pes_pid & 0xe000) != 0xe000) + return AVCONTEXT_TS_ERROR; + + pes_pid &= 0x1fff; + + // len of descriptor section + len = (size_t)(av_rb16(psi + 3) & 0x0fff); + psi += 5; + + // ignore unknown streams + STREAM_TYPE stream_type = get_stream_type(pes_type); + demux_dbg(DEMUX_DBG_DEBUG, "%s: PMT(%.4x) version %u: new PES %.4x %s\n", __FUNCTION__, + this->packet->pid, version, pes_pid, ElementaryStream::GetStreamCodecName(stream_type)); + if (stream_type != STREAM_TYPE_UNKNOWN) + { + Packet& pes = this->packets[pes_pid]; + pes.pid = pes_pid; + pes.packet_type = PACKET_TYPE_PES; + pes.channel = this->packet->channel; + // Disable streaming by default + pes.streaming = false; + // Get basic stream infos from PMT table + ElementaryStream::STREAM_INFO stream_info; + stream_info = parse_pes_descriptor(psi, len, &stream_type); + + ElementaryStream* es; + if (stream_type == STREAM_TYPE_VIDEO_MPEG1) + es = new ES_MPEG2Video(pes_pid); + else if (stream_type == STREAM_TYPE_VIDEO_MPEG2) + es = new ES_MPEG2Video(pes_pid); + else if (stream_type == STREAM_TYPE_AUDIO_MPEG1) + es = new ES_MPEG2Audio(pes_pid); + else if (stream_type == STREAM_TYPE_AUDIO_MPEG2) + es = new ES_MPEG2Audio(pes_pid); + else if (stream_type == STREAM_TYPE_VIDEO_H264) + es = new ES_h264(pes_pid); + else if (stream_type == STREAM_TYPE_AUDIO_AAC) + es = new ES_AAC(pes_pid); + else if (stream_type == STREAM_TYPE_AUDIO_AC3) + es = new ES_AC3(pes_pid); + else if (stream_type == STREAM_TYPE_AUDIO_EAC3) + es = new ES_AC3(pes_pid); + else if (stream_type == STREAM_TYPE_DVB_SUBTITLE) + es = new ES_Subtitle(pes_pid); + else if (stream_type == STREAM_TYPE_DVB_TELETEXT) + es = new ES_Teletext(pes_pid); + else + { + // No parser: pass-through + es = new ElementaryStream(pes_pid); + es->has_stream_info = true; + } + + es->stream_type = stream_type; + es->stream_info = stream_info; + pes.stream = es; + demux_dbg(DEMUX_DBG_DEBUG, "%s: PMT(%.4x) version %u: register PES %.4x %s\n", __FUNCTION__, + this->packet->pid, version, pes_pid, es->GetStreamCodecName()); + } + psi += len; + } + + if (psi != end_psi) + return AVCONTEXT_TS_ERROR; + + // PMT is processed. New version is available + this->packet->packet_table.id = id; + this->packet->packet_table.version = version; + return AVCONTEXT_PROGRAM_CHANGE; + } + default: + // CAT, NIT table + break; + } + + return AVCONTEXT_CONTINUE; +} + +ElementaryStream::STREAM_INFO AVContext::parse_pes_descriptor(const unsigned char* p, size_t len, STREAM_TYPE* st) +{ + const unsigned char* desc_end = p + len; + ElementaryStream::STREAM_INFO si; + memset(&si, 0, sizeof(ElementaryStream::STREAM_INFO)); + + while (p < desc_end) + { + uint8_t desc_tag = av_rb8(p); + uint8_t desc_len = av_rb8(p + 1); + p += 2; + demux_dbg(DEMUX_DBG_DEBUG, "%s: tag %.2x len %d\n", __FUNCTION__, desc_tag, desc_len); + switch (desc_tag) + { + case 0x02: + case 0x03: + break; + case 0x0a: /* ISO 639 language descriptor */ + if (desc_len >= 4) + { + si.language[0] = av_rb8(p); + si.language[1] = av_rb8(p + 1); + si.language[2] = av_rb8(p + 2); + si.language[3] = 0; + } + break; + case 0x56: /* DVB teletext descriptor */ + *st = STREAM_TYPE_DVB_TELETEXT; + break; + case 0x6a: /* DVB AC3 */ + case 0x81: /* AC3 audio stream */ + *st = STREAM_TYPE_AUDIO_AC3; + break; + case 0x7a: /* DVB enhanced AC3 */ + *st = STREAM_TYPE_AUDIO_EAC3; + break; + case 0x7b: /* DVB DTS */ + *st = STREAM_TYPE_AUDIO_DTS; + break; + case 0x7c: /* DVB AAC */ + *st = STREAM_TYPE_AUDIO_AAC; + break; + case 0x59: /* subtitling descriptor */ + if (desc_len >= 8) + { + /* + * Byte 4 is the subtitling_type field + * av_rb8(p + 3) & 0x10 : normal + * av_rb8(p + 3) & 0x20 : for the hard of hearing + */ + *st = STREAM_TYPE_DVB_SUBTITLE; + si.language[0] = av_rb8(p); + si.language[1] = av_rb8(p + 1); + si.language[2] = av_rb8(p + 2); + si.language[3] = 0; + si.composition_id = (int)av_rb16(p + 4); + si.ancillary_id = (int)av_rb16(p + 6); + } + break; + case 0x05: /* registration descriptor */ + case 0x1E: /* SL descriptor */ + case 0x1F: /* FMC descriptor */ + case 0x52: /* stream identifier descriptor */ + default: + break; + } + p += desc_len; + } + + return si; +} + +/* + * Parse PES payload + * + * returns: + * + * AVCONTEXT_CONTINUE + * Parse completed. When streaming is enabled for PID, data is appended to + * the frame buffer of corresponding elementary stream. + * + * AVCONTEXT_TS_ERROR + * Parsing error ! + */ +int AVContext::parse_ts_pes() +{ + if (!this->has_payload || !this->payload || !this->payload_len || !this->packet) + return AVCONTEXT_CONTINUE; + + if (!this->packet->stream) + return AVCONTEXT_CONTINUE; + + if (this->payload_unit_start) + { + // Wait for unit start: Reset frame buffer to clear old data + if (this->packet->wait_unit_start) + { + packet->stream->Reset(); + packet->stream->p_dts = PTS_UNSET; + packet->stream->p_pts = PTS_UNSET; + } + this->packet->wait_unit_start = false; + this->packet->has_stream_data = false; + // Reset header table + this->packet->packet_table.Reset(); + // Header len is at least 6 bytes. So getting 6 bytes first + this->packet->packet_table.len = 6; + } + + // Position in the payload buffer. Start at 0 + size_t pos = 0; + + while (this->packet->packet_table.offset < this->packet->packet_table.len) + { + if (pos >= this->payload_len) + return AVCONTEXT_CONTINUE; + + size_t n = this->packet->packet_table.len - this->packet->packet_table.offset; + + if (n > (this->payload_len - pos)) + n = this->payload_len - pos; + + memcpy(this->packet->packet_table.buf + this->packet->packet_table.offset, this->payload + pos, n); + this->packet->packet_table.offset += n; + pos += n; + + if (this->packet->packet_table.offset == 6) + { + if (memcmp(this->packet->packet_table.buf, "\x00\x00\x01", 3)) + { + this->packet->packet_table.Reset(); + return AVCONTEXT_TS_ERROR; + } + uint8_t stream_id = av_rb8(this->packet->packet_table.buf + 3); + if (stream_id == 0xbd || (stream_id >= 0xc0 && stream_id <= 0xef)) + this->packet->packet_table.len = 9; + } + else if (this->packet->packet_table.offset == 9) + { + this->packet->packet_table.len += av_rb8(this->packet->packet_table.buf + 8); + } + } + + // parse header table + bool has_pts = false; + + if (this->packet->packet_table.len >= 9) + { + uint8_t flags = av_rb8(this->packet->packet_table.buf + 7); + + //this->packet->stream->frame_num++; + + switch (flags & 0xc0) + { + case 0x80: // PTS only + { + has_pts = true; + if (this->packet->packet_table.len >= 14) + { + uint64_t pts = decode_pts(this->packet->packet_table.buf + 9); + this->packet->stream->p_dts = this->packet->stream->c_dts; + this->packet->stream->p_pts = this->packet->stream->c_pts; + this->packet->stream->c_dts = this->packet->stream->c_pts = pts; + } + else + { + this->packet->stream->c_dts = this->packet->stream->c_pts = PTS_UNSET; + } + } + break; + case 0xc0: // PTS,DTS + { + has_pts = true; + if (this->packet->packet_table.len >= 19 ) + { + uint64_t pts = decode_pts(this->packet->packet_table.buf + 9); + uint64_t dts = decode_pts(this->packet->packet_table.buf + 14); + int64_t d = (pts - dts) & PTS_MASK; + // more than two seconds of PTS/DTS delta, probably corrupt + if(d > 180000) + { + this->packet->stream->c_dts = this->packet->stream->c_pts = PTS_UNSET; + } + else + { + this->packet->stream->p_dts = this->packet->stream->c_dts; + this->packet->stream->p_pts = this->packet->stream->c_pts; + this->packet->stream->c_dts = dts; + this->packet->stream->c_pts = pts; + } + } + else + { + this->packet->stream->c_dts = this->packet->stream->c_pts = PTS_UNSET; + } + } + break; + } + this->packet->packet_table.Reset(); + } + + if (this->packet->streaming) + { + const unsigned char* data = this->payload + pos; + size_t len = this->payload_len - pos; + this->packet->stream->Append(data, len, has_pts); + } + + return AVCONTEXT_CONTINUE; +} diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/tsDemuxer.h b/addons/pvr.mythtv.cmyth/src/demuxer/tsDemuxer.h new file mode 100644 index 000000000..3878b3256 --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/tsDemuxer.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2013 Jean-Luc Barriere + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef TSDEMUXER_H +#define TSDEMUXER_H + +#include "common.h" +#include "tsPacket.h" +#include "elementaryStream.h" +#include "platform/threads/mutex.h" + +#include +#include + +#define FLUTS_NORMAL_TS_PACKETSIZE 188 +#define FLUTS_M2TS_TS_PACKETSIZE 192 +#define FLUTS_DVB_ASI_TS_PACKETSIZE 204 +#define FLUTS_ATSC_TS_PACKETSIZE 208 + +#define AV_CONTEXT_PACKETSIZE 208 +#define TS_CHECK_MIN_SCORE 2 +#define TS_CHECK_MAX_SCORE 10 + +class TSDemuxer +{ +public: + virtual const unsigned char* ReadAV(uint64_t pos, size_t len) = 0; +}; + +enum { + AVCONTEXT_TS_ERROR = -3, + AVCONTEXT_IO_ERROR = -2, + AVCONTEXT_TS_NOSYNC = -1, + AVCONTEXT_CONTINUE = 0, + AVCONTEXT_PROGRAM_CHANGE = 1, + AVCONTEXT_STREAM_PID_DATA = 2, + AVCONTEXT_DISCONTINUITY = 3 +}; + +class AVContext +{ +public: + AVContext(TSDemuxer* const demux, uint64_t pos, uint16_t channel); + void Reset(void); + + uint16_t GetPID() const; + PACKET_TYPE GetPIDType() const; + uint16_t GetPIDChannel() const; + bool HasPIDStreamData() const; + bool HasPIDPayload() const; + ElementaryStream* GetPIDStream(); + std::vector GetStreams(); + void StartStreaming(uint16_t pid); + void StopStreaming(uint16_t pid); + + ElementaryStream* GetStream(uint16_t pid) const; + uint16_t GetChannel(uint16_t pid) const; + void ResetPackets(); + + // TS parser + int TSResync(); + uint64_t GoNext(); + uint64_t Shift(); + void GoPosition(uint64_t pos); + uint64_t GetPosition() const; + int ProcessTSPacket(); + int ProcessTSPayload(); + +private: + AVContext(const AVContext&); + AVContext& operator=(const AVContext&); + + int configure_ts(); + static STREAM_TYPE get_stream_type(uint8_t pes_type); + static uint8_t av_rb8(const unsigned char* p); + static uint16_t av_rb16(const unsigned char* p); + static uint32_t av_rb32(const unsigned char* p); + static uint64_t decode_pts(const unsigned char* p); + void clear_pmt(); + void clear_pes(uint16_t channel); + int parse_ts_psi(); + static ElementaryStream::STREAM_INFO parse_pes_descriptor(const unsigned char* p, size_t len, STREAM_TYPE* st); + int parse_ts_pes(); + + // Critical section + mutable PLATFORM::CMutex mutex; + + // AV stream owner + TSDemuxer* m_demux; + + // Raw packet buffer + uint64_t av_pos; + size_t av_data_len; + size_t av_pkt_size; + unsigned char av_buf[AV_CONTEXT_PACKETSIZE]; + + // TS Streams context + bool is_configured; + uint16_t channel; + std::map packets; + + // Packet context + uint16_t pid; + bool transport_error; + bool has_payload; + bool payload_unit_start; + bool discontinuity; + const unsigned char* payload; + size_t payload_len; + Packet* packet; +}; + +#endif /* TSDEMUXER_H */ diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/tsPacket.h b/addons/pvr.mythtv.cmyth/src/demuxer/tsPacket.h new file mode 100644 index 000000000..9ecf27d32 --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/tsPacket.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2013 Jean-Luc Barriere + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef TSPACKET_H +#define TSPACKET_H + +#include "common.h" +#include "tsTable.h" +#include "elementaryStream.h" + +enum PACKET_TYPE +{ + PACKET_TYPE_UNKNOWN = 0, + PACKET_TYPE_PSI, + PACKET_TYPE_PES +}; + +class Packet +{ +public: + Packet(void) + : pid(0xffff) + , continuity(0xff) + , packet_type(PACKET_TYPE_UNKNOWN) + , packet_table() + , channel(0) + , wait_unit_start(true) + , has_stream_data(false) + , streaming(false) + , stream(NULL) + { + } + + ~Packet(void) + { + if (stream) + delete stream; + } + + void Reset(void) + { + continuity = 0xff; + wait_unit_start = true; + packet_table.Reset(); + if (stream) + stream->Reset(); + } + + uint16_t pid; + uint8_t continuity; + PACKET_TYPE packet_type; + TSTable packet_table; + uint16_t channel; + bool wait_unit_start; + bool has_stream_data; + bool streaming; + ElementaryStream* stream; +}; + +#endif /* TSPACKET_H */ diff --git a/addons/pvr.mythtv.cmyth/src/demuxer/tsTable.h b/addons/pvr.mythtv.cmyth/src/demuxer/tsTable.h new file mode 100644 index 000000000..988951324 --- /dev/null +++ b/addons/pvr.mythtv.cmyth/src/demuxer/tsTable.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2013 Jean-Luc Barriere + * + * This Program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This Program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef TSTABLE_H +#define TSTABLE_H + +#include "common.h" + +#define TABLE_BUFFER_SIZE 1024 + +class TSTable +{ +public: + unsigned char buf[TABLE_BUFFER_SIZE]; + uint8_t table_id; + uint8_t version; + uint16_t id; + uint16_t len; + uint16_t offset; + + TSTable(void) + : table_id(0xff) + , version(0xff) + , id(0xffff) + , len(0) + , offset(0) + { + memset(buf, 0, sizeof(buf)); + } + + void Reset(void) + { + len = 0; + offset = 0; + } +}; + +#endif /* TSTABLE_H */ diff --git a/addons/pvr.mythtv.cmyth/src/fileOps.cpp b/addons/pvr.mythtv.cmyth/src/fileOps.cpp index 835cce0e6..8f9fb77b2 100644 --- a/addons/pvr.mythtv.cmyth/src/fileOps.cpp +++ b/addons/pvr.mythtv.cmyth/src/fileOps.cpp @@ -81,7 +81,7 @@ CStdString FileOps::GetChannelIconPath(const CStdString &remoteFilename) if (!XBMC->FileExists(localFilename, true)) { CLockObject lock(m_lock); - FileOps::JobItem job(localFilename, remoteFilename, ""); + FileOps::JobItem job(localFilename, remoteFilename, GetFolderNameByFileType(FileTypeChannelIcon)); m_jobQueue.push_back(job); m_queueContent.Signal(); } diff --git a/addons/pvr.mythtv.cmyth/src/pvrclient-mythtv.cpp b/addons/pvr.mythtv.cmyth/src/pvrclient-mythtv.cpp index 97473879a..bbe55aaf6 100644 --- a/addons/pvr.mythtv.cmyth/src/pvrclient-mythtv.cpp +++ b/addons/pvr.mythtv.cmyth/src/pvrclient-mythtv.cpp @@ -40,6 +40,7 @@ PVRClientMythTV::PVRClientMythTV() , m_connectionString("") , m_categories() , m_channelGroups() + , m_demux(NULL) { } @@ -62,6 +63,12 @@ PVRClientMythTV::~PVRClientMythTV() delete m_scheduleManager; m_scheduleManager = NULL; } + + if (m_demux) + { + delete m_demux; + m_demux = NULL; + } } void Log(int level, char *msg) @@ -185,7 +192,7 @@ void PVRClientMythTV::OnSleep() void PVRClientMythTV::OnWake() { if (m_pEventHandler) - m_pEventHandler->Resume(); + m_pEventHandler->Resume(true); if (m_fileOps) m_fileOps->Resume(); } @@ -198,18 +205,13 @@ PVR_ERROR PVRClientMythTV::GetEPGForChannel(ADDON_HANDLE handle, const PVR_CHANN if (!channel.bIsHidden) { EPGInfoMap EPG = m_db.GetGuide(channel.iUniqueId, iStart, iEnd); - EPGInfoMap::reverse_iterator prevIt = EPG.rbegin(); // Transfer EPG for the given channel for (EPGInfoMap::reverse_iterator it = EPG.rbegin(); it != EPG.rend(); ++it) { EPG_TAG tag; memset(&tag, 0, sizeof(EPG_TAG)); - // Fill the gap until previous start time tag.startTime = it->first; - if (it != prevIt) - tag.endTime = prevIt->first; - else - tag.endTime = it->second.EndTime(); + tag.endTime = it->second.EndTime(); // Reject bad entry if (tag.endTime <= tag.startTime) continue; @@ -221,7 +223,7 @@ PVR_ERROR PVRClientMythTV::GetEPGForChannel(ADDON_HANDLE handle, const PVR_CHANN CStdString description; CStdString category; - tag.iUniqueBroadcastId = (((int)(difftime(it->second.StartTime(), 0) / 60) & 0xFFFF) << 16) + (it->second.ChannelNumberInt() & 0xFFFF); + tag.iUniqueBroadcastId = MakeBroadcastID(it->second.ChannelID(), it->second.StartTime()); tag.iChannelNumber = it->second.ChannelNumberInt(); title = this->MakeProgramTitle(it->second.Title(), it->second.Subtitle()); tag.strTitle = title; @@ -247,7 +249,6 @@ PVR_ERROR PVRClientMythTV::GetEPGForChannel(ADDON_HANDLE handle, const PVR_CHANN tag.iStarRating = 0; PVR->TransferEpgEntry(handle, &tag); - prevIt = it; } } @@ -273,9 +274,10 @@ PVR_ERROR PVRClientMythTV::GetChannels(ADDON_HANDLE handle, bool bRadio) XBMC->Log(LOG_DEBUG, "%s - radio: %s", __FUNCTION__, (bRadio ? "true" : "false")); LoadChannelsAndChannelGroups(); + m_PVRChannelUidById.clear(); - // Create a set to merge channels with same channum and callsign - std::set > channelIdentifiers; + // Create a map<(channum, callsign), chanid> to merge channels with same channum and callsign + std::map, unsigned int> channelIdentifiers; // Transfer channels of the requested type (radio / tv) for (ChannelIdMap::iterator it = m_channelsById.begin(); it != m_channelsById.end(); ++it) @@ -283,13 +285,18 @@ PVR_ERROR PVRClientMythTV::GetChannels(ADDON_HANDLE handle, bool bRadio) if (it->second.IsRadio() == bRadio && !it->second.IsNull()) { // Skip channels with same channum and callsign - std::pair channelIdentifier = make_pair(it->second.Number(), it->second.Callsign()); - if (channelIdentifiers.find(channelIdentifier) != channelIdentifiers.end()) + std::pair channelIdentifier = std::make_pair(it->second.Number(), it->second.Callsign()); + std::map, unsigned int>::iterator itm = channelIdentifiers.find(channelIdentifier); + if (itm != channelIdentifiers.end()) { XBMC->Log(LOG_DEBUG, "%s - skipping channel: %d", __FUNCTION__, it->second.ID()); + // Map channel with merged channel + m_PVRChannelUidById.insert(std::make_pair(it->first, itm->second)); continue; } - channelIdentifiers.insert(channelIdentifier); + channelIdentifiers.insert(std::make_pair(channelIdentifier, it->first)); + // Map channel to itself + m_PVRChannelUidById.insert(std::make_pair(it->first, it->first)); PVR_CHANNEL tag; memset(&tag, 0, sizeof(PVR_CHANNEL)); @@ -387,7 +394,7 @@ PVR_ERROR PVRClientMythTV::GetChannelGroupMembers(ADDON_HANDLE handle, const PVR memset(&tag, 0, sizeof(PVR_CHANNEL_GROUP_MEMBER)); tag.iChannelNumber = channelNumber++; - tag.iChannelUniqueId = channelIt->second.ID(); + tag.iChannelUniqueId = FindPVRChannelUid(channelIt->second.ID()); PVR_STRCPY(tag.strGroupName, group.strGroupName); PVR->TransferChannelGroupMember(handle, &tag); } @@ -412,6 +419,14 @@ void PVRClientMythTV::LoadChannelsAndChannelGroups() m_channelGroups = m_db.GetChannelGroups(); } +int PVRClientMythTV::FindPVRChannelUid(int channelId) const +{ + PVRChannelMap::const_iterator it = m_PVRChannelUidById.find(channelId); + if (it != m_PVRChannelUidById.end()) + return it->second; + return -1; // PVR dummy channel UID +} + void PVRClientMythTV::UpdateRecordings() { PVR->TriggerRecordingUpdate(); @@ -1085,7 +1100,7 @@ PVR_ERROR PVRClientMythTV::GetTimers(ADDON_HANDLE handle) CStdString rulemarker = ""; tag.startTime = it->second->StartTime(); tag.endTime = it->second->EndTime(); - tag.iClientChannelUid = it->second->ChannelID(); + tag.iClientChannelUid = FindPVRChannelUid(it->second->ChannelID()); tag.iPriority = it->second->Priority(); int genre = m_categories.Category(it->second->Category()); tag.iGenreSubType = genre & 0x0F; @@ -1096,60 +1111,14 @@ PVR_ERROR PVRClientMythTV::GetTimers(ADDON_HANDLE handle) if (node) { MythRecordingRule rule = node->GetRule(); + RuleMetadata meta = m_scheduleManager->GetMetadata(rule); tag.iMarginEnd = rule.EndOffset(); tag.iMarginStart = rule.StartOffset(); tag.firstDay = it->second->RecordingStartTime(); - - // Then set bIsRepeating and iweekdays for repeating rules - time_t st = rule.StartTime(); - switch (rule.Type()) - { - case MythRecordingRule::RT_DailyRecord: // (0.27) Replaces TimeslotRecord - case MythRecordingRule::RT_FindDailyRecord: // (0.27) Obsolete. Kept for backward compatibility - case MythRecordingRule::RT_ChannelRecord: - case MythRecordingRule::RT_AllRecord: - tag.bIsRepeating = true; - tag.iWeekdays = 0x7F; - break; - case MythRecordingRule::RT_WeeklyRecord: // (0.27) Replaces WeekslotRecord - case MythRecordingRule::RT_FindWeeklyRecord: // (0.27) Obsolete. Kept for backward compatibility - tag.bIsRepeating = true; - tag.iWeekdays = 1 << ((weekday(&st) + 6) % 7); - break; - default: - tag.bIsRepeating = false; - tag.iWeekdays = 0; - break; - } - // Define a marker for this type of rule - switch(rule.Type()) - { - case MythRecordingRule::RT_DontRecord: - rulemarker = "(x)"; - break; - case MythRecordingRule::RT_OverrideRecord: - rulemarker = "(o)"; - break; - case MythRecordingRule::RT_OneRecord: - rulemarker = "(1)"; - break; - case MythRecordingRule::RT_DailyRecord: - case MythRecordingRule::RT_FindDailyRecord: - rulemarker = "(d)"; - break; - case MythRecordingRule::RT_ChannelRecord: - rulemarker = "(C)"; - break; - case MythRecordingRule::RT_AllRecord: - rulemarker = "(A)"; - break; - case MythRecordingRule::RT_WeeklyRecord: - case MythRecordingRule::RT_FindWeeklyRecord: - rulemarker = "(w)"; - break; - default: - break; - } + tag.bIsRepeating = meta.isRepeating; + tag.iWeekdays = meta.weekDays; + if (*(meta.marker)) + rulemarker.append("(").append(meta.marker).append(")"); } else { @@ -1556,6 +1525,8 @@ bool PVRClientMythTV::OpenLiveStream(const PVR_CHANNEL &channel) if (m_rec.SpawnLiveTV((*channelByNumberIt).second)) { + if(g_bDemuxing) + m_demux = new Demux(m_rec); XBMC->Log(LOG_DEBUG, "%s - Done", __FUNCTION__); return true; } @@ -1601,6 +1572,11 @@ void PVRClientMythTV::CloseLiveStream() m_pEventHandler->DisablePlayback(); m_pEventHandler->AllowLiveChainUpdate(); + if (m_demux) + { + delete m_demux; + m_demux = NULL; + } // Resume fileOps m_fileOps->Resume(); @@ -1658,6 +1634,11 @@ bool PVRClientMythTV::SwitchChannel(const PVR_CHANNEL &channelinfo) m_pEventHandler->SetRecordingListener("", m_file); m_pEventHandler->SetRecorder(m_rec); m_pEventHandler->AllowLiveChainUpdate(); + if (m_demux) + { + delete m_demux; + m_demux = NULL; + } // Try to reopen live stream if (retval) retval = OpenLiveStream(channelinfo); @@ -1740,6 +1721,67 @@ PVR_ERROR PVRClientMythTV::SignalStatus(PVR_SIGNAL_STATUS &signalStatus) return PVR_ERROR_NO_ERROR; } +PVR_ERROR PVRClientMythTV::GetStreamProperties(PVR_STREAM_PROPERTIES* pProperties) +{ + return m_demux && m_demux->GetStreamProperties(pProperties) ? PVR_ERROR_NO_ERROR : PVR_ERROR_SERVER_ERROR; +} + +void PVRClientMythTV::DemuxAbort(void) +{ + if (m_demux) + m_demux->Abort(); +} + +void PVRClientMythTV::DemuxFlush(void) +{ + if (m_demux) + m_demux->Flush(); +} + +DemuxPacket* PVRClientMythTV::DemuxRead(void) +{ + return m_demux ? m_demux->Read() : NULL; +} + +bool PVRClientMythTV::SeekTime(int time, bool backwards, double* startpts) +{ + return m_demux ? m_demux->SeekTime(time, backwards, startpts) : false; +} + +time_t PVRClientMythTV::GetPlayingTime() +{ + CLockObject lock(m_lock); + if (m_rec.IsNull() || !m_demux) + return 0; + int sec = m_demux->GetPlayingTime() / 1000; + time_t st = GetBufferTimeStart(); + struct tm playtm; + localtime_r(&st, &playtm); + playtm.tm_sec += sec; + time_t pt = mktime(&playtm); + return pt; +} + +time_t PVRClientMythTV::GetBufferTimeStart() +{ + CLockObject lock(m_lock); + if (m_rec.IsNull() || m_rec.GetLiveTVChainLast() < 0) + return 0; + MythProgramInfo prog = m_rec.GetLiveTVChainProgram(0); + return prog.RecordingStartTime(); +} + +time_t PVRClientMythTV::GetBufferTimeEnd() +{ + CLockObject lock(m_lock); + int last; + if (m_rec.IsNull() || (last = m_rec.GetLiveTVChainLast()) < 0) + return 0; + time_t now = time(NULL); + MythProgramInfo prog = m_rec.GetLiveTVChainProgram(last); + return (now > prog.RecordingEndTime() ? prog.RecordingEndTime() : now); +} + bool PVRClientMythTV::OpenRecordedStream(const PVR_RECORDING &recording) { if (g_bExtraDebug) @@ -1892,6 +1934,64 @@ PVR_ERROR PVRClientMythTV::CallMenuHook(const PVR_MENUHOOK &menuhook, const PVR_ return PVR_ERROR_FAILED; } + if (menuhook.category == PVR_MENUHOOK_SETTING) + { + if (menuhook.iHookId == MENUHOOK_SHOW_HIDE_NOT_RECORDING && m_scheduleManager) + { + bool flag = m_scheduleManager->ToggleShowNotRecording(); + UpdateSchedules(); + CStdString info = (flag ? XBMC->GetLocalizedString(30310) : XBMC->GetLocalizedString(30311)); + XBMC->QueueNotification(QUEUE_INFO, info.c_str()); + return PVR_ERROR_NO_ERROR; + } + } + + if (menuhook.category == PVR_MENUHOOK_EPG && item.cat == PVR_MENUHOOK_EPG) + { + time_t attime; + unsigned int chanid; + BreakBroadcastID(item.data.iEpgUid, &chanid, &attime); + MythEPGInfo epgInfo; + if (m_db.FindCurrentProgram(attime, chanid, epgInfo)) + { + // Scheduling actions + if (m_scheduleManager) + { + MythRecordingRule rule; + switch(menuhook.iHookId) + { + case MENUHOOK_EPG_REC_CHAN_ALL_SHOWINGS: + rule = m_scheduleManager->NewChannelRecord(epgInfo); + break; + case MENUHOOK_EPG_REC_CHAN_WEEKLY: + rule = m_scheduleManager->NewWeeklyRecord(epgInfo); + break; + case MENUHOOK_EPG_REC_CHAN_DAILY: + rule = m_scheduleManager->NewDailyRecord(epgInfo); + break; + case MENUHOOK_EPG_REC_ONE_SHOWING: + rule = m_scheduleManager->NewOneRecord(epgInfo); + break; + case MENUHOOK_EPG_REC_NEW_EPISODES: + rule = m_scheduleManager->NewChannelRecord(epgInfo); + rule.SetFilter(rule.Filter() | MythRecordingRule::FM_FirstShowing); + break; + default: + return PVR_ERROR_NOT_IMPLEMENTED; + } + if (m_scheduleManager->ScheduleRecording(rule) == MythScheduleManager::MSM_ERROR_SUCCESS) + return PVR_ERROR_NO_ERROR; + } + } + else + { + XBMC->QueueNotification(QUEUE_WARNING, XBMC->GetLocalizedString(30312)); + XBMC->Log(LOG_DEBUG, "%s - broadcast: %d chanid: %u attime: %lu", __FUNCTION__, item.data.iEpgUid, chanid, attime); + return PVR_ERROR_INVALID_PARAMETERS; + } + return PVR_ERROR_FAILED; + } + return PVR_ERROR_NOT_IMPLEMENTED; } @@ -1926,3 +2026,39 @@ CStdString PVRClientMythTV::MakeProgramTitle(const CStdString &title, const CStd epgtitle = title + SUBTITLE_SEPARATOR + subtitle; return epgtitle; } + +// Broacast ID is 32 bits integer and allows to identify a EPG item. +// MythTV backend doesn't provide one. So we make it encoding time and channel +// as below: +// 31. . . . . . . . . . . . . . . 15. . . . . . . . . . . . . . 0 +// [ timecode (self-relative) ][ channel Id ] +// Timecode is the count of minutes since epoch modulo 0xFFFF. Now therefore it +// is usable for a period of +/- 32767 minutes (+/-22 days) around itself. + +int PVRClientMythTV::MakeBroadcastID(unsigned int chanid, time_t starttime) const +{ + int timecode = (int)(difftime(starttime, 0) / 60) & 0xFFFF; + return (int)((timecode << 16) | (chanid & 0xFFFF)); +} + +void PVRClientMythTV::BreakBroadcastID(int broadcastid, unsigned int* chanid, time_t* attime) const +{ + time_t now; + int ntc, ptc, distance; + struct tm epgtm; + + now = time(NULL); + ntc = (int)(difftime(now, 0) / 60) & 0xFFFF; + ptc = (broadcastid >> 16) & 0xFFFF; // removes arithmetic bits + if (ptc > ntc) + distance = (ptc - ntc) < 0x8000 ? ptc - ntc : ptc - ntc - 0xFFFF; + else + distance = (ntc - ptc) < 0x8000 ? ptc - ntc : ptc - ntc + 0xFFFF; + localtime_r(&now, &epgtm); + epgtm.tm_min += distance; + // Time precision is minute, so we are looking for program started before next minute. + epgtm.tm_sec = 59; + + *attime = mktime(&epgtm); + *chanid = (unsigned int)broadcastid & 0xFFFF; +} diff --git a/addons/pvr.mythtv.cmyth/src/pvrclient-mythtv.h b/addons/pvr.mythtv.cmyth/src/pvrclient-mythtv.h index 1e0e05aef..517b3a8e8 100644 --- a/addons/pvr.mythtv.cmyth/src/pvrclient-mythtv.h +++ b/addons/pvr.mythtv.cmyth/src/pvrclient-mythtv.h @@ -21,6 +21,7 @@ #include "cppmyth.h" #include "fileOps.h" #include "categories.h" +#include "demux.h" #include #include @@ -81,6 +82,16 @@ class PVRClientMythTV : public MythEventObserver long long LengthLiveStream(); PVR_ERROR SignalStatus(PVR_SIGNAL_STATUS &signalStatus); + PVR_ERROR GetStreamProperties(PVR_STREAM_PROPERTIES* pProperties); + void DemuxAbort(void); + void DemuxFlush(void); + DemuxPacket* DemuxRead(void); + bool SeekTime(int time, bool backwards, double *startpts); + + time_t GetPlayingTime(); + time_t GetBufferTimeStart(); + time_t GetBufferTimeEnd(); + // Recording playback bool OpenRecordedStream(const PVR_RECORDING &recinfo); void CloseRecordedStream(); @@ -117,7 +128,13 @@ class PVRClientMythTV : public MythEventObserver ChannelIdMap m_channelsById; ChannelNumberMap m_channelsByNumber; ChannelGroupMap m_channelGroups; + typedef std::map PVRChannelMap; + PVRChannelMap m_PVRChannelUidById; void LoadChannelsAndChannelGroups(); + int FindPVRChannelUid(int channelId) const; + + // Demuxer TS + Demux *m_demux; // Recordings ProgramInfoMap m_recordings; @@ -141,4 +158,11 @@ class PVRClientMythTV : public MythEventObserver * \see class MythProgramInfo , class MythEPGInfo */ CStdString MakeProgramTitle(const CStdString &title, const CStdString &subtitle) const; + + /** + * + * \brief Handle broadcast UID for MythTV program + */ + int MakeBroadcastID(unsigned int chanid, time_t starttime) const; + void BreakBroadcastID(int broadcastid, unsigned int *chanid, time_t *starttime) const; }; diff --git a/lib/cmyth/include/cmyth/cmyth.h b/lib/cmyth/include/cmyth/cmyth.h index 3d3dc896f..f2a383e4c 100644 --- a/lib/cmyth/include/cmyth/cmyth.h +++ b/lib/cmyth/include/cmyth/cmyth.h @@ -799,6 +799,10 @@ extern cmyth_keyframe_t cmyth_keyframe_create(void); extern char *cmyth_keyframe_string(cmyth_keyframe_t kf); +extern uint32_t cmyth_keyframe_number(cmyth_keyframe_t kf); + +extern int64_t cmyth_keyframe_pos(cmyth_keyframe_t kf); + /* * ----------------------------------------------------------------- * Position Map Operations @@ -806,6 +810,10 @@ extern char *cmyth_keyframe_string(cmyth_keyframe_t kf); */ extern cmyth_posmap_t cmyth_posmap_create(void); +extern int cmyth_posmap_count(cmyth_posmap_t pm); + +extern cmyth_keyframe_t cmyth_posmap_keyframe(cmyth_posmap_t pm, int index); + /* * ----------------------------------------------------------------- * Program Info Operations @@ -936,7 +944,7 @@ extern uint16_t cmyth_proginfo_episode(cmyth_proginfo_t prog); * \param prog proginfo handle * \return null-terminated string */ -extern char *proginfo_syndicated_episode(cmyth_proginfo_t prog); +extern char *cmyth_proginfo_syndicated_episode(cmyth_proginfo_t prog); /** * Retrieve the category of a program. @@ -1846,6 +1854,40 @@ extern uint32_t cmyth_recordingrule_prefinput(cmyth_recordingrule_t rr); */ extern void cmyth_recordingrule_set_prefinput(cmyth_recordingrule_t rr, uint32_t prefinput); +/** + * Retrieves the 'programid' field of a recording rule structure. + * Before forgetting the reference to this string the caller + * must call ref_release(). + * \param rr + * \return success: programid + * \return failure: NULL + */ +extern char *cmyth_recordingrule_programid(cmyth_recordingrule_t rr); + +/** + * Set the 'programid' field of the recording rule structure 'rr'. + * \param rr + * \param programid + */ +extern void cmyth_recordingrule_set_programid(cmyth_recordingrule_t rr, char *programid); + +/** + * Retrieves the 'seriesid' field of a recording rule structure. + * Before forgetting the reference to this string the caller + * must call ref_release(). + * \param rr + * \return success: seriesid + * \return failure: NULL + */ +extern char *cmyth_recordingrule_seriesid(cmyth_recordingrule_t rr); + +/** + * Set the 'seriesid' field of the recording rule structure 'rr'. + * \param rr + * \param seriesid + */ +extern void cmyth_recordingrule_set_seriesid(cmyth_recordingrule_t rr, char *seriesid); + /** * Retrieves the 'autometadata' field of a recording rule structure. * \param rr diff --git a/lib/cmyth/libcmyth/cmyth_local.h b/lib/cmyth/libcmyth/cmyth_local.h index 85b988198..29e1acbf2 100644 --- a/lib/cmyth/libcmyth/cmyth_local.h +++ b/lib/cmyth/libcmyth/cmyth_local.h @@ -341,6 +341,11 @@ extern int cmyth_rcv_keyframe(cmyth_conn_t conn, int *err, cmyth_keyframe_t buf, int count); +#define cmyth_rcv_posmap __cmyth_rcv_posmap +extern int cmyth_rcv_posmap(cmyth_conn_t conn, int *err, + cmyth_posmap_t buf, + int count); + #define cmyth_rcv_freespace __cmyth_rcv_freespace extern int cmyth_rcv_freespace(cmyth_conn_t conn, int *err, cmyth_freespace_t buf, @@ -476,6 +481,8 @@ struct cmyth_recordingrule { uint32_t parentid; //parent rule recordid char* profile; uint32_t prefinput; + char* programid; + char* seriesid; uint8_t autometadata; //DB version 1278 char* inetref; //DB version 1278 uint16_t season; //DB version 1278 diff --git a/lib/cmyth/libcmyth/file.c b/lib/cmyth/libcmyth/file.c index 1d33e6c07..96c2704d8 100644 --- a/lib/cmyth/libcmyth/file.c +++ b/lib/cmyth/libcmyth/file.c @@ -508,7 +508,7 @@ cmyth_file_seek(cmyth_file_t file, int64_t offset, int8_t whence) ret = 0; while(file->file_pos < file->file_req) { c = file->file_req - file->file_pos; - if(c > sizeof(msg)) + if (c > (int64_t)sizeof(msg)) c = sizeof(msg); if ((ret = cmyth_file_get_block(file, msg, (size_t)c)) < 0) diff --git a/lib/cmyth/libcmyth/keyframe.c b/lib/cmyth/libcmyth/keyframe.c index 33520c578..e3d55da0a 100644 --- a/lib/cmyth/libcmyth/keyframe.c +++ b/lib/cmyth/libcmyth/keyframe.c @@ -98,3 +98,21 @@ cmyth_keyframe_string(cmyth_keyframe_t kf) strcat(ret, pos); return ret; } + +uint32_t +cmyth_keyframe_number(cmyth_keyframe_t kf) +{ + if (kf) + return kf->keyframe_number; + else + return 0; +} + +int64_t +cmyth_keyframe_pos(cmyth_keyframe_t kf) +{ + if (kf) + return kf->keyframe_pos; + else + return 0; +} diff --git a/lib/cmyth/libcmyth/mythtv_mysql.c b/lib/cmyth/libcmyth/mythtv_mysql.c index 9bf718d9b..ba9a42f3e 100644 --- a/lib/cmyth/libcmyth/mythtv_mysql.c +++ b/lib/cmyth/libcmyth/mythtv_mysql.c @@ -908,7 +908,7 @@ cmyth_mysql_get_chanlist(cmyth_database_t db, cmyth_chanlist_t *chanlist) * The is_audio_service (radio) flag is only available from the channel scan. * The subquery therefore get the flag from the most recent channel scan. */ - const char *query_str = "SELECT c.chanid, c.channum, c.name, c.icon, c.visible, c.sourceid, c.mplexid, c.callsign, " + const char *query_str = "SELECT DISTINCT c.chanid, c.channum, c.name, c.icon, c.visible, c.sourceid, c.mplexid, c.callsign, " "IFNULL(cs.is_audio_service, 0) AS is_audio_service " "FROM channel c " "LEFT JOIN (SELECT service_id, MAX(scanid) AS scanid FROM channelscan_channel GROUP BY service_id) s " @@ -982,7 +982,7 @@ cmyth_mysql_get_recordingrules(cmyth_database_t db, cmyth_recordingrulelist_t *r "subtitle, recpriority, startoffset, endoffset, search, inactive, station, dupmethod, dupin, recgroup, " "storagegroup, playgroup, autotranscode, (autouserjob1 | (autouserjob2 << 1) | (autouserjob3 << 2) | " "(autouserjob4 << 3)), autocommflag, autoexpire, maxepisodes, maxnewest, transcoder, parentid, profile, " - "prefinput, autometadata, inetref, season, episode , filter " + "prefinput, programid, seriesid, autometadata, inetref, season, episode , filter " "FROM record ORDER BY recordid"; } else { @@ -991,7 +991,7 @@ cmyth_mysql_get_recordingrules(cmyth_database_t db, cmyth_recordingrulelist_t *r "subtitle, recpriority, startoffset, endoffset, search, inactive, station, dupmethod, dupin, recgroup, " "storagegroup, playgroup, autotranscode, (autouserjob1 | (autouserjob2 << 1) | (autouserjob3 << 2) | " "(autouserjob4 << 3)), autocommflag, autoexpire, maxepisodes, maxnewest, transcoder, parentid, profile, " - "prefinput " + "prefinput, programid, seriesid " "FROM record ORDER BY recordid"; } @@ -1053,12 +1053,14 @@ cmyth_mysql_get_recordingrules(cmyth_database_t db, cmyth_recordingrulelist_t *r rr->parentid = safe_atol(row[27]); rr->profile = ref_strdup(row[28]); rr->prefinput = safe_atol(row[29]); + rr->programid = ref_strdup(row[30]); + rr->seriesid =ref_strdup(row[31]); if (db->db_version >= 1278) { - rr->autometadata = safe_atoi(row[30]); - rr->inetref = ref_strdup(row[31]); - rr->season = safe_atoi(row[32]); - rr->episode = safe_atoi(row[33]); - rr->filter = safe_atol(row[34]); + rr->autometadata = safe_atoi(row[32]); + rr->inetref = ref_strdup(row[33]); + rr->season = safe_atoi(row[34]); + rr->episode = safe_atoi(row[35]); + rr->filter = safe_atol(row[36]); } else { rr->autometadata = 0; @@ -1097,18 +1099,18 @@ cmyth_mysql_add_recordingrule(cmyth_database_t db, cmyth_recordingrule_t rr) "description, category, findtime, findday, station, subtitle, recpriority, startoffset, endoffset, " "search, inactive, dupmethod, dupin, recgroup, storagegroup, playgroup, autotranscode, autouserjob1, " "autouserjob2, autouserjob3, autouserjob4, autocommflag, autoexpire, maxepisodes, maxnewest, transcoder, " - "parentid, profile, prefinput, autometadata, inetref, season, episode, filter) " + "parentid, profile, prefinput, programid, seriesid, autometadata, inetref, season, episode, filter) " "VALUES (?, ?, TIME(?), DATE(?), TIME(?), DATE(?), ?, ?, ?, TIME(?), MOD(DAYOFWEEK(?),7), " - "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ,? ,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; } else { query_str = "INSERT INTO record (record.type, chanid, starttime, startdate, endtime, enddate, title, " "description, category, findtime, findday, station, subtitle, recpriority, startoffset, endoffset, " "search, inactive, dupmethod, dupin, recgroup, storagegroup, playgroup, autotranscode, autouserjob1, " "autouserjob2, autouserjob3, autouserjob4, autocommflag, autoexpire, maxepisodes, maxnewest, transcoder, " - "parentid, profile, prefinput) " + "parentid, profile, prefinput, programid, seriesid) " "VALUES (?, ?, TIME(?), DATE(?), TIME(?), DATE(?), ?, ?, ?, TIME(?), MOD(DAYOFWEEK(?),7), " - "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ,? ,?, ?, ?, ?, ?, ?)"; + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ,? ,?, ?, ?, ?, ?, ?, ?, ?)"; } starttime = cmyth_timestamp_to_unixtime(rr->starttime); @@ -1150,7 +1152,9 @@ cmyth_mysql_add_recordingrule(cmyth_database_t db, cmyth_recordingrule_t rr) || cmyth_mysql_query_param_uint32(query, rr->transcoder) < 0 || cmyth_mysql_query_param_uint32(query, rr->parentid) < 0 || cmyth_mysql_query_param_str(query, rr->profile) < 0 - || cmyth_mysql_query_param_uint32(query, rr->prefinput) < 0) + || cmyth_mysql_query_param_uint32(query, rr->prefinput) < 0 + || cmyth_mysql_query_param_str(query, rr->programid) < 0 + || cmyth_mysql_query_param_str(query, rr->seriesid) < 0) || (db->db_version >= 1278 && (cmyth_mysql_query_param_uint(query, rr->autometadata) < 0 || cmyth_mysql_query_param_str(query, rr->inetref) < 0 @@ -1267,7 +1271,8 @@ cmyth_mysql_update_recordingrule(cmyth_database_t db, cmyth_recordingrule_t rr) "station = ?, dupmethod = ?, dupin = ?, recgroup = ?, storagegroup = ?, playgroup = ?, " "autotranscode = ?, autouserjob1 = ?, autouserjob2 = ?, autouserjob3 = ?, autouserjob4 = ?, " "autocommflag = ?, autoexpire = ?, maxepisodes = ?, maxnewest = ?, transcoder = ?, parentid = ?, " - "profile = ?, prefinput = ?, autometadata = ?, inetref = ?, season = ?, episode = ?, filter = ? " + "profile = ?, prefinput = ?, programid = ?, seriesid = ?, " + "autometadata = ?, inetref = ?, season = ?, episode = ?, filter = ? " "WHERE recordid = ?"; } else { @@ -1278,7 +1283,7 @@ cmyth_mysql_update_recordingrule(cmyth_database_t db, cmyth_recordingrule_t rr) "station = ?, dupmethod = ?, dupin = ?, recgroup = ?, storagegroup = ?, playgroup = ?, " "autotranscode = ?, autouserjob1 = ?, autouserjob2 = ?, autouserjob3 = ?, autouserjob4 = ?, " "autocommflag = ?, autoexpire = ?, maxepisodes = ?, maxnewest = ?, transcoder = ?, parentid = ?, " - "profile = ?, prefinput = ? " + "profile = ?, prefinput = ? , programid = ?, seriesid = ? " "WHERE recordid = ?"; } starttime = cmyth_timestamp_to_unixtime(rr->starttime); @@ -1319,8 +1324,10 @@ cmyth_mysql_update_recordingrule(cmyth_database_t db, cmyth_recordingrule_t rr) || cmyth_mysql_query_param_uint(query, rr->maxnewest) < 0 || cmyth_mysql_query_param_uint32(query, rr->transcoder) < 0 || cmyth_mysql_query_param_uint32(query, rr->parentid) < 0 - || cmyth_mysql_query_param_str(query, rr->playgroup) < 0 - || cmyth_mysql_query_param_uint32(query, rr->prefinput) < 0) + || cmyth_mysql_query_param_str(query, rr->profile) < 0 + || cmyth_mysql_query_param_uint32(query, rr->prefinput) < 0 + || cmyth_mysql_query_param_str(query, rr->programid) < 0 + || cmyth_mysql_query_param_str(query, rr->seriesid) < 0) || (db->db_version >= 1278 && ( cmyth_mysql_query_param_uint(query, rr->autometadata) < 0 || cmyth_mysql_query_param_str(query, rr->inetref) < 0 @@ -2318,9 +2325,7 @@ cmyth_mysql_get_recording_seek_offset(cmyth_database_t db, cmyth_proginfo_t prog */ const char *query_str1 = "SELECT mark,offset FROM recordedseek WHERE chanid = ? AND starttime = ? AND type = ? AND mark >= ? ORDER BY chanid ASC, starttime ASC, type ASC, mark ASC LIMIT 1;"; const char *query_str2 = "SELECT mark,offset FROM recordedseek WHERE chanid = ? AND starttime = ? AND type = ? AND mark <= ? ORDER BY chanid DESC, starttime DESC, type DESC, mark DESC LIMIT 1;"; - int64_t next_mark = 0; int64_t next_offset = 0; - int64_t prev_mark = 0; int64_t prev_offset = 0; uint8_t mask = 0; time_t start_ts_dt; @@ -2348,7 +2353,6 @@ cmyth_mysql_get_recording_seek_offset(cmyth_database_t db, cmyth_proginfo_t prog return -1; } while ((row = mysql_fetch_row(res))) { - next_mark = safe_atoll(row[0]); next_offset = safe_atoll(row[1]); mask |= 1; } @@ -2371,7 +2375,6 @@ cmyth_mysql_get_recording_seek_offset(cmyth_database_t db, cmyth_proginfo_t prog return -1; } while ((row = mysql_fetch_row(res))) { - prev_mark = safe_atoll(row[0]); prev_offset = safe_atoll(row[1]); mask |= 2; } diff --git a/lib/cmyth/libcmyth/posmap.c b/lib/cmyth/libcmyth/posmap.c index 15a24c9cc..cbd254b5b 100644 --- a/lib/cmyth/libcmyth/posmap.c +++ b/lib/cmyth/libcmyth/posmap.c @@ -92,3 +92,21 @@ cmyth_posmap_create(void) ret->posmap_list = NULL; return ret; } + +int +cmyth_posmap_count(cmyth_posmap_t pm) +{ + if (pm) + return pm->posmap_count; + else + return 0; +} + +cmyth_keyframe_t +cmyth_posmap_keyframe(cmyth_posmap_t pm, int index) +{ + if (pm && pm->posmap_count > index) + return ref_hold(pm->posmap_list[index]); + else + return NULL; +} diff --git a/lib/cmyth/libcmyth/recorder.c b/lib/cmyth/libcmyth/recorder.c index f40cbd39f..f17eb68c1 100644 --- a/lib/cmyth/libcmyth/recorder.c +++ b/lib/cmyth/libcmyth/recorder.c @@ -292,7 +292,48 @@ cmyth_recorder_get_framerate(cmyth_recorder_t rec, int64_t cmyth_recorder_get_frames_written(cmyth_recorder_t rec) { - return (int64_t) -ENOSYS; + int r, err, count; + int64_t ret; + char msg[256]; + + if (!rec || !rec->rec_conn) { + cmyth_dbg(CMYTH_DBG_ERROR, "%s: no recorder connection\n", + __FUNCTION__); + return (int64_t) -EINVAL; + } + + pthread_mutex_lock(&rec->rec_conn->conn_mutex); + + if (rec->rec_conn->conn_version >= 66) + { + snprintf(msg, sizeof(msg), "QUERY_RECORDER %"PRIu32"[]:[]GET_FRAMES_WRITTEN", rec->rec_id); + + if ((r = cmyth_send_message(rec->rec_conn, msg)) < 0) { + cmyth_dbg(CMYTH_DBG_ERROR, + "%s: cmyth_send_message() failed (%d)\n", + __FUNCTION__, r); + ret = r; + goto fail; + } + + count = cmyth_rcv_length(rec->rec_conn); + if ((r = cmyth_rcv_int64(rec->rec_conn, &err, &ret, count)) < 0) { + cmyth_dbg(CMYTH_DBG_ERROR, + "%s: cmyth_rcv_int64() failed (%d)\n", + __FUNCTION__, r); + ret = err; + goto fail; + } + } + else + { + ret = (int64_t) -EPERM; + } + +fail: + pthread_mutex_unlock(&rec->rec_conn->conn_mutex); + + return ret; } /* @@ -318,7 +359,7 @@ cmyth_recorder_get_free_space(cmyth_recorder_t rec) } /* - * cmyth_recorder_get_key_frame() + * cmyth_recorder_get_keyframe_pos() * * Scope: PUBLIC * @@ -336,7 +377,48 @@ cmyth_recorder_get_free_space(cmyth_recorder_t rec) int64_t cmyth_recorder_get_keyframe_pos(cmyth_recorder_t rec, uint32_t keynum) { - return (int64_t) -ENOSYS; + int r, err, count; + int64_t ret; + char msg[256]; + + if (!rec || !rec->rec_conn) { + cmyth_dbg(CMYTH_DBG_ERROR, "%s: no recorder connection\n", + __FUNCTION__); + return (int64_t) -EINVAL; + } + + pthread_mutex_lock(&rec->rec_conn->conn_mutex); + + if (rec->rec_conn->conn_version >= 66) + { + snprintf(msg, sizeof(msg), "QUERY_RECORDER %"PRIu32"[]:[]GET_KEYFRAME_POS[]:[]%"PRIu32, rec->rec_id, keynum); + + if ((r = cmyth_send_message(rec->rec_conn, msg)) < 0) { + cmyth_dbg(CMYTH_DBG_ERROR, + "%s: cmyth_send_message() failed (%d)\n", + __FUNCTION__, r); + ret = r; + goto fail; + } + + count = cmyth_rcv_length(rec->rec_conn); + if ((r = cmyth_rcv_int64(rec->rec_conn, &err, &ret, count)) < 0) { + cmyth_dbg(CMYTH_DBG_ERROR, + "%s: cmyth_rcv_int64() failed (%d)\n", + __FUNCTION__, r); + ret = err; + goto fail; + } + } + else + { + ret = (int64_t) -EPERM; + } + +fail: + pthread_mutex_unlock(&rec->rec_conn->conn_mutex); + + return ret; } /* @@ -352,16 +434,75 @@ cmyth_recorder_get_keyframe_pos(cmyth_recorder_t rec, uint32_t keynum) * * Return Value: * - * Success: 0 - * - * Failure: -(ERRNO) + * Success: A non-NULL, held cmyth_posmap_t + * Failure: A NULL pointer */ cmyth_posmap_t -cmyth_recorder_get_position_map(cmyth_recorder_t rec, - uint32_t start, - uint32_t end) +cmyth_recorder_get_position_map(cmyth_recorder_t rec, uint32_t start, uint32_t end) { - return NULL; + int err, count, consumed; + cmyth_posmap_t ret = NULL; + char msg[256]; + char tmp[1024]; + + if (!rec || !rec->rec_conn) { + cmyth_dbg(CMYTH_DBG_ERROR, "%s: no recorder connection\n", + __FUNCTION__); + return NULL; + } + + pthread_mutex_lock(&rec->rec_conn->conn_mutex); + + if (rec->rec_conn->conn_version >= 43) + { + snprintf(msg, sizeof(msg), "QUERY_RECORDER %"PRIu32"[]:[]FILL_POSITION_MAP[]:[]%"PRIu32"[]:[]%"PRIu32, rec->rec_id, start, end); + + if ((err = cmyth_send_message(rec->rec_conn, msg)) < 0) { + cmyth_dbg(CMYTH_DBG_ERROR, + "%s: cmyth_send_message() failed (%d)\n", + __FUNCTION__, err); + goto fail; + } + + count = cmyth_rcv_length(rec->rec_conn); + if (count < 0) { + cmyth_dbg(CMYTH_DBG_ERROR, + "%s: cmyth_rcv_length() failed (%d)\n", + __FUNCTION__, count); + goto fail; + } + + ret = cmyth_posmap_create(); + if (ret == NULL) { + cmyth_dbg(CMYTH_DBG_ERROR, + "%s: cmyth_posmap_create() failed\n", + __FUNCTION__); + goto fail; + } + + if (count > 2) { + consumed = cmyth_rcv_posmap(rec->rec_conn, &err, ret, count); + count -= consumed; + if (err) { + cmyth_dbg(CMYTH_DBG_ERROR, + "%s: cmyth_rcv_posmap() failed (%d)\n", + __FUNCTION__, err); + ref_release(ret); + ret = NULL; + } + } + err = 0; + while(count > 0 && err == 0) { + consumed = cmyth_rcv_data(rec->rec_conn, &err, (unsigned char*)tmp, sizeof(tmp) - 1, count); + cmyth_dbg(CMYTH_DBG_ERROR, "%s: leftover data: count %i, read %i, errno %i\n", __FUNCTION__, count, consumed, err); + count -= consumed; + } + } + +fail: + pthread_mutex_unlock(&rec->rec_conn->conn_mutex); + + return ret; } /* @@ -377,14 +518,75 @@ cmyth_recorder_get_position_map(cmyth_recorder_t rec, * * Return Value: * - * Success: 0 - * - * Failure: -(ERRNO) + * Success: A non-NULL, held cmyth_posmap_t + * Failure: A NULL pointer */ cmyth_posmap_t cmyth_recorder_get_duration_map(cmyth_recorder_t rec, uint32_t start, uint32_t end) { - return NULL; + int err, count, consumed; + cmyth_posmap_t ret = NULL; + char msg[256]; + char tmp[1024]; + + if (!rec || !rec->rec_conn) { + cmyth_dbg(CMYTH_DBG_ERROR, "%s: no recorder connection\n", + __FUNCTION__); + return NULL; + } + + pthread_mutex_lock(&rec->rec_conn->conn_mutex); + + if (rec->rec_conn->conn_version >= 77) + { + snprintf(msg, sizeof(msg), "QUERY_RECORDER %"PRIu32"[]:[]FILL_DURATION_MAP[]:[]%"PRIu32"[]:[]%"PRIu32, rec->rec_id, start, end); + + if ((err = cmyth_send_message(rec->rec_conn, msg)) < 0) { + cmyth_dbg(CMYTH_DBG_ERROR, + "%s: cmyth_send_message() failed (%d)\n", + __FUNCTION__, err); + goto fail; + } + + count = cmyth_rcv_length(rec->rec_conn); + if (count < 0) { + cmyth_dbg(CMYTH_DBG_ERROR, + "%s: cmyth_rcv_length() failed (%d)\n", + __FUNCTION__, count); + goto fail; + } + + ret = cmyth_posmap_create(); + if (ret == NULL) { + cmyth_dbg(CMYTH_DBG_ERROR, + "%s: cmyth_posmap_create() failed\n", + __FUNCTION__); + goto fail; + } + + if (count > 2) { + consumed = cmyth_rcv_posmap(rec->rec_conn, &err, ret, count); + count -= consumed; + if (err) { + cmyth_dbg(CMYTH_DBG_ERROR, + "%s: cmyth_rcv_posmap() failed (%d)\n", + __FUNCTION__, err); + ref_release(ret); + ret = NULL; + } + } + err = 0; + while(count > 0 && err == 0) { + consumed = cmyth_rcv_data(rec->rec_conn, &err, (unsigned char*)tmp, sizeof(tmp) - 1, count); + cmyth_dbg(CMYTH_DBG_ERROR, "%s: leftover data: count %i, read %i, errno %i\n", __FUNCTION__, count, consumed, err); + count -= consumed; + } + } + +fail: + pthread_mutex_unlock(&rec->rec_conn->conn_mutex); + + return ret; } /* diff --git a/lib/cmyth/libcmyth/recordingrule.c b/lib/cmyth/libcmyth/recordingrule.c index 83ff12dc9..472e37088 100644 --- a/lib/cmyth/libcmyth/recordingrule.c +++ b/lib/cmyth/libcmyth/recordingrule.c @@ -73,6 +73,10 @@ cmyth_recordingrule_destroy(cmyth_recordingrule_t rr) ref_release(rr->profile); if (rr->inetref) ref_release(rr->inetref); + if (rr->programid) + ref_release(rr->programid); + if (rr->seriesid) + ref_release(rr->seriesid); } /* @@ -236,6 +240,8 @@ cmyth_recordingrule_init(void) cmyth_recordingrule_set_parentid(rr, 0); cmyth_recordingrule_set_profile(rr, "Default"); cmyth_recordingrule_set_prefinput(rr, 0); + cmyth_recordingrule_set_programid(rr, ""); + cmyth_recordingrule_set_seriesid(rr, ""); cmyth_recordingrule_set_autometadata(rr, 0); cmyth_recordingrule_set_inetref(rr, ""); cmyth_recordingrule_set_season(rr, 0); @@ -297,6 +303,8 @@ cmyth_recordingrule_dup(cmyth_recordingrule_t rule) { cmyth_recordingrule_set_parentid(rr, rule->parentid); cmyth_recordingrule_set_profile(rr, rule->profile); cmyth_recordingrule_set_prefinput(rr, rule->prefinput); + cmyth_recordingrule_set_programid(rr, rule->programid); + cmyth_recordingrule_set_seriesid(rr, rule->seriesid); cmyth_recordingrule_set_autometadata(rr, rule->autometadata); cmyth_recordingrule_set_inetref(rr, rule->inetref); cmyth_recordingrule_set_season(rr, rule->season); @@ -777,6 +785,40 @@ cmyth_recordingrule_set_prefinput(cmyth_recordingrule_t rr, uint32_t prefinput) rr->prefinput = prefinput; } +char * +cmyth_recordingrule_programid(cmyth_recordingrule_t rr) +{ + if (!rr) { + return NULL; + } + return ref_hold(rr->programid); +} + +void +cmyth_recordingrule_set_programid(cmyth_recordingrule_t rr, char *programid) +{ + if (rr->programid) + ref_release(rr->programid); + rr->programid = ref_strdup(programid); +} + +char * +cmyth_recordingrule_seriesid(cmyth_recordingrule_t rr) +{ + if (!rr) { + return NULL; + } + return ref_hold(rr->seriesid); +} + +void +cmyth_recordingrule_set_seriesid(cmyth_recordingrule_t rr, char *seriesid) +{ + if (rr->seriesid) + ref_release(rr->seriesid); + rr->seriesid = ref_strdup(seriesid); +} + uint8_t cmyth_recordingrule_autometadata(cmyth_recordingrule_t rr) { diff --git a/lib/cmyth/libcmyth/socket.c b/lib/cmyth/libcmyth/socket.c index 8558b96fa..627b998d6 100644 --- a/lib/cmyth/libcmyth/socket.c +++ b/lib/cmyth/libcmyth/socket.c @@ -2727,15 +2727,121 @@ cmyth_rcv_keyframe(cmyth_conn_t conn, int *err, cmyth_keyframe_t buf, int count) { int tmp_err; + int consumed; + int total = 0; + char *failed = NULL; if (!err) { err = &tmp_err; } + if (count <= 0) { + *err = EINVAL; + return 0; + } + if(!buf) { + *err = EINVAL; + cmyth_dbg(CMYTH_DBG_ERROR, "%s: NULL buffer\n", __FUNCTION__); + return 0; + } + + *err = 0; + /* - * For now this is unimplemented. + * Get frame number (uint32) */ - *err = ENOSYS; - return 0; + consumed = cmyth_rcv_uint32(conn, err, &(buf->keyframe_number), count); + count -= consumed; + total += consumed; + if (*err) { + failed = "cmyth_rcv_uint32"; + goto fail; + } + + /* + * Get position (int64) + */ + consumed = cmyth_rcv_int64(conn, err, &(buf->keyframe_pos) , count); + count -= consumed; + total += consumed; + if (*err) { + failed = "cmyth_rcv_int64"; + goto fail; + } + + return total; + +fail: + cmyth_dbg(CMYTH_DBG_ERROR, "%s: %s() failed (%d) (count = %d)\n", + __FUNCTION__, failed, *err, count); + return total; +} + +int +cmyth_rcv_posmap(cmyth_conn_t conn, int *err, cmyth_posmap_t buf, + int count) +{ + int tmp_err; + int consumed = 0; + int r; + int c; + cmyth_keyframe_t kf; + int i; + void *ptr; + + cmyth_dbg(CMYTH_DBG_DEBUG, "%s\n", __FUNCTION__); + if (!err) { + err = &tmp_err; + } + if (count <= 0) { + *err = EINVAL; + return 0; + } + if(!buf) { + *err = EINVAL; + cmyth_dbg(CMYTH_DBG_ERROR, "%s: NULL buffer\n", __FUNCTION__); + return 0; + } + + *err = 0; + c = i = 0; + + while (count > 0) { + kf = cmyth_keyframe_create(); + if (!kf) { + cmyth_dbg(CMYTH_DBG_ERROR, + "%s: cmyth_keyframe_create() failed\n", + __FUNCTION__); + *err = ENOMEM; + break; + } + r = cmyth_rcv_keyframe(conn, err, kf, count); + consumed += r; + count -= r; + if (*err) { + ref_release(kf); + cmyth_dbg(CMYTH_DBG_ERROR, + "%s: cmyth_rcv_keyframe() failed (%d)\n", + __FUNCTION__, *err); + break; + } + + if (c <= i) { + c += 100; + ptr = realloc(buf->posmap_list, c * sizeof(cmyth_keyframe_t)); + if (!ptr) { + ref_release(kf); + *err = ENOMEM; + cmyth_dbg(CMYTH_DBG_ERROR, + "%s: %s: realloc() failed for list\n", + __FUNCTION__, *err); + break; + } + buf->posmap_list = ptr; + } + buf->posmap_list[i++] = kf; + buf->posmap_count = i; + } + return consumed; } /* @@ -2991,9 +3097,9 @@ cmyth_rcv_data(cmyth_conn_t conn, int *err, unsigned char *buf, int buflen, int void cmyth_toupper_string(char *str) { + size_t i; if (str) { - int i; - for ( i=0 ; i < sizeof(str) && str[i] != '\0' ; i++ ) { + for (i=0 ; i < sizeof(str) && str[i] != '\0' ; i++) { str[i] = toupper(str[i]); } }