gst_object_unref (demux);
return res;
- wrong_format:
+ }
+
+ static inline GstClockTime
+ calculate_gsttime (TSPcrOffset * start, guint64 pcr)
+ {
+
+ GstClockTime time = start->gsttime;
+
+ if (start->pcr > pcr)
+ time += PCRTIME_TO_GSTTIME (PCR_MAX_VALUE - start->pcr) +
+ PCRTIME_TO_GSTTIME (pcr);
+ else
+ time += PCRTIME_TO_GSTTIME (pcr - start->pcr);
+
+ return time;
+ }
+
+ static GstFlowReturn
+ gst_ts_demux_parse_pes_header_pts (GstTSDemux * demux,
+ MpegTSPacketizerPacket * packet, guint64 * time)
+ {
+ GstFlowReturn res = GST_FLOW_ERROR;
+ guint8 *data;
+ guint32 length;
+ guint32 psc_stid;
+ guint8 stid;
+ guint16 pesplength;
+ guint8 PES_header_data_length = 0;
+
+ data = packet->payload;
+ length = packet->data_end - data;
+
+ GST_MEMDUMP ("Header buffer", data, MIN (length, 32));
+
+ /* packet_start_code_prefix 24
+ * stream_id 8*/
+ psc_stid = GST_READ_UINT32_BE (data);
+ data += 4;
+ length -= 4;
+ if (G_UNLIKELY ((psc_stid & 0xffffff00) != 0x00000100)) {
+ GST_DEBUG ("WRONG PACKET START CODE! pid: 0x%x", packet->pid);
+ goto discont;
+ }
+ stid = psc_stid & 0x000000ff;
+ GST_LOG ("stream_id:0x%02x", stid);
+
+ /* PES_packet_length 16 */
+ /* FIXME : store the expected pes length somewhere ? */
+ pesplength = GST_READ_UINT16_BE (data);
+ data += 2;
+ length -= 2;
+ GST_LOG ("PES_packet_length:%d", pesplength);
+
+ /* FIXME : Only parse header on streams which require it (see table 2-21) */
+ if (stid != 0xbf) {
+ guint64 pts;
+ guint8 p1, p2;
+ p1 = *data++;
+ p2 = *data++;
+ PES_header_data_length = *data++ + 3;
+ length -= 3;
+
+ GST_LOG ("0x%02x 0x%02x 0x%02x", p1, p2, PES_header_data_length);
+ GST_LOG ("PES header data length:%d", PES_header_data_length);
+
+ /* '10' 2
+ * PES_scrambling_control 2
+ * PES_priority 1
+ * data_alignment_indicator 1
+ * copyright 1
+ * original_or_copy 1 */
+ if (G_UNLIKELY ((p1 & 0xc0) != 0x80)) {
+ GST_WARNING ("p1 >> 6 != 0x2");
+ goto discont;
+ }
+
+ /* PTS_DTS_flags 2
+ * ESCR_flag 1
+ * ES_rate_flag 1
+ * DSM_trick_mode_flag 1
+ * additional_copy_info_flag 1
+ * PES_CRC_flag 1
+ * PES_extension_flag 1*/
+
+ /* PES_header_data_length 8 */
+ if (G_UNLIKELY (length < PES_header_data_length)) {
+ GST_WARNING ("length < PES_header_data_length");
+ goto discont;
+ }
+
+ /* PTS 32 */
+ if ((p2 & 0x80)) { /* PTS */
+ READ_TS (data, pts, discont);
+ length -= 4;
+ *time = pts;
+ res = GST_FLOW_OK;
+ }
+ }
+ discont:
+ return res;
+ }
+
+ /* performs a accurate/key_unit seek */
+ static GstFlowReturn
+ gst_ts_demux_perform_auxiliary_seek (MpegTSBase * base, GstClockTime seektime,
+ TSPcrOffset * pcroffset, gint64 length, gint16 pid, GstSeekFlags flags,
+ payload_parse_keyframe auxiliary_seek_fn)
+ {
+ GstTSDemux *demux = (GstTSDemux *) base;
+ GstFlowReturn res = GST_FLOW_ERROR;
+ gboolean done = FALSE;
+ gboolean found_keyframe = FALSE, found_accurate = FALSE, need_more = TRUE;
+ GstBuffer *buf;
+ MpegTSPacketizerPacket packet;
+ MpegTSPacketizerPacketReturn pret;
+ gint64 offset = pcroffset->offset;
+ gint64 scan_offset = MIN (length, 50 * MPEGTS_MAX_PACKETSIZE);
+ guint32 state = 0xffffffff;
+ TSPcrOffset key_pos = { 0 };
+
+ GST_DEBUG ("auxiliary seek for %" GST_TIME_FORMAT " from offset: %"
+ G_GINT64_FORMAT " in %" G_GINT64_FORMAT " bytes for PID: %d "
+ "%s %s", GST_TIME_ARGS (seektime), pcroffset->offset, length, pid,
+ (flags & GST_SEEK_FLAG_ACCURATE) ? "accurate" : "",
+ (flags & GST_SEEK_FLAG_KEY_UNIT) ? "key_unit" : "");
+
+ mpegts_packetizer_flush (base->packetizer);
+
+ if (base->packetizer->packet_size == MPEGTS_M2TS_PACKETSIZE)
+ offset -= 4;
+
+ while (!done && scan_offset <= length) {
+ res =
+ gst_pad_pull_range (base->sinkpad, offset + scan_offset,
+ 50 * MPEGTS_MAX_PACKETSIZE, &buf);
+ if (res != GST_FLOW_OK)
+ goto beach;
+ mpegts_packetizer_push (base->packetizer, buf);
+
+ while ((!done)
+ && ((pret =
+ mpegts_packetizer_next_packet (base->packetizer,
+ &packet)) != PACKET_NEED_MORE)) {
+ if (G_UNLIKELY (pret == PACKET_BAD))
+ /* bad header, skip the packet */
+ goto next;
+
+ if (packet.payload_unit_start_indicator)
+ GST_DEBUG ("found packet for PID: %d with pcr: %" GST_TIME_FORMAT
+ " at offset: %" G_GINT64_FORMAT, packet.pid,
+ GST_TIME_ARGS (packet.pcr), packet.offset);
+
+ if (packet.payload != NULL && packet.pid == pid) {
+
+ if (packet.payload_unit_start_indicator) {
+ guint64 pts = 0;
+ GstFlowReturn ok =
+ gst_ts_demux_parse_pes_header_pts (demux, &packet, &pts);
+ if (ok == GST_FLOW_OK) {
+ GstClockTime time = calculate_gsttime (pcroffset, pts * 300);
+
+ GST_DEBUG ("packet has PTS: %" GST_TIME_FORMAT,
- GST_TIME_ARGS (time));
++ GST_TIME_ARGS (time));
+
+ if (time <= seektime) {
+ pcroffset->gsttime = time;
+ pcroffset->pcr = packet.pcr;
+ pcroffset->offset = packet.offset;
+ } else
+ found_accurate = TRUE;
+ } else
+ goto next;
+ /* reset state for new packet */
+ state = 0xffffffff;
+ need_more = TRUE;
+ }
+
+ if (auxiliary_seek_fn) {
+ if (need_more) {
+ if (auxiliary_seek_fn (&state, &packet, &need_more)) {
+ found_keyframe = TRUE;
+ key_pos = *pcroffset;
+ GST_DEBUG ("found keyframe: time: %" GST_TIME_FORMAT " pcr: %"
+ GST_TIME_FORMAT " offset %" G_GINT64_FORMAT,
+ GST_TIME_ARGS (pcroffset->gsttime),
+ GST_TIME_ARGS (pcroffset->pcr), pcroffset->offset);
+ }
+ }
+ } else {
+ /* if we don't have a payload parsing function
+ * every frame is a keyframe */
+ found_keyframe = TRUE;
+ }
+ }
+ if (flags & GST_SEEK_FLAG_ACCURATE)
+ done = found_accurate && found_keyframe;
+ else
+ done = found_keyframe;
+ if (done)
+ *pcroffset = key_pos;
+ next:
+ mpegts_packetizer_clear_packet (base->packetizer, &packet);
+ }
+ scan_offset += 50 * MPEGTS_MAX_PACKETSIZE;
+ }
+
+ beach:
+ if (done)
+ res = GST_FLOW_OK;
+ else if (GST_FLOW_OK == res)
+ res = GST_FLOW_CUSTOM_ERROR_1;
+
+ mpegts_packetizer_flush (base->packetizer);
+ return res;
+ }
+
+ static gint
+ TSPcrOffset_find (gconstpointer a, gconstpointer b, gpointer user_data)
+ {
+
+ /* GST_INFO ("a: %" GST_TIME_FORMAT " offset: %" G_GINT64_FORMAT, */
+ /* GST_TIME_ARGS (((TSPcrOffset *) a)->gsttime), ((TSPcrOffset *) a)->offset); */
+ /* GST_INFO ("b: %" GST_TIME_FORMAT " offset: %" G_GINT64_FORMAT, */
+ /* GST_TIME_ARGS (((TSPcrOffset *) b)->gsttime), ((TSPcrOffset *) b)->offset); */
+
+ if (((TSPcrOffset *) a)->gsttime < ((TSPcrOffset *) b)->gsttime)
+ return -1;
+ else if (((TSPcrOffset *) a)->gsttime > ((TSPcrOffset *) b)->gsttime)
+ return 1;
+ else
+ return 0;
+ }
+
+ static GstFlowReturn
+ gst_ts_demux_perform_seek (MpegTSBase * base, GstSegment * segment, guint16 pid)
+ {
+ GstTSDemux *demux = (GstTSDemux *) base;
+ GstFlowReturn res = GST_FLOW_ERROR;
+ int max_loop_cnt, loop_cnt = 0;
+ gint64 seekpos = 0;
+ gint64 time_diff;
+ GstClockTime seektime;
+ TSPcrOffset seekpcroffset, pcr_start, pcr_stop, *tmp;
+
+ max_loop_cnt = (segment->flags & GST_SEEK_FLAG_ACCURATE) ? 25 : 10;
+
+ seektime =
+ MAX (0,
+ segment->last_stop - SEEK_TIMESTAMP_OFFSET) + demux->first_pcr.gsttime;
+ seekpcroffset.gsttime = seektime;
+
+ GST_DEBUG ("seeking to %" GST_TIME_FORMAT, GST_TIME_ARGS (seektime));
+
+ gst_ts_demux_flush_streams (demux);
+
+ if (G_UNLIKELY (!demux->index)) {
+ GST_ERROR ("no index");
+ goto done;
+ }
+
+ /* get the first index entry before the seek position */
+ tmp = gst_util_array_binary_search (demux->index->data, demux->index_size,
+ sizeof (*tmp), TSPcrOffset_find, GST_SEARCH_MODE_BEFORE, &seekpcroffset,
+ NULL);
+
+ if (G_UNLIKELY (!tmp)) {
+ GST_ERROR ("value not found");
+ goto done;
+ }
+
+ pcr_start = *tmp;
+ pcr_stop = *(++tmp);
+
+ if (G_UNLIKELY (!pcr_stop.offset)) {
+ GST_ERROR ("invalid entry");
+ goto done;
+ }
+
+ /* check if the last recorded pcr can be used */
+ if (pcr_start.offset < demux->cur_pcr.offset
+ && demux->cur_pcr.offset < pcr_stop.offset) {
+ demux->cur_pcr.gsttime = calculate_gsttime (&pcr_start, demux->cur_pcr.pcr);
+ if (demux->cur_pcr.gsttime < seekpcroffset.gsttime)
+ pcr_start = demux->cur_pcr;
+ else
+ pcr_stop = demux->cur_pcr;
+ }
+
+ GST_DEBUG ("start %" GST_TIME_FORMAT " offset: %" G_GINT64_FORMAT,
+ GST_TIME_ARGS (pcr_start.gsttime), pcr_start.offset);
+ GST_DEBUG ("stop %" GST_TIME_FORMAT " offset: %" G_GINT64_FORMAT,
+ GST_TIME_ARGS (pcr_stop.gsttime), pcr_stop.offset);
+
+ time_diff = seektime - pcr_start.gsttime;
+ seekpcroffset = pcr_start;
+
+ GST_DEBUG ("cur %" GST_TIME_FORMAT " offset: %" G_GINT64_FORMAT
+ " time diff: %" G_GINT64_FORMAT,
+ GST_TIME_ARGS (demux->cur_pcr.gsttime), demux->cur_pcr.offset, time_diff);
+
+ /* seek loop */
+ while (loop_cnt++ < max_loop_cnt && (time_diff > SEEK_TIMESTAMP_OFFSET >> 1)
+ && (pcr_stop.gsttime - pcr_start.gsttime > SEEK_TIMESTAMP_OFFSET)) {
+ gint64 duration = pcr_stop.gsttime - pcr_start.gsttime;
+ gint64 size = pcr_stop.offset - pcr_start.offset;
+
+ if (loop_cnt & 1)
+ seekpos = pcr_start.offset + (size >> 1);
+ else
+ seekpos =
+ pcr_start.offset + size * ((double) (seektime -
+ pcr_start.gsttime) / duration);
+
+ /* look a litle bit behind */
+ seekpos =
+ MAX (pcr_start.offset + 188, seekpos - 55 * MPEGTS_MAX_PACKETSIZE);
+
+ GST_DEBUG ("looking for time: %" GST_TIME_FORMAT " .. %" GST_TIME_FORMAT
+ " .. %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (pcr_start.gsttime),
+ GST_TIME_ARGS (seektime), GST_TIME_ARGS (pcr_stop.gsttime));
+ GST_DEBUG ("looking in bytes: %" G_GINT64_FORMAT " .. %" G_GINT64_FORMAT
+ " .. %" G_GINT64_FORMAT, pcr_start.offset, seekpos, pcr_stop.offset);
+
+ res =
+ find_pcr_packet (&demux->parent, seekpos, 4000 * MPEGTS_MAX_PACKETSIZE,
+ &seekpcroffset);
+ if (G_UNLIKELY (res == GST_FLOW_UNEXPECTED)) {
+ seekpos =
+ MAX ((gint64) pcr_start.offset,
+ seekpos - 2000 * MPEGTS_MAX_PACKETSIZE) + 188;
+ res =
+ find_pcr_packet (&demux->parent, seekpos,
+ 8000 * MPEGTS_MAX_PACKETSIZE, &seekpcroffset);
+ }
+ if (G_UNLIKELY (res != GST_FLOW_OK)) {
+ GST_WARNING ("seeking failed %s", gst_flow_get_name (res));
+ goto done;
+ }
+
+ seekpcroffset.gsttime = calculate_gsttime (&pcr_start, seekpcroffset.pcr);
+
+ /* validate */
+ if (G_UNLIKELY ((seekpcroffset.gsttime < pcr_start.gsttime) ||
+ (seekpcroffset.gsttime > pcr_stop.gsttime))) {
+ GST_ERROR ("Unexpected timestamp found, seeking failed! %"
+ GST_TIME_FORMAT, GST_TIME_ARGS (seekpcroffset.gsttime));
+ res = GST_FLOW_ERROR;
+ goto done;
+ }
+
+ if (seekpcroffset.gsttime > seektime) {
+ pcr_stop = seekpcroffset;
+ } else {
+ pcr_start = seekpcroffset;
+ }
+ time_diff = seektime - pcr_start.gsttime;
+ GST_DEBUG ("seeking: %" GST_TIME_FORMAT " found: %" GST_TIME_FORMAT
+ " diff = %" G_GINT64_FORMAT, GST_TIME_ARGS (seektime),
+ GST_TIME_ARGS (seekpcroffset.gsttime), time_diff);
+ }
+
+ GST_DEBUG ("seeking finished after %d loops", loop_cnt);
+
+ /* use correct seek position for the auxiliary search */
+ seektime += SEEK_TIMESTAMP_OFFSET;
+
{
- GST_DEBUG_OBJECT (demux, "only query duration on TIME is supported");
- res = FALSE;
+ payload_parse_keyframe keyframe_seek = NULL;
+ MpegTSBaseProgram *program = demux->program;
+ guint64 avg_bitrate, length;
+
+ if (program->streams[pid]) {
+ switch (program->streams[pid]->stream_type) {
+ case ST_VIDEO_MPEG1:
+ case ST_VIDEO_MPEG2:
+ keyframe_seek = gst_tsdemux_has_mpeg2_keyframe;
+ break;
+ case ST_VIDEO_H264:
+ keyframe_seek = gst_tsdemux_has_h264_keyframe;
+ break;
+ case ST_VIDEO_MPEG4:
+ case ST_VIDEO_DIRAC:
+ GST_WARNING ("no payload parser for stream 0x%04x type: 0x%02x", pid,
+ program->streams[pid]->stream_type);
+ break;
+ }
+ } else
+ GST_WARNING ("no stream info for PID: 0x%04x", pid);
+
+ avg_bitrate =
+ (pcr_stop.offset -
+ pcr_start.offset) * 1000 * GST_MSECOND / (pcr_stop.gsttime -
+ pcr_start.gsttime);
+
+ seekpcroffset = pcr_start;
+ /* search in 2500ms for a keyframe */
+ length =
+ MIN (demux->last_pcr.offset - pcr_start.offset,
+ (avg_bitrate * 25) / 10);
+ res =
+ gst_ts_demux_perform_auxiliary_seek (base, seektime, &seekpcroffset,
+ length, pid, segment->flags, keyframe_seek);
+
+ if (res == GST_FLOW_CUSTOM_ERROR_1) {
+ GST_ERROR ("no keyframe found in %" G_GUINT64_FORMAT
+ " bytes starting from %" G_GUINT64_FORMAT, length,
+ seekpcroffset.offset);
+ res = GST_FLOW_ERROR;
+ }
+ if (res != GST_FLOW_OK)
+ goto done;
+ }
+
+
+ /* update seektime to the actual timestamp of the found keyframe */
+ if (segment->flags & GST_SEEK_FLAG_KEY_UNIT)
+ seektime = seekpcroffset.gsttime;
+
+ seektime -= demux->first_pcr.gsttime;
+
+ segment->last_stop = seektime;
+ segment->time = seektime;
+
+ /* we stop at the end */
+ if (segment->stop == -1)
+ segment->stop = demux->first_pcr.gsttime + segment->duration;
+
+ demux->need_newsegment = TRUE;
+ demux->parent.seek_offset = seekpcroffset.offset;
+ GST_DEBUG ("seeked to postion:%" GST_TIME_FORMAT, GST_TIME_ARGS (seektime));
+ res = GST_FLOW_OK;
+
+ done:
+ return res;
+ }
+
+
+ static GstFlowReturn
+ gst_ts_demux_do_seek (MpegTSBase * base, GstEvent * event, guint16 pid)
+ {
+ GstTSDemux *demux = (GstTSDemux *) base;
+ GstFlowReturn res = GST_FLOW_ERROR;
+ gdouble rate;
+ gboolean accurate, flush;
+ GstFormat format;
+ GstSeekFlags flags;
+ GstSeekType start_type, stop_type;
+ gint64 start, stop;
+ GstSegment seeksegment;
+ gboolean update;
+
+ gst_event_parse_seek (event, &rate, &format, &flags, &start_type, &start,
+ &stop_type, &stop);
+
+ if (format != GST_FORMAT_TIME) {
+ goto done;
+ }
+
+ GST_DEBUG ("seek event, rate: %f start: %" GST_TIME_FORMAT
+ " stop: %" GST_TIME_FORMAT, rate, GST_TIME_ARGS (start),
+ GST_TIME_ARGS (stop));
+
+ accurate = flags & GST_SEEK_FLAG_ACCURATE;
+ flush = flags & GST_SEEK_FLAG_FLUSH;
+
+ if (flags & (GST_SEEK_FLAG_SEGMENT | GST_SEEK_FLAG_SKIP)) {
+ GST_WARNING ("seek flags 0x%x are not supported", (int) flags);
goto done;
}
+
+ /* copy segment, we need this because we still need the old
+ * segment when we close the current segment. */
+ memcpy (&seeksegment, &demux->segment, sizeof (GstSegment));
+ /* configure the segment with the seek variables */
+ GST_DEBUG_OBJECT (demux, "configuring seek");
+ GST_DEBUG ("seeksegment: start: %" GST_TIME_FORMAT " stop: %"
+ GST_TIME_FORMAT " time: %" GST_TIME_FORMAT " accum: %" GST_TIME_FORMAT
+ " last_stop: %" GST_TIME_FORMAT " duration: %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (seeksegment.start), GST_TIME_ARGS (seeksegment.stop),
+ GST_TIME_ARGS (seeksegment.time), GST_TIME_ARGS (seeksegment.accum),
+ GST_TIME_ARGS (seeksegment.last_stop),
+ GST_TIME_ARGS (seeksegment.duration));
+ gst_segment_set_seek (&seeksegment, rate, format, flags, start_type, start,
+ stop_type, stop, &update);
+ GST_DEBUG ("seeksegment: start: %" GST_TIME_FORMAT " stop: %"
+ GST_TIME_FORMAT " time: %" GST_TIME_FORMAT " accum: %" GST_TIME_FORMAT
+ " last_stop: %" GST_TIME_FORMAT " duration: %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (seeksegment.start), GST_TIME_ARGS (seeksegment.stop),
+ GST_TIME_ARGS (seeksegment.time), GST_TIME_ARGS (seeksegment.accum),
+ GST_TIME_ARGS (seeksegment.last_stop),
+ GST_TIME_ARGS (seeksegment.duration));
+
+ res = gst_ts_demux_perform_seek (base, &seeksegment, pid);
+ if (G_UNLIKELY (res != GST_FLOW_OK)) {
+ GST_WARNING ("seeking failed %s", gst_flow_get_name (res));
+ goto done;
+ }
+
+ /* commit the new segment */
+ memcpy (&demux->segment, &seeksegment, sizeof (GstSegment));
+
+ if (demux->segment.flags & GST_SEEK_FLAG_SEGMENT) {
+ gst_element_post_message (GST_ELEMENT_CAST (demux),
+ gst_message_new_segment_start (GST_OBJECT_CAST (demux),
+ demux->segment.format, demux->segment.last_stop));
+ }
+
+ done:
+ return res;
}
+ static gboolean
+ gst_ts_demux_srcpad_event (GstPad * pad, GstEvent * event)
+ {
+ gboolean res = TRUE;
+ GstTSDemux *demux = GST_TS_DEMUX (gst_pad_get_parent (pad));
+
+ GST_DEBUG_OBJECT (pad, "Got event %s",
+ gst_event_type_get_name (GST_EVENT_TYPE (event)));
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_SEEK:
+ res = mpegts_base_handle_seek_event ((MpegTSBase *) demux, pad, event);
+ if (!res) {
+ GST_WARNING ("seeking failed");
+ }
+ gst_event_unref (event);
+ break;
+ default:
+ res = gst_pad_event_default (pad, event);
+ }
+
+ gst_object_unref (demux);
+ return res;
+ }
static gboolean
push_event (MpegTSBase * base, GstEvent * event)