缘起
配音秀里的配音很好玩,我们也想做一个配音的模块。于是找到了工具ffmpeg。试图把它移植到Android上。ffmpeg大概捣鼓了我3、4天,差不多是一个版本版本的试,网上的很多文章都不行,最后让我成功的是这篇文章《android 使用ffmpeg 并调用接口》 。我试过很多版本,只有ffmpeg1.2和ndkr9搭配,能移植成功。
思路
ffmpeg是一个c写的程序,ffmpeg.c里面有main函数,我们先把它在Android ndk下编译出来libffmpeg.so文件,然后利用这个库,来编译ffmpeg.c文件,把ffmpeg.c文件中的main函数改为video_merge函数,即video_merge(int argc,char **argv)。那么接下来像这样子合成视频视频:
void jstringToCstr(JNIEnv *env,jstring _jstring,jbyte **_cstr){ jboolean isCopy=0; (*_cstr)=(*env)->GetStringUTFChars(env,_jstring,&isCopy); } JNIEXPORT jstring JNICALL Java_com_lzw_iword_video_Myffmpeg_stringFromJNI (JNIEnv * env, jclass clz, jstring videoPath, jstring audioPath, jstring avPath){ char const *str1; int n = 0; char *argv[20]; jbyte* str[3]; jstringToCstr(env,videoPath,&str[0]); jstringToCstr(env,audioPath,&str[1]); jstringToCstr(env,avPath,&str[2]); argv[n++] = "ffmpeg"; argv[n++] = "-i"; argv[n++] = str[0]; argv[n++] = "-i"; argv[n++] = str[1]; argv[n++] = "-y"; argv[n++] = "-strict"; argv[n++] = "-2"; argv[n++] = str[2]; int ret = vedio_merge(n, argv); str1 = "Using FFMPEG doing your job"; return (*env)->NewStringUTF(env, str1); }
也就像在命令行下调用:ffmpeg -i src1 -i src2 -y output
环境
ubuntu 12.04
先去看那篇教程,不行了再来翻阅这篇,讲讲在那篇教程下还会遇到的问题。
改接口调用
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) PATH_TO_FFMPEG_SOURCE:=$(LOCAL_PATH)/ffmpeg LOCAL_C_INCLUDES += $(PATH_TO_FFMPEG_SOURCE) LOCAL_LDLIBS := -lffmpeg -llog -ljnigraphics -lz -ldl -lgcc //这些就是所要关联的库了,刚才把ffmpeg.so复制到android-ndk-r8d/platforms/android-14/arch-arm/usr/lib目录下的原因就是为了这个 LOCAL_MODULE := ffmpeg-jni LOCAL_SRC_FILES := ffmpeg-jni.c ffmpeg/cmdutils.h ffmpeg/cmdutils.c ffmpeg/ffmpeg.h ffmpeg/ffmpeg_opt.c ffmpeg/ffmpeg_filter.c //必须把这几个文件编译进去,不然会很多undefinded的。。 include $(BUILD_SHARED_LIBRARY)在改接口调用的时候,那篇文章,用了这个Android.mk,
首先,
LOCAL_LDLIBS := -lffmpeg
这个我一直没有运行成功。
这个表示链接库,也就是要编译当前这个MODULE,那些所需要的函数等就去一个目录找,找libffmpeg.so文件,-l的意思是说link的意思,ffmpeg表示库的名字,至于lib和.so系统自动会加上。
于是我用了这个:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE =myffmpeg LOCAL_SRC_FILES :=libffmpeg.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) PATH_TO_FFMPEG_SOURCE:=$(LOCAL_PATH)/ffmpeg LOCAL_C_INCLUDES += $(PATH_TO_FFMPEG_SOURCE) LOCAL_MODULE=ffmpeg-jni LOCAL_LDLIBS := -llog -ljnigraphics -lz -ldl -lgcc -lm LOCAL_SRC_FILES := myffmpeg.c ffmpeg/cmdutils.h ffmpeg/cmdutils.c ffmpeg/ffmpeg.h ffmpeg/ffmpeg_opt.c ffmpeg/ffmpeg_filter.c LOCAL_SHARED_LIBRARIES:=myffmpeg include $(BUILD_SHARED_LIBRARY)
注意到这个先编译了一个共享库myffmpeg,然后再通过
LOCAL_SHARED_LIBRARIES:=myffmpeg
链接进来编译最终我们需要的ffmpeg-jni。
这时候要把libffmpeg.so文件放在jni的目录下,像这样:
调试ffmpeg
ffmpeg移植后,也用了jni来调用函数,但可能出现很多问题,怎么调试呢?
如果我们也能得到像命令行下这个调式信息就好了,
这个是log日志,我们只用找到相应的位置,把它的printf语句附近加一个Log.i就可以了。
这个printf的位置,你可以在eclipse下从原来的main函数入口在av_log类似的位置按住ctrl键点击,跳转到函数位置一步步跟踪下去得到。它在ffmpeg/libavutil/log.c里面的av_log_default_callback函数里,像这样:
注意到LOGD了么,它是
...是可变长参数。可变长参数最简单的例子就是printf,有时可以pirntf("%d",a),还可以printf("%d %d",a,a);前一个是两个参数,后一个是三个参数。就是用类似的语法做到的。
__android_log_print就是android提供的打印到Logcat里的函数。
这样,你就可以看到输出日志了,对于为什么合成不了,是不支持mp3还是不支持amr,还是视频问题就可以通过这个日志来帮忙了。
有时会突然异常突出。
如何知道出错位置呢。
命令行下输入:adb shell logcat | ndk-stack -sym obj/local/armeabi
你会发现合成好了,app还是会崩溃退出,因为ffmpeg原来的main函数最后面有个exit语句,注释掉即可。
合成好了,又会发现,再把它调用一次,会出现NVALID HEAP ADDRESS IN dlfree ffmpeg的错误。原因是ffmpeg内存泄漏,还有一些动态申请的空间没有释放掉。我们干脆让ffmpeg的合成放在一个service里好了。合成一次,然后就把那个service杀掉。
AnroidMainfest.xml :
在你的程序里注册一个receiver:
所以,你就可以第一次调用ffmpeg生成一个没有声音的video文件,播放它,让用户配音,然后第二次调用,合成用户的声音以及视频。
在我这里,比较蛋疼的是,如果录音AAC Encoder的话,能合成视频,但是不能用mediaplayer播放AAC文件(小米2s专有问题?),而如果用armnb的话便不能合成,还要重新生成libffmpeg.so,在configure里enable-armnb,还有一些问题没有成功,就是enable-armnb之后运行./config.sh编译armnb出问题,说不能找到相应的文件。找到了又说它所include的文件又没找到。同样,enable-libmp3lame也没弄成功。
之所以要求也能播放,是因为合成一次10s的1280*720与10秒的录音要大概1分钟的时间。希望用户可以先听听配音的效果,然后再确定合不合成。
观察配音秀的dubbing文件夹就可以发现,配音秀录好了用户的声音,得到tmp.amr,tmp.pcm然后上传到服务器,进行合成,再把合成好的下载下来。
当配音的时候,已经下载好了原声的视频、字幕、已经去除了需要配音的片段的声音的音频。这样配音的时候,它一直播放那个音频就行,到用户配音的时候,音频里是静音的。
Eclipse里用ndk-build
其实不用在命令行中进行ndk build,选中项目,右击选择Android tools,然后选择Add native Support,那么每次在项目里运行的时候,自动会ndk build。
一键生成jni函数头
每次都要自己来写这个挺麻烦的,其实可以一键生成:
配置一个external tools,
就是用javah命令来生成,然后让它集成到eclipse里面。
在eclipse里的标题栏里点击就可以在jni目录里看到com_lzw_iword_video_Myffmpeg.h类似的文件,像这样:
像这样,把这个头文件include进你的c文件然后,复制粘贴函数头就行。
如果君移植ffmpeg到Android,或者用ffmpeg遇到什么问题,可以评论评论提提问呗,交流交流~