diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/DeviceEncodersTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/DeviceEncodersTest.java new file mode 100644 index 00000000..63151947 --- /dev/null +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/DeviceEncodersTest.java @@ -0,0 +1,217 @@ +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.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 { + + 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"); + } + + @Test + public void testGetDeviceEncoders() { + DeviceEncoders deviceEncoders = create(); + if (DeviceEncoders.ENABLED) { + List 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 allEncoders = deviceEncoders.getDeviceEncoders(); + MediaCodecInfo encoder = deviceEncoders.findDeviceEncoder(allEncoders, "video/avc"); + assertNotNull(encoder); + List 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(500, 500); + Size output = deviceEncoders.getSupportedVideoSize(input); + assertSame(input, output); + } + + @Test(expected = RuntimeException.class) + public void testGetSupportedVideoSize_hugeWidth() { + DeviceEncoders deviceEncoders = create(); + if (DeviceEncoders.ENABLED) { + Size input = new Size(Integer.MAX_VALUE, 500); + deviceEncoders.getSupportedVideoSize(input); + } else { + throw new RuntimeException("Test should pass."); + } + } + + @Test(expected = RuntimeException.class) + public void testGetSupportedVideoSize_hugeHeight() { + DeviceEncoders deviceEncoders = create(); + if (DeviceEncoders.ENABLED) { + Size input = new Size(500, Integer.MAX_VALUE); + deviceEncoders.getSupportedVideoSize(input); + } else { + throw new RuntimeException("Test should pass."); + } + } + + @Test + public void testGetSupportedVideoSize_alignsSize() { + DeviceEncoders deviceEncoders = create(); + if (DeviceEncoders.ENABLED) { + Size input = new Size(501, 501); + 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(new Size(1000, 1000), 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(new Size(1000, 1000)); + int output = deviceEncoders.getSupportedVideoFrameRate(inputSize, input); + assertNotEquals(input, output); + assertTrue(output >= 0); + } + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/DeviceEncoders.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/DeviceEncoders.java index caaed92a..d1eae5ad 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/DeviceEncoders.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/DeviceEncoders.java @@ -1,20 +1,17 @@ package com.otaliastudios.cameraview.internal; import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaCodecList; import android.os.Build; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; +import androidx.annotation.VisibleForTesting; import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.size.Size; -import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -29,7 +26,8 @@ public class DeviceEncoders { private final static String TAG = DeviceEncoders.class.getSimpleName(); private final static CameraLogger LOG = CameraLogger.create(TAG); - private final boolean mEnabled; + @VisibleForTesting static boolean ENABLED = Build.VERSION.SDK_INT >= 21; + @SuppressWarnings("FieldCanBeLocal") private final MediaCodecInfo mVideoEncoder; @SuppressWarnings("FieldCanBeLocal") @@ -39,10 +37,9 @@ public class DeviceEncoders { @SuppressLint("NewApi") public DeviceEncoders(@NonNull String videoType, @NonNull String audioType) { - mEnabled = Build.VERSION.SDK_INT >= 21; // 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 (mEnabled) { + if (ENABLED) { List encoders = getDeviceEncoders(); mVideoEncoder = findDeviceEncoder(encoders, videoType); LOG.i("Enabled. Found video encoder:", mVideoEncoder.getName()); @@ -63,9 +60,10 @@ public class DeviceEncoders { * Collects all the device encoders, which means excluding decoders. * @return encoders */ - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @NonNull - private List getDeviceEncoders() { + @SuppressLint("NewApi") + @VisibleForTesting + List getDeviceEncoders() { ArrayList results = new ArrayList<>(); MediaCodecInfo[] array = new MediaCodecList(MediaCodecList.REGULAR_CODECS).getCodecInfos(); for (MediaCodecInfo info : array) { @@ -82,8 +80,9 @@ public class DeviceEncoders { * @return true if hardware */ @SuppressLint("NewApi") - private boolean isHardwareEncoder(@NonNull MediaCodecInfo encoder) { - return !encoder.getName().startsWith("OMX.google"); + @VisibleForTesting + boolean isHardwareEncoder(@NonNull String encoder) { + return !encoder.startsWith("OMX.google"); } /** @@ -96,7 +95,8 @@ public class DeviceEncoders { */ @SuppressLint("NewApi") @NonNull - private MediaCodecInfo findDeviceEncoder(@NonNull List encoders, @NonNull String mimeType) { + @VisibleForTesting + MediaCodecInfo findDeviceEncoder(@NonNull List encoders, @NonNull String mimeType) { ArrayList results = new ArrayList<>(); for (MediaCodecInfo encoder : encoders) { String[] types = encoder.getSupportedTypes(); @@ -111,8 +111,8 @@ public class DeviceEncoders { Collections.sort(results, new Comparator() { @Override public int compare(MediaCodecInfo o1, MediaCodecInfo o2) { - boolean hw1 = isHardwareEncoder(o1); - boolean hw2 = isHardwareEncoder(o2); + boolean hw1 = isHardwareEncoder(o1.getName()); + boolean hw2 = isHardwareEncoder(o2.getName()); if (hw1 && hw2) return 0; if (hw1) return -1; if (hw2) return 1; @@ -135,7 +135,7 @@ public class DeviceEncoders { @SuppressLint("NewApi") @NonNull public Size getSupportedVideoSize(@NonNull Size size) { - if (!mEnabled) return size; + if (!ENABLED) return size; int width = size.getWidth(); int height = size.getHeight(); while (width % mVideoCapabilities.getWidthAlignment() != 0) width++; @@ -164,7 +164,7 @@ public class DeviceEncoders { */ @SuppressLint("NewApi") public int getSupportedVideoBitRate(int bitRate) { - if (!mEnabled) return bitRate; + if (!ENABLED) return bitRate; int newBitRate = mVideoCapabilities.getBitrateRange().clamp(bitRate); LOG.i("getSupportedVideoBitRate -", "inputRate:", bitRate, "adjustedRate:", newBitRate); return newBitRate; @@ -179,7 +179,7 @@ public class DeviceEncoders { */ @SuppressLint("NewApi") public int getSupportedVideoFrameRate(@NonNull Size size, int frameRate) { - if (!mEnabled) return frameRate; + if (!ENABLED) return frameRate; int newFrameRate = (int) (double) mVideoCapabilities .getSupportedFrameRatesFor(size.getWidth(), size.getHeight()) .clamp((double) frameRate); @@ -196,7 +196,7 @@ public class DeviceEncoders { */ @SuppressLint("NewApi") public int getSupportedAudioBitRate(int bitRate) { - if (!mEnabled) return bitRate; + if (!ENABLED) return bitRate; int newBitRate = mAudioCapabilities.getBitrateRange().clamp(bitRate); LOG.i("getSupportedAudioBitRate -", "inputRate:", bitRate, "adjustedRate:", newBitRate); return newBitRate;