bitfield in ctypes @Python

Table of Content:

在C语言中,解析二进制数据常使用位域.可以直接从二进制数据映射到C语言中的结构提.

在Python中,如果要解析二进制数据的话,可以使用struct模块.

struct模块中定义了unpack方法可以用作这个用途.但是unpack其中内涵了一个小的描述语言,掌握其中的细节比较麻烦,而且谁能够记住这些东西呢.这个必然是write once, read never的代码,除非加上几倍的注释在附近.

而利用Python的ctypes模块中的Structure的功能,我们也可以写出类似于C语言位域的代码,同时结合Python的特点,可以写出更为简明易懂的代码.

闲话不说了,直接上例子.(也只有一个例子)

VP8是Google出品的一种视频编码方式,VP8视频流可以存储在一种IVF的文件格式中.这种文件格式本身非常简单.

- 32字节的文件头
- 12字节的帧头,帧头的前4个字节为帧数据的长度.

据此我们可以完成一个简单的IVF文件的probe代码.


import ctypes

tab = "  "


def showFields(obj, name, prefix=tab):
    s = name
    w = max([len(i) for i, _ in obj._fields_])
    for k, _ in obj._fields_:
        s += "\n%s%s: %s" % (prefix, k.ljust(w), getattr(obj, k))
    return s


class IvfHeader(ctypes.LittleEndianStructure):
    _fields_ = [
        ("signature", ctypes.c_char * 4),
        ("version", ctypes.c_ushort),
        ("lenght", ctypes.c_ushort),
        ("fourcc", ctypes.c_char * 4),
        ("width", ctypes.c_ushort),
        ("height", ctypes.c_ushort),
        ("framerate", ctypes.c_uint),
        ("timescale", ctypes.c_uint),
        ("numFrames", ctypes.c_uint),
        ("reserved", ctypes.c_uint),
    ]

    def __str__(self):
        return showFields(self, "IVF Header")


class IvfFrmHeader(ctypes.LittleEndianStructure):
    # with out _pack_, this will as 64bit alignment
    _pack_ = 1
    _fields_ = [
        ("size", ctypes.c_uint32),
        ("timestamp", ctypes.c_uint64),
    ]

    def __str__(self):
        return showFields(self, "IVF Header")

HdrLen = ctypes.sizeof(IvfHeader)
FrmLen = ctypes.sizeof(IvfFrmHeader)


def parseIVF(fn):
    with open(fn, "rb") as fp:
        data = fp.read()
        hdr = IvfHeader.from_buffer_copy(data)
        data = data[HdrLen:]
        print hdr
        while len(data) > 0:
            frm = IvfFrmHeader.from_buffer_copy(data)
            print frm
            data = data[FrmLen + frm.size:]

其中__str__函数,是Python对象的默认转换为字符串的函数. 重载这个函数,可以直接简单的调用print来打印对象的信息.

原始代码::