note

这里放一些简单的示例,不少是自己系统学习FFmpeg前写的,不一定完全对,要注意甄别。

一、FFmpeg读取流来展示

这个跟推流没关系,但主要也涉及到拉流解码展示,算是比较吻合。


下载最前面的环境问题:

首先这次我用了ffmpeg的两个版本:


​ ffmpeg的avformat_open_input此函数,一般给视频地址、rtsp地址,linux下的摄像头名字,windows的摄像头还不知道怎么搞(这有一个博客,暂时没用,放这里看以后有没有可能做一个参考),

rtsp摄像头读取时,直接使用opencv解流,画面会存在明显的延迟,现在使用ffmpeg来读取流,然后用opencv的cv::imshow()来展示;或者再转换成opengl的格式,用imgui的界面来展示


* AV_PIX_FMT_BGRA 对应opencv中的CV_8UC4,

* AV_PIX_FMT_BGR24 对应opencv中的CV_8UC3。

1.1. 第一种:ffmpeg读流,转Mat格式,opencv做展示

1.2. 第二种:Mat格式,opengl加载,imgui做展示


然后把直接opencv读取的流数据(上面是ffmpeg读取的数据流),转成opengl需要的格式,用imgui展示的主要代码也写一下吧:

//视频的处理,cv的mat转成opengl要得格式
void Mat2Texture(cv::Mat &image, GLuint &imagetexture) {
	if (image.empty()) return;

	//generate texture using gl commands
	glBindTexture(GL_TEXTURE_2D, imagetexture);

	// setup filtering parameters for display
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.cols, image.rows, 0, GL_RGB, GL_UNSIGNED_BYTE, image.data);
}


int main() {
	cv::VideoCapture cap(0);
	cv::mat frame;
	
	GLuint video_texture = 0;
	while (1) {
		cap >> frame;
		cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB);
		Mat2Texture(frame, video_texture);
		ImGui::Image((void*)(intptr_t)video_texture, ImVec2(frame.cols, frame.rows));
	}
}

总结:1最流畅、(2、3似乎差不多)

  1. 以上用ffmpeg读取rtsp摄像头(换成opengl格式在imgui里展示,或使用opencv来展示)、画面是最流畅的;
  2. 用opencv读取rtsp摄像头(换成opengl的格式在imgui里展示),画面也算流畅,看起来有一些延迟;
  3. 但是用opencv直接读取rtsp摄像头,opencv直接来展示画面,无论python、c++的方式,画面都是有相当明显明显的延迟。(c++中release和python画面看起来差不多,c++中的Debug下会更卡顿)

(注:ffmpeg读取、opencv读取,两种方式在imgui中显示,imgui中都显示大概都是50ms,fps在20)(以上为什么要把ffmpeg读取的数据转成Mat格式呢,是因为搞了半天,不会直接把ffmpeg读取的数据转成opengl想要的, 所以有这么一个中间过渡)

1.3. 第三种:cuda硬解码

这是家里的电脑,使用cuda,然后是根据官方的这个硬件的demo改的,先说版本:

注意:以下的方式,跟这个博客的教程的做法有一些不同(把官方代码,按照博客那样去改了,运行始终有问题,第一帧画面可以出来,但是后面就不行了,如果暂时用不到就不去深究了,就先按照官方改的这个来把,也==注意当有av_frame_alloc()、av_packet_alloc()申请内存时,一定要去释放,会内存泄露的==,下面代码没改了,可以参见“第一种”的代码去改这两次的内存泄漏,搜索“解决读取内存泄露”):

#include <stdio.h>

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/pixdesc.h>
#include <libavutil/hwcontext.h>
#include <libavutil/opt.h>
#include <libavutil/avassert.h>
#include <libavutil/imgutils.h>

#include <libavutil/hwcontext.h>
#include <libswscale/swscale.h>
}

#include <vector>
#include <opencv2/opencv.hpp>

#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "swscale.lib")


struct Color_RGB {
	uint8_t r;
	uint8_t g;
	uint8_t b;
};


static AVBufferRef *hw_device_ctx = NULL;
static enum AVPixelFormat hw_pix_fmt;
//static FILE *output_file = NULL;

static int hw_decoder_init(AVCodecContext *ctx, const enum AVHWDeviceType type)
{
	int err = 0;

	if ((err = av_hwdevice_ctx_create(&hw_device_ctx, type,
		NULL, NULL, 0)) < 0) {
		fprintf(stderr, "Failed to create specified HW device.\n");
		return err;
	}
	ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);

	return err;
}

static enum AVPixelFormat get_hw_format(AVCodecContext *ctx,
	const enum AVPixelFormat *pix_fmts)
{
	const enum AVPixelFormat *p;

	for (p = pix_fmts; *p != -1; p++) {
		if (*p == hw_pix_fmt)
			return *p;
	}

	fprintf(stderr, "Failed to get HW surface format.\n");
	return AV_PIX_FMT_NONE;
}

static int decode_write(AVCodecContext *avctx, AVPacket *packet, std::vector<Color_RGB> &my_buffer)
{
	AVFrame *frame = NULL, *sw_frame = NULL;
	AVFrame *tmp_frame = NULL;
	uint8_t *buffer = NULL;
	int size;
	int ret = 0;

	ret = avcodec_send_packet(avctx, packet);
	if (ret < 0) {
		fprintf(stderr, "Error during decoding\n");
		return ret;
	}

	while (1) {
		if (!(frame = av_frame_alloc()) || !(sw_frame = av_frame_alloc())) {
			fprintf(stderr, "Can not alloc frame\n");
			ret = AVERROR(ENOMEM);
			goto fail;
		}

		ret = avcodec_receive_frame(avctx, frame);
		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
			av_frame_free(&frame);
			av_frame_free(&sw_frame);
			return 0;
		}
		else if (ret < 0) {
			fprintf(stderr, "Error while decoding\n");
			goto fail;
		}

		if (frame->format == hw_pix_fmt) {
			/* retrieve data from GPU to CPU */
			if ((ret = av_hwframe_transfer_data(sw_frame, frame, 0)) < 0) {
				fprintf(stderr, "Error transferring the data to system memory\n");
				goto fail;
			}
			tmp_frame = sw_frame;

			// 这是我加的,方便opencv 这展示
			static SwsContext *swsctx = nullptr;
			swsctx = sws_getCachedContext(swsctx,
				tmp_frame->width, tmp_frame->height, static_cast<AVPixelFormat>(tmp_frame->format),
				tmp_frame->width, tmp_frame->height, AVPixelFormat::AV_PIX_FMT_BGR24, NULL, NULL, NULL, NULL
			);


			uint8_t* data[] = { reinterpret_cast<uint8_t*>(&my_buffer[0]) };
            // 可等价写成,.begin()得到的类型是iter,只是类似指针,所以要*解引用,得到第一个,在&取地址
            // uint8_t* data[] = { reinterpret_cast<uint8_t*>(&*my_buffer.begin()) };
			int linesize[] = { tmp_frame->width * 3 };
			sws_scale(swsctx, tmp_frame->data, tmp_frame->linesize, 0, tmp_frame->height, data, linesize);
			
            // tmp_frame->format对应的pixel的值是 enum AVPixelFormat::AV_PIX_FMT_NV12 ,那篇教程里提到过的,以后可以参考吧,教程里就是用的sws_scale来处理的
            // 到这,上

		}
		else
			tmp_frame = frame;

		// 这是写到文件的代码,暂时不要,写出来的视频也打不开
		//size = av_image_get_buffer_size(static_cast<AVPixelFormat>(tmp_frame->format), tmp_frame->width,
		//	tmp_frame->height, 1);
		 // 下面这行原来的代码是buffer=av_malloc(size),在c++中不能隐式的类型转换,参考:https://m.656463.com/wenda/Cmalloccwcvoidwxzhwuint8_t_371
		//buffer = static_cast<uint8_t *>(av_malloc(size));
		//if (!buffer) {
		//	fprintf(stderr, "Can not alloc buffer\n");
		//	ret = AVERROR(ENOMEM);
		//	goto fail;
		//}
		//ret = av_image_copy_to_buffer(buffer, size,
		//	(const uint8_t * const *)tmp_frame->data,
		//	(const int *)tmp_frame->linesize, static_cast<AVPixelFormat>(tmp_frame->format),
		//	tmp_frame->width, tmp_frame->height, 1);
		//if (ret < 0) {
		//	fprintf(stderr, "Can not copy image to buffer\n");
		//	goto fail;
		//}

		//if ((ret = fwrite(buffer, 1, size, output_file)) < 0) {
		//	fprintf(stderr, "Failed to dump raw data.\n");
		//	goto fail;
		//}


	fail:
		av_frame_free(&frame);
		av_frame_free(&sw_frame);
		av_freep(&buffer);
		if (ret < 0)
			return ret;
	}
}

int main(int argc, char *argv[])
{

	// cuda "C:\\Users\\Administrator\\Videos\\keypoint_result.mp4"  
	// cuda "rtsp://192.168.108.11:554/user=admin&password=&channel=1&stream=1.sdp?" 

	AVFormatContext *input_ctx = NULL;
	int video_stream, ret;
	AVStream *video = NULL;
	AVCodecContext *decoder_ctx = NULL;
	const AVCodec *decoder = NULL;
	AVPacket *packet = NULL;
	enum AVHWDeviceType type;
	int i;

	if (argc < 3) {
		fprintf(stderr, "Usage: %s <device type> <input file>\n", argv[0]);
		return -1;
	}

	type = av_hwdevice_find_type_by_name(argv[1]);
	if (type == AV_HWDEVICE_TYPE_NONE) {
		fprintf(stderr, "Device type %s is not supported.\n", argv[1]);
		fprintf(stderr, "Available device types:");
		while ((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE)
			fprintf(stderr, " %s", av_hwdevice_get_type_name(type));
		fprintf(stderr, "\n");
		return -1;
	}

	packet = av_packet_alloc();
	if (!packet) {
		fprintf(stderr, "Failed to allocate AVPacket\n");
		return -1;
	}

	/* open the input file */
	if (avformat_open_input(&input_ctx, argv[2], NULL, NULL) != 0) {
		fprintf(stderr, "Cannot open input file '%s'\n", argv[2]);
		return -1;
	}

	if (avformat_find_stream_info(input_ctx, NULL) < 0) {
		fprintf(stderr, "Cannot find input stream information.\n");
		return -1;
	}

	/* find the video stream information */
	ret = av_find_best_stream(input_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &decoder, 0);
	if (ret < 0) {
		fprintf(stderr, "Cannot find a video stream in the input file\n");
		return -1;
	}
	video_stream = ret;

	for (i = 0;; i++) {
		const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i);
		if (!config) {
			fprintf(stderr, "Decoder %s does not support device type %s.\n",
				decoder->name, av_hwdevice_get_type_name(type));
			return -1;
		}
		if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&
			config->device_type == type) {
			// reelase下,这行报错的话,尝试把hw_pix_fmt定义前面的static去掉
			hw_pix_fmt = config->pix_fmt;   // 117 enum AVPixelFormat::AV_PIX_FMT_CUDA
			break;
		}
	}

	if (!(decoder_ctx = avcodec_alloc_context3(decoder)))
		return AVERROR(ENOMEM);

	video = input_ctx->streams[video_stream];
	if (avcodec_parameters_to_context(decoder_ctx, video->codecpar) < 0)
		return -1;

	decoder_ctx->get_format = get_hw_format;

	if (hw_decoder_init(decoder_ctx, type) < 0)
		return -1;

	if ((ret = avcodec_open2(decoder_ctx, decoder, NULL)) < 0) {
		fprintf(stderr, "Failed to open codec for stream #%u\n", video_stream);
		return -1;
	}

	/* open the file to dump raw data */
	//output_file = fopen(argv[3], "w+b");


	std::vector<Color_RGB> my_buffer(decoder_ctx->width * decoder_ctx->height);
	cv::Mat img(cv::Size(decoder_ctx->width, decoder_ctx->height), CV_8UC3);


	/* actual decoding and dump the raw data */
	while (ret >= 0) {
		if ((ret = av_read_frame(input_ctx, packet)) < 0)
			break;

		if (video_stream == packet->stream_index) {
			ret = decode_write(decoder_ctx, packet, my_buffer);
			// 不要用下面这种循环了,图像大了,会很慢,在这里搜索img.data = data
             int time = 0;
			for (int i = 0; i < img.rows; ++i) {
				for (int j = 0; j < img.cols; ++j) {
					img.at<cv::Vec3b>(i, j) = { my_buffer[time].r, my_buffer[time].g, my_buffer[time].b };
					time++;
				}
			}
			cv::imshow("1", img);
			if ((cv::waitKey(1) & 0xff) != 255) break;
		}


		av_packet_unref(packet);
	}

	/* flush the decoder */
	ret = decode_write(decoder_ctx, NULL, my_buffer);

	//if (output_file)
	//	fclose(output_file);

	av_packet_free(&packet);
	avcodec_free_context(&decoder_ctx);
	avformat_close_input(&input_ctx);
	av_buffer_unref(&hw_device_ctx);

	cv::destroyAllWindows();

	return 0;
}

1.4. 第四种:ffmpeg(opencv)读流+opengl贴图+glfw展示

肯定要准备ffmpeg、opencv、opengl、glfw、glad这些的环境,这里不赘述了。

