From b574dfdafb4654c3c040cecd8731c9edc9734b61 Mon Sep 17 00:00:00 2001 From: wumingjie Date: Thu, 4 Jun 2026 17:34:51 +0800 Subject: [PATCH] feat(vi) k3 support framectl && rotate Function --- al/vi/k1/vi_k1.c | 4 - mpi/vi/vi.c | 215 ++++++++++++++++++++++++++++++- sample/sample_vi_k3_4venc_bind.c | 115 ++++++++++++----- 3 files changed, 293 insertions(+), 41 deletions(-) diff --git a/al/vi/k1/vi_k1.c b/al/vi/k1/vi_k1.c index c048ed0..ce8f15a 100644 --- a/al/vi/k1/vi_k1.c +++ b/al/vi/k1/vi_k1.c @@ -199,10 +199,6 @@ S32 K1_VI_HandleNormalCallback(K1_VI_CHN_CTX_S *pstChnCtx, K1_VI_BUF_NODE_S *pst if (pstChnCtx == NULL || pstBufNode == NULL) return K1_VI_ERR_INVALID_PARAM; - if (K1_VI_ShouldKeepFrame(pstChnCtx) != MPP_TRUE) { - return K1_VI_HandleDroppedFrame(pstChnCtx, pstBufNode); - } - pstBufNode->enState = K1_VI_BUF_STATE_READY; return K1_VI_DonePush(pstChnCtx, pstBufNode->u32Index); } diff --git a/mpi/vi/vi.c b/mpi/vi/vi.c index 458250e..0125ca9 100644 --- a/mpi/vi/vi.c +++ b/mpi/vi/vi.c @@ -13,6 +13,7 @@ #include "vi_buf_mgr.h" #include "sys/sys_api.h" #include "sys/vb_api.h" +#include "v2d_api.h" #define VI_MPI_MAX_BUF_CNT 8 #define VI_MPI_MAX_DEPTH 8 @@ -53,6 +54,17 @@ typedef struct _MpiViChnBufCtx { pthread_t hRecycleTid; U32 u32BadSlotMask; /* bitmask of slots where QBUF has persistently failed */ + /* V2D transform output pool — populated when bMirror/bFlip/eRotateMode is set */ + BOOL bNeedV2dTransform; + UL ulV2dOutPoolId; + U32 u32V2dOutBufCnt; + VideoFrameInfo astV2dOutFrameInfo[VI_MPI_MAX_BUF_CNT]; + UL aulV2dOutBufferId[VI_MPI_MAX_BUF_CNT]; + + /* Frame rate control — shared by K1 and K3, enforced in push_task */ + ViFrameRateCtrlS stFrameRateCtrl; + U32 u32FrameRateSeq; + /* Depth queue (pull mode, used only when u32Depth > 0) */ pthread_mutex_t depthLock; pthread_cond_t depthNotEmpty; @@ -91,6 +103,55 @@ static const ViAlOps *g_pViOps = NULL; static VOID mpi_vi_destroy_chn_buf_ctx(VI_DEV ViDev, VI_CHN ViChn); +/* ============================================================ + * Frame rate control helper (shared by K1 and K3) + * ============================================================ */ + +static BOOL mpi_vi_should_keep_frame(MpiViChnBufCtx *pstBufCtx) { + U32 u32In = pstBufCtx->stFrameRateCtrl.u32InputFrameStep; + U32 u32Out = pstBufCtx->stFrameRateCtrl.u32OutputFrameStep; + U32 u32Idx; + + if (u32In == 0 || u32Out == 0) + return MPP_FALSE; + if (u32Out >= u32In) + return MPP_TRUE; + + u32Idx = pstBufCtx->u32FrameRateSeq % u32In; + pstBufCtx->u32FrameRateSeq++; + return (u32Idx < u32Out) ? MPP_TRUE : MPP_FALSE; +} + +/* ============================================================ + * V2D transform helpers + * ============================================================ */ + +static V2DRotateAngle mpi_vi_get_v2d_rotate(const ViChnAttrS *pstAttr) { + if (pstAttr == NULL) + return V2D_ROT_0; + switch (pstAttr->eRotateMode) { + case VI_ROT_0: return V2D_ROT_0; + case VI_ROT_90: return V2D_ROT_90; + case VI_ROT_180: return V2D_ROT_180; + case VI_ROT_270: return V2D_ROT_270; + case VI_ROT_MIRROR: return V2D_ROT_MIRROR; + case VI_ROT_FLIP: return V2D_ROT_FLIP; + default: break; + } + if (pstAttr->bMirror == MPP_TRUE && pstAttr->bFlip == MPP_TRUE) return V2D_ROT_180; + if (pstAttr->bMirror == MPP_TRUE) return V2D_ROT_MIRROR; + if (pstAttr->bFlip == MPP_TRUE) return V2D_ROT_FLIP; + return V2D_ROT_0; +} + +static BOOL mpi_vi_needs_v2d_transform(const ViChnAttrS *pstAttr) { + if (pstAttr == NULL) + return MPP_FALSE; + return (pstAttr->bMirror || pstAttr->bFlip || + (pstAttr->eRotateMode != VI_ROT_0 && pstAttr->eRotateMode != VI_ROT_BUTT)) + ? MPP_TRUE : MPP_FALSE; +} + /* ============================================================ * Push thread: DQBUF → SYS_SendFrame (+ depth queue if depth>0) * ============================================================ */ @@ -122,6 +183,12 @@ static void *mpi_vi_push_task(void *arg) { UL ulBuf = pstBufCtx->aulBufferId[u32Index]; + /* Frame rate control: drop frames not scheduled for output */ + if (mpi_vi_should_keep_frame(pstBufCtx) != MPP_TRUE) { + VB_ReleaseBuffer(ulBuf); + continue; + } + /* Build frame info: copy template, fill PTS/sequence from DQBUF metadata, * convert frame type so VENC can accept it directly. */ VideoFrameInfo stFrame = pstBufCtx->astFrameInfo[u32Index]; @@ -138,6 +205,93 @@ static void *mpi_vi_push_task(void *arg) { } } + /* V2D transform path: raw capture buffer → V2D output buffer */ + if (pstBufCtx->bNeedV2dTransform) { + UL ulV2dBuf = VB_GetBuffer(pstBufCtx->ulV2dOutPoolId, 200); + if (ulV2dBuf == 0 || ulV2dBuf == (UL)-1) { + error("vi push: v2d out pool exhausted, drop frame dev=%d chn=%d", ViDev, ViChn); + VB_ReleaseBuffer(ulBuf); + continue; + } + + U32 v2dSlot = (U32)-1; + for (U32 i = 0; i < pstBufCtx->u32V2dOutBufCnt; i++) { + if (pstBufCtx->aulV2dOutBufferId[i] == ulV2dBuf) { + v2dSlot = i; + break; + } + } + if (v2dSlot == (U32)-1) { + error("vi push: v2d buf %lu not found in slot table", ulV2dBuf); + VB_ReleaseBuffer(ulV2dBuf); + VB_ReleaseBuffer(ulBuf); + continue; + } + + VideoFrameInfo stV2dFrame = pstBufCtx->astV2dOutFrameInfo[v2dSlot]; + stV2dFrame.stVFrame.u64PTS = stFrame.stVFrame.u64PTS; + stV2dFrame.stVFrame.u32FrameFlag = stFrame.stVFrame.u32FrameFlag; + + V2DHandle hV2d = 0; + S32 v2dRet = V2D_BeginJob(&hV2d); + if (v2dRet == MPP_OK) { + v2dRet = V2D_RotateFrame(hV2d, &stFrame, &stV2dFrame, + mpi_vi_get_v2d_rotate(&pstBufCtx->stChnAttr)); + if (v2dRet == MPP_OK) + v2dRet = V2D_EndJob(hV2d); + else + (void)V2D_CancelJob(hV2d); + } else { + VB_ReleaseBuffer(ulV2dBuf); + VB_ReleaseBuffer(ulBuf); + continue; + } + if (v2dRet != MPP_OK) { + error("vi push: V2D_RotateFrame failed ret=%d dev=%d chn=%d", v2dRet, ViDev, ViChn); + VB_ReleaseBuffer(ulV2dBuf); + VB_ReleaseBuffer(ulBuf); + continue; + } + + stV2dFrame.stVFrame.u32PlaneSizeValid[0] = stV2dFrame.stVFrame.u32PlaneSize[0]; + if (stV2dFrame.stVFrame.u32PlaneNum > 1) + stV2dFrame.stVFrame.u32PlaneSizeValid[1] = stV2dFrame.stVFrame.u32PlaneSize[1]; + + /* Release raw capture buffer — recycle thread re-queues it to V4L2 */ + VB_ReleaseBuffer(ulBuf); + + CommonFrameInfo stV2dCommInfo = stV2dFrame.stViFrameInfo.stCommFrameInfo; + stV2dFrame.eFrameType = FRAME_TYPE_VENC; + stV2dFrame.eModId = MPP_ID_VENC; + stV2dFrame.stVencFrameInfo.stCommFrameInfo = stV2dCommInfo; + + VB_UpdateBufferFrameInfo(ulV2dBuf, &stV2dFrame); + (void)SYS_SendFrame(&stSrcNode, ulV2dBuf); + + U32 u32Depth = pstBufCtx->stChnAttr.u32Depth; + if (u32Depth > 0) { + VB_RefAdd(ulV2dBuf); + pthread_mutex_lock(&pstBufCtx->depthLock); + if (pstBufCtx->u32DepthCount >= u32Depth) { + MpiViDepthEntry *pOld = &pstBufCtx->astDepthQueue[pstBufCtx->u32DepthHead]; + VB_ReleaseBuffer(pOld->ulBufferId); + pstBufCtx->u32DepthHead = (pstBufCtx->u32DepthHead + 1) % VI_MPI_MAX_DEPTH; + pstBufCtx->u32DepthCount--; + } + MpiViDepthEntry *pNew = &pstBufCtx->astDepthQueue[pstBufCtx->u32DepthTail]; + pNew->ulBufferId = ulV2dBuf; + pNew->stFrameInfo = stV2dFrame; + pstBufCtx->u32DepthTail = (pstBufCtx->u32DepthTail + 1) % VI_MPI_MAX_DEPTH; + pstBufCtx->u32DepthCount++; + pthread_cond_signal(&pstBufCtx->depthNotEmpty); + pthread_mutex_unlock(&pstBufCtx->depthLock); + } + + VB_ReleaseBuffer(ulV2dBuf); + + continue; + } + /* Convert to VENC frame type so VENC's frame input thread can handle it. * stViFrameInfo and stVencFrameInfo share the same union storage; save * stCommFrameInfo to a local copy before reinterpreting the union to @@ -404,6 +558,8 @@ static VOID mpi_vi_destroy_chn_buf_ctx(VI_DEV ViDev, VI_CHN ViChn) { return; pstBufCtx = &g_astViBufCtx[ViDev][ViChn]; + if (pstBufCtx->ulV2dOutPoolId != 0) + VB_DestroyPool(pstBufCtx->ulV2dOutPoolId); if (pstBufCtx->ulPoolId != 0) MPI_VI_DestroyOutBufPool(pstBufCtx->ulPoolId, pstBufCtx->u32BufCnt, pstBufCtx->aulBufferId); @@ -482,6 +638,42 @@ static S32 mpi_vi_rebuild_chn_buf_ctx(VI_DEV ViDev, VI_CHN ViChn, const ViChnAtt pstBufCtx->u32BufCnt = u32BufCnt; pstBufCtx->stChnAttr = *pstChnAttr; + + /* Reset frame rate to 1:1 on every rebuild so seq and rate are consistent */ + pstBufCtx->stFrameRateCtrl.u32InputFrameStep = 1; + pstBufCtx->stFrameRateCtrl.u32OutputFrameStep = 1; + pstBufCtx->u32FrameRateSeq = 0; + + /* Create V2D output pool if mirror/flip/rotate is configured */ + pstBufCtx->bNeedV2dTransform = mpi_vi_needs_v2d_transform(pstChnAttr); + if (pstBufCtx->bNeedV2dTransform) { + VideoFrameInfo stV2dTemplate; + V2DRotateAngle enRot = mpi_vi_get_v2d_rotate(pstChnAttr); + ViChnAttrS stOutAttr = *pstChnAttr; + if (enRot == V2D_ROT_90 || enRot == V2D_ROT_270) { + stOutAttr.u32Width = pstChnAttr->u32Height; + stOutAttr.u32Height = pstChnAttr->u32Width; + } + pstBufCtx->u32V2dOutBufCnt = u32BufCnt; + s32Ret = MPI_VI_CreateOutBufPool( + ViDev, ViChn, &stOutAttr, pstBufCtx->u32V2dOutBufCnt, + &pstBufCtx->ulV2dOutPoolId, &stV2dTemplate, + pstBufCtx->astV2dOutFrameInfo, pstBufCtx->aulV2dOutBufferId); + if (s32Ret != MPP_OK) { + pstBufCtx->ulV2dOutPoolId = 0; + pstBufCtx->u32V2dOutBufCnt = 0; + mpi_vi_destroy_chn_buf_ctx(ViDev, ViChn); + return s32Ret; + } + info("vi v2d out pool created: dev=%d chn=%d rot=%d pool=%lu cnt=%u", + ViDev, ViChn, (int)enRot, pstBufCtx->ulV2dOutPoolId, pstBufCtx->u32V2dOutBufCnt); + + /* MPI_VI_CreateOutBufPool acquires every buffer (ref=1). Release them + * all so VB_GetBuffer in push_task can acquire them one by one. */ + for (U32 j = 0; j < pstBufCtx->u32V2dOutBufCnt; j++) + VB_ReleaseBuffer(pstBufCtx->aulV2dOutBufferId[j]); + } + return MPP_OK; } @@ -625,15 +817,30 @@ S32 VI_GetChnAttr(VI_DEV ViDev, VI_CHN ViChn, ViChnAttrS *pstChnAttr) { } S32 VI_SetChnFrameRate(VI_DEV ViDev, VI_CHN ViChn, const ViFrameRateCtrlS *pstFrameRateCtrl) { + MpiViChnBufCtx *pstBufCtx = NULL; + if (vi_load_plugin() != MPP_OK) return MPP_INIT_FAILED; - return g_pViOps->set_chn_framerate(ViDev, ViChn, pstFrameRateCtrl); + if (pstFrameRateCtrl == NULL || mpi_vi_is_valid_dev_chn(ViDev, ViChn) != MPP_TRUE) + return MPI_VI_ERR_INVALID_PARAM; + if (pstFrameRateCtrl->u32InputFrameStep == 0 || pstFrameRateCtrl->u32OutputFrameStep == 0) + return MPI_VI_ERR_INVALID_PARAM; + + pstBufCtx = &g_astViBufCtx[ViDev][ViChn]; + if (pstBufCtx->bEnabled != MPP_TRUE) + return MPI_VI_ERR_INVALID_PARAM; + pstBufCtx->stFrameRateCtrl = *pstFrameRateCtrl; + pstBufCtx->u32FrameRateSeq = 0; + return MPP_OK; } S32 VI_GetChnFrameRate(VI_DEV ViDev, VI_CHN ViChn, ViFrameRateCtrlS *pstFrameRateCtrl) { - if (vi_load_plugin() != MPP_OK) - return MPP_INIT_FAILED; - return g_pViOps->get_chn_framerate(ViDev, ViChn, pstFrameRateCtrl); + if (pstFrameRateCtrl == NULL || mpi_vi_is_valid_dev_chn(ViDev, ViChn) != MPP_TRUE) + return MPI_VI_ERR_INVALID_PARAM; + if (g_astViBufCtx[ViDev][ViChn].bEnabled != MPP_TRUE) + return MPI_VI_ERR_INVALID_PARAM; + *pstFrameRateCtrl = g_astViBufCtx[ViDev][ViChn].stFrameRateCtrl; + return MPP_OK; } S32 VI_EnableChn(VI_DEV ViDev, VI_CHN ViChn) { diff --git a/sample/sample_vi_k3_4venc_bind.c b/sample/sample_vi_k3_4venc_bind.c index 33df55a..e957d43 100644 --- a/sample/sample_vi_k3_4venc_bind.c +++ b/sample/sample_vi_k3_4venc_bind.c @@ -8,14 +8,13 @@ * @Brief : Demo: K3 VI 4-channel → 4×VENC via SYS bind, save 4 H.264 files. * * Device mapping: - * dev0/chn0 → /dev/video0 → VENC chn0 → output_chn0.h264 - * dev0/chn1 → /dev/video1 → VENC chn1 → output_chn1.h264 - * dev0/chn2 → /dev/video2 → VENC chn2 → output_chn2.h264 - * dev0/chn3 → /dev/video3 → VENC chn3 → output_chn3.h264 + * dev0/chn0 → /dev/video0 → VENC chn0 → output_chn0.h264 (full rate, reference) + * dev0/chn1 → /dev/video1 → VENC chn1 → output_chn1.h264 (1/2 rate) + * dev0/chn2 → /dev/video2 → VENC chn2 → output_chn2.h264 (1/3 rate) + * dev0/chn3 → /dev/video3 → VENC chn3 → output_chn3.h264 (full rate) * - * Each VI channel runs in bind-only mode (u32Depth=0). - * A dedicated reader thread per VENC channel pulls encoded - * NAL units and writes them to the corresponding file. + * chn0 is the reference channel. When it saves frame_count frames + * all other channels stop and report their actual saved counts. * * Usage: ./sample_vi_k3_4venc_bind [frame_count] * default frame_count = 300 (~10 s @ 30 fps) @@ -30,6 +29,7 @@ #include #include #include +#include #include "sys/sys_api.h" #include "sys/vb_api.h" @@ -44,32 +44,32 @@ #define DEFAULT_HEIGHT 720 #define DEFAULT_FRAMES 300 +/* Global stop flag: set by chn0 when it reaches frame_count */ +static volatile int g_stop = 0; + typedef struct { - int venc_chn; - FILE *fp; - int frame_count; - int saved; - int error; + int venc_chn; + FILE *fp; + int frame_count; /* only meaningful for trigger channel (chn0) */ + int is_trigger; /* 1 = chn0, drives g_stop */ + int saved; + int error; + double elapsed_s; } ReaderArg; static void *reader_thread(void *arg) { ReaderArg *ra = arg; StreamBufferInfo stream; S32 ret; + struct timespec t0, t1; + + clock_gettime(CLOCK_MONOTONIC, &t0); - while (ra->saved < ra->frame_count) { + while (!g_stop) { memset(&stream, 0, sizeof(stream)); - ret = VENC_GetStream(ra->venc_chn, &stream, 3000); - if (ret != 0) { - fprintf(stderr, "[chn%d] VENC_GetStream error on frame %d: %d\n", - ra->venc_chn, ra->saved, ret); - ra->error++; - if (ra->error > 10) { - fprintf(stderr, "[chn%d] too many errors, aborting\n", ra->venc_chn); - break; - } + ret = VENC_GetStream(ra->venc_chn, &stream, 100); /* short timeout to check g_stop */ + if (ret != 0) continue; - } if (fwrite(stream.pu8Addr, 1, stream.u32Size, ra->fp) != stream.u32Size) fprintf(stderr, "[chn%d] short write on frame %d\n", ra->venc_chn, ra->saved); @@ -77,15 +77,32 @@ static void *reader_thread(void *arg) { VENC_ReleaseStream(ra->venc_chn, &stream); ra->saved++; - if (ra->saved % 30 == 0) - printf("[chn%d] saved %d / %d frames\n", ra->venc_chn, ra->saved, ra->frame_count); + if (ra->saved % 30 == 0) { + if (ra->is_trigger) + printf("[chn0] saved %d / %d frames\n", ra->saved, ra->frame_count); + else + printf("[chn%d] saved %d frames so far\n", ra->venc_chn, ra->saved); + } + + /* Trigger channel: stop all when frame_count reached */ + if (ra->is_trigger && ra->saved >= ra->frame_count) { + g_stop = 1; + break; + } } + + /* Ensure other threads are unblocked if this thread exits for any reason */ + if (ra->is_trigger) + g_stop = 1; + + clock_gettime(CLOCK_MONOTONIC, &t1); + ra->elapsed_s = (t1.tv_sec - t0.tv_sec) + (t1.tv_nsec - t0.tv_nsec) * 1e-9; return NULL; } static void usage(const char *prog) { printf("Usage: %s [frame_count]\n", prog); - printf(" frame_count number of encoded frames per channel (default %d)\n", DEFAULT_FRAMES); + printf(" frame_count frames for chn0 (reference channel, default %d)\n", DEFAULT_FRAMES); } int main(int argc, char **argv) { @@ -190,6 +207,33 @@ int main(int argc, char **argv) { i, i, DEFAULT_WIDTH, DEFAULT_HEIGHT); } + /* ------------------------------------------------------------------ */ + /* 2b. Frame rate control: chn1 half-rate, chn2 one-third rate */ + /* ------------------------------------------------------------------ */ + { + ViFrameRateCtrlS frc; + + /* chn1: output 1 out of every 2 input frames → ~15 fps */ + frc.u32InputFrameStep = 2; + frc.u32OutputFrameStep = 1; + ret = VI_SetChnFrameRate(VI_DEV_ID, 1, &frc); + if (ret != 0) + fprintf(stderr, "VI_SetChnFrameRate(chn1) failed: %d\n", ret); + else + printf("VI chn1 frame rate: %u/%u (every %u-th frame)\n", + frc.u32OutputFrameStep, frc.u32InputFrameStep, frc.u32InputFrameStep); + + /* chn2: output 1 out of every 3 input frames → ~10 fps */ + frc.u32InputFrameStep = 3; + frc.u32OutputFrameStep = 1; + ret = VI_SetChnFrameRate(VI_DEV_ID, 2, &frc); + if (ret != 0) + fprintf(stderr, "VI_SetChnFrameRate(chn2) failed: %d\n", ret); + else + printf("VI chn2 frame rate: %u/%u (every %u-th frame)\n", + frc.u32OutputFrameStep, frc.u32InputFrameStep, frc.u32InputFrameStep); + } + /* ------------------------------------------------------------------ */ /* 3. VENC init: 4 channels, each H.264 CBR 2 Mbps */ /* ------------------------------------------------------------------ */ @@ -258,6 +302,7 @@ int main(int argc, char **argv) { reader_args[i].venc_chn = i; reader_args[i].fp = fps[i]; reader_args[i].frame_count = frame_count; + reader_args[i].is_trigger = (i == 0) ? 1 : 0; reader_args[i].saved = 0; reader_args[i].error = 0; @@ -278,17 +323,21 @@ int main(int argc, char **argv) { pthread_join(tids[i], NULL); } - printf("\nAll channels done:\n"); + printf("\nResults (chn0 is reference, others stopped when chn0 reached %d frames):\n", + frame_count); + printf(" %-6s %-8s %-10s %-12s %s\n", + "chn", "frames", "elapsed(s)", "actual fps", "file"); for (i = 0; i < NUM_CHN; i++) { snprintf(outpath, sizeof(outpath), "./output_chn%d.h264", i); - printf(" chn%d: %d frames saved to %s\n", i, reader_args[i].saved, outpath); + double fps_actual = (reader_args[i].elapsed_s > 0.0) + ? reader_args[i].saved / reader_args[i].elapsed_s : 0.0; + printf(" chn%-3d %-8d %-10.2f %-12.1f %s%s\n", + i, reader_args[i].saved, reader_args[i].elapsed_s, fps_actual, outpath, + (i == 0) ? " [reference]" : ""); } - ret = 0; - for (i = 0; i < NUM_CHN; i++) { - if (reader_args[i].saved != frame_count) - ret = 1; - } + /* Success only requires chn0 to hit its target */ + ret = (reader_args[0].saved >= frame_count) ? 0 : 1; /* ------------------------------------------------------------------ */ /* 7. Cleanup (reverse order) */