gaspard7/wiki_gpac huggingface dataset:
FileIO Wrapping

GPAC allows using wrappers for file operations (open, close, read, write, seek...), and such wrappers can be constructed from Python.

A FileIO wrapper is constructed using:

- the URL you want to wrap
- a 'factory' object providing the callbacks for GPAC.

Let's define a factory that simply wraps python file system calls:

```
class MyFileIO:
	def __init__(self):
		self.file = None
		self.is_eof=False

	def open(self, url, mode):
		if mode.find('r')>=0 and not os.path.isfile(url):
			return False
		self.file = open(url, mode)
		self.url = url
		return True

	def close(self):
		self.file.close()
		self.file=None

	def write(self, np_arr, _size):
		self.file.write(np_arr)
		return np.size(np_arr)

	def read(self, np_array, _size):
		tmp = np.fromfile(self.file, dtype=np.ubyte, count=np.size(np_array))
		size = np.size(tmp)
		np_array[:size] = tmp
		if size==0:
			self.is_eof=True
		return size

	def seek(self, pos, whence):
		self.file.seek(pos, whence)
		self.is_eof=False
		return 0

	def tell(self):
		return self.file.tell()

	def eof(self):
		return self.is_eof

	def exists(self, url):
		if not os.path.isfile(url):
			return False
		return True
```

You can then wrap input or output URLs using:

```
fio_factory = MyFileIO()
src_wrap = gpac.FileIO("mysource.hvc", fio_factory)
dst_wrap = gpac.FileIO("mydest.mp4", fio_factory)
f1 = fs.load_src(src_wrap.url)
f2 = fs.load_dst(dst_wrap.url+':option')

```

File IO wrapping can be useful when you want to distribute produced content through other means that GPAC built-in sinks, or if your source content is not a file.

When opening a file, the factory object is cloned and the 'open' callback is called on the clone. 
This allows handling, with a single wrapper, cases where a URL resolves in multiple URLs when processing, for example DASH or HLS with manifest file(s) and media segments.
HTTP Server

You can override the default behaviour of the httpout filter. See [the documentation](https://doxygen.gpac.io/group__pyhttpout__grp.html) for further details.

The principle is as follows:

- the script can get notification of each new request being received
- the script can decide to let GPAC handle the request as usual (typically used for injecting http headers, throttling and monitoring)
- the script can feed the data to GPAC (GET) or receive the data from GPAC (PUT/POST)


```
class MyHTTPOutRequest(gpac.HTTPOutRequest):
	def on_request(self, method, url, auth_code, headers):
		print('got request type ' + str(method) + ' URL ' + str(url) + ' headers ' + str(headers))
		self.headers_out.append("x-GPAC");
		self.headers_out.append("toto");
		//let GPAC handle the request processing
		self.reply=0
		self.send();

	def throttle(self, done, total):
		return 1

	def close(self, err):
		print("session closed: " + gpac.e2s(err))

#create request handler
myhandler = MyHTTPOutRequest()


#define a custom filter session monitoring the creation of new filters
class MyFilterSession(gpac.FilterSession):
	def __init__(self, flags=0, blacklist=None, nb_threads=0, sched_type=0):
		gpac.FilterSession.__init__(self, flags, blacklist, nb_threads, sched_type)

	def on_filter_new(self, f):
		#bind the httpout filter to our request handler object
		if f.name == "httpout":
			f.bind(myhandler);

#create a session
fs = MyFilterSession()

#load the server
f2 = fs.load("httpout:port=8080:rdirs=.")
if not f2:
    raise Exception('Failed to load htppout')

#run the session in blocking mode
fs.run()

```

The following script always serves the same file content using python instead of GPAC for GET, and monitor bytes received for PUT/POST:

```
class MyHTTPOutRequest(gpac.HTTPOutRequest):
	def on_request(self, method, url, auth_code, headers):
		print('got request type ' + str(method) + ' URL ' + str(url) + ' headers ' + str(headers))

		self.headers_out.append("x-GPAC");
		self.headers_out.append("toto");
		self.file = open("source.mp4", "r")

		#we will handle the request in python
		self.reply=200
		#send the request - this can also be done later on, e.g. in a callback task
		self.send();

	def throttle(self, done, total):
		return 1

	def close(self, err):
		print("session closed: " + gpac.e2s(err))
		self.file.close()

	def read(self, np_array, _size):
		tmp = np.fromfile(self.file, dtype=np.ubyte, count=np.size(np_array))
		size = np.size(tmp)
		np_array[:size] = tmp
		return size

	def write(self, np_array, _size):
		count=np.size(np_array)
		print('Got bytes: ' + str(count))
		return 0

```
Advanced example

The following is an example showing:

- DASH custom logic
- Custom sink filter with buffering control
- Raw video access for both GPU-based or system-based decoders
- Using OpenCV to display the frames


```
import time
import sys
import cv2

import libgpac as gpac

#initialize gpac
gpac.init()
#indicate we want to start with min bw by using global parameters
gpac.set_args(["Ignored", "--start_with=min_bw"])

#Our custom DASH adaptation logic
class MyCustomDASHAlgo:
    # get notifications when a DASH period starts or ends
    def on_period_reset(self, type):
        print('period reset type ' + str(type))

    # get notification when a new group (i.e., set of adaptable qualities, `AdaptationSet`in DASH) is created. Some groups may be left unexposed by the DASH client
    # the qualities are sorted for min bandwidth/quality to max bandwidth/quality
    def on_new_group(self, group):
        print('new group ' + str(group.idx) + ' qualities ' + str(len(group.qualities)) + ' codec ' + group.qualities[
            0].codec)

    # perform adaptation logic - return value is the new quality index, or -1 to keep as current, -2 to discard  (debug, segments won't be fetched/decoded)
    def on_rate_adaptation(self, group, base_group, force_low_complexity, stats):
        print('We are adapting on group ' + str(group.idx) + ' quality ' + str(stats.active_quality_idx))
        print('' + str(stats))

        #loop through qualities
        res = 1 + stats.active_quality_idx;
        if (res == len(group.qualities)):
            res = 0
        return res
define a custom filter session monitoring the creation of new filters

class MyFilterSession(gpac.FilterSession):
    def __init__(self, flags=0, blacklist=None, nb_threads=0, sched_type=0):
        gpac.FilterSession.__init__(self, flags, blacklist, nb_threads, sched_type)

    def on_filter_new(self, f):
        # bind the dashin filter to our algorithm object
        if f.name == "dashin":
            f.bind(mydash)
Overview {:data-level="all"}

We discuss here about the ability to deal with dynamic metadata such as SCTE-35 in GPAC Filters.
The information is this page applies to other metadata such as ID3 markers (e.g. Nielsen), timecodes (TEMI, QT), or virtually any type of dynamic metadata.

A special [dec_scte35 filter](dec_scte35) allows to segment SCTE-35 content for 23001-18 CMAF Event Track creation.

GPAC is able to:
- inspect,
- remux or transmux,
- segment for DASH,
- create 23001-18 ISOBMFF Event Track (with ```emib``` and ```emeb``` boxes),
- edit these data to some extend.
GPAC inspect

```
gpac -i input inspect:analyze=full:deep:props
```

would output:

```
[...]
<Packet number="970" PID="1" framing="complete" dts="1636452" cts="1640956" dur="1502" sap="0" ilace="0" corr="0" seek="0" bo="N/A" roll="0" crypt="0" vers="0" size="6799" lp="0" depo="0" depf="0" red="0" CRC32="0x17CDFEAC">
 <SCTE35>
  <scte35:SpliceInfoSection xmlns:scte35="urn:scte:scte35:2013:bin" sap_type="3" protocol_version="0" encrypted_packet="0" pts_adjustment="183000" tier="4095">
   <scte35:SpliceInsert splice_event_id="4026531841" splice_event_cancel_indicator="False" out_of_network_indicator="True" program_splice_flag="True" duration_flag="False" splice_immediate_flag="False">
    <scte35:SpliceTime pts_time="1813812"/>
   </scte35:SpliceInsert>
  </scte35:SpliceInfoSection>
 </SCTE35>
</Packet>
[...]
```
MP4Box -dxml

```
MP4Box -dxml scte35.mp4
```

scte35_dump.xml would contain for instance such descriptions for a sample:

```
[...]
<NHNTSample number="94" DTS="41224943904144" dataLength="98" CTSOffset="0" isRAP="yes" mediaOffset="0" sourceByteOffset="13584" duration="48048" depends="24" >
<EventMessageInstanceBox Size="98" Type="emib" Version="0" Flags="0" Specification="EventMessageTrack" Container="file" presentation_time_delta="122121" event_duration="239239" event_id="1" scheme_id_uri="urn:scte:scte35:2013:bin" value="" message_data="0xFC302500000000697800FFF01405F00000027FEFFE1D0AC9C1FE000DB07B0001010100001231F90A" >
  <scte35:SpliceInfoSection xmlns:scte35="urn:scte:scte35:2013:bin" sap_type="3" protocol_version="0" encrypted_packet="0" pts_adjustment="27000" tier="4095">
   <scte35:SpliceInsert splice_event_id="4026531842" splice_event_cancel_indicator="False" out_of_network_indicator="True" program_splice_flag="True" duration_flag="True" splice_immediate_flag="False">
    <scte35:SpliceTime pts_time="487246273"/>
    <scte35:BreakDuration duration="897147"/>
   </scte35:SpliceInsert>
  </scte35:SpliceInfoSection>
</EventMessageInstanceBox>
</NHNTSample>
[...]
```
Remux and transmux {:data-level="beginner"}

GPAC is able to remux any to any, including but not limited to:
- TS to TS
- MP4 (23001-18) to MP4 (23001-18)
- TS to MP4 (23001-18)

See specific instructions in the next section when segmenting for OTT packaging (MPEG-DASH, HLS).

The command-line is trivial:

```
gpac -i input -o output
```

The format is selected automatically depending on the file extension (or can be forced with ```:ext=```).
Segmentation and 23001-18 ISOBMFF Event Track

Adaptive streaming with DASH or HLS requires the content to be segmented. Simple segmentation (where content can be replicated over several segments without any change) works for all kind of streams.

For SCTE-35 streams the [dec_scte35 filter](dec_scte35) must be explicitly loaded.
Note that it can detect the segment duration from the [dasher](dasher) to create the right segmentation for you:

```
gpac -i input scte35dec -o output.mpd:segdur=2
```
RTP reader

The reader is a regular video playback from RTP (using SDP as input). We will:

- locate the `rtpin` filter in the chain, i.e. the first filter after the `fin`filter used for SDP access
- update every 2 second the `loss_rate`option of the `rtpin` filter: this will force the loss ratio in RTCP Receiver Reports, but will not drop any packet at the receiver side

```
#file rtp_receiver.py

import types
import libgpac as gpac
import sys


#create a session, blacklisting vtbdec
fs = gpac.FilterSession(0)

#load a source
src = fs.load_src("session.sdp")
disp = fs.load("vout")


#update loss report of RTP source
def update_loss(self):
	if (self.session.last_task):
		return -1

	if self.rtpin==None:
		if src.nb_opid > 0:
			outputs = src.opid_sinks(0)
			if len(outputs):
				self.rtpin = outputs[0]

	if self.rtpin:
		self.loss_rate+=self.inc
		if self.loss_rate>=1000:
			self.inc=-100
			self.loss_rate=1000
		elif self.loss_rate<=0:
			self.inc=+100
			self.loss_rate=0

		print('Update source loss rate to ' + str(self.loss_rate));
		self.rtpin.update("loss_rate", ""+str(self.loss_rate), 0);

	return 2000

#create the custom task
task = gpac.FilterTask('loss')
task.execute = types.MethodType(update_loss, task)
task.rtpin=None
task.loss_rate=0
task.inc=100
fs.post(task)

#run the session in blocking mode
fs.run()

print('done')

fs.delete()
gpac.close()

```
Encoder and RTP sender

The encoder consists in a source (here a single video file playing in loop), an AVC encoder and an RTP output. We will:

- locate the `rtpout` filter in the chain, i.e. the first filter before the `fout` filter used for SDP output
- monitor every 2 second the statistics of the input PID of `rtpout` to get the real-time measurements reported by RTCP
- adjust encoder max rate based on the percentage of loss packets

```
#file rtp_sender.py
import types
import libgpac as gpac
import sys

#create a session, blacklisting vtbdec
fs = gpac.FilterSession(0)

#load a source, single video in our example
src = fs.load("flist:srcs=av1.mp4#video:floop=-1")
enc = fs.load("c=avc:maxrate=8M:bufsize=2M")
out = fs.load_dst("session.sdp")
out.set_source(enc);

def update_stats(low_q, stats):
	cur_lq=False
	if stats.loss_rate<500:
		cur_lq = True

	if cur_lq == low_q:
		return low_q

	q = 8000000
	if cur_lq:
		q = 100000
	enc.update("maxrate", str(q))
	print('Updating quality to ' + str(q))
	return cur_lq


#custom task callback for checking rtp quality reports
def check_quality(self):
	if (self.session.last_task):
		return -1

	if self.rtpout==None:
		if out.nb_ipid > 0:
			self.rtpout = out.ipid_source(0)

	if self.rtpout:
		stats = self.rtpout.ipid_stats(0);
		if stats!=None:
			self.low_q = update_stats(self.low_q, stats)
	return 2000

#create the custom task
task = gpac.FilterTask('stats')
task.execute = types.MethodType(check_quality, task)
task.rtpout=None
task.low_q=False
fs.post(task)

#run the session in blocking mode
fs.run()

print('done')

fs.delete()
gpac.close()

```
HTTP reader

The reader is a regular video playback from HTTP, the client hosting the web server. We will update the overall http rate limit every 4 second.


```
#file http_receiver.py

import types
import libgpac as gpac
import sys

#create a session, blacklisting vtbdec
fs = gpac.FilterSession(0)

#load a source
src = fs.load("httpout:dst=http://127.0.0.1:8080/:hmode=source:reqlog=*")
disp = fs.load("vout")


#custom task callback for inspection of filters
def update_rate(self):
	if (self.session.last_task):
		return -1

	self.high_r = not self.high_r
	rate = 200000
	if self.high_r:
		rate = 10000000

	print('Update http read rate to ' + str(rate));
	fs.http_max_bitrate = rate
	return 6000

#create the custom task
task = gpac.FilterTask('rate')
task.execute = types.MethodType(update_rate, task)
task.high_r=False
fs.post(task)

#run the session in blocking mode
fs.run()

print('done')

fs.delete()
gpac.close()

```
Encoder and HTTP sender

The encoder consists in a source (here a single video file playing in loop), an AVC encoder and an HTTP output in push mode.

We will monitor every 2 second the statistics of the input PID to the HTTP sink and adjust encoder max rate based on the throughput.


```
#file http_sender.py

import types
import libgpac as gpac
import sys

#create a session, blacklisting vtbdec
fs = gpac.FilterSession(0)

#load a source, single video in our example
src = fs.load("flist:srcs=av1.mp4#video:floop=-1")
enc = fs.load("c=avc:maxrate=8M:bufsize=2M")
out = fs.load("httpout:dst=http://127.0.0.1:8080/live.264:gpac:hmode=push")
out.set_source(enc);

def update_stats(low_q, stats):
	print('Stats process rate ' + str(stats.average_process_rate) + ' media rate ' + str(stats.average_bitrate))
	cur_q=False
	if stats.average_process_rate < stats.average_bitrate:
		cur_q = True

	if cur_q == low_q:
		return low_q

	q = 8000000
	if cur_q:
		q = 100000
	enc.update("maxrate", str(q))
	print('Updating quality to ' + str(q))
	return cur_q


#custom task callback for checking rtp quality reports
def check_quality(self):
	if (self.session.last_task):
		return -1

	if out.nb_ipid > 0:
		stats = out.ipid_stats(0);
		if stats!=None:
			self.low_q = update_stats(self.low_q, stats)
	return 1000

#create the custom task
task = gpac.FilterTask('stats')
task.execute = types.MethodType(check_quality, task)
task.low_q=False
fs.post(task)

#run the session in blocking mode
fs.run()

print('done')

fs.delete()
gpac.close()

```
Remarks

The rate adaptation logic shown in these examples is more than basic, you may want to smooth statistics, use a bitstream ladder for the encoder, etc...

We used VBR rate control in this example, but other modes can be used depending on the underlying encoder used.
To check which parameters of your encoder can be updated:

`gpac -hx ffenc | grep updatable`: list all generic encoder options in ffenc that can be updated at run time


`gpac -hx ffenc:libx264 | grep updatable`: list all per-codec options in ffenc that can be updated at run time
 
_Warning: There is no guarantee that an updatable option in ffenc (or in ffmpeg in general) is taken into account at run-time. You can force encoder reloading using [`rld`](ffenc#rld) option of ffenc, but this will likely increase latency._
Overview {: data-level="all"}

GPAC supports both sending and receiving data using the ROUTE (Real-time Object delivery over Unidirectional Transport) [RFC9223](https://www.rfc-editor.org/rfc/rfc9223) protocol.

In the media field ROUTE is typically used for multicast ABR. Technically ROUTE allows to broadcast local file systems. This is useful to transport a [DASH](https://github.com/gpac/gpac/wiki/DASH-Introduction) session over any physical layers (e.g. broadcast (ATSC, DVB, ...), broadband (IP), or hybrid broadcast-broadband). ROUTE technologically inherits from [FLUTE](https://tools.ietf.org/html/rfc6726).

Receiving ROUTE implies to dump the local file system described in the ROUTE session. GPAC allows to process this input, including (but not limited to) dumping the files on disk or exposing them through the GPAC embedded HTTP server. This behaviour of re-exposing the data using another protocol is commonly named "Gateway".

Sending ROUTE means starting a ROUTE server. This server listens for input data that it will broadcast. It then exposes the data using the ROUTE protocol via a ROUTE URL (in GPAC: ```route://``` or ```atsc://```).

The GPAC ROUTE implementation has been tested with both DASH and HLS sessions. The versions of the protocols used for the implementation are [ATSC/A331 2017](https://www.atsc.org/wp-content/uploads/2017/12/A331-2017-Signaling-Deivery-Sync-FEC-3.pdf) and [2019](https://www.atsc.org/wp-content/uploads/2017/12/A331-2019-Signaling-Deivery-Sync-FEC-2.pdf). Both the Korean and US flavours of ATSC3 ROUTE are supported (both addressed with ```atsc://```), as well as a generic ROUTE implementation (addressed with ```route://```). Please note that ROUTE was also added to DVB-mABR.
Setup a ROUTE server

### Parameters

First you need an input content. This can be any input supported by GPAC e.g. a file or a live URL, and you most likely want an adaptive streaming source (DASH, HLS).

 Let's consider the ```https://akamaibroadcasteruseast.akamaized.net/cmaf/live/657078/akasource/out.mpd``` URL as an example.

You also need a pair of IP address and UDP port. Let's consider ```225.1.1.0:6000``` as an example.

### Starting the server

```
gpac -i https://akamaibroadcasteruseast.akamaized.net/cmaf/live/657078/akasource/out.mpd dashin:forward=file -o route://225.1.1.0:6000
```

_Note_
If your session is ATSC-compliant, replace ```route://``` by ```atsc://```.  Additional options are available in this mode, in particular multiple services and US versus Korean flavour, as [described here](routeout#atsc-30-mode).



### Enabling low latency

Add the ```llmode``` option:
```
gpac -i https://akamaibroadcasteruseast.akamaized.net/cmaf/live/657078/akasource/out.mpd dashin:forward=file -o route://225.1.1.0:6000:llmode
```

Technical details are [available here](routeout#low-latency-mode).

### Technical documentation

See [the route_out filter documentation](route_out) for technical details.
Setup ROUTE playback and gateway

### Local playback

To play a ROUTE session back, simply do:

```
gpac -play route://225.1.1.0:6000
```

See the [playback](Playback) howto for more details on content playback with GPAC.


### ROUTE gateway

#### Using the GPAC HTTP server

You may want to re-expose a ROUTE session as HTTP (typically to be played back by a third-party player), having ```gpac``` act as a gateway.

Let's consider a HTTP local server address and port at 127.0.0.1:8080:

```
gpac -i route://225.1.1.0:6000/:max_segs=4 dashin:forward=file httpout:port=8080 --rdirs=temp --reqlog=* --cors=auto
```

__Discussion__
- The [dashin](dashin) filter is required here to indicate we process the ROUTE+DASH session in file mode (no demultiplexing of content). Pushing files using [gcache=false](routein#gcache) is possible but more complex and limited to full segments only for the time being (no low-latency push).  
- The  [max_segs](routein#max_segs) option limits the number of segments kept on disk, set it to 0 to store the entire session. The number of segments stored on disk is currently NOT derived from the timeshift buffer information of the session. 



You can also re-push to any output format supported by GPAC, not only DASH/HTTP. For example sending it as an MPEG-2 Transport Stream:

```
gpac -i route://225.1.1.0:6000/ -o udp://225.1.1.10:1234/:ext=ts
```


#### Pushing to any HTTP ingest/origin

```
gpac -i route://225.1.1.0:6000 dashin:forward=file -o http://127.0.0.1:8080/live.mpd --hmode=push
```

To test locally, you can start the GPAC HTTP server as standalone, in this example enabling CORS (e.g. for DASH.js access) and logging PUT requests:
```
gpac httpout:port=8080:rdirs=$TEMP_DIR:wdir=$TEMP_DIR:reqlog=PUT:cors=auto
```

### ROUTE dumping session

To dump a ROUTE session to the ```dump_route``` folder in standalone mode:

```
gpac -i route://225.1.1.0:6000:odir=dump_route

```

The results will be in folder `dump_route/serviceN` with `N` the service ID of the session: 1 for pure ROUTE or the ATSC service ID  for  ATSC 3.0  ([details here](routein#source-mode)):


You can also forward files to receiving filters and use file templating. The following command will forward received ROUTE files to [fout](fout), writing to `ATSCN_rec/` folders, with `N`the ATSC service ID:
```
gpac -i atsc://225.1.1.0:6000:gcache=false -o ATSC$ServiceID$_rec/$File$:dynext
```

### Technical documentation

See [the routein](routein)  filter documentation for technical details.
Troubleshooting

As this is complex technology, small changes can make a big difference in how your system behaves:

- If your session is ATSC-compliant, replace ```route://``` by ```atsc://```.
- If no data is seen on the network, your system may require some [multicast routing indications](routein#interface-setup).
- You can improve logging by adding ```-logs=dash:route@info``` (or even more verbose: ```-logs=dash:route@debug```) to your command-lines.
Simple reception of data

Assume you have a source that dispatches AVC|H264 over TCP or UDP at `127.0.0.1:1234`. You can read from this socket using:
 
```
gpac -i udp://127.0.0.1:1234/:ext=avc -o test.mp4
gpac -i tcp://127.0.0.1:1234/:ext=avc -o test.mp4
```

The above command will open the given socket read AVC|H264 Annex B data from it and use it to mux MP4 data. Obviously you can use whatever output and processing:
```gpac -i tcp://127.0.0.1:1234/:ext=avc ffsws:osize=640x360 -o test.yuv```

This will grab the source, resize it and dump it to file.


__Discussion__  
The  `ext` option is used to indicate the input format of the data carried on the socket. If not set, the first received block of data will be used to probe for media format. Most mux format used by GPAC can be used on input sockets; a notable exception is ISOBMFF which is not supported, even in fragmented mode (this should be patched soon). 

Notice the final `/` after the port number. If you omit it, you will need to [escape the filter arguments](filters_general#generic-declaration):
```
gpac -i udp://127.0.0.1:1234:gpac:ext=avc -o test.mp4
```