Encoders improvements (#545)
	
		
	
				
					
				
			* Create DeviceEncoders * Prefer hardware encoders, adjust size and bitrate * Do the same for video frame rate * Fix docs * Rename CameraView method * Fix DeviceEncoders comparator * Add tests * Fix tests * Fix tests again * Scale down based on encoder requirements * Add DeviceEncoders MODE_ values * Use DeviceEncoders for full videos as well * Fix small bugpull/564/head
							parent
							
								
									f48d1c17ba
								
							
						
					
					
						commit
						f2ea77ce79
					
				| @ -0,0 +1,224 @@ | ||||
| package com.otaliastudios.cameraview.internal; | ||||
| 
 | ||||
| 
 | ||||
| import android.media.MediaCodecInfo; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||
| import androidx.test.filters.MediumTest; | ||||
| import androidx.test.rule.ActivityTestRule; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.BaseTest; | ||||
| import com.otaliastudios.cameraview.TestActivity; | ||||
| import com.otaliastudios.cameraview.controls.Grid; | ||||
| import com.otaliastudios.cameraview.size.AspectRatio; | ||||
| import com.otaliastudios.cameraview.size.Size; | ||||
| 
 | ||||
| import org.junit.After; | ||||
| import org.junit.Before; | ||||
| import org.junit.Rule; | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertFalse; | ||||
| import static org.junit.Assert.assertNotEquals; | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| import static org.junit.Assert.assertNull; | ||||
| import static org.junit.Assert.assertSame; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| import static org.mockito.Mockito.mock; | ||||
| import static org.mockito.Mockito.when; | ||||
| 
 | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| @MediumTest | ||||
| public class DeviceEncodersTest extends BaseTest { | ||||
| 
 | ||||
|     // This is guaranteed to work, see
 | ||||
|     // https://developer.android.com/guide/topics/media/media-formats
 | ||||
|     private final static Size GUARANTEED_SIZE = new Size(176, 144); | ||||
| 
 | ||||
|     private boolean enabled; | ||||
| 
 | ||||
|     @Before | ||||
|     public void setUp() { | ||||
|         enabled = DeviceEncoders.ENABLED; | ||||
|     } | ||||
| 
 | ||||
|     @After | ||||
|     public void tearDown() { | ||||
|         DeviceEncoders.ENABLED = enabled; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private DeviceEncoders create() { | ||||
|         return new DeviceEncoders( | ||||
|                 "video/avc", | ||||
|                 "audio/mp4a-latm", | ||||
|                 DeviceEncoders.MODE_TAKE_FIRST); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetDeviceEncoders() { | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         if (DeviceEncoders.ENABLED) { | ||||
|             List<MediaCodecInfo> infos = deviceEncoders.getDeviceEncoders(); | ||||
|             for (MediaCodecInfo info : infos) { | ||||
|                 assertTrue(info.isEncoder()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testIsHardwareEncoder() { | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         if (DeviceEncoders.ENABLED) { | ||||
|             assertFalse(deviceEncoders.isHardwareEncoder("OMX.google.encoder")); | ||||
|             assertTrue(deviceEncoders.isHardwareEncoder("OMX.other.encoder")); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testFindDeviceEncoder() { | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         if (DeviceEncoders.ENABLED) { | ||||
|             List<MediaCodecInfo> allEncoders = deviceEncoders.getDeviceEncoders(); | ||||
|             MediaCodecInfo encoder = deviceEncoders.findDeviceEncoder(allEncoders, | ||||
|                     "video/avc", DeviceEncoders.MODE_TAKE_FIRST); | ||||
|             assertNotNull(encoder); | ||||
|             List<String> encoderTypes = Arrays.asList(encoder.getSupportedTypes()); | ||||
|             assertTrue(encoderTypes.contains("video/avc")); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetVideoEncoder() { | ||||
|         if (DeviceEncoders.ENABLED) { | ||||
|             DeviceEncoders deviceEncoders = create(); | ||||
|             assertNotNull(deviceEncoders.getVideoEncoder()); | ||||
|         } | ||||
| 
 | ||||
|         DeviceEncoders.ENABLED = false; | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         assertNull(deviceEncoders.getVideoEncoder()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetAudioEncoder() { | ||||
|         if (DeviceEncoders.ENABLED) { | ||||
|             DeviceEncoders deviceEncoders = create(); | ||||
|             assertNotNull(deviceEncoders.getAudioEncoder()); | ||||
|         } | ||||
| 
 | ||||
|         DeviceEncoders.ENABLED = false; | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         assertNull(deviceEncoders.getAudioEncoder()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetSupportedVideoSize_disabled() { | ||||
|         DeviceEncoders.ENABLED = false; | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         Size input = new Size(GUARANTEED_SIZE.getWidth(), GUARANTEED_SIZE.getHeight()); | ||||
|         Size output = deviceEncoders.getSupportedVideoSize(input); | ||||
|         assertSame(input, output); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetSupportedVideoSize_scalesDown() { | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         if (DeviceEncoders.ENABLED) { | ||||
|             Size input = new Size( | ||||
|                     GUARANTEED_SIZE.getWidth() * 1000, | ||||
|                     GUARANTEED_SIZE.getHeight() * 1000); | ||||
|             try { | ||||
|                 Size output = deviceEncoders.getSupportedVideoSize(input); | ||||
|                 assertTrue(AspectRatio.of(input).matches(output, 0.01F)); | ||||
|             } catch (RuntimeException e) { | ||||
|                 // The scaled down size happens to be not supported.
 | ||||
|                 // I see no way of testing this easily if we're not sure of supported ranges.
 | ||||
|                 // This depends highly on the alignment since scaling down, while keeping AR,
 | ||||
|                 // can change the alignment and require width / height changes.
 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetSupportedVideoSize_aligns() { | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         if (DeviceEncoders.ENABLED) { | ||||
|             Size input = new Size(GUARANTEED_SIZE.getWidth() + 1, | ||||
|                     GUARANTEED_SIZE.getHeight() + 1); | ||||
|             Size output = deviceEncoders.getSupportedVideoSize(input); | ||||
|             assertTrue(output.getWidth() <= input.getWidth()); | ||||
|             assertTrue(output.getHeight() <= input.getHeight()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetSupportedVideoBitRate_disabled() { | ||||
|         DeviceEncoders.ENABLED = false; | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         int input = 1000; | ||||
|         int output = deviceEncoders.getSupportedVideoBitRate(input); | ||||
|         assertEquals(input, output); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetSupportedVideoBitRate_enabled() { | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         if (DeviceEncoders.ENABLED) { | ||||
|             // Ensure it's clamped: we can pass a negative value and check it's >= 0.
 | ||||
|             int input = -1000; | ||||
|             int output = deviceEncoders.getSupportedVideoBitRate(input); | ||||
|             assertNotEquals(input, output); | ||||
|             assertTrue(output >= 0); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetSupportedAudioBitRate_disabled() { | ||||
|         DeviceEncoders.ENABLED = false; | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         int input = 1000; | ||||
|         int output = deviceEncoders.getSupportedAudioBitRate(input); | ||||
|         assertEquals(input, output); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetSupportedAudioBitRate_enabled() { | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         if (DeviceEncoders.ENABLED) { | ||||
|             // Ensure it's clamped: we can pass a negative value and check it's >= 0.
 | ||||
|             int input = -1000; | ||||
|             int output = deviceEncoders.getSupportedAudioBitRate(input); | ||||
|             assertNotEquals(input, output); | ||||
|             assertTrue(output >= 0); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetSupportedFrameRate_disabled() { | ||||
|         DeviceEncoders.ENABLED = false; | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         int input = 1000; | ||||
|         int output = deviceEncoders.getSupportedVideoFrameRate(GUARANTEED_SIZE, input); | ||||
|         assertEquals(input, output); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetSupportedFrameRate_enabled() { | ||||
|         DeviceEncoders deviceEncoders = create(); | ||||
|         if (DeviceEncoders.ENABLED) { | ||||
|             // Ensure it's clamped: we can pass a negative value and check it's >= 0.
 | ||||
|             int input = -10; | ||||
|             Size inputSize = deviceEncoders.getSupportedVideoSize(GUARANTEED_SIZE); | ||||
|             int output = deviceEncoders.getSupportedVideoFrameRate(inputSize, input); | ||||
|             assertNotEquals(input, output); | ||||
|             assertTrue(output >= 0); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,302 @@ | ||||
| package com.otaliastudios.cameraview.internal; | ||||
| 
 | ||||
| import android.annotation.SuppressLint; | ||||
| import android.media.MediaCodecInfo; | ||||
| import android.media.MediaCodecList; | ||||
| import android.os.Build; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.annotation.VisibleForTesting; | ||||
| 
 | ||||
| import com.otaliastudios.cameraview.CameraLogger; | ||||
| import com.otaliastudios.cameraview.size.Size; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.Comparator; | ||||
| import java.util.List; | ||||
| 
 | ||||
| /** | ||||
|  * Checks the capabilities of device encoders and adjust parameters to ensure | ||||
|  * that they'll be supported by the final encoder. | ||||
|  * This can choose the encoder in two ways, based on the mode flag: | ||||
|  * | ||||
|  * 1. {@link #MODE_TAKE_FIRST} | ||||
|  * | ||||
|  * Chooses the encoder as the first one that matches the given mime type. | ||||
|  * This is what {@link android.media.MediaCodec#createEncoderByType(String)} does, | ||||
|  * and what {@link android.media.MediaRecorder} also does when recording. | ||||
|  * | ||||
|  * The list is ordered based on the encoder definitions in system/etc/media_codecs.xml, | ||||
|  * as explained here: https://source.android.com/devices/media , for example.
 | ||||
|  * So taking the first means respecting the vendor priorities and should generally be | ||||
|  * a good idea. | ||||
|  * | ||||
|  * About {@link android.media.MediaRecorder}, we know it uses this option from here: | ||||
|  * https://stackoverflow.com/q/57479564/4288782 where all links to source code are shown.
 | ||||
|  * - StagefrightRecorder (https://android.googlesource.com/platform/frameworks/av/+/master/media/libmediaplayerservice/StagefrightRecorder.cpp#1782)
 | ||||
|  * - MediaCodecSource (https://android.googlesource.com/platform/frameworks/av/+/master/media/libstagefright/MediaCodecSource.cpp#515)
 | ||||
|  * - MediaCodecList (https://android.googlesource.com/platform/frameworks/av/+/master/media/libstagefright/MediaCodecList.cpp#322)
 | ||||
|  * | ||||
|  * To be fair, what {@link android.media.MediaRecorder} does is actually choose the first one | ||||
|  * that configures itself without errors. We currently do not offer this option here. TODO | ||||
|  * | ||||
|  * 2. {@link #MODE_PREFER_HARDWARE} | ||||
|  * | ||||
|  * This takes the list - as ordered by the vendor - and just sorts it such that hardware encoders | ||||
|  * are preferred over software ones. It's questionable whether this is good or not. Some vendors | ||||
|  * might forget to put hardware encoders first in the list, some others might put poor hardware | ||||
|  * encoders on the bottom of the list on purpose. | ||||
|  */ | ||||
| public class DeviceEncoders { | ||||
| 
 | ||||
|     private final static String TAG = DeviceEncoders.class.getSimpleName(); | ||||
|     private final static CameraLogger LOG = CameraLogger.create(TAG); | ||||
| 
 | ||||
|     @VisibleForTesting static boolean ENABLED = Build.VERSION.SDK_INT >= 21; | ||||
| 
 | ||||
|     public final static int MODE_TAKE_FIRST = 0; | ||||
|     public final static int MODE_PREFER_HARDWARE = 1; | ||||
| 
 | ||||
|     @SuppressWarnings("FieldCanBeLocal") | ||||
|     private final MediaCodecInfo mVideoEncoder; | ||||
|     @SuppressWarnings("FieldCanBeLocal") | ||||
|     private final MediaCodecInfo mAudioEncoder; | ||||
|     private final MediaCodecInfo.VideoCapabilities mVideoCapabilities; | ||||
|     private final MediaCodecInfo.AudioCapabilities mAudioCapabilities; | ||||
| 
 | ||||
|     @SuppressLint("NewApi") | ||||
|     public DeviceEncoders(@NonNull String videoType, @NonNull String audioType, int mode) { | ||||
|         // We could still get a list of MediaCodecInfo for API >= 16, but it seems that the APIs
 | ||||
|         // for querying the availability of a specified MediaFormat were only added in 21 anyway.
 | ||||
|         if (ENABLED) { | ||||
|             List<MediaCodecInfo> encoders = getDeviceEncoders(); | ||||
|             mVideoEncoder = findDeviceEncoder(encoders, videoType, mode); | ||||
|             LOG.i("Enabled. Found video encoder:", mVideoEncoder.getName()); | ||||
|             mAudioEncoder = findDeviceEncoder(encoders, audioType, mode); | ||||
|             LOG.i("Enabled. Found audio encoder:", mAudioEncoder.getName()); | ||||
|             mVideoCapabilities = mVideoEncoder.getCapabilitiesForType(videoType).getVideoCapabilities(); | ||||
|             mAudioCapabilities = mAudioEncoder.getCapabilitiesForType(audioType).getAudioCapabilities(); | ||||
|         } else { | ||||
|             mVideoEncoder = null; | ||||
|             mAudioEncoder = null; | ||||
|             mVideoCapabilities = null; | ||||
|             mAudioCapabilities = null; | ||||
|             LOG.i("Disabled."); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Collects all the device encoders, which means excluding decoders. | ||||
|      * @return encoders | ||||
|      */ | ||||
|     @NonNull | ||||
|     @SuppressLint("NewApi") | ||||
|     @VisibleForTesting | ||||
|     List<MediaCodecInfo> getDeviceEncoders() { | ||||
|         ArrayList<MediaCodecInfo> results = new ArrayList<>(); | ||||
|         MediaCodecInfo[] array = new MediaCodecList(MediaCodecList.REGULAR_CODECS).getCodecInfos(); | ||||
|         for (MediaCodecInfo info : array) { | ||||
|             if (info.isEncoder()) results.add(info); | ||||
|         } | ||||
|         return results; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Whether an encoder is a hardware encoder or not. We don't have an API to check this, | ||||
|      * but we can follow what libstagefright does: | ||||
|      * https://android.googlesource.com/platform/frameworks/av/+/master/media/libstagefright/MediaCodecList.cpp#293
 | ||||
|      * | ||||
|      * @param encoder encoder | ||||
|      * @return true if hardware | ||||
|      */ | ||||
|     @SuppressLint("NewApi") | ||||
|     @VisibleForTesting | ||||
|     boolean isHardwareEncoder(@NonNull String encoder) { | ||||
|         encoder = encoder.toLowerCase(); | ||||
|         boolean isSoftwareEncoder = encoder.startsWith("omx.google.") | ||||
|                 || encoder.startsWith("c2.android.") | ||||
|                 || (!encoder.startsWith("omx.") && !encoder.startsWith("c2.")); | ||||
|         return !isSoftwareEncoder; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Finds the encoder we'll be using, depending on the given mode flag: | ||||
|      * - {@link #MODE_TAKE_FIRST} will just take the first of the list | ||||
|      * - {@link #MODE_PREFER_HARDWARE} will prefer hardware encoders | ||||
|      * Throws if we find no encoder for this type. | ||||
|      * | ||||
|      * @param encoders encoders | ||||
|      * @param mimeType mime type | ||||
|      * @param mode mode | ||||
|      * @return encoder | ||||
|      */ | ||||
|     @SuppressLint("NewApi") | ||||
|     @NonNull | ||||
|     @VisibleForTesting | ||||
|     MediaCodecInfo findDeviceEncoder(@NonNull List<MediaCodecInfo> encoders, @NonNull String mimeType, int mode) { | ||||
|         ArrayList<MediaCodecInfo> results = new ArrayList<>(); | ||||
|         for (MediaCodecInfo encoder : encoders) { | ||||
|             String[] types = encoder.getSupportedTypes(); | ||||
|             for (String type : types) { | ||||
|                 if (type.equalsIgnoreCase(mimeType)) { | ||||
|                     results.add(encoder); | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         LOG.i("findDeviceEncoder -", "type:", mimeType, "encoders:", results.size()); | ||||
|         if (mode == MODE_PREFER_HARDWARE) { | ||||
|             Collections.sort(results, new Comparator<MediaCodecInfo>() { | ||||
|                 @Override | ||||
|                 public int compare(MediaCodecInfo o1, MediaCodecInfo o2) { | ||||
|                     boolean hw1 = isHardwareEncoder(o1.getName()); | ||||
|                     boolean hw2 = isHardwareEncoder(o2.getName()); | ||||
|                     if (hw1 && hw2) return 0; | ||||
|                     if (hw1) return -1; | ||||
|                     if (hw2) return 1; | ||||
|                     return 0; | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|         if (results.isEmpty()) { | ||||
|             throw new RuntimeException("No encoders for type:" + mimeType); | ||||
|         } | ||||
|         return results.get(0); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns a video size supported by the device encoders. | ||||
|      * Throws if input width or height are out of the supported boundaries. | ||||
|      * | ||||
|      * @param size input size | ||||
|      * @return adjusted size | ||||
|      */ | ||||
|     @SuppressLint("NewApi") | ||||
|     @NonNull | ||||
|     public Size getSupportedVideoSize(@NonNull Size size) { | ||||
|         if (!ENABLED) return size; | ||||
|         int width = size.getWidth(); | ||||
|         int height = size.getHeight(); | ||||
|         double aspect = (double) width / height; | ||||
| 
 | ||||
|         // If width is too large, scale down, but keep aspect ratio.
 | ||||
|         if (mVideoCapabilities.getSupportedWidths().getUpper() < width) { | ||||
|             width = mVideoCapabilities.getSupportedWidths().getUpper(); | ||||
|             height = (int) Math.round(width / aspect); | ||||
|         } | ||||
| 
 | ||||
|         // If height is too large, scale down, but keep aspect ratio.
 | ||||
|         if (mVideoCapabilities.getSupportedHeights().getUpper() < height) { | ||||
|             height = mVideoCapabilities.getSupportedHeights().getUpper(); | ||||
|             width = (int) Math.round(aspect * height); | ||||
|         } | ||||
| 
 | ||||
|         // Adjust the alignment.
 | ||||
|         while (width % mVideoCapabilities.getWidthAlignment() != 0) width--; | ||||
|         while (height % mVideoCapabilities.getHeightAlignment() != 0) height--; | ||||
| 
 | ||||
|         // It's still possible that we're BELOW the lower.
 | ||||
|         if (!mVideoCapabilities.getSupportedWidths().contains(width)) { | ||||
|             throw new RuntimeException("Width not supported after adjustment." + | ||||
|                     " Desired:" + width + | ||||
|                     " Range:" + mVideoCapabilities.getSupportedWidths()); | ||||
|         } | ||||
|         if (!mVideoCapabilities.getSupportedHeights().contains(height)) { | ||||
|             throw new RuntimeException("Height not supported after adjustment." + | ||||
|                     " Desired:" + height + | ||||
|                     " Range:" + mVideoCapabilities.getSupportedHeights()); | ||||
|         } | ||||
| 
 | ||||
|         // It's still possible that we're unsupported for other reasons.
 | ||||
|         if (!mVideoCapabilities.isSizeSupported(width, height)) { | ||||
|             throw new RuntimeException("Size not supported for unknown reason." + | ||||
|                     " Might be an aspect ratio issue." + | ||||
|                     " Desired size:" + new Size(width, height)); | ||||
|         } | ||||
|         Size adjusted = new Size(width, height); | ||||
|         LOG.i("getSupportedVideoSize -", "inputSize:", size, "adjustedSize:", adjusted); | ||||
|         return adjusted; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns a video bit rate supported by the device encoders. | ||||
|      * This means adjusting the input bit rate if needed, to match encoder constraints. | ||||
|      * | ||||
|      * @param bitRate input rate | ||||
|      * @return adjusted rate | ||||
|      */ | ||||
|     @SuppressLint("NewApi") | ||||
|     public int getSupportedVideoBitRate(int bitRate) { | ||||
|         if (!ENABLED) return bitRate; | ||||
|         int newBitRate = mVideoCapabilities.getBitrateRange().clamp(bitRate); | ||||
|         LOG.i("getSupportedVideoBitRate -", "inputRate:", bitRate, "adjustedRate:", newBitRate); | ||||
|         return newBitRate; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns a video frame rate supported by the device encoders. | ||||
|      * This means adjusting the input frame rate if needed, to match encoder constraints. | ||||
|      * | ||||
|      * @param frameRate input rate | ||||
|      * @return adjusted rate | ||||
|      */ | ||||
|     @SuppressLint("NewApi") | ||||
|     public int getSupportedVideoFrameRate(@NonNull Size size, int frameRate) { | ||||
|         if (!ENABLED) return frameRate; | ||||
|         int newFrameRate = (int) (double) mVideoCapabilities | ||||
|                 .getSupportedFrameRatesFor(size.getWidth(), size.getHeight()) | ||||
|                 .clamp((double) frameRate); | ||||
|         LOG.i("getSupportedVideoFrameRate -", "inputRate:", frameRate, "adjustedRate:", newFrameRate); | ||||
|         return newFrameRate; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns an audio bit rate supported by the device encoders. | ||||
|      * This means adjusting the input bit rate if needed, to match encoder constraints. | ||||
|      * | ||||
|      * @param bitRate input rate | ||||
|      * @return adjusted rate | ||||
|      */ | ||||
|     @SuppressLint("NewApi") | ||||
|     public int getSupportedAudioBitRate(int bitRate) { | ||||
|         if (!ENABLED) return bitRate; | ||||
|         int newBitRate = mAudioCapabilities.getBitrateRange().clamp(bitRate); | ||||
|         LOG.i("getSupportedAudioBitRate -", "inputRate:", bitRate, "adjustedRate:", newBitRate); | ||||
|         return newBitRate; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     // Won't do this for audio sample rate. As far as I remember, the value we're using,
 | ||||
|     // 44.1kHz, is guaranteed to be available, and it's not configurable.
 | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the name of the video encoder if we were able to determine one. | ||||
|      * @return encoder name | ||||
|      */ | ||||
|     @SuppressLint("NewApi") | ||||
|     @Nullable | ||||
|     public String getVideoEncoder() { | ||||
|         if (mVideoEncoder != null) { | ||||
|             return mVideoEncoder.getName(); | ||||
|         } else { | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the name of the audio encoder if we were able to determine one. | ||||
|      * @return encoder name | ||||
|      */ | ||||
|     @SuppressLint("NewApi") | ||||
|     @Nullable | ||||
|     public String getAudioEncoder() { | ||||
|         if (mAudioEncoder != null) { | ||||
|             return mAudioEncoder.getName(); | ||||
|         } else { | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
					Loading…
					
					
				
		Reference in new issue