xpra icon
Bug tracker and wiki

Ticket #832: vp9-yuv444p.patch

File vp9-yuv444p.patch, 18.4 KB (added by Antoine Martin, 4 years ago)

large work in progress patch: we need to support different colorspaces per encoding, which is an API change..

  • tests/xpra/codecs/test_encoder.py

     
    4646    log("input colorspaces=%s", ics)
    4747    for ic in ics:
    4848        for encoding in encoder_module.get_encodings():
    49             ocs = encoder_module.get_output_colorspaces(ic)
     49            ocs = encoder_module.get_output_colorspaces(encoding, ic)
    5050            for c in ocs:
    5151                log("spec(%s)=%s" % (c, encoder_module.get_spec(encoding, ic)))
    5252    log("version=%s" % str(encoder_module.get_version()))
  • tests/xpra/codecs/test_nvenc.py

     
    103103        for w,h in TEST_DIMENSIONS:
    104104            log("test_context_limits() %s @ %sx%s" % (encoding, w, h))
    105105            src_format = encoder_module.get_input_colorspaces()[0]
    106             dst_formats = encoder_module.get_output_colorspaces(src_format)
     106            dst_formats = encoder_module.get_output_colorspaces(encoding, src_format)
    107107            for device_id in cuda_devices:
    108108                device_info = get_device_info(device_id)
    109109                options = {"cuda_device" : device_id}
     
    134134    IMAGE_COUNT = 40
    135135    ENCODER_CONTEXTS_PER_DEVICE = 10
    136136    src_format = encoder_module.get_input_colorspaces()[0]
    137     dst_formats = encoder_module.get_output_colorspaces(src_format)
     137    dst_formats = encoder_module.get_output_colorspaces(encoding, src_format)
    138138    log("generating %s images..." % IMAGE_COUNT)
    139139    images = []
    140140    for _ in range(IMAGE_COUNT):
  • tests/xpra/codecs/test_video_codec.py

     
    1111from tests.xpra.codecs.test_codec import make_rgb_input, make_planar_input
    1212
    1313
    14 def do_test_codec_roundtrip(encoder_class, decoder_class, encoding, src_format, w, h, populate):
     14def do_test_codec_roundtrip(encoder_class, decoder_class, encoding, src_format, dst_formats, w, h, populate):
    1515    if src_format.find("RGB")>=0 or src_format.find("BGR")>=0:
    1616        pixels = make_rgb_input(src_format, w, h, populate=populate)
    1717        isize = len(pixels)
     
    3131
    3232    start = time.time()
    3333    encoder = encoder_class()
    34     encoder.init_context(w, h, src_format, encoding, quality, speed, scaling, options)
     34    #print("%s%s" % (encoder.init_context, (w, h, src_format, dst_formats, encoding, quality, speed, scaling, options)))
     35    encoder.init_context(w, h, src_format, dst_formats, encoding, quality, speed, scaling, options)
    3536    end = time.time()
    3637    print("encoder %s initialized in %.1fms" % (encoder, 1000.0*(end-start)))
    3738
     
    4344
    4445    print("using %s to compress %s" % (encoder, image))
    4546    start = time.time()
    46     data, options = encoder.compress_image(image, {})
     47    data, options = encoder.compress_image(image)
    4748    end = time.time()
    4849    assert data is not None, "compression failed"
    4950    print("compressed %s bytes down to %s (%.1f%%) in %.1fms" % (isize, len(data), 100.0*len(data)/isize, 1000.0*(end-start)))
  • tests/xpra/codecs/test_vpx.py

     
    55# later version. See the file COPYING for details.
    66
    77from tests.xpra.codecs.test_video_codec import do_test_codec_roundtrip
     8from xpra.codecs.vpx import encoder as vpx_encoder     #@UnresolvedImport
    89from xpra.codecs.vpx.decoder import Decoder     #@UnresolvedImport
    910from xpra.codecs.vpx.encoder import Encoder     #@UnresolvedImport
    1011
     
    1314        print("")
    1415        print("test_roundtrip() %s" % encoding)
    1516        for populate in (True, False):
    16             do_test_codec_roundtrip(Encoder, Decoder, encoding, "YUV420P", 640, 480, populate)
     17            src_formats = vpx_encoder.get_input_colorspaces(encoding)
     18            for src_format in src_formats:
     19                do_test_codec_roundtrip(Encoder, Decoder, encoding, src_format, [src_format], 640, 480, populate)
    1720
    1821
    1922def main():
  • xpra/codecs/enc_x264/encoder.pyx

     
    258258def get_encodings():
    259259    return ["h264"]
    260260
    261 def get_input_colorspaces():
    262     global COLORSPACES
     261def get_input_colorspaces(encoding):
     262    assert encoding in get_encodings()
    263263    return  COLORSPACES.keys()
    264264
    265 def get_output_colorspaces(input_colorspace):
     265def get_output_colorspaces(encoding, input_colorspace):
     266    assert encoding in get_encodings()
    266267    assert input_colorspace in COLORSPACES
    267268    return COLORSPACE_FORMATS[input_colorspace]
    268269
  • xpra/codecs/enc_x265/encoder.pyx

     
    220220def get_encodings():
    221221    return ["h265"]
    222222
    223 def get_input_colorspaces():
     223def get_input_colorspaces(encoding):
     224    assert encoding in get_encodings()
    224225    return COLORSPACES
    225226
    226 def get_output_colorspaces(input_colorspace):
     227def get_output_colorspaces(encoding, input_colorspace):
     228    assert encoding in get_encodings()
    227229    assert input_colorspace in COLORSPACES
    228230    return (input_colorspace, )
    229231
  • xpra/codecs/nvenc3/encoder.pyx

     
    11291129        COLORSPACES = {"BGRX" : ("YUV420P",)}
    11301130    return COLORSPACES
    11311131
    1132 def get_input_colorspaces():
     1132def get_input_colorspaces(encoding):
    11331133    return get_COLORSPACES().keys()
    11341134
    1135 def get_output_colorspaces(input_colorspace):
     1135def get_output_colorspaces(encoding, input_colorspace):
     1136    assert encoding in get_encodings()
    11361137    assert input_colorspace in get_COLORSPACES(), "invalid input colorspace: %s (must be one of: %s)" % (input_colorspace, get_COLORSPACES())
    11371138    #the output will actually be in one of those two formats once decoded
    11381139    #because internally that's what we convert to before encoding
     
    21762177    #load the library / DLL:
    21772178    init_nvencode_library()
    21782179
    2179     colorspaces = get_input_colorspaces()
    2180     assert colorspaces, "cannot use NVENC: no colorspaces available"
    2181 
    21822180    success = False
    21832181    valid_keys = []
    21842182    failed_keys = []
     
    21922190
    21932191        test_encoder = Encoder()
    21942192        for encoding in get_encodings():
     2193            colorspaces = get_input_colorspaces(encoding)
     2194            assert colorspaces, "cannot use NVENC: no colorspaces available"
    21952195            src_format = colorspaces[0]
    21962196            dst_formats = get_output_colorspaces(src_format)
    21972197            try:
  • xpra/codecs/nvenc4/encoder.pyx

     
    10071007        COLORSPACES = {"BGRX" : ("YUV420P",)}
    10081008    return COLORSPACES
    10091009
    1010 def get_input_colorspaces():
     1010def get_input_colorspaces(encoding):
     1011    assert encoding in get_encodings()
    10111012    return get_COLORSPACES().keys()
    10121013
    1013 def get_output_colorspaces(input_colorspace):
     1014def get_output_colorspaces(encoding, input_colorspace):
     1015    assert encoding in get_encodings()
    10141016    assert input_colorspace in get_COLORSPACES(), "invalid input colorspace: %s (must be one of: %s)" % (input_colorspace, get_COLORSPACES())
    10151017    #the output will actually be in one of those two formats once decoded
    10161018    #because internally that's what we convert to before encoding
     
    22252227    #load the library / DLL:
    22262228    init_nvencode_library()
    22272229
    2228     colorspaces = get_input_colorspaces()
    2229     assert colorspaces, "cannot use NVENC: no colorspaces available"
    2230 
    22312230    global YUV444_ENABLED, LOSSLESS_ENABLED
    22322231    YUV444_ENABLED, LOSSLESS_ENABLED = True, True
    22332232    success = False
     
    22432242
    22442243        test_encoder = Encoder()
    22452244        for encoding in get_encodings():
     2245            colorspaces = get_input_colorspaces(encoding)
     2246            assert colorspaces, "cannot use NVENC: no colorspaces available"
    22462247            src_format = colorspaces[0]
    22472248            dst_formats = get_output_colorspaces(src_format)
    22482249            try:
  • xpra/codecs/nvenc5/encoder.pyx

     
    990990        COLORSPACES = {"BGRX" : ("YUV420P",)}
    991991    return COLORSPACES
    992992
    993 def get_input_colorspaces():
     993def get_input_colorspaces(encoding):
     994    assert encoding in get_encodings()
    994995    return get_COLORSPACES().keys()
    995996
    996 def get_output_colorspaces(input_colorspace):
     997def get_output_colorspaces(encoding, input_colorspace):
     998    assert encoding in get_encodings()
    997999    assert input_colorspace in get_COLORSPACES(), "invalid input colorspace: %s (must be one of: %s)" % (input_colorspace, get_COLORSPACES())
    9981000    #the output will actually be in one of those two formats once decoded
    9991001    #because internally that's what we convert to before encoding
     
    22072209    #load the library / DLL:
    22082210    init_nvencode_library()
    22092211
    2210     colorspaces = get_input_colorspaces()
    2211     assert colorspaces, "cannot use NVENC: no colorspaces available"
    2212 
    22132212    global YUV444_ENABLED, LOSSLESS_ENABLED
    22142213    YUV444_ENABLED, LOSSLESS_ENABLED = True, True
    22152214
    22162215    test_encoder = Encoder()
    22172216    for encoding in get_encodings():
     2217        colorspaces = get_input_colorspaces(encoding)
     2218        assert colorspaces, "cannot use NVENC: no colorspaces available for %s" % encoding
    22182219        src_format = colorspaces[0]
    22192220        dst_formats = get_output_colorspaces(src_format)
    22202221        try:
  • xpra/codecs/video_helper.py

     
    276276            log("exception in %s module %s initialization %s: %s", encoder_type, encoder_module.__name__, encoder_module.init_module, e, exc_info=True)
    277277            log.warn("Warning: %s video encoder failed: %s", encoder_type, e)
    278278            raise e
    279         colorspaces = encoder_module.get_input_colorspaces()
    280         log("init_video_encoder_option(%s) %s input colorspaces=%s", encoder_module, encoder_type, colorspaces)
    281279        encodings = encoder_module.get_encodings()
    282280        log("init_video_encoder_option(%s) %s encodings=%s", encoder_module, encoder_type, encodings)
    283281        for encoding in encodings:
    284             #we assume that all encodings support all colorspaces here...
    285             #which may not be the case in the future!
     282            colorspaces = encoder_module.get_input_colorspaces(encoding)
     283            log("init_video_encoder_option(%s) %s input colorspaces(%s)=%s", encoder_module, encoder_type, encoding, colorspaces)
    286284            for colorspace in colorspaces:
    287285                spec = encoder_module.get_spec(encoding, colorspace)
    288286                self.add_encoder_spec(encoding, colorspace, spec)
  • xpra/codecs/vpx/decoder.pyx

     
    1313VPX_THREADS = os.environ.get("XPRA_VPX_THREADS", "2")
    1414
    1515DEF ENABLE_VP8 = True
    16 DEF ENABLE_VP9 = False
     16DEF ENABLE_VP9 = True
    1717
    1818
    1919from libc.stdint cimport int64_t
     
    9191    vpx_image_t *vpx_codec_get_frame(vpx_codec_ctx_t *ctx, vpx_codec_iter_t *iter) nogil
    9292
    9393
     94#https://groups.google.com/a/webmproject.org/forum/?fromgroups#!msg/webm-discuss/f5Rmi-Cu63k/IXIzwVoXt_wJ
     95#"RGB is not supported.  You need to convert your source to YUV, and then compress that."
     96COLORSPACES = {}
    9497CODECS = []
    9598IF ENABLE_VP8 == True:
    9699    CODECS.append("vp8")
     100    COLORSPACES["vp8"] = [b"YUV420P"]
    97101IF ENABLE_VP9 == True:
    98102    CODECS.append("vp9")
     103    vp9_cs = [b"YUV420P"]
     104    #this is the ABI version with libvpx 1.4.0:
     105    if VPX_DECODER_ABI_VERSION>=9:
     106        vp9_cs.append(b"YUV444P")
     107    COLORSPACES["vp9"] = vp9_cs
    99108
    100 #https://groups.google.com/a/webmproject.org/forum/?fromgroups#!msg/webm-discuss/f5Rmi-Cu63k/IXIzwVoXt_wJ
    101 #"RGB is not supported.  You need to convert your source to YUV, and then compress that."
    102 COLORSPACES = ["YUV420P"]
    103109
    104 
    105110def init_module():
    106111    log("vpx.decoder.init_module()")
    107112    assert len(CODECS)>0, "no supported encodings!"
     
    123128    return CODECS
    124129
    125130def get_input_colorspaces(encoding):
    126     assert encoding in ("vp8", "vp9")
    127     return COLORSPACES
     131    assert encoding in CODECS
     132    return COLORSPACES.get(encoding)
    128133
    129134def get_output_colorspace(encoding, csc):
    130135    #same as input
     
    140145            "build_config"  : vpx_codec_build_config()}
    141146
    142147
    143 def get_spec(colorspace):
    144     assert colorspace in COLORSPACES, "invalid colorspace: %s (must be one of %s)" % (colorspace, COLORSPACES)
    145     #quality: we only handle YUV420P but this is already accounted for by get_colorspaces() based score calculations
    146     #setup cost is reasonable (usually about 5ms)
    147     return codec_spec(Decoder, codec_type="vpx", setup_cost=40)
    148 
    149 
    150148cdef const vpx_codec_iface_t  *make_codec_dx(encoding):
    151149    IF ENABLE_VP8 == True:
    152150        if encoding=="vp8":
     
    201199
    202200    def init_context(self, encoding, width, height, colorspace):
    203201        assert encoding in CODECS
    204         assert colorspace=="YUV420P"
    205         assert colorspace in COLORSPACES
     202        assert colorspace in get_input_colorspaces(encoding)
    206203        cdef int flags = 0
    207204        cdef const vpx_codec_iface_t *codec_iface = make_codec_dx(encoding)
    208205        self.encoding = encoding
    209         self.dst_format = "YUV420P"
     206        self.dst_format = colorspace
    210207        self.pixfmt = get_vpx_colorspace(self.dst_format)
    211208        self.width = width
    212209        self.height = height
  • xpra/codecs/vpx/encoder.pyx

     
    1010from xpra.log import Logger
    1111log = Logger("encoder", "vpx")
    1212
    13 VPX_THREADS = os.environ.get("XPRA_VPX_THREADS", "2")
    1413
     14#sensible default:
     15cpus = 2
     16try:
     17    cpus = os.cpu_count()
     18except:
     19    try:
     20        import multiprocessing
     21        multiprocessing.cpu_count()
     22    except:
     23        pass
     24VPX_THREADS = os.environ.get("XPRA_VPX_THREADS", cpus)
     25
    1526DEF ENABLE_VP8 = True
    1627DEF ENABLE_VP9 = True
    1728
     
    5263
    5364cdef extern from "vpx/vpx_image.h":
    5465    cdef int VPX_IMG_FMT_I420
     66    cdef int VPX_IMG_FMT_I444
    5567    ctypedef struct vpx_image_t:
    5668        unsigned int w
    5769        unsigned int h
     
    169181
    170182#https://groups.google.com/a/webmproject.org/forum/?fromgroups#!msg/webm-discuss/f5Rmi-Cu63k/IXIzwVoXt_wJ
    171183#"RGB is not supported.  You need to convert your source to YUV, and then compress that."
    172 COLORSPACES = {b"YUV420P" : [b"YUV420P"]}
     184COLORSPACES = {}
    173185
    174186CODECS = []
    175187IF ENABLE_VP8 == True:
    176188    CODECS.append("vp8")
     189    COLORSPACES["vp8"] = [b"YUV420P"]
    177190IF ENABLE_VP9 == True:
    178191    CODECS.append("vp9")
     192    vp9_cs = [b"YUV420P"]
     193    #this is the ABI version with libvpx 1.4.0:
     194    if VPX_ENCODER_ABI_VERSION>=10:
     195        vp9_cs.append(b"YUV444P")
     196    COLORSPACES["vp9"] = vp9_cs
    179197
    180198
    181199def init_module():
     
    198216def get_encodings():
    199217    return CODECS
    200218
    201 def get_input_colorspaces():
    202     return COLORSPACES.keys()
     219def get_input_colorspaces(encoding):
     220    assert encoding in get_encodings(), "invalid encoding: %s" % encoding
     221    return COLORSPACES[encoding]
    203222
    204 def get_output_colorspaces(input_colorspace):
    205     return COLORSPACES[input_colorspace]
     223def get_output_colorspaces(encoding, input_colorspace):
     224    assert encoding in get_encodings(), "invalid encoding: %s" % encoding
     225    csdict = COLORSPACES[input_colorspace]
     226    assert input_colorspace in csdict, "invalid input colorspace: %s" % input_colorspace
     227    #always unchanged in output:
     228    return input_colorspace
    206229
    207230
    208231def get_info():
     
    225248
    226249def get_spec(encoding, colorspace):
    227250    assert encoding in CODECS, "invalid encoding: %s (must be one of %s" % (encoding, get_encodings())
    228     assert colorspace in COLORSPACES, "invalid colorspace: %s (must be one of %s)" % (colorspace, COLORSPACES)
     251    assert colorspace in get_input_colorspaces(encoding), "invalid colorspace: %s (must be one of %s)" % (colorspace, get_input_colorspaces(encoding))
    229252    #quality: we only handle YUV420P but this is already accounted for by the subsampling factor
    230253    #setup cost is reasonable (usually about 5ms)
    231     return video_codec_spec(encoding=encoding, output_colorspaces=COLORSPACES[colorspace],
     254    return video_codec_spec(encoding=encoding, output_colorspaces=colorspace,
    232255                            codec_class=Encoder, codec_type=get_type(), setup_cost=40)
    233256
    234257
    235258cdef vpx_img_fmt_t get_vpx_colorspace(colorspace):
    236259    assert colorspace in COLORSPACES
    237     return VPX_IMG_FMT_I420
     260    if colorspace=="YUV420P":
     261        return VPX_IMG_FMT_I420
     262    elif colorspace=="YUV444P":
     263        return VPX_IMG_FMT_I444
     264    raise Exception("invalid colorspace %s" % colorspace)
    238265
    239266
    240267cdef class Encoder:
     
    253280
    254281    cdef object __weakref__
    255282
     283#init_context(w, h, src_format, encoding, quality, speed, scaling, options)
    256284    def init_context(self, int width, int height, src_format, dst_formats, encoding, int quality, int speed, scaling, options):    #@DuplicatedSignature
    257285        assert encoding in CODECS, "invalid encoding: %s" % encoding
    258286        assert scaling==(1,1), "vpx does not handle scaling"
     287        assert encoding in get_encodings()
     288        assert src_format in get_input_colorspaces(encoding)
     289        self.src_format = src_format
     290
    259291        cdef const vpx_codec_iface_t *codec_iface = make_codec_cx(encoding)
    260292        self.encoding = encoding
    261293        self.width = width
     
    263295        self.speed = speed
    264296        self.quality = quality
    265297        self.frames = 0
    266         assert src_format=="YUV420P" and "YUV420P" in dst_formats
    267         self.src_format = "YUV420P"
    268298        self.pixfmt = get_vpx_colorspace(self.src_format)
    269299        try:
    270300            self.max_threads = max(0, min(32, int(options.get("threads", VPX_THREADS))))