Skip to content

Commit

Permalink
#1308 add 10-bit support to nvenc, simplify / rationalize the 'csc' c…
Browse files Browse the repository at this point in the history
…lient option: let the encoders tell the client what colorspace was used

git-svn-id: https://xpra.org/svn/Xpra/trunk@26968 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Jul 12, 2020
1 parent 964e463 commit bd0e7ea
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 56 deletions.
18 changes: 5 additions & 13 deletions src/xpra/codecs/dec_avcodec2/decoder.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ FORMAT_TO_ENUM = {
"ARGB" : AV_PIX_FMT_ARGB,
"BGRA" : AV_PIX_FMT_BGRA,
"GBRP" : AV_PIX_FMT_GBRP,
"BGR48" : AV_PIX_FMT_GBRP10LE,
"GBRP10" : AV_PIX_FMT_GBRP10LE,
}
#for planar formats, this is the number of bytes per channel
BYTES_PER_PIXEL = {
Expand All @@ -536,12 +536,11 @@ BYTES_PER_PIXEL = {

#given an ffmpeg pixel format,
#what is our format name for it:
COLORSPACES = tuple(FORMAT_TO_ENUM.keys())
COLORSPACES = list(FORMAT_TO_ENUM.keys())+["r210"]
ENUM_TO_FORMAT = {}
for pix_fmt, av_enum in FORMAT_TO_ENUM.items():
ENUM_TO_FORMAT[av_enum] = pix_fmt
#ENUM_TO_FORMAT[AV_PIX_FMT_YUV422P10LE] = "YUV422P10"
ENUM_TO_FORMAT[AV_PIX_FMT_GBRP10LE] = "GBRP10"
FORMAT_TO_ENUM["r210"] = AV_PIX_FMT_GBRP10LE


def get_version():
Expand Down Expand Up @@ -612,7 +611,7 @@ def get_output_colorspace(encoding, csc):
if csc in ("RGB", "XRGB", "BGRX", "ARGB", "BGRA"):
#h264 from plain RGB data is returned as "GBRP"!
return "GBRP"
if csc=="BGR48":
if csc=="GBRP10":
return "GBRP10"
elif encoding in ("vp8", "mpeg4", "mpeg1", "mpeg2"):
return "YUV420P"
Expand Down Expand Up @@ -719,14 +718,7 @@ cdef class Decoder:
self.width = width
self.height = height
assert colorspace in COLORSPACES, "invalid colorspace: %s" % colorspace
self.colorspace = ""
for x in COLORSPACES:
if x==colorspace:
self.colorspace = x
break
if not self.colorspace:
log.error("invalid pixel format: %s", colorspace)
return False
self.colorspace = str(colorspace)
self.pix_fmt = FORMAT_TO_ENUM.get(colorspace, AV_PIX_FMT_NONE)
if self.pix_fmt==AV_PIX_FMT_NONE:
log.error("invalid pixel format: %s", colorspace)
Expand Down
7 changes: 6 additions & 1 deletion src/xpra/codecs/enc_ffmpeg/encoder.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1695,7 +1695,12 @@ cdef class Encoder:
if hw_frame:
av_frame_free(&hw_frame)

client_options = {}
#NV12 also uses YUV420P,
#only with a different pixel layout
#which is irrelevant to the client
client_options = {
"csc" : "YUV420P",
}
if self.frames==0 and self.profile:
client_options["profile"] = self.profile
client_options["level"] = "3.0"
Expand Down
24 changes: 14 additions & 10 deletions src/xpra/codecs/enc_x264/encoder.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -364,21 +364,21 @@ if SUPPORT_24BPP:
})

COLORSPACES = {
"YUV420P" : ("YUV420P",),
"YUV422P" : ("YUV422P",),
"YUV444P" : ("YUV444P",),
"BGRA" : ("BGRA",),
"BGRX" : ("BGRX",),
"YUV420P" : "YUV420P",
"YUV422P" : "YUV422P",
"YUV444P" : "YUV444P",
"BGRA" : "BGRA",
"BGRX" : "BGRX",
}
emit_ifdef_bitdepth()
if SUPPORT_30BPP:
COLORSPACE_FORMATS["BGR48"] = (X264_CSP_BGR | X264_CSP_HIGH_DEPTH, PROFILE_HIGH444_PREDICTIVE, RGB_PROFILES)
COLORSPACES["BGR48"] = ("BGR48", )
COLORSPACES["BGR48"] = "GBRP10"
emit_endif_bitdepth()
if SUPPORT_24BPP:
COLORSPACES.update({
"BGR" : ("BGR",),
"RGB" : ("RGB",),
"BGR" : "BGR",
"RGB" : "RGB",
})


Expand Down Expand Up @@ -414,7 +414,7 @@ def get_input_colorspaces(encoding):
def get_output_colorspaces(encoding, input_colorspace):
assert encoding in get_encodings()
assert input_colorspace in COLORSPACES
return COLORSPACES[input_colorspace]
return (COLORSPACES[input_colorspace],)

if X264_BUILD<146:
#untested, but should be OK for 4k:
Expand All @@ -430,7 +430,7 @@ def get_spec(encoding, colorspace):
#we can handle high quality and any speed
#setup cost is moderate (about 10ms)
has_lossless_mode = colorspace in ("YUV444P", "BGR", "BGRA", "BGRX", "RGB")
return video_spec(encoding=encoding, input_colorspace=colorspace, output_colorspaces=COLORSPACES[colorspace],
return video_spec(encoding=encoding, input_colorspace=colorspace, output_colorspaces=(COLORSPACES[colorspace],),
has_lossless_mode=has_lossless_mode,
codec_class=Encoder, codec_type=get_type(),
quality=60+40*int(has_lossless_mode), speed=60,
Expand Down Expand Up @@ -479,6 +479,7 @@ cdef class Encoder:
cdef unsigned int fast_decode
#cdef int opencl
cdef object src_format
cdef object csc_format
cdef object content_type
cdef object profile
cdef object tune
Expand Down Expand Up @@ -520,6 +521,7 @@ cdef class Encoder:
self.max_delayed = options.intget("max-delayed", MAX_DELAYED_FRAMES) * int(not self.fast_decode) * int(self.b_frames)
self.preset = self.get_preset_for_speed(speed)
self.src_format = src_format
self.csc_format = COLORSPACES[src_format]
self.colorspace = cs_info[0]
self.frames = 0
self.frame_types = {}
Expand Down Expand Up @@ -686,6 +688,7 @@ cdef class Encoder:
"quality" : self.quality,
"lossless" : self.quality==100,
"src_format" : self.src_format,
"csc_format" : self.csc_format,
"content-type" : self.content_type,
"version" : get_version(),
"frame-types" : self.frame_types,
Expand Down Expand Up @@ -929,6 +932,7 @@ cdef class Encoder:
"pts" : int(pic_out.i_pts),
#"quality" : max(0, min(100, quality)),
#"speed" : max(0, min(100, speed)),
"csc" : self.csc_format,
"type" : slice_type,
}
if self.delayed_frames>0:
Expand Down
1 change: 1 addition & 0 deletions src/xpra/codecs/enc_x265/encoder.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,7 @@ cdef class Encoder:
finally:
x265_picture_free(pic_out)
client_options = {
"csc" : self.src_format,
"frame" : self.frames,
"pts" : image.get_timestamp()-self.first_frame_timestamp,
}
Expand Down
47 changes: 32 additions & 15 deletions src/xpra/codecs/nvenc/encoder.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ DESIRED_PRESET = os.environ.get("XPRA_NVENC_PRESET", "")
#NVENC requires compute capability value 0x30 or above:
cdef int MIN_COMPUTE = 0x30

cdef int SUPPORT_30BPP = envbool("XPRA_NVENC_SUPPORT_30BPP", True)
cdef int YUV444_THRESHOLD = envint("XPRA_NVENC_YUV444_THRESHOLD", 85)
cdef int LOSSLESS_THRESHOLD = envint("XPRA_NVENC_LOSSLESS_THRESHOLD", 100)
cdef int NATIVE_RGB = int(not WIN32)
Expand Down Expand Up @@ -1300,6 +1301,8 @@ def get_COLORSPACES(encoding):
"XRGB" : out_cs,
"ARGB" : out_cs,
}
if SUPPORT_30BPP:
COLORSPACES["r210"] = ("GBRP10", )
return COLORSPACES

def get_input_colorspaces(encoding):
Expand Down Expand Up @@ -1355,15 +1358,15 @@ def get_spec(encoding, colorspace):
#FIXME: we should probe this using WIDTH_MAX, HEIGHT_MAX!
global MAX_SIZE
max_w, max_h = MAX_SIZE.get(encoding, (4096, 4096))
has_lossless_mode = colorspace in ("XRGB", "ARGB", "BGRX", "BGRA" ) and encoding=="h264"
has_lossless_mode = colorspace in ("XRGB", "ARGB", "BGRX", "BGRA", "r210") and encoding=="h264"
cs = video_spec(encoding=encoding, input_colorspace=colorspace, output_colorspaces=get_COLORSPACES(encoding)[colorspace], has_lossless_mode=LOSSLESS_CODEC_SUPPORT.get(encoding, LOSSLESS_ENABLED),
codec_class=Encoder, codec_type=get_type(),
quality=60+has_lossless_mode*40, speed=100, size_efficiency=100,
setup_cost=80, cpu_cost=10, gpu_cost=100,
#using a hardware encoder for something this small is silly:
min_w=min_w, min_h=min_h,
max_w=max_w, max_h=max_h,
can_scale=True,
can_scale=colorspace!="r210",
width_mask=WIDTH_MASK, height_mask=HEIGHT_MASK)
cs.get_runtime_factor = get_runtime_factor
return cs
Expand Down Expand Up @@ -1541,7 +1544,7 @@ cdef class Encoder:
def init_context(self, int width, int height, src_format, dst_formats, encoding, int quality, int speed, scaling, options={}): #@DuplicatedSignature
assert NvEncodeAPICreateInstance is not None, "encoder module is not initialized"
log("init_context%s", (width, height, src_format, dst_formats, encoding, quality, speed, scaling, options))
assert src_format in ("ARGB", "XRGB", "BGRA", "BGRX"), "invalid source format %s" % src_format
assert src_format in ("ARGB", "XRGB", "BGRA", "BGRX", "r210"), "invalid source format %s" % src_format
assert "YUV420P" in dst_formats or "YUV444P" in dst_formats
self.width = width
self.height = height
Expand Down Expand Up @@ -1635,6 +1638,8 @@ cdef class Encoder:
nativergb = NATIVE_RGB and hasyuv444
if nativergb and self.src_format in ("BGRX", "BGRA"):
v = "BGRX"
elif self.src_format=="r210":
v = "r210"
else:
x,y = self.scaling
hasyuv420 = YUV420_ENABLED and "YUV420P" in self.dst_formats
Expand All @@ -1655,7 +1660,7 @@ cdef class Encoder:

def get_target_lossless(self, pixel_format, quality):
global LOSSLESS_ENABLED, LOSSLESS_CODEC_SUPPORT
if pixel_format!="YUV444P":
if pixel_format not in ("YUV444P", "r210"):
return False
if not LOSSLESS_CODEC_SUPPORT.get(self.encoding, LOSSLESS_ENABLED):
return False
Expand Down Expand Up @@ -1716,6 +1721,13 @@ cdef class Encoder:
plane_size_div= 1
wmult = 4
hmult = 1
elif self.pixel_format=="r210":
assert NATIVE_RGB
kernel_name = None
self.bufferFmt = NV_ENC_BUFFER_FORMAT_ARGB10
plane_size_div= 1
wmult = 4
hmult = 1
#if supported (separate plane flag), use YUV444P:
elif self.pixel_format=="YUV444P":
assert YUV444_CODEC_SUPPORT.get(self.encoding, YUV444_ENABLED), "YUV444 is not enabled for %s" % self.encoding
Expand Down Expand Up @@ -1885,6 +1897,8 @@ cdef class Encoder:

if self.pixel_format=="BGRX":
chromaFormatIDC = 3
elif self.pixel_format=="r210":
chromaFormatIDC = 3
elif self.pixel_format=="NV12":
chromaFormatIDC = 1
elif self.pixel_format=="YUV444P":
Expand Down Expand Up @@ -2525,6 +2539,7 @@ cdef class Encoder:
self.free_memory, self.total_memory = driver.mem_get_info()

