Skip to content

Commit

Permalink
#464: work on vp9 support (IFDEF disabled for now):
Browse files Browse the repository at this point in the history
* make dec_vpx more like dec_avcodec: can handle 2 encodings
* load vpx decoders on start, and try both dec_vpx and dec_avcodec for all vpx codecs: vp8 and vp9
* add stub vp9 code to dec_avcodec[2]
* add vp9 codec functions to vpx/decoder and vpx/encoder
* add vp9 to list of video encodings

git-svn-id: https://xpra.org/svn/Xpra/trunk@5105 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Jan 3, 2014
1 parent 2a713ae commit b5b6bb5
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 64 deletions.
23 changes: 13 additions & 10 deletions src/xpra/client/ui_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,6 @@ def get_core_encodings(self):
#we always support rgb24:
core_encodings = ["rgb24"]
for modules, encodings in {
("dec_vpx", "csc_swscale") : ["vp8"],
("dec_webp",) : ["webp"],
("PIL",) : ["png", "png/L", "png/P", "jpeg"],
}.items():
Expand All @@ -346,15 +345,19 @@ def get_core_encodings(self):
log("get_core_encodings() not adding %s because of missing modules: %s", encodings, missing)
continue
core_encodings += encodings
#special case for avcodec which may be able to decode both 'vp8' and 'h264':
#(both of which "need" swscale - until we get more clever
# and test the availibility of GL windows)
if has_codec("csc_swscale"): #or has_codec("csc_opencl"): (see window_backing_base)
avcodec_module = get_codec("dec_avcodec")
if avcodec_module:
encodings = avcodec_module.get_codecs()
log("avcodec supports %s", encodings)
core_encodings += encodings
#special case for "dec_avcodec" which may be able to decode both 'vp8' and 'h264':
#and for "dec_vpx" which may be able to decode both 'vp8' and 'vp9':
#(both may "need" some way of converting YUV data to RGB - at least until we get more clever
# and test the availibility of GL windows... but those aren't always applicable..
# or test if the codec can somehow gives us plain RGB out)
if has_codec("csc_swscale"): # or has_codec("csc_opencl"): (see window_backing_base)
for module in ("dec_avcodec", "dec_avcodec2", "dec_vpx"):
decoder = get_codec(module)
if decoder:
for encoding in decoder.get_encodings():
log.info("%s supports %s", module, encoding)
if encoding not in core_encodings:
core_encodings.append(encoding)
log("get_core_encodings()=%s", core_encodings)
#remove duplicates and use prefered encoding order:
return [x for x in PREFERED_ENCODING_ORDER if x in set(core_encodings)]
Expand Down
36 changes: 18 additions & 18 deletions src/xpra/client/window_backing_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from xpra.net.protocol import has_lz4, LZ4_uncompress
from xpra.os_util import BytesIOClass, bytestostr
from xpra.codecs.codec_constants import get_colorspace_from_avutil_enum
from xpra.codecs.loader import get_codec, has_codec
from xpra.codecs.loader import get_codec


#logging in the draw path is expensive:
Expand Down Expand Up @@ -54,17 +54,17 @@ def load_csc_options():
except:
log.warn("failed to load csc module %s", csc_module, exc_info=True)

VPX_DECODER = None
def load_vpx_decoder():
global VPX_DECODER
if has_codec("dec_vpx"):
VPX_DECODER = "dec_vpx"
else:
#try dec_avcodec:
avcodec_module = get_codec("dec_avcodec")
if avcodec_module and "vpx" in avcodec_module.get_codecs():
VPX_DECODER = "dec_avcodec"

VPX_DECODERS = {}
def load_vpx_decoders():
global VPX_DECODERS
for codec in ("vp8", "vp9"):
#prefer native vpx ahead of avcodec:
for module in ("dec_vpx", "dec_avcodec"):
decoder = get_codec(module)
if codec in decoder.get_encodings():
VPX_DECODERS[codec] = module
break
log("vpx decoders: %s", VPX_DECODERS)

def fire_paint_callbacks(callbacks, success):
for x in callbacks:
Expand All @@ -82,7 +82,7 @@ def fire_paint_callbacks(callbacks, success):
class WindowBackingBase(object):
def __init__(self, wid, idle_add):
load_csc_options()
load_vpx_decoder()
load_vpx_decoders()
self.wid = wid
self.idle_add = idle_add
self._has_alpha = False
Expand Down Expand Up @@ -373,8 +373,8 @@ def paint_with_video_decoder(self, decoder_name, coding, img_data, x, y, width,

img = self._video_decoder.decompress_image(img_data, options)
if not img:
raise Exception("paint_with_video_decoder: wid=%s, %s decompression error on %s bytes of picture data for %sx%s pixels, options=%s" % (
self.wid, coding, len(img_data), width, height, options))
raise Exception("paint_with_video_decoder: wid=%s, %s decompression error on %s bytes of picture data for %sx%s pixels using %s, options=%s" % (
self.wid, coding, len(img_data), width, height, self._video_decoder, options))
self.do_video_paint(img, x, y, enc_width, enc_height, width, height, options, callbacks)
finally:
self._decoder_lock.release()
Expand Down Expand Up @@ -472,9 +472,9 @@ def draw_region(self, x, y, width, height, coding, img_data, rowstride, options,
self.paint_rgb32(img_data, x, y, width, height, rowstride, options, callbacks)
elif coding=="h264":
self.paint_with_video_decoder("dec_avcodec", "h264", img_data, x, y, width, height, options, callbacks)
elif coding=="vp8":
assert VPX_DECODER, "no vpx decoder available"
self.paint_with_video_decoder(VPX_DECODER, "vp8", img_data, x, y, width, height, options, callbacks)
elif coding in ("vp8", "vp9"):
assert coding in VPX_DECODERS, "no %s decoder available" % coding
self.paint_with_video_decoder(VPX_DECODERS.get(coding), coding, img_data, x, y, width, height, options, callbacks)
elif coding == "webp":
self.paint_webp(img_data, x, y, width, height, options, callbacks)
elif coding[:3]=="png" or coding=="jpeg":
Expand Down
19 changes: 11 additions & 8 deletions src/xpra/codecs/dec_avcodec/decoder.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ cdef extern from "libavcodec/avcodec.h":
AVPixelFormat PIX_FMT_NONE
AVCodecID CODEC_ID_H264
AVCodecID CODEC_ID_VP8
#AVCodecID CODEC_ID_VP9

#init and free:
void avcodec_register_all()
Expand Down Expand Up @@ -160,17 +161,19 @@ def get_colorspaces():
init_colorspaces()
return COLORSPACES

_CODECS = None
def get_codecs():
global _CODECS
if _CODECS is None:
CODECS = None
def get_encodings():
global CODECS
if CODECS is None:
avcodec_register_all()
_CODECS = []
CODECS = []
if avcodec_find_decoder(CODEC_ID_H264)!=NULL:
_CODECS.append("h264")
CODECS.append("h264")
if avcodec_find_decoder(CODEC_ID_VP8)!=NULL:
_CODECS.append("vp8")
return _CODECS
CODECS.append("vp8")
#if avcodec_find_decoder(CODEC_ID_VP9)!=NULL:
# CODECS.append("vp9")
return CODECS


#maps AVCodecContext to the Decoder that manages it
Expand Down
19 changes: 11 additions & 8 deletions src/xpra/codecs/dec_avcodec2/decoder.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ cdef extern from "libavcodec/avcodec.h":
AVPixelFormat PIX_FMT_NONE
AVCodecID CODEC_ID_H264
AVCodecID CODEC_ID_VP8
#AVCodecID CODEC_ID_VP9

#init and free:
void avcodec_register_all()
Expand Down Expand Up @@ -149,17 +150,19 @@ def get_colorspaces():
init_colorspaces()
return COLORSPACES

_CODECS = None
def get_codecs():
global _CODECS
if _CODECS is None:
CODECS = None
def get_encodings():
global CODECS
if CODECS is None:
avcodec_register_all()
_CODECS = []
CODECS = []
if avcodec_find_decoder(CODEC_ID_H264)!=NULL:
_CODECS.append("h264")
CODECS.append("h264")
if avcodec_find_decoder(CODEC_ID_VP8)!=NULL:
_CODECS.append("vp8")
return _CODECS
CODECS.append("vp8")
#if avcodec_find_decoder(CODEC_ID_VP9)!=NULL:
# CODECS.append("vp9")
return CODECS


cdef void clear_frame(AVFrame *frame):
Expand Down
50 changes: 39 additions & 11 deletions src/xpra/codecs/vpx/decoder.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ log = Logger()
debug = debug_if_env(log, "XPRA_VPX_DEBUG")
error = log.error

DEF ENABLE_VP8 = True
DEF ENABLE_VP9 = False


from libc.stdint cimport int64_t


Expand Down Expand Up @@ -58,7 +62,10 @@ cdef extern from "vpx/vpx_image.h":
unsigned int y_chroma_shift

cdef extern from "vpx/vp8dx.h":
vpx_codec_iface_t *vpx_codec_vp8_dx()
IF ENABLE_VP8 == True:
const vpx_codec_iface_t *vpx_codec_vp8_dx()
IF ENABLE_VP9 == True:
const vpx_codec_iface_t *vpx_codec_vp9_dx()

cdef extern from "vpx/vpx_decoder.h":
ctypedef struct vpx_codec_enc_cfg_t:
Expand Down Expand Up @@ -87,8 +94,26 @@ def get_version():
return vpx_codec_version_str()

def get_type(self):
return "vp8"
return "vpx"


CODECS = []
IF ENABLE_VP8 == True:
CODECS.append("vp8")
IF ENABLE_VP9 == True:
CODECS.append("vp9")

cdef const vpx_codec_iface_t *make_codec_dx(encoding):
IF ENABLE_VP8 == True:
if encoding=="vp8":
return vpx_codec_vp8_dx()
IF ENABLE_VP9 == True:
if encoding=="vp9":
return vpx_codec_vp9_dx()
raise Exception("unsupported encoding: %s" % encoding)

def get_encodings():
return CODECS

#https://groups.google.com/a/webmproject.org/forum/?fromgroups#!msg/webm-discuss/f5Rmi-Cu63k/IXIzwVoXt_wJ
#"RGB is not supported. You need to convert your source to YUV, and then compress that."
Expand All @@ -100,7 +125,7 @@ def get_spec(colorspace):
assert colorspace in COLORSPACES, "invalid colorspace: %s (must be one of %s)" % (colorspace, COLORSPACES)
#quality: we only handle YUV420P but this is already accounted for by get_colorspaces() based score calculations
#setup cost is reasonable (usually about 5ms)
return codec_spec(Decoder, codec_type="vp8", setup_cost=40)
return codec_spec(Decoder, codec_type="vpx", setup_cost=40)

cdef vpx_img_fmt_t get_vpx_colorspace(colorspace):
assert colorspace in COLORSPACES
Expand Down Expand Up @@ -140,15 +165,17 @@ cdef class Decoder:
cdef int width
cdef int height
cdef vpx_img_fmt_t pixfmt
cdef char* src_format
cdef char* dst_format
cdef object encoding

def init_context(self, encoding, width, height, colorspace):
cdef const vpx_codec_iface_t *codec_iface = vpx_codec_vp8_dx()
cdef int flags = 0
assert encoding=="vp8"
assert encoding in CODECS
assert colorspace=="YUV420P"
self.src_format = "YUV420P"
self.pixfmt = get_vpx_colorspace(self.src_format)
cdef int flags = 0
cdef const vpx_codec_iface_t *codec_iface = make_codec_dx(encoding)
self.encoding = encoding
self.dst_format = "YUV420P"
self.pixfmt = get_vpx_colorspace(self.dst_format)
self.width = width
self.height = height
self.context = <vpx_codec_ctx_t *> xmemalign(sizeof(vpx_codec_ctx_t))
Expand All @@ -166,7 +193,7 @@ cdef class Decoder:
}

def get_colorspace(self):
return self.src_format
return self.dst_format

def get_width(self):
return self.width
Expand All @@ -178,7 +205,7 @@ cdef class Decoder:
return self.context==NULL

def get_encoding(self):
return "vp8"
return self.encoding

def get_type(self): #@DuplicatedSignature
return "vpx"
Expand Down Expand Up @@ -237,4 +264,5 @@ cdef class Decoder:
pixels.append(plane)

image.add_buffer(<unsigned long> padded_buf)
#log("vpx returning decoded %s image %s with colorspace=%s", self.encoding, image, image.get_pixel_format())
return image
41 changes: 33 additions & 8 deletions src/xpra/codecs/vpx/encoder.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ log = Logger()
debug = debug_if_env(log, "XPRA_VPX_DEBUG")
error = log.error

DEF ENABLE_VP8 = True
DEF ENABLE_VP9 = False


from libc.stdint cimport int64_t


Expand Down Expand Up @@ -55,7 +59,10 @@ cdef extern from "vpx/vpx_image.h":
unsigned int y_chroma_shift

cdef extern from "vpx/vp8cx.h":
const vpx_codec_iface_t *vpx_codec_vp8_cx()
IF ENABLE_VP8 == True:
const vpx_codec_iface_t *vpx_codec_vp8_cx()
IF ENABLE_VP9 == True:
const vpx_codec_iface_t *vpx_codec_vp9_cx()

cdef extern from "vpx/vpx_encoder.h":
int VPX_ENCODER_ABI_VERSION
Expand Down Expand Up @@ -102,8 +109,24 @@ def get_version():
def get_type():
return "vpx"

CODECS = []
IF ENABLE_VP8 == True:
CODECS.append("vp8")
IF ENABLE_VP9 == True:
CODECS.append("vp9")

def get_encodings():
return ["vp8"]
return CODECS


cdef const vpx_codec_iface_t *make_codec_cx(encoding):
IF ENABLE_VP8 == True:
if encoding=="vp8":
return vpx_codec_vp8_cx()
IF ENABLE_VP9 == True:
if encoding=="vp9":
return vpx_codec_vp9_cx()
raise Exception("unsupported encoding: %s" % encoding)


#https://groups.google.com/a/webmproject.org/forum/?fromgroups#!msg/webm-discuss/f5Rmi-Cu63k/IXIzwVoXt_wJ
Expand All @@ -113,7 +136,7 @@ def get_colorspaces():
return COLORSPACES

def get_spec(encoding, colorspace):
assert encoding in get_encodings(), "invalid encoding: %s (must be one of %s" % (encoding, get_encodings())
assert encoding in CODECS, "invalid encoding: %s (must be one of %s" % (encoding, get_encodings())
assert colorspace in COLORSPACES, "invalid colorspace: %s (must be one of %s)" % (colorspace, COLORSPACES)
#quality: we only handle YUV420P but this is already accounted for by get_colorspaces() based score calculations
#setup cost is reasonable (usually about 5ms)
Expand All @@ -138,12 +161,14 @@ cdef class Encoder:
cdef int width
cdef int height
cdef int max_threads
cdef object encoding
cdef char* src_format

def init_context(self, int width, int height, src_format, encoding, int quality, int speed, scaling, options): #@DuplicatedSignature
assert encoding=="vp8", "invalid encoding: %s" % encoding
assert scaling==(1,1), "vp8 does not handle scaling"
cdef const vpx_codec_iface_t *codec_iface
assert encoding in CODECS, "invalid encoding: %s" % encoding
assert scaling==(1,1), "vpx does not handle scaling"
cdef const vpx_codec_iface_t *codec_iface = make_codec_cx(encoding)
self.encoding = encoding
self.width = width
self.height = height
self.frames = 0
Expand All @@ -156,7 +181,6 @@ cdef class Encoder:
log.warn("error parsing number of threads: %s", e)
self.max_threads =2

codec_iface = vpx_codec_vp8_cx()
self.cfg = <vpx_codec_enc_cfg_t *> xmemalign(sizeof(vpx_codec_enc_cfg_t))
if self.cfg==NULL:
raise Exception("failed to allocate memory for vpx encoder config")
Expand Down Expand Up @@ -191,7 +215,7 @@ cdef class Encoder:
"max_threads": self.max_threads}

def get_encoding(self):
return "vp8"
return self.encoding

def get_width(self):
return self.width
Expand Down Expand Up @@ -279,6 +303,7 @@ cdef class Encoder:
cout = get_frame_buffer(pkt)
img = cout[:coutsz]
free(image)
#log("vpx returning %s image: %s bytes", self.encoding, len(img))
return img

def set_encoding_speed(self, int pct):
Expand Down
2 changes: 1 addition & 1 deletion src/xpra/server/window_video_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def __init__(self, *args):
#0.10 onwards should have specified csc_modes:
self.csc_modes = self.encoding_options.get("csc_modes", def_csc_modes)

self.video_encodings = ("vp8", "h264")
self.video_encodings = ("vp8", "vp9", "h264")
for x in self.video_encodings:
if x in self.server_core_encodings:
self._encoders[x] = self.video_encode
Expand Down

0 comments on commit b5b6bb5

Please sign in to comment.