• 使用DirectDraw直接显示YUV视频数据


    最近在编写一个进行视频播放的ActiveX控件,工作已经接近尾声,现将其中显示YUV数据的使用DirectDraw的一些经验总结如下:(解码部分不是我编写的,我负责从网络接收数据,将数据传给解码器,并将解码得到的YUV数据进行显示,最初在显示部分我是先将YUV数据转换为RGB数据,再以位图的形式显示到屏幕上,但发现CPU占用率比较高,后来改用DirectDraw直接显示YUV数据)

    1.在DirectDraw中创建YUV表面

      与一般表面不同的是,创建YUV表面时需要指定象素格式,并指定YUV数据的FourCC码,关于FourCC码可以参考微软MSDN站点上的说明,下面是具体的创建方法:(以YUV4:2:0格式为例,其中drawwidth和drawheight是欲显示图像的宽度和高度,以象素为单位)

    LPDIRECTDRAW7           lpDD;    // DirectDraw 对象指针
    LPDIRECTDRAWSURFACE7    lpDDSPrimary;  // DirectDraw 主表面指针
    LPDIRECTDRAWSURFACE7    lpDDSOffScr;  // DirectDraw 离屏表面指针
    DDSURFACEDESC2   ddsd;    // DirectDraw 表面描述

      // 创建DirectCraw对象
      if (DirectDrawCreateEx(NULL, (VOID**)&lpDD, IID_IDirectDraw7, NULL) != DD_OK)
     {
      //MessageBox("Error Create DDraw.");
      return FALSE;
     }

     // 设置协作层
        if (lpDD->SetCooperativeLevel(hWnd,
       DDSCL_NORMAL | DDSCL_NOWINDOWCHANGES) != DD_OK)
      {
      //MessageBox("Error Create Level.", s);
            return FALSE;
     }

        // 创建主表面
     ZeroMemory(&ddsd, sizeof(ddsd));
        ddsd.dwSize = sizeof(ddsd);
        ddsd.dwFlags = DDSD_CAPS;
        ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
        if (lpDD->CreateSurface(&ddsd, &lpDDSPrimary, NULL) != DD_OK)
      {
      //MessageBox("Error Create Primary Surface.", s);
            return FALSE;
     }
       
     LPDIRECTDRAWCLIPPER  pcClipper;   // Cliper
     if( lpDD->CreateClipper( 0, &pcClipper, NULL ) != DD_OK )
            return FALSE;

        if( pcClipper->SetHWnd( 0, hWnd ) != DD_OK )
        {
            pcClipper->Release();
            return FALSE;
        }

        if( lpDDSPrimary->SetClipper( pcClipper ) != DD_OK )
        {
            pcClipper->Release();
            return FALSE;
        }

        // Done with clipper
        pcClipper->Release();

     // 创建YUV表面 
     ZeroMemory(&ddsd, sizeof(ddsd));
     ddsd.dwSize = sizeof(ddsd);
     ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
     ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
     ddsd.dwWidth = drawwidth;
     ddsd.dwHeight = drawheight;
     ddsd.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT);
     ddsd.ddpfPixelFormat.dwFlags  = DDPF_FOURCC | DDPF_YUV ;
     ddsd.ddpfPixelFormat.dwFourCC = MAKEFOURCC('Y','V', '1', '2');
     ddsd.ddpfPixelFormat.dwYUVBitCount = 8;
     if (lpDD->CreateSurface(&ddsd, &lpDDSOffScr, NULL) != DD_OK)
      {
      //MessageBox("Error Create Off Surface.", s);
            return FALSE;
     }

    2.将解码得到的YUV数据拷贝到YUV表面

      设解码得到的YUV数据的指针分别是Y,U,V, 每行数据长度为BPS,具体拷贝代码如下,这里需要特别注意每拷一行都要对写指针加ddsd.lPitch(对于Y)或ddsd.lPitch/2(对于UV):

     LPBYTE lpSurf = (LPBYTE)ddsd.lpSurface;
     LPBYTE PtrY = Y;
     LPBYTE PtrU = U;
     LPBYTE PtrV = V;
     
     do {
      ddRval = lpDDSOffScr->Lock(NULL,&ddsd,DDLOCK_WAIT | DDLOCK_WRITEONLY,NULL);
     } while(ddRval == DDERR_WASSTILLDRAWING);
     if(ddRval != DD_OK)
      return 1;

    // 填充离屏表面
     if(lpSurf)
     {
      for (int i=0;iHeight;i++)
      {
       memcpy(lpSurf, PtrY, ddsd.dwWidth);
       PtrY += BpS;
       lpSurf += ddsd.lPitch;
      }

      for (int i=0;iHeight/2;i++)
      {
       memcpy(lpSurf, PtrV, ddsd.dwWidth/2);
       PtrV += BpS;
       lpSurf += ddsd.lPitch/2;
      }
      for (int i=0;iHeight/2;i++)
      {
       memcpy(lpSurf, PtrU, ddsd.dwWidth/2);
       PtrU += BpS;
       lpSurf += ddsd.lPitch/2;
      }

     }
     
     lpDDSOffScr->Unlock(NULL);

    3.YUV表面的显示

      现在就可以直接将YUV表面Blt到主表面或后备表面进行显示了:(设lpDDSBack为后备表面)

      ddRval = lpDDSBack->Blt(NULL, lpDDSOffScr, NULL, DDBLT_WAIT, NULL);

    这样就实现了YUV数据的显示,对比发现使用DirectDraw直接进行YUV数据显示,CPU占用率降低了一半。

  • 相关阅读:
    Mybatis 是否支持延迟加载?如果支持,它的实现原理是什么?
    MyBatis 实现一对多有几种方式,怎么操作的?
    利用 ps 怎么显示所有的进程? 怎么利用 ps 查看指定进 程的信息?
    哪个命令专门用来查看后台任务?
    什么是 MyBatis 的接口绑定?有哪些实现方式?
    什么是端到端微服务测试?
    我们如何在测试中消除非决定论?
    什么是持续监测?
    怎么使一个命令在后台运行?
    博客园样式美化(兼容为知笔记)
  • 原文地址:https://www.cnblogs.com/keanuyaoo/p/3260692.html
Copyright © 2020-2023  润新知