• Android使用ANativeWindow更新surfaceView内容最简Demo


    SurfaceView简介

    SurfaceView对比View的区别

            安卓的普通VIew,依赖当前ActivityWindowsurface这个surface用于承载view绘制出来所有内容因此任何一个view需要更新需要所有view进行更新即使使用区域依然其他view相应像素进行合成操作因此不适合频繁更新绘制而且更新过程只能UI线程。

            而SurfaceView自己Surface因此可以单独更新内容触发整个view更新在子线程刷新不会阻塞主线程,适用于界面频繁更新、对帧率要求较高的情况因此十分适合于视频渲染。而很多视频渲染库如FFMpeg,或者有时候一些开源OpenGL ES代码Native编写,如果要在折腾到Java层进行显示是非常麻烦的,所以SurfaceViewNative层面更新安卓图像处理输出一个必须技能

    SurfaceView的ANativeWindow的获取和使用

            Surface对象不能直接jni native使用因此需要通过Android NDK工具获取本地对象ANativeWindow

            这里借用一下大佬一张图表达SurfaceViewANativeWindow调用过程描述和流程图:

     

    ● java层将Surface传递给native层

    ● 获取ANativeWindow对象

    ● 将显示数据写到ANativeWindow的buffer中,注意需要将显示的数据格式转换成ANativeWindow设置的数据格式

    ● 释放ANativeWindow

    最简测试Demo

            gradle配置:

            主要是打开NativeBuild指定创建ABI编译目标以及cmake配置文件位置

    1. plugins {
    2. id 'com.android.application'
    3. id 'org.jetbrains.kotlin.android'
    4. }
    5. android {
    6. compileSdkVersion 34
    7. buildToolsVersion "30.0.3"
    8. defaultConfig {
    9. applicationId "com.example.learnopengl"
    10. minSdkVersion 24
    11. targetSdkVersion 30
    12. versionCode 1
    13. versionName "1.0"
    14. testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    15. externalNativeBuild {
    16. cmake {
    17. cppFlags ""
    18. }
    19. }
    20. ndk {
    21. abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
    22. }
    23. }
    24. //……
    25. externalNativeBuild {
    26. cmake {
    27. path "CMakeLists.txt"
    28. }
    29. }
    30. sourceSets {
    31. main {
    32. jniLibs.srcDirs = ['libs']
    33. jni.srcDirs = []
    34. }
    35. }
    36. ndkVersion '20.1.5948944'
    37. kotlinOptions {
    38. jvmTarget = '1.8'
    39. }
    40. }

            根CMakeLists配置:

            这个只是个人配置大家可以根据实际情况修改

    1. # Sets the minimum version of CMake required to build the native
    2. # library. You should either keep the default value or only pass a
    3. # value of 3.4.0 or lower.
    4. cmake_minimum_required(VERSION 3.4.1)
    5. add_compile_options(
    6. -fno-omit-frame-pointer
    7. -fexceptions
    8. -Wall
    9. )
    10. set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -mfloat-abi=soft -DANDROID")
    11. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -mfloat-abi=soft -DANDROID")
    12. # 生成中间文件,便于debug
    13. set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -save-temps=obj")
    14. set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -save-temps=obj")
    15. # Creates and names a library, sets it as either STATIC
    16. # or SHARED, and provides the relative paths to its source code.
    17. # You can define multiple libraries, and CMake builds it for you.
    18. # Gradle automatically packages shared libraries with your APK.
    19. ADD_SUBDIRECTORY(src/main/cpp/opengl_decoder)

            子工程opengl_decoder的CMake配置

                    声明工程文件位置、工程名,和要引入编译名字

    1. # Sets the minimum version of CMake required to build the native
    2. # library. You should either keep the default value or only pass a
    3. # value of 3.4.0 or lower.
    4. cmake_minimum_required(VERSION 3.4.1)
    5. #project(zbar LANGUAGES C VERSION 2.0.2)
    6. project(opengl_decoder)
    7. #SET(zbar_sdk_dir ${CMAKE_SOURCE_DIR}/src/main/cpp/opengl_decoder)
    8. message(AUTHOR_WARNING ${CMAKE_CURRENT_SOURCE_DIR})
    9. message(AUTHOR_WARNING ${opengl_decoder})
    10. add_definitions("-DDYNAMIC_ES3")
    11. # Creates and names a library, sets it as either STATIC
    12. # or SHARED, and provides the relative paths to its source code.
    13. # You can define multiple libraries, and CMake builds it for you.
    14. # Gradle automatically packages shared libraries with your APK.
    15. #INCLUDE_DIRECTORIES("/lib")
    16. add_library( # Sets the name of the library.
    17. opengl_decoder
    18. # Sets the library as a shared library.
    19. SHARED
    20. # Provides a relative path to your source file(s).
    21. # Associated headers in the same location as their source
    22. # file are automatically included.
    23. OpenGLNativeRenderJNIBridgeDemo.h
    24. OpenGLNativeRenderJNIBridgeDemo.cpp
    25. )
    26. # Searches for a specified prebuilt library and stores the path as a
    27. # variable. Because CMake includes system libraries in the search path by
    28. # default, you only need to specify the name of the public NDK library
    29. # you want to add. CMake verifies that the library exists before
    30. # completing its build.
    31. find_library(log-lib log)
    32. find_library(android-lib android)
    33. find_library(EGL-lib EGL)
    34. #find_library(GLESv2-lib GLESv2)
    35. find_library(GLESv3-lib GLESv3)
    36. find_library(OpenSLES-lib OpenSLES)
    37. find_library(dl-lib dl)
    38. find_library(z-lib z)
    39. target_link_libraries(
    40. opengl_decoder
    41. jnigraphics
    42. ${log-lib}
    43. ${android-lib}
    44. ${EGL-lib}
    45. ${GLESv3-lib}
    46. ${OpenSLES-lib}
    47. ${dl-lib}
    48. ${z-lib}
    49. #数学库:
    50. m
    51. )
    52. ${PROJECT_SOURCE_DIR}/lib/${ANDROID_ABI}/libiconv.so)
    53. message(AUTHOR_WARNING ${PROJECT_SOURCE_DIR})

     

            实际逻辑代码:

            1、  先编写一个SurfaceView子类然后编写一个线程TestThread,意图循环输出随机的颜色清屏信号,通过获取SurfaceView的holder内部Surface、以及清屏颜色传入到事先编写的native方法JniBridge.drawToSurface实现

    1. package com.cjztest.glOffscreenProcess.demo1
    2. import android.content.Context
    3. import android.graphics.Color
    4. import android.graphics.PixelFormat
    5. import android.util.AttributeSet
    6. import android.view.SurfaceHolder
    7. import android.view.SurfaceView
    8. import com.opengldecoder.jnibridge.JniBridge
    9. import kotlin.random.Random
    10. class NativeModifySurfaceView @JvmOverloads constructor(
    11. context: Context, attrs: AttributeSet? = null
    12. ) : SurfaceView(context, attrs), SurfaceHolder.Callback {
    13. private lateinit var mSurfaceHolder: SurfaceHolder
    14. inner class TestThread : Thread() {
    15. override fun run() {
    16. while(this@NativeModifySurfaceView.isAttachedToWindow) {
    17. JniBridge.drawToSurface(holder.surface
    18. , (0xFF000000.toInt()
    19. or (Random.nextFloat() * 255f).toInt()
    20. or ((Random.nextFloat() * 255f).toInt() shl 8)
    21. or ((Random.nextFloat() * 255f).toInt() shl 16)))
    22. sleep(16)
    23. }
    24. }
    25. }
    26. init {
    27. mSurfaceHolder = holder
    28. mSurfaceHolder.addCallback(this)
    29. mSurfaceHolder.setFormat(PixelFormat.RGBA_8888)
    30. isFocusable = true
    31. setFocusableInTouchMode(true)
    32. }
    33. private var mTestThread: TestThread ?= null
    34. override fun surfaceCreated(holder: SurfaceHolder) {
    35. if (mTestThread == null) {
    36. mTestThread = TestThread()
    37. }
    38. mTestThread?.start()
    39. }
    40. override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
    41. }
    42. override fun surfaceDestroyed(holder: SurfaceHolder) {
    43. }
    44. }

            其中要注意为了确保其中Surface像素格式RGBA8888,方便进行颜色填充实验初始化通过SurfaceHolder颜色设置RGBA8888了:

    mSurfaceHolder.setFormat(PixelFormat.RGBA_8888)

    2、  创建JniBridge类,编写JNI方法签名,可以传入surface对象,交由JNIEnv进行处理:

    1. package com.opengldecoder.jnibridge;
    2. import android.graphics.Bitmap;
    3. import android.view.Surface;
    4. public class JniBridge {
    5. static {
    6. System.loadLibrary("opengl_decoder");
    7. }
    8. public static native void drawToSurface(Surface surface, int color);
    9. }

    3、  编写JNI方法,获取SurfaceANativeWindow然后对其进行颜色填充步骤使用SurfaceView类似的。过程如下:

    通过ANativeWindow_fromSurface获取传入SurfaceANativeWindow对象,再通过ANativeWindow_lock锁定Surface获取它的数据buffer指针,然后传入color值循环便利写入对象最后调用ANativeWindow_release解锁即可看到Surface被刷成指定颜色了。

    1. //cpp/opengl_decoder/OpenGLNativeRenderJNIBridgeDemo.cpp
    2. JNIEXPORT void JNICALL
    3. Java_com_opengldecoder_jnibridge_JniBridge_drawToSurface(JNIEnv *env, jobject activity,
    4. jobject surface, jint color) {
    5. ANativeWindow_Buffer nwBuffer;
    6. LOGI("ANativeWindow_fromSurface ");
    7. ANativeWindow *mANativeWindow = ANativeWindow_fromSurface(env, surface);
    8. if (mANativeWindow == NULL) {
    9. LOGE("ANativeWindow_fromSurface error");
    10. return;
    11. }
    12. LOGI("ANativeWindow_lock ");
    13. if (0 != ANativeWindow_lock(mANativeWindow, &nwBuffer, 0)) {
    14. LOGE("ANativeWindow_lock error");
    15. return;
    16. }
    17. LOGI("ANativeWindow_lock nwBuffer->format ");
    18. if (nwBuffer.format == WINDOW_FORMAT_RGBA_8888) {
    19. LOGI("nwBuffer->format == WINDOW_FORMAT_RGBA_8888 ");
    20. for (int i = 0; i < nwBuffer.height * nwBuffer.width; i++) {
    21. *((int*)nwBuffer.bits + i) = color;
    22. }
    23. }
    24. LOGI("ANativeWindow_unlockAndPost ");
    25. if (0 != ANativeWindow_unlockAndPost(mANativeWindow)) {
    26. LOGE("ANativeWindow_unlockAndPost error");
    27. return;
    28. }
    29. ANativeWindow_release(mANativeWindow);
    30. LOGI("ANativeWindow_release ");
    31. }

    4、  让SurfaceHolder的在创建时生成测试线程对象,测试线程将循环合成不同的颜色然后传入刚才到刚才的native函数逻辑中,实现native层面对surface进行内容填充实例的目的:

     

    1. override fun surfaceCreated(holder: SurfaceHolder) {
    2. if (mTestThread == null) {
    3. mTestThread = TestThread()
    4. }
    5. mTestThread?.start()
    6. }
    7. inner class TestThread : Thread() {
    8. override fun run() {
    9. while(this@NativeModifySurfaceView.isAttachedToWindow) {
    10. JniBridge.drawToSurface(holder.surface
    11. , (0xFF000000.toInt()
    12. or (Random.nextFloat() * 255f).toInt()
    13. or ((Random.nextFloat() * 255f).toInt() shl 8)
    14. or ((Random.nextFloat() * 255f).toInt() shl 16)))
    15. sleep(16)
    16. }
    17. }
    18. }

            效果:

                    截两次不同颜色填充结果

    结尾:

            本文内容主要是展示了如何搭建一个native层面填充Surface内容的环境实际场景可以用于FFMpeg解码内容、OpenGL ES渲染拷贝显示Surface的。

    引用

    Android的Surface、View、SurfaceView、Window概念整理 | superxlcr's notebook

    Android基础--利用ANativeWindow显示视频-腾讯云开发者社区-腾讯云

  • 相关阅读:
    【前端】-css的详解
    VueX
    App Languages 批量化导入iOS多语言
    linux之perf(8)annotate标注
    解决/usr/bin/env: ‘python\r’: No such file or directory
    微信迎来史诗级更新,以及两款微信清理工具
    (二)ndarray(Python)与Mat(C++)数据的传输
    网络安全(黑客)自学
    【矩阵论】3. 矩阵运算与函数——矩阵函数
    什么是跨域问题?如何解决?
  • 原文地址:https://blog.csdn.net/cjzjolly/article/details/140448984