/*
- Copyright 2008 Mark Calderbank
+ Copyright 2008 Mark Calderbank, 2020 Chris Tallon
This file is part of VOMP.
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
- along with VOMP; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ along with VOMP. If not, see <https://www.gnu.org/licenses/>.
*/
/* Sections of code and general methods adopted from VDR version 1.6.0
* dvbsubtitle.c (GPL version 2 or later)
/* Reference ETSI EN 300 743 V1.3.1 (2006-11) */
-#include "dvbsubtitles.h"
-#include "demuxer.h"
-#include "osdreceiver.h"
-#include "video.h"
-#include "log.h"
-// --- cTimeMs ---------------------------------------------------------------
-// Borrowed from the vdr tools
+#define DVBSDEBUG 0
-cTimeMs::cTimeMs(int Ms)
-{
- initted = false;
- if (Ms >= 0)
- Set(Ms);
- else
- {
- begin = 0;
- isFirstCheck = false;
- }
-}
+#ifdef DVBSDEBUG
-uint64_t cTimeMs::Now(void)
-{
- struct timespec tp;
+// DBG
+#include <stdio.h>
+#include <string>
+#include <chrono>
+#include <iomanip>
- if (getClockRealTime(&tp) == 0)
- return (uint64_t(tp.tv_sec)) * 1000 + tp.tv_nsec / 1000000;
- else
- Log::getInstance()->log("SUBTITLES", Log::ERR, "cTimeMs: clock_gettime(CLOCK_REALTIME) failed");
- return 0;
+std::string tp2str(const std::chrono::time_point<std::chrono::system_clock>& tp)
+{
+ auto tms = std::chrono::time_point_cast<std::chrono::milliseconds>(tp);
+ std::chrono::milliseconds e = tms.time_since_epoch();
+ long long c = e.count();
+ time_t tt = c / 1000;
+ int ttm = c % 1000;
+ auto stm = std::localtime(&tt);
+ std::stringstream ss;
+ ss << std::put_time(stm, "%T") << "." << std::setfill('0') << std::setw(3) << ttm;
+ return ss.str();
}
-void cTimeMs::Set(int Ms)
-{
- isFirstCheck = true; // Timer set, the first check can be done once
- begin = Now() + Ms;
+#endif
- if (Ms) initted = true;
-}
-bool cTimeMs::TimedOut(void)
-{
- if (isFirstCheck && (Now() >= begin))
- {
- isFirstCheck = false; // Timer timed out & the first check is done
- return true;
- }
- else
- return false;
-}
-uint64_t cTimeMs::Elapsed(void)
-{
- if (!initted) return 0;
- return Now() - begin;
-}
+#include "demuxer.h"
+#include "osdreceiver.h"
+#include "video.h"
+#include "log.h"
-//-------- End of cTimeMs borrowed from the vdr tools --------
+#include "dvbsubtitles.h"
DVBSubtitleCLUT::DVBSubtitleCLUT()
: version(0xFF),
: osd(tosd),
pageOnDisplay(65536),
running(false),
- showing(false),
- threadNudged(false),
- SubtitleTimeout(0),
- timeout_clear(false)
+ showing(false)
{
}
void DVBSubtitles::put(const PESPacket& packet)
{
+ // Player object play feed thread comes here, via the demuxer
+ // input_mutex only used in put() here, start(), stop() and threadMethod()
+ // So, roll old Thread::mutex/cond into input_mutex?
+
input_mutex.lock();
if (running)
{
if (packet.getPTS() != PESPacket::PTS_INVALID)
{
worklist.push_back(packet);
- nudge();
+ signalRecalcWLTO = true;
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "Received packet: PTS: %llu, size: %u, type: %u", packet.getPTS(), packet.getSize(), packet.getPacketType());
+
+
+ dvbsCond.notify_one();
+ }
+ else
+ {
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "PUT DROPPING INVALID PACKET: PTS: %llu, size: %u, type: %u", packet.getPTS(), packet.getSize(), packet.getPacketType());
}
}
input_mutex.unlock();
DVBSubtitlePage::RegionMap::const_iterator region_iter;
region_iter = page.regions.find(i->first);
if (region_iter == page.regions.end()) continue;
- Log::getInstance()->log("SUBTITLES", Log::DEBUG, "Clear region %d", i->first);
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "Clear region %d", i->first);
if (!osdMenuShowing) osd->clearOSDArea(i->second.x, i->second.y,
region_iter->second.getWidth(), region_iter->second.getHeight(),dds);
}
DVBSubtitlePage::RegionMap::const_iterator region_iter;
region_iter = page.regions.find(i->first);
if (region_iter == page.regions.end()) continue;
- Log::getInstance()->log("SUBTITLES", Log::DEBUG, "Display region %d", i->first);
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "Display region %d", i->first);
if (!osdMenuShowing) osd->drawOSDBitmap(i->second.x, i->second.y,
region_iter->second,dds);
}
// after displaying regions set the page timeout timer
- SubtitleTimeout.Set(page.timeout * 1000);
- timeout_clear=false;
- Log::getInstance()->log("SUBTITLES", Log::DEBUG, "SubtitleTimeout %d", page.timeout);
+
+ subtitleTimeoutPoint = std::chrono::system_clock::now() + std::chrono::seconds(page.timeout);
+ subtitleTimeoutPointActive = true;
+
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "SubtitleTimeout %d", page.timeout);
}
-int DVBSubtitles::start()
+void DVBSubtitles::start()
{
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "subs start");
+
+#ifdef DVBSDEBUG
+
+ DBG = fopen("dfifo", "a");
+
+ fprintf(DBG, "\033[H\033[2J");
+ fflush(DBG);
+
+#endif
+
input_mutex.lock();
+
dds=DVBSubtitleDisplayDefinition();
running = true;
+
+ dvbsThread = std::thread([this] { threadMethod(); });
+
input_mutex.unlock();
- return threadStart();
}
void DVBSubtitles::stop()
{
- threadStop();
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "subs stop");
+
input_mutex.lock();
- running = false;
- worklist.clear();
- input_mutex.unlock();
- output_mutex.lock();
- PageMap::const_iterator pageEntry = pages.find(pageOnDisplay);
- if (pageEntry != pages.end())
+ if (!running)
{
- const DVBSubtitlePage& page = pageEntry->second;
- DVBSubtitlePage::DisplayMap::const_iterator i;
- for (i = page.currentDisplay.begin(); i != page.currentDisplay.end(); ++i)
- {
- DVBSubtitlePage::RegionMap::const_iterator region_iter;
- region_iter = page.regions.find(i->first);
- if (region_iter == page.regions.end()) continue;
- if (!osdMenuShowing) osd->clearOSDArea(i->second.x, i->second.y,
- region_iter->second.getWidth(), region_iter->second.getHeight(),dds);
- }
+ Log::getInstance()->log("DVBSubs", Log::ERR, "STOP called, already dead!");
+ input_mutex.unlock();
+ return;
}
- pages.clear();
- pageOnDisplay = 65536;
- output_mutex.unlock();
- threadNudged = false;
+
+ running = false; // disables put()
+ signalStop = true;
+ dvbsCond.notify_one();
+ input_mutex.unlock();
+ dvbsThread.join();
+
+ worklist.clear();
+ input_mutex.unlock();
+
+ clearDisplayedPages();
+
+
+#ifdef DVBSDEBUG
+ fclose(DBG);
+#endif
}
void DVBSubtitles::show()
{
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "subs show");
+
output_mutex.lock();
showing = true;
output_mutex.unlock();
void DVBSubtitles::hide()
{
- output_mutex.lock();
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "subs hide");
+
showing = false;
+ clearDisplayedPages();
+}
+
+void DVBSubtitles::clearDisplayedPages()
+{
+ output_mutex.lock();
+
PageMap::const_iterator pageEntry = pages.find(pageOnDisplay);
if (pageEntry != pages.end())
{
}
pages.clear();
pageOnDisplay = 65536;
+
output_mutex.unlock();
}
-void DVBSubtitles::nudge()
+void DVBSubtitles::pause()
{
-#ifndef WIN32
- pthread_mutex_lock(&threadCondMutex);
- threadNudged = true;
- pthread_cond_signal(&threadCond);
- pthread_mutex_unlock(&threadCondMutex);
-#else
- WaitForSingleObject(threadCondMutex, INFINITE);
- threadNudged = true;
- SetEvent(threadCond);
- ReleaseMutex(threadCondMutex);
-#endif
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "subs pause");
+
+ input_mutex.lock();
+ if (!running)
+ {
+ Log::getInstance()->log("DVBSubs", Log::ERR, "pause called, already dead!");
+ input_mutex.unlock();
+ return;
+ }
+
+ signalPause = true;
+ dvbsCond.notify_one();
+ input_mutex.unlock();
}
-#define SUBTITLE_TIMEOUT_MS 750
-// How often do we check if subtitles have been timed out.
+void DVBSubtitles::unPause()
+{
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "subs pause");
+
+ input_mutex.lock();
+ if (!running)
+ {
+ Log::getInstance()->log("DVBSubs", Log::ERR, "pause called, already dead!");
+ input_mutex.unlock();
+ return;
+ }
+
+ signalRecalcWLTO = true;
+ dvbsCond.notify_one();
+ input_mutex.unlock();
+}
#if WIN32
// FIXME win pragma
void DVBSubtitles::threadMethod()
{
+ std::unique_lock<std::mutex> ul(input_mutex); // Lock here, force start() to return before this does anything
+
+ bool waitExpireST{};
+ bool waitExpireWL{};
+
+ std::chrono::time_point<std::chrono::system_clock> worklistTimeoutPoint;
+ bool worklistTimeoutPointActive{};
+
+ while(1)
+ {
+#ifdef DVBSDEBUG
+
+ // TEMP DEBUG - re-enable nowPTS below when this goes
+ ULLONG nowPTS = Video::getInstance()->getCurrentTimestamp();
+ fprintf(DBG, "\033[H\033[2J");
+ fprintf(DBG, "Now: %s %llu\n", tp2str(std::chrono::system_clock::now()).c_str(), Video::getInstance()->getCurrentTimestamp());
+ for (auto p : worklist)
+ {
+ fprintf(DBG, "packet PTS %llu SIZE %i\tTIME %s\n", p.getPTS(), p.getSize(),
+ tp2str(std::chrono::system_clock::now() + std::chrono::milliseconds(PTSDifference(p.getPTS(), nowPTS) / 90)).c_str());
+ }
+ fflush(DBG);
+
+#endif
+
+
+ if (signalStop)
+ {
+ Log::getInstance()->log("DVBSubs", Log::INFO, "Thread exiting");
+ signalStop = false;
+ return;
+ }
+ else if (signalPause)
+ {
+ signalPause = false;
+ subtitleTimeoutPointActive = false;
+ worklistTimeoutPointActive = false;
+ }
+ else if (signalRecalcWLTO) // once per incoming packet, and other times
+ {
+ signalRecalcWLTO = false;
+#ifndef DVBSDEBUG
+ ULLONG nowPTS = Video::getInstance()->getCurrentTimestamp();
+#endif
+
+ if (nowPTS == 0)
+ {
+ // Video is not started yet. DVBSub packet has come in. Don't set worklistTimeoutPoint,
+ // just store the packet and allow next signal to start things off
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "signalRecalcWLTO but Video PTS == 0");
+ }
+ else
+ {
+ worklistTimeoutPointActive = true;
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "Calc: Num packets available: %i", worklist.size());
+
+ ULLONG pktPTS = worklist.front().getPTS();
+ ULLONG diff = PTSDifference(pktPTS, nowPTS);
+ diff /= 90; // convert diff to ms (PTS difference is in 1/90000s)
+ if (diff < 60 * 1000)
+ {
+ worklistTimeoutPoint = std::chrono::system_clock::now() + std::chrono::milliseconds(diff);
+ }
+ else
+ {
+ // We have a problem. An action so far in the future should have
+ // been culled. Probably the action is already due and PTSDifference
+ // wrapped around. Therefore we sleep for a minimal time instead.
+
+ // FIXME check if this still works
+ worklistTimeoutPoint = std::chrono::system_clock::now();
+ }
+
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "Calc'd new worklistTimeoutPoint");
+ }
+ }
+ else if (waitExpireST) // do real work - subtitletimeout
+ {
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "Subtitle timeout occurred");
+ waitExpireST = false;
+ subtitleTimeoutPointActive = false;
+ output_mutex.lock();
+ if (showing && !osdMenuShowing) osd->clearOSD();
+ output_mutex.unlock();
+ }
+ else if (waitExpireWL) // do real work - worklist - could be multiple packets to do
+ {
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "Something to do");
+
+ waitExpireWL = false;
+ worklistTimeoutPointActive = false;
+
+ ULLONG nowPTS = Video::getInstance()->getCurrentTimestamp();
+
+ // Guaranteed to be at least one packet in the worklist
+ PESPacket packet = worklist.front();
+ ULLONG pktPTS = worklist.front().getPTS();
+
+ while(1)
+ {
+ worklist.pop_front();
+
+ output_mutex.lock();
+ if (showing) decodePacket(packet);
+ output_mutex.unlock();
+
+ // Repeat this loop only if there is another packet and it's the same PTS as this one
+ // Not sure this ever happens.
+ if (worklist.size() && (worklist.front().getPTS() == pktPTS))
+ {
+ packet = worklist.front();
+ continue;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if (worklist.size())
+ {
+ signalRecalcWLTO = true;
+ continue; // restart the loop and force a recalc
+ }
+
+
+ /*
+
+
+ if (PTSDifference(nowPTS, pktPTS) < 2*90000 // need this? we should be here exactly on time
+ || PTSDifference(pktPTS, nowPTS) < 4000)
+ { // It is due for processing or discarding.
+ worklist.pop_front();
+
+ output_mutex.lock();
+ if (showing) decodePacket(packet);
+ output_mutex.unlock();
+ }
+ else if (PTSDifference(pktPTS, nowPTS) >= 60*90000)
+ { // Seems like a bad or very old entry. Get rid of it.
+ finished = false;
+ worklist.pop_front();
+ }
+*/
+
+
+ // Loop has exited. Either worklist is now empty or the next packet doesn't match the PTSDifference ranges
+ }
+ else
+ {
+ // Spurious?
+ }
+
+ // Either:
+ // 1. sleep until timeout
+ // 2. sleep until next worklist
+ // 3. sleep until signalled
+
+ if (!subtitleTimeoutPointActive && !worklistTimeoutPointActive)
+ {
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "No WL or STT. Infinite wait");
+ dvbsCond.wait(ul); // Wait until signalled
+ }
+ else if (subtitleTimeoutPointActive)
+ {
+ if (worklistTimeoutPointActive && (worklistTimeoutPoint < subtitleTimeoutPoint))
+ {
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "WaitFor: WL");
+ if (dvbsCond.wait_until(ul, worklistTimeoutPoint) == std::cv_status::timeout) waitExpireWL = true;
+ }
+ else
+ {
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "WaitFor: ST");
+ if (dvbsCond.wait_until(ul, subtitleTimeoutPoint) == std::cv_status::timeout) waitExpireST = true;
+ }
+ }
+ else
+ {
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "WaitFor: WL");
+ if (dvbsCond.wait_until(ul, worklistTimeoutPoint) == std::cv_status::timeout) waitExpireWL = true;
+ }
+ }
+/*
+
struct timespec sleeptime;
sleeptime.tv_sec = 0;
sleeptime.tv_nsec = 0;
timeout_clear=false;
while (1)
{
- if (SubtitleTimeout.TimedOut()) // do we have a subtitle timeout
+ if (subtitleTimeoutPointActive && (subtitleTimeoutPoint < std::chrono::system_clock::now())) // do we have a subtitle timeout
{
+ subtitleTimeoutPointActive = false;
+
+
+// if (SubtitleTimeout.TimedOut()) // do we have a subtitle timeout
+// {
output_mutex.lock();
if (showing && !osdMenuShowing) {
if (!timeout_clear) {
}
else if (showing) // if not lets check when will we have it
{
+ // Get milliseconds until the timeout
+ wakeup = std::chrono::duration_cast<std::chrono::milliseconds>(subtitleTimeoutPoint - std::chrono::system_clock::now()).count();
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "A1: %lu", wakeup);
+
+ // now - begin
wakeup = -(SubtitleTimeout.Elapsed());
+
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "A2: %lu", wakeup);
+
if (wakeup > 0 && sleeptime.tv_nsec == 0 && sleeptime.tv_sec == 0)
// We are not done, we still have a Subtitle Timeout!
{
timeout_clear=false;
}
}
+
+
+ //if (dvbsThreadStop) return;
threadCheckExit();
+
+
threadLock();
if (!threadNudged)
{ // We have done the current work and no more has arrived. Sleep.
if (sleeptime.tv_sec == 0 && sleeptime.tv_nsec == 0)
{
- Log::getInstance()->log("SUBTITLES", Log::DEBUG, "Sleeping until nudged.");
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "Sleeping until nudged.");
threadWaitForSignal();
}
else
{
- Log::getInstance()->log("SUBTITLES", Log::DEBUG, "Sleeping for %d and %d", sleeptime.tv_sec, sleeptime.tv_nsec);
+ Log::getInstance()->log("DVBSubs", Log::DEBUG, "Sleeping for %d and %d", sleeptime.tv_sec, sleeptime.tv_nsec);
struct timespec targetTime;
getClockRealTime(&targetTime);
}
}
}
+
+ */
}
void DVBSubtitles::setOSDMenuVisibility(bool visible)