<NME> native-lib.cpp
<BEF> ﻿/*
 * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
 *
 * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
 *
 * Use of this source code is governed by MIT license that can be found in the
 * LICENSE file in the root of the source tree. All contributing project authors
 * may be found in the AUTHORS file in the root of the source tree.
 */

#include <jni.h>
#include <string>
#include "Util/logger.h"
#include "Thread/semaphore.h"
#include "Common/config.h"
#include "Player/MediaPlayer.h"
#include "Extension/Frame.h"

using namespace std;
using namespace toolkit;
using namespace mediakit;

#define JNI_API(retType, funName, ...) extern "C"  JNIEXPORT retType Java_com_zlmediakit_jni_ZLMediaKit_##funName(JNIEnv* env, jclass cls,##__VA_ARGS__)
#define MediaPlayerCallBackSign "com/zlmediakit/jni/ZLMediaKit$MediaPlayerCallBack"
#define MediaFrameSign "com/zlmediakit/jni/ZLMediaKit$MediaFrame"

string stringFromJstring(JNIEnv *env, jstring jstr) {
    if (!env || !jstr) {
        WarnL << "invalid args";
        return "";
    }
    const char *field_char = env->GetStringUTFChars(jstr, 0);
    string ret(field_char, env->GetStringUTFLength(jstr));
    env->ReleaseStringUTFChars(jstr, field_char);
    return ret;
}

string stringFromJbytes(JNIEnv *env, jbyteArray jbytes) {
    if (!env || !jbytes) {
        WarnL << "invalid args";
        return "";
    }
    jbyte *bytes = env->GetByteArrayElements(jbytes, 0);
    string ret((char *) bytes, env->GetArrayLength(jbytes));
    env->ReleaseByteArrayElements(jbytes, bytes, 0);
    return ret;
}

string stringFieldFromJava(JNIEnv *env, jobject jdata, jfieldID jid) {
    if (!env || !jdata || !jid) {
        WarnL << "invalid args";
        return "";
    }
    jstring field_str = (jstring) env->GetObjectField(jdata, jid);
    auto ret = stringFromJstring(env, field_str);
    env->DeleteLocalRef(field_str);
    return ret;
}

string bytesFieldFromJava(JNIEnv *env, jobject jdata, jfieldID jid) {
    if (!env || !jdata || !jid) {
        WarnL << "invalid args";
        return "";
    }
    jbyteArray jbufArray = (jbyteArray) env->GetObjectField(jdata, jid);
    string ret = stringFromJbytes(env, jbufArray);
    env->DeleteLocalRef(jbufArray);
    return ret;
}

jstring jstringFromString(JNIEnv *env, const char *pat) {
    return (jstring) env->NewStringUTF(pat);
}

jbyteArray jbyteArrayFromString(JNIEnv *env, const char *pat, int len = 0) {
    if (len <= 0) {
        len = strlen(pat);
    }
    jbyteArray jarray = env->NewByteArray(len);
    env->SetByteArrayRegion(jarray, 0, len, (jbyte * )(pat));
    return jarray;
}

jobject makeJavaFrame(JNIEnv *env, const Frame::Ptr &frame) {
    static jclass jclass_obj = (jclass) env->NewGlobalRef(env->FindClass(MediaFrameSign));
    static jmethodID jmethodID_init = env->GetMethodID(jclass_obj, "<init>", "()V");
    static jfieldID jfieldID_dts = env->GetFieldID(jclass_obj, "dts", "I");
    static jfieldID jfieldID_pts = env->GetFieldID(jclass_obj, "pts", "I");
    static jfieldID jfieldID_prefixSize = env->GetFieldID(jclass_obj, "prefixSize", "I");
    static jfieldID jfieldID_keyFrame = env->GetFieldID(jclass_obj, "keyFrame", "Z");
    static jfieldID jfieldID_data = env->GetFieldID(jclass_obj, "data", "[B");
    static jfieldID jfieldID_trackType = env->GetFieldID(jclass_obj, "trackType", "I");
    static jfieldID jfieldID_codecId = env->GetFieldID(jclass_obj, "codecId", "I");

    if (!frame) {
        return nullptr;
    }
    jobject ret = env->NewObject(jclass_obj, jmethodID_init);
    env->SetIntField(ret, jfieldID_dts, frame->dts());
    env->SetIntField(ret, jfieldID_pts, frame->pts());
    env->SetIntField(ret, jfieldID_prefixSize, frame->prefixSize());
    env->SetBooleanField(ret, jfieldID_keyFrame, frame->keyFrame());
    env->SetObjectField(ret, jfieldID_data, jbyteArrayFromString(env, frame->data(), frame->size()));
    env->SetIntField(ret, jfieldID_trackType, frame->getTrackType());
    env->SetIntField(ret, jfieldID_codecId, frame->getCodecId());
    return ret;
}

static JavaVM *s_jvm = nullptr;

template <typename FUN>
void doInJavaThread(FUN &&fun){
    JNIEnv *env;
    int status = s_jvm->GetEnv((void **) &env, JNI_VERSION_1_6);
    if (status != JNI_OK) {
        if (s_jvm->AttachCurrentThread(&env, NULL) != JNI_OK) {
            return;
        }
    }
    fun(env);
    if (status != JNI_OK) {
        //Detach线程
        s_jvm->DetachCurrentThread();
    }
}

#define emitEvent(delegate,method,argFmt,...) \
{ \
    doInJavaThread([&](JNIEnv* env) { \
        static jclass cls = env->GetObjectClass(delegate); \
        static jmethodID jmid = env->GetMethodID(cls, method, argFmt); \
        jobject localRef = env->NewLocalRef(delegate); \
        if(localRef){ \
            env->CallVoidMethod(localRef, jmid, ##__VA_ARGS__); \
        }else{ \
            WarnL << "弱引用已经释放:" << method << " " << argFmt; \
        }\
    }); \
}

/*
 * 加载动态库
 */
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    //设置日志
    s_jvm = vm;
    Logger::Instance().add(std::make_shared<ConsoleChannel>());
    Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());
    InfoL;
    return JNI_VERSION_1_6;
}

static pthread_t s_tread_id = 0;
/*
 * 卸载动态库
 */
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved){
    InfoL;
    if(s_tread_id){
        pthread_kill(s_tread_id,SIGINT);
    }
}

extern int start_main(int argc,char *argv[]);

JNI_API(jboolean, startDemo, jstring ini_dir){
    string sd_path = stringFromJstring(env,ini_dir);
    string ini_file = sd_path +  "/zlmediakit.ini";

    //可以在sd卡根目录下放置ssl证书以便支持https服务器，证书支持p12或pem格式
    string pem_file = sd_path +  "/zlmediakit.pem";

    DebugL << "sd_path:" << sd_path;
    DebugL << "ini file:" << ini_file;

    static thread s_th([sd_path,ini_file,pem_file](){
        s_tread_id = pthread_self();
        try {
                replace(pr.second,"https://127.0.0.1/","http://127.0.0.1:8080/");
            }
            //默认打开hook
            mINI::Instance()["hook.enable"] = 1;
            //默认打开http api调试
            mINI::Instance()["api.apiDebug"] = 1;

            mINI::Instance()["http.port"] = 8080;
            mINI::Instance()["http.sslport"] = 8443;
            mINI::Instance()["rtsp.port"] = 8554;
            mINI::Instance()["rtsp.sslport"] = 8332;
            mINI::Instance()["general.enableVhost"] = 0;
            for (auto &pr : mINI::Instance()) {
                //替换hook默认地址
                replace(pr.second, "https://127.0.0.1/", "http://127.0.0.1:8080/");
            }
            //默认打开hook
            mINI::Instance()["hook.enable"] = 0;
            //默认打开http api调试
            mINI::Instance()["api.apiDebug"] = 1;

            int argc = 5;
            const char *argv[] = {"", "-c", ini_file.data(), "-s", pem_file.data()};
            start_main(argc, (char **) argv);
        } catch (std::exception &ex) {
            WarnL << ex.what();
        }
    });

    static onceToken s_token([]{
        s_th.detach();
    });

    return true;
};

JNI_API(jlong, createMediaPlayer, jstring url, jobject callback){
    static auto loadFrameClass = makeJavaFrame(env,nullptr);
    MediaPlayer::Ptr *ret = new MediaPlayer::Ptr(new MediaPlayer());
    MediaPlayer::Ptr &player = *ret;

    weak_ptr<MediaPlayer> weakPlayer = player;
    jobject globalWeakRef = env->NewWeakGlobalRef(callback);
    player->setOnPlayResult([weakPlayer,globalWeakRef](const SockException &ex) {
        auto strongPlayer = weakPlayer.lock();
        if (!strongPlayer) {
            return;
        }
        emitEvent((jobject)globalWeakRef,"onPlayResult","(ILjava/lang/String;)V",(jint)ex.getErrCode(),env->NewStringUTF(ex.what()));

        if(ex){
            return;
        }

        auto viedoTrack = strongPlayer->getTrack(TrackVideo);
        if (viedoTrack) {
            viedoTrack->addDelegate(std::make_shared<FrameWriterInterfaceHelper>([globalWeakRef](const Frame::Ptr &frame) {
                emitEvent((jobject)globalWeakRef,"onData","(L" MediaFrameSign ";)V",makeJavaFrame(env,frame));
                return true;
            }));
        }

        auto audioTrack = strongPlayer->getTrack(TrackAudio);
        if (audioTrack) {
            audioTrack->addDelegate(std::make_shared<FrameWriterInterfaceHelper>([globalWeakRef](const Frame::Ptr &frame) {
                emitEvent((jobject)globalWeakRef,"onData","(L" MediaFrameSign ";)V",makeJavaFrame(env,frame));
                return true;
            }));
        }

    });

    player->setOnShutdown([globalWeakRef,weakPlayer](const SockException &ex) {
        auto strongPlayer = weakPlayer.lock();
        if (!strongPlayer) {
            return;
        }
        emitEvent((jobject)globalWeakRef,"onShutdown","(ILjava/lang/String;)V",(jint)ex.getErrCode(),env->NewStringUTF(ex.what()));
    });

    (*player)[Client::kRtpType] = Rtsp::RTP_TCP;
    player->play(stringFromJstring(env,url));

    return (jlong)(ret);
}

JNI_API(void,releaseMediaPlayer,jlong ptr){
    MediaPlayer::Ptr *player = (MediaPlayer::Ptr *)ptr;
    delete player;
}

<MSG> 默认关闭hook

<DFF> @@ -179,7 +179,7 @@ JNI_API(jboolean,startDemo,jstring ini_dir){
                 replace(pr.second,"https://127.0.0.1/","http://127.0.0.1:8080/");
             }
             //默认打开hook
-            mINI::Instance()["hook.enable"] = 1;
+            mINI::Instance()["hook.enable"] = 0;
             //默认打开http api调试
             mINI::Instance()["api.apiDebug"] = 1;
 

{'commit': 'diff --git a/Android/app/src/main/cpp/native-lib.cpp b/Android/app/src/main/cpp/native-lib.cpp\nindex f0033287..193ebff7 100644\n--- a/Android/app/src/main/cpp/native-lib.cpp\n+++ b/Android/app/src/main/cpp/native-lib.cpp\n@@ -179,7 +179,7 @@ JNI_API(jboolean,startDemo,jstring ini_dir){\n                 replace(pr.second,"https://127.0.0.1/","http://127.0.0.1:8080/");\n             }\n             //默认打开hook\n-            mINI::Instance()["hook.enable"] = 1;\n+            mINI::Instance()["hook.enable"] = 0;\n             //默认打开http api调试\n             mINI::Instance()["api.apiDebug"] = 1;\n \n', 'commit_subject': '默认关闭hook', 'repo_name': 'xiongziliang/ZLMediaKit', 'committer': {'name': 'xiongziliang', 'email': '2c1ef1aaf06ec408d7b37a0565446d2bba75fe9a@qq.com', 'time_sec': '1569312550', 'tz_offset': '480', 'date': {'seconds': '1569312550'}}, 'commit_tree': 'f8fce65273469c5240b67846cf8e1bb521ed56d6', 'commit_message': '默认关闭hook\n', 'language_name': 'HTML', 'license': 'mit', 'hunks': '@@ -179,7 +179,7 @@ JNI_API(jboolean,startDemo,jstring ini_dir){\n                 replace(pr.second,"https://127.0.0.1/","http://127.0.0.1:8080/");\n             }\n             //默认打开hook\n-            mINI::Instance()["hook.enable"] = 1;\n+            mINI::Instance()["hook.enable"] = 0;\n             //默认打开http api调试\n             mINI::Instance()["api.apiDebug"] = 1;\n \n', 'addition_count': 1, 'deletion_count': 1, 'src_file': 'a/Android/app/src/main/cpp/native-lib.cpp', 'tgt_file': 'b/Android/app/src/main/cpp/native-lib.cpp', 'file_extension': '.cpp', 'before_file': '\ufeff/*\n * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.\n *\n * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).\n *\n * Use of this source code is governed by MIT license that can be found in the\n * LICENSE file in the root of the source tree. All contributing project authors\n * may be found in the AUTHORS file in the root of the source tree.\n */\n\n#include <jni.h>\n#include <string>\n#include "Util/logger.h"\n#include "Thread/semaphore.h"\n#include "Common/config.h"\n#include "Player/MediaPlayer.h"\n#include "Extension/Frame.h"\n\nusing namespace std;\nusing namespace toolkit;\nusing namespace mediakit;\n\n#define JNI_API(retType, funName, ...) extern "C"  JNIEXPORT retType Java_com_zlmediakit_jni_ZLMediaKit_##funName(JNIEnv* env, jclass cls,##__VA_ARGS__)\n#define MediaPlayerCallBackSign "com/zlmediakit/jni/ZLMediaKit$MediaPlayerCallBack"\n#define MediaFrameSign "com/zlmediakit/jni/ZLMediaKit$MediaFrame"\n\nstring stringFromJstring(JNIEnv *env, jstring jstr) {\n    if (!env || !jstr) {\n        WarnL << "invalid args";\n        return "";\n    }\n    const char *field_char = env->GetStringUTFChars(jstr, 0);\n    string ret(field_char, env->GetStringUTFLength(jstr));\n    env->ReleaseStringUTFChars(jstr, field_char);\n    return ret;\n}\n\nstring stringFromJbytes(JNIEnv *env, jbyteArray jbytes) {\n    if (!env || !jbytes) {\n        WarnL << "invalid args";\n        return "";\n    }\n    jbyte *bytes = env->GetByteArrayElements(jbytes, 0);\n    string ret((char *) bytes, env->GetArrayLength(jbytes));\n    env->ReleaseByteArrayElements(jbytes, bytes, 0);\n    return ret;\n}\n\nstring stringFieldFromJava(JNIEnv *env, jobject jdata, jfieldID jid) {\n    if (!env || !jdata || !jid) {\n        WarnL << "invalid args";\n        return "";\n    }\n    jstring field_str = (jstring) env->GetObjectField(jdata, jid);\n    auto ret = stringFromJstring(env, field_str);\n    env->DeleteLocalRef(field_str);\n    return ret;\n}\n\nstring bytesFieldFromJava(JNIEnv *env, jobject jdata, jfieldID jid) {\n    if (!env || !jdata || !jid) {\n        WarnL << "invalid args";\n        return "";\n    }\n    jbyteArray jbufArray = (jbyteArray) env->GetObjectField(jdata, jid);\n    string ret = stringFromJbytes(env, jbufArray);\n    env->DeleteLocalRef(jbufArray);\n    return ret;\n}\n\njstring jstringFromString(JNIEnv *env, const char *pat) {\n    return (jstring) env->NewStringUTF(pat);\n}\n\njbyteArray jbyteArrayFromString(JNIEnv *env, const char *pat, int len = 0) {\n    if (len <= 0) {\n        len = strlen(pat);\n    }\n    jbyteArray jarray = env->NewByteArray(len);\n    env->SetByteArrayRegion(jarray, 0, len, (jbyte * )(pat));\n    return jarray;\n}\n\njobject makeJavaFrame(JNIEnv *env, const Frame::Ptr &frame) {\n    static jclass jclass_obj = (jclass) env->NewGlobalRef(env->FindClass(MediaFrameSign));\n    static jmethodID jmethodID_init = env->GetMethodID(jclass_obj, "<init>", "()V");\n    static jfieldID jfieldID_dts = env->GetFieldID(jclass_obj, "dts", "I");\n    static jfieldID jfieldID_pts = env->GetFieldID(jclass_obj, "pts", "I");\n    static jfieldID jfieldID_prefixSize = env->GetFieldID(jclass_obj, "prefixSize", "I");\n    static jfieldID jfieldID_keyFrame = env->GetFieldID(jclass_obj, "keyFrame", "Z");\n    static jfieldID jfieldID_data = env->GetFieldID(jclass_obj, "data", "[B");\n    static jfieldID jfieldID_trackType = env->GetFieldID(jclass_obj, "trackType", "I");\n    static jfieldID jfieldID_codecId = env->GetFieldID(jclass_obj, "codecId", "I");\n\n    if (!frame) {\n        return nullptr;\n    }\n    jobject ret = env->NewObject(jclass_obj, jmethodID_init);\n    env->SetIntField(ret, jfieldID_dts, frame->dts());\n    env->SetIntField(ret, jfieldID_pts, frame->pts());\n    env->SetIntField(ret, jfieldID_prefixSize, frame->prefixSize());\n    env->SetBooleanField(ret, jfieldID_keyFrame, frame->keyFrame());\n    env->SetObjectField(ret, jfieldID_data, jbyteArrayFromString(env, frame->data(), frame->size()));\n    env->SetIntField(ret, jfieldID_trackType, frame->getTrackType());\n    env->SetIntField(ret, jfieldID_codecId, frame->getCodecId());\n    return ret;\n}\n\nstatic JavaVM *s_jvm = nullptr;\n\ntemplate <typename FUN>\nvoid doInJavaThread(FUN &&fun){\n    JNIEnv *env;\n    int status = s_jvm->GetEnv((void **) &env, JNI_VERSION_1_6);\n    if (status != JNI_OK) {\n        if (s_jvm->AttachCurrentThread(&env, NULL) != JNI_OK) {\n            return;\n        }\n    }\n    fun(env);\n    if (status != JNI_OK) {\n        //Detach线程\n        s_jvm->DetachCurrentThread();\n    }\n}\n\n#define emitEvent(delegate,method,argFmt,...) \\\n{ \\\n    doInJavaThread([&](JNIEnv* env) { \\\n        static jclass cls = env->GetObjectClass(delegate); \\\n        static jmethodID jmid = env->GetMethodID(cls, method, argFmt); \\\n        jobject localRef = env->NewLocalRef(delegate); \\\n        if(localRef){ \\\n            env->CallVoidMethod(localRef, jmid, ##__VA_ARGS__); \\\n        }else{ \\\n            WarnL << "弱引用已经释放:" << method << " " << argFmt; \\\n        }\\\n    }); \\\n}\n\n/*\n * 加载动态库\n */\nJNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {\n    //设置日志\n    s_jvm = vm;\n    Logger::Instance().add(std::make_shared<ConsoleChannel>());\n    Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());\n    InfoL;\n    return JNI_VERSION_1_6;\n}\n\nstatic pthread_t s_tread_id = 0;\n/*\n * 卸载动态库\n */\nJNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved){\n    InfoL;\n    if(s_tread_id){\n        pthread_kill(s_tread_id,SIGINT);\n    }\n}\n\nextern int start_main(int argc,char *argv[]);\n\nJNI_API(jboolean, startDemo, jstring ini_dir){\n    string sd_path = stringFromJstring(env,ini_dir);\n    string ini_file = sd_path +  "/zlmediakit.ini";\n\n    //可以在sd卡根目录下放置ssl证书以便支持https服务器，证书支持p12或pem格式\n    string pem_file = sd_path +  "/zlmediakit.pem";\n\n    DebugL << "sd_path:" << sd_path;\n    DebugL << "ini file:" << ini_file;\n\n    static thread s_th([sd_path,ini_file,pem_file](){\n        s_tread_id = pthread_self();\n        try {\n                replace(pr.second,"https://127.0.0.1/","http://127.0.0.1:8080/");\n            }\n            //默认打开hook\n            mINI::Instance()["hook.enable"] = 1;\n            //默认打开http api调试\n            mINI::Instance()["api.apiDebug"] = 1;\n\n            mINI::Instance()["http.port"] = 8080;\n            mINI::Instance()["http.sslport"] = 8443;\n            mINI::Instance()["rtsp.port"] = 8554;\n            mINI::Instance()["rtsp.sslport"] = 8332;\n            mINI::Instance()["general.enableVhost"] = 0;\n            for (auto &pr : mINI::Instance()) {\n                //替换hook默认地址\n                replace(pr.second, "https://127.0.0.1/", "http://127.0.0.1:8080/");\n            }\n            //默认打开hook\n            mINI::Instance()["hook.enable"] = 0;\n            //默认打开http api调试\n            mINI::Instance()["api.apiDebug"] = 1;\n\n            int argc = 5;\n            const char *argv[] = {"", "-c", ini_file.data(), "-s", pem_file.data()};\n            start_main(argc, (char **) argv);\n        } catch (std::exception &ex) {\n            WarnL << ex.what();\n        }\n    });\n\n    static onceToken s_token([]{\n        s_th.detach();\n    });\n\n    return true;\n};\n\nJNI_API(jlong, createMediaPlayer, jstring url, jobject callback){\n    static auto loadFrameClass = makeJavaFrame(env,nullptr);\n    MediaPlayer::Ptr *ret = new MediaPlayer::Ptr(new MediaPlayer());\n    MediaPlayer::Ptr &player = *ret;\n\n    weak_ptr<MediaPlayer> weakPlayer = player;\n    jobject globalWeakRef = env->NewWeakGlobalRef(callback);\n    player->setOnPlayResult([weakPlayer,globalWeakRef](const SockException &ex) {\n        auto strongPlayer = weakPlayer.lock();\n        if (!strongPlayer) {\n            return;\n        }\n        emitEvent((jobject)globalWeakRef,"onPlayResult","(ILjava/lang/String;)V",(jint)ex.getErrCode(),env->NewStringUTF(ex.what()));\n\n        if(ex){\n            return;\n        }\n\n        auto viedoTrack = strongPlayer->getTrack(TrackVideo);\n        if (viedoTrack) {\n            viedoTrack->addDelegate(std::make_shared<FrameWriterInterfaceHelper>([globalWeakRef](const Frame::Ptr &frame) {\n                emitEvent((jobject)globalWeakRef,"onData","(L" MediaFrameSign ";)V",makeJavaFrame(env,frame));\n                return true;\n            }));\n        }\n\n        auto audioTrack = strongPlayer->getTrack(TrackAudio);\n        if (audioTrack) {\n            audioTrack->addDelegate(std::make_shared<FrameWriterInterfaceHelper>([globalWeakRef](const Frame::Ptr &frame) {\n                emitEvent((jobject)globalWeakRef,"onData","(L" MediaFrameSign ";)V",makeJavaFrame(env,frame));\n                return true;\n            }));\n        }\n\n    });\n\n    player->setOnShutdown([globalWeakRef,weakPlayer](const SockException &ex) {\n        auto strongPlayer = weakPlayer.lock();\n        if (!strongPlayer) {\n            return;\n        }\n        emitEvent((jobject)globalWeakRef,"onShutdown","(ILjava/lang/String;)V",(jint)ex.getErrCode(),env->NewStringUTF(ex.what()));\n    });\n\n    (*player)[Client::kRtpType] = Rtsp::RTP_TCP;\n    player->play(stringFromJstring(env,url));\n\n    return (jlong)(ret);\n}\n\nJNI_API(void,releaseMediaPlayer,jlong ptr){\n    MediaPlayer::Ptr *player = (MediaPlayer::Ptr *)ptr;\n    delete player;\n}\n', 'message': '默认关闭hook\n', 'author_name': 'xiongziliang', 'lang': 'cpp'}