詳解Android開發(fā)之MP4文件轉(zhuǎn)GIF文件
一 基本實(shí)現(xiàn)原理
在介紹具體實(shí)現(xiàn)過程之前,先簡單說下基本原理和實(shí)現(xiàn)步驟,在解決相對比較復(fù)雜的問題,我習(xí)慣先理清主要原理步驟,不要一開始就被繁瑣細(xì)節(jié)絆住,待具體實(shí)現(xiàn)時(shí)再逐個(gè)攻破。下面是主要步驟:
1、視頻文件的讀?。喊ㄤ浿坪捅镜匚募x取
2、將需要轉(zhuǎn)換的視頻部分解析為 Bitmap 序列
3、將解析好的 Bitmap 序列編碼生成 GIF 文件
二 視頻文件的讀取
視頻文件的讀取比較簡單,沒什么特別需要說的地方,這里簡單貼出視頻讀取的核心部分代碼,詳細(xì)實(shí)現(xiàn)可以Google一下就行了。
private View.OnClickListener clickListener = new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(); intent.setType("video/*"); intent.setAction(Intent.ACTION_GET_CONTENT); startActivityForResult(Intent.createChooser(intent, "Select Video"), SELECT_VIDEO); } }; @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_SELECT_VIDEO) { if (resultCode == RESULT_OK) { Uri videoUri = data.getData(); filePath = getRealFilePath(videoUri); } } }
三 視頻文件的解析
視頻文件讀取成功后,接下來要做的就是解析視頻文件,選取需要轉(zhuǎn)換的視頻片段,提取Bitmap序列。下面來看下具體實(shí)現(xiàn),提取 Bitmap 序列就是根據(jù)給定的起始時(shí)間和結(jié)束時(shí)間以及幀率從視頻文件中獲取相應(yīng)的 Bitmap,本文主要是利用 MediaMetadataRetriever
提供的 API 來實(shí)現(xiàn)的,在看代碼前可以先看下 MediaMetadataRetriever
的 API 文檔,該類的核心功能就是獲取視頻的幀和元數(shù)據(jù),下面是核心實(shí)現(xiàn)代碼:
public List<Bitmap> createBitmaps(String path) { MediaMetadataRetriever mmr = new MediaMetadataRetriever(); mmr.setDataSource(path); double inc = 1000 * 1000 / fps; for (double i = begin; i < end; i += inc) { Bitmap frame = mmr.getFrameAtTime((long) i, MediaMetadataRetriever.OPTION_CLOSEST); if (frame != null) { bitmaps.add(scale(frame)); } } return bitmaps; } private Bitmap scale(Bitmap bitmap) { return Bitmap.createScaledBitmap(bitmap, width > 0 ? width : bitmap.getWidth(), height > 0 ? height : bitmap.getHeight(), true); }
四 生成 GIF 文件
拿到要生成 GIF 的 Bitmap 序列,接下來需要做的就是將 Bitmap 序列中的數(shù)據(jù)按照 GIF 的文件格式編碼,生成最終的 GIF 文件。目標(biāo)很明確,接下來就看具體實(shí)現(xiàn)過程了。
1. GIF 格式簡介
生成 GIF 文件之前有必要介紹下 GIF 的存儲格式,GIF 格式的相關(guān)文章比較多,這里也沒必要太詳細(xì)的介紹,只是簡單說下后面程序中會用到的方面。
GIF 圖象是基于顏色列表的(存儲的數(shù)據(jù)是該點(diǎn)的顏色對應(yīng)于顏色列表的索引值),最多只支持 8 位(256 色)。GIF 文件內(nèi)部分成許多存儲塊,用來存儲多幅圖象或者是決定圖象表現(xiàn)行為的控制塊,用以實(shí)現(xiàn)動畫和交互式應(yīng)用。GIF 文件還通過 LZW 壓縮算法壓縮圖象數(shù)據(jù)來減少圖象尺寸。
GIF 文件內(nèi)部是按塊劃分的,包括控制塊和數(shù)據(jù)塊兩種??刂茐K是控制數(shù)據(jù)塊行為的,根據(jù)不同的控制塊包含一些不同的控制參數(shù);數(shù)據(jù)塊只包含一些 8-bit 的字符流,由它前面的控制塊來決定它的功能,每個(gè)數(shù)據(jù)塊 0 到 255 個(gè)字節(jié),數(shù)據(jù)塊的第一個(gè)字節(jié)指出這個(gè)數(shù)據(jù)塊大?。ㄗ止?jié)數(shù)),計(jì)算數(shù)據(jù)塊的大小時(shí)不包括這個(gè)字節(jié),所以一個(gè)空的數(shù)據(jù)塊有一個(gè)字節(jié),那就是數(shù)據(jù)塊的大小0x00。
2. GIF 文件寫入
剛開始接觸 GIF 文件會覺得比較復(fù)雜,存儲格式、編碼格式等都比 Bitmap 要復(fù)雜的多,但其實(shí)可以把問題簡單化理解,生成 GIF 和生成 Bitmap 原理類似,就是按照規(guī)定的格式寫文件就行了,不用太糾結(jié)內(nèi)部細(xì)節(jié),否則就會陷入繁瑣的細(xì)節(jié)(俗稱鉆牛角尖)而忽略了最終目的只是為了生成 GIF 文件。下面就來看下有哪些文件部分需要寫入的:
提取 Bitmap 的像素值
首先需要將上面得到的 Bitmap 的像素值提取出來,方便后面把像素值寫入到 GIF 文件中,在提取像素值的同時(shí),生成 GIF 文件所需要的顏色表,生成顏色表過程比較復(fù)雜,這里就不貼出源碼,感興趣的可以Google一下顏色量化算法,不感興趣的直接用現(xiàn)成的就好,下面是提取像素值的具體實(shí)現(xiàn):
protected void getImagePixels() { int w = image.getWidth(); int h = image.getHeight(); pixels = new byte[w*h*3]; for (int i = 0; i < h; i++) { int stride = w * 3 * i; for (int j = 0; j < w; j++) { int p = image.getPixel(j, i); int step = j * 3; int offset = stride + step; // blue pixels[offset+0] = (byte) ((p & 0x0000FF) >> 0); // green pixels[offset+1] = (byte) ((p & 0x00FF00) >> 8); // red pixels[offset+2] = (byte) ((p & 0xFF0000) >> 16); } } }
GIF 文件頭(Header)
文件頭部分總共 6 個(gè)字節(jié),包括:GIF 署名和版本號,GIF 署名由 3 個(gè)字符"GIF"組成,共 3 個(gè)字節(jié),版本號也是由 3 個(gè)字節(jié)組成,可以為"87a"或"89a"(分別為 1987 年和 1989 年版本),實(shí)現(xiàn)代碼如下:
// 寫入文件頭 protected void writeHeader() throws IOException { writeString("GIF89a"); } protected void writeString(String s) throws IOException { for (int i = 0; i < s.length(); i++) { out.write((byte) s.charAt(i)); } }
邏輯屏幕標(biāo)識符(Logical Screen Descriptor)
文件頭的后面是邏輯屏幕標(biāo)識符(Logical Screen Descriptor),這一部分由 7 個(gè)字節(jié)組成,定義了 GIF 圖象的大小、顏色深度、背景色以及有無全局顏色列表和顏色列表的索引數(shù)。實(shí)現(xiàn)代碼如下:
// 寫入邏輯屏幕標(biāo)識符 protected void writeLSD() throws IOException { writeShort(width); // 寫入圖像寬度 writeShort(height); // 寫入圖像高度 out.write((0x80 | // 全局顏色列表標(biāo)志置 1 0x70 | // 確定圖象的顏色深度(7+1=8) 0x00 | // 全局顏色列表分類排列置為 0 0x07)); // 顏色列表的索引數(shù)(2的7+1次方) out.write(0); // 背景顏色(在全局顏色列表中的索引) out.write(0); // 像素寬高比默認(rèn) 1:1 } protected void writeShort(int value) throws IOException { out.write(value & 0xff); out.write((value >> 8) & 0xff); }
邏輯屏幕標(biāo)識符部分結(jié)構(gòu)稍微復(fù)雜些,如果不知道每一位代表什么意思可以參考:GIF圖形文件格式文檔 中的邏輯屏幕標(biāo)識符部分。
全局顏色列表(Global Color Table)
全局顏色列表必須緊跟在邏輯屏幕標(biāo)識符后面,每個(gè)顏色列表索引條目由三個(gè)字節(jié)組成,按R、G、B的順序排列,具體生成顏色表的實(shí)現(xiàn)可以看源碼部分,由于生成過程比較復(fù)雜,這里就不貼顏色表生成的代碼了,下面是寫入顏色表的代碼:
// 寫入顏色表 protected void writePalette() throws IOException { out.write(colorTab, 0, colorTab.length); int n = (3 * 256) - colorTab.length; for (int i = 0; i < n; i++) { out.write(0); } }
圖形控制擴(kuò)展(Graphic Control Extension)
這一部分是可選的,89a 版本才支持,可以放在一個(gè)圖象塊(包括圖象標(biāo)識符、局部顏色列表和圖象數(shù)據(jù))或文本擴(kuò)展塊的前面,用來控制跟在它后面的第一個(gè)圖象(或文本)的渲染( Render )形式,下面實(shí)現(xiàn)代碼:
protected void writeGraphicCtrlExt() throws IOException { out.write(0x21); // 擴(kuò)展塊標(biāo)識,固定值 0x21 out.write(0xf9); // 圖形控制擴(kuò)展標(biāo)簽,固定值 0xf9 out.write(4); // 塊大小,固定值 4 out.write(0 | // 1:3 保留位 0 | // 4:6 不使用處置方法 0 | // 7 用戶輸入標(biāo)志置 0 0); // 8 透明色標(biāo)志置 0 writeShort(delay); // 延遲時(shí)間 out.write(0); // 透明色索引值 out.write(0); // 塊終結(jié)器,固定值 0 }
圖象標(biāo)識符(Image Descriptor)
一個(gè) GIF 文件內(nèi)可以包含多幅圖象,一幅圖象結(jié)束之后緊接著下是一幅圖象的標(biāo)識符,圖象標(biāo)識符以 0x2C(',')字符開始,定義緊接著它的圖象的性質(zhì),包括圖象相對于邏輯屏幕邊界的偏移量、圖象大小以及有無局部顏色列表和顏色列表大小,由10個(gè)字節(jié)組成,下面是實(shí)現(xiàn)代碼:
protected void writeImageDesc() throws IOException { out.write(0x2c); // 圖象標(biāo)識符開始,固定值為 0x2c writeShort(0); // x 方向偏移 writeShort(0); // y 方向偏移 writeShort(width); // 圖像寬度 writeShort(height); // 圖像高度 out.write(( 0x80 | // 局部顏色列表標(biāo)志置 1 0x00 | 0x00 | 0x07)); // 局部顏色列表的索引數(shù)(2的7+1次方) }
圖象數(shù)據(jù)(Image Data)
GIF 圖象數(shù)據(jù)使用了 LZW 壓縮算法,大大減小了圖象數(shù)據(jù)的大小,具體的 LZW 壓縮算法可以Google一下,程序?qū)崿F(xiàn)部分可以參考文章底部的源碼鏈接。下面是圖像數(shù)據(jù)的寫入實(shí)現(xiàn):
protected void writePixels() throws IOException { LZWEncoder encoder = new LZWEncoder( width, height, indexedPixels, colorDepth); encoder.encode(out); }
文件終結(jié)器(Trailer)
這一部分只有一個(gè)字節(jié),標(biāo)識一個(gè)GIF文件結(jié)束,固定值為 0x3B,實(shí)現(xiàn)代碼:
public void finish() throws IOException { out.write(0x3b); out.flush(); out.close(); }
總結(jié)
到目前為止,將 MP4 文件轉(zhuǎn)換為 GIF 文件的實(shí)現(xiàn)過程基本完成,如果需要對 GIF 文件進(jìn)行裁剪、添加水印等處理的話,可以在 Bitmap 序列寫入 GIF 之前,對 Bitmap 進(jìn)行相應(yīng)的處理即可,如果有什么問題歡迎交流學(xué)習(xí)。希望本文的內(nèi)容對大家的學(xué)習(xí)工作能有所幫助。
- Android音頻錄制MediaRecorder之簡易的錄音軟件實(shí)現(xiàn)代碼
- Android簡單的利用MediaRecorder進(jìn)行錄音的實(shí)例代碼
- Android應(yīng)用開發(fā):電話監(jiān)聽和錄音代碼示例
- Android App調(diào)用MediaRecorder實(shí)現(xiàn)錄音功能的實(shí)例
- Android開發(fā)四大組件之實(shí)現(xiàn)電話攔截和電話錄音
- Android使用MediaRecorder實(shí)現(xiàn)錄音及播放
- Android錄音應(yīng)用實(shí)例教程
- 一個(gè)html5播放視頻的video控件只支持android的默認(rèn)格式mp4和3gp
- Android 使用VideoView播放MP4的簡單實(shí)現(xiàn)
- Android錄音并且輸出為Mp4文件的方法教程
相關(guān)文章
Android Studio開發(fā)之 JNI 篇的簡單示例
本篇文章主要介紹了Android Studio開發(fā)之 JNI 篇的簡單示例,它提供了若干的API實(shí)現(xiàn)了Java和其他語言的通信,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10Android實(shí)現(xiàn)短信、微信、微博分享功能
微信、微博分享功能大家都體驗(yàn)過吧,非常方便我們的生活,下面通過本文給大家介紹Android實(shí)現(xiàn)短信、微信、微博分享功能,需要的朋友參考下吧2017-12-12史上最全Android build.gradle配置詳解(小結(jié))
這篇文章主要介紹了史上最全Android build.gradle配置詳解(小結(jié)),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-04-04Android音視頻開發(fā)只硬件解碼組件MediaCodec講解
在Android開發(fā)中提供了實(shí)現(xiàn)音視頻編解碼工具M(jìn)ediaCodec,針對對應(yīng)音視頻解碼類型通過該類創(chuàng)建對應(yīng)解碼器就能實(shí)現(xiàn)對數(shù)據(jù)進(jìn)行解碼操作。本文通過示例詳細(xì)講解了MediaCodec的使用,需要的可以參考一下2023-01-01Android學(xué)習(xí)項(xiàng)目之簡易版微信為例(二)
這篇文章主要以簡易版微信為例,實(shí)現(xiàn)簡易版微信的登陸、注冊界面的編寫與簡單交互,感興趣的小伙伴們可以參考一下2016-06-06Android Studio設(shè)置或修改Android SDK路徑方法
在本篇文章中小編給大家整理了關(guān)于Android Studio設(shè)置或修改Android SDK路徑方法和相關(guān)知識點(diǎn),需要的朋友們學(xué)習(xí)下。2019-04-04詳談android 6.0 fuse文件系統(tǒng)的掛載和卸載問題
今天小編就為大家分享一篇詳談android 6.0 fuse文件系統(tǒng)的掛載和卸載問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08