欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

基于Qt和ffmpeg的抓屏rtsp服务(一)

程序员文章站 2022-07-02 09:46:48
...

实现一个基于ffmpeg的rtsp抓屏服务

一、服务实现图

1、整体图
YUV420p
H264
ES
采集
编码
封装
传输
图1
2、 采集图
yes
no
yes
no
yes
no
yes
no
yes
yes
no
开始
avdevice_register_all
av_dict_set_int
av_find_input_format?
avformat_alloc_context
avformat_find_stream_info?
avcodec_close
avcodec_find_decoder?
avformat_parameters_to_context
avcodec_open2?
av_read_frame?
aavcodec_send_packet
avcodec_receive_frame
avformat_close_input
结束
图2
3、 编码图
yes
no
yes
开始
av_register_all /avcodec_register_all
avcodec_find_encoder_by_name?
avcodec_allo_context3
sws_freeContext
av_opt_set
avcodec_open2?
sws_scale
avcodec_send_frame
avcodec_receive_packet
avcodec_close
avcodec_free_context
结束
图3
4、 传输
采集 编码 ES流 rtsp服务 用户 ③ 采集桌面数据 ④ 编码,封装(这里不做封装,简单处理) ⑤ 装备传输 ① 用户向rtsp服务请求rtsp流 ② 通知采集,开始采集,编码与传输 ⑥ 开始视频流传输 采集 编码 ES流 rtsp服务 用户
图4

二、各个模块简要说明

1、模块讲解

上面图1

	在上面的1、中,主要是整个程序的主要流程走向,使用ffmpeg **gdigrab**进行采集,ffmpeg中的libx264进行编码,
	由于直接使用ES流进行传输。所以,封装这层先不做过多的处理。传输,是网络上找的一个rtsp服务,最终会使用**vlc**进行验证。

上面图2、

	图2中,有几个函数avdevice_register_all,是需要使用到采集的gdi设备,所以需要该函数进行注册。使用av_dict_set_int来设
置编码参数,

图2中av_dict_set_int,设置抓屏参数
部分采集编码:

	AVDictionary *options = nullptr;
	av_dict_set_int(&options, "framerate", 25, 1);
	av_dict_set_int(&options, "draw_mouse", 1, 1);
	av_dict_set_int(&options, "offset_x", 0, 1);
	av_dict_set_int(&options, "offset_y", 0, 1);
	av_dict_set(&options, "video_size", video_size, 1);
	AVInputFormat* pInputFmt = av_find_input_format("gdigrab");
	AVFormatContext* pFmtCtx = avformat_alloc_context();
	avformat_open_input(&pFmtCtx , "desktop", pInputFmt, &options);
	if (avformat_find_stream_info(pFmtCtx , nullptr) < 0) 
	{
		printf("Couldn't find stream info.\n");
		avformat_close_input(&pFmtCtx);
		pFmtCtx = nullptr;
		return false;
	}
	int video_index = -1;
	for (unsigned int i = 0; i < pFmtCtx ->nb_streams; i++) 
	{
		if (pFmtCtx ->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) 
		{
			video_index = i;
		}
	}
	if (video_index < 0) 
	{
		printf("Couldn't find video stream.\n");
		avformat_close_input(&pFmtCtx );
		pFmtCtx = nullptr;
		return false;
	}
	AVCodec* codec = avcodec_find_decoder(pFmtCtx->streams[video_index]->codecpar->codec_id);
	if (!codec) 
	{
		avformat_close_input(&pFmtCtx );
		pFmtCtx = nullptr;
		return false;
	}

	m_pCodecCtx = avcodec_alloc_context3(codec);
	if (!m_pCodecCtx ) 
	{
		return false;
	}
	
	avcodec_parameters_to_context(m_pCodecCtx , pFmtCtx->streams[video_index]->codecpar);
	if (avcodec_open2(m_pCodecCtx , codec, nullptr) != 0) 
	{
		avcodec_close(m_pCodecCtx );
		codec_context_ = nullptr;
		avformat_close_input(&pFmtCtx);
		pFmtCtx= nullptr;
		return false;
	}

部分编码

	AVCodec *codec = nullptr;
	//codec = avcodec_find_encoder(AV_CODEC_ID_H264);
	codec = avcodec_find_encoder_by_name("libx264");
	if (!codec) 
	{
		std::cout << "H.264 Encoder not found.\n"  << std::endl;
		return false;
	}

	codec_context_ = avcodec_alloc_context3(codec);
	if (!codec_context_) 
	{
		std::cout << "avcodec_alloc_context3() failed." << std::endl;
		return false;
	}
	
	codec_context_->width = av_config_.video.width;
	codec_context_->height = av_config_.video.height;
	codec_context_->time_base = { 1,  (int)av_config_.video.framerate };
	codec_context_->framerate = { (int)av_config_.video.framerate, 1 };
	codec_context_->gop_size = av_config_.video.gop;
	codec_context_->max_b_frames = 0;
	codec_context_->pix_fmt = AV_PIX_FMT_YUV420P;

	// rc control mode: abr
	codec_context_->bit_rate = av_config_.video.bitrate;
	// cbr mode config
	codec_context_->rc_min_rate = av_config_.video.bitrate;
	codec_context_->rc_max_rate = av_config_.video.bitrate;
	codec_context_->rc_buffer_size = (int)av_config_.video.bitrate;
	codec_context_->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

	if (codec->id == AV_CODEC_ID_H264) 
	{
		av_opt_set(codec_context_->priv_data, "preset", "ultrafast", 0); //ultrafast 
	}

	av_opt_set(codec_context_->priv_data, "tune", "zerolatency", 0);
	av_opt_set_int(codec_context_->priv_data, "forced-idr", 1, 0);
	av_opt_set_int(codec_context_->priv_data, "avcintra-class", -1, 0);
	
	if (avcodec_open2(codec_context_, codec, NULL) != 0) 
	{
		std::cout << "avcodec_open2() failed." << std::endl;
		return false;
	}
	

部分代码如上

① 采集流程

上面主要是获取设备信息,并打开解码器
后面通过av_read_frame读取流,avcodec_send_packet/avcodec_receive_frame来进行解码流
同时,将获取到的数据进行编码成h264,

② 编码流程

编码流程中需要注意,数据是通过采集中获取到的数据,AVframe,需要先将AVFrame转为yuvAVFrame,然后通过接口avcodec_send_frame/avcodec_receive_packet,avcodec_receive_packet中的数据,得到的是AVPacket的数据,需要去掉其中h264中的起始码,传给rtsp服务,rtsp服务就能将数据传输给用户端。

③ 封装

由于此处直接使用ES流,不做封装,直接将编码器中的数据进行传输。

④ 传输

传输参考下面的3.rtsp服务

2、引用参考部分
  1. 技术视频参考:https://ke.qq.com/course/3202131?flowToken=1040952
  2. 参考文献:最简单的基于FFmpeg的AVDevice例子(屏幕录制)
  3. rtsp服务参考: https://blog.csdn.net/weixin_42462202/article/details/98956346
  4. 参考代码:https://github.com/PHZ76/DesktopSharing
3、总结
本章节只讲述整个服务器相关的流程,实现代码请关注后续章节!