從AVFrame到MediaFrame數組(二)

本文記錄的是從AVFrame到Bitmap的實現過程,爲了突出重點,FFmpeg解碼視頻文件得到AVFrame的過程不在這裏記錄,如需要了解,可以看下【Samples】demuxing_decoding

目的

前提:假定我們已經通過FFmpeg解碼視頻文件獲取到AVFrame了。

實現從AVFrame到Bitmap的轉換。

Native層創建Bitmap

這個bitmap也可以由Java層傳遞過來,不過我們這裏假設Java層只給了我們一個視頻文件的路徑。

底層創建Bitmap,也是一寫JNI方面的操作了,這裏給出提供一個create_bitmap函數:

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對象:

jobject bitmap  = create_bimap(env, frame->width, frame->height);

獲取Bitmap像素數據地址,並鎖定

void *addr_pixels;
AndroidBitmap_lockPixels(env, bitmap, &addr_pixels);

解釋一下這兩句話:

  1. 第一句的作用聲明並定義一個指向任意類型的指針變量,名稱是addr_pixels。我們定義它的目的,是讓它指向bitmap像素數據(即: addr_pixels的值爲bitmap像素數據的地址)。注意哦,這時候,addr_pixels的值是一個隨機的值(假定此時爲:0x01),由系統分配,它還不指向bitmap像素數據。
  2. 第二句話的作用就是將bitmap的像素數據地址賦值給addr_pixels,此時它的值被修改(假定爲:0x002)。並且鎖定該地址,保證不會被移動。【注:地址不會被移動這裏我也不太懂什麼意思,有興趣的可以去查看該方法的API文檔】

【注:】此時的bitmap由像素數據的地址,但是該地址內還沒有任何像素數據哦,或者說它的像素數據爲\0

到這裏,我們已經有了源像素數據在AVFrame中,有了目的像素數據地址addr_pixels,那麼接下來的任務就是將AVFrame中的像素數據寫入到addr_pixels指向的那片內存中去。

向Bitmap中寫入像素數據

這裏要說一下,我們獲取到的AVFrame的像素格式通常是YUV格式的,而Bitmap的像素格式通常是RGB格式的。因此我們需要將YUV格式的像素數據轉換成RGB格式進行存儲。而RGB的存儲空間Bitmap不是已經給我門提供好了嗎?嘿嘿,直接用就OK了,那現在問題就是YUV如何轉換成RGB呢?
關於YUV和RGB之間的轉換,我知道的有三種方式:

  1. 通過公式換算
  2. FFmpeg提供的libswscale
  3. Google提供的libyuv

這裏我們選擇libyuv因爲它的性能好、使用簡單。

說它使用簡單,到底有多簡單,嘿,一個函數就夠了!!

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);

解釋一下這個函數:

  1. I420ToABGR: I420表示的是YUV420P格式,ABGR表示的RGBA格式(execuse me?? 是的,你沒看錯,Google說RGBA格式的數據在底層的存儲方式是ABGR,順序反過來,看下libyuv源碼的函數註釋就知道了)
  2. frame->data&linesize: 這些個參數表示的是源YUV數據,上面有標註
  3. (uint8_t *) addr_pixels: 嘿,這個就是說往這塊空間裏寫入像素數據啦
  4. 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層

Native回調Java接口

說下Java層

  1. 有一個MainActivity.java用於界面的顯示
  2. 有一個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);

同樣也解釋一下:

  1. FindClass: 找到JNIHelper類
  2. GetMethodID: 找到JNIHelper類中的void onReceived(Bitmap bitmap)方法
  3. CallVoidMethod: 剛開始我們創建的bitmap對象,作爲參數,執行onReceived

至此,從AVFrame到Bitmap,再將Bitmap上傳,就已經完成了。

參考鏈接

  1. Android JNI 之 Bitmap操作:https://juejin.im/post/5b5810...
  2. Bitmap | Android NDK:https://developer.android.com...
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章