<작업환경>

-윈도우10

-안드로이드 스튜디오 2.3.3

-필요 라이브러리 빌드된 ffmpeg 라이브러리

  (ffmpeg 빌드하기 : http://gamdekong.tistory.com/103?category=776642 )



1. 안드로이드 프로젝트 생성




Android 에서 Project로 변경






2. NDK 설치


FIle -> Setting

설치되는 공간은 안드로이드 스튜디오가 설치된 폴더 안에 생성된다.







3. External Tools Setting


File -> Setting




external tools 셋팅창에서 +를 눌러 아래와 같이 추가한다.




ndk-build

- program 의 경로는 설치된 안드로이드 스튜디오에 있다.( ndk가 설치된곳)

- insert macro를 누르면 환경변수에 대한 설명이 나온다.



ndk-build clean

-clean만 추가시켜 준다.




javah

-javah 파일은 jdk가 설치된 곳에 있다.

-간혹 parameters가 문제가 될수 있는데 여기서 중요한것은 -classpath 옵션은 클래스가 있는 위치를 설정하는것이다.

    -jni -classpath "$ProjectFileDir$/app/src/main/java/;$Classpath$" -v $FileClass$  로 변경

  $FileClass$ 는 패키지를 포함한 클래스 위치를 나타내는것으로 알고있다.






4. NDK Sample 작성


$Project/app/src/main/jni 디렉토리를 만들어 줍니다.

( javah를 이용하여 헤더를 만들어 넣을 폴더입니다. javah tools 에서 워킹 디렉토리의 대상이 됩니다.)




NDK.java 를 다음 내용으로 만듭니다.

- sample-ffmpeg 는 다음에 만들 c파일의 라이브러리 이름이 된다.




NDK 클래스에서 오른쪽 버튼을 누르고 javah를 클리하여 헤더파일을 만든다.




헤더파일이 만들어진것을 확인할 수 있다.


NDK클래스에 선언된 native 함수를 자동적으로 헤더파일에 추가해준다.


JNIEXPORT jint JNICALL Java_company_co_kr_ffmpegndk_NDK_scanning(JNIEnv *, jobject, jstring);


기본적으로

녹색 : 패키지명

적색 : 클래스명

청색 : 함수명


JNI문법은 따로 공부하자






5. ffmpeg 라이브러리 설정하기


기본적으로 미리 빌드된 ffmpeg 라이브러리는 포스팅 하기 편하게 프로젝트 폴더안에 넣었다.

이 라이브러리는 프로젝트 밖에 있어도 상관없다. 이 라이브러리를 이용해 so파일을 만들기 때문에

경로만 알고 있으면 된다.


여기서는 편하게 app/libs에 넣었다.



5-1 Android.mk 파일을 만들어 준다.




Android.mk 내용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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)
cs


위와 같이 파일 생성후 내용을 입력해준다.

간단하게


LOCAL_PATH:= $(call my-dir)   //LOCAL_PATH 에 현재 Android.mk파일이 있는 위치를 저장


include $(CLEAR_VARS)        // 변수 초기화


LOCAL_MODULE:= libavcodec            // 라이브러리 이름설정

LOCAL_SRC_FILES:= lib/libavcodec-57.so        //소스파일 경로설정

LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include        // include 경로 설정

PREBUILT_SHARED_LIBRARY        // 미리 만들어진 공유 라이브러리


더 공부를 위해서  Android.mk 내용 https://developer.android.com/ndk/guides/android_mk.html?hl=ko  참조한다.




5-2 $Project/app/build.gradle 에 추가.


여기서 src/main/libs안에  .so 파일이 떨어지고

src/main/libs 안에 있는 라이브러리를 가지고 apk파일을 생성한다.


경로는 수정 가능하다.




그리고 gradle 파일 수정후에는 항상 sync gradle을 해주자





5-3 $Project/app/src/main/jni/에 Android.mk 를 다음 내용으로 만들자


Android.mk 내용

1
2
3
4
5
6
7
8
9
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := sample-ffmpeg
LOCAL_SRC_FILES := sample-ffmpeg.c
LOCAL_LDLIBS := -llog
LOCAL_SHARED_LIBRARIES := libavformat libavcodec libswscale libavutil libswresample libavfilter
include $(BUILD_SHARED_LIBRARY)
$(call import-add-path, D:\AndroidSample\FFmpegTest3\app)
$(call import-module, libs)
cs


여기서 주위점은


$(call import-module, libs) 에서 기존에 있는 라이브러리의 경로를 지정하는 것이다.

 그런데 이 경로의 기준은 $NDK_MODULE_PATH 이다 기존에 ndk가 설치된 위치를 가르킨다.

 그렇기 때문에 다른곳이 있다면 따로 추가를 해줘야한다.



$(call import-add-path, D:\AndroidSample\FFmpegTest3\app) 를 이용하여 프로젝트의 app폴더의 위치를 추가한다.



LOCAL_LDLIBS := -llog        //NDK에서 제공하는 라이브러리

 참조는 https://developer.android.com/ndk/guides/stable_apis.html?hl=ko



LOCAL_SHARED_LIBRARIES         //공유라이브러리들의 이름을 적는다.






5-4 $Project/app/src/main/jni/에 Application.mk 만든다


Application.mk 내용

1
APP_ABI=armeabi-v7a


cs





5-5 JNI 작성


$Project/app/src/main/jni/에 sample-ffmpeg.c 를 만든다



sample-ffmpeg.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <jni.h>
#include "libavformat/avformat.h"
#include <android/log.h>
 
#define LOG_TAG "FFmpegForAndroid"
 
#define LOGI(...) __android_log_print(4, LOG_TAG, __VA_ARGS__);
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG , "libnav", __VA_ARGS__)
#define LOGE(...) __android_log_print(6, LOG_TAG, __VA_ARGS__);
 
 
JNIEXPORT jint JNICALL Java_company_co_kr_ffmpegndk_NDK_scanning(JNIEnv *env, jobject object, jstring filepath)
 
{
 
    const char* nativeFilepath = (*env)->GetStringUTFChars( env, filepath , NULL ) ;
    //const char* nativeFilepath = "/storage/9016-4EF8/aaa.mkv";
 
    AVFormatContext* avFormatContext = NULL;
 
    // muxer, demuxer, decoder, encoder 초기화
   av_register_all();
 
    // nativeFilepath로 avFormatContext 가져오기
 
    if(avformat_open_input(&avFormatContext, nativeFilepath, NULLNULL< 0)
    {
        LOGE("Can't open input file '%s'", nativeFilepath);
        (*env)->ReleaseStringUTFChars(env, filepath, nativeFilepath);
        return -1;
    }
 
    // 유효한 스트림 정보 찾기
    if(avformat_find_stream_info(avFormatContext, NULL< 0)
    {
        LOGE("Failed to retrieve input stream information");
        (*env)->ReleaseStringUTFChars(env, filepath, nativeFilepath);
        return -2;
    }
 
    // avFormatContext->nb_streams : 비디오 파일의 전체 스트림 수
    for(unsigned int index = 0; index < avFormatContext->nb_streams; index++)
    {
        AVCodecParameters* avCodecParameters = avFormatContext->streams[index]->codecpar;
        if(avCodecParameters->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            LOGI("------- Video info -------");
            LOGI("codec_id : %d", avCodecParameters->codec_id);
            LOGI("bitrate : %lld", avCodecParameters->bit_rate);
            LOGI("width : %d / height : %d", avCodecParameters->width, avCodecParameters->height);
        }
        else if(avCodecParameters->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            LOGI("------- Audio info -------");
            LOGI("codec_id : %d", avCodecParameters->codec_id);
            LOGI("bitrate : %lld", avCodecParameters->bit_rate);
            LOGI("sample_rate : %d", avCodecParameters->sample_rate);
            LOGI("number of channels : %d", avCodecParameters->channels);
        }
    }
 
    // release
    if(avFormatContext != NULL)
    {
        avformat_close_input(&avFormatContext);
    }
 
    // release
    (*env)->ReleaseStringUTFChars(env, filepath, nativeFilepath);
 
    return 0;
}
 
 
cs


scanning 라는 JNI function 을 제작했으며, 넘겨진 영상파일 경로를 받아, 해당 영상정보를 로그로 출력한다.




5-6 JNI 컴파일


오른쪽 버튼을 눌러 ndk-build를 클릭하자



정상적으로 실행되었다면 libs 폴더에 라이브러리 파일이 생성된것을 확인할 수 있다.




6. AndroidManifest.xml 수정



AndroidManifest.xml 에 퍼미션  부분을 추가한다.

외장 저장 공간을 사용하기 위해서는 허가가 필요하다.




Android 6.0(마시멜로) 이상에서는 추가된 Runtime Permission 을 추가하여야 한다.

참고 사이트 : https://developer.android.com/training/permissions/requesting.html?hl=ko







7. MainActivity 수정


다음과 같이 수정한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package company.co.kr.ffmpegndk;
 
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
 
import java.io.File;
import java.io.IOException;
 
public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
 
        String filepath;
        try {
            filepath = new File(Environment.getExternalStorageDirectory(), "/sample-video.mp4").getCanonicalPath();
            new NDK().scanning(filepath);
        } catch (IOException e) {
            Log.e("FFmpegForAndroid""", e);
        }
 
    }
}

cs






8. App 실행


App을 실행해 본다

정상적이라면 아래와 비슷한 로그가 LogCat 에 출력된다.


1
2
3
4
5
6
7
8
9
10
I/FFmpegForAndroid: ------- Video info -------
I/FFmpegForAndroid: codec_id : 28
I/FFmpegForAndroid: bitrate : 816549
I/FFmpegForAndroid: width : 640 / height : 640
I/FFmpegForAndroid: ------- Audio info -------
I/FFmpegForAndroid: codec_id : 86018
I/FFmpegForAndroid: bitrate : 126711
I/FFmpegForAndroid: sample_rate : 48000
I/FFmpegForAndroid: number of channels : 2
 
cs






9. 주의점


절대로 주위해야 할것은 오타이다.


나는 이 오타때문에 하루를 날렸다....


특히 언더바( _ ) 와 데쉬 ( - ) 구별을 잘하자!!!





참고사이트


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

http://whiteduck.tistory.com/130

http://gogorchg.tistory.com/entry/Android-javalangUnsatisfiedLinkError-dalviksystemPathClassLoaderDexPathList

http://gavinliu.cn/2017/03/05/Android-FFmpeg-Mac-AndroidStudio-CMake-%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/


+ Recent posts