需要解析相机的字节流文件(就是一个一个Byte组成的文件,不解析的话没有啥实际意义)。

解析需要按照一定的规则,比如:如何确定数据的头(Header),以及一个数据帧(一个数据包)的结构是什么样的,这个有点像网络包的定义,在头部定义了很多信息,这些信息能够让这个数据包按照规则解析出来后有具体的意义

下面按照尝试-失败-再尝试-成功之后的思考来进行整理。因为一开始采用直接“复制-粘贴(ChatGPT指导)”的方式,遇到了解析失败的情况,根据结果一点点分析,始终没发现问题在哪。

因为问题出现在了一开始,也是比较基础的知识点,字节流在保存时分为大端存储和小端存储,由于这个知识点我压根就忘记了,所以迟迟没有发现,最后是不断往上追问,从ChatGPT的指导中发现了可能的原因,惭愧啊!

大小端(Endianness)概念

大小端是指多字节数据(如整数、浮点数等)在内存中字节的排列顺序。这是计算机内存架构中的一个重要概念,影响了数据的存储和通信。大小端问题通常在处理跨平台数据传输和文件读写时显得尤为重要。

  1. 大端(Big Endian)

    • 在大端字节序中,一个数的最高位字节(Big End)存储在内存的低地址处,其次是次高位,依此类推,最低位字节存储在高地址处。
    • 例如,数 0x12345678 在内存中的存储方式(地址从低到高)为 12 34 56 78
    • “大端易读”,左到右直接读出来就是原始保存的数据
  2. 小端(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

  1. 二进制形式:1001 0001 0010 1100 0001
  2. 4个一组,转换16进制:9 1 2 c 1
  3. 使用4个字节来表示时,高位补零:00 09 12 c1
  4. 小端字节序,低位字节存于低地址:
    • 高位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)

如果用大端方式读取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;
}