xpra icon
Bug tracker and wiki

Opened 7 years ago

Closed 7 years ago

#94 closed enhancement (fixed)

h264 frame encoding

Reported by: Antoine Martin Owned by: ahuillet
Priority: minor Milestone: 0.2
Component: core Version: 0.1.0
Keywords: Cc:

Description

Using xprax264 and some cython glue code it shouldn't be too hard to get x264 encoded bytes from an rgb24 screen grab.

Code to follow.

Attachments (9)

xpra-x264.patch (10.8 KB) - added by Antoine Martin 7 years ago.
adds an x264 encoder/decoder library, cython glue and makes distutils build it
xpra-x264-r2.patch (14.6 KB) - added by Antoine Martin 7 years ago.
v2 of the patch with support for up to 32 contexts
xpra-x264-r3.patch (26.3 KB) - added by Antoine Martin 7 years ago.
patch with all the changes to client/server/main
xpra-x264-r4.2.patch (27.0 KB) - added by Antoine Martin 7 years ago.
now using proper cython classes to simplify code
xpra-x264-r5.patch (27.4 KB) - added by Antoine Martin 7 years ago.
many fixes, this almost works
xpra-x264-r6.patch (27.6 KB) - added by Antoine Martin 7 years ago.
fix for rowstride, very close..
xpra-x264-setup-Cython-win32.patch (4.0 KB) - added by Antoine Martin 7 years ago.
to try to tell py2exe to invoke Cython on the x264 bindings
x264_win32.patch (1.0 KB) - added by ahuillet 7 years ago.
This patch enables x264 building under win32.
py2exe-xpra.log (45.2 KB) - added by Antoine Martin 7 years ago.
log from a successful py2exe build

Download all attachments as: .zip

Change History (29)

Changed 7 years ago by Antoine Martin

Attachment: xpra-x264.patch added

adds an x264 encoder/decoder library, cython glue and makes distutils build it

comment:1 Changed 7 years ago by ahuillet

Updated the library:
https://github.com/ahuillet/xprax264/

We need to create one encoder per window, ie. one encoder context per window.

init_encoder(width, height) will now return a pointer to an opaque structure (the encoder context). Create one for each window.

Same goes for init_decoder on the client side.

Then, compression is done by passing the context as the first argument to compress_image. Same goes for decompression.

Note that you cannot compress a subpart of the image: the full window must be encoded.

Changed 7 years ago by Antoine Martin

Attachment: xpra-x264-r2.patch added

v2 of the patch with support for up to 32 contexts

comment:2 Changed 7 years ago by Antoine Martin

The patch is a bit ugly, I couldn't find a way of doing dicts (or maps) in cython, so I used python maps to map to the index of a Cython/C array.
Also, I had to use (void *) instead of (x264lib_ctx *) to avoid compilation errors.
Apart from that, it seems to work.

Note: please update to latest trunk which has moved the actual window drawing code to xpra/window_backing.py - you may just implement it for PixmapBacking (gtk2) if you like.
Then add code like this to xpra/scripts/main.py's encoding section:

try:
    from xpra.x264 import codec
    ENCODINGS.append("x264")
except:
    pass

Either to the non is_gtk3() codepath, or to the common case if drawing is implemented for both.

Last edited 7 years ago by Antoine Martin (previous) (diff)

Changed 7 years ago by Antoine Martin

Attachment: xpra-x264-r3.patch added

patch with all the changes to client/server/main

Changed 7 years ago by Antoine Martin

Attachment: xpra-x264-r4.2.patch added

now using proper cython classes to simplify code

Changed 7 years ago by Antoine Martin

Attachment: xpra-x264-r5.patch added

many fixes, this almost works

Changed 7 years ago by Antoine Martin

Attachment: xpra-x264-r6.patch added

fix for rowstride, very close..

comment:3 Changed 7 years ago by Antoine Martin

Status: newaccepted

committed in r642, remaining issues:

  • use rgb24 for small windows (since rgb24 has to be supported for x264)
  • client does not close the decoders when windows go away (ouch!)
  • server encoder close called multiple times if we have resized (must cancel old on_close callback)
  • fix compilation warnings
  • enable x264 logging if xpra debug is on (via new argument to init(w,h)? or separate call?)

etc.

Last edited 7 years ago by Antoine Martin (previous) (diff)

comment:4 Changed 7 years ago by ahuillet

Todo:

  • evaluate bandwidth as compared to TurboVNC with similar visual quality
  • evaluate CPU usage in the same context
  • evaluate compression latency
  • dynamically change compression presets based on compression latency
  • measure decompression latency
  • add hooks for rate control (set target bandwidth)
  • add hooks to change color subsampling (currently hardcoded to 4x YUV420)
  • check if automatic lossless refresh is needed or if ratecontrol does it for free
  • dominate the world
Last edited 7 years ago by ahuillet (previous) (diff)

comment:5 Changed 7 years ago by Antoine Martin

Owner: changed from Antoine Martin to ahuillet
Status: acceptedassigned
  • r648 now fallsback to rgb264 for OR windows
  • r650 ensures we clean the encoder just once (and removes verbose logging)
  • r651 ensures we clean the decoder whenever the window it draws goes away

Changed 7 years ago by Antoine Martin

to try to tell py2exe to invoke Cython on the x264 bindings

comment:6 Changed 7 years ago by Antoine Martin

r660 re-enables x264 for OR windows - still seems to work here

comment:7 Changed 7 years ago by Antoine Martin

For reference, this is what one needs to do to build on OSX (from a jhbuild shell), we add Cython, yasm, ffmpeg and x264:

# Cython:
curl -O http://cython.org/release/Cython-0.15.1.tar.gz
tar -zxvf Cython-0.15.1.tar.gz 
cd Cython-0.15.1
python ./setup.py install

# yasm:
curl -O http://www.tortall.net/projects/yasm/releases/yasm-1.2.0.tar.gz
tar -zxvf yasm-1.2.0.tar.gz 
cd yasm-1.2.0
./configure --prefix=${JHBUILD_PREFIX} --libdir=${JHBUILD_PREFIX}/lib --build=i386-darwin
make & make install

# ffmpeg:
curl -O http://ffmpeg.org/releases/ffmpeg-0.10.2.tar.bz2
tar -jxf ffmpeg-0.10.2.tar.bz2 
cd ffmpeg-0.10.2
./configure --libdir=${JHBUILD_PREFIX}/lib --prefix=${JHBUILD_PREFIX}  --enable-shared --disable-static
make & make install

# x264:
curl -O ftp://ftp.videolan.org/pub/x264/snapshots/last_x264.tar.bz2
tar -jxf last_x264.tar.bz2
cd x264-snapshot-*
./configure --libdir=/Users/MacAdmin/gtk/inst/lib --prefix=/Users/MacAdmin/gtk/inst --enable-shared --disable-static --enable-pic
make & make_install

Then build xpra as usual:

python setup.py install

comment:8 Changed 7 years ago by Antoine Martin

For win32, you probably need this patch to Cython's Cython/Distutils/extension.py:

@@ -16,7 +16,6 @@ except ImportError:
     warnings = None
 
 class Extension(_Extension.Extension):
-    _Extension.Extension.__doc__ + \
     """pyrex_include_dirs : [string]
         list of directories to search for Pyrex header files (.pxd) (in
         Unix form for portability)

to avoid this error:

TypeError: unsupported operand type(s) for +: 'NoneType' and 'str' in setup.py

Then some magic incantations to tell Visual Studio where to find the libraries and headers, otherwise you get the very unhelpful error:

error: command '"C:\Program Files\Microsoft Visual Studio 9.0\VC\BIN\cl.exe"' failed with exit status 2
Last edited 7 years ago by Antoine Martin (previous) (diff)

comment:9 Changed 7 years ago by ahuillet

The actual error messages seem to be in fact logged to src/py2exe-xpra.log.

They are a bunch of syntax errors in x264lib.h because the include paths are incorrect.

Last edited 7 years ago by ahuillet (previous) (diff)

comment:10 Changed 7 years ago by ahuillet

Latest patch attached to this ticket makes py2exe succeed. The paths to ffmpeg are hardcoded however.

Changed 7 years ago by ahuillet

Attachment: x264_win32.patch added

This patch enables x264 building under win32.

comment:11 Changed 7 years ago by ahuillet

Rebased patch on latest rev. It builds fine under win32 but "xpra attach --encoding x264" refuses the x264 argument.

comment:12 Changed 7 years ago by Antoine Martin

r675 fixes the build for win32.

One troubling issue though: I can connect using x264 from win32 to a Linux server but on the second connection the server will crash hard.. memleak?

comment:13 Changed 7 years ago by ahuillet

A memleak won't trigger a crash, and I'm curious that it is the *server* crashing. The client crashing would be less surprising: can you confirm that the encoder context is *per window* and not *per window per client*?

Naturally it would be easier in the second case.

comment:14 Changed 7 years ago by Antoine Martin

It crashes on avcodec_close() which is called from clean_decoder() in x264lib.c, itself called from Encoder.clean().
We only clean when the client disconnects (via self._on_close or when the dimensions change).
Is it possible somehow that we end up doing both? del encoders[wid] is supposed to remove the reference to the encoder.

(gdb) bt
#0  0x000000300a664604 in avcodec_close () from /usr/lib64/libavcodec.so.53
#1  0x00007fdff357490b in clean_decoder (ctx=0x7fdfdc0017e0) at xpra/x264/x264lib.c:106
#2  0x00007fdff35715da in __pyx_pf_4xpra_4x264_5codec_6xcoder_2clean (__pyx_v_self=
    <xpra.x264.codec.Encoder at remote 0x1754e50>, unused=0x0) at xpra/x264/codec.c:719
#3  0x00000032da6dfb13 in call_function (oparg=<optimized out>, pp_stack=0x7fff65856388)
    at /usr/src/debug/Python-2.7.2/Python/ceval.c:4074
#4  PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at /usr/src/debug/Python-2.7.2/Python/ceval.c:2740
#5  0x00000032da6e15a5 in PyEval_EvalCodeEx (co=<optimized out>, globals=<optimized out>, locals=<optimized out>, 
    args=<optimized out>, argcount=0, kws=0x1a30438, kwcount=0, defs=0x0, defcount=0, closure=
    (<cell at remote 0x195d440>, <cell at remote 0x195dda8>, <cell at remote 0x195dc58>))
    at /usr/src/debug/Python-2.7.2/Python/ceval.c:3330
#6  0x00000032da6dfadb in fast_function (nk=<optimized out>, na=0, n=<optimized out>, pp_stack=0x7fff65856578, func=
    <function at remote 0x195cd70>) at /usr/src/debug/Python-2.7.2/Python/ceval.c:4186
#7  call_function (oparg=<optimized out>, pp_stack=0x7fff65856578) at /usr/src/debug/Python-2.7.2/Python/ceval.c:4111
#8  PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at /usr/src/debug/Python-2.7.2/Python/ceval.c:2740
#9  0x00000032da6e0580 in fast_function (nk=<optimized out>, na=1, n=<optimized out>, pp_stack=0x7fff658566b8, func=
    <function at remote 0x1839f50>) at /usr/src/debug/Python-2.7.2/Python/ceval.c:4176
#10 call_function (oparg=<optimized out>, pp_stack=0x7fff658566b8) at /usr/src/debug/Python-2.7.2/Python/ceval.c:4111
#11 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at /usr/src/debug/Python-2.7.2/Python/ceval.c:2740
#12 0x00000032da6e0580 in fast_function (nk=<optimized out>, na=3, n=<optimized out>, pp_stack=0x7fff658567f8, func=
    <function at remote 0x183dc08>) at /usr/src/debug/Python-2.7.2/Python/ceval.c:4176
#13 call_function (oparg=<optimized out>, pp_stack=0x7fff658567f8) at /usr/src/debug/Python-2.7.2/Python/ceval.c:4111
#14 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at /usr/src/debug/Python-2.7.2/Python/ceval.c:2740
#15 0x00000032da6e0580 in fast_function (nk=<optimized out>, na=3, n=<optimized out>, pp_stack=0x7fff65856938, func=
    <function at remote 0x183dcf8>) at /usr/src/debug/Python-2.7.2/Python/ceval.c:4176
#16 call_function (oparg=<optimized out>, pp_stack=0x7fff65856938) at /usr/src/debug/Python-2.7.2/Python/ceval.c:4111
#17 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at /usr/src/debug/Python-2.7.2/Python/ceval.c:2740
#18 0x00000032da6e15a5 in PyEval_EvalCodeEx (co=<optimized out>, globals=<optimized out>, locals=<optimized out>, 
    args=<optimized out>, argcount=3, kws=0x0, kwcount=0, defs=0x7fdff48a1260, defcount=2, closure=0x0)
    at /usr/src/debug/Python-2.7.2/Python/ceval.c:3330
#19 0x00000032da66dc2c in function_call (func=<function at remote 0x7fdff48a2c08>, arg=
    (<Protocol(_read_queue=<Queue(unfinished_tasks=14, queue=<collections.deque at remote 0x18fb7c0>, maxsize=5, all_tasks_done=<_Condition(_Verbose__verbose=False, _Condition__lock=<thread.lock at remote 0x1754cd0>, acquire=<built-in method acquire of thread.lock object at remote 0x1754cd0>, _Condition__waiters=[], release=<built-in method release of thread.lock object at remote 0x1754cd0>) at remote 0x195b110>, mutex=<thread.lock at remote 0x1754cd0>, not_full=<_Condition(_Verbose__verbose=False, _Condition__lock=<thread.lock at remote 0x1754cd0>, acquire=<built-in method acquire of thread.lock object at remote 0x1754cd0>, _Condition__waiters=[], release=<built-in method release of thread.lock object at remote 0x1754cd0>) at remote 0x195b0d0>, not_empty=<_Condition(_Verbose__verbose=False, _Condition__lock=<thread.lock at remote 0x1754cd0>, acquire=<built-in method acquire of thread.lock object at remote 0x1754cd0>, _Condition__waiters=[], release=<built-in method release of thread.lock object at remote 0x1754c...(truncated), kw=0x0) at /usr/src/debug/Python-2.7.2/Objects/funcobject.c:526

comment:15 Changed 7 years ago by Antoine Martin

that's fixed in r678

comment:16 Changed 7 years ago by ahuillet

Trying to run under Windows:

cannot load x264: cannot import name codec

comment:17 Changed 7 years ago by Antoine Martin

You need to ensure that the paths in the build files are correct, if they are then py2exe will generate a loader for "codec.pyd" and include all the required DLLs itself.

Changed 7 years ago by Antoine Martin

Attachment: py2exe-xpra.log added

log from a successful py2exe build

comment:18 Changed 7 years ago by Antoine Martin

The important parts from the log file above:

creating python loader for extension 'xpra.x264.codec'
  (E:\xpra\src\xpra\x264\codec.pyd -> xpra.x264.codec.pyd)
*** copy extensions ***
(..)
copying E:\xpra\src\xpra\x264\codec.pyd -> E:\xpra\src\dist\xpra.x264.codec.pyd
*** copy dlls ***
(..)
copying Z:\ffmpeg-win32-shared\bin\avcodec-54.dll -> E:\xpra\src\dist
(..)
copying Z:\ffmpeg-win32-shared\bin\swscale-2.dll -> E:\xpra\src\dist
(..)
copying Z:\ffmpeg-win32-shared\bin\avutil-51.dll -> E:\xpra\src\dist

Assuming you have those, the win32 build should have x264 support.

comment:19 Changed 7 years ago by ahuillet

x264 under windows now works.

comment:20 Changed 7 years ago by Antoine Martin

Resolution: fixed
Status: assignedclosed

this is enough for the 0.2 release, the remaining issues are in #110

Note: See TracTickets for help on using tickets.