2026年2月更新,Step to UEFI 文章索引:
设计一个简单的文件传输协议
1.文件头设计
| Type | Seq | Length | Checksum |
| 2 Byte | 2 Bytes | 2 Bytes | 2 Bytes |
2.包设计
a.文件信息包
| Type | Seq | Length | Checksum | Protocol Version | FileName Length | FileSize | FileName |
| TYPE_FILE_INFO | 00 00 | 包括文件头的总长度 | 2 Bytes | 协议版本 1 byte | 1 Byte | 4 Bytes | N Bytes |
b.数据包
| Type | Seq | Length | Checksum | PayLoad |
| TYPE_DATA | NN MM | 包括文件头的总长度 | 4 Bytes | XX Byte |
c. 确认包
| Type | Seq | Length | Checksum |
| TYPE_ACK | NN MM 收到的前面包序号 | 包括文件头的总长度 | 4 Bytes |
d.结束包
| Seq | Length | Checksum | |
| TYPE_END | NN MM | 包括文件头的总长度 | 4 Bytes |
为了验证上述协议,编写一个 C 代码,在 VS2019 中编译成功,使用串口通讯。理论上串口是流通讯,并不是基于包的通讯。但是实际上我们使用的USB 转串口模块之类的,存在一个缓冲区的问题,特别是对于速度很高的情况(例如,6Mbps),收下来的数据都是先缓存在设备内部的,如果取走的速度比进入的速度快就会有数据丢失的问题。这种情况下,通讯包使用缓冲区一样大小的尺寸效率最高。
用于测试的C代码:
编译后的 exe
使用方法:
发送:
FileTransferProtocolTest.exe -s com17 FileTransferProtocolTest.pdb
接收:
FileTransferProtocolTest.exe -r com16
VS2019 编译 7-Zip的方法
1.首先在官方网站下载 Source Code:https://7-zip.org/download.html
2.打开 VS2019 X64 (注意必须 native x64 command 窗口)
3.进入 cd CPP\7zip 目录下, 运行 NMake 即可


Step to UEFI (308)UEFI Shell 下的 PPM转JPEG 程序
经过大量的测试和研究,终于找到一个可以工作的代码,能够将PPM转为JPEG 格式(通过前面的文章,我们也知道PPM和BMP 区别不大)。
项目来自 https://github.com/schiermike/jpeg-encoder/tree/master ,只有一个文件,能将PPM 转为 JPG 格式。
代码很长,这里就不列出了,有兴趣的可以直接下载研究。
需要注意的是:
1.只能除了宽度是16整数倍的图片(实际使用中,通常的处理方法是先填充到16的整数倍,处理完成之后再裁剪)

2.代码颜色上应该还有一些问题,估计是颜色排列错误

处理后的结果是

3.测试 Lenna图片

处理之后的结果

4.代码对于 PPM 格式有一些要求,文件头的参数需要用 0x0A 来进行分割,如果换成 0x0D 之类的会报错。

完整的代码和测试数据下载:
力科的 USB 抓包工具的小Bug
最近在Ch32V307上实现 USB Camera 的功能,使用之前的一个设计作为参考。结果在照抄的描述符的时候时偶然发现力科的USB T3 存在一个小Bug。
问题的描述为:描述符解析时,部分值不会反映的 Hex Value中:
最典型的是下面这个 GUID, 解析之后只有部分值,其余部分被丢掉了,如果你认为这个Field 的值为 0x32315659 只有4字节,会导致后续的描述符完全错位:

上面这种相对明显,因为 明确知道GUID应该是 16Bytes,但是下面这个就比较隐蔽,如果只将 Hex Value拷贝出来,会导致对应的结构体会不够 12 Bytes。

