Commit 402c985a by Julian Cable

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

parents d8746c3d 11c16d83
Showing with 1723 additions and 488 deletions
# Release notes #
### r2.1.1 ###
### r2.2.0 ###
* Demo app: Automatic recovery from BehindLiveWindowException, plus improved
handling of pausing and resuming live streams
([#2344](https://github.com/google/ExoPlayer/issues/2344)).
* AndroidTV: Added Support for tunneled video playback
([#1688](https://github.com/google/ExoPlayer/issues/1688)).
* DRM: Renamed StreamingDrmSessionManager to DefaultDrmSessionManager and
added support for using offline licenses
([#876](https://github.com/google/ExoPlayer/issues/876)).
* DRM: Introduce OfflineLicenseHelper to help with offline license acquisition,
renewal and release.
* UI: Updated player control assets. Added vector drawables for use on API level
21 and above.
* UI: Made player control seek bar work correctly with key events if focusable
([#2278](https://github.com/google/ExoPlayer/issues/2278)).
* HLS: Improved support for streams that use EXT-X-DISCONTINUITY without
EXT-X-DISCONTINUITY-SEQUENCE
([#1789](https://github.com/google/ExoPlayer/issues/1789)).
* HLS: Support for EXT-X-START tag
([#1544](https://github.com/google/ExoPlayer/issues/1544)).
* HLS: Check #EXTM3U header is present when parsing the playlist. Fail
gracefully if not ([#2301](https://github.com/google/ExoPlayer/issues/2301)).
* HLS: Fix memory leak
([#2319](https://github.com/google/ExoPlayer/issues/2319)).
* HLS: Fix non-seamless first adaptation where master playlist omits resolution
tags ([#2096](https://github.com/google/ExoPlayer/issues/2096)).
* HLS: Fix handling of WebVTT subtitle renditions with non-standard segment file
extensions ([#2025](https://github.com/google/ExoPlayer/issues/2025) and
[#2355](https://github.com/google/ExoPlayer/issues/2355)).
* HLS: Better handle inconsistent HLS playlist update
([#2249](https://github.com/google/ExoPlayer/issues/2249)).
* DASH: Don't overflow when dealing with large segment numbers
([#2311](https://github.com/google/ExoPlayer/issues/2311)).
* DASH: Fix propagation of language from the manifest
([#2335](https://github.com/google/ExoPlayer/issues/2335)).
* SmoothStreaming: Work around "Offset to sample data was negative" failures
([#2292](https://github.com/google/ExoPlayer/issues/2292),
[#2101](https://github.com/google/ExoPlayer/issues/2101) and
[#1152](https://github.com/google/ExoPlayer/issues/1152)).
* MP3/ID3: Added support for parsing Chapter and URL link frames
([#2316](https://github.com/google/ExoPlayer/issues/2316)).
* MP3/ID3: Handle ID3 frames that end with empty text field
([#2309](https://github.com/google/ExoPlayer/issues/2309)).
* Added ClippingMediaSource for playing clipped portions of media
([#1988](https://github.com/google/ExoPlayer/issues/1988)).
* Added convenience methods to query whether the current window is dynamic and
seekable ([#2320](https://github.com/google/ExoPlayer/issues/2320)).
* Support setting of default headers on HttpDataSource.Factory implementations
([#2166](https://github.com/google/ExoPlayer/issues/2166)).
* Fixed cache failures when using an encrypted cache content index.
* Fix visual artifacts when switching output surface
([#2093](https://github.com/google/ExoPlayer/issues/2093)).
* Fix gradle + proguard configurations.
* Fix player position when replacing the MediaSource
([#2369](https://github.com/google/ExoPlayer/issues/2369)).
* Misc bug fixes, including
[#2330](https://github.com/google/ExoPlayer/issues/2330),
[#2269](https://github.com/google/ExoPlayer/issues/2269),
[#2252](https://github.com/google/ExoPlayer/issues/2252),
[#2264](https://github.com/google/ExoPlayer/issues/2264) and
[#2290](https://github.com/google/ExoPlayer/issues/2290).
Bugfix release only. Users of r2.1.0 and r2.0.x should proactively update to
this version.
### r2.1.1 ###
* Fix some subtitle types (e.g. WebVTT) being displayed out of sync
([#2208](https://github.com/google/ExoPlayer/issues/2208)).
......@@ -52,9 +112,9 @@ this version.
* Improved flexibility of SimpleExoPlayer
([#2102](https://github.com/google/ExoPlayer/issues/2102)).
* Fix issue where only the audio of a video would play due to capability
detection issues ([#2007](https://github.com/google/ExoPlayer/issues/2007))
([#2034](https://github.com/google/ExoPlayer/issues/2034))
([#2157](https://github.com/google/ExoPlayer/issues/2157)).
detection issues ([#2007](https://github.com/google/ExoPlayer/issues/2007),
[#2034](https://github.com/google/ExoPlayer/issues/2034) and
[#2157](https://github.com/google/ExoPlayer/issues/2157)).
* Fix issues that could cause ExtractorMediaSource based playbacks to get stuck
buffering ([#1962](https://github.com/google/ExoPlayer/issues/1962)).
* Correctly set SimpleExoPlayerView surface aspect ratio when an active player
......@@ -186,6 +246,14 @@ in all V2 releases. This cannot be assumed for changes in r1.5.12 and later,
however it can be assumed that all such changes are included in the most recent
V2 release.
### r1.5.14 ###
* Fixed cache failures when using an encrypted cache content index.
* SmoothStreaming: Work around "Offset to sample data was negative" failures
([#2292](https://github.com/google/ExoPlayer/issues/2292),
[#2101](https://github.com/google/ExoPlayer/issues/2101) and
[#1152](https://github.com/google/ExoPlayer/issues/1152)).
### r1.5.13 ###
* Improvements to the upstream cache package.
......
......@@ -29,13 +29,13 @@ allprojects {
jcenter()
}
project.ext {
compileSdkVersion=24
targetSdkVersion=24
buildToolsVersion='23.0.3'
compileSdkVersion=25
targetSdkVersion=25
buildToolsVersion='25'
releaseRepoName = 'exoplayer'
releaseUserOrg = 'google'
releaseGroupId = 'com.google.android.exoplayer'
releaseVersion = 'r2.1.1'
releaseVersion = 'r2.2.0'
releaseWebsite = 'https://github.com/google/ExoPlayer'
}
}
......@@ -16,8 +16,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.exoplayer2.demo"
android:versionCode="2101"
android:versionName="2.1.1">
android:versionCode="2200"
android:versionName="2.2.0">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
......@@ -27,7 +27,7 @@
<application
android:label="@string/application_name"
android:icon="@drawable/ic_launcher"
android:icon="@mipmap/ic_launcher"
android:banner="@drawable/ic_banner"
android:largeHeap="true"
android:allowBackup="false"
......
......@@ -101,9 +101,6 @@ import java.util.Locale;
@Override
public void onTimelineChanged(Timeline timeline, Object manifest) {
if (timeline == null) {
return;
}
int periodCount = timeline.getPeriodCount();
int windowCount = timeline.getWindowCount();
Log.d(TAG, "sourceInfo [periodCount=" + periodCount + ", windowCount=" + windowCount);
......
......@@ -70,8 +70,6 @@ import com.google.android.exoplayer2.util.Util;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
......@@ -239,19 +237,9 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
if (drmSchemeUuid != null) {
String drmLicenseUrl = intent.getStringExtra(DRM_LICENSE_URL);
String[] keyRequestPropertiesArray = intent.getStringArrayExtra(DRM_KEY_REQUEST_PROPERTIES);
Map<String, String> keyRequestProperties;
if (keyRequestPropertiesArray == null || keyRequestPropertiesArray.length < 2) {
keyRequestProperties = null;
} else {
keyRequestProperties = new HashMap<>();
for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) {
keyRequestProperties.put(keyRequestPropertiesArray[i],
keyRequestPropertiesArray[i + 1]);
}
}
try {
drmSessionManager = buildDrmSessionManager(drmSchemeUuid, drmLicenseUrl,
keyRequestProperties);
keyRequestPropertiesArray);
} catch (UnsupportedDrmException e) {
int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported
: (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
......@@ -317,8 +305,11 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
}
MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0]
: new ConcatenatingMediaSource(mediaSources);
player.seekTo(resumeWindow, resumePosition);
player.prepare(mediaSource, false, false);
boolean haveResumePosition = resumeWindow != C.INDEX_UNSET;
if (haveResumePosition) {
player.seekTo(resumeWindow, resumePosition);
}
player.prepare(mediaSource, !haveResumePosition, false);
playerNeedsSource = false;
updateButtonVisibilities();
}
......@@ -346,12 +337,18 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
}
private DrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManager(UUID uuid,
String licenseUrl, Map<String, String> keyRequestProperties) throws UnsupportedDrmException {
String licenseUrl, String[] keyRequestPropertiesArray) throws UnsupportedDrmException {
if (Util.SDK_INT < 18) {
return null;
}
HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl,
buildHttpDataSourceFactory(false), keyRequestProperties);
buildHttpDataSourceFactory(false));
if (keyRequestPropertiesArray != null) {
for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) {
drmCallback.setKeyRequestProperty(keyRequestPropertiesArray[i],
keyRequestPropertiesArray[i + 1]);
}
}
return new DefaultDrmSessionManager<>(uuid,
FrameworkMediaDrm.newInstance(uuid), drmCallback, null, mainHandler, eventLogger);
}
......@@ -377,7 +374,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
}
private void clearResumePosition() {
resumeWindow = 0;
resumeWindow = C.INDEX_UNSET;
resumePosition = C.TIME_UNSET;
}
......@@ -422,7 +419,12 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
@Override
public void onPositionDiscontinuity() {
// Do nothing.
if (playerNeedsSource) {
// This will only occur if the user has performed a seek whilst in the error state. Update the
// resume position so that if the user then retries, playback will resume from the position to
// which they seeked.
updateResumePosition();
}
}
@Override
......@@ -461,11 +463,12 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
playerNeedsSource = true;
if (isBehindLiveWindow(e)) {
clearResumePosition();
initializePlayer();
} else {
updateResumePosition();
updateButtonVisibilities();
showControls();
}
updateButtonVisibilities();
showControls();
}
@Override
......
......@@ -301,15 +301,18 @@ import java.util.Locale;
private static String buildTrackName(Format format) {
String trackName;
if (MimeTypes.isVideo(format.sampleMimeType)) {
trackName = joinWithSeparator(joinWithSeparator(buildResolutionString(format),
buildBitrateString(format)), buildTrackIdString(format));
trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(
buildResolutionString(format), buildBitrateString(format)), buildTrackIdString(format)),
buildSampleMimeTypeString(format));
} else if (MimeTypes.isAudio(format.sampleMimeType)) {
trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(buildLanguageString(format),
buildAudioPropertyString(format)), buildBitrateString(format)),
buildTrackIdString(format));
trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(joinWithSeparator(
buildLanguageString(format), buildAudioPropertyString(format)),
buildBitrateString(format)), buildTrackIdString(format)),
buildSampleMimeTypeString(format));
} else {
trackName = joinWithSeparator(joinWithSeparator(buildLanguageString(format),
buildBitrateString(format)), buildTrackIdString(format));
trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(buildLanguageString(format),
buildBitrateString(format)), buildTrackIdString(format)),
buildSampleMimeTypeString(format));
}
return trackName.length() == 0 ? "unknown" : trackName;
}
......@@ -342,4 +345,8 @@ import java.util.Locale;
return format.id == null ? "" : ("id:" + format.id);
}
private static String buildSampleMimeTypeString(Format format) {
return format.sampleMimeType == null ? "" : format.sampleMimeType;
}
}

8.46 KB | W: | H:

6.72 KB | W: | H:

demo/src/main/res/drawable-xhdpi/ic_banner.png
demo/src/main/res/drawable-xhdpi/ic_banner.png
demo/src/main/res/drawable-xhdpi/ic_banner.png
demo/src/main/res/drawable-xhdpi/ic_banner.png
  • 2-up
  • Swipe
  • Onion skin
......@@ -16,8 +16,7 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- The user visible name of the application. [CHAR LIMIT=20] -->
<string name="application_name">ExoPlayer2 Demo</string>
<string name="application_name">ExoPlayer</string>
<string name="video">Video</string>
......
......@@ -63,6 +63,7 @@ git clone git://source.ffmpeg.org/ffmpeg ffmpeg && cd ffmpeg && \
--enable-decoder=vorbis \
--enable-decoder=opus \
--enable-decoder=flac \
--enable-decoder=alac \
&& \
make -j4 && \
make install-libs
......
......@@ -19,8 +19,8 @@ import android.os.Handler;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.AudioCapabilities;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.BufferProcessor;
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.MimeTypes;
......@@ -43,21 +43,12 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer {
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
*/
public FfmpegAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener) {
super(eventHandler, eventListener);
}
/**
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param audioCapabilities The audio capabilities for playback on this device. May be null if the
* default capabilities (no encoded audio passthrough support) should be assumed.
* @param bufferProcessors Optional {@link BufferProcessor}s which will process PCM audio buffers
* before they are output.
*/
public FfmpegAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
AudioCapabilities audioCapabilities) {
super(eventHandler, eventListener, audioCapabilities);
BufferProcessor... bufferProcessors) {
super(eventHandler, eventListener, bufferProcessors);
}
@Override
......
......@@ -19,6 +19,7 @@ import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.decoder.SimpleDecoder;
import com.google.android.exoplayer2.decoder.SimpleOutputBuffer;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.nio.ByteBuffer;
import java.util.List;
......@@ -88,6 +89,13 @@ import java.util.List;
if (!hasOutputFormat) {
channelCount = ffmpegGetChannelCount(nativeContext);
sampleRate = ffmpegGetSampleRate(nativeContext);
if (sampleRate == 0 && "alac".equals(codecName)) {
// ALAC decoder did not set the sample rate in earlier versions of FFMPEG.
// See https://trac.ffmpeg.org/ticket/6096
ParsableByteArray parsableExtraData = new ParsableByteArray(extraData);
parsableExtraData.setPosition(extraData.length - 4);
sampleRate = parsableExtraData.readUnsignedIntToInt();
}
hasOutputFormat = true;
}
outputBuffer.data.position(0);
......@@ -123,6 +131,7 @@ import java.util.List;
private static byte[] getExtraData(String mimeType, List<byte[]> initializationData) {
switch (mimeType) {
case MimeTypes.AUDIO_AAC:
case MimeTypes.AUDIO_ALAC:
case MimeTypes.AUDIO_OPUS:
return initializationData.get(0);
case MimeTypes.AUDIO_VORBIS:
......
......@@ -92,6 +92,8 @@ public final class FfmpegLibrary {
return "amrwb";
case MimeTypes.AUDIO_FLAC:
return "flac";
case MimeTypes.AUDIO_ALAC:
return "alac";
default:
return null;
}
......
......@@ -5,7 +5,10 @@
native <methods>;
}
# Some members of this class are being accessed from native methods. Keep them unobfuscated.
# Some members of these classes are being accessed from native methods. Keep them unobfuscated.
-keep class com.google.android.exoplayer2.ext.flac.FlacDecoderJni {
*;
}
-keep class com.google.android.exoplayer2.util.FlacStreamInfo {
*;
}
......@@ -67,7 +67,7 @@ public final class FlacExtractor implements Extractor {
@Override
public void init(ExtractorOutput output) {
extractorOutput = output;
trackOutput = extractorOutput.track(0);
trackOutput = extractorOutput.track(0, C.TRACK_TYPE_AUDIO);
extractorOutput.endTracks();
try {
decoderJni = new FlacDecoderJni();
......
......@@ -17,8 +17,8 @@ package com.google.android.exoplayer2.ext.flac;
import android.os.Handler;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.AudioCapabilities;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.BufferProcessor;
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.MimeTypes;
......@@ -38,21 +38,12 @@ public class LibflacAudioRenderer extends SimpleDecoderAudioRenderer {
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
*/
public LibflacAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener) {
super(eventHandler, eventListener);
}
/**
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param audioCapabilities The audio capabilities for playback on this device. May be null if the
* default capabilities (no encoded audio passthrough support) should be assumed.
* @param bufferProcessors Optional {@link BufferProcessor}s which will process PCM audio buffers
* before they are output.
*/
public LibflacAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
AudioCapabilities audioCapabilities) {
super(eventHandler, eventListener, audioCapabilities);
BufferProcessor... bufferProcessors) {
super(eventHandler, eventListener, bufferProcessors);
}
@Override
......
......@@ -31,7 +31,7 @@ LOCAL_C_INCLUDES := \
LOCAL_SRC_FILES := $(FLAC_SOURCES)
LOCAL_CFLAGS += '-DVERSION="1.3.1"' -DFLAC__NO_MD5 -DFLAC__INTEGER_ONLY_LIBRARY -DFLAC__NO_ASM
LOCAL_CFLAGS += -D_REENTRANT -DPIC -DU_COMMON_IMPLEMENTATION -fPIC
LOCAL_CFLAGS += -D_REENTRANT -DPIC -DU_COMMON_IMPLEMENTATION -fPIC -DHAVE_SYS_PARAM_H
LOCAL_CFLAGS += -O3 -funroll-loops -finline-functions
LOCAL_LDLIBS := -llog -lz -lm
......
......@@ -453,7 +453,8 @@ int64_t FLACParser::getSeekPosition(int64_t timeUs) {
}
FLAC__StreamMetadata_SeekPoint* points = mSeekTable->points;
for (unsigned i = mSeekTable->num_points - 1; i >= 0; i--) {
for (unsigned i = mSeekTable->num_points; i > 0; ) {
i--;
if (points[i].sample_number <= sample) {
return firstFrameOffset + points[i].stream_offset;
}
......
......@@ -17,8 +17,8 @@ package com.google.android.exoplayer2.ext.opus;
import android.os.Handler;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.AudioCapabilities;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.BufferProcessor;
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
......@@ -40,35 +40,26 @@ public final class LibopusAudioRenderer extends SimpleDecoderAudioRenderer {
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
*/
public LibopusAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener) {
super(eventHandler, eventListener);
}
/**
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param audioCapabilities The audio capabilities for playback on this device. May be null if the
* default capabilities (no encoded audio passthrough support) should be assumed.
* @param bufferProcessors Optional {@link BufferProcessor}s which will process PCM audio buffers
* before they are output.
*/
public LibopusAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
AudioCapabilities audioCapabilities) {
super(eventHandler, eventListener, audioCapabilities);
BufferProcessor... bufferProcessors) {
super(eventHandler, eventListener, bufferProcessors);
}
/**
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param audioCapabilities The audio capabilities for playback on this device. May be null if the
* default capabilities (no encoded audio passthrough support) should be assumed.
* @param bufferProcessors Optional {@link BufferProcessor}s which will process PCM audio
* buffers before they are output.
*/
public LibopusAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
AudioCapabilities audioCapabilities, DrmSessionManager<ExoMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys) {
super(eventHandler, eventListener, audioCapabilities, drmSessionManager,
playClearSamplesWithoutKeys);
DrmSessionManager<ExoMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys,
BufferProcessor... bufferProcessors) {
super(eventHandler, eventListener, null, drmSessionManager, playClearSamplesWithoutKeys,
bufferProcessors);
}
@Override
......
......@@ -213,7 +213,7 @@ import java.util.List;
SimpleOutputBuffer outputBuffer, int sampleRate);
private native int opusSecureDecode(long decoder, long timeUs, ByteBuffer inputBuffer,
int inputSize, SimpleOutputBuffer outputBuffer, int sampleRate,
ExoMediaCrypto wvCrypto, int inputMode, byte[] key, byte[] iv,
ExoMediaCrypto mediaCrypto, int inputMode, byte[] key, byte[] iv,
int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData);
private native void opusClose(long decoder);
private native void opusReset(long decoder);
......
......@@ -141,7 +141,7 @@ import java.nio.ByteBuffer;
private native long vpxClose(long context);
private native long vpxDecode(long context, ByteBuffer encoded, int length);
private native long vpxSecureDecode(long context, ByteBuffer encoded, int length,
ExoMediaCrypto wvCrypto, int inputMode, byte[] key, byte[] iv,
ExoMediaCrypto mediaCrypto, int inputMode, byte[] key, byte[] iv,
int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData);
private native int vpxGetFrame(long context, VpxOutputBuffer outputBuffer);
private native int vpxGetErrorCode(long context);
......
seekMap:
isSeekable = false
duration = UNSET TIME
getPosition(0) = 0
numberOfTracks = 3
track 0:
format:
bitrate = -1
id = 1
containerMimeType = null
sampleMimeType = video/avc
maxInputSize = -1
width = 1080
height = 720
frameRate = -1.0
rotationDegrees = 0
pixelWidthHeightRatio = 1.0
channelCount = -1
sampleRate = -1
pcmEncoding = -1
encoderDelay = -1
encoderPadding = -1
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = null
drmInitData = -
initializationData:
data = length 29, hash 4746B5D9
data = length 10, hash 7A0D0F2B
sample count = 30
sample 0:
time = 66000
flags = 1
data = length 38070, hash B58E1AEE
sample 1:
time = 199000
flags = 0
data = length 8340, hash 8AC449FF
sample 2:
time = 132000
flags = 0
data = length 1295, hash C0DA5090
sample 3:
time = 100000
flags = 0
data = length 469, hash D6E0A200
sample 4:
time = 166000
flags = 0
data = length 564, hash E5F56C5B
sample 5:
time = 332000
flags = 0
data = length 6075, hash 8756E49E
sample 6:
time = 266000
flags = 0
data = length 847, hash DCC2B618
sample 7:
time = 233000
flags = 0
data = length 455, hash B9CCE047
sample 8:
time = 299000
flags = 0
data = length 467, hash 69806D94
sample 9:
time = 466000
flags = 0
data = length 4549, hash 3944F501
sample 10:
time = 399000
flags = 0
data = length 1087, hash 491BF106
sample 11:
time = 367000
flags = 0
data = length 380, hash 5FED016A
sample 12:
time = 433000
flags = 0
data = length 455, hash 8A0610
sample 13:
time = 599000
flags = 0
data = length 5190, hash B9031D8
sample 14:
time = 533000
flags = 0
data = length 1071, hash 684E7DC8
sample 15:
time = 500000
flags = 0
data = length 653, hash 8494F326
sample 16:
time = 566000
flags = 0
data = length 485, hash 2CCC85F4
sample 17:
time = 733000
flags = 0
data = length 4884, hash D16B6A96
sample 18:
time = 666000
flags = 0
data = length 997, hash 164FF210
sample 19:
time = 633000
flags = 0
data = length 640, hash F664125B
sample 20:
time = 700000
flags = 0
data = length 491, hash B5930C7C
sample 21:
time = 866000
flags = 0
data = length 2989, hash 92CF4FCF
sample 22:
time = 800000
flags = 0
data = length 838, hash 294A3451
sample 23:
time = 767000
flags = 0
data = length 544, hash FCCE2DE6
sample 24:
time = 833000
flags = 0
data = length 329, hash A654FFA1
sample 25:
time = 1000000
flags = 0
data = length 1517, hash 5F7EBF8B
sample 26:
time = 933000
flags = 0
data = length 803, hash 7A5C4C1D
sample 27:
time = 900000
flags = 0
data = length 415, hash B31BBC3B
sample 28:
time = 967000
flags = 0
data = length 415, hash 850DFEA3
sample 29:
time = 1033000
flags = 0
data = length 619, hash AB5E56CA
track 1:
format:
bitrate = -1
id = 2
containerMimeType = null
sampleMimeType = audio/mp4a-latm
maxInputSize = -1
width = -1
height = -1
frameRate = -1.0
rotationDegrees = -1
pixelWidthHeightRatio = -1.0
channelCount = 1
sampleRate = 44100
pcmEncoding = -1
encoderDelay = -1
encoderPadding = -1
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = und
drmInitData = -
initializationData:
data = length 5, hash 2B7623A
sample count = 46
sample 0:
time = 0
flags = 1
data = length 18, hash 96519432
sample 1:
time = 23000
flags = 1
data = length 4, hash EE9DF
sample 2:
time = 46000
flags = 1
data = length 4, hash EEDBF
sample 3:
time = 69000
flags = 1
data = length 157, hash E2F078F4
sample 4:
time = 92000
flags = 1
data = length 371, hash B9471F94
sample 5:
time = 116000
flags = 1
data = length 373, hash 2AB265CB
sample 6:
time = 139000
flags = 1
data = length 402, hash 1295477C
sample 7:
time = 162000
flags = 1
data = length 455, hash 2D8146C8
sample 8:
time = 185000
flags = 1
data = length 434, hash F2C5D287
sample 9:
time = 208000
flags = 1
data = length 450, hash 84143FCD
sample 10:
time = 232000
flags = 1
data = length 429, hash EF769D50
sample 11:
time = 255000
flags = 1
data = length 450, hash EC3DE692
sample 12:
time = 278000
flags = 1
data = length 447, hash 3E519E13
sample 13:
time = 301000
flags = 1
data = length 457, hash 1E4F23A0
sample 14:
time = 325000
flags = 1
data = length 447, hash A439EA97
sample 15:
time = 348000
flags = 1
data = length 456, hash 1E9034C6
sample 16:
time = 371000
flags = 1
data = length 398, hash 99DB7345
sample 17:
time = 394000
flags = 1
data = length 474, hash 3F05F10A
sample 18:
time = 417000
flags = 1
data = length 416, hash C105EE09
sample 19:
time = 441000
flags = 1
data = length 454, hash 5FDBE458
sample 20:
time = 464000
flags = 1
data = length 438, hash 41A93AC3
sample 21:
time = 487000
flags = 1
data = length 443, hash 10FDA652
sample 22:
time = 510000
flags = 1
data = length 412, hash 1F791E25
sample 23:
time = 534000
flags = 1
data = length 482, hash A6D983D
sample 24:
time = 557000
flags = 1
data = length 386, hash BED7392F
sample 25:
time = 580000
flags = 1
data = length 463, hash 5309F8C9
sample 26:
time = 603000
flags = 1
data = length 394, hash 21C7321F
sample 27:
time = 626000
flags = 1
data = length 489, hash 71B4730D
sample 28:
time = 650000
flags = 1
data = length 403, hash D9C6DE89
sample 29:
time = 673000
flags = 1
data = length 447, hash 9B14B73B
sample 30:
time = 696000
flags = 1
data = length 439, hash 4760D35B
sample 31:
time = 719000
flags = 1
data = length 463, hash 1601F88D
sample 32:
time = 743000
flags = 1
data = length 423, hash D4AE6773
sample 33:
time = 766000
flags = 1
data = length 497, hash A3C674D3
sample 34:
time = 789000
flags = 1
data = length 419, hash D3734A1F
sample 35:
time = 812000
flags = 1
data = length 474, hash DFB41F9
sample 36:
time = 835000
flags = 1
data = length 413, hash 53E7CB9F
sample 37:
time = 859000
flags = 1
data = length 445, hash D15B0E39
sample 38:
time = 882000
flags = 1
data = length 453, hash 77ED81E4
sample 39:
time = 905000
flags = 1
data = length 545, hash 3321AEB9
sample 40:
time = 928000
flags = 1
data = length 317, hash F557D0E
sample 41:
time = 952000
flags = 1
data = length 537, hash ED58CF7B
sample 42:
time = 975000
flags = 1
data = length 458, hash 51CDAA10
sample 43:
time = 998000
flags = 1
data = length 465, hash CBA1EFD7
sample 44:
time = 1021000
flags = 1
data = length 446, hash D6735B8A
sample 45:
time = 1044000
flags = 1
data = length 10, hash A453EEBE
track 3:
format:
bitrate = -1
id = null
containerMimeType = null
sampleMimeType = application/cea-608
maxInputSize = -1
width = -1
height = -1
frameRate = -1.0
rotationDegrees = -1
pixelWidthHeightRatio = -1.0
channelCount = -1
sampleRate = -1
pcmEncoding = -1
encoderDelay = -1
encoderPadding = -1
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = null
drmInitData = -
initializationData:
sample count = 0
tracksEnded = true
......@@ -6,7 +6,7 @@ numberOfTracks = 1
track 0:
format:
bitrate = -1
id = null
id = 0
containerMimeType = null
sampleMimeType = audio/ac3
maxInputSize = -1
......
......@@ -6,7 +6,7 @@ numberOfTracks = 2
track 0:
format:
bitrate = -1
id = null
id = 0
containerMimeType = null
sampleMimeType = audio/mp4a-latm
maxInputSize = -1
......@@ -606,7 +606,7 @@ track 0:
track 1:
format:
bitrate = -1
id = null
id = 1
containerMimeType = null
sampleMimeType = application/id3
maxInputSize = -1
......
......@@ -6,7 +6,7 @@ numberOfTracks = 2
track 192:
format:
bitrate = -1
id = null
id = 192
containerMimeType = null
sampleMimeType = audio/mpeg-L2
maxInputSize = 4096
......@@ -45,7 +45,7 @@ track 192:
track 224:
format:
bitrate = -1
id = null
id = 224
containerMimeType = null
sampleMimeType = video/mpeg2
maxInputSize = -1
......
......@@ -6,7 +6,7 @@ numberOfTracks = 2
track 256:
format:
bitrate = -1
id = null
id = 1/256
containerMimeType = null
sampleMimeType = video/mpeg2
maxInputSize = -1
......@@ -38,7 +38,7 @@ track 256:
track 257:
format:
bitrate = -1
id = null
id = 1/257
containerMimeType = null
sampleMimeType = audio/mpeg-L2
maxInputSize = 4096
......
......@@ -32,6 +32,7 @@ import com.google.android.exoplayer2.source.dash.manifest.Representation;
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.Arrays;
import java.util.HashMap;
import org.mockito.Mock;
......@@ -213,11 +214,15 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase {
}
private static AdaptationSet newAdaptationSets(Representation... representations) {
return new AdaptationSet(0, C.TRACK_TYPE_VIDEO, Arrays.asList(representations));
return new AdaptationSet(0, C.TRACK_TYPE_VIDEO, Arrays.asList(representations), null);
}
private static Representation newRepresentations(DrmInitData drmInitData) {
Format format = Format.createVideoSampleFormat("", "", "", 0, 0, 0, 0, 0, null, drmInitData);
Format format = Format.createVideoContainerFormat("id", MimeTypes.VIDEO_MP4,
MimeTypes.VIDEO_H264, "", Format.NO_VALUE, 1024, 768, Format.NO_VALUE, null, 0);
if (drmInitData != null) {
format = format.copyWithDrmInitData(drmInitData);
}
return Representation.newInstance("", 0, format, "", new SingleSegmentBase());
}
......
......@@ -25,21 +25,32 @@ import com.google.android.exoplayer2.testutil.TestUtil;
*/
public final class FragmentedMp4ExtractorTest extends InstrumentationTestCase {
private static final TestUtil.ExtractorFactory EXTRACTOR_FACTORY =
new TestUtil.ExtractorFactory() {
@Override
public Extractor create() {
return new FragmentedMp4Extractor();
}
};
public void testSample() throws Exception {
TestUtil.assertOutput(EXTRACTOR_FACTORY, "mp4/sample_fragmented.mp4", getInstrumentation());
TestUtil.assertOutput(getExtractorFactory(), "mp4/sample_fragmented.mp4", getInstrumentation());
}
public void testSampleWithSeiPayloadParsing() throws Exception {
// Enabling the CEA-608 track enables SEI payload parsing.
TestUtil.assertOutput(getExtractorFactory(FragmentedMp4Extractor.FLAG_ENABLE_CEA608_TRACK),
"mp4/sample_fragmented_sei.mp4", getInstrumentation());
}
public void testAtomWithZeroSize() throws Exception {
TestUtil.assertThrows(EXTRACTOR_FACTORY, "mp4/sample_fragmented_zero_size_atom.mp4",
TestUtil.assertThrows(getExtractorFactory(), "mp4/sample_fragmented_zero_size_atom.mp4",
getInstrumentation(), ParserException.class);
}
private static TestUtil.ExtractorFactory getExtractorFactory() {
return getExtractorFactory(0);
}
private static TestUtil.ExtractorFactory getExtractorFactory(final int flags) {
return new TestUtil.ExtractorFactory() {
@Override
public Extractor create() {
return new FragmentedMp4Extractor(flags, null);
}
};
}
}
......@@ -69,8 +69,8 @@ public class AdtsReaderTest extends TestCase {
@Override
protected void setUp() throws Exception {
FakeExtractorOutput fakeExtractorOutput = new FakeExtractorOutput();
adtsOutput = fakeExtractorOutput.track(0);
id3Output = fakeExtractorOutput.track(1);
adtsOutput = fakeExtractorOutput.track(0, C.TRACK_TYPE_AUDIO);
id3Output = fakeExtractorOutput.track(1, C.TRACK_TYPE_METADATA);
adtsReader = new AdtsReader(true);
TrackIdGenerator idGenerator = new TrackIdGenerator(0, 1);
adtsReader.createTracks(fakeExtractorOutput, idGenerator);
......
......@@ -16,9 +16,9 @@
package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
......
......@@ -17,11 +17,11 @@ package com.google.android.exoplayer2.extractor.ts;
import android.test.InstrumentationTestCase;
import android.util.SparseArray;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
......@@ -30,6 +30,7 @@ import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
import com.google.android.exoplayer2.testutil.FakeTrackOutput;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.io.ByteArrayOutputStream;
import java.util.Random;
......@@ -92,7 +93,7 @@ public final class TsExtractorTest extends InstrumentationTestCase {
TrackOutput trackOutput = reader.getTrackOutput();
assertTrue(trackOutput == output.trackOutputs.get(257 /* PID of audio track. */));
assertEquals(
Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0, "und", null, 0),
Format.createTextSampleFormat("1/257", "mime", null, 0, 0, "und", null, 0),
((FakeTrackOutput) trackOutput).format);
}
......@@ -178,8 +179,9 @@ public final class TsExtractorTest extends InstrumentationTestCase {
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
output.format(Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0,
idGenerator.generateNewId();
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_UNKNOWN);
output.format(Format.createTextSampleFormat(idGenerator.getFormatId(), "mime", null, 0, 0,
language, null, 0));
}
......
/*
* 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.metadata.scte35;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataDecoderException;
import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.nio.ByteBuffer;
import java.util.List;
import junit.framework.TestCase;
/**
* Test for {@link SpliceInfoDecoder}.
*/
public final class SpliceInfoDecoderTest extends TestCase {
private SpliceInfoDecoder decoder;
private MetadataInputBuffer inputBuffer;
@Override
public void setUp() {
decoder = new SpliceInfoDecoder();
inputBuffer = new MetadataInputBuffer();
}
public void testWrappedAroundTimeSignalCommand() throws MetadataDecoderException {
byte[] rawTimeSignalSection = new byte[] {
0, // table_id.
(byte) 0x80, // section_syntax_indicator, private_indicator, reserved, section_length(4).
0x14, // section_length(8).
0x00, // protocol_version.
0x00, // encrypted_packet, encryption_algorithm, pts_adjustment(1).
0x00, 0x00, 0x00, 0x00, // pts_adjustment(32).
0x00, // cw_index.
0x00, // tier(8).
0x00, // tier(4), splice_command_length(4).
0x05, // splice_command_length(8).
0x06, // splice_command_type = time_signal.
// Start of splice_time().
(byte) 0x80, // time_specified_flag, reserved, pts_time(1).
0x52, 0x03, 0x02, (byte) 0x8f, // pts_time(32). PTS for a second after playback position.
0x00, 0x00, 0x00, 0x00}; // CRC_32 (ignored, check happens at extraction).
// The playback position is 57:15:58.43 approximately.
// With this offset, the playback position pts before wrapping is 0x451ebf851.
Metadata metadata = feedInputBuffer(rawTimeSignalSection, 0x3000000000L, -0x50000L);
assertEquals(1, metadata.length());
assertEquals(removePtsConversionPrecisionError(0x3001000000L, inputBuffer.subsampleOffsetUs),
((TimeSignalCommand) metadata.get(0)).playbackPositionUs);
}
public void test2SpliceInsertCommands() throws MetadataDecoderException {
byte[] rawSpliceInsertCommand1 = new byte[] {
0, // table_id.
(byte) 0x80, // section_syntax_indicator, private_indicator, reserved, section_length(4).
0x19, // section_length(8).
0x00, // protocol_version.
0x00, // encrypted_packet, encryption_algorithm, pts_adjustment(1).
0x00, 0x00, 0x00, 0x00, // pts_adjustment(32).
0x00, // cw_index.
0x00, // tier(8).
0x00, // tier(4), splice_command_length(4).
0x0e, // splice_command_length(8).
0x05, // splice_command_type = splice_insert.
// Start of splice_insert().
0x00, 0x00, 0x00, 0x42, // splice_event_id.
0x00, // splice_event_cancel_indicator, reserved.
0x40, // out_of_network_indicator, program_splice_flag, duration_flag,
// splice_immediate_flag, reserved.
// start of splice_time().
(byte) 0x80, // time_specified_flag, reserved, pts_time(1).
0x00, 0x00, 0x00, 0x00, // PTS for playback position 3s.
0x00, 0x10, // unique_program_id.
0x01, // avail_num.
0x02, // avails_expected.
0x00, 0x00, 0x00, 0x00}; // CRC_32 (ignored, check happens at extraction).
Metadata metadata = feedInputBuffer(rawSpliceInsertCommand1, 2000000, 3000000);
assertEquals(1, metadata.length());
SpliceInsertCommand command = (SpliceInsertCommand) metadata.get(0);
assertEquals(66, command.spliceEventId);
assertFalse(command.spliceEventCancelIndicator);
assertFalse(command.outOfNetworkIndicator);
assertTrue(command.programSpliceFlag);
assertFalse(command.spliceImmediateFlag);
assertEquals(3000000, command.programSplicePlaybackPositionUs);
assertEquals(C.TIME_UNSET, command.breakDuration);
assertEquals(16, command.uniqueProgramId);
assertEquals(1, command.availNum);
assertEquals(2, command.availsExpected);
byte[] rawSpliceInsertCommand2 = new byte[] {
0, // table_id.
(byte) 0x80, // section_syntax_indicator, private_indicator, reserved, section_length(4).
0x22, // section_length(8).
0x00, // protocol_version.
0x00, // encrypted_packet, encryption_algorithm, pts_adjustment(1).
0x00, 0x00, 0x00, 0x00, // pts_adjustment(32).
0x00, // cw_index.
0x00, // tier(8).
0x00, // tier(4), splice_command_length(4).
0x13, // splice_command_length(8).
0x05, // splice_command_type = splice_insert.
// Start of splice_insert().
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, // splice_event_id.
0x00, // splice_event_cancel_indicator, reserved.
0x00, // out_of_network_indicator, program_splice_flag, duration_flag,
// splice_immediate_flag, reserved.
0x02, // component_count.
0x10, // component_tag.
// start of splice_time().
(byte) 0x81, // time_specified_flag, reserved, pts_time(1).
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, // PTS for playback position 10s.
// start of splice_time().
0x11, // component_tag.
0x00, // time_specified_flag, reserved.
0x00, 0x20, // unique_program_id.
0x01, // avail_num.
0x02, // avails_expected.
0x00, 0x00, 0x00, 0x00}; // CRC_32 (ignored, check happens at extraction).
// By changing the subsample offset we force adjuster reconstruction.
long subsampleOffset = 1000011;
metadata = feedInputBuffer(rawSpliceInsertCommand2, 1000000, subsampleOffset);
assertEquals(1, metadata.length());
command = (SpliceInsertCommand) metadata.get(0);
assertEquals(0xffffffffL, command.spliceEventId);
assertFalse(command.spliceEventCancelIndicator);
assertFalse(command.outOfNetworkIndicator);
assertFalse(command.programSpliceFlag);
assertFalse(command.spliceImmediateFlag);
assertEquals(C.TIME_UNSET, command.programSplicePlaybackPositionUs);
assertEquals(C.TIME_UNSET, command.breakDuration);
List<SpliceInsertCommand.ComponentSplice> componentSplices = command.componentSpliceList;
assertEquals(2, componentSplices.size());
assertEquals(16, componentSplices.get(0).componentTag);
assertEquals(1000000, componentSplices.get(0).componentSplicePlaybackPositionUs);
assertEquals(17, componentSplices.get(1).componentTag);
assertEquals(C.TIME_UNSET, componentSplices.get(1).componentSplicePts);
assertEquals(32, command.uniqueProgramId);
assertEquals(1, command.availNum);
assertEquals(2, command.availsExpected);
}
private Metadata feedInputBuffer(byte[] data, long timeUs, long subsampleOffset)
throws MetadataDecoderException{
inputBuffer.clear();
inputBuffer.data = ByteBuffer.allocate(data.length).put(data);
inputBuffer.timeUs = timeUs;
inputBuffer.subsampleOffsetUs = subsampleOffset;
return decoder.decode(inputBuffer);
}
private static long removePtsConversionPrecisionError(long timeUs, long offsetUs) {
return TimestampAdjuster.ptsToUs(TimestampAdjuster.usToPts(timeUs - offsetUs)) + offsetUs;
}
}
......@@ -20,6 +20,8 @@ import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.testutil.TestUtil;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
/**
* Unit tests for {@link DashManifestParser}.
......@@ -70,34 +72,57 @@ public class DashManifestParserTest extends InstrumentationTestCase {
}
public void testParseCea608AccessibilityChannel() {
assertEquals(1, DashManifestParser.parseCea608AccessibilityChannel("CC1=eng"));
assertEquals(2, DashManifestParser.parseCea608AccessibilityChannel("CC2=eng"));
assertEquals(3, DashManifestParser.parseCea608AccessibilityChannel("CC3=eng"));
assertEquals(4, DashManifestParser.parseCea608AccessibilityChannel("CC4=eng"));
assertEquals(1, DashManifestParser.parseCea608AccessibilityChannel(
buildCea608AccessibilityDescriptors("CC1=eng")));
assertEquals(2, DashManifestParser.parseCea608AccessibilityChannel(
buildCea608AccessibilityDescriptors("CC2=eng")));
assertEquals(3, DashManifestParser.parseCea608AccessibilityChannel(
buildCea608AccessibilityDescriptors("CC3=eng")));
assertEquals(4, DashManifestParser.parseCea608AccessibilityChannel(
buildCea608AccessibilityDescriptors("CC4=eng")));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel(null));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel(""));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel("CC0=eng"));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel("CC5=eng"));
assertEquals(Format.NO_VALUE,
DashManifestParser.parseCea608AccessibilityChannel("Wrong format"));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel(
buildCea608AccessibilityDescriptors(null)));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel(
buildCea608AccessibilityDescriptors("")));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel(
buildCea608AccessibilityDescriptors("CC0=eng")));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel(
buildCea608AccessibilityDescriptors("CC5=eng")));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel(
buildCea608AccessibilityDescriptors("Wrong format")));
}
public void testParseCea708AccessibilityChannel() {
assertEquals(1, DashManifestParser.parseCea708AccessibilityChannel("1=lang:eng"));
assertEquals(2, DashManifestParser.parseCea708AccessibilityChannel("2=lang:eng"));
assertEquals(3, DashManifestParser.parseCea708AccessibilityChannel("3=lang:eng"));
assertEquals(62, DashManifestParser.parseCea708AccessibilityChannel("62=lang:eng"));
assertEquals(63, DashManifestParser.parseCea708AccessibilityChannel("63=lang:eng"));
assertEquals(1, DashManifestParser.parseCea708AccessibilityChannel(
buildCea708AccessibilityDescriptors("1=lang:eng")));
assertEquals(2, DashManifestParser.parseCea708AccessibilityChannel(
buildCea708AccessibilityDescriptors("2=lang:eng")));
assertEquals(3, DashManifestParser.parseCea708AccessibilityChannel(
buildCea708AccessibilityDescriptors("3=lang:eng")));
assertEquals(62, DashManifestParser.parseCea708AccessibilityChannel(
buildCea708AccessibilityDescriptors("62=lang:eng")));
assertEquals(63, DashManifestParser.parseCea708AccessibilityChannel(
buildCea708AccessibilityDescriptors("63=lang:eng")));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea708AccessibilityChannel(null));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea708AccessibilityChannel(""));
assertEquals(Format.NO_VALUE,
DashManifestParser.parseCea708AccessibilityChannel("0=lang:eng"));
assertEquals(Format.NO_VALUE,
DashManifestParser.parseCea708AccessibilityChannel("64=lang:eng"));
assertEquals(Format.NO_VALUE,
DashManifestParser.parseCea708AccessibilityChannel("Wrong format"));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea708AccessibilityChannel(
buildCea708AccessibilityDescriptors(null)));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea708AccessibilityChannel(
buildCea708AccessibilityDescriptors("")));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea708AccessibilityChannel(
buildCea708AccessibilityDescriptors("0=lang:eng")));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea708AccessibilityChannel(
buildCea708AccessibilityDescriptors("64=lang:eng")));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea708AccessibilityChannel(
buildCea708AccessibilityDescriptors("Wrong format")));
}
private static List<SchemeValuePair> buildCea608AccessibilityDescriptors(String value) {
return Collections.singletonList(new SchemeValuePair("urn:scte:dash:cc:cea-608:2015", value));
}
private static List<SchemeValuePair> buildCea708AccessibilityDescriptors(String value) {
return Collections.singletonList(new SchemeValuePair("urn:scte:dash:cc:cea-708:2015", value));
}
}
......@@ -35,6 +35,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
String playlistString = "#EXTM3U\n"
+ "#EXT-X-VERSION:3\n"
+ "#EXT-X-PLAYLIST-TYPE:VOD\n"
+ "#EXT-X-TARGETDURATION:8\n"
+ "#EXT-X-MEDIA-SEQUENCE:2679\n"
+ "#EXT-X-DISCONTINUITY-SEQUENCE:4\n"
......@@ -71,6 +72,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
assertEquals(HlsPlaylist.TYPE_MEDIA, playlist.type);
HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist;
assertEquals(HlsMediaPlaylist.PLAYLIST_TYPE_VOD, mediaPlaylist.playlistType);
assertEquals(2679, mediaPlaylist.mediaSequence);
assertEquals(3, mediaPlaylist.version);
......
......@@ -20,9 +20,9 @@ import android.test.InstrumentationTestCase;
import android.test.MoreAsserts;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.testutil.FakeDataSource;
import com.google.android.exoplayer2.testutil.FakeDataSource.Builder;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.FileDataSource;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
......@@ -119,9 +119,22 @@ public class CacheDataSourceTest extends InstrumentationTestCase {
C.LENGTH_UNSET, KEY_2)));
}
public void testIgnoreCacheForUnsetLengthRequests() throws Exception {
CacheDataSource cacheDataSource = createCacheDataSource(false, true,
CacheDataSource.FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS);
assertReadData(cacheDataSource, true, 0, C.LENGTH_UNSET);
MoreAsserts.assertEmpty(simpleCache.getKeys());
}
public void testReadOnlyCache() throws Exception {
CacheDataSource cacheDataSource = createCacheDataSource(false, false, 0, null);
assertReadDataContentLength(cacheDataSource, false, false);
assertEquals(0, cacheDir.list().length);
}
private void assertCacheAndRead(boolean unboundedRequest, boolean simulateUnknownLength)
throws IOException {
// Read all data from upstream and cache
// Read all data from upstream and write to cache
CacheDataSource cacheDataSource = createCacheDataSource(false, simulateUnknownLength);
assertReadDataContentLength(cacheDataSource, unboundedRequest, simulateUnknownLength);
......@@ -171,15 +184,27 @@ public class CacheDataSourceTest extends InstrumentationTestCase {
private CacheDataSource createCacheDataSource(boolean setReadException,
boolean simulateUnknownLength) {
Builder builder = new Builder();
return createCacheDataSource(setReadException, simulateUnknownLength,
CacheDataSource.FLAG_BLOCK_ON_CACHE);
}
private CacheDataSource createCacheDataSource(boolean setReadException,
boolean simulateUnknownLength, @CacheDataSource.Flags int flags) {
return createCacheDataSource(setReadException, simulateUnknownLength, flags,
new CacheDataSink(simpleCache, MAX_CACHE_FILE_SIZE));
}
private CacheDataSource createCacheDataSource(boolean setReadException,
boolean simulateUnknownLength, @CacheDataSource.Flags int flags,
CacheDataSink cacheWriteDataSink) {
FakeDataSource.Builder builder = new FakeDataSource.Builder();
if (setReadException) {
builder.appendReadError(new IOException("Shouldn't read from upstream"));
}
builder.setSimulateUnknownLength(simulateUnknownLength);
builder.appendReadData(TEST_DATA);
FakeDataSource upstream = builder.build();
return new CacheDataSource(simpleCache, upstream, CacheDataSource.FLAG_BLOCK_ON_CACHE,
MAX_CACHE_FILE_SIZE);
FakeDataSource upstream =
builder.setSimulateUnknownLength(simulateUnknownLength).appendReadData(TEST_DATA).build();
return new CacheDataSource(simpleCache, upstream, new FileDataSource(), cacheWriteDataSink,
flags, null);
}
}
......@@ -515,7 +515,13 @@ public final class C {
* The stereo mode for 360/3D/VR videos.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, STEREO_MODE_MONO, STEREO_MODE_TOP_BOTTOM, STEREO_MODE_LEFT_RIGHT})
@IntDef({
Format.NO_VALUE,
STEREO_MODE_MONO,
STEREO_MODE_TOP_BOTTOM,
STEREO_MODE_LEFT_RIGHT,
STEREO_MODE_STEREO_MESH
})
public @interface StereoMode {}
/**
* Indicates Monoscopic stereo layout, used with 360/3D/VR videos.
......@@ -529,6 +535,11 @@ public final class C {
* Indicates Left-Right stereo layout, used with 360/3D/VR videos.
*/
public static final int STEREO_MODE_LEFT_RIGHT = 2;
/**
* Indicates a stereo layout where the left and right eyes have separate meshes,
* used with 360/3D/VR videos.
*/
public static final int STEREO_MODE_STEREO_MESH = 3;
/**
* Converts a time in microseconds to the corresponding time in milliseconds, preserving
......
......@@ -1215,7 +1215,7 @@ import java.io.IOException;
long newLoadingPeriodStartPositionUs;
if (loadingPeriodHolder == null) {
newLoadingPeriodStartPositionUs = playbackInfo.startPositionUs;
newLoadingPeriodStartPositionUs = playbackInfo.positionUs;
} else {
int newLoadingWindowIndex = timeline.getPeriod(newLoadingPeriodIndex, period).windowIndex;
if (newLoadingPeriodIndex
......
......@@ -23,7 +23,7 @@ public interface ExoPlayerLibraryInfo {
/**
* The version of the library, expressed as a string.
*/
String VERSION = "2.1.1";
String VERSION = "2.2.0";
/**
* The version of the library, expressed as an integer.
......@@ -32,7 +32,7 @@ public interface ExoPlayerLibraryInfo {
* corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding
* integer version 123045006 (123-045-006).
*/
int VERSION_INT = 2001001;
int VERSION_INT = 2002000;
/**
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
......
......@@ -120,7 +120,7 @@ public final class Format implements Parcelable {
/**
* The stereo layout for 360/3D/VR video, or {@link #NO_VALUE} if not applicable. Valid stereo
* modes are {@link C#STEREO_MODE_MONO}, {@link C#STEREO_MODE_TOP_BOTTOM}, {@link
* C#STEREO_MODE_LEFT_RIGHT}.
* C#STEREO_MODE_LEFT_RIGHT}, {@link C#STEREO_MODE_STEREO_MESH}.
*/
@C.StereoMode
public final int stereoMode;
......@@ -447,16 +447,19 @@ public final class Format implements Parcelable {
drmInitData, metadata);
}
public Format copyWithManifestFormatInfo(Format manifestFormat,
boolean preferManifestDrmInitData) {
public Format copyWithManifestFormatInfo(Format manifestFormat) {
if (this == manifestFormat) {
// No need to copy from ourselves.
return this;
}
String id = manifestFormat.id;
String codecs = this.codecs == null ? manifestFormat.codecs : this.codecs;
int bitrate = this.bitrate == NO_VALUE ? manifestFormat.bitrate : this.bitrate;
float frameRate = this.frameRate == NO_VALUE ? manifestFormat.frameRate : this.frameRate;
@C.SelectionFlags int selectionFlags = this.selectionFlags | manifestFormat.selectionFlags;
String language = this.language == null ? manifestFormat.language : this.language;
DrmInitData drmInitData = (preferManifestDrmInitData && manifestFormat.drmInitData != null)
|| this.drmInitData == null ? manifestFormat.drmInitData : this.drmInitData;
DrmInitData drmInitData = manifestFormat.drmInitData != null ? manifestFormat.drmInitData
: this.drmInitData;
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width,
height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode,
channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, selectionFlags,
......@@ -681,9 +684,6 @@ public final class Format implements Parcelable {
dest.writeParcelable(metadata, 0);
}
/**
* {@link Creator} implementation.
*/
public static final Creator<Format> CREATOR = new Creator<Format>() {
@Override
......
......@@ -29,6 +29,7 @@ import android.view.SurfaceView;
import android.view.TextureView;
import com.google.android.exoplayer2.audio.AudioCapabilities;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.BufferProcessor;
import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer;
import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.drm.DrmSessionManager;
......@@ -479,8 +480,8 @@ public class SimpleExoPlayer implements ExoPlayer {
}
@Override
public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetTimeline) {
player.prepare(mediaSource, resetPosition, resetTimeline);
public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) {
player.prepare(mediaSource, resetPosition, resetState);
}
@Override
......@@ -624,7 +625,7 @@ public class SimpleExoPlayer implements ExoPlayer {
buildVideoRenderers(context, mainHandler, drmSessionManager, extensionRendererMode,
componentListener, allowedVideoJoiningTimeMs, out);
buildAudioRenderers(context, mainHandler, drmSessionManager, extensionRendererMode,
componentListener, out);
componentListener, buildBufferProcessors(), out);
buildTextRenderers(context, mainHandler, extensionRendererMode, componentListener, out);
buildMetadataRenderers(context, mainHandler, extensionRendererMode, componentListener, out);
buildMiscellaneousRenderers(context, mainHandler, extensionRendererMode, out);
......@@ -636,7 +637,7 @@ public class SimpleExoPlayer implements ExoPlayer {
* @param context The {@link Context} associated with the player.
* @param mainHandler A handler associated with the main thread's looper.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player will
* not be used for DRM protected playbacks.
* not be used for DRM protected playbacks.
* @param extensionRendererMode The extension renderer mode.
* @param eventListener An event listener.
* @param allowedVideoJoiningTimeMs The maximum duration in milliseconds for which video renderers
......@@ -681,17 +682,19 @@ public class SimpleExoPlayer implements ExoPlayer {
* @param context The {@link Context} associated with the player.
* @param mainHandler A handler associated with the main thread's looper.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player will
* not be used for DRM protected playbacks.
* not be used for DRM protected playbacks.
* @param extensionRendererMode The extension renderer mode.
* @param eventListener An event listener.
* @param bufferProcessors An array of {@link BufferProcessor}s which will process PCM audio
* buffers before they are output. May be empty.
* @param out An array to which the built renderers should be appended.
*/
protected void buildAudioRenderers(Context context, Handler mainHandler,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@ExtensionRendererMode int extensionRendererMode, AudioRendererEventListener eventListener,
ArrayList<Renderer> out) {
BufferProcessor[] bufferProcessors, ArrayList<Renderer> out) {
out.add(new MediaCodecAudioRenderer(MediaCodecSelector.DEFAULT, drmSessionManager, true,
mainHandler, eventListener, AudioCapabilities.getCapabilities(context)));
mainHandler, eventListener, AudioCapabilities.getCapabilities(context), bufferProcessors));
if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) {
return;
......@@ -705,8 +708,9 @@ public class SimpleExoPlayer implements ExoPlayer {
Class<?> clazz =
Class.forName("com.google.android.exoplayer2.ext.opus.LibopusAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class);
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener);
AudioRendererEventListener.class, BufferProcessor[].class);
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener,
bufferProcessors);
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded LibopusAudioRenderer.");
} catch (ClassNotFoundException e) {
......@@ -719,8 +723,9 @@ public class SimpleExoPlayer implements ExoPlayer {
Class<?> clazz =
Class.forName("com.google.android.exoplayer2.ext.flac.LibflacAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class);
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener);
AudioRendererEventListener.class, BufferProcessor[].class);
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener,
bufferProcessors);
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded LibflacAudioRenderer.");
} catch (ClassNotFoundException e) {
......@@ -733,8 +738,9 @@ public class SimpleExoPlayer implements ExoPlayer {
Class<?> clazz =
Class.forName("com.google.android.exoplayer2.ext.ffmpeg.FfmpegAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class);
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener);
AudioRendererEventListener.class, BufferProcessor[].class);
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener,
bufferProcessors);
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded FfmpegAudioRenderer.");
} catch (ClassNotFoundException e) {
......@@ -787,6 +793,14 @@ public class SimpleExoPlayer implements ExoPlayer {
// Do nothing.
}
/**
* Builds an array of {@link BufferProcessor}s which will process PCM audio buffers before they
* are output.
*/
protected BufferProcessor[] buildBufferProcessors() {
return new BufferProcessor[0];
}
// Internal methods.
private void removeSurfaceCallbacks() {
......
/*
* Copyright (C) 2017 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.audio;
import com.google.android.exoplayer2.C;
import java.nio.ByteBuffer;
/**
* Interface for processors of audio buffers.
*/
public interface BufferProcessor {
/**
* Exception thrown when a processor can't be configured for a given input format.
*/
final class UnhandledFormatException extends Exception {
public UnhandledFormatException(int sampleRateHz, int channelCount, @C.Encoding int encoding) {
super("Unhandled format: " + sampleRateHz + " Hz, " + channelCount + " channels in encoding "
+ encoding);
}
}
/**
* Configures this processor to take input buffers with the specified format.
*
* @param sampleRateHz The sample rate of input audio in Hz.
* @param channelCount The number of interleaved channels in input audio.
* @param encoding The encoding of input audio.
* @throws UnhandledFormatException Thrown if the specified format can't be handled as input.
*/
void configure(int sampleRateHz, int channelCount, @C.Encoding int encoding)
throws UnhandledFormatException;
/**
* Returns the encoding used in buffers output by this processor.
*/
@C.Encoding
int getOutputEncoding();
/**
* Processes the data in the specified input buffer in its entirety.
*
* @param input A buffer containing the input data to process.
* @return A buffer containing the processed output. This may be the same as the input buffer if
* no processing was required.
*/
ByteBuffer handleBuffer(ByteBuffer input);
/**
* Clears any state in preparation for receiving a new stream of buffers.
*/
void flush();
/**
* Releases any resources associated with this instance.
*/
void release();
}
......@@ -121,13 +121,16 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param audioCapabilities The audio capabilities for playback on this device. May be null if the
* default capabilities (no encoded audio passthrough support) should be assumed.
* @param bufferProcessors Optional {@link BufferProcessor}s which will process PCM audio buffers
* before they are output.
*/
public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys, Handler eventHandler,
AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities) {
AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities,
BufferProcessor... bufferProcessors) {
super(C.TRACK_TYPE_AUDIO, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys);
audioTrack = new AudioTrack(audioCapabilities, new AudioTrackListener());
audioTrack = new AudioTrack(audioCapabilities, bufferProcessors, new AudioTrackListener());
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
}
......@@ -183,7 +186,8 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
}
@Override
protected void configureCodec(MediaCodec codec, Format format, MediaCrypto crypto) {
protected void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format,
MediaCrypto crypto) {
if (passthroughEnabled) {
// Override the MIME type used to configure the codec if we are using a passthrough decoder.
passthroughMediaFormat = format.getFrameworkMediaFormatV16();
......@@ -218,14 +222,19 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
}
@Override
protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat) {
protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat)
throws ExoPlaybackException {
boolean passthrough = passthroughMediaFormat != null;
String mimeType = passthrough ? passthroughMediaFormat.getString(MediaFormat.KEY_MIME)
: MimeTypes.AUDIO_RAW;
MediaFormat format = passthrough ? passthroughMediaFormat : outputFormat;
int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
audioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding, 0);
try {
audioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding, 0);
} catch (AudioTrack.ConfigurationException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
}
/**
......
/*
* Copyright (C) 2017 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.audio;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import java.nio.ByteBuffer;
/**
* A {@link BufferProcessor} that outputs buffers in {@link C#ENCODING_PCM_16BIT}.
*/
/* package */ final class ResamplingBufferProcessor implements BufferProcessor {
@C.PcmEncoding
private int encoding;
private ByteBuffer outputBuffer;
public ResamplingBufferProcessor() {
encoding = C.ENCODING_INVALID;
}
@Override
public void configure(int sampleRateHz, int channelCount, @C.Encoding int encoding)
throws UnhandledFormatException {
if (encoding != C.ENCODING_PCM_8BIT && encoding != C.ENCODING_PCM_16BIT
&& encoding != C.ENCODING_PCM_24BIT && encoding != C.ENCODING_PCM_32BIT) {
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
}
if (encoding == C.ENCODING_PCM_16BIT) {
outputBuffer = null;
}
this.encoding = encoding;
}
@Override
public int getOutputEncoding() {
return C.ENCODING_PCM_16BIT;
}
@Override
public ByteBuffer handleBuffer(ByteBuffer buffer) {
int position = buffer.position();
int limit = buffer.limit();
int size = limit - position;
int resampledSize;
switch (encoding) {
case C.ENCODING_PCM_16BIT:
// No processing required.
return buffer;
case C.ENCODING_PCM_8BIT:
resampledSize = size * 2;
break;
case C.ENCODING_PCM_24BIT:
resampledSize = (size / 3) * 2;
break;
case C.ENCODING_PCM_32BIT:
resampledSize = size / 2;
break;
case C.ENCODING_INVALID:
case Format.NO_VALUE:
default:
// Never happens.
throw new IllegalStateException();
}
if (outputBuffer == null || outputBuffer.capacity() < resampledSize) {
outputBuffer = ByteBuffer.allocateDirect(resampledSize).order(buffer.order());
} else {
outputBuffer.clear();
}
// Samples are little endian.
switch (encoding) {
case C.ENCODING_PCM_8BIT:
// 8->16 bit resampling. Shift each byte from [0, 256) to [-128, 128) and scale up.
for (int i = position; i < limit; i++) {
outputBuffer.put((byte) 0);
outputBuffer.put((byte) ((buffer.get(i) & 0xFF) - 128));
}
break;
case C.ENCODING_PCM_24BIT:
// 24->16 bit resampling. Drop the least significant byte.
for (int i = position; i < limit; i += 3) {
outputBuffer.put(buffer.get(i + 1));
outputBuffer.put(buffer.get(i + 2));
}
break;
case C.ENCODING_PCM_32BIT:
// 32->16 bit resampling. Drop the two least significant bytes.
for (int i = position; i < limit; i += 4) {
outputBuffer.put(buffer.get(i + 2));
outputBuffer.put(buffer.get(i + 3));
}
break;
case C.ENCODING_PCM_16BIT:
case C.ENCODING_INVALID:
case Format.NO_VALUE:
default:
// Never happens.
throw new IllegalStateException();
}
outputBuffer.flip();
return outputBuffer;
}
@Override
public void flush() {
// Do nothing.
}
@Override
public void release() {
outputBuffer = null;
}
}
......@@ -102,10 +102,12 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param bufferProcessors Optional {@link BufferProcessor}s which will process PCM audio buffers
* before they are output.
*/
public SimpleDecoderAudioRenderer(Handler eventHandler,
AudioRendererEventListener eventListener) {
this(eventHandler, eventListener, null);
AudioRendererEventListener eventListener, BufferProcessor... bufferProcessors) {
this(eventHandler, eventListener, null, null, false, bufferProcessors);
}
/**
......@@ -133,13 +135,16 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
* begin in parallel with key acquisition. This parameter specifies whether the renderer is
* permitted to play clear regions of encrypted media files before {@code drmSessionManager}
* has obtained the keys necessary to decrypt encrypted regions of the media.
* @param bufferProcessors Optional {@link BufferProcessor}s which will process PCM audio
* buffers before they are output.
*/
public SimpleDecoderAudioRenderer(Handler eventHandler,
AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities,
DrmSessionManager<ExoMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys) {
DrmSessionManager<ExoMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys,
BufferProcessor... bufferProcessors) {
super(C.TRACK_TYPE_AUDIO);
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
audioTrack = new AudioTrack(audioCapabilities, new AudioTrackListener());
audioTrack = new AudioTrack(audioCapabilities, bufferProcessors, new AudioTrackListener());
this.drmSessionManager = drmSessionManager;
formatHolder = new FormatHolder();
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
......@@ -193,8 +198,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
while (drainOutputBuffer()) {}
while (feedInputBuffer()) {}
TraceUtil.endSection();
} catch (AudioTrack.InitializationException | AudioTrack.WriteException
| AudioDecoderException e) {
} catch (AudioDecoderException | AudioTrack.ConfigurationException
| AudioTrack.InitializationException | AudioTrack.WriteException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
decoderCounters.ensureUpdated();
......@@ -255,7 +260,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
}
private boolean drainOutputBuffer() throws ExoPlaybackException, AudioDecoderException,
AudioTrack.InitializationException, AudioTrack.WriteException {
AudioTrack.ConfigurationException, AudioTrack.InitializationException,
AudioTrack.WriteException {
if (outputBuffer == null) {
outputBuffer = decoder.dequeueOutputBuffer();
if (outputBuffer == null) {
......
......@@ -280,6 +280,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
* required.
*
* <p>{@code mode} must be one of these:
* <ul>
* <li>{@link #MODE_PLAYBACK}: If {@code offlineLicenseKeySetId} is null, a streaming license is
* requested otherwise the offline license is restored.
* <li>{@link #MODE_QUERY}: {@code offlineLicenseKeySetId} can not be null. The offline license
......@@ -288,6 +289,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
* requested otherwise the offline license is renewed.
* <li>{@link #MODE_RELEASE}: {@code offlineLicenseKeySetId} can not be null. The offline license
* is released.
* </ul>
*
* @param mode The mode to be set.
* @param offlineLicenseKeySetId The key set id of the license to be used with the given mode.
......@@ -530,9 +532,8 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
}
private void postKeyRequest(byte[] scope, int keyType) {
KeyRequest keyRequest;
try {
keyRequest = mediaDrm.getKeyRequest(scope, schemeInitData, schemeMimeType, keyType,
KeyRequest keyRequest = mediaDrm.getKeyRequest(scope, schemeInitData, schemeMimeType, keyType,
optionalKeyRequestParameters);
postRequestHandler.obtainMessage(MSG_KEYS, keyRequest).sendToTarget();
} catch (Exception e) {
......@@ -564,7 +565,8 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
}
} else {
byte[] keySetId = mediaDrm.provideKeyResponse(sessionId, (byte[]) response);
if (keySetId != null && keySetId.length != 0) {
if ((mode == MODE_DOWNLOAD || (mode == MODE_PLAYBACK && offlineLicenseKeySetId != null))
&& keySetId != null && keySetId.length != 0) {
offlineLicenseKeySetId = keySetId;
}
state = STATE_OPENED_WITH_KEYS;
......
......@@ -31,7 +31,7 @@ public interface DrmSession<T extends ExoMediaCrypto> {
/** Wraps the exception which is the cause of the error state. */
class DrmSessionException extends Exception {
DrmSessionException(Exception e) {
public DrmSessionException(Exception e) {
super(e);
}
......
......@@ -24,6 +24,8 @@ import com.google.android.exoplayer2.drm.ExoMediaDrm.ProvisionRequest;
import com.google.android.exoplayer2.upstream.DataSourceInputStream;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource.Factory;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.util.HashMap;
......@@ -57,21 +59,62 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback {
}
/**
* @deprecated Use {@link HttpMediaDrmCallback#HttpMediaDrmCallback(String, Factory)}. Request
* properties can be set by calling {@link #setKeyRequestProperty(String, String)}.
* @param defaultUrl The default license URL.
* @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
* @param keyRequestProperties Request properties to set when making key requests, or null.
*/
@Deprecated
public HttpMediaDrmCallback(String defaultUrl, HttpDataSource.Factory dataSourceFactory,
Map<String, String> keyRequestProperties) {
this.dataSourceFactory = dataSourceFactory;
this.defaultUrl = defaultUrl;
this.keyRequestProperties = keyRequestProperties;
this.keyRequestProperties = new HashMap<>();
if (keyRequestProperties != null) {
this.keyRequestProperties.putAll(keyRequestProperties);
}
}
/**
* Sets a header for key requests made by the callback.
*
* @param name The name of the header field.
* @param value The value of the field.
*/
public void setKeyRequestProperty(String name, String value) {
Assertions.checkNotNull(name);
Assertions.checkNotNull(value);
synchronized (keyRequestProperties) {
keyRequestProperties.put(name, value);
}
}
/**
* Clears a header for key requests made by the callback.
*
* @param name The name of the header field.
*/
public void clearKeyRequestProperty(String name) {
Assertions.checkNotNull(name);
synchronized (keyRequestProperties) {
keyRequestProperties.remove(name);
}
}
/**
* Clears all headers for key requests made by the callback.
*/
public void clearAllKeyRequestProperties() {
synchronized (keyRequestProperties) {
keyRequestProperties.clear();
}
}
@Override
public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws IOException {
String url = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData());
return executePost(url, new byte[0], null);
return executePost(dataSourceFactory, url, new byte[0], null);
}
@Override
......@@ -85,14 +128,14 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback {
if (C.PLAYREADY_UUID.equals(uuid)) {
requestProperties.putAll(PLAYREADY_KEY_REQUEST_PROPERTIES);
}
if (keyRequestProperties != null) {
synchronized (keyRequestProperties) {
requestProperties.putAll(keyRequestProperties);
}
return executePost(url, request.getData(), requestProperties);
return executePost(dataSourceFactory, url, request.getData(), requestProperties);
}
private byte[] executePost(String url, byte[] data, Map<String, String> requestProperties)
throws IOException {
private static byte[] executePost(HttpDataSource.Factory dataSourceFactory, String url,
byte[] data, Map<String, String> requestProperties) throws IOException {
HttpDataSource dataSource = dataSourceFactory.createDataSource();
if (requestProperties != null) {
for (Map.Entry<String, String> requestProperty : requestProperties.entrySet()) {
......
......@@ -93,7 +93,7 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
public static OfflineLicenseHelper<FrameworkMediaCrypto> newWidevineInstance(
String licenseUrl, Factory httpDataSourceFactory) throws UnsupportedDrmException {
return newWidevineInstance(
new HttpMediaDrmCallback(licenseUrl, httpDataSourceFactory, null), null);
new HttpMediaDrmCallback(licenseUrl, httpDataSourceFactory), null);
}
/**
......@@ -210,11 +210,14 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
Representation representation = adaptationSet.representations.get(0);
DrmInitData drmInitData = representation.format.drmInitData;
if (drmInitData == null) {
InitializationChunk initializationChunk = loadInitializationChunk(dataSource, representation);
ChunkExtractorWrapper extractorWrapper = newWrappedExtractor(representation.format,
adaptationSet.type);
InitializationChunk initializationChunk = loadInitializationChunk(dataSource, representation,
extractorWrapper);
if (initializationChunk == null) {
return null;
}
Format sampleFormat = initializationChunk.getSampleFormat();
Format sampleFormat = extractorWrapper.getSampleFormat();
if (sampleFormat != null) {
drmInitData = sampleFormat.drmInitData;
}
......@@ -288,8 +291,9 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
return session;
}
private static InitializationChunk loadInitializationChunk(final DataSource dataSource,
final Representation representation) throws IOException, InterruptedException {
private static InitializationChunk loadInitializationChunk(DataSource dataSource,
Representation representation, ChunkExtractorWrapper extractorWrapper)
throws IOException, InterruptedException {
RangedUri rangedUri = representation.getInitializationUri();
if (rangedUri == null) {
return null;
......@@ -298,18 +302,17 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
rangedUri.length, representation.getCacheKey());
InitializationChunk initializationChunk = new InitializationChunk(dataSource, dataSpec,
representation.format, C.SELECTION_REASON_UNKNOWN, null /* trackSelectionData */,
newWrappedExtractor(representation.format));
extractorWrapper);
initializationChunk.load();
return initializationChunk;
}
private static ChunkExtractorWrapper newWrappedExtractor(final Format format) {
private static ChunkExtractorWrapper newWrappedExtractor(Format format, int trackType) {
final String mimeType = format.containerMimeType;
final boolean isWebm = mimeType.startsWith(MimeTypes.VIDEO_WEBM)
|| mimeType.startsWith(MimeTypes.AUDIO_WEBM);
final Extractor extractor = isWebm ? new MatroskaExtractor() : new FragmentedMp4Extractor();
return new ChunkExtractorWrapper(extractor, format, false /* preferManifestDrmInitData */,
false /* resendFormatOnInit */);
return new ChunkExtractorWrapper(extractor, format, trackType);
}
}
......@@ -70,6 +70,8 @@ public final class DefaultTrackOutput implements TrackOutput {
private Format downstreamFormat;
// Accessed only by the loading thread (or the consuming thread when there is no loading thread).
private boolean pendingFormatAdjustment;
private Format lastUnadjustedFormat;
private long sampleOffsetUs;
private long totalBytesWritten;
private Allocation lastAllocation;
......@@ -445,23 +447,24 @@ public final class DefaultTrackOutput implements TrackOutput {
}
/**
* Like {@link #format(Format)}, but with an offset that will be added to the timestamps of
* samples subsequently queued to the buffer. The offset is also used to adjust
* {@link Format#subsampleOffsetUs} for both the {@link Format} passed and those subsequently
* passed to {@link #format(Format)}.
* Sets an offset that will be added to the timestamps (and sub-sample timestamps) of samples
* subsequently queued to the buffer.
*
* @param format The format.
* @param sampleOffsetUs The timestamp offset in microseconds.
*/
public void formatWithOffset(Format format, long sampleOffsetUs) {
this.sampleOffsetUs = sampleOffsetUs;
format(format);
public void setSampleOffsetUs(long sampleOffsetUs) {
if (this.sampleOffsetUs != sampleOffsetUs) {
this.sampleOffsetUs = sampleOffsetUs;
pendingFormatAdjustment = true;
}
}
@Override
public void format(Format format) {
Format adjustedFormat = getAdjustedSampleFormat(format, sampleOffsetUs);
boolean formatChanged = infoQueue.format(adjustedFormat);
lastUnadjustedFormat = format;
pendingFormatAdjustment = false;
if (upstreamFormatChangeListener != null && formatChanged) {
upstreamFormatChangeListener.onUpstreamFormatChanged(adjustedFormat);
}
......@@ -518,6 +521,9 @@ public final class DefaultTrackOutput implements TrackOutput {
@Override
public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
byte[] encryptionKey) {
if (pendingFormatAdjustment) {
format(lastUnadjustedFormat);
}
if (!startWriteOperation()) {
infoQueue.commitSampleTimestamp(timeUs);
return;
......
......@@ -102,4 +102,5 @@ public interface Extractor {
* Releases all kept resources.
*/
void release();
}
......@@ -23,17 +23,18 @@ public interface ExtractorOutput {
/**
* Called by the {@link Extractor} to get the {@link TrackOutput} for a specific track.
* <p>
* The same {@link TrackOutput} is returned if multiple calls are made with the same
* {@code trackId}.
* The same {@link TrackOutput} is returned if multiple calls are made with the same {@code id}.
*
* @param trackId A track identifier.
* @param id A track identifier.
* @param type The type of the track. Typically one of the {@link com.google.android.exoplayer2.C}
* {@code TRACK_TYPE_*} constants.
* @return The {@link TrackOutput} for the given track identifier.
*/
TrackOutput track(int trackId);
TrackOutput track(int id, int type);
/**
* Called when all tracks have been identified, meaning no new {@code trackId} values will be
* passed to {@link #track(int)}.
* passed to {@link #track(int, int)}.
*/
void endTracks();
......
......@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.extractor.flv;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
......@@ -183,10 +184,12 @@ public final class FlvExtractor implements Extractor, SeekMap {
boolean hasAudio = (flags & 0x04) != 0;
boolean hasVideo = (flags & 0x01) != 0;
if (hasAudio && audioReader == null) {
audioReader = new AudioTagPayloadReader(extractorOutput.track(TAG_TYPE_AUDIO));
audioReader = new AudioTagPayloadReader(
extractorOutput.track(TAG_TYPE_AUDIO, C.TRACK_TYPE_AUDIO));
}
if (hasVideo && videoReader == null) {
videoReader = new VideoTagPayloadReader(extractorOutput.track(TAG_TYPE_VIDEO));
videoReader = new VideoTagPayloadReader(
extractorOutput.track(TAG_TYPE_VIDEO, C.TRACK_TYPE_VIDEO));
}
if (metadataReader == null) {
metadataReader = new ScriptTagPayloadReader(null);
......
......@@ -546,11 +546,9 @@ public final class MatroskaExtractor implements Extractor {
}
break;
case ID_TRACK_ENTRY:
if (tracks.get(currentTrack.number) == null && isCodecSupported(currentTrack.codecId)) {
if (isCodecSupported(currentTrack.codecId)) {
currentTrack.initializeOutput(extractorOutput, currentTrack.number);
tracks.put(currentTrack.number, currentTrack);
} else {
// We've seen this track entry before, or the codec is unsupported. Do nothing.
}
currentTrack = null;
break;
......@@ -692,6 +690,9 @@ public final class MatroskaExtractor implements Extractor {
case 3:
currentTrack.stereoMode = C.STEREO_MODE_TOP_BOTTOM;
break;
case 15:
currentTrack.stereoMode = C.STEREO_MODE_STEREO_MESH;
break;
default:
break;
}
......@@ -1525,6 +1526,7 @@ public final class MatroskaExtractor implements Extractor {
throw new ParserException("Unrecognized codec identifier.");
}
int type;
Format format;
@C.SelectionFlags int selectionFlags = 0;
selectionFlags |= flagDefault ? C.SELECTION_FLAG_DEFAULT : 0;
......@@ -1532,10 +1534,12 @@ public final class MatroskaExtractor implements Extractor {
// TODO: Consider reading the name elements of the tracks and, if present, incorporating them
// into the trackId passed when creating the formats.
if (MimeTypes.isAudio(mimeType)) {
type = C.TRACK_TYPE_AUDIO;
format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null,
Format.NO_VALUE, maxInputSize, channelCount, sampleRate, pcmEncoding,
initializationData, drmInitData, selectionFlags, language);
} else if (MimeTypes.isVideo(mimeType)) {
type = C.TRACK_TYPE_VIDEO;
if (displayUnit == Track.DISPLAY_UNIT_PIXELS) {
displayWidth = displayWidth == Format.NO_VALUE ? width : displayWidth;
displayHeight = displayHeight == Format.NO_VALUE ? height : displayHeight;
......@@ -1548,10 +1552,12 @@ public final class MatroskaExtractor implements Extractor {
Format.NO_VALUE, maxInputSize, width, height, Format.NO_VALUE, initializationData,
Format.NO_VALUE, pixelWidthHeightRatio, projectionData, stereoMode, drmInitData);
} else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) {
type = C.TRACK_TYPE_TEXT;
format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null,
Format.NO_VALUE, selectionFlags, language, drmInitData);
} else if (MimeTypes.APPLICATION_VOBSUB.equals(mimeType)
|| MimeTypes.APPLICATION_PGS.equals(mimeType)) {
type = C.TRACK_TYPE_TEXT;
format = Format.createImageSampleFormat(Integer.toString(trackId), mimeType, null,
Format.NO_VALUE, initializationData, language, drmInitData);
} else if (MimeTypes.TEXT_SSA.equals(mimeType)) {
......@@ -1561,7 +1567,7 @@ public final class MatroskaExtractor implements Extractor {
throw new ParserException("Unexpected MIME type.");
}
this.output = output.track(number);
this.output = output.track(number, type);
this.output.format(format);
}
......
......@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.extractor.mp3;
import android.support.annotation.IntDef;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException;
......@@ -33,6 +34,8 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.io.EOFException;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Extracts data from an MP3 file.
......@@ -52,6 +55,18 @@ public final class Mp3Extractor implements Extractor {
};
/**
* Flags controlling the behavior of the extractor.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {FLAG_ENABLE_CONSTANT_BITRATE_SEEKING})
public @interface Flags {}
/**
* Flag to force enable seeking using a constant bitrate assumption in cases where seeking would
* otherwise not be possible.
*/
public static final int FLAG_ENABLE_CONSTANT_BITRATE_SEEKING = 1;
/**
* The maximum number of bytes to search when synchronizing, before giving up.
*/
private static final int MAX_SYNC_BYTES = 128 * 1024;
......@@ -72,6 +87,7 @@ public final class Mp3Extractor implements Extractor {
private static final int INFO_HEADER = Util.getIntegerCodeForString("Info");
private static final int VBRI_HEADER = Util.getIntegerCodeForString("VBRI");
@Flags private final int flags;
private final long forcedFirstSampleTimestampUs;
private final ParsableByteArray scratch;
private final MpegAudioHeader synchronizedHeader;
......@@ -93,16 +109,27 @@ public final class Mp3Extractor implements Extractor {
* Constructs a new {@link Mp3Extractor}.
*/
public Mp3Extractor() {
this(C.TIME_UNSET);
this(0);
}
/**
* Constructs a new {@link Mp3Extractor}.
*
* @param flags Flags that control the extractor's behavior.
*/
public Mp3Extractor(@Flags int flags) {
this(flags, C.TIME_UNSET);
}
/**
* Constructs a new {@link Mp3Extractor}.
*
* @param flags Flags that control the extractor's behavior.
* @param forcedFirstSampleTimestampUs A timestamp to force for the first sample, or
* {@link C#TIME_UNSET} if forcing is not required.
*/
public Mp3Extractor(long forcedFirstSampleTimestampUs) {
public Mp3Extractor(@Flags int flags, long forcedFirstSampleTimestampUs) {
this.flags = flags;
this.forcedFirstSampleTimestampUs = forcedFirstSampleTimestampUs;
scratch = new ParsableByteArray(SCRATCH_LENGTH);
synchronizedHeader = new MpegAudioHeader();
......@@ -118,7 +145,7 @@ public final class Mp3Extractor implements Extractor {
@Override
public void init(ExtractorOutput output) {
extractorOutput = output;
trackOutput = extractorOutput.track(0);
trackOutput = extractorOutput.track(0, C.TRACK_TYPE_AUDIO);
extractorOutput.endTracks();
}
......@@ -350,7 +377,8 @@ public final class Mp3Extractor implements Extractor {
}
}
if (seeker == null) {
if (seeker == null || (!seeker.isSeekable()
&& (flags & FLAG_ENABLE_CONSTANT_BITRATE_SEEKING) != 0)) {
// Repopulate the synchronized header in case we had to skip an invalid seeking header, which
// would give an invalid CBR bitrate.
input.resetPeekPosition();
......
......@@ -135,6 +135,7 @@ import java.util.List;
public static final int TYPE_vp09 = Util.getIntegerCodeForString("vp09");
public static final int TYPE_vpcC = Util.getIntegerCodeForString("vpcC");
public static final int TYPE_camm = Util.getIntegerCodeForString("camm");
public static final int TYPE_alac = Util.getIntegerCodeForString("alac");
public final int type;
......
......@@ -332,6 +332,9 @@ import java.util.List;
return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags);
}
// Omit any sample at the end point of an edit for audio tracks.
boolean omitClippedSample = track.type == C.TRACK_TYPE_AUDIO;
// Count the number of samples after applying edits.
int editedSampleCount = 0;
int nextSampleIndex = 0;
......@@ -342,7 +345,8 @@ import java.util.List;
long duration = Util.scaleLargeTimestamp(track.editListDurations[i], track.timescale,
track.movieTimescale);
int startIndex = Util.binarySearchCeil(timestamps, mediaTime, true, true);
int endIndex = Util.binarySearchCeil(timestamps, mediaTime + duration, true, false);
int endIndex = Util.binarySearchCeil(timestamps, mediaTime + duration, omitClippedSample,
false);
editedSampleCount += endIndex - startIndex;
copyMetadata |= nextSampleIndex != startIndex;
nextSampleIndex = endIndex;
......@@ -365,7 +369,7 @@ import java.util.List;
long endMediaTime = mediaTime + Util.scaleLargeTimestamp(duration, track.timescale,
track.movieTimescale);
int startIndex = Util.binarySearchCeil(timestamps, mediaTime, true, true);
int endIndex = Util.binarySearchCeil(timestamps, endMediaTime, true, false);
int endIndex = Util.binarySearchCeil(timestamps, endMediaTime, omitClippedSample, false);
if (copyMetadata) {
int count = endIndex - startIndex;
System.arraycopy(offsets, startIndex, editedOffsets, sampleIndex, count);
......@@ -604,7 +608,7 @@ import java.util.List;
|| childAtomType == Atom.TYPE_dtsh || childAtomType == Atom.TYPE_dtsl
|| childAtomType == Atom.TYPE_samr || childAtomType == Atom.TYPE_sawb
|| childAtomType == Atom.TYPE_lpcm || childAtomType == Atom.TYPE_sowt
|| childAtomType == Atom.TYPE__mp3) {
|| childAtomType == Atom.TYPE__mp3 || childAtomType == Atom.TYPE_alac) {
parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId,
language, isQuickTime, drmInitData, out, i);
} else if (childAtomType == Atom.TYPE_TTML) {
......@@ -716,6 +720,9 @@ import java.util.List;
case 2:
stereoMode = C.STEREO_MODE_LEFT_RIGHT;
break;
case 3:
stereoMode = C.STEREO_MODE_STEREO_MESH;
break;
default:
break;
}
......@@ -839,6 +846,8 @@ import java.util.List;
mimeType = MimeTypes.AUDIO_RAW;
} else if (atomType == Atom.TYPE__mp3) {
mimeType = MimeTypes.AUDIO_MPEG;
} else if (atomType == Atom.TYPE_alac) {
mimeType = MimeTypes.AUDIO_ALAC;
}
byte[] initializationData = null;
......@@ -876,6 +885,10 @@ import java.util.List;
out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null,
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, null, drmInitData, 0,
language);
} else if (childAtomType == Atom.TYPE_alac) {
initializationData = new byte[childAtomSize];
parent.setPosition(childPosition);
parent.readBytes(initializationData, 0, childAtomSize);
}
childPosition += childAtomSize;
}
......
......@@ -344,7 +344,8 @@ public final class Mp4Extractor implements Extractor, SeekMap {
continue;
}
Mp4Track mp4Track = new Mp4Track(track, trackSampleTable, extractorOutput.track(i));
Mp4Track mp4Track = new Mp4Track(track, trackSampleTable,
extractorOutput.track(i, track.type));
// Each sample has up to three bytes of overhead for the start code that replaces its length.
// Allow ten source samples per output sample, like the platform extractor.
int maxInputSize = trackSampleTable.maximumSize + 3 * 10;
......
......@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.extractor.ogg;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput;
......@@ -75,7 +76,7 @@ public class OggExtractor implements Extractor {
@Override
public void init(ExtractorOutput output) {
TrackOutput trackOutput = output.track(0);
TrackOutput trackOutput = output.track(0, C.TRACK_TYPE_AUDIO);
output.endTracks();
// TODO: fix the case if sniff() isn't called
streamReader.init(output, trackOutput);
......
......@@ -65,7 +65,7 @@ public final class RawCcExtractor implements Extractor {
@Override
public void init(ExtractorOutput output) {
output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET));
trackOutput = output.track(0);
trackOutput = output.track(0, C.TRACK_TYPE_TEXT);
output.endTracks();
trackOutput.format(format);
}
......
......@@ -39,6 +39,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
private final ParsableByteArray headerScratchBytes;
private final String language;
private String trackFormatId;
private TrackOutput output;
private int state;
......@@ -84,7 +85,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator generator) {
output = extractorOutput.track(generator.getNextId());
generator.generateNewId();
trackFormatId = generator.getFormatId();
output = extractorOutput.track(generator.getTrackId(), C.TRACK_TYPE_AUDIO);
}
@Override
......@@ -180,8 +183,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
headerScratchBits.skipBits(40);
isEac3 = headerScratchBits.readBits(5) == 16;
headerScratchBits.setPosition(headerScratchBits.getPosition() - 45);
format = isEac3 ? Ac3Util.parseEac3SyncframeFormat(headerScratchBits, null, language , null)
: Ac3Util.parseAc3SyncframeFormat(headerScratchBits, null, language, null);
format = isEac3
? Ac3Util.parseEac3SyncframeFormat(headerScratchBits, trackFormatId, language , null)
: Ac3Util.parseAc3SyncframeFormat(headerScratchBits, trackFormatId, language, null);
output.format(format);
}
sampleSize = isEac3 ? Ac3Util.parseEAc3SyncframeSize(headerScratchBits.data)
......
......@@ -61,6 +61,7 @@ import java.util.Collections;
private final ParsableByteArray id3HeaderBuffer;
private final String language;
private String formatId;
private TrackOutput output;
private TrackOutput id3Output;
......@@ -108,11 +109,14 @@ import java.util.Collections;
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
idGenerator.generateNewId();
formatId = idGenerator.getFormatId();
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_AUDIO);
if (exposeId3) {
id3Output = extractorOutput.track(idGenerator.getNextId());
id3Output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, null,
Format.NO_VALUE, null));
idGenerator.generateNewId();
id3Output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_METADATA);
id3Output.format(Format.createSampleFormat(idGenerator.getFormatId(),
MimeTypes.APPLICATION_ID3, null, Format.NO_VALUE, null));
} else {
id3Output = new DummyTrackOutput();
}
......@@ -300,7 +304,7 @@ import java.util.Collections;
Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(
audioSpecificConfig);
Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_AAC, null,
Format format = Format.createAudioSampleFormat(formatId, MimeTypes.AUDIO_AAC, null,
Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first,
Collections.singletonList(audioSpecificConfig), null, 0, language);
// In this class a sample is an access unit, but the MediaFormat sample rate specifies the
......
......@@ -74,10 +74,11 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
case TsExtractor.TS_STREAM_TYPE_H262:
return new PesReader(new H262Reader());
case TsExtractor.TS_STREAM_TYPE_H264:
return isSet(FLAG_IGNORE_H264_STREAM) ? null : new PesReader(
new H264Reader(isSet(FLAG_ALLOW_NON_IDR_KEYFRAMES), isSet(FLAG_DETECT_ACCESS_UNITS)));
return isSet(FLAG_IGNORE_H264_STREAM) ? null
: new PesReader(new H264Reader(new SeiReader(), isSet(FLAG_ALLOW_NON_IDR_KEYFRAMES),
isSet(FLAG_DETECT_ACCESS_UNITS)));
case TsExtractor.TS_STREAM_TYPE_H265:
return new PesReader(new H265Reader());
return new PesReader(new H265Reader(new SeiReader()));
case TsExtractor.TS_STREAM_TYPE_SPLICE_INFO:
return isSet(FLAG_IGNORE_SPLICE_INFO_STREAM)
? null : new SectionReader(new SpliceInfoSectionReader());
......
......@@ -39,6 +39,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
private final ParsableByteArray headerScratchBytes;
private final String language;
private String formatId;
private TrackOutput output;
private int state;
......@@ -79,7 +80,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
idGenerator.generateNewId();
formatId = idGenerator.getFormatId();
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_AUDIO);
}
@Override
......@@ -165,7 +168,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
private void parseHeader() {
byte[] frameData = headerScratchBytes.data;
if (format == null) {
format = DtsUtil.parseDtsFormat(frameData, null, language, null);
format = DtsUtil.parseDtsFormat(frameData, formatId, language, null);
output.format(format);
}
sampleSize = DtsUtil.getDtsFrameSize(frameData);
......
......@@ -37,6 +37,7 @@ import java.util.Collections;
private static final int START_EXTENSION = 0xB5;
private static final int START_GROUP = 0xB8;
private String formatId;
private TrackOutput output;
// Maps (frame_rate_code - 1) indices to values, as defined in ITU-T H.262 Table 6-4.
......@@ -78,7 +79,9 @@ import java.util.Collections;
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
idGenerator.generateNewId();
formatId = idGenerator.getFormatId();
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_VIDEO);
}
@Override
......@@ -126,7 +129,7 @@ import java.util.Collections;
int bytesAlreadyPassed = lengthToStartCode < 0 ? -lengthToStartCode : 0;
if (csdBuffer.onStartCode(startCodeValue, bytesAlreadyPassed)) {
// The csd data is complete, so we can decode and output the media format.
Pair<Format, Long> result = parseCsdBuffer(csdBuffer);
Pair<Format, Long> result = parseCsdBuffer(csdBuffer, formatId);
output.format(result.first);
frameDurationUs = result.second;
hasOutputFormat = true;
......@@ -166,10 +169,11 @@ import java.util.Collections;
* Parses the {@link Format} and frame duration from a csd buffer.
*
* @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.
*/
private static Pair<Format, Long> parseCsdBuffer(CsdBuffer csdBuffer) {
private static Pair<Format, Long> parseCsdBuffer(CsdBuffer csdBuffer, String formatId) {
byte[] csdData = Arrays.copyOf(csdBuffer.data, csdBuffer.length);
int firstByte = csdData[4] & 0xFF;
......@@ -195,7 +199,7 @@ import java.util.Collections;
break;
}
Format format = Format.createVideoSampleFormat(null, MimeTypes.VIDEO_MPEG2, null,
Format format = Format.createVideoSampleFormat(formatId, MimeTypes.VIDEO_MPEG2, null,
Format.NO_VALUE, Format.NO_VALUE, width, height, Format.NO_VALUE,
Collections.singletonList(csdData), Format.NO_VALUE, pixelWidthHeightRatio, null);
......
......@@ -39,6 +39,7 @@ import java.util.List;
private static final int NAL_UNIT_TYPE_SPS = 7; // Sequence parameter set
private static final int NAL_UNIT_TYPE_PPS = 8; // Picture parameter set
private final SeiReader seiReader;
private final boolean allowNonIdrKeyframes;
private final boolean detectAccessUnits;
private final NalUnitTargetBuffer sps;
......@@ -47,8 +48,8 @@ import java.util.List;
private long totalBytesWritten;
private final boolean[] prefixFlags;
private String formatId;
private TrackOutput output;
private SeiReader seiReader;
private SampleReader sampleReader;
// State that should not be reset on seek.
......@@ -61,15 +62,17 @@ import java.util.List;
private final ParsableByteArray seiWrapper;
/**
* @param seiReader An SEI reader for consuming closed caption channels.
* @param allowNonIdrKeyframes Whether to treat samples consisting of non-IDR I slices as
* synchronization samples (key-frames).
* @param detectAccessUnits Whether to split the input stream into access units (samples) based on
* slice headers. Pass {@code false} if the stream contains access unit delimiters (AUDs).
*/
public H264Reader(boolean allowNonIdrKeyframes, boolean detectAccessUnits) {
prefixFlags = new boolean[3];
public H264Reader(SeiReader seiReader, boolean allowNonIdrKeyframes, boolean detectAccessUnits) {
this.seiReader = seiReader;
this.allowNonIdrKeyframes = allowNonIdrKeyframes;
this.detectAccessUnits = detectAccessUnits;
prefixFlags = new boolean[3];
sps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SPS, 128);
pps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_PPS, 128);
sei = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SEI, 128);
......@@ -88,9 +91,11 @@ import java.util.List;
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
idGenerator.generateNewId();
formatId = idGenerator.getFormatId();
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_VIDEO);
sampleReader = new SampleReader(output, allowNonIdrKeyframes, detectAccessUnits);
seiReader = new SeiReader(extractorOutput.track(idGenerator.getNextId()));
seiReader.createTracks(extractorOutput, idGenerator);
}
@Override
......@@ -175,7 +180,7 @@ import java.util.List;
initializationData.add(Arrays.copyOf(pps.nalData, pps.nalLength));
NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(sps.nalData, 3, sps.nalLength);
NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(pps.nalData, 3, pps.nalLength);
output.format(Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H264, null,
output.format(Format.createVideoSampleFormat(formatId, MimeTypes.VIDEO_H264, null,
Format.NO_VALUE, Format.NO_VALUE, spsData.width, spsData.height, Format.NO_VALUE,
initializationData, Format.NO_VALUE, spsData.pixelWidthAspectRatio, null));
hasOutputFormat = true;
......
......@@ -44,9 +44,11 @@ import java.util.Collections;
private static final int PREFIX_SEI_NUT = 39;
private static final int SUFFIX_SEI_NUT = 40;
private final SeiReader seiReader;
private String formatId;
private TrackOutput output;
private SampleReader sampleReader;
private SeiReader seiReader;
// State that should not be reset on seek.
private boolean hasOutputFormat;
......@@ -66,7 +68,11 @@ import java.util.Collections;
// Scratch variables to avoid allocations.
private final ParsableByteArray seiWrapper;
public H265Reader() {
/**
* @param seiReader An SEI reader for consuming closed caption channels.
*/
public H265Reader(SeiReader seiReader) {
this.seiReader = seiReader;
prefixFlags = new boolean[3];
vps = new NalUnitTargetBuffer(VPS_NUT, 128);
sps = new NalUnitTargetBuffer(SPS_NUT, 128);
......@@ -90,9 +96,11 @@ import java.util.Collections;
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
idGenerator.generateNewId();
formatId = idGenerator.getFormatId();
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_VIDEO);
sampleReader = new SampleReader(output);
seiReader = new SeiReader(extractorOutput.track(idGenerator.getNextId()));
seiReader.createTracks(extractorOutput, idGenerator);
}
@Override
......@@ -183,7 +191,7 @@ import java.util.Collections;
sps.endNalUnit(discardPadding);
pps.endNalUnit(discardPadding);
if (vps.isCompleted() && sps.isCompleted() && pps.isCompleted()) {
output.format(parseMediaFormat(vps, sps, pps));
output.format(parseMediaFormat(formatId, vps, sps, pps));
hasOutputFormat = true;
}
}
......@@ -205,8 +213,8 @@ import java.util.Collections;
}
}
private static Format parseMediaFormat(NalUnitTargetBuffer vps, NalUnitTargetBuffer sps,
NalUnitTargetBuffer pps) {
private static Format parseMediaFormat(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);
......@@ -311,7 +319,7 @@ import java.util.Collections;
}
}
return Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H265, null, Format.NO_VALUE,
return Format.createVideoSampleFormat(formatId, MimeTypes.VIDEO_H265, null, Format.NO_VALUE,
Format.NO_VALUE, picWidthInLumaSamples, picHeightInLumaSamples, Format.NO_VALUE,
Collections.singletonList(csd), Format.NO_VALUE, pixelWidthHeightRatio, null);
}
......
......@@ -56,9 +56,10 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, null, Format.NO_VALUE,
null));
idGenerator.generateNewId();
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_METADATA);
output.format(Format.createSampleFormat(idGenerator.getFormatId(), MimeTypes.APPLICATION_ID3,
null, Format.NO_VALUE, null));
}
@Override
......
......@@ -38,6 +38,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
private final MpegAudioHeader header;
private final String language;
private String formatId;
private TrackOutput output;
private int state;
......@@ -76,7 +77,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
idGenerator.generateNewId();
formatId = idGenerator.getFormatId();
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_AUDIO);
}
@Override
......@@ -176,9 +179,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
frameSize = header.frameSize;
if (!hasOutputFormat) {
frameDurationUs = (C.MICROS_PER_SECOND * header.samplesPerFrame) / header.sampleRate;
Format format = Format.createAudioSampleFormat(null, header.mimeType, null, Format.NO_VALUE,
MpegAudioHeader.MAX_FRAME_SIZE_BYTES, header.channels, header.sampleRate, null, null, 0,
language);
Format format = Format.createAudioSampleFormat(formatId, header.mimeType, null,
Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, header.channels, header.sampleRate,
null, null, 0, language);
output.format(format);
hasOutputFormat = true;
}
......
......@@ -16,12 +16,11 @@
package com.google.android.exoplayer2.extractor.ts;
import android.util.Log;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
/**
* Parses PES packet data and extracts samples.
......
......@@ -23,10 +23,10 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
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.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
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;
/**
......
......@@ -16,10 +16,10 @@
package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
/**
* Reads section data.
......
......@@ -17,8 +17,8 @@ package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import com.google.android.exoplayer2.util.Util;
/**
......
......@@ -17,8 +17,10 @@ package com.google.android.exoplayer2.extractor.ts;
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.text.cea.Cea608Decoder;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.text.cea.CeaUtil;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
......@@ -27,49 +29,17 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
*/
/* package */ final class SeiReader {
private final TrackOutput output;
private TrackOutput output;
public SeiReader(TrackOutput output) {
this.output = output;
output.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, null,
Format.NO_VALUE, 0, null, null));
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
idGenerator.generateNewId();
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT);
output.format(Format.createTextSampleFormat(idGenerator.getFormatId(),
MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, null, null));
}
public void consume(long pesTimeUs, ParsableByteArray seiBuffer) {
int b;
while (seiBuffer.bytesLeft() > 1 /* last byte will be rbsp_trailing_bits */) {
// Parse payload type.
int payloadType = 0;
do {
b = seiBuffer.readUnsignedByte();
payloadType += b;
} while (b == 0xFF);
// Parse payload size.
int payloadSize = 0;
do {
b = seiBuffer.readUnsignedByte();
payloadSize += b;
} while (b == 0xFF);
// Process the payload.
if (Cea608Decoder.isSeiMessageCea608(payloadType, payloadSize, seiBuffer)) {
// Ignore country_code (1) + provider_code (2) + user_identifier (4)
// + user_data_type_code (1).
seiBuffer.skipBytes(8);
// Ignore first three bits: reserved (1) + process_cc_data_flag (1) + zero_bit (1).
int ccCount = seiBuffer.readUnsignedByte() & 0x1F;
// Ignore em_data (1)
seiBuffer.skipBytes(1);
// Each data packet consists of 24 bits: marker bits (5) + cc_valid (1) + cc_type (2)
// + cc_data_1 (8) + cc_data_2 (8).
int sampleLength = ccCount * 3;
output.sampleData(seiBuffer, sampleLength);
output.sampleMetadata(pesTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleLength, 0, null);
// Ignore trailing information in SEI, if any.
seiBuffer.skipBytes(payloadSize - (10 + ccCount * 3));
} else {
seiBuffer.skipBytes(payloadSize);
}
}
CeaUtil.consume(pesTimeUs, seiBuffer, output);
}
}
......@@ -18,10 +18,10 @@ package com.google.android.exoplayer2.extractor.ts;
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.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
/**
* Parses splice info sections as defined by SCTE35.
......@@ -36,9 +36,10 @@ public final class SpliceInfoSectionReader implements SectionPayloadReader {
public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput,
TsPayloadReader.TrackIdGenerator idGenerator) {
this.timestampAdjuster = timestampAdjuster;
output = extractorOutput.track(idGenerator.getNextId());
output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_SCTE35, null,
Format.NO_VALUE, null));
idGenerator.generateNewId();
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_METADATA);
output.format(Format.createSampleFormat(idGenerator.getFormatId(), MimeTypes.APPLICATION_SCTE35,
null, Format.NO_VALUE, null));
}
@Override
......
......@@ -25,16 +25,19 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
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.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo;
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 com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Facilitates the extraction of data from the MPEG-2 TS container format.
......@@ -79,7 +82,7 @@ public final class TsExtractor implements Extractor {
private static final int BUFFER_SIZE = TS_PACKET_SIZE * BUFFER_PACKET_COUNT;
private final boolean hlsMode;
private final TimestampAdjuster timestampAdjuster;
private final List<TimestampAdjuster> timestampAdjusters;
private final ParsableByteArray tsPacketBuffer;
private final ParsableBitArray tsScratch;
private final SparseIntArray continuityCounters;
......@@ -89,18 +92,12 @@ public final class TsExtractor implements Extractor {
// Accessed only by the loading thread.
private ExtractorOutput output;
private int remainingPmts;
private boolean tracksEnded;
private TsPayloadReader id3Reader;
public TsExtractor() {
this(new TimestampAdjuster(0));
}
/**
* @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps.
*/
public TsExtractor(TimestampAdjuster timestampAdjuster) {
this(timestampAdjuster, new DefaultTsPayloadReaderFactory(), false);
this(new TimestampAdjuster(0), new DefaultTsPayloadReaderFactory(), false);
}
/**
......@@ -111,7 +108,12 @@ public final class TsExtractor implements Extractor {
*/
public TsExtractor(TimestampAdjuster timestampAdjuster,
TsPayloadReader.Factory payloadReaderFactory, boolean hlsMode) {
this.timestampAdjuster = timestampAdjuster;
if (hlsMode) {
timestampAdjusters = Collections.singletonList(timestampAdjuster);
} else {
timestampAdjusters = new ArrayList<>();
timestampAdjusters.add(timestampAdjuster);
}
this.payloadReaderFactory = Assertions.checkNotNull(payloadReaderFactory);
this.hlsMode = hlsMode;
tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE);
......@@ -150,7 +152,10 @@ public final class TsExtractor implements Extractor {
@Override
public void seek(long position, long timeUs) {
timestampAdjuster.reset();
int timestampAdjustersCount = timestampAdjusters.size();
for (int i = 0; i < timestampAdjustersCount; i++) {
timestampAdjusters.get(i).reset();
}
tsPacketBuffer.reset();
continuityCounters.clear();
// Elementary stream readers' state should be cleared to get consistent behaviours when seeking.
......@@ -307,8 +312,12 @@ public final class TsExtractor implements Extractor {
} else {
int pid = patScratch.readBits(13);
tsPayloadReaders.put(pid, new SectionReader(new PmtReader(pid)));
remainingPmts++;
}
}
if (!hlsMode) {
tsPayloadReaders.remove(TS_PAT_PID);
}
}
}
......@@ -345,10 +354,21 @@ public final class TsExtractor implements Extractor {
// See ISO/IEC 13818-1, section 2.4.4.4 for more information on table id assignment.
return;
}
// section_syntax_indicator(1), '0'(1), reserved(2), section_length(12), program_number (16),
// reserved (2), version_number (5), current_next_indicator (1), // section_number (8),
// TimestampAdjuster assignment.
TimestampAdjuster timestampAdjuster;
if (hlsMode || remainingPmts == 1) {
timestampAdjuster = timestampAdjusters.get(0);
} else {
timestampAdjuster = new TimestampAdjuster(timestampAdjusters.get(0).firstSampleTimestampUs);
timestampAdjusters.add(timestampAdjuster);
}
// section_syntax_indicator(1), '0'(1), reserved(2), section_length(12)
sectionData.skipBytes(2);
int programNumber = sectionData.readUnsignedShort();
// reserved (2), version_number (5), current_next_indicator (1), section_number (8),
// last_section_number (8), reserved (3), PCR_PID (13)
sectionData.skipBytes(9);
sectionData.skipBytes(5);
// Read program_info_length.
sectionData.readBytes(pmtScratch, 2);
......@@ -364,7 +384,7 @@ public final class TsExtractor implements Extractor {
EsInfo dummyEsInfo = new EsInfo(TS_STREAM_TYPE_ID3, null, new byte[0]);
id3Reader = payloadReaderFactory.createPayloadReader(TS_STREAM_TYPE_ID3, dummyEsInfo);
id3Reader.init(timestampAdjuster, output,
new TrackIdGenerator(TS_STREAM_TYPE_ID3, MAX_PID_PLUS_ONE));
new TrackIdGenerator(programNumber, TS_STREAM_TYPE_ID3, MAX_PID_PLUS_ONE));
}
int remainingEntriesLength = sectionData.bytesLeft();
......@@ -393,7 +413,8 @@ public final class TsExtractor implements Extractor {
} else {
reader = payloadReaderFactory.createPayloadReader(streamType, esInfo);
if (reader != null) {
reader.init(timestampAdjuster, output, new TrackIdGenerator(trackId, MAX_PID_PLUS_ONE));
reader.init(timestampAdjuster, output,
new TrackIdGenerator(programNumber, trackId, MAX_PID_PLUS_ONE));
}
}
......@@ -404,13 +425,17 @@ public final class TsExtractor implements Extractor {
if (hlsMode) {
if (!tracksEnded) {
output.endTracks();
remainingPmts = 0;
tracksEnded = true;
}
} else {
tsPayloadReaders.remove(TS_PAT_PID);
tsPayloadReaders.remove(pid);
output.endTracks();
remainingPmts--;
if (remainingPmts == 0) {
output.endTracks();
tracksEnded = true;
}
}
tracksEnded = true;
}
/**
......
......@@ -17,9 +17,9 @@ package com.google.android.exoplayer2.extractor.ts;
import android.util.SparseArray;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
/**
* Parses TS packet payload data.
......@@ -81,17 +81,63 @@ public interface TsPayloadReader {
*/
final class TrackIdGenerator {
private final int firstId;
private final int idIncrement;
private int generatedIdCount;
private static final int ID_UNSET = Integer.MIN_VALUE;
public TrackIdGenerator(int firstId, int idIncrement) {
this.firstId = firstId;
this.idIncrement = idIncrement;
private final String formatIdPrefix;
private final int firstTrackId;
private final int trackIdIncrement;
private int trackId;
private String formatId;
public TrackIdGenerator(int firstTrackId, int trackIdIncrement) {
this(ID_UNSET, firstTrackId, trackIdIncrement);
}
public TrackIdGenerator(int programNumber, int firstTrackId, int trackIdIncrement) {
this.formatIdPrefix = programNumber != ID_UNSET ? programNumber + "/" : "";
this.firstTrackId = firstTrackId;
this.trackIdIncrement = trackIdIncrement;
trackId = ID_UNSET;
}
/**
* Generates a new set of track and track format ids. Must be called before {@code get*}
* methods.
*/
public void generateNewId() {
trackId = trackId == ID_UNSET ? firstTrackId : trackId + trackIdIncrement;
formatId = formatIdPrefix + trackId;
}
/**
* Returns the last generated track id. Must be called after the first {@link #generateNewId()}
* call.
*
* @return The last generated track id.
*/
public int getTrackId() {
maybeThrowUninitializedError();
return trackId;
}
/**
* Returns the last generated format id, with the format {@code "programNumber/trackId"}. If no
* {@code programNumber} was provided, the {@code trackId} alone is used as format id. Must be
* called after the first {@link #generateNewId()} call.
*
* @return The last generated format id, with the format {@code "programNumber/trackId"}. If no
* {@code programNumber} was provided, the {@code trackId} alone is used as
* format id.
*/
public String getFormatId() {
maybeThrowUninitializedError();
return formatId;
}
public int getNextId() {
return firstId + idIncrement * generatedIdCount++;
private void maybeThrowUninitializedError() {
if (trackId == ID_UNSET) {
throw new IllegalStateException("generateNewId() must be called before retrieving ids.");
}
}
}
......
......@@ -60,7 +60,7 @@ public final class WavExtractor implements Extractor, SeekMap {
@Override
public void init(ExtractorOutput output) {
extractorOutput = output;
trackOutput = output.track(0);
trackOutput = output.track(0, C.TRACK_TYPE_AUDIO);
wavHeader = null;
output.endTracks();
}
......
......@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.mediacodec;
import android.annotation.TargetApi;
import android.graphics.Point;
import android.media.MediaCodec;
import android.media.MediaCodecInfo.AudioCapabilities;
import android.media.MediaCodecInfo.CodecCapabilities;
......@@ -23,6 +24,7 @@ import android.media.MediaCodecInfo.CodecProfileLevel;
import android.media.MediaCodecInfo.VideoCapabilities;
import android.util.Log;
import android.util.Pair;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
......@@ -142,70 +144,68 @@ public final class MediaCodecInfo {
}
/**
* Whether the decoder supports video with a specified width and height.
* Whether the decoder supports video with a given width, height and frame rate.
* <p>
* Must not be called if the device SDK version is less than 21.
*
* @param width Width in pixels.
* @param height Height in pixels.
* @return Whether the decoder supports video with the given width and height.
* @param frameRate Optional frame rate in frames per second. Ignored if set to
* {@link Format#NO_VALUE} or any value less than or equal to 0.
* @return Whether the decoder supports video with the given width, height and frame rate.
*/
@TargetApi(21)
public boolean isVideoSizeSupportedV21(int width, int height) {
public boolean isVideoSizeAndRateSupportedV21(int width, int height, double frameRate) {
if (capabilities == null) {
logNoSupport("size.caps");
logNoSupport("sizeAndRate.caps");
return false;
}
VideoCapabilities videoCapabilities = capabilities.getVideoCapabilities();
if (videoCapabilities == null) {
logNoSupport("size.vCaps");
logNoSupport("sizeAndRate.vCaps");
return false;
}
if (!videoCapabilities.isSizeSupported(width, height)) {
if (!areSizeAndRateSupported(videoCapabilities, width, height, frameRate)) {
// Capabilities are known to be inaccurately reported for vertical resolutions on some devices
// (b/31387661). If the video is vertical and the capabilities indicate support if the width
// and height are swapped, we assume that the vertical resolution is also supported.
if (width >= height || !videoCapabilities.isSizeSupported(height, width)) {
logNoSupport("size.support, " + width + "x" + height);
if (width >= height
|| !areSizeAndRateSupported(videoCapabilities, height, width, frameRate)) {
logNoSupport("sizeAndRate.support, " + width + "x" + height + "x" + frameRate);
return false;
}
logAssumedSupport("size.rotated, " + width + "x" + height);
logAssumedSupport("sizeAndRate.rotated, " + width + "x" + height + "x" + frameRate);
}
return true;
}
/**
* Whether the decoder supports video with a given width, height and frame rate.
* Returns the smallest video size greater than or equal to a specified size that also satisfies
* the {@link MediaCodec}'s width and height alignment requirements.
* <p>
* Must not be called if the device SDK version is less than 21.
*
* @param width Width in pixels.
* @param height Height in pixels.
* @param frameRate Frame rate in frames per second.
* @return Whether the decoder supports video with the given width, height and frame rate.
* @return The smallest video size greater than or equal to the specified size that also satisfies
* the {@link MediaCodec}'s width and height alignment requirements, or null if not a video
* codec.
*/
@TargetApi(21)
public boolean isVideoSizeAndRateSupportedV21(int width, int height, double frameRate) {
public Point alignVideoSizeV21(int width, int height) {
if (capabilities == null) {
logNoSupport("sizeAndRate.caps");
return false;
logNoSupport("align.caps");
return null;
}
VideoCapabilities videoCapabilities = capabilities.getVideoCapabilities();
if (videoCapabilities == null) {
logNoSupport("sizeAndRate.vCaps");
return false;
logNoSupport("align.vCaps");
return null;
}
if (!videoCapabilities.areSizeAndRateSupported(width, height, frameRate)) {
// Capabilities are known to be inaccurately reported for vertical resolutions on some devices
// (b/31387661). If the video is vertical and the capabilities indicate support if the width
// and height are swapped, we assume that the vertical resolution is also supported.
if (width >= height || !videoCapabilities.areSizeAndRateSupported(height, width, frameRate)) {
logNoSupport("sizeAndRate.support, " + width + "x" + height + "x" + frameRate);
return false;
}
logAssumedSupport("sizeAndRate.rotated, " + width + "x" + height + "x" + frameRate);
}
return true;
int widthAlignment = videoCapabilities.getWidthAlignment();
int heightAlignment = videoCapabilities.getHeightAlignment();
return new Point(Util.ceilDivide(width, widthAlignment) * widthAlignment,
Util.ceilDivide(height, heightAlignment) * heightAlignment);
}
/**
......@@ -279,6 +279,14 @@ public final class MediaCodecInfo {
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback);
}
@TargetApi(21)
private static boolean areSizeAndRateSupported(VideoCapabilities capabilities, int width,
int height, double frameRate) {
return frameRate == Format.NO_VALUE || frameRate <= 0
? capabilities.isSizeSupported(width, height)
: capabilities.areSizeAndRateSupported(width, height, frameRate);
}
private static boolean isTunneling(CodecCapabilities capabilities) {
return Util.SDK_INT >= 21 && isTunnelingV21(capabilities);
}
......
......@@ -183,6 +183,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private boolean codecNeedsAdaptationWorkaround;
private boolean codecNeedsEosPropagationWorkaround;
private boolean codecNeedsEosFlushWorkaround;
private boolean codecNeedsEosOutputExceptionWorkaround;
private boolean codecNeedsMonoChannelCountWorkaround;
private boolean codecNeedsAdaptationWorkaroundBuffer;
private boolean shouldSkipAdaptationWorkaroundOutputBuffer;
......@@ -201,6 +202,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private boolean inputStreamEnded;
private boolean outputStreamEnded;
private boolean waitingForKeys;
private boolean waitingForFirstSyncFrame;
protected DecoderCounters decoderCounters;
......@@ -276,11 +278,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
/**
* Configures a newly created {@link MediaCodec}.
*
* @param codecInfo Information about the {@link MediaCodec} being configured.
* @param codec The {@link MediaCodec} to configure.
* @param format The format for which the codec is being configured.
* @param crypto For drm protected playbacks, a {@link MediaCrypto} to use for decryption.
* @throws DecoderQueryException If an error occurs querying {@code codecInfo}.
*/
protected abstract void configureCodec(MediaCodec codec, Format format, MediaCrypto crypto);
protected abstract void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format,
MediaCrypto crypto) throws DecoderQueryException;
@SuppressWarnings("deprecation")
protected final void maybeInitCodec() throws ExoPlaybackException {
......@@ -338,6 +343,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecNeedsAdaptationWorkaround = codecNeedsAdaptationWorkaround(codecName);
codecNeedsEosPropagationWorkaround = codecNeedsEosPropagationWorkaround(codecName);
codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName);
codecNeedsEosOutputExceptionWorkaround = codecNeedsEosOutputExceptionWorkaround(codecName);
codecNeedsMonoChannelCountWorkaround = codecNeedsMonoChannelCountWorkaround(codecName, format);
try {
long codecInitializingTimestamp = SystemClock.elapsedRealtime();
......@@ -345,7 +351,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codec = MediaCodec.createByCodecName(codecName);
TraceUtil.endSection();
TraceUtil.beginSection("configureCodec");
configureCodec(codec, format, mediaCrypto);
configureCodec(decoderInfo, codec, format, mediaCrypto);
TraceUtil.endSection();
TraceUtil.beginSection("startCodec");
codec.start();
......@@ -363,6 +369,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
? (SystemClock.elapsedRealtime() + MAX_CODEC_HOTSWAP_TIME_MS) : C.TIME_UNSET;
inputIndex = C.INDEX_UNSET;
outputIndex = C.INDEX_UNSET;
waitingForFirstSyncFrame = true;
decoderCounters.decoderInitCount++;
}
......@@ -501,13 +508,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecHotswapDeadlineMs = C.TIME_UNSET;
inputIndex = C.INDEX_UNSET;
outputIndex = C.INDEX_UNSET;
waitingForFirstSyncFrame = true;
waitingForKeys = false;
shouldSkipOutputBuffer = false;
decodeOnlyPresentationTimestamps.clear();
codecNeedsAdaptationWorkaroundBuffer = false;
shouldSkipAdaptationWorkaroundOutputBuffer = false;
if (codecNeedsFlushWorkaround || (codecNeedsEosFlushWorkaround && codecReceivedEos)) {
// Workaround framework bugs. See [Internal: b/8347958, b/8578467, b/8543366, b/23361053].
releaseCodec();
maybeInitCodec();
} else if (codecReinitializationState != REINITIALIZATION_STATE_NONE) {
......@@ -630,6 +637,16 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
}
return false;
}
if (waitingForFirstSyncFrame && !buffer.isKeyFrame()) {
buffer.clear();
if (codecReconfigurationState == RECONFIGURATION_STATE_QUEUE_PENDING) {
// The buffer we just cleared contained reconfiguration data. We need to re-write this
// data into a subsequent buffer (if there is one).
codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
}
return true;
}
waitingForFirstSyncFrame = false;
boolean bufferEncrypted = buffer.isEncrypted();
waitingForKeys = shouldWaitForKeys(bufferEncrypted);
if (waitingForKeys) {
......@@ -763,8 +780,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
*
* @param codec The {@link MediaCodec} instance.
* @param outputFormat The new output format.
* @throws ExoPlaybackException Thrown if an error occurs handling the new output format.
*/
protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat) {
protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat)
throws ExoPlaybackException {
// Do nothing.
}
......@@ -849,7 +868,22 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private boolean drainOutputBuffer(long positionUs, long elapsedRealtimeUs)
throws ExoPlaybackException {
if (outputIndex < 0) {
outputIndex = codec.dequeueOutputBuffer(outputBufferInfo, getDequeueOutputBufferTimeoutUs());
if (codecNeedsEosOutputExceptionWorkaround && codecReceivedEos) {
try {
outputIndex = codec.dequeueOutputBuffer(outputBufferInfo,
getDequeueOutputBufferTimeoutUs());
} catch (IllegalStateException e) {
processEndOfStream();
if (outputStreamEnded) {
// Release the codec, as it's in an error state.
releaseCodec();
}
return false;
}
} else {
outputIndex = codec.dequeueOutputBuffer(outputBufferInfo,
getDequeueOutputBufferTimeoutUs());
}
if (outputIndex >= 0) {
// We've dequeued a buffer.
if (shouldSkipAdaptationWorkaroundOutputBuffer) {
......@@ -888,9 +922,27 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
}
}
if (processOutputBuffer(positionUs, elapsedRealtimeUs, codec, outputBuffers[outputIndex],
outputIndex, outputBufferInfo.flags, outputBufferInfo.presentationTimeUs,
shouldSkipOutputBuffer)) {
boolean processedOutputBuffer;
if (codecNeedsEosOutputExceptionWorkaround && codecReceivedEos) {
try {
processedOutputBuffer = processOutputBuffer(positionUs, elapsedRealtimeUs, codec,
outputBuffers[outputIndex], outputIndex, outputBufferInfo.flags,
outputBufferInfo.presentationTimeUs, shouldSkipOutputBuffer);
} catch (IllegalStateException e) {
processEndOfStream();
if (outputStreamEnded) {
// Release the codec, as it's in an error state.
releaseCodec();
}
return false;
}
} else {
processedOutputBuffer = processOutputBuffer(positionUs, elapsedRealtimeUs, codec,
outputBuffers[outputIndex], outputIndex, outputBufferInfo.flags,
outputBufferInfo.presentationTimeUs, shouldSkipOutputBuffer);
}
if (processedOutputBuffer) {
onProcessedOutputBuffer(outputBufferInfo.presentationTimeUs);
outputIndex = C.INDEX_UNSET;
return true;
......@@ -902,7 +954,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
/**
* Processes a new output format.
*/
private void processOutputFormat() {
private void processOutputFormat() throws ExoPlaybackException {
MediaFormat format = codec.getOutputFormat();
if (codecNeedsAdaptationWorkaround
&& format.getInteger(MediaFormat.KEY_WIDTH) == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT
......@@ -992,6 +1044,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* <p>
* If true is returned, the renderer will work around the issue by releasing the decoder and
* instantiating a new one rather than flushing the current instance.
* <p>
* See [Internal: b/8347958, b/8543366].
*
* @param name The name of the decoder.
* @return True if the decoder is known to fail when flushed.
......@@ -1061,6 +1115,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* <p>
* If true is returned, the renderer will work around the issue by instantiating a new decoder
* when this case occurs.
* <p>
* See [Internal: b/8578467, b/23361053].
*
* @param name The name of the decoder.
* @return True if the decoder is known to behave incorrectly if flushed after receiving an input
......@@ -1074,6 +1130,21 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
}
/**
* Returns whether the decoder may throw an {@link IllegalStateException} from
* {@link MediaCodec#dequeueOutputBuffer(MediaCodec.BufferInfo, long)} or
* {@link MediaCodec#releaseOutputBuffer(int, boolean)} after receiving an input
* buffer with {@link MediaCodec#BUFFER_FLAG_END_OF_STREAM} set.
* <p>
* See [Internal: b/17933838].
*
* @param name The name of the decoder.
* @return True if the decoder may throw an exception after receiving an end-of-stream buffer.
*/
private static boolean codecNeedsEosOutputExceptionWorkaround(String name) {
return Util.SDK_INT == 21 && "OMX.google.aac.decoder".equals(name);
}
/**
* Returns whether the decoder is known to set the number of audio channels in the output format
* to 2 for the given input format, whilst only actually outputting a single channel.
* <p>
......
......@@ -26,7 +26,6 @@ public final class PrivateCommand extends SpliceCommand {
public final long ptsAdjustment;
public final long identifier;
public final byte[] commandBytes;
private PrivateCommand(long identifier, byte[] commandBytes, long ptsAdjustment) {
......
......@@ -21,6 +21,7 @@ import com.google.android.exoplayer2.metadata.MetadataDecoderException;
import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.nio.ByteBuffer;
/**
......@@ -37,6 +38,8 @@ public final class SpliceInfoDecoder implements MetadataDecoder {
private final ParsableByteArray sectionData;
private final ParsableBitArray sectionHeader;
private TimestampAdjuster timestampAdjuster;
public SpliceInfoDecoder() {
sectionData = new ParsableByteArray();
sectionHeader = new ParsableBitArray();
......@@ -44,6 +47,13 @@ public final class SpliceInfoDecoder implements MetadataDecoder {
@Override
public Metadata decode(MetadataInputBuffer inputBuffer) throws MetadataDecoderException {
// Internal timestamps adjustment.
if (timestampAdjuster == null
|| inputBuffer.subsampleOffsetUs != timestampAdjuster.getTimestampOffsetUs()) {
timestampAdjuster = new TimestampAdjuster(inputBuffer.timeUs);
timestampAdjuster.adjustSampleTimestamp(inputBuffer.timeUs - inputBuffer.subsampleOffsetUs);
}
ByteBuffer buffer = inputBuffer.data;
byte[] data = buffer.array();
int size = buffer.limit();
......@@ -69,10 +79,11 @@ public final class SpliceInfoDecoder implements MetadataDecoder {
command = SpliceScheduleCommand.parseFromSection(sectionData);
break;
case TYPE_SPLICE_INSERT:
command = SpliceInsertCommand.parseFromSection(sectionData, ptsAdjustment);
command = SpliceInsertCommand.parseFromSection(sectionData, ptsAdjustment,
timestampAdjuster);
break;
case TYPE_TIME_SIGNAL:
command = TimeSignalCommand.parseFromSection(sectionData, ptsAdjustment);
command = TimeSignalCommand.parseFromSection(sectionData, ptsAdjustment, timestampAdjuster);
break;
case TYPE_PRIVATE_COMMAND:
command = PrivateCommand.parseFromSection(sectionData, spliceCommandLength, ptsAdjustment);
......
......@@ -19,6 +19,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
......@@ -34,6 +35,7 @@ public final class SpliceInsertCommand extends SpliceCommand {
public final boolean programSpliceFlag;
public final boolean spliceImmediateFlag;
public final long programSplicePts;
public final long programSplicePlaybackPositionUs;
public final List<ComponentSplice> componentSpliceList;
public final boolean autoReturn;
public final long breakDuration;
......@@ -43,14 +45,16 @@ public final class SpliceInsertCommand extends SpliceCommand {
private SpliceInsertCommand(long spliceEventId, boolean spliceEventCancelIndicator,
boolean outOfNetworkIndicator, boolean programSpliceFlag, boolean spliceImmediateFlag,
long programSplicePts, List<ComponentSplice> componentSpliceList, boolean autoReturn,
long breakDuration, int uniqueProgramId, int availNum, int availsExpected) {
long programSplicePts, long programSplicePlaybackPositionUs,
List<ComponentSplice> componentSpliceList, boolean autoReturn, long breakDuration,
int uniqueProgramId, int availNum, int availsExpected) {
this.spliceEventId = spliceEventId;
this.spliceEventCancelIndicator = spliceEventCancelIndicator;
this.outOfNetworkIndicator = outOfNetworkIndicator;
this.programSpliceFlag = programSpliceFlag;
this.spliceImmediateFlag = spliceImmediateFlag;
this.programSplicePts = programSplicePts;
this.programSplicePlaybackPositionUs = programSplicePlaybackPositionUs;
this.componentSpliceList = Collections.unmodifiableList(componentSpliceList);
this.autoReturn = autoReturn;
this.breakDuration = breakDuration;
......@@ -66,6 +70,7 @@ public final class SpliceInsertCommand extends SpliceCommand {
programSpliceFlag = in.readByte() == 1;
spliceImmediateFlag = in.readByte() == 1;
programSplicePts = in.readLong();
programSplicePlaybackPositionUs = in.readLong();
int componentSpliceListSize = in.readInt();
List<ComponentSplice> componentSpliceList = new ArrayList<>(componentSpliceListSize);
for (int i = 0; i < componentSpliceListSize; i++) {
......@@ -80,7 +85,7 @@ public final class SpliceInsertCommand extends SpliceCommand {
}
/* package */ static SpliceInsertCommand parseFromSection(ParsableByteArray sectionData,
long ptsAdjustment) {
long ptsAdjustment, TimestampAdjuster timestampAdjuster) {
long spliceEventId = sectionData.readUnsignedInt();
// splice_event_cancel_indicator(1), reserved(7).
boolean spliceEventCancelIndicator = (sectionData.readUnsignedByte() & 0x80) != 0;
......@@ -88,7 +93,7 @@ public final class SpliceInsertCommand extends SpliceCommand {
boolean programSpliceFlag = false;
boolean spliceImmediateFlag = false;
long programSplicePts = C.TIME_UNSET;
ArrayList<ComponentSplice> componentSplices = new ArrayList<>();
List<ComponentSplice> componentSplices = Collections.emptyList();
int uniqueProgramId = 0;
int availNum = 0;
int availsExpected = 0;
......@@ -112,7 +117,8 @@ public final class SpliceInsertCommand extends SpliceCommand {
if (!spliceImmediateFlag) {
componentSplicePts = TimeSignalCommand.parseSpliceTime(sectionData, ptsAdjustment);
}
componentSplices.add(new ComponentSplice(componentTag, componentSplicePts));
componentSplices.add(new ComponentSplice(componentTag, componentSplicePts,
timestampAdjuster.adjustTsTimestamp(componentSplicePts)));
}
}
if (durationFlag) {
......@@ -125,7 +131,8 @@ public final class SpliceInsertCommand extends SpliceCommand {
availsExpected = sectionData.readUnsignedByte();
}
return new SpliceInsertCommand(spliceEventId, spliceEventCancelIndicator, outOfNetworkIndicator,
programSpliceFlag, spliceImmediateFlag, programSplicePts, componentSplices, autoReturn,
programSpliceFlag, spliceImmediateFlag, programSplicePts,
timestampAdjuster.adjustTsTimestamp(programSplicePts), componentSplices, autoReturn,
duration, uniqueProgramId, availNum, availsExpected);
}
......@@ -136,19 +143,23 @@ public final class SpliceInsertCommand extends SpliceCommand {
public final int componentTag;
public final long componentSplicePts;
public final long componentSplicePlaybackPositionUs;
private ComponentSplice(int componentTag, long componentSplicePts) {
private ComponentSplice(int componentTag, long componentSplicePts,
long componentSplicePlaybackPositionUs) {
this.componentTag = componentTag;
this.componentSplicePts = componentSplicePts;
this.componentSplicePlaybackPositionUs = componentSplicePlaybackPositionUs;
}
public void writeToParcel(Parcel dest) {
dest.writeInt(componentTag);
dest.writeLong(componentSplicePts);
dest.writeLong(componentSplicePlaybackPositionUs);
}
public static ComponentSplice createFromParcel(Parcel in) {
return new ComponentSplice(in.readInt(), in.readLong());
return new ComponentSplice(in.readInt(), in.readLong(), in.readLong());
}
}
......@@ -163,6 +174,7 @@ public final class SpliceInsertCommand extends SpliceCommand {
dest.writeByte((byte) (programSpliceFlag ? 1 : 0));
dest.writeByte((byte) (spliceImmediateFlag ? 1 : 0));
dest.writeLong(programSplicePts);
dest.writeLong(programSplicePlaybackPositionUs);
int componentSpliceListSize = componentSpliceList.size();
dest.writeInt(componentSpliceListSize);
for (int i = 0; i < componentSpliceListSize; i++) {
......
......@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.metadata.scte35;
import android.os.Parcel;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
/**
* Represents a time signal command as defined in SCTE35, Section 9.3.4.
......@@ -25,14 +26,18 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
public final class TimeSignalCommand extends SpliceCommand {
public final long ptsTime;
public final long playbackPositionUs;
private TimeSignalCommand(long ptsTime) {
private TimeSignalCommand(long ptsTime, long playbackPositionUs) {
this.ptsTime = ptsTime;
this.playbackPositionUs = playbackPositionUs;
}
/* package */ static TimeSignalCommand parseFromSection(ParsableByteArray sectionData,
long ptsAdjustment) {
return new TimeSignalCommand(parseSpliceTime(sectionData, ptsAdjustment));
long ptsAdjustment, TimestampAdjuster timestampAdjuster) {
long ptsTime = parseSpliceTime(sectionData, ptsAdjustment);
long playbackPositionUs = timestampAdjuster.adjustTsTimestamp(ptsTime);
return new TimeSignalCommand(ptsTime, playbackPositionUs);
}
/**
......@@ -61,6 +66,7 @@ public final class TimeSignalCommand extends SpliceCommand {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(ptsTime);
dest.writeLong(playbackPositionUs);
}
public static final Creator<TimeSignalCommand> CREATOR =
......@@ -68,7 +74,7 @@ public final class TimeSignalCommand extends SpliceCommand {
@Override
public TimeSignalCommand createFromParcel(Parcel in) {
return new TimeSignalCommand(in.readLong());
return new TimeSignalCommand(in.readLong(), in.readLong());
}
@Override
......
......@@ -381,7 +381,7 @@ import java.io.IOException;
// ExtractorOutput implementation. Called by the loading thread.
@Override
public TrackOutput track(int id) {
public TrackOutput track(int id, int type) {
DefaultTrackOutput trackOutput = sampleQueues.get(id);
if (trackOutput == null) {
trackOutput = new DefaultTrackOutput(allocator);
......@@ -519,7 +519,7 @@ import java.io.IOException;
}
private boolean isLoadableExceptionFatal(IOException e) {
return e instanceof ExtractorMediaSource.UnrecognizedInputFormatException;
return e instanceof UnrecognizedInputFormatException;
}
private void notifyLoadError(final IOException error) {
......@@ -625,7 +625,7 @@ import java.io.IOException;
length += position;
}
input = new DefaultExtractorInput(dataSource, position, length);
Extractor extractor = extractorHolder.selectExtractor(input);
Extractor extractor = extractorHolder.selectExtractor(input, dataSource.getUri());
if (pendingExtractorSeek) {
extractor.seek(position, seekTimeUs);
pendingExtractorSeek = false;
......@@ -677,13 +677,13 @@ import java.io.IOException;
* later calls.
*
* @param input The {@link ExtractorInput} from which data should be read.
* @param uri The {@link Uri} of the data.
* @return An initialized extractor for reading {@code input}.
* @throws ExtractorMediaSource.UnrecognizedInputFormatException Thrown if the input format
* could not be detected.
* @throws UnrecognizedInputFormatException Thrown if the input format could not be detected.
* @throws IOException Thrown if the input could not be read.
* @throws InterruptedException Thrown if the thread was interrupted.
*/
public Extractor selectExtractor(ExtractorInput input)
public Extractor selectExtractor(ExtractorInput input, Uri uri)
throws IOException, InterruptedException {
if (extractor != null) {
return extractor;
......@@ -701,7 +701,8 @@ import java.io.IOException;
}
}
if (extractor == null) {
throw new ExtractorMediaSource.UnrecognizedInputFormatException(extractors);
throw new UnrecognizedInputFormatException("None of the available extractors ("
+ Util.getCommaDelimitedSimpleClassNames(extractors) + ") could read the stream.", uri);
}
extractor.init(extractorOutput);
return extractor;
......
......@@ -19,7 +19,6 @@ import android.net.Uri;
import android.os.Handler;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.Extractor;
......@@ -27,7 +26,6 @@ import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
/**
......@@ -58,18 +56,6 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List
}
/**
* Thrown if the input format could not recognized.
*/
public static final class UnrecognizedInputFormatException extends ParserException {
public UnrecognizedInputFormatException(Extractor[] extractors) {
super("None of the available extractors ("
+ Util.getCommaDelimitedSimpleClassNames(extractors) + ") could read the stream.");
}
}
/**
* The default minimum number of times to retry loading prior to failing for on-demand streams.
*/
public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT_ON_DEMAND = 3;
......
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.source;
import android.net.Uri;
import com.google.android.exoplayer2.ParserException;
/**
* Thrown if the input format was not recognized.
*/
public class UnrecognizedInputFormatException extends ParserException {
/**
* The {@link Uri} from which the unrecognized data was read.
*/
public final Uri uri;
/**
* @param message The detail message for the exception.
* @param uri The {@link Uri} from which the unrecognized data was read.
*/
public UnrecognizedInputFormatException(String message, Uri uri) {
super(message);
this.uri = uri;
}
}
......@@ -17,7 +17,7 @@ package com.google.android.exoplayer2.source.chunk;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
......@@ -30,33 +30,19 @@ import java.io.IOException;
/**
* An {@link Extractor} wrapper for loading chunks containing a single track.
* <p>
* The wrapper allows switching of the {@link SeekMapOutput} and {@link TrackOutput} that receive
* parsed data.
* The wrapper allows switching of the {@link TrackOutput} that receives parsed data.
*/
public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput {
/**
* Receives {@link SeekMap}s extracted by the wrapped {@link Extractor}.
*/
public interface SeekMapOutput {
/**
* @see ExtractorOutput#seekMap(SeekMap)
*/
void seekMap(SeekMap seekMap);
}
public final Extractor extractor;
private final Format manifestFormat;
private final boolean preferManifestDrmInitData;
private final boolean resendFormatOnInit;
private final int primaryTrackType;
private boolean extractorInitialized;
private SeekMapOutput seekMapOutput;
private TrackOutput trackOutput;
private Format sentFormat;
private SeekMap seekMap;
private Format sampleFormat;
// Accessed only on the loader thread.
private boolean seenTrack;
......@@ -66,36 +52,44 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
* @param extractor The extractor to wrap.
* @param manifestFormat A manifest defined {@link Format} whose data should be merged into any
* sample {@link Format} output from the {@link Extractor}.
* @param preferManifestDrmInitData Whether {@link DrmInitData} defined in {@code manifestFormat}
* should be preferred when the sample and manifest {@link Format}s are merged.
* @param resendFormatOnInit Whether the extractor should resend the previous {@link Format} when
* it is initialized via {@link #init(SeekMapOutput, TrackOutput)}.
* @param primaryTrackType The type of the primary track. Typically one of the {@link C}
* {@code TRACK_TYPE_*} constants.
*/
public ChunkExtractorWrapper(Extractor extractor, Format manifestFormat,
boolean preferManifestDrmInitData, boolean resendFormatOnInit) {
public ChunkExtractorWrapper(Extractor extractor, Format manifestFormat, int primaryTrackType) {
this.extractor = extractor;
this.manifestFormat = manifestFormat;
this.preferManifestDrmInitData = preferManifestDrmInitData;
this.resendFormatOnInit = resendFormatOnInit;
this.primaryTrackType = primaryTrackType;
}
/**
* Returns the {@link SeekMap} most recently output by the extractor, or null.
*/
public SeekMap getSeekMap() {
return seekMap;
}
/**
* Returns the sample {@link Format} most recently output by the extractor, or null.
*/
public Format getSampleFormat() {
return sampleFormat;
}
/**
* Initializes the extractor to output to the provided {@link SeekMapOutput} and
* {@link TrackOutput} instances, and configures it to receive data from a new chunk.
* Initializes the extractor to output to the provided {@link TrackOutput}, and configures it to
* receive data from a new chunk.
*
* @param seekMapOutput The {@link SeekMapOutput} that will receive extracted {@link SeekMap}s.
* @param trackOutput The {@link TrackOutput} that will receive sample data.
*/
public void init(SeekMapOutput seekMapOutput, TrackOutput trackOutput) {
this.seekMapOutput = seekMapOutput;
public void init(TrackOutput trackOutput) {
this.trackOutput = trackOutput;
if (!extractorInitialized) {
extractor.init(this);
extractorInitialized = true;
} else {
extractor.seek(0, 0);
if (resendFormatOnInit && sentFormat != null) {
trackOutput.format(sentFormat);
if (sampleFormat != null && trackOutput != null) {
trackOutput.format(sampleFormat);
}
}
}
......@@ -103,7 +97,10 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
// ExtractorOutput implementation.
@Override
public TrackOutput track(int id) {
public TrackOutput track(int id, int type) {
if (primaryTrackType != C.TRACK_TYPE_UNKNOWN && primaryTrackType != type) {
return new DummyTrackOutput();
}
Assertions.checkState(!seenTrack || seenTrackId == id);
seenTrack = true;
seenTrackId = id;
......@@ -117,15 +114,17 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
@Override
public void seekMap(SeekMap seekMap) {
seekMapOutput.seekMap(seekMap);
this.seekMap = seekMap;
}
// TrackOutput implementation.
@Override
public void format(Format format) {
sentFormat = format.copyWithManifestFormatInfo(manifestFormat, preferManifestDrmInitData);
trackOutput.format(sentFormat);
sampleFormat = format.copyWithManifestFormatInfo(manifestFormat);
if (trackOutput != null) {
trackOutput.format(sampleFormat);
}
}
@Override
......
......@@ -20,8 +20,6 @@ import com.google.android.exoplayer2.extractor.DefaultExtractorInput;
import com.google.android.exoplayer2.extractor.DefaultTrackOutput;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper.SeekMapOutput;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Assertions;
......@@ -31,12 +29,11 @@ import java.io.IOException;
/**
* A {@link BaseMediaChunk} that uses an {@link Extractor} to decode sample data.
*/
public class ContainerMediaChunk extends BaseMediaChunk implements SeekMapOutput {
public class ContainerMediaChunk extends BaseMediaChunk {
private final int chunkCount;
private final long sampleOffsetUs;
private final ChunkExtractorWrapper extractorWrapper;
private final Format sampleFormat;
private volatile int bytesLoaded;
private volatile boolean loadCanceled;
......@@ -56,19 +53,15 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SeekMapOutput
* underlying media are being merged into a single load.
* @param sampleOffsetUs An offset to add to the sample timestamps parsed by the extractor.
* @param extractorWrapper A wrapped extractor to use for parsing the data.
* @param sampleFormat The {@link Format} of the samples in the chunk, if known. May be null if
* the data is known to define its own sample format.
*/
public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat,
int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs,
int chunkIndex, int chunkCount, long sampleOffsetUs, ChunkExtractorWrapper extractorWrapper,
Format sampleFormat) {
int chunkIndex, int chunkCount, long sampleOffsetUs, ChunkExtractorWrapper extractorWrapper) {
super(dataSource, dataSpec, trackFormat, trackSelectionReason, trackSelectionData, startTimeUs,
endTimeUs, chunkIndex);
this.chunkCount = chunkCount;
this.sampleOffsetUs = sampleOffsetUs;
this.extractorWrapper = extractorWrapper;
this.sampleFormat = sampleFormat;
}
@Override
......@@ -86,13 +79,6 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SeekMapOutput
return bytesLoaded;
}
// SeekMapOutput implementation.
@Override
public final void seekMap(SeekMap seekMap) {
// Do nothing.
}
// Loadable implementation.
@Override
......@@ -116,8 +102,8 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SeekMapOutput
if (bytesLoaded == 0) {
// Set the target to ourselves.
DefaultTrackOutput trackOutput = getTrackOutput();
trackOutput.formatWithOffset(sampleFormat, sampleOffsetUs);
extractorWrapper.init(this, trackOutput);
trackOutput.setSampleOffsetUs(sampleOffsetUs);
extractorWrapper.init(trackOutput);
}
// Load and decode the sample data.
try {
......
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