ffmpeg录制桌面(自己用gdi抓图)

本文探讨了一种使用GDI抓图技术改进ffmpeg录制桌面视频的问题,通过调整抓图频率和编码器帧率,确保视频时长与实际录制时间一致,避免因线程性能差异导致的时间偏差。

之前写过两篇ffmpeg录制桌面,读者可以翻看我的博客,但是都存在着问题,尤其当一个进程里面,自己创建几个线程干其他事情时,出错概率大大增加,

ret = avcodec_send_frame(pCodecEncodeCtx_Video, pFrameYUV);
if (ret == AVERROR(EAGAIN))
{
   
   
	continue;
}

ret = avcodec_receive_packet(pCodecEncodeCtx_Video, &packet);
if (ret == AVERROR(EAGAIN))
{
   
   
	continue;
}

这是个编码的过程,其中avcodec_receive_packet的返回值经常是AVERROR(EAGAIN),而我源头是通过av_read_frame(pFormatCtx_Video, &packet)读取的视频帧,最终的结果导致视频里面的时间比实际录制的时间要短。

这让我很头疼,无意中发现了一个人的博客,自己用gdi抓图,博客地址如下:
https://blog.youkuaiyun.com/Donghui_Luo/article/details/88383313

但是这篇博客存在着一点瑕疵,该博客设置帧率为15,我自己稍微修改了点代码,修改录制时间为1分钟,则最终发现,生成的mp4文件中的视频时长不止1分钟,大概在1分钟17秒的样子,经过分析,其抓图时,一秒钟不止抓15张,所以导致最终的时间大于1分钟。如果其线程得不到充分运行,比如一分钟才能抓5张,则对应的最终mp4的文件视频时长是20秒。

所以需要自己控制抓图频率,线程性能太好,一秒钟抓的图片数量大于帧率时,需要适当的进行Sleep,如果线程性能太差,比如无论如何,也无法做到1秒钟抓15张,则可以降低编码器的帧率。

为方便演示,本人将编码器的帧率设置为10,核心逻辑修改如下:

DWORD dwBeginTime = ::GetTickCount();
	for (;;)
	{
   
   
		BYTE* frameimage = ccs->CaptureImage();

		RGB24_TO_YUV420(frameimage, width, height, outbuffer);
		//Sframe->pkt_dts = frame->pts = frameNumber * avCodecCtx_Out->time_base.num * avStream->time_base.den / (avCodecCtx_Out->time_base.den *  avStream->time_base.num);
		frame->pkt_dts = frame->pts = av_rescale_q_rnd(frameNumber, avCodecCtx_Out->time_base, avStream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
		frame->pkt_duration = 0;
		frame->pkt_pos = -1;

		ret = avcodec_send_frame(avCodecCtx_Out, frame);
		if (ret < 0)
			continue;

		ret = avcodec_receive_packet(avCodecCtx_Out, packet);
		if (ret < 0)
			continue;

		static DWORD dwInitTime = ::GetTickCount();
		if (packet->size > 0)
		{
   
   
			//av_packet_rescale_ts(packet, avCodecCtx_Out->time_base, avStream->time_base);
			av_write_frame(avFormCtx_Out, packet);
			frameNumber++;
			printf("录入第%d帧....\n", frameNumber);
		}

		DWORD dwCurrentTime = ::GetTickCount();
		if (dwCurrentTime - dwInitTime > 60000)
		{
   
   
			break;
		}

		int dwPassedMillSeconds = dwCurrentTime - dwBeginTime;
		int dwDiff = frameNumber * 100 - dwPassedMillSeconds;
		if (dwDiff > 0)
		{
   
   
			Sleep(dwDiff);
		}
	}

本人在抓图开始前记录下时间dwBeginTime,然后每次抓图后,计算出从开始到现在的抓图时间dwPassedMillSeconds,因为帧率是10,所以理论上,每100毫秒抓取一张,dwDiff用于表示实际的时差。

本人乐于直接将代码贴在博客内,便于本人日后查看。
总共是3个文件,如下图所示:
在这里插入图片描述
现在本人依次粘贴它们的代码
CaptureScreen.cpp代码如下:

//#include "stdafx.h"
#include "CaptureScreen.h"

CCaptureScreen::CCaptureScreen(void)
{
   
   
	m_hdib = NULL;
	m_hSavedCursor = NULL;
	hScreenDC = NULL;
	hMemDC = NULL;
	hbm = NULL;
	m_width = 1920;
	m_height = 1080;
	FetchCursorHandle();
}
//
// 释放资源
//
CCaptureScreen::~CCaptureScreen(void)
{
   
   
	DeleteObject(hbm);
	if (m_hdib){
   
   

		free(m_hdib);
		m_hdib = NULL;
	}
	if (hScreenDC){
   
   

		::ReleaseDC(NULL, hScreenDC);
	}
	if (hMemDC) {
   
   

		DeleteDC(hMemDC);
	}
	if (hbm)
	{
   
   
		DeleteObject(hbm);
	}
}

//
// 初始化
//
int CCaptureScreen::Init(int& src_VideoWidth, int& src_VideoHeight)
{
   
   
	hScreenDC = ::GetDC(GetDesktopWindow());
	if (hScreenDC == NULL) return 0;

	int m_nMaxxScreen = GetDeviceCaps(hScreenDC, HORZRES);
	int m_nMaxyScreen = GetDeviceCaps(hScreenDC, VERTRES);

	hMemDC = ::CreateCompatibleDC(hScreenDC);
	if (hMemDC == NULL) return 0;

	m_width = m_nMaxxScreen;
	m_height = m_nMaxyScreen;

	if (!m_hdib){
   
   
		m_hdib = (PRGBTRIPLE)malloc(m_width * m_height * 3
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值