当前位置: 移动技术网 > IT编程>移动开发>Android > Android Studio通过JNI(CMake方式)集成FFMpeg音视频处理框架

Android Studio通过JNI(CMake方式)集成FFMpeg音视频处理框架

2018年12月27日  | 移动技术网IT编程  | 我要评论

android studio通过jni(cmake方式)集成ffmpeg音视频处理框架。

本ffmpeg的多采用c/c++代码编写的,想要使用ffmpeg提供的库函数,需要将其打包成动态链接库文件。如linux的so库文件,windows上的dll库文件,android的底层是基于linux系统内核的,因此要在android平台上使用ffmpeg框架提供的库函数,需要将其打包成so库文件。而在linux系统编译打包,需要在ffmpeg框架源码的目录下建立linux的批文件shell脚本文件,即可在linux系统运行命令的脚本文件(windows系统为bat文件)。本篇博文介绍如何用jni的方式集成ffmpeg,如果你还了解如何用cmake集成ffmpeg,可以查看android cmake集成jni开发环境和 android jni实现java与c/c++互相调用,以及so库的生成和应用(jni方式调用美图秀秀so)。

本篇文件按如下步骤进行介绍

windows下编译ffmpeg源码,得到so库文件 利用jni将so库集成到android项目中 通过native方法在c++文件中调用ffmpeg框架的库函数

下面我们看看具体的实现步骤

编译ffmpeg,取得so库文件

windows环境下编译ffmpeg,需要下载如下工具和源码

下载安装mingw 下载yasm 下载ffmpeg

下载好ffmpeg源码和mingw工具后,就可以开始编译ffmpeg了

首先需要对源代码中的configure文件进行修改,由于编译出来的动态库文件名的版本号在.so之后(例如 “libaodec.so.5.100.1”),而android平台不能识别这样的文件名,所以需要修改这种文件名。找到ffmpeg文件夹下面的configure文件,做如下修改

slibname_with_major='$(slibname).$(libmajor)'
lib_install_extra_cmd='$$(ranlib) "$(libdir)/$(libname)"'
slib_install_name='$(slibname_with_version)' 
slib_install_links='$(slibname_with_major) $(slibname)'
将其修改成: 
slibname_with_major='$(slibpref)$(fullname)-$(libmajor)$(slibsuf)'
lib_install_extra_cmd='$$(ranlib) "$(libdir)/$(libname)"'
slib_install_name='$(slibname_with_major)'
slib_install_links='$(slibname)'

然后新建一个build_android.sh文件。注意:要根据环境配置前四项,且每行末尾不能有空格

#!/bin/bash 
export tmpdir="c:/users/jacket/desktop/ff" 
ndk=c:/users/jacket/appdata/local/android/sdk/ndk-bundle 
sysroot=$ndk/platforms/android-21/arch-arm/ 
toolchain=$ndk/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64 
function build_one {
./configure \ 
--prefix=$prefix \ 
--enable-shared \ 
--disable-static \
--disable-doc \ 
--disable-ffmpeg \ 
--disable-ffplay \ 
--disable-ffprobe \ 
--disable-ffserver \ 
--disable-avdevice \ --disable-doc \ 
--disable-symver \
--cross-prefix=$toolchain/bin/arm-linux-androideabi- \ 
--target-os=linux \
--arch=arm \
--enable-cross-compile \ 
--sysroot=$sysroot \ 
--extra-cflags="-os -fpic $addi_cflags" \ 
--extra-ldflags="$addi_ldflags" \ 
$additional_configure_flag
make clean
make
make install
}
cpu=arm
prefix=$(pwd)/android/$cpu
addi_cflags="

启动mingw进入ffmpeg源代码对应目录下执行命令脚本
这里写图片描述
chmod ./build_android.sh
./build_android.sh然后就开始编译了

下面开始移植到android studio中

得到so库文件后,在libs目录下新建armeabi和include文件夹,将上面编译得到的android文件夹下对应的armeabi文件夹下的so库文件和include文件夹下的c头文件分别拷贝到libs下的armeabi和include文件夹下。如下图所示:
这里写图片描述

编写cmakelists.txt文件,在其中指明ffmpeg库文件目录

# for more information about using cmake with android studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# sets the minimum version of cmake required to build the native library.

cmake_minimum_required(version 3.4.1)

# 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 them for you.
# gradle automatically packages shared libraries with your apk.

