图灵完备(Turing Complete)144*256像素1536个时钟刻为一帧的视频播放器

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个时钟刻为一帧,还有那个你导入别人的存档后最好再去设计一下你自己的计算机,不设计也行,只要你理解了别人的计算机的原理,这样汇编代码你就懂了,自己通关就不难了。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值