因此,使用工具 Dump USB设备描述符,然后编写自己的描述符时,务必数一下最后的描述符长度。
ESP32S3+MSM261S4030H0+HT513 扬声器
去年使用ESP32S3+MSM261S4030H0+HT513 做了一个扬声器的项目。具体硬件设计可以在 https://oshwhub.com/arduinoai-hao-zhe/portable-speake 看到。
在使用的时候,遇到的一个问题就是MSM261S4030H0输出是32Bits(实际只有24Bits)。最近忽然想起来查了一下是否有机会按照 16Bits输出,搜索出来的资料说的是:只要设置 I2S Master按照16Bits 取即可。因为这个输出有效数据只有 24Bits,直接截断取前面的16Bit即可。
根据这个资料编写代码如下,工作正常。
#include <Arduino.h>
#include <Wire.h>
#include <driver/i2s.h>
#define I2S_PORT1 I2S_NUM_0
#define I2S_PORT2 I2S_NUM_1
#define SAMPLE_RATE 16000
#define CHANNEL_FORMAT I2S_CHANNEL_FMT_ONLY_LEFT
//#define BITS_PER_SAMPLE I2S_BITS_PER_SAMPLE_32BIT
#define BITS_PER_SAMPLE I2S_BITS_PER_SAMPLE_16BIT
#define MIC_BCK_PIN 37
#define MIC_WS_PIN 38
#define MIC_DATA_PIN 36
#define SPK_BCK_PIN 15
#define SPK_WS_PIN 6
#define SPK_DATA_PIN 7
#define SPK_MCK_PIN 16
// HT513 音量
uint16_t Volume;
#define TOLENCE 16
#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;
}
void setup() {
Serial.begin(115200);
delay(2000);
i2s_config_t MIC_i2sConfig = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = SAMPLE_RATE,
.bits_per_sample = BITS_PER_SAMPLE,
.channel_format = CHANNEL_FORMAT,
.communication_format = I2S_COMM_FORMAT_I2S,
.intr_alloc_flags = 0,
.dma_buf_count = 8,
.dma_buf_len = 64,
.use_apll = false,
.tx_desc_auto_clear = false,
.fixed_mclk = 0
};
i2s_pin_config_t MIC_pinConfig = {
.bck_io_num = MIC_BCK_PIN,
.ws_io_num = MIC_WS_PIN,
.data_out_num = I2S_PIN_NO_CHANGE,
.data_in_num = MIC_DATA_PIN
};
i2s_driver_install(I2S_PORT1, &MIC_i2sConfig, 0, NULL);
i2s_set_pin(I2S_PORT1, &MIC_pinConfig);
i2s_config_t SPK_i2sConfig = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = SAMPLE_RATE,
.bits_per_sample = BITS_PER_SAMPLE,
.channel_format = CHANNEL_FORMAT,
.communication_format = I2S_COMM_FORMAT_I2S,
.intr_alloc_flags = 0,
.dma_buf_count = 8,
.dma_buf_len = 64,
.use_apll = false,
.tx_desc_auto_clear = false,
.fixed_mclk = 0
};
i2s_pin_config_t SPK_pinConfig = {
.mck_io_num = SPK_MCK_PIN,
.bck_io_num = SPK_BCK_PIN,
.ws_io_num = SPK_WS_PIN,
.data_out_num = SPK_DATA_PIN,
.data_in_num = I2S_PIN_NO_CHANGE
};
i2s_driver_install(I2S_PORT2, &SPK_i2sConfig, 0, NULL);
i2s_set_pin(I2S_PORT2, &SPK_pinConfig);
analogReadResolution(9);
Wire.begin(18, 17);
// 设置 SD 为LOW
HT513_WriteOneByte(0x12, 0b11110000);
// 设置数据格式为 I2S, 16Bits
HT513_WriteOneByte(0x13, 0b00110000);
// 读取音量设置
Volume = analogRead(3);
uint8_t Vol = (Volume, 0, 511, 0x07, 0xff);
HT513_WriteOneByte(0x16, Vol);
HT513_WriteOneByte(0x15, Vol);
Serial.println(Volume, HEX);
// 调整声道
HT513_WriteOneByte(0x17, 0b10110000);
Serial.println("++++++++++++++++");
// 设置 SD 为HIGH
HT513_WriteOneByte(0x12, 0b11110100);
uint8_t Value = HT513_ReadOneByte(0x12);
Serial.println(Value, HEX);
Value = HT513_ReadOneByte(0x13);
Serial.println(Value, HEX);
Value = HT513_ReadOneByte(0x16);
Serial.println(Value, HEX);
Value = HT513_ReadOneByte(0x17);
Serial.println(Value, HEX);
Serial.print(" starting");
// dsp.beginAEC(256, 1024, 16000); // Initialize AEC with frame size, filter length, and sample rate
// dsp.enableAEC(true); // Enable AEC
}
int8_t mic[512], speaker[521], out[512];
void loop() {
size_t bytesRead = 0;
i2s_read(I2S_PORT1, &mic, sizeof(mic), &bytesRead, portMAX_DELAY);
i2s_write(I2S_PORT2, &mic, sizeof(mic), &bytesRead, portMAX_DELAY);
if (abs(analogRead(3) - Volume) > TOLENCE) {
// 读取音量设置
Volume = analogRead(3);
// 设置 SD 为LOW
HT513_WriteOneByte(0x12, 0b11110000);
uint8_t Vol = map(Volume, 0, 511, 0x07, 0xff);
HT513_WriteOneByte(0x16, Vol);
// 设置 SD 为HIGH
HT513_WriteOneByte(0x12, 0b11110100);
Serial.print(analogRead(3), HEX);
Serial.print(" ");
Serial.print(Volume, HEX);
Serial.print(" ");
Serial.println(Vol, HEX);
}
}
Step to UEFI (307)UEFI 下使用PRIu8 以及类似宏
上次的代码中碰到了C语言中的 PRIu8 ,这次专门研究了一下如何在代码中直接使用。
<inttypes.h> 是 C 标准库提供的头文件,其中有定义(例如EDK2 中的StdLib\Include\X64\machine\int_fmtio.h文件):
#define PRIu8 "u" /* uint8_t */
#define PRIu16 "u" /* uint16_t */
#define PRIu32 "u" /* uint32_t */
它们C99 标准引入的格式说明符宏。可以通过下面的这种方法进行使用:
printf("Value: %" PRIu16 "\n", value);
在 C 语言中,当你编写多个字符串字面量(例如使用双引号括起来的字符串)在代码中相邻放置时,编译器会自动将它们连接起来形成一个单独的字符串,这被称作C 语言的字符串字面量的自动连接,所以上述代码展开后就是:
printf("Value: %u \n", value);
在EDK2 中编译一个简单的代码进行验证:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
#define LABZTest "LABZ"
int main(int argc, char *argv[]) {
printf("Value: " LABZTest "\n");
return 0;
}
模拟器中运行结果如下:

上述试验说明 VC2019 完全支持字符串字面量的自动连接。
所以理论上只要引用了inttypes.h 就可以使用PRIu8 这种宏定义,但是还是会遇到编译期的错误。经过研究发现 \StdLib\Include\inttypes.h 中相关引用是被注释掉的。下图中左侧是原始的代码,右侧是修改后的。完全无法理解原始代码要去掉这个引用,经过这样的修改即可正常编译和执行:

完整的测试代码:
#include <stdio.h>
#include <inttypes.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
int main(int argc, char *argv[]) {
printf("Value: %" PRIu8 "\n",1234);
return 0;
}
模拟器中测试,得到了期望的结果:

完整的代码下载:
NSPGD1M006DT04 测试模块
一、项目介绍
本项目使用表压传感器NSPGD1M006DT04,制作了一个模块,能够获得当前的气压。
很多年前,如果需要制作“吹气熄灯项目”,通常只能使用声音传感器,使用获取声音的方法来判断是否进行了吹气,显而易见这种方法要受到诸多影响。现在可以使用NSPGD1M006DT04 这种传感器来来完成。
二、项目设计思路
1、芯片选型
NSPGD1M006DT04是表压传感器。表压传感器(Gauge Pressure Sensor)是一种测量相对压力的传感器,即相对于大气压力的压力值。相对的还有绝压传感器(Absolute Pressure Sensor)是一种测量相对于真空或绝对零点的压力的传感器。与表压传感器不同,绝压传感器不仅测量被测介质的压力,还考虑到环境中的气压。绝压传感器的工作原理与表压传感器类似,但其参考点是绝对真空或绝对零点而不是大气压力。
2、测试主控选型
为了方便进行测试,选择使用 Arduino 来作为主控,它可以方便的实现 I2C 通讯,同时我们还会设计一个 Arduino 的库,方便日后的使用。
3、原理图绘制
作为模块,非常简单,严格按照Datasheet设计。下图是 DataSheet 中给出的参考:

据此设计的模块电路图:

4、PCB绘制与制板

5、焊接与调试
为了便于使用,焊接了 2.54mm 排母,方便插拔测试。


三、开发过程中遇到的问题与解决方式
为了方便的进行调试,我使用了一款国产开源逻辑分析仪:章鱼哥出品的 PX logic 逻辑分析仪

这款逻辑分析仪主打高速采集,性能指标如下:
•最大32通道逻辑分析仪
•buff模式最大支持8ch@1G,总带宽8G,存储深度4Gbits
•usb3.0下stream最大2ch@1G,总带宽2G,存储深度1024Gbits
•采样usb3.0进行传输,兼容usb2.0
•输入采样阈值0-6v可调
•开源解码协议库200+,使用python进行开发,可自行开发编写
硬件核心部分采用国产器件:
- USB3.0 Phy 采用南京沁恒微的WCH569W
- FPGA采用国产FPGA 紫光同创PGL22G
其中的 Buffer 模式意思是:内部自带了缓冲,可以将采集的信号直接传输存储在内部,存储多少数据取决于当前采样数量居和板载内存的大小;Stream的意思是随时将当前采样到的数据发送到电脑上,理论上只要你的电脑性能足够,可以不停的进行采样。
在动手之前首先在面包板上动手实验(NSPGD1M006DT04这种封装非常友好,便于实验)。这里无需额外的上拉I2C 即可工作

设置采样20秒,采样率5Mhz(I2C 400KHz, 相当于10倍采样率)

Buffer 模式,2V 阈值,只要2个通道即可抓取

解码器选择 I2C

指定0 通道是 SCL, 1通道是 SDA

接下来就可以抓到结果了:

四、实现结果展示

