Add DeviceEncoders MODE_ values

pull/545/head
Mattia Iavarone 6 years ago
parent d69a5e286a
commit ab9e09c3ac
  1. 8
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/DeviceEncodersTest.java
  2. 59
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/DeviceEncoders.java
  3. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java

@ -55,7 +55,10 @@ public class DeviceEncodersTest extends BaseTest {
@NonNull
private DeviceEncoders create() {
return new DeviceEncoders("video/avc", "audio/mp4a-latm");
return new DeviceEncoders(
"video/avc",
"audio/mp4a-latm",
DeviceEncoders.MODE_TAKE_FIRST);
}
@Test
@ -83,7 +86,8 @@ public class DeviceEncodersTest extends BaseTest {
DeviceEncoders deviceEncoders = create();
if (DeviceEncoders.ENABLED) {
List<MediaCodecInfo> allEncoders = deviceEncoders.getDeviceEncoders();
MediaCodecInfo encoder = deviceEncoders.findDeviceEncoder(allEncoders, "video/avc");
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"));

@ -20,6 +20,34 @@ 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 {
@ -28,6 +56,9 @@ public class DeviceEncoders {
@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")
@ -36,14 +67,14 @@ public class DeviceEncoders {
private final MediaCodecInfo.AudioCapabilities mAudioCapabilities;
@SuppressLint("NewApi")
public DeviceEncoders(@NonNull String videoType, @NonNull String audioType) {
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);
mVideoEncoder = findDeviceEncoder(encoders, videoType, mode);
LOG.i("Enabled. Found video encoder:", mVideoEncoder.getName());
mAudioEncoder = findDeviceEncoder(encoders, audioType);
mAudioEncoder = findDeviceEncoder(encoders, audioType, mode);
LOG.i("Enabled. Found audio encoder:", mAudioEncoder.getName());
mVideoCapabilities = mVideoEncoder.getCapabilitiesForType(videoType).getVideoCapabilities();
mAudioCapabilities = mAudioEncoder.getCapabilitiesForType(audioType).getAudioCapabilities();
@ -74,7 +105,8 @@ public class DeviceEncoders {
/**
* Whether an encoder is a hardware encoder or not. We don't have an API to check this,
* but it seems that we can assume that all OMX.google encoders are software encoders.
* 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
@ -82,21 +114,28 @@ public class DeviceEncoders {
@SuppressLint("NewApi")
@VisibleForTesting
boolean isHardwareEncoder(@NonNull String encoder) {
return !encoder.startsWith("OMX.google");
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.
* The way we do this is to list all possible encoders and prefer the hardware encoders
* over software encoders. Throws if we find no encoder for this type.
* 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) {
MediaCodecInfo findDeviceEncoder(@NonNull List<MediaCodecInfo> encoders, @NonNull String mimeType, int mode) {
ArrayList<MediaCodecInfo> results = new ArrayList<>();
for (MediaCodecInfo encoder : encoders) {
String[] types = encoder.getSupportedTypes();
@ -108,6 +147,7 @@ public class DeviceEncoders {
}
}
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) {
@ -119,6 +159,7 @@ public class DeviceEncoders {
return 0;
}
});
}
if (results.isEmpty()) {
throw new RuntimeException("No encoders for type:" + mimeType);
}

@ -132,7 +132,7 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
String audioType = "audio/mp4a-latm";
// Check the availability of values
DeviceEncoders deviceEncoders = new DeviceEncoders(videoType, audioType);
DeviceEncoders deviceEncoders = new DeviceEncoders(videoType, audioType, DeviceEncoders.MODE_PREFER_HARDWARE);
mResult.size = deviceEncoders.getSupportedVideoSize(mResult.size);
mResult.videoBitRate = deviceEncoders.getSupportedVideoBitRate(mResult.videoBitRate);
mResult.audioBitRate = deviceEncoders.getSupportedAudioBitRate(mResult.audioBitRate);

Loading…
Cancel
Save