多协议、性能稳定、丰富API的流媒体服务器软件
如何将直播流和VOD中的onTextData Event转换为HLS中的ID3 Tag格式?
你可以将RTMP直播流或VOD文件中的 AMF data event 转换为Apple HLS中的 timed metadata. Apple HLS 支持的timed metadata event 采用ID3 tag格式。 Wowza 软件提供了API,可以监听直播流或VOD中的AMF data event,然后将其转换为ID3 tag,让后将ID3时间发送到HLS流中。 直播流中的AMF data event 即可以来自编码器也可以是用IMediaStream.sendDirect接口插入的。 VOD流中的 AMF data event 既可以来自AMF event也可以是Mp4文件中的subtitle数据。


这篇文章提供了几个代码示例,向您说明如何监听onTextData event 并将其转换为ID3 tag:



Vod 代码示例 - 针对Wowza Streaming Engine™ 和 Wowza Media Server™ 3


package com.wowza.wms.plugin.test.module;

import com.wowza.wms.httpstreamer.model.*;
import com.wowza.wms.module.*;
import com.wowza.wms.amf.*;
import com.wowza.wms.application.*;
import com.wowza.wms.httpstreamer.cupertinostreaming.file.*;
import com.wowza.wms.httpstreamer.cupertinostreaming.httpstreamer.*;
import com.wowza.wms.httpstreamer.cupertinostreaming.livestreampacketizer.*;
import com.wowza.wms.media.mp3.model.idtags.*;

public class ModuleCupertinoVODOnTextToID3 extends ModuleBase
{
	class VODActionNotify implements IHTTPStreamerCupertinoVODActionNotify2
	{
		IApplicationInstance appInstance = null;
		
		public VODActionNotify(IApplicationInstance appInstance)
		{
			this.appInstance = appInstance;
		}
		
		public void onCreate(IHTTPStreamerCupertinoIndex fileIndex, IHTTPStreamerApplicationContext appContext, IHTTPStreamerSession httpStreamerSession, String rawStreamName, String streamExt, String streamName)
		{
		}

		public void onInit(IHTTPStreamerCupertinoIndex fileIndex, IHTTPStreamerApplicationContext appContext, IHTTPStreamerSession httpStreamerSession, String rawStreamName, String streamExt, String streamName)
		{
		}

		public void onOpen(IHTTPStreamerCupertinoIndex fileIndex, IHTTPStreamerApplicationContext appContext, IHTTPStreamerSession httpStreamerSession, String rawStreamName, String streamExt, String streamName)
		{
		}

		public void onIndex(IHTTPStreamerCupertinoIndex fileIndex, IHTTPStreamerApplicationContext appContext, IHTTPStreamerSession httpStreamerSession, String rawStreamName, String streamExt, String streamName)
		{
		}

		public void onFillChunkStart(IHTTPStreamerCupertinoIndex fileIndex, IHTTPStreamerCupertinoIndexItem item, LiveStreamPacketizerCupertinoChunk chunk, boolean audioOnly)
		{
		}

		public void onFillChunkEnd(IHTTPStreamerCupertinoIndex fileIndex, IHTTPStreamerCupertinoIndexItem item, LiveStreamPacketizerCupertinoChunk chunk, boolean audioOnly)
		{
		}

		public void onDestroy(IHTTPStreamerCupertinoIndex fileIndex)
		{
		}

		public void onFillChunkDataPacket(IHTTPStreamerCupertinoIndex fileIndex, IHTTPStreamerCupertinoIndexItem item, LiveStreamPacketizerCupertinoChunk chunk, boolean audioOnly, AMFPacket packet, ID3Frames id3Frames)
		{
			
			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;
				
				getLogger().info("ModuleCupertinoVODOnTextToID3.onAppStart["+appInstance.getContextStr()+"] Send string: "+textData.toString());
								
				ID3V2FrameTextInformation comment = new ID3V2FrameTextInformation(ID3V2FrameBase.TAG_TIT2);
				comment.setValue(textData.toString());
				id3Frames.putFrame(comment);
				break;
			}
		}
	}

	public void onAppStart(IApplicationInstance appInstance)
	{
		while(true)
		{
			IHTTPStreamerApplicationContext appContext = appInstance.getHTTPStreamerApplicationContext("cupertinostreaming", true);
			if (appContext == null)
				break;
			
			if (!(appContext instanceof HTTPStreamerApplicationContextCupertinoStreamer))
				break;
			
			HTTPStreamerApplicationContextCupertinoStreamer cupertinoAppContext = (HTTPStreamerApplicationContextCupertinoStreamer)appContext;
			cupertinoAppContext.addVODActionListener(new VODActionNotify(appInstance));
			break;
		}
		
		getLogger().info("ModuleCupertinoVODOnTextToID3.onAppStart["+appInstance.getContextStr()+"]");
	}
}

