안녕하세요 ^^
세번째 강좌로 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 로 파일명을 수정하셔서 사용하시면 됩니다.
다음 강좌에서는 마지막으로 정말 컴파일을 해서 간단한 플레이어를 돌려보도록 하겠습니다.
테스트해보진 않았으나 가능한 해결책(?) 인것 같습니다.
AAC 디코딩 문제가 neon instruction 관련해서 발생하는건 확실합니다.
결국 AAC 디코딩 할 때, neon을 안쓰면 해결되는 문제인데
저 부분을 주석 처리하는 것도 결국 이런 효과를 내는 것으로 보입니다.
달아주신 코드를 조금 따라가보면
fmtconvert_init_arm.c 의
if (HAVE_NEON) { 어쩌고... }
위와 같은 부분을 주석처리 해버리는 것도 좋은 방법일 것 같네요.
neon도 쓰고 디코딩도 잘 되면 최선이겠으나
현재로서는 간단한 해결책은 보이지 않네요...
좋은 피드백 감사합니다.
궁금한게 있습니다 강좌 4의 소스들을 만들지 않고 현재 3강좌 까지만 작업하고 jni 폴더에서 ndk-build 를 하면
ffmpeg 가 빌드가 되나요 ? 그후 라이브러리를 옮겨서 강좌4 와 링킹 해주어야 하는건가요 ?
제가 계속 interface.c:5: undefined reference to `av_register_all' 이런 메시지가 뜨는걸 봐선 ffmpeg 빌드도 안되고
링킹도 제데로 안되는것 같은데 머가 문제인지를 모르겠습니다 ㅠ
궁금한게 있습니다!
libavcodec 폴더의 Makefile 에는 중간에 -include $(SUBDIR)$(ARCH)/Makefile 이러한 문장이 있다고 하셨는데
제 제 소스코드에는 그게 안보여요 ㅠㅠ 왜이러는 거죠?? Makefile2를 못만들어서 나중에 ndk-build 할때 에러나요 ㅠㅠ
뒤늦게 오류 발견하였습니다.
libavcodec의 Android.mk 파일에서 NEON_ADD_C := aacdec.c 이하 4 줄이 문제가 있는 것으로 추정됩니다.
audio가 AAC 로 인코딩 된 파일에서 문제가 발생합니다.
(audio mpeg 등 다른 코덱인 경우에는 문제가 없음)
현재 해결안 찾고 있는 중입니다....