jingjin221 发表于 2015-12-22 11:07:02

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.name; i++) {
                *(jclass*)((uint8_t*)p_sys + classes.offset) = (*env)->FindClass(env, classes.name);

                if ((*env)->ExceptionOccurred(env)) {
                  printf("Unable to find class %s", classes.name);
                  (*env)->ExceptionClear(env);
                  goto error;
                }
      }
      
    jclass last_class;
    for (int i = 0; members.name; i++) {
      if (i == 0 || strcmp(members.class, members.class))
            last_class = (*env)->FindClass(env, members.class);

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

      switch (members.type) {
      case METHOD:
            *(jmethodID*)((uint8_t*)p_sys + members.offset) =
                (*env)->GetMethodID(env, last_class, members.name, members.sig);
            break;
      case STATIC_METHOD:
            *(jmethodID*)((uint8_t*)p_sys + members.offset) =
                (*env)->GetStaticMethodID(env, last_class, members.name, members.sig);
            break;
      case FIELD:
            *(jfieldID*)((uint8_t*)p_sys + members.offset) =
                (*env)->GetFieldID(env, last_class, members.name, members.sig);
            break;
      }
      if ((*env)->ExceptionOccurred(env)) {
            printf("Unable to find the member %s in %s",
                     members.name, members.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 = '\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,还不错!








jingjin221 发表于 2016-3-10 13:42:46

没事,自己顶一下

0000li 发表于 2016-4-22 16:54:18

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

脑残丞相 发表于 2016-4-27 12:57:11

很是佩服。学习一下

daihuan509 发表于 2016-5-31 17:32:10

大神,你太生猛了。

jingjin221 发表于 2016-8-23 09:46:39

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

sankycui 发表于 2016-8-24 09:18:33

顶一个

yly123ycyn 发表于 2016-8-25 17:40:32

最好多写些注释,大神

jingjin221 发表于 2016-11-11 09:58:21

没事,自己顶!!!

jingjin221 发表于 2016-11-11 17:50:07

{:4_158:}
页: [1] 2 3
查看完整版本: MEDIACODEC在JNI层下的H264,H265视频硬解码实现(ACODEC )