android 使用NativeWindow渲染RGB视频

Ndk中使用Mediacode解码
android mediacodec 编码demo(java)
NDK中使用mediacodec编码h264
Android native 层使用opengl渲染YUV420p和NV12
android 使用NativeWindow渲染RGB视频
opengl 叠加显示文字
android studio 编译freeType
最原始的yuv图像叠加文字的实现--手动操作像素

一. android native渲染图像的五种方式

1.0 java层canvas, 然后在native层修改canvas的bitmap缓存区
2.0 使用opengl
3.0 使用nativeWindow 直接渲染
4.0 使用mediacodec 解码时配置Surface,直接通过mediacodec的结构releasebuffer的时候渲染,不过这种方式并不是渲染视频源数据,必须使用mediacodec解码,有缺陷。
之前在分析vlc源码的过程中就列举过,vlc在android上视频的渲染就有两种途径,其中一个即使用nativeWindow渲染。《vlc-video解码后的输出路径
5.0 vulkan 渲染。(类似于opengl的显卡渲染接口,据说多线程渲染,比opengl效率更高,要在android 7.0 以上)


二:这里写个demo,使用nativeWindow直接渲染RGBA8888 视频序列

效果:

2.1需要一个RGBA8888格式的视频序列,我们用ffmpeg解码一个视频文件就可以得到

#ffmpeg -i native.mp4 -t 10 -pix_fmt rgba native_rgba.yuv
-i :input,输入文件
-t: 时间,10s
-pix_fmt: 颜色格式,rgba格式,可以使用 ffmpeg -pix_fmts 查看支持的格式

2.2之所以是渲染RGBA8888 而不是渲染YUV420p, 因为nativeWindow只支持以下格式:

/**
 * Legacy window pixel format names, kept for backwards compatibility.
 * New code and APIs should use AHARDWAREBUFFER_FORMAT_*.
 */
enum ANativeWindow_LegacyFormat {
    // NOTE: these values must match the values from graphics/common/x.x/types.hal

    /** Red: 8 bits, Green: 8 bits, Blue: 8 bits, Alpha: 8 bits. **/
    WINDOW_FORMAT_RGBA_8888          = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
    /** Red: 8 bits, Green: 8 bits, Blue: 8 bits, Unused: 8 bits. **/
    WINDOW_FORMAT_RGBX_8888          = AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM,
    /** Red: 5 bits, Green: 6 bits, Blue: 5 bits. **/
    WINDOW_FORMAT_RGB_565            = AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM,
};

以上枚举变量是android ndk21.1 native_window.h中的源码。

使用native_window渲染很简单,调用:

//1.0 设置缓存区几何形状
int32_t ANativeWindow_setBuffersGeometry(ANativeWindow* window,
        int32_t width, int32_t height, int32_t format);


while(1){
//2.0获取缓存区
int32_t ANativeWindow_lock(ANativeWindow* window, ANativeWindow_Buffer* outBuffer,
        ARect* inOutDirtyBounds);
//3.0 拷贝数据到缓冲区
memcpy(outBuffer.bits,buf,framelen);

//4.0 释放缓冲区(渲染)
int32_t ANativeWindow_unlockAndPost(ANativeWindow* window)
}

三:上代码

jni_NativewindRender.cpp
//canok 20210625
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include <jni.h>
#include<android/native_window.h>
#include<android/native_window_jni.h>
#include <pthread.h>
#include <unistd.h>
#include "logs.h"

struct RenderContext{
    char*file_in;
    bool bRun;
    ANativeWindow *pWind;
    int w;
    int h;
    int fps;
};
RenderContext gContext={0};

int64_t getNowUs(){
#if 1
    timeval tv;
    gettimeofday(&tv, 0);
    return (int64_t)tv.tv_sec * 1000000 + (int64_t)tv.tv_usec;
#else
    return ALooper::GetNowUs();
#endif
}
static char* jstringToChar(JNIEnv* env, jstring jstr) {
    char* rtn = NULL;
    jclass clsstring = env->FindClass("java/lang/String");
    jstring strencode = env->NewStringUTF("utf-8");
    jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
    jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
    jsize alen = env->GetArrayLength(barr);
    jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
    if (alen > 0) {
        rtn = (char*) malloc(alen + 1);
        memcpy(rtn, ba, alen);
        rtn[alen] = 0;
    }
    env->ReleaseByteArrayElements(barr, ba, 0);
    return rtn;
}

void *rendthread(void *pram){
    RenderContext *pContext = (RenderContext*)pram;
/*------------------*/
    //开始
    int32_t ret =0;
    //添加个引用,避免自动释放
    ANativeWindow_acquire(pContext->pWind);
    ret = ANativeWindow_setBuffersGeometry(pContext->pWind,pContext->w,pContext->h,WINDOW_FORMAT_RGBA_8888);
    if(ret !=0){
        ALOGD("[%s%d] setbufferGeometry err,ret:%d",__FUNCTION__ ,__LINE__,ret);
        return NULL;
    }
    int framelen = pContext->w*pContext->h*4;
    char*buf = (char*)malloc(framelen);
    if(buf ==NULL){
        ALOGD("[%s%d] malloc err",__FUNCTION__ ,__LINE__);
        return NULL;
    }

    FILE*fp_in = fopen(pContext->file_in,"r");
    if(fp_in == NULL){
        ALOGD("[%s%d] fopen err ,file:%s,errno:%d(%s)",__FUNCTION__ ,__LINE__,pContext->file_in,errno, strerror(errno));
        free(buf);
        return NULL;
    }

/*------------------*/
    //渲染
    ANativeWindow_Buffer outBuffer;
    int64_t duration = 1000*1000/pContext->fps;
    while(gContext.bRun){
        int64_t time1=getNowUs();
        ret = fread(buf,1,framelen,fp_in);
        if(ret <framelen){
            fseek(fp_in,SEEK_SET,0);
            ret = fread(buf,1,framelen,fp_in);
        }
        if(ret<framelen){
            ALOGD("[%s%d] fread err",__FUNCTION__ ,__LINE__);
            break;
        }

        ANativeWindow_lock(pContext->pWind, &outBuffer,0);
        memcpy(outBuffer.bits,buf,framelen);

        ANativeWindow_unlockAndPost(pContext->pWind);
        int64_t time2=getNowUs();
        int64_t taketimes=time2-time1;
        //休眠,控制帧率
        ALOGD("[%s%d] take times:%ld",__FUNCTION__ ,__LINE__,taketimes);
        usleep(duration-taketimes>0?duration-taketimes:0);

    }

/*------------------*/
    //结束
    //释放引用,
    ANativeWindow_release(pContext->pWind);

    free(buf);
    fclose(fp_in);
    return NULL;
}
void jStart(JNIEnv *env, jobject obj,jobject jsurface, jstring srcfile, jint w, jint h, int fps){
    gContext.file_in=jstringToChar(env,srcfile);
    gContext.pWind = ANativeWindow_fromSurface(env, jsurface);
    gContext.w=w;
    gContext.h=h;
    gContext.fps=fps;
    gContext.bRun = true;
    pthread_t pid;
   if(pthread_create(&pid,NULL,rendthread,(void*)&gContext)<0){
       ALOGD("[%s%d] pthread_create err\n",__FUNCTION__ ,__LINE__);
       return;
   }else{
       pthread_detach(pid);
   }

}

void jStop(JNIEnv *env, jobject obj){
    gContext.bRun = false;
    free(gContext.file_in);
}
static JNINativeMethod gMethods[] = {
        {"native_start",     "(Landroid/view/Surface;Ljava/lang/String;III)V",      (void*)jStart},
        {"native_stop",  "()V",   (void*)jStop},
};


static const char* const kClassPathName = "com/example/nativewindowrender/NativeWindowRender";
static int registerNativeMethods(JNIEnv* env
        , const char* className
        , JNINativeMethod* gMethods, int numMethods) {
    jclass clazz;
    clazz = env->FindClass(className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
        return JNI_FALSE;
    }
    return JNI_TRUE;
}
// This function only registers the native methods
static int registerFunctios(JNIEnv *env)
{
    ALOGD("register [%s]%d",__FUNCTION__,__LINE__);
    return registerNativeMethods(env,
                                 kClassPathName, gMethods, sizeof(gMethods)/sizeof(gMethods[0]));
}

jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    ALOGD("onloader");
    JNIEnv* env = NULL;
    jint result = -1;
    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        ALOGD("ERROR: GetEnv failed\n");
        goto bail;
    }
    assert(env != NULL);
    if (registerFunctios(env) < 0) {
        ALOGE(" onloader ERROR: MediaPlayer native registration failed\n");
        goto bail;
    }
    ALOGD("onloader register ok ![%s]%d",__FUNCTION__,__LINE__);
    result = JNI_VERSION_1_4;
    bail:
    return result;
}
package com.example.nativewindowrender;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
    private static String TAG = "MainActivity";
    private SurfaceView mView;
    private Surface mSurface;
    private Button mButtonPlay;
    private NativeWindowRender mRender;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        verifyStoragePermissions(this);

        mView = findViewById(R.id.surface);
        SurfaceHolder holder = mView.getHolder();
        holder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                // TODO Auto-generated method stub
                Log.i("SURFACE","destroyed");
                if(mRender!=null){
                    mRender.stop();
                }
            }

            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                // TODO Auto-generated method stub
                Log.i("SURFACE","create");
                mSurface =  holder.getSurface();
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width,
                                       int height) {
                // TODO Auto-generated method stub
            }
        });
        mButtonPlay = findViewById(R.id.button);
        mButtonPlay.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(mSurface!=null && mRender==null) {
                    mRender = new NativeWindowRender(mSurface);
                    mRender.start("/storage/emulated/0/native_1280x720_rgba.yuv",1280,720,25);
                }
            }
        });
    }



    private static final int REQUEST_EXTERNAL_STORAGE = 1;
    private static String[] PERMISSIONS_STORAGE = {
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE};
    public static boolean verifyStoragePermissions(Activity activity) {

        if(Build.VERSION.SDK_INT < 23)
        {
            Log.d(TAG, "verifyStoragePermissions: <23");
            return true;
        }
        // Check if we have write permission
        int permission = ActivityCompat.checkSelfPermission(activity,
                Manifest.permission.RECORD_AUDIO);
        if (permission != PackageManager.PERMISSION_GRANTED) {
            // We don't have permission so prompt the user
            ActivityCompat.requestPermissions(activity,PERMISSIONS_STORAGE,
                    REQUEST_EXTERNAL_STORAGE);
            return false;
        }
        else
        {
            return true;
        }
    }
}
package com.example.nativewindowrender;

import android.view.Surface;

public class NativeWindowRender {
    private Surface mSurface;
    static {
        System.loadLibrary("nativeWind");
    }
    NativeWindowRender(Surface surface){
        mSurface = surface;
    }

    public void start(String file,int w, int h, int fps){
        native_start(mSurface,file,w,h,fps);
    }
    public void stop(){
        native_stop();
    }
    private native void native_start(Surface surface,String file,int w, int h, int fps);
    private native void native_stop();
}
//
// Created by xiancan.wang on 6/25/21.
//

#ifndef __MY_LOGS_HEADER__
#define __MY_LOGS_HEADER__
#ifdef __cplusplus
extern "C" {
#endif
#include <android/log.h>
// 宏定义类似java 层的定义,不同级别的Log LOGI, LOGD, LOGW, LOGE, LOGF。 对就Java中的 Log.i log.d
#define LOG_TAG    "JNILOG" // 这个是自定义的LOG的标识
//#undef LOG // 取消默认的LOG
#define ALOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__)
#define ALOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__)
#define ALOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG, __VA_ARGS__)
#define ALOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG, __VA_ARGS__)
#define ALOGF(...)  __android_log_print(ANDROID_LOG_FATAL,LOG_TAG, __VA_ARGS__)

#ifdef __cplusplus
}
#endif

#endif
Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE    := nativeWind
LOCAL_SRC_FILES :=  jni_NativewindRender.cpp
LOCAL_LDLIBS +=  -llog -landroid

include $(BUILD_SHARED_LIBRARY)

  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值