+ goto error;
+ }
+
+ ret = canAllocateFrontend(feparm, simulate);
+
+error:
+ if (decremented_fe_usecount)
+ ++(*decremented_fe_usecount);
+ if (decremented_cached_channel_fe_usecount)
+ ++(*decremented_cached_channel_fe_usecount);
+
+ return ret;
+}
+
+bool eDVBResourceManager::canMeasureFrontendInputPower()
+{
+ for (eSmartPtrList<eDVBRegisteredFrontend>::iterator i(m_frontend.begin()); i != m_frontend.end(); ++i)
+ {
+ return i->m_frontend->readInputpower() >= 0;
+ }
+ return false;
+}
+
+class eDVBChannelFilePush: public eFilePushThread
+{
+public:
+ eDVBChannelFilePush() { setIFrameSearch(0); setTimebaseChange(0); }
+ void setIFrameSearch(int enabled) { m_iframe_search = enabled; m_iframe_state = 0; }
+
+ /* "timebase change" is for doing trickmode playback at an exact speed, even when pictures are skipped. */
+ /* you need to set it to 1/16 if you want 16x playback, for example. you need video master sync. */
+ void setTimebaseChange(int ratio) { m_timebase_change = ratio; } /* 16bit fixpoint, 0 for disable */
+protected:
+ int m_iframe_search, m_iframe_state, m_pid;
+ int m_timebase_change;
+ int filterRecordData(const unsigned char *data, int len, size_t ¤t_span_remaining);
+};
+
+int eDVBChannelFilePush::filterRecordData(const unsigned char *_data, int len, size_t ¤t_span_remaining)
+{
+#if 0
+ if (m_timebase_change)
+ {
+ eDebug("timebase change: %d", m_timebase_change);
+ int offset;
+ for (offset = 0; offset < len; offset += 188)
+ {
+ unsigned char *pkt = (unsigned char*)_data + offset;
+ if (pkt[1] & 0x40) /* pusi */
+ {
+ if (pkt[3] & 0x20) // adaption field present?
+ pkt += pkt[4] + 4 + 1; /* skip adaption field and header */
+ else
+ pkt += 4; /* skip header */
+ if (pkt[0] || pkt[1] || (pkt[2] != 1))
+ {
+ eWarning("broken startcode");
+ continue;
+ }
+
+ pts_t pts = 0;
+
+ if (pkt[7] & 0x80) // PTS present?
+ {
+ pts = ((unsigned long long)(pkt[ 9]&0xE)) << 29;
+ pts |= ((unsigned long long)(pkt[10]&0xFF)) << 22;
+ pts |= ((unsigned long long)(pkt[11]&0xFE)) << 14;
+ pts |= ((unsigned long long)(pkt[12]&0xFF)) << 7;
+ pts |= ((unsigned long long)(pkt[13]&0xFE)) >> 1;
+
+#if 0
+ off_t off = 0;
+ RESULT r = m_tstools.fixupPTS(off, pts);
+ if (r)
+ eWarning("fixup PTS while trickmode playback failed.\n");
+#endif
+
+ int sec = pts / 90000;
+ int frm = pts % 90000;
+ int min = sec / 60;
+ sec %= 60;
+ int hr = min / 60;
+ min %= 60;
+ int d = hr / 24;
+ hr %= 24;
+
+// eDebug("original, fixed pts: %016llx %d:%02d:%02d:%02d:%05d", pts, d, hr, min, sec, frm);
+
+ pts += 0x80000000LL;
+ pts *= m_timebase_change;
+ pts >>= 16;
+
+ sec = pts / 90000;
+ frm = pts % 90000;
+ min = sec / 60;
+ sec %= 60;
+ hr = min / 60;
+ min %= 60;
+ d = hr / 24;
+ hr %= 24;
+
+// eDebug("new pts (after timebase change): %016llx %d:%02d:%02d:%02d:%05d", pts, d, hr, min, sec, frm);
+
+ pkt[9] &= ~0xE;
+ pkt[10] = 0;
+ pkt[11] &= ~1;
+ pkt[12] = 0;
+ pkt[13] &= ~1;
+
+ pkt[9] |= (pts >> 29) & 0xE;
+ pkt[10] |= (pts >> 22) & 0xFF;
+ pkt[11] |= (pts >> 14) & 0xFE;
+ pkt[12] |= (pts >> 7) & 0xFF;
+ pkt[13] |= (pts << 1) & 0xFE;
+ }
+ }
+ }
+ }
+#endif
+
+#if 0
+ if (!m_iframe_search)
+ return len;
+
+ unsigned char *data = (unsigned char*)_data; /* remove that const. we know what we are doing. */
+
+// eDebug("filterRecordData, size=%d (mod 188=%d), first byte is %02x", len, len %188, data[0]);
+
+ unsigned char *d = data;
+ while ((d + 3 < data + len) && (d = (unsigned char*)memmem(d, data + len - d, "\x00\x00\x01", 3)))
+ {
+ int offset = d - data;
+ int ts_offset = offset - offset % 188; /* offset to the start of TS packet */
+ unsigned char *ts = data + ts_offset;
+ int pid = ((ts[1] << 8) | ts[2]) & 0x1FFF;
+
+ if ((d[3] == 0 || d[3] == 0x09 && d[-1] == 0 && (ts[1] & 0x40)) && (m_pid == pid)) /* picture start */
+ {
+ int picture_type = (d[3]==0 ? (d[5] >> 3) & 7 : (d[4] >> 5) + 1);
+ d += 4;
+
+// eDebug("%d-frame at %d, offset in TS packet: %d, pid=%04x", picture_type, offset, offset % 188, pid);
+
+ if (m_iframe_state == 1)
+ {
+ /* we are allowing data, and stop allowing data on the next frame.
+ we now found a frame. so stop here. */
+ memset(data + offset, 0, 188 - (offset%188)); /* zero out rest of TS packet */
+ current_span_remaining = 0;
+ m_iframe_state = 0;
+ unsigned char *fts = ts + 188;
+ while (fts < (data + len))
+ {
+ fts[1] |= 0x1f;
+ fts[2] |= 0xff; /* drop packet */
+ fts += 188;
+ }
+
+ return len; // ts_offset + 188; /* deliver this packet, but not more. */
+ } else
+ {
+ if (picture_type != 1) /* we are only interested in I frames */
+ continue;
+
+ unsigned char *fts = data;
+ while (fts < ts)
+ {
+ fts[1] |= 0x1f;
+ fts[2] |= 0xff; /* drop packet */
+
+ fts += 188;
+ }
+
+ m_iframe_state = 1;
+ }
+ } else if ((d[3] & 0xF0) == 0xE0) /* video stream */
+ {
+ /* verify that this is actually a PES header, not just some ES data */
+ if (ts[1] & 0x40) /* PUSI set */
+ {
+ int payload_start = 4;
+ if (ts[3] & 0x20) /* adaptation field present */
+ payload_start += ts[4] + 1; /* skip AF */
+ if (payload_start == (offset%188)) /* the 00 00 01 should be directly at the payload start, otherwise it's not a PES header */
+ {
+ if (m_pid != pid)
+ {
+ eDebug("now locked to pid %04x (%02x %02x %02x %02x)", pid, ts[0], ts[1], ts[2], ts[3]);
+ m_pid = pid;
+ }
+ }
+ }
+// m_pid = 0x6e;
+ d += 4;
+ } else
+ d += 4; /* ignore */
+ }
+
+ if (m_iframe_state == 1)
+ return len;
+ else
+ return 0; /* we need find an iframe first */
+#else
+ return len;
+#endif
+}
+
+DEFINE_REF(eDVBChannel);
+
+eDVBChannel::eDVBChannel(eDVBResourceManager *mgr, eDVBAllocatedFrontend *frontend): m_state(state_idle), m_mgr(mgr)
+{
+ m_frontend = frontend;
+
+ m_pvr_thread = 0;
+
+ m_skipmode_n = m_skipmode_m = m_skipmode_frames = 0;
+
+ if (m_frontend)
+ m_frontend->get().connectStateChange(slot(*this, &eDVBChannel::frontendStateChanged), m_conn_frontendStateChanged);
+}
+
+eDVBChannel::~eDVBChannel()
+{
+ if (m_channel_id)
+ m_mgr->removeChannel(this);
+
+ stopFile();
+}
+
+void eDVBChannel::frontendStateChanged(iDVBFrontend*fe)
+{
+ int state, ourstate = 0;
+
+ /* if we are already in shutdown, don't change state. */
+ if (m_state == state_release)
+ return;
+
+ if (fe->getState(state))
+ return;
+
+ if (state == iDVBFrontend::stateLock)
+ {
+ eDebug("OURSTATE: ok");
+ ourstate = state_ok;
+ } else if (state == iDVBFrontend::stateTuning)
+ {
+ eDebug("OURSTATE: tuning");
+ ourstate = state_tuning;
+ } else if (state == iDVBFrontend::stateLostLock)
+ {
+ /* on managed channels, we try to retune in order to re-acquire lock. */
+ if (m_current_frontend_parameters)
+ {
+ eDebug("OURSTATE: lost lock, trying to retune");
+ ourstate = state_tuning;
+ m_frontend->get().tune(*m_current_frontend_parameters);
+ } 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");
+ ourstate = state_failed;
+ } else
+ eFatal("state unknown");
+
+ if (ourstate != m_state)
+ {
+ m_state = ourstate;
+ m_stateChanged(this);
+ }
+}
+
+void eDVBChannel::pvrEvent(int event)
+{
+ switch (event)
+ {
+ case eFilePushThread::evtEOF:
+ 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)
+{
+ /* we might end up here if playing failed or stopped, but the client hasn't (yet) noted. */
+ if (!m_pvr_thread)
+ return;
+ switch (event)
+ {
+ case eCueSheet::evtSeek:
+ eDebug("seek.");
+ flushPVR(m_cue->m_decoding_demux);
+ break;
+ case eCueSheet::evtSkipmode:
+ {
+ {
+ m_cue->m_lock.WrLock();
+ m_cue->m_seek_requests.push_back(std::pair<int, pts_t>(1, 0)); /* resync */
+ m_cue->m_lock.Unlock();
+ eRdLocker l(m_cue->m_lock);
+ 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;
+ m_skipmode_frames = m_cue->m_skipmode_ratio / 90000;
+ m_skipmode_frames_remainder = 0;
+
+ 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_frames = m_skipmode_n = m_skipmode_m = 0;
+ }
+ } else
+ {
+ eDebug("skipmode ratio is 0, normal play");
+ m_skipmode_frames = m_skipmode_n = m_skipmode_m = 0;
+ }
+ }
+ m_pvr_thread->setIFrameSearch(m_skipmode_n != 0);
+ if (m_cue->m_skipmode_ratio != 0)
+ m_pvr_thread->setTimebaseChange(0x10000 * 9000 / (m_cue->m_skipmode_ratio / 10)); /* negative values are also ok */
+ else
+ m_pvr_thread->setTimebaseChange(0); /* normal playback */
+ eDebug("flush pvr");
+ flushPVR(m_cue->m_decoding_demux);
+ eDebug("done");
+ 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, -1) || m_tstools.getOffset(offset_out, pts_out, 1))
+ {
+ 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;
+ }
+ }
+}
+
+ /* align toward zero */
+static inline long long align(long long x, int align)
+{
+ int sign = x < 0;
+
+ if (sign)
+ x = -x;
+
+ x -= x % align;
+
+ if (sign)
+ x = -x;
+
+ return x;
+}
+
+ /* align toward zero */
+static inline long long align_with_len(long long x, int align, size_t &len)
+{
+ int sign = x < 0;
+
+ if (sign)
+ x = -x;
+
+ x -= x % align;
+ len += x % align;
+
+ if (sign)
+ x = -x;
+
+ return x;
+}
+
+ /* remember, this gets called from another thread. */
+void eDVBChannel::getNextSourceSpan(off_t current_offset, size_t bytes_read, off_t &start, size_t &size)
+{
+ const int blocksize = 188;
+ unsigned int max = align(10*1024*1024, blocksize);
+ current_offset = align(current_offset, blocksize);
+
+ if (!m_cue)
+ {
+ eDebug("no cue sheet. forcing normal play");
+ start = current_offset;
+ size = max;
+ return;
+ }
+
+ m_cue->m_lock.RdLock();
+ if (!m_cue->m_decoding_demux)
+ {
+ start = current_offset;
+ size = max;
+ eDebug("getNextSourceSpan, no decoding demux. forcing normal play");
+ m_cue->m_lock.Unlock();
+ return;
+ }
+
+ if (m_skipmode_n)
+ {
+ eDebug("skipmode %d:%d (x%d)", m_skipmode_m, m_skipmode_n, m_skipmode_frames);
+ max = align(m_skipmode_n, blocksize);
+ }
+
+ eDebug("getNextSourceSpan, current offset is %08llx, m_skipmode_m = %d!", current_offset, m_skipmode_m);
+
+ int frame_skip_success = 0;
+
+ if (m_skipmode_m)
+ {
+ int frames_to_skip = m_skipmode_frames + m_skipmode_frames_remainder;
+ eDebug("we are at %llx, and we try to skip %d+%d frames from here", current_offset, m_skipmode_frames, m_skipmode_frames_remainder);
+ size_t iframe_len;
+ off_t iframe_start = current_offset;
+ int frames_skipped = frames_to_skip;
+ if (!m_tstools.findNextPicture(iframe_start, iframe_len, frames_skipped))
+ {
+ m_skipmode_frames_remainder = frames_to_skip - frames_skipped;
+ eDebug("successfully skipped %d (out of %d, rem now %d) frames.", frames_skipped, frames_to_skip, m_skipmode_frames_remainder);
+ current_offset = align_with_len(iframe_start, blocksize, iframe_len);
+ max = align(iframe_len + 187, blocksize);
+ frame_skip_success = 1;
+ } else
+ {
+ m_skipmode_frames_remainder = 0;
+ eDebug("frame skipping failed, reverting to byte-skipping");
+ }