From patchwork Sat Oct 1 17:27:31 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Carlos Fernandez Sanz X-Patchwork-Id: 810 Delivered-To: ffmpegpatchwork@gmail.com Received: by 10.103.140.66 with SMTP id o63csp822250vsd; Sat, 1 Oct 2016 10:30:18 -0700 (PDT) X-Received: by 10.194.205.134 with SMTP id lg6mr13042927wjc.148.1475343018426; Sat, 01 Oct 2016 10:30:18 -0700 (PDT) Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id l73si10904009wmb.34.2016.10.01.10.30.16; Sat, 01 Oct 2016 10:30:18 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@nisupu-com.20150623.gappssmtp.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 8527868A0C4; Sat, 1 Oct 2016 20:29:46 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-pa0-f65.google.com (mail-pa0-f65.google.com [209.85.220.65]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 00907689A91 for ; Sat, 1 Oct 2016 20:29:38 +0300 (EEST) Received: by mail-pa0-f65.google.com with SMTP id oz2so6218995pac.0 for ; Sat, 01 Oct 2016 10:29:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nisupu-com.20150623.gappssmtp.com; s=20150623; h=sender:from:to:cc:subject:date:message-id:in-reply-to:references; bh=iYf3DNpXn2PB9s8YObAHFQXIibZlFTwkwl2yEIbT9bQ=; b=PAh5mRHvA1l71uiJ27arMnPfynyFJy4sUhjYhSCQT/joaqGrXHytR8VGqHP0gRedgv q+4Ti770GVvmAeIylKodwzvsXzCcetWHEAx18jXlo9MMVwFOt8SO13psbjnDL9OLjwWm Iwicu/+lh2bwSCPyocjDYlFdyAQztkve/ehJ+2VKk+0+96OX0fKUIjV1LwKz3x8Z1xzx 8XjXFa/Nri7mbo0wlOIMk8hrB2ztHcnLBBRWbwgjBBsk/EbEZtgdU9ukEI83Ek42GVuC y39j/X0SEoMC0oNyxEZ2IDOOwy8Yg+LJ4WFDr3i8IKWLWiNFGCIG04PtPfqtwCuzG7fE I1eQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:sender:from:to:cc:subject:date:message-id :in-reply-to:references; bh=iYf3DNpXn2PB9s8YObAHFQXIibZlFTwkwl2yEIbT9bQ=; b=R4L/PqswQxDjidZlLY22jQhEliTFMMGGRklbIvHPpIbPv0QGovNlCfe3GTktOgm8Nr z0L/vfsPzpedMmUfaPG74aJp+cEZienpHcIl0ssqlph68H6AK1SWvcj9v98C522vXo6j FnmVh6n+FstZEThb5W6rl7p+8kSCJf3WwqF9IXFDCLN7WcvPYM4jINct6Vux75a7aR25 d8CHHgGeqcVnNi7+1MD5U9dlz5cuMpkZfp5VR6d4CyC+h69oxv273DQD6ckO+2URraUL I/CsPGb+dZcRkvIdD7WC/cmanF9t5LTt0ppa+6etayZtHqdWFJPJBUFoGPuq+g6pqCq8 ibOA== X-Gm-Message-State: AA6/9RkMn5yzpwrFCEk2tlUZMEWhVOH1tiwKc/SEt43Qxi8DFwsbPJx/Mli80KpbAZ/fKQ== X-Received: by 10.66.12.234 with SMTP id b10mr22388892pac.91.1475342990819; Sat, 01 Oct 2016 10:29:50 -0700 (PDT) Received: from carlos-linux-dev.monkeybrains.net (162-245-20-150.PUBLIC.monkeybrains.net. [162.245.20.150]) by smtp.gmail.com with ESMTPSA id 27sm36243720pfr.29.2016.10.01.10.29.50 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Sat, 01 Oct 2016 10:29:50 -0700 (PDT) From: Carlos Fernandez Sanz To: ffmpeg-devel@ffmpeg.org Date: Sat, 1 Oct 2016 10:27:31 -0700 Message-Id: <1475342852-19156-4-git-send-email-carlos@ccextractor.org> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1475342852-19156-1-git-send-email-carlos@ccextractor.org> References: <1475342852-19156-1-git-send-email-carlos@ccextractor.org> Subject: [FFmpeg-devel] [PATCH 3/4] V12 - SCTE-35 support in hlsenc X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Carlos Fernandez MIME-Version: 1.0 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" From: Carlos Fernandez Signed-off-by: Carlos Fernandez --- libavformat/Makefile | 2 +- libavformat/hlsenc.c | 112 +++++++++-- libavformat/scte_35.c | 527 ++++++++++++++++++++++++++++++++++++++++++++++++++ libavformat/scte_35.h | 86 ++++++++ 4 files changed, 705 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 c161937..a3224ef 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]; @@ -92,6 +97,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 @@ -108,6 +115,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; @@ -241,6 +250,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 +371,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; @@ -397,9 +408,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)); @@ -473,7 +498,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; @@ -603,9 +628,27 @@ 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 || 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); + if (strlen(fname) >= 1024) { + av_log(s, AV_LOG_WARNING,"Segment filename too long"); + } else { + strncat(fname, en->filename, 1024 - strlen(fname) ); + 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) @@ -630,9 +673,15 @@ 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 || 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) @@ -782,6 +831,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; @@ -914,7 +964,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]; @@ -927,16 +977,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: @@ -962,6 +1014,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 */ @@ -995,14 +1053,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; @@ -1064,7 +1132,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) { @@ -1085,6 +1153,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; @@ -1141,6 +1210,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..1a01d14 --- /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