本博文主要介绍Android中视频的播放形式,以及Android中音视频编解码库。
一、Android中视频播放的三种方式:
1、MediaPlayer
如果只是播放音频,直接使用MediaPlayer即可。如果播放视频的话,则需要MediaPlayer+Surface。
关于MdiaPlayer详解:
1.1 MediaPlayer的实例化:两种方式:MediaPlayer mp = new MediaPlayer();//MediaPlayer只有这么一个无参的构造方法
或者 MediaPlayer mp = MediaPlayer.create(this,R.raw.test); //此时不需要调用setDataSource()方法了
1.2 设置要播放的文件:用户在应用中事先自带的resource资源 MediaPlayer.create(this,R.raw.test);
存储在sd卡或者其他路径下的媒体文件 mp.setDataSource("/sdcard/test.mp3");
网络上的媒体文件
MediaPlayer的setDataSource一共四个方法:
setDataSource (String path)
setDataSource (FileDescriptor fd)
setDataSource (Context context, Uri uri)
setDataSource (FileDescriptor fd, long offset, long length)
其中使用FileDescriptor时,需要将文件放到与res文件夹平级的assets文件夹里,然后使用:
AssetFileDescriptor fileDescriptor = getAssets().openFd("rain.mp3");
mp.setDataSource(fileDescriptor.getFileDescriptor(),fileDescriptor.getStartOffset(), fileDescriptor.getLength());来设置datasource
1.3 对播放器的主要控制方法
Android通过控制播放器的状态的方式来控制媒体文件的播放,其中:prepare()和prepareAsync() 提供了同步和异步两种方式设置播放器进入prepare状态。
如果是采用create的方式创建的mediaPlayer,则第一次启动播放前是不需要调用prepare方法的,因为create方法中已经调用了。
启动文件播放:start()
暂停:pause()
停止播放:stop()
定位方法:seekTo() 可以让播放器从指定的位置开始播放,需要注意的是该方法是个异步方法,也就是说该方法返回时并不意味着定位完成,尤其是播放的网络文件,真正定位完成时会触发OnSeekComplete.onSeekComplete(),如果需要是可以调用setOnSeekCompleteListener(OnSeekCompleteListener)设置监听器来处理的
释放资源:release() 一旦确定不再使用播放器,应尽早释放资源
从错误状态中恢复:reset()
1.4 设置播放器的监听器
MediaPlayer提供了一些设置不同监听器的方法来更好地对播放器的工作状态进行监听,以期及时处理各种情况,如: setOnCompletionListener(MediaPlayer.OnCompletionListener listener)、
setOnErrorListener(MediaPlayer.OnErrorListener listener)、setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener)等,设置播放器时需要考虑到播放器可能出现的情况设置好监听和处理逻辑,以保持播放器的健壮性。
1.5 MediaPlayer除了可以播放本地音视频、网络媒体文件以外,还支持rtsp实时流的播放。
mp.setDataSource(rtsp流地址);
mp.setDisplay(surfaceView.getHolder());
1.6 用MediaPlayer播放视频时,需要注意得在surface被创建好了后再setDisplay(surfaceView.getHolder());
可以在surfaceCreated(SurfaceHolder holder)方法中进行此操作,如下:
surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
player = new MediaPlayer();
try {
player.setDataSource(rtspUri);
player.setDisplay(surfaceView.getHolder());
player.prepare();
player.start();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
});
1.7 也可以通过MediaPlayer+TextureView实现播放视频。此时应该是mp.setSurface(surface);
2、VideoView
使用VideoView来播放。在布局文件中使用VideoView结合MediaController来实现对其控制。
示例代码:
VideoView videoView = (VideoView)this.findViewById(R.id.videoView);
videoView.setMediaController(new MediaController(this));
videoView.setVideoURI(uri);
videoView.start();
videoView.requestFocus();
当我们用VideoView播放网络视频的时候,可能需要等待一段时间,尤其当网络不好的时候。为了提高用户体验,此时我们增加缓冲条,并且通过setOnPreparedListener这个方法给VideoView设置监听。代码如下:
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
progressBar.setVisibility(View.GONE);
}
});
关于该方法的官方解释如下:
/**
* Register a callback to be invoked when the media file
* is loaded and ready to go.
*
* @param l The callback that will be run
*/
public void setOnPreparedListener(MediaPlayer.OnPreparedListener l)
{
mOnPreparedListener = l;
}
即,注册一个回调,当媒体文件已经被加载并且准备播放时 ,这个回调被调用。
3、调用安卓自带的播放器
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri,"video/mp4");
startActivity(intent);
二、Android中音视频编解码库
MediaCodec类可以用于访问底层媒体编解码器。它是Android底层多媒体支持基础设施的一部分。通常和MediaExtractor
,MediaSync
,MediaMuxer
,MediaCrypto
,MediaDrm
,Image
,Surface
, andAudioTrack一起使用。
工作机制示意图:
其生命周期示意图:
在项目中使用MediaCodec进行实时视频解码的代码示例:
首先,我们得创建解码器对象
MediaCodec decoder = MediaCodec.createCodecByName("OMX.google.h264.decoder"); //Uninitialized State
//这里使用的是软解
//MediaCodec既支持硬解,也支持软解
当然,也可以采用下面的代码进行实例化:MediaCodec.createDecoderByType(String);
然后,进行配置configure:
MediaFormat format = MediaFormat.createVideoFormat("video/avc",mSurfaceWidth,mSurfaceHeight);
decoder.configure(format,surface,null,0); //Configured State 第三个参数是加密
接着,调用start()开启解码器
decoder.start(); //Executing state--->Flushed sub-state
ByteBuffer[] inputBuffers = decoder.getInputBuffers(); //这里使用的是Buffer Array的方式,这种方式在Android 5.0(API 21)以上已经过时,这里我为了向下兼容,故而采用此方式。
int index = decoder.dequeueInputBuffer(-1); //Running sub-state
ByteBuffer buffer = inputBuffers[index];
buffer.clear();
buffer.put(array,0,array.length); //把需要解码的数据拷贝到empty buffer
decoder.queueInputBuffer(index,0,array.length,timestamp,MediaCodec.BUFFER_FLAG_CODEC_CONFIG); //End-of-Stream sub-stream
最后,从解码器中拿解码后的数据:
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
int index2 = decoder.dequeueOutputBuffer(info,0);
ByteBuffer outputBuffer;
byte[] array_output;
while(index2 >= 0)
{
ByteBuffer[] outputBuffers = decoder.getOutputBuffers();
outputBuffer = outputBuffers[index2]; //解码后数据
array_output = new byte[info.size];
outputBuffer.get(array_output);
decoder.releaseOutputBuffer(index2,true); //返回缓冲区给解码器,并且在surface上渲染它
index2 = decoder.dequeueOutputBuffer(info,0);
}
MediaFormat format_output = decoder.getOutputFormat();
int color_format = format_output.getInteger(KEY_COLOR_FORMAT); //获得解码后的数据格式
int width_format = format_output.getInteger(KEY_WIDTH); //获得解码后的视频宽
int height_format = format_output.getInteger(KEY_HEIGHT); //获得解码后的视频高
//得到这些后就可以实现抓取实时视频的一帧图像的功能了
最后别忘了,decoder.stop(); //Uninitialized State
decoder.release();