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_; // Y
			frame_->linesize[1] = width_ / 2;//U
			frame_->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_);	//Y
		ifs_.read((char*)frame_->data[1],
			frame_->linesize[1] * height_ / 2);	//U
		ifs_.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],//Y
			frame->data[1], frame->linesize[1],	//U
			frame->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); //Y
			memcpy(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;// 移位Y
				memcpy(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.h264
    string 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);


    //硬件加速格式 DXVA2
    AVHWDeviceType 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(); //硬解码转换用这个frame
    long long begin = NowMs();
    int count = 0;//解码统计
    bool is_init_win = false;

    while (!ifs.eof())
    {
        ifs.read((char*)inbuf, sizeof(inbuf));//将h264编码的流读入inbuf中,一次读4096
        int 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_VLD
                    AV_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;
}

运行结果:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

初阶 《函数》 4. 函数的调用

4. 函数的调用 4.1 传值调用 函数的形参和实参分别占有不同内存块&#xff0c;对形参的修改不会影响实参 4.2 传址调用 传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式 这种传参方式可以让函数和函数外边的变量建立起真正的联系&#xff0c;也就是…

电脑上的瑞士军刀

一、简介 1、一款专为 Windows 操作系统设计的桌面管理工具&#xff0c;它具备保存和恢复桌面图标位置的功能&#xff0c;使用户能够在各种情况下&#xff0c;如分辨率变动、系统更新或其他原因导致的图标位置混乱后&#xff0c;快速恢复到熟悉的工作环境。它还拥有诸多实用功能…

大数据数仓的数据回溯

在大数据领域&#xff0c;数据回溯是一项至关重要的任务&#xff0c;它涉及到对历史数据的重新处理以确保数据的准确性和一致性。 数据回溯的定义与重要性 数据回溯&#xff0c;也称为数据补全&#xff0c;是指在数据模型迭代或新模型上线后&#xff0c;对历史数据进行重新处理…

Java 数据类型 -- Java 语言的 8 种基本数据类型、字符串与数组

大家好&#xff0c;我是栗筝i&#xff0c;这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 004 篇文章&#xff0c;在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验&#xff0c;并希望进…

ssm汽车在线销售系统

摘 要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&#xff0c;科学化的管理&#xff0c;使信息存…

上位机图像处理和嵌入式模块部署(f407 mcu和其他mcu品类的选择)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 很多朋友读书的时候学的是stm32&#xff0c;工作中用的也是stm32。这本来问题不大&#xff0c;但是过去两三年的经历告诉我们&#xff0c;mcu的使用…

Vmess协议是什么意思? VLESS与VMess有什么区别?

VMess 是一个基于 TCP 的加密传输协议&#xff0c;所有数据使用 TCP 传输&#xff0c;是由 V2Ray 原创并使用于 V2Ray 的加密传输协议&#xff0c;它分为入站和出站两部分&#xff0c;其作用是帮助客户端跟服务器之间建立通信。在 V2Ray 上客户端与服务器的通信主要是通过 VMes…

表格状态码转换,其他索引串转成名字

1.问题分析 原数据库 关联指标为数字串的形式&#xff0c;每个小数对应的是另一张表index的属性&#xff0c;我们想知道对应指标的名称&#xff0c;怎么在这里下面这种形式呢&#xff1f; 两种思路&#xff1a; 1.修改在后端处理&#xff0c;把后端关联指标部分修改成图二的…

数据结构之线性表(3)

数据结构之线性表&#xff08;3&#xff09; 上文我们了解了线性表的静动态存储的相关操作&#xff0c;此篇我们对线性表中链表的相关操作探讨。 在进行链表的相关操作时&#xff0c;我们先来理解单链表是什么&#xff1f; 1.链表的概念及结构 链表是一种物理存储结构上非连…

​2020-2024 idea最新安装激活

前言&#xff1a;怎么才能既免费&#xff0c;又能使用上正式版呢&#xff01;&#xff08;不是正版用不起&#xff0c;而是‘激活’更有性价比&#xff09; 1-2 下载安装&#xff0c;此处省略 记得安装好不要打开&#xff0c;看下一步。 3.开始 3.1打开idea 首先打开idea&am…

ChatGPT Prompt技术全攻略-总结篇:Prompt工程技术的未来发展

系列篇章&#x1f4a5; No.文章1ChatGPT Prompt技术全攻略-入门篇&#xff1a;AI提示工程基础2ChatGPT Prompt技术全攻略-进阶篇&#xff1a;深入Prompt工程技术3ChatGPT Prompt技术全攻略-高级篇&#xff1a;掌握高级Prompt工程技术4ChatGPT Prompt技术全攻略-应用篇&#xf…

● 343. 整数拆分 ● 96.不同的二叉搜索树

343. 整数拆分 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 示例 1: 输入: n 2 输出: 1 解释: 2 1 1, 1 1 1。 示例 2: 输入: n 10 输出: 3…

Opencv基本操作

Opencv基本操作 导入并使用opencv进行图像与视频的基本处理 opencv读取的格式是BGR import cv2 #opencv读取的格式是BGR import numpy import matplotlib.pyplot as plt %matplotlib inline图像读取 通过cv2.imread()来加载指定位置的图像信息。 img cv2.imread(./res/ca…

公式转换坑

在线LaTeX公式编辑器-编辑器 (latexlive.com) 这个好用 latex输入后转mathtype等 1 \mathcal{V}\{0,1,\ldots,|\mathcal{V}|-1\} 这个玩意在Word死活打不出来 使用下面的方法也不行 mathtype也不行 故换符号之 LaTeX公式与MathType公式如何快速转换-MathType中文网 如何在…

1909java内部知识管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java内部知识管理系统是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助采用了java设计&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统采用web模式&#xff0c;系统主要采用B/S模式开发。开 发环境为TOMCAT7.0,Myeclipse8.5开发&…

解决windows11开机xbox自启动

1、同时按键盘“ctrlaltdelete”键&#xff0c;在弹出页面中选择任务管理器&#xff1b; 2、点击启动应用 3、找到软件Xbox App Services&#xff0c;选择“已启用”点击右键&#xff0c;点击禁用&#xff1b;

Redis使用中的性能优化——搭建Redis的监测服务

大纲 环境安装配置Redis安装 安装配置redis_exporter编译运行直接运行以服务形式运行 安装启动Prometheus创建用户下载并解压修改配置启动 安装启动grafana安装启动 测试参考资料 抛开场景和数据&#xff0c;谈论性能优化&#xff0c;就是纸上谈兵。这个系列我们将通过相关数据…

【Python深度学习】——信息量|熵

【Python深度学习】——信息量|熵 假设1. 信息量1.1 含义1.2 信息量的公式: 2. 熵Entropy2. 含义2.2 熵的计算公式:2.3 熵的作用 假设 例子&#xff1a;掷硬币 假设我们有一个公平的硬币。这个硬币有两个面&#xff1a;正面&#xff08;H&#xff09;和反面&#xff08;T&…

Netty

优势 1.API使用简单&#xff0c;开发门槛低 2.功能强大&#xff0c;预置了多种编码功能&#xff0c;支持多种主流协议&#xff1b; 3.定制能力强&#xff0c;可以通过channelHandler对通信框架进行灵活地扩展&#xff1b; 4.性能高&#xff0c;通过与其他业界主流的NIO框架对比…

C++网络编程基础

文章目录 协议局域网通信IP 地址网络通信的本质tcp 和 udp 协议网络字节序网络主机数据转化接口 协议 协议&#xff1a;收到数据后&#xff0c;多出来的那一部分&#xff0c;也叫一种 “约定”&#xff0c;一整套的自硬件到软件&#xff0c;都有协议&#xff0c;需要有人定制&a…