之前写过两篇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

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

被折叠的 条评论
为什么被折叠?



