【精通SDL之----SDL_RenderReadPixels截屏】

SDL_RenderReadPixels截屏

  • 前言
  • 一、SDL_RenderReadPixels简介
  • 二、问题现象
  • 三、规避方案
    • 1. 离屏纹理
    • 2. `ding!` *==灵光一现==*

前言

  最近使用SDL2在鸿蒙系统(Harmoney OS)上截取视频播放过程中的数据,发现捕获的数据为空,然在windows上却可以正常捕获,欲定位其中问题并解决之。

一、SDL_RenderReadPixels简介

  SDL_RenderReadPixels用于从当前渲染目标(render target)读取像素数据,以便进行后续处理、保存图像或者其他用途。其参数定义及实现如下:
  renderer:指向用于渲染的 SDL_Renderer。
  rect:指向定义读取区域的 SDL_Rect。如果为 NULL,则读取整个渲染目标。
  format:希望得到的像素数据格式。使用 SDL 的像素格式,如 SDL_PIXELFORMAT_ARGB8888。
  pixels:指向用于存储读取到的像素数据的缓冲区。
  pitch:一行像素数据的字节数,即每行像素数据的步幅(宽度)。

SDL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,Uint32 format, void * pixels, int pitch)
{SDL_Rect real_rect;CHECK_RENDERER_MAGIC(renderer, -1);if (!renderer->RenderReadPixels) {return SDL_Unsupported();}FlushRenderCommands(renderer);  /* we need to render before we read the results. */if (!format) {format = SDL_GetWindowPixelFormat(renderer->window);}real_rect.x = renderer->viewport.x;real_rect.y = renderer->viewport.y;real_rect.w = renderer->viewport.w;real_rect.h = renderer->viewport.h;if (rect) {if (!SDL_IntersectRect(rect, &real_rect, &real_rect)) {return 0;}if (real_rect.y > rect->y) {pixels = (Uint8 *)pixels + pitch * (real_rect.y - rect->y);}if (real_rect.x > rect->x) {int bpp = SDL_BYTESPERPIXEL(format);pixels = (Uint8 *)pixels + bpp * (real_rect.x - rect->x);}}return renderer->RenderReadPixels(renderer, &real_rect,format, pixels, pitch);
}
  1. 上面先确定当前系统有无适配渲染器的RenderReadPixels函数,在每个平台下都有自己的渲染器实现,如windowsD3D11苹果metal索尼psp、嵌入式系统如安卓和鸿蒙opengles,及其他平台的opengl

  2. 使用FlushRenderCommands将当前渲染命令队列中的命令发送给GPU执行渲染,这里可以看到官方的注释说一定要在渲染之后才能读取到像素数据。【这里同时我也在github上问了下SDL的维护人员进行了确认:】
    在这里插入图片描述

  3. 如果传入的rect比实际渲染视口real_rect的大小要小,,则将更新pixels指向的起始捕获地址,这里如果是rgba8888格式,那么pitch就是1920(视频宽) * 4 = 7680

二、问题现象

  从上面的renderer->RenderReadPixels调到具体各平台的实现:
  在Open GLES上直接调用渲染器的driverdata的glReadPixels获取数据,但是此处得到的数据是个空值,具体也无法再往下跟了。

