本文記錄的是 從AVFrame到Bitmap的實現過程,爲了突出重點,FFmpeg解碼視頻文件獲得AVFrame的過程不在這裏記錄,如須要了解,能夠看下 【Samples】demuxing_decoding
前提:假定咱們已經經過FFmpeg解碼視頻文件獲取到AVFrame了。
實現從AVFrame到Bitmap的轉換。java
這個bitmap也能夠由Java層傳遞過來,不過咱們這裏假設Java層只給了咱們一個視頻文件的路徑。
底層建立Bitmap,也是一寫JNI方面的操做了,這裏給出提供一個create_bitmap函數:android
jobject create_bitmap(JNIEnv *env, int width, int height) { // 找到 Bitmap.class 和 該類中的 createBitmap 方法 jclass clz_bitmap = env->FindClass("android/graphics/Bitmap"); jmethodID mtd_bitmap = env->GetStaticMethodID( clz_bitmap, "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;"); // 配置 Bitmap jstring str_config = env->NewStringUTF("ARGB_8888"); jclass clz_config = env->FindClass("android/graphics/Bitmap$Config"); jmethodID mtd_config = env->GetStaticMethodID( clz_config, "valueOf", "(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;"); jobject obj_config = env->CallStaticObjectMethod(clz_config, mtd_config, str_config); // 建立 Bitmap 對象 jobject bitmap = env->CallStaticObjectMethod( clz_bitmap, mtd_bitmap, width, height, obj_config); return bitmap; }
而後,咱們調用該函數,獲取bimtap對象:git
jobject bitmap = create_bimap(env, frame->width, frame->height);
void *addr_pixels; AndroidBitmap_lockPixels(env, bitmap, &addr_pixels);
解釋一下這兩句話:github
【注:】此時的bitmap由像素數據的地址,可是該地址內尚未任何像素數據哦,或者說它的像素數據爲\0segmentfault
到這裏,咱們已經有了源像素數據在AVFrame中,有了目的像素數據地址addr_pixels,那麼接下來的任務就是將AVFrame中的像素數據寫入到addr_pixels指向的那片內存中去。數組
這裏要說一下,咱們獲取到的AVFrame的像素格式一般是YUV格式的,而Bitmap的像素格式一般是RGB格式的。所以咱們須要將YUV格式的像素數據轉換成RGB格式進行存儲。而RGB的存儲空間Bitmap不是已經給我門提供好了嗎?嘿嘿,直接用就OK了,那如今問題就是YUV如何轉換成RGB呢?
關於YUV和RGB之間的轉換,我知道的有三種方式:函數
- 經過公式換算
- FFmpeg提供的libswscale
- Google提供的libyuv
這裏咱們選擇libyuv由於它的性能好、使用簡單。post
說它使用簡單,到底有多簡單,嘿,一個函數就夠了!!性能
libyuv::I420ToABGR(frame->data[0], frame->linesize[0], // Y frame->data[1], frame->linesize[1], // U frame->data[2], frame->linesize[2], // V (uint8_t *) addr_pixels, linesize, // RGBA frame->width, frame->height);
解釋一下這個函數:ui
linesize: 這個表示的是該圖片一行數據的字節大小,Bitmap按照RBGA格式存儲,也就是說一個像素是4個字節,那麼一行共有:frame->width 個像素,因此:
linesize = frame-> width * 4
【注:】關於這一小塊功能的實現,可能其餘地方你會看到這樣的寫法,他們用了以下接口:
// 思路是:新建一個AVFrame(RGB格式),經過av_image_fill_arrays來實現AVFrame(RGB)中像素數據和Bitmap像素數據的關聯,也就是讓AVFrame(RGB)像素數據指針等於addr_pixels pRGBFrame = av_frame_alloc() av_image_get_buffer_size() av_image_fill_arrays() /* 我也是寫到這裏的時候,纔想到這個問題,爲何要這樣用呢,直接使用addr_pixels不是也同樣能夠麼? 不過你們都這麼用,應該是有它不可替代的使用場景的。所以這裏也說一下av_image_fill_arrays這個函數。 */ // TODO: 解釋下這個函數的做用 av_image_fill_arrays(dst_data, dst_linesize, src_data, pix_fmt, width, height, align); 它的做用就是 1. 根據src_data,設置dst_data,事實上根據現象或者本身去調試,能夠發現dst_data的值就是src_data的值(我印象中好像值是相同的,這會我忘了,後面我再驗證下) 2. 根據pix_fmt, width, height設置linesize的值,其實linesize的計算就和我上面給出的那個公式是同樣子的值
OK, 函數執行完畢,咱們Bitmap就有了像素數據,下面就是把Bitmap上傳給Java層
說下Java層
- 有一個MainActivity.java用於界面的顯示
有一個JNIHelper.java用於Java層和Native層的溝通
public class JNIHelper { public void onReceived(Bitmap bitmap){ // TODO: Java層接收到Bitmap後,能夠開始搞事情了 } }
Native層的回調代碼以下:
jclass clz = env->FindClass("me/oogh/xplayer/JNIHelper"); jmethodID method = env->GetMethodID(clz, "onReceived", "(Landroid/graphics/Bitmap;)V"); env->CallVoidMethod(obj, method, bitmap);
一樣也解釋一下:
至此,從AVFrame到Bitmap,再將Bitmap上傳,就已經完成了。
連接到下一文:《從AVFrame到MediaFrame數組(三)》