2024年Android最全Android音视频开发入门(2)MediaPlayer 生命周期及create()分析,小红书前端面试题

分享读者

作者2013年java转到Android开发,在小厂待过,也去过华为,OPPO等大厂待过,18年四月份进了阿里一直到现在。

被人面试过,也面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长,而且极易碰到天花板技术停滞不前!

我们整理了一份阿里P7级别的Android架构师全套学习资料,特别适合有3-5年以上经验的小伙伴深入学习提升。

主要包括阿里,以及字节跳动,腾讯,华为,小米,等一线互联网公司主流架构技术。如果你有需要,尽管拿走好了。

35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

//如果myLooper不为空就赋值给myLooper,并实例化一个EventHandler对象

mEventHandler = new EventHandler(this, looper);

} else if ((looper = Looper.getMainLooper()) != null) {

//如果主线程Looper不为空,就赋值给looper,并实例化一个EventHandler对象

mEventHandler = new EventHandler(this, looper);

} else {

mEventHandler = null;

}

//时间数据容器,一般provider都是和数据联系起来的,如ContentProvider、VideoProvider

mTimeProvider = new TimeProvider(this);

mOpenSubtitleSources = new Vector();

//通过Binder机制获取到原生的OPS服务

IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);

mAppOps = IAppOpsService.Stub.asInterface(b);

//通过之后获取到的服务,就能使用native_setup方法开始创建MediaPlayer了,而且还是软引用,

//接下来就是放在C++层去创建MediaPlayer了

native_setup(new WeakReference(this));

}

MediaPlayer构造函数总结

  1. 定义Looper

  2. 初始化一个TimeProvider

  3. 通过Binder获取原生ops服务

  4. 进入c++层创建一个弱引用的MediaPlayer

所以接下来我们就进入C++层,去分析Native层如何创建MediaPlayer的了。

在分析native_setup之前,请注意 .so文件一般都是在静态代码块中加载的。在MediaPlayer中有一段静态代码块,用于加载和链接库文件media_jni.so

static {

System.loadLibrary(“media_jni”);

native_init();

}

所以我们要去查看 "media_jni"这个c代码,Android Studio是看不到JNI文件的

我们要去Android源码大全这上面去找,代码是通过 loadLibrary(xxx_jni)加载的,那么我的搜索关键字就是 libxxx_jni

然后我们就找到目录/frameworks/base/media/jni下的android_media_MediaPlayer.cpp文件,从它来分析,因为它的第一个函数 android_media_MediaPlayer_init就是从Java静态代码快调过来的nativie_init

//android_media_MediaPlayer.cpp

static void

android_media_MediaPlayer_native_init(JNIEnv *env) {

//类的句柄

jclass clazz;

//这里通过Native层调用Java层,获取MediaPlayer对象

clazz = env -> FindClass(“android/media/MediaPlayer”);

if (clazz == NULL) {

//判空

return;

}

//获取成员变量mNativeContext,它是一个long型,实际对应的是一个内存地址

fields.context = env -> GetFieldID(clazz, “mNativeContext”, “J”);

if (fields.context == NULL) {

return;

}

fields.post_event = env -> GetStaticMethodID(clazz, “postEventFromNative”,

“(Ljava/lang/Object;IIILjava/lang/Object;)V”);

if (fields.post_event == NULL) {

return;

}

}

init方法总结

  1. 被通知init后,从Java层获取一个MediaPlayer

  2. 从MediaPlayer获取一些变量

  3. 从MediaPlayer获取 postEventFromNative()并执行

这个方法顾名思义:Native通知Java层,我初始化好了,你可以开始做一些事情了。

这个时候我们就要去Java层中看 postEventFromNative()这个方法做什么了。

private static void postEventFromNative(Object mediaplayer_ref,

int what, int arg1, int arg2, Object obj)

{

//得到弱引用的MediaPlayer

final MediaPlayer mp = (MediaPlayer)((WeakReference)mediaplayer_ref).get();

if (mp == null) {

return;

}

//如果handler不为空,则发送一条message

if (mp.mEventHandler != null) {

Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);

mp.mEventHandler.sendMessage(m);

}

}

postEventFromNative总结

总的来说,就是拿到Native层创建好的MediaPlayer,并且向Handler发送一条message,至于后面做了啥之后再讲。

我们之前在create()的时候,创建MediaPlayer之后,会走native_setup方法,我们来看看这个方法做了什么:

//android_media_MediaPlayer.cpp

static void

android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)

{

ALOGV(“native_setup”);

sp mp = new MediaPlayer();

// 给MediaPlayer创建一个Listener,便于我们在Java层设置的 setPrepareListener、setOnCompleteListener能产生回调

sp listener = new JNIMediaPlayerListener(env, thiz, weak_this);

mp->setListener(listener);

// 对于Java层来说,C++中的MediaPlayer是不透明的,也无需关心其对应的逻辑,各司其职就行了

setMediaPlayer(env, thiz, mp);

}

native_setup总结

  1. 设置监听器

  2. C++层自己处理这个MediaPlayer

2.2 setDataSource过程


上面是一个MediaPlayer的构造过程,在构造完,着实的获取到MediaPlayer这个对象之后,我们就会去 setDataSource(),我们来看看它的源码(注:这一段代码比较无聊,可以大致浏览一下并直接看总结):

(注:下面一段是传入的uri为文件资源形式的代码,就比如我们传了一个本地的媒体文件

private void setDataSource(String path, Map<String, String> headers, List cookies)

throws IOException, IllegalArgumentException, SecurityException, IllegalStateException

{

String[] keys = null;

String[] values = null;

if (headers != null) {

keys = new String[headers.size()];

values = new String[headers.size()];

int i = 0;

//把HTPP/RTSP中包含的key、value分别装到两个数组中

for (Map.Entry<String, String> entry: headers.entrySet()) {

keys[i] = entry.getKey();

values[i] = entry.getValue();

++i;

}

}

setDataSource(path, keys, values, cookies);

}

private void setDataSource(String path, String[] keys, String[] values,

List cookies)

throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {

//解析path

final Uri uri = Uri.parse(path);

final String scheme = uri.getScheme();

if (“file”.equals(scheme)) {

path = uri.getPath();

} else if (scheme != null) {

// 处理非资源文件

nativeSetDataSource(

MediaHTTPService.createHttpServiceBinderIfNecessary(path, cookies),

path,

keys,

values);

return;

}

//处理文件类型

final File file = new File(path);

if (file.exists()) {

FileInputStream is = new FileInputStream(file);

//得到文件标识符

FileDescriptor fd = is.getFD();

//传入文件标志符

setDataSource(fd);

is.close();

} else {

throw new IOException(“setDataSource failed.”);

}

}

public void setDataSource(FileDescriptor fd)

throws IOException, IllegalArgumentException, IllegalStateException {

setDataSource(fd, 0, 0x7ffffffffffffffL);

}

public void setDataSource(FileDescriptor fd, long offset, long length)

throws IOException, IllegalArgumentException, IllegalStateException {

//进入native层

_setDataSource(fd, offset, length);

}

setDataSource()总结

解析uri,如果得到的路径是文件,则把该文件资源的资源标识符丢给native层处理。

native层并没有setDataSource函数,但是有一个函数名映射函数说明,这是JNI中常用的动态注册方法:

在这里插入图片描述

所以我们找到 setDataSourceFD():

static void

android_media_MediaPlayer_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)

{

//获得MediaPlayer

sp mp = getMediaPlayer(env, thiz);

//在JNI中获取 java.io.FileDescription 这里开始调用JNIEnv*中的 GetIntField函数获取对应的变量

int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);

ALOGV(“setDataSourceFD: fd %d”, fd);

//进行 setDataSource

process_media_player_call( env, thiz, mp->setDataSource(fd, offset, length), “java/io/IOException”, “setDataSourceFD failed.” );

}

static void process_media_player_call(JNIEnv env, jobject thiz, status_t opStatus, const char exception, const char *message)

{

if (exception == NULL) {

//如果没有异常

if (opStatus != (status_t) OK) {

//如果在 setDataSource过程中opStatus不Ok

sp mp = getMediaPlayer(env, thiz);

//则通知MEDIA_ERROR

if (mp != 0) mp->notify(MEDIA_ERROR, opStatus, 0);

}

} else { // Throw exception!

}

}

setDataSourceFD()总结

拿到标识符(uri的内容)后,用mp->setDataSource(fd, offset, length)得到的结果,进行异常处理,如果有异常就抛出。

在setDataSource的时候我们是以本地文件的形式来走的,如果我们当时走的是网络请求,即我们uri内容是 HTTP/RTSP,那么就会nativeSetDataSource()方法,在jni层,对应的是setDataSourceAndHeader()

