NDK开发前奏 - 视频转码压缩
这几次的分享都是基于 NDK 部分的实战,并没有涉及到 C 和 C++ 的基础知识,春节后会从 C 基础的 HelloWorld 开始,还望我们能耐心等待。视频转码压缩开发中比较常见,记得上次有个哥们问我,自己基于 FFmpeg 去压缩,致命的是压缩时间太长了。这里给大家分享几种解决方案:
1. 基于服务器压缩
2. 基于开源库 FFmpeg 转码压缩 (NDK)
3. 基于开源库 iosparser 压缩
2. 基于开源库 FFmpeg 转码压缩
下载 FFmpeg 版本自己选择,写个脚本编译好 so 库。这个步骤是比较麻烦的,文章这里先略过。如果感兴趣可以自己查查资料试试,实在不行可以先用我编译好的即可。(后面我会单独写编译步骤)
编译好会有一大堆的 .so 和 .h 文件,选择性的 copy 到我们自己的工程目录下进行配置,这里用的是 AS2.3 + CMake 的方式。之前在 Android图片压缩加密上传 - JPEG压缩算法解析 中我们用的是 Android.mk 和 Application.mk 的方式。两种方式随便选择哪种都行,贴一下 CMakeLists.txt 中的配置。
cmake_minimum_required(VERSION 3.4.1)
#FFMpeg配置
#FFmpeg配置目录
set(distribution_DIR ${CMAKE_SOURCE_DIR}/../../../../src/main/jniLibs)
# 编解码(最重要的库)
add_library(
avcodec
SHARED
IMPORTED)
set_target_properties(
avcodec
PROPERTIES IMPORTED_LOCATION
../../../../src/main/jniLibs/armeabi/libavcodec-56.so)
# 设备信息
add_library(
avdevice
SHARED
IMPORTED)
set_target_properties(
avdevice
PROPERTIES IMPORTED_LOCATION
../../../../src/main/jniLibs/armeabi/libavdevice-56.so)
# 滤镜特效处理库
add_library(
avfilter
SHARED
IMPORTED)
set_target_properties(
avfilter
PROPERTIES IMPORTED_LOCATION
../../../../src/main/jniLibs/armeabi/libavfilter-5.so)
# 封装格式处理库
add_library(
avformat
SHARED
IMPORTED)
set_target_properties(
avformat
PROPERTIES IMPORTED_LOCATION
../../../../src/main/jniLibs/armeabi/libavformat-56.so)
# 工具库(大部分库都需要这个库的支持)
add_library(
avutil
SHARED
IMPORTED)
set_target_properties(
avutil
PROPERTIES IMPORTED_LOCATION
../../../../src/main/jniLibs/armeabi/libavutil-54.so)
# 后期处理
add_library(
postproc
SHARED
IMPORTED)
set_target_properties(
postproc
PROPERTIES IMPORTED_LOCATION
../../../../src/main/jniLibs/armeabi/libpostproc-53.so)
# 音频采样数据格式转换库
add_library(
swresample
SHARED
IMPORTED)
set_target_properties(
swresample
PROPERTIES IMPORTED_LOCATION
../../../../src/main/jniLibs/armeabi/libswresample-1.so)
# 视频像素数据格式转换
add_library(
swscale
SHARED
IMPORTED)
set_target_properties(
swscale
PROPERTIES IMPORTED_LOCATION
../../../../src/main/jniLibs/armeabi/libswscale-3.so)
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
#判断编译器类型,如果是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)
#需要引入我们头文件
include_directories(src/main/jniLibs/include)
include_directories(src/main/jniLibs/other)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds it for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
ffmpeg-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/native-lib.cpp )
# Searches for a specified prebuilt library and stores the path as a
# variable. Because system libraries are included in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in the
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
# 配置链接动态库
ffmpeg-lib avcodec avdevice avfilter avformat avutil postproc swresample swscale
# Links the target library to the log library
# included in the NDK.
${log-lib} )
整合好之后,最好还是先运行下,如果能正常运行再开始写压缩代码。FFmpeg下载后我们可以直接用命令行去压缩视频,我们自己写代码也是基于这个,如果不是特别了解可以先看看文档:
#include <jni.h>
#include <android/log.h>
#include <malloc.h>
#include <string.h>
// 定义 log 日志打印
#define NDK_TAG "NDK_TAG"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, NDK_TAG, __VA_ARGS__)
// 回调Java层刷新进度
static jobject callback_jobj = NULL; //java object
static JNIEnv *mEnv;
void compress_callback(int total, int current) {
if (callback_jobj != NULL && mEnv != NULL) {
// 回调 java 层代码
jclass callback_class = mEnv->GetObjectClass(callback_jobj);
jmethodID callbak_mid = mEnv->GetMethodID(callback_class, "nativeCompressCallback",
"(II)V");
mEnv->CallVoidMethod(callback_jobj, callbak_mid, total, current);
}
}
extern "C" {
// 函数声明
JNIEXPORT void JNICALL
Java_com_darren_ndk_day02_VideoCompress_ffmpegCompress(JNIEnv *env, jobject instance,
jobjectArray compressParams);
// 声明压缩函数
// argc 是参数的个数
// argv 二维数组指针
int ffmpegmain(int argc, char **argv,void(*callback)(int, int));
}
// 压缩方法实现
JNIEXPORT void JNICALL
Java_com_darren_ndk_day02_VideoCompress_ffmpegCompress(JNIEnv *env, jobject instance,
jobjectArray compressParams) {
// 1. 获取数组的长度
int argc = env->GetArrayLength(compressParams);
LOGE("数组长度:%d", argc);
// 2. Java String[] -> char**
// 2.1 开辟内存空间
char **argv = (char **) malloc(sizeof(char *) * argc);
// 2.2 填充内容
for (int i = 0; i < argc; ++i) {
jstring j_param = (jstring) env->GetObjectArrayElement(compressParams, i);
const char *c_param = env->GetStringUTFChars(j_param, NULL);
argv[i] = (char *) c_param;
LOGE("数组元素:%s", c_param);
}
// 3. 调用方法压缩
// 开始转码(实现就是命令行一样) 并且处理回调,一种死办法
mEnv = env;
callback_jobj = env->NewGlobalRef(instance);
ffmpegmain(argc, argv,compress_callback);
// 4. 释放资源
for (int i = 0; i < argc; ++i) {
free(argv[i]);
}
free(argv);
// free(mEnv);
env->DeleteGlobalRef(callback_jobj);
}
基于 FFmpeg 的压缩效果是非常好的,但由于采用的是软编码很致命的是压缩时间比较长,肯定有人问怎么才能优化,目前自己能力有限无能为力了,唯一能做的就是把压缩进度回调出去。
更多的 FFmpeg 音视频处理学习可以参考雷霄骅的博客:http://blog.csdn.net/leixiaohua1020
3. 基于开源库 iosparser 压缩
iosparser 主要是基于 android 下的 android.media ,使用起来方便且简单,这里也有个开源库大家可以参考一下:https://github.com/RudreshJR/VideoCompression。速度很不错,就是质量和兼容性有所欠缺。