Embed CC into a captured file or network stream
To embed CC into the captured file, you should set this key value:
HKEY_CURRENT_USER/SOFTWARE/Medialooks/MPlatform/MWriter/MWriterFFM/embed_cc = true
Or, you can specify this property with the PropsSet method:
(myWriter as IMProps).PropsSet("embed_cc", "true");Once it is enabled, all CC data is captured into files for these codecs:
- MPEG-2
- H264
Please note that you can use this option to stream closed captions data over the network, for example with UDP stream.
Write CC into an external file
It is possible to capture Closed Captioning data into an external file while you use MWriter object. If your source (live or playlist content) contains CC data, it is captured into the external file automatically. So you can use the captured content with closed captions data later.
When you start a capturing, all 608 CC data (with the one that is included into 708) is captured into SCC file with the same name. For example, if you start a capturing to file c:\myCaptureTest.mov then CC is captured into c:\myCaptureTest.scc. You can use SCC files in any application.
The 708 CC data is captured into ANC file. This file has a custom format and you can use such files only in Medialooks-based applications.
To be able to capture Closed Captioning data into a file you should set "scc_capture" attribute of the required format into "true" for 608 CC data and "anc_capture" attribute to "true" for 708 CC data. You can do it with the AttibutesStringSet method in code:
IMAttributes attributes;
string name;
m_objWriter.ConfigGet("format", out name, out attributes);
attributes.AttributesStringSet("anc_capture", "true");
attributes.AttributesStringSet("scc_capture", "true");
string config;
m_objWriter.ConfigGetAll(1, out config);To set a specific file to capture CC data you should set "scc_path" property with required SCC file path and "anc_path" property with required ANC file path.
Overriding Closed Captions
If the source video does not contain any closed captions (CCs), you can generate the CC byte packets yourself and integrate them into the video frames using the following methods:
// CC Data to set (just an example):
Byte[] C608_BytePacket = new Byte[]
{
0x01, 0x02, 0x03, 0x04
};
Byte[] ATSC_BytePacket = new Byte[]
{
0x05, 0x06, 0x07, 0x08
};
Byte[] C708_BytePacket = new byte[]
{
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22,
0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C,
0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55
};
Byte[] GA94_BytePacket = new Byte[]
{
0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62,
0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B
};
// Grab a frame from video source and insert the CC data into them and pass into MFWriter:
m_objReader.SourceFrameGet(0, out MFFrame pFrame, "");
IntPtr PTR_ATSC = new IntPtr();
IntPtr PTR_GA94 = new IntPtr();
IntPtr PTR_C608 = new IntPtr();
IntPtr PTR_C708 = new IntPtr();
PTR_ATSC = Marshal.AllocHGlobal(Marshal.SizeOf(ATSC_BytePacket[0]) * ATSC_BytePacket.Length);
PTR_GA94 = Marshal.AllocHGlobal(Marshal.SizeOf(GA94_BytePacket[0]) * GA94_BytePacket.Length);
PTR_C608 = Marshal.AllocHGlobal(Marshal.SizeOf(C608_BytePacket[0]) * C608_BytePacket.Length);
PTR_C708 = Marshal.AllocHGlobal(Marshal.SizeOf(C708_BytePacket[0]) * C708_BytePacket.Length);
Marshal.Copy(ATSC_BytePacket, 0, PTR_ATSC, ATSC_BytePacket.Length);
Marshal.Copy(GA94_BytePacket, 0, PTR_GA94, GA94_BytePacket.Length);
Marshal.Copy(C608_BytePacket, 0, PTR_C608, C608_BytePacket.Length);
Marshal.Copy(C708_BytePacket, 0, PTR_C708, C708_BytePacket.Length);
// Set the information for the current frame:
(pFrame as IMFFrame).MFDataSet("ATSC", ATSC_BytePacket.Length, (Int64)PTR_ATSC);
(pFrame as IMFFrame).MFDataSet("GA94", GA94_BytePacket.Length, (Int64)PTR_GA94);
(pFrame as IMFFrame).MFDataSet("C608", C608_BytePacket.Length, (Int64)PTR_C608);
(pFrame as IMFFrame).MFDataSet("C708", C708_BytePacket.Length, (Int64)PTR_C708);
m_objWriter.ReceiverFramePut(pFrame, -1, "");
Marshal.ReleaseComObject(pFrame);For the MPlatform SDK, use the .OnFrameSafe event:
m_objFile.PropsSet("object::on_frame.sync", "true");
m_objFile.PropsSet("object::on_frame.data", "true");
m_objFile.PropsSet("object::events.use_window", "false"); // Set "true" for your application isn't console
m_objFile.OnFrameSafe += (String bsChannelID, Object pFrame) =>
{
IntPtr PTR_ATSC = new IntPtr();
IntPtr PTR_GA94 = new IntPtr();
IntPtr PTR_C608 = new IntPtr();
IntPtr PTR_C708 = new IntPtr();
PTR_ATSC = Marshal.AllocHGlobal(Marshal.SizeOf(ATSC_BytePacket[0]) * ATSC_BytePacket.Length);
PTR_GA94 = Marshal.AllocHGlobal(Marshal.SizeOf(GA94_BytePacket[0]) * GA94_BytePacket.Length);
PTR_C608 = Marshal.AllocHGlobal(Marshal.SizeOf(C608_BytePacket[0]) * C608_BytePacket.Length);
PTR_C708 = Marshal.AllocHGlobal(Marshal.SizeOf(C708_BytePacket[0]) * C708_BytePacket.Length);
Marshal.Copy(ATSC_BytePacket, 0, PTR_ATSC, ATSC_BytePacket.Length);
Marshal.Copy(GA94_BytePacket, 0, PTR_GA94, GA94_BytePacket.Length);
Marshal.Copy(C608_BytePacket, 0, PTR_C608, C608_BytePacket.Length);
Marshal.Copy(C708_BytePacket, 0, PTR_C708, C708_BytePacket.Length);
// Set the information for the current frame:
(pFrame as IMFFrame).MFDataSet("ATSC", ATSC_BytePacket.Length, (Int64)PTR_ATSC);
(pFrame as IMFFrame).MFDataSet("GA94", GA94_BytePacket.Length, (Int64)PTR_GA94);
(pFrame as IMFFrame).MFDataSet("C608", C608_BytePacket.Length, (Int64)PTR_C608);
(pFrame as IMFFrame).MFDataSet("C708", C708_BytePacket.Length, (Int64)PTR_C708);
Marshal.ReleaseComObject(pFrame);
};If you need to remove all CCs from a file that contains them, you can simply pass null as the CC data packet:
// Erase the CC packets from the frame:
(pFrame as IMFFrame).MFDataSet("C608", -1, null);
(pFrame as IMFFrame).MFDataSet("C708", -1, null);
(pFrame as IMFFrame).MFDataSet("GA94", -1, null);
(pFrame as IMFFrame).MFDataSet("ATSC", -1, null);If the video source already contains CC packets and you need to override them with your own, you should combine the two previous approaches - first, erase the original CC packets, and then set your own:
// Erase the CC packets from the frame:
pFrame.MFDataSet("C608", -1, null);
pFrame.MFDataSet("C708", -1, null);
pFrame.MFDataSet("GA94", -1, null);
pFrame.MFDataSet("ATSC", -1, null);
// Set the information for the current frame:
(pFrame as IMFFrame).MFDataSet("ATSC", ATSC_BytePacket.Length, (Int64)PTR_ATSC);
(pFrame as IMFFrame).MFDataSet("GA94", GA94_BytePacket.Length, (Int64)PTR_GA94);
(pFrame as IMFFrame).MFDataSet("C608", C608_BytePacket.Length, (Int64)PTR_C608);
(pFrame as IMFFrame).MFDataSet("C708", C708_BytePacket.Length, (Int64)PTR_C708);