Android Story

[ Android ] FFMpeg 패키지 연동하기

WhiteDuck 2016. 12. 13. 15:35

// 본글은 여러 블로그를 참고하여 작성되었습니다. //

// 작성 일자 : 2016.12.13 //

// FFMPEG과 동영상 플레이어에 관한 기본 지식 : [ http://d2.naver.com/helloworld/8794 ] //



|| 개발 환경 ||


1. OS : Ubuntu 16.04 ( VMware workstation 12 ) : https://www.ubuntu.com/download/desktop

2. Android Studio 2.2.3 : https://developer.android.com/studio/index.html?hl=ko

3. RAM 3GB ( VMware에서 돌아가기 위해서 충분한 RAM 이 필요 )

4. Memory : 30GB ( VMware에서 돌아가기 위해서 충분한 메모리가 필요 )



// Ubuntu 설치 이후부터 설명하며 한글은 지원하지 않은 채로 진행합니다. //

// USB 인식이 안 될 수 있습니다. VMWare 업데이트를 진행한 후에 팔로우하시기 바랍니다. //

// 저자의 ubuntu id는 user입니다. //





|| Android  studio 설치 ||


 참고 : Android 설치 환경 구축 : https://www.davidlab.net/ko/tech/how-to-setup-android-dev-env-on-ubuntu-part1/


1. JDK 설치 

$ sudo add-apt-repository ppa:webupd8team/java
$ sudo apt-get update
$ sudo apt-get install oracle-java8-installer
view raw gistfile1.txt hosted with ❤ by GitHub



2. Android 설치

// 저자는 앞으로 다운로드하는 모든 항목은 Downloads에 모두 저장하였습니다. //



3. gmtp 설치 [ cpu가 VT-x 를 지원하지 않을 경우, 또는 실 장비 테스트가 필요한 경우 ]

// 저자의 경우에는 그냥 안되서 실 장비 테스트를 하였습니다. //

참고 : http://askubuntu.com/questions/146529/how-to-connect-mtp-devices-via-usb


$ sudo apt-get install gmtp
view raw gistfile1.txt hosted with ❤ by GitHub


4. SDK 설치

 4.1. AS(Android Studio 실행)

$ cd /home/user/Downloads/android-studio/bin/
$ ./studio.sh
view raw gistfile1.txt hosted with ❤ by GitHub


 4.2 SDK Manager

 Configure > SDKManager




 4.3 install SDK

 하고 싶은 SDK 선택



4.4 install SDK tool

 NDK 설치









|| FFmpeg 빌드 ||


 참고 : http://dev2.prompt.co.kr/77


1. FFMpeg 다운로드 :  https://ffmpeg.org/download.html#releases



2. FFMpeg 파일 이동

 미리 설치했던 NDK 폴더 하위의 sourses 폴더에 집어넣습니다.



3. Configure 파일 수정

 configure 파일은 ffmpeg-3.2.2 파일 안에 있습니다. 이 파일을 변경하지 않으면 빌드 후 so 파일이 만들어질때 *.so.57 과 같은 형식으로 만들어서 AS에 적용되지 않습니다.

#SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)'
#LIB_INSTALL_EXTRA_CMD='$$(RANLIB) "$(LIBDIR)/$(LIBNAME)"'
#SLIB_INSTALL_NAME='$(SLIBNAME_WITH_VERSION)'
#SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR) $(SLIBNAME)'
SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB) "$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'
SLIB_INSTALL_LINKS='$(SLIBNAME)'
view raw configure hosted with ❤ by GitHub



4. build script 작성

 ffmpeg-3.2.2 하위에  build_android.sh 생성



NDK=$HOME/Android/Sdk/ndk-bundle
SYSROOT=$NDK/platforms/android-16/arch-arm/
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64
function build_one
{
./configure \
--prefix=$PREFIX \
--enable-shared \
--disable-static \
--disable-doc \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-ffserver \
--disable-avdevice \
--disable-doc \
--disable-symver \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--target-os=linux \
--arch=arm \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
make clean
make
make install
}
CPU=arm
PREFIX=$(pwd)/android/$CPU
ADDI_CFLAGS="-marm"
build_one


5. build

$ chmod +x build_android.sh
$ ./build_android.sh
view raw build.txt hosted with ❤ by GitHub










|| JNI 환경 구축 ||


 참고 : http://dev2.prompt.co.kr/78


1. 프로젝트 생성

 일반적인 프로젝트 생성후 Project로 보기



2. 오른쪽 클릭 tool 생성 [ 편의성을 위해서 사용합니다. ]

 File > Setting 클릭 ( 키 중첩으로 Ctrl + Alt + S 가 먹히지 않습니다. )



 2.1. 추가



Name : javah
Program : /usr/lib/jvm/java-8-oracle/bin/javah
Parameters : -classpath $ClassPath$ -v -jni $FileClass$
Working directory : $ModuleFileDir$/src/main/jni
Name : ndk-build
Program : /home/user/Android/Sdk/ndk-bundle/build/ndk-build
Working directory : $ProjectFileDir$/app/src/main
Name : ndk-build clean
Program : /home/user/Android/Sdk/ndk-bundle/build/ndk-build
Parameters : clean
Working directory : $ProjectFileDir$/app/src/main
view raw gistfile1.txt hosted with ❤ by GitHub










|| FFMpeg Setting ||


 참고 : http://dev2.prompt.co.kr/79 http://dev2.prompt.co.kr/80


1. ffmpeg Android.mk 세팅 [ ffmpeg에서 so 파일을 가져오기 위한 설정 ]


 $NDK/sources/ffmpeg-3.2.2/android/arm/Android.mk 를 다음 내용으로 만듭니다.

 실제 파일이 정말로 있는지 확인한 후에 진행합니다.

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:= libavcodec
LOCAL_SRC_FILES:= lib/libavcodec-57.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= libavformat
LOCAL_SRC_FILES:= lib/libavformat-57.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= libswscale
LOCAL_SRC_FILES:= lib/libswscale-4.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= libavutil
LOCAL_SRC_FILES:= lib/libavutil-55.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= libavfilter
LOCAL_SRC_FILES:= lib/libavfilter-6.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE:= libswresample
LOCAL_SRC_FILES:= lib/libswresample-2.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
view raw gistfile1.txt hosted with ❤ by GitHub


2. build.gradle 수정

 src 하위의 build.gradle을 수정합니다.



sourceSets.main {
jni.srcDirs=[]
jniLibs.srcDirs "src/main/libs"
}
view raw gistfile1.txt hosted with ❤ by GitHub



3. jni의 Android.mk, Application.mk 작성

src의 main에 jni을 생성하고 Android.mk와 Application.mk를 작성합니다.



LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := ffmpeg
LOCAL_SRC_FILES := ffmpeg.c ffmpeg_opt.c cmdutils.c ffmpeg_filter.c
LOCAL_LDLIBS := -llog
LOCAL_SHARED_LIBRARIES := libavformat libavcodec libswscale libavutil libswresample libavfilter
include $(BUILD_SHARED_LIBRARY)
$(call import-module, ffmpeg-3.2.2/android/arm)
view raw Android.mk hosted with ❤ by GitHub


보면 알겠지만 ffmpeg라이브러리 이름을 맞춰줘야 한다.

LOCAL_MODULE은 호출되는 ModuleName이며 나중에 loadLibrary와 쌍을 이룬다.


APP_ABI=armeabi-v7a
view raw Application.mk hosted with ❤ by GitHub

build.gradle에 다음과 같이 써야 적용된다.






|| JNI 컴파일 및 간단한 예제 ||


예제는 http://dev2.prompt.co.kr/80 와 같이 비디오 파일을 복사하는 것이며

ffmpeg 명령은


ffmpeg -i input.mp4 -filter:v crop=300:400:10:20 output.mp4


이라도 한다.


저자는 Download 폴더 밑 demo.mp4 파일을 output.mp4 파일로 복사하였다.


1. AndroidManifest.xml 변경

 외부 입력 장치 접근 권한 허가

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
view raw gistfile1.txt hosted with ❤ by GitHub


2. MainActivity.java 변경

  ndk는 NDKAdapter를 나중에 추가할 것이며, 메소드는 run_ffmepg이다.

  WRITE_EXTERNAL_STORAGE가 있어야 최신 AndroidVersion에서 외부 저장장치 접근이 가능하다.

package com.gluesys.nvr;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import com.gluesys.util.NDKAdapter;
import java.io.File;
public class MainActivity extends AppCompatActivity {
final int MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE =0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (ContextCompat.checkSelfPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) {
// 이 권한을 필요한 이유를 설명
// It can see the file list in external storage.
if (ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// 다이어로그같은것을 띄워서 사용자에게 해당 권한이 필요한 이유에 대해 설명합니다
// 해당 설명이 끝난뒤 requestPermissions()함수를 호출하여 권한허가를 요청해야 합니다
} else {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
// 필요한 권한과 요청 코드를 넣어서 권한허가요청에 대한 결과를 받아야 합니다
}
}
else {
new NDKAdapter().run_ffmpeg();
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE:
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 권한 허가
// 해당 권한을 사용해서 작업을 진행할 수 있습니다
new NDKAdapter().run_ffmpeg();
} else {
// 권한 거부
// 사용자가 해당권한을 거부했을때 해주어야 할 동작을 수행합니다
}
return;
}
}
}


3. NDKAdapter.java 생성

  간접적으로 ffmpeg 라이브러리에 접근하기 위한 클래스이다.

package com.gluesys.util;
/**
* Created by user on 12/12/16.
*/
public class NDKAdapter {
static {
System.loadLibrary("ffmpeg");
}
public native int run_ffmpeg();
}
view raw NDKAdapter.java hosted with ❤ by GitHub



4. ffmpeg 라이브러리 가져오기 

 jni파일에 ffmpeg에 있던 *.h 밑 .c 파일을 가져온다. ( 파일 통채로 복사해도 됨 )