static int
GLES2_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,Uint32 pixel_format, void * pixels, int pitch)
{GLES2_RenderData *data = (GLES2_RenderData *)renderer->driverdata;Uint32 temp_format = renderer->target ? renderer->target->format : SDL_PIXELFORMAT_ABGR8888;size_t buflen;void *temp_pixels;int temp_pitch;Uint8 *src, *dst, *tmp;int w, h, length, rows;int status;temp_pitch = rect->w * SDL_BYTESPERPIXEL(temp_format);buflen = rect->h * temp_pitch;if (buflen == 0) {return 0;  /* nothing to do. */}temp_pixels = SDL_malloc(buflen);if (!temp_pixels) {return SDL_OutOfMemory();}SDL_GetRendererOutputSize(renderer, &w, &h);data->glReadPixels(rect->x, renderer->target ? rect->y : (h-rect->y)-rect->h,rect->w, rect->h, GL_RGBA, GL_UNSIGNED_BYTE, temp_pixels);if (GL_CheckError("glReadPixels()", renderer) < 0) {return -1;}/* Flip the rows to be top-down if necessary */if (!renderer->target) {SDL_bool isstack;length = rect->w * SDL_BYTESPERPIXEL(temp_format);src = (Uint8*)temp_pixels + (rect->h-1)*temp_pitch;dst = (Uint8*)temp_pixels;tmp = SDL_small_alloc(Uint8, length, &isstack);rows = rect->h / 2;while (rows--) {SDL_memcpy(tmp, dst, length);SDL_memcpy(dst, src, length);SDL_memcpy(src, tmp, length);dst += temp_pitch;src -= temp_pitch;}SDL_small_free(tmp, isstack);}status = SDL_ConvertPixels(rect->w, rect->h,temp_format, temp_pixels, temp_pitch,pixel_format, pixels, pitch);SDL_free(temp_pixels);return status;
}

  在windows下,同样的流程使用D3D11,确能正常获取到数据:

static int
D3D11_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,Uint32 format, void * pixels, int pitch)
{D3D11_RenderData * data = (D3D11_RenderData *) renderer->driverdata;ID3D11Texture2D *backBuffer = NULL;ID3D11Texture2D *stagingTexture = NULL;HRESULT result;int status = -1;D3D11_TEXTURE2D_DESC stagingTextureDesc;D3D11_RECT srcRect = {0, 0, 0, 0};D3D11_BOX srcBox;D3D11_MAPPED_SUBRESOURCE textureMemory;/* Retrieve a pointer to the back buffer: */result = IDXGISwapChain_GetBuffer(data->swapChain,0,&SDL_IID_ID3D11Texture2D,(void **)&backBuffer);if (FAILED(result)) {WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("IDXGISwapChain1::GetBuffer [get back buffer]"), result);goto done;}/* Create a staging texture to copy the screen's data to: */ID3D11Texture2D_GetDesc(backBuffer, &stagingTextureDesc);stagingTextureDesc.Width = rect->w;stagingTextureDesc.Height = rect->h;stagingTextureDesc.BindFlags = 0;stagingTextureDesc.MiscFlags = 0;stagingTextureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;stagingTextureDesc.Usage = D3D11_USAGE_STAGING;result = ID3D11Device_CreateTexture2D(data->d3dDevice,&stagingTextureDesc,NULL,&stagingTexture);if (FAILED(result)) {WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11Device1::CreateTexture2D [create staging texture]"), result);goto done;}/* Copy the desired portion of the back buffer to the staging texture: */if (D3D11_GetViewportAlignedD3DRect(renderer, rect, &srcRect, FALSE) != 0) {/* D3D11_GetViewportAlignedD3DRect will have set the SDL error */goto done;}srcBox.left = srcRect.left;srcBox.right = srcRect.right;srcBox.top = srcRect.top;srcBox.bottom = srcRect.bottom;srcBox.front = 0;srcBox.back = 1;ID3D11DeviceContext_CopySubresourceRegion(data->d3dContext,(ID3D11Resource *)stagingTexture,0,0, 0, 0,(ID3D11Resource *)backBuffer,0,&srcBox);/* Map the staging texture's data to CPU-accessible memory: */result = ID3D11DeviceContext_Map(data->d3dContext,(ID3D11Resource *)stagingTexture,0,D3D11_MAP_READ,0,&textureMemory);if (FAILED(result)) {WIN_SetErrorFromHRESULT(SDL_COMPOSE_ERROR("ID3D11DeviceContext1::Map [map staging texture]"), result);goto done;}/* Copy the data into the desired buffer, converting pixels to the* desired format at the same time:*/if (SDL_ConvertPixels(rect->w, rect->h,D3D11_DXGIFormatToSDLPixelFormat(stagingTextureDesc.Format),textureMemory.pData,textureMemory.RowPitch,format,pixels,pitch) != 0) {/* When SDL_ConvertPixels fails, it'll have already set the format.* Get the error message, and attach some extra data to it.*/char errorMessage[1024];SDL_snprintf(errorMessage, sizeof(errorMessage), "%s, Convert Pixels failed: %s", __FUNCTION__, SDL_GetError());SDL_SetError("%s", errorMessage);goto done;}/* Unmap the texture: */ID3D11DeviceContext_Unmap(data->d3dContext,(ID3D11Resource *)stagingTexture,0);status = 0;done:SAFE_RELEASE(backBuffer);SAFE_RELEASE(stagingTexture);return status;
}

  可以看出ID3D11Device_CreateTexture2D首先创建了一个纹理,然后复制后缓冲区的一部分到纹理中,然后通过ID3D11DeviceContext_Map将暂存纹理的数据映射到 CPU 可访问的内存,最后将数据复制到所需的缓冲区,同时将像素转换为所需格式。
  从上面不难看出,两者的主要不同在于D3D11中创建了一个离屏的纹理,通过将这个纹理映射到内存中来读取数据,这样即使视频渲染和截屏操作不在同一个线程中,也可以捕获到正常的数据。

