aja: Adjust delay when sending frames to card

The plugin will try to maintain a hardware queue depth of 4 frames,

adjusting video and audio delay dynamically.
This commit is contained in:
davids 2022-07-29 15:49:51 -07:00 committed by Jim
parent b247a401c9
commit 33081419e7
2 changed files with 143 additions and 63 deletions

View File

@ -16,14 +16,16 @@
#include <atomic>
#include <stdlib.h>
// Log AJA Output video/audio delay and av-sync
// Log AJA Output video/audio delay/sync
// #define AJA_OUTPUT_STATS
#define MATCH_OBS_FRAMERATE true
static constexpr uint32_t kNumCardFrames = 3;
static const int64_t kDefaultStatPeriod = 3000000000;
static const int64_t kAudioSyncAdjust = 20000;
static constexpr uint32_t kNumCardFrames = 8;
static constexpr uint32_t kFrameDelay = 4;
static const int64_t kDefaultStatPeriod = 2000000000;
static const int64_t kAudioSyncAdjust = 500;
static const int64_t kVideoSyncAdjust = 1;
static void copy_audio_data(struct audio_data *src, struct audio_data *dst,
size_t size)
@ -110,9 +112,14 @@ AJAOutput::AJAOutput(CNTV2Card *card, const std::string &cardID,
mFirstAudioTS{0},
mLastVideoTS{0},
mLastAudioTS{0},
mOutputDelay{0},
mVideoMax{0},
mVideoDelay{0},
mVideoSync{0},
mVideoAdjust{0},
mAudioMax{0},
mAudioDelay{0},
mAudioVideoSync{0},
mAudioSync{0},
mAudioAdjust{0},
mLastStatTime{0},
#ifdef AJA_WRITE_DEBUG_WAV
@ -171,9 +178,13 @@ void AJAOutput::Initialize(const OutputProps &props)
GetFramesPerSecond(cardFrameRate, fpsNum, fpsDen);
mFrameRateNum = fpsNum;
mFrameRateDen = fpsDen;
mVideoDelay = ((int64_t)mNumCardFrames - 0) * 1000000 * mFrameRateDen /
mFrameRateNum;
mOutputDelay =
(int64_t)kFrameDelay * 1000000 * mFrameRateDen / mFrameRateNum;
mVideoMax = (int64_t)kVideoSyncAdjust * 1000000 * mFrameRateDen /
mFrameRateNum;
mVideoMax -= 100;
mAudioRate = props.audioSampleRate;
mAudioMax = (int64_t)kAudioSyncAdjust * 1000000 / props.audioSampleRate;
SetOutputProps(props);
}
@ -425,6 +436,9 @@ void AJAOutput::DMAAudioFromQueue(NTV2AudioSystem audioSys)
// lock video queue before calling
void AJAOutput::DMAVideoFromQueue()
{
bool writeFrame = true;
bool freeFrame = true;
auto &vf = mVideoQueue->front();
auto data = vf.frame.data[0];
@ -432,26 +446,47 @@ void AJAOutput::DMAVideoFromQueue()
mFirstVideoTS = vf.frame.timestamp;
mLastVideoTS = vf.frame.timestamp;
// find the next buffer
uint32_t writeCardFrame = mWriteCardFrame + 1;
if (writeCardFrame > mLastCardFrame)
writeCardFrame = mFirstCardFrame;
mVideoDelay = (((int64_t)mWriteCardFrame + (int64_t)mNumCardFrames -
(int64_t)mPlayCardFrame) %
(int64_t)mNumCardFrames) *
1000000 * mFrameRateDen / mFrameRateNum;
// use the next buffer if available
if (writeCardFrame != mPlayCardFrame)
mWriteCardFrame = writeCardFrame;
// Adjust video sync when requested
if (mVideoAdjust != 0) {
if (mVideoAdjust > 0) {
// skip writing this frame
writeFrame = false;
} else {
// write this frame twice
freeFrame = false;
}
mVideoAdjust = 0;
}
mVideoWriteFrames++;
if (writeFrame) {
// find the next buffer
uint32_t writeCardFrame = mWriteCardFrame + 1;
if (writeCardFrame > mLastCardFrame)
writeCardFrame = mFirstCardFrame;
auto result = mCard->DMAWriteFrame(mWriteCardFrame,
reinterpret_cast<ULWord *>(data),
(ULWord)vf.size);
if (!result)
blog(LOG_DEBUG,
"AJAOutput::DMAVideoFromQueue: Failed ot write video frame!");
// use the next buffer if available
if (writeCardFrame != mPlayCardFrame)
mWriteCardFrame = writeCardFrame;
free_video_frame(&vf.frame);
mVideoQueue->pop_front();
mVideoWriteFrames++;
auto result = mCard->DMAWriteFrame(
mWriteCardFrame, reinterpret_cast<ULWord *>(data),
(ULWord)vf.size);
if (!result)
blog(LOG_DEBUG,
"AJAOutput::DMAVideoFromQueue: Failed ot write video frame!");
}
if (freeFrame) {
free_video_frame(&vf.frame);
mVideoQueue->pop_front();
}
}
// TODO(paulh): Keep track of framebuffer indices used on the card, between the capture
@ -625,17 +660,20 @@ void AJAOutput::OutputThread(AJAThread *thread, void *ctx)
const auto &props = ajaOutput->GetOutputProps();
const auto &audioSystem = props.AudioSystem();
uint64_t videoPlayLast = ajaOutput->get_frame_count();
uint32_t audioSyncCount = 0;
uint32_t videoSyncCount = 0;
uint32_t syncCountMax = 5;
int64_t audioSyncSum = 0;
int64_t videoSyncSum = 0;
uint32_t audioSyncSlowCount = 0;
uint32_t audioSyncFastCount = 0;
uint32_t audioSyncCountMax = 3;
uint32_t videoSyncSlowCount = 0;
uint32_t videoSyncFastCount = 0;
uint32_t videoSyncCountMax = 3;
int64_t audioSyncSlowSum = 0;
int64_t audioSyncFastSum = 0;
// thread loop
while (ajaOutput->ThreadRunning()) {
// Wait for preroll
if (!ajaOutput->mAudioStarted &&
(ajaOutput->mAudioDelay > ajaOutput->mVideoDelay)) {
(ajaOutput->mAudioDelay > ajaOutput->mOutputDelay)) {
card->StartAudioOutput(audioSystem, false);
ajaOutput->mAudioStarted = true;
blog(LOG_DEBUG,
@ -692,47 +730,84 @@ void AJAOutput::OutputThread(AJAThread *thread, void *ctx)
card->ReadAudioLastOut(ajaOutput->mAudioPlayCursor,
audioSystem);
if (ajaOutput->mAudioStarted &&
((curTime - ajaOutput->mLastStatTime) >
kDefaultStatPeriod)) {
if ((curTime - ajaOutput->mLastStatTime) > kDefaultStatPeriod) {
ajaOutput->mLastStatTime = curTime;
// Calculate av sync delay
ajaOutput->mAudioVideoSync =
ajaOutput->mAudioDelay - ajaOutput->mVideoDelay;
if (ajaOutput->mAudioStarted) {
// Calculate audio sync delay
ajaOutput->mAudioSync = ajaOutput->mAudioDelay -
ajaOutput->mOutputDelay;
if (ajaOutput->mAudioVideoSync > kAudioSyncAdjust) {
audioSyncCount++;
audioSyncSum += ajaOutput->mAudioVideoSync;
if (audioSyncCount >= syncCountMax) {
ajaOutput->mAudioAdjust =
audioSyncSum / syncCountMax;
audioSyncCount = 0;
audioSyncSum = 0;
if (ajaOutput->mAudioSync >
ajaOutput->mAudioMax) {
audioSyncSlowCount++;
audioSyncSlowSum +=
ajaOutput->mAudioSync;
if (audioSyncSlowCount >=
audioSyncCountMax) {
ajaOutput->mAudioAdjust =
audioSyncSlowSum /
audioSyncCountMax;
audioSyncSlowCount = 0;
audioSyncSlowSum = 0;
}
} else {
audioSyncSlowCount = 0;
audioSyncSlowSum = 0;
}
if (ajaOutput->mAudioSync <
-ajaOutput->mAudioMax) {
audioSyncFastCount++;
audioSyncFastSum +=
ajaOutput->mAudioSync;
if (audioSyncFastCount >=
audioSyncCountMax) {
ajaOutput->mAudioAdjust =
audioSyncFastSum /
audioSyncCountMax;
audioSyncFastCount = 0;
audioSyncFastSum = 0;
}
} else {
audioSyncFastCount = 0;
audioSyncFastSum = 0;
}
} else {
audioSyncCount = 0;
audioSyncSum = 0;
}
if (ajaOutput->mAudioVideoSync < -kAudioSyncAdjust) {
videoSyncCount++;
videoSyncSum += ajaOutput->mAudioVideoSync;
if (videoSyncCount >= syncCountMax) {
ajaOutput->mAudioAdjust =
videoSyncSum / syncCountMax;
videoSyncCount = 0;
videoSyncSum = 0;
// calculate video sync delay
if (ajaOutput->mVideoDelay > 0) {
ajaOutput->mVideoSync = ajaOutput->mVideoDelay -
ajaOutput->mOutputDelay;
if (ajaOutput->mVideoSync >
ajaOutput->mVideoMax) {
videoSyncSlowCount++;
if (videoSyncSlowCount >
videoSyncCountMax) {
ajaOutput->mVideoAdjust = 1;
}
} else {
videoSyncSlowCount = 0;
}
if (ajaOutput->mVideoSync <
-ajaOutput->mVideoMax) {
videoSyncFastCount++;
if (videoSyncFastCount >
videoSyncCountMax) {
ajaOutput->mVideoAdjust = -1;
}
} else {
videoSyncFastCount = 0;
}
} else {
videoSyncCount = 0;
videoSyncSum = 0;
}
#ifdef AJA_OUTPUT_STATS
blog(LOG_DEBUG,
"AJAOutput::OutputThread: vd %li ad %li avs %li",
ajaOutput->mVideoDelay, ajaOutput->mAudioDelay,
ajaOutput->mAudioVideoSync);
blog(LOG_INFO,
"AJAOutput::OutputThread: od %li vd %li vs %li vm %li ad %li as %li am %li",
ajaOutput->mOutputDelay, ajaOutput->mVideoDelay,
ajaOutput->mVideoSync, ajaOutput->mVideoMax,
ajaOutput->mAudioDelay, ajaOutput->mAudioSync,
ajaOutput->mAudioMax);
#endif
}
@ -743,7 +818,7 @@ void AJAOutput::OutputThread(AJAThread *thread, void *ctx)
blog(LOG_INFO,
"AJAOutput::OutputThread: Thread stopped. Played %lld video frames",
ajaOutput->mVideoQueueFrames);
(long long int)ajaOutput->mVideoQueueFrames);
}
void populate_output_device_list(obs_property_t *list)

View File

@ -111,9 +111,14 @@ public:
uint64_t mLastVideoTS;
uint64_t mLastAudioTS;
int64_t mOutputDelay;
int64_t mVideoMax;
int64_t mVideoDelay;
int64_t mVideoSync;
int64_t mVideoAdjust;
int64_t mAudioMax;
int64_t mAudioDelay;
int64_t mAudioVideoSync;
int64_t mAudioSync;
int64_t mAudioAdjust;
int64_t mLastStatTime;
#ifdef AJA_WRITE_DEBUG_WAV