Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發實例 >> 在Ubuntu中用Android NDK編譯FFmpeg

在Ubuntu中用Android NDK編譯FFmpeg

編輯:Android開發實例

最近在做 Android 上的項目,我被惡心的一塌糊塗。本以為 Java 是 Android 上的一等公民,結果深入學習之後才發現,Java 在 Android 上 就是個做 UI 的,除此之外無論想干什都得用 C 語言去實現。Android 一個非常糟糕差勁的操作系統,甚至連 Windows Mobile 都不如。Android 能取得今天的市場占有率只是因為當年微軟的 Window Phone 7 還在開發中,而 iOS 又只給 iPhone用,所以手機生產商沒得選,只能被迫采用 Android 這個連 Linux 內核開發團隊都不承認的 Linux 操作系統。而基於 Linux 內核就是 Android 唯一的優點了,正是因為如此我們才想辦法能把那些 Linux 上的偉大開源項目移植到 Android 上以彌補 Android 的不足。

Android 的多媒體功能是如此之弱,限制是如此之多,逼著我只能想辦法去把 FFmpeg 移植到 Android 上。 感謝 havlenapetr 給出的示例代碼,感謝 ABitNo 整理的說明文檔,沒有他們的貢獻,我不可能把 FFmpeg 成功移植到 Android 上。下面我將說明將 FFmpeg 移植到 Android 上的詳細步驟,希望能對正在進行同樣工作的朋友有所幫助。

一、下載必要軟件

Oracle VM VirtualBox 3.2.12

Ubuntu Desktop Edition 10.10 32-bit

Android NDK r4b(需要翻牆訪問)

Android NDK r5(需要翻牆訪問)

FFmpeg 0.6.1

二、配置編譯環境

  1. 在 VirtualBox 中創建一個 Ubuntu 虛擬機
  2. 在 Ubuntu 虛擬機中使用 sudo passwd root 命令啟動 root 賬戶
  3. 用 root 賬戶登錄進入 Ubuntu
  4. 將 android-ndk-r4b-linux-x86.zip 中的內容解壓縮到 /root 目錄下
  5. 將 android-sdk_r07-linux_x86.tgz 中的內容解壓縮到 /root 目錄下
  6. 將 ffmpeg-0.6.1.tar.bz2 中的內容解壓縮到 /root/ffmpeg/jni 目錄下

三、准備編譯 FFmpeg

  1. 編寫 mk 文件
    1. 在 /root/ffmpeg/jni 目錄中創建一個 Android.mk 文件,內容如下
      include $(all-subdir-makefiles)
    2. 在 /root/ffmpeg/jni/ffmpeg-0.6.1 目錄中創建一個 Android.mk 文件,內容如下
      LOCAL_PATH := $(call my-dir)
      include $(CLEAR_VARS)
      LOCAL_WHOLE_STATIC_LIBRARIES := libavformat libavcodec libavutil libpostproc libswscale
      LOCAL_MODULE := ffmpeg
      include $(BUILD_SHARED_LIBRARY)
      include $(call all-makefiles-under,$(LOCAL_PATH))
    3. 在 /root/ffmpeg/jni/ffmpeg-0.6.1 目錄中創建一個 av.mk 文件,內容如下
      # LOCAL_PATH is one of libavutil, libavcodec, libavformat, or libswscale

      #include $(LOCAL_PATH)/../config-$(TARGET_ARCH).mak
      include $(LOCAL_PATH)/../config.mak

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

      # collect objects
      OBJS-$(HAVE_MMX) += $(MMX-OBJS-yes)
      OBJS += $(OBJS-yes)

      FFNAME := lib$(NAME)
      FFLIBS := $(foreach,NAME,$(FFLIBS),lib$(NAME))
      FFCFLAGS = -DHAVE_AV_CONFIG_H -Wno-sign-compare -Wno-switch -Wno-pointer-sign
      FFCFLAGS += -DTARGET_CONFIG=\"config-$(TARGET_ARCH).h\"

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

      ifneq ($(ALL_S_FILES),)
      ALL_S_OBJS := $(patsubst %.S,%.o,$(ALL_S_FILES))
      C_OBJS := $(filter-out $(ALL_S_OBJS),$(OBJS))
      S_OBJS := $(filter $(ALL_S_OBJS),$(OBJS))
      else
      C_OBJS := $(OBJS)
      S_OBJS :=
      endif

      C_FILES := $(patsubst %.o,%.c,$(C_OBJS))
      S_FILES := $(patsubst %.o,%.S,$(S_OBJS))

      FFFILES := $(sort $(S_FILES)) $(sort $(C_FILES))
    4. 在 /root/ffmpeg/jni/ffmpeg-0.6.1/libavcodec 目錄中創建一個 Android.mk 文件,內容如下
      LOCAL_PATH := $(call my-dir)
      include $(CLEAR_VARS)
      include $(LOCAL_PATH)/../av.mk
      LOCAL_SRC_FILES := $(FFFILES)
      LOCAL_C_INCLUDES := \
      $(LOCAL_PATH) \
      $(LOCAL_PATH)/..
      LOCAL_CFLAGS += $(FFCFLAGS)
      LOCAL_LDLIBS := -lz
      LOCAL_STATIC_LIBRARIES := $(FFLIBS)
      LOCAL_MODULE := $(FFNAME)
      include $(BUILD_STATIC_LIBRARY)
    5. 在 /root/ffmpeg/jni/ffmpeg-0.6.1/libavformat 目錄中創建一個 Android.mk 文件,內容如下
      LOCAL_PATH := $(call my-dir)
      include $(CLEAR_VARS)
      include $(LOCAL_PATH)/../av.mk
      LOCAL_SRC_FILES := $(FFFILES)
      LOCAL_C_INCLUDES := \
      $(LOCAL_PATH) \
      $(LOCAL_PATH)/..
      LOCAL_CFLAGS += $(FFCFLAGS)
      LOCAL_CFLAGS += -include "string.h" -Dipv6mr_interface=ipv6mr_ifindex
      LOCAL_LDLIBS := -lz
      LOCAL_STATIC_LIBRARIES := $(FFLIBS)
      LOCAL_MODULE := $(FFNAME)
      include $(BUILD_STATIC_LIBRARY)
    6. 在 libavfilter、libavutil、libpostproc 和 libswscale 目錄中各創建一個 Android.mk 文件,內容如下
      LOCAL_PATH := $(call my-dir)
      include $(CLEAR_VARS)
      include $(LOCAL_PATH)/../av.mk
      LOCAL_SRC_FILES := $(FFFILES)
      LOCAL_C_INCLUDES := \
      $(LOCAL_PATH) \
      $(LOCAL_PATH)/..
      LOCAL_CFLAGS += $(FFCFLAGS)
      LOCAL_STATIC_LIBRARIES := $(FFLIBS)
      LOCAL_MODULE := $(FFNAME)
      include $(BUILD_STATIC_LIBRARY)
  2. 修改 libm.h 文件和 Makefile 文件
    1. 編輯 /root/ffmpeg/jni/ffmpeg-0.6.1/libavutil 目錄中的 libm.h 文件,刪除以下 static 方法
      #if !HAVE_LRINT
      static av_always_inline av_const long int lrint(double x)
      {
      return rint(x);
      }
      #endif /* HAVE_LRINT */

      #if !HAVE_LRINTF
      static av_always_inline av_const long int lrintf(float x)
      {
      return (int)(rint(x));
      }
      #endif /* HAVE_LRINTF */

      #if !HAVE_ROUND
      static av_always_inline av_const double round(double x)
      {
      return (x > 0) ? floor(x + 0.5) : ceil(x - 0.5);
      }
      #endif /* HAVE_ROUND */

      #if !HAVE_ROUNDF
      static av_always_inline av_const float roundf(float x)
      {
      return (x > 0) ? floor(x + 0.5) : ceil(x - 0.5);
      }
      #endif /* HAVE_ROUNDF */

      #if !HAVE_TRUNCF
      static av_always_inline av_const float truncf(float x)
      {
      return (x > 0) ? floor(x) : ceil(x);
      }
      #endif /* HAVE_TRUNCF */
    2. 編輯 libavcodec、libavfilter、libavformat、libavutil、libpostproc 和 libswscale 目錄中的 Makefile 文件,刪除
      include $(SUBDIR)../subdir.mak
      include $(SUBDIR)../config.mak
  3. 生成 config.h 文件
    1. 在 /root/ffmpeg/jni/ffmpeg-0.6.1 目錄中創建一個 config.sh 文件,使用 Android NDK r4b 編譯時內容如下
      PREBUILT=/root/android-ndk-r4b/build/prebuilt/linux-x86/arm-eabi-4.4.0
      PLATFORM=/root/android-ndk-r4b/build/platforms/android-8/arch-arm

      ./configure --target-os=linux \
      --arch=arm \
      --enable-version3 \
      --enable-gpl \
      --enable-nonfree \
      --disable-stripping \
      --disable-ffmpeg \
      --disable-ffplay \
      --disable-ffserver \
      --disable-ffprobe \
      --disable-encoders \
      --disable-muxers \
      --disable-devices \
      --disable-protocols \
      --enable-protocol=file \
      --enable-avfilter \
      --disable-network \
      --disable-mpegaudio-hp \
      --disable-avdevice \
      --enable-cross-compile \
      --cc=$PREBUILT/bin/arm-eabi-gcc \
      --cross-prefix=$PREBUILT/bin/arm-eabi- \
      --nm=$PREBUILT/bin/arm-eabi-nm \
      --extra-cflags="-fPIC -DANDROID" \
      --disable-asm \
      --enable-neon \
      --enable-armv5te \
      --extra-ldflags="-Wl,-T,$PREBUILT/arm-eabi/lib/ldscripts/armelf.x -Wl,-rpath-link=$PLATFORM/usr/lib -L$PLATFORM/usr/lib -nostdlib $PREBUILT/lib/gcc/arm-eabi/4.4.0/crtbegin.o $PREBUILT/lib/gcc/arm-eabi/4.4.0/crtend.o -lc -lm -ldl"
      使用 Android NDK r5 編譯時內容如下
      #!/bin/bash

      PREBUILT=/root/android-ndk-r5/toolchains/arm-eabi-4.4.0/prebuilt/linux-x86
      PLATFORM=/root/android-ndk-r5/platforms/android-8/arch-arm

      ./configure --target-os=linux \
      --arch=arm \
      --enable-version3 \
      --enable-gpl \
      --enable-nonfree \
      --disable-stripping \
      --disable-ffmpeg \
      --disable-ffplay \
      --disable-ffserver \
      --disable-ffprobe \
      --disable-encoders \
      --disable-muxers \
      --disable-devices \
      --disable-protocols \
      --enable-protocol=file \
      --enable-avfilter \
      --disable-network \
      --disable-mpegaudio-hp \
      --disable-avdevice \
      --enable-cross-compile \
      --cc=$PREBUILT/bin/arm-eabi-gcc \
      --cross-prefix=$PREBUILT/bin/arm-eabi- \
      --nm=$PREBUILT/bin/arm-eabi-nm \
      --extra-cflags="-fPIC -DANDROID" \
      --disable-asm \
      --enable-neon \
      --enable-armv5te \
      --extra-ldflags="-Wl,-T,$PREBUILT/arm-eabi/lib/ldscripts/armelf.x -Wl,-rpath-link=$PLATFORM/usr/lib -L$PLATFORM/usr/lib -nostdlib $PREBUILT/lib/gcc/arm-eabi/4.4.0/crtbegin.o $PREBUILT/lib/gcc/arm-eabi/4.4.0/crtend.o -lc -lm -ldl"
  1.  
    1. 打開終端,進入 /root/ffmpeg/jni/ffmpeg-0.6.1 目錄,運行下面的命令
      chmod +x config.sh
      ./config.sh
    2. 編輯 /root/ffmpeg/jni/ffmpeg-0.6.1 目錄中的 config.h 文件,將
      #define restrict restrict
      修改為
      #define restrict

四、開始編譯 FFmpeg

很多人在用 havlenapetr 的方法編譯 FFmpeg 時只得到一個 1599 字節 1.6KB 大小的 libffmpeg.so 文件,無論是用 Android NDK r4b 編譯還是用 Android NDK r5 編譯結果都是如此,很讓人抓狂。我也很郁悶,最後花時間研究了一下 NDK,終於發現了解決方法,而且 Android NDK r4b 和 Android NDK r5  的情況還是完全不同的,請繼續往下讀。

  1. 使用 Android NDK r4b 編譯

    打開 android-ndk-r4b/build/toolchains/arm-eabi-4.4.0 目錄中的 setup.mk 文件,你會發現 Google 在裡面定義了一個用於編譯動態庫的 cmd-build-shared-library 函數。在cmd-build-shared-library 函數中 Google 使用了 PRIVATE_WHOLE_STATIC_LIBRARIES 函數。但是你在 android-ndk-r4b/build/core 目錄中的 build-binary.mk 文件裡卻找不到 PRIVATE_WHOLE_STATIC_LIBRARIES 函數…… 外?WHY?終於搞清楚了,原來得不到正確的 libffmpeg.so 文件不是我的錯,而是 Android NDK r4b 的 BUG!你妹啊!你大爺啊!坑爹呢這是!發布前不做測試嗎!居然漏掉一個函數!!!(我敢說這是個 BUG 是因為 Google 在 Android NDK r5 中修復了這個 BUG)

    木辦法,只好手動替 Google 修補這個 BUG。好在修改方法很簡單,只需要照 build-binary.mk 文件裡的 PRIVATE_STATIC_LIBRARIES 增加一個 PRIVATE_WHOLE_STATIC_LIBRARIES 就行了。具體方法見下圖

    修改前的 build-binary.mk 文件

    修改後的 build-binary.mk 文件

    保存 build-binary.mk 文件之後,運行下面的命令編譯

    /root/android-ndk-r4b/ndk-build NDK_PROJECT_PATH=/root/ffmpeg

    接著你會看到 warning 不停的出現在屏幕上,熬過這段心驚肉跳的時間之後,你會看到 libffmpeg.so 文件已經被編譯生成了。


    看看 /root/ffmpeg/obj/local/armeabi 目錄中的 libffmpeg.so 文件,文件大小是 12.2MB 

    再看看 /root/ffmpeg/libs/local/armeabi 目錄中的 libffmpeg.so 文件,文件大小是 3.2MB

  2. 使用 Android NDK r5 編譯

    打開 android-ndk-r5/build/core 目錄中的 build-binary.mk 文件,發現 Google 這次沒有忘記 PRIVATE_WHOLE_STATIC_LIBRARIES,但還最後編譯得到的 libffmpeg.so 文件大小還是不正確。 這次的問題是,android-ndk-r5 默認是使用 arm-linux-androideabi-4.4.3 編譯,而不是 arm-eabi-4.4.0。但 android-ndk-r5/toolchains/arm-linux-androideabi-4.4.3 目錄中的 setup.mk 文件裡定義的  cmd-build-shared-library 函數並沒有將靜態庫文件鏈接在一起生成動態庫文件。所以解決的辦法就是在執行 ndk-build 時加上 NDK_TOOLCHAIN 參數,指定使用 arm-eabi-4.4.0 來編譯。完整命令如下

    /root/android-ndk-r5/ndk-build NDK_PROJECT_PATH=/root/ffmpeg NDK_TOOLCHAIN=arm-eabi-4.4.0 NDK_PLATFORM=android-8

五、結語

 

我的最終目的是實現 Android 平台下對 Apple Http Live Streaming 的支持,但現在我已經被 Android 折磨的死去活來了。不知道在實現最終目標之前,我還要被 Android 惡心多少次……

Android 讓這個世界變得更混亂了,Windows Phone 7 才是救世主。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved