Basado en WebRTC para realizar envíos de resolución de codificación personalizada

Si preguntas, ¿qué campo de la tecnología es el más candente en 2020? No hay duda: audio y video. En 2020, el fuerte desarrollo del teletrabajo y la educación en línea es inseparable del audio y el video. Las videoconferencias, la enseñanza en línea y el entretenimiento en vivo son escenarios típicos de aplicaciones para audio y video.
Los escenarios de uso más ricos requieren que consideremos cómo proporcionar elementos más configurables, como resolución, velocidad de fotogramas, velocidad de bits, etc., para lograr una mejor experiencia de usuario. Este artículo se centrará en la "resolución" para compartir de forma específica.

Cómo implementar una resolución de codificación personalizada

Veamos primero la definición de "resolución". Resolución: parámetro que mide la cantidad de datos de píxeles en una imagen y un indicador clave para medir la calidad de un cuadro de imagen o video. Cuanto mayor sea la resolución, mayor será el volumen de la imagen (número de bytes) y mejor será la calidad de la imagen. Para una transmisión de video con formato YUV i420 y resolución 1080p, el volumen de un cuadro de imagen es 1920x1080x1.5x8 / 1024 / 1024≈23.73Mbit, y la velocidad de fotogramas es 30, por lo que el tamaño de 1s es 30x23.73≈711.9Mbit . Se puede ver que la gran cantidad de datos requiere una alta tasa de bits, por lo que en el proceso de transmisión real, el video debe comprimirse y codificarse. Por lo tanto, la resolución de los datos originales recopilados por el dispositivo de captura de video se denomina resolución de adquisición, y la resolución de los datos realmente enviados al codificador se denomina resolución de codificación.
Si la imagen del video es clara y las proporciones apropiadas, esto afectará directamente la experiencia del usuario. La elección de la resolución de captura de la cámara es limitada y, a veces, la cámara no puede capturar directamente la resolución que queremos. Entonces, la capacidad de configurar la resolución de codificación adecuada según la escena es crucial. ¿Cómo convertir el video capturado a la resolución de codificación que queremos enviar? Esto es lo que compartimos principalmente hoy.
WebRTC es el potente proyecto de audio y video en tiempo real y de código abierto de Google. La mayoría de los desarrolladores del mercado crean soluciones de comunicación de audio y video en tiempo real basadas en WebRTC. Cada módulo en WebRTC tiene un buen proceso de desacoplamiento de abstracción, que es muy amigable para nuestro desarrollo secundario. Cuando creamos soluciones de comunicación de audio y video en tiempo real, necesitamos comprender y aprender las ideas de diseño y los módulos de código de WebRTC, y tener la capacidad de desarrollar y expandir. En este artículo, nos basamos en la versión 72 de WebRTC y hablamos sobre cómo implementar una resolución de codificación personalizada.
Primero, pensemos en las siguientes preguntas:
¿Cuál es el flujo de datos de video desde la recolección hasta la codificación y el envío?
¿Cómo seleccionar la resolución de adquisición adecuada según la resolución de codificación establecida?
¿Cómo puedo obtener la resolución de codificación deseada?
El contenido de este artículo también se compartirá a partir de los tres puntos anteriores.

El entorno de instalación y varios materiales de video se han preparado para todos, y los materiales se colocan en su propio grupo: 832218493 (debe recogerlo)

Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí

Canalización de datos de video

Primero, echemos un vistazo a la canalización de datos de video. Los datos de video son generados por VideoCapturer. Después de que VideoCapturer recopila datos, son procesados ​​por VideoAdapter y luego distribuidos a VideoSink registrado a través de VideoBroadcaster de VideoSource. VideoSink es el codificador Encoder Sink y el Preview Sink local.

Para la resolución de video, el proceso es: establecer la resolución deseada en VideoCapturer, VideoCapturer selecciona la resolución apropiada para capturar, y VideoAdapter calcula los datos de resolución de captura originales, y luego los escala y recorta para obtener la codificación después de que no cumpla con las expectativas. Los datos de video de la resolución se envían al codificador después de la codificación.

