openh264 帧间预测编码过程源码分析

openh264

OpenH264 是一个开源的 H.264 编码和解码器,由思科系统开发并维护。它专为实时应用程序如 WebRTC 设计,提供了从基础到高级特性的广泛支持。OpenH264 的编码器支持从 Constrained Baseline Profile 到 5.2 级别,允许任意分辨率的编码,不限于 16x16 的倍数,并且具有自适应量化或恒定量化的速率控制、多切片选项、多线程自动用于多切片等特性。此外,它还支持高达 4 层的时序可伸缩性、单一输入的 4 种空间分辨率的 Spatial Simulcast、长期参考帧(LTR)、内存管理控制操作(MMCO)等功能。

帧间预测编码

视频帧间预测编码(Inter-frame prediction coding)是视频压缩技术中的一种关键方法,主要用于减少视频序列中时间维度上的冗余。这种编码方式依赖于视频帧之间的空间相关性,通过预测和补偿来减少数据量,从而实现高效的视频压缩。

帧间预测编码是视频编码中非常有效的技术,广泛应用于各种视频编码标准,如H.264/AVC、H.265/HEVC、VP9和AV1等。通过减少时间冗余,帧间预测编码显著提高了视频数据的压缩率,同时保持了视频质量。

帧间预测的核心技术主要包括以下几个方面:

  1. 运动估计(Motion Estimation, ME):

    • 运动估计是指在参考帧中搜索与当前块最匹配的区域,以确定最佳匹配块的位置。
  2. 运动补偿(Motion Compensation, MC):

    • 运动补偿使用运动估计得到的运动向量来预测当前块,通过补偿先前图像的抽样点来生成当前图像块的预测值。
  3. 宏块(Macroblock, MB)和子宏块(Sub-Macroblock)的树状结构分块:

    • H.264 支持多种宏块分割方式,如16x16、16x8、8x16和8x8,以及更细致的子宏块分割,如8x8、8x4、4x8和4x4。
  4. 多参考帧预测:

    • 特别是在B帧中,可以使用两个方向的参考帧(List0和List1)进行双向预测。
  5. 亚像素精度的运动估计:

    • 除了整像素精度外,H.264还支持1/2像素和1/4像素的亚像素插值,以提高预测精度。
  6. 快速搜索算法:

    • 为了减少计算复杂度,使用快速搜索算法来确定最佳匹配块。
  7. 运动向量的编码:

    • 运动向量的编码通常涉及编码运动向量与预测值的差值(MVD),而不是直接编码运动向量本身。
  8. 预测模式的选择:

    • H.264定义了多种预测模式,包括直接模式、双向模式、List0和List1模式等。
  9. 参考帧管理:

    • 管理参考帧以确保解码器能够正确地重建预测图像。
  10. 变换/量化编码和熵编码:

    • 这些步骤与帧内编码相同或相似,用于进一步压缩预测残差。

帧间预测通过这些技术有效地减少了视频序列中的时间冗余,提高了压缩效率。

openh264 帧间预测编码过程

  1. 帧间预测过程函数关系图
    在这里插入图片描述
  2. 关键模块说明
  • 通过API 函数EncodeFrame完成具体的编码过程,可执行程序封装该函数进行编码;
  • 通过二维数组g_pWelsSliceCoding[2][2]来完成具体的I 帧、P 帧,以及变化片数和非变化片数编码;
  • 在非变化片数帧间编码过程中,在WelsMdInterMbLoop函数中循环处理每个宏块;
  • 在变化片数帧间编码过程中,在 WelsMdInterMbLoopOverDynamicSlice函数中循环处理每个宏块;
  • pfInterMd函数指针指向具体的增强层WelsMdInterMbEnhancelayer或基本层WelsMdInterMb帧间预测过程;
  • 在基本层预测过程中,WelsMdInterSecondaryModesEnc函数完成二级帧间预测过程编码;
  • 在增强层预测过程中,WelsMdSpatialelInterMbIlfmdNoilp函数完成具体的预测过程编码;
  1. 帧间预测过程核心函数介绍
  • WelsMdInterMbLoopOverDynamicSlice 函数
  • 用于动态切片的宏块编码过程,循环处理每个 MB;
// Only for inter dynamic slicing
int32_t WelsMdInterMbLoopOverDynamicSlice (sWelsEncCtx* pEncCtx, SSlice* pSlice, void* pWelsMd,
    const int32_t kiSliceFirstMbXY) {
  SWelsMD* pMd          = (SWelsMD*)pWelsMd;
  SBitStringAux* pBs    = pSlice->pSliceBsa;
  SDqLayer* pCurLayer   = pEncCtx->pCurDqLayer;
  SSliceCtx* pSliceCtx  = &pCurLayer->sSliceEncCtx;
  SMbCache* pMbCache    = &pSlice->sMbCacheInfo;
  SMB* pMbList          = pCurLayer->sMbDataP;
  SMB* pCurMb           = NULL;
  int32_t iNumMbCoded   = 0;
  const int32_t kiTotalNumMb = pCurLayer->iMbWidth * pCurLayer->iMbHeight;
  int32_t iNextMbIdx = kiSliceFirstMbXY;
  int32_t iCurMbIdx = -1;
  const int32_t kiMvdInterTableStride = pEncCtx->iMvdCostTableStride;
  uint16_t* pMvdCostTable = &pEncCtx->pMvdCostTable[pEncCtx->iMvdCostTableSize];
  const int32_t kiSliceIdx = pSlice->iSliceIdx;
  const int32_t kiPartitionId = (kiSliceIdx % pEncCtx->iActiveThreadsNum);
  const uint8_t kuiChromaQpIndexOffset = pCurLayer->sLayerInfo.pPpsP->uiChromaQpIndexOffset;
  int32_t iEncReturn = ENC_RETURN_SUCCESS;

  SDynamicSlicingStack sDss;
  if (pEncCtx->pSvcParam->iEntropyCodingModeFlag) {
    WelsInitSliceCabac (pEncCtx, pSlice);
    sDss.iStartPos = sDss.iCurrentPos = 0;
    sDss.pRestoreBuffer = pEncCtx->pDynamicBsBuffer[kiPartitionId];
  } else {
    sDss.iStartPos = BsGetBitsPos (pBs);
  }
  pSlice->iMbSkipRun = 0;
  for (;;) {
    //DYNAMIC_SLICING_ONE_THREAD - MultiD
    //stack pBs pointer
    pEncCtx->pFuncList->pfStashMBStatus (&sDss, pSlice, pSlice->iMbSkipRun);

    //point to current pMb
    iCurMbIdx = iNextMbIdx;
    pCurMb = &pMbList[ iCurMbIdx ];

    //step(1): set QP for the current MB
    pEncCtx->pFuncList->pfRc.pfWelsRcMbInit (pEncCtx, pCurMb, pSlice);
    // if already reaches the largest number of slices, set QPs to the upper bound
    if (pSlice->bDynamicSlicingSliceSizeCtrlFlag) {
      //a clearer logic may be:
      //if there is no need from size control from the pSlice size, the QP will be decided by RC; else it will be set to the max QP
      //    however, there are some parameter updating in the rc_mb_init() function, so it cannot be skipped?
      pCurMb->uiLumaQp = pEncCtx->pWelsSvcRc[pEncCtx->uiDependencyId].iMaxQp;
      pCurMb->uiChromaQp = g_kuiChromaQpTable[CLIP3_QP_0_51 (pCurMb->uiLumaQp + kuiChromaQpIndexOffset)];
    }

    //step (2). save some vale for future use, initial pWelsMd
    WelsMdIntraInit (pEncCtx, pCurMb, pMbCache, kiSliceFirstMbXY);
    WelsMdInterInit (pEncCtx, pSlice, pCurMb, kiSliceFirstMbXY);

TRY_REENCODING:
    WelsInitInterMDStruc (pCurMb, pMvdCostTable, kiMvdInterTableStride, pMd);
    pEncCtx->pFuncList->pfInterMd (pEncCtx, pMd, pSlice, pCurMb, pMbCache);
    //mb_qp

    //step (4): save from the MD process from future use
    WelsMdInterSaveSadAndRefMbType ((pCurLayer->pDecPic->uiRefMbType), pMbCache, pCurMb, pMd);

    pEncCtx->pFuncList->pfMdBackgroundInfoUpdate (pCurLayer, pCurMb, pMbCache->bCollocatedPredFlag,
        pEncCtx->pRefPic->iPictureType);

    //step (5): update cache
    UpdateNonZeroCountCache (pCurMb, pMbCache);

    //step (6): begin to write bit stream; if the pSlice size is controlled, the writing may be skipped



    iEncReturn = pEncCtx->pFuncList->pfWelsSpatialWriteMbSyn (pEncCtx, pSlice, pCurMb);
    if (iEncReturn == ENC_RETURN_VLCOVERFLOWFOUND  && (pCurMb->uiLumaQp < 50)) {
      pSlice->iMbSkipRun = pEncCtx->pFuncList->pfStashPopMBStatus (&sDss, pSlice);
      UpdateQpForOverflow (pCurMb, kuiChromaQpIndexOffset);
      goto TRY_REENCODING;
    }
    if (ENC_RETURN_SUCCESS != iEncReturn)
      return iEncReturn;


    //DYNAMIC_SLICING_ONE_THREAD - MultiD
    sDss.iCurrentPos = pEncCtx->pFuncList->pfGetBsPosition (pSlice);
    if (DynSlcJudgeSliceBoundaryStepBack (pEncCtx, pSlice, pSliceCtx, pCurMb, &sDss)) {
      pSlice->iMbSkipRun = pEncCtx->pFuncList->pfStashPopMBStatus (&sDss, pSlice);
      pCurLayer->LastCodedMbIdxOfPartition[kiPartitionId] = iCurMbIdx -
          1; // update LastCodedMbIdxOfPartition, need to -1 due to stepping back
      ++ pCurLayer->NumSliceCodedOfPartition[kiPartitionId];

      break;
    }

    //step (7): reconstruct current MB
    pCurMb->uiSliceIdc = kiSliceIdx;
    OutputPMbWithoutConstructCsRsNoCopy (pEncCtx, pCurLayer, pSlice, pCurMb);

#if defined(MB_TYPES_CHECK)
    WelsCountMbType (pEncCtx->sPerInfo.iMbCount, P_SLICE, pCurMb);
#endif//MB_TYPES_CHECK

    //step (8): update status and other parameters
    pEncCtx->pFuncList->pfRc.pfWelsRcMbInfoUpdate (pEncCtx, pCurMb, pMd->iCostLuma, pSlice);

    /*judge if all pMb in cur pSlice has been encoded*/
    ++ iNumMbCoded;
    iNextMbIdx = WelsGetNextMbOfSlice (pCurLayer, iCurMbIdx);
    //whether all of MB in current pSlice encoded or not
    if (iNextMbIdx == -1 || iNextMbIdx >= kiTotalNumMb || iNumMbCoded >= kiTotalNumMb) {
      pCurLayer->LastCodedMbIdxOfPartition[kiPartitionId] = iCurMbIdx;
      ++ pCurLayer->NumSliceCodedOfPartition[kiPartitionId];

      break;
    }
  }

  if (pSlice->iMbSkipRun) {
    BsWriteUE (pBs, pSlice->iMbSkipRun);
  }

  return iEncReturn;
}

}//namespace WelsEnc

  • WelsMdInterMbLoop函数
  • 用于固定切片的宏块编码过程,循环处理每个 MB;
