EDF 文件「第 109 组只写完 3 个通道」时的结构、Resume 如何发现与如何修复
本文回答一个具体场景:磁盘上已有 108 条完整 data record,第 109 条只按通道顺序写入了前 3 个通道就中断——此时文件长什么样、prepareForResume 如何「发现」不完整、以及「修复」究竟做了什么。实现依据为工程内 EDFwriter.java(writePhysicalSamples、prepareForResume)。
1. 前提:一条完整 data record 多长?
以下与常见 EDF+、5 路数据 + 1 路 EDF Annotations、每 record 持续 1 秒、各通道每 record 样本数为 500、500、50、50、50 的配置一致(与 configureEdfWriter 类场景一致)。
1.1 头区长度
headerSize = (edfsignals + nr_annot_chns + 1) × 256 = (5 + 1 + 1) × 256 = 1792 字节
1.2 每条完整 data record 的字节数 recordsize
实现逻辑(概念上):
- 各通道本 record 内样本数求和:
500 + 500 + 50 + 50 + 50 = 1150。 - EDF 16 位:再
× 2→2300字节(纯数据通道)。 - 每条 record 末尾还有 注释轨 在本 record 内的固定宽度:
114 × nr_annot_chns(nr_annot_chns = 1时为 114 字节)。
因此:
recordsize = 1150 × 2 + 114 = 2414 字节/条(完整 record)
1.3 单通道一次 writePhysicalSamples 往文件追加多少字节?
当前通道 edfsignal 追加:param_smp_per_record[edfsignal] × 2 字节。
- 通道 0:
500 × 2 = 1000 - 通道 1:
500 × 2 = 1000 - 通道 2:
50 × 2 = 100 - 通道 3、4 若写完:各
50 × 2 = 100
只有写完所有数据通道后,库才会写入本 record 的 TAL/注释块(write_tal),并把 datarecords 加 1。若只写完 3 个通道就停:不会写第 109 条的注释块,datarecords 仍为 108。
2. 此时文件内容结构(从字节视角)
设已成功写入 108 条完整 record,第 109 条只写了通道 0、1、2。
文件总长度:
L = headerSize + 108 × recordsize + (1000 + 1000 + 100)
= 1792 + 108 × 2414 + 2100
结构可画成:
[ 1792 字节:头区 ]
[ 108 × 2414 字节:第 1~108 条完整 data record,每条含 5 路数据 + 本 record 注释轨 ]
[ 2100 字节:第 109 条 record 的「半截」——仅 ch0/ch1/ch2 的二进制,无 ch4/ch5,也无本 record 的 114 字节注释轨 ]
注意:这里的「第 109 组」是业务口语;在 EDF 逻辑条数里,第 109 条 record 并未形成,文件里只多了一段 无法单独构成一条 record 的尾部二进制。
3. Resume 时如何「定位到第 109 组不全」?
prepareForResume 不做「第几组、缺哪几路」的语义诊断,只做 长度与 recordsize 的整除对齐:
payloadSize = L - headerSize
completeRecords = floor(payloadSize / recordsize)
repairedLength = headerSize + completeRecords × recordsize
truncatedBytes = L - repairedLength
代入数字:
payloadSize = 108 × 2414 + 2100
completeRecords = floor((108 × 2414 + 2100) / 2414) = 108 + floor(2100 / 2414) = 108
因为 2100 < 2414,多出来的 2100 字节不够凑成第 109 条完整 record,故 completeRecords 仍为 108。
结论:实现上不是「识别出第 109 组缺两路」,而是 「payload 长度对单条 record 长度取 floor,余数即未完成的尾部」;在你给的场景里,余数恰好等于「只写了 3 路」的那 2100 字节。
4. 「修复」具体做了什么?
在 prepareForResume 末尾(概念步骤):
- 若
file_out.length() != repairedLength,则setLength(repairedLength)—— 物理截断文件,删除尾部truncatedBytes(此处为 2100) 字节。 datarecords = completeRecords(此处为 108),并把写指针seek到repairedLength,下一轮writePhysicalSamples从 下一条 record 的起点、通道 0 重新开始。
因此所谓修复是:丢弃无法构成完整 data record 的尾部字节,而不是在原地补写通道 3、4 与注释轨。未完成的「伪第 109 条」已写部分会被整体删掉;若继续采集,相当于重新从「第 109 条 record」的开头写起。
5. 小结表
| 问题 | 答案 |
|---|---|
| 文件里算不算已有 109 条 record? | 不算;逻辑上仍是 108 条,尾部多 2100 B 零头。 |
| 如何「发现」不全? | (L - headerSize) % recordsize ≠ 0 → floor 后完整条数少于「若写满时的条数」。 |
| 修复是否补全通道? | 否;setLength 去掉零头,再从头开始写后续 record。 |
与《EDF+ 文件头区长度与字节布局》笔记配合阅读:头长 headerSize 与 recordsize 一致时,payload / recordsize 的划分才可靠。