[FFmpeg-devel] [PATCH 3/4] V8 - SCTE-35 support in hlsenc
Carlos Fernandez Sanz
carlos at ccextractor.org
Wed Aug 31 20:39:38 EEST 2016
From: Carlos Fernandez <carlos at ccextractor.org>
Signed-off-by: Carlos Fernandez <carlos at ccextractor.org>
---
libavformat/Makefile | 2 +-
libavformat/hlsenc.c | 110 ++++++++---
libavformat/scte_35.c | 527 ++++++++++++++++++++++++++++++++++++++++++++++++++
libavformat/scte_35.h | 86 ++++++++
4 files changed, 702 insertions(+), 23 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 81b7efa..25092f7 100644
--- a/libavformat/hlsenc.c
+++ b/libavformat/hlsenc.c
@@ -1,4 +1,4 @@
-/*
+ /*
* Apple HTTP Live Streaming segmenter
* Copyright (c) 2012, Luca Barbato
*
@@ -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 scte_35_event *event;
+ int out;
+ int adv_count;
+ int64_t start_pts;
char key_uri[LINE_BUFFER_SIZE + 1];
char iv_string[KEYSIZE*2 + 1];
@@ -91,6 +96,8 @@ typedef struct HLSContext {
uint32_t flags; // enum HLSFlags
uint32_t pl_type; // enum PlaylistType
char *segment_filename;
+ char *adv_filename;
+ char *adv_subfilename;
int use_localtime; ///< flag to expand filename with localtime
int use_localtime_mkdir;///< flag to mkdir dirname in timebased filename
@@ -106,6 +113,8 @@ typedef struct HLSContext {
int nb_entries;
int discontinuity_set;
+ int adv_count;
+ struct scte_35_interface *scte_iface;
HLSSegment *segments;
HLSSegment *last_segment;
HLSSegment *old_segments;
@@ -205,6 +214,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);
}
@@ -324,8 +335,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 scte_35_event *event, int64_t size)
{
HLSSegment *en = av_malloc(sizeof(*en));
char *tmp, *p;
@@ -361,9 +372,23 @@ 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) {
+ en->adv_count = hls->adv_count;;
+ hls->adv_count++;
+ en->out = hls->scte_iface->event_state;
+ } else {
+ hls->adv_count = 0;
+ en->out = 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));
@@ -434,7 +459,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;
@@ -533,9 +558,23 @@ static int hls_window(AVFormatContext *s, int last)
if (hls->flags & HLS_SINGLE_FILE)
avio_printf(out, "#EXT-X-BYTERANGE:%"PRIi64"@%"PRIi64"\n",
en->size, en->pos);
- if (hls->baseurl)
- avio_printf(out, "%s", hls->baseurl);
- avio_printf(out, "%s\n", en->filename);
+ if (hls->scte_iface && (en->event || en->out) ) {
+ char *str;
+ char fname[1024] = "";
+ if (hls->adv_filename) {
+ str = hls->scte_iface->get_hls_string(hls->scte_iface, en->event, hls->adv_filename, en->out, en->adv_count, en->start_pts);
+ } else {
+ if (hls->baseurl)
+ strncat(fname, hls->baseurl, 1024);
+ strncat(fname, en->filename, 1024);
+ str = hls->scte_iface->get_hls_string(hls->scte_iface, en->event, fname, en->out, -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)
@@ -560,9 +599,15 @@ static int hls_window(AVFormatContext *s, int last)
if (hls->flags & HLS_SINGLE_FILE)
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 || en->out) ) {
+ char *str = hls->scte_iface->get_hls_string(hls->scte_iface, en->event, hls->adv_subfilename, en->out, 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)
@@ -705,6 +750,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;
@@ -831,19 +877,21 @@ 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];
- 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:
@@ -869,6 +917,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 scte_35_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 */
@@ -902,14 +956,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;
@@ -956,7 +1020,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) {
@@ -977,6 +1041,7 @@ static int hls_write_trailer(struct AVFormatContext *s)
hls->avf = NULL;
hls_window(s, 1);
+ ff_delete_scte35_parser(hls->scte_iface);
hls_free_segments(hls->segments);
hls_free_segments(hls->old_segments);
return 0;
@@ -1031,6 +1096,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..64e5f67
--- /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
+ */
+/*
+ * Refrence 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 scte_35_interface *iface, struct scte_35_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 scte_35_event* alloc_scte35_event(int id)
+{
+ struct scte_35_event* event = av_malloc(sizeof(struct scte_35_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 scte_35_event *event)
+{
+ event->ref_count++;
+}
+
+static void unref_scte35_event(struct scte_35_event **event)
+{
+ if(!(*event))
+ return;
+ if(!(*event)->ref_count) {
+ av_freep(event);
+ } else {
+ (*event)->ref_count--;
+ }
+}
+
+static void unlink_scte35_event(struct scte_35_interface *iface, struct scte_35_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 scte_35_event* get_event_id(struct scte_35_interface *iface, int id)
+{
+ struct scte_35_event *event = iface->event_list;
+ struct scte_35_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 scte_35_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 scte_35_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 scte_35_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 scte_35_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 scte_35_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 scte_35_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 scte_35_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 Recieved");
+ 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 scte_35_event* get_event_ciel_out(struct scte_35_interface *iface, uint64_t pts)
+{
+ struct scte_35_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 commerial ends and
+ * mainstream should be coupled in.
+ * This event is generally last event to be consumed.
+ */
+static struct scte_35_event* get_event_floor_in(struct scte_35_interface *iface, uint64_t pts)
+{
+ struct scte_35_event *event = iface->event_list;
+ struct scte_35_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 scte_35_interface *iface, uint64_t pts)
+{
+ struct scte_35_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 scte_35_event* update_event_state(struct scte_35_interface *iface)
+{
+
+ struct scte_35_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 scte_35 parser
+ * using function pointer so that this module reveals least interface
+ * to API uses
+ */
+struct scte_35_interface* ff_alloc_scte35_parser(void *parent, AVRational timebase)
+{
+ struct scte_35_interface* iface = av_mallocz(sizeof(struct scte_35_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 scte_35_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..cac49ea
--- /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 scte_35_event {
+ /* ID given for each seprate 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 scte_35_event *next;
+ struct scte_35_event *prev;
+};
+
+enum 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 scte_35_interface {
+ /* contain all the events */
+ struct scte_35_event *event_list;
+ /* state of current event */
+ enum event_state event_state;
+ /* time base of pts used in parser */
+ AVRational timebase;
+ struct scte_35_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 scte_35_interface *iface, uint64_t pts);
+ struct scte_35_event* (*update_event_state)(struct scte_35_interface *iface);
+ char* (*get_hls_string)(struct scte_35_interface *iface, struct scte_35_event *event,
+ const char *adv_filename, int out_state, int seg_count, int64_t pos);
+
+ void (*unref_scte35_event)(struct scte_35_event **event);
+ void (*ref_scte35_event)(struct scte_35_event *event);
+};
+
+int ff_parse_scte35_pkt(struct scte_35_interface *iface, const AVPacket *avpkt);
+
+struct scte_35_interface* ff_alloc_scte35_parser(void *parent, AVRational timebase);
+void ff_delete_scte35_parser(struct scte_35_interface* iface);
+#endif
--
2.7.4
More information about the ffmpeg-devel
mailing list