Товарищи, необходимо быстро конвертировать bitmap одного формата (может быть любого разрешения, но 24 бита на пиксель) в bitmap другого формата (разрешение фиксированное -- 320х240, 24 бита на пиксель) для последующего вывода на экран, после чего ещё необходимо послать кадр по сети (естественно он жмётся, но это уже к делу не относится).
Конвертирую так :
//Здесь VIDEOCHAT_X, VIDEOCHAT_Y -- константы, равные 320 и 240 соответственно
BITMAPINFOHEADER * srcInfo = videoCB.getBitmapInfo(); //Исходный формат данных
unsigned char * srcData = videoCB.getBuffer(); //Исходные данные
unsigned char * bmpData = srcData;//NULL; //Буффер, куда будут записываться преобразованные данные
BITMAPINFOHEADER inf; // Задаём формат буффера
inf.biSize = sizeof(BITMAPINFOHEADER);
inf.biWidth = VIDEOCHAT_X;
inf.biHeight = VIDEOCHAT_Y;
inf.biPlanes = 1;
inf.biBitCount = 24;
inf.biCompression = BI_RGB;
inf.biSizeImage = (((inf.biWidth*inf.biBitCount + 31)& ~31)/8)*(inf.biHeight);
inf.biXPelsPerMeter = 0;
inf.biYPelsPerMeter = 0;
inf.biClrUsed = 0;
inf.biClrImportant = 0;
// Convert to compatible format (bitmap, no compression, 24bpp, 320x240)
HDC dc = ::GetDC(NULL);
HDC memDc = ::CreateCompatibleDC(dc);
HBITMAP bmp = ::CreateCompatibleBitmap(dc, VIDEOCHAT_X, VIDEOCHAT_Y);
::ReleaseDC(NULL, dc);
::SelectObject(memDc, bmp);
::SetStretchBltMode(memDc, COLORONCOLOR);
int x, y;
int dx, dy;
if (((double)srcInfo->biWidth / (double) srcInfo->biHeight) > (4.0/3.0)) {
x = VIDEOCHAT_X;
y = srcInfo->biHeight * (3.0/4.0);
dx = 0;
dy = (VIDEOCHAT_Y - y) / 2.0;
}
else {
y = VIDEOCHAT_Y;
x = srcInfo->biWidth * (4.0/3.0);
dy = 0;
dx = (VIDEOCHAT_X - x) / 2.0;
}
int res = ::StretchDIBits(memDc, dx, dy, x, y, 0, 0, srcInfo->biWidth, srcInfo->biHeight, srcData, (BITMAPINFO*)srcInfo, DIB_RGB_COLORS, SRCCOPY);
HBITMAP curr = (HBITMAP)::SelectObject(memDc, NULL);
bmpData = new unsigned char[inf.biSizeImage];
int numLines = GetDIBits(memDc, bmp, 0, VIDEOCHAT_Y, bmpData, (BITMAPINFO*)&inf, DIB_RGB_COLORS);
::DeleteBitmap(bmp);
::DeleteDC(memDc);
Всё бы хорошо, только вот скорость меня не устраивает. Если этот код закомментировать, то видео и звук будут синхронизированы, а если оставить, то видео будет заметно отставать. Подскажите пожалуйста, как сделать быстрее это преобразование? Может быть стоит использовать что-то другое, а не GDI?
Здравствуйте, andrey.viktorov, Вы писали:
AV>Товарищи, необходимо быстро конвертировать bitmap одного формата (может быть любого разрешения, но 24 бита на пиксель) в bitmap другого формата (разрешение фиксированное -- 320х240, 24 бита на пиксель) для последующего вывода на экран, после чего ещё необходимо послать кадр по сети (естественно он жмётся, но это уже к делу не относится).
[skip]
AV>Всё бы хорошо, только вот скорость меня не устраивает. Если этот код закомментировать, то видео и звук будут синхронизированы, а если оставить, то видео будет заметно отставать. Подскажите пожалуйста, как сделать быстрее это преобразование? Может быть стоит использовать что-то другое, а не GDI?
Даешь больше кода!!! Как я понял ты это делаешь в своем фильтре ( скорей всего собственный соурс фильтр. ) . Покажи, что ты там еще написал. В свое время делал подобное Из картинок делал поток + наложение фоновой музыки. (было несколько заморочек с синхронизацией). В качестве альтернативы можно использовать DES.
Мм... не совсем. Имеется граф, построенный почти руками. Имеется два ISampleGrabber'a (свой фильтр было лень писать, думаю обойтись этим) : один цепляется перед IFileSource'ом для выдирания собственно bitmap'ов (там же и ставится 24-х битность битмапа), другой цепляется перед Default Direct Sound Device и хватает PCM (по идее надо на концы посадить по Null Renderer'у, но тоже было лень). Есть два каллбака на каждый из потоков. Как только у нас приходит семлп, сразу вызывается процедура обработки видео\аудио и дальнейшей отсылки на сервер (есть нюанс такой : аудио отсылается ровно каждую секунду, а не по приходу семлпа, т.е. данные из семплов копятся, пока таймер не сработает). Вот аудио нормально шлётся и играется, а вот с видео... целыми днями с ним борюсь.
процедура обработки видео (немного оптимизировал, сделав bmpData массивом с фиксированной длиной и вынеся videoFmt за пределы функции, ибо он один раз должен инициализироваться) :
void FileCapture::processPendingVideo() {
BITMAPINFOHEADER * srcInfo = videoCB.getBitmapInfo();
unsigned char * srcData = videoCB.getBuffer();
// Convert to compatible format (bitmap, no compression, 24bpp, 320x240)
HDC dc = ::GetDC(NULL);
HDC memDc = ::CreateCompatibleDC(dc);
HBITMAP bmp = ::CreateCompatibleBitmap(dc, VIDEOCHAT_X, VIDEOCHAT_Y);
::ReleaseDC(NULL, dc);
::SelectObject(memDc, bmp);
::SetStretchBltMode(memDc, COLORONCOLOR);
int x, y;
int dx, dy;
if (((double)srcInfo->biWidth / (double) srcInfo->biHeight) > (4.0/3.0)) {
x = VIDEOCHAT_X;
y = srcInfo->biHeight * (3.0/4.0);
dx = 0;
dy = (VIDEOCHAT_Y - y) / 2.0;
}
else {
y = VIDEOCHAT_Y;
x = srcInfo->biWidth * (4.0/3.0);
dy = 0;
dx = (VIDEOCHAT_X - x) / 2.0;
}
int res = ::StretchDIBits(memDc, dx, dy, x, y, 0, 0, srcInfo->biWidth, srcInfo->biHeight, srcData, (BITMAPINFO*)srcInfo, DIB_RGB_COLORS, SRCCOPY);
HBITMAP curr = (HBITMAP)::SelectObject(memDc, NULL);
int numLines = GetDIBits(memDc, bmp, 0, VIDEOCHAT_Y, &bmpData[0], (BITMAPINFO*)&videoFmt, DIB_RGB_COLORS);
::DeleteBitmap(bmp);
::DeleteDC(memDc);
// Send data to listeners
ResetEvent(videoProcessingWaiter);
POSITION p = listeners.GetHeadPosition();
while (NULL != p) {
CaptureListener * listener = listeners.GetNext(p);
LOCK(listeners);
listener->onVideoFrame(VIDEOCHAT_X, VIDEOCHAT_Y, (BYTE*)&bmpData[0]);
UNLOCK(listeners);
}
}
////////////////// Описание videoFmt ////////////
videoFmt.biSize = sizeof(BITMAPINFOHEADER);
videoFmt.biWidth = VIDEOCHAT_X;
videoFmt.biHeight = VIDEOCHAT_Y;
videoFmt.biPlanes = 1;
videoFmt.biBitCount = 24;
videoFmt.biCompression = BI_RGB;
videoFmt.biSizeImage = (((videoFmt.biWidth*videoFmt.biBitCount + 31)& ~31)/8)*(videoFmt.biHeight);
videoFmt.biXPelsPerMeter = 0;
videoFmt.biYPelsPerMeter = 0;
videoFmt.biClrUsed = 0;
videoFmt.biClrImportant = 0;
Звук обрабатывается аналогичным образом (конвертируется через acm в нужный формат и так же шлётся всем листенерам).
Т.е. по сути я не забочусь о синхронности: она должна обеспечиваться за счёт правильного поступления семплов, полученных из ISampleGrabber'ов. Если закомментировать код преобразования, то всё будет играть так, как надо.
По поводу DES. Можно поподробнее? Или ссылку на их сайт?
Я бы попробовал создать битмап с помощью CreateDIBSection, чтобы не нужно было каждый раз копировать его в GetDIBits. К тому же, все создания контекстов и битампов лучше делать один раз при изменении формата поступающего видео, а затем каждый раз вызывать только StretchDIBits. Ещё можно попробовать вместо StretchDIBits использовать функцию DrawDibDraw — есть мнение, что она побыстрее будет.
AV>По поводу DES. Можно поподробнее? Или ссылку на их сайт?
DES — это DirectShow Editing Services. Достаточно подробно описано в том же DirectShow SDK.
Возникла идея: поставить у ISampleGrabber'а, который хватает битмапы, в AM_MEDIA_TYPE нужный формат битмапа и попробовать установить соединение между пинами в таком формате. Такое возможно? В общем, было бы просто супер, если можно это сделать средствами DirectShow. А нужно сделать следующее : есть семпл определённой длины и ширины, его необходимо подогонать в окно 320х240 не изменяя пропорций (если один из размеров получаются меньше, то оставшееся пространство забивается чёрными пикселями и получаются аля полосы как широкоформатном видео).
Здравствуйте, andrey.viktorov, Вы писали:
AV>Возникла идея: поставить у ISampleGrabber'а, который хватает битмапы, в AM_MEDIA_TYPE нужный формат битмапа и попробовать установить соединение между пинами в таком формате. Такое возможно? В общем, было бы просто супер, если можно это сделать средствами DirectShow. А нужно сделать следующее : есть семпл определённой длины и ширины, его необходимо подогонать в окно 320х240 не изменяя пропорций (если один из размеров получаются меньше, то оставшееся пространство забивается чёрными пикселями и получаются аля полосы как широкоформатном видео).
Не все так просто как кажеться. Да, конечно возможно, либо используй PushSource Filter для поставки видеокадров. Если хочешь в реальном времени про GDI забудь. Сейчас занимаюсь аналогичной задачей, не знаю получиться ли в реальном времени, но чувствую что — нет, вот откуда такие мысли. Сейчас я написал программу для генерации видеопотока но основе определенных спрайтов которые генерируються на отдельной машине. Потому что когда я попробовал рисовать прямо в буфер кадра, естественно производительность заставляла желать лучшего. После того как я начал генерировать спрайты на отдельной машине, попробовал опять посредством GDI составлять картинку из спрайтов, но опять таки получались довольно приличные тормоза, но уже меньше ) Решил делать посредством memcpy(pSample, pBackBuf, _screenSize), просто у меня задачка еще свзана с тем что аппаратное обеспечение очень специализированное и аппаратное ускорение графики как таковое отсутствует.
Я делаю:
hr = m_pIPushSource->GetFrameBuffer(&pSample);
if (SUCCEEDED(hr))
{
hr = pSample->GetPointer(&pBuffer);
cbBuffer = pSample->GetActualDataLength();
if (pamt && pvih)
WriteBuffer(pBuffer); // тут внутри memcpy про который я говорил вышеif (SUCCEEDED(m_pIPushSource->Deliver(pSample)))
{
m_FrameCount += 1;
pBuffer = NULL;
}
...
}
Далее, функция DrawBackBuffer примерно следующего содержания, вызываеться в WriteBuffer перед memcpy(...):
byte* DrawBackBuffer(byte* pScreen)
{
...
int _size = dwWidth*dwHeight*4 + (dwWidth*4*400); // специфика такая, бэкбуфер должен быть больше рабочего кадра, не имеет отношения к теме ;)if(backbuf == NULL)
backbuf = new byte[_size];
// дальше "рисуем"for(int i=0; i < m_vImages.size(); i++)
{
...
m_vImages[i]->LockBits(&rect, ImageLockModeRead,
PixelFormat32bppRGB, &bitmapData);
pixels = (unsigned char*)bitmapData.Scan0;
DrawImage(x, calc_y, backbuf, pixels)
}
...
return backbuf+dwWidth*4*200;
}
Все работет отлично. Теперь у меня появилась потребность ко всей этой каше еще подмешать видеопоток с масштабированием, какзлось бы все просто, действуем по аналогии, ан нет, после небольшого теста пришел к сомнению что получиться, а тест был следующий, я добавил функцию, TestMask(... u_char* buf) примерно следующего содержания: