码迷,mamicode.com
首页 > 其他好文 > 详细

jni下使用ffmpeg

时间:2015-04-06 12:40:52      阅读:306      评论:0      收藏:0      [点我收藏+]

标签:

系统,ubuntu14.04LTS,32bit(x86),android-ndk-r10d,ffmpeg-2.6.1.tar.bz2源码包

 

1.首先去官网上下载上述ffmpeg源码,http://ffmpeg.org/download.html

技术分享

 

2.切换到ffmpeg解压后的目录下,执行如下配置检查命令:

技术分享

在我的电脑上没有什么问题,如果有问题请自行google或是stackoverflow,要不问问我也行,mylhyz@sina.com

 

3.为了支持Android的JNI拓展,一般第三方包都是编译成动态链接库.so或是静态库.a然后在构建的时候被包含,为了复用性我们一般使用的是.a动态库,我这里使用的是编译的静态库,在当前源码目录下建立一个build_android.sh的可执行bash脚本,所有代码如下:

技术分享

其中,NDK目录还有其他的目录一般都要根据你自己的实际情况配置,上述脚本我在查资料的时候都是大同小异,所以如果你有好的配置脚本请自行尝试(但是要提醒一句,编译过程比较漫长),要理解./configure的那些参数的意义,可以使用./configure --help命令查看所有支持的参数和默认值([NO]),

要支持Android平台只需要很少的东西,至少可执行的工具选项可以被disable,架构要选arm,我选用的是静态库,因此enable-static,其他的按照help逐个理解即可,其他额外编解码工具应该也可以自行设置,我暂时没有设置,也不会设置。

技术分享

最后在build_one函数执行之前设置的PREFIX就是编译完成后生成的库文件所在的位置。

 

4.执行上述构建脚本,不要管他是不是有警告,只要最后成功编译完成即可(因为肯定有warning提示),最后成功之后生成的东西在这里找到

技术分享

..屏幕不够大,只能截出这么多

共计 9 directories, 102 files。

 

5.将上述ffmpeg目录拷贝到一个用来配置NDK的目录下

技术分享

,在ext下就是ffmpeg目录:

技术分享

这时候我们来建立上述Android.mk文件,内容如下:

 1 LOCAL_PATH:= $(call my-dir)
 2 
 3 #static version of libavcodec
 4 include $(CLEAR_VARS)
 5 LOCAL_MODULE:= libavcodec_static
 6 LOCAL_SRC_FILES:= lib/libavcodec.a
 7 LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
 8 include $(PREBUILT_STATIC_LIBRARY)
 9 
10 #static version of libavformat
11 include $(CLEAR_VARS)
12 LOCAL_MODULE:= libavformat_static
13 LOCAL_SRC_FILES:= lib/libavformat.a
14 LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
15 include $(PREBUILT_STATIC_LIBRARY)
16 
17 #static version of libswscale
18 include $(CLEAR_VARS)
19 LOCAL_MODULE:= libswscale_static
20 LOCAL_SRC_FILES:= lib/libswscale.a
21 LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
22 include $(PREBUILT_STATIC_LIBRARY)
23 
24 #static version of libavutil
25 include $(CLEAR_VARS)
26 LOCAL_MODULE:= libavutil_static
27 LOCAL_SRC_FILES:= lib/libavutil.a
28 LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
29 include $(PREBUILT_STATIC_LIBRARY)
30 
31 #static version of libavfilter
32 include $(CLEAR_VARS)
33 LOCAL_MODULE:= libavfilter_static
34 LOCAL_SRC_FILES:= lib/libavfilter.a
35 LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
36 include $(PREBUILT_STATIC_LIBRARY)
37 
38 #static version of libavswresample
39 include $(CLEAR_VARS)
40 LOCAL_MODULE:= libswresample_static
41 LOCAL_SRC_FILES:= lib/libswresample.a
42 LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
43 include $(PREBUILT_STATIC_LIBRARY)

 这个将来是要被预编译的链接库,不要单独编译这个makefile检来测是否有问题。

 

6.现在我们需要来到最难的问题,编辑JNI模块并集成到Android应用中。首先建立一个Android项目,主Activity的代码如下:

技术分享

文件路径是一个绝对路径,文件是一个.mp4文件。编译出来的库模块我命名为ffmpeg。另外要在权限上声明:

1 <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
2     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
3     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

然后通过javah按照上述Activity的java文件生成c头文件

然后将头文件转移到刚才包含ext/ffmpeg 的 NDK目录下的jni中:

技术分享

