Я использую комбинацию python и С++ для создания снимка области экрана и использую этот снимок экрана как часть видеопотока (пиксельное изображение в метке PyQt5), а также сохраняю снимок экрана как .bmp.. в настоящее время делается в 30 кадров в секунду для создания видеофайла позже. Код работает до сих пор просто отлично, если не считать утечки памяти, которую я получаю.
contextcreator.cpp, помещенный в libccreator.so
#include "contextcreator.h"
#include <cstring>
#include <stdio.h>
#include <shlwapi.h>
#include <typeinfo>
BYTE* createContext(int x, int y, int width, int height){
HDC hdesktop = GetDC(NULL);
HDC memDC = CreateCompatibleDC(hdesktop);
HBITMAP hbitmap = CreateCompatibleBitmap(hdesktop, width, height);
HGDIOBJ hbitmapOld = (HBITMAP)SelectObject(memDC, hbitmap);
BitBlt(memDC, 0, 0, width, height, hdesktop, x, y, SRCCOPY|CAPTUREBLT);
SelectObject(memDC, hbitmapOld);
BITMAPINFO bmi = {0};
bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
GetDIBits(hdesktop, hbitmap, 0, 0, NULL, &bmi, DIB_RGB_COLORS);
BYTE* stream = new BYTE[bmi.bmiHeader.biSizeImage];
bmi.bmiHeader.biCompression = BI_RGB;
GetDIBits(hdesktop, hbitmap, 0, bmi.bmiHeader.biHeight, (LPVOID)stream, &bmi, DIB_RGB_COLORS);
BYTE* data = new BYTE[14 + sizeof(bmi) + bmi.bmiHeader.biSizeImage];
memcpy(data + 14, &bmi, sizeof(bmi));
memcpy(&data[0] + sizeof(bmi) + 14, stream, bmi.bmiHeader.biSizeImage);
for(int i = 0; i < 14; i++){
data[i] = 0; }
delete[] stream;
ReleaseDC(NULL, hdesktop);
DeleteDC(memDC);
return data;
}
void releaseData(BYTE* stream){
delete[] stream;
}
Код Python, использующий libccreator.so
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from ctypes import *
import ctypes.wintypes as wintypes
import time
import os
os.add_dll_directory("C:/msys64/mingw64/bin")
mylib = cdll.LoadLibrary('C:/Users/amish_ac2c1jm/OneDrive/Documents/blahblah/libccreator.so')
create_context = mylib.createContext
create_context.argtypes = [c_int, c_int, c_int, c_int]
create_context.restype = POINTER(wintypes.BYTE)
release_stream = mylib.releaseData
release_stream.argtypes = [POINTER(wintypes.BYTE)]
release_stream.restype = None
class CaptureThread(QObject):
finished = pyqtSignal()
update_image = pyqtSignal([bytearray])
def __init__(self, x, y, w, h, parent=None):
super().__init__(parent)
self.x = x
self.y = y
self.w = w
self.h = h
self.stopthread = False
self.framenumber = 0
def run(self):
test_timer = time.time()
while not self.stopthread:
if time.time() - test_timer >= 1000/30/1000:
test_timer = time.time()
self.capture()
self.finished.emit()
def capture(self):
bmpptr = create_context(self.x, self.y, self.w, self.h)
data = bytearray(string_at(addressof(bmpptr.contents) + 0x22, 0x4))
size = int.from_bytes(data, byteorder='little', signed=False) + 0x36
data = bytearray(string_at(bmpptr, size))
release_stream(bmpptr)
data[0:2] = b'BM'
value = int.from_bytes(data[0x22:0x26], byteorder='little', signed=False)
data[2:6] = (value + 0x36).to_bytes(4, byteorder='little', signed=False)
data[6:10] = b'\x00\x00\x00\x00'
data[10:14] = b'\x36\x00\x00\x00'
with open(f"images/frame{self.framenumber}.bmp", "wb") as f:
f.write(data)
self.framenumber += 1
self.update_image.emit(data)
Первоначально у меня были некоторые утечки памяти из-за того, что я не удалял массивы байтов, созданные с помощью нового ключевого слова, и такая проблема с утечкой памяти стала очевидной довольно быстро, когда мои мониторы начали мигать, а хром зависал вместе с pycharm. Я также изначально не использовал ReleaseDC для [аппаратного?] DC, а вместо этого использовал DeleteDC как для этого DC, так и для контроллера домена памяти. Я смог визуально увидеть утечку памяти в диспетчере задач, так как мой проект очень быстро преодолел использование памяти chrome и pycharm (в конце концов, эти bmp не сжаты ... что-то, что я рассмотрю позже). Тем не менее, утечка памяти сохраняется, но не отображается для моего приложения в диспетчере задач, а показывает только постепенное увеличение общего использования памяти, пока у меня не закончится память. Занимает около 7 минут или около того (у меня 16 ГБ ОЗУ).
Я чувствую, что это как-то связано с DC, но я не совсем уверен. У меня есть некоторый опыт работы с С++ некоторое время назад, но как только я изучил Python, я не пропустил компилятор и проблемы с компоновкой, если не сказать больше, лол. Я использую PyCharm для своей IDE для Python и Qt Creator для C++. Заранее благодарю за любую помощь :)
CreateCompatibleBitmap
не сочетается с DeleteObject
Для высокопроизводительной записи экрана вам, вероятно, следует использовать Windows.Graphics.Capture . Роберт Михаелян собрал полное приложение Win32CaptureSample, которое иллюстрирует как захват экрана, так и кодирование видео.
@IgorTandetnik, кажется, это проблема, я дал ему поработать несколько минут, и мое общее использование памяти никогда не превышало 46%, за исключением небольших моментов. Это способ для меня отметить это как ответ? Я проведу еще тесты, прежде чем официально закрою его, но я уверен, что вы меня прикрыли. Кроме того, большое спасибо .. я немного злюсь, что не перепроверил документы по функциям для получения дополнительных заметок по очистке.
Вы можете написать самостоятельный ответ (и принять его позже).
@IInspectable Я столкнулся с этим, когда уже был глубоко увлечен своим текущим решением проблемы захвата. Краткий взгляд только что, и я не понимаю, как я могу захватить часть экрана / приложения, а не все это. В настоящее время я перехватываю и блокирую события мыши, чтобы выбрать область экрана, чтобы получить регион. У меня есть приложение, которое теперь создает видеофайлы из области, которая была целью (без звука, лол). Кроме того, это практика программирования для любителей, в то время как у меня нет проектов, которые меня действительно волнуют, поэтому в целом я изучаю и поддерживаю свой кодовый мозг в тонусе. В любом случае, я ценю информацию. :)
Согласно ответу Игоря Тандетника на комментарий, утечка произошла из-за того, что объект, возвращенный функцией CreateCompatibleBitmap, не был удален.
Просто не используйте необработанные указатели. Кто собирается освободить байты, которые вы выделили? Есть также много других вещей, которые могут выиграть от большего количества RAII, чтобы гарантировать, что в случае ошибок ничего не утечет.