From 9fe74b62de7113548912693200da0753b6eb3ab0 Mon Sep 17 00:00:00 2001 From: Sebastian <sebastian@baldr.asgard> Date: Sun, 20 Oct 2019 19:05:44 +0300 Subject: [PATCH] initial python code to decode ftl snapshots --- python/ftlstream.py | 31 +++++++ python/libde265.py | 191 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 python/ftlstream.py create mode 100644 python/libde265.py diff --git a/python/ftlstream.py b/python/ftlstream.py new file mode 100644 index 000000000..ec0155133 --- /dev/null +++ b/python/ftlstream.py @@ -0,0 +1,31 @@ +import msgpack +from collections import namedtuple + +_packet = namedtuple("Packet", ["codec", "definition", "block_total", "block_number", "flags", "data"]) +_stream_packet = namedtuple("StreamPacket", ["timestamp", "streamID", "chanel_count", "channel"]) + +class FTLStream: + def __init__(self, file): + self._file = open(file, "br") + + try: + magic = self._file.read(5) + if magic[:4] != bytearray(ord(c) for c in "FTLF"): + raise Exception("wrong magic") + + self._unpacker = msgpack.Unpacker(self._file, raw=True, use_list=False) + + except Exception as ex: + self._file.close() + raise ex + + def __del__(self): + self._file.close() + + def read(self): + # TODO: Different methods for reading different types? + return self._read_next() + + def _read_next(self): + v1, v2 = self._unpacker.unpack() + return _stream_packet._make(v1), _packet._make(v2) \ No newline at end of file diff --git a/python/libde265.py b/python/libde265.py new file mode 100644 index 000000000..567d4872d --- /dev/null +++ b/python/libde265.py @@ -0,0 +1,191 @@ +''' +Python wrapper for libde265. Only decoding is (partly) implemented. + +Requirements: + * libde265 library (libde265.so.0) + * numpy + * skimage (rescaling) + +''' + +import ctypes + +import numpy as np +from skimage.transform import rescale + +# error codes copied from header (de265.h) + +DE265_OK = 0 +DE265_ERROR_NO_SUCH_FILE=1 +DE265_ERROR_COEFFICIENT_OUT_OF_IMAGE_BOUNDS=4 +DE265_ERROR_CHECKSUM_MISMATCH=5 +DE265_ERROR_CTB_OUTSIDE_IMAGE_AREA=6 +DE265_ERROR_OUT_OF_MEMORY=7 +DE265_ERROR_CODED_PARAMETER_OUT_OF_RANGE=8 +DE265_ERROR_IMAGE_BUFFER_FULL=9 +DE265_ERROR_CANNOT_START_THREADPOOL=10 +DE265_ERROR_LIBRARY_INITIALIZATION_FAILED=11 +DE265_ERROR_LIBRARY_NOT_INITIALIZED=12 +DE265_ERROR_WAITING_FOR_INPUT_DATA=13 +DE265_ERROR_CANNOT_PROCESS_SEI=14 +DE265_ERROR_PARAMETER_PARSING=15 +DE265_ERROR_NO_INITIAL_SLICE_HEADER=16 +DE265_ERROR_PREMATURE_END_OF_SLICE=17 +DE265_ERROR_UNSPECIFIED_DECODING_ERROR=18 +DE265_ERROR_NOT_IMPLEMENTED_YET = 502 +DE265_WARNING_NO_WPP_CANNOT_USE_MULTITHREADING = 1000 +DE265_WARNING_WARNING_BUFFER_FULL=1001 +DE265_WARNING_PREMATURE_END_OF_SLICE_SEGMENT=1002 +DE265_WARNING_INCORRECT_ENTRY_POINT_OFFSET=1003 +DE265_WARNING_CTB_OUTSIDE_IMAGE_AREA=1004 +DE265_WARNING_SPS_HEADER_INVALID=1005 +DE265_WARNING_PPS_HEADER_INVALID=1006 +DE265_WARNING_SLICEHEADER_INVALID=1007 +DE265_WARNING_INCORRECT_MOTION_VECTOR_SCALING=1008 +DE265_WARNING_NONEXISTING_PPS_REFERENCED=1009 +DE265_WARNING_NONEXISTING_SPS_REFERENCED=1010 +DE265_WARNING_BOTH_PREDFLAGS_ZERO=1011 +DE265_WARNING_NONEXISTING_REFERENCE_PICTURE_ACCESSED=1012 +DE265_WARNING_NUMMVP_NOT_EQUAL_TO_NUMMVQ=1013 +DE265_WARNING_NUMBER_OF_SHORT_TERM_REF_PIC_SETS_OUT_OF_RANGE=1014 +DE265_WARNING_SHORT_TERM_REF_PIC_SET_OUT_OF_RANGE=1015 +DE265_WARNING_FAULTY_REFERENCE_PICTURE_LIST=1016 +DE265_WARNING_EOSS_BIT_NOT_SET=1017 +DE265_WARNING_MAX_NUM_REF_PICS_EXCEEDED=1018 +DE265_WARNING_INVALID_CHROMA_FORMAT=1019 +DE265_WARNING_SLICE_SEGMENT_ADDRESS_INVALID=1020 +DE265_WARNING_DEPENDENT_SLICE_WITH_ADDRESS_ZERO=1021 +DE265_WARNING_NUMBER_OF_THREADS_LIMITED_TO_MAXIMUM=1022 +DE265_NON_EXISTING_LT_REFERENCE_CANDIDATE_IN_SLICE_HEADER=1023 +DE265_WARNING_CANNOT_APPLY_SAO_OUT_OF_MEMORY=1024 +DE265_WARNING_SPS_MISSING_CANNOT_DECODE_SEI=1025 +DE265_WARNING_COLLOCATED_MOTION_VECTOR_OUTSIDE_IMAGE_AREA=1026 + +libde265 = ctypes.cdll.LoadLibrary("libde265.so.0") + +libde265.de265_get_error_text.argtypes = [ctypes.c_void_p] +libde265.de265_get_error_text.restype = ctypes.c_char_p +libde265.de265_get_version_number_major.restype = ctypes.c_uint32 +libde265.de265_get_version_number_minor.restype = ctypes.c_uint32 + +libde265.de265_new_decoder.restype = ctypes.c_void_p + +libde265.de265_free_decoder.argtypes = [ctypes.c_void_p] +libde265.de265_free_decoder.restype = ctypes.c_int + +libde265.de265_start_worker_threads.argtypes = [ctypes.c_void_p, ctypes.c_int] +libde265.de265_start_worker_threads.restype = ctypes.c_int + +libde265.de265_push_data.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p] +libde265.de265_push_data.restype = ctypes.c_int + +libde265.de265_push_NAL.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p] +libde265.de265_push_data.restype = ctypes.c_int + +libde265.de265_push_end_of_frame.argtypes = [ctypes.c_void_p] + +libde265.de265_flush_data.argtypes = [ctypes.c_void_p] +libde265.de265_flush_data.restype = ctypes.c_int + +libde265.de265_decode.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_int)] +libde265.de265_decode.restype = ctypes.c_int + +libde265.de265_get_next_picture.argtypes = [ctypes.c_void_p] +libde265.de265_get_next_picture.restype = ctypes.c_void_p + +libde265.de265_get_image_width.argtypes = [ctypes.c_void_p, ctypes.c_int] +libde265.de265_get_image_width.restype = ctypes.c_int + +libde265.de265_get_image_height.argtypes = [ctypes.c_void_p, ctypes.c_int] +libde265.de265_get_image_height.restype = ctypes.c_int + +libde265.de265_get_bits_per_pixel.argtypes = [ctypes.c_void_p, ctypes.c_int] +libde265.de265_get_bits_per_pixel.restype = ctypes.c_int + +libde265.de265_get_image_plane.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.POINTER(ctypes.c_int)] +libde265.de265_get_image_plane.restype = ctypes.POINTER(ctypes.c_char) + +class Decoder: + def __init__(self, size, threads=1): + self.size_ = size + self.more_ = ctypes.c_int() + self.out_stride_ = ctypes.c_int() + self.ctx_ = libde265.de265_new_decoder() + err = libde265.de265_start_worker_threads(self.ctx_, threads) + if err: + raise Exception(self.get_error_str(err)) + + def __del__(self): + libde265.de265_free_decoder(self.ctx_) + + def get_error_str(self, code): + return libde265.de265_get_error_text(code).decode("ascii") + + def push_data(self, data): + if not isinstance(data, bytes): + raise ValueError("expected bytes") + + err = libde265.de265_push_data(self.ctx_, data, len(data), None, None) + + if err: + raise Exception(self.get_error_str(err)) + + def push_end_of_frame(self): + err = libde265.de265_push_end_of_frame(self.ctx_) + + if err: + raise Exception(self.get_error_str(err)) + + def push_NAL(self, data): + if not isinstance(data, bytes): + raise ValueError("expected bytes") + + err = libde265.de265_push_NAL(self.ctx_, data, len(data), None, None) + + if err: + raise Exception(self.get_error_str(err)) + + def decode(self): + err = libde265.de265_decode(self.ctx_, self.more_) + + if err and err != DE265_ERROR_WAITING_FOR_INPUT_DATA: + raise Exception(self.get_error_str(err)) + + return self.more_.value != 0 + + def flush_data(self): + libde265.flush_data(self.ctx_) + + def get_next_picture(self): + ''' + Returns next decoded frame. Image in YCrCb format. If no frame available + returns None. + + TODO: Skimage's ycrcb2rgb() does not look correct. Is Cr/Cb channel + created correctly? + ''' + img = libde265.de265_get_next_picture(self.ctx_) + + if not img: + return None + + res = np.zeros((self.size_[0], self.size_[1], 3), dtype=np.uint8) + + for c in range(0, 3): + size = (libde265.de265_get_image_height(img, c), libde265.de265_get_image_width(img, c)) + bpp = libde265.de265_get_bits_per_pixel(img, c) + img_ptr = libde265.de265_get_image_plane(img, c, self.out_stride_) + scale_y = self.size_[0] / size[0] + scale_x = self.size_[1] / size[1] + + if bpp != 8: + raise NotImplementedError("unsupported bits per pixel %i" % bpp) + + if scale_x != scale_y: + raise NotImplementedError("unsupported subsampling") + + channel = np.asarray(bytearray(img_ptr[:size[0] * size[1]]), dtype=np.uint8) + channel.shape = size + res[:,:,c] = (rescale(channel, scale_x) * 255).astype(np.uint8) + + return res \ No newline at end of file -- GitLab