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编写会很麻烦。

相关推荐
selt7911 小时前
Redisson之RedissonLock源码完全解析
android·java·javascript
Yao_YongChao2 小时前
Android MVI处理副作用(Side Effect)
android·mvi·mvi副作用
非凡ghost3 小时前
JRiver Media Center(媒体管理软件)
android·学习·智能手机·媒体·软件需求
席卷全城3 小时前
Android 推箱子实现(引流文章)
android
齊家治國平天下3 小时前
Android 14 系统中 Tombstone 深度分析与解决指南
android·crash·系统服务·tombstone·android 14
maycho1235 小时前
MATLAB环境下基于双向长短时记忆网络的时间序列预测探索
android
思成不止于此6 小时前
【MySQL 零基础入门】MySQL 函数精讲(二):日期函数与流程控制函数篇
android·数据库·笔记·sql·学习·mysql
brave_zhao6 小时前
达梦数据库(DM8)支持全文索引功能,但并不直接兼容 MySQL 的 FULLTEXT 索引语法
android·adb
sheji34166 小时前
【开题答辩全过程】以 基于Android的网上订餐系统为例,包含答辩的问题和答案
android
easyboot7 小时前
C#使用SqlSugar操作mysql数据库
android·sqlsugar