Add SilenceMediaSource

Issue: #5735
PiperOrigin-RevId: 248745617
This commit is contained in:
andrewlewis 2019-05-17 18:34:07 +01:00 committed by Oliver Woodman
parent d11778dbc8
commit 578abccf16
2 changed files with 244 additions and 0 deletions

View File

@ -4,6 +4,8 @@
* Add `ResolvingDataSource` for just-in-time resolution of `DataSpec`s
([#5779](https://github.com/google/ExoPlayer/issues/5779)).
* Add `SilenceMediaSource` that can be used to play silence of a given
duration ([#5735](https://github.com/google/ExoPlayer/issues/5735)).
* Subtitles:
* CEA-608: Handle XDS and TEXT modes
([#5807](https://github.com/google/ExoPlayer/pull/5807)).

View File

@ -0,0 +1,242 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.source;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList;
import org.checkerframework.checker.nullness.compatqual.NullableType;
/** Media source with a single period consisting of silent raw audio of a given duration. */
public final class SilenceMediaSource extends BaseMediaSource {
private static final int SAMPLE_RATE_HZ = 44100;
@C.PcmEncoding private static final int ENCODING = C.ENCODING_PCM_16BIT;
private static final int CHANNEL_COUNT = 2;
private static final Format FORMAT =
Format.createAudioSampleFormat(
/* id=*/ null,
MimeTypes.AUDIO_RAW,
/* codecs= */ null,
/* bitrate= */ Format.NO_VALUE,
/* maxInputSize= */ Format.NO_VALUE,
CHANNEL_COUNT,
SAMPLE_RATE_HZ,
ENCODING,
/* initializationData= */ null,
/* drmInitData= */ null,
/* selectionFlags= */ 0,
/* language= */ null);
private static final byte[] SILENCE_SAMPLE =
new byte[Util.getPcmFrameSize(ENCODING, CHANNEL_COUNT) * 1024];
private final long durationUs;
/**
* Creates a new media source providing silent audio of the given duration.
*
* @param durationUs The duration of silent audio to output, in microseconds.
*/
public SilenceMediaSource(long durationUs) {
Assertions.checkArgument(durationUs >= 0);
this.durationUs = durationUs;
}
@Override
public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
refreshSourceInfo(
new SinglePeriodTimeline(durationUs, /* isSeekable= */ true, /* isDynamic= */ false),
/* manifest= */ null);
}
@Override
public void maybeThrowSourceInfoRefreshError() {}
@Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
return new SilenceMediaPeriod(durationUs);
}
@Override
public void releasePeriod(MediaPeriod mediaPeriod) {}
@Override
public void releaseSourceInternal() {}
private static final class SilenceMediaPeriod implements MediaPeriod {
private static final TrackGroupArray TRACKS = new TrackGroupArray(new TrackGroup(FORMAT));
private final long durationUs;
private final ArrayList<SampleStream> sampleStreams;
public SilenceMediaPeriod(long durationUs) {
this.durationUs = durationUs;
sampleStreams = new ArrayList<>();
}
@Override
public void prepare(Callback callback, long positionUs) {
callback.onPrepared(/* mediaPeriod= */ this);
}
@Override
public void maybeThrowPrepareError() {}
@Override
public TrackGroupArray getTrackGroups() {
return TRACKS;
}
@Override
public long selectTracks(
@NullableType TrackSelection[] selections,
boolean[] mayRetainStreamFlags,
@NullableType SampleStream[] streams,
boolean[] streamResetFlags,
long positionUs) {
for (int i = 0; i < selections.length; i++) {
if (streams[i] != null && (selections[i] == null || !mayRetainStreamFlags[i])) {
sampleStreams.remove(streams[i]);
streams[i] = null;
}
if (streams[i] == null && selections[i] != null) {
SilenceSampleStream stream = new SilenceSampleStream(durationUs);
stream.seekTo(positionUs);
sampleStreams.add(stream);
streams[i] = stream;
streamResetFlags[i] = true;
}
}
return positionUs;
}
@Override
public void discardBuffer(long positionUs, boolean toKeyframe) {}
@Override
public long readDiscontinuity() {
return C.TIME_UNSET;
}
@Override
public long seekToUs(long positionUs) {
for (int i = 0; i < sampleStreams.size(); i++) {
((SilenceSampleStream) sampleStreams.get(i)).seekTo(positionUs);
}
return positionUs;
}
@Override
public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) {
return positionUs;
}
@Override
public long getBufferedPositionUs() {
return C.TIME_END_OF_SOURCE;
}
@Override
public long getNextLoadPositionUs() {
return C.TIME_END_OF_SOURCE;
}
@Override
public boolean continueLoading(long positionUs) {
return false;
}
@Override
public void reevaluateBuffer(long positionUs) {}
}
private static final class SilenceSampleStream implements SampleStream {
private final long durationBytes;
private boolean sentFormat;
private long positionBytes;
public SilenceSampleStream(long durationUs) {
durationBytes = getAudioByteCount(durationUs);
seekTo(0);
}
public void seekTo(long positionUs) {
positionBytes = getAudioByteCount(positionUs);
}
@Override
public boolean isReady() {
return true;
}
@Override
public void maybeThrowError() {}
@Override
public int readData(
FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired) {
if (!sentFormat || formatRequired) {
formatHolder.format = FORMAT;
sentFormat = true;
return C.RESULT_FORMAT_READ;
}
long bytesRemaining = durationBytes - positionBytes;
if (bytesRemaining == 0) {
buffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM);
return C.RESULT_BUFFER_READ;
}
int bytesToWrite = (int) Math.min(SILENCE_SAMPLE.length, bytesRemaining);
buffer.ensureSpaceForWrite(bytesToWrite);
buffer.addFlag(C.BUFFER_FLAG_KEY_FRAME);
buffer.data.put(SILENCE_SAMPLE, /* offset= */ 0, bytesToWrite);
buffer.timeUs = getAudioPositionUs(positionBytes);
positionBytes += bytesToWrite;
return C.RESULT_BUFFER_READ;
}
@Override
public int skipData(long positionUs) {
long oldPositionBytes = positionBytes;
seekTo(positionUs);
return (int) ((positionBytes - oldPositionBytes) / SILENCE_SAMPLE.length);
}
}
private static long getAudioByteCount(long durationUs) {
long audioSampleCount = durationUs * SAMPLE_RATE_HZ / C.MICROS_PER_SECOND;
return Util.getPcmFrameSize(ENCODING, CHANNEL_COUNT) * audioSampleCount;
}
private static long getAudioPositionUs(long bytes) {
long audioSampleCount = bytes / Util.getPcmFrameSize(ENCODING, CHANNEL_COUNT);
return audioSampleCount * C.MICROS_PER_SECOND / SAMPLE_RATE_HZ;
}
}