// for inter non-dynamic pSlice
int32_t WelsMdInterMbLoop (sWelsEncCtx* pEncCtx, SSlice* pSlice, void* pWelsMd, const int32_t kiSliceFirstMbXY) {
  SWelsMD* pMd          = (SWelsMD*)pWelsMd;
  SBitStringAux* pBs    = pSlice->pSliceBsa;
  SDqLayer* pCurLayer   = pEncCtx->pCurDqLayer;
  SMbCache* pMbCache    = &pSlice->sMbCacheInfo;
  SMB* pMbList          = pCurLayer->sMbDataP;
  SMB* pCurMb           = NULL;
  int32_t iNumMbCoded   = 0;
  int32_t iNextMbIdx    = kiSliceFirstMbXY;
  int32_t iCurMbIdx     = -1;
  const int32_t kiTotalNumMb = pCurLayer->iMbWidth * pCurLayer->iMbHeight;
  const int32_t kiMvdInterTableStride = pEncCtx->iMvdCostTableStride;
  uint16_t* pMvdCostTable = &pEncCtx->pMvdCostTable[pEncCtx->iMvdCostTableSize];
  const int32_t kiSliceIdx = pSlice->iSliceIdx;
  const uint8_t kuiChromaQpIndexOffset = pCurLayer->sLayerInfo.pPpsP->uiChromaQpIndexOffset;
  int32_t iEncReturn = ENC_RETURN_SUCCESS;
  SDynamicSlicingStack sDss;
  if (pEncCtx->pSvcParam->iEntropyCodingModeFlag) {
    WelsInitSliceCabac (pEncCtx, pSlice);
    sDss.pRestoreBuffer = NULL;
    sDss.iStartPos = sDss.iCurrentPos = 0;
  }
  pSlice->iMbSkipRun = 0;
  for (;;) {
    if (!pEncCtx->pSvcParam->iEntropyCodingModeFlag)
      pEncCtx->pFuncList->pfStashMBStatus (&sDss, pSlice, pSlice->iMbSkipRun);
    //point to current pMb
    iCurMbIdx = iNextMbIdx;
    pCurMb = &pMbList[ iCurMbIdx ];


    //step(1): set QP for the current MB
    pEncCtx->pFuncList->pfRc.pfWelsRcMbInit (pEncCtx, pCurMb, pSlice);

    //step (2). save some vale for future use, initial pWelsMd
    WelsMdIntraInit (pEncCtx, pCurMb, pMbCache, kiSliceFirstMbXY);
    WelsMdInterInit (pEncCtx, pSlice, pCurMb, kiSliceFirstMbXY);

TRY_REENCODING:
    WelsInitInterMDStruc (pCurMb, pMvdCostTable, kiMvdInterTableStride, pMd);
    pEncCtx->pFuncList->pfInterMd (pEncCtx, pMd, pSlice, pCurMb, pMbCache);
    //mb_qp

    //step (4): save from the MD process from future use
    WelsMdInterSaveSadAndRefMbType ((pCurLayer->pDecPic->uiRefMbType), pMbCache, pCurMb, pMd);

    pEncCtx->pFuncList->pfMdBackgroundInfoUpdate (pCurLayer, pCurMb, pMbCache->bCollocatedPredFlag,
        pEncCtx->pRefPic->iPictureType);

    //step (5): update cache
    UpdateNonZeroCountCache (pCurMb, pMbCache);

    //step (6): begin to write bit stream; if the pSlice size is controlled, the writing may be skipped

    iEncReturn = pEncCtx->pFuncList->pfWelsSpatialWriteMbSyn (pEncCtx, pSlice, pCurMb);
    if (!pEncCtx->pSvcParam->iEntropyCodingModeFlag) {
      if (iEncReturn == ENC_RETURN_VLCOVERFLOWFOUND && (pCurMb->uiLumaQp < 50)) {
        pSlice->iMbSkipRun = pEncCtx->pFuncList->pfStashPopMBStatus (&sDss, pSlice);
        UpdateQpForOverflow (pCurMb, kuiChromaQpIndexOffset);
        goto TRY_REENCODING;
      }
    }
    if (ENC_RETURN_SUCCESS != iEncReturn)
      return iEncReturn;


    //step (7): reconstruct current MB
    pCurMb->uiSliceIdc = kiSliceIdx;
    OutputPMbWithoutConstructCsRsNoCopy (pEncCtx, pCurLayer, pSlice, pCurMb);

#if defined(MB_TYPES_CHECK)
    WelsCountMbType (pEncCtx->sPerInfo.iMbCount, P_SLICE, pCurMb);
#endif//MB_TYPES_CHECK

    //step (8): update status and other parameters
    pEncCtx->pFuncList->pfRc.pfWelsRcMbInfoUpdate (pEncCtx, pCurMb, pMd->iCostLuma, pSlice);

    /*judge if all pMb in cur pSlice has been encoded*/
    ++ iNumMbCoded;
    iNextMbIdx = WelsGetNextMbOfSlice (pCurLayer, iCurMbIdx);
    //whether all of MB in current pSlice encoded or not
    if (iNextMbIdx == -1 || iNextMbIdx >= kiTotalNumMb || iNumMbCoded >= kiTotalNumMb) {
      break;
    }
  }

  if (pSlice->iMbSkipRun) {
    BsWriteUE (pBs, pSlice->iMbSkipRun);
  }

  return iEncReturn;
}

  • WelsMdInterMb函数
  • 基本层的预测编码的核心实现函数,主要用模式决策等过程;
