| 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 os |
| 7 | import weakref |
| 8 | from xpra.log import Logger |
| 9 | log = Logger("encoder", "ffmpeg") |
| 10 | |
| 11 | from xpra.codecs.image_wrapper import ImageWrapper |
| 12 | from xpra.codecs.codec_constants import get_subsampling_divs, video_spec |
| 13 | from xpra.codecs.libav_common.av_log cimport override_logger, restore_logger #@UnresolvedImport |
| 14 | from xpra.codecs.libav_common.av_log import suspend_nonfatal_logging, resume_nonfatal_logging |
| 15 | from xpra.util import AtomicInteger, bytestostr, csv, print_nested_dict |
| 16 | |
| 17 | SAVE_TO_FILE = os.environ.get("XPRA_SAVE_TO_FILE") |
| 18 | |
| 19 | |
| 20 | from libc.stdint cimport uint8_t, int64_t, uint8_t |
| 21 | |
| 22 | cdef extern from "string.h": |
| 23 | void free(void * ptr) nogil |
| 24 | |
| 25 | cdef extern from "../../buffers/buffers.h": |
| 26 | int object_as_buffer(object obj, const void ** buffer, Py_ssize_t * buffer_len) |
| 27 | int get_buffer_api_version() |
| 28 | |
| 29 | cdef extern from "../../inline.h": |
| 30 | pass |
| 31 | |
| 32 | cdef extern from "../../buffers/memalign.h": |
| 33 | void *xmemalign(size_t size) nogil |
| 34 | |
| 35 | |
| 36 | cdef extern from "libavutil/mem.h": |
| 37 | void av_free(void *ptr) |
| 38 | |
| 39 | cdef extern from "libavutil/error.h": |
| 40 | int av_strerror(int errnum, char *errbuf, size_t errbuf_size) |
| 41 | |
| 42 | cdef extern from "libavcodec/version.h": |
| 43 | int LIBAVCODEC_VERSION_MAJOR |
| 44 | int LIBAVCODEC_VERSION_MINOR |
| 45 | int LIBAVCODEC_VERSION_MICRO |
| 46 | |
| 47 | #why can't we define this inside the avcodec.h section? (beats me) |
| 48 | ctypedef unsigned int AVCodecID |
| 49 | ctypedef long AVPixelFormat |
| 50 | ctypedef int AVPictureType |
| 51 | |
| 52 | |
| 53 | cdef extern from "libavutil/avutil.h": |
| 54 | int AV_PICTURE_TYPE_NONE |
| 55 | int AV_PICTURE_TYPE_I |
| 56 | int AV_PICTURE_TYPE_P |
| 57 | int AV_PICTURE_TYPE_B |
| 58 | int AV_PICTURE_TYPE_S |
| 59 | int AV_PICTURE_TYPE_SI |
| 60 | int AV_PICTURE_TYPE_SP |
| 61 | int AV_PICTURE_TYPE_BI |
| 62 | |
| 63 | cdef extern from "libavutil/pixfmt.h": |
| 64 | AVPixelFormat AV_PIX_FMT_NONE |
| 65 | AVPixelFormat AV_PIX_FMT_YUV420P |
| 66 | AVPixelFormat AV_PIX_FMT_YUV422P |
| 67 | AVPixelFormat AV_PIX_FMT_YUV444P |
| 68 | AVPixelFormat AV_PIX_FMT_RGB24 |
| 69 | AVPixelFormat AV_PIX_FMT_0RGB |
| 70 | AVPixelFormat AV_PIX_FMT_BGR0 |
| 71 | AVPixelFormat AV_PIX_FMT_ARGB |
| 72 | AVPixelFormat AV_PIX_FMT_BGRA |
| 73 | AVPixelFormat AV_PIX_FMT_GBRP |
| 74 | |
| 75 | cdef extern from "libavcodec/avcodec.h": |
| 76 | int CODEC_FLAG2_FAST |
| 77 | int CODEC_CAP_DRAW_HORIZ_BAND |
| 78 | int CODEC_CAP_DR1 |
| 79 | int CODEC_CAP_TRUNCATED |
| 80 | int CODEC_CAP_HWACCEL |
| 81 | int CODEC_CAP_DELAY |
| 82 | int CODEC_CAP_SMALL_LAST_FRAME |
| 83 | int CODEC_CAP_HWACCEL_VDPAU |
| 84 | int CODEC_CAP_SUBFRAMES |
| 85 | int CODEC_CAP_EXPERIMENTAL |
| 86 | int CODEC_CAP_CHANNEL_CONF |
| 87 | int CODEC_CAP_NEG_LINESIZES |
| 88 | int CODEC_CAP_FRAME_THREADS |
| 89 | int CODEC_CAP_SLICE_THREADS |
| 90 | int CODEC_CAP_PARAM_CHANGE |
| 91 | int CODEC_CAP_AUTO_THREADS |
| 92 | int CODEC_CAP_VARIABLE_FRAME_SIZE |
| 93 | int CODEC_CAP_INTRA_ONLY |
| 94 | int CODEC_CAP_LOSSLESS |
| 95 | |
| 96 | ctypedef struct AVFrame: |
| 97 | uint8_t **data |
| 98 | int *linesize |
| 99 | int width |
| 100 | int height |
| 101 | int format |
| 102 | int key_frame |
| 103 | int64_t pts |
| 104 | int coded_picture_number |
| 105 | int display_picture_number |
| 106 | int quality |
| 107 | void *opaque |
| 108 | AVPictureType pict_type |
| 109 | ctypedef struct AVCodec: |
| 110 | int capabilities |
| 111 | const char *name |
| 112 | const char *long_name |
| 113 | ctypedef struct AVDictionary: |
| 114 | pass |
| 115 | ctypedef struct AVPacket: |
| 116 | uint8_t *data |
| 117 | int size |
| 118 | |
| 119 | ctypedef struct AVRational: |
| 120 | int num |
| 121 | int den |
| 122 | |
| 123 | ctypedef struct AVCodecContext: |
| 124 | int width |
| 125 | int height |
| 126 | AVPixelFormat pix_fmt |
| 127 | int thread_safe_callbacks |
| 128 | int thread_count |
| 129 | int thread_type |
| 130 | int flags |
| 131 | int flags2 |
| 132 | int refcounted_frames |
| 133 | int max_b_frames |
| 134 | int has_b_frames |
| 135 | int gop_size |
| 136 | int delay |
| 137 | AVRational framerate |
| 138 | AVRational time_base |
| 139 | |
| 140 | ctypedef struct AVFormatContext: |
| 141 | pass |
| 142 | |
| 143 | AVCodecID AV_CODEC_ID_H264 |
| 144 | AVCodecID AV_CODEC_ID_H265 |
| 145 | AVCodecID AV_CODEC_ID_VP8 |
| 146 | AVCodecID AV_CODEC_ID_VP9 |
| 147 | AVCodecID AV_CODEC_ID_MPEG4 |
| 148 | |
| 149 | #init and free: |
| 150 | void avcodec_register_all() |
| 151 | AVCodec *avcodec_find_encoder(AVCodecID id) |
| 152 | AVCodecContext *avcodec_alloc_context3(const AVCodec *codec) |
| 153 | int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options) |
| 154 | int avcodec_send_frame(AVCodecContext *avctx,const AVFrame *frame) nogil |
| 155 | int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt) nogil |
| 156 | |
| 157 | int av_write_frame(AVFormatContext *s, AVPacket *pkt) |
| 158 | AVFrame* av_frame_alloc() |
| 159 | void av_frame_free(AVFrame **frame) |
| 160 | int avcodec_close(AVCodecContext *avctx) |
| 161 | void av_frame_unref(AVFrame *frame) nogil |
| 162 | void av_init_packet(AVPacket *pkt) nogil |
| 163 | void av_packet_unref(AVPacket *pkt) nogil |
| 164 | |
| 165 | |
| 166 | CAPS = { |
| 167 | CODEC_CAP_DRAW_HORIZ_BAND : "DRAW_HORIZ_BAND", |
| 168 | CODEC_CAP_DR1 : "DR1", |
| 169 | CODEC_CAP_TRUNCATED : "TRUNCATED", |
| 170 | CODEC_CAP_HWACCEL : "HWACCEL", |
| 171 | CODEC_CAP_DELAY : "DELAY", |
| 172 | CODEC_CAP_SMALL_LAST_FRAME : "SMALL_LAST_FRAME", |
| 173 | CODEC_CAP_HWACCEL_VDPAU : "HWACCEL_VDPAU", |
| 174 | CODEC_CAP_SUBFRAMES : "SUBFRAMES", |
| 175 | CODEC_CAP_EXPERIMENTAL : "EXPERIMENTAL", |
| 176 | CODEC_CAP_CHANNEL_CONF : "CHANNEL_CONF", |
| 177 | CODEC_CAP_NEG_LINESIZES : "NEG_LINESIZES", |
| 178 | CODEC_CAP_FRAME_THREADS : "FRAME_THREADS", |
| 179 | CODEC_CAP_SLICE_THREADS : "SLICE_THREADS", |
| 180 | CODEC_CAP_PARAM_CHANGE : "PARAM_CHANGE", |
| 181 | CODEC_CAP_AUTO_THREADS : "AUTO_THREADS", |
| 182 | CODEC_CAP_VARIABLE_FRAME_SIZE : "VARIABLE_FRAME_SIZE", |
| 183 | CODEC_CAP_INTRA_ONLY : "INTRA_ONLY", |
| 184 | CODEC_CAP_LOSSLESS : "LOSSLESS", |
| 185 | } |
| 186 | log("CODEC_CAP:") |
| 187 | print_nested_dict(dict((hex(abs(k)),v) for k,v in CAPS.items()), print_fn=log.debug) |
| 188 | |
| 189 | PICTURE_TYPE = { |
| 190 | AV_PICTURE_TYPE_NONE : "NONE", |
| 191 | AV_PICTURE_TYPE_I : "I", |
| 192 | AV_PICTURE_TYPE_P : "P", |
| 193 | AV_PICTURE_TYPE_B : "B", |
| 194 | AV_PICTURE_TYPE_S : "S", |
| 195 | AV_PICTURE_TYPE_SI : "SI", |
| 196 | AV_PICTURE_TYPE_SP : "SP", |
| 197 | AV_PICTURE_TYPE_BI : "BI", |
| 198 | } |
| 199 | log("AV_PICTURE:") |
| 200 | print_nested_dict(PICTURE_TYPE, print_fn=log.debug) |
| 201 | |
| 202 | FORMAT_TO_ENUM = { |
| 203 | "YUV420P" : AV_PIX_FMT_YUV420P, |
| 204 | "YUV422P" : AV_PIX_FMT_YUV422P, |
| 205 | "YUV444P" : AV_PIX_FMT_YUV444P, |
| 206 | "RGB" : AV_PIX_FMT_RGB24, |
| 207 | "XRGB" : AV_PIX_FMT_0RGB, |
| 208 | "BGRX" : AV_PIX_FMT_BGR0, |
| 209 | "ARGB" : AV_PIX_FMT_ARGB, |
| 210 | "BGRA" : AV_PIX_FMT_BGRA, |
| 211 | "GBRP" : AV_PIX_FMT_GBRP, |
| 212 | } |
| 213 | |
| 214 | COLORSPACES = FORMAT_TO_ENUM.keys() |
| 215 | ENUM_TO_FORMAT = {} |
| 216 | for pix_fmt, av_enum in FORMAT_TO_ENUM.items(): |
| 217 | ENUM_TO_FORMAT[av_enum] = pix_fmt |
| 218 | log("AV_PIX_FMT:") |
| 219 | print_nested_dict(ENUM_TO_FORMAT, print_fn=log.debug) |
| 220 | |
| 221 | def get_version(): |
| 222 | return (LIBAVCODEC_VERSION_MAJOR, LIBAVCODEC_VERSION_MINOR, LIBAVCODEC_VERSION_MICRO) |
| 223 | |
| 224 | avcodec_register_all() |
| 225 | CODECS = [] |
| 226 | if avcodec_find_encoder(AV_CODEC_ID_H264)!=NULL: |
| 227 | CODECS.append("h264") |
| 228 | #if avcodec_find_encoder(AV_CODEC_ID_VP8)!=NULL: |
| 229 | # CODECS.append("vp8") |
| 230 | #if avcodec_find_encoder(AV_CODEC_ID_VP9)!=NULL: |
| 231 | # CODECS.append("vp9") |
| 232 | #if avcodec_find_encoder(AV_CODEC_ID_H265)!=NULL: |
| 233 | # CODECS.append("h265") |
| 234 | #if avcodec_find_encoder(AV_CODEC_ID_MPEG4)!=NULL: |
| 235 | # CODECS.append("mpeg4") |
| 236 | log("enc_ffmpeg.init_module: CODECS=%s", CODECS) |
| 237 | |
| 238 | cdef av_error_str(int errnum): |
| 239 | cdef char[128] err_str |
| 240 | cdef int i = 0 |
| 241 | if av_strerror(errnum, err_str, 128)==0: |
| 242 | while i<128 and err_str[i]!=0: |
| 243 | i += 1 |
| 244 | return bytestostr(err_str[:i]) |
| 245 | return "error %s" % errnum |
| 246 | |
| 247 | DEF EAGAIN = -11 |
| 248 | |
| 249 | |
| 250 | def init_module(): |
| 251 | log("enc_ffmpeg.init_module()") |
| 252 | override_logger() |
| 253 | |
| 254 | def cleanup_module(): |
| 255 | log("enc_ffmpeg.cleanup_module()") |
| 256 | restore_logger() |
| 257 | |
| 258 | def get_type(): |
| 259 | return "ffmpeg" |
| 260 | |
| 261 | generation = AtomicInteger() |
| 262 | def get_info(): |
| 263 | global generation |
| 264 | f = {} |
| 265 | for e in get_encodings(): |
| 266 | f[e] = get_input_colorspaces(e) |
| 267 | return { |
| 268 | "version" : get_version(), |
| 269 | "encodings" : get_encodings(), |
| 270 | "buffer_api" : get_buffer_api_version(), |
| 271 | "formats" : f, |
| 272 | "generation" : generation.get(), |
| 273 | } |
| 274 | |
| 275 | def get_encodings(): |
| 276 | global CODECS |
| 277 | return CODECS |
| 278 | |
| 279 | def get_input_colorspaces(encoding): |
| 280 | return ["YUV420P"] |
| 281 | |
| 282 | def get_output_colorspaces(encoding, csc): |
| 283 | if encoding not in CODECS: |
| 284 | return "" |
| 285 | return ["YUV420P"] |
| 286 | |
| 287 | |
| 288 | MAX_WIDTH, MAX_HEIGHT = 4096, 4096 |
| 289 | def get_spec(encoding, colorspace): |
| 290 | assert encoding in get_encodings(), "invalid encoding: %s (must be one of %s" % (encoding, get_encodings()) |
| 291 | assert colorspace in get_input_colorspaces(encoding), "invalid colorspace: %s (must be one of %s)" % (colorspace, get_input_colorspaces(encoding)) |
| 292 | return video_spec(encoding=encoding, output_colorspaces=get_output_colorspaces(encoding, colorspace), has_lossless_mode=False, |
| 293 | codec_class=Encoder, codec_type=get_type(), |
| 294 | quality=50, speed=50, |
| 295 | setup_cost=50, width_mask=0xFFFE, height_mask=0xFFFE, max_w=MAX_WIDTH, max_h=MAX_HEIGHT) |
| 296 | |
| 297 | |
| 298 | cdef class Encoder: |
| 299 | """ |
| 300 | This wraps the AVCodecContext and its configuration, |
| 301 | """ |
| 302 | cdef AVCodec *codec |
| 303 | cdef AVCodecContext *codec_ctx |
| 304 | cdef AVPixelFormat pix_fmt |
| 305 | cdef object src_format |
| 306 | cdef AVFrame *av_frame |
| 307 | #this is the actual number of images we have returned |
| 308 | cdef unsigned long frames |
| 309 | cdef unsigned int width |
| 310 | cdef unsigned int height |
| 311 | cdef object encoding |
| 312 | cdef object file |
| 313 | |
| 314 | cdef object __weakref__ |
| 315 | |
| 316 | def init_context(self, unsigned int width, unsigned int height, src_format, dst_formats, encoding, int quality, int speed, scaling, options): #@DuplicatedSignature |
| 317 | global CODECS, generation |
| 318 | assert encoding in CODECS |
| 319 | assert src_format in get_input_colorspaces(encoding), "invalid colorspace: %s" % src_format |
| 320 | self.encoding = encoding |
| 321 | self.width = width |
| 322 | self.height = height |
| 323 | self.src_format = src_format |
| 324 | self.pix_fmt = FORMAT_TO_ENUM.get(src_format, AV_PIX_FMT_NONE) |
| 325 | if self.pix_fmt==AV_PIX_FMT_NONE: |
| 326 | raise Exception("invalid pixel format: %s", src_format) |
| 327 | |
| 328 | avcodec_register_all() |
| 329 | cdef AVCodecID CodecID |
| 330 | if self.encoding=="h264": |
| 331 | CodecID = AV_CODEC_ID_H264 |
| 332 | elif self.encoding=="h265": |
| 333 | CodecID = AV_CODEC_ID_H265 |
| 334 | elif self.encoding=="vp8": |
| 335 | CodecID = AV_CODEC_ID_VP8 |
| 336 | elif self.encoding=="vp9": |
| 337 | CodecID = AV_CODEC_ID_VP9 |
| 338 | elif self.encoding=="mpeg4": |
| 339 | CodecID = AV_CODEC_ID_MPEG4 |
| 340 | else: |
| 341 | raise Exception("invalid codec; %s" % self.encoding) |
| 342 | self.codec = avcodec_find_encoder(CodecID) |
| 343 | if self.codec==NULL: |
| 344 | raise Exception("codec %s not found!" % self.encoding) |
| 345 | log("%s: \"%s\", codec flags: %s", self.codec.name, self.codec.long_name, csv(v for k,v in CAPS.items() if (self.codec.capabilities & k))) |
| 346 | |
| 347 | #from here on, we have to call clean_encoder(): |
| 348 | self.codec_ctx = avcodec_alloc_context3(self.codec) |
| 349 | if self.codec_ctx==NULL: |
| 350 | self.clean_encoder() |
| 351 | raise Exception("failed to allocate codec context!") |
| 352 | |
| 353 | cdef int b_frames = int(options.get("b-frames")) |
| 354 | #we need a framerate.. make one up: |
| 355 | self.codec_ctx.framerate.num = 1 |
| 356 | self.codec_ctx.framerate.den = 25 |
| 357 | self.codec_ctx.time_base.num = 1 |
| 358 | self.codec_ctx.time_base.den = 25 |
| 359 | self.codec_ctx.refcounted_frames = 1 |
| 360 | self.codec_ctx.max_b_frames = b_frames*1 |
| 361 | self.codec_ctx.has_b_frames = b_frames |
| 362 | self.codec_ctx.delay = 0 |
| 363 | self.codec_ctx.gop_size = 1 |
| 364 | self.codec_ctx.width = width |
| 365 | self.codec_ctx.height = height |
| 366 | self.codec_ctx.pix_fmt = self.pix_fmt |
| 367 | self.codec_ctx.thread_safe_callbacks = 1 |
| 368 | self.codec_ctx.thread_type = 2 #FF_THREAD_SLICE: allow more than one thread per frame |
| 369 | self.codec_ctx.thread_count = 0 #auto |
| 370 | self.codec_ctx.flags2 |= CODEC_FLAG2_FAST #may cause "no deblock across slices" - which should be fine |
| 371 | #av_opt_set(c->priv_data, "preset", "slow", 0) |
| 372 | cdef int r = avcodec_open2(self.codec_ctx, self.codec, NULL) |
| 373 | if r<0: |
| 374 | self.clean_encoder() |
| 375 | raise Exception("could not open %s encoder context: %s" % (self.encoding, av_error_str(r))) |
| 376 | self.av_frame = av_frame_alloc() |
| 377 | if self.av_frame==NULL: |
| 378 | self.clean_encoder() |
| 379 | raise Exception("could not allocate an AVFrame for encoding") |
| 380 | self.frames = 0 |
| 381 | log("enc_ffmpeg.Encoder.init_context(%s, %s, %s) self=%s", width, height, src_format, self.get_info()) |
| 382 | gen = generation.increase() |
| 383 | if SAVE_TO_FILE is not None: |
| 384 | filename = SAVE_TO_FILE+"ffmpeg-"+self.encoding+"-"+str(gen)+".%s" % encoding |
| 385 | self.file = open(filename, 'wb') |
| 386 | log.info("saving %s stream to %s", encoding, filename) |
| 387 | |
| 388 | def clean(self): |
| 389 | self.clean_encoder() |
| 390 | self.codec = NULL |
| 391 | self.pix_fmt = 0 |
| 392 | self.src_format = "" |
| 393 | self.av_frame = NULL #should be redundant |
| 394 | self.frames = 0 |
| 395 | self.width = 0 |
| 396 | self.height = 0 |
| 397 | self.encoding = "" |
| 398 | f = self.file |
| 399 | if f: |
| 400 | self.file = None |
| 401 | f.close() |
| 402 | |
| 403 | |
| 404 | def clean_encoder(self): |
| 405 | cdef int r, i |
| 406 | log("%s.clean_encoder()", self) |
| 407 | |
| 408 | if self.av_frame!=NULL: |
| 409 | log("clean_encoder() freeing AVFrame: %#x", <unsigned long> self.av_frame) |
| 410 | av_frame_free(&self.av_frame) |
| 411 | #redundant: self.frame = NULL |
| 412 | |
| 413 | cdef unsigned long ctx_key #@DuplicatedSignature |
| 414 | log("clean_encoder() freeing AVCodecContext: %#x", <unsigned long> self.codec_ctx) |
| 415 | if self.codec_ctx!=NULL: |
| 416 | r = avcodec_close(self.codec_ctx) |
| 417 | if r!=0: |
| 418 | log.error("Error: failed to close encoder context %#x", <unsigned long> self.codec_ctx) |
| 419 | log.error(" %s", av_error_str(r)) |
| 420 | av_free(self.codec_ctx) |
| 421 | self.codec_ctx = NULL |
| 422 | log("clean_encoder() done") |
| 423 | |
| 424 | def __repr__(self): #@DuplicatedSignature |
| 425 | if self.is_closed(): |
| 426 | return "enc_ffmpeg.Encoder(*closed*)" |
| 427 | return "enc_ffmpeg.Encoder(%s)" % self.get_info() |
| 428 | |
| 429 | def get_info(self): #@DuplicatedSignature |
| 430 | info = { |
| 431 | "version" : get_version(), |
| 432 | "encoding" : self.encoding, |
| 433 | "formats" : get_input_colorspaces(self.encoding), |
| 434 | "type" : self.get_type(), |
| 435 | "frames" : self.frames, |
| 436 | "width" : self.width, |
| 437 | "height" : self.height, |
| 438 | } |
| 439 | if self.codec: |
| 440 | info["codec"] = self.codec.name[:] |
| 441 | info["description"] = self.codec.long_name[:] |
| 442 | if self.src_format: |
| 443 | info["src_format"] = self.src_format |
| 444 | if not self.is_closed(): |
| 445 | info["encoder_width"] = self.codec_ctx.width |
| 446 | info["encoder_height"] = self.codec_ctx.height |
| 447 | else: |
| 448 | info["closed"] = True |
| 449 | return info |
| 450 | |
| 451 | def is_closed(self): |
| 452 | return self.codec_ctx==NULL |
| 453 | |
| 454 | def __dealloc__(self): #@DuplicatedSignature |
| 455 | self.clean() |
| 456 | |
| 457 | def get_width(self): |
| 458 | return self.width |
| 459 | |
| 460 | def get_height(self): |
| 461 | return self.height |
| 462 | |
| 463 | def get_src_format(self): |
| 464 | return self.src_format |
| 465 | |
| 466 | def get_encoding(self): |
| 467 | return self.encoding |
| 468 | |
| 469 | def get_type(self): #@DuplicatedSignature |
| 470 | return "ffmpeg" |
| 471 | |
| 472 | def get_delayed_frames(self): |
| 473 | return 0 |
| 474 | |
| 475 | def log_av_error(self, image, err_no, options={}): |
| 476 | msg = av_error_str(err_no) |
| 477 | self.log_error(image, msg, options, "error %i" % err_no) |
| 478 | |
| 479 | def log_error(self, image, err, options={}, error_type="error"): |
| 480 | log.error("Error: ffmpeg %s encoding %s:", error_type, self.encoding) |
| 481 | log.error(" '%s'", err) |
| 482 | log.error(" on image %s", image) |
| 483 | log.error(" frame %i", self.frames) |
| 484 | if options: |
| 485 | log.error(" options=%s", options) |
| 486 | log.error(" encoder state:") |
| 487 | for k,v in self.get_info().items(): |
| 488 | log.error(" %s = %s", k, v) |
| 489 | |
| 490 | def compress_image(self, image, int quality=-1, int speed=-1, options={}): |
| 491 | cdef unsigned char * padded_buf = NULL |
| 492 | cdef const unsigned char * buf = NULL |
| 493 | cdef Py_ssize_t buf_len = 0 |
| 494 | cdef int ret |
| 495 | cdef AVPacket avpkt |
| 496 | cdef AVFrame *frame |
| 497 | assert self.codec_ctx!=NULL, "no codec context! (not initialized or already closed)" |
| 498 | assert self.codec!=NULL |
| 499 | |
| 500 | if image: |
| 501 | pixels = image.get_pixels() |
| 502 | istrides = image.get_rowstride() |
| 503 | assert len(pixels)==3, "image pixels does not have 3 planes! (found %s)" % len(pixels) |
| 504 | assert len(istrides)==3, "image strides does not have 3 values! (found %s)" % len(istrides) |
| 505 | |
| 506 | #populate the avframe: |
| 507 | for i in range(4): |
| 508 | if i<3: |
| 509 | assert object_as_buffer(pixels[i], <const void**> &buf, &buf_len)==0, "unable to convert %s to a buffer (plane=%s)" % (type(pixels[i]), i) |
| 510 | #log("plane %s: %i bytes (%ix%i stride=%i)", ["Y", "U", "V"][i], buf_len, self.width, self.height, istrides[i]) |
| 511 | self.av_frame.data[i] = <uint8_t *> buf |
| 512 | self.av_frame.linesize[i] = istrides[i] |
| 513 | else: |
| 514 | self.av_frame.data[i] = NULL |
| 515 | self.av_frame.width = self.width |
| 516 | self.av_frame.height = self.height |
| 517 | self.av_frame.format = self.pix_fmt |
| 518 | self.av_frame.pts = self.frames+1 |
| 519 | self.av_frame.coded_picture_number = self.frames+1 |
| 520 | self.av_frame.display_picture_number = self.frames+1 |
| 521 | if self.frames==0: |
| 522 | self.av_frame.pict_type = AV_PICTURE_TYPE_I |
| 523 | else: |
| 524 | self.av_frame.pict_type = AV_PICTURE_TYPE_P |
| 525 | #self.av_frame.quality = 1 |
| 526 | frame = self.av_frame |
| 527 | else: |
| 528 | assert options.get("flush") |
| 529 | frame = NULL |
| 530 | |
| 531 | with nogil: |
| 532 | ret = avcodec_send_frame(self.codec_ctx, frame) |
| 533 | if ret!=0: |
| 534 | self.log_av_error(image, ret, options) |
| 535 | raise Exception(av_error_str(ret)) |
| 536 | |
| 537 | buf_len = 1024+self.width*self.height |
| 538 | av_init_packet(&avpkt) |
| 539 | avpkt.data = <uint8_t *> xmemalign(buf_len) |
| 540 | avpkt.size = buf_len |
| 541 | assert ret==0 |
| 542 | bufs = [] |
| 543 | client_options = {} |
| 544 | while ret==0: |
| 545 | with nogil: |
| 546 | ret = avcodec_receive_packet(self.codec_ctx, &avpkt) |
| 547 | if ret==EAGAIN: |
| 548 | client_options["delayed"] = 1 |
| 549 | log("ffmpeg EAGAIN: delayed picture") |
| 550 | break |
| 551 | if ret!=0 and bufs: |
| 552 | break |
| 553 | if ret<0: |
| 554 | free(avpkt.data) |
| 555 | self.log_av_error(image, ret, options) |
| 556 | raise Exception(av_error_str(ret)) |
| 557 | if ret>0: |
| 558 | free(avpkt.data) |
| 559 | self.log_error(image, ret, options, "no stream") |
| 560 | raise Exception("no stream") |
| 561 | log("avcodec_receive_packet returned %#x bytes of data", avpkt.size) |
| 562 | packet_data = avpkt.data[:avpkt.size] |
| 563 | bufs.append(packet_data) |
| 564 | if self.file and packet_data: |
| 565 | self.file.write(packet_data) |
| 566 | self.file.flush() |
| 567 | av_packet_unref(&avpkt) |
| 568 | free(avpkt.data) |
| 569 | self.frames += 1 |
| 570 | data = b"".join(bufs) |
| 571 | log("compress_image(%s) %5i bytes (%i buffers) for %4s frame %-3i, client options: %s", image, len(data), len(bufs), self.encoding, self.frames, client_options) |
| 572 | return data, client_options |
| 573 | |
| 574 | def flush(self, delayed): |
| 575 | return self.compress_image(None, options={"flush" : True}) |
| 576 | |
| 577 | |
| 578 | def selftest(full=False): |
| 579 | global CODECS |
| 580 | from xpra.codecs.codec_checks import testencoder |
| 581 | from xpra.codecs.enc_ffmpeg import encoder |
| 582 | try: |
| 583 | suspend_nonfatal_logging() |
| 584 | CODECS = testencoder(encoder, full) |
| 585 | finally: |
| 586 | resume_nonfatal_logging() |