Commit 65e6d50e by ybai001

Merge branch 'dev-v2' of https://github.com/google/ExoPlayer into google-dev-v2

parents cc5e981e 4f15cfaa
Showing with 1969 additions and 661 deletions
...@@ -32,6 +32,30 @@ ...@@ -32,6 +32,30 @@
([#6733](https://github.com/google/ExoPlayer/issues/6733)). Incorrect handling ([#6733](https://github.com/google/ExoPlayer/issues/6733)). Incorrect handling
could previously cause downloads to be paused when they should have been able could previously cause downloads to be paused when they should have been able
to proceed. to proceed.
* Fix handling of E-AC-3 streams that contain AC-3 syncframes
([#6602](https://github.com/google/ExoPlayer/issues/6602)).
* Fix playback of TrueHD streams in Matroska
([#6845](https://github.com/google/ExoPlayer/issues/6845)).
* Support "twos" codec (big endian PCM) in MP4
([#5789](https://github.com/google/ExoPlayer/issues/5789)).
* WAV: Support IMA ADPCM encoded data.
* Show ad group markers in `DefaultTimeBar` even if they are after the end of
the current window
([#6552](https://github.com/google/ExoPlayer/issues/6552)).
* WAV:
* Support IMA ADPCM encoded data.
* Improve support for G.711 A-law and mu-law encoded data.
* Fix MKV subtitles to disappear when intended instead of lasting until the
next cue ([#6833](https://github.com/google/ExoPlayer/issues/6833)).
* Parse \<ruby\> and \<rt\> tags in WebVTT subtitles (rendering is coming
later).
* Parse `text-combine-upright` CSS property (i.e. tate-chu-yoko) in WebVTT
subtitles (rendering is coming later).
* OkHttp extension: Upgrade OkHttp dependency to 3.12.7, which fixes a class of
`SocketTimeoutException` issues when using HTTP/2
([#4078](https://github.com/google/ExoPlayer/issues/4078)).
* Don't use notification chronometer if playback speed is != 1.0
([#6816](https://github.com/google/ExoPlayer/issues/6816)).
### 2.11.1 (2019-12-20) ### ### 2.11.1 (2019-12-20) ###
......
...@@ -98,8 +98,7 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer { ...@@ -98,8 +98,7 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer {
Assertions.checkNotNull(format.sampleMimeType); Assertions.checkNotNull(format.sampleMimeType);
if (!FfmpegLibrary.isAvailable()) { if (!FfmpegLibrary.isAvailable()) {
return FORMAT_UNSUPPORTED_TYPE; return FORMAT_UNSUPPORTED_TYPE;
} else if (!FfmpegLibrary.supportsFormat(format.sampleMimeType, format.pcmEncoding) } else if (!FfmpegLibrary.supportsFormat(format.sampleMimeType) || !isOutputSupported(format)) {
|| !isOutputSupported(format)) {
return FORMAT_UNSUPPORTED_SUBTYPE; return FORMAT_UNSUPPORTED_SUBTYPE;
} else if (!supportsFormatDrm(drmSessionManager, format.drmInitData)) { } else if (!supportsFormatDrm(drmSessionManager, format.drmInitData)) {
return FORMAT_UNSUPPORTED_DRM; return FORMAT_UNSUPPORTED_DRM;
......
...@@ -64,9 +64,7 @@ import java.util.List; ...@@ -64,9 +64,7 @@ import java.util.List;
throw new FfmpegDecoderException("Failed to load decoder native libraries."); throw new FfmpegDecoderException("Failed to load decoder native libraries.");
} }
Assertions.checkNotNull(format.sampleMimeType); Assertions.checkNotNull(format.sampleMimeType);
codecName = codecName = Assertions.checkNotNull(FfmpegLibrary.getCodecName(format.sampleMimeType));
Assertions.checkNotNull(
FfmpegLibrary.getCodecName(format.sampleMimeType, format.pcmEncoding));
extraData = getExtraData(format.sampleMimeType, format.initializationData); extraData = getExtraData(format.sampleMimeType, format.initializationData);
encoding = outputFloat ? C.ENCODING_PCM_FLOAT : C.ENCODING_PCM_16BIT; encoding = outputFloat ? C.ENCODING_PCM_FLOAT : C.ENCODING_PCM_16BIT;
outputBufferSize = outputFloat ? OUTPUT_BUFFER_SIZE_32BIT : OUTPUT_BUFFER_SIZE_16BIT; outputBufferSize = outputFloat ? OUTPUT_BUFFER_SIZE_32BIT : OUTPUT_BUFFER_SIZE_16BIT;
...@@ -145,16 +143,12 @@ import java.util.List; ...@@ -145,16 +143,12 @@ import java.util.List;
nativeContext = 0; nativeContext = 0;
} }
/** /** Returns the channel count of output audio. */
* Returns the channel count of output audio. May only be called after {@link #decode}.
*/
public int getChannelCount() { public int getChannelCount() {
return channelCount; return channelCount;
} }
/** /** Returns the sample rate of output audio. */
* Returns the sample rate of output audio. May only be called after {@link #decode}.
*/
public int getSampleRate() { public int getSampleRate() {
return sampleRate; return sampleRate;
} }
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.ext.ffmpeg; package com.google.android.exoplayer2.ext.ffmpeg;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.util.LibraryLoader; import com.google.android.exoplayer2.util.LibraryLoader;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
...@@ -65,13 +64,12 @@ public final class FfmpegLibrary { ...@@ -65,13 +64,12 @@ public final class FfmpegLibrary {
* Returns whether the underlying library supports the specified MIME type. * Returns whether the underlying library supports the specified MIME type.
* *
* @param mimeType The MIME type to check. * @param mimeType The MIME type to check.
* @param encoding The PCM encoding for raw audio.
*/ */
public static boolean supportsFormat(String mimeType, @C.PcmEncoding int encoding) { public static boolean supportsFormat(String mimeType) {
if (!isAvailable()) { if (!isAvailable()) {
return false; return false;
} }
String codecName = getCodecName(mimeType, encoding); String codecName = getCodecName(mimeType);
if (codecName == null) { if (codecName == null) {
return false; return false;
} }
...@@ -86,7 +84,7 @@ public final class FfmpegLibrary { ...@@ -86,7 +84,7 @@ public final class FfmpegLibrary {
* Returns the name of the FFmpeg decoder that could be used to decode the format, or {@code null} * Returns the name of the FFmpeg decoder that could be used to decode the format, or {@code null}
* if it's unsupported. * if it's unsupported.
*/ */
/* package */ static @Nullable String getCodecName(String mimeType, @C.PcmEncoding int encoding) { /* package */ static @Nullable String getCodecName(String mimeType) {
switch (mimeType) { switch (mimeType) {
case MimeTypes.AUDIO_AAC: case MimeTypes.AUDIO_AAC:
return "aac"; return "aac";
...@@ -116,14 +114,10 @@ public final class FfmpegLibrary { ...@@ -116,14 +114,10 @@ public final class FfmpegLibrary {
return "flac"; return "flac";
case MimeTypes.AUDIO_ALAC: case MimeTypes.AUDIO_ALAC:
return "alac"; return "alac";
case MimeTypes.AUDIO_RAW: case MimeTypes.AUDIO_MLAW:
if (encoding == C.ENCODING_PCM_MU_LAW) { return "pcm_mulaw";
return "pcm_mulaw"; case MimeTypes.AUDIO_ALAW:
} else if (encoding == C.ENCODING_PCM_A_LAW) { return "pcm_alaw";
return "pcm_alaw";
} else {
return null;
}
default: default:
return null; return null;
} }
......
...@@ -126,6 +126,8 @@ import java.nio.ByteBuffer; ...@@ -126,6 +126,8 @@ import java.nio.ByteBuffer;
if (targetSampleInLastFrame) { if (targetSampleInLastFrame) {
// We are holding the target frame in outputFrameHolder. Set its presentation time now. // We are holding the target frame in outputFrameHolder. Set its presentation time now.
outputFrameHolder.timeUs = decoderJni.getLastFrameTimestamp(); outputFrameHolder.timeUs = decoderJni.getLastFrameTimestamp();
// The input position is passed even though it does not indicate the frame containing the
// target sample because the extractor must continue to read from this position.
return TimestampSearchResult.targetFoundResult(input.getPosition()); return TimestampSearchResult.targetFoundResult(input.getPosition());
} else if (nextFrameSampleIndex <= targetSampleIndex) { } else if (nextFrameSampleIndex <= targetSampleIndex) {
return TimestampSearchResult.underestimatedResult( return TimestampSearchResult.underestimatedResult(
......
/*
* Copyright (C) 2016 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.ext.flac;
import static com.google.common.truth.Truth.assertThat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.amr.AmrExtractor;
import com.google.android.exoplayer2.extractor.flv.FlvExtractor;
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor;
import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor;
import com.google.android.exoplayer2.extractor.ogg.OggExtractor;
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
import com.google.android.exoplayer2.extractor.ts.Ac4Extractor;
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
import com.google.android.exoplayer2.extractor.ts.PsExtractor;
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
import com.google.android.exoplayer2.extractor.wav.WavExtractor;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit test for {@link DefaultExtractorsFactory}. */
@RunWith(AndroidJUnit4.class)
public final class DefaultExtractorsFactoryTest {
@Test
public void testCreateExtractors_returnExpectedClasses() {
DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory();
Extractor[] extractors = defaultExtractorsFactory.createExtractors();
List<Class<?>> listCreatedExtractorClasses = new ArrayList<>();
for (Extractor extractor : extractors) {
listCreatedExtractorClasses.add(extractor.getClass());
}
Class<?>[] expectedExtractorClassses =
new Class<?>[] {
MatroskaExtractor.class,
FragmentedMp4Extractor.class,
Mp4Extractor.class,
Mp3Extractor.class,
AdtsExtractor.class,
Ac3Extractor.class,
Ac4Extractor.class,
TsExtractor.class,
FlvExtractor.class,
OggExtractor.class,
PsExtractor.class,
WavExtractor.class,
AmrExtractor.class,
FlacExtractor.class
};
assertThat(listCreatedExtractorClasses).containsNoDuplicates();
assertThat(listCreatedExtractorClasses).containsExactlyElementsIn(expectedExtractorClassses);
}
}
...@@ -39,9 +39,9 @@ dependencies { ...@@ -39,9 +39,9 @@ dependencies {
testImplementation 'org.robolectric:robolectric:' + robolectricVersion testImplementation 'org.robolectric:robolectric:' + robolectricVersion
// Do not update to 3.13.X or later until minSdkVersion is increased to 21: // Do not update to 3.13.X or later until minSdkVersion is increased to 21:
// https://cashapp.github.io/2019-02-05/okhttp-3-13-requires-android-5 // https://cashapp.github.io/2019-02-05/okhttp-3-13-requires-android-5
// Since OkHttp is distributed as a jar rather than an aar, Gradle wont stop // Since OkHttp is distributed as a jar rather than an aar, Gradle won't
// us from making this mistake! // stop us from making this mistake!
api 'com.squareup.okhttp3:okhttp:3.12.5' api 'com.squareup.okhttp3:okhttp:3.12.7'
} }
ext { ext {
......
...@@ -5,6 +5,12 @@ ...@@ -5,6 +5,12 @@
public static android.net.Uri buildRawResourceUri(int); public static android.net.Uri buildRawResourceUri(int);
} }
# Methods accessed via reflection in DefaultExtractorsFactory
-dontnote com.google.android.exoplayer2.ext.flac.FlacLibrary
-keepclassmembers class com.google.android.exoplayer2.ext.flac.FlacLibrary {
public static boolean isAvailable();
}
# Some members of this class are being accessed from native methods. Keep them unobfuscated. # Some members of this class are being accessed from native methods. Keep them unobfuscated.
-keep class com.google.android.exoplayer2.video.VideoDecoderOutputBuffer { -keep class com.google.android.exoplayer2.video.VideoDecoderOutputBuffer {
*; *;
......
...@@ -150,10 +150,10 @@ public final class C { ...@@ -150,10 +150,10 @@ public final class C {
/** /**
* Represents an audio encoding, or an invalid or unset value. One of {@link Format#NO_VALUE}, * Represents an audio encoding, or an invalid or unset value. One of {@link Format#NO_VALUE},
* {@link #ENCODING_INVALID}, {@link #ENCODING_PCM_8BIT}, {@link #ENCODING_PCM_16BIT}, {@link * {@link #ENCODING_INVALID}, {@link #ENCODING_PCM_8BIT}, {@link #ENCODING_PCM_16BIT}, {@link
* #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT}, {@link #ENCODING_PCM_FLOAT}, {@link * #ENCODING_PCM_16BIT_BIG_ENDIAN}, {@link #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT},
* #ENCODING_PCM_MU_LAW}, {@link #ENCODING_PCM_A_LAW}, {@link #ENCODING_MP3}, {@link * {@link #ENCODING_PCM_FLOAT}, {@link #ENCODING_MP3}, {@link #ENCODING_AC3}, {@link
* #ENCODING_AC3}, {@link #ENCODING_E_AC3}, {@link #ENCODING_E_AC3_JOC}, {@link #ENCODING_AC4}, * #ENCODING_E_AC3}, {@link #ENCODING_E_AC3_JOC}, {@link #ENCODING_AC4}, {@link #ENCODING_DTS},
* {@link #ENCODING_DTS}, {@link #ENCODING_DTS_HD} or {@link #ENCODING_DOLBY_TRUEHD}. * {@link #ENCODING_DTS_HD} or {@link #ENCODING_DOLBY_TRUEHD}.
*/ */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
...@@ -162,11 +162,10 @@ public final class C { ...@@ -162,11 +162,10 @@ public final class C {
ENCODING_INVALID, ENCODING_INVALID,
ENCODING_PCM_8BIT, ENCODING_PCM_8BIT,
ENCODING_PCM_16BIT, ENCODING_PCM_16BIT,
ENCODING_PCM_16BIT_BIG_ENDIAN,
ENCODING_PCM_24BIT, ENCODING_PCM_24BIT,
ENCODING_PCM_32BIT, ENCODING_PCM_32BIT,
ENCODING_PCM_FLOAT, ENCODING_PCM_FLOAT,
ENCODING_PCM_MU_LAW,
ENCODING_PCM_A_LAW,
ENCODING_MP3, ENCODING_MP3,
ENCODING_AC3, ENCODING_AC3,
ENCODING_E_AC3, ENCODING_E_AC3,
...@@ -174,15 +173,15 @@ public final class C { ...@@ -174,15 +173,15 @@ public final class C {
ENCODING_AC4, ENCODING_AC4,
ENCODING_DTS, ENCODING_DTS,
ENCODING_DTS_HD, ENCODING_DTS_HD,
ENCODING_DOLBY_TRUEHD, ENCODING_DOLBY_TRUEHD
}) })
public @interface Encoding {} public @interface Encoding {}
/** /**
* Represents a PCM audio encoding, or an invalid or unset value. One of {@link Format#NO_VALUE}, * Represents a PCM audio encoding, or an invalid or unset value. One of {@link Format#NO_VALUE},
* {@link #ENCODING_INVALID}, {@link #ENCODING_PCM_8BIT}, {@link #ENCODING_PCM_16BIT}, {@link * {@link #ENCODING_INVALID}, {@link #ENCODING_PCM_8BIT}, {@link #ENCODING_PCM_16BIT}, {@link
* #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT}, {@link #ENCODING_PCM_FLOAT}, {@link * #ENCODING_PCM_16BIT_BIG_ENDIAN}, {@link #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT},
* #ENCODING_PCM_MU_LAW} or {@link #ENCODING_PCM_A_LAW}. * {@link #ENCODING_PCM_FLOAT}.
*/ */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
...@@ -191,11 +190,10 @@ public final class C { ...@@ -191,11 +190,10 @@ public final class C {
ENCODING_INVALID, ENCODING_INVALID,
ENCODING_PCM_8BIT, ENCODING_PCM_8BIT,
ENCODING_PCM_16BIT, ENCODING_PCM_16BIT,
ENCODING_PCM_16BIT_BIG_ENDIAN,
ENCODING_PCM_24BIT, ENCODING_PCM_24BIT,
ENCODING_PCM_32BIT, ENCODING_PCM_32BIT,
ENCODING_PCM_FLOAT, ENCODING_PCM_FLOAT
ENCODING_PCM_MU_LAW,
ENCODING_PCM_A_LAW
}) })
public @interface PcmEncoding {} public @interface PcmEncoding {}
/** @see AudioFormat#ENCODING_INVALID */ /** @see AudioFormat#ENCODING_INVALID */
...@@ -204,16 +202,14 @@ public final class C { ...@@ -204,16 +202,14 @@ public final class C {
public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT; public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT;
/** @see AudioFormat#ENCODING_PCM_16BIT */ /** @see AudioFormat#ENCODING_PCM_16BIT */
public static final int ENCODING_PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT; public static final int ENCODING_PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT;
/** Like {@link #ENCODING_PCM_16BIT}, but with the bytes in big endian order. */
public static final int ENCODING_PCM_16BIT_BIG_ENDIAN = 0x10000000;
/** PCM encoding with 24 bits per sample. */ /** PCM encoding with 24 bits per sample. */
public static final int ENCODING_PCM_24BIT = 0x80000000; public static final int ENCODING_PCM_24BIT = 0x20000000;
/** PCM encoding with 32 bits per sample. */ /** PCM encoding with 32 bits per sample. */
public static final int ENCODING_PCM_32BIT = 0x40000000; public static final int ENCODING_PCM_32BIT = 0x30000000;
/** @see AudioFormat#ENCODING_PCM_FLOAT */ /** @see AudioFormat#ENCODING_PCM_FLOAT */
public static final int ENCODING_PCM_FLOAT = AudioFormat.ENCODING_PCM_FLOAT; public static final int ENCODING_PCM_FLOAT = AudioFormat.ENCODING_PCM_FLOAT;
/** Audio encoding for mu-law. */
public static final int ENCODING_PCM_MU_LAW = 0x10000000;
/** Audio encoding for A-law. */
public static final int ENCODING_PCM_A_LAW = 0x20000000;
/** @see AudioFormat#ENCODING_MP3 */ /** @see AudioFormat#ENCODING_MP3 */
public static final int ENCODING_MP3 = AudioFormat.ENCODING_MP3; public static final int ENCODING_MP3 = AudioFormat.ENCODING_MP3;
/** @see AudioFormat#ENCODING_AC3 */ /** @see AudioFormat#ENCODING_AC3 */
...@@ -981,8 +977,8 @@ public final class C { ...@@ -981,8 +977,8 @@ public final class C {
/** /**
* Network connection type. One of {@link #NETWORK_TYPE_UNKNOWN}, {@link #NETWORK_TYPE_OFFLINE}, * Network connection type. One of {@link #NETWORK_TYPE_UNKNOWN}, {@link #NETWORK_TYPE_OFFLINE},
* {@link #NETWORK_TYPE_WIFI}, {@link #NETWORK_TYPE_2G}, {@link #NETWORK_TYPE_3G}, {@link * {@link #NETWORK_TYPE_WIFI}, {@link #NETWORK_TYPE_2G}, {@link #NETWORK_TYPE_3G}, {@link
* #NETWORK_TYPE_4G}, {@link #NETWORK_TYPE_CELLULAR_UNKNOWN}, {@link #NETWORK_TYPE_ETHERNET} or * #NETWORK_TYPE_4G}, {@link #NETWORK_TYPE_5G}, {@link #NETWORK_TYPE_CELLULAR_UNKNOWN}, {@link
* {@link #NETWORK_TYPE_OTHER}. * #NETWORK_TYPE_ETHERNET} or {@link #NETWORK_TYPE_OTHER}.
*/ */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
...@@ -993,6 +989,7 @@ public final class C { ...@@ -993,6 +989,7 @@ public final class C {
NETWORK_TYPE_2G, NETWORK_TYPE_2G,
NETWORK_TYPE_3G, NETWORK_TYPE_3G,
NETWORK_TYPE_4G, NETWORK_TYPE_4G,
NETWORK_TYPE_5G,
NETWORK_TYPE_CELLULAR_UNKNOWN, NETWORK_TYPE_CELLULAR_UNKNOWN,
NETWORK_TYPE_ETHERNET, NETWORK_TYPE_ETHERNET,
NETWORK_TYPE_OTHER NETWORK_TYPE_OTHER
...@@ -1010,6 +1007,8 @@ public final class C { ...@@ -1010,6 +1007,8 @@ public final class C {
public static final int NETWORK_TYPE_3G = 4; public static final int NETWORK_TYPE_3G = 4;
/** Network type for a 4G cellular connection. */ /** Network type for a 4G cellular connection. */
public static final int NETWORK_TYPE_4G = 5; public static final int NETWORK_TYPE_4G = 5;
/** Network type for a 5G cellular connection. */
public static final int NETWORK_TYPE_5G = 9;
/** /**
* Network type for cellular connections which cannot be mapped to one of {@link * Network type for cellular connections which cannot be mapped to one of {@link
* #NETWORK_TYPE_2G}, {@link #NETWORK_TYPE_3G}, or {@link #NETWORK_TYPE_4G}. * #NETWORK_TYPE_2G}, {@link #NETWORK_TYPE_3G}, or {@link #NETWORK_TYPE_4G}.
...@@ -1017,10 +1016,7 @@ public final class C { ...@@ -1017,10 +1016,7 @@ public final class C {
public static final int NETWORK_TYPE_CELLULAR_UNKNOWN = 6; public static final int NETWORK_TYPE_CELLULAR_UNKNOWN = 6;
/** Network type for an Ethernet connection. */ /** Network type for an Ethernet connection. */
public static final int NETWORK_TYPE_ETHERNET = 7; public static final int NETWORK_TYPE_ETHERNET = 7;
/** /** Network type for other connections which are not Wifi or cellular (e.g. VPN, Bluetooth). */
* Network type for other connections which are not Wifi or cellular (e.g. Ethernet, VPN,
* Bluetooth).
*/
public static final int NETWORK_TYPE_OTHER = 8; public static final int NETWORK_TYPE_OTHER = 8;
/** /**
......
...@@ -138,13 +138,7 @@ public final class Format implements Parcelable { ...@@ -138,13 +138,7 @@ public final class Format implements Parcelable {
* The audio sampling rate in Hz, or {@link #NO_VALUE} if unknown or not applicable. * The audio sampling rate in Hz, or {@link #NO_VALUE} if unknown or not applicable.
*/ */
public final int sampleRate; public final int sampleRate;
/** /** The {@link C.PcmEncoding} for PCM audio. Set to {@link #NO_VALUE} for other media types. */
* The encoding for PCM audio streams. If {@link #sampleMimeType} is {@link MimeTypes#AUDIO_RAW}
* then one of {@link C#ENCODING_PCM_8BIT}, {@link C#ENCODING_PCM_16BIT}, {@link
* C#ENCODING_PCM_24BIT}, {@link C#ENCODING_PCM_32BIT}, {@link C#ENCODING_PCM_FLOAT}, {@link
* C#ENCODING_PCM_MU_LAW} or {@link C#ENCODING_PCM_A_LAW}. Set to {@link #NO_VALUE} for other
* media types.
*/
public final @C.PcmEncoding int pcmEncoding; public final @C.PcmEncoding int pcmEncoding;
/** /**
* The number of frames to trim from the start of the decoded audio stream, or 0 if not * The number of frames to trim from the start of the decoded audio stream, or 0 if not
......
...@@ -84,6 +84,7 @@ public final class PlaybackStatsListener ...@@ -84,6 +84,7 @@ public final class PlaybackStatsListener
@Player.State private int playbackState; @Player.State private int playbackState;
private boolean isSuppressed; private boolean isSuppressed;
private float playbackSpeed; private float playbackSpeed;
private boolean isSeeking;
/** /**
* Creates listener for playback stats. * Creates listener for playback stats.
...@@ -169,6 +170,9 @@ public final class PlaybackStatsListener ...@@ -169,6 +170,9 @@ public final class PlaybackStatsListener
@Override @Override
public void onSessionCreated(EventTime eventTime, String session) { public void onSessionCreated(EventTime eventTime, String session) {
PlaybackStatsTracker tracker = new PlaybackStatsTracker(keepHistory, eventTime); PlaybackStatsTracker tracker = new PlaybackStatsTracker(keepHistory, eventTime);
if (isSeeking) {
tracker.onSeekStarted(eventTime, /* belongsToPlayback= */ true);
}
tracker.onPlayerStateChanged( tracker.onPlayerStateChanged(
eventTime, playWhenReady, playbackState, /* belongsToPlayback= */ true); eventTime, playWhenReady, playbackState, /* belongsToPlayback= */ true);
tracker.onIsSuppressedChanged(eventTime, isSuppressed, /* belongsToPlayback= */ true); tracker.onIsSuppressedChanged(eventTime, isSuppressed, /* belongsToPlayback= */ true);
...@@ -288,20 +292,20 @@ public final class PlaybackStatsListener ...@@ -288,20 +292,20 @@ public final class PlaybackStatsListener
public void onSeekStarted(EventTime eventTime) { public void onSeekStarted(EventTime eventTime) {
sessionManager.updateSessions(eventTime); sessionManager.updateSessions(eventTime);
for (String session : playbackStatsTrackers.keySet()) { for (String session : playbackStatsTrackers.keySet()) {
if (sessionManager.belongsToSession(eventTime, session)) { boolean belongsToPlayback = sessionManager.belongsToSession(eventTime, session);
playbackStatsTrackers.get(session).onSeekStarted(eventTime); playbackStatsTrackers.get(session).onSeekStarted(eventTime, belongsToPlayback);
}
} }
isSeeking = true;
} }
@Override @Override
public void onSeekProcessed(EventTime eventTime) { public void onSeekProcessed(EventTime eventTime) {
sessionManager.updateSessions(eventTime); sessionManager.updateSessions(eventTime);
for (String session : playbackStatsTrackers.keySet()) { for (String session : playbackStatsTrackers.keySet()) {
if (sessionManager.belongsToSession(eventTime, session)) { boolean belongsToPlayback = sessionManager.belongsToSession(eventTime, session);
playbackStatsTrackers.get(session).onSeekProcessed(eventTime); playbackStatsTrackers.get(session).onSeekProcessed(eventTime, belongsToPlayback);
}
} }
isSeeking = false;
} }
@Override @Override
...@@ -563,23 +567,27 @@ public final class PlaybackStatsListener ...@@ -563,23 +567,27 @@ public final class PlaybackStatsListener
} }
/** /**
* Notifies the tracker of the start of a seek in the current playback. * Notifies the tracker of the start of a seek, including all seeks while the playback is not in
* the foreground.
* *
* @param eventTime The {@link EventTime}. * @param eventTime The {@link EventTime}.
* @param belongsToPlayback Whether the {@code eventTime} belongs to the current playback.
*/ */
public void onSeekStarted(EventTime eventTime) { public void onSeekStarted(EventTime eventTime, boolean belongsToPlayback) {
isSeeking = true; isSeeking = true;
maybeUpdatePlaybackState(eventTime, /* belongsToPlayback= */ true); maybeUpdatePlaybackState(eventTime, belongsToPlayback);
} }
/** /**
* Notifies the tracker of a seek has been processed in the current playback. * Notifies the tracker that a seek has been processed, including all seeks while the playback
* is not in the foreground.
* *
* @param eventTime The {@link EventTime}. * @param eventTime The {@link EventTime}.
* @param belongsToPlayback Whether the {@code eventTime} belongs to the current playback.
*/ */
public void onSeekProcessed(EventTime eventTime) { public void onSeekProcessed(EventTime eventTime, boolean belongsToPlayback) {
isSeeking = false; isSeeking = false;
maybeUpdatePlaybackState(eventTime, /* belongsToPlayback= */ true); maybeUpdatePlaybackState(eventTime, belongsToPlayback);
} }
/** /**
...@@ -875,7 +883,7 @@ public final class PlaybackStatsListener ...@@ -875,7 +883,7 @@ public final class PlaybackStatsListener
return currentPlaybackState == PlaybackStats.PLAYBACK_STATE_ENDED return currentPlaybackState == PlaybackStats.PLAYBACK_STATE_ENDED
? PlaybackStats.PLAYBACK_STATE_ENDED ? PlaybackStats.PLAYBACK_STATE_ENDED
: PlaybackStats.PLAYBACK_STATE_ABANDONED; : PlaybackStats.PLAYBACK_STATE_ABANDONED;
} else if (isSeeking) { } else if (isSeeking && isForeground) {
// Seeking takes precedence over errors such that we report a seek while in error state. // Seeking takes precedence over errors such that we report a seek while in error state.
return PlaybackStats.PLAYBACK_STATE_SEEKING; return PlaybackStats.PLAYBACK_STATE_SEEKING;
} else if (hasFatalError) { } else if (hasFatalError) {
......
...@@ -31,7 +31,7 @@ import java.nio.ByteBuffer; ...@@ -31,7 +31,7 @@ import java.nio.ByteBuffer;
/** /**
* Utility methods for parsing Dolby TrueHD and (E-)AC-3 syncframes. (E-)AC-3 parsing follows the * Utility methods for parsing Dolby TrueHD and (E-)AC-3 syncframes. (E-)AC-3 parsing follows the
* definition in ETSI TS 102 366 V1.2.1. * definition in ETSI TS 102 366 V1.4.1.
*/ */
public final class Ac3Util { public final class Ac3Util {
...@@ -39,8 +39,8 @@ public final class Ac3Util { ...@@ -39,8 +39,8 @@ public final class Ac3Util {
public static final class SyncFrameInfo { public static final class SyncFrameInfo {
/** /**
* AC3 stream types. See also ETSI TS 102 366 E.1.3.1.1. One of {@link #STREAM_TYPE_UNDEFINED}, * AC3 stream types. See also E.1.3.1.1. One of {@link #STREAM_TYPE_UNDEFINED}, {@link
* {@link #STREAM_TYPE_TYPE0}, {@link #STREAM_TYPE_TYPE1} or {@link #STREAM_TYPE_TYPE2}. * #STREAM_TYPE_TYPE0}, {@link #STREAM_TYPE_TYPE1} or {@link #STREAM_TYPE_TYPE2}.
*/ */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
...@@ -114,9 +114,7 @@ public final class Ac3Util { ...@@ -114,9 +114,7 @@ public final class Ac3Util {
* The number of new samples per (E-)AC-3 audio block. * The number of new samples per (E-)AC-3 audio block.
*/ */
private static final int AUDIO_SAMPLES_PER_AUDIO_BLOCK = 256; private static final int AUDIO_SAMPLES_PER_AUDIO_BLOCK = 256;
/** /** Each syncframe has 6 blocks that provide 256 new audio samples. See subsection 4.1. */
* Each syncframe has 6 blocks that provide 256 new audio samples. See ETSI TS 102 366 4.1.
*/
private static final int AC3_SYNCFRAME_AUDIO_SAMPLE_COUNT = 6 * AUDIO_SAMPLES_PER_AUDIO_BLOCK; private static final int AC3_SYNCFRAME_AUDIO_SAMPLE_COUNT = 6 * AUDIO_SAMPLES_PER_AUDIO_BLOCK;
/** /**
* Number of audio blocks per E-AC-3 syncframe, indexed by numblkscod. * Number of audio blocks per E-AC-3 syncframe, indexed by numblkscod.
...@@ -134,20 +132,21 @@ public final class Ac3Util { ...@@ -134,20 +132,21 @@ public final class Ac3Util {
* Channel counts, indexed by acmod. * Channel counts, indexed by acmod.
*/ */
private static final int[] CHANNEL_COUNT_BY_ACMOD = new int[] {2, 1, 2, 3, 3, 4, 4, 5}; private static final int[] CHANNEL_COUNT_BY_ACMOD = new int[] {2, 1, 2, 3, 3, 4, 4, 5};
/** /** Nominal bitrates in kbps, indexed by frmsizecod / 2. (See table 4.13.) */
* Nominal bitrates in kbps, indexed by frmsizecod / 2. (See ETSI TS 102 366 table 4.13.) private static final int[] BITRATE_BY_HALF_FRMSIZECOD =
*/ new int[] {
private static final int[] BITRATE_BY_HALF_FRMSIZECOD = new int[] {32, 40, 48, 56, 64, 80, 96, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 576, 640
112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 576, 640}; };
/** /** 16-bit words per syncframe, indexed by frmsizecod / 2. (See table 4.13.) */
* 16-bit words per syncframe, indexed by frmsizecod / 2. (See ETSI TS 102 366 table 4.13.) private static final int[] SYNCFRAME_SIZE_WORDS_BY_HALF_FRMSIZECOD_44_1 =
*/ new int[] {
private static final int[] SYNCFRAME_SIZE_WORDS_BY_HALF_FRMSIZECOD_44_1 = new int[] {69, 87, 104, 69, 87, 104, 121, 139, 174, 208, 243, 278, 348, 417, 487, 557, 696, 835, 975, 1114, 1253,
121, 139, 174, 208, 243, 278, 348, 417, 487, 557, 696, 835, 975, 1114, 1253, 1393}; 1393
};
/** /**
* Returns the AC-3 format given {@code data} containing the AC3SpecificBox according to ETSI TS * Returns the AC-3 format given {@code data} containing the AC3SpecificBox according to Annex F.
* 102 366 Annex F. The reading position of {@code data} will be modified. * The reading position of {@code data} will be modified.
* *
* @param data The AC3SpecificBox to parse. * @param data The AC3SpecificBox to parse.
* @param trackId The track identifier to set on the format. * @param trackId The track identifier to set on the format.
...@@ -179,8 +178,8 @@ public final class Ac3Util { ...@@ -179,8 +178,8 @@ public final class Ac3Util {
} }
/** /**
* Returns the E-AC-3 format given {@code data} containing the EC3SpecificBox according to ETSI TS * Returns the E-AC-3 format given {@code data} containing the EC3SpecificBox according to Annex
* 102 366 Annex F. The reading position of {@code data} will be modified. * F. The reading position of {@code data} will be modified.
* *
* @param data The EC3SpecificBox to parse. * @param data The EC3SpecificBox to parse.
* @param trackId The track identifier to set on the format. * @param trackId The track identifier to set on the format.
...@@ -243,9 +242,10 @@ public final class Ac3Util { ...@@ -243,9 +242,10 @@ public final class Ac3Util {
public static SyncFrameInfo parseAc3SyncframeInfo(ParsableBitArray data) { public static SyncFrameInfo parseAc3SyncframeInfo(ParsableBitArray data) {
int initialPosition = data.getPosition(); int initialPosition = data.getPosition();
data.skipBits(40); data.skipBits(40);
boolean isEac3 = data.readBits(5) == 16; // See bsid in subsection E.1.3.1.6. // Parse the bitstream ID for AC-3 and E-AC-3 (see subsections 4.3, E.1.2 and E.1.3.1.6).
boolean isEac3 = data.readBits(5) > 10;
data.setPosition(initialPosition); data.setPosition(initialPosition);
String mimeType; @Nullable String mimeType;
@StreamType int streamType = SyncFrameInfo.STREAM_TYPE_UNDEFINED; @StreamType int streamType = SyncFrameInfo.STREAM_TYPE_UNDEFINED;
int sampleRate; int sampleRate;
int acmod; int acmod;
...@@ -254,7 +254,7 @@ public final class Ac3Util { ...@@ -254,7 +254,7 @@ public final class Ac3Util {
boolean lfeon; boolean lfeon;
int channelCount; int channelCount;
if (isEac3) { if (isEac3) {
// Syntax from ETSI TS 102 366 V1.2.1 subsections E.1.2.1 and E.1.2.2. // Subsection E.1.2.
data.skipBits(16); // syncword data.skipBits(16); // syncword
switch (data.readBits(2)) { // strmtyp switch (data.readBits(2)) { // strmtyp
case 0: case 0:
...@@ -472,7 +472,8 @@ public final class Ac3Util { ...@@ -472,7 +472,8 @@ public final class Ac3Util {
if (data.length < 6) { if (data.length < 6) {
return C.LENGTH_UNSET; return C.LENGTH_UNSET;
} }
boolean isEac3 = ((data[5] & 0xFF) >> 3) == 16; // See bsid in subsection E.1.3.1.6. // Parse the bitstream ID for AC-3 and E-AC-3 (see subsections 4.3, E.1.2 and E.1.3.1.6).
boolean isEac3 = ((data[5] & 0xF8) >> 3) > 10;
if (isEac3) { if (isEac3) {
int frmsiz = (data[2] & 0x07) << 8; // Most significant 3 bits. int frmsiz = (data[2] & 0x07) << 8; // Most significant 3 bits.
frmsiz |= data[3] & 0xFF; // Least significant 8 bits. frmsiz |= data[3] & 0xFF; // Least significant 8 bits.
...@@ -485,24 +486,22 @@ public final class Ac3Util { ...@@ -485,24 +486,22 @@ public final class Ac3Util {
} }
/** /**
* Returns the number of audio samples in an AC-3 syncframe. * Reads the number of audio samples represented by the given (E-)AC-3 syncframe. The buffer's
*/
public static int getAc3SyncframeAudioSampleCount() {
return AC3_SYNCFRAME_AUDIO_SAMPLE_COUNT;
}
/**
* Reads the number of audio samples represented by the given E-AC-3 syncframe. The buffer's
* position is not modified. * position is not modified.
* *
* @param buffer The {@link ByteBuffer} from which to read the syncframe. * @param buffer The {@link ByteBuffer} from which to read the syncframe.
* @return The number of audio samples represented by the syncframe. * @return The number of audio samples represented by the syncframe.
*/ */
public static int parseEAc3SyncframeAudioSampleCount(ByteBuffer buffer) { public static int parseAc3SyncframeAudioSampleCount(ByteBuffer buffer) {
// See ETSI TS 102 366 subsection E.1.2.2. // Parse the bitstream ID for AC-3 and E-AC-3 (see subsections 4.3, E.1.2 and E.1.3.1.6).
int fscod = (buffer.get(buffer.position() + 4) & 0xC0) >> 6; boolean isEac3 = ((buffer.get(buffer.position() + 5) & 0xF8) >> 3) > 10;
return AUDIO_SAMPLES_PER_AUDIO_BLOCK * (fscod == 0x03 ? 6 if (isEac3) {
: BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD[(buffer.get(buffer.position() + 4) & 0x30) >> 4]); int fscod = (buffer.get(buffer.position() + 4) & 0xC0) >> 6;
int numblkscod = fscod == 0x03 ? 3 : (buffer.get(buffer.position() + 4) & 0x30) >> 4;
return BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD[numblkscod] * AUDIO_SAMPLES_PER_AUDIO_BLOCK;
} else {
return AC3_SYNCFRAME_AUDIO_SAMPLE_COUNT;
}
} }
/** /**
......
...@@ -58,6 +58,11 @@ public final class Ac4Util { ...@@ -58,6 +58,11 @@ public final class Ac4Util {
// TODO: Parse AC-4 stream channel count. // TODO: Parse AC-4 stream channel count.
private static final int CHANNEL_COUNT_2 = 2; private static final int CHANNEL_COUNT_2 = 2;
/** /**
* The AC-4 sync frame header size for extractor. The seven bytes are 0xAC, 0x40, 0xFF, 0xFF,
* sizeByte1, sizeByte2, sizeByte3. See ETSI TS 103 190-1 V1.3.1, Annex G
*/
public static final int SAMPLE_HEADER_SIZE = 7;
/**
* The header size for AC-4 parser. Only needs to be as big as we need to read, not the full * The header size for AC-4 parser. Only needs to be as big as we need to read, not the full
* header size. * header size.
*/ */
...@@ -218,7 +223,7 @@ public final class Ac4Util { ...@@ -218,7 +223,7 @@ public final class Ac4Util {
/** Populates {@code buffer} with an AC-4 sample header for a sample of the specified size. */ /** Populates {@code buffer} with an AC-4 sample header for a sample of the specified size. */
public static void getAc4SampleHeader(int size, ParsableByteArray buffer) { public static void getAc4SampleHeader(int size, ParsableByteArray buffer) {
// See ETSI TS 103 190-1 V1.3.1, Annex G. // See ETSI TS 103 190-1 V1.3.1, Annex G.
buffer.reset(/* limit= */ 7); buffer.reset(SAMPLE_HEADER_SIZE);
buffer.data[0] = (byte) 0xAC; buffer.data[0] = (byte) 0xAC;
buffer.data[1] = 0x40; buffer.data[1] = 0x40;
buffer.data[2] = (byte) 0xFF; buffer.data[2] = (byte) 0xFF;
......
...@@ -1149,9 +1149,7 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1149,9 +1149,7 @@ public final class DefaultAudioSink implements AudioSink {
case C.ENCODING_PCM_24BIT: case C.ENCODING_PCM_24BIT:
case C.ENCODING_PCM_32BIT: case C.ENCODING_PCM_32BIT:
case C.ENCODING_PCM_8BIT: case C.ENCODING_PCM_8BIT:
case C.ENCODING_PCM_A_LAW:
case C.ENCODING_PCM_FLOAT: case C.ENCODING_PCM_FLOAT:
case C.ENCODING_PCM_MU_LAW:
case Format.NO_VALUE: case Format.NO_VALUE:
default: default:
throw new IllegalArgumentException(); throw new IllegalArgumentException();
...@@ -1166,10 +1164,9 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1166,10 +1164,9 @@ public final class DefaultAudioSink implements AudioSink {
case C.ENCODING_DTS_HD: case C.ENCODING_DTS_HD:
return DtsUtil.parseDtsAudioSampleCount(buffer); return DtsUtil.parseDtsAudioSampleCount(buffer);
case C.ENCODING_AC3: case C.ENCODING_AC3:
return Ac3Util.getAc3SyncframeAudioSampleCount();
case C.ENCODING_E_AC3: case C.ENCODING_E_AC3:
case C.ENCODING_E_AC3_JOC: case C.ENCODING_E_AC3_JOC:
return Ac3Util.parseEAc3SyncframeAudioSampleCount(buffer); return Ac3Util.parseAc3SyncframeAudioSampleCount(buffer);
case C.ENCODING_AC4: case C.ENCODING_AC4:
return Ac4Util.parseAc4SyncframeAudioSampleCount(buffer); return Ac4Util.parseAc4SyncframeAudioSampleCount(buffer);
case C.ENCODING_DOLBY_TRUEHD: case C.ENCODING_DOLBY_TRUEHD:
......
...@@ -81,7 +81,10 @@ public final class DtsUtil { ...@@ -81,7 +81,10 @@ public final class DtsUtil {
* @return The DTS format parsed from data in the header. * @return The DTS format parsed from data in the header.
*/ */
public static Format parseDtsFormat( public static Format parseDtsFormat(
byte[] frame, String trackId, @Nullable String language, @Nullable DrmInitData drmInitData) { byte[] frame,
@Nullable String trackId,
@Nullable String language,
@Nullable DrmInitData drmInitData) {
ParsableBitArray frameBits = getNormalizedFrameHeader(frame); ParsableBitArray frameBits = getNormalizedFrameHeader(frame);
frameBits.skipBits(32 + 1 + 5 + 1 + 7 + 14); // SYNC, FTYPE, SHORT, CPF, NBLKS, FSIZE frameBits.skipBits(32 + 1 + 5 + 1 + 7 + 14); // SYNC, FTYPE, SHORT, CPF, NBLKS, FSIZE
int amode = frameBits.readBits(6); int amode = frameBits.readBits(6);
......
...@@ -79,6 +79,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -79,6 +79,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
private static final int MAX_PENDING_STREAM_CHANGE_COUNT = 10; private static final int MAX_PENDING_STREAM_CHANGE_COUNT = 10;
private static final String TAG = "MediaCodecAudioRenderer"; private static final String TAG = "MediaCodecAudioRenderer";
/**
* Custom key used to indicate bits per sample by some decoders on Vivo devices. For example
* OMX.vivo.alac.decoder on the Vivo Z1 Pro.
*/
private static final String VIVO_BITS_PER_SAMPLE_KEY = "v-bits-per-sample";
private final Context context; private final Context context;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
...@@ -566,7 +571,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -566,7 +571,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
mediaFormat.getString(MediaFormat.KEY_MIME)); mediaFormat.getString(MediaFormat.KEY_MIME));
} else { } else {
mediaFormat = outputMediaFormat; mediaFormat = outputMediaFormat;
encoding = getPcmEncoding(inputFormat); if (outputMediaFormat.containsKey(VIVO_BITS_PER_SAMPLE_KEY)) {
encoding = Util.getPcmEncoding(outputMediaFormat.getInteger(VIVO_BITS_PER_SAMPLE_KEY));
} else {
encoding = getPcmEncoding(inputFormat);
}
} }
int channelCount = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT); int channelCount = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
int sampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE); int sampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
......
...@@ -29,8 +29,11 @@ import java.nio.ByteBuffer; ...@@ -29,8 +29,11 @@ import java.nio.ByteBuffer;
public AudioFormat onConfigure(AudioFormat inputAudioFormat) public AudioFormat onConfigure(AudioFormat inputAudioFormat)
throws UnhandledAudioFormatException { throws UnhandledAudioFormatException {
@C.PcmEncoding int encoding = inputAudioFormat.encoding; @C.PcmEncoding int encoding = inputAudioFormat.encoding;
if (encoding != C.ENCODING_PCM_8BIT && encoding != C.ENCODING_PCM_16BIT if (encoding != C.ENCODING_PCM_8BIT
&& encoding != C.ENCODING_PCM_24BIT && encoding != C.ENCODING_PCM_32BIT) { && encoding != C.ENCODING_PCM_16BIT
&& encoding != C.ENCODING_PCM_16BIT_BIG_ENDIAN
&& encoding != C.ENCODING_PCM_24BIT
&& encoding != C.ENCODING_PCM_32BIT) {
throw new UnhandledAudioFormatException(inputAudioFormat); throw new UnhandledAudioFormatException(inputAudioFormat);
} }
return encoding != C.ENCODING_PCM_16BIT return encoding != C.ENCODING_PCM_16BIT
...@@ -50,6 +53,9 @@ import java.nio.ByteBuffer; ...@@ -50,6 +53,9 @@ import java.nio.ByteBuffer;
case C.ENCODING_PCM_8BIT: case C.ENCODING_PCM_8BIT:
resampledSize = size * 2; resampledSize = size * 2;
break; break;
case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
resampledSize = size;
break;
case C.ENCODING_PCM_24BIT: case C.ENCODING_PCM_24BIT:
resampledSize = (size / 3) * 2; resampledSize = (size / 3) * 2;
break; break;
...@@ -58,8 +64,6 @@ import java.nio.ByteBuffer; ...@@ -58,8 +64,6 @@ import java.nio.ByteBuffer;
break; break;
case C.ENCODING_PCM_16BIT: case C.ENCODING_PCM_16BIT:
case C.ENCODING_PCM_FLOAT: case C.ENCODING_PCM_FLOAT:
case C.ENCODING_PCM_A_LAW:
case C.ENCODING_PCM_MU_LAW:
case C.ENCODING_INVALID: case C.ENCODING_INVALID:
case Format.NO_VALUE: case Format.NO_VALUE:
default: default:
...@@ -70,21 +74,28 @@ import java.nio.ByteBuffer; ...@@ -70,21 +74,28 @@ import java.nio.ByteBuffer;
ByteBuffer buffer = replaceOutputBuffer(resampledSize); ByteBuffer buffer = replaceOutputBuffer(resampledSize);
switch (inputAudioFormat.encoding) { switch (inputAudioFormat.encoding) {
case C.ENCODING_PCM_8BIT: case C.ENCODING_PCM_8BIT:
// 8->16 bit resampling. Shift each byte from [0, 256) to [-128, 128) and scale up. // 8 -> 16 bit resampling. Shift each byte from [0, 256) to [-128, 128) and scale up.
for (int i = position; i < limit; i++) { for (int i = position; i < limit; i++) {
buffer.put((byte) 0); buffer.put((byte) 0);
buffer.put((byte) ((inputBuffer.get(i) & 0xFF) - 128)); buffer.put((byte) ((inputBuffer.get(i) & 0xFF) - 128));
} }
break; break;
case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
// Big endian to little endian resampling. Swap the byte order.
for (int i = position; i < limit; i += 2) {
buffer.put(inputBuffer.get(i + 1));
buffer.put(inputBuffer.get(i));
}
break;
case C.ENCODING_PCM_24BIT: case C.ENCODING_PCM_24BIT:
// 24->16 bit resampling. Drop the least significant byte. // 24 -> 16 bit resampling. Drop the least significant byte.
for (int i = position; i < limit; i += 3) { for (int i = position; i < limit; i += 3) {
buffer.put(inputBuffer.get(i + 1)); buffer.put(inputBuffer.get(i + 1));
buffer.put(inputBuffer.get(i + 2)); buffer.put(inputBuffer.get(i + 2));
} }
break; break;
case C.ENCODING_PCM_32BIT: case C.ENCODING_PCM_32BIT:
// 32->16 bit resampling. Drop the two least significant bytes. // 32 -> 16 bit resampling. Drop the two least significant bytes.
for (int i = position; i < limit; i += 4) { for (int i = position; i < limit; i += 4) {
buffer.put(inputBuffer.get(i + 2)); buffer.put(inputBuffer.get(i + 2));
buffer.put(inputBuffer.get(i + 3)); buffer.put(inputBuffer.get(i + 3));
...@@ -92,8 +103,6 @@ import java.nio.ByteBuffer; ...@@ -92,8 +103,6 @@ import java.nio.ByteBuffer;
break; break;
case C.ENCODING_PCM_16BIT: case C.ENCODING_PCM_16BIT:
case C.ENCODING_PCM_FLOAT: case C.ENCODING_PCM_FLOAT:
case C.ENCODING_PCM_A_LAW:
case C.ENCODING_PCM_MU_LAW:
case C.ENCODING_INVALID: case C.ENCODING_INVALID:
case Format.NO_VALUE: case Format.NO_VALUE:
default: default:
......
...@@ -32,15 +32,17 @@ public final class WavUtil { ...@@ -32,15 +32,17 @@ public final class WavUtil {
public static final int DATA_FOURCC = 0x64617461; public static final int DATA_FOURCC = 0x64617461;
/** WAVE type value for integer PCM audio data. */ /** WAVE type value for integer PCM audio data. */
private static final int TYPE_PCM = 0x0001; public static final int TYPE_PCM = 0x0001;
/** WAVE type value for float PCM audio data. */ /** WAVE type value for float PCM audio data. */
private static final int TYPE_FLOAT = 0x0003; public static final int TYPE_FLOAT = 0x0003;
/** WAVE type value for 8-bit ITU-T G.711 A-law audio data. */ /** WAVE type value for 8-bit ITU-T G.711 A-law audio data. */
private static final int TYPE_A_LAW = 0x0006; public static final int TYPE_ALAW = 0x0006;
/** WAVE type value for 8-bit ITU-T G.711 mu-law audio data. */ /** WAVE type value for 8-bit ITU-T G.711 mu-law audio data. */
private static final int TYPE_MU_LAW = 0x0007; public static final int TYPE_MLAW = 0x0007;
/** WAVE type value for IMA ADPCM audio data. */
public static final int TYPE_IMA_ADPCM = 0x0011;
/** WAVE type value for extended WAVE format. */ /** WAVE type value for extended WAVE format. */
private static final int TYPE_WAVE_FORMAT_EXTENSIBLE = 0xFFFE; public static final int TYPE_WAVE_FORMAT_EXTENSIBLE = 0xFFFE;
/** /**
* Returns the WAVE format type value for the given {@link C.PcmEncoding}. * Returns the WAVE format type value for the given {@link C.PcmEncoding}.
...@@ -57,10 +59,6 @@ public final class WavUtil { ...@@ -57,10 +59,6 @@ public final class WavUtil {
case C.ENCODING_PCM_24BIT: case C.ENCODING_PCM_24BIT:
case C.ENCODING_PCM_32BIT: case C.ENCODING_PCM_32BIT:
return TYPE_PCM; return TYPE_PCM;
case C.ENCODING_PCM_A_LAW:
return TYPE_A_LAW;
case C.ENCODING_PCM_MU_LAW:
return TYPE_MU_LAW;
case C.ENCODING_PCM_FLOAT: case C.ENCODING_PCM_FLOAT:
return TYPE_FLOAT; return TYPE_FLOAT;
case C.ENCODING_INVALID: case C.ENCODING_INVALID:
...@@ -81,10 +79,6 @@ public final class WavUtil { ...@@ -81,10 +79,6 @@ public final class WavUtil {
return Util.getPcmEncoding(bitsPerSample); return Util.getPcmEncoding(bitsPerSample);
case TYPE_FLOAT: case TYPE_FLOAT:
return bitsPerSample == 32 ? C.ENCODING_PCM_FLOAT : C.ENCODING_INVALID; return bitsPerSample == 32 ? C.ENCODING_PCM_FLOAT : C.ENCODING_INVALID;
case TYPE_A_LAW:
return C.ENCODING_PCM_A_LAW;
case TYPE_MU_LAW:
return C.ENCODING_PCM_MU_LAW;
default: default:
return C.ENCODING_INVALID; return C.ENCODING_INVALID;
} }
......
...@@ -64,10 +64,18 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { ...@@ -64,10 +64,18 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
@Nullable Constructor<? extends Extractor> flacExtensionExtractorConstructor = null; @Nullable Constructor<? extends Extractor> flacExtensionExtractorConstructor = null;
try { try {
// LINT.IfChange // LINT.IfChange
flacExtensionExtractorConstructor = @SuppressWarnings("nullness:argument.type.incompatible")
Class.forName("com.google.android.exoplayer2.ext.flac.FlacExtractor") boolean isFlacNativeLibraryAvailable =
.asSubclass(Extractor.class) Boolean.TRUE.equals(
.getConstructor(); Class.forName("com.google.android.exoplayer2.ext.flac.FlacLibrary")
.getMethod("isAvailable")
.invoke(/* obj= */ null));
if (isFlacNativeLibraryAvailable) {
flacExtensionExtractorConstructor =
Class.forName("com.google.android.exoplayer2.ext.flac.FlacExtractor")
.asSubclass(Extractor.class)
.getConstructor();
}
// LINT.ThenChange(../../../../../../../../proguard-rules.txt) // LINT.ThenChange(../../../../../../../../proguard-rules.txt)
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
// Expected if the app was built without the FLAC extension. // Expected if the app was built without the FLAC extension.
......
...@@ -167,7 +167,7 @@ public final class FlacFrameReader { ...@@ -167,7 +167,7 @@ public final class FlacFrameReader {
* @param data The array to read the data from, whose position must correspond to the block size * @param data The array to read the data from, whose position must correspond to the block size
* bits. * bits.
* @param blockSizeKey The key in the block size lookup table. * @param blockSizeKey The key in the block size lookup table.
* @return The block size in samples. * @return The block size in samples, or -1 if the {@code blockSizeKey} is invalid.
*/ */
public static int readFrameBlockSizeSamplesFromKey(ParsableByteArray data, int blockSizeKey) { public static int readFrameBlockSizeSamplesFromKey(ParsableByteArray data, int blockSizeKey) {
switch (blockSizeKey) { switch (blockSizeKey) {
......
...@@ -256,15 +256,18 @@ public final class FlacExtractor implements Extractor { ...@@ -256,15 +256,18 @@ public final class FlacExtractor implements Extractor {
// Copy more bytes into the buffer. // Copy more bytes into the buffer.
int currentLimit = buffer.limit(); int currentLimit = buffer.limit();
int bytesRead = boolean foundEndOfInput = false;
input.read( if (currentLimit < BUFFER_LENGTH) {
buffer.data, /* offset= */ currentLimit, /* length= */ BUFFER_LENGTH - currentLimit); int bytesRead =
boolean foundEndOfInput = bytesRead == C.RESULT_END_OF_INPUT; input.read(
if (!foundEndOfInput) { buffer.data, /* offset= */ currentLimit, /* length= */ BUFFER_LENGTH - currentLimit);
buffer.setLimit(currentLimit + bytesRead); foundEndOfInput = bytesRead == C.RESULT_END_OF_INPUT;
} else if (buffer.bytesLeft() == 0) { if (!foundEndOfInput) {
outputSampleMetadata(); buffer.setLimit(currentLimit + bytesRead);
return Extractor.RESULT_END_OF_INPUT; } else if (buffer.bytesLeft() == 0) {
outputSampleMetadata();
return Extractor.RESULT_END_OF_INPUT;
}
} }
// Search for a frame. // Search for a frame.
...@@ -272,7 +275,7 @@ public final class FlacExtractor implements Extractor { ...@@ -272,7 +275,7 @@ public final class FlacExtractor implements Extractor {
// Skip frame search on the bytes within the minimum frame size. // Skip frame search on the bytes within the minimum frame size.
if (currentFrameBytesWritten < minFrameSize) { if (currentFrameBytesWritten < minFrameSize) {
buffer.skipBytes(Math.min(minFrameSize, buffer.bytesLeft())); buffer.skipBytes(Math.min(minFrameSize - currentFrameBytesWritten, buffer.bytesLeft()));
} }
long nextFrameFirstSampleNumber = findFrame(buffer, foundEndOfInput); long nextFrameFirstSampleNumber = findFrame(buffer, foundEndOfInput);
......
...@@ -69,9 +69,20 @@ import java.util.Collections; ...@@ -69,9 +69,20 @@ import java.util.Collections;
} else if (audioFormat == AUDIO_FORMAT_ALAW || audioFormat == AUDIO_FORMAT_ULAW) { } else if (audioFormat == AUDIO_FORMAT_ALAW || audioFormat == AUDIO_FORMAT_ULAW) {
String type = audioFormat == AUDIO_FORMAT_ALAW ? MimeTypes.AUDIO_ALAW String type = audioFormat == AUDIO_FORMAT_ALAW ? MimeTypes.AUDIO_ALAW
: MimeTypes.AUDIO_MLAW; : MimeTypes.AUDIO_MLAW;
int pcmEncoding = (header & 0x01) == 1 ? C.ENCODING_PCM_16BIT : C.ENCODING_PCM_8BIT; Format format =
Format format = Format.createAudioSampleFormat(null, type, null, Format.NO_VALUE, Format.createAudioSampleFormat(
Format.NO_VALUE, 1, 8000, pcmEncoding, null, null, 0, null); /* id= */ null,
/* sampleMimeType= */ type,
/* codecs= */ null,
/* bitrate= */ Format.NO_VALUE,
/* maxInputSize= */ Format.NO_VALUE,
/* channelCount= */ 1,
/* sampleRate= */ 8000,
/* pcmEncoding= */ Format.NO_VALUE,
/* initializationData= */ null,
/* drmInitData= */ null,
/* selectionFlags= */ 0,
/* language= */ null);
output.format(format); output.format(format);
hasOutputFormat = true; hasOutputFormat = true;
} else if (audioFormat != AUDIO_FORMAT_AAC) { } else if (audioFormat != AUDIO_FORMAT_AAC) {
......
...@@ -1250,10 +1250,10 @@ public class MatroskaExtractor implements Extractor { ...@@ -1250,10 +1250,10 @@ public class MatroskaExtractor implements Extractor {
if (CODEC_ID_SUBRIP.equals(track.codecId) || CODEC_ID_ASS.equals(track.codecId)) { if (CODEC_ID_SUBRIP.equals(track.codecId) || CODEC_ID_ASS.equals(track.codecId)) {
if (blockSampleCount > 1) { if (blockSampleCount > 1) {
Log.w(TAG, "Skipping subtitle sample in laced block."); Log.w(TAG, "Skipping subtitle sample in laced block.");
} else if (durationUs == C.TIME_UNSET) { } else if (blockDurationUs == C.TIME_UNSET) {
Log.w(TAG, "Skipping subtitle sample with no duration."); Log.w(TAG, "Skipping subtitle sample with no duration.");
} else { } else {
setSubtitleEndTime(track.codecId, durationUs, subtitleSample.data); setSubtitleEndTime(track.codecId, blockDurationUs, subtitleSample.data);
// Note: If we ever want to support DRM protected subtitles then we'll need to output the // Note: If we ever want to support DRM protected subtitles then we'll need to output the
// appropriate encryption data here. // appropriate encryption data here.
track.output.sampleData(subtitleSample, subtitleSample.limit()); track.output.sampleData(subtitleSample, subtitleSample.limit());
...@@ -1829,10 +1829,8 @@ public class MatroskaExtractor implements Extractor { ...@@ -1829,10 +1829,8 @@ public class MatroskaExtractor implements Extractor {
chunkSize += size; chunkSize += size;
chunkOffset = offset; // The offset is to the end of the sample. chunkOffset = offset; // The offset is to the end of the sample.
if (chunkSampleCount >= Ac3Util.TRUEHD_RECHUNK_SAMPLE_COUNT) { if (chunkSampleCount >= Ac3Util.TRUEHD_RECHUNK_SAMPLE_COUNT) {
// We haven't read enough samples to output a chunk. outputPendingSampleMetadata(track);
return;
} }
outputPendingSampleMetadata(track);
} }
public void outputPendingSampleMetadata(Track track) { public void outputPendingSampleMetadata(Track track) {
......
...@@ -379,6 +379,9 @@ import java.util.List; ...@@ -379,6 +379,9 @@ import java.util.List;
@SuppressWarnings("ConstantCaseForConstants") @SuppressWarnings("ConstantCaseForConstants")
public static final int TYPE_dfLa = 0x64664c61; public static final int TYPE_dfLa = 0x64664c61;
@SuppressWarnings("ConstantCaseForConstants")
public static final int TYPE_twos = 0x74776f73;
public final int type; public final int type;
public Atom(int type) { public Atom(int type) {
......
...@@ -798,6 +798,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -798,6 +798,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|| childAtomType == Atom.TYPE_sawb || childAtomType == Atom.TYPE_sawb
|| childAtomType == Atom.TYPE_lpcm || childAtomType == Atom.TYPE_lpcm
|| childAtomType == Atom.TYPE_sowt || childAtomType == Atom.TYPE_sowt
|| childAtomType == Atom.TYPE_twos
|| childAtomType == Atom.TYPE__mp3 || childAtomType == Atom.TYPE__mp3
|| childAtomType == Atom.TYPE_alac || childAtomType == Atom.TYPE_alac
|| childAtomType == Atom.TYPE_alaw || childAtomType == Atom.TYPE_alaw
...@@ -1086,6 +1087,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -1086,6 +1087,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
int channelCount; int channelCount;
int sampleRate; int sampleRate;
@C.PcmEncoding int pcmEncoding = Format.NO_VALUE;
if (quickTimeSoundDescriptionVersion == 0 || quickTimeSoundDescriptionVersion == 1) { if (quickTimeSoundDescriptionVersion == 0 || quickTimeSoundDescriptionVersion == 1) {
channelCount = parent.readUnsignedShort(); channelCount = parent.readUnsignedShort();
...@@ -1147,6 +1149,10 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -1147,6 +1149,10 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
mimeType = MimeTypes.AUDIO_AMR_WB; mimeType = MimeTypes.AUDIO_AMR_WB;
} else if (atomType == Atom.TYPE_lpcm || atomType == Atom.TYPE_sowt) { } else if (atomType == Atom.TYPE_lpcm || atomType == Atom.TYPE_sowt) {
mimeType = MimeTypes.AUDIO_RAW; mimeType = MimeTypes.AUDIO_RAW;
pcmEncoding = C.ENCODING_PCM_16BIT;
} else if (atomType == Atom.TYPE_twos) {
mimeType = MimeTypes.AUDIO_RAW;
pcmEncoding = C.ENCODING_PCM_16BIT_BIG_ENDIAN;
} else if (atomType == Atom.TYPE__mp3) { } else if (atomType == Atom.TYPE__mp3) {
mimeType = MimeTypes.AUDIO_MPEG; mimeType = MimeTypes.AUDIO_MPEG;
} else if (atomType == Atom.TYPE_alac) { } else if (atomType == Atom.TYPE_alac) {
...@@ -1233,9 +1239,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -1233,9 +1239,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
} }
if (out.format == null && mimeType != null) { if (out.format == null && mimeType != null) {
// TODO: Determine the correct PCM encoding.
@C.PcmEncoding int pcmEncoding =
MimeTypes.AUDIO_RAW.equals(mimeType) ? C.ENCODING_PCM_16BIT : Format.NO_VALUE;
out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null, out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null,
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, pcmEncoding, Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, pcmEncoding,
initializationData == null ? null : Collections.singletonList(initializationData), initializationData == null ? null : Collections.singletonList(initializationData),
......
...@@ -168,7 +168,6 @@ public class FragmentedMp4Extractor implements Extractor { ...@@ -168,7 +168,6 @@ public class FragmentedMp4Extractor implements Extractor {
private int sampleBytesWritten; private int sampleBytesWritten;
private int sampleCurrentNalBytesRemaining; private int sampleCurrentNalBytesRemaining;
private boolean processSeiNalUnitPayload; private boolean processSeiNalUnitPayload;
private boolean isAc4HeaderRequired;
// Extractor output. // Extractor output.
@MonotonicNonNull private ExtractorOutput extractorOutput; @MonotonicNonNull private ExtractorOutput extractorOutput;
...@@ -302,7 +301,6 @@ public class FragmentedMp4Extractor implements Extractor { ...@@ -302,7 +301,6 @@ public class FragmentedMp4Extractor implements Extractor {
pendingMetadataSampleBytes = 0; pendingMetadataSampleBytes = 0;
pendingSeekTimeUs = timeUs; pendingSeekTimeUs = timeUs;
containerAtoms.clear(); containerAtoms.clear();
isAc4HeaderRequired = false;
enterReadingAtomHeaderState(); enterReadingAtomHeaderState();
} }
...@@ -1270,11 +1268,18 @@ public class FragmentedMp4Extractor implements Extractor { ...@@ -1270,11 +1268,18 @@ public class FragmentedMp4Extractor implements Extractor {
} }
sampleBytesWritten = currentTrackBundle.outputSampleEncryptionData(); sampleBytesWritten = currentTrackBundle.outputSampleEncryptionData();
sampleSize += sampleBytesWritten; sampleSize += sampleBytesWritten;
<<<<<<< HEAD
outputSampleEncryptionDataSize = sampleBytesWritten; outputSampleEncryptionDataSize = sampleBytesWritten;
=======
if (MimeTypes.AUDIO_AC4.equals(currentTrackBundle.track.format.sampleMimeType)) {
Ac4Util.getAc4SampleHeader(sampleSize, scratch);
currentTrackBundle.output.sampleData(scratch, Ac4Util.SAMPLE_HEADER_SIZE);
sampleBytesWritten += Ac4Util.SAMPLE_HEADER_SIZE;
sampleSize += Ac4Util.SAMPLE_HEADER_SIZE;
}
>>>>>>> 4f15cfaa78f8053c4bf83ef0871df98d29fa6e0b
parserState = STATE_READING_SAMPLE_CONTINUE; parserState = STATE_READING_SAMPLE_CONTINUE;
sampleCurrentNalBytesRemaining = 0; sampleCurrentNalBytesRemaining = 0;
isAc4HeaderRequired =
MimeTypes.AUDIO_AC4.equals(currentTrackBundle.track.format.sampleMimeType);
} }
TrackFragment fragment = currentTrackBundle.fragment; TrackFragment fragment = currentTrackBundle.fragment;
...@@ -1339,6 +1344,7 @@ public class FragmentedMp4Extractor implements Extractor { ...@@ -1339,6 +1344,7 @@ public class FragmentedMp4Extractor implements Extractor {
} }
} }
} else { } else {
<<<<<<< HEAD
if (isAc4HeaderRequired) { if (isAc4HeaderRequired) {
Ac4Util.getAc4SampleHeader(sampleSize - outputSampleEncryptionDataSize, scratch); Ac4Util.getAc4SampleHeader(sampleSize - outputSampleEncryptionDataSize, scratch);
int length = scratch.limit(); int length = scratch.limit();
...@@ -1347,6 +1353,8 @@ public class FragmentedMp4Extractor implements Extractor { ...@@ -1347,6 +1353,8 @@ public class FragmentedMp4Extractor implements Extractor {
sampleBytesWritten += length; sampleBytesWritten += length;
isAc4HeaderRequired = false; isAc4HeaderRequired = false;
} }
=======
>>>>>>> 4f15cfaa78f8053c4bf83ef0871df98d29fa6e0b
while (sampleBytesWritten < sampleSize) { while (sampleBytesWritten < sampleSize) {
int writtenBytes = output.sampleData(input, sampleSize - sampleBytesWritten, false); int writtenBytes = output.sampleData(input, sampleSize - sampleBytesWritten, false);
sampleBytesWritten += writtenBytes; sampleBytesWritten += writtenBytes;
......
...@@ -110,9 +110,9 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -110,9 +110,9 @@ public final class Mp4Extractor implements Extractor, SeekMap {
@Nullable private ParsableByteArray atomData; @Nullable private ParsableByteArray atomData;
private int sampleTrackIndex; private int sampleTrackIndex;
private int sampleBytesRead;
private int sampleBytesWritten; private int sampleBytesWritten;
private int sampleCurrentNalBytesRemaining; private int sampleCurrentNalBytesRemaining;
private boolean isAc4HeaderRequired;
// Extractor outputs. // Extractor outputs.
@MonotonicNonNull private ExtractorOutput extractorOutput; @MonotonicNonNull private ExtractorOutput extractorOutput;
...@@ -160,9 +160,9 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -160,9 +160,9 @@ public final class Mp4Extractor implements Extractor, SeekMap {
containerAtoms.clear(); containerAtoms.clear();
atomHeaderBytesRead = 0; atomHeaderBytesRead = 0;
sampleTrackIndex = C.INDEX_UNSET; sampleTrackIndex = C.INDEX_UNSET;
sampleBytesRead = 0;
sampleBytesWritten = 0; sampleBytesWritten = 0;
sampleCurrentNalBytesRemaining = 0; sampleCurrentNalBytesRemaining = 0;
isAc4HeaderRequired = false;
if (position == 0) { if (position == 0) {
enterReadingAtomHeaderState(); enterReadingAtomHeaderState();
} else if (tracks != null) { } else if (tracks != null) {
...@@ -507,15 +507,13 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -507,15 +507,13 @@ public final class Mp4Extractor implements Extractor, SeekMap {
if (sampleTrackIndex == C.INDEX_UNSET) { if (sampleTrackIndex == C.INDEX_UNSET) {
return RESULT_END_OF_INPUT; return RESULT_END_OF_INPUT;
} }
isAc4HeaderRequired =
MimeTypes.AUDIO_AC4.equals(tracks[sampleTrackIndex].track.format.sampleMimeType);
} }
Mp4Track track = tracks[sampleTrackIndex]; Mp4Track track = tracks[sampleTrackIndex];
TrackOutput trackOutput = track.trackOutput; TrackOutput trackOutput = track.trackOutput;
int sampleIndex = track.sampleIndex; int sampleIndex = track.sampleIndex;
long position = track.sampleTable.offsets[sampleIndex]; long position = track.sampleTable.offsets[sampleIndex];
int sampleSize = track.sampleTable.sizes[sampleIndex]; int sampleSize = track.sampleTable.sizes[sampleIndex];
long skipAmount = position - inputPosition + sampleBytesWritten; long skipAmount = position - inputPosition + sampleBytesRead;
if (skipAmount < 0 || skipAmount >= RELOAD_MINIMUM_SEEK_DISTANCE) { if (skipAmount < 0 || skipAmount >= RELOAD_MINIMUM_SEEK_DISTANCE) {
positionHolder.position = position; positionHolder.position = position;
return RESULT_SEEK; return RESULT_SEEK;
...@@ -543,6 +541,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -543,6 +541,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
if (sampleCurrentNalBytesRemaining == 0) { if (sampleCurrentNalBytesRemaining == 0) {
// Read the NAL length so that we know where we find the next one. // Read the NAL length so that we know where we find the next one.
input.readFully(nalLengthData, nalUnitLengthFieldLengthDiff, nalUnitLengthFieldLength); input.readFully(nalLengthData, nalUnitLengthFieldLengthDiff, nalUnitLengthFieldLength);
sampleBytesRead += nalUnitLengthFieldLength;
nalLength.setPosition(0); nalLength.setPosition(0);
int nalLengthInt = nalLength.readInt(); int nalLengthInt = nalLength.readInt();
if (nalLengthInt < 0) { if (nalLengthInt < 0) {
...@@ -557,21 +556,23 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -557,21 +556,23 @@ public final class Mp4Extractor implements Extractor, SeekMap {
} else { } else {
// Write the payload of the NAL unit. // Write the payload of the NAL unit.
int writtenBytes = trackOutput.sampleData(input, sampleCurrentNalBytesRemaining, false); int writtenBytes = trackOutput.sampleData(input, sampleCurrentNalBytesRemaining, false);
sampleBytesRead += writtenBytes;
sampleBytesWritten += writtenBytes; sampleBytesWritten += writtenBytes;
sampleCurrentNalBytesRemaining -= writtenBytes; sampleCurrentNalBytesRemaining -= writtenBytes;
} }
} }
} else { } else {
if (isAc4HeaderRequired) { if (MimeTypes.AUDIO_AC4.equals(track.track.format.sampleMimeType)) {
Ac4Util.getAc4SampleHeader(sampleSize, scratch); if (sampleBytesWritten == 0) {
int length = scratch.limit(); Ac4Util.getAc4SampleHeader(sampleSize, scratch);
trackOutput.sampleData(scratch, length); trackOutput.sampleData(scratch, Ac4Util.SAMPLE_HEADER_SIZE);
sampleSize += length; sampleBytesWritten += Ac4Util.SAMPLE_HEADER_SIZE;
sampleBytesWritten += length; }
isAc4HeaderRequired = false; sampleSize += Ac4Util.SAMPLE_HEADER_SIZE;
} }
while (sampleBytesWritten < sampleSize) { while (sampleBytesWritten < sampleSize) {
int writtenBytes = trackOutput.sampleData(input, sampleSize - sampleBytesWritten, false); int writtenBytes = trackOutput.sampleData(input, sampleSize - sampleBytesWritten, false);
sampleBytesRead += writtenBytes;
sampleBytesWritten += writtenBytes; sampleBytesWritten += writtenBytes;
sampleCurrentNalBytesRemaining -= writtenBytes; sampleCurrentNalBytesRemaining -= writtenBytes;
} }
...@@ -580,6 +581,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -580,6 +581,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
track.sampleTable.flags[sampleIndex], sampleSize, 0, null); track.sampleTable.flags[sampleIndex], sampleSize, 0, null);
track.sampleIndex++; track.sampleIndex++;
sampleTrackIndex = C.INDEX_UNSET; sampleTrackIndex = C.INDEX_UNSET;
sampleBytesRead = 0;
sampleBytesWritten = 0; sampleBytesWritten = 0;
sampleCurrentNalBytesRemaining = 0; sampleCurrentNalBytesRemaining = 0;
return RESULT_CONTINUE; return RESULT_CONTINUE;
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.extractor.ts; package com.google.android.exoplayer2.extractor.ts;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.Ac3Util; import com.google.android.exoplayer2.audio.Ac3Util;
...@@ -23,11 +24,15 @@ import com.google.android.exoplayer2.audio.Ac3Util.SyncFrameInfo; ...@@ -23,11 +24,15 @@ import com.google.android.exoplayer2.audio.Ac3Util.SyncFrameInfo;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** /**
* Parses a continuous (E-)AC-3 byte stream and extracts individual samples. * Parses a continuous (E-)AC-3 byte stream and extracts individual samples.
...@@ -47,10 +52,10 @@ public final class Ac3Reader implements ElementaryStreamReader { ...@@ -47,10 +52,10 @@ public final class Ac3Reader implements ElementaryStreamReader {
private final ParsableBitArray headerScratchBits; private final ParsableBitArray headerScratchBits;
private final ParsableByteArray headerScratchBytes; private final ParsableByteArray headerScratchBytes;
private final String language; @Nullable private final String language;
private String trackFormatId; @MonotonicNonNull private String formatId;
private TrackOutput output; @MonotonicNonNull private TrackOutput output;
@State private int state; @State private int state;
private int bytesRead; private int bytesRead;
...@@ -60,7 +65,7 @@ public final class Ac3Reader implements ElementaryStreamReader { ...@@ -60,7 +65,7 @@ public final class Ac3Reader implements ElementaryStreamReader {
// Used when parsing the header. // Used when parsing the header.
private long sampleDurationUs; private long sampleDurationUs;
private Format format; @MonotonicNonNull private Format format;
private int sampleSize; private int sampleSize;
// Used when reading the samples. // Used when reading the samples.
...@@ -78,7 +83,7 @@ public final class Ac3Reader implements ElementaryStreamReader { ...@@ -78,7 +83,7 @@ public final class Ac3Reader implements ElementaryStreamReader {
* *
* @param language Track language. * @param language Track language.
*/ */
public Ac3Reader(String language) { public Ac3Reader(@Nullable String language) {
headerScratchBits = new ParsableBitArray(new byte[HEADER_SIZE]); headerScratchBits = new ParsableBitArray(new byte[HEADER_SIZE]);
headerScratchBytes = new ParsableByteArray(headerScratchBits.data); headerScratchBytes = new ParsableByteArray(headerScratchBits.data);
state = STATE_FINDING_SYNC; state = STATE_FINDING_SYNC;
...@@ -95,7 +100,7 @@ public final class Ac3Reader implements ElementaryStreamReader { ...@@ -95,7 +100,7 @@ public final class Ac3Reader implements ElementaryStreamReader {
@Override @Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator generator) { public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator generator) {
generator.generateNewId(); generator.generateNewId();
trackFormatId = generator.getFormatId(); formatId = generator.getFormatId();
output = extractorOutput.track(generator.getTrackId(), C.TRACK_TYPE_AUDIO); output = extractorOutput.track(generator.getTrackId(), C.TRACK_TYPE_AUDIO);
} }
...@@ -106,6 +111,7 @@ public final class Ac3Reader implements ElementaryStreamReader { ...@@ -106,6 +111,7 @@ public final class Ac3Reader implements ElementaryStreamReader {
@Override @Override
public void consume(ParsableByteArray data) { public void consume(ParsableByteArray data) {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
while (data.bytesLeft() > 0) { while (data.bytesLeft() > 0) {
switch (state) { switch (state) {
case STATE_FINDING_SYNC: case STATE_FINDING_SYNC:
...@@ -185,19 +191,28 @@ public final class Ac3Reader implements ElementaryStreamReader { ...@@ -185,19 +191,28 @@ public final class Ac3Reader implements ElementaryStreamReader {
return false; return false;
} }
/** /** Parses the sample header. */
* Parses the sample header. @RequiresNonNull("output")
*/
@SuppressWarnings("ReferenceEquality")
private void parseHeader() { private void parseHeader() {
headerScratchBits.setPosition(0); headerScratchBits.setPosition(0);
SyncFrameInfo frameInfo = Ac3Util.parseAc3SyncframeInfo(headerScratchBits); SyncFrameInfo frameInfo = Ac3Util.parseAc3SyncframeInfo(headerScratchBits);
if (format == null || frameInfo.channelCount != format.channelCount if (format == null
|| frameInfo.channelCount != format.channelCount
|| frameInfo.sampleRate != format.sampleRate || frameInfo.sampleRate != format.sampleRate
|| frameInfo.mimeType != format.sampleMimeType) { || Util.areEqual(frameInfo.mimeType, format.sampleMimeType)) {
format = Format.createAudioSampleFormat(trackFormatId, frameInfo.mimeType, null, format =
Format.NO_VALUE, Format.NO_VALUE, frameInfo.channelCount, frameInfo.sampleRate, null, Format.createAudioSampleFormat(
null, 0, language); formatId,
frameInfo.mimeType,
null,
Format.NO_VALUE,
Format.NO_VALUE,
frameInfo.channelCount,
frameInfo.sampleRate,
null,
null,
0,
language);
output.format(format); output.format(format);
} }
sampleSize = frameInfo.frameSize; sampleSize = frameInfo.frameSize;
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.extractor.ts; package com.google.android.exoplayer2.extractor.ts;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.Ac4Util; import com.google.android.exoplayer2.audio.Ac4Util;
...@@ -23,12 +24,15 @@ import com.google.android.exoplayer2.audio.Ac4Util.SyncFrameInfo; ...@@ -23,12 +24,15 @@ import com.google.android.exoplayer2.audio.Ac4Util.SyncFrameInfo;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** Parses a continuous AC-4 byte stream and extracts individual samples. */ /** Parses a continuous AC-4 byte stream and extracts individual samples. */
public final class Ac4Reader implements ElementaryStreamReader { public final class Ac4Reader implements ElementaryStreamReader {
...@@ -44,10 +48,10 @@ public final class Ac4Reader implements ElementaryStreamReader { ...@@ -44,10 +48,10 @@ public final class Ac4Reader implements ElementaryStreamReader {
private final ParsableBitArray headerScratchBits; private final ParsableBitArray headerScratchBits;
private final ParsableByteArray headerScratchBytes; private final ParsableByteArray headerScratchBytes;
private final String language; @Nullable private final String language;
private String trackFormatId; @MonotonicNonNull private String formatId;
private TrackOutput output; @MonotonicNonNull private TrackOutput output;
@State private int state; @State private int state;
private int bytesRead; private int bytesRead;
...@@ -58,7 +62,7 @@ public final class Ac4Reader implements ElementaryStreamReader { ...@@ -58,7 +62,7 @@ public final class Ac4Reader implements ElementaryStreamReader {
// Used when parsing the header. // Used when parsing the header.
private long sampleDurationUs; private long sampleDurationUs;
private Format format; @MonotonicNonNull private Format format;
private int sampleSize; private int sampleSize;
// Used when reading the samples. // Used when reading the samples.
...@@ -74,7 +78,7 @@ public final class Ac4Reader implements ElementaryStreamReader { ...@@ -74,7 +78,7 @@ public final class Ac4Reader implements ElementaryStreamReader {
* *
* @param language Track language. * @param language Track language.
*/ */
public Ac4Reader(String language) { public Ac4Reader(@Nullable String language) {
headerScratchBits = new ParsableBitArray(new byte[Ac4Util.HEADER_SIZE_FOR_PARSER]); headerScratchBits = new ParsableBitArray(new byte[Ac4Util.HEADER_SIZE_FOR_PARSER]);
headerScratchBytes = new ParsableByteArray(headerScratchBits.data); headerScratchBytes = new ParsableByteArray(headerScratchBits.data);
state = STATE_FINDING_SYNC; state = STATE_FINDING_SYNC;
...@@ -95,7 +99,7 @@ public final class Ac4Reader implements ElementaryStreamReader { ...@@ -95,7 +99,7 @@ public final class Ac4Reader implements ElementaryStreamReader {
@Override @Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator generator) { public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator generator) {
generator.generateNewId(); generator.generateNewId();
trackFormatId = generator.getFormatId(); formatId = generator.getFormatId();
output = extractorOutput.track(generator.getTrackId(), C.TRACK_TYPE_AUDIO); output = extractorOutput.track(generator.getTrackId(), C.TRACK_TYPE_AUDIO);
} }
...@@ -106,6 +110,7 @@ public final class Ac4Reader implements ElementaryStreamReader { ...@@ -106,6 +110,7 @@ public final class Ac4Reader implements ElementaryStreamReader {
@Override @Override
public void consume(ParsableByteArray data) { public void consume(ParsableByteArray data) {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
while (data.bytesLeft() > 0) { while (data.bytesLeft() > 0) {
switch (state) { switch (state) {
case STATE_FINDING_SYNC: case STATE_FINDING_SYNC:
...@@ -185,7 +190,7 @@ public final class Ac4Reader implements ElementaryStreamReader { ...@@ -185,7 +190,7 @@ public final class Ac4Reader implements ElementaryStreamReader {
} }
/** Parses the sample header. */ /** Parses the sample header. */
@SuppressWarnings("ReferenceEquality") @RequiresNonNull("output")
private void parseHeader() { private void parseHeader() {
headerScratchBits.setPosition(0); headerScratchBits.setPosition(0);
SyncFrameInfo frameInfo = Ac4Util.parseAc4SyncframeInfo(headerScratchBits); SyncFrameInfo frameInfo = Ac4Util.parseAc4SyncframeInfo(headerScratchBits);
...@@ -195,7 +200,7 @@ public final class Ac4Reader implements ElementaryStreamReader { ...@@ -195,7 +200,7 @@ public final class Ac4Reader implements ElementaryStreamReader {
|| !MimeTypes.AUDIO_AC4.equals(format.sampleMimeType)) { || !MimeTypes.AUDIO_AC4.equals(format.sampleMimeType)) {
format = format =
Format.createAudioSampleFormat( Format.createAudioSampleFormat(
trackFormatId, formatId,
MimeTypes.AUDIO_AC4, MimeTypes.AUDIO_AC4,
/* codecs= */ null, /* codecs= */ null,
/* bitrate= */ Format.NO_VALUE, /* bitrate= */ Format.NO_VALUE,
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.extractor.ts; package com.google.android.exoplayer2.extractor.ts;
import android.util.Pair; import android.util.Pair;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
...@@ -23,13 +24,18 @@ import com.google.android.exoplayer2.extractor.DummyTrackOutput; ...@@ -23,13 +24,18 @@ import com.google.android.exoplayer2.extractor.DummyTrackOutput;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.CodecSpecificDataUtil; import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** /**
* Parses a continuous ADTS byte stream and extracts individual frames. * Parses a continuous ADTS byte stream and extracts individual frames.
...@@ -62,11 +68,11 @@ public final class AdtsReader implements ElementaryStreamReader { ...@@ -62,11 +68,11 @@ public final class AdtsReader implements ElementaryStreamReader {
private final boolean exposeId3; private final boolean exposeId3;
private final ParsableBitArray adtsScratch; private final ParsableBitArray adtsScratch;
private final ParsableByteArray id3HeaderBuffer; private final ParsableByteArray id3HeaderBuffer;
private final String language; @Nullable private final String language;
private String formatId; @MonotonicNonNull private String formatId;
private TrackOutput output; @MonotonicNonNull private TrackOutput output;
private TrackOutput id3Output; @MonotonicNonNull private TrackOutput id3Output;
private int state; private int state;
private int bytesRead; private int bytesRead;
...@@ -90,7 +96,7 @@ public final class AdtsReader implements ElementaryStreamReader { ...@@ -90,7 +96,7 @@ public final class AdtsReader implements ElementaryStreamReader {
// Used when reading the samples. // Used when reading the samples.
private long timeUs; private long timeUs;
private TrackOutput currentOutput; @MonotonicNonNull private TrackOutput currentOutput;
private long currentSampleDuration; private long currentSampleDuration;
/** /**
...@@ -104,7 +110,7 @@ public final class AdtsReader implements ElementaryStreamReader { ...@@ -104,7 +110,7 @@ public final class AdtsReader implements ElementaryStreamReader {
* @param exposeId3 True if the reader should expose ID3 information. * @param exposeId3 True if the reader should expose ID3 information.
* @param language Track language. * @param language Track language.
*/ */
public AdtsReader(boolean exposeId3, String language) { public AdtsReader(boolean exposeId3, @Nullable String language) {
adtsScratch = new ParsableBitArray(new byte[HEADER_SIZE + CRC_SIZE]); adtsScratch = new ParsableBitArray(new byte[HEADER_SIZE + CRC_SIZE]);
id3HeaderBuffer = new ParsableByteArray(Arrays.copyOf(ID3_IDENTIFIER, ID3_HEADER_SIZE)); id3HeaderBuffer = new ParsableByteArray(Arrays.copyOf(ID3_IDENTIFIER, ID3_HEADER_SIZE));
setFindingSampleState(); setFindingSampleState();
...@@ -130,6 +136,7 @@ public final class AdtsReader implements ElementaryStreamReader { ...@@ -130,6 +136,7 @@ public final class AdtsReader implements ElementaryStreamReader {
idGenerator.generateNewId(); idGenerator.generateNewId();
formatId = idGenerator.getFormatId(); formatId = idGenerator.getFormatId();
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_AUDIO); output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_AUDIO);
currentOutput = output;
if (exposeId3) { if (exposeId3) {
idGenerator.generateNewId(); idGenerator.generateNewId();
id3Output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_METADATA); id3Output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_METADATA);
...@@ -147,6 +154,7 @@ public final class AdtsReader implements ElementaryStreamReader { ...@@ -147,6 +154,7 @@ public final class AdtsReader implements ElementaryStreamReader {
@Override @Override
public void consume(ParsableByteArray data) throws ParserException { public void consume(ParsableByteArray data) throws ParserException {
assertTracksCreated();
while (data.bytesLeft() > 0) { while (data.bytesLeft() > 0) {
switch (state) { switch (state) {
case STATE_FINDING_SAMPLE: case STATE_FINDING_SAMPLE:
...@@ -425,9 +433,8 @@ public final class AdtsReader implements ElementaryStreamReader { ...@@ -425,9 +433,8 @@ public final class AdtsReader implements ElementaryStreamReader {
return true; return true;
} }
/** /** Parses the Id3 header. */
* Parses the Id3 header. @RequiresNonNull("id3Output")
*/
private void parseId3Header() { private void parseId3Header() {
id3Output.sampleData(id3HeaderBuffer, ID3_HEADER_SIZE); id3Output.sampleData(id3HeaderBuffer, ID3_HEADER_SIZE);
id3HeaderBuffer.setPosition(ID3_SIZE_OFFSET); id3HeaderBuffer.setPosition(ID3_SIZE_OFFSET);
...@@ -435,9 +442,8 @@ public final class AdtsReader implements ElementaryStreamReader { ...@@ -435,9 +442,8 @@ public final class AdtsReader implements ElementaryStreamReader {
id3HeaderBuffer.readSynchSafeInt() + ID3_HEADER_SIZE); id3HeaderBuffer.readSynchSafeInt() + ID3_HEADER_SIZE);
} }
/** /** Parses the sample header. */
* Parses the sample header. @RequiresNonNull("output")
*/
private void parseAdtsHeader() throws ParserException { private void parseAdtsHeader() throws ParserException {
adtsScratch.setPosition(0); adtsScratch.setPosition(0);
...@@ -487,9 +493,8 @@ public final class AdtsReader implements ElementaryStreamReader { ...@@ -487,9 +493,8 @@ public final class AdtsReader implements ElementaryStreamReader {
setReadingSampleState(output, sampleDurationUs, 0, sampleSize); setReadingSampleState(output, sampleDurationUs, 0, sampleSize);
} }
/** /** Reads the rest of the sample */
* Reads the rest of the sample @RequiresNonNull("currentOutput")
*/
private void readSample(ParsableByteArray data) { private void readSample(ParsableByteArray data) {
int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead); int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead);
currentOutput.sampleData(data, bytesToRead); currentOutput.sampleData(data, bytesToRead);
...@@ -501,4 +506,10 @@ public final class AdtsReader implements ElementaryStreamReader { ...@@ -501,4 +506,10 @@ public final class AdtsReader implements ElementaryStreamReader {
} }
} }
@EnsuresNonNull({"output", "currentOutput", "id3Output"})
private void assertTracksCreated() {
Assertions.checkNotNull(output);
Util.castNonNull(currentOutput);
Util.castNonNull(id3Output);
}
} }
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.extractor.ts; ...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.extractor.ts;
import android.util.SparseArray; import android.util.SparseArray;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo;
import com.google.android.exoplayer2.text.cea.Cea708InitializationData; import com.google.android.exoplayer2.text.cea.Cea708InitializationData;
...@@ -134,6 +135,7 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact ...@@ -134,6 +135,7 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
return new SparseArray<>(); return new SparseArray<>();
} }
@Nullable
@Override @Override
public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) { public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) {
switch (streamType) { switch (streamType) {
...@@ -247,7 +249,7 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact ...@@ -247,7 +249,7 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
// Skip reserved (8). // Skip reserved (8).
scratchDescriptorData.skipBytes(1); scratchDescriptorData.skipBytes(1);
List<byte[]> initializationData = null; @Nullable List<byte[]> initializationData = null;
// The wide_aspect_ratio flag only has meaning for CEA-708. // The wide_aspect_ratio flag only has meaning for CEA-708.
if (isDigital) { if (isDigital) {
boolean isWideAspectRatio = (flags & 0x40) != 0; boolean isWideAspectRatio = (flags & 0x40) != 0;
......
...@@ -15,13 +15,17 @@ ...@@ -15,13 +15,17 @@
*/ */
package com.google.android.exoplayer2.extractor.ts; package com.google.android.exoplayer2.extractor.ts;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.DtsUtil; import com.google.android.exoplayer2.audio.DtsUtil;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** /**
* Parses a continuous DTS byte stream and extracts individual samples. * Parses a continuous DTS byte stream and extracts individual samples.
...@@ -35,10 +39,10 @@ public final class DtsReader implements ElementaryStreamReader { ...@@ -35,10 +39,10 @@ public final class DtsReader implements ElementaryStreamReader {
private static final int HEADER_SIZE = 18; private static final int HEADER_SIZE = 18;
private final ParsableByteArray headerScratchBytes; private final ParsableByteArray headerScratchBytes;
private final String language; @Nullable private final String language;
private String formatId; @MonotonicNonNull private String formatId;
private TrackOutput output; @MonotonicNonNull private TrackOutput output;
private int state; private int state;
private int bytesRead; private int bytesRead;
...@@ -48,7 +52,7 @@ public final class DtsReader implements ElementaryStreamReader { ...@@ -48,7 +52,7 @@ public final class DtsReader implements ElementaryStreamReader {
// Used when parsing the header. // Used when parsing the header.
private long sampleDurationUs; private long sampleDurationUs;
private Format format; @MonotonicNonNull private Format format;
private int sampleSize; private int sampleSize;
// Used when reading the samples. // Used when reading the samples.
...@@ -59,7 +63,7 @@ public final class DtsReader implements ElementaryStreamReader { ...@@ -59,7 +63,7 @@ public final class DtsReader implements ElementaryStreamReader {
* *
* @param language Track language. * @param language Track language.
*/ */
public DtsReader(String language) { public DtsReader(@Nullable String language) {
headerScratchBytes = new ParsableByteArray(new byte[HEADER_SIZE]); headerScratchBytes = new ParsableByteArray(new byte[HEADER_SIZE]);
state = STATE_FINDING_SYNC; state = STATE_FINDING_SYNC;
this.language = language; this.language = language;
...@@ -86,6 +90,7 @@ public final class DtsReader implements ElementaryStreamReader { ...@@ -86,6 +90,7 @@ public final class DtsReader implements ElementaryStreamReader {
@Override @Override
public void consume(ParsableByteArray data) { public void consume(ParsableByteArray data) {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
while (data.bytesLeft() > 0) { while (data.bytesLeft() > 0) {
switch (state) { switch (state) {
case STATE_FINDING_SYNC: case STATE_FINDING_SYNC:
...@@ -162,9 +167,8 @@ public final class DtsReader implements ElementaryStreamReader { ...@@ -162,9 +167,8 @@ public final class DtsReader implements ElementaryStreamReader {
return false; return false;
} }
/** /** Parses the sample header. */
* Parses the sample header. @RequiresNonNull("output")
*/
private void parseHeader() { private void parseHeader() {
byte[] frameData = headerScratchBytes.data; byte[] frameData = headerScratchBytes.data;
if (format == null) { if (format == null) {
......
...@@ -64,12 +64,12 @@ public final class DvbSubtitleReader implements ElementaryStreamReader { ...@@ -64,12 +64,12 @@ public final class DvbSubtitleReader implements ElementaryStreamReader {
Format.createImageSampleFormat( Format.createImageSampleFormat(
idGenerator.getFormatId(), idGenerator.getFormatId(),
MimeTypes.APPLICATION_DVBSUBS, MimeTypes.APPLICATION_DVBSUBS,
null, /* codecs= */ null,
Format.NO_VALUE, Format.NO_VALUE,
0, /* selectionFlags= */ 0,
Collections.singletonList(subtitleInfo.initializationData), Collections.singletonList(subtitleInfo.initializationData),
subtitleInfo.language, subtitleInfo.language,
null)); /* drmInitData= */ null));
outputs[i] = output; outputs[i] = output;
} }
} }
......
...@@ -16,16 +16,20 @@ ...@@ -16,16 +16,20 @@
package com.google.android.exoplayer2.extractor.ts; package com.google.android.exoplayer2.extractor.ts;
import android.util.Pair; import android.util.Pair;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /**
* Parses a continuous H262 byte stream and extracts individual frames. * Parses a continuous H262 byte stream and extracts individual frames.
...@@ -38,27 +42,27 @@ public final class H262Reader implements ElementaryStreamReader { ...@@ -38,27 +42,27 @@ public final class H262Reader implements ElementaryStreamReader {
private static final int START_GROUP = 0xB8; private static final int START_GROUP = 0xB8;
private static final int START_USER_DATA = 0xB2; private static final int START_USER_DATA = 0xB2;
private String formatId; @MonotonicNonNull private String formatId;
private TrackOutput output; @MonotonicNonNull private TrackOutput output;
// Maps (frame_rate_code - 1) indices to values, as defined in ITU-T H.262 Table 6-4. // Maps (frame_rate_code - 1) indices to values, as defined in ITU-T H.262 Table 6-4.
private static final double[] FRAME_RATE_VALUES = new double[] { private static final double[] FRAME_RATE_VALUES = new double[] {
24000d / 1001, 24, 25, 30000d / 1001, 30, 50, 60000d / 1001, 60}; 24000d / 1001, 24, 25, 30000d / 1001, 30, 50, 60000d / 1001, 60};
// State that should not be reset on seek. @Nullable private final UserDataReader userDataReader;
private boolean hasOutputFormat; @Nullable private final ParsableByteArray userDataParsable;
private long frameDurationUs;
private final UserDataReader userDataReader;
private final ParsableByteArray userDataParsable;
// State that should be reset on seek. // State that should be reset on seek.
@Nullable private final NalUnitTargetBuffer userData;
private final boolean[] prefixFlags; private final boolean[] prefixFlags;
private final CsdBuffer csdBuffer; private final CsdBuffer csdBuffer;
private final NalUnitTargetBuffer userData;
private long totalBytesWritten; private long totalBytesWritten;
private boolean startedFirstSample; private boolean startedFirstSample;
// State that should not be reset on seek.
private boolean hasOutputFormat;
private long frameDurationUs;
// Per packet state that gets reset at the start of each packet. // Per packet state that gets reset at the start of each packet.
private long pesTimeUs; private long pesTimeUs;
...@@ -72,7 +76,7 @@ public final class H262Reader implements ElementaryStreamReader { ...@@ -72,7 +76,7 @@ public final class H262Reader implements ElementaryStreamReader {
this(null); this(null);
} }
/* package */ H262Reader(UserDataReader userDataReader) { /* package */ H262Reader(@Nullable UserDataReader userDataReader) {
this.userDataReader = userDataReader; this.userDataReader = userDataReader;
prefixFlags = new boolean[4]; prefixFlags = new boolean[4];
csdBuffer = new CsdBuffer(128); csdBuffer = new CsdBuffer(128);
...@@ -89,7 +93,7 @@ public final class H262Reader implements ElementaryStreamReader { ...@@ -89,7 +93,7 @@ public final class H262Reader implements ElementaryStreamReader {
public void seek() { public void seek() {
NalUnitUtil.clearPrefixFlags(prefixFlags); NalUnitUtil.clearPrefixFlags(prefixFlags);
csdBuffer.reset(); csdBuffer.reset();
if (userDataReader != null) { if (userData != null) {
userData.reset(); userData.reset();
} }
totalBytesWritten = 0; totalBytesWritten = 0;
...@@ -114,6 +118,7 @@ public final class H262Reader implements ElementaryStreamReader { ...@@ -114,6 +118,7 @@ public final class H262Reader implements ElementaryStreamReader {
@Override @Override
public void consume(ParsableByteArray data) { public void consume(ParsableByteArray data) {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
int offset = data.getPosition(); int offset = data.getPosition();
int limit = data.limit(); int limit = data.limit();
byte[] dataArray = data.data; byte[] dataArray = data.data;
...@@ -130,7 +135,7 @@ public final class H262Reader implements ElementaryStreamReader { ...@@ -130,7 +135,7 @@ public final class H262Reader implements ElementaryStreamReader {
if (!hasOutputFormat) { if (!hasOutputFormat) {
csdBuffer.onData(dataArray, offset, limit); csdBuffer.onData(dataArray, offset, limit);
} }
if (userDataReader != null) { if (userData != null) {
userData.appendToNalUnit(dataArray, offset, limit); userData.appendToNalUnit(dataArray, offset, limit);
} }
return; return;
...@@ -157,7 +162,7 @@ public final class H262Reader implements ElementaryStreamReader { ...@@ -157,7 +162,7 @@ public final class H262Reader implements ElementaryStreamReader {
hasOutputFormat = true; hasOutputFormat = true;
} }
} }
if (userDataReader != null) { if (userData != null) {
int bytesAlreadyPassed = 0; int bytesAlreadyPassed = 0;
if (lengthToStartCode > 0) { if (lengthToStartCode > 0) {
userData.appendToNalUnit(dataArray, offset, startCodeOffset); userData.appendToNalUnit(dataArray, offset, startCodeOffset);
...@@ -167,8 +172,8 @@ public final class H262Reader implements ElementaryStreamReader { ...@@ -167,8 +172,8 @@ public final class H262Reader implements ElementaryStreamReader {
if (userData.endNalUnit(bytesAlreadyPassed)) { if (userData.endNalUnit(bytesAlreadyPassed)) {
int unescapedLength = NalUnitUtil.unescapeStream(userData.nalData, userData.nalLength); int unescapedLength = NalUnitUtil.unescapeStream(userData.nalData, userData.nalLength);
userDataParsable.reset(userData.nalData, unescapedLength); Util.castNonNull(userDataParsable).reset(userData.nalData, unescapedLength);
userDataReader.consume(sampleTimeUs, userDataParsable); Util.castNonNull(userDataReader).consume(sampleTimeUs, userDataParsable);
} }
if (startCodeValue == START_USER_DATA && data.data[startCodeOffset + 2] == 0x1) { if (startCodeValue == START_USER_DATA && data.data[startCodeOffset + 2] == 0x1) {
...@@ -211,10 +216,10 @@ public final class H262Reader implements ElementaryStreamReader { ...@@ -211,10 +216,10 @@ public final class H262Reader implements ElementaryStreamReader {
* *
* @param csdBuffer The csd buffer. * @param csdBuffer The csd buffer.
* @param formatId The id for the generated format. May be null. * @param formatId The id for the generated format. May be null.
* @return A pair consisting of the {@link Format} and the frame duration in microseconds, or * @return A pair consisting of the {@link Format} and the frame duration in microseconds, or 0 if
* 0 if the duration could not be determined. * the duration could not be determined.
*/ */
private static Pair<Format, Long> parseCsdBuffer(CsdBuffer csdBuffer, String formatId) { private static Pair<Format, Long> parseCsdBuffer(CsdBuffer csdBuffer, @Nullable String formatId) {
byte[] csdData = Arrays.copyOf(csdBuffer.data, csdBuffer.length); byte[] csdData = Arrays.copyOf(csdBuffer.data, csdBuffer.length);
int firstByte = csdData[4] & 0xFF; int firstByte = csdData[4] & 0xFF;
......
...@@ -23,15 +23,21 @@ import com.google.android.exoplayer2.Format; ...@@ -23,15 +23,21 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.CodecSpecificDataUtil; import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.NalUnitUtil.SpsData; import com.google.android.exoplayer2.util.NalUnitUtil.SpsData;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.ParsableNalUnitBitArray; import com.google.android.exoplayer2.util.ParsableNalUnitBitArray;
import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** /**
* Parses a continuous H264 byte stream and extracts individual frames. * Parses a continuous H264 byte stream and extracts individual frames.
...@@ -51,9 +57,9 @@ public final class H264Reader implements ElementaryStreamReader { ...@@ -51,9 +57,9 @@ public final class H264Reader implements ElementaryStreamReader {
private long totalBytesWritten; private long totalBytesWritten;
private final boolean[] prefixFlags; private final boolean[] prefixFlags;
private String formatId; @MonotonicNonNull private String formatId;
private TrackOutput output; @MonotonicNonNull private TrackOutput output;
private SampleReader sampleReader; @MonotonicNonNull private SampleReader sampleReader;
// State that should not be reset on seek. // State that should not be reset on seek.
private boolean hasOutputFormat; private boolean hasOutputFormat;
...@@ -87,13 +93,15 @@ public final class H264Reader implements ElementaryStreamReader { ...@@ -87,13 +93,15 @@ public final class H264Reader implements ElementaryStreamReader {
@Override @Override
public void seek() { public void seek() {
totalBytesWritten = 0;
randomAccessIndicator = false;
NalUnitUtil.clearPrefixFlags(prefixFlags); NalUnitUtil.clearPrefixFlags(prefixFlags);
sps.reset(); sps.reset();
pps.reset(); pps.reset();
sei.reset(); sei.reset();
sampleReader.reset(); if (sampleReader != null) {
totalBytesWritten = 0; sampleReader.reset();
randomAccessIndicator = false; }
} }
@Override @Override
...@@ -113,6 +121,8 @@ public final class H264Reader implements ElementaryStreamReader { ...@@ -113,6 +121,8 @@ public final class H264Reader implements ElementaryStreamReader {
@Override @Override
public void consume(ParsableByteArray data) { public void consume(ParsableByteArray data) {
assertTracksCreated();
int offset = data.getPosition(); int offset = data.getPosition();
int limit = data.limit(); int limit = data.limit();
byte[] dataArray = data.data; byte[] dataArray = data.data;
...@@ -159,6 +169,7 @@ public final class H264Reader implements ElementaryStreamReader { ...@@ -159,6 +169,7 @@ public final class H264Reader implements ElementaryStreamReader {
// Do nothing. // Do nothing.
} }
@RequiresNonNull("sampleReader")
private void startNalUnit(long position, int nalUnitType, long pesTimeUs) { private void startNalUnit(long position, int nalUnitType, long pesTimeUs) {
if (!hasOutputFormat || sampleReader.needsSpsPps()) { if (!hasOutputFormat || sampleReader.needsSpsPps()) {
sps.startNalUnit(nalUnitType); sps.startNalUnit(nalUnitType);
...@@ -168,6 +179,7 @@ public final class H264Reader implements ElementaryStreamReader { ...@@ -168,6 +179,7 @@ public final class H264Reader implements ElementaryStreamReader {
sampleReader.startNalUnit(position, nalUnitType, pesTimeUs); sampleReader.startNalUnit(position, nalUnitType, pesTimeUs);
} }
@RequiresNonNull("sampleReader")
private void nalUnitData(byte[] dataArray, int offset, int limit) { private void nalUnitData(byte[] dataArray, int offset, int limit) {
if (!hasOutputFormat || sampleReader.needsSpsPps()) { if (!hasOutputFormat || sampleReader.needsSpsPps()) {
sps.appendToNalUnit(dataArray, offset, limit); sps.appendToNalUnit(dataArray, offset, limit);
...@@ -177,6 +189,7 @@ public final class H264Reader implements ElementaryStreamReader { ...@@ -177,6 +189,7 @@ public final class H264Reader implements ElementaryStreamReader {
sampleReader.appendToNalUnit(dataArray, offset, limit); sampleReader.appendToNalUnit(dataArray, offset, limit);
} }
@RequiresNonNull({"output", "sampleReader"})
private void endNalUnit(long position, int offset, int discardPadding, long pesTimeUs) { private void endNalUnit(long position, int offset, int discardPadding, long pesTimeUs) {
if (!hasOutputFormat || sampleReader.needsSpsPps()) { if (!hasOutputFormat || sampleReader.needsSpsPps()) {
sps.endNalUnit(discardPadding); sps.endNalUnit(discardPadding);
...@@ -237,6 +250,12 @@ public final class H264Reader implements ElementaryStreamReader { ...@@ -237,6 +250,12 @@ public final class H264Reader implements ElementaryStreamReader {
} }
} }
@EnsuresNonNull({"output", "sampleReader"})
private void assertTracksCreated() {
Assertions.checkStateNotNull(output);
Util.castNonNull(sampleReader);
}
/** Consumes a stream of NAL units and outputs samples. */ /** Consumes a stream of NAL units and outputs samples. */
private static final class SampleReader { private static final class SampleReader {
...@@ -478,7 +497,7 @@ public final class H264Reader implements ElementaryStreamReader { ...@@ -478,7 +497,7 @@ public final class H264Reader implements ElementaryStreamReader {
private boolean isComplete; private boolean isComplete;
private boolean hasSliceType; private boolean hasSliceType;
private SpsData spsData; @Nullable private SpsData spsData;
private int nalRefIdc; private int nalRefIdc;
private int sliceType; private int sliceType;
private int frameNum; private int frameNum;
...@@ -542,6 +561,8 @@ public final class H264Reader implements ElementaryStreamReader { ...@@ -542,6 +561,8 @@ public final class H264Reader implements ElementaryStreamReader {
private boolean isFirstVclNalUnitOfPicture(SliceHeaderData other) { private boolean isFirstVclNalUnitOfPicture(SliceHeaderData other) {
// See ISO 14496-10 subsection 7.4.1.2.4. // See ISO 14496-10 subsection 7.4.1.2.4.
SpsData spsData = Assertions.checkStateNotNull(this.spsData);
SpsData otherSpsData = Assertions.checkStateNotNull(other.spsData);
return isComplete return isComplete
&& (!other.isComplete && (!other.isComplete
|| frameNum != other.frameNum || frameNum != other.frameNum
...@@ -552,15 +573,15 @@ public final class H264Reader implements ElementaryStreamReader { ...@@ -552,15 +573,15 @@ public final class H264Reader implements ElementaryStreamReader {
&& bottomFieldFlag != other.bottomFieldFlag) && bottomFieldFlag != other.bottomFieldFlag)
|| (nalRefIdc != other.nalRefIdc && (nalRefIdc == 0 || other.nalRefIdc == 0)) || (nalRefIdc != other.nalRefIdc && (nalRefIdc == 0 || other.nalRefIdc == 0))
|| (spsData.picOrderCountType == 0 || (spsData.picOrderCountType == 0
&& other.spsData.picOrderCountType == 0 && otherSpsData.picOrderCountType == 0
&& (picOrderCntLsb != other.picOrderCntLsb && (picOrderCntLsb != other.picOrderCntLsb
|| deltaPicOrderCntBottom != other.deltaPicOrderCntBottom)) || deltaPicOrderCntBottom != other.deltaPicOrderCntBottom))
|| (spsData.picOrderCountType == 1 || (spsData.picOrderCountType == 1
&& other.spsData.picOrderCountType == 1 && otherSpsData.picOrderCountType == 1
&& (deltaPicOrderCnt0 != other.deltaPicOrderCnt0 && (deltaPicOrderCnt0 != other.deltaPicOrderCnt0
|| deltaPicOrderCnt1 != other.deltaPicOrderCnt1)) || deltaPicOrderCnt1 != other.deltaPicOrderCnt1))
|| idrPicFlag != other.idrPicFlag || idrPicFlag != other.idrPicFlag
|| (idrPicFlag && other.idrPicFlag && idrPicId != other.idrPicId)); || (idrPicFlag && idrPicId != other.idrPicId));
} }
} }
} }
......
...@@ -20,12 +20,18 @@ import com.google.android.exoplayer2.Format; ...@@ -20,12 +20,18 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.ParsableNalUnitBitArray; import com.google.android.exoplayer2.util.ParsableNalUnitBitArray;
import com.google.android.exoplayer2.util.Util;
import java.util.Collections; import java.util.Collections;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** /**
* Parses a continuous H.265 byte stream and extracts individual frames. * Parses a continuous H.265 byte stream and extracts individual frames.
...@@ -46,9 +52,9 @@ public final class H265Reader implements ElementaryStreamReader { ...@@ -46,9 +52,9 @@ public final class H265Reader implements ElementaryStreamReader {
private final SeiReader seiReader; private final SeiReader seiReader;
private String formatId; @MonotonicNonNull private String formatId;
private TrackOutput output; @MonotonicNonNull private TrackOutput output;
private SampleReader sampleReader; @MonotonicNonNull private SampleReader sampleReader;
// State that should not be reset on seek. // State that should not be reset on seek.
private boolean hasOutputFormat; private boolean hasOutputFormat;
...@@ -84,14 +90,16 @@ public final class H265Reader implements ElementaryStreamReader { ...@@ -84,14 +90,16 @@ public final class H265Reader implements ElementaryStreamReader {
@Override @Override
public void seek() { public void seek() {
totalBytesWritten = 0;
NalUnitUtil.clearPrefixFlags(prefixFlags); NalUnitUtil.clearPrefixFlags(prefixFlags);
vps.reset(); vps.reset();
sps.reset(); sps.reset();
pps.reset(); pps.reset();
prefixSei.reset(); prefixSei.reset();
suffixSei.reset(); suffixSei.reset();
sampleReader.reset(); if (sampleReader != null) {
totalBytesWritten = 0; sampleReader.reset();
}
} }
@Override @Override
...@@ -111,6 +119,8 @@ public final class H265Reader implements ElementaryStreamReader { ...@@ -111,6 +119,8 @@ public final class H265Reader implements ElementaryStreamReader {
@Override @Override
public void consume(ParsableByteArray data) { public void consume(ParsableByteArray data) {
assertTracksCreated();
while (data.bytesLeft() > 0) { while (data.bytesLeft() > 0) {
int offset = data.getPosition(); int offset = data.getPosition();
int limit = data.limit(); int limit = data.limit();
...@@ -160,6 +170,7 @@ public final class H265Reader implements ElementaryStreamReader { ...@@ -160,6 +170,7 @@ public final class H265Reader implements ElementaryStreamReader {
// Do nothing. // Do nothing.
} }
@RequiresNonNull("sampleReader")
private void startNalUnit(long position, int offset, int nalUnitType, long pesTimeUs) { private void startNalUnit(long position, int offset, int nalUnitType, long pesTimeUs) {
if (hasOutputFormat) { if (hasOutputFormat) {
sampleReader.startNalUnit(position, offset, nalUnitType, pesTimeUs); sampleReader.startNalUnit(position, offset, nalUnitType, pesTimeUs);
...@@ -172,6 +183,7 @@ public final class H265Reader implements ElementaryStreamReader { ...@@ -172,6 +183,7 @@ public final class H265Reader implements ElementaryStreamReader {
suffixSei.startNalUnit(nalUnitType); suffixSei.startNalUnit(nalUnitType);
} }
@RequiresNonNull("sampleReader")
private void nalUnitData(byte[] dataArray, int offset, int limit) { private void nalUnitData(byte[] dataArray, int offset, int limit) {
if (hasOutputFormat) { if (hasOutputFormat) {
sampleReader.readNalUnitData(dataArray, offset, limit); sampleReader.readNalUnitData(dataArray, offset, limit);
...@@ -184,6 +196,7 @@ public final class H265Reader implements ElementaryStreamReader { ...@@ -184,6 +196,7 @@ public final class H265Reader implements ElementaryStreamReader {
suffixSei.appendToNalUnit(dataArray, offset, limit); suffixSei.appendToNalUnit(dataArray, offset, limit);
} }
@RequiresNonNull({"output", "sampleReader"})
private void endNalUnit(long position, int offset, int discardPadding, long pesTimeUs) { private void endNalUnit(long position, int offset, int discardPadding, long pesTimeUs) {
if (hasOutputFormat) { if (hasOutputFormat) {
sampleReader.endNalUnit(position, offset); sampleReader.endNalUnit(position, offset);
...@@ -214,8 +227,11 @@ public final class H265Reader implements ElementaryStreamReader { ...@@ -214,8 +227,11 @@ public final class H265Reader implements ElementaryStreamReader {
} }
} }
private static Format parseMediaFormat(String formatId, NalUnitTargetBuffer vps, private static Format parseMediaFormat(
NalUnitTargetBuffer sps, NalUnitTargetBuffer pps) { @Nullable String formatId,
NalUnitTargetBuffer vps,
NalUnitTargetBuffer sps,
NalUnitTargetBuffer pps) {
// Build codec-specific data. // Build codec-specific data.
byte[] csd = new byte[vps.nalLength + sps.nalLength + pps.nalLength]; byte[] csd = new byte[vps.nalLength + sps.nalLength + pps.nalLength];
System.arraycopy(vps.nalData, 0, csd, 0, vps.nalLength); System.arraycopy(vps.nalData, 0, csd, 0, vps.nalLength);
...@@ -389,6 +405,12 @@ public final class H265Reader implements ElementaryStreamReader { ...@@ -389,6 +405,12 @@ public final class H265Reader implements ElementaryStreamReader {
} }
} }
@EnsuresNonNull({"output", "sampleReader"})
private void assertTracksCreated() {
Assertions.checkStateNotNull(output);
Util.castNonNull(sampleReader);
}
private static final class SampleReader { private static final class SampleReader {
/** /**
......
...@@ -23,9 +23,11 @@ import com.google.android.exoplayer2.Format; ...@@ -23,9 +23,11 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /**
* Parses ID3 data and extracts individual text information frames. * Parses ID3 data and extracts individual text information frames.
...@@ -36,7 +38,7 @@ public final class Id3Reader implements ElementaryStreamReader { ...@@ -36,7 +38,7 @@ public final class Id3Reader implements ElementaryStreamReader {
private final ParsableByteArray id3Header; private final ParsableByteArray id3Header;
private TrackOutput output; @MonotonicNonNull private TrackOutput output;
// State that should be reset on seek. // State that should be reset on seek.
private boolean writingSample; private boolean writingSample;
...@@ -76,6 +78,7 @@ public final class Id3Reader implements ElementaryStreamReader { ...@@ -76,6 +78,7 @@ public final class Id3Reader implements ElementaryStreamReader {
@Override @Override
public void consume(ParsableByteArray data) { public void consume(ParsableByteArray data) {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
if (!writingSample) { if (!writingSample) {
return; return;
} }
...@@ -106,6 +109,7 @@ public final class Id3Reader implements ElementaryStreamReader { ...@@ -106,6 +109,7 @@ public final class Id3Reader implements ElementaryStreamReader {
@Override @Override
public void packetFinished() { public void packetFinished() {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
if (!writingSample || sampleSize == 0 || sampleBytesRead != sampleSize) { if (!writingSample || sampleSize == 0 || sampleBytesRead != sampleSize) {
return; return;
} }
......
...@@ -23,11 +23,14 @@ import com.google.android.exoplayer2.ParserException; ...@@ -23,11 +23,14 @@ import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.CodecSpecificDataUtil; import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import java.util.Collections; import java.util.Collections;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** /**
* Parses and extracts samples from an AAC/LATM elementary stream. * Parses and extracts samples from an AAC/LATM elementary stream.
...@@ -43,14 +46,14 @@ public final class LatmReader implements ElementaryStreamReader { ...@@ -43,14 +46,14 @@ public final class LatmReader implements ElementaryStreamReader {
private static final int SYNC_BYTE_FIRST = 0x56; private static final int SYNC_BYTE_FIRST = 0x56;
private static final int SYNC_BYTE_SECOND = 0xE0; private static final int SYNC_BYTE_SECOND = 0xE0;
private final String language; @Nullable private final String language;
private final ParsableByteArray sampleDataBuffer; private final ParsableByteArray sampleDataBuffer;
private final ParsableBitArray sampleBitArray; private final ParsableBitArray sampleBitArray;
// Track output info. // Track output info.
private TrackOutput output; @MonotonicNonNull private TrackOutput output;
private Format format; @MonotonicNonNull private String formatId;
private String formatId; @MonotonicNonNull private Format format;
// Parser state info. // Parser state info.
private int state; private int state;
...@@ -99,6 +102,7 @@ public final class LatmReader implements ElementaryStreamReader { ...@@ -99,6 +102,7 @@ public final class LatmReader implements ElementaryStreamReader {
@Override @Override
public void consume(ParsableByteArray data) throws ParserException { public void consume(ParsableByteArray data) throws ParserException {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
int bytesToRead; int bytesToRead;
while (data.bytesLeft() > 0) { while (data.bytesLeft() > 0) {
switch (state) { switch (state) {
...@@ -150,6 +154,7 @@ public final class LatmReader implements ElementaryStreamReader { ...@@ -150,6 +154,7 @@ public final class LatmReader implements ElementaryStreamReader {
* *
* @param data A {@link ParsableBitArray} containing the AudioMuxElement's bytes. * @param data A {@link ParsableBitArray} containing the AudioMuxElement's bytes.
*/ */
@RequiresNonNull("output")
private void parseAudioMuxElement(ParsableBitArray data) throws ParserException { private void parseAudioMuxElement(ParsableBitArray data) throws ParserException {
boolean useSameStreamMux = data.readBit(); boolean useSameStreamMux = data.readBit();
if (!useSameStreamMux) { if (!useSameStreamMux) {
...@@ -173,9 +178,8 @@ public final class LatmReader implements ElementaryStreamReader { ...@@ -173,9 +178,8 @@ public final class LatmReader implements ElementaryStreamReader {
} }
} }
/** /** Parses a StreamMuxConfig as defined in ISO/IEC 14496-3:2009 Section 1.7.3.1, Table 1.42. */
* Parses a StreamMuxConfig as defined in ISO/IEC 14496-3:2009 Section 1.7.3.1, Table 1.42. @RequiresNonNull("output")
*/
private void parseStreamMuxConfig(ParsableBitArray data) throws ParserException { private void parseStreamMuxConfig(ParsableBitArray data) throws ParserException {
int audioMuxVersion = data.readBits(1); int audioMuxVersion = data.readBits(1);
audioMuxVersionA = audioMuxVersion == 1 ? data.readBits(1) : 0; audioMuxVersionA = audioMuxVersion == 1 ? data.readBits(1) : 0;
...@@ -198,9 +202,19 @@ public final class LatmReader implements ElementaryStreamReader { ...@@ -198,9 +202,19 @@ public final class LatmReader implements ElementaryStreamReader {
data.setPosition(startPosition); data.setPosition(startPosition);
byte[] initData = new byte[(readBits + 7) / 8]; byte[] initData = new byte[(readBits + 7) / 8];
data.readBits(initData, 0, readBits); data.readBits(initData, 0, readBits);
Format format = Format.createAudioSampleFormat(formatId, MimeTypes.AUDIO_AAC, null, Format format =
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRateHz, Format.createAudioSampleFormat(
Collections.singletonList(initData), null, 0, language); formatId,
MimeTypes.AUDIO_AAC,
/* codecs= */ null,
Format.NO_VALUE,
Format.NO_VALUE,
channelCount,
sampleRateHz,
Collections.singletonList(initData),
/* drmInitData= */ null,
/* selectionFlags= */ 0,
language);
if (!format.equals(this.format)) { if (!format.equals(this.format)) {
this.format = format; this.format = format;
sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / format.sampleRate; sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / format.sampleRate;
...@@ -280,6 +294,7 @@ public final class LatmReader implements ElementaryStreamReader { ...@@ -280,6 +294,7 @@ public final class LatmReader implements ElementaryStreamReader {
} }
} }
@RequiresNonNull("output")
private void parsePayloadMux(ParsableBitArray data, int muxLengthBytes) { private void parsePayloadMux(ParsableBitArray data, int muxLengthBytes) {
// The start of sample data in // The start of sample data in
int bitPosition = data.getPosition(); int bitPosition = data.getPosition();
......
...@@ -21,7 +21,11 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput; ...@@ -21,7 +21,11 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.MpegAudioHeader; import com.google.android.exoplayer2.extractor.MpegAudioHeader;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** /**
* Parses a continuous MPEG Audio byte stream and extracts individual frames. * Parses a continuous MPEG Audio byte stream and extracts individual frames.
...@@ -36,10 +40,10 @@ public final class MpegAudioReader implements ElementaryStreamReader { ...@@ -36,10 +40,10 @@ public final class MpegAudioReader implements ElementaryStreamReader {
private final ParsableByteArray headerScratch; private final ParsableByteArray headerScratch;
private final MpegAudioHeader header; private final MpegAudioHeader header;
private final String language; @Nullable private final String language;
private String formatId; @MonotonicNonNull private TrackOutput output;
private TrackOutput output; @MonotonicNonNull private String formatId;
private int state; private int state;
private int frameBytesRead; private int frameBytesRead;
...@@ -59,7 +63,7 @@ public final class MpegAudioReader implements ElementaryStreamReader { ...@@ -59,7 +63,7 @@ public final class MpegAudioReader implements ElementaryStreamReader {
this(null); this(null);
} }
public MpegAudioReader(String language) { public MpegAudioReader(@Nullable String language) {
state = STATE_FINDING_HEADER; state = STATE_FINDING_HEADER;
// The first byte of an MPEG Audio frame header is always 0xFF. // The first byte of an MPEG Audio frame header is always 0xFF.
headerScratch = new ParsableByteArray(4); headerScratch = new ParsableByteArray(4);
...@@ -89,6 +93,7 @@ public final class MpegAudioReader implements ElementaryStreamReader { ...@@ -89,6 +93,7 @@ public final class MpegAudioReader implements ElementaryStreamReader {
@Override @Override
public void consume(ParsableByteArray data) { public void consume(ParsableByteArray data) {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
while (data.bytesLeft() > 0) { while (data.bytesLeft() > 0) {
switch (state) { switch (state) {
case STATE_FINDING_HEADER: case STATE_FINDING_HEADER:
...@@ -146,20 +151,21 @@ public final class MpegAudioReader implements ElementaryStreamReader { ...@@ -146,20 +151,21 @@ public final class MpegAudioReader implements ElementaryStreamReader {
/** /**
* Attempts to read the remaining two bytes of the frame header. * Attempts to read the remaining two bytes of the frame header.
* <p> *
* If a frame header is read in full then the state is changed to {@link #STATE_READING_FRAME}, * <p>If a frame header is read in full then the state is changed to {@link #STATE_READING_FRAME},
* the media format is output if this has not previously occurred, the four header bytes are * the media format is output if this has not previously occurred, the four header bytes are
* output as sample data, and the position of the source is advanced to the byte that immediately * output as sample data, and the position of the source is advanced to the byte that immediately
* follows the header. * follows the header.
* <p> *
* If a frame header is read in full but cannot be parsed then the state is changed to * <p>If a frame header is read in full but cannot be parsed then the state is changed to {@link
* {@link #STATE_READING_HEADER}. * #STATE_READING_HEADER}.
* <p> *
* If a frame header is not read in full then the position of the source is advanced to the limit, * <p>If a frame header is not read in full then the position of the source is advanced to the
* and the method should be called again with the next source to continue the read. * limit, and the method should be called again with the next source to continue the read.
* *
* @param source The source from which to read. * @param source The source from which to read.
*/ */
@RequiresNonNull("output")
private void readHeaderRemainder(ParsableByteArray source) { private void readHeaderRemainder(ParsableByteArray source) {
int bytesToRead = Math.min(source.bytesLeft(), HEADER_SIZE - frameBytesRead); int bytesToRead = Math.min(source.bytesLeft(), HEADER_SIZE - frameBytesRead);
source.readBytes(headerScratch.data, frameBytesRead, bytesToRead); source.readBytes(headerScratch.data, frameBytesRead, bytesToRead);
...@@ -195,16 +201,17 @@ public final class MpegAudioReader implements ElementaryStreamReader { ...@@ -195,16 +201,17 @@ public final class MpegAudioReader implements ElementaryStreamReader {
/** /**
* Attempts to read the remainder of the frame. * Attempts to read the remainder of the frame.
* <p> *
* If a frame is read in full then true is returned. The frame will have been output, and the * <p>If a frame is read in full then true is returned. The frame will have been output, and the
* position of the source will have been advanced to the byte that immediately follows the end of * position of the source will have been advanced to the byte that immediately follows the end of
* the frame. * the frame.
* <p> *
* If a frame is not read in full then the position of the source will have been advanced to the * <p>If a frame is not read in full then the position of the source will have been advanced to
* limit, and the method should be called again with the next source to continue the read. * the limit, and the method should be called again with the next source to continue the read.
* *
* @param source The source from which to read. * @param source The source from which to read.
*/ */
@RequiresNonNull("output")
private void readFrameRemainder(ParsableByteArray source) { private void readFrameRemainder(ParsableByteArray source) {
int bytesToRead = Math.min(source.bytesLeft(), frameSize - frameBytesRead); int bytesToRead = Math.min(source.bytesLeft(), frameSize - frameBytesRead);
output.sampleData(source, bytesToRead); output.sampleData(source, bytesToRead);
...@@ -219,5 +226,4 @@ public final class MpegAudioReader implements ElementaryStreamReader { ...@@ -219,5 +226,4 @@ public final class MpegAudioReader implements ElementaryStreamReader {
frameBytesRead = 0; frameBytesRead = 0;
state = STATE_FINDING_HEADER; state = STATE_FINDING_HEADER;
} }
} }
...@@ -15,13 +15,17 @@ ...@@ -15,13 +15,17 @@
*/ */
package com.google.android.exoplayer2.extractor.ts; package com.google.android.exoplayer2.extractor.ts;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster; import com.google.android.exoplayer2.util.TimestampAdjuster;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** /**
* Parses PES packet data and extracts samples. * Parses PES packet data and extracts samples.
...@@ -45,7 +49,7 @@ public final class PesReader implements TsPayloadReader { ...@@ -45,7 +49,7 @@ public final class PesReader implements TsPayloadReader {
private int state; private int state;
private int bytesRead; private int bytesRead;
private TimestampAdjuster timestampAdjuster; @MonotonicNonNull private TimestampAdjuster timestampAdjuster;
private boolean ptsFlag; private boolean ptsFlag;
private boolean dtsFlag; private boolean dtsFlag;
private boolean seenFirstDts; private boolean seenFirstDts;
...@@ -79,6 +83,8 @@ public final class PesReader implements TsPayloadReader { ...@@ -79,6 +83,8 @@ public final class PesReader implements TsPayloadReader {
@Override @Override
public final void consume(ParsableByteArray data, @Flags int flags) throws ParserException { public final void consume(ParsableByteArray data, @Flags int flags) throws ParserException {
Assertions.checkStateNotNull(timestampAdjuster); // Asserts init has been called.
if ((flags & FLAG_PAYLOAD_UNIT_START_INDICATOR) != 0) { if ((flags & FLAG_PAYLOAD_UNIT_START_INDICATOR) != 0) {
switch (state) { switch (state) {
case STATE_FINDING_HEADER: case STATE_FINDING_HEADER:
...@@ -119,7 +125,7 @@ public final class PesReader implements TsPayloadReader { ...@@ -119,7 +125,7 @@ public final class PesReader implements TsPayloadReader {
int readLength = Math.min(MAX_HEADER_EXTENSION_SIZE, extendedHeaderLength); int readLength = Math.min(MAX_HEADER_EXTENSION_SIZE, extendedHeaderLength);
// Read as much of the extended header as we're interested in, and skip the rest. // Read as much of the extended header as we're interested in, and skip the rest.
if (continueRead(data, pesScratch.data, readLength) if (continueRead(data, pesScratch.data, readLength)
&& continueRead(data, null, extendedHeaderLength)) { && continueRead(data, /* target= */ null, extendedHeaderLength)) {
parseHeaderExtension(); parseHeaderExtension();
flags |= dataAlignmentIndicator ? FLAG_DATA_ALIGNMENT_INDICATOR : 0; flags |= dataAlignmentIndicator ? FLAG_DATA_ALIGNMENT_INDICATOR : 0;
reader.packetStarted(timeUs, flags); reader.packetStarted(timeUs, flags);
...@@ -162,7 +168,8 @@ public final class PesReader implements TsPayloadReader { ...@@ -162,7 +168,8 @@ public final class PesReader implements TsPayloadReader {
* @param targetLength The target length of the read. * @param targetLength The target length of the read.
* @return Whether the target length has been reached. * @return Whether the target length has been reached.
*/ */
private boolean continueRead(ParsableByteArray source, byte[] target, int targetLength) { private boolean continueRead(
ParsableByteArray source, @Nullable byte[] target, int targetLength) {
int bytesToRead = Math.min(source.bytesLeft(), targetLength - bytesRead); int bytesToRead = Math.min(source.bytesLeft(), targetLength - bytesRead);
if (bytesToRead <= 0) { if (bytesToRead <= 0) {
return true; return true;
...@@ -207,6 +214,7 @@ public final class PesReader implements TsPayloadReader { ...@@ -207,6 +214,7 @@ public final class PesReader implements TsPayloadReader {
return true; return true;
} }
@RequiresNonNull("timestampAdjuster")
private void parseHeaderExtension() { private void parseHeaderExtension() {
pesScratch.setPosition(0); pesScratch.setPosition(0);
timeUs = C.TIME_UNSET; timeUs = C.TIME_UNSET;
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.extractor.ts; package com.google.android.exoplayer2.extractor.ts;
import android.util.SparseArray; import android.util.SparseArray;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
...@@ -25,10 +26,13 @@ import com.google.android.exoplayer2.extractor.ExtractorsFactory; ...@@ -25,10 +26,13 @@ import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster; import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.io.IOException; import java.io.IOException;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** /**
* Extracts data from the MPEG-2 PS container format. * Extracts data from the MPEG-2 PS container format.
...@@ -67,8 +71,8 @@ public final class PsExtractor implements Extractor { ...@@ -67,8 +71,8 @@ public final class PsExtractor implements Extractor {
private long lastTrackPosition; private long lastTrackPosition;
// Accessed only by the loading thread. // Accessed only by the loading thread.
private PsBinarySearchSeeker psBinarySearchSeeker; @Nullable private PsBinarySearchSeeker psBinarySearchSeeker;
private ExtractorOutput output; @MonotonicNonNull private ExtractorOutput output;
private boolean hasOutputSeekMap; private boolean hasOutputSeekMap;
public PsExtractor() { public PsExtractor() {
...@@ -160,6 +164,7 @@ public final class PsExtractor implements Extractor { ...@@ -160,6 +164,7 @@ public final class PsExtractor implements Extractor {
@Override @Override
public int read(ExtractorInput input, PositionHolder seekPosition) public int read(ExtractorInput input, PositionHolder seekPosition)
throws IOException, InterruptedException { throws IOException, InterruptedException {
Assertions.checkStateNotNull(output); // Asserts init has been called.
long inputLength = input.getLength(); long inputLength = input.getLength();
boolean canReadDuration = inputLength != C.LENGTH_UNSET; boolean canReadDuration = inputLength != C.LENGTH_UNSET;
...@@ -221,7 +226,7 @@ public final class PsExtractor implements Extractor { ...@@ -221,7 +226,7 @@ public final class PsExtractor implements Extractor {
PesReader payloadReader = psPayloadReaders.get(streamId); PesReader payloadReader = psPayloadReaders.get(streamId);
if (!foundAllTracks) { if (!foundAllTracks) {
if (payloadReader == null) { if (payloadReader == null) {
ElementaryStreamReader elementaryStreamReader = null; @Nullable ElementaryStreamReader elementaryStreamReader = null;
if (streamId == PRIVATE_STREAM_1) { if (streamId == PRIVATE_STREAM_1) {
// Private stream, used for AC3 audio. // Private stream, used for AC3 audio.
// NOTE: This may need further parsing to determine if its DTS, but that's likely only // NOTE: This may need further parsing to determine if its DTS, but that's likely only
...@@ -278,6 +283,7 @@ public final class PsExtractor implements Extractor { ...@@ -278,6 +283,7 @@ public final class PsExtractor implements Extractor {
// Internals. // Internals.
@RequiresNonNull("output")
private void maybeOutputSeekMap(long inputLength) { private void maybeOutputSeekMap(long inputLength) {
if (!hasOutputSeekMap) { if (!hasOutputSeekMap) {
hasOutputSeekMap = true; hasOutputSeekMap = true;
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.extractor.ts; package com.google.android.exoplayer2.extractor.ts;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
...@@ -45,7 +46,7 @@ public final class SeiReader { ...@@ -45,7 +46,7 @@ public final class SeiReader {
idGenerator.generateNewId(); idGenerator.generateNewId();
TrackOutput output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT); TrackOutput output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT);
Format channelFormat = closedCaptionFormats.get(i); Format channelFormat = closedCaptionFormats.get(i);
String channelMimeType = channelFormat.sampleMimeType; @Nullable String channelMimeType = channelFormat.sampleMimeType;
Assertions.checkArgument(MimeTypes.APPLICATION_CEA608.equals(channelMimeType) Assertions.checkArgument(MimeTypes.APPLICATION_CEA608.equals(channelMimeType)
|| MimeTypes.APPLICATION_CEA708.equals(channelMimeType), || MimeTypes.APPLICATION_CEA708.equals(channelMimeType),
"Invalid closed caption mime type provided: " + channelMimeType); "Invalid closed caption mime type provided: " + channelMimeType);
...@@ -69,5 +70,4 @@ public final class SeiReader { ...@@ -69,5 +70,4 @@ public final class SeiReader {
public void consume(long pesTimeUs, ParsableByteArray seiBuffer) { public void consume(long pesTimeUs, ParsableByteArray seiBuffer) {
CeaUtil.consume(pesTimeUs, seiBuffer, outputs); CeaUtil.consume(pesTimeUs, seiBuffer, outputs);
} }
} }
...@@ -19,17 +19,21 @@ import com.google.android.exoplayer2.C; ...@@ -19,17 +19,21 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster; import com.google.android.exoplayer2.util.TimestampAdjuster;
import com.google.android.exoplayer2.util.Util;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /**
* Parses splice info sections as defined by SCTE35. * Parses splice info sections as defined by SCTE35.
*/ */
public final class SpliceInfoSectionReader implements SectionPayloadReader { public final class SpliceInfoSectionReader implements SectionPayloadReader {
private TimestampAdjuster timestampAdjuster; @MonotonicNonNull private TimestampAdjuster timestampAdjuster;
private TrackOutput output; @MonotonicNonNull private TrackOutput output;
private boolean formatDeclared; private boolean formatDeclared;
@Override @Override
...@@ -44,6 +48,7 @@ public final class SpliceInfoSectionReader implements SectionPayloadReader { ...@@ -44,6 +48,7 @@ public final class SpliceInfoSectionReader implements SectionPayloadReader {
@Override @Override
public void consume(ParsableByteArray sectionData) { public void consume(ParsableByteArray sectionData) {
assertInitialized();
if (!formatDeclared) { if (!formatDeclared) {
if (timestampAdjuster.getTimestampOffsetUs() == C.TIME_UNSET) { if (timestampAdjuster.getTimestampOffsetUs() == C.TIME_UNSET) {
// There is not enough information to initialize the timestamp adjuster. // There is not enough information to initialize the timestamp adjuster.
...@@ -59,4 +64,9 @@ public final class SpliceInfoSectionReader implements SectionPayloadReader { ...@@ -59,4 +64,9 @@ public final class SpliceInfoSectionReader implements SectionPayloadReader {
sampleSize, 0, null); sampleSize, 0, null);
} }
@EnsuresNonNull({"timestampAdjuster", "output"})
private void assertInitialized() {
Assertions.checkStateNotNull(timestampAdjuster);
Util.castNonNull(output);
}
} }
...@@ -21,6 +21,7 @@ import android.util.SparseArray; ...@@ -21,6 +21,7 @@ import android.util.SparseArray;
import android.util.SparseBooleanArray; import android.util.SparseBooleanArray;
import android.util.SparseIntArray; import android.util.SparseIntArray;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
...@@ -587,8 +588,11 @@ public final class TsExtractor implements Extractor { ...@@ -587,8 +588,11 @@ public final class TsExtractor implements Extractor {
continue; continue;
} }
TsPayloadReader reader = mode == MODE_HLS && streamType == TS_STREAM_TYPE_ID3 ? id3Reader @Nullable
: payloadReaderFactory.createPayloadReader(streamType, esInfo); TsPayloadReader reader =
mode == MODE_HLS && streamType == TS_STREAM_TYPE_ID3
? id3Reader
: payloadReaderFactory.createPayloadReader(streamType, esInfo);
if (mode != MODE_HLS if (mode != MODE_HLS
|| elementaryPid < trackIdToPidScratch.get(trackId, MAX_PID_PLUS_ONE)) { || elementaryPid < trackIdToPidScratch.get(trackId, MAX_PID_PLUS_ONE)) {
trackIdToPidScratch.put(trackId, elementaryPid); trackIdToPidScratch.put(trackId, elementaryPid);
...@@ -602,7 +606,7 @@ public final class TsExtractor implements Extractor { ...@@ -602,7 +606,7 @@ public final class TsExtractor implements Extractor {
int trackPid = trackIdToPidScratch.valueAt(i); int trackPid = trackIdToPidScratch.valueAt(i);
trackIds.put(trackId, true); trackIds.put(trackId, true);
trackPids.put(trackPid, true); trackPids.put(trackPid, true);
TsPayloadReader reader = trackIdToReaderScratch.valueAt(i); @Nullable TsPayloadReader reader = trackIdToReaderScratch.valueAt(i);
if (reader != null) { if (reader != null) {
if (reader != id3Reader) { if (reader != id3Reader) {
reader.init(timestampAdjuster, output, reader.init(timestampAdjuster, output,
......
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.extractor.ts; ...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.extractor.ts;
import android.util.SparseArray; import android.util.SparseArray;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
...@@ -53,11 +54,11 @@ public interface TsPayloadReader { ...@@ -53,11 +54,11 @@ public interface TsPayloadReader {
* *
* @param streamType Stream type value as defined in the PMT entry or associated descriptors. * @param streamType Stream type value as defined in the PMT entry or associated descriptors.
* @param esInfo Information associated to the elementary stream provided in the PMT. * @param esInfo Information associated to the elementary stream provided in the PMT.
* @return A {@link TsPayloadReader} for the packet stream carried by the provided pid. * @return A {@link TsPayloadReader} for the packet stream carried by the provided pid, or
* {@code null} if the stream is not supported. * {@code null} if the stream is not supported.
*/ */
@Nullable
TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo); TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo);
} }
/** /**
...@@ -66,18 +67,21 @@ public interface TsPayloadReader { ...@@ -66,18 +67,21 @@ public interface TsPayloadReader {
final class EsInfo { final class EsInfo {
public final int streamType; public final int streamType;
public final String language; @Nullable public final String language;
public final List<DvbSubtitleInfo> dvbSubtitleInfos; public final List<DvbSubtitleInfo> dvbSubtitleInfos;
public final byte[] descriptorBytes; public final byte[] descriptorBytes;
/** /**
* @param streamType The type of the stream as defined by the * @param streamType The type of the stream as defined by the {@link TsExtractor}{@code
* {@link TsExtractor}{@code .TS_STREAM_TYPE_*}. * .TS_STREAM_TYPE_*}.
* @param language The language of the stream, as defined by ISO/IEC 13818-1, section 2.6.18. * @param language The language of the stream, as defined by ISO/IEC 13818-1, section 2.6.18.
* @param dvbSubtitleInfos Information about DVB subtitles associated to the stream. * @param dvbSubtitleInfos Information about DVB subtitles associated to the stream.
* @param descriptorBytes The descriptor bytes associated to the stream. * @param descriptorBytes The descriptor bytes associated to the stream.
*/ */
public EsInfo(int streamType, String language, List<DvbSubtitleInfo> dvbSubtitleInfos, public EsInfo(
int streamType,
@Nullable String language,
@Nullable List<DvbSubtitleInfo> dvbSubtitleInfos,
byte[] descriptorBytes) { byte[] descriptorBytes) {
this.streamType = streamType; this.streamType = streamType;
this.language = language; this.language = language;
...@@ -134,6 +138,7 @@ public interface TsPayloadReader { ...@@ -134,6 +138,7 @@ public interface TsPayloadReader {
this.firstTrackId = firstTrackId; this.firstTrackId = firstTrackId;
this.trackIdIncrement = trackIdIncrement; this.trackIdIncrement = trackIdIncrement;
trackId = ID_UNSET; trackId = ID_UNSET;
formatId = "";
} }
/** /**
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.extractor.ts; package com.google.android.exoplayer2.extractor.ts;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
...@@ -44,7 +45,7 @@ import java.util.List; ...@@ -44,7 +45,7 @@ import java.util.List;
idGenerator.generateNewId(); idGenerator.generateNewId();
TrackOutput output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT); TrackOutput output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT);
Format channelFormat = closedCaptionFormats.get(i); Format channelFormat = closedCaptionFormats.get(i);
String channelMimeType = channelFormat.sampleMimeType; @Nullable String channelMimeType = channelFormat.sampleMimeType;
Assertions.checkArgument( Assertions.checkArgument(
MimeTypes.APPLICATION_CEA608.equals(channelMimeType) MimeTypes.APPLICATION_CEA608.equals(channelMimeType)
|| MimeTypes.APPLICATION_CEA708.equals(channelMimeType), || MimeTypes.APPLICATION_CEA708.equals(channelMimeType),
......
/*
* Copyright (C) 2020 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.
*/
@NonNullApi
package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.util.NonNullApi;
...@@ -49,20 +49,18 @@ import com.google.android.exoplayer2.util.Util; ...@@ -49,20 +49,18 @@ import com.google.android.exoplayer2.util.Util;
@Override @Override
public SeekPoints getSeekPoints(long timeUs) { public SeekPoints getSeekPoints(long timeUs) {
// Calculate the expected number of bytes of sample data corresponding to the requested time.
long positionOffset = (timeUs * wavHeader.averageBytesPerSecond) / C.MICROS_PER_SECOND;
// Calculate the containing block index, constraining to valid indices. // Calculate the containing block index, constraining to valid indices.
long blockSize = wavHeader.blockSize; long blockIndex = (timeUs * wavHeader.frameRateHz) / (C.MICROS_PER_SECOND * framesPerBlock);
long blockIndex = Util.constrainValue(positionOffset / blockSize, 0, blockCount - 1); blockIndex = Util.constrainValue(blockIndex, 0, blockCount - 1);
long seekPosition = firstBlockPosition + (blockIndex * blockSize); long seekPosition = firstBlockPosition + (blockIndex * wavHeader.blockSize);
long seekTimeUs = blockIndexToTimeUs(blockIndex); long seekTimeUs = blockIndexToTimeUs(blockIndex);
SeekPoint seekPoint = new SeekPoint(seekTimeUs, seekPosition); SeekPoint seekPoint = new SeekPoint(seekTimeUs, seekPosition);
if (seekTimeUs >= timeUs || blockIndex == blockCount - 1) { if (seekTimeUs >= timeUs || blockIndex == blockCount - 1) {
return new SeekPoints(seekPoint); return new SeekPoints(seekPoint);
} else { } else {
long secondBlockIndex = blockIndex + 1; long secondBlockIndex = blockIndex + 1;
long secondSeekPosition = firstBlockPosition + (secondBlockIndex * blockSize); long secondSeekPosition = firstBlockPosition + (secondBlockIndex * wavHeader.blockSize);
long secondSeekTimeUs = blockIndexToTimeUs(secondBlockIndex); long secondSeekTimeUs = blockIndexToTimeUs(secondBlockIndex);
SeekPoint secondSeekPoint = new SeekPoint(secondSeekTimeUs, secondSeekPosition); SeekPoint secondSeekPoint = new SeekPoint(secondSeekTimeUs, secondSeekPosition);
return new SeekPoints(seekPoint, secondSeekPoint); return new SeekPoints(seekPoint, secondSeekPoint);
......
...@@ -33,12 +33,12 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -33,12 +33,12 @@ import com.google.android.exoplayer2.util.Assertions;
*/ */
@RequiresApi(21) @RequiresApi(21)
/* package */ final class AsynchronousMediaCodecAdapter implements MediaCodecAdapter { /* package */ final class AsynchronousMediaCodecAdapter implements MediaCodecAdapter {
private MediaCodecAsyncCallback mediaCodecAsyncCallback; private final MediaCodecAsyncCallback mediaCodecAsyncCallback;
private final Handler handler; private final Handler handler;
private final MediaCodec codec; private final MediaCodec codec;
@Nullable private IllegalStateException internalException; @Nullable private IllegalStateException internalException;
private boolean flushing; private boolean flushing;
private Runnable onCodecStart; private Runnable codecStartRunnable;
/** /**
* Create a new {@code AsynchronousMediaCodecAdapter}. * Create a new {@code AsynchronousMediaCodecAdapter}.
...@@ -51,11 +51,16 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -51,11 +51,16 @@ import com.google.android.exoplayer2.util.Assertions;
@VisibleForTesting @VisibleForTesting
/* package */ AsynchronousMediaCodecAdapter(MediaCodec codec, Looper looper) { /* package */ AsynchronousMediaCodecAdapter(MediaCodec codec, Looper looper) {
this.mediaCodecAsyncCallback = new MediaCodecAsyncCallback(); mediaCodecAsyncCallback = new MediaCodecAsyncCallback();
handler = new Handler(looper); handler = new Handler(looper);
this.codec = codec; this.codec = codec;
this.codec.setCallback(mediaCodecAsyncCallback); this.codec.setCallback(mediaCodecAsyncCallback);
onCodecStart = () -> codec.start(); codecStartRunnable = codec::start;
}
@Override
public void start() {
codecStartRunnable.run();
} }
@Override @Override
...@@ -105,7 +110,7 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -105,7 +110,7 @@ import com.google.android.exoplayer2.util.Assertions;
flushing = false; flushing = false;
mediaCodecAsyncCallback.flush(); mediaCodecAsyncCallback.flush();
try { try {
onCodecStart.run(); codecStartRunnable.run();
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
// Catch IllegalStateException directly so that we don't have to wrap it. // Catch IllegalStateException directly so that we don't have to wrap it.
internalException = e; internalException = e;
...@@ -115,8 +120,8 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -115,8 +120,8 @@ import com.google.android.exoplayer2.util.Assertions;
} }
@VisibleForTesting @VisibleForTesting
/* package */ void setOnCodecStart(Runnable onCodecStart) { /* package */ void setCodecStartRunnable(Runnable codecStartRunnable) {
this.onCodecStart = onCodecStart; this.codecStartRunnable = codecStartRunnable;
} }
private void maybeThrowException() throws IllegalStateException { private void maybeThrowException() throws IllegalStateException {
......
...@@ -26,7 +26,6 @@ import androidx.annotation.Nullable; ...@@ -26,7 +26,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
...@@ -54,7 +53,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -54,7 +53,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@MonotonicNonNull private Handler handler; @MonotonicNonNull private Handler handler;
private long pendingFlushCount; private long pendingFlushCount;
private @State int state; private @State int state;
private Runnable onCodecStart; private Runnable codecStartRunnable;
@Nullable private IllegalStateException internalException; @Nullable private IllegalStateException internalException;
/** /**
...@@ -77,31 +76,20 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -77,31 +76,20 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.codec = codec; this.codec = codec;
this.handlerThread = handlerThread; this.handlerThread = handlerThread;
state = STATE_CREATED; state = STATE_CREATED;
onCodecStart = codec::start; codecStartRunnable = codec::start;
} }
/** @Override
* Starts the operation of the instance.
*
* <p>After a call to this method, make sure to call {@link #shutdown()} to terminate the internal
* Thread. You can only call this method once during the lifetime of this instance; calling this
* method again will throw an {@link IllegalStateException}.
*
* @throws IllegalStateException If this method has been called already.
*/
public synchronized void start() { public synchronized void start() {
Assertions.checkState(state == STATE_CREATED);
handlerThread.start(); handlerThread.start();
handler = new Handler(handlerThread.getLooper()); handler = new Handler(handlerThread.getLooper());
codec.setCallback(this, handler); codec.setCallback(this, handler);
codecStartRunnable.run();
state = STATE_STARTED; state = STATE_STARTED;
} }
@Override @Override
public synchronized int dequeueInputBufferIndex() { public synchronized int dequeueInputBufferIndex() {
Assertions.checkState(state == STATE_STARTED);
if (isFlushing()) { if (isFlushing()) {
return MediaCodec.INFO_TRY_AGAIN_LATER; return MediaCodec.INFO_TRY_AGAIN_LATER;
} else { } else {
...@@ -112,8 +100,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -112,8 +100,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public synchronized int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) { public synchronized int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
Assertions.checkState(state == STATE_STARTED);
if (isFlushing()) { if (isFlushing()) {
return MediaCodec.INFO_TRY_AGAIN_LATER; return MediaCodec.INFO_TRY_AGAIN_LATER;
} else { } else {
...@@ -124,15 +110,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -124,15 +110,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public synchronized MediaFormat getOutputFormat() { public synchronized MediaFormat getOutputFormat() {
Assertions.checkState(state == STATE_STARTED);
return mediaCodecAsyncCallback.getOutputFormat(); return mediaCodecAsyncCallback.getOutputFormat();
} }
@Override @Override
public synchronized void flush() { public synchronized void flush() {
Assertions.checkState(state == STATE_STARTED);
codec.flush(); codec.flush();
++pendingFlushCount; ++pendingFlushCount;
Util.castNonNull(handler).post(this::onFlushCompleted); Util.castNonNull(handler).post(this::onFlushCompleted);
...@@ -177,8 +159,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -177,8 +159,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
@VisibleForTesting @VisibleForTesting
/* package */ void setOnCodecStart(Runnable onCodecStart) { /* package */ void setCodecStartRunnable(Runnable codecStartRunnable) {
this.onCodecStart = onCodecStart; this.codecStartRunnable = codecStartRunnable;
} }
private synchronized void onFlushCompleted() { private synchronized void onFlushCompleted() {
...@@ -199,7 +181,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -199,7 +181,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
mediaCodecAsyncCallback.flush(); mediaCodecAsyncCallback.flush();
try { try {
onCodecStart.run(); codecStartRunnable.run();
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
internalException = e; internalException = e;
} catch (Exception e) { } catch (Exception e) {
......
...@@ -32,6 +32,13 @@ import android.media.MediaFormat; ...@@ -32,6 +32,13 @@ import android.media.MediaFormat;
/* package */ interface MediaCodecAdapter { /* package */ interface MediaCodecAdapter {
/** /**
* Starts this instance.
*
* @see MediaCodec#start().
*/
void start();
/**
* Returns the next available input buffer index from the underlying {@link MediaCodec} or {@link * Returns the next available input buffer index from the underlying {@link MediaCodec} or {@link
* MediaCodec#INFO_TRY_AGAIN_LATER} if no such buffer exists. * MediaCodec#INFO_TRY_AGAIN_LATER} if no such buffer exists.
* *
......
...@@ -995,13 +995,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -995,13 +995,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} else if (mediaCodecOperationMode == OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD } else if (mediaCodecOperationMode == OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD
&& Util.SDK_INT >= 23) { && Util.SDK_INT >= 23) {
codecAdapter = new DedicatedThreadAsyncMediaCodecAdapter(codec, getTrackType()); codecAdapter = new DedicatedThreadAsyncMediaCodecAdapter(codec, getTrackType());
((DedicatedThreadAsyncMediaCodecAdapter) codecAdapter).start();
} else if (mediaCodecOperationMode == OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK } else if (mediaCodecOperationMode == OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK
&& Util.SDK_INT >= 23) { && Util.SDK_INT >= 23) {
codecAdapter = new MultiLockAsyncMediaCodecAdapter(codec, getTrackType()); codecAdapter = new MultiLockAsyncMediaCodecAdapter(codec, getTrackType());
((MultiLockAsyncMediaCodecAdapter) codecAdapter).start();
} else { } else {
codecAdapter = new SynchronousMediaCodecAdapter(codec, getDequeueOutputBufferTimeoutUs()); codecAdapter = new SynchronousMediaCodecAdapter(codec);
} }
TraceUtil.endSection(); TraceUtil.endSection();
...@@ -1009,7 +1007,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -1009,7 +1007,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
configureCodec(codecInfo, codec, inputFormat, crypto, codecOperatingRate); configureCodec(codecInfo, codec, inputFormat, crypto, codecOperatingRate);
TraceUtil.endSection(); TraceUtil.endSection();
TraceUtil.beginSection("startCodec"); TraceUtil.beginSection("startCodec");
codec.start(); codecAdapter.start();
TraceUtil.endSection(); TraceUtil.endSection();
codecInitializedTimestamp = SystemClock.elapsedRealtime(); codecInitializedTimestamp = SystemClock.elapsedRealtime();
getCodecBuffers(codec); getCodecBuffers(codec);
...@@ -1461,15 +1459,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -1461,15 +1459,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
/** /**
* Returns the maximum time to block whilst waiting for a decoded output buffer.
*
* @return The maximum time to block, in microseconds.
*/
protected long getDequeueOutputBufferTimeoutUs() {
return 0;
}
/**
* Returns the {@link MediaFormat#KEY_OPERATING_RATE} value for a given renderer operating rate, * Returns the {@link MediaFormat#KEY_OPERATING_RATE} value for a given renderer operating rate,
* current {@link Format} and set of possible stream formats. * current {@link Format} and set of possible stream formats.
* *
......
...@@ -27,7 +27,6 @@ import androidx.annotation.Nullable; ...@@ -27,7 +27,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.IntArrayQueue; import com.google.android.exoplayer2.util.IntArrayQueue;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.util.ArrayDeque; import java.util.ArrayDeque;
...@@ -94,7 +93,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -94,7 +93,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final HandlerThread handlerThread; private final HandlerThread handlerThread;
@MonotonicNonNull private Handler handler; @MonotonicNonNull private Handler handler;
private Runnable onCodecStart; private Runnable codecStartRunnable;
/** Creates a new instance that wraps the specified {@link MediaCodec}. */ /** Creates a new instance that wraps the specified {@link MediaCodec}. */
/* package */ MultiLockAsyncMediaCodecAdapter(MediaCodec codec, int trackType) { /* package */ MultiLockAsyncMediaCodecAdapter(MediaCodec codec, int trackType) {
...@@ -114,25 +113,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -114,25 +113,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
codecException = null; codecException = null;
state = STATE_CREATED; state = STATE_CREATED;
this.handlerThread = handlerThread; this.handlerThread = handlerThread;
onCodecStart = codec::start; codecStartRunnable = codec::start;
} }
/** @Override
* Starts the operation of this instance.
*
* <p>After a call to this method, make sure to call {@link #shutdown()} to terminate the internal
* Thread. You can only call this method once during the lifetime of an instance; calling this
* method again will throw an {@link IllegalStateException}.
*
* @throws IllegalStateException If this method has been called already.
*/
public void start() { public void start() {
synchronized (objectStateLock) { synchronized (objectStateLock) {
Assertions.checkState(state == STATE_CREATED);
handlerThread.start(); handlerThread.start();
handler = new Handler(handlerThread.getLooper()); handler = new Handler(handlerThread.getLooper());
codec.setCallback(this, handler); codec.setCallback(this, handler);
codecStartRunnable.run();
state = STATE_STARTED; state = STATE_STARTED;
} }
} }
...@@ -140,8 +130,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -140,8 +130,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public int dequeueInputBufferIndex() { public int dequeueInputBufferIndex() {
synchronized (objectStateLock) { synchronized (objectStateLock) {
Assertions.checkState(state == STATE_STARTED);
if (isFlushing()) { if (isFlushing()) {
return MediaCodec.INFO_TRY_AGAIN_LATER; return MediaCodec.INFO_TRY_AGAIN_LATER;
} else { } else {
...@@ -154,8 +142,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -154,8 +142,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) { public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
synchronized (objectStateLock) { synchronized (objectStateLock) {
Assertions.checkState(state == STATE_STARTED);
if (isFlushing()) { if (isFlushing()) {
return MediaCodec.INFO_TRY_AGAIN_LATER; return MediaCodec.INFO_TRY_AGAIN_LATER;
} else { } else {
...@@ -168,8 +154,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -168,8 +154,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public MediaFormat getOutputFormat() { public MediaFormat getOutputFormat() {
synchronized (objectStateLock) { synchronized (objectStateLock) {
Assertions.checkState(state == STATE_STARTED);
if (currentFormat == null) { if (currentFormat == null) {
throw new IllegalStateException(); throw new IllegalStateException();
} }
...@@ -181,8 +165,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -181,8 +165,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public void flush() { public void flush() {
synchronized (objectStateLock) { synchronized (objectStateLock) {
Assertions.checkState(state == STATE_STARTED);
codec.flush(); codec.flush();
pendingFlush++; pendingFlush++;
Util.castNonNull(handler).post(this::onFlushComplete); Util.castNonNull(handler).post(this::onFlushComplete);
...@@ -200,8 +182,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -200,8 +182,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
@VisibleForTesting @VisibleForTesting
/* package */ void setOnCodecStart(Runnable onCodecStart) { /* package */ void setCodecStartRunnable(Runnable codecStartRunnable) {
this.onCodecStart = onCodecStart; this.codecStartRunnable = codecStartRunnable;
} }
private int dequeueAvailableInputBufferIndex() { private int dequeueAvailableInputBufferIndex() {
...@@ -307,7 +289,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -307,7 +289,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
clearAvailableOutput(); clearAvailableOutput();
codecException = null; codecException = null;
try { try {
onCodecStart.run(); codecStartRunnable.run();
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
codecException = e; codecException = e;
} catch (Exception e) { } catch (Exception e) {
......
...@@ -23,12 +23,16 @@ import android.media.MediaFormat; ...@@ -23,12 +23,16 @@ import android.media.MediaFormat;
* A {@link MediaCodecAdapter} that operates the underlying {@link MediaCodec} in synchronous mode. * A {@link MediaCodecAdapter} that operates the underlying {@link MediaCodec} in synchronous mode.
*/ */
/* package */ final class SynchronousMediaCodecAdapter implements MediaCodecAdapter { /* package */ final class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
private final MediaCodec codec; private final MediaCodec codec;
private final long dequeueOutputBufferTimeoutMs;
public SynchronousMediaCodecAdapter(MediaCodec mediaCodec, long dequeueOutputBufferTimeoutMs) { public SynchronousMediaCodecAdapter(MediaCodec mediaCodec) {
this.codec = mediaCodec; this.codec = mediaCodec;
this.dequeueOutputBufferTimeoutMs = dequeueOutputBufferTimeoutMs; }
@Override
public void start() {
codec.start();
} }
@Override @Override
...@@ -38,7 +42,7 @@ import android.media.MediaFormat; ...@@ -38,7 +42,7 @@ import android.media.MediaFormat;
@Override @Override
public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) { public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
return codec.dequeueOutputBuffer(bufferInfo, dequeueOutputBufferTimeoutMs); return codec.dequeueOutputBuffer(bufferInfo, 0);
} }
@Override @Override
......
...@@ -773,7 +773,7 @@ public final class DownloadHelper { ...@@ -773,7 +773,7 @@ public final class DownloadHelper {
} }
// Initialization of array of Lists. // Initialization of array of Lists.
@SuppressWarnings("unchecked") @SuppressWarnings({"unchecked", "rawtypes"})
private void onMediaPrepared() { private void onMediaPrepared() {
Assertions.checkNotNull(mediaPreparer); Assertions.checkNotNull(mediaPreparer);
Assertions.checkNotNull(mediaPreparer.mediaPeriods); Assertions.checkNotNull(mediaPreparer.mediaPeriods);
......
...@@ -165,7 +165,7 @@ public final class Requirements implements Parcelable { ...@@ -165,7 +165,7 @@ public final class Requirements implements Parcelable {
private static boolean isInternetConnectivityValidated(ConnectivityManager connectivityManager) { private static boolean isInternetConnectivityValidated(ConnectivityManager connectivityManager) {
// It's possible to query NetworkCapabilities from API level 23, but RequirementsWatcher only // It's possible to query NetworkCapabilities from API level 23, but RequirementsWatcher only
// fires an event to update its Requirements when NetworkCapabilities change from API level 24. // fires an event to update its Requirements when NetworkCapabilities change from API level 24.
// Since Requirements wont be updated, we assume connectivity is validated on API level 23. // Since Requirements won't be updated, we assume connectivity is validated on API level 23.
if (Util.SDK_INT < 24) { if (Util.SDK_INT < 24) {
return true; return true;
} }
......
/*
* Copyright (C) 2020 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.text;
import android.text.Spannable;
import android.text.style.ForegroundColorSpan;
/**
* Utility methods for Android <a href="https://developer.android.com/guide/topics/text/spans">span
* styling</a>.
*/
public final class SpanUtil {
/**
* Adds {@code span} to {@code spannable} between {@code start} and {@code end}, removing any
* existing spans of the same type and with the same indices and flags.
*
* <p>This is useful for types of spans that don't make sense to duplicate and where the
* evaluation order might have an unexpected impact on the final text, e.g. {@link
* ForegroundColorSpan}.
*
* @param spannable The {@link Spannable} to add {@code span} to.
* @param span The span object to be added.
* @param start The start index to add the new span at.
* @param end The end index to add the new span at.
* @param spanFlags The flags to pass to {@link Spannable#setSpan(Object, int, int, int)}.
*/
public static void addOrReplaceSpan(
Spannable spannable, Object span, int start, int end, int spanFlags) {
Object[] existingSpans = spannable.getSpans(start, end, span.getClass());
for (Object existingSpan : existingSpans) {
if (spannable.getSpanStart(existingSpan) == start
&& spannable.getSpanEnd(existingSpan) == end
&& spannable.getSpanFlags(existingSpan) == spanFlags) {
spannable.removeSpan(existingSpan);
}
}
spannable.setSpan(span, start, end, spanFlags);
}
private SpanUtil() {}
}
...@@ -481,8 +481,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -481,8 +481,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* *
* @return The parsed object data. * @return The parsed object data.
*/ */
// incompatible types in argument.
@SuppressWarnings("nullness:argument.type.incompatible")
private static ObjectData parseObjectData(ParsableBitArray data) { private static ObjectData parseObjectData(ParsableBitArray data) {
int objectId = data.readBits(16); int objectId = data.readBits(16);
data.skipBits(4); // Skip object_version_number data.skipBits(4); // Skip object_version_number
...@@ -490,8 +488,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -490,8 +488,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
boolean nonModifyingColorFlag = data.readBit(); boolean nonModifyingColorFlag = data.readBit();
data.skipBits(1); // Skip reserved. data.skipBits(1); // Skip reserved.
@Nullable byte[] topFieldData = null; byte[] topFieldData = Util.EMPTY_BYTE_ARRAY;
@Nullable byte[] bottomFieldData = null; byte[] bottomFieldData = Util.EMPTY_BYTE_ARRAY;
if (objectCodingMethod == OBJECT_CODING_STRING) { if (objectCodingMethod == OBJECT_CODING_STRING) {
int numberOfCodes = data.readBits(8); int numberOfCodes = data.readBits(8);
......
/*
* Copyright (C) 2020 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.text.span;
/**
* A styling span for horizontal text in a vertical context.
*
* <p>This is used in vertical text to write some characters in a horizontal orientation, known in
* Japanese as tate-chu-yoko.
*
* <p>More information on <a
* href="https://www.w3.org/TR/jlreq/#handling_of_tatechuyoko">tate-chu-yoko</a> and <a
* href="https://developer.android.com/guide/topics/text/spans">span styling</a>.
*/
// NOTE: There's no Android layout support for this, so this span currently doesn't extend any
// styling superclasses (e.g. MetricAffectingSpan). The only way to render this styling is to
// extract the spans and do the layout manually.
public final class HorizontalTextInVerticalContextSpan {}
/*
* Copyright (C) 2020 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.text.span;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import androidx.annotation.IntDef;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
/**
* A styling span for ruby text.
*
* <p>The text covered by this span is known as the "base text", and the ruby text is stored in
* {@link #rubyText}.
*
* <p>More information on <a href="https://en.wikipedia.org/wiki/Ruby_character">ruby characters</a>
* and <a href="https://developer.android.com/guide/topics/text/spans">span styling</a>.
*/
// NOTE: There's no Android layout support for rubies, so this span currently doesn't extend any
// styling superclasses (e.g. MetricAffectingSpan). The only way to render these rubies is to
// extract the spans and do the layout manually.
// TODO: Consider adding support for parenthetical text to be used when rendering doesn't support
// rubies (e.g. HTML <rp> tag).
public final class RubySpan {
/** The ruby position is unknown. */
public static final int POSITION_UNKNOWN = -1;
/**
* The ruby text should be positioned above the base text.
*
* <p>For vertical text it should be positioned to the right, same as CSS's <a
* href="https://developer.mozilla.org/en-US/docs/Web/CSS/ruby-position">ruby-position</a>.
*/
public static final int POSITION_OVER = 1;
/**
* The ruby text should be positioned below the base text.
*
* <p>For vertical text it should be positioned to the left, same as CSS's <a
* href="https://developer.mozilla.org/en-US/docs/Web/CSS/ruby-position">ruby-position</a>.
*/
public static final int POSITION_UNDER = 2;
/**
* The possible positions of the ruby text relative to the base text.
*
* <p>One of:
*
* <ul>
* <li>{@link #POSITION_UNKNOWN}
* <li>{@link #POSITION_OVER}
* <li>{@link #POSITION_UNDER}
* </ul>
*/
@Documented
@Retention(SOURCE)
@IntDef({POSITION_UNKNOWN, POSITION_OVER, POSITION_UNDER})
public @interface Position {}
/** The ruby text, i.e. the smaller explanatory characters. */
public final String rubyText;
/** The position of the ruby text relative to the base text. */
@Position public final int position;
public RubySpan(String rubyText, @Position int position) {
this.rubyText = rubyText;
this.position = position;
}
}
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer2.text.ttml; package com.google.android.exoplayer2.text.ttml;
import android.text.Spannable;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.Spanned; import android.text.Spanned;
import android.text.style.AbsoluteSizeSpan; import android.text.style.AbsoluteSizeSpan;
...@@ -27,6 +26,7 @@ import android.text.style.StrikethroughSpan; ...@@ -27,6 +26,7 @@ import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
import android.text.style.TypefaceSpan; import android.text.style.TypefaceSpan;
import android.text.style.UnderlineSpan; import android.text.style.UnderlineSpan;
import com.google.android.exoplayer2.text.SpanUtil;
import java.util.Map; import java.util.Map;
/** /**
...@@ -77,32 +77,60 @@ import java.util.Map; ...@@ -77,32 +77,60 @@ import java.util.Map;
builder.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); builder.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
if (style.hasFontColor()) { if (style.hasFontColor()) {
builder.setSpan(new ForegroundColorSpan(style.getFontColor()), start, end, SpanUtil.addOrReplaceSpan(
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); builder,
new ForegroundColorSpan(style.getFontColor()),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
if (style.hasBackgroundColor()) { if (style.hasBackgroundColor()) {
builder.setSpan(new BackgroundColorSpan(style.getBackgroundColor()), start, end, SpanUtil.addOrReplaceSpan(
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); builder,
new BackgroundColorSpan(style.getBackgroundColor()),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
if (style.getFontFamily() != null) { if (style.getFontFamily() != null) {
builder.setSpan(new TypefaceSpan(style.getFontFamily()), start, end, SpanUtil.addOrReplaceSpan(
builder,
new TypefaceSpan(style.getFontFamily()),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
if (style.getTextAlign() != null) { if (style.getTextAlign() != null) {
builder.setSpan(new AlignmentSpan.Standard(style.getTextAlign()), start, end, SpanUtil.addOrReplaceSpan(
builder,
new AlignmentSpan.Standard(style.getTextAlign()),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
switch (style.getFontSizeUnit()) { switch (style.getFontSizeUnit()) {
case TtmlStyle.FONT_SIZE_UNIT_PIXEL: case TtmlStyle.FONT_SIZE_UNIT_PIXEL:
builder.setSpan(new AbsoluteSizeSpan((int) style.getFontSize(), true), start, end, SpanUtil.addOrReplaceSpan(
builder,
new AbsoluteSizeSpan((int) style.getFontSize(), true),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break; break;
case TtmlStyle.FONT_SIZE_UNIT_EM: case TtmlStyle.FONT_SIZE_UNIT_EM:
builder.setSpan(new RelativeSizeSpan(style.getFontSize()), start, end, SpanUtil.addOrReplaceSpan(
builder,
new RelativeSizeSpan(style.getFontSize()),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break; break;
case TtmlStyle.FONT_SIZE_UNIT_PERCENT: case TtmlStyle.FONT_SIZE_UNIT_PERCENT:
builder.setSpan(new RelativeSizeSpan(style.getFontSize() / 100), start, end, SpanUtil.addOrReplaceSpan(
builder,
new RelativeSizeSpan(style.getFontSize() / 100),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break; break;
case TtmlStyle.UNSPECIFIED: case TtmlStyle.UNSPECIFIED:
......
...@@ -31,14 +31,19 @@ import java.util.regex.Pattern; ...@@ -31,14 +31,19 @@ import java.util.regex.Pattern;
*/ */
/* package */ final class CssParser { /* package */ final class CssParser {
private static final String TAG = "CssParser";
private static final String RULE_START = "{";
private static final String RULE_END = "}";
private static final String PROPERTY_BGCOLOR = "background-color"; private static final String PROPERTY_BGCOLOR = "background-color";
private static final String PROPERTY_FONT_FAMILY = "font-family"; private static final String PROPERTY_FONT_FAMILY = "font-family";
private static final String PROPERTY_FONT_WEIGHT = "font-weight"; private static final String PROPERTY_FONT_WEIGHT = "font-weight";
private static final String PROPERTY_TEXT_COMBINE_UPRIGHT = "text-combine-upright";
private static final String VALUE_ALL = "all";
private static final String VALUE_DIGITS = "digits";
private static final String PROPERTY_TEXT_DECORATION = "text-decoration"; private static final String PROPERTY_TEXT_DECORATION = "text-decoration";
private static final String VALUE_BOLD = "bold"; private static final String VALUE_BOLD = "bold";
private static final String VALUE_UNDERLINE = "underline"; private static final String VALUE_UNDERLINE = "underline";
private static final String RULE_START = "{";
private static final String RULE_END = "}";
private static final String PROPERTY_FONT_STYLE = "font-style"; private static final String PROPERTY_FONT_STYLE = "font-style";
private static final String VALUE_ITALIC = "italic"; private static final String VALUE_ITALIC = "italic";
...@@ -182,6 +187,8 @@ import java.util.regex.Pattern; ...@@ -182,6 +187,8 @@ import java.util.regex.Pattern;
style.setFontColor(ColorParser.parseCssColor(value)); style.setFontColor(ColorParser.parseCssColor(value));
} else if (PROPERTY_BGCOLOR.equals(property)) { } else if (PROPERTY_BGCOLOR.equals(property)) {
style.setBackgroundColor(ColorParser.parseCssColor(value)); style.setBackgroundColor(ColorParser.parseCssColor(value));
} else if (PROPERTY_TEXT_COMBINE_UPRIGHT.equals(property)) {
style.setCombineUpright(VALUE_ALL.equals(value) || value.startsWith(VALUE_DIGITS));
} else if (PROPERTY_TEXT_DECORATION.equals(property)) { } else if (PROPERTY_TEXT_DECORATION.equals(property)) {
if (VALUE_UNDERLINE.equals(value)) { if (VALUE_UNDERLINE.equals(value)) {
style.setUnderline(true); style.setUnderline(true);
......
...@@ -95,6 +95,7 @@ public final class WebvttCssStyle { ...@@ -95,6 +95,7 @@ public final class WebvttCssStyle {
@FontSizeUnit private int fontSizeUnit; @FontSizeUnit private int fontSizeUnit;
private float fontSize; private float fontSize;
@Nullable private Layout.Alignment textAlign; @Nullable private Layout.Alignment textAlign;
private boolean combineUpright;
// Calling reset() is forbidden because `this` isn't initialized. This can be safely suppressed // Calling reset() is forbidden because `this` isn't initialized. This can be safely suppressed
// because reset() only assigns fields, it doesn't read any. // because reset() only assigns fields, it doesn't read any.
...@@ -118,6 +119,7 @@ public final class WebvttCssStyle { ...@@ -118,6 +119,7 @@ public final class WebvttCssStyle {
italic = UNSPECIFIED; italic = UNSPECIFIED;
fontSizeUnit = UNSPECIFIED; fontSizeUnit = UNSPECIFIED;
textAlign = null; textAlign = null;
combineUpright = false;
} }
public void setTargetId(String targetId) { public void setTargetId(String targetId) {
...@@ -287,35 +289,12 @@ public final class WebvttCssStyle { ...@@ -287,35 +289,12 @@ public final class WebvttCssStyle {
return fontSize; return fontSize;
} }
public void cascadeFrom(WebvttCssStyle style) { public void setCombineUpright(boolean enabled) {
if (style.hasFontColor) { this.combineUpright = enabled;
setFontColor(style.fontColor); }
}
if (style.bold != UNSPECIFIED) { public boolean getCombineUpright() {
bold = style.bold; return combineUpright;
}
if (style.italic != UNSPECIFIED) {
italic = style.italic;
}
if (style.fontFamily != null) {
fontFamily = style.fontFamily;
}
if (linethrough == UNSPECIFIED) {
linethrough = style.linethrough;
}
if (underline == UNSPECIFIED) {
underline = style.underline;
}
if (textAlign == null) {
textAlign = style.textAlign;
}
if (fontSizeUnit == UNSPECIFIED) {
fontSizeUnit = style.fontSizeUnit;
fontSize = style.fontSize;
}
if (style.hasBackgroundColor) {
setBackgroundColor(style.backgroundColor);
}
} }
private static int updateScoreForMatch( private static int updateScoreForMatch(
......
...@@ -15,11 +15,11 @@ ...@@ -15,11 +15,11 @@
*/ */
package com.google.android.exoplayer2.text.webvtt; package com.google.android.exoplayer2.text.webvtt;
import static com.google.android.exoplayer2.text.SpanUtil.addOrReplaceSpan;
import static java.lang.annotation.RetentionPolicy.SOURCE; import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.text.Layout; import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.Spanned; import android.text.Spanned;
import android.text.SpannedString; import android.text.SpannedString;
...@@ -37,6 +37,8 @@ import androidx.annotation.IntDef; ...@@ -37,6 +37,8 @@ import androidx.annotation.IntDef;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan;
import com.google.android.exoplayer2.text.span.RubySpan;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
...@@ -120,11 +122,13 @@ public final class WebvttCueParser { ...@@ -120,11 +122,13 @@ public final class WebvttCueParser {
private static final String ENTITY_NON_BREAK_SPACE = "nbsp"; private static final String ENTITY_NON_BREAK_SPACE = "nbsp";
private static final String TAG_BOLD = "b"; private static final String TAG_BOLD = "b";
private static final String TAG_CLASS = "c";
private static final String TAG_ITALIC = "i"; private static final String TAG_ITALIC = "i";
private static final String TAG_LANG = "lang";
private static final String TAG_RUBY = "ruby";
private static final String TAG_RUBY_TEXT = "rt";
private static final String TAG_UNDERLINE = "u"; private static final String TAG_UNDERLINE = "u";
private static final String TAG_CLASS = "c";
private static final String TAG_VOICE = "v"; private static final String TAG_VOICE = "v";
private static final String TAG_LANG = "lang";
private static final int STYLE_BOLD = Typeface.BOLD; private static final int STYLE_BOLD = Typeface.BOLD;
private static final int STYLE_ITALIC = Typeface.ITALIC; private static final int STYLE_ITALIC = Typeface.ITALIC;
...@@ -197,6 +201,7 @@ public final class WebvttCueParser { ...@@ -197,6 +201,7 @@ public final class WebvttCueParser {
ArrayDeque<StartTag> startTagStack = new ArrayDeque<>(); ArrayDeque<StartTag> startTagStack = new ArrayDeque<>();
List<StyleMatch> scratchStyleMatches = new ArrayList<>(); List<StyleMatch> scratchStyleMatches = new ArrayList<>();
int pos = 0; int pos = 0;
List<Element> nestedElements = new ArrayList<>();
while (pos < markup.length()) { while (pos < markup.length()) {
char curr = markup.charAt(pos); char curr = markup.charAt(pos);
switch (curr) { switch (curr) {
...@@ -225,8 +230,14 @@ public final class WebvttCueParser { ...@@ -225,8 +230,14 @@ public final class WebvttCueParser {
break; break;
} }
startTag = startTagStack.pop(); startTag = startTagStack.pop();
applySpansForTag(id, startTag, spannedText, styles, scratchStyleMatches); applySpansForTag(
} while(!startTag.name.equals(tagName)); id, startTag, nestedElements, spannedText, styles, scratchStyleMatches);
if (!startTagStack.isEmpty()) {
nestedElements.add(new Element(startTag, spannedText.length()));
} else {
nestedElements.clear();
}
} while (!startTag.name.equals(tagName));
} else if (!isVoidTag) { } else if (!isVoidTag) {
startTagStack.push(StartTag.buildStartTag(fullTagExpression, spannedText.length())); startTagStack.push(StartTag.buildStartTag(fullTagExpression, spannedText.length()));
} }
...@@ -256,9 +267,15 @@ public final class WebvttCueParser { ...@@ -256,9 +267,15 @@ public final class WebvttCueParser {
} }
// apply unclosed tags // apply unclosed tags
while (!startTagStack.isEmpty()) { while (!startTagStack.isEmpty()) {
applySpansForTag(id, startTagStack.pop(), spannedText, styles, scratchStyleMatches); applySpansForTag(
} id, startTagStack.pop(), nestedElements, spannedText, styles, scratchStyleMatches);
applySpansForTag(id, StartTag.buildWholeCueVirtualTag(), spannedText, styles, }
applySpansForTag(
id,
StartTag.buildWholeCueVirtualTag(),
/* nestedElements= */ Collections.emptyList(),
spannedText,
styles,
scratchStyleMatches); scratchStyleMatches);
return SpannedString.valueOf(spannedText); return SpannedString.valueOf(spannedText);
} }
...@@ -442,6 +459,8 @@ public final class WebvttCueParser { ...@@ -442,6 +459,8 @@ public final class WebvttCueParser {
case TAG_CLASS: case TAG_CLASS:
case TAG_ITALIC: case TAG_ITALIC:
case TAG_LANG: case TAG_LANG:
case TAG_RUBY:
case TAG_RUBY_TEXT:
case TAG_UNDERLINE: case TAG_UNDERLINE:
case TAG_VOICE: case TAG_VOICE:
return true; return true;
...@@ -453,6 +472,7 @@ public final class WebvttCueParser { ...@@ -453,6 +472,7 @@ public final class WebvttCueParser {
private static void applySpansForTag( private static void applySpansForTag(
@Nullable String cueId, @Nullable String cueId,
StartTag startTag, StartTag startTag,
List<Element> nestedElements,
SpannableStringBuilder text, SpannableStringBuilder text,
List<WebvttCssStyle> styles, List<WebvttCssStyle> styles,
List<StyleMatch> scratchStyleMatches) { List<StyleMatch> scratchStyleMatches) {
...@@ -467,6 +487,29 @@ public final class WebvttCueParser { ...@@ -467,6 +487,29 @@ public final class WebvttCueParser {
text.setSpan(new StyleSpan(STYLE_ITALIC), start, end, text.setSpan(new StyleSpan(STYLE_ITALIC), start, end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break; break;
case TAG_RUBY:
@Nullable Element rubyTextElement = null;
for (int i = 0; i < nestedElements.size(); i++) {
if (TAG_RUBY_TEXT.equals(nestedElements.get(i).startTag.name)) {
rubyTextElement = nestedElements.get(i);
// Behaviour of multiple <rt> tags inside <ruby> is undefined, so use the first one.
break;
}
}
if (rubyTextElement == null) {
break;
}
// Move the rubyText from spannedText into the RubySpan.
CharSequence rubyText =
text.subSequence(rubyTextElement.startTag.position, rubyTextElement.endPosition);
text.delete(rubyTextElement.startTag.position, rubyTextElement.endPosition);
end -= rubyText.length();
text.setSpan(
new RubySpan(rubyText.toString(), RubySpan.POSITION_OVER),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case TAG_UNDERLINE: case TAG_UNDERLINE:
text.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); text.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break; break;
...@@ -492,7 +535,11 @@ public final class WebvttCueParser { ...@@ -492,7 +535,11 @@ public final class WebvttCueParser {
return; return;
} }
if (style.getStyle() != WebvttCssStyle.UNSPECIFIED) { if (style.getStyle() != WebvttCssStyle.UNSPECIFIED) {
spannedText.setSpan(new StyleSpan(style.getStyle()), start, end, addOrReplaceSpan(
spannedText,
new StyleSpan(style.getStyle()),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
if (style.isLinethrough()) { if (style.isLinethrough()) {
...@@ -502,39 +549,71 @@ public final class WebvttCueParser { ...@@ -502,39 +549,71 @@ public final class WebvttCueParser {
spannedText.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); spannedText.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
if (style.hasFontColor()) { if (style.hasFontColor()) {
spannedText.setSpan(new ForegroundColorSpan(style.getFontColor()), start, end, addOrReplaceSpan(
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); spannedText,
new ForegroundColorSpan(style.getFontColor()),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
if (style.hasBackgroundColor()) { if (style.hasBackgroundColor()) {
spannedText.setSpan(new BackgroundColorSpan(style.getBackgroundColor()), start, end, addOrReplaceSpan(
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); spannedText,
new BackgroundColorSpan(style.getBackgroundColor()),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
if (style.getFontFamily() != null) { if (style.getFontFamily() != null) {
spannedText.setSpan(new TypefaceSpan(style.getFontFamily()), start, end, addOrReplaceSpan(
spannedText,
new TypefaceSpan(style.getFontFamily()),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
Layout.Alignment textAlign = style.getTextAlign(); Layout.Alignment textAlign = style.getTextAlign();
if (textAlign != null) { if (textAlign != null) {
spannedText.setSpan( addOrReplaceSpan(
new AlignmentSpan.Standard(textAlign), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); spannedText,
new AlignmentSpan.Standard(textAlign),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
switch (style.getFontSizeUnit()) { switch (style.getFontSizeUnit()) {
case WebvttCssStyle.FONT_SIZE_UNIT_PIXEL: case WebvttCssStyle.FONT_SIZE_UNIT_PIXEL:
spannedText.setSpan(new AbsoluteSizeSpan((int) style.getFontSize(), true), start, end, addOrReplaceSpan(
spannedText,
new AbsoluteSizeSpan((int) style.getFontSize(), true),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break; break;
case WebvttCssStyle.FONT_SIZE_UNIT_EM: case WebvttCssStyle.FONT_SIZE_UNIT_EM:
spannedText.setSpan(new RelativeSizeSpan(style.getFontSize()), start, end, addOrReplaceSpan(
spannedText,
new RelativeSizeSpan(style.getFontSize()),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break; break;
case WebvttCssStyle.FONT_SIZE_UNIT_PERCENT: case WebvttCssStyle.FONT_SIZE_UNIT_PERCENT:
spannedText.setSpan(new RelativeSizeSpan(style.getFontSize() / 100), start, end, addOrReplaceSpan(
spannedText,
new RelativeSizeSpan(style.getFontSize() / 100),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break; break;
case WebvttCssStyle.UNSPECIFIED: case WebvttCssStyle.UNSPECIFIED:
// Do nothing. // Do nothing.
break; break;
} }
if (style.getCombineUpright()) {
spannedText.setSpan(
new HorizontalTextInVerticalContextSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
} }
/** /**
...@@ -773,4 +852,19 @@ public final class WebvttCueParser { ...@@ -773,4 +852,19 @@ public final class WebvttCueParser {
} }
} }
/** Information about a complete element (i.e. start tag and end position). */
private static class Element {
private final StartTag startTag;
/**
* The position of the end of this element's text in the un-marked-up cue text (i.e. the
* corollary to {@link StartTag#position}).
*/
private final int endPosition;
private Element(StartTag startTag, int endPosition) {
this.startTag = startTag;
this.endPosition = endPosition;
}
}
} }
...@@ -203,9 +203,10 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList ...@@ -203,9 +203,10 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList
result.append(C.NETWORK_TYPE_2G, DEFAULT_INITIAL_BITRATE_ESTIMATES_2G[groupIndices[1]]); result.append(C.NETWORK_TYPE_2G, DEFAULT_INITIAL_BITRATE_ESTIMATES_2G[groupIndices[1]]);
result.append(C.NETWORK_TYPE_3G, DEFAULT_INITIAL_BITRATE_ESTIMATES_3G[groupIndices[2]]); result.append(C.NETWORK_TYPE_3G, DEFAULT_INITIAL_BITRATE_ESTIMATES_3G[groupIndices[2]]);
result.append(C.NETWORK_TYPE_4G, DEFAULT_INITIAL_BITRATE_ESTIMATES_4G[groupIndices[3]]); result.append(C.NETWORK_TYPE_4G, DEFAULT_INITIAL_BITRATE_ESTIMATES_4G[groupIndices[3]]);
// Assume default Wifi bitrate for Ethernet to prevent using the slower fallback bitrate. // Assume default Wifi bitrate for Ethernet and 5G to prevent using the slower fallback.
result.append( result.append(
C.NETWORK_TYPE_ETHERNET, DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI[groupIndices[0]]); C.NETWORK_TYPE_ETHERNET, DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI[groupIndices[0]]);
result.append(C.NETWORK_TYPE_5G, DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI[groupIndices[0]]);
return result; return result;
} }
......
...@@ -1355,6 +1355,7 @@ public final class Util { ...@@ -1355,6 +1355,7 @@ public final class Util {
public static boolean isEncodingLinearPcm(@C.Encoding int encoding) { public static boolean isEncodingLinearPcm(@C.Encoding int encoding) {
return encoding == C.ENCODING_PCM_8BIT return encoding == C.ENCODING_PCM_8BIT
|| encoding == C.ENCODING_PCM_16BIT || encoding == C.ENCODING_PCM_16BIT
|| encoding == C.ENCODING_PCM_16BIT_BIG_ENDIAN
|| encoding == C.ENCODING_PCM_24BIT || encoding == C.ENCODING_PCM_24BIT
|| encoding == C.ENCODING_PCM_32BIT || encoding == C.ENCODING_PCM_32BIT
|| encoding == C.ENCODING_PCM_FLOAT; || encoding == C.ENCODING_PCM_FLOAT;
...@@ -1423,14 +1424,13 @@ public final class Util { ...@@ -1423,14 +1424,13 @@ public final class Util {
case C.ENCODING_PCM_8BIT: case C.ENCODING_PCM_8BIT:
return channelCount; return channelCount;
case C.ENCODING_PCM_16BIT: case C.ENCODING_PCM_16BIT:
case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
return channelCount * 2; return channelCount * 2;
case C.ENCODING_PCM_24BIT: case C.ENCODING_PCM_24BIT:
return channelCount * 3; return channelCount * 3;
case C.ENCODING_PCM_32BIT: case C.ENCODING_PCM_32BIT:
case C.ENCODING_PCM_FLOAT: case C.ENCODING_PCM_FLOAT:
return channelCount * 4; return channelCount * 4;
case C.ENCODING_PCM_A_LAW:
case C.ENCODING_PCM_MU_LAW:
case C.ENCODING_INVALID: case C.ENCODING_INVALID:
case Format.NO_VALUE: case Format.NO_VALUE:
default: default:
...@@ -2126,6 +2126,8 @@ public final class Util { ...@@ -2126,6 +2126,8 @@ public final class Util {
return C.NETWORK_TYPE_3G; return C.NETWORK_TYPE_3G;
case TelephonyManager.NETWORK_TYPE_LTE: case TelephonyManager.NETWORK_TYPE_LTE:
return C.NETWORK_TYPE_4G; return C.NETWORK_TYPE_4G;
case TelephonyManager.NETWORK_TYPE_NR:
return C.NETWORK_TYPE_5G;
case TelephonyManager.NETWORK_TYPE_IWLAN: case TelephonyManager.NETWORK_TYPE_IWLAN:
return C.NETWORK_TYPE_WIFI; return C.NETWORK_TYPE_WIFI;
case TelephonyManager.NETWORK_TYPE_GSM: case TelephonyManager.NETWORK_TYPE_GSM:
......
...@@ -31,13 +31,13 @@ track 1: ...@@ -31,13 +31,13 @@ track 1:
sample 0: sample 0:
time = 0 time = 0
flags = 1 flags = 1
data = length 59, hash A0217393 data = length 59, hash 1AD38625
sample 1: sample 1:
time = 2345000 time = 2345000
flags = 1 flags = 1
data = length 95, hash 4904F2 data = length 95, hash F331C282
sample 2: sample 2:
time = 4567000 time = 4567000
flags = 1 flags = 1
data = length 59, hash EFAB6D8A data = length 59, hash F8CD7C60
tracksEnded = true tracksEnded = true
...@@ -31,13 +31,13 @@ track 1: ...@@ -31,13 +31,13 @@ track 1:
sample 0: sample 0:
time = 0 time = 0
flags = 1 flags = 1
data = length 59, hash A0217393 data = length 59, hash 1AD38625
sample 1: sample 1:
time = 2345000 time = 2345000
flags = 1 flags = 1
data = length 95, hash 4904F2 data = length 95, hash F331C282
sample 2: sample 2:
time = 4567000 time = 4567000
flags = 1 flags = 1
data = length 59, hash EFAB6D8A data = length 59, hash F8CD7C60
tracksEnded = true tracksEnded = true
...@@ -31,13 +31,13 @@ track 1: ...@@ -31,13 +31,13 @@ track 1:
sample 0: sample 0:
time = 0 time = 0
flags = 1 flags = 1
data = length 59, hash A0217393 data = length 59, hash 1AD38625
sample 1: sample 1:
time = 2345000 time = 2345000
flags = 1 flags = 1
data = length 95, hash 4904F2 data = length 95, hash F331C282
sample 2: sample 2:
time = 4567000 time = 4567000
flags = 1 flags = 1
data = length 59, hash EFAB6D8A data = length 59, hash F8CD7C60
tracksEnded = true tracksEnded = true
...@@ -31,13 +31,13 @@ track 1: ...@@ -31,13 +31,13 @@ track 1:
sample 0: sample 0:
time = 0 time = 0
flags = 1 flags = 1
data = length 59, hash A0217393 data = length 59, hash 1AD38625
sample 1: sample 1:
time = 2345000 time = 2345000
flags = 1 flags = 1
data = length 95, hash 4904F2 data = length 95, hash F331C282
sample 2: sample 2:
time = 4567000 time = 4567000
flags = 1 flags = 1
data = length 59, hash EFAB6D8A data = length 59, hash F8CD7C60
tracksEnded = true tracksEnded = true
seekMap:
isSeekable = true
duration = 760000
getPosition(0) = [[timeUs=0, position=758]]
numberOfTracks = 1
track 0:
format:
bitrate = -1
id = 1
containerMimeType = null
sampleMimeType = audio/ac4
maxInputSize = 622
width = -1
height = -1
frameRate = -1.0
rotationDegrees = 0
pixelWidthHeightRatio = 1.0
channelCount = 2
sampleRate = 48000
pcmEncoding = -1
encoderDelay = 0
encoderPadding = 0
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = und
drmInitData = -
metadata = null
initializationData:
total output bytes = 7613
sample count = 19
sample 0:
time = 0
flags = 1
data = length 367, hash D2762FA
sample 1:
time = 40000
flags = 0
data = length 367, hash BDD3224A
sample 2:
time = 80000
flags = 0
data = length 367, hash 9302227B
sample 3:
time = 120000
flags = 0
data = length 367, hash 72996003
sample 4:
time = 160000
flags = 0
data = length 367, hash 88AE5A1B
sample 5:
time = 200000
flags = 0
data = length 367, hash E5346FE3
sample 6:
time = 240000
flags = 0
data = length 367, hash CE558362
sample 7:
time = 280000
flags = 0
data = length 367, hash 51AD3043
sample 8:
time = 320000
flags = 0
data = length 367, hash EB72E95B
sample 9:
time = 360000
flags = 0
data = length 367, hash 47F8FF23
sample 10:
time = 400000
flags = 0
data = length 367, hash 8133883D
sample 11:
time = 440000
flags = 0
data = length 495, hash E14BDFEE
sample 12:
time = 480000
flags = 0
data = length 520, hash FEE56928
sample 13:
time = 519999
flags = 0
data = length 599, hash 41F496C5
sample 14:
time = 560000
flags = 0
data = length 436, hash 76D6404
sample 15:
time = 600000
flags = 0
data = length 366, hash 56D49D4D
sample 16:
time = 640000
flags = 0
data = length 393, hash 822FC8
sample 17:
time = 680000
flags = 0
data = length 374, hash FA8AE217
sample 18:
time = 720000
flags = 536870912
data = length 393, hash 8506A1B
tracksEnded = true
seekMap:
isSeekable = true
duration = 760000
getPosition(0) = [[timeUs=0, position=758]]
numberOfTracks = 1
track 0:
format:
bitrate = -1
id = 1
containerMimeType = null
sampleMimeType = audio/ac4
maxInputSize = 622
width = -1
height = -1
frameRate = -1.0
rotationDegrees = 0
pixelWidthHeightRatio = 1.0
channelCount = 2
sampleRate = 48000
pcmEncoding = -1
encoderDelay = 0
encoderPadding = 0
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = und
drmInitData = -
metadata = null
initializationData:
total output bytes = 7613
sample count = 19
sample 0:
time = 0
flags = 1
data = length 367, hash D2762FA
sample 1:
time = 40000
flags = 0
data = length 367, hash BDD3224A
sample 2:
time = 80000
flags = 0
data = length 367, hash 9302227B
sample 3:
time = 120000
flags = 0
data = length 367, hash 72996003
sample 4:
time = 160000
flags = 0
data = length 367, hash 88AE5A1B
sample 5:
time = 200000
flags = 0
data = length 367, hash E5346FE3
sample 6:
time = 240000
flags = 0
data = length 367, hash CE558362
sample 7:
time = 280000
flags = 0
data = length 367, hash 51AD3043
sample 8:
time = 320000
flags = 0
data = length 367, hash EB72E95B
sample 9:
time = 360000
flags = 0
data = length 367, hash 47F8FF23
sample 10:
time = 400000
flags = 0
data = length 367, hash 8133883D
sample 11:
time = 440000
flags = 0
data = length 495, hash E14BDFEE
sample 12:
time = 480000
flags = 0
data = length 520, hash FEE56928
sample 13:
time = 519999
flags = 0
data = length 599, hash 41F496C5
sample 14:
time = 560000
flags = 0
data = length 436, hash 76D6404
sample 15:
time = 600000
flags = 0
data = length 366, hash 56D49D4D
sample 16:
time = 640000
flags = 0
data = length 393, hash 822FC8
sample 17:
time = 680000
flags = 0
data = length 374, hash FA8AE217
sample 18:
time = 720000
flags = 536870912
data = length 393, hash 8506A1B
tracksEnded = true
seekMap:
isSeekable = true
duration = 760000
getPosition(0) = [[timeUs=0, position=758]]
numberOfTracks = 1
track 0:
format:
bitrate = -1
id = 1
containerMimeType = null
sampleMimeType = audio/ac4
maxInputSize = 622
width = -1
height = -1
frameRate = -1.0
rotationDegrees = 0
pixelWidthHeightRatio = 1.0
channelCount = 2
sampleRate = 48000
pcmEncoding = -1
encoderDelay = 0
encoderPadding = 0
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = und
drmInitData = -
metadata = null
initializationData:
total output bytes = 7613
sample count = 19
sample 0:
time = 0
flags = 1
data = length 367, hash D2762FA
sample 1:
time = 40000
flags = 0
data = length 367, hash BDD3224A
sample 2:
time = 80000
flags = 0
data = length 367, hash 9302227B
sample 3:
time = 120000
flags = 0
data = length 367, hash 72996003
sample 4:
time = 160000
flags = 0
data = length 367, hash 88AE5A1B
sample 5:
time = 200000
flags = 0
data = length 367, hash E5346FE3
sample 6:
time = 240000
flags = 0
data = length 367, hash CE558362
sample 7:
time = 280000
flags = 0
data = length 367, hash 51AD3043
sample 8:
time = 320000
flags = 0
data = length 367, hash EB72E95B
sample 9:
time = 360000
flags = 0
data = length 367, hash 47F8FF23
sample 10:
time = 400000
flags = 0
data = length 367, hash 8133883D
sample 11:
time = 440000
flags = 0
data = length 495, hash E14BDFEE
sample 12:
time = 480000
flags = 0
data = length 520, hash FEE56928
sample 13:
time = 519999
flags = 0
data = length 599, hash 41F496C5
sample 14:
time = 560000
flags = 0
data = length 436, hash 76D6404
sample 15:
time = 600000
flags = 0
data = length 366, hash 56D49D4D
sample 16:
time = 640000
flags = 0
data = length 393, hash 822FC8
sample 17:
time = 680000
flags = 0
data = length 374, hash FA8AE217
sample 18:
time = 720000
flags = 536870912
data = length 393, hash 8506A1B
tracksEnded = true
seekMap:
isSeekable = true
duration = 760000
getPosition(0) = [[timeUs=0, position=758]]
numberOfTracks = 1
track 0:
format:
bitrate = -1
id = 1
containerMimeType = null
sampleMimeType = audio/ac4
maxInputSize = 622
width = -1
height = -1
frameRate = -1.0
rotationDegrees = 0
pixelWidthHeightRatio = 1.0
channelCount = 2
sampleRate = 48000
pcmEncoding = -1
encoderDelay = 0
encoderPadding = 0
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = und
drmInitData = -
metadata = null
initializationData:
total output bytes = 7613
sample count = 19
sample 0:
time = 0
flags = 1
data = length 367, hash D2762FA
sample 1:
time = 40000
flags = 0
data = length 367, hash BDD3224A
sample 2:
time = 80000
flags = 0
data = length 367, hash 9302227B
sample 3:
time = 120000
flags = 0
data = length 367, hash 72996003
sample 4:
time = 160000
flags = 0
data = length 367, hash 88AE5A1B
sample 5:
time = 200000
flags = 0
data = length 367, hash E5346FE3
sample 6:
time = 240000
flags = 0
data = length 367, hash CE558362
sample 7:
time = 280000
flags = 0
data = length 367, hash 51AD3043
sample 8:
time = 320000
flags = 0
data = length 367, hash EB72E95B
sample 9:
time = 360000
flags = 0
data = length 367, hash 47F8FF23
sample 10:
time = 400000
flags = 0
data = length 367, hash 8133883D
sample 11:
time = 440000
flags = 0
data = length 495, hash E14BDFEE
sample 12:
time = 480000
flags = 0
data = length 520, hash FEE56928
sample 13:
time = 519999
flags = 0
data = length 599, hash 41F496C5
sample 14:
time = 560000
flags = 0
data = length 436, hash 76D6404
sample 15:
time = 600000
flags = 0
data = length 366, hash 56D49D4D
sample 16:
time = 640000
flags = 0
data = length 393, hash 822FC8
sample 17:
time = 680000
flags = 0
data = length 374, hash FA8AE217
sample 18:
time = 720000
flags = 536870912
data = length 393, hash 8506A1B
tracksEnded = true
seekMap:
isSeekable = true
duration = 760000
getPosition(0) = [[timeUs=0, position=685]]
numberOfTracks = 1
track 0:
format:
bitrate = -1
id = 1
containerMimeType = null
sampleMimeType = audio/ac4
maxInputSize = -1
width = -1
height = -1
frameRate = -1.0
rotationDegrees = 0
pixelWidthHeightRatio = 1.0
channelCount = 2
sampleRate = 48000
pcmEncoding = -1
encoderDelay = 0
encoderPadding = 0
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = und
drmInitData = -
metadata = null
initializationData:
total output bytes = 7613
sample count = 19
sample 0:
time = 0
flags = 1
data = length 367, hash D2762FA
sample 1:
time = 40000
flags = 1
data = length 367, hash BDD3224A
sample 2:
time = 80000
flags = 1
data = length 367, hash 9302227B
sample 3:
time = 120000
flags = 1
data = length 367, hash 72996003
sample 4:
time = 160000
flags = 1
data = length 367, hash 88AE5A1B
sample 5:
time = 200000
flags = 1
data = length 367, hash E5346FE3
sample 6:
time = 240000
flags = 1
data = length 367, hash CE558362
sample 7:
time = 280000
flags = 1
data = length 367, hash 51AD3043
sample 8:
time = 320000
flags = 1
data = length 367, hash EB72E95B
sample 9:
time = 360000
flags = 1
data = length 367, hash 47F8FF23
sample 10:
time = 400000
flags = 1
data = length 367, hash 8133883D
sample 11:
time = 440000
flags = 1
data = length 495, hash E14BDFEE
sample 12:
time = 480000
flags = 1
data = length 520, hash FEE56928
sample 13:
time = 520000
flags = 1
data = length 599, hash 41F496C5
sample 14:
time = 560000
flags = 1
data = length 436, hash 76D6404
sample 15:
time = 600000
flags = 1
data = length 366, hash 56D49D4D
sample 16:
time = 640000
flags = 1
data = length 393, hash 822FC8
sample 17:
time = 680000
flags = 1
data = length 374, hash FA8AE217
sample 18:
time = 720000
flags = 1
data = length 393, hash 8506A1B
tracksEnded = true
seekMap:
isSeekable = true
duration = 760000
getPosition(0) = [[timeUs=0, position=685]]
numberOfTracks = 1
track 0:
format:
bitrate = -1
id = 1
containerMimeType = null
sampleMimeType = audio/ac4
maxInputSize = -1
width = -1
height = -1
frameRate = -1.0
rotationDegrees = 0
pixelWidthHeightRatio = 1.0
channelCount = 2
sampleRate = 48000
pcmEncoding = -1
encoderDelay = 0
encoderPadding = 0
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = und
drmInitData = -
metadata = null
initializationData:
total output bytes = 5411
sample count = 13
sample 0:
time = 240000
flags = 1
data = length 367, hash CE558362
sample 1:
time = 280000
flags = 1
data = length 367, hash 51AD3043
sample 2:
time = 320000
flags = 1
data = length 367, hash EB72E95B
sample 3:
time = 360000
flags = 1
data = length 367, hash 47F8FF23
sample 4:
time = 400000
flags = 1
data = length 367, hash 8133883D
sample 5:
time = 440000
flags = 1
data = length 495, hash E14BDFEE
sample 6:
time = 480000
flags = 1
data = length 520, hash FEE56928
sample 7:
time = 520000
flags = 1
data = length 599, hash 41F496C5
sample 8:
time = 560000
flags = 1
data = length 436, hash 76D6404
sample 9:
time = 600000
flags = 1
data = length 366, hash 56D49D4D
sample 10:
time = 640000
flags = 1
data = length 393, hash 822FC8
sample 11:
time = 680000
flags = 1
data = length 374, hash FA8AE217
sample 12:
time = 720000
flags = 1
data = length 393, hash 8506A1B
tracksEnded = true
seekMap:
isSeekable = true
duration = 760000
getPosition(0) = [[timeUs=0, position=685]]
numberOfTracks = 1
track 0:
format:
bitrate = -1
id = 1
containerMimeType = null
sampleMimeType = audio/ac4
maxInputSize = -1
width = -1
height = -1
frameRate = -1.0
rotationDegrees = 0
pixelWidthHeightRatio = 1.0
channelCount = 2
sampleRate = 48000
pcmEncoding = -1
encoderDelay = 0
encoderPadding = 0
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = und
drmInitData = -
metadata = null
initializationData:
total output bytes = 3081
sample count = 7
sample 0:
time = 480000
flags = 1
data = length 520, hash FEE56928
sample 1:
time = 520000
flags = 1
data = length 599, hash 41F496C5
sample 2:
time = 560000
flags = 1
data = length 436, hash 76D6404
sample 3:
time = 600000
flags = 1
data = length 366, hash 56D49D4D
sample 4:
time = 640000
flags = 1
data = length 393, hash 822FC8
sample 5:
time = 680000
flags = 1
data = length 374, hash FA8AE217
sample 6:
time = 720000
flags = 1
data = length 393, hash 8506A1B
tracksEnded = true
seekMap:
isSeekable = true
duration = 760000
getPosition(0) = [[timeUs=0, position=685]]
numberOfTracks = 1
track 0:
format:
bitrate = -1
id = 1
containerMimeType = null
sampleMimeType = audio/ac4
maxInputSize = -1
width = -1
height = -1
frameRate = -1.0
rotationDegrees = 0
pixelWidthHeightRatio = 1.0
channelCount = 2
sampleRate = 48000
pcmEncoding = -1
encoderDelay = 0
encoderPadding = 0
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = und
drmInitData = -
metadata = null
initializationData:
total output bytes = 393
sample count = 1
sample 0:
time = 720000
flags = 1
data = length 393, hash 8506A1B
tracksEnded = true
seekMap:
isSeekable = true
duration = 1018185
getPosition(0) = [[timeUs=0, position=94]]
numberOfTracks = 1
track 0:
format:
bitrate = 177004
id = null
containerMimeType = null
sampleMimeType = audio/raw
maxInputSize = 8820
width = -1
height = -1
frameRate = -1.0
rotationDegrees = 0
pixelWidthHeightRatio = 1.0
channelCount = 1
sampleRate = 44100
pcmEncoding = 2
encoderDelay = 0
encoderPadding = 0
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = null
drmInitData = -
metadata = null
initializationData:
total output bytes = 89804
sample count = 11
sample 0:
time = 0
flags = 1
data = length 8820, hash E90A457C
sample 1:
time = 100000
flags = 1
data = length 8820, hash EA798370
sample 2:
time = 200000
flags = 1
data = length 8820, hash A57ED989
sample 3:
time = 300000
flags = 1
data = length 8820, hash 8B681816
sample 4:
time = 400000
flags = 1
data = length 8820, hash 48177BEB
sample 5:
time = 500000
flags = 1
data = length 8820, hash 70197776
sample 6:
time = 600000
flags = 1
data = length 8820, hash DB4A4704
sample 7:
time = 700000
flags = 1
data = length 8820, hash 84A525D0
sample 8:
time = 800000
flags = 1
data = length 8820, hash 197A4377
sample 9:
time = 900000
flags = 1
data = length 8820, hash 6982BC91
sample 10:
time = 1000000
flags = 1
data = length 1604, hash 3DED68ED
tracksEnded = true
seekMap:
isSeekable = true
duration = 1018185
getPosition(0) = [[timeUs=0, position=94]]
numberOfTracks = 1
track 0:
format:
bitrate = 177004
id = null
containerMimeType = null
sampleMimeType = audio/raw
maxInputSize = 8820
width = -1
height = -1
frameRate = -1.0
rotationDegrees = 0
pixelWidthHeightRatio = 1.0
channelCount = 1
sampleRate = 44100
pcmEncoding = 2
encoderDelay = 0
encoderPadding = 0
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = null
drmInitData = -
metadata = null
initializationData:
total output bytes = 61230
sample count = 7
sample 0:
time = 339395
flags = 1
data = length 8820, hash 25FCA092
sample 1:
time = 439395
flags = 1
data = length 8820, hash 9400B4BE
sample 2:
time = 539395
flags = 1
data = length 8820, hash 5BA7E45D
sample 3:
time = 639395
flags = 1
data = length 8820, hash 5AC42905
sample 4:
time = 739395
flags = 1
data = length 8820, hash D57059C
sample 5:
time = 839395
flags = 1
data = length 8820, hash DEF5C480
sample 6:
time = 939395
flags = 1
data = length 8310, hash 10B3FC93
tracksEnded = true
seekMap:
isSeekable = true
duration = 1018185
getPosition(0) = [[timeUs=0, position=94]]
numberOfTracks = 1
track 0:
format:
bitrate = 177004
id = null
containerMimeType = null
sampleMimeType = audio/raw
maxInputSize = 8820
width = -1
height = -1
frameRate = -1.0
rotationDegrees = 0
pixelWidthHeightRatio = 1.0
channelCount = 1
sampleRate = 44100
pcmEncoding = 2
encoderDelay = 0
encoderPadding = 0
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = null
drmInitData = -
metadata = null
initializationData:
total output bytes = 32656
sample count = 4
sample 0:
time = 678790
flags = 1
data = length 8820, hash DB7FF64C
sample 1:
time = 778790
flags = 1
data = length 8820, hash B895DFDC
sample 2:
time = 878790
flags = 1
data = length 8820, hash E3AB416D
sample 3:
time = 978790
flags = 1
data = length 6196, hash E27E175A
tracksEnded = true
seekMap:
isSeekable = true
duration = 1018185
getPosition(0) = [[timeUs=0, position=94]]
numberOfTracks = 1
track 0:
format:
bitrate = 177004
id = null
containerMimeType = null
sampleMimeType = audio/raw
maxInputSize = 8820
width = -1
height = -1
frameRate = -1.0
rotationDegrees = 0
pixelWidthHeightRatio = 1.0
channelCount = 1
sampleRate = 44100
pcmEncoding = 2
encoderDelay = 0
encoderPadding = 0
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = null
drmInitData = -
metadata = null
initializationData:
total output bytes = 4082
sample count = 1
sample 0:
time = 1018185
flags = 1
data = length 4082, hash 4CB1A490
tracksEnded = true
WEBVTT WEBVTT
STYLE STYLE
::cue(\n#id ){text-decoration:underline;} ::cue(#id ){text-decoration:underline;}
STYLE STYLE
::cue(#id.class1.class2 ){ color: violet;} ::cue(#id.class1.class2 ){ color: violet;}
...@@ -20,7 +20,7 @@ STYLE ...@@ -20,7 +20,7 @@ STYLE
id id
00:00.000 --> 00:01.001 00:00.000 --> 00:01.001
This should be underlined and <lang.class1.class2> courier and violet. This should be underlined and <lang.class1.class2>courier and violet.
íd íd
00:02.000 --> 00:02.001 00:02.000 --> 00:02.001
...@@ -31,10 +31,10 @@ _id ...@@ -31,10 +31,10 @@ _id
This <lang.class.another>should be courier and bold. This <lang.class.another>should be courier and bold.
00:04.000 --> 00:04.001 00:04.000 --> 00:04.001
This <v Strider Trancos> shouldn't be bold.</v> This <v Strider Trancos>shouldn't be bold.</v>
This <v.class.clazz Strider Trancos> should be bold. This <v.class.clazz Strider Trancos>should be bold.
anId anId
00:05.000 --> 00:05.001 00:05.000 --> 00:05.001
This is <v.class1.class3.class2 Pipo> specific </v> This is <v.class1.class3.class2 Pipo>specific</v>
<v.class1.class3.class2 Robert> But this is more italic</v> <v.class1.class3.class2 Robert>But this is more italic</v>
WEBVTT
NOTE https://developer.mozilla.org/en-US/docs/Web/CSS/text-combine-upright
NOTE The `digits` values are ignored in CssParser and all assumed to be `all`
STYLE
::cue(.tcu-all) {
text-combine-upright: all;
}
::cue(.tcu-digits) {
text-combine-upright: digits 4;
}
00:00:00.000 --> 00:00:01.000 vertical:rl
Combine <c.tcu-all>all</c> test
00:03.000 --> 00:04.000 vertical:rl
Combine <c.tcu-digits>0004</c> digits
...@@ -8,12 +8,12 @@ This is the first subtitle. ...@@ -8,12 +8,12 @@ This is the first subtitle.
NOTE Wrong position provided. It should be provided as NOTE Wrong position provided. It should be provided as
a percentage value a percentage value
00:02.345 --> 00:03.456 position:10 align:end size:35% 00:02.345 --> 00:03.456 position:10 align:end
This is the second subtitle. This is the second subtitle.
NOTE Line as percentage and line alignment NOTE Line as percentage and line alignment
00:04.000 --> 00:05.000 line:45%,end align:middle size:35% 00:04.000 --> 00:05.000 line:45%,end align:middle
This is the third subtitle. This is the third subtitle.
NOTE Line as absolute negative number and without line alignment. NOTE Line as absolute negative number and without line alignment.
...@@ -23,10 +23,10 @@ This is the fourth subtitle. ...@@ -23,10 +23,10 @@ This is the fourth subtitle.
NOTE The position and positioning alignment should be inherited from align. NOTE The position and positioning alignment should be inherited from align.
00:07.000 --> 00:08.000 align:right 00:08.000 --> 00:09.000 align:right
This is the fifth subtitle. This is the fifth subtitle.
NOTE In newer drafts, align:middle has been replaced by align:center NOTE In newer drafts, align:middle has been replaced by align:center
00:10.000 --> 00:11.000 line:45%,end align:center size:35% 00:10.000 --> 00:11.000 align:center
This is the sixth subtitle. This is the sixth subtitle.
...@@ -15,13 +15,8 @@ ...@@ -15,13 +15,8 @@
*/ */
package com.google.android.exoplayer2.extractor.flac; package com.google.android.exoplayer2.extractor.flac;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.testutil.ExtractorAsserts; import com.google.android.exoplayer2.testutil.ExtractorAsserts;
import com.google.android.exoplayer2.testutil.TestUtil;
import java.io.IOException;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
...@@ -66,9 +61,7 @@ public class FlacExtractorTest { ...@@ -66,9 +61,7 @@ public class FlacExtractorTest {
@Test @Test
public void testOneMetadataBlock() throws Exception { public void testOneMetadataBlock() throws Exception {
// Don't simulate IO errors as it is too slow when using the binary search seek map (see ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear_one_metadata_block.flac");
// [Internal: b/145994869]).
assertBehaviorWithoutSimulatingIOErrors("flac/bear_one_metadata_block.flac");
} }
@Test @Test
...@@ -85,61 +78,4 @@ public class FlacExtractorTest { ...@@ -85,61 +78,4 @@ public class FlacExtractorTest {
public void testUncommonSampleRate() throws Exception { public void testUncommonSampleRate() throws Exception {
ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear_uncommon_sample_rate.flac"); ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear_uncommon_sample_rate.flac");
} }
private static void assertBehaviorWithoutSimulatingIOErrors(String file)
throws IOException, InterruptedException {
// Check behavior prior to initialization.
Extractor extractor = new FlacExtractor();
extractor.seek(0, 0);
extractor.release();
// Assert output.
Context context = ApplicationProvider.getApplicationContext();
byte[] data = TestUtil.getByteArray(context, file);
ExtractorAsserts.assertOutput(
new FlacExtractor(),
file,
data,
context,
/* sniffFirst= */ true,
/* simulateIOErrors= */ false,
/* simulateUnknownLength= */ false,
/* simulatePartialReads= */ false);
ExtractorAsserts.assertOutput(
new FlacExtractor(),
file,
data,
context,
/* sniffFirst= */ true,
/* simulateIOErrors= */ false,
/* simulateUnknownLength= */ false,
/* simulatePartialReads= */ true);
ExtractorAsserts.assertOutput(
new FlacExtractor(),
file,
data,
context,
/* sniffFirst= */ true,
/* simulateIOErrors= */ false,
/* simulateUnknownLength= */ true,
/* simulatePartialReads= */ false);
ExtractorAsserts.assertOutput(
new FlacExtractor(),
file,
data,
context,
/* sniffFirst= */ true,
/* simulateIOErrors= */ false,
/* simulateUnknownLength= */ true,
/* simulatePartialReads= */ true);
ExtractorAsserts.assertOutput(
new FlacExtractor(),
file,
data,
context,
/* sniffFirst= */ false,
/* simulateIOErrors= */ false,
/* simulateUnknownLength= */ false,
/* simulatePartialReads= */ false);
}
} }
...@@ -51,6 +51,12 @@ public final class FragmentedMp4ExtractorTest { ...@@ -51,6 +51,12 @@ public final class FragmentedMp4ExtractorTest {
ExtractorAsserts.assertBehavior(extractorFactory, "mp4/sample_fragmented_sei.mp4"); ExtractorAsserts.assertBehavior(extractorFactory, "mp4/sample_fragmented_sei.mp4");
} }
@Test
public void testSampleWithAc4Track() throws Exception {
ExtractorAsserts.assertBehavior(
getExtractorFactory(Collections.emptyList()), "mp4/sample_ac4_fragmented.mp4");
}
private static ExtractorFactory getExtractorFactory(final List<Format> closedCaptionFormats) { private static ExtractorFactory getExtractorFactory(final List<Format> closedCaptionFormats) {
return () -> new FragmentedMp4Extractor(0, null, null, null, closedCaptionFormats); return () -> new FragmentedMp4Extractor(0, null, null, null, closedCaptionFormats);
} }
......
...@@ -42,4 +42,9 @@ public final class Mp4ExtractorTest { ...@@ -42,4 +42,9 @@ public final class Mp4ExtractorTest {
public void testMp4SampleWithMdatTooLong() throws Exception { public void testMp4SampleWithMdatTooLong() throws Exception {
ExtractorAsserts.assertBehavior(Mp4Extractor::new, "mp4/sample_mdat_too_long.mp4"); ExtractorAsserts.assertBehavior(Mp4Extractor::new, "mp4/sample_mdat_too_long.mp4");
} }
@Test
public void testMp4SampleWithAc4Track() throws Exception {
ExtractorAsserts.assertBehavior(Mp4Extractor::new, "mp4/sample_ac4.mp4");
}
} }
...@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.extractor.ts; ...@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.extractor.ts;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import android.util.SparseArray; import android.util.SparseArray;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
...@@ -172,6 +173,7 @@ public final class TsExtractorTest { ...@@ -172,6 +173,7 @@ public final class TsExtractorTest {
} }
} }
@Nullable
@Override @Override
public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) { public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) {
if (provideCustomEsReader && streamType == 3) { if (provideCustomEsReader && streamType == 3) {
......
...@@ -28,4 +28,9 @@ public final class WavExtractorTest { ...@@ -28,4 +28,9 @@ public final class WavExtractorTest {
public void testSample() throws Exception { public void testSample() throws Exception {
ExtractorAsserts.assertBehavior(WavExtractor::new, "wav/sample.wav"); ExtractorAsserts.assertBehavior(WavExtractor::new, "wav/sample.wav");
} }
@Test
public void testSampleImaAdpcm() throws Exception {
ExtractorAsserts.assertBehavior(WavExtractor::new, "wav/sample_ima_adpcm.wav");
}
} }
...@@ -19,7 +19,7 @@ package com.google.android.exoplayer2.mediacodec; ...@@ -19,7 +19,7 @@ package com.google.android.exoplayer2.mediacodec;
import static com.google.android.exoplayer2.mediacodec.MediaCodecTestUtils.areEqual; import static com.google.android.exoplayer2.mediacodec.MediaCodecTestUtils.areEqual;
import static com.google.android.exoplayer2.mediacodec.MediaCodecTestUtils.waitUntilAllEventsAreExecuted; import static com.google.android.exoplayer2.mediacodec.MediaCodecTestUtils.waitUntilAllEventsAreExecuted;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail; import static org.junit.Assert.assertThrows;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaFormat; import android.media.MediaFormat;
...@@ -29,7 +29,7 @@ import android.os.Looper; ...@@ -29,7 +29,7 @@ import android.os.Looper;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
...@@ -45,27 +45,32 @@ public class AsynchronousMediaCodecAdapterTest { ...@@ -45,27 +45,32 @@ public class AsynchronousMediaCodecAdapterTest {
private MediaCodec.BufferInfo bufferInfo; private MediaCodec.BufferInfo bufferInfo;
@Before @Before
public void setup() throws IOException { public void setUp() throws IOException {
handlerThread = new HandlerThread("TestHandlerThread"); handlerThread = new HandlerThread("TestHandlerThread");
handlerThread.start(); handlerThread.start();
looper = handlerThread.getLooper(); looper = handlerThread.getLooper();
codec = MediaCodec.createByCodecName("h264"); codec = MediaCodec.createByCodecName("h264");
adapter = new AsynchronousMediaCodecAdapter(codec, looper); adapter = new AsynchronousMediaCodecAdapter(codec, looper);
adapter.setCodecStartRunnable(() -> {});
bufferInfo = new MediaCodec.BufferInfo(); bufferInfo = new MediaCodec.BufferInfo();
} }
@After @After
public void tearDown() { public void tearDown() {
adapter.shutdown();
handlerThread.quit(); handlerThread.quit();
} }
@Test @Test
public void dequeueInputBufferIndex_withoutInputBuffer_returnsTryAgainLater() { public void dequeueInputBufferIndex_withoutInputBuffer_returnsTryAgainLater() {
adapter.start();
assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER); assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
} }
@Test @Test
public void dequeueInputBufferIndex_withInputBuffer_returnsInputBuffer() { public void dequeueInputBufferIndex_withInputBuffer_returnsInputBuffer() {
adapter.start();
adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 0); adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 0);
assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(0); assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(0);
...@@ -73,6 +78,7 @@ public class AsynchronousMediaCodecAdapterTest { ...@@ -73,6 +78,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void dequeueInputBufferIndex_whileFlushing_returnsTryAgainLater() { public void dequeueInputBufferIndex_whileFlushing_returnsTryAgainLater() {
adapter.start();
adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 0); adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 0);
adapter.flush(); adapter.flush();
adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 1); adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 1);
...@@ -83,9 +89,7 @@ public class AsynchronousMediaCodecAdapterTest { ...@@ -83,9 +89,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void dequeueInputBufferIndex_afterFlushCompletes_returnsNextInputBuffer() public void dequeueInputBufferIndex_afterFlushCompletes_returnsNextInputBuffer()
throws InterruptedException { throws InterruptedException {
// Disable calling codec.start() after flush() completes to avoid receiving buffers from the adapter.start();
// shadow codec impl
adapter.setOnCodecStart(() -> {});
Handler handler = new Handler(looper); Handler handler = new Handler(looper);
handler.post( handler.post(
() -> adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 0)); () -> adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 0));
...@@ -100,28 +104,35 @@ public class AsynchronousMediaCodecAdapterTest { ...@@ -100,28 +104,35 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void dequeueInputBufferIndex_afterFlushCompletesWithError_throwsException() public void dequeueInputBufferIndex_afterFlushCompletesWithError_throwsException()
throws InterruptedException { throws InterruptedException {
adapter.setOnCodecStart( AtomicInteger calls = new AtomicInteger(0);
adapter.setCodecStartRunnable(
() -> { () -> {
throw new IllegalStateException("codec#start() exception"); if (calls.incrementAndGet() == 2) {
throw new IllegalStateException();
}
}); });
adapter.start();
adapter.flush(); adapter.flush();
assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue(); assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue();
try { assertThrows(
adapter.dequeueInputBufferIndex(); IllegalStateException.class,
fail(); () -> {
} catch (IllegalStateException expected) { adapter.dequeueInputBufferIndex();
} });
} }
@Test @Test
public void dequeueOutputBufferIndex_withoutOutputBuffer_returnsTryAgainLater() { public void dequeueOutputBufferIndex_withoutOutputBuffer_returnsTryAgainLater() {
adapter.start();
assertThat(adapter.dequeueOutputBufferIndex(bufferInfo)) assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
.isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER); .isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
} }
@Test @Test
public void dequeueOutputBufferIndex_withOutputBuffer_returnsOutputBuffer() { public void dequeueOutputBufferIndex_withOutputBuffer_returnsOutputBuffer() {
adapter.start();
MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo(); MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo();
outBufferInfo.presentationTimeUs = 10; outBufferInfo.presentationTimeUs = 10;
adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 0, outBufferInfo); adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 0, outBufferInfo);
...@@ -132,6 +143,7 @@ public class AsynchronousMediaCodecAdapterTest { ...@@ -132,6 +143,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void dequeueOutputBufferIndex_whileFlushing_returnsTryAgainLater() { public void dequeueOutputBufferIndex_whileFlushing_returnsTryAgainLater() {
adapter.start();
adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 0, bufferInfo); adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 0, bufferInfo);
adapter.flush(); adapter.flush();
adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 1, bufferInfo); adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 1, bufferInfo);
...@@ -143,9 +155,7 @@ public class AsynchronousMediaCodecAdapterTest { ...@@ -143,9 +155,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void dequeueOutputBufferIndex_afterFlushCompletes_returnsNextOutputBuffer() public void dequeueOutputBufferIndex_afterFlushCompletes_returnsNextOutputBuffer()
throws InterruptedException { throws InterruptedException {
// Disable calling codec.start() after flush() completes to avoid receiving buffers from the adapter.start();
// shadow codec impl
adapter.setOnCodecStart(() -> {});
Handler handler = new Handler(looper); Handler handler = new Handler(looper);
MediaCodec.BufferInfo info0 = new MediaCodec.BufferInfo(); MediaCodec.BufferInfo info0 = new MediaCodec.BufferInfo();
handler.post( handler.post(
...@@ -164,31 +174,23 @@ public class AsynchronousMediaCodecAdapterTest { ...@@ -164,31 +174,23 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void dequeueOutputBufferIndex_afterFlushCompletesWithError_throwsException() public void dequeueOutputBufferIndex_afterFlushCompletesWithError_throwsException()
throws InterruptedException { throws InterruptedException {
adapter.setOnCodecStart( AtomicInteger calls = new AtomicInteger(0);
adapter.setCodecStartRunnable(
() -> { () -> {
throw new RuntimeException("codec#start() exception"); if (calls.incrementAndGet() == 2) {
throw new RuntimeException("codec#start() exception");
}
}); });
adapter.start();
adapter.flush(); adapter.flush();
assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue(); assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue();
try { assertThrows(IllegalStateException.class, () -> adapter.dequeueOutputBufferIndex(bufferInfo));
adapter.dequeueOutputBufferIndex(bufferInfo);
fail();
} catch (IllegalStateException expected) {
}
}
@Test
public void getOutputFormat_withoutFormat_throwsException() {
try {
adapter.getOutputFormat();
fail();
} catch (IllegalStateException expected) {
}
} }
@Test @Test
public void getOutputFormat_withMultipleFormats_returnsFormatsInCorrectOrder() { public void getOutputFormat_withMultipleFormats_returnsFormatsInCorrectOrder() {
adapter.start();
MediaFormat[] formats = new MediaFormat[10]; MediaFormat[] formats = new MediaFormat[10];
MediaCodec.Callback mediaCodecCallback = adapter.getMediaCodecCallback(); MediaCodec.Callback mediaCodecCallback = adapter.getMediaCodecCallback();
for (int i = 0; i < formats.length; i++) { for (int i = 0; i < formats.length; i++) {
...@@ -212,6 +214,7 @@ public class AsynchronousMediaCodecAdapterTest { ...@@ -212,6 +214,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void getOutputFormat_afterFlush_returnsPreviousFormat() throws InterruptedException { public void getOutputFormat_afterFlush_returnsPreviousFormat() throws InterruptedException {
adapter.start();
MediaFormat format = new MediaFormat(); MediaFormat format = new MediaFormat();
adapter.getMediaCodecCallback().onOutputFormatChanged(codec, format); adapter.getMediaCodecCallback().onOutputFormatChanged(codec, format);
adapter.dequeueOutputBufferIndex(bufferInfo); adapter.dequeueOutputBufferIndex(bufferInfo);
...@@ -223,13 +226,13 @@ public class AsynchronousMediaCodecAdapterTest { ...@@ -223,13 +226,13 @@ public class AsynchronousMediaCodecAdapterTest {
@Test @Test
public void shutdown_withPendingFlush_cancelsFlush() throws InterruptedException { public void shutdown_withPendingFlush_cancelsFlush() throws InterruptedException {
AtomicBoolean onCodecStartCalled = new AtomicBoolean(false); AtomicInteger onCodecStartCalled = new AtomicInteger(0);
Runnable onCodecStart = () -> onCodecStartCalled.set(true); adapter.setCodecStartRunnable(() -> onCodecStartCalled.incrementAndGet());
adapter.setOnCodecStart(onCodecStart); adapter.start();
adapter.flush(); adapter.flush();
adapter.shutdown(); adapter.shutdown();
assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue(); assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue();
assertThat(onCodecStartCalled.get()).isFalse(); assertThat(onCodecStartCalled.get()).isEqualTo(1);
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment