안녕하세요 ^^

 

세번째 강좌로 Android.mk 를 작성해 보겠습니다.



참고) 2011. 10. 20. 현재 오류 발견하여 수정 중 입니다.

libavcodec의 Android.mk 파일에서 NEON_ADD_C := aacdec.c 이하 4 줄이 문제가 있는 것 같습니다.

Audio가 AAC LC 코덱으로 인코딩 된 경우 디코딩 되지 않으며 튕깁니다.

현재 작업중이며 해결되면 글 수정하겠습니다.

 

 

6. Application.mk 작성

 

g:/Root/FFmpegBasic/jni 폴더에 Application.mk 파일을 만듭니다.

내용은 간단히 아래와 같이 한 줄만 작성합니다.

 

 APP_ABI := armeabi-v7a

 

참고:
arm architecture ARMv7-A 이상을 타겟으로 컴파일 하겠다는 옵션입니다.
arm CoretexA8 이상의 core가 이에 해당됩니다.
앞서 말씀드린대로 arm11 코어를 사용한 Optimus One, Galaxy Neo 같은 폰에서는 안 돌아가겠지요.

7. Android.mk 작성

 

Android.mk 는 폴더마다 여러개를 작성해야 합니다.
공통으로 사용할 common.mk 파일을 먼저 작성한 후, 각각 폴더마다 설명하겠습니다.

 

* common.mk

 

g:/Root/FFmpegBasic/jni/ffmpeg 폴더에 common.mk 파일을 만듭니다.
모든 Android.mk에서 공통으로 include 해서 사용할 파일입니다.

 

common.mk에서는 크게 두가지 일을 할 것입니다.
1) 공통으로 사용할 컴파일 옵션을 정의합니다.
2) configure를 통해 생성된 파일에서 컴파일 할 소스 파일 이름들을 읽어 저장합니다.

 

1) 컴파일 옵션은 다음과 같이 한 줄이면 됩니다.

 

COMMON_CFLAGS := -DHAVE_AV_CONFIG_H -D_ISOC99_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_POSIX_C_SOURCE=200112 -D_XOPEN_SOURCE=600 -DPIC -std=c99 -fomit-frame-pointer -fPIC -fno-math-errno -fno-signed-zeros -fno-tree-vectorize


참고:
컴파일 옵션이 복잡해 보이지만 그냥 configure에서 생성된 컴파일 옵션을 그대로 정리해 준 것 뿐입니다.

 

ffmpeg 폴더의 common.mak 파일을 열어보시면
아래와 같은 부분이 있습니다.

%.o: %.c
      $(CCDEP)
      $(CC) $(CPPFLAGS) $(CFLAGS) $(CC_DEPFLAGS) -c $(CC_O) $<

FFmpeg 컴파일 할 때, $(CPPFLAGS) $(CFLAGS) $(CC_DEPFLAGS) 이 세 개의 매크로에 정의된 옵션들을 사용하는 것을 알 수 있습니다.

 

ffmpeg 폴더의 config.mak 파일을 열어보시면 이 값들이 정의되어 있습니다.

 

CPPFLAGS는 아래와 같습니다.

CPPFLAGS= -D_ISOC99_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_POSIX_C_SOURCE=200112 -D_XOPEN_SOURCE=600 -DPIC

이 값들은 다 사용해 줍니다.

 

CFLAGS는 엄청 깁니다.

CFLAGS= -marm -march=armv7-a -mfloat-abi=softfp -mfpu=neon  -std=c99 -fomit-frame-pointer -fPIC -marm -g -Wdeclaration-after-statement -Wall -Wno-parentheses -Wno-switch -Wdisabled-optimization -Wpointer-arith -Wredundant-decls -Wno-pointer-sign -Wcast-qual -Wwrite-strings -Wtype-limits -Wundef -Wmissing-prototypes -Wno-pointer-to-int-cast -O3 -fno-math-errno -fno-signed-zeros -fno-tree-vectorize -Werror=implicit-function-declaration -Werror=missing-prototypes

복잡해 보이지만 하나씩 차근히 보면 정리가 됩니다.

여기서 -marm -march=armv7-a -mfloat-abi=softfp -mfpu=neon -g -O3 옵션들은 다 뺍니다.
우리는 Android ndk-build를 사용할 것이기 때문에 -O3 같은 최적화 관련 옵션은 지정하지 않습니다.
(이것은 android build system이 알아서 해줍니다)
-marm -march=armv7-a -mfloat-abi=softfp -mfpu=neon 이와 같은 cross compile관련, neon 관련 옵션도 빼줍니다.
(이것은 나중에 Android.mk의 옵션으로 지정해 줄 것입니다)
마지막으로 -W 로 시작하는 옵션은 warning 관련 옵션이니 그냥 다 뺍니다.

 

