Libyuv之初体验

应用场景

最近在接入腾讯实时音视频,我们的应用场景是主叫向被叫推送外部采集的视频数据,主、被叫端的视频界面都得实现缩放、标注功能;音频传输使用SDK默认的就好了;
在实际开发中发现主叫还好做,被叫端就坑爹了,收到的数据是yuv i420格式的数据,根本无法直接转成bitmap显示到容器上。后面找到一个java方法去i420转nv21,然后再生成bitmap数据显示出来,但是这样每帧数据的转换时间再80ms左右,根本达不到要求,这下就尴尬了。
最后没办法了,只能在JNI上想办法,后面通过libyuv库达到了要求,平均每帧的转换时间再20ms的样子,在这种效率下就不会出现视频卡顿的情况了。

Java转换代码

若只转换单个图片,或对性能没有太大要求的话,使用java代码进行转换就可以满足要求了。

/**
 * 使用java代码方式将I420数据转换成NV21数据,转换后,颜色、格式都是OK的
 * 但是转换过程时间太长,平均时间再80ms左右,无法满足视频的要求
 *
 * @param data   i420类型的byte[]数据
 * @param width  图片宽度
 * @param height 图片高度
 * @return
 */
public Bitmap I420ToNv21(byte[] data, int width, int height) {
    long preTim = System.currentTimeMillis();
    long tagTime = System.currentTimeMillis();

    final int frameSize = width * height;   //bufferY
    final int qFrameSize = frameSize / 4;   //bufferV
    final int tempFrameSize = frameSize * 5 / 4;    //bufferU

    byte[] ret = new byte[data.length];
    System.arraycopy(data, 0, ret, 0, frameSize); // Y

    for (int i = 0; i < qFrameSize; i++) {
        ret[frameSize + i * 2] = data[tempFrameSize + i]; // Cb (U)
        ret[frameSize + i * 2 + 1] = data[frameSize + i]; // Cr (V)
    }

    long i420ToNV21 = System.currentTimeMillis() - tagTime;

    Bitmap bitmap = null;
    try {
        tagTime = System.currentTimeMillis();

        //调用这两个方法,生成bitmap的帧率都在10帧左右,无法满足要求
        bitmap = nv21ToBitmapByArgb(ret, width, height); //使用数组创建bitmap
		//bitmap = nv21ToBitmapByYuvImage(ret, width, height);//使用yuvImage方式

        long bitmapTime = System.currentTimeMillis() - tagTime;
        showLog("I420转Nv21时间:" + i420ToNV21 + " bitmap时间:" + bitmapTime);
    } catch (Exception e) {
        e.printStackTrace();
    }

    showLog("生成 bimtp时间:" + (System.currentTimeMillis() - preTim) + "  文件大小:" + bitmap.getRowBytes());
    return bitmap;
}

/**
 * 将nv21数据通过数组变换的方式转换成bitMap
 *
 * @param nv21
 * @param width
 * @param height
 * @return
 */
private Bitmap nv21ToBitmapByArgb(byte[] nv21, int width, int height) {
    if (yuvType == null) {
        yuvType = new Type.Builder(rs, Element.U8(rs)).setX(nv21.length);
        in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT);

        rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height);
        out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT);
    }

    in.copyFrom(nv21);

    yuvToRgbIntrinsic.setInput(in);
    yuvToRgbIntrinsic.forEach(out);

    Bitmap bmpout = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    out.copyTo(bmpout);

    return bmpout;
}

/**
 * 将nv21数据通过YuvImage的方式转换成bitmap
 *
 * @param nv21
 * @param width
 * @param height
 * @return
 */
private Bitmap nv21ToBitmapByYuvImage(byte[] nv21, int width, int height) {
    Bitmap bitmap = null;
    try {
        YuvImage image = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        image.compressToJpeg(new Rect(0, 0, width, height), 80, stream);
        bitmap = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());
        stream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return bitmap;
}

Libyuv

libyuv是Google开源的实现各种YUV与RGB之间相互转换、旋转、缩放的库。它是跨平台的,可在Windows、Linux、Mac、Android等操作系统,x86、x64、arm架构上进行编译运行,支持SSE、AVX、NEON等SIMD指令加速。总而言之就是,在图像处理方面牛逼得不行。
官方源码,科学上网方可获取,我的网络太慢了,下载不下来。

JNI环境搭建

目前Android使用NDK有两种方式Android.mk和Cmake.txt两种方式,Cmake是官方比较推荐的方式,在配置方面会比Android.mk方式简单很多。
网上关于JNI环境搭建、这两种方式如何去配置已经有很多很详细的博文了,这里我列出我在开发过程中所搜集到的,比较详细全面的博文列举出来:

  • Android.mk方式:
    博文:Android Studio–NDK编译C代码为.so文件,JNI调用此博文在以下几点做了详细说明:

    1. JNI环境搭建
    2. 如何在一个已有项目中加入传统的JNI代码编译方式如何在一个已有项目中加入传统的JNI代码编译方式
    3. 手动编译SO包,项目每次运行的时候不用编译SO,只有在修改了源码,需要更新时候在手动去编译新的SO,有利于加快run的速度手动编译SO包,项目每次运行的时候不用编译SO,只有在修改了源码,需要更新时候在手动去编译新的SO,有利于加快run的速度
  • Cmake.txt方式:

    1. NDK环境搭建
    2. 在原有旧项目中引入cmake方式运行JNI
    3. 此文章对cmake中的参数、native生成、第三方so、h文件引入等问题有详细说明,值得一看;
    1. cmke与传统JNI做了对比说明
    2. 普通项目加入传统、cmake两种方式做了说明普通项目加入传统、cmake两种方式做了说明

I420数据转换

在搜索Libyuv使用方法的时候,找到一位博主写的Demo跟我们的需求无限接近,于是我们就愉快的引用博主的Demo,在此基础上来做修改了,libyuv—libyuv测试使用ARGBToI420和ConvertToARGB接口
其实从博文的描述上看,我几乎可以不用改代码,直接用这个里面的源码既可以,但是在实际开发过程中发现,demo里面将一张图片转成i420,然后将i420转argb显示为bitmap是没有问题;可是从腾讯实时音视频里面获取到的i420数据转argb的时候,发现转换之后颜色不对了,通过使用工具查看对比发现,貌似将其转换成了YV12的数据;
到现在还未找到原因,希望有同样需求的童鞋可以指点一下;
后面从这篇博文(使用libyuv对YUV数据进行缩放,旋转,镜像,裁剪等操作)中收到启发,可以将i420转成NV21的数据,然后再转成argb显示到容器中,经过这样的一顿操作,可算是将音视频的i420数据转成了我们需要的bitmap数据,虽然绕了一下,但是生成每帧画面的时间还是在20ms左右,并不会影响到我们的视觉体验。
到此,卡了N久的数据转换问题总算是基本搞定了。

Yuv原始数据查看

在i420数据转换过程中,只是通过效果看到颜色不对,但是对于数据转成了哪种格式,从结果中无法看出。这时我们可以把yuv原始数据保存为.yuv的文件,然后用YUV格式查看器RawViewer(并不是我上传的)这个工具去打开源文件,就可以分析出转换后的数据格式、宽高等信息。

/**
 *	保存yuv文件
 * @param name
 * @param buffer
 */
private void writeToFile(String name, ByteBuffer buffer) {
    try {
        String path = "/sdcard/" + name + ".yuv";
        FileOutputStream fos = new FileOutputStream(path, true);
        byte[] in = new byte[buffer.capacity()];
        buffer.clear();
        buffer.get(in);
        fos.write(in);
        fos.close();
    } catch (Exception ex) {
        ex.printStackTrace();
    }
}

SO引用

在项目中JNI开发好了之后,我们一般是不会变动的,若每次跑项目的时候,还需要去编译生成SO包,就太费时间了;
在此我给Demo中的项目改成了静态引用的libs里面的so的方式,我们只需要有修改的时候去更新一下SO即可。
SO包引用的两种方式:动态编译、静态引用。

动态编译

这种模式下面,每次跑项目的时候都会去重新生成新的SO包,在module的gradle中做如下配置:

android{
    externalNativeBuild {
        ndkBuild {
            path "jni/Android.mk"
        }
    }
    defaultConfig {
        externalNativeBuild {
            ndkBuild {
                arguments "NDK_APPLICATION_MK:=jni/Application.mk", "APP_PLATFORM:=android-14"
                abiFilters "armeabi-v7a"
            }
        }
    }
}

静态引用

每次更新代码之后,都需要手动执行ndk-build命令去生成SO包,然后引用,这样在没有更新到JNI相关内容的时候就无需跑JNI代码,节约时间。同样在module的gralde中配置。

android {
    defaultConfig {
        ndk {
            abiFilters 'armeabi', "armeabi-v7a"//, "x86", "mips"
        }
    }
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }
}

ndk-build命令问题

刚开始的时候我一直在项目的根目录执行ndk-build命令,发现一直报错,无法生成SO包;在设置好ndk环境变量之后,进入到Android.mk所在的根目录,我这里是jni文件夹下面,然后再执行ndk-build就OK了,解决办法
在这里插入图片描述

文档说明

Android.mk字段解释
Application.mk字段解释
ndk-build构建命令解释

最后附上Demo

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: Google Libyuv是一个开源的视频处理库。它提供了一些基本的功能,例如YUV格式的图像转换,缩放,旋转和裁剪等。Libyuv可以在多个平台上使用,包括Windows、Mac、Linux和Android等。 Libyuv支持多种YUV格式,如I420、NV12、NV21等,可以进行相互转换并且能够处理不同的像素格式。 除了常规的图像转换功能,Libyuv还提供了其他强大的功能。比如它可以在YUV图像上执行各种视频编辑操作,如降噪、锐化、模糊和亮度调整等。此外,Libyuv还提供了图像格式的缩放功能,可以按照指定的比例或绝对尺寸来调整图像的大小。 Libyuv的使用非常简便,只需包含对应的头文件和库文件即可。它采用C++编写,并且提供了方便的API接口,易于集成到现有的项目中。 Libyuv广泛应用于视频通信、视频播放器、视频编码器等领域。它能够处理实时视频数据,提供高效、快速的图像处理能力,极大地简化了开发者的工作。 总之,Google Libyuv是一个功能强大且易于使用的开源视频处理库,提供了丰富的功能和灵活的API接口,广泛应用于各种视频处理场景。 ### 回答2: Google libyuv是一个由Google开发的开源跨平台视频处理库。该库主要用于解决音视频编解码、图像处理等方面的问题。 Google libyuv库提供了一系列高效的算法和函数,旨在优化图像和视频的处理速度和质量。它支持多种常用的图像和视频格式,包括I420、NV12、ARGB等。用户可以通过调用libyuv库中提供的函数进行图像和视频的转换、裁剪、旋转、缩放、镜像等操作,以满足自己的需求。 同时,Google libyuv还提供了一些高级功能,如视频降噪、颜色空间转换以及支持将图像和视频编码为VP8和VP9等视频编码格式。通过使用libyuv库,用户能够更轻松地在视频处理过程中实现各种功能,以提高处理效率和优化视频质量。 除此之外,Google libyuv还具有跨平台的特点。它可以在多种操作系统上运行,包括Windows、Linux、macOS等。这使得开发者可以在不同的平台上使用相同的代码,方便了软件跨平台的开发和移植。 综上所述,Google libyuv是一个非常实用的视频处理库,它提供了丰富的功能和高效的算法,可以满足用户在图像处理和视频编解码方面的需求。无论是开发视频处理应用还是进行图像和视频处理的研究,libyuv库都是一个值得使用和推荐的工具。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值