mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Fix a race condition in AudioGraphInput
AudioGraphInput.onMediaItemChanged is called on input thread. Pending media item changes are processed on processing thread, inside calls to getOutput(). This change allows multiple pending media item changes to be enqueued, and processed in sequence. PiperOrigin-RevId: 638995291
This commit is contained in:
parent
4dd8360693
commit
d3fa33250b
@ -36,7 +36,6 @@ import androidx.media3.common.audio.ChannelMixingAudioProcessor;
|
|||||||
import androidx.media3.common.audio.ChannelMixingMatrix;
|
import androidx.media3.common.audio.ChannelMixingMatrix;
|
||||||
import androidx.media3.common.audio.SonicAudioProcessor;
|
import androidx.media3.common.audio.SonicAudioProcessor;
|
||||||
import androidx.media3.common.audio.SpeedChangingAudioProcessor;
|
import androidx.media3.common.audio.SpeedChangingAudioProcessor;
|
||||||
import androidx.media3.common.util.NullableType;
|
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.decoder.DecoderInputBuffer;
|
import androidx.media3.decoder.DecoderInputBuffer;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
@ -45,7 +44,6 @@ import java.nio.ByteOrder;
|
|||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes a single sequential stream of PCM audio samples.
|
* Processes a single sequential stream of PCM audio samples.
|
||||||
@ -64,7 +62,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||||||
|
|
||||||
private final Queue<DecoderInputBuffer> availableInputBuffers;
|
private final Queue<DecoderInputBuffer> availableInputBuffers;
|
||||||
private final Queue<DecoderInputBuffer> pendingInputBuffers;
|
private final Queue<DecoderInputBuffer> pendingInputBuffers;
|
||||||
private final AtomicReference<@NullableType MediaItemChange> pendingMediaItemChange;
|
private final Queue<MediaItemChange> pendingMediaItemChanges;
|
||||||
private final AtomicLong startTimeUs;
|
private final AtomicLong startTimeUs;
|
||||||
|
|
||||||
// silentAudioGenerator.audioFormat must match the current media item's input format.
|
// silentAudioGenerator.audioFormat must match the current media item's input format.
|
||||||
@ -103,7 +101,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||||||
availableInputBuffers.add(inputBuffer);
|
availableInputBuffers.add(inputBuffer);
|
||||||
}
|
}
|
||||||
pendingInputBuffers = new ConcurrentLinkedQueue<>();
|
pendingInputBuffers = new ConcurrentLinkedQueue<>();
|
||||||
pendingMediaItemChange = new AtomicReference<>();
|
pendingMediaItemChanges = new ConcurrentLinkedQueue<>();
|
||||||
silentAudioGenerator = new SilentAudioGenerator(inputAudioFormat);
|
silentAudioGenerator = new SilentAudioGenerator(inputAudioFormat);
|
||||||
audioProcessingPipeline =
|
audioProcessingPipeline =
|
||||||
configureProcessing(
|
configureProcessing(
|
||||||
@ -138,7 +136,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||||||
return outputBuffer;
|
return outputBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasDataToOutput() && pendingMediaItemChange.get() != null) {
|
if (!hasDataToOutput() && !pendingMediaItemChanges.isEmpty()) {
|
||||||
configureForPendingMediaItemChange();
|
configureForPendingMediaItemChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,7 +165,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||||||
AudioFormat audioFormat = new AudioFormat(decodedFormat);
|
AudioFormat audioFormat = new AudioFormat(decodedFormat);
|
||||||
checkState(isInputAudioFormatValid(audioFormat), /* errorMessage= */ audioFormat);
|
checkState(isInputAudioFormatValid(audioFormat), /* errorMessage= */ audioFormat);
|
||||||
}
|
}
|
||||||
pendingMediaItemChange.set(
|
pendingMediaItemChanges.add(
|
||||||
new MediaItemChange(editedMediaItem, durationUs, decodedFormat, isLast));
|
new MediaItemChange(editedMediaItem, durationUs, decodedFormat, isLast));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,7 +177,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public DecoderInputBuffer getInputBuffer() {
|
public DecoderInputBuffer getInputBuffer() {
|
||||||
if (inputBlocked || (pendingMediaItemChange.get() != null)) {
|
if (inputBlocked || !pendingMediaItemChanges.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return availableInputBuffers.peek();
|
return availableInputBuffers.peek();
|
||||||
@ -195,7 +193,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||||||
if (inputBlocked) {
|
if (inputBlocked) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
checkState(pendingMediaItemChange.get() == null);
|
checkState(pendingMediaItemChanges.isEmpty());
|
||||||
DecoderInputBuffer inputBuffer = availableInputBuffers.remove();
|
DecoderInputBuffer inputBuffer = availableInputBuffers.remove();
|
||||||
pendingInputBuffers.add(inputBuffer);
|
pendingInputBuffers.add(inputBuffer);
|
||||||
startTimeUs.compareAndSet(
|
startTimeUs.compareAndSet(
|
||||||
@ -235,7 +233,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||||||
* <p>Should only be called if the input thread and processing thread are the same.
|
* <p>Should only be called if the input thread and processing thread are the same.
|
||||||
*/
|
*/
|
||||||
public void flush() {
|
public void flush() {
|
||||||
pendingMediaItemChange.set(null);
|
pendingMediaItemChanges.clear();
|
||||||
processedFirstMediaItemChange = true;
|
processedFirstMediaItemChange = true;
|
||||||
if (!availableInputBuffers.isEmpty()) {
|
if (!availableInputBuffers.isEmpty()) {
|
||||||
// Clear first available buffer in case the caller wrote data in the input buffer without
|
// Clear first available buffer in case the caller wrote data in the input buffer without
|
||||||
@ -279,7 +277,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||||||
if (hasDataToOutput()) {
|
if (hasDataToOutput()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (pendingMediaItemChange.get() != null) {
|
if (!pendingMediaItemChanges.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (currentItemExpectedInputDurationUs != C.TIME_UNSET) {
|
if (currentItemExpectedInputDurationUs != C.TIME_UNSET) {
|
||||||
@ -323,7 +321,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||||||
|
|
||||||
@Nullable DecoderInputBuffer pendingInputBuffer = pendingInputBuffers.peek();
|
@Nullable DecoderInputBuffer pendingInputBuffer = pendingInputBuffers.peek();
|
||||||
if (pendingInputBuffer == null) {
|
if (pendingInputBuffer == null) {
|
||||||
if (pendingMediaItemChange.get() != null) {
|
if (!pendingMediaItemChanges.isEmpty()) {
|
||||||
if (shouldAppendSilence()) {
|
if (shouldAppendSilence()) {
|
||||||
appendSilence();
|
appendSilence();
|
||||||
return true;
|
return true;
|
||||||
@ -377,7 +375,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||||||
|
|
||||||
@Nullable DecoderInputBuffer currentInputBuffer = pendingInputBuffers.poll();
|
@Nullable DecoderInputBuffer currentInputBuffer = pendingInputBuffers.poll();
|
||||||
if (currentInputBuffer == null) {
|
if (currentInputBuffer == null) {
|
||||||
if (pendingMediaItemChange.get() != null && shouldAppendSilence()) {
|
if (!pendingMediaItemChanges.isEmpty() && shouldAppendSilence()) {
|
||||||
appendSilence();
|
appendSilence();
|
||||||
}
|
}
|
||||||
return EMPTY_BUFFER;
|
return EMPTY_BUFFER;
|
||||||
@ -439,7 +437,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||||||
* through {@link #getOutput()}.
|
* through {@link #getOutput()}.
|
||||||
*/
|
*/
|
||||||
private void configureForPendingMediaItemChange() throws UnhandledAudioFormatException {
|
private void configureForPendingMediaItemChange() throws UnhandledAudioFormatException {
|
||||||
MediaItemChange pendingChange = checkStateNotNull(pendingMediaItemChange.get());
|
MediaItemChange pendingChange = checkStateNotNull(pendingMediaItemChanges.poll());
|
||||||
|
|
||||||
currentItemInputBytesRead = 0;
|
currentItemInputBytesRead = 0;
|
||||||
isCurrentItemLast = pendingChange.isLast;
|
isCurrentItemLast = pendingChange.isLast;
|
||||||
@ -476,7 +474,6 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||||||
/* requiredOutputAudioFormat= */ outputAudioFormat);
|
/* requiredOutputAudioFormat= */ outputAudioFormat);
|
||||||
}
|
}
|
||||||
audioProcessingPipeline.flush();
|
audioProcessingPipeline.flush();
|
||||||
pendingMediaItemChange.set(null);
|
|
||||||
receivedEndOfStreamFromInput = false;
|
receivedEndOfStreamFromInput = false;
|
||||||
processedFirstMediaItemChange = true;
|
processedFirstMediaItemChange = true;
|
||||||
}
|
}
|
||||||
|
@ -481,6 +481,36 @@ public class AudioGraphInputTest {
|
|||||||
assertThat(bytesOutput).isEqualTo(expectedSampleCount * STEREO_44100.bytesPerFrame);
|
assertThat(bytesOutput).isEqualTo(expectedSampleCount * STEREO_44100.bytesPerFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getOutput_withThreeSilentMediaItemChanges_outputsCorrectAmountOfSilentBytes()
|
||||||
|
throws Exception {
|
||||||
|
AudioGraphInput audioGraphInput =
|
||||||
|
new AudioGraphInput(
|
||||||
|
/* requestedOutputAudioFormat= */ AudioFormat.NOT_SET,
|
||||||
|
/* editedMediaItem= */ FAKE_ITEM,
|
||||||
|
/* inputFormat= */ getPcmFormat(STEREO_44100));
|
||||||
|
|
||||||
|
audioGraphInput.onMediaItemChanged(
|
||||||
|
/* editedMediaItem= */ FAKE_ITEM,
|
||||||
|
/* durationUs= */ 200_000,
|
||||||
|
/* decodedFormat= */ null,
|
||||||
|
/* isLast= */ false);
|
||||||
|
audioGraphInput.onMediaItemChanged(
|
||||||
|
/* editedMediaItem= */ FAKE_ITEM,
|
||||||
|
/* durationUs= */ 300_000,
|
||||||
|
/* decodedFormat= */ null,
|
||||||
|
/* isLast= */ false);
|
||||||
|
audioGraphInput.onMediaItemChanged(
|
||||||
|
/* editedMediaItem= */ FAKE_ITEM,
|
||||||
|
/* durationUs= */ 500_000,
|
||||||
|
/* decodedFormat= */ null,
|
||||||
|
/* isLast= */ true);
|
||||||
|
|
||||||
|
int bytesOutput = drainAudioGraphInputUntilEnded(audioGraphInput).size();
|
||||||
|
long expectedSampleCount = Util.durationUsToSampleCount(1_000_000, STEREO_44100.sampleRate);
|
||||||
|
assertThat(bytesOutput).isEqualTo(expectedSampleCount * STEREO_44100.bytesPerFrame);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getOutput_withSilentMediaItemAndEffectsChange_outputsCorrectAmountOfSilentBytes()
|
public void getOutput_withSilentMediaItemAndEffectsChange_outputsCorrectAmountOfSilentBytes()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user