futzu / SCTE-35_HLS_x9k3

HLS and SCTE-35 x9k3 is a HLS Segmenter with SCTE 35, and Live Streaming from Non-Live Soures and Looping.
72 stars 17 forks source link

Would this work with audio-only HLS #3

Closed josepowera closed 2 years ago

josepowera commented 2 years ago

Is supported to make HLS with SCTE-35 from TS with audio only (radio station) with x9k3?

futzu commented 2 years ago

I don't see why not. I currently use video iframes to split segments, let me see what I can work out for audio.

Adrian

futzu commented 2 years ago

I had no problem doing it, let me know if this works for you.

a@debian:~/build/x9k3$ ffprobe -hide_banner audio.ts Input #0, mpegts, from 'audio.ts': Duration: 00:48:04.10, start: 4.220300, bitrate: 159 kb/s Program 1 Metadata: service_name : Service01 service_provider: FFmpeg Stream #0:00x100: Audio: aac (LC) ([15][0][0][0] / 0x000F), 44100 Hz, stereo, fltp, 108 kb/s


* I made a sidecar file of  PTS, SCTE35 Cue pairs
```lua
a@debian:~/build/x9k3$ cat sidecar.txt
102.805,/DAlAAAAAAAAAP/wFAUAAAABf+/+AJKsov4ADdtEAAEAAAAA7eV+Kg==
177.6,/DAlAAAAAAAAAP/wFAUAAAACf+/+APljwP4ADdtEAAIAAAAACcxDcg====
411.0,/DAlAAAAAAAAAP/wFAUAAAADf+/+AjnqcP4ADdtEAAMAAAAASnrfIQ====
927.697,/DAlAAAAAAAAAP/wFAUAAAAEf+/+BP99uv4ADdtEAAQAAAAAtZKbsQ==
1286.52,/DAlAAAAAAAAAP/wFAUAAAAFf+/+BuxCcP4ADdtEAAUAAAAAxSzhag==
2190.39,/DAlAAAAAAAAAP/wFAUAAAAGf+/+C8WJPP4ADdtEAAYAAAAAM8x/Xw==
2807.71,/DAlAAAAAAAAAP/wFAUAAAAHf+/+DxVLzP4ADdtEAAcAAAAAljzkJg==
2891.63,/DAlAAAAAAAAAP/wFAUAAAAIf+/+D4iK7P4ADdtEAAgAAAAAR8yZag== 

* tested in ffplay

ffplay index.m3u8

josepowera commented 2 years ago

Will try this and come back (have to dev first the sidecar generator using live not file -extract PTS and auto use that to generate sidecar)

futzu commented 2 years ago

This should help, https://github.com/futzu/scte35-threefive/blob/master/Encoding.md let me know if you have any questions.

futzu commented 2 years ago

@josepowera

This is what I use to generate splice inserts.


    from threefive import Cue,SpliceInsert

    def mk_cue(event_id, pts, duration=None):
        """
        mk_cue  make a SCTE-35 Cue
        with a Splice Insert command.
        set duration to make a CUE-OUT,

        """
        pts= float(pts)
        cue = Cue()
        # default is a CUE-IN 
        sin = SpliceInsert()
        sin.splice_event_id = event_id
        sin.splice_event_cancel_indicator = False
        sin.out_of_network_indicator = False
        sin.time_specified_flag = False
        sin.program_splice_flag = True
        sin.duration_flag = False
        sin.splice_immediate_flag = True
        sin.unique_program_id = event_id
        sin.avail_num = 0
        sin.avail_expected = 0
        # If we have a duration, make a CUE-OUT
        if duration is not None:
            sin.time_specified_flag = True
            sin.time_specified_flag = True
            sin.break_duration = float(duration)
            sin.break_auto_return = True
            sin.break_duration = duration
            sin.splice_immediate_flag = False
            sin.duration_flag = True
            sin.out_of_network_indicator = True
            sin.pts_time = pts
        cue.command = sin # Add SpliceInsert to the SCTE35 cue
        cue_string = cue.encode()  # Use cue.encode_as_hex() for hex instead of base64
        cue.decode()
        return pts,cue_string
futzu commented 2 years ago

This will be in the next threefive release, you can use it now.

"""
encode.py

threefive.encode has helper functions for Cue encoding.

"""

from threefive.commands import SpliceNull, SpliceInsert, TimeSignal
from threefive.cue import Cue

def mk_splice_null():
    """
    mk_splice_null returns a Cue
    with a Splice Null
    """
    cue = Cue()
    sn = SpliceNull()
    cue.command = sn
    cue.encode()
    return cue

def mk_time_signal(pts=None):
    """
     mk_time_signal returns a Cue
     with a Time Signal

     if pts is NOT set:
         time_specified_flag   False

    if pts IS set:
         time_specified_flag   True
         pts_time                     pts

    """
    cue = Cue()
    ts = TimeSignal()
    ts.time_specified_flag = False
    if pts:
        pts = float(pts)
        ts.time_specified_flag = True
        ts.pts_time = pts
    cue.command = ts
    cue.encode()
    return cue

def mk_splice_insert(event_id, pts, duration=None):
    """
    mk_cue returns a Cue
    with a Splice Insert.

    splice_event_id = event_id

    If duration is NOT set,
        out_of_network_indicator     False
        time_specified_flag              False
        duration_flag                        False
        splice_immediate_flag         True

    if duration IS set:
        out_of_network_indicator     True
        time_specified_flag               True
        duration_flag                         True
        splice_immediate_flag          False
        break_auto_return                True
        break_duration                      duration
        pts_time                                pts

    """
    pts = float(pts)
    cue = Cue()
    # default is a CUE-IN
    sin = SpliceInsert()
    sin.splice_event_id = event_id
    sin.splice_event_cancel_indicator = False
    sin.out_of_network_indicator = False
    sin.time_specified_flag = False
    sin.program_splice_flag = True
    sin.duration_flag = False
    sin.splice_immediate_flag = True
    sin.unique_program_id = event_id
    sin.avail_num = 0
    sin.avail_expected = 0
    # If we have a duration, make a CUE-OUT
    if duration is not None:
        duration = float(duration)
        sin.time_specified_flag = True
        sin.break_duration = duration
        sin.break_auto_return = True
        sin.splice_immediate_flag = False
        sin.duration_flag = True
        sin.out_of_network_indicator = True
        sin.pts_time = pts
    cue.command = sin  # Add SpliceInsert to the SCTE35 cue
    cue.encode()  
    return cue
josepowera commented 2 years ago

One more thing I'm searching for (I'm still working on this):
-get combination of PTS (could be begining of first segment for each m3u8) and PROGRAM-DATE-TIME. Even if written to console. -written EXT-X-PROGRAM-DATE-TIME for live streams

EXT-X-PROGRAM-DATE-TIME:2022-10-11T18:46:47.257Z

-CUE-OUT-CONT currentTime/totalDuration for segments in the middle of cue-out/ cut-in

futzu commented 2 years ago

I have to say, #EXT-X-PROGRAM-DATE-TIME makes no sense to me. If it's a "live" stream wouldn't the time be the current time?

class X9K4(X9K3): """ X9K4 is X9K3 with #EXT-X-PROGRAM-DATE-TIME """ def _write_segment(self): """ _write_segment creates segment file, writes segment meta data to self.active_data """ if not self.start: return seg_file = f"seg{self.seg.seg_num}.ts" self.seg.seg_uri = self.mk_uri(self.output_dir, seg_file) if self.seg.seg_stop: self.seg.seg_time = round(self.seg.seg_stop - self.seg.seg_start, 6) if self.live: self.cue_out_continue()

PTS

            self.active_data.write(f'# PTS {round(self.seg.seg_start, 6)}\n')

EXT-X-PROGRAM-DATE-TIME

            iso8601 = f"{datetime.datetime.utcnow().isoformat()}Z"
            pdt = f"#EXT-X-PROGRAM-DATE-TIME:{iso8601}"
            self.active_data.write(pdt + "\n")

        if self.scte35.cue_tag:
            self.active_data.write(self.scte35.cue_tag + "\n")
            self.scte35.cue_tag = None
        with open(self.seg.seg_uri, "wb+") as a_seg:
            a_seg.write(self.active_segment.getbuffer())
            a_seg.flush()
        del self.active_segment
        self.active_data.write(f"#EXTINF:{self.seg.seg_time},\n")
        self.active_data.write(seg_file + "\n")
        self.seg.seg_start = self.seg.seg_stop
        self.seg.seg_stop += self.seconds
        self.window.append(
            (self.seg.seg_num, self.seg.seg_uri, self.active_data.getvalue())
        )
        self.seg.seg_num += 1

if name == "main": x9k = X9K4() x9k.run()


* Output
```smalltalk
# PTS 21.286556
#EXT-X-PROGRAM-DATE-TIME:2022-10-12T01:47:06.666295Z
#EXTINF:2.002,
seg9.ts
futzu commented 2 years ago

I'm redoing the tag stuff. It will solve the CONT issues. Probably less than a week for me to finish it,

futzu commented 2 years ago

@josepowera

pip3 install --upgrade x9k3

HLS Tag Options now available

futzu commented 2 years ago

Are you good on this? I'm going to close it if you are, let me know.

josepowera commented 2 years ago

Yes, also HLS tag option was a great addition. Will reopen another issue as I move forward.

Just regarding #EXT-X-PROGRAM-DATE-TIME - I still believe it is useful for live. If for live stream you know that it has to CUE-OUT on example 06:00:00.000AM you can calculate where this is when you combine PROGRAM-DATE-TIME and segment duration to get correct PTS. Without PROGRAM-DATE-TIME you don't know what delay it took in encoding and have no reference to real clock.

josepowera commented 2 years ago

Regarding CUE-IN and #EXT-X-DISCONTINUITY tag. As I see example below, #EXT-X-DISCONTINUITY should be on next segment after break not last segment of ad-break. I'm working on live stream not VOD.

using x9ks latest from pip (0.1.45):

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:3
#EXT-X-MEDIA-SEQUENCE:1566
#PTS 3453.421333
#EXTINF:2.0,
seg1566.ts
#EXT-X-DISCONTINUITY
# Splice Point @ 3456.421333
#EXT-X-CUE-OUT:6.0
#PTS 3457.454667
#EXTINF:3.966666,
seg1567.ts
#EXT-X-DISCONTINUITY
# Splice Point @ 3462.421333
#EXT-X-CUE-IN
#PTS 3463.454667
#EXTINF:3.966666,
seg1568.ts
#PTS 3467.421333
#EXTINF:2.0,

https://stackoverflow.com/questions/47047919/how-does-ext-x-discontinuity-sequence-tag-work-in-hls-m3u8-file https://developer.apple.com/documentation/http_live_streaming/example_playlists_for_http_live_streaming/incorporating_ads_into_a_playlist example:

#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:4
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
ad0.ts
#EXTINF:8.0,
ad1.ts
#EXT-X-DISCONTINUITY
#EXTINF:10.0,
movieA.ts
#EXTINF:10.0,
movieB.ts

Maybe it makes no reason to add discontinuity in x9k3, since discontinuity is needed only if CUE-OUT is actually used (not skipped). In that case application replacing content can also update (add) #EXT-X-DISCONTINUITY.


Are automatic returns (CUE-IN) after CUE-OUT duration supported. At the moment when we add CUE-OUT but no CUE-IN to sidecar -> cue-out-cont will go over cue-out duration. Maybe just add some warning in console if SCTE-35 has "break_auto_return": true, that auto_return will not run.

#EXT-X-TARGETDURATION:3
#EXT-X-MEDIA-SEQUENCE:1876
#EXT-X-CUE-OUT-CONT:11.966666/6.0
#PTS 4941.421333
#EXTINF:2.0,
seg1876.ts
#EXT-X-CUE-OUT-CONT:13.966666/6.0
#PTS 4943.421333
#EXTINF:2.0,
seg1877.ts
#EXT-X-CUE-OUT-CONT:15.966666/6.0
#PTS 4945.421333

used scte-35

{
    "info_section": {
        "table_id": "0xfc",
        "section_syntax_indicator": false,
        "private": false,
        "sap_type": "0x3",
        "sap_details": "No Sap Type",
        "section_length": 47,
        "protocol_version": 0,
        "encrypted_packet": false,
        "encryption_algorithm": 0,
        "pts_adjustment_ticks": 0,
        "pts_adjustment": 0.0,
        "cw_index": "0x0",
        "tier": "0xfff",
        "splice_command_length": 20,
        "splice_command_type": 5,
        "descriptor_loop_length": 10,
        "crc": "0x14e4df6a"
    },
    "command": {
        "command_length": 20,
        "command_type": 5,
        "name": "Splice Insert",
        "time_specified_flag": true,
        "pts_time": 5070.421333,
        "pts_time_ticks": 456337920,
        "break_auto_return": true,
        "break_duration": 6.0,
        "break_duration_ticks": 540000,
        "splice_event_id": 19,
        "splice_event_cancel_indicator": false,
        "out_of_network_indicator": true,
        "program_splice_flag": true,
        "duration_flag": true,
        "splice_immediate_flag": false,
        "unique_program_id": 2,
        "avail_num": 0,
        "avail_expected": 0
    },
    "descriptors": [
        {
            "tag": 0,
            "descriptor_length": 8,
            "name": "Avail Descriptor",
            "identifier": "CUEI",
            "provider_avail_id": 0
        }
    ]
}
futzu commented 2 years ago

'Regarding CUE-IN and #EXT-X-DISCONTINUITY tag. As I see example below, #EXT-X-DISCONTINUITY should be on next segment after break not last segment of ad-break. I'm working on live stream not VOD.'

Damn it, I meant to fix that, I saw that too.

futzu commented 2 years ago

'Are automatic returns (CUE-IN) after CUE-OUT duration supported. At the moment when we add CUE-OUT but no CUE-IN to sidecar -> cue-out-cont will go over cue-out duration. Maybe just add some warning in console if SCTE-35 has "break_auto_return": true, that auto_return will not run.'

Yeah, I see what you're saying, I agree that needs to be fixed, but it probably wont be anytime soon.

I showed you how to add the #EXT-X-PROGRAM-DATE-TIME and PTS if you want it, you might want to look at the stream_diff in the output.

./seg3.ts   start: 8.868422 duration: 2.002000  stream diff: 1.950796

Stream diff is the difference between real time playback and segment generation. The stream diff above means that the last segment was generated 1.950796 seconds ahead of real time playback, As long as the stream diff stays between -2 and +2, the stream should play without error.

90% of the time, x9k3 is sleeping to slow itself down to keep the sliding window in sync.

I cannot assume that an application will add a DISCONTINUITY and if it's not in place the stream tends to break.

Look, you make a lot of valid points, but I just don't have time to implement all the features you want, if you want to hire me, I can get on it next week, if not, it won't be any time soon. I am just being honest with you.

josepowera commented 2 years ago

I fully understand your point of view. My ideas are mostly brainstorming after I'm working around using TS splicing in x9k3 for a few days. Hope more users come to use and find x9k3 useful! Maybe just add license header to source files since I see you already declared MIT license on PIP (https://pypi.org/).

futzu commented 2 years ago

I think I finally fixed the CUE-IN DISCONTINUITY bug.

futzu commented 2 years ago

pypy3 -mpip install --upgrade x9k3

a@debian:~/build/x9k3$ pypy3 x9k3 -h
usage: x9k3.py [-h] [-i INPUT] [-o OUTPUT_DIR] [-s SIDECAR] [-t TIME]
               [-T HLS_TAG] [-w WINDOW_SIZE] [-d] [-l] [-r] [-v] [-p]

