]> git.sesse.net Git - nageru/commitdiff
Add a system for queue length policies, so that we have as little as possible (but...
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Sat, 2 Apr 2016 13:22:11 +0000 (15:22 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Sat, 2 Apr 2016 13:22:11 +0000 (15:22 +0200)
mixer.cpp
mixer.h

index 104a6c03a22278b9b9ddfde7e8eb7edc4f9ad96e..52d285ce632e9e922cd48337cdcd91a2c1a2e4ef 100644 (file)
--- a/mixer.cpp
+++ b/mixer.cpp
@@ -111,6 +111,29 @@ string generate_local_dump_filename(int frame)
 
 }  // namespace
 
+void QueueLengthPolicy::update_policy(int queue_length)
+{
+       if (queue_length < 0) {  // Starvation.
+               if (safe_queue_length < 5) {
+                       ++safe_queue_length;
+                       fprintf(stderr, "Card %u: Starvation, increasing safe limit to %u frames\n",
+                               card_index, safe_queue_length);
+               }
+               frames_with_at_least_one = 0;
+               return;
+       }
+       if (queue_length > 0) {
+               if (++frames_with_at_least_one >= 50) {
+                       --safe_queue_length;
+                       fprintf(stderr, "Card %u: Spare frames for more than 50 frames, reducing safe limit to %u frames\n",
+                               card_index, safe_queue_length);
+                       frames_with_at_least_one = 0;
+               }
+       } else {
+               frames_with_at_least_one = 0;
+       }
+}
+
 Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
        : httpd(WIDTH, HEIGHT),
          num_cards(num_cards),
@@ -182,6 +205,7 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
        }
 
        for (card_index = 0; card_index < num_cards; ++card_index) {
+               cards[card_index].queue_length_policy.reset(card_index);
                cards[card_index].capture->start_bm_capture();
        }
 
@@ -593,6 +617,8 @@ void Mixer::thread_func()
                        for (unsigned card_index = 0; card_index < num_cards; ++card_index) {
                                CaptureCard *card = &cards[card_index];
                                if (card->new_frames.empty()) {
+                                       assert(card_index != master_card_index);
+                                       card->queue_length_policy.update_policy(-1);
                                        continue;
                                }
                                new_frames[card_index] = move(card->new_frames.front());
@@ -604,6 +630,15 @@ void Mixer::thread_func()
                                num_samples[card_index] = num_samples_times_timebase / TIMEBASE;
                                card->fractional_samples = num_samples_times_timebase % TIMEBASE;
                                assert(num_samples[card_index] >= 0);
+
+                               if (card_index != master_card_index) {
+                                       // If we have excess frames compared to the policy for this card,
+                                       // drop frames from the head.
+                                       card->queue_length_policy.update_policy(card->new_frames.size());
+                                       while (card->new_frames.size() > card->queue_length_policy.get_safe_queue_length()) {
+                                               card->new_frames.pop();
+                                       }
+                               }
                        }
                }
 
diff --git a/mixer.h b/mixer.h
index 90a8869360894a372b3a74fdc41442129b070632..f1e5fa2bc6498a6c0b6935bd349a381453dc3060 100644 (file)
--- a/mixer.h
+++ b/mixer.h
@@ -52,6 +52,47 @@ class YCbCrInput;
 class QOpenGLContext;
 class QSurfaceFormat;
 
+// For any card that's not the master (where we pick out the frames as they
+// come, as fast as we can process), there's going to be a queue. The question
+// is when we should drop frames from that queue (apart from the obvious
+// dropping if the 16-frame queue should become full), especially given that
+// the frame rate could be lower or higher than the master (either subtly or
+// dramatically). We have two (conflicting) demands:
+//
+//   1. We want to avoid starving the queue.
+//   2. We don't want to add more delay than is needed.
+//
+// Our general strategy is to drop as many frames as we can (helping for #2)
+// that we think is safe for #1 given jitter. To this end, we set a lower floor N,
+// where we assume that if we have N frames in the queue, we're always safe from
+// starvation. (Typically, N will be 0 or 1. It starts off at 0.) If we have
+// more than N frames in the queue after reading out the one we need, we head-drop
+// them to reduce the queue.
+//
+// N is reduced as follows: If the queue has had at least one spare frame for
+// at least 50 (master) frames (ie., it's been too conservative for a second),
+// we reduce N by 1 and reset the timers.
+//
+// Whenever the queue is starved (we needed a frame but there was none), N was
+// obviously too low, so we increment N. We will never set N above 5, though.
+class QueueLengthPolicy {
+public:
+       QueueLengthPolicy() {}
+       void reset(unsigned card_index) {
+               this->card_index = card_index;
+               safe_queue_length = 0;
+               frames_with_at_least_one = 0;
+       }
+
+       void update_policy(int queue_length);  // Give in -1 for starvation.
+       unsigned get_safe_queue_length() const { return safe_queue_length; }
+
+private:
+       unsigned card_index;  // For debugging only.
+       unsigned safe_queue_length = 0;  // Called N in the comments.
+       unsigned frames_with_at_least_one = 0;
+};
+
 class Mixer {
 public:
        // The surface format is used for offscreen destinations for OpenGL contexts we need.
@@ -340,6 +381,8 @@ private:
                bool should_quit = false;
                std::condition_variable new_frames_changed;  // Set whenever new_frames (or should_quit) is changed.
 
+               QueueLengthPolicy queue_length_policy;  // Refers to the "new_frames" queue.
+
                // Accumulated errors in number of 1/TIMEBASE samples. If OUTPUT_FREQUENCY divided by
                // frame rate is integer, will always stay zero.
                unsigned fractional_samples = 0;