[FFmpeg-devel,3/4] V14 - SCTE-35 support in hlsenc

Submitted by Carlos Fernandez Sanz on Oct. 19, 2016, 12:36 a.m.

Details

Message ID 1476837390-20225-4-git-send-email-carlos@ccextractor.org
State New
Headers show

Commit Message

Carlos Fernandez Sanz Oct. 19, 2016, 12:36 a.m.
From: Carlos Fernandez <carlos@ccextractor.org>

Signed-off-by: Carlos Fernandez <carlos@ccextractor.org>
---
 libavformat/Makefile  |   2 +-
 libavformat/hlsenc.c  | 104 ++++++++--
 libavformat/scte_35.c | 527 ++++++++++++++++++++++++++++++++++++++++++++++++++
 libavformat/scte_35.h |  86 ++++++++
 4 files changed, 697 insertions(+), 22 deletions(-)
 create mode 100644 libavformat/scte_35.c
 create mode 100644 libavformat/scte_35.h

Comments

Carlos Fernandez Sanz Oct. 31, 2016, 6:58 p.m.
Hi,

Just a ping in case this can be revised and/or applied soonish - thanks.

CC'ing Carl Eugen and Thilo since I could discuss this a bit in person
at the GSoC summit. Thanks both for your time by the way.

Carlos

