mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Refactor AudioSamplePipeline configuration.
The encoder and sonic are now set up in the constructor rather than in a configuration method called from processData(). This is more similar to VideoSamplePipeline and reduces null checks. PiperOrigin-RevId: 420260526
This commit is contained in:
parent
6c6f91a3d9
commit
8f8cde661f
2 changed files with 38 additions and 74 deletions
|
|
@ -30,9 +30,6 @@ import com.google.android.exoplayer2.audio.AudioProcessor.AudioFormat;
|
||||||
import com.google.android.exoplayer2.audio.SonicAudioProcessor;
|
import com.google.android.exoplayer2.audio.SonicAudioProcessor;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
|
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|
||||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pipeline to decode audio samples, apply transformations on the raw samples, and re-encode them.
|
* Pipeline to decode audio samples, apply transformations on the raw samples, and re-encode them.
|
||||||
|
|
@ -42,9 +39,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
private static final String TAG = "AudioSamplePipeline";
|
private static final String TAG = "AudioSamplePipeline";
|
||||||
private static final int DEFAULT_ENCODER_BITRATE = 128 * 1024;
|
private static final int DEFAULT_ENCODER_BITRATE = 128 * 1024;
|
||||||
|
|
||||||
private final Format inputFormat;
|
|
||||||
private final TransformationRequest transformationRequest;
|
private final TransformationRequest transformationRequest;
|
||||||
private final Codec.EncoderFactory encoderFactory;
|
|
||||||
|
|
||||||
private final Codec decoder;
|
private final Codec decoder;
|
||||||
private final DecoderInputBuffer decoderInputBuffer;
|
private final DecoderInputBuffer decoderInputBuffer;
|
||||||
|
|
@ -52,11 +47,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
private final SonicAudioProcessor sonicAudioProcessor;
|
private final SonicAudioProcessor sonicAudioProcessor;
|
||||||
private final SpeedProvider speedProvider;
|
private final SpeedProvider speedProvider;
|
||||||
|
|
||||||
|
private final Codec encoder;
|
||||||
|
private final AudioFormat encoderInputAudioFormat;
|
||||||
private final DecoderInputBuffer encoderInputBuffer;
|
private final DecoderInputBuffer encoderInputBuffer;
|
||||||
private final DecoderInputBuffer encoderOutputBuffer;
|
private final DecoderInputBuffer encoderOutputBuffer;
|
||||||
|
|
||||||
private @MonotonicNonNull AudioFormat encoderInputAudioFormat;
|
|
||||||
private @MonotonicNonNull Codec encoder;
|
|
||||||
private long nextEncoderInputBufferTimeUs;
|
private long nextEncoderInputBufferTimeUs;
|
||||||
private long encoderBufferDurationRemainder;
|
private long encoderBufferDurationRemainder;
|
||||||
|
|
||||||
|
|
@ -70,20 +65,50 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
Codec.EncoderFactory encoderFactory,
|
Codec.EncoderFactory encoderFactory,
|
||||||
Codec.DecoderFactory decoderFactory)
|
Codec.DecoderFactory decoderFactory)
|
||||||
throws TransformationException {
|
throws TransformationException {
|
||||||
this.inputFormat = inputFormat;
|
|
||||||
this.transformationRequest = transformationRequest;
|
this.transformationRequest = transformationRequest;
|
||||||
this.encoderFactory = encoderFactory;
|
|
||||||
decoderInputBuffer =
|
decoderInputBuffer =
|
||||||
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
|
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
|
||||||
encoderInputBuffer =
|
encoderInputBuffer =
|
||||||
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
|
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
|
||||||
encoderOutputBuffer =
|
encoderOutputBuffer =
|
||||||
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
|
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
|
||||||
|
|
||||||
|
this.decoder = decoderFactory.createForAudioDecoding(inputFormat);
|
||||||
|
|
||||||
sonicAudioProcessor = new SonicAudioProcessor();
|
sonicAudioProcessor = new SonicAudioProcessor();
|
||||||
sonicOutputBuffer = AudioProcessor.EMPTY_BUFFER;
|
sonicOutputBuffer = AudioProcessor.EMPTY_BUFFER;
|
||||||
speedProvider = new SegmentSpeedProvider(inputFormat);
|
speedProvider = new SegmentSpeedProvider(inputFormat);
|
||||||
currentSpeed = speedProvider.getSpeed(0);
|
currentSpeed = speedProvider.getSpeed(0);
|
||||||
this.decoder = decoderFactory.createForAudioDecoding(inputFormat);
|
AudioFormat encoderInputAudioFormat =
|
||||||
|
new AudioFormat(
|
||||||
|
inputFormat.sampleRate,
|
||||||
|
inputFormat.channelCount,
|
||||||
|
// The decoder uses ENCODING_PCM_16BIT by default.
|
||||||
|
// https://developer.android.com/reference/android/media/MediaCodec#raw-audio-buffers
|
||||||
|
C.ENCODING_PCM_16BIT);
|
||||||
|
if (transformationRequest.flattenForSlowMotion) {
|
||||||
|
try {
|
||||||
|
encoderInputAudioFormat = sonicAudioProcessor.configure(encoderInputAudioFormat);
|
||||||
|
} catch (AudioProcessor.UnhandledAudioFormatException impossible) {
|
||||||
|
throw new IllegalStateException(impossible);
|
||||||
|
}
|
||||||
|
sonicAudioProcessor.setSpeed(currentSpeed);
|
||||||
|
sonicAudioProcessor.setPitch(currentSpeed);
|
||||||
|
sonicAudioProcessor.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder =
|
||||||
|
encoderFactory.createForAudioEncoding(
|
||||||
|
new Format.Builder()
|
||||||
|
.setSampleMimeType(
|
||||||
|
transformationRequest.audioMimeType == null
|
||||||
|
? inputFormat.sampleMimeType
|
||||||
|
: transformationRequest.audioMimeType)
|
||||||
|
.setSampleRate(encoderInputAudioFormat.sampleRate)
|
||||||
|
.setChannelCount(encoderInputAudioFormat.channelCount)
|
||||||
|
.setAverageBitrate(DEFAULT_ENCODER_BITRATE)
|
||||||
|
.build());
|
||||||
|
this.encoderInputAudioFormat = encoderInputAudioFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -98,10 +123,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean processData() throws TransformationException {
|
public boolean processData() {
|
||||||
if (!ensureEncoderAndAudioProcessingConfigured()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (sonicAudioProcessor.isActive()) {
|
if (sonicAudioProcessor.isActive()) {
|
||||||
return feedEncoderFromSonic() || feedSonicFromDecoder();
|
return feedEncoderFromSonic() || feedSonicFromDecoder();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -152,7 +174,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
* Attempts to pass decoder output data to the encoder, and returns whether it may be possible to
|
* Attempts to pass decoder output data to the encoder, and returns whether it may be possible to
|
||||||
* pass more data immediately by calling this method again.
|
* pass more data immediately by calling this method again.
|
||||||
*/
|
*/
|
||||||
@RequiresNonNull({"encoderInputAudioFormat", "encoder"})
|
|
||||||
private boolean feedEncoderFromDecoder() {
|
private boolean feedEncoderFromDecoder() {
|
||||||
if (!encoder.maybeDequeueInputBuffer(encoderInputBuffer)) {
|
if (!encoder.maybeDequeueInputBuffer(encoderInputBuffer)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -182,7 +203,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
* Attempts to pass audio processor output data to the encoder, and returns whether it may be
|
* Attempts to pass audio processor output data to the encoder, and returns whether it may be
|
||||||
* possible to pass more data immediately by calling this method again.
|
* possible to pass more data immediately by calling this method again.
|
||||||
*/
|
*/
|
||||||
@RequiresNonNull({"encoderInputAudioFormat", "encoder"})
|
|
||||||
private boolean feedEncoderFromSonic() {
|
private boolean feedEncoderFromSonic() {
|
||||||
if (!encoder.maybeDequeueInputBuffer(encoderInputBuffer)) {
|
if (!encoder.maybeDequeueInputBuffer(encoderInputBuffer)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -247,7 +267,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
* Feeds as much data as possible between the current position and limit of the specified {@link
|
* Feeds as much data as possible between the current position and limit of the specified {@link
|
||||||
* ByteBuffer} to the encoder, and advances its position by the number of bytes fed.
|
* ByteBuffer} to the encoder, and advances its position by the number of bytes fed.
|
||||||
*/
|
*/
|
||||||
@RequiresNonNull({"encoder", "encoderInputAudioFormat"})
|
|
||||||
private void feedEncoder(ByteBuffer inputBuffer) {
|
private void feedEncoder(ByteBuffer inputBuffer) {
|
||||||
ByteBuffer encoderInputBufferData = checkNotNull(encoderInputBuffer.data);
|
ByteBuffer encoderInputBufferData = checkNotNull(encoderInputBuffer.data);
|
||||||
int bufferLimit = inputBuffer.limit();
|
int bufferLimit = inputBuffer.limit();
|
||||||
|
|
@ -264,7 +283,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
encoder.queueInputBuffer(encoderInputBuffer);
|
encoder.queueInputBuffer(encoderInputBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresNonNull("encoder")
|
|
||||||
private void queueEndOfStreamToEncoder() {
|
private void queueEndOfStreamToEncoder() {
|
||||||
checkState(checkNotNull(encoderInputBuffer.data).position() == 0);
|
checkState(checkNotNull(encoderInputBuffer.data).position() == 0);
|
||||||
encoderInputBuffer.timeUs = nextEncoderInputBufferTimeUs;
|
encoderInputBuffer.timeUs = nextEncoderInputBufferTimeUs;
|
||||||
|
|
@ -274,53 +292,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
encoder.queueInputBuffer(encoderInputBuffer);
|
encoder.queueInputBuffer(encoderInputBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to configure the {@link #encoder} and Sonic (if applicable), if they have not been
|
|
||||||
* configured yet, and returns whether they have been configured.
|
|
||||||
*/
|
|
||||||
@EnsuresNonNullIf(
|
|
||||||
expression = {"encoder", "encoderInputAudioFormat"},
|
|
||||||
result = true)
|
|
||||||
private boolean ensureEncoderAndAudioProcessingConfigured() throws TransformationException {
|
|
||||||
if (encoder != null && encoderInputAudioFormat != null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@Nullable Format decoderOutputFormat = decoder.getOutputFormat();
|
|
||||||
if (decoderOutputFormat == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
AudioFormat outputAudioFormat =
|
|
||||||
new AudioFormat(
|
|
||||||
decoderOutputFormat.sampleRate,
|
|
||||||
decoderOutputFormat.channelCount,
|
|
||||||
decoderOutputFormat.pcmEncoding);
|
|
||||||
if (transformationRequest.flattenForSlowMotion) {
|
|
||||||
try {
|
|
||||||
outputAudioFormat = sonicAudioProcessor.configure(outputAudioFormat);
|
|
||||||
flushSonicAndSetSpeed(currentSpeed);
|
|
||||||
} catch (AudioProcessor.UnhandledAudioFormatException e) {
|
|
||||||
throw TransformationException.createForAudioProcessor(
|
|
||||||
e,
|
|
||||||
"Sonic",
|
|
||||||
outputAudioFormat,
|
|
||||||
TransformationException.ERROR_CODE_AUDIO_PROCESSOR_INIT_FAILED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
encoder =
|
|
||||||
encoderFactory.createForAudioEncoding(
|
|
||||||
new Format.Builder()
|
|
||||||
.setSampleMimeType(
|
|
||||||
transformationRequest.audioMimeType == null
|
|
||||||
? inputFormat.sampleMimeType
|
|
||||||
: transformationRequest.audioMimeType)
|
|
||||||
.setSampleRate(outputAudioFormat.sampleRate)
|
|
||||||
.setChannelCount(outputAudioFormat.channelCount)
|
|
||||||
.setAverageBitrate(DEFAULT_ENCODER_BITRATE)
|
|
||||||
.build());
|
|
||||||
encoderInputAudioFormat = outputAudioFormat;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isSpeedChanging(BufferInfo bufferInfo) {
|
private boolean isSpeedChanging(BufferInfo bufferInfo) {
|
||||||
if (!transformationRequest.flattenForSlowMotion) {
|
if (!transformationRequest.flattenForSlowMotion) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,6 @@ public final class TransformationException extends Exception {
|
||||||
ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED,
|
ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED,
|
||||||
ERROR_CODE_GL_INIT_FAILED,
|
ERROR_CODE_GL_INIT_FAILED,
|
||||||
ERROR_CODE_GL_PROCESSING_FAILED,
|
ERROR_CODE_GL_PROCESSING_FAILED,
|
||||||
ERROR_CODE_AUDIO_PROCESSOR_INIT_FAILED,
|
|
||||||
})
|
})
|
||||||
public @interface ErrorCode {}
|
public @interface ErrorCode {}
|
||||||
|
|
||||||
|
|
@ -154,12 +153,7 @@ public final class TransformationException extends Exception {
|
||||||
/** Caused by a failure while using or releasing a GL program. */
|
/** Caused by a failure while using or releasing a GL program. */
|
||||||
public static final int ERROR_CODE_GL_PROCESSING_FAILED = 5002;
|
public static final int ERROR_CODE_GL_PROCESSING_FAILED = 5002;
|
||||||
|
|
||||||
// Audio editing errors (6xxx).
|
// Muxing errors (6xxx).
|
||||||
|
|
||||||
/** Caused by an audio processor initialization failure. */
|
|
||||||
public static final int ERROR_CODE_AUDIO_PROCESSOR_INIT_FAILED = 6001;
|
|
||||||
|
|
||||||
// Muxing errors (7xxx).
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Caused by an output sample MIME type inferred from the input not being supported by the muxer.
|
* Caused by an output sample MIME type inferred from the input not being supported by the muxer.
|
||||||
|
|
@ -167,7 +161,7 @@ public final class TransformationException extends Exception {
|
||||||
* <p>Use {@link TransformationRequest.Builder#setAudioMimeType(String)} or {@link
|
* <p>Use {@link TransformationRequest.Builder#setAudioMimeType(String)} or {@link
|
||||||
* TransformationRequest.Builder#setVideoMimeType(String)} to transcode to a supported MIME type.
|
* TransformationRequest.Builder#setVideoMimeType(String)} to transcode to a supported MIME type.
|
||||||
*/
|
*/
|
||||||
public static final int ERROR_CODE_MUXER_SAMPLE_MIME_TYPE_UNSUPPORTED = 7001;
|
public static final int ERROR_CODE_MUXER_SAMPLE_MIME_TYPE_UNSUPPORTED = 6001;
|
||||||
|
|
||||||
private static final ImmutableBiMap<String, @ErrorCode Integer> NAME_TO_ERROR_CODE =
|
private static final ImmutableBiMap<String, @ErrorCode Integer> NAME_TO_ERROR_CODE =
|
||||||
new ImmutableBiMap.Builder<String, @ErrorCode Integer>()
|
new ImmutableBiMap.Builder<String, @ErrorCode Integer>()
|
||||||
|
|
@ -189,7 +183,6 @@ public final class TransformationException extends Exception {
|
||||||
.put("ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED", ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED)
|
.put("ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED", ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED)
|
||||||
.put("ERROR_CODE_GL_INIT_FAILED", ERROR_CODE_GL_INIT_FAILED)
|
.put("ERROR_CODE_GL_INIT_FAILED", ERROR_CODE_GL_INIT_FAILED)
|
||||||
.put("ERROR_CODE_GL_PROCESSING_FAILED", ERROR_CODE_GL_PROCESSING_FAILED)
|
.put("ERROR_CODE_GL_PROCESSING_FAILED", ERROR_CODE_GL_PROCESSING_FAILED)
|
||||||
.put("ERROR_CODE_AUDIO_PROCESSOR_INIT_FAILED", ERROR_CODE_AUDIO_PROCESSOR_INIT_FAILED)
|
|
||||||
.put(
|
.put(
|
||||||
"ERROR_CODE_MUXER_SAMPLE_MIME_TYPE_UNSUPPORTED",
|
"ERROR_CODE_MUXER_SAMPLE_MIME_TYPE_UNSUPPORTED",
|
||||||
ERROR_CODE_MUXER_SAMPLE_MIME_TYPE_UNSUPPORTED)
|
ERROR_CODE_MUXER_SAMPLE_MIME_TYPE_UNSUPPORTED)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue