Insert ID3 data events into MPEG-TS streams in Wowza Streaming Engine

This article describes how to inject ID3 data events into an MPEG-TS stream using the IRTPPacketizerMPEGTSPacketNotify interface in Wowza Streaming Engine™ media server software. The IRTPPacketizerMPEGTSPacketNotify is a simple interface that's called 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 in the contents panel.
     
  2. On the 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 application to apply the changes.
The class will be instantiated for each new outgoing MPEG-TS stream.