MEDIACODEC在JNI层下的H264,H265视频硬解码实现(ACODEC )

本帖最后由 jingjin221 于 2016-11-21 12:03 编辑

有疑惑请一起商讨QQ(512975979)        之前提供的代码是基于已有的SDK来开发的,只能适用于这一个机型的运行。比如说,我是基于FIREFLY给的RK3288 ANDROID4.4 PAD的SDK开发的,编译出来的库和APK,只能用于这一个机型!    所以说,要开发一个基于MEDIACODEC,而且是在NATIVE层下的通用视频解码播放器,就必须脱离具体的FRAMEWORK,独立出来。
之前参考了FFMPEG FOR ANDROID,XBMC等一些SDK,他们采用的公用的头文件,和一些通用的基本库,基本是在OMXCODEC下做的解码,渲染的话,FFMPEG是用的软件渲染YUV,而XBMC是用的GL来直接渲染的,这两者差距甚大,而且对我们二次开发难度不小,况且一般我们需要的不是整个模型,只是对其中的一块有兴趣,那么就要进行裁剪,这工作量是很大的,况且通过通用的库编译出来的apk,不一定适用于通用ANDROID机型。毕竟安卓是开源的,太多的厂商对他进行二次开发,导致标准不一等等!
综上所述,我又下载了VLC FOR ANDROID 发现,VLC基本可以支持所有ANDROID设备,为什么呢?我看了VLC的源码,发现了猫腻。VLC的数据输入,解复用,都采用的是FFMPEG来做的,在解码和渲染这一块,针对安卓有特殊处理!VLC针对解码和渲染,这里指硬解码和直接渲染,VLC是直接调用JAVA下的MEDIACODEC,来实现的!也就是说在JNI下直接调用JAVA的接口,这本身和在JAVA下调用MEDIACODEC没有任何区别了!至于为什么要这样做,有点疑惑!为什么不直接在JAVA下用MEDIACODEC呢?JAVA下的MEDIACODEC网上例子很多,基本上MEDIACODEC是绝大多数ANDROID版本支持的硬解码库!自认为是VLC想把解码这一套机制全部封装在通用解码框架里,这样就可以无缝为APP提供通用解码接口了吧!
废话不多说,我们就来试一试,如何在NATIVE下调用JAVA的MEDIACODEC吧!经过测试,这套机制基本通吃所有机型吧!而且清晰方便,兼容性强!    JNI的基本操作我就不讲了!自己学习吧
解码的数据结构!
enum Types
{
    METHOD, STATIC_METHOD, FIELD
};

struct audio_track_sys_t {
        jclass audio_track_class;//
        jmethodID audio_track_init, audio_set_volume, audio_get_min_buffer_size, audio_write, audio_play, audio_stop, audio_release;//
        jobject audio_track;//
};

struct decoder_sys_t
{
    jclass media_codec_list_class, media_codec_class, media_format_class;
    jclass buffer_info_class, byte_buffer_class;
        /*
        jclass audio_track_class;//
        jmethodID audio_track_init, audio_set_volume, audio_get_min_buffer_size, audio_write, audio_play, audio_stop, audio_release;//
        */
    jmethodID tostring;
    jmethodID get_codec_count, get_codec_info_at, is_encoder, get_capabilities_for_type;
    jfieldID profile_levels_field, profile_field, level_field;
    jmethodID get_supported_types, get_name;
    jmethodID create_by_codec_name, configure, start, stop, flush, release;
    jmethodID get_output_format, get_input_buffers, get_output_buffers;
    jmethodID dequeue_input_buffer, dequeue_output_buffer, queue_input_buffer;
    jmethodID release_output_buffer;
    jmethodID create_video_format, create_audio_format, set_integer, set_bytebuffer, get_integer;
    jmethodID buffer_info_ctor;
    jmethodID allocate_direct, limit;
    jfieldID size_field, offset_field, pts_field;

    uint32_t nal_size;
        /*
        jobject audio_track;//
        */
    jobject codec;
    jobject buffer_info;
    jobject input_buffers, output_buffers;
    int pixel_format;
    int stride, slice_height;
    int crop_top, crop_left;
    char *name;

    bool allocated;
    bool started;
    bool decoded;
    bool error_state;
    bool error_event_sent;

    /* Direct rendering members. */
    bool direct_rendering;
    int i_output_buffers; /**< number of MediaCodec output buffers */
};

#define OFF(x) offsetof(struct decoder_sys_t, x)
struct classname
{
    const char *name;
    int offset;
};

struct member
{
    const char *name;
    const char *sig;
    const char *class;
    int offset;
    int type;
};
解码器初始化
{
        char *source_path = (char *) (*env)->GetStringUTFChars(env, path, 0);
        const char *mime = "video/avc";
        int width = 1920;
        int height = 1080;
        bool b_direct_renderer = true;
        if ((p_sys = calloc(1, sizeof(*p_sys))) == NULL)
        return -1;

///////////////////////////////////Find Class and Get Method/Field

        for (int i = 0; classes[i].name; i++) {
                *(jclass*)((uint8_t*)p_sys + classes[i].offset) = (*env)->FindClass(env, classes[i].name);

                if ((*env)->ExceptionOccurred(env)) {
                    printf("Unable to find class %s", classes[i].name);
                    (*env)->ExceptionClear(env);
                    goto error;
                }
        }

    jclass last_class;
    for (int i = 0; members[i].name; i++) {
        if (i == 0 || strcmp(members[i].class, members[i - 1].class))
            last_class = (*env)->FindClass(env, members[i].class);

        if ((*env)->ExceptionOccurred(env)) {
            printf("Unable to find class %s", members[i].class);
            (*env)->ExceptionClear(env);
            goto error;
        }

        switch (members[i].type) {
        case METHOD:
            *(jmethodID*)((uint8_t*)p_sys + members[i].offset) =
                (*env)->GetMethodID(env, last_class, members[i].name, members[i].sig);
            break;
        case STATIC_METHOD:
            *(jmethodID*)((uint8_t*)p_sys + members[i].offset) =
                (*env)->GetStaticMethodID(env, last_class, members[i].name, members[i].sig);
            break;
        case FIELD:
            *(jfieldID*)((uint8_t*)p_sys + members[i].offset) =
                (*env)->GetFieldID(env, last_class, members[i].name, members[i].sig);
            break;
        }
        if ((*env)->ExceptionOccurred(env)) {
            printf("Unable to find the member %s in %s",
                     members[i].name, members[i].class);
            (*env)->ExceptionClear(env);
            goto error;
        }
    }

        printf("////////////// MEDIACODEC LIST\n");
        int num_codecs = (*env)->CallStaticIntMethod(env, p_sys->media_codec_list_class,
                                                 p_sys->get_codec_count);
    jobject codec_name = NULL;
    for (int i = 0; i < num_codecs; i++) {
                jobject info = NULL;
        jobject name = NULL;
                jobject types = NULL;
                jsize name_len = 0;
        const char *name_ptr = NULL;
                int profile_levels_len = 0, num_types = 0;
        bool found = false;

        info = (*env)->CallStaticObjectMethod(env, p_sys->media_codec_list_class,
                                              p_sys->get_codec_info_at, i);
        if ((*env)->CallBooleanMethod(env, info, p_sys->is_encoder))
            goto loopclean;

        types = (*env)->CallObjectMethod(env, info, p_sys->get_supported_types);
        num_types = (*env)->GetArrayLength(env, types);
                name = (*env)->CallObjectMethod(env, info, p_sys->get_name);
                name_len = (*env)->GetStringUTFLength(env, name);
        name_ptr = (*env)->GetStringUTFChars(env, name, NULL);
        found = false;

                printf("MEDIACODEC::LIST#%s\n", name_ptr);
                if (!strncmp(name_ptr, "OMX.google.", __MIN(11, name_len)))
            goto loopclean;

                for (int j = 0; j < num_types && !found; j++) {
            jobject type = (*env)->GetObjectArrayElement(env, types, j);
            if (!jstrcmp(env, type, mime)) {
                found = true;
            }
            (*env)->DeleteLocalRef(env, type);
        }

                if (found) {
            printf("using %.*s", name_len, name_ptr);
            p_sys->name = malloc(name_len + 1);
            memcpy(p_sys->name, name_ptr, name_len);
            p_sys->name[name_len] = '\0';
            codec_name = name;
        }
loopclean:
        if (name)
            (*env)->ReleaseStringUTFChars(env, name, name_ptr);
        if (types)
            (*env)->DeleteLocalRef(env, types);
        if (info)
            (*env)->DeleteLocalRef(env, info);
        if (found)
            break;
        }

        printf("////////////// MEDIACODEC\n");

        if (!codec_name) {
        printf("No suitable codec matching %s was found", mime);
        goto error;
    }

        printf("////////////// MEDIACODEC Create\n");
        p_sys->codec = (*env)->CallStaticObjectMethod(env, p_sys->media_codec_class,
                                                  p_sys->create_by_codec_name, codec_name);
    if ((*env)->ExceptionOccurred(env)) {
        printf("Exception occurred in MediaCodec.createByCodecName.");
        (*env)->ExceptionClear(env);
        goto error;
    }

        p_sys->allocated = true;
    p_sys->codec = (*env)->NewGlobalRef(env, p_sys->codec);

    jobject format = (*env)->CallStaticObjectMethod(env, p_sys->media_format_class,
                         p_sys->create_video_format, (*env)->NewStringUTF(env, mime),
                         width, height);

        printf("////////////// MEDIACODEC Configure\n");
        p_sys->direct_rendering = b_direct_renderer;
    if (p_sys->direct_rendering) {
        jobject surf = surface;
        if (surf) {
                        (*env)->CallVoidMethod(env, p_sys->codec, p_sys->configure, format, surf, NULL, 0);
                        if ((*env)->ExceptionOccurred(env)) {
                    printf("Exception occurred in MediaCodec.configure");
                    (*env)->ExceptionClear(env);
                    goto error;
                }
        } else {
                    printf("Failed to get the Android Surface, disabling direct rendering.");
            p_sys->direct_rendering = false;
        }
        }

        if (!p_sys->direct_rendering) {
        (*env)->CallVoidMethod(env, p_sys->codec, p_sys->configure, format, NULL, NULL, 0);
        if ((*env)->ExceptionOccurred(env)) {
            printf("Exception occurred in MediaCodec.configure");
            (*env)->ExceptionClear(env);
            goto error;
        }
    }

        printf("////////////// MEDIACODEC Start\n");
        (*env)->CallVoidMethod(env, p_sys->codec, p_sys->start);
    if ((*env)->ExceptionOccurred(env)) {
        printf("Exception occurred in MediaCodec.start");
        (*env)->ExceptionClear(env);
        goto error;
    }
    p_sys->started = true;

    p_sys->input_buffers = (*env)->CallObjectMethod(env, p_sys->codec, p_sys->get_input_buffers);
    p_sys->output_buffers = (*env)->CallObjectMethod(env, p_sys->codec, p_sys->get_output_buffers);
    p_sys->buffer_info = (*env)->NewObject(env, p_sys->buffer_info_class, p_sys->buffer_info_ctor);
    p_sys->input_buffers = (*env)->NewGlobalRef(env, p_sys->input_buffers);
    p_sys->output_buffers = (*env)->NewGlobalRef(env, p_sys->output_buffers);
    p_sys->buffer_info = (*env)->NewGlobalRef(env, p_sys->buffer_info);
    p_sys->i_output_buffers = (*env)->GetArrayLength(env, p_sys->output_buffers);

        VideoDecoderStart(p_sys);
        //receiver_start(source_path, 2);
        return 0;
error:

        if(p_sys != NULL)
        {
                free(p_sys);
                p_sys = NULL;
        }
        return -1;
}

解码和渲染

{
        JNIEnv *env;
        struct decoder_sys_t *p_sys = (struct decoder_sys_t *)arg;
        struct frame_queue *p_frame_queue = &programe_media.video.frame_queue;
        int sampleSize;
        int64_t pts;
        int fifo_cnts = 0;
        buffer_t *buffer = NULL;

          //Attach主线程
        if((*g_jvm)->AttachCurrentThread(g_jvm, &env, NULL) != JNI_OK)
        {
                   printf("%s: AttachCurrentThread() failed", __FUNCTION__);
                return NULL;
        }
        sem_wait(&video_decode_sem);
        //while(fifo_count(p_frame_queue->fifo) < 240)
                //usleep(1000);
        while(true)
        {
                int input_index = (*env)->CallIntMethod(env, p_sys->codec, p_sys->dequeue_input_buffer, (jlong) 0);
        if ((*env)->ExceptionOccurred(env)) {
            printf("Exception occurred in MediaCodec.dequeueInputBuffer");
            (*env)->ExceptionClear(env);
            p_sys->error_state = true;
            break;
        }
                if(input_index >= 0) {
                        jobject buf = (*env)->GetObjectArrayElement(env, p_sys->input_buffers, input_index);
                jsize size = (*env)->GetDirectBufferCapacity(env, buf);
                uint8_t *bufptr = (*env)->GetDirectBufferAddress(env, buf);

                        //printf("size is %d\n", size);
retry:
                        sampleSize = av_read_one_frame((uint8_t *)bufptr, &pts);
                        if(sampleSize <= 0)
                        {
                                //printf("VIDEO@sampleSize is %d\n", sampleSize);
                                goto retry;
                        }
                        size = sampleSize;

                        (*env)->CallVoidMethod(env, p_sys->codec, p_sys->queue_input_buffer, input_index, 0, size, pts, 0);
                (*env)->DeleteLocalRef(env, buf);
                if ((*env)->ExceptionOccurred(env)) {
                    printf("Exception in MediaCodec.queueInputBuffer");
                    (*env)->ExceptionClear(env);
                    p_sys->error_state = true;
                    break;
                }
                }

                jlong timeout = 0;
                int output_index = (*env)->CallIntMethod(env, p_sys->codec, p_sys->dequeue_output_buffer,
                                          p_sys->buffer_info, timeout);
        if ((*env)->ExceptionOccurred(env)) {
            printf("Exception in MediaCodec.dequeueOutputBuffer (GetOutput)");
            (*env)->ExceptionClear(env);
            p_sys->error_state = true;
            return NULL;
        }

                if(output_index >= 0) {
                        //printf("Buffers returned before output format is set, dropping frame");
                        (*env)->CallVoidMethod(env, p_sys->codec, p_sys->release_output_buffer, output_index, p_sys->direct_rendering);
                        if ((*env)->ExceptionOccurred(env)) {
                            printf("Exception in MediaCodec.releaseOutputBuffer");
                            (*env)->ExceptionClear(env);
                            p_sys->error_state = true;
                            return NULL;
                        }
                }

        }

error:
        //Detach主线程
        if((*g_jvm)->DetachCurrentThread(g_jvm) != JNI_OK)
        {
        printf("%s: DetachCurrentThread() failed", __FUNCTION__);
    }

    pthread_exit(0);
}

这就是核心代码,基本就是模拟在JAVA下的解码流程!
另外,注明下!音频解码!音频解码如果也用着套机制,可能对大多数机型不是很友好,具体指MP3格式!,有些机型支持,GOOGLE.MP3.DECODER,有些不支持,不一定可用,所以这里奉劝大家还是自己移植MP3解码库吧,我用的LIBMAD,还不错!

没事,自己顶一下

lubuntu下的视频硬解码 跟你的一样吗?

很是佩服。学习一下

大神,你太生猛了。

很多同学大多是在JAVA层做的,这样会导致很多奇奇怪怪的问题,建议还是在JNI层下做吧,这样可以一步一步分析是哪里出错了!

顶一个

最好多写些注释,大神

没事,自己顶!!!

{:4_158:}

ding

先顶,再试试。
已经实现了MEDIACODEC,但很多机子跑步了。看这个效果怎样

学习下实现思路,多谢

huntengan 发表于 2016-11-24 19:59
先顶,再试试。
已经实现了MEDIACODEC,但很多机子跑步了。看这个效果怎样

这个肯定是完美支持的,只要他支持mediacodec库

学习了

学习一下

顶一个