Перейти к содержанию

Перехват и обработка декодированных кадров при помощи OpenCV

Overview

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

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

Building OpenCV

Рассмотрим сборку OpenCV 4.9.0 на Centos 7, чтобы обеспечить совместимость с glibc 2.17.

  1. Устанавливаем JDK 8 и ANT

    yum install openjdk-8-jdk ant
    

  2. Определяем местоположение каталога установки JDK и настраиваем переменную JAVA_HOME

    export JAVA_HOME=$(echo $(readlink -f $(which javac)) | sed 's/\/bin\/javac//g')
    

  3. Устанавливаем CMake 3.6.2 или новее

    wget https://cmake.org/files/v3.6/cmake-3.6.2.tar.gz
    tar -zxf cmake-3.6.2.tar.gz
    cd cmake-3.6.2
    ./bootstrap --prefix=/usr/local
    make -j$(nproc)
    make install
    

  4. Устанавливаем GCC 11 и переключаемся на него для сборки OpenCV

    yum install centos-release-scl
    yum install devtoolset-11-gcc devtoolset-11-gcc-c++
    scl enable devtoolset-11 -- bash
    

  5. Скачиваем и распаковываем исходные тексты OpenCV

    wget -O opencv-490.zip https://github.com/opencv/opencv/archive/4.9.0.zip
    unzip opencv-490.zip
    

  6. Настраиваем сборку

    cd opencv-490
    mkdir build
    cd build
    cmake ..
    

  7. Собираем OpenCV

    make -j $(nproc)
    

  8. Копируем собранные модули в каталог установки WCS

    cp bin/opencv-490.jar /usr/local/FlashphonerWebCallServer/lib/custom
    cp -r lib/* /usr/local/FlashphonerWebCallServer/lib/so
    

Реализация перехватчика с использованием OpenCV

Для перехвата декодированных кадров необходимо разработать класс на языке Java, реализующий интерфейс IDecodedFrameInterceptor. Функция этого класса frameDecoded() будет получать декодированные кадры в формате YUV I420, создавать OpenCV матрицы для каждого из массивов данных YUV420 и применять размытие

Реализация для транскодинга предыдущего поколения

transcoding_gen3=false

TestInterceptor.java

// Package name should be strictly defined as com.flashphoner.frameInterceptor
package com.flashphoner.frameInterceptor;

import com.flashphoner.sdk.media.IDecodedFrameInterceptor;
import com.flashphoner.sdk.media.YUVFrame;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;

import java.nio.ByteBuffer;

public class OpenCvInterceptor implements IDecodedFrameInterceptor {

   // Load OpenCv java wrapper
   static {
       System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
   }

   @Override
   public void frameDecoded(String streamName, YUVFrame frame) {
       ByteBuffer[] data = frame.getData();

       // WCS handles YUV pointers separately. Create Matrix for each YUV420 plane
       Mat yMatrix = new Mat(frame.getHeight(),        frame.getStrideY(),      CvType.CV_8UC1, data[YUVFrame.Y_POINTER_IDX]);
       Mat uMatrix = new Mat(frame.getChromaHeight(),  frame.getStrideChroma(), CvType.CV_8UC1, data[YUVFrame.U_POINTER_IDX]);
       Mat vMatrix = new Mat(frame.getChromaHeight(),  frame.getStrideChroma(), CvType.CV_8UC1, data[YUVFrame.V_POINTER_IDX]);

       Size gaussianKernelSize = new Size(33, 33);
       // Apply Gaussian Blue. Passed kernel size should be odd
       Imgproc.GaussianBlur(yMatrix, yMatrix, gaussianKernelSize, 0);
       Imgproc.GaussianBlur(uMatrix, uMatrix, gaussianKernelSize, 0);
       Imgproc.GaussianBlur(vMatrix, vMatrix, gaussianKernelSize, 0);
   }

}

Реализация для транскодинга 3 поколения

transcoding_gen3=true

TestInterceptor.java

// Package name should be strictly defined as com.flashphoner.frameInterceptor
package com.flashphoner.frameInterceptor;

import com.flashphoner.sdk.media.IDecodedFrameInterceptor;
import com.flashphoner.sdk.media.YUVFrame;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;

import java.nio.ByteBuffer;

public class OpenCvInterceptor implements IDecodedFrameInterceptor {

   // Load OpenCv java wrapper
   static {
       System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
   }

   @Override
   public void frameDecoded(String streamName, YUVFrame frame) {
       if (!frame.isNewGen()) {
           // skip only GEN3 supported
           return;
       }
       ByteBuffer[] data = frame.getData();

       // WCS handles YUV pointers separately. Create Matrix for each YUV420 plane
       Mat yMatrix = new Mat(frame.getHeight(),        frame.getStrideY(),      CvType.CV_8UC1, data[YUVFrame.Y_POINTER_IDX]);
       Mat uMatrix = new Mat(frame.getChromaHeight(),  frame.getStrideChroma(), CvType.CV_8UC1, data[YUVFrame.U_POINTER_IDX]);
       Mat vMatrix = new Mat(frame.getChromaHeight(),  frame.getStrideChroma(), CvType.CV_8UC1, data[YUVFrame.V_POINTER_IDX]);

       Size gaussianKernelSize = new Size(33, 33);
       // Apply Gaussian Blue. Passed kernel size should be odd
       Imgproc.GaussianBlur(yMatrix, yMatrix, gaussianKernelSize, 0);
       Imgproc.GaussianBlur(uMatrix, uMatrix, gaussianKernelSize, 0);
       Imgproc.GaussianBlur(vMatrix, vMatrix, gaussianKernelSize, 0);
   }

}

Сборка и использование примера

Затем следует скомпилировать класс в байт-код. Для этого создаем дерево каталогов, соответствующее названию пакета написанного класса

mkdir -p com/flashphoner/frameInterceptor

и выполняем команду

javac -cp /usr/local/FlashphonerWebCallServer/lib/wcs-core.jar:/usr/local/FlashphonerWebCallServer/lib/custom/opencv-490.jar ./com/flashphoner/frameInterceptor/TestInterceptor.java

Теперь упакуем скомпилированный код в jar-файл

jar -cf testinterceptor.jar ./com/flashphoner/frameInterceptor/TestInterceptor.class

и скопируем его в каталог, где размещены собственные Java библиотеки для интеграции с WCS

cp testinterceptor.jar /usr/local/FlashphonerWebCallServer/lib/custom

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

decoded_frame_interceptor=com.flashphoner.frameInterceptor.TestInterceptor

и перезапустить WCS.

Тестирование

  1. Опубликуйте поток в примере Two Way Streaming

  2. Проиграйте поток в примере Player с указанием разрешения, чтобы включился транскодинг, например
    https://test1.flashphoner.com:8444/client2/examples/demo/streaming/player/player.html?resolution=320x240,
    где test1.flashphoner.com адрес WCS сервера

Изображение будет размыто.