optional arguments:
  -h, --help            show this help message and exit
  -i INPUT, --input INPUT
                        Input source, like "/home/a/vid.ts" or
                        "udp://@235.35.3.5:3535" or "https://futzu.com/xaa.ts"
  -o OUTPUT_DIR, --output_dir OUTPUT_DIR
                        Directory for segments and index.m3u8 ( created if it
                        does not exist )
  -s SIDECAR, --sidecar SIDECAR
                        Sidecar file of scte35 cues. each line contains PTS,
                        Cue
  -t TIME, --time TIME  Segment time in seconds ( default is 2)
  -T HLS_TAG, --hls_tag HLS_TAG
                        x_scte35, x_cue, x_daterange, or x_splicepoint
                        (default x_cue)
  -w WINDOW_SIZE, --window_size WINDOW_SIZE
                        sliding window size(default:5)
  -d, --delete          delete segments ( enables --live )
  -l, --live            Flag for a live event ( enables sliding window m3u8 )
  -r, --replay          Flag for replay (looping) ( enables --live and
                        --delete )
  -v, --version         Show version
josepowera commented 2 years ago

I think I finally fixed the CUE-IN DISCONTINUITY bug.

I'm afraid that #EXT-X-DISCONTINUITY is still 1 segment too fast (I'm running on v.0.1.59). As I understand it should be on seg4967.ts and not on seg4966.ts.

I'm running in HLS LIVE mode with cue-out and cue-in signals sent to sidecar.

current version:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:3
#EXT-X-MEDIA-SEQUENCE:4963
#EXT-X-DISCONTINUITY-SEQUENCE:702
# Splice Point @ 10448.823856
#EXT-X-DISCONTINUITY
#Iframe @ 10448.823856 
#EXT-X-PROGRAM-DATE-TIME:2022-10-27T17:59:21.292480Z
#EXT-X-SCTE35:CUE="/DAvAAAAAAAAAP/wFAUAAAGPf+/+OAt8Yf4ACD1gAAIAAAAKAAhDVUVJAAAAAOynsiA=" ,CUE-OUT=YES 
#EXTINF:2.090,
seg4963.ts
#Iframe @ 10451.099411 
#EXT-X-PROGRAM-DATE-TIME:2022-10-27T17:59:23.590936Z
#EXT-X-SCTE35:CUE="/DAvAAAAAAAAAP/wFAUAAAGPf+/+OAt8Yf4ACD1gAAIAAAAKAAhDVUVJAAAAAOynsiA=" ,CUE-OUT=CONT
#EXTINF:2.067,
seg4964.ts
#Iframe @ 10453.142767 
#EXT-X-PROGRAM-DATE-TIME:2022-10-27T17:59:25.594458Z
#EXT-X-SCTE35:CUE="/DAvAAAAAAAAAP/wFAUAAAGPf+/+OAt8Yf4ACD1gAAIAAAAKAAhDVUVJAAAAAOynsiA=" ,CUE-OUT=CONT
#EXTINF:2.043,
seg4965.ts
# Splice Point @ 10455.186122
#EXT-X-DISCONTINUITY
#Iframe @ 10455.186122 
#EXT-X-PROGRAM-DATE-TIME:2022-10-27T17:59:27.637347Z
#EXT-X-SCTE35:CUE="/DAqAAAAAAAAAP/wDwUAAAGPf0/+OBO5wQACAAAACgAIQ1VFSQAAAADsueIZ" ,CUE-IN=YES 
#EXTINF:2.043,
seg4966.ts
#Iframe @ 10457.4849 
#EXT-X-PROGRAM-DATE-TIME:2022-10-27T17:59:29.957457Z
#EXTINF:2.090,
seg4967.ts

I believe #EXT-X-DISCONTINUITY should be on seg4967.ts and not seg4966.ts (yes I know if this is last segment for VOD you simple can't write it)

seg4965.ts
# Splice Point @ 10455.186122
#Iframe @ 10455.186122 
#EXT-X-PROGRAM-DATE-TIME:2022-10-27T17:59:27.637347Z
#EXT-X-SCTE35:CUE="/DAqAAAAAAAAAP/wDwUAAAGPf0/+OBO5wQACAAAACgAIQ1VFSQAAAADsueIZ" ,CUE-IN=YES 
#EXTINF:2.043,
seg4966.ts
#Iframe @ 10457.4849 
#EXT-X-DISCONTINUITY
#EXT-X-PROGRAM-DATE-TIME:2022-10-27T17:59:29.957457Z
#EXTINF:2.090,
seg4967.ts