将二进制时间戳转换为字符串
我正在尝试用Python解析一种专有的二进制格式(Wintec NAL)。现在已经有一段能正常工作的C语言代码(作者:Dennis Heynlein),我想把它移植到Python上。
不过,我在理解这段C代码的某些部分时遇到了困难。以下是C语言中对这种二进制格式的定义:
/*
* File extension:. NAL
* File format: binary, 32 byte fixed block length
*/
/*
* For now we will read raw structs direct from the data file, ignoring byte
* order issues (since the data is in little-endian form compatible with i386)
*
* XXX TODO: write marshalling functions to read records in the proper
* byte-order agnostic way.
*/
#pragma pack (1)
typedef struct nal_data32 {
unsigned char point_type; /* 0 - normal, 1 - start, 2 - marked */
unsigned char padding_1;
unsigned int second: 6, minute: 6, hour: 5;
unsigned int day: 5, month: 4, year: 6; /* add 2000 to year */
signed int latitude; /* divide by 1E7 for degrees */
signed int longitude; /* divide by 1E7 for degrees */
unsigned short height; /* meters */
signed char temperature; /* °C */
unsigned short pressure; /* mbar */
unsigned char cadence; /* RPM */
unsigned char pulse; /* BPM */
signed char slope; /* degrees */
signed short compass; /* °Z axis */
signed short roll; /* °X axis */
signed short yaw; /* °Y axis */
unsigned char speed; /* km/h */
unsigned char bike; /* ID# 0-3 */
unsigned char padding_2;
unsigned char padding_3;
} nal_t;
我正在使用python-bitstring库来在Python中实现相同的功能,但我对上面给出的时间格式感到困惑,不知道该如何将其转换为Python。
from bitstring import ConstBitStream
nal_format=('''
uint:8,
uint:8,
bin:32,
intle:32,
intle:32,
uint:16,
uint:8,
uint:16,
uint:8,
uint:8,
uint:8,
uint:16,
uint:16,
uint:16,
uint:8,
uint:8,
uint:8,
uint:8
''')
f = ConstBitStream('0x01009f5a06379ae1cb13f7a6b62bca010dc703000000c300fefff9ff00000000')
f.pos=0
#type,padding1,second,minute,hour,day,month,year,lat,lon,height,temp,press,cad,pulse,slope,compass,roll,yaw,speed,bike,padding2,padding3=f.peeklist(nal_format)
type,padding1,time,lat,lon,height,temp,press,cad,pulse,slope,compass,roll,yaw,speed,bike,padding2,padding3=f.readlist(nal_format)
print type
print padding1
#print second
#print minute
#print hour
#print day
#print month
#print year
print time
print lat
print lon
虽然我已经弄明白了纬度和经度需要用小端格式来定义,但我不知道如何调整32位的时间戳,以使其符合C语言定义中的格式(而且我也没能找到与“高度”对应的掩码,因此也没尝试后面的字段)。
以上十六进制字符串的值如下:
- 日期:2013/12/03-T05:42:31
- 位置:东经73.3390583°,北纬33.2128666°
- 方向:195°,滚转 -2°,偏航 -7°
- 高度:458米
- 温度:13 °C
- 气压:967 mb
2 个回答
在'C'结构中,时间戳是一个'C'位域。编译器会根据冒号后面的数字来分配在这个更大字段定义中的位数。在这个例子里,它是一个无符号整型(4个字节)。想要更详细的解释,可以看看这里。位域的一个大问题是,位的分配是根据计算机的字节序来决定的,所以它们在不同的系统间不太兼容。
你的Python格式声明似乎有个错误。可能需要额外分配一个4字节的无符号整型来表示日期。像这样:
nal_format=('''
uint:8,
uint:8,
bin:32,
bin:32,
intle:32,
intle:32,
''')
要在Python中表示位域,可以使用Python的位数组来表示这些位。可以查看这个链接。
还有一点需要注意的是,结构中的pack(1)。它告诉编译器在一个字节的边界上对齐。换句话说,就是在字段之间不要添加任何填充。通常情况下,对齐是4个字节,这样编译器会让每个字段从4字节的边界开始。想了解更多信息,可以查看这里。
我对 bitstring
这个东西不太熟悉,所以我会把你的输入转换成打包的二进制数据,然后用 struct
来处理它。如果你对这部分不感兴趣,可以直接跳到后面。
import binascii
packed = binascii.unhexlify('01009f5a06379ae1cb13f7a6b62bca010dc703000000c300fefff9ff00000000')
如果你想的话,我可以更详细地讲讲这部分。其实就是把 '0100...'
转换成 b'\x01\x00...'
。
现在,解包的时候唯一需要注意的是,你只想解包一个无符号整数,因为那个比特字段正好适合32位(一个无符号整数的宽度):
format = '<ccIiiHbHBBbhhhBBBB'
import struct
struct.unpack(format,packed)
Out[49]:
('\x01',
'\x00',
923163295,
...
)
这样就把输出转换成我们可以使用的格式。你可以像之前那样,把它解包成一长串变量。
现在,你的问题似乎是关于如何对 time
(上面提到的 923163295
)进行掩码处理,以从比特字段中获取正确的值。这只是一些简单的数学运算:
second_mask = 2**6 - 1
minute_mask = second_mask << 6
hour_mask = (2**5 - 1) << (6+6)
day_mask = hour_mask << 5
month_mask = (2**4 - 1) << (6+6+5+5)
year_mask = (2**6 - 1) << (6+6+5+5+4)
time & second_mask
Out[59]: 31
(time & minute_mask) >> 6
Out[63]: 42
(time & hour_mask) >> (6+6)
Out[64]: 5
(time & day_mask) >> (6+6+5)
Out[65]: 3
(time & month_mask) >> (6+6+5+5)
Out[66]: 12
(time & year_mask) >> (6+6+5+5+4)
Out[67]: 13L
如果用函数的形式来看,这一切就显得更自然了:
def unmask(num, width, offset):
return (num & (2**width - 1) << offset) >> offset
想想看,这可以重新排列成:
def unmask(num, width, offset):
return (num >> offset) & (2**width - 1)
unmask(time, 6, 0)
Out[77]: 31
unmask(time, 6, 6)
Out[78]: 42
#etc
如果你想要更复杂一点,
from itertools import starmap
from functools import partial
width_offsets = [(6,0),(6,6),(5,12),(5,17),(4,22),(6,26)]
list(starmap(partial(unmask,time), width_offsets))
Out[166]: [31, 42, 5, 3, 12, 13L]
把所有这些数字格式化正确,最后就能得到预期的日期/时间:
'20{:02d}/{:02d}/{:02d}-T{:02d}:{:02d}:{:02d}'.format(*reversed(_))
Out[167]: '2013/12/03-T05:42:31'
(可能有一种方法可以用 bitstring
模块优雅地完成所有这些位运算,但我觉得从基本原理出发解决问题更让人满意。)