static void

android_media_MediaPlayer_setDataSourceAndHeaders(

JNIEnv *env, jobject thiz, jobject httpServiceBinderObj, jstring path,

jobjectArray keys, jobjectArray values) {

sp mp = getMediaPlayer(env, thiz);

…//解析网络路径、判空、判内存溢出

//下面是通过Binder机制,将httpServiceBinderObj传给IPC返回给binder,然后强制转换成IMediaHTTPService

sp httpService;

if (httpServiceBinderObj != NULL) {

sp binder = ibinderForJavaObject(env, httpServiceBinderObj);

httpService = interface_cast(binder);

}

//开始判断状态,和上面的文件操作是一样的

status_t opStatus =

mp->setDataSource(

httpService,

pathStr,

headersVector.size() > 0? &headersVector : NULL);

process_media_player_call(

env, thiz, opStatus, “java/io/IOException”,

“setDataSource failed.” );

}

至此,setDataSource()的过程就完成了。

这里最后总结一下setDataSource做了什么:

拿到uri,判断uri的内容是否为文件资源 (用大白话讲:就是你用的是本地文件,还是一个指定的网络url)

  • 如果是文件资源,就去检查它的 文件描述符(判断类型啥的),没问题的话,就mp-> setDataSource()

  • 如果是HTTP/RTSP(网络请求),则通过Binder,去一次做IPC(这里暂且推断为:网络请求),然后就mp-> setDataSource()

这样通过JNI, JAVA和C++相互调用,有这么几个好处:

  • 安全

封装在native层的代码是.so形式的,破坏风险小。

  • 效率高

在运行速度上C++更高效,所以对于复杂的渲染,放在native层做最好

  • 连通性强

因为正向调用就传值,反向调用就把处理后的值通知回去,其实就是一条路

2.3 setDisplay过程


接下来看看在create()阶段里,setDataSource后 的setDisplay(holder)做了什么

首先我们都知道 Holder是容器、管家的意思,RecyclerView有ViewHolder来控制每个item,那SurfaceHolder同样的,也是控制着每一个Surface。

public void setDisplay(SurfaceHolder sh) {

//给Surface设置一个控制器

mSurfaceHolder = sh;

Surface surface;

if (sh != null) {

surface = sh.getSurface();

} else {

surface = null;

}

//到native层,给视频设置Surface

_setVideoSurface(surface);

//更新Surface到屏幕上

updateSurfaceScreenOn();

}

在定义完SurfaceHolder后,就要去native层了,我们在同样的cpp文件中找到了对应的函数:

static void

setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface, jboolean mediaPlayerMustBeAlive) {

decVideoSurfaceRef(env, thiz);

sp new_st;

if (jsurface) {

//得到Java层的Surface

sp surface (android_view_Surface_getSurface(env, jsurface));

if (surface != NULL) {

//如果Surface不为空,则获取 IGraphicBufferProducer

new_st = surface -> getIGraphicBufferProducer();

//调用 incStrong

new_st -> incStrong(( void*)decVideoSurfaceRef);

} else {

}

}

env -> SetLongField(thiz, fields.surface_texture, (jlong) new_st.get());

//如果MediaPlayer还没有被初始化,setDataSource将会失败,但setDataSource之前就setDisplay了

//在prepare/prepareAsync中调用setVideoSurfaceTexture 可以覆盖该cas

mp -> setVideoSurfaceTexture(new_st);

}

static void

decVideoSurfaceRef(JNIEnv *env, jobject thiz)

{

sp old_st = getVideoSurfaceTexture(env, thiz);

if (old_st != NULL) {

old_st->decStrong((void*)decVideoSurfaceRef);

}

}

这里有几个很陌生的类,我们先来理清一下较为基础的:

  • SurfaceTexture:

它是API 11之后加入的类。这个类可以从 视频解码里面获取图像流(image stream)。但是,和SurfaceView不同的是,SurfaceTexture在接收图像流之后,不需要显示出来。SurfaceTexture不要显示到屏幕上。

因此我们可以用SurfaceTexture接收解码出来的图像流,然后从SurfaceTexture中取得图像帧的副本进行处理,处理完后就交给一个SurfaceView来显示。

  • Surface:

处理被屏幕排序的原生的Buffer,Android中的Surface就是就一个画图的平台。对于View及其子类,都是画在Surface上的。

