需要解析相机的字节流文件(就是一个一个Byte组成的文件,不解析的话没有啥实际意义)。
解析需要按照一定的规则,比如:如何确定数据的头(Header),以及一个数据帧(一个数据包)的结构是什么样的,这个有点像网络包的定义,在头部定义了很多信息,这些信息能够让这个数据包按照规则解析出来后有具体的意义。
下面按照尝试-失败-再尝试-成功之后的思考来进行整理。因为一开始采用直接“复制-粘贴(ChatGPT指导)”的方式,遇到了解析失败的情况,根据结果一点点分析,始终没发现问题在哪。
因为问题出现在了一开始,也是比较基础的知识点,字节流在保存时分为大端存储和小端存储,由于这个知识点我压根就忘记了,所以迟迟没有发现,最后是不断往上追问,从ChatGPT的指导中发现了可能的原因,惭愧啊!
大小端(Endianness)概念
大小端是指多字节数据(如整数、浮点数等)在内存中字节的排列顺序。这是计算机内存架构中的一个重要概念,影响了数据的存储和通信。大小端问题通常在处理跨平台数据传输和文件读写时显得尤为重要。
-
大端(Big Endian)
- 在大端字节序中,一个数的最高位字节(Big End)存储在内存的低地址处,其次是次高位,依此类推,最低位字节存储在高地址处。
- 例如,数
0x12345678
在内存中的存储方式(地址从低到高)为12 34 56 78
。 - “大端易读”,左到右直接读出来就是原始保存的数据
-
小端(Little Endian)
- 在小端字节序中,一个数的最低位字节(Little End)存储在内存的低地址处,随后是次低位,依此类推,最高位字节存储在高地址处。
- 例如,数
0x12345678
在内存中的存储方式(地址从低到高)为78 56 34 12
。 - “小端反直觉”,因为现代汉语默认从左往右看,这里则需要反过来,两两看
大小端存在的原因主要与不同的处理器架构有关。不同的硬件制造商选择不同的存储方法,这通常取决于硬件设计的优化、历史原因和技术遗留问题。
-
大端模式的优点在于从内存的起始地址开始即可读取高位,易于从地址解析其代表的数值大小,这在网络协议设计中尤为重要,因为许多网络协议规定为大端。
-
小端模式则使得低位首先被处理,这在进行数学运算时常常更为高效,因为低位通常是先处理的部分。小端是多数现代个人计算机处理器,如x86架构,采用的存储方式。
在不同字节序的系统间交换数据时,如果不进行字节序的转换,会导致数据解释错误。例如,一个系统以大端模式写入数据,另一个以小端模式读取,未经转换直接读取会得到错误的结果。
大小端字节序举例
假设有一个4字节(Byte)的整数值 0x12345678
,在内存中的存储会根据大小端不同而不同。
- 大端(Big Endian):高位字节(靠近
Ox
)存于低地址,所以内存布局是12 34 56 78
。 - 小端(Little Endian):低位字节存于低地址,所以内存布局是
78 56 34 12
。
在内存中,地址从低到高增加。低地址存储较“早”的数据,而高地址存储较“晚”的数据,所以可以近似看成从左往右。
比如一个字节流的数据如下:
10010001001011000001
- 二进制形式:
1001 0001 0010 1100 0001
- 4个一组,转换16进制:
9 1 2 c 1
- 使用4个字节来表示时,高位补零:
00 09 12 c1
- 小端字节序,低位字节存于低地址:
- 高位
00
-> 高地址- 次高位
09
-> 次高地址- 次低位
12
-> 次低地址- 低位
c1
-> 低地址- 实际在内存的中存储位
c1 12 09 00
。
具体而言:
- Package Data Length
594625
-> 内存布局c1 12 09 00
(对应00 09 12 c1
) - Unit Data:
- Length
43978
-> 内存布局ca ab 00 00
(实际为00 00 ab ca
) - Type
3
-> 内存布局03 00 00 00
(实际为00 00 00 03
)
- Length
如果用大端方式读取C1 12 09 00
,那么将得到 12653065
,这个数字特别大,明显要大于文件大小了
如果用大端方式读取ca ab 00 00
,那么将得到 13282048
,这个数字也特别大,并且还大于12653065,所以肯定出错了。
$594625 \div 1024 \div 1024 = 0.57 MB$
$12653065 \div 1024 \div 1024 = 12.07MB$
$43978 \div 1024 \div 1024 = 0.042 MB$
$13282048 \div 1024 \div 1024 = 12.67MB$
补充说明:
字节流文件“保存”的是二进制数据,也就是说文件中的数据以二进制形式存放,这意味着每个字节由8位二进制数字(0或1)组成。而使用文件浏览器或某些工具查看这些文件时,常常会看到十六进制的表示,因为十六进制能更有效地展示和定位文件内容,特别是对于非文本数据。
C++打印时的进制
当在C++中使用 std::cout
打印数字时,除非指定格式(如使用 std::hex
来指定十六进制),否则默认为十进制( std::dec
)。例如:
uint32_t num = 0x1234;
std::cout << num; // 默认以十进制输出,打印为 "4660"
std::cout << std::hex << num; // 指定十六进制,打印为 "1234"
如果前面使用了std::hex,那么后面的std::cout也会受到影响,因此比较好的策略是用std::hex « … « std::dec包起来
C++代码实现
#include <iostream>
#include <fstream>
#include <vector>
#include <cstdint>
#include <iomanip> // For std::setw and std::setfill
struct UnitData {
uint32_t len;
uint32_t type;
std::vector<uint8_t> content;
UnitData(uint32_t len, uint32_t type, std::vector<uint8_t>& content)
: len(len), type(type), content(content) {}
};
// 大端
bool readUint32Big(std::ifstream& stream, uint32_t& value) {
char buffer[4];
if (stream.read(buffer, 4)) {
value = static_cast<uint32_t>((unsigned char)(buffer[3]) |
(unsigned char)(buffer[2]) << 8 |
(unsigned char)(buffer[1]) << 16 |
(unsigned char)(buffer[0]) << 24);
return true;
}
return false;
}
// 小端
bool readUint32Little(std::ifstream& stream, uint32_t& value) {
char buffer[4];
if (stream.read(buffer, 4)) {
value = static_cast<uint32_t>((unsigned char)(buffer[0]) |
(unsigned char)(buffer[1]) << 8 |
(unsigned char)(buffer[2]) << 16 |
(unsigned char)(buffer[3]) << 24);
return true;
}
return false;
}
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " <filename>" << std::endl;
return 1;
}
std::ifstream file(argv[1], std::ios::binary);
if (!file.is_open()) {
std::cerr << "Failed to open file: " << argv[1] << std::endl;
return 1;
}
int frameNumber = 0; // Frame counter
while (!file.eof()) {
// Read and check the header
uint8_t header[2];
if (!file.read(reinterpret_cast<char*>(header), 2) || header[0] != 0xFF || header[1] != 0xAA) {
if (file.eof()) {
break; // Break the loop if EOF is reached during header read
}
std::cerr << "Invalid header in frame " << frameNumber + 1 << "." << std::endl;
return 1;
}
std::cout << "Frame " << ++frameNumber << " - Header: 0x" << std::hex << (int)header[0] << " 0x" << (int)header[1] << std::dec << std::endl;
// Read the package length
uint32_t packageLength;
if (!readUint32Little(file, packageLength)) {
std::cerr << "Failed to read package length." << std::endl;
return 1;
}
std::cout << "Package Data Length: " << packageLength << std::endl;
// Process Unit Data
uint32_t bytesRead = 0;
while (bytesRead < packageLength) {
uint32_t unitLen, unitType;
if (!readUint32Little(file, unitLen) || !readUint32Little(file, unitType)) {
std::cerr << "Failed to read unit data." << std::endl;
return 1;
}
// Correct the content length by subtracting the length of unitType
uint32_t contentLength = unitLen - 4; // Subtracting the 4 bytes of the unitType
std::vector<uint8_t> content(contentLength);
if (!file.read(reinterpret_cast<char*>(content.data()), contentLength)) {
if (file.eof()) {
std::cerr << "Error: EOF reached before reading all data. Expected " << contentLength << " bytes, but less were available." << std::endl;
} else {
std::cerr << "Error: Failed to read unit content. Expected " << contentLength << " bytes." << std::endl;
}
return 1;
}
// Print the unit data len and type in hex
std::cout << "Unit Length: " << unitLen << ", Unit Type: 0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(unitType) << std::dec << std::endl;
bytesRead += 4 + unitLen;
}
}
file.close();
return 0;
}