基于FireBeetle 2 实现 VGA视频输出

之前有使用 FireBeetle(ESP32)实现过 VGA 输出,这次基于 FireBeetle 2 ESP32-S3 的板卡来实现 VGA 的输出。

首先进行电路的设计,其中的Hsync和Vsync分别使用 IO1 和 IO2,颜色则通过 R-2R 来实现:

PCB 设计如下:

3D预览如下:

图中的USB母头是预留给外部供电的,比如,VGA-HDMI转接器通常需要外部供电,这种情况下可以从这个端口取电。

焊接之后插入 FireBeetle 2 的板子(注意USB接口方向和 VGA接口方向相反):

接下来使用来自 https://github.com/spikepavel/ESP32-S3-VGA 的库

运行如下代码

#include "VGA.h"
#include <FONT_9x16.h>

VGA vga;

int scale = 2;

void setup()
{
//                   r,r,r,r,r,  g,g, g, g, g, g,   b, b, b, b, b,   h,v
const PinConfig pins(4,5,6,7,8,  9,10,11,12,13,14,  15,16,17,18,21,  1,2);
	Mode mode = Mode::MODE_640x480x60;
	
	if(!vga.init(pins, mode, 8, 3)) while(1) delay(1);
	
	vga.start();

	for(int y = 0; y < 480; y++)
		for(int x = 0; x < 640; x++)
			vga.dotdit(x, y, x, y, 255-x);

	vga.setFont(FONT_9x16);
	vga.start();
	
	delay(5000);
}

void loop() 
{
vga.clear(0);
delay(1000);
for(int count = 0; count < 100000; count++)
vga.dot(rand()%640, rand()%480, rand()%255);
delay(1000);

vga.clear(0);
delay(1000);
for(int count = 0; count < 10000; count++)
vga.line(rand()%640, rand()%480, rand()%640, rand()%480, rand()%255);
vga.clear(0);
delay(1000);

vga.clear(0);
delay(1000);
for(int count = 0; count < 10000; count++)
vga.tri(rand()%640, rand()%480, rand()%640, rand()%480, rand()%640, rand()%480, rand()%255);
vga.clear(0);
delay(1000);

vga.clear(0);
delay(1000);
for(int count = 0; count < 1000; count++)
vga.fillTri(rand()%640, rand()%480, rand()%640, rand()%480, rand()%640, rand()%480, rand()%255);
vga.clear(0);
delay(1000);

vga.clear(0);
delay(1000);
for(int count = 0; count < 10000; count++)
vga.rect(rand()%640, rand()%480, rand()%640, rand()%480, rand()%255);
vga.clear(0);
delay(1000);

vga.clear(0);
delay(1000);
for(int count = 0; count < 1000; count++)
vga.fillRect(rand()%640, rand()%480, rand()%640, rand()%480, rand()%255);
vga.clear(0);
delay(1000);

vga.clear(0);
delay(1000);
for(int count = 0; count < 10000; count++)
vga.circle(rand()%640, rand()%480, rand()%100, rand()%255);
vga.clear(0);
delay(1000);

vga.clear(0);
delay(1000);
for(int count = 0; count < 5000; count++)
vga.fillCircle(rand()%640, rand()%480, rand()%50, rand()%255);
vga.clear(0);
delay(1000);

vga.clear(0);
delay(1000);
for(int count = 0; count < 10000; count++)
vga.ellipse(rand()%640, rand()%480, rand()%100, rand()%100, rand()%255);
vga.clear(0);
delay(1000);

vga.clear(0);
delay(1000);
for(int count = 0; count < 1000; count++)
vga.fillEllipse(rand()%640, rand()%480, rand()%100, rand()%100, rand()%255);
vga.clear(0);
delay(1000);

vga.clear(0);
delay(1000);
for(int count = 0; count < 100000; count++)
vga.mouse(rand()%640, rand()%480);
vga.clear(0);
delay(1000);

for(int count = 0; count < 1000; count++)
{
	static int c = 0;
	static int d = 1;
	c += d;
	if (c == 0 || c == 255)
		d = -d;

	char text[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 0, 0, 0, 0, 0
				  };
	
	for (int i = 0; i < 256; i++)
		text[i] = 33 + (i + (c >> 2));
	vga.setCursor(8, 48);
	vga.setTextColor(vga.rgb(c, 255 - c, 255), vga.rgb(0, c / 2, 127 - c / 2));
	vga.print(text);
	vga.setCursor(8, 148);
	vga.print(text);
	vga.setCursor(8, 248);
	vga.print(text);
	vga.setCursor(8, 348);
	vga.print(text);
}
 delay(4000);
}

实践测试发现,这个板卡在不同显示器上会有兼容性问题,可能和选择的分辨率及其参数有关系。

工作的测试视频【FireBeelte 2 ESP32-S3 VGA 输出测试】 https://www.bilibili.com/video/BV1yrzjY4EmG/?share_source=copy_web&vd_source=5ca375392c3dd819bfc37d4672cb6d54

Three实现旋转地球效果的例子

本文根据 https://blog.csdn.net/m0_51180924/article/details/144498003 编写,实现了旋转地球的效果。

代码如下,如果想测试需要本地补充three.js,jquery.js和CCapture.all.min.js,在【参考1】文章末尾可以下载到(示例代码保存为 html 放入带有 JS文件的目录即可)

<html>

<head>
    <meta charset="UTF-8">
    <!-- <script src="http://threejs.org/build/three.js"></script>-->
    <!-- <script src="http://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>-->
    <script src="ThreeJS/three.js"></script>
    <script src="ThreeJS/jquery.js"></script>
    <script src="CCapture/CCapture.all.min.js"></script>
</head>

<body>
    <center id="myContainer"></center>
    <script>
        //创建渲染器
        var myRenderer = new THREE.WebGLRenderer({
            antialias: false,
            preserveDrawingBuffer: true
        });
        document.body.appendChild(myRenderer.domElement);
        myRenderer.setPixelRatio(window.devicePixelRatio);
        myRenderer.setSize(window.innerWidth, window.innerHeight);
        $("#myContainer").append(myRenderer.domElement);
        var myClock = new THREE.Clock();
        var myCamera = new THREE.PerspectiveCamera(60,
            window.innerWidth / window.innerHeight, 1, 10000);
        myCamera.position.set(0, 100, 300);
        myCamera.lookAt(new THREE.Vector3(0, 0, 0))
        var myScene = new THREE.Scene();

        myScene.background = new THREE.Color('white');
        myScene.add(new THREE.AmbientLight(0xFFFFFF, 1.0));

        //创建球体(地球)
        var myGeometry = new THREE.SphereBufferGeometry(120, 64, 64);
        var myMap = new THREE.TextureLoader().load('image/img007.png');
        var myMaterial = new THREE.MeshPhongMaterial({
            map: myMap
        });
        var myMesh = new THREE.Mesh(myGeometry, myMaterial);
        myScene.add(myMesh);
        let isAnimating = true; // 动画状态标志

        // 初始化捕获器(以 WebM 格式为例)
        const capturer = new CCapture({
            format: 'webm',
            framerate: 30,
            verbose: true
        });

        // 开始捕获
        capturer.start();

        //渲染(旋转)球体(地球)
        animate();


        function animate() {
            if (!isAnimating) return; // 停止动画时退出循环
            requestAnimationFrame(animate);
            var delta = myClock.getDelta();
            myRenderer.render(myScene, myCamera);
            //按照设置的角度增量实现绕y轴旋转地球
            myMesh.rotation.y += delta / 5;

            // 捕获当前帧
            capturer.capture(myRenderer.domElement);

            // 旋转超过一圈(2π)时停止
            if (myMesh.rotation.y >= Math.PI * 2) {
                isAnimating = false; // 停止动画
                console.log("旋转完成!");
                capturer.stop();
                capturer.save();
            }
        }
    </script>
</body>

</html>

最终完整的代码下载:

工作的视频

参考:

1.https://www.lab-z.com/cthc/

CCapture 录制 ThreeJs的例子

使用 ThreeJs 制作一个旋转的立方体,然后录制它沿着Z轴旋转一圈。代码如下:

	<html>
		<head>
			<meta charset="UTF-8">
			<!-- <script src="http://threejs.org/build/three.js"></script>-->
			<!-- <script src="http://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>-->
			<script src="ThreeJS/three.js"></script>
			<script src="ThreeJS/jquery.js"></script>
			<script src="CCapture//CCapture.all.min.js"></script>
		</head>
		<body>
			<script>
				// 初始化场景
				const scene = new THREE.Scene();
				const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
				const renderer = new THREE.WebGLRenderer({
					antialias: true,
					preserveDrawingBuffer: true
				});

				// 设置渲染器
				renderer.setSize(window.innerWidth, window.innerHeight);
				document.body.appendChild(renderer.domElement);

				// 创建红色立方体
				const geometry = new THREE.BoxGeometry(2, 2, 2);
				const material = new THREE.MeshBasicMaterial({
					color: 0xff0000,
					transparent: true,
					opacity: 0.8
				});
				const cube = new THREE.Mesh(geometry, material);

				// 添加绿色边框
				const edges = new THREE.EdgesGeometry(geometry);
				const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({
					color: 0x00ff00
				}));

				// 组合对象
				const cubeWithEdges = new THREE.Group();
				cubeWithEdges.add(cube);
				cubeWithEdges.add(line);
				scene.add(cubeWithEdges);

				// 设置相机角度
				camera.position.set(5, 5, 5); // 45°倾斜视角[^1]
				camera.lookAt(0, 0, 0);

				// 初始化捕获器(以 WebM 格式为例)
				const capturer = new CCapture({
				  format: 'webm',
				  framerate: 30,
				  verbose: true
				});
			
				// 开始捕获
				capturer.start();
				let cap=true;

				console.log("start");
				animate();

				// 动画循环
				function animate() {
					if (cubeWithEdges.rotation.z<Math.PI * 2) {
						requestAnimationFrame(animate);
						// Z轴旋转
						cubeWithEdges.rotation.z += 0.01;
						renderer.render(scene, camera);
						// 捕获当前帧
						capturer.capture(renderer.domElement);
					} else {
						if (cap==true) {
							capturer.stop();
							capturer.save();
							cap=false;
						}
					}

				}
	
			</script>
		</body>
	
	</html>

上述代码生成的视频:

完整的代码和JS文件下载:

FireBeetle(ESP32)测试 HT513

HT513 是一款国产的D类单声道I2S 功放芯片。特别之处在于它支持通过 I2C 接口控制输出音量,因此可以在保证输出效果的情况下极大简化软件设计。

首先设计一个 HT513的功能板:

PCB设计如下:

焊接完成后,可以直接在面包板上使用。

这次测试DFRobot 的 FireBeetle 通过 I2S 接口发送音频数据给 HT513, 最终通过喇叭将音频播放出来:

HT513模块FireBeetle说明 说明FireBeetleHT513模块
GNDGND数字电源3V3VCC33
SD#N/A接地关闭功放GNDGND
SDAIO21I2C 数据芯片错误N/AFAULT
SCLIO22I2C 时钟模拟参考电压N/ABYPASS
MCKIO0I2S主时钟功放负输出N/ALOUTN 接喇叭
BCLK14I2S串行时钟GNDGND
DIN18I2S数据功率电源VCCPVDD
LRCLK15帧时钟功放正输出N/ALOUTP 接喇叭

测试使用 AudioTools 库(arduino-audio-tools-1.0.0)。特别注意: HT513 工作时需要 MCLK 信号,因此需要修改库文件 AudioConfig.h:

#define PWM_FREQENCY 30000
#define PIN_PWM_START 12
#define PIN_I2S_BCK 14
#define PIN_I2S_WS 15
#define PIN_I2S_DATA_IN 32
#define PIN_I2S_DATA_OUT 18
#define PIN_I2S_MCK 0
#define I2S_USE_APLL true
// Default Setting: The mute pin can be switched actovated by setting it to a gpio (e.g 23). Or you could drive the LED by assigning LED_BUILTIN
#define PIN_I2S_MUTE -1
#define SOFT_MUTE_VALUE 0
#define PIN_CS SS
#define PIN_ADC1 34 

测试的代码是基于这个库自带的两个例程,一个是生成正弦波送至I2S接口(streams-generator-i2s),我们在这里测试评估 HT513音量调整的功能;另外一个是播放存储在 Flash 中的WAV文件(streams-memory_raw-i2)。在例子的寄存上增加对于 HT513初始化设定的代码。关键部分如下:

#define HT513_ADDR_L 0x6c
/**
   @brief  ht513写寄存器
   @param  addr 寄存器地址
   @param  val 要写的值
   @retval None
*/
void HT513_WriteOneByte(uint8_t addr, uint8_t val)
{
  Wire.beginTransmission(HT513_ADDR_L);
  Wire.write(addr);
  Wire.write(val);
  Wire.endTransmission(true);
}

/**
   @brief  ht513读寄存器
   @param  addr 寄存器地址
   @retval 读取到的寄存器值
*/
uint8_t HT513_ReadOneByte(uint8_t addr)
{
  uint8_t temp = 0;

  Wire.beginTransmission(HT513_ADDR_L);
  Wire.write(addr);
  Wire.endTransmission(true);

  Wire.requestFrom(HT513_ADDR_L, (uint8_t)1);
  temp = Wire.read();

  return temp;
}

在Setup() 中加入下面的代码:

Wire.begin(21,22); 
  //  设置 SD 为LOW
  HT513_WriteOneByte(0x12,0b11110000);
    // 设置数据格式为 I2S, 16Bits
  HT513_WriteOneByte(0x13, 0b00110000);
  // 调整音量
  HT513_WriteOneByte(0x16, 0b10100000);
    //  设置 SD 为LOW
  HT513_WriteOneByte(0x12,0b11110100);

其中的HT513_WriteOneByte(0x16, XX) 是调整音量的代码:

换句话说,有了上面的代码就可以完整的发挥出 HT513的功能了。

最终完整的代码streams-generator-i2sHT513.ino 如下:

#include <Wire.h>
#include "AudioTools.h"

AudioInfo info(44100, 1, 16);
SineWaveGenerator<int16_t> sineWave(32000);                // subclass of SoundGenerator with max amplitude of 32000
GeneratedSoundStream<int16_t> sound(sineWave);             // Stream generated from sine wave
I2SStream out;
StreamCopy copier(out, sound);                             // copies sound into i2s

#define HT513_ADDR_L 0x6c
/**
   @brief  ht513写寄存器
   @param  addr 寄存器地址
   @param  val 要写的值
   @retval None
*/
void HT513_WriteOneByte(uint8_t addr, uint8_t val)
{
  Wire.beginTransmission(HT513_ADDR_L);
  Wire.write(addr);
  Wire.write(val);
  int ack = Wire.endTransmission(true);
  Serial.print("Ack ");
  Serial.println(ack, HEX);
}

/**
   @brief  ht513读寄存器
   @param  addr 寄存器地址
   @retval 读取到的寄存器值
*/
uint8_t HT513_ReadOneByte(uint8_t addr)
{
  uint8_t temp = 0;

  Wire.beginTransmission(HT513_ADDR_L);
  Wire.write(addr);
  Wire.endTransmission(false);
  uint8_t bytesReceived = 0;
  bytesReceived = Wire.requestFrom(HT513_ADDR_L, (uint8_t)1, true);
  if (bytesReceived == 1) {
    temp = Wire.read();
  }
  else {
    Serial.println("Read Error ");
  }

  return temp;
}

// Arduino Setup
void setup(void) {
    Wire.begin(21,22);
  // Open Serial
  Serial.begin(115200);
  while (!Serial);
  AudioLogger::instance().begin(Serial, AudioLogger::Info);

  // start I2S
  Serial.println("starting I2S...");
  auto config = out.defaultConfig(TX_MODE);
  config.copyFrom(info);
  out.begin(config);

  // Setup sine wave
  sineWave.begin(info, N_B4);
  Serial.println("started...");

  int nDevices;
  byte error, address;
  Serial.println("Scanning...");
  nDevices = 0;
  for( address = 1; address < 127; address++ ) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    if (error == 0) {
      Serial.print("I2C device found at address 0x");
      if (address<16) {
        Serial.print("0");
      }
      Serial.println(address,HEX);
      nDevices++;
    }
    else if (error==4) {
      Serial.print("Unknow error at address 0x");
      if (address<16) {
        Serial.print("0");
      }
      Serial.println(address,HEX);
    }    
  }
  if (nDevices == 0) {
    Serial.println("No I2C devices found\n");
  }
  else {
    Serial.println("done\n");
  }
  
  //  设置 SD 为LOW
  HT513_WriteOneByte(0x12, 0b11110000);

  // 设置数据格式为 I2S, 16Bits
  HT513_WriteOneByte(0x13, 0b00110000);

  // 调整音量
  HT513_WriteOneByte(0x16, 0b01111111);
  
  //  设置 SD 为HIGH
  HT513_WriteOneByte(0x12, 0b11110100);
  uint8_t Value = HT513_ReadOneByte(0x12);
  Serial.println("++++++++++++++++");
  Serial.println(Value, HEX);

}

// Arduino loop - copy sound to out
void loop() {
  copier.copy();
}

本文提到的HT513 测试板电路图和 PCB:

工作的视频在:

2025 上海慕尼黑电子展即将开幕

2025慕尼黑上海电子展将于4月15-17日在上海新国际博览中心举办,本届展示面积达10万平,将设立半导体、传感器、电源、测试测量、半导体智造、分销商、无源器件、显示、连接器、开关、线束线缆、印刷电路板、电子制造服务等展区,1,700家海内外优质展商将纷纷加入,从设计研发到应用落地,横跨电子产业上下游,与业界同仁共绘电子行业未来蓝图。

2025慕尼黑上海电子展

  一、展会时间

  2025年4月15-17日

  二、参观时间

  4月15日(周二) 9:00–17:00

  4月16日(周三) 9:00–17:00

  4月17日(周四) 9:00–16:00

  三、展会地点

  展馆:上海新国际博览中心(W3-W5馆 & N1-N5馆)

  地址:浦东新区龙阳路2345号 (地铁站旁边)

扫码即可免费注册参观,在上海的朋友有兴趣可以去转转:

或者使用这个链接进行注册 https://ec.global-eservice.com/web/?lang=cn&channel=bdsem#/index

一个C#编译生成 EFI 的框架

一个CS编译生成 EFI 的框架

偶然间看到了 https://github.com/bflattened/bflat  这个项目(官方网站https://flattened.net),可以使用 C# 编程,然后生成 EFI 文件。

下载项目 Release 文件,解压后将samples/Snake/ 中的解压进去。使用如下命令即可编译:

bflat build --os:uefi --stdlib:zero -o:bootx64.efi

编译之后即可得到 bootx64.efi, 可以在  VirtualBox上运行起来:

感觉目前的支持还不完整,很多基本的库还未完成 Porting。有兴趣的朋友可以研究一下。

ELF文件转至EFI文件的转换器

本文是应作者格拉斯神剑要求,转载的文章。原文在:https://www.bilibili.com/opus/1049776129847590912?spm_id_from=333.1387.0.0

有兴趣的朋友可以关注他的项目。

格拉斯神剑使用Pascal(Free Pascal)语言写了一个能够将Linux下的可执行文件格式ELF转为UEFI下的可执行格式EFI的转换器(为了跨平台方便,做成了命令行程序,可以把各个UEFI所支持架构对应的ELF文件转换为对应的EFI文件(并不是x64平台下只能转换x64平台下的elf文件,而是什么架构的elf文件转换为对应架构的efi文件,如果elf是riscv的,转换为riscv上的UEFI EFI文件,这点可以直接点击或命令行执行elf2efi/elf2efi.exe可以看到),不知道这是不是第一个使用Pascal写elf2efi软件的。

当然这里的ELF文件是有要求的,要求如下:

1.转换前的ELF文件必须是static-pie(静态并且位置无关的可执行文件)(可以用-fPIE -Wl,–no-dynamic-linker(GNU C Compiler,gcc)或-k-pie -k–no-dynamic-linker(free pascal compiler,fpc)的编译选项得到)。

2.不能链接任何动态库,否则会导致转成的EFI文件在UEFI下运行失败。

3.对于x64平台,入口函数要求必须是微软ABI;对于i386平台,入口函数要求必须是cdecl(c的调用规范)。显然,对于所有UEFI支持的平台,其入口函数的参数必须符合UEFI规范(在UEFI国际论坛(UEFI.org)的开发者选项点开规范,会看到UEFI Specifications,点开版本号最高的就是,点进去搜索入口函数的定义即可)。

4.ELF文件本身不能有空指针这样运行时发生的错误,否则就会在转换的EFI文件里面出现错误并被UEFI Debugger报告显示。

5.ELF文件必须平台无关,避免链接系统相关的静态库和标准静态库(当然UEFI相关的库可以链接,但必须是静态库,不能动态库,动态库在链接时不会链接进去,会造成EFI文件运行时发生错误)

本程序既可以在gitee上面(https://gitee.com/tydqsoft/elf2efi)也可以在github上面(https://github.com/TYDQSoft/elf2efi)上面进行下载(两边都是一样的,下载最新版本Alpha v0.0.2就行,顺带附上Pascal源代码,自由开源)

顺带附上该命令行程序的截图(amd64架构windows下):

1.对UEFI支持的所有架构(总共4个,x64,aarch64,riscv64,loongarch64)的支持更加完美

2.比上一个版本有更快的转换速度

3.转换出来的efi文件一般来说比elf文件小

(以下的测试efi程序(由elf文件转换出来的efi文件)的功能仅是把屏幕的所有部分都变成黄色,当然不同架构的需要编译器编译成不同架构的二进制文件elf然后使用elf2efi转换来进行测试)(本elf2efi软件是自己用pascal写的,不是github上那个用C语言写的elf2efi)

x64转换成功可运行示意图:

FFMPEG  生成测试视频

在研发测试中,我们经常需要从网络或者本地播放一段视频以便验证系统的稳定性。同样的,我们在测试网络视频播放的问题时经常要被问到: 本地播放是否存在问题?因此,这里研究如何生成一个测试视频,并且传到网络上,这样可以方便的得知问题是否和网络有关系。

ffmpeg 可以使用下面的命令产生一个5秒,1280×720名称为LABZPattenX.mp4的

本地视频,编码和压缩使用 Windows 默认方式,这样生成的视频可以直接在Windows中播放:

ffmpeg -f lavfi -i gradients=c0=red:c1=blue:c2=green:n=3:duration=5:size=1280x720:rate=60:type=circular:seed=1,format=rgb0 -c:v libx264 -pix_fmt yuv420p -b:v 6000k LABZPattenX.mp4

视频内容:

为了更好的测试,我用上述方法生成了1小时的视频内容上传到 B站,链接如下:

有兴趣的朋友可以使用如下方法生成1小时的内容

ffmpeg -f lavfi -i gradients=c0=red:c1=blue:c2=green:n=3:duration=3600:size=1920x1080:rate=60:type=circular:seed=1,format=rgb0 -c:v libx264 -pix_fmt yuv420p LABZPatten1.mp4

还可以指定一个较高的码率:

ffmpeg -f lavfi -i gradients=c0=red:c1=blue:c2=green:n=3:duration=3600:size=1920x1080:rate=60:type=circular:seed=1,format=rgb0 -c:v libx264 -pix_fmt yuv420p  -b:v 6000k LABZPatten3.mp4

谢尔宾斯基地毯:

ffmpeg -y -filter_complex  sierpinski=s=1920x1080:type=carpet:rate=60:jump=3:seed=1 -c:v libx264 -pix_fmt yuv420p -b:v 6000k -t 3600 LABPatten2.mp4

上述方法的优点:

  1. 没有版权问题;
  2. 线上线下内容相同;
  3. 可以生成需要的任意分辨率,以及帧率;

网站提供了这次测试对应的 FFMPEG 文件,有兴趣的朋友可以在网站的“常用测试工具以及软件下载”页面看到:

有需要的朋友可以自行尝试。

本文提到的生成的测试视频可以在下面的链接看到:

https://player.bilibili.com/player.html?isOutside=true&aid=114268108231934&bvid=BV1mvfKYJE8A&cid=29141635048&p=1

https://player.bilibili.com/player.html?isOutside=true&aid=114268125010083&bvid=BV1mYfKYDEdb&cid=29141699907&p=1

https://player.bilibili.com/player.html?isOutside=true&aid=114268141785602&bvid=BV1PcfKYCEUA&cid=29141697090&p=1

参考:

  1. https://trac.ffmpeg.org/wiki/FancyFilteringExamples   介绍测试Patten
  2. https://ayosec.github.io/ffmpeg-filters-docs/5.1/Sources/Video/gradients.html gradients 使用参数介绍
  3. https://ffmpeg.org/doxygen/trunk/vsrc__gradients_8c_source.html  gradients源代码参数部分

如何制作一个 UEFI Shell 启动盘

UEFI Shell 是一个类似 DOS Command 启动环境的东西,对于普通用户来说它最常见的功能是启动进入之后能够烧写更新BIOS或者其他Firmware。

这次就介绍一个如何制作标准的 UEFI Shell 启动盘。

简单的说,主要分为两步:

1.格式化一个U盘,特别注意需要格式化为 FAT

对于一些容量较大的U盘,还可以选择 exFAT,但是兼容性会差一些,不能保证一定能够启动

2.下载一个 UEFI Shell 的 EFI 文件,这个是 EDK2 开源的项目,在 https://github.com/tianocore/edk2 下载到源代码后,可以编译获得 EFI 文件。如果觉得麻烦,可以在这里下载一个我编译好的(来自edk2-stable202408.01)

3.在上面格式化好的U盘上创建 EFI\BOOT 目录,然后放入 UEFI Shell 文件并且命名为 BOOTX64.EFI

4.在目标机上选择启动到U盘,即可进入 UEFI Shell。

如果你希望其他版本的UEFI Shell(比如 IA32版本, ARM 版本),可以在 https://github.com/pbatard/UEFI-Shell/releases/tag/24H2 下载到