Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Бесшовное разбиение и склейка видео с поддержкой DirectShow

Anna | 24.06.2014 | нет комментариев

Один из наших отделов занимается ручным тестированием мультимедиа-компонентов для автомобилей. При этом непрерывно ведется видеозапись всех изготавливаемых действий (нажатие кнопок, вставка дисков и т. п.) и реакции системы: одна из камер направлена на дисплей. Видео в данном случае является доказательством слежения ошибки, а также предоставляет разработчикам дорогую информацию о том, какие действия производились и как стремительно. Согласитесь, информация крайне значимая для багрепортов, не так ли?

Особенность систем состоит в том, что ошибки могут появиться самопроизвольно и невзначай, на любом этапе тесткейса, а то и вообще легко в режиме ожидания, когда запись видео не ведется. Интересующихся приглашаю под кат, где я опишу разработанное мною решение для бесшовного разбиения и склеивания видео. Вследствие нему запись ведется каждый день, и видео сохраняется в комфортные файлы небольшого размера, что разрешает нам отлавливать и документировать редкие ошибки, а заодно и радовать разработчиков десятками видео с немыслимыми реакциями системы.

Для чего разбивать видео?

В самом деле — для чего? Сберегали бы себе восьмичасовые файлики, а потом в каком-нибудь видеоредакторе нарезали бы ломтики надобного видео, и дело с концом. На самом деле все так и происходило прежде, но здесь много недостатков: копаться в длинных файлах неудобно, позже вырезания так либо напротив доводится перекомпрессировать видео с потерей времени и качества, да и место на дисках не резиновое все же. Помимо того, рано либо поздно доводится останавливать запись для вероятности работы с файлами, и вот здесь-то по закону подлости неизменно происходят самые пагубные и неповторяющиеся ошибки. В всеобщем, приговор — нужно резать на этапе записи!

Что нам предлагает DirectShow

Для реализации была выбрана спецтехнология DirectShow. Одно из требований также — на выходе обязаны получаться файлы Windows Media, а именно WMV3, так что было принято решение делать компрессию на лету, благо современные компьютеры это с легкостью разрешают. Основная идея такова: нам нужна вероятность в произвольный момент времени переключить входящие потоки аудио и видео на иной файл, не утратив при этом ни кадра. Так мы сумеем вести запись в файлы длительностью, скажем, две минуты, а при необходимости бесшовно склеивать их.

Возведем самый обыкновенный граф фильтров для записи видео со звуком в формат Windows Media и с предпросмотром. Получится что-то как бы этого:

Как работает данный граф? Два входных фильтра для аудио и видео обслуживаются различными потоками, которые доставляют сэмплы (samples) на входные пины следующих фильтров. С поддержкой фильтра Smart Tee мы дублируем входящие видео-данные, одна копия отправляется на экран в Video Renderer, а вторая уходит в фильтр WM ASF Writer, тот, что собственно и изготавливает синхронизацию аудио и видео, их компрессию и запись в файл.

Решение «в лоб» с двумя фильтрами, которые дозволено было бы попеременно применять в графе, изменяя имена выходных файлов, не работает.

У графа DirectShow есть одна специфика: до тех пор, пока он не будет остановлен, все его «выходные» фильтры держат файлы открытыми и не финализируют их. Помимо того, без остановки графа немыслимо поменять имена выходных файлов либо соединять/разъединять фильтры. Но остановка и запуск графа чреваты потерями нескольких кадров, а то и нескольких секунд! Ясно, что стандартными средствами не обойтись.

Независимые графы

Одно из решений — сделать граф захвата аудио- и видео-данных (Capture Graph) само­стоятельным от графа записи (Record Graph), Дабы дозволено было останавливать конечный для финализации файлов. Это допустимо, скажем, с поддержкой GMFBridge от создателя DirectShow — Geraint Davies. Приблизительная схема работы каждой системы выглядела бы так:

GMFBridge находится единовременно во всех 3 графах, разрешая на лету переключать потоки сэмплов между первым и вторым Record Graph, не теряя ни одного сэмпла. В товремя как один из графов записи коспрессирует наше видео, мы настраиваем 2-й граф (имя выходного файла), благо он абсолютно может быть остановлен, не влияя на остальные. В необходимый момент мы запускаем 2-й граф, переключаем GMFBridge и останавливаем 1-й. Вуаля!

