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

本文來自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方法的詳細參數
以下為各參數詳解
新發現:原來視訊檔案存在關鍵幀的問題,通過度神了解,原來是可能getFrameAtTime傳入的時間值,在視訊的那一刻,并沒有關鍵幀,是以OPTION_CLOSEST_SYNC傳回的值為附近的時間截圖。根據部落客參考API的詳情,果斷換成了OPTION_CLOSEST。
參看資料:
失敗:有些截圖是正常了,但是有些時間的截圖卻直接傳回null,尼瑪這是在坑爹啊。。。5
尋找第三方架構截圖
雖然前面都失敗了,不過部落客的資料搜集的又全了些,部落客打算開始找第三方架構了,于是部落客找到了比較出名的兩個架構導入代碼測試,這個架構好像将videoview封裝了一層,叫mVideoview,據官網介紹,截圖方法為getCurrentFrame();不過據部落客測試,傳回值為空。
導入代碼進行測試,發現該架構代碼裡預設為rtps協定視訊,并且封裝了C++的so檔案,使用到了JNI技術,截圖什麼的還要自己打更新檔,看了看各位大神的文章介紹,看起來好複雜,本人水準有限,就默默放棄了。
6
TextureView播放視訊并截圖
部落客在度神上已經實在找不到方法了,部落客打算去***google一下,功夫不負有心人,老外也碰到了和部落客一樣的問題,部落客找到了新的一個解決方案,那就是TextureView播放視訊并截圖,後來搜集了下情報,據說這玩意是2014年google搞出來的玩意。
附上回答:
附上翻譯:
**我已經找到了解決這個問題的辦法。看來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問題,無截圖花屏問題,該問題終于終結了,曆時一天半,過程雖然心酸,但收獲了許多關于視訊方面相關的知識。部落客在搜集資料的同時,看到了很多像部落客一樣對該問題的困擾,故将詳細經曆總結出來,希望不要再有人步部落客後塵了。完畢,收工。