Поделиться через


Пользовательские видеоэффекты

В этой статье описывается, как создать компонент среда выполнения Windows, реализующий интерфейс IBasicVideoEffect для создания настраиваемых эффектов для видеопотоков. Пользовательские эффекты можно использовать с несколькими разными среда выполнения Windows API, включая MediaCapture, который предоставляет доступ к камере устройства и MediaComposition, что позволяет создавать сложные композиции из клипов мультимедиа.

Добавление пользовательского эффекта в приложение

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

Добавление компонента среда выполнения Windows для эффекта видео

  1. В Microsoft Visual Studio с открытым решением перейдите в меню "Файл " и выберите "Добавить> новый проект".
  2. Выберите тип проекта компонента среда выполнения Windows (универсальная версия Windows).
  3. В этом примере назовите проект VideoEffectComponent. Это имя будет ссылаться в коде позже.
  4. Щелкните OK.
  5. Шаблон проекта создает класс с именем Class1.cs. В Обозреватель решений щелкните правой кнопкой мыши значок Class1.cs и выберите "Переименовать".
  6. Переименуйте файл в ExampleVideoEffect.cs. Visual Studio отобразит запрос на обновление всех ссылок на новое имя. Нажмите кнопку Да.
  7. Откройте ExampleVideoEffect.cs и обновите определение класса, чтобы реализовать интерфейс IBasicVideoEffect.
public sealed class ExampleVideoEffect : IBasicVideoEffect

Чтобы получить доступ ко всем типам, используемым в примерах в этой статье, необходимо включить следующие пространства имен в файл класса эффектов.

using Windows.Media.Effects;
using Windows.Media.MediaProperties;
using Windows.Foundation.Collections;
using Windows.Graphics.DirectX.Direct3D11;
using Windows.Graphics.Imaging;

Реализация интерфейса IBasicVideoEffect с помощью программной обработки

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

Метод Close

Система вызовет метод Close в классе, когда эффект должен завершить работу. Этот метод следует использовать для удаления созданных ресурсов. Аргументом метода является MediaEffectClosedReason , который позволяет узнать, был ли эффект закрыт обычно, если произошла ошибка, или если эффект не поддерживает требуемый формат кодирования.

public void Close(MediaEffectClosedReason reason)
{
    // Dispose of effect resources
}

Метод DiscardQueuedFrames

Метод DiscardQueuedFrames вызывается при сбросе эффекта. Типичный сценарий заключается в том, что эффект сохраняет ранее обработанные кадры для обработки текущего кадра. При вызове этого метода следует удалить набор сохраненных предыдущих кадров. Этот метод можно использовать для сброса любого состояния, связанного с предыдущими кадрами, а не только накопленных видеокадров.

private int frameCount;
public void DiscardQueuedFrames()
{
    frameCount = 0;
}

IsReadOnly, свойство

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

Совет

Если для свойства IsReadOnly задано значение true, система копирует входной кадр в выходной кадр перед вызовом ProcessFrame. Если для свойства IsReadOnly задано значение true, вы не можете записывать выходные кадры эффекта в ProcessFrame.

public bool IsReadOnly { get { return false; } }

Метод SetEncodingProperties

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

private VideoEncodingProperties encodingProperties;
public void SetEncodingProperties(VideoEncodingProperties encodingProperties, IDirect3DDevice device)
{
    this.encodingProperties = encodingProperties;
}

Свойство SupportedEncodingProperties

Система проверяет свойство SupportedEncodingProperties , чтобы определить, какие свойства кодирования поддерживаются вашим эффектом. Обратите внимание, что если потребитель эффекта не может кодировать видео с помощью указанных свойств, он вызовет close on your effect и удалит эффект из конвейера видео.

public IReadOnlyList<VideoEncodingProperties> SupportedEncodingProperties
{            
    get
    {
        var encodingProperties = new VideoEncodingProperties();
        encodingProperties.Subtype = "ARGB32";
        return new List<VideoEncodingProperties>() { encodingProperties };

        // If the list is empty, the encoding type will be ARGB32.
        // return new List<VideoEncodingProperties>();
    }
}

Примечание.

Если вы возвращаете пустой список объектов VideoEncodingProperties из SupportedEncodingProperties, система по умолчанию будет кодировать ARGB32.

 

Свойство SupportedMemoryTypes

