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 @@
([#6733](https://github.com/google/ExoPlayer/issues/6733)). Incorrect handling
could previously cause downloads to be paused when they should have been able
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) ###
......
......@@ -98,8 +98,7 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer {
Assertions.checkNotNull(format.sampleMimeType);
if (!FfmpegLibrary.isAvailable()) {
return FORMAT_UNSUPPORTED_TYPE;
} else if (!FfmpegLibrary.supportsFormat(format.sampleMimeType, format.pcmEncoding)
|| !isOutputSupported(format)) {
} else if (!FfmpegLibrary.supportsFormat(format.sampleMimeType) || !isOutputSupported(format)) {
return FORMAT_UNSUPPORTED_SUBTYPE;
} else if (!supportsFormatDrm(drmSessionManager, format.drmInitData)) {
return FORMAT_UNSUPPORTED_DRM;
......
......@@ -64,9 +64,7 @@ import java.util.List;
throw new FfmpegDecoderException("Failed to load decoder native libraries.");
}
Assertions.checkNotNull(format.sampleMimeType);
codecName =
Assertions.checkNotNull(
FfmpegLibrary.getCodecName(format.sampleMimeType, format.pcmEncoding));
codecName = Assertions.checkNotNull(FfmpegLibrary.getCodecName(format.sampleMimeType));
extraData = getExtraData(format.sampleMimeType, format.initializationData);
encoding = outputFloat ? C.ENCODING_PCM_FLOAT : C.ENCODING_PCM_16BIT;
outputBufferSize = outputFloat ? OUTPUT_BUFFER_SIZE_32BIT : OUTPUT_BUFFER_SIZE_16BIT;
......@@ -145,16 +143,12 @@ import java.util.List;
nativeContext = 0;
}
/**
* Returns the channel count of output audio. May only be called after {@link #decode}.
*/
/** Returns the channel count of output audio. */
public int getChannelCount() {
return channelCount;
}
/**
* Returns the sample rate of output audio. May only be called after {@link #decode}.
*/
/** Returns the sample rate of output audio. */
public int getSampleRate() {
return sampleRate;
}
......
......@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.ext.ffmpeg;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.util.LibraryLoader;
import com.google.android.exoplayer2.util.Log;
......@@ -65,13 +64,12 @@ public final class FfmpegLibrary {
* Returns whether the underlying library supports the specified MIME type.
*
* @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()) {
return false;
}
String codecName = getCodecName(mimeType, encoding);
String codecName = getCodecName(mimeType);
if (codecName == null) {
return false;
}
......@@ -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}
* if it's unsupported.
*/
/* package */ static @Nullable String getCodecName(String mimeType, @C.PcmEncoding int encoding) {
/* package */ static @Nullable String getCodecName(String mimeType) {
switch (mimeType) {
case MimeTypes.AUDIO_AAC:
return "aac";
......@@ -116,14 +114,10 @@ public final class FfmpegLibrary {
return "flac";
case MimeTypes.AUDIO_ALAC:
return "alac";
case MimeTypes.AUDIO_RAW:
if (encoding == C.ENCODING_PCM_MU_LAW) {
return "pcm_mulaw";
} else if (encoding == C.ENCODING_PCM_A_LAW) {
return "pcm_alaw";
} else {
return null;
}
case MimeTypes.AUDIO_MLAW:
return "pcm_mulaw";
case MimeTypes.AUDIO_ALAW:
return "pcm_alaw";
default:
return null;
}
......
......@@ -126,6 +126,8 @@ import java.nio.ByteBuffer;
if (targetSampleInLastFrame) {
// We are holding the target frame in outputFrameHolder. Set its presentation time now.
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());
} else if (nextFrameSampleIndex <= targetSampleIndex) {
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 {
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
// 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
// Since OkHttp is distributed as a jar rather than an aar, Gradle wont stop
// us from making this mistake!
api 'com.squareup.okhttp3:okhttp:3.12.5'
// Since OkHttp is distributed as a jar rather than an aar, Gradle won't
// stop us from making this mistake!
api 'com.squareup.okhttp3:okhttp:3.12.7'
}
ext {
......
......@@ -5,6 +5,12 @@
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.
-keep class com.google.android.exoplayer2.video.VideoDecoderOutputBuffer {
*;
......
......@@ -150,10 +150,10 @@ public final class C {
/**
* 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
* #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT}, {@link #ENCODING_PCM_FLOAT}, {@link
* #ENCODING_PCM_MU_LAW}, {@link #ENCODING_PCM_A_LAW}, {@link #ENCODING_MP3}, {@link
* #ENCODING_AC3}, {@link #ENCODING_E_AC3}, {@link #ENCODING_E_AC3_JOC}, {@link #ENCODING_AC4},
* {@link #ENCODING_DTS}, {@link #ENCODING_DTS_HD} or {@link #ENCODING_DOLBY_TRUEHD}.
* #ENCODING_PCM_16BIT_BIG_ENDIAN}, {@link #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT},
* {@link #ENCODING_PCM_FLOAT}, {@link #ENCODING_MP3}, {@link #ENCODING_AC3}, {@link
* #ENCODING_E_AC3}, {@link #ENCODING_E_AC3_JOC}, {@link #ENCODING_AC4}, {@link #ENCODING_DTS},
* {@link #ENCODING_DTS_HD} or {@link #ENCODING_DOLBY_TRUEHD}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
......@@ -162,11 +162,10 @@ public final class C {
ENCODING_INVALID,
ENCODING_PCM_8BIT,
ENCODING_PCM_16BIT,
ENCODING_PCM_16BIT_BIG_ENDIAN,
ENCODING_PCM_24BIT,
ENCODING_PCM_32BIT,
ENCODING_PCM_FLOAT,
ENCODING_PCM_MU_LAW,
ENCODING_PCM_A_LAW,
ENCODING_MP3,
ENCODING_AC3,
ENCODING_E_AC3,
......@@ -174,15 +173,15 @@ public final class C {
ENCODING_AC4,
ENCODING_DTS,
ENCODING_DTS_HD,
ENCODING_DOLBY_TRUEHD,
ENCODING_DOLBY_TRUEHD
})
public @interface Encoding {}
/**
* 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
* #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT}, {@link #ENCODING_PCM_FLOAT}, {@link
* #ENCODING_PCM_MU_LAW} or {@link #ENCODING_PCM_A_LAW}.
* #ENCODING_PCM_16BIT_BIG_ENDIAN}, {@link #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT},
* {@link #ENCODING_PCM_FLOAT}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
......@@ -191,11 +190,10 @@ public final class C {
ENCODING_INVALID,
ENCODING_PCM_8BIT,
ENCODING_PCM_16BIT,
ENCODING_PCM_16BIT_BIG_ENDIAN,
ENCODING_PCM_24BIT,
ENCODING_PCM_32BIT,
ENCODING_PCM_FLOAT,
ENCODING_PCM_MU_LAW,
ENCODING_PCM_A_LAW
ENCODING_PCM_FLOAT
})
public @interface PcmEncoding {}
/** @see AudioFormat#ENCODING_INVALID */
......@@ -204,16 +202,14 @@ public final class C {
public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT;
/** @see 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. */
public static final int ENCODING_PCM_24BIT = 0x80000000;
public static final int ENCODING_PCM_24BIT = 0x20000000;
/** 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 */
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 */
public static final int ENCODING_MP3 = AudioFormat.ENCODING_MP3;
/** @see AudioFormat#ENCODING_AC3 */
......@@ -981,8 +977,8 @@ public final class C {
/**
* 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
* #NETWORK_TYPE_4G}, {@link #NETWORK_TYPE_CELLULAR_UNKNOWN}, {@link #NETWORK_TYPE_ETHERNET} or
* {@link #NETWORK_TYPE_OTHER}.
* #NETWORK_TYPE_4G}, {@link #NETWORK_TYPE_5G}, {@link #NETWORK_TYPE_CELLULAR_UNKNOWN}, {@link
* #NETWORK_TYPE_ETHERNET} or {@link #NETWORK_TYPE_OTHER}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
......@@ -993,6 +989,7 @@ public final class C {
NETWORK_TYPE_2G,
NETWORK_TYPE_3G,
NETWORK_TYPE_4G,
NETWORK_TYPE_5G,
NETWORK_TYPE_CELLULAR_UNKNOWN,
NETWORK_TYPE_ETHERNET,
NETWORK_TYPE_OTHER
......@@ -1010,6 +1007,8 @@ public final class C {
public static final int NETWORK_TYPE_3G = 4;
/** Network type for a 4G cellular connection. */
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_2G}, {@link #NETWORK_TYPE_3G}, or {@link #NETWORK_TYPE_4G}.
......@@ -1017,10 +1016,7 @@ public final class C {
public static final int NETWORK_TYPE_CELLULAR_UNKNOWN = 6;
/** Network type for an Ethernet connection. */
public static final int NETWORK_TYPE_ETHERNET = 7;
/**
* Network type for other connections which are not Wifi or cellular (e.g. Ethernet, VPN,
* Bluetooth).
*/
/** Network type for other connections which are not Wifi or cellular (e.g. VPN, Bluetooth). */
public static final int NETWORK_TYPE_OTHER = 8;
/**
......
......@@ -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.
*/
public final int sampleRate;
/**
* 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.
*/
/** The {@link C.PcmEncoding} for PCM audio. Set to {@link #NO_VALUE} for other media types. */
public final @C.PcmEncoding int pcmEncoding;
/**
* 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
@Player.State private int playbackState;
private boolean isSuppressed;
private float playbackSpeed;
private boolean isSeeking;
/**
* Creates listener for playback stats.
......@@ -169,6 +170,9 @@ public final class PlaybackStatsListener
@Override
public void onSessionCreated(EventTime eventTime, String session) {
PlaybackStatsTracker tracker = new PlaybackStatsTracker(keepHistory, eventTime);
if (isSeeking) {
tracker.onSeekStarted(eventTime, /* belongsToPlayback= */ true);
}
tracker.onPlayerStateChanged(
eventTime, playWhenReady, playbackState, /* belongsToPlayback= */ true);
tracker.onIsSuppressedChanged(eventTime, isSuppressed, /* belongsToPlayback= */ true);
......@@ -288,20 +292,20 @@ public final class PlaybackStatsListener
public void onSeekStarted(EventTime eventTime) {
sessionManager.updateSessions(eventTime);
for (String session : playbackStatsTrackers.keySet()) {
if (sessionManager.belongsToSession(eventTime, session)) {
playbackStatsTrackers.get(session).onSeekStarted(eventTime);
}
boolean belongsToPlayback = sessionManager.belongsToSession(eventTime, session);
playbackStatsTrackers.get(session).onSeekStarted(eventTime, belongsToPlayback);
}
isSeeking = true;
}
@Override
public void onSeekProcessed(EventTime eventTime) {
sessionManager.updateSessions(eventTime);
for (String session : playbackStatsTrackers.keySet()) {
if (sessionManager.belongsToSession(eventTime, session)) {
playbackStatsTrackers.get(session).onSeekProcessed(eventTime);
}
boolean belongsToPlayback = sessionManager.belongsToSession(eventTime, session);
playbackStatsTrackers.get(session).onSeekProcessed(eventTime, belongsToPlayback);
}
isSeeking = false;
}
@Override
......@@ -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 belongsToPlayback Whether the {@code eventTime} belongs to the current playback.
*/
public void onSeekStarted(EventTime eventTime) {
public void onSeekStarted(EventTime eventTime, boolean belongsToPlayback) {
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 belongsToPlayback Whether the {@code eventTime} belongs to the current playback.
*/
public void onSeekProcessed(EventTime eventTime) {
public void onSeekProcessed(EventTime eventTime, boolean belongsToPlayback) {
isSeeking = false;
maybeUpdatePlaybackState(eventTime, /* belongsToPlayback= */ true);
maybeUpdatePlaybackState(eventTime, belongsToPlayback);
}
/**
......@@ -875,7 +883,7 @@ public final class PlaybackStatsListener
return currentPlaybackState == PlaybackStats.PLAYBACK_STATE_ENDED
? PlaybackStats.PLAYBACK_STATE_ENDED
: 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.
return PlaybackStats.PLAYBACK_STATE_SEEKING;
} else if (hasFatalError) {
......
......@@ -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
* definition in ETSI TS 102 366 V1.2.1.
* definition in ETSI TS 102 366 V1.4.1.
*/
public final class Ac3Util {
......@@ -39,8 +39,8 @@ public final class Ac3Util {
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},
* {@link #STREAM_TYPE_TYPE0}, {@link #STREAM_TYPE_TYPE1} or {@link #STREAM_TYPE_TYPE2}.
* AC3 stream types. See also E.1.3.1.1. One of {@link #STREAM_TYPE_UNDEFINED}, {@link
* #STREAM_TYPE_TYPE0}, {@link #STREAM_TYPE_TYPE1} or {@link #STREAM_TYPE_TYPE2}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
......@@ -114,9 +114,7 @@ public final class Ac3Util {
* The number of new samples per (E-)AC-3 audio block.
*/
private static final int AUDIO_SAMPLES_PER_AUDIO_BLOCK = 256;
/**
* Each syncframe has 6 blocks that provide 256 new audio samples. See ETSI TS 102 366 4.1.
*/
/** Each syncframe has 6 blocks that provide 256 new audio samples. See subsection 4.1. */
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.
......@@ -134,20 +132,21 @@ public final class Ac3Util {
* Channel counts, indexed by acmod.
*/
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 ETSI TS 102 366 table 4.13.)
*/
private static final int[] BITRATE_BY_HALF_FRMSIZECOD = new int[] {32, 40, 48, 56, 64, 80, 96,
112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 576, 640};
/**
* 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[] {69, 87, 104,
121, 139, 174, 208, 243, 278, 348, 417, 487, 557, 696, 835, 975, 1114, 1253, 1393};
/** Nominal bitrates in kbps, indexed by frmsizecod / 2. (See table 4.13.) */
private static final int[] BITRATE_BY_HALF_FRMSIZECOD =
new int[] {
32, 40, 48, 56, 64, 80, 96, 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.) */
private static final int[] SYNCFRAME_SIZE_WORDS_BY_HALF_FRMSIZECOD_44_1 =
new int[] {
69, 87, 104, 121, 139, 174, 208, 243, 278, 348, 417, 487, 557, 696, 835, 975, 1114, 1253,
1393
};
/**
* Returns the AC-3 format given {@code data} containing the AC3SpecificBox according to ETSI TS
* 102 366 Annex F. The reading position of {@code data} will be modified.
* Returns the AC-3 format given {@code data} containing the AC3SpecificBox according to Annex F.
* The reading position of {@code data} will be modified.
*
* @param data The AC3SpecificBox to parse.
* @param trackId The track identifier to set on the format.
......@@ -179,8 +178,8 @@ public final class Ac3Util {
}
/**
* Returns the E-AC-3 format given {@code data} containing the EC3SpecificBox according to ETSI TS
* 102 366 Annex F. The reading position of {@code data} will be modified.
* Returns the E-AC-3 format given {@code data} containing the EC3SpecificBox according to Annex
* F. The reading position of {@code data} will be modified.
*
* @param data The EC3SpecificBox to parse.
* @param trackId The track identifier to set on the format.
......@@ -243,9 +242,10 @@ public final class Ac3Util {
public static SyncFrameInfo parseAc3SyncframeInfo(ParsableBitArray data) {
int initialPosition = data.getPosition();
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);
String mimeType;
@Nullable String mimeType;
@StreamType int streamType = SyncFrameInfo.STREAM_TYPE_UNDEFINED;
int sampleRate;
int acmod;
......@@ -254,7 +254,7 @@ public final class Ac3Util {
boolean lfeon;
int channelCount;
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
switch (data.readBits(2)) { // strmtyp
case 0:
......@@ -472,7 +472,8 @@ public final class Ac3Util {
if (data.length < 6) {
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) {
int frmsiz = (data[2] & 0x07) << 8; // Most significant 3 bits.
frmsiz |= data[3] & 0xFF; // Least significant 8 bits.
......@@ -485,24 +486,22 @@ public final class Ac3Util {
}
/**
* Returns the number of audio samples in an AC-3 syncframe.
*/
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
* Reads the number of audio samples represented by the given (E-)AC-3 syncframe. The buffer's
* position is not modified.
*
* @param buffer The {@link ByteBuffer} from which to read the syncframe.
* @return The number of audio samples represented by the syncframe.
*/
public static int parseEAc3SyncframeAudioSampleCount(ByteBuffer buffer) {
// See ETSI TS 102 366 subsection E.1.2.2.
int fscod = (buffer.get(buffer.position() + 4) & 0xC0) >> 6;
return AUDIO_SAMPLES_PER_AUDIO_BLOCK * (fscod == 0x03 ? 6
: BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD[(buffer.get(buffer.position() + 4) & 0x30) >> 4]);
public static int parseAc3SyncframeAudioSampleCount(ByteBuffer buffer) {
// 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 = ((buffer.get(buffer.position() + 5) & 0xF8) >> 3) > 10;
if (isEac3) {
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 {
// TODO: Parse AC-4 stream channel count.
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
* header size.
*/
......@@ -218,7 +223,7 @@ public final class Ac4Util {
/** Populates {@code buffer} with an AC-4 sample header for a sample of the specified size. */
public static void getAc4SampleHeader(int size, ParsableByteArray buffer) {
// 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[1] = 0x40;
buffer.data[2] = (byte) 0xFF;
......
......@@ -1149,9 +1149,7 @@ public final class DefaultAudioSink implements AudioSink {
case C.ENCODING_PCM_24BIT:
case C.ENCODING_PCM_32BIT:
case C.ENCODING_PCM_8BIT:
case C.ENCODING_PCM_A_LAW:
case C.ENCODING_PCM_FLOAT:
case C.ENCODING_PCM_MU_LAW:
case Format.NO_VALUE:
default:
throw new IllegalArgumentException();
......@@ -1166,10 +1164,9 @@ public final class DefaultAudioSink implements AudioSink {
case C.ENCODING_DTS_HD:
return DtsUtil.parseDtsAudioSampleCount(buffer);
case C.ENCODING_AC3:
return Ac3Util.getAc3SyncframeAudioSampleCount();
case C.ENCODING_E_AC3:
case C.ENCODING_E_AC3_JOC:
return Ac3Util.parseEAc3SyncframeAudioSampleCount(buffer);
return Ac3Util.parseAc3SyncframeAudioSampleCount(buffer);
case C.ENCODING_AC4:
return Ac4Util.parseAc4SyncframeAudioSampleCount(buffer);
case C.ENCODING_DOLBY_TRUEHD:
......
......@@ -81,7 +81,10 @@ public final class DtsUtil {
* @return The DTS format parsed from data in the header.
*/
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);
frameBits.skipBits(32 + 1 + 5 + 1 + 7 + 14); // SYNC, FTYPE, SHORT, CPF, NBLKS, FSIZE
int amode = frameBits.readBits(6);
......
......@@ -79,6 +79,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
private static final int MAX_PENDING_STREAM_CHANGE_COUNT = 10;
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 EventDispatcher eventDispatcher;
......@@ -566,7 +571,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
mediaFormat.getString(MediaFormat.KEY_MIME));
} else {
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 sampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
......
......@@ -29,8 +29,11 @@ import java.nio.ByteBuffer;
public AudioFormat onConfigure(AudioFormat inputAudioFormat)
throws UnhandledAudioFormatException {
@C.PcmEncoding int encoding = inputAudioFormat.encoding;
if (encoding != C.ENCODING_PCM_8BIT && encoding != C.ENCODING_PCM_16BIT
&& encoding != C.ENCODING_PCM_24BIT && encoding != C.ENCODING_PCM_32BIT) {
if (encoding != C.ENCODING_PCM_8BIT
&& 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);
}
return encoding != C.ENCODING_PCM_16BIT
......@@ -50,6 +53,9 @@ import java.nio.ByteBuffer;
case C.ENCODING_PCM_8BIT:
resampledSize = size * 2;
break;
case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
resampledSize = size;
break;
case C.ENCODING_PCM_24BIT:
resampledSize = (size / 3) * 2;
break;
......@@ -58,8 +64,6 @@ import java.nio.ByteBuffer;
break;
case C.ENCODING_PCM_16BIT:
case C.ENCODING_PCM_FLOAT:
case C.ENCODING_PCM_A_LAW:
case C.ENCODING_PCM_MU_LAW:
case C.ENCODING_INVALID:
case Format.NO_VALUE:
default:
......@@ -70,21 +74,28 @@ import java.nio.ByteBuffer;
ByteBuffer buffer = replaceOutputBuffer(resampledSize);
switch (inputAudioFormat.encoding) {
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++) {
buffer.put((byte) 0);
buffer.put((byte) ((inputBuffer.get(i) & 0xFF) - 128));
}
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:
// 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) {
buffer.put(inputBuffer.get(i + 1));
buffer.put(inputBuffer.get(i + 2));
}
break;
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) {
buffer.put(inputBuffer.get(i + 2));
buffer.put(inputBuffer.get(i + 3));
......@@ -92,8 +103,6 @@ import java.nio.ByteBuffer;
break;
case C.ENCODING_PCM_16BIT:
case C.ENCODING_PCM_FLOAT:
case C.ENCODING_PCM_A_LAW:
case C.ENCODING_PCM_MU_LAW:
case C.ENCODING_INVALID:
case Format.NO_VALUE:
default:
......
......@@ -32,15 +32,17 @@ public final class WavUtil {
public static final int DATA_FOURCC = 0x64617461;
/** 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. */
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. */
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. */
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. */
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}.
......@@ -57,10 +59,6 @@ public final class WavUtil {
case C.ENCODING_PCM_24BIT:
case C.ENCODING_PCM_32BIT:
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:
return TYPE_FLOAT;
case C.ENCODING_INVALID:
......@@ -81,10 +79,6 @@ public final class WavUtil {
return Util.getPcmEncoding(bitsPerSample);
case TYPE_FLOAT:
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:
return C.ENCODING_INVALID;
}
......
......@@ -64,10 +64,18 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
@Nullable Constructor<? extends Extractor> flacExtensionExtractorConstructor = null;
try {
// LINT.IfChange
flacExtensionExtractorConstructor =
Class.forName("com.google.android.exoplayer2.ext.flac.FlacExtractor")
.asSubclass(Extractor.class)
.getConstructor();
@SuppressWarnings("nullness:argument.type.incompatible")
boolean isFlacNativeLibraryAvailable =
Boolean.TRUE.equals(
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)
} catch (ClassNotFoundException e) {
// Expected if the app was built without the FLAC extension.
......
......@@ -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
* bits.
* @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) {
switch (blockSizeKey) {
......
......@@ -256,15 +256,18 @@ public final class FlacExtractor implements Extractor {
// Copy more bytes into the buffer.
int currentLimit = buffer.limit();
int bytesRead =
input.read(
buffer.data, /* offset= */ currentLimit, /* length= */ BUFFER_LENGTH - currentLimit);
boolean foundEndOfInput = bytesRead == C.RESULT_END_OF_INPUT;
if (!foundEndOfInput) {
buffer.setLimit(currentLimit + bytesRead);
} else if (buffer.bytesLeft() == 0) {
outputSampleMetadata();
return Extractor.RESULT_END_OF_INPUT;
boolean foundEndOfInput = false;
if (currentLimit < BUFFER_LENGTH) {
int bytesRead =
input.read(
buffer.data, /* offset= */ currentLimit, /* length= */ BUFFER_LENGTH - currentLimit);
foundEndOfInput = bytesRead == C.RESULT_END_OF_INPUT;
if (!foundEndOfInput) {
buffer.setLimit(currentLimit + bytesRead);
} else if (buffer.bytesLeft() == 0) {
outputSampleMetadata();
return Extractor.RESULT_END_OF_INPUT;
}
}
// Search for a frame.
......@@ -272,7 +275,7 @@ public final class FlacExtractor implements Extractor {
// Skip frame search on the bytes within the minimum frame size.
if (currentFrameBytesWritten < minFrameSize) {
buffer.skipBytes(Math.min(minFrameSize, buffer.bytesLeft()));
buffer.skipBytes(Math.min(minFrameSize - currentFrameBytesWritten, buffer.bytesLeft()));
}
long nextFrameFirstSampleNumber = findFrame(buffer, foundEndOfInput);
......
......@@ -69,9 +69,20 @@ import java.util.Collections;
} else if (audioFormat == AUDIO_FORMAT_ALAW || audioFormat == AUDIO_FORMAT_ULAW) {
String type = audioFormat == AUDIO_FORMAT_ALAW ? MimeTypes.AUDIO_ALAW
: MimeTypes.AUDIO_MLAW;
int pcmEncoding = (header & 0x01) == 1 ? C.ENCODING_PCM_16BIT : C.ENCODING_PCM_8BIT;
Format format = Format.createAudioSampleFormat(null, type, null, Format.NO_VALUE,
Format.NO_VALUE, 1, 8000, pcmEncoding, null, null, 0, null);
Format format =
Format.createAudioSampleFormat(
/* 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);
hasOutputFormat = true;
} else if (audioFormat != AUDIO_FORMAT_AAC) {
......
......@@ -1250,10 +1250,10 @@ public class MatroskaExtractor implements Extractor {
if (CODEC_ID_SUBRIP.equals(track.codecId) || CODEC_ID_ASS.equals(track.codecId)) {
if (blockSampleCount > 1) {
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.");
} 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
// appropriate encryption data here.
track.output.sampleData(subtitleSample, subtitleSample.limit());
......@@ -1829,10 +1829,8 @@ public class MatroskaExtractor implements Extractor {
chunkSize += size;
chunkOffset = offset; // The offset is to the end of the sample.
if (chunkSampleCount >= Ac3Util.TRUEHD_RECHUNK_SAMPLE_COUNT) {
// We haven't read enough samples to output a chunk.
return;
outputPendingSampleMetadata(track);
}
outputPendingSampleMetadata(track);
}
public void outputPendingSampleMetadata(Track track) {
......
......@@ -379,6 +379,9 @@ import java.util.List;
@SuppressWarnings("ConstantCaseForConstants")
public static final int TYPE_dfLa = 0x64664c61;
@SuppressWarnings("ConstantCaseForConstants")
public static final int TYPE_twos = 0x74776f73;
public final int type;
public Atom(int type) {
......
......@@ -798,6 +798,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|| childAtomType == Atom.TYPE_sawb
|| childAtomType == Atom.TYPE_lpcm
|| childAtomType == Atom.TYPE_sowt
|| childAtomType == Atom.TYPE_twos
|| childAtomType == Atom.TYPE__mp3
|| childAtomType == Atom.TYPE_alac
|| childAtomType == Atom.TYPE_alaw
......@@ -1086,6 +1087,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
int channelCount;
int sampleRate;
@C.PcmEncoding int pcmEncoding = Format.NO_VALUE;
if (quickTimeSoundDescriptionVersion == 0 || quickTimeSoundDescriptionVersion == 1) {
channelCount = parent.readUnsignedShort();
......@@ -1147,6 +1149,10 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
mimeType = MimeTypes.AUDIO_AMR_WB;
} else if (atomType == Atom.TYPE_lpcm || atomType == Atom.TYPE_sowt) {
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) {
mimeType = MimeTypes.AUDIO_MPEG;
} else if (atomType == Atom.TYPE_alac) {
......@@ -1233,9 +1239,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
}
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,
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, pcmEncoding,
initializationData == null ? null : Collections.singletonList(initializationData),
......
......@@ -168,7 +168,6 @@ public class FragmentedMp4Extractor implements Extractor {
private int sampleBytesWritten;
private int sampleCurrentNalBytesRemaining;
private boolean processSeiNalUnitPayload;
private boolean isAc4HeaderRequired;
// Extractor output.
@MonotonicNonNull private ExtractorOutput extractorOutput;
......@@ -302,7 +301,6 @@ public class FragmentedMp4Extractor implements Extractor {
pendingMetadataSampleBytes = 0;
pendingSeekTimeUs = timeUs;
containerAtoms.clear();
isAc4HeaderRequired = false;
enterReadingAtomHeaderState();
}
......@@ -1270,11 +1268,18 @@ public class FragmentedMp4Extractor implements Extractor {
}
sampleBytesWritten = currentTrackBundle.outputSampleEncryptionData();
sampleSize += sampleBytesWritten;
<<<<<<< HEAD
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;
sampleCurrentNalBytesRemaining = 0;
isAc4HeaderRequired =
MimeTypes.AUDIO_AC4.equals(currentTrackBundle.track.format.sampleMimeType);
}
TrackFragment fragment = currentTrackBundle.fragment;
......@@ -1339,6 +1344,7 @@ public class FragmentedMp4Extractor implements Extractor {
}
}
} else {
<<<<<<< HEAD
if (isAc4HeaderRequired) {
Ac4Util.getAc4SampleHeader(sampleSize - outputSampleEncryptionDataSize, scratch);
int length = scratch.limit();
......@@ -1347,6 +1353,8 @@ public class FragmentedMp4Extractor implements Extractor {
sampleBytesWritten += length;
isAc4HeaderRequired = false;
}
=======
>>>>>>> 4f15cfaa78f8053c4bf83ef0871df98d29fa6e0b
while (sampleBytesWritten < sampleSize) {
int writtenBytes = output.sampleData(input, sampleSize - sampleBytesWritten, false);
sampleBytesWritten += writtenBytes;
......
......@@ -110,9 +110,9 @@ public final class Mp4Extractor implements Extractor, SeekMap {
@Nullable private ParsableByteArray atomData;
private int sampleTrackIndex;
private int sampleBytesRead;
private int sampleBytesWritten;
private int sampleCurrentNalBytesRemaining;
private boolean isAc4HeaderRequired;
// Extractor outputs.
@MonotonicNonNull private ExtractorOutput extractorOutput;
......@@ -160,9 +160,9 @@ public final class Mp4Extractor implements Extractor, SeekMap {
containerAtoms.clear();
atomHeaderBytesRead = 0;
sampleTrackIndex = C.INDEX_UNSET;
sampleBytesRead = 0;
sampleBytesWritten = 0;
sampleCurrentNalBytesRemaining = 0;
isAc4HeaderRequired = false;
if (position == 0) {
enterReadingAtomHeaderState();
} else if (tracks != null) {
......@@ -507,15 +507,13 @@ public final class Mp4Extractor implements Extractor, SeekMap {
if (sampleTrackIndex == C.INDEX_UNSET) {
return RESULT_END_OF_INPUT;
}
isAc4HeaderRequired =
MimeTypes.AUDIO_AC4.equals(tracks[sampleTrackIndex].track.format.sampleMimeType);
}
Mp4Track track = tracks[sampleTrackIndex];
TrackOutput trackOutput = track.trackOutput;
int sampleIndex = track.sampleIndex;
long position = track.sampleTable.offsets[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) {
positionHolder.position = position;
return RESULT_SEEK;
......@@ -543,6 +541,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
if (sampleCurrentNalBytesRemaining == 0) {
// Read the NAL length so that we know where we find the next one.
input.readFully(nalLengthData, nalUnitLengthFieldLengthDiff, nalUnitLengthFieldLength);
sampleBytesRead += nalUnitLengthFieldLength;
nalLength.setPosition(0);
int nalLengthInt = nalLength.readInt();
if (nalLengthInt < 0) {
......@@ -557,21 +556,23 @@ public final class Mp4Extractor implements Extractor, SeekMap {
} else {
// Write the payload of the NAL unit.
int writtenBytes = trackOutput.sampleData(input, sampleCurrentNalBytesRemaining, false);
sampleBytesRead += writtenBytes;
sampleBytesWritten += writtenBytes;
sampleCurrentNalBytesRemaining -= writtenBytes;
}
}
} else {
if (isAc4HeaderRequired) {
Ac4Util.getAc4SampleHeader(sampleSize, scratch);
int length = scratch.limit();
trackOutput.sampleData(scratch, length);
sampleSize += length;
sampleBytesWritten += length;
isAc4HeaderRequired = false;
if (MimeTypes.AUDIO_AC4.equals(track.track.format.sampleMimeType)) {
if (sampleBytesWritten == 0) {
Ac4Util.getAc4SampleHeader(sampleSize, scratch);
trackOutput.sampleData(scratch, Ac4Util.SAMPLE_HEADER_SIZE);
sampleBytesWritten += Ac4Util.SAMPLE_HEADER_SIZE;
}
sampleSize += Ac4Util.SAMPLE_HEADER_SIZE;
}
while (sampleBytesWritten < sampleSize) {
int writtenBytes = trackOutput.sampleData(input, sampleSize - sampleBytesWritten, false);
sampleBytesRead += writtenBytes;
sampleBytesWritten += writtenBytes;
sampleCurrentNalBytesRemaining -= writtenBytes;
}
......@@ -580,6 +581,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
track.sampleTable.flags[sampleIndex], sampleSize, 0, null);
track.sampleIndex++;
sampleTrackIndex = C.INDEX_UNSET;
sampleBytesRead = 0;
sampleBytesWritten = 0;
sampleCurrentNalBytesRemaining = 0;
return RESULT_CONTINUE;
......
......@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.extractor.ts;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.Ac3Util;
......@@ -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.TrackOutput;
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.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
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.
......@@ -47,10 +52,10 @@ public final class Ac3Reader implements ElementaryStreamReader {
private final ParsableBitArray headerScratchBits;
private final ParsableByteArray headerScratchBytes;
private final String language;
@Nullable private final String language;
private String trackFormatId;
private TrackOutput output;
@MonotonicNonNull private String formatId;
@MonotonicNonNull private TrackOutput output;
@State private int state;
private int bytesRead;
......@@ -60,7 +65,7 @@ public final class Ac3Reader implements ElementaryStreamReader {
// Used when parsing the header.
private long sampleDurationUs;
private Format format;
@MonotonicNonNull private Format format;
private int sampleSize;
// Used when reading the samples.
......@@ -78,7 +83,7 @@ public final class Ac3Reader implements ElementaryStreamReader {
*
* @param language Track language.
*/
public Ac3Reader(String language) {
public Ac3Reader(@Nullable String language) {
headerScratchBits = new ParsableBitArray(new byte[HEADER_SIZE]);
headerScratchBytes = new ParsableByteArray(headerScratchBits.data);
state = STATE_FINDING_SYNC;
......@@ -95,7 +100,7 @@ public final class Ac3Reader implements ElementaryStreamReader {
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator generator) {
generator.generateNewId();
trackFormatId = generator.getFormatId();
formatId = generator.getFormatId();
output = extractorOutput.track(generator.getTrackId(), C.TRACK_TYPE_AUDIO);
}
......@@ -106,6 +111,7 @@ public final class Ac3Reader implements ElementaryStreamReader {
@Override
public void consume(ParsableByteArray data) {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
while (data.bytesLeft() > 0) {
switch (state) {
case STATE_FINDING_SYNC:
......@@ -185,19 +191,28 @@ public final class Ac3Reader implements ElementaryStreamReader {
return false;
}
/**
* Parses the sample header.
*/
@SuppressWarnings("ReferenceEquality")
/** Parses the sample header. */
@RequiresNonNull("output")
private void parseHeader() {
headerScratchBits.setPosition(0);
SyncFrameInfo frameInfo = Ac3Util.parseAc3SyncframeInfo(headerScratchBits);
if (format == null || frameInfo.channelCount != format.channelCount
if (format == null
|| frameInfo.channelCount != format.channelCount
|| frameInfo.sampleRate != format.sampleRate
|| frameInfo.mimeType != format.sampleMimeType) {
format = Format.createAudioSampleFormat(trackFormatId, frameInfo.mimeType, null,
Format.NO_VALUE, Format.NO_VALUE, frameInfo.channelCount, frameInfo.sampleRate, null,
null, 0, language);
|| Util.areEqual(frameInfo.mimeType, format.sampleMimeType)) {
format =
Format.createAudioSampleFormat(
formatId,
frameInfo.mimeType,
null,
Format.NO_VALUE,
Format.NO_VALUE,
frameInfo.channelCount,
frameInfo.sampleRate,
null,
null,
0,
language);
output.format(format);
}
sampleSize = frameInfo.frameSize;
......
......@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.extractor.ts;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.Ac4Util;
......@@ -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.TrackOutput;
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.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
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. */
public final class Ac4Reader implements ElementaryStreamReader {
......@@ -44,10 +48,10 @@ public final class Ac4Reader implements ElementaryStreamReader {
private final ParsableBitArray headerScratchBits;
private final ParsableByteArray headerScratchBytes;
private final String language;
@Nullable private final String language;
private String trackFormatId;
private TrackOutput output;
@MonotonicNonNull private String formatId;
@MonotonicNonNull private TrackOutput output;
@State private int state;
private int bytesRead;
......@@ -58,7 +62,7 @@ public final class Ac4Reader implements ElementaryStreamReader {
// Used when parsing the header.
private long sampleDurationUs;
private Format format;
@MonotonicNonNull private Format format;
private int sampleSize;
// Used when reading the samples.
......@@ -74,7 +78,7 @@ public final class Ac4Reader implements ElementaryStreamReader {
*
* @param language Track language.
*/
public Ac4Reader(String language) {
public Ac4Reader(@Nullable String language) {
headerScratchBits = new ParsableBitArray(new byte[Ac4Util.HEADER_SIZE_FOR_PARSER]);
headerScratchBytes = new ParsableByteArray(headerScratchBits.data);
state = STATE_FINDING_SYNC;
......@@ -95,7 +99,7 @@ public final class Ac4Reader implements ElementaryStreamReader {
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator generator) {
generator.generateNewId();
trackFormatId = generator.getFormatId();
formatId = generator.getFormatId();
output = extractorOutput.track(generator.getTrackId(), C.TRACK_TYPE_AUDIO);
}
......@@ -106,6 +110,7 @@ public final class Ac4Reader implements ElementaryStreamReader {
@Override
public void consume(ParsableByteArray data) {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
while (data.bytesLeft() > 0) {
switch (state) {
case STATE_FINDING_SYNC:
......@@ -185,7 +190,7 @@ public final class Ac4Reader implements ElementaryStreamReader {
}
/** Parses the sample header. */
@SuppressWarnings("ReferenceEquality")
@RequiresNonNull("output")
private void parseHeader() {
headerScratchBits.setPosition(0);
SyncFrameInfo frameInfo = Ac4Util.parseAc4SyncframeInfo(headerScratchBits);
......@@ -195,7 +200,7 @@ public final class Ac4Reader implements ElementaryStreamReader {
|| !MimeTypes.AUDIO_AC4.equals(format.sampleMimeType)) {
format =
Format.createAudioSampleFormat(
trackFormatId,
formatId,
MimeTypes.AUDIO_AC4,
/* codecs= */ null,
/* bitrate= */ Format.NO_VALUE,
......
......@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.extractor.ts;
import android.util.Pair;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException;
......@@ -23,13 +24,18 @@ import com.google.android.exoplayer2.extractor.DummyTrackOutput;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
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.Log;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.util.Arrays;
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.
......@@ -62,11 +68,11 @@ public final class AdtsReader implements ElementaryStreamReader {
private final boolean exposeId3;
private final ParsableBitArray adtsScratch;
private final ParsableByteArray id3HeaderBuffer;
private final String language;
@Nullable private final String language;
private String formatId;
private TrackOutput output;
private TrackOutput id3Output;
@MonotonicNonNull private String formatId;
@MonotonicNonNull private TrackOutput output;
@MonotonicNonNull private TrackOutput id3Output;
private int state;
private int bytesRead;
......@@ -90,7 +96,7 @@ public final class AdtsReader implements ElementaryStreamReader {
// Used when reading the samples.
private long timeUs;
private TrackOutput currentOutput;
@MonotonicNonNull private TrackOutput currentOutput;
private long currentSampleDuration;
/**
......@@ -104,7 +110,7 @@ public final class AdtsReader implements ElementaryStreamReader {
* @param exposeId3 True if the reader should expose ID3 information.
* @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]);
id3HeaderBuffer = new ParsableByteArray(Arrays.copyOf(ID3_IDENTIFIER, ID3_HEADER_SIZE));
setFindingSampleState();
......@@ -130,6 +136,7 @@ public final class AdtsReader implements ElementaryStreamReader {
idGenerator.generateNewId();
formatId = idGenerator.getFormatId();
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_AUDIO);
currentOutput = output;
if (exposeId3) {
idGenerator.generateNewId();
id3Output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_METADATA);
......@@ -147,6 +154,7 @@ public final class AdtsReader implements ElementaryStreamReader {
@Override
public void consume(ParsableByteArray data) throws ParserException {
assertTracksCreated();
while (data.bytesLeft() > 0) {
switch (state) {
case STATE_FINDING_SAMPLE:
......@@ -425,9 +433,8 @@ public final class AdtsReader implements ElementaryStreamReader {
return true;
}
/**
* Parses the Id3 header.
*/
/** Parses the Id3 header. */
@RequiresNonNull("id3Output")
private void parseId3Header() {
id3Output.sampleData(id3HeaderBuffer, ID3_HEADER_SIZE);
id3HeaderBuffer.setPosition(ID3_SIZE_OFFSET);
......@@ -435,9 +442,8 @@ public final class AdtsReader implements ElementaryStreamReader {
id3HeaderBuffer.readSynchSafeInt() + ID3_HEADER_SIZE);
}
/**
* Parses the sample header.
*/
/** Parses the sample header. */
@RequiresNonNull("output")
private void parseAdtsHeader() throws ParserException {
adtsScratch.setPosition(0);
......@@ -487,9 +493,8 @@ public final class AdtsReader implements ElementaryStreamReader {
setReadingSampleState(output, sampleDurationUs, 0, sampleSize);
}
/**
* Reads the rest of the sample
*/
/** Reads the rest of the sample */
@RequiresNonNull("currentOutput")
private void readSample(ParsableByteArray data) {
int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead);
currentOutput.sampleData(data, bytesToRead);
......@@ -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;
import android.util.SparseArray;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo;
import com.google.android.exoplayer2.text.cea.Cea708InitializationData;
......@@ -134,6 +135,7 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
return new SparseArray<>();
}
@Nullable
@Override
public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) {
switch (streamType) {
......@@ -247,7 +249,7 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
// Skip reserved (8).
scratchDescriptorData.skipBytes(1);
List<byte[]> initializationData = null;
@Nullable List<byte[]> initializationData = null;
// The wide_aspect_ratio flag only has meaning for CEA-708.
if (isDigital) {
boolean isWideAspectRatio = (flags & 0x40) != 0;
......
......@@ -15,13 +15,17 @@
*/
package com.google.android.exoplayer2.extractor.ts;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.DtsUtil;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
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.
......@@ -35,10 +39,10 @@ public final class DtsReader implements ElementaryStreamReader {
private static final int HEADER_SIZE = 18;
private final ParsableByteArray headerScratchBytes;
private final String language;
@Nullable private final String language;
private String formatId;
private TrackOutput output;
@MonotonicNonNull private String formatId;
@MonotonicNonNull private TrackOutput output;
private int state;
private int bytesRead;
......@@ -48,7 +52,7 @@ public final class DtsReader implements ElementaryStreamReader {
// Used when parsing the header.
private long sampleDurationUs;
private Format format;
@MonotonicNonNull private Format format;
private int sampleSize;
// Used when reading the samples.
......@@ -59,7 +63,7 @@ public final class DtsReader implements ElementaryStreamReader {
*
* @param language Track language.
*/
public DtsReader(String language) {
public DtsReader(@Nullable String language) {
headerScratchBytes = new ParsableByteArray(new byte[HEADER_SIZE]);
state = STATE_FINDING_SYNC;
this.language = language;
......@@ -86,6 +90,7 @@ public final class DtsReader implements ElementaryStreamReader {
@Override
public void consume(ParsableByteArray data) {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
while (data.bytesLeft() > 0) {
switch (state) {
case STATE_FINDING_SYNC:
......@@ -162,9 +167,8 @@ public final class DtsReader implements ElementaryStreamReader {
return false;
}
/**
* Parses the sample header.
*/
/** Parses the sample header. */
@RequiresNonNull("output")
private void parseHeader() {
byte[] frameData = headerScratchBytes.data;
if (format == null) {
......
......@@ -64,12 +64,12 @@ public final class DvbSubtitleReader implements ElementaryStreamReader {
Format.createImageSampleFormat(
idGenerator.getFormatId(),
MimeTypes.APPLICATION_DVBSUBS,
null,
/* codecs= */ null,
Format.NO_VALUE,
0,
/* selectionFlags= */ 0,
Collections.singletonList(subtitleInfo.initializationData),
subtitleInfo.language,
null));
/* drmInitData= */ null));
outputs[i] = output;
}
}
......
......@@ -16,16 +16,20 @@
package com.google.android.exoplayer2.extractor.ts;
import android.util.Pair;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
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.NalUnitUtil;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.util.Arrays;
import java.util.Collections;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Parses a continuous H262 byte stream and extracts individual frames.
......@@ -38,27 +42,27 @@ public final class H262Reader implements ElementaryStreamReader {
private static final int START_GROUP = 0xB8;
private static final int START_USER_DATA = 0xB2;
private String formatId;
private TrackOutput output;
@MonotonicNonNull private String formatId;
@MonotonicNonNull private TrackOutput output;
// 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[] {
24000d / 1001, 24, 25, 30000d / 1001, 30, 50, 60000d / 1001, 60};
// State that should not be reset on seek.
private boolean hasOutputFormat;
private long frameDurationUs;
private final UserDataReader userDataReader;
private final ParsableByteArray userDataParsable;
@Nullable private final UserDataReader userDataReader;
@Nullable private final ParsableByteArray userDataParsable;
// State that should be reset on seek.
@Nullable private final NalUnitTargetBuffer userData;
private final boolean[] prefixFlags;
private final CsdBuffer csdBuffer;
private final NalUnitTargetBuffer userData;
private long totalBytesWritten;
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.
private long pesTimeUs;
......@@ -72,7 +76,7 @@ public final class H262Reader implements ElementaryStreamReader {
this(null);
}
/* package */ H262Reader(UserDataReader userDataReader) {
/* package */ H262Reader(@Nullable UserDataReader userDataReader) {
this.userDataReader = userDataReader;
prefixFlags = new boolean[4];
csdBuffer = new CsdBuffer(128);
......@@ -89,7 +93,7 @@ public final class H262Reader implements ElementaryStreamReader {
public void seek() {
NalUnitUtil.clearPrefixFlags(prefixFlags);
csdBuffer.reset();
if (userDataReader != null) {
if (userData != null) {
userData.reset();
}
totalBytesWritten = 0;
......@@ -114,6 +118,7 @@ public final class H262Reader implements ElementaryStreamReader {
@Override
public void consume(ParsableByteArray data) {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
int offset = data.getPosition();
int limit = data.limit();
byte[] dataArray = data.data;
......@@ -130,7 +135,7 @@ public final class H262Reader implements ElementaryStreamReader {
if (!hasOutputFormat) {
csdBuffer.onData(dataArray, offset, limit);
}
if (userDataReader != null) {
if (userData != null) {
userData.appendToNalUnit(dataArray, offset, limit);
}
return;
......@@ -157,7 +162,7 @@ public final class H262Reader implements ElementaryStreamReader {
hasOutputFormat = true;
}
}
if (userDataReader != null) {
if (userData != null) {
int bytesAlreadyPassed = 0;
if (lengthToStartCode > 0) {
userData.appendToNalUnit(dataArray, offset, startCodeOffset);
......@@ -167,8 +172,8 @@ public final class H262Reader implements ElementaryStreamReader {
if (userData.endNalUnit(bytesAlreadyPassed)) {
int unescapedLength = NalUnitUtil.unescapeStream(userData.nalData, userData.nalLength);
userDataParsable.reset(userData.nalData, unescapedLength);
userDataReader.consume(sampleTimeUs, userDataParsable);
Util.castNonNull(userDataParsable).reset(userData.nalData, unescapedLength);
Util.castNonNull(userDataReader).consume(sampleTimeUs, userDataParsable);
}
if (startCodeValue == START_USER_DATA && data.data[startCodeOffset + 2] == 0x1) {
......@@ -211,10 +216,10 @@ public final class H262Reader implements ElementaryStreamReader {
*
* @param csdBuffer The csd buffer.
* @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
* 0 if the duration could not be determined.
* @return A pair consisting of the {@link Format} and the frame duration in microseconds, or 0 if
* 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);
int firstByte = csdData[4] & 0xFF;
......
......@@ -23,15 +23,21 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
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.MimeTypes;
import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.NalUnitUtil.SpsData;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.ParsableNalUnitBitArray;
import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList;
import java.util.Arrays;
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.
......@@ -51,9 +57,9 @@ public final class H264Reader implements ElementaryStreamReader {
private long totalBytesWritten;
private final boolean[] prefixFlags;
private String formatId;
private TrackOutput output;
private SampleReader sampleReader;
@MonotonicNonNull private String formatId;
@MonotonicNonNull private TrackOutput output;
@MonotonicNonNull private SampleReader sampleReader;
// State that should not be reset on seek.
private boolean hasOutputFormat;
......@@ -87,13 +93,15 @@ public final class H264Reader implements ElementaryStreamReader {
@Override
public void seek() {
totalBytesWritten = 0;
randomAccessIndicator = false;
NalUnitUtil.clearPrefixFlags(prefixFlags);
sps.reset();
pps.reset();
sei.reset();
sampleReader.reset();
totalBytesWritten = 0;
randomAccessIndicator = false;
if (sampleReader != null) {
sampleReader.reset();
}
}
@Override
......@@ -113,6 +121,8 @@ public final class H264Reader implements ElementaryStreamReader {
@Override
public void consume(ParsableByteArray data) {
assertTracksCreated();
int offset = data.getPosition();
int limit = data.limit();
byte[] dataArray = data.data;
......@@ -159,6 +169,7 @@ public final class H264Reader implements ElementaryStreamReader {
// Do nothing.
}
@RequiresNonNull("sampleReader")
private void startNalUnit(long position, int nalUnitType, long pesTimeUs) {
if (!hasOutputFormat || sampleReader.needsSpsPps()) {
sps.startNalUnit(nalUnitType);
......@@ -168,6 +179,7 @@ public final class H264Reader implements ElementaryStreamReader {
sampleReader.startNalUnit(position, nalUnitType, pesTimeUs);
}
@RequiresNonNull("sampleReader")
private void nalUnitData(byte[] dataArray, int offset, int limit) {
if (!hasOutputFormat || sampleReader.needsSpsPps()) {
sps.appendToNalUnit(dataArray, offset, limit);
......@@ -177,6 +189,7 @@ public final class H264Reader implements ElementaryStreamReader {
sampleReader.appendToNalUnit(dataArray, offset, limit);
}
@RequiresNonNull({"output", "sampleReader"})
private void endNalUnit(long position, int offset, int discardPadding, long pesTimeUs) {
if (!hasOutputFormat || sampleReader.needsSpsPps()) {
sps.endNalUnit(discardPadding);
......@@ -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. */
private static final class SampleReader {
......@@ -478,7 +497,7 @@ public final class H264Reader implements ElementaryStreamReader {
private boolean isComplete;
private boolean hasSliceType;
private SpsData spsData;
@Nullable private SpsData spsData;
private int nalRefIdc;
private int sliceType;
private int frameNum;
......@@ -542,6 +561,8 @@ public final class H264Reader implements ElementaryStreamReader {
private boolean isFirstVclNalUnitOfPicture(SliceHeaderData other) {
// See ISO 14496-10 subsection 7.4.1.2.4.
SpsData spsData = Assertions.checkStateNotNull(this.spsData);
SpsData otherSpsData = Assertions.checkStateNotNull(other.spsData);
return isComplete
&& (!other.isComplete
|| frameNum != other.frameNum
......@@ -552,15 +573,15 @@ public final class H264Reader implements ElementaryStreamReader {
&& bottomFieldFlag != other.bottomFieldFlag)
|| (nalRefIdc != other.nalRefIdc && (nalRefIdc == 0 || other.nalRefIdc == 0))
|| (spsData.picOrderCountType == 0
&& other.spsData.picOrderCountType == 0
&& otherSpsData.picOrderCountType == 0
&& (picOrderCntLsb != other.picOrderCntLsb
|| deltaPicOrderCntBottom != other.deltaPicOrderCntBottom))
|| (spsData.picOrderCountType == 1
&& other.spsData.picOrderCountType == 1
&& otherSpsData.picOrderCountType == 1
&& (deltaPicOrderCnt0 != other.deltaPicOrderCnt0
|| deltaPicOrderCnt1 != other.deltaPicOrderCnt1))
|| idrPicFlag != other.idrPicFlag
|| (idrPicFlag && other.idrPicFlag && idrPicId != other.idrPicId));
|| (idrPicFlag && idrPicId != other.idrPicId));
}
}
}
......
......@@ -20,12 +20,18 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
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.MimeTypes;
import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.ParsableNalUnitBitArray;
import com.google.android.exoplayer2.util.Util;
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.
......@@ -46,9 +52,9 @@ public final class H265Reader implements ElementaryStreamReader {
private final SeiReader seiReader;
private String formatId;
private TrackOutput output;
private SampleReader sampleReader;
@MonotonicNonNull private String formatId;
@MonotonicNonNull private TrackOutput output;
@MonotonicNonNull private SampleReader sampleReader;
// State that should not be reset on seek.
private boolean hasOutputFormat;
......@@ -84,14 +90,16 @@ public final class H265Reader implements ElementaryStreamReader {
@Override
public void seek() {
totalBytesWritten = 0;
NalUnitUtil.clearPrefixFlags(prefixFlags);
vps.reset();
sps.reset();
pps.reset();
prefixSei.reset();
suffixSei.reset();
sampleReader.reset();
totalBytesWritten = 0;
if (sampleReader != null) {
sampleReader.reset();
}
}
@Override
......@@ -111,6 +119,8 @@ public final class H265Reader implements ElementaryStreamReader {
@Override
public void consume(ParsableByteArray data) {
assertTracksCreated();
while (data.bytesLeft() > 0) {
int offset = data.getPosition();
int limit = data.limit();
......@@ -160,6 +170,7 @@ public final class H265Reader implements ElementaryStreamReader {
// Do nothing.
}
@RequiresNonNull("sampleReader")
private void startNalUnit(long position, int offset, int nalUnitType, long pesTimeUs) {
if (hasOutputFormat) {
sampleReader.startNalUnit(position, offset, nalUnitType, pesTimeUs);
......@@ -172,6 +183,7 @@ public final class H265Reader implements ElementaryStreamReader {
suffixSei.startNalUnit(nalUnitType);
}
@RequiresNonNull("sampleReader")
private void nalUnitData(byte[] dataArray, int offset, int limit) {
if (hasOutputFormat) {
sampleReader.readNalUnitData(dataArray, offset, limit);
......@@ -184,6 +196,7 @@ public final class H265Reader implements ElementaryStreamReader {
suffixSei.appendToNalUnit(dataArray, offset, limit);
}
@RequiresNonNull({"output", "sampleReader"})
private void endNalUnit(long position, int offset, int discardPadding, long pesTimeUs) {
if (hasOutputFormat) {
sampleReader.endNalUnit(position, offset);
......@@ -214,8 +227,11 @@ public final class H265Reader implements ElementaryStreamReader {
}
}
private static Format parseMediaFormat(String formatId, NalUnitTargetBuffer vps,
NalUnitTargetBuffer sps, NalUnitTargetBuffer pps) {
private static Format parseMediaFormat(
@Nullable String formatId,
NalUnitTargetBuffer vps,
NalUnitTargetBuffer sps,
NalUnitTargetBuffer pps) {
// Build codec-specific data.
byte[] csd = new byte[vps.nalLength + sps.nalLength + pps.nalLength];
System.arraycopy(vps.nalData, 0, csd, 0, vps.nalLength);
......@@ -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 {
/**
......
......@@ -23,9 +23,11 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
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.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Parses ID3 data and extracts individual text information frames.
......@@ -36,7 +38,7 @@ public final class Id3Reader implements ElementaryStreamReader {
private final ParsableByteArray id3Header;
private TrackOutput output;
@MonotonicNonNull private TrackOutput output;
// State that should be reset on seek.
private boolean writingSample;
......@@ -76,6 +78,7 @@ public final class Id3Reader implements ElementaryStreamReader {
@Override
public void consume(ParsableByteArray data) {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
if (!writingSample) {
return;
}
......@@ -106,6 +109,7 @@ public final class Id3Reader implements ElementaryStreamReader {
@Override
public void packetFinished() {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
if (!writingSample || sampleSize == 0 || sampleBytesRead != sampleSize) {
return;
}
......
......@@ -23,11 +23,14 @@ import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
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.MimeTypes;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
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.
......@@ -43,14 +46,14 @@ public final class LatmReader implements ElementaryStreamReader {
private static final int SYNC_BYTE_FIRST = 0x56;
private static final int SYNC_BYTE_SECOND = 0xE0;
private final String language;
@Nullable private final String language;
private final ParsableByteArray sampleDataBuffer;
private final ParsableBitArray sampleBitArray;
// Track output info.
private TrackOutput output;
private Format format;
private String formatId;
@MonotonicNonNull private TrackOutput output;
@MonotonicNonNull private String formatId;
@MonotonicNonNull private Format format;
// Parser state info.
private int state;
......@@ -99,6 +102,7 @@ public final class LatmReader implements ElementaryStreamReader {
@Override
public void consume(ParsableByteArray data) throws ParserException {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
int bytesToRead;
while (data.bytesLeft() > 0) {
switch (state) {
......@@ -150,6 +154,7 @@ public final class LatmReader implements ElementaryStreamReader {
*
* @param data A {@link ParsableBitArray} containing the AudioMuxElement's bytes.
*/
@RequiresNonNull("output")
private void parseAudioMuxElement(ParsableBitArray data) throws ParserException {
boolean useSameStreamMux = data.readBit();
if (!useSameStreamMux) {
......@@ -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 {
int audioMuxVersion = data.readBits(1);
audioMuxVersionA = audioMuxVersion == 1 ? data.readBits(1) : 0;
......@@ -198,9 +202,19 @@ public final class LatmReader implements ElementaryStreamReader {
data.setPosition(startPosition);
byte[] initData = new byte[(readBits + 7) / 8];
data.readBits(initData, 0, readBits);
Format format = Format.createAudioSampleFormat(formatId, MimeTypes.AUDIO_AAC, null,
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRateHz,
Collections.singletonList(initData), null, 0, language);
Format format =
Format.createAudioSampleFormat(
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)) {
this.format = format;
sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / format.sampleRate;
......@@ -280,6 +294,7 @@ public final class LatmReader implements ElementaryStreamReader {
}
}
@RequiresNonNull("output")
private void parsePayloadMux(ParsableBitArray data, int muxLengthBytes) {
// The start of sample data in
int bitPosition = data.getPosition();
......
......@@ -21,7 +21,11 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.MpegAudioHeader;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
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.
......@@ -36,10 +40,10 @@ public final class MpegAudioReader implements ElementaryStreamReader {
private final ParsableByteArray headerScratch;
private final MpegAudioHeader header;
private final String language;
@Nullable private final String language;
private String formatId;
private TrackOutput output;
@MonotonicNonNull private TrackOutput output;
@MonotonicNonNull private String formatId;
private int state;
private int frameBytesRead;
......@@ -59,7 +63,7 @@ public final class MpegAudioReader implements ElementaryStreamReader {
this(null);
}
public MpegAudioReader(String language) {
public MpegAudioReader(@Nullable String language) {
state = STATE_FINDING_HEADER;
// The first byte of an MPEG Audio frame header is always 0xFF.
headerScratch = new ParsableByteArray(4);
......@@ -89,6 +93,7 @@ public final class MpegAudioReader implements ElementaryStreamReader {
@Override
public void consume(ParsableByteArray data) {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
while (data.bytesLeft() > 0) {
switch (state) {
case STATE_FINDING_HEADER:
......@@ -146,20 +151,21 @@ public final class MpegAudioReader implements ElementaryStreamReader {
/**
* 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
* output as sample data, and the position of the source is advanced to the byte that immediately
* follows the header.
* <p>
* If a frame header is read in full but cannot be parsed then the state is changed to
* {@link #STATE_READING_HEADER}.
* <p>
* If a frame header is not read in full then the position of the source is advanced to the limit,
* and the method should be called again with the next source to continue the read.
*
* <p>If a frame header is read in full but cannot be parsed then the state is changed to {@link
* #STATE_READING_HEADER}.
*
* <p>If a frame header is not read in full then the position of the source is advanced to 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.
*/
@RequiresNonNull("output")
private void readHeaderRemainder(ParsableByteArray source) {
int bytesToRead = Math.min(source.bytesLeft(), HEADER_SIZE - frameBytesRead);
source.readBytes(headerScratch.data, frameBytesRead, bytesToRead);
......@@ -195,16 +201,17 @@ public final class MpegAudioReader implements ElementaryStreamReader {
/**
* 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
* the frame.
* <p>
* If a frame is not read in full then the position of the source will have been advanced to the
* limit, and the method should be called again with the next source to continue the read.
*
* <p>If a frame is not read in full then the position of the source will have been advanced to
* 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.
*/
@RequiresNonNull("output")
private void readFrameRemainder(ParsableByteArray source) {
int bytesToRead = Math.min(source.bytesLeft(), frameSize - frameBytesRead);
output.sampleData(source, bytesToRead);
......@@ -219,5 +226,4 @@ public final class MpegAudioReader implements ElementaryStreamReader {
frameBytesRead = 0;
state = STATE_FINDING_HEADER;
}
}
......@@ -15,13 +15,17 @@
*/
package com.google.android.exoplayer2.extractor.ts;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException;
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.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
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.
......@@ -45,7 +49,7 @@ public final class PesReader implements TsPayloadReader {
private int state;
private int bytesRead;
private TimestampAdjuster timestampAdjuster;
@MonotonicNonNull private TimestampAdjuster timestampAdjuster;
private boolean ptsFlag;
private boolean dtsFlag;
private boolean seenFirstDts;
......@@ -79,6 +83,8 @@ public final class PesReader implements TsPayloadReader {
@Override
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) {
switch (state) {
case STATE_FINDING_HEADER:
......@@ -119,7 +125,7 @@ public final class PesReader implements TsPayloadReader {
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.
if (continueRead(data, pesScratch.data, readLength)
&& continueRead(data, null, extendedHeaderLength)) {
&& continueRead(data, /* target= */ null, extendedHeaderLength)) {
parseHeaderExtension();
flags |= dataAlignmentIndicator ? FLAG_DATA_ALIGNMENT_INDICATOR : 0;
reader.packetStarted(timeUs, flags);
......@@ -162,7 +168,8 @@ public final class PesReader implements TsPayloadReader {
* @param targetLength The target length of the read.
* @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);
if (bytesToRead <= 0) {
return true;
......@@ -207,6 +214,7 @@ public final class PesReader implements TsPayloadReader {
return true;
}
@RequiresNonNull("timestampAdjuster")
private void parseHeaderExtension() {
pesScratch.setPosition(0);
timeUs = C.TIME_UNSET;
......
......@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.extractor.ts;
import android.util.SparseArray;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.Extractor;
......@@ -25,10 +26,13 @@ import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap;
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.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
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.
......@@ -67,8 +71,8 @@ public final class PsExtractor implements Extractor {
private long lastTrackPosition;
// Accessed only by the loading thread.
private PsBinarySearchSeeker psBinarySearchSeeker;
private ExtractorOutput output;
@Nullable private PsBinarySearchSeeker psBinarySearchSeeker;
@MonotonicNonNull private ExtractorOutput output;
private boolean hasOutputSeekMap;
public PsExtractor() {
......@@ -160,6 +164,7 @@ public final class PsExtractor implements Extractor {
@Override
public int read(ExtractorInput input, PositionHolder seekPosition)
throws IOException, InterruptedException {
Assertions.checkStateNotNull(output); // Asserts init has been called.
long inputLength = input.getLength();
boolean canReadDuration = inputLength != C.LENGTH_UNSET;
......@@ -221,7 +226,7 @@ public final class PsExtractor implements Extractor {
PesReader payloadReader = psPayloadReaders.get(streamId);
if (!foundAllTracks) {
if (payloadReader == null) {
ElementaryStreamReader elementaryStreamReader = null;
@Nullable ElementaryStreamReader elementaryStreamReader = null;
if (streamId == PRIVATE_STREAM_1) {
// Private stream, used for AC3 audio.
// 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 {
// Internals.
@RequiresNonNull("output")
private void maybeOutputSeekMap(long inputLength) {
if (!hasOutputSeekMap) {
hasOutputSeekMap = true;
......
......@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.extractor.ts;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
......@@ -45,7 +46,7 @@ public final class SeiReader {
idGenerator.generateNewId();
TrackOutput output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT);
Format channelFormat = closedCaptionFormats.get(i);
String channelMimeType = channelFormat.sampleMimeType;
@Nullable String channelMimeType = channelFormat.sampleMimeType;
Assertions.checkArgument(MimeTypes.APPLICATION_CEA608.equals(channelMimeType)
|| MimeTypes.APPLICATION_CEA708.equals(channelMimeType),
"Invalid closed caption mime type provided: " + channelMimeType);
......@@ -69,5 +70,4 @@ public final class SeiReader {
public void consume(long pesTimeUs, ParsableByteArray seiBuffer) {
CeaUtil.consume(pesTimeUs, seiBuffer, outputs);
}
}
......@@ -19,17 +19,21 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
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.ParsableByteArray;
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.
*/
public final class SpliceInfoSectionReader implements SectionPayloadReader {
private TimestampAdjuster timestampAdjuster;
private TrackOutput output;
@MonotonicNonNull private TimestampAdjuster timestampAdjuster;
@MonotonicNonNull private TrackOutput output;
private boolean formatDeclared;
@Override
......@@ -44,6 +48,7 @@ public final class SpliceInfoSectionReader implements SectionPayloadReader {
@Override
public void consume(ParsableByteArray sectionData) {
assertInitialized();
if (!formatDeclared) {
if (timestampAdjuster.getTimestampOffsetUs() == C.TIME_UNSET) {
// There is not enough information to initialize the timestamp adjuster.
......@@ -59,4 +64,9 @@ public final class SpliceInfoSectionReader implements SectionPayloadReader {
sampleSize, 0, null);
}
@EnsuresNonNull({"timestampAdjuster", "output"})
private void assertInitialized() {
Assertions.checkStateNotNull(timestampAdjuster);
Util.castNonNull(output);
}
}
......@@ -21,6 +21,7 @@ import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.Extractor;
......@@ -587,8 +588,11 @@ public final class TsExtractor implements Extractor {
continue;
}
TsPayloadReader reader = mode == MODE_HLS && streamType == TS_STREAM_TYPE_ID3 ? id3Reader
: payloadReaderFactory.createPayloadReader(streamType, esInfo);
@Nullable
TsPayloadReader reader =
mode == MODE_HLS && streamType == TS_STREAM_TYPE_ID3
? id3Reader
: payloadReaderFactory.createPayloadReader(streamType, esInfo);
if (mode != MODE_HLS
|| elementaryPid < trackIdToPidScratch.get(trackId, MAX_PID_PLUS_ONE)) {
trackIdToPidScratch.put(trackId, elementaryPid);
......@@ -602,7 +606,7 @@ public final class TsExtractor implements Extractor {
int trackPid = trackIdToPidScratch.valueAt(i);
trackIds.put(trackId, true);
trackPids.put(trackPid, true);
TsPayloadReader reader = trackIdToReaderScratch.valueAt(i);
@Nullable TsPayloadReader reader = trackIdToReaderScratch.valueAt(i);
if (reader != null) {
if (reader != id3Reader) {
reader.init(timestampAdjuster, output,
......
......@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.extractor.ts;
import android.util.SparseArray;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
......@@ -53,11 +54,11 @@ public interface TsPayloadReader {
*
* @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.
* @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.
*/
@Nullable
TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo);
}
/**
......@@ -66,18 +67,21 @@ public interface TsPayloadReader {
final class EsInfo {
public final int streamType;
public final String language;
@Nullable public final String language;
public final List<DvbSubtitleInfo> dvbSubtitleInfos;
public final byte[] descriptorBytes;
/**
* @param streamType The type of the stream as defined by the
* {@link TsExtractor}{@code .TS_STREAM_TYPE_*}.
* @param streamType The type of the stream as defined by the {@link TsExtractor}{@code
* .TS_STREAM_TYPE_*}.
* @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 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) {
this.streamType = streamType;
this.language = language;
......@@ -134,6 +138,7 @@ public interface TsPayloadReader {
this.firstTrackId = firstTrackId;
this.trackIdIncrement = trackIdIncrement;
trackId = ID_UNSET;
formatId = "";
}
/**
......
......@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.extractor.ts;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
......@@ -44,7 +45,7 @@ import java.util.List;
idGenerator.generateNewId();
TrackOutput output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT);
Format channelFormat = closedCaptionFormats.get(i);
String channelMimeType = channelFormat.sampleMimeType;
@Nullable String channelMimeType = channelFormat.sampleMimeType;
Assertions.checkArgument(
MimeTypes.APPLICATION_CEA608.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;
@Override
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.
long blockSize = wavHeader.blockSize;
long blockIndex = Util.constrainValue(positionOffset / blockSize, 0, blockCount - 1);
long blockIndex = (timeUs * wavHeader.frameRateHz) / (C.MICROS_PER_SECOND * framesPerBlock);
blockIndex = Util.constrainValue(blockIndex, 0, blockCount - 1);
long seekPosition = firstBlockPosition + (blockIndex * blockSize);
long seekPosition = firstBlockPosition + (blockIndex * wavHeader.blockSize);
long seekTimeUs = blockIndexToTimeUs(blockIndex);
SeekPoint seekPoint = new SeekPoint(seekTimeUs, seekPosition);
if (seekTimeUs >= timeUs || blockIndex == blockCount - 1) {
return new SeekPoints(seekPoint);
} else {
long secondBlockIndex = blockIndex + 1;
long secondSeekPosition = firstBlockPosition + (secondBlockIndex * blockSize);
long secondSeekPosition = firstBlockPosition + (secondBlockIndex * wavHeader.blockSize);
long secondSeekTimeUs = blockIndexToTimeUs(secondBlockIndex);
SeekPoint secondSeekPoint = new SeekPoint(secondSeekTimeUs, secondSeekPosition);
return new SeekPoints(seekPoint, secondSeekPoint);
......
......@@ -33,12 +33,12 @@ import com.google.android.exoplayer2.util.Assertions;
*/
@RequiresApi(21)
/* package */ final class AsynchronousMediaCodecAdapter implements MediaCodecAdapter {
private MediaCodecAsyncCallback mediaCodecAsyncCallback;
private final MediaCodecAsyncCallback mediaCodecAsyncCallback;
private final Handler handler;
private final MediaCodec codec;
@Nullable private IllegalStateException internalException;
private boolean flushing;
private Runnable onCodecStart;
private Runnable codecStartRunnable;
/**
* Create a new {@code AsynchronousMediaCodecAdapter}.
......@@ -51,11 +51,16 @@ import com.google.android.exoplayer2.util.Assertions;
@VisibleForTesting
/* package */ AsynchronousMediaCodecAdapter(MediaCodec codec, Looper looper) {
this.mediaCodecAsyncCallback = new MediaCodecAsyncCallback();
mediaCodecAsyncCallback = new MediaCodecAsyncCallback();
handler = new Handler(looper);
this.codec = codec;
this.codec.setCallback(mediaCodecAsyncCallback);
onCodecStart = () -> codec.start();
codecStartRunnable = codec::start;
}
@Override
public void start() {
codecStartRunnable.run();
}
@Override
......@@ -105,7 +110,7 @@ import com.google.android.exoplayer2.util.Assertions;
flushing = false;
mediaCodecAsyncCallback.flush();
try {
onCodecStart.run();
codecStartRunnable.run();
} catch (IllegalStateException e) {
// Catch IllegalStateException directly so that we don't have to wrap it.
internalException = e;
......@@ -115,8 +120,8 @@ import com.google.android.exoplayer2.util.Assertions;
}
@VisibleForTesting
/* package */ void setOnCodecStart(Runnable onCodecStart) {
this.onCodecStart = onCodecStart;
/* package */ void setCodecStartRunnable(Runnable codecStartRunnable) {
this.codecStartRunnable = codecStartRunnable;
}
private void maybeThrowException() throws IllegalStateException {
......
......@@ -26,7 +26,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
......@@ -54,7 +53,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@MonotonicNonNull private Handler handler;
private long pendingFlushCount;
private @State int state;
private Runnable onCodecStart;
private Runnable codecStartRunnable;
@Nullable private IllegalStateException internalException;
/**
......@@ -77,31 +76,20 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.codec = codec;
this.handlerThread = handlerThread;
state = STATE_CREATED;
onCodecStart = codec::start;
codecStartRunnable = codec::start;
}
/**
* 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.
*/
@Override
public synchronized void start() {
Assertions.checkState(state == STATE_CREATED);
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
codec.setCallback(this, handler);
codecStartRunnable.run();
state = STATE_STARTED;
}
@Override
public synchronized int dequeueInputBufferIndex() {
Assertions.checkState(state == STATE_STARTED);
if (isFlushing()) {
return MediaCodec.INFO_TRY_AGAIN_LATER;
} else {
......@@ -112,8 +100,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override
public synchronized int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
Assertions.checkState(state == STATE_STARTED);
if (isFlushing()) {
return MediaCodec.INFO_TRY_AGAIN_LATER;
} else {
......@@ -124,15 +110,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override
public synchronized MediaFormat getOutputFormat() {
Assertions.checkState(state == STATE_STARTED);
return mediaCodecAsyncCallback.getOutputFormat();
}
@Override
public synchronized void flush() {
Assertions.checkState(state == STATE_STARTED);
codec.flush();
++pendingFlushCount;
Util.castNonNull(handler).post(this::onFlushCompleted);
......@@ -177,8 +159,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
@VisibleForTesting
/* package */ void setOnCodecStart(Runnable onCodecStart) {
this.onCodecStart = onCodecStart;
/* package */ void setCodecStartRunnable(Runnable codecStartRunnable) {
this.codecStartRunnable = codecStartRunnable;
}
private synchronized void onFlushCompleted() {
......@@ -199,7 +181,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
mediaCodecAsyncCallback.flush();
try {
onCodecStart.run();
codecStartRunnable.run();
} catch (IllegalStateException e) {
internalException = e;
} catch (Exception e) {
......
......@@ -32,6 +32,13 @@ import android.media.MediaFormat;
/* 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
* MediaCodec#INFO_TRY_AGAIN_LATER} if no such buffer exists.
*
......
......@@ -995,13 +995,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} else if (mediaCodecOperationMode == OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD
&& Util.SDK_INT >= 23) {
codecAdapter = new DedicatedThreadAsyncMediaCodecAdapter(codec, getTrackType());
((DedicatedThreadAsyncMediaCodecAdapter) codecAdapter).start();
} else if (mediaCodecOperationMode == OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_MULTI_LOCK
&& Util.SDK_INT >= 23) {
codecAdapter = new MultiLockAsyncMediaCodecAdapter(codec, getTrackType());
((MultiLockAsyncMediaCodecAdapter) codecAdapter).start();
} else {
codecAdapter = new SynchronousMediaCodecAdapter(codec, getDequeueOutputBufferTimeoutUs());
codecAdapter = new SynchronousMediaCodecAdapter(codec);
}
TraceUtil.endSection();
......@@ -1009,7 +1007,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
configureCodec(codecInfo, codec, inputFormat, crypto, codecOperatingRate);
TraceUtil.endSection();
TraceUtil.beginSection("startCodec");
codec.start();
codecAdapter.start();
TraceUtil.endSection();
codecInitializedTimestamp = SystemClock.elapsedRealtime();
getCodecBuffers(codec);
......@@ -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,
* current {@link Format} and set of possible stream formats.
*
......
......@@ -27,7 +27,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
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.Util;
import java.util.ArrayDeque;
......@@ -94,7 +93,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final HandlerThread handlerThread;
@MonotonicNonNull private Handler handler;
private Runnable onCodecStart;
private Runnable codecStartRunnable;
/** Creates a new instance that wraps the specified {@link MediaCodec}. */
/* package */ MultiLockAsyncMediaCodecAdapter(MediaCodec codec, int trackType) {
......@@ -114,25 +113,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
codecException = null;
state = STATE_CREATED;
this.handlerThread = handlerThread;
onCodecStart = codec::start;
codecStartRunnable = codec::start;
}
/**
* 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.
*/
@Override
public void start() {
synchronized (objectStateLock) {
Assertions.checkState(state == STATE_CREATED);
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
codec.setCallback(this, handler);
codecStartRunnable.run();
state = STATE_STARTED;
}
}
......@@ -140,8 +130,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override
public int dequeueInputBufferIndex() {
synchronized (objectStateLock) {
Assertions.checkState(state == STATE_STARTED);
if (isFlushing()) {
return MediaCodec.INFO_TRY_AGAIN_LATER;
} else {
......@@ -154,8 +142,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override
public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
synchronized (objectStateLock) {
Assertions.checkState(state == STATE_STARTED);
if (isFlushing()) {
return MediaCodec.INFO_TRY_AGAIN_LATER;
} else {
......@@ -168,8 +154,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override
public MediaFormat getOutputFormat() {
synchronized (objectStateLock) {
Assertions.checkState(state == STATE_STARTED);
if (currentFormat == null) {
throw new IllegalStateException();
}
......@@ -181,8 +165,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override
public void flush() {
synchronized (objectStateLock) {
Assertions.checkState(state == STATE_STARTED);
codec.flush();
pendingFlush++;
Util.castNonNull(handler).post(this::onFlushComplete);
......@@ -200,8 +182,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
@VisibleForTesting
/* package */ void setOnCodecStart(Runnable onCodecStart) {
this.onCodecStart = onCodecStart;
/* package */ void setCodecStartRunnable(Runnable codecStartRunnable) {
this.codecStartRunnable = codecStartRunnable;
}
private int dequeueAvailableInputBufferIndex() {
......@@ -307,7 +289,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
clearAvailableOutput();
codecException = null;
try {
onCodecStart.run();
codecStartRunnable.run();
} catch (IllegalStateException e) {
codecException = e;
} catch (Exception e) {
......
......@@ -23,12 +23,16 @@ import android.media.MediaFormat;
* A {@link MediaCodecAdapter} that operates the underlying {@link MediaCodec} in synchronous mode.
*/
/* package */ final class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
private final MediaCodec codec;
private final long dequeueOutputBufferTimeoutMs;
public SynchronousMediaCodecAdapter(MediaCodec mediaCodec, long dequeueOutputBufferTimeoutMs) {
public SynchronousMediaCodecAdapter(MediaCodec mediaCodec) {
this.codec = mediaCodec;
this.dequeueOutputBufferTimeoutMs = dequeueOutputBufferTimeoutMs;
}
@Override
public void start() {
codec.start();
}
@Override
......@@ -38,7 +42,7 @@ import android.media.MediaFormat;
@Override
public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
return codec.dequeueOutputBuffer(bufferInfo, dequeueOutputBufferTimeoutMs);
return codec.dequeueOutputBuffer(bufferInfo, 0);
}
@Override
......
......@@ -773,7 +773,7 @@ public final class DownloadHelper {
}
// Initialization of array of Lists.
@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "rawtypes"})
private void onMediaPrepared() {
Assertions.checkNotNull(mediaPreparer);
Assertions.checkNotNull(mediaPreparer.mediaPeriods);
......
......@@ -165,7 +165,7 @@ public final class Requirements implements Parcelable {
private static boolean isInternetConnectivityValidated(ConnectivityManager connectivityManager) {
// 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.
// 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) {
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;
*
* @return The parsed object data.
*/
// incompatible types in argument.
@SuppressWarnings("nullness:argument.type.incompatible")
private static ObjectData parseObjectData(ParsableBitArray data) {
int objectId = data.readBits(16);
data.skipBits(4); // Skip object_version_number
......@@ -490,8 +488,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
boolean nonModifyingColorFlag = data.readBit();
data.skipBits(1); // Skip reserved.
@Nullable byte[] topFieldData = null;
@Nullable byte[] bottomFieldData = null;
byte[] topFieldData = Util.EMPTY_BYTE_ARRAY;
byte[] bottomFieldData = Util.EMPTY_BYTE_ARRAY;
if (objectCodingMethod == OBJECT_CODING_STRING) {
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 @@
*/
package com.google.android.exoplayer2.text.ttml;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.AbsoluteSizeSpan;
......@@ -27,6 +26,7 @@ import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.TypefaceSpan;
import android.text.style.UnderlineSpan;
import com.google.android.exoplayer2.text.SpanUtil;
import java.util.Map;
/**
......@@ -77,32 +77,60 @@ import java.util.Map;
builder.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (style.hasFontColor()) {
builder.setSpan(new ForegroundColorSpan(style.getFontColor()), start, end,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
SpanUtil.addOrReplaceSpan(
builder,
new ForegroundColorSpan(style.getFontColor()),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (style.hasBackgroundColor()) {
builder.setSpan(new BackgroundColorSpan(style.getBackgroundColor()), start, end,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
SpanUtil.addOrReplaceSpan(
builder,
new BackgroundColorSpan(style.getBackgroundColor()),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
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);
}
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);
}
switch (style.getFontSizeUnit()) {
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);
break;
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);
break;
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);
break;
case TtmlStyle.UNSPECIFIED:
......
......@@ -31,14 +31,19 @@ import java.util.regex.Pattern;
*/
/* 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_FONT_FAMILY = "font-family";
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 VALUE_BOLD = "bold";
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 VALUE_ITALIC = "italic";
......@@ -182,6 +187,8 @@ import java.util.regex.Pattern;
style.setFontColor(ColorParser.parseCssColor(value));
} else if (PROPERTY_BGCOLOR.equals(property)) {
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)) {
if (VALUE_UNDERLINE.equals(value)) {
style.setUnderline(true);
......
......@@ -95,6 +95,7 @@ public final class WebvttCssStyle {
@FontSizeUnit private int fontSizeUnit;
private float fontSize;
@Nullable private Layout.Alignment textAlign;
private boolean combineUpright;
// Calling reset() is forbidden because `this` isn't initialized. This can be safely suppressed
// because reset() only assigns fields, it doesn't read any.
......@@ -118,6 +119,7 @@ public final class WebvttCssStyle {
italic = UNSPECIFIED;
fontSizeUnit = UNSPECIFIED;
textAlign = null;
combineUpright = false;
}
public void setTargetId(String targetId) {
......@@ -287,35 +289,12 @@ public final class WebvttCssStyle {
return fontSize;
}
public void cascadeFrom(WebvttCssStyle style) {
if (style.hasFontColor) {
setFontColor(style.fontColor);
}
if (style.bold != UNSPECIFIED) {
bold = style.bold;
}
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);
}
public void setCombineUpright(boolean enabled) {
this.combineUpright = enabled;
}
public boolean getCombineUpright() {
return combineUpright;
}
private static int updateScoreForMatch(
......
......@@ -15,11 +15,11 @@
*/
package com.google.android.exoplayer2.text.webvtt;
import static com.google.android.exoplayer2.text.SpanUtil.addOrReplaceSpan;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.graphics.Typeface;
import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.SpannedString;
......@@ -37,6 +37,8 @@ import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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.Log;
import com.google.android.exoplayer2.util.ParsableByteArray;
......@@ -120,11 +122,13 @@ public final class WebvttCueParser {
private static final String ENTITY_NON_BREAK_SPACE = "nbsp";
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_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_CLASS = "c";
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_ITALIC = Typeface.ITALIC;
......@@ -197,6 +201,7 @@ public final class WebvttCueParser {
ArrayDeque<StartTag> startTagStack = new ArrayDeque<>();
List<StyleMatch> scratchStyleMatches = new ArrayList<>();
int pos = 0;
List<Element> nestedElements = new ArrayList<>();
while (pos < markup.length()) {
char curr = markup.charAt(pos);
switch (curr) {
......@@ -225,8 +230,14 @@ public final class WebvttCueParser {
break;
}
startTag = startTagStack.pop();
applySpansForTag(id, startTag, spannedText, styles, scratchStyleMatches);
} while(!startTag.name.equals(tagName));
applySpansForTag(
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) {
startTagStack.push(StartTag.buildStartTag(fullTagExpression, spannedText.length()));
}
......@@ -256,9 +267,15 @@ public final class WebvttCueParser {
}
// apply unclosed tags
while (!startTagStack.isEmpty()) {
applySpansForTag(id, startTagStack.pop(), spannedText, styles, scratchStyleMatches);
}
applySpansForTag(id, StartTag.buildWholeCueVirtualTag(), spannedText, styles,
applySpansForTag(
id, startTagStack.pop(), nestedElements, spannedText, styles, scratchStyleMatches);
}
applySpansForTag(
id,
StartTag.buildWholeCueVirtualTag(),
/* nestedElements= */ Collections.emptyList(),
spannedText,
styles,
scratchStyleMatches);
return SpannedString.valueOf(spannedText);
}
......@@ -442,6 +459,8 @@ public final class WebvttCueParser {
case TAG_CLASS:
case TAG_ITALIC:
case TAG_LANG:
case TAG_RUBY:
case TAG_RUBY_TEXT:
case TAG_UNDERLINE:
case TAG_VOICE:
return true;
......@@ -453,6 +472,7 @@ public final class WebvttCueParser {
private static void applySpansForTag(
@Nullable String cueId,
StartTag startTag,
List<Element> nestedElements,
SpannableStringBuilder text,
List<WebvttCssStyle> styles,
List<StyleMatch> scratchStyleMatches) {
......@@ -467,6 +487,29 @@ public final class WebvttCueParser {
text.setSpan(new StyleSpan(STYLE_ITALIC), start, end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
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:
text.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
......@@ -492,7 +535,11 @@ public final class WebvttCueParser {
return;
}
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);
}
if (style.isLinethrough()) {
......@@ -502,39 +549,71 @@ public final class WebvttCueParser {
spannedText.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (style.hasFontColor()) {
spannedText.setSpan(new ForegroundColorSpan(style.getFontColor()), start, end,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
addOrReplaceSpan(
spannedText,
new ForegroundColorSpan(style.getFontColor()),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (style.hasBackgroundColor()) {
spannedText.setSpan(new BackgroundColorSpan(style.getBackgroundColor()), start, end,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
addOrReplaceSpan(
spannedText,
new BackgroundColorSpan(style.getBackgroundColor()),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (style.getFontFamily() != null) {
spannedText.setSpan(new TypefaceSpan(style.getFontFamily()), start, end,
addOrReplaceSpan(
spannedText,
new TypefaceSpan(style.getFontFamily()),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
Layout.Alignment textAlign = style.getTextAlign();
if (textAlign != null) {
spannedText.setSpan(
new AlignmentSpan.Standard(textAlign), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
addOrReplaceSpan(
spannedText,
new AlignmentSpan.Standard(textAlign),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
switch (style.getFontSizeUnit()) {
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);
break;
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);
break;
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);
break;
case WebvttCssStyle.UNSPECIFIED:
// Do nothing.
break;
}
if (style.getCombineUpright()) {
spannedText.setSpan(
new HorizontalTextInVerticalContextSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
/**
......@@ -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
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_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(
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;
}
......
......@@ -1355,6 +1355,7 @@ public final class Util {
public static boolean isEncodingLinearPcm(@C.Encoding int encoding) {
return encoding == C.ENCODING_PCM_8BIT
|| encoding == C.ENCODING_PCM_16BIT
|| encoding == C.ENCODING_PCM_16BIT_BIG_ENDIAN
|| encoding == C.ENCODING_PCM_24BIT
|| encoding == C.ENCODING_PCM_32BIT
|| encoding == C.ENCODING_PCM_FLOAT;
......@@ -1423,14 +1424,13 @@ public final class Util {
case C.ENCODING_PCM_8BIT:
return channelCount;
case C.ENCODING_PCM_16BIT:
case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
return channelCount * 2;
case C.ENCODING_PCM_24BIT:
return channelCount * 3;
case C.ENCODING_PCM_32BIT:
case C.ENCODING_PCM_FLOAT:
return channelCount * 4;
case C.ENCODING_PCM_A_LAW:
case C.ENCODING_PCM_MU_LAW:
case C.ENCODING_INVALID:
case Format.NO_VALUE:
default:
......@@ -2126,6 +2126,8 @@ public final class Util {
return C.NETWORK_TYPE_3G;
case TelephonyManager.NETWORK_TYPE_LTE:
return C.NETWORK_TYPE_4G;
case TelephonyManager.NETWORK_TYPE_NR:
return C.NETWORK_TYPE_5G;
case TelephonyManager.NETWORK_TYPE_IWLAN:
return C.NETWORK_TYPE_WIFI;
case TelephonyManager.NETWORK_TYPE_GSM:
......
......@@ -31,13 +31,13 @@ track 1:
sample 0:
time = 0
flags = 1
data = length 59, hash A0217393
data = length 59, hash 1AD38625
sample 1:
time = 2345000
flags = 1
data = length 95, hash 4904F2
data = length 95, hash F331C282
sample 2:
time = 4567000
flags = 1
data = length 59, hash EFAB6D8A
data = length 59, hash F8CD7C60
tracksEnded = true
......@@ -31,13 +31,13 @@ track 1:
sample 0:
time = 0
flags = 1
data = length 59, hash A0217393
data = length 59, hash 1AD38625
sample 1:
time = 2345000
flags = 1
data = length 95, hash 4904F2
data = length 95, hash F331C282
sample 2:
time = 4567000
flags = 1
data = length 59, hash EFAB6D8A
data = length 59, hash F8CD7C60
tracksEnded = true
......@@ -31,13 +31,13 @@ track 1:
sample 0:
time = 0
flags = 1
data = length 59, hash A0217393
data = length 59, hash 1AD38625
sample 1:
time = 2345000
flags = 1
data = length 95, hash 4904F2
data = length 95, hash F331C282
sample 2:
time = 4567000
flags = 1
data = length 59, hash EFAB6D8A
data = length 59, hash F8CD7C60
tracksEnded = true
......@@ -31,13 +31,13 @@ track 1:
sample 0:
time = 0
flags = 1
data = length 59, hash A0217393
data = length 59, hash 1AD38625
sample 1:
time = 2345000
flags = 1
data = length 95, hash 4904F2
data = length 95, hash F331C282
sample 2:
time = 4567000
flags = 1
data = length 59, hash EFAB6D8A
data = length 59, hash F8CD7C60
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
STYLE
::cue(\n#id ){text-decoration:underline;}
::cue(#id ){text-decoration:underline;}
STYLE
::cue(#id.class1.class2 ){ color: violet;}
......@@ -20,7 +20,7 @@ STYLE
id
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
00:02.000 --> 00:02.001
......@@ -31,10 +31,10 @@ _id
This <lang.class.another>should be courier and bold.
00:04.000 --> 00:04.001
This <v Strider Trancos> shouldn't be bold.</v>
This <v.class.clazz Strider Trancos> should be bold.
This <v Strider Trancos>shouldn't be bold.</v>
This <v.class.clazz Strider Trancos>should be bold.
anId
00:05.000 --> 00:05.001
This is <v.class1.class3.class2 Pipo> specific </v>
<v.class1.class3.class2 Robert> But this is more italic</v>
This is <v.class1.class3.class2 Pipo>specific</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.
NOTE Wrong position provided. It should be provided as
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.
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.
NOTE Line as absolute negative number and without line alignment.
......@@ -23,10 +23,10 @@ This is the fourth subtitle.
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.
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.
......@@ -15,13 +15,8 @@
*/
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 com.google.android.exoplayer2.extractor.Extractor;
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.runner.RunWith;
......@@ -66,9 +61,7 @@ public class FlacExtractorTest {
@Test
public void testOneMetadataBlock() throws Exception {
// Don't simulate IO errors as it is too slow when using the binary search seek map (see
// [Internal: b/145994869]).
assertBehaviorWithoutSimulatingIOErrors("flac/bear_one_metadata_block.flac");
ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear_one_metadata_block.flac");
}
@Test
......@@ -85,61 +78,4 @@ public class FlacExtractorTest {
public void testUncommonSampleRate() throws Exception {
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 {
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) {
return () -> new FragmentedMp4Extractor(0, null, null, null, closedCaptionFormats);
}
......
......@@ -42,4 +42,9 @@ public final class Mp4ExtractorTest {
public void testMp4SampleWithMdatTooLong() throws Exception {
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;
import static com.google.common.truth.Truth.assertThat;
import android.util.SparseArray;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
......@@ -172,6 +173,7 @@ public final class TsExtractorTest {
}
}
@Nullable
@Override
public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) {
if (provideCustomEsReader && streamType == 3) {
......
......@@ -28,4 +28,9 @@ public final class WavExtractorTest {
public void testSample() throws Exception {
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;
import static com.google.android.exoplayer2.mediacodec.MediaCodecTestUtils.areEqual;
import static com.google.android.exoplayer2.mediacodec.MediaCodecTestUtils.waitUntilAllEventsAreExecuted;
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.MediaFormat;
......@@ -29,7 +29,7 @@ import android.os.Looper;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
......@@ -45,27 +45,32 @@ public class AsynchronousMediaCodecAdapterTest {
private MediaCodec.BufferInfo bufferInfo;
@Before
public void setup() throws IOException {
public void setUp() throws IOException {
handlerThread = new HandlerThread("TestHandlerThread");
handlerThread.start();
looper = handlerThread.getLooper();
codec = MediaCodec.createByCodecName("h264");
adapter = new AsynchronousMediaCodecAdapter(codec, looper);
adapter.setCodecStartRunnable(() -> {});
bufferInfo = new MediaCodec.BufferInfo();
}
@After
public void tearDown() {
adapter.shutdown();
handlerThread.quit();
}
@Test
public void dequeueInputBufferIndex_withoutInputBuffer_returnsTryAgainLater() {
adapter.start();
assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
}
@Test
public void dequeueInputBufferIndex_withInputBuffer_returnsInputBuffer() {
adapter.start();
adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 0);
assertThat(adapter.dequeueInputBufferIndex()).isEqualTo(0);
......@@ -73,6 +78,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test
public void dequeueInputBufferIndex_whileFlushing_returnsTryAgainLater() {
adapter.start();
adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 0);
adapter.flush();
adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 1);
......@@ -83,9 +89,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test
public void dequeueInputBufferIndex_afterFlushCompletes_returnsNextInputBuffer()
throws InterruptedException {
// Disable calling codec.start() after flush() completes to avoid receiving buffers from the
// shadow codec impl
adapter.setOnCodecStart(() -> {});
adapter.start();
Handler handler = new Handler(looper);
handler.post(
() -> adapter.getMediaCodecCallback().onInputBufferAvailable(codec, /* index=*/ 0));
......@@ -100,28 +104,35 @@ public class AsynchronousMediaCodecAdapterTest {
@Test
public void dequeueInputBufferIndex_afterFlushCompletesWithError_throwsException()
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();
assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue();
try {
adapter.dequeueInputBufferIndex();
fail();
} catch (IllegalStateException expected) {
}
assertThrows(
IllegalStateException.class,
() -> {
adapter.dequeueInputBufferIndex();
});
}
@Test
public void dequeueOutputBufferIndex_withoutOutputBuffer_returnsTryAgainLater() {
adapter.start();
assertThat(adapter.dequeueOutputBufferIndex(bufferInfo))
.isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
}
@Test
public void dequeueOutputBufferIndex_withOutputBuffer_returnsOutputBuffer() {
adapter.start();
MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo();
outBufferInfo.presentationTimeUs = 10;
adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 0, outBufferInfo);
......@@ -132,6 +143,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test
public void dequeueOutputBufferIndex_whileFlushing_returnsTryAgainLater() {
adapter.start();
adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 0, bufferInfo);
adapter.flush();
adapter.getMediaCodecCallback().onOutputBufferAvailable(codec, /* index=*/ 1, bufferInfo);
......@@ -143,9 +155,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test
public void dequeueOutputBufferIndex_afterFlushCompletes_returnsNextOutputBuffer()
throws InterruptedException {
// Disable calling codec.start() after flush() completes to avoid receiving buffers from the
// shadow codec impl
adapter.setOnCodecStart(() -> {});
adapter.start();
Handler handler = new Handler(looper);
MediaCodec.BufferInfo info0 = new MediaCodec.BufferInfo();
handler.post(
......@@ -164,31 +174,23 @@ public class AsynchronousMediaCodecAdapterTest {
@Test
public void dequeueOutputBufferIndex_afterFlushCompletesWithError_throwsException()
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();
assertThat(waitUntilAllEventsAreExecuted(looper, /* time= */ 5, TimeUnit.SECONDS)).isTrue();
try {
adapter.dequeueOutputBufferIndex(bufferInfo);
fail();
} catch (IllegalStateException expected) {
}
}
@Test
public void getOutputFormat_withoutFormat_throwsException() {
try {
adapter.getOutputFormat();
fail();
} catch (IllegalStateException expected) {
}
assertThrows(IllegalStateException.class, () -> adapter.dequeueOutputBufferIndex(bufferInfo));
}
@Test
public void getOutputFormat_withMultipleFormats_returnsFormatsInCorrectOrder() {
adapter.start();
MediaFormat[] formats = new MediaFormat[10];
MediaCodec.Callback mediaCodecCallback = adapter.getMediaCodecCallback();
for (int i = 0; i < formats.length; i++) {
......@@ -212,6 +214,7 @@ public class AsynchronousMediaCodecAdapterTest {
@Test
public void getOutputFormat_afterFlush_returnsPreviousFormat() throws InterruptedException {
adapter.start();
MediaFormat format = new MediaFormat();
adapter.getMediaCodecCallback().onOutputFormatChanged(codec, format);
adapter.dequeueOutputBufferIndex(bufferInfo);
......@@ -223,13 +226,13 @@ public class AsynchronousMediaCodecAdapterTest {
@Test
public void shutdown_withPendingFlush_cancelsFlush() throws InterruptedException {
AtomicBoolean onCodecStartCalled = new AtomicBoolean(false);
Runnable onCodecStart = () -> onCodecStartCalled.set(true);
adapter.setOnCodecStart(onCodecStart);
AtomicInteger onCodecStartCalled = new AtomicInteger(0);
adapter.setCodecStartRunnable(() -> onCodecStartCalled.incrementAndGet());
adapter.start();
adapter.flush();
adapter.shutdown();
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