Android录制视频-nv21转nv12姿势(libyuv使用)

这篇具有很好参考价值的文章主要介绍了Android录制视频-nv21转nv12姿势(libyuv使用)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Android录制视频-nv21转nv12姿势(libyuv使用)

前言

近期接触项目当中的视频录制项目,测试人员提出了一个bug:说项目录制的视频,会断断续续,每隔一段时间就会停住,然后再继续播放。当前项目是采用视频,音频分轨录制,然后再用ffmpeg合成视频。经过走代码逻辑,发现出现bug的原因是因为缺少视频帧数,定义的合成帧数以及录制帧数是24,但是在yuv数据转化的过程中,耗时间比较长,导致了丢帧的情况。因为目前项目里面所有的转化都是通过 java 方法转化而成。So,现在需要采用google的libyuv进行格式转换,提高速度。

为什么需要转化?

那么有些聪明的小明就要问了,直接拿到yuv数据录制不就好了吗,为什么需要转化呢?
当然不行,转化是必须的。因为nv21是Android摄像头返回的数据。我们是通过MediaCodec生成H.264文件。MediaCodec使用需要创建并设置好 MediaFormat 对象,而MediaFormat 使用的是 COLOR_FormatYUV420SemiPlanar,也就是 NV12 模式,那么就得做一个转换,把 NV21 转换到 NV12 。

具体使用代码:

        MediaFormat mediaFormat;
        if (rotation == 90 || rotation == 270) {
            //设置视频宽高
            mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, videoHeight, videoWidth);
        } else {
            mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, videoWidth, videoHeight);
        }
        //图像数据格式 YUV420(nv12)
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, 
        MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
        //码率
        Log.d("输出视频", "码率" + videoWidth * videoHeight);
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, videoWidth * videoHeight);
        //每秒30帧
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, video_frameRate);
        //1秒一个关键帧
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
        videoMediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
        videoMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        videoMediaCodec.start();

关于格式的区别以及Android MediaCodec的了解,可以跳转观看,本文不再描述:
音视频基础知识—像素格式YUV
Android音视频—YUV格式深入浅出
Android MediaCodec 硬编码 H264 文件

libyuv简介

libyuv是Google开源的实现各种YUV与RGB之间相互转换、旋转、缩放的库。它是跨平台的,可在Windows、Linux、Mac、Android等操作系统,x86、x64、arm架构上进行编译运行,支持SSE、AVX、NEON等SIMD指令加速。

实际使用

对于Android来说,我们不能直接使用libyuv,需要集成到项目当中或者编译so动态库才能使用。

1.集成

为了方便,我们直接集成已经实现了的项目里面的module。

LibyuvDemo
Android录制视频-nv21转nv12姿势(libyuv使用)

这个老哥已经把libyuv 集成并且弄成了一个module,我们只需要下载源码,然后再自己的项目里面添加module就可以直接使用啦!!!但是需要注意的是这个项目里面的YuvUtil的 compressYUV 方法有误,后续需要修改下(后面会说)

当我们下载好了源码之后,在我们项目里面通过AndroidStudio添加module。
Android录制视频-nv21转nv12姿势(libyuv使用)
添加刚刚下载的源码,里面的libyuv路径,直接finish,剩下的就交给AndroidStudio了
Android录制视频-nv21转nv12姿势(libyuv使用)

添加完毕之后,项目里就多了一个module,到这里,基本上就完成了集成。
Android录制视频-nv21转nv12姿势(libyuv使用)

2.修改代码

前面集成的时候说过,compressYUV方法是有错误的,我们需要修改一下,直接把下面的代码替换掉 compressYUV方法里面的代码就 阔以鸟~~~

