eDVBResourceManager *eDVBResourceManager::instance;
eDVBResourceManager::eDVBResourceManager()
+ :m_releaseCachedChannelTimer(eApp)
{
avail = 1;
busy = 0;
eDebug("found %d adapter, %d frontends and %d demux",
m_adapter.size(), m_frontend.size(), m_demux.size());
+
+ CONNECT(m_releaseCachedChannelTimer.timeout, eDVBResourceManager::releaseCachedChannel);
}
if(channelid==cache_chan->getChannelID())
{
eDebug("use cached_channel");
- channel=m_cached_channel;
+ channel = m_cached_channel;
return 0;
}
+ m_cached_channel_state_changed_conn.disconnect();
m_cached_channel=0;
+ m_releaseCachedChannelTimer.stop();
}
// eDebug("allocate channel.. %04x:%04x", channelid.transport_stream_id.get(), channelid.original_network_id.get());
return errChidNotFound;
}
m_cached_channel = channel = ch;
+ m_cached_channel_state_changed_conn =
+ CONNECT(ch->m_stateChanged,eDVBResourceManager::DVBChannelStateChanged);
return 0;
}
+void eDVBResourceManager::DVBChannelStateChanged(iDVBChannel *chan)
+{
+ int state=0;
+ chan->getState(state);
+ switch (state)
+ {
+ case iDVBChannel::state_release:
+ case iDVBChannel::state_ok:
+ {
+ eDebug("stop release channel timer");
+ m_releaseCachedChannelTimer.stop();
+ break;
+ }
+ case iDVBChannel::state_last_instance:
+ {
+ eDebug("start release channel timer");
+ m_releaseCachedChannelTimer.start(3000, true);
+ break;
+ }
+ default: // ignore all other events
+ break;
+ }
+}
+
+void eDVBResourceManager::releaseCachedChannel()
+{
+ eDebug("release cached channel (timer timeout)");
+ m_cached_channel=0;
+}
+
RESULT eDVBResourceManager::allocateRawChannel(eUsePtr<iDVBChannel> &channel, int frontend_index)
{
ePtr<eDVBAllocatedFrontend> fe;
if (m_cached_channel)
+ {
+ m_cached_channel_state_changed_conn.disconnect();
m_cached_channel=0;
+ m_releaseCachedChannelTimer.stop();
+ }
if (allocateFrontendByIndex(fe, frontend_index))
return errNoFrontend;
{
ePtr<eDVBAllocatedDemux> demux;
- if (m_cached_channel)
- m_cached_channel=0;
-
eDVBChannel *ch;
ch = new eDVBChannel(this, 0);
m_pvr_thread = 0;
+ m_skipmode_n = m_skipmode_m = 0;
+
if (m_frontend)
m_frontend->get().connectStateChange(slot(*this, &eDVBChannel::frontendStateChanged), m_conn_frontendStateChanged);
}
{
if (m_channel_id)
m_mgr->removeChannel(this);
-
- if (m_pvr_thread)
- {
- m_pvr_thread->stop();
- ::close(m_pvr_fd_src);
- ::close(m_pvr_fd_dst);
- delete m_pvr_thread;
- }
+
+ stopFile();
}
void eDVBChannel::frontendStateChanged(iDVBFrontend*fe)
ourstate = state_tuning;
} else if (state == iDVBFrontend::stateLostLock)
{
- eDebug("OURSTATE: lost lock");
- ourstate = state_unavailable;
+ /* on managed channels, we try to retune in order to re-acquire lock. */
+ if (m_feparm)
+ {
+ eDebug("OURSTATE: lost lock, trying to retune");
+ ourstate = state_tuning;
+ m_frontend->get().tune(*m_feparm);
+ } else
+ /* on unmanaged channels, we don't do this. the client will do this. */
+ {
+ eDebug("OURSTATE: lost lock, unavailable now.");
+ ourstate = state_unavailable;
+ }
} else if (state == iDVBFrontend::stateFailed)
{
eDebug("OURSTATE: failed");
eDebug("eDVBChannel: End of file!");
m_event(this, evtEOF);
break;
+ case eFilePushThread::evtUser: /* start */
+ eDebug("SOF");
+ m_event(this, evtSOF);
+ break;
+ }
+}
+
+void eDVBChannel::cueSheetEvent(int event)
+{
+ switch (event)
+ {
+ case eCueSheet::evtSeek:
+ eDebug("seek.");
+ flushPVR(m_cue->m_decoding_demux);
+ break;
+ case eCueSheet::evtSkipmode:
+ {
+ {
+ eSingleLocker l(m_cue->m_lock);
+ m_cue->m_seek_requests.push_back(std::pair<int, pts_t>(1, 0)); /* resync */
+ if (m_cue->m_skipmode_ratio)
+ {
+ int bitrate = m_tstools.calcBitrate(); /* in bits/s */
+ eDebug("skipmode ratio is %lld:90000, bitrate is %d bit/s", m_cue->m_skipmode_ratio, bitrate);
+ /* i agree that this might look a bit like black magic. */
+ m_skipmode_n = 512*1024; /* must be 1 iframe at least. */
+ m_skipmode_m = bitrate / 8 / 90000 * m_cue->m_skipmode_ratio / 8;
+
+ if (m_cue->m_skipmode_ratio < 0)
+ m_skipmode_m -= m_skipmode_n;
+
+ eDebug("resolved to: %d %d", m_skipmode_m, m_skipmode_n);
+
+ if (abs(m_skipmode_m) < abs(m_skipmode_n))
+ {
+ eWarning("something is wrong with this calculation");
+ m_skipmode_n = m_skipmode_m = 0;
+ }
+
+ } else
+ {
+ eDebug("skipmode ratio is 0, normal play");
+ m_skipmode_n = m_skipmode_m = 0;
+ }
+ }
+ flushPVR(m_cue->m_decoding_demux);
+ break;
+ }
+ case eCueSheet::evtSpanChanged:
+ {
+ m_source_span.clear();
+ for (std::list<std::pair<pts_t, pts_t> >::const_iterator i(m_cue->m_spans.begin()); i != m_cue->m_spans.end(); ++i)
+ {
+ off_t offset_in, offset_out;
+ pts_t pts_in = i->first, pts_out = i->second;
+ if (m_tstools.getOffset(offset_in, pts_in) || m_tstools.getOffset(offset_out, pts_out))
+ {
+ eDebug("span translation failed.\n");
+ continue;
+ }
+ eDebug("source span: %llx .. %llx, translated to %llx..%llx", pts_in, pts_out, offset_in, offset_out);
+ m_source_span.push_back(std::pair<off_t, off_t>(offset_in, offset_out));
+ }
+ break;
+ }
+ }
+}
+
+ /* remember, this gets called from another thread. */
+void eDVBChannel::getNextSourceSpan(off_t current_offset, size_t bytes_read, off_t &start, size_t &size)
+{
+ unsigned int max = 10*1024*1024;
+
+ if (!m_cue)
+ {
+ eDebug("no cue sheet. forcing normal play");
+ start = current_offset;
+ size = max;
+ return;
+ }
+
+ eSingleLocker l(m_cue->m_lock);
+
+ if (!m_cue->m_decoding_demux)
+ {
+ start = current_offset;
+ size = max;
+ eDebug("getNextSourceSpan, no decoding demux. forcing normal play");
+ return;
+ }
+
+ if (m_skipmode_n)
+ {
+ eDebug("skipmode %d:%d", m_skipmode_m, m_skipmode_n);
+ max = m_skipmode_n;
+ }
+
+ eDebug("getNextSourceSpan, current offset is %08llx!", current_offset);
+
+ current_offset += m_skipmode_m;
+
+ while (!m_cue->m_seek_requests.empty())
+ {
+ std::pair<int, pts_t> seek = m_cue->m_seek_requests.front();
+ m_cue->m_seek_requests.pop_front();
+ int relative = seek.first;
+ pts_t pts = seek.second;
+
+ pts_t now = 0;
+ if (relative)
+ {
+ if (!m_cue->m_decoder)
+ {
+ eDebug("no decoder - can't seek relative");
+ continue;
+ }
+ if (m_cue->m_decoder->getPTS(0, now))
+ {
+ eDebug("decoder getPTS failed, can't seek relative");
+ continue;
+ }
+ if (getCurrentPosition(m_cue->m_decoding_demux, now, 1))
+ {
+ eDebug("seekTo: getCurrentPosition failed!");
+ continue;
+ }
+ }
+
+ if (relative == 1) /* pts relative */
+ pts += now;
+
+ if (relative != 2)
+ if (pts < 0)
+ pts = 0;
+
+ if (relative == 2) /* AP relative */
+ {
+ eDebug("AP relative seeking: %lld, at %lld", pts, now);
+ pts_t nextap;
+ if (m_tstools.getNextAccessPoint(nextap, now, pts))
+ {
+ pts = now;
+ eDebug("AP relative seeking failed!");
+ } else
+ {
+ eDebug("next ap is %llx\n", pts);
+ pts = nextap;
+ }
+ }
+
+ off_t offset = 0;
+ if (m_tstools.getOffset(offset, pts))
+ continue;
+
+ eDebug("ok, resolved skip (rel: %d, diff %lld), now at %08llx", relative, pts, offset);
+ current_offset = offset;
+ }
+
+ for (std::list<std::pair<off_t, off_t> >::const_iterator i(m_source_span.begin()); i != m_source_span.end(); ++i)
+ {
+ if ((current_offset >= i->first) && (current_offset < i->second))
+ {
+ start = current_offset;
+ size = i->second - current_offset;
+ if (size > max)
+ size = max;
+ eDebug("HIT, %lld < %lld < %lld", i->first, current_offset, i->second);
+ return;
+ }
+ if (current_offset < i->first)
+ {
+ /* ok, our current offset is in an 'out' zone. */
+ if ((m_skipmode_m >= 0) || (i == m_source_span.begin()))
+ {
+ /* in normal playback, just start at the next zone. */
+ start = i->first;
+ size = i->second - i->first;
+ if (size > max)
+ size = max;
+ eDebug("skip");
+ if (m_skipmode_m < 0)
+ {
+ eDebug("reached SOF");
+ /* reached SOF */
+ m_skipmode_m = 0;
+ m_pvr_thread->sendEvent(eFilePushThread::evtUser);
+ }
+ } else
+ {
+ /* when skipping reverse, however, choose the zone before. */
+ --i;
+ eDebug("skip to previous block, which is %llx..%llx", i->first, i->second);
+ size_t len = i->second - i->first;
+ if (max > len)
+ max = len;
+ start = i->second - max;
+ size = max;
+ eDebug("skipping to %llx, %d", start, size);
+ }
+ return;
+ }
}
+
+ if ((current_offset < -m_skipmode_m) && (m_skipmode_m < 0))
+ {
+ eDebug("reached SOF");
+ m_skipmode_m = 0;
+ m_pvr_thread->sendEvent(eFilePushThread::evtUser);
+ }
+
+ start = current_offset;
+ size = max;
+ eDebug("END OF CUESHEET. (%08llx, %d)", start, size);
+ return;
}
void eDVBChannel::AddUse()
{
- ++m_use_count;
+ if (++m_use_count > 1 && m_state == state_last_instance)
+ {
+ m_state = state_ok;
+ m_stateChanged(this);
+ }
}
void eDVBChannel::ReleaseUse()
m_state = state_release;
m_stateChanged(this);
}
+ else if (m_use_count == 1)
+ {
+ m_state = state_last_instance;
+ m_stateChanged(this);
+ }
}
RESULT eDVBChannel::setChannel(const eDVBChannelID &channelid, ePtr<iDVBFrontendParameters> &feparm)
/* if tuning fails, shutdown the channel immediately. */
int res;
res = m_frontend->get().tune(*feparm);
+ m_feparm = feparm;
if (res)
{
THEN DO A REAL FIX HERE! */
/* (this codepath needs to be improved anyway.) */
+#if HAVE_DVB_API_VERSION < 3
+ m_pvr_fd_dst = open("/dev/pvr", O_WRONLY);
+#else
m_pvr_fd_dst = open("/dev/misc/pvr", O_WRONLY);
+#endif
if (m_pvr_fd_dst < 0)
{
eDebug("can't open /dev/misc/pvr - you need to buy the new(!) $$$ box! (%m)"); // or wait for the driver to be improved.
m_pvr_thread = new eFilePushThread();
m_pvr_thread->enablePVRCommit(1);
+ m_pvr_thread->setScatterGather(this);
+
m_pvr_thread->start(m_pvr_fd_src, m_pvr_fd_dst);
CONNECT(m_pvr_thread->m_event, eDVBChannel::pvrEvent);
return 0;
}
+void eDVBChannel::stopFile()
+{
+ if (m_pvr_thread)
+ {
+ m_pvr_thread->stop();
+ ::close(m_pvr_fd_src);
+ ::close(m_pvr_fd_dst);
+ delete m_pvr_thread;
+ m_pvr_thread = 0;
+ }
+}
+
+void eDVBChannel::setCueSheet(eCueSheet *cuesheet)
+{
+ m_conn_cueSheetEvent = 0;
+ m_cue = cuesheet;
+ if (m_cue)
+ m_cue->connectEvent(slot(*this, &eDVBChannel::cueSheetEvent), m_conn_cueSheetEvent);
+}
+
RESULT eDVBChannel::getLength(pts_t &len)
{
return m_tstools.calcLen(len);
if (!decoding_demux)
return -1;
- off_t begin = 0;
- /* getPTS for offset 0 is cached, so it doesn't harm. */
- int r = m_tstools.getPTS(begin, pos);
- if (r)
- {
- eDebug("tstools getpts(0) failed!");
- return r;
- }
-
pts_t now;
- /* TODO: this is a gross hack. */
- r = decoding_demux->getSTC(now, mode ? 128 : 0);
-
- if (r)
- {
- eDebug("demux getSTC failed");
- return -1;
- }
+ int r;
-// eDebug("STC: %08llx PTS: %08llx, diff %lld", now, pos, now - pos);
- /* when we are less than 10 seconds before the start, return 0. */
- /* (we're just waiting for the timespam to start) */
- if ((now < pos) && ((pos - now) < 90000 * 10))
+ if (mode == 0) /* demux */
{
- pos = 0;
- return 0;
- }
-
- if (now < pos) /* wrap around */
- pos = now + ((pts_t)1)<<33 - pos;
- else
- pos = now - pos;
-
- return 0;
-}
-
-RESULT eDVBChannel::seekTo(iDVBDemux *decoding_demux, int relative, pts_t &pts)
-{
- int bitrate = m_tstools.calcBitrate(); /* in bits/s */
-
- if (bitrate == -1)
- return -1;
-
- if (relative)
- {
- pts_t now;
- if (getCurrentPosition(decoding_demux, now, 0))
+ r = decoding_demux->getSTC(now, 0);
+ if (r)
{
- eDebug("seekTo: getCurrentPosition failed!");
+ eDebug("demux getSTC failed");
return -1;
}
- pts += now;
- }
+ } else
+ now = pos; /* fixup supplied */
- if (pts < 0)
- pts = 0;
+ off_t off = 0; /* TODO: fixme */
+ r = m_tstools.fixupPTS(off, now);
+ if (r)
+ {
+ eDebug("fixup PTS failed");
+ return -1;
+ }
- off_t offset = (pts * (pts_t)bitrate) / 8ULL / 90000ULL;
+ pos = now;
- seekToPosition(decoding_demux, offset);
return 0;
}
-RESULT eDVBChannel::seekToPosition(iDVBDemux *decoding_demux, const off_t &r)
+void eDVBChannel::flushPVR(iDVBDemux *decoding_demux)
{
/* when seeking, we have to ensure that all buffers are flushed.
there are basically 3 buffers:
the ratebuffer (for example) would immediately refill from
the not-yet-flushed PVR buffer.
*/
- eDebug("eDVBChannel: seekToPosition .. %llx", r);
- m_pvr_thread->pause();
+ m_pvr_thread->pause();
/* flush internal filepush buffer */
m_pvr_thread->flush();
-
/* HACK: flush PVR buffer */
::ioctl(m_pvr_fd_dst, 0);
decoding_demux->flush();
/* demux will also flush all decoder.. */
- m_pvr_thread->seek(SEEK_SET, r);
+ /* resume will re-query the SG */
m_pvr_thread->resume();
+}
+
+DEFINE_REF(eCueSheet);
+
+eCueSheet::eCueSheet()
+{
+ m_skipmode_ratio = 0;
+}
+
+void eCueSheet::seekTo(int relative, const pts_t &pts)
+{
+ {
+ eSingleLock l(m_lock);
+ m_seek_requests.push_back(std::pair<int, pts_t>(relative, pts));
+ }
+ m_event(evtSeek);
+}
+
+void eCueSheet::clear()
+{
+ eSingleLock l(m_lock);
+ m_spans.clear();
+}
+
+void eCueSheet::addSourceSpan(const pts_t &begin, const pts_t &end)
+{
+ {
+ eSingleLock l(m_lock);
+ m_spans.push_back(std::pair<pts_t, pts_t>(begin, end));
+ }
+}
+
+void eCueSheet::commitSpans()
+{
+ m_event(evtSpanChanged);
+}
+
+void eCueSheet::setSkipmode(const pts_t &ratio)
+{
+ {
+ eSingleLock l(m_lock);
+ m_skipmode_ratio = ratio;
+ }
+ m_event(evtSkipmode);
+}
+
+void eCueSheet::setDecodingDemux(iDVBDemux *demux, iTSMPEGDecoder *decoder)
+{
+ m_decoding_demux = demux;
+ m_decoder = decoder;
+}
+
+RESULT eCueSheet::connectEvent(const Slot1<void,int> &event, ePtr<eConnection> &connection)
+{
+ connection = new eConnection(this, m_event.connect(event));
return 0;
}