add_library( # sets the name of the library.
native-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 cmake includes system libraries 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 this
# build script, prebuilt third-party libraries, or system libraries.

set(distribution_dir ../../../../libs)

add_library( avcodec-57
shared
imported)
set_target_properties( avcodec-57
properties imported_location
${distribution_dir}/armeabi/libavcodec-57.so)

add_library( avfilter-6
shared
imported)
set_target_properties( avfilter-6
properties imported_location
${distribution_dir}/armeabi/libavfilter-6.so)

add_library( avformat-57
shared
imported)
set_target_properties( avformat-57
properties imported_location
${distribution_dir}/armeabi/libavformat-57.so)

add_library( avutil-55
shared
imported)
set_target_properties( avutil-55
properties imported_location
${distribution_dir}/armeabi/libavutil-55.so)

add_library( swresample-2
shared
imported)
set_target_properties( swresample-2
properties imported_location
${distribution_dir}/armeabi/libswresample-2.so)

add_library( swscale-4
shared
imported)
set_target_properties( swscale-4
properties imported_location
${distribution_dir}/armeabi/libswscale-4.so)

include_directories(libs/include)


target_link_libraries( # specifies the target library.
native-lib
avcodec-57
avfilter-6
avformat-57
avutil-55
swresample-2
swscale-4
# links the target library to the log library
# included in the ndk.
${log-lib}
)

3.编写native方法,在c/c++文件中调用ffmpeg库提供的函数

#include 
#include 
#include 

extern "c" {
//编码
#include "libavcodec/avcodec.h"
//封装格式处理
#include "libavformat/avformat.h"
//像素处理
#include "libswscale/swscale.h"
//视频滤镜
#include "libavfilter/avfilter.h"
}

#define fflogi(format,...) __android_log_print(android_log_info,"ffmpeg",format,##__va_args__);
#define ffloge(format,...) __android_log_print(android_log_error,"ffmpeg",format,##__va_args__);

extern "c"
jniexport jstring jnicall
java_com_jacket_ffmpeg_mainactivity_stringfromjni(
jnienv *env,
jobject /* this */) {
// std::string hello = "hello from c++";
// return env->newstringutf(hello.c_str());
// return env->newstringutf(av_version_info());
return env->newstringutf(avcodec_configuration());
}

extern "c"
jniexport void jnicall
java_com_jacket_ffmpeg_mainactivity_decode(jnienv *env, jclass type, jstring input_,
jstring output_) {
//获取输入输出文件名
const char *input = env->getstringutfchars(input_, 0);
const char *output = env->getstringutfchars(output_, 0);

//1.注册所有组件
av_register_all();

//封装格式上下文,统领全局的结构体,保存了视频文件封装格式的相关信息
avformatcontext *pformatctx = avformat_alloc_context();

//2.打开输入视频文件
if (avformat_open_input(&pformatctx, input, null, null) != 0)
{
ffloge("%s","无法打开输入视频文件");
return;
}

//3.获取视频文件信息
if (avformat_find_stream_info(pformatctx,null) < 0)
{
ffloge("%s","无法获取视频文件信息");
return;
}

//获取视频流的索引位置
//遍历所有类型的流(音频流、视频流、字幕流),找到视频流
int v_stream_idx = -1;
int i = 0;
//number of streams
for (; i < pformatctx->nb_streams; i++)
{
//流的类型
if (pformatctx->streams[i]->codec->codec_type == avmedia_type_video)
{
v_stream_idx = i;
break;
}
}

if (v_stream_idx == -1)
{
ffloge("%s","找不到视频流\n");
return;
}

//只有知道视频的编码方式,才能够根据编码方式去找到解码器
//获取视频流中的编解码上下文
avcodeccontext *pcodecctx = pformatctx->streams[v_stream_idx]->codec;
//4.根据编解码上下文中的编码id查找对应的解码
avcodec *pcodec = avcodec_find_decoder(pcodecctx->codec_id);
if (pcodec == null)
{
ffloge("%s","找不到解码器\n");
return;
}

//5.打开解码器
if (avcodec_open2(pcodecctx,pcodec,null)<0)
{
ffloge("%s","解码器无法打开\n");
return;
}

//输出视频信息
fflogi("视频的文件格式:%s",pformatctx->iformat->name);
fflogi("视频时长:%d", (pformatctx->duration)/1000000);
fflogi("视频的宽高:%d,%d",pcodecctx->width,pcodecctx->height);
fflogi("解码器的名称:%s",pcodec->name);


//准备读取
//avpacket用于存储一帧一帧的压缩数据(h264)
//缓冲区,开辟空间
avpacket *packet = (avpacket*)av_malloc(sizeof(avpacket));

//avframe用于存储解码后的像素数据(yuv)
//内存分配
avframe *pframe = av_frame_alloc();
//yuv420
avframe *pframeyuv = av_frame_alloc();
//只有指定了avframe的像素格式、画面大小才能真正分配内存
//缓冲区分配内存
uint8_t *out_buffer = (uint8_t *)av_malloc(avpicture_get_size(av_pix_fmt_yuv420p, pcodecctx->width, pcodecctx->height));
//初始化缓冲区
avpicture_fill((avpicture *)pframeyuv, out_buffer, av_pix_fmt_yuv420p, pcodecctx->width, pcodecctx->height);

//用于转码(缩放)的参数,转之前的宽高,转之后的宽高,格式等
struct swscontext *sws_ctx = sws_getcontext(pcodecctx->width,pcodecctx->height,pcodecctx->pix_fmt,
pcodecctx->width, pcodecctx->height, av_pix_fmt_yuv420p,
sws_bicubic, null, null, null);
int got_picture, ret;

file *fp_yuv = fopen(output, "wb+");

int frame_count = 0;

//6.一帧一帧的读取压缩数据
while (av_read_frame(pformatctx, packet) >= 0)
{
//只要视频压缩数据(根据流的索引位置判断)
if (packet->stream_index == v_stream_idx)
{
//7.解码一帧视频压缩数据,得到视频像素数据
ret = avcodec_decode_video2(pcodecctx, pframe, &got_picture, packet);
if (ret < 0)
{
ffloge("%s","解码错误");
return;
}

//为0说明解码完成,非0正在解码
if (got_picture)
{
//avframe转为像素格式yuv420,宽高
//2 6输入、输出数据
//3 7输入、输出画面一行的数据的大小 avframe 转换是一行一行转换的
//4 输入数据第一列要转码的位置 从0开始
//5 输入画面的高度
sws_scale(sws_ctx, pframe->data, pframe->linesize, 0, pcodecctx->height,
pframeyuv->data, pframeyuv->linesize);

//输出到yuv文件
//avframe像素帧写入文件
//data解码后的图像像素数据(音频采样数据)
//y 亮度 uv 色度(压缩了) 人对亮度更加敏感
//u v 个数是y的1/4
int y_size = pcodecctx->width * pcodecctx->height;
fwrite(pframeyuv->data[0], 1, y_size, fp_yuv);
fwrite(pframeyuv->data[1], 1, y_size / 4, fp_yuv);
fwrite(pframeyuv->data[2], 1, y_size / 4, fp_yuv);

frame_count++;
fflogi("解码第%d帧",frame_count);
}
}

//释放资源
av_free_packet(packet);
}

fclose(fp_yuv);

av_frame_free(&pframe);

avcodec_close(pcodecctx);

avformat_free_context(pformatctx);

env->releasestringutfchars(input_, input);
env->releasestringutfchars(output_, output);
}

extern "c"
jniexport jstring jnicall
java_com_jacket_ffmpeg_mainactivity_avfilterinfo(
jnienv * env,jobject){
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;
}

return env->newstringutf(info);

}

extern "c"
jniexport jstring jnicall
java_com_jacket_ffmpeg_mainactivity_avcodecinfo(
jnienv * env,jobject){
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,"%sdecode:",info);
} else{
sprintf(info,"%sencode",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;
default:
sprintf(info,"%s(other):",info);
break;
}
sprintf(info,"%s[%10s]\n",info,c_temp->name);
c_temp = c_temp->next;
}
return env->newstringutf(info);
}

extern "c"
jniexport jstring jnicall
java_com_jacket_ffmpeg_mainactivity_avformatinfo(
jnienv * env,jobject){
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,"%sinput:%s\n",info,of_temp->name);
if_temp = if_temp->next;
}
while (of_temp != null){
sprintf(info,"%soutput: %s\n",info,of_temp->name);
of_temp = of_temp->next;
}
return env->newstringutf(info);
}

extern "c"
jniexport jstring jnicall
java_com_jacket_ffmpeg_mainactivity_urlprotocolinfo(
jnienv * env,jobject){
char info[40000] = {0};
av_register_all();

struct urlprotocol *pup = null;
struct urlprotocol **p_temp = &pup;
avio_enum_protocols((void **)p_temp,0);

while ((*p_temp) != null){
sprintf(info,"%sinput: %s\n",info,avio_enum_protocols((void **)p_temp,0));
}
pup = null;
avio_enum_protocols((void **)p_temp,1);
while ((*p_temp) != null){
sprintf(info,"%sinput: %s\n",info,avio_enum_protocols((void **)p_temp,1));
}
return env->newstringutf(info);
}

