Skip to content
Snippets Groups Projects
mpegts.c 53.4 KiB
Newer Older
  • Learn to ignore specific revisions
  •                     if (code != 0x1bc && code != 0x1bf && /* program_stream_map, private_stream_2 */
                            code != 0x1f0 && code != 0x1f1 && /* ECM, EMM */
                            code != 0x1ff && code != 0x1f2 && /* program_stream_directory, DSMCC_stream */
                            code != 0x1f8) {                  /* ITU-T Rec. H.222.1 type E stream */
    
                            pes->state = MPEGTS_PESHEADER;
    
                            if (pes->st->codec->codec_id == CODEC_ID_NONE) {
                                dprintf(pes->stream, "pid=%x stream_type=%x probing\n",
                                        pes->pid, pes->stream_type);
                                pes->st->codec->codec_id = CODEC_ID_PROBE;
                            }
    
                        } else {
                            pes->state = MPEGTS_PAYLOAD;
                            pes->data_index = 0;
                        }
    
                    } else {
                        /* otherwise, it should be a table */
                        /* skip packet */
                    skip:
    
                        continue;
                    }
                }
                break;
                /**********************************************/
                /* PES packing parsing */
    
            case MPEGTS_PESHEADER:
                len = PES_HEADER_SIZE - pes->data_index;
                if (len < 0)
                    return -1;
                if (len > buf_size)
                    len = buf_size;
                memcpy(pes->header + pes->data_index, p, len);
                pes->data_index += len;
                p += len;
                buf_size -= len;
                if (pes->data_index == PES_HEADER_SIZE) {
                    pes->pes_header_size = pes->header[8] + 9;
                    pes->state = MPEGTS_PESHEADER_FILL;
                }
                break;
    
            case MPEGTS_PESHEADER_FILL:
    
                if (len < 0)
    
                if (len > buf_size)
                    len = buf_size;
                memcpy(pes->header + pes->data_index, p, len);
                pes->data_index += len;
    
                if (pes->data_index == pes->pes_header_size) {
                    const uint8_t *r;
    
    
                    flags = pes->header[7];
                    r = pes->header + 9;
                    pes->pts = AV_NOPTS_VALUE;
                    pes->dts = AV_NOPTS_VALUE;
                    if ((flags & 0xc0) == 0x80) {
    
                        pes->dts = pes->pts = get_pts(r);
    
                        r += 5;
                    } else if ((flags & 0xc0) == 0xc0) {
                        pes->pts = get_pts(r);
                        r += 5;
                        pes->dts = get_pts(r);
                        r += 5;
                    }
    
                    pes->extended_stream_id = -1;
                    if (flags & 0x01) { /* PES extension */
                        pes_ext = *r++;
                        /* Skip PES private data, program packet sequence counter and P-STD buffer */
                        skip = (pes_ext >> 4) & 0xb;
                        skip += skip & 0x9;
                        r += skip;
                        if ((pes_ext & 0x41) == 0x01 &&
                            (r + 2) <= (pes->header + pes->pes_header_size)) {
                            /* PES extension 2 */
                            if ((r[0] & 0x7f) > 0 && (r[1] & 0x80) == 0)
                                pes->extended_stream_id = r[1];
                        }
                    }
    
                    /* we got the full header. We parse it and get the payload */
                    pes->state = MPEGTS_PAYLOAD;
    
                    pes->data_index = 0;
    
                if (buf_size > 0 && pes->buffer) {
    
                    if (pes->data_index+buf_size > pes->total_size) {
                        new_pes_packet(pes, ts->pkt);
                        pes->total_size = MAX_PES_PAYLOAD;
                        pes->buffer = av_malloc(pes->total_size+FF_INPUT_BUFFER_PADDING_SIZE);
                        if (!pes->buffer)
    
                            return AVERROR(ENOMEM);
    
                    memcpy(pes->buffer+pes->data_index, p, buf_size);
                    pes->data_index += buf_size;
    
                }
                buf_size = 0;
                break;
            case MPEGTS_SKIP:
                buf_size = 0;
                break;
            }
        }
    
    static PESContext *add_pes_stream(MpegTSContext *ts, int pid, int pcr_pid, int stream_type)
    
    {
        MpegTSFilter *tss;
        PESContext *pes;
    
        /* if no pid found, then add a pid context */
        pes = av_mallocz(sizeof(PESContext));
        if (!pes)
    
        pes->state = MPEGTS_SKIP;
    
        pes->pts = AV_NOPTS_VALUE;
        pes->dts = AV_NOPTS_VALUE;
    
        tss = mpegts_open_pes_filter(ts, pid, mpegts_push_data, pes);
        if (!tss) {
            av_free(pes);
    
    static int handle_packet(MpegTSContext *ts, const uint8_t *packet)
    
        MpegTSFilter *tss;
        int len, pid, cc, cc_ok, afc, is_start;
        const uint8_t *p, *p_end;
    
        pid = AV_RB16(packet + 1) & 0x1fff;
    
        is_start = packet[1] & 0x40;
        tss = ts->pids[pid];
        if (ts->auto_guess && tss == NULL && is_start) {
    
    
        /* continuity check (currently not used) */
        cc = (packet[3] & 0xf);
        cc_ok = (tss->last_cc < 0) || ((((tss->last_cc + 1) & 0x0f) == cc));
        tss->last_cc = cc;
    
        /* skip adaptation field */
        afc = (packet[3] >> 4) & 3;
        p = packet + 4;
        if (afc == 0) /* reserved value */
    
        if (afc == 3) {
            /* skip adapation field */
            p += p[0] + 1;
        }
        /* if past the end of packet, ignore */
        p_end = packet + TS_PACKET_SIZE;
        if (p >= p_end)
    
        pos = url_ftell(ts->stream->pb);
        ts->pos47= pos % ts->raw_packet_size;
    
        if (tss->type == MPEGTS_SECTION) {
            if (is_start) {
                /* pointer field present */
                len = *p++;
                if (p + len > p_end)
    
                    write_section_data(s, tss,
    
                    /* check whether filter has been closed */
                    if (!ts->pids[pid])
    
                    write_section_data(s, tss,
    
                    write_section_data(s, tss,
    
            // Note: The position here points actually behind the current packet.
    
            if ((ret = tss->u.pes_filter.pes_cb(tss, p, p_end - p, is_start,
                                                pos - ts->raw_packet_size)) < 0)
                return ret;
    
    /* XXX: try to find a better synchro over several packets (use
       get_packet_size() ?) */
    
    static int mpegts_resync(AVFormatContext *s)
    
        int c, i;
    
        for(i = 0;i < MAX_RESYNC_SIZE; i++) {
            c = url_fgetc(pb);
            if (c < 0)
                return -1;
            if (c == 0x47) {
                url_fseek(pb, -1, SEEK_CUR);
                return 0;
            }
        }
    
        av_log(s, AV_LOG_ERROR, "max resync size reached, could not find sync byte\n");
    
    /* return -1 if error or EOF. Return 0 if OK. */
    
    static int read_packet(AVFormatContext *s, uint8_t *buf, int raw_packet_size)
    
        int skip, len;
    
        for(;;) {
            len = get_buffer(pb, buf, TS_PACKET_SIZE);
            if (len != TS_PACKET_SIZE)
    
            /* check paquet sync byte */
            if (buf[0] != 0x47) {
                /* find a new packet start */
                url_fseek(pb, -TS_PACKET_SIZE, SEEK_CUR);
    
                if (mpegts_resync(s) < 0)
                    return AVERROR(EAGAIN);
    
                else
                    continue;
            } else {
                skip = raw_packet_size - TS_PACKET_SIZE;
                if (skip > 0)
                    url_fskip(pb, skip);
                break;
            }
        }
        return 0;
    }
    
    static int handle_packets(MpegTSContext *ts, int nb_packets)
    {
        AVFormatContext *s = ts->stream;
        uint8_t packet[TS_PACKET_SIZE];
        int packet_num, ret;
    
                break;
            packet_num++;
            if (nb_packets != 0 && packet_num >= nb_packets)
                break;
    
            ret = read_packet(s, packet, ts->raw_packet_size);
    
            ret = handle_packet(ts, packet);
            if (ret != 0)
                return ret;
    
        const int size= p->buf_size;
    
        int score, fec_score, dvhs_score;
    
        int check_count= size / TS_FEC_PACKET_SIZE;
    
    #define CHECK_COUNT 10
    
        score     = analyze(p->buf, TS_PACKET_SIZE     *check_count, TS_PACKET_SIZE     , NULL)*CHECK_COUNT/check_count;
        dvhs_score= analyze(p->buf, TS_DVHS_PACKET_SIZE*check_count, TS_DVHS_PACKET_SIZE, NULL)*CHECK_COUNT/check_count;
        fec_score = analyze(p->buf, TS_FEC_PACKET_SIZE *check_count, TS_FEC_PACKET_SIZE , NULL)*CHECK_COUNT/check_count;
    
    //    av_log(NULL, AV_LOG_DEBUG, "score: %d, dvhs_score: %d, fec_score: %d \n", score, dvhs_score, fec_score);
    
    // we need a clear definition for the returned score otherwise things will become messy sooner or later
    
        if     (score > fec_score && score > dvhs_score && score > 6) return AVPROBE_SCORE_MAX + score     - CHECK_COUNT;
        else if(dvhs_score > score && dvhs_score > fec_score && dvhs_score > 6) return AVPROBE_SCORE_MAX + dvhs_score  - CHECK_COUNT;
    
        else if(                 fec_score > 6) return AVPROBE_SCORE_MAX + fec_score - CHECK_COUNT;
        else                                    return -1;
    
    #else
        /* only use the extension for safer guess */
        if (match_ext(p->filename, "ts"))
            return AVPROBE_SCORE_MAX;
        else
            return 0;
    #endif
    
    Diego Biurrun's avatar
    Diego Biurrun committed
    /* return the 90kHz PCR and the extension for the 27MHz PCR. return
    
    static int parse_pcr(int64_t *ppcr_high, int *ppcr_low,
    
                         const uint8_t *packet)
    {
        int afc, len, flags;
        const uint8_t *p;
        unsigned int v;
    
        afc = (packet[3] >> 4) & 3;
        if (afc <= 1)
            return -1;
        p = packet + 4;
        len = p[0];
        p++;
        if (len == 0)
            return -1;
        flags = *p++;
        len--;
        if (!(flags & 0x10))
            return -1;
        if (len < 6)
            return -1;
    
        v = AV_RB32(p);
    
        *ppcr_high = ((int64_t)v << 1) | (p[4] >> 7);
        *ppcr_low = ((p[4] & 1) << 8) | p[5];
        return 0;
    }
    
    
    static int mpegts_read_header(AVFormatContext *s,
                                  AVFormatParameters *ap)
    {
        MpegTSContext *ts = s->priv_data;
    
    Michael Niedermayer's avatar
    Michael Niedermayer committed
        int len;
    
        if (ap) {
            ts->mpeg2ts_compute_pcr = ap->mpeg2ts_compute_pcr;
    
            if(ap->mpeg2ts_raw){
                av_log(s, AV_LOG_ERROR, "use mpegtsraw_demuxer!\n");
                return -1;
            }
    
        /* read the first 1024 bytes to get packet size */
        pos = url_ftell(pb);
        len = get_buffer(pb, buf, sizeof(buf));
        if (len != sizeof(buf))
            goto fail;
        ts->raw_packet_size = get_packet_size(buf, sizeof(buf));
        if (ts->raw_packet_size <= 0)
            goto fail;
    
    Benoit Fouet's avatar
    Benoit Fouet committed
            /* first do a scaning to get all the services */
            url_fseek(pb, pos, SEEK_SET);
    
    
            mpegts_open_section_filter(ts, SDT_PID, sdt_cb, ts, 1);
    
            mpegts_open_section_filter(ts, PAT_PID, pat_cb, ts, 1);
    
            handle_packets(ts, s->probesize / ts->raw_packet_size);
    
    Benoit Fouet's avatar
    Benoit Fouet committed
            /* if could not find service, enable auto_guess */
    
    Benoit Fouet's avatar
    Benoit Fouet committed
            ts->auto_guess = 1;
    
            dprintf(ts->stream, "tuning done\n");
    
    
            s->ctx_flags |= AVFMTCTX_NOHEADER;
        } else {
            AVStream *st;
            int pcr_pid, pid, nb_packets, nb_pcrs, ret, pcr_l;
            int64_t pcrs[2], pcr_h;
            int packet_count[2];
            uint8_t packet[TS_PACKET_SIZE];
    
            av_set_pts_info(st, 60, 1, 27000000);
    
            st->codec->codec_type = CODEC_TYPE_DATA;
            st->codec->codec_id = CODEC_ID_MPEG2TS;
    
            /* we iterate until we find two PCRs to estimate the bitrate */
            pcr_pid = -1;
            nb_pcrs = 0;
            nb_packets = 0;
            for(;;) {
    
                ret = read_packet(s, packet, ts->raw_packet_size);
    
                pid = AV_RB16(packet + 1) & 0x1fff;
    
                if ((pcr_pid == -1 || pcr_pid == pid) &&
                    parse_pcr(&pcr_h, &pcr_l, packet) == 0) {
                    pcr_pid = pid;
                    packet_count[nb_pcrs] = nb_packets;
                    pcrs[nb_pcrs] = pcr_h * 300 + pcr_l;
                    nb_pcrs++;
                    if (nb_pcrs >= 2)
                        break;
                }
                nb_packets++;
            }
    
            /* NOTE1: the bitrate is computed without the FEC */
            /* NOTE2: it is only the bitrate of the start of the stream */
            ts->pcr_incr = (pcrs[1] - pcrs[0]) / (packet_count[1] - packet_count[0]);
            ts->cur_pcr = pcrs[0] - ts->pcr_incr * packet_count[0];
            s->bit_rate = (TS_PACKET_SIZE * 8) * 27e6 / ts->pcr_incr;
    
            st->start_time = ts->cur_pcr;
    
            av_log(ts->stream, AV_LOG_DEBUG, "start=%0.3f pcr=%0.3f incr=%d\n",
    
                   st->start_time / 1000000.0, pcrs[0] / 27e6, ts->pcr_incr);
    
    #define MAX_PACKET_READAHEAD ((128 * 1024) / 188)
    
    static int mpegts_raw_read_packet(AVFormatContext *s,
                                      AVPacket *pkt)
    {
        MpegTSContext *ts = s->priv_data;
        int ret, i;
        int64_t pcr_h, next_pcr_h, pos;
        int pcr_l, next_pcr_l;
        uint8_t pcr_buf[12];
    
        if (av_new_packet(pkt, TS_PACKET_SIZE) < 0)
    
        pkt->pos= url_ftell(s->pb);
    
        ret = read_packet(s, pkt->data, ts->raw_packet_size);
    
        if (ret < 0) {
            av_free_packet(pkt);
            return ret;
        }
        if (ts->mpeg2ts_compute_pcr) {
            /* compute exact PCR for each packet */
            if (parse_pcr(&pcr_h, &pcr_l, pkt->data) == 0) {
                /* we read the next PCR (XXX: optimize it by using a bigger buffer */
    
                    url_fseek(s->pb, pos + i * ts->raw_packet_size, SEEK_SET);
                    get_buffer(s->pb, pcr_buf, 12);
    
                    if (parse_pcr(&next_pcr_h, &next_pcr_l, pcr_buf) == 0) {
                        /* XXX: not precise enough */
    
                        ts->pcr_incr = ((next_pcr_h - pcr_h) * 300 + (next_pcr_l - pcr_l)) /
    
                url_fseek(s->pb, pos, SEEK_SET);
    
                /* no next PCR found: we use previous increment */
                ts->cur_pcr = pcr_h * 300 + pcr_l;
            }
            pkt->pts = ts->cur_pcr;
            pkt->duration = ts->pcr_incr;
            ts->cur_pcr += ts->pcr_incr;
        }
        pkt->stream_index = 0;
        return 0;
    }
    
    
    static int mpegts_read_packet(AVFormatContext *s,
                                  AVPacket *pkt)
    {
        MpegTSContext *ts = s->priv_data;
    
        int ret, i;
    
        if (url_ftell(s->pb) != ts->last_pos) {
            /* seek detected, flush pes buffer */
            for (i = 0; i < NB_PID_MAX; i++) {
                if (ts->pids[i] && ts->pids[i]->type == MPEGTS_PES) {
                    PESContext *pes = ts->pids[i]->u.pes_filter.opaque;
                    av_freep(&pes->buffer);
                    pes->data_index = 0;
                    pes->state = MPEGTS_SKIP; /* skip until pes header */
                }
            }
        }
    
        ret = handle_packets(ts, 0);
        if (ret < 0) {
            /* flush pes data left */
            for (i = 0; i < NB_PID_MAX; i++) {
                if (ts->pids[i] && ts->pids[i]->type == MPEGTS_PES) {
                    PESContext *pes = ts->pids[i]->u.pes_filter.opaque;
                    if (pes->state == MPEGTS_PAYLOAD && pes->data_index > 0) {
                        new_pes_packet(pes, pkt);
    
                        ret = 0;
                        break;
                    }
                }
            }
        }
    
        ts->last_pos = url_ftell(s->pb);
    
        return ret;
    
    }
    
    static int mpegts_read_close(AVFormatContext *s)
    {
        MpegTSContext *ts = s->priv_data;
        int i;
    
    Michael Niedermayer's avatar
    Michael Niedermayer committed
    
        clear_programs(ts);
    
    
        for(i=0;i<NB_PID_MAX;i++)
    
            if (ts->pids[i]) mpegts_close_filter(ts, ts->pids[i]);
    
    static int64_t mpegts_get_pcr(AVFormatContext *s, int stream_index,
    
                                  int64_t *ppos, int64_t pos_limit)
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
    {
        MpegTSContext *ts = s->priv_data;
        int64_t pos, timestamp;
        uint8_t buf[TS_PACKET_SIZE];
    
        int pcr_l, pcr_pid = ((PESContext*)s->streams[stream_index]->priv_data)->pcr_pid;
    
        pos = ((*ppos  + ts->raw_packet_size - 1 - ts->pos47) / ts->raw_packet_size) * ts->raw_packet_size + ts->pos47;
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
        if (find_next) {
            for(;;) {
    
                url_fseek(s->pb, pos, SEEK_SET);
                if (get_buffer(s->pb, buf, TS_PACKET_SIZE) != TS_PACKET_SIZE)
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
                    return AV_NOPTS_VALUE;
    
                if ((pcr_pid < 0 || (AV_RB16(buf + 1) & 0x1fff) == pcr_pid) &&
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
                    parse_pcr(&timestamp, &pcr_l, buf) == 0) {
                    break;
                }
                pos += ts->raw_packet_size;
            }
        } else {
            for(;;) {
                pos -= ts->raw_packet_size;
                if (pos < 0)
                    return AV_NOPTS_VALUE;
    
                url_fseek(s->pb, pos, SEEK_SET);
                if (get_buffer(s->pb, buf, TS_PACKET_SIZE) != TS_PACKET_SIZE)
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
                    return AV_NOPTS_VALUE;
    
                if ((pcr_pid < 0 || (AV_RB16(buf + 1) & 0x1fff) == pcr_pid) &&
    
    Fabrice Bellard's avatar
    Fabrice Bellard committed
                    parse_pcr(&timestamp, &pcr_l, buf) == 0) {
                    break;
                }
            }
        }
        *ppos = pos;
    
    
    static int read_seek2(AVFormatContext *s,
                          int stream_index,
                          int64_t min_ts,
                          int64_t target_ts,
                          int64_t max_ts,
                          int flags)
    
        int64_t ts_ret, ts_adj;
        int stream_index_gen_search;
        AVStream *st;
        AVParserState *backup;
    
        // detect direction of seeking for search purposes
    
        flags |= (target_ts - min_ts > (uint64_t)(max_ts - target_ts)) ?
                 AVSEEK_FLAG_BACKWARD : 0;
    
            // use position directly, we will search starting from it
    
            // search for some position with good timestamp match
            if (stream_index < 0) {
    
                stream_index_gen_search = av_find_default_stream_index(s);
                if (stream_index_gen_search < 0) {
                    ff_restore_parser_state(s, backup);
                    return -1;
                }
    
                st = s->streams[stream_index_gen_search];
    
                // timestamp for default must be expressed in AV_TIME_BASE units
                ts_adj = av_rescale(target_ts,
                                    st->time_base.den,
                                    AV_TIME_BASE * (int64_t)st->time_base.num);
    
            } else {
                ts_adj = target_ts;
                stream_index_gen_search = stream_index;
            }
            pos = av_gen_search(s, stream_index_gen_search, ts_adj,
                                0, INT64_MAX, -1,
                                AV_NOPTS_VALUE,
                                AV_NOPTS_VALUE,
                                flags, &ts_ret, mpegts_get_pcr);
            if (pos < 0) {
                ff_restore_parser_state(s, backup);
    
        // search for actual matching keyframe/starting position for all streams
        if (ff_gen_syncpoint_search(s, stream_index, pos,
                                    min_ts, target_ts, max_ts,
                                    flags) < 0) {
    
            ff_restore_parser_state(s, backup);
            return -1;
        }
    
        ff_free_parser_state(s, backup);
    
    static int read_seek(AVFormatContext *s, int stream_index, int64_t target_ts, int flags)
    {
        int ret;
        if (flags & AVSEEK_FLAG_BACKWARD) {
    
            flags &= ~AVSEEK_FLAG_BACKWARD;
            ret = read_seek2(s, stream_index, INT64_MIN, target_ts, target_ts, flags);
            if (ret < 0)
                // for compatibility reasons, seek to the best-fitting timestamp
                ret = read_seek2(s, stream_index, INT64_MIN, target_ts, INT64_MAX, flags);
    
        } else {
            ret = read_seek2(s, stream_index, target_ts, target_ts, INT64_MAX, flags);
    
            if (ret < 0)
                // for compatibility reasons, seek to the best-fitting timestamp
    
                ret = read_seek2(s, stream_index, INT64_MIN, target_ts, INT64_MAX, flags);
        }
        return ret;
    }
    
    
    #else
    
    static int read_seek(AVFormatContext *s, int stream_index, int64_t target_ts, int flags){
        MpegTSContext *ts = s->priv_data;
        uint8_t buf[TS_PACKET_SIZE];
        int64_t pos;
    
        if(av_seek_frame_binary(s, stream_index, target_ts, flags) < 0)
            return -1;
    
        pos= url_ftell(s->pb);
    
        for(;;) {
            url_fseek(s->pb, pos, SEEK_SET);
            if (get_buffer(s->pb, buf, TS_PACKET_SIZE) != TS_PACKET_SIZE)
                return -1;
    //        pid = AV_RB16(buf + 1) & 0x1fff;
            if(buf[1] & 0x40) break;
            pos += ts->raw_packet_size;
        }
        url_fseek(s->pb, pos, SEEK_SET);
    
        return 0;
    }
    
    #endif
    
    
    /**************************************************************/
    /* parsing functions - called from other demuxers such as RTP */
    
    MpegTSContext *mpegts_parse_open(AVFormatContext *s)
    {
        MpegTSContext *ts;
    
        ts = av_mallocz(sizeof(MpegTSContext));
        if (!ts)
            return NULL;
        /* no stream case, currently used by RTP */
        ts->raw_packet_size = TS_PACKET_SIZE;
        ts->stream = s;
        ts->auto_guess = 1;
        return ts;
    }
    
    /* return the consumed length if a packet was output, or -1 if no
       packet is output */
    int mpegts_parse_packet(MpegTSContext *ts, AVPacket *pkt,
                            const uint8_t *buf, int len)
    {
        int len1;
    
        len1 = len;
        ts->pkt = pkt;
        ts->stop_parse = 0;
        for(;;) {
    
                break;
            if (len < TS_PACKET_SIZE)
                return -1;
            if (buf[0] != 0x47) {
    
                len--;
            } else {
                handle_packet(ts, buf);
                buf += TS_PACKET_SIZE;
                len -= TS_PACKET_SIZE;
            }
        }
        return len1 - len;
    }
    
    void mpegts_parse_close(MpegTSContext *ts)
    {
        int i;
    
        for(i=0;i<NB_PID_MAX;i++)
            av_free(ts->pids[i]);
        av_free(ts);
    }
    
    
    AVInputFormat mpegts_demuxer = {
    
        NULL_IF_CONFIG_SMALL("MPEG-2 transport stream format"),
    
        sizeof(MpegTSContext),
        mpegts_probe,
        mpegts_read_header,
        mpegts_read_packet,
        mpegts_read_close,
    
        .flags = AVFMT_SHOW_IDS|AVFMT_TS_DISCONT,
    
        NULL_IF_CONFIG_SMALL("MPEG-2 raw transport stream format"),
    
        mpegts_read_header,
        mpegts_raw_read_packet,
        mpegts_read_close,
        read_seek,
        mpegts_get_pcr,
    
        .flags = AVFMT_SHOW_IDS|AVFMT_TS_DISCONT,