最近在做一个视频监控项目的android客户端,要求用rtsp协议完成视频流的传输,但苦于找到不合适的库。之前考虑过用live555或ffmpeg,但涉及到jni调用,加之不熟悉函数调用顺序,开发难度和周期较长,遂作罢。于是乎,混迹于各大论坛寻找解决方案,经过一番苦苦寻觅,终于找到了一个比较满意的多媒体框架——vitamio。
vitamio作为一个国人开发的android多媒体开发框架,以支持格式多、操作简单、容易上手著称。vitamio也支持多种流媒体协议,如HTTP Streaming、rtsp、mms等等。国内也有很多播放器是基于该框架构建而成,用户基数较大。
在开始分析源码之前,我们先来分析一下mediaplayer的生命周期。
状态1:Idel(空闲)状态 当 mediaplayer创建或者执行reset()方法后处于这个状态。
状态2:Initialized(已初始化)状态 当 调用mediaplayer的setDataResource()方法给mediaplayer设置播放的数据源后,mediaplayer会处于该状态。
状态3:Prepared(准备就续)状态 设置完数据源后,调用mediaplayer的prepare()方法,让mediaplayer准备播放。值得一提的是,这里除了prepare()方法,还有prepareAsnyc()方法,此方法是异步方法,一般用于网络视频的缓冲。当缓冲完毕后,就会触发准备完毕的事件。我们要做的就是监听该事件(OnPreparedListener),当缓冲完成时,执行相应的操作。在此状态上,我们可以调用seekTo()方法定位视频,此方法不改变mediaplayer的状态;亦可调用stop()放弃视频播放,使mediaplayer处于Stopped状态。一般我们会在此状态上调用start()方法开始播放视频。
状态4:Started(开始)状态 当处于Prepared状态、Paused状态和PlayebackCompeleted状态时,调用Started()方法即可进入该状态。在该状态中,mediaplayer开始播放视频,可以通过seekTo()方法和start()方法改变视频播放的进度,当Looping为真且播放完毕后,它会重新开始播放(即循环播放);否则播放完毕后,会触发事件并调用OnCompletionaListener.OnCompletion()方法,进行特定操作,并进入PlaybackCompleted状态。在此状态中,亦可调用pause()方法或者stop()方法让视频暂停或停止,此时mediaplayer分别处于Stopped和Paused状态。
状态5:Stopped(停止)状态 当 mediaplayer处于Prepared 、Started、Paused、PlaybackCompleted状态时,调用stop()方法即可进入本状态。应特别注意的是,在本状态中,若想重新开始播放,不能直接调用start()方法,必须调用prepare()方法或prepareAsync()方法重新让mediaplayer处于Prepared状态方可调用start()方法播放视频。
状态6:Paused(暂停)状态 当 mediaplayer处于Started状态是,调用pause()方法即可进入本状态。在本状态里,可直接调用start()方法使,mediaplayer回到Started状态,亦可调用stop()方法停止视频播放,让播放器处于停止态。
状态7:PlaybackCompleted(播放完成)状态 当 mediaplayer播放完成且Looping为假时即可进入本状态。在本状态可调用start()方法使mediaplayer回到Started状态(注意此时是从头开始播放);亦可调用stop()方法使mediaplayer处于停止态,结束播放。
状态8:Error(错误)状态 当 mediaplayer出现错误时处于此状态。
调用release()方法即可释放此mediaplayer对象。
至此,我们已经了解了mediaplayer的声明周期,下面开始分析代码:
1、布局文件
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <io.vov.vitamio.widget.CenterLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <SurfaceView android:id="@+id/surface" android:layout_width="320dp" android:layout_height="480dp" android:layout_gravity="center" > </SurfaceView> </io.vov.vitamio.widget.CenterLayout> </LinearLayout>
2、代码
/* * Copyright (C) 2013 yixia.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.vov.vitamio.demo; import io.vov.vitamio.LibsChecker; import io.vov.vitamio.MediaPlayer; import io.vov.vitamio.MediaPlayer.OnBufferingUpdateListener; import io.vov.vitamio.MediaPlayer.OnCompletionListener; import io.vov.vitamio.MediaPlayer.OnPreparedListener; import io.vov.vitamio.MediaPlayer.OnVideoSizeChangedListener; import android.app.Activity; import android.graphics.PixelFormat; import android.media.AudioManager; import android.os.Bundle; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.widget.Toast;
/*
注意这个activity的写法。它实现了很多接口,这样的话,我们在绑定事件监听时,就不用编写很多个内部监听类了,直接用this即可。
*/ public class MediaPlayerDemo_Video extends Activity implements OnBufferingUpdateListener, OnCompletionListener, OnPreparedListener, OnVideoSizeChangedListener, SurfaceHolder.Callback { private static final String TAG = "MediaPlayerDemo"; private int mVideoWidth; private int mVideoHeight; private MediaPlayer mMediaPlayer; private SurfaceView mPreview; private SurfaceHolder holder; private String path="rtsp://184.72.239.149/vod/mp4:BigBuckBunny_175k.mov";//视频资源地址,是rtsp协议的流媒体 private Bundle extras; private static final String MEDIA = "media"; private static final int LOCAL_AUDIO = 1; private static final int STREAM_AUDIO = 2; private static final int RESOURCES_AUDIO = 3; private static final int LOCAL_VIDEO = 4; private static final int STREAM_VIDEO = 5; private boolean mIsVideoSizeKnown = false; private boolean mIsVideoReadyToBePlayed = false; /** * * Called when the activity is first created. */ @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); if (!LibsChecker.checkVitamioLibs(this))//初始化库。若少了会报错!! return; setContentView(R.layout.mediaplayer_2);//加载布局文件 mPreview = (SurfaceView) findViewById(R.id.surface);//查找组件
holder = mPreview.getHolder();//获取此surfaceView的holder对象。此holder对象即为mediaplayer显示的地方。 holder.addCallback(this);//设置回调。这里主要是surfaceChanged、surfaceDestroyed、surfaceCreated三个方法。 holder.setFormat(PixelFormat.RGBA_8888); extras = getIntent().getExtras();//获取数据 } private void playVideo(Integer Media) { doCleanUp(); try { switch (Media) { case LOCAL_VIDEO: /* * TODO: Set the path variable to a local media file path. */ path = "/storage/sdcard0/1.avi"; if (path == "") { // Tell the user to provide a media file URL. Toast.makeText(MediaPlayerDemo_Video.this, "Please edit MediaPlayerDemo_Video Activity, " + "and set the path variable to your media file path." + " Your media file must be stored on sdcard.", Toast.LENGTH_LONG).show(); return; } break; case STREAM_VIDEO: /* * TODO: Set path variable to progressive streamable mp4 or * 3gpp format URL. Http protocol should be used. * Mediaplayer can only play "progressive streamable * contents" which basically means: 1. the movie atom has to * precede all the media data atoms. 2. The clip has to be * reasonably interleaved. * */ //path = ""; if (path == "") { // Tell the user to provide a media file URL. Toast.makeText(MediaPlayerDemo_Video.this, "Please edit MediaPlayerDemo_Video Activity," + " and set the path variable to your media file URL.", Toast.LENGTH_LONG).show(); return; } break; } // Create a new media player and set the listeners mMediaPlayer = new MediaPlayer(this);//初始化mediaplayer。 mMediaPlayer.setDataSource("rtsp://184.72.239.149/vod/mp4:BigBuckBunny_175k.mov");//设置数据源 mMediaPlayer.setDisplay(holder);//设置显示 mMediaPlayer.prepare();//准备(这里用prepareAsync()应该会更好。。) mMediaPlayer.setOnBufferingUpdateListener(this);//设置缓冲监听 mMediaPlayer.setOnCompletionListener(this);//设置播放完毕监听 mMediaPlayer.setOnPreparedListener(this);//设置准备完毕监听 mMediaPlayer.setOnVideoSizeChangedListener(this);//设置显示大小改变监听 //mMediaPlayer.getMetadata();//在播放网络流媒体时。若加上此句,会产生I/O Error,不清楚为什么......另外个人认为此句没有意义。
setVolumeControlStream(AudioManager.STREAM_MUSIC); } catch (Exception e) { Log.e(TAG, "error: " + e.getMessage(), e); } } public void onBufferingUpdate(MediaPlayer arg0, int percent) {//缓冲监听的实现 Log.d(TAG, "onBufferingUpdate percent:" + percent); } public void onCompletion(MediaPlayer arg0) {//播放完毕监听的实现 Log.d(TAG, "onCompletion called"); } public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {//显示大小改变监听的实现 Log.v(TAG, "onVideoSizeChanged called"); if (width == 0 || height == 0) { Log.e(TAG, "invalid video width(" + width + ") or height(" + height + ")"); return; } mIsVideoSizeKnown = true; mVideoWidth = width; mVideoHeight = height; if (mIsVideoReadyToBePlayed && mIsVideoSizeKnown) { startVideoPlayback(); } } public void onPrepared(MediaPlayer mediaplayer) {//准备完毕监听的实现 Log.d(TAG, "onPrepared called"); mIsVideoReadyToBePlayed = true; if (mIsVideoReadyToBePlayed && mIsVideoSizeKnown) { startVideoPlayback(); } } public void surfaceChanged(SurfaceHolder surfaceholder, int i, int j, int k) { Log.d(TAG, "surfaceChanged called"); } public void surfaceDestroyed(SurfaceHolder surfaceholder) { Log.d(TAG, "surfaceDestroyed called"); } public void surfaceCreated(SurfaceHolder holder) { Log.d(TAG, "surfaceCreated called"); playVideo(extras.getInt(MEDIA)); } @Override protected void onPause() {//当此activity处于pause状态时,停止播放,销毁mediaplayer super.onPause(); releaseMediaPlayer(); doCleanUp(); } @Override protected void onDestroy() {//当此activity即将销毁时,销毁mediaplayer super.onDestroy(); releaseMediaPlayer(); doCleanUp(); } private void releaseMediaPlayer() { if (mMediaPlayer != null) { mMediaPlayer.release(); mMediaPlayer = null; } } private void doCleanUp() { mVideoWidth = 0; mVideoHeight = 0; mIsVideoReadyToBePlayed = false; mIsVideoSizeKnown = false; } private void startVideoPlayback() { Log.v(TAG, "startVideoPlayback"); holder.setFixedSize(mVideoWidth, mVideoHeight); mMediaPlayer.start(); } }现在初步实验成果,网络流媒体的播放也基本达到音画同步的要求,下一步就是用vitamio打造属于自己的播放器。
欢迎转载,转载时请保留出处和原文链接,尊重知识产权,从小事做起