三、规避方案

1. 离屏纹理

  于是乎,想参照D3D11一样也搞个离屏纹理,然后使用离屏纹理的数据来生成图片,代码如下:

	SDL_Rect renderRect = { 0, 0, viewSize.width(), viewSize.height() };// 1.先创建纹理SDL_Texture* targetTexture = _createSdlTexture(m_sdlRender, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, renderRect.w, renderRect.h, m_videoControl);if (!targetTexture)return false;// 2.设置渲染目标SDL_SetRenderTarget(m_sdlRender, targetTexture);// 3.拷贝纹理SDL_RenderCopyEx(m_sdlRender, m_videoYUVTexture, &m_videoOriginRect, &renderRect, 0, nullptr, SDL_FLIP_NONE);*ppImg = new QImage(renderRect.w, renderRect.h, QImage::Format_ARGB32);(*ppImg)->fill(0);uchar* data = (*ppImg)->bits();// 4.将拷贝的纹理中像素数据读到QImage中去SDL_RenderReadPixels(m_sdlRender, &renderRect, SDL_PIXELFORMAT_ARGB8888, (void*)data, (*ppImg)->bytesPerLine());// 5.销毁渲染目标SDL_SetRenderTarget(m_sdlRender, nullptr);_safeDestroy(targetTexture, &SDL_DestroyTexture, m_videoControl);

  这样可是可以,只不过只能适用于一张纹理的情况(欲哭无泪.jpg),如果同时还要捕获视频的前景和背景,实现是可以实现,就太繁琐了。

2. ding! 灵光一现

  既然github上SDL的维护人员说SDL_RenderReadPixels一定要在SDL_RenderPresent之前调用才有数据,那SDL_RenderPresent里面是不是有方法让它不去交换前后缓冲区呢?如果不交换缓冲区,就可以捕获到当前前景+背景+视频帧的所有纹理数据了!
  带着这个疑问,去看了下SDL_RenderPresent的实现:

void
SDL_RenderPresent(SDL_Renderer * renderer)
{CHECK_RENDERER_MAGIC(renderer, );FlushRenderCommands(renderer);  /* time to send everything to the GPU! *//* Don't present while we're hidden */if (renderer->hidden) {return;}renderer->RenderPresent(renderer);
}

  嘿嘿,在交换缓冲区之前有个判断渲染器是否隐藏,如果隐藏就直接return了,好家伙,可以用这个来控制。又由于渲染器SDL_Render和SDL_Window是绑定的,因此就直接在SDL_RenderReadPixels前隐藏窗口就OK了:

	SDL_HideWindow(m_sdlWindow);SDL_RenderReadPixels(m_sdlRenderer, &destRect, SDL_PIXELFORMAT_ARGB8888, (void*)data, image.bytesPerLine());SDL_RaiseWindow(m_sdlWindow);

真是踏破铁鞋无觅处,得来全不费工夫
XD

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/400160.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux-软件管理

文章目录 19. 软件管理19.1 linux软件介绍19.2 RPM包概述19.3 RPM软件包安装19.4 RPM软件包依赖问题19.5 DPKG软件包19.6 linux 软件包前端工具19.7 windows 前端工具winget19.8 linux 前端工具yum概述19.9 设置yum远程仓库19.10 yum 软件包管理19.11 epel软件仓库19.12 yum本地…

海康相机二次开发学习笔记1-环境配置

因为最近可以用一段时间海康加密狗,Visionpro二次开发暂时停更一段时间,开始记录一下海康相机二次开发的学习笔记. 环境配置 1. 创建项目 打开Visual Studio,新建.NetFramework项目,选择WindowsForms,点击下一步,选择项目名称,点击下一步,点击确定打开项目属性,点击生成选项…

TED: 1靶场复现【附代码】(权限提升)

机下载地址&#xff1a; Ted: 1 ~ VulnHubTed: 1, made by Avraham Cohen. Download & walkthrough links are available.https://www.vulnhub.com/entry/ted-1,327/ 1. 主机发现端口扫描目录扫描敏感信息获取 1.1. 主机发现 nmap -sn 192.168.59.0/24|grep -B 2 00:0C…

面试题:什么是 Redis 的雪崩、穿透和击穿?Redis 崩溃之后会怎么样?该如何应对这种情况?如何处理 Redis 的穿透?

面试题&#xff1a;什么是 Redis 的雪崩、穿透和击穿&#xff1f;Redis 崩溃之后会怎么样&#xff1f;该如何应对这种情况&#xff1f;如何处理 Redis 的穿透&#xff1f; 面试题面试官心理分析面试题剖析缓存雪崩缓存穿透缓存击穿 面试题 了解什么是 Redis 的雪崩、穿透和击穿…

百度智能云发布3款轻量级+2款场景大模型

文心大模型ERNIE 3.5是目前百度智能云千帆大模型平台上最受欢迎的基础大模型之一。针对用户的常见通用的对话场景&#xff0c;ERNIE 3.5 在指令遵循、上下文学习和逻辑推理能力三方面分别进行了能力增强。 ERNIE Speed作为三款轻量级大模型中的“大个子”&#xff0c;推理场景…

微调LLama 3.1——七月论文审稿GPT第5.5版:拿早期paper-review数据集微调LLama 3.1

前言 对于llama3&#xff0c;我们之前已经做了针对llama3 早7数据微调后的测评 去pk llama2的早7数据微调后&#xff0c;推理测试集中的早期paper&#xff1a;出来7方面review去pk gpt4推理测试集中的早期paper&#xff1a;7方面reviewground truth是早期paper的7方面人工rev…

Mysql-B树和B+树的区别

当我们为ID去建立一个主键索引的时候&#xff0c;Mysql底层就会为我们去维护一棵树的结构&#xff0c;从而提升我们的数据检索效率&#xff0c;树的共同特性&#xff1a;小的索引在左边&#xff0c;大的索引在右边&#xff0c;每一次结点的寻址&#xff0c;都是一次磁盘的IO&am…

Linux 基本指令讲解 上

linux 基本指令 clear 清屏 Alt Enter 全屏/退出全屏 pwd 显示当前用户所处路径 cd 改变目录 cd /root/mikecd … 返回上级目录cd - 返回最近所处的路径cd ~ 直接返回当前用户自己的家目 roor 中&#xff1a;/root普通用户中&#xff1a;/home/mike mkdir 创建一个文件夹(d) …

简单的class.getResource与classLoader.getResource区别

简单的getClass().getResource()与ClassLoader.getResource()区别 1.简介 我们在springboot项目中&#xff0c;如果要获取到自己配置的资源或者配置类信息一般会用到Class.getResource()或ClassLoader.getResource()&#xff0c;这两种方式在使用的过程中很容易混淆&#xff…

智慧景区系统:科技赋能旅游新体验

随着信息技术的飞速发展&#xff0c;旅游业正经历着前所未有的变革&#xff0c;智慧景区系统作为这一变革的先锋&#xff0c;正以其独特的魅力重塑着游客的旅行方式。智慧景区系统&#xff0c;顾名思义&#xff0c;是运用物联网、大数据、云计算、人工智能等现代信息技术&#…

NFT 合约:部署 ERC 721 到 Testnet 并发布

目录 1. 创建智能合约2. 配置 Network3. 配置发布脚本4. 执行发布命令Refs1. 创建智能合约 访问: https://wizard.openzeppelin.com/#erc721 填入必要信息,勾选选项。然后点击【Download】下载 hardhat 开发版本。 2. 配置 Network 在 hardhat.config.ts 中添加网络配置:…

Golang面试题六(GMP)

目录 1.Go线程实现模型 1:1 关系 N:1关系 M:N关系 2.GM模型 3.GMP模型 概念 模型简介 有关P和M的个数问题 P和M何时会被创建 4.调度器的设计策略 5.go func() 调度流程 6.调度器的生命周期 7.Go work stealing 机制 8.Go hand off 机制 9.Go 抢占式调度 9.Sys…

外卖O2O系统开发源码开源介绍

外卖O2O系统开发源码开源介绍 开源外卖O2O系统源码可以为开发者提供快速搭建外卖平台的基础&#xff0c;节省从零开始的开发时间。 以下是几个推荐的开源项目&#xff1a; flash-waimai 是一个基于Spring Boot和Vue.js的前后端分离的外卖系统&#xff0c;包含手机端和后台管理…

spring boot 发送微信小程序订阅消息

首先我们需要订阅一个消息&#xff1a; 订阅教程本文章并未提起&#xff0c;感兴趣的同学自行百度。 我们可以看到订阅消息中【消息内容】有很多参数&#xff0c;我们在发送消息时就需要将这些参数进行填充&#xff0c;当然填充的时候要注意格式&#xff0c;如果格式不对还是会…

LDR6020在Type-C手机同时充电与USB2.0数据传输方案

随着科技的飞速发展&#xff0c;Type-C接口已成为智能手机等移动设备的主流充电和数据传输接口。为了满足用户对于高效充电与稳定数据传输的双重需求&#xff0c;乐得瑞科技推出的LDR6020芯片凭借其卓越的性能和丰富的功能&#xff0c;为Type-C手机提供了同时充电与USB2.0数据传…

关于归并排序:

![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_urlhttps%3A%2F return 语句开始之后&#xff0c;会执行之前剩余遗留下的语句和状态#include<bits/stdc.h> using namespace std…

使用RestHighLevelClient进行Elasticsearch Function Score查询

简介 Function Score查询在Elasticsearch中是一个强大的工具&#xff0c;它允许我们根据一个或多个函数来调整查询结果的相关性得分。这使得我们可以基于某些条件对搜索结果进行更精细的控制。本文将介绍如何在Java应用程序中使用Elasticsearch的RestHighLevelClient执行Funct…

快速把文件名统计到excel表的方法

文件名统计到EXCEL表&#xff0c;这似乎很多人都没听说过&#xff0c;因为它与EXCEL表格不沾边&#xff0c;那么这个需求如何实现&#xff0c;用到什么方法&#xff0c;今天给大家介绍一个比较实用的方法&#xff0c;它可以把文件名或文件夹的名快速提取并统计到EXCEL表格上去。…

【Day05】0基础微信小程序入门-学习笔记

文章目录 基础加强学习目标使用npm包1.准备项目2. 小程序对于npm的支持和限制3. Vant Weapp小程序UI组件库4. 使用Vant组件5. 定制全局主题样式6. API Promise化 全局数据共享1. 简介2. MobX2.1 安装MobX相关包并构建npm2.2 创建MobX的Store实例2.3 将Store成员绑定到页面中2.4…

ppt中添加页码(幻灯片编号)及问题解决方案

在幻灯片母版中&#xff0c;选择插入 幻灯片编号 右下角显示幻灯片编号 问题一&#xff1a;母版中没有显示编号 原因可能是母版版式中没有设置显示&#xff0c;勾选即可。 问题二&#xff1a;子母版中没有显示幻灯片 将母版中的编号复制到子母版中。 问题三&#xff1a;应用…