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