Skip to content

Smooth DVR playback: match display refresh to the file (true 90fps at 720p90) and stop quantizing recorder timestamps#618

Open
bob9 wants to merge 2 commits into
hd-zero:mainfrom
bob9:dvr-smooth-playback
Open

Smooth DVR playback: match display refresh to the file (true 90fps at 720p90) and stop quantizing recorder timestamps#618
bob9 wants to merge 2 commits into
hd-zero:mainfrom
bob9:dvr-smooth-playback

Conversation

@bob9

@bob9 bob9 commented Jul 16, 2026

Copy link
Copy Markdown

Hi HDZero team — thanks for all the work on this firmware, and for considering this PR. It tries to improve on-goggle DVR playback smoothness, which a number of racers (myself included) have noticed, particularly with 90fps recordings. Happy to adjust anything here to fit the project's direction — feedback very welcome.

Problem

On-goggle DVR playback can drop frames noticeably. As far as I can tell there are two independent causes:

  1. Playback runs at the menu's 50Hz panel timing. The playback page lives in UI mode, where Display_UI_init() drives the panel at 1080p50. A 60fps recording loses 10 frames/second in an uneven 6:5 cadence; a 90fps race recording loses 40 frames/second irregularly. The frames decode fine — they just have nowhere to go at 50Hz.

  2. Recorded timestamps are quantized. ffpack writes the video stream with a 1/fps time base, which rounds every capture timestamp to a whole frame slot (~±8ms). That judder is baked into the file and shows on any player, including PCs. I suspect (though can't be certain) this also explains the existing note in dvr.c"If set fps to 60, DVR is wrong. I don't know why. 59 or 61 is ok." — with a 1/60 tick and real ~60fps input, jittered frames can occasionally round to identical PTS, which the muxer rejects; fps=59 happens to avoid the collisions.

Proposed fix

Playback — the player reads the file's frame rate from the demuxer (mFrameRate) and retimes the UI output for the duration of playback via a new Display_UI_SetRefresh(hz):

  • 60fps files → 1080p60. Same 148.5MHz pixel clock as the 1080p50 menu, so only the vdpo timing changes.
  • 90fps files → 720p90, showing every frame. This reuses the vdpo timing, vclk/pclk phases and OLED mode already used by the live 720p90 path — the FPGA simply stays on the UI input — and the VO layer is sized 1280x720 to fill it.
  • Exit (or a failed open) restores stock menu timing with a full Display_UI_init(). Menu browsing stays at 1080p throughout; only active playback retimes.
  • Unknown frame rate → no retime, current behaviour is preserved.

The retime decision lives in media_instantiate() because the VO rect needs to be sized before the decoder pipeline is prepared.

Recording — the muxed video stream now uses the standard 90kHz time base instead of 1/fps, so capture timestamps are written exactly. MPEG-TS is 90kHz internally anyway, and for MP4 it's the most common track timescale. avg_frame_rate metadata is unchanged, and the existing fps=59 setting is left untouched. With ~1500 ticks between consecutive frames, the PTS-collision scenario described above can no longer occur, and new recordings also play noticeably smoother off-goggle (PC players, editors, YouTube).

Targets

  • HDZGOGGLE / HDZGOGGLE2: full support as above.
  • HDZBOXPRO: Display_UI_SetRefresh is a no-op — its UI already runs 720p60, which suits 60fps files and gives 90fps files an even 3:2 cadence.
  • Emulator build unaffected.

Testing

  • Full firmware image builds cleanly for HDZGOGGLE (cross-compiled, packaging verified); touched files compile warning-free for HDZGOGGLE2 and HDZBOXPRO.
  • On-hardware validation on HDZGOGGLE is in progress; I'll report results here as they come in.

Thanks again for taking a look!

bob9 added 2 commits July 16, 2026 13:02
DVR playback on the goggles dropped frames badly for two reasons:

- The menu UI drives the panel at 1080p50, so 60fps DVR files lost 10
  frames a second in an uneven 6:5 cadence and 90fps race files lost 40.
  The player now reads the file's frame rate from the demuxer and
  retimes the UI output to 1080p60 for the duration of playback (same
  148.5MHz pixel clock, so OLED/FPGA settings stay valid), restoring
  1080p50 on exit. 90fps files fall into an even 3:2 pulldown, the best
  the panel path can do.

- The recorder muxed with a 1/fps stream time base, rounding every
  capture timestamp to a whole frame slot and baking judder into the
  file (the reason behind the "fps=59 or DVR is wrong" workaround).
  Streams are now muxed on the standard 90kHz clock so timestamps stay
  exact; this also makes recordings play smoother on PCs.
Follow-up to the playback smoothing fix: instead of playing 90fps race
DVR at 60Hz with a 3:2 pulldown, the player now retimes the output to
720p90 while the file plays, showing every frame.

The menu stays at 1080p for browsing; starting a 90fps file switches
the vdpo output to 720p90 reusing the timing, clock phases and OLED
mode already proven by the live 720p90 path, and the video surface is
sized 1280x720 to fill it. Exiting the player restores the stock menu
timing with a full Display_UI_init. 60fps files keep the lighter
1080p60 retime on the same pixel clock.

The retime decision moved from ui_player into the media engine, since
the video output rect has to be sized before the decoder pipeline is
prepared; failure paths restore the menu timing too.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant