天天看點

android videoview截屏,如何進行網絡視訊截圖/擷取視訊的縮略圖

小編導讀:擷取視訊的縮略圖,截圖正在播放的視訊某一幀,是在音視訊開發中,常遇到的問題。本文是主要用于點播中截圖視訊,同時還可以擷取點播視訊的縮略圖進行顯示,留下一個問題,如下圖所示,如果要擷取直播中節目視訊縮略圖,該怎麼做呢?(ps:直播是直播流,具有實時性)

android videoview截屏,如何進行網絡視訊截圖/擷取視訊的縮略圖

本文來自HowNoon分享,HowNoon的blog位址為:http://blog.csdn.net/yuehenhn。

最近部落客遇到一個Android電視的開發項目,項目需要電視用戶端播放伺服器端視訊,通過遙控器一鍵截圖,并将截圖雲推送到手機用戶端,于是部落客就開始找度神去求助了,畢竟以前沒搞過視訊,當然要去先搜集下資料再開工啦,正所謂知己知彼,百戰百勝嘛,于是,你将看到部落客以下兩天中的蛋疼經曆

參考内容

部落客在搜集完資料之後,将兩篇文章進行了對比,videoview一切都內建好了,而SurfaceView要設定很多東西,看起來複雜一些,雖然提供了截圖方法,但隻是用于本地截圖,對部落客沒什麼用啊,于是本着省事的原則,果斷的選擇了videoview。以下是幾個走過的坑:1

videoView.getDrawingCache();擷取截圖

部落客屁颠屁颠的将代碼copy下來,先跑跑看,果然執行沒問題。好了,那就來找找videoview怎樣截圖吧,部落客回想到以前View截圖為調用getDrawingCache(),于是部落客機智的先看了看videoview的API文檔,發現其中也有這個方法:videoView.getDrawingCache();

該方法傳回值為Bitmap類型,我擦,趕快去使用。結果截到的圖檔果然不出意外——-黑屏2

videoView.setDrawingCacheEnabled(true);

bitmap = videoView.getDrawingCache();擷取截圖

既然bitmap

=

videoView.getDrawingCache();擷取不到截圖,就該查找原因額,部落客又去找度神了,果然好多哥們和部落客一樣的問題,皆以失敗告終。最後碰見一CSDN論壇的朋友回複,知道了其中的緣由,因為view走的是framebuffer,而videoview走的是overlay

附上各種求救連結3

MediaMetaDataRetreive擷取截圖

既然getDrawingCache();方法行不通,看來還是搜集資料不夠多,于是部落客又找到了MediaMetaDataRetreive這個類public static Bitmap getCurrentVideoBitmap(String url,VideoView videoView){        Bitmap bitmap = null;        MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();        try {            mediaMetadataRetriever.setDataSource(url,new HashMap());            Log.e(LOG_STRING,"截圖的時間為"+videoView.getCurrentPosition());            //取得指定時間的Bitmap,即可以實作抓圖(縮略圖)功能            bitmap = mediaMetadataRetriever.getFrameAtTime(videoView.getCurrentPosition()*1000,MediaMetadataRetriever.OPTION_CLOSEST);         } catch (IllegalArgumentException ex) {            // Assume this is a corrupt video file        } catch (RuntimeException ex) {            // Assume this is a corrupt video file.        } finally {            try {                mediaMetadataRetriever.release();            } catch (RuntimeException ex) {                // Ignore failures while cleaning up.            }        }        if (bitmap == null) {            return null;        }        //bitmap = Bitmap.createScaledBitmap(bitmap, 200, 200, true);        bitmap = Bitmap.createBitmap(bitmap);        return bitmap;}

getFrameAtTime說明:videoView.getCurrentPosition()得到的為毫秒,不轉化為秒的話,截出來的圖都是視訊第一幀的圖像,第二個參數可以傳遞的值有四個,分别為OPTION_CLOSEST,

OPTION_CLOSEST_SYNC, OPTION_NEXT_SYNC, OPTION_PREVIOUS_SYNC

部落客前期用的參數為OPTION_NEXT_SYNC,部落客以為問題解決了,去幹别的事情了,但等到晚上測試的時候卻發現一個問題,這種方案截圖是可以,但僅僅做個沒播放視訊之前的縮略圖還算可以,但真的做實時截圖還是不妥,這是因為:經部落客測試,這種方法截圖的畫面,和電視截圖的畫面,存在着1-2秒的誤差,也就是說截出的圖完全不一樣(測試機器三星Note4,華為榮耀6Plus),部落客又失敗了。4

OPTION_CLOSEST_SYNC參數換為OPTION_CLOSEST

部落客在思考,既然網上好多文章都說這種方法可以實作,會不會是我的參數有問題,于是樓主找到了很多相同的文章介紹

同時部落客又搜集了一些資料,并仔細檢視了getFrameAtTime方法的詳細參數

以下為各參數詳解

android videoview截屏,如何進行網絡視訊截圖/擷取視訊的縮略圖

新發現:原來視訊檔案存在關鍵幀的問題,通過度神了解,原來是可能getFrameAtTime傳入的時間值,在視訊的那一刻,并沒有關鍵幀,是以OPTION_CLOSEST_SYNC傳回的值為附近的時間截圖。根據部落客參考API的詳情,果斷換成了OPTION_CLOSEST。

參看資料:

android videoview截屏,如何進行網絡視訊截圖/擷取視訊的縮略圖

失敗:有些截圖是正常了,但是有些時間的截圖卻直接傳回null,尼瑪這是在坑爹啊。。。5

尋找第三方架構截圖

雖然前面都失敗了,不過部落客的資料搜集的又全了些,部落客打算開始找第三方架構了,于是部落客找到了比較出名的兩個架構導入代碼測試,這個架構好像将videoview封裝了一層,叫mVideoview,據官網介紹,截圖方法為getCurrentFrame();不過據部落客測試,傳回值為空。

android videoview截屏,如何進行網絡視訊截圖/擷取視訊的縮略圖

導入代碼進行測試,發現該架構代碼裡預設為rtps協定視訊,并且封裝了C++的so檔案,使用到了JNI技術,截圖什麼的還要自己打更新檔,看了看各位大神的文章介紹,看起來好複雜,本人水準有限,就默默放棄了。

6

TextureView播放視訊并截圖

部落客在度神上已經實在找不到方法了,部落客打算去***google一下,功夫不負有心人,老外也碰到了和部落客一樣的問題,部落客找到了新的一個解決方案,那就是TextureView播放視訊并截圖,後來搜集了下情報,據說這玩意是2014年google搞出來的玩意。

附上回答:

android videoview截屏,如何進行網絡視訊截圖/擷取視訊的縮略圖

附上翻譯:

**我已經找到了解決這個問題的辦法。看來VideoView不允許因為底層硬體GPU的原因而使用SurfaceView。

該解決方案是使用一個textureview使用MediaPlayer播放視訊裡面。該活動将需要實作textureview.surfacetexturelistener。當用這個解決方案的截圖,視訊當機了一段時間。同時,該textureview不顯示播放進度條預設UI(播放,暫停,快進/

RW,玩的時間,等)。這是一個缺點。如果你有另一個解決方案,請讓我知道:)**

看到這個頁面,當然廢話不多說了,建工程,copy,測試!public class TextureViewActivity extends Activity     implements TextureView.SurfaceTextureListener,                 OnBufferingUpdateListener,                 OnCompletionListener,                 OnPreparedListener,                 OnVideoSizeChangedListener {    private MediaPlayer mp;    private TextureView tv;    public static String MY_VIDEO = "https://www.blahblahblah.com/myVideo.mp4";    public static String TAG = "TextureViewActivity";    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_texture_view);        tv = (TextureView) findViewById(R.id.textureView1);        tv.setSurfaceTextureListener(this);    }    public void getBitmap(TextureView vv)    {        String mPath = Environment.getExternalStorageDirectory().toString()                 + "/Pictures/" + Utilities.getDayTimeString() + ".png";           Toast.makeText(getApplicationContext(), "Capturing Screenshot: " + mPath, Toast.LENGTH_SHORT).show();        Bitmap bm = vv.getBitmap();        if(bm == null)            Log.e(TAG,"bitmap is null");        OutputStream fout = null;        File imageFile = new File(mPath);        try {            fout = new FileOutputStream(imageFile);            bm.compress(Bitmap.CompressFormat.PNG, 90, fout);            fout.flush();            fout.close();        } catch (FileNotFoundException e) {            Log.e(TAG, "FileNotFoundException");            e.printStackTrace();        } catch (IOException e) {            Log.e(TAG, "IOException");            e.printStackTrace();        }    }    @Override    public boolean onCreateOptionsMenu(Menu menu) {        // Inflate the menu; this adds items to the action bar if it is present.        getMenuInflater().inflate(R.menu.media_player_video, menu);        return true;    }    @Override    public boolean onOptionsItemSelected(MenuItem item) {        // Handle action bar item clicks here. The action bar will        // automatically handle clicks on the Home/Up button, so long        // as you specify a parent activity in AndroidManifest.xml.        int id = item.getItemId();        if (id == R.id.action_settings) {            return true;        }        return super.onOptionsItemSelected(item);    }    @Override    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)     {        Surface s = new Surface(surface);        try        {            mp = new MediaPlayer();            mp.setDataSource(MY_VIDEO);            mp.setSurface(s);            mp.prepare();            mp.setOnBufferingUpdateListener(this);            mp.setOnCompletionListener(this);            mp.setOnPreparedListener(this);            mp.setOnVideoSizeChangedListener(this);            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);            mp.start();            Button b = (Button) findViewById(R.id.textureViewButton);            b.setOnClickListener(new OnClickListener(){                @Override                public void onClick(View v)                 {                    TextureViewActivity.this.getBitmap(tv);                }            });        }        catch (IllegalArgumentException e) {            // TODO Auto-generated catch block            e.printStackTrace();        } catch (SecurityException e) {            // TODO Auto-generated catch block            e.printStackTrace();        } catch (IllegalStateException e) {            // TODO Auto-generated catch block            e.printStackTrace();        } catch (IOException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }      }

問題終結

經過部落客反複測試TextureView的getBitmap();方法,完美截圖,無任何時差,無傳回值為null問題,無截圖花屏問題,該問題終于終結了,曆時一天半,過程雖然心酸,但收獲了許多關于視訊方面相關的知識。部落客在搜集資料的同時,看到了很多像部落客一樣對該問題的困擾,故将詳細經曆總結出來,希望不要再有人步部落客後塵了。完畢,收工。