-
-
Save ilyaevseev/5bcdfdbb4b503724f9e0df9491534b1c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Based on https://github.com/FFmpeg/FFmpeg/pull/238 that is already merged partially to upstream | |
diff -urN ffmpeg-3.2.4.orig/libavformat/hlsenc.c ffmpeg-3.2.4/libavformat/hlsenc.c | |
--- ffmpeg-3.2.4.orig/libavformat/hlsenc.c 2017-02-10 16:25:27.000000000 +0300 | |
+++ ffmpeg-3.2.4/libavformat/hlsenc.c 2017-03-24 15:35:06.253046675 +0300 | |
@@ -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 @@ | |
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 @@ | |
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 @@ | |
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 @@ | |
} | |
/* 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; | |
@@ -382,10 +391,21 @@ | |
else | |
en->sub_filename[0] = '\0'; | |
- en->duration = duration; | |
- en->pos = pos; | |
- en->size = size; | |
- en->next = NULL; | |
+ 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)); | |
@@ -460,7 +480,7 @@ | |
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 @@ | |
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 @@ | |
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 @@ | |
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 @@ | |
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 @@ | |
} | |
} | |
- 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 @@ | |
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 @@ | |
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 @@ | |
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 @@ | |
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 @@ | |
.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 -urN ffmpeg-3.2.4.orig/libavformat/Makefile ffmpeg-3.2.4/libavformat/Makefile | |
--- ffmpeg-3.2.4.orig/libavformat/Makefile 2017-02-10 16:25:27.000000000 +0300 | |
+++ ffmpeg-3.2.4/libavformat/Makefile 2017-03-24 15:34:20.673628685 +0300 | |
@@ -205,7 +205,7 @@ | |
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 -urN ffmpeg-3.2.4.orig/libavformat/scte_35.c ffmpeg-3.2.4/libavformat/scte_35.c | |
--- ffmpeg-3.2.4.orig/libavformat/scte_35.c 1970-01-01 03:00:00.000000000 +0300 | |
+++ ffmpeg-3.2.4/libavformat/scte_35.c 2017-03-24 15:32:29.692045835 +0300 | |
@@ -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 -urN ffmpeg-3.2.4.orig/libavformat/scte_35.h ffmpeg-3.2.4/libavformat/scte_35.h | |
--- ffmpeg-3.2.4.orig/libavformat/scte_35.h 1970-01-01 03:00:00.000000000 +0300 | |
+++ ffmpeg-3.2.4/libavformat/scte_35.h 2017-03-24 15:32:38.564932537 +0300 | |
@@ -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 */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey there @ilyaevseev,
I followed this up from the ticket on https://trac.ffmpeg.org/ticket/3356 and I'd be interested in helping you get this patch upstream and add the SCTE-35 markers into the HLS manifests.
You can contact me via the email address on my profile.
Thanks and regards,
Brainiarc7.