一个简单的拉流播放器,需要具备以下功能:
在定义播放器的生命周期们需要做到以下两步:
1. 先定义一下播放器的事件
2. 定义播放器展示的控件
一个播放器,需要定义出来相应的播放器的事件,最基本的播放器的操作就是:播放、暂停、停止。示例代码如下:
/**
* 播放器抽象类
*/
public abstract class Player {
protected boolean play = false;
public void play() {
this.play = true;
}
public void pause() {
this.play = false;
}
public void stop() {
this.play = false;
}
}
这里其实和原生的MediaPlayer及Ijkplayer是一样的,都是监听Surface的状态来控制播放器:创建,暂停,停止并release。
这里我们使用的控件是SurfaceView,创建一个VideoSurfaceView继承SurfaceView,并实现SurfaceHolder.Callback接口:
@Override
public void surfaceCreated(SurfaceHolder holder) {
initLayout(mPlayer.getWidth(), mPlayer.getHeight());
play();
if (onPreparedListener != null) onPreparedListener.onPrepared();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.v(TAG, "surfaceChanged...");
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mPlayer.pause();
}
public void releasePlayer() {
mPlayer.stop();
}
到这里我们把基本的播放器的生命周期的控制部分完成了,接下来就是完成基本的音视频数据的获取和播放了。
我们使用的是 JavaCV + FFmpeg的API,关于JavaCV的基本的介绍在上一篇文章 JavaCV入门 里面已经做了,下面一边介绍使用到的核心类一边说明音视频播放的流程:
所在package包为:org.bytedeco.javacv,完整类名为:org.bytedeco.javacv.FFmpegFrameGrabber
FFmpegFrameGrabber可以理解为解码器,也可以理解为帧收集器,主要作用就是将视频流以帧的形式拉去到手机设备上。
mFrameGrabber = FFmpegFrameGrabber.createDefault(path);
上面的代码就是创建FFmpegFrameGrabber的方式,path就是要拉取流的地址。
mFrameGrabber.setPixelFormat(AV_PIX_FMT_RGBA);
设置帧收集时的像素格式,这块设置AV_PIX_FMT_RGBA的原因主要是,我们展示画面的时候是转换为Bitmap格式的。
mFrameGrabber.setOption("fflags", "nobuffer");
上面的代码表示我们可以像ijkplayer一样,设置一些参数,这些参数格式我们可以参考ijkplayer也可以去ffmpeg命令行的一些设置参数文档里面去查找,这里就不多赘述了。
mFrameGrabber.start();
上面的代码就是让帧收集器启动,这样就开始拉流了。
所在package包为:org.bytedeco.javacv,完整类名为:org.bytedeco.javacv.Frame
Frame 是一个用于管理音频和视频帧数据的类。 在CanvasFrame、FrameGrabber、FrameRecorder及他们的子类里面都有用到。
Frame grabframe = mFrameGrabber.grab();
上面的代码表示从帧收集器里面抓去最新的帧:
播放音频:grabframe.samples里面获取到的就是原始的pcm音频数据,交给AudioTrack处理就ok了。
播放视频:首先需要将Frame图像转换为Bitmap,AndroidFrameConverter.convert(frame)就可以转换,但是在这之前需要使用OpenCVFrameConverter.ToIplImage将抓出来的Frame转换一下。
Canvas canvas = mHolder.lockCanvas();
canvas.drawBitmap(bmp, null, new Rect(0, 0, canvas.getWidth(), frame.imageHeight * canvas.getWidth() / frame.imageWidth), null);
mHolder.unlockCanvasAndPost(canvas);
上面的代码表示将获取到的位图绘制到SurfaceHolder里面去,这里建议启动线程去绘制,这样效率会高很多。