[原]DirectDraw视频播放要点

[本文于2008.08.29更新,增加了实际验证部分]

最近在写一个用DirectDraw播放视频的程序,用DirectDraw的目的是要减小图像显示过程的CPU占用。在网上看了不少教程、例子,大都是介绍一下各个结构、变量的含义,基本用法。对于如何正确的应用、高效的显示方面的资料却较难找到。经过反复试验、揣摩,总结如下:

DirectDraw主要是创建Surface,在Surface上绘制。Surface主要有DDSCAPS_PRIMARYSURFACE、DDSCAPS_OFFSCREENPLAIN、DDSCAPS_OVERLAY。创建Surface时的一个重要参数为DDSCAPS_VIDEOMEMORY / DDSCAPS_SYSTEMMEMORY,意思是将Surface创建在显存 / 系统内存中。为了防止撕裂效应(写入一半的图像被绘制出来),在写显存时要将Surface锁定(Lock,Unlock)。

可以通过Blt、UpdateOverlay在Surface间拷贝图像,也可取得Surface的DC,通过GDI函数在Surface间拷贝图像。如果两个Surface都是创建在显存中,Blt操作时不占CPU,但如果源Surface在系统内存而目的Surface在显存则占用CPU。

由于解码图像的数据量巨大,如将每帧解码图像都在系统内存中拷贝一遍会占用大量的CPU时间。进一步,由于计算机体系结构和显存的固有特点,读取、写入显存的时间大于读取、写入系统内存的时间,如将每帧解码图像都从系统内存到显存拷贝一遍则会占用比在系统内存间拷贝更多的CPU时间。但是,将解码图像写入显存这一步骤是无论如何都无法避免的,如果直接将图像写入显存,写入时的代码(如memcpy())会占用很多的CPU时间,如果先将图像写入DDSCAPS_SYSTEMMEMORY创建的Surface,Blt时由显卡驱动程序完成拷贝,结果是显卡驱动占用很多的CPU时间。事实上,调用GDI函数BitBlt、StretchBlt显示图像时,亦可观察到显卡驱动占用了很多的CPU时间完成图像从系统内存到显存的拷贝。

难道就没有办法减小写显存的这一大块CPU占用时间了吗?

将数据写入显存需要占用较多的CPU时间这一点是固有的。但CPU的流水线特性可以使得一条指令开始执行后,不必等待执行完毕即可执行下一条指令,当然这需要满足一定的条件,主要就是后面的指令不需要前面指令的计算结果。

到这里应该有写想法了吧?在写显存的过程中可以执行一些其它的操作或是在执行某些操作的过程中顺便写一下显存!事实上,可以在解码时直接将解码像素写入显存,或是将图像缩放、AlphaBlend、YUV2RGB转换等操作与写显存的操作融合起来。如此一来,可以观察到似乎是写显存的过程吸收了部分这些操作的CPU占用时间,系统的整体性能会有较大的提高。但另一方面,操作的融合也会降低代码的可读性和可维护性。有时性能优化和代码质量之间的矛盾很难调和。。。


[以下为2008.08.09的更新内容]

 以下给出了实际验证的结果。test_compute()函数的内循环为一个计算式,test_video_mem_write()的内循环为写显存,test_combine()将test_compute()和test_video_mem_write()的内循环合并。test_sys_mem_write()的内循环为写系统内存,用于对比写显存与写系统内存的时间。test_null()用于观察循环本身耗费的时间,如此观察不一定正确。循环模拟写100秒30fps(frame per second) 720 * 576的RGB32图像。

实验配置:P4 2.41GHz,1024M内存,L2 Cache 512K。Visual C++ 6.0 debug版编译(为了防止编译器将代码优化掉,得不到真正的运行时间)。程序运行时间由VTune 9.0测得。

下表给出了实验结果,计算式使用了两个,经过调整使时间 略大于 / 略小于 写显存的时间。从表中可以看出,计算时间 > 显存写入时间时,test_combine()时间约为计算时间,完全吸收了写显存的时间。计算时间 < 显存写入时间时,test_combine时间约为显存写入时间,计算时间完全融入到写显存的时间中,写显存并未耗费多余的时间。(说某一种时间吸收或融入到另一种时间实质上是一个意思。)

注意到写显存的时间远大于写系统内存的时间。

=============================================================
计算时间 > 显存写入时间时,test_combine时间约为计算时间
-------------------------------------------------------------
Function               Clockticks Samples
 test_combine           39770
 test_compute           39553
 test_video_mem_write   28277
 test_sys_mem_write     8254
 test_null              4107
=============================================================
计算时间 < 显存写入时间时,test_combine时间约为显存写入时间
-------------------------------------------------------------
Function               Clockticks Samples
 test_combine           28294
 test_video_mem_write   28248
 test_compute           19750
 test_sys_mem_write     8255
 test_null              4106

=============================================================

#include <stdio.h>
#include <windows.h>
#include <ddraw.h>

static LPDIRECTDRAW         lpdd;          //DirectDraw 对象指针
static LPDIRECTDRAWSURFACE7 lpddsBack;     //Back surface
static DDSURFACEDESC2       ddsd;          //DirectDraw 表面描述

static const GUID IID_IDirectDraw7 = {
    0x15e65ec0, 0x3b9c, 0x11d2, {0xb9, 0x2f, 0x00, 0x60, 0x97, 0x97, 0xea, 0x5b}
};

static const GUID IID_IDirectDrawColorControl = {
    0x4b9f0ee0, 0x0d7e, 0x11d0, {0x9b, 0x06, 0x00, 0xa0, 0xc9, 0x03, 0xa3, 0xb8}
};

static int ddraw_init()
{
    // 创建DirectCraw对象
    if (DirectDrawCreateEx(NULL, (VOID**)&lpdd, &IID_IDirectDraw7, NULL) != DD_OK)
        return -1;

    // 设置协作层
    if (lpdd->lpVtbl->SetCooperativeLevel(lpdd, GetDesktopWindow(),
        DDSCL_NORMAL | DDSCL_NOWINDOWCHANGES) != DD_OK)
        return -1;

    //Back surface
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY;
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
    ddsd.dwWidth = 720;
    ddsd.dwHeight = 576;
    if (lpdd->lpVtbl->CreateSurface(lpdd, &ddsd, &lpddsBack, NULL) != DD_OK) {
        printf("DirectDraw create back surface failed!"n");
        return -1;
    }

    return 0;
}

static void test_null()
{
    int i, j, k;
    int t;
    for (k = 0; k < 3000; k++) {
        for (j = 0; j < 576; j++) {
            for (i = 0; i < 720; i++) {
                t = i;
            }
        }

        if (k % 30 == 0)
            printf("In test null, k = %d"n", k);
    }
}

static void test_compute()
{
    int i, j, k;
    double test_num;

    for (k = 0; k < 3000; k++) {
        for (j = 0; j < 576; j++) {
            for (i = 0; i < 720; i++) {
                //test_num = i / 1.23456 + j / 1.45678;
                test_num = i / 1.23456;
            }
        }

        if (k % 30 == 0)
            printf("In test compute, k = %d"n", k);
    }
}

static void test_sys_mem_write()
{
    int i, j, k;
    int *p_pixel;
    int *write_buf = (int *)malloc(720 * 576 * sizeof(int));

    for (k = 0; k < 3000; k++) {
        p_pixel = write_buf;
        for (j = 0; j < 576; j++) {
            for (i = 0; i < 720; i++) {
                p_pixel[i] = i;
            }
            p_pixel += 720;
        }

        if (k % 30 == 0)
            printf("In test system memory write, k = %d"n", k);
    }

    free(write_buf);
}

static void test_video_mem_write()
{
    int i, j, k;
    char *p_pixel;
    char *write_buf;
    HRESULT ddRval;

    do {
        ddRval = lpddsBack->lpVtbl->Lock(lpddsBack, NULL,
            &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY,NULL);
    } while(ddRval == DDERR_WASSTILLDRAWING);

    if(ddRval == DD_OK) {
        write_buf = (LPBYTE)ddsd.lpSurface;
    }
    else {
        return;
    }

    for (k = 0; k < 3000; k++) {
        p_pixel = write_buf;
        for (j = 0; j < 576; j++) {
            for (i = 0; i < 720; i++) {
                ((int *)p_pixel)[i] = i;
            }
            p_pixel += ddsd.lPitch;
        }

        if (k % 30 == 0)
            printf("In test video memory write, k = %d"n", k);
    }

    lpddsBack->lpVtbl->Unlock(lpddsBack, NULL);
}

static void test_combine()
{
    int i, j, k;
    char *p_pixel;
    double test_num;

    char *write_buf;
    HRESULT ddRval;

    do {
        ddRval = lpddsBack->lpVtbl->Lock(lpddsBack, NULL,
            &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL);
    } while(ddRval == DDERR_WASSTILLDRAWING);

    if(ddRval == DD_OK) {
        write_buf = (LPBYTE)ddsd.lpSurface;
    }
    else {
        return;
    }

    for (k = 0; k < 3000; k++) {
        p_pixel = write_buf;
        for (j = 0; j < 576; j++) {
            for (i = 0; i < 720; i++) {
                ((int *)p_pixel)[i] = j;
                //test_num = i / 2.23456 + j / 2.45678;
                test_num = i / 2.23456;
            }
            p_pixel += ddsd.lPitch;
        }

        if (k % 30 == 0)
            printf("In test combine, k = %d"n", k);
    }

    lpddsBack->lpVtbl->Unlock(lpddsBack, NULL);
}

int main()
{
    ddraw_init();

    test_null();
    test_sys_mem_write();
    test_video_mem_write();
    test_compute();
    test_combine();
}

原文地址:https://www.cnblogs.com/techsunny/p/1271990.html