[FFmpeg-devel] DVB EPG decoder

Submitted by Anthony Delannoy on Aug. 26, 2019, 9:17 a.m.

Details

Message ID CAF7m-45e_g-wCM+Nk4kVkWRbHa0yZ5GP7oKY0=KPXdQYQ4ASFw@mail.gmail.com
State New
Headers show

Commit Message

Anthony Delannoy Aug. 26, 2019, 9:17 a.m.
Here the version without EPG added to PMT and programs.

Le lun. 26 août 2019 à 10:21, Anthony Delannoy
<anthony.2lannoy@gmail.com> a écrit :
>
> Okay, thanks
>
> I will patch that
>
> Le sam. 24 août 2019 à 20:09, Marton Balint <cus@passwd.hu> a écrit :
> >
> >
> > On Fri, 23 Aug 2019, Anthony Delannoy wrote:
> >
> > >> I think we should only merge the part of this patchset which makes the EIT
> > >> available as a data stream. Parsing the whole EIT or dumping the data as
> > >> ASCII is not libavcodec's or libavutil's job.
> > >
> > > The EPG decoder does not change the table's data, it just store them
> > > and it happens to
> > > contains text sometimes.
> > > Some utilites functions I made in libavutil/dvb can convert those raw
> > > data in text
> > > description(s) for certain descriptors.
> > >
> > >> Also there is no such concept in libavcodec as a data decoder, if something happens to
> > >> work with avcodec_send_packet/avcodec_receive_frame that is mostly luck I
> > >> believe.
> > >
> > > avcodec_send_packet and avcodec_receive_frame both call
> > > AVCodec::receive_frame if it is
> > > implemented. That's why my implementation of the EPG decoder does
> > > contain this function.
> > >
> > > For now my test scripts consists to:
> > > ```
> > > 99         if (st->codecpar->codec_id != AV_CODEC_ID_EPG)
> > > 100             goto free_pkt;
> > > 101
> > > 102         ret = avcodec_send_packet(dec_ctx, &pkt);
> > > ...
> > > 112         while (1) {
> > > 113             ret = avcodec_receive_frame(dec_ctx, frame);
> > > 114             if (ret < 0)
> > > 115                 break;
> > > 116
> > > 117             for (int i = 0; i < frame->nb_side_data; i++) {
> > > 118                 AVFrameSideData *sd = frame->side_data[i];
> > > 119                 if (sd && sd->type == AV_FRAME_DATA_EPG_TABLE) {
> > > 120                     EPGTable *table = sd->data;
> > > 121                     av_epg_show_table(table, AV_LOG_WARNING);
> > > 122                 }
> > > 123             }
> > > 124             av_frame_unref(frame);
> > > 125         }
> > > 126
> > > 127 free_pkt:
> > > 128         av_packet_unref(&pkt);
> > > ```
> > > It works as intended and permits to decode EPGTable without issues, I
> > > tried on multiple channels.
> > >
> > > I wanted to permit the table decoding and not just an EPG data stream
> > > to permit easy reading
> > > and in the future easy modification before encoding EPG back.
> > >
> > >> I am also not sure if we should add the EIT PID to all programs, that
> > >> would mess up the direct relation between a PMT and an AVProgram, and we
> > >> probably don't want that. So I'd rather see the EIT data stream as a
> > >> standalone PID separate from the programs.
> > >
> > > I'm not an expert but I think each service/program contains a PMT
> > > table and all others. So one
> > > EPG stream (if available) for each service.
> > > That's what I understood from the ETSI EN 300 468 V1.16.1
> >
> > The EPG stream is a single stream in the whole TS, it is on a single PID.
> > The PMTs do not reference the EIT PID, therefore we should not make the
> > EPG stream part of the programs, even if the EPG stream can contain the
> > schedule of the programs.
> >
> > Regards,
> > Marton
> > _______________________________________________
> > 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".

Comments

Marton Balint Sept. 3, 2019, 9:39 p.m.
Hi,

I am not sure if you are interested getting only patch 1, 2 and 3
merged, but if you are, then here are my comments for patch 3.

