【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;
} 果断给跪。。。 如果出现了一个单独的窗口显示视频,那说明解码器有问题,需要修复。
http://support.microsoft.com/?scid=kb;zh-cn;142731&spid=1139&sid=460 这个好帅!技术宅赛高! Bad_Apple在召唤
页:
[1]