为你的app增加更强大的音视频处理功能(FFmpeg In Practice)

缘起

  至于还不知道ffmpeg的同学,请移步FFmpeg常用基本命令,翻阅一两下,看看它的功能能不能让你的app更好玩更强大。
   它可以合成音视频,播放各类编码的音视频,截取某一段,让多张图片合成一个视频并且和音频合成、转格式等等。

   配音秀里的配音很好玩,我们也想做一个配音的模块。于是找到了工具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的目录下,像这样:

为你的app增加更强大的音视频处理功能(FFmpeg In Practice)


调试ffmpeg

ffmpeg移植后,也用了jni来调用函数,但可能出现很多问题,怎么调试呢?

如果我们也能得到像命令行下这个调式信息就好了,

为你的app增加更强大的音视频处理功能(FFmpeg In Practice)

这个是log日志,我们只用找到相应的位置,把它的printf语句附近加一个Log.i就可以了。


这个printf的位置,你可以在eclipse下从原来的main函数入口在av_log类似的位置按住ctrl键点击,跳转到函数位置一步步跟踪下去得到。它在ffmpeg/libavutil/log.c里面的av_log_default_callback函数里,像这样:

为你的app增加更强大的音视频处理功能(FFmpeg In Practice)


注意到LOGD了么,它是

为你的app增加更强大的音视频处理功能(FFmpeg In Practice)

...是可变长参数。可变长参数最简单的例子就是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杀掉。

为你的app增加更强大的音视频处理功能(FFmpeg In Practice)

AnroidMainfest.xml :

为你的app增加更强大的音视频处理功能(FFmpeg In Practice)

在你的程序里注册一个receiver:

为你的app增加更强大的音视频处理功能(FFmpeg In Practice)


为你的app增加更强大的音视频处理功能(FFmpeg In Practice)

所以,你就可以第一次调用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函数头

为你的app增加更强大的音视频处理功能(FFmpeg In Practice)

每次都要自己来写这个挺麻烦的,其实可以一键生成:

配置一个external tools,

为你的app增加更强大的音视频处理功能(FFmpeg In Practice)

就是用javah命令来生成,然后让它集成到eclipse里面。

在eclipse里的标题栏里点击为你的app增加更强大的音视频处理功能(FFmpeg In Practice)就可以在jni目录里看到com_lzw_iword_video_Myffmpeg.h类似的文件,像这样:

为你的app增加更强大的音视频处理功能(FFmpeg In Practice)

像这样,把这个头文件include进你的c文件然后,复制粘贴函数头就行。


如果君移植ffmpeg到Android,或者用ffmpeg遇到什么问题,可以评论评论提提问呗,交流交流~


为你的app增加更强大的音视频处理功能(FFmpeg In Practice),布布扣,bubuko.com

为你的app增加更强大的音视频处理功能(FFmpeg In Practice)

上一篇:android中Bitmap数据如何释放


下一篇:Android - Intent广播(broadcast)