> From 025ec8e8d607d02f2e5b4021783ab8f3b42d0bc1 Mon Sep 17 00:00:00 2001
> From: Anthony Delannoy <anthony.2lannoy@gmail.com>
> Date: Wed, 21 Aug 2019 11:46:56 +0200
> Subject: [PATCH 03/10] lavf/mpegts: EPG extraction from mpegts
> 
> ---
>  libavformat/mpegts.c | 67 ++++++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 65 insertions(+), 2 deletions(-)
> 
> diff --git a/libavformat/mpegts.c b/libavformat/mpegts.c
> index 47d8d5f877..03c1753ac7 100644
> --- a/libavformat/mpegts.c
> +++ b/libavformat/mpegts.c
> @@ -2489,13 +2489,57 @@ 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;
> +    MpegTSSectionFilter *tssf = &filter->u.section_filter;
> +    const uint8_t *p, *p_end;
> +    SectionHeader h1, *h = &h1;
> +    int idx;
> +    AVProgram *prg;

Maybe you can check if AVStream->discard == DISCARD_ALL for the EIT stream and
skip the packet if the stream is discarded.

> +
> +    p_end = section + section_len - 4;
> +    p     = section;
> +
> +    if (parse_section_header(h, &p, p_end) < 0)
> +        return;
> +    if (h->tid < EIT_TID || h->tid > OEITS_END_TID)
> +        return;

> +    if (ts->skip_changes)
> +        return;
> +    if (skip_identical(h, tssf))
> +        return;

I don't think these last two checks (skip changes and skip identical) makes
much sense here, just remove them.

> +
> +    idx = ff_find_stream_index(ts->stream, filter->pid);
> +    if (idx < 0)
> +        return;

Instead of finding the stream each time, you should simply add an AVStream
*epg_stream to MpegTsContext, set it where you create the stream, and use that
always.

> +
> +    /**
> +     * In case we receive an EPG packet before mpegts context is fully
> +     * initialized.
> +     */
> +    if (!ts->pkt)
> +        return;
> +
> +    new_data_packet(section, section_len, ts->pkt);
> +    ts->pkt->stream_index = idx;

> +    prg = av_find_program_from_stream(ts->stream, NULL, idx);
> +    if (prg && prg->pcr_pid != -1 && prg->discard != AVDISCARD_ALL) {
> +        MpegTSFilter *f = ts->pids[prg->pcr_pid];
> +        if (f && f->last_pcr != -1)
> +            ts->pkt->pts = ts->pkt->dts = f->last_pcr/300;
> +    }

This program based logic is no longer needed since EIT is not added to programs.

> +    ts->stop_parse = 1;
> +}
> +
>  static void sdt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len)
>  {
>      MpegTSContext *ts = filter->u.section_filter.opaque;
>      MpegTSSectionFilter *tssf = &filter->u.section_filter;
>      SectionHeader h1, *h = &h1;
>      const uint8_t *p, *p_end, *desc_list_end, *desc_end;
> -    int onid, val, sid, desc_list_len, desc_tag, desc_len, service_type;
> +    int onid, val, sid, desc_list_len, desc_tag, desc_len, service_type,
> +        eit_sched, eit_pres_following;
>      char *name, *provider_name;
>
>      av_log(ts->stream, AV_LOG_TRACE, "SDT:\n");
> @@ -2525,6 +2569,24 @@ static void sdt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len
>          val = get8(&p, p_end);
>          if (val < 0)
>              break;
> +        eit_sched = (val >> 1) & 0x1;
> +        eit_pres_following = val & 0x1;
> +
> +        if (eit_sched | eit_pres_following) {
> +            int idx = ff_find_stream_index(ts->stream, EIT_PID);
> +            AVStream *st = (idx >= 0) ? ts->stream->streams[EIT_PID] : NULL;

I guess ts->stream->streams[EIT_PID] wanted to be ts->stream->streams[idx] but
it does not matter because you should rework this if you add epg_stream to
MpegTsContext.

> +
> +            if (!st) {
> +                st = avformat_new_stream(ts->stream, NULL);
> +                if (!st)
> +                    return;
> +                st->id = EIT_PID;
> +                st->program_num = sid;
> +                st->codecpar->codec_type = AVMEDIA_TYPE_DATA;
> +                st->codecpar->codec_id = AV_CODEC_ID_EPG;
> +            }
> +        }
> +
>          desc_list_len = get16(&p, p_end);
>          if (desc_list_len < 0)
>              break;
> @@ -2975,8 +3037,8 @@ static int mpegts_read_header(AVFormatContext *s)
>          seek_back(s, pb, pos);
>
>          mpegts_open_section_filter(ts, SDT_PID, sdt_cb, ts, 1);
> -
>          mpegts_open_section_filter(ts, PAT_PID, pat_cb, ts, 1);
> +        mpegts_open_section_filter(ts, EIT_PID, eit_cb, ts, 1);
>
>          handle_packets(ts, probesize / ts->raw_packet_size);
>          /* if could not find service, enable auto_guess */
> @@ -3233,6 +3295,7 @@ MpegTSContext *avpriv_mpegts_parse_open(AVFormatContext *s)
>      ts->auto_guess = 1;
>      mpegts_open_section_filter(ts, SDT_PID, sdt_cb, ts, 1);
>      mpegts_open_section_filter(ts, PAT_PID, pat_cb, ts, 1);
> +    mpegts_open_section_filter(ts, EIT_PID, eit_cb, ts, 1);
>
>      return ts;
>  }
> -- 
> 2.23.0

Regards,
Marton

Patch hide | download patch | download mbox

From ed58403e8b8e53279db36f412d340600a509ea70 Mon Sep 17 00:00:00 2001
From: Anthony Delannoy <anthony.2lannoy@gmail.com>
Date: Wed, 21 Aug 2019 16:07:59 +0200
Subject: [PATCH 10/10] lavc/epgdec: add EPG data decoder

New EPG table decoder which store an EPGTable in AVFrame::side_data as
a AV_FRAME_DATA_EPG_TABLE type for each AVPacket decoded.
---
 Changelog              |   1 +
 configure              |   1 +
 libavcodec/Makefile    |   1 +
 libavcodec/allcodecs.c |   1 +
 libavcodec/epgdec.c    | 278 +++++++++++++++++++++++++++++++++++++++++
 5 files changed, 282 insertions(+)
 create mode 100644 libavcodec/epgdec.c

diff --git a/Changelog b/Changelog
index efdc65610f..fda1372053 100644
--- a/Changelog
+++ b/Changelog
@@ -2,6 +2,7 @@  Entries are sorted chronologically from oldest to youngest within each release,
 releases are sorted from youngest to oldest.
 
 version <next>:
+- EPG data decoder
 - v360 filter
 - Intel QSV-accelerated MJPEG decoding
 - Intel QSV-accelerated VP9 decoding
diff --git a/configure b/configure
index 68507abbb9..f71e68af1b 100755
--- a/configure
+++ b/configure
@@ -2687,6 +2687,7 @@  eac3_encoder_select="ac3_encoder"
 eamad_decoder_select="aandcttables blockdsp bswapdsp idctdsp mpegvideo"
 eatgq_decoder_select="aandcttables"
 eatqi_decoder_select="aandcttables blockdsp bswapdsp idctdsp"
+epg_decoder_select="dvb"
 exr_decoder_deps="zlib"
 ffv1_decoder_select="rangecoder"
 ffv1_encoder_select="rangecoder"
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index e49188357b..b9c42bdb14 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -300,6 +300,7 @@  OBJS-$(CONFIG_EATQI_DECODER)           += eatqi.o eaidct.o mpeg12.o mpeg12data.o
 OBJS-$(CONFIG_EIGHTBPS_DECODER)        += 8bps.o
 OBJS-$(CONFIG_EIGHTSVX_EXP_DECODER)    += 8svx.o
 OBJS-$(CONFIG_EIGHTSVX_FIB_DECODER)    += 8svx.o
+OBJS-$(CONFIG_EPG_DECODER)             += epgdec.o
 OBJS-$(CONFIG_ESCAPE124_DECODER)       += escape124.o
 OBJS-$(CONFIG_ESCAPE130_DECODER)       += escape130.o
 OBJS-$(CONFIG_EVRC_DECODER)            += evrcdec.o acelp_vectors.o lsp.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index 22985325e0..b669c80bca 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -419,6 +419,7 @@  extern AVCodec ff_dss_sp_decoder;
 extern AVCodec ff_dst_decoder;
 extern AVCodec ff_eac3_encoder;
 extern AVCodec ff_eac3_decoder;
+extern AVCodec ff_epg_decoder;
 extern AVCodec ff_evrc_decoder;
 extern AVCodec ff_ffwavesynth_decoder;
 extern AVCodec ff_flac_encoder;