五、关键代码及说明
读取的代码数据需要和 Datasheet 上的 Demo 完成相同:
float NSPGD1M006DT04::readPressure() {
uint32_t PData;
_i2cPort->beginTransmission(_i2cAddress);
_i2cPort->write(0x30);
_i2cPort->write(0x0A);
_i2cPort->endTransmission(true);
int number = 0;
while (number < 50) {
number++;
delay(1);
//NSPGD1M006DT04_Read_Byte(0x30,Reg30);
_i2cPort->beginTransmission(_i2cAddress);
_i2cPort->write(0x30);
_i2cPort->endTransmission(false);
_i2cPort->requestFrom(_i2cAddress, 1, true);
if (_i2cPort->read() == 0x02) {
//NSPGD1M006DT04_Read_3Byte(0x06,PData);
_i2cPort->beginTransmission(_i2cAddress);
_i2cPort->write(0x06);
_i2cPort->endTransmission(false);
_i2cPort->requestFrom(_i2cAddress, 3, true);
PData = _i2cPort->read();
PData = (PData << 8) | _i2cPort->read();
PData = (PData << 8) | _i2cPort->read();
if (PData > 8388607) {
PData = PData - 16777216;
}
return (((float)PData) / 8388607 - 0.1) / 0.13333;
}
}
return 0.0;
}
取得的数值以 float 返回给调用者。
六、难点以及解决方法
在调试过程中,我发现根据文档要求,发送指令之后,一直会出现 I2C 的 NA ,无法取得设备的数据。搜索资料也无果,无奈之下继续研究Datasheet, 忽然发现它给出的例子是多次操作的,下面这一块发送 0x0A 之后,发0x30 然后尝试50次(之前上学的时候,英文会有改错的题目,其中一种不为人知的方法是将错误通过排版的方法,放在换行或者换页的地方,看到这里忽然让我想起来被这种题目支配的恐惧)

于是,我也修改代码多次读取,然后问题就解决了。总结下来之前碰到问题的原因是:
- 为了便于调试,只尝试一次;
- 代码中间隔2秒才进行一次尝试;
- 我逻辑分析仪设置采样20秒,通常我也只会观察20秒;
修改库函数,多尝试问题就解决了。
==================================================
本项目是参考电子森林的活动项目,原始链接在 https://www.eetree.cn/project/4719
在线取得 EFI 文件中文本的工具
在 https://convert.guru/converter 提供了一个在线工具,能够解析出来 EFI 文件中的文本字符串。
用法非常简单,把EFI 文件拖拽上去即可解析,比如,我使用手边的 EFI 测试如下:

