1. 程式人生 > >Android Studio通過cmake建立FFmpeg專案

Android Studio通過cmake建立FFmpeg專案

        在上一篇部落格《FFmpeg(3.3.2)移植Android平臺》 中介紹了FFmpeg移植到Android平臺上來,通過部落格我們最終得到了Android開發FFmpeg所需要的動態庫(.so)和庫檔案(.h),部落格結尾也上傳了一個Android整合FFmpeg的demo,但是此demo是在eclipse下開發的,因為現在Android開發都是用的Android Studio了,所以就來講講在AS中怎麼搭建FFmpeg開發環境吧。

一:首先下載需要的元件CMake和LLDB,前者是構建工具,後者是除錯工具,注意這裡我沒有用SDK下載NDK,我喜歡單獨下載NDK,後面匯入。

二:建立支援C++的AS專案

新建工程,並勾選“C++ Support”,如圖:

後面預設直接下一步,直到建立好工程。

三:此時工程編譯出錯,因為還沒有匯入NDK編譯目錄(注:最好用最新的NDK來構建,我原來的是10e,導致cmake構建失敗,升級NDK就行了,下載地址:https://developer.android.com/ndk/downloads/index.html)我這裡用的是現在最新14b版本。

下載後,配置環境變數,併為工程匯入NDK編譯目錄:

Window配置:

ANDROID_NDK:E:\Android\android-ndk-r14b

Path:%ANDROID_NDK%

專案配置:

配置好後,再構建專案就可以了,下面是構建好的專案:

執行專案,效果如下就證明我們的C/C++專案建立成功了

四:AS預設C/C++專案介紹

1):c檔案所在目錄預設為cpp,並有一個簡單的返回字串的方法

2):我們看build.gradle裡面

其中第一個cmake裡面可以配置一些需要的引數,這裡暫時就預設,不用更改;第二個cmakd裡面匯入了要參與構建的CMakeLists.txt指令碼,這個腳本里面就是我們需要改寫的,如:新增自己的c檔案和系統庫以及第三方庫等。

3):CMakeLists.txt檔案簡析

這是根據功能推敲的,如有不對的,還請指正,哈哈。

4):生成的libxxxx.so位置

就在這裡了,不用移動動態庫位置,系統會自動呼叫這裡面的。

5):Java呼叫

好的,預設的專案基本介紹完了,應該對AS中的c編譯過程大概清楚了,接下來改造成我們自己的專案。

五:搭建FFmpeg目錄

1):刪除main下面的cpp目錄和檔案,建立jni目錄(個人喜歡,其實在cpp目錄裡面也可以)

現在看著是不是熟悉又清爽了。

2):匯入我們的FFmpeg庫和標頭檔案

因為只編譯了arm平臺下的FFmpeg,所以這裡就只建立armeabi了,這樣FFmpeg需要的庫和標頭檔案就匯入到了專案中。

3):在activity同級目錄下(這裡就不新建包了,原理一樣)新增我們自己的類,FFmpegPlayer.java

package com.ywl5320.ffmpegas;

/**
 * Created by ywl on 2017-7-14.
 */

public class FFmpegPlayer {

    static
    {
        System.loadLibrary("avutil-55");
        System.loadLibrary("swresample-2");
        System.loadLibrary("avcodec-57");
        System.loadLibrary("avformat-57");
        System.loadLibrary("swscale-4");
        System.loadLibrary("postproc-54");
        System.loadLibrary("avfilter-6");
        System.loadLibrary("avdevice-57");
    }
    public native void playMyMedia(String url);

}

首先是匯入FFmpeg的動態庫,然後自定義一個native方法來呼叫c程式碼。接下來生成xxx.c檔案,AS挺智慧的,把滑鼠移到我們的native方法上面,然後 “alt+回車” 就能在jni中生成包含我們native方法的.c檔案,檔名可以改一下,我這裡改成的是play.c

這樣就生成了我們需要的.c檔案,不需要我們在命令列用javah來生成.h再寫.c檔案了,原來紅色的native方法也變成

灰色的了:

4)接下來重點來了,配置CMakeLists.txt檔案

4.1:為我們自己的c檔案生成的動態庫命名,這裡就命名為wlffmpeg,把預設的改成如下:

add_library( # Sets the name of the library.
             wlffmpeg

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/jni/player.c )

4.2:新增FFmpeg動態庫:

#新增libavcodec-57.so
add_library( avcodec-57
             SHARED
             IMPORTED)
set_target_properties( avcodec-57
                       PROPERTIES IMPORTED_LOCATION
                       ${CMAKE_SOURCE_DIR}/src/main/jni/ffmpeg/armeabi/libavcodec-57.so)
                       
#新增libavdevice-57.so                  
add_library( avdevice-57
             SHARED
             IMPORTED)
set_target_properties( avdevice-57
                       PROPERTIES IMPORTED_LOCATION
                       ${CMAKE_SOURCE_DIR}/src/main/jni/ffmpeg/armeabi/libavdevice-57.so)

add_library( avfilter-6
             SHARED
             IMPORTED)
set_target_properties( avfilter-6
                       PROPERTIES IMPORTED_LOCATION
                       ${CMAKE_SOURCE_DIR}/src/main/jni/ffmpeg/armeabi/libavfilter-6.so)

add_library( avformat-57
             SHARED
             IMPORTED)
set_target_properties( avformat-57
                       PROPERTIES IMPORTED_LOCATION
                       ${CMAKE_SOURCE_DIR}/src/main/jni/ffmpeg/armeabi/libavformat-57.so)

add_library( avutil-55
             SHARED
             IMPORTED)
set_target_properties( avutil-55
                       PROPERTIES IMPORTED_LOCATION
                       ${CMAKE_SOURCE_DIR}/src/main/jni/ffmpeg/armeabi/libavutil-55.so)

add_library( swresample-2
             SHARED
             IMPORTED)
set_target_properties( swresample-2
                       PROPERTIES IMPORTED_LOCATION
                       ${CMAKE_SOURCE_DIR}/src/main/jni/ffmpeg/armeabi/libswresample-2.so)

add_library( swscale-4
             SHARED
             IMPORTED)
set_target_properties( swscale-4
                       PROPERTIES IMPORTED_LOCATION
                       ${CMAKE_SOURCE_DIR}/src/main/jni/ffmpeg/armeabi/libswscale-4.so)
                       
add_library( postproc-54
             SHARED
             IMPORTED)
set_target_properties( postproc-54
                       PROPERTIES IMPORTED_LOCATION
                       ${CMAKE_SOURCE_DIR}/src/main/jni/ffmpeg/armeabi/libpostproc-54.so)

簡單介紹一下如圖:

4.3:添加了FFmpeg動態庫,最後還需要連線到系統中:

在此之前還需要匯入FFmpeg的標頭檔案才行:

include_directories(src/main/jni/ffmpeg/include)

然後再連線FFmpeg動態庫

target_link_libraries( # Specifies the target library.

                       wlffmpeg
                       avcodec-57
                       avdevice-57
                       avfilter-6
                       avformat-57
                       avutil-55
                       swresample-2
                       swscale-4
                       postproc-54

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

4.4:現在還不能編譯,現在編譯會報“找不到mip64或其他cpu型別的錯誤”,因為我們這裡只有arm的值,此時需要在build.gradle中配置編譯的cup的型別,這裡就只配置arm的:

externalNativeBuild {
            cmake {
                cppFlags ""
            }
            ndk {
                abiFilters "armeabi"
            }
        }

4.5:此時也還不能編譯,會報“找不到FFmpeg的庫的錯誤”,因為我們還需要給系統指定jniLibs的目錄:

sourceSets {
            main {
                jniLibs.srcDirs = ['src/main/jni/ffmpeg']
            }
        }

4.6:至此我們就可以愉快的編譯程式碼了

完整的CMakeLists.txt

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
             wlffmpeg

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/jni/player.c )


#新增libavcodec-57.so
add_library( avcodec-57
             SHARED
             IMPORTED)
set_target_properties( avcodec-57
                       PROPERTIES IMPORTED_LOCATION
                       ${CMAKE_SOURCE_DIR}/src/main/jni/ffmpeg/armeabi/libavcodec-57.so)


#新增libavdevice-57.so
add_library( avdevice-57
             SHARED
             IMPORTED)
set_target_properties( avdevice-57
                       PROPERTIES IMPORTED_LOCATION
                       ${CMAKE_SOURCE_DIR}/src/main/jni/ffmpeg/armeabi/libavdevice-57.so)

add_library( avfilter-6
             SHARED
             IMPORTED)
set_target_properties( avfilter-6
                       PROPERTIES IMPORTED_LOCATION
                       ${CMAKE_SOURCE_DIR}/src/main/jni/ffmpeg/armeabi/libavfilter-6.so)

add_library( avformat-57
             SHARED
             IMPORTED)
set_target_properties( avformat-57
                       PROPERTIES IMPORTED_LOCATION
                       ${CMAKE_SOURCE_DIR}/src/main/jni/ffmpeg/armeabi/libavformat-57.so)

add_library( avutil-55
             SHARED
             IMPORTED)
set_target_properties( avutil-55
                       PROPERTIES IMPORTED_LOCATION
                       ${CMAKE_SOURCE_DIR}/src/main/jni/ffmpeg/armeabi/libavutil-55.so)

add_library( swresample-2
             SHARED
             IMPORTED)
set_target_properties( swresample-2
                       PROPERTIES IMPORTED_LOCATION
                       ${CMAKE_SOURCE_DIR}/src/main/jni/ffmpeg/armeabi/libswresample-2.so)

add_library( swscale-4
             SHARED
             IMPORTED)
set_target_properties( swscale-4
                       PROPERTIES IMPORTED_LOCATION
                       ${CMAKE_SOURCE_DIR}/src/main/jni/ffmpeg/armeabi/libswscale-4.so)

add_library( postproc-54
             SHARED
             IMPORTED)
set_target_properties( postproc-54
                       PROPERTIES IMPORTED_LOCATION
                       ${CMAKE_SOURCE_DIR}/src/main/jni/ffmpeg/armeabi/libpostproc-54.so)


# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

include_directories(src/main/jni/ffmpeg/include)

target_link_libraries( # Specifies the target library.

                       wlffmpeg
                       avcodec-57
                       avdevice-57
                       avfilter-6
                       avformat-57
                       avutil-55
                       swresample-2
                       swscale-4
                       postproc-54

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

完整的build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.3"
    defaultConfig {
        applicationId "com.ywl5320.ffmpegas"
        minSdkVersion 14
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
            ndk {
                abiFilters "armeabi"
            }
        }

        sourceSets {
            main {
                jniLibs.srcDirs = ['src/main/jni/ffmpeg']
            }
        }

    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.0-beta5'
    testCompile 'junit:junit:4.12'
}

編譯後我們執行一下apk吧:

哈哈,there is nothing !當然了,因為我們在play.c裡面還沒有寫什麼程式碼,也還沒有呼叫呢。

六:測試FFmpeg,呼叫裡面的簡單api,這裡也檢視其編碼器的資訊(本來想播放一段視訊的聲音或者畫面,但是還需要整合SDL,所以就下次吧)。

player.c

#include <jni.h>

//ffmpeg庫
#include "libavformat/avformat.h"

//列印日誌
#include <android/log.h>
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"ywl5320",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"ywl5320",FORMAT,##__VA_ARGS__);

JNIEXPORT void JNICALL
Java_com_ywl5320_ffmpegas_FFmpegPlayer_playMyMedia(JNIEnv *env, jobject instance, jstring url_) {
    const char *url = (*env)->GetStringUTFChars(env, url_, 0);

    LOGI("url:%s", url);

    av_register_all();
    AVCodec *c_temp = av_codec_next(NULL);
    while (c_temp != NULL)
    {
        switch (c_temp->type)
        {
            case AVMEDIA_TYPE_VIDEO:
                LOGI("[Video]:%s", c_temp->name);
                break;
            case AVMEDIA_TYPE_AUDIO:
                LOGI("[Audio]:%s", c_temp->name);
                break;
            default:
                LOGI("[Other]:%s", c_temp->name);
                break;
        }
        c_temp = c_temp->next;
    }
    // TODO

    (*env)->ReleaseStringUTFChars(env, url_, url);
}

呼叫程式碼:MainActivity.java

package com.ywl5320.ffmpegas;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    FFmpegPlayer fFmpegPlayer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        fFmpegPlayer = new FFmpegPlayer();
        fFmpegPlayer.playMyMedia("http://blog.csdn.net/ywl5320");
    }
}

別忘了在FFmpegPlayer.java中新增我們自己的動態庫(libwlffmpeg.so)哦:

package com.ywl5320.ffmpegas;

/**
 * Created by ywl on 2017-7-14.
 */

public class FFmpegPlayer {

    static
    {
        System.loadLibrary("avutil-55");
        System.loadLibrary("swresample-2");
        System.loadLibrary("avcodec-57");
        System.loadLibrary("avformat-57");
        System.loadLibrary("swscale-4");
        System.loadLibrary("postproc-54");
        System.loadLibrary("avfilter-6");
        System.loadLibrary("avdevice-57");
        System.loadLibrary("wlffmpeg");
    }
    public native void playMyMedia(String url);

}

其中libwlffmpeg.so的生成位置在這裡:

因為只是列印了FFmpeg的編解碼器資訊,所以在AS的log日誌中可以看到FFmpeg的資訊了:


看到了我們的資訊,說明FFmpeg已經在Android Studio上面跑起了,它能跑多快,就看各位大神的發揮了哈,搞定收工!!

且慢,還是總結一下:授人以魚不如授人以漁,通過本次對FFmpeg的整合,基本瞭解了AS中用CMake來構建c/c++專案的過程了,以後遇到其他的庫(c/c++優秀的庫太多了)就可以迎刃而解了。最後附上github下載地址,裡面有eclipse和AS兩個版本的,僅供大家參考,eclipse版本的沒有寫部落格,建立過程和部落格《Android編譯SDL2和demo展示(2.0.5)》中SDL一樣,相信大家沒問題的。