本文系作者自己学习之所用,文章内容仅出自作者拙劣之思考,问题之处烦请不吝指教。

  MediaPlayer 能被用来控制音/视频文件或流媒体的回放。Android中以MediaPlayer类作为音视频播放的基础类,围绕着他开展了一系列的处理。学习一个新的模块,最简单的步骤就是找到一个典型的应用程序,通过它的实现,来分析整个模块的数据流和控制流。典型的MediaPlayer在Java处的接口包括视频播放类VideoView以及音频专用MediaPlayer类。

  一、 一个简单的视频播放demo app

  Android中实现视频的播放可以采用MediaPlayer+SurfaceView配合的方式,其实Android还为开发人员提供了另外一种更简单的播放视频媒体的方式,那就是VideoView。VideoView类,其实质是用MediaPlayer类来实现的,只是由于其是视频播放,不得不和Surfaceview挂上够,才将其独立出来。使得其有如下的结构:

 public class VideoView extends SurfaceView
implements MediaPlayerControl, SubtitleController.Anchor {
private static final String TAG = "VideoView";
......
}

  在Android中,提供了VideoView组件用于播放视频文件。想要使用VideoView组件播放视频,首先需要在布局文件中创建该组件,然后在Activity中获取该组件,并应用其setVideoPath()方法或setVideoURI()方法加载要播放的视频,最后调用start()方法来播放视频。另外,VideoView组件还提供了stop()和pause()方法,用于停止或暂停视频的播放。

  在APP中,VideoView的典型简单使用如下:

     mMediaController =new MediaController(this);
mVideoView = (VideoView) findViewById(R.id.videoView);
mVideoView.setVideoPath("/sdcard/1080P24FPS.mp4"); // 设置档案路径
mVideoView.setMediaController(mMediaController); // 设置播放器的控制器
mVideoView.start(); // 开始播放

  先看看效果就是下面这个样子,短短几行代码一个播放器就做好了,还可以进行暂停,快进,快退,进度条控制。

  PS:VideoView还提供许多其他播放控制API,在此不做重点介绍,以上代码也仅仅是个人demo,难免有误,谨慎参考使用。

二、 VideoView中setVideoPath的处理

  任何华丽的语言都不如source code来的简单直接,上代码:

     /**
* Sets video path.
*
* @param path the path of the video.
*/
public void setVideoPath(String path) {
setVideoURI(Uri.parse(path));
} /**
* Sets video URI.
*
* @param uri the URI of the video.
*/
public void setVideoURI(Uri uri) {
setVideoURI(uri, null);
} /**
* Sets video URI using specific headers.
*
* @param uri the URI of the video.
* @param headers the headers for the URI request.
* Note that the cross domain redirection is allowed by default, but that can be
* changed with key/value pairs through the headers parameter with
* "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value
* to disallow or allow cross domain redirection.
*/
public void setVideoURI(Uri uri, Map<String, String> headers) {
mUri = uri;
mHeaders = headers;
mSeekWhenPrepared = 0;
openVideo(); // openVideo的处理,让最终的处理权交给了MediaPlayer
requestLayout();
invalidate();
}

  经过setVideoPath(String path) --> setVideoURI(Uri uri) --> setVideoURI(Uri uri, Map<String, String> headers) 的调用流程,程序最终来到了openVideo()这一函数中:

     private void openVideo() {
......
mMediaPlayer = new MediaPlayer(); mMediaPlayer.setDataSource(mContext, mUri, mHeaders);
mMediaPlayer.setDisplay(mSurfaceHolder); mMediaPlayer.prepareAsync();
.......
}

  在上面的代码中可以清楚的看到,我们首先new 了一个MediaPlayer类的对象,然后去setDataSource,到这里VideoView::openVideo的处理让最终的处理权交给了MediaPlayer。接下来我们就进入MediaPlayer的世界.

三、 MediaPlayer的世界

  3.1 new MediaPlayer对象过程

  首先关注MediaPlayer对象的创建过程,这也是分析android源码的一个基本要求。依次通过Java --> JNI(libmedia_jni.so) -- > Frameworks(libmedia.so)的处理流程。

  MediaPlayer.java 构造函数,这一部分在 Android MediaPlayer架构 -- 前言小知识点(一)也有分析

    public MediaPlayer() {
super(new AudioAttributes.Builder().build()); Looper looper;
if ((looper = Looper.myLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else if ((looper = Looper.getMainLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else {
mEventHandler = null;
} mTimeProvider = new TimeProvider(this);
mOpenSubtitleSources = new Vector<InputStream>(); /* Native setup requires a weak reference to our object.
* It's easier to create it here than in C++.
*/
native_setup(new WeakReference<MediaPlayer>(this));
}

  可以看到,在使用VideoView中到创建MediaPlayer会经过:new VideoView——> new MediaPlayer ——>native_setup 这样一个典型的对象建立过程,并传递到JNI。

  native_setup主要用于本地C++层的对象的建立,在JNI代码(frameworks\base\media\jni\android_media_MediaPlayer.cpp)中可以找到对应的native函数:

 static void
android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
ALOGV("native_setup");
sp<MediaPlayer> mp = new MediaPlayer(); // 实例化一个native MediaPlayer(frameworks\av\media\libmedia\mediaplayer.cpp)
if (mp == NULL) {
jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
return;
} // create new listener and give it to MediaPlayer
sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener(env, thiz, weak_this);
mp->setListener(listener); // Stow our new C++ MediaPlayer in an opaque field in the Java object.
setMediaPlayer(env, thiz, mp);
}

  进入JNI做android_media_MediaPlayer_native_setup处理:sp<MediaPlayer> mp = new MediaPlayer() 这个native MediaPlayer会去和media service进行交互实现真正的播放功能,使得最终处理进入C++的世界。   

  3.2 setDataSource过程

  MediaPlayer java class中提供了多种setDataSource方法来设置不同的URI播放流,在此我们以播放本地档案为例来介绍处理流程:

  VideoView::setVideoURI() --> MediaPlayer::setDataSource(mContext, mUri, mHeaders); --> MediaPlayer::setDataSource(uri.toString()) --> MediaPlayer::setDataSource(path, null, null) --> MediaPlayer::setDataSource(fd) --> setDataSource(fd, 0, 0x7ffffffffffffffL) --> _setDataSource(fd, offset, length)

  最后会调到 _setDataSource(fd, offset, length),看这个方法被声明为 native method

     private native void _setDataSource(MediaDataSource dataSource)
throws IllegalArgumentException, IllegalStateException;

  在JNI层我们找到该方法对应的JNI method实现:

     {"_setDataSource",      "(Ljava/io/FileDescriptor;JJ)V",    (void *)android_media_MediaPlayer_setDataSourceFD},

    android_media_MediaPlayer_setDataSourceFD()方法定义如下:

static void
android_media_MediaPlayer_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
{
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
if (mp == NULL ) {
jniThrowException(env, "java/lang/IllegalStateException", NULL);
return;
} if (fileDescriptor == NULL) {
jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
return;
}
int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
ALOGV("setDataSourceFD: fd %d", fd);
process_media_player_call( env, thiz, mp->setDataSource(fd, offset, length), "java/io/IOException", "setDataSourceFD failed." );
}

  上面这段代码可以看到最终调用了status_t MediaPlayer::setDataSource(int fd, int64_t offset, int64_t length)

//*********************************************************************************************************************************************************************

  MediaPlayer的C++代码位于/frameworks/av/media/libmedia/mediaplayer.cpp, 编译后形成一个libmedia.so。

  下面来看这个API的处理,接下去都只分析framework层的C++的处理流

 status_t MediaPlayer::setDataSource(int fd, int64_t offset, int64_t length)
{
ALOGV("setDataSource(%d, %" PRId64 ", %" PRId64 ")", fd, offset, length);
status_t err = UNKNOWN_ERROR;
const sp<IMediaPlayerService>& service(getMediaPlayerService());
if (service != ) {
sp<IMediaPlayer> player(service->create(this, mAudioSessionId));
if ((NO_ERROR != doSetRetransmitEndpoint(player)) ||
(NO_ERROR != player->setDataSource(fd, offset, length))) {
player.clear();
}
err = attachNewPlayer(player);
}
return err;
}

  典型的Binder C/S架构,获取MediaPlayerService的proxy,通过MediaPlayerService来创建一个player,然后对这个player调用setDataSource。

  3.3 MediaPlayerService的工作

  启动与获取

  MediaPlayerService同其他的Binder Service一样,作为一个server对外提供服务,它是在mediaserver中启动的:

  /frameworks/av/media/mediaserver/main_mediaserver.cpp

 int main(int argc __unused, char **argv __unused)
{
signal(SIGPIPE, SIG_IGN); sp<ProcessState> proc(ProcessState::self());
sp<IServiceManager> sm(defaultServiceManager());
ALOGI("ServiceManager: %p", sm.get());
InitializeIcuOrDie();
MediaPlayerService::instantiate(); //启动MediaPlayerService
ResourceManagerService::instantiate();
registerExtensions();
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}

  在/frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp中对instantiate()方法的定义:

 void MediaPlayerService::instantiate() {
defaultServiceManager()->addService(
String16("media.player"), new MediaPlayerService());
}

  在上面这段代码中我们注册了一个名为“media.player"的Binder Service,也就是MediaPlayerService,之后就可以通过 binder = sm->getService(String16("media.player"));来请求这个服务了

  Player的创建

  获取MediaPlayerService后就要去create player: sp<IMediaPlayer> player(service->create(this, mAudioSessionId));

  create请求处理:

  

Android MediaPlayer架构 -- MediaPlayer的创建过程的更多相关文章

  1. Android应用程序的进程创建过程

    目录 前言 步骤 step1 Ams发起请求startProcessLocked step2 Zygote收到请求 step3 handleChildProc -- 进入子进程的世界 step4 Ru ...

  2. Android MediaPlayer架构 -- 前言小知识点(一)

    在Android中可以使用MediaPlayer+SurfaceView来实现一个简单的多媒体播放器. 一  构造函数 java MediaPlayer class 的源码位置:frameworks\ ...

  3. Android -- 多媒体播放之MediaPlayer使用内部实现简析

    Android -- MediaPlayer内部实现简析 在之前的博客中,已经介绍了使用MediaPlayer时要注意的内容.如今,这里就通过一个MediaPlayer代码实例,来进一步分析Media ...

  4. Android四大组件--MediaPlayer详解(转)

    一. MediaPlayer 状态机 介绍 Android MediaPlayer 状态即图例 : 1. Idle (闲置) 状态 和 End (结束) 状态 MediaPlayer 对象声明周期 : ...

  5. Android开发之Mediaplayer

    Android提供了常见的音频.视频的编码.解码机制.借助于多媒体类MediaPlayer的支持,开发者能够非常方便在在应用中播放音频.视频.本篇博客主要解说在Android平台下怎样播放一个音频文件 ...

  6. 【Android笔记】MediaPlayer基本使用方式

    Android MediaPlayer基本使用方式 使用MediaPlayer播放音频或者视频的最简单样例: JAVA代码部分: public class MediaPlayerStudy exten ...

  7. 【Android笔记】MediaPlayer基本用法

    Android MediaPlayer基本使用方式 使用MediaPlayer播放音频或者视频的最简单样例: JAVA代码部分: public class MediaPlayerStudy exten ...

  8. 【转】Android播放音频MediaPlayer的几种方式介绍

    接下来笔者介绍一下Android中播放音频的几种方式,android.media包下面包含了Android开发中媒体类,当然笔者不会依次去介绍,下面介绍几个音频播放中常用的类: 1.使用MediaPl ...

  9. Android Context创建过程

        特定的资源或者类构成了Android应用程序的运行上下文环境 PackageManager, ClassLoader, Assert等等 Android应用程序窗口的运行上下文环境是通过Con ...

随机推荐

  1. oracle 用户 权限

    一. 概述 与权限,角色相关的视图大概有下面这些: DBA_SYS_PRIVS: 查询某个用户所拥有的系统权限 USER_SYS_PRIVS:   当前用户所拥有的系统权限 SESSION_PRIVS ...

  2. javascript中的return、return true、return false、continue区别

    1.语法为:return 表达式; 2.w3c中的解释: 语句结束函数执行,返回调用函数,而且把表达式的值作为函数的结果  也就是:当代码执行到return语句时,函数返回一个结果就结束运行了,ret ...

  3. 50个常用的sql语句

    50个常用的sql语句 Student(S#,Sname,Sage,Ssex) 学生表 Course(C#,Cname,T#) 课程表 SC(S#,C#,score) 成绩表 Teacher(T#,T ...

  4. Java用四种方法实现阶乘n! (factorial)

    1. 引言 实现阶乘的方法很多,这边介绍四种方法,分别是递归,尾递归,循环和BigDecimal. 2. 代码 public class Test { public static void main( ...

  5. js发送get 、post请求的方法简介

    POST请求: 发送的参数格式不同,请求头设置不同,具体参照 Http请求中请求头Content-Type讲解 发送的参数格式不同,后台获取方式也不相同 php请看 php获取POST数据的三种方法 ...

  6. LeetCode(4):两个排序数组的中位数

    Hard! 题目描述: 有两个大小为 m 和 n 的排序数组 nums1 和 nums2 . 请找出两个排序数组的中位数并且总的运行时间复杂度为 O(log (m+n)) . 示例 1: nums1 ...

  7. splay好板子

    找到一份比较好的板子,链接https://blog.csdn.net/crazy_ac/article/details/8034190 #include<cstdio> #include& ...

  8. 性能测试十一:jmeter进阶之java请求

    使用Java编写JDBC脚本对Mysql进行增删改查等操作的性能测试 使用Jmeter提供的脚本框架依赖的jar包(分别在jmeter目录下的lib和ext目录下) ApacheJMeter_core ...

  9. python 全栈开发,Day75(Django与Ajax,文件上传,ajax发送json数据,基于Ajax的文件上传,SweetAlert插件)

    昨日内容回顾 基于对象的跨表查询 正向查询:关联属性在A表中,所以A对象找关联B表数据,正向查询 反向查询:关联属性在A表中,所以B对象找A对象,反向查询 一对多: 按字段:xx book ----- ...

  10. java根据word模板导出word文件

    1.word模板文件处理,如下图所示在word 文档中填值的地方写入占位变量 2.将word文档另存为xml文件.编辑如下图,找到填写的占位,修改为${bcrxm}格式 3.将文件后缀名改为.ftl文 ...