CC_DEPFLAGS는 별 것 없고 상관없는 값들입니다. 무시합니다.

 

추가로 subdir.mak 파일을 보시면 아래와 같은 부분이 있습니다.

 $(OBJS) $(SUBDIR)%.ho $(SUBDIR)%-test.o $(TESTOBJS): CPPFLAGS += -DHAVE_AV_CONFIG_H

$(OBJS) 에 정의된 모든 파일에 위 조건이 해당되므로 -DHAVE_AV_CONFIG_H 도 포함합니다.

 

이렇게 정리하면 위에서 한 줄로 정리한 COMMON_CFLAGS 컴파일 옵션들이 나옵니다.

 

2) 컴파일 할 소스 파일들을 정의

 

이 부분은 소스가 좀 길고 복잡하게 느껴질 수 있습니다. 하지만 역시 핵심은 간단합니다.

 

먼저 FFmpeg의 Makefile을 하나만 분석해 보겠습니다.

ffmpeg 폴더의 common.mak 파일을 열어보면 아래와 같은 부분이 있습니다.

 OBJS      += $(OBJS-yes)

컴파일에 사용할 소스 파일은 OBJS 매크로와 OBJS-yes 매크로에 정의되어 있다는 것을 알 수 있습니다.

우리도 이 소스들을 컴파일 하면 되므로 똑같이 적어줍니다.

 

이제 OBJS 매크로에는 xxxxx.o 와 같은 object 파일들이 쭉 저장되게 됩니다.
이걸 그냥 간단히 전부 xxxxx.c로 변환해서 쓰면 가장 쉽겠지만 그렇게 간단하지는 않습니다.
우선 c 파일 외에도 xxxxx.S 와 같은 어셈블리 코드들이 포함되어 있고,
neon 컴파일 해야하는 소스들은 xxxxx.c.neon 또는 xxxxx.S.neon 과 같이 neon 접미사를 붙여줘야 하기 때문입니다.

 

다행인 것은, FFmpeg 소스들을 보면 neon 컴파일 해야 하는 소스들은 모두

_neon.c 와 같이 _neon 접미사가 붙어 있어서 이것으로 구분이 가능합니다.
(ffmpeg/libavcodec/arm 폴더의 파일들을 훑어 보시기 바랍니다)
따라서 _neon 접미사를 검색해서 해당 접미사가 있는 소스에만 .neon을 마지막에 추가해 주면 됩니다.

 

위와 같은 과정을 수동으로 일일이 진행하셔도 좋지만 번거로우니 Makefile 문법을 사용해 작성해 주면 됩니다.
최종적으로 컴파일 할 소스 파일들은 각각 다음 매크로에 저장할 것입니다.

 

C_FILES: 컴파일 할 c 파일
S_FILES: 컴파일 할 S 파일
NEON_C_FILES: neon 컴파일 할 c 파일
NEON_S_FILES: neon 컴파일 할 S 파일
FFFILES: 컴파일 할 모든 소스 파일 전부 정의

 

이제까지 설명한 것을 종합해서 common.mk의 전체 소스를 보여드리면 아래와 같습니다.

common.mk 파일을 다음과 같이 작성해줍니다.

 

COMMON_CFLAGS := -DHAVE_AV_CONFIG_H -D_ISOC99_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_POSIX_C_SOURCE=200112 -D_XOPEN_SOURCE=600 -DPIC -std=c99 -fomit-frame-pointer -fPIC -fno-math-errno -fno-signed-zeros -fno-tree-vectorize

OBJS += $(OBJS-yes)

