This package includes a simple module, ModuleClosedCaptionLive, that converts onTextData data events to CEA-608/CEA-708 closed caption data in a live video stream. This package only works for live streaming. We believe the closed caption data that's injected to be in conformance with the ANSI/CEA-608-E specification. The full UTF-8 single byte character set is supported where it overlaps with the CEA-608 character set. The implementation is quite simple but useful. The source code is included so the solution can be extended as needed.
Also included is a simple module, ModulePublishOnTextData, that injects onTextData events into a live stream. It's useful for testing a closed caption implementation based on the ModuleClosedCaptionLive module. This module takes caption data from a flat text file and injects an onTextData event into the stream every 6.5 seconds. Again, this module is only for testing purposes and is an example of how to inject onTextData events into a live stream.
Note: Wowza Media Server 3.1.1.08 or later is required.
An onTextData event is an AMF (Action Message Format) event that's traditionally associated with Real Time Messaging Protocol (RTMP) and Adobe® HTTP Dynamic Streaming (HDS). These events are used to carry closed caption or subtitle information. An onTextData event usually includes the following fields:
The ModuleClosedCaptionLive module monitors a live stream for onTextData events, extracts the text field, formats it as CEA-608 closed caption data, and injects it into the video stream as SEI NAL units. Many player technologies, including Apple® iOS devices, VideoLAN VLC, and many set-top boxes, can display the embedded closed captioning data.
Using the Wowza Media Server® server-side API, it's possible to inject onTextData events into a live stream using the IMediaStream.sendDirect() API call (there are details below). This enables a publisher that passes closed caption data in the form of timed text events or SMPTE events to use the Wowza Media Server server-side API to inject onTextData into a live stream. Using the ModuleClosedCaptionLive module, the onTextData is intercepted and injected into the stream as CEA-608 events.
In the future, we plan to take this functionality further by providing a means to extract closed caption data from an MPEG-TS stream and re-inject it into the video portion of the stream. At this time, it's not possible to do this with the current Wowza Media Server server-side API.
<Module> <Name>ModulePublishOnTextData</Name> <Description>ModulePublishOnTextData</Description> <Class>com.wowza.wms.plugin.closedcaption.test.ModulePublishOnTextData</Class> </Module>
<Property> <Name>publishOnTextDataFile</Name> <Value>${com.wowza.wms.context.VHostConfigHome}/content/ontextdata.txt</Value> </Property> <Property> <Name>publishOnTextDataPublishInterval</Name> <Value>6500</Value> <Type>Integer</Type> </Property> <Property> <Name>publishOnTextCharsetTest</Name> <Value>false</Value> <Type>Boolean</Type> </Property>
Where:
<Module> <Name>ModuleClosedCaptionLive</Name> <Description>ModuleClosedCaptionLive</Description> <Class>com.wowza.wms.plugin.closedcaption.live.ModuleClosedCaptionLive</Class> </Module>
<Property> <Name>closedCaptionLiveMaxDisplayTime</Name> <Value>10000</Value> <Type>Integer</Type> </Property> <Property> <Name>closedCaptionLiveCommandsPerFrame</Name> <Value>30</Value> <Type>Integer</Type> </Property> <Property> <Name>closedCaptionLiveChannel</Name> <Value>0</Value> <Type>Integer</Type> </Property> <!-- white:0, green:2, blue:4, cyan:6, red:8, yellow:10, magenta:12 --> <Property> <Name>closedCaptionLiveColor</Name> <Value>0</Value> <Type>Integer</Type> </Property> <Property> <Name>closedCaptionLiveCharacterSet</Name> <Value>UTF-8</Value> </Property> <Property> <Name>closedCaptionLiveLogOnTextDataEvents</Name> <Value>true</Value> <Type>Boolean</Type> </Property> <Property> <Name>closedCaptionLiveRemoveExistingCEA608</Name> <Value>false</Value> <Type>Boolean</Type> </Property>
Where:
http://[wowza-ip-address]:1935/[application]/myStream/playlist.m3u8
Note: Captions will only display if closed captioning is turned on. To turn on closed captioning on an iOS device, go to Settings > Video and turn on the Closed Captioning option.
If you plan to extend the source code, it's best to read the ANSI/CEA-608-E specification. (It can be purchased here: http://standards.ce.org.) For a good overview of CEA-608/CEA-708 data, see "Closed Captions and the SCC Format" (http://www.theneitherworld.com/mcpoodle/SCC_TOOLS/DOCS/SCC_FORMAT.HTML). Wowza Media Server 3.1.1.08 or later includes the ClosedCaptionCEA608Utils utility class, which includes most of the closed captioning commands that are needed to extend the code. The Javadocs for this class are included in the Wowza Media Server Server-Side API documentation.
The source code is included so that you can extend it as needed. The charToByte(String text, int pos, String characterSet, int channel) method is used to convert characters to closed captioning character codes. It's this method that must be extended to add support for additional character sets.
Several special characters are mapped to UTF-8 control codes that weren't being used so that the full CEA-608 character set was covered. Below are these mappings:
Unicode code point | UTF-8 (hex.) | Character |
0x7F | 0x007F | opening single quote |
0x80 | 0xC280 | plain single quote |
0x81 | 0xC281 | em dash |
0x82 | 0xC282 | service mark |
0x83 | 0xC283 | round bullet |
0x84 | 0xC284 | opening double quotes |
0x85 | 0xC285 | closing double quotes |
0x86 | 0xC286 | upper left corner |
0x87 | 0xC287 | upper right corner |
0x88 | 0xC288 | lower left corner |
0x89 | 0xC289 | lower right corner |
0x8A | 0xC28A | trademark - TM |
0x8B | 0xC28B | music note |
To add onTextData events to a live stream, use the sendDirect(handler, param...) method. The code to add an onTextData event to a stream looks like this:
AMFDataObj amfData = new AMFDataObj(); amfData.put("text", new AMFDataItem(onTextData.text)); amfData.put("language", new AMFDataItem("eng")); amfData.put("trackid", new AMFDataItem(99)); stream.sendDirect("onTextData", amfData);