diff --git a/libavcodec/epgdec.c b/libavcodec/epgdec.c
new file mode 100644
index 0000000000..2003bb1905
--- /dev/null
+++ b/libavcodec/epgdec.c
@@ -0,0 +1,278 @@ 
+/*
+ * epg data decoder
+ * Copyright (c) 2019 Anthony Delannoy
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file epg data decoder
+ */
+
+#include "avcodec.h"
+#include "internal.h"
+#include "decode.h"
+#include "libavutil/common.h"
+#include "libavutil/dvb.h"
+#include "libavutil/dvbdescriptors.h"
+#include "libavformat/mpegts.h"
+
+typedef struct EpgTidInfo {
+    int last_version;
+    int last_section_num;
+    int last_segment_section_num;
+} EpgTidInfo;
+
+typedef struct EPGContext {
+    const AVClass *class;
+    struct EpgTidInfo infos[4];
+    int last_sched_table_id;
+    int o_last_sched_table_id;
+    AVPacket *pkt;
+} EPGContext;
+
+static int epg_handle_descriptor(DvbDescriptorHeader *h, DvbDescriptor *desc,
+                                 EPGSubTable *subtable, const uint8_t **pp,
+                                 const uint8_t *p_end)
+{
+    void *data = desc->parse(desc, pp, p_end);
+    if (!data)
+        return AVERROR_INVALIDDATA;
+    memcpy(data, h, sizeof(DvbDescriptorHeader));
+
+    if (av_reallocp_array(&subtable->descriptors, (subtable->nb_descriptors + 1), sizeof(void*)) < 0) {
+        desc->free(data);
+        return AVERROR(ENOMEM);
+    }
+    subtable->descriptors[subtable->nb_descriptors++] = data;
+
+    return 0;
+}
+
+static int epg_receive_frame(AVCodecContext *avctx, AVFrame *frame)
+{
+    EPGContext *epg_ctx = avctx->priv_data;
+    EpgTidInfo *epg_info;
+    DvbSectionHeader h1, *h = &h1;
+    const uint8_t *p, *p_end;
+    int val, ret, max_last_table_id;
+    uint8_t next_version;
+    AVFrameSideData *sd;
+    AVBufferRef *buf_ref;
+    EPGTable *table;
+
+    if (!epg_ctx->pkt->data) {
+        ret = ff_decode_get_packet(avctx, epg_ctx->pkt);
+        if (ret < 0)
+            return ret;
+    }
+
+    p       = epg_ctx->pkt->data;
+    p_end   = p + epg_ctx->pkt->size - 4;
+
+    if (avpriv_dvb_parse_section_header(h, &p, p_end) < 0)
+        goto fail;
+
+    table = av_epg_table_alloc();
+    if (!table)
+        goto fail;
+    table->h = h1;
+
+    val = avpriv_dvb_get16(&p, p_end);
+    if (val < 0)
+        goto fail;
+    table->ts_id = val;
+
+    val = avpriv_dvb_get16(&p, p_end);
+    if (val < 0)
+        goto fail;
+    table->network_id = val;
+
+    val = avpriv_dvb_get8(&p, p_end);
+    if (val < 0)
+        goto fail;
+    table->segment_last_section_num = val;
+
+    val = avpriv_dvb_get8(&p, p_end);
+    if (val < 0)
+        goto fail;
+    table->last_tid = val;
+
+    // Check eit data
+    switch (h->tid) {
+        case EIT_TID:
+            epg_info = &epg_ctx->infos[0];
+            if (table->last_tid != EIT_TID || h->last_sec_num != table->segment_last_section_num)
+                goto fail;
+            break;
+        case OEIT_TID:
+            epg_info = &epg_ctx->infos[1];
+            if (table->last_tid != OEIT_TID || h->last_sec_num != table->segment_last_section_num)
+                goto fail;
+            break;
+        case EITS_START_TID ... EITS_END_TID:
+            epg_info = &epg_ctx->infos[2];
+            if (table->segment_last_section_num != h->sec_num)
+                goto fail;
+            max_last_table_id = FFMAX(table->last_tid, epg_ctx->last_sched_table_id);
+            if (table->last_tid != max_last_table_id)
+                goto fail;
+            epg_ctx->last_sched_table_id = max_last_table_id;
+            break;
+        case OEITS_START_TID ... OEITS_END_TID:
+            epg_info = &epg_ctx->infos[3];
+            if (table->segment_last_section_num != h->sec_num)
+                goto fail;
+            max_last_table_id = FFMAX(table->last_tid, epg_ctx->o_last_sched_table_id);
+            if (table->last_tid != max_last_table_id)
+                goto fail;
+            epg_ctx->o_last_sched_table_id = max_last_table_id;
+            break;
+        default:
+            goto fail;
+    }
+
+    next_version = (epg_info->last_version == 31) ? 0 : epg_info->last_version + 1;
+    if (epg_info->last_version != (-1) &&
+            (h->version == epg_info->last_version || h->version == next_version))
+        goto fail;
+
+    // Subtables
+    while (p < p_end) {
+        const uint8_t *desc_list_end;
+        EPGSubTable *subtable;
+
+        if (av_reallocp_array(&table->subtables, (table->nb_subtables + 1), sizeof(EPGSubTable*)) < 0)
+            goto fail;
+
+        table->subtables[table->nb_subtables] = av_epg_subtable_alloc();
+        if (!table->subtables[table->nb_subtables])
+            goto fail;
+        subtable = table->subtables[table->nb_subtables++];
+
+        // Get event_id
+        val = avpriv_dvb_get16(&p, p_end);
+        if (val < 0)
+            break;
+        subtable->event_id = val;
+
+        memcpy(subtable->start_time, p, 5);
+        p += 5;
+
+        memcpy(subtable->duration, p, 3);
+        p += 3;
+
+        val = avpriv_dvb_get16(&p, p_end);
+        if (val < 0)
+            break;
+        subtable->running_status = (val >> 13);
+        subtable->free_ca_mode = (val >> 12) & 0x1;
+        subtable->desc_loop_len = val & 0xfff;
+
+        desc_list_end = p + subtable->desc_loop_len;
+
+        // Descriptors
+        for (;;) {
+            DvbDescriptor *desc;
+            DvbDescriptorHeader h;
+            const uint8_t *desc_end;
+
+            if (av_dvb_parse_descriptor_header(&h, &p, p_end) < 0)
+                break;
+
+            if (!(desc = (DvbDescriptor*)av_dvb_get_descriptor(&h))) {
+                p += h.len;
+                continue;
+            }
+
+            desc_end = p + h.len;
+            if (desc_end > desc_list_end)
+                break;
+
+            if (epg_handle_descriptor(&h, desc, subtable, &p, desc_end) < 0)
+                break;
+
+            p = desc_end;
+        }
+        p = desc_list_end;
+    }
+
+    // CRC32
+    val = avpriv_dvb_get32(&p_end, (p_end + 4));
+    if (val < 0)
+        goto fail;
+    table->crc = val;
+
+    buf_ref = av_buffer_allocz(sizeof(*table) + AV_INPUT_BUFFER_PADDING_SIZE);
+    if (!buf_ref)
+        goto fail;
+    buf_ref->data = (uint8_t*)table;
+    frame->buf[0] = buf_ref;
+
+    sd = av_frame_new_side_data(frame, AV_FRAME_DATA_EPG_TABLE, sizeof(*table));
+    sd->data = (uint8_t*)table;
+
+    frame->pts = epg_ctx->pkt->pts;
+    frame->pkt_dts = epg_ctx->pkt->dts;
+    frame->pkt_size = epg_ctx->pkt->size;
+
+    av_packet_unref(epg_ctx->pkt);
+    return 0;
+
+fail:
+    av_packet_unref(epg_ctx->pkt);
+    av_epg_table_free(&table);
+    return AVERROR_INVALIDDATA;
+}
+
+static av_cold int decode_init(AVCodecContext *avctx)
+{
+    EPGContext *epg_ctx = avctx->priv_data;
+
+    for (int i = 0; i < 4; i++)
+        epg_ctx->infos[i].last_version = (-1);
+    epg_ctx->last_sched_table_id = EITS_START_TID;
+    epg_ctx->o_last_sched_table_id = OEITS_START_TID;
+
+    epg_ctx->pkt = av_packet_alloc();
+    return epg_ctx->pkt ? 0 : AVERROR(ENOMEM);
+}
+
+static av_cold int decode_end(AVCodecContext *avctx)
+{
+    EPGContext *epg_ctx = avctx->priv_data;
+    av_packet_free(&epg_ctx->pkt);
+    return 0;
+}
+
+static const AVClass epg_class = {
+    .class_name = "EPG Decoder",
+    .item_name  = av_default_item_name,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+AVCodec ff_epg_decoder = {
+    .name   = "epg",
+    .long_name  = NULL_IF_CONFIG_SMALL("EPG (Electronic Program Guide)"),
+    .type   = AVMEDIA_TYPE_DATA,
+    .id = AV_CODEC_ID_EPG,
+    .priv_data_size = sizeof(EPGContext),
+    .init = decode_init,
+    .close = decode_end,
+    .receive_frame = epg_receive_frame,
+    .priv_class = &epg_class,
+};
-- 
2.23.0