# jetpack-demo **Repository Path**: oxy/jetpack-demo ## Basic Information - **Project Name**: jetpack-demo - **Description**: Android平台上的一些测试代码,其中包括jetpack、ndk、音视频、ffmpeg、opencv、opengl等一些测试代码 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-11-08 - **Last Updated**: 2024-04-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: Android, ffmpeg, NDK, OpenCV, Opengl ## README # Android平台上代码测试库 # NDK ## NDK常用工具接口 > 主要包括: > > 1、注册JNI函数工具 > > 2、JNIContext JNI工具集,通常是全局对象 > > 3、AssetManager工具,用于访问asset目录下文件 > > 4、AttachCurrentThread用于将native线程附加到Java虚拟机,通常用于native层和java层通信 NdkHelper.h ```c++ // // Created by ozcom on 2023/11/14. // #ifndef FFMPEGDEMO_NDKHELPER_H #define FFMPEGDEMO_NDKHELPER_H #include #include #include #include #include #include #include #include "Handler.hpp" #include namespace we { inline namespace helper { using jni_native_method_vector = std::vector>>; using jni_class_name_vector = std::vector; using jActivityThread = jobject; using jApplication = jobject; using jAssetManager = jobject; class bad_jni final : public std::exception { public: inline bad_jni(std::string msg) : msg_(std::move(msg)) {} inline const char *what() const noexcept override { return msg_.c_str(); } private: std::string msg_; }; jApplication getApplication4JNIEnv(JNIEnv *env); jAssetManager getAssetManager4JNIEnv(JNIEnv *env); JNIEnv *getJNIEnv4JavaVM(JavaVM *vm, jint version); std::string getJNIError(int32_t err_code, char const *msg = ""); } /*AssetManager*/ class AssetManager final { public: AssetManager(JNIEnv *env, jobject assetManager); std::string read(std::string const &filename) const; private: AAssetManager *asm_; }; class AppContext final { public: inline explicit AppContext(JNIEnv *env) : amr_({env, getAssetManager4JNIEnv(env)}) {} const AssetManager &getAssetManager() const { return amr_; } private: AssetManager amr_; }; /*AttachCurrentThread*/ class AttachCurrentThread final { public: void *operator new(std::size_t) = delete; void *operator new[](std::size_t) = delete; AttachCurrentThread(JavaVM *vm); AttachCurrentThread(JNIEnv *env); virtual ~AttachCurrentThread(); JavaVM *getVm() const; JNIEnv *getEnv() const; bool checkException() const; void clearException() const; void printException() const; jthrowable occurredException() const; void throwException(jthrowable ex) const; void throwByName(char const *classname, char const *msg) const; void clearThrowException() const; void checkClearException() const; /* enter synchronized */ jint monitorEnter(jobject obj) const; /* exit synchronized */ jint monitorExit(jobject obj) const; private: JavaVM *vm_; JNIEnv *env_{}; }; /*JNIContext*/ class JNIContext final { public: JNIContext(JavaVM *vm); ~JNIContext(); jint getJniVersion() const noexcept; JavaVM *getVm() const noexcept; JNIEnv *getEnv() const noexcept; Handler const &getHandler() const noexcept; bool registerNatives(const jni_native_method_vector &_vec) noexcept; bool unregisterNatives(const jni_class_name_vector &_vec) noexcept; template inline auto getUserdata() noexcept(noexcept(std::any_cast(&userdata_))) { return std::any_cast(&userdata_); } template inline void setUserdata(T &&_v) noexcept { userdata_ = _v; } inline const AppContext &getAppContext() const noexcept { return appContext_; } private: jint jni_version_; JavaVM *vm_; JNIEnv *env_; AppContext appContext_; Handler handler_{}; mutable std::any userdata_{}; //use for cache user data }; } #endif //FFMPEGDEMO_NDKHELPER_H ``` NdkHelper.cpp ```c++ // // Created by ozcom on 2023/11/14. // #include "NdkHelper.h" #include #include #include "log.h" #ifdef __cplusplus extern "C" { #endif #include #ifdef __cplusplus } #endif using namespace we; namespace we::helper { jApplication getApplication4JNIEnv(JNIEnv *env) { AttachCurrentThread act{env}; auto cls = env->FindClass("com/oz/we/NdkHelper"); jmethodID getApplication = env->GetStaticMethodID(cls, "getApplication", "()Landroid/app/Application;"); jobject japp = env->CallStaticObjectMethod(cls, getApplication); return japp; } jobject getAssetManager4JNIEnv(JNIEnv *env) { jobject japp = getApplication4JNIEnv(env); AttachCurrentThread act{env}; jmethodID getApplication = env->GetMethodID(env->GetObjectClass(japp), "getAssets", "()Landroid/content/res/AssetManager;"); jobject jam = env->CallObjectMethod(japp, getApplication); return jam; } JNIEnv *getJNIEnv4JavaVM(JavaVM *vm, jint version) { if (vm == nullptr) throw std::invalid_argument("vm_ != nullptr"); JNIEnv *env = nullptr; int32_t ret = vm->GetEnv(reinterpret_cast(&env), version); if (ret != JNI_OK)throw bad_jni{getJNIError(ret, "GetEnv")}; return env; } std::string getJNIError(int32_t err_code, char const *msg) { std::stringstream ss{}; ss << msg << ":"; switch (err_code) { case JNI_OK: ss << "no error"; break; case JNI_ERR: ss << "generic error"; break; case JNI_EDETACHED: ss << "thread detached from the VM"; break; case JNI_EVERSION: ss << "JNI version error"; break; case JNI_ENOMEM: ss << "Out of memory"; break; case JNI_EEXIST: ss << "VM already created"; break; case JNI_EINVAL: ss << "Invalid argument"; break; case JNI_COMMIT: ss << "copy content, do not free buffer"; break; case JNI_ABORT: ss << "free buffer w/o copying back"; break; default: ss << "unknown"; break; } std::string str = ss.str(); if (err_code == JNI_OK) LOGD("%s", str.c_str()); else LOGE("%s", str.c_str()); return std::move(str); } } namespace we { AssetManager::AssetManager(JNIEnv *env, jobject assetManager) : asm_(AAssetManager_fromJava(env, assetManager)) { } std::string AssetManager::read(std::string const &filename) const { AAsset *asset = AAssetManager_open(asm_, filename.c_str(), AASSET_MODE_RANDOM); size_t len = AAsset_getLength(asset); char buf[len]; len = AAsset_read(asset, buf, len); AAsset_close(asset); return {buf, len}; } } namespace we { AttachCurrentThread::AttachCurrentThread(JavaVM *vm) : vm_(([&]() -> JavaVM * { if (vm == nullptr) throw std::invalid_argument("vm != nullptr"); if (gettid() != getpid()) { int32_t ret = vm->AttachCurrentThread(&env_, nullptr); if (ret != JNI_OK) throw bad_jni{getJNIError(ret, "AttachCurrentThread")}; } else { int32_t ret = vm->GetEnv(reinterpret_cast(&env_), JNI_VERSION_1_6); if (ret != JNI_OK) throw bad_jni{getJNIError(ret, "GetEnv JNI_VERSION_1_6")}; } return vm; })()) {} AttachCurrentThread::AttachCurrentThread(JNIEnv *env) : vm_(([&]() { if (env == nullptr) throw std::invalid_argument("env != nullptr"); JavaVM *vm = nullptr; int32_t ret = env->GetJavaVM(&vm); if (ret != JNI_OK) throw bad_jni{getJNIError(ret, "GetJavaVM")}; if (gettid() != getpid()) { ret = vm->AttachCurrentThread(&env_, nullptr); if (ret != JNI_OK) throw bad_jni{getJNIError(ret, "AttachCurrentThread")}; } else { ret = vm->GetEnv(reinterpret_cast(&env_), JNI_VERSION_1_6); if (ret != JNI_OK) throw bad_jni{getJNIError(ret, "GetEnv JNI_VERSION_1_6")}; } return vm; })()) {} AttachCurrentThread::~AttachCurrentThread() { env_ = nullptr; if (gettid() != getpid()) vm_->DetachCurrentThread(); } JavaVM *AttachCurrentThread::getVm() const { return vm_; } JNIEnv *AttachCurrentThread::getEnv() const { return env_; } bool AttachCurrentThread::checkException() const { return env_->ExceptionCheck(); } void AttachCurrentThread::clearException() const { env_->ExceptionClear(); } void AttachCurrentThread::printException() const { env_->ExceptionDescribe(); } jthrowable AttachCurrentThread::occurredException() const { return env_->ExceptionOccurred(); } void AttachCurrentThread::throwException(jthrowable throwable) const { env_->Throw(throwable); } void AttachCurrentThread::throwByName(const char *classname, const char *msg) const { auto *env = env_; jclass cls = env->FindClass(classname); /*if cls is NULL, an exception has already been thrown */ if (cls) { env->ThrowNew(cls, msg); } /* free the local ref */ env->DeleteLocalRef(cls); } void AttachCurrentThread::clearThrowException() const { if (!env_->ExceptionCheck())return; env_->ExceptionDescribe(); auto th = env_->ExceptionOccurred(); env_->ExceptionClear(); env_->Throw(th); } void AttachCurrentThread::checkClearException() const { if (env_->ExceptionCheck())env_->ExceptionClear(); } jint AttachCurrentThread::monitorEnter(jobject obj) const { return env_->MonitorEnter(obj); } jint AttachCurrentThread::monitorExit(jobject obj) const { return env_->MonitorExit(obj); } } namespace we { JNIContext::JNIContext(JavaVM *vm) : vm_(([vm]() { if (vm == nullptr) throw std::invalid_argument("vm_ != nullptr"); return vm; })()), env_(getJNIEnv4JavaVM(vm, JNI_VERSION_1_6)), jni_version_(JNI_VERSION_1_6), appContext_(env_) {} JNIContext::~JNIContext() { env_ = nullptr; vm_ = nullptr; } jint JNIContext::getJniVersion() const noexcept { return jni_version_; } JavaVM *JNIContext::getVm() const noexcept { return vm_; } JNIEnv *JNIContext::getEnv() const noexcept { return env_; } const Handler &JNIContext::getHandler() const noexcept { return handler_; } bool JNIContext::registerNatives(const jni_native_method_vector &_vec) noexcept { for (const auto &[k, v]: _vec) { jclass cls = env_->FindClass(k.c_str()); if (!cls) { LOGE("FindClass Error: %s", k.c_str()); return false; } if (env_->RegisterNatives(cls, v.data(), static_cast(v.size())) != JNI_OK) { LOGE("RegisterNatives Error: %s", k.c_str()); return false; } } return true; } bool JNIContext::unregisterNatives(const jni_class_name_vector &_vec) noexcept { for (const auto &s: _vec) { jclass cls = env_->FindClass(s.c_str()); if (!cls) { LOGE("FindClass Error: %s", s.c_str()); return false; } if (env_->UnregisterNatives(cls) != JNI_OK) { LOGE("UnregisterNatives Error: %s", s.c_str()); return false; } } return true; } } ``` ## NDK日志接口 > 日志打印宏 > > 1、易于使用 > > 2、方便找到bug的源代码文件和具体位置 log.h ```c++ #ifndef ANDROID_LOG_H #define ANDROID_LOG_H #include #include #if ANDROID_LOG //#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "^_^", __VA_ARGS__)) //#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "^_*", __VA_ARGS__)) //#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "*_*", __VA_ARGS__)) //#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "^_9", __VA_ARGS__)) #define LOGV(...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, "-_-", __VA_ARGS__)) #define LOGI(...) do{ \ char TAG[64] = {0}; \ sprintf(TAG, "I/%s: ", __FILE_NAME__); \ __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__); \ }while(0) #define LOGD(...) do{ \ char TAG[128] = {0}; \ sprintf(TAG, "%s line:%d D/%s: ", __FILE_NAME__, __LINE__, __FUNCTION__); \ __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__); \ }while(0) #define LOGW(...) do{ \ char TAG[128] = {0}; \ sprintf(TAG, "%s line:%d W/%s: ", __FILE_NAME__, __LINE__, __FUNCTION__); \ __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__); \ }while(0) #define LOGE(...) do{ \ char TAG[128] = {0}; \ sprintf(TAG, "%s line:%d E/%s: ", __FILE_NAME__, __LINE__, __FUNCTION__); \ __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__); \ }while(0) #else #define LOGI(...) ((void)0) #define LOGW(...) ((void)0) #define LOGE(...) ((void)0) #define LOGD(...) ((void)0) #define LOGV(...) ((void)0) #endif void a_log_i(const char *fmt, va_list ap); void a_log_d(const char *fmt, va_list ap); void a_log_w(const char *fmt, va_list ap); void a_log_e(const char *fmt, va_list ap); #endif //ANDROID_LOG_H ``` log.cpp ```c++ // // Created by ozcom on 2023/10/8. // #include "log.h" void a_log_i(const char *fmt, va_list ap) { char TAG[64] = {0}; sprintf(TAG, "I/%s: ", __FILE_NAME__); __android_log_vprint(ANDROID_LOG_INFO, TAG, fmt, ap); } void a_log_d(const char *fmt, va_list ap) { char TAG[128] = {0}; sprintf(TAG, "%s line:%d D/%s: ", __FILE_NAME__, __LINE__, __FUNCTION__); __android_log_vprint(ANDROID_LOG_DEBUG, TAG, fmt, ap); } void a_log_w(const char *fmt, va_list ap) { char TAG[128] = {0}; sprintf(TAG, "%s line:%d W/%s: ", __FILE_NAME__, __LINE__, __FUNCTION__); __android_log_vprint(ANDROID_LOG_WARN, TAG, fmt, ap); } void a_log_e(const char *fmt, va_list ap) { char TAG[128] = {0}; sprintf(TAG, "%s line:%d E/%s: ", __FILE_NAME__, __LINE__, __FUNCTION__); __android_log_vprint(ANDROID_LOG_ERROR, TAG, fmt, ap); } ``` ## ALooper实现Java层Hander类似功能 实现与Java层类似功能组件:Looper 、Handler、HandlerThread > 核心知识: > > 1、ALooper原理 > > 2、Linux线程和进程的区别 > > 3、IPC通信中无名管道pipe的原理 > > 4、thread_local原理 [Handler.hpp]: we/src/main/cpp/ndkutil/Handler.hpp ```c++ // // Created by ozcom on 2023/10/14. // #ifndef FFMPEGDEMO_HANDLER_HPP #define FFMPEGDEMO_HANDLER_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #include #include #include #ifdef __cplusplus } #endif namespace we { class pipe_error : public std::bad_exception { public: pipe_error() : pipe_error("pipe: ") {} pipe_error(std::string what) : m_sWhat(what) { m_sWhat.append(strerror(errno)); } const char *what() const noexcept override { return m_sWhat.c_str(); } private: std::string m_sWhat; }; class bad_looper : public std::exception { public: bad_looper(std::string msg) : msg_(std::move(msg)) {} const char *what() const noexcept override { return msg_.c_str(); } private: std::string msg_; }; class Handler; using MSG_CB_FUN = void (*)(); enum MSG_TYPE : int32_t { COMMON, ASYNC, RENDING, IPC }; struct Message final { int32_t when; int32_t id; mutable MSG_TYPE type; int64_t v1; int64_t v2; int64_t v3; mutable Handler const *tag; MSG_CB_FUN cb; void *data; }; class Looper final { public: using PIPE_FD = std::array; using MSG_QUEUE = std::deque; private: Looper(); void handle_(); static int32_t looperCallback_(int32_t fd, int32_t events, void *data); public: virtual ~Looper(); bool isMainLooper() const; volatile bool looping() const; void sendMessage(const Message &msg); void quit(); static Looper &myLooper(); static void loop(); private: const bool isMainLooper_; volatile bool looping_{false}; ALooper *looper_; PIPE_FD pipeFD_{}; MSG_QUEUE que_{}; std::mutex mutex_{}; }; class Handler final { public: class Callback { public: virtual void handleMessage(const Message &msg) = 0; }; using HandleCallback = void (*)(const Message &msg); inline explicit Handler(Looper *looper = &Looper::myLooper()) : looper_(looper) {} inline ~Handler() { cb_ = nullptr; hcb_ = nullptr; LOGD("--->~Handler"); } virtual void handleMessage(const Message &msg) const; void post(MSG_CB_FUN running) const; void sendMessage(const Message &msg, MSG_TYPE msgType = COMMON) const noexcept; void sendAsyncMessage(const Message &msg) const noexcept; void sendRenderMessage(const Message &msg) const noexcept; void operator<<(const Message &msg) { sendMessage(msg); } void setHandleCallback(HandleCallback cb) { if (cb) hcb_ = cb; } void setCallback(Callback *cb) { if (cb) cb_ = cb; } Looper *getLooper() const; private: Callback *cb_{}; HandleCallback hcb_{}; Looper *looper_; }; /* 不适用于Android主线程, 主线程推荐使用异步方式 */ class HandlerThread final { public: class Callback { public: virtual void onLooperCompleted(Looper *looper) = 0; }; explicit HandlerThread(std::string name, Callback *cb = nullptr); void start(); void quit() const; Looper *getLooper() const; private: void start_(); private: Looper *looper_{}; Callback *cb_; std::string name_; }; } #endif //FFMPEGDEMO_HANDLER_HPP ``` [Handler.cpp]: we/src/main/cpp/ndkutil/Handler.cpp ```c++ // // Created by ozcom on 2023/10/14. // #include "Handler.hpp" namespace we { Looper::Looper() : isMainLooper_(gettid() == getpid()) { looper_ = ALooper_prepare(0); if (looper_) ALooper_acquire(looper_); if (pipe(pipeFD_.data()) == -1) throw pipe_error(); if (ALooper_addFd(looper_, pipeFD_[0], 0, ALOOPER_EVENT_INPUT, looperCallback_, (void *) this) == -1) throw bad_looper{"ALooper_addFd error"}; } Looper::~Looper() { looping_ = false; if (pipeFD_[0] >= 0) { ALooper_removeFd(looper_, pipeFD_[0]); close(pipeFD_[0]); } if (pipeFD_[1] >= 0) close(pipeFD_[1]); ALooper_release(looper_); looper_ = nullptr; LOGD("--->Looper::~Looper"); } bool Looper::isMainLooper() const { return isMainLooper_; } volatile bool Looper::looping() const { return looping_; } Looper &Looper::myLooper() { thread_local Looper l{}; return l; } void Looper::loop() { if (getpid() != gettid()) myLooper().handle_(); } void Looper::sendMessage(const Message &msg) { if (!looping_)return; std::unique_lock lock{mutex_}; if (msg.type == IPC || msg.type == COMMON) { write(pipeFD_[1], &msg, sizeof(msg)); } else if (msg.type == RENDING) { que_.push_front(msg); ALooper_wake(looper_); } else { que_.push_back(msg); ALooper_wake(looper_); } } void Looper::quit() { std::unique_lock lock{mutex_}; looping_ = false; que_.clear(); } void Looper::handle_() { int32_t ret; looping_ = true; while (looping_) { //rending or async message if (!que_.empty()) { std::unique_lock lock{mutex_}; auto &msg = que_.front(); if (msg.tag) { msg.tag->handleMessage(msg); } if (msg.cb) { msg.cb(); } que_.pop_front(); } //IPC or common message ret = ALooper_pollOnce(-1, nullptr, nullptr, nullptr); if (ret == ALOOPER_POLL_ERROR) { LOGW("--->ALooper_pollOnce: ALOOPER_POLL_ERROR"); } else if (ret == ALOOPER_POLL_TIMEOUT) { LOGW("--->ALooper_pollOnce: ALOOPER_POLL_TIMEOUT"); } else if (ret == ALOOPER_POLL_WAKE) { // LOGW("--->ALooper_pollOnce: ALOOPER_POLL_WAKE"); } } } int32_t Looper::looperCallback_(int32_t fd, int32_t events, void *data) { Message msg{}; read(fd, &msg, sizeof(msg)); if (msg.tag) { msg.tag->handleMessage(msg); } if (msg.cb) { msg.cb(); } return 1; } } namespace we { void Handler::post(MSG_CB_FUN running) const { Message msg{}; msg.when = -1; msg.id = -1; msg.type = COMMON; msg.tag = this; msg.cb = running; msg.data = nullptr; if (looper_)looper_->sendMessage(msg); } void Handler::sendMessage(Message const &msg, MSG_TYPE msgType) const noexcept { msg.type = msgType; msg.tag = this; if (looper_)looper_->sendMessage(msg); } void Handler::handleMessage(const Message &msg) const { if (cb_)cb_->handleMessage(msg); if (hcb_)hcb_(msg); } void Handler::sendAsyncMessage(const Message &msg) const noexcept { sendMessage(msg, ASYNC); } void Handler::sendRenderMessage(const Message &msg) const noexcept { sendMessage(msg, RENDING); } Looper *Handler::getLooper() const { return looper_; } } namespace we { HandlerThread::HandlerThread(std::string name, Callback *cb) : name_(std::move(name)), cb_(cb) {} void HandlerThread::start() { std::thread t{[this]() { start_(); }}; pthread_setname_np(t.native_handle(), name_.c_str()); t.detach(); } void HandlerThread::quit() const { if (looper_) looper_->quit(); } Looper *HandlerThread::getLooper() const { return looper_; } void HandlerThread::start_() { looper_ = &Looper::myLooper(); if (cb_)cb_->onLooperCompleted(looper_); Looper::loop(); } } ``` [Hnadler的测试demo]: we/src/main/cpp/we_jni.cpp ```c++ #include "we.hpp" #include "ndkutil/log.h" static std::unique_ptr g_WeJNIContext; std::unique_ptr &getWeJNIContext() noexcept { return g_WeJNIContext; } extern jni_native_method_vector WE_JNI_METHOD_VEC; static constexpr char WE_JAVA_CLASS_NAME[]{"com/oz/we/We"}; extern "C" JNIEXPORT void JNICALL nativeInit(JNIEnv *env, jobject thiz); extern "C" JNIEXPORT void JNICALL nativeRelease(JNIEnv *env, jobject thiz); extern "C" JNIEXPORT jlong JNICALL nativeGetVersion(JNIEnv *env, jclass clazz); extern "C" JNIEXPORT void JNICALL nativeTestMutilThreadObjectCallback(JNIEnv *env, jobject thiz); extern "C" JNIEXPORT void JNICALL nativeTestMutilThreadClassCallback(JNIEnv *env, jclass clazz); JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { g_WeJNIContext = std::make_unique(vm); g_WeJNIContext->setUserdata(jni_we{}); {//设置用户数据 auto env = g_WeJNIContext->getEnv(); auto pWe = g_WeJNIContext->getUserdata(); jclass cls = env->FindClass(WE_JAVA_CLASS_NAME); pWe->glob_class = cls; pWe->methodId_mutilThreadObjectCallback = env->GetMethodID(cls, "mutilThreadObjectCallback", "()V"); pWe->methodId_mutilThreadClassCallback = env->GetStaticMethodID(cls, "mutilThreadClassCallback", "(J)V"); } g_WeJNIContext->registerNatives(WE_JNI_METHOD_VEC); LOGD("--->WeJNIContext JNI_OnLoad: pid = %d, tid = %d\n", getpid(), gettid()); return g_WeJNIContext->getJniVersion(); } JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *reserved) { } jni_native_method_vector WE_JNI_METHOD_VEC{ {WE_JAVA_CLASS_NAME, { {"nativeInit", "()V", reinterpret_cast(nativeInit) }, {"nativeRelease", "()V", reinterpret_cast(nativeRelease) }, {"nativeGetVersion", "()J", reinterpret_cast(nativeGetVersion) }, {"nativeTestMutilThreadClassCallback", "()V", reinterpret_cast(nativeTestMutilThreadObjectCallback) }, {"nativeTestMutilThreadClassCallback", "()V", reinterpret_cast(nativeTestMutilThreadClassCallback) } } } }; extern "C" JNIEXPORT void JNICALL nativeInit(JNIEnv *env, jobject thiz) { if (auto *pWe = getWeJNIContext()->getUserdata()) { pWe->glob_object = env->NewWeakGlobalRef(thiz); } else { getWeJNIContext()->setUserdata(jni_we{ env->GetObjectClass(thiz), env->NewWeakGlobalRef(thiz), env->GetMethodID(env->GetObjectClass(thiz), "mutilThreadObjectCallback", "()V"), env->GetStaticMethodID(env->GetObjectClass(thiz), "mutilThreadClassCallback", "(J)V") }); } LOGD("--->We.nativeInit: pid = %d, tid = %d\n", getpid(), gettid()); } extern "C" JNIEXPORT void JNICALL nativeRelease(JNIEnv *env, jobject thiz) { if (auto *p = getWeJNIContext()->getUserdata()) p->release(); LOGD("--->We.nativeRelease: pid = %d, tid = %d\n", getpid(), gettid()); } extern "C" JNIEXPORT jlong JNICALL nativeGetVersion(JNIEnv *env, jclass clazz) { LOGD("--->We.nativeGetVersion: pid = %d, tid = %d\n", getpid(), gettid()); return 1234567890; } extern "C" JNIEXPORT void JNICALL nativeTestMutilThreadObjectCallback(JNIEnv *env, jobject thiz) { LOGD("--->We.nativeTestMutilThreadObjectCallback: pid = %d, tid = %d\n", getpid(), gettid()); std::thread([]() { LOGD("--->We.nativeTestMutilThreadObjectCallback.std::thread start: pid = %d, tid = %d\n", getpid(), gettid()); getWeJNIContext()->getHandler().post([]() { LOGD("--->We.nativeTestMutilThreadObjectCallback.std::thread.getHandler.post start: pid = %d, tid = %d\n", getpid(), gettid()); if (auto *pWe = getWeJNIContext()->getUserdata()) { auto *pEnv = getWeJNIContext()->getEnv(); if (pWe->glob_object && pWe->methodId_mutilThreadObjectCallback) pEnv->CallVoidMethod(pWe->glob_object, pWe->methodId_mutilThreadObjectCallback); LOGD("--->We.nativeTestMutilThreadObjectCallback.std::thread.getHandler.post working: pid = %d, tid = %d\n", getpid(), gettid()); } LOGD("--->We.nativeTestMutilThreadObjectCallback.std::thread.getHandler.post exit: pid = %d, tid = %d\n", getpid(), gettid()); }); LOGD("--->We.nativeTestMutilThreadObjectCallback.std::thread exit: pid = %d, tid = %d\n", getpid(), gettid()); }).join(); } extern "C" JNIEXPORT void JNICALL nativeTestMutilThreadClassCallback(JNIEnv *env, jclass clazz) { LOGD("--->We.nativeTestMutilThreadClassCallback: pid = %d, tid = %d\n", getpid(), gettid()); std::thread([]() { LOGD("--->We.nativeTestMutilThreadClassCallback.std::thread start: pid = %d, tid = %d\n", getpid(), gettid()); getWeJNIContext()->getHandler().post([]() { LOGD("--->We.nativeTestMutilThreadClassCallback.std::thread.getHandler.post start: pid = %d, tid = %d\n", getpid(), gettid()); if (auto *pWe = getWeJNIContext()->getUserdata()) { auto *pEnv = getWeJNIContext()->getEnv(); if (pWe->glob_class && pWe->methodId_mutilThreadClassCallback) pEnv->CallStaticVoidMethod(pWe->glob_class, pWe->methodId_mutilThreadObjectCallback, getpid()); LOGD("--->We.nativeTestMutilThreadClassCallback.std::thread.getHandler.post working: pid = %d, tid = %d\n", getpid(), gettid()); } LOGD("--->We.nativeTestMutilThreadClassCallback.std::thread.getHandler.post exit: pid = %d, tid = %d\n", getpid(), gettid()); }); LOGD("--->We.nativeTestMutilThreadClassCallback.std::thread.getHandler.post exit: pid = %d, tid = %d\n", getpid(), gettid()); }).join(); } ``` ## Ndk接收Vsync信号 ### Choreograph AChoreographer是NDK提供Vsync信号接收接口,由于Vsync信号回调函数所在的线程和使用Vsync信号不同,因此使用不当容易出现内存泄漏问题,会导致程序崩溃。Choreograph的实现采用ALooper+AChoreographer+pipe的方式,规避了内存泄漏问题,且易于使用。 Choreograph.h ```c++ // // Created by ozcom on 2023/11/22. // #ifndef FFMPEGDEMO_CHOREOGRAPH_H #define FFMPEGDEMO_CHOREOGRAPH_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #include #include #include #ifdef __cplusplus } #endif namespace we { inline namespace helper { class Tube final { public: inline Tube() { int ret = pipe(pipeFD_.data()); assert(ret != -1); } inline ~Tube() { if (pipeFD_[0] >= 0) { close(pipeFD_[0]); pipeFD_[0] = -1; } if (pipeFD_[1] >= 0) { close(pipeFD_[1]); pipeFD_[1] = -1; } } int32_t getReadChannel() const { return pipeFD_[0]; } int32_t getWriteChannel() const { return pipeFD_[1]; } private: std::array pipeFD_{}; }; } class Choreograph final { public: struct Vsync { int64_t vsyncTimeNs; }; struct Callback { virtual void onVsync(const Vsync &v) = 0; }; static Choreograph &getInstance() { static Choreograph ins{}; return ins; } ~Choreograph(); void addCallback(Callback *cb); void removeCallback(Callback *cb); private: Choreograph(); static int32_t looperCallback_(int32_t fd, int32_t events, void *data); static void frameCallback_(long frameTimeNs, void *data); void initLooper_(); void dispatch_(const Vsync &v); void scheduled_(); void post_(); private: volatile bool starting{false}; ALooper *looper_{}; Tube tube_{}; std::list cbs_{}; }; } // we #endif //FFMPEGDEMO_CHOREOGRAPH_H ``` Choreograph.cpp ```c++ // // Created by ozcom on 2023/11/22. // #include "Choreograph.h" namespace we { Choreograph::Choreograph() = default; Choreograph::~Choreograph() { cbs_.clear(); ALooper_removeFd(looper_, tube_.getReadChannel()); ALooper_release(looper_); looper_ = nullptr; } void Choreograph::addCallback(Callback *cb) { if (cb) { cbs_.push_back(cb); if (!starting)scheduled_(); } } void Choreograph::removeCallback(Callback *cb) { if (cb) cbs_.remove(cb); starting = !cbs_.empty(); } int32_t Choreograph::looperCallback_(int32_t fd, int32_t events, void *data) { auto *p = reinterpret_cast(data); if (p && p->starting) { // p->post_(); Vsync v{}; read(fd, &v, sizeof(Vsync)); p->dispatch_(v); } return 1; } void Choreograph::initLooper_() { looper_ = ALooper_prepare(0); if (looper_) ALooper_acquire(looper_); int ret = ALooper_addFd(looper_, tube_.getReadChannel(), 0, ALOOPER_EVENT_INPUT, looperCallback_, this); assert(ret != -1); } void Choreograph::dispatch_(const Vsync &v) { for (auto *cb: cbs_) { cb->onVsync(v); } } void Choreograph::scheduled_() { starting = true; std::thread{[](Choreograph *p) { if (p) { p->initLooper_(); p->post_(); } int32_t ret; while (p && p->starting) { ret = ALooper_pollOnce(-1, nullptr, nullptr, nullptr); if (ret == ALOOPER_POLL_ERROR) { LOGE("--->Choreograph::scheduled_: ALOOPER_POLL_ERROR"); } else if (ret == ALOOPER_POLL_TIMEOUT) { LOGD("--->Choreograph::scheduled_: ALOOPER_POLL_TIMEOUT"); } else if (ret == ALOOPER_POLL_WAKE) { LOGD("--->Choreograph::scheduled_: ALOOPER_POLL_WAKE"); } } p->starting = false; }, this}.detach(); } void Choreograph::post_() { AChoreographer_postFrameCallback(AChoreographer_getInstance(), frameCallback_, (void *) tube_.getWriteChannel()); } void Choreograph::frameCallback_(long frameTimeNs, void *data) { auto wfd = reinterpret_cast(data); if (wfd > 0) { Vsync v{frameTimeNs}; int ret = write(static_cast(wfd), &v, sizeof(Vsync)); if (ret == -1 && (errno == EBADF || errno == EPIPE)) { LOGE("--->Choreograph::frameCallback_->write: %s", strerror(errno)); return; } AChoreographer_postFrameCallback(AChoreographer_getInstance(), frameCallback_, data); } } } // we ``` ## NdkCamera的使用 ## NdkMediaCodec的使用 # FFmpeg ## 编译Android平台ffmpeg动态连接库 ## 使用popen执行ffmpeg和ffprobe > 核心知识 > > 1、高级管道popen > > 2、Linux子进程创建和回收 > > 3、Linux信号处理 ```c++ #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __cplusplus } #endif #include #include #include #include #include #include #include #include #include #include "ndkutil/log.h" #include "ndkutil/misc.h" #include "ndkutil/JNIContext.hpp" static std::unique_ptr g_AvJNIContext; std::unique_ptr &getAvJNIContext() noexcept { return g_AvJNIContext; } extern "C" JNIEXPORT jint JNICALL nativeForkHandle(JNIEnv *env, jclass clazz, jstring _cmd_dir, jstring _cmd); extern "C" JNIEXPORT jint JNICALL nativeFFprobe(JNIEnv *env, jclass clazz, jstring _workdir, jstring _path); static jni_native_method_vector AV_JNI_METHOD_VEC{ {"com/oz/we/av/FFmpeg", { {"nativeForkHandle", "(Ljava/lang/String;Ljava/lang/String;)I", reinterpret_cast(nativeForkHandle) }, {"nativeFFprobe", "(Ljava/lang/String;Ljava/lang/String;)I", reinterpret_cast(nativeFFprobe) } } } }; JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { g_AvJNIContext = std::make_unique(vm); g_AvJNIContext->registerNatives(AV_JNI_METHOD_VEC); return g_AvJNIContext->getJniVersion(); } JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *reserved) { } using namespace std::chrono; using std::string; using std::stringstream; using std::array; using std::vector; template using string_array = array; using string_vector = vector; using String = char *; static inline void sys_err(const char *s, bool cond = true) noexcept { if (cond) { LOGE("%s: %s", s, strerror(errno)); exit(EXIT_FAILURE); } } static int common_pid{}; void recycle_child_process_handle(int sig) { LOGD("signal: %s\n", strsignal(sig)); int stat; int wpid; while ((wpid = waitpid(-1, &stat, WNOHANG)) > 0) { if (common_pid == wpid) { if (WIFEXITED(stat)) { //true->进程正常结束 LOGD("--->child process %d exit with %d\n", common_pid, WEXITSTATUS(stat)); //获取exit()的进程退出参数 } else if (WIFSIGNALED(stat)) {//true->进程异常终止 LOGD("--->child process %d kill with signal %d\n", common_pid, WTERMSIG(stat)); //获取使得进程终止的信号编号 } else if (WIFSTOPPED(stat)) {//true->进程处于暂停状态 LOGD("--->child process %d stop with signal %d\n", common_pid, WSTOPSIG(stat)); // 获取使得进程暂停的信号编号 } else if (WIFCONTINUED(stat)) {//true->进程暂停后继续运行 LOGD("--->child process %d continue\n", common_pid); } } } } static int fork_handle(const char *_cmd_dir, const char *_cmd) noexcept { int ret; { struct sigaction act{}; struct sigaction old_act{}; act.sa_flags = 0; act.sa_handler = recycle_child_process_handle; ret = sigaction(SIGCHLD, &act, &old_act); if (ret == -1) { LOGE("--->sigaction: %s\n", strerror(errno)); return -1; } } ret = fork(); if (ret < 0) { LOGE("--->fork: %s", strerror(errno)); return ret; } else if (ret == 0) { LOGD("--->I'm child process %d start\n", getpid()); { { std::stringstream ss{}; ss << _cmd_dir << ":" << getenv("PATH"); setenv("PATH", ss.str().c_str(), 1); } FILE *fp = popen(_cmd, "r"); char buf[BUFSIZ]{}; while (fgets(buf, BUFSIZ, fp)) { LOGD("--->%s", buf); } pclose(fp); } exit(EXIT_SUCCESS); } else { common_pid = ret; LOGI("--->fork child process %d\n", ret); return ret; } } extern "C" JNIEXPORT jint JNICALL nativeForkHandle(JNIEnv *env, jclass clazz, jstring _cmd_dir, jstring _cmd) { const char *cmd_dir_ = env->GetStringUTFChars(_cmd_dir, nullptr); LOGD("--->cmd_dir_ = %s\n", cmd_dir_); const char *cmd_ = env->GetStringUTFChars(_cmd, nullptr); LOGD("--->cmd_ = %s\n", cmd_); std::string cmd_dir{cmd_dir_}; std::string cmd{cmd_}; env->ReleaseStringUTFChars(_cmd, cmd_); env->ReleaseStringUTFChars(_cmd_dir, cmd_dir_); int ret = fork_handle(cmd_dir.c_str(), cmd.c_str()); return ret; } ``` # OpenCV ## 人脸识别 ## 对象检测 ## 绘制直方图 # OpenGL ## OpenGLES画三角形 ### 基础类和接口 #### Shader > 用于管理Shader Program对象 Shader.hpp ```c++ // // Created by ozcom on 2023/11/10. // #ifndef FFMPEGDEMO_SHADER_HPP #define FFMPEGDEMO_SHADER_HPP #include #include "gl_ins.h" #include "ndkutil/NdkHelper.h" #include "water/core/Context.h" namespace we { using namespace std; class Shader { public: Shader(); explicit inline Shader(const Shader &ref) noexcept: program_(ref.program_) { }; explicit inline Shader(Shader &&ref) noexcept: program_(ref.program_) { ref.program_ = 0; }; inline Shader &operator=(const Shader &ref) noexcept { if (this != &ref) { program_ = ref.program_; } return *this; } inline Shader &operator=(Shader &&ref) noexcept { if (this != &ref) { program_ = ref.program_; ref.program_ = 0; } return *this; } inline operator bool() const { return glIsProgram(program_); } virtual ~Shader(); void load(const char *vertexSrc, const char *fragmentSrc, const char *computeSrc = nullptr); void load(IResManager const *const resManager, const char *vertexFilename, const char *fragmentFilename, const char *computeFilename = nullptr); void use() const; void disuse() const; int getUniformLocation(const char *name) const; template inline void setUniform(const int &location, Tp const &v) const { if (is_same_v) { glProgramUniform1f(program_, location, v); } else if (is_same_v || is_same_v) { glProgramUniform1i(program_, location, v); } else if (is_same_v) { glProgramUniform1ui(program_, location, v); } } template inline void setUniform(int const &location, Tp const &v) const { if (is_same_v) { glProgramUniform1f(program_, location, v); } else if (is_same_v || is_same_v) { glProgramUniform1i(program_, location, v); } else if (is_same_v) { glProgramUniform1ui(program_, location, v); } } template inline void setUniform(const char *name, Tp const &v) const { setUniform(getUniformLocation(name), v); } template inline void setUniform(int const &location, Tp const &v0, Tp const &v1) const { if (is_same_v) { glProgramUniform2f(program_, location, v0, v1); } else if (is_same_v) { glProgramUniform2i(program_, location, v0, v1); } else if (is_same_v) { glProgramUniform2ui(program_, location, v0, v1); } } template inline void setUniform(const char *name, Tp const &v0, Tp const &v1) const { setUniform(getUniformLocation(name), v0, v1); } template inline void setUniform(int const &location, Tp const &v0, Tp const &v1, Tp const &v2) const { if (is_same_v) { glProgramUniform3f(program_, location, v0, v1, v2); } else if (is_same_v) { glProgramUniform3i(program_, location, v0, v1, v2); } else if (is_same_v) { glProgramUniform3ui(program_, location, v0, v1, v2); } } template inline void setUniform(const char *name, Tp const &v0, Tp const &v1, Tp const &v2) const { setUniform(getUniformLocation(name), v0, v1, v2); } template inline void setUniform(int const &location, Tp const &v0, Tp const &v1, Tp const &v2, Tp const &v3) const { if (is_same_v) { glProgramUniform4f(program_, location, v0, v1, v2, v3); } else if (is_same_v) { glProgramUniform4i(program_, location, v0, v1, v2, v3); } else if (is_same_v) { glProgramUniform4ui(program_, location, v0, v1, v2, v3); } } template inline void setUniform(const char *name, Tp const &v0, Tp const &v1, Tp const &v2, Tp const &v3) const { setUniform(getUniformLocation(name), v0, v1, v2, v3); } void setUniformMatrix4fv(int const &location, const float *v) const; void setUniformMatrix4fv(const char *name, const float *v) const { setUniformMatrix4fv(getUniformLocation(name), v); } template inline Tp getUniform1(int const &location) const { Tp v{}; if (is_same_v) { glProgramUniform1fv(program_, location, 1, &v); } else if (is_same_v || is_same_v) { glProgramUniform1iv(program_, location, 1, &v); } else if (is_same_v) { glProgramUniform1uiv(program_, location, 1, &v); } return v; } template inline Tp getUniform1(const char *name) const { return getUniform1(getUniformLocation(name)); } template inline const Tp *getUniform2(int const &location) const { Tp v[2]; if (is_same_v) { glProgramUniform2fv(program_, location, 2, v); } else if (is_same_v) { glProgramUniform2iv(program_, location, 2, v); } else if (is_same_v) { glProgramUniform2uiv(program_, location, 2, v); } return v; } template inline Tp getUniform2(const char *name) const { return getUniform2(getUniformLocation(name)); } template inline const Tp *getUniform3(int const &location) const { Tp v[3]; if (is_same_v) { glProgramUniform3fv(program_, location, 3, v); } else if (is_same_v) { glProgramUniform3iv(program_, location, 3, v); } else if (is_same_v) { glProgramUniform3uiv(program_, location, 3, v); } return v; } template inline Tp getUniform3(const char *name) const { return getUniform3(getUniformLocation(name)); } template inline const Tp *getUniform4(int const &location) const { Tp v[4]; if (is_same_v) { glProgramUniform4fv(program_, location, 4, v); } else if (is_same_v) { glProgramUniform4iv(program_, location, 4, v); } else if (is_same_v) { glProgramUniform4uiv(program_, location, 4, v); } return v; } template inline Tp getUniform4(const char *name) const { return getUniform4(getUniformLocation(name)); } private: GLuint program_; }; } #endif //FFMPEGDEMO_SHADER_HPP ``` Shader.cpp ```c++ // // Created by ozcom on 2023/11/10. // #include "shader.hpp" #include "ndkutil/log.h" #include #include using namespace std; using namespace we; static GLuint compile(const char *source, int len, GLuint shaderType) { GLuint shader; shader = glCreateShader(shaderType); glShaderSource(shader, 1, &source, &len); glCompileShader(shader); GLint ret; GLchar infoLog[512]; glGetShaderiv(shader, GL_COMPILE_STATUS, &ret); if (!ret) { glGetShaderInfoLog(shader, 512, nullptr, infoLog); LOGE("--->Shader.compile:ERROR::SHADER::VERTEX::COMPILATION_FAILED\n %s", infoLog); return ret; } return shader; } static GLuint loadShader(GLuint program, const char *source, int len, GLuint shaderType) { GLuint shader = compile(source, len, shaderType); glAttachShader(program, shader); glLinkProgram(program); glDeleteShader(shader); } Shader::Shader() : program_(0) { } Shader::~Shader() { if (program_ > 0)glDeleteProgram(program_); } void Shader::load(const char *vertexSrc, const char *fragmentSrc, const char *computeSrc) { if (vertexSrc == nullptr || fragmentSrc == nullptr) { throw std::invalid_argument{"vertexSrc != nullptr && fragmentSrc != nullptr"}; } LOGD("--->Shader::load: vertexSrc{\n%s\n}", vertexSrc); LOGD("--->Shader::load: fragmentSrc{\n%s\n}", fragmentSrc); if (computeSrc)LOGD("--->Shader::load: computeSrc{\n%s\n}", computeSrc); // vertex shader GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &vertexSrc, nullptr); glCompileShader(vertexShader); // check for shader compile errors int success; char infoLog[512]; glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(vertexShader, 512, nullptr, infoLog); LOGE("--->Shader::load: ERROR::SHADER::VERTEX::COMPILATION_FAILED: %s\n", infoLog); } // fragment shader GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fragmentSrc, nullptr); glCompileShader(fragmentShader); // check for shader compile errors glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(fragmentShader, 512, nullptr, infoLog); LOGE("--->Shader::load: ERROR::SHADER::FRAGMENT::COMPILATION_FAILED: %s\n", infoLog); } // compute shader GLuint computeShader; if (computeSrc) { computeShader = glCreateShader(GL_COMPUTE_SHADER); glShaderSource(computeShader, 1, &computeSrc, nullptr); glCompileShader(computeShader); // check for shader compile errors glGetShaderiv(computeShader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(computeShader, 512, nullptr, infoLog); LOGE("--->Shader::load: ERROR::SHADER::COMPUTE::COMPILATION_FAILED: %s\n", infoLog); } } // link shaders GLuint shaderProgram = glCreateProgram(); glAttachShader(shaderProgram, vertexShader); glAttachShader(shaderProgram, fragmentShader); if (computeSrc)glAttachShader(shaderProgram, computeShader); glLinkProgram(shaderProgram); // check for linking errors glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); if (!success) { glGetProgramInfoLog(shaderProgram, 512, nullptr, infoLog); LOGE("--->Shader::load: ERROR::SHADER::PROGRAM::LINKING_FAILED: %s\n", infoLog); } glDeleteShader(vertexShader); glDeleteShader(fragmentShader); if (computeSrc)glDeleteShader(computeShader); program_ = shaderProgram; } void Shader::load(const IResManager *const resManager, const char *vertexFilename, const char *fragmentFilename, const char *computeFilename) { if (resManager == nullptr || vertexFilename == nullptr || fragmentFilename == nullptr) { throw std::invalid_argument{ "resManager != nullptr && vertexFilename != nullptr && fragmentFilename != nullptr"}; } const auto &vertexSrc = resManager->readAsset(vertexFilename); if (!vertexSrc) { std::stringstream ss; ss << vertexFilename << " file does not exist!"; throw std::invalid_argument{ss.str().c_str()}; } const auto &fragmentSrc = resManager->readAsset(fragmentFilename); if (!fragmentSrc) { std::stringstream ss; ss << fragmentFilename << " file does not exist!"; throw std::invalid_argument{ss.str().c_str()}; } bool loaded = false; if (computeFilename) { const auto &computeSrc = resManager->readAsset(computeFilename); if (computeSrc) { load((*vertexSrc).c_str(), (*fragmentSrc).c_str(), (*computeSrc).c_str()); loaded = true; } } if (!loaded) { load((*vertexSrc).c_str(), (*fragmentSrc).c_str()); } } void Shader::use() const { glUseProgram(program_); } void Shader::disuse() const { glUseProgram(GL_NONE); } int Shader::getUniformLocation(const char *name) const { return glGetUniformLocation(program_, name); } void Shader::setUniformMatrix4fv(const int &location, const float *v) const { glProgramUniformMatrix4fv(program_, location, 1, false, v); } ``` #### Vertex > 用管理OpenGL顶点对象 > > 1、VBO > > 2、EBO > > 3、VAO > > 4、Vertex用于综合管理VBO、VAO、EBO对象 Vertex.h ```c++ // // Created by ozcom on 2023/11/26. // #ifndef FFMPEGDEMO_VERTEX_H #define FFMPEGDEMO_VERTEX_H #include #include #include #include #include "gl_ins.h" #include "ndkutil/NdkHelper.h" #include "water/core/Context.h" namespace we { inline namespace helper { template struct num_same : public std::false_type { }; template struct num_same : public std::true_type { }; template constexpr bool num_same_v = num_same::value; template inline std::array genVertexArrays() noexcept { std::array arr{}; if (Num) { glGenVertexArrays(Num, arr.data()); assert(glGetError() == GL_NO_ERROR); } return arr; } template inline std::array genBuffers() noexcept { std::array arr{}; if (Num) { glGenBuffers(Num, arr.data()); assert(glGetError() == GL_NO_ERROR); } return arr; } } template class Vertex { public: inline Vertex() noexcept: vaos_(genVertexArrays<_A_Num_>()), vbos_(genBuffers<_B_Num_>()), ebos_(genBuffers<_E_Num_>()) {} inline virtual ~Vertex() noexcept { glDeleteBuffers(vbos_.size(), vbos_.data()); glDeleteBuffers(ebos_.size(), ebos_.data()); glDeleteVertexArrays(vaos_.size(), vaos_.data()); } template inline void bindVBO(const Tps... vertices) { bindVBO(std::array{vertices...}); } template inline void bindVBO(const T (&vertices)[N]) { if (!_B_Num_)return; if (_A_Num_)glBindVertexArray(vaos_[Idx]); glBindBuffer(GL_ARRAY_BUFFER, vbos_[Idx]); glBufferData(GL_ARRAY_BUFFER, sizeof(T) * N, vertices, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); if (_A_Num_)glBindVertexArray(0); } template inline void bindVBO(const std::array &vertices) { if (!_B_Num_)return; if (_A_Num_)glBindVertexArray(vaos_[Idx]); glBindBuffer(GL_ARRAY_BUFFER, vbos_[Idx]); glBufferData(GL_ARRAY_BUFFER, sizeof(T) * N, vertices.data(), GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); if (_A_Num_)glBindVertexArray(0); } template inline void bindEBO(const Tps... indices) { bindEBO(std::array{indices...}); } template inline void bindEBO(const T (&indices)[N]) { if (!_E_Num_)return; if (_A_Num_)glBindVertexArray(vaos_[Idx]); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebos_[Idx]); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(T) * N, indices, GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); if (_A_Num_)glBindVertexArray(0); } template inline void bindEBO(const std::array &indices) { if (!_E_Num_)return; if (_A_Num_)glBindVertexArray(vaos_[Idx]); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebos_[Idx]); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(T) * N, indices.data(), GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); if (_A_Num_)glBindVertexArray(0); } template inline void vertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer) { if (!_B_Num_)return; if (_A_Num_)glBindVertexArray(vaos_[Idx]); glBindBuffer(GL_ARRAY_BUFFER, vbos_[Idx]); glVertexAttribPointer(index, size, type, normalized, stride, pointer); glEnableVertexAttribArray(index); glBindBuffer(GL_ARRAY_BUFFER, 0); if (_A_Num_)glBindVertexArray(0); } inline void enableVertexAttribArray(GLuint index) { glEnableVertexAttribArray(index); } inline void disableVertexAttribArray(GLuint index) { glDisableVertexAttribArray(index); } template inline void bindVAO() { if (_A_Num_)glBindVertexArray(vaos_[Idx]); } inline void unbindVAO() { if (_A_Num_)glBindVertexArray(0); } private: std::array vbos_; std::array ebos_; std::array vaos_; }; // template // explicit // Vertex(const Tps...vertices) -> Vertex, sizeof...(vertices), Tps...>; enum class __BO_TYPE { _VBO_, _EBO_ }; template<__BO_TYPE type> class __BO final { public: inline __BO() : bo_(genBuffers()[0]) {} inline ~__BO() noexcept { if (bo_)glDeleteBuffers(1, &bo_); } inline operator bool() const { return glIsBuffer(bo_); } inline operator unsigned int() const { return bo_; } template inline void bindData(const Tps ...data) { LOGD("--->__BO::bindData: const Tps... data"); bindData(std::array, sizeof...(data)>{data...}); } template inline void bindData(const T (&data)[N]) { size_ = N; if constexpr (type == __BO_TYPE::_EBO_) { glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(T) * N, data, GL_STATIC_DRAW); LOGD("--->__BO::bindData:[] __BO_TYPE::_EBO_ sizeof(T)=%u, N=%u", sizeof(T), N); } else if constexpr (type == __BO_TYPE::_VBO_) { glBufferData(GL_ARRAY_BUFFER, sizeof(T) * N, data, GL_STATIC_DRAW); LOGD("--->__BO::bindData:[] __BO_TYPE::_VBO_ sizeof(T)=%u, N=%u", sizeof(T), N); } } template inline void bindData(const std::array &data) { size_ = N; if constexpr (type == __BO_TYPE::_EBO_) { glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(T) * N, (const void *) data, GL_STATIC_DRAW); LOGD("--->__BO::bindData:array __BO_TYPE::_EBO_ sizeof(T)=%u, N=%u", sizeof(T), N); } else if constexpr (type == __BO_TYPE::_VBO_) { glBufferData(GL_ARRAY_BUFFER, sizeof(T) * N, data, GL_STATIC_DRAW); LOGD("--->__BO::bindData:array __BO_TYPE::_VBO_ sizeof(T)=%u, N=%u", sizeof(T), N); } } inline bool empty() const { return !size_; } inline size_t getSize() const { return size_; } private: GLuint bo_; std::size_t size_{}; }; class __VAO final { public: inline __VAO() : vao_(genVertexArrays()[0]) {} inline ~__VAO() noexcept { if (vao_)glDeleteVertexArrays(1, &vao_); } inline operator bool() { return glIsVertexArray(vao_); } inline operator unsigned int() { return vao_; } private: GLuint vao_; }; using VBO = __BO<__BO_TYPE::_VBO_>; using EBO = __BO<__BO_TYPE::_EBO_>; using VAO = __VAO; } // we #endif //FFMPEGDEMO_VERTEX_H ``` Vertex.cpp ```c++ // // Created by ozcom on 2023/11/26. // #include "vertex.h" namespace we { } // we ``` ### 绘制三角形的4中方式 #### 方式1:VBO+Shader ```c++ void MyTriangle::t0() const noexcept { thread_local Shader shader0{}; if (!shader0) { const char *vertexShaderSrc = GL_SL_STR( precision mediump float; layout(location = 0) in vec3 aPos; void main() { gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0f); } ); const char *fragmentShaderSrc = GL_SL_STR( precision mediump float; out vec4 FragColor; void main() { FragColor = vec4(1.0f, 0.0f, 0.0f, 1.0f); } ); shader0.load(vertexShaderSrc, fragmentShaderSrc); } thread_local VBO vbo0{}; if (vbo0.empty()) { glBindBuffer(GL_ARRAY_BUFFER, vbo0); const float vertices[] = { -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f, 0.0f, 0.5f, 0.0f }; vbo0.bindData(vertices); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), reinterpret_cast(0)); glEnableVertexAttribArray(0); } glViewport(0, 0, getWindowSurface()->getWidth() / 2, getWindowSurface()->getHeight() / 2); shader0.use(); glEnableVertexAttribArray(0); glDrawArrays(GL_TRIANGLES, 0, 3); glDisableVertexAttribArray(0); shader0.disuse(); } ``` #### 方式2:VBO+VAO+Shader ```c++ void MyTriangle::t1() const noexcept { thread_local Shader shader1{}; if (!shader1) { const char *vertexShaderSrc = GL_SL_STR( precision mediump float; layout(location = 0) in vec3 aPos; void main() { gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0f); } ); const char *fragmentShaderSrc = GL_SL_STR( precision mediump float; out vec4 FragColor; void main() { FragColor = vec4(0.0f, 1.0f, 0.0f, 1.0f); } ); shader1.load(vertexShaderSrc, fragmentShaderSrc); } thread_local VAO vao1{}; thread_local VBO vbo1{}; if (vbo1.empty()) { glBindVertexArray(vao1); glBindBuffer(GL_ARRAY_BUFFER, vbo1); const float vertices[] = { -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f, 0.0f, 0.5f, 0.0f }; vbo1.bindData(vertices); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), reinterpret_cast(0)); glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); } glViewport(getWindowSurface()->getWidth() / 2, 0, getWindowSurface()->getWidth() / 2, getWindowSurface()->getHeight() / 2); shader1.use(); glBindVertexArray(vao1); glDrawArrays(GL_TRIANGLES, 0, 3); glBindVertexArray(0); shader1.disuse(); } ``` #### 方式3:VBO+VAO+EBO+Shader ```c++ void MyTriangle::t2() const noexcept { thread_local Shader shader2{}; if (!shader2) { const char *vertexShaderSrc = GL_SL_STR( precision mediump float; layout(location = 0) in vec3 aPos; void main() { gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0f); } ); const char *fragmentShaderSrc = GL_SL_STR( precision mediump float; out vec4 FragColor; void main() { FragColor = vec4(0.0f, 0.0f, 1.0f, 1.0f); } ); shader2.load(vertexShaderSrc, fragmentShaderSrc); } thread_local VAO vao2{}; thread_local VBO vbo2{}; thread_local EBO ebo2{}; if (vbo2.empty()) { glBindVertexArray(vao2); glBindBuffer(GL_ARRAY_BUFFER, vbo2); const float vertices[] = { -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f, 0.0f, 0.5f, 0.0f }; vbo2.bindData(vertices); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), reinterpret_cast(0)); glEnableVertexAttribArray(0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo2); unsigned int indices[] = { // note that we start from 0! 0, 1, 3, // first Triangle 1, 2, 3 // second Triangle }; ebo2.bindData({0, 1, 2}); // remember: do NOT unbind the EBO while a VAO is active as the bound element buffer object IS stored in the VAO; keep the EBO bound. //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); // note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); } glViewport(getWindowSurface()->getWidth() / 2, getWindowSurface()->getHeight() / 2, getWindowSurface()->getWidth() / 2, getWindowSurface()->getHeight() / 2); shader2.use(); glBindVertexArray(vao2); glDrawElements(GL_TRIANGLES, ebo2.getSize(), GL_UNSIGNED_INT, 0); glBindVertexArray(0); shader2.disuse(); } ``` #### 方式4:Shader ```c++ void MyTriangle::t3() const noexcept { thread_local Shader shader3{}; if (!shader3) { shader3.load(&getContext()->getResManager(), "shader/triangle.vert", "shader/triangle.frag"); } glViewport(0, getWindowSurface()->getHeight() / 2, getWindowSurface()->getWidth() / 2, getWindowSurface()->getHeight() / 2); shader3.use(); glDrawArrays(GL_TRIANGLES, 0, 3); shader3.disuse(); } ``` triangle.vert ```glsl #version 320 es precision mediump float; const vec4 Vertices[] = vec4[3]( vec4(-0.5f, -0.5f, 0.0f, 1.0f), vec4(0.5f, -0.5f, 0.0f, 1.0f), vec4(0.0f, 0.5f, 0.0f, 1.0f) ); const vec4 Colors[]=vec4[3]( vec4(1.0f, 0.0f, 0.0f, 1.0f), vec4(0.0f, 1.0f, 0.0f, 1.0f), vec4(0.0f, 0.0f, 1.0f, 1.0f) ); out vec4 vtxColor; void main() { gl_Position = Vertices[gl_VertexID]; vtxColor = Colors[gl_VertexID]; } ``` triangle.frag ```glsl #version 320 es precision mediump float; out vec4 FragColor; in vec4 vtxColor; void main() { FragColor = vtxColor; } ``` ![ ](./README.assets/Screenshot_20231130_182315.png) ## OpenGLES显示图片 ### 基础类和接口 #### texture 主要作用方便纹理的使用,目前可以支持2D和OES类型纹理 texture.h ```c++ // // Created by ozcom on 2023/11/29. // #ifndef FFMPEGDEMO_TEXTURE_H #define FFMPEGDEMO_TEXTURE_H #include #include #include #include #include "gl_ins.h" #include "ndkutil/NdkHelper.h" #include "water/core/Context.h" namespace we { inline namespace helper { template inline std::array genTextures() noexcept { std::array arr{}; if (Num) { glGenTextures(Num, arr.data()); assert(glGetError() == GL_NO_ERROR); } return arr; } } enum class TEX_TYPE { _2D, _OES }; template class __TEX final { public: inline __TEX() noexcept: tex_(genTextures()[0]) { if constexpr (_type == TEX_TYPE::_2D) { glBindTexture(GL_TEXTURE_2D, tex_); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); glBindTexture(GL_TEXTURE_2D, 0); } else if constexpr (_type == TEX_TYPE::_OES) { glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex_); glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0); } } inline ~__TEX() noexcept { glDeleteTextures(1, &tex_); } inline explicit operator bool() const { return glIsTexture(tex_); } inline explicit operator unsigned int() const { return tex_; } inline void texImage2D(GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels) const { if (_type == TEX_TYPE::_2D) { glBindTexture(GL_TEXTURE_2D, tex_); glTexImage2D(GL_TEXTURE_2D, level, internalformat, width, height, border, format, type, pixels); glGenerateMipmap(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); } } inline void texSubImage2D(GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels) const { if (_type == TEX_TYPE::_2D) { glBindTexture(GL_TEXTURE_2D, tex_); glTexSubImage2D(GL_TEXTURE_2D, level, internalformat, width, height, border, format, type, pixels); glBindTexture(GL_TEXTURE_2D, 0); } } inline void bind() const { if (_type == TEX_TYPE::_2D) { glBindTexture(GL_TEXTURE_2D, tex_); } else if constexpr (_type == TEX_TYPE::_OES) { glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex_); } } inline void unbind() const { if (_type == TEX_TYPE::_2D) { glBindTexture(GL_TEXTURE_2D, 0); } else if constexpr (_type == TEX_TYPE::_OES) { glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0); } } /* * activeTexture * unit [0~31] * */ inline void activeTexture(GLuint unit) const { glActiveTexture(GL_TEXTURE0 + unit); bind(); } private: GLuint tex_; }; using Texture2D = __TEX; using TextureOES = __TEX; template class Textures { public: inline Textures() noexcept {} template inline void bind() const { textures_[Idx].bind(); } template inline void unbind() const { textures_[Idx].unbind(); } template inline void activeTexture(GLuint unit) const { textures_[Idx].activeTexture(unit); } template inline void texImage2D(GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels) const { textures_[Idx].texImage2D(GL_TEXTURE_2D, level, internalformat, width, height, border, format, type, pixels); } template inline void texSubImage2D(GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels) const { textures_[Idx].texSubImage2D(GL_TEXTURE_2D, level, internalformat, width, height, border, format, type, pixels); } inline __TEX<_type> get(std::size_t index) const { return textures_[index]; } inline __TEX<_type> operator[](std::size_t index) const { return textures_[index]; } private: std::array<__TEX<_type>, Num> textures_{}; }; template using Textures2D = Textures; template using TexturesOES = Textures; } // we #endif //FFMPEGDEMO_TEXTURE_H ``` texture.cpp ```c++ // // Created by ozcom on 2023/11/29. // #include "texture.h" namespace we { } // we ``` #### ImageUtil 使用opencv解析asset文件夹下的图片,方便获取纹理所需的图片像素 ImageUtil.h ```c++ // // Created by ozcom on 2023/11/30. // #ifndef FFMPEGDEMO_IMAGEUTIL_H #define FFMPEGDEMO_IMAGEUTIL_H #include #include namespace we { class ImageUtil final { public: static cv::Mat readAssetImage2Mat(const IResManager *resManager, const char *filename); }; } #endif //FFMPEGDEMO_IMAGEUTIL_H ``` ImageUtil.cpp ```c++ // // Created by ozcom on 2023/11/30. // #include "ImageUtil.h" namespace we { cv::Mat ImageUtil::readAssetImage2Mat(const IResManager *resManager, const char *filename) { if (resManager == nullptr || filename == nullptr) throw std::invalid_argument{"resManager!=nullptr && filename!=nullptr"}; uint32_t len = resManager->getAssetLength(filename); std::unique_ptr buf = std::make_unique(len); len = resManager->readAssetRaw(filename, buf.get()); cv::_InputArray pic_arr(buf.get(), static_cast(len)); cv::Mat src = cv::imdecode(pic_arr, cv::IMREAD_COLOR); cv::Mat dst; //因为opencv加载jpg图片的颜色空间是BGR,如在OpenGL中使用需要转换颜色空间 cv::cvtColor(src, dst, cv::COLOR_BGR2RGB); return dst; } } ``` ### 显示jpg图片 image.h ```c++ // // Created by ozcom on 2023/11/30. // #ifndef FFMPEGDEMO_IMAGE_H #define FFMPEGDEMO_IMAGE_H #include "jni.h" #include "water/core/WaterEngine.h" #include "water/core/Context.h" #include "android/native_window.h" #include "ndkutil/NdkHelper.h" #include "core/egl_wrapper.h" #include "water/mesh/Triangle.h" #include "ogl_test_jni.h" namespace test { class ImageScene : public we::Scene { public: explicit ImageScene(we::Context *context, we::EglCore *eglCore, we::WindowSurface *windowSurface); void setup() override; void rending(int64_t tsNs) override; ~ImageScene() override; private: we::Node *image_; }; class MyImage : public we::Mesh { public: MyImage(Context *context, EglCore *eglCore, WindowSurface *windowSurface); ~MyImage() override; void setup() override; void rending(int64_t tsNs) override; private: void draw() const noexcept; }; } #endif //FFMPEGDEMO_IMAGE_H ``` image.cpp ```c++ // // Created by ozcom on 2023/11/30. // #include "image.h" #include "utils/ColorUtil.h" #include "utils/ImageUtil.h" namespace test { ImageScene::ImageScene(we::Context *context, we::EglCore *eglCore, we::WindowSurface *windowSurface) : Scene(context, eglCore, windowSurface) { getWindowSurface()->makeCurrent(); //attach eglcontext image_ = new MyImage{getContext(), getEglCore(), getWindowSurface()}; addNode(image_); } void ImageScene::setup() { Scene::setup(); } void ImageScene::rending(int64_t tsNs) { glViewport(0, 0, getWindowSurface()->getWidth(), getWindowSurface()->getHeight()); glClearColor(0, 0, 0, 1.0f); glClear(GL_COLOR_BUFFER_BIT); Scene::rending(tsNs); getWindowSurface()->setPresentationTime(tsNs); getWindowSurface()->swapBuffers(); } ImageScene::~ImageScene() { delete image_; } } namespace test { MyImage::MyImage(Context *context, EglCore *eglCore, WindowSurface *windowSurface) : Mesh(context, eglCore, windowSurface) {} MyImage::~MyImage() { } void MyImage::setup() { Mesh::setup(); } void MyImage::rending(int64_t tsNs) { Mesh::rending(tsNs); draw(); } void MyImage::draw() const noexcept { thread_local Shader shader_{}; thread_local Texture2D tex0_{}; thread_local Texture2D tex1_{}; if (!shader_) { auto rgb = ImageUtil::readAssetImage2Mat(&getContext()->getResManager(), "imgs/xg.jpg"); tex0_.texImage2D(0, GL_RGB, rgb.cols, rgb.rows, 0, GL_RGB, GL_UNSIGNED_BYTE, rgb.data); auto rgb2 = ImageUtil::readAssetImage2Mat(&getContext()->getResManager(), "imgs/xl.jpg"); tex1_.texImage2D(0, GL_RGB, rgb2.cols, rgb2.rows, 0, GL_RGB, GL_UNSIGNED_BYTE, rgb2.data); shader_.load(&getContext()->getResManager(), "shader/image.vert", "shader/image.frag"); } { glViewport(0, getWindowSurface()->getHeight() / 2, getWindowSurface()->getWidth(), getWindowSurface()->getHeight() / 2); tex0_.activeTexture(0); shader_.use(); shader_.setUniform("mTexSampler", 0); glDrawArrays(GL_TRIANGLES, 0, 6); shader_.disuse(); } { glViewport(0, 0, getWindowSurface()->getWidth(), getWindowSurface()->getHeight() / 2); tex1_.activeTexture(1); shader_.use(); shader_.setUniform("mTexSampler", 1); glDrawArrays(GL_TRIANGLES, 0, 6); shader_.disuse(); } } } ``` ![](./README.assets/Screenshot_20231130_180041.png) ## OpenGLES内存泄漏问题 在Android系统之中,OpenGLES造成可能存在的内存泄漏原因: 1. 程序没有OpenGLES回收资源操作 2. OpenGLES资源回收操作在Surface被回收之后 ## shader ### compute shader 并行计算 ### vertex shader ### fragment shader ## 实时绘制视频帧的直方图 ## 图像合成 ## 实时视频帧绿屏过滤 # jetpack