文章目录
1前言
近期学校要做OS图灵完备大作业,我在网上找资源和学习后,想要把资源也分享给大家,事先说明,文章的内容是我学习B站UP三好学生Monika的作品后自我感想,分享的资源都在百度网盘里,如果你急于完成作业,可以直接导入存档,百度网盘里有我的视频存档,图灵完备的通关存档有人发过了,你可以通过这个链接去找。
分享一下图灵完备的存档https://github.com/CoccaGuo/Turing-Complete-Saves
这个是B站UP_cocca分享的。
我的百度网盘分享:
通过网盘分享的文件:神力绫华分享的资源
链接: https://pan.baidu.com/s/1JiK6t1TqICoB-6mwbky0NQ?pwd=sllh 提取码: sllh
2实现原理
2.1显示器
先看一下显示器的原理:
视频颜色部分大家都知道吧。
在像素部分,列优先模式会比较好理解,一个8B数据的输入,我们只要看其2~7字节的数据。
第2个字节就是显示屏的第一行,以此类推。
每一个字节中的第8位是显示屏的第1列,同理第1位是第8列。
举个例子:图示:第2个字节中第8位为“1”,第7个字节中第1位为“1”,对应的位置就是(1,1)和(6,8)有颜色。
2.2电路总体结构
由于截图像素限制的原因,有些细节的地方是看不清的,等会我会放大逐一讲解。
2.3颜色的选择
它是由这些东西实现的。
2.3.1文件加载器
这是游戏沙盒模式下提供的元件,它会根据偏移量的大小,每次读取我们上传文件中的8个字节数据并输出。而这个文件很重要待会会讲。
2.3.2 xdriver自定义元件
这是我自定义的元件(xdriver),功能是为文件加载器输出偏移量。
我们先假设输入是0,也就是说它是为第1个文件加载器输出偏移量的。
转为十进制显示可能会更好理解。
图中的32位计数器,我设置每次步进量为“1”,会在每个时钟后其所存储值加1,直到32位计数器输出的值为1535时,它会被擦写,而且擦写时的输入值永远是0,也就是会被置为0,所以32位计数器输出的值范围是0~1535。
然后是上面红圈的部分。64位寄存器会在32位计数器输出1535时被写入,写入的值是它原本存储的值加上36864,所以64位寄存器输出的值会是36864的整数倍。
我们再看最终输出的部分,也就是右下角64位输出。如果我们假设输入为“x”,图中为0,那么最终输出会是:
3(1536x + (32位计数器输出) + (64位寄存器输出))
2.3.3 xd2自定义元件
这个是自定元件(xd2),功能是选出文件加载器输出的前三位进行输出,也就是3字节的RGB数据了。
2.3.4文件加载器读取的文件
我们再重点讲一下“上传文件”,也就是文件加载器读取的文件。
该文件是我们要播放的视频的像素颜色文件,我们可以这样理解
每一张图片都会有其对应的.rgb文件,视频可以拆解为多张图片的连续播放,每一个图片就是一帧,所以“上传文件”是包含视频每一帧图片RGB信息的.bin文件,下面我来详细讲解.bin文件是如何生成的。
首先,你要有你播放的原视频文件(.mp4),然后再确定播放视频的像素值,我的是144*256=36864个像素,如果你的原视频像素值不是你想要的,你可以先转化,我是用python工具调用ffmpeg实现的。
import subprocess
input_file = 'shenlilinghua.mp4'
output_file = 'output_video.mp4'
resolution = '256x144'
ffmpeg_command = [
'ffmpeg',
'-i', input_file,
'-s', resolution,
output_file
]
try:
subprocess.run(ffmpeg_command, check=True)
print("视频转换成功!")
except subprocess.CalledProcessError as e:
print(f"视频转换失败:{e}")
第二步,转化好后就要生成视频的每一帧的.rgb文件,这个我也是用python工具调用ffmpeg实现的。
import subprocess
import os
def convert_mp4_to_rgb888(input_file, output_folder):
# 检查输入文件是否存在
if not os.path.exists(input_file):
print(f"错误:输入文件 {input_file} 不存在。")
return
# 确保输出文件夹存在
if not os.path.exists(output_folder):
try:
os.makedirs(output_folder)
print(f"已创建输出文件夹: {output_folder}")
except Exception as e:
print(f"创建输出文件夹时出错: {e}")
return
# FFmpeg命令模板,指定完整路径
ffmpeg_path = 'C:\\Weican\\Software\\ffmpeg-7.0.2-full_build-shared\\bin\\ffmpeg.exe' # 请替换为你的实际路径
ffmpeg_command = [
ffmpeg_path,
'-i', input_file,
'-f', 'image2pipe',
'-pix_fmt', 'rgb24',
'-vcodec', 'rawvideo', '-'
]
# 执行FFmpeg命令并读取输出
try:
process = subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
if process.returncode != 0:
print(f"FFmpeg error: {stderr.decode()}")
return
# 写入RGB数据到文件
frame_number = 0
# 根据实际视频分辨率调整帧大小
frame_size = 256 * 144 * 3
while stdout:
frame_data = stdout[:frame_size]
stdout = stdout[frame_size:]
output_file_path = os.path.join(output_folder, f'frame_{frame_number:04d}.rgb')
with open(output_file_path, 'wb') as frame_file:
frame_file.write(frame_data)
frame_number += 1
except Exception as e:
print(f"An error occurred: {e}")
def convert_image_to_rgb888(input_image, output_file):
# 检查输入文件是否存在
if not os.path.exists(input_image):
print(f"错误:输入文件 {input_image} 不存在。")
return
# FFmpeg命令模板,指定完整路径
ffmpeg_path = 'C:\\ffmpeg\\bin\\ffmpeg.exe' # 请替换为你的实际路径
ffmpeg_command = [
ffmpeg_path,
'-i', input_image,
'-pix_fmt', 'rgb24',
output_file
]
# 执行FFmpeg命令
try:
subprocess.run(ffmpeg_command, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
print(f"图片转换成功,输出文件: {output_file}")
except subprocess.CalledProcessError as e:
print(f"FFmpeg error: {e}")
# 主程序
def main():
# 询问用户输入
user_input = input("请选择操作(输入1执行视频转换,输入2执行图片转换):")
if user_input == '1':
input_mp4 = 'shenlilinghua.mp4' # 相对路径
output_folder = 'output_rgb_frames_1' # 输出文件夹名称
convert_mp4_to_rgb888(input_mp4, output_folder)
elif user_input == '2':
input_image = input("请输入图片地址:")
output_file = 'output_image.rgb' # 输出文件名
convert_image_to_rgb888(input_image, output_file)
else:
print("无效的选择,请输入1或2。")
# 运行主程序
if __name__ == "__main__":
main()
代码文件我也会上传到百度网盘,注意直接调用我的python代码大概率会错,因为我的python虚拟解释器的环境变量没有ffmpeg的路径,我代码中是放了ffmpeg的绝对路径,就是你们拿到代码遇到问题后,问AI基本能解决的。
最后把每一帧的.rgb文件连接起来,生成.bin文件,这个我也是用python工具实现的。
import os
import glob
# 设置源文件夹和目标二进制文件的路径
source_folder = 'output_rgb_frames' # 替换为你的文件夹路径
target_bin = 'merged_content.bin' # 合并后的二进制文件名
# 使用glob模块查找所有符合命名模式的文件
files_to_merge = glob.glob(os.path.join(source_folder, 'frame_*.rgb'))
# 如果找到了文件,则打开目标二进制文件进行二进制写入
if files_to_merge:
with open(target_bin, 'wb') as outfile:
for f in files_to_merge:
try:
with open(f, 'rb') as infile: # 以二进制模式打开输入文件
# 将文件内容复制到目标二进制文件中
outfile.write(infile.read())
except Exception as e:
print(f"Error reading file {f}: {e}")
else:
print("No files found matching the pattern.")
print(f"Merged content has been written to {target_bin}")
假如你第二步做好了,你会有这些.rgb文件,你可以借助010 Editor 工具打开他们。
打开后,我们可以看一下内容。
这个frame_0000.rgb文件是我的视频的第一帧图片,里面刚好有36864个像素,里面的像素颜色值排布是根据图片从左到右、从上到下的像素确定的,连接好的.bin文件内的数据也是这样,你可以在游戏中按一个时钟刻播放视频,看一下文件加载器的输出,再回到.rgb文件里对应一下对不对。
2.3.5具体实现
知道上面这些信息后,我们就可以颜色是如何实现的了。
从整体上看:
我的颜色确定是,每个时钟刻确定24行中的一个像素的颜色,看下面的图片会好理解。
xdriver元件共有24个,只要刷新6行就可以实现一帧图片的打印(24*6=144行),我们再回看xdriver元件的输出结果,也就是文件加载器的偏移量。
3(1536x + (32位计数器输出) + (64位寄存器输出))
这个结果中1536刚好是6行乘256列的像素个数,也就是说当x为“0”时颜色的确定是从第1行开始,同理x为“1”时颜色的确定是从第7行开始,这样就实现了
第(1、7、13、19、25…)行共24行的同时颜色确定;
32位计数器输出范围是0~1535,这刚好是每六行的像素总个数,当32位计数器输出1535时,64位寄存器输出会被写入,写入的值在上面已经说明过了,我们可以这样理解,写入的值是它原本存储的值加上36864(刚好是一帧图片的总像素点),这就相当于说明,当下次64位寄存器输出时会比上一次多36864,也就实现了下一帧图片像素颜色的确定。
那为什么是当32位计数器输出1535时,64位寄存器被写入,想必你们也知道,当32位计数器输出0~1535的过程中,就实现了6行256列的像素颜色确定,而我刚好有24个xdriver
元件,就相当于实现了24个6行256列的像素颜色确定,也就是一帧图片颜色的确定,这时候要换到下一张图片颜色的确定,64位寄存器的输出刚好比之前多了36864,就多了一张图片像素个数,文件加载器就会偏移一张图片的量去输出8B的数据,最后xd2只要8B的前3B数据,也就是当前要确定的颜色值。
2.4像素坐标的确定
2.4.1 一个6*8显示器的像素坐标确定
我们可以先理解一个显示器的像素坐标是如何确定的。
先讲控制8列的
先看到蓝色圈那里,64位除法器的被除数输入是64位计数器的输出,除数输入是固定值256,(可以根据图中左上方的电路确定64位计数器的输出范围是0到36863,是一帧图片的像素点个数,在64位计数器输出36863后,它会被置为0,对应视频播放那边就是一帧已打印完,需要打印下一帧图片。)那么商的范围是0到143,余数的范围是0~255,
蓝色圈内的64位除法器的余数会作为红色圈内8位除法器的被除数输入,红色圈内8位除法器的商的范围是0~31,这个待会会说明。
红色圈内8位除法器的余数范围是0~7,余数如果输出0,那控制8列的红色方框会输出128,说明一个68的显示器的第1列的像素坐标被确定了;余数如果输出的是1,那控制8列的红色方框会输出64,说明一个68的显示器的第2列的像素坐标被确定了,以此类推。大家可以结合图来理解。
图中的64位输入中的第2、5、7个字节数据是128,对应的显示器就是第1列有颜色。
然后再是控制6行的
还记得上面蓝色圈64位除法器的商的范围是0到143,这个输出会作为图片中最上方8位除法器的被除数输入,而8位除法器的余数范围是0到5,如果8位除法器的余数是0,那就确定了68显示器的第1行的坐标,你可以看到图中此时8位除法器的余数是0,那最终的结果(图中右下角绿色线)的第2个字节会有数据,3~7字节中的其他字节会是0,也就是68显示器的第1行的坐标了,以此类推。
这样你应该理解了一个68显示器的像素坐标确定原理,紧接着要理解1行32个68显示器的像素坐标确定原理。
2.4.2 一行32个6*8显示器的像素坐标确定
还记得上面的红色圈内8位除法器的商的范围是0~31吗,后面连接的是5-32译码器。
32种输出中每一种输出保持8个时钟就可以,可以看到上面红色圈内8位除法器的除数是8,也就是说红色圈内8位除法器的被除数输入每变化8次后,5-32译码器的输出才变化1次,实际上是32种输出分别控制一行中的32个显示器。
现在我们就理解了1行32个68显示器的像素坐标确定原理,只要设计24行32个68显示器就可以实现144*256像素视频播放。
说实话啊,我讲解的有点乱,最好是有自己的理解和思路,我只是提供参考。
3总体演示
sllh-HD(图灵完备游戏播放神里绫华视频)
4总结
我实现的视频播放器是144*256像素的,1536个时钟刻为一帧的,不过总体上的延迟有80万,门数量有121万,如果你的设备cpu不支持设计的话,你可以适当优化一下我的视频播放器,也可以减少像素点个数,还有1536个时钟刻为一帧的可能太慢了,你可以实现并行刷新的视频播放器,速度可以变成256个时钟刻为一帧,还有那个你导入别人的存档后最好再去设计一下你自己的计算机,不设计也行,只要你理解了别人的计算机的原理,这样汇编代码你就懂了,自己通关就不难了。