赞
踩
本人之前写了一个博客: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.
代码结构如下:
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<time.h> #include <d3d9.h> #include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <windows.h> #include <tchar.h> #include <winbase.h> #include <winreg.h> #include <Strsafe.h> // // ---抓屏类---- // 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 <string> extern "C" { #include <libavutil/opt.h> #include <libavutil/mathematics.h> #include <libavutil\time.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> #include <libswresample/swresample.h> #include <libavutil/imgutils.h> #include <libavcodec/avcodec.h> #include <libavdevice\avdevice.h> #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 <ipp.h> #include <chrono> 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; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。