Android NDK 主要应用于游戏和多媒体应用。这些应用依赖于native代码处理性能表现要求严格的场景。
从native层直接渲染图形对于这类应用至关重要。本章将会探索Android NDK提供的如下native图形API:
- JNI Graphics API(aka Bitmap API)
- OpenGL ES 1.x and 2.0
- Native Window API
通过本章的学习将会通过native图形API完成渲染视频帧的视频播放器AVI video player。
创建一个AVI Video Player
AVI 视频播放器将会作为一个测试平台。示例工程将会提供以下功能:- 一个支持native代码的Android应用工程
- 一个静态链接AVI库,这个库通过Java暴露基础的方法并和Activity生命周期绑定
- 一个简单的GUI来获取AVI视频文件和用于播放的native 图形API
播放AVI视频文件需要解析AVI文件。尽管AVI格式本身并不复杂,但是为了简单起见,使用AVILib这个三方库来处理AVI文件。
将AVILib作为NDK导入库
步骤如下:- 导入avilib库,即复制avilib文件夹到cpp文件夹下
- 修改头platform.h文件
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
- 创建CMake文件CMakeLists.txt:
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
avi-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
avilib.c
platform_posix.c)
创建AVIPlayerActivity
1. 首先创建AbsAVIPlayerActivity package com.example.nativeexe.player
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import java.io.IOException
abstract class AbsAVIPlayerActivity : AppCompatActivity() {
init {
System.loadLibrary("player")
}
protected var avi: Long = 0
override fun onStart() {
super.onStart()
try {
avi = open(getFileName())
} catch (e: Exception) {
AlertDialog.Builder(this)
.setTitle("视频文件加载错误")
.setMessage(e.message.toString())
.show()
}
}
abstract fun getFileName(): String
override fun onStop() {
super.onStop()
if (avi != 0L) {
close(avi)
avi = 0L
}
}
@Throws(IOException::class)
external fun open(fileName: String): Long
external fun getWidth(avi: Long): Int
external fun getHeight(avi: Long): Int
external fun getFrameRate(avi: Long): Double
external fun close(avi: Long)
}
- 实现JNI方法
//
// Created by Jenry Sun on 2024/9/24.
//
extern "C" {
#include "avilib/avilib.h"
}
#include <jni.h>
#include "error_handle.h"
extern "C"
JNIEXPORT jlong JNICALL
Java_com_example_nativeexe_player_AbsAVIPlayerActivity_open(JNIEnv *env,
jobject thiz,
jstring file_name) {
avi_t *avi = nullptr;
const char *cFileName = env->GetStringUTFChars(file_name, nullptr);
if (nullptr == cFileName) {
goto exit;
}
avi = AVI_open_input_file(cFileName, 1);
env->ReleaseStringUTFChars(file_name, cFileName);
if (nullptr == avi) {
ThrowException(env, "java/io/IOException", AVI_strerror());
}
exit:
return reinterpret_cast<jlong>(avi);
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_nativeexe_player_AbsAVIPlayerActivity_getWidth(JNIEnv *env, jobject thiz,
jlong avi) {
return AVI_video_width((avi_t *) avi);
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_nativeexe_player_AbsAVIPlayerActivity_getHeight(JNIEnv *env, jobject thiz,
jlong avi) {
return AVI_video_height((avi_t *) avi);
}
extern "C"
JNIEXPORT jdouble JNICALL
Java_com_example_nativeexe_player_AbsAVIPlayerActivity_getFrameRate(JNIEnv *env, jobject thiz,
jlong avi) {
return AVI_frame_rate((avi_t *) avi);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_nativeexe_player_AbsAVIPlayerActivity_close(JNIEnv *env, jobject thiz, jlong avi) {
AVI_close((avi_t *) avi);
}
- 修改CMake文件
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html.
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.
# Sets the minimum CMake version required for this project.
cmake_minimum_required(VERSION 3.22.1)
# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
# Since this is the top level CMakeLists.txt, the project name is also accessible
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
# build script scope).
project("nativeexe")
add_compile_definitions(MY_LOG_TAG="hello-jni~")
if (CMAKE_BUILD_TYPE MATCHES "Debug")
add_compile_definitions(MY_LOG_LEVEL=MY_LOG_LEVEL_VERBOSE)
else ()
add_compile_definitions(MY_LOG_LEVEL=MY_LOG_LEVEL_ERROR)
endif ()
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#
# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME}
# is preferred for the same purpose.
#
# In order to load a library into your app from Java/Kotlin, you must call
# System.loadLibrary() and pass the name of the library defined here;
# for GameActivity/NativeActivity derived applications, the same library name must be
# used in the AndroidManifest.xml file.
add_library(${CMAKE_PROJECT_NAME} SHARED
# List C/C++ source files with relative paths to this CMakeLists.txt.
native-lib.cpp)
# Specifies libraries CMake should link to your target library. You
# can link libraries from various origins, such as libraries defined in this
# build script, prebuilt third-party libraries, or Android system libraries.
target_link_libraries(${CMAKE_PROJECT_NAME}
# List libraries link to the target library
android
log)
set(LIB_ECHO "echo")
add_library(${LIB_ECHO} SHARED
# List C/C++ source files with relative paths to this CMakeLists.txt.
echo.cpp
error_handle.cpp)
target_link_libraries(${LIB_ECHO}
# List libraries link to the target library
android
log)
add_subdirectory(avilib)
set(LIB_PLAYER "player")
add_library(${LIB_PLAYER} SHARED
player.cpp
error_handle.cpp)
target_link_libraries(${LIB_PLAYER}
android
log
avi-lib)
到这里AVI文件处理的相关代码就完成了
JNI Graphics API
首先需要开启native代码对JNI Graphics API的支持:- 修改CMakeLists.txt
target_link_libraries(${LIB_PLAYER}
android
log
avi-lib
jnigraphics)//新增jnigraphics
- 在需要使用的文件中增加头文件:
#include <android/bitmap.h>
使用JNI Graphics API
JNI Graphics API提供了4个native方法用于访问和操作Bitmap对象。从Bitmap对象中获取信息
`AndroidBitmap_getInfo`方法返回Bitmap对象尺寸和pixel格式信息:int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap,
AndroidBitmapInfo* info);
方法有三个入参:
- JNI 接口指针
- Bitmap对象引用
- 指向AndroidBItmapInfo的指针
返回bitmap的信息:
/** Bitmap info, see AndroidBitmap_getInfo(). */
typedef struct {
/** The bitmap width in pixels. */
uint32_t width;
/** The bitmap height in pixels. */
uint32_t height;
/** The number of byte per row. */
uint32_t stride;
/** The bitmap pixel format. See {@link AndroidBitmapFormat} */
int32_t format;
/** Bitfield containing information about the bitmap.
*
* <p>Two bits are used to encode alpha. Use {@link ANDROID_BITMAP_FLAGS_ALPHA_MASK}
* and {@link ANDROID_BITMAP_FLAGS_ALPHA_SHIFT} to retrieve them.</p>
*
* <p>One bit is used to encode whether the Bitmap uses the HARDWARE Config. Use
* {@link ANDROID_BITMAP_FLAGS_IS_HARDWARE} to know.</p>
*
* <p>These flags were introduced in API level 30.</p>
*/
uint32_t flags;
} AndroidBitmapInfo;
其中format表示像素格式,定义如下:
/** Bitmap pixel format. */
enum AndroidBitmapFormat {
/** No format. */
ANDROID_BITMAP_FORMAT_NONE = 0,
/** Red: 8 bits, Green: 8 bits, Blue: 8 bits, Alpha: 8 bits. **/
ANDROID_BITMAP_FORMAT_RGBA_8888 = 1,
/** Red: 5 bits, Green: 6 bits, Blue: 5 bits. **/
ANDROID_BITMAP_FORMAT_RGB_565 = 4,
/** Deprecated in API level 13. Because of the poor quality of this configuration, it is advised to use ARGB_8888 instead. **/
ANDROID_BITMAP_FORMAT_RGBA_4444 = 7,
/** Alpha: 8 bits. */
ANDROID_BITMAP_FORMAT_A_8 = 8,
/** Each component is stored as a half float. **/
ANDROID_BITMAP_FORMAT_RGBA_F16 = 9,
/** Red: 10 bits, Green: 10 bits, Blue: 10 bits, Alpha: 2 bits. **/
ANDROID_BITMAP_FORMAT_RGBA_1010102 = 10,
};
方法调用成功返回0,否则返回负值。完整的返回值错误码定义在android/bitmap.h中。
访问Native Pixel Buffer
`AndroidBitmap_lockPixels`方法会锁定像素buffer来确保像素的内存不会移动。这个方法会返回像素buffer的native指针,这样native代码可以放回像素数据或者操作这些数据。/**
* Given a java bitmap object, attempt to lock the pixel address.
* Locking will ensure that the memory for the pixels will not move
* until the unlockPixels call, and ensure that, if the pixels had been
* previously purged, they will have been restored.
*
* If this call succeeds, it must be balanced by a call to
* AndroidBitmap_unlockPixels, after which time the address of the pixels should
* no longer be used.
*
* If this succeeds, *addrPtr will be set to the pixel address. If the call
* fails, addrPtr will be ignored.
*/
int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr);
该方法有三个入参:
- JNI接口指针
- Bitmap对象引用
- 一个指向void指针的指针来返回native pixel buffer的地址
方法调用成功返回0,否则返回负值。和之前AndroidBitmap_getInfo一样,完整的错误码定义在android/bitmap.h头文件中。
释放Native Pixel Buffer
每次调用`AndroidBitmap_lockPixels`后都必须对应调用`AndroidBitmap_lockPixels`来释放native pixel buffer。native应用在读、写完native pixel buffer后需要立刻释放相应的native pixel buffer。一旦释放完毕,Bitmap对象就可以在Java层使用。/**
* Call this to balance a successful call to AndroidBitmap_lockPixels.
*/
int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap);
该方法有两个入参:
- JNI接口指针
- Bitmap对象引用
方法调用成功返回0,否则返回负值。
在AVI Player中增加Bitmap渲染
1. 创建BitmapAVIPlayerActivitypackage com.example.nativeexe.player
import android.graphics.Bitmap
import android.graphics.Matrix
import android.os.Bundle
import android.util.Log
import android.view.SurfaceHolder
import android.view.SurfaceView
import androidx.activity.enableEdgeToEdge
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.example.nativeexe.R
import java.io.File
import java.io.FilenameFilter
import java.util.concurrent.atomic.AtomicBoolean
class BitmapAVIPlayerActivity : AbsAVIPlayerActivity() {
private val isPlaying = AtomicBoolean()
private lateinit var svVideo: SurfaceView
private lateinit var surfaceHolder: SurfaceHolder
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_aviplayer)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
svVideo = findViewById(R.id.sv_video)
surfaceHolder = svVideo.holder
surfaceHolder.addCallback(surfaceHolderCallback)
}
private val surfaceHolderCallback = object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
isPlaying.set(true)
Thread(renderer).start()
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
isPlaying.set(false)
}
}
private val renderer = Runnable {
//创建一个容纳视频帧的bitmap
val bitmap =
Bitmap.createBitmap(getWidth(avi), getHeight(avi), Bitmap.Config.RGB_565)
//原视频太小,放大一下,方便观察
val matrix = Matrix()
matrix.postScale(2.0f, 2.0f)
//根据帧率计算延迟
val frameDelay = (1000 / getFrameRate(avi)).toLong()
while (isPlaying.get()) {
//将frame渲染到bitmap上
val isFrameRead = render(avi, bitmap)
// Log.e(TAG, "isFrameRead:$isFrameRead")
//lock canvas
val canvas = surfaceHolder.lockCanvas()
//将bitmap绘制到canvas上
canvas.drawBitmap(bitmap, matrix, null)
//post canvas到显示
surfaceHolder.unlockCanvasAndPost(canvas)
//等待下一帧
try {
Thread.sleep(frameDelay)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
external fun render(avi: Long, bitmap: Bitmap): Boolean
}
布局如下:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".player.BitmapAVIPlayerActivity">
<SurfaceView
android:id="@+id/sv_video"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
- 完善native中render方法:
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_example_nativeexe_player_BitmapAVIPlayerActivity_render(JNIEnv *env, jobject thiz,
jlong avi,
jobject bitmap) {
jboolean isFrameRead = JNI_FALSE;
char *frameBuffer = 0;
long frameSize;
int keyFrame = 0;
//lock bitmap 并且获取原始bytes
if (0 > AndroidBitmap_lockPixels(env, bitmap, (void **) &frameBuffer)) {
ThrowException(env, "java/io/IOException", "Unable to lock pixels");
goto exit;
}
//将AVI frame byte读取到bitmap中
frameSize = AVI_read_frame((avi_t *) avi, frameBuffer, &keyFrame);
if (0 < frameSize) {
isFrameRead = JNI_TRUE;
}
exit:
return isFrameRead;
}
- 导入avi文件开始测试。注意要使用项目中提供的avi文件,其他的avi文件我试了很多都是无法正常渲染。
使用Native Window API渲染
从Android API9开始,Android NDK提供了通过native代码直接操作native window上的像素。这些API就是native window API。在本章中,将会通过native代码直接完成渲染功能。开启Native Window API
开启Native Window API需要如下两步:- 导入native window 头文件
#include <android/native_window.h>
#include <android/native_window_jni.h>
- 在CMake文件中增加android库
target_link_libraries(${LIB_PLAYER}
android //这里
log
avi-lib
jnigraphics
GLESv3)
使用Native Window API
native window API提供了native方法访问和操作Bitmap对象。从Surface对象获取Native Window
`ANativeWindow_fromSurface`方法可以从给定的Surface对象获取native windowANativeWindow* ANativeWindow_fromSurface(JNIEnv* env,
jobject surface);
方法有两个入参
- JNI接口指针
- Surface对象引用
方法返回native window实例指针。这个方法也会获取一份返回native window实例引用,需要通过调用ANativeWindow_release 方法释放引用来防止内存泄漏。
获取Native Window实例的引用
为了防止native window实例被删除,native代码可以通过`ANativeWindow_acquire`方法获取native window的引用。void ANativeWindow_acquire(ANativeWindow* window);
每次调用完该方法都必须配对调用ANativeWindow_release方法。
释放Native Window引用
通过`ANativeWindow_release`方法可以释放native window引用void ANativeWindow_release(ANativeWindow* window);
方法有一个入参:
- window,指向native window实例的指针
获取Native Window信息
native window API提供了一系列native 方法获取native window诸如尺寸、像素格式等信息ANativeWindow_getWidth方法用于获取native window宽度ANativeWindow_getHeight方法用于获取native window高度ANativeWindow_getFormat方法用于获取native window像素格式
设置Native Window Buffer属性
native window的尺寸和像素格式需要和被渲染的图像匹配。如果图像的尺寸和像素属性不同,`ANatvieWindow_setBufferGeometry`方法可以用于调整native window buffer相关配置。buffer将会自动缩放到匹配native window:int32_t ANativeWindow_setBuffersGeometry(ANativeWindow* window,
int32_t width,
int32_t height,
int32_t format);
方法有4个入参:
- 指向native window的指针
- 新的宽度
- 新的高度
- 新的像素格式
方法调用成功返回0。对于所有参数,如果提供了0,相关值会被恢复到native window buffer的基础值。
访问Native Window Buffer
`ANativeWindow_lock`方法用于锁定native window buffer并且获取指向原始像素buffer的指针。Native代码可以通过这个指针来访问和操作像素buffer。int32_t ANativeWindow_lock(ANativeWindow* window,
ANativeWindow_Buffer* outBuffer,
ARect* inOutDirtyBounds);
方法有三个入参:
- 一个指向native window的指针
- 一个指向ANativeWindow_Buffer结构体的指针
- 一个可选的指向ARect结构体的指针
ANativeWindow_Buffer结构体如下:
typedef struct ANativeWindow_Buffer {
// The number of pixels that are show horizontally.
int32_t width;
// The number of pixels that are shown vertically.
int32_t height;
// The number of *pixels* that a line in the buffer takes in
// memory. This may be >= width.
int32_t stride;
// The format of the buffer. One of WINDOW_FORMAT_*
int32_t format;
// The actual bits.
void* bits;
// Do not touch.
uint32_t reserved[6];
} ANativeWindow_Buffer;
除了native window的信息外,还通过bits成员变量提供了对原始图像信息的访问。方法调用成功返回0。
释放 Native Window Buffer
native代码处理完buffer后,需要使用`ANativeWindow_unlockAndPost`解锁并post 回native window buffer。int32_t ANativeWindow_unlockAndPost(ANativeWindow* window);
方法有一个入参:
- 指向native window的指针
方法调用成功返回0。
后续将会通过上面介绍的这些API来实现新的AVIPlayer应用。
使用Native Window完成AVI Player 渲染
1. 新增Activity和xmlpackage com.example.nativeexe.player
import android.os.Bundle
import android.view.Surface
import android.view.SurfaceHolder
import android.view.SurfaceView
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.example.nativeexe.R
class NativeWindowAVIPlayerActivity : AbsAVIPlayerActivity() {
private lateinit var svVideo: SurfaceView
private lateinit var surfaceHolder: SurfaceHolder
private val surfaceHolderCallback = object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
isPlaying.set(true)
Thread(runnable).start()
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
isPlaying.set(false)
}
}
private val runnable = Runnable {
val surface = surfaceHolder.surface
initNativeWindow(avi, surface)
val frameDelay = (1000 / getFrameRate(avi)).toLong()
while (isPlaying.get()) {
renderNativeWindow(avi, surface)
try {
Thread.sleep(frameDelay)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_native_window_aviplayer)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
svVideo = findViewById(R.id.sv_video)
surfaceHolder = svVideo.holder
surfaceHolder.addCallback(surfaceHolderCallback)
}
external fun initNativeWindow(avi: Long, surface: Surface)
external fun renderNativeWindow(avi: Long, surface: Surface):Boolean
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".player.NativeWindowAVIPlayerActivity">
<SurfaceView
android:id="@+id/sv_video"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
- 完善Native部分代码:
extern "C"
JNIEXPORT void JNICALL
Java_com_example_nativeexe_player_NativeWindowAVIPlayerActivity_initNativeWindow(JNIEnv *env,
jobject thiz,
jlong avi,
jobject surface) {
ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, surface);
if (0 == nativeWindow) {
ThrowException(env, "java/lang/RuntimeException",
"Unable to get native nativeWindow from surface.");
goto exit;
}
if (0 > ANativeWindow_setBuffersGeometry(nativeWindow, AVI_video_width((avi_t *) avi),
AVI_video_height((avi_t *) avi),
WINDOW_FORMAT_RGB_565)) {
ThrowException(env, "java/lang/RuntimeException", "Unable to set buffers geometry");
}
ANativeWindow_release(nativeWindow);
nativeWindow = 0;
exit:
return;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_example_nativeexe_player_NativeWindowAVIPlayerActivity_renderNativeWindow(JNIEnv *env,
jobject thiz,
jlong avi,
jobject surface) {
jboolean isFrameRead = JNI_FALSE;
long frameSize = 0;
int keyFrame = 0;
ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, surface);
if (0 == nativeWindow) {
ThrowException(env, "java/lang/RuntimeException",
"Unable to get native window from surface.");
goto exit;
}
ANativeWindow_Buffer windowBuffer;
if (0 > ANativeWindow_lock(nativeWindow, &windowBuffer, 0)) {
ThrowException(env, "java/lang/RuntimeException",
"Unable to lock native window.");
goto release;
}
frameSize = AVI_read_frame((avi_t *) avi, (char *) windowBuffer.bits, &keyFrame);
if (0 < frameSize) {
isFrameRead = JNI_TRUE;
}
if (0 > ANativeWindow_unlockAndPost(nativeWindow)) {
ThrowException(env, "java/lang/RuntimeException",
"Unable to unlock and post native window");
goto release;
}
release:
ANativeWindow_release(nativeWindow);
nativeWindow = 0;
exit:
return isFrameRead;
}
EGL 图形库
从API 9开始,Android NDK支持EGL 图形库,native代码可以通过这个库来管理OpenGL ES surface。开启EGL需要两步:- 导入头文件
#include <EGL/egl.h>
#include <EGL/eglext.h>
- 修改CMake文件,增加EGL库
target_link_libraries(${LIB_PLAYER}
android
log
avi-lib
jnigraphics
EGL//这里
GLESv3)
这样就可以通过EGL图形库API方法来支持EGL操作,例如分配和释放OpenGL ES surface,交换/翻转surface。