1、前言
在 Android 音視頻開發學習思路 中,咱們不斷的學習和了解音視頻相關的知識,隨着知識點不斷的學習,咱們如今應該作的事情,就是將知識點不斷的串聯起來。這樣才能獲得更深層次的領悟。經過整理 Android 音視頻開發(一) : 經過三種方式繪製圖片 咱們知道可使用ImageView和SurfaceView甚至是View來展現圖片,經過整理 Android 音視頻開發(三):使用 AudioTrack 播放PCM音頻 咱們知道如何播放音頻原始數據了。那麼可不能夠定義爲,咱們找到了如何播放音視頻的最基本的方式呢?答,固然是的!在 JavaCV 學習(一):JavaCV 初體驗 裏,咱們接觸了一次JavaCV,發現裏面提供的API至關豐富,尤爲是圖形圖像處理方面,那麼下面咱們就基於JavaCV加上它提供的ffmpegAPi工具,來完成一個基本的拉流播放器的製做,鑑於起名很難,咱們先把名字起好:NBPlayer。html
2、設計方案
咱們要作的是一個簡單的拉流播放器,須要具有如下功能:java
- 將直播流拉取到設備上並展示出來;
- 保證播放當前直播流的音視頻是同步的;
- 播放視頻時能夠切換全屏幕與非全屏;
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(); }
上述代碼能夠看到咱們把基本的播放器的生命週期的控制部分完成了,後續的工做就是完成基本的音視頻數據的獲取和播放了。學習
4、使用JavaCV + FFmpeg的API播放拉取音視頻流
咱們使用的是 JavaCV + FFmpeg的API,關於JavaCV的基本的介紹在上一篇文章 JavaCV 學習(一):JavaCV 初體驗 裏面已經作了,下面一邊介紹使用到的核心類一邊說明音視頻播放的流程:ui
1. FFmpegFrameGrabber
所在package包爲:org.bytedeco.javacv,完整類名爲:org.bytedeco.javacv.FFmpegFrameGrabberthis
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示例:
- 代碼已經開源到github,地址爲:https://github.com/renhui/NBPlayer ,各位有興趣的話,能夠給個star,感激涕零!
3. 針對此播放器實現時本人的一些感悟:
- 作技術嘛,感受更多的是對一些知識的理解和整合,其實能作出來這個播放器,成就感也是不小的。
- 若是沒有以前的一些知識儲備和技術鋪墊,也是沒辦法實現的,作出來了,對音視頻的一些理解,也變得更加清晰了。
4. 針對此播放器的一些功能拓展的想法:
- 展現的內容爲RGB的,若是須要是能夠轉換爲YUV格式的,這個在實際項目中可能會使用到。
- 咱們能拿到直播的畫面和聲音數據,固然能夠實時的保存這些數據了,這也就爲錄製成文件作好了鋪墊了。