// 본글은 여러 블로그를 참고하여 작성되었습니다. //
// 작성 일자 : 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 |
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 |
4. SDK 설치
4.1. AS(Android Studio 실행)
$ cd /home/user/Downloads/android-studio/bin/ | |
$ ./studio.sh |
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)' |
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 |
|| 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 |
|| 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) |
2. build.gradle 수정
src 하위의 build.gradle을 수정합니다.
sourceSets.main { | |
jni.srcDirs=[] | |
jniLibs.srcDirs "src/main/libs" | |
} |
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) |
보면 알겠지만 ffmpeg라이브러리 이름을 맞춰줘야 한다.
LOCAL_MODULE은 호출되는 ModuleName이며 나중에 loadLibrary와 쌍을 이룬다.
APP_ABI=armeabi-v7a |
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" /> |
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(); | |
} |
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) |
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 // |
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); | |
} |
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. |
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); | |
} |
4. 실행 (run) 하면 메인 화면이 크다면 300x400 만큼 짤린 비디오가 생성되어 있을 것이다.
/Dowdloads
└ demo.mp4
└ output.mp4
'Android Story' 카테고리의 다른 글
[ Android ] 줄번호 설정 (0) | 2017.01.13 |
---|---|
[ Android ] FFmpeg Rtsp Player 사용하기 (12) | 2016.12.27 |
[ Android ] Network type (0) | 2016.12.19 |
[ Android ] FFplay 문서 번역 (0) | 2016.12.13 |
[ Android ] inflate (0) | 2016.12.07 |
[ Android ] 다음 맵에서 지원하지 않는 점선 라인.. 해결?? (0) | 2016.11.26 |
[ Android ] 마커 이미지에 인덱스 합성 (0) | 2016.11.26 |
[ Android ] 사이즈가 큰 이미지 뷰 (0) | 2016.11.25 |