extern "C"
JNIEXPORT void JNICALL
Java_com_libyuv_util_YuvUtil_compressYUV(JNIEnv *env, jclass type,
                                         jbyteArray nv21Src, jint width,
                                         jint height, jbyteArray i420Dst,
                                         jint dst_width, jint dst_height,
                                         jint mode, jint degree,
                                         jboolean isMirror) {

    jbyte *src_nv21_data = env->GetByteArrayElements(nv21Src, NULL);
    jbyte *dst_i420_data = env->GetByteArrayElements(i420Dst, NULL);
    jbyte *tmp_dst_i420_data = NULL;

    // nv21转化为i420
    jbyte *i420_data = (jbyte *) malloc(sizeof(jbyte) * width * height * 3 / 2);
    nv21ToI420(src_nv21_data, width, height, i420_data);
    tmp_dst_i420_data = i420_data;

    // 镜像
    jbyte *i420_mirror_data = NULL;
    if(isMirror){
        i420_mirror_data = (jbyte *)malloc(sizeof(jbyte) * width * height * 3 / 2);
        mirrorI420(tmp_dst_i420_data, width, height, i420_mirror_data);
        tmp_dst_i420_data = i420_mirror_data;
    }

    // 缩放
    jbyte *i420_scale_data = NULL;
    if(width != dst_width || height != dst_height){
        i420_scale_data = (jbyte *)malloc(sizeof(jbyte) * width * height * 3 / 2);
        scaleI420(tmp_dst_i420_data, width, height, i420_scale_data, dst_width, dst_height, mode);
        tmp_dst_i420_data = i420_scale_data;
        width = dst_width;
        height = dst_height;
    }

    // 旋转
    jbyte *i420_rotate_data = NULL;
    if (degree == libyuv::kRotate90 || degree == libyuv::kRotate180 || degree == libyuv::kRotate270){
        i420_rotate_data = (jbyte *)malloc(sizeof(jbyte) * width * height * 3 / 2);
        rotateI420(tmp_dst_i420_data, width, height, i420_rotate_data, degree);
        tmp_dst_i420_data = i420_rotate_data;
    }

    // 同步数据
    // memcpy(dst_i420_data, tmp_dst_i420_data, sizeof(jbyte) * width * height * 3 / 2);
    jint len = env->GetArrayLength(i420Dst);
    memcpy(dst_i420_data, tmp_dst_i420_data, len);
    tmp_dst_i420_data = NULL;
    env->ReleaseByteArrayElements(i420Dst, dst_i420_data, 0);

    // 释放
    if(i420_data != NULL) free(i420_data);
    if(i420_mirror_data != NULL) free(i420_mirror_data);
    if(i420_scale_data != NULL) free(i420_scale_data);
    if(i420_rotate_data != NULL) free(i420_rotate_data);
}

这个方法会 针对我们传入的nv21数据,进行压缩,旋转并且返回yuvI420格式的数据,这对于我们下一步转换nv12有重要作用。参数说明定义方法YuvUtil说得很清楚。
Android录制视频-nv21转nv12姿势(libyuv使用)

3.添加方法

我们集成的libyuv里面,是没有 I420 转 nv12的方法的,这个方法需要我们自己动手完成。但是我们也没有写过C语言的代码哇。而且我们也不熟悉java与C的相互调用哇。那怎么办呢?俗话说,好记性不如烂笔头。我告诉你不然直接你自己去了解。所以我们只能先去了解一下了。Android的JNI开发全面介绍与最佳实践

篇幅很长,我们简单过一遍就可以,有兴趣的朋友可以详细推敲推敲。我们只需要知道我们需要在YuvUtils里面定义一个转换的方法。然后使用native关键字,这关键字就是java调用C的。然后在 C代码也就是YuvJni.cpp文件里面添加对应的方法(注意方法名需要指定具体位置,仿照前面的compressYUV方法的命名即可)

接着我们百度一下 I420转nv12的C端代码,然后我在SharryChoo/LibyuvSample里面找到了一段转换代码如下:

void LibyuvUtil::I420ToNV21(jbyte *src, jbyte *dst, int width, int height) {
    jint src_y_size = width * height;
    jint src_u_size = src_y_size >> 2;
    jbyte *src_y = src;
    jbyte *src_u = src + src_y_size;
    jbyte *src_v = src + src_y_size + src_u_size;

    jint dst_y_size = width * height;
    jbyte *dst_y = dst;
    jbyte *dst_vu = dst + dst_y_size;

    libyuv::I420ToNV21(
            (uint8_t *) src_y, width,
            (uint8_t *) src_u, width >> 1,
            (uint8_t *) src_v, width >> 1,
            (uint8_t *) dst_y, width,
            (uint8_t *) dst_vu, width,
            width, height
    );
}

但是我们还是需要修改一下方法的定义类型才可以让我们使用(仿照compressYUV方法)下面贴出修改后的代码:


// i420 --> nv12
extern "C"
JNIEXPORT void JNICALL
Java_com_libyuv_util_YuvUtil_yuvI420ToNV12(JNIEnv *env, jclass type, jbyteArray i420src,
                                           jbyteArray nv12Dst,
                                           jint width, jint height) {


    jbyte *src_i420_data = env->GetByteArrayElements(i420src, NULL);
    jbyte *src_nv12_data = env->GetByteArrayElements(nv12Dst, NULL);

    jint src_y_size = width * height;
    jint src_u_size = src_y_size >> 2;

    jbyte *src_y = src_i420_data;
    jbyte *src_u = src_i420_data + src_y_size;
    jbyte *src_v = src_i420_data + src_y_size + src_u_size;

    jint dst_y_size = width * height;
    jbyte *dst_y = src_nv12_data;
    jbyte *dst_uv = src_nv12_data + dst_y_size;

    libyuv::I420ToNV12(
            (uint8_t *) src_y, width,
            (uint8_t *) src_u, width >> 1,
            (uint8_t *) src_v, width >> 1,
            (uint8_t *) dst_y, width,
            (uint8_t *) dst_uv, width,
            width, height
    );
}

我们将上面的代码,复制到YuvJni.cpp文件里面,然后在YuvUtil里面定义一个方法:

  /**
     * 将I420转化为NV2
     *
     * @param i420Src 原始I420数据
     * @param nv12Dst 转化后的NV12数据
     * @param width   输出的宽
     * @param height  输出的高
     */
    public static native void yuvI420ToNV12(byte[] i420Src,byte[] nv12Dst,int width,int height);

到这里,我们的I420转nv12的方法,就已经添加完毕了。当然,如果还需要其他的方法,我们google一下转换代码,举一反三的往里面添加就好了。那么回到主题,nv21转nv12的姿势,到底是什么呢?

4.项目中使用

因为我们添加的是一个module,那么我们必须在项目的build.gradle里面添加module才能调用libyuv里面的东西。

dependencies {

    ···
    api project(":leo-libyuv")
    ···
}

然后在我需要使用转换的地方,添加关键的两行代码。


    /**
     * 编码视频
     * @param nv21 nv21 byte数组
     * @throws IOException 抛出IO异常
     */
    private void encodeVideo(byte[] nv21) throws IOException {
 
        //定义i420 byte数组
        byte[] yuvI420 = new byte[nv21.length];
        //定义我们需要的nv12数组
        byte[] nv12Result = new byte[nv21.length];
        //初始化(多次使用造成 anr,弃用)
        //YuvUtil.init(videoWidth, videoHeight, videoWidth, videoHeight);
        //将nv21转换成I420
        YuvUtil.yuvCompress(nv21, videoWidth, videoHeight, yuvI420, videoWidth, videoHeight, 0, rotation, isFrontCamera);
        //将I420转换成nv12
        YuvUtil.yuvI420ToNV12(yuvI420, nv12Result, videoWidth, videoHeight);

        //得到编码器的输入和输出流, 输入流写入源数据 输出流读取编码后的数据
        //得到要使用的缓存序列角标
        int inputIndex = videoMediaCodec.dequeueInputBuffer(TIMEOUT_USEC);
        if (inputIndex >= 0) {
            ByteBuffer inputBuffer = videoMediaCodec.getInputBuffer(inputIndex);
            inputBuffer.clear();
            //把要编码的数据添加进去
            inputBuffer.put(nv12Result);
            //塞到编码序列中, 等待MediaCodec编码
            videoMediaCodec.queueInputBuffer(inputIndex, 0, nv12Result.length, System.nanoTime() / 1000, 0);
        }

        ······编码h.264等等一系列操作。
   }

到这里,我们的nv21转nv12的所有姿势,就已经 恁 完毕了。

总结

姿势总共分为三步:

1.继承libyuv到我们的项目当中。
2.修改libyuv里面的方法,添加对应的I420转nv12的方法。
3.将module添加到我们自己的项目里调用相对应的方法。

当然,这种姿势是一个比较投机取巧的。有时间有精力的话,还是得好好推敲推敲关于libyuv、jni开发、yuv等等相关知识。

相关借鉴资料:
《Android音视频——Libyuv使用实战》
《音视频基础知识—像素格式YUV》
《Android音视频—YUV格式深入浅出》
《Android MediaCodec 硬编码 H264 文件》
《Android的JNI开发全面介绍与最佳实践》
《LibyuvDemo》
《SharryChoo/LibyuvSample》文章来源地址https://www.toymoban.com/news/detail-404896.html

到了这里,关于Android录制视频-nv21转nv12姿势(libyuv使用)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • OpenGL ES 渲染 NV21、NV12、I420、YV12、YUYV、UYVY、I444(建议收藏)

    本文主要讲解常见的 YUV 格式图像渲染方式,如果对 YUV 格式不是很熟悉的同学可以翻看旧文一文掌握 YUV 图像的基本处理,YUV 格式的介绍这里不再展开。 NV21、NV12 可以看成同一种结构,区别只是 uv 的交错排列顺序不同。 渲染 NV21/NV12 格式图像需要使用 2 个纹理,分别用于保

    2024年02月22日
    浏览(31)
  • yuv数据(nv12和nv21)和RGB数据之间转换的c++代码

    nv21 Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y V U V U V U V U nv21 Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y U V U V U V U V 主要就是UV的顺序不同,交互一下UV的位置就可以互换NV12和NV21. 一般手机等移动端的数据流格式都是yuv格式,而神经网络的输入一般都是rgb格式,所以需要进行转换,这里给出c++的代码示例。 cv

    2024年02月11日
    浏览(31)
  • 【Unity-Shader脚本】0基础学会通过用Unity-Shader脚本渲染图像数据(NV21,NV12,RGBA数据)详细教程--附demo,NV21测试图像,YUV图像查看器。

      最近有一个需求是需要我在Unity中将获取到的图像数据来展示在Unity的界面之中。功能其实很简单,熟悉Unity-Shader的小伙伴可能很快就可以做出来。然而我很少和图像的渲染打交道,基本上是0基础了,在做这个需求之前连Shader是什么都不知道。本文记录了自己做这个需求

    2024年02月03日
    浏览(39)
  • android nv21 转 yuv420sp

    上面两个函数的目标都是将NV21格式的数据转换为YUV420P格式,但是它们在处理U和V分量的方式上有所不同。 在第一个函数 NV21toYUV420P_1 中,U和V分量的处理方式是这样的:对于U分量,它从NV21数据的Y分量之后的每个奇数位置取数据;对于V分量,它从NV21数据的Y分量之后的每个偶

    2024年02月11日
    浏览(29)
  • android nv21数据用mediacodec编解码

    在 Android 中使用 MediaCodec 进行 NV21 编码和解码的过程如下: 编码 NV21 数据: 解码编码后的数据: 上述代码中的变量和参数需要根据你的实际情况进行调整。此外,NV21 格式的数据需要根据具体需要进行分割和处理传入编码器和解码器。

    2024年02月13日
    浏览(25)
  • Android和JNI交互 : 常见的图像格式转换 : NV21、RGBA、Bitmap等

    最近在使用 OpenCV 处理图片的时候,经常会遇到需要转换图像的情况,网上相关资料比较少,也不全,有时候得费劲老半天才能搞定。 自己踩了坑后,在这里记录下,都是我在项目中遇到的图像转化操作,是一些常用的图像格式转换操作。 具体包括: nv21、rgba、rgb 转换 OpenC

    2024年02月05日
    浏览(34)
  • Android JNI和原生交互,常见的图像格式转换 : NV21、RGBA、Bitmap等

    最近在使用 OpenCV 处理图片的时候,经常会遇到需要转换图像的情况,网上相关资料比较少,也不全,有时候得费劲老半天才能搞定。 自己踩了坑后,在这里记录下,都是我在项目中遇到的图像转化操作,是一些常用的图像格式转换操作。 具体包括: nv21、rgba、rgb 转换 OpenC

    2024年02月06日
    浏览(33)
  • 使用OpenCV将图像转换为NV12格式并加载NV12数据

    摘要:在新项目中,需要为上层应用开放几个接口,但又不想让上层应用过多依赖OpenCV。本文将详细介绍如何使用C++和OpenCV,通过加载图片并转换为NV12格式,实现对图像数据的处理,以及如何加载NV12数据并显示。这些步骤对于在相机等设备中处理YUV数据并与OpenCV进行无缝集

    2024年01月19日
    浏览(43)
  • android 调用系统录制视频和音频

    1、录制视频 请先申请相机权限 2、录制音频 请先申请权限

    2023年04月08日
    浏览(26)
  • c++ java rgb与nv21互转

    目录 jni函数 c++ rgb转nv21,可以转,不报错,但是转完只有黑白图 java yuv420保存图片,先转nv21,再保存ok: c++ yuv420月bgr互转,测试ok

    2024年02月11日
    浏览(27)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包