On Tue, Oct 18, 2016 at 5:36 PM, Carlos Fernandez Sanz
<carlos@ccextractor.org> wrote:
> From: Carlos Fernandez <carlos@ccextractor.org>
>
> Signed-off-by: Carlos Fernandez <carlos@ccextractor.org>
> ---
>  libavformat/Makefile  |   2 +-
>  libavformat/hlsenc.c  | 104 ++++++++--
>  libavformat/scte_35.c | 527 ++++++++++++++++++++++++++++++++++++++++++++++++++
>  libavformat/scte_35.h |  86 ++++++++
>  4 files changed, 697 insertions(+), 22 deletions(-)
>  create mode 100644 libavformat/scte_35.c
>  create mode 100644 libavformat/scte_35.h
>
> diff --git a/libavformat/Makefile b/libavformat/Makefile
> index 5d827d31..9218606 100644
> --- a/libavformat/Makefile
> +++ b/libavformat/Makefile
> @@ -205,7 +205,7 @@ OBJS-$(CONFIG_HDS_MUXER)                 += hdsenc.o
>  OBJS-$(CONFIG_HEVC_DEMUXER)              += hevcdec.o rawdec.o
>  OBJS-$(CONFIG_HEVC_MUXER)                += rawenc.o
>  OBJS-$(CONFIG_HLS_DEMUXER)               += hls.o
> -OBJS-$(CONFIG_HLS_MUXER)                 += hlsenc.o
> +OBJS-$(CONFIG_HLS_MUXER)                 += hlsenc.o scte_35.o
>  OBJS-$(CONFIG_HNM_DEMUXER)               += hnm.o
>  OBJS-$(CONFIG_ICO_DEMUXER)               += icodec.o
>  OBJS-$(CONFIG_ICO_MUXER)                 += icoenc.o
> diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
> index 9ca2df7..01e3237 100644
> --- a/libavformat/hlsenc.c
> +++ b/libavformat/hlsenc.c
> @@ -38,6 +38,7 @@
>  #include "avio_internal.h"
>  #include "internal.h"
>  #include "os_support.h"
> +#include "scte_35.h"
>
>  #define KEYSIZE 16
>  #define LINE_BUFFER_SIZE 1024
> @@ -48,6 +49,10 @@ typedef struct HLSSegment {
>      double duration; /* in seconds */
>      int64_t pos;
>      int64_t size;
> +    struct scte35_event *event;
> +    enum scte35_event_state event_state;
> +    int adv_count;
> +    int64_t start_pts;
>
>      char key_uri[LINE_BUFFER_SIZE + 1];
>      char iv_string[KEYSIZE*2 + 1];
> @@ -108,6 +113,8 @@ typedef struct HLSContext {
>      int nb_entries;
>      int discontinuity_set;
>
> +    int adv_count;
> +    struct scte35_interface *scte_iface;
>      HLSSegment *segments;
>      HLSSegment *last_segment;
>      HLSSegment *old_segments;
> @@ -241,6 +248,8 @@ static int hls_delete_old_segments(HLSContext *hls) {
>          av_freep(&path);
>          previous_segment = segment;
>          segment = previous_segment->next;
> +        if (hls->scte_iface)
> +            hls->scte_iface->unref_scte35_event(&previous_segment->event);
>          av_free(previous_segment);
>      }
>
> @@ -360,8 +369,8 @@ static int hls_mux_init(AVFormatContext *s)
>  }
>
>  /* Create a new segment and append it to the segment list */
> -static int hls_append_segment(struct AVFormatContext *s, HLSContext *hls, double duration,
> -                              int64_t pos, int64_t size)
> +static int hls_append_segment(struct AVFormatContext *s, HLSContext *hls, double duration, int64_t pos,
> +                              int64_t start_pts, struct scte35_event *event, int64_t size)
>  {
>      HLSSegment *en = av_malloc(sizeof(*en));
>      const char  *filename;
> @@ -384,9 +393,20 @@ static int hls_append_segment(struct AVFormatContext *s, HLSContext *hls, double
>
>      en->duration = duration;
>      en->pos      = pos;
> +    en->event    = event;
>      en->size     = size;
> +    en->start_pts  = start_pts;
>      en->next     = NULL;
>
> +    if (hls->scte_iface) {
> +        if (hls->scte_iface->event_state == EVENT_OUT_CONT)
> +            hls->adv_count++;
> +        else
> +            hls->adv_count = 0;
> +        en->event_state = hls->scte_iface->event_state;
> +    }
> +
> +
>      if (hls->key_info_file) {
>          av_strlcpy(en->key_uri, hls->key_uri, sizeof(en->key_uri));
>          av_strlcpy(en->iv_string, hls->iv_string, sizeof(en->iv_string));
> @@ -460,7 +480,7 @@ static int parse_playlist(AVFormatContext *s, const char *url)
>                  new_start_pos = avio_tell(hls->avf->pb);
>                  hls->size = new_start_pos - hls->start_pos;
>                  av_strlcpy(hls->avf->filename, line, sizeof(line));
> -                ret = hls_append_segment(s, hls, hls->duration, hls->start_pos, hls->size);
> +                ret = hls_append_segment(s, hls, hls->duration, hls->start_pos, 0, NULL, hls->size);
>                  if (ret < 0)
>                      goto fail;
>                  hls->start_pos = new_start_pos;
> @@ -590,9 +610,19 @@ static int hls_window(AVFormatContext *s, int last)
>              avio_printf(out, "#EXT-X-PROGRAM-DATE-TIME:%s.%03d%s\n", buf0, milli, buf1);
>              prog_date_time += en->duration;
>          }
> -        if (hls->baseurl)
> -            avio_printf(out, "%s", hls->baseurl);
> -        avio_printf(out, "%s\n", en->filename);
> +        if (hls->scte_iface && en->event) {
> +            char *str;
> +            char fname[1024] = "";
> +            if (hls->baseurl)
> +                strncat(fname, hls->baseurl, sizeof(fname)-1);
> +            strncat(fname, en->filename, sizeof(fname)-strlen(fname)-1);
> +            str = hls->scte_iface->get_hls_string(hls->scte_iface, en->event, fname, en->event_state, -1, en->start_pts);
> +            avio_printf(out, "%s", str);
> +        } else {
> +            if (hls->baseurl)
> +                avio_printf(out, "%s", hls->baseurl);
> +            avio_printf(out, "%s\n", en->filename);
> +        }
>      }
>
>      if (last && (hls->flags & HLS_OMIT_ENDLIST)==0)
> @@ -617,9 +647,20 @@ static int hls_window(AVFormatContext *s, int last)
>              if (byterange_mode)
>                   avio_printf(sub_out, "#EXT-X-BYTERANGE:%"PRIi64"@%"PRIi64"\n",
>                           en->size, en->pos);
> -            if (hls->baseurl)
> -                avio_printf(sub_out, "%s", hls->baseurl);
> -            avio_printf(sub_out, "%s\n", en->sub_filename);
> +            if (hls->scte_iface && en->event) {
> +                char *str;
> +                char fname[1024] = "";
> +                if (hls->baseurl)
> +                    strncat(fname, hls->baseurl, sizeof(fname)-1);
> +                strncat(fname, en->sub_filename, sizeof(fname)-strlen(fname)-1);
> +                str = hls->scte_iface->get_hls_string(hls->scte_iface, en->event, fname, en->event_state, en->adv_count, en->pos);
> +                avio_printf(out, "%s", str);
> +            } else {
> +                if (hls->baseurl)
> +                    avio_printf(out, "%s", hls->baseurl);
> +                avio_printf(sub_out, "%s\n", en->sub_filename);
> +            }
> +
>          }
>
>          if (last)
> @@ -769,6 +810,7 @@ static int hls_write_header(AVFormatContext *s)
>      AVDictionary *options = NULL;
>      int basename_size;
>      int vtt_basename_size;
> +    int ts_index = 0;
>
>      hls->sequence       = hls->start_sequence;
>      hls->recording_time = (hls->init_time ? hls->init_time : hls->time) * AV_TIME_BASE;
> @@ -901,7 +943,7 @@ static int hls_write_header(AVFormatContext *s)
>          goto fail;
>      }
>      //av_assert0(s->nb_streams == hls->avf->nb_streams);
> -    for (i = 0; i < s->nb_streams; i++) {
> +    for (ts_index = 0, i = 0; i < s->nb_streams; i++) {
>          AVStream *inner_st;
>          AVStream *outer_st = s->streams[i];
>
> @@ -914,16 +956,18 @@ static int hls_write_header(AVFormatContext *s)
>              }
>          }
>
> -        if (outer_st->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE)
> -            inner_st = hls->avf->streams[i];
> -        else if (hls->vtt_avf)
> +        if (hls->vtt_avf && outer_st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {
>              inner_st = hls->vtt_avf->streams[0];
> -        else {
> -            /* We have a subtitle stream, when the user does not want one */
> -            inner_st = NULL;
> +            avpriv_set_pts_info(outer_st, inner_st->pts_wrap_bits, inner_st->time_base.num, inner_st->time_base.den);
> +        } else if (outer_st->codecpar->codec_type == AVMEDIA_TYPE_DATA) {
> +            inner_st = hls->avf->streams[ts_index];
> +            hls->scte_iface = ff_alloc_scte35_parser(hls, outer_st->time_base);
>              continue;
> +        } else {
> +            inner_st = hls->avf->streams[ts_index];
> +            avpriv_set_pts_info(outer_st, inner_st->pts_wrap_bits, inner_st->time_base.num, inner_st->time_base.den);
> +            ts_index++;
>          }
> -        avpriv_set_pts_info(outer_st, inner_st->pts_wrap_bits, inner_st->time_base.num, inner_st->time_base.den);
>      }
>  fail:
>
> @@ -949,6 +993,12 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
>      int is_ref_pkt = 1;
>      int ret, can_split = 1;
>      int stream_index = 0;
> +    struct scte35_event *event = NULL;
> +
> +    if (st->codecpar->codec_id == AV_CODEC_ID_SCTE_35) {
> +        ret = ff_parse_scte35_pkt(hls->scte_iface, pkt);
> +        return ret;
> +    }
>
>      if (hls->sequence - hls->nb_entries > hls->start_sequence && hls->init_time > 0) {
>          /* reset end_pts, hls->recording_time at end of the init hls list */
> @@ -982,14 +1032,24 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
>          hls->duration = (double)(pkt->pts - hls->end_pts)
>                                     * st->time_base.num / st->time_base.den;
>
> -    if (can_split && av_compare_ts(pkt->pts - hls->start_pts, st->time_base,
> -                                   end_pts, AV_TIME_BASE_Q) >= 0) {
> +    if (hls->scte_iface)
> +        hls->scte_iface->update_video_pts(hls->scte_iface, pkt->pts * st->time_base.num / st->time_base.den);
> +
> +
> +    if (can_split && (( av_compare_ts(pkt->pts - hls->start_pts, st->time_base,
> +                                   end_pts, AV_TIME_BASE_Q) >= 0) ||
> +         (hls->scte_iface && hls->scte_iface->event_state == EVENT_OUT)) ) {
>          int64_t new_start_pos;
>          av_write_frame(oc, NULL); /* Flush any buffered data */
>
>          new_start_pos = avio_tell(hls->avf->pb);
>          hls->size = new_start_pos - hls->start_pos;
> -        ret = hls_append_segment(s, hls, hls->duration, hls->start_pos, hls->size);
> +        if (hls->scte_iface) {
> +            event = hls->scte_iface->update_event_state(hls->scte_iface);
> +            if (event)
> +                hls->scte_iface->ref_scte35_event(event);
> +        }
> +        ret = hls_append_segment(s, hls, hls->duration, hls->start_pos, pkt->pts, event, hls->size);
>          hls->start_pos = new_start_pos;
>          if (ret < 0)
>              return ret;
> @@ -1051,7 +1111,7 @@ static int hls_write_trailer(struct AVFormatContext *s)
>      if (oc->pb) {
>          hls->size = avio_tell(hls->avf->pb) - hls->start_pos;
>          ff_format_io_close(s, &oc->pb);
> -        hls_append_segment(s, hls, hls->duration, hls->start_pos, hls->size);
> +        hls_append_segment(s, hls, hls->duration, hls->start_pos, hls->end_pts, NULL, hls->size);
>      }
>
>      if (vtt_oc) {
> @@ -1072,6 +1132,7 @@ static int hls_write_trailer(struct AVFormatContext *s)
>          avformat_free_context(vtt_oc);
>      }
>
> +    ff_delete_scte35_parser(hls->scte_iface);
>      hls_free_segments(hls->segments);
>      hls_free_segments(hls->old_segments);
>      return 0;
> @@ -1128,6 +1189,7 @@ AVOutputFormat ff_hls_muxer = {
>      .audio_codec    = AV_CODEC_ID_AAC,
>      .video_codec    = AV_CODEC_ID_H264,
>      .subtitle_codec = AV_CODEC_ID_WEBVTT,
> +    .data_codec     = AV_CODEC_ID_SCTE_35,
>      .flags          = AVFMT_NOFILE | AVFMT_ALLOW_FLUSH,
>      .write_header   = hls_write_header,
>      .write_packet   = hls_write_packet,
> diff --git a/libavformat/scte_35.c b/libavformat/scte_35.c
> new file mode 100644
> index 0000000..5a9b571
> --- /dev/null
> +++ b/libavformat/scte_35.c
> @@ -0,0 +1,527 @@
> +/*
> + * SCTE 35 decoder
> + * Copyright (c) 2016 Carlos Fernandez
> + *
> + * 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
> + */
> +/*
> + * Reference Material Used
> + *
> + * ANSI/SCTE 35 2013 (Digital Program Insertion Cueing Message for Cable)
> + *
> + * SCTE 67 2014 (Recommended Practice for SCTE 35
> + *          Digital Program Insertion Cueing Message for Cable)
> + */
> +
> +
> +
> +#include "libavcodec/bytestream.h"
> +#include "libavcodec/avcodec.h"
> +#include "libavcodec/get_bits.h"
> +#include "libavutil/buffer_internal.h"
> +#include "libavutil/base64.h"
> +#include "scte_35.h"
> +
> +#define SCTE_CMD_NULL                  0x00
> +#define SCTE_CMD_SCHEDULE              0x04
> +#define SCTE_CMD_INSERT                0x05
> +#define SCTE_CMD_SIGNAL                0x06
> +#define SCTE_CMD_BANDWIDTH_RESERVATION 0x07
> +
> +
> +static char* get_hls_string(struct scte35_interface *iface, struct scte35_event *event,
> +                 const char *filename, int out_state, int seg_count, int64_t pos)
> +{
> +    int ret;
> +    av_bprint_clear(&iface->avbstr);
> +    if (out_state == EVENT_IN) {
> +        av_bprintf(&iface->avbstr, "#EXT-OATCLS-SCTE35:%s\n", iface->pkt_base64);
> +        av_bprintf(&iface->avbstr, "#EXT-X-CUE-IN\n");
> +        av_bprintf(&iface->avbstr, "#EXT-X-DISCONTINUITY\n");
> +    } else if (out_state == EVENT_OUT) {
> +        if (event) {
> +            av_bprintf(&iface->avbstr, "#EXT-OATCLS-SCTE35:%s\n", iface->pkt_base64);
> +            if (event->duration != AV_NOPTS_VALUE) {
> +                int64_t dur = ceil(((double)event->duration * iface->timebase.num) /iface->timebase.den);
> +                av_bprintf(&iface->avbstr, "#EXT-X-CUE-OUT:%"PRIu64"\n", dur);
> +            } else {
> +                av_bprintf(&iface->avbstr, "#EXT-X-CUE-OUT\n");
> +            }
> +            av_bprintf(&iface->avbstr, "#EXT-X-DISCONTINUITY\n");
> +        }
> +    } else if (out_state == EVENT_OUT_CONT) {
> +        if (event && event->duration != AV_NOPTS_VALUE) {
> +            int64_t dur = ceil(((double)event->duration * iface->timebase.num) /iface->timebase.den);
> +            int64_t elapsed_time = ceil(((double)pos * iface->timebase.num) /iface->timebase.den) - event->out_pts;
> +            av_bprintf(&iface->avbstr, "#EXT-X-CUE-OUT-CONT:ElapsedTime=%"PRIu64",Duration=%"PRIu64",SCTE35=%s\n",
> +                elapsed_time,  dur, iface->pkt_base64);
> +        } else {
> +            av_bprintf(&iface->avbstr, "#EXT-X-CUE-OUT-CONT:SCTE35=%s\n", iface->pkt_base64);
> +        }
> +    }
> +    if (seg_count >= 0)
> +        av_bprintf(&iface->avbstr, filename, seg_count);
> +    else
> +        av_bprintf(&iface->avbstr, "%s", filename);
> +    av_bprintf(&iface->avbstr, "\n");
> +
> +    ret = av_bprint_is_complete(&iface->avbstr);
> +    if (ret == 0) {
> +        av_log(iface->parent, AV_LOG_DEBUG, "Out of Memory");
> +        return NULL;
> +    }
> +
> +    av_log(iface->parent, AV_LOG_DEBUG, "%s", iface->avbstr.str);
> +    return iface->avbstr.str;
> +}
> +
> +static struct scte35_event* alloc_scte35_event(int id)
> +{
> +    struct scte35_event* event = av_malloc(sizeof(struct scte35_event));
> +    if (!event)
> +        return NULL;
> +
> +    event->id = id;
> +    event->in_pts = AV_NOPTS_VALUE;
> +    event->nearest_in_pts = AV_NOPTS_VALUE;
> +    event->out_pts = AV_NOPTS_VALUE;
> +    event->running = 0;
> +    event->next = NULL;
> +    event->prev = NULL;
> +    return event;
> +}
> +
> +static void ref_scte35_event(struct scte35_event *event)
> +{
> +    event->ref_count++;
> +}
> +
> +static void unref_scte35_event(struct scte35_event **event)
> +{
> +    if (!(*event))
> +        return;
> +    if (!(*event)->ref_count) {
> +        av_freep(event);
> +    } else {
> +        (*event)->ref_count--;
> +    }
> +}
> +
> +static void unlink_scte35_event(struct scte35_interface *iface, struct scte35_event *event)
> +{
> +    if (!event)
> +        return;
> +    if (!event->prev)
> +        iface->event_list = event->next;
> +    else
> +        event->prev->next = event->next;
> +    if (event->next)
> +        event->next->prev = event->prev;
> +    unref_scte35_event(&event);
> +}
> +
> +static struct scte35_event* get_event_id(struct scte35_interface *iface, int id)
> +{
> +    struct scte35_event *event = iface->event_list;
> +    struct scte35_event *pevent = NULL;
> +
> +    while(event) {
> +
> +        if (event->id == id)
> +            break;
> +        pevent = event;
> +        event = event->next;
> +    }
> +    if (!event) {
> +        event = alloc_scte35_event(id);
> +        if (pevent)
> +            pevent->next = event;
> +        else
> +            iface->event_list = event;
> +    }
> +
> +    return event;
> +}
> +
> +/**
> + * save the parsed time in ctx pts_time
> +   @return length of buffer consumed
> +*/
> +static int parse_splice_time(struct scte35_interface *iface, const uint8_t *buf, uint64_t *pts, int64_t pts_adjust)
> +{
> +    GetBitContext gb;
> +    int ret;
> +    init_get_bits(&gb, buf, 40);
> +    /* is time specified */
> +    ret =  get_bits(&gb, 1);
> +    if (ret) {
> +        skip_bits(&gb, 6);
> +        *pts = get_bits64(&gb,33) + pts_adjust;
> +        return 5;
> +    } else {
> +        skip_bits(&gb, 7);
> +        return 1;
> +    }
> +}
> +
> +static int parse_schedule_cmd(struct scte35_interface *iface, const uint8_t *buf)
> +{
> +    const uint8_t *sbuf = buf;
> +    av_log(iface->parent, AV_LOG_DEBUG, "Schedule cmd\n");
> +    return buf - sbuf;
> +}
> +/**
> +     @return length of buffer used
> + */
> +static int parse_insert_cmd(struct scte35_interface *iface,
> +    const uint8_t *buf,const int len, int64_t pts_adjust, int64_t current_pts)
> +{
> +    GetBitContext gb;
> +    int ret;
> +    const uint8_t *sbuf = buf;
> +    int program_splice_flag;
> +    int duration_flag;
> +    int splice_immediate_flag;
> +    int component_tag;
> +    int auto_return;
> +    uint16_t u_program_id;
> +    uint8_t avail_num;
> +    uint8_t avail_expect;
> +    int inout;
> +    int event_id;
> +    struct scte35_event *event;
> +    char buffer[128];
> +    int cancel;
> +
> +
> +    av_log(iface->parent, AV_LOG_DEBUG, "%s Insert cmd\n", buffer);
> +    event_id  = AV_RB32(buf);
> +    av_log(iface->parent, AV_LOG_DEBUG, "event_id  = %x\n", event_id);
> +    event = get_event_id(iface, event_id);
> +    buf +=4;
> +    cancel = *buf & 0x80;
> +    av_log(iface->parent, AV_LOG_DEBUG, "splice_event_cancel_indicator  = %d\n", cancel);
> +    buf++;
> +
> +    if (!cancel) {
> +        init_get_bits(&gb, buf, 8);
> +        inout =  get_bits(&gb, 1);
> +        program_splice_flag =  get_bits(&gb, 1);
> +        duration_flag =  get_bits(&gb, 1);
> +        splice_immediate_flag =  get_bits(&gb, 1);
> +        skip_bits(&gb, 4);
> +
> +    } else {
> +        /*   Delete event only if its not already started */
> +        if (!event->running) {
> +            unlink_scte35_event(iface, event);
> +        }
> +    }
> +    buf++;
> +
> +
> +    av_log(iface->parent, AV_LOG_DEBUG, "out_of_network_indicator  = %d\n", inout);
> +    av_log(iface->parent, AV_LOG_DEBUG, "program_splice_flag  = %d\n", program_splice_flag);
> +    av_log(iface->parent, AV_LOG_DEBUG, "duration_flag  = %d\n", duration_flag);
> +    av_log(iface->parent, AV_LOG_DEBUG, "splice_immediate_flag  = %d\n", splice_immediate_flag);
> +
> +    if (program_splice_flag &&  !splice_immediate_flag) {
> +        if (inout) {
> +            ret = parse_splice_time(iface, buf, &event->out_pts, pts_adjust);
> +            event->out_pts = event->out_pts * iface->timebase.num / iface->timebase.den;
> +        } else {
> +            ret = parse_splice_time(iface, buf, &event->in_pts, pts_adjust);
> +            event->in_pts = event->in_pts * iface->timebase.num / iface->timebase.den;
> +        }
> +
> +        buf += ret;
> +    } else if (program_splice_flag && splice_immediate_flag) {
> +        if (inout)
> +            event->out_pts = current_pts * iface->timebase.num / iface->timebase.den;
> +        else
> +            event->in_pts = current_pts * iface->timebase.num / iface->timebase.den;
> +    }
> +    if (program_splice_flag == 0) {
> +        int comp_cnt = *buf++;
> +        int  i;
> +        av_log(iface->parent, AV_LOG_DEBUG, "component_count  = %d\n", comp_cnt);
> +        for (i = 0; i < comp_cnt; i++) {
> +            component_tag = *buf++;
> +            av_log(iface->parent, AV_LOG_DEBUG, "component_tag  = %d\n", component_tag);
> +            if (splice_immediate_flag) {
> +                if (inout)
> +                    ret = parse_splice_time(iface, buf, &event->in_pts, pts_adjust);
> +                else
> +                    ret = parse_splice_time(iface, buf, &event->out_pts, pts_adjust);
> +                buf += ret;
> +            }
> +        }
> +    }
> +    if (duration_flag) {
> +        init_get_bits(&gb, buf, 40);
> +        auto_return =  get_bits(&gb, 1);
> +        av_log(iface->parent, AV_LOG_DEBUG, "autoreturn  = %d\n", auto_return);
> +        skip_bits(&gb, 6);
> +        event->duration = get_bits64(&gb,33) + pts_adjust;
> +        buf += 5;
> +    }
> +    u_program_id = AV_RB16(buf);
> +    av_log(iface->parent, AV_LOG_DEBUG, "u_program_id  = %hd\n", u_program_id);
> +    buf += 2;
> +    avail_num = *buf++;
> +    av_log(iface->parent, AV_LOG_DEBUG, "avail_num  = %hhd\n", avail_num);
> +    avail_expect = *buf++;
> +    av_log(iface->parent, AV_LOG_DEBUG, "avail_expect  = %hhd\n", avail_expect);
> +
> +    return buf - sbuf;
> +}
> +static int parse_time_signal_cmd(struct scte35_interface *iface, const uint8_t *buf)
> +{
> +    const uint8_t *sbuf = buf;
> +    av_log(iface->parent, AV_LOG_DEBUG, "Time Signal cmd\n");
> +    return buf - sbuf;
> +}
> +static int parse_bandwidth_reservation_cmd(struct scte35_interface *iface, const uint8_t *buf)
> +{
> +    const uint8_t *sbuf = buf;
> +    av_log(iface->parent, AV_LOG_DEBUG, "Band Width reservation cmd\n");
> +    return buf - sbuf;
> +}
> +
> +int ff_parse_scte35_pkt(struct scte35_interface *iface, const AVPacket *avpkt)
> +{
> +    const uint8_t *buf = avpkt->data;
> +    int section_length;
> +    int cmd_length;
> +    uint8_t cmd_type;
> +    int16_t tier;
> +    GetBitContext gb;
> +    int ret;
> +    int64_t pts_adjust;
> +
> +    if (!buf)
> +        return AVERROR_EOF;
> +
> +
> +    /* check table id */
> +    if (*buf != 0xfc)
> +        av_log(iface->parent, AV_LOG_ERROR, "Invalid SCTE packet\n");
> +
> +
> +    init_get_bits(&gb, buf + 1, 104);
> +
> +    /* section_syntax_indicator should be 0 */
> +    ret = get_bits(&gb,1);
> +    if (ret)
> +        av_log(iface->parent, AV_LOG_DEBUG, "Section indicator should be 0, since MPEG short sections are to be used.\n");
> +
> +    /* private indicator */
> +    ret = get_bits(&gb,1);
> +    if (ret)
> +        av_log(iface->parent, AV_LOG_DEBUG, "corrupt packet\n");
> +
> +    skip_bits(&gb,2);
> +
> +    /* section length may be there */
> +    section_length = get_bits(&gb,12);
> +    if (section_length > 4093)
> +    if (ret) {
> +        av_log(iface->parent, AV_LOG_ERROR, "Invalid length of section\n");
> +        return AVERROR_INVALIDDATA;
> +    }
> +
> +    av_base64_encode(iface->pkt_base64, AV_BASE64_SIZE(section_length + 3), buf, section_length + 3);
> +
> +    /* protocol version */
> +    skip_bits(&gb,8);
> +
> +    ret = get_bits(&gb,1);
> +    if (ret) {
> +        av_log(iface->parent, AV_LOG_ERROR, "Encrytion not yet supported\n");
> +        return AVERROR_PATCHWELCOME;
> +    }
> +    /* encryption algo */
> +    skip_bits(&gb,6);
> +
> +    pts_adjust =  get_bits64(&gb, 33);
> +
> +    /* cw_index: used in encryption */
> +    skip_bits(&gb,8);
> +
> +
> +    /* tier */
> +    tier = get_bits(&gb,12);
> +    if ((tier & 0xfff) == 0xfff)
> +        tier = -1;
> +
> +    cmd_length = get_bits(&gb,12);
> +    if (cmd_length == 0xfff) {
> +        /* Setting max limit to  cmd_len so it does not cross memory barrier */
> +        cmd_length = section_length - 17;
> +    } else if (cmd_length != 0xfff && (cmd_length > (section_length - 17) ) ) {
> +        av_log(iface->parent, AV_LOG_ERROR, "Command length %d invalid\n", cmd_length);
> +        return AVERROR_INVALIDDATA;
> +    }
> +
> +    cmd_type = get_bits(&gb,8);
> +    switch(cmd_type) {
> +    case SCTE_CMD_NULL:
> +        av_log(iface->parent, AV_LOG_DEBUG, "SCTE-35 Ping Received");
> +        break;
> +    case SCTE_CMD_SCHEDULE:
> +        ret = parse_schedule_cmd(iface, buf + 14);
> +        break;
> +    case SCTE_CMD_INSERT:
> +        ret = parse_insert_cmd(iface, buf + 14, cmd_length, pts_adjust, avpkt->pts);
> +        break;
> +    case SCTE_CMD_SIGNAL:
> +        ret = parse_time_signal_cmd(iface, buf + 14);
> +        break;
> +    case SCTE_CMD_BANDWIDTH_RESERVATION:
> +        ret = parse_bandwidth_reservation_cmd(iface, buf + 14);
> +        break;
> +    default:
> +        break;
> +    /* reserved yet */
> +    }
> +    if (ret < 0)
> +        goto fail;
> +    buf += ret;
> +
> +fail:
> +    return ret;
> +}
> +
> +/*
> + * return event, if there is any event whose starting time aka out_pts is less then
> + * current pts. This condition also means that event starting time has already been passed.
> + * This function will look for event in events list which resides inside iface.
> + */
> +static struct scte35_event* get_event_ciel_out(struct scte35_interface *iface, uint64_t pts)
> +{
> +    struct scte35_event *event = iface->event_list;
> +    while(event) {
> +        if (!event->running && event->out_pts < pts) {
> +            iface->event_state = EVENT_OUT;
> +            break;
> +        }
> +        event = event->next;
> +    }
> +    return event;
> +}
> +
> +/*
> + * return event, if current event is in running state
> + * and check that in_pts is less then current pts.
> + * Event from this function specify commercial ends and
> + * mainstream should be coupled in.
> + * This event is generally last event to be consumed.
> + */
> +static struct scte35_event* get_event_floor_in(struct scte35_interface *iface, uint64_t pts)
> +{
> +    struct scte35_event *event = iface->event_list;
> +    struct scte35_event *sevent = NULL;
> +    while(event) {
> +        if (event->in_pts != AV_NOPTS_VALUE && event->in_pts < pts &&
> +          (event->nearest_in_pts == AV_NOPTS_VALUE || pts <= event->nearest_in_pts) ) {
> +            event->nearest_in_pts = pts;
> +            unlink_scte35_event(iface, event);
> +            /* send in_event only when that event was in running state */
> +            if (event->running) {
> +                iface->event_state = EVENT_IN;
> +                sevent = event;
> +            }
> +        }
> +        event = event->next;
> +    }
> +    return sevent;
> +}
> +
> +
> +/*
> + *  If there is no running event, then search for an event which have
> + *  the pts matching to current pts. Otherwise only give event when
> + *  its time to end the commercial.
> + *  if we have some event to be presented at this video then cache it
> + *  for later use.
> + */
> +static void update_video_pts(struct scte35_interface *iface, uint64_t pts)
> +{
> +    struct scte35_event *event = NULL;
> +    if (iface->event_state == EVENT_NONE) {
> +        event = get_event_ciel_out(iface, pts);
> +        if (event)
> +            event->running = 1;
> +    } else {
> +        event = get_event_floor_in(iface, pts);
> +    }
> +    if (event)
> +        iface->current_event = event;
> +}
> +
> +/*
> + * update the state of scte-35 parser
> + * return current event
> + */
> +static struct scte35_event* update_event_state(struct scte35_interface *iface)
> +{
> +
> +    struct scte35_event* event = iface->current_event;
> +    if (iface->prev_event_state == EVENT_IN)
> +        iface->event_state = EVENT_NONE;
> +    else if (iface->prev_event_state == EVENT_OUT)
> +        iface->event_state = EVENT_OUT_CONT;
> +
> +    if (iface->event_state == EVENT_NONE)
> +        iface->current_event = NULL;
> +
> +    iface->prev_event_state = iface->event_state;
> +    return event;
> +}
> +
> +
> +/*
> + * Allocate scte35 parser
> + * using function pointer so that this module reveals least interface
> + * to API uses
> + */
> +struct scte35_interface* ff_alloc_scte35_parser(void *parent, AVRational timebase)
> +{
> +    struct scte35_interface* iface = av_mallocz(sizeof(struct scte35_interface));
> +    if (!iface)
> +       return NULL;
> +
> +    iface->parent = parent;
> +    iface->timebase = timebase;
> +    iface->update_video_pts = update_video_pts;
> +    iface->update_event_state = update_event_state;
> +    av_bprint_init(&iface->avbstr, 0, AV_BPRINT_SIZE_UNLIMITED);
> +    iface->get_hls_string = get_hls_string;
> +    iface->unref_scte35_event = unref_scte35_event;
> +    iface->ref_scte35_event = ref_scte35_event;
> +    iface->event_state = EVENT_NONE;
> +    iface->prev_event_state = EVENT_NONE;
> +    return iface;
> +}
> +
> +void ff_delete_scte35_parser(struct scte35_interface* iface)
> +{
> +    if (!iface)
> +        return;
> +    av_bprint_finalize(&iface->avbstr, NULL);
> +    av_freep(&iface);
> +}
> diff --git a/libavformat/scte_35.h b/libavformat/scte_35.h
> new file mode 100644
> index 0000000..a586594
> --- /dev/null
> +++ b/libavformat/scte_35.h
> @@ -0,0 +1,86 @@
> +/*
> + * SCTE-35 parser
> + * Copyright (c) 2016 Carlos Fernandez
> + *
> + * 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
> + */
> +#ifndef AVFORMAT_SCTE_35_H
> +#define AVFORMAT_SCTE_35_H
> +
> +#include "libavutil/bprint.h"
> +
> +struct scte35_event {
> +    /* ID given for each separate event */
> +    int32_t id;
> +    /* pts specify time when event starts */
> +    uint64_t in_pts;
> +    uint64_t nearest_in_pts;
> +    /* pts specify ehen events end */
> +    uint64_t out_pts;
> +    /* duration of the event */
> +    int64_t duration;
> +    int64_t start_pos;
> +    int running;
> +    int ref_count;
> +    /* to traverse the list of events */
> +    struct scte35_event *next;
> +    struct scte35_event *prev;
> +};
> +
> +enum scte35_event_state {
> +    /* NO event */
> +    EVENT_NONE,
> +    /* Commercials need to end */
> +    EVENT_IN,
> +    /* Commercials can start from here */
> +    EVENT_OUT,
> +    /* commercial can continue */
> +    EVENT_OUT_CONT,
> +};
> +
> +struct scte35_interface {
> +    /* contain all  the events */
> +    struct scte35_event *event_list;
> +    /* state of current event */
> +    enum scte35_event_state event_state;
> +    /* time base of pts used in parser */
> +    AVRational timebase;
> +    struct scte35_event *current_event;
> +    /* saved previous state to correctly transition
> +        the event state */
> +    int prev_event_state;
> +    //TODO use AV_BASE64_SIZE to dynamically allocate the array
> +    char pkt_base64[1024];
> +    /* keep context of its parent for log */
> +    void *parent;
> +    /* general purpose str */
> +    AVBPrint avbstr;
> +
> +    void (*update_video_pts)(struct scte35_interface *iface, uint64_t pts);
> +    struct scte35_event* (*update_event_state)(struct scte35_interface *iface);
> +    char* (*get_hls_string)(struct scte35_interface *iface, struct scte35_event *event,
> +               const char *adv_filename, int out_state, int seg_count, int64_t pos);
> +
> +    void (*unref_scte35_event)(struct scte35_event **event);
> +    void (*ref_scte35_event)(struct scte35_event *event);
> +};
> +
> +int ff_parse_scte35_pkt(struct scte35_interface *iface, const AVPacket *avpkt);
> +
> +struct scte35_interface* ff_alloc_scte35_parser(void *parent, AVRational timebase);
> +void ff_delete_scte35_parser(struct scte35_interface* iface);
> +#endif /* AVFORMAT_SCTE_35_H */
> --
> 2.7.4
>

Patch hide | download patch | download mbox

diff --git a/libavformat/Makefile b/libavformat/Makefile
index 5d827d31..9218606 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -205,7 +205,7 @@  OBJS-$(CONFIG_HDS_MUXER)                 += hdsenc.o
 OBJS-$(CONFIG_HEVC_DEMUXER)              += hevcdec.o rawdec.o
 OBJS-$(CONFIG_HEVC_MUXER)                += rawenc.o
 OBJS-$(CONFIG_HLS_DEMUXER)               += hls.o
-OBJS-$(CONFIG_HLS_MUXER)                 += hlsenc.o
+OBJS-$(CONFIG_HLS_MUXER)                 += hlsenc.o scte_35.o
 OBJS-$(CONFIG_HNM_DEMUXER)               += hnm.o
 OBJS-$(CONFIG_ICO_DEMUXER)               += icodec.o
 OBJS-$(CONFIG_ICO_MUXER)                 += icoenc.o
diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
index 9ca2df7..01e3237 100644
--- a/libavformat/hlsenc.c
+++ b/libavformat/hlsenc.c
@@ -38,6 +38,7 @@ 
 #include "avio_internal.h"
 #include "internal.h"
 #include "os_support.h"
+#include "scte_35.h"
 
 #define KEYSIZE 16
 #define LINE_BUFFER_SIZE 1024
@@ -48,6 +49,10 @@  typedef struct HLSSegment {
     double duration; /* in seconds */
     int64_t pos;
     int64_t size;
+    struct scte35_event *event;
+    enum scte35_event_state event_state;
+    int adv_count;
+    int64_t start_pts;
 
     char key_uri[LINE_BUFFER_SIZE + 1];
     char iv_string[KEYSIZE*2 + 1];
@@ -108,6 +113,8 @@  typedef struct HLSContext {
     int nb_entries;
     int discontinuity_set;
 
+    int adv_count;
+    struct scte35_interface *scte_iface;
     HLSSegment *segments;
     HLSSegment *last_segment;
     HLSSegment *old_segments;
@@ -241,6 +248,8 @@  static int hls_delete_old_segments(HLSContext *hls) {
         av_freep(&path);
         previous_segment = segment;
         segment = previous_segment->next;
+        if (hls->scte_iface)
+            hls->scte_iface->unref_scte35_event(&previous_segment->event);
         av_free(previous_segment);
     }
 
@@ -360,8 +369,8 @@  static int hls_mux_init(AVFormatContext *s)
 }
 
 /* Create a new segment and append it to the segment list */
-static int hls_append_segment(struct AVFormatContext *s, HLSContext *hls, double duration,
-                              int64_t pos, int64_t size)
+static int hls_append_segment(struct AVFormatContext *s, HLSContext *hls, double duration, int64_t pos,
+                              int64_t start_pts, struct scte35_event *event, int64_t size)
 {
     HLSSegment *en = av_malloc(sizeof(*en));
     const char  *filename;
@@ -384,9 +393,20 @@  static int hls_append_segment(struct AVFormatContext *s, HLSContext *hls, double
 
     en->duration = duration;
     en->pos      = pos;
+    en->event    = event;
     en->size     = size;
+    en->start_pts  = start_pts;
     en->next     = NULL;
 
+    if (hls->scte_iface) {
+        if (hls->scte_iface->event_state == EVENT_OUT_CONT)
+            hls->adv_count++;
+        else
+            hls->adv_count = 0;
+        en->event_state = hls->scte_iface->event_state;
+    }
+
+
     if (hls->key_info_file) {
         av_strlcpy(en->key_uri, hls->key_uri, sizeof(en->key_uri));
         av_strlcpy(en->iv_string, hls->iv_string, sizeof(en->iv_string));
@@ -460,7 +480,7 @@  static int parse_playlist(AVFormatContext *s, const char *url)
                 new_start_pos = avio_tell(hls->avf->pb);
                 hls->size = new_start_pos - hls->start_pos;
                 av_strlcpy(hls->avf->filename, line, sizeof(line));
-                ret = hls_append_segment(s, hls, hls->duration, hls->start_pos, hls->size);
+                ret = hls_append_segment(s, hls, hls->duration, hls->start_pos, 0, NULL, hls->size);
                 if (ret < 0)
                     goto fail;
                 hls->start_pos = new_start_pos;
@@ -590,9 +610,19 @@  static int hls_window(AVFormatContext *s, int last)
             avio_printf(out, "#EXT-X-PROGRAM-DATE-TIME:%s.%03d%s\n", buf0, milli, buf1);
             prog_date_time += en->duration;
         }
-        if (hls->baseurl)
-            avio_printf(out, "%s", hls->baseurl);
-        avio_printf(out, "%s\n", en->filename);
+        if (hls->scte_iface && en->event) {
+            char *str;
+            char fname[1024] = "";
+            if (hls->baseurl)
+                strncat(fname, hls->baseurl, sizeof(fname)-1);
+            strncat(fname, en->filename, sizeof(fname)-strlen(fname)-1);
+            str = hls->scte_iface->get_hls_string(hls->scte_iface, en->event, fname, en->event_state, -1, en->start_pts);
+            avio_printf(out, "%s", str);
+        } else {
+            if (hls->baseurl)
+                avio_printf(out, "%s", hls->baseurl);
+            avio_printf(out, "%s\n", en->filename);
+        }
     }
 
     if (last && (hls->flags & HLS_OMIT_ENDLIST)==0)
@@ -617,9 +647,20 @@  static int hls_window(AVFormatContext *s, int last)
             if (byterange_mode)
                  avio_printf(sub_out, "#EXT-X-BYTERANGE:%"PRIi64"@%"PRIi64"\n",
                          en->size, en->pos);
-            if (hls->baseurl)
-                avio_printf(sub_out, "%s", hls->baseurl);
-            avio_printf(sub_out, "%s\n", en->sub_filename);
+            if (hls->scte_iface && en->event) {
+                char *str;
+                char fname[1024] = "";
+                if (hls->baseurl)
+                    strncat(fname, hls->baseurl, sizeof(fname)-1);
+                strncat(fname, en->sub_filename, sizeof(fname)-strlen(fname)-1);
+                str = hls->scte_iface->get_hls_string(hls->scte_iface, en->event, fname, en->event_state, en->adv_count, en->pos);
+                avio_printf(out, "%s", str);
+            } else {
+                if (hls->baseurl)
+                    avio_printf(out, "%s", hls->baseurl);
+                avio_printf(sub_out, "%s\n", en->sub_filename);
+            }
+
         }
 
         if (last)
@@ -769,6 +810,7 @@  static int hls_write_header(AVFormatContext *s)
     AVDictionary *options = NULL;
     int basename_size;
     int vtt_basename_size;
+    int ts_index = 0;
 
     hls->sequence       = hls->start_sequence;
     hls->recording_time = (hls->init_time ? hls->init_time : hls->time) * AV_TIME_BASE;
@@ -901,7 +943,7 @@  static int hls_write_header(AVFormatContext *s)
         goto fail;
     }
     //av_assert0(s->nb_streams == hls->avf->nb_streams);
-    for (i = 0; i < s->nb_streams; i++) {
+    for (ts_index = 0, i = 0; i < s->nb_streams; i++) {
         AVStream *inner_st;
         AVStream *outer_st = s->streams[i];
 
@@ -914,16 +956,18 @@  static int hls_write_header(AVFormatContext *s)
             }
         }
 
-        if (outer_st->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE)
-            inner_st = hls->avf->streams[i];
-        else if (hls->vtt_avf)
+        if (hls->vtt_avf && outer_st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {
             inner_st = hls->vtt_avf->streams[0];
-        else {
-            /* We have a subtitle stream, when the user does not want one */
-            inner_st = NULL;
+            avpriv_set_pts_info(outer_st, inner_st->pts_wrap_bits, inner_st->time_base.num, inner_st->time_base.den);
+        } else if (outer_st->codecpar->codec_type == AVMEDIA_TYPE_DATA) {
+            inner_st = hls->avf->streams[ts_index];
+            hls->scte_iface = ff_alloc_scte35_parser(hls, outer_st->time_base);
             continue;
+        } else {
+            inner_st = hls->avf->streams[ts_index];
+            avpriv_set_pts_info(outer_st, inner_st->pts_wrap_bits, inner_st->time_base.num, inner_st->time_base.den);
+            ts_index++;
         }
-        avpriv_set_pts_info(outer_st, inner_st->pts_wrap_bits, inner_st->time_base.num, inner_st->time_base.den);
     }
 fail:
 
@@ -949,6 +993,12 @@  static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
     int is_ref_pkt = 1;
     int ret, can_split = 1;
     int stream_index = 0;
+    struct scte35_event *event = NULL;
+
+    if (st->codecpar->codec_id == AV_CODEC_ID_SCTE_35) {
+        ret = ff_parse_scte35_pkt(hls->scte_iface, pkt);
+        return ret;
+    }
 
     if (hls->sequence - hls->nb_entries > hls->start_sequence && hls->init_time > 0) {
         /* reset end_pts, hls->recording_time at end of the init hls list */
@@ -982,14 +1032,24 @@  static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
         hls->duration = (double)(pkt->pts - hls->end_pts)
                                    * st->time_base.num / st->time_base.den;
 
-    if (can_split && av_compare_ts(pkt->pts - hls->start_pts, st->time_base,
-                                   end_pts, AV_TIME_BASE_Q) >= 0) {
+    if (hls->scte_iface)
+        hls->scte_iface->update_video_pts(hls->scte_iface, pkt->pts * st->time_base.num / st->time_base.den);
+
+
+    if (can_split && (( av_compare_ts(pkt->pts - hls->start_pts, st->time_base,
+                                   end_pts, AV_TIME_BASE_Q) >= 0) ||
+         (hls->scte_iface && hls->scte_iface->event_state == EVENT_OUT)) ) {
         int64_t new_start_pos;
         av_write_frame(oc, NULL); /* Flush any buffered data */
 
         new_start_pos = avio_tell(hls->avf->pb);
         hls->size = new_start_pos - hls->start_pos;
-        ret = hls_append_segment(s, hls, hls->duration, hls->start_pos, hls->size);
+        if (hls->scte_iface) {
+            event = hls->scte_iface->update_event_state(hls->scte_iface);
+            if (event)
+                hls->scte_iface->ref_scte35_event(event);
+        }
+        ret = hls_append_segment(s, hls, hls->duration, hls->start_pos, pkt->pts, event, hls->size);
         hls->start_pos = new_start_pos;
         if (ret < 0)
             return ret;
@@ -1051,7 +1111,7 @@  static int hls_write_trailer(struct AVFormatContext *s)
     if (oc->pb) {
         hls->size = avio_tell(hls->avf->pb) - hls->start_pos;
         ff_format_io_close(s, &oc->pb);
-        hls_append_segment(s, hls, hls->duration, hls->start_pos, hls->size);
+        hls_append_segment(s, hls, hls->duration, hls->start_pos, hls->end_pts, NULL, hls->size);
     }
 
     if (vtt_oc) {
@@ -1072,6 +1132,7 @@  static int hls_write_trailer(struct AVFormatContext *s)
         avformat_free_context(vtt_oc);
     }
 
+    ff_delete_scte35_parser(hls->scte_iface);
     hls_free_segments(hls->segments);
     hls_free_segments(hls->old_segments);
     return 0;
@@ -1128,6 +1189,7 @@  AVOutputFormat ff_hls_muxer = {
     .audio_codec    = AV_CODEC_ID_AAC,
     .video_codec    = AV_CODEC_ID_H264,
     .subtitle_codec = AV_CODEC_ID_WEBVTT,
+    .data_codec     = AV_CODEC_ID_SCTE_35,
     .flags          = AVFMT_NOFILE | AVFMT_ALLOW_FLUSH,
     .write_header   = hls_write_header,
     .write_packet   = hls_write_packet,
diff --git a/libavformat/scte_35.c b/libavformat/scte_35.c
new file mode 100644
index 0000000..5a9b571
--- /dev/null
+++ b/libavformat/scte_35.c
@@ -0,0 +1,527 @@ 
+/*
+ * SCTE 35 decoder
+ * Copyright (c) 2016 Carlos Fernandez
+ *
+ * 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
+ */
+/*
+ * Reference Material Used
+ *
+ * ANSI/SCTE 35 2013 (Digital Program Insertion Cueing Message for Cable)
+ *
+ * SCTE 67 2014 (Recommended Practice for SCTE 35
+ *          Digital Program Insertion Cueing Message for Cable)
+ */
+
+
+
+#include "libavcodec/bytestream.h"
+#include "libavcodec/avcodec.h"
+#include "libavcodec/get_bits.h"
+#include "libavutil/buffer_internal.h"
+#include "libavutil/base64.h"
+#include "scte_35.h"
+
+#define SCTE_CMD_NULL                  0x00
+#define SCTE_CMD_SCHEDULE              0x04
+#define SCTE_CMD_INSERT                0x05
+#define SCTE_CMD_SIGNAL                0x06
+#define SCTE_CMD_BANDWIDTH_RESERVATION 0x07
+
+
+static char* get_hls_string(struct scte35_interface *iface, struct scte35_event *event,
+                 const char *filename, int out_state, int seg_count, int64_t pos)
+{
+    int ret;
+    av_bprint_clear(&iface->avbstr);
+    if (out_state == EVENT_IN) {
+        av_bprintf(&iface->avbstr, "#EXT-OATCLS-SCTE35:%s\n", iface->pkt_base64);
+        av_bprintf(&iface->avbstr, "#EXT-X-CUE-IN\n");
+        av_bprintf(&iface->avbstr, "#EXT-X-DISCONTINUITY\n");
+    } else if (out_state == EVENT_OUT) {
+        if (event) {
+            av_bprintf(&iface->avbstr, "#EXT-OATCLS-SCTE35:%s\n", iface->pkt_base64);
+            if (event->duration != AV_NOPTS_VALUE) {
+                int64_t dur = ceil(((double)event->duration * iface->timebase.num) /iface->timebase.den);
+                av_bprintf(&iface->avbstr, "#EXT-X-CUE-OUT:%"PRIu64"\n", dur);
+            } else {
+                av_bprintf(&iface->avbstr, "#EXT-X-CUE-OUT\n");
+            }
+            av_bprintf(&iface->avbstr, "#EXT-X-DISCONTINUITY\n");
+        }
+    } else if (out_state == EVENT_OUT_CONT) {
+        if (event && event->duration != AV_NOPTS_VALUE) {
+            int64_t dur = ceil(((double)event->duration * iface->timebase.num) /iface->timebase.den);
+            int64_t elapsed_time = ceil(((double)pos * iface->timebase.num) /iface->timebase.den) - event->out_pts;
+            av_bprintf(&iface->avbstr, "#EXT-X-CUE-OUT-CONT:ElapsedTime=%"PRIu64",Duration=%"PRIu64",SCTE35=%s\n",
+                elapsed_time,  dur, iface->pkt_base64);
+        } else {
+            av_bprintf(&iface->avbstr, "#EXT-X-CUE-OUT-CONT:SCTE35=%s\n", iface->pkt_base64);
+        }
+    }
+    if (seg_count >= 0)
+        av_bprintf(&iface->avbstr, filename, seg_count);
+    else
+        av_bprintf(&iface->avbstr, "%s", filename);
+    av_bprintf(&iface->avbstr, "\n");
+
+    ret = av_bprint_is_complete(&iface->avbstr);
+    if (ret == 0) {
+        av_log(iface->parent, AV_LOG_DEBUG, "Out of Memory");
+        return NULL;
+    }
+
+    av_log(iface->parent, AV_LOG_DEBUG, "%s", iface->avbstr.str);
+    return iface->avbstr.str;
+}
+
+static struct scte35_event* alloc_scte35_event(int id)
+{
+    struct scte35_event* event = av_malloc(sizeof(struct scte35_event));
+    if (!event)
+        return NULL;
+
+    event->id = id;
+    event->in_pts = AV_NOPTS_VALUE;
+    event->nearest_in_pts = AV_NOPTS_VALUE;
+    event->out_pts = AV_NOPTS_VALUE;
+    event->running = 0;
+    event->next = NULL;
+    event->prev = NULL;
+    return event;
+}
+
+static void ref_scte35_event(struct scte35_event *event)
+{
+    event->ref_count++;
+}
+
+static void unref_scte35_event(struct scte35_event **event)
+{
+    if (!(*event))
+        return;
+    if (!(*event)->ref_count) {
+        av_freep(event);
+    } else {
+        (*event)->ref_count--;
+    }
+}
+
+static void unlink_scte35_event(struct scte35_interface *iface, struct scte35_event *event)
+{
+    if (!event)
+        return;
+    if (!event->prev)
+        iface->event_list = event->next;
+    else
+        event->prev->next = event->next;
+    if (event->next)
+        event->next->prev = event->prev;
+    unref_scte35_event(&event);
+}
+
+static struct scte35_event* get_event_id(struct scte35_interface *iface, int id)
+{
+    struct scte35_event *event = iface->event_list;
+    struct scte35_event *pevent = NULL;
+
+    while(event) {
+
+        if (event->id == id)
+            break;
+        pevent = event;
+        event = event->next;
+    }
+    if (!event) {
+        event = alloc_scte35_event(id);
+        if (pevent)
+            pevent->next = event;
+        else
+            iface->event_list = event;
+    }
+
+    return event;
+}
+
+/**
+ * save the parsed time in ctx pts_time
+   @return length of buffer consumed
+*/
+static int parse_splice_time(struct scte35_interface *iface, const uint8_t *buf, uint64_t *pts, int64_t pts_adjust)
+{
+    GetBitContext gb;
+    int ret;
+    init_get_bits(&gb, buf, 40);
+    /* is time specified */
+    ret =  get_bits(&gb, 1);
+    if (ret) {
+        skip_bits(&gb, 6);
+        *pts = get_bits64(&gb,33) + pts_adjust;
+        return 5;
+    } else {
+        skip_bits(&gb, 7);
+        return 1;
+    }
+}
+
+static int parse_schedule_cmd(struct scte35_interface *iface, const uint8_t *buf)
+{
+    const uint8_t *sbuf = buf;
+    av_log(iface->parent, AV_LOG_DEBUG, "Schedule cmd\n");
+    return buf - sbuf;
+}
+/**
+     @return length of buffer used
+ */
+static int parse_insert_cmd(struct scte35_interface *iface,
+    const uint8_t *buf,const int len, int64_t pts_adjust, int64_t current_pts)
+{
+    GetBitContext gb;
+    int ret;
+    const uint8_t *sbuf = buf;
+    int program_splice_flag;
+    int duration_flag;
+    int splice_immediate_flag;
+    int component_tag;
+    int auto_return;
+    uint16_t u_program_id;
+    uint8_t avail_num;
+    uint8_t avail_expect;
+    int inout;
+    int event_id;
+    struct scte35_event *event;
+    char buffer[128];
+    int cancel;
+
+
+    av_log(iface->parent, AV_LOG_DEBUG, "%s Insert cmd\n", buffer);
+    event_id  = AV_RB32(buf);
+    av_log(iface->parent, AV_LOG_DEBUG, "event_id  = %x\n", event_id);
+    event = get_event_id(iface, event_id);
+    buf +=4;
+    cancel = *buf & 0x80;
+    av_log(iface->parent, AV_LOG_DEBUG, "splice_event_cancel_indicator  = %d\n", cancel);
+    buf++;
+
+    if (!cancel) {
+        init_get_bits(&gb, buf, 8);
+        inout =  get_bits(&gb, 1);
+        program_splice_flag =  get_bits(&gb, 1);
+        duration_flag =  get_bits(&gb, 1);
+        splice_immediate_flag =  get_bits(&gb, 1);
+        skip_bits(&gb, 4);
+
+    } else {
+        /*   Delete event only if its not already started */
+        if (!event->running) {
+            unlink_scte35_event(iface, event);
+        }
+    }
+    buf++;
+
+
+    av_log(iface->parent, AV_LOG_DEBUG, "out_of_network_indicator  = %d\n", inout);
+    av_log(iface->parent, AV_LOG_DEBUG, "program_splice_flag  = %d\n", program_splice_flag);
+    av_log(iface->parent, AV_LOG_DEBUG, "duration_flag  = %d\n", duration_flag);
+    av_log(iface->parent, AV_LOG_DEBUG, "splice_immediate_flag  = %d\n", splice_immediate_flag);
+
+    if (program_splice_flag &&  !splice_immediate_flag) {
+        if (inout) {
+            ret = parse_splice_time(iface, buf, &event->out_pts, pts_adjust);
+            event->out_pts = event->out_pts * iface->timebase.num / iface->timebase.den;
+        } else {
+            ret = parse_splice_time(iface, buf, &event->in_pts, pts_adjust);
+            event->in_pts = event->in_pts * iface->timebase.num / iface->timebase.den;
+        }
+
+        buf += ret;
+    } else if (program_splice_flag && splice_immediate_flag) {
+        if (inout)
+            event->out_pts = current_pts * iface->timebase.num / iface->timebase.den;
+        else
+            event->in_pts = current_pts * iface->timebase.num / iface->timebase.den;
+    }
+    if (program_splice_flag == 0) {
+        int comp_cnt = *buf++;
+        int  i;
+        av_log(iface->parent, AV_LOG_DEBUG, "component_count  = %d\n", comp_cnt);
+        for (i = 0; i < comp_cnt; i++) {
+            component_tag = *buf++;
+            av_log(iface->parent, AV_LOG_DEBUG, "component_tag  = %d\n", component_tag);
+            if (splice_immediate_flag) {
+                if (inout)
+                    ret = parse_splice_time(iface, buf, &event->in_pts, pts_adjust);
+                else
+                    ret = parse_splice_time(iface, buf, &event->out_pts, pts_adjust);
+                buf += ret;
+            }
+        }
+    }
+    if (duration_flag) {
+        init_get_bits(&gb, buf, 40);
+        auto_return =  get_bits(&gb, 1);
+        av_log(iface->parent, AV_LOG_DEBUG, "autoreturn  = %d\n", auto_return);
+        skip_bits(&gb, 6);
+        event->duration = get_bits64(&gb,33) + pts_adjust;
+        buf += 5;
+    }
+    u_program_id = AV_RB16(buf);
+    av_log(iface->parent, AV_LOG_DEBUG, "u_program_id  = %hd\n", u_program_id);
+    buf += 2;
+    avail_num = *buf++;
+    av_log(iface->parent, AV_LOG_DEBUG, "avail_num  = %hhd\n", avail_num);
+    avail_expect = *buf++;
+    av_log(iface->parent, AV_LOG_DEBUG, "avail_expect  = %hhd\n", avail_expect);
+
+    return buf - sbuf;
+}
+static int parse_time_signal_cmd(struct scte35_interface *iface, const uint8_t *buf)
+{
+    const uint8_t *sbuf = buf;
+    av_log(iface->parent, AV_LOG_DEBUG, "Time Signal cmd\n");
+    return buf - sbuf;
+}
+static int parse_bandwidth_reservation_cmd(struct scte35_interface *iface, const uint8_t *buf)
+{
+    const uint8_t *sbuf = buf;
+    av_log(iface->parent, AV_LOG_DEBUG, "Band Width reservation cmd\n");
+    return buf - sbuf;
+}
+
+int ff_parse_scte35_pkt(struct scte35_interface *iface, const AVPacket *avpkt)
+{
+    const uint8_t *buf = avpkt->data;
+    int section_length;
+    int cmd_length;
+    uint8_t cmd_type;
+    int16_t tier;
+    GetBitContext gb;
+    int ret;
+    int64_t pts_adjust;
+
+    if (!buf)
+        return AVERROR_EOF;
+
+
+    /* check table id */
+    if (*buf != 0xfc)
+        av_log(iface->parent, AV_LOG_ERROR, "Invalid SCTE packet\n");
+
+
+    init_get_bits(&gb, buf + 1, 104);
+
+    /* section_syntax_indicator should be 0 */
+    ret = get_bits(&gb,1);
+    if (ret)
+        av_log(iface->parent, AV_LOG_DEBUG, "Section indicator should be 0, since MPEG short sections are to be used.\n");
+
+    /* private indicator */
+    ret = get_bits(&gb,1);
+    if (ret)
+        av_log(iface->parent, AV_LOG_DEBUG, "corrupt packet\n");
+
+    skip_bits(&gb,2);
+
+    /* section length may be there */
+    section_length = get_bits(&gb,12);
+    if (section_length > 4093)
+    if (ret) {
+        av_log(iface->parent, AV_LOG_ERROR, "Invalid length of section\n");
+        return AVERROR_INVALIDDATA;
+    }
+
+    av_base64_encode(iface->pkt_base64, AV_BASE64_SIZE(section_length + 3), buf, section_length + 3);
+
+    /* protocol version */
+    skip_bits(&gb,8);
+
+    ret = get_bits(&gb,1);
+    if (ret) {
+        av_log(iface->parent, AV_LOG_ERROR, "Encrytion not yet supported\n");
+        return AVERROR_PATCHWELCOME;
+    }
+    /* encryption algo */
+    skip_bits(&gb,6);
+
+    pts_adjust =  get_bits64(&gb, 33);
+
+    /* cw_index: used in encryption */
+    skip_bits(&gb,8);
+
+
+    /* tier */
+    tier = get_bits(&gb,12);
+    if ((tier & 0xfff) == 0xfff)
+        tier = -1;
+
+    cmd_length = get_bits(&gb,12);
+    if (cmd_length == 0xfff) {
+        /* Setting max limit to  cmd_len so it does not cross memory barrier */
+        cmd_length = section_length - 17;
+    } else if (cmd_length != 0xfff && (cmd_length > (section_length - 17) ) ) {
+        av_log(iface->parent, AV_LOG_ERROR, "Command length %d invalid\n", cmd_length);
+        return AVERROR_INVALIDDATA;
+    }
+
+    cmd_type = get_bits(&gb,8);
+    switch(cmd_type) {
+    case SCTE_CMD_NULL:
+        av_log(iface->parent, AV_LOG_DEBUG, "SCTE-35 Ping Received");
+        break;
+    case SCTE_CMD_SCHEDULE:
+        ret = parse_schedule_cmd(iface, buf + 14);
+        break;
+    case SCTE_CMD_INSERT:
+        ret = parse_insert_cmd(iface, buf + 14, cmd_length, pts_adjust, avpkt->pts);
+        break;
+    case SCTE_CMD_SIGNAL:
+        ret = parse_time_signal_cmd(iface, buf + 14);
+        break;
+    case SCTE_CMD_BANDWIDTH_RESERVATION:
+        ret = parse_bandwidth_reservation_cmd(iface, buf + 14);
+        break;
+    default:
+        break;
+    /* reserved yet */
+    }
+    if (ret < 0)
+        goto fail;
+    buf += ret;
+
+fail:
+    return ret;
+}
+
+/*
+ * return event, if there is any event whose starting time aka out_pts is less then
+ * current pts. This condition also means that event starting time has already been passed.
+ * This function will look for event in events list which resides inside iface.
+ */
+static struct scte35_event* get_event_ciel_out(struct scte35_interface *iface, uint64_t pts)
+{
+    struct scte35_event *event = iface->event_list;
+    while(event) {
+        if (!event->running && event->out_pts < pts) {
+            iface->event_state = EVENT_OUT;
+            break;
+        }
+        event = event->next;
+    }
+    return event;
+}
+
+/*
+ * return event, if current event is in running state
+ * and check that in_pts is less then current pts.
+ * Event from this function specify commercial ends and
+ * mainstream should be coupled in.
+ * This event is generally last event to be consumed.
+ */
+static struct scte35_event* get_event_floor_in(struct scte35_interface *iface, uint64_t pts)
+{
+    struct scte35_event *event = iface->event_list;
+    struct scte35_event *sevent = NULL;
+    while(event) {
+        if (event->in_pts != AV_NOPTS_VALUE && event->in_pts < pts &&
+          (event->nearest_in_pts == AV_NOPTS_VALUE || pts <= event->nearest_in_pts) ) {
+            event->nearest_in_pts = pts;
+            unlink_scte35_event(iface, event);
+            /* send in_event only when that event was in running state */
+            if (event->running) {
+                iface->event_state = EVENT_IN;
+                sevent = event;
+            }
+        }
+        event = event->next;
+    }
+    return sevent;
+}
+
+
+/*
+ *  If there is no running event, then search for an event which have
+ *  the pts matching to current pts. Otherwise only give event when
+ *  its time to end the commercial.
+ *  if we have some event to be presented at this video then cache it
+ *  for later use.
+ */
+static void update_video_pts(struct scte35_interface *iface, uint64_t pts)
+{
+    struct scte35_event *event = NULL;
+    if (iface->event_state == EVENT_NONE) {
+        event = get_event_ciel_out(iface, pts);
+        if (event)
+            event->running = 1;
+    } else {
+        event = get_event_floor_in(iface, pts);
+    }
+    if (event)
+        iface->current_event = event;
+}
+
+/*
+ * update the state of scte-35 parser
+ * return current event
+ */
+static struct scte35_event* update_event_state(struct scte35_interface *iface)
+{
+
+    struct scte35_event* event = iface->current_event;
+    if (iface->prev_event_state == EVENT_IN)
+        iface->event_state = EVENT_NONE;
+    else if (iface->prev_event_state == EVENT_OUT)
+        iface->event_state = EVENT_OUT_CONT;
+
+    if (iface->event_state == EVENT_NONE)
+        iface->current_event = NULL;
+
+    iface->prev_event_state = iface->event_state;
+    return event;
+}
+
+
+/*
+ * Allocate scte35 parser
+ * using function pointer so that this module reveals least interface
+ * to API uses
+ */
+struct scte35_interface* ff_alloc_scte35_parser(void *parent, AVRational timebase)
+{
+    struct scte35_interface* iface = av_mallocz(sizeof(struct scte35_interface));
+    if (!iface)
+       return NULL;
+
+    iface->parent = parent;
+    iface->timebase = timebase;
+    iface->update_video_pts = update_video_pts;
+    iface->update_event_state = update_event_state;
+    av_bprint_init(&iface->avbstr, 0, AV_BPRINT_SIZE_UNLIMITED);
+    iface->get_hls_string = get_hls_string;
+    iface->unref_scte35_event = unref_scte35_event;
+    iface->ref_scte35_event = ref_scte35_event;
+    iface->event_state = EVENT_NONE;
+    iface->prev_event_state = EVENT_NONE;
+    return iface;
+}
+
+void ff_delete_scte35_parser(struct scte35_interface* iface)
+{
+    if (!iface)
+        return;
+    av_bprint_finalize(&iface->avbstr, NULL);
+    av_freep(&iface);
+}
diff --git a/libavformat/scte_35.h b/libavformat/scte_35.h
new file mode 100644
index 0000000..a586594
--- /dev/null
+++ b/libavformat/scte_35.h
@@ -0,0 +1,86 @@ 
+/*
+ * SCTE-35 parser
+ * Copyright (c) 2016 Carlos Fernandez
+ *
+ * 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
+ */
+#ifndef AVFORMAT_SCTE_35_H
+#define AVFORMAT_SCTE_35_H
+
+#include "libavutil/bprint.h"
+
+struct scte35_event {
+    /* ID given for each separate event */
+    int32_t id;
+    /* pts specify time when event starts */
+    uint64_t in_pts;
+    uint64_t nearest_in_pts;
+    /* pts specify ehen events end */
+    uint64_t out_pts;
+    /* duration of the event */
+    int64_t duration;
+    int64_t start_pos;
+    int running;
+    int ref_count;
+    /* to traverse the list of events */
+    struct scte35_event *next;
+    struct scte35_event *prev;
+};
+
+enum scte35_event_state {
+    /* NO event */
+    EVENT_NONE,
+    /* Commercials need to end */
+    EVENT_IN,
+    /* Commercials can start from here */
+    EVENT_OUT,
+    /* commercial can continue */
+    EVENT_OUT_CONT,
+};
+
+struct scte35_interface {
+    /* contain all  the events */
+    struct scte35_event *event_list;
+    /* state of current event */
+    enum scte35_event_state event_state;
+    /* time base of pts used in parser */
+    AVRational timebase;
+    struct scte35_event *current_event;
+    /* saved previous state to correctly transition
+        the event state */
+    int prev_event_state;
+    //TODO use AV_BASE64_SIZE to dynamically allocate the array
+    char pkt_base64[1024];
+    /* keep context of its parent for log */
+    void *parent;
+    /* general purpose str */
+    AVBPrint avbstr;
+
+    void (*update_video_pts)(struct scte35_interface *iface, uint64_t pts);
+    struct scte35_event* (*update_event_state)(struct scte35_interface *iface);
+    char* (*get_hls_string)(struct scte35_interface *iface, struct scte35_event *event,
+               const char *adv_filename, int out_state, int seg_count, int64_t pos);
+
+    void (*unref_scte35_event)(struct scte35_event **event);
+    void (*ref_scte35_event)(struct scte35_event *event);
+};
+
+int ff_parse_scte35_pkt(struct scte35_interface *iface, const AVPacket *avpkt);
+
+struct scte35_interface* ff_alloc_scte35_parser(void *parent, AVRational timebase);
+void ff_delete_scte35_parser(struct scte35_interface* iface);
+#endif /* AVFORMAT_SCTE_35_H */