arduino-audio-tools
Loading...
Searching...
No Matches
PacedVideoOutput.h
Go to the documentation of this file.
1#pragma once
2#include <atomic>
3#include <string.h>
10
11namespace audio_tools {
12
59 public:
69 PacedVideoOutput(VideoOutput& target, float fps = 0,
70 uint32_t schedulingDelayMs = 0)
71 : p_target(&target), scheduling_delay_ms(schedulingDelayMs) {
72 setFps(fps);
73 }
74
77 void setFps(float fps) { frame_period_ms = fps > 0 ? 1000.0f / fps : 0; }
78
85 void setAudioClock(TimeSource& clock) { p_clock = &clock; }
86
96 void setSchedulingDelayMs(uint32_t delayMs) { scheduling_delay_ms = delayMs; }
97
105 void setCatchUpThresholdFrames(float frames) {
107 }
108
118 void setIgnorePFrames(bool active) { ignore_p_frames = active; }
119 bool ignorePFrames() const { return ignore_p_frames; }
120
129 void setResyncThresholdMs(uint32_t ms) { resync_threshold_ms = ms; }
130
139 void setResyncQueueFillFraction(float fraction) {
141 }
142
150 void setMaxQueuedIFrames(int count) { max_queued_i_frames = count; }
151
154 void setTaskParameters(uint32_t stackSizeWords, uint8_t priority,
155 int core = -1) {
156 task_stack_size = stackSizeWords;
157 task_priority = priority;
158 task_core = core;
159 }
160
169 void setQueueBytes(size_t bytes) { queue_bytes = bytes > 0 ? bytes : 1; }
170
182 void setQueueUsePSRAM(bool flag) { queue_use_psram = flag; }
183
189 bool begin() {
190 frame_index = 0;
191 frame_count = 0;
196 logged_drop_burst = false;
197 awaiting_keyframe = false;
199 i_frame_count = 0;
200 p_frame_count = 0;
203 start_set = false;
204 input_start_set = false;
205 output_start_set = false;
206 have_header = false;
212 } else {
213 queue.reset();
214 }
215 task.create("PacedVideoOutput", task_stack_size, task_priority,
216 task_core);
217 bool ok = task.begin([this]() { taskLoop(); });
219 LOGI(
220 "PacedVideoOutput: %s (fps=%.2f, clock=%s, scheduling delay=%u ms, "
221 "queue=%u bytes, stack=%u words, priority=%u)",
222 ok ? "render task started" : "render task failed to start",
223 frame_period_ms > 0 ? 1000.0f / frame_period_ms : 0.0f,
224 p_clock != nullptr ? "external audio" : "wall",
225 (unsigned)scheduling_delay_ms, (unsigned)queue.size(),
226 (unsigned)task_stack_size, (unsigned)task_priority);
227 return ok;
228 }
229
231 void end() {
232 task.end();
233 task_started = false;
234 LOGI(
235 "PacedVideoOutput: render task stopped (%u frames rendered - %u "
236 "I / %u P, %u P dropped / %u I dropped, input=%.2f fps, "
237 "output=%.2f fps)",
238 (unsigned)frame_count, (unsigned)i_frame_count.load(),
239 (unsigned)p_frame_count.load(), (unsigned)dropped_frame_count,
241 }
242
263 size_t write(const uint8_t* data, size_t len) override {
264 if (len == 0) return 0;
265 if (!task_started) begin();
266 if (!input_start_set) {
268 input_start_set = true;
269 }
270 uint32_t this_frame = frame_index;
271 uint32_t target_ms = (uint32_t)((double)frame_index * frame_period_ms);
272 frame_index++;
273 bool is_key = isKeyFrame(data, len);
274
275 // See setIgnorePFrames() - unconditional, independent of queue/
276 // lateness state, and checked before the header/queue-space bookkeeping
277 // below since a dropped-here frame never touches any of that. Counts
278 // into droppedFrameCount() too (same externally visible effect as any
279 // other dropped P-frame - never rendered), with ignoredFrameCount()
280 // as the more specific breakdown of how many were dropped for this
281 // reason rather than backlog.
282 if (ignore_p_frames && !is_key) {
285 frame_count++;
286 return len;
287 }
288
289 FrameHeader header;
290 header.size = (uint32_t)len;
291 header.target_ms = target_ms;
292 header.is_key = is_key ? 1 : 0;
293 size_t needed = sizeof(header) + len;
294
295 // decode+render isn't keeping up right now. A non-keyframe is
296 // dropped outright instead of enqueued (see droppedFrameCount()) to
297 // catch back up without blocking this call, which would otherwise
298 // stall the caller's own thread (typically a demuxer's dispatch
299 // loop, delaying audio too). Two triggers: the queue is actually
300 // full, or the render task's most recently rendered frame was
301 // already more than setCatchUpThresholdFrames() frame periods late.
302 // Keyframes are never dropped here (losing one would desync every
303 // dependent P-frame) - if the queue can't fit one, this still blocks
304 // below.
305 bool queue_full = (size_t)queue.availableForWrite() < needed;
306 bool falling_behind =
307 frame_period_ms > 0 &&
308 render_lateness_ms.load() >
310 if (!is_key && (queue_full || falling_behind)) {
312 frame_count++;
313 // Rate-limited to one line per unbroken run of drops - this can
314 // trigger on every P-frame in a row for a long stretch, and
315 // logging each one individually is expensive enough (Serial I/O on
316 // this thread) to measurably worsen the backlog it's trying to
317 // fix.
318 if (!logged_drop_burst) {
319 LOGW(
320 "PacedVideoOutput: dropping P frames starting at #%u - %s "
321 "(%d/%u bytes free, need %u, render %d ms behind)",
322 (unsigned)this_frame,
323 queue_full ? "render queue full" : "catching up",
324 queue.availableForWrite(), (unsigned)queue.size(),
325 (unsigned)needed, (int)render_lateness_ms.load());
326 logged_drop_burst = true;
327 }
328 return len;
329 }
330 logged_drop_burst = false;
331
332 bool logged_wait = false;
333 while ((size_t)queue.availableForWrite() < needed) {
334 if (!logged_wait) {
335 LOGW(
336 "PacedVideoOutput: write() blocked on frame #%u - render "
337 "queue full (%d/%u bytes free, need %u)",
338 (unsigned)this_frame, queue.availableForWrite(),
339 (unsigned)queue.size(), (unsigned)needed);
340 logged_wait = true;
341 }
342 delay(1);
343 }
344 queue.writeArray((const uint8_t*)&header, sizeof(header));
345 queue.writeArray(data, len);
346 if (is_key) queued_i_frame_count++;
347 LOGD(
348 "PacedVideoOutput: queued frame #%u (%s, %u bytes, target=%u ms, "
349 "%d bytes free)",
350 (unsigned)this_frame, is_key ? "I" : "P", (unsigned)len,
351 (unsigned)target_ms, queue.availableForWrite());
352 frame_count++;
353 return len;
354 }
355
359 void flush() override {}
360
361 void setSkipRender(bool skip) override { p_target->setSkipRender(skip); }
362
366 uint32_t frameCount() const { return frame_count; }
367
376 uint32_t droppedFrameCount() const { return dropped_frame_count; }
377
381 uint32_t ignoredFrameCount() const { return ignored_frame_count; }
382
387 uint32_t droppedIFrameCount() const { return dropped_i_frame_count; }
388
392 int queuedIFrameCount() const { return queued_i_frame_count.load(); }
393
398 size_t queuedBytes() { return (size_t)queue.available(); }
399
403 size_t queueCapacityBytes() { return queue.size(); }
404
407 uint32_t frameCountI() const { return i_frame_count; }
409 uint32_t frameCountP() const { return p_frame_count; }
410
413 float avgIFrameMs() const {
414 uint32_t n = i_frame_count;
415 return n > 0 ? (float)i_frame_total_ms.load() / n : 0.0f;
416 }
419 float avgPFrameMs() const {
420 uint32_t n = p_frame_count;
421 return n > 0 ? (float)p_frame_total_ms.load() / n : 0.0f;
422 }
425 float avgFrameMs() const {
426 uint32_t n = i_frame_count + p_frame_count;
427 return n > 0 ? (float)(i_frame_total_ms + p_frame_total_ms) / n : 0.0f;
428 }
429
436 float inputFPS() {
437 if (!input_start_set) return 0.0f;
438 uint32_t elapsed = clockMs() - input_start_ms;
439 return elapsed > 0 ? (1000.0f * frame_count) / elapsed : 0.0f;
440 }
441
449 float outputFPS() {
450 if (!output_start_set) return 0.0f;
451 uint32_t elapsed = millis() - output_start_ms.load();
452 uint32_t count = i_frame_count + p_frame_count;
453 return elapsed > 0 ? (1000.0f * count) / elapsed : 0.0f;
454 }
455
460 void logTo(Print& out) {
461 char buf[160];
462 StrView str(buf, sizeof(buf) - 1);
463
464 out.println(str.printf("input fps: %.2f / output fps: %.2f", inputFPS(),
465 outputFPS()));
466
467 out.println(str.printf("avg render ms - I: %.2f / P: %.2f / overall: %.2f",
469
470 size_t queueCapacity = queueCapacityBytes();
471 out.println(str.printf(
472 "frames - I: %d / P: %d / dropped P: %d / dropped I: %d / "
473 "queued I: %d / queue: %d/%d bytes (%.2f%% full)",
474 (int)frameCountI(), (int)frameCountP(), (int)droppedFrameCount(),
476 (int)queueCapacity,
477 queueCapacity > 0 ? 100.0f * queuedBytes() / queueCapacity : 0.0f));
478
479 // Splits avgFrameMs() (the whole target write()+flush() call) into its
480 // decode share vs everything after it (convert/render/SPI, ...) -
481 // tells us which half of the render budget is actually worth
482 // optimizing next. Only printed when the target overrides
483 // VideoOutput::totalDecodeMs() (currently H264Decoder) - 0 otherwise
484 // means "not tracked separately", not "instant decode".
485 uint64_t decodeMs = p_target->totalDecodeMs();
486 if (decodeMs > 0) {
487 uint32_t renderedFrames = i_frame_count + p_frame_count;
488 float avgDecodeMs =
489 renderedFrames > 0 ? (float)decodeMs / renderedFrames : 0.0f;
490 out.println(str.printf("avg decode ms: %.2f / avg convert+SPI ms: %.2f",
491 avgDecodeMs, avgFrameMs() - avgDecodeMs));
492 }
493#ifdef ESP32
494 size_t heapFree = ESP.getFreeHeap();
495 size_t heapTotal = ESP.getHeapSize();
496 size_t heapUsed = heapTotal - heapFree;
497
498 size_t psramFree = ESP.getFreePsram();
499 size_t psramTotal = ESP.getPsramSize();
500 size_t psramUsed = psramTotal - psramFree;
501
502 out.println(str.printf("Heap: total=%u, used=%u, free=%u bytes",
503 (unsigned)heapTotal, (unsigned)heapUsed,
504 (unsigned)heapFree));
505 out.println(str.printf("PSRAM: total=%u, used=%u, free=%u bytes",
506 (unsigned)psramTotal, (unsigned)psramUsed,
507 (unsigned)psramFree));
508
509 // Largest currently allocatable blocks - a low value here despite
510 // plenty of total free bytes above means the heap is fragmented
511 // (e.g. by the queue's own allocation), not actually out of memory.
512 out.println(str.printf(
513 "Largest heap block: %u bytes",
514 (unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL)));
515 out.println(str.printf(
516 "Largest PSRAM block: %u bytes",
517 (unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM)));
518#endif
519 }
520
521 protected:
523 TimeSource* p_clock = nullptr;
525 uint32_t scheduling_delay_ms = 0; // see setSchedulingDelayMs()
526 float catch_up_threshold_frames = 1.0f; // see setCatchUpThresholdFrames()
527 bool ignore_p_frames = false; // see setIgnorePFrames()
528 uint32_t resync_threshold_ms = 2000; // see setResyncThresholdMs()
529 float resync_queue_fill_fraction = 0.8f; // see setResyncQueueFillFraction()
530 int max_queued_i_frames = 4; // see setMaxQueuedIFrames()
531 uint64_t frame_index = 0;
532 uint32_t frame_count = 0;
533 uint32_t dropped_frame_count = 0; // see droppedFrameCount()
534 uint32_t dropped_i_frame_count = 0; // consumer-thread-only; see droppedIFrameCount()
535 uint32_t ignored_frame_count = 0; // write()-thread-only; see ignoredFrameCount()
536 // Keyframes currently queued, not yet consumed - incremented by
537 // write(), decremented by taskLoop()/drainQueueKeepingLastKeyframe();
538 // atomic since producer and consumer both touch it. See
539 // queuedIFrameCount().
540 std::atomic<int32_t> queued_i_frame_count{0};
541 bool logged_drop_burst = false; // write()-thread-only; see write()
542 uint32_t start_ms = 0;
543 bool start_set = false;
544 bool task_started = false;
545 // True from the moment a resync fires until a real keyframe renders -
546 // consumer-thread-only. A resync can leave the decoder's
547 // reference-frame state unreliable, so P-frames are discarded until a
548 // self-contained keyframe resets it.
549 bool awaiting_keyframe = false;
550 // How many ms late (or early) the most recently rendered frame was -
551 // written by taskLoop(), read by write() (its proactive drop check),
552 // hence atomic. A heuristic: stale by one frame's processing time by
553 // the time write() reads it, same as output_start_ms below.
554 std::atomic<int32_t> render_lateness_ms{0};
555
559 struct FrameHeader {
560 uint32_t size = 0;
561 uint32_t target_ms = 0;
562 uint8_t is_key = 0;
563 };
569 size_t queue_bytes = 32 * 1024; // desired capacity - see setQueueBytes()
570 size_t queue_bytes_allocated = 0; // what 'queue' was last resize()d to
571 bool queue_use_psram = true; // see setQueueUsePSRAM()
572
573 // Consumer-side-only framing state (see taskLoop()): a header already
574 // pulled out of 'queue' whose payload isn't fully written yet. Never
575 // touched by write()/the caller thread.
576 bool have_header = false;
581
583 uint32_t task_stack_size = 4096;
584 uint8_t task_priority = 2;
585 int task_core = -1;
586
587 // Per-frame-type render-time stats - written only from taskLoop(),
588 // read via the getters above. Plain atomic (single writer; not for
589 // contended updates) - the only shared state left outside 'queue'
590 // itself.
591 std::atomic<uint32_t> i_frame_count{0};
592 std::atomic<uint32_t> p_frame_count{0};
593 std::atomic<uint64_t> i_frame_total_ms{0};
594 std::atomic<uint64_t> p_frame_total_ms{0};
595
596 // inputFPS()/outputFPS() anchors, set once and never moved - averaging
597 // runs "since the very start" up to now. Deliberately different
598 // clocks: input_start_ms is clockMs() (see inputFPS()),
599 // output_start_ms is always wall-clock millis() (see outputFPS()).
600 // input_* is caller-thread-only; output_* is written by taskLoop(),
601 // hence atomic.
602 uint32_t input_start_ms = 0;
603 bool input_start_set = false;
604 std::atomic<uint32_t> output_start_ms{0};
605 std::atomic<bool> output_start_set{false};
606
610 uint32_t clockMs() { return p_clock != nullptr ? p_clock->playbackTime() : millis(); }
611
617 bool isKeyFrame(const uint8_t* data, size_t len) {
618 return p_target->isKeyFrame(data, len);
619 }
620
623 static constexpr int kDrainYieldEvery = 32;
624
650 bool drainQueueKeepingLastKeyframe(uint32_t& out_target_ms) {
651 bool found_key = false;
652 int count = 0;
653 while (true) {
654 if (!have_header) {
655 if ((size_t)queue.available() < sizeof(FrameHeader)) break;
656 queue.readArray((uint8_t*)&current_header, sizeof(FrameHeader));
657 have_header = true;
658 }
659 if ((size_t)queue.available() < current_header.size) break;
660
662 if (found_key) dropped_i_frame_count++; // superseded by this one
665 out_target_ms = current_header.target_ms;
666 found_key = true;
668 } else {
669 uint8_t scratch[256];
670 size_t remaining = current_header.size;
671 while (remaining > 0) {
672 size_t chunk = remaining < sizeof(scratch) ? remaining : sizeof(scratch);
673 size_t got = (size_t)queue.readArray(scratch, chunk);
674 if (got == 0) break; // shouldn't happen; avoids ever spinning forever
675 remaining -= got;
676 }
678 }
679 have_header = false;
680 if (++count % kDrainYieldEvery == 0) delay(1);
681 }
682 return found_key;
683 }
684
690 void taskLoop() {
691 if (!have_header) {
692 if ((size_t)queue.available() < sizeof(FrameHeader)) {
693 delay(1);
694 return;
695 }
696 queue.readArray((uint8_t*)&current_header, sizeof(FrameHeader));
697 have_header = true;
698 }
699 // The header may already be visible before its payload fully is -
700 // write() writes them as two separate writeArray() calls - so just
701 // wait for the rest; have_header stays true across calls in the
702 // meantime.
703 if ((size_t)queue.available() < current_header.size) {
704 delay(1);
705 return;
706 }
709 uint32_t target_ms = current_header.target_ms;
710 bool is_key = current_header.is_key != 0;
711 have_header = false;
712 // Leaving the queue for rendering (not a discard - see
713 // drainQueueKeepingLastKeyframe() for that side of this same counter).
714 if (is_key) queued_i_frame_count--;
715
716 // Still waiting for a keyframe after a previous resync - skip
717 // straight past any P-frame without considering its schedule,
718 // reaching the next keyframe as fast as the queue allows.
719 if (awaiting_keyframe && !is_key) {
721 frame_count++;
722 return;
723 }
724
725 if (!start_set) {
726 start_ms = clockMs();
727 start_set = true;
728 LOGI("PacedVideoOutput: playback anchored at %u ms (%s clock)",
729 (unsigned)start_ms, p_clock != nullptr ? "external audio" : "wall");
730 }
731 // scheduling_delay_ms (see setSchedulingDelayMs()) only ever pushes
732 // this later, correcting for the audio output's own buffering
733 // latency between "accepted by write()" and "actually audible".
734 uint32_t scheduled_ms = start_ms + target_ms + scheduling_delay_ms;
735 while ((int32_t)(scheduled_ms - clockMs()) > 0) {
736 delay(1);
737 }
738 // Captured right as the wait loop exits, before write()/flush() - a
739 // measure of how far behind schedule this frame already was when we
740 // started rendering it, separate from processMs (how long rendering
741 // itself then took).
742 int32_t lateness_ms = (int32_t)(clockMs() - scheduled_ms);
743 // Dropping non-keyframes alone can only prevent the backlog from
744 // growing, not shrink it - the producer can't deliver frames faster
745 // than real time. Past resync_threshold_ms, give up on the skipped
746 // content and jump the schedule anchor forward, making *this* frame
747 // "on time" - a visible jump instead of an ever-growing lag.
748 //
749 // Three independent triggers, since they can diverge: lateness_ms
750 // only reflects the frame we're about to render - dropped P-frames
751 // can keep that low even while newer bytes keep piling up in the
752 // queue, since overall render throughput can still trail the arrival
753 // rate. queue fill catches that directly, but only once
754 // substantially full; queued keyframe count (setMaxQueuedIFrames())
755 // catches it earlier still.
756 bool lateness_resync =
757 resync_threshold_ms > 0 && lateness_ms > (int32_t)resync_threshold_ms;
758 bool queue_resync =
760 (float)queue.available() >=
762 bool iframe_resync = max_queued_i_frames > 0 &&
764 if (lateness_resync || queue_resync || iframe_resync) {
765 // Keep the freshest keyframe found in the backlog instead of
766 // discarding it too (see drainQueueKeepingLastKeyframe()).
767 uint32_t new_target_ms = target_ms;
768 bool found_fresher_key = drainQueueKeepingLastKeyframe(new_target_ms);
769 LOGW(
770 "PacedVideoOutput: resyncing (%s) - %s instead of trying to "
771 "catch up",
772 lateness_resync
773 ? "lateness"
774 : (queue_resync ? "queue fill" : "I-frame backlog"),
775 found_fresher_key ? "jumping to the newest available keyframe"
776 : "jumping the schedule forward");
777 if (found_fresher_key) {
778 // Supersede whatever was already dequeued.
779 if (is_key) {
781 } else {
783 }
784 target_ms = new_target_ms;
785 is_key = true;
786 awaiting_keyframe = false;
787 } else {
788 // A resync can leave the decoder's reference state unreliable,
789 // so require a real (self-contained) keyframe before resuming -
790 // discard any P-frame in between (see the check below) - unless
791 // the frame we already have is one.
792 awaiting_keyframe = true;
793 }
794 // Recomputed for whichever frame we're actually about to render.
795 start_ms = clockMs() - target_ms - scheduling_delay_ms;
796 scheduled_ms = start_ms + target_ms + scheduling_delay_ms; // == clockMs() now
797 lateness_ms = 0;
798 }
799 if (awaiting_keyframe) {
800 if (!is_key) {
802 frame_count++;
804 return;
805 }
806 awaiting_keyframe = false;
807 }
808 // Published for write()'s proactive drop check (see
809 // setCatchUpThresholdFrames()) before doing the possibly-slow render
810 // call, so a caller blocked in write() sees it as soon as possible
811 // rather than only after this frame finishes too.
812 render_lateness_ms = lateness_ms;
813 uint32_t processStart = millis();
815 p_target->flush();
816 uint32_t processMs = millis() - processStart;
817 // See VideoOutput::hadOutput() - true for every synchronous decoder
818 // (the common case), but a decoder like MPGDecoder can legitimately
819 // do real decode work here without a picture actually reaching the
820 // screen yet (held back for B-picture display-order reordering), or
821 // emit an earlier held picture instead. Only count/time this call as
822 // a rendered frame when a picture was actually produced - otherwise
823 // outputFPS()/frameCountI()/frameCountP()/avgFrameMs() would
824 // overcount calls that did no real rendering work.
825 if (p_target->hadOutput()) {
826 if (!output_start_set) {
828 output_start_set = true;
829 }
830 if (is_key) {
832 i_frame_total_ms += processMs;
833 } else {
835 p_frame_total_ms += processMs;
836 }
837 LOGD(
838 "PacedVideoOutput: rendered %s frame - scheduled=%u actual=%u "
839 "(%d ms late), process=%u ms",
840 is_key ? "I" : "P", (unsigned)scheduled_ms, (unsigned)clockMs(),
841 (int)lateness_ms, (unsigned)processMs);
842 } else {
843 LOGD(
844 "PacedVideoOutput: decoded %s frame, no picture emitted yet "
845 "(scheduled=%u actual=%u, %d ms late), process=%u ms",
846 is_key ? "I" : "P", (unsigned)scheduled_ms, (unsigned)clockMs(),
847 (int)lateness_ms, (unsigned)processMs);
848 }
849 if (frame_period_ms > 0 && processMs > (uint32_t)frame_period_ms) {
850 LOGW(
851 "PacedVideoOutput: %s frame took %u ms to render - longer than "
852 "the %.1f ms frame period, falling behind",
853 is_key ? "I" : "P", (unsigned)processMs, frame_period_ms);
854 }
855 }
856};
857
858} // namespace audio_tools
#define LOGW(...)
Definition AudioLoggerIDF.h:29
#define LOGI(...)
Definition AudioLoggerIDF.h:28
#define LOGD(...)
Definition AudioLoggerIDF.h:27
Definition Arduino.h:56
Buffers a small, configurable amount of video (see setQueueBytes()) and renders it frame by frame fro...
Definition PacedVideoOutput.h:58
float avgFrameMs() const
Definition PacedVideoOutput.h:425
void flush() override
Definition PacedVideoOutput.h:359
size_t queue_bytes
Definition PacedVideoOutput.h:569
uint32_t input_start_ms
Definition PacedVideoOutput.h:602
PacedVideoOutput(VideoOutput &target, float fps=0, uint32_t schedulingDelayMs=0)
Definition PacedVideoOutput.h:69
std::atomic< int32_t > render_lateness_ms
Definition PacedVideoOutput.h:554
Vector< uint8_t > frame_buf
Definition PacedVideoOutput.h:580
std::atomic< uint64_t > i_frame_total_ms
Definition PacedVideoOutput.h:593
float resync_queue_fill_fraction
Definition PacedVideoOutput.h:529
float catch_up_threshold_frames
Definition PacedVideoOutput.h:526
bool have_header
Definition PacedVideoOutput.h:576
int task_core
Definition PacedVideoOutput.h:585
RingBufferSPSC< uint8_t > queue
Definition PacedVideoOutput.h:568
bool logged_drop_burst
Definition PacedVideoOutput.h:541
uint32_t droppedIFrameCount() const
Definition PacedVideoOutput.h:387
std::atomic< uint64_t > p_frame_total_ms
Definition PacedVideoOutput.h:594
uint32_t scheduling_delay_ms
Definition PacedVideoOutput.h:525
bool ignore_p_frames
Definition PacedVideoOutput.h:527
void setResyncThresholdMs(uint32_t ms)
Definition PacedVideoOutput.h:129
uint32_t resync_threshold_ms
Definition PacedVideoOutput.h:528
std::atomic< int32_t > queued_i_frame_count
Definition PacedVideoOutput.h:540
bool input_start_set
Definition PacedVideoOutput.h:603
uint32_t start_ms
Definition PacedVideoOutput.h:542
float inputFPS()
Definition PacedVideoOutput.h:436
float avgIFrameMs() const
Definition PacedVideoOutput.h:413
void taskLoop()
Definition PacedVideoOutput.h:690
std::atomic< bool > output_start_set
Definition PacedVideoOutput.h:605
uint32_t frameCount() const
Definition PacedVideoOutput.h:366
bool begin()
Definition PacedVideoOutput.h:189
uint32_t frameCountI() const
Definition PacedVideoOutput.h:407
std::atomic< uint32_t > output_start_ms
Definition PacedVideoOutput.h:604
uint8_t task_priority
Definition PacedVideoOutput.h:584
uint32_t droppedFrameCount() const
Definition PacedVideoOutput.h:376
size_t queue_bytes_allocated
Definition PacedVideoOutput.h:570
size_t write(const uint8_t *data, size_t len) override
Definition PacedVideoOutput.h:263
size_t queueCapacityBytes()
Definition PacedVideoOutput.h:403
uint64_t frame_index
Definition PacedVideoOutput.h:531
TimeSource * p_clock
Definition PacedVideoOutput.h:523
void setMaxQueuedIFrames(int count)
Definition PacedVideoOutput.h:150
std::atomic< uint32_t > p_frame_count
Definition PacedVideoOutput.h:592
Task task
Definition PacedVideoOutput.h:582
bool awaiting_keyframe
Definition PacedVideoOutput.h:549
void setSkipRender(bool skip) override
Definition PacedVideoOutput.h:361
bool task_started
Definition PacedVideoOutput.h:544
bool drainQueueKeepingLastKeyframe(uint32_t &out_target_ms)
Definition PacedVideoOutput.h:650
bool ignorePFrames() const
Definition PacedVideoOutput.h:119
uint32_t frameCountP() const
Number of P-frames (non-key frames) actually rendered so far.
Definition PacedVideoOutput.h:409
VideoOutput * p_target
Definition PacedVideoOutput.h:522
uint32_t ignoredFrameCount() const
Definition PacedVideoOutput.h:381
float frame_period_ms
Definition PacedVideoOutput.h:524
bool isKeyFrame(const uint8_t *data, size_t len)
Definition PacedVideoOutput.h:617
bool start_set
Definition PacedVideoOutput.h:543
void setFps(float fps)
Definition PacedVideoOutput.h:77
void setSchedulingDelayMs(uint32_t delayMs)
Definition PacedVideoOutput.h:96
void end()
Stops the background render task.
Definition PacedVideoOutput.h:231
FrameHeader current_header
Definition PacedVideoOutput.h:577
size_t queuedBytes()
Definition PacedVideoOutput.h:398
int max_queued_i_frames
Definition PacedVideoOutput.h:530
void setQueueUsePSRAM(bool flag)
Definition PacedVideoOutput.h:182
float outputFPS()
Definition PacedVideoOutput.h:449
void setResyncQueueFillFraction(float fraction)
Definition PacedVideoOutput.h:139
void setCatchUpThresholdFrames(float frames)
Definition PacedVideoOutput.h:105
static constexpr int kDrainYieldEvery
Definition PacedVideoOutput.h:623
void logTo(Print &out)
Definition PacedVideoOutput.h:460
uint32_t dropped_i_frame_count
Definition PacedVideoOutput.h:534
float avgPFrameMs() const
Definition PacedVideoOutput.h:419
void setAudioClock(TimeSource &clock)
Definition PacedVideoOutput.h:85
uint32_t dropped_frame_count
Definition PacedVideoOutput.h:533
bool queue_use_psram
Definition PacedVideoOutput.h:571
int queuedIFrameCount() const
Definition PacedVideoOutput.h:392
uint32_t clockMs()
Definition PacedVideoOutput.h:610
uint32_t frame_count
Definition PacedVideoOutput.h:532
std::atomic< uint32_t > i_frame_count
Definition PacedVideoOutput.h:591
void setQueueBytes(size_t bytes)
Definition PacedVideoOutput.h:169
uint32_t task_stack_size
Definition PacedVideoOutput.h:583
void setTaskParameters(uint32_t stackSizeWords, uint8_t priority, int core=-1)
Definition PacedVideoOutput.h:154
void setIgnorePFrames(bool active)
Definition PacedVideoOutput.h:118
uint32_t ignored_frame_count
Definition PacedVideoOutput.h:535
Lock-free Single-Producer Single-Consumer ring buffer.
Definition RingBufferSPSC.h:52
size_t size() override
Definition RingBufferSPSC.h:190
int available() override
provides the number of entries that are available to read
Definition RingBufferSPSC.h:115
int availableForWrite() override
provides the number of entries that are available to write
Definition RingBufferSPSC.h:121
int writeArray(const T data[], int len) override
Fills the buffer data.
Definition RingBufferSPSC.h:73
bool resize(size_t capacity) override
Resizes the buffer if supported: returns false if not supported.
Definition RingBufferSPSC.h:144
void setUsePSRAM(bool flag)
Definition RingBufferSPSC.h:137
void reset() override
clears the buffer
Definition RingBufferSPSC.h:129
int readArray(T data[], int len) override
reads multiple values
Definition RingBufferSPSC.h:93
A simple wrapper to provide string functions on existing allocated char*. If the underlying char* is ...
Definition StrView.h:29
virtual const char * printf(const char *fmt,...)
Definition StrView.h:166
FreeRTOS task.
Definition Task.h:26
bool create(const char *name, int stackSizeWords, int priority=1, int core=-1)
Definition Task.h:54
void end()
suspends the task
Definition Task.h:105
bool begin(std::function< void()> process)
Definition Task.h:94
Interface for classes that can provide time information - two distinct notions of "now",...
Definition AudioTypes.h:585
virtual uint32_t playbackTime()
Definition AudioTypes.h:603
Vector implementation which provides the most important methods as defined by std::vector....
Definition Vector.h:21
void clear()
Definition Vector.h:176
bool resize(size_t newSize, T value)
Definition Vector.h:266
T * data()
Definition Vector.h:316
int size()
Definition Vector.h:178
Abstract class for video playback. This class is used to assemble a complete video frame in memory....
Definition VideoOutput.h:107
virtual size_t write(const uint8_t *data, size_t len)=0
virtual uint64_t totalDecodeMs() const
Definition VideoOutput.h:170
virtual bool hadOutput() const
Definition VideoOutput.h:152
virtual void setSkipRender(bool skip)
Definition VideoOutput.h:121
virtual void flush()
Definition VideoOutput.h:113
virtual bool isKeyFrame(const uint8_t *data, size_t len)
Definition VideoOutput.h:135
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition LMSEchoCancellationStream.h:6
void delay(uint32_t ms)
Definition Arduino.h:259
uint32_t millis()
Returns the milliseconds since the start.
Definition Arduino.h:260
Definition PacedVideoOutput.h:559
uint8_t is_key
Definition PacedVideoOutput.h:562
uint32_t size
Definition PacedVideoOutput.h:560
uint32_t target_ms
Definition PacedVideoOutput.h:561