Android开发Android-NDK/JNIAndroid开发经验谈

NDK开发前奏 - 视频转码压缩

2020-07-03  本文已影响0人  你也不知道

这几次的分享都是基于 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。速度很不错,就是质量和兼容性有所欠缺。

视频讲解地址:https://pan.baidu.com/s/1eTYofQE

上一篇下一篇

猜你喜欢

热点阅读