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,还不错!
没事,自己顶一下 lubuntu下的视频硬解码 跟你的一样吗? 很是佩服。学习一下 大神,你太生猛了。 很多同学大多是在JAVA层做的,这样会导致很多奇奇怪怪的问题,建议还是在JNI层下做吧,这样可以一步一步分析是哪里出错了! 顶一个 最好多写些注释,大神 没事,自己顶!!! {:4_158:}