| 1 | # This file is part of Xpra. |
| 2 | # Copyright (C) 2012-2014 Antoine Martin <antoine@devloop.org.uk> |
| 3 | # Xpra is released under the terms of the GNU GPL v2, or, at your option, any |
| 4 | # later version. See the file COPYING for details. |
| 5 | |
| 6 | import weakref |
| 7 | from xpra.log import Logger |
| 8 | log = Logger("decoder", "ffmpeg") |
| 9 | |
| 10 | from xpra.codecs.codec_constants import get_subsampling_divs |
| 11 | from xpra.codecs.image_wrapper import ImageWrapper |
| 12 | from xpra.codecs.libav_common.av_log cimport override_logger, restore_logger #@UnresolvedImport |
| 13 | from xpra.util import bytestostr |
| 14 | |
| 15 | |
| 16 | ctypedef unsigned long size_t |
| 17 | ctypedef unsigned char uint8_t |
| 18 | |
| 19 | |
| 20 | cdef extern from "../../buffers/buffers.h": |
| 21 | object memory_as_pybuffer(void* ptr, Py_ssize_t buf_len, int readonly) |
| 22 | int object_as_buffer(object obj, const void ** buffer, Py_ssize_t * buffer_len) |
| 23 | int get_buffer_api_version() |
| 24 | |
| 25 | cdef extern from "string.h": |
| 26 | void * memcpy(void * destination, void * source, size_t num) nogil |
| 27 | void * memset(void * ptr, int value, size_t num) nogil |
| 28 | void free(void * ptr) nogil |
| 29 | |
| 30 | |
| 31 | cdef extern from "../../inline.h": |
| 32 | pass |
| 33 | |
| 34 | cdef extern from "../../buffers/memalign.h": |
| 35 | void *xmemalign(size_t size) |
| 36 | |
| 37 | |
| 38 | cdef extern from "libavutil/mem.h": |
| 39 | void av_free(void *ptr) |
| 40 | |
| 41 | cdef extern from "libavutil/error.h": |
| 42 | int av_strerror(int errnum, char *errbuf, size_t errbuf_size) |
| 43 | |
| 44 | cdef extern from "libavcodec/version.h": |
| 45 | int LIBAVCODEC_VERSION_MAJOR |
| 46 | int LIBAVCODEC_VERSION_MINOR |
| 47 | int LIBAVCODEC_VERSION_MICRO |
| 48 | |
| 49 | #why can't we define this inside the avcodec.h section? (beats me) |
| 50 | ctypedef unsigned int AVCodecID |
| 51 | ctypedef long AVPixelFormat |
| 52 | |
| 53 | |
| 54 | cdef extern from "libavutil/pixfmt.h": |
| 55 | AVPixelFormat AV_PIX_FMT_NONE |
| 56 | AVPixelFormat AV_PIX_FMT_YUV420P |
| 57 | |
| 58 | cdef extern from "libavcodec/avcodec.h": |
| 59 | int CODEC_FLAG2_FAST |
| 60 | |
| 61 | ctypedef struct AVFrame: |
| 62 | uint8_t **data |
| 63 | int *linesize |
| 64 | int format |
| 65 | void *opaque |
| 66 | ctypedef struct AVCodec: |
| 67 | pass |
| 68 | ctypedef struct AVDictionary: |
| 69 | pass |
| 70 | ctypedef struct AVPacket: |
| 71 | uint8_t *data |
| 72 | int size |
| 73 | |
| 74 | ctypedef struct AVCodecContext: |
| 75 | int width |
| 76 | int height |
| 77 | AVPixelFormat pix_fmt |
| 78 | int thread_safe_callbacks |
| 79 | int thread_count |
| 80 | int thread_type |
| 81 | int flags |
| 82 | int flags2 |
| 83 | int refcounted_frames |
| 84 | |
| 85 | AVCodecID AV_CODEC_ID_H264 |
| 86 | AVCodecID AV_CODEC_ID_H265 |
| 87 | AVCodecID AV_CODEC_ID_VP8 |
| 88 | AVCodecID AV_CODEC_ID_VP9 |
| 89 | AVCodecID AV_CODEC_ID_MPEG4 |
| 90 | |
| 91 | #init and free: |
| 92 | void avcodec_register_all() |
| 93 | AVCodec *avcodec_find_decoder(AVCodecID id) |
| 94 | AVCodecContext *avcodec_alloc_context3(const AVCodec *codec) |
| 95 | int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options) |
| 96 | AVFrame* av_frame_alloc() |
| 97 | void av_frame_free(AVFrame **frame) |
| 98 | int avcodec_close(AVCodecContext *avctx) |
| 99 | |
| 100 | #actual decoding: |
| 101 | void av_init_packet(AVPacket *pkt) nogil |
| 102 | void avcodec_get_frame_defaults(AVFrame *frame) nogil |
| 103 | int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture, |
| 104 | int *got_picture_ptr, const AVPacket *avpkt) nogil |
| 105 | |
| 106 | void av_frame_unref(AVFrame *frame) nogil |
| 107 | |
| 108 | |
| 109 | FORMAT_TO_ENUM = { |
| 110 | "YUV420P" : AV_PIX_FMT_YUV420P, |
| 111 | "YUV422P" : AV_PIX_FMT_YUV422P, |
| 112 | "YUV444P" : AV_PIX_FMT_YUV444P, |
| 113 | "RGB" : AV_PIX_FMT_RGB24, |
| 114 | "XRGB" : AV_PIX_FMT_0RGB, |
| 115 | "BGRX" : AV_PIX_FMT_BGR0, |
| 116 | "ARGB" : AV_PIX_FMT_ARGB, |
| 117 | "BGRA" : AV_PIX_FMT_BGRA, |
| 118 | "GBRP" : AV_PIX_FMT_GBRP, |
| 119 | } |
| 120 | |
| 121 | COLORSPACES = FORMAT_TO_ENUM.keys() |
| 122 | ENUM_TO_FORMAT = {} |
| 123 | for pix_fmt, av_enum in FORMAT_TO_ENUM.items(): |
| 124 | ENUM_TO_FORMAT[av_enum] = pix_fmt |
| 125 | |
| 126 | def get_version(): |
| 127 | return (LIBAVCODEC_VERSION_MAJOR, LIBAVCODEC_VERSION_MINOR, LIBAVCODEC_VERSION_MICRO) |
| 128 | |
| 129 | avcodec_register_all() |
| 130 | CODECS = [] |
| 131 | if avcodec_find_decoder(AV_CODEC_ID_H264)!=NULL: |
| 132 | CODECS.append("h264") |
| 133 | if avcodec_find_decoder(AV_CODEC_ID_VP8)!=NULL: |
| 134 | CODECS.append("vp8") |
| 135 | if avcodec_find_decoder(AV_CODEC_ID_VP9)!=NULL: |
| 136 | CODECS.append("vp9") |
| 137 | if avcodec_find_decoder(AV_CODEC_ID_H265)!=NULL: |
| 138 | CODECS.append("h265") |
| 139 | if avcodec_find_decoder(AV_CODEC_ID_MPEG4)!=NULL: |
| 140 | CODECS.append("mpeg4") |
| 141 | log("enc_ffmpeg.init_module: CODECS=%s", CODECS) |
| 142 | |
| 143 | |
| 144 | def init_module(): |
| 145 | log("enc_ffmpeg.init_module()") |
| 146 | override_logger() |
| 147 | |
| 148 | def cleanup_module(): |
| 149 | log("enc_ffmpeg.cleanup_module()") |
| 150 | restore_logger() |
| 151 | |
| 152 | def get_type(): |
| 153 | return "ffmpeg" |
| 154 | |
| 155 | def get_info(): |
| 156 | f = {} |
| 157 | for e in get_encodings(): |
| 158 | f["formats.%s" % e] = get_input_colorspaces(e) |
| 159 | return {"version" : get_version(), |
| 160 | "encodings" : get_encodings(), |
| 161 | "buffer_api" : get_buffer_api_version(), |
| 162 | "formats" : f, |
| 163 | } |
| 164 | |
| 165 | def get_encodings(): |
| 166 | global CODECS |
| 167 | return CODECS |
| 168 | |
| 169 | def get_input_colorspaces(encoding): |
| 170 | return ["YUV420P"] |
| 171 | |
| 172 | def get_output_colorspace(encoding, csc): |
| 173 | if encoding not in CODECS: |
| 174 | return "" |
| 175 | return "YUV420P" |
| 176 | |
| 177 | |
| 178 | cdef void clear_frame(AVFrame *frame): |
| 179 | assert frame!=NULL, "frame is not set!" |
| 180 | for i in range(4): |
| 181 | frame.data[i] = NULL |
| 182 | |
| 183 | |
| 184 | cdef class AVFrameWrapper: |
| 185 | """ |
| 186 | Wraps an AVFrame so we can free it |
| 187 | once both xpra and avcodec are done with it. |
| 188 | """ |
| 189 | cdef AVCodecContext *avctx |
| 190 | cdef AVFrame *frame |
| 191 | cdef int xpra_freed |
| 192 | |
| 193 | cdef set_context(self, AVCodecContext *avctx, AVFrame *frame): |
| 194 | self.avctx = avctx |
| 195 | self.frame = frame |
| 196 | log("%s.set_context(%#x, %#x)", self, <unsigned long> avctx, <unsigned long> frame) |
| 197 | |
| 198 | def __dealloc__(self): |
| 199 | #By the time this wrapper is garbage collected, |
| 200 | #we must have freed it! |
| 201 | assert self.frame==NULL and self.avctx==NULL, "frame was freed by both, but not actually freed!" |
| 202 | |
| 203 | def __str__(self): |
| 204 | if self.frame==NULL: |
| 205 | return "AVFrameWrapper(NULL)" |
| 206 | return "AVFrameWrapper(%#x)" % <unsigned long> self.frame |
| 207 | |
| 208 | def xpra_free(self): |
| 209 | log("%s.xpra_free()", self) |
| 210 | self.free() |
| 211 | |
| 212 | cdef free(self): |
| 213 | log("%s.free() context=%#x, frame=%#x", self, <unsigned long> self.avctx, <unsigned long> self.frame) |
| 214 | if self.avctx!=NULL and self.frame!=NULL: |
| 215 | av_frame_unref(self.frame) |
| 216 | self.frame = NULL |
| 217 | self.avctx = NULL |
| 218 | |
| 219 | |
| 220 | class AVImageWrapper(ImageWrapper): |
| 221 | """ |
| 222 | Wrapper which allows us to call xpra_free on the decoder |
| 223 | when the image is freed, or once we have made a copy of the pixels. |
| 224 | """ |
| 225 | |
| 226 | def __repr__(self): #@DuplicatedSignature |
| 227 | return ImageWrapper.__repr__(self)+"-(%s)" % self.av_frame |
| 228 | |
| 229 | def free(self): #@DuplicatedSignature |
| 230 | log("AVImageWrapper.free()") |
| 231 | ImageWrapper.free(self) |
| 232 | self.xpra_free_frame() |
| 233 | |
| 234 | def clone_pixel_data(self): |
| 235 | log("AVImageWrapper.clone_pixel_data()") |
| 236 | ImageWrapper.clone_pixel_data(self) |
| 237 | self.xpra_free_frame() |
| 238 | |
| 239 | def xpra_free_frame(self): |
| 240 | av_frame = self.av_frame |
| 241 | log("AVImageWrapper.xpra_free_frame() av_frame=%s", av_frame) |
| 242 | if av_frame: |
| 243 | self.av_frame = None |
| 244 | av_frame.xpra_free() |
| 245 | |
| 246 | |
| 247 | cdef class Encoder: |
| 248 | """ |
| 249 | This wraps the AVCodecContext and its configuration, |
| 250 | also tracks AVFrames. |
| 251 | It also handles reconstructing a single ImageWrapper |
| 252 | constructed from 3-pass decoding (see plane_sizes). |
| 253 | """ |
| 254 | cdef AVCodec *codec |
| 255 | cdef AVCodecContext *codec_ctx |
| 256 | cdef AVPixelFormat pix_fmt |
| 257 | cdef AVPixelFormat actual_pix_fmt |
| 258 | cdef object colorspace |
| 259 | cdef object weakref_images |
| 260 | cdef AVFrame *av_frame |
| 261 | #this is the actual number of images we have returned |
| 262 | cdef unsigned long frames |
| 263 | cdef int width |
| 264 | cdef int height |
| 265 | cdef object encoding |
| 266 | |
| 267 | cdef object __weakref__ |
| 268 | |
| 269 | def init_context(self, encoding, int width, int height, colorspace): |
| 270 | cdef int r |
| 271 | cdef int i |
| 272 | assert encoding in CODECS |
| 273 | self.encoding = encoding |
| 274 | self.width = width |
| 275 | self.height = height |
| 276 | assert colorspace in COLORSPACES, "invalid colorspace: %s" % colorspace |
| 277 | self.colorspace = "" |
| 278 | for x in COLORSPACES: |
| 279 | if x==colorspace: |
| 280 | self.colorspace = x |
| 281 | break |
| 282 | if not self.colorspace: |
| 283 | log.error("invalid pixel format: %s", colorspace) |
| 284 | return False |
| 285 | self.pix_fmt = FORMAT_TO_ENUM.get(colorspace, AV_PIX_FMT_NONE) |
| 286 | if self.pix_fmt==AV_PIX_FMT_NONE: |
| 287 | log.error("invalid pixel format: %s", colorspace) |
| 288 | return False |
| 289 | self.actual_pix_fmt = self.pix_fmt |
| 290 | |
| 291 | avcodec_register_all() |
| 292 | |
| 293 | cdef AVCodecID CodecID |
| 294 | if self.encoding=="h264": |
| 295 | CodecID = AV_CODEC_ID_H264 |
| 296 | elif self.encoding=="h265": |
| 297 | CodecID = AV_CODEC_ID_H265 |
| 298 | elif self.encoding=="vp8": |
| 299 | CodecID = AV_CODEC_ID_VP8 |
| 300 | elif self.encoding=="vp9": |
| 301 | CodecID = AV_CODEC_ID_VP9 |
| 302 | elif self.encoding=="mpeg4": |
| 303 | CodecID = AV_CODEC_ID_MPEG4 |
| 304 | else: |
| 305 | raise Exception("invalid codec; %s" % self.encoding) |
| 306 | self.codec = avcodec_find_decoder(CodecID) |
| 307 | if self.codec==NULL: |
| 308 | log.error("codec %s not found!" % self.encoding) |
| 309 | return False |
| 310 | |
| 311 | #from here on, we have to call clean_decoder(): |
| 312 | self.codec_ctx = avcodec_alloc_context3(self.codec) |
| 313 | if self.codec_ctx==NULL: |
| 314 | log.error("failed to allocate codec context!") |
| 315 | self.clean_decoder() |
| 316 | return False |
| 317 | |
| 318 | self.codec_ctx.refcounted_frames = 1 |
| 319 | self.codec_ctx.width = width |
| 320 | self.codec_ctx.height = height |
| 321 | self.codec_ctx.pix_fmt = self.pix_fmt |
| 322 | #self.codec_ctx.get_buffer2 = avcodec_get_buffer2 |
| 323 | #self.codec_ctx.release_buffer = avcodec_release_buffer |
| 324 | self.codec_ctx.thread_safe_callbacks = 1 |
| 325 | self.codec_ctx.thread_type = 2 #FF_THREAD_SLICE: allow more than one thread per frame |
| 326 | self.codec_ctx.thread_count = 0 #auto |
| 327 | self.codec_ctx.flags2 |= CODEC_FLAG2_FAST #may cause "no deblock across slices" - which should be fine |
| 328 | r = avcodec_open2(self.codec_ctx, self.codec, NULL) |
| 329 | if r<0: |
| 330 | log.error("could not open codec: %s", self.av_error_str(r)) |
| 331 | self.clean_decoder() |
| 332 | return False |
| 333 | #up to 3 AVFrame objects used: |
| 334 | self.av_frame = av_frame_alloc() |
| 335 | if self.av_frame==NULL: |
| 336 | log.error("could not allocate an AVFrame for decoding") |
| 337 | self.clean_decoder() |
| 338 | return False |
| 339 | self.frames = 0 |
| 340 | #to keep track of images not freed yet: |
| 341 | #(we want a weakref.WeakSet() but this is python2.7+ only..) |
| 342 | self.weakref_images = [] |
| 343 | #register this decoder in the global dictionary: |
| 344 | log("dec_avcodec.Decoder.init_context(%s, %s, %s) self=%s", width, height, colorspace, self.get_info()) |
| 345 | return True |
| 346 | |
| 347 | def clean(self): |
| 348 | self.clean_decoder() |
| 349 | self.codec = NULL |
| 350 | self.pix_fmt = 0 |
| 351 | self.actual_pix_fmt = 0 |
| 352 | self.colorspace = "" |
| 353 | self.weakref_images = [] |
| 354 | self.av_frame = NULL #should be redundant |
| 355 | self.frames = 0 |
| 356 | self.width = 0 |
| 357 | self.height = 0 |
| 358 | self.encoding = "" |
| 359 | |
| 360 | |
| 361 | def clean_decoder(self): |
| 362 | cdef int r, i |
| 363 | log("%s.clean_decoder()", self) |
| 364 | #we may have images handed out, ensure we don't reference any memory |
| 365 | #that needs to be freed using avcodec_release_buffer(..) |
| 366 | #as this requires the context to still be valid! |
| 367 | #copying the pixels should ensure we free the AVFrameWrapper associated with it: |
| 368 | if self.weakref_images: |
| 369 | images = [y for y in [x() for x in self.weakref_images] if y is not None] |
| 370 | self.weakref_images = [] |
| 371 | log("clean_decoder() cloning pixels for images still in use: %s", images) |
| 372 | for img in images: |
| 373 | if not img.freed: |
| 374 | img.clone_pixel_data() |
| 375 | |
| 376 | if self.av_frame!=NULL: |
| 377 | log("clean_decoder() freeing AVFrame: %#x", <unsigned long> self.av_frame) |
| 378 | av_frame_free(&self.av_frame) |
| 379 | #redundant: self.frame = NULL |
| 380 | |
| 381 | cdef unsigned long ctx_key #@DuplicatedSignature |
| 382 | log("clean_decoder() freeing AVCodecContext: %#x", <unsigned long> self.codec_ctx) |
| 383 | if self.codec_ctx!=NULL: |
| 384 | r = avcodec_close(self.codec_ctx) |
| 385 | if r!=0: |
| 386 | log.warn("error closing decoder context %#x: %s", <unsigned long> self.codec_ctx, self.av_error_str(r)) |
| 387 | av_free(self.codec_ctx) |
| 388 | self.codec_ctx = NULL |
| 389 | log("clean_decoder() done") |
| 390 | |
| 391 | cdef av_error_str(self, errnum): |
| 392 | cdef char[128] err_str |
| 393 | cdef int i = 0 |
| 394 | if av_strerror(errnum, err_str, 128)==0: |
| 395 | while i<128 and err_str[i]!=0: |
| 396 | i += 1 |
| 397 | return bytestostr(err_str[:i]) |
| 398 | return str(errnum) |
| 399 | |
| 400 | def __repr__(self): #@DuplicatedSignature |
| 401 | if self.is_closed(): |
| 402 | return "dec_avcodec.Decoder(*closed*)" |
| 403 | return "dec_avcodec.Decoder(%s)" % self.get_info() |
| 404 | |
| 405 | def get_info(self): #@DuplicatedSignature |
| 406 | info = {"version" : get_version(), |
| 407 | "encoding" : self.encoding, |
| 408 | "formats" : get_input_colorspaces(self.encoding), |
| 409 | "type" : self.get_type(), |
| 410 | "frames" : self.frames, |
| 411 | "width" : self.width, |
| 412 | "height" : self.height, |
| 413 | } |
| 414 | if self.colorspace: |
| 415 | info["colorspace"] = self.colorspace |
| 416 | info["actual_colorspace"] = self.get_actual_colorspace() |
| 417 | if not self.is_closed(): |
| 418 | info["decoder_width"] = self.codec_ctx.width |
| 419 | info["decoder_height"] = self.codec_ctx.height |
| 420 | else: |
| 421 | info["closed"] = True |
| 422 | return info |
| 423 | |
| 424 | def is_closed(self): |
| 425 | return self.codec_ctx==NULL |
| 426 | |
| 427 | def __dealloc__(self): #@DuplicatedSignature |
| 428 | self.clean() |
| 429 | |
| 430 | def get_width(self): |
| 431 | return self.width |
| 432 | |
| 433 | def get_height(self): |
| 434 | return self.height |
| 435 | |
| 436 | def get_encoding(self): |
| 437 | return self.encoding |
| 438 | |
| 439 | def get_type(self): #@DuplicatedSignature |
| 440 | return "avcodec" |
| 441 | |
| 442 | def decompress_image(self, input, options): |
| 443 | cdef unsigned char * padded_buf = NULL |
| 444 | cdef const unsigned char * buf = NULL |
| 445 | cdef Py_ssize_t buf_len = 0 |
| 446 | cdef int size |
| 447 | cdef int len = 0 |
| 448 | cdef int nplanes |
| 449 | cdef int got_picture |
| 450 | cdef AVPacket avpkt |
| 451 | cdef unsigned long frame_key #@DuplicatedSignature |
| 452 | cdef AVFrameWrapper framewrapper |
| 453 | cdef AVFrame *av_frame |
| 454 | cdef object img |
| 455 | assert self.codec_ctx!=NULL, "no codec context! (not initialized or already closed)" |
| 456 | assert self.codec!=NULL |
| 457 | |
| 458 | #copy the whole input buffer into a padded C buffer: |
| 459 | assert object_as_buffer(input, <const void**> &buf, &buf_len)==0 |
| 460 | padded_buf = <unsigned char *> xmemalign(buf_len+128) |
| 461 | memcpy(padded_buf, buf, buf_len) |
| 462 | memset(padded_buf+buf_len, 0, 128) |
| 463 | |
| 464 | #note: plain RGB output, will redefine those: |
| 465 | out = [] |
| 466 | strides = [] |
| 467 | outsize = 0 |
| 468 | |
| 469 | #ensure we can detect if the frame buffer got allocated: |
| 470 | clear_frame(self.av_frame) |
| 471 | #now safe to run without gil: |
| 472 | with nogil: |
| 473 | av_init_packet(&avpkt) |
| 474 | avpkt.data = <uint8_t *> (padded_buf) |
| 475 | avpkt.size = buf_len |
| 476 | len = avcodec_decode_video2(self.codec_ctx, self.av_frame, &got_picture, &avpkt) |
| 477 | if len<0: |
| 478 | av_frame_unref(self.av_frame) |
| 479 | log("%s.decompress_image(%s:%s, %s) avcodec_decode_video2 failure: %s", self, type(input), buf_len, options, self.av_error_str(len)) |
| 480 | log.error("avcodec_decode_video2 %s decoding failure:", self.encoding) |
| 481 | log.error(" %s", self.av_error_str(len)) |
| 482 | return None |
| 483 | if len==0: |
| 484 | av_frame_unref(self.av_frame) |
| 485 | log("%s.decompress_image(%s:%s, %s) avcodec_decode_video2 failed to decode the stream", self, type(input), buf_len, options) |
| 486 | log.error("avcodec_decode_video2 %s decoding failure - no stream", self.encoding) |
| 487 | return None |
| 488 | |
| 489 | if self.actual_pix_fmt!=self.av_frame.format: |
| 490 | if self.av_frame.format==-1: |
| 491 | log.error("avcodec error decoding %i bytes of %s data", buf_len, self.encoding) |
| 492 | log.error(" frame %i", self.frames) |
| 493 | log.error(" options=%s", options) |
| 494 | log.error(" decoder state:") |
| 495 | for k,v in self.get_info().items(): |
| 496 | log.error(" %s = %s", k, v) |
| 497 | return None |
| 498 | self.actual_pix_fmt = self.av_frame.format |
| 499 | if self.actual_pix_fmt not in ENUM_TO_FORMAT: |
| 500 | av_frame_unref(self.av_frame) |
| 501 | log.error("unknown output pixel format: %s, expected %s (%s)", self.actual_pix_fmt, self.pix_fmt, self.colorspace) |
| 502 | return None |
| 503 | log("avcodec actual output pixel format is %s (%s), expected %s (%s)", self.actual_pix_fmt, self.get_actual_colorspace(), self.pix_fmt, self.colorspace) |
| 504 | |
| 505 | cs = self.get_actual_colorspace() |
| 506 | if cs.endswith("P"): |
| 507 | divs = get_subsampling_divs(cs) |
| 508 | nplanes = 3 |
| 509 | for i in range(3): |
| 510 | _, dy = divs[i] |
| 511 | if dy==1: |
| 512 | height = self.codec_ctx.height |
| 513 | elif dy==2: |
| 514 | height = (self.codec_ctx.height+1)>>1 |
| 515 | else: |
| 516 | av_frame_unref(self.av_frame) |
| 517 | raise Exception("invalid height divisor %s" % dy) |
| 518 | stride = self.av_frame.linesize[i] |
| 519 | size = height * stride |
| 520 | outsize += size |
| 521 | |
| 522 | out.append(memory_as_pybuffer(<void *>self.av_frame.data[i], size, True)) |
| 523 | strides.append(stride) |
| 524 | log("decompress_image() read back yuv plane %s: %s bytes", i, size) |
| 525 | else: |
| 526 | #RGB mode: "out" is a single buffer |
| 527 | strides = self.av_frame.linesize[0]+self.av_frame.linesize[1]+self.av_frame.linesize[2] |
| 528 | outsize = self.codec_ctx.height * strides |
| 529 | out = memory_as_pybuffer(<void *>self.av_frame.data[0], outsize, True) |
| 530 | nplanes = 0 |
| 531 | log("decompress_image() read back rgb buffer: %s bytes", outsize) |
| 532 | |
| 533 | #FIXME: we could lose track of framewrappers if an error occurs before the end: |
| 534 | framewrapper = AVFrameWrapper() |
| 535 | framewrapper.set_context(self.codec_ctx, self.av_frame) |
| 536 | |
| 537 | if outsize==0: |
| 538 | av_frame_unref(self.av_frame) |
| 539 | raise Exception("output size is zero!") |
| 540 | |
| 541 | free(padded_buf) |
| 542 | assert self.codec_ctx.width>=self.width, "codec width is smaller than our width: %s<%s" % (self.codec_ctx.width, self.width) |
| 543 | assert self.codec_ctx.height>=self.height, "codec height is smaller than our height: %s<%s" % (self.codec_ctx.height, self.height) |
| 544 | img = AVImageWrapper(0, 0, self.width, self.height, out, cs, 24, strides, nplanes, thread_safe=False) |
| 545 | img.av_frame = framewrapper |
| 546 | self.frames += 1 |
| 547 | #add to weakref list after cleaning it up: |
| 548 | self.weakref_images = [x for x in self.weakref_images if x() is not None] |
| 549 | self.weakref_images.append(weakref.ref(img)) |
| 550 | log("%s.decompress_image(%s:%s, %s)=%s", self, type(input), buf_len, options, img) |
| 551 | return img |
| 552 | |
| 553 | |
| 554 | def get_colorspace(self): |
| 555 | return self.colorspace |
| 556 | |
| 557 | def get_actual_colorspace(self): |
| 558 | return ENUM_TO_FORMAT.get(self.actual_pix_fmt, "unknown/invalid") |
| 559 | |
| 560 | |
| 561 | def selftest(full=False): |
| 562 | global CODECS |
| 563 | from xpra.codecs.codec_checks import testdecoder |
| 564 | from xpra.codecs.dec_avcodec2 import decoder |
| 565 | global CODECS |
| 566 | CODECS = testdecoder(decoder, full) |