ffmpeg gdi录制桌面视频时注意事项

本人之前写了一个博客:ffmpeg录制桌面(自己用gdi抓图)
当时设置的帧率是10,设置的比较低,原因在于,设置高了,1秒钟抓不到对应的图片数量,会导致最终生成文件播放起来,时间变短。比如设置帧率为20,但是由于电脑的分辨率比较高,一秒钟只能抓10帧。则录制一分钟时,会发现,最终播放起来只有30秒。这显然是有问题的。

当然,我们设置帧率为10也不安全,因为电脑由于各种原因,可能一秒钟也抓不了10帧,当然,我们也不能再将帧率往下设置了,太憋屈了。

此时修改frame->pkt_dts可以达到这种效果,比如写文件时,AVStream的时间基是1000,帧率设置为20.
第一帧的pkt_dts设置为0,则第二帧正常应该为50,第三帧为100,…,第20帧的pkt_dts为950,第21帧的pkt_dts为1000.
但是现在出问题了,电脑性能不够,一秒钟只能抓10张图片,则第一帧的pkt_dts设置为0,第二张的设置为100,这样才能保证最后生成的文件播放起来是ok的;但是这样有个不好的结果,就是最终显示的文件的帧率只有10,而不是设置的20。

所以本篇采取在设置帧率的前提下,尽力交付。比如设置帧率为10时,则如果实际能抓到的张数大于10,则进行相应的Sleep;如果实际能抓到的张数小于10,比如8.则dts设置正确即可,最终生成的文件的帧率是8.

代码结构如下:
ffmpeg gdi录制桌面视频时注意事项_第1张图片
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)
{
	if (hbm)
	{
		DeleteObject(hbm);
		hbm = NULL;
	}

	if (m_hdib){

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

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

		DeleteDC(hMemDC);
	}
}

//
// 初始化
//
int CCaptureScreen::Init(int iPosX, int iPosY, int iWidth, int iHeight)
{
	HWND hDeskTop = GetDesktopWindow();
	RECT rc;
	GetWindowRect(hDeskTop, &rc);
	hScreenDC = ::GetDC(NULL);
	int iSizeX = GetDeviceCaps(hScreenDC, HORZRES);
	if (hScreenDC == NULL) return 0;

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

	m_iXPos = iPosX;
	m_iYPos = iPosY;
	m_width = iWidth;
	m_height = iHeight;

	if (!m_hdib){
		m_hdib = (PRGBTRIPLE)malloc(m_width * m_height * 3);//24位图像大小
	}
	//位图头信息结构体
	pbi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	pbi.bmiHeader.biWidth = m_width;
	pbi.bmiHeader.biHeight = m_height;
	pbi.bmiHeader.biPlanes = 1;
	pbi.bmiHeader.biBitCount = 24;
	pbi.bmiHeader.biCompression = BI_RGB;

	hbm = CreateCompatibleBitmap(hScreenDC, m_width, m_height);
	SelectObject(hMemDC, hbm);

	wLineLen = ((m_width * 24 + 31) & 0xffffffe0) / 8;
	wColSize = sizeof(RGBQUAD)* ((24 <= 8) ? 1 << 24 : 0);
	dwSize = (DWORD)(UINT)wLineLen * (DWORD)(UINT)m_height;

	return 1;
}

//抓取屏幕数据
BYTE* CCaptureScreen::CaptureImage()
{

	VOID*  alpbi = CaptureScreenFrame(m_iXPos, m_iYPos, m_width, m_height);
	return (BYTE*)(alpbi);
}


void* CCaptureScreen::CaptureScreenFrame(int left, int top, int width, int height)
{
	if (hbm == NULL || hMemDC == NULL || hScreenDC == NULL) return NULL;
	
	//BitBlt(hMemDC, 0, 0, width, height, hScreenDC, left, top, SRCCOPY);
	BOOL bRet = StretchBlt(hMemDC, 0, 0, width, height, hScreenDC, left, top, width, height, SRCCOPY);
	/*-------------------------捕获鼠标-------------------------------*/
	{
		POINT xPoint;
		GetCursorPos(&xPoint);
		HCURSOR hcur = FetchCursorHandle();
		xPoint.x -= left;
		xPoint.y -= top;

		ICONINFO iconinfo;
		BOOL ret;
		ret = GetIconInfo(hcur, &iconinfo);
		if (ret){
			xPoint.x -= iconinfo.xHotspot;
			xPoint.y -= iconinfo.yHotspot;

			if (iconinfo.hbmMask) DeleteObject(iconinfo.hbmMask);
			if (iconinfo.hbmColor) DeleteObject(iconinfo.hbmColor);
		}
		/*画鼠标*/
		::DrawIcon(hMemDC, xPoint.x, xPoint.y, hcur);
	}
	//动态分配的内存
	PRGBTRIPLE hdib = m_hdib;
	if (!hdib)
		return hdib;

	GetDIBits(hMemDC, hbm, 0, m_height, hdib, (LPBITMAPINFO)&pbi, DIB_RGB_COLORS);
	return hdib;
}

//
// 获取窗体鼠标光标
//
HCURSOR CCaptureScreen::FetchCursorHandle()
{
	if (m_hSavedCursor == NULL)
	{
		m_hSavedCursor = GetCursor();
	}
	return m_hSavedCursor;
}

CaptureScreen.h的代码如下:

#ifndef _CCAPTURE_SCREEN_HH
#define _CCAPTURE_SCREEN_HH

#include
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 


//
// ---抓屏类----
//
class CCaptureScreen
{
public:
	CCaptureScreen(void);
	~CCaptureScreen(void);

public:
	/*-----------定义外部调用函数-----------*/
	int Init(int iPosX, int iPosY, int iWidth, int iHeight);//初始化
	BYTE* CaptureImage(); //抓取屏幕

private:
	/*-----------定义内部调用函数-----------*/
	void* CaptureScreenFrame(int, int, int, int);//抓屏
	HCURSOR FetchCursorHandle(); //获取鼠标光标

private:
	/*-----------定义私有变量-----------*/
	int m_iXPos;
	int m_iYPos;
	int m_width;
	int m_height;
	UINT   wLineLen;
	DWORD  dwSize;
	DWORD  wColSize;

	//设备句柄
	HDC hScreenDC;
	HDC hMemDC;
	//图像RGB内存缓存
	PRGBTRIPLE m_hdib;
	//位图头信息结构体
	BITMAPINFO pbi;

	HBITMAP hbm = NULL;
	//鼠标光标
	HCURSOR m_hSavedCursor;


};

#endif //--_CCAPTURE_SCREEN_HH

FfmpegVideoCaptureWithGdi.cpp的代码如下:

// RecordingScreen.cpp : 定义控制台应用程序的入口点。
//

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

extern "C"
{
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "avdevice.lib")
#pragma comment(lib, "avfilter.lib")
#pragma comment(lib, "postproc.lib")
#pragma comment(lib, "swresample.lib")
#pragma comment(lib, "swscale.lib")


}

int g_iWidth = 2880;
int g_iHeight = 1800;
int g_iFrameRate = 30;
//#include 
#include 


std::string time_tSimpleString(time_t t_time)
{
	tm temptm = *localtime(&t_time);
	char strOut[64] = { 0 };
	sprintf(strOut, "%04d-%02d-%02dT%02d-%02d-%02d", temptm.tm_year + 1900, temptm.tm_mon + 1,
		temptm.tm_mday, temptm.tm_hour, temptm.tm_min, temptm.tm_sec);
	return (std::string)strOut;
}

unsigned char clip_value(unsigned char x, unsigned char min_val, unsigned char  max_val) {
	if (x > max_val) {
		return max_val;
	}
	else if (x < min_val) {
		return min_val;
	}
	else {
		return x;
	}
}

//RGB to YUV420
bool RGB24_TO_YUV420(unsigned char *RgbBuf, int w, int h, unsigned char *yuvBuf)
{
	unsigned char*ptrY, *ptrU, *ptrV, *ptrRGB;
	memset(yuvBuf, 0, w*h * 3 / 2);
	ptrY = yuvBuf;
	ptrU = yuvBuf + w * h;
	ptrV = ptrU + (w*h * 1 / 4);
	unsigned char y, u, v, r, g, b;
	for (int j = h - 1; j >= 0; j--) {
		ptrRGB = RgbBuf + w * j * 3;
		for (int i = 0; i < w; i++) {

			b = *(ptrRGB++);
			g = *(ptrRGB++);
			r = *(ptrRGB++);


			y = (unsigned char)((66 * r + 129 * g + 25 * b + 128) >> 8) + 16;
			u = (unsigned char)((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128;
			v = (unsigned char)((112 * r - 94 * g - 18 * b + 128) >> 8) + 128;
			*(ptrY++) = clip_value(y, 0, 255);
			if (j % 2 == 0 && i % 2 == 0) {
				*(ptrU++) = clip_value(u, 0, 255);
			}
			else {
				if (i % 2 == 0) {
					*(ptrV++) = clip_value(v, 0, 255);
				}
			}
		}
	}
	return true;
}


DWORD WINAPI ScreenCapThreadProc(LPVOID lpParam)
{
	CCaptureScreen* ccs = new CCaptureScreen();

	//int width = GetSystemMetrics(SM_CXSCREEN);
	//int height = GetSystemMetrics(SM_CYSCREEN);
	int width = g_iWidth;
	int height = g_iHeight;

	ccs->Init(0, 0, width, height);

	AVFormatContext* avFormCtx_Out;
	AVCodecContext*  avCodecCtx_Out;
	AVCodec*  avCodec;
	AVStream* avStream;
	AVFrame* frame;
	AVPacket* packet;

	int ret = 0;

	std::string strFileName = "D:\\learn\\ffmpeg\\FfmpegTest\\x64\\Release\\";
	strFileName += time_tSimpleString(time(NULL));
	strFileName += ".mp4";

	const char* filename = strFileName.c_str();

	ret = avformat_alloc_output_context2(&avFormCtx_Out, NULL, NULL, filename);
	if (ret < 0)
	{
		printf("Init avformat object is faild! \n");
		return 0;
	}

	avCodec = (AVCodec *)avcodec_find_encoder(avFormCtx_Out->oformat->video_codec);
	if (!avCodec)
	{
		printf("Init avCodec object is faild! \n");
		return 0;
	}

	avCodecCtx_Out = avcodec_alloc_context3(avCodec);
	if (!avCodecCtx_Out)
	{
		printf("Init avCodecCtx_Out object is faild! \n");
		return 0;
	}
	avStream = avformat_new_stream(avFormCtx_Out, avCodec);
	if (!avStream)
	{
		printf("Init avStream object is faild! \n");
		return 0;
	}

	avCodecCtx_Out->flags |= AV_CODEC_FLAG_QSCALE;
	avCodecCtx_Out->bit_rate = 4000000;
	avCodecCtx_Out->rc_min_rate = 4000000;
	avCodecCtx_Out->rc_max_rate = 4000000;
	avCodecCtx_Out->bit_rate_tolerance = 4000000;
	avCodecCtx_Out->time_base.den = g_iFrameRate;
	avCodecCtx_Out->time_base.num = 1;

	avCodecCtx_Out->width = width;
	avCodecCtx_Out->height = height;
	//pH264Encoder->pCodecCtx->frame_number = 1;
	avCodecCtx_Out->gop_size = 12;
	avCodecCtx_Out->max_b_frames = 0;
	avCodecCtx_Out->thread_count = 4;
	avCodecCtx_Out->pix_fmt = AV_PIX_FMT_YUV420P;
	avCodecCtx_Out->codec_id = AV_CODEC_ID_H264;
	avCodecCtx_Out->codec_type = AVMEDIA_TYPE_VIDEO;

	av_opt_set(avCodecCtx_Out->priv_data, "b-pyramid", "none", 0);
	av_opt_set(avCodecCtx_Out->priv_data, "preset", "superfast", 0);
	av_opt_set(avCodecCtx_Out->priv_data, "tune", "zerolatency", 0);

	if (avFormCtx_Out->oformat->flags & AVFMT_GLOBALHEADER)
		avCodecCtx_Out->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

	ret = avcodec_open2(avCodecCtx_Out, avCodec, NULL);
	if (ret < 0)
	{
		printf("Open avcodec is faild! \n");
		return 0;
	}

	avcodec_parameters_from_context(avStream->codecpar, avCodecCtx_Out);
	if (!(avFormCtx_Out->oformat->flags & AVFMT_NOFILE))
	{
		ret = avio_open(&avFormCtx_Out->pb, filename, AVIO_FLAG_WRITE);
		if (ret < 0)
		{
			printf("Open file is faild! \n");
			return 0;
		}
	}
	ret = avformat_write_header(avFormCtx_Out, NULL);
	if (ret < 0)
	{
		printf("write header is faild! \n");
		return 0;
	}

	frame = av_frame_alloc();
	if (!frame)
	{
		printf("Init frame is faild! \n");
		return 0;
	}
	frame->format = AV_PIX_FMT_YUV420P;
	frame->width = width;
	frame->height = height;

	LONG64 frameSize = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, width, height, 1);
	BYTE* outbuffer = new BYTE[frameSize];

	ret = av_image_fill_arrays(frame->data,
		frame->linesize,
		outbuffer,
		AV_PIX_FMT_YUV420P,
		width,
		height, 1);

	if (ret < 0)
	{
		printf("av_image_fill_arrays is faild! \n");
		return 0;
	}
	packet = av_packet_alloc();
	//av_init_packet(packet);
	if (!packet)
	{
		printf("packet is faild! \n");
		return 0;
	}

	int frameNumber = 0;
	int got_packet = 0;

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

		RGB24_TO_YUV420(frameimage, width, height, outbuffer);

		DWORD dwCurrTime = ::GetTickCount();
		int dts = dwCurrTime - dwBeginTime;

		AVRational millBase;
		millBase.den = 1000;
		millBase.num = 1;

		frame->pkt_dts = frame->pts = av_rescale_q_rnd(dts, millBase, 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);
		}

		/*if (frameNumber >= 600)
		{
			break;
		}*/

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

		int dwPassedMillSeconds = dwCurrentTime - dwBeginTime;
		int dwDiff = frameNumber * 1000 / g_iFrameRate - dwPassedMillSeconds;
		if (dwDiff > 0)
		{
			Sleep(dwDiff);
		}
	}
	av_write_trailer(avFormCtx_Out);
	avformat_free_context(avFormCtx_Out);
	avcodec_close(avCodecCtx_Out);
	avcodec_free_context(&avCodecCtx_Out);
	av_free(avCodec);
	av_packet_free(&packet);
	av_frame_free(&frame);
	return 0;
}


int _tmain(int argc, _TCHAR* argv[])
{
	//av_register_all();
	//avformat_network_init();
	avdevice_register_all();


	HANDLE hThread = CreateThread(NULL, 0, ScreenCapThreadProc, 0, 0, NULL);
	WaitForSingleObject(hThread, INFINITE);
	return 0;
}






你可能感兴趣的:(音视频)