最簡單的基于FFmpeg的移動端例子:Android HelloWorld

=====================================================

最簡單的基于FFmpeg的移動端例子系列文章列表:

最簡單的基于FFmpeg的移動端例子:Android HelloWorld

最簡單的基于FFmpeg的移動端例子:Android 視頻解碼器

最簡單的基于FFmpeg的移動端例子:Android 視頻解碼器-單個庫版

最簡單的基于FFmpeg的移動端例子:Android 推流器

最簡單的基于FFmpeg的移動端例子:Android 視頻轉碼器

最簡單的基于FFmpeg的移動端例子附件:Android 自帶播放器

最簡單的基于FFmpeg的移動端例子附件:SDL Android HelloWorld

最簡單的基于FFmpeg的移動端例子:IOS HelloWorld

最簡單的基于FFmpeg的移動端例子:IOS 視頻解碼器

最簡單的基于FFmpeg的移動端例子:IOS 推流器

最簡單的基于FFmpeg的移動端例子:IOS 視頻轉碼器

最簡單的基于FFmpeg的移動端例子附件:IOS自帶播放器

最簡單的基于FFmpeg的移動端例子:Windows Phone HelloWorld

=====================================================

從本文開始打算記錄一系列FFmpeg在Android/IOS開發(fā)的示例程序彪标。前面幾篇文章記錄FFmpeg安卓端開發(fā)的例子,后面幾篇文章記錄FFmpeg IOS端開發(fā)的例子。這些例子中FFmpeg相關的代碼源自于《FFmpeg示例合集》中的程序杯聚。本文記錄第一個程序:安卓平臺下基于FFmpeg的HelloWorld程序。該程序的源代碼源自于《最簡單的基于FFMPEG的Helloworld程序》诲锹。

image
image.gif

?

Android程序FFmpeg類庫使用說明

Android應用程序使用FFmpeg類庫的流程圖如下所示驳规。

image
image.gif

?

上圖中的流程可以分為“編譯FFmpeg類庫”、“編寫Java端代碼”璃哟、“編寫C語言端代碼”三個步驟。

(1)編譯FFmpeg類庫

a) 下載安裝NDK

下載NDK之后直接解壓縮就可以使用了喊递。在Windows下使用的時候需要用到Cygwin随闪。在這里我自己使用Linux編譯類庫。

b) 修改FFmpeg的configure

下載FFmpeg源代碼之后骚勘,首先需要對源代碼中的configure文件進行修改铐伴。由于編譯出來的動態(tài)庫文件名的版本號在.so之后(例如“l(fā)ibavcodec.so.5.100.1”)撮奏,而android平臺不能識別這樣文件名,所以需要修改這種文件名当宴。在configure文件中找到下面幾行代碼:

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)'
image.gif

替換為下面內(nèi)容就可以了:

SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'
SLIB_INSTALL_LINKS='$(SLIBNAME)'
image.gif

c) 生成類庫

按照configure畜吊、make、make install的步驟就可以得到FFmpeg的頭文件和類庫文件了户矢。其中configure的配置腳本在網(wǎng)上比較多玲献。下面列舉幾個腳本。

FFmpeg類庫完整功能腳本
下面這個腳本可以生成一套功能完整逗嫡,體積比較大的類庫青自。

cd ffmpeg

make clean

export NDK=/home/leixiaohua1020/cdtworkspace/android-ndk-r9d
export PREBUILT=$NDK/toolchains/arm-linux-androideabi-4.8/prebuilt
export PLATFORM=$NDK/platforms/android-8/arch-arm
export PREFIX=../simplefflib
build_one(){
  ./configure --target-os=linux --prefix=$PREFIX \
--enable-cross-compile \
--enable-runtime-cpudetect \
--disable-asm \
--arch=arm \
--cc=$PREBUILT/linux-x86_64/bin/arm-linux-androideabi-gcc \
--cross-prefix=$PREBUILT/linux-x86_64/bin/arm-linux-androideabi- \
--disable-stripping \
--nm=$PREBUILT/linux-x86_64/bin/arm-linux-androideabi-nm \
--sysroot=$PLATFORM \
--enable-gpl --enable-shared --disable-static --enable-small \
--disable-ffprobe --disable-ffplay --disable-ffmpeg --disable-ffserver --disable-debug \
--extra-cflags="-fPIC -DANDROID -D__thumb__ -mthumb -Wfatal-errors -Wno-deprecated -mfloat-abi=softfp -marm -march=armv7-a" 
}

build_one

make
make install

cd ..

image.gif

該腳本中前面幾個變量“NDK”、“PREBUILT”驱证、“PLATFORM”根據(jù)NDK路徑的不同需要做相應的修改延窜。另外需要注意64位NDK和32位NDK中某些文件夾名稱也有一些區(qū)別:例如32位NDK中文件夾名稱為“l(fā)inux-x86”而64位NDK中文件夾名稱為“l(fā)inux-x86_64”。

將上述腳本拷貝至ffmpeg源代碼外面抹锄,成功執(zhí)行之后逆瑞,會將類庫和頭文件生成到腳本所在目錄下的“simplefflib”文件夾中。

FFmpeg類庫裁剪功能后包含libx264和libfaac支持腳本

下面這個腳本可以生成一個裁剪功能后伙单,包含libx264和libfaac支持的類庫获高。

export NDK=/home/leixiaohua1020/cdtworkspace/android-ndk-r9d
export PREBUILT=$NDK/toolchains/arm-linux-androideabi-4.6/prebuilt
export PLATFORM=$NDK/platforms/android-8/arch-arm
export PREFIX=../264fflib
build_one(){
  ./configure --target-os=linux --prefix=$PREFIX \
--enable-cross-compile \
--enable-runtime-cpudetect \
--disable-asm \
--arch=arm \
--cc=$PREBUILT/linux-x86_64/bin/arm-linux-androideabi-gcc \
--cross-prefix=$PREBUILT/linux-x86_64/bin/arm-linux-androideabi- \
--disable-stripping \
--nm=$PREBUILT/linux-x86_64/bin/arm-linux-androideabi-nm \
--sysroot=$PLATFORM \
--enable-gpl --enable-shared --disable-static --enable-nonfree --enable-version3 --enable-small --disable-vda --disable-iconv \
--disable-encoders --enable-libx264 --enable-libfaac --enable-encoder=libx264 --enable-encoder=libfaac \
--disable-muxers --enable-muxer=mov --enable-muxer=ipod --enable-muxer=psp --enable-muxer=mp4 --enable-muxer=avi \
--disable-decoders --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=h264 --enable-decoder=mpeg4 \
--disable-demuxers --enable-demuxer=h264 --enable-demuxer=avi --enable-demuxer=mpc --enable-demuxer=mov \
--disable-parsers --enable-parser=aac --enable-parser=ac3 --enable-parser=h264 \
--disable-protocols --enable-protocol=file \
--disable-bsfs --enable-bsf=aac_adtstoasc --enable-bsf=h264_mp4toannexb \
--disable-indevs --enable-zlib \
--disable-outdevs --disable-ffprobe --disable-ffplay --disable-ffmpeg --disable-ffserver --disable-debug \
--extra-cflags="-I ../android-lib/include -fPIC -DANDROID -D__thumb__ -mthumb -Wfatal-errors -Wno-deprecated -mfloat-abi=softfp -marm -march=armv7-a" \
--extra-ldflags="-L ../android-lib/lib"

}

build_one

make
make install

cd ..

image.gif

其中l(wèi)ibfaac和libx264需要單獨編譯生成。它們編譯過的類庫位于“android-lib”文件夾中吻育。libx264的編譯腳本如下所示念秧。

cd x264
export NDK=/home/leixiaohua1020/cdtworkspace/android-ndk-r9d
export PREBUILT=$NDK/toolchains/arm-linux-androideabi-4.6/prebuilt
export PLATFORM=$NDK/platforms/android-8/arch-arm
export PREFIX=../android-lib
./configure --prefix=$PREFIX \
--enable-static \
--enable-pic \
--disable-asm \
--disable-cli \
--host=arm-linux \
--cross-prefix=$PREBUILT/linux-x86_64/bin/arm-linux-androideabi- \
--sysroot=$PLATFORM
cd ..

image.gif

libfaac的編譯腳本如下所示。

