0xAA55 发表于 2014-11-12 23:58:40

【C】技术宅教你控制台播放视频


是不是看着就很清晰!确实是很清晰,因为我不仅用了那个半格的字符“▄”来表现两个像素,我还用了“░”和“▒”来表现更丰富的颜色,简单地使用了抖动算法。其实这个抖动算法做得不是特别好,还有改进的空间。
原理很简单,使用VideoIP将视频的每一帧都实时提取出来,然后将其缩小到适合控制台窗口的尺寸,再根据每个像素的颜色选择字符,或者抖动,最后把字符攒起来,在需要调用SetConsoleTextAttribute的时候才将字符用fputs显示出来(注意不是puts)。
下载地址:
BIN:
SRC:http://pan.baidu.com/s/1hqtI4fU(访问密码:t9j6)//=============================================================================
//作者:0xAA55
//论坛:http://www.0xaa55.com/
//版权所有(C) 2013-2014 技术宅的结界
//请保留原作者信息,否则视为侵权
//-----------------------------------------------------------------------------

#include<VIPc.h>
#include<math.h>
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<signal.h>

BOOL        g_bCompleted=FALSE;//播放完后这个值为真,退出程序
HANDLE        g_hStdout=NULL;//控制台标准输出句柄

typedef struct
{
        DWORD        B;
        DWORD        G;
        DWORD        R;
        DWORD        Sum;
}RGBStretch,*RGBStretchP;//用于收缩图像时计算重合像素的平均颜色

UINT                g_uScrWidth=80;//虚拟的屏幕尺寸
UINT                g_uScrHeight=25;
size_t                g_NbScrBufUnits=0;//虚拟屏幕缓冲区单元数
size_t                g_cbScrBuf=0;//虚拟屏幕缓冲区大小
RGBStretchP        g_pScrBuf=NULL;//虚拟屏幕缓冲区
//我们把要输出的相同颜色的字符攒起来,等颜色变了再一次性输出,以减少写入stdout的次数而增加流畅度
size_t                g_cbStrBuf=0;
char                *g_pStrBuf=NULL;//字符缓冲区

BOOL                g_bHftnPos=FALSE;//抖动的方式,制造闪烁的效果

//控制台调色板
RGBStretch        g_Palette=
{
        {0x00,0x00,0x00,0},
        {0x80,0x00,0x00,0},
        {0x00,0x80,0x00,0},
        {0x80,0x80,0x00,0},
        {0x00,0x00,0x80,0},
        {0x80,0x00,0x80,0},
        {0x00,0x80,0x80,0},
        {0xC0,0xC0,0xC0,0},
        {0x80,0x80,0x80,0},
        {0xFF,0x00,0x00,0},
        {0x00,0xFF,0x00,0},
        {0xFF,0xFF,0x00,0},
        {0x00,0x00,0xFF,0},
        {0xFF,0x00,0xFF,0},
        {0x00,0xFF,0xFF,0},
        {0xFF,0xFF,0xFF,0}
};

#define        PixelChar                0xDC/*PixelChar这个字符“▄”可以表示两个像素*/
#define        HftnChar1                0xB0/*淡的渐变块*/
#define        HftnChar2                0xB1/*渐变块*/
#define        HftnChar3                0xB2/*深的渐变块*/

//=============================================================================
//函数:Usage
//描述:打印程序的用法
//-----------------------------------------------------------------------------
void Usage(wchar_t*argv0)
{
        fwprintf(stderr,L"Usage:\n"
                L"%s videofile \n"
                L"Width and Height were defaulted to 80x25.\n"
                ,
                argv0?argv0:L"ConPlay");
}

//=============================================================================
//函数:SigProc
//描述:信号处理程序,用于处理用户产生的信号,Ctrl+C等
//-----------------------------------------------------------------------------
void SigProc(int Signal)
{
        switch(Signal)
        {
        case SIGINT:
                g_bCompleted=TRUE;
                break;
        }
}

//=============================================================================
//函数:InitBuffer
//描述:初始化缓冲区以用于绘图
//-----------------------------------------------------------------------------
BOOL InitBuffer()
{
        g_NbScrBufUnits=g_uScrWidth*g_uScrHeight*2;
        g_pScrBuf=(RGBStretchP)malloc(g_cbScrBuf=g_NbScrBufUnits*sizeof(RGBStretch));//分配屏幕缓冲区内存
        if(!g_pScrBuf)
                return FALSE;

        g_cbStrBuf=g_uScrWidth*g_uScrHeight+1;
        g_pStrBuf=(char*)malloc(g_cbStrBuf);
        if(!g_pStrBuf)
                return FALSE;

        return TRUE;
}

//=============================================================================
//函数:CleanupBuffer
//描述:清理绘图缓冲区
//-----------------------------------------------------------------------------
void CleanupBuffer()
{
        free(g_pScrBuf);
        g_pScrBuf=NULL;
        g_cbScrBuf=0;
        free(g_pStrBuf);
}

//=============================================================================
//函数:GetColorDistSq
//描述:取得两颜色之间的距离平方
//-----------------------------------------------------------------------------
UINT GetColorDistSq(int X,int Y,int Z)
{
        return X*X+Y*Y+Z*Z;
}

//=============================================================================
//函数:GetTriArea
//描述:取得三角形面积
//-----------------------------------------------------------------------------
double GetTriArea(double a,double b,double c)
{
        double p=(a+b+c)/2;
        return sqrt(p*(p-a)*(p-b)*(p-c));
}

//=============================================================================
//函数:GetNearestClr
//描述:取得最相近的、值不等于wExcept的颜色
//-----------------------------------------------------------------------------
WORD GetNearestClr
(
        RGBStretchP pClr,//颜色
        WORD wExcept,//除外
        UINT*uDistOut//输出距离,可为NULL
)
{
        WORD        i;
        WORD        wNearest=0;//最近颜色索引
        UINT        DistSq=0x7FFFFFFF;//颜色距离平方
        UINT        NewDistSq;//新的颜色距离平方
        for(i=0;i<16;i++)
        {
                NewDistSq=GetColorDistSq
                (//计算距离的平方
                        (int)(pClr->R)-(int)(g_Palette.R),//相对坐标X
                        (int)(pClr->G)-(int)(g_Palette.G),
                        (int)(pClr->B)-(int)(g_Palette.B)
                );
                if(NewDistSq<DistSq && i!=wExcept)//如果更接近
                {
                        DistSq=NewDistSq;
                        wNearest=i;
                }
        }
        if(uDistOut)
                *uDistOut=DistSq;
        return wNearest;
}

//=============================================================================
//函数:OnRender
//描述:VIP回调函数,用于处理图像数据
//-----------------------------------------------------------------------------
void VIPCallback OnRender
(
        size_t Width,//视频宽度
        size_t Height,//视频高度
        size_t Pitch,//视频每行字节数
        BYTE*pBits,//图像数据
        void*pUserData
)
{
        size_t x,y;//像素位置
        size_t iSrc,iDest;//像素索引
        size_t lSrc=0,lDest;//像素行开始索引
        COORD dwNewPos={0};
        size_t lSrc1=g_uScrWidth,lSrc2=0;
        size_t iSrc1,iSrc2;
        WORD wCharAttr;
        WORD wOldCharAttr=0xFFFF;//0xFFFF表示这是这一行第一个字符
        size_t LineBufEnd=0;
        char charToAdd=' ';//要添加的字符

        //先缩小图像
        memset(g_pScrBuf,0,g_cbScrBuf);
        for(y=0;y<Height;y++)
        {
                iSrc=lSrc;
                lDest=((Height-y-1)*g_uScrHeight*2/Height)*g_uScrWidth;
                for(x=0;x<Width;x++)
                {
                        iDest=lDest+x*g_uScrWidth/Width;
                        g_pScrBuf.B+=pBits;//将合并的像素的颜色混合起来
                        g_pScrBuf.G+=pBits;
                        g_pScrBuf.R+=pBits;
                        g_pScrBuf.Sum++;
                }
                lSrc+=Pitch;
        }

        //然后处理好合并的像素
        for(iDest=0;iDest<g_NbScrBufUnits;iDest++)
        {
                if(g_pScrBuf.Sum)
                {
                        g_pScrBuf.R/=g_pScrBuf.Sum;
                        g_pScrBuf.G/=g_pScrBuf.Sum;
                        g_pScrBuf.B/=g_pScrBuf.Sum;
                }
        }

        //行缓冲区,用来攒字符
        memset(g_pStrBuf,0,g_cbStrBuf);

        //绘制每一帧
        SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),dwNewPos);
        for(y=0;y<g_uScrHeight;y++)
        {
                iSrc1=lSrc1;//像素行指针
                iSrc2=lSrc2;//第二行的指针
                for(x=0;x<g_uScrWidth-1;x++)
                {
                        WORD wCol1,wCol2;
                       
                        wCol1=GetNearestClr(&g_pScrBuf,0xFFFF,NULL);//取得上半截颜色
                        wCol2=GetNearestClr(&g_pScrBuf,0xFFFF,NULL);//取得下半截颜色

                        //攒字符
                        if(wCol1==wCol2)//如果这个块的颜色上半部分和下半部分相同
                        {
                                UINT uDist1,uDist2,uDist3;
                                double Area;
                                double TriHeight;
                                double Split1,Split2;
                                RGBStretch Avr=//上下两个点的平均色
                                {
                                        (g_pScrBuf.B+g_pScrBuf.B)/2,
                                        (g_pScrBuf.G+g_pScrBuf.G)/2,
                                        (g_pScrBuf.R+g_pScrBuf.R)/2,
                                        0
                                };
                                wCol1=GetNearestClr(&Avr,16,&uDist1);//取得最相近颜色
                                wCol2=GetNearestClr(&Avr,wCol1,&uDist2);//取得第二接近颜色
                                //      Avr*_
                                //      /| ~"-,_
                                // uDist1/ |      ~"-,_uDist2
                                //      /|         ~"-,_
                                //wCol1*---+-----------------*wCol2
                                //            uDist3
                                uDist3=GetColorDistSq
                                (
                                        (int)(g_Palette.R)-(int)(g_Palette.R),
                                        (int)(g_Palette.G)-(int)(g_Palette.G),
                                        (int)(g_Palette.B)-(int)(g_Palette.B)
                                );
                                Area=GetTriArea((double)uDist1,(double)uDist2,(double)uDist3);
                                TriHeight=Area*2/(double)uDist3;//三角形的高
                                Split1=sqrt((double)uDist1*(double)uDist1-TriHeight*TriHeight);
                                Split2=sqrt((double)uDist2*(double)uDist2-TriHeight*TriHeight);
                                switch((UINT)(Split1*3/Split2))
                                {
                                default:
                                        wCharAttr=wCol1|(wCol1<<4);
                                        charToAdd=PixelChar;
                                        break;
                                case 1:
                                        if(g_bHftnPos)
                                        {
                                                wCharAttr=wCol2|(wCol1<<4);
                                                charToAdd=HftnChar1;
                                        }
                                        else
                                        {
                                                wCharAttr=wCol1|(wCol2<<4);
                                                charToAdd=HftnChar3;
                                        }
                                        break;
                                case 2:
                                        if(g_bHftnPos)
                                                wCharAttr=wCol1|(wCol2<<4);
                                        else
                                                wCharAttr=wCol2|(wCol1<<4);
                                        charToAdd=HftnChar2;
                                        break;
                                }
                        }
                        else
                        {
                                wCharAttr=wCol1|(wCol2<<4);
                                charToAdd=PixelChar;
                        }

                        if(wCharAttr!=wOldCharAttr)//如果颜色发生了改变
                        {
                                if(wOldCharAttr==0xFFFF)
                                        SetConsoleTextAttribute(g_hStdout,wCharAttr);//如果没设置过颜色则设置为新颜色
                                fputs(g_pStrBuf,stdout);//写入之前攒好的字符串
                                memset(g_pStrBuf,0,g_cbStrBuf);//清空字符串缓冲区
                                LineBufEnd=0;//重新攒字符串
                                SetConsoleTextAttribute(g_hStdout,wCharAttr);//设置为新的颜色
                                wOldCharAttr=wCharAttr;//记录新的颜色
                        }
                        g_pStrBuf=charToAdd;
                        iSrc1++;
                        iSrc2++;
                }
                if(y<g_uScrHeight-1)
                        g_pStrBuf='\n';
                lSrc1+=g_uScrWidth+g_uScrWidth;//指针一次跨两行
                lSrc2+=g_uScrWidth+g_uScrWidth;
        }
        fputs(g_pStrBuf,stdout);
        g_bHftnPos=!g_bHftnPos;
}

