--- ../../tags/v0.10.x/src/xpra/codecs/dec_avcodec/decoder.pyx 2014-01-07 15:19:02.590025141 +0700 +++ ./xpra/codecs/dec_avcodec/decoder.pyx 2014-01-03 23:19:11.289552989 +0700 @@ -10,8 +10,13 @@ debug = debug_if_env(log, "XPRA_AVCODEC_DEBUG") error = log.error +#some consumers need a writeable buffer (ie: OpenCL...) +READ_ONLY = False + from xpra.codecs.codec_constants import get_subsampling_divs, get_colorspace_from_avutil_enum, RGB_FORMATS from xpra.codecs.image_wrapper import ImageWrapper +from xpra.util import AtomicInteger + include "constants.pxi" @@ -23,6 +28,7 @@ ctypedef int Py_ssize_t ctypedef object PyObject object PyBuffer_FromMemory(void *ptr, Py_ssize_t size) + object PyBuffer_FromReadWriteMemory(void *ptr, Py_ssize_t size) int PyObject_AsReadBuffer(object obj, void ** buffer, Py_ssize_t * buffer_len) except -1 @@ -50,11 +56,15 @@ cdef extern from "libavutil/mem.h": void av_free(void *ptr) +cdef extern from "libavutil/error.h": + int av_strerror(int errnum, char *errbuf, size_t errbuf_size) + cdef extern from "libavcodec/avcodec.h": ctypedef struct AVFrame: uint8_t **data int *linesize int format + void *opaque ctypedef struct AVCodec: pass ctypedef struct AVCodecID: @@ -79,6 +89,8 @@ AVPixelFormat PIX_FMT_NONE AVCodecID CODEC_ID_H264 + AVCodecID CODEC_ID_VP8 + #AVCodecID CODEC_ID_VP9 #init and free: void avcodec_register_all() @@ -103,16 +115,18 @@ def get_version(): return (LIBAVCODEC_VERSION_MAJOR, LIBAVCODEC_VERSION_MINOR, LIBAVCODEC_VERSION_MICRO) +def get_type(): + return "avcodec" MIN_AVCODEC_VERSION = 54 COLORSPACES = None FORMAT_TO_ENUM = {} ENUM_TO_FORMAT = {} if LIBAVCODEC_VERSION_MAJOR frame.data[0] + frame_key = frame.opaque return frame_key cdef void clear_frame(AVFrame *frame): @@ -178,12 +208,12 @@ frame.data[i] = NULL -cdef get_decoder(AVCodecContext *avctx): +cdef Decoder get_decoder(AVCodecContext *avctx): cdef unsigned long ctx_key = get_context_key(avctx) global DECODERS decoder = DECODERS.get(ctx_key) assert decoder is not None, "decoder not found for context %s" % hex(ctx_key) - return decoder + return decoder @@ -192,16 +222,19 @@ we create an AVFrameWrapper object and register it with the Decoder for this context. """ + global frame_alloc_counter cdef unsigned long frame_key = 0 cdef AVFrameWrapper frame_wrapper + cdef Decoder decoder cdef int ret decoder = get_decoder(avctx) ret = avcodec_default_get_buffer(avctx, frame) if ret==0: - frame_key = get_frame_key(frame) frame_wrapper = AVFrameWrapper() frame_wrapper.set_context(avctx, frame) - decoder.add_framewrapper(frame_key, frame_wrapper) + frame_key = frame_alloc_counter.increase() + frame.opaque = frame_key + decoder.add_framewrapper(frame_wrapper, frame_key) #debug("avcodec_get_buffer(%s, %s) ret=%s, decoder=%s, frame pointer=%s", # hex( avctx), hex(frame_key), ret, decoder, hex(frame_key)) return ret @@ -211,6 +244,8 @@ we tell the Decoder to manage it. """ cdef unsigned long frame_key = get_frame_key(frame) + cdef Decoder decoder #@DuplicatedSignature + debug("avcodec_release_buffer(%s, %s) frame_key=%s", hex( avctx), hex( frame), hex(frame_key)) decoder = get_decoder(avctx) decoder.av_free(frame_key) @@ -230,6 +265,7 @@ self.frame = frame self.av_freed = 0 self.xpra_freed = 0 + debug("%s.set_context(%s, %s)", self, hex( avctx), hex( frame)) def __dealloc__(self): #debug("CSCImage.__dealloc__()") @@ -240,10 +276,12 @@ assert self.frame==NULL and self.avctx==NULL, "frame was freed by both, but not actually freed!" def __str__(self): + if self.frame==NULL: + return "AVFrameWrapper(NULL)" return "AVFrameWrapper(%s)" % hex(self.get_key()) def xpra_free(self): - debug("%s.xpra_free()", self) + debug("%s.xpra_free() av_freed=%s", self, self.av_freed) self.xpra_freed = 1 if self.av_freed==0: return False @@ -251,26 +289,24 @@ return True def av_free(self): - debug("%s.av_free()", self) + debug("%s.av_free() xpra_freed=%s", self, self.xpra_freed) self.av_freed = 1 if self.xpra_freed==0: return False self.free() return True - def free(self): - debug("%s.free()", self) + cdef free(self): + debug("%s.free() context=%s, frame=%s", self, hex( self.avctx), hex( self.frame)) if self.avctx!=NULL and self.frame!=NULL: avcodec_default_release_buffer(self.avctx, self.frame) - #do we need this? - #avcodec_free_frame(frame) - self.avctx = NULL self.frame = NULL + self.avctx = NULL def get_key(self): return get_frame_key(self.frame) - + class AVImageWrapper(ImageWrapper): """ Wrapper which allows us to call xpra_free on the decoder @@ -281,15 +317,17 @@ return ImageWrapper.__str__(self)+"-(%s)" % self.av_frame def free(self): #@DuplicatedSignature - debug("AVImageWrapper.free() av_frame=%s", self.av_frame) + debug("AVImageWrapper.free()") ImageWrapper.free(self) self.xpra_free_frame() def clone_pixel_data(self): + debug("AVImageWrapper.clone_pixel_data()") ImageWrapper.clone_pixel_data(self) self.xpra_free_frame() def xpra_free_frame(self): + debug("AVImageWrapper.xpra_free_frame() av_frame=%s", self.av_frame) if self.av_frame: assert self.decoder, "no decoder set!" self.decoder.xpra_free(self.av_frame.get_key()) @@ -313,9 +351,13 @@ cdef int frames cdef int width cdef int height + cdef object encoding - def init_context(self, int width, int height, colorspace): + def init_context(self, encoding, int width, int height, colorspace): + cdef int r init_colorspaces() + assert encoding in ("vp8", "h264") + self.encoding = encoding self.width = width self.height = height assert colorspace in COLORSPACES, "invalid colorspace: %s" % colorspace @@ -335,10 +377,18 @@ avcodec_register_all() - self.codec = avcodec_find_decoder(CODEC_ID_H264) - if self.codec==NULL: - error("codec H264 not found!") - return False + if self.encoding=="h264": + self.codec = avcodec_find_decoder(CODEC_ID_H264) + if self.codec==NULL: + error("codec H264 not found!") + return False + else: + assert self.encoding=="vp8" + self.codec = avcodec_find_decoder(CODEC_ID_VP8) + if self.codec==NULL: + error("codec VP8 not found!") + return False + #from here on, we have to call clean_decoder(): self.codec_ctx = avcodec_alloc_context3(self.codec) if self.codec_ctx==NULL: @@ -355,8 +405,9 @@ self.codec_ctx.thread_type = 2 #FF_THREAD_SLICE: allow more than one thread per frame self.codec_ctx.thread_count = 0 #auto self.codec_ctx.flags2 |= CODEC_FLAG2_FAST #may cause "no deblock across slices" - which should be fine - if avcodec_open2(self.codec_ctx, self.codec, NULL) < 0: - error("could not open codec") + r = avcodec_open2(self.codec_ctx, self.codec, NULL) + if r<0: + error("could not open codec: %s", self.av_error_str(r)) self.clean_decoder() return False self.frame = avcodec_alloc_frame() @@ -381,11 +432,12 @@ self.clean_decoder() def clean_decoder(self): + cdef int r #@DuplicateSignature debug("%s.clean_decoder()", self) #we may have images handed out, ensure we don't reference any memory #that needs to be freed using avcodec_release_buffer(..) #as this requires the context to still be valid! - #copying the pixels should ensure we free the AVFrameWrapper associated with it: + #copying the pixels should ensure we free the AVFrameWrapper associated with it: if self.weakref_images: images = [y for y in [x() for x in self.weakref_images] if y is not None] self.weakref_images = [] @@ -401,7 +453,9 @@ cdef unsigned long ctx_key #@DuplicatedSignature debug("clean_decoder() freeing AVCodecContext: %s", hex( self.codec_ctx)) if self.codec_ctx!=NULL: - avcodec_close(self.codec_ctx) + r = avcodec_close(self.codec_ctx) + if r!=0: + log.warn("error closing decoder context %s: %s", hex( self.codec_ctx), self.av_error_str(r)) av_free(self.codec_ctx) global DECODERS ctx_key = get_context_key(self.codec_ctx) @@ -410,6 +464,11 @@ self.codec_ctx = NULL debug("clean_decoder() done") + cdef av_error_str(self, errnum): + cdef char[128] err_str + if av_strerror(errnum, err_str, 128)==0: + return str(err_str[:128]) + return str(errnum) def __str__(self): #@DuplicatedSignature return "dec_avcodec.Decoder(%s)" % self.get_info() @@ -417,14 +476,15 @@ def get_info(self): info = { "type" : self.get_type(), - "colorspace": self.get_colorspace(), - "actual_colorspace": self.get_actual_colorspace(), "frames" : self.frames, "width" : self.width, "height" : self.height, } if self.framewrappers is not None: info["buffers"] = len(self.framewrappers) + if self.colorspace: + info["colorspace"] = self.colorspace + info["actual_colorspace"] = self.get_actual_colorspace() if not self.is_closed(): info["decoder_width"] = self.codec_ctx.width info["decoder_height"] = self.codec_ctx.height @@ -444,8 +504,11 @@ def get_height(self): return self.height - def get_type(self): - return "x264" + def get_encoding(self): + return "h264" + + def get_type(self): #@DuplicatedSignature + return "avcodec" def decompress_image(self, input, options): cdef unsigned char * padded_buf = NULL @@ -455,7 +518,8 @@ cdef int got_picture cdef AVPacket avpkt cdef unsigned long frame_key #@DuplicatedSignature - cdef object framewrapper, img + cdef AVFrameWrapper framewrapper + cdef object img assert self.codec_ctx!=NULL assert self.codec!=NULL #copy input buffer into padded C buffer: @@ -474,7 +538,7 @@ free(padded_buf) if len < 0: #for testing add: or options.get("frame", 0)%100==99: framewrapper = self.frame_error() - log.warn("%s.decompress_image(%s:%s, %s) avcodec_decode_video2 failure: %s, framewrapper=%s", self, type(input), buf_len, options, len, framewrapper) + log.warn("%s.decompress_image(%s:%s, %s) avcodec_decode_video2 failure: %s, framewrapper=%s", self, type(input), buf_len, options, self.av_error_str(len), framewrapper) return None #raise Exception("avcodec_decode_video2 failed to decode this frame and returned %s, decoder=%s" % (len, self.get_info())) @@ -505,23 +569,32 @@ stride = self.frame.linesize[i] size = height * stride outsize += size - plane = PyBuffer_FromMemory(self.frame.data[i], size) + if READ_ONLY: + plane = PyBuffer_FromMemory(self.frame.data[i], size) + else: + plane = PyBuffer_FromReadWriteMemory(self.frame.data[i], size) out.append(plane) strides.append(stride) else: strides = self.frame.linesize[0]+self.frame.linesize[1]+self.frame.linesize[2] outsize = self.codec_ctx.height * strides - out = PyBuffer_FromMemory(self.frame.data[0], outsize) + if READ_ONLY: + out = PyBuffer_FromMemory(self.frame.data[0], outsize) + else: + out = PyBuffer_FromReadWriteMemory(self.frame.data[0], outsize) nplanes = 0 if outsize==0: self.frame_error() raise Exception("output size is zero!") - img = AVImageWrapper(0, 0, self.codec_ctx.width, self.codec_ctx.height, out, cs, 24, strides, nplanes) + assert self.codec_ctx.width>=self.width, "codec width is smaller than our width: %s<%s" % (self.codec_ctx.width, self.width) + assert self.codec_ctx.height>=self.height, "codec height is smaller than our height: %s<%s" % (self.codec_ctx.height, self.height) + img = AVImageWrapper(0, 0, self.width, self.height, out, cs, 24, strides, nplanes) img.decoder = self img.av_frame = None - #we must find the frame wrapper used by our get_buffer override: frame_key = get_frame_key(self.frame) framewrapper = self.get_framewrapper(frame_key) + assert framewrapper is not None, "framewrapper not found for frame key %s" % frame_key + #framewrapper.set_frame(self.frame) img.av_frame = framewrapper self.frames += 1 #add to weakref list after cleaning it up: @@ -532,39 +605,43 @@ return img - def frame_error(self): - cdef unsigned long frame_key = get_frame_key(self.frame) + cdef AVFrameWrapper frame_error(self): + cdef unsigned long frame_key #@DuplicatedSignature + log("frame_error() frame_key=%s", hex( self.frame)) + frame_key = get_frame_key(self.frame) framewrapper = self.get_framewrapper(frame_key, True) - log("frame_error() freeing %s", framewrapper) + log("frame_error() av-freeing %s", hex( self.frame), framewrapper) if framewrapper: #avcodec had allocated a buffer, make sure we don't claim to be using it: self.av_free(frame_key) - return framewrapper + return framewrapper - def get_framewrapper(self, frame_key, ignore_missing=False): + cdef AVFrameWrapper get_framewrapper(self, frame_key, ignore_missing=False): framewrapper = self.framewrappers.get(int(frame_key)) - #debug("get_framewrapper(%s)=%s, known frame keys=%s", frame_key, framewrapper, - # [hex(x) for x in self.framewrappers.keys()]) assert ignore_missing or framewrapper is not None, "frame not found for pointer %s, known frame keys=%s" % (hex(frame_key), [hex(x) for x in self.framewrappers.keys()]) - return framewrapper + return framewrapper - def add_framewrapper(self, frame_key, frame_wrapper): - debug("add_framewrapper(%s, %s) known frame keys: %s", hex(frame_key), frame_wrapper, + cdef add_framewrapper(self, AVFrameWrapper frame_wrapper, unsigned long frame_key): + if len(self.framewrappers)>50: + log.warn("too many frames kept in memory - dec_avcodec is probably leaking memory") + debug("add_framewrapper(%s, %s) known frame keys: %s", frame_wrapper, hex(frame_key), [hex(x) for x in self.framewrappers.keys()]) self.framewrappers[int(frame_key)] = frame_wrapper - def av_free(self, frame_key): #@DuplicatedSignature - avframe = self.get_framewrapper(frame_key) - debug("av_free(%s) framewrapper=%s", hex(frame_key), avframe) - if avframe.av_free(): + cdef av_free(self, unsigned long frame_key): #@DuplicatedSignature + cdef AVFrameWrapper framewrapper #@DuplicatedSignature + framewrapper = self.get_framewrapper(frame_key, True) + debug("av_free(%s) framewrapper=%s", hex(frame_key), framewrapper) + if framewrapper is not None and framewrapper.av_free(): #frame has been freed - remove it from dict: del self.framewrappers[frame_key] - def xpra_free(self, frame_key): #@DuplicatedSignature - avframe = self.get_framewrapper(frame_key) - debug("xpra_free(%s) framewrapper=%s", hex(frame_key), avframe) - if avframe.xpra_free(): + def xpra_free(self, unsigned long frame_key): #@DuplicatedSignature + cdef AVFrameWrapper framewrapper #@DuplicatedSignature + framewrapper = self.get_framewrapper(frame_key) + debug("xpra_free(%s) framewrapper=%s", hex(frame_key), framewrapper) + if framewrapper.xpra_free(): #frame has been freed - remove it from dict: del self.framewrappers[frame_key]