Но такое решение имеет явственные недочеты. Во-первых, источники. Нужно иметь две копии графов записи, что негативно сказывается на всеобщей продуктивности и применении памяти. Помимо того, при наличии единовременно видео и аудио весьма трудно синхронизировать их — всякий граф имеет свой отсчет времени, к тому же сами сэмплы поставляются различными потоками. Все это приводило к спонтанным «зависаниям» самого GMFBridge в момент переключения графов, так что от этого решения было решено отказаться. Начальный код инструмента, безусловно, открыт, и при желании дозволено было бы разобраться в причинах его нестабильной работы, но все же желание сэкономить источники перевесило, и я решил подойти к задаче с иной стороны.

Пишем свой ASF Writer

С преферансом и куртизанками. Верно! Нам необходим такой WM ASF Writer, тот, что умел бы по команде сам переключаться на иной файл без необходимости останавливать граф. Тогда мы сумеем взять 1-й и самый примитивный граф, вставить туда наш кастомный фильтр взамен стандартного WM ASF Writer и радоваться жизни.
Сотворим свой фильтр, добавив к стандартным способам еще один новейший StreamToFile, тот, что будет служить для переключения между файлами.

class CCustomASFWriter : public CBaseFilter
{
public:
	STDMETHOD(StreamToFile)(BSTR szFileName);
}

По поводу примеров кода

Все примеры кода ниже достаточно крепко упрощены для наглядности, скажем, всецело выброшена обработка ошибок, убраны разные добавочные проверки и т. п.

Дабы не утратить сэмплы в момент переключения, а также Дабы не блокировать потоки доставки сэмплов, добавим в наш фильтр многопоточную очередь для входящих данных. Я применял реализацию подобно вот этой, немножко допилив ее для применения в режиме multiple producers — single consumer. Очередь я решил применять одну и для видео, и для аудио, и вот отчего. Все упирается в нашу новую функцию переключения файлов. Для этого значимо помнить, что частота поставки видео-сэмплов, как правило, много выше таковой у аудио: скажем, 30 Гц видео и 2 Гц (по 500 мс на сэмпл) у аудио. Соответственно, переключение необходимо изготавливать сразу позже доставки аудио-сэмпла. Соблюдая обычный порядок сэмплов в очереди, дозволено дюже комфортно делать именно так.

Рассматривая это, наш способ StreamToFile будет каждого лишь сигнализировать фильтру, что он должен сразу позже дальнейшего аудио-сэмпла закрыть нынешний файл и начать запись в новейший. Пока подготавливается новейший файл, все входящие сэмплы сохраняются в очереди.

HRESULT STDMETHODCALLTYPE CCustomASFWriter::StreamToFile(BSTR szFileName)
{
	wcscpy_s(m_szCurrentFile, szFileName);
	{
		CAutoLock lock(m_pLock);
		m_bSwitchRequested = TRUE;
	}
	return S_OK;
}

Собственно сама компрессия и запись в файлы происходит с применением Windows Media Format SDK, а именно интерфейса IWMWriter.

	IWMWriter *pWriter = NULL;
	WMCreateWriter(NULL, &pWriter);

Для этого в отдельном потоке вертится цикл обработки входящих сэмплов:

	while (bRunning)
	{
		StreamSamplesToWriter(pWriter);
		DWORD dwWaitResult = WaitForSingleObject(hEventStopStreaming, 33);
		if (dwWaitResult == WAIT_OBJECT_0)
		{
			m_pPinVideo->StopQueuingNow();
			m_pPinAudio->StopQueuingNow();
			bRunning = FALSE;
		}
	}

Самое увлекательное и происходит в способе StreamSamplesToWriter. Тут сэмплы отправляются вIWMWriter, а также происходит переключение файлов в верный момент времени, если был дан сигнал к переключению с поддержкой способа StreamToFile.

STDMETHODIMP CCustomASFWriter::StreamSamplesToWriter(IWMWriter *pWriter)
{
	BOOL bMustSwitch = FALSE;
	void *pObject = NULL;

	while (m_pSamplesQueue->Pop(pObject))
	{
		CQueuedSample *pSample = (CQueuedSample*)pObject;	
		DWORD inputNumber = pSample->MediaType == MEDIATYPE_Video ? m_pPinVideo->InputNumber : m_pPinAudio->InputNumber;

		INSSBuffer *pBuffer = NULL;
		pWriter->AllocateSample(pSample->DataSize, &pBuffer);
		LPBYTE pbDestBuffer = NULL;
		pBuffer->GetBuffer(&pbDestBuffer);

		CopyMemory(pbDestBuffer, pSample->Data, pSample->DataSize);			
		pWriter->WriteSample(inputNumber, pSample->Start, pSample->IsDiscontinuity | pSample->IsSyncPoint, pBuffer);
		pBuffer->Release();

		if (inputNumber == m_pPinAudio->InputNumber)
		{
			{
				CAutoLock lock(m_pLock);
				bMustSwitch = m_bSwitchRequested;
				if (m_bSwitchRequested)
					m_bSwitchRequested = FALSE;
			}
			if (bMustSwitch)
			{				
				pWriter->EndWriting();				
				pWriter->SetOutputFilename(m_szCurrentFile);
				pWriter->BeginWriting();
			}
		}
		delete pSample;
	}
}

Выходит, нам удалось добиться итога! Дергая способ StreamToFile в произвольные моменты времени, мы получаем новые файлы, причем не теряя ни цельного кадра.

Склеиваем разрезанное

Ну что же, мы получили кучу файликов по две минуты. А что же делать, если нам необходимо видео длиной 4 минуты, а самое увлекательное место отслеживается аккурат в момент переключения с одного файла на иной? Не напасть — мы можем дюже легко склеить эти файлы в один, причем сделать это без перекодирования! При этом склейка будет подлинно бесшовной, так как при записи не было утрачено ни ни одного кадра.

Для этого используем IWMSyncReader и IWMWriterAdvanced.

	IWMWriter *pWriter = NULL;
	IWMWriterAdvanced *pWriterA = NULL;
	WMCreateWriter(NULL, &pWriter);
	pWriter->QueryInterface(IID_IWMWriterAdvanced, (void**)&pWriterA;
	IWMSyncReader *pReader = NULL;
	WMCreateSyncReader(NULL, 0, &pReader);

	for (element = m_oMergeFileList.begin(); element < m_oMergeFileList.end(); element   )
	{
		pReader->Open(element->FileName);
		IWMProfile *pProfile = NULL;
		pReader->QueryInterface(IID_IWMProfile, (void**)&pProfile);

		// устанавливаем знак того, что мы не хотим декомпрессировать данные, а будем легко читать пакеты "как есть"
		for (WORD i = 0; i < dwStreamCount; i  )
		{
			pProfile->GetStream(i, &pStream);
			pStream->GetStreamNumber(&wStreamNumber);
			pReader->SetReadStreamSamples(wStreamNumber, TRUE);
		}

		HRESULT hr = S_OK;
		while (SUCCEEDED(hr))
		{
			hr = pReader->GetNextSample(0, &pSample, &cnsSampleTime, &cnsDuration, &dwFlags, &dwOutputNum, &wStreamNum);
			pWriterA->WriteStreamSample(wStreamNum, qwSampleTimeToWrite, 0, cnsDuration, dwFlags, pSample);
		}
	}

В результате дюже стремительно (миллисекунды!) получаем файл длиной 4 минуты, место склейки в котором найти немыслимо. Сурово говоря, это не вовсе так, и при редких и определенных условиях склейка все же не получается настоль безукоризненной (скажем, при дюже низкой частоте кадров, настроенной на камере). Впрочем в типичных условиях при просмотре это место подлинно неприметно.

Хочу еще резать и клеить!

На этом я не остановился и решил пойти дальше. Была добавлена магическая кнопка, которая разрешает мигом получить файл с видео за последнюю минуту (на самом деле, желаемая продолжительность настраивается). Функция оказалась дюже актуальна тестерами — увидел непредвиденную ошибку, тыкнул на кнопку, получил видео.

Проиллюстрирую обстановку, Дабы было больше ясно. Представим, кнопка нажимается примерно сразу позже того, как теснее было произведено механическое переключение на дальнейший файл:

При нажатии на фантастическую кнопку вновь происходит переключение на новейший файл, Дабы Файл 2стал доступен. Но сейчас нам необходимо еще разрезать Файл 1, а потом склеить его с Файлом 2.

Тут тоже ничего трудного. Про склеивание я теснее писал, а разрезание производится подобно: читаются сжатые пакеты без декомпрессии и пропускаются все непотребные, а начиная с некоторого момента времени все читаемые пакеты пишутся в файл с корректировкой временных меток, так Дабы 1-й пакет имел 00:00:00. Здесь нужно также верно предпочесть момент разрезания, Дабы 1-й пакет нового файла содержал опорный кадр (ключевой либо I-кадр), а не предсказанный P-кадр (дельта-кадр). Опорные кадры могут размещаться в WMV-файлах даже раз в полминуты при маленьких изменениях картинки, следственно пришлось сконфигурировать применение форсированных опорных кадров.

 

Источник: programmingmaster.ru

 

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB