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