一、前言
在 Android 音視頻開發學習思路 中,我們不斷的學習和了解音視頻相關的知識,隨着知識點不斷的學習,我們現在應該做的事情,就是將知識點不斷的串聯起來。這樣才能得到更深層次的領悟。通過整理 Android 音視頻開發(一) : 通過三種方式繪制圖片 我們知道可以使用ImageView和SurfaceView甚至是View來展示圖片,通過整理 Android 音視頻開發(三):使用 AudioTrack 播放PCM音頻 我們知道如何播放音頻原始數據了。那么可不可以定義為,我們找到了如何播放音視頻的最基本的方式呢?答,當然是的!在 JavaCV 學習(一):JavaCV 初體驗 里,我們接觸了一次JavaCV,發現里面提供的API相當豐富,尤其是圖形圖像處理方面,那么下面我們就基於JavaCV加上它提供的ffmpegAPi工具,來完成一個基本的拉流播放器的制作,鑒於起名很難,我們先把名字起好:NBPlayer。
二、設計方案
我們要做的是一個簡單的拉流播放器,需要具備以下功能:
- 將直播流拉取到設備上並展現出來;
- 保證播放當前直播流的音視頻是同步的;
- 播放視頻時可以切換全屏幕與非全屏;
三、定義播放器的生命周期
在定義播放器的生命周期們需要做到以下兩步:1. 先定義一下播放器的事件 2. 定義播放器展示的控件
1. 定義播放器事件
因為我們要做的就是一個播放器,所以就需要定義出來相應的播放器的事件,最基本的播放器的操作就是:播放、暫停、停止。示例代碼如下:
/** * 播放器抽象類 */ 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
為什么定義完播放器的操作事件之后,就去定義播放器展示的控件呢?
答:主要是因為我們做的播放器在展示控件方面的思路上和Android原生的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 + FFmpeg的API,關於JavaCV的基本的介紹在上一篇文章 JavaCV 學習(一):JavaCV 初體驗 里面已經做了,下面一邊介紹使用到的核心類一邊說明音視頻播放的流程:
1. FFmpegFrameGrabber
所在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();
上面的代碼就是讓幀收集器啟動,這樣就開始拉流了。
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去。
五、說明
1. 針對此播放器實現的功能的說明:
- 只實現了拉取直播RTMP流並播放的功能,只能播放不帶B幀的直播流,因為B幀解析出來全是帶方向的箭頭(雙向預測幀),所以這個播放器也就順勢起名叫做NBPlayer。
- 有關於I幀、B幀、P幀這方面的內容的可以參考本人之前寫的 視頻直播技術——幀概念 了解一下,當然也可以自行百度,有很多大神的文章。
2. 針對此播放器的Demo示例:
- 代碼已經開源到github,地址為:https://github.com/renhui/NBPlayer ,各位有興趣的話,可以給個star,感激不盡!
3. 針對此播放器實現時本人的一些感悟:
- 做技術嘛,感覺更多的是對一些知識的理解和整合,其實能做出來這個播放器,成就感也是不小的。
- 如果沒有之前的一些知識儲備和技術鋪墊,也是沒辦法實現的,做出來了,對音視頻的一些理解,也變得更加清晰了。
4. 針對此播放器的一些功能拓展的想法:
- 展示的內容為RGB的,如果需要是可以轉換為YUV格式的,這個在實際項目中可能會使用到。
- 我們能拿到直播的畫面和聲音數據,當然可以實時的保存這些數據了,這也就為錄制成文件做好了鋪墊了。
