You can also use FFmpeg commands to capture a frame of image data, save it locally, and then load it into ImageView. Sometimes using commands is easier and easier than writing code. So this is a blog post on how to import the FFmpeg source code and how to implement the command line tool, but in fact this is just a Demo, because there are a lot of details to deal with, recommend directly using the open source library.

Import the source code

Import CMDutils.c, cmDutils.h, config.h, FFmpeg.c, ffmpeg.h,

Ffmpeg_filter. c, FFmpeg_hw. C, ffmpeg_opt.c these sources, generally stored in the FFTools directory. H If not in the build directory, you can directly use the ffmpeg root directory config.h.

Write CmakeList

Set the minimum version of CMake required to build this library file
cmake_minimum_required(VERSION 3.4.1)

Add the search path for the header file
include_directories(src/main/cpp/include)

Set to find dynamic library locations
set(LINK_DIR ${CMAKE_SOURCE_DIR}/libs/${CMAKE_ANDROID_ARCH_ABI})
link_directories(${LINK_DIR})
Find all so libraries in the global variable SO_DIR
file(GLOB SO_DIR ${LINK_DIR}/*.so)

Find all source files and store them in global variables
#file(GLOB FFMPEG_DIR src/main/cpp/ffmpeg/*.c)
#message("FFMPEG_DIR == ${FFMPEG_DIR}")

file(GLOB CPP_DIR src/main/cpp/*.cpp)
file(GLOB FFMPEG_DIR src/main/cpp/include/*.c)

# Add your own C/C++ source files
add_library(utils # so name
        SHARED # dynamic libraries
        ${CPP_DIR}
        ${FFMPEG_DIR}
        )

# Rely on the NDK's built-in log library
find_library(log-lib log)

# link library
target_link_libraries(
        utils
        ${SO_DIR}
        jnigraphics
        ${log-lib})
Copy the code

I placed the ffmPEG source and the generated ffmpeg header directories in the CPP /include directory so that include_directories would be able to find all the header directories. Then ffMPEG source code and write their own utility class source code associated on the line.

Modify FFmpeg source code

Change the main method name of ffmpeg.c to exe_cmd and add a method declaration with the same name to the ffmpeg.h header file.

//ffmpeg.c
int exe_cmd(int argc, char **argv) {... }//ffmpeg.h
int exe_cmd(int argc, char **argv);
Copy the code

Native command line tools exit the program after executing FFmpeg commands, but this is not the case in Android, so we need to change the FFmpeg termination function.

Modify cmDutils.c and cmDutils.h to comment out the code for exiting the program, and add an int return value.

//cmdutils.c
int exit_program(int ret)
{
// if (program_exit)
// program_exit(ret);

// exit(ret);
    return ret;
}

//cmdutils.h
int exit_program(int ret);
Copy the code

And in Android we must execute one command and then continue to execute other commands, so we need to reinitialize some key variable values.

Find the ffmpeg_cleanup function in ffmpeg.c and reinitialize some key variables at the end.

//ffmpeg.c
static void ffmpeg_cleanup(int ret) {... nb_filtergraphs =0;
    nb_output_files = 0;
    nb_output_streams = 0;
    nb_input_files = 0;
    nb_input_streams = 0;
}
Copy the code

Finally, the ffmpeg_cleanup function is called at the end of main

int exe_cmd(int argc, char **argv) {...// exit_program(received_nb_signals ? 255 : main_return_code);
    ffmpeg_cleanup(0);
}
Copy the code

Add FFmpeg log output

Find the log_callback_NULL function in ffmpeg.c and add the following code. The original code block is an empty implementation.

#include "android/log.h"
#define logDebug(...) __android_log_print(ANDROID_LOG_DEBUG,"MainActivity",__VA_ARGS__)

static void log_callback_null(void *ptr, int level, const char *fmt, va_list vl) {
    static int print_prefix = 1;
    static int count;
    static char prev[1024];
    char line[1024];
    static int is_atty;
    av_log_format_line(ptr, level, fmt, vl, line, sizeof(line), &print_prefix);
    strcpy(prev, line);

    logDebug("ffmpeg log ----- %s", line);
}
Copy the code

The log_callback_NULL function is called in the main function

int exe_cmd(int argc, char **argv) {
    av_log_set_callback(log_callback_null);
  	 inti, ret; . }Copy the code

Write utility class methods

Add exeCmd(String[] CMD) to MainActivity

public static native int exeCmd(String[] cmd);
Copy the code

Add jNI methods to ffmpeg_utils.cpp

JNIEXPORT jint JNICALL
Java_demo_simple_example_1ffmpeg_MainActivity_exeCmd(JNIEnv *env, jclass clazz, jobjectArray cmd) {
    int argc = env->GetArrayLength(cmd);
    logDebug("argc == %d", argc);
    char *argv[argc];

    for (int i = 0; i < argc; ++i) {
        jstring str = (jstring) env->GetObjectArrayElement(cmd, i);
        argv[i] = (char *) env->GetStringUTFChars(str, JNI_FALSE);
        logDebug("%s ", argv[i]);
    }

    return exe_cmd(argc, argv);
// return 1;
}
Copy the code

Execute the command

private void exeCmd(a) {
        String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator
                + "get_cover1.mp4";
        String outPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator
                + "video.flv";
        File outFile = new File(outPath);
        if (outFile.exists()) {
            outFile.delete();
        }
        // Clip a 1s video
        String cmd = "ffmpeg -ss 00:00:00 -t 00:00:10 -i " + path + " -vcodec copy -acodec copy " + outPath;
        String[] cmdArr = cmd.split("");
        int result = exeCmd(cmdArr);
        Log.d(TAG, "exe cmd result == " + result);
    }
Copy the code

Viewing Log Output

demo.simple.example_ffmpeg D/MainActivity: ffmpeg log —– Output file #0 (/storage/emulated/0/video.flv):

demo.simple.example_ffmpeg D/MainActivity: exe cmd result == 0

Adb pull exports the file to the desktop using ffprobe or ffplay.

ffprobe video.flv
Copy the code

major_brand : mp42

Duration: 00:00:01.07, start: 0.033000, bitrate: 3130 KB /s

Stream #0:0: Video: h264 (Main), yuv420p(tv, bt709, progressive), 1080×1920, 3390 kb/s, 30 fps, 30 tbr, 1k tbn, 60 tbc

Stream #0:1: Audio: aac (LC), 48000 Hz, stereo, fltp, 317 kb/s

As you can see, we did crop out a 1-second video, although we used the.flv suffix, but we actually copied the video encoding, so it’s still in MP4 package format.

The source code

Use existing wheels

The above example is not a perfect tool class, such as the lack of thread support in the Native layer, the error will be directly backtraced, lack of progress callback, etc., so it is more reasonable to directly use the ready-made wheel, but we need to know about how the wheel is made.

Here I recommend using the open source library mobile-ffmpeg, 1.8K STAR is enough to prove its quality is ok, directly import compiled AAR can execute command line tool chain, and can compile and link to many useful third party libraries, such as X264, libwebp, etc.

Those with hands-on skills or special needs can use Android. sh to compile FFmpeg headers and dynamic libraries, as well as aar of android toolchain.

For example, if I only need a dynamic library that supports ARM64-V8A and API16 and above, I will create a new shell script file myself:

#! /bin/bash

export ANDROID_HOME="/Users/chenpeng/Library/Android/sdk/"
export ANDROID_NDK_ROOT="/Users/chenpeng/Desktop/work_space/ndk/android-ndk-r21b/"

build() {
    ./android.sh \
        --lts \
        --disable-arm-v7a \
        --disable-arm-v7a-neon \
        --disable-x86 \
        --disable-x86-64
}

build
Copy the code

After executing the shell, the corresponding headers, dynamic libraries, and AAR files are produced in the prebuilt directory and used directly.