視頻開發(1)- 基礎知識點整理

話說音視頻播放現在真是不搞不行啦,不光我們要從市面上 N 多的播放器中找出好用的,熟悉其 API ,甚至還需要我們能利用開源解碼器來編寫我們自己的視頻播放器,模塊,這點在越來越不一樣,差異化的播放器中可以看得見,很多 app 不光播放器 UI 不一樣,甚至很多邏輯都不同

所以大家努力吧,先不求能搞定 FFMG 編解碼等底層技術,至少我們現在得能利用 ijkPlayer 編寫自己的視頻播放器出來,才能徹底搞得定 app 端的播放需求,要是打算自己上直播推流 sdk ,那么學的就更多了,都是得按年來計算了,音視頻水太神了,至少要求我們得搞定 UI 部分才行,在視頻這塊用別人開源的播放器是走不遠的,因為需求差異性太大

錄制音視頻 AudioRecord/MediaRecord

視頻的基礎知識點推薦看下面:


視頻流媒體協議有哪些

  • HTTP
    -> 最傳統的視頻流媒體,不支持實時流媒體的播放
  • RTMP
    -> Adobe 公司用于 flash 播放的
  • RTSP
    -> android 原生支持此種流媒體協議
  • FLV
  • HLS
    -> apple 公司開發,把視頻分成 3-10 秒的小段,下發 m3u8 文件來標記文件順序,使用 HTTP、HTTPS 傳輸
  • MMS
    -> 微軟公司開發的

官方原生播放器 MediaPlayer

MediaPlayer 是 Androd 多媒體框架中的一個重要組件,通過該類,我們可以解碼和播放音視頻,但是 MediaPlayer 本身只支持 音頻 播放,需要傳入專門的視頻承載 view 才能播放視頻

MediaPlayer 可以支持三種不同的媒體來源:

  • 本地資源
  • 內部URI,比如你可以通過ContentResolver來獲取
  • 外部URL(流)

MediaPlayer支持兩種流媒體協議,HTTP 和 RTSP,這兩種協議最大的不同是,RTSP 協議支持實時流媒體的播放,而 HTTP 協議不支持

原生 MediaPalyer 支持的協議和封裝格式實在太有限了,如果我們想播放那些它不支持的視頻,這時候就需要第三方播放器了,很多第三方播放器的底層實現都是基于 FFmpeg

MediaPlayer 對于視頻格式支持的也是非常少,值能支持: MP4,AVI,3DP 這3個早期的手機品是格式

開源的音視頻解碼器在 API 上都參考了 MediaPlayer ,所以學習 MediaPlayer API 就是我們的第一步

  1. 創建 MediaPlayer 對象,可以直接和本地資源綁定
MediaPlayer mp = new MediaPlayer();
MediaPlayer mp = MediaPlayer.create(this, R.raw.test); //無需再調用setDataSource
create(Context context, Uri uri, SurfaceHolder holder)
  1. 設置播放資源
MediaPlayer.create(this, R.raw.test); // raw下的資源
mp.setDataSource("/sdcard/test.mp3"); // 本地文件路徑
mp.setDataSource("http://www.xxx.com/music/test.mp3");// 網絡URL文件
  1. 播放資源
// uri 資源
Uri myUri = ....;   /**initialize Uri here*/
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();

// 網絡文件
String url = "http://........"; // your URL here 
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepareAsync()
mediaPlayer.setOnPreparedListener({
    // 加載完畢再開始播放
    mediaPlayer.start()
})
  1. MediaPlayer 主要 API,需要熟知
getCurrentPosition( ):得到當前的播放位置
getDuration() :得到文件的時間
getVideoHeight() :得到視頻高度
getVideoWidth() :得到視頻寬度
isLooping():是否循環播放
isPlaying():是否正在播放
pause():暫停
prepare():準備(同步)
prepareAsync():準備(異步)
release():釋放MediaPlayer對象
reset():重置MediaPlayer對象
seekTo(int msec):指定播放的位置(以毫秒為單位的時間)
setAudioStreamType(int streamtype):指定流媒體的類型
setDisplay(SurfaceHolder sh):設置用SurfaceHolder來顯示多媒體
setLooping(boolean looping):設置是否循環播放
setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener):網絡流媒體的緩沖監聽
setOnCompletionListener(MediaPlayer.OnCompletionListener listener):網絡流媒體播放結束監聽
setOnErrorListener(MediaPlayer.OnErrorListener listener):設置錯誤信息監聽
setOnVideoSizeChangedListener(MediaPlayer.OnVideoSizeChangedListener listener):視頻尺寸監聽
setScreenOnWhilePlaying(boolean screenOn):設置是否使用SurfaceHolder顯示
setVolume(float leftVolume, float rightVolume):設置音量
start():開始播放
stop():停止播放

我們需要熟知下面這張 MediaPalyer 解碼器生命周期圖,所有的開源項目都是按這個思路來做的


播放器生命周期圖
  • 狀態1:Idel(空閑)狀態
    當 mediaplayer創建或者執行reset()方法后處于這個狀態。

  • 狀態2:Initialized(已初始化)狀態
    當調用mediaplayer的setDataResource()方法給mediaplayer設置播放的數據源后,mediaplayer會處于該狀態。

  • 狀態3:Prepared(準備就續)狀態
    設置完數據源后,調用mediaplayer的prepare()方法,讓mediaplayer準備播放。值得一提的是,這里除了prepare()方法,還有prepareAsnyc()方法,此方法是異步方法,一般用于網絡視頻的緩沖。當緩沖完畢后,就會觸發準備完畢的事件。我們要做的就是監聽該事件(OnPreparedListener),當緩沖完成時,執行相應的操作。在此狀態上,我們可以調用seekTo()方法定位視頻,此方法不改變mediaplayer的狀態;亦可調用stop()放棄視頻播放,使mediaplayer處于Stopped狀態。一般我們會在此狀態上調用start()方法開始播放視頻。

  • 狀態4:Started(開始)狀態
    當處于Prepared狀態、Paused狀態和PlayebackCompeleted狀態時,調用Started()方法即可進入該狀態。在該狀態中,mediaplayer開始播放視頻,可以通過seekTo()方法和start()方法改變視頻播放的進度,當Looping為真且播放完畢后,它會重新開始播放(即循環播放);否則播放完畢后,會觸發事件并調用OnCompletionaListener.OnCompletion()方法,進行特定操作,并進入PlaybackCompleted狀態。在此狀態中,亦可調用pause()方法或者stop()方法讓視頻暫?;蛲V梗藭rmediaplayer分別處于Stopped和Paused狀態。

  • 狀態5:Stopped(停止)狀態
    當 mediaplayer處于Prepared、Started、Paused、PlaybackCompleted狀態時,調用stop()方法即可進入本狀態。應特別注意的是,在本狀態中,若想重新開始播放,不能直接調用start()方法,必須調用prepare()方法或prepareAsync()方法重新讓mediaplayer處于Prepared狀態方可調用start()方法播放視頻。

  • 狀態6:Paused(暫停)狀態
    當mediaplayer處于Started狀態是,調用pause()方法即可進入本狀態。在本狀態里,可直接調用start()方法使,mediaplayer回到Started狀態,亦可調用stop()方法停止視頻播放,讓播放器處于停止態。

  • 狀態7:PlaybackCompleted(播放完成)狀態
    當mediaplayer播放完成且Looping為假時即可進入本狀態。在本狀態可調用start()方法使mediaplayer回到Started狀態(注意此時是從頭開始播放);亦可調用stop()方法使mediaplayer處于停止態,結束播放。

  • 狀態8:Error(錯誤)狀態
    當mediaplayer出現錯誤時處于此狀態。


SurfaceView , TextureView