可以看出,java_com_jacket_ffmepg_mainactivity_stringfromjni()根据宏定义判定了系统类型并且返回了一个字符串。在这里要注意,c语言中的char[]是不能直接对应为java中的string类型的(即jstring)。char[]转换为string需要通过jnienv的newstringutf()函数。为了调用ffmpeg而经过修改后的java_com_jacket_ffmpeg_mainactivity_stringfromjn的源代码如下:

java_com_jacket_ffmpeg_mainactivity_stringfromjni(
        jnienv *env,
        jobject /* this */) {
  return env->newstringutf(avcodec_configuration());
}

在native-lib.cpp文件中调用libavcodec的avcodec_configuration()方法,用于获取ffmpeg的配置信息。该程序会输出ffmpeg类库下列信息

protocol: ffmpeg类库支持的协议 avformat: ffmpeg类库支持的封装格式 avcodec: ffmpeg类库支持的编解码器 avfilter: ffmpeg类库支持的滤镜 configure: ffmpeg类库的配置信息

最后在mainactivity中通过native方法调用c/c++函数

public class mainactivity extends appcompatactivity implements view.onclicklistener
{

    // used to load the 'native-lib' library on application startup.
    static
    {
        system.loadlibrary("native-lib");
//        system.loadlibrary("avcodec-57");
//        system.loadlibrary("avfilter-6");
//        system.loadlibrary("avformat-57");
//        system.loadlibrary("avutil-55");
//        system.loadlibrary("swresample-2");
//        system.loadlibrary("swscale-4");
    }

    private button protocol,format,codec,filter;
    private textview tv_info;

    @override
    protected void oncreate(bundle savedinstancestate)
    {
        super.oncreate(savedinstancestate);
        setcontentview(r.layout.activity_main);
        init();
    }


    private void init() {
        protocol = (button) findviewbyid(r.id.btn_protocol);
        format = (button) findviewbyid(r.id.btn_format);
        codec = (button) findviewbyid(r.id.btn_codec);
        filter = (button) findviewbyid(r.id.btn_filter);
        tv_info = (textview) findviewbyid(r.id.tv_info);

        protocol.setonclicklistener(this);
        format.setonclicklistener(this);
        codec.setonclicklistener(this);
        filter.setonclicklistener(this);
    }

    @override
    public void onclick(view view) {
        switch (view.getid()) {
            case r.id.btn_protocol:
                tv_info.settext(urlprotocolinfo());
                break;
            case r.id.btn_format:
                tv_info.settext(avformatinfo());
                break;
            case r.id.btn_codec:
                tv_info.settext(avcodecinfo());
                break;
            case r.id.btn_filter:
                tv_info.settext(avfilterinfo());
                break;
            default:
                break;
        }
    }

    @override
    public boolean oncreateoptionsmenu(menu menu)
    {
        // inflate the menu; this adds items to the action bar if it is present.
        getmenuinflater().inflate(r.menu.menu_main, menu);
        return true;
    }

    @override
    public boolean onoptionsitemselected(menuitem item)
    {
        // handle action bar item clicks here. the action bar will
        // automatically handle clicks on the home/up button, so long
        // as you specify a parent activity in androidmanifest.xml.
        int id = item.getitemid();

        //noinspection simplifiableifstatement
        if (id == r.id.action_settings)
        {
            return true;
        }

        return super.onoptionsitemselected(item);
    }

    /**
     * a native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native string stringfromjni();

    public native void decode(string input, string output);

    public native string avfilterinfo();

    public native string avcodecinfo();

    public native string avformatinfo();

    public native string urlprotocolinfo();
}

运行结果如下所示:
这里写图片描述

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网