Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 詳解Android開發之MP4文件轉GIF文件

詳解Android開發之MP4文件轉GIF文件

編輯:關於Android編程

一 基本實現原理

在介紹具體實現過程之前,先簡單說下基本原理和實現步驟,在解決相對比較復雜的問題,我習慣先理清主要原理步驟,不要一開始就被繁瑣細節絆住,待具體實現時再逐個攻破。下面是主要步驟:

     1、視頻文件的讀取:包括錄制和本地文件讀取 

     2、將需要轉換的視頻部分解析為 Bitmap 序列

     3、將解析好的 Bitmap 序列編碼生成 GIF 文件

二 視頻文件的讀取

視頻文件的讀取比較簡單,沒什麼特別需要說的地方,這裡簡單貼出視頻讀取的核心部分代碼,詳細實現可以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);
 }
 }
}

三 視頻文件的解析

視頻文件讀取成功後,接下來要做的就是解析視頻文件,選取需要轉換的視頻片段,提取Bitmap序列。下面來看下具體實現,提取 Bitmap 序列就是根據給定的起始時間和結束時間以及幀率從視頻文件中獲取相應的 Bitmap,本文主要是利用 MediaMetadataRetriever 提供的 API 來實現的,在看代碼前可以先看下 MediaMetadataRetriever 的 API 文檔,該類的核心功能就是獲取視頻的幀和元數據,下面是核心實現代碼:

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 序列中的數據按照 GIF 的文件格式編碼,生成最終的 GIF 文件。目標很明確,接下來就看具體實現過程了。

1. GIF 格式簡介

生成 GIF 文件之前有必要介紹下 GIF 的存儲格式,GIF 格式的相關文章比較多,這裡也沒必要太詳細的介紹,只是簡單說下後面程序中會用到的方面。

GIF 圖象是基於顏色列表的(存儲的數據是該點的顏色對應於顏色列表的索引值),最多只支持 8 位(256 色)。GIF 文件內部分成許多存儲塊,用來存儲多幅圖象或者是決定圖象表現行為的控制塊,用以實現動畫和交互式應用。GIF 文件還通過 LZW 壓縮算法壓縮圖象數據來減少圖象尺寸。

GIF 文件內部是按塊劃分的,包括控制塊和數據塊兩種。控制塊是控制數據塊行為的,根據不同的控制塊包含一些不同的控制參數;數據塊只包含一些 8-bit 的字符流,由它前面的控制塊來決定它的功能,每個數據塊 0 到 255 個字節,數據塊的第一個字節指出這個數據塊大小(字節數),計算數據塊的大小時不包括這個字節,所以一個空的數據塊有一個字節,那就是數據塊的大小0x00。

2. GIF 文件寫入

剛開始接觸 GIF 文件會覺得比較復雜,存儲格式、編碼格式等都比 Bitmap 要復雜的多,但其實可以把問題簡單化理解,生成 GIF 和生成 Bitmap 原理類似,就是按照規定的格式寫文件就行了,不用太糾結內部細節,否則就會陷入繁瑣的細節(俗稱鑽牛角尖)而忽略了最終目的只是為了生成 GIF 文件。下面就來看下有哪些文件部分需要寫入的:

提取 Bitmap 的像素值

首先需要將上面得到的 Bitmap 的像素值提取出來,方便後面把像素值寫入到 GIF 文件中,在提取像素值的同時,生成 GIF 文件所需要的顏色表,生成顏色表過程比較復雜,這裡就不貼出源碼,感興趣的可以Google一下顏色量化算法,不感興趣的直接用現成的就好,下面是提取像素值的具體實現:

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 個字節,包括:GIF 署名和版本號,GIF 署名由 3 個字符"GIF"組成,共 3 個字節,版本號也是由 3 個字節組成,可以為"87a"或"89a"(分別為 1987 年和 1989 年版本),實現代碼如下:

// 寫入文件頭
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));
 }
}

邏輯屏幕標識符(Logical Screen Descriptor)

文件頭的後面是邏輯屏幕標識符(Logical Screen Descriptor),這一部分由 7 個字節組成,定義了 GIF 圖象的大小、顏色深度、背景色以及有無全局顏色列表和顏色列表的索引數。實現代碼如下:

// 寫入邏輯屏幕標識符
protected void writeLSD() throws IOException {
 writeShort(width); // 寫入圖像寬度
 writeShort(height); // 寫入圖像高度
 
 out.write((0x80 | // 全局顏色列表標志置 1
    0x70 | // 確定圖象的顏色深度(7+1=8)
    0x00 | // 全局顏色列表分類排列置為 0
    0x07)); // 顏色列表的索引數(2的7+1次方)
 
 out.write(0); // 背景顏色(在全局顏色列表中的索引)
 out.write(0); // 像素寬高比默認 1:1
}
 
protected void writeShort(int value) throws IOException {
 out.write(value & 0xff);
 out.write((value >> 8) & 0xff);
}

邏輯屏幕標識符部分結構稍微復雜些,如果不知道每一位代表什麼意思可以參考:GIF圖形文件格式文檔 中的邏輯屏幕標識符部分。

全局顏色列表(Global Color Table)

全局顏色列表必須緊跟在邏輯屏幕標識符後面,每個顏色列表索引條目由三個字節組成,按R、G、B的順序排列,具體生成顏色表的實現可以看源碼部分,由於生成過程比較復雜,這裡就不貼顏色表生成的代碼了,下面是寫入顏色表的代碼:

// 寫入顏色表
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);
 }
}

圖形控制擴展(Graphic Control Extension)

這一部分是可選的,89a 版本才支持,可以放在一個圖象塊(包括圖象標識符、局部顏色列表和圖象數據)或文本擴展塊的前面,用來控制跟在它後面的第一個圖象(或文本)的渲染( Render )形式,下面實現代碼:

protected void writeGraphicCtrlExt() throws IOException {
 out.write(0x21); // 擴展塊標識,固定值 0x21
 out.write(0xf9); // 圖形控制擴展標簽,固定值 0xf9
 out.write(4); // 塊大小,固定值 4
 out.write(0 | // 1:3 保留位
   0 | // 4:6 不使用處置方法
   0 | // 7 用戶輸入標志置 0
   0); // 8 透明色標志置 0
 
 writeShort(delay); // 延遲時間
 out.write(0);  // 透明色索引值
 out.write(0);  // 塊終結器,固定值 0
}

圖象標識符(Image Descriptor)

一個 GIF 文件內可以包含多幅圖象,一幅圖象結束之後緊接著下是一幅圖象的標識符,圖象標識符以 0x2C(',')字符開始,定義緊接著它的圖象的性質,包括圖象相對於邏輯屏幕邊界的偏移量、圖象大小以及有無局部顏色列表和顏色列表大小,由10個字節組成,下面是實現代碼:

protected void writeImageDesc() throws IOException {
 out.write(0x2c); // 圖象標識符開始,固定值為 0x2c
 writeShort(0);  // x 方向偏移
 writeShort(0);  // y 方向偏移
 writeShort(width); // 圖像寬度
 writeShort(height); // 圖像高度
 out.write((
  0x80 |  // 局部顏色列表標志置 1
  0x00 |
  0x00 |
  0x07));  // 局部顏色列表的索引數(2的7+1次方)
}

圖象數據(Image Data)

GIF 圖象數據使用了 LZW 壓縮算法,大大減小了圖象數據的大小,具體的 LZW 壓縮算法可以Google一下,程序實現部分可以參考文章底部的源碼鏈接。下面是圖像數據的寫入實現:

protected void writePixels() throws IOException {
 LZWEncoder encoder = new LZWEncoder(
  width, height, indexedPixels, colorDepth);
 encoder.encode(out);
}

文件終結器(Trailer)

這一部分只有一個字節,標識一個GIF文件結束,固定值為 0x3B,實現代碼:

public void finish() throws IOException {
 out.write(0x3b);
 out.flush();
 out.close();
}

總結

到目前為止,將 MP4 文件轉換為 GIF 文件的實現過程基本完成,如果需要對 GIF 文件進行裁剪、添加水印等處理的話,可以在 Bitmap 序列寫入 GIF 之前,對 Bitmap 進行相應的處理即可,如果有什麼問題歡迎交流學習。希望本文的內容對大家的學習工作能有所幫助。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved