Inserting STANAG Klv metadata into TS stream sent over UDP.

In this article, we'll show how to get up and running quickly with a simple app that inserts ASYNC Klv or SYNC Klv data into a transport stream. We'll use KlvInjector SDK.

First things first, lets create a VS project, add the references and make sure all the required dependencies are copied to the target directory (we'll do it using the 'post-build event')
Next, we create an instance of the Klv Injector, initialize it and add events:
// Create Player instance

CKlvInjector m_KlvInjector = new CKlvInjector();

// Setup events
m_KlvInjector.PlayerEvent += new NotifyPlayerEvent(OnPlayerEvent);
m_KlvInjector.PidDetectionEvent += new NotifyPidDetection(OnPidDetectionEvent);
m_KlvInjector.SyncFrameEvent += new NotifySyncFrame(OnSyncFrameEvent);
m_KlvInjector.ErrorEvent += new NotifyError(OnErrorEvent);
There are some additional parameters we can set up.
m_KlvInjector.RenderVideo = true; // to show video

m_KlvInjector.Caching = 200; // Caching delay, needed to get smooth video playback (if you have jitter)

m_KlvInjector.Hwnd = 0; // Where to render video

m_KlvInjector.VideoCaptureMode.uncompressedVideo = UncompressedVideoModeWr.UncompressedVideoMode_None;
m_KlvInjector.VideoCaptureMode.fCompressedVideo = true;

// Set the output target
m_KlvInjector.NetTarget.Url = targetUrl;
m_KlvInjector.NetTarget.Use = true;

// Initialize
m_KlvInjector.Init("udp://227.1.1.1:30120"); // source stream Url

// Set required KlvProcessing mode
KlvDataPropsWr m_klvProps = new KlvDataPropsWr(ProcessingModeWr.INSERT_KLV_ASYNC);

// Read packet
m_klvProps.DefaultPacket = System.IO.File.ReadAllBytes("KlvPacket.bin");
Ok, we're ready to go. Let's first check the playback.
// Start Processing
m_KlvInjector.Start();
If everything is Ok and you have a source stream (as defined in the source url), a preview window will open and we'll see the video.
We should get a Player_State.Starting and after that Player_State.Running event.
As during the initialization we set the NotifyPidDetection event, it should trigger shortly after the playback is started. That's a good place to configure what should be included into our output stream.
OnPidDetectionEvent will receive a list of pids found in the stream, so we can find out if there is a klv pid out there. If it is, we can decide whether to leave it in the stream or remove and replace with a brand new one.
void OnPidDetectionEvent(List<PidInfoWr> pidList)
{
     int firstKlvPidFound = 0;
   
     pidList.ForEach(pid =>
     {
        Console.WriteLine("Pid 0X{0} type {1}", pid.streamId.ToString("X"), pid.streamType, Color.LightGray);

        if (pid.streamType == StreamType.KLV && firstKlvPidFound == 0)
        {
            firstKlvPidFound = pid.streamId;
            m_KlvInjector.IgnorePidInOutput(firstKlvPidFound);
        }
     });

     m_KlvPid = firstKlvPidFound != 0 ? firstKlvPidFound : 0X1F1;

     // If the stream has no Klv, we have to add a new Klv Pid.
     m_KlvInjector.AddDataPidToOutput(m_KlvPid, m_klvProps);
}
Last thing we have to do is to setup the actual insertion.
If all we needed is to insert an ASYNC klv into the stream, we could simply use timer and call WritePacketToOutputPid method.
m_KlvInjector.WritePacketToOutputPid(m_KlvPid, klvData, klvTimestamp);
But, if we want it to work with both files and stream, we need more control over the timing - in case of file processing (without video preview rendering) the processing is done faster than real time, so we must know where in the stream we are. Fortunately, OnSyncFrameEvent method provides this and other useful information.
OnSyncFrameEvent provides the information on the video frames and existing klv or private data pids accompanied with the timing information. So, based on this information we can easily insert a synchronized data (external of remuxed with the current one).
As you can see in the code below, we're using m_KlvInjector.WritePacketToOutputPid method to insert the KLV data, specifying PTS timestamp.
 Please remember, we MUST take care of the Klv Packet timestamp. There is a helper function (AdjustTime) that sets a correct time and recalculates packet's checksum.
void OnSyncFrameEvent(List<StreamFrameInfoWr> streamList)
{
     streamList.ForEach(delegate(StreamFrameInfoWr streamFrame)
     {
       switch (streamFrame.streamType)
       {
         case StreamType.VIDEO:
         {
           // Here we got an uncompressed frame
           VideoFrameInfoWr vf = streamFrame as VideoFrameInfoWr;
           
           // Modify and insert Klv Packet
           m_KlvInjector.WritePacketToOutputPid(m_KlvPid, m_KlvInjector.KlvRemux.AdjustTime(m_DefaultPcktBuffer, m_firstKlvTime.Add(dt.Subtract(m_FramesFirstReceivedTime))), vf.timeStamp);
         }
         break;

        case StreamType.KLV:
        {
           // Here we got a klv packet
           KlvFrameInfoWr kf = streamFrame as KlvFrameInfoWr;

        }
        break;

        case StreamType.PRIVATE_DATA:
        {
          // Here we got a private data packet
          DataFrameInfoWr df = streamFrame as DataFrameInfoWr;
       
        }
        break;
     }
    });
}
Note on CaptureMode.

KlvStreamInjector has to receive (especially when working with files) timing info in order to be able sync Klv data (insert it at the correct place in the file). This can be achieved with or without actual decoding. If no decoding is done, the offline process can be a lot faster (and real time / stream mode use less CPU). So, when no preview is needed, set CaptureMode to Compressed (fCompressedVideo = true) and disable Uncompressed capture (VideoCaptureMode.uncompressedVideo = UncompressedVideoModeWr.UncompressedVideoMode_None;) to avoid unnecessary video decoding.

That’s it! You can now open your stream with any STANAG player and see the results.

No Comments

Post a Comment