diff --git a/src/driver/hardware-boxpro.c b/src/driver/hardware-boxpro.c index 3409a9f3b..384ea521f 100644 --- a/src/driver/hardware-boxpro.c +++ b/src/driver/hardware-boxpro.c @@ -606,6 +606,11 @@ void Display_UI() { pthread_mutex_unlock(&hardware_mutex); } +// The BoxPRO UI already runs at 720p60, which suits 60 and 90 fps DVR files, +// so there is nothing to retime here. +void Display_UI_SetRefresh(int hz) { +} + void HDZero_open(int bw) { if (bw != g_hw_stat.hdz_bw) // reopen with different bw HDZero_Close(); diff --git a/src/driver/hardware-goggle.c b/src/driver/hardware-goggle.c index 585092fcd..6396c34b1 100644 --- a/src/driver/hardware-goggle.c +++ b/src/driver/hardware-goggle.c @@ -596,6 +596,41 @@ void Display_UI() { pthread_mutex_unlock(&hardware_mutex); } +// Retime the UI output for DVR playback. 60Hz keeps the 1080p UI on the +// same 148.5MHz pixel clock, so the OLED and FPGA settings from +// Display_UI_init stay valid. 90Hz reuses the vdpo timing, clock phases and +// OLED mode proven by the live 720p90 path (the FPGA stays on the UI input). +// 0 restores the stock menu timing with a full Display_UI_init. +void Display_UI_SetRefresh(int hz) { + int tmg = (hz == 90) ? VDPO_TMG_720P90 : (hz == 60) ? VDPO_TMG_1080P60 : VDPO_TMG_1080P50; + + pthread_mutex_lock(&hardware_mutex); + if ((g_hw_stat.source_mode == SOURCE_MODE_UI) && (g_hw_stat.vdpo_tmg != tmg)) { + screen.display(0); + + if (hz == 90) { + system_exec("dispw -s vdpo 720p90"); + g_hw_stat.vdpo_tmg = VDPO_TMG_720P90; + vclk_phase_set(VIDEO_SOURCE_HDZERO_IN_720P90, 0); + pclk_phase_set(VIDEO_SOURCE_HDZERO_IN_720P90); + screen.vtmg(1); + system_exec("aww 0x0300b084 0x00001565"); // Set vdpo clock driver strength to level 2. Refer datasheet 12.7.5.11 + system_exec("aww 0x06542018 0x00000044"); // disable horizontal chroma FIR filter. + } else if (hz == 60) { + system_exec("dispw -s vdpo 1080p60"); + g_hw_stat.vdpo_tmg = VDPO_TMG_1080P60; + system_exec("aww 0x0300b084 0x00001565"); // Set vdpo clock driver strength to level 2. Refer datasheet 12.7.5.11 + system_exec("aww 0x06542018 0x00000044"); // disable horizontal chroma FIR filter. + } else { + Display_UI_init(); + } + + screen.display(1); + LOGI("Display_UI_SetRefresh: %dHz", hz ? hz : 50); + } + pthread_mutex_unlock(&hardware_mutex); +} + void Display_720P60_50_t(int mode, uint8_t is_43) // fps: 0=50, 1=60 { screen.display(0); diff --git a/src/driver/hardware-goggle2.c b/src/driver/hardware-goggle2.c index 9a80ed116..fe8beb07a 100755 --- a/src/driver/hardware-goggle2.c +++ b/src/driver/hardware-goggle2.c @@ -569,6 +569,43 @@ void Display_UI() { pthread_mutex_unlock(&hardware_mutex); } +// Retime the UI output for DVR playback. 60Hz keeps the 1080p UI on the +// same 148.5MHz pixel clock, so the OLED and FPGA settings from +// Display_UI_init stay valid. 90Hz reuses the vdpo timing, clock phases and +// OLED mode proven by the live 720p90 path (the FPGA stays on the UI input). +// 0 restores the stock menu timing with a full Display_UI_init. +void Display_UI_SetRefresh(int hz) { + int tmg = (hz == 90) ? VDPO_TMG_720P90 : (hz == 60) ? VDPO_TMG_1080P60 : VDPO_TMG_1080P50; + + pthread_mutex_lock(&hardware_mutex); + if ((g_hw_stat.source_mode == SOURCE_MODE_UI) && (g_hw_stat.vdpo_tmg != tmg)) { + screen.display(0); + + if (hz == 90) { + system_exec("dispw -s vdpo 720p90"); + g_hw_stat.vdpo_tmg = VDPO_TMG_720P90; + system_exec("aww 0x0300b340 0x00000008"); + vclk_phase_set(VIDEO_SOURCE_HDZERO_IN_720P90, 0); + pclk_phase_set(VIDEO_SOURCE_HDZERO_IN_720P90); + screen.vtmg(1); + system_exec("aww 0x0300b084 0x00003fff"); // Set vdpo clock driver strength to level 2. Refer datasheet 12.7.5.11 + system_exec("aww 0x06542018 0x00000044"); // disable horizontal chroma FIR filter. + } else if (hz == 60) { + system_exec("dispw -s vdpo 1080p60"); + g_hw_stat.vdpo_tmg = VDPO_TMG_1080P60; + system_exec("aww 0x0300b340 0x00000008"); + system_exec("aww 0x0300b084 0x00003fff"); // Set vdpo clock driver strength to level 2. Refer datasheet 12.7.5.11 + system_exec("aww 0x06542018 0x00000044"); // disable horizontal chroma FIR filter. + } else { + Display_UI_init(); + } + + screen.display(1); + LOGI("Display_UI_SetRefresh: %dHz", hz ? hz : 50); + } + pthread_mutex_unlock(&hardware_mutex); +} + void Display_720P60_50_t(int mode, uint8_t is_43) // fps: 0=50, 1=60 { screen.display(0); diff --git a/src/driver/hardware.h b/src/driver/hardware.h index b232a713c..fe209c5cc 100644 --- a/src/driver/hardware.h +++ b/src/driver/hardware.h @@ -89,6 +89,7 @@ void Source_HDMI_in(); void Source_AV(bool is_av_in); void Display_UI_init(); void Display_UI(); +void Display_UI_SetRefresh(int hz); // retime the UI output for DVR playback (60 = 1080p60, 90 = 720p90); 0 restores the default UI timing void Display_720P90(int mode); void Display_720P60_50(int mode, uint8_t is_43); diff --git a/src/player/awdmx.c b/src/player/awdmx.c index 5d47f1e5c..4a37ddeff 100644 --- a/src/player/awdmx.c +++ b/src/player/awdmx.c @@ -181,8 +181,9 @@ AwdmxContext_t *awdmx_open(char *sFile, CB_onDmxEof cbOnEof, void *context) { dmxCtx->width = DemuxMediaInfo.mVideoStreamInfo[nIndex].mWidth; dmxCtx->height = DemuxMediaInfo.mVideoStreamInfo[nIndex].mHeight; dmxCtx->codecType = DemuxMediaInfo.mVideoStreamInfo[nIndex].mCodecType; + dmxCtx->fpsX1000 = DemuxMediaInfo.mVideoStreamInfo[nIndex].mFrameRate; dmxCtx->msDuration = DemuxMediaInfo.mDuration; - LOGD("stream info %dx%d", DemuxMediaInfo.mVideoStreamInfo[nIndex].mWidth, DemuxMediaInfo.mVideoStreamInfo[nIndex].mHeight); + LOGD("stream info %dx%d @%dmfps", DemuxMediaInfo.mVideoStreamInfo[nIndex].mWidth, DemuxMediaInfo.mVideoStreamInfo[nIndex].mHeight, dmxCtx->fpsX1000); if (DemuxMediaInfo.mAudioNum > 0) { nIndex = DemuxMediaInfo.mAudioIndex; diff --git a/src/player/awdmx.h b/src/player/awdmx.h index d9ab4c4c2..038d469ae 100644 --- a/src/player/awdmx.h +++ b/src/player/awdmx.h @@ -30,6 +30,7 @@ typedef struct int videoNum; uint16_t width; uint16_t height; + int fpsX1000; // video frame rate * 1000, 0 if unknown PAYLOAD_TYPE_E codecType; int audioNum; diff --git a/src/player/media.c b/src/player/media.c index 59abb2ee4..485d38985 100644 --- a/src/player/media.c +++ b/src/player/media.c @@ -17,6 +17,7 @@ #include "adec2ao.h" #include "awdmx.h" +#include "driver/hardware.h" #include "gogglemsg.h" #include "vdec2vo.h" #include "version.h" @@ -66,6 +67,7 @@ typedef struct pthread_mutex_t mutex; int playingTime; // ms + int retimedHz; // nonzero while the UI output is retimed for this file } PlayContext_t; static int play_start(PlayContext_t *playCtx) { @@ -238,8 +240,29 @@ media_t *media_instantiate(char *filename, notify_cb_t notify) { goto failed; } else { Vdec2VoParams_t vvParams; + int voWidth = VO_WIDTH; + int voHeight = VO_HEIGHT; memset(&vvParams, 0, sizeof(vvParams)); +#if PLAY_HDZERO && (defined(HDZGOGGLE) || defined(HDZGOGGLE2)) + // The menu UI drives the panel at 1080p50, which drops frames of + // 60/90fps DVR files unevenly. Retime for the duration of playback: + // 90fps files reuse the live-mode 720p90 timing so every frame is + // shown, 60fps files keep the 1080p UI on the same pixel clock. + int fps = (playCtx->dmx->fpsX1000 + 500) / 1000; + if (fps >= 80) { + playCtx->retimedHz = 90; + voWidth = 1280; + voHeight = 720; + } else if (fps >= 55) { + playCtx->retimedHz = 60; + } + if (playCtx->retimedHz) { + LOGD("retiming display to %dHz for %dfps file", playCtx->retimedHz, fps); + Display_UI_SetRefresh(playCtx->retimedHz); + } +#endif + vvParams.initRotation = 0; vvParams.pixelFormat = MM_PIXEL_FORMAT_YVU_PLANAR_420; @@ -247,8 +270,8 @@ media_t *media_instantiate(char *filename, notify_cb_t notify) { vvParams.vdec.width = playCtx->dmx->width; vvParams.vdec.height = playCtx->dmx->height; - vvParams.vo.width = VO_WIDTH; - vvParams.vo.height = VO_HEIGHT; + vvParams.vo.width = voWidth; + vvParams.vo.height = voHeight; vvParams.vo.intfType = VO_intfTYPE; vvParams.vo.intfSync = VO_intfSYNC; vvParams.vo.uiChn = VO_uiCHN; @@ -288,6 +311,9 @@ media_t *media_instantiate(char *filename, notify_cb_t notify) { adec2ao_deinitSys(playCtx->aa); vdec2vo_deinitSys(playCtx->vv); awdmx_close(playCtx->dmx); + if (playCtx->retimedHz) { + Display_UI_SetRefresh(0); + } pthread_mutex_destroy(&playCtx->mutex); free(playCtx); LOGD("exit done"); @@ -305,6 +331,9 @@ void media_exit(media_t *media) { adec2ao_deinitSys(playCtx->aa); vdec2vo_deinitSys(playCtx->vv); awdmx_close(playCtx->dmx); + if (playCtx->retimedHz) { + Display_UI_SetRefresh(0); + } pthread_mutex_destroy(&playCtx->mutex); free(media->context); free(media); diff --git a/src/record/ffpack.c b/src/record/ffpack.c index 4cd116f35..2810b87f9 100755 --- a/src/record/ffpack.c +++ b/src/record/ffpack.c @@ -196,8 +196,11 @@ int ffpack_newVideoStream(FFPack_t* ff, uint16_t programId, FFStreamParameters_t stream->r_frame_rate.den = 0;//1; stream->avg_frame_rate.num = param->video.fps; stream->avg_frame_rate.den = 1; - stream->time_base.num = 1;//0; - stream->time_base.den = param->video.fps;//100000;//1; //10us + /* a 1/fps time base rounds every capture timestamp to a whole frame + * slot (and collides when the real rate drifts from the configured + * fps), baking judder into the file; 90kHz keeps timestamps exact */ + stream->time_base.num = 1; + stream->time_base.den = 90000; cp->codec_type = param->mediaType; cp->codec_id = param->codecId; cp->width = param->video.width;