pull/696/head
Mattia Iavarone 6 years ago
parent cbde71fd96
commit 658f91b4fa
  1. 2
      .github/workflows/emulator_script.sh
  2. 67
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/DeviceEncoders.java
  3. 32
      cameraview/src/main/java/com/otaliastudios/cameraview/video/FullVideoRecorder.java
  4. 35
      cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java
  5. 9
      cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/AudioConfig.java

@ -1,4 +1,4 @@
#!/usr/bin/env bash #!/usr/bin/env bash
adb logcat -c adb logcat -c
adb logcat CameraOrchestrator:I CameraEngine:I CameraView:I CameraCallbacks:I CameraIntegrationTest:I MediaEncoderEngine:I MediaEncoder:I AudioMediaEncoder:I VideoMediaEncoder:I TextureMediaEncoder:I VideoRecorder:I FullVideoRecorder:I SnapshotVideoRecorder:I MessageQueue:W WorkerHandler:I MPEG4Writer:I *:E -v color & adb logcat CameraOrchestrator:I CameraEngine:I CameraView:I CameraCallbacks:I CameraIntegrationTest:I MediaEncoderEngine:I MediaEncoder:I AudioMediaEncoder:I VideoMediaEncoder:I TextureMediaEncoder:I VideoRecorder:I FullVideoRecorder:I SnapshotVideoRecorder:I MessageQueue:W WorkerHandler:I DeviceEncoders:I MPEG4Writer:I *:E -v color &
./gradlew cameraview:connectedCheck ./gradlew cameraview:connectedCheck

@ -1,8 +1,11 @@
package com.otaliastudios.cameraview.internal; package com.otaliastudios.cameraview.internal;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.media.AudioFormat;
import android.media.MediaCodec;
import android.media.MediaCodecInfo; import android.media.MediaCodecInfo;
import android.media.MediaCodecList; import android.media.MediaCodecList;
import android.media.MediaFormat;
import android.os.Build; import android.os.Build;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -49,8 +52,9 @@ import java.util.List;
* - MediaCodecList (https://android.googlesource.com/platform/frameworks/av/+/master/media/libstagefright/MediaCodecList.cpp#322) * - 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 * 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. * that configures itself without errors. We offer this option through
* TODO add a tryConfigure() step, that throws AudioException/VideoException ? * {@link #tryConfigureVideo(String, Size, int, int)} and
* {@link #tryConfigureAudio(String, int, int, int)}.
* *
* 2. {@link #MODE_PREFER_HARDWARE} * 2. {@link #MODE_PREFER_HARDWARE}
* *
@ -350,4 +354,63 @@ public class DeviceEncoders {
} }
} }
@SuppressLint("NewApi")
public void tryConfigureVideo(@NonNull String mimeType,
@NonNull Size size,
int frameRate,
int bitRate) {
if (mVideoEncoder != null) {
MediaCodec codec = null;
try {
MediaFormat format = MediaFormat.createVideoFormat(mimeType, size.getWidth(),
size.getHeight());
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
codec = MediaCodec.createByCodecName(mVideoEncoder.getName());
codec.configure(format, null, null,
MediaCodec.CONFIGURE_FLAG_ENCODE);
} catch (Exception e) {
throw new VideoException("Failed to configure video codec: " + e.getMessage());
} finally {
if (codec != null) {
try {
codec.release();
} catch (Exception ignore) {}
}
}
}
}
@SuppressLint("NewApi")
public void tryConfigureAudio(@NonNull String mimeType,
int bitRate,
int sampleRate,
int channels) {
if (mAudioEncoder != null) {
MediaCodec codec = null;
try {
final MediaFormat format = MediaFormat.createAudioFormat(mimeType, sampleRate,
channels);
int channelMask = channels == 2 ? AudioFormat.CHANNEL_IN_STEREO
: AudioFormat.CHANNEL_IN_MONO;
format.setInteger(MediaFormat.KEY_CHANNEL_MASK, channelMask);
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
codec = MediaCodec.createByCodecName(mAudioEncoder.getName());
codec.configure(format, null, null,
MediaCodec.CONFIGURE_FLAG_ENCODE);
} catch (Exception e) {
throw new AudioException("Failed to configure video audio: " + e.getMessage());
} finally {
if (codec != null) {
try {
codec.release();
} catch (Exception ignore) {}
}
}
}
}
} }

@ -84,9 +84,15 @@ public abstract class FullVideoRecorder extends VideoRecorder {
// 2. Set the video and audio sources. // 2. Set the video and audio sources.
applyVideoSource(stub, mMediaRecorder); applyVideoSource(stub, mMediaRecorder);
boolean hasAudio = stub.audio == Audio.ON int audioChannels = 0;
|| stub.audio == Audio.MONO if (stub.audio == Audio.ON) {
|| stub.audio == Audio.STEREO; audioChannels = mProfile.audioChannels;
} else if (stub.audio == Audio.MONO) {
audioChannels = 1;
} else if (stub.audio == Audio.STEREO) {
audioChannels = 2;
}
boolean hasAudio = audioChannels > 0;
if (hasAudio) { if (hasAudio) {
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
} }
@ -156,13 +162,23 @@ public abstract class FullVideoRecorder extends VideoRecorder {
try { try {
newVideoSize = encoders.getSupportedVideoSize(stub.size); newVideoSize = encoders.getSupportedVideoSize(stub.size);
newVideoBitRate = encoders.getSupportedVideoBitRate(stub.videoBitRate); newVideoBitRate = encoders.getSupportedVideoBitRate(stub.videoBitRate);
newAudioBitRate = encoders.getSupportedAudioBitRate(stub.audioBitRate);
newVideoFrameRate = encoders.getSupportedVideoFrameRate(newVideoSize, newVideoFrameRate = encoders.getSupportedVideoFrameRate(newVideoSize,
stub.videoFrameRate); stub.videoFrameRate);
encoders.tryConfigureVideo(videoType, newVideoSize, newVideoFrameRate,
newVideoBitRate);
if (hasAudio) {
newAudioBitRate = encoders.getSupportedAudioBitRate(stub.audioBitRate);
encoders.tryConfigureAudio(audioType, newAudioBitRate,
mProfile.audioSampleRate, audioChannels);
}
encodersFound = true; encodersFound = true;
} catch (DeviceEncoders.VideoException videoException) { } catch (DeviceEncoders.VideoException videoException) {
LOG.i("prepareMediaRecorder:", "Got VideoException:",
videoException.getMessage());
videoEncoderOffset++; videoEncoderOffset++;
} catch (DeviceEncoders.AudioException audioException) { } catch (DeviceEncoders.AudioException audioException) {
LOG.i("prepareMediaRecorder:", "Got AudioException:",
audioException.getMessage());
audioEncoderOffset++; audioEncoderOffset++;
} }
} }
@ -185,13 +201,7 @@ public abstract class FullVideoRecorder extends VideoRecorder {
// 6B. Configure MediaRecorder from stub and from profile (audio) // 6B. Configure MediaRecorder from stub and from profile (audio)
if (hasAudio) { if (hasAudio) {
if (stub.audio == Audio.ON) { mMediaRecorder.setAudioChannels(audioChannels);
mMediaRecorder.setAudioChannels(mProfile.audioChannels);
} else if (stub.audio == Audio.MONO) {
mMediaRecorder.setAudioChannels(1);
} else if (stub.audio == Audio.STEREO) {
mMediaRecorder.setAudioChannels(2);
}
mMediaRecorder.setAudioSamplingRate(mProfile.audioSampleRate); mMediaRecorder.setAudioSamplingRate(mProfile.audioSampleRate);
mMediaRecorder.setAudioEncoder(mProfile.audioCodec); mMediaRecorder.setAudioEncoder(mProfile.audioCodec);
mMediaRecorder.setAudioEncodingBitRate(stub.audioBitRate); mMediaRecorder.setAudioEncodingBitRate(stub.audioBitRate);

@ -146,6 +146,19 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
case DEVICE_DEFAULT: videoType = "video/avc"; break; case DEVICE_DEFAULT: videoType = "video/avc"; break;
} }
String audioType = "audio/mp4a-latm"; String audioType = "audio/mp4a-latm";
TextureConfig videoConfig = new TextureConfig();
AudioConfig audioConfig = new AudioConfig();
// See if we have audio
int audioChannels = 0;
if (mResult.audio == Audio.ON) {
audioChannels = audioConfig.channels;
} else if (mResult.audio == Audio.MONO) {
audioChannels = 1;
} else if (mResult.audio == Audio.STEREO) {
audioChannels = 2;
}
boolean hasAudio = audioChannels > 0;
// Check the availability of values // Check the availability of values
Size newVideoSize = null; Size newVideoSize = null;
@ -157,18 +170,30 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
boolean encodersFound = false; boolean encodersFound = false;
DeviceEncoders deviceEncoders = null; DeviceEncoders deviceEncoders = null;
while (!encodersFound) { while (!encodersFound) {
LOG.i("Checking DeviceEncoders...",
"videoOffset:", videoEncoderOffset,
"audioOffset:", audioEncoderOffset);
deviceEncoders = new DeviceEncoders(DeviceEncoders.MODE_PREFER_HARDWARE, deviceEncoders = new DeviceEncoders(DeviceEncoders.MODE_PREFER_HARDWARE,
videoType, audioType, videoEncoderOffset, audioEncoderOffset); videoType, audioType, videoEncoderOffset, audioEncoderOffset);
try { try {
newVideoSize = deviceEncoders.getSupportedVideoSize(mResult.size); newVideoSize = deviceEncoders.getSupportedVideoSize(mResult.size);
newVideoBitRate = deviceEncoders.getSupportedVideoBitRate(mResult.videoBitRate); newVideoBitRate = deviceEncoders.getSupportedVideoBitRate(mResult.videoBitRate);
newAudioBitRate = deviceEncoders.getSupportedAudioBitRate(mResult.audioBitRate);
newVideoFrameRate = deviceEncoders.getSupportedVideoFrameRate(newVideoSize, newVideoFrameRate = deviceEncoders.getSupportedVideoFrameRate(newVideoSize,
mResult.videoFrameRate); mResult.videoFrameRate);
deviceEncoders.tryConfigureVideo(videoType, newVideoSize, newVideoFrameRate,
newVideoBitRate);
if (hasAudio) {
newAudioBitRate = deviceEncoders
.getSupportedAudioBitRate(mResult.audioBitRate);
deviceEncoders.tryConfigureAudio(audioType, newAudioBitRate,
audioConfig.samplingFrequency, audioChannels);
}
encodersFound = true; encodersFound = true;
} catch (DeviceEncoders.VideoException videoException) { } catch (DeviceEncoders.VideoException videoException) {
LOG.i("Got VideoException:", videoException.getMessage());
videoEncoderOffset++; videoEncoderOffset++;
} catch (DeviceEncoders.AudioException audioException) { } catch (DeviceEncoders.AudioException audioException) {
LOG.i("Got AudioException:", audioException.getMessage());
audioEncoderOffset++; audioEncoderOffset++;
} }
} }
@ -178,7 +203,6 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
mResult.videoFrameRate = newVideoFrameRate; mResult.videoFrameRate = newVideoFrameRate;
// Video // Video
TextureConfig videoConfig = new TextureConfig();
videoConfig.width = mResult.size.getWidth(); videoConfig.width = mResult.size.getWidth();
videoConfig.height = mResult.size.getHeight(); videoConfig.height = mResult.size.getHeight();
videoConfig.bitRate = mResult.videoBitRate; videoConfig.bitRate = mResult.videoBitRate;
@ -206,12 +230,9 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
// Audio // Audio
AudioMediaEncoder audioEncoder = null; AudioMediaEncoder audioEncoder = null;
if (mResult.audio == Audio.ON || mResult.audio == Audio.MONO if (hasAudio) {
|| mResult.audio == Audio.STEREO) {
AudioConfig audioConfig = new AudioConfig();
audioConfig.bitRate = mResult.audioBitRate; audioConfig.bitRate = mResult.audioBitRate;
if (mResult.audio == Audio.MONO) audioConfig.channels = 1; audioConfig.channels = audioChannels;
if (mResult.audio == Audio.STEREO) audioConfig.channels = 2;
audioConfig.encoder = deviceEncoders.getAudioEncoder(); audioConfig.encoder = deviceEncoders.getAudioEncoder();
audioEncoder = new AudioMediaEncoder(audioConfig); audioEncoder = new AudioMediaEncoder(audioConfig);
} }

@ -16,11 +16,11 @@ public class AudioConfig {
public int channels = 1; public int channels = 1;
public String encoder; public String encoder;
public String mimeType = "audio/mp4a-latm"; public String mimeType = "audio/mp4a-latm";
public int samplingFrequency = 44100; // samples/sec
// Not configurable options (for now) // Not configurable options (for now)
final int encoding = AudioFormat.ENCODING_PCM_16BIT; // Determines the sampleSizePerChannel final int encoding = AudioFormat.ENCODING_PCM_16BIT; // Determines the sampleSizePerChannel
// The 44.1KHz frequency is the only setting guaranteed to be available on all devices. // The 44.1KHz frequency is the only setting guaranteed to be available on all devices.
final int samplingFrequency = 44100; // samples/sec
// If sampleSizePerChannel changes, review noise introduction // If sampleSizePerChannel changes, review noise introduction
final int sampleSizePerChannel = 2; // byte/sample/channel [16bit]. final int sampleSizePerChannel = 2; // byte/sample/channel [16bit].
final int byteRatePerChannel = samplingFrequency * sampleSizePerChannel; // byte/sec/channel final int byteRatePerChannel = samplingFrequency * sampleSizePerChannel; // byte/sec/channel
@ -28,10 +28,11 @@ public class AudioConfig {
@NonNull @NonNull
AudioConfig copy() { AudioConfig copy() {
AudioConfig config = new AudioConfig(); AudioConfig config = new AudioConfig();
config.bitRate = this.bitRate; config.bitRate = bitRate;
config.channels = this.channels; config.channels = channels;
config.encoder = this.encoder; config.encoder = encoder;
config.mimeType = mimeType; config.mimeType = mimeType;
config.samplingFrequency = samplingFrequency;
return config; return config;
} }

Loading…
Cancel
Save