Android 视频播放器 (三):使用NBPlayer播放直播视频

1、前言

  在 Android 音视频开发学习思路 中,咱们不断的学习和了解音视频相关的知识,随着知识点不断的学习,咱们如今应该作的事情,就是将知识点不断的串联起来。这样才能获得更深层次的领悟。经过整理 Android 音视频开发(一) : 经过三种方式绘制图片 咱们知道可使用ImageView和SurfaceView甚至是View来展现图片,经过整理 Android 音视频开发(三):使用 AudioTrack 播放PCM音频 咱们知道如何播放音频原始数据了。那么可不能够定义为,咱们找到了如何播放音视频的最基本的方式呢?答,固然是的!在 JavaCV 学习(一):JavaCV 初体验 里,咱们接触了一次JavaCV,发现里面提供的API至关丰富,尤为是图形图像处理方面,那么下面咱们就基于JavaCV加上它提供的ffmpegAPi工具,来完成一个基本的拉流播放器的制做,鉴于起名很难,咱们先把名字起好:NBPlayer。html

2、设计方案

咱们要作的是一个简单的拉流播放器,须要具有如下功能:java

  1. 将直播流拉取到设备上并展示出来;
  2. 保证播放当前直播流的音视频是同步的;
  3. 播放视频时能够切换全屏幕与非全屏;

3、定义播放器的生命周期

在定义播放器的生命周期们须要作到如下两步:1. 先定义一下播放器的事件   2. 定义播放器展现的控件git

1. 定义播放器事件

由于咱们要作的就是一个播放器,因此就须要定义出来相应的播放器的事件,最基本的播放器的操做就是:播放、暂停、中止。示例代码以下:github

/**
 * 播放器抽象类
 */
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; } } 

2. 定义播放器展现的控件 - SurfaceView

为何定义完播放器的操做事件以后,就去定义播放器展现的控件呢?canvas

答:主要是由于咱们作的播放器在展现控件方面的思路上和Android原生的MediaPlayer及Ijkplayer是同样的,都是监听Surface的状态来控制播放器何时建立,何时暂停,何时中止并release。ide

这里咱们使用的控件是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(); }

上述代码能够看到咱们把基本的播放器的生命周期的控制部分完成了,后续的工做就是完成基本的音视频数据的获取和播放了。post

4、使用JavaCV + FFmpeg的API播放拉取音视频流

咱们使用的是 JavaCV + FFmpeg的API,关于JavaCV的基本的介绍在上一篇文章 JavaCV 学习(一):JavaCV 初体验 里面已经作了,下面一边介绍使用到的核心类一边说明音视频播放的流程:学习

1. FFmpegFrameGrabber

所在package包为:org.bytedeco.javacv,完整类名为:org.bytedeco.javacv.FFmpegFrameGrabberui

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();

上面的代码就是让帧收集器启动,这样就开始拉流了。

2. Frame

所在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里面去,这里建议启动线程去绘制,这样效率会高不少。And 别问为啥子能在线程里面绘制画面,本身学习SurfaceView去。

5、说明

1. 针对此播放器实现的功能的说明:

  • 只实现了拉取直播RTMP流并播放的功能,只能播放不带B帧的直播流,由于B帧解析出来全是带方向的箭头(双向预测帧),因此这个播放器也就顺势起名叫作NBPlayer。
  • 有关于I帧、B帧、P帧这方面的内容的能够参考本人以前写的 视频直播技术——帧概念 了解一下,固然也能够自行百度,有不少大神的文章。

2. 针对此播放器的Demo示例:

3. 针对此播放器实现时本人的一些感悟:

  • 作技术嘛,感受更多的是对一些知识的理解和整合,其实能作出来这个播放器,成就感也是不小的。
  • 若是没有以前的一些知识储备和技术铺垫,也是没办法实现的,作出来了,对音视频的一些理解,也变得更加清晰了。

4. 针对此播放器的一些功能拓展的想法:

  • 展现的内容为RGB的,若是须要是能够转换为YUV格式的,这个在实际项目中可能会使用到。
  • 咱们能拿到直播的画面和声音数据,固然能够实时的保存这些数据了,这也就为录制成文件作好了铺垫了。