在Mac上編譯移植FFmpeg-4.0.3到Android平臺

以前有作了下FFmpeg-4.0.3的移植,如今記錄一下FFmpeg-4.0.3版本的編譯移植過程,雖然網上有不少這方面的資料,可是都是針對比較舊的版本了,在編譯過程當中會踩到不少坑,這裏我只把FFmpeg-4.0.3這個版本編譯成功的步驟記錄下來,中間的坑就不寫了。html

準備

FFmpeg下載(這裏我下載的版本4.0.3,如今好像到4.1版本了。)linux

NDK下載(這個不要用Android Studio裏面下載的ndk包,不完整,本身去官網下載r16b這個版本的就行)android

這兩個包下載完成後解壓後分別是這樣子的git

ffmpeggithub

ndk緩存

步驟

在解壓後的ffmpeg包的根目錄下新建一個.sh文件,這裏我建立一個名爲build_android.sh的文件,文件編輯內容以下:bash

ADDI_CFLAGS="-marm"
API=19
PLATFORM=arm-linux-androideabi
CPU=armv7-a
NDK=/Users/wepon/android-ndk-r16b # 修改爲本身本地的ndk路徑。
SYSROOT=$NDK/platforms/android-$API/arch-arm/
ISYSROOT=$NDK/sysroot
ASM=$ISYSROOT/usr/include/$PLATFORM
TOOLCHAIN=$NDK/toolchains/$PLATFORM-4.9/prebuilt/darwin-x86_64
OUTPUT=/Users/wepon/Downloads/ffmpeg-4.0.3/android #本身指定一個輸出目錄,用來放生成的文件的。
function build
{
./configure \
--prefix=$OUTPUT \
--enable-shared \
--disable-static \
--disable-doc \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-avdevice \
--disable-doc \
--disable-symver \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--target-os=android \
--arch=arm \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-I$ASM -isysroot $ISYSROOT -Os -fpic -marm" \
--extra-ldflags="-marm" \
$ADDITIONAL_CONFIGURE_FLAG
  make clean
  make 
  make install
}

build
複製代碼

這裏主要修改的兩個點我註釋在那裏了,你們改爲本身的目錄就好了,第一個地方是你的NDK包下載後解壓的路徑,第二個地方是生成編譯後的文件目錄路徑。app

而後咱們使用 sh build_android.sh就能夠開始編譯了(沒錯,有的文章寫的在這以前還有替換代碼什麼的操做啥的不須要了),這個時候就須要耐心等待大概5-10分鐘的樣子(基於個人電腦上的不許確統計)才能編譯完成,編譯完成後能夠查看生成的文件目錄是否是跟個人同樣。我這裏放兩張編譯成功後的圖片:ide

走到這裏就能夠放Android項目移植使用了。學習

移植到Android中經過FFmpeg解碼播放視頻

首先,咱們新建一個Android項目,記得把Include C++ support勾上。

而後把咱們的so文件和include文件放到lib目錄下,還有build.gradle文件和CMakeLists.txt文件的代碼一併放出,以下:

lib

cmake

# 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)

# 定義變量
set(distribution_DIR ../../../../libs)

# 添加庫——本身編寫的庫
# 庫名稱:native-lib
# 庫類型:SHARED,表示動態庫,後綴爲.so(若是是STATIC,則表示靜態庫,後綴爲.a)
# 庫源碼文件:src/main/cpp/native-lib.cpp
add_library( native-lib
        SHARED
        src/main/cpp/native-lib.cpp )

# 添加庫——外部引入的庫
# 庫名稱:avcodec(不須要包含前綴lib)
# 庫類型:SHARED,表示動態庫,後綴爲.so(若是是STATIC,則表示靜態庫,後綴爲.a)
# IMPORTED代表是外部引入的庫
add_library( avcodec
        SHARED
        IMPORTED)
# 設置目標屬性
# 設置avcodec目標庫的IMPORTED_LOCATION屬性,用於說明引入庫的位置
# 還能夠設置其餘屬性,格式:PROPERTIES key value
set_target_properties( avcodec
        PROPERTIES IMPORTED_LOCATION
        ${distribution_DIR}/${ANDROID_ABI}/libavcodec.so)


find_library(
        log-lib
        log)
add_library( avfilter
        SHARED
        IMPORTED)
set_target_properties( avfilter
        PROPERTIES IMPORTED_LOCATION
        ${distribution_DIR}/${ANDROID_ABI}/libavfilter.so)

add_library( avformat
        SHARED
        IMPORTED)
set_target_properties( avformat
        PROPERTIES IMPORTED_LOCATION
        ${distribution_DIR}/${ANDROID_ABI}/libavformat.so)

add_library( avutil
        SHARED
        IMPORTED)
set_target_properties( avutil
        PROPERTIES IMPORTED_LOCATION
        ${distribution_DIR}/${ANDROID_ABI}/libavutil.so)

add_library( swresample
        SHARED
        IMPORTED)
set_target_properties( swresample
        PROPERTIES IMPORTED_LOCATION
        ${distribution_DIR}/${ANDROID_ABI}/libswresample.so)

add_library( swscale
        SHARED
        IMPORTED)
set_target_properties( swscale
        PROPERTIES IMPORTED_LOCATION
        ${distribution_DIR}/${ANDROID_ABI}/libswscale.so)

# 引入頭文件
include_directories(libs/include)

# 告訴編譯器生成native-lib庫須要連接的庫
# native-lib庫須要依賴avcodec、avfilter等庫
target_link_libraries( native-lib
        avcodec
        avfilter
        avformat
        avutil
        swresample
        swscale
        -landroid
        ${log-lib} )


複製代碼

build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.wepon.ffmpeg4cmake"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags "-frtti -fexceptions -Wno-deprecated-declarations"
            }
            ndk{
                abiFilters "armeabi-v7a"
            }
        }

        sourceSets {
            main {
                jniLibs.srcDirs = ['libs']
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

複製代碼

native-lib.cpp (c文件,主要是使用ffmpeg解碼播放視頻的代碼)

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

extern "C"
{

#include <android/native_window_jni.h>
#include <libavfilter/avfilter.h>
#include <libavcodec/avcodec.h>
//封裝格式處理
#include <libavformat/avformat.h>
//像素處理
#include <libswscale/swscale.h>
#include <unistd.h>

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

    // 註冊。
    av_register_all();
    // 打開地址而且獲取裏面的內容  avFormatContext是內容的一個上下文
    AVFormatContext *avFormatContext = avformat_alloc_context();
    avformat_open_input(&avFormatContext, url, NULL, NULL);
    avformat_find_stream_info(avFormatContext, NULL);

    // 找出視頻流
    int video_index = -1;
    for (int i = 0; i < avFormatContext->nb_streams; ++i) {
        if (avFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_index = i;
        }
    }
    // 解碼  轉換  繪製
    // 獲取解碼器上下文
    AVCodecContext *avCodecContext = avFormatContext->streams[video_index]->codec;
    // 獲取解碼器
    AVCodec *avCodec = avcodec_find_decoder(avCodecContext->codec_id);
    // 打開解碼器
    if (avcodec_open2(avCodecContext, avCodec, NULL) < 0) {
        // 打開失敗。
        return;
    }
    // 申請AVPacket和AVFrame,
    // 其中AVPacket的做用是:保存解碼以前的數據和一些附加信息,如顯示時間戳(pts)、解碼時間戳(dts)、數據時長,所在媒體流的索引等;
    // AVFrame的做用是:存放解碼事後的數據。
    AVPacket *avPacket = (AVPacket *) av_malloc(sizeof(AVPacket));
    av_init_packet(avPacket);
    // 分配一個AVFrame結構體,AVFrame結構體通常用於存儲原始數據,指向解碼後的原始幀
    AVFrame *avFrame = av_frame_alloc();
    //分配一個AVFrame結構體,指向存放轉換成rgb後的幀
    AVFrame *rgb_frame = av_frame_alloc();
    // rgb_frame是一個緩存區域,因此須要設置。
    // 緩存區
    uint8_t *out_buffer = (uint8_t *) av_malloc(
            avpicture_get_size(AV_PIX_FMT_RGBA, avCodecContext->width, avCodecContext->height));
    // 與緩存區相關聯,設置rgb_frame緩存區
    avpicture_fill((AVPicture *) rgb_frame, out_buffer, AV_PIX_FMT_RGBA, avCodecContext->width,
                   avCodecContext->height);
    // 原生繪製,須要ANativeWindow
    ANativeWindow *pANativeWindow = ANativeWindow_fromSurface(env, surface);
    if (pANativeWindow == 0) {
        // 獲取native window 失敗
        return;
    }
    SwsContext *swsContext = sws_getContext(
            avCodecContext->width,
            avCodecContext->height,
            avCodecContext->pix_fmt,
            avCodecContext->width,
            avCodecContext->height,
            AV_PIX_FMT_RGBA,
            SWS_BICUBIC,
            NULL,
            NULL,
            NULL);
    // 視頻緩衝區
    ANativeWindow_Buffer native_outBuffer;
    // 開始解碼了。
    int frameCount;
    while (av_read_frame(avFormatContext, avPacket) >= 0) {
        if (avPacket->stream_index == video_index) {
            avcodec_decode_video2(avCodecContext, avFrame, &frameCount, avPacket);
            // 當解碼一幀成功事後,咱們轉換成rgb格式而且繪製。
            if (frameCount) {
                ANativeWindow_setBuffersGeometry(pANativeWindow, avCodecContext->width,
                                                 avCodecContext->height, WINDOW_FORMAT_RGBA_8888);
                // 上鎖
                ANativeWindow_lock(pANativeWindow, &native_outBuffer, NULL);
                // 轉換爲rgb格式
                sws_scale(swsContext, (const uint8_t *const *) avFrame->data, avFrame->linesize, 0,
                          avFrame->height, rgb_frame->data, rgb_frame->linesize);
                uint8_t *dst = (uint8_t *) native_outBuffer.bits;
                int destStride = native_outBuffer.stride * 4;
                uint8_t *src = rgb_frame->data[0];
                int srcStride = rgb_frame->linesize[0];
                for (int i = 0; i < avCodecContext->height; ++i) {
                    memcpy(dst + i * destStride, src + i * srcStride, srcStride);
                }
                ANativeWindow_unlockAndPost(pANativeWindow);
//                usleep(1000 * 16);
            }
        }
        av_free_packet(avPacket);

    }

    ANativeWindow_release(pANativeWindow);
    av_frame_free(&avFrame);
    av_frame_free(&rgb_frame);
    avcodec_close(avCodecContext);
    avformat_free_context(avFormatContext);


    env->ReleaseStringUTFChars(url_, url);
}


JNIEXPORT jstring JNICALL
Java_com_wepon_ffmpeg4cmake_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

JNIEXPORT jstring JNICALL
Java_com_wepon_ffmpeg4cmake_MainActivity_urlprotocolinfo(JNIEnv *env, jobject instance) {
    char info[40000] = {0};
    av_register_all();
    struct URLProtocol *pup = NULL;
    struct URLProtocol **p_temp = &pup;
    avio_enum_protocols((void **) p_temp, 0);
    while ((*p_temp) != NULL) {
        sprintf(info, "%sInput: %s\n", info, avio_enum_protocols((void **) p_temp, 0));
    }
    pup = NULL;
    avio_enum_protocols((void **) p_temp, 1);
    while ((*p_temp) != NULL) {
        sprintf(info, "%sInput: %s\n", info, avio_enum_protocols((void **) p_temp, 1));
    }

    return env->NewStringUTF(info);
}

JNIEXPORT jstring JNICALL
Java_com_wepon_ffmpeg4cmake_MainActivity_avformatinfo(JNIEnv *env, jobject instance) {

    char info[40000] = {0};

    av_register_all();

    AVInputFormat *if_temp = av_iformat_next(NULL);
    AVOutputFormat *of_temp = av_oformat_next(NULL);
    while (if_temp != NULL) {
        sprintf(info, "%sInput: %s\n", info, if_temp->name);
        if_temp = if_temp->next;
    }
    while (of_temp != NULL) {
        sprintf(info, "%sOutput: %s\n", info, of_temp->name);
        of_temp = of_temp->next;
    }
    return env->NewStringUTF(info);
}

JNIEXPORT jstring JNICALL
Java_com_wepon_ffmpeg4cmake_MainActivity_avcodecinfo(JNIEnv *env, jobject instance) {
    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, "%sdecode:", info);
        } else {
            sprintf(info, "%sencode:", 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;
    }

    return env->NewStringUTF(info);
}

JNIEXPORT jstring JNICALL
Java_com_wepon_ffmpeg4cmake_MainActivity_avfilterinfo(JNIEnv *env, jobject instance) {
    char info[40000] = {0};
    avfilter_register_all();

    AVFilter *f_temp = (AVFilter *) avfilter_next(NULL);
    while (f_temp != NULL) {
        sprintf(info, "%s%s\n", info, f_temp->name);
        f_temp = f_temp->next;
    }
    return env->NewStringUTF(info);
}
}
複製代碼

到這裏咱們就能寫視頻文件的播放了,這裏我使用了一個網上找的視頻文件地址進行播放,記得加上聯網的權限。播放效果以下,生成gif太大了就沒生成了(只是簡單的處理了視頻的解壓,音頻這塊暫時沒有處理,FFmpeg要學習的東西不少,能夠參考其餘文檔):

代碼放在 github github.com/ywp0919/FFm…

相關文章
相關標籤/搜索