一直看网上各种大神做的BadApple代码版视频,所以手痒想自己做一版出来,然而无奈本人编程菜鸟,只能参考别的大神的代码做下自己的修改。对于这里引用到的代码都会贴上链接,并原作者表达深深的敬意,权当是一个小项目自己练手。
附上项目链接,有需要的可以参考下载:点击打开链接(https://github.com/clauyiye/BadApple)。
整体思路总结起来主要分为四个步骤:1)原版视频的抽帧,生成每一帧的图像;2)图像转制为字符文档;3)文件的压缩与解压;4)插入音频,调整同步率,生成exe文件。
系统及工具:Win10 64位系统,Visual Studio2015,集成OpenCV2.0。
1、视频抽帧生成BMP图像。
当然素材要准备好.AVI格式的“BadApple”视频文件,以及.WAV格式的音频文件。这一步的处理中有采用第三方软件抽取视频每帧图像的,我这里因为有使用OpenCV2.0图像处理库,因此参考这个帖子《opencv2 用imwrite 抽取并保存视频图像帧》(http://www.cnblogs.com/miaojinmin799/p/6845462.html),做了处理。代码如下:
#include "stdafx.h"
#include
#include
#include "highgui.h"
#include
using namespace cv;
using namespace std;
int main()
{
VideoCapture capture("badapple.avi");
if (!capture.isOpened()) {
cout << "fail to open!" << endl;
}
long totalFrameNumber = capture.get(CV_CAP_PROP_FRAME_COUNT);//获取视频总帧数
cout << "整个视频共" << totalFrameNumber << "帧" << endl;
double rate = capture.get(CV_CAP_PROP_FPS);//获取帧率
cout << "帧率为:" << rate << endl;
stringstream ss;
int num=0;
string str;
char imagename[100];
while (capture.read(frame))//读入视频
{
ss << num;
ss >> str;
sprintf_s(imagename, "%s%d%s","./image/badapple_", ++num,".bmp");//指定保存路径与存储名称,存储格式为.BMP
imwrite(imagename, frame);//保存图像
}
capture.release();
return 0;
}
从而生成出了5000多幅BMP图像,有点庞大... 继续第二步。
2、图像转制为字符文档。
1)图像的转制。
后面的代码就主要参考这个帖子了(先膜拜下):写一个自己的C++控制台字符画版BadApple!!(http://www.cnblogs.com/CodeMIRACLE/p/5508236.html)。将上一步制作好的图像文件夹放入这一步的项目文件夹下,闲话少叙,上代码。
#include "stdafx.h"
#include
#include
#include
#include
int32_t width, height;
RGBQUAD *pixels;
bool OpenBitmap(char const *filename)//位图文件打开函数
{
FILE *file = fopen(filename, "rb");
if (file)
{
width = 0;
height = 0;
BITMAPFILEHEADER bf;
BITMAPINFOHEADER bi;
fread(&bf, sizeof(bf), 1, file);
fread(&bi, sizeof(bi), 1, file);
if (bi.biBitCount != 24)
return false;
if (bi.biCompression != BI_RGB)
return false;
width = bi.biWidth;
height = bi.biHeight;
pixels = new RGBQUAD[width*height];
uint32_t rowSize = (bi.biBitCount * width + 31) / 32 * 4;
uint8_t *line = new uint8_t[rowSize];
for (int y = 0; y < height; y++)
{
fread(line, rowSize, 1, file);
for (int x = 0; x < width; x++)
{
uint8_t *color = line + x * 3;
RGBQUAD *pixel = &pixels[(height - y - 1) * width + x];
pixel->rgbBlue = color[0];
pixel->rgbGreen = color[1];
pixel->rgbRed = color[2];
}
}
delete[] line;
fclose(file);
return true;
}
return false;
}
RGBQUAD GetColor(int x, int y, int w, int h)//自定义获取像素颜色函数
{
int r = 0, g = 0, b = 0;
for (int i = 0; i < w; i++)
{
if (i + x >= width) continue;
for (int j = 0; j < h; j++)
{
if (j + y >= height) continue;
RGBQUAD const& color = pixels[(y + j) * width + (x + i)];
r += color.rgbRed;
g += color.rgbGreen;
b += color.rgbBlue;
}
}
return RGBQUAD{(BYTE) (r / (w * h)), (BYTE)(g / (w * h)),(BYTE)(b / (w * h)) };
}
char ColorToCharacter(RGBQUAD const& color)//颜色转制字符函数
{
int brightness = (color.rgbRed + color.rgbGreen + color.rgbBlue) / 3;
static char const *characters = "8O&*dboc:_. ";
int len = strlen(characters);
int span = 0xFF / len;
int cidx = brightness / span;
if (cidx == len)
cidx--;
return characters[cidx];
}
void OutputAscii(const char* filename, int w, int h)//ASCII码输出函数
{
FILE *file = fopen(filename, "a+");
int x = width / w;
int y = height / h;
for (int i = 0; i < height; i += y)
{
for (int j = 0; j < width; j += x)
{
RGBQUAD color = GetColor(j, i, x, y);
fprintf(file, "%c", ColorToCharacter(color));
//printf("%c", ColorToCharacter(color));
}
fprintf(file, "\n");
//printf("\n");
}
delete[] pixels;
fclose(file);
}
int main()
{
char filename[1024];
printf("转换中,请稍后~\n");
for (int i = 1; i <= 5262; i++)
{
sprintf(filename, "image/badapple_%d.bmp", i);
if (OpenBitmap(filename))
OutputAscii("badapple.txt", width / 6, height / 12);
}
printf("转换完成!");
}
2)文件动态预览。
上一步生成了一个大约5.6M左右的“badapple.txt”文档,可以对其进行下动态预览。由于本人C语言实在初学,里面很多代码都是边看边学,看注释就看出来水平不高了。
#include "stdafx.h"
#include
#include
struct fps_limit {
int previous_time;
int tpf_limit;
int tpf;
fps_limit(int fps = 60) : previous_time(GetTickCount()), tpf(0) {
limit_fps(fps);
}
void reset() {
previous_time = GetTickCount(), //GetTickCount()返回从系统启动到当前所经过的毫秒数
tpf = 0;
tpf_limit = 60;
}
void limit_fps(int fps) {
tpf_limit = (int)(1000.0f / (float)fps);
}
void delay() {
tpf = GetTickCount() - previous_time; //得到当前过程经过的毫秒数
if (tpf < tpf_limit)
Sleep(tpf_limit - tpf - 1); //程序挂起一段时间,单位为毫秒
previous_time = GetTickCount();
}
};
int main()
{
FILE* fp = fopen("badapple.txt", "r");
char buf[2048];
fps_limit fps(25);//画面刷新帧率,正常24针/秒,但是应该是转换时出现的误差,因此调整后多少有些不太同步,25为最佳值
while (!feof(fp))
{
HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE); //GetStdHandle()用于从一个特定的标准设备
//(标准输入、标准输出或标准错误)中取得一个句柄
//STD_OUTPUT_HANDLE是个宏,代表标准输出,可以看作显示器
COORD pos;//定义光标起始点
pos.X = 0;
pos.Y = 0;
SetConsoleCursorPosition(hConsoleOutput, pos);//设置控制台光标坐标,参数就是设备句柄,坐标
for (int i = 0; i<20; i++) //这里调整的是整个画面的刷新频率,小于20太慢,大于20太快,试出来的( ╯□╰ )
{
fgets(buf, 2048, fp);
printf("%s", buf);
}
fps.delay();
}
return 0;
}
3、文件的压制与解压。
1)文件的压制。
首先上代码:
#include "stdafx.h"
#include
#include
#include
#include "zlib.h"
int main()
{
FILE* fp = fopen("badapple.txt", "r");
Bytef* buf = NULL;
Bytef* src = NULL;
fseek(fp, 0, SEEK_END);
uLong flen = ftell(fp);
fseek(fp, 0, SEEK_SET);
if ((src = (Bytef*)malloc(sizeof(Bytef) * flen)) == NULL)
{
printf("no enough memory!\n");
return -1;
}
fread(src, flen, sizeof(char), fp);
uLongf blen = compressBound(flen);
if ((buf = (Bytef*)malloc(sizeof(Bytef) * blen)) == NULL)
{
printf("no enough memory!\n");
return -1;
}
if (compress(buf, &blen, src, flen) != Z_OK)
{
printf("compress failed!\n");
return -1;
}
fclose(fp);
free(src);
fp = fopen("badapple.dat", "wb"); //wb 只写打开或新建一个二进制文件;只允许写数据
fwrite(&flen, 1, sizeof(int), fp);
fwrite(buf, blen, sizeof(char), fp);
fclose(fp);
free(buf);
return 0;
}
#include "stdafx.h"
#include
#include
#include
#include "zlib.h"
using namespace std;
int main()
{
FILE* fp = fopen("badapple.dat", "rb");
uLongf slen;
fread(&slen, 1, sizeof(int), fp);
Bytef* buf = NULL;
Bytef* dst = NULL;
fseek(fp, 0, SEEK_END);
uLongf flen = ftell(fp);
fseek(fp, 4, SEEK_SET);
flen -= 4;
uLongf blen = compressBound(slen);
if ((dst = (Bytef*)malloc(sizeof(Bytef) * slen)) == NULL)
{
printf("no enough memory!\n");
return -1;
}
if ((buf = (Bytef*)malloc(sizeof(Bytef) * blen)) == NULL)
{
printf("no enough memory!\n");
return -1;
}
fread(buf, flen, sizeof(char), fp);
if (uncompress(dst, &slen, buf, blen) != Z_OK)
{
printf("uncompress failed!\n");
return -1;
}
free(buf);
fclose(fp);
for (int i = 0; i
0 TYPEDATA "badapple.dat"
1 TYPEWAV "BadApple.wav"
(2)在使用PlaySound()播放WAV声音文件没声 [问题点数:100分,结帖人asdjy123](http://bbs.csdn.net/topics/390547623?ticket=ST-104735-4AkGFsn2blcV0xeQHr6Y-passport.csdn.net)里面提到:
可以使用PlaySound(TEXT("C:\\windows\\Media\\Windows 关机.wav"),NULL,SND_FILENAME | | SND_ASYNC);做测试。
将其中的文件路径进行替换,再次尝试,终于有声音了。生成exe文件,完成。下面贴上代码:
// badapple_exe.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include
#include
#include
#pragma comment(lib, "WINMM.LIB")
#include "zlib.h"
Bytef* dst = NULL;
struct fps_limit {//刷新率设置函数
int previous_time;
int tpf_limit;
int tpf;
fps_limit(int fps = 60) : previous_time(GetTickCount()), tpf(0) {
limit_fps(fps);
}
void reset() {
previous_time = GetTickCount(),
tpf = 0;
tpf_limit = 60;
}
void limit_fps(int fps) {
tpf_limit = (int)(1000.0f / (float)fps);
}
void delay() {
tpf = GetTickCount() - previous_time;
if (tpf < tpf_limit)
Sleep(tpf_limit - tpf - 1);
previous_time = GetTickCount();
}
};
void Uncompressdata()//文件解压缩函数
{
FILE* fp = fopen("badapple.dat", "rb");
uLongf slen;
fread(&slen, 1, sizeof(int), fp);
Bytef* buf = NULL;
fseek(fp, 0, SEEK_END);
uLongf flen = ftell(fp);
fseek(fp, 4, SEEK_SET);
flen -= 4;
uLongf blen = compressBound(slen);
if ((dst = (Bytef*)malloc(sizeof(Bytef) * slen)) == NULL)
{
printf("no enough memory!\n");
exit(1);
}
if ((buf = (Bytef*)malloc(sizeof(Bytef) * blen)) == NULL)
{
printf("no enough memory!\n");
exit(1);
}
fread(buf, flen, sizeof(char), fp);
if (uncompress(dst, &slen, buf, blen) != Z_OK)
{
printf("uncompress failed!\n");
exit(1);
}
dst[slen] = '\0';
free(buf);
}
int main()
{
printf("Loading......");
Uncompressdata();
system("PAUSE");//程序暂停并清屏,按任意键继续
system("CLS");
PlaySound(TEXT("C:\\opencv\\works\\others\\badapple_exe\\badapple_exe\\BadApple.wav"), NULL, SND_FILENAME || SND_ASYNC);//修改后的调用音频代码,换成自己的音频文件地址
fps_limit fps(24);//画面刷新帧数
int i = 0, j = 0;
char buf[3000];
// Sleep(1000);//这里可以调整data文件的读取延时时间以配合音频播放
while (1)
{
HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos;
pos.X = 0;
pos.Y = 0;
SetConsoleCursorPosition(hConsoleOutput, pos);
while (j<22 * 50) //确定每页刷新出来的字符总数,这里取1100时画面稳定
{
if (dst[i] == '\0')//检测到空字符时停止跳出循环
return 0;
buf[j++] = dst[i++];
}
j = 0;
printf("%s", buf);
fps.delay();
}
}
贴出效果图:
以上就是整个程序的修改过程,虽然有现成的代码可以参考,依然在调试过程中出现了许多问题要自己查证解决,算是对自己的一次锻炼吧。