5. javah NDKAdapter 

 이것을 통해서 jni에 직접적으로 ffmpeg에 접근할 것을 만들어준다.

 참고로 Build > Make Project를 하지 않으면 안 만들어 진다. 



6. com_gluesys_util_NDKAdapter.h 확인

 해당 폴더에는 (5) 번에서 진행되어 컴파일된 .h파일이 있다. 안에는 사용되는 클래스와 메소드가 나오는데 이에 따라 직접적으로 연결시킬 메소드의 이름을 알수 있다. 


/*
* Class: com_gluesys_util_NDKAdapter
* Method: run_ffmpeg
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_com_gluesys_util_NDKAdapter_run_1ffmpeg (JNIEnv *, jobject);

7. ffmpeg.c 수정

 jni 파일에 보면 ffmpeg..c 파일이 있다. 거기에는 main()이 있는데 이를 run_ffmpeg으로 수정한다.

//int main(int argc, char **argv)
int run_ffmpeg(int argc, char **argv)
view raw gistfile1.txt hosted with ❤ by GitHub



7.1 상단에는 include와 define을 정의하고

// test - start //
#include <jni.h>
#include <android/log.h>
#define LOG_TAG "veaver"
#define LOGI(...) __android_log_print(4, LOG_TAG, __VA_ARGS__);
#define LOGE(...) __android_log_print(6, LOG_TAG, __VA_ARGS__);
// test -end //
view raw gistfile1.txt hosted with ❤ by GitHub


7.2최 하단에는 (6)에서 만들어진 메소드에 다음과 같은 소스를 기입한다. [ 실제로 테스트의 모바일 저장장치에는 /Download 밑에 demo.mp4가 있어야 한다. ]

JNIEXPORT jint JNICALL Java_com_gluesys_util_NDKAdapter_run_1ffmpeg(JNIEnv *env, jobject obj)
{
// /sdcard/input.mp4 가 있다는 가정하에 작성된 코드 입니다.
// 아래의 명령어를 수행하는 코드입니다.
// ffmpeg -i /sdcard/input.mp4 -filter:v crop=300:400:10:20 /sdcard/output.mp4
char* a0 = "ffmpeg";
char* a1 = "-i";
char* a2 = "/storage/emulated/0/Download/demo.mp4";
char* a3 = "-filter:v";
char* a4 = "crop=300:400:10:20";
char* a5 = "/storage/emulated/0/Download/output.mp4";
char* argv[6];
argv[0] = a0;
argv[1] = a1;
argv[2] = a2;
argv[3] = a3;
argv[4] = a4;
argv[5] = a5;
LOGI("call run_ffmpeg");
run_ffmpeg(6, &argv);
}
view raw gistfile1.txt hosted with ❤ by GitHub



8. 다 작성되었으면 ndk-build를 실행한다.




9. 실행 (run) 하면 메인 화면이 크다면 300x400 만큼 짤린 비디오가 생성되어 있을 것이다.


/Dowdloads

  └ demo.mp4

  └ output.mp4




 그 외 참고 자료 : 

 * Android Studio에서 JNI 연동 #1 [ http://blog.naver.com/PostView.nhn?blogId=just4u78&logNo=220630233740&parentCategoryNo=&categoryNo=&viewDate=&isShowPopularPosts=false&from=postView ]









|| 2016.12.21 일자 추가 사항 ||


 .ffmpeg 명령을 java에서 동작할 수 있도록 수정


1. MainActivity.java


String a0 = "ffmpeg";
String a1 = "-i";
String a2 = "/storage/emulated/0/Download/demo.mp4";
String a3 = "-filter:v";
String a4 = "crop=300:400:10:20";
String a5 = "/storage/emulated/0/Download/output.mp4";
String[] argv = new String[]{a0,a1,a2,a3,a4,a5};
// 동작
new NDKAdapter().run_ffmpeg(argv);




2. NDKActivity.java

public native int run_ffmpeg(String[] cmdLine); // It is thread.
view raw NDKAdapter.java hosted with ❤ by GitHub



3. ffmpeg.c


JNIEXPORT jint JNICALL Java_com_gluesys_util_NDKAdapter_run_1ffmpeg(JNIEnv *env, jobject obj, jobjectArray cmdLines)
{
jstring jstr;
jsize len = (*env)->GetArrayLength(env, cmdLines);
char **argv = (char **) malloc(len*sizeof(char *));
int i=0;
for (i=0 ; i<len; i++) {
jstr = (*env)->GetObjectArrayElement(env, cmdLines, i);
argv[i] = (char *)(*env)->GetStringUTFChars(env, jstr, 0);
}
LOGI("call run_ffmpeg");
if( len > 0)
run_ffmpeg(len, argv);
}
view raw ffmpeg.c hosted with ❤ by GitHub




4. 실행 (run) 하면 메인 화면이 크다면 300x400 만큼 짤린 비디오가 생성되어 있을 것이다.


/Dowdloads

  └ demo.mp4

  └ output.mp4



반응형