当前位置:网站首页>使用gdi实现多路视频流合并
使用gdi实现多路视频流合并
2022-06-10 02:34:00 【Alfred-N】
文章目录
前言
以前用ffmpeg的滤镜实现过多路视频流合并,后来想到其实只要是图形处理库应该都能实现图像的合并,于是尝试了一下用使用gdi来实现视频流的合并,实际发现效果还可以,可以支持rgb格式的图像数据的合并,对于1080p的数据合并2路流的耗时也在可接受范围内。本文将介绍gdi如何实现视频流的合并,以及封装成对象使用。
一、如何实现?
1、创建HBitmap
合并视频流首先需要一个图像缓存用于保存合并的图片,以及能够使用gdi进行操作,所以必然是需要一个HBitmap对象的。
//获取桌面hdc,用于创建设备兼容hdc
auto srcHdc = GetDC(GetDesktopWindow());
//创建设备兼容的hdc
auto hdc = CreateCompatibleDC(srcHdc);
//设置bitmap参数
BITMAPINFO bmi;
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = _outputWidth;
bmi.bmiHeader.biHeight = -_outputHeight;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = 0;
bmi.bmiHeader.biXPelsPerMeter = 0;
bmi.bmiHeader.biYPelsPerMeter = 0;
bmi.bmiHeader.biClrUsed = 0;
bmi.bmiHeader.biClrImportant = 0;
//创建bitmap
auto hBitmap = CreateDIBSection(_hdc, &bmi, DIB_RGB_COLORS, NULL, NULL, 0);
//将bitmap设备hdc的位图缓存
auto oldBitmap = (HBITMAP)SelectObject(_hdc, _hBitmap);
2、写入HBitmap
对于得到的输入流图像数据我们可以直接调用StretchDIBits将图像数据写入指定的位置
//输入流的图像信息
BITMAPINFO bmi;
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = j->bufferFrame.Width;
bmi.bmiHeader.biHeight = -j->bufferFrame.Height;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32 ;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = 0;
bmi.bmiHeader.biXPelsPerMeter = 0;
bmi.bmiHeader.biYPelsPerMeter = 0;
bmi.bmiHeader.biClrUsed = 0;
bmi.bmiHeader.biClrImportant = 0;
//写入到bitmap
StretchDIBits(_hdc, dstX, dstY, dstWidth, dstHeight, srcX, srcY, srcWidth, srcHeight, j->bufferFrame.Data, &bmi, 0, SRCCOPY);
3、获取HBitmap数据
合并图像后需要拿到内部的byte数据,我们可以通过GetObject获取到位图的信息里面就包括了byte数据指针。
BITMAP bmp;
//获取位图对象信息
GetObject(hBitmap, sizeof(BITMAP), &bmp);
//图像数据
int Data = (unsigned char*)bmp.bmBits;
//图像数据长度
int DataLength = bmp.bmWidthBytes * bmp.bmHeight * bmp.bmPlanes;
//一行数据长度
int Stride = bmp.bmWidthBytes;
二、封装对象
1、接口设计
设计一个视频流合并工具,接口如下:
#pragma once
#include"PixelFormat.h"
#include<map>
#include<shared_mutex>
#include<Windows.h>
/************************************************************************ * @Project: GdiVideoMerger * @Decription: Gdi多路视频合并工具 * 支持多路视频视频的剪切与合并 * 只支持bgra32和bgr24格式数据 * 出构造析构外,所有方法线程安全。 * @Verision: v1.0.0.0 * @Author: Xin Nie * @Create: 2022/6/6 22:46:00 * @LastUpdate: 2022/6/6 22:46:00 ************************************************************************ * Copyright @ 2022. All rights reserved. ************************************************************************/
namespace AC {
class GdiVideoMerger
{
public:
/// <summary>
/// 区域值的模式
/// </summary>
enum RectMode {
/// <summary>
/// 按实际计算
/// </summary>
TrueValue,
/// <summary>
/// 按比例计算
/// </summary>
RatioValue
};
/// <summary>
/// 构造方法
/// </summary>
GdiVideoMerger();
/// <summary>
/// 析构方法
/// </summary>
~GdiVideoMerger();
/// <summary>
/// 设置输入流的区域
/// 未设置的流默认为全屏
/// 线程安全,可以与其他方法同时调用
/// </summary>
/// <param name="streamId">输入流id,由用户设置</param>
/// <param name="dstX">在输出流中的x坐标</param>
/// <param name="dstY">在输出流中的y坐标</param>
/// <param name="dstWidth">在输出流中的宽</param>
/// <param name="dstHeight">在输出流中的高</param>
/// <param name="dstZIndex">在输出流中的层次</param>
/// <param name="dstRectMode">区域(x,y,width,height)值的模式,按比例还是实际值</param>
void SetStreamRect(int streamId,double dstX, double dstY, double dstWidth, double dstHeight, int dstZIndex, RectMode dstRectMode = RatioValue);
/// <summary>
/// 设置输入流的区域
/// 未设置的流默认为全屏
/// 线程安全,可以与其他方法同时调用
/// </summary>
/// <param name="streamId">输入流id,由用户设置</param>
/// <param name="srcX">输入流裁剪x坐标</param>
/// <param name="srcY">输入流裁剪y坐标</param>
/// <param name="srcWidth">输入流裁剪的宽</param>
/// <param name="srcHeight">输入流裁剪的高</param>
/// <param name="dstX">在输出流中的x坐标</param>
/// <param name="dstY">在输出流中的y坐标</param>
/// <param name="dstWidth">在输出流中的宽</param>
/// <param name="dstHeight">在输出流中的高</param>
/// <param name="dstZIndex">在输出流中的层次</param>
/// <param name="dstRectMode">区域(x,y,width,height)值的模式,按比例还是实际值</param>
void SetStreamRect(int streamId, double srcX, double srcY, double srcWidth, double srcHeight, double dstX, double dstY, double dstWidth, double dstHeight, int dstZIndex, RectMode dstRectMode = RatioValue);
/// <summary>
/// 写入输入流
/// 线程安全,可以与其他方法同时调用
/// </summary>
/// <param name="streamId">输入流id,由用户设置</param>
/// <param name="frame">流数据帧,PixelFormat只支持PIXELFORMAT_RGB32和PIXELFORMAT_RGB24</param>
void Write(int streamId, VideoFrame*frame);
/// <summary>
/// 获取一帧合并流
/// 可以在单独线程按照一定帧率调用此方法
/// 也可以在某条输入流中调用此方法
/// 调用后会锁住当前数据,使用完成后调用UnlockFrame解锁。
/// 线程安全,可以与其他方法同时调用
/// </summary>
/// <returns>合并流帧</returns>
VideoFrame* LockFrame();
/// <summary>
/// 解锁合并流帧
/// 必须与LockFrame成对出现。
/// 线程安全,可以与其他方法同时调用
/// </summary>
void UnlockFrame();
/// <summary>
/// 设置输出大小
/// 线程安全,可以与其他方法同时调用
/// </summary>
/// <param name="width"></param>
/// <param name="height"></param>
void SetOutputSize(int width, int height);
/// <summary>
/// 设置输出格式
/// 线程安全,可以与其他方法同时调用
/// </summary>
/// <param name="format">像素格式,只支持PIXELFORMAT_RGB32和PIXELFORMAT_RGB24</param>
void SetOutputFormat(PixelFormat format);
};
}
其他的必要的对象:
#pragma once
#include<stdint.h>
namespace AC {
typedef enum
{
PIXELFORMAT_UNKNOWN = -1,
PIXELFORMAT_BGR24 = 0,
PIXELFORMAT_BGRA32,
}PixelFormat;
class VideoFrame {
public:
AC::PixelFormat Format;
uint8_t* Data;
int DataLength;
int Stride;
int Width;
int Height;
int64_t Timestamp;
};
}
2、完整代码
包含了完整代码和示例的vs项目。gdi合并多路视频流只能输入输出bgra32或bgr24,请根据需要下载。
https://download.csdn.net/download/u013113678/85596536
三、使用示例
1、输入流中合并
int main(int argc, char** argv) {
//创建窗口
Win32Window window(L"多路流合并预览窗口");
bool exitFlag = false;
//流合并对象
AC::GdiVideoMerger gm;
//设置每条输入流参数
gm.SetStreamRect(0, 0, 0, 0.25, 0.25, 2);
gm.SetStreamRect(1, 360, 0, 960, 540, 1, AC::GdiVideoMerger::TrueValue);
gm.SetStreamRect(2, 0, 0, 1, 1, 0.25, 0.5, 0.5, 0.5, 1);
gm.SetStreamRect(3, 360, 540, 360, 540, 960, 540, 960, 540, 1, AC::GdiVideoMerger::TrueValue);
//设置输出流大小
gm.SetOutputSize(1920, 1080);
//设置输出流格式
gm.SetOutputFormat(AC::PixelFormat::PIXELFORMAT_BGRA32);
//启动截屏线程
//输入流1
std::thread t1([&]() {
DesktopGrabber dg;
while (!exitFlag)
{
//截屏
dg.Grab();
//填充数据
AC::VideoFrame frame;
frame.Data = dg.Data();
frame.DataLength = dg.DataLength();
frame.Format = AC::PixelFormat::PIXELFORMAT_BGRA32;
frame.Stride = dg.Stride();
frame.Width = dg.Width();
frame.Height = dg.Height();
//写入流
gm.Write(0, &frame);
//读取合并流,放在此输入流线程中合并,相当于以这条流帧率为基准进行合并。也可以放在独立的线程中,自定义帧率。
auto t = clock();
//读取当前合并帧
auto oFrame = gm.LockFrame();
printf("merge one frame cost time:%dms\n", clock() - t);
//显示合并帧
PresentBgra(oFrame->Format == AC::PIXELFORMAT_BGRA32 ? 32 : 24, window.Hwnd(), oFrame->Data, oFrame->Width, oFrame->Height, 0, 0, 640, 360);
gm.UnlockFrame();
Sleep(40);
}
});
//输入流2
std::thread t2([&]() {
DesktopGrabber dg(24);
while (!exitFlag)
{
//截屏
dg.Grab();
//填充数据
AC::VideoFrame frame;
frame.Data = dg.Data();
frame.DataLength = dg.DataLength();
frame.Format = AC::PixelFormat::PIXELFORMAT_BGR24;
frame.Stride = dg.Stride();
frame.Width = dg.Width();
frame.Height = dg.Height();
//写入流
gm.Write(1, &frame);
//动态修改参数-向右拉伸
width += 0.01;
gm.SetStreamRect(0, 0, 0, width, 0.25, 1);
if (width > 1)
width = 0.1;
Sleep(30);
}
});
//消息循环等待窗口关闭
Win32Window::Exec();
exitFlag = true;
t1.join();
t2.join();
return 0;
}
2、单独线程合并
在单独线程中合并可以自定义帧率。下面示例只是简单的固定值调用Sleep,实际情况一般需要计算延时动态值调用Sleep以保持帧率稳定。
int main(int argc, char** argv) {
//创建窗口
Win32Window window(L"多路流合并预览窗口");
bool exitFlag = false;
//流合并对象
AC::GdiVideoMerger gm;
//设置每条输入流参数
gm.SetStreamRect(0, 0, 0, 0.25, 0.25, 2);
gm.SetStreamRect(1, 360, 0, 960, 540, 1, AC::GdiVideoMerger::TrueValue);
gm.SetStreamRect(2, 0, 0, 1, 1, 0.25, 0.5, 0.5, 0.5, 1);
gm.SetStreamRect(3, 360, 540, 360, 540, 960, 540, 960, 540, 1, AC::GdiVideoMerger::TrueValue);
//设置输出流大小
gm.SetOutputSize(1920, 1080);
//设置输出流格式
gm.SetOutputFormat(AC::PixelFormat::PIXELFORMAT_BGRA32);
//启动截屏线程
//输入流1
std::thread t1([&]() {
DesktopGrabber dg;
while (!exitFlag)
{
//截屏
dg.Grab();
//填充数据
AC::VideoFrame frame;
frame.Data = dg.Data();
frame.DataLength = dg.DataLength();
frame.Format = AC::PixelFormat::PIXELFORMAT_BGRA32;
frame.Stride = dg.Stride();
frame.Width = dg.Width();
frame.Height = dg.Height();
//写入流
gm.Write(0, &frame);
Sleep(40);
}
});
//输入流2
std::thread t2([&]() {
DesktopGrabber dg(24);
while (!exitFlag)
{
//截屏
dg.Grab();
//填充数据
AC::VideoFrame frame;
frame.Data = dg.Data();
frame.DataLength = dg.DataLength();
frame.Format = AC::PixelFormat::PIXELFORMAT_BGR24;
frame.Stride = dg.Stride();
frame.Width = dg.Width();
frame.Height = dg.Height();
//写入流
gm.Write(1, &frame);
//动态修改参数-向右拉伸
width += 0.01;
gm.SetStreamRect(0, 0, 0, width, 0.25, 1);
if (width > 1)
width = 0.1;
Sleep(30);
}
});
//单独线程合并
AC::PixelFormat foramt = AC::PixelFormat::PIXELFORMAT_BGRA32;
std::thread t7([&]() {
DesktopGrabber dg;
while (!exitFlag)
{
auto t = clock();
//读取当前合并帧
auto oFrame = gm.LockFrame();
printf("merge one frame cost time:%dms\n", clock() - t);
//显示合并帧
PresentBgra(oFrame->Format == AC::PIXELFORMAT_BGRA32 ? 32 : 24, window.Hwnd(), oFrame->Data, oFrame->Width, oFrame->Height, 0, 0, 640, 360);
gm.UnlockFrame();
//按照一定的帧率合并
Sleep(33);
};
});
//消息循环等待窗口关闭
Win32Window::Exec();
exitFlag = true;
t1.join();
t2.join();
t7.join();
return 0;
}
3、效果预览

四、性能测试
操作系统:win11
cpu:i7 8750
测试方法:30秒内取5次数据计算均值
| 输入流 | 输出流 | 合并一帧耗时 |
|---|---|---|
| 2路1080p | 1080p | 24.8ms |
| 3路1080p | 1080p | 38.2ms |
| 4路1080p | 1080p | 41.8ms |
总结
以上就是今天要讲的内容,gdi是可以实现多路视频流合并的,对于1080p两路流的合并耗时是可以接受的, 但是仅支持bgra32和bgr24。但总的来说还算是一个可用方案,而且也为我们提供了一些思路,图像引擎可以用来做视频流合并。
边栏推荐
- Opengauss "user story" is officially launched! One click to share practical experience, Limited Edition Gift waiting for you
- Introduction to 51 single chip microcomputer UART serial port communication
- Become a white hat God | challenge opengauss vulnerability reward program
- PAT (Advanced Level) Practice 1156 Sexy Primes (试除法)
- The process of manually installing redis extensions in yii2 framework
- 新建文档 bug,也可能是卡死了
- command
- STM32的内部资源
- MySQL 8.0.28 installation and configuration tutorial
- What are the challenges facing energy project management? Skills and tools for successfully managing energy projects
猜你喜欢

2022 Shanghai safety officer C certificate examination simulation 100 questions and simulation examination

副业收入是我做程序员的3倍,工作外的B面人生是怎样的?

SSL证书安装后网站还是显示不安全

3、NLP模型

Introduction to 51 single chip microcomputer UART serial port communication

Cat dog classification based on tensorflow

Introduction to 51 single chip microcomputer infrared communication

大学生 生活小技巧:利用插件(Tampermonkey )学习网课 | 查题

三维重建系统 | L2相机模型

wps 调整文字间距
随机推荐
Wechat applet saves video to photo album wx saveVideoToPhotosAlbum()
Data product learning - real time computing platform
pixi. JS drag function
midway的使用教程
How to use Google home speaker voice to control zhiting home cloud devices?
PAT (Advanced Level) Practice 1001 A+B Format (模拟)
Is the financial product guaranteed or not?
Deepin compiles VirtualBox records and resolves compilation errors
步进电机总结
MySQL表管理(文件)
2022.6.9 C asynchronous
鸡兔同笼问题
After the SSL certificate is installed, the website still shows insecurity
latex怎么也不换行,奇怪
How to do enterprise digital transformation? Three fusions and three transformations
Stepper motor summary
新建文档 bug,也可能是卡死了
What does the server stop responding mean and how to troubleshoot it?
Viewing memory management from entering kernel state
Programming Examples Using IBV Verbs