Android Q播放器(编译报错处理)

最新在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...

你可能感兴趣的:(android)