(Android-RTC-6) Analizar la fábrica de videos *** de createPeerConnectionFactory

Este capítulo comienza analizando los módulos relacionados con video en PeerConnectionFactory, no hay muchos, solo dos VideoEncoderFactory
 /VideoDecoderFactory.

Antes de comenzar, aquí hay una pregunta de entrevista que hago a menudo: ¿ Se utiliza la API relacionada con MediaCodec de Android para codificar o decodificar de forma rígida?

一、VideoDecoderFactory

// AppRTCDemo.PeerConnectionClient.java
private void createPeerConnectionFactoryInternal(PeerConnectionFactory.Options options) {
    // ... ...
    final AudioDeviceModule adm = createJavaAudioDevice();
    final boolean enableH264HighProfile =
            VIDEO_CODEC_H264_HIGH.equals(peerConnectionParameters.videoCodec);
    final VideoEncoderFactory encoderFactory;
    final VideoDecoderFactory decoderFactory;
    if (peerConnectionParameters.videoCodecHwAcceleration) {
        encoderFactory = new DefaultVideoEncoderFactory(
                rootEglBase.getEglBaseContext(), true, enableH264HighProfile);
        decoderFactory = new DefaultVideoDecoderFactory(rootEglBase.getEglBaseContext());
    } else {
        encoderFactory = new SoftwareVideoEncoderFactory();
        decoderFactory = new SoftwareVideoDecoderFactory();
    }
    factory = PeerConnectionFactory.builder()
            .setOptions(options)
            .setAudioDeviceModule(adm)
            .setVideoEncoderFactory(encoderFactory)
            .setVideoDecoderFactory(decoderFactory)
            .createPeerConnectionFactory();
    Log.d(TAG, "Peer connection factory created.");
    adm.release();
}

Método de entrada familiar, vemos que los modos de VideoEncoderFactory y VideoDecoderFactory son los mismos: DefaultVideo****Factory y SoftwareVideo****Factory se distinguen según el parámetro de señalización HwAcceleration. Comencemos el análisis con Decoder, la relación de herencia es la siguiente (haga clic para ampliar si no puede ver claramente):

Inesperadamente, hay dos implementaciones que heredan MediaCodecVideoDecoderFactory, HardwareVideoDecoderFactory / PlatformSoftwareVideoDecoderFactory. Por el método de denominación, sabemos que uno es un decodificador de hardware real y la otra plataforma implementa el decodificador. La diferencia entre los dos es que el Predicate <MediaCodecInfo> se pasa al superconstructor. Ver el código es en realidad un comparador y la prioridad se obtiene en función de MediaCodecInfo. (Puedes ver la lógica del código básico en la imagen)

Esto se remonta a la pregunta al principio del artículo: ¿ Se utiliza la API relacionada con MediaCodec de Android con codificación/decodificación rígida? la respuesta es negativa. MediaCodec en realidad se divide en implementación suave del sistema e implementación dura del fabricante. En el código de MediaCodecUtils.java se realizan dos tipos de juicios de decodificador.

// HardwareVideoDecoderFactory.Predicate<MediaCodecInfo> 
static boolean isHardwareAccelerated(MediaCodecInfo info) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        return isHardwareAcceleratedQOrHigher(info);
    }
    return !isSoftwareOnly(info);
}
@TargetApi(29)
private static boolean isHardwareAcceleratedQOrHigher(MediaCodecInfo codecInfo) {
    return codecInfo.isHardwareAccelerated();
}
// PlatformSoftwareVideoDecoderFactory.Predicate<MediaCodecInfo> 
static boolean isSoftwareOnly(MediaCodecInfo codecInfo) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        return isSoftwareOnlyQOrHigher(codecInfo);
    }
    String name = codecInfo.getName();
    for (String prefix : SOFTWARE_IMPLEMENTATION_PREFIXES) {
        if (name.startsWith(prefix)) {
            return true;
        }
    }
    return false;
}
@TargetApi(29)
private static boolean isSoftwareOnlyQOrHigher(MediaCodecInfo codecInfo) {
    return codecInfo.isSoftwareOnly();
}

static final String[] SOFTWARE_IMPLEMENTATION_PREFIXES = {
            "OMX.google.", "OMX.SEC.", "c2.android"};

Echemos un vistazo a la implementación del código de MediaCodecVideoDecoderFactory. Si explora los detalles a continuación, puede profundizar en isH264HighProfileSupported para determinar si se admite el perfil alto H264.

// MediaCodecVideoDecoderFactory.java
@Override
public VideoDecoder createDecoder(VideoCodecInfo codecType) {
    VideoCodecMimeType type = VideoCodecMimeType.valueOf(codecType.getName());
    MediaCodecInfo info = findCodecForType(type);
    if (info == null) {
        return null;
    }
    CodecCapabilities capabilities = info.getCapabilitiesForType(type.mimeType());
    return new AndroidVideoDecoder(new MediaCodecWrapperFactoryImpl(), info.getName(), type,
            MediaCodecUtils.selectColorFormat(MediaCodecUtils.DECODER_COLOR_FORMATS, capabilities),
            sharedContext);
}
@Override
public VideoCodecInfo[] getSupportedCodecs() {
    List<VideoCodecInfo> supportedCodecInfos = new ArrayList<VideoCodecInfo>();
    // Generate a list of supported codecs in order of preference:
    // VP8, VP9, H264 (high profile), and H264 (baseline profile).
    for (VideoCodecMimeType type : new VideoCodecMimeType[]{
            VideoCodecMimeType.VP8, VideoCodecMimeType.VP9, VideoCodecMimeType.H264}) {
        MediaCodecInfo codec = findCodecForType(type);
        if (codec != null) {
            String name = type.name();
            if (type == VideoCodecMimeType.H264 && isH264HighProfileSupported(codec)) {
                supportedCodecInfos.add(new VideoCodecInfo(
                        name, MediaCodecUtils.getCodecProperties(type, /* highProfile= */ true)));
            }
            supportedCodecInfos.add(new VideoCodecInfo(
                    name, MediaCodecUtils.getCodecProperties(type, /* highProfile= */ false)));
        }
    }
    return supportedCodecInfos.toArray(new VideoCodecInfo[supportedCodecInfos.size()]);
}

Después de determinar que se admite el perfil alto H264, se construye el VideoCodecInfo correspondiente y se llama a MediaCodecUtils.getCodecProperties. Hagamos un seguimiento y echemos un vistazo.

// MediaCodecUtils.java
static Map<String, String> getCodecProperties(VideoCodecMimeType type, boolean highProfile) {
    switch (type) {
        case VP8:
        case VP9:
            return new HashMap<String, String>();
        case H264:
            return H264Utils.getDefaultH264Params(highProfile);
        default:
            throw new IllegalArgumentException("Unsupported codec: " + type);
    }
}
// H264Utils.java
public static final String H264_FMTP_PROFILE_LEVEL_ID = "profile-level-id";
public static final String H264_FMTP_LEVEL_ASYMMETRY_ALLOWED = "level-asymmetry-allowed";
public static final String H264_FMTP_PACKETIZATION_MODE = "packetization-mode";
public static final String H264_PROFILE_CONSTRAINED_BASELINE = "42e0";
public static final String H264_PROFILE_CONSTRAINED_HIGH = "640c";
public static final String H264_LEVEL_3_1 = "1f"; // 31 in hex.
public static final String H264_CONSTRAINED_HIGH_3_1 =
        H264_PROFILE_CONSTRAINED_HIGH + H264_LEVEL_3_1;
public static final String H264_CONSTRAINED_BASELINE_3_1 =
        H264_PROFILE_CONSTRAINED_BASELINE + H264_LEVEL_3_1;

public static Map<String, String> getDefaultH264Params(boolean isHighProfile) {
    final Map<String, String> params = new HashMap<>();
    params.put(VideoCodecInfo.H264_FMTP_LEVEL_ASYMMETRY_ALLOWED, "1");
    params.put(VideoCodecInfo.H264_FMTP_PACKETIZATION_MODE, "1");
    params.put(VideoCodecInfo.H264_FMTP_PROFILE_LEVEL_ID,
            isHighProfile ? VideoCodecInfo.H264_CONSTRAINED_HIGH_3_1
                    : VideoCodecInfo.H264_CONSTRAINED_BASELINE_3_1);
    return params;
}

Aquí vemos los parámetros predeterminados de alto perfil h264. Es necesario popularizar el significado de los tres parámetros:

1.  ID de nivel de perfil

Profile-level-id es un número entero de 3 bytes representado en hexadecimal, dividido en 3 bytes en orden, y cada byte representa un significado diferente.

  • perfil_idc
  • perfil-iop: los primeros 6 bits son constraint_set0_flag, constraint_set1_flag, constraint_set2_flag, constraint_set3_flag, constraint_set4_flag, constraint_set5_flag, los dos últimos bits son bits reservados
  • nivel_idc
