资讯专栏INFORMATION COLUMN

【Android音视频开发】从AVFrame到MediaFrame数组(二)

imtianx / 2651人阅读

摘要:实现从到的转换。注意哦,这时候,的值是一个随机的值假定此时为,由系统分配,它还不指向像素数据。并且锁定该地址,保证不会被移动。链接到下一文从到数组三参考链接之操作

本文记录的是从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);

解释一下这两句话:

第一句的作用声明并定义一个指向任意类型的指针变量,名称是addr_pixels。我们定义它的目的,是让它指向bitmap像素数据(即: addr_pixels的值为bitmap像素数据的地址)。注意哦,这时候,addr_pixels的值是一个随机的值(假定此时为:0x01),由系统分配,它还不指向bitmap像素数据。

第二句话的作用就是将bitmap的像素数据地址赋值给addr_pixels,此时它的值被修改(假定为:0x002)。并且锁定该地址,保证不会被移动。【注:地址不会被移动这里我也不太懂什么意思,有兴趣的可以去查看该方法的API文档】

【注:】此时的bitmap由像素数据的地址,但是该地址内还没有任何像素数据哦,或者说它的像素数据为

到这里,我们已经有了源像素数据在AVFrame中,有了目的像素数据地址addr_pixels,那么接下来的任务就是将AVFrame中的像素数据写入到addr_pixels指向的那片内存中去。

向Bitmap中写入像素数据

这里要说一下,我们获取到的AVFrame的像素格式通常是YUV格式的,而Bitmap的像素格式通常是RGB格式的。因此我们需要将YUV格式的像素数据转换成RGB格式进行存储。而RGB的存储空间Bitmap不是已经给我门提供好了吗?嘿嘿,直接用就OK了,那现在问题就是YUV如何转换成RGB呢?
关于YUV和RGB之间的转换,我知道的有三种方式:

通过公式换算

FFmpeg提供的libswscale

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

解释一下这个函数:

I420ToABGR: I420表示的是YUV420P格式,ABGR表示的RGBA格式(execuse me?? 是的,你没看错,Google说RGBA格式的数据在底层的存储方式是ABGR,顺序反过来,看下libyuv源码的函数注释就知道了)

frame->data&linesize: 这些个参数表示的是源YUV数据,上面有标注

(uint8_t *) addr_pixels: 嘿,这个就是说往这块空间里写入像素数据啦

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层

有一个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);

同样也解释一下:

FindClass: 找到JNIHelper类

GetMethodID: 找到JNIHelper类中的void onReceived(Bitmap bitmap)方法

CallVoidMethod: 刚开始我们创建的bitmap对象,作为参数,执行onReceived

至此,从AVFrame到Bitmap,再将Bitmap上传,就已经完成了。

链接到下一文:《从AVFrame到MediaFrame数组(三)》

参考链接

Android JNI 之 Bitmap操作:https://juejin.im/post/5b5810...

Bitmap | Android NDK:https://developer.android.com...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/14482.html

相关文章

  • Android视频开发AVFrameMediaFrame数组(一)

    摘要:最近在学习中的音视频开发,从到数组是一个学习的记录它共分为个部分一是对从到数组所实现的功能以及涉及到知识点的介绍二从到层的三从到层自定义的实体类四从到层自定义的实体类的数组简介目的输入一个视频素材输出屏幕上的画面分析用户将一个视频文件传递到 最近在学习Android中的音视频开发,《从AVFrame到MediaFrame数组》是一个学习的记录它共分为4个部分:(一):是对《从AVFra...

    didikee 评论0 收藏0
  • Android视频开发AVFrameMediaFrame数组(三)

    摘要:回调接口至此,从到,再将上传,就已经结束了。链接到下一文从到数组四 本文记录的是从AVFrame到自定义实体类MediaFrame的过程 目的 前提:假定你已经看完了《从AVFrame到MediaFrame数组(二)》,学会了Bitmap的创建 实现从AVFrame到自定义实体类MediaFrame的过程 Java层创建MediaFrame实体类 直接看代码: // 假定全路径名:m...

    frontoldman 评论0 收藏0
  • Android视频开发AVFrameMediaFrame数组(四)

    摘要:本文记录的是从到数组的实现过程目的前提假定你已经看完了从到数组三,学会了的创建实现从到自定义实体类数组的过程修改层回调接口中添加一个方法层接收到后,可以开始搞事情了封装数组层封装数组,分为步创建数组对象数组大小元素类型元素初始化值给数组 本文记录的是从AVFrame到MediaFrame数组的实现过程 目的 前提:假定你已经看完了《从AVFrame到MediaFrame数组(三)》,学...

    ?xiaoxiao, 评论0 收藏0
  • Android 视频开发核心知识点笔记整合

    摘要:这里给大家推荐一套学习路线,并附有相关音视频开发核心知识点笔记,相信可以给大家提供一些帮助,有需要的朋友们也可以下载下来随时查漏补缺。 很多开发者都知道Androi...

    lily_wang 评论0 收藏0
  • 5步告诉你QQ音乐的完美音质是怎么来的,播放器的秘密都在这里

    摘要:音频解码在中对中的数据进行解码解码后的帧存放到中音频播放中,通过调用回调接口,对中的音频帧数据进行解码成数据写入数据到数组,并由播放。对进行修改,将数据额外输出到本地,并与正常的数据进行对比。欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由QQ音乐技术团队发表于云+社区专栏 一、问题背景与分析 不久前,团队发现其Android平台App在播放MV视频《凤凰花开的路口》时...

    voidking 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<