Android NDK ffmpeg 音视频开发实战

发布于:2025-07-22 ⋅ 阅读:(16) ⋅ 点赞:(0)

接入FFmpeg

1.下载FFmpeg 源码

FFmpeg官网地址

2.编译FFmpeg.so库

  • 移动 FFmpeg 源码文件夹至 Android Studio 的 cpp 包下(也可以不移)
  • 在 FFmpeg 文件夹内创建用来编译 .so 库 的 sh脚本

编译脚本是基于以下 Android 各较新版本:

版本
Android SDK 35
NDK 26
CMake 3.6
JDK 11
Gradle 8.11.0
Android Gradle Plugin 8.6.0
#!/usr/bin/env bash
# 声明脚本解释器为 bash

# -------------------- 配置路径 --------------------
# ‼️修改成自己的NDK版本和路径
NDK=/Users/xxx/Library/Android/sdk/ndk/26.1.10909125
# Android NDK 路径

HOST_TAG=darwin-x86_64
# 主机系统标识,macOS 64位

TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/$HOST_TAG
# LLVM 交叉编译工具链路径

SYSROOT=$TOOLCHAIN/sysroot
# sysroot 路径,NDK 中包含标准库等文件的根目录

API=21
# Android API 级别

FFMPEG_SRC=$(pwd)
# 当前工作目录,即 ffmpeg 源码根目录

OUTPUT_ROOT=$FFMPEG_SRC/android-build
# 编译输出目录

ARCHS=("armeabi-v7a" "arm64-v8a" "x86_64")
# 需要编译的架构列表

# -------------------- 函数:编译单个架构 --------------------
build_one() {
  ARCH=$1
  # 接收参数,架构名称

  echo "==================== 编译架构: $ARCH ===================="
  # 打印当前编译的架构,方便查看进度

  case $ARCH in
    armeabi-v7a)
      TARGET=armv7a-linux-androideabi
      # armeabi-v7a 架构对应的目标三元组
      ;;
    arm64-v8a)
      TARGET=aarch64-linux-android
      # arm64-v8a 架构对应的目标三元组
      ;;
    x86_64)
      TARGET=x86_64-linux-android
      # x86_64 架构对应的目标三元组
      ;;
    *)
      echo "❌ 未知架构: $ARCH"
      exit 1
      # 如果传入架构不在已知列表,退出脚本
      ;;
  esac

  CC="$TOOLCHAIN/bin/clang --target=${TARGET}${API} --sysroot=$SYSROOT"
  # 定义 C 编译器,带上目标三元组和 API 版本,指定 sysroot

  CXX="$TOOLCHAIN/bin/clang++ --target=${TARGET}${API} --sysroot=$SYSROOT"
  # 定义 C++ 编译器,参数同上

  PREFIX=$OUTPUT_ROOT/$ARCH
  # 该架构编译后文件的安装目录

  mkdir -p $PREFIX
  # 创建安装目录,若不存在则新建

  cd $FFMPEG_SRC
  # 进入 ffmpeg 源码目录,准备开始编译

  make clean
  # 清理之前的编译结果,避免干扰

  ./configure \
  --prefix=$PREFIX \
  --target-os=android \
  --arch=$ARCH \
  --enable-cross-compile \
  --cc="$CC" \
  --cxx="$CXX" \
  --sysroot=$SYSROOT \
  --enable-shared \
  --disable-static \
  --disable-doc \
  --disable-programs \
  --disable-symver \
  --disable-debug \
  --disable-asm \
  --extra-cflags="-Os -fPIC" \
  --extra-ldflags=""
  # 调用 ffmpeg 的 configure 脚本,配置编译选项:
  # --prefix:安装路径
  # --target-os=android:目标操作系统为 Android
  # --arch:目标架构
  # --enable-cross-compile:启用交叉编译
  # --cc 和 --cxx:指定 C 和 C++ 编译器
  # --sysroot:指定 sysroot 路径
  # --enable-shared:生成动态库(so)
  # --disable-static:不生成静态库
  # --disable-doc:不生成文档
  # --disable-programs:不编译 ffmpeg 命令行工具
  # --disable-symver:关闭符号版本控制
  # --disable-debug:关闭调试
  # --disable-asm:禁用汇编优化(可根据需求开启)
  # --extra-cflags:额外的编译参数,此处优化大小且使用 PIC
  # --extra-ldflags:额外的链接参数,当前为空

  if [ $? -ne 0 ]; then
    echo "❌ 配置失败: $ARCH"
    exit 1
  fi
  # 如果 configure 出错,则打印失败信息并退出

  make -j$(sysctl -n hw.ncpu)
  # 多线程编译,线程数为当前 CPU 核数

  make install
  # 安装编译结果到指定目录
}

# -------------------- 循环编译所有架构 --------------------
for ARCH in "${ARCHS[@]}"; do
  build_one $ARCH
  # 按顺序调用函数,编译每个架构
done

echo "==================== 全部架构编译完成 ===================="
# 全部架构编译结束,打印提示

  • 编译成功后在路径下可以看到生成的 .so 库和 .h 头文件

cpp/ffmpeg-7.1.1/android-build/arm64-v8a/lib .so库位置
cpp/ffmpeg-7.1.1/android-build/arm64-v8a/include .h 头文件

一共输出7个 .so库:

so库 功能
libavcodec.so 编解码器核心库
libavformat.so 负责封装/解封装(容器格式,如 mp4、mkv)
libavutil.so 工具库(数学、字节序、日志等)
libswscale.so 视频像素格式转换(YUV ↔ RGB 等)
libswresample.so 音频重采样、通道布局转换
libavfilter.so 滤镜处理(裁剪、特效等)
libavdevice.so 设备输入输出(通常可以不用)

编译.so库完成。

异常处理

  • 报错:nasm/yasm not found or too old. Use --disable-x86asm for a crippled build.
    • 安装 nasm

      		brew install nasm   
      
  • 报错:C compiler test failed.
    • 在系统NDK路径确认 clang 编译路径的有效性
      $NDK/toolchains/llvm/prebuilt/darwin-arm64/bin/clang
      

3.自定义FFmpeg交互so库创建

  • 创建一个Java类,自定义调用 native 层的方法

    package com.example.video;
    
    public class FFmpegLoader {
    
    	//自定义方法,返回FFmpeg的版本,判断接入成功
    	public native String getFFmpegVersion();
    	
     static {
      	  //自定义so库名
        	System.loadLibrary("native-lib-myFFmpeg");
    	}
    }
    
  • 通过命令行在当前路径中生成 .h 文件,再放入cpp文件夹中

    javac -h ./jni FFmpegLoader.java
    

    .h文件内容:

    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class com_example_video_FFmpegLoader */
    
    #ifndef _Included_com_example_video_FFmpegLoader
    #define _Included_com_example_video_FFmpegLoader
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     com_example_video_FFmpegLoader
     * Method:    getFFmpegVersion
     * Signature: ()V
     */
    JNIEXPORT void JNICALL 	Java_com_example_video_FFmpegLoader_getFFmpegVersion
      (JNIEnv *, jobject);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    

    移入路径:cpp/com_example_video_FFmpegLoader.h

  • 在同一cpp路径下,创建 cpp 文件,会有 #include <libavcodec/avcodec.h> 等文件报红,是因为没有配置CMakeLists.txt 链接,可以先不管,或者先注释掉 FFmpge 库的引用,编译完之后再调用。

    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class com_example_video_FFmpegLoader */
    #include <string>
    extern "C" {
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    #include <libavutil/avutil.h>
    }
    
    #ifndef _Included_com_example_video_FFmpegLoader
    #define _Included_com_example_video_FFmpegLoader
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     com_example_video_FFmpegLoader
     * Method:    getFFmpegVersion
     * Signature: ()V
     */
    JNIEXPORT jstring JNICALL 	Java_com_example_video_FFmpegLoader_getFFmpegVersion
      (JNIEnv *env, jobject){
     const char *version = av_version_info();
     return env->NewStringUTF(version);
    }
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    

4.配置CMakeLists.txt

  • 外部so库调用 :将编译成功的 so 库(路径:cpp/ffmpeg-7.1.1/android-build/arm64-v8a/lib),集中存放到 jni 文件夹下(/src/main/jni)。并且根据 不同架构分别存放,用于之后在CMakeList.txt 中可以动态链接到这些so库。

    根据 jni 文件夹相对 CMakeLists.txt 文件的位置,找到各 so 库,通过外部引入。

    # 引入 FFmpeg 的 so 库
    # 1.编解码器核心库
    add_library(avcodec SHARED IMPORTED)
    set_target_properties(avcodec PROPERTIES IMPORTED_LOCATION
        ${CMAKE_SOURCE_DIR}/../jni/${ANDROID_ABI}/libavcodec.so)
    
  • 添加头文件目录:让自定义 cpp 文件,可以通过头文件链接到其他 so 库

    	# 添加头文件目录
    	include_directories(${CMAKE_SOURCE_DIR}/include)
    
  • 将自定义 cpp 文件(native-lib-myFFmpeg 库)与FFmpeg 库 链接到一起,使得我的 native 方法可以调用到 FFmpage 的内容。

完整代码:

cmake_minimum_required(VERSION 3.6)
project(ffmpeg_test)

# 添加头文件目录
include_directories(${CMAKE_SOURCE_DIR}/include)

# 引入 FFmpeg 的 so 库
# 1.编解码器核心库
add_library(avcodec SHARED IMPORTED)
set_target_properties(avcodec PROPERTIES IMPORTED_LOCATION
        ${CMAKE_SOURCE_DIR}/../jni/${ANDROID_ABI}/libavcodec.so)

#2.负责封装/解封装(容器格式,如 mp4、mkv)
add_library(avformat SHARED IMPORTED)
set_target_properties(avformat PROPERTIES IMPORTED_LOCATION
        ${CMAKE_SOURCE_DIR}/../jni/${ANDROID_ABI}/libavformat.so)

#3.工具库(数学、字节序、日志等)
add_library(avutil SHARED IMPORTED)
set_target_properties(avutil PROPERTIES IMPORTED_LOCATION
        ${CMAKE_SOURCE_DIR}/../jni/${ANDROID_ABI}/libavutil.so)

#4.视频像素格式转换(YUV ↔ RGB 等)
add_library(swscale SHARED IMPORTED)
set_target_properties(swscale PROPERTIES IMPORTED_LOCATION
        ${CMAKE_SOURCE_DIR}/../jni/${ANDROID_ABI}/libswscale.so)

#5.音频重采样、通道布局转换
add_library(swresample SHARED IMPORTED)
set_target_properties(swresample PROPERTIES IMPORTED_LOCATION
        ${CMAKE_SOURCE_DIR}/../jni/${ANDROID_ABI}/libswresample.so)

#6.滤镜处理(裁剪、特效等)
add_library(avfilter SHARED IMPORTED)
set_target_properties(avfilter PROPERTIES IMPORTED_LOCATION
        ${CMAKE_SOURCE_DIR}/../jni/${ANDROID_ABI}/libavfilter.so)


# 添加你的本地库
add_library(native-lib-myFFmpeg SHARED native-lib-myFFmpeg.cpp)

# 链接 FFmpeg 库
target_link_libraries(
        native-lib-myFFmpeg
        avcodec
        avformat
        avutil
        swscale
        swresample
        avfilter
        android
       ${log-lib}
)

5.CMakeLists.txt 环境配置

配置如下三点:

  • CMakeLists.txt 路径
  • ndk 编译架构
  • C++编译语言
android {
    defaultConfig {
       🧠 externalNativeBuild{
            cmake{
                cppFlags("")
            }
        }
       🧠 ndk{
            abiFilters += listOf("armeabi-v7a","x86_64","arm64-v8a")
        }
    }
   🧠  externalNativeBuild{
       cmake {
            path = file("src/main/cpp/CMakeLists.txt")
        }
    }
}

build 项目,生成自定义的 native-lib-myFFmpeg.so 文件,路径:

build/intermediates/cxx/Debug/48u475k3/obj/armeabi-v7a/libnative-lib-myFFmpeg.so

6.Native与Java层调用

        TextView tv = findViewById(R.id.video_text);
        FFmpegLoader fFmpegLoader = new FFmpegLoader();
        tv.setText(fFmpegLoader.getFFmpegVersion());

显示FFmpeg的版本,完成FFmpeg 接入。


解码器准备

native-lib-myFFmpeg.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_video_FFmpegLoader */

#ifndef _Included_com_example_video_FFmpegLoader
#define _Included_com_example_video_FFmpegLoader
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_video_FFmpegLoader
 * Method:    getFFmpegVersion
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_video_SimplePlayer_getFFmpegVersion
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

native-lib-myFFmpeg.cpp

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_video_FFmpegLoader */
#include <string>
#include "NativeSimplePlayer.h"
#include "JNICallbackHelper.h"

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
}

#ifndef _Included_com_example_video_FFmpegLoader
#define _Included_com_example_video_FFmpegLoader
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_video_FFmpegLoader
 * Method:    getFFmpegVersion
 * Signature: ()V
 */
JNIEXPORT jstring JNICALL Java_com_example_video_SimplePlayer_getFFmpegVersion
  (JNIEnv *env, jobject){
    const char *version = av_version_info();
    return env->NewStringUTF(version);
}

#ifdef __cplusplus
}
#endif
#endif

//虚拟机可以跨线程
JavaVM  *vm = 0;
jint JNI_OnLoad(JavaVM *vm,void *args){
    ::vm = vm;
    return JNI_VERSION_1_6;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_video_SimplePlayer_prepareNative(JNIEnv *env, jobject job, jstring data_source) {
    const char* data_source_ = env->GetStringUTFChars(data_source,0);
    //可能是主线程,也可能是子线程
    auto *helper = new JNICallbackHelper(vm,env,job);
    auto *player = new NativeSimplePlayer(data_source_,helper);
    player->prepare();
    env->ReleaseStringUTFChars(data_source,data_source_);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_video_SimplePlayer_startNative(JNIEnv *env, jobject thiz) {
    // TODO: implement startNative()
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_video_SimplePlayer_stopNative(JNIEnv *env, jobject thiz) {
    // TODO: implement stopNative()
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_video_SimplePlayer_releaseNative(JNIEnv *env, jobject thiz) {
    // TODO: implement releaseNative()
}

NativeSimplePlayer.cpp

//
// Created on 2025/7/22.
//

#include <string.h>
#include "NativeSimplePlayer.h"

NativeSimplePlayer::NativeSimplePlayer(const char *data_source_,JNICallbackHelper *helper) {
    //如果被释放,会造成悬空指针
    //this->data_source = data_source_;

    //深拷贝
    //C层:demo.mp4\0 C层会自动 + \0 ,strlen 不计算 \0 的长度
    this->data_source = new char[strlen(data_source_) + 1];
    strcpy(this->data_source,data_source_);

    this->helper = helper;
}

NativeSimplePlayer::~NativeSimplePlayer() {
    if( data_source ){
        delete data_source;
    }

    if( helper){
        delete helper;
    }
}

void* task_prepare(void * args){
    //读取文件
    auto *player = static_cast<NativeSimplePlayer *>(args);
    player->prepare_();
    return 0;//必须返回,错误很难找
}

void NativeSimplePlayer::prepare_() {
    //为什么FFmpeg源码,大量使用上下文 Context
    //因为FFmpge源码是纯C,他没有对象,只能上下文贯穿环境,操作成员变量


    /**
     * 第一步,打开媒体文件地址
     * @parm AVFormatContext  -> formatContext 上下文
     * @parm filename         -> data_source 路径
     * @parm AVInputFormat    -> *fmt Mac、Windows 摄像头、麦克风
     * @param AVDictionary    -> 设置Http连接超时,打开rtmp超时
     */
     formatContext = avformat_alloc_context();
     AVDictionary  *dictionary = 0;
     av_dict_set(&dictionary,"timeout","5000000",0);
     int result = avformat_open_input(&formatContext,data_source,0,&dictionary);

     //释放字典
     av_dict_free(&dictionary);

     if(result){
         //回调错误信息给Java,通过JNI反射
         return;
     }

     /**
      * 第二步,查找媒体中的音视频流信息
      */
    result = avformat_find_stream_info(formatContext,0);
    if( result < 0 ){
        return;
    }

    /**
     * 第三步,根据流信息,流的个数,用循环来找
     */

//    for(int i = 0;i < formatContext->nb_streams;++i){
    for(int i = 0;i < 2;++i){

        /**
         * 第四步,获取媒体流(视频,音频)
         */
         AVStream *stream = formatContext->streams[i];

         /**
          * 第五步,从上面的流中 获取 编码解码的参数
          * 由于后面的解码器 编码器 都需要参数(记录的宽高)
          */
          AVCodecParameters *parameters = stream->codecpar;

          /**
           * 第六步,获取编/解码器,根据上面的参数👆
           */
         const AVCodec *codec = avcodec_find_decoder(parameters->codec_id);

         /**
          * 第七步,编解码器 上下文【真正干活】
          */
          AVCodecContext *codecContext = avcodec_alloc_context3(codec);
          if( !codecContext ){
              return;
          }

          /**
           * 第八步,他目前是一张白纸
           * 把 parameter 拷贝给=> codecContext
           */
        result = avcodec_parameters_to_context(codecContext,parameters);
        if( result < 0){
            return;
        }

        /**
         * 第九步,打开解码器
         */
        result = avcodec_open2(codecContext,codec,0);
        if( result ){
            return;
        }

        /**
         * 第十步,从编解码器从,获取流的类型 codec_type 决定是音频还是视频
         */
        if( parameters->codec_type == AVMediaType::AVMEDIA_TYPE_AUDIO){
            //分开音频
            audio_channel = new AudioChannel();
        } else if(parameters->codec_type == AVMediaType::AVMEDIA_TYPE_VIDEO){
            //分开视频
            video_channel = new VideoChannel();
        }
    }

    /**
     * 第十一步,如果流中没有音频,也没有视频 【健壮性校验】
     */
     if( !audio_channel && !video_channel){
         return;
     }

     /**
      * 第十二步,恭喜你,准备成功,媒体准备完成,通知上层
      */

     if(helper){
         helper->onPrepared(THREAD_CHILD);
     }
}




void NativeSimplePlayer::prepare() {
    //此时为Activity调用到的,所以为主线程

    //解封装 FFmpeg 来解析,要使用子线程
    pthread_create(&pid_prepare,0,task_prepare,this);

}

NativeSimplePlayer.h

//
// Created on 2025/7/22.
//

#ifndef COROUTINE_NATIVESIMPLEPLAYER_H
#define COROUTINE_NATIVESIMPLEPLAYER_H

#include <cstring>
#include <pthread.h>
#include "AudioChannel.h"
#include "VideoChannel.h"
#include "JNICallbackHelper.h"
#include "AudioChannel.h"
#include "VideoChannel.h"
#include "util.h"


extern "C"{ //ffmpeg 是纯C写的,必须采用C的编译方式,否则崩溃
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"

};

class NativeSimplePlayer {
private:
    char  *data_source = 0;//指针需要初始值
    pthread_t pid_prepare;
    AVFormatContext  *formatContext = 0;
    AudioChannel *audio_channel = 0;
    VideoChannel *video_channel = 0;
    JNICallbackHelper *helper =0 ;
public:
    NativeSimplePlayer(const char *data_source,JNICallbackHelper *helper);

    ~NativeSimplePlayer();

    void prepare();

    void prepare_();
};

#endif //COROUTINE_NATIVESIMPLEPLAYER_H

JNICallbackHelper.h Native层调用Java层

//
// Created on 2025/7/22.
//

#ifndef COROUTINE_JNICALLBACKHELPER_H
#define COROUTINE_JNICALLBACKHELPER_H


#include <jni.h>
#include "util.h"


class JNICallbackHelper {
private:
    JavaVM *vm = 0;
    JNIEnv *env = 0;
    jobject job;
    jmethodID jmd_prepared;

public:
    JNICallbackHelper(_JavaVM *vm, _JNIEnv *env, jobject jobject);

    virtual ~JNICallbackHelper();

    void onPrepared(int thread_mode);
};


#endif //COROUTINE_JNICALLBACKHELPER_H

JNICallbackHelper.cpp

//
// Created on 2025/7/22.
//

#include "JNICallbackHelper.h"

JNICallbackHelper::JNICallbackHelper(_JavaVM *vm, _JNIEnv *env, jobject jobject) {
    this->vm = vm;
    this->env = env;
    //全局引用
    this->job = env->NewGlobalRef(jobject);
    jclass claz = env->GetObjectClass(jobject);
    //🌟这里没有赋值
    jmd_prepared = env->GetMethodID(claz,"onPrepared","()V");
}

JNICallbackHelper::~JNICallbackHelper() {
    vm = 0;
    env->DeleteGlobalRef(job);
    job = 0;
    env = 0;
}

void JNICallbackHelper::onPrepared(int thread_mode) {
    if( thread_mode == THREAD_MAIN){
        env->CallVoidMethod(job,jmd_prepared);
    } else if (thread_mode == THREAD_CHILD){
        //子线程不可以跨线程,要用全新的env
        JNIEnv  *env_child;
        vm->AttachCurrentThread(&env_child,0);
        env_child->CallVoidMethod(job,jmd_prepared);
        vm->DetachCurrentThread();
    }
}

util.h

//
// Created on 2025/7/22.
//

#ifndef COROUTINE_UTIL_H
#define COROUTINE_UTIL_H

#define THREAD_MAIN 1
#define THREAD_CHILD 2


#endif //COROUTINE_UTIL_H

CMakeLists.txt 添加调用的 cpp

# 添加你的本地库
add_library(native-lib-myFFmpeg
        SHARED
        native-lib-myFFmpeg.cpp
        JNICallbackHelper.cpp
        VideoChannel.cpp
        AudioChannel.cpp
        NativeSimplePlayer.cpp)

Jave层调用类

package com.example.video;


public class SimplePlayer {

    public native String getFFmpegVersion();

    static {
        System.loadLibrary("native-lib-myFFmpeg");
    }


    public SimplePlayer() {

    }


    private String dataSource;

    private OnPreparedListener onPreparedListener;

    public void setDataSource(String dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * 播放器的准备工作
     */
    public void prepare(){
        prepareNative(dataSource);
    }

    /**
     * 开始播放
     */
    public void start(){
        startNative();
    }

    /**
     * 停止播放
     */
    public void stop(){
        stopNative();
    }

    /**
     * 释放资源
     */
    public void release(){
        releaseNative();
    }


    public void onPrepared(){
        if( onPreparedListener != null){
            onPreparedListener.onPrepared();
        }
    }


    public void setOnPreparedListener(OnPreparedListener listener){
        onPreparedListener = listener;
    }

    public interface OnPreparedListener{
        void onPrepared();
    }

    /**
     * ======================= native 函数区 ==========================
     */

    private native void prepareNative(String dataSource);
    private native void startNative();
    private native void stopNative() ;
    private native void releaseNative() ;

}

Activity 准备视频编码器完成

package com.example.video;

import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import java.io.File;

public class VideoActivity extends AppCompatActivity {

    private SimplePlayer mPlayer;
    public static final String TAG = VideoActivity.class.getName();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_video);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        TextView tv = findViewById(R.id.video_text);

        //tv.setText(mPlayer.getFFmpegVersion());
        initPlayer();
    }


    private void initPlayer(){
        mPlayer = new SimplePlayer();
        File videFile = new File(Environment.getExternalStorageDirectory()
                + File.separator + "Download/Mcloud.mp4");
        mPlayer.setDataSource(videFile.getAbsolutePath());
        //准备成功的回调——C++子线程调用
        mPlayer.setOnPreparedListener(new SimplePlayer.OnPreparedListener() {
            @Override
            public void onPrepared() {
                Log.d(TAG, "onPrepared: 准备完成");
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(VideoActivity.this,"准备完成,即将播放",Toast.LENGTH_SHORT).show();
                    }
                });
                mPlayer.start();
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        //触发
        mPlayer.prepare();
    }

    @Override
    protected void onStop() {
        super.onStop();
        mPlayer.stop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mPlayer.release();
    }
}

网站公告

今日签到

点亮在社区的每一天
去签到