From 21a14c4c3fe8d826f5b0fb8aca5ae02e51a3c548 Mon Sep 17 00:00:00 2001 From: HBiSoft Date: Tue, 20 Oct 2020 08:39:58 +0200 Subject: [PATCH] Updated HBRecorderCodecInfo to get more info --- .../hbrecorderexample/MainActivity.java | 44 ++ .../com/hbisoft/hbrecorder/HBRecorder.java | 14 + .../hbrecorder/HBRecorderCodecInfo.java | 401 ++++++++++++++++++ 3 files changed, 459 insertions(+) diff --git a/app/src/main/java/com/hbisoft/hbrecorderexample/MainActivity.java b/app/src/main/java/com/hbisoft/hbrecorderexample/MainActivity.java index e76d980..e47a3b3 100644 --- a/app/src/main/java/com/hbisoft/hbrecorderexample/MainActivity.java +++ b/app/src/main/java/com/hbisoft/hbrecorderexample/MainActivity.java @@ -36,13 +36,19 @@ import androidx.core.content.ContextCompat; import com.hbisoft.hbrecorder.HBRecorder; +import com.hbisoft.hbrecorder.HBRecorderCodecInfo; import com.hbisoft.hbrecorder.HBRecorderListener; import java.io.ByteArrayOutputStream; import java.io.File; import java.sql.Date; import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.HashMap; import java.util.Locale; +import java.util.Map; + +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; /** @@ -122,6 +128,44 @@ protected void onCreate(Bundle savedInstanceState) { } } + // Examples of how to use the HBRecorderCodecInfo class to get codec info + HBRecorderCodecInfo hbRecorderCodecInfo = new HBRecorderCodecInfo(); + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + int mWidth = hbRecorder.getDefaultWidth(); + int mHeight = hbRecorder.getDefaultHeight(); + String mMimeType = "video/avc"; + int mFPS = 30; + if (hbRecorderCodecInfo.isMimeTypeSupported(mMimeType)) { + String defaultVideoEncoder = hbRecorderCodecInfo.getDefaultVideoEncoderName(mMimeType); + boolean isSizeAndFramerateSupported = hbRecorderCodecInfo.isSizeAndFramerateSupported(mWidth, mHeight, mFPS, mMimeType, ORIENTATION_PORTRAIT); + Log.e("EXAMPLE", "THIS IS AN EXAMPLE OF HOW TO USE THE (HBRecorderCodecInfo) TO GET CODEC INFO:"); + Log.e("HBRecorderCodecInfo", "defaultVideoEncoder for (" + mMimeType + ") -> " + defaultVideoEncoder); + Log.e("HBRecorderCodecInfo", "MaxSupportedFrameRate -> " + hbRecorderCodecInfo.getMaxSupportedFrameRate(mWidth, mHeight, mMimeType)); + Log.e("HBRecorderCodecInfo", "MaxSupportedBitrate -> " + hbRecorderCodecInfo.getMaxSupportedBitrate(mMimeType)); + Log.e("HBRecorderCodecInfo", "isSizeAndFramerateSupported @ Width = "+mWidth+" Height = "+mHeight+" FPS = "+mFPS+" -> " + isSizeAndFramerateSupported); + Log.e("HBRecorderCodecInfo", "isSizeSupported @ Width = "+mWidth+" Height = "+mHeight+" -> " + hbRecorderCodecInfo.isSizeSupported(mWidth, mHeight, mMimeType)); + Log.e("HBRecorderCodecInfo", "Default Video Format = " + hbRecorderCodecInfo.getDefaultVideoFormat()); + + HashMap supportedVideoMimeTypes = hbRecorderCodecInfo.getSupportedVideoMimeTypes(); + for (Map.Entry entry : supportedVideoMimeTypes.entrySet()) { + Log.e("HBRecorderCodecInfo", "Supported VIDEO encoders and mime types : " + entry.getKey() + " -> " + entry.getValue()); + } + + HashMap supportedAudioMimeTypes = hbRecorderCodecInfo.getSupportedAudioMimeTypes(); + for (Map.Entry entry : supportedAudioMimeTypes.entrySet()) { + Log.e("HBRecorderCodecInfo", "Supported AUDIO encoders and mime types : " + entry.getKey() + " -> " + entry.getValue()); + } + + ArrayList supportedVideoFormats = hbRecorderCodecInfo.getSupportedVideoFormats(); + for (int j = 0; j < supportedVideoFormats.size(); j++) { + Log.e("HBRecorderCodecInfo", "Available Video Formats : " + supportedVideoFormats.get(j)); + } + }else{ + Log.e("HBRecorderCodecInfo", "MimeType not supported"); + } + + } + } //Create Folder diff --git a/hbrecorder/src/main/java/com/hbisoft/hbrecorder/HBRecorder.java b/hbrecorder/src/main/java/com/hbisoft/hbrecorder/HBRecorder.java index fd36343..690fb07 100644 --- a/hbrecorder/src/main/java/com/hbisoft/hbrecorder/HBRecorder.java +++ b/hbrecorder/src/main/java/com/hbisoft/hbrecorder/HBRecorder.java @@ -157,6 +157,20 @@ private void setScreenDensity() { mScreenDensity = metrics.densityDpi; } + //Get default width + public int getDefaultWidth(){ + HBRecorderCodecInfo hbRecorderCodecInfo = new HBRecorderCodecInfo(); + hbRecorderCodecInfo.setContext(context); + return hbRecorderCodecInfo.getMaxSupportedWidth(); + } + + //Get default height + public int getDefaultHeight(){ + HBRecorderCodecInfo hbRecorderCodecInfo = new HBRecorderCodecInfo(); + hbRecorderCodecInfo.setContext(context); + return hbRecorderCodecInfo.getMaxSupportedHeight(); + } + //Set Custom Dimensions (NOTE - YOUR DEVICE MIGHT NOT SUPPORT THE SIZE YOU PASS IT) public void setScreenDimensions(int heightInPX, int widthInPX){ mScreenHeight = heightInPX; diff --git a/hbrecorder/src/main/java/com/hbisoft/hbrecorder/HBRecorderCodecInfo.java b/hbrecorder/src/main/java/com/hbisoft/hbrecorder/HBRecorderCodecInfo.java index 3536d8b..e3f5231 100644 --- a/hbrecorder/src/main/java/com/hbisoft/hbrecorder/HBRecorderCodecInfo.java +++ b/hbrecorder/src/main/java/com/hbisoft/hbrecorder/HBRecorderCodecInfo.java @@ -3,11 +3,21 @@ import android.content.Context; import android.content.res.Configuration; import android.media.CamcorderProfile; +import android.media.MediaCodecInfo; +import android.media.MediaCodecList; +import android.os.Build; import android.util.DisplayMetrics; +import android.util.Range; import android.view.WindowManager; +import androidx.annotation.RequiresApi; + +import java.util.ArrayList; +import java.util.HashMap; + import static android.content.Context.WINDOW_SERVICE; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; /** * Created by HBiSoft on 13 Aug 2019 @@ -92,4 +102,395 @@ static RecordingInfo calculateRecordingInfo(int displayWidth, int displayHeight, } return new RecordingInfo(frameWidth, frameHeight, cameraFrameRate, displayDensity); } + + + // ALL PUBLIC METHODS + // This is for testing purposes only + + // Select the default video encoder + // This will only return the default video encoder, it does not mean that the encoder supports a particular set of parameters + public final MediaCodecInfo selectVideoCodec(final String mimeType) { + MediaCodecInfo result = null; + // get the list of available codecs + final int numCodecs = MediaCodecList.getCodecCount(); + for (int i = 0; i < numCodecs; i++) { + final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); + if (!codecInfo.isEncoder()) { // skipp decoder + continue; + } + String[] types = codecInfo.getSupportedTypes(); + for (String type : types) { + if (type.equalsIgnoreCase(mimeType)) { + return codecInfo; + } + } + } + return result; + } + + private String selectCodecByMime(String mimeType) { + int numCodecs = MediaCodecList.getCodecCount(); + for (int i = 0; i < numCodecs; i++) { + MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); + if (!codecInfo.isEncoder()) { + continue; + } + String[] types = codecInfo.getSupportedTypes(); + for (String type : types) { + if (type.equalsIgnoreCase(mimeType)) { + return codecInfo.getName(); + } + } + } + return "Mime not supported"; + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + private MediaCodecInfo selectDefaultCodec() { + MediaCodecInfo result = null; + // get the list of available codecs + final int numCodecs = MediaCodecList.getCodecCount(); + for (int i = 0; i < numCodecs; i++) { + final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); + if (!codecInfo.isEncoder()) { // skipp decoder + continue; + } + final String[] types = codecInfo.getSupportedTypes(); + for (int j = 0; j < types.length; j++) { + if (types[j].contains("video")){ + MediaCodecInfo.CodecCapabilities codecCapabilities = codecInfo.getCapabilitiesForType(types[j]); + boolean formatSup = codecCapabilities.isFormatSupported(codecCapabilities.getDefaultFormat()); + if (formatSup) { + return codecInfo; + } + } + } + } + return result; + } + + // Get the default video encoder name + // The default one will be returned first + public String getDefaultVideoEncoderName(String mimeType){ + String defaultEncoder = ""; + try { + defaultEncoder = selectCodecByMime(mimeType); + }catch (Exception e){ + e.printStackTrace(); + } + return defaultEncoder; + } + + // Get the default video format + // The default one will be returned first + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public String getDefaultVideoFormat(){ + String supported = ""; + try { + final MediaCodecInfo codecInfo = selectDefaultCodec(); + if (codecInfo != null) { + String[] types = codecInfo.getSupportedTypes(); + for (String type : types) { + if (type.contains("video")) { + MediaCodecInfo.CodecCapabilities codecCapabilities = codecInfo.getCapabilitiesForType(type); + String result = codecCapabilities.getDefaultFormat().toString(); + return returnTypeFromMime(result.substring(result.indexOf("=") + 1, result.indexOf(","))); + } + } + }else { + supported = "null"; + } + }catch (Exception e){ + e.printStackTrace(); + } + return supported; + } + + private String returnTypeFromMime(String mimeType){ + switch (mimeType) { + case "video/MP2T": + return "MPEG_2_TS"; + case "video/mp4v-es": + return "MPEG_4"; + case "video/mp4v": + return "MPEG_4"; + case "video/mp4": + return "MPEG_4"; + case "video/avc": + return "MPEG_4"; + case "video/3gpp": + return "THREE_GPP"; + case "video/webm": + return "WEBM"; + case "video/x-vnd.on2.vp8": + return "WEBM"; + } + return ""; + } + + // Example usage - isSizeAndFramerateSupported(hbrecorder.getWidth(), hbrecorder.getHeight(), 30, "video/mp4", ORIENTATION_PORTRAIT); + // int width - The width of the view to be recorder + // int height - The height of the view to be recorder + // String mimeType - for ex. video/mp4 + // int orientation - ORIENTATION_PORTRAIT or ORIENTATION_LANDSCAPE + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public boolean isSizeAndFramerateSupported(int width, int height, int fps, String mimeType, int orientation){ + boolean supported = false; + try { + final MediaCodecInfo codecInfo = selectVideoCodec(mimeType); + String[] types = codecInfo.getSupportedTypes(); + for (String type : types) { + if (type.contains("video")) { + MediaCodecInfo.CodecCapabilities codecCapabilities = codecInfo.getCapabilitiesForType(type); + MediaCodecInfo.VideoCapabilities videoCapabilities = codecCapabilities.getVideoCapabilities(); + + // Flip around the width and height in ORIENTATION_PORTRAIT because android's default orientation is ORIENTATION_LANDSCAPE + if (ORIENTATION_PORTRAIT == orientation) { + supported = videoCapabilities.areSizeAndRateSupported(height, width, fps); + }else { + supported = videoCapabilities.areSizeAndRateSupported(width, height, fps); + } + } + } + }catch (Exception e){ + e.printStackTrace(); + } + return supported; + } + + public boolean isMimeTypeSupported(String mimeType){ + try { + final MediaCodecInfo codecInfo = selectVideoCodec(mimeType); + String[] types = codecInfo.getSupportedTypes(); + for (String type : types) { + if (type.contains("video")) { + //ignore + } + } + }catch (Exception e){ + return false; + } + return true; + } + + // Check if a particular size is supported + // Provide width and height in Portrait mode, for example. isSizeSupported(1080, 1920, "video/mp4"); + // We do this because android's default orientation is landscape, so we have to flip around the width and height + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public boolean isSizeSupported(int width, int height, String mimeType){ + boolean supported = false; + try { + final MediaCodecInfo codecInfo = selectVideoCodec(mimeType); + String[] types = codecInfo.getSupportedTypes(); + for (String type : types) { + if (type.contains("video")) { + MediaCodecInfo.CodecCapabilities codecCapabilities = codecInfo.getCapabilitiesForType(type); + MediaCodecInfo.VideoCapabilities videoCapabilities = codecCapabilities.getVideoCapabilities(); + supported = videoCapabilities.isSizeSupported(height, width); + } + } + }catch (Exception e){ + e.printStackTrace(); + } + return supported; + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public double getMaxSupportedFrameRate(int width, int height, String mimeType){ + double maxFPS = 0; + try { + final MediaCodecInfo codecInfo = selectVideoCodec(mimeType); + String[] types = codecInfo.getSupportedTypes(); + for (String type : types) { + if (type.contains("video")) { + MediaCodecInfo.CodecCapabilities codecCapabilities = codecInfo.getCapabilitiesForType(type); + MediaCodecInfo.VideoCapabilities videoCapabilities = codecCapabilities.getVideoCapabilities(); + Range bit = videoCapabilities.getSupportedFrameRatesFor(height, width); + maxFPS = bit.getUpper(); + } + } + }catch (Exception e){ + e.printStackTrace(); + } + return maxFPS; + } + + // Get the max supported bitrate for a particular mime type + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public int getMaxSupportedBitrate(String mimeType){ + int bitrate = 0; + try { + final MediaCodecInfo codecInfo = selectVideoCodec(mimeType); + String[] types = codecInfo.getSupportedTypes(); + for (String type : types) { + if (type.contains("video")) { + MediaCodecInfo.CodecCapabilities codecCapabilities = codecInfo.getCapabilitiesForType(type); + MediaCodecInfo.VideoCapabilities videoCapabilities = codecCapabilities.getVideoCapabilities(); + Range bit = videoCapabilities.getBitrateRange(); + + bitrate = bit.getUpper(); + + } + } + }catch (Exception e){ + e.printStackTrace(); + } + return bitrate; + } + + // Get supported video formats + ArrayList supportedVideoFormats = new ArrayList<>(); + public ArrayList getSupportedVideoFormats(){ + String[] allFormats = {"video/MP2T", "video/mp4v-es", "video/m4v", "video/mp4", "video/avc", "video/3gpp", "video/webm", "video/x-vnd.on2.vp8"}; + + for (String allFormat : allFormats) { + checkSupportedVideoFormats(allFormat); + } + return supportedVideoFormats; + } + private void checkSupportedVideoFormats(String mimeType){ + // get the list of available codecs + final int numCodecs = MediaCodecList.getCodecCount(); + LOOP: + for (int i = 0; i < numCodecs; i++) { + final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); + if (!codecInfo.isEncoder()) { // skipp decoder + continue; + } + final String[] types = codecInfo.getSupportedTypes(); + for (int j = 0; j < types.length; j++) { + if (types[j].contains("video")){ + switch (mimeType) { + case "video/MP2T": + supportedVideoFormats.add("MPEG_2_TS"); + break LOOP; + case "video/mp4v-es": + if(!supportedVideoFormats.contains("MPEG_4")) { + supportedVideoFormats.add("MPEG_4"); + } + break LOOP; + case "video/mp4v": + if(!supportedVideoFormats.contains("MPEG_4")) { + supportedVideoFormats.add("MPEG_4"); + } + break LOOP; + case "video/mp4": + if(!supportedVideoFormats.contains("MPEG_4")) { + supportedVideoFormats.add("MPEG_4"); + } + break LOOP; + case "video/avc": + if(!supportedVideoFormats.contains("MPEG_4")) { + supportedVideoFormats.add("MPEG_4"); + } + break LOOP; + case "video/3gpp": + supportedVideoFormats.add("THREE_GPP"); + break LOOP; + + case "video/webm": + if(!supportedVideoFormats.contains("WEBM")) { + supportedVideoFormats.add("WEBM"); + } + break LOOP; + case "video/video/x-vnd.on2.vp8": + if(!supportedVideoFormats.contains("WEBM")) { + supportedVideoFormats.add("WEBM"); + } + break LOOP; + + } + } + } + } + } + + // Get supported audio formats + ArrayList supportedAudioFormats = new ArrayList<>(); + public ArrayList getSupportedAudioFormats(){ + String[] allFormats = {"audio/amr_nb", "audio/amr_wb", "audio/x-hx-aac-adts", "audio/ogg"}; + + for (String allFormat : allFormats) { + checkSupportedAudioFormats(allFormat); + } + return supportedAudioFormats; + } + private void checkSupportedAudioFormats(String mimeType){ + // get the list of available codecs + final int numCodecs = MediaCodecList.getCodecCount(); + LOOP: + for (int i = 0; i < numCodecs; i++) { + final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); + if (!codecInfo.isEncoder()) { // skipp decoder + continue; + } + final String[] types = codecInfo.getSupportedTypes(); + label: + for (int j = 0; j < types.length; j++) { + if (types[j].contains("audio")){ + switch (mimeType) { + case "audio/amr_nb": + supportedAudioFormats.add("AMR_NB"); + break LOOP; + case "audio/amr_wb": + supportedAudioFormats.add("AMR_WB"); + break LOOP; + case "audio/x-hx-aac-adts": + supportedAudioFormats.add("AAC_ADTS"); + break LOOP; + case "audio/ogg": + supportedAudioFormats.add("OGG"); + break LOOP; + } + } + } + } + } + + // Get supported video mime types + HashMap mVideoMap= new HashMap<>(); + public HashMap getSupportedVideoMimeTypes(){ + checkIfSupportedVideoMimeTypes(); + return mVideoMap; + } + private void checkIfSupportedVideoMimeTypes(){ + // get the list of available codecs + final int numCodecs = MediaCodecList.getCodecCount(); + for (int i = 0; i < numCodecs; i++) { + final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); + if (!codecInfo.isEncoder()) { // skipp decoder + continue; + } + final String[] types = codecInfo.getSupportedTypes(); + for (String type : types) { + if (type.contains("video")) { + mVideoMap.put(codecInfo.getName(), type); + } + } + } + } + + // Get supported audio mime types + HashMap mAudioMap= new HashMap<>(); + public HashMap getSupportedAudioMimeTypes(){ + checkIfSupportedAudioMimeTypes(); + return mAudioMap; + } + private void checkIfSupportedAudioMimeTypes(){ + // get the list of available codecs + final int numCodecs = MediaCodecList.getCodecCount(); + for (int i = 0; i < numCodecs; i++) { + final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); + if (!codecInfo.isEncoder()) { // skipp decoder + continue; + } + final String[] types = codecInfo.getSupportedTypes(); + for (String type : types) { + if (type.contains("audio")) { + mAudioMap.put(codecInfo.getName(), type); + } + } + } + } + }