一直看网上各种大神做的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 <opencv2\opencv.hpp>
#include <opencv2/video/video.hpp>
#include "highgui.h"
#include <iostream>
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");//指定保存路径与存储名称,存储格式为.BMPimwrite(imagename, frame);//保存图像}capture.release();return 0;
}

从而生成出了5000多幅BMP图像,有点庞大... 继续第二步。

2、图像转制为字符文档。

1)图像的转制。

后面的代码就主要参考这个帖子了(先膜拜下):写一个自己的C++控制台字符画版BadApple!!(http://www.cnblogs.com/CodeMIRACLE/p/5508236.html)。将上一步制作好的图像文件夹放入这一步的项目文件夹下,闲话少叙,上代码。

#include "stdafx.h"
#include <cstdio>
#include <cstring>
#include <stdint.h>
#include <windows.h>
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 <cstdio>
#include <windows.h>
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 <stdlib.h>
#include <string.h>
#include <cstdio>
#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;
}
里面需要注意的是使用了"zlib.h"的库,这个需要另外下载配置,下载链接贴出:http://zlib.net/。库的配置方法与其他无异,可以参考Visual Studio下C++第三方库的配置方法总结(http://qiusuoge.com/12233.html)尝试几次应该问题不大。还要注意到的是其中几个压缩函数(compress()、compressBound()等)的参数类型大概由于版本的原因,都做了相应的修改。运行成功后生成"badapple.dat"文件。
        2)文件的解压缩。
        这一步可以作为最后一步exe文件生成的预调试,其本身可不作为整个项目的必备步骤。同样先上代码,内容也比较简短,不做过多注释。
#include "stdafx.h"
#include <cstdio>
#include <iostream>
#include <cstdlib>
#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<slen; i++)printf("%c", dst[i]);
}
        4、exe文件的生成。
        这一步由于是参考别人的代码进行修改,因此走了一些弯路,这里也记录下吧。
        原文中使用的方法是将数据文件"badapple.dat"与音频文件"BadApple.wav"作为资源文件,制作rc文件并转换为res文件加入到项目中。因此首先要做文件转换,先下载了windres.exe执行程序(http://www.zhaodll.co/W/20130707/235684.html),放入C:\Windows\SysWOW64\与C:\Windows\System32文件夹。随后创建source.rc文件,里面加入命令行:
0 TYPEDATA "badapple.dat"
1 TYPEWAV "BadApple.wav"
        然而在项目文件目录下打开命令窗口,执行"windres.exe -i source.rc -o source.res" 随后又提示“'gcc'不是内部或外部命令,也不是可运行的程序或批处理文件”。网上搜索后是需要安装MinGW,一个将GCC编译器和GNU Binutils移植到Win32平台下的程序,(下载链接https://sourceforge.net/projects/mingw/files/)。这一步完成后重新执行windres命令,依然出现了问题。提示:windres.exe: can't open file `TYPEWAV': No such file or directory。这一关实在是过不去了,尝试网上搜索到的各种方法,都没能解决。同时也尝试重新向VS项目中加入资源的形式加入两个文件,都出现了问题。
        因此我思考能不能跳过这一步直接获取两个文件,毕竟其中的代码也不过是将数据文件的解压缩。因此我参考第3步1)的代码重新做了修改,可以获取"badapple.dat"文件了!当然刷新的帧率还需要做调整,可以参考注释。
        而此时音频文件依然不能获取到,并提示错误“error LNK2001: 无法解析的外部符号 __imp__PlaySoundW”,继续网上找解决方案,直到找到这两篇提问中的回答。
           (1)error LNK2019: 无法解析的外部符号 __imp__PlaySoundW@12,该符号在函数 "long __stdcall WndProc(str[问题点数:40分,结帖人QQadw](http://bbs.csdn.net/topics/370042779),里面提到:
    使用PlaySound函数时需要在#include<windows.h>后面加上(注意:不能加在前面):
  #include <mmsystem.h>
  #pragma comment(lib, "WINMM.LIB") 

这样就解决了PlaySound()函数的调用问题。

(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 <cstdio>
#include <windows.h>
#include <mmsystem.h>
#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();}
}

贴出效果图:

以上就是整个程序的修改过程,虽然有现成的代码可以参考,依然在调试过程中出现了许多问题要自己查证解决,算是对自己的一次锻炼吧。

BadApple个人试制,大神勿喷。相关推荐

  1. 汉诺塔_java_大神勿喷

    汉诺塔_java_大神勿喷 问题描述 问题分析 代码实现 1.通项公式法 2.递推法 3.递归打印步骤 问题描述 汉诺塔是一个古老的数学问题: 有三根杆子A,B,C.A杆上有N个(N>1)穿孔圆 ...

  2. 谈C++求a+b(大神勿喷)

    刚学C++,发现C++的输入输出不再是printf与scanf,而是cout和cin,下面我由一道题来详细说明一下! 参照hduoj第1089题:求a+b的和. #include <iostre ...

  3. 【易语言作品】夜猫护眼宝 PC版 大神勿喷,语言是国产的易语言!

    可保护您长时间或夜晚坐在电脑桌前,给您最大的眼睛保护!夜猫护眼宝电脑版是一款蓝光过滤软件.通过该软件,可以有效降低电脑辐射,减少对眼睛的伤害,是专为办公人员.游戏玩家等长时间对着电脑人群而设计.根据时 ...

  4. 如何做一段简单的视频(大神勿入系列)?

    文章目录 1. 为什么要做一段视频? 2. 原则 3. 你需要一个剪辑(cut)软件 3.1 剪辑窗口的认知 4. 你的素材来源 4.1 关于录像 4.2关于录屏 4.3 关于录音 4.4 关于BGM ...

  5. Linux面试试题宝典,你能打多少分?大神勿进

    linux面试宝典(1) 一.选择题 1. Linux系统中DNS服务进程名为 (  ) A.named  B.httpd  C.ftpd  D.SysLog 2.在UINX/Linux中,系统Roo ...

  6. android go 刷机教程,所有安卓手机详细 线刷 卡刷教程!看完小白变大神!

    安卓手机通病 越用越卡 !刷机成为了唯一解决办法!今天教大家刷机 就算你没用过别的品牌手机 看玩基本也就会了!大神勿喷! 卡刷: 首先进入re模式 方法:上下电源键选择 电源键或者home键确定 小米 ...

  7. C++ 大神 John Carmack:我要去搞通用 AI 了,勿念!

    今天,VR 大神.Oculus CTO John Carmack 在 Facebook 上发布了一条动态,他表示,接下来他将要挑战自己,投入通用 AI 的研究中. 原文大意如下: 从本周开始,我将转到 ...

  8. 大神带飞————动态生成对象并绑定父对象(绑定对象池中的对象使自己成为对象池中对象的子对象)

    实例代码 using System.Collections; using System.Collections.Generic; using UnityEngine; public class New ...

  9. java中使用ElasticSearch聚合查询代码实例(个人笔记,不喜勿喷)

    PS→无奈:拥有梦想只是一种智力,实现梦想才是一种能力. 空闲之余就把先前做过的一个通过ElasticSearch的聚合来实现查询当月实际签到的天数记录一下,纯留下点记忆,大家勿喷. 1.如果不了解e ...

最新文章

  1. c语言随机函数五位数,【分享代码】弥补c语言随机数不足
  2. android monitor 汉化
  3. 微信小程序底部导航Tabbar
  4. C++五子棋(一)——开发环境
  5. vivado中的rtl中电路图无发生成_FPGA零基础学习:数字电路中的组合逻辑
  6. Javascript 問題汇总(不定期更新)【一】
  7. h5 兑换商品 页面模版_H5页面制作工具编辑功能对比:木疙瘩、微吾、云
  8. Spring Boot (16)---优雅的入门篇
  9. 【编译工具】之gcc中-march与-arch的使用
  10. 2021年中国余热回收锅炉市场趋势报告、技术动态创新及2027年市场预测
  11. html上传图片(进度条变化)、音乐
  12. java heapdump 分析工具_Java Heap dump文件分析工具jhat简介
  13. 树状数组的理解以及简单应用
  14. 机器纪元中文字幕(Automata.2014.720p.WEB-DL.DD5.1.H264-RARBG.srt)
  15. php不能上传doc文件,PHPCMS不能上传WORD、EXCEL等文件的问题
  16. 二阶线性微分方程解的结构(齐次与非齐次)+ 常数变易法 | 高阶微分方程(二)
  17. 数据库系统概论第五版(第 3 章 关系数据库标准语言SQL)习题答案
  18. 华为P30pro手机电路图
  19. linux kvm 桥接模式,kvm的网络桥接模式与快照管理介绍
  20. python数据分析基础教程 numpy 2pdf_Python数据分析基础教程:NumPy学习指南(第2版)...

热门文章

  1. c:out标签怎么是使用?
  2. AndroidHook机制——Hook动态代理实现插件化
  3. 小日期时间型_货币基金破2%?稳健型选手们来瞧瞧这类基金咯!
  4. 【unity shader案例】如何实现一个玻璃效果
  5. c语言 指数 2 n,c语言中指数函数中求2的N次方输出2^n中n为常数
  6. linux文件系统基础--VFS中的file、dentry和inode--讲得非常透的一篇文章
  7. [软件工具][windows]pdf二维码条形码识别重命名工具使用教程
  8. 如何删除指定列中包含重复值的行
  9. 使用高效的MCUs开发强大的事件驱动视频监控系统
  10. 剑网3手游微信第一个服务器,剑网3手游一开服就有大批网友涌入,不过服务器质量令人唏嘘...