ffmpeg在Android上的第一個小實例

首先上一個圖交代一下我的文件目錄 :

需要說明的幾點:

1.首先在Androidstudio上創建一個支持c++的項目。

2.我用的ffmpeg編譯是那種編譯成一個so文件的方式,不是那種對各個模塊分別生成一個so的方式。

3.libffmpeg.so放在libs armeabi-v7a目錄下,這是在app 的gradle裏配置的。涉及到這兩個配置

sourceSets {
    main {
        jniLibs.srcDirs = ['libs']
    }
}
ndk{
    abiFilters "armeabi-v7a"
}

這個libffmpeg只能放在armeabi-v7a目錄下,不能再建子目錄(我之前踩坑了,創建了一個子目錄,老是提示找不到so文件)

4. .so包裏並不包含頭文件,有頭文件的需要單獨拿出來(include裏放的就是ffmpeg的頭文件)。我之前把include放在armeabi-v7a目錄下,一直提示找不到頭文件。後面發現要放你寫的jni的cpp文件同一個目錄下。然後還要加一些配置。

#包含頭文件
target_include_directories(ffmpegPlayer PRIVATE ${CMAKE_SOURCE_DIR}/src/main/cpp/include)

5.重點就是CmakeList.txt的配置,如下:



cmake_minimum_required(VERSION 3.4.1)

#判斷編譯器類型,如果是gcc編譯器,則在編譯選項中加入c++11支持
if(CMAKE_COMPILER_IS_GNUCXX)
    set(CMAKE_CXX_FLAGS "-std=c++11 ${CMAKE_CXX_FLAGS}")
    message(STATUS "optional:-std=c++11")
endif(CMAKE_COMPILER_IS_GNUCXX)

#添加jni的so
add_library( ffmpegPlayer
             SHARED
             src/main/cpp/ffmpegPlayer.cpp )

#查找log-lib的so,系統的庫可以通過find來添加
find_library(log-lib  log )

#聲明libffmpeg
add_library(ffmpeg SHARED IMPORTED)

#給ffmpeg添加具體的so,在build.gradle中ndk模塊中添加下面的代碼:abiFilters 'armeabi-v7a'
#注意armeabi-v7a下面不能再添加目錄,so直接放在裏面
set_target_properties(ffmpeg
                    PROPERTIES IMPORTED_LOCATION

                    #CMAKE_CURRENT_SOURCE_DIR 這個變量是系統自定義的,表示CMakeLists.txt文件的絕對路徑
                    ${CMAKE_CURRENT_SOURCE_DIR}/libs/armeabi-v7a/libffmpeg.so
                    )
#包含頭文件
target_include_directories(ffmpegPlayer PRIVATE ${CMAKE_SOURCE_DIR}/src/main/cpp/include)

#最後將上面準備好的so鏈接到一起去
target_link_libraries( ffmpegPlayer
                       ffmpeg
                       ${log-lib} )

5.需要交代的是,我們可以直接通過CmakeList 的這個配置
add_library( ffmpegPlayer
             SHARED
             src/main/cpp/ffmpegPlayer.cpp )

直接運行,就能生成libffmpegPlayer.so 文件。我一直有個疑問,爲什麼不能直接這樣編譯出ffmepg的so文件呢?我不知道確實原因,我想應該是ffmpeg項目裏的代碼比較多,而且有封複雜的依賴關係,不適合通過AndroidStudio直接編譯。

下面看看我的jni文件 ffmpegPlayer.cpp 這就是一箇中介,java通過這個入口文件來調用ffmpeg裏編碼、編碼、封包、解包、解碼、YUV to RGBA的轉換的功能,當然這是後話,我現在僅僅通過這個入口文件調用一個ffmpeg各個模塊的最基礎的方法,只是爲了證明確實調到了ffmpeg。

代碼如下:

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

#ifdef __cplusplus
extern "C"
{
#endif

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

#ifdef __cplusplus
};
#endif

#ifdef ANDROID
#include <android/log.h>
#define LOG_TAG    "FFmpegHello"
#define LOGE(format, ...)  __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, format, ##__VA_ARGS__)
#define LOGI(format, ...)  __android_log_print(ANDROID_LOG_INFO,  LOG_TAG, format, ##__VA_ARGS__)
#else
#define LOGE(format, ...)  printf(LOG_TAG format "\n", ##__VA_ARGS__)
#define LOGI(format, ...)  printf(LOG_TAG format "\n", ##__VA_ARGS__)
#endif



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

// 被extern “C”修飾的函數或者變量是按照C語言方式編譯和鏈接的,所以可以用一句話來概括extern “C”的真實目的是實現C++與C的混合編程。
  JNIEXPORT jstring JNICALL Java_com_example_ffmpegplayerone_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(info);
 
}


  JNIEXPORT jstring JNICALL Java_com_example_ffmpegplayerone_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(info);
}


 JNIEXPORT jstring JNICALL Java_com_example_ffmpegplayerone_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(info);
}

 JNIEXPORT jstring JNICALL Java_com_example_ffmpegplayerone_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[%s]\n", info, f_temp->name);
    }
    LOGE("%s", info);
  	return env->NewStringUTF(info);
}

JNIEXPORT jstring JNICALL Java_com_example_ffmpegplayerone_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(info);
}

#ifdef __cplusplus
}
#endif

java調調用的代碼如下:

package com.example.ffmpegplayerone;

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

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("ffmpegPlayer");
    }

    private TextView tv_protocol_info;
    private TextView tv_format_info;
    private TextView tv_codec_info;
    private TextView tv_filter_info;
    private TextView tv_configuration_info;
    private TextView tv_content;

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


        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());

        findViewById(R.id.tv_protocol_info).setOnClickListener(this);
        findViewById(R.id.tv_format_info).setOnClickListener(this);
        findViewById(R.id.tv_codec_info).setOnClickListener(this);
        findViewById(R.id.tv_filter_info).setOnClickListener(this);
        findViewById(R.id.tv_configuration_info).setOnClickListener(this);
        tv_content = findViewById(R.id.tv_content);

    }

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

    public native String urlprotocolinfo();

    public native String avformatinfo();

    public native String avcodecinfo();

    public native String avfilterinfo();

    public native String configurationinfo();

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.tv_protocol_info:
                tv_content.setText(urlprotocolinfo());
                break;
            case R.id.tv_codec_info:
                tv_content.setText(avcodecinfo());
                break;
            case R.id.tv_filter_info:
                tv_content.setText(avfilterinfo());
                break;
            case R.id.tv_format_info:
                tv_content.setText(avformatinfo());
                break;
            case R.id.tv_configuration_info:
                tv_content.setText(configurationinfo());
                break;
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/sample_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hello World!" />

    <TextView
        android:id="@+id/tv_protocol_info"
        android:layout_width="match_parent"
        android:layout_height="40dip"
        android:layout_margin="5dip"
        android:background="#ff4163"
        android:gravity="center"
        android:text="Protocol信息"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_format_info"
        android:layout_width="match_parent"
        android:layout_height="40dip"
        android:layout_margin="5dip"
        android:background="#ff4163"
        android:gravity="center"
        android:text="format信息"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_codec_info"
        android:layout_width="match_parent"
        android:layout_height="40dip"
        android:layout_margin="5dip"
        android:background="#ff4163"
        android:gravity="center"
        android:text="codec信息"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_filter_info"
        android:layout_width="match_parent"
        android:layout_height="40dip"
        android:layout_margin="5dip"
        android:background="#ff4163"
        android:gravity="center"
        android:text="filter信息"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_configuration_info"
        android:layout_width="match_parent"
        android:layout_height="40dip"
        android:layout_margin="5dip"
        android:background="#ff4163"
        android:gravity="center"
        android:text="Configuration信息"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

運行效果如下圖:

其中filter那個jni方法報錯,沒查出來什麼原因。

下一步通過jni調用一下ffmpeg音視頻相關的方法。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章