//=============================================================================
//函数:wmain
//描述:程序入口点,UNICODE版本
//-----------------------------------------------------------------------------
int wmain(int argc,wchar_t**argv)
{
        VIP VIPlayer;
        HRESULT hr;

        //参数不足打印用法
        if(argc<2)
        {
                Usage(argc?argv:NULL);
                return 1;
        }

        //取得用户输入的可选的屏幕尺寸
        if(argc>=3)
        {
                g_uScrWidth=(UINT)_wtoi(argv);
                if(argc>=4)
                        g_uScrHeight=(UINT)_wtoi(argv);
        }

        //安装信号处理程序
        signal(SIGINT,SigProc);

        g_hStdout=GetStdHandle(STD_OUTPUT_HANDLE);

        //必须先初始化COM才能初始化VIP
        if(FAILED(hr=CoInitialize(NULL)))
                goto Cleanup;

        if(FAILED(hr=InitVIP(&VIPlayer,argv,NULL,(FnOnRender)OnRender,TRUE,NULL)))
                goto Cleanup;

        //修改代码页,使其能显示DOS字符
        system("chcp 437");

        if(!InitBuffer())
        {
                hr=E_OUTOFMEMORY;
                goto Cleanup;
        }

        //开始播放
        if(FAILED(hr=VIPPlay(&VIPlayer)))
                goto Cleanup;

        //轮询检查是否播放完成
        while(!g_bCompleted)
        {
                VIPCheckCompleted(&VIPlayer,&g_bCompleted);
        }

        VIPStop(&VIPlayer);
        CleanupVIP(&VIPlayer);
        CleanupBuffer();
        system("color 07");
        return 0;
Cleanup:
        fprintf(stderr,"Error:HRESULT=0x%08X\n",hr);
        VIPStop(&VIPlayer);
        CleanupVIP(&VIPlayer);
        CleanupBuffer();
        system("color 07");
        return 2;
}

噗叽 发表于 2014-11-13 13:27:35

果断给跪。。。

0xAA55 发表于 2014-11-13 23:36:41

如果出现了一个单独的窗口显示视频,那说明解码器有问题,需要修复。
http://support.microsoft.com/?scid=kb;zh-cn;142731&spid=1139&sid=460

如月桃 发表于 2015-7-31 01:00:25

这个好帅!技术宅赛高!

simakeng 发表于 2017-1-26 19:07:53

Bad_Apple在召唤
页: [1]
查看完整版本: 【C】技术宅教你控制台播放视频