首页 > 代码库 > [原]如何在Android用FFmpeg解码图像

[原]如何在Android用FFmpeg解码图像

前一篇[原]如何用Android NDK编译FFmpeg 我们知道了如何使用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 <stdlib.h>#include <string.h>#include <stdio.h>#include <jni.h>#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 <jni.h>#include <android/native_window_jni.h>#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");    }}

八、执行结果如下图:

 

[原]如何在Android用FFmpeg解码图像