Tuesday, November 7, 2017

Fix Matlab VideoReader plugin libmwgstreamerplugin failed to load properly issue

When I run the following command on Matlab R2017a from CentOS 6.9
v = VideoReader('test.avi');
I get the following error:
Error using VideoReader/init (line 619)
The VideoReader plugin libmwgstreamerplugin failed to load properly.
Error in VideoReader (line 172)
            obj.init(fileName);
The reason is that old versions of Matlab used to rely on gstreamer-0.10 to process video but from at lease R2014a the dependency of gstreamer library has changed to gstreamer-1.0 if you want to use VideoReader('video.avi') command. So upgrade your OS will be the easiest way to solve this issue but if this is not possible as I am facing, then upgrade the gstreamer library is the only solution.

To check which gstreamer you need check the dependencies of Matlab libmwgstreamerplugin.so by readelf command:
# cd /usr/local/MATLAB/R2017a/toolbox/shared/multimedia/bin/glnxa64/reader
# readelf -d libmwgstreamerplugin.so

Dynamic section at offset 0xd780 contains 43 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libmwmultimediacommonexceptions.so]
 0x0000000000000001 (NEEDED)             Shared library: [libmwtamimframe.so]
 0x0000000000000001 (NEEDED)             Shared library: [libmwi18n.so]
 0x0000000000000001 (NEEDED)             Shared library: [libboost_system.so.1.56.0]
 0x0000000000000001 (NEEDED)             Shared library: [libmwcpp11compat.so]
 0x0000000000000001 (NEEDED)             Shared library: [libtbb.so.2]
 0x0000000000000001 (NEEDED)             Shared library: [libtbbmalloc.so.2]
 0x0000000000000001 (NEEDED)             Shared library: [libgstvideo-1.0.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [libgstbase-1.0.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [libgstreamer-1.0.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [libgobject-2.0.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [libgmodule-2.0.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [libgthread-2.0.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [librt.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libxml2.so.2]
 0x0000000000000001 (NEEDED)             Shared library: [libglib-2.0.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [libgstapp-1.0.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libm.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libpthread.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000f (RPATH)              Library rpath: [$ORIGIN:$ORIGIN/../../../../../../bin/glnxa64:$ORIGIN/../../../../../../sys/os/glnxa64]
 0x000000000000000c (INIT)               0x43e8
 0x000000000000000d (FINI)               0xacf8
 0x0000000000000004 (HASH)               0x1b8
 0x000000006ffffef5 (GNU_HASH)           0x5e8
 0x0000000000000005 (STRTAB)             0x1368
 0x0000000000000006 (SYMTAB)             0x6d8
 0x000000000000000a (STRSZ)              4804 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000003 (PLTGOT)             0x20db48
 0x0000000000000002 (PLTRELSZ)           1896 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0x3c80
 0x0000000000000007 (RELA)               0x27c8
 0x0000000000000008 (RELASZ)             5304 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x000000006ffffffe (VERNEED)            0x2738
 0x000000006fffffff (VERNEEDNUM)         4
 0x000000006ffffff0 (VERSYM)             0x262c
 0x000000006ffffff9 (RELACOUNT)          114
 0x0000000000000000 (NULL)               0x0
Above is R2017a library dependencies and libgstreamer-1.0.so.0 is required. But on CentOS 6.x there is no existing gstreamer-1.0 package AFAIK, build it from the source code is my only option and here are the steps.

Step 1: Check your OS version

# cat /etc/centos-release
CentOS release 6.9 (Final)

If your CentOS is under version 7, then you need do following steps, otherwise these steps will not fit.

Step 2:  Upgrade GCC

To compile gstreamer-1.0 requires gcc 4.5 or above but CentOS 6.x only ships gcc 4.4.7 which need be upgraded first.

# gcc --version
gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-18)
Copyright (C) 2010 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

# yum install centos-release-scl
# yum install devtoolset-3-toolchain
After install devtoolset, you need enable it

# scl enable devtoolset-3 bash
Now to check the gcc version

# gcc --version
gcc (GCC) 4.9.2 20150212 (Red Hat 4.9.2-6)
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Step 3: Upgrade Python

Python 2.7 or above is required to compile gstreamer-1.0 but CentOS 6.x only provides Python 2.6.x and the operating system is heavily depends on it which should NOT be replaced by Python 2.7 or newer version. So make sure when you install Python 2.7, choose "make altinstall"!

# yum install -y centos-release-SCL
# yum install -y python27
# cd ~
# wget https://www.python.org/ftp/python/2.7.14/Python-2.7.14.tgz
# tar xzf Python-2.7.14.tgz
# cd Python-2.7.14
# ./configure
# make altinstall
# echo "alias python='/usr/local/bin/python2.7'" >> ~/.bashrc
# . ~/.bashrc

Optional install pip:
# curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"
# python2.7 get-pip.py

 Step 4: Install yasm 1.3.0

The performance of gstreamer-1.0 openh264 and x264 plugins can be dramatically improved by hardware acceleration which requires yasm optimization. But again the CentOS 6.x default yasm is too old which is not supported by gstreamer-1.0.

# cd ~
# curl -O -L http://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz
# tar xzvf yasm-1.3.0.tar.gz
# cd yasm-1.3.0
# ./configure
# make
# make install

Step 5: Prepare cerbero

Cerbero is the tool recommended by gstreamer team and it need some prerequisites to work properly. First is to create the default profile and target folder:

# mkdir -p ~/.cerbero/
# vi ~/.cerbero/cerbero.cbc
Paste following content to the file
----------------------------------------------------------
import os
from cerbero.config import Platform, Architecture, Distro

packages_prefix = 'gstreamer-sdk'
packager = 'GStreamer SDK packagers <packages@gstreamer.com>'
prefix = "/opt/gstreamer-sdk"

git_root = "git://anongit.freedesktop.org/gstreamer-sdk"

# Uncomment to allow parallel builds
# allow_parallel_build = True

# Uncomment this to allow cross-building for another architecture
# target_arch = Architecture.X86_64
----------------------------------------------------------

# mkdir -p /opt/gstreamer-sdk
 I am using /opt/gstreamer-sdk/ as the target folder. Then you need download the cerbero framework and set it up:

# cd ~
# git clone git://anongit.freedesktop.org/gstreamer/cerbero
# echo "alias cerbero='~/cerbero/cerbero-uninstalled'" >> ~/.bashrc
# . ~/.bashrc
# cd ~/cerbero
# cerbero bootstrap

Step 6: Build gstreamer-1.0

Finally we can start building gstreamer-1.0 but there are couple of catches in the recipes need be fixed first.

cdparanoia: post install step will fail because the global environment FilesProvider.LIBS_CAT is empty and I don't know how to change it. So my simple solution is to hard coded it!

# vi ~/cerbero/recipes/cdparanoia.recipe
----------------------------------------------------------
            #libs = self.files_list_by_category(FilesProvider.LIBS_CAT)
            #for lib in libs:
            for lib in self.files_libs:
                f = os.path.join(self.config.prefix + '/lib', lib + '.so.0.' + self.version)
                print(f)
----------------------------------------------------------
The post install steps are just to change the permissions on the generated .so files, so find the files in target folder first and work out the hard code values. Be aware of the indents of your code and print(f) is just for fun to show you the changes worked :D.

# cd /opt/gstreamer-sdk/lib
# ls | grep cdda
libcdda_interface.a
libcdda_interface.so
libcdda_interface.so.0
libcdda_interface.so.0.10.2
libcdda_paranoia.a
libcdda_paranoia.so
libcdda_paranoia.so.0
libcdda_paranoia.so.0.10.2
 nettle: has the exact issue as cdparanoia but a bit more complex with two different libraries with two different versions XD.

# vi ~/cerbero/recipes/nettle/nettle.recipe
----------------------------------------------------------
            #libs = self.files_list_by_category(FilesProvider.LIBS_CAT)
            #for lib in libs:
            for lib in self.files_libs:
                f = os.path.join(self.config.prefix + '/lib', lib)
                print(f)
                if fnmatch(f, '*libnettle*'):
                   f = f + '.so.6.3'
                if fnmatch(f, '*libhogweed*'):
                   f = f + '.so.4.3'
                print(f)
----------------------------------------------------------

# cd /opt/gstreamer-sdk/lib
# ls | grep nettle
libnettle.a
libnettle.la
libnettle.so
libnettle.so.6
libnettle.so.6.3
# ls | grep hogweed
libhogweed.a
libhogweed.la
libhogweed.so
libhogweed.so.4
libhogweed.so.4.3
 flac: you need specify -lrt to enable real-time functions, otherwise the linker will complain undefined reference to 'clock_gettime'.

# vi ~/cerbero/recipes/flac.recipe
----------------------------------------------------------
    def prepare(self):
        if self.config.target_platform in [Platform.DARWIN, Platform.IOS]:
            if self.config.target_arch == Architecture.X86:
                self.configure_options += ' --disable-asm-optimizations'
        if self.config.target_platform in [Platform.ANDROID, Platform.IOS]:
            self.autoreconf = True
        if self.config.target_platform == Platform.LINUX:
            self.new_env['LIBS'] = '-lrt'
----------------------------------------------------------
 OK, we have everything ready, pull the trigger:

# cerbero package gstreamer-1.0
 Unfortunately the above command cannot generate the proper RPM package and since I only need solve the issue for Matlab, this is not a big problem to me, I only need the compiled run-time libraries.

Step 7: Let Matlab using the new gstreamer-1.0

The quick way is to change LD_LIBRARY_PATH

# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/gstreamer-sdk/lib
# ldconfig

And the permanent way is to update ld.so.conf.d

# vi /etc/ld.so.conf.d/gstreamer.conf
----------------------------------------------------------
/opt/gstreamer-sdk/lib
----------------------------------------------------------
# reboot
Now Matlab R2017a is able to load video files:

>> obj=VideoReader('test.avi');
0:00:00.138478543  7933 0x7f339c007b70 ERROR                  libav :0:: Context scratch buffers could not be allocated due to unknown size.
0:00:00.138711800  7933 0x7f339c007b70 ERROR                  libav :0:: Context scratch buffers could not be allocated due to unknown size.
>>
The ERROR is caused by the leading black frames in the video file, not the gstreamer-1.0 package issue.

Just uploaded the compiled folder to Google Drive https://drive.google.com/open?id=1jUL2q9LvpTcNtEbVtpaugJUPVBHoYsJ8. After you download the gstreamer-sdk.tgz file, just do following

# cd /opt/
# mv ~/Downloads/gstreamer-sdk.tgz .
# tar zxf gstreamer-sdk.tgz
Then add gstreamer.conf as Step 7 described. I have tested it on CentOS 6.9, CentOS 7.5, Ubuntu 18.04 lts along with Matlab R2017a, R2017b and R2018b.

WARNING about Ubuntu 18.04 lts with NVidia video card, the gstreamer libraries will cause you desktop failed to start, in this case, please boot into recovery mode and delete the file /etc/ld.so.conf.d/gstreamer.conf and run ldconfig. Use the official packages instead:

# apt-get install libgstreamer1.0-0 gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-doc gstreamer1.0-tools

SIZEOFINFINITY[∞]