peertube-live-streaming/encoder/src/main/java/com/pedro/encoder/utils/CodecUtil.java

442 lines
17 KiB
Java

/*
* Copyright (C) 2021 pedroSG94.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pedro.encoder.utils;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.os.Build;
import androidx.annotation.RequiresApi;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Created by pedro on 14/02/18.
*/
public class CodecUtil {
private static final String TAG = "CodecUtil";
public static final String H264_MIME = "video/avc";
public static final String H265_MIME = "video/hevc";
public static final String AAC_MIME = "audio/mp4a-latm";
public static final String VORBIS_MIME = "audio/ogg";
public static final String OPUS_MIME = "audio/opus";
public enum Force {
FIRST_COMPATIBLE_FOUND, SOFTWARE, HARDWARE
}
public static List<String> showAllCodecsInfo() {
List<MediaCodecInfo> mediaCodecInfoList = getAllCodecs(false);
List<String> infos = new ArrayList<>();
for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) {
StringBuilder info = new StringBuilder("----------------\n");
info.append("Name: ")
.append(mediaCodecInfo.getName())
.append("\n");
for (String type : mediaCodecInfo.getSupportedTypes()) {
info.append("Type: ")
.append(type)
.append("\n");
MediaCodecInfo.CodecCapabilities codecCapabilities =
mediaCodecInfo.getCapabilitiesForType(type);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
info.append("Max instances: ")
.append(codecCapabilities.getMaxSupportedInstances())
.append("\n");
}
if (mediaCodecInfo.isEncoder()) {
info.append("----- Encoder info -----\n");
MediaCodecInfo.EncoderCapabilities encoderCapabilities = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
encoderCapabilities = codecCapabilities.getEncoderCapabilities();
info.append("Complexity range: ")
.append(encoderCapabilities.getComplexityRange().getLower())
.append(" - ")
.append(encoderCapabilities.getComplexityRange().getUpper())
.append("\n");
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
info.append("Quality range: ")
.append(encoderCapabilities.getQualityRange().getLower())
.append(" - ")
.append(encoderCapabilities.getQualityRange().getUpper())
.append("\n");
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
info.append("CBR supported: ")
.append(encoderCapabilities.isBitrateModeSupported(MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR))
.append("\n")
.append("VBR supported: ")
.append(encoderCapabilities.isBitrateModeSupported(MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR))
.append("\n")
.append("CQ supported: ")
.append(encoderCapabilities.isBitrateModeSupported(MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ))
.append("\n");
}
info.append("----- -----\n");
} else {
info.append("----- Decoder info -----\n")
.append("----- -----\n");
}
if (codecCapabilities.colorFormats != null && codecCapabilities.colorFormats.length > 0) {
info.append("----- Video info -----\n")
.append("Supported colors: \n");
for (int color : codecCapabilities.colorFormats)
info.append(color)
.append("\n");
for (MediaCodecInfo.CodecProfileLevel profile : codecCapabilities.profileLevels)
info.append("Profile: ")
.append(profile.profile)
.append(", level: ")
.append(profile.level)
.append("\n");
MediaCodecInfo.VideoCapabilities videoCapabilities = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
videoCapabilities = codecCapabilities.getVideoCapabilities();
info.append("Bitrate range: ")
.append(videoCapabilities.getBitrateRange().getLower())
.append(" - ")
.append(videoCapabilities.getBitrateRange().getUpper())
.append("\n")
.append("Frame rate range: ")
.append(videoCapabilities.getSupportedFrameRates().getLower())
.append(" - ")
.append(videoCapabilities.getSupportedFrameRates().getUpper())
.append("\n")
.append("Width range: ")
.append(videoCapabilities.getSupportedWidths().getLower())
.append(" - ")
.append(videoCapabilities.getSupportedWidths().getUpper())
.append("\n")
.append("Height range: ")
.append(videoCapabilities.getSupportedHeights().getLower())
.append(" - ")
.append(videoCapabilities.getSupportedHeights().getUpper())
.append("\n");
}
info.append("----- -----\n");
} else {
info.append("----- Audio info -----\n");
for (MediaCodecInfo.CodecProfileLevel profile : codecCapabilities.profileLevels)
info.append("Profile: ")
.append(profile.profile)
.append(", level: ")
.append(profile.level)
.append("\n");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
MediaCodecInfo.AudioCapabilities audioCapabilities =
codecCapabilities.getAudioCapabilities();
info.append("Bitrate range: ")
.append(audioCapabilities.getBitrateRange().getLower())
.append(" - ")
.append(audioCapabilities.getBitrateRange().getUpper())
.append("\n")
.append("Channels supported: ")
.append(audioCapabilities.getMaxInputChannelCount())
.append("\n");
try {
if (audioCapabilities.getSupportedSampleRates() != null
&& audioCapabilities.getSupportedSampleRates().length > 0) {
info.append("Supported sample rate: \n");
for (int sr : audioCapabilities.getSupportedSampleRates())
info.append(sr)
.append("\n");
}
} catch (Exception ignored) { }
}
info.append("----- -----\n");
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
info.append("Max instances: ")
.append(codecCapabilities.getMaxSupportedInstances())
.append("\n");
}
}
info.append("----------------\n");
infos.add(info.toString());
}
return infos;
}
public static List<MediaCodecInfo> getAllCodecs(boolean filterBroken) {
List<MediaCodecInfo> mediaCodecInfoList = new ArrayList<>();
if (Build.VERSION.SDK_INT >= 21) {
MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
MediaCodecInfo[] mediaCodecInfos = mediaCodecList.getCodecInfos();
mediaCodecInfoList.addAll(Arrays.asList(mediaCodecInfos));
} else {
int count = MediaCodecList.getCodecCount();
for (int i = 0; i < count; i++) {
MediaCodecInfo mci = MediaCodecList.getCodecInfoAt(i);
mediaCodecInfoList.add(mci);
}
}
return filterBroken ? filterBrokenCodecs(mediaCodecInfoList) : mediaCodecInfoList;
}
public static List<MediaCodecInfo> getAllHardwareEncoders(String mime, boolean cbrPriority) {
List<MediaCodecInfo> mediaCodecInfoList = getAllEncoders(mime);
List<MediaCodecInfo> mediaCodecInfoHardware = new ArrayList<>();
List<MediaCodecInfo> mediaCodecInfoHardwareCBR = new ArrayList<>();
for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) {
if (isHardwareAccelerated(mediaCodecInfo)) {
mediaCodecInfoHardware.add(mediaCodecInfo);
if (cbrPriority &&Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
&& isCBRModeSupported(mediaCodecInfo, mime)) {
mediaCodecInfoHardwareCBR.add(mediaCodecInfo);
}
}
}
mediaCodecInfoHardware.removeAll(mediaCodecInfoHardwareCBR);
mediaCodecInfoHardware.addAll(0, mediaCodecInfoHardwareCBR);
return mediaCodecInfoHardware;
}
public static List<MediaCodecInfo> getAllHardwareEncoders(String mime) {
return getAllHardwareEncoders(mime, false);
}
public static List<MediaCodecInfo> getAllHardwareDecoders(String mime) {
List<MediaCodecInfo> mediaCodecInfoList = getAllDecoders(mime);
List<MediaCodecInfo> mediaCodecInfoHardware = new ArrayList<>();
for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) {
if (isHardwareAccelerated(mediaCodecInfo)) {
mediaCodecInfoHardware.add(mediaCodecInfo);
}
}
return mediaCodecInfoHardware;
}
public static List<MediaCodecInfo> getAllSoftwareEncoders(String mime, boolean cbrPriority) {
List<MediaCodecInfo> mediaCodecInfoList = getAllEncoders(mime);
List<MediaCodecInfo> mediaCodecInfoSoftware = new ArrayList<>();
List<MediaCodecInfo> mediaCodecInfoSoftwareCBR = new ArrayList<>();
for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) {
if (isSoftwareOnly(mediaCodecInfo)) {
mediaCodecInfoSoftware.add(mediaCodecInfo);
if (cbrPriority &&Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
&& isCBRModeSupported(mediaCodecInfo, mime)) {
mediaCodecInfoSoftwareCBR.add(mediaCodecInfo);
}
}
}
mediaCodecInfoSoftware.removeAll(mediaCodecInfoSoftwareCBR);
mediaCodecInfoSoftware.addAll(0, mediaCodecInfoSoftwareCBR);
return mediaCodecInfoSoftware;
}
public static List<MediaCodecInfo> getAllSoftwareEncoders(String mime) {
return getAllSoftwareEncoders(mime, false);
}
public static List<MediaCodecInfo> getAllSoftwareDecoders(String mime) {
List<MediaCodecInfo> mediaCodecInfoList = getAllDecoders(mime);
List<MediaCodecInfo> mediaCodecInfoSoftware = new ArrayList<>();
for (MediaCodecInfo mediaCodecInfo : mediaCodecInfoList) {
if (isSoftwareOnly(mediaCodecInfo)) {
mediaCodecInfoSoftware.add(mediaCodecInfo);
}
}
return mediaCodecInfoSoftware;
}
/**
* choose encoder by mime.
*/
public static List<MediaCodecInfo> getAllEncoders(String mime) {
List<MediaCodecInfo> mediaCodecInfoList = new ArrayList<>();
List<MediaCodecInfo> mediaCodecInfos = getAllCodecs(true);
for (MediaCodecInfo mci : mediaCodecInfos) {
if (!mci.isEncoder()) {
continue;
}
String[] types = mci.getSupportedTypes();
for (String type : types) {
if (type.equalsIgnoreCase(mime)) {
mediaCodecInfoList.add(mci);
}
}
}
return mediaCodecInfoList;
}
public static List<MediaCodecInfo> getAllEncoders(String mime, boolean hardwarePriority, boolean cbrPriority) {
List<MediaCodecInfo> mediaCodecInfoList = new ArrayList<>();
if (hardwarePriority) {
mediaCodecInfoList.addAll(getAllHardwareEncoders(mime, cbrPriority));
mediaCodecInfoList.addAll(getAllSoftwareEncoders(mime, cbrPriority));
} else {
mediaCodecInfoList.addAll(getAllEncoders(mime));
}
return mediaCodecInfoList;
}
public static List<MediaCodecInfo> getAllEncoders(String mime, boolean hardwarePriority) {
return getAllEncoders(mime, hardwarePriority, false);
}
/**
* choose decoder by mime.
*/
public static List<MediaCodecInfo> getAllDecoders(String mime) {
List<MediaCodecInfo> mediaCodecInfoList = new ArrayList<>();
List<MediaCodecInfo> mediaCodecInfos = getAllCodecs(true);
for (MediaCodecInfo mci : mediaCodecInfos) {
if (mci.isEncoder()) {
continue;
}
String[] types = mci.getSupportedTypes();
for (String type : types) {
if (type.equalsIgnoreCase(mime)) {
mediaCodecInfoList.add(mci);
}
}
}
return mediaCodecInfoList;
}
public static List<MediaCodecInfo> getAllDecoders(String mime, boolean hardwarePriority) {
List<MediaCodecInfo> mediaCodecInfoList = new ArrayList<>();
if (hardwarePriority) {
mediaCodecInfoList.addAll(getAllHardwareDecoders(mime));
mediaCodecInfoList.addAll(getAllSoftwareDecoders(mime));
} else {
mediaCodecInfoList.addAll(getAllDecoders(mime));
}
return mediaCodecInfoList;
}
/* Adapted from google/ExoPlayer
* https://github.com/google/ExoPlayer/commit/48555550d7fcf6953f2382466818c74092b26355
*/
private static boolean isHardwareAccelerated(MediaCodecInfo codecInfo) {
if (Build.VERSION.SDK_INT >= 29) {
return codecInfo.isHardwareAccelerated();
}
// codecInfo.isHardwareAccelerated() != codecInfo.isSoftwareOnly() is not necessarily true.
// However, we assume this to be true as an approximation.
return !isSoftwareOnly(codecInfo);
}
/* Adapted from google/ExoPlayer
* https://github.com/google/ExoPlayer/commit/48555550d7fcf6953f2382466818c74092b26355
*/
private static boolean isSoftwareOnly(MediaCodecInfo mediaCodecInfo) {
if (Build.VERSION.SDK_INT >= 29) {
//mediaCodecInfo.isSoftwareOnly() is not working on emulators.
//Use !mediaCodecInfo.isHardwareAccelerated() to make sure that all codecs are classified as software or hardware
return !mediaCodecInfo.isHardwareAccelerated();
}
String name = mediaCodecInfo.getName().toLowerCase();
if (name.startsWith("arc.")) { // App Runtime for Chrome (ARC) codecs
return false;
}
return name.startsWith("omx.google.")
|| name.startsWith("omx.ffmpeg.")
|| (name.startsWith("omx.sec.") && name.contains(".sw."))
|| name.equals("omx.qcom.video.decoder.hevcswvdec")
|| name.startsWith("c2.android.")
|| name.startsWith("c2.google.")
|| (!name.startsWith("omx.") && !name.startsWith("c2."));
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static boolean isCBRModeSupported(MediaCodecInfo mediaCodecInfo, String mime) {
MediaCodecInfo.CodecCapabilities codecCapabilities =
mediaCodecInfo.getCapabilitiesForType(mime);
MediaCodecInfo.EncoderCapabilities encoderCapabilities =
codecCapabilities.getEncoderCapabilities();
return encoderCapabilities.isBitrateModeSupported(
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR);
}
/**
* Filter broken codecs by name and device model.
*
* Note:
* There is no way to know broken encoders so we will check by name and device.
* Please add your encoder to this method if you detect one.
*
* @param codecs All device codecs
* @return a list without broken codecs
*/
private static List<MediaCodecInfo> filterBrokenCodecs(List<MediaCodecInfo> codecs) {
List<MediaCodecInfo> listFilter = new ArrayList<>();
List<MediaCodecInfo> listLowPriority = new ArrayList<>();
List<MediaCodecInfo> listUltraLowPriority = new ArrayList<>();
for (MediaCodecInfo mediaCodecInfo : codecs) {
if (isValid(mediaCodecInfo.getName())) {
CodecPriority priority = checkCodecPriority(mediaCodecInfo.getName());
switch (priority) {
case ULTRA_LOW:
listUltraLowPriority.add(mediaCodecInfo);
break;
case LOW:
listLowPriority.add(mediaCodecInfo);
break;
case NORMAL:
default:
listFilter.add(mediaCodecInfo);
break;
}
}
}
listFilter.addAll(listLowPriority);
listFilter.addAll(listUltraLowPriority);
return listFilter;
}
/**
* For now, none broken codec reported.
*/
private static boolean isValid(String name) {
//This encoder is invalid and produce errors (Only found in AVD API 16)
if (name.equalsIgnoreCase("aacencoder")) return false;
return true;
}
private enum CodecPriority {
NORMAL, LOW, ULTRA_LOW
}
/**
* Few devices have codecs that is not working properly in few cases like using AWS MediaLive or YouTube
* but it is still usable in most of cases.
* @return priority level.
*/
private static CodecPriority checkCodecPriority(String name) {
//maybe only broke on samsung with Android 12+ using YouTube and AWS MediaLive
// but set as ultra low priority in all cases.
if (name.equalsIgnoreCase("c2.sec.aac.encoder")) return CodecPriority.ULTRA_LOW;
//broke on few devices using YouTube and AWS MediaLive
else if (name.equalsIgnoreCase("omx.google.aac.encoder")) return CodecPriority.LOW;
else return CodecPriority.NORMAL;
}
}