Procházet zdrojové kódy

aja: Fix output of garbage video during preroll

Add additional logging of card frame counts/indices
Paul Hindt před 3 roky
rodič
revize
44acdd68b0
2 změnil soubory, kde provedl 113 přidání a 85 odebrání
  1. 104 76
      plugins/aja/aja-output.cpp
  2. 9 9
      plugins/aja/aja-output.hpp

+ 104 - 76
plugins/aja/aja-output.cpp

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

+ 9 - 9
plugins/aja/aja-output.hpp

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