美文网首页
第三节 FFmpeg解码流程、C++中的多线程

第三节 FFmpeg解码流程、C++中的多线程

作者: 最美下雨天 | 来源:发表于2018-12-06 14:44 被阅读14次
    image.png
    image.png
    image.png

    我们都知道Android是基于Linux内核的,而Linux是遵循POSIX线程标准的,POSIX线程库中有一系列Pthreads API方便我们对Linux线程的操作。所以我们在Android中使用C/C++线程也就转到了使用POSIX线程库。他们都在头文件“pthread.h”中。

    创建线程

    image.png

    测试:

    pthread_t pthread;
    void *threadCallBack(void *data)
    {
        LOGI("测试C++子线程");
        //销毁线程
        pthread_exit(&pthread);
    
    
    }
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_example_voicelib_Player_mutipleThread(JNIEnv *env, jobject instance) {
    
        pthread_create(&pthread,NULL,threadCallBack,NULL);
    
    }
    

    生产者、消费者模型

    image.png

    代码演示:

    #include <jni.h>
    #include <string>
    #include <android/log.h>
    #include <pthread.h>
    // sleep 的头文件
    #include <unistd.h>
    extern "C"
    {
    #include <libavformat/avformat.h>
    }
    
    #define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"VoicePlayer",FORMAT,##__VA_ARGS__);
    
    pthread_t  p_thread;
    pthread_t  c_thread;
    int sum=10;
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    bool excute=true;
    void *product(void *data)
    {
        while (excute)
        {
            pthread_mutex_lock(&mutex);
            sum++;
            LOGI("生产了产品,产品数量为:%d",sum);
            pthread_cond_signal(&cond);
            pthread_mutex_unlock(&mutex);
            sleep(5);
        }
        pthread_exit(&p_thread);
    
    }
    
    void *consumer(void *data)
    {
        while (excute)
        {
            pthread_mutex_lock(&mutex);
            if(sum>1)
            {
                sum--;
                LOGI("消费了产品,产品数量为:%d",sum);
            } else{
                LOGI("没有产品了,进入等待状态");
                pthread_cond_wait(&cond,&mutex);
            }
            pthread_mutex_unlock(&mutex);
            //单位是秒
            sleep(1);
        }
        pthread_exit(&c_thread);
    
    
    
    }
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_example_voicelib_Player_produceConsumer(JNIEnv *env, jobject instance) {
    
        pthread_mutex_init(&mutex,NULL);
        pthread_cond_init(&cond,NULL);
        pthread_create(&p_thread, NULL, product, (void *) sum);
        pthread_create(&c_thread, NULL, consumer, (void *) sum);
    }
    

    输出:


    image.png

    C++调用Java层代码,要区分主线程、子线程

    image.png image.png

    Java层代码:

    public void onError(int code,String msg)
        {
            Log.e("VoicePlayer","code:"+code+"msg:"+msg);
    
        }
    

    c++层

    JavaVM *javaVM;
    JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void* reserved)
    {
        JNIEnv *env;
        javaVM = vm;
        if(vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK)
        {
            return -1;
        }
        return JNI_VERSION_1_6;
    }
    
    JNIEXPORT void JNICALL JNI_OnUnload (JavaVM *vm, void* reserved)
    {
    
    
    }
    
    jobject jobj;
    jmethodID jid;
    pthread_t pthread;
    
    void *threadCallBack(void *data)
    {
        JNIEnv *jniEnv;
        javaVM->AttachCurrentThread(&jniEnv,0);
        if(jniEnv==NULL)
        {
            LOGI("jnienv为NULL");
    
        } else{
            jstring js=jniEnv->NewStringUTF("子线程调用");
            jniEnv->CallVoidMethod(jobj,jid,500,js);
            jniEnv->DeleteLocalRef(js);
            jniEnv->DeleteGlobalRef(jobj);
            javaVM->DetachCurrentThread();
        }
        //销毁线程
        pthread_exit(&pthread);
    
    
    }
    
    
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_example_voicelib_Player_mutipleThread(JNIEnv *env, jobject instance) {
    
    //    jniEnv=env;
        jclass jc=env->GetObjectClass(instance);
        //注意这儿必须转换为全局引用,这儿是形参
        jobj=env->NewGlobalRef(instance);
        //这儿不用,jmethodID是一个结构体,直接赋值即可
        jid=env->GetMethodID(jc,"onError","(ILjava/lang/String;)V");
        jstring js=env->NewStringUTF("主线程调用");
        env->CallVoidMethod(instance,jid,404,js);
        env->DeleteLocalRef(js);
        pthread_create(&pthread,NULL,threadCallBack,NULL);
    
    }
    

    需要注意的地方:


    image.png

    第一点:关于JNIEnv这个对象

    JNIEnv与JavaVM

    JNIEnv 概念 : 是一个线程相关的结构体, 该结构体代表了 Java 在本线程的运行环境 ;

    JNIEnv 与 JavaVM : 注意区分这两个概念;
    -- JavaVM : JavaVM 是 Java虚拟机在 JNI 层的代表, JNI 全局只有一个;
    -- JNIEnv : JavaVM 在线程中的代表, 每个线程都有一个, JNI 中可能有很多个 JNIEnv;

    JNIEnv 作用 :
    -- 调用 Java 函数 : JNIEnv 代表 Java 运行环境, 可以使用 JNIEnv 调用 Java 中的代码;
    -- 操作 Java 对象 : Java 对象传入 JNI 层就是 Jobject 对象, 需要使用 JNIEnv 来操作这个 Java 对象;

    JNIEnv 体系结构

    线程相关 : JNIEnv 是线程相关的, 即 在 每个线程中 都有一个 JNIEnv 指针, 每个JNIEnv 都是线程专有的, 其它线程不能使用本线程中的 JNIEnv, 线程 A 不能调用 线程 B 的 JNIEnv;

    JNIEnv 不能跨线程 :
    -- 当前线程有效 : JNIEnv 只在当前线程有效, JNIEnv 不能在 线程之间进行传递, 在同一个线程中, 多次调用 JNI层方法, 传入的 JNIEnv 是相同的;
    -- 本地方法匹配多JNIEnv : 在 Java 层定义的本地方法, 可以在不同的线程调用, 因此 可以接受不同的 JNIEnv;

    JNIEnv 结构 : 由上面的代码可以得出, JNIEnv 是一个指针, 指向一个线程相关的结构, 线程相关结构指向 JNI 函数指针 数组, 这个数组中存放了大量的 JNI 函数指针, 这些指针指向了具体的 JNI 函数;

    注意:JNIEnv只在当前线程中有效。本地方法不能将JNIEnv从一个线程传递到另一个线程中。相同的 Java 线程中对本地方法多次调用时,传递给该本地方法的JNIEnv是相同的。但是,一个本地方法可被不同的 Java 线程所调用,因此可以接受不同的 JNIEnv。

    看下AttachCurrentThread这个方法
    参考地址:https://www.zybuluo.com/cxm-2016/note/566623

    image.png
    也就是说调用这个方法会得到当前线程的一个JNIEnv对象

    第三点,注意全局引用

    这儿将instance转为了全局引用,但是jid却没有转(在子线程中都是用了这两个参数)
    事实上:instance必须转,jid一定不能转
    Global Reference 全局引用 ,这种对象如不主动释放,它永远都不会被垃圾回收

    创建: env->NewGlobalRef(obj);

    释放: env->DeleteGlobalRef(obj)

    若要在某个 Native 代码返回后,还希望能继续使用 JVM 提供的参数, 则将该对象设为 global reference,以后只能使用这个 global reference;若不是一个 jobject,则无需这么做。

    因为在子线程中还需要使用这个instance,当前线程在运行完成后会结束,此时子线程还在运行,所以必须把instance转为全局引用,要不然instance会被回收,那么为什么jid不需要呢?因为它不是一个jobject

    参考地址:https://blog.csdn.net/JQ_AK47/article/details/53448561

    image.png

    第二点,注意释放全局引用,以及释放代码的位置

    对代码优化

    将Log提出来
    Log.h

    //
    // Created by 霍振鹏 on 2018/10/19.
    //
    
    #ifndef VOICEPLAYER_LOG_H
    #define VOICEPLAYER_LOG_H
    
    #endif //VOICEPLAYER_LOG_H
    #include <android/log.h>
    #define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"VoicePlayer",FORMAT,##__VA_ARGS__);
    

    定义CallBack类实现在主、子线程中回调Java方法


    image.png

    CallBackJava.h

    //
    // Created by 霍振鹏 on 2018/10/19.
    //
    #include "jni.h"
    #ifndef VOICEPLAYER_CALLBACKJAVA_H
    #define VOICEPLAYER_CALLBACKJAVA_H
    
    class CallBackJava
    {
    public:
        JavaVM *javaVM;
        JNIEnv *jniEnv;
        jobject instance;
        jmethodID jmd;
    
    public:
        CallBackJava(JavaVM *vm,JNIEnv *env,jobject job);
        ~CallBackJava();
        /**
         * @param type 1主线程,0子线程
         * @param code
         * @param msg
         */
        void onError(int type,int code, const char *msg);
    
    };
    
    #endif //VOICEPLAYER_CALLBACKJAVA_H
    
    
    

    CallBackJava.cpp

    //
    // Created by 霍振鹏 on 2018/10/19.
    //
    
    #include "CallBackJava.h"
    #include "Log.h"
    
    CallBackJava::CallBackJava(JavaVM *vm, JNIEnv *env, jobject job) {
    
        javaVM=vm;
        jniEnv=env;
        instance=job;
        jclass jcl=env->GetObjectClass(job);
        this->jmd=env->GetMethodID(jcl,"onError","(ILjava/lang/String;)V");
    }
    
    CallBackJava::~CallBackJava() {
    
        LOGI("析构函数执行了");
    }
    
    void CallBackJava::onError(int type, int code, const char *msg) {
        if(type==0)
        {
            //子线程
            //这儿会重新给JNIEnv赋值
            javaVM->AttachCurrentThread(&jniEnv,0);
            jstring jsr=jniEnv->NewStringUTF(msg);
            jniEnv->CallVoidMethod(instance,jmd,code,jsr);
            jniEnv->DeleteLocalRef(jsr);
            javaVM->DetachCurrentThread();
    
        }
        else if(type==1)
        {
            jstring jsr=jniEnv->NewStringUTF(msg);
            //主线程
            jniEnv->CallVoidMethod(instance,jmd,code,jsr);
            jniEnv->DeleteLocalRef(jsr);
    
        }
    
    }
    
    

    使用:

    pthread_t  t_pthread;
    
    void *childThread(void * data)
    {
        CallBackJava *callBackJava= (CallBackJava *) data;
        callBackJava->onError(0,500,"子线程调用");
        delete callBackJava;
        pthread_exit(&t_pthread);
    }
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_example_voicelib_Player_mutipleCallBack(JNIEnv *env, jobject instance) {
    
        CallBackJava *callBackJava=new CallBackJava(javaVM,env,env->NewGlobalRef(instance));
        callBackJava->onError(1,404,"主线程调用");
        pthread_create(&t_pthread,NULL,childThread,callBackJava);
    }
    

    相关文章

      网友评论

          本文标题:第三节 FFmpeg解码流程、C++中的多线程

          本文链接:https://www.haomeiwen.com/subject/pxeszftx.html