直播代码示例 - 针对Wowza Streaming Engine software (version 4.4.0 及以上版本)


package com.wowza.wms.plugin.test.module;

import java.util.*;
import com.wowza.wms.amf.*;
import com.wowza.wms.application.*;
import com.wowza.wms.httpstreamer.cupertinostreaming.livestreampacketizer.*;
import com.wowza.wms.media.mp3.model.idtags.*;
import com.wowza.wms.module.*;
import com.wowza.wms.stream.livepacketizer.*;

public class ModuleCupertinoLiveOnTextToID3 extends ModuleBase
{
	class LiveStreamPacketizerDataHandler implements IHTTPStreamerCupertinoLivePacketizerDataHandler2
	{
		private LiveStreamPacketizerCupertino liveStreamPacketizer = null;
		private int textId = 1;
		
		public LiveStreamPacketizerDataHandler(LiveStreamPacketizerCupertino liveStreamPacketizer)
		{
			this.liveStreamPacketizer = liveStreamPacketizer;
		}

		public void onFillChunkStart(LiveStreamPacketizerCupertinoChunk chunk)
		{
			getLogger().info("ModuleCupertinoVODOnTextToID3.onFillChunkStart["+chunk.getRendition().toString()+":"+liveStreamPacketizer.getContextStr()+"]: chunkId:"+chunk.getChunkIndexForPlaylist());
			
			// Add custom M3U tag to chunklist header
			CupertinoUserManifestHeaders userManifestHeaders = liveStreamPacketizer.getUserManifestHeaders(chunk.getRendition());
			if (userManifestHeaders != null)
				userManifestHeaders.addHeader("MY-USER-HEADER-DATA", "LAST-CHUNK-TIME", (new Date()).toString());
			
			// Add ID3 tag to start of chunk
			ID3Frames id3Frames = liveStreamPacketizer.getID3FramesHeader();
			ID3V2FrameTextInformation comment = new ID3V2FrameTextInformation(ID3V2FrameBase.TAG_TIT2);
			comment.setValue("LAST-CHUNK-TIME: "+(new Date()).toString());
			id3Frames.clear();
			id3Frames.putFrame(comment);

			textId = 1;
		}
		
		public void onFillChunkEnd(LiveStreamPacketizerCupertinoChunk chunk, long timecode)
		{
			getLogger().info("ModuleCupertinoVODOnTextToID3.onFillChunkEnd["+chunk.getRendition().toString()+":"+liveStreamPacketizer.getContextStr()+"]: chunkId:"+chunk.getChunkIndexForPlaylist());
		}
		
		public void onFillChunkMediaPacket(LiveStreamPacketizerCupertinoChunk chunk, CupertinoPacketHolder holder, AMFPacket packet)
		{
			
		}

		public void onFillChunkDataPacket(LiveStreamPacketizerCupertinoChunk chunk, CupertinoPacketHolder holder, AMFPacket packet, ID3Frames id3Frames)
		{			
			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 textStr = textData.toString();
				
				getLogger().info("ModuleCupertinoVODOnTextToID3.onFillChunkDataPacket["+chunk.getRendition().toString()+":"+liveStreamPacketizer.getContextStr()+"] Send string: "+textStr);
						
				// Add ID3 tag with text data in chunk based on timecode order
				ID3V2FrameTextInformation comment = new ID3V2FrameTextInformation(ID3V2FrameBase.TAG_TIT2);
				comment.setValue(textStr);
				id3Frames.putFrame(comment);
				
				// Add custom M3U tag to chunklist just above chunk
				CupertinoUserManifestHeaders userManifestHeaders = chunk.getUserManifestHeaders();
				if (userManifestHeaders != null)
					userManifestHeaders.addHeader("MY-USER-CHUNK-DATA-"+textId, "ON-TEXT-DATA", textStr, true);
				
				textId++;
				break;
			}
		}
	}
	
	class LiveStreamPacketizerListener extends LiveStreamPacketizerActionNotifyBase
	{
		public void onLiveStreamPacketizerCreate(ILiveStreamPacketizer liveStreamPacketizer, String streamName)
		{
			if (liveStreamPacketizer instanceof LiveStreamPacketizerCupertino)
			{
				getLogger().info("ModuleCupertinoLiveOnTextToID3#MyLiveListener.onLiveStreamPacketizerCreate["+((LiveStreamPacketizerCupertino)liveStreamPacketizer).getContextStr()+"]");
				((LiveStreamPacketizerCupertino)liveStreamPacketizer).setDataHandler(new LiveStreamPacketizerDataHandler((LiveStreamPacketizerCupertino)liveStreamPacketizer));
			}
		}
	}

	public void onAppStart(IApplicationInstance appInstance)
	{		
		appInstance.addLiveStreamPacketizerListener(new LiveStreamPacketizerListener());

		getLogger().info("ModuleCupertinoLiveOnTextToID3.onAppStart["+appInstance.getContextStr()+"]");
	}
}

