How to insert ID3 data events into MPEG-TS streams

This article describes how to inject ID3 data events into an MPEG-TS stream using the IRTPPacketizerMPEGTSPacketNotify interface. The IRTPPacketizerMPEGTSPacketNotify is a simple interface that's called back at initialization of an MPEG-TS packetizer and each time a new packet is processed. This notifier is a good place to inject ID3 metadata into the stream using the IRTPPacketizerMPEGTS.addDataEvent method.

Note: Wowza Streaming Engine™ 4.4.0 or later is required.
The following is an example of an IRTPPacketizerMPEGTSPacketNotify implementation. This example injects ID3 metadata events both on processing of incoming AMF events as well as periodically injecting events into the stream. It is two examples of how the IRTPPacketizerMPEGTS.addDataEvent method can be used:
package com.mycompany.wowza.plugin;

import java.io.*;

import com.wowza.wms.amf.*;
import com.wowza.wms.logging.*;
import com.wowza.wms.media.mp3.model.idtags.*;
import com.wowza.wms.rtp.model.*;
import com.wowza.wms.rtp.packetizer.*;
import com.wowza.wms.stream.*;
import com.wowza.wms.vhost.*;

public class RTPPacketizerMPEGTSPacketNotifyID3Data implements IRTPPacketizerMPEGTSPacketNotify
{
	private static final Class<RTPPacketizerMPEGTSPacketNotifyID3Data> CLASS = RTPPacketizerMPEGTSPacketNotifyID3Data.class;
	private static final String CLASSNAME = "RTPPacketizerMPEGTSPacketNotifyID3Data";

	private RTPPacketizerMPEGTS rtpPacketizerMPEGTS = null;
	private long lastDataEventTC = -1;

	@Override
	public void init(RTPPacketizerMPEGTS rtpPacketizerMPEGTS, IMediaStream stream, RTPTrack rtpTrack)
	{
		WMSLoggerFactory.getLogger(CLASS).info(CLASSNAME+".init");

		this.rtpPacketizerMPEGTS = rtpPacketizerMPEGTS;

		// this will add data PID to the TS program map
		rtpPacketizerMPEGTS.setDataPID(RTPPacketizerMPEGTS.DEFAULT_PID_DATA);
	}

	@Override
	public void handleAMFPacket(OutputStream out, IMediaStream stream, RTPTrack rtpTrack, AMFPacket packet, long timecode)
	{
		int packetType = packet.getType();

		// convert AMF data events to ID3 tags and insert into the stream
		if (packetType == IVHost.CONTENTTYPE_DATA0 || packetType == IVHost.CONTENTTYPE_DATA3)
		{
			while(true)
			{
				byte[] buffer = packet.getData();
				if (buffer == null)
					break;

				if (packet.getSize() <= 2)
					break;

				int offset = 0;
				if (buffer[0] == 0)
					offset++;

				AMFDataList amfList = new AMFDataList(buffer, offset, buffer.length-offset);

				if (amfList.size() <= 1)
					break;

				if (amfList.get(0).getType() != AMFData.DATA_TYPE_STRING && amfList.get(1).getType() != AMFData.DATA_TYPE_OBJECT)
					break;

				String metaDataStr = amfList.getString(0);
				AMFDataObj dataObj = amfList.getObject(1);

				if (!metaDataStr.equalsIgnoreCase("onTextData"))
					break;

				AMFDataItem textData = (AMFDataItem)dataObj.get("text");
				if (textData == null)
					break;

				String dataStr = textData.toString();
				if (dataStr == null)
					break;

				ID3Frames id3Frames = new ID3Frames();
				ID3V2FrameTextInformation comment = new ID3V2FrameTextInformation(ID3V2FrameBase.TAG_TIT2);
				comment.setValue(dataStr);
				id3Frames.putFrame(comment);

				byte[] id3Bytes = id3Frames.serialize(true, false, ID3Frames.ID3HEADERFLAGS_DEFAULT);

				WMSLoggerFactory.getLogger(CLASS).info(CLASSNAME+".handleAMFPacket: sendDataEvent[amf]: "+dataStr);
				rtpPacketizerMPEGTS.addDataEvent(timecode, id3Bytes);
				break;
			}
		}

		// periodically insert a data event into the stream
		long currTime = System.currentTimeMillis();
		if (lastDataEventTC < 0 || (currTime-lastDataEventTC) > 1500)
		{
			if (lastDataEventTC > 0)
			{
				String dataStr = "Hello Wowza, time: "+currTime;
				ID3Frames id3Frames = new ID3Frames();
				ID3V2FrameTextInformation comment = new ID3V2FrameTextInformation(ID3V2FrameBase.TAG_TIT2);
				comment.setValue(dataStr);
				id3Frames.putFrame(comment);

				byte[] id3Bytes = id3Frames.serialize(true, false, ID3Frames.ID3HEADERFLAGS_DEFAULT);

				WMSLoggerFactory.getLogger(CLASS).info(CLASSNAME+".handleAMFPacket: sendDataEvent[async]: "+dataStr);
				rtpPacketizerMPEGTS.addDataEvent(timecode, id3Bytes);
			}
			lastDataEventTC = currTime;
		}
	}
}
To add this notifier to all out-going MPEG-TS streams for a given application, add the following custom property:
 
  1. In Wowza Streaming Engine Manager, click the Applications tab and then click the name of your live application (such as live) in the contents panel.
     
  2. On the live application page Properties tab, click Custom in the Quick Links bar.
     
    Note: Access to the Properties tab is limited to administrators with advanced permissions. For more information, see Manage credentials.
  3. In the Custom area, click Edit.
     
  4. Click Add Custom Property, specify the following settings in the Add Custom Property dialog box, and then click Add:
     
    • Path - Select /Root/Application/Streams.
       
    • Name - Enter mpegtsPacketNotifyClass.
       
    • Type - Select String.
       
    • Value - Enter com.mycompany.wowza.plugin.RTPPacketizerMPEGTSPacketNotifyID3Data.
  5. Click Save, and then restart the live application to apply the changes.
Your class will be instantiated for each new outgoing MPEG-TS stream.
Originally Published: For Wowza Streaming Engine 4.4.0 on 02-02-2016.
 

If you're having problems or want to discuss this article, post in our forum.