aja: Fix output of garbage video during preroll

Add additional logging of card frame counts/indices
master
Paul Hindt 2022-08-01 19:42:01 -07:00 committed by Jim
parent 33081419e7
commit 44acdd68b0
2 changed files with 113 additions and 85 deletions

View File

@ -18,14 +18,16 @@
// Log AJA Output video/audio delay/sync // Log AJA Output video/audio delay/sync
// #define AJA_OUTPUT_STATS // #define AJA_OUTPUT_STATS
// Log AJA Output card frame buffer numbers
// #define AJA_OUTPUT_FRAME_NUMBERS
#define MATCH_OBS_FRAMERATE true #define MATCH_OBS_FRAMERATE true
static constexpr uint32_t kNumCardFrames = 8; static constexpr uint32_t kNumCardFrames = 8;
static constexpr uint32_t kFrameDelay = 4; static constexpr uint32_t kFrameDelay = 4;
static const int64_t kDefaultStatPeriod = 2000000000; static constexpr int64_t kDefaultStatPeriod = 2000000000;
static const int64_t kAudioSyncAdjust = 500; static constexpr int64_t kAudioSyncAdjust = 500;
static const int64_t kVideoSyncAdjust = 1; static constexpr int64_t kVideoSyncAdjust = 1;
static void copy_audio_data(struct audio_data *src, struct audio_data *dst, static void copy_audio_data(struct audio_data *src, struct audio_data *dst,
size_t size) size_t size)
@ -93,10 +95,9 @@ AJAOutput::AJAOutput(CNTV2Card *card, const std::string &cardID,
mAudioPlayCursor{0}, mAudioPlayCursor{0},
mAudioWriteCursor{0}, mAudioWriteCursor{0},
mAudioWrapAddress{0}, mAudioWrapAddress{0},
mAudioRate{0}, mAudioQueueBytes{0},
mAudioQueueSamples{0}, mAudioWriteBytes{0},
mAudioWriteSamples{0}, mAudioPlayBytes{0},
mAudioPlaySamples{0},
mNumCardFrames{0}, mNumCardFrames{0},
mFirstCardFrame{0}, mFirstCardFrame{0},
mLastCardFrame{0}, mLastCardFrame{0},
@ -158,18 +159,23 @@ CNTV2Card *AJAOutput::GetCard()
void AJAOutput::Initialize(const OutputProps &props) void AJAOutput::Initialize(const OutputProps &props)
{ {
const auto &audioSystem = props.AudioSystem(); reset_frame_counts();
// Store the address to the end of the card's audio buffer. // Store the address to the end of the card's audio buffer.
mCard->GetAudioWrapAddress(mAudioWrapAddress, audioSystem); mCard->GetAudioWrapAddress(mAudioWrapAddress, props.AudioSystem());
// Specify the frame indices for the "on-air" frames on the card. // Specify small ring of frame buffers on the card for DMA and playback.
// Starts at frame index corresponding to the output Channel * numFrames // Starts at frame index corresponding to the output Channel * numFrames.
calculate_card_frame_indices(kNumCardFrames, mCard->GetDeviceID(), calculate_card_frame_indices(kNumCardFrames, mCard->GetDeviceID(),
props.Channel(), props.videoFormat, props.Channel(), props.videoFormat,
props.pixelFormat); props.pixelFormat);
mCard->SetOutputFrame(props.Channel(), mWriteCardFrame); // Write black frames to the card to prevent playing garbage before the first DMA write.
for (uint32_t i = mFirstCardFrame; i <= mLastCardFrame; i++) {
GenerateTestPattern(props.videoFormat, props.pixelFormat,
NTV2_TestPatt_Black, i);
}
mCard->WaitForOutputVerticalInterrupt(props.Channel()); mCard->WaitForOutputVerticalInterrupt(props.Channel());
const auto &cardFrameRate = const auto &cardFrameRate =
GetNTV2FrameRateFromVideoFormat(props.videoFormat); GetNTV2FrameRateFromVideoFormat(props.videoFormat);
@ -183,11 +189,20 @@ void AJAOutput::Initialize(const OutputProps &props)
mVideoMax = (int64_t)kVideoSyncAdjust * 1000000 * mFrameRateDen / mVideoMax = (int64_t)kVideoSyncAdjust * 1000000 * mFrameRateDen /
mFrameRateNum; mFrameRateNum;
mVideoMax -= 100; mVideoMax -= 100;
mAudioRate = props.audioSampleRate;
mAudioMax = (int64_t)kAudioSyncAdjust * 1000000 / props.audioSampleRate; mAudioMax = (int64_t)kAudioSyncAdjust * 1000000 / props.audioSampleRate;
SetOutputProps(props); SetOutputProps(props);
} }
void AJAOutput::reset_frame_counts()
{
mAudioQueueBytes = 0;
mAudioWriteBytes = 0;
mAudioPlayBytes = 0;
mVideoQueueFrames = 0;
mVideoWriteFrames = 0;
mVideoPlayFrames = 0;
}
void AJAOutput::SetOBSOutput(obs_output_t *output) void AJAOutput::SetOBSOutput(obs_output_t *output)
{ {
mOBSOutput = output; mOBSOutput = output;
@ -223,11 +238,11 @@ void AJAOutput::ClearConnections()
} }
void AJAOutput::GenerateTestPattern(NTV2VideoFormat vf, NTV2PixelFormat pf, void AJAOutput::GenerateTestPattern(NTV2VideoFormat vf, NTV2PixelFormat pf,
NTV2TestPatternSelect pattern) NTV2TestPatternSelect pattern,
uint32_t frameNum)
{ {
NTV2VideoFormat vid_fmt = vf; NTV2VideoFormat vid_fmt = vf;
NTV2PixelFormat pix_fmt = pf; NTV2PixelFormat pix_fmt = pf;
if (vid_fmt == NTV2_FORMAT_UNKNOWN) if (vid_fmt == NTV2_FORMAT_UNKNOWN)
vid_fmt = NTV2_FORMAT_720p_5994; vid_fmt = NTV2_FORMAT_720p_5994;
if (pix_fmt == NTV2_FBF_INVALID) if (pix_fmt == NTV2_FBF_INVALID)
@ -235,12 +250,10 @@ void AJAOutput::GenerateTestPattern(NTV2VideoFormat vf, NTV2PixelFormat pf,
NTV2FormatDesc fd(vid_fmt, pix_fmt, NTV2_VANCMODE_OFF); NTV2FormatDesc fd(vid_fmt, pix_fmt, NTV2_VANCMODE_OFF);
auto bufSize = fd.GetTotalRasterBytes(); auto bufSize = fd.GetTotalRasterBytes();
// Raster size changed, regenerate pattern
if (bufSize != mTestPattern.size()) { if (bufSize != mTestPattern.size()) {
// Raster size changed, regenerate pattern
mTestPattern.clear(); mTestPattern.clear();
mTestPattern.resize(bufSize); mTestPattern.resize(bufSize);
NTV2TestPatternGen gen; NTV2TestPatternGen gen;
gen.DrawTestPattern(pattern, fd.GetRasterWidth(), gen.DrawTestPattern(pattern, fd.GetRasterWidth(),
fd.GetRasterHeight(), pix_fmt, fd.GetRasterHeight(), pix_fmt,
@ -253,14 +266,12 @@ void AJAOutput::GenerateTestPattern(NTV2VideoFormat vf, NTV2PixelFormat pf,
return; return;
} }
auto outputChannel = mOutputProps.Channel(); if (mCard->DMAWriteFrame(
frameNum,
mCard->SetOutputFrame(outputChannel, mWriteCardFrame); reinterpret_cast<ULWord *>(&mTestPattern.data()[0]),
static_cast<ULWord>(mTestPattern.size()))) {
mCard->DMAWriteFrame( mCard->SetOutputFrame(mOutputProps.Channel(), frameNum);
mWriteCardFrame, }
reinterpret_cast<ULWord *>(&mTestPattern.data()[0]),
static_cast<ULWord>(mTestPattern.size()));
} }
void AJAOutput::QueueVideoFrame(struct video_data *frame, size_t size) void AJAOutput::QueueVideoFrame(struct video_data *frame, size_t size)
@ -302,8 +313,7 @@ void AJAOutput::QueueAudioFrames(struct audio_data *frames, size_t size)
copy_audio_data(frames, &af.frames, size); copy_audio_data(frames, &af.frames, size);
mAudioQueue->push_back(af); mAudioQueue->push_back(af);
mAudioQueueSamples += size / ((uint64_t)kDefaultAudioChannels * mAudioQueueBytes += size;
kDefaultAudioSampleSize);
} }
void AJAOutput::ClearVideoQueue() void AJAOutput::ClearVideoQueue()
@ -337,7 +347,8 @@ size_t AJAOutput::AudioQueueSize()
} }
// lock audio queue before calling // lock audio queue before calling
void AJAOutput::DMAAudioFromQueue(NTV2AudioSystem audioSys) void AJAOutput::DMAAudioFromQueue(NTV2AudioSystem audioSys, uint32_t channels,
uint32_t sampleRate, uint32_t sampleSize)
{ {
AudioFrames &af = mAudioQueue->front(); AudioFrames &af = mAudioQueue->front();
size_t sizeLeft = af.size - af.offset; size_t sizeLeft = af.size - af.offset;
@ -357,28 +368,25 @@ void AJAOutput::DMAAudioFromQueue(NTV2AudioSystem audioSys)
// Calculate audio delay // Calculate audio delay
uint32_t audioPlaySamples = 0; uint32_t audioPlaySamples = 0;
uint32_t audioPlayBytes = mAudioWriteCursor - mAudioPlayCursor;
if (mAudioPlayCursor <= mAudioWriteCursor) { if (mAudioPlayCursor <= mAudioWriteCursor) {
audioPlaySamples = audioPlaySamples = audioPlayBytes / (channels * sampleSize);
(mAudioWriteCursor - mAudioPlayCursor) /
(kDefaultAudioChannels * kDefaultAudioSampleSize);
} else { } else {
audioPlaySamples = audioPlayBytes = mAudioWrapAddress - mAudioPlayCursor +
(mAudioWrapAddress - mAudioPlayCursor + mAudioWriteCursor;
mAudioWriteCursor) / audioPlaySamples = audioPlayBytes / (channels * sampleSize);
(kDefaultAudioChannels * kDefaultAudioSampleSize);
} }
mAudioDelay = 1000000 * (int64_t)audioPlaySamples / mAudioRate; mAudioDelay = 1000000 * (int64_t)audioPlaySamples / sampleRate;
mAudioPlayBytes += audioPlayBytes;
// Adjust audio sync when requested // Adjust audio sync when requested
if (mAudioAdjust != 0) { if (mAudioAdjust != 0) {
if (mAudioAdjust > 0) { if (mAudioAdjust > 0) {
// Throw away some samples to resync audio // Throw away some samples to resync audio
uint32_t adjustSamples = uint32_t adjustSamples =
(uint32_t)mAudioAdjust * mAudioRate / 1000000; (uint32_t)mAudioAdjust * sampleRate / 1000000;
uint32_t adjustSize = adjustSamples * uint32_t adjustSize =
kDefaultAudioSampleSize * adjustSamples * sampleSize * channels;
kDefaultAudioChannels;
if (adjustSize <= sizeLeft) { if (adjustSize <= sizeLeft) {
af.offset += adjustSize; af.offset += adjustSize;
sizeLeft -= adjustSize; sizeLeft -= adjustSize;
@ -388,13 +396,12 @@ void AJAOutput::DMAAudioFromQueue(NTV2AudioSystem audioSys)
adjustSamples); adjustSamples);
} else { } else {
uint32_t samples = (uint32_t)sizeLeft / uint32_t samples = (uint32_t)sizeLeft /
(kDefaultAudioSampleSize * (sampleSize * channels);
kDefaultAudioChannels);
af.offset += sizeLeft; af.offset += sizeLeft;
sizeLeft = 0; sizeLeft = 0;
adjustSamples -= samples; adjustSamples -= samples;
mAudioAdjust = (int64_t)adjustSamples * mAudioAdjust = (int64_t)adjustSamples *
1000000 / mAudioRate; 1000000 / sampleRate;
blog(LOG_DEBUG, blog(LOG_DEBUG,
"AJAOutput::DMAAudioFromQueue: Drop %d audio samples", "AJAOutput::DMAAudioFromQueue: Drop %d audio samples",
samples); samples);
@ -402,10 +409,9 @@ void AJAOutput::DMAAudioFromQueue(NTV2AudioSystem audioSys)
} else { } else {
// Add some silence to resync audio // Add some silence to resync audio
uint32_t adjustSamples = (uint32_t)(-mAudioAdjust) * uint32_t adjustSamples = (uint32_t)(-mAudioAdjust) *
mAudioRate / 1000000; sampleRate / 1000000;
uint32_t adjustSize = adjustSamples * uint32_t adjustSize =
kDefaultAudioSampleSize * adjustSamples * sampleSize * channels;
kDefaultAudioChannels;
uint8_t *silentBuffer = new uint8_t[adjustSize]; uint8_t *silentBuffer = new uint8_t[adjustSize];
memset(silentBuffer, 0, adjustSize); memset(silentBuffer, 0, adjustSize);
dma_audio_samples(audioSys, (uint32_t *)silentBuffer, dma_audio_samples(audioSys, (uint32_t *)silentBuffer,
@ -480,7 +486,7 @@ void AJAOutput::DMAVideoFromQueue()
(ULWord)vf.size); (ULWord)vf.size);
if (!result) if (!result)
blog(LOG_DEBUG, blog(LOG_DEBUG,
"AJAOutput::DMAVideoFromQueue: Failed ot write video frame!"); "AJAOutput::DMAVideoFromQueue: Failed to write video frame!");
} }
if (freeFrame) { if (freeFrame) {
@ -509,17 +515,18 @@ void AJAOutput::calculate_card_frame_indices(uint32_t numFrames,
mNumCardFrames = numFrames; mNumCardFrames = numFrames;
mWriteCardFrame = mFirstCardFrame; mWriteCardFrame = mFirstCardFrame;
mLastCardFrame = lastFrame; mLastCardFrame = lastFrame;
mPlayCardFrame = mWriteCardFrame;
mPlayCardNext = mPlayCardFrame + 1;
blog(LOG_INFO, "AJA Output using %d card frames (%d-%d).",
mNumCardFrames, mFirstCardFrame, mLastCardFrame);
} else { } else {
// otherwise just grab 2 frames to ping-pong between blog(LOG_WARNING,
mNumCardFrames = 2; "AJA Output Card frames %d-%d out of bounds. %d total frames on card!",
mWriteCardFrame = channelIndex * 2; mFirstCardFrame, mLastCardFrame, totalCardFrames);
mLastCardFrame = mWriteCardFrame + (mNumCardFrames - 1);
} }
blog(LOG_DEBUG, "AJA Output using %d card frame indices (%d-%d)",
mNumCardFrames, mFirstCardFrame, mLastCardFrame);
} }
uint32_t AJAOutput::get_frame_count() uint32_t AJAOutput::get_card_play_count()
{ {
uint32_t frameCount = 0; uint32_t frameCount = 0;
NTV2Channel channel = mOutputProps.Channel(); NTV2Channel channel = mOutputProps.Channel();
@ -555,9 +562,6 @@ void AJAOutput::dma_audio_samples(NTV2AudioSystem audioSys, uint32_t *data,
{ {
bool result = false; bool result = false;
mAudioWriteSamples += size / ((uint64_t)kDefaultAudioChannels *
kDefaultAudioSampleSize);
if ((mAudioWriteCursor + size) > mAudioWrapAddress) { if ((mAudioWriteCursor + size) > mAudioWrapAddress) {
const uint32_t remainingBuffer = const uint32_t remainingBuffer =
mAudioWrapAddress - mAudioWriteCursor; mAudioWrapAddress - mAudioWriteCursor;
@ -571,7 +575,9 @@ void AJAOutput::dma_audio_samples(NTV2AudioSystem audioSys, uint32_t *data,
result = mCard->DMAWriteAudio(audioSys, data, result = mCard->DMAWriteAudio(audioSys, data,
mAudioWriteCursor, mAudioWriteCursor,
remainingBuffer); remainingBuffer);
if (!result) { if (result) {
mAudioWriteBytes += remainingBuffer;
} else {
blog(LOG_DEBUG, blog(LOG_DEBUG,
"AJAOutput::dma_audio_samples: " "AJAOutput::dma_audio_samples: "
"failed to write bytes at end of buffer (address = %d)", "failed to write bytes at end of buffer (address = %d)",
@ -580,11 +586,14 @@ void AJAOutput::dma_audio_samples(NTV2AudioSystem audioSys, uint32_t *data,
} }
// ...transfer remaining bytes at the front of the card audio buffer. // ...transfer remaining bytes at the front of the card audio buffer.
if (size - remainingBuffer > 0) { size_t frontRemaining = size - remainingBuffer;
result = mCard->DMAWriteAudio( if (frontRemaining > 0) {
audioSys, audioDataRemain, 0, result = mCard->DMAWriteAudio(audioSys, audioDataRemain,
(uint32_t)size - remainingBuffer); 0,
if (!result) { (ULWord)frontRemaining);
if (result) {
mAudioWriteBytes += frontRemaining;
} else {
blog(LOG_DEBUG, blog(LOG_DEBUG,
"AJAOutput::dma_audio_samples " "AJAOutput::dma_audio_samples "
"failed to write bytes at front of buffer (address = %d)", "failed to write bytes at front of buffer (address = %d)",
@ -599,7 +608,9 @@ void AJAOutput::dma_audio_samples(NTV2AudioSystem audioSys, uint32_t *data,
result = mCard->DMAWriteAudio(audioSys, data, result = mCard->DMAWriteAudio(audioSys, data,
mAudioWriteCursor, mAudioWriteCursor,
(ULWord)size); (ULWord)size);
if (!result) { if (result) {
mAudioWriteBytes += size;
} else {
blog(LOG_DEBUG, blog(LOG_DEBUG,
"AJAOutput::dma_audio_samples " "AJAOutput::dma_audio_samples "
"failed to write bytes to buffer (address = %d)", "failed to write bytes to buffer (address = %d)",
@ -659,7 +670,7 @@ void AJAOutput::OutputThread(AJAThread *thread, void *ctx)
const auto &props = ajaOutput->GetOutputProps(); const auto &props = ajaOutput->GetOutputProps();
const auto &audioSystem = props.AudioSystem(); const auto &audioSystem = props.AudioSystem();
uint64_t videoPlayLast = ajaOutput->get_frame_count(); uint64_t videoPlayLast = ajaOutput->get_card_play_count();
uint32_t audioSyncSlowCount = 0; uint32_t audioSyncSlowCount = 0;
uint32_t audioSyncFastCount = 0; uint32_t audioSyncFastCount = 0;
uint32_t audioSyncCountMax = 3; uint32_t audioSyncCountMax = 3;
@ -681,7 +692,7 @@ void AJAOutput::OutputThread(AJAThread *thread, void *ctx)
} }
// Check if a vsync occurred // Check if a vsync occurred
uint32_t frameCount = ajaOutput->get_frame_count(); uint32_t frameCount = ajaOutput->get_card_play_count();
if (frameCount > videoPlayLast) { if (frameCount > videoPlayLast) {
videoPlayLast = frameCount; videoPlayLast = frameCount;
ajaOutput->mPlayCardFrame = ajaOutput->mPlayCardNext; ajaOutput->mPlayCardFrame = ajaOutput->mPlayCardNext;
@ -702,8 +713,8 @@ void AJAOutput::OutputThread(AJAThread *thread, void *ctx)
ajaOutput->mOutputProps ajaOutput->mOutputProps
.Channel(), .Channel(),
ajaOutput->mPlayCardNext); ajaOutput->mPlayCardNext);
ajaOutput->mVideoPlayFrames++;
} }
ajaOutput->mVideoPlayFrames++;
} }
} }
@ -712,7 +723,10 @@ void AJAOutput::OutputThread(AJAThread *thread, void *ctx)
const std::lock_guard<std::mutex> lock( const std::lock_guard<std::mutex> lock(
ajaOutput->mAudioLock); ajaOutput->mAudioLock);
while (ajaOutput->AudioQueueSize() > 0) { while (ajaOutput->AudioQueueSize() > 0) {
ajaOutput->DMAAudioFromQueue(audioSystem); ajaOutput->DMAAudioFromQueue(
audioSystem, props.audioNumChannels,
props.audioSampleRate,
props.audioSampleSize);
} }
} }
@ -810,15 +824,28 @@ void AJAOutput::OutputThread(AJAThread *thread, void *ctx)
ajaOutput->mAudioMax); ajaOutput->mAudioMax);
#endif #endif
} }
#ifdef AJA_OUTPUT_FRAME_NUMBERS
blog(LOG_INFO,
"AJAOutput::OutputThread: dma: %d play: %d next: %d",
ajaOutput->mWriteCardFrame, ajaOutput->mPlayCardFrame,
ajaOutput->mPlayCardNext);
#endif
os_sleep_ms(1); os_sleep_ms(1);
} }
ajaOutput->mAudioStarted = false; ajaOutput->mAudioStarted = false;
blog(LOG_INFO, // Log total number of queued/written/played video frames and audio samples
"AJAOutput::OutputThread: Thread stopped. Played %lld video frames", uint32_t audioSize = props.audioNumChannels / props.audioSampleSize;
(long long int)ajaOutput->mVideoQueueFrames); if (audioSize > 0) {
blog(LOG_INFO,
"AJAOutput::OutputThread: Thread stopped\n[Video] qf: %lu wf: %lu pf: %lu\n[Audio] qs: %lu ws: %lu ps: %lu",
ajaOutput->mVideoQueueFrames, ajaOutput->mVideoWriteFrames,
ajaOutput->mVideoPlayFrames,
ajaOutput->mAudioQueueBytes / audioSize,
ajaOutput->mAudioWriteBytes / audioSize,
ajaOutput->mAudioPlayBytes / audioSize);
}
} }
void populate_output_device_list(obs_property_t *list) void populate_output_device_list(obs_property_t *list)
@ -1252,7 +1279,8 @@ static void aja_output_stop(void *data, uint64_t ts)
ajaOutput->GenerateTestPattern(outputProps.videoFormat, ajaOutput->GenerateTestPattern(outputProps.videoFormat,
outputProps.pixelFormat, outputProps.pixelFormat,
NTV2_TestPatt_Black); NTV2_TestPatt_Black,
ajaOutput->mWriteCardFrame);
obs_output_end_data_capture(ajaOutput->GetOBSOutput()); obs_output_end_data_capture(ajaOutput->GetOBSOutput());
auto audioSystem = outputProps.AudioSystem(); auto audioSystem = outputProps.AudioSystem();

View File

@ -62,7 +62,8 @@ public:
void ClearConnections(); void ClearConnections();
void GenerateTestPattern(NTV2VideoFormat vf, NTV2PixelFormat pf, void GenerateTestPattern(NTV2VideoFormat vf, NTV2PixelFormat pf,
NTV2TestPatternSelect pattern); NTV2TestPatternSelect pattern,
uint32_t frameNum);
void QueueVideoFrame(struct video_data *frame, size_t size); void QueueVideoFrame(struct video_data *frame, size_t size);
void QueueAudioFrames(struct audio_data *frames, size_t size); void QueueAudioFrames(struct audio_data *frames, size_t size);
@ -71,7 +72,8 @@ public:
size_t VideoQueueSize(); size_t VideoQueueSize();
size_t AudioQueueSize(); size_t AudioQueueSize();
void DMAAudioFromQueue(NTV2AudioSystem audioSys); void DMAAudioFromQueue(NTV2AudioSystem audioSys, uint32_t channels,
uint32_t sampleRate, uint32_t sampleSize);
void DMAVideoFromQueue(); void DMAVideoFromQueue();
void CreateThread(bool enable = false); void CreateThread(bool enable = false);
@ -87,11 +89,10 @@ public:
uint32_t mAudioPlayCursor; uint32_t mAudioPlayCursor;
uint32_t mAudioWriteCursor; uint32_t mAudioWriteCursor;
uint32_t mAudioWrapAddress; uint32_t mAudioWrapAddress;
uint32_t mAudioRate;
uint64_t mAudioQueueSamples; uint64_t mAudioQueueBytes;
uint64_t mAudioWriteSamples; uint64_t mAudioWriteBytes;
uint64_t mAudioPlaySamples; uint64_t mAudioPlayBytes;
uint32_t mNumCardFrames; uint32_t mNumCardFrames;
uint32_t mFirstCardFrame; uint32_t mFirstCardFrame;
@ -126,13 +127,12 @@ public:
#endif #endif
private: private:
void reset_frame_counts();
void calculate_card_frame_indices(uint32_t numFrames, NTV2DeviceID id, void calculate_card_frame_indices(uint32_t numFrames, NTV2DeviceID id,
NTV2Channel channel, NTV2Channel channel,
NTV2VideoFormat vf, NTV2VideoFormat vf,
NTV2PixelFormat pf); NTV2PixelFormat pf);
uint32_t get_card_play_count();
uint32_t get_frame_count();
void dma_audio_samples(NTV2AudioSystem audioSys, uint32_t *data, void dma_audio_samples(NTV2AudioSystem audioSys, uint32_t *data,
size_t size); size_t size);