PGzxc 2019-06-28
实现从AVFrame到Bitmap的转换。
底层创建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);
void *addr_pixels; AndroidBitmap_lockPixels(env, bitmap, &addr_pixels);
解释一下这两句话:
【注:】此时的bitmap由像素数据的地址,但是该地址内还没有任何像素数据哦,或者说它的像素数据为\0
到这里,我们已经有了源像素数据在AVFrame中,有了目的像素数据地址addr_pixels,那么接下来的任务就是将AVFrame中的像素数据写入到addr_pixels指向的那片内存中去。
这里要说一下,我们获取到的AVFrame的像素格式通常是YUV格式的,而Bitmap的像素格式通常是RGB格式的。因此我们需要将YUV格式的像素数据转换成RGB格式进行存储。而RGB的存储空间Bitmap不是已经给我门提供好了吗?嘿嘿,直接用就OK了,那现在问题就是YUV如何转换成RGB呢?
关于YUV和RGB之间的转换,我知道的有三种方式:
这里我们选择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);
解释一下这个函数:
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层
有一个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数组(三)》