各Surface对象通过SurfaceFlinger合成到frameBuffer。每个Surface都是双缓冲的(双线程),它的介绍在Android自定义控件开发入门与实战(15)SurfaceView,我已经分析的很清楚了

  • SurfaceView

SurfaceView是一个View,内嵌一个专门用来绘制的Surface。

可以这么说他们的关系:Surface是计算器,SurfaceView是画布

  • SurfaceHolder

上面已经简单比喻出它的用法。它可以监听Surface的操作。

  • IGraphicBufferProducer

它是图形缓冲的管理者,它是App和BufferQueue的重要桥梁,承担着整个应用进程中的UI显示需求。

Surface、SurfaceHolder、SurfaceView就像MVC有木有。

这时候再来总结一下native层的setVideoSurface做了什么

  1. 先拿到旧的 SurfaceTexture,用旧的获取到 Producer,然后调用它的decStrong()

  2. nartive层拿到了Surface

  3. 从Surface获取缓冲数据的管理者

  4. 用管理者调用 incStrong()

现在不知道decStrong和incStrong具体做了什么,我们可以先理解为处理Surface中的数据吧。

拿现在来概括一下 setDisplay做了什么

  1. SurfaceHolder 交给native层一个Surface,让它处理数据

  2. natve层处理好后,SurfaceHolder来通知更新SurfaceView

2.4 开始prepare后的流程


我们前面分析了MediaPlayer从创建到 setDataSource的过程,尽管分析了代码,但是没有从MediaPlayer生态上认识各类库之间的调用关系。下图是从别的blog上找的:MediaPlayer各个具体类之间的依赖关系图

在这里插入图片描述

从上图可以看出,MediaPlayer是C/S架构。他们之间用Binder机制实现IPC通信。

从框架结构上看,IMediaPlayerService.h、IMediaPlayerClient.h和mediaplayer.h这3个头文件中定义了MediaPlayer的接口和架构。

在给播放器设置数据源且展现了Surface后,你应当开始调用prepare或prepareAsync函数。

对于文件类型,调用prepare() 将暂时阻塞,因为它是一个同步函数,直到MediaPlayer已经准备好数据即将播放,也就是播放回调了 onPrepared(),进入Prepared函数。prepare()函数如下:

public void prepare() throws IOException, IllegalStateException {

_prepare();

scanInternalSubtitleTracks();

}

//native的 prepare如下:

static void

android_media_MediaPlayer_prepare(JNIEnv *env, jobject thiz)

{

//1

sp st = getVideoSurfaceTexture(env, thiz);

//2

mp->setVideoSurfaceTexture(st);

//3

process_media_player_call( env, thiz, mp->prepare(), “java/io/IOException”, “Prepare failed.” );

}

static sp

getVideoSurfaceTexture(JNIEnv* env, jobject thiz) {

IGraphicBufferProducer * const p = (IGraphicBufferProducer*)env->GetLongField(thiz, fields.surface_texture);

return sp§;

}

我们上一节就一直看过1、2、3的代码了。

1是从getVideoSurfaceTexture()方法获取一个 IGraphicBufferProducer 类型指针

2是 把1中得到的指针传给 MediaPlayer。BpGraphicBufferProducer是 GraphicBufferProducer在客户端这边的代理对象,负责和SurfaceFlinger交互。GraphicBufferProducer通过gbp(IGraphicBufferProducer 对象)向BufferQueue获取Buffer,然后填充UI信息,填充完毕会通知SurfaceFlinger

3是走 mp->prepare()并通知结果的函数。

我们知道还有一个 prepareAsync(),前面的思路都是从create走过来的,如果一个网络URL被发过来,这个时候就要用到prepareAsync()了:

public void startPlayUrl(String url) {

Uri uri = Uri.parse(url);

MediaPlayer mp = new MediaPlayer();

try {

mp.setDataSource(this, uri);

} catch (IOException e) {

e.printStackTrace();

}

mp.setOnPreparedListener(prepareListener);

mp.setOnVideoSizeChangedListener(videoSizeChangedListener);

mp.setOnErrorListener(errorListener);

mp.prepareAsync();

}

public native void prepareAsync() throws IllegalStateException;

static void

android_media_MediaPlayer_prepareAsync(JNIEnv *env, jobject thiz)

{

sp…in to make it stick.

sp st = getVideoSurfaceTexture(env, thiz);

mp->setVideoSurfaceTexture(st);

process_media_player_call( env, thiz, mp->prepareAsync(), “java/io/IOException”, “Prepare Async failed.” );

}

发现他的代码除了最后一行,和prepare()无异

接下来我们来看的mp-> prepareAsync()中 MediaPlayer的 prepareAsync():

status_t MediaPlayer::prepareAsync()

{

ALOGV(“prepareAsync”);

//互斥锁头

Mutex::Autolock _l(mLock);

return prepareAsync_l();

}

status_t MediaPlayer::prepareAsync_l()

{

if ( (mPlayer != 0) && ( mCurrentState & (MEDIA_PLAYER_INITIALIZED | MEDIA_PLAYER_STOPPED) ) ) {

//设置音频流类型,在IMediaPlayer.cpp中对应的transact操作是SET_AUDIO_STREAM_TYPE

if (mAudioAttributesParcel != NULL) {

mPlayer->setParameter(KEY_PARAMETER_AUDIO_ATTRIBUTES, *mAudioAttributesParcel);

} else {

mPlayer->setAudioStreamType(mStreamType);

}

//将当前状态置为 MEDIA_PLAYER_PREPARING

mCurrentState = MEDIA_PLAYER_PREPARING;

return mPlayer->prepareAsync();

}

ALOGE(“prepareAsync called in state %d, mPlayer(%p)”, mCurrentState, mPlayer.get());

return INVALID_OPERATION;

}

下面进行分析prepareAsync(),mp->prepareAsync()对应的BnMediaPlayer操作如下:

PREPARE_ASYNC: {

CHECK_INTERFACE(IMediaPlayer, data, reply);

reply->writeInt32(prepareAsync());

return NO_ERROR;

}

接着分析 MediaPlayerService::Client::prepareAsync()

status_t MediaPlayerService::Client::prepareAsync()

{

status_t ret = p->prepareAsync();

}

//serivce中调用了 AwesomePlayer的prepareAsync()

//注意 AwesomePlayer在Andorid6.0之后已经弃用

status_t AwesomePlayer::prepareAsync() {

ATRACE_CALL();

Mutex::Autolock autoLock(mLock);

if (mFlags & PREPARING) {

return UNKNOWN_ERROR; // async prepare already pending

}

mIsAsyncPrepare = true;

//调用了 prepareAsync_l()

return prepareAsync_l();

}

status_t AwesomePlayer::prepareAsync_l() {

if (mFlags & PREPARING) {

//如果是Preparing状态,就返回

return UNKNOWN_ERROR;

}

if (!mQueueStarted) {

//队列不是开始状态时,设置成开始状态

mQueue.start();

mQueueStarted = true;

}

//修改状态为Preparing

modifyFlags(PREPARING, SET);

//这里AwesomeEvent接收到时间,进行回调

mAsyncPrepareEvent = new AwesomeEvent(

this, &AwesomePlayer::onPrepareAsyncEvent);

最后

给大家送上我成功跳槽复习中所整理的资料,由于文章篇幅有限,所以只是把题目列出来了

image

image

image

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

ayer在Andorid6.0之后已经弃用

status_t AwesomePlayer::prepareAsync() {

ATRACE_CALL();

Mutex::Autolock autoLock(mLock);

if (mFlags & PREPARING) {

return UNKNOWN_ERROR; // async prepare already pending

}

mIsAsyncPrepare = true;

//调用了 prepareAsync_l()

return prepareAsync_l();

}

status_t AwesomePlayer::prepareAsync_l() {

if (mFlags & PREPARING) {

//如果是Preparing状态,就返回

return UNKNOWN_ERROR;

}

if (!mQueueStarted) {

//队列不是开始状态时,设置成开始状态

mQueue.start();

mQueueStarted = true;

}

//修改状态为Preparing

modifyFlags(PREPARING, SET);

//这里AwesomeEvent接收到时间,进行回调

mAsyncPrepareEvent = new AwesomeEvent(

this, &AwesomePlayer::onPrepareAsyncEvent);

最后

给大家送上我成功跳槽复习中所整理的资料,由于文章篇幅有限,所以只是把题目列出来了

[外链图片转存中…(img-t0jTQLWj-1714831640755)]

[外链图片转存中…(img-useuGHtZ-1714831640755)]

[外链图片转存中…(img-3D8MXhR6-1714831640755)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 16
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值