void WelsMdInterMb (sWelsEncCtx* pEncCtx, SWelsMD* pWelsMd, SSlice* pSlice, SMB* pCurMb, SMbCache* pUnused) {
  SDqLayer* pCurDqLayer             = pEncCtx->pCurDqLayer;
  SMbCache* pMbCache                = &pSlice->sMbCacheInfo;
  const uint32_t kuiNeighborAvail   = pCurMb->uiNeighborAvail;
  const int32_t kiMbWidth           = pCurDqLayer->iMbWidth;
  const  SMB* top_mb                = pCurMb - kiMbWidth;
  const bool bMbLeftAvailPskip      = ((kuiNeighborAvail & LEFT_MB_POS) ? IS_SKIP ((pCurMb - 1)->uiMbType) : false);
  const bool bMbTopAvailPskip       = ((kuiNeighborAvail & TOP_MB_POS) ? IS_SKIP (top_mb->uiMbType) : false);
  const bool bMbTopLeftAvailPskip   = ((kuiNeighborAvail & TOPLEFT_MB_POS) ? IS_SKIP ((top_mb - 1)->uiMbType) : false);
  const bool bMbTopRightAvailPskip = ((kuiNeighborAvail & TOPRIGHT_MB_POS) ? IS_SKIP ((top_mb + 1)->uiMbType) : false);
  bool bTrySkip = bMbLeftAvailPskip || bMbTopAvailPskip || bMbTopLeftAvailPskip || bMbTopRightAvailPskip;
  bool bKeepSkip = bMbLeftAvailPskip && bMbTopAvailPskip && bMbTopRightAvailPskip;
  bool bSkip = false;

  //try BGD skip
  if (pEncCtx->pFuncList->pfInterMdBackgroundDecision (pEncCtx, pWelsMd, pSlice, pCurMb, pMbCache, &bKeepSkip)) {
    return;
  }

  //try static or scrolled Pskip
  if (pEncCtx->pFuncList->pfSCDPSkipDecision (pEncCtx, pWelsMd, pSlice, pCurMb, pMbCache)) {
    return;
  }

  //step 1: try SKIP
  bSkip = WelsMdInterJudgePskip (pEncCtx, pWelsMd, pSlice, pCurMb, pMbCache, bTrySkip);

  if (bSkip) {
    if (bKeepSkip) {
      WelsMdInterDecidedPskip (pEncCtx,  pSlice,  pCurMb, pMbCache);
      return;
    }
  } else {
    PredictSad (pMbCache->sMvComponents.iRefIndexCache, pMbCache->iSadCost, 0, &pWelsMd->iSadPredMb);

    //step 2: P_16x16
    pWelsMd->iCostLuma = WelsMdP16x16 (pEncCtx->pFuncList, pCurDqLayer, pWelsMd, pSlice, pCurMb);
    pCurMb->uiMbType = MB_TYPE_16x16;
  }

  WelsMdInterSecondaryModesEnc (pEncCtx, pWelsMd, pSlice, pCurMb, pMbCache, bSkip);
}
  • WelsMdInterSecondaryModesEnc函数
  • 基本层帧间二级模式的编码实现函数,主要用与判断出了 skip 和 p16x16 块类型之外的模式决策;
void WelsMdInterSecondaryModesEnc (sWelsEncCtx* pEncCtx, SWelsMD* pWelsMd, SSlice* pSlice, SMB* pCurMb,
                                   SMbCache* pMbCache, const bool bSkip) {
  //step 2: Intra
  const bool kbTrySkip = pEncCtx->pFuncList->pfFirstIntraMode (pEncCtx, pWelsMd, pCurMb, pMbCache);
  if (kbTrySkip)
    return;

  if (bSkip) {
    WelsMdInterDecidedPskip (pEncCtx,  pSlice,  pCurMb, pMbCache);
  } else {
    //Step 3: SubP16 MD
    pEncCtx->pFuncList->pfSetScrollingMv (pEncCtx->pVaa, pWelsMd); //SCC
    pEncCtx->pFuncList->pfInterFineMd (pEncCtx, pWelsMd, pSlice, pCurMb, pWelsMd->iCostLuma);

    //refinement for inter type
    WelsMdInterMbRefinement (pEncCtx, pWelsMd, pCurMb, pMbCache);

    //step 7: invoke encoding
    WelsMdInterEncode (pEncCtx, pSlice, pCurMb, pMbCache);

    //step 8: double check Pskip
    WelsMdInterDoubleCheckPskip (pCurMb, pMbCache);
  }
}
  • WelsMdSpatialelInterMbIlfmdNoilp函数
  • 增强层的预测编码的核心实现函数,主要用模式决策等过程;
//
// MD for enhancement layers
//
void WelsMdSpatialelInterMbIlfmdNoilp (sWelsEncCtx* pEncCtx, SWelsMD* pWelsMd, SSlice* pSlice,
                                       SMB* pCurMb, const Mb_Type kuiRefMbType) {
  SDqLayer* pCurDqLayer = pEncCtx->pCurDqLayer;
  SMbCache* pMbCache = &pSlice->sMbCacheInfo;

  const uint32_t kuiNeighborAvail = pCurMb->uiNeighborAvail;
  const int32_t kiMbWidth = pCurDqLayer->iMbWidth;
  const  SMB* kpTopMb = pCurMb - kiMbWidth;
  const bool kbMbLeftAvailPskip = ((kuiNeighborAvail & LEFT_MB_POS) ? IS_SKIP ((pCurMb - 1)->uiMbType) : false);
  const bool kbMbTopAvailPskip  = ((kuiNeighborAvail & TOP_MB_POS) ? IS_SKIP (kpTopMb->uiMbType) : false);
  const bool kbMbTopLeftAvailPskip  = ((kuiNeighborAvail & TOPLEFT_MB_POS) ? IS_SKIP ((kpTopMb - 1)->uiMbType) : false);
  const bool kbMbTopRightAvailPskip = ((kuiNeighborAvail & TOPRIGHT_MB_POS) ? IS_SKIP ((kpTopMb + 1)->uiMbType) : false);

  bool bTrySkip  = kbMbLeftAvailPskip | kbMbTopAvailPskip | kbMbTopLeftAvailPskip | kbMbTopRightAvailPskip;
  bool bKeepSkip = kbMbLeftAvailPskip & kbMbTopAvailPskip & kbMbTopRightAvailPskip;
  bool bSkip = false;

  if (pEncCtx->pFuncList->pfInterMdBackgroundDecision (pEncCtx, pWelsMd, pSlice, pCurMb, pMbCache, &bKeepSkip)) {
    return;
  }

  //step 1: try SKIP
  bSkip = WelsMdInterJudgePskip (pEncCtx, pWelsMd, pSlice, pCurMb, pMbCache, bTrySkip);

  if (bSkip && bKeepSkip) {
    WelsMdInterDecidedPskip (pEncCtx,  pSlice,  pCurMb, pMbCache);
    return;
  }

  if (! IS_SVC_INTRA (kuiRefMbType)) {
    if (!bSkip) {
      PredictSad (pMbCache->sMvComponents.iRefIndexCache, pMbCache->iSadCost, 0, &pWelsMd->iSadPredMb);

      //step 2: P_16x16
      pWelsMd->iCostLuma = WelsMdP16x16 (pEncCtx->pFuncList, pCurDqLayer, pWelsMd, pSlice, pCurMb);
      pCurMb->uiMbType = MB_TYPE_16x16;
    }

    WelsMdInterSecondaryModesEnc (pEncCtx, pWelsMd, pSlice, pCurMb, pMbCache, bSkip);
  } else { //BLMODE == SVC_INTRA
    //initial prediction memory for I_16x16
    const int32_t kiCostI16x16 = WelsMdI16x16 (pEncCtx->pFuncList, pEncCtx->pCurDqLayer, pMbCache, pWelsMd->iLambda);
    if (bSkip && (pWelsMd->iCostLuma <= kiCostI16x16)) {
      WelsMdInterDecidedPskip (pEncCtx,  pSlice,  pCurMb, pMbCache);
    } else {
      pWelsMd->iCostLuma = kiCostI16x16;
      pCurMb->uiMbType = MB_TYPE_INTRA16x16;

      WelsMdIntraSecondaryModesEnc (pEncCtx, pWelsMd, pCurMb, pMbCache);
    }
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/718314.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于51单片机的音乐彩灯设计

基于51单片机的音乐彩灯设计 &#xff08;程序&#xff0b;原理图&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 由STC单片机ADC0809模块LM386功放模块喇叭音频接口发光二极管电源构成 1.通过音频线输入可以播放电脑、手机、MP3里面的音乐。 2.AD对音频…

Java 桥接模式(Bridge Pattern)是设计模式中的一种结构型设计模式,桥接模式的核心思想是将抽象与实现解耦

桥接模式&#xff08;Bridge Pattern&#xff09;是一种结构型设计模式&#xff0c;它将抽象部分与它的实现部分分离&#xff0c;使它们都可以独立地变化。桥接模式的核心思想是将抽象与实现解耦&#xff0c;使得它们可以独立扩展。 在桥接模式中&#xff0c;通常包含以下四个…

揭秘:边缘智能网关P1600在智慧灯杆上的应用

智慧灯杆作为智慧城市建设的重要组成部分&#xff0c;集成了照明、通信、安防、环境监测等多重功能&#xff0c;是实现城市智能化的关键载体。边缘智能网关P1600在这一系统中扮演着至关重要的角色&#xff0c;它不仅连接和管理各种传感器和设备&#xff0c;还负责数据的采集、处…

保护密码安全,探讨密码加盐及其在Go语言中的实现

介绍 在当今数字化时代&#xff0c;个人隐私和数据安全成为了人们关注的焦点之一。随着网络犯罪的不断增加&#xff0c;用户的密码安全性变得尤为重要。密码加盐作为一种常见的安全措施&#xff0c;被广泛应用于密码存储和认证系统中。本文将深入探讨密码加盐的概念、重要性以…

Ubuntu网络管理命令:ifconfig

安装Ubuntu桌面系统&#xff08;虚拟机&#xff09;_虚拟机安装ubuntu桌面版-CSDN博客 关于ifconfig命令&#xff0c;在11.1节已经介绍过了。通过该命令可以查看和配置网络接口。ifconfig是一个比较古老的命令&#xff0c;在Ubuntu 22以及其他的许多发行版中&#xff0c;已经不…

多种传感器在钢铁工业安全风险监测预警中的应用

中国作为钢铁行业的生产与消费大国&#xff0c;其钢铁冶炼流程的复杂性和长周期性使得各环节中频繁出现的有毒有害、易燃易爆气体以及粉尘等危险物质成为行业安全管理的重大挑战。为了保障工作人员的安全&#xff0c;多种传感器在安全风险监测预警中的应用显得尤为重要。 钢铁产…

地表位移监测系统:原理、组成与功能

地表位移监测系统是一项用于实时监测地表下沉、沉降、地面位移和地下水位变化的关键工具。本文将介绍该系统的工作原理、系统组成、工作模式以及功能特点&#xff0c;以便更深入地了解如何有效利用该系统进行沉降监测。 一、工作原理 地表位移监测系统主要由位移监测站、数据采…

华宽通成功中标岳麓山大学科技城管理委员会投资环境推介可视化平台

湖南华宽通科技股份有限公司成功中标岳麓山大学科技城管理委员会投资环境推介可视化平台项目。该平台将整合区域经济数据、产业布局、创新能力等关键信息&#xff0c;通过智能分析工具&#xff0c;为投资者提供定制化的投资建议和实时更新的动态信息。通过该平台&#xff0c;岳…

C语言王国——数组的旋转(轮转数组)三种解法

目录 一、题目 二、分析 2.1 暴力求解法 2.2 找规律 2.3 追求时间效率&#xff0c;以空间换时间 三、结论 一、题目 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3 输出…

Flink系列之:Generating Watermarks生成水印

Flink系列之&#xff1a;Generating Watermarks生成水印 一、水印策略简介二、使用水印策略三、处理闲置资源四、水印对齐五、编写水印生成器六、编写周期性水印生成器七、编写标点水印生成器八、水印策略和 Kafka 连接器九、Operators如何处理水印十、已弃用的AssignerWithPer…

Http协议JSON格式

1. 计算机网络 计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备&#xff0c;通过通信线路连接起来&#xff0c;在网络操作系统&#xff0c;网络管理软件及网络通信协议的管理和协调下&#xff0c;实现资源共享和信息传递的计算机系统。 思考:计算机网络…

六西格玛助力便携式产品功耗大降:打造绿色节能新标杆!

随着功能的日益强大&#xff0c;便携式电子产品的功耗问题也日益凸显&#xff0c;成为制约产品性能提升和用户体验改善的关键因素。为了应对这一挑战&#xff0c;越来越多的企业开始探索应用六西格玛方法来降低便携式产品的功耗&#xff0c;实现绿色节能的目标。 六西格玛是一…

IPA清洁棉签 IPA清洁擦拭棒:打印机头、电子设备等清洁的有力工具!

在数字化快速发展的今天&#xff0c;打印机头、电子设备等已经成为了我们日常生活和工作中不可或缺的一部分。然而&#xff0c;随着使用时间的增长&#xff0c;这些设备往往会因为灰尘、油渍等污染物的积累而影响其性能。此时&#xff0c;一款高效、便捷的清洁工具就显得尤为重…

一篇搞定Spring,IOC容器,Bean管理,3.AOP底层原理和实现(收下吧,真的很详细)

1.Spring容器的概念 Spring是一个轻量级的框架&#xff0c;可以解决企业开发的复杂性&#xff0c;让开发效率提升&#xff0c;他核心的两个点是&#xff1a; 1.IOC IOC&#xff1a;在java中&#xff0c;我们程序员一般是去创建一个对象&#xff0c;那么有个问题就是耦合性太…

Windows采用txt和bat来一次性建立多个文件夹

前言 最近工作需要一次性建立多个文件夹&#xff0c;方便保存不同的数据&#xff0c;所以在网上搜了搜方法&#xff0c;方法还挺多的&#xff0c;这里只是给出流程最简洁、最适合自己的方法&#xff0c;供自己日后回顾&#xff0c;如果大家想学习更多方法可以百度一下。 方法…

Hue Hadoop 图形化用户界面 BYD

软件简介 Hue 是运营和开发 Hadoop 应用的图形化用户界面。Hue 程序被整合到一个类似桌面的环境&#xff0c;以 web 程序的形式发布&#xff0c;对于单独的用户来说不需要额外的安装。

白酒:茅台镇白酒的品牌合作与跨界营销案例

云仓酒庄豪迈白酒&#xff0c;作为茅台镇的知名品牌&#xff0c;在品牌合作与跨界营销方面也有着杰出的表现。通过与不同领域品牌的合作&#xff0c;豪迈白酒进一步拓宽了市场渠道&#xff0c;提升了品牌曝光度和影响力。 首先&#xff0c;云仓酒庄豪迈白酒与品质餐产品牌的合作…

【SpringCloud学习笔记】RabbitMQ(中)

1. 交换机概述 前面《RabbitMQ上篇》我们使用SpringAMQP来演示如何用Java代码操作RabbitMQ&#xff0c;当时采用的是生产者直接将消息发布给队列&#xff0c;但是实际开发中不建议这么做&#xff0c;更加推荐生产者将消息发布到交换机(exchange)&#xff0c;然后由exchange路由…

Mac M3 Pro 部署Flink-1.16.3

目录 1、下载安装包 2、解压及配置 3、启动&测试 4、测试FlinkSQL读取hive数据 以上是mac硬件配置 1、下载安装包 官网&#xff1a;Downloads | Apache Flink 网盘&#xff1a; Flink 安装包 https://pan.baidu.com/s/1IN62_T5JUrnYUycYMwsQqQ?pwdgk4e Flink 已…

✅生产问题之Emoji表情如何操作存储,MySQL是否支持

针对 Emoji 表情 MySQL 存储是否支持的问题&#xff0c;结论是&#xff1a; MySQL 中可以存储 emoji 表情&#xff0c;但需要使用 UTF8MB4 字符编码。如果使用 UTF8MB3&#xff0c;存储这些扩展字符会导致解析错误。 课外补充 MySQL 对 Unicode 的支持 Unicode 字符集已成为…