├── IntelCudaVideoDecodDetect.pro ├── Nv12Render_Gpu.dll ├── NvidiaDecoderPlugin.dll ├── README.md ├── VideoWidget ├── render │ ├── factory.h │ ├── nv12render.cpp │ ├── nv12render.h │ ├── videorender.h │ ├── yuvrender.cpp │ └── yuvrender.h ├── renderthread.cpp ├── renderthread.h ├── videodecode │ ├── decodetaskmanagerimpl.cpp │ ├── decodetaskmanagerimpl.h │ ├── decodtask.cpp │ ├── decodtask.h │ ├── ffmpegcpudecode.cpp │ ├── ffmpegcpudecode.h │ ├── ffmpegcudadecode.cpp │ ├── ffmpegcudadecode.h │ ├── ffmpegqsvdecode.cpp │ ├── ffmpegqsvdecode.h │ ├── nvidia_decoer_api.h │ ├── nvidiadecode.cpp │ └── nvidiadecode.h ├── videowidget.cpp ├── videowidget.h └── videowidget.pri ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h └── run.png /IntelCudaVideoDecodDetect.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2020-07-21T15:05:06 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui 8 | 9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 | 11 | TARGET = IntelCudaVideoDecodDetect 12 | TEMPLATE = app 13 | 14 | # The following define makes your compiler emit warnings if you use 15 | # any feature of Qt which has been marked as deprecated (the exact warnings 16 | # depend on your compiler). Please consult the documentation of the 17 | # deprecated API in order to know how to port your code away from it. 18 | DEFINES += QT_DEPRECATED_WARNINGS 19 | 20 | # You can also make your code fail to compile if you use deprecated APIs. 21 | # In order to do so, uncomment the following line. 22 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 23 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 24 | 25 | CONFIG += c++11 26 | 27 | include(VideoWidget/videowidget.pri) 28 | SOURCES += \ 29 | main.cpp \ 30 | mainwindow.cpp 31 | 32 | HEADERS += \ 33 | mainwindow.h 34 | 35 | INCLUDEPATH += E:/ffmpeg/x64/include \ 36 | $$(INTELMEDIASDKROOT)/include 37 | 38 | LIBS += -LE:/ffmpeg/x64/lib \ 39 | avcodec.lib avdevice.lib avfilter.lib avformat.lib avutil.lib postproc.lib swresample.lib swscale.lib 40 | 41 | # Default rules for deployment. 42 | qnx: target.path = /tmp/$${TARGET}/bin 43 | else: unix:!android: target.path = /opt/$${TARGET}/bin 44 | !isEmpty(target.path): INSTALLS += target 45 | -------------------------------------------------------------------------------- /Nv12Render_Gpu.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitHubwhl562916378/IntelCudaVideoDecodDetect/1f6d5a7c857f52b5e2cc2c46a5024ba72984f3c3/Nv12Render_Gpu.dll -------------------------------------------------------------------------------- /NvidiaDecoderPlugin.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitHubwhl562916378/IntelCudaVideoDecodDetect/1f6d5a7c857f52b5e2cc2c46a5024ba72984f3c3/NvidiaDecoderPlugin.dll -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![IntelCudaVideoDecodDetect](run.png) 2 | ## IntelCudaVideoDecodDetect 3 | 一个使用ffmpeg硬解码的测试工具,加入了Qt显示,用于测量解码性能;`目前包括cuda和qsv解码`。 4 | 测量时可关闭显示,观看帧率以判断解码性能 5 | 6 | ## 编译环境 7 | * windows 64bit 8 | * ffmpeg-4.3 [https://ffmpeg.zeranoe.com/builds/](https://ffmpeg.zeranoe.com/builds/) 9 | * Version 选择版本4.3 10 | * Architecture 选择64bit 11 | * Linking Dev下载一次,Shared和Static选一个下载;我的工程是使用的Shared 12 | * nvidia独立显卡 13 | * 驱动版本436.15或以上[https://www.nvidia.cn/Download/index.aspx?lang=cn](https://www.nvidia.cn/Download/index.aspx?lang=cn) 14 | * cpu 15 | * 建议i5及以上 16 | * 带核显,`需要将显示器接到核显。本程序接到独显,qsv解码会失败` 17 | * 下载IntelMedia-SDK[https://software.intel.com/content/www/cn/zh/develop/tools/media-sdk.html](https://software.intel.com/content/www/cn/zh/develop/tools/media-sdk.html) 18 | * 安装好后,需要将C:\Program Files (x86)\IntelSWTools\Intel(R) Media SDK 2020 R1\Software Development Kit\include里面`再建一个mfx目录,把所有头文件放进去` 19 | * Qt 20 | * 低版本注意配置qopenglwidget,高版本不用配置 21 | 22 | ## 编译 23 | * 打开.pro,选择64bit。 24 | * 配置ffmpeg的头文件和库文件路径 25 | * 编译 26 | 27 | ## 运行 28 | * 默认为带视频显示,地址栏输入rtsp地址。先在vlc中播放,`确保视频正常` 29 | * 每次play,会依次在下面窗口播放视频 30 | * 将videowidget.cpp中connect(m_decoThr,SIGNAL(sigFrameLoaded()),this,SLOT(update()));去掉即可去掉视频显示 31 | * 将ffmpegqsvdecode.cpp,ffmpegqsvdecode.cpp中的av_image_copy_to_buffer去掉可以去掉拷贝 32 | * 下载GPU-Z可以观察硬件运行情况 33 | 34 | ## 其它 35 | * tag1.0分支的`IntelCudaVideoDecodDetect.rar`为编译好的可执行包,可以直接运行不用配置环境编译 36 | * tag1.0分支有一个`解码性能报告.xlsx`可供参考 37 | * tag1.0分支的intelmedia-sdk下载需要注册,所以下载好了,放这儿。是64bit的 38 | 39 | ## 更新 40 | 41 | ### 2020/8/4 42 | * 更换最新的离屏渲染,cuda与opengl交互模块。提升性能 -------------------------------------------------------------------------------- /VideoWidget/render/factory.h: -------------------------------------------------------------------------------- 1 | #ifndef FACTORY_H 2 | #define FACTORY_H 3 | 4 | #include 5 | #include 6 | template 7 | class DefaultFactoryError 8 | { 9 | public: 10 | class Exception : public std::exception 11 | { 12 | public: 13 | Exception(const IdentifierType& unkownId) 14 | :unknownId_(unkownId){} 15 | 16 | const char* what() const noexcept override 17 | { 18 | return "Unknown object type passed to Factory"; 19 | } 20 | 21 | const IdentifierType GetId() 22 | { 23 | return unknownId_; 24 | } 25 | 26 | private: 27 | IdentifierType unknownId_; 28 | }; 29 | 30 | protected: 31 | ProductType* OnUnknownType(const IdentifierType& id) 32 | { 33 | throw Exception(id); 34 | } 35 | }; 36 | 37 | template 38 | < 39 | class AbstractProduct, 40 | class IdentifierType, 41 | class ProductCreator = std::function, 42 | template 43 | class FactoryErrorPolicy = DefaultFactoryError 44 | > 45 | class Factory 46 | : public FactoryErrorPolicy 47 | { 48 | public: 49 | bool Register(const IdentifierType& id, ProductCreator functor) 50 | { 51 | return associations_.insert(std::make_pair(id, functor)).second; 52 | } 53 | 54 | bool Unregister(const IdentifierType& id) 55 | { 56 | return associations_.erase(id); 57 | } 58 | 59 | AbstractProduct* CreateObject(const IdentifierType& id) 60 | { 61 | typename std::map::const_iterator i = associations_.find(id); 62 | if(i != associations_.end()) 63 | { 64 | return (i->second)(); 65 | } 66 | 67 | return this->OnUnknownType(id); 68 | } 69 | 70 | private: 71 | std::map associations_; 72 | }; 73 | 74 | #endif // FACTORY_H 75 | -------------------------------------------------------------------------------- /VideoWidget/render/nv12render.cpp: -------------------------------------------------------------------------------- 1 | #include "nv12render.h" 2 | #include 3 | #include 4 | #include 5 | 6 | Nv12Render::~Nv12Render() 7 | { 8 | vbo.destroy(); 9 | glDeleteTextures(sizeof(textures) / sizeof(GLuint),textures); 10 | if(buffer_){ 11 | delete buffer_; 12 | buffer_ = nullptr; 13 | } 14 | } 15 | 16 | Q_GLOBAL_STATIC(QMutex, initMutex) 17 | void Nv12Render::initialize(const int width, const int height, const bool horizontal, const bool vertical) 18 | { 19 | initializeOpenGLFunctions(); 20 | QMutexLocker initLock(initMutex()); 21 | const char *vsrc = 22 | "attribute vec4 vertexIn; \ 23 | attribute vec4 textureIn; \ 24 | varying vec4 textureOut; \ 25 | void main(void) \ 26 | { \ 27 | gl_Position = vertexIn; \ 28 | textureOut = textureIn; \ 29 | }"; 30 | 31 | const char *fsrc = 32 | "varying mediump vec4 textureOut;\n" 33 | "uniform sampler2D textureY;\n" 34 | "uniform sampler2D textureUV;\n" 35 | "void main(void)\n" 36 | "{\n" 37 | "vec3 yuv; \n" 38 | "vec3 rgb; \n" 39 | "yuv.x = texture2D(textureY, textureOut.st).r - 0.0625; \n" 40 | "yuv.y = texture2D(textureUV, textureOut.st).r - 0.5; \n" 41 | "yuv.z = texture2D(textureUV, textureOut.st).g - 0.5; \n" 42 | "rgb = mat3( 1, 1, 1, \n" 43 | "0, -0.39465, 2.03211, \n" 44 | "1.13983, -0.58060, 0) * yuv; \n" 45 | "gl_FragColor = vec4(rgb, 1); \n" 46 | "}\n"; 47 | 48 | program.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex,vsrc); 49 | program.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment,fsrc); 50 | program.link(); 51 | 52 | if(horizontal){ 53 | if(vertical){ 54 | GLfloat points[]{ 55 | -1.0f, 1.0f, 56 | 1.0f, 1.0f, 57 | 1.0f, -1.0f, 58 | -1.0f, -1.0f, 59 | 60 | 1.0f,1.0f, 61 | 0.0f,1.0f, 62 | 0.0f,0.0f, 63 | 1.0f,0.0f 64 | }; 65 | 66 | vbo.create(); 67 | vbo.bind(); 68 | vbo.allocate(points,sizeof(points)); 69 | }else{ 70 | GLfloat points[]{ 71 | -1.0f, 1.0f, 72 | 1.0f, 1.0f, 73 | 1.0f, -1.0f, 74 | -1.0f, -1.0f, 75 | 76 | 1.0f,0.0f, 77 | 0.0f,0.0f, 78 | 0.0f,1.0f, 79 | 1.0f,1.0f 80 | }; 81 | 82 | vbo.create(); 83 | vbo.bind(); 84 | vbo.allocate(points,sizeof(points)); 85 | } 86 | }else{ 87 | if(vertical){ 88 | GLfloat points[]{ 89 | -1.0f, 1.0f, 90 | 1.0f, 1.0f, 91 | 1.0f, -1.0f, 92 | -1.0f, -1.0f, 93 | 94 | 0.0f,1.0f, 95 | 1.0f,1.0f, 96 | 1.0f,0.0f, 97 | 0.0f,0.0f 98 | }; 99 | 100 | vbo.create(); 101 | vbo.bind(); 102 | vbo.allocate(points,sizeof(points)); 103 | }else{ 104 | GLfloat points[]{ 105 | -1.0f, 1.0f, 106 | 1.0f, 1.0f, 107 | 1.0f, -1.0f, 108 | -1.0f, -1.0f, 109 | 110 | 0.0f,0.0f, 111 | 1.0f,0.0f, 112 | 1.0f,1.0f, 113 | 0.0f,1.0f 114 | }; 115 | 116 | vbo.create(); 117 | vbo.bind(); 118 | vbo.allocate(points,sizeof(points)); 119 | } 120 | } 121 | 122 | GLuint id[2]; 123 | glGenTextures(2,id); 124 | idY = id[0]; 125 | idUV = id[1]; 126 | std::copy(std::begin(id),std::end(id),std::begin(textures)); 127 | 128 | glBindTexture(GL_TEXTURE_2D,idY); 129 | glTexImage2D(GL_TEXTURE_2D,0,GL_RED,width,height,0,GL_RED,GL_UNSIGNED_BYTE,nullptr); 130 | glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); 131 | glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); 132 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 133 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 134 | 135 | 136 | glBindTexture(GL_TEXTURE_2D,idUV); 137 | glTexImage2D(GL_TEXTURE_2D,0,GL_RG,width >> 1,height >> 1,0,GL_RG,GL_UNSIGNED_BYTE,nullptr); 138 | glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); 139 | glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); 140 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 141 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 142 | } 143 | 144 | void Nv12Render::render(uchar *nv12Ptr, const int w, const int h) 145 | { 146 | glClearColor(0.0f, 0.0f, 0.0f, 0.0f); 147 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 148 | glDisable(GL_DEPTH_TEST); 149 | glDisable(GL_CULL_FACE); 150 | glDepthMask(false); 151 | if(!nv12Ptr){ 152 | return; 153 | } 154 | 155 | program.bind(); 156 | vbo.bind(); 157 | program.enableAttributeArray("vertexIn"); 158 | program.enableAttributeArray("textureIn"); 159 | program.setAttributeBuffer("vertexIn",GL_FLOAT, 0, 2, 2*sizeof(GLfloat)); 160 | program.setAttributeBuffer("textureIn",GL_FLOAT,2 * 4 * sizeof(GLfloat),2,2*sizeof(GLfloat)); 161 | 162 | glActiveTexture(GL_TEXTURE0 + 1); 163 | glBindTexture(GL_TEXTURE_2D,idY); 164 | glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RED, GL_UNSIGNED_BYTE, nv12Ptr); 165 | 166 | glActiveTexture(GL_TEXTURE0 + 0); 167 | glBindTexture(GL_TEXTURE_2D,idUV); 168 | glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w >> 1,h >> 1, GL_RG, GL_UNSIGNED_BYTE, nv12Ptr + w*h); 169 | 170 | program.setUniformValue("textureY",1); 171 | program.setUniformValue("textureUV",0); 172 | glDrawArrays(GL_QUADS,0,4); 173 | program.disableAttributeArray("vertexIn"); 174 | program.disableAttributeArray("textureIn"); 175 | vbo.release(); 176 | program.release(); 177 | } 178 | 179 | void Nv12Render::render(unsigned char *planr[], const int line_size[], const int width, const int height) 180 | { 181 | if(!planr){ 182 | glClearColor(0.0f, 0.0f, 0.0f, 0.0f); 183 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 184 | glDisable(GL_DEPTH_TEST); 185 | glDisable(GL_CULL_FACE); 186 | glDepthMask(false); 187 | return; 188 | } 189 | 190 | if(!buffer_){ 191 | buffer_ = new unsigned char[width * height * 3 / 2]; 192 | } 193 | 194 | int bytes = 0; //yuv data有3块内存分别拷,nv12只有2块内存分别拷 195 | for(int i = 0; i > 1; 200 | for(int i = 0; i < uv; i++){ //将u分量拷贝 201 | ::memcpy(buffer_ + bytes,planr[1] + line_size[1] * i, width); 202 | bytes += width; 203 | } 204 | render(buffer_, width, height); 205 | } 206 | 207 | void Nv12Render::upLoad(unsigned char *buffer, const int w, const int h) 208 | { 209 | if(!buffer){ 210 | return; 211 | } 212 | 213 | QMutexLocker lock(&mtx); 214 | glActiveTexture(GL_TEXTURE0 + 1); 215 | glBindTexture(GL_TEXTURE_2D,idY); 216 | glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RED, GL_UNSIGNED_BYTE, buffer); 217 | 218 | glActiveTexture(GL_TEXTURE0 + 0); 219 | glBindTexture(GL_TEXTURE_2D,idUV); 220 | glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w >> 1,h >> 1, GL_RG, GL_UNSIGNED_BYTE, buffer + w*h); 221 | glFlush(); 222 | } 223 | 224 | void Nv12Render::upLoad(unsigned char* planr[], const int line_size[], const int width, const int height) 225 | { 226 | if(!planr){ 227 | return; 228 | } 229 | 230 | if(!buffer_){ 231 | buffer_ = new unsigned char[width * height * 3 / 2]; 232 | } 233 | 234 | int bytes = 0; //yuv data有3块内存分别拷,nv12只有2块内存分别拷 235 | for(int i = 0; i > 1; 240 | for(int i = 0; i < uv; i++){ //将u分量拷贝 241 | ::memcpy(buffer_ + bytes,planr[1] + line_size[1] * i, width); 242 | bytes += width; 243 | } 244 | upLoad(buffer_, width, height); 245 | } 246 | 247 | void Nv12Render::draw() 248 | { 249 | QMutexLocker lock(&mtx); 250 | glClearColor(0.0f, 0.0f, 0.0f, 0.0f); 251 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 252 | glDisable(GL_DEPTH_TEST); 253 | glDisable(GL_CULL_FACE); 254 | glDepthMask(false); 255 | 256 | program.bind(); 257 | vbo.bind(); 258 | program.enableAttributeArray("vertexIn"); 259 | program.enableAttributeArray("textureIn"); 260 | program.setAttributeBuffer("vertexIn",GL_FLOAT, 0, 2, 2*sizeof(GLfloat)); 261 | program.setAttributeBuffer("textureIn",GL_FLOAT,2 * 4 * sizeof(GLfloat),2,2*sizeof(GLfloat)); 262 | 263 | glActiveTexture(GL_TEXTURE0 + 1); 264 | glBindTexture(GL_TEXTURE_2D,idY); 265 | 266 | glActiveTexture(GL_TEXTURE0 + 0); 267 | glBindTexture(GL_TEXTURE_2D,idUV); 268 | 269 | program.setUniformValue("textureY",1); 270 | program.setUniformValue("textureUV",0); 271 | glDrawArrays(GL_QUADS,0,4); 272 | program.disableAttributeArray("vertexIn"); 273 | program.disableAttributeArray("textureIn"); 274 | vbo.release(); 275 | program.release(); 276 | } 277 | -------------------------------------------------------------------------------- /VideoWidget/render/nv12render.h: -------------------------------------------------------------------------------- 1 | #ifndef NV12RENDER_H 2 | #define NV12RENDER_H 3 | #include "videorender.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class Nv12Render : public QOpenGLFunctions, public VideoRender 10 | { 11 | public: 12 | Nv12Render() = default; 13 | Nv12Render(const Nv12Render&) = delete; 14 | ~Nv12Render() override; 15 | void initialize(const int width, const int height, const bool horizontal = false, const bool vertical = false) override; 16 | void render(uchar*nv12Ptr, const int w, const int h) override; 17 | void render(unsigned char* planr[], const int line_size[], const int width, const int height) override; 18 | void upLoad(unsigned char* buffer, const int w, const int h) override; 19 | void upLoad(unsigned char* planr[], const int line_size[], const int width, const int height) override; 20 | void draw() override; 21 | 22 | private: 23 | QMutex mtx; 24 | QOpenGLShaderProgram program; 25 | GLuint idY,idUV, textures[2]; 26 | QOpenGLBuffer vbo; 27 | unsigned char* buffer_ = nullptr; 28 | }; 29 | 30 | #endif // NV12RENDER_H 31 | -------------------------------------------------------------------------------- /VideoWidget/render/videorender.h: -------------------------------------------------------------------------------- 1 | #ifndef VIDEORENDER_H 2 | #define VIDEORENDER_H 3 | 4 | extern "C" { 5 | #include "libavutil/pixfmt.h" 6 | } 7 | 8 | class VideoRender 9 | { 10 | public: 11 | virtual ~VideoRender(){} 12 | /** 13 | * @description: 初始化opengl上下文,编译链接shader;如果是GPU直接与OOPENGL对接数据,则会分配GPU内存或注册资源 14 | * @param width 视频宽度 15 | * @param height 视频高度 16 | * @param horizontal 是否水平镜像 17 | * @param vertical 是否垂直镜像 18 | */ 19 | virtual void initialize(const int width, const int height, const bool horizontal = false, const bool vertical = false) = 0; 20 | /** 21 | * @description: 渲染一帧数据,buffer需要为连续空间 22 | * @param buffer 内存地址 23 | * @param width 视频帧宽度 24 | * @param height 视频帧高度 25 | */ 26 | virtual void render(unsigned char* buffer, const int width, const int height) = 0; 27 | /** 28 | * @description: 渲染一帧分离在多个planr的数据 29 | * @param planr 多个平面地址的指针数组。按照默认格式排序,如YUV为0(Y分量)、1(U分量)、2(V分量); NV12为0(Y分量)、1(UV分量) 30 | * @param line_size 二维图片的每行字节大小,也是GPU内存的nPitch 31 | * @param width 视频帧宽度 32 | * @param height 视频帧高度 33 | */ 34 | virtual void render(unsigned char* planr[], const int line_size[], const int width, const int height) = 0; 35 | /** 36 | * @description: 异步加载数据到纹理 37 | * @param buffer 连续内存地址 38 | * @param width 视频帧宽度 39 | * @param height 视频帧高度 40 | */ 41 | virtual void upLoad(unsigned char* buffer, const int width, const int height) = 0; 42 | /** 43 | * @description: 异步加载数一个分散在多个planr的数据到纹理 44 | * @param planr 多个平面地址的指针数组。按照默认格式排序,如YUV为0(Y分量)、1(U分量)、2(V分量); NV12为0(Y分量)、1(UV分量) 45 | * @param line_size 二维图片的每行字节大小,也是GPU内存的nPitch 46 | * @param width 视频帧宽度 47 | * @param height 视频帧高度 48 | */ 49 | virtual void upLoad(unsigned char* planr[], const int line_size[], const int width, const int height) = 0; 50 | /** 51 | * @description: 异步绘制纹理数据 52 | */ 53 | virtual void draw() = 0; 54 | }; 55 | 56 | typedef VideoRender* (*CreateRenderFunc)(void *ctx); 57 | #endif // VIDEORENDER_H 58 | -------------------------------------------------------------------------------- /VideoWidget/render/yuvrender.cpp: -------------------------------------------------------------------------------- 1 | #include "yuvrender.h" 2 | #include 3 | #include 4 | #include 5 | YuvRender::~YuvRender() 6 | { 7 | vbo.destroy(); 8 | glDeleteTextures(sizeof(textures) / sizeof(GLuint),textures); 9 | if(buffer_){ 10 | delete buffer_; 11 | buffer_ = nullptr; 12 | } 13 | } 14 | 15 | Q_GLOBAL_STATIC(QMutex, initMutex) 16 | void YuvRender::initialize(const int width, const int height, const bool horizontal, const bool vertical) 17 | { 18 | initializeOpenGLFunctions(); 19 | QMutexLocker initLock(initMutex()); 20 | const char *vsrc = 21 | "attribute vec4 vertexIn; \ 22 | attribute vec4 textureIn; \ 23 | varying vec4 textureOut; \ 24 | void main(void) \ 25 | { \ 26 | gl_Position = vertexIn; \ 27 | textureOut = textureIn; \ 28 | }"; 29 | 30 | const char *fsrc = 31 | "varying mediump vec4 textureOut;\ 32 | uniform sampler2D tex_y; \ 33 | uniform sampler2D tex_u; \ 34 | uniform sampler2D tex_v; \ 35 | void main(void) \ 36 | { \ 37 | vec3 yuv; \ 38 | vec3 rgb; \ 39 | yuv.x = texture2D(tex_y, textureOut.st).r; \ 40 | yuv.y = texture2D(tex_u, textureOut.st).r - 0.5; \ 41 | yuv.z = texture2D(tex_v, textureOut.st).r - 0.5; \ 42 | rgb = mat3( 1, 1, 1, \ 43 | 0, -0.39465, 2.03211, \ 44 | 1.13983, -0.58060, 0) * yuv; \ 45 | gl_FragColor = vec4(rgb, 1); \ 46 | }"; 47 | 48 | program.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex,vsrc); 49 | program.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment,fsrc); 50 | program.link(); 51 | 52 | if(horizontal){ 53 | if(vertical){ 54 | GLfloat points[]{ 55 | -1.0f, 1.0f, 56 | 1.0f, 1.0f, 57 | 1.0f, -1.0f, 58 | -1.0f, -1.0f, 59 | 60 | 1.0f,1.0f, 61 | 0.0f,1.0f, 62 | 0.0f,0.0f, 63 | 1.0f,0.0f 64 | }; 65 | 66 | vbo.create(); 67 | vbo.bind(); 68 | vbo.allocate(points,sizeof(points)); 69 | }else{ 70 | GLfloat points[]{ 71 | -1.0f, 1.0f, 72 | 1.0f, 1.0f, 73 | 1.0f, -1.0f, 74 | -1.0f, -1.0f, 75 | 76 | 1.0f,0.0f, 77 | 0.0f,0.0f, 78 | 0.0f,1.0f, 79 | 1.0f,1.0f 80 | }; 81 | 82 | vbo.create(); 83 | vbo.bind(); 84 | vbo.allocate(points,sizeof(points)); 85 | } 86 | }else{ 87 | if(vertical){ 88 | GLfloat points[]{ 89 | -1.0f, 1.0f, 90 | 1.0f, 1.0f, 91 | 1.0f, -1.0f, 92 | -1.0f, -1.0f, 93 | 94 | 0.0f,1.0f, 95 | 1.0f,1.0f, 96 | 1.0f,0.0f, 97 | 0.0f,0.0f 98 | }; 99 | 100 | vbo.create(); 101 | vbo.bind(); 102 | vbo.allocate(points,sizeof(points)); 103 | }else{ 104 | GLfloat points[]{ 105 | -1.0f, 1.0f, 106 | 1.0f, 1.0f, 107 | 1.0f, -1.0f, 108 | -1.0f, -1.0f, 109 | 110 | 0.0f,0.0f, 111 | 1.0f,0.0f, 112 | 1.0f,1.0f, 113 | 0.0f,1.0f 114 | }; 115 | 116 | vbo.create(); 117 | vbo.bind(); 118 | vbo.allocate(points,sizeof(points)); 119 | } 120 | } 121 | 122 | GLuint id[3]; 123 | glGenTextures(3,id); 124 | idY = id[0]; 125 | idU = id[1]; 126 | idV = id[2]; 127 | std::copy(std::begin(id),std::end(id),std::begin(textures)); 128 | 129 | glBindTexture(GL_TEXTURE_2D,idY); 130 | glTexImage2D(GL_TEXTURE_2D,0,GL_RED,width,height,0,GL_RED,GL_UNSIGNED_BYTE,nullptr); 131 | //https://blog.csdn.net/xipiaoyouzi/article/details/53584798 纹理参数解析 132 | glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR ); 133 | glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR ); 134 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 135 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 136 | 137 | glBindTexture(GL_TEXTURE_2D,idU); 138 | glTexImage2D(GL_TEXTURE_2D,0,GL_RED,width >> 1, height >> 1,0,GL_RED,GL_UNSIGNED_BYTE,nullptr); 139 | glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR ); 140 | glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR ); 141 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 142 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 143 | 144 | glBindTexture(GL_TEXTURE_2D,idV); 145 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width >> 1, height >> 1, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr); 146 | glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR ); 147 | glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR ); 148 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 149 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 150 | } 151 | 152 | void YuvRender::render(uchar *yuvPtr, const int w, const int h) 153 | { 154 | glClearColor(0.0f, 0.0f, 0.0f, 0.0f); 155 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 156 | glDisable(GL_DEPTH_TEST); 157 | glDisable(GL_CULL_FACE); 158 | glDepthMask(false); 159 | if(!yuvPtr){ 160 | return; 161 | } 162 | 163 | program.bind(); 164 | vbo.bind(); 165 | program.enableAttributeArray("vertexIn"); 166 | program.enableAttributeArray("textureIn"); 167 | program.setAttributeBuffer("vertexIn",GL_FLOAT, 0, 2, 2*sizeof(GLfloat)); 168 | program.setAttributeBuffer("textureIn",GL_FLOAT,2 * 4 * sizeof(GLfloat),2,2*sizeof(GLfloat)); 169 | 170 | glActiveTexture(GL_TEXTURE0 + 2); 171 | glBindTexture(GL_TEXTURE_2D,idY); 172 | glTexSubImage2D(GL_TEXTURE_2D,0,0,0,w,h,GL_RED,GL_UNSIGNED_BYTE,yuvPtr); 173 | //https://blog.csdn.net/xipiaoyouzi/article/details/53584798 纹理参数解析 174 | 175 | glActiveTexture(GL_TEXTURE0 + 1); 176 | glBindTexture(GL_TEXTURE_2D,idU); 177 | glTexSubImage2D(GL_TEXTURE_2D,0,0,0,w >> 1, h >> 1,GL_RED,GL_UNSIGNED_BYTE,yuvPtr + w * h); 178 | 179 | glActiveTexture(GL_TEXTURE0 + 0); 180 | glBindTexture(GL_TEXTURE_2D,idV); 181 | glTexSubImage2D(GL_TEXTURE_2D,0,0,0,w >> 1, h >> 1,GL_RED,GL_UNSIGNED_BYTE,yuvPtr + w*h*5/4); 182 | 183 | program.setUniformValue("tex_y",2); 184 | program.setUniformValue("tex_u",1); 185 | program.setUniformValue("tex_v",0); 186 | glDrawArrays(GL_QUADS,0,4); 187 | program.disableAttributeArray("vertexIn"); 188 | program.disableAttributeArray("textureIn"); 189 | vbo.release(); 190 | program.release(); 191 | } 192 | 193 | void YuvRender::render(unsigned char *planr[], const int line_size[], const int width, const int height) 194 | { 195 | if(!planr){ 196 | glClearColor(0.0f, 0.0f, 0.0f, 0.0f); 197 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 198 | glDisable(GL_DEPTH_TEST); 199 | glDisable(GL_CULL_FACE); 200 | glDepthMask(false); 201 | return; 202 | } 203 | 204 | if(!buffer_){ 205 | buffer_ = new unsigned char[width * height * 3 / 2]; 206 | } 207 | 208 | int bytes = 0; //yuv data有3块内存分别拷,nv12只有2块内存分别拷 209 | for(int i = 0; i > 1; 214 | for(int i = 0; i < uv; i++){ //将u分量拷贝 215 | ::memcpy(buffer_ + bytes,planr[1] + line_size[1] * i, width >> 1); 216 | bytes += width >> 1; 217 | } 218 | for(int i = 0; i < uv; i++){ //将v分量拷贝 219 | ::memcpy(buffer_ + bytes,planr[2] + line_size[2] * i, width >> 1); 220 | bytes += width >> 1; 221 | } 222 | render(buffer_, width, height); 223 | } 224 | 225 | void YuvRender::upLoad(unsigned char *buffer, const int w, const int h) 226 | { 227 | if(!buffer){ 228 | return; 229 | } 230 | 231 | QMutexLocker lock(&mtx); 232 | glActiveTexture(GL_TEXTURE0 + 2); 233 | glBindTexture(GL_TEXTURE_2D,idY); 234 | glTexSubImage2D(GL_TEXTURE_2D,0,0,0,w,h,GL_RED,GL_UNSIGNED_BYTE,buffer); 235 | //https://blog.csdn.net/xipiaoyouzi/article/details/53584798 纹理参数解析 236 | 237 | glActiveTexture(GL_TEXTURE0 + 1); 238 | glBindTexture(GL_TEXTURE_2D,idU); 239 | glTexSubImage2D(GL_TEXTURE_2D,0,0,0,w >> 1, h >> 1,GL_RED,GL_UNSIGNED_BYTE,buffer + w * h); 240 | 241 | glActiveTexture(GL_TEXTURE0 + 0); 242 | glBindTexture(GL_TEXTURE_2D,idV); 243 | glTexSubImage2D(GL_TEXTURE_2D,0,0,0,w >> 1, h >> 1,GL_RED,GL_UNSIGNED_BYTE,buffer + w*h*5/4); 244 | glFlush(); 245 | } 246 | 247 | void YuvRender::upLoad(unsigned char *planr[], const int line_size[], const int width, const int height) 248 | { 249 | if(!planr){ 250 | return; 251 | } 252 | 253 | if(!buffer_){ 254 | buffer_ = new unsigned char[width * height * 3 / 2]; 255 | } 256 | 257 | int bytes = 0; //yuv data有3块内存分别拷,nv12只有2块内存分别拷 258 | for(int i = 0; i > 1; 263 | for(int i = 0; i < uv; i++){ //将u分量拷贝 264 | ::memcpy(buffer_ + bytes,planr[1] + line_size[1] * i, width >> 1); 265 | bytes += width >> 1; 266 | } 267 | for(int i = 0; i < uv; i++){ //将v分量拷贝 268 | ::memcpy(buffer_ + bytes,planr[2] + line_size[2] * i, width >> 1); 269 | bytes += width >> 1; 270 | } 271 | upLoad(buffer_, width, height); 272 | } 273 | 274 | void YuvRender::draw() 275 | { 276 | QMutexLocker lock(&mtx); 277 | glClearColor(0.0f, 0.0f, 0.0f, 0.0f); 278 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 279 | glDisable(GL_DEPTH_TEST); 280 | glDisable(GL_CULL_FACE); 281 | glDepthMask(false); 282 | 283 | program.bind(); 284 | vbo.bind(); 285 | program.enableAttributeArray("vertexIn"); 286 | program.enableAttributeArray("textureIn"); 287 | program.setAttributeBuffer("vertexIn",GL_FLOAT, 0, 2, 2*sizeof(GLfloat)); 288 | program.setAttributeBuffer("textureIn",GL_FLOAT,2 * 4 * sizeof(GLfloat),2,2*sizeof(GLfloat)); 289 | 290 | glActiveTexture(GL_TEXTURE0 + 2); 291 | glBindTexture(GL_TEXTURE_2D,idY); 292 | 293 | glActiveTexture(GL_TEXTURE0 + 1); 294 | glBindTexture(GL_TEXTURE_2D,idU); 295 | 296 | glActiveTexture(GL_TEXTURE0 + 0); 297 | glBindTexture(GL_TEXTURE_2D,idV); 298 | 299 | program.setUniformValue("tex_y",2); 300 | program.setUniformValue("tex_u",1); 301 | program.setUniformValue("tex_v",0); 302 | glDrawArrays(GL_QUADS,0,4); 303 | program.disableAttributeArray("vertexIn"); 304 | program.disableAttributeArray("textureIn"); 305 | vbo.release(); 306 | program.release(); 307 | } 308 | -------------------------------------------------------------------------------- /VideoWidget/render/yuvrender.h: -------------------------------------------------------------------------------- 1 | #ifndef YUVRENDER_H 2 | #define YUVRENDER_H 3 | #include "videorender.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class YuvRender : public QOpenGLFunctions , public VideoRender 10 | { 11 | public: 12 | YuvRender() = default; 13 | YuvRender(const YuvRender &) = delete; 14 | ~YuvRender() override; 15 | void initialize(const int width, const int height, const bool horizontal = false, const bool vertical = false) override; 16 | void render(uchar*yuvPtr, const int w, const int h) override; 17 | void render(unsigned char* planr[], const int line_size[], const int width, const int height) override; 18 | void upLoad(unsigned char* buffer, const int w, const int h) override; 19 | void upLoad(unsigned char* planr[], const int line_size[], const int width, const int height) override; 20 | void draw() override; 21 | 22 | private: 23 | QMutex mtx; 24 | QOpenGLShaderProgram program; 25 | GLuint idY,idU,idV,textures[3]; 26 | QOpenGLBuffer vbo; 27 | unsigned char* buffer_ = nullptr; 28 | }; 29 | 30 | #endif // YUVRENDER_H 31 | -------------------------------------------------------------------------------- /VideoWidget/renderthread.cpp: -------------------------------------------------------------------------------- 1 | extern "C" 2 | { 3 | #include "libavformat/avformat.h" 4 | } 5 | #include 6 | #include 7 | #include 8 | #include "videowidget.h" 9 | #include "render/nv12render.h" 10 | #include "render/yuvrender.h" 11 | #include "videodecode/decodtask.h" 12 | #include "renderthread.h" 13 | 14 | Q_GLOBAL_STATIC(QMutex, initMutex) 15 | std::atomic_bool RenderThread::isInited_; 16 | RenderThread::RenderThread(VideoWidget *glw, QObject *parent): 17 | QThread (parent), 18 | gl_widget_(glw) 19 | { 20 | if(!isInited_.load()){ 21 | renderFactoryInstance()->Register(AV_PIX_FMT_YUV420P, []()->VideoRender*{return new YuvRender;}); 22 | renderFactoryInstance()->Register(AV_PIX_FMT_NV12, []()->VideoRender*{return new Nv12Render;}); 23 | CreateRenderFunc func = nullptr; 24 | QLibrary dllLoad("Nv12Render_Gpu"); 25 | if(dllLoad.load()){ 26 | func = (CreateRenderFunc)dllLoad.resolve("createRender"); 27 | renderFactoryInstance()->Register(AV_PIX_FMT_CUDA, func); 28 | } 29 | 30 | isInited_.store(true); 31 | } 32 | } 33 | 34 | RenderThread::~RenderThread() 35 | { 36 | 37 | } 38 | 39 | VideoRender *RenderThread::getRender(int pix) 40 | { 41 | try { 42 | return renderFactoryInstance()->CreateObject(pix); 43 | } catch (const std::exception &e) { 44 | emit sigError(e.what()); 45 | } 46 | return nullptr; 47 | } 48 | 49 | void RenderThread::Render(const std::function handle) 50 | { 51 | if(isInterruptionRequested()){ 52 | return; 53 | } 54 | 55 | QOpenGLContext *ctx = gl_widget_->context(); 56 | if (!ctx){ // QOpenGLWidget not yet initialized 57 | emit sigError("QOpenGLWidget not yet initialized"); 58 | return; 59 | } 60 | 61 | // Grab the context. 62 | m_grabMutex.lock(); 63 | if(!gl_widget_->isFrameSwapped()) //保证程序启动时,RenderThread先拿到锁后,能终止此次渲染。因为这时界面还没显示的话, FrameSwapped还没切换过来 64 | { 65 | m_grabMutex.unlock(); 66 | return; 67 | } 68 | emit sigContextWanted(); 69 | m_grabCond.wait(&m_grabMutex); 70 | QMutexLocker lock(&m_renderMutex); 71 | m_grabMutex.unlock(); 72 | 73 | if(isInterruptionRequested()){ 74 | return; 75 | } 76 | 77 | Q_ASSERT(ctx->thread() == QThread::currentThread()); 78 | // Make the context (and an offscreen surface) current for this thread. The 79 | // QOpenGLWidget's fbo is bound in the context. 80 | gl_widget_->makeCurrent(); 81 | 82 | if(handle) 83 | { 84 | handle(); 85 | } 86 | 87 | // Make no context current on this thread and move the QOpenGLWidget's 88 | // context back to the gui thread. 89 | gl_widget_->doneCurrent(); 90 | ctx->moveToThread(qGuiApp->thread()); 91 | 92 | // Schedule composition. Note that this will use QueuedConnection, meaning 93 | // that update() will be invoked on the gui thread. 94 | QMetaObject::invokeMethod(gl_widget_, "update"); 95 | } 96 | 97 | void RenderThread::setFileName(QString f) 98 | { 99 | file_name_ = f; 100 | } 101 | 102 | void RenderThread::setDevice(QString d) 103 | { 104 | device_ = d; 105 | } 106 | 107 | void RenderThread::run() 108 | { 109 | std::shared_ptr task(DecodeTaskManager::Instance()->makeTask(this, device_)); 110 | task->decode(file_name_); 111 | } 112 | 113 | Factory *RenderThread::renderFactoryInstance() 114 | { 115 | static Factory render_factory; 116 | return &render_factory; 117 | } 118 | -------------------------------------------------------------------------------- /VideoWidget/renderthread.h: -------------------------------------------------------------------------------- 1 | #ifndef RENDERTHREAD_H 2 | #define RENDERTHREAD_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "render/factory.h" 8 | #include "render/videorender.h" 9 | QT_FORWARD_DECLARE_CLASS(VideoWidget) 10 | QT_FORWARD_DECLARE_CLASS(QOpenGLContext) 11 | class RenderThread : public QThread 12 | { 13 | Q_OBJECT 14 | public: 15 | RenderThread(VideoWidget *glw, QObject *parent = nullptr); 16 | ~RenderThread() override; 17 | void lockRenderer() { m_renderMutex.lock(); } 18 | void unlockRenderer() { m_renderMutex.unlock(); } 19 | QMutex *grabMutex() { return &m_grabMutex; } 20 | QWaitCondition *grabCond() { return &m_grabCond; } 21 | void prepareExit() {m_grabCond.wakeAll(); } 22 | 23 | virtual VideoRender* getRender(int); 24 | virtual void Render(const std::function); 25 | 26 | void setFileName(QString); 27 | void setDevice(QString); 28 | 29 | signals: 30 | void sigContextWanted(); 31 | 32 | void sigError(QString); 33 | void sigVideoStarted(int, int); 34 | void sigFps(int); 35 | void sigCurFpsChanged(int); 36 | 37 | protected: 38 | void run() override; 39 | 40 | private: 41 | static Factory* renderFactoryInstance(); 42 | static std::atomic_bool isInited_; 43 | 44 | VideoWidget *gl_widget_; 45 | QMutex m_renderMutex; 46 | QMutex m_grabMutex; 47 | QWaitCondition m_grabCond; 48 | 49 | QString file_name_, device_; 50 | }; 51 | 52 | #endif // RENDERTHREAD_H 53 | -------------------------------------------------------------------------------- /VideoWidget/videodecode/decodetaskmanagerimpl.cpp: -------------------------------------------------------------------------------- 1 | extern "C" 2 | { 3 | #include "libavutil/hwcontext_qsv.h" 4 | } 5 | #include 6 | #include "../renderthread.h" 7 | #include "ffmpegcudadecode.h" 8 | #include "ffmpegqsvdecode.h" 9 | #include "ffmpegcpudecode.h" 10 | #include "nvidiadecode.h" 11 | 12 | static DecodeTaskManagerImpl* g_task_manager = nullptr; 13 | DecodeTaskManagerImpl::DecodeTaskManagerImpl() 14 | { 15 | if(!g_task_manager) 16 | { 17 | g_task_manager = this; 18 | } 19 | } 20 | 21 | DecodeTaskManagerImpl::~DecodeTaskManagerImpl() 22 | { 23 | } 24 | 25 | int DecodeTaskManagerImpl::hwDecoderInit(const AVHWDeviceType fmt) 26 | { 27 | std::lock_guard lock(hw_buf_mtx_); 28 | auto iter = hw_buffer_map_.find(fmt); 29 | if(iter == hw_buffer_map_.end()) 30 | { 31 | int ret = 0; 32 | AVBufferRef *buffer = nullptr; 33 | switch (fmt) { 34 | case AV_HWDEVICE_TYPE_CUDA: 35 | ret = av_hwdevice_ctx_create(&buffer, AV_HWDEVICE_TYPE_CUDA, 36 | nullptr, nullptr, 0); 37 | break; 38 | case AV_HWDEVICE_TYPE_QSV: 39 | ret = av_hwdevice_ctx_create(&buffer, AV_HWDEVICE_TYPE_QSV, 40 | "auto", nullptr, 0); 41 | break; 42 | default: 43 | break; 44 | } 45 | if(ret == 0) 46 | { 47 | hw_buffer_map_.insert(std::make_pair(fmt, buffer)); 48 | } 49 | return ret; 50 | } 51 | 52 | return 0; 53 | } 54 | 55 | AVBufferRef *DecodeTaskManagerImpl::hwDecoderBuffer(const AVHWDeviceType type) 56 | { 57 | std::lock_guard lock(hw_buf_mtx_); 58 | auto iter = hw_buffer_map_.find(type); 59 | if(iter == hw_buffer_map_.end()){ 60 | return nullptr; 61 | } 62 | 63 | return iter->second; 64 | } 65 | 66 | DecodeTask *DecodeTaskManagerImpl::makeTask(RenderThread *render_thr, const QString &device) 67 | { 68 | if(device == "cuda") 69 | { 70 | return new FFmpegCudaDecode(this, render_thr); 71 | }else if(device == "qsv") 72 | { 73 | return new FFmpegQsvDecode(this, render_thr); 74 | }else if(device == "cpu") 75 | { 76 | return new FFmpegCpuDecode(this, render_thr); 77 | }else if(device == "cuda_plugin") 78 | { 79 | std::lock_guard lock(decoder_plugin_mtx_); 80 | CreateDecoderFunc func = nullptr; 81 | auto iter = decoder_plug_map_.find(device); 82 | if(iter == decoder_plug_map_.end()) 83 | { 84 | QLibrary dllLoad("NvidiaDecoderPlugin"); 85 | if(dllLoad.load()){ 86 | func = (CreateDecoderFunc)dllLoad.resolve("createDecoder"); 87 | bool code = decoder_plug_map_.insert(std::make_pair(device, func)).second; 88 | if(!code){ 89 | render_thr->sigError("load NvidiaDecoderPlugin failed"); 90 | } 91 | }else{ 92 | render_thr->sigError("load NvidiaDecoderPlugin failed"); 93 | } 94 | }else{ 95 | func = iter->second; 96 | } 97 | return new NvidiaDecode(this, func, render_thr); 98 | } 99 | 100 | return nullptr; 101 | } 102 | 103 | AVPixelFormat get_cuda_hw_format(AVCodecContext *ctx, const AVPixelFormat *pix_fmts) 104 | { 105 | const enum AVPixelFormat *p; 106 | 107 | for (p = pix_fmts; *p != -1; p++) { 108 | if (*p == AV_PIX_FMT_CUDA) 109 | return *p; 110 | } 111 | 112 | fprintf(stderr, "Failed to get HW surface format.\n"); 113 | return AV_PIX_FMT_NONE; 114 | } 115 | 116 | AVPixelFormat get_qsv_hw_format(AVCodecContext *avctx, const AVPixelFormat *pix_fmts) 117 | { 118 | while (*pix_fmts != AV_PIX_FMT_NONE) { 119 | if (*pix_fmts == AV_PIX_FMT_QSV) { 120 | AVHWFramesContext *frames_ctx; 121 | AVQSVFramesContext *frames_hwctx; 122 | int ret; 123 | 124 | /* create a pool of surfaces to be used by the decoder */ 125 | avctx->hw_frames_ctx = av_hwframe_ctx_alloc(g_task_manager->hwDecoderBuffer(AV_HWDEVICE_TYPE_QSV)); 126 | if (!avctx->hw_frames_ctx) 127 | return AV_PIX_FMT_NONE; 128 | frames_ctx = (AVHWFramesContext*)avctx->hw_frames_ctx->data; 129 | frames_hwctx = (AVQSVFramesContext*) frames_ctx->hwctx; 130 | 131 | frames_ctx->format = AV_PIX_FMT_QSV; 132 | frames_ctx->sw_format = avctx->sw_pix_fmt; 133 | frames_ctx->width = FFALIGN(avctx->coded_width, 32); 134 | frames_ctx->height = FFALIGN(avctx->coded_height, 32); 135 | frames_ctx->initial_pool_size = 32; 136 | 137 | frames_hwctx->frame_type = MFX_MEMTYPE_VIDEO_MEMORY_DECODER_TARGET; 138 | 139 | ret = av_hwframe_ctx_init(avctx->hw_frames_ctx); 140 | if (ret < 0) 141 | return AV_PIX_FMT_NONE; 142 | 143 | return AV_PIX_FMT_QSV; 144 | } 145 | 146 | pix_fmts++; 147 | } 148 | 149 | fprintf(stderr, "The QSV pixel format not offered in get_format()\n"); 150 | 151 | return AV_PIX_FMT_NONE; 152 | } 153 | -------------------------------------------------------------------------------- /VideoWidget/videodecode/decodetaskmanagerimpl.h: -------------------------------------------------------------------------------- 1 | #ifndef DECODETASKMANAGERIMPL_H 2 | #define DECODETASKMANAGERIMPL_H 3 | 4 | #include 5 | #include 6 | #include 7 | extern "C" 8 | { 9 | #include "libavcodec/avcodec.h" 10 | } 11 | #include "nvidia_decoer_api.h" 12 | #include "decodtask.h" 13 | 14 | AVPixelFormat get_cuda_hw_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts); 15 | AVPixelFormat get_qsv_hw_format(AVCodecContext *avctx, const enum AVPixelFormat *pix_fmts); 16 | 17 | class DecodeTaskManagerImpl : public DecodeTaskManager 18 | { 19 | public: 20 | DecodeTaskManagerImpl(); 21 | ~DecodeTaskManagerImpl() override; 22 | int hwDecoderInit(const enum AVHWDeviceType); 23 | AVBufferRef *hwDecoderBuffer(const AVHWDeviceType); 24 | DecodeTask* makeTask(RenderThread*render_thr, const QString &device) override; 25 | 26 | private: 27 | std::mutex hw_buf_mtx_, decoder_plugin_mtx_; 28 | std::map hw_buffer_map_; 29 | std::map decoder_plug_map_; 30 | }; 31 | 32 | #endif // DECODETASKMANAGERIMPL_H 33 | -------------------------------------------------------------------------------- /VideoWidget/videodecode/decodtask.cpp: -------------------------------------------------------------------------------- 1 | #include "decodetaskmanagerimpl.h" 2 | 3 | DecodeTaskManager* DecodeTaskManager::Instance() 4 | { 5 | static DecodeTaskManagerImpl obj; 6 | return &obj; 7 | } 8 | -------------------------------------------------------------------------------- /VideoWidget/videodecode/decodtask.h: -------------------------------------------------------------------------------- 1 | #ifndef DECODTASK_H 2 | #define DECODTASK_H 3 | 4 | #include 5 | class RenderThread; 6 | class DecodeTask 7 | { 8 | public: 9 | DecodeTask(RenderThread *render_thr):m_thread_(render_thr){} 10 | virtual ~DecodeTask(){} 11 | virtual RenderThread* thread(){return m_thread_;} 12 | virtual void decode(const QString &url) = 0; 13 | 14 | private: 15 | RenderThread *m_thread_; 16 | }; 17 | 18 | class DecodeTaskManager 19 | { 20 | public: 21 | DecodeTaskManager() = default; 22 | virtual ~DecodeTaskManager(){} 23 | virtual DecodeTask* makeTask(RenderThread*glw, const QString &device) = 0; 24 | 25 | static DecodeTaskManager* Instance(); 26 | }; 27 | 28 | #endif // DECODTASK_H 29 | -------------------------------------------------------------------------------- /VideoWidget/videodecode/ffmpegcpudecode.cpp: -------------------------------------------------------------------------------- 1 | extern "C" 2 | { 3 | #include "libavcodec/avcodec.h" 4 | #include "libavformat/avformat.h" 5 | #include "libavutil/pixdesc.h" 6 | #include "libavutil/opt.h" 7 | #include "libavutil/avassert.h" 8 | #include "libswscale/swscale.h" 9 | #include "libavutil/imgutils.h" 10 | } 11 | #include 12 | #include 13 | #include "../renderthread.h" 14 | #include "ffmpegcpudecode.h" 15 | 16 | FFmpegCpuDecode::FFmpegCpuDecode(DecodeTaskManagerImpl *taskManger, RenderThread *render_thr): 17 | DecodeTask(render_thr), 18 | taskManager_(taskManger) 19 | { 20 | 21 | } 22 | 23 | FFmpegCpuDecode::~FFmpegCpuDecode() 24 | { 25 | if(buffer_) 26 | { 27 | av_free(buffer_); 28 | buffer_ = nullptr; 29 | } 30 | qDebug() << "FFmpegCpuDecode::~FFmpegCpuDecode()"; 31 | } 32 | 33 | void FFmpegCpuDecode::decode(const QString &url) 34 | { 35 | AVFormatContext *pFormatCtx = nullptr; 36 | AVCodecContext *pCodecCtx = nullptr; 37 | AVCodec *pCodec = nullptr; 38 | AVFrame *pFrame = nullptr; 39 | uint8_t *out_buffer; 40 | AVPacket packet; 41 | AVStream *video = nullptr; 42 | char errorbuf[1024]{0}; 43 | QString errorMsg; 44 | int videoStream, i; 45 | int ret; 46 | 47 | AVDictionary *opt = nullptr; 48 | //av_dict_set(&opt,"buffer_size","1024000",0); 49 | //av_dict_set(&opt,"max_delay","0",0); 50 | av_dict_set(&opt,"rtsp_transport","tcp",0); 51 | av_dict_set(&opt,"stimeout","5000000",0); 52 | if ((ret = avformat_open_input(&pFormatCtx, url.toUtf8().data(), nullptr, &opt)) != 0) { 53 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 54 | errorMsg = errorbuf; 55 | thread()->sigError(errorMsg); 56 | goto END; 57 | } 58 | 59 | if ((ret = avformat_find_stream_info(pFormatCtx, nullptr)) < 0) { 60 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 61 | errorMsg = errorbuf; 62 | thread()->sigError(errorMsg); 63 | goto END; 64 | } 65 | 66 | if((ret = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &pCodec, 0)) < 0){ 67 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 68 | errorMsg = errorbuf; 69 | thread()->sigError(errorMsg); 70 | goto END; 71 | } 72 | videoStream = ret; 73 | video = pFormatCtx->streams[videoStream]; 74 | 75 | if(!(pCodec = avcodec_find_decoder(video->codecpar->codec_id))) 76 | { 77 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 78 | errorMsg = QString("avcodec_find_decoder error: %1").arg(errorbuf); 79 | thread()->sigError(errorMsg); 80 | goto END; 81 | } 82 | 83 | if (!(pCodecCtx = avcodec_alloc_context3(pCodec))) 84 | { 85 | errorMsg = QString("avcodec_alloc_context3(pCodec) error: %1").arg(AVERROR(ENOMEM)); 86 | thread()->sigError(errorMsg); 87 | goto END; 88 | } 89 | 90 | if ((ret = avcodec_parameters_to_context(pCodecCtx, video->codecpar)) < 0) 91 | { 92 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 93 | errorMsg = errorbuf; 94 | thread()->sigError(errorMsg); 95 | goto END; 96 | } 97 | 98 | if(pCodecCtx->pix_fmt < 0) 99 | { 100 | errorMsg = "unknow pixformat"; 101 | thread()->sigError(errorMsg); 102 | goto END; 103 | } 104 | 105 | int vden = video->avg_frame_rate.den,vnum = video->avg_frame_rate.num; 106 | if(vden > 0) 107 | { 108 | thread()->sigFps(vnum/vden); 109 | } 110 | stream_time_base_ = video->time_base; 111 | 112 | ///打开解码器 113 | if ((ret = avcodec_open2(pCodecCtx, pCodec, nullptr)) < 0) { 114 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 115 | errorMsg = errorbuf; 116 | thread()->sigError(errorMsg); 117 | goto END; 118 | } 119 | 120 | pFrame = av_frame_alloc(); 121 | av_dump_format(pFormatCtx, 0, url.toUtf8().data(), 0); //输出视频信息 122 | 123 | while (!thread()->isInterruptionRequested() && ret >= 0) 124 | { 125 | if ((ret = av_read_frame(pFormatCtx, &packet)) < 0) 126 | { 127 | break; //这里认为视频读取完了 128 | } 129 | 130 | if (packet.stream_index == videoStream) { 131 | ret = decode_packet(pCodecCtx, &packet, pFrame); 132 | } 133 | 134 | qint64 end_pt = QDateTime::currentMSecsSinceEpoch(); 135 | if((end_pt - start_pt_) >= 1000) 136 | { 137 | if(decode_frames_ != curFps_) 138 | { 139 | curFps_ = decode_frames_; 140 | thread()->sigCurFpsChanged(curFps_); 141 | } 142 | start_pt_ = end_pt; 143 | decode_frames_ = 0; 144 | } 145 | decode_frames_++; 146 | 147 | av_packet_unref(&packet); 148 | } 149 | packet.data = nullptr; 150 | packet.size = 0; 151 | // ret = decode_packet(pCodecCtx, &packet, pFrame); 152 | 153 | thread()->sigCurFpsChanged(0); 154 | if(!thread()->isInterruptionRequested()){ 155 | if(url.left(4) == "rtsp"){ 156 | thread()->sigError("AVERROR_EOF"); 157 | } 158 | } 159 | END: 160 | if(pFrame) 161 | { 162 | av_frame_free(&pFrame); 163 | } 164 | if(pCodecCtx) 165 | { 166 | avcodec_free_context(&pCodecCtx); 167 | } 168 | if(pFormatCtx) 169 | { 170 | avformat_close_input(&pFormatCtx); 171 | } 172 | } 173 | 174 | int FFmpegCpuDecode::decode_packet(AVCodecContext *pCodecCtx, AVPacket *packet, AVFrame *pFrame) 175 | { 176 | int ret; 177 | QString errorMsg; 178 | char errorbuf[1024]{0}; 179 | 180 | if((ret = avcodec_send_packet(pCodecCtx, packet)) < 0 ){ 181 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 182 | errorMsg = QString("Error during avcodec_send_packet: %1").arg(errorbuf); 183 | thread()->sigError(errorMsg); 184 | return ret; 185 | } 186 | 187 | while (true) { 188 | ret = avcodec_receive_frame(pCodecCtx, pFrame); 189 | if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) 190 | { 191 | break; 192 | }else if(ret < 0){ 193 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 194 | errorMsg = QString("Error while avcodec_receive_frame: %1").arg(errorbuf); 195 | thread()->sigError(errorMsg); 196 | goto fail; 197 | } 198 | 199 | #if 0 200 | thread()->Render([&](){ 201 | if(!render_) 202 | { 203 | render_ = thread()->getRender(pFrame->format); 204 | render_->initialize(pFrame->width, pFrame->height); 205 | thread()->sigVideoStarted(pFrame->width, pFrame->height); 206 | } 207 | render_->upLoad(pFrame->data, pFrame->linesize, pFrame->width, pFrame->height); 208 | }); 209 | #else 210 | // pFrame->pts = pFrame->best_effort_timestamp; 211 | // if(pFrame->pts != AV_NOPTS_VALUE) 212 | // { 213 | // if(last_pts_ != AV_NOPTS_VALUE) 214 | // { 215 | // AVRational ra; 216 | // ra.num = 1; 217 | // ra.den = AV_TIME_BASE; 218 | // int64_t delay = av_rescale_q(pFrame->pts - last_pts_, stream_time_base_, ra); 219 | // if(delay > 0 && delay < 1000000) 220 | // { 221 | // QThread::usleep(delay); 222 | // } 223 | // } 224 | // last_pts_ = pFrame->pts; 225 | // } 226 | if(!buffer_) 227 | { 228 | bufferSize_ = av_image_get_buffer_size(AVPixelFormat(pFrame->format), pFrame->width, 229 | pFrame->height, 1); 230 | buffer_ = (uint8_t*) av_malloc(bufferSize_); 231 | } 232 | 233 | ret = av_image_copy_to_buffer(buffer_, bufferSize_, 234 | (const uint8_t * const *)pFrame->data, 235 | (const int *)pFrame->linesize, AVPixelFormat(pFrame->format), 236 | pFrame->width, pFrame->height, 1); 237 | 238 | thread()->Render([&](){ 239 | if(!render_) 240 | { 241 | render_ = thread()->getRender(pFrame->format); 242 | render_->initialize(pFrame->width, pFrame->height); 243 | thread()->sigVideoStarted(pFrame->width, pFrame->height); 244 | } 245 | render_->upLoad(buffer_, pFrame->width, pFrame->height); 246 | }); 247 | #endif 248 | 249 | fail: 250 | if(ret < 0){ 251 | return ret; 252 | } 253 | } 254 | 255 | return 0; 256 | } 257 | -------------------------------------------------------------------------------- /VideoWidget/videodecode/ffmpegcpudecode.h: -------------------------------------------------------------------------------- 1 | #ifndef FFMPEGCPUDECODE_H 2 | #define FFMPEGCPUDECODE_H 3 | 4 | #include "decodetaskmanagerimpl.h" 5 | class VideoRender; 6 | class FFmpegCpuDecode : public DecodeTask 7 | { 8 | public: 9 | FFmpegCpuDecode(DecodeTaskManagerImpl *taskManger, RenderThread *render_thr); 10 | ~FFmpegCpuDecode() override; 11 | 12 | void decode(const QString &url) override; 13 | 14 | private: 15 | int decode_packet(AVCodecContext *avctx, AVPacket *packet, AVFrame *frame); 16 | 17 | DecodeTaskManagerImpl *taskManager_ = nullptr; 18 | uint8_t *buffer_ = nullptr; 19 | int bufferSize_ = 0, curFps_ = 0; 20 | qint64 start_pt_ = 0, last_pts_ = AV_NOPTS_VALUE; 21 | int decode_frames_ = 0; 22 | AVRational stream_time_base_; 23 | 24 | VideoRender *render_{nullptr}; 25 | }; 26 | 27 | #endif // FFMPEGCPUDECODE_H 28 | -------------------------------------------------------------------------------- /VideoWidget/videodecode/ffmpegcudadecode.cpp: -------------------------------------------------------------------------------- 1 | extern "C" 2 | { 3 | #include "libavcodec/avcodec.h" 4 | #include "libavformat/avformat.h" 5 | #include "libavutil/pixdesc.h" 6 | #include "libavutil/opt.h" 7 | #include "libavutil/avassert.h" 8 | #include "libswscale/swscale.h" 9 | #include "libavutil/imgutils.h" 10 | } 11 | #include 12 | #include 13 | #include "../renderthread.h" 14 | #include "ffmpegcudadecode.h" 15 | 16 | FFmpegCudaDecode::FFmpegCudaDecode(DecodeTaskManagerImpl *taskManger, RenderThread *render_thr): 17 | DecodeTask(render_thr), 18 | taskManager_(taskManger) 19 | { 20 | } 21 | 22 | FFmpegCudaDecode::~FFmpegCudaDecode() 23 | { 24 | if(buffer_) 25 | { 26 | av_free(buffer_); 27 | buffer_ = nullptr; 28 | } 29 | qDebug() << "FFmpegCudaDecode::~FFmpegCudaDecode()"; 30 | } 31 | 32 | void FFmpegCudaDecode::decode(const QString &url) 33 | { 34 | AVFormatContext *pFormatCtx = nullptr; 35 | AVCodecContext *pCodecCtx = nullptr; 36 | AVCodec *pCodec = nullptr; 37 | AVFrame *pFrame = nullptr, *swFrame = nullptr; 38 | uint8_t *out_buffer; 39 | AVPacket packet; 40 | AVStream *video = nullptr; 41 | char errorbuf[1024]{0}; 42 | QString errorMsg; 43 | int videoStream, i; 44 | int ret; 45 | 46 | const char *device_name = "cuda"; 47 | enum AVHWDeviceType type = av_hwdevice_find_type_by_name(device_name); 48 | if(type == AV_HWDEVICE_TYPE_NONE) 49 | { 50 | QString str = "Available device types:"; 51 | while((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE) 52 | str += QString(" %1").arg(av_hwdevice_get_type_name(type)); 53 | errorMsg = QString("Device type %1 is not supported.%2").arg(device_name, str); 54 | 55 | thread()->sigError(errorMsg); 56 | return; 57 | } 58 | 59 | AVDictionary *opt = nullptr; 60 | //av_dict_set(&opt,"buffer_size","1024000",0); 61 | //av_dict_set(&opt,"max_delay","0",0); 62 | av_dict_set(&opt,"rtsp_transport","tcp",0); 63 | av_dict_set(&opt,"stimeout","5000000",0); 64 | if ((ret = avformat_open_input(&pFormatCtx, url.toUtf8().data(), nullptr, &opt)) != 0) { 65 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 66 | errorMsg = errorbuf; 67 | thread()->sigError(errorMsg); 68 | goto END; 69 | } 70 | 71 | if ((ret = avformat_find_stream_info(pFormatCtx, nullptr)) < 0) { 72 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 73 | errorMsg = errorbuf; 74 | thread()->sigError(errorMsg); 75 | goto END; 76 | } 77 | 78 | if((ret = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &pCodec, 0)) < 0){ 79 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 80 | errorMsg = errorbuf; 81 | thread()->sigError(errorMsg); 82 | goto END; 83 | } 84 | videoStream = ret; 85 | 86 | for (i = 0;; i++) { 87 | const AVCodecHWConfig *config = avcodec_get_hw_config(pCodec, i); 88 | if (!config) { 89 | errorMsg = QString("Decoder %1 does not support device type %2").arg(pCodec->name).arg(av_hwdevice_get_type_name(type)); 90 | thread()->sigError(errorMsg); 91 | goto END; 92 | } 93 | if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX && 94 | config->device_type == type) { 95 | hw_pix_fmt = config->pix_fmt; 96 | break; 97 | } 98 | } 99 | 100 | if (!(pCodecCtx = avcodec_alloc_context3(pCodec))) 101 | { 102 | errorMsg = QString("avcodec_alloc_context3(pCodec) error: %1").arg(AVERROR(ENOMEM)); 103 | thread()->sigError(errorMsg); 104 | goto END; 105 | } 106 | 107 | video = pFormatCtx->streams[videoStream]; 108 | if ((ret = avcodec_parameters_to_context(pCodecCtx, video->codecpar)) < 0) 109 | { 110 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 111 | errorMsg = errorbuf; 112 | thread()->sigError(errorMsg); 113 | goto END; 114 | } 115 | 116 | if(pCodecCtx->pix_fmt < 0) 117 | { 118 | errorMsg = "unknow pixformat"; 119 | thread()->sigError(errorMsg); 120 | goto END; 121 | } 122 | 123 | int vden = video->avg_frame_rate.den,vnum = video->avg_frame_rate.num; 124 | if(vden > 0) 125 | { 126 | thread()->sigFps(vnum/vden); 127 | } 128 | stream_time_base_ = video->time_base; 129 | 130 | pCodecCtx->get_format = get_cuda_hw_format; 131 | 132 | if ((ret = taskManager_->hwDecoderInit(type)) < 0) 133 | { 134 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 135 | errorMsg = errorbuf; 136 | thread()->sigError(errorMsg); 137 | goto END; 138 | } 139 | pCodecCtx->hw_device_ctx = av_buffer_ref(taskManager_->hwDecoderBuffer(type)); 140 | 141 | ///打开解码器 142 | if ((ret = avcodec_open2(pCodecCtx, pCodec, nullptr)) < 0) { 143 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 144 | errorMsg = errorbuf; 145 | thread()->sigError(errorMsg); 146 | goto END; 147 | } 148 | 149 | pFrame = av_frame_alloc(); 150 | swFrame = av_frame_alloc(); 151 | av_dump_format(pFormatCtx, 0, url.toUtf8().data(), 0); //输出视频信息 152 | while (!thread()->isInterruptionRequested() && ret >= 0) 153 | { 154 | if ((ret = av_read_frame(pFormatCtx, &packet)) < 0) 155 | { 156 | break; //这里认为视频读取完了 157 | } 158 | 159 | if (packet.stream_index == videoStream) { 160 | ret = decode_packet(pCodecCtx, &packet, pFrame, swFrame); 161 | } 162 | 163 | qint64 end_pt = QDateTime::currentMSecsSinceEpoch(); 164 | if((end_pt - start_pt_) >= 1000) 165 | { 166 | if(decode_frames_ != curFps_) 167 | { 168 | curFps_ = decode_frames_; 169 | thread()->sigCurFpsChanged(curFps_); 170 | } 171 | start_pt_ = end_pt; 172 | decode_frames_ = 0; 173 | } 174 | decode_frames_++; 175 | 176 | av_packet_unref(&packet); 177 | } 178 | packet.data = nullptr; 179 | packet.size = 0; 180 | // ret = decode_packet(pCodecCtx, &packet, pFrame, swFrame); 181 | 182 | thread()->sigCurFpsChanged(0); 183 | if(!thread()->isInterruptionRequested()){ 184 | if(url.left(4) == "rtsp"){ 185 | thread()->sigError("AVERROR_EOF"); 186 | } 187 | } 188 | END: 189 | if(pFrame) 190 | { 191 | av_frame_free(&pFrame); 192 | } 193 | if(swFrame) 194 | { 195 | av_frame_free(&swFrame); 196 | } 197 | if(pCodecCtx) 198 | { 199 | avcodec_free_context(&pCodecCtx); 200 | } 201 | if(pFormatCtx) 202 | { 203 | avformat_close_input(&pFormatCtx); 204 | } 205 | } 206 | 207 | int FFmpegCudaDecode::decode_packet(AVCodecContext *pCodecCtx, AVPacket *packet, AVFrame *pFrame, AVFrame *swFrame) 208 | { 209 | int ret; 210 | QString errorMsg; 211 | char errorbuf[1024]{0}; 212 | 213 | if((ret = avcodec_send_packet(pCodecCtx, packet)) < 0 ){ 214 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 215 | errorMsg = QString("Error during avcodec_send_packet: %1").arg(errorbuf); 216 | thread()->sigError(errorMsg); 217 | return ret; 218 | } 219 | 220 | while (true) { 221 | ret = avcodec_receive_frame(pCodecCtx, pFrame); 222 | if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) 223 | { 224 | break; 225 | }else if(ret < 0){ 226 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 227 | errorMsg = QString("Error while avcodec_receive_frame: %1").arg(errorbuf); 228 | thread()->sigError(errorMsg); 229 | goto fail; 230 | } 231 | 232 | #if 1 233 | thread()->Render([&](){ 234 | if(!render_) 235 | { 236 | thread()->setExtraData(nullptr); 237 | render_ = thread()->getRender(pFrame->format); 238 | render_->initialize(pFrame->width, pFrame->height); 239 | thread()->sigVideoStarted(pFrame->width, pFrame->height); 240 | } 241 | render_->upLoad(pFrame->data, pFrame->linesize, pFrame->width, pFrame->height); 242 | }); 243 | #else 244 | //gpu拷贝到cpu,相对耗时 245 | if((ret = av_hwframe_transfer_data(swFrame, pFrame, 0)) < 0 ){ 246 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 247 | errorMsg = QString("Error transferring the data to system memory: %1").arg(errorbuf); 248 | thread()->sigError(errorMsg); 249 | goto fail; 250 | } 251 | 252 | // swFrame->pts = pFrame->best_effort_timestamp; 253 | // if(swFrame->pts != AV_NOPTS_VALUE) 254 | // { 255 | // if(last_pts_ != AV_NOPTS_VALUE) 256 | // { 257 | // AVRational ra; 258 | // ra.num = 1; 259 | // ra.den = AV_TIME_BASE; 260 | // int64_t delay = av_rescale_q(swFrame->pts - last_pts_, stream_time_base_, ra); 261 | // if(delay > 0 && delay < 1000000) 262 | // { 263 | // QThread::usleep(delay); 264 | // } 265 | // } 266 | // last_pts_ = swFrame->pts; 267 | // } 268 | if(!buffer_) 269 | { 270 | bufferSize_ = av_image_get_buffer_size(AVPixelFormat(swFrame->format), swFrame->width, 271 | swFrame->height, 1); 272 | 273 | buffer_ = (uint8_t*) av_malloc(bufferSize_); 274 | } 275 | 276 | ret = av_image_copy_to_buffer(buffer_, bufferSize_, 277 | (const uint8_t * const *)swFrame->data, 278 | (const int *)swFrame->linesize, AVPixelFormat(swFrame->format), 279 | swFrame->width, swFrame->height, 1); 280 | 281 | thread()->Render([&](){ 282 | if(!render_) 283 | { 284 | render_ = thread()->getRender(swFrame->format); 285 | render_->initialize(swFrame->width, swFrame->height); 286 | thread()->sigVideoStarted(pFrame->width, pFrame->height); 287 | } 288 | render_->upLoad(buffer_, swFrame->width, swFrame->height); 289 | }); 290 | #endif 291 | 292 | fail: 293 | if(ret < 0){ 294 | return ret; 295 | } 296 | } 297 | 298 | return 0; 299 | } 300 | -------------------------------------------------------------------------------- /VideoWidget/videodecode/ffmpegcudadecode.h: -------------------------------------------------------------------------------- 1 | #ifndef FFMPEGCUDADECODE_H 2 | #define FFMPEGCUDADECODE_H 3 | 4 | #include "decodetaskmanagerimpl.h" 5 | class VideoRender; 6 | class FFmpegCudaDecode : public DecodeTask 7 | { 8 | public: 9 | FFmpegCudaDecode(DecodeTaskManagerImpl *taskManger, RenderThread *render_thr); 10 | ~FFmpegCudaDecode() override; 11 | 12 | void decode(const QString &url) override; 13 | 14 | private: 15 | int decode_packet(AVCodecContext *avctx, AVPacket *packet, AVFrame *frame, AVFrame *sw_frame); 16 | 17 | DecodeTaskManagerImpl *taskManager_ = nullptr; 18 | AVPixelFormat hw_pix_fmt; 19 | uint8_t *buffer_ = nullptr; 20 | int bufferSize_ = 0, curFps_ = 0; 21 | qint64 start_pt_ = 0, last_pts_ = AV_NOPTS_VALUE; 22 | int decode_frames_ = 0; 23 | AVRational stream_time_base_; 24 | 25 | VideoRender *render_{nullptr}; 26 | }; 27 | 28 | #endif // FFMPEGCUDADECODE_H 29 | -------------------------------------------------------------------------------- /VideoWidget/videodecode/ffmpegqsvdecode.cpp: -------------------------------------------------------------------------------- 1 | extern "C" 2 | { 3 | #include "libavcodec/avcodec.h" 4 | #include "libavformat/avformat.h" 5 | #include "libavutil/pixdesc.h" 6 | #include "libavutil/opt.h" 7 | #include "libavutil/avassert.h" 8 | #include "libswscale/swscale.h" 9 | #include "libavutil/imgutils.h" 10 | } 11 | #include 12 | #include 13 | #include "../renderthread.h" 14 | #include "ffmpegqsvdecode.h" 15 | 16 | FFmpegQsvDecode::FFmpegQsvDecode(DecodeTaskManagerImpl *taskManger, RenderThread *render_thr): 17 | DecodeTask(render_thr), 18 | taskManager_(taskManger) 19 | { 20 | 21 | } 22 | 23 | FFmpegQsvDecode::~FFmpegQsvDecode() 24 | { 25 | if(buffer_) 26 | { 27 | av_free(buffer_); 28 | buffer_ = nullptr; 29 | } 30 | 31 | qDebug() << "FFmpegQsvDecode::~FFmpegQsvDecode()"; 32 | } 33 | 34 | void FFmpegQsvDecode::decode(const QString &url) 35 | { 36 | AVFormatContext *pFormatCtx = nullptr; 37 | AVCodecContext *pCodecCtx = nullptr; 38 | AVCodec *pCodec = nullptr; 39 | AVFrame *pFrame = nullptr, *swFrame = nullptr; 40 | uint8_t *out_buffer; 41 | AVPacket packet; 42 | AVStream *video_st = nullptr; 43 | char errorbuf[1024]{0}; 44 | QString errorMsg; 45 | int ret, i; 46 | 47 | AVDictionary *opt = nullptr; 48 | //av_dict_set(&opt,"buffer_size","1024000",0); 49 | //av_dict_set(&opt,"max_delay","0",0); 50 | av_dict_set(&opt,"rtsp_transport","tcp",0); 51 | av_dict_set(&opt,"stimeout","5000000",0); 52 | if ((ret = avformat_open_input(&pFormatCtx, url.toUtf8().data(), nullptr, &opt)) != 0) { 53 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 54 | errorMsg = errorbuf; 55 | thread()->sigError(errorMsg); 56 | goto END; 57 | } 58 | 59 | if ((ret = avformat_find_stream_info(pFormatCtx, nullptr)) < 0) { 60 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 61 | errorMsg = errorbuf; 62 | thread()->sigError(errorMsg); 63 | goto END; 64 | } 65 | 66 | /* find the first H.264 video stream */ 67 | for (i = 0; i < pFormatCtx->nb_streams; i++) { 68 | AVStream *st = pFormatCtx->streams[i]; 69 | 70 | if (st->codecpar->codec_id == AV_CODEC_ID_H264 && !video_st)//AV_CODEC_ID_HEVC 71 | video_st = st; 72 | else 73 | st->discard = AVDISCARD_ALL; 74 | } 75 | if (!video_st) { 76 | errorMsg = "No H.264 video stream in the input file"; 77 | thread()->sigError(errorMsg); 78 | goto END; 79 | } 80 | 81 | int vden = video_st->avg_frame_rate.den,vnum = video_st->avg_frame_rate.num; 82 | if(vden > 0) 83 | { 84 | thread()->sigFps(vnum/vden); 85 | } 86 | 87 | if ((ret = taskManager_->hwDecoderInit(AV_HWDEVICE_TYPE_QSV)) < 0) 88 | { 89 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 90 | errorMsg = errorbuf; 91 | thread()->sigError(errorMsg); 92 | goto END; 93 | } 94 | 95 | pCodec = avcodec_find_decoder_by_name("h264_qsv"); 96 | if(!pCodec){ 97 | errorMsg = "The QSV decoder is not present in libavcodec"; 98 | thread()->sigError(errorMsg); 99 | goto END; 100 | } 101 | 102 | if (!(pCodecCtx = avcodec_alloc_context3(pCodec))) 103 | { 104 | errorMsg = QString("avcodec_alloc_context3(pCodec) error: %1").arg(AVERROR(ENOMEM)); 105 | thread()->sigError(errorMsg); 106 | goto END; 107 | } 108 | 109 | pCodecCtx->codec_id = AV_CODEC_ID_H264; 110 | if (video_st->codecpar->extradata_size) { 111 | pCodecCtx->extradata = (uint8_t*)av_mallocz(video_st->codecpar->extradata_size + 112 | AV_INPUT_BUFFER_PADDING_SIZE); 113 | if (!pCodecCtx->extradata) { 114 | ret = AVERROR(ENOMEM); 115 | goto END; 116 | } 117 | memcpy(pCodecCtx->extradata, video_st->codecpar->extradata, 118 | video_st->codecpar->extradata_size); 119 | pCodecCtx->extradata_size = video_st->codecpar->extradata_size; 120 | } 121 | pCodecCtx->get_format = get_qsv_hw_format; 122 | 123 | ///打开解码器 124 | if ((ret = avcodec_open2(pCodecCtx, nullptr, nullptr)) < 0) { 125 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 126 | errorMsg = errorbuf; 127 | thread()->sigError(errorMsg); 128 | goto END; 129 | } 130 | 131 | pFrame = av_frame_alloc(); 132 | swFrame = av_frame_alloc(); 133 | av_dump_format(pFormatCtx, 0, url.toUtf8().data(), 0); //输出视频信息 134 | 135 | while (!thread()->isInterruptionRequested()) 136 | { 137 | if ((ret = av_read_frame(pFormatCtx, &packet)) < 0) 138 | { 139 | break; //这里认为视频读取完了 140 | } 141 | 142 | if (packet.stream_index == video_st->index) { 143 | ret = decode_packet(pCodecCtx, pFrame, swFrame, &packet); 144 | } 145 | 146 | qint64 end_pt = QDateTime::currentMSecsSinceEpoch(); 147 | if((end_pt - start_pt_) >= 1000) 148 | { 149 | if(decode_frames_ != curFps_) 150 | { 151 | curFps_ = decode_frames_; 152 | thread()->sigCurFpsChanged(curFps_); 153 | } 154 | start_pt_ = end_pt; 155 | decode_frames_ = 0; 156 | } 157 | decode_frames_++; 158 | av_packet_unref(&packet); 159 | } 160 | //flash decoder 161 | packet.data = nullptr; 162 | packet.size = 0; 163 | // ret = decode_packet(pCodecCtx, pFrame, swFrame, &packet); 164 | 165 | thread()->sigCurFpsChanged(0); 166 | if(!thread()->isInterruptionRequested()){ 167 | if(url.left(4) == "rtsp"){ 168 | thread()->sigError("AVERROR_EOF"); 169 | } 170 | } 171 | END: 172 | if(pFrame) 173 | { 174 | av_frame_free(&pFrame); 175 | } 176 | if(swFrame) 177 | { 178 | av_frame_free(&swFrame); 179 | } 180 | if(pCodecCtx) 181 | { 182 | avcodec_free_context(&pCodecCtx); 183 | } 184 | if(pFormatCtx) 185 | { 186 | avformat_close_input(&pFormatCtx); 187 | } 188 | } 189 | 190 | int FFmpegQsvDecode::decode_packet(AVCodecContext *decoder_ctx, AVFrame *frame, AVFrame *sw_frame, AVPacket *pkt) 191 | { 192 | int ret = 0; 193 | QString errorMsg; 194 | char errorbuf[1024]{0}; 195 | 196 | ret = avcodec_send_packet(decoder_ctx, pkt); 197 | if (ret < 0) { 198 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 199 | errorMsg = errorbuf; 200 | // thread()->sigError(errorMsg); 201 | return ret; 202 | } 203 | 204 | while (ret >= 0) { 205 | int i, j; 206 | 207 | ret = avcodec_receive_frame(decoder_ctx, frame); 208 | if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) 209 | break; 210 | else if (ret < 0) { 211 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 212 | errorMsg = QString("Error during decoding: %1").arg(errorbuf); 213 | thread()->sigError(errorMsg); 214 | return ret; 215 | } 216 | 217 | //gpu拷贝到cpu,相对耗时 218 | ret = av_hwframe_transfer_data(sw_frame, frame, 0); 219 | if (ret < 0) { 220 | av_strerror(ret, errorbuf, sizeof(errorbuf)); 221 | errorMsg = QString("Error transferring the data to system memory: %1").arg(errorbuf); 222 | thread()->sigError(errorMsg); 223 | goto fail; 224 | } 225 | // sw_frame->pts = frame->best_effort_timestamp; 226 | // if(sw_frame->pts != AV_NOPTS_VALUE) 227 | // { 228 | // if(last_pts_ != AV_NOPTS_VALUE) 229 | // { 230 | // AVRational ra; 231 | // ra.num = 1; 232 | // ra.den = AV_TIME_BASE; 233 | // int64_t delay = av_rescale_q(sw_frame->pts - last_pts_, stream_time_base_, ra); 234 | // if(delay > 0 && delay < 1000000) 235 | // { 236 | // QThread::usleep(delay); 237 | // } 238 | // } 239 | // last_pts_ = sw_frame->pts; 240 | // } 241 | if(!buffer_) 242 | { 243 | bufferSize_ = av_image_get_buffer_size(AVPixelFormat(sw_frame->format), sw_frame->width, 244 | sw_frame->height, 1); 245 | 246 | buffer_ = (uint8_t*) av_malloc(bufferSize_); 247 | } 248 | ret = av_image_copy_to_buffer(buffer_, bufferSize_, 249 | (const uint8_t * const *)sw_frame->data, 250 | (const int *)sw_frame->linesize, AVPixelFormat(sw_frame->format), 251 | sw_frame->width, sw_frame->height, 1); 252 | 253 | thread()->Render([&](){ 254 | if(!render_) 255 | { 256 | render_ = thread()->getRender(sw_frame->format); 257 | render_->initialize(sw_frame->width, sw_frame->height); 258 | thread()->sigVideoStarted(sw_frame->width, sw_frame->height); 259 | } 260 | render_->upLoad(buffer_, sw_frame->width, sw_frame->height); 261 | }); 262 | 263 | fail: 264 | av_frame_unref(sw_frame); 265 | av_frame_unref(frame); 266 | 267 | if (ret < 0) 268 | return ret; 269 | } 270 | 271 | return 0; 272 | } 273 | -------------------------------------------------------------------------------- /VideoWidget/videodecode/ffmpegqsvdecode.h: -------------------------------------------------------------------------------- 1 | #ifndef FFMPEGQSVDECODE_H 2 | #define FFMPEGQSVDECODE_H 3 | 4 | #include "decodetaskmanagerimpl.h" 5 | class VideoRender; 6 | class FFmpegQsvDecode : public DecodeTask 7 | { 8 | public: 9 | FFmpegQsvDecode(DecodeTaskManagerImpl *taskManger, RenderThread *render_thr); 10 | ~FFmpegQsvDecode(); 11 | void decode(const QString &url) override; 12 | 13 | private: 14 | int decode_packet(AVCodecContext *decoder_ctx, 15 | AVFrame *frame, AVFrame *sw_frame, 16 | AVPacket *pkt); 17 | 18 | DecodeTaskManagerImpl *taskManager_ = nullptr; 19 | AVPixelFormat hw_pix_fmt; 20 | uint8_t *buffer_ = nullptr; 21 | int bufferSize_ = 0, curFps_ = 0; 22 | qint64 start_pt_ = 0, last_pts_ = AV_NOPTS_VALUE; 23 | int decode_frames_ = 0; 24 | AVRational stream_time_base_; 25 | 26 | VideoRender *render_{nullptr}; 27 | }; 28 | 29 | #endif // FFMPEGQSVDECODE_H 30 | -------------------------------------------------------------------------------- /VideoWidget/videodecode/nvidia_decoer_api.h: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2020-08-03 19:00:06 4 | * @LastEditTime: 2020-08-03 21:25:17 5 | * @LastEditors: Please set LastEditors 6 | * @Description: In User Settings Edit 7 | * @FilePath: \git\NvidiaDecoder\nvidia_decoer_api.h 8 | */ 9 | #pragma once 10 | 11 | #ifdef linux 12 | # define DECODER_EXPORT __attribute__((visibility("default"))) 13 | #else 14 | # define DECODER_EXPORT __declspec(dllimport) 15 | #endif 16 | #include 17 | #include 18 | 19 | class DECODER_EXPORT DecoderApi 20 | { 21 | public: 22 | virtual ~DecoderApi(){}; 23 | virtual int fps() = 0; 24 | virtual void stop() = 0; 25 | virtual void* context() = 0; 26 | virtual void decode(const std::string &source, const bool useDeviceFrame, const std::function call_back) = 0; 27 | }; 28 | 29 | typedef DecoderApi* (*CreateDecoderFunc)(void); 30 | -------------------------------------------------------------------------------- /VideoWidget/videodecode/nvidiadecode.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: your name 3 | * @Date: 2020-08-03 18:38:52 4 | * @LastEditTime: 2020-08-03 21:05:29 5 | * @LastEditors: your name 6 | * @Description: In User Settings Edit 7 | * @FilePath: \qt_project\VideoWidget\VideoWidget\videodecode\nvidiadecode.cpp 8 | */ 9 | extern "C" 10 | { 11 | #include "libavformat/avformat.h" 12 | } 13 | #include 14 | #include 15 | #include "../renderthread.h" 16 | #include "nvidiadecode.h" 17 | 18 | NvidiaDecode::NvidiaDecode(DecodeTaskManagerImpl *taskManger, CreateDecoderFunc func, RenderThread *render_thr): 19 | DecodeTask(render_thr), 20 | taskManager_(taskManger), 21 | createDecoderFunc_(func) 22 | { 23 | 24 | } 25 | 26 | NvidiaDecode::~NvidiaDecode() 27 | { 28 | qDebug() << "NvidiaDecode::~NvidiaDecode()"; 29 | if(decoder_){ 30 | delete decoder_; 31 | decoder_ = nullptr; 32 | } 33 | } 34 | 35 | extern CreateDecoderFunc g_gpurender_fuc_; 36 | void NvidiaDecode::decode(const QString &url) 37 | { 38 | if(!createDecoderFunc_){ 39 | return; 40 | } 41 | 42 | decoder_ = createDecoderFunc_(); 43 | if(!decoder_){ 44 | return; 45 | } 46 | std::string error; 47 | decoder_->decode(url.toStdString().data(), true, [&](void* ptr, const int pix, const int width, const int height, const std::string &err){ 48 | if(thread()->isInterruptionRequested()){ 49 | decoder_->stop(); 50 | } 51 | error = err; 52 | if(!err.empty()) 53 | { 54 | thread()->sigError(QString::fromStdString(err)); 55 | } 56 | if(pix != AV_PIX_FMT_NV12){ 57 | return; 58 | } 59 | 60 | qint64 end_pt = QDateTime::currentMSecsSinceEpoch(); 61 | if((end_pt - start_pt_) >= 1000) 62 | { 63 | if(decode_frames_ != curFps_) 64 | { 65 | curFps_ = decode_frames_; 66 | thread()->sigCurFpsChanged(curFps_); 67 | } 68 | start_pt_ = end_pt; 69 | decode_frames_ = 0; 70 | } 71 | decode_frames_++; 72 | 73 | thread()->Render([&](){ 74 | if(!render_) 75 | { 76 | thread()->setExtraData(decoder_->context()); 77 | render_ = thread()->getRender(AV_PIX_FMT_CUDA); 78 | render_->initialize(width, height); 79 | if(decoder_->fps()) 80 | { 81 | thread()->sigFps(decoder_->fps()); 82 | } 83 | thread()->sigVideoStarted(width, height); 84 | } 85 | render_->upLoad(reinterpret_cast(ptr), width, height); 86 | }); 87 | }); 88 | 89 | if(!thread()->isInterruptionRequested()){ 90 | if(url.left(4) == "rtsp" && error.empty()){ 91 | thread()->sigError("AVERROR_EOF"); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /VideoWidget/videodecode/nvidiadecode.h: -------------------------------------------------------------------------------- 1 | #ifndef NVIDIADECODE_H 2 | #define NVIDIADECODE_H 3 | 4 | #include "decodetaskmanagerimpl.h" 5 | class VideoRender; 6 | class NvidiaDecode : public DecodeTask 7 | { 8 | public: 9 | NvidiaDecode(DecodeTaskManagerImpl *taskManger, CreateDecoderFunc func, RenderThread *render_thr); 10 | ~NvidiaDecode() override; 11 | 12 | void decode(const QString &url) override; 13 | 14 | private: 15 | DecodeTaskManagerImpl *taskManager_ = nullptr; 16 | CreateDecoderFunc createDecoderFunc_; 17 | DecoderApi *decoder_ = nullptr; 18 | VideoRender *render_{nullptr}; 19 | 20 | qint64 decode_frames_ = 0, curFps_ = 0, start_pt_ = 0; 21 | }; 22 | 23 | #endif // NVIDIADECODE_H 24 | -------------------------------------------------------------------------------- /VideoWidget/videowidget.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "renderthread.h" 4 | #include "videowidget.h" 5 | 6 | VideoWidget::VideoWidget(QWidget *parent) 7 | { 8 | isFrameSwapped_.store(false); 9 | connect(this, &QOpenGLWidget::aboutToCompose, this, &VideoWidget::slotAboutToCompose); 10 | connect(this, &QOpenGLWidget::frameSwapped, this, &VideoWidget::slotFrameSwapped); 11 | connect(this, &QOpenGLWidget::aboutToResize, this, &VideoWidget::slotAboutToResize); 12 | connect(this, &QOpenGLWidget::resized, this, &VideoWidget::slotResized); 13 | 14 | m_thread = new RenderThread(this); 15 | connect(m_thread, &RenderThread::sigContextWanted, this, &VideoWidget::slotGrabContext); 16 | 17 | connect(m_thread, &RenderThread::sigError, this, &VideoWidget::sigError); 18 | connect(m_thread, &RenderThread::sigVideoStarted, this, [&](int w, int h){m_state_ = Play; emit sigVideoStarted(w, h);}); 19 | connect(m_thread, &RenderThread::sigFps, this, &VideoWidget::sigFps); 20 | connect(m_thread, &RenderThread::sigCurFpsChanged, this, &VideoWidget::sigCurFpsChanged); 21 | } 22 | 23 | VideoWidget::~VideoWidget() 24 | { 25 | if(m_thread->isRunning()) 26 | { 27 | slotStop(); 28 | } 29 | delete m_thread; 30 | } 31 | 32 | bool VideoWidget::isFrameSwapped() const 33 | { 34 | return isFrameSwapped_.load(); 35 | } 36 | 37 | QString VideoWidget::url() const 38 | { 39 | return source_file_; 40 | } 41 | 42 | QString VideoWidget::deviceName() const 43 | { 44 | return device_name_; 45 | } 46 | 47 | VideoWidget::PlayState VideoWidget::state() const 48 | { 49 | return m_state_; 50 | } 51 | 52 | void VideoWidget::slotPlay(QString filename, QString device) 53 | { 54 | m_state_ = Ready; 55 | if(m_thread->isRunning()) 56 | { 57 | slotStop(); 58 | } 59 | source_file_ = filename; 60 | device_name_ = device; 61 | m_thread->setDevice(device); 62 | m_thread->setFileName(filename); 63 | m_thread->start(); 64 | } 65 | 66 | void VideoWidget::slotStop() 67 | { 68 | m_thread->requestInterruption(); 69 | m_thread->prepareExit(); 70 | m_thread->quit(); 71 | m_thread->wait(); 72 | m_state_ = Stopped; 73 | } 74 | 75 | void VideoWidget::resizeGL(int w, int h) 76 | { 77 | // m_thread->lockRenderer(); 78 | context()->functions()->glViewport(0, 0, w, h); 79 | // m_thread->unlockRenderer(); 80 | } 81 | 82 | void VideoWidget::slotGrabContext() 83 | { 84 | m_thread->lockRenderer(); 85 | QMutexLocker lock(m_thread->grabMutex()); 86 | context()->moveToThread(m_thread); 87 | m_thread->grabCond()->wakeAll(); 88 | m_thread->unlockRenderer(); 89 | } 90 | 91 | void VideoWidget::slotAboutToCompose() 92 | { 93 | m_thread->lockRenderer(); 94 | } 95 | 96 | void VideoWidget::slotFrameSwapped() 97 | { 98 | m_thread->unlockRenderer(); 99 | isFrameSwapped_.store(true); 100 | } 101 | 102 | void VideoWidget::slotAboutToResize() 103 | { 104 | m_thread->lockRenderer(); 105 | isFrameSwapped_.store(false); 106 | } 107 | 108 | void VideoWidget::slotResized() 109 | { 110 | m_thread->unlockRenderer(); 111 | } 112 | -------------------------------------------------------------------------------- /VideoWidget/videowidget.h: -------------------------------------------------------------------------------- 1 | #ifndef VIDEOWIDGET_H 2 | #define VIDEOWIDGET_H 3 | 4 | #include 5 | #include 6 | QT_FORWARD_DECLARE_CLASS(RenderThread) 7 | class VideoWidget : public QOpenGLWidget 8 | { 9 | Q_OBJECT 10 | public: 11 | enum PlayState{ 12 | Ready, 13 | Play, 14 | Stopped 15 | }; 16 | VideoWidget(QWidget *parent = nullptr); 17 | ~VideoWidget() override; 18 | bool isFrameSwapped() const; 19 | QString url() const; 20 | QString deviceName() const; 21 | PlayState state() const; 22 | 23 | public slots: 24 | void slotPlay(QString filename, QString device); 25 | void slotStop(); 26 | 27 | signals: 28 | void sigRequestRender(); 29 | 30 | void sigError(QString); 31 | void sigVideoStarted(int, int); 32 | void sigFps(int); 33 | void sigCurFpsChanged(int); 34 | 35 | protected: 36 | void paintEvent(QPaintEvent *) override { } 37 | void resizeGL(int w, int h) override; 38 | 39 | private slots: 40 | void slotGrabContext(); 41 | void slotAboutToCompose(); 42 | void slotFrameSwapped(); 43 | void slotAboutToResize(); 44 | void slotResized(); 45 | 46 | private: 47 | RenderThread *m_thread; 48 | std::atomic_bool isFrameSwapped_; 49 | QString source_file_, device_name_; 50 | PlayState m_state_; 51 | }; 52 | 53 | #endif // VIDEOWIDGET_H 54 | -------------------------------------------------------------------------------- /VideoWidget/videowidget.pri: -------------------------------------------------------------------------------- 1 | HEADERS += \ 2 | $$PWD/render/factory.h \ 3 | $$PWD/render/nv12render.h \ 4 | $$PWD/render/videorender.h \ 5 | $$PWD/render/yuvrender.h \ 6 | $$PWD/renderthread.h \ 7 | $$PWD/videodecode/decodetaskmanagerimpl.h \ 8 | $$PWD/videodecode/decodtask.h \ 9 | $$PWD/videodecode/ffmpegcpudecode.h \ 10 | $$PWD/videodecode/ffmpegcudadecode.h \ 11 | $$PWD/videodecode/ffmpegqsvdecode.h \ 12 | $$PWD/videodecode/nvidia_decoer_api.h \ 13 | $$PWD/videodecode/nvidiadecode.h \ 14 | $$PWD/videowidget.h 15 | 16 | SOURCES += \ 17 | $$PWD/render/nv12render.cpp \ 18 | $$PWD/render/yuvrender.cpp \ 19 | $$PWD/renderthread.cpp \ 20 | $$PWD/videodecode/decodetaskmanagerimpl.cpp \ 21 | $$PWD/videodecode/decodtask.cpp \ 22 | $$PWD/videodecode/ffmpegcpudecode.cpp \ 23 | $$PWD/videodecode/ffmpegcudadecode.cpp \ 24 | $$PWD/videodecode/ffmpegqsvdecode.cpp \ 25 | $$PWD/videodecode/nvidiadecode.cpp \ 26 | $$PWD/videowidget.cpp 27 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "mainwindow.h" 6 | 7 | extern "C" { 8 | //确保连接了nvidia的显示器 9 | // _declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; 10 | //确保连接了amd的显示器 11 | // __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 0x00000001; 12 | } 13 | 14 | int main(int argc, char *argv[]) 15 | { 16 | QApplication a(argc, argv); 17 | 18 | // QSurfaceFormat format; 19 | // format.setDepthBufferSize(8); 20 | // format.setStencilBufferSize(8); 21 | // format.setSwapBehavior(QSurfaceFormat::DoubleBuffer); 22 | // QSurfaceFormat::setDefaultFormat(format); 23 | 24 | MainWindow w; 25 | w.show(); 26 | 27 | return a.exec(); 28 | } 29 | -------------------------------------------------------------------------------- /mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "mainwindow.h" 8 | #include "VideoWidget/videowidget.h" 9 | 10 | MainWindow::MainWindow(QWidget *parent) 11 | : QWidget(parent) 12 | { 13 | playBtn_ = new QPushButton("play"); 14 | //rtsp://192.168.2.66/mclz_cooking.mp4 rtsp://192.168.2.66/person.avi 15 | urlEdit_ = new QLineEdit("rtsp://10.10.8.11/person.avi"); 16 | decoderBox_ = new QComboBox(); 17 | gridLay_ = new QGridLayout; 18 | dCheck_ = new QCheckBox("auto transform"); 19 | dCheck_->setChecked(true); 20 | 21 | QVBoxLayout *mainLay = new QVBoxLayout; 22 | 23 | decoderBox_->addItems(QStringList() << tr("cpu") << tr("cuda") << tr("cuda_plugin") << tr("qsv")); 24 | QHBoxLayout *hlay = new QHBoxLayout; 25 | hlay->addWidget(urlEdit_); 26 | hlay->addStretch(); 27 | mainLay->addLayout(hlay); 28 | 29 | hlay = new QHBoxLayout; 30 | hlay->addWidget(decoderBox_); 31 | hlay->addWidget(dCheck_); 32 | hlay->addWidget(playBtn_); 33 | hlay->addStretch(); 34 | mainLay->addLayout(hlay); 35 | 36 | mainLay->addLayout(gridLay_); 37 | connect(playBtn_, SIGNAL(clicked()), this, SLOT(slotPlayBtnClicked())); 38 | 39 | setLayout(mainLay); 40 | 41 | for(int i = 0; i < 5; i++) 42 | { 43 | for(int j = 0; j < 4; j++) 44 | { 45 | QLabel *fpsL = new QLabel; 46 | QPalette pal = fpsL->palette(); 47 | pal.setColor(QPalette::Foreground, Qt::blue); 48 | fpsL->setPalette(pal); 49 | QFont f = fpsL->font(); 50 | f.setPixelSize(18); 51 | fpsL->setFont(f); 52 | QLabel *curFpsL = new QLabel; 53 | curFpsL->setPalette(pal); 54 | curFpsL->setFont(f); 55 | VideoWidget *videoW = new VideoWidget; 56 | connect(videoW, &VideoWidget::sigFps, [=](int fps){ 57 | fpsL->setNum(fps); 58 | QPalette pal = fpsL->palette(); 59 | pal.setColor(QPalette::Foreground, Qt::green); 60 | fpsL->setPalette(pal); 61 | }); 62 | 63 | connect(videoW, &VideoWidget::sigCurFpsChanged, [=](int curFps){ 64 | curFpsL->setNum(curFps); 65 | QPalette pal = curFpsL->palette(); 66 | pal.setColor(QPalette::Foreground, Qt::red); 67 | curFpsL->setPalette(pal); 68 | }); 69 | 70 | QHBoxLayout *hlay = new QHBoxLayout; 71 | hlay->addStretch(); 72 | hlay->addWidget(curFpsL); 73 | hlay->addWidget(fpsL); 74 | QVBoxLayout *vlay = new QVBoxLayout; 75 | vlay->addLayout(hlay); 76 | vlay->addStretch(); 77 | videoW->setLayout(vlay); 78 | gridLay_->addWidget(videoW, i, j); 79 | } 80 | } 81 | } 82 | 83 | MainWindow::~MainWindow() 84 | { 85 | 86 | } 87 | 88 | void MainWindow::slotPlayBtnClicked() 89 | { 90 | VideoWidget *videoW = qobject_cast(gridLay_->itemAt(playIndex % gridLay_->count())->widget()); 91 | QString decoder; 92 | decoder = decoderBox_->currentText(); 93 | if(dCheck_->isChecked()) 94 | { 95 | decoder = decoderBox_->itemText(playIndex % decoderBox_->count()); 96 | } 97 | videoW->slotPlay(urlEdit_->text(), decoder); 98 | playIndex++; 99 | } 100 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | QT_FORWARD_DECLARE_CLASS(QPushButton) 6 | QT_FORWARD_DECLARE_CLASS(QLineEdit) 7 | QT_FORWARD_DECLARE_CLASS(QGridLayout) 8 | QT_FORWARD_DECLARE_CLASS(QComboBox) 9 | QT_FORWARD_DECLARE_CLASS(QCheckBox) 10 | class MainWindow : public QWidget 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | MainWindow(QWidget *parent = 0); 16 | ~MainWindow(); 17 | 18 | private slots: 19 | void slotPlayBtnClicked(); 20 | 21 | private: 22 | QPushButton *playBtn_; 23 | QLineEdit *urlEdit_; 24 | QGridLayout *gridLay_; 25 | QComboBox *decoderBox_; 26 | QCheckBox *dCheck_; 27 | int playIndex = 0; 28 | }; 29 | 30 | #endif // MAINWINDOW_H 31 | -------------------------------------------------------------------------------- /run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitHubwhl562916378/IntelCudaVideoDecodDetect/1f6d5a7c857f52b5e2cc2c46a5024ba72984f3c3/run.png --------------------------------------------------------------------------------