Improve encoder selection

pull/588/head
Mattia Iavarone 6 years ago
parent f668d8aae1
commit 470c53c2ba
  1. 7
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/DeviceEncodersTest.java
  2. 48
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/DeviceEncoders.java
  3. 33
      cameraview/src/main/java/com/otaliastudios/cameraview/video/FullVideoRecorder.java
  4. 34
      cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java

@ -6,11 +6,8 @@ import android.media.MediaCodecInfo;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest; import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
import com.otaliastudios.cameraview.BaseTest; 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.AspectRatio;
import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.Size;
@ -30,8 +27,6 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame; import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@MediumTest @MediumTest
@ -87,7 +82,7 @@ public class DeviceEncodersTest extends BaseTest {
if (DeviceEncoders.ENABLED) { if (DeviceEncoders.ENABLED) {
List<MediaCodecInfo> allEncoders = deviceEncoders.getDeviceEncoders(); List<MediaCodecInfo> allEncoders = deviceEncoders.getDeviceEncoders();
MediaCodecInfo encoder = deviceEncoders.findDeviceEncoder(allEncoders, MediaCodecInfo encoder = deviceEncoders.findDeviceEncoder(allEncoders,
"video/avc", DeviceEncoders.MODE_TAKE_FIRST); "video/avc", DeviceEncoders.MODE_TAKE_FIRST, 0);
assertNotNull(encoder); assertNotNull(encoder);
List<String> encoderTypes = Arrays.asList(encoder.getSupportedTypes()); List<String> encoderTypes = Arrays.asList(encoder.getSupportedTypes());
assertTrue(encoderTypes.contains("video/avc")); assertTrue(encoderTypes.contains("video/avc"));

@ -59,6 +59,26 @@ public class DeviceEncoders {
public final static int MODE_TAKE_FIRST = 0; public final static int MODE_TAKE_FIRST = 0;
public final static int MODE_PREFER_HARDWARE = 1; public final static int MODE_PREFER_HARDWARE = 1;
/**
* Exception thrown when trying to find appropriate values
* for a video encoder.
*/
public class VideoException extends RuntimeException {
private VideoException(@NonNull String message) {
super(message);
}
}
/**
* Exception thrown when trying to find appropriate values
* for an audio encoder. Currently never thrown.
*/
public class AudioException extends RuntimeException {
private AudioException(@NonNull String message) {
super(message);
}
}
@SuppressWarnings("FieldCanBeLocal") @SuppressWarnings("FieldCanBeLocal")
private final MediaCodecInfo mVideoEncoder; private final MediaCodecInfo mVideoEncoder;
@SuppressWarnings("FieldCanBeLocal") @SuppressWarnings("FieldCanBeLocal")
@ -68,13 +88,22 @@ public class DeviceEncoders {
@SuppressLint("NewApi") @SuppressLint("NewApi")
public DeviceEncoders(@NonNull String videoType, @NonNull String audioType, int mode) { public DeviceEncoders(@NonNull String videoType, @NonNull String audioType, int mode) {
this(videoType, audioType, mode, 0, 0);
}
@SuppressLint("NewApi")
public DeviceEncoders(@NonNull String videoType,
@NonNull String audioType,
int mode,
int videoOffset,
int audioOffset) {
// We could still get a list of MediaCodecInfo for API >= 16, but it seems that the APIs // 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. // for querying the availability of a specified MediaFormat were only added in 21 anyway.
if (ENABLED) { if (ENABLED) {
List<MediaCodecInfo> encoders = getDeviceEncoders(); List<MediaCodecInfo> encoders = getDeviceEncoders();
mVideoEncoder = findDeviceEncoder(encoders, videoType, mode); mVideoEncoder = findDeviceEncoder(encoders, videoType, mode, videoOffset);
LOG.i("Enabled. Found video encoder:", mVideoEncoder.getName()); LOG.i("Enabled. Found video encoder:", mVideoEncoder.getName());
mAudioEncoder = findDeviceEncoder(encoders, audioType, mode); mAudioEncoder = findDeviceEncoder(encoders, audioType, mode, audioOffset);
LOG.i("Enabled. Found audio encoder:", mAudioEncoder.getName()); LOG.i("Enabled. Found audio encoder:", mAudioEncoder.getName());
mVideoCapabilities = mVideoEncoder.getCapabilitiesForType(videoType) mVideoCapabilities = mVideoEncoder.getCapabilitiesForType(videoType)
.getVideoCapabilities(); .getVideoCapabilities();
@ -139,7 +168,8 @@ public class DeviceEncoders {
@VisibleForTesting @VisibleForTesting
MediaCodecInfo findDeviceEncoder(@NonNull List<MediaCodecInfo> encoders, MediaCodecInfo findDeviceEncoder(@NonNull List<MediaCodecInfo> encoders,
@NonNull String mimeType, @NonNull String mimeType,
int mode) { int mode,
int offset) {
ArrayList<MediaCodecInfo> results = new ArrayList<>(); ArrayList<MediaCodecInfo> results = new ArrayList<>();
for (MediaCodecInfo encoder : encoders) { for (MediaCodecInfo encoder : encoders) {
String[] types = encoder.getSupportedTypes(); String[] types = encoder.getSupportedTypes();
@ -164,10 +194,12 @@ public class DeviceEncoders {
} }
}); });
} }
if (results.isEmpty()) { if (results.size() < offset + 1) {
// This should not be a VideoException or AudioException - we want the process
// to crash here.
throw new RuntimeException("No encoders for type:" + mimeType); throw new RuntimeException("No encoders for type:" + mimeType);
} }
return results.get(0); return results.get(offset);
} }
/** /**
@ -203,19 +235,19 @@ public class DeviceEncoders {
// It's still possible that we're BELOW the lower. // It's still possible that we're BELOW the lower.
if (!mVideoCapabilities.getSupportedWidths().contains(width)) { if (!mVideoCapabilities.getSupportedWidths().contains(width)) {
throw new RuntimeException("Width not supported after adjustment." + throw new VideoException("Width not supported after adjustment." +
" Desired:" + width + " Desired:" + width +
" Range:" + mVideoCapabilities.getSupportedWidths()); " Range:" + mVideoCapabilities.getSupportedWidths());
} }
if (!mVideoCapabilities.getSupportedHeights().contains(height)) { if (!mVideoCapabilities.getSupportedHeights().contains(height)) {
throw new RuntimeException("Height not supported after adjustment." + throw new VideoException("Height not supported after adjustment." +
" Desired:" + height + " Desired:" + height +
" Range:" + mVideoCapabilities.getSupportedHeights()); " Range:" + mVideoCapabilities.getSupportedHeights());
} }
// It's still possible that we're unsupported for other reasons. // It's still possible that we're unsupported for other reasons.
if (!mVideoCapabilities.isSizeSupported(width, height)) { if (!mVideoCapabilities.isSizeSupported(width, height)) {
throw new RuntimeException("Size not supported for unknown reason." + throw new VideoException("Size not supported for unknown reason." +
" Might be an aspect ratio issue." + " Might be an aspect ratio issue." +
" Desired size:" + new Size(width, height)); " Desired size:" + new Size(width, height));
} }

@ -101,14 +101,35 @@ public abstract class FullVideoRecorder extends VideoRecorder {
} }
// Check DeviceEncoders support // Check DeviceEncoders support
DeviceEncoders encoders = new DeviceEncoders(videoType, audioType,
DeviceEncoders.MODE_TAKE_FIRST);
boolean flip = stub.rotation % 180 != 0; boolean flip = stub.rotation % 180 != 0;
if (flip) stub.size = stub.size.flip(); if (flip) stub.size = stub.size.flip();
stub.size = encoders.getSupportedVideoSize(stub.size); Size newVideoSize = null;
stub.videoBitRate = encoders.getSupportedVideoBitRate(stub.videoBitRate); int newVideoBitRate = 0;
stub.audioBitRate = encoders.getSupportedAudioBitRate(stub.audioBitRate); int newAudioBitRate = 0;
stub.videoFrameRate = encoders.getSupportedVideoFrameRate(stub.size, stub.videoFrameRate); int newVideoFrameRate = 0;
int videoEncoderOffset = 0;
int audioEncoderOffset = 0;
boolean encodersFound = false;
while (!encodersFound) {
DeviceEncoders encoders = new DeviceEncoders(videoType, audioType,
DeviceEncoders.MODE_TAKE_FIRST, videoEncoderOffset, audioEncoderOffset);
try {
newVideoSize = encoders.getSupportedVideoSize(stub.size);
newVideoBitRate = encoders.getSupportedVideoBitRate(stub.videoBitRate);
newAudioBitRate = encoders.getSupportedAudioBitRate(stub.audioBitRate);
newVideoFrameRate = encoders.getSupportedVideoFrameRate(newVideoSize,
stub.videoFrameRate);
encodersFound = true;
} catch (DeviceEncoders.VideoException videoException) {
videoEncoderOffset++;
} catch (DeviceEncoders.AudioException audioException) {
audioEncoderOffset++;
}
}
stub.size = newVideoSize;
stub.videoBitRate = newVideoBitRate;
stub.audioBitRate = newAudioBitRate;
stub.videoFrameRate = newVideoFrameRate;
if (flip) stub.size = stub.size.flip(); if (flip) stub.size = stub.size.flip();
// Set video params // Set video params

@ -136,13 +136,35 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
String audioType = "audio/mp4a-latm"; String audioType = "audio/mp4a-latm";
// Check the availability of values // Check the availability of values
DeviceEncoders deviceEncoders = new DeviceEncoders(videoType, audioType, Size newVideoSize = null;
DeviceEncoders.MODE_PREFER_HARDWARE); int newVideoBitRate = 0;
mResult.size = deviceEncoders.getSupportedVideoSize(mResult.size); int newAudioBitRate = 0;
mResult.videoBitRate = deviceEncoders.getSupportedVideoBitRate(mResult.videoBitRate); int newVideoFrameRate = 0;
mResult.audioBitRate = deviceEncoders.getSupportedAudioBitRate(mResult.audioBitRate); int videoEncoderOffset = 0;
mResult.videoFrameRate = deviceEncoders.getSupportedVideoFrameRate(mResult.size, int audioEncoderOffset = 0;
boolean encodersFound = false;
DeviceEncoders deviceEncoders = null;
while (!encodersFound) {
deviceEncoders = new DeviceEncoders(videoType, audioType,
DeviceEncoders.MODE_PREFER_HARDWARE, videoEncoderOffset,
audioEncoderOffset);
try {
newVideoSize = deviceEncoders.getSupportedVideoSize(mResult.size);
newVideoBitRate = deviceEncoders.getSupportedVideoBitRate(mResult.videoBitRate);
newAudioBitRate = deviceEncoders.getSupportedAudioBitRate(mResult.audioBitRate);
newVideoFrameRate = deviceEncoders.getSupportedVideoFrameRate(newVideoSize,
mResult.videoFrameRate); mResult.videoFrameRate);
encodersFound = true;
} catch (DeviceEncoders.VideoException videoException) {
videoEncoderOffset++;
} catch (DeviceEncoders.AudioException audioException) {
audioEncoderOffset++;
}
}
mResult.size = newVideoSize;
mResult.videoBitRate = newVideoBitRate;
mResult.audioBitRate = newAudioBitRate;
mResult.videoFrameRate = newVideoFrameRate;
// Video // Video
TextureConfig videoConfig = new TextureConfig(); TextureConfig videoConfig = new TextureConfig();

Loading…
Cancel
Save