使用 FFmpeg 生成視頻封面圖時,其實能夠直接使用 FFmpeg 相關命令截取一幀的圖像數據保存到本地,而後加載到 ImageView 上。android
有時候使用命令確實比寫代碼更加簡單和令人輕鬆一點。git
因此這一篇是講解如何導入 FFmpeg 相關源碼 而後如何執行命令行工具的博客,可是其實這只是個 Demo 而已,由於有不少細節須要處理,推薦直接使用開源庫。github
導入源碼
從FFmpeg源碼中導入 cmdutils.c、cmdutils.h、config.h、ffmpeg.c、ffmpeg.h、web
ffmpeg_filter.c、ffmpeg_hw.c、ffmpeg_opt.c 這幾個源碼。shell
通常存放在 fftools 目錄下。config.h 若是編譯生成目錄下沒有,就能夠直接使用ffmpeg 根目錄下的 config.h。api
編寫 CmakeList
# 設置構建本機庫文件所需的 CMake的最小版本
cmake_minimum_required(VERSION 3.4.1)
#添加頭文件的搜索路徑
include_directories(src/main/cpp/include)
#設置查找動態庫位置
set(LINK_DIR ${CMAKE_SOURCE_DIR}/libs/${CMAKE_ANDROID_ARCH_ABI})
link_directories(${LINK_DIR})
#找到全部的so庫,存放在全局變量SO_DIR中
file(GLOB SO_DIR ${LINK_DIR}/*.so)
#找到全部的源文件,存放在全局變量中
#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)
# 添加本身寫的 C/C++源文件
add_library(utils #so名稱
SHARED #動態庫
${CPP_DIR}
${FFMPEG_DIR}
)
# 依賴 NDK中自帶的log庫
find_library(log-lib log)
# 連接庫
target_link_libraries(
utils
${SO_DIR}
jnigraphics
${log-lib})
我是將 ffmpeg 的源碼和以前生成的 ffmpeg 頭文件都放在了 cpp/include 目錄下。bash
這樣在 CmakeList 中使用 include_directories 就能夠直接找到全部的頭文件,而後將 ffmpeg 的源碼和本身寫的工具類源碼關聯起來就好了。微信
修改 FFmpeg 的源碼
修改ffmpeg.c的main方法名稱爲exe_cmd,並在ffmpeg.h頭文件加上一樣名稱的方法聲明。app
//ffmpeg.c
int exe_cmd(int argc, char **argv) {
...
}
//ffmpeg.h
int exe_cmd(int argc, char **argv);
原生命令行工具在執行完 FFmpeg 命令後都會退出程序,可是在 Android 裏面可不能這樣,因此咱們要修改 FFmpeg 結束程序的函數。
編輯器
修改 cmdutils.c 和 cmdutils.h,註釋掉退出程序的代碼,而且增長一個int的返回值。
//cmdutils.c
int exit_program(int ret)
{
// if (program_exit)
// program_exit(ret);
// exit(ret);
return ret;
}
//cmdutils.h
int exit_program(int ret);
而且在 Android 裏面咱們確定是執行完一條命令,接着還會繼續執行其餘命令,因此咱們須要從新初始化一些關鍵變量的值。
找到 ffmpeg.c 中的 ffmpeg_cleanup 函數,在末尾將一些關鍵變量從新初始化。
//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;
}
最後在 main 函數末尾調用 ffmpeg_cleanup 函數。
int exe_cmd(int argc, char **argv) {
...
// exit_program(received_nb_signals ? 255 : main_return_code);
ffmpeg_cleanup(0);
}
增長 FFmpeg 日誌輸出
在 ffmpeg.c 中找到 log_callback_null 的函數,添加以下代碼,原代碼塊是空實現。
#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);
}
代
在 main 函數中調用 log_callback_null 函數。
int exe_cmd(int argc, char **argv) {
av_log_set_callback(log_callback_null);
int i, ret;
...
}
編寫工具類方法
在 MainActivity中 增長 exeCmd(String[] cmd) 方法。
public static native int exeCmd(String[] cmd);
在 ffmpeg_utils.cpp 增長 jni 方法。
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;
}
執行命令。
private void exeCmd() {
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();
}
//裁剪個1s視頻
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);
}
查看日誌輸出
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
執行命令的返回值 ==0,而且也看到確實文件已經生成出來了,咱們 adb pull 把文件導出到桌面用 ffprobe 或 ffplay 看看。
ffprobe video.flv
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), 1080x1920, 3390 kb/s, 30 fps, 30 tbr, 1k tbn, 60 tbc
Stream #0:1: Audio: aac (LC), 48000 Hz, stereo, fltp, 317 kb/s
能夠看到確實裁剪生成了一個1秒的視頻,雖而後綴名咱們用的 .flv,可是其實咱們是拷貝的視頻編碼,因此仍是mp4的封裝格式。
源碼:
https://github.com/simplepeng/AndroidExamples/tree/master/example_ffmpeg
使用已有的輪子
上面的例子並非一個完善的工具類,好比缺乏 Native 層的線程支持,出現錯誤就會直接閃退,缺乏進度回調等,因此仍是直接使用現成的輪子比較靠譜,只是咱們須要知道輪子大概是怎麼造出來的就好了。
這裏我推薦使用 mobile-ffmpeg 這個開源庫,1.8k 的 star 足以證實其品質還行,直接導入編譯好的aar就能夠執行命令行工具鏈,並且能夠自行編譯連接不少有用的第三方library,好比 x26四、libwebp 等。
動手能力強或有特殊需求的可使用 android.sh 自行編譯出 FFmpeg 頭文件和動態庫,以及 Android 工具鏈的 aar。
好比說我如今只須要一個支持 arm64-v8a 和 api16 及以上的動態庫,那麼我就本身新建了一個shell腳本文件:
#!/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
執行完這個 shell 後,就會在 prebuilt 目錄下生產對應的頭文件,動態庫,以及 aar 文件,直接拿來用就能夠了。
來源:https://juejin.im/post/6850418116602642440
做者:simplepeng
-- END --
進技術交流羣,掃碼添加個人微信:Byte-Flow
獲取視頻教程和源碼
推薦:
FFmpeg + OpenGL ES 實現 3D 全景播放器
FFmpeg + OpenGLES 實現視頻解碼播放和視頻濾鏡
以爲不錯,點個在看唄~
本文分享自微信公衆號 - 字節流動(google_developer)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。