估计这个工具是用来方便进行EFI 破解的吧?
给 7Z 增加解压后打开目录的功能
修改 7Zip CPP\7zip\UI\GUI\ExtractGUI.cpp ExtractGUI() 函数,在末尾增加一段代码:
extracter.ArchivePaths = &archivePaths;
extracter.ArchivePathsFull = &archivePathsFull;
extracter.WildcardCensor = &wildcardCensor;
extracter.Options = &options;
#ifndef Z7_SFX
extracter.HashBundle = hb;
#endif
extracter.IconID = IDI_ICON;
RINOK(extracter.Create(title, hwndParent))
messageWasDisplayed = extracter.ThreadFinishedOK && extracter.MessagesDisplayed;
//LAB-Z_Debug_Start
if (extracter.Result == S_OK && !options.TestMode && !options.OutputDir.IsEmpty())
{
FString outputDir = options.OutputDir;
NName::NormalizeDirPathPrefix(outputDir);
ShellExecute(NULL, L"explore", fs2us(outputDir), NULL, NULL, SW_SHOWNORMAL);
}
//LAB-Z_Debug_End
return extracter.Result;
}
重新编译,之后将编译生成的 7zG.exe 拷贝覆盖到之前的 7z 安装目录下,使用右键解压的时候就能自动打开解压目录了。
修改后的代码:
基于26.01 源代码修改后编译生成的 7zG.exe:
Step to UEFI (306)JPEG 图片转PPM 程序
这次带来的是一个 Jpeg图片Decode 工具,和之前的显示 Jpeg图片工具类似。相关代码来自 https://github.com/xbarin02/jpeg 项目。修正了一些 UEFI 下的编译问题,主要有:
- EDK2 没有提供 roundf()/sqrtf()/cosf()函数,解决方法是通过下面的调用内部的其他函数。
float sqrtf(float x) {
return sqrt(x);
}
float cosf(float x) {
return cos(x);
}
float roundf(float x) {
if (x >= 0.0f) {
return (float)(INT32)(x + 0.5f);
} else {
return (float)(INT32)(x - 0.5f);
}
}
- 源代码中设用了 PRIu16/SCNu16/PRIu8 这样的宏定义,编译时会出现错误。对应的都使用手工方式进行了修改。
最终,完整的主程序decoder.c:
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <assert.h>
#include "common.h"
#include "io.h"
#include "huffman.h"
#include "coeffs.h"
#include "imgproc.h"
#include "frame.h"
#include <inttypes.h>
const char *Pq_to_str[] = {
[0] = "8-bit",
[1] = "16-bit"
};
/* B.2.4.1 Quantization table-specification syntax */
int parse_qtable(FILE *stream, struct context *context)
{
int err;
uint8_t Pq, Tq;
struct qtable *qtable;
assert(context != NULL);
err = read_nibbles(stream, &Pq, &Tq);
RETURN_IF(err);
if (Tq >= 4) {
/* invalid value */
return RET_FAILURE_FILE_UNSUPPORTED;
}
assert(Tq < 4);
assert(Pq < 2);
//printf("Pq = %" PRIu8 " (%s), Tq = %" PRIu8 " (QT identifier)\n", Pq, Pq_to_str[Pq], Tq);
qtable = &context->qtable[Tq];
/* precision */
qtable->Pq = Pq;
for (int i = 0; i < 64; ++i) {
if (Pq == 0) {
uint8_t byte;
err = read_byte(stream, &byte);
RETURN_IF(err);
qtable->Q[zigzag[i]] = (uint16_t)byte;
} else {
uint16_t word;
err = read_word(stream, &word);
RETURN_IF(err);
qtable->Q[zigzag[i]] = word;
}
}
for (int y = 0; y < 8; ++y) {
for (int x = 0; x < 8; ++x) {
//printf("%3" PRIu16 " ", qtable->Q[y * 8 + x]);
}
printf("\n");
}
return RET_SUCCESS;
}
int parse_frame_header(FILE *stream, struct context *context)
{
int err;
/* Sample precision */
uint8_t P;
/* Number of lines, Number of samples per line */
uint16_t Y, X;
/* Number of image components in frame */
uint8_t Nf;
assert(context != NULL);
err = read_byte(stream, &P);
RETURN_IF(err);
err = read_word(stream, &Y);
RETURN_IF(err);
err = read_word(stream, &X);
RETURN_IF(err);
err = read_byte(stream, &Nf);
RETURN_IF(err);
assert(X > 0);
assert(Nf > 0);
//printf("P = %" PRIu8 " (Sample precision), Y = %" PRIu16 ", X = %" PRIu16 ", Nf = %" PRIu8 " (Number of image components)\n", P, Y, X, Nf);
/* precision */
context->P = P;
context->Y = Y;
context->X = X;
/* components */
context->Nf = Nf;
uint8_t max_H = 0, max_V = 0;
for (int i = 0; i < Nf; ++i) {
uint8_t C;
uint8_t H, V;
uint8_t Tq;
err = read_byte(stream, &C);
RETURN_IF(err);
err = read_nibbles(stream, &H, &V);
RETURN_IF(err);
err = read_byte(stream, &Tq);
RETURN_IF(err);
//printf("C = %" PRIu8 " (Component identifier), H = %" PRIu8 ", V = %" PRIu8 ", Tq = %" PRIu8 " (QT identifier)\n", C, H, V, Tq);
context->component[C].H = H;
context->component[C].V = V;
context->component[C].Tq = Tq;
max_H = (H > max_H) ? H : max_H;
max_V = (V > max_V) ? V : max_V;
}
context->max_H = max_H;
context->max_V = max_V;
err = compute_no_blocks_and_alloc_buffers(context);
RETURN_IF(err);
return RET_SUCCESS;
}
const char *Tc_to_str[] = {
[0] = "DC",
[1] = "AC"
};
int parse_huffman_tables(FILE *stream, struct context *context)
{
int err;
uint8_t Tc, Th;
assert(context != NULL);
err = read_nibbles(stream, &Tc, &Th);
RETURN_IF(err);
if (Tc >= 2) {
return RET_FAILURE_FILE_UNSUPPORTED;
}
assert(Tc < 2);
//printf("Tc = %" PRIu8 " (%s table) Th = %" PRIu8 " (HT identifier)\n", Tc, Tc_to_str[Tc], Th);
struct htable *htable = &context->htable[Tc][Th];
for (int i = 0; i < 16; ++i) {
err = read_byte(stream, &htable->L[i]);
RETURN_IF(err);
}
for (int i = 0; i < 16; ++i) {
uint8_t L = htable->L[i];
for (int l = 0; l < L; ++l) {
err = read_byte(stream, &htable->V[i][l]);
RETURN_IF(err);
}
}
/* Annex C */
struct hcode *hcode = &context->hcode[Tc][Th];
err = conv_htable_to_hcode(htable, hcode);
RETURN_IF(err);
return RET_SUCCESS;
}
struct scan {
uint8_t Ns;
uint8_t Cs[256];
/* useful to remove differential DC coding
*
* At the beginning of the scan and at the beginning of each restart interval, the prediction for the DC coefficient prediction
* is initialized to 0. */
struct int_block *last_block[256];
};
int parse_scan_header(FILE *stream, struct context *context, struct scan *scan)
{
int err;
/* Number of image components in scan */
uint8_t Ns;
err = read_byte(stream, &Ns);
RETURN_IF(err);
//printf("Ns = %" PRIu8 " (Number of image components in scan)\n", Ns);
assert(scan != NULL);
scan->Ns = Ns;
for (int j = 0; j < Ns; ++j) {
uint8_t Cs;
uint8_t Td, Ta;
err = read_byte(stream, &Cs);
RETURN_IF(err);
err = read_nibbles(stream, &Td, &Ta);
RETURN_IF(err);
//printf("Cs%i = %" PRIu8 " (Component identifier), Td%i = %" PRIu8 " (DC HT identifier), Ta%i = %" PRIu8 " (AC HT identifier)\n", j, Cs, j, Td, j, Ta);
scan->Cs[j] = Cs;
context->component[Cs].Td = Td;
context->component[Cs].Ta = Ta;
}
uint8_t Ss;
uint8_t Se;
uint8_t Ah, Al;
err = read_byte(stream, &Ss);
RETURN_IF(err);
err = read_byte(stream, &Se);
RETURN_IF(err);
err = read_nibbles(stream, &Ah, &Al);
RETURN_IF(err);
if (Ss != 0 || Se != 63) {
return RET_FAILURE_FILE_UNSUPPORTED;
}
assert(Ss == 0);
assert(Se == 63);
//printf("Ss = %" PRIu8 " (the first DCT coefficient), Se = %" PRIu8 " (the last DCT coefficient)\n", Ss, Se);
if (Ah != 0 || Al != 0) {
return RET_FAILURE_FILE_UNSUPPORTED;
}
assert(Ah == 0);
assert(Al == 0);
//printf("Ah = %" PRIu8 " (bit position high), Al = %" PRIu8 " (bit position low)\n", Ah, Al);
context->mblocks = 0;
return RET_SUCCESS;
}
/* read MCU */
int read_macroblock(struct bits *bits, struct context *context, struct scan *scan)
{
int err;
assert(scan != NULL);
assert(context != NULL);
size_t seq_no = context->mblocks;
if (scan->Ns == 0) {
/* nothing to do */
return RET_FAILURE_NO_MORE_DATA;
} else if (scan->Ns == 1) {
/* A.2.2 Non-interleaved order (Ns = 1) */
assert(scan->Ns == 1);
uint8_t Cs = scan->Cs[0];
uint8_t H = context->component[Cs].H;
uint8_t V = context->component[Cs].V;
size_t blocks_in_mb = H * V;
for (size_t w = 0; w < blocks_in_mb; ++w) {
size_t block_x = (blocks_in_mb * seq_no + w) % context->component[Cs].b_x;
size_t block_y = (blocks_in_mb * seq_no + w) / context->component[Cs].b_x;
size_t block_seq = block_y * context->component[Cs].b_x + block_x;
struct int_block *int_block = &context->component[Cs].int_buffer[block_seq];
/* read block */
err = read_block(bits, context, Cs, int_block);
RETURN_IF(err);
if (scan->last_block[Cs] != NULL) {
int_block->c[0] += scan->last_block[Cs]->c[0];
}
scan->last_block[Cs] = int_block;
}
} else {
assert(scan->Ns > 1);
if (context->m_x == 0) {
/* missing SOF before SOS? */
return RET_FAILURE_FILE_UNSUPPORTED;
}
assert(context->m_x != 0);
size_t x = seq_no % context->m_x;
size_t y = seq_no / context->m_x;
// printf("[DEBUG] reading macroblock... x=%zu y=%zu\n", x, y);
/* for each component */
for (int j = 0; j < scan->Ns; ++j) {
uint8_t Cs = scan->Cs[j];
uint8_t H = context->component[Cs].H;
uint8_t V = context->component[Cs].V;
// printf("[DEBUG] reading component %" PRIu8 " blocks @ x=%zu y=%zu\n", Cs, x * H, y * V);
/* for each 8x8 block */
for (int v = 0; v < V; ++v) {
for (int h = 0; h < H; ++h) {
size_t block_x = x * H + h;
size_t block_y = y * V + v;
assert(block_x < context->component[Cs].b_x);
size_t block_seq = block_y * context->component[Cs].b_x + block_x;
// printf("[DEBUG] reading component %" PRIu8 " blocks @ x=%zu y=%zu out of X=%zu Y=%zu\n", Cs, x * H + h, y * V + v, context->component[Cs].b_x, context->component[Cs].b_y);
// printf("[DEBUG] reading component %" PRIu8 " block# %zu out of %zu\n", Cs, block_seq, context->component[Cs].b_x * context->component[Cs].b_y);
struct int_block *int_block = &context->component[Cs].int_buffer[block_seq];
/* past the end of data? */
if (block_seq >= context->component[Cs].b_x * context->component[Cs].b_y) {
int_block = NULL;
}
/* read block */
err = read_block(bits, context, Cs, int_block);
RETURN_IF(err);
/* remove differential DC coding */
if (scan->last_block[Cs] != NULL) {
int_block->c[0] += scan->last_block[Cs]->c[0];
}
scan->last_block[Cs] = int_block;
}
}
}
}
return RET_SUCCESS;
}
int read_ecs(FILE *stream, struct context *context, struct scan *scan)
{
int err;
struct bits bits;
init_bits(&bits, stream);
for (int i = 0; i < 256; ++i) {
scan->last_block[i] = NULL;
}
/* loop over macroblocks */
do {
err = read_macroblock(&bits, context, scan);
if (err == RET_FAILURE_NO_MORE_DATA)
goto end;
RETURN_IF(err);
context->mblocks++;
} while (1);
end:
printf("Processed: %zu macroblocks\n", context->mblocks);
return RET_SUCCESS;
}
int parse_restart_interval(FILE *stream, struct context *context)
{
int err;
uint16_t Ri;
err = read_word(stream, &Ri);
RETURN_IF(err);
context->Ri = Ri;
return RET_SUCCESS;
}
int parse_comment(FILE *stream, uint16_t len)
{
if (len < 2) {
return RET_FAILURE_FILE_UNSUPPORTED;
}
assert(len >= 2);
size_t l = len - 2;
char *buf = malloc(l + 1);
if (buf == NULL) {
return RET_FAILURE_MEMORY_ALLOCATION;
}
if (fread(buf, sizeof(char), l, stream) != l) {
free(buf);
return RET_FAILURE_FILE_IO;
}
buf[l] = 0;
printf("%s\n", buf);
free(buf);
return RET_SUCCESS;
}
int write_image(struct context *context, const char *path)
{
int err;
struct frame frame;
err = frame_create(context, &frame);
RETURN_IF(err);
err = frame_to_rgb(&frame);
if (err) {
goto end;
}
err = write_frame(&frame, path);
end:
frame_destroy(&frame);
return err;
}
int epilogue(struct context *context, const char *path)
{
int err;
err = dequantize(context);
RETURN_IF(err);
err = inverse_dct(context);
RETURN_IF(err);
err = conv_blocks_to_frame(context);
RETURN_IF(err);
err = write_image(context, path);
RETURN_IF(err);
return RET_SUCCESS;
}
int parse_format(FILE *stream, struct context *context, const char *path)
{
int err;
struct scan scan;
// init
scan.Ns = 0;
while (1) {
uint16_t marker;
err = read_marker(stream, &marker);
RETURN_IF(err);
/* An asterisk (*) indicates a marker which stands alone,
* that is, which is not the start of a marker segment. */
switch (marker) {
uint16_t len;
long pos;
/* SOI* Start of image */
case 0xffd8:
printf("SOI\n");
break;
/* APPn */
case 0xffe0:
case 0xffe1:
case 0xffe2:
case 0xffe3:
case 0xffe4:
case 0xffe5:
case 0xffe6:
case 0xffe7:
case 0xffe8:
case 0xffeb:
case 0xffec:
case 0xffed:
case 0xffee:
printf("APP%i\n", marker & 0xf);
err = read_length(stream, &len);
RETURN_IF(err);
err = skip_segment(stream, len);
RETURN_IF(err);
break;
/* DQT Define quantization table(s) */
case 0xffdb:
printf("DQT\n");
pos = ftell(stream);
err = read_length(stream, &len);
RETURN_IF(err);
do {
err = parse_qtable(stream, context);
RETURN_IF(err);
} while (ftell(stream) < pos + len);
break;
/* SOF0 Baseline DCT */
case 0xffc0:
printf("SOF0\n");
err = read_length(stream, &len);
RETURN_IF(err);
err = parse_frame_header(stream, context);
RETURN_IF(err);
break;
/* SOF1 Extended sequential DCT */
case 0xffc1:
printf("SOF1\n");
err = read_length(stream, &len);
RETURN_IF(err);
err = parse_frame_header(stream, context);
RETURN_IF(err);
break;
/* SOF2 Progressive DCT */
case 0xffc2:
printf("SOF2\n");
err = read_length(stream, &len);
RETURN_IF(err);
err = parse_frame_header(stream, context);
RETURN_IF(err);
fprintf(stderr, "Progressive DCT not supported!\n");
return RET_FAILURE_FILE_UNSUPPORTED;
/* SOF3 Lossless (sequential) */
case 0xffc3:
printf("SOF3\n");
err = read_length(stream, &len);
RETURN_IF(err);
err = parse_frame_header(stream, context);
RETURN_IF(err);
fprintf(stderr, "Lossless JPEG not supported!\n");
return RET_FAILURE_FILE_UNSUPPORTED;
/* SOF9 Extended sequential DCT (arithmetic coding) */
case 0xffc9:
printf("SOF9\n");
err = read_length(stream, &len);
RETURN_IF(err);
err = parse_frame_header(stream, context);
RETURN_IF(err);
fprintf(stderr, "Arithmetic coding not supported!\n");
return RET_FAILURE_FILE_UNSUPPORTED;
/* SOF10 Progressive DCT (arithmetic coding) */
case 0xffca:
printf("SOF10\n");
err = read_length(stream, &len);
RETURN_IF(err);
err = parse_frame_header(stream, context);
RETURN_IF(err);
fprintf(stderr, "Arithmetic coding not supported!\n");
return RET_FAILURE_FILE_UNSUPPORTED;
/* DHT Define Huffman table(s) */
case 0xffc4:
printf("DHT\n");
pos = ftell(stream);
err = read_length(stream, &len);
RETURN_IF(err);
/* parse multiple tables in single DHT */
do {
err = parse_huffman_tables(stream, context);
RETURN_IF(err);
} while (ftell(stream) < pos + len);
break;
/* SOS Start of scan */
case 0xffda:
printf("SOS\n");
err = read_length(stream, &len);
RETURN_IF(err);
err = parse_scan_header(stream, context, &scan);
RETURN_IF(err);
err = read_ecs(stream, context, &scan);
RETURN_IF(err);
break;
/* EOI* End of image */
case 0xffd9:
printf("EOI\n");
pos = ftell(stream);
fseek(stream, 0, SEEK_END);
if (ftell(stream) - pos > 0) {
printf("*** %li bytes of garbage ***\n", ftell(stream) - pos);
}
err = epilogue(context, path);
RETURN_IF(err);
return RET_SUCCESS;
/* DRI Define restart interval */
case 0xffdd:
printf("DRI\n");
err = read_length(stream, &len);
RETURN_IF(err);
err = parse_restart_interval(stream, context);
RETURN_IF(err);
break;
/* RSTm* Restart with modulo 8 count “m” */
case 0xffd0:
case 0xffd1:
case 0xffd2:
case 0xffd3:
case 0xffd4:
case 0xffd5:
case 0xffd6:
case 0xffd7:
printf("RST%i\n", marker & 0xf);
err = read_ecs(stream, context, &scan);
RETURN_IF(err);
break;
/* COM Comment */
case 0xfffe:
printf("COM\n");
err = read_length(stream, &len);
RETURN_IF(err);
err = parse_comment(stream, len);
RETURN_IF(err);
break;
/* TEM* For temporary private use in arithmetic coding */
case 0xff01:
printf("TEM\n");
break;
/* DAC Define arithmetic coding conditioning(s) */
case 0xffcc:
printf("DAC\n");
err = read_length(stream, &len);
RETURN_IF(err);
err = skip_segment(stream, len);
RETURN_IF(err);
break;
default:
//fprintf(stderr, "unhandled marker 0x%" PRIx16 "\n", marker);
return RET_FAILURE_FILE_UNSUPPORTED;
}
}
}
int process_jpeg_stream(FILE *stream, const char *path)
{
int err;
struct context *context = malloc(sizeof(struct context));
if (context == NULL) {
fprintf(stderr, "malloc failure\n");
return RET_FAILURE_MEMORY_ALLOCATION;
}
err = init_context(context);
if (err) {
goto end;
}
err = parse_format(stream, context, path);
end:
free_buffers(context);
free(context);
return err;
}
int process_jpeg_file(const char *i_path, const char *o_path)
{
FILE *stream = fopen(i_path, "r");
if (stream == NULL) {
fprintf(stderr, "fopen failure\n");
return RET_FAILURE_FILE_OPEN;
}
int err = process_jpeg_stream(stream, o_path);
fclose(stream);
return err;
}
int main(int argc, char *argv[])
{
const char *i_path = argc > 1 ? argv[1] : "Lenna.jpg";
const char *o_path = argc > 2 ? argv[2] : NULL;
int err = process_jpeg_file(i_path, o_path);
if (err) {
printf("Failure.\n");
return 1;
}
printf("Success.\n");
return 0;
}
编译完成后,在模拟器中进行测试。首先运行一下 JPG2PPM.EFI,它会将Lenna.jpg 转为为 Test.ppm。

接下来,运行 ppmdemo.efi output.ppm 即可看到转换后的结果:

有兴趣的朋友可以试试。
完整的代码如下: