Программирование Windows Media Format SDK

Урок 1-й. Запись аудио и видео в формат ASF

Файловый формат ASF

ASF (Advanced System Format) является современной альтернативой более старому формату AVI, который появился ещё во времена 16-разрядных версий Windows. В создаваемых приложениях при использовании функций работы с файлами ASF необходимо задействовать Windows Media Format SDK. На сегодняшний день в современных средствах разработки приложений Windows Media Format SDK является составной частью платформы Windows SDK и не требует установки дополнительных компонентов ни в среду разработки, ни на компьютер конечного пользователя. Файлы ASF наиболее известны под расширениями WMV и WMA.

Тестовое приложение

Тестовым примером для получения навыков работы с интерфейсами записи Windows Media Format SDK служит простое консольное приложение для Windows. Проект консольного приложения легко можно создать в среде разработки Visual Studio. При создании нового проекта в окне параметров необходимо указать поддержку библиотек ATL – это упростит работу с СОМ-интерфейсами.

Итак, проект приложения создан. Теперь можно приступать к написанию самого программного кода на языке С++. Для работы с компонентами Windows Media Format SDK необходимо включить в исходный код приложения заголовочный файл wmsdk.h Необходимо также добавить в проект библиотечный файл Wmvcore.lib Одним из обязательных условий использования в клиентском приложении модулей, созданных по технологии СОМ, является вызов функции CoInitialize(). Эта функция обычно вызывается одной из первых в самом начале работы приложения. Функция CoUninitialize() вызывается чаще всего при завершении программы.

Создание контейнера для записи

Основным средством Windows Media Format SDK для записи медийных данных является интерфейс IWMWriter. Чтобы получить к нему доступ, необходимо вызвать функцию WMCreateWriter(). Имя выходного файла задаётся в методе SetOutputFilename().

Создание профиля

Перед тем, как начать запись данных в контейнер, необходимо передать ему заголовочный профиль. В этом профиле содержится информация о типе входных данных и описывается их формат. От того, на сколько правильно заданы параметры профиля, зависит конечный результат всей записи. При несоответствии параметров профиля входным данным записи, проигрыватель не сможет корректно воспроизвести полученный файл. Чтобы получить нужный профиль и передать его контейнеру, можно воспользоваться несколькими способами:
  • 1. Использовать имеющиеся системные профили.
  • 2. Загрузить описание готового профиля в виде текста.
  • 3. Сформировать собственный профиль в программном коде приложения.
Можно также брать за основу уже имеющийся профиль и затем корректировать его параметры. В любом случае профиль должен быть сформирован и передан контейнеру до начала записи медийных данных. Использование системных профилей является самым простым способом инициализации заголовочных параметров контейнера. Для этого достаточно лишь вызвать метод SetProfileByID() интерфейса IWMWriter. Перечень системных профилей содержится в заголовочном файле wmsysprf.h Но этот способ хорош лишь в том случае, когда параметры записываемых данных точно соответствуют одному из имеющихся системных профилей. Параметры всех системных профилей можно посмотреть в этом текстовом файле: SysProfiles.txt
В остальных же случаях необходимо создавать собственный профиль. Для этого используется интерфейс IWMProfileManager. Это по сути фабрика классов для создания профилей. Чтобы его активизировать, необходимо вызвать функцию WMCreateProfileManager(). Порождаемые в нём профили передаются в виде указателей на интерфейс IWMProfile. Одним из достаточно простых способов создания профиля является загрузка его описания из текстовой строки. Эта строка имеет вид XML - ресурса и может содержаться как в самом программном коде статически, так и во внешних или внутренних ресурсах приложения. Для создания профиля из тэгов текстового описания используется метод LoadProfileByData() интерфейса IWMProfileManager. Метод SaveProfile() в том же менеджере профилей возвращает текстовую строку с описанием заданного профиля. В качестве примера представлен текст описания одного из системных профилей...

Наиболее универсальным в использовании, но более трудоёмким в реализации, является метод процедурной инициализации профиля. При таком подходе создание самого профиля и запись в него всех необходимых параметров полностью ложится на плечи разработчика и реализуется непосредственно в программном коде приложения. Сам по себе профиль является по сути контейнером свойств записываемых данных. Эти свойства передаются профилю в виде указателя на интерфейс IWMStreamConfig. Интерфейс IWMStreamConfig в свою очередь содержит параметры одного из потоков записываемых в контейнер данных, например: аудио, видео и др. Получить структуру с конкретными параметрами медиа-потока можно через интерфейс IWMMediaProps, который запрашивается у интерфейса IWMStreamConfig с помощью метода QueryInterface(). Метод GetMediaType() интерфейса IWMMediaProps возвращает указатель на структуру, соответствующую входному типу медиа-данных. Так для звуковых данных это структура WAVEFORMATEX, а для видео — структура VIDEOINFOHEADER. Каждая такая конфигурация потока добавляется в профиль при помощи метода AddStream() интерфейса IWMProfile. Получить экземпляр конфигурации потока можно с помощью метода CreateNewStream() интерфейса IWMProfile, либо с помощью метода GetCodecFormat() интерфейса IWMCodecInfo. Добавлять конфигурации потоков можно и в уже существующие профили, а так же можно при необходимости менять параметры имеющихся конфигураций потоков. За основу можно взять один из системных профилей с помощью метода LoadProfileByID() интерфейса IWMProfileManager или создать новый профиль с помощью метода CreateEmptyProfile(). Внести изменения в существующую конфигурацию потока можно с помощью метода ReconfigStream() интерфейса IWMProfile. Важно отметить, что все действия по формированию профиля должны быть завершены перед тем, как профиль будет передан контейнеру записи. Установка профиля в контейнер записи выполняется с помощью метода SetProfile() интерфейса IWMWriter. После этого можно приступать к записи самих данных.

Запись данных

Запись данных начинается с вызова метода BeginWriting() и завершается методом EndWriting() интерфейса IWMWriter. Если метод BeginWriting() вместо S_OK возвращает код ошибки, то это может означать, что некоторые параметры заголовочного профиля были заданы некорректно. В этом случае дальнейшая запись данных производиться не будет. Передача самих бинарных данных для записи в контейнер осуществляется через интерфейс INSSBuffer. Чтобы получить указатель на этот интерфейс, нужно вызвать метод AllocateSample(). Далее будут рассмотрены два способа записи данных:
  • 1. Запись несжатых входных данных
  • 2. Запись предварительно сжатых данных
Метод WriteSample() интерфейса IWMWriter позволяет принимать на вход несжатые данные и записывать их как в нативном формате, так и сжатыми с помощью компрессоров Windows Media. Чтобы задействовать механизмы сжатия данных, нужно при создании профиля указать соответствующие значения подтипов медиа для каждого потока записываемых данных. Каждый подтип медийных данных имеет свой GUID и обозначает формат кодирования медийных данных.

Для сжатия входных данных видео допустимы следующие форматы: WMMEDIASUBTYPE_WMV1, WMMEDIASUBTYPE_WMV2, WMMEDIASUBTYPE_WMV3, WMMEDIASUBTYPE_WVC1

Для записи аудио можно применить форматы сжатия, такие как: WMMEDIASUBTYPE_WMAudioV9, WMMEDIASUBTYPE_WMAudioV8 WMMEDIASUBTYPE_WMAudioV7, WMMEDIASUBTYPE_WMAudioV2

Несжатыми входными данными являются растровые образы в формате RGB24 для записи видео и звуковые фрагменты в формате PCM для аудио.
Метод WriteStreamSample() интерфейса IWMWriterAdvanced позволяет записывать в контейнер те данные, которые уже были сжаты внешними компрессорами и их формат может отличаться от формата Windows Media, например такие как: MPEG-4, H.264, Motion JPEG и др. Интерфейс IWMWriterAdvanced можно получить из интерфейса IWMWriter через метод QueryInterface().

Реализация примера в программном коде на Visual C++

Так уж сложилось, что программный интерфейс Windows SDK ориентирован в основном на использование в языке программирования С/С++, поэтому было бы вполне разумным применить объектно-ориентированные возможности Visual C++ и написать собственные классы для работы с интерфейсами Windows Media Format SDK. В приведённом примере это будут классы CProfileManager и CWinMediaWriter. Класс CProfileManager, судя по его названию, предназначен для работы с интерфейсом IWMProfileManager, а класс CWinMediaWriter - соответственно с интерфейсом IWMWriter. Основное назначение класса CProfileManager состоит в том, чтобы создать нужный профиль и передать его контейнеру IWMWriter для последующей записи медийных данных. В классе CWinMediaWriter реализовано несколько методов для изменения конфигурации профиля по заданным параметрам входных данных. Так выглядят методы для конфигурации аудио-потока...

Входные данные для демонстрации примера

Чтобы на деле оценить работу контейнера записи данных в Windows Media Format SDK, необходимо сформировать в программном коде (или получить извне) серию кадров для видео-потока и соответствующий по продолжительности аудио-фрагмент. В программном коде данного примера есть класс CAudioSampler, который формирует аудио-сигнал заданной частоты и продолжительности. В качестве формирователя видео-изображений используется обычный байтовый массив RGB, который заполняется значениями определённого цвета, плавно меняющегося от одного кадра к другому. Таким образом в результате должен получиться однотонный цветовой фон с меняющимся в процессе воспроизведения цветом в сопровождении непрерывно-звучащего аудио-сигнала. Этот видеоролик записывается в выходной файл Test.wmv

Полностью проект с данным примером можно загрузить отсюда:

Запись видео Motion JPEG в контейнер ASF

Далее речь пойдёт о записи видео-потока, представленного серией закодированных в формате JPEG изображений. Каждое из этих изображений можно рассматривать как отдельный ключевой кадр. Трансляция видео в кодировке Motion JPEG широко применяется в цифровых системах видео-наблюдения, где каждая передающая камера реализует внутри себя соответствующие алгоритмы сжатия видео-потока, что позволяет существенно сократить сетевой трафик и за счёт этого передавать видео в более высоком разрешении. Приведённый в статье тестовый пример не содержит запись звука, поэтому всё внимание будет сосредоточено только на записи видео. Для записи видео Motion JPEG в файл формата ASF будет задействован не интерфейс IWMWriter, как в предыдущем примере, а интерфейс IWMWriterAdvanced, который специально предназначен для записи предварительно сжатых аудио- и видео-данных, полученных за счёт использования сторонних компрессоров, не являющихся системными компонентами Windows Media. Для получения серии кадров формата JPEG использован кодировщик изображений графической библиотеки GDI+. Исходное изображение формата RGB24 формируется непосредственно в программном коде демонстрационного примера, а затем после преобразования его в формат JPEG передаётся на запись видео-фрагмента.

Проект с данным примером можно загрузить отсюда: