This article describes how to port an application that uses the old CNTV2Task
/AutoCircTimeCodeTask
API to frame-accurately capture/playout timecode to the newer AUTOCIRCULATE_TRANSFER
/AutoCirculateTransfer
API that was introduced in SDK version 12.3.
(This article applies to SDK versions before 12.3)
NOTE: You only need to do this if your application:
• Uses AutoCirculate to capture or play video
• Must run on version 12.4 or later NTV2 drivers
• Requires frame-accurate timecode to/from SDI spigots that don’t correspond to the AutoCirculate channel that’s being used (e.g., you’re AutoCirculating Channel 2, but need to embed/deembed timecode to/from SDI 1).
If your AutoCirculate-based application only sets/gets timecode to/from the SDI spigot that corresponds to the AutoCirculate channel, your application will continue to run with the NTV2 driver version 12.4.
Background:
CNTV2Task
/AUTOCIRCULATE_TASK_STRUCT
was hacked into the SDK long ago to allow AutoCirculate clients to frame-accurately set/get timecode to/from SDI spigots other than the channel being circulated. In order to add support for embedded Field2 VITC for interlaced video in the 12.4 development, the Tasks hack wouldn’t scale … which added a lot of unnecessary complexity to the driver. By droppingCNTV2Task
/AUTOCIRCULATE_TASK_STRUCT
, the driver is now simpler, more reliable and easier to test.
IMPORTANT: The new API’s structs use constructors to properly initialize themselves. DO NOT use ‘memset’ or ‘bzero’ to clear them, as this will undo much of what their constructors accomplish, and in most cases will leak memory. Please follow the coding patterns shown here.
An application captures video using AutoCirculate channel 2, but FrameStore 2 is routed to receive video (with timecode) from SDI Input 1.
. . .
CNTV2Card device (0);
AUTOCIRCULATE_TRANSFER_STRUCT inputXfer; // My A/C input transfer info
AUTOCIRCULATE_TRANSFER_STATUS_STRUCT inputXferStatus; // My A/C input status
CNTV2Task inputTaskList; // My task list for frame-accurate input timecode
::memset (&inputXfer, 0, sizeof (inputXfer));
::memset (&inputXferStatus, 0, sizeof (inputXferStatus));
inputXfer.channelSpec = NTV2CROSSPOINT_INPUT2;
inputXfer.videoBuffer = new UByte [myVideoBufferSize];
inputXfer.videoBufferSize = myVideoBufferSize;
inputXfer.frameRepeatCount = 1;
inputXfer.desiredFrame = -1;
inputXfer.bDisableExtraAudioInfo = true;
device.StopAutoCirculate (inputXfer.channelSpec);
device.InitAutoCirculate (inputXfer.channelSpec, /*startFrame*/ 0, /*endFrame*/ 6, /* # chls */ 1, NTV2_AUDIOSYSTEM_1);
inputTaskList.Clear ();
inputTaskList.AddTimeCodeReadTask ();
device.SetAutoCirculateCaptureTask (inputXfer.channelSpec, inputTaskList);
device.StartAutoCirculate (inputXfer.channelSpec);
while (!mGlobalQuit)
{
AUTOCIRCULATE_STATUS_STRUCT acStatus;
device.GetAutoCirculate (inputXfer.channelSpec, &acStatus);
if (acStatus.state == NTV2_AUTOCIRCULATE_RUNNING && acStatus.bufferLevel > 1)
{
// Transfer from the device into our host AVDataBuffer...
device.TransferWithAutoCirculate (&inputXfer, &inputXferStatus);
// Clear the input task list
inputTaskList.Clear ();
// Get the capture tasks for the transfered frame...
FRAME_STAMP_STRUCT acFrameStamp;
device.GetFrameStampEx2 (inputXfer.channelSpec, inputXferStatus.transferFrame, &acFrameStamp, inputTaskList);
// Get the first task and confirm it is a time code read task...
AutoCircGenericTask * pTask (inputTaskList.GetTask (0));
if (pTask == NULL || pTask->taskType != eAutoCircTaskTimeCodeRead)
continue; // Failed -- try again later
// Find the timecodes of interest inside pTask->u.timeCodeTask...
// pTask->u.timeCodeTask.TCInOut1
// pTask->u.timeCodeTask.LTCEmbedded
} // if A/C running and frame(s) are available for transfer
else
device.WaitForInputVerticalInterrupt (NTV2_CHANNEL2);
} // loop til quit signaled
device.StopAutoCirculate (inputXfer.channelSpec);
. . .
. . .
CNTV2Card device (0);
AUTOCIRCULATE_TRANSFER inputXfer; // My A/C input transfer info
device.AutoCirculateStop (NTV2_CHANNEL2);
device.AutoCirculateInitForInput (NTV2_CHANNEL2, /*#frames*/ 7, NTV2_AUDIOSYSTEM_INVALID, AUTOCIRCULATE_WITH_RP188);
device.AutoCirculateStart (NTV2_CHANNEL2);
while (!mGlobalQuit)
{
AUTOCIRCULATE_STATUS acStatus;
device.AutoCirculateGetStatus (NTV2_CHANNEL2, acStatus);
if (acStatus.IsRunning () && acStatus.HasAvailableInputFrame ())
{
// Transfer from the device into our host AVDataBuffer...
device.AutoCirculateTransfer (NTV2_CHANNEL2, inputXfer);
NTV2_RP188 tcSDI1, ltcSDI1;
inputXfer.GetInputTimeCode (tcSDI1, NTV2_TCINDEX_SDI1);
inputXfer.GetInputTimeCode (ltcSDI, NTV2_TCINDEX_SDI1_LTC);
} // if A/C running and frame is available for transfer
else
device.WaitForInputVerticalInterrupt (NTV2_CHANNEL2);
} // loop til quit signaled
device.AutoCirculateStop (NTV2_CHANNEL2);
. . .
An application plays video using AutoCirculate channel 3, but in addition to routing video (with timecode) to SDI Out 3, it also routes video (with same timecode) to SDI Out 4 and SDI Out 5.
. . .
CNTV2Card device (0);
AUTOCIRCULATE_TRANSFER_STRUCT outputXfer; // My A/C output transfer info
AUTOCIRCULATE_TRANSFER_STATUS_STRUCT outputXferStatus; // My A/C output status
CNTV2Task outputTaskList; // My task list for frame-accurate output timecode
::memset (&outputXfer, 0, sizeof (outputXfer));
::memset (&outputXferStatus, 0, sizeof (outputXferStatus));
outputXfer.channelSpec = NTV2CROSSPOINT_CHANNEL3;
outputXfer.videoBuffer = new UByte [myVideoBufferSize];
outputXfer.videoBufferSize = myVideoBufferSize;
outputXfer.frameRepeatCount = 1;
outputXfer.desiredFrame = -1;
outputXfer.bDisableExtraAudioInfo = true;
device.StopAutoCirculate (outputXfer.channelSpec);
device.InitAutoCirculate (outputXfer.channelSpec, /*startFrame*/ 0, /*endFrame*/ 6, /* # chls */ 1, NTV2_AUDIOSYSTEM_1);
outputTaskList.Clear ();
outputTaskList.AddTimeCodeReadTask ();
device.SetAutoCirculateCaptureTask (outputXfer.channelSpec, outputTaskList);
device.StartAutoCirculate (outputXfer.channelSpec);
while (!mGlobalQuit)
{
AUTOCIRCULATE_STATUS_STRUCT acStatus;
device.GetAutoCirculate (outputXfer.channelSpec, &acStatus);
if (acStatus.state == NTV2_AUTOCIRCULATE_RUNNING && acStatus.bufferLevel < (ULWord)(acStatus.endFrame - acStatus.startFrame - 1))
{
RP188_STRUCT tc;
// Output the timecode into the desired places...
outputTaskList.Clear ();
device.AddTimeCodeWriteTask (NULL, NULL, NULL, NULL, NULL, NULL, &tc, &tc, &tc);
// Transfer from the host to the device...
device.TransferWithAutoCirculateEx2 (&outputXfer, &outputXferStatus);
} // if A/C running and frame(s) are available for transfer
else
device.WaitForOutputVerticalInterrupt (NTV2_CHANNEL3);
} // loop til quit signaled
device.StopAutoCirculate (outputXfer.channelSpec);
. . .
. . .
CNTV2Card device (0);
AUTOCIRCULATE_TRANSFER outputXfer; // My A/C output transfer info
device.AutoCirculateStop (NTV2_CHANNEL3);
device.AutoCirculateInitForOutput (NTV2_CHANNEL3, /*#frames*/ 7, NTV2_AUDIOSYSTEM_INVALID, AUTOCIRCULATE_WITH_RP188);
device.AutoCirculateStart (NTV2_CHANNEL3);
while (!mGlobalQuit)
{
AUTOCIRCULATE_STATUS acStatus;
device.AutoCirculateGetStatus (NTV2_CHANNEL3, acStatus);
if (acStatus.IsRunning () && acStatus.CanAcceptMoreOutputFrames ())
{
NTV2_RP188 tc;
outputXfer.SetOutputTimeCode (tc, NTV2_TCINDEX_SDI3);
outputXfer.SetOutputTimeCode (tc, NTV2_TCINDEX_SDI4);
outputXfer.SetOutputTimeCode (tc, NTV2_TCINDEX_SDI5);
// Transfer from the host to the device...
device.AutoCirculateTransfer (NTV2_CHANNEL3, outputXfer);
} // if A/C running and frame is available for transfer
else
device.WaitForOutputVerticalInterrupt (NTV2_CHANNEL3);
} // loop til quit signaled
device.AutoCirculateStop (NTV2_CHANNEL3);
. . .