client_options = {
"csc" : self.src_format,
"frame" : int(self.frames),
"pts" : int(timestamp-self.first_frame_timestamp),
"speed" : self.speed,
Expand Down Expand Up @@ -2596,22 +2611,24 @@ cdef class Encoder:
preset_name = CODEC_PRESETS_GUIDS.get(guidstr(preset_GUID))
if DEBUG_API:
log("* %s : %s", guidstr(preset_GUID), preset_name or "unknown!")
presetConfig = self.get_preset_config(preset_name, encode_GUID, preset_GUID)
if presetConfig!=NULL:
try:
encConfig = &presetConfig.presetCfg
if DEBUG_API:
log("presetConfig.presetCfg=%s", <uintptr_t> encConfig)
gop = {NVENC_INFINITE_GOPLENGTH : "infinite"}.get(encConfig.gopLength, encConfig.gopLength)
log("* %-20s P frame interval=%i, gop length=%-10s", preset_name or "unknown!", encConfig.frameIntervalP, gop)
finally:
free(presetConfig)
if preset_name is None:
unknowns.append(guidstr(preset_GUID))
else:
presetConfig = self.get_preset_config(preset_name, encode_GUID, preset_GUID)
if presetConfig!=NULL:
try:
encConfig = &presetConfig.presetCfg
if DEBUG_API:
log("presetConfig.presetCfg=%s", <uintptr_t> encConfig)
gop = {NVENC_INFINITE_GOPLENGTH : "infinite"}.get(encConfig.gopLength, encConfig.gopLength)
log("* %-20s P frame interval=%i, gop length=%-10s", preset_name or "unknown!", encConfig.frameIntervalP, gop)
finally:
free(presetConfig)
presets[preset_name] = guidstr(preset_GUID)
if len(unknowns)>0:
log.warn("Warning: found some unknown NVENC presets: %s", csv(unknowns))
log.warn("Warning: found some unknown NVENC presets:")
for x in unknowns:
log.warn(" * %s", x)
finally:
free(preset_GUIDs)
if DEBUG_API:
Expand Down
7 changes: 4 additions & 3 deletions src/xpra/codecs/vpx/encoder.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ log = Logger("encoder", "vpx")

from xpra.codecs.codec_constants import video_spec
from xpra.os_util import bytestostr, WIN32, OSX, POSIX, BITS
from xpra.util import AtomicInteger, envint, envbool
from xpra.util import AtomicInteger, envint, envbool, typedict
from xpra.buffers.membuf cimport object_as_buffer #pylint: disable=syntax-error

from libc.stdint cimport uint8_t
Expand Down Expand Up @@ -582,7 +582,7 @@ cdef class Encoder:
assert object_as_buffer(pixels[i], <const void**> &pic_buf, &pic_buf_len)==0
pic_in[i] = pic_buf
strides[i] = istrides[i]
cdef unsigned long bandwidth_limit = options.intget("bandwidth-limit", self.bandwidth_limit)
cdef unsigned long bandwidth_limit = typedict(options).intget("bandwidth-limit", self.bandwidth_limit)
if bandwidth_limit!=self.bandwidth_limit:
self.bandwidth_limit = bandwidth_limit
self.update_cfg()
Expand All @@ -591,7 +591,8 @@ cdef class Encoder:
if quality>=0:
self.set_encoding_quality(quality)
return self.do_compress_image(pic_in, strides), {
"frame" : int(self.frames),
"csc" : self.src_format,
"frame" : int(self.frames),
#"quality" : min(99+self.lossless, self.quality),
#"speed" : self.speed,
}
Expand Down
15 changes: 1 addition & 14 deletions src/xpra/server/window/window_video_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -1334,10 +1334,9 @@ def add_scores(info, csc_spec, enc_in_format):
# pixel_format, [x[0] for x in csc_specs], set(x[1] for x in csc_specs))
#we have csc module(s) that can get us from pixel_format to out_csc:
for out_csc, l in csc_specs.items():
actual_csc = self.csc_equiv(out_csc)
if not bool(FORCE_CSC_MODE) or FORCE_CSC_MODE==out_csc:
for csc_spec in l:
add_scores("via %s (%s)" % (out_csc, actual_csc), csc_spec, out_csc)
add_scores("via %s" % out_csc, csc_spec, out_csc)
s = sorted(scores, key=lambda x : -x[0])
scorelog("get_video_pipeline_options%s scores=%s", (encodings, width, height, src_format), s)
if self.is_cancelled():
Expand All @@ -1349,14 +1348,6 @@ def add_scores(info, csc_spec, enc_in_format):
self.last_pipeline_time = monotonic_time()
return s

def csc_equiv(self, csc_mode):
#in some places, we want to check against the subsampling used
#and not the colorspace itself.
#and NV12 uses the same subsampling as YUV420P...
#(the colorspace should be separated from the encoding format)
return {"NV12" : "YUV420P",
"BGRX" : "YUV444P"}.get(csc_mode, csc_mode)


def get_video_fps(self, width, height):
mvsub = self.matches_video_subregion(width, height)
Expand Down Expand Up @@ -2235,9 +2226,6 @@ def do_video_encode(self, encoding, image, options):
self.video_stream_file.write(data)
self.video_stream_file.flush()

#tell the client which colour subsampling we used:
#(note: see csc_equiv!)
client_options["csc"] = self.csc_equiv(csc)
#tell the client about scaling (the size of the encoded picture):
#(unless the video encoder has already done so):
scaled_size = None
Expand Down Expand Up @@ -2328,7 +2316,6 @@ def do_flush_video_encoder(self):
if self.video_stream_file:
self.video_stream_file.write(data)
self.video_stream_file.flush()
client_options["csc"] = self.csc_equiv(csc)
if frame<self.start_video_frame:
client_options["paint"] = False
if scaled_size:
Expand Down

0 comments on commit bd0e7ea

Please sign in to comment.