直播时移(nDVR)代码示例 - 针对Wowza Streaming Engine software (4.3.0 及以上版本)


package com.wowza.wms.plugin.test.module;

import com.wowza.wms.amf.AMFData;
import com.wowza.wms.amf.AMFDataItem;
import com.wowza.wms.amf.AMFDataList;
import com.wowza.wms.amf.AMFDataObj;
import com.wowza.wms.amf.AMFPacket;
import com.wowza.wms.application.IApplicationInstance;
import com.wowza.wms.dvr.IDvrStreamManager;
import com.wowza.wms.httpstreamer.cupertinostreaming.livestreampacketizer.CupertinoPacketHolder;
import com.wowza.wms.httpstreamer.cupertinostreaming.livestreampacketizer.IHTTPStreamerCupertinoLivePacketizerDataHandler;
import com.wowza.wms.media.mp3.model.idtags.ID3Frames;
import com.wowza.wms.media.mp3.model.idtags.ID3V2FrameBase;
import com.wowza.wms.media.mp3.model.idtags.ID3V2FrameTextInformation;
import com.wowza.wms.module.ModuleBase;
import com.wowza.wms.stream.livedvr.IDvrStreamManagerActionNotify2;


public class ModuleDvrDataToID3 extends ModuleBase
{
	class LiveStreamPacketizerDataHandler implements IHTTPStreamerCupertinoLivePacketizerDataHandler
	{
		IApplicationInstance appInstance = null;
		
		public LiveStreamPacketizerDataHandler(IApplicationInstance appInstance)
		{
			this.appInstance = appInstance;
		}

		public void onFillChunkDataPacket(CupertinoPacketHolder holder, AMFPacket packet, ID3Frames id3Frames)
		{			
			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);
				System.out.println("meta:"+metaDataStr);
				
				AMFDataObj dataObj = amfList.getObject(1);
				
				if (!metaDataStr.equalsIgnoreCase("onTextData"))
					break;
				
				AMFDataItem textData = (AMFDataItem)dataObj.get("text");
				if (textData == null)
					break;
				
				getLogger().info("ModuleDvrDataToID3.onAppStart["+appInstance.getContextStr()+"] Send string: "+textData.toString());
								
				ID3V2FrameTextInformation comment = new ID3V2FrameTextInformation(ID3V2FrameBase.TAG_TIT2);
				comment.setValue(textData.toString());
				id3Frames.putFrame(comment);
				break;
			}
		}
	}
	
	class LiveStreamPacketizerListener implements IDvrStreamManagerActionNotify2
	{
		IApplicationInstance appInstance = null;
		
		public LiveStreamPacketizerListener(IApplicationInstance appInstance)
		{
			this.appInstance = appInstance;
		}


		@Override
		public void onDvrStreamManagerCreate(IDvrStreamManager dvrMgr)
		{
			getLogger().info("ModuleDvrDataToID3#MyLiveListener.onDvrStreamManagerCreate["+dvrMgr+"]");
			
			dvrMgr.setDataHandler(new LiveStreamPacketizerDataHandler(appInstance));
		}

		@Override
		public void onDvrStreamManagerInit(IDvrStreamManager dvrMgr)
		{
		}

		@Override
		public void onDvrStreamManagerDestroy(IDvrStreamManager dvrMgr)
		{
		}

		@Override
		public void onDvrStreamManagerInitStreamName(IDvrStreamManager dvrManager, String streamName)
		{
		}
	}

	public void onAppStart(IApplicationInstance appInstance)
	{		
		appInstance.addDvrStreamManagerListener(new LiveStreamPacketizerListener(appInstance));

		getLogger().info("ModuleDvrDataToID3.onAppStart["+appInstance.getContextStr()+"]");
	}
}