Система проверяет свойство SupportedMemoryTypes , чтобы определить, будет ли ваш эффект получать доступ к видеокадрам в программной памяти или аппаратной памяти (GPU). При возврате MediaMemoryTypes.Cpu эффект будет передан входным и выходным кадрам, содержащим данные изображения в объектах SoftwareBitmap. При возврате MediaMemoryTypes.Gpu эффект будет передан входным и выходным кадрам, содержащим данные изображения в объектах IDirect3DSurface .

public MediaMemoryTypes SupportedMemoryTypes { get { return MediaMemoryTypes.Cpu; } }

Примечание.

Если указать MediaMemoryTypes.GpuAndCpu, система будет использовать GPU или системную память, что бы ни было эффективнее для конвейера. При использовании этого значения необходимо проверить, содержит ли метод ProcessFrame файл SoftwareBitmap или IDirect3DSurface, содержащий данные, а затем обработать кадр соответствующим образом.

 

Свойство TimeIndependent

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

public bool TimeIndependent { get { return true; } }

Метод SetProperties

Метод SetProperties позволяет приложению, использующее эффект для настройки параметров эффекта. Свойства передаются в виде карты IPropertySet имен и значений свойств.

private IPropertySet configuration;
public void SetProperties(IPropertySet configuration)
{
    this.configuration = configuration;
}

Этот простой пример будет уменьшать пиксели в каждом кадре видео в соответствии с указанным значением. Свойство объявляется, и TryGetValue используется для получения значения, заданного вызывающим приложением. Если значение не задано, используется значение по умолчанию 5.

public double FadeValue
{
    get
    {
        object val;
        if (configuration != null && configuration.TryGetValue("FadeValue", out val))
        {
            return (double)val;
        }
        return .5;
    }
}

Метод ProcessFrame

Метод ProcessFrame заключается в том, что эффект изменяет данные изображения видео. Метод вызывается один раз на кадр и передается объект ProcessVideoFrameContext . Этот объект содержит входной объект VideoFrame, содержащий входящий кадр для обработки, и выходной объект VideoFrame, в который записывается данные изображения, которые будут переданы остальной части конвейера видео. Каждый из этих объектов VideoFrame имеет свойство SoftwareBitmap и свойство Direct3DSurface, но которое из них можно использовать, определяется значением, возвращаемым из свойства SupportedMemoryTypes.

В этом примере показана простая реализация метода ProcessFrame с помощью программной обработки. Дополнительные сведения о работе с объектами SoftwareBitmap см. в разделе "Образы". Пример реализации ProcessFrame с помощью аппаратной обработки показан далее в этой статье.

Для доступа к буферу данных SoftwareBitmap требуется COM-взаимодействие, поэтому необходимо включить пространство имен System.Runtime.InteropServices в файл класса эффектов .

using System.Runtime.InteropServices;

Добавьте следующий код в пространство имен для импорта интерфейса для доступа к буферу изображения.

[ComImport]
[Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
unsafe interface IMemoryBufferByteAccess
{
    void GetBuffer(out byte* buffer, out uint capacity);
}

Примечание.

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

  1. В Обозреватель решений щелкните правой кнопкой мыши проект VideoEffectComponent и выберите "Свойства".
  2. Перейдите на вкладку Сборка.
  3. Установите флажок "Разрешить небезопасный код".

 

Теперь можно добавить реализацию метода ProcessFrame . Во-первых, этот метод получает объект BitmapBuffer как из входных, так и выходных растровых карт программного обеспечения. Обратите внимание, что выходной кадр открыт для записи и ввода для чтения. Затем для каждого буфера получается IMemoryBufferReference путем вызова CreateReference. Затем фактический буфер данных получается путем приведения объектов IMemoryBufferReference в качестве интерфейса взаимодействия COM, определенного выше, IMemoryByteAccess, а затем вызывая GetBuffer.

Теперь, когда буферы данных получены, можно считывать из входного буфера и записывать в выходной буфер. Макет буфера получается путем вызова GetPlaneDescription, который предоставляет сведения о ширине, шаге и начальном смещение буфера. Биты на пиксель определяются свойствами кодирования, заданными ранее с помощью метода SetEncodingProperties. Сведения о формате буфера используются для поиска индекса в буфер для каждого пикселя. Значение пикселя из исходного буфера копируется в целевой буфер, при этом значения цвета умножаются на свойство FadeValue, определенное для этого эффекта, чтобы свести их к определенному значению.

public unsafe void ProcessFrame(ProcessVideoFrameContext context)
{
    using (BitmapBuffer buffer = context.InputFrame.SoftwareBitmap.LockBuffer(BitmapBufferAccessMode.Read))
    using (BitmapBuffer targetBuffer = context.OutputFrame.SoftwareBitmap.LockBuffer(BitmapBufferAccessMode.Write))
    {
        using (var reference = buffer.CreateReference())
        using (var targetReference = targetBuffer.CreateReference())
        {
            byte* dataInBytes;
            uint capacity;
            ((IMemoryBufferByteAccess)reference).GetBuffer(out dataInBytes, out capacity);

            byte* targetDataInBytes;
            uint targetCapacity;
            ((IMemoryBufferByteAccess)targetReference).GetBuffer(out targetDataInBytes, out targetCapacity);

            var fadeValue = FadeValue;

            // Fill-in the BGRA plane
            BitmapPlaneDescription bufferLayout = buffer.GetPlaneDescription(0);
            for (int i = 0; i < bufferLayout.Height; i++)
            {
                for (int j = 0; j < bufferLayout.Width; j++)
                {

                    byte value = (byte)((float)j / bufferLayout.Width * 255);

                    int bytesPerPixel = 4; 
                    if (encodingProperties.Subtype != "ARGB32")
                    {
                        // If you support other encodings, adjust index into the buffer accordingly
                    }
                    

                    int idx = bufferLayout.StartIndex + bufferLayout.Stride * i + bytesPerPixel * j;

                    targetDataInBytes[idx + 0] = (byte)(fadeValue * (float)dataInBytes[idx + 0]);
                    targetDataInBytes[idx + 1] = (byte)(fadeValue * (float)dataInBytes[idx + 1]);
                    targetDataInBytes[idx + 2] = (byte)(fadeValue * (float)dataInBytes[idx + 2]);
                    targetDataInBytes[idx + 3] = dataInBytes[idx + 3];
                }
            }
        }
    }
}

Реализация интерфейса IBasicVideoEffect с помощью аппаратной обработки

Создание пользовательского эффекта видео с помощью аппаратной обработки (GPU) почти идентично использованию программной обработки, как описано выше. В этом разделе показано несколько различий в эффекте, использующего аппаратную обработку. В этом примере используется API среда выполнения Windows Win2D. Дополнительные сведения об использовании Win2D см. в документации по Win2D.

Выполните следующие действия, чтобы добавить пакет NuGet Win2D в созданный проект, как описано в разделе "Добавление пользовательского эффекта в приложение" в начале этой статьи.

Добавление пакета NuGet Win2D в проект эффекта

  1. В Обозреватель решений щелкните правой кнопкой мыши проект VideoEffectComponent и выберите пункт "Управление пакетами NuGet".
  2. В верхней части окна выберите вкладку "Обзор ".
  3. В поле поиска введите Win2D.
  4. Выберите Win2D.uwp и выберите " Установить " в правой области.
  5. В диалоговом окне "Изменения проверки" отображается установленный пакет. Щелкните OK.
  6. Примите лицензию на пакет.

Помимо пространств имен, включенных в базовую настройку проекта, необходимо включить следующие пространства имен, предоставляемые Win2D.

using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas;

Так как этот эффект будет использовать память GPU для работы с данными образа, необходимо вернуть MediaMemoryTypes.Gpu из свойства SupportedMemoryTypes.

public MediaMemoryTypes SupportedMemoryTypes { get { return MediaMemoryTypes.Gpu; } }

Задайте свойства кодирования, поддерживаемые свойством SupportedEncodingProperties . При работе с Win2D необходимо использовать кодировку ARGB32.

public IReadOnlyList<VideoEncodingProperties> SupportedEncodingProperties {
    get
    {
        var encodingProperties = new VideoEncodingProperties();
        encodingProperties.Subtype = "ARGB32";
        return new List<VideoEncodingProperties>() { encodingProperties };
    }
}

Используйте метод SetEncodingProperties для создания нового объекта Win2D CanvasDevice из IDirect3Device, переданного в метод.

private CanvasDevice canvasDevice;
public void SetEncodingProperties(VideoEncodingProperties encodingProperties, IDirect3DDevice device)
{
    canvasDevice = CanvasDevice.CreateFromDirect3D11Device(device);
}

Реализация SetProperties идентична предыдущему примеру обработки программного обеспечения. В этом примере используется свойство BlurAmount для настройки эффекта размытия Win2D.

private IPropertySet configuration;
public void SetProperties(IPropertySet configuration)
{
    this.configuration = configuration;
}
public double BlurAmount
{
    get
    {
        object val;
        if (configuration != null && configuration.TryGetValue("BlurAmount", out val))
        {
            return (double)val;
        }
        return 3;
    }
}

Последний шаг — реализовать метод ProcessFrame , который фактически обрабатывает данные изображения.

С помощью API Win2D создается canvasBitmap из свойства Direct3DSurface входного кадра. CanvasRenderTarget создается из выходного кадра Direct3DSurface, а canvasDrawingSession создается из этого целевого объекта отрисовки. Новое свойство Win2D GaussianBlurEffect инициализируется с помощью свойства BlurAmount, которое предоставляется через SetProperties. Наконец, метод CanvasDrawingSession.DrawImage вызывается для рисования входной растровой карты в целевой объект отрисовки с помощью эффекта размытия.

public void ProcessFrame(ProcessVideoFrameContext context)
{

    using (CanvasBitmap inputBitmap = CanvasBitmap.CreateFromDirect3D11Surface(canvasDevice, context.InputFrame.Direct3DSurface))
    using (CanvasRenderTarget renderTarget = CanvasRenderTarget.CreateFromDirect3D11Surface(canvasDevice, context.OutputFrame.Direct3DSurface))
    using (CanvasDrawingSession ds = renderTarget.CreateDrawingSession())
    {


        var gaussianBlurEffect = new GaussianBlurEffect
        {
            Source = inputBitmap,
            BlurAmount = (float)BlurAmount,
            Optimization = EffectOptimization.Speed
        };

        ds.DrawImage(gaussianBlurEffect);

    }
}

Добавление пользовательского эффекта в приложение

Чтобы использовать эффект видео из приложения, необходимо добавить ссылку на проект эффекта в приложение.

  1. В Обозреватель решений в проекте приложения щелкните правой кнопкой мыши ссылки и выберите "Добавить ссылку".
  2. Разверните вкладку "Проекты" , выберите "Решение" и установите флажок для имени проекта эффекта. В этом примере имя — VideoEffectComponent.
  3. Щелкните OK.

Добавление пользовательского эффекта в видеопоток камеры

Вы можете настроить простой поток предварительного просмотра с камеры, выполнив действия, описанные в статье Простой доступ к предварительной версии камеры. После выполнения этих действий вы получите инициализированный объект MediaCapture , используемый для доступа к видеопотоку камеры.

Чтобы добавить настраиваемый эффект видео в поток камеры, сначала создайте объект VideoEffectDefinition , передавая имя пространства имен и имени класса для эффекта. Затем вызовите метод AddVideoEffect объекта MediaCapture, чтобы добавить эффект в указанный поток. В этом примере используется значение MediaStreamType.VideoPreview , указывающее, что эффект следует добавить в поток предварительного просмотра. Если приложение поддерживает запись видео, можно также использовать MediaStreamType.VideoRecord для добавления эффекта в поток захвата. AddVideoEffect возвращает объект IMediaExtension, представляющий настраиваемый эффект. Вы можете использовать метод SetProperties, чтобы задать конфигурацию для эффекта.

После добавления эффекта вызывается StartPreviewAsync для запуска потока предварительной версии.

var videoEffectDefinition = new VideoEffectDefinition("VideoEffectComponent.ExampleVideoEffect");

IMediaExtension videoEffect =
   await mediaCapture.AddVideoEffectAsync(videoEffectDefinition, MediaStreamType.VideoPreview);

videoEffect.SetProperties(new PropertySet() { { "FadeValue", .25 } });

await mediaCapture.StartPreviewAsync();

Добавление пользовательского эффекта в клип в MediaComposition

Общие рекомендации по созданию композиций мультимедиа из клипов см. в разделе "Композиции мультимедиа" и "Редактирование". В следующем фрагменте кода показано создание простой композиции мультимедиа, которая использует настраиваемый эффект видео. Объект MediaClip создается путем вызова CreateFromFileAsync, передав видеофайла, выбранного пользователем с помощью FileOpenPicker, и клип добавляется в новое положение MediaComposition. Затем создается новый объект VideoEffectDefinition , передавая имя пространства имен и класса для эффекта конструктору. Наконец, определение эффекта добавляется в коллекцию VideoEffectDefinitions объекта MediaClip.

MediaComposition composition = new MediaComposition();
var clip = await MediaClip.CreateFromFileAsync(pickedFile);
composition.Clips.Add(clip);

var videoEffectDefinition = new VideoEffectDefinition("VideoEffectComponent.ExampleVideoEffect", new PropertySet() { { "FadeValue", .5 } });

clip.VideoEffectDefinitions.Add(videoEffectDefinition);