前言

请注意:这是一个不成功的探索,仅供参考。

在做鸿蒙三方库移植时,一眼相中了目前还没有人移植的 vlc。

作为音视频、播放器领域巨头,vlc 的名号可谓无人不知、无人不晓。

但是,由于 vlc 使用的编译工具链过于古旧,项目庞大难以一一分析,本人对于三方库编译移植一条龙又是第一次接触。

凡此种种,不一而足。这些因素都指向了此次探索的失败结局。

解构

一切恐惧都来源于未知,对于 vlc 及其衍生物的不熟悉,不了解,片面理解导致了选型的失败。

从项目层级来看:

  • vlc - vlc 的总库,可以编译出来可执行文件

  • libvlc - C 语言的 vlc 基础库

  • libvlcpp - C++ 语言的绑定接口,方便 C++ 开发者开发 ( 并非 libvlc 的 C++ 实现 )

分析

从目标入手

由官方文档可知,vlc 目录下的每个模块含义

  • lib - libvlc 源码
  • src - libvlccore 源码
  • compat - libcompat 源码
  • include - 头文件
  • modules - 各种必要与非必要插件依赖

按照官方教程编译出来 libvlc.so 之后可以查看依赖关系

readelf -d libvlc.so

libvlc.so

readelf -d libvlccore.so

libvlccore.so

直接看上去似乎只要编译生成 libsrc 对应的目标就可以了

回到配置文件

Makefile.amconfigure.acautotools 工具链中的 autoMakeautoConfigure 两个工具的配置文件,二者会共同生成 configure 脚本文件。

configure 文件则可以生成 makefile 文件,以供编译。

configure.ac 文件中包含了各种变量、宏定义,其实这些都可以通过 configure 参数来调整,因此我们先不管它。

我们来看最重要的 Makefile.am

重点一共有两部分

第一部分是要配置的子文件夹,更改这个能够调整需要编译的文件夹

1
2
SUBDIRS = compat po share src modules lib doc bin test
DIST_SUBDIRS = m4 $(SUBDIRS)

第二部分是依赖关系和目标,可以看到 libvlc -> libvlccore -> libcompat

更改这个能够调整编译目标,也告诉了我们可以在 make 时选择你所需要的目标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Shortcut for developers to rebuild the core (libvlc + vlc)
# Don't use it if you don't know what it is about.
# Don't complain if it doesn't work. -- Courmisch
libcompat:
cd compat && $(MAKE) $(AM_MAKEFLAGS)

libvlccore: libcompat
cd src && $(MAKE) $(AM_MAKEFLAGS) libvlccore.la

libvlc: libvlccore
cd lib && $(MAKE) $(AM_MAKEFLAGS) libvlc.la

core: libvlc vlc$(EXEEXT)
cd bin && $(MAKE) $(AM_MAKEFLAGS) vlc$(EXEEXT) vlc-static$(EXEEXT)

doc:
cd doc && $(MAKE) $(AM_MAKEFLAGS) doc

.PHONY: libvlc core doc

我尝试过直接 make libvlc 会报

1
fatal error: 'fourcc_tables.h' file not found

注意他写的是 rebuild,可能是你得先编译通过了才能这么弄

因此,我们可以确定编译目标了:

  • 编译 compat src lib 文件夹中的目标
  • 获取 libvlc.so 供进一步开发

编译

交叉编译工具

工欲善其事,必先利其器。

选择 lycium 作为交叉编译工具,因为它支持 configure 编译方式,而且不需要写 BUILD.gn,也就不需要和源码一起编译。

选择这款工具会让你写一个 HPKBUILD,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# This is an example HPKBUILD file. Use this as a start to creating your own,
# and remove these comments.
# NOTE: Please fill out the license field for your package! If it is unknown,
# then please put 'unknown'.

# Contributor: Your Name <youremail@domain.com>
# Maintainer: Your Name <youremail@domain.com>

pkgname=vlc # 库名
pkgver=3.0.20 # 库版本
pkgrel=0 # 发布号
pkgdesc="" # 库描述
url="" # 官网链接
archs=("armeabi-v7a", "arm64-v8a") # cpu 架构
license=()
depends=() # 依赖库的目录名 必须保证被依赖的库的archs是当前库的archs的超集
makedepends=() # 构建库时的依赖工具->需要用户安装的工具
source="https://get.videolan.org/$pkgname/$pkgver/$pkgname-$pkgver.tar.xz" # 库源码下载链接

downloadpackage=true # 是否自动下载压缩包,如若不写默认 true. (应对一些特殊情况,代码只能 git clone (项目中依赖 submoudle ))
autounpack=true # 是否自动解压,如若不写默认 true, 如果为 false 则需要用户在 prepare 函数中自行解压
buildtools="configure" # 编译方法, 暂时支持cmake, configure, make等, 是什么就填写什么. 如若不写默认为cmake.

builddir=$pkgname-$pkgver # 源码压缩包解压后目录名 编译目录名
packagename=$builddir.tar.xz # 压缩包名
# source envset.sh
myhost=
# 为编译设置环境,如设置环境变量,创建编译目录等
prepare() {
mkdir -p $builddir/$ARCH-build
if [ $ARCH == ${archs[0]} ]
then
#setarm32ENV
export AS=${OHOS_SDK}/native/llvm/bin/llvm-as
export CC=${OHOS_SDK}/native/llvm/bin/arm-linux-ohos-clang
export CXX=${OHOS_SDK}/native/llvm/bin/arm-linux-ohos-clang++
export LD=${OHOS_SDK}/native/llvm/bin/ld.lld
export STRIP=${OHOS_SDK}/native/llvm/bin/llvm-strip
export RANLIB=${OHOS_SDK}/native/llvm/bin/llvm-ranlib
export OBJDUMP=${OHOS_SDK}/native/llvm/bin/llvm-objdump
export OBJCOPY=${OHOS_SDK}/native/llvm/bin/llvm-objcopy
export NM=${OHOS_SDK}/native/llvm/bin/llvm-nm
export AR=${OHOS_SDK}/native/llvm/bin/llvm-ar
export CFLAGS="-DOHOS_NDK -fPIC -D__MUSL__=1 -I${OHOS_SDK}/native/sysroot/usr/include"
export CXXFLAGS="-DOHOS_NDK -fPIC -D__MUSL__=1 -I${OHOS_SDK}/native/sysroot/usr/include -g -O2"
export LDFLAGS="-L${OHOS_SDK}/native/sysroot/usr/lib"
export LIBS="$LIBS -lc"
myhost=arm-rk3568-linux-gnu
fi
if [ $ARCH == ${archs[1]} ]
then
# setarm64ENV
export AS=${OHOS_SDK}/native/llvm/bin/llvm-as
export CC=${OHOS_SDK}/native/llvm/bin/aarch64-linux-ohos-clang
export CXX=${OHOS_SDK}/native/llvm/bin/aarch64-linux-ohos-clang++
export LD=${OHOS_SDK}/native/llvm/bin/ld.lld
export STRIP=${OHOS_SDK}/native/llvm/bin/llvm-strip
export RANLIB=${OHOS_SDK}/native/llvm/bin/llvm-ranlib
export OBJDUMP=${OHOS_SDK}/native/llvm/bin/llvm-objdump
export OBJCOPY=${OHOS_SDK}/native/llvm/bin/llvm-objcopy
export NM=${OHOS_SDK}/native/llvm/bin/llvm-nm
export AR=${OHOS_SDK}/native/llvm/bin/llvm-ar
export CFLAGS="-DOHOS_NDK -fPIC -D__MUSL__=1 -I${OHOS_SDK}/native/sysroot/usr/include"
export CXXFLAGS="-DOHOS_NDK -fPIC -D__MUSL__=1 -I${OHOS_SDK}/native/sysroot/usr/include -g -O2"
export LDFLAGS="-L${OHOS_SDK}/native/sysroot/usr/lib"
export LIBS="$LIBS -lc"
myhost=aarch64-rk3568-linux-gnu
fi
cd $builddir
./bootstrap
cd ${OLDPWD}
}

# ${OHOS_SDK} oh sdk安装路径
# $ARCH 编译的架构是 archs 的遍历
# $LYCIUM_ROOT/usr/$pkgname/$ARCH 安装到顶层目录的usr/$pkgname/$ARCH
# 执行编译构建的命令
build() {
cd $builddir/$ARCH-build
../configure $* --build=x86_64-unknown-linux-gnu --host=$myhost --with-sysroot=/${OHOS_SDK}/native/sysroot --disable-winstore-app --disable-dbus --disable-debug --disable-gprof --disable-cprof --disable-coverage --enable-optimizations --disable-mmx --disable-sse --disable-neon --disable-arm64 --disable-altivec --disable-lua --disable-vlm --disable-addonmanagermodules --disable-archive --disable-live555 --disable-dc1394 --disable-dv1394 --disable-linsys --disable-dvdread --disable-dvdnav --disable-bluray --disable-opencv --disable-smbclient --disable-dsm --disable-sftp --disable-nfs --disable-smb2 --disable-v4l2 --disable-decklink --disable-vcd --disable-libcddb --disable-screen --disable-vnc --disable-freerdp --disable-realrtsp --disable-macosx-avfoundation --disable-asdcp --disable-dvbpsi --disable-gme --disable-sid --disable-ogg --disable-shout --disable-matroska --disable-mod --disable-mpc --disable-wma-fixed --disable-shine --disable-omxil --disable-omxil-vout --disable-rpi-omxil --disable-crystalhd --disable-mad --disable-mpg123 --disable-gst-decode --disable-merge-ffmpeg --disable-avcodec --disable-libva --disable-dxva2 --disable-d3d11va --disable-avformat --disable-swscale --disable-postproc --disable-ffad --disable-aom --disable-dav1d --disable-vpx --disable-twolame --disable-fdkaac --disable-a52 --disable-dca --disable-flac --disable-libmpeg2 --disable-vorbis --disable-tremor --disable-speex --disable-opus --disable-spatialaudio --disable-theroa --disable-oggspots --disable-daala --disable-schroedinger --disable-png --disable-jpeg --disable-bpg --disable-x262 --disable-x265 --disable-x264 --disable-x26410b --disable-mfx --disable-fluidsynth --disable-fluidlite --disable-zvbi --disable-telx --disable-libass --disable-aribsub --disable-aribb25 --disable-kate --disable-tiger --disable-css --enable-gles2 --disable-xcb --disable-xvideo --disable-vdpau --disable-wayland --disable-sdl-image --disable-freetype --disable-fribidi --disable-harfbuzz --disable-fontconfig --disable-svg --disable-svgdec --disable-directx --disable-aa --disable-caca --disable-kva --disable-mmal --disable-evas --disable-pulse --disable-alsa --disable-oss --disable-sndio --disable-wasapi --disable-jack --disable-opensles --disable-tizen-audio --disable-samplerate --disable-soxr --disable-kai --disable-chromaprint --disable-chromecast --disable-qt --disable-skins2 --disable-sparkle --disable-ncurses --disable-lirc --disable-srt --disable-goom --disable-projectm --disable-vsxu --disable-avahi --disable-udev --disable-mtp --disable-upnp --disable-microdns --disable-libxml2 --disable-libgcrypt --disable-gnutls --disable-taglib --disable-secret --disable-kwallet --disable-update-chcek --disable-osx-notifications --disable-notify --disable-libplacebo --enable-run-as-root
make -j4
ret=$?
cd $OLDPWD
return $ret
}

# 打包安装
package() {
cd $builddir
make -C $ARCH-build install
cd $OLDPWD
}

# 进行测试的准备和说明
check() {
echo "The test must be on an OpenHarmony device!"
}

# 清理环境
cleanbuild() {
rm -rf ${PWD}/$builddir #${PWD}/$packagename
}

适配

可能这时你会有疑问,为什么 configure 的时候这么多 disable 呢,因为鸿蒙没有这些三方库(或许是部分,可惜 configure 的时候一直在找宿主机的三方库),SDK里面找不到他们。

好了,让我们进入错综复杂的 make 环节

pthread 起手

请注意:我没能解决这个问题

1
2
3
error: call to undeclared function 'pthread_cancel'; ISO C99 and later do not support implicit function declarations [-Werror,-Wimplicit-function-declaration]
error: call to undeclared function 'pthread_setcancelstate'; ISO C99 and later do not support implicit function declarations [-Werror,-Wimplicit-function-declaration]
error: call to undeclared function 'pthread_testcancel'; ISO C99 and later do not support implicit function declarations [-Werror,-Wimplicit-function-declaration]

和安卓类似,鸿蒙针对 pthread 做了许多限制,如上三个函数都不能使用

你可以很轻松的在网上找到 pthread_cancel pthread_setcancelstate解决方案

但是 pthread_testcancel 始终找不到

当然,你也可以试试这个 libbthread

尽管我在尝试的时候有一些未记录的bug,比如 pthread.h 缺少宏定义之类的,但是说不定有大用

由于时间有限,我选择了最坏的解决方式,GPT随便生成了一个塞进去了,能通过编译。

我把下面这段塞进了 vlc/src/posix/thread.c 里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#define SIG_CANCEL_SIGNAL SIGUSR1

static inline int pthread_cancel(pthread_t thread) {
return pthread_kill(thread, SIG_CANCEL_SIGNAL);
}

static int pthread_setcancelstate(int state, int *oldstate) {
sigset_t new, old;
int ret;
sigemptyset (&new);
sigaddset (&new, SIG_CANCEL_SIGNAL);

ret = pthread_sigmask(state == PTHREAD_CANCEL_ENABLE ? SIG_BLOCK : SIG_UNBLOCK, &new , &old);
if(oldstate != NULL)
{
*oldstate =sigismember(&old,SIG_CANCEL_SIGNAL) == 0 ? PTHREAD_CANCEL_DISABLE : PTHREAD_CANCEL_ENABLE;
}
return ret;
}

void pthread_testcancel(void) {
sigset_t pending;
sigemptyset(&pending);

// 检查是否有取消信号在队列中
if (sigpending(&pending) == 0 && sigismember(&pending, SIG_CANCEL_SIGNAL)) {
// 处理线程取消
pthread_exit(PTHREAD_CANCELED);
}
}

posix 的重重困扰

1
error: call to undeclared function 'posix_close'; ISO C99 and later do not support implicit function declarations [-Werror,-Wimplicit-function-declaration]

这种鸿蒙里面没有 posix 的函数问题可太多了,我难以全部解决。

决定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SUBDIRS = compat src lib

libcompat:
cd compat && $(MAKE) $(AM_MAKEFLAGS)

libvlccore: libcompat
cd src && $(MAKE) $(AM_MAKEFLAGS) libvlccore.la

libvlc: libvlccore
cd lib && $(MAKE) $(AM_MAKEFLAGS) libvlc.la

#core: libvlc vlc$(EXEEXT)
# cd bin && $(MAKE) $(AM_MAKEFLAGS) vlc$(EXEEXT) vlc-static$(EXEEXT)

#doc:
# cd doc && $(MAKE) $(AM_MAKEFLAGS) doc

.PHONY: libvlc

# Building aliases
# 全部注释

# 其余不变

这就是我的终极解决方案

部署

经验证,这样确实可以编译通过并生成 libvlc.solibvlccore.so

编译成功

放到板子上也能够成功使用 version 函数

version

可惜的是,调用 libvlc_new 返回值始终是 NULL

官方文档解释,需要为 src 设置环境变量;网上解释,需要把 modules 放到可执行文件的同文件夹下。

由于我打包成了 HPK 到开发板上运行, 这两种方法都难以解决我的问题。

思考

  1. 由于时间和设备限制,我没有尝试过使用 .a 文件(主要是忘了),感觉应该有戏
  2. 其实应该重点改造 vlc-for-android,但是 SDK 差距太大,项目也比较复杂,本人能力有限