UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
decklink_drift_fix.hpp
Go to the documentation of this file.
1
6/*
7 * Copyright (c) 2021-2023 CESNET, z. s. p. o.
8 * All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, is permitted provided that the following conditions
12 * are met:
13 *
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 *
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 *
21 * 3. Neither the name of CESNET nor the names of its contributors may be
22 * used to endorse or promote products derived from this software without
23 * specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
26 * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
27 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
28 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
29 * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
30 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
35 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
36 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 */
38
39#ifndef SRC_VIDEO_DISPLAY_DECKLINK_DRIFT_FIX_HPP_69DBC8A9_974D_46C5_833D_5A77CF35E034
40#define SRC_VIDEO_DISPLAY_DECKLINK_DRIFT_FIX_HPP_69DBC8A9_974D_46C5_833D_5A77CF35E034
41
42#include <cassert>
43#include <chrono>
44#include <cmath>
45#include <cstdint>
46#include <string>
47
48#include "debug.h"
49#include "host.h"
50#include "messaging.h"
51#include "rtp/audio_decoders.h"
52#include "utils/color_out.h"
53
54#define MAX_RESAMPLE_DELTA_DEFAULT 30
55#define MIN_RESAMPLE_DELTA_DEFAULT 1
56#define TARGET_BUFFER_DEFAULT 2700
57
59public:
60 MovingAverage(unsigned int period) :
61 period(period),
62 window(new double[period]),
63 head(NULL),
64 tail(NULL),
65 total(0) {
66
67 assert(period >= 1);
68 }
69
71 delete[] window;
72 }
73
74 void add(double val) {
75 // Init
76 if (head == NULL) {
77 head = window;
78 *head = val;
79 tail = head;
80 inc(tail);
81 total = val;
82 return;
83 }
84 // full?
85 if (head == tail) {
86 total -= *head;
87 inc(head);
88 }
89
90 *tail = val;
91 inc(tail);
92 total += val;
93 }
94
95 // Returns the average of the last P elements added.
96 // If no elements have been added yet, returns 0.0
97 double avg() const {
98 ptrdiff_t size = this->size();
99 if (size == 0) {
100 return 0; // No entries => 0 average
101 }
102 return total / (double)size;
103 }
104
105 ptrdiff_t size() const {
106 if (head == NULL)
107 return 0;
108 if (head == tail)
109 return period;
110 return (period + tail - head) % period;
111 }
112 // returns true if we have filled the period with samples
113 ptrdiff_t filled(){
114 bool filled = false;
115 if (this->size() >= (ptrdiff_t) this->period ){
116 filled = true;
117 }
118 return filled;
119 }
120
121 double getTotal() const {
122 return total;
123 }
124
125private:
126 unsigned int period;
127 double* window; // Holds the values to calculate the average of.
128 double* head;
129 double* tail;
130 double total; // Cache the total
131
132 void inc(double* &p) {
133 if (++p >= window + period) {
134 p = window;
135 }
136 }
137};
138
140public:
145 void report() {
146 std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
147 if(std::chrono::duration_cast<std::chrono::seconds>(now - this->last_summary).count() > 10) {
148 LOG(LOG_LEVEL_INFO) << SUNDERLINE("Decklink stats (cumulative)")
149 << " - Total Audio Frames Played: "
150 << SBOLD(this->frames_played)
151 << " / Missing Audio Frames: "
152 << SBOLD(this->frames_missed)
153 << " / Buffer Underflows: "
154 << SBOLD(this->buffer_underflow)
155 << " / Buffer Overflows: "
156 << SBOLD(this->buffer_overflow)
157 << " / Resample (Higher Hz): "
158 << SBOLD(this->resample_high)
159 << " / Resample (Lower Hz): "
160 << SBOLD(this->resample_low)
161 << " / Average Buffer: "
162 << SBOLD(this->buffer_average)
163 << " / Average Added Frames: "
164 << SBOLD(this->avg_added_frames.avg())
165 << " / Max time diff audio (ms): "
166 << SBOLD(this->audio_time_diff_max)
167 << " / Min time diff audio (ms): "
168 << SBOLD(this->audio_time_diff_min)
169 << "\n";
170 // Reset some of the variables
171 this->audio_time_diff_max = 0;
172 this->audio_time_diff_min = std::numeric_limits<long long>().max();
173 // Ensure that the summary gets called 30 seconds from now
174 this->last_summary = now;
175 }
176 }
177
183 this->resample_low++;
184 }
185
191 this->resample_high++;
192 }
193
198 this->buffer_overflow++;
199 }
200
205 this->buffer_underflow++;
206 }
207
212 this->frames_played++;
213 }
214
221 this->buffer_average = (int32_t)round(buffer_average);
222 }
223
233 void calculate_missing(uint32_t buffer_samples, uint32_t samples) {
234 this->avg_added_frames.add(samples);
235 if(this->avg_added_frames.filled()) {
236 samples = (uint32_t)this->avg_added_frames.avg();
237 }
238 // Check to see if the amount in the buffer has dropped by over half the average
239 // number of samples being written. If so, we likely dropped a frame.
240 if(prev_buffer_samples >= 0 && (uint32_t)this->prev_buffer_samples > buffer_samples + (samples / 2)) {
241 this->frames_missed++;
242 }
243 this->prev_buffer_samples = buffer_samples;
244 }
245
252 // CHeck the previous time has been initialised
253 if(this->prev_audio_end.time_since_epoch().count() != 0) {
254 // Collect the time now and do a comparison to the time when we ended the previous function call
255 std::chrono::high_resolution_clock::time_point audio_begin = std::chrono::high_resolution_clock::now();
256 std::chrono::milliseconds time_diff = std::chrono::duration_cast<std::chrono::milliseconds>(audio_begin - this->prev_audio_end);
257
258 // Set a max or min if the timing is outside of whats already been collected
259 long long duration_diff = time_diff.count();
260 if(duration_diff > this->audio_time_diff_max) {
261 this->audio_time_diff_max = duration_diff;
262 }
263 else if(duration_diff < this->audio_time_diff_min) {
264 this->audio_time_diff_min = duration_diff;
265 }
266 }
267 }
268
274 this->prev_audio_end = std::chrono::high_resolution_clock::now();
275 }
276private:
277 // Keep a track of the amount in the decklink buffer
279 // How many frames have been successfully written
280 uint32_t frames_played = 0;
281 // How many times the buffer dropped avg amount of frames being added
282 uint32_t frames_missed = 0;
284 // How many buffer underflows and overflows have occured.
285 uint32_t buffer_underflow = 0;
286 uint32_t buffer_overflow = 0;
287 // How many times it was requested a higher or lower sample rate
288 uint32_t resample_high = 0;
289 uint32_t resample_low = 0;
290 // Sample count average
291 uint32_t buffer_average = 0;
292 // Timing between calls of audio put
293 std::chrono::high_resolution_clock::time_point prev_audio_end{};
294 long long audio_time_diff_max = 0;
295 long long audio_time_diff_min = std::numeric_limits<long long>().max();
296 // We want to the summary to be outputted every 30 or so seconds. So keep track of
297 // the last we outputted data.
298 std::chrono::steady_clock::time_point last_summary = std::chrono::steady_clock::now();
299};
300
301
309public:
310 void enable() {
311 m_enabled = true;
312 if (commandline_params.find("resampler") == commandline_params.end()) {
313 LOG(LOG_LEVEL_INFO) << MOD_NAME << "Using SoxR resampler by default when DecKlink audio drift fixer is enabled.\n";
314 commandline_params["resampler"] = "soxr";
315 }
316 }
322 void set_max_hz(uint32_t max_hz) {
323 this->max_hz = max_hz;
324 }
325
331 void set_min_hz(uint32_t min_hz) {
332 this->min_hz = min_hz;
333 }
334
340 void set_target_buffer(uint32_t target_buffer) {
341 this->target_buffer_fill = target_buffer;
342 }
343
349 void set_root(module *root) {
350 m_root = root;
351 }
352
358 double get_buffer_avg() {
359 return this->average_buffer_samples.avg();
360 }
361
375 double scale_buffer_delta(int delta) {
376 // Get a positive delta so that the scale can be calculated properly
377 delta = abs(delta);
378 // Check the boundaries for the scaling calculation
379 if((uint32_t)delta > this->max_buffer) {
380 delta = this->max_buffer;
381 }
382 else if ((uint32_t)delta < this->min_buffer) {
383 delta = this->min_buffer;
384 }
385 return (((this->max_hz - this->min_hz) * (delta - this->min_buffer)) / (this->max_buffer - this->min_buffer)) + this->min_hz;
386 }
387
389 void update(uint32_t buffered_count, uint32_t sample_frame_count, uint32_t sampleFramesWritten) {
390 if (!this->m_enabled) {
391 return;
392 }
393
395 audio_summary.calculate_missing(buffered_count, sample_frame_count);
396 if (buffered_count == 0) {
398 }
399
400 // Add the amount currently in the buffer to the moving average, and calculate the delta between that and the previous amount
401 // Store the previous buffer count so we can calculate this next frame.
402 this->average_buffer_samples.add((double)buffered_count);
403 this->average_delta.add((double)abs((int32_t)buffered_count - (int32_t)previous_buffer));
404 this->previous_buffer = buffered_count;
405
406 long long dst_frame_rate = 0;
407 // Calculate the average
408 uint32_t average_buffer_depth = (uint32_t)(this->average_buffer_samples.avg());
409
410 int resample_hz = dst_frame_rate = (bmdAudioSampleRate48kHz) * BASE;
411
412 // Check to see if our buffered samples has enough to calculate a good average
413 if (this->average_buffer_samples.filled()) {
414 // @todo might be worth trying to make this more dynamic so that certain input values
415 // for different cards can be applied
416 // Check to see if we have a target amount of the buffer we'd like to fill
417 if(this->pos_jitter == 0) {
419 }
420 if(this->neg_jitter == 0) {
422 }
423
424 // Check whether there needs to be any resampling
425 if (average_buffer_depth > target_buffer_fill + this->pos_jitter)
426 {
427 // The buffer is too large, so we need to resample down to remove some frames
428 resample_hz = (int)this->scale_buffer_delta(average_buffer_depth - target_buffer_fill - this->pos_jitter);
429 dst_frame_rate = (bmdAudioSampleRate48kHz - resample_hz) * BASE;
431 } else if(average_buffer_depth < target_buffer_fill - this->neg_jitter) {
432 // The buffer is too small, so we need to resample up to generate some additional frames
433 resample_hz = (int)this->scale_buffer_delta(target_buffer_fill - average_buffer_depth - this->neg_jitter);
434 dst_frame_rate = (bmdAudioSampleRate48kHz + resample_hz) * BASE;
436 } else {
437 dst_frame_rate = (bmdAudioSampleRate48kHz) * BASE;
438 }
439 }
440
441 LOG(LOG_LEVEL_DEBUG) << MOD_NAME << " UPDATE playing speed " << average_buffer_depth << " vs " << buffered_count << " " << average_delta.avg() << " average_velocity " << resample_hz << " resample_hz\n";
442
443 if (dst_frame_rate != 0 &&
444 dst_frame_rate != dst_frame_rate_set) {
445 auto *m = new msg_universal((std::string(MSG_UNIVERSAL_TAG_AUDIO_DECODER) + std::to_string(dst_frame_rate << ADEC_CH_RATE_SHIFT | BASE)).c_str());
447 << MOD_NAME "Sending resample request "
448 << dst_frame_rate << "/" << BASE << "\n";
449 assert(m_root != nullptr);
450 auto *response = send_message_sync(m_root, "audio.receiver.decoder", reinterpret_cast<message *>(m), 100, SEND_MESSAGE_FLAG_NO_STORE);
452 LOG(LOG_LEVEL_WARNING) << MOD_NAME "Unable to send resample message: " << response_get_text(response) << " (" << response_get_status(response) << ")\n";
453 } else {
454 dst_frame_rate_set = dst_frame_rate;
455 }
457 }
458
459
460 if (sampleFramesWritten != sample_frame_count) {
462 }
467 }
468
469private:
470 bool m_enabled = false;
471
472 static constexpr unsigned long BASE = (1U<<8U);
473 struct module *m_root = nullptr;
474
477
479 uint32_t previous_buffer = 0;
480
481 // The min and max Hz changes we can resample between
484 // The min and max values to scale between
485 uint32_t min_buffer = 100;
486 uint32_t max_buffer = 600;
487 // Calculate the jitter so that we're within an acceptable range
488 uint32_t pos_jitter = 5;
489 uint32_t neg_jitter = 5;
490 // Currently unused but might form a part of a more dynamic
491 // solution to finding good jitter values in the future. @todo
492 [[maybe_unused]] uint32_t max_avg = 3650;
493 [[maybe_unused]] uint32_t min_avg = 1800;
494
495 // Store a audio_summary of resampling
497
498 static const uint32_t POS_JITTER_DEFAULT = 600;
499 static const uint32_t NEG_JITTER_DEFAULT = 600;
500
501 long long dst_frame_rate_set = 0;
502};
503
504#endif // defined SRC_VIDEO_DISPLAY_DECKLINK_DRIFT_FIX_HPP_69DBC8A9_974D_46C5_833D_5A77CF35E034
int void * val
#define MOD_NAME
Definition audio.cpp:110
#define ADEC_CH_RATE_SHIFT
#define MSG_UNIVERSAL_TAG_AUDIO_DECODER
double get_buffer_avg()
Get the average sample count per channel.
void set_target_buffer(uint32_t target_buffer)
Set the target buffer object.
static const uint32_t NEG_JITTER_DEFAULT
static const uint32_t POS_JITTER_DEFAULT
struct module * m_root
void set_max_hz(uint32_t max_hz)
Set the max hz object.
static constexpr unsigned long BASE
MovingAverage average_buffer_samples
DecklinkAudioSummary audio_summary
void update(uint32_t buffered_count, uint32_t sample_frame_count, uint32_t sampleFramesWritten)
double scale_buffer_delta(int delta)
This function will check the buffer delta and will return a delta in the sample rate that is required...
void set_min_hz(uint32_t min_hz)
Set the min hz object.
void set_root(module *root)
Set the root object.
MovingAverage average_delta
double getTotal() const
void add(double val)
double avg() const
void inc(double *&p)
ptrdiff_t size() const
MovingAverage(unsigned int period)
#define SBOLD(x)
Definition color_out.h:80
#define SUNDERLINE(x)
Definition color_out.h:81
static __device__ vec2 abs(const vec2 &v)
Definition cuda_dxt.cu:118
static __device__ vec3 round(const vec3 &v)
Definition cuda_dxt.cu:122
#define LOG_LEVEL_INFO
normal reporting
Definition debug.h:62
#define LOG(level)
Definition debug.h:313
#define LOG_LEVEL_WARNING
less severe errors
Definition debug.h:60
int count
Definition hd-rum.c:44
std::unordered_map< std::string, std::string > commandline_params
Definition host.cpp:184
#define LOG_LEVEL_DEBUG
Definition kernels.cu:48
struct response * send_message_sync(struct module *root, const char *const_path, struct message *msg, int timeout_ms, int flags)
Sends message without waiting for response.
int response_get_status(struct response *r)
void free_response(struct response *r)
const char * response_get_text(struct response *r)
#define RESPONSE_SUCCESSFUL(code)
Definition messaging.h:66
#define SEND_MESSAGE_FLAG_NO_STORE
if receiver doesn't exist, doesn't store it and retur 404 instead
Definition messaging.h:172