Custom Forwarding example

The following defines a custom filter doing packet forwarding in the middle of the pipeline, exercising all possible packet creation modes (new, clone, copy, forward by ref, forward data by ref).
```
#define a custom filter
class MyFilter(gpac.FilterCustom):
	def __init__(self, session):
		gpac.FilterCustom.__init__(self, session, "PYnspect")
		#indicate what we accept and produce - here, video in and out
		self.push_cap("StreamType", "Visual", gpac.GF_CAPS_INPUT_OUTPUT)
		self.nb_pck=0

	#configure input PIDs
	def configure_pid(self, pid, is_remove):
		if is_remove:
			return 0
		if pid in self.ipids:
			print('PID reconfigured')
		else:
			print('New PID !')
			#create associated output PID
			opid = self.new_pid()
			#copy properties - this should always be done unless you have a good reason not to
			opid.copy_props(pid)
			#and set a bitrate property
			opid.set_prop('Bitrate', 500000)
			pid.opid = opid
			opid.pck_ref = None
		return 0

	#process
	def process(self):
		for pid in self.ipids:
			#we still have our last send packet not fully processed by the pipeline, wait for its destruction - see case 4 below
			if pid.opid.pck_ref:
				continue

			pck = pid.get_packet()
			if pck==None:
				if pid.eos:
					pid.opid.eos = True
				break

			size = pck.size
			print('Got Packet DTS ' + str(pck.dts) + ' CTS ' + str(pck.cts) + ' SAP ' + str(pck.sap) + ' dur ' + str(pck.dur) + ' size ' + str(size))
			if pck.frame_ifce:
				print("packet data is in GPU/filter shared memory")
			else:
				data = pck.data
				#if numpy support, you can access the packet data as NPArray 
				if gpac.numpy_support:
					print("packet buffer class " + data.__class__.__name__ + " size " + str(len(data) ) )
				else:
					print("packet buffer class " + data.__class__.__name__ )

			#test forward
			self.nb_pck += 1

		if self.nb_pck==1:
				pid.opid.forward(pck)
			#test new ref
			elif self.nb_pck==2:
				opck = pid.opid.new_pck_ref(pck)
				opck.copy_props(pck)
				opck.send()
			#test new alloc
			elif self.nb_pck==3:
				opck = pid.opid.new_pck(size)
				opck.copy_props(pck)
				odata = opck.data
				#copy array
				if gpac.numpy_support:
					numpy.copyto(odata, data)
				opck.send()
			#test new packet using shared data (data owned by the filter) 
			#in this case our shared data is the data from the source packet, 
			#so we need to track when our new packet is consumed before
			#releasing the source packet, and keep a reference to the source
			#for as long as our new packet is alive
			#In this mode, the associated filter MUST have a `packet_release` callback function
			elif self.nb_pck==4:
				opck = pid.opid.new_pck_shared(data)
				opck.copy_props(pck)
				#keep a reference to our source, so that drop_packet() below does not destroy it
				pid.opid.pck_ref = pck
				pid.opid.pck_ref.ref()
				opck.send()
			#test packet copy
			elif self.nb_pck==5:
				opck = pid.opid.new_pck_copy(pck)
				opck.copy_props(pck)
				opck.send()
			#test packet clone
			elif self.nb_pck==6:
				opck = pid.opid.new_pck_clone(pck)
				opck.copy_props(pck)
				opck.send()
				self.nb_pck = 0

			#we can drop the input packet
			pid.drop_packet()
		return 0

	#a shared packet is destroyed, unreference the source packet we used for it
	def packet_release(self, opid, pck):
		if opid.pck_ref:
			opid.pck_ref.unref()
			opid.pck_ref = None

#load a source filter
src=fs.load_src("source.mp4")

#load a custom filter
my_filter = MyFilter(fs)

#load a destination filter
dst=fs.load("vout")

#we need to indicate that our destination only gets its input from our custom filter !
dst.set_source(my_filter)