Windows下控制台播放Badapple,opencv的使用,以及代码优化方案

news2024/10/7 6:43:16

Windows下控制台播放Badapple

环境准备

  • VS2022编译环境
  • Opencv(对图像进行灰度处理)的配置 可以看我写的这篇文章vs下opencv的配置
  • 素材(Badpple的视频文件) 可以私信我
  • FFmpeg(对视频文件进行处理) 让视频文件的声音分离出来生成mp3文件

在这里插入图片描述

ffmpeg -i test.mp4 -map 0:v:0 -c copy test_video.mp4 -map 0:a:0 -c copy test_audio.mp3

关于ffmpeg的音视频分离就到这里,不做赘述。

编写流程

该程序首先使用VideoCapture打开一个视频文件,并获取视频的特征信息,如帧数、帧率和帧大小。然后,它根据设定的抽样大小将每一帧转换为字符画,并保存到内存中。

接下来,程序使用Windows API设置控制台窗口的大小和光标位置,并隐藏光标显示。然后,它从内存中读取字符画,并在控制台中连续输出,实现视频的字符化播放效果。

最后,该程序使用mciSendString函数播放背景音乐,并通过无限循环不断播放字符化视频,直到用户手动停止程序

main()

int main(void)
{  
	Init();
	readData();
	init2();
	play();
	return 0;
}

Init()

Init() 函数主要用于设置控制台窗口的大小、隐藏光标,并调整控制台窗口的缓冲区,为后续字符化视频的播放做准备。

int Init()
{

	SetConsoleTitle(L"坏苹果-M");//设置控制台窗口的标题为 "坏苹果-M"。
	sprintf_s(cmd, sizeof(cmd), "mode con cols=%d lines=%d", width, height);//控制台窗口的大小设置为 width 列和 height 行,并将命令保存在 cmd 字符数组中
	system(cmd);//行上一步生成的命令,从而改变控制台窗口的大小
	//隐藏光标
	HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);//使用Windows API函数 GetStdHandle() 获取标准输出流的句柄,并将其保存在变量 hOut 中
	CONSOLE_CURSOR_INFO info{ 1,0 };//将其成员 dwSize 设置为 1,使光标变得不可见。
	SetConsoleCursorInfo(hOut, &info);//将 hOut 和 info 作为参数,从而隐藏控制台窗口中的光标。

	COORD co = { width,height};//将其成员 X 设置为 width,将其成员 Y 设置为 height。
	SetConsoleCursorInfo(hOut, &info);
	SetConsoleScreenBufferSize(hOut,co);		//置控制台屏幕缓冲区的大小为 co
	SMALL_RECT rc = { 0, 0, width - 1, height - 1 };	//重要点:设置缓冲,消除闪烁
	SetConsoleWindowInfo(hOut, TRUE, &rc);							//将 hOut、TRUE 和 rc 作为参数,以设置控制台窗口的大小和位置。

	if (ret == false)
	{
		printf("视频文件打开失败!\n");
		exit(-1);
	}

	if (data == NULL)
	{
		printf("内存不足!\n");
		return 1;
	}

}

这里是对代码中的 Init() 函数进行分析:

  1. 首先,该函数使用 SetConsoleTitle() 设置控制台窗口的标题为 “坏苹果-M”。
  2. 然后,通过 sprintf_s() 函数将控制台窗口的大小设置为 width 列和 height 行,并将命令保存在 cmd 字符数组中。
  3. 使用 system() 函数执行上一步生成的命令,从而改变控制台窗口的大小。
  4. 接下来,使用Windows API函数 GetStdHandle() 获取标准输出流的句柄,并将其保存在变量 hOut 中。
  5. 创建一个 CONSOLE_CURSOR_INFO 结构体变量 info,并将其成员 dwSize 设置为 1,使光标变得不可见。
  6. 调用 SetConsoleCursorInfo() 函数,将 hOutinfo 作为参数,从而隐藏控制台窗口中的光标。
  7. 创建一个 COORD 结构体变量 co,将其成员 X 设置为 width,将其成员 Y 设置为 height
  8. 再次调用 SetConsoleCursorInfo() 函数,将 hOutinfo 作为参数,从而设置控制台屏幕缓冲区的大小为 co
  9. 创建一个 SMALL_RECT 结构体变量 rc,将其成员 Left 设置为 0,将其成员 Top 设置为 0,将其成员 Right 设置为 width - 1,将其成员 Bottom 设置为 height - 1
  10. 最后,调用 SetConsoleWindowInfo() 函数,将 hOutTRUErc 作为参数,以设置控制台窗口的大小和位置。

总结起来,Init() 函数主要用于设置控制台窗口的大小、隐藏光标,并调整控制台窗口的缓冲区,为后续字符化视频的播放做准备

代码优化

int Init()
{
    SetConsoleTitle(L"坏苹果-M");
    sprintf_s(cmd, sizeof(cmd), "mode con cols=%d lines=%d", width, height);
    system(cmd);

    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_CURSOR_INFO info{ 1,0 };
    SetConsoleCursorInfo(hOut, &info);

    COORD co = { width,height };
    SetConsoleScreenBufferSize(hOut, co);
    SMALL_RECT rc = { 0, 0, width - 1, height - 1 };
    SetConsoleWindowInfo(hOut, TRUE, &rc);

    // 错误处理改为异常抛出
    if (ret == false)
    {
        throw std::runtime_error("视频文件打开失败!");
    }

    if (data == nullptr)
    {
        throw std::bad_alloc();
    }

    

   
}

视频信息的处理

VideoCapture video;	
Mat frameImg, grayImg;
bool ret = video.open("./apple.mp4");

// 读取视频特征信息
int framecount = video.get(CV_CAP_PROP_FRAME_COUNT); //视频文件的总帧数
int fps = video.get(CV_CAP_PROP_FPS); //帧率
int cols = video.get(CV_CAP_PROP_FRAME_WIDTH); //视频流中的帧宽
int rows = video.get(CV_CAP_PROP_FRAME_HEIGHT);//视频流中的帧高

// 定义抽样大小
int hSize = 10; // 每 10 行,每 5 列,转换为 1 个字符
int wSize = 5;

// 定义字符集合 
char charImgs[] = " .,-'`:!1&@#$";

int height = rows / hSize;
int width = cols / wSize;
char cmd[128];

int countpersent = 1;

// 待优化为指针数组 
int frameSize = height * (width + 1) + 1; //每行的末尾有一个回车符
char* data = (char*)malloc(sizeof(char) * framecount * frameSize);

readdata()

void readData()
{
	sprintf_s(cmd, sizeof(cmd), "mode con cols=%d lines=%d", 22, 4);//
	system(cmd);//设置控制台大小
	 
		for (int n = 0; n < framecount; n++)
		{
				char* p = data + n * frameSize; video.read(frameImg); // 转换图片的色彩,这里转换为灰度效果
				cvtColor(frameImg, grayImg, COLOR_BGR2GRAY);//opencv 函数灰度图片
				string s = "";
				int k = 0;
				for (int row = 0; row < rows - hSize; row = row + hSize)
				{
						for (int col = 0; col < cols - wSize; col = col + wSize)
							 
						{
							int value = grayImg.at<uchar>(row, col);
							p[k++] = charImgs[int(value / 20)];	
						}
							p[k++] = '\n';
						
				}
							p[k++] = 0;
							 system("cls");
            printf("正在读取:%d / %d", n + 1, framecount);  					 						 						 													 				 						
		}	 	 
	mciSendString(L"play apple.mp3 repeat", 0, 0, 0);
			 
}

  1. 使用 sprintf_s() 函数将命令字符串 cmd 格式化为设置控制台窗口大小和行数的命令。
  2. 使用 system() 函数执行上述生成的命令,从而改变控制台窗口的大小和行数。
  3. 在每一帧循环中,首先声明一个指针 p 并将其初始化为指向当前帧的字符画数据的位置。
  4. 使用 video.read(frameImg) 从视频读取一帧图像,并将其转换为灰度图像。
  5. 在双重循环中,将灰度图像中的像素值转换为字符,并将字符保存到指针 p 指向的位置。每个字符的值通过除以20来映射到 charImgs 数组中。
  6. 在内层循环结束后,添加换行符 \n 到字符画中,以分隔每一行的字符。
  7. 在外层循环结束后,添加空字符 \0 到字符画末尾,以标识字符画的结尾。
  8. 使用 system("cls") 清空控制台屏幕。
  9. 使用 printf() 打印正在读取的帧数和总帧数。
  10. 最后,使用 mciSendString() 函数播放背景音乐 “apple.mp3” 并设置循环播放。

