惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
量子位
小众软件
小众软件
C
Cybersecurity and Infrastructure Security Agency CISA
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tenable Blog
V
Vulnerabilities – Threatpost
Know Your Adversary
Know Your Adversary
T
Threat Research - Cisco Blogs
Latest news
Latest news
Spread Privacy
Spread Privacy
C
Cyber Attacks, Cyber Crime and Cyber Security
NISL@THU
NISL@THU
T
Tor Project blog
Hacker News: Ask HN
Hacker News: Ask HN
V2EX - 技术
V2EX - 技术
T
The Exploit Database - CXSecurity.com
博客园 - 三生石上(FineUI控件)
K
Kaspersky official blog
Cyberwarzone
Cyberwarzone
博客园 - 叶小钗
博客园 - 聂微东
Last Week in AI
Last Week in AI
爱范儿
爱范儿
腾讯CDC
博客园 - Franky
美团技术团队
J
Java Code Geeks
O
OpenAI News
L
Lohrmann on Cybersecurity
Simon Willison's Weblog
Simon Willison's Weblog
有赞技术团队
有赞技术团队
T
Threatpost
G
GRAHAM CLULEY
Hugging Face - Blog
Hugging Face - Blog
博客园 - 【当耐特】
宝玉的分享
宝玉的分享
I
Intezer
N
News and Events Feed by Topic
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
The Blog of Author Tim Ferriss
S
Security @ Cisco Blogs
Forbes - Security
Forbes - Security
N
News | PayPal Newsroom
Stack Overflow Blog
Stack Overflow Blog
Scott Helme
Scott Helme
H
Hacker News: Front Page
Cloudbric
Cloudbric

音视频开发进阶

音视频教程-第三节 音视频教程-第二节 真的,AI 可能就是新时代的信息差 充值 Cursor 之后,工作有了哪些变化?🤔 个人'蒸馏'大模型能做哪些有意思的事情 DeepSeek 大模型在 Mac 上的部署和运行 音视频教程-第一节 【WebRTC 专栏】-- Android 开发集成 WebRTC 库的几种方式 【WebRTC 专栏】-- 在 Mac M1 等系列芯片编译和开发 WebRTC-Android 库 Meta Llama3 大模型在 Mac 上的部署和运行 iOS VideoToolBox 解码 HEVC Open-GOP 视频的问题排查 Flutter 状态管理之 InheritedWidget 使用和分析 用 ChatGPT 回答技术问题怎么样 ? 音视频开发系统入门大致路线 UE 4.27 添加自定义 ShadingModel 用 UE4 虚幻引擎做个捏脸小功能~~ UE4 材质练习 之 凹凸贴图偏移的使用 UE4 材质练习系列基础 OpenGL上下文创建以及共享机制 007 | 播放器系列专栏-解析 MP4 文件读取信息 006 | 播放器系列专栏-在 Mac 上查看 MP4 格式信息 干货 | 快速抽取缩略图是怎么练成的? 关于直播、WebRTC、FFmpeg 的那些事 005 | 播放器系列专栏-在 Windows 上查看 MP4 格式信息 将音视频中的花屏、绿屏、黑屏问题一网打尽 关于音视频里面的解码帧率和渲染帧率 004 | 播放器系列专栏-认识MP4视频(下) 003 | 播放器系列专栏-认识MP4视频(上) 入门或者转行音视频,应该要怎么做? H264视频文件如何缩放分辨率 002 | 播放器系列专栏-FFmpeg依赖库的配置 001 | 播放器系列专栏-关于播放器项目的一个小实践 Seek策略以及在有B帧情况下的处理 目前流媒体开发工程师工作内容主要是什么? 一个音视频领域专业问答的小圈子 干货收藏 || Vulkan Game Engine 视频教程 音视频春节假期内卷指南(实操) Vulkan 在 FFmpeg 中的支持 Windows 下 FFmpeg 和 LibX264 的编译和配置 Metal 开发 | 使用 C++ 进行接口调用 音视频开发工作经验分享 || 视频版 代码吸猫 | 用 OpenGL 图像渲染的养猫计划 百倍变速--解码到底能不能丢 非参考帧 ?FFmpeg 有话说!!! 老生常谈-FFmpeg 的编译问题轻松搞定 FFmpeg 调用 Android MediaCodec 进行硬解码(附源码) 【WebRTC 专栏】--创建相机预览 Unity Shader 光照基础之 Half Lambert 光照模型 Unity Shader 光照基础之Lambert光照模型 Unity Shader 光照基础内容 Unity Shader 显示一张图片纹理 UnityShader 的基本概念 Unity 物体的基本操作 C++ 模板系列小结07-尾置返回类型 C++ 模板系列小结06-可变参数模板特性 C++ 中的多线程的使用和线程池建设 C++ 模板系列小结05-模板类型作为模板参数 C++ 模板系列小结04-类模板中的成员模板 C++ 模板系列小结03-在模板中指定变量类型 C++ 模板系列小结02-非类型模板参数 C++ 模板系列小结01-函数模板和类模板 从零打造渲染引擎系列01-什么是渲染引擎 iOS开发 - 在 Swift 中去调用 C/C++ 代码 2021 技术新番 - 从零打造渲染引擎系列 iOS 音视频开发的一些基础准备工作 音视频交流群又来啦~~~ 【WebRTC 专栏】WebRTC & Android 开发学习环境搭建~ 【喜大普奔】域名终于备案通过啦 Shader 优化 | OpenGL 绘制网格效果 【音视频连载-011】第二季 FFmpeg 一层一层获取文件信息 KodeLife | Shader 实时编辑预览的强大工具使用实践 推荐几个堪称教科书级别的 Android 音视频入门项目 【音视频连载-010】第二季 FFmpeg 日志打印 【音视频连载-008】基础学习篇-SDL 播放 PCM 音频文件(下) 【音视频连载-007】基础学习篇-SDL 播放 PCM 音频文件(上) 【音视频连载-006】基础学习篇-SDL 播放 YUV 视频文件 【音视频连载-005】基础学习篇-SDL 加载 YUV 文件并显示 【音视频连载-004】基础学习篇-SDL 加载图片并显示 【音视频连载-003】基础学习篇-SDL 消息循环和事件响应 【音视频连载-002】基础学习篇-SDL 创建窗口并显示颜色 【音视频连载-001】基础学习篇- SDL 介绍以及工程配置 LearnOpenGL 源码在 MAC 上的编译与调试 2019 年终总结与回顾 Android NDK 开发的免费技术视频来啦~~ OpenGL 实现视频编辑中的转场效果 OpenGL 实践之贝塞尔曲线绘制 图像库 libjpeg-turbo 编译与实践 图像库 libpng 编译与实践 rust 开发编译 Android 动态库实践 Android NDK 开发 —— 从 Assets 文件夹加载图片并上传纹理 简单易用的图像解码库介绍 —— stb_image 博客图床迁移记 进击的 Vulkan 移动开发之 SwapChain 进击的 Vulkan 移动开发之 Command Buffer 进击的 Vulkan 移动开发之 Instance & Device & Queue 进击的 Vulkan 移动开发(一)之今生前世 Java 显式锁 Lock 与条件队列 C++ 标准容器库小结 MediaCodec 硬编码之相机内容编码成 H264 文件 一文读懂 YUV 的采样与格式 《OpenGL ES 3.x 游戏开发》碰撞检测之 AABB 包围盒
FFmpeg 调用 MediaCodec 硬解码到 Surface 上
音视频开发进阶 · 2021-11-14 · via 音视频开发进阶

一个专注音视频领域的小圈子

这是关于 FFmpeg 和 MediaCodec 爱恨情仇系列的第三篇文章了。 之前写了 FFmpeg 调用 MediaCodec 进行硬解码的内容。

另外也给出了 FFmpeg 的编译脚本,轻松搞定编译问题。

众所周知,MediaCodec 的解码能力不仅可以解码出 YUV 数据,还能直接解码到 Surface 上。

在短视频领域中,MediaCodec 解码到 Surface 上的能力反而更加常用,这样就能将画面转到 OES 纹理上,从而进行后续各种渲染操作。

之前介绍的 FFmpeg 调用 MediaCodec 进行硬解码只是解码出了 Buffer 数据,没有把解码到 Surface 上的能力用起来。

再看了更多资料之后,发现 FFmpeg 调用 MediaCodec 已经可以解码到 Surface 上。

具体参考的是这篇邮件内容:

http://mplayerhq.hu/pipermail/ffmpeg-devel/2016-March/191700.html

在这里面详细介绍了这种能力,挑重点截图一下:

图片内容介绍的很详细,按照步骤实践就好了。

代码实践

如果熟悉了 FFmpeg 调用 MediaCodec 解码 Buffer 数据的流程,那么解码到 Surface 只是在流程上稍微改动一点就行。

首先要准备好 Surface 对象,在 Java 上层构建好 Surface 对象通过 NDK 传到 Native 层,传下来的是一个 jobject 对象。

如果不熟悉 NDK 的话,可以看看我在慕课网上的录制的免费课程:

Android NDK 免费视频在线学习!!!

其次是两个新的函数方法:

av_mediacodec_alloc_context 和 av_mediacodec_default_init 方法就是让 Surface 和 AVMediaCodecContext 、AVCodecContext 三者之间产生关联。

具体就是 AVCodecContext 持有 AVMediaCodecContext ,AVMediaCodecContext 持有 Surface 。

至于为什么要关联,因为在 FFmpeg 源码里要根据 Surface 是否为 nullptr 对 MediaCodec 的初始化和解码后的数据做不同处理。

感兴趣的可以阅读这块的源码,内容不多通俗易懂。

等到解码之后,FFmpeg 同样会返回一个 AVFrame 数据,只不过它的 data 字段不再是 Buffer 内容了。

AVFrame 的格式不再是 NV12 (解码 Buffer 数据的话就是 NV12),而是自定义的 AV_PIX_FMT_MEDIACODEC ,代表走的 Surface 模式。

if (s->surface) {
   // surface 不为 null 的情况下:
   // 通过 mediacodec_wrap_hw_buffer 对数据进行处理
  if ((ret = mediacodec_wrap_hw_buffer(avctx, s, index, &info, frame)) < 0) {
    av_log(avctx, AV_LOG_ERROR, "Failed to wrap MediaCodec buffer\n");
       return ret;
  }
}

Surface 模式下对数据的处理是 mediacodec_wrap_hw_buffer 函数,Buffer 模式就是 mediacodec_wrap_sw_buffer 函数了。

同时,真正解码后的数据存储在 AVFrame->data[3] 字段上,这个字段是个老员工了。

一般解码非 Buffer 数据的情况,都会将特殊的内容保存到 data[3] 上,比如 Window 上的硬解,部分源码如下:

static int mediacodec_wrap_hw_buffer(AVCodecContext *avctx,
                                  MediaCodecDecContext *s,
                                  ssize_t index,
                                  FFAMediaCodecBufferInfo *info,
                                  AVFrame *frame)
// 省略部分源码
// 创建   AVMediaCodecBuffer 对象
buffer = av_mallocz(sizeof(AVMediaCodecBuffer));
frame->buf[0] = av_buffer_create(NULL,
                       0,
                       mediacodec_buffer_release,
                       buffer,
                       AV_BUFFER_FLAG_READONLY);
// buffer 相关属性赋值
buffer->ctx = s;
buffer->serial = atomic_load(&s->serial);
if (s->delay_flush)
    ff_mediacodec_dec_ref(s);
// index 索引,代表 mediacodec 中 buffer 的索引
buffer->index = index;
buffer->pts = info->presentationTimeUs;
// 将 buffer 赋值给 data[3] 字段
frame->data[3] = (uint8_t *)buffer;

有了 AVFrame 数据之后,Surface 上还是没有画面。

回想一下,在 MediaCodec 上想要数据渲染到 Surface 还得调用一个 releaseOutputBuffer 方法,其中第二个参数要传 true 才可以。

public void releaseOutputBuffer (int index, boolean render)

同样,在 FFmpeg 中也有这么个方法。

int av_mediacodec_release_buffer(AVMediaCodecBuffer *buffer, int render);

buffer 就是 frame->data[3] 的内容,render 的含义和 releaseOutputBuffer 中的含义一致。

另外 releaseOutputBuffer 方法第一个参数 index 其实就已经在 buffer 中赋值过了。

这样一来,解码后就可以直接上屏渲染展示啦。

 AVMediaCodecBuffer *buffer = (AVMediaCodecBuffer *) frame->data[3];
 av_mediacodec_release_buffer(buffer, 1);

完整代码实践可以在公众号 音视频开发进阶 回复 1019 获取。

经过测试验证确实可行,不过直接不断解码上屏的速度是很快的,可不止视频播放 30ms 一帧的速度哦,想要来做播放器的话,还得自己管理控制一下了。

另外,完整代码演示中直接解码到了 SurfaceView 的 Surface 上。

除此之外,还可以解码到 SurfaceTexture 构造的 Surface 上,这样就可以用到 SurfaceTexture 的 OnFrameAvailableListener 回调方法, 并且还可以用 attachToGLContext 方法关联到 OpenGL 环境上,每次解码时通过 updateTexImage 更新画面,实现解码到 OES 纹理的目标,具体操作起来也是很容易方便。

原创文章,转载请注明来源:    FFmpeg 调用 MediaCodec 硬解码到 Surface 上