ALL_S_FILES := $(wildcard $(LOCAL_PATH)/$(ARCH)/*.S)
ALL_S_FILES := $(addprefix $(ARCH)/,$(notdir $(ALL_S_FILES)))

NEON_S_FILES := $(wildcard $(LOCAL_PATH)/$(ARCH)/*_neon.S)
NEON_S_FILES := $(addprefix $(ARCH)/,$(notdir $(NEON_S_FILES)))

NEON_C_FILES := $(wildcard $(LOCAL_PATH)/$(ARCH)/*_neon.c)
NEON_C_FILES := $(addprefix $(ARCH)/,$(notdir $(NEON_C_FILES)))

S_FILES := $(filter-out $(NEON_S_FILES),$(ALL_S_FILES))

C_OBJS := $(OBJS)
ifneq ($(S_FILES),)
S_OBJS := $(S_FILES:.S=.o)
S_OBJS := $(filter $(S_OBJS),$(C_OBJS))
C_OBJS := $(filter-out $(S_OBJS),$(C_OBJS))
else
S_OBJS :=
endif

ifneq ($(NEON_S_FILES),)
NEON_S_OBJS := $(NEON_S_FILES:.S=.o)
NEON_S_OBJS := $(filter $(NEON_S_OBJS),$(C_OBJS))
C_OBJS := $(filter-out $(NEON_S_OBJS),$(C_OBJS))
else
NEON_S_OBJS :=
endif

ifneq ($(NEON_C_FILES),)
NEON_C_OBJS := $(NEON_C_FILES:.c=.o)
NEON_C_OBJS := $(filter $(NEON_C_OBJS),$(C_OBJS))
C_OBJS := $(filter-out $(NEON_C_OBJS),$(C_OBJS))
else
NEON_C_OBJS :=
endif

C_FILES := $(C_OBJS:.o=.c)
S_FILES := $(S_OBJS:.o=.S)
NEON_C_FILES := $(NEON_C_OBJS:.o=.c.neon)
NEON_S_FILES := $(NEON_S_OBJS:.o=.S.neon)

FFFILES := $(sort $(NEON_S_FILES)) $(sort $(NEON_C_FILES)) $(sort $(S_FILES)) $(sort $(C_FILES))


참고:
위의 makefile 문법이 생소하시고, 어떻게 돌아가는지 궁금하시면 아래 글을 참고하시기 바랍니다.
http://www.viper.pe.kr/docs/make-ko/make-ko_toc.html (한글)
http://sunsite.ualberta.ca/Documentation/Gnu/make-3.79/html_chapter/make_toc.html (영문)

 

참고:
OBJS 와 OBJS-yes 매크로가 어떻게 생성되는지 보겠습니다.

 

ffmpeg/libavcodec 폴더의 Makefile을 열어 봅니다.

OBJS = allcodecs.o                                                      \
       audioconvert.o                                                   \
       avpacket.o                                                       \
       bitstream.o                                                      \
       bitstream_filter.o                                               \
       dsputil.o                                                        \

위와 같은 소스를 볼 수 있습니다.
이는 다시 말하면 allcodecs.c, audioconvert.c ... 와 같은 소스들은 컴파일 옵션과 상관없이 무조건 컴파일 하겠다는 뜻입니다.

 

다음으로 아래와 같은 코드들이 이어집니다.

OBJS-$(CONFIG_AANDCT)                  += aandcttab.o
OBJS-$(CONFIG_AC3DSP)                  += ac3dsp.o
OBJS-$(CONFIG_CRYSTALHD)               += crystalhd.o


ffmpeg 폴더의 config.mak 파일을 열어서 CONFIG_AANDCT, CONFIG_CRYSTALHD 등을 찾아 보시면 이게 어떻게 돌아가는지 알 수 있습니다.
config.mak 파일을 열어 보면 아래와 같이 되어 있습니다.

CONFIG_AANDCT=yes
!CONFIG_CRYSTALHD=yes

즉, "OBJS-$(CONFIG_AANDCT)"는 "OBJS-yes"로 변환되어 aandcttab.c 는 컴파일할 것이고,
"OBJS-$(CONFIG_CRYSTALHD)"는 그렇지 않으니 crystalhd.c 는 컴파일 하지 않을 것 입니다.

 

이런 방법은 거의 모든 open source library에서 사용하고 있는 표준적인 방법이니 익숙해지는 것이 좋습니다.

 

* Makefile

 

ffmpeg 폴더 밑의 libavcodec, libavformat, libavutil, libswscale 네 개의 폴더만 컴파일 하면 됩니다.
각각 폴더에서 Makefile을 모두 열어줍니다. (총 4개)
"include" 또는 "-include" 로 시작하는 모든 문장을 주석처리 합니다.

 

보통 Makefile의 시작과 끝 부분에 아래와 같이 되어 있습니다.

include $(SUBDIR)../config.mak
include $(SUBDIR)../subdir.mak


이를 아래와 같이 "#"을 붙여서 주석처리 합니다.

#include $(SUBDIR)../config.mak
#include $(SUBDIR)../subdir.mak

 

libavcodec 폴더의 Makefile 에는 중간에 아래와 같이 문장도 있습니다.

-include $(SUBDIR)$(ARCH)/Makefile

잊지 말고 찾아서 "#"을 붙여서 주석처리 합니다.


다음으로 libavcodec 폴더의 Makefile을 둘로 나눠줍니다.

 #include $(SUBDIR)../config.mak

부터

 #-include $(SUBDIR)$(ARCH)/Makefile

까지는 "Makefile" 에 그대로 두고

 

 SKIPHEADERS                            += %_tablegen.h                  \

부터 파일 끝 까지는 잘라내어서 "Makefile2" 라는 이름으로 같은 폴더에 따로 저장합니다.


* Android.mk

 

g:/Root/FFmpegBasic/jni/ 폴더와 g:/Root/FFmpegBasic/jni/ffmpeg 폴더에
Android.mk 파일을 만들어 아래와 같이 한 줄만 작성해 줍니다.

 

 include $(call all-subdir-makefiles)

하위 폴더의 Android.mk들을 읽어서 컴파일하라는 뜻입니다.

 

g:/Root/FFmpegBasic/jni/ffmpeg/libavcodec 폴더의 Android.mk는 아래와 같이 작성합니다.

 

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

include $(LOCAL_PATH)/../config.mak

OBJS :=
OBJS-yes :=
include $(LOCAL_PATH)/Makefile
include $(LOCAL_PATH)/$(ARCH)/Makefile
include $(LOCAL_PATH)/Makefile2

include $(LOCAL_PATH)/../common.mk

LOCAL_MODULE := lib$(NAME)

LOCAL_C_INCLUDES := $(LOCAL_PATH)/..

LOCAL_CFLAGS := $(COMMON_CFLAGS)

NEON_ADD_C := aacdec.c
FFFILES := $(filter-out $(NEON_ADD_C),$(FFFILES))
NEON_ADD_C := $(NEON_ADD_C:.c=.c.neon)
LOCAL_SRC_FILES := $(FFFILES) $(NEON_ADD_C)

LOCAL_ARM_MODE := arm

LOCAL_STATIC_LIBRARIES := $(foreach,NAME,$(FFLIBS),lib$(NAME)) cpufeatures

include $(BUILD_STATIC_LIBRARY)

$(call import-module,android/cpufeatures)


g:/Root/FFmpegBasic/jni/ffmpeg/libavformat, libavutil, libswscale 세 개의 폴더에는 모두 아래와 같이 작성합니다.

 

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

include $(LOCAL_PATH)/../config.mak

OBJS :=
OBJS-yes :=
include $(LOCAL_PATH)/Makefile

include $(LOCAL_PATH)/../common.mk

LOCAL_MODULE := lib$(NAME)

LOCAL_C_INCLUDES := $(LOCAL_PATH)/..

LOCAL_CFLAGS := $(COMMON_CFLAGS)

LOCAL_SRC_FILES := $(FFFILES)

LOCAL_ARM_MODE := arm

LOCAL_STATIC_LIBRARIES := $(foreach,NAME,$(FFLIBS),lib$(NAME))

include $(BUILD_STATIC_LIBRARY)

 

참고:
이제까지 만든 common.mk와 수정한 Makefile을 적절한 위치에서 include 해줍니다.

LOCAL_CFLAGS 에는 우리가 아까 COMMON_CFLAGS 에 정의했던 컴파일 옵션이 들어갑니다.
LOCAL_SRC_FILES 에는 FFFILES 매크로에 정의했던 컴파일 할 소스 파일들이 들어갑니다.
cpufeatures library를 포함해서 컴파일 하는데 이것은 나중에 neon이 포함되지 않은 폰에서 에러 메시지를 출력할 때 사용 할 것 입니다.

보다 자세한 사항은 c:/android-ndk-r5b/docs/ANDROID-MK.html 파일을 참고하시기 바랍니다.

 

 

Application.mk, common.mk 파일, 그리고 3개의 Android.mk 파일을 첨부드리니 참고하시기 바랍니다.

 

Android_1.mk 는  g:/Root/FFmpegBasic/jni 와 g:/Root/FFmpegBasic/jni/ffmpeg

Android_2.mk 는 g:/Root/FFmpegBasic/jni/ffmpeg/libavcodec

Android_3.mk 는 g:/Root/FFmpegBasic/jni/ffmpeg/libavformat 와 libavutil 와 libswscale

폴더에 쓰이는 파일이니 각각 폴더에 복사하셔서 Android.mk 로 파일명을 수정하셔서 사용하시면 됩니다.

 

 

다음 강좌에서는 마지막으로 정말 컴파일을 해서 간단한 플레이어를 돌려보도록 하겠습니다.