代码优化

void readData()
{
	sprintf_s(cmd, sizeof(cmd), "mode con cols=%d lines=%d", 22, 4);
	system(cmd);

	// 预分配内存
	char** data = new char*[framecount];
	for (int n = 0; n < framecount; n++)
	{
		data[n] = new char[frameSize];
	}

#pragma omp parallel for
	for (int n = 0; n < framecount; n++)
	{
		char* p = data[n];

		Mat frameImg;
		video.read(frameImg);
		cvtColor(frameImg, grayImg, COLOR_BGR2GRAY);

		string s = "";
		int k = 0;
		for (int row = 0; row < rows - hSize; row = row + hSize)
		{
			for (int col = 0; col < cols - wSize; col = col + wSize)
			{
				int value = grayImg.at<uchar>(row, col);
				p[k++] = charImgs[int(value / 20)];
			}
			p[k++] = '\n';
		}
		p[k++] = 0;

#pragma omp critical
	{
		system("cls");
		printf("正在读取:%d / %d", n + 1, framecount);
	}

	}

	mciSendString(L"play apple.mp3 repeat", 0, 0, 0);

	// 释放内存
	for (int n = 0; n < framecount; n++) {
		delete[] data[n];
	}
	delete[] data;
}

这里使用了OpenMP来并行处理每一帧的转换过程,以提高性能。使用#pragma omp parallel for将循环并行化,从而使每个线程独立处理一帧的转换操作。

另外,内存分配和释放的操作被提到循环外部,通过预分配内存避免了在每一帧循环中的重复动态内存分配和释放操作。最后,在关键的控制台输出部分使用了临界区(#pragma omp critical)来保证多个线程之间的输出不会互相干扰。

这些优化措施可以显著提高程序的运行速度。然而,请注意在实际应用中进行测试和评估,以确保其在特定环境下的有效性。

Init2()

void init2()
{
	sprintf_s(cmd, sizeof(cmd), "mode con cols=%d lines=%d", width, height);
	system(cmd);
}

paly()

函数优化

void play()
{
    for (int i = 0; i < framecount; i++)
    {
        char* p = data + i * frameSize;
        SetConsoleCursorPosition(h, pos); // 跳转控制台光标位置
        
        // 使用 WriteConsoleOutputCharacterA() 直接写入控制台屏幕缓冲区
        DWORD written;
        WriteConsoleOutputCharacterA(h, p, frameSize , pos, &written);
        
        Sleep(1000 / fps);
    }
}

在优化后的代码中:

使用 WriteConsoleOutputCharacterA() 函数直接将帧数据 p 写入控制台屏幕缓冲区,而不是使用 printf() 函数。这样可以提高输出的效率。

删除了外部的 while 循环,因为循环内的逻辑已经包含了需要重复播放的功能。每次迭代都会播放一帧,并通过 Sleep() 函数等待 1000 / fps 毫秒,以模拟视频的帧率。

请注意,在使用该函数之前,请确保已经定义和初始化了相关变量(如 framecountdataframeSizehpos),并且已经设置了合适的 fps 值。

指针数组优化

	// 待优化为指针数组 
	int frameSize = height * (width + 1) + 1; //每行的末尾有一个回车符
	char* data = (char*)malloc(sizeof(char) * framecount * frameSize);

要将上述代码优化为使用指针数组,您需要进行以下修改:

  1. 修改 data 的声明:将 char* data 改为 char** data,用于存储指向字符画的指针。
  2. 动态分配内存给 data:替换 char* data = (char*)malloc(sizeof(char) * framecount * frameSize);char** data = new char*[framecount],这将创建一个指针数组,每个指针指向一帧的字符画。
  3. 在循环中为每帧分配内存并保存字符画:在 for (int n = 0; n < framecount; n++) 循环开始之前添加 data[n] = new char[frameSize]; 来为每一帧分配内存。
  4. 将字符画保存到对应的指针位置:在 char* p = data + n * frameSize; 处将 p 更改为 char* p = data[n];
  5. 在程序结束前释放内存:在 main() 函数的最后使用循环来释放为每帧字符画分配的内存,例如:
for (int n = 0; n < framecount; n++) {
    delete[] data[n];
}
delete[] data;

通过上述修改,您将能够将字符画以指针数组的形式保存,并正确释放内存。请记得适时进行错误处理和异常处理。

需要代码以及素材或者我的opencv可以私信我

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/740368.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL用户权限管理和密码策略

目录 用户和权限介绍 密码策略 修改密码策略 用户权限管理 赋予权限 收回权限 用户和权限介绍 所有用户的信息都保存在mysql.user 数据表中 可使用desc 查看数据表结构 user 常用字段 部分字段解析 完整的账户 包含host和user&#xff0c;都是char型 都没有默认值 ho…

fork函数,为什么有两次返回?

前言 fork函数用于创建一个新进程&#xff0c;称为子进程&#xff0c;它与旧进程&#xff08;称为系统调用fork的进程&#xff09;同时运行&#xff0c;此进程称为父进程。 创建新的子进程后&#xff0c;两个进程将执行fork()系统调用之后的下一条指令。子进程使用相同的pc&a…

网络传输层协议详解(TCP/UDP)

目录 一、TCP协议 1.1、TCP协议段格式 1.2、TCP原理 确认应答机制 超时重传机制 (安全机制) 连接管理机制(安全机制) 滑动窗口 流量控制(安全机制) 拥塞控制 延迟应答(效率机制) 捎带应答&#xff08;效率机制&#xff09; ​编辑面向字节流(粘包问题) 缓…

[pyqt5]如何给工具栏图标添加资源文件

第一步新建一个qrc文件 首先新建一个 .qrc 文件&#xff0c;内容格式如下&#xff1a; <RCC><qresource prefix"/" ><file>img/image1.png</file><file>img/image2.png</file><file>img/image3.png</file></qr…

解决win11系统下vivado使用RTL分析闪退、小蓝熊easy anti chat无法启动问题

最近在接触使用vivado时被这个软件庞大的包体和繁多的报错搞得焦头烂额&#xff0c;经过多次尝试&#xff0c;我解决了两个困扰我许久的关乎软件正常使用的问题&#xff0c;将解决办法分享给大家。 一.RTL analysis运行闪退 这个问题关系到程序的正常使用&#xff0c;主要发生…

css animation动画

代码 <!DOCTYPE html> <html ng-app"my_app"><head><meta charset"utf-8"><title></title><script type"text/javascript"></script><style type"text/css">.my-animation {p…

关于海外的应用商店优化

应用商店优化的工作方式与搜索引擎优化 (SEO) 类似&#xff0c;但它有专门为应用商店设计的专业功能。应用商店优化可以帮助应用程序出现在应用商店的顶部结果中&#xff0c;从而提高下载转化率和应用安装量&#xff0c;并且对于我们的应用也有长期可见性。 应用商店优化是一个…

本地用户管理(NETBASE第九课)

本地用户管理(NETBASE第九课) 本地用户&#xff1a;用户本地创建、本地存储、本地登录且只能登录本地一台计算机。 1.认识用户帐户&#xff1a; 1&#xff09;Windows帐户有帐户名、有密码、用户安全标识&#xff08;SID&#xff09; 注:SID安全标识符&#xff08;身份证号…

数据结构-二叉树的代码实现(详解)

内容&#xff1a;二叉树的前、中&#xff0c;后序遍历&#xff0c;层序遍历&#xff0c;二叉树节点个数&#xff0c;叶子节点个数&#xff0c;二叉树高度&#xff0c;第k层节点的个数&#xff0c;查找某个节点&#xff0c;二叉树销毁&#xff0c;判断是否为完全二叉树 目录 前…

科研热点|《电气工程领域高质量科技期刊分级目录(2022版)》公示!

为贯彻落实《关于深化改革 培育世界一流科技期刊的意见》&#xff0c;推进国内外科技期刊的同质等效使用&#xff0c;助力我国科技期刊高质量发展&#xff0c;在中国科协的统一部署下&#xff0c;中国电工技术学会制定《电气工程领域高质量科技期刊分级目录发布工作实施方案&am…

【HarmonyOS】ArkTS学习之页面转场动画的实现

【关键字】 ArkTS、转场动画、PageTransitionEnter、PageTransitionExit 【介绍】 动画交互的实现是一种提升用户体验的方式。动画分为属性属性动画、显示动画、转场动画和路径动画&#xff0c;而转场动画又包含页面间转场、组件内转场和共享元素转场。我觉得页面转场动画挺有趣…

电机的调压调速和PWM调速方法的性能比较、应用场景简介

电机的调压调速和PWM调速是两种不同的电机控制方式。 调压调速&#xff1a;调压调速是通过改变电机输入电压的大小来控制电机的转速和扭矩。通过调节电压&#xff0c;可以改变电机的转矩特性和转速。这种调速方式适用于直流电机和某些类型的交流电机&#xff0c;如感应电动机。…

Windows电源模式(命令行)

一、简介 windows使用powercfg.exe来控制电源方案,像cmd.exe一样,powercfg.exe也是windows自带的。 powercfg命令行选项 选项说明/?、-help显示有关命令行参数的信息。/list、/L列出所有电源方案。/query、/Q显示电源方案的内容。

华为云CodeArts DevSecOps系列插件——助力更高效的软件研发

HDC期间入驻华为云&#xff0c;可参与Toolkit插件抽奖活动&#xff0c;活动链接在文末 一、前言 DevOps的概念想必大家都不陌生&#xff0c;它是一组过程、方法与系统的统称&#xff0c;通过它可以对交付速率、协作效率、部署频率速率、质量、安全和可靠性等进行提升改善。相比…

Linux——信号发送代码

目录 一.信号集操作函数 sigpromask函数 代码详解&#xff1a; ​编辑 考虑情况一&#xff1a;对多个信号的屏蔽 考虑情况二&#xff1a;取消对信号的阻塞 运行结果&#xff1a; 考虑情况三&#xff1a;若不想让进程退出&#xff0c;采用自定义捕捉信号方式&#xff01;…

Java阶段五Day02

Java阶段五Day02 文章目录 Java阶段五Day02MAVEN-聚合(多模块3)回顾多模块2个特性依赖:继承: 聚合场景聚合目的实现聚合聚合总结 远程仓库远程仓库概念配置settings.xml远程库配置注意事项 GIT详细学习git概括git历史本地版本控制相关命令git分支管理分支管理基本概念分支管理相…

axios请求错误处理Uncaught runtime errors:handleError@webpack-internal:///./node_modules/webpack-dev-server

项目环境&#xff1a; vue3.2.13 vue-cli5.0.0 注&#xff1a;该问题只是出现在开发环境&#xff0c;打包后是不会出现的。 问题描述&#xff1a; 由于需要新开一个开发项目&#xff0c;引入必备网络请求axios及他的副手&#xff08;集中处理请求及返回数据&#xff09;&a…

springboot+MySQL大学生体质测试管理系统

功能需求分析的任务是通过详细调查大学生体质测试的测试信息管理系统要处理的所有对象&#xff0c;通过充分了解大学生体质测试管理系统的工作流程&#xff0c;明确使用者的各种需求&#xff0c;充分思考之后可能扩充和改变的情况&#xff0c;然后在这个基础上来设计数据库。

C#(五十九)之三种数据结构 stack queue sortedList

堆栈&#xff08;Stack&#xff09;&#xff1a; 代表了一个后进先出的对象集合。当您需要对各项进行后进先出的访问时&#xff0c;则使用堆栈。当您在列表中添加一项&#xff0c;称为推入元素&#xff0c;当您从列表中移除一项时&#xff0c;称为弹出元素。 Stack 类的方法和…

UVM中transaction中数据持续时间的控制

一、代码 首先需要确认持续时间是在driver中进行控制 其中data_size就是数据的持续时钟周期个数