ffmpeg视频解码原理和实战-(5)硬件加速解码后进行渲染并输出帧率

头文件:

xvideoview.h


#ifndef XVIDEO_VIEW_H
#define XVIDEO_VIEW_H
#include <mutex>
#include <fstream>
struct AVFrame;void MSleep(unsigned int ms);//获取当前时间戳 毫秒
long long NowMs();/// 视频渲染接口类
/// 隐藏SDL实现
/// 渲染方案可替代
// 线程安全
class XVideoView
{
public:enum Format  //枚举的值和ffmpeg中一致{YUV420P = 0,NV12 = 23,ARGB = 25,RGBA = 26,BGRA = 28};enum RenderType{SDL = 0};static XVideoView* Create(RenderType type = SDL);/// 初始化渲染窗口 线程安全 可多次调用/// @para w 窗口宽度/// @para h 窗口高度/// @para fmt 绘制的像素格式/// @para win_id 窗口句柄,如果为空,创建新窗口/// @return 是否创建成功virtual bool Init(int w, int h,Format fmt = RGBA) = 0;//清理所有申请的资源,包括关闭窗口virtual void Close() = 0;//处理窗口退出事件virtual bool IsExit() = 0;///// 渲染图像 线程安全///@para data 渲染的二进制数据///@para linesize 一行数据的字节数,对于YUV420P就是Y一行字节数/// linesize<=0 就根据宽度和像素格式自动算出大小/// @return 渲染是否成功virtual bool Draw(const unsigned  char* data, int linesize = 0) = 0;virtual bool Draw(const unsigned  char* y, int y_pitch,const unsigned  char* u, int u_pitch,const unsigned  char* v, int v_pitch) = 0;//显示缩放void Scale(int w, int h){scale_w_ = w;scale_h_ = h;}bool DrawFrame(AVFrame* frame);int render_fps() { return render_fps_; }//打开文件bool Open(std::string filepath);///// 读取一帧数据,并维护AVFrame空间/// 每次调用会覆盖上一次数据AVFrame* Read();void set_win_id(void* win) { win_id_ = win; }virtual ~XVideoView();
protected:void* win_id_ = nullptr; //窗口句柄int render_fps_ = 0;       //显示帧率int width_ = 0;             //材质宽高int height_ = 0;Format fmt_ = RGBA;         //像素格式std::mutex mtx_;            //确保线程安全int scale_w_ = 0;           //显示大小int scale_h_ = 0;long long beg_ms_ = 0;       //计时开始时间int count_ = 0;              //统计显示次数unsigned char* cache_ = nullptr;//用于复制NV12缓冲
private:std::ifstream ifs_;AVFrame* frame_ = nullptr;
};#endif

xsdl.h


#pragma once#include "xvideoview.h"
struct SDL_Window;
struct SDL_Renderer;
struct SDL_Texture;
class XSDL :public XVideoView
{
public:void Close() override;/// 初始化渲染窗口 线程安全/// @para w 窗口宽度/// @para h 窗口高度/// @para fmt 绘制的像素格式/// @para win_id 窗口句柄,如果为空,创建新窗口/// @return 是否创建成功bool Init(int w, int h,Format fmt = RGBA) override;///// 渲染图像 线程安全///@para data 渲染的二进制数据///@para linesize 一行数据的字节数,对于YUV420P就是Y一行字节数/// linesize<=0 就根据宽度和像素格式自动算出大小/// @return 渲染是否成功bool Draw(const unsigned  char* data,int linesize = 0) override;bool Draw(const unsigned  char* y, int y_pitch,const unsigned  char* u, int u_pitch,const unsigned  char* v, int v_pitch) override;bool IsExit() override;
private:SDL_Window* win_ = nullptr;SDL_Renderer* render_ = nullptr;SDL_Texture* texture_ = nullptr;
};

源文件:

xvideoview.cpp


#include "xsdl.h"
#include <thread>
#include <iostream>
using namespace std;
extern "C"
{
#include <libavcodec/avcodec.h>
}
#pragma comment(lib,"avutil.lib")void MSleep(unsigned int ms)
{auto beg = clock();for (int i = 0; i < ms; i++){this_thread::sleep_for(1ms);if ((clock() - beg) / (CLOCKS_PER_SEC / 1000) >= ms)break;}
}
long long NowMs()
{return clock() / (CLOCKS_PER_SEC / 1000);
}
AVFrame* XVideoView::Read()
{if (width_ <= 0 || height_ <= 0 || !ifs_)return NULL;//AVFrame空间已经申请,如果参数发生变化,需要释放空间if (frame_){if (frame_->width != width_|| frame_->height != height_|| frame_->format != fmt_){//释放AVFrame对象空间,和buf引用计数减一av_frame_free(&frame_);}}if (!frame_){//分配对象空间和像素空间frame_ = av_frame_alloc();frame_->width = width_;frame_->height = height_;frame_->format = fmt_;frame_->linesize[0] = width_ * 4;if (frame_->format == AV_PIX_FMT_YUV420P){frame_->linesize[0] = width_; // Yframe_->linesize[1] = width_ / 2;//Uframe_->linesize[2] = width_ / 2;//V}//生成AVFrame空间,使用默认对齐auto re = av_frame_get_buffer(frame_, 0);if (re != 0){char buf[1024] = { 0 };av_strerror(re, buf, sizeof(buf) - 1);cout << buf << endl;av_frame_free(&frame_);return NULL;}}if (!frame_)return NULL;//读取一帧数据if (frame_->format == AV_PIX_FMT_YUV420P){ifs_.read((char*)frame_->data[0],frame_->linesize[0] * height_);	//Yifs_.read((char*)frame_->data[1],frame_->linesize[1] * height_ / 2);	//Uifs_.read((char*)frame_->data[2],frame_->linesize[2] * height_ / 2);	//V}else	//RGBA ARGB BGRA 32{ifs_.read((char*)frame_->data[0], frame_->linesize[0] * height_);}if (ifs_.gcount() == 0)return NULL;return frame_;}//打开文件
bool XVideoView::Open(std::string filepath)
{if (ifs_.is_open()){ifs_.close();}ifs_.open(filepath, ios::binary);return ifs_.is_open();
}
XVideoView* XVideoView::Create(RenderType type)
{switch (type){case XVideoView::SDL:return new XSDL();break;default:break;}return nullptr;
}
XVideoView::~XVideoView()
{if (cache_)delete cache_;cache_ = nullptr;
}bool XVideoView::DrawFrame(AVFrame* frame)
{if (!frame || !frame->data[0])return false;count_++;if (beg_ms_ <= 0){beg_ms_ = clock();}//计算显示帧率else if ((clock() - beg_ms_) / (CLOCKS_PER_SEC / 1000) >= 1000) //一秒计算一次fps{render_fps_ = count_;count_ = 0;beg_ms_ = clock();}int linesize = 0;switch (frame->format){case AV_PIX_FMT_YUV420P:return Draw(frame->data[0], frame->linesize[0],//Yframe->data[1], frame->linesize[1],	//Uframe->data[2], frame->linesize[2]	//V);case AV_PIX_FMT_NV12:if (!cache_){cache_ = new unsigned char[4096 * 2160 * 1.5];}linesize = frame->width;if (frame->linesize[0] == frame->width){memcpy(cache_, frame->data[0], frame->linesize[0] * frame->height); //Ymemcpy(cache_ + frame->linesize[0] * frame->height, frame->data[1], frame->linesize[1] * frame->height / 2); //UV}else //逐行复制{for (int i = 0; i < frame->height; i++) //Y{memcpy(cache_ + i * frame->width,frame->data[0] + i * frame->linesize[0],frame->width);}for (int i = 0; i < frame->height / 2; i++)  //UV{auto p = cache_ + frame->height * frame->width;// 移位Ymemcpy(p + i * frame->width,frame->data[1] + i * frame->linesize[1],frame->width);}}//frame->data[0] + frame->data[1]return Draw(cache_, linesize);case AV_PIX_FMT_BGRA:case AV_PIX_FMT_ARGB:case AV_PIX_FMT_RGBA:return Draw(frame->data[0], frame->linesize[0]);default:break;}return false;
}

xsdl.cpp


#include "xsdl.h"
#include "sdl/SDL.h"
#include <iostream>
using namespace std;
#pragma comment(lib,"SDL2.lib")
static bool InitVideo()
{static bool is_first = true;static mutex mux;unique_lock<mutex> sdl_lock(mux);if (!is_first)return true;is_first = false;if (SDL_Init(SDL_INIT_VIDEO)){cout << SDL_GetError() << endl;return false;}//设定缩放算法,解决锯齿问题,线性插值算法SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");return true;
}
bool XSDL::IsExit()
{SDL_Event ev;SDL_WaitEventTimeout(&ev, 1);if (ev.type == SDL_QUIT)return true;return false;
}
void XSDL::Close()
{//确保线程安全unique_lock<mutex> sdl_lock(mtx_);if (texture_){SDL_DestroyTexture(texture_);texture_ = nullptr;}if (render_){SDL_DestroyRenderer(render_);render_ = nullptr;}if (win_){SDL_DestroyWindow(win_);win_ = nullptr;}
}bool XSDL::Init(int w, int h, Format fmt)
{if (w <= 0 || h <= 0)return false;//初始化SDL 视频库InitVideo();//确保线程安全unique_lock<mutex> sdl_lock(mtx_);width_ = w;height_ = h;fmt_ = fmt;if (texture_)SDL_DestroyTexture(texture_);if (render_)SDL_DestroyRenderer(render_);///1 创建窗口if (!win_){if (!win_id_){//新建窗口win_ = SDL_CreateWindow("",SDL_WINDOWPOS_UNDEFINED,SDL_WINDOWPOS_UNDEFINED,w, h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);}else{//渲染到控件窗口win_ = SDL_CreateWindowFrom(win_id_);}}if (!win_){cerr << SDL_GetError() << endl;return false;}/// 2 创建渲染器render_ = SDL_CreateRenderer(win_, -1, SDL_RENDERER_ACCELERATED);if (!render_){cerr << SDL_GetError() << endl;return false;}//创建材质 (显存)unsigned int sdl_fmt = SDL_PIXELFORMAT_RGBA8888;switch (fmt){case XVideoView::RGBA:sdl_fmt = SDL_PIXELFORMAT_RGBA32;break;case XVideoView::BGRA:sdl_fmt = SDL_PIXELFORMAT_BGRA32;break;case XVideoView::ARGB:sdl_fmt = SDL_PIXELFORMAT_ARGB32;break;case XVideoView::YUV420P:sdl_fmt = SDL_PIXELFORMAT_IYUV;break;case XVideoView::NV12:sdl_fmt = SDL_PIXELFORMAT_NV12;break;default:break;}texture_ = SDL_CreateTexture(render_,sdl_fmt,                        //像素格式SDL_TEXTUREACCESS_STREAMING,    //频繁修改的渲染(带锁)w, h                            //材质大小);if (!texture_){cerr << SDL_GetError() << endl;return false;}return true;
}
bool XSDL::Draw(const unsigned  char* y, int y_pitch,const unsigned  char* u, int u_pitch,const unsigned  char* v, int v_pitch
)
{//参数检查if (!y || !u || !v)return false;unique_lock<mutex> sdl_lock(mtx_);if (!texture_ || !render_ || !win_ || width_ <= 0 || height_ <= 0)return false;//复制内存到显显存auto re = SDL_UpdateYUVTexture(texture_,NULL,y, y_pitch,u, u_pitch,v, v_pitch);if (re != 0){cout << SDL_GetError() << endl;return false;}//清空屏幕SDL_RenderClear(render_);//材质复制到渲染器SDL_Rect rect;SDL_Rect* prect = nullptr;if (scale_w_ > 0)  //用户手动设置缩放{rect.x = 0; rect.y = 0;rect.w = scale_w_;//渲染的宽高,可缩放rect.h = scale_w_;prect = &rect;}re = SDL_RenderCopy(render_, texture_, NULL, prect);if (re != 0){cout << SDL_GetError() << endl;return false;}SDL_RenderPresent(render_);return true;
}
bool XSDL::Draw(const unsigned char* data, int linesize)
{if (!data)return false;unique_lock<mutex> sdl_lock(mtx_);if (!texture_ || !render_ || !win_ || width_ <= 0 || height_ <= 0)return false;if (linesize <= 0){switch (fmt_){case XVideoView::RGBA:case XVideoView::ARGB:linesize = width_ * 4;break;case XVideoView::YUV420P:linesize = width_;break;default:break;}}if (linesize <= 0)return false;//复制内存到显显存auto re = SDL_UpdateTexture(texture_, NULL, data, linesize);if (re != 0){cout << SDL_GetError() << endl;return false;}//清空屏幕SDL_RenderClear(render_);//材质复制到渲染器SDL_Rect rect;SDL_Rect* prect = nullptr;if (scale_w_ > 0)  //用户手动设置缩放{rect.x = 0; rect.y = 0;rect.w = scale_w_;//渲染的宽高,可缩放rect.h = scale_w_;prect = &rect;}re = SDL_RenderCopy(render_, texture_, NULL, prect);if (re != 0){cout << SDL_GetError() << endl;return false;}SDL_RenderPresent(render_);return true;
}

main.cpp

#include <iostream>
#include <fstream>
#include<string>
#include"xsdl.h"
using namespace std;
extern "C" { //指定函数是c语言函数,函数名不包含重载标注
//引用ffmpeg头文件
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
}
//预处理指令导入库
#pragma comment(lib,"avcodec.lib")
#pragma comment(lib,"avutil.lib")
int main(int argc, char* argv[])
{auto view = XVideoView::Create();//1 分割h264 存入AVPacket// ffmpeg -i v1080.mp4 -s 400x300 test.h264string filename = "test.h264";ifstream ifs(filename, ios::binary);if (!ifs)return -1;unsigned char inbuf[4096] = { 0 };//用于存储h264编码流AVCodecID codec_id = AV_CODEC_ID_H264;//1 找解码器auto codec = avcodec_find_decoder(codec_id);//2 创建解码器上下文AVCodecContext *c = avcodec_alloc_context3(codec);//硬件加速格式 DXVA2AVHWDeviceType hw_type = AV_HWDEVICE_TYPE_DXVA2;/// 打印所有支持的硬件加速方式for (int i = 0;; i++){//const AVCodecHWConfig *avcodec_get_hw_config(const AVCodec *codec, int index);//用于获取指定编解码器的硬件加速配置。//index: 硬件配置的索引值,从 0 开始递增。用于遍历所有可用的硬件配置。const AVCodecHWConfig *config = avcodec_get_hw_config(codec, i);if (!config)break;if (config->device_type)//const char *av_hwdevice_get_type_name(enum AVHWDeviceType type);//该函数接收一个 AVHWDeviceType 类型的参数,并返回一个描述该硬件设备类型的字符串指针//如 CUDA、DXVA2、VAAPI 等。cout << av_hwdevice_get_type_name(config->device_type) << endl;}//初始化硬件加速器上下文 AVBufferRef* hw_ctx = nullptr;av_hwdevice_ctx_create(&hw_ctx, hw_type, NULL, NULL, 0);//设定硬件GPU加速c->hw_device_ctx = av_buffer_ref(hw_ctx);//把硬件加速器上下文引用赋值给解码器上下文,以便在后续的编码或解码操作中利用硬件加速功能c->thread_count = 16;//3 打开编码器上下文avcodec_open2(c, NULL, NULL);//分割上下文AVCodecParserContext* parser = av_parser_init(codec_id);AVPacket* pkt = av_packet_alloc();AVFrame* frame = av_frame_alloc();AVFrame* hw_frame = av_frame_alloc(); //硬解码转换用这个framelong long begin = NowMs();int count = 0;//解码统计bool is_init_win = false;while (!ifs.eof()){ifs.read((char*)inbuf, sizeof(inbuf));//将h264编码的流读入inbuf中,一次读4096int data_size = ifs.gcount();// 返回上一次读取操作实际读取的字符数,并用data_size记录if (data_size <= 0)break;//检查输入流是否已经到达文件末尾(EOF,End Of File)if (ifs.eof()){ifs.clear();ifs.seekg(0, ios::beg);//回到文件开头重复读取}auto data = inbuf;//data和inbuf指向同一片区域while (data_size > 0) //一次有多帧数据{//通过0001 截断输出到AVPacket 返回帧大小int ret = av_parser_parse2(parser, c,&pkt->data, &pkt->size, //截断后输出到AVpacket的data中data, data_size,        //h264编码流,待处理数据AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);//返回消耗的输入数据字节数ret。如果所有输入数据都被消耗,则返回值等于 buf_size。如果解析过程中出现错误,则返回负数。data += ret;//data指针向前移动 ret个字节,继续处理inbuf未处理的数据data_size -= ret; //待处理的数据大小if (pkt->size)//如果还有截断后的avpacket流则进行解码操作{//cout << pkt->size << " "<<flush;//发送packet到解码线程ret = avcodec_send_packet(c, pkt);//把avpacket给解码器进行解码if (ret < 0)break;//获取多帧解码数据 while (ret >= 0)//如果解码成功{//每次回调用av_frame_unref ret = avcodec_receive_frame(c, frame);//拿到解码后的数据存储到frame中if (ret < 0)break;auto pframe = frame;  //为了同时支持硬解码和软解码if (c->hw_device_ctx) //硬解码{//硬解码转换GPU =》CPU ,显存=》内存//AV_PIX_FMT_NV12,      ///< planar YUV 4:2:0, 12bpp, 1 plane for Y and 1 plane for the UV components, which are interleaved (first byte U and the following byte V)av_hwframe_transfer_data(hw_frame, frame, 0);pframe = hw_frame;//如果是硬解码则pframe = hw_frame}//AV_PIX_FMT_DXVA2_VLDAV_PIX_FMT_DXVA2_VLD;cout << frame->format << " " << flush;///// 第一帧初始化窗口if (!is_init_win){is_init_win = true;view->Init(pframe->width, pframe->height, (XVideoView::Format)pframe->format);}view->DrawFrame(pframe);count++;auto cur = NowMs();if (cur - begin >= 1000)// 1秒钟计算一次{cout << "\nfps = " << count << endl;count = 0;begin = cur;}}}}}///取出缓存数据,防止丢帧int ret = avcodec_send_packet(c, NULL);while (ret >= 0){ret = avcodec_receive_frame(c, frame);if (ret < 0)break;cout << frame->format << "-" << flush;}av_parser_close(parser);avcodec_free_context(&c);av_frame_free(&frame);av_packet_free(&pkt);getchar();return 0;
}

运行结果:

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

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

相关文章

【电机控制】FOC算法验证步骤

【电机控制】FOC算法验证步骤 文章目录 前言一、PWM——不接电机1、PWMA-H-50%2、PWMB-H-25%3、PWMC-H-0%4、PWMA-L-50%5、PWMB-L-75%6、PWMC-L-100% 二、ADC——不接电机1.电流零点稳定性、ADC读取的OFFSET2.电流钳准备3.运放电路分析1.电路OFFSET2.AOP3.采样电路的采样值范围…

归并排序——逆序数对的统计

逆序数对的统计 题目描述 运行代码 #include <iostream> using namespace std; #define LL long long const int N 1e5 5; int a[N], tmp[N]; LL merge_sort(int q[], int l, int r) {if (l > r)return 0; int mid l r >> 1; LL res merge_sort(q, l,…

【数据结构(邓俊辉)学习笔记】图07——最短路径

文章目录 0. 概述1. 问题2. 最短路径2.1 最短路径树2.1.1 单调性2.1.2 歧义性2.1. 3 无环性 2.2 Dijkstra 算法2.2.1 贪心迭代2.2.2 实现2.2.3 实例2.2.4 复杂度 0. 概述 学习下最短路径和Dijistra算法 1. 问题 给定带权网络G (V, E)&#xff0c;以及源点&#xff08;source…

4.大模型微调技术LoRA

大模型低秩适配(LoRA)技术 现有PEFT 方法的局限与挑战 Adapter方法,通过增加模型深度而额外增加了模型推理延时。Prompt Tuning、Prefix Tuning、P-Tuning等方法中的提示较难训练,同时缩短了模型可用的序列长度。往往难以同时实现高效率和高质量,效果通常不及完全微调(f…

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《计及电力不平衡风险的配电网分区协同规划》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

探究IOC容器刷新环节初始化前的预处理

目录 一、IOC容器的刷新环节快速回顾 二、初始化前的预处理prepareRefresh源码分析 三、初始化属性源 &#xff08;一&#xff09;GenericWebApplicationContext初始化属性源 &#xff08;二&#xff09;StaticWebApplicationContext初始化属性源 四、初始化早期事件集合…

3.大模型高效微调PEFT

大模型高效微调(PEFT)技术 预训练模型的背景 预训练与微调:传统的微调方法通常涉及对整个预训练模型的参数进行再训练,以适应特定任务。这虽然有效,但计算成本高,且需要大量的标记数据。模型结构:像BERT或GPT这样的模型通常包含数亿甚至数十亿个参数,构成一个深层次的…

Qt——升级系列(Level Four):控件概述、QWidget 核心属性、按钮类控件

目录 控件概述 QWidget 核心属性 核心属性概览 enabled geometry windowTitle windowIcon windowOpacity cursor font toolTip focusPolicy styleSheet 按钮类控件 Push Button Radio Buttion Check Box Tool Button 控件概述 Widget 是 Qt 中的核⼼概念. 英⽂原义是 "…

西门子学习笔记11 - PTO脉冲指令的使用

1、使用指令前的设置 1、打开一个脉冲发生器&#xff0c;并启用 2、选择使用PTO(脉冲A和方向B) 3、硬件设置输出 4、这样前期的准备工作就完成了 2、指令的使用 1、添加指令CTRL_PTO 2、配置如下 3、方向控制程序如下 4、最后进行测试即可

C语言之存储类、作用域、生命周期、链接属性

一 &#xff1a;概念解析 1&#xff1a; 存储类 &#xff08;1&#xff09;存储类就是存储类型&#xff0c;就是描述C语言变量存储在什么地方 &#xff08;2&#xff09;内存有多种管理方法&#xff1a;栈、堆数据段、bss段、.text段......一个变量的存储类属性就是描述…

html--万年历

<!DOCTYPE html> <html lang"zh_CN"><head><meta http-equiv"Content-Type" content"text/html; charsetUTF-8" /><meta charset"utf-8" /><title>万年历</title><link rel"styles…

C语言 | Leetcode C语言题解之第142题环形链表II

题目&#xff1a; 题解&#xff1a; struct ListNode* detectCycle(struct ListNode* head) {struct ListNode *slow head, *fast head;while (fast ! NULL) {slow slow->next;if (fast->next NULL) {return NULL;}fast fast->next->next;if (fast slow) {s…

LLVM Cpu0 新后端9 objdump readelf

想好好熟悉一下llvm开发一个新后端都要干什么&#xff0c;于是参考了老师的系列文章&#xff1a; LLVM 后端实践笔记 代码在这里&#xff08;还没来得及准备&#xff0c;先用网盘暂存一下&#xff09;&#xff1a; 链接: https://pan.baidu.com/s/1yLAtXs9XwtyEzYSlDCSlqw?…

EE trade:如何在A股市场中有效设定止盈止损点

A股市场充满机遇和风险&#xff0c;很多投资者在这里实现了财富增长&#xff0c;也有投资者在这里遭受损失。如何在波动性较大的市场中&#xff0c;控制风险&#xff0c;保护利润和本金?止盈止损是关键。 什么是止盈止损? 止盈止损是指在交易中&#xff0c;根据预先设定的条…

如何稳定高效地进行 TiDB 数据导入导出?

对于在数据库行业中摸爬滚打多年的老鸟 DBA 来说&#xff0c;TiDB 可是一点也不陌生&#xff0c;作为 PingCAP 公司自主研发的真开源分布式数据库&#xff0c;其先进的设计理念以及丰富的生态工具&#xff0c;可算得上是业界自主创新和性能领先的代名词。 TiDB 是谁&#xff1…

MAVEN架构项目管理工具

1、什么是maven Maven是跨平台的项目管理工具。主要服务于基于Java平台的项目构建&#xff0c;依赖管理和项目信息管理。 2、maven的目标&#xff1a;Maven的主要目标是为了使开发人员在最短的时间内领会项目的所有状态 3、使用maven不需要考虑各个依赖的版本&#xff0c;因…

【将xml文件转yolov5训练数据txt标签文件】连classes.txt都可以生成

将xml文件转yolov5训练数据txt标签文件 前言一、代码解析 二、使用方法总结 前言 找遍全网&#xff0c;我觉得写得最详细的就是这个博文⇨将xml文件转yolov5训练数据txt标签文件 虽然我还是没有跑成功。那个正则表达式我不会改QWQ&#xff0c;但是不妨碍我会训练ai。 最终成功…

UE5中在地形中加入湖、河

系统水资产添加 前提步骤123 完成 前提 使用版本 UE5.0.3,使用插件为UE内置的Water和water Extras. 步骤 1 记得重启 2 增加地形&#xff0c;把<启用编辑图层>勾选 如果地形没有勾选上编辑图层&#xff0c;那么就会导致湖、河等水景象无法融入地形。 如果忘记勾选…

Hive知识体系保姆级教程

一. Hive概览 1.1 hive的简介 Hive是基于Hadoop的一个数据仓库工具&#xff0c;可以将结构化的数据文件映射为一张数据库表&#xff0c;并提供类SQL查询功能。 其本质是将SQL转换为MapReduce/Spark的任务进行运算&#xff0c;底层由HDFS来提供数据的存储&#xff0c;说白了h…

如何从 Android 图库中恢复误删除的照片

如果您正在阅读这篇文章&#xff0c;那么您肯定意外地从 Android 设备中删除了照片。并且您正在寻找一种简单的方法来恢复 Android 图库中已删除的照片。 从图库恢复已删除的照片 随着技术的进步&#xff0c;现在使用单个设备&#xff08;即 Android 手机&#xff09;&#xf…