SurfaceView , TextureView 都是 android 中用于承載視頻幀顯示的 view ,熟悉這2個 view 的大家都知道,這 2個 view 都是異步的,都是再非 UI 線程中計算的,擁有獨立 surface 顯存的,這是因為視頻播放任務太重,UI 線程 hold 不住

  • SurfaceView
    大概原理就是在現有View的位置上創建一個新的 Window,內容的顯示和渲染都在新的 Window 中。這使得 SurfaceView 的繪制和刷新可以在單獨的線程中進行,從而大大提高效率。但是呢,由于 SurfaceView 的內容沒有顯示在View中而是顯示在新建的 Window中, 使得 SurfaceView 的顯示不受 View 的屬性控制,不能進行平移,縮放等變換,也不能放在其它 RecyclerView 或 ScrollView中,一些 View 中的特性也無法使用。

  • TextureView
    TextureView 是在4.0(API level 14)引入的,與 SurfaceView 相比,它不會創建新的窗口來顯示內容。它是將內容流直接投放到View中,并且可以和其它普通View一樣進行移動,旋轉,縮放,動畫等變化。TextureView必須在硬件加速的窗口中使用。TextureView被創建后不能直接使用,必須要在它被它添加到ViewGroup后,待SurfaceTexture準備就緒才能起作用(看TextureView的源碼,TextureView是在繪制的時候創建的內部SurfaceTexture。SurfaceTexture的準備就緒、大小變化、銷毀、更新等狀態變化時都會回調相對應的方法。當TextureView內部創建好SurfaceTexture后,在監聽器的onSurfaceTextureAvailable方法中,用SurfaceTexture來關聯MediaPlayer,作為播放視頻的圖像數據來源。SurfaceTexture作為數據通道,把從數據源(MediaPlayer)中獲取到的圖像幀數據轉為GL外部紋理,交給TextureVeiw作為View heirachy中的一個硬件加速層來顯示,從而實現視頻播放功能。


視頻播放方式一:VideoView + MediaController

這是官方原生的實現,VideoView 繼承 SurfaceView ,內部封裝了 SurfaceView 的所有操作,MediaController 這樣來顯示視頻控制相關的 view 部分

我個人是非常喜歡官方的 API 設計的,代碼功能,層次分離,起名都非常規范,讓人一看就懂,用起來也是很爽,奈何就是支持的流媒體格式太少,我們只能去使用開源組件

VideoView + MediaController 的簡單使用

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    button = (Button) findViewById(R.id.play);
    videoview = (VideoView) findViewById(R.id.video);

    mMediaController = new MediaController(this);
    videoview.setMediaController(mMediaController);
    mMediaController.setAnchorView(videoview)

    button.setOnClickListener(new View.OnClickListener(){
        @Override
        public void onClick(View v) {
            loadView(url.getText().toString());
        }
    });
}

public void loadView(String path) {
    Uri uri = Uri.parse(path);
    videoview.setVideoURI(uri);
    videoview.start;
    videoview.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
        @Override
        public void onPrepared(MediaPlayer mp) {
 //         mp.setLooping(true);
            mp.start();// 播放
            Toast.makeText(MainActivity.this, "開始播放!", Toast.LENGTH_LONG).show();
        }
    });
    videoview.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
        @Override
        public void onCompletion(MediaPlayer mp) {
            Toast.makeText(MainActivity.this, "播放完畢", Toast.LENGTH_SHORT).show();
        }
    });
}

我們簡單看下 MediaController 是如何管理 視頻控制 view 的

MediaController 繼承 FrameLayout

public class MediaController extends FrameLayout

在 makeControllerView 中創建 控制布局 view ,并初始化 view

    protected View makeControllerView() {
        LayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        mRoot = inflate.inflate(com.android.internal.R.layout.media_controller, null);

        initControllerView(mRoot);

        return mRoot;
    }

media_controller 樣式


media_controller

在 setAnchorView 關聯視頻播放器 VideoView 時把 控制 view 添加到自己身上

    public void setAnchorView(View view) {
        if (mAnchor != null) {
            mAnchor.removeOnLayoutChangeListener(mLayoutChangeListener);
        }
        mAnchor = view;
        if (mAnchor != null) {
            mAnchor.addOnLayoutChangeListener(mLayoutChangeListener);
        }

        FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT
        );

        removeAllViews();
        View v = makeControllerView();
        addView(v, frameParams);
    }

視頻播放方式二:開源解碼器 + TextureView

我們注定是不會使用原生 VideoView 去遠程視頻的,沒辦法主流的流媒體格式 VideoView 都不支持,我們只能去使用 開源的視頻解碼器 + TextureView 去自己實現了

這就是我們后面需要自己的搞定的


參考:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380