【Android FFMPEG 开发】FFMPEG ANativeWindow 原生绘制 ( Java 层获取 Surface | 传递画布到本地 | 创建 ANativeWindow )

简介: 【Android FFMPEG 开发】FFMPEG ANativeWindow 原生绘制 ( Java 层获取 Surface | 传递画布到本地 | 创建 ANativeWindow )

文章目录

I . FFMPEG ANativeWindow 原生绘制

II . FFMPEG 原生绘制流程

III . Java 层获取 Surface 画布

IV . 传递 Surface 画布到 Native 层

V . Native 层创建 ANativeWindow 原生绘制窗口



I . FFMPEG ANativeWindow 原生绘制


FFMPEG ANativeWindow 原生绘制前置操作 :



① FFMPEG 初始化 : 参考博客 【Android FFMPEG 开发】FFMPEG 初始化 ( 网络初始化 | 打开音视频 | 查找音视频流 )


② FFMPEG 获取 AVStream 音视频流 : 参考博客 【Android FFMPEG 开发】FFMPEG 获取 AVStream 音视频流 ( AVFormatContext 结构体 | 获取音视频流信息 | 获取音视频流个数 | 获取音视频流 )


③ FFMPEG 获取 AVCodec 编解码器 : 参考博客 【Android FFMPEG 开发】FFMPEG 获取编解码器 ( 获取编解码参数 | 查找编解码器 | 获取编解码器上下文 | 设置上下文参数 | 打开编解码器 )


④ FFMPEG 读取音视频流中的数据到 AVPacket : 参考博客 【Android FFMPEG 开发】FFMPEG 读取音视频流中的数据到 AVPacket ( 初始化 AVPacket 数据 | 读取 AVPacket )


⑤ FFMPEG 解码 AVPacket 数据到 AVFrame : 参考博客 【Android FFMPEG 开发】FFMPEG 解码 AVPacket 数据到 AVFrame ( AVPacket->解码器 | 初始化 AVFrame | 解码为 AVFrame 数据 )


⑥ FFMPEG AVFrame 图像格式转换 YUV -> RGBA : 参考博客 【Android FFMPEG 开发】FFMPEG AVFrame 图像格式转换 YUV -> RGBA ( 获取 SwsContext | 初始化图像数据存储内存 | 图像格式转换 )




II . FFMPEG 原生绘制流程


FFMPEG 解码 AVPacket 数据到 AVFrame 流程 :



〇 前置操作 : FFMPEG 环境初始化 , 获取 AVStream 音视频流 , 获取 AVCodec 编解码器 , 读取音视频流中的数据到 AVPacket , 解码 AVPacket 数据到 AVFrame , AVFrame 图像格式转换 YUV -> RGBA , 然后才能进行下面的操作 ;



① Java 层获取 Surface 对象 : Surface 画布可以在 SurfaceView 的 SurfaceHolder 中获取


//绘制图像的 SurfaceView
SurfaceView surfaceView;
//在 SurfaceView 回调函数中获取
SurfaceHolder surfaceHolder = surfaceView.getHolder() ; 
//获取 Surface 画布
Surface surface = surfaceHolder.getSurface() ;



② 将 Surface 对象传递到 Native 层 : 在 SurfaceHolder.Callback 接口的 surfaceChanged 实现方法中 , 将 Surface 画布传递给 Native 层 ;


@Override

public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    //画布改变 , 横竖屏切换 , 按下 Home 键 , 按下菜单键
    //将 Surface 传到 Native 层 , 在 Native 层绘制图像
    native_set_surface(holder.getSurface());
}
//调用该方法将 Surface 传递到 Native 层
native void native_set_surface(Surface surface);



③ 创建 ANativeWindow : 在 Native 层的 C++ 代码中 , 接收 Surface 画布 , 并创建 ANativeWindow 本地绘制窗口 , 原生绘制主要在 ANativeWindow 中进行 ;


//CPP 中接收 Surface 画布 , 并创建 ANativeWindow
extern "C"
JNIEXPORT void JNICALL
Java_kim_hsl_ffmpeg_Player_native_1set_1surface(JNIEnv *env, jobject instance, jobject surface) {
    // 将从 Java 层传递的 Surface 对象转换成 ANativeWindow 结构体
    //      如果之前已经有了 ANativeWindow 结构体 , 那么先将原来的释放掉
    //释放原来的 ANativeWindow
    if(aNativeWindow){
        ANativeWindow_release(aNativeWindow);
    }
    //转换新的 ANativeWindow
    aNativeWindow = ANativeWindow_fromSurface(env, surface);
}




III . Java 层获取 Surface 画布


1 . Surface 画布 : 这里的 Surface 画布从 SurfaceView 中获得 , SurfaceHolder.Callback 的监听方法中获取 SurfaceHolder 及 Surface ;



2 . 设置 SurfaceHolder 回调函数 : 首先要获取 SurfaceView 的 SurfaceHolder ; 设置 SurfaceHolder 监听回调函数 SurfaceHolder.Callback ;


   

//监听获取画布
        this.surfaceHolder = this.surfaceView.getHolder();
        surfaceHolder.addCallback(this);


3 . 获取 Surface 画布 : 在 surfaceChanged 回调方法中 , 获取 Surface 画布 , 这样可以保证在横竖屏切换时可以实时获取到最新画布 ;


 

@Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        //画布改变 , 横竖屏切换 , 按下 Home 键 , 按下菜单键
        //holder.getSurface() 就是 Surface 画布 ; 
    }


4 . Surface 画布获取 代码示例 :


package kim.hsl.ffmpeg;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
/**
 * Java 层与 Native 层交互 接口
 */
public class Player implements SurfaceHolder.Callback {
    private static final String TAG = "Player";
    // 加载动态库
    static {
        System.loadLibrary("native-lib");
    }
    /**
     * 视频显示组件
     */
    private SurfaceView surfaceView;
    /**
     * 控制 Surface 画布接口
     */
    private SurfaceHolder surfaceHolder;
    ...
    public void setSurfaceView(SurfaceView surfaceView) {
        this.surfaceView = surfaceView;
        //监听获取画布
        this.surfaceHolder = this.surfaceView.getHolder();
        surfaceHolder.addCallback(this);
    }
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        //画布创建
    }
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        //画布改变 , 横竖屏切换 , 按下 Home 键 , 按下菜单键
        //holder.getSurface() 就是 Surface 画布 ; 
    }
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        //画布销毁
    }
}





IV . 传递 Surface 画布到 Native 层


1 . 原生绘制需求 : 在 Native 层使用 C/C++ 进行原生绘制需要将 Surface 画布传递到 Native 层进行绘制 ;



2 . 定义传递方法 : 在 Java 层定义传递 Surface 画布的 Native 方法 ;


native void native_set_surface(Surface surface);

1


3 . 实现 Surface 传递方法 : 在 Native 层实现 Java 中定义的方法 ;



extern "C"

JNIEXPORT void JNICALL

Java_kim_hsl_ffmpeg_Player_native_1set_1surface(JNIEnv *env, jobject instance, jobject surface) {

...

}

1

2

3

4

5

6


4 . 传递 Surface 画布操作 : 在 surfaceChanged 函数中 , 通过调用 SurfaceHolder 的 getSurface ( ) 方法获取 Surface 画布 , 再调用 native_set_surface(holder.getSurface()) , 将画布传递到 Surface 层 ;


   @Override

   public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

       //画布改变 , 横竖屏切换 , 按下 Home 键 , 按下菜单键


       //将 Surface 传到 Native 层 , 在 Native 层绘制图像

       native_set_surface(holder.getSurface());


   }

1

2

3

4

5

6

7

8



V . Native 层创建 ANativeWindow 原生绘制窗口


1 . ANativeWindow 创建 : 在 Native 层实现 Java 中定义的本地方法 native_set_surface ( ) , 在该方法中传入了 Surface 对象作为参数 ; 在该 Native 方法中 , 调用了 ANativeWindow_fromSurface ( ) 方法 , 将 Surface 对象转为了 ANativeWindow 原生绘制窗口 ;



2 . ANativeWindow_fromSurface ( ) 函数原型 :



① JNIEnv* env 参数 : JNI 环境 , 在 JNI 方法中自带 ;


② jobject surface 参数 : Java 层传入的 Surface 对象 ;


/**
 * Return the ANativeWindow associated with a Java Surface object,
 * for interacting with it through native code.  This acquires a reference
 * on the ANativeWindow that is returned; be sure to use ANativeWindow_release()
 * when done with it so that it doesn't leak.
 */
ANativeWindow* ANativeWindow_fromSurface(JNIEnv* env, jobject surface);



3 . Native 层创建 ANativeWindow 原生绘制窗口 代码示例 :


/**
 * 原生绘制窗口
 */
ANativeWindow * aNativeWindow;
extern "C"
JNIEXPORT void JNICALL
Java_kim_hsl_ffmpeg_Player_native_1set_1surface(JNIEnv *env, jobject instance, jobject surface) {
    //加同步锁
    pthread_mutex_lock(&mutex);
    // 将从 Java 层传递的 Surface 对象转换成 ANativeWindow 结构体
    //      如果之前已经有了 ANativeWindow 结构体 , 那么先将原来的释放掉
    //释放原来的 ANativeWindow
    if(aNativeWindow){
        ANativeWindow_release(aNativeWindow);
    }
    //转换新的 ANativeWindow
    aNativeWindow = ANativeWindow_fromSurface(env, surface);
    //解除同步锁
    pthread_mutex_unlock(&mutex);
}


目录
相关文章
|
2月前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
2月前
|
缓存 前端开发 Android开发
安卓开发中的自定义视图:从零到英雄
【10月更文挑战第42天】 在安卓的世界里,自定义视图是一块画布,让开发者能够绘制出独一无二的界面体验。本文将带你走进自定义视图的大门,通过深入浅出的方式,让你从零基础到能够独立设计并实现复杂的自定义组件。我们将探索自定义视图的核心概念、实现步骤,以及如何优化你的视图以提高性能和兼容性。准备好了吗?让我们开始这段创造性的旅程吧!
46 1
|
2月前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
4天前
|
缓存 前端开发 Android开发
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
|
8天前
|
Dart 前端开发 Android开发
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
1月前
|
搜索推荐 前端开发 API
探索安卓开发中的自定义视图:打造个性化用户界面
在安卓应用开发的广阔天地中,自定义视图是一块神奇的画布,让开发者能够突破标准控件的限制,绘制出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战技巧,逐步揭示如何在安卓平台上创建和运用自定义视图来提升用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你的应用在众多同质化产品中脱颖而出。
71 19
|
2月前
|
IDE Java 开发工具
移动应用与系统:探索Android开发之旅
在这篇文章中,我们将深入探讨Android开发的各个方面,从基础知识到高级技术。我们将通过代码示例和案例分析,帮助读者更好地理解和掌握Android开发。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和技巧。让我们一起开启Android开发的旅程吧!
|
1月前
|
JSON Java API
探索安卓开发:打造你的首个天气应用
在这篇技术指南中,我们将一起潜入安卓开发的海洋,学习如何从零开始构建一个简单的天气应用。通过这个实践项目,你将掌握安卓开发的核心概念、界面设计、网络编程以及数据解析等技能。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供一个清晰的路线图和实用的代码示例,帮助你在安卓开发的道路上迈出坚实的一步。让我们一起开始这段旅程,打造属于你自己的第一个安卓应用吧!
75 14
|
1月前
|
Java Linux 数据库
探索安卓开发:打造你的第一款应用
在数字时代的浪潮中,每个人都有机会成为创意的实现者。本文将带你走进安卓开发的奇妙世界,通过浅显易懂的语言和实际代码示例,引导你从零开始构建自己的第一款安卓应用。无论你是编程新手还是希望拓展技术的开发者,这篇文章都将为你打开一扇门,让你的创意和技术一起飞扬。
|
1月前
|
XML 存储 Java
探索安卓开发之旅:从新手到专家
在数字时代,掌握安卓应用开发技能是进入IT行业的关键。本文将引导读者从零基础开始,逐步深入安卓开发的世界,通过实际案例和代码示例,展示如何构建自己的第一个安卓应用。我们将探讨基本概念、开发工具设置、用户界面设计、数据处理以及发布应用的全过程。无论你是编程新手还是有一定基础的开发者,这篇文章都将为你提供宝贵的知识和技能,帮助你在安卓开发的道路上迈出坚实的步伐。
41 5