Android 13(T) - Media框架(2)- libmedia

这一节学习有两个目标:

1 熟悉Android Media API的源码路径与调用层次

2 从MediaPlayer的创建与销毁了解与native的串接

1、源码路径

Media相关的API位于:frameworks/base/media/java/android/media,里面提供有MediaPlayer MediaCodecList MediaExtractor MediaCodec等常用类型;

JNI函数位于:frameworks/base/media/jni,每一个Java类型都有其对应的JNI函数文件android_media_xxx。譬如MediaPlayer.java,它对应的JNI函数文件名是android_media_MediaPlayer.cpp

JNI函数中会封装有真正的native实现,这些native实现分布在源码的不同位置,用的比较多的位置在libmedia 和 libstagefright下,这里我们先了解libmediaframeworks/av/media/libmedia

这里将常用API与其底层实现分为两组:

  • MediaPlayer:Android为我们提供的播放器接口,实现了基本的播放器功能与常见Callback事件的串接,使用起来很方便;
  • MediaCodecList MediaExractor MediaCodec:实现播放器需要的基本组件,用这些组件也可以实现强大的播放器,自由度高也会麻烦一些;

我们的目标是了解Media框架,所以这里从简单一点的MediaPlayer入手,看看它是如何调用native实现的。


2、MediaPlayer的创建

相关代码路径:

在看正式的创建流程前,我们先要注意MediaPlayer.java中的如下代码段:

java 复制代码
    static {
        System.loadLibrary("media_jni");
        native_init();
    }

它会加载media_jni.so,并执行native函数android_media_MediaPlayer_native_init。native_init会获取java类中的postEventFromNative方法ID,mNativeContextmNativeSurfaceTexture等字段的ID,获取到的ID会存储在静态变量fields_t中,后期可以通过这些ID找到Java对象中对应的成员,获取其存储的值或者向其中存储值。

cpp 复制代码
static fields_t fields;

fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative", "(Ljava/lang/Object;IIILjava/lang/Object;)V");
fields.surface_texture = env->GetFieldID(clazz, "mNativeSurfaceTexture", "J");

接下来看MediaPlayer.java中的构造函数:

java 复制代码
private MediaPlayer(int sessionId) {
	...
	try (ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()) {
		native_setup(new WeakReference<MediaPlayer>(this), attributionSourceState.getParcel());
	}
}

核心是调用native_setup方法,需要将自身的弱引用对象作为参数向下传递:

cpp 复制代码
static void
android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
                                       jobject jAttributionSource)
{
    Parcel* parcel = parcelForJavaObject(env, jAttributionSource);
    android::content::AttributionSourceState attributionSource;
    attributionSource.readFromParcel(parcel);
    // 创建MediaPlayer native对象
    sp<MediaPlayer> mp = sp<MediaPlayer>::make(attributionSource);
	// 创建并注册Callback对象
    sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener(env, thiz, weak_this);
    mp->setListener(listener);
	// 将MediaPlayer对象存储到Java对象中
    setMediaPlayer(env, thiz, mp);
}

native_setup干了3件事:

  1. 创建MediaPlayer对象;
  2. 用传下来的弱引用对象创建Listener对象,用于Callback调用;
  3. 将MediaPlayer对象存储到Java对象中;

前两个对象的创建很简单,我们来看看如何存储MediaPlayer native对象的:

cpp 复制代码
static sp<MediaPlayer> setMediaPlayer(JNIEnv* env, jobject thiz, const sp<MediaPlayer>& player)
{
    Mutex::Autolock l(sLock);
    sp<MediaPlayer> old = (MediaPlayer*)env->GetLongField(thiz, fields.context);
   	// 手动增加强引用计数
    if (player.get()) {
        player->incStrong((void*)setMediaPlayer);
    }
    // 检查mNativeContext中存储的MediaPlayer对象
    if (old != 0) {
        old->decStrong((void*)setMediaPlayer);
    }
    // 将MediaPlayer的地址存储到mNativeContext
    env->SetLongField(thiz, fields.context, (jlong)player.get());
    return old;
}

setMediaPlayer很简单,但是也有需要注意的地方:

  1. 首先要增加MediaPlayer native对象的强引用计数;
  2. 检查mNativeContext中存储的地址是否为NULL,如果不为NULL则需要销毁存储的MediaPlayer对象;
  3. 将新的MediaPlayer对象存储到mNativeContext中。

虽然我们会把MediaPlayer对象存储到Java字段中,但是其引用计数在setMediaPlayer中仍为1,如不增加引用计数,出了当前作用域对象将自动销毁。


3、MediaPlayer的销毁

我平时看代码可能会注重了解播放器的启动、运行以及Callback处理流程,很容易就忽视release的流程,但是release做了哪些事情,按照什么顺序释放资源同样也是很重要的。

MediaPlayer.java的release会调用native函数_release

java 复制代码
public void release() {
	...
	_release();
}

_release方法如下,会有4件事需要处理:

cpp 复制代码
static void
android_media_MediaPlayer_release(JNIEnv *env, jobject thiz)
{
    // 释放IGraphicBufferProducer
    decVideoSurfaceRef(env, thiz);
    // 删除Java字段mNativeContext存储的指针
    sp<MediaPlayer> mp = setMediaPlayer(env, thiz, 0);
    // 取消Callback注册,释放mediaplayer wrapper
    if (mp != NULL) {
        // this prevents native callbacks after the object is released
        mp->setListener(0);
        mp->disconnect();
    }
}
  1. 释放IGraphicBufferProducer(surface),暂时不去了解;
  2. 删除Java字段mNativeContext存储的地址,将MediaPlayer的强引用计数减一;
  3. 取消Callback注册,释放MediaPlayer wrapper的内容;
  4. 出了当前函数作用域,MediaPlayer对象自动销毁。

到这儿MediaPlayer与native的串接就了解结束了,其他API是如何调用的想必也很容易看懂了。


4、libmedia

从前面几节我们了解到MediaPlayer的native实现位于libmedia中,这里我们来看看libmedia到底包含哪些东西。

libmedia底下的内容大致可以分为四类:

  1. Java类的native实现,例如:mediaplayer.cpp mediarecorder.cpp MediaScanner.cpp
  2. binder service所需要的文件,这类可以分为两小类:
    • aidl文件,编译时直接生成bp/bn文件,例如IMediaExtractorService.aidl
    • 手动实现的bp/bn文件,例如IMediaPlayerService.cpp
  3. binder service中传输对象所需的文件,例如IMediaPlayer.cpp IMediaExtractor.cpp IDataSource.cpp IMediaCodecList.cpp
  4. 其他成员类,例如MediaCodecInfo.cpp MediaCodecBuffer.cpp OMXBuffer.cpp

虽说第二类第三类都是用于binder service,但是他们之间仍有不一样的地方。第二类文件用于提供binder service服务,而第三类文件一般是在service中创建对象,通过binder传给远程使用。

为什么有的binder service用aidl生成相关bp/bn文件,有的却手动实现呢?大致浏览aidl文件可以发现,需要传递的类型已经使用aidl定义过了或者是基础类型;手动编写的文件例如IMediaPlayer.cpp需要传递比较复杂的类型,例如KeyedVector等,如果要用aidl编写会很麻烦。

相关推荐
深海呐4 小时前
Android 最新的AndroidStudio引入依赖失败如何解决?如:Failed to resolve:xxxx
android·failed to res·failed to·failed to resol·failed to reso
解压专家6664 小时前
安卓解压软件推荐:高效处理压缩文件的实用工具
android·智能手机·winrar·7-zip
Rverdoser4 小时前
Android 老项目适配 Compose 混合开发
android
️ 邪神6 小时前
【Android、IOS、Flutter、鸿蒙、ReactNative 】标题栏
android·flutter·ios·鸿蒙·reatnative
努力遇见美好的生活6 小时前
Mysql每日一题(行程与用户,困难※)
android·数据库·mysql
图王大胜8 小时前
Android Framework AMS(17)APP 异常Crash处理流程解读
android·app·异常处理·ams·crash·binderdied·讣告
ch_s_t9 小时前
电子商务网站之首页设计
android
豆 腐11 小时前
MySQL【四】
android·数据库·笔记·mysql
想取一个与众不同的名字好难13 小时前
android studio导入OpenCv并改造成.kts版本
android·ide·android studio
Jewel10514 小时前
Flutter代码混淆
android·flutter·ios