2023年更新:
没想到还有人关注,上传了最新代码
https://github.com/yc97/TDMSDecoder
该代码经过测试,基本没什么bug了
reference:
http://www.eefocus.com/Junking/blog/12-07/281264_7bf69.html
http://www.ni.com/white-paper/14252/zhs/
http://www.ni.com/white-paper/5696/en/
http://liuhao815.blog.163.com/blog/static/2314297820091016113039903/
https://github.com/yc97/TDMSDecoder
本人项目中软件是用java写的,Swing做的界面(BTW:Swing真的是丑,而且美化界面的资料实在是少啊,克服了很多困难才完成,相关技术总结在写,先挖坑),然后需要对LabVIEW保存的TDMS文件进行解析,NI公司主推的文件格式,但是貌似只在NI软件里面流行,使用其他语言解析的资料确实不多,他有C库,可是我的软件要运行多种系统,用C写的话想着也很麻烦。最后看到了Github上有一个java写的TDMS解析项目,于是参考了一下他的基本思路,针对自己的项目修改了很多,然后用到了自己的代码里,在这里表示感谢。在这里总结一下相关知识
1技术背景:
1.1TDMS文件逻辑结构:
TDMS(Technical DataManagement Streaming)文件是NI主推的一种二进制记录文件,它兼顾了高速、易存取和方便等多种优势,能够在NI的各种数据分析或挖掘软件之间进行无缝交互,也能够提供一系列API函数供其它应用程序调用。
TDMS的逻辑结构分为三层:文件(File)、通道组(Channel Groups)和通道(Channels),如图 1所示,每一个层次上都可以附加特定的属性(Properties)。程序员可以非常方便地使用这三个逻辑层次定义测试数据,也可以任意检索各个逻辑层次的数据,这使得数据检索是有序的、方便存取的。
图1 TDMS文件的逻辑结构
1.2 TDMS文件二进制结构
TDMS文件的内部结构(二进制结构、物理结构)如图2所示。TDMS内部结构的核心是segment,当数据被写入到segment中时实际上是新建了segment。各个segment中包含的主要数据段的含义如下所示:
Ø ToC Bitmask:这是一个32位的整型数据段,它表示该segment是否包含meta data、raw data。
Ø Version number:表示segment的版本,这可以确保兼容一些旧的TDMS文件版本。
Ø Next segment offset:表示下一个segment的偏移字节。
Ø Raw data offset:表示Raw data的偏移字节。
Ø Meta Data:三个层次的属性存储字段。
Ø Raw data:实际的raw data存储字段。
这种物理结构也就决定了TDMS文件的随机存取特性,当程序员只希望读取raw data而并不关注属性时,此时可以利用raw data offset直接获得raw data信息而无需关注Meta Data信息。正如前面所述,往TDMS中添加信息时实际上是添加了新的segment,因此无需关心segment之前的内容是什么,也就确保了写TDMS文件的速度与TDMS文件的大小无关,保证了高速存取。值得注意的是,TDMS物理结构中的segment与其三层次逻辑结构没有任何关系。可能一个通道对应着多个segment,也可能一个segment中包含多个通道。当把数据写入硬盘时就是产生一个segment,而把数据从硬盘中读取出来时也就是把一个个的segment内容读取出来。
图2 TDMS文件的物理结构
2技术简介
通过上述介绍,可以知道通过二进制的方式读取TDMS文件,需要依次读取segment,然后解析segment的内容。
基本的读取流程如下图所示:
由于文件是按照二进制存储的,需要对每个字节进行解析,每个部分的数据格式不尽相同,因此,在读取以上数据的时候,需要TDMS文件支持的数据类型有所了解。
2.1数据类型
以下是数据类型的结构与说明
typedef enum {
tdsTypeVoid,//空类型
tdsTypeI8,//有符号整型8位
tdsTypeI16, //有符号整型16位
tdsTypeI32, //有符号整型32位
tdsTypeI64, //有符号整型64位
tdsTypeU8, //无符号整型8位
tdsTypeU16, //无符号整型16位
tdsTypeU32, //无符号整型32位
tdsTypeU64, //无符号整型64位
tdsTypeSingleFloat, //单精度浮点
tdsTypeDoubleFloat, //双精度浮点
tdsTypeExtendedFloat,
tdsTypeSingleFloatWithUnit=0x19,
tdsTypeDoubleFloatWithUnit,
tdsTypeExtendedFloatWithUnit,
tdsTypeString=0x20, //字符串
tdsTypeBoolean=0x21, //布尔型
tdsTypeTimeStamp=0x44, //时间戳
tdsTypeFixedPoint=0x4F,
tdsTypeComplexSingleFloat=0x08000c,
tdsTypeComplexDoubleFloat=0x10000d,
tdsTypeDAQmxRawData=0xFFFFFFFF
} tdsDataType;
在TDMS文件读取过程中,需要通过按照相应的长度读取相应的数据类型并转换成所需要的数据。以下是各个结构的详细说明:
2.1 Lead In
Lead In结构的详细描述见下表
二进制结构 (16进制) | 描述 |
54 44 53 6D | "TDSm" tag |
0E 00 00 00 | ToC mask 0x1110 (segment contains object list, meta data, raw data) |
69 12 00 00 | Version number (4713) |
E6 00 00 00 00 00 00 00 | Next segment offset (value: 230) |
DE 00 00 00 00 00 00 00 | Raw data offset (value: 222) |
前四个字节是字符串“TDSm”文件标识;接下来四个字节是Toc(Table of Contents)掩码,指示了segment中是否含有Metadata和RawData等信息;接下来四个字节是该文件的版本信息;接下来8个字节指示了下一个segment的位置;最后8个字节指示了RawData的位置。
2.2 Meta Data
- segment中对象的数量(4个字节)
- 对象路径字符串(表格中的表头)的长度length(4个字节)
- 对象路径字符串(length个字节)
- Raw data index指示RawData的类型(4个字节)
- 属性properties的数量 (4个字节)
- 属性,包含
-
- 属性名称(string)
- 属性的数据类型
- 属性值
- 在读取过程中,我们通过一个全局的Channel类型的数组来进行保存属性对象
2.3 Raw Data
这一部分是我们最关心的数据,根据RawDataIndex来解析数据类型之后,也保存在Channel类型的数组中。
最终,我们获取到若干个Channel类型的数据,进行解析后即可得到存入的数据。
在了解以上知识的基础上,参考github上一个java解析TDMS的项目
https://github.com/yc97/TDMSDecoder
这个项目已经好久没动静了,虽然基本思路很正,但是不适用我的TDMS文件,存在很多问题,然后我对其进行修改完善,用到了自己的代码里
写的不够详细,后续再写