Perfil perfil_idc (hexadecimal) perfil-iop (binario)
CB 42 (B) x1xx0000
CB 4D (M) 1xxx0000
CB 58 (mi) 11xx0000
B 42 (B) x0xx0000
B 58 (mi) 10xx0000
METRO 4D (M) 0x0x0000
mi 58 00xx0000
h 64 00000000
H10 6E 00000000
H42 7A 00000000
H44 F4 00000000
H10I 6E 00010000
H42I 7A 00010000
H44I F4 00010000
C44I 2C 00010000

El significado del nombre del perfil específico es el siguiente:

CB: Perfil base restringido,
B: Perfil base,
M: Perfil principal,
E: Perfil extendido,
H: Perfil alto,
H10: Perfil alto 10,
H42: Perfil alto 4:2:2,
H44: Alto 4:4:4 Perfil predictivo,
H10I: Intra perfil alto 10,
H42I: Intra perfil alto 4:2:2,
H44I: Intra perfil alto 4:4:4,
C44I: CAVLC 4:4:4 Intra perfil

level_idc representa el valor del nivel. En el ejemplo, 0x1f == 31, que es el nivel 3.1.

Referencia de la tabla de niveles H264: https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels

2. modo de paquetización:
el modo de paquetización indica la forma en que se dividen y envían los paquetes de datos de imágenes.

0: NAL (Capa de abstracción de red) única, cada cuadro de datos de imagen se coloca en una unidad NAL para su transmisión;
1: No entrelazado, cada cuadro de datos de imagen se divide en múltiples unidades NAL para su transmisión y el orden de transmisión de Estas unidades NAL están de acuerdo con la decodificación 2: Intercalado
, cada cuadro de datos de imagen se divide en múltiples unidades NAL para su transmisión, pero no es necesario enviar el orden de transmisión de estas unidades NAL en el orden de decodificación. sólo
los fotogramas I se pueden dividir y enviar, y los fotogramas P. Ni los fotogramas B ni los fotogramas B se pueden dividir y enviar. Entonces, si modo de paquetización = 1, significa que la trama I se dividirá y enviará.

3.asimetría de nivel permitida:

nivel-asimetría-permitida indica si se permite que los niveles de codificación en ambos extremos sean inconsistentes. Tenga en cuenta que el valor debe ser 1 en el SDP en ambos extremos para que esto surta efecto.

Todo lo anterior se utiliza en realidad para analizar el protocolo H264 RTP en intercambios SDP entre un extremo y otro. Estudiaremos este aspecto en profundidad en el futuro.

En cuanto a SoftwareVideoDecoderFactory, es relativamente simple, podemos echarle un vistazo rápido.

@Override
public VideoDecoder createDecoder(VideoCodecInfo codecType) {
    if (codecType.getName().equalsIgnoreCase("VP8")) {
        return new LibvpxVp8Decoder();
    }
    if (codecType.getName().equalsIgnoreCase("VP9") && LibvpxVp9Decoder.nativeIsSupported()) {
        return new LibvpxVp9Decoder();
    }
    return null;
}
@Override
public VideoCodecInfo[] getSupportedCodecs() {
    return supportedCodecs();
}
static VideoCodecInfo[] supportedCodecs() {
    List<VideoCodecInfo> codecs = new ArrayList<VideoCodecInfo>();
    codecs.add(new VideoCodecInfo("VP8", new HashMap<>()));
    if (LibvpxVp9Decoder.nativeIsSupported()) {
        codecs.add(new VideoCodecInfo("VP9", new HashMap<>()));
    }
    return codecs.toArray(new VideoCodecInfo[codecs.size()]);
}

En cuanto a LibvpxVp8Decoder/LibvpxVp9Decoder/AndroidVideoDecoder, cada decodificador se analizará en un blog separado en el futuro. Ahora primero comprenda la composición general de la arquitectura; de lo contrario, si analiza el decodificador solo y no sabe cómo funciona, aún tendrá que regresar y analizar la composición de la arquitectura.

二, VideoEncoderFactory

A primera vista, el diagrama de herencia de VideoEncoderFactory es diferente de VideoDecoderFactory: falta una capa de MediaCodecVideoDecoderFactory. ¿Por qué el codificador de MediaCodec no distingue entre implementación suave e implementación dura? DefaultVideoEncoderFactory y SoftwareVideoEncoderFactory no son diferentes de la parte de decodificación. Con dudas, nos centramos en la implementación de HardwareVideoEncoderFactory.

La mayor diferencia es que no hay ningún predicado adicional. Siguiendo la idea, rápidamente nos fijamos en:

Información de MediaCodecInfo = findCodecForType(tipo);

Echemos un vistazo a la diferencia entre el método isSupportedCodec y el método isCodecAllowed (oculto un poco más):

// HardwareVideoEncoderFactory.java
private boolean isSupportedCodec(MediaCodecInfo info, VideoCodecMimeType type) {
    if (!MediaCodecUtils.codecSupportsType(info, type)) {
        return false;
    }
    // Check for a supported color format.
    if (MediaCodecUtils.selectColorFormat(
            MediaCodecUtils.ENCODER_COLOR_FORMATS, info.getCapabilitiesForType(type.mimeType()))
            == null) {
        return false;
    }
    return isHardwareSupportedInCurrentSdk(info, type) && isMediaCodecAllowed(info);
}
// MediaCodecVideoDecoderFactory.java
private boolean isSupportedCodec(MediaCodecInfo info, VideoCodecMimeType type) {
    String name = info.getName();
    if (!MediaCodecUtils.codecSupportsType(info, type)) {
        return false;
    }
    // Check for a supported color format.
    if (MediaCodecUtils.selectColorFormat(
            MediaCodecUtils.DECODER_COLOR_FORMATS, info.getCapabilitiesForType(type.mimeType()))
            == null) {
        return false;
    }
    return isCodecAllowed(info);
}

Preste atención a la diferencia entre los dos métodos DecoderFactory y EncoderFactory: obviamente hay más codificadores, isHardwareSupportedInCurrentSdk, y luego rastree el código.

// Returns true if the given MediaCodecInfo indicates a hardware module that is supported on the current SDK.
private boolean isHardwareSupportedInCurrentSdk(MediaCodecInfo info, VideoCodecMimeType type) {
    switch (type) {
        case VP8:
            return isHardwareSupportedInCurrentSdkVp8(info);
        case VP9:
            return isHardwareSupportedInCurrentSdkVp9(info);
        case H264:
            return isHardwareSupportedInCurrentSdkH264(info);
    }
    return false;
}
private boolean isHardwareSupportedInCurrentSdkVp8(MediaCodecInfo info) {
    String name = info.getName();
    // QCOM Vp8 encoder is supported in KITKAT or later.
    return (name.startsWith(QCOM_PREFIX) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
            // Exynos VP8 encoder is supported in M or later.
            || (name.startsWith(EXYNOS_PREFIX) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
            // Intel Vp8 encoder is supported in LOLLIPOP or later, with the intel encoder enabled.
            || (name.startsWith(INTEL_PREFIX) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
            && enableIntelVp8Encoder);
}
private boolean isHardwareSupportedInCurrentSdkVp9(MediaCodecInfo info) {
    String name = info.getName();
    return (name.startsWith(QCOM_PREFIX) || name.startsWith(EXYNOS_PREFIX))
            // Both QCOM and Exynos VP9 encoders are supported in N or later.
            && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
}
private boolean isHardwareSupportedInCurrentSdkH264(MediaCodecInfo info) {
    // First, H264 hardware might perform poorly on this model.
    if (H264_HW_EXCEPTION_MODELS.contains(Build.MODEL)) {
        return false;
    }
    String name = info.getName();
    // QCOM H264 encoder is supported in KITKAT or later.
    return (name.startsWith(QCOM_PREFIX) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
            // Exynos H264 encoder is supported in LOLLIPOP or later.
            || (name.startsWith(EXYNOS_PREFIX)
            && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP);
}
// List of devices with poor H.264 encoder quality.
// HW H.264 encoder on below devices has poor bitrate control - actual
// bitrates deviates a lot from the target value.
private static final List<String> H264_HW_EXCEPTION_MODELS =
        Arrays.asList("SAMSUNG-SGH-I337", "Nexus 7", "Nexus 4");

Descubrí cuidadosamente algo H264_HW_EXCEPTION_MODELS. De los comentarios, puedo aprender que el control de velocidad de bits del codificador de algunas máquinas no es preciso. Esto también puede considerarse como una lista negra de dispositivos. Por lo tanto, no podemos simplemente confiar en los comentarios de la API para determinar si se trata de una implementación dura o una implementación suave.

Los restantes LibvpxVp8Encoder/LibvpxVp9Encoder/HardwareVideoEncoder también se reservan para análisis por separado en el futuro.

3. Resumen

Este artículo parece no tener nada que decir. Principalmente analiza brevemente VideoEncoderFactory y VideoDecoderFactory pasados ​​​​por PeerConnectionFactory.builder y comprende la diferencia entre la implementación dura y la implementación suave de MediaCodec. Como dice el refrán, es beneficioso abrir el libro. . El análisis del códec específico se deja analizar después del proceso de creación de createPeerConnectionFactory.

Referencias:

https://blog.csdn.net/zqxf123456789/article/details/109696266

Supongo que te gusta

Origin blog.csdn.net/a360940265a/article/details/120275042
Recomendado
Clasificación