|
arduino-audio-tools
|
Wraps a VideoOutput (typically a codec decoder, e.g. H264Decoder/ MJPEGDecoder/MPGDecoder or a MultiVideoDecoder) and measures how long decoding+outputting each frame actually takes - forwards every write()/ flush()/setSkipRender()/isKeyFrame() call unmodified to the wrapped target, but times write()+flush() together and classifies the result I- vs P-frame via the target's own isKeyFrame(), the same way PacedVideoOutput classifies frames for its own frameCountI()/ frameCountP() stats. More...
#include <OutputFPSMeter.h>
Public Member Functions | |
| OutputFPSMeter (VideoOutput &target) | |
| void | flush () override |
| uint32_t | frameCount () const |
| uint32_t | frameCountI () const |
| Number of those classified as keyframes (I-frames). | |
| uint32_t | frameCountP () const |
| Number of those classified as non-keyframes (P-frames). | |
| virtual uint32_t | getWriteTimeMs () const |
| Optional: returns the time (ms) spent in the last write() call. | |
| virtual bool | hadOutput () const |
| bool | isKeyFrame (const uint8_t *data, size_t len) override |
| void | logTo (Print &out) |
| void | setSkipRender (bool skip) override |
| virtual void | setVideoInfoSource (VideoInfoSource &source) |
| virtual uint64_t | totalDecodeMs () const |
| size_t | write (const uint8_t *data, size_t len) override |
Protected Attributes | |
| uint32_t | call_ms = 0 |
| uint32_t | frame_count = 0 |
| bool | have_pending = false |
| uint32_t | i_count = 0 |
| uint32_t | i_ms = 0 |
| uint32_t | last_ms = 0 |
| uint32_t | p_count = 0 |
| uint32_t | p_ms = 0 |
| VideoOutput * | p_target |
| bool | pending_is_key = false |
| uint32_t | start_ms = 0 |
| uint32_t | total_ms = 0 |
Wraps a VideoOutput (typically a codec decoder, e.g. H264Decoder/ MJPEGDecoder/MPGDecoder or a MultiVideoDecoder) and measures how long decoding+outputting each frame actually takes - forwards every write()/ flush()/setSkipRender()/isKeyFrame() call unmodified to the wrapped target, but times write()+flush() together and classifies the result I- vs P-frame via the target's own isKeyFrame(), the same way PacedVideoOutput classifies frames for its own frameCountI()/ frameCountP() stats.
Timed and finalized at flush() time, not write() - deliberately, per the VideoOutput contract's own "one or more write() calls, then finalized with flush()" pattern: H264Decoder/MPGDecoder do decode+ output entirely within write() (their own flush() is a no-op, so timing/tallying at flush() still captures the real cost correctly), but MJPEGDecoder only accumulates bytes in write() (a cheap memcpy) and does the actual decode+output on flush(). A version of this class that finalized at write() time (an earlier bug here) would time MJPEGDecoder's cheap accumulate call and never see its real decode cost at all - reporting a near-zero, meaningless "avg ms" and a wildly inflated frame count/fps, since VideoOutput::hadOutput() defaults true for every call regardless of whether real work happened.
Important: this measures the whole time inside the wrapped target's write()+flush() calls, not decode alone. Every current decoder (H264Decoder/MJPEGDecoder/MPGDecoder) pushes each decoded picture to its own configured output synchronously, from within write() or flush() (see above) - and every current VideoOutput display driver (e.g. OutputTinyGPU, whose TinyGPU bus drivers busy-wait for their DMA transfer to finish before returning) blocks until the pixels have actually reached the panel. So if the decoder you wrap here has a real display wired as its own output, the numbers below already include that display's render/SPI cost - they are NOT decode-only unless the decoder's own output is a no-op sink (see NullVideoOutput in sd-measure-fps.ino) instead of a real display.
Only tallies a write()+flush() pair where the target's hadOutput() is true after flush() (a decoder like MPGDecoder can legitimately swallow a call into B-picture reordering without emitting a picture that same call - see VideoOutput::hadOutput()'s own comment), so the stats reflect actually-decoded pictures, not raw access-unit count.
Drop this in front of a decoder with no pacing above it (i.e. no PacedVideoOutput - feed the demuxer's setOutputVideo() straight into a OutputFPSMeter wrapping the decoder) and drive it with plain CodecCopy to answer "how fast could this hardware actually play this content if nothing were pacing it" - exactly the ceiling PacedVideoOutput itself needs to stay under (see its own inputFPS()/outputFPS()/avgFrameMs() diagnostics, and the "Audio/Video Synchronization" wiki chapter) to avoid ever falling behind and dropping/resyncing during real playback. See sd-measure-fps.ino for the full pattern - it wires a real display as the default target (so the measurement reflects real achievable playback fps), with a no-op VideoOutput sink as a drop-in alternative to isolate pure codec throughput from display cost instead.
|
inline |
| target | every write()/flush() call is forwarded here, unmodified - its own isKeyFrame()/hadOutput() answers drive this meter's I/P classification and which write()+flush() pairs actually count. Must outlive this object. |
|
inlineoverridevirtual |
Finalizes the frame most recently written via one or more write() calls - see class comment. Default no-op for implementations that display/decode synchronously in write() instead.
Reimplemented from VideoOutput.
|
inline |
Total number of frames actually decoded so far - handy for a progress line while a long run is still going.
|
inline |
Number of those classified as keyframes (I-frames).
|
inline |
Number of those classified as non-keyframes (P-frames).
|
inlinevirtualinherited |
Optional: returns the time (ms) spent in the last write() call.
Reimplemented in OutputTFT_eSPI, OutputTinyGPU, and OutputOpenCV.
|
inlinevirtualinherited |
True if the most recent write()+flush() call actually produced a displayable picture - default true, matching every synchronous decoder (H264Decoder, MJPEGDecoder, ...), which always decodes and pushes pixels fully within that one call. Override this only if your decoder can legitimately accept/decode a frame's bytes without emitting a picture during that same call - e.g. MPGDecoder, whose B-picture display-order reordering can hold a just-decoded picture back and instead emit an earlier one (or nothing at all) from a given write(), see its own override. Used by PacedVideoOutput to avoid counting/timing a call that did no real rendering work as a rendered frame - without this, its outputFPS()/frameCountI()/ frameCountP()/avgFrameMs() would overcount for such a decoder.
Reimplemented in MPGDecoder.
|
inlineoverridevirtual |
True if data (one complete encoded frame, as handed to write()) is a keyframe/sync-sample - self-contained, decodable without any earlier frame. Used e.g. by PacedVideoOutput to decide which frames are safe to drop, and whether it's safe to resume decoding after abandoning a backlog (see its own class comment). Default false: a plain VideoOutput doesn't know or care about codec structure - override this in a decoder for the bitstream format it actually parses (see H264Decoder/H264DecoderESP32S3's isH264KeyFrame()-based override, MPGDecoder's isMpeg1KeyFrame()- based one). Getting this right matters beyond bookkeeping: a target whose frames are never recognized as keyframes can leave a caller like PacedVideoOutput unable to ever resume after a resync.
Reimplemented from VideoOutput.
|
inline |
Prints the measurement so far: overall fps actually achieved (frames decoded / wall-clock time spent inside the wrapped target's write()+flush() calls since the first one), plus the avg-ms/ implied-fps breakdown per frame type, mirroring PacedVideoOutput:: logTo()'s own style. Safe to call mid-run (e.g. for a periodic progress line) or once at the end.
Labeled "write ms", not "decode ms" - see this class's own comment: unless the wrapped target's own output is a no-op sink, these times already include that target's real render/display cost, not just decode.
|
inlineoverridevirtual |
Hint to skip the expensive part of displaying the next frame(s) (e.g. the panel refresh) while still accepting and fully processing write() calls - used to recover from falling behind the playback schedule without breaking a codec's decode state (e.g. H.264 inter-prediction reference chain, which requires every frame to still be decoded even if it's never shown). Default no-op: implementations that can't skip rendering cheaply just ignore it and always render.
Reimplemented from VideoOutput.
|
inlinevirtualinherited |
Optional: registers where width/height/fps/format (VideoInfo) come from - e.g. the demuxer feeding this output, so it can size its own buffers/panel setup without the caller having to duplicate that call per sketch (VideoPlayer::begin() does this automatically for whichever VideoOutput it was given). Default no-op: only implementations that actually need VideoInfo (OutputTinyGPU/ OutputOpenCV/OutputTFT_eSPI) override this.
Reimplemented in MultiVideoDecoder, OutputOpenCV, OutputTFT_eSPI, and OutputTinyGPU.
|
inlinevirtualinherited |
Optional: sum of time (ms) spent purely decoding (excluding any surrounding convert/render/SPI work a subclass's write() also does) since begin() - see H264Decoder's own override for the only current implementation. Default 0: only meaningful for a decoder that separates decode time from render time internally: PacedVideoOutput:: logTo() prints a decode-vs-render split under "avg decode ms:" only when this returns nonzero.
Reimplemented in H264Decoder, and MultiVideoDecoder.
|
inlineoverridevirtual |
Implements VideoOutput.
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |
|
protected |