可以先编写infomsg这个源文件作为将来生成模块的源文件:所有代码如下,包含了那个生成的头文件:

  1 #include <jni.h>
  2 #include <android/log.h>
  3 #include <stdio.h>
  4 
  5 #include <libavcodec/avcodec.h>
  6 #include <libavformat/avformat.h>
  7 #include <libavformat/avio.h>
  8 #include <libavutil/fifo.h>
  9 #include <libavutil/avutil.h>
 10 #include <libavutil/mem.h>
 11 #include <libswscale/swscale.h>
 12 
 13 #include "com_example_jnidemo_MainActivity.h"
 14 
 15 #define  LOG_TAG    "FFMPEG INFO"
 16 #define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
 17 #define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
 18 
 19 
 20 void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) {
 21   FILE* pFile;
 22   char szFileName[32];
 23   int y;
 24 
 25   sprintf(szFileName,"/mnt/sdcard/frame%d.ppm",iFrame);
 26 
 27   LOGI("filename : %s",szFileName);
 28 
 29   pFile = fopen(szFileName,"w");
 30   if(pFile == NULL){
 31       LOGI("can not open file %s",szFileName);
 32       return ;
 33   }
 34 
 35   fprintf(pFile,"P6\n%d %d\n255\n",width,height);
 36 
 37   for(y=0;y<width;++y){
 38       LOGI("Write file AVFrame");
 39       fwrite(pFrame->data[0]+y*pFrame->linesize[0],1,width*3,pFile);
 40   }
 41 
 42   fclose(pFile);
 43   LOGI("close file %s",szFileName);
 44 }
 45 
 46 JNIEXPORT void JNICALL Java_com_example_jnidemo_MainActivity_info(JNIEnv *env, jobject obj, jstring jpath){
 47     const jbyte* path = (*env)->GetStringUTFChars(env,jpath,NULL);
 48       AVFormatContext   *pFormatCtx = NULL;
 49       int               i, videoStream;
 50       AVCodecContext    *pCodecCtxOrig = NULL;
 51       AVCodecContext    *pCodecCtx = NULL;
 52       AVCodec           *pCodec = NULL;
 53       AVFrame           *pFrame = NULL;
 54       AVFrame           *pFrameRGB = NULL;
 55       AVPacket          packet;
 56       int               frameFinished;
 57       int               numBytes;
 58       uint8_t           *buffer = NULL;
 59       struct SwsContext *sws_ctx = NULL;
 60 
 61       av_register_all();
 62 
 63       if(avformat_open_input(&pFormatCtx, path, NULL, NULL)!=0)
 64       {
 65           LOGE("Could not open the file : %s",path);
 66           return ;
 67     }
 68 
 69       if(avformat_find_stream_info(pFormatCtx, NULL)<0)
 70         return ;
 71 
 72       av_dump_format(pFormatCtx, 0, path, 0);
 73   
 74       videoStream=-1;
 75       for(i=0; i<pFormatCtx->nb_streams; i++)
 76         if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) 
 77         {
 78               videoStream=i;
 79               break;
 80         }
 81 
 82       if(videoStream==-1)
 83         return ;
 84 
 85       pCodecCtxOrig=pFormatCtx->streams[videoStream]->codec;
 86       
 87       pCodec=avcodec_find_decoder(pCodecCtxOrig->codec_id);
 88       if(pCodec==NULL) 
 89       {
 90         LOGE("Unsupported codec!\n");
 91         return ;
 92       }
 93 
 94       pCodecCtx = avcodec_alloc_context3(pCodec);
 95       if(avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) 
 96       {
 97         LOGE("Couldn‘t copy codec context");
 98         return ;
 99       }
100 
101     if(avcodec_open2(pCodecCtx, pCodec, NULL)<0)
102         return ;
103   
104       pFrame=av_frame_alloc();
105       pFrameRGB=av_frame_alloc();
106       if(pFrameRGB==NULL)
107         return ;
108 
109       numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,pCodecCtx->height);
110       buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
111 
112       avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,pCodecCtx->width, pCodecCtx->height);
113 
114       sws_ctx = sws_getContext(pCodecCtx->width,
115                pCodecCtx->height,
116                pCodecCtx->pix_fmt,
117                pCodecCtx->width,
118                pCodecCtx->height,
119                PIX_FMT_RGB24,
120                SWS_BILINEAR,
121                NULL,
122                NULL,
123                NULL
124                );
125 
126       i=0;
127       while(av_read_frame(pFormatCtx, &packet)>=0) {
128         if(packet.stream_index==videoStream) {
129             
130             avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
131               
132             if(frameFinished) {
133 
134                 sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
135           pFrame->linesize, 0, pCodecCtx->height,
136           pFrameRGB->data, pFrameRGB->linesize);
137     
138                 if(++i<=5)
139                       SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i);
140               }
141         }
142         av_free_packet(&packet);
143     }
144       av_free(buffer);
145       av_frame_free(&pFrameRGB);
146       av_frame_free(&pFrame);
147       avcodec_close(pCodecCtx);
148       avcodec_close(pCodecCtxOrig);
149       avformat_close_input(&pFormatCtx);
150       (*env)->ReleaseStringUTFChars(env,jpath,path);
151 }

具体这个文件的原始来源是:http://dranger.com/ffmpeg/tutorial01.html,由于为了支持到Android原生代码,我做了一些简单修改。

 

编辑完成之后我们先编辑Application.mk文件来指定平台:

技术分享

这是因为其他平台都不能使用我用在x86工具链生成的静态库,如果强制使用(就是指定了arm64或是mips/x86_64),会出现一些类似符号引用或是不支持的.a库格式(稍后我会添加我遇到的错误的解决方法),如果需要支持其他平台可能需要更改ffmpeg源代码编译脚本上的工具链等地方。

然后我们来编辑Android.mk文件生成链接库:

技术分享

 

这样我们只需要在NDK目录下依次执行如下命令:

技术分享

原因是NDK_MODULE_PATH是上述代码$(call import-module,ext/ffmpeg)中第二个参数的相对路径,相对的路径就是NDK_MODULE_PATH的目录。

 

到此为止我成功在Android下编译运行了上述应用,生成的framex.ppm文件如下(我copy到了电脑上):技术分享

 

 

上述小demo的github地址:https://github.com/wylhyz/JNIDemo-FFMPEG-config,包含了上述所有的文件,连带有编译好的ffmpeg下的动态库和include头文件。

 由于Github空间有限,NDK部分已上传到百度网盘:http://pan.baidu.com/s/1gdKnePP

如果还有其他问题,请私聊:

mylhyz@sina.com

 

jni下使用ffmpeg

标签:

原文地址:http://www.cnblogs.com/lhyz/p/4395579.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!