前一篇 我们知道了如何使用NDK来编译Android平台下使用的FFmpeg动态库。这篇文章我们就可以使用Android下的JNI来调用FFMpeg进行解码了。
一、编译出来可以使用的动态库,我们会看到如下输出则表示link完成了:
CC libavcodec/log2_tab.oCC libavutil/log2_tab.oCC libswresample/log2_tab.oAR libavcodec/libavcodec.aLD libavutil/libavutil.so.52AR libavutil/libavutil.aAR libswresample/libswresample.aLD libavcodec/libavcodec.so.55LD libswresample/libswresample.so.0LD libswscale/libswscale.so.2LD libavformat/libavformat.so.55INSTALL libavformat/libavformat.aINSTALL libavformat/libavformat.soSTRIP install-libavformat-sharedINSTALL libavcodec/libavcodec.aINSTALL libavcodec/libavcodec.soSTRIP install-libavcodec-sharedINSTALL libswresample/libswresample.aINSTALL libswresample/libswresample.soSTRIP install-libswresample-sharedINSTALL libswscale/libswscale.aINSTALL libswscale/libswscale.soSTRIP install-libswscale-sharedINSTALL libavutil/libavutil.aINSTALL libavutil/libavutil.soSTRIP install-libavutil-sharedINSTALL libavformat/avformat.hINSTALL libavformat/avio.hINSTALL libavformat/version.hINSTALL libavformat/libavformat.pcINSTALL libavcodec/avcodec.hINSTALL libavcodec/avfft.hINSTALL libavcodec/dxva2.hINSTALL libavcodec/old_codec_ids.hINSTALL libavcodec/vaapi.hINSTALL libavcodec/vda.hINSTALL libavcodec/vdpau.hINSTALL libavcodec/version.hINSTALL libavcodec/xvmc.hINSTALL libavcodec/libavcodec.pcINSTALL libswresample/swresample.hINSTALL libswresample/version.hINSTALL libswresample/libswresample.pcINSTALL libswscale/swscale.hINSTALL libswscale/version.hINSTALL libswscale/libswscale.pcINSTALL libavutil/adler32.hINSTALL libavutil/aes.hINSTALL libavutil/attributes.hINSTALL libavutil/audio_fifo.hINSTALL libavutil/audioconvert.hINSTALL libavutil/avassert.hINSTALL libavutil/avstring.hINSTALL libavutil/avutil.hINSTALL libavutil/base64.hINSTALL libavutil/blowfish.hINSTALL libavutil/bprint.hINSTALL libavutil/bswap.hINSTALL libavutil/buffer.hINSTALL libavutil/channel_layout.hINSTALL libavutil/common.hINSTALL libavutil/cpu.hINSTALL libavutil/crc.hINSTALL libavutil/error.hINSTALL libavutil/eval.hINSTALL libavutil/fifo.hINSTALL libavutil/file.hINSTALL libavutil/frame.hINSTALL libavutil/hmac.hINSTALL libavutil/imgutils.hINSTALL libavutil/intfloat.hINSTALL libavutil/intfloat_readwrite.hINSTALL libavutil/intreadwrite.hINSTALL libavutil/lfg.hINSTALL libavutil/log.hINSTALL libavutil/mathematics.hINSTALL libavutil/md5.hINSTALL libavutil/mem.hINSTALL libavutil/murmur3.hINSTALL libavutil/dict.hINSTALL libavutil/old_pix_fmts.hINSTALL libavutil/opt.hINSTALL libavutil/parseutils.hINSTALL libavutil/pixdesc.hINSTALL libavutil/pixfmt.hINSTALL libavutil/random_seed.hINSTALL libavutil/rational.hINSTALL libavutil/ripemd.hINSTALL libavutil/samplefmt.hINSTALL libavutil/sha.hINSTALL libavutil/sha512.hINSTALL libavutil/time.hINSTALL libavutil/timecode.hINSTALL libavutil/timestamp.hINSTALL libavutil/version.hINSTALL libavutil/xtea.hINSTALL libavutil/lzo.hINSTALL libavutil/avconfig.hINSTALL libavutil/libavutil.pc link ffmpeg.
二、新建一个Android工程,在工程目录下新建一个jni文件夹,在文件夹下新建一个ffmpeg文件夹,用来放ffmpeg相关的头文件。在ffmpeg文件夹下新建Android.mk文件用来预先加载ffmpeg动态库。Android.mk文件内容如下:
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE := ffmpegLOCAL_SRC_FILES := /path/to/build/output/libffmpeg.soinclude $(PREBUILT_SHARED_LIBRARY)
三、在jni下新建Android.mk文件和Application.mk两个文件用来指定编译的顺序和编译的平台以及对应的cpu指令集。
Application.mkAPP_ABI := armeabiAPP_PLATFORM := android-9
Android.mk include $(call all-subdir-makefiles)
四、编写JNI文件,用来绑定java文件与.c文件的交互,文件内容如下:
/* * ffmpeg_jni.c * * Created on: Sep 1, 2014 * Author: clarck */#include#include #include #include #include "../include/ffmpeg_logger.h"#include "../include/ffmpeg.h"// 指定要注册的类,对应完整的java类名#define JNIREG_CLASS "com/clarck/android/ffmpeg/MainActivity"JNIEXPORT void JNICALL native_setDataSource(JNIEnv *env, jclass classzz, jstring path) { char *filepath = ffmpeg_jstringTostr(env, path); ffmpeg_setDataSource(filepath);}//Java和JNI函数的绑定static JNINativeMethod method_table[] = { { "setDataSource", "(Ljava/lang/String;)V", native_setDataSource }};//注冊native方法到java中static int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *gMethods, int numMethods) { jclass clazz; clazz = (*env)->FindClass(env, className); if (clazz == NULL) { return JNI_FALSE; } if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) { return JNI_FALSE; } return JNI_TRUE;}//調用註冊方法int register_ndk_load(JNIEnv *env) { return registerNativeMethods(env, JNIREG_CLASS, method_table, (int) (sizeof(method_table) / sizeof(method_table[0])));}JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { JNIEnv *env = NULL; jint result = -1; if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) { return result; } register_ndk_load(env); //返回JNI的版本 return JNI_VERSION_1_6;}
五、编写ffmpeg调用函数,内容如下:
/* * ffmpeg.c * * Created on: Sep 1, 2014 * Author: clarck */#include#include #include "../include/ffmpeg.h"#include "../include/ffmpeg_logger.h"#include "../ffmpeg/include/libavcodec/avcodec.h"#include "../ffmpeg/include/libavformat/avformat.h"#include "../ffmpeg/include/libavutil/pixfmt.h"#include "../ffmpeg/include/libswscale/swscale.h"char* ffmpeg_jstringTostr(JNIEnv* env, jstring jstr) { char* pStr = NULL; jclass jstrObj = (*env)->FindClass(env, "java/lang/String"); jstring encode = (*env)->NewStringUTF(env, "utf-8"); jmethodID methodId = (*env)->GetMethodID(env, jstrObj, "getBytes", "(Ljava/lang/String;)[B"); jbyteArray byteArray = (jbyteArray) (*env)->CallObjectMethod(env, jstr, methodId, encode); jsize strLen = (*env)->GetArrayLength(env, byteArray); jbyte *jBuf = (*env)->GetByteArrayElements(env, byteArray, JNI_FALSE); if (jBuf > 0) { pStr = (char*) malloc(strLen + 1); if (!pStr) { return NULL ; } memcpy(pStr, jBuf, strLen); pStr[strLen] = 0; } (*env)->ReleaseByteArrayElements(env, byteArray, jBuf, 0); return pStr;}void ffmpeg_setDataSource(char *file_path) { LOGI("ffmpeg_setDataSource:%s", file_path); AVFormatContext *pFormatCtx; AVCodecContext *pCodecCtx; AVCodec *pCodec; AVFrame *pFrame, *pFrameYUV; AVPacket *packet; uint8_t *out_buffer; static struct SwsContext *img_convert_ctx; int videoStream, i, numBytes; int ret, got_picture; av_register_all(); pFormatCtx = avformat_alloc_context(); if (avformat_open_input(&pFormatCtx, file_path, NULL, NULL) != 0) { LOGE("can't open the file. \n"); return; } if (avformat_find_stream_info(pFormatCtx, NULL) < 0) { LOGE("Could't find stream infomation.\n"); return; } videoStream = 1; for (i = 0; i < pFormatCtx->nb_streams; i++) { if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { videoStream = i; } } if (videoStream == -1) { LOGE("Didn't find a video stream.\n"); return; } pCodecCtx = pFormatCtx->streams[videoStream]->codec; pCodec = avcodec_find_decoder(pCodecCtx->codec_id); if (pCodec == NULL) { LOGE("Codec not found.\n"); return; } if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { LOGE("Could not open codec.\n"); return; } pFrame = av_frame_alloc(); pFrameYUV = av_frame_alloc(); numBytes = avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height); out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t)); avpicture_fill((AVPicture *) pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height); int y_size = pCodecCtx->width * pCodecCtx->height; packet = (AVPacket *) malloc(sizeof(AVPacket)); av_new_packet(packet, y_size); av_dump_format(pFormatCtx, 0, file_path, 0); while (av_read_frame(pFormatCtx, packet) >= 0) { if (packet->stream_index == videoStream) { ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet); LOGI("avcodec_decode_video2 ret:%d", ret); if (ret < 0) { LOGE("decode error.\n"); return; } if (got_picture) { //TODO 此处可以将解码出来的图片保存起来。 } } av_free_packet(packet); } av_free(out_buffer); av_free(pFrameYUV); avcodec_close(pCodecCtx); avformat_close_input(&pFormatCtx);}
六、编写Android.mk用来编译相关的.c文件,内容如下:
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)FFMPEG_PATH := ../ffmpegLOCAL_C_INCLUDES := $(LOCAL_PATH)/$(FFMPEG_PATH)/includeLOCAL_MODULE := ffmpeg_playerLOCAL_SRC_FILES += ffmpeg_jni.c LOCAL_SRC_FILES += ffmpeg.cLOCAL_SHARED_LIBRARIES := ffmpegLOCAL_LDLIBS := -lloginclude $(BUILD_SHARED_LIBRARY)
七、编写java文件中相关执行调用方法
package com.clarck.android.ffmpeg;import android.app.Activity;import android.os.Bundle;public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setDataSource("/sdcard/a.mp4"); } public native void setDataSource(String path); static { System.loadLibrary("ffmpeg"); System.loadLibrary("ffmpeg_player"); }}
八、执行结果如下图: