文章目录
I . FFMPEG ANativeWindow 原生绘制 前置操作
代码语言:txt复制 - [II . FFMPEG 原生绘制流程](https://cloud.tencent.com/developer)
- [III . 设置 ANativeWindow 绘制窗口属性 ANativeWindow_setBuffersGeometry ( )](https://cloud.tencent.com/developer)
- [IV . 获取 ANativeWindow 原生绘制的 ANativeWindow_Buffer 绘制缓冲区](https://cloud.tencent.com/developer)
- [V . 填充图像图像数据到 ANativeWindow_Buffer 绘制缓冲区](https://cloud.tencent.com/developer)
- [VI . 启动绘制](https://cloud.tencent.com/developer)
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 | 初始化图像数据存储内存 | 图像格式转换 )
⑦ FFMPEG ANativeWindow 原生绘制 准备 : 参考博客 【Android FFMPEG 开发】FFMPEG ANativeWindow 原生绘制 ( Java 层获取 Surface | 传递画布到本地 | 创建 ANativeWindow )
II . FFMPEG 原生绘制流程
FFMPEG 解码 AVPacket 数据到 AVFrame 流程 :
〇 前置操作 : FFMPEG 环境初始化 , 获取 AVStream 音视频流 , 获取 AVCodec 编解码器 , 读取音视频流中的数据到 AVPacket , 解码 AVPacket 数据到 AVFrame , AVFrame 图像格式转换 YUV -> RGBA , ANativeWindow 原生绘制 准备工作 , 然后才能进行下面的操作 ;
① Java 层获取 Surface 对象 ( 上一篇博客讲解 ) : Surface 画布可以在 SurfaceView 的 SurfaceHolder 中获取
代码语言:javascript复制//绘制图像的 SurfaceView
SurfaceView surfaceView;
//在 SurfaceView 回调函数中获取
SurfaceHolder surfaceHolder = surfaceView.getHolder() ;
//获取 Surface 画布
Surface surface = surfaceHolder.getSurface() ;
② 将 Surface 对象传递到 Native 层 ( 上一篇博客讲解 ) : 在 SurfaceHolder.Callback 接口的 surfaceChanged 实现方法中 , 将 Surface 画布传递给 Native 层 ;
代码语言:javascript复制@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 中进行 ;
代码语言:javascript复制//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);
}
④ 设置 ANativeWindow 绘制缓冲区属性 : ANativeWindow_setBuffersGeometry ( )
代码语言:javascript复制//设置 ANativeWindow 绘制窗口属性
// 传入的参数分别是 : ANativeWindow 结构体指针 , 图像的宽度 , 图像的高度 , 像素的内存格式
ANativeWindow_setBuffersGeometry(aNativeWindow, width, height, WINDOW_FORMAT_RGBA_8888);
⑤ 获取 ANativeWindow_Buffer 绘制缓冲区 : ANativeWindow_lock ( )
代码语言:javascript复制//获取 ANativeWindow_Buffer , 如果获取失败 , 直接释放相关资源退出
ANativeWindow_Buffer aNativeWindow_Buffer;
//如果获取成功 , 可以继续向后执行 , 获取失败 , 直接退出
if(ANativeWindow_lock(aNativeWindow, &aNativeWindow_Buffer, 0)){
//退出操作 , 释放 aNativeWindow 结构体指针
ANativeWindow_release(aNativeWindow);
aNativeWindow = 0;
return;
}
⑥ 填充图像数据到 ANativeWindow_Buffer 绘制缓冲区中 : 将图像字节数据使用内存拷贝到 ANativeWindow_Buffer 结构体的 bits 字段中 ;
代码语言:javascript复制//向 ANativeWindow_Buffer 填充 RGBA 像素格式的图像数据
uint8_t *dst_data = static_cast<uint8_t *>(aNativeWindow_Buffer.bits);
//参数中的 uint8_t *data 数据中 , 每一行有 linesize 个 , 拷贝的目标也要逐行拷贝
// aNativeWindow_Buffer.stride 是每行的数据个数 , 每个数据都包含一套 RGBA 像素数据 ,
// RGBA 数据每个占1字节 , 一个 RGBA 占 4 字节
// 每行的数据个数 * 4 代表 RGBA 数据个数
int dst_linesize = aNativeWindow_Buffer.stride * 4;
//获取 ANativeWindow_Buffer 中数据的地址
// 一次拷贝一行 , 有 像素高度 行数
for(int i = 0; i < aNativeWindow_Buffer.height; i ){
//计算拷贝的指针地址
// 每次拷贝的目的地址 : dst_data ( i * dst_linesize )
// 每次拷贝的源地址 : data ( i * linesize )
memcpy(dst_data ( i * dst_linesize ), data ( i * linesize ), dst_linesize);
}
⑦ 启动绘制 : ANativeWindow_unlockAndPost ( )
代码语言:javascript复制//启动绘制
ANativeWindow_unlockAndPost(aNativeWindow);
III . 设置 ANativeWindow 绘制窗口属性 ANativeWindow_setBuffersGeometry ( )
1 . 绘制窗口属性设置 : 在绘制图像之前 , 首先要设置绘制的 宽度 , 高度 , 绘制像素格式 ( ARGB ) , 调用 ANativeWindow_setBuffersGeometry ( ) 方法 , 即可为 ANativeWindow 设置上述绘制参数 ;
2 . ANativeWindow_setBuffersGeometry ( ) 函数原型 : 设置这些属性目的是修改绘制缓冲区参数 ;
① ANativeWindow* window 参数 : 进行原生绘制的 ANativeWindow 结构体指针 ;
② int32_t width 参数 : 缓冲区存储的图像数据的像素宽度 ;
③ int32_t height 参数 : 存储数据的像素高度 ;
④ int32_t format 参数 : 缓冲区中的图片像素格式 , 这里是 ARGB 格式的 ;
代码语言:javascript复制/**
* Change the format and size of the window buffers.
*
* The width and height control the number of pixels in the buffers, not the
* dimensions of the window on screen. If these are different than the
* window's physical size, then its buffer will be scaled to match that size
* when compositing it to the screen. The width and height must be either both zero
* or both non-zero.
*
* For all of these parameters, if 0 is supplied then the window's base
* value will come back in force.
*
* param width width of the buffers in pixels.
* param height height of the buffers in pixels.
* param format one of the AHardwareBuffer_Format constants.
* return 0 for success, or a negative value on error.
*/
int32_t ANativeWindow_setBuffersGeometry(ANativeWindow* window,
int32_t width, int32_t height, int32_t format);
3 . ANativeWindow_setBuffersGeometry ( ) 设置 ANativeWindow 绘制窗口属性 代码示例 :
代码语言:javascript复制//设置 ANativeWindow 绘制窗口属性
// 传入的参数分别是 : ANativeWindow 结构体指针 , 图像的宽度 , 图像的高度 , 像素的内存格式
ANativeWindow_setBuffersGeometry(aNativeWindow, width, height, WINDOW_FORMAT_RGBA_8888);
IV . 获取 ANativeWindow 原生绘制的 ANativeWindow_Buffer 绘制缓冲区
1 . ANativeWindow_Buffer 缓冲区 : 每个 ANativeWindow 都对应着一个 ANativeWindow_Buffer 绘制缓冲区 , 只要将图像数据写入到该缓冲区中 , 再启动绘制 , 就可以将图像绘制到 ANativeWindow 中 , 即 Surface 所在的 SurfaceView 中 ; 调用 ANativeWindow_lock ( ) 方法可以获取该绘制缓冲区 ;
2 . ANativeWindow_lock ( ) 函数原型 : 锁定画布 , 获取 ANativeWindow 对应的 ANativeWindow_Buffer 缓冲区 ;
代码语言:javascript复制/**
* Lock the window's next drawing surface for writing.
* inOutDirtyBounds is used as an in/out parameter, upon entering the
* function, it contains the dirty region, that is, the region the caller
* intends to redraw. When the function returns, inOutDirtyBounds is updated
* with the actual area the caller needs to redraw -- this region is often
* extended by {@link ANativeWindow_lock}.
*
* return 0 for success, or a negative value on error.
*/
int32_t ANativeWindow_lock(ANativeWindow* window, ANativeWindow_Buffer* outBuffer,
ARect* inOutDirtyBounds);
3 . 获取 ANativeWindow 原生绘制的 ANativeWindow_Buffer 绘制缓冲区 代码示例 :
代码语言:javascript复制//获取 ANativeWindow_Buffer , 如果获取失败 , 直接释放相关资源退出
ANativeWindow_Buffer aNativeWindow_Buffer;
//如果获取成功 , 可以继续向后执行 , 获取失败 , 直接退出
if(ANativeWindow_lock(aNativeWindow, &aNativeWindow_Buffer, 0)){
//退出操作 , 释放 aNativeWindow 结构体指针
ANativeWindow_release(aNativeWindow);
aNativeWindow = 0;
return;
}
V . 填充图像图像数据到 ANativeWindow_Buffer 绘制缓冲区
转换好的图像数据 : 在博客 【Android FFMPEG 开发】FFMPEG AVFrame 图像格式转换 YUV -> RGBA ( 获取 SwsContext | 初始化图像数据存储内存 | 图像格式转换 ) _ VI . FFMPEG 初图像格式转换 章节进行了图像格式转换 , 转换后的图像格式是 ARGB 格式的 , 得到了一个指针数组 , 和 行数数组 , 其中只用到了上面两个数组的第 0 个元素 , 即绘制使用一个指针 和 每行字节数 ; 下面是得到的源数据信息 : 指针就是 dst_data0 , 每行的字节数是 dst_linesize0 , 只用到这两个数据 ;
代码语言:javascript复制//指针数组 , 数组中存放的是指针
uint8_t *dst_data[4];
//普通的 int 数组
int dst_linesize[4];
//初始化 dst_data 和 dst_linesize , 为其申请内存 , 注意使用完毕后需要释放内存
av_image_alloc(dst_data, dst_linesize,
avCodecContext->width, avCodecContext->height, AV_PIX_FMT_RGBA,
1);
//3 . 格式转换
sws_scale(
//SwsContext *swsContext 转换上下文
swsContext,
//要转换的数据内容
avFrame->data,
//数据中每行的字节长度
avFrame->linesize,
0,
avFrame->height,
//转换后目标图像数据存放在这里
dst_data,
//转换后的目标图像行数
dst_linesize
);
————————————————
版权声明:本文为CSDN博主「韩曙亮」的原创文章,遵循 CC 4.0 BY-NC-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://hanshuliang.blog.csdn.net/article/details/104772549
2 . 内存拷贝 : 拷贝时逐行拷贝 , 每一行都有 dst_linesize0 字节数据 , 行数是 ANativeWindow_Buffer 结构体中的 height 元素值 , 每行的大小是 ANativeWindow_Buffer 结构体的 stride * 4 字节 , stride 代表像素个数 , 乘以四表示 每个像素有 ARGB 四个字节数据 ;
3 . 逐行拷贝代码示例 :
代码语言:javascript复制//1 . 向 ANativeWindow_Buffer 填充 RGBA 像素格式的图像数据
uint8_t *dst_data = static_cast<uint8_t *>(aNativeWindow_Buffer.bits);
//2 . 参数中的 uint8_t *data 数据中 , 每一行有 linesize 个 , 拷贝的目标也要逐行拷贝
// aNativeWindow_Buffer.stride 是每行的数据个数 , 每个数据都包含一套 RGBA 像素数据 ,
// RGBA 数据每个占1字节 , 一个 RGBA 占 4 字节
// 每行的数据个数 * 4 代表 RGBA 数据个数
int dst_linesize = aNativeWindow_Buffer.stride * 4;
//3 . 获取 ANativeWindow_Buffer 中数据的地址
// 一次拷贝一行 , 有 像素高度 行数
for(int i = 0; i < aNativeWindow_Buffer.height; i ){
//计算拷贝的指针地址
// 每次拷贝的目的地址 : dst_data ( i * dst_linesize )
// 每次拷贝的源地址 : data ( i * linesize )
memcpy(dst_data ( i * dst_linesize ), data ( i * linesize ), dst_linesize);
}
VI . 启动绘制
调用 ANativeWindow_unlockAndPost ( ) 方法 , 即可启动图像的绘制 ; 该方法与 ANativeWindow_lock 对应 , 类似于 Java 中画布的锁定 与 解锁操作 ;
代码语言:javascript复制//启动绘制
ANativeWindow_unlockAndPost(aNativeWindow);