欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

FFmpeg 编译的so和include在android studio中的使用

程序员文章站 2022-03-21 08:04:32
...

FFmpeg 编译的so和include在android studio中的使用

上一篇博客讲了如何编译ffmpeg,产生适合在android上调用的so库文件和include头文件,下面讲讲如何在android studio中使用这些库文件来进行ndk开发

1、新建android 项目**,

注意勾选支持c/c++,然后将编译好的文件拷贝到libs目录下:
FFmpeg 编译的so和include在android studio中的使用

2、拷贝带版本号的库文件到libs/armeabi目录**(没有就新建)

3、 gradle将jni目录设置到libs****

      //     如果将so文件放在libs文件夹下得加上下面几句
        sourceSets {
        main {
        jniLibs.srcDirs = ['libs']
        }
    }

4.gradle设置 cpp文件目录 ,设置cpu编译库**

        externalNativeBuild {
            cmake {
                cppFlags ""
            }
            ndk{
            abiFilters "armeabi"
        }
        }

这里给出完整的gradle:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.example.lammy.ffmpegdemo"
        minSdkVersion 21
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
            ndk{
            abiFilters "armeabi"
        }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    //     如果将so文件放在libs文件夹下得加上下面几句
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }

    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }

    }



}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

5、配置CMakeLists.txt文件**

  • 1、将libs/include导入
  • 2、将so各个库添加进来(add_library 和 set_target_properties)

这里直接贴出来:

# 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 )

include_directories(libs/include)
#设置so的路径
set(ffmpeg_DIR ${CMAKE_SOURCE_DIR}/libs)
add_library( avcodec-57
             SHARED
             IMPORTED)
set_target_properties( avcodec-57
                       PROPERTIES IMPORTED_LOCATION
                       ${ffmpeg_DIR}/armeabi/libavcodec-57.so)


add_library( avfilter-6
             SHARED
             IMPORTED)
set_target_properties( avfilter-6
                       PROPERTIES IMPORTED_LOCATION
                       ${ffmpeg_DIR}/armeabi/libavfilter-6.so)

add_library( avformat-57
             SHARED
             IMPORTED)
set_target_properties( avformat-57
                       PROPERTIES IMPORTED_LOCATION
                       ${ffmpeg_DIR}/armeabi/libavformat-57.so)

add_library( avutil-55
             SHARED
             IMPORTED)
set_target_properties( avutil-55
                       PROPERTIES IMPORTED_LOCATION
                       ${ffmpeg_DIR}/armeabi/libavutil-55.so)

add_library( swresample-2
             SHARED
             IMPORTED)
set_target_properties( swresample-2
                       PROPERTIES IMPORTED_LOCATION
                       ${ffmpeg_DIR}/armeabi/libswresample-2.so)

add_library( swscale-4
             SHARED
             IMPORTED)
set_target_properties( swscale-4
                       PROPERTIES IMPORTED_LOCATION
                       ${ffmpeg_DIR}/armeabi/libswscale-4.so)



# 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.

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} )

6、编写jni文件和java文件

public class MainActivity extends AppCompatActivity {


    private int permissionRequestCode = 1;
    private String inputFilePath = new File(Environment.getExternalStorageDirectory()+
             "/a-lammytest/","test.mp4").getAbsolutePath();
    private String outputFilePath = new File(Environment.getExternalStorageDirectory()+
         "/a-lammytest/","output_yuv420p.yuv").getAbsolutePath();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        requestPermissions();

    }

    private String permissions[] = new String[]{
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE,

    };


    private void requestPermissions(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            for(int i =0; i < permissions.length ; i ++) {
                if( checkSelfPermission(permissions[0]) == PackageManager.PERMISSION_DENIED){
                    requestPermissions(permissions ,permissionRequestCode );
                    return;
     //         Toast.makeText(this , "请打开"+permissions[0]+"权限",Toast.LENGTH_LONG).show();
                }
            }
            decode(inputFilePath, outputFilePath);
        }else{
            decode(inputFilePath, outputFilePath);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, 
    @NonNull int[] grantResults) {

        if(requestCode ==permissionRequestCode ){

            for(int result:grantResults){
                if(result == PackageManager.PERMISSION_DENIED){
                    return;
                }
            }

            decode(inputFilePath, outputFilePath);

        }
    }
}

public class FFmpegUtil {
    // 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");
    }

    public native static void decode(String input,String output);

}

jni中解码demo参考:https://www.cnblogs.com/CoderTian/p/6651343.html

extern "C" {
//编码
#include "libavcodec/avcodec.h"
//封装格式处理
#include "libavformat/avformat.h"
//像素处理
#include "libswscale/swscale.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 void JNICALL
Java_com_example_lammy_ffmpegdemo_FFmpegUtil_decode(JNIEnv *env, jclass type, jstring input_,
                                                  jstring output_) {
    const char *input = env->GetStringUTFChars(input_, 0);
    const char *output = env->GetStringUTFChars(output_, 0);

    // TODO
    //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);
}

最后注意添加内存卡的访问权限:

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

运行的结果是将mp4格式的视频 解码后编码为 YUV420格式的视频,yuv播发器打开,并且设置号视频参数
1、yuv播发器:YUV Player Deluxe打开:
FFmpeg 编译的so和include在android studio中的使用

2、设置参数为:FFmpeg 编译的so和include在android studio中的使用

代码:这里给出完整代码,欢迎各位老铁去下载。

相关标签: ffmpeg