Inserte la descripción de la imagen aquí

Aquí hay dos preguntas clave:
¿Cómo elige VideoCapturer la resolución de captura adecuada?
¿Cómo convierte VideoAdapter la resolución de captura a la resolución de codificación?

Cómo elegir la resolución de adquisición adecuada

Selección de la resolución de captura
WebRTC abstrae una clase base para la captura de video: videocapturer.cc. Llamamos a la abstracción VideoCapturer. Establecemos los atributos de los parámetros en VideoCapturer, como resolución de video, velocidad de cuadros, formato de píxel admitido, etc., VideoCapturer lo hará De acuerdo con el configure los parámetros, calcule el mejor formato de recopilación y luego utilice este formato de recopilación para llamar al VDM (Módulo de dispositivo de video) de cada plataforma. La configuración específica es la siguiente: El
código se toma de src / media / base / videocapturer.h en WebRTC

VideoCapturer.h
bool GetBestCaptureFormat(const VideoFormat& desired, VideoFormat* best_format);//内部遍历设备支持的所有采集格式调用GetFormatDistance()计算出每个格式的distance,选出distance最小的那个格式
int64_t GetFormatDistance(const VideoFormat& desired, const VideoFormat& supported);//根据算法计算出设备支持的格式与我们想要的采集格式的差距,distance为0即刚好满足我们的设置
void SetSupportedFormats(const std::vector<VideoFormat>& formats);//设置采集设备支持的格式fps,resolution,NV12, I420,MJPEG等       

De acuerdo con los parámetros establecidos, a veces GetBestCaptureFormat () no puede obtener el formato de captura que está más en línea con nuestra configuración, porque diferentes dispositivos tienen diferentes capacidades de captura. La captura de cámara nativa de iOS, Android, PC, Mac y la captura de cámara USB externa admiten la resolución. es diferente, especialmente la capacidad de captura de la cámara USB externa es desigual. Por lo tanto, necesitamos ajustar ligeramente GetFormatDistance () para satisfacer nuestras necesidades. Hablemos sobre cómo ajustar el código para satisfacer nuestras necesidades.

Análisis del código fuente de la estrategia de selección

Primero analicemos el código fuente de GetFormatDistance () y extraigamos parte del código: el
código se toma de src / media / base / videocapturer.cc en WebRTC

// Get the distance between the supported and desired formats.
int64_t VideoCapturer::GetFormatDistance(const VideoFormat& desired,
                                         const VideoFormat& supported) {
    
    
  //....省略部分代码
  // Check resolution and fps.
  int desired_width = desired.width;//编码分辨率宽
  int desired_height = desired.height;//编码分辨率高
  int64_t delta_w = supported.width - desired_width;//宽的差
  
  float supported_fps = VideoFormat::IntervalToFpsFloat(supported.interval);//采集设备支持的帧率
  float delta_fps = supported_fps - VideoFormat::IntervalToFpsFloat(desired.interval);//帧率差
  int64_t aspect_h = desired_width
                         ? supported.width * desired_height / desired_width
                         : desired_height;//计算出设置的宽高比的高,采集设备的分辨率支持一般宽>高
  int64_t delta_h = supported.height - aspect_h;//高的差
  int64_t delta_fourcc;//设置的支持像素格式优先顺序,比如优先设置了NV12,同样分辨率和帧率的情况优先使用NV12格式采集
  
  //....省略部分降级策略代码,主要针对设备支持的分辨率和帧率不满足设置后的降级策略
  
  int64_t distance = 0;
  distance |=
      (delta_w << 28) | (delta_h << 16) | (delta_fps << 8) | delta_fourcc;

  return distance;
}

Nos centramos principalmente en el parámetro Distancia. La distancia es un concepto en WebRTC. Es la diferencia entre el formato de adquisición establecido y el formato de adquisición admitido por el dispositivo de acuerdo con una determinada estrategia de algoritmo. Cuanto menor es la diferencia, más cercano es el formato de adquisición admitido por el dispositivo al formato deseado , que es 0. Es decir, solo coincide.

La distancia se compone de cuatro partes: delta_w, delta_h, delta_fps, delta_fourcc, delta_w (ancho de resolución) tiene el peso más pesado, delta_h (resolución alta) segundo, delta_fps (velocidad de fotogramas) nuevamente, delta_fourcc (formato de píxel) último. El problema causado por esto es que la proporción de ancho es demasiado alta y la proporción de alto es demasiado baja para coincidir con la resolución admitida con mayor precisión.

Ejemplo:
Tomando el iPhone xs Max 800x800 fps: 10 como ejemplo, extraemos la distancia de algunos formatos de adquisición. El algoritmo GetFormatDistance () original no satisface la demanda. Lo que desea es 800x800. Puede ver en la figura siguiente que el El resultado es el mejor 960x540, no como se esperaba:

Supported NV12 192x144x10 distance 489635708928
Supported NV12 352x288x10 distance 360789835776
Supported NV12 480x360x10 distance 257721630720
Supported NV12 640x480x10 distance 128880476160
Supported NV12 960x540x10 distance 43032248320
Supported NV12 1024x768x10 distance 60179873792
Supported NV12 1280x720x10 distance 128959119360
Supported NV12 1440x1080x10 distance 171869470720
Supported NV12 1920x1080x10 distance 300812861440
Supported NV12 1920x1440x10 distance 300742082560
Supported NV12 3088x2316x10 distance 614332104704
Best NV12 960x540x10 distance 43032248320

Ajuste de la estrategia de selección

Para obtener la resolución que queremos, de acuerdo con nuestro análisis, necesitamos ajustar claramente el algoritmo GetFormatDisctance (), ajustar el peso de la resolución al más alto y la frecuencia de cuadros en segundo lugar. Si no se especifica el formato de píxel, el El formato de píxel es el último, luego modifique los detalles de la siguiente manera:

int64_t VideoCapturer::GetFormatDistance(const VideoFormat& desired,
const VideoFormat& supported) {
    
    
 //....省略部分代码
  // Check resolution and fps.
int desired_width = desired.width; //编码分辨率宽
int desired_height = desired.height; //编码分辨率高
  int64_t delta_w = supported.width - desired_width;
  int64_t delta_h = supported.height - desired_height;
  int64_t delta_fps = supported.framerate() - desired.framerate();
  distance = std::abs(delta_w) + std::abs(delta_h);
  //....省略降级策略, 比如设置了1080p,但是摄像采集设备最高支持720p,需要降级
  distance = (distance << 16 | std::abs(delta_fps) << 8 | delta_fourcc);
return distance;
}

Después de la modificación: la distancia consta de tres partes: resolución (delta_w + delta_h), velocidad de fotogramas delta_fps, píxel delta_fourcc, donde (delta_w + delta_h) tiene la proporción más alta, delta_fps en segundo lugar y delta_fourcc en último lugar.

Ejemplo:
Tome el iPhone xs Max 800x800 fps: 10 como ejemplo. Extraemos la Distancia de algunos formatos de adquisición. Después de la modificación de GetFormatDistance (), queremos 800x800, y el Best seleccionado es 1440x1080. Podemos obtener 800x800 haciendo zoom y De acuerdo con las expectativas (si los requisitos de resolución no son particularmente precisos, puede ajustar la estrategia de degradación y elegir 1024x768):

Supported NV12 192x144x10 distance 828375040
Supported NV12 352x288x10 distance 629145600
Supported NV12 480x360x10 distance 498073600
Supported NV12 640x480x10 distance 314572800
Supported NV12 960x540x10 distance 275251200
Supported NV12 1024x768x10 distance 167772160
Supported NV12 1280x720x10 distance 367001600
Supported NV12 1440x1080x10 distance 60293120
Supported NV12 1920x1080x10 distance 91750400
Supported NV12 1920x1440x10 distance 115343360
Supported NV12 3088x2316x10 distance 249298944
Best NV12 1440x1080x10 distance 60293120

Cómo lograr la resolución de adquisición a la resolución de codificación

Una vez que se recopilan los datos del video, VideoAdapter los procesará (resumen en WebRTC) y luego se distribuirán al Sink correspondiente (resumen en WebRTC). Hacemos un pequeño ajuste en el VideoAdapter para calcular los parámetros necesarios para escalar y recortar, y luego escalamos los datos de video con LibYUV y luego los recortamos a la resolución de codificación (para preservar la mayor cantidad de información de imagen posible, primero use escalado , la relación de aspecto es inconsistente al recortar la información de píxeles adicional). Aquí nos centramos en dos cuestiones:

Todavía use el ejemplo anterior, queremos que la resolución sea 800x800, pero la mejor resolución de captura que obtenemos es 1440x1080, entonces, ¿cómo obtener la resolución de codificación establecida 800x800 a partir de la resolución de captura 1440x1080?
Los datos de video serán procesados ​​por VideoAdapter durante el proceso de transmisión desde VideoCapture a VideoSink ¿Qué hace exactamente VideoAdapter?

Comencemos un análisis específico sobre estos dos temas, primero entendamos qué es VideoAdapter.

Introducción a VideoAdapter

Así es como se describe el VideoAdapter en WebRTC:

VideoAdapter adapts an input video frame to an output frame based on the specified input and output formats. The adaptation includes dropping frames to reduce frame rate and scaling frames.VideoAdapter is
thread safe.

Podemos entender que: VideoAdapter es un módulo para el control de entrada y salida de datos, que puede controlar la velocidad de fotogramas y la resolución y degradar la resolución en consecuencia. En el módulo de control de calidad de video VQC (Video Quality Control), a través de la configuración del VideoAdapter, la velocidad de fotogramas se puede reducir dinámicamente en condiciones de bajo ancho de banda y alta CPU, y la resolución se puede escalar dinámicamente para garantizar un video fluido Para mejorar la experiencia del usuario .
Tomado de src / media / base / videoadapter.h

VideoAdapter.h
bool AdaptFrameResolution(int in_width,
int in_height,
                            int64_t in_timestamp_ns,
int* cropped_width,
int* cropped_height,
int* out_width,
int* out_height);
void OnOutputFormatRequest(
const absl::optional<std::pair<int, int>>& target_aspect_ratio,
const absl::optional<int>& max_pixel_count,
const absl::optional<int>& max_fps);
void OnOutputFormatRequest(const absl::optional<VideoFormat>& format);

Análisis del código fuente de VideoAdapter

El VideoAdapter llama a AdaptFrameResolution () de acuerdo con el formato desried_format establecido para calcular los parámetros cropped_width, cropped_height, out_width, out_height que se deben escalar y recortar desde la resolución de captura a la resolución de codificación. El adaptFrameResolution nativo de WebRTC calcula los parámetros de zoom en función de los parámetros calculados área de píxeles y No se puede obtener el ancho y el alto precisos:
tomado de src / media / base / videoadapter.cc

bool VideoAdapter::AdaptFrameResolution(int in_width,
int in_height,
                                        int64_t in_timestamp_ns,
int* cropped_width,
int* cropped_height,
int* out_width,
int* out_height) {
    
    
//.....省略部分代码
// Calculate how the input should be cropped.
if (!target_aspect_ratio || target_aspect_ratio->first <= 0 ||
        target_aspect_ratio->second <= 0) {
    
    
      *cropped_width = in_width;
      *cropped_height = in_height;
    } else {
    
    
const float requested_aspect =
          target_aspect_ratio->first /
static_cast<float>(target_aspect_ratio->second);
      *cropped_width =
          std::min(in_width, static_cast<int>(in_height * requested_aspect));
      *cropped_height =
          std::min(in_height, static_cast<int>(in_width / requested_aspect));
    }
const Fraction scale;//vqc 缩放系数 ....省略代码
    // Calculate final output size.
    *out_width = *cropped_width / scale.denominator * scale.numerator;
    *out_height = *cropped_height / scale.denominator * scale.numerator;
 }

Ejemplo:
Tome iPhone xs Max 800x800 fps: 10 como ejemplo, establezca la resolución de codificación en 800x800 y la resolución de adquisición en 1440 x 1080. Según el algoritmo nativo, la nueva resolución calculada es 720x720, lo que no cumple con las expectativas.
Ajuste de
VideoAdapter VideoAdapter es una parte importante del ajuste de calidad de video en VQC (Módulo de control de calidad de video). La razón por la cual VQC puede completar el control de velocidad de cuadros, escalado de resolución y otras operaciones se basa principalmente en VideoAdapter, por lo que la modificación debe considerar el impacto en VQC .
Para obtener con precisión la resolución deseada sin afectar el control de resolución del módulo VQC, realizamos los siguientes ajustes en AdaptFrameResolution ():

bool VideoAdapter::AdaptFrameResolution(int in_width,
int in_height,
                                        int64_t in_timestamp_ns,
int* cropped_width,
int* cropped_height,
int* out_width,
int* out_height) {
    
    
  //....省略部分代码
bool in_more =
        (static_cast<float>(in_width) / static_cast<float>(in_height)) >=
        (static_cast<float>(desired_width_) /
static_cast<float>(desired_height_));
if (in_more) {
    
    
        *cropped_height = in_height;
        *cropped_width = *cropped_height * desired_width_ / desired_height_;
    } else {
    
    
      *cropped_width = in_width;
      *cropped_height = *cropped_width * desired_height_ / desired_width_;
    }
    *out_width = desired_width_;
    *out_height = desired_height_;
    //....省略部分代码
return true;
}

Ejemplo:
Tome el iPhone xs Max 800x800 fps: 10 como ejemplo, establezca la resolución de codificación en 800x800 y la resolución de adquisición en 1440x1080. De acuerdo con el algoritmo ajustado, la resolución de codificación calculada es 800x800, que está en línea con las expectativas.

para resumir

Este artículo presenta principalmente cómo realizar la configuración de resolución de codificación basada en WebRTC. Cuando queremos modificar la resolución de codificación de video, necesitamos comprender todo el proceso de recopilación, transmisión, procesamiento, codificación de datos de video, etc. Aquí hay algunos pasos clave para compartir hoy, cuando queremos implementar codificación personalizada al enviar la resolución :
Primero, establezca la resolución de codificación deseada;
modifique VideoCapturer.cc para seleccionar la resolución de adquisición apropiada de acuerdo con la resolución de codificación;
modifique VideoAdapter.cc para calcular la resolución de adquisición escalando y recortando a los parámetros de resolución de codificación;
use libyuv para escalar y recortar el los datos originales a la resolución de codificación de acuerdo con los parámetros de escalado y recorte;
luego envíe los nuevos datos al codificador para codificarlos y enviarlos;
finalmente, Listo.
De la misma forma, también podemos realizar otros ajustes basados ​​en esta idea. Lo anterior es toda la introducción de este artículo. Continuaremos compartiendo más implementaciones técnicas relacionadas con audio y video, y bienvenido a dejar un mensaje para intercambiar tecnologías relacionadas con nosotros.
Ha llegado la era 5G, y las áreas de aplicación de audio y video serán cada vez más amplias, y todo es prometedor.

Supongo que te gusta

Origin blog.csdn.net/lingshengxueyuan/article/details/112986451
Recomendado
Clasificación