diff mbox series

[FFmpeg-devel] libavformat/mpegts.c: add: parse EIT descriptors

Message ID 20220409151518.38945-1-aimingoff@pc.nifty.jp
State New
Headers show
Series [FFmpeg-devel] libavformat/mpegts.c: add: parse EIT descriptors | expand

Checks

Context Check Description
yinshiyou/make_loongarch64 fail Make failed
andriy/make_x86 fail Make failed
andriy/make_armv7_RPi4 fail Make failed

Commit Message

TADANO Tokumei April 9, 2022, 3:15 p.m. UTC
This patch add to parse descriptors on EIT packets.
The patch is intended to set information to current program and/or
A/V stream.
On Japanese ISDB, some important / useful information is provided
via EIT only.
ref: ARIB STD B10 Table 6-1, Section 6.1, Part 2.

This patch only parse short event descriptor (0x4d) and set title
information to the program. It may not be useful, but a good example
of EIT descriptor common for DVB and ISDB.

Note that it only parse EIT for actual and present TS stream as bllow:
* Parse EIT table_id 0x4E (actual TS stream) only.
  ref: DVB Blue Book A038 (EN 300 468) Table 2, Section 5.1.3.
* Parse section number 0x00 only.
  Section number 0x00 is present event.
  Section number 0x01 is following (i.e., not for present stream).
  Section number 0x02 or later may contain event for present stream,
  but it is hard to distinguish and rarely sent.
  ref: DVB-SI Guidelines (TS 101 211) Section 4.1.4.1.
* Find a program associated to the EIT in already initialized
  AVProgram and Program structures.
  If no program found, abort to parse the EIT.

Since EIT packets may be sent several times for the same program,
add 'eit_version' in Program structure and ignore EITs with the
same version as previously parsed one.

There is a warning: "variable 'language' set but not used" at
compilation. It should be resolved by later patches.

An sample DVB TS file is found at:
  https://streams.videolan.org/streams/ts/Teletext/TELETEXTO.ts
After aplying this patch, ffprobe TELETEXTO.ts shows tile as:
  Program 340
    Metadata:
      title           : NAVY : INVESTIGACIÓN CRIMINAL

Many sample ISDB TS files are found at:
  https://streams.videolan.org/streams/ts/ARIB/japan/
Most of TS files show their title by ffprobe, but unrecognizable.
It is due to encoding problem of text string.
It should be also resolved by future patches.

Signed-off-by: TADANO Tokumei <aimingoff@pc.nifty.jp>
---
 libavformat/mpegts.c | 129 ++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 127 insertions(+), 2 deletions(-)

Comments

Marton Balint April 9, 2022, 6:34 p.m. UTC | #1
On Sun, 10 Apr 2022, TADANO Tokumei wrote:

> This patch add to parse descriptors on EIT packets.
> The patch is intended to set information to current program and/or
> A/V stream.
> On Japanese ISDB, some important / useful information is provided
> via EIT only.
> ref: ARIB STD B10 Table 6-1, Section 6.1, Part 2.

libavformat is a generic A-V demux library, therefore we probably don't 
want to add full or even partial EIT parsing capabilites to it. EIT is 
exported as a data stream, the API user can use that to get all EIT 
related information.

Regards,
Marton

>
> This patch only parse short event descriptor (0x4d) and set title
> information to the program. It may not be useful, but a good example
> of EIT descriptor common for DVB and ISDB.
>
> Note that it only parse EIT for actual and present TS stream as bllow:
> * Parse EIT table_id 0x4E (actual TS stream) only.
>  ref: DVB Blue Book A038 (EN 300 468) Table 2, Section 5.1.3.
> * Parse section number 0x00 only.
>  Section number 0x00 is present event.
>  Section number 0x01 is following (i.e., not for present stream).
>  Section number 0x02 or later may contain event for present stream,
>  but it is hard to distinguish and rarely sent.
>  ref: DVB-SI Guidelines (TS 101 211) Section 4.1.4.1.
> * Find a program associated to the EIT in already initialized
>  AVProgram and Program structures.
>  If no program found, abort to parse the EIT.
>
> Since EIT packets may be sent several times for the same program,
> add 'eit_version' in Program structure and ignore EITs with the
> same version as previously parsed one.
>
> There is a warning: "variable 'language' set but not used" at
> compilation. It should be resolved by later patches.
>
> An sample DVB TS file is found at:
>  https://streams.videolan.org/streams/ts/Teletext/TELETEXTO.ts
> After aplying this patch, ffprobe TELETEXTO.ts shows tile as:
>  Program 340
>    Metadata:
>      title           : NAVY : INVESTIGACIÓN CRIMINAL
>
> Many sample ISDB TS files are found at:
>  https://streams.videolan.org/streams/ts/ARIB/japan/
> Most of TS files show their title by ffprobe, but unrecognizable.
> It is due to encoding problem of text string.
> It should be also resolved by future patches.
>
> Signed-off-by: TADANO Tokumei <aimingoff@pc.nifty.jp>
> ---
> libavformat/mpegts.c | 129 ++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 127 insertions(+), 2 deletions(-)
>
> diff --git a/libavformat/mpegts.c b/libavformat/mpegts.c
> index 49f7735123..8417d71fb3 100644
> --- a/libavformat/mpegts.c
> +++ b/libavformat/mpegts.c
> @@ -123,6 +123,8 @@ struct Program {
>
>     /** have we found pmt for this program */
>     int pmt_found;
> +
> +    int eit_version;
> };
> 
> struct MpegTSContext {
> @@ -304,6 +306,7 @@ static void clear_program(struct Program *p)
>     p->nb_pids = 0;
>     p->nb_streams = 0;
>     p->pmt_found = 0;
> +    p->eit_version = -1;
> }
> 
> static void clear_programs(MpegTSContext *ts)
> @@ -2616,8 +2619,12 @@ static void pat_cb(MpegTSFilter *filter, const uint8_t *section, int section_len
> static void eit_cb(MpegTSFilter *filter, const uint8_t *section, int section_len)
> {
>     MpegTSContext *ts = filter->u.section_filter.opaque;
> -    const uint8_t *p, *p_end;
> +    const uint8_t *p, *p_end, *desc_list_end, *desc_end;
>     SectionHeader h1, *h = &h1;
> +    AVProgram *program;
> +    struct Program *prg;
> +    int desc_len;
> +    char language[252];
>
>     /*
>      * Sometimes we receive EPG packets but SDT table do not have
> @@ -2645,6 +2652,7 @@ static void eit_cb(MpegTSFilter *filter, const uint8_t *section, int section_len
>         return;
>
>     av_log(ts->stream, AV_LOG_TRACE, "EIT: tid received = %.02x\n", h->tid);
> +    hex_dump_debug(ts->stream, section, section_len);
>
>     /**
>      * Service_id 0xFFFF is reserved, it indicates that the current EIT table
> @@ -2664,7 +2672,124 @@ static void eit_cb(MpegTSFilter *filter, const uint8_t *section, int section_len
>
>     new_data_packet(section, section_len, ts->pkt);
>     ts->pkt->stream_index = ts->epg_stream->index;
> -    ts->stop_parse = 1;
> +
> +    /* parse present event of actual TS stream only */
> +    if (h->tid != EIT_TID)
> +        return;
> +    if (!h->current_next)
> +        return;
> +    if (ts->skip_changes)
> +        return;
> +
> +    av_log(ts->stream, AV_LOG_TRACE, "sid=0x%x sec_num=%d/%d version=%d\n",
> +           h->id, h->sec_num, h->last_sec_num, h->version);
> +
> +    /* DVB-SI Guidelines (TS 101 211) 4.1.4.1 */
> +    /* 0x00 indicates present event, 0x01 indicates following event */
> +    /* 0x02 and after is optional */
> +    if (h->sec_num > 0)
> +        return;
> +
> +    program = NULL;
> +    for (int i = 0; i < ts->stream->nb_programs; i++)
> +        if (ts->stream->programs[i]->id == h->id)
> +            program = ts->stream->programs[i];
> +    if (!program || program->nb_stream_indexes <= 0)
> +        return;
> +
> +    prg = get_program(ts, h->id);
> +    if (!prg)
> +        return;
> +    if (h->version == prg->eit_version)
> +        return;
> +    prg->eit_version = h->version;
> +
> +    /* skip ts_id, original_network_id, last_section_no, last_table_id */
> +    if (p + 6 > p_end)
> +        return;
> +    p += 6;
> +
> +    for (;;) {
> +        int eid, val;
> +
> +        eid = get16(&p, p_end);
> +        if (eid < 0)
> +            break;
> +        {
> +            int hh, mm, ss, d_hh, d_mm, d_ss, running_status;
> +            val = get16(&p, p_end); /* Date */
> +            if (val < 0)
> +                break;
> +            hh = get8(&p, p_end);
> +            if (hh < 0)
> +                break;
> +            mm = get8(&p, p_end);
> +            if (mm < 0)
> +                break;
> +            ss = get8(&p, p_end);
> +            if (ss < 0)
> +                break;
> +            d_hh = get8(&p, p_end);
> +            if (d_hh < 0)
> +                break;
> +            d_mm = get8(&p, p_end);
> +            if (d_mm < 0)
> +                break;
> +            d_ss = get8(&p, p_end);
> +            if (d_ss < 0)
> +                break;
> +            desc_len = get16(&p, p_end);
> +            if (desc_len < 0)
> +                break;
> +            running_status = (desc_len & 0xe000) >> 5;
> +            av_log(ts->stream, AV_LOG_TRACE,
> +                   "eid=0x%04x start %02x:%02x:%02x duration %02x:%02x:%02x running_status=%d\n",
> +                   eid, hh, mm, ss, d_hh, d_mm, d_ss, running_status);
> +        }
> +        desc_len &= 0x0fff;
> +        desc_list_end = p + desc_len;
> +        if (desc_list_end > p_end)
> +            break;
> +
> +        for (;;) {
> +            int desc_tag;
> +
> +            desc_tag = get8(&p, desc_list_end);
> +            if (desc_tag < 0)
> +                break;
> +            desc_len = get8(&p, desc_list_end);
> +            desc_end = p + desc_len;
> +            if (desc_len < 0 || desc_end > desc_list_end)
> +                break;
> +
> +            av_log(ts->stream, AV_LOG_DEBUG, "tag: 0x%02x len=%d\n",
> +                   desc_tag, desc_len);
> +
> +            switch (desc_tag) {
> +            case 0x4d:  /* short event descriptor */
> +                {
> +                    char *txt;
> +
> +                    if (desc_len < 3)
> +                        break;
> +                    language[0] = get8(&p, desc_end);
> +                    language[1] = get8(&p, desc_end);
> +                    language[2] = get8(&p, desc_end);
> +                    language[3] = '\0';
> +                    txt = getstr8(&p, desc_end);
> +                    if (!txt)
> +                        break;
> +                    av_dict_set(&program->metadata, "title", txt, 0);
> +                    av_free(txt);
> +                }
> +                break;
> +            default:
> +                break;
> +            }
> +            p = desc_end;
> +        }
> +        p = desc_list_end;
> +    }
> }
> 
> static void sdt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len)
> -- 
> 2.30.2
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
TADANO Tokumei April 10, 2022, 7:49 a.m. UTC | #2
On 2022/04/10 3:34, Marton Balint wrote:
> 
> 
> On Sun, 10 Apr 2022, TADANO Tokumei wrote:
> 
>> This patch add to parse descriptors on EIT packets.
>> The patch is intended to set information to current program and/or
>> A/V stream.
>> On Japanese ISDB, some important / useful information is provided
>> via EIT only.
>> ref: ARIB STD B10 Table 6-1, Section 6.1, Part 2.
> 
> libavformat is a generic A-V demux library, therefore we probably don't want to add full or even partial EIT parsing capabilites to it. EIT is exported as a data stream, the API user can use that to get all EIT related information.

Thanks for your comments.

I have a plan to add EIT parsing capabilities for Japanese ISDB:

* dual-monoral audio stream: Audio component descriptor (0xC4)
   libavcodec already has capability to handle Japanese dual-mono audio.
   (libavcodec/packet.h, libavcodec/avpacket.c, libavcodec/aacdec_template.c, ...)
   However, the information of dual-mono mode is provided via only the EIT descriptor.
   Thus, most of application treat the dual-mono audio stream as stereo.
   ref: ARIB STD-B10 Part 2, Section 6.2.26
   There is no standard way to pass the dual-mono audio information.
   My idea is to add side data of AV_PKT_DATA_JP_DUALMONO to the audio stream
   (Similar way to DOVI).
   To play the dual-mono audio correctly, application have to set (or pass)
   AV_PKT_DATA_JP_DUALMONO side data into each audio packet if the stream has
   AV_PKT_DATA_JP_DUALMONO side data. (The stream's side data indicates default
   mode, and packet's side data should be set user selected mode)

* Language information of ARIB Caption: Data content(s) descriptor (0xC7)
   On DVB, similar information is provided by Subtitling descriptor (0x59).
   But on ISDB, such information is provided by this EIT descriptor.
   ref: ARIB STD-B10 Part 2, Section 6.2.28 and Annex J
        ARIB STD-B24 Fascicle 1 Part 3 Chapter 9 Table 9-17

On DVB, most important information for A/V stream are provided by PMT.
But on ISDB, some important information are lacked without parsing EIT.

Regards,
TADANO

> Regards,
> Marton
> 
>>
>> This patch only parse short event descriptor (0x4d) and set title
>> information to the program. It may not be useful, but a good example
>> of EIT descriptor common for DVB and ISDB.
>>
>> Note that it only parse EIT for actual and present TS stream as bllow:
>> * Parse EIT table_id 0x4E (actual TS stream) only.
>>  ref: DVB Blue Book A038 (EN 300 468) Table 2, Section 5.1.3.
>> * Parse section number 0x00 only.
>>  Section number 0x00 is present event.
>>  Section number 0x01 is following (i.e., not for present stream).
>>  Section number 0x02 or later may contain event for present stream,
>>  but it is hard to distinguish and rarely sent.
>>  ref: DVB-SI Guidelines (TS 101 211) Section 4.1.4.1.
>> * Find a program associated to the EIT in already initialized
>>  AVProgram and Program structures.
>>  If no program found, abort to parse the EIT.
>>
>> Since EIT packets may be sent several times for the same program,
>> add 'eit_version' in Program structure and ignore EITs with the
>> same version as previously parsed one.
>>
>> There is a warning: "variable 'language' set but not used" at
>> compilation. It should be resolved by later patches.
>>
>> An sample DVB TS file is found at:
>>  https://streams.videolan.org/streams/ts/Teletext/TELETEXTO.ts
>> After aplying this patch, ffprobe TELETEXTO.ts shows tile as:
>>  Program 340
>>    Metadata:
>>      title           : NAVY : INVESTIGACIÓN CRIMINAL
>>
>> Many sample ISDB TS files are found at:
>>  https://streams.videolan.org/streams/ts/ARIB/japan/
>> Most of TS files show their title by ffprobe, but unrecognizable.
>> It is due to encoding problem of text string.
>> It should be also resolved by future patches.
>>
>> Signed-off-by: TADANO Tokumei <aimingoff@pc.nifty.jp>
>> ---
>> libavformat/mpegts.c | 129 ++++++++++++++++++++++++++++++++++++++++++-
>> 1 file changed, 127 insertions(+), 2 deletions(-)
>>
>> diff --git a/libavformat/mpegts.c b/libavformat/mpegts.c
>> index 49f7735123..8417d71fb3 100644
>> --- a/libavformat/mpegts.c
>> +++ b/libavformat/mpegts.c
>> @@ -123,6 +123,8 @@ struct Program {
>>
>>     /** have we found pmt for this program */
>>     int pmt_found;
>> +
>> +    int eit_version;
>> };
>>
>> struct MpegTSContext {
>> @@ -304,6 +306,7 @@ static void clear_program(struct Program *p)
>>     p->nb_pids = 0;
>>     p->nb_streams = 0;
>>     p->pmt_found = 0;
>> +    p->eit_version = -1;
>> }
>>
>> static void clear_programs(MpegTSContext *ts)
>> @@ -2616,8 +2619,12 @@ static void pat_cb(MpegTSFilter *filter, const uint8_t *section, int section_len
>> static void eit_cb(MpegTSFilter *filter, const uint8_t *section, int section_len)
>> {
>>     MpegTSContext *ts = filter->u.section_filter.opaque;
>> -    const uint8_t *p, *p_end;
>> +    const uint8_t *p, *p_end, *desc_list_end, *desc_end;
>>     SectionHeader h1, *h = &h1;
>> +    AVProgram *program;
>> +    struct Program *prg;
>> +    int desc_len;
>> +    char language[252];
>>
>>     /*
>>      * Sometimes we receive EPG packets but SDT table do not have
>> @@ -2645,6 +2652,7 @@ static void eit_cb(MpegTSFilter *filter, const uint8_t *section, int section_len
>>         return;
>>
>>     av_log(ts->stream, AV_LOG_TRACE, "EIT: tid received = %.02x\n", h->tid);
>> +    hex_dump_debug(ts->stream, section, section_len);
>>
>>     /**
>>      * Service_id 0xFFFF is reserved, it indicates that the current EIT table
>> @@ -2664,7 +2672,124 @@ static void eit_cb(MpegTSFilter *filter, const uint8_t *section, int section_len
>>
>>     new_data_packet(section, section_len, ts->pkt);
>>     ts->pkt->stream_index = ts->epg_stream->index;
>> -    ts->stop_parse = 1;
>> +
>> +    /* parse present event of actual TS stream only */
>> +    if (h->tid != EIT_TID)
>> +        return;
>> +    if (!h->current_next)
>> +        return;
>> +    if (ts->skip_changes)
>> +        return;
>> +
>> +    av_log(ts->stream, AV_LOG_TRACE, "sid=0x%x sec_num=%d/%d version=%d\n",
>> +           h->id, h->sec_num, h->last_sec_num, h->version);
>> +
>> +    /* DVB-SI Guidelines (TS 101 211) 4.1.4.1 */
>> +    /* 0x00 indicates present event, 0x01 indicates following event */
>> +    /* 0x02 and after is optional */
>> +    if (h->sec_num > 0)
>> +        return;
>> +
>> +    program = NULL;
>> +    for (int i = 0; i < ts->stream->nb_programs; i++)
>> +        if (ts->stream->programs[i]->id == h->id)
>> +            program = ts->stream->programs[i];
>> +    if (!program || program->nb_stream_indexes <= 0)
>> +        return;
>> +
>> +    prg = get_program(ts, h->id);
>> +    if (!prg)
>> +        return;
>> +    if (h->version == prg->eit_version)
>> +        return;
>> +    prg->eit_version = h->version;
>> +
>> +    /* skip ts_id, original_network_id, last_section_no, last_table_id */
>> +    if (p + 6 > p_end)
>> +        return;
>> +    p += 6;
>> +
>> +    for (;;) {
>> +        int eid, val;
>> +
>> +        eid = get16(&p, p_end);
>> +        if (eid < 0)
>> +            break;
>> +        {
>> +            int hh, mm, ss, d_hh, d_mm, d_ss, running_status;
>> +            val = get16(&p, p_end); /* Date */
>> +            if (val < 0)
>> +                break;
>> +            hh = get8(&p, p_end);
>> +            if (hh < 0)
>> +                break;
>> +            mm = get8(&p, p_end);
>> +            if (mm < 0)
>> +                break;
>> +            ss = get8(&p, p_end);
>> +            if (ss < 0)
>> +                break;
>> +            d_hh = get8(&p, p_end);
>> +            if (d_hh < 0)
>> +                break;
>> +            d_mm = get8(&p, p_end);
>> +            if (d_mm < 0)
>> +                break;
>> +            d_ss = get8(&p, p_end);
>> +            if (d_ss < 0)
>> +                break;
>> +            desc_len = get16(&p, p_end);
>> +            if (desc_len < 0)
>> +                break;
>> +            running_status = (desc_len & 0xe000) >> 5;
>> +            av_log(ts->stream, AV_LOG_TRACE,
>> +                   "eid=0x%04x start %02x:%02x:%02x duration %02x:%02x:%02x running_status=%d\n",
>> +                   eid, hh, mm, ss, d_hh, d_mm, d_ss, running_status);
>> +        }
>> +        desc_len &= 0x0fff;
>> +        desc_list_end = p + desc_len;
>> +        if (desc_list_end > p_end)
>> +            break;
>> +
>> +        for (;;) {
>> +            int desc_tag;
>> +
>> +            desc_tag = get8(&p, desc_list_end);
>> +            if (desc_tag < 0)
>> +                break;
>> +            desc_len = get8(&p, desc_list_end);
>> +            desc_end = p + desc_len;
>> +            if (desc_len < 0 || desc_end > desc_list_end)
>> +                break;
>> +
>> +            av_log(ts->stream, AV_LOG_DEBUG, "tag: 0x%02x len=%d\n",
>> +                   desc_tag, desc_len);
>> +
>> +            switch (desc_tag) {
>> +            case 0x4d:  /* short event descriptor */
>> +                {
>> +                    char *txt;
>> +
>> +                    if (desc_len < 3)
>> +                        break;
>> +                    language[0] = get8(&p, desc_end);
>> +                    language[1] = get8(&p, desc_end);
>> +                    language[2] = get8(&p, desc_end);
>> +                    language[3] = '\0';
>> +                    txt = getstr8(&p, desc_end);
>> +                    if (!txt)
>> +                        break;
>> +                    av_dict_set(&program->metadata, "title", txt, 0);
>> +                    av_free(txt);
>> +                }
>> +                break;
>> +            default:
>> +                break;
>> +            }
>> +            p = desc_end;
>> +        }
>> +        p = desc_list_end;
>> +    }
>> }
>>
>> static void sdt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len)
>> -- 
>> 2.30.2
>>
>> _______________________________________________
>> ffmpeg-devel mailing list
>> ffmpeg-devel@ffmpeg.org
>> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>>
>> To unsubscribe, visit link above, or email
>> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
> 
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
diff mbox series

Patch

diff --git a/libavformat/mpegts.c b/libavformat/mpegts.c
index 49f7735123..8417d71fb3 100644
--- a/libavformat/mpegts.c
+++ b/libavformat/mpegts.c
@@ -123,6 +123,8 @@  struct Program {
 
     /** have we found pmt for this program */
     int pmt_found;
+
+    int eit_version;
 };
 
 struct MpegTSContext {
@@ -304,6 +306,7 @@  static void clear_program(struct Program *p)
     p->nb_pids = 0;
     p->nb_streams = 0;
     p->pmt_found = 0;
+    p->eit_version = -1;
 }
 
 static void clear_programs(MpegTSContext *ts)
@@ -2616,8 +2619,12 @@  static void pat_cb(MpegTSFilter *filter, const uint8_t *section, int section_len
 static void eit_cb(MpegTSFilter *filter, const uint8_t *section, int section_len)
 {
     MpegTSContext *ts = filter->u.section_filter.opaque;
-    const uint8_t *p, *p_end;
+    const uint8_t *p, *p_end, *desc_list_end, *desc_end;
     SectionHeader h1, *h = &h1;
+    AVProgram *program;
+    struct Program *prg;
+    int desc_len;
+    char language[252];
 
     /*
      * Sometimes we receive EPG packets but SDT table do not have
@@ -2645,6 +2652,7 @@  static void eit_cb(MpegTSFilter *filter, const uint8_t *section, int section_len
         return;
 
     av_log(ts->stream, AV_LOG_TRACE, "EIT: tid received = %.02x\n", h->tid);
+    hex_dump_debug(ts->stream, section, section_len);
 
     /**
      * Service_id 0xFFFF is reserved, it indicates that the current EIT table
@@ -2664,7 +2672,124 @@  static void eit_cb(MpegTSFilter *filter, const uint8_t *section, int section_len
 
     new_data_packet(section, section_len, ts->pkt);
     ts->pkt->stream_index = ts->epg_stream->index;
-    ts->stop_parse = 1;
+
+    /* parse present event of actual TS stream only */
+    if (h->tid != EIT_TID)
+        return;
+    if (!h->current_next)
+        return;
+    if (ts->skip_changes)
+        return;
+
+    av_log(ts->stream, AV_LOG_TRACE, "sid=0x%x sec_num=%d/%d version=%d\n",
+           h->id, h->sec_num, h->last_sec_num, h->version);
+
+    /* DVB-SI Guidelines (TS 101 211) 4.1.4.1 */
+    /* 0x00 indicates present event, 0x01 indicates following event */
+    /* 0x02 and after is optional */
+    if (h->sec_num > 0)
+        return;
+
+    program = NULL;
+    for (int i = 0; i < ts->stream->nb_programs; i++)
+        if (ts->stream->programs[i]->id == h->id)
+            program = ts->stream->programs[i];
+    if (!program || program->nb_stream_indexes <= 0)
+        return;
+
+    prg = get_program(ts, h->id);
+    if (!prg)
+        return;
+    if (h->version == prg->eit_version)
+        return;
+    prg->eit_version = h->version;
+
+    /* skip ts_id, original_network_id, last_section_no, last_table_id */
+    if (p + 6 > p_end)
+        return;
+    p += 6;
+
+    for (;;) {
+        int eid, val;
+
+        eid = get16(&p, p_end);
+        if (eid < 0)
+            break;
+        {
+            int hh, mm, ss, d_hh, d_mm, d_ss, running_status;
+            val = get16(&p, p_end); /* Date */
+            if (val < 0)
+                break;
+            hh = get8(&p, p_end);
+            if (hh < 0)
+                break;
+            mm = get8(&p, p_end);
+            if (mm < 0)
+                break;
+            ss = get8(&p, p_end);
+            if (ss < 0)
+                break;
+            d_hh = get8(&p, p_end);
+            if (d_hh < 0)
+                break;
+            d_mm = get8(&p, p_end);
+            if (d_mm < 0)
+                break;
+            d_ss = get8(&p, p_end);
+            if (d_ss < 0)
+                break;
+            desc_len = get16(&p, p_end);
+            if (desc_len < 0)
+                break;
+            running_status = (desc_len & 0xe000) >> 5;
+            av_log(ts->stream, AV_LOG_TRACE,
+                   "eid=0x%04x start %02x:%02x:%02x duration %02x:%02x:%02x running_status=%d\n",
+                   eid, hh, mm, ss, d_hh, d_mm, d_ss, running_status);
+        }
+        desc_len &= 0x0fff;
+        desc_list_end = p + desc_len;
+        if (desc_list_end > p_end)
+            break;
+
+        for (;;) {
+            int desc_tag;
+
+            desc_tag = get8(&p, desc_list_end);
+            if (desc_tag < 0)
+                break;
+            desc_len = get8(&p, desc_list_end);
+            desc_end = p + desc_len;
+            if (desc_len < 0 || desc_end > desc_list_end)
+                break;
+
+            av_log(ts->stream, AV_LOG_DEBUG, "tag: 0x%02x len=%d\n",
+                   desc_tag, desc_len);
+
+            switch (desc_tag) {
+            case 0x4d:  /* short event descriptor */
+                {
+                    char *txt;
+
+                    if (desc_len < 3)
+                        break;
+                    language[0] = get8(&p, desc_end);
+                    language[1] = get8(&p, desc_end);
+                    language[2] = get8(&p, desc_end);
+                    language[3] = '\0';
+                    txt = getstr8(&p, desc_end);
+                    if (!txt)
+                        break;
+                    av_dict_set(&program->metadata, "title", txt, 0);
+                    av_free(txt);
+                }
+                break;
+            default:
+                break;
+            }
+            p = desc_end;
+        }
+        p = desc_list_end;
+    }
 }
 
 static void sdt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len)