最新在Bring up公司的播放器从Android 4.4到Android Q,期间遇到很多“坑”,总结出来记录一下。期间也会简单介绍一下在Android系统中新建一个类似Nuplayer的播放器大概需要哪些步骤。
代码目录:frameworks/av/media/libmediaplayerservice/
最先动的地方是MediaPlayerFactory.cpp,我的做法是根据项目需求,选择是否走自己的播放器还是Android原生播放器(因为过XTS认证会走Android原生flow),改动如下(记得把RAIN_PLAYER的define放在跟NuPlayer一样的地方,以后这样简单的事情就不在赘述啦^_^):
void MediaPlayerFactory::registerBuiltinFactories() {
Mutex::Autolock lock_(&sLock);
if (sInitComplete)
return;
IFactory* factory = new NuPlayerFactory();
if (registerFactory_l(factory, NU_PLAYER) != OK)
delete factory;
factory = new TestPlayerFactory();
if (registerFactory_l(factory, TEST_PLAYER) != OK)
delete factory;
#ifdef BUILD_WITH_XXXX_MM
registerFactory_l(new RainPlayerFactory(), RAIN_PLAYER);
#endif
sInitComplete = true;
}
分享一个小技巧,我们公司的coding style是如果有动到Android原生的部分,要在改动上下加上如下打印:
// xxxx Android Patch Begin
// xxxx Android Patch End
RainPlayerFactory部分如下,至于自己的player放在哪里,完全由自己决定,可以选择放在frameworks/av/media/libmedia或是其他什么地方,我放在了vendor目录。
// xxxx Android Patch Begin
class RainPlayerFactory : public MediaPlayerFactory::IFactory {
public:
virtual float scoreFactory(const sp& /* client */,
const char* url,
const sp& /* httpService */,
float curScore) {
static const float kOurScore = 2.0; // 定义一个比较高的分数,PK赢了会走自己的player
if (kOurScore <= curScore)
return 0.0;
if (!strncasecmp("http://", url, 7)
|| !strncasecmp("https://", url, 8)) {
size_t len = strlen(url);
if (len >= 5 && !strcasecmp(".m3u8", &url[len - 5])) {
#ifdef SPECIAL_STREAM_USE_NUPLAYER
if (strstr(url, "/sample_aes/")) {
return 0.0;
}
#endif
return kOurScore;
}
if (strstr(url,"m3u8")) {
#ifdef SPECIAL_STREAM_USE_NUPLAYER
if (strstr(url, "/sample_aes/")) {
return 0.0;
}
#endif
return kOurScore;
}
}
if (!strncasecmp("rtsp://", url, 7)) {
return kOurScore;
}
return 0.0;
}
virtual float scoreFactory(const sp& /* client */,
const sp& /* source */,
float /* curScore */) {
return 1.0;
}
virtual sp createPlayer(pid_t /* pid */) {
ALOGV("Create RainPlyer");
return new RainPlyer;
}
};
#ifdef SUPPORT_RAINPLAYER
class RainPlayerFactory : public MediaPlayerFactory::IFactory {
public:
virtual sp createPlayer(pid_t /* pid */) {
ALOGV("Create RainPlyer");
return new RainPlyer;
}
};
#endif
// xxxx Android Patch End
编译部分,我在mediaplayerservice加入了soong,这样可以根据BoardConfig.mk来进行动态编译,简单来说就是编译中的环境变量会传给libmediaplayerservice.go比如可以加变量来动态选择走NuPlayer还是RainPlayer,只在BoardConfig.mk中改一行配置信息即可。BoardConfig.mk修改之后,编译系统会自动去检测:
device/xxx/xxx/BoardConfig.mk was modified, regenerating...
修改frameworks/av/media/libmediaplayerservice/Android.bp:
// xxx Android Patch Begin
defaults: [
"mediaplayerservicedefaults",
],
// xxx Android Patch End
添加frameworks/av/media/libmediaplayerservice/soong/Android.bp:
bootstrap_go_package {
name: "soong-mediaplayerservice",
pkgPath: "android/soong/vendor/xxx/mediaplayerservice",
deps: [
"blueprint",
"blueprint-pathtools",
"soong",
"soong-android",
"soong-cc",
],
srcs: [
"libmediaplayerservice.go",
],
pluginFor: ["soong_build"],
}
mediaplayerservicedefaults {
name: "mediaplayerservicedefaults",
}
添加frameworks/av/media/libmediaplayerservice/soong/libmediaplayerservice.go:
package libmediaplayerservice
import (
"fmt"
"android/soong/android"
"android/soong/cc"
)
//Build Option
const BUILD_WITH_RIAN string = "BUILD_WITH_RIAN"
func init() {
android.RegisterModuleType("mediaplayerservicedefaults",
mediaplayerserviceDefaultsFactory)
}
func mediaplayerserviceDefaultsFactory() android.Module {
module := cc.DefaultsFactory()
android.AddLoadHook(module, globalMediaPlayerServiceDefault)
return module
}
func globalMediaPlayerServiceDefault(ctx android.LoadHookContext) {
type props struct {
Srcs []string
Include_dirs []string
Shared_libs []string
Static_libs []string
Cflags []string
}
p := &props{}
p.Srcs = globalMediaPlayerServiceSrcs(ctx)
p.Include_dirs = globalMediaPlayerServiceIncludeDirs(ctx)
p.Shared_libs = globalMediaPlayerServiceSharedLibs(ctx)
p.Static_libs = globalMediaPlayerServiceStaticLibs(ctx)
p.Cflags = globalMediaPlayerServiceCflags(ctx)
ctx.AppendProperties(p)
}
func globalMediaPlayerServiceSrcs(ctx android.BaseContext) ([]string) {
var srcs []string
return srcs
}
func globalMediaPlayerServiceIncludeDirs(ctx android.BaseContext) ([]string) {
var includeDirs []string
var tmp string
if (envIsTrue(ctx, BUILD_WITH_RIAN) {
// 在这里可以加入你新建Player的相关头文件
includeDirs = append(includeDirs, "external/skia/include")
includeDirs = append(includeDirs, "external/skia/include/core")
includeDirs = append(includeDirs, "vendor/XXX/common/libraries/media/mmplayer/")
}
return includeDirs
}
func globalMediaPlayerServiceSharedLibs(ctx android.BaseContext) ([]string) {
var shared_libs []string
if (envIsTrue(ctx, BUILD_WITH_RIAN) {
// 在这里可以加入你新建Player的相关动态链接库
shared_libs = append(shared_libs, "librainplayer")
}
return shared_libs
}
func globalMediaPlayerServiceStaticLibs(ctx android.BaseContext) ([]string) {
var static_libs []string
if envIsTrue(ctx, BUILD_WITH_MARLIN) {
// 在这里可以加入你新建Player的相关静态链接库
static_libs = append(static_libs, "libstagefright_nuplayer_wasabi")
static_libs = append(static_libs, "libWasabi")
}
return static_libs
}
func globalMediaPlayerServiceCflags(ctx android.BaseContext) ([]string) {
var cflags []string
// 如果没成功,可以在这里加打印,看看env有没有设置下来
fmt.Println("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
fmt.Println("BUILD_WITH_RIAN:",
ctx.AConfig().IsEnvTrue("BUILD_WITH_RIAN"))
if (envIsTrue(ctx, BUILD_WITH_RAIN) {
// 在这里可以加入你新建Player的条件编译
// 与Android.mk的LOCAL_CFLAGS += -DBUILD_WITH_RAIN类似
cflags = append(cflags, "-BUILD_WITH_RAIN")
}
return cflags
}
func envDefault(ctx android.BaseContext, key string, defaultValue string) string {
ret := ctx.AConfig().Getenv(key)
if ret == "" {
return defaultValue
}
return ret
}
func envIsOptionValue(ctx android.BaseContext, key string, value string) bool {
return ctx.AConfig().Getenv(key) == value
}
func envIsNotOptionValue(ctx android.BaseContext, key string, value string) bool {
return ctx.AConfig().Getenv(key) != value
}
func envIsTrue(ctx android.BaseContext, key string) bool { //ctx android.LoadHookContext) {
return ctx.AConfig().IsEnvTrue(key);
}
func printLog(msg string) {
fmt.Println(msg)
}
func print2Log(msg string, param string) {
if param != "" {
fmt.Errorf(msg + ", %s", param)
}
}
mediaplayerservice的工作量基本就这些,剩下再往下就是播放器本身的部分,还有OpenMax、display...这些部分涉及公司的代码太多,不好粘贴了。接下说一下遇到的坑...
坑1:
如果编译的模块开始vndk,你又修改了他的API,那么VNDK就会报错,出错信息一般长这样:
******************************************************
error: VNDK library: libmedia_omx's ABI has EXTENDING CHANGES Please check compatiblity report at : out/soong/.intermediates/frameworks/av/media/libmedia/libmedia_omx/android_arm_armv8-a_cortex-a53_vendor_shared/libmedia_omx.so.abidiff
******************************************************
---- Please update abi references by running platform/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libmedia_omx ----
我总结了如下几种方式:
1、最粗暴的(不推荐)
在BoardConfig.mk中加入如下两行,可以在编译的时候不去check VNDK,关掉这个可能会导致XTS某些测项跑不过,一般code review也不会给过...
BOARD_VNDK_VERSION :=
BOARD_VNDK_RUNTIME_DISABLE := true
2、一般粗暴:
删除如下目录的这些文件
/prebuilts/abi-dumps/vndk
./28/32/arm_armv7-a-neon/source-based/libion.so.lsdump.gz
./28/32/x86/source-based/libion.so.lsdump.gz
./28/64/arm64_armv8-a/source-based/libion.so.lsdump.gz
./28/64/arm_armv8-a/source-based/libion.so.lsdump.gz
./28/64/x86/source-based/libion.so.lsdump.gz
./28/64/x86_64/source-based/libion.so.lsdump.gz
./28/64/x86_x86_64/source-based/libion.so.lsdump.gz
我最终在本地的解法就是进入到/prebuilts/abi-dumps/vndk,搜索libmedia_omx, 将搜索到的libmedia_omx.so.lsdump*全部删除。并重新编译,结果就build pass了。这样也不太好,如果你上代码到服务器,自己这样一搞可以编过,那别人就惨了...
3、正解(推荐):
其实报错信息有让你去跑一个脚本,之前我也有试过,始终跑不过,找了很多资料,结果少了-products xxx // xxx为boardname,敲如下命令就build pass了,上code到服务器,完美~
development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libmedia_omx -products xxx // xxx为boardname
坑2:
error: frameworks/av/media/libmediaplayerservice/Android.bp:1:1: dependency "librain" of "libmediaplayerservice" missing variant:
arch:android_arm64_armv8-a_cortex-a53, image:core, link:shared, version:
available variants:
arch:android_arm_armv7-a-neon_cortex-a7, image:core, link:shared, version:
FAILED: [W][2019-10-21T15:15:03+0800][1] bool caps::initNs(nsjconf_t *)():215 prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL): Invalid argument
15:15:21 soong bootstrap failed with: exit status 1
初步看起来像是想要去编译librain的64位的其他架构的版本,晚上也有很多种情况,这里我遇到的情况是,librain的Android.bp中动态链接了一些vendor的底层lib,导致libmediaplayerservice链接librain的时候报错,拿掉这些lib之后就build pass了。
shared_libs: [
"libc",
"liblog",
"libdl",
"libcutils",
"libutils",
"libui",
"libmedia",
"libsurfaceflinger",
"libbinder",
"libstagefright",
"libstagefright_foundation",
"libz",
"libcrypto",
"libgui",
//"[email protected]",
//"[email protected]",
//"[email protected]",
],
坑3:
FAILED:
build/make/core/base_rules.mk:325: error: vendor/xxx/common/libraries/media/mmplayer/mmp: MODULE.TARGET.SHARED_LIBRARIES.libmmp_32 already defined by vendor/xxx/common/libraries/media/mmplayer/mmp.
15:02:20 ckati failed with: exit status 1
#### failed to build some targets (02:35 (mm:ss)) ####
因为是Andorid 4.4移植到Android 10.0,所以原来Android 4.4的Android.mk还存在各个目录,如果Android.mk和Android.bp定义了相同的moudle就会报如上错误。解决方式也很简单,我的方法是在Android.mk中加入如下条件,若是API 28以下让整个Andorid.mk失效,这样既不会影响老版本的Andorid项目,也解决的Android Q的编译问题。
ifeq (1, $(strip $(shell expr $(PLATFORM_SDK_VERSION) \< 28)))
xxx
endif
坑4:
FAILED: out/soong/.intermediates/vendor/xxx/common/libraries/media/mmplayer/mmp/libmmp/android_arm_armv7-a-neon_cortex-a7_core_shared/libmmp.so.toc
echo "module libmmp missing dependencies: libmmdisp" && false
module libmmp missing dependencies: libmmdisp
[ 97% 987/1013] //vendor/xxx/common/libraries/media/mmplayer/mmp:libmmp strip libmmp.so [arm]
FAILED: out/soong/.intermediates/vendor/xxx/common/libraries/media/mmplayer/mmp/libmmp/android_arm_armv7-a-neon_cortex-a7_core_shared/libmmp.so
echo "module libmmp missing dependencies: libmmdisp" && false
module libmmp missing dependencies: libmmdisp
[ 97% 988/1013] //vendor/xxx/common/libraries/media/mmplayer/mmp:libmmp link libmmp.so [arm]
FAILED: out/soong/.intermediates/vendor/xxx/common/libraries/media/mmplayer/mmp/libmmp/android_arm_armv7-a-neon_cortex-a7_core_shared/unstripped/libmmp.so
echo "module libmmp missing dependencies: libmmdisp" && false
module libmmp missing dependencies: libmmdisp
libmmdisp是我司的显示模块,跟我移植的播放器不是同一process,所以要动态链接一下,链接libmmdisp时就会报错,然后本身libmmdisp编译是可以pass的。
原因是libmmdisp还是延用之前Android.mk的编译方式,我的player是用Android.bp去链接libmmdisp,所以找不到。解法自然是在libmmdisp中引入Android.bp的编译方式。
编译上比较棘手的坑大概就这些了,这种工作势必会经历如下阶段:
代码编不过;
代码编过了链接出问题;
链接没问题了,编版本烧录起不来;
平台起来了,播放会挂掉;
播放不挂了,但是黑屏,显示不正常;
最后显示正常,剩一些无声,硬解码起不来等小bug在慢慢修...
如上每一步都遇到了,要时刻保持良好心态,一步一个脚印,不多说了,解bug去了,o(╥﹏╥)o...