[FFmpeg-devel] [PATCH 2/2] SCTE-35 support in hlsenc
Steven Liu
lingjiujianke at gmail.com
Fri Jul 22 04:11:20 EEST 2016
2016-07-22 6:31 GMT+08:00 Carlos Fernandez Sanz <carlos at ccextractor.org>:
> From: carlos <carlos at ccextractor.org>
>
> Signed-off-by: carlos <carlos at ccextractor.org>
> ---
> libavformat/Makefile | 1 +
> libavformat/hlsenc.c | 179 +++++++++++-------
> libavformat/scte_35.c | 489
> ++++++++++++++++++++++++++++++++++++++++++++++++++
> libavformat/scte_35.h | 76 ++++++++
> 4 files changed, 680 insertions(+), 65 deletions(-)
> create mode 100644 libavformat/scte_35.c
> create mode 100644 libavformat/scte_35.h
>
> diff --git a/libavformat/Makefile b/libavformat/Makefile
> index c3f38b4..59f5046 100644
> --- a/libavformat/Makefile
> +++ b/libavformat/Makefile
> @@ -21,6 +21,7 @@ OBJS = allformats.o \
> qtpalette.o \
> protocols.o \
> riff.o \
> + scte_35.o \
> sdp.o \
> url.o \
> utils.o \
> diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
> index 5dc518d..0b1052b 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 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];
> @@ -89,6 +94,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
> @@ -104,6 +111,7 @@ typedef struct HLSContext {
> int nb_entries;
> int discontinuity_set;
>
> + struct scte_35_interface *scte_iface;
> HLSSegment *segments;
> HLSSegment *last_segment;
> HLSSegment *old_segments;
> @@ -132,9 +140,10 @@ static int hls_delete_old_segments(HLSContext *hls) {
>
> HLSSegment *segment, *previous_segment = NULL;
> float playlist_duration = 0.0f;
> - int ret = 0, path_size, sub_path_size;
> - char *dirname = NULL, *p, *sub_path;
> - char *path = NULL;
> + int ret = 0;
> + size_t dir_size;
> + const char *dirname;
> + char *base, *path;
>
> segment = hls->segments;
> while (segment) {
> @@ -147,69 +156,55 @@ static int hls_delete_old_segments(HLSContext *hls) {
> playlist_duration -= segment->duration;
> previous_segment = segment;
> segment = previous_segment->next;
> - if (playlist_duration <= -previous_segment->duration) {
> + //if (playlist_duration <= -previous_segment->duration) {
> + if (playlist_duration <= 9) {
> previous_segment->next = NULL;
> break;
> }
> }
>
> - if (segment) {
> - if (hls->segment_filename) {
> - dirname = av_strdup(hls->segment_filename);
> - } else {
> - dirname = av_strdup(hls->avf->filename);
> - }
> - if (!dirname) {
> - ret = AVERROR(ENOMEM);
> - goto fail;
> - }
> - p = (char *)av_basename(dirname);
> - *p = '\0';
> + if (!segment)
> + return ret;
> +
> + if (hls->segment_filename) {
> + dirname = hls->segment_filename;
> + } else {
> + dirname = hls->avf->filename;
> + }
> +
> + dir_size = av_basename(dirname) - dirname;
> + path = av_malloc(PATH_MAX);
> + if (!path) {
> + return (ret = AVERROR(ENOMEM));
> }
> + (void)av_strlcpy(path, dirname, PATH_MAX);
> + base = &path[dir_size];
>
> while (segment) {
> av_log(hls, AV_LOG_DEBUG, "deleting old segment %s\n",
> segment->filename);
> - path_size = strlen(dirname) + strlen(segment->filename) + 1;
> - path = av_malloc(path_size);
> - if (!path) {
> - ret = AVERROR(ENOMEM);
> - goto fail;
> - }
>
> - av_strlcpy(path, dirname, path_size);
> - av_strlcat(path, segment->filename, path_size);
> + av_strlcat(base, segment->filename, PATH_MAX);
> if (unlink(path) < 0) {
> av_log(hls, AV_LOG_ERROR, "failed to delete old segment %s:
> %s\n",
> path, strerror(errno));
> }
>
> - if (segment->sub_filename[0] != '\0') {
> - sub_path_size = strlen(dirname) +
> strlen(segment->sub_filename) + 1;
> - sub_path = av_malloc(sub_path_size);
> - if (!sub_path) {
> - ret = AVERROR(ENOMEM);
> - goto fail;
> - }
> -
> - av_strlcpy(sub_path, dirname, sub_path_size);
> - av_strlcat(sub_path, segment->sub_filename, sub_path_size);
> - if (unlink(sub_path) < 0) {
> + if (hls->has_subtitle) {
> + av_strlcat(base, segment->sub_filename, PATH_MAX);
> + if (unlink(path) < 0) {
> av_log(hls, AV_LOG_ERROR, "failed to delete old segment
> %s: %s\n",
> - sub_path, strerror(errno));
> + path, strerror(errno));
> }
> - av_free(sub_path);
> }
> - 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);
> }
>
> -fail:
> av_free(path);
> - av_free(dirname);
> -
> return ret;
> }
>
> @@ -314,8 +309,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;
> @@ -349,11 +344,25 @@ static int hls_append_segment(struct AVFormatContext
> *s, HLSContext *hls, double
> else
> en->sub_filename[0] = '\0';
>
> - en->duration = duration;
> - en->pos = pos;
> - en->size = size;
> + 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_out == EVENT_OUT_CONT) {
> + en->adv_count = hls->scte_iface->adv_count;
> + hls->scte_iface->adv_count++;
> + en->out = hls->scte_iface->event_out;
> + } else {
> + hls->scte_iface->adv_count = 0;
> + en->out = hls->scte_iface->event_out;
> + }
> + }
> +
> +
> 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));
> @@ -475,9 +484,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)
> @@ -502,9 +525,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)
> @@ -645,6 +674,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->time * AV_TIME_BASE;
> @@ -761,19 +791,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:
>
> @@ -799,7 +831,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( st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE ) {
> oc = hls->vtt_avf;
> stream_index = 0;
> @@ -824,14 +861,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->get_event_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_out == 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->get_event_cache(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;
> @@ -878,7 +925,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);
>
Just use hls maybe better for this function.
> }
>
> if (vtt_oc) {
> @@ -899,6 +946,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;
> @@ -920,7 +968,7 @@ static const AVOption options[] = {
> {"hls_subtitle_path", "set path of hls subtitles",
> OFFSET(subtitle_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E},
> {"hls_flags", "set flags affecting HLS playlist and media file
> generation", OFFSET(flags), AV_OPT_TYPE_FLAGS, {.i64 = 0 }, 0, UINT_MAX, E,
> "flags"},
> {"single_file", "generate a single media file indexed with byte
> ranges", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SINGLE_FILE }, 0, UINT_MAX, E,
> "flags"},
> - {"delete_segments", "delete segment files that are no longer part of
> the playlist", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_DELETE_SEGMENTS }, 0,
> UINT_MAX, E, "flags"},
> + {"delete_segments", "delete segment files that are no longer part of
> the playlist", 1, AV_OPT_TYPE_CONST, {.i64 = HLS_DELETE_SEGMENTS }, 0,
> UINT_MAX, E, "flags"},
>
Keep the flags 0 default
> {"round_durations", "round durations in m3u8 to whole numbers", 0,
> AV_OPT_TYPE_CONST, {.i64 = HLS_ROUND_DURATIONS }, 0, UINT_MAX, E,
> "flags"},
> {"discont_start", "start the playlist with a discontinuity tag", 0,
> AV_OPT_TYPE_CONST, {.i64 = HLS_DISCONT_START }, 0, UINT_MAX, E, "flags"},
> {"omit_endlist", "Do not append an endlist when ending stream", 0,
> AV_OPT_TYPE_CONST, {.i64 = HLS_OMIT_ENDLIST }, 0, UINT_MAX, E, "flags"},
> @@ -951,6 +999,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..c50811b
> --- /dev/null
> +++ b/libavformat/scte_35.c
> @@ -0,0 +1,489 @@
> +/*
> + * 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(NULL, AV_LOG_WARNING, "Out of Memory");
> + return NULL;
> + }
> +
> + av_log(iface->parent, AV_LOG_WARNING, "%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));
> + event->id = id;
> + event->in_pts = AV_NOPTS_VALUE;
> + event->nearest_in_pts = AV_NOPTS_VALUE;
> + event->out_pts = AV_NOPTS_VALUE;
> + event->lock = 0;
> + event->cancel = 1;
> + 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;
> + time_t rawtime;
> + struct tm * timeinfo;
> + char buffer[128];
> +
> + time ( &rawtime );
> + timeinfo = localtime ( &rawtime );
> + strftime(buffer, 25, "%Y:%m:%d%H:%M:%S", timeinfo);
> +
> +
> + av_log(iface->parent, AV_LOG_WARNING, "%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;
> + event->cancel = *buf & 0x80;
> + av_log(iface->parent, AV_LOG_DEBUG, "splice_event_cancel_indicator =
> %d\n", event->cancel );
> + buf++;
> +
> + if (!event->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->lock) {
> + 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 len = avpkt->size;
> + 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_WARNING, "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_WARNING, "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:
> + 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;
> +}
> +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->lock && event->out_pts < pts) {
> + iface->event_out = EVENT_OUT;
> + break;
> + }
> + event = event->next;
> + }
> + return event;
> +}
> +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;
> + while(event) {
> + if(event->lock && 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;
> + iface->event_out = EVENT_IN;
> + break;
> + }
> + event = event->next;
> + }
> + return event;
> +}
> +
> +static struct scte_35_event* get_event_pts(struct scte_35_interface
> *iface, uint64_t pts)
> +{
> + struct scte_35_event *event = NULL;
> + if(iface->event_out == EVENT_NONE) {
> + event = get_event_ciel_out(iface, pts);
> + if(event)
> + event->lock = 1;
> + } else {
> + event = get_event_floor_in(iface, pts);
> + unlink_scte35_event(iface, event);
> + }
> + if (event)
> + iface->cache_event = event;
> +
> + return event;
> +}
> +
> +static struct scte_35_event* get_event_cache(struct scte_35_interface
> *iface)
> +{
> +
> + struct scte_35_event* event = iface->cache_event;
> + if (iface->prev_event_state == EVENT_IN)
> + iface->event_out = EVENT_NONE;
> + else if (iface->prev_event_state == EVENT_OUT)
> + iface->event_out = EVENT_OUT_CONT;
> +
> + if(iface->event_out == EVENT_NONE)
> + iface->cache_event = NULL;
> +
> + iface->prev_event_state = iface->event_out;
> + return event;
> +}
> +
> +struct scte_35_interface* ff_alloc_scte35_parser(void *parent, AVRational
> timebase)
> +{
> + struct scte_35_interface* iface = av_mallocz(sizeof(struct
> scte_35_interface));
> +
> + iface->parent = parent;
> + iface->timebase = timebase;
> + iface->get_event_pts = get_event_pts;
> + iface->get_event_cache = get_event_cache;
> + 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_out = EVENT_NONE;
> + iface->prev_event_state = EVENT_NONE;
> + return iface;
> +}
> +
> +void ff_delete_scte35_parser(struct scte_35_interface* iface)
> +{
> + av_freep(&iface);
> +}
> diff --git a/libavformat/scte_35.h b/libavformat/scte_35.h
> new file mode 100644
> index 0000000..add9826
> --- /dev/null
> +++ b/libavformat/scte_35.h
> @@ -0,0 +1,76 @@
> +/*
> + * SCTE-35 parser
> + * Copyright (c) 2014 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 SCTE_35_H
> +#define SCTE_35_H
> +
> +#include "libavutil/bprint.h"
> +
> +struct scte_35_event {
> + int32_t id;
> + uint64_t in_pts;
> + uint64_t nearest_in_pts;
> + uint64_t out_pts;
> + int64_t duration;
> + int64_t start_pos;
> + int cancel;
> + /*if advertisement have already started cancel command can't delete
> advertisement */
> + volatile int lock;
> + volatile int ref_count;
> + struct scte_35_event *next;
> + struct scte_35_event *prev;
> +};
> +struct scte_35_interface {
> + struct scte_35_event *event_list;
> + char adv_filename[1024];
> + char filename[1024];
> + int event_out;
> + AVRational timebase;
> + int adv_count;
> + struct scte_35_event *cache_event;
> + 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;
> +
> + struct scte_35_event* (*get_event_pts)(struct scte_35_interface
> *iface, uint64_t pts);
> + struct scte_35_event* (*get_event_cache)(struct scte_35_interface
> *iface);
> + /* general purpose str */
> + AVBPrint avbstr;
> + 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);
> +};
> +
> +enum event_state {
> + EVENT_NONE,
> + EVENT_IN,
> + EVENT_OUT,
> + EVENT_OUT_CONT,
> +};
> +
> +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
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
More information about the ffmpeg-devel
mailing list