cd faac
export NDK=/home/leixiaohua1020/cdtworkspace/android-ndk-r9d
export PREBUILT=$NDK/toolchains/arm-linux-androideabi-4.6/prebuilt
export PLATFORM=$NDK/platforms/android-9/arch-arm
CFLAGS="-fpic -DANDROID -fpic -mthumb-interwork -ffunction-sections -funwind-tables -fstack-protector -fno-short-enums -D__ARM_ARCH_7__ -Wno-psabi -march=armv7 -mtune=xscale -msoft-float -mthumb -Os -fomit-frame-pointer -fno-strict-aliasing -finline-limit=64 -DANDROID -Wa,--noexecstack -MMD -MP "
#CFLAGS="-fpic -DANDROID -fpic -mthumb-interwork -D__ARM_ARCH_7__ -Wno-psabi -march=armv7-a -mtune=xscale -msoft-float -mthumb -Os -fomit-frame-pointer -fno-strict-aliasing -finline-limit=64 -DANDROID -Wa, -MMD -MP "
CROSS_COMPILE=$PREBUILT/linux-x86_64/bin/arm-linux-androideabi-
export CPPFLAGS="$CFLAGS"
export CFLAGS="$CFLAGS"
export CXXFLAGS="$CFLAGS"
export CXX="${CROSS_COMPILE}g++ --sysroot=${PLATFORM}"
export LDFLAGS="$LDFLAGS"
export CC="${CROSS_COMPILE}gcc --sysroot=${PLATFORM}"
export NM="${CROSS_COMPILE}nm"
export STRIP="${CROSS_COMPILE}strip"
export RANLIB="${CROSS_COMPILE}ranlib"
export AR="${CROSS_COMPILE}ar"

./configure \
--without-mp4v2 \
--host=arm-linux \
--enable-static \

make
make install

cp -rf /usr/local/include/faac.h ../android-lib/include
cp -rf /usr/local/include/faaccfg.h ../android-lib/include
cp -rf /usr/local/lib/libfaac.a ../android-lib/lib

cd ..

image.gif

FFmpeg編譯后生成的類庫文件包含下面幾個:

libavformat-56.so
libavcodec-56.so
libavfilter-5.so
libavdevice-56.so
libavutil-54.so
libpostproc-53.so
libswresample-1.so
libswscale-3.so

(2)編寫Java端代碼

使用Android IDE(例如Eclipse ADT)創(chuàng)建一個空的Android項目布疼。也可以直接使用NDK中的hello-jni例子摊趾,該項目位于“{NDK目錄}/samples/hello-jni”中。后文將會逐步改造hello-jni游两,使它支持FFmpeg類庫的調(diào)用砾层。
修改Android項目中“src”文件夾下的Java源代碼,準備調(diào)用C語言函數(shù)贱案。使用JNI調(diào)用C語言代碼有兩點需要做的步驟:

  • 聲明C語言函數(shù)對應的Java函數(shù)
  • 聲明要加載的類庫

需要注意肛炮,C語言函數(shù)的聲明要加上“native”關鍵字;加載類庫的時候需要使用“System.loadLibrary()”方法宝踪。例如hello-jni例子中的Activity源代碼如下所示侨糟。

package com.example.hellojni;

import android.app.Activity;
import android.widget.TextView;
import android.os.Bundle;

public class HelloJni extends Activity
{
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        /* Create a TextView and set its content.
         * the text is retrieved by calling a native
         * function.
         */
        TextView  tv = new TextView(this);
        tv.setText( stringFromJNI() );
        setContentView(tv);
    }

    /* A native method that is implemented by the
     * 'hello-jni' native library, which is packaged
     * with this application.
     */
    public native String  stringFromJNI();

    /* This is another native method declaration that is *not*
     * implemented by 'hello-jni'. This is simply to show that
     * you can declare as many native methods in your Java code
     * as you want, their implementation is searched in the
     * currently loaded native libraries only the first time
     * you call them.
     *
     * Trying to call this function will result in a
     * java.lang.UnsatisfiedLinkError exception !
     */
    public native String  unimplementedStringFromJNI();

    /* this is used to load the 'hello-jni' library on application
     * startup. The library has already been unpacked into
     * /data/data/com.example.hellojni/lib/libhello-jni.so at
     * installation time by the package manager.
     */
    static {
        System.loadLibrary("hello-jni");
    }
}
image.gif

從源代碼可以看出,該Activity加載了名稱為“l(fā)ibhello-jni.so”的類庫(Java代碼中不包含前面的“l(fā)ib”和后面的“.so”)肴沫,并聲明了stringFromJNI()方法粟害。
在這里,為了調(diào)用FFmpeg而經(jīng)過修改后的Activity加載類庫部分的源代碼如下所示颤芬。

static {
     System.loadLibrary("avutil-54");
        System.loadLibrary("avcodec-56");
        System.loadLibrary("avformat-56");
        System.loadLibrary("avdevice-56");
        System.loadLibrary("swresample-1");
        System.loadLibrary("swscale-3");
        System.loadLibrary("postproc-53");
        System.loadLibrary("avfilter-5");
     System.loadLibrary("hello-jni");
    }
image.gif

(3)編寫C語言端代碼
a) 獲取C語言的接口函數(shù)聲明
根據(jù)Java對于C語言接口的定義,生成相應的接口函數(shù)聲明。這一步需要用到JDK中的“javah”命令站蝠。例如對于hello-jni例子汰具,首先切換到src文件夾下,輸入如下命令:
javah com.example.hellojni.HelloJni
就可以在當前目錄下生成一個頭文件“com_example_hellojni_HelloJni.h”菱魔,該頭文件內(nèi)容如下所示留荔。

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_hellojni_HelloJni */

#ifndef _Included_com_example_hellojni_HelloJni
#define _Included_com_example_hellojni_HelloJni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_hellojni_HelloJni
 * Method:    stringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI
  (JNIEnv *, jobject);

/*
 * Class:     com_example_hellojni_HelloJni
 * Method:    unimplementedStringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_unimplementedStringFromJNI
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
image.gif

從源代碼可以看出,JNI調(diào)用的C語言函數(shù)是有固定格式的澜倦,即:

*Java_{包名}{包名}…{類名}(JNIEnv ,…)

對于HelloJni類中的stringFromJNI方法聚蝶,其C語言版本的函數(shù)聲明為:

JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI (JNIEnv *, jobject)
image.gif

PS:這個頭文件只是一個參考,對于JNI來說并不是必須的藻治。也可以根據(jù)命名規(guī)則直接編寫C語言函數(shù)碘勉。

b) 編寫C語言接口函數(shù)代碼

在Android項目根目錄下新建“jni”文件夾,用于存儲C語言源代碼以及相關的開發(fā)資源桩卵。將編譯生成的FFmpeg的類庫(.so文件)和頭文件(.h文件)拷貝到這個目錄下验靡,然后新建一個C語言文件,就可以開始編寫相應的邏輯了雏节。此時jni文件夾目錄結構如下圖所示胜嗓。

image
image.gif

?

C語言文件用于實現(xiàn)上文中通過“javah”命令生成的頭文件的函數(shù)。對于hello-jni例子钩乍,其C語言文件內(nèi)容如下所示辞州。

#include <string.h>
#include <jni.h>

/* This is a trivial JNI example where we use a native method
 * to return a new VM String. See the corresponding Java source
 * file located at:
 *
 *   apps/samples/hello-jni/project/src/com/example/hellojni/HelloJni.java
 */
jstring
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
                                                  jobject thiz )
{
#if defined(__arm__)
  #if defined(__ARM_ARCH_7A__)
    #if defined(__ARM_NEON__)
      #define ABI "armeabi-v7a/NEON"
    #else
      #define ABI "armeabi-v7a"
    #endif
  #else
   #define ABI "armeabi"
  #endif
#elif defined(__i386__)
   #define ABI "x86"
#elif defined(__mips__)
   #define ABI "mips"
#else
   #define ABI "unknown"
#endif

    return (*env)->NewStringUTF(env, "Hello from JNI !  Compiled with ABI " ABI ".");
}
image.gif

可以看出,Java_com_example_hellojni_HelloJni_stringFromJNI()根據(jù)宏定義判定了系統(tǒng)類型并且返回了一個字符串寥粹。在這里要注意变过,C語言中的char[]是不能直接對應為Java中的String類型的(即jstring)。char[]轉換為String需要通過JNIEnv的NewStringUTF()函數(shù)排作。
為了調(diào)用FFmpeg而經(jīng)過修改后的Java_com_example_hellojni_HelloJni_stringFromJNI()的源代碼如下所示牵啦。

#include <string.h>
#include <jni.h>
#include "libavcodec/avcodec.h"

jstring
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
                                                  jobject thiz )
{
    char info[10000] = { 0 };
    sprintf(info, "%s\n", avcodec_configuration());
    return (*env)->NewStringUTF(env, info);
}
image.gif

可以看出該函數(shù)調(diào)用了libavcodec的avcodec_configuration()方法,用于獲取FFmpeg的配置信息妄痪。

c) 編寫Android.mk

完成C語言程序的編寫后哈雏,就可以開始編寫Android的makefile文件Android.mk了。hello-jni例子中的Android.mk內(nèi)容如下:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := hello-jni
LOCAL_SRC_FILES := hello-jni.c

include $(BUILD_SHARED_LIBRARY)

image.gif

編譯FFmpeg示例程序的時候由于用到了libavcodec等相關的庫衫生,所以將Android.mk文件修改如下:

LOCAL_PATH := $(call my-dir)

# FFmpeg library
include $(CLEAR_VARS)
LOCAL_MODULE := avcodec
LOCAL_SRC_FILES := libavcodec-56.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avdevice
LOCAL_SRC_FILES := libavdevice-56.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avfilter
LOCAL_SRC_FILES := libavfilter-5.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avformat
LOCAL_SRC_FILES := libavformat-56.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avutil
LOCAL_SRC_FILES := libavutil-54.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := postproc
LOCAL_SRC_FILES := libpostproc-53.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := swresample
LOCAL_SRC_FILES := libswresample-1.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := swscale
LOCAL_SRC_FILES := libswscale-3.so
include $(PREBUILT_SHARED_LIBRARY)

# Program
include $(CLEAR_VARS)
LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
LOCAL_LDLIBS := -llog -lz
LOCAL_SHARED_LIBRARIES := avcodec avdevice avfilter avformat avutil postproc swresample swscale
include $(BUILD_SHARED_LIBRARY)

image.gif

d) 編寫Application.mk(可選)

Application.mk中的APP_ABI設定了編譯后庫文件支持的指令集裳瘪,默認使用“armeabi”。在hello-jni例子中罪针,APP_ABI取值為“all”彭羹。由于我們編譯的FFmpeg并不在像x86這樣的平臺下運行,所以不需要“all”泪酱,把它修改為“armeabi”或者刪除就可以了(對于hello-jni這個例子派殷,不做這一步的話會在編譯x86平臺類庫的時候報錯还最,但并不影響后面的測試運行)。

e) 運行ndk-build

編寫完Android的Makefile文件之后毡惜,就可以運行ndk-build編譯生成可以通過JNI調(diào)用的類庫了拓轻。ndk-build本身是一個腳本,位于NDK根目錄下经伙。切換到Android程序目錄中扶叉,直接執(zhí)行該腳本就可以了。
ndk-build成功后帕膜,會在根目錄下的“l(fā)ibs/armeabi”目錄中生成相關的庫文件枣氧。hello-jni例子中,會生成以下庫文件:

libavformat-56.so
libavcodec-56.so
libavfilter-5.so
libavdevice-56.so
libavutil-54.so
libpostproc-53.so
libswresample-1.so
libswscale-3.so
libhello-jni.so

接下來就可以在Android手機或者虛擬機上對整個Android工程進行測試了垮刹。

f) 程序運行結果

程序最終的運行結果截圖如下所示达吞。

image
image.gif

?

從圖中可以看出,程序中打印出了FFmpeg的配置信息危纫。

FFmpeg Helloworld

本文記錄的FFmpeg Helloworld程序C語言的源代碼來自于《最簡單的基于FFMPEG的Helloworld程序》宗挥。改程序會輸出FFmpeg類庫下列信息:

Protocol: FFmpeg類庫支持的協(xié)議
AVFormat: FFmpeg類庫支持的封裝格式
AVCodec: FFmpeg類庫支持的編解碼器
AVFilter: FFmpeg類庫支持的濾鏡
Configure: FFmpeg類庫的配置信息

源代碼

項目的目錄結構如圖所示。Java源代碼位于src目錄种蝶,而C代碼位于jni目錄契耿。

image
image.gif

?

Android程序Java端代碼位于src\com\leixiaohua1020\sffmpegandroidhelloworld\MainActivity.java,如下所示螃征。

/**
 * 最簡單的基于FFmpeg的Helloworld例子-安卓
 * Simplest FFmpeg Android Helloworld
 * 
 * 雷霄驊 Lei Xiaohua
 * leixiaohua1020@126.com
 * 中國傳媒大學/數(shù)字電視技術
 * Communication University of China / Digital TV Technology
 * http://blog.csdn.net/leixiaohua1020
 * 
 * 
 * 本程序是移植FFmpeg到安卓平臺的最簡單程序搪桂。它可以打印出FFmpeg類庫的下列信息:
 *  Protocol:  FFmpeg類庫支持的協(xié)議
 *  AVFormat:  FFmpeg類庫支持的封裝格式
 *  AVCodec:   FFmpeg類庫支持的編解碼器
 *  AVFilter:  FFmpeg類庫支持的濾鏡
 *  Configure: FFmpeg類庫的配置信息
 * 
 * This is the simplest program based on FFmpeg in Android. It can show following 
 * informations about FFmpeg library:
 *  Protocol:  Protocols supported by FFmpeg.
 *  AVFormat:  Container format supported by FFmpeg.
 *  AVCodec:   Encoder/Decoder supported by FFmpeg.
 *  AVFilter:  Filters supported by FFmpeg.
 *  Configure: configure information of FFmpeg.
 * 
 */

package com.leixiaohua1020.sffmpegandroidhelloworld;

import android.os.Bundle;
import android.app.Activity;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final TextView libinfoText = (TextView) findViewById(R.id.text_libinfo);
        libinfoText.setMovementMethod(ScrollingMovementMethod.getInstance()); 

        libinfoText.setText(configurationinfo());

        Button configurationButton = (Button) this.findViewById(R.id.button_configuration);
        Button urlprotocolButton = (Button) this.findViewById(R.id.button_urlprotocol);
        Button avformatButton = (Button) this.findViewById(R.id.button_avformat);
        Button avcodecButton = (Button) this.findViewById(R.id.button_avcodec);
        Button avfilterButton = (Button) this.findViewById(R.id.button_avfilter);

        urlprotocolButton.setOnClickListener(new OnClickListener() {
            public void onClick(View arg0){
                libinfoText.setText(urlprotocolinfo());
            }
        });

        avformatButton.setOnClickListener(new OnClickListener() {
            public void onClick(View arg0){
                libinfoText.setText(avformatinfo());
            }
        });

        avcodecButton.setOnClickListener(new OnClickListener() {
            public void onClick(View arg0){
                libinfoText.setText(avcodecinfo());
            }
        });

        avfilterButton.setOnClickListener(new OnClickListener() {
            public void onClick(View arg0){
                libinfoText.setText(avfilterinfo());
            }
        });

        configurationButton.setOnClickListener(new OnClickListener() {
            public void onClick(View arg0){
                libinfoText.setText(configurationinfo());
            }
        });

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    //JNI
    public native String urlprotocolinfo();
    public native String avformatinfo();
    public native String avcodecinfo();
    public native String avfilterinfo();
    public native String configurationinfo();

    static{
        System.loadLibrary("avutil-54");
        System.loadLibrary("swresample-1");
        System.loadLibrary("avcodec-56");
        System.loadLibrary("avformat-56");
        System.loadLibrary("swscale-3");
        System.loadLibrary("postproc-53");
        System.loadLibrary("avfilter-5");
        System.loadLibrary("avdevice-56");
        System.loadLibrary("sffhelloworld");
    }

}
image.gif

C語言端源代碼位于jni/simplest_ffmpeg_helloworld.c,如下所示盯滚。

/**
 * 最簡單的基于FFmpeg的Helloworld例子-安卓
 * Simplest FFmpeg Android Helloworld
 *
 * 雷霄驊 Lei Xiaohua
 * leixiaohua1020@126.com
 * 中國傳媒大學/數(shù)字電視技術
 * Communication University of China / Digital TV Technology
 * http://blog.csdn.net/leixiaohua1020
 *
 *
 * 本程序是移植FFmpeg到安卓平臺的最簡單程序踢械。它可以打印出FFmpeg類庫的下列信息:
 * Protocol:  FFmpeg類庫支持的協(xié)議
 * AVFormat:  FFmpeg類庫支持的封裝格式
 * AVCodec:   FFmpeg類庫支持的編解碼器
 * AVFilter:  FFmpeg類庫支持的濾鏡
 * Configure: FFmpeg類庫的配置信息
 *
 * This is the simplest program based on FFmpeg in Android. It can show following
 * informations about FFmpeg library:
 * Protocol:  Protocols supported by FFmpeg.
 * AVFormat:  Container format supported by FFmpeg.
 * AVCodec:   Encoder/Decoder supported by FFmpeg.
 * AVFilter:  Filters supported by FFmpeg.
 * Configure: configure information of FFmpeg.
 */

#include <stdio.h>

#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavfilter/avfilter.h"

//Log
#ifdef ANDROID
#include <jni.h>
#include <android/log.h>
#define LOGE(format, ...)  __android_log_print(ANDROID_LOG_ERROR, "(>_<)", format, ##__VA_ARGS__)
#else
#define LOGE(format, ...)  printf("(>_<) " format "\n", ##__VA_ARGS__)
#endif

//FIX
struct URLProtocol;
/**
 * com.leixiaohua1020.sffmpegandroidhelloworld.MainActivity.urlprotocolinfo()
 * Protocol Support Information
 */
JNIEXPORT jstring Java_com_leixiaohua1020_sffmpegandroidhelloworld_MainActivity_urlprotocolinfo(JNIEnv *env, jobject obj){

    char info[40000]={0};
    av_register_all();

    struct URLProtocol *pup = NULL;
    //Input
    struct URLProtocol **p_temp = &pup;
    avio_enum_protocols((void **)p_temp, 0);
    while ((*p_temp) != NULL){
        sprintf(info, "%s[In ][%10s]\n", info, avio_enum_protocols((void **)p_temp, 0));
    }
    pup = NULL;
    //Output
    avio_enum_protocols((void **)p_temp, 1);
    while ((*p_temp) != NULL){
        sprintf(info, "%s[Out][%10s]\n", info, avio_enum_protocols((void **)p_temp, 1));
    }

    //LOGE("%s", info);
    return (*env)->NewStringUTF(env, info);
}

/**
 * com.leixiaohua1020.sffmpegandroidhelloworld.MainActivity.avformatinfo()
 * AVFormat Support Information
 */
JNIEXPORT jstring Java_com_leixiaohua1020_sffmpegandroidhelloworld_MainActivity_avformatinfo(JNIEnv *env, jobject obj){

    char info[40000] = { 0 };

    av_register_all();

    AVInputFormat *if_temp = av_iformat_next(NULL);
    AVOutputFormat *of_temp = av_oformat_next(NULL);
    //Input
    while(if_temp!=NULL){
        sprintf(info, "%s[In ][%10s]\n", info, if_temp->name);
        if_temp=if_temp->next;
    }
    //Output
    while (of_temp != NULL){
        sprintf(info, "%s[Out][%10s]\n", info, of_temp->name);
        of_temp = of_temp->next;
    }
    //LOGE("%s", info);
    return (*env)->NewStringUTF(env, info);
}

/**
 * com.leixiaohua1020.sffmpegandroidhelloworld.MainActivity.avcodecinfo()
 * AVCodec Support Information
 */
JNIEXPORT jstring Java_com_leixiaohua1020_sffmpegandroidhelloworld_MainActivity_avcodecinfo(JNIEnv *env, jobject obj)
{
    char info[40000] = { 0 };

    av_register_all();

    AVCodec *c_temp = av_codec_next(NULL);

    while(c_temp!=NULL){
        if (c_temp->decode!=NULL){
            sprintf(info, "%s[Dec]", info);
        }
        else{
            sprintf(info, "%s[Enc]", info);
        }
        switch (c_temp->type){
        case AVMEDIA_TYPE_VIDEO:
            sprintf(info, "%s[Video]", info);
            break;
        case AVMEDIA_TYPE_AUDIO:
            sprintf(info, "%s[Audio]", info);
            break;
        default:
            sprintf(info, "%s[Other]", info);
            break;
        }
        sprintf(info, "%s[%10s]\n", info, c_temp->name);

        c_temp=c_temp->next;
    }
    //LOGE("%s", info);

    return (*env)->NewStringUTF(env, info);
}

/**
 * com.leixiaohua1020.sffmpegandroidhelloworld.MainActivity.avfilterinfo()
 * AVFilter Support Information
 */
JNIEXPORT jstring Java_com_leixiaohua1020_sffmpegandroidhelloworld_MainActivity_avfilterinfo(JNIEnv *env, jobject obj)
{
    char info[40000] = { 0 };
    avfilter_register_all();
    AVFilter *f_temp = (AVFilter *)avfilter_next(NULL);
    while (f_temp != NULL){
        sprintf(info, "%s[%10s]\n", info, f_temp->name);
    }
    //LOGE("%s", info);

    return (*env)->NewStringUTF(env, info);
}

/**
 * com.leixiaohua1020.sffmpegandroidhelloworld.MainActivity.urlprotocolinfo()
 * Protocol Support Information
 */
JNIEXPORT jstring Java_com_leixiaohua1020_sffmpegandroidhelloworld_MainActivity_configurationinfo(JNIEnv *env, jobject obj)
{
    char info[10000] = { 0 };
    av_register_all();

    sprintf(info, "%s\n", avcodec_configuration());

    //LOGE("%s", info);
    return (*env)->NewStringUTF(env, info);
}
image.gif

Android.mk文件位于jni/Android.mk,如下所示魄藕。

# Android.mk for FFmpeg
#
# Lei Xiaohua 雷霄驊
# leixiaohua1020@126.com
# http://blog.csdn.net/leixiaohua1020
# 

LOCAL_PATH := $(call my-dir)

# FFmpeg library
include $(CLEAR_VARS)
LOCAL_MODULE := avcodec
LOCAL_SRC_FILES := libavcodec-56.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avdevice
LOCAL_SRC_FILES := libavdevice-56.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avfilter
LOCAL_SRC_FILES := libavfilter-5.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avformat
LOCAL_SRC_FILES := libavformat-56.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avutil
LOCAL_SRC_FILES := libavutil-54.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := postproc
LOCAL_SRC_FILES := libpostproc-53.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := swresample
LOCAL_SRC_FILES := libswresample-1.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := swscale
LOCAL_SRC_FILES := libswscale-3.so
include $(PREBUILT_SHARED_LIBRARY)

# Program
include $(CLEAR_VARS)
LOCAL_MODULE := sffhelloworld
LOCAL_SRC_FILES :=simplest_ffmpeg_helloworld.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
LOCAL_LDLIBS := -llog -lz
LOCAL_SHARED_LIBRARIES := avcodec avdevice avfilter avformat avutil postproc swresample swscale
include $(BUILD_SHARED_LIBRARY)

image.gif

其它部分源代碼暫不詳細例舉内列。

運行結果

App在手機上運行后的結果如下圖所示。

image
image.gif

?

單擊不同的按鈕背率,可以查看FFmpeg類庫相關的信息话瞧。

image
image.gif

?

下載

**simplest ffmpeg mobile

項目主頁**

Github:https://github.com/leixiaohua1020/simplest_ffmpeg_mobile

開源中國:https://git.oschina.net/leixiaohua1020/simplest_ffmpeg_mobile

SourceForge:https://sourceforge.net/projects/simplestffmpegmobile/

CSDN工程下載地址:http://download.csdn.net/detail/leixiaohua1020/8924391

本解決方案包含了使用FFmpeg在移動端處理多媒體的各種例子:

[Android]
simplest_android_player: 基于安卓接口的視頻播放器
simplest_ffmpeg_android_helloworld: 安卓平臺下基于FFmpeg的HelloWorld程序
simplest_ffmpeg_android_decoder: 安卓平臺下最簡單的基于FFmpeg的視頻解碼器
simplest_ffmpeg_android_decoder_onelib: 安卓平臺下最簡單的基于FFmpeg的視頻解碼器-單庫版
simplest_ffmpeg_android_streamer: 安卓平臺下最簡單的基于FFmpeg的推流器
simplest_ffmpeg_android_transcoder: 安卓平臺下移植的FFmpeg命令行工具
simplest_sdl_android_helloworld: 移植SDL到安卓平臺的最簡單程序
[IOS]
simplest_ios_player: 基于IOS接口的視頻播放器
simplest_ffmpeg_ios_helloworld: IOS平臺下基于FFmpeg的HelloWorld程序
simplest_ffmpeg_ios_decoder: IOS平臺下最簡單的基于FFmpeg的視頻解碼器
simplest_ffmpeg_ios_streamer: IOS平臺下最簡單的基于FFmpeg的推流器
simplest_ffmpeg_ios_transcoder: IOS平臺下移植的ffmpeg.c命令行工具

simplest_sdl_ios_helloworld: 移植SDL到IOS平臺的最簡單程序

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市寝姿,隨后出現(xiàn)的幾起案子交排,更是在濱河造成了極大的恐慌,老刑警劉巖饵筑,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件埃篓,死亡現(xiàn)場離奇詭異,居然都是意外死亡根资,警方通過查閱死者的電腦和手機架专,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門同窘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人胶征,你說我怎么就攤上這事塞椎〗胺拢” “怎么了睛低?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長服傍。 經(jīng)常有香客問我钱雷,道長,這世上最難降的妖魔是什么吹零? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任罩抗,我火速辦了婚禮,結果婚禮上灿椅,老公的妹妹穿的比我還像新娘套蒂。我一直安慰自己,他們只是感情好茫蛹,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布操刀。 她就那樣靜靜地躺著,像睡著了一般婴洼。 火紅的嫁衣襯著肌膚如雪骨坑。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天柬采,我揣著相機與錄音欢唾,去河邊找鬼。 笑死粉捻,一個胖子當著我的面吹牛礁遣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肩刃,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼祟霍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了树酪?” 一聲冷哼從身側響起浅碾,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎续语,沒想到半個月后垂谢,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡疮茄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年滥朱,在試婚紗的時候發(fā)現(xiàn)自己被綠了根暑。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡徙邻,死狀恐怖排嫌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缰犁,我是刑警寧澤淳地,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站帅容,受9級特大地震影響颇象,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜并徘,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一遣钳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧麦乞,春花似錦蕴茴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至简肴,卻和暖如春晃听,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背砰识。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工能扒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人辫狼。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓初斑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親膨处。 傳聞我的和親對象是個殘疾皇子见秤,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容