我们今天的目标是仿照雷神的博客(https://blog.csdn.net/leixiaohua1020/article/details/47008825),把这个APP写出来,先上图:
APP中有5个按钮,点击不同的按钮,会有不同的ffmpeg信息显示出来,分别是我们编译的ffmpeg所支持的协议,编解码器,过滤器,格式和配置等。
1. 新建Android Studio工程,参照上一篇文章的详细介绍。
2. 在app/src/main/下新建一个jniLibs目录,并将ffmpeg的lib库和include文件夹拷贝过来。
3. 修改CMakeLists.txt文件,与上一篇的一致,以后都使用的是这个CMakList.txt文件,如下所示:
#CMake版本信息
cmake_minimum_required(VERSION 3.4.1)
#ffmpeg so文件路径
set(lib_src_DIR ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})
#配置加载native依赖
include_directories(${CMAKE_SOURCE_DIR}/src/main/jniLibs/include)
#动态方式加载ffmepg的so文件 第三方库加载方式
add_library(ffmpeg SHARED IMPORTED)
#引入libffmpeg.so文件
set_target_properties(ffmpeg PROPERTIES IMPORTED_LOCATION
${lib_src_DIR}/libffmpeg.so)
#CPP文件夹下待编译的c文件
add_library(native-lib SHARED ${CMAKE_SOURCE_DIR}/src/main/cpp/native-lib.cpp)
#C 日志 ndk官方库
find_library(log-lib log)
#静态库与动态库进行链接 相当于gcc命令行参数 -l。
target_link_libraries(native-lib
android
ffmpeg
${log-lib})
4. 修改app/src/build.gradle文件,添加ndk选项,参照上篇。
5. APP的界面是5个按钮和一个显示文字的TextView,修改app/src/main/res/layout/activity_main.xml文件,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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"
tools:context=".MainActivity">
<LinearLayout
android:id="@+id/linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/button_protocol"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="onButtonClick"
android:text="@string/button_protocol"
android:textSize="12sp"/>
<Button
android:id="@+id/button_codec"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="onButtonClick"
android:text="@string/button_codec"
android:textSize="12sp"/>
<Button
android:id="@+id/button_filter"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="onButtonClick"
android:text="@string/button_filter"
android:textSize="12sp"/>
<Button
android:id="@+id/button_format"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="onButtonClick"
android:text="@string/button_format"
android:textSize="12sp"/>
<Button
android:id="@+id/button_configuration"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="onButtonClick"
android:text="@string/button_configuration"
android:textSize="12sp"/>
</LinearLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/linear_layout"
android:padding="16dp">
<TextView
android:id="@+id/sample_text"
android:layout_width="match_parent"
android:textSize="24sp"
android:textColor="#ff0000"
android:layout_height="wrap_content"/>
</ScrollView>
</RelativeLayout>
同时,这个layout文件中使用了的一些变量需要在app/src/main/res/values/strings.xml里面定义,如下所示:
<resources>
<string name="app_name">ffmpeg_android_demo_2</string>
<string name="button_protocol">Protocol</string>
<string name="button_codec">Codec</string>
<string name="button_filter">Filter</string>
<string name="button_format">Format</string>
<string name="button_configuration">Configuration</string>
</resources>
在做这一步操作时,当切换到app/src/main/res/layout/activity_main.xml的Design视图时,向里面拖拽Button,没有显示,同时提示Render problem,如下所示:
解决办法是找到app/src/main/res/values/styles.xml文件,在Theme前面添加个Base字符,如图所示:
6. 这时候就可以写我们的主程序了,打开app/src/main/java/xxx/MainActivity文件,主要目的就是复写onButtonClick方法,每当点击按钮时,就显示按钮所对应的内容,所以这个函数很好写,只需要设置TextView的显示内容即可,如下所示:
package com.example.nxf31081.ffmpeg_android_demo_2;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
mTextView = (TextView) findViewById(R.id.sample_text);
}
public void onButtonClick(View view) {
int id = view.getId();
switch (id) {
case R.id.button_protocol:
mTextView.setText(avProtocolInfo());
break;
case R.id.button_codec:
mTextView.setText(avCodecInfo());
break;
case R.id.button_filter:
mTextView.setText(avFilterInfo());
break;
case R.id.button_format:
mTextView.setText(avFormatInfo());
break;
case R.id.button_configuration:
mTextView.setText(avConfigurationInfo());
break;
}
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String avProtocolInfo();
public native String avFormatInfo();
public native String avCodecInfo();
public native String avFilterInfo();
public native String avConfigurationInfo();
}
7. 重点还是这几个native函数,根据上篇中的介绍,分别生成这个native函数的函数模板,在native-lib.cpp函数中,这几个函数就是ffmpeg API的简单应用了,如下所示:
#include <jni.h>
#include <string>
#include <android/log.h>
#define LOG_TAG "FFNative"
#define ALOGV(...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
#define ALOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
#define ALOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
#define ALOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
#define ALOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavfilter/avfilter.h>
#include <libswscale/swscale.h>
JNIEXPORT jstring JNICALL
Java_com_example_nxf31081_ffmpeg_1android_1demo_12_MainActivity_avProtocolInfo(JNIEnv *env,
jobject instance) {
char info[40000] = {0};
av_register_all();
void *opaque = NULL;
const char *name;
//Input
while((name = avio_enum_protocols(&opaque, 0)))
sprintf(info, "%s[Input] : [%10s] \n", info, name);
//Output
opaque = NULL;
while((name = avio_enum_protocols(&opaque, 1)))
sprintf(info, "%s[Output] : [%10s] \n", info, name);
return env->NewStringUTF(info);
}
JNIEXPORT jstring JNICALL
Java_com_example_nxf31081_ffmpeg_1android_1demo_12_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, "%s[Input]: %10s, %s \n", info, if_temp->name, if_temp->long_name ? if_temp->long_name : " ");
if_temp = if_temp->next;
}
while (of_temp != NULL) {
sprintf(info, "%s[Output]: %10s, %s \n", info, of_temp->name, of_temp->long_name ? of_temp->long_name : " ");
of_temp = of_temp->next;
}
ALOGI("%s", info);
return env->NewStringUTF(info);
}
JNIEXPORT jstring JNICALL
Java_com_example_nxf31081_ffmpeg_1android_1demo_12_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, "%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;
case AVMEDIA_TYPE_SUBTITLE:
sprintf(info, "%s (subtitle) : ", info);
break;
default:
sprintf(info, "%s (other) : ", info);
break;
}
sprintf(info, "%s[%10s] %s \n", info, c_temp->name, c_temp->long_name ? c_temp->long_name : " ");
c_temp = c_temp->next;
}
ALOGI("%s", info);
return env->NewStringUTF(info);
}
JNIEXPORT jstring JNICALL
Java_com_example_nxf31081_ffmpeg_1android_1demo_12_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;
}
ALOGI("%s", info);
return env->NewStringUTF(info);
}
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_nxf31081_ffmpeg_1android_1demo_12_MainActivity_avConfigurationInfo(JNIEnv *env,
jobject instance) {
char info[10000] = {0};
av_register_all();
sprintf(info, "%s \n", avcodec_configuration());
return env->NewStringUTF(info);
}
这时候,这个app函数就算写好了,图放在文章的开头,可以看到,我们配置的硬编解码器h264_mediacodec已经能够检测到了。在下一篇文章中再仔细分析CMakeLists.txt文件和native-lib.cpp中所使用的ffmpeg函数。