​ 过程中发现了一个问题:用ffmpeg读取,opengl+glfw做展示,因为av_frame_alloc()、av_packet_alloc()出现了内存泄露的问题,这个算是解决了,但随着时间的推移,内存使用还是在缓慢增加,发现是vs的ServiceHub.DataWarehouseHost.exe这个程序在持续慢慢泄露内存,要使用的话,要解决这个问题,或者说linux上看会不会出现这个问题。

  1. 首先是opengl的shader,以及处理shader的类:

    • 顶点着色器:shader.vs

      // 调整顶点着色器使其能够接受顶点坐标为一个顶点属性,并把坐标传给片段着色器
      #version 330 core
      layout(location = 0) in vec3 aPos;
      layout(location = 1) in vec3 aColor;
      layout(location = 2) in vec2 aTexCoord;  // 这个0、1、2和代码中设置顶点属性是对应的
           
      out vec3 ourColor;
      out vec2 TexCoord;
           
      void main() {
      	gl_Position = vec4(aPos, 1.0);
      	ourColor = aColor;
      	TexCoord = aTexCoord;
      }
      
    • 片段着色器:shader.fs

      #version 330 core     // opengl版本决定的
      out vec4 FragColor;
           
      in vec3 ourColor;
      in vec2 TexCoord;
           
      // GLSL有一个供纹理对象使用的内建数据类型,叫做采样器(Sampler),它以纹理类型作为后缀,比如sampler1D、sampler3D
      uniform sampler2D ourTexture;  // 记得一定要声明为uniform
           
      void main() {
      	// GLSL内建的texture函数来采样纹理的颜色,它第一个参数是纹理采样器,第二个参数是对应的纹理坐标
           FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0f);
          // FragColor = texture(ourTexture, TexCoord);
      }
      
    • shader的类:shader_s.h

      #pragma once
      #ifndef SHADER_H
      #define SHADER_H
           
      #include <glad/glad.h>
      #include <string>
      #include <fstream>
      #include <sstream>
      #include <iostream>
           
      class Shader {
      public:
      	unsigned int ID;
      	// constructor generates the shader on the fly
      	Shader(const char* vertexPath, const char* fragmentPath) {
      		// 1. retrieve the vertex/fragment source code from filePath
      		std::string vertexCode;
      		std::string fragmentCode;
      		std::ifstream vShaderFile;
      		std::ifstream fShaderFile;
      		// ensure ifstream objects can throw exceptions:
      		vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
      		fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
      		try {
      			// open files
      			vShaderFile.open(vertexPath);
      			fShaderFile.open(fragmentPath);
      			std::stringstream vShaderStream, fShaderStream;
      			// read file's buffer contents into streams
      			vShaderStream << vShaderFile.rdbuf();
      			fShaderStream << fShaderFile.rdbuf();
      			// close file handlers
      			vShaderFile.close();
      			fShaderFile.close();
      			// convert stream into string
      			vertexCode = vShaderStream.str();
      			fragmentCode = fShaderStream.str();
      		}
      		catch (std::ifstream::failure& e) {
      			std::cout << "ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ: " << e.what() << std::endl;
      		}
      		const char* vShaderCode = vertexCode.c_str();
      		const char * fShaderCode = fragmentCode.c_str();
      		// 2. compile shaders
      		unsigned int vertex, fragment;
      		// vertex shader
      		vertex = glCreateShader(GL_VERTEX_SHADER);
      		glShaderSource(vertex, 1, &vShaderCode, NULL);
      		glCompileShader(vertex);
      		checkCompileErrors(vertex, "VERTEX");
      		// fragment Shader
      		fragment = glCreateShader(GL_FRAGMENT_SHADER);
      		glShaderSource(fragment, 1, &fShaderCode, NULL);
      		glCompileShader(fragment);
      		checkCompileErrors(fragment, "FRAGMENT");
      		// shader Program
      		ID = glCreateProgram();
      		glAttachShader(ID, vertex);
      		glAttachShader(ID, fragment);
      		glLinkProgram(ID);
      		checkCompileErrors(ID, "PROGRAM");
      		// delete the shaders as they're linked into our program now and no longer necessary
      		glDeleteShader(vertex);
      		glDeleteShader(fragment);
      	}
      	// activate the shader
      	void use() {
      		glUseProgram(ID);
      	}
      	// utility uniform functions
      	void setBool(const std::string &name, bool value) const {
      		glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
      	}
      	void setInt(const std::string &name, int value) const {
      		glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
      	}
      	void setFloat(const std::string &name, float value) const {
      		glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
      	}
           
      private:
      	// utility function for checking shader compilation/linking errors.
      	void checkCompileErrors(unsigned int shader, std::string type) {
      		int success;
      		char infoLog[1024];
      		if (type != "PROGRAM") {
      			glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
      			if (!success) {
      				glGetShaderInfoLog(shader, 1024, NULL, infoLog);
      				std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
      			}
      		}
      		else {
      			glGetProgramiv(shader, GL_LINK_STATUS, &success);
      			if (!success) {
      				glGetProgramInfoLog(shader, 1024, NULL, infoLog);
      				std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
      			}
      		}
      	}
      };
           
      #endif
      
  2. 使用opencv、ffmpeg读流,然后opengl做贴图,glfw展示:

    注:把下面“#define ffmpeg” 这行注释掉就会使用opencv读取视频流,默认是用ffmpeg

    #include <iostream>
    #include <glad/glad.h>  // 特别注意:在包含GLFW的头文件之前包含了GLAD的头文件。GLAD的头文件包含了正确的OpenGL头文件(例如GL/gl.h),所以需要在其它依赖于OpenGL的头文件之前包含GLAD。
    #include <GLFW/glfw3.h>
    #include <opencv2/opencv.hpp>
       
    #include "shader_s.h"
       
    extern "C" {
    #include <libavcodec/avcodec.h>
    #pragma comment(lib, "avcodec.lib")
       
    #include <libavformat/avformat.h>
    #pragma comment(lib, "avformat.lib")
       
    #include <libavutil/imgutils.h>
    #pragma comment(lib, "avutil.lib")
       
    	// 彩色画面要的
    #include <libswscale/swscale.h>
    #pragma comment(lib, "swscale.lib")
    }
       
    /*  下面是ffmpeg的读取  */
    /*
    	yuvj×××这个格式被丢弃了,然后转化为yuv格式,
    	不然有一个警告 deprecated pixel format used, make sure you did set range correctly,
    	这个问题在前面和win32写api时可用,但是不知道其它地方会不会报错,就改过了
    */
    AVPixelFormat ConvertDeprecatedFormat(enum AVPixelFormat format) {
    	switch (format) {
    	case AV_PIX_FMT_YUVJ420P:
    		return AV_PIX_FMT_YUV420P;
    		break;
    	case AV_PIX_FMT_YUVJ422P:
    		return AV_PIX_FMT_YUV422P;
    		break;
    	case AV_PIX_FMT_YUVJ444P:
    		return AV_PIX_FMT_YUV444P;
    		break;
    	case AV_PIX_FMT_YUVJ440P:
    		return AV_PIX_FMT_YUV440P;
    		break;
    	default:
    		return format;
    		break;
    	}
    }
    struct Color_RGB {
    	uint8_t r;
    	uint8_t g;
    	uint8_t b;
    };
    // 把获取第一帧那个函数拆分了一下
    struct DecoderParam {
    	AVFormatContext *fmtCtx;
    	AVCodecContext *vcodecCtx;
    	int width;
    	int height;
    	int VideoStreamIndex;
    };
    void InitDecoder(const char* filepath, DecoderParam &param) {
    	AVFormatContext *fmtCtx = nullptr;
       
    	// linux上这里一直报错,返回的是 -1330794744,网上搜索一圈了,ffmpeg也重新编译了,还是不行
    	int ret = avformat_open_input(&fmtCtx, filepath, NULL, NULL);
       
    	avformat_find_stream_info(fmtCtx, NULL);
       
    	AVCodecContext *vcodecCtx = nullptr;
    	for (int i = 0; i < fmtCtx->nb_streams; i++) {
    		const AVCodec *codec = avcodec_find_decoder(fmtCtx->streams[i]->codecpar->codec_id);
    		if (codec->type == AVMEDIA_TYPE_VIDEO) {
    			param.VideoStreamIndex = i;
    			vcodecCtx = avcodec_alloc_context3(codec);
    			avcodec_parameters_to_context(vcodecCtx, fmtCtx->streams[i]->codecpar);
    			avcodec_open2(vcodecCtx, codec, NULL);
                 break;  // 我加的,应该找到后就可以break了,有问题再删掉。
    		}
    	}
    	param.fmtCtx = fmtCtx;
    	param.vcodecCtx = vcodecCtx;
    	param.width = vcodecCtx->width;
    	param.height = vcodecCtx->height;
    }
    int RequestFrame(DecoderParam &param, AVFrame *frame, AVPacket *packet) {
    	auto &fmtCtx = param.fmtCtx;
    	auto &vcodecCtx = param.vcodecCtx;
    	auto &VideoStreamIndex = param.VideoStreamIndex;
       
    	while (1) {
    		// AVPacket *packet = av_packet_alloc();
    		int ret = av_read_frame(fmtCtx, packet);
    		if (ret == 0 && packet->stream_index == param.VideoStreamIndex) {
    			ret = avcodec_send_packet(vcodecCtx, packet);
    			if (ret == 0) {
    				ret = avcodec_receive_frame(vcodecCtx, frame);
    				if (ret == 0) {
    					av_packet_unref(packet);
    					return 0;  // 代表读取成功
    				}
    				else if (ret == AVERROR(EAGAIN)) {
    					av_frame_unref(frame);
    				}
    			}
    		}
    		av_packet_unref(packet);
    	}
    	return -1;
    }
    // 写一个转换颜色编码的函数
    void GetRGBPixels(AVFrame *frame, std::vector<Color_RGB> &buffer) {
    	static SwsContext *swsctx = nullptr;
    	swsctx = sws_getCachedContext(swsctx,
    		frame->width, frame->height, static_cast<AVPixelFormat>(frame->format),
    		frame->width, frame->height, AVPixelFormat::AV_PIX_FMT_BGR24, NULL, NULL, NULL, NULL
    	);  // 这里原来的类型转换是用的 (AVPixelFormat)frame->format
       
    	// 每次循环调用这个函数,都会重新分配这个vector,debug下就很慢
    	//std::vector<Color_RGB> buffer(frame->width * frame->height);
       
    	//uint8_t* data[] = {(uint8_t*)&buffer[0]};
    	uint8_t* data[] = { reinterpret_cast<uint8_t*>(&buffer[0]) }; 
    	int linesize[] = { frame->width * 3 };
    	// sws_scale 函数可以对画面进行缩放,同时还能改变颜色编码,
    	sws_scale(swsctx, frame->data, frame->linesize, 0, frame->height, data, linesize);
    	// return buffer;  // 不返回了,直接用buffer
    }
       
       
    void framebuffer_size_callback(GLFWwindow* window, int width, int height);
    void processInput(GLFWwindow* window);
       
    // settings
    #define ffmpeg   // 这行注释掉就用opencv读取视频流
    unsigned int SCR_WIDTH = 800;
    unsigned int SCR_HEIGHT = 600;
    const char* rtsp_path = "rtsp://192.168.108.146:554/user=admin&password=&channel=1&stream=0.sdp?";
       
    int main() {
       
    #ifdef ffmpeg
    	DecoderParam decoderParam;
    	InitDecoder(rtsp_path, decoderParam);  // 如果file_path.c_str()是,std::string,就写成file_path.c_str()
    	SCR_WIDTH = decoderParam.width;
    	SCR_HEIGHT = decoderParam.height;
    	auto &fmtCtx = decoderParam.fmtCtx;   // 这是引用,取一个别名
    	auto &vcodecCtx = decoderParam.vcodecCtx;
       
    	cv::Mat img(cv::Size(SCR_WIDTH, SCR_HEIGHT), CV_8UC3);
    	std::vector<Color_RGB> buffer(SCR_WIDTH * SCR_HEIGHT);
    	AVFrame *frame = av_frame_alloc();  // 提前分配
        AVPacket *packet = av_packet_alloc();
       
    #else
    	cv::VideoCapture cap;
    	cap.open(rtsp_path);
    	cv::Mat img;
    	if (cap.read(img)) {
    		SCR_WIDTH = img.cols;
    		SCR_HEIGHT = img.rows;
    	}
       
    #endif // ffmpeg
       
    	// 1、实例化GLFW窗口
    	glfwInit();
    	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);  // 说明opengl版本,方便glfw做调整
    	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);  //告诉GLFW我们使用的是核心模式(Core-profile);明确告诉GLFW我们需要使用核心模式意味着我们只能使用OpenGL功能的一个子集(没有我们已不再需要的向后兼容特性)
    #ifdef __APPLE__
    	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);  // 针对苹果,上一行core-profile才生效
    #endif
       
    	// 2、创建一个窗口对象
    	GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    	if (window == NULL) {
    		std::cout << "Failed to create GLFW window" << std::endl;
    		glfwTerminate();
    		return -1;
    	}
    	// 通知GLFW将我们窗口的上下文设置为当前线程的主上下文了
    	glfwMakeContextCurrent(window);
       
    	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
       
    	// 3、初始化glad,GLAD是用来管理OpenGL的函数指针的,所以在调用任何OpenGL的函数之前我们需要初始化GLAD
    	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
    		std::cout << "Failed to initialize GLAD" << std::endl;
    		return -1;
    	}  // 给GLAD传入了用来加载系统相关的OpenGL函数指针地址的函数
       
    	// 创建编译shader pragram
    	Shader ourShader("E:\\VS_project\\Study\\OpenGL_demo\\shader.vs", "E:\\VS_project\\Study\\OpenGL_demo\\shader.fs");
       
    	//float vertices[] = {   // 3个顶点属性  // 带一些颜色
    	//	// ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -
    	//	0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // 右上
    	//	0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // 右下
    	//	-0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // 左下
    	//	-0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // 左上
    	//};
       
    	//float vertices[] = {   // 3个顶点属性    //纯白,就是原图案
    	//	// ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -
    	//	0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 1.0f,   1.0f, 1.0f,   // 右上
    	//	0.5f, -0.5f, 0.0f,   1.0f, 1.0f, 1.0f,   1.0f, 0.0f,   // 右下
    	//	-0.5f, -0.5f, 0.0f,   1.0f, 1.0f, 1.0f,   0.0f, 0.0f,   // 左下
    	//	-0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 1.0f,   0.0f, 1.0f    // 左上
    	//  0.5相当于就占了窗口的一半
    	//};
       
    	float vertices[] = {   // 3个顶点属性   // 改了位置坐标,让它把图形填满
    		// ---- 位置 ----       ---- 颜色(纯白)--  -- 纹理坐标 -
    		1.0f,  1.0f, 0.0f,   1.0f, 1.0f, 1.0f,   1.0f, 1.0f,   // 右上
    		1.0f, -1.0f, 0.0f,   1.0f, 1.0f, 1.0f,   1.0f, 0.0f,   // 右下
    		-1.0f, -1.0f, 0.0f,   1.0f, 1.0f, 1.0f,   0.0f, 0.0f,   // 左下
    		-1.0f,  1.0f, 0.0f,   1.0f, 1.0f, 1.0f,   0.0f, 1.0f    // 左上
    	};
       
       
    	unsigned int indices[] = {
    		0, 1, 3,   // first triangle
    		1, 2, 3    // second triangle
    	};
       
       
    	unsigned int VBO, VAO, EBO;
    	glGenVertexArrays(1, &VAO);
    	glGenBuffers(1, &VBO);
    	glGenBuffers(1, &EBO);
       
    	glBindVertexArray(VAO);
       
    	glBindBuffer(GL_ARRAY_BUFFER, VBO);
    	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
       
    	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
       
    	// position attribute
    	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    	glEnableVertexAttribArray(0);
    	// texture coord attribute
    	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
    	glEnableVertexAttribArray(1);
    	//// texture coord attribute
    	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
    	glEnableVertexAttribArray(2);
       
       
    	// 1、load and create a texture
    	unsigned int texture;
    	glGenTextures(1, &texture);
    	glBindTexture(GL_TEXTURE_2D, texture);
    	// 2、为当前绑定的纹理对象设置环绕、过滤方式
    	// set the texture wrapping parameters
    	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    	// set texture filtering parameters
    	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
       
    	//// 3、加载图片并生成纹理
    	//int width, height, nrChannels;
    	//cv::Mat img = cv::imread(R"(C:\Users\Administrator\Downloads\container.jpg)");
    	//cv::cvtColor(img, img, cv::COLOR_BGR2RGB);
    	//unsigned char* data = img.data;
    	//if (data) {
    	//	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img.cols, img.rows, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
    	//	glGenerateMipmap(GL_TEXTURE_2D);
    	//}
    	//else {
    	//	std::cout << "Failed to load texture!" << std::endl;
    	//}
       
    	// 哪怕就一张纹理,也记得设置(下一小节,纹理单位有详说)
    	ourShader.use();  // 在设置uniform变量之一定激活着色器程序
    	glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0);
       
    	// while 中绘制就是 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
    	// 但是在这之前要记得激活绑定(下一小节有讲)
    	glActiveTexture(GL_TEXTURE0); // 在绑定纹理之前先激活纹理单元
    	glBindTexture(GL_TEXTURE_2D, texture);
       
       
    	// render loop
    	while (!glfwWindowShouldClose(window)) {
    		// input
    		processInput(window);
       
    		// render
    		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    		glClear(GL_COLOR_BUFFER_BIT);
       
    #ifdef ffmpeg
    		RequestFrame(decoderParam, frame, packet);
    		// 原来的格式是AV_PIX_FMT_YUVJ420P,被丢弃,会有一个警告:deprecated pixel format used, make sure you did set range correctly  (主要是针对rtsp,本地视频好像不用)
    		frame->format = ConvertDeprecatedFormat(static_cast<AVPixelFormat>(frame->format));
    		GetRGBPixels(frame, buffer);  // 解码调用
    		//uint8_t* data[] = { reinterpret_cast<uint8_t*>(&pixels[0]) };
    		// uint8_t *data = reinterpret_cast<uint8_t*>(&buffer[0]);  // 上面那行也是一个意思,一般写作unsigned char* data,OpenGL都是这样的
    		img.data = reinterpret_cast<uint8_t*>(&buffer[0]);
    #else
    		cap >> img;
       		
    #endif // ffmpeg
    		cv::cvtColor(img, img, cv::COLOR_BGR2RGB);
    		cv::flip(img, img, 0);  // opengl学习时也说了,它默认是上下颠倒的,所以这里要翻转一下
    		// 必须要转成RGB才行,前面给GL_BGR,虽然有,但是显示是有问题的
    		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img.cols, img.rows, 0, GL_RGB, GL_UNSIGNED_BYTE, img.data);
    		glGenerateMipmap(GL_TEXTURE_2D);
       
    		// render the triangle
    		ourShader.use();
    		glBindVertexArray(VAO);
    		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
       
    		// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
    		glfwSwapBuffers(window);
    		glfwPollEvents();
    	}
       
    	// optional: de-allocate all resources once they've outlived their purpose:
    	glDeleteVertexArrays(1, &VAO);
    	glDeleteBuffers(1, &VBO);
       
    #ifdef ffmpeg
    	// av_frame_unref(frame);  // 这似乎不是释放
    	av_frame_free(&frame);  // 来释放内存
        av_packet_free(&packet);
    	avcodec_free_context(&decoderParam.vcodecCtx);  // alloc的都要去free
    #else
    	cap.release();
    #endif // !ffmpeg
       
    	// glfw: terminate, clearing all previously allocated GLFW resources.
    	glfwTerminate();
    	return 0;
    }
       
    void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
    	// 4、glViewport函数前两个参数控制窗口左下角的位置。第三个和第四个参数控制渲染窗口的宽度和高度(像素)(有更深含义的技术,看这些代码的链接解释)
    	glViewport(0, 0, width, height);
    }
    void processInput(GLFWwindow* window) {
    	// 按esc关闭窗口
    	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
    		glfwSetWindowShouldClose(window, true);
    }
    

1.5. linux用ffmpeg加载有警告,chatgpt回答的解决

在linux上,ffmpeg代码视频流读取时,总会出现以下的提示:

[rtsp @ 0x260d500] max delay reached. need to consume packet [rtsp @ 0x260d500] RTP: missed 24 packets

问了chatgpt,解决办法:

这是原来回答:

​ 该提示表示FFmpeg在解码rtsp视频流时存在某些问题,其中最大延迟已达到,需要消耗数据包以进行下一步操作。同时,由于丢失了24个数据包,因此还显示了RTP:missed 24 packets的警告。

以下是一些可能的解决方案:

  1. 增加缓冲区大小

在FFmpeg命令行中,您可以增加rtsp协议的缓冲区大小:

ffmpeg -rtsp_flags initial_pause -max_delay 5000000 -i rtsp://192.168.1.1:554/stream_1 -vcodec copy -f rawvideo -y /dev/null

其中,-max_delay的值可以适当增加,以增加缓冲区大小。如果缓冲区过小,会导致最大延迟达到,从而导致数据包丢失。

  1. 调整FFmpeg缓冲区大小

您可以尝试通过调整FFmpeg缓冲区大小来解决此问题。您可以使用以下命令行:

ffmpeg -buffersize 1024k -i rtsp://192.168.1.1:554/stream_1 -vcodec copy -f rawvideo -y /dev/null

这将使FFmpeg在处理RTSP流时使用1MB的缓冲区,以便防止数据包丢失。

  1. 降低视频分辨率和帧率

如果您的RTSP视频流分辨率和帧率过高,在FFmpeg解码时可能会遇到问题。您可以尝试降低分辨率和/或帧率来解决此问题。

ffmpeg -i rtsp://192.168.1.1:554/stream_1 -filter:v scale=1280:-1 -r 30 -vcodec copy -f rawvideo -y /dev/null

该命令将视频分辨率降低为1280x720(如果原始分辨率更高)并将帧率降低为30帧/秒。

  1. 更改rtsp_flags

您可以尝试更改rtsp_flags以绕过此问题。

ffmpeg -rtsp_flags listen -i rtsp://192.168.1.1:554/stream_1 -vcodec copy -f rawvideo -y /dev/null

使用-rtsp_flags listen会在启动时暂停FFmpeg的流程,直到播放器可以接收RTSP数据包。

  1. 使用播放器的缓存

您可以尝试使用播放器的缓存,例如VLC或MPlayer,以防止出现数据包丢失的问题。这将使播放器自动解决RTSP数据包的缓冲问题,从而避免数据包丢失。

这些解决方案中的任何一种都可以帮助您解决FFmpeg解码RTSP视频流时遇到的“max delay reached”和“RTP:missed xx packets”问题。


下面是FFmpeg增加缓冲区的示例代码。(这也是chatgpt回答的,==试过了基本没啥变化。==)

​ 在这个示例中,我们使用avformat_open_input函数打开一个RTSP视频流,并使用avformat_find_stream_info函数获取视频流信息。在获取视频流信息之后,我们对AVStream对象进行迭代,并设置它们的缓冲区大小为2MB。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libavformat/avformat.h>

int main(int argc, char *argv[]) {
    AVFormatContext *formatCtx = NULL;
    int videoStream = -1;
    AVStream *video = NULL;

    av_register_all();

    if (argc < 2) {
        printf("Usage: %s <input>\n", argv[0]);
        return EXIT_FAILURE;
    }

    if (avformat_open_input(&formatCtx, argv[1], NULL, NULL) != 0) {
        printf("Error: could not open input file '%s'\n", argv[1]);
        return EXIT_FAILURE;
    }

    if (avformat_find_stream_info(formatCtx, NULL) < 0) {
        printf("Error: could not find stream info\n");
        return EXIT_FAILURE;
    }

    for (int i = 0; i < formatCtx->nb_streams; i++) {
        if (formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStream = i;
            video = formatCtx->streams[i];
            break;
        }
    }

    if (videoStream == -1) {
        printf("Error: could not find video stream\n");
        return EXIT_FAILURE;
    }

    // Set buffer size
    formatCtx->max_delay = (int64_t)(2 * AV_TIME_BASE);  // 2M
    formatCtx->flags |= AVFMT_FLAG_CUSTOM_IO;

    // Print video stream information
    av_dump_format(formatCtx, videoStream, argv[1], 0);

    avformat_close_input(&formatCtx);

    return EXIT_SUCCESS;
}

AVStream对象上设置max_delay属性为2秒(2 * AV_TIME_BASE)将设置2MB的缓冲区大小。注意,为了使新的缓冲区设置生效,我们还需要设置flags属性为AVFMT_FLAG_CUSTOM_IO

在示例代码中,我们还使用了av_dump_format函数来打印视频流信息。您可以在运行示例代码时,使用以下命令来编译并运行:

gcc -o increase_bufsize increase_bufsize.c -lavformat
./increase_bufsize rtsp://example.com/stream

这样,FFmpeg将使用2MB的缓冲区来打开RTSP视频流。

二、只用FFmpeg+Win32实现播放器

这是教程的博客地址。写的非常好,一步步循序渐进,一共有三个,这下面的算是第一个教程跟着写的代码,也还没整完,因为没有GPU硬件加速,走不下去了。 从这里来的。


准备工作:

2.1. 第一阶段:只是把第一帧的画面,黑白显示出来

#include <stdio.h>
#include <Windows.h>
#include <string>
#include <vector>

extern "C" {
#include <libavcodec/avcodec.h>
#pragma comment(lib, "avcodec.lib")

#include <libavformat/avformat.h>
#pragma comment(lib, "avformat.lib")

#include <libavutil/imgutils.h>
#pragma comment(lib, "avutil.lib")
}

struct Color_RGB {
	uint8_t r;
	uint8_t g;
	uint8_t b;
};


// 获取第一帧画面
AVFrame* getFirstFrame(const char* filepath) {
	AVFormatContext *fmtCtx = nullptr;
	avformat_open_input(&fmtCtx, filepath, NULL, NULL);
	avformat_find_stream_info(fmtCtx, NULL);

	int VideoStreamIndex;
	AVCodecContext *vcodecCtx = nullptr;
	for (int i = 0; i < fmtCtx->nb_streams; i++) {
		AVStream *stream = fmtCtx->streams[i];
		if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            // 这是软解码
			const AVCodec *codec = avcodec_find_decoder(stream->codecpar->codec_id);
			VideoStreamIndex = i;
			vcodecCtx = avcodec_alloc_context3(codec);
			avcodec_parameters_to_context(vcodecCtx, fmtCtx->streams[i]->codecpar);
			avcodec_open2(vcodecCtx, codec, NULL);
		}
	}

	while (1) {
		AVPacket *packet = av_packet_alloc();
		int ret = av_read_frame(fmtCtx, packet);
		if (ret == 0 && packet->stream_index == VideoStreamIndex) {
			ret = avcodec_send_packet(vcodecCtx, packet);
			if (ret == 0) {
				AVFrame *frame = av_frame_alloc();
				ret = avcodec_receive_frame(vcodecCtx, frame);
				if (ret == 0) {
					av_packet_unref(packet);
					avcodec_free_context(&vcodecCtx);
					avformat_close_input(&fmtCtx);
					return frame;
				}
				else if (ret == AVERROR(EAGAIN)) {
					av_frame_unref(frame);
					continue;
				}
			}
		}
		av_packet_unref(packet);
	}
}


/*
 YUV420P格式会把Y、U、V三个值分开存储到三个数组,AVFrame::data[0] 就是Y通道数组,我们简单的把亮度值同时放进RGB就可以实现黑白画面了。接下来写一个函数对处理出来的RGB数组进行渲染,我们这里先使用最传统的GDI绘图方式,,但是这种方式太慢了
*/
//void StretchBits(HWND hwnd, const std::vector<Color_RGB> &bits, int width, int height) {
//	auto hdc = GetDC(hwnd);
//	for (int x = 0; x < width; x++) {
//		for (int y = 0; y < height; y++) {
//			auto &pixel = bits[x + y * width];
//			SetPixel(hdc, x, y, RGB(pixel.r, pixel.g, pixel.b));  // 主要是SetPixel这个函数效率太低
//		}
//	}
//	ReleaseDC(hwnd, hdc);
//}

void StretchBits(HWND hwnd, const std::vector<Color_RGB> &bits, int width, int height) {
	auto hdc = GetDC(hwnd);
	BITMAPINFO bitinfo = {};
	auto &bmiHeader = bitinfo.bmiHeader;
	bmiHeader.biSize = sizeof(bitinfo.bmiHeader);
	bmiHeader.biWidth = width;
	bmiHeader.biHeight = -height;  // 注意负号,否则会画面颠倒
	bmiHeader.biPlanes = 1;
	bmiHeader.biBitCount = 24;
	bmiHeader.biCompression = BI_RGB;

	// StretchDIBits 函数就快了很多
	StretchDIBits(hdc, 0, 0, width, height, 0, 0, width, height, &bits[0], &bitinfo, DIB_RGB_COLORS, SRCCOPY);
	ReleaseDC(hwnd, hdc);

}


// 主函数入口
int WINAPI WinMain(
	_In_ HINSTANCE hInstance,
	_In_opt_ HINSTANCE hPrevInstance,
	_In_ LPSTR lpCmdLine,
	_In_ int nShowCmd
) {
	SetProcessDPIAware();

	auto className = L"MyWindow";
	WNDCLASSW wndClass = {};
	wndClass.hInstance = NULL;
	wndClass.lpszClassName = className;
	wndClass.lpfnWndProc = [](HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -> LRESULT {
		return DefWindowProc(hwnd, msg, wParam, lParam);
	};


	// 下面这个宏函数的定义(可点进去),要看是否启用了Unicode编译,不然类型不行,这个网址一个介绍:https://blog.csdn.net/huashuolin001/article/details/95620424
	RegisterClass(&wndClass);
	auto window = CreateWindow(className, L"Hello World 标题", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, NULL, NULL, NULL, NULL);

	ShowWindow(window, SW_SHOW);


	// 
	std::string file_path = "C:\\Users\\Administrator\\Videos\\keypoint_result.mp4";
	AVFrame *firstframe = getFirstFrame(file_path.c_str());
	int width = firstframe->width;
	int height = firstframe->height;
	std::vector<Color_RGB> pixels(width * height);
	for (int i = 0; i < pixels.size(); i++) {
		uint8_t r = firstframe->data[0][i];
		uint8_t g = r;
		uint8_t b = r;
		pixels[i] = { r, g, b };

	}
	
	StretchBits(window, pixels, width, height);


	MSG msg;
	while (GetMessage(&msg, window, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return 0;
}

2.2. 第二阶段:改成不移动鼠标也能自动播放

#include <stdio.h>
#include <Windows.h>
#include <string>
#include <vector>

extern "C" {
#include <libavcodec/avcodec.h>
#pragma comment(lib, "avcodec.lib")

#include <libavformat/avformat.h>
#pragma comment(lib, "avformat.lib")

#include <libavutil/imgutils.h>
#pragma comment(lib, "avutil.lib")
}

struct Color_RGB {
	uint8_t r;
	uint8_t g;
	uint8_t b;
};


// 把获取第一帧那个函数拆分了一下
struct DecoderParam {
	AVFormatContext *fmtCtx;
	AVCodecContext *vcodecCtx;
	int width;
	int height;
	int VideoStreamIndex;
};

void InitDecoder(const char* filepath, DecoderParam &param) {
	AVFormatContext *fmtCtx = nullptr;
	avformat_open_input(&fmtCtx, filepath, NULL, NULL);
	avformat_find_stream_info(fmtCtx, NULL);

	AVCodecContext *vcodecCtx = nullptr;
	for (int i = 0; i < fmtCtx->nb_streams; i++) {
		const AVCodec *codec = avcodec_find_decoder(fmtCtx->streams[i]->codecpar->codec_id);
		if (codec->type == AVMEDIA_TYPE_VIDEO) {
			param.VideoStreamIndex = i;
			vcodecCtx = avcodec_alloc_context3(codec);
			avcodec_parameters_to_context(vcodecCtx, fmtCtx->streams[i]->codecpar);
			avcodec_open2(vcodecCtx, codec, NULL);
		}
	}
	param.fmtCtx = fmtCtx;
	param.vcodecCtx = vcodecCtx;
	param.width = vcodecCtx->width;
	param.height = vcodecCtx->height;
}

AVFrame* RequestFrame(DecoderParam &param) {
	auto &fmtCtx = param.fmtCtx;
	auto &vcodecCtx = param.vcodecCtx;
	auto &VideoStreamIndex = param.VideoStreamIndex;

	while (1) {
		AVPacket *packet = av_packet_alloc();
		int ret = av_read_frame(fmtCtx, packet);
		if (ret == 0 && packet->stream_index == param.VideoStreamIndex) {
			ret = avcodec_send_packet(vcodecCtx, packet);
			if (ret == 0) {
				AVFrame *frame = av_frame_alloc();
				ret = avcodec_receive_frame(vcodecCtx, frame);
				if (ret == 0) {
					av_packet_unref(packet);
					return frame;
				}
				else if (ret == AVERROR(EAGAIN)) {
					av_frame_unref(frame);
				}
			}
		}
		
		av_packet_unref(packet);
	}
	return nullptr;
}

void StretchBits(HWND hwnd, const std::vector<Color_RGB> &bits, int width, int height) {
	auto hdc = GetDC(hwnd);
	BITMAPINFO bitinfo = {};
	auto &bmiHeader = bitinfo.bmiHeader;
	bmiHeader.biSize = sizeof(bitinfo.bmiHeader);
	bmiHeader.biWidth = width;
	bmiHeader.biHeight = -height;  // 注意负号,否则会画面颠倒
	bmiHeader.biPlanes = 1;
	bmiHeader.biBitCount = 24;
	bmiHeader.biCompression = BI_RGB;

	// StretchDIBits 函数就快了很多
	StretchDIBits(hdc, 0, 0, width, height, 0, 0, width, height, &bits[0], &bitinfo, DIB_RGB_COLORS, SRCCOPY);
	ReleaseDC(hwnd, hdc);

}

// 主函数入口
int WINAPI WinMain(
	_In_ HINSTANCE hInstance,
	_In_opt_ HINSTANCE hPrevInstance,
	_In_ LPSTR lpCmdLine,
	_In_ int nShowCmd
) {

	SetProcessDPIAware();

	auto className = L"MyWindow";
	WNDCLASSW wndClass = {};
	wndClass.hInstance = NULL;
	wndClass.lpszClassName = className;
	// // 这一个为了自动播放较前面的做了修改
	wndClass.lpfnWndProc = [](HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -> LRESULT {
		switch (msg) {
		case WM_DESTROY:
			PostQuitMessage(0);
			return 0;
		default:
			return DefWindowProc(hwnd, msg, wParam, lParam);
		}
	};

	std::string file_path = "C:\\Users\\Administrator\\Videos\\keypoint_result.mp4";

	DecoderParam decoderParam;
	InitDecoder(file_path.c_str(), decoderParam);
	int width = decoderParam.width;
	int height = decoderParam.height;
	auto &fmtCtx = decoderParam.fmtCtx;   // 不知道它这都习惯定义变量时用 & 引用
	auto &vcodecCtx = decoderParam.vcodecCtx;


	// 下面这个宏函数的定义(可点进去),要看是否启用了Unicode编译,不然类型不行,这个网址一个介绍:https://blog.csdn.net/huashuolin001/article/details/95620424
	RegisterClass(&wndClass);
	auto window = CreateWindow(className, L"Hello World 标题", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, NULL, NULL, NULL, NULL);

	ShowWindow(window, SW_SHOW);


	MSG msg;
	//// GetMessage是收到消息才会执行,就要鼠标就要一直动,不然就会卡在那里,就要改,成下面的
	//while (GetMessage(&msg, window, 0, 0) > 0) {
	//	AVFrame *frame = RequestFrame(decoderParam);
	//	std::vector<Color_RGB> pixels(width * height);
	//	for (int i = 0; i < pixels.size(); i++) {
	//		uint8_t r = frame->data[0][i];
	//		uint8_t g = r;
	//		uint8_t b = r;
	//		pixels[i] = { r, g, b };
	//	}
	//	av_frame_free(&frame);
	//	StretchBits(window, pixels, width, height);
	//	TranslateMessage(&msg);
	//	DispatchMessage(&msg);
	//}

	while (1) {
		BOOL hasMsg = PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
		if (hasMsg) {
			if (msg.message == WM_QUIT) break;
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else {
			AVFrame *frame = RequestFrame(decoderParam);
			std::vector<Color_RGB> pixels(width * height);
			for (int i = 0; i < pixels.size(); i++) {
				uint8_t r = frame->data[0][i];
				uint8_t g = r;
				uint8_t b = r;
				pixels[i] = { r, g, b };
			}

			av_frame_free(&frame);
			StretchBits(window, pixels, width, height);
		}
	}
	return 0;
}

2.3. 第三阶段:不再是黑白,添加色彩

#include <stdio.h>
#include <Windows.h>
#include <string>
#include <vector>

extern "C" {
#include <libavcodec/avcodec.h>
#pragma comment(lib, "avcodec.lib")

#include <libavformat/avformat.h>
#pragma comment(lib, "avformat.lib")

#include <libavutil/imgutils.h>
#pragma comment(lib, "avutil.lib")

// 彩色画面要的
#include <libswscale/swscale.h>
#pragma comment(lib, "swscale.lib")
}


/*
	yuvj×××这个格式被丢弃了,然后转化为yuv格式,
	不然有一个警告 deprecated pixel format used, make sure you did set range correctly,
	这个问题在前面和win32写api时可用,但是不知道其它地方会不会报错,就改过了
*/
AVPixelFormat ConvertDeprecatedFormat(enum AVPixelFormat format)
{
	switch (format) {
	case AV_PIX_FMT_YUVJ420P:
		return AV_PIX_FMT_YUV420P;
		break;
	case AV_PIX_FMT_YUVJ422P:
		return AV_PIX_FMT_YUV422P;
		break;
	case AV_PIX_FMT_YUVJ444P:
		return AV_PIX_FMT_YUV444P;
		break;
	case AV_PIX_FMT_YUVJ440P:
		return AV_PIX_FMT_YUV440P;
		break;
	default:
		return format;
		break;
	}
}


struct Color_RGB {
	uint8_t r;
	uint8_t g;
	uint8_t b;
};


// 把获取第一帧那个函数拆分了一下
struct DecoderParam {
	AVFormatContext *fmtCtx;
	AVCodecContext *vcodecCtx;
	int width;
	int height;
	int VideoStreamIndex;
};

void InitDecoder(const char* filepath, DecoderParam &param) {
	AVFormatContext *fmtCtx = nullptr;
	avformat_open_input(&fmtCtx, filepath, NULL, NULL);
	avformat_find_stream_info(fmtCtx, NULL);

	AVCodecContext *vcodecCtx = nullptr;
	for (int i = 0; i < fmtCtx->nb_streams; i++) {
		const AVCodec *codec = avcodec_find_decoder(fmtCtx->streams[i]->codecpar->codec_id);
		if (codec->type == AVMEDIA_TYPE_VIDEO) {
			param.VideoStreamIndex = i;
			vcodecCtx = avcodec_alloc_context3(codec);
			avcodec_parameters_to_context(vcodecCtx, fmtCtx->streams[i]->codecpar);
			avcodec_open2(vcodecCtx, codec, NULL);
		}
	}
	param.fmtCtx = fmtCtx;
	param.vcodecCtx = vcodecCtx;
	param.width = vcodecCtx->width;
	param.height = vcodecCtx->height;
}

AVFrame* RequestFrame(DecoderParam &param) {
	auto &fmtCtx = param.fmtCtx;
	auto &vcodecCtx = param.vcodecCtx;
	auto &VideoStreamIndex = param.VideoStreamIndex;

	while (1) {
		AVPacket *packet = av_packet_alloc();
		int ret = av_read_frame(fmtCtx, packet);
		if (ret == 0 && packet->stream_index == param.VideoStreamIndex) {
			ret = avcodec_send_packet(vcodecCtx, packet);
			if (ret == 0) {
				AVFrame *frame = av_frame_alloc();
				ret = avcodec_receive_frame(vcodecCtx, frame);
				if (ret == 0) {
					av_packet_unref(packet);
					return frame;
				}
				else if (ret == AVERROR(EAGAIN)) {
					av_frame_unref(frame);
				}
			}
		}
		
		av_packet_unref(packet);
	}
	return nullptr;
}


void StretchBits(HWND hwnd, const std::vector<Color_RGB> &bits, int width, int height) {
	auto hdc = GetDC(hwnd);
	BITMAPINFO bitinfo = {};
	auto &bmiHeader = bitinfo.bmiHeader;
	bmiHeader.biSize = sizeof(bitinfo.bmiHeader);
	bmiHeader.biWidth = width;
	bmiHeader.biHeight = -height;  // 注意负号,否则会画面颠倒
	bmiHeader.biPlanes = 1;
	bmiHeader.biBitCount = 24;
	bmiHeader.biCompression = BI_RGB;

	// StretchDIBits 函数就快了很多
	StretchDIBits(hdc, 0, 0, width, height, 0, 0, width, height, &bits[0], &bitinfo, DIB_RGB_COLORS, SRCCOPY);
	ReleaseDC(hwnd, hdc);

}

// 写一个转换颜色编码的函数
std::vector<Color_RGB> GetRGBPixels(AVFrame *frame, std::vector<Color_RGB> &buffer) {
	static SwsContext *swsctx = nullptr;
	swsctx = sws_getCachedContext(swsctx,
		frame->width, frame->height, static_cast<AVPixelFormat>(frame->format),
		frame->width, frame->height, AVPixelFormat::AV_PIX_FMT_BGR24, NULL, NULL, NULL, NULL
	);  // 这里原来的类型转换是用的 (AVPixelFormat)frame->format

	// 每次循环调用这个函数,都会重新分配这个vector,debug下就很慢
	//std::vector<Color_RGB> buffer(frame->width * frame->height);
	
	//uint8_t* data[] = {(uint8_t*)&buffer[0]};
	uint8_t* data[] = {reinterpret_cast<uint8_t*>(&buffer[0])};  // c++类型的指针风格转换
	int linesize[] = { frame->width * 3 };
	// sws_scale 函数可以对画面进行缩放,同时还能改变颜色编码,
	sws_scale(swsctx, frame->data, frame->linesize, 0, frame->height, data, linesize);
	return buffer;
}


// 主函数入口
int WINAPI WinMain(
	_In_ HINSTANCE hInstance,
	_In_opt_ HINSTANCE hPrevInstance,
	_In_ LPSTR lpCmdLine,
	_In_ int nShowCmd
) {

	SetProcessDPIAware();

	auto className = L"MyWindow";
	WNDCLASSW wndClass = {};
	wndClass.hInstance = NULL;
	wndClass.lpszClassName = className;
	wndClass.lpfnWndProc = [](HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -> LRESULT {
		switch (msg) {
		case WM_DESTROY:
			PostQuitMessage(0);
			return 0;
		default:
			return DefWindowProc(hwnd, msg, wParam, lParam);
		}
	};
	
    // 视频地址要对,不然会报错
	// std::string file_path = "C:\\Users\\Administrator\\Videos\\keypoint_result.mp4";
    std::string file_path = "rtsp://192.168.108.11:554/user=admin&password=&channel=1&stream=1.sdp?";

	DecoderParam decoderParam;
	InitDecoder(file_path.c_str(), decoderParam);
	int width = decoderParam.width;
	int height = decoderParam.height;
	auto &fmtCtx = decoderParam.fmtCtx;   // 不知道它这都习惯定义变量时用 & 引用
	auto &vcodecCtx = decoderParam.vcodecCtx;


	// 下面这个宏函数的定义(可点进去),要看是否启用了Unicode编译,不然类型不行,这个网址一个介绍:https://blog.csdn.net/huashuolin001/article/details/95620424
	RegisterClass(&wndClass);
	auto window = CreateWindow(className, L"Hello World 标题", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, NULL, NULL, NULL, NULL);

	ShowWindow(window, SW_SHOW);

	MSG msg;

	// 进入循环,debug下,很慢,原来是每次调用 GetRGBPixels 函数,里面就要分配一个很大的vector
	// 所以在循环前创建好,直接传进去,避免每次循环都去重新分配
	std::vector<Color_RGB> buffer(width * height);

	while (1) {
		BOOL hasMsg = PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
		if (hasMsg) {
			if (msg.message == WM_QUIT) break;
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else {
			AVFrame *frame = RequestFrame(decoderParam);
            // 原来的格式是AV_PIX_FMT_YUVJ420P,被丢弃,会有一个警告:deprecated pixel format used, make sure you did set range correctly
			frame->format = ConvertDeprecatedFormat(static_cast<AVPixelFormat>(frame->format));
            
			/*
			// 这是原来的写法
			std::vector<Color_RGB> pixels(width * height);
			for (int i = 0; i < pixels.size(); i++) {
				uint8_t r = frame->data[0][i];
				uint8_t g = r;
				uint8_t b = r;
				pixels[i] = { r, g, b };
			}*/
			std::vector<Color_RGB> pixels = GetRGBPixels(frame, buffer);  // 解码调用
			av_frame_free(&frame);
			StretchBits(window, pixels, width, height);
		}
	}

	return 0;
}

2.4. 第四阶段:本地视频播放过快

避免播放过快的一种解题思路: (因为现在视频播放速度是由cpu的计算决定的,可能就会很快)

	auto currentTime = std::chrono::system_clock::now();  // 需要头文件 <chrono>
	MSG msg;
	while (1) {
		BOOL hasMsg = PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
		if (hasMsg) {
			if (msg.message == WM_QUIT) break;
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else {
			AVFrame *frame = RequestFrame(decoderParam);
			std::vector<Color_RGB> pixels = GetRGBPixels(frame, buffer);  // 解码调用
			av_frame_free(&frame);

			// 为了正确的播放速度,以上的速度都是取决cpu运算速度
			/*
			AVCodecContext::framerate 可以获取视频的帧率,代表每秒需要呈现多少帧,他是 AVRational 类型,类似于分数,num 是分子,den 是分母。这里我们把他倒过来,再乘以1000得出每帧需要等待的毫秒数。

			double framerate = (double)vcodecCtx->framerate.den / vcodecCtx->framerate.num;
			Sleep(framerate * 1000);
			// 但是纯上面这样做,整个画面就慢了,因为计算渲染还要时间
			*/

			double framerate = (double)vcodecCtx->framerate.den / vcodecCtx->framerate.num;
			std::this_thread::sleep_until(currentTime + std::chrono::milliseconds(
				(int)(framerate * 1000)));
			currentTime = std::chrono::system_clock::now();
			
			StretchBits(window, pixels, width, height);
		}
	}

2.5. 第五阶段:cuda硬解码

硬件解码,改了这三个函数:

void InitDecoder(const char* filepath, DecoderParam &param) {
	AVCodecContext *vcodecCtx = nullptr;
    AVFormatContext *fmtCtx = nullptr;

	// 之前是这种解码方式
	avformat_open_input(&fmtCtx, filepath, NULL, NULL);
	avformat_find_stream_info(fmtCtx, NULL);
	
	for (int i = 0; i < fmtCtx->nb_streams; i++) {
		const AVCodec *codec = avcodec_find_decoder(fmtCtx->streams[i]->codecpar->codec_id);
		if (codec->type == AVMEDIA_TYPE_VIDEO) {
			param.VideoStreamIndex = i;
			vcodecCtx = avcodec_alloc_context3(codec);
			avcodec_parameters_to_context(vcodecCtx, fmtCtx->streams[i]->codecpar);
			avcodec_open2(vcodecCtx, codec, NULL);
		}
	}
	
    
    // 启用硬件解码器 (加的是这一段)
	AVBufferRef *hw_device_ctx = nullptr;
    // linux下这个函数要这个头文件,#include <libavutil/hwcontext.h>,vs上不用
	int ret = av_hwdevice_ctx_create(&hw_device_ctx, AVHWDeviceType::AV_HWDEVICE_TYPE_DXVA2, NULL, NULL, NULL);  // linux下,最后一个参数NULL改成0好些
	vcodecCtx->hw_device_ctx = hw_device_ctx;  
	// 我在linux上,上面的类型用的cuda这个AVHWDeviceType::AV_HWDEVICE_TYPE_CUDA,ret得到的是0,成功了,但是上一行代码赋值时,始终错误,跟公司没有gpu的win上报的错好像类似;不知道是不是因为linux是ffmpeg3.4的版本,换4点几的版本可能就好了

	param.fmtCtx = fmtCtx;
	param.vcodecCtx = vcodecCtx;
	param.width = vcodecCtx->width;
	param.height = vcodecCtx->height;
}

std::vector<Color_RGB> GetRGBPixels(AVFrame *frame, std::vector<Color_RGB> &buffer) {
	
	AVFrame *swFrame = av_frame_alloc();
	av_hwframe_transfer_data(swFrame, frame, 0);
	frame = swFrame;  // 这是为了硬件解码加的几行
	
	static SwsContext *swsctx = nullptr;
	swsctx = sws_getCachedContext(swsctx,
		frame->width, frame->height, static_cast<AVPixelFormat>(frame->format),
		frame->width, frame->height, AVPixelFormat::AV_PIX_FMT_BGR24, NULL, NULL, NULL, NULL
	);  

	uint8_t* data[] = {reinterpret_cast<uint8_t*>(&buffer[0])};  // c++类型的指针风格转换
	int linesize[] = { frame->width * 3 };
	// sws_scale 函数可以对画面进行缩放,同时还能改变颜色编码,
	sws_scale(swsctx, frame->data, frame->linesize, 0, frame->height, data, linesize);
	av_frame_free(&swFrame);  // 这样也是
    return buffer;
}

下面这个代码是简单先跑得起来的,跟前面的代码也差不多(但不是再用的GetRGBPixels这个函数,而是重新实现的另外的StretchBits),用的是cuda(AVHWDeviceType::AV_HWDEVICE_TYPE_CUDA),这个代码用AV_HWDEVICE_TYPE_DXVA2或是AV_HWDEVICE_TYPE_D3D11VA都是会报错的,所以博客第一篇教程最后一点进行不下去。

#include <stdio.h>
#include <Windows.h>
#include <string>
#include <vector>
#include <chrono>  // 这和thread是为了播放速度正常要用到的
#include <thread>

#include <d3d9.h>  // D3D9渲染画面,Direct3D 9 渲染。(有些头文件在这里面暂时是没有用到的)
#pragma comment(lib, "d3d9.lib")

#include <wrl.h>
using Microsoft::WRL::ComPtr;

//using namespace std::chrono;

extern "C" {
#include <libavcodec/avcodec.h>
#pragma comment(lib, "avcodec.lib")

#include <libavformat/avformat.h>
#pragma comment(lib, "avformat.lib")

#include <libavutil/imgutils.h>
#pragma comment(lib, "avutil.lib")

	// 彩色画面要的,以及一些变换都要这个头文件
#include <libswscale/swscale.h>
#pragma comment(lib, "swscale.lib")
}


struct Color_RGB {
	uint8_t r;
	uint8_t g;
	uint8_t b;
};


// 把获取第一帧那个函数拆分了一下
struct DecoderParam {
	AVFormatContext *fmtCtx;
	AVCodecContext *vcodecCtx;
	int width;
	int height;
	int VideoStreamIndex;
};

void InitDecoder(const char* filepath, DecoderParam &param) {
	AVCodecContext *vcodecCtx = nullptr;
	AVFormatContext *fmtCtx = nullptr;


	avformat_open_input(&fmtCtx, filepath, NULL, NULL);
	avformat_find_stream_info(fmtCtx, NULL);

	for (int i = 0; i < fmtCtx->nb_streams; i++) {
		const AVCodec *codec = avcodec_find_decoder(fmtCtx->streams[i]->codecpar->codec_id);
		if (codec->type == AVMEDIA_TYPE_VIDEO) {
			param.VideoStreamIndex = i;
			vcodecCtx = avcodec_alloc_context3(codec);
			avcodec_parameters_to_context(vcodecCtx, fmtCtx->streams[i]->codecpar);
			avcodec_open2(vcodecCtx, codec, NULL);
		}
	}

	// 启用硬件解码器
	AVBufferRef *hw_device_ctx = nullptr;
	av_hwdevice_ctx_create(&hw_device_ctx, AVHWDeviceType::AV_HWDEVICE_TYPE_CUDA, NULL, NULL, NULL);
	vcodecCtx->hw_device_ctx = hw_device_ctx;  // 没有GPU,这会报错


	param.fmtCtx = fmtCtx;
	param.vcodecCtx = vcodecCtx;
	param.width = vcodecCtx->width;
	param.height = vcodecCtx->height;
}

AVFrame* RequestFrame(DecoderParam &param) {
	auto &fmtCtx = param.fmtCtx;
	auto &vcodecCtx = param.vcodecCtx;
	auto &VideoStreamIndex = param.VideoStreamIndex;

	while (1) {
		AVPacket *packet = av_packet_alloc();
		int ret = av_read_frame(fmtCtx, packet);
		if (ret == 0 && packet->stream_index == param.VideoStreamIndex) {
			ret = avcodec_send_packet(vcodecCtx, packet);
			if (ret == 0) {
                // 把内存的申请放循环外,这是有问题的,会内存泄露,去看opencv_c++中,做了对应的修改
				AVFrame *frame = av_frame_alloc();
				ret = avcodec_receive_frame(vcodecCtx, frame);
				if (ret == 0) {
					av_packet_unref(packet);
					return frame;
				}
				else if (ret == AVERROR(EAGAIN)) {
					av_frame_unref(frame);
				}
			}
		}

		av_packet_unref(packet);
	}
	return nullptr;
}

void StretchBits(IDirect3DDevice9 *device, const std::vector<uint8_t> &bits, int width, int height) {
	ComPtr<IDirect3DSurface9> surface;
	device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, surface.GetAddressOf());

	D3DLOCKED_RECT lockRect;
	surface->LockRect(&lockRect, NULL, D3DLOCK_DISCARD);

	memcpy(lockRect.pBits, &bits[0], bits.size());

	surface->UnlockRect();
	device->Present(NULL, NULL, NULL, NULL);
}
// 这基本丢弃了前面的方法,把StretchBits、GetRGBPixels这俩函数重新别的原理实现了
void GetRGBPixels(AVFrame* frame, std::vector<uint8_t> &buffer, AVPixelFormat pixelFormat, int byteCount) {
	AVFrame* swFrame = av_frame_alloc();
	av_hwframe_transfer_data(swFrame, frame, 0);
	frame = swFrame;

	static SwsContext* swsctx = nullptr;
	swsctx = sws_getCachedContext(
		swsctx,
		frame->width, frame->height, (AVPixelFormat)frame->format,
		frame->width, frame->height, pixelFormat, NULL, NULL, NULL, NULL);

	uint8_t* data[] = { &buffer[0] };
	int linesize[] = { frame->width * byteCount };
	sws_scale(swsctx, frame->data, frame->linesize, 0, frame->height, data, linesize);

	av_frame_free(&swFrame);
}

// 主函数入口
int WINAPI WinMain(
	_In_ HINSTANCE hInstance,
	_In_opt_ HINSTANCE hPrevInstance,
	_In_ LPSTR lpCmdLine,
	_In_ int nShowCmd
) {

	SetProcessDPIAware();

	auto className = L"MyWindow";
	WNDCLASSW wndClass = {};
	wndClass.hInstance = NULL;
	wndClass.lpszClassName = className;
	wndClass.lpfnWndProc = [](HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -> LRESULT {
		switch (msg) {
		case WM_DESTROY:
			PostQuitMessage(0);
			return 0;
		default:
			return DefWindowProc(hwnd, msg, wParam, lParam);
		}
	};  // 这一个较前面的做了修改


	std::string file_path = "C:\\Users\\Administrator\\Videos\\keypoint_result.mp4";
	//std::string file_path = "rtsp://192.168.108.11:554/user=admin&password=&channel=1&stream=1.sdp?";

	DecoderParam decoderParam;
	InitDecoder(file_path.c_str(), decoderParam);
	int width = decoderParam.width;
	int height = decoderParam.height;
	auto &fmtCtx = decoderParam.fmtCtx;   // 不知道它这都习惯定义变量时用 & 引用
	auto &vcodecCtx = decoderParam.vcodecCtx;


	// D3D9 初始化设备
	ComPtr<IDirect3D9> d3d9 = Direct3DCreate9(D3D_SDK_VERSION);
	ComPtr<IDirect3DDevice9> d3d9Device;

	D3DPRESENT_PARAMETERS d3dParams = {};
	d3dParams.Windowed = TRUE;
	d3dParams.SwapEffect = D3DSWAPEFFECT_DISCARD;
	d3dParams.BackBufferFormat = D3DFORMAT::D3DFMT_X8R8G8B8;
	d3dParams.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
	d3dParams.BackBufferWidth = width;
	d3dParams.BackBufferHeight = height;


	// 下面这个宏函数的定义(可点进去),要看是否启用了Unicode编译,不然类型不行,这个网址一个介绍:https://blog.csdn.net/huashuolin001/article/details/95620424
	RegisterClass(&wndClass);
	auto window = CreateWindow(className, L"Hello World 标题", WS_OVERLAPPEDWINDOW, 0, 0, width, height, NULL, NULL, hInstance, NULL);

	d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, window, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dParams, d3d9Device.GetAddressOf());

	ShowWindow(window, SW_SHOW);

	std::vector<uint8_t> buffer(width * height * 4);

	auto currentTime = std::chrono::system_clock::now();
	MSG msg;
	while (1) {
		BOOL hasMsg = PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
		if (hasMsg) {
			if (msg.message == WM_QUIT) break;
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else {
			AVFrame *frame = RequestFrame(decoderParam);
			GetRGBPixels(frame, buffer, AVPixelFormat::AV_PIX_FMT_BGRA, 4);
			av_frame_free(&frame);

			// 看要不要和这个延迟的代码
			//double framerate = (double)vcodecCtx->framerate.den / vcodecCtx->framerate.num;
			//std::this_thread::sleep_until(currentTime + std::chrono::milliseconds(
			//	(int)(framerate * 1000)));
			//currentTime = std::chrono::system_clock::now();

			StretchBits(d3d9Device.Get(), buffer, width, height);
		}
	}

	return 0;
}

可以改成无边框:

auto window = CreateWindow(className, L”Hello World 标题”, WS_POPUP, 100, 100, width, height, NULL, NULL, hInstance, NULL);

2.6. 硬解码的一个思路

这是群里一个问题的求助,放这里,以后可能会有一个参考:

Q:请问有人做过ffmpeg硬解码rtsp,能控制在200 ms以内不,我现在遇到的问题使用h264cuvid解码 延迟有500ms,用h264软解码200ms,都是用opencv显示的,不知道为啥硬解码显示的延迟比软解码还大。

A:一些参考

三、用FFmpeg进行推流

实现的代码,暂时是在:SRS推流.md中