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

This commit is contained in:
Julian Cable 2017-02-21 08:57:49 +00:00
commit 402c985aec
149 changed files with 3843 additions and 1799 deletions

View File

@ -1,9 +1,69 @@
# Release notes # # Release notes #
### r2.1.1 ### ### r2.2.0 ###
Bugfix release only. Users of r2.1.0 and r2.0.x should proactively update to * Demo app: Automatic recovery from BehindLiveWindowException, plus improved
this version. 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).
### r2.1.1 ###
* Fix some subtitle types (e.g. WebVTT) being displayed out of sync * Fix some subtitle types (e.g. WebVTT) being displayed out of sync
([#2208](https://github.com/google/ExoPlayer/issues/2208)). ([#2208](https://github.com/google/ExoPlayer/issues/2208)).
@ -52,9 +112,9 @@ this version.
* Improved flexibility of SimpleExoPlayer * Improved flexibility of SimpleExoPlayer
([#2102](https://github.com/google/ExoPlayer/issues/2102)). ([#2102](https://github.com/google/ExoPlayer/issues/2102)).
* Fix issue where only the audio of a video would play due to capability * Fix issue where only the audio of a video would play due to capability
detection issues ([#2007](https://github.com/google/ExoPlayer/issues/2007)) detection issues ([#2007](https://github.com/google/ExoPlayer/issues/2007),
([#2034](https://github.com/google/ExoPlayer/issues/2034)) [#2034](https://github.com/google/ExoPlayer/issues/2034) and
([#2157](https://github.com/google/ExoPlayer/issues/2157)). [#2157](https://github.com/google/ExoPlayer/issues/2157)).
* Fix issues that could cause ExtractorMediaSource based playbacks to get stuck * Fix issues that could cause ExtractorMediaSource based playbacks to get stuck
buffering ([#1962](https://github.com/google/ExoPlayer/issues/1962)). buffering ([#1962](https://github.com/google/ExoPlayer/issues/1962)).
* Correctly set SimpleExoPlayerView surface aspect ratio when an active player * 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 however it can be assumed that all such changes are included in the most recent
V2 release. 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 ### ### r1.5.13 ###
* Improvements to the upstream cache package. * Improvements to the upstream cache package.

View File

@ -29,13 +29,13 @@ allprojects {
jcenter() jcenter()
} }
project.ext { project.ext {
compileSdkVersion=24 compileSdkVersion=25
targetSdkVersion=24 targetSdkVersion=25
buildToolsVersion='23.0.3' buildToolsVersion='25'
releaseRepoName = 'exoplayer' releaseRepoName = 'exoplayer'
releaseUserOrg = 'google' releaseUserOrg = 'google'
releaseGroupId = 'com.google.android.exoplayer' releaseGroupId = 'com.google.android.exoplayer'
releaseVersion = 'r2.1.1' releaseVersion = 'r2.2.0'
releaseWebsite = 'https://github.com/google/ExoPlayer' releaseWebsite = 'https://github.com/google/ExoPlayer'
} }
} }

View File

@ -16,8 +16,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.exoplayer2.demo" package="com.google.android.exoplayer2.demo"
android:versionCode="2101" android:versionCode="2200"
android:versionName="2.1.1"> android:versionName="2.2.0">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
@ -27,7 +27,7 @@
<application <application
android:label="@string/application_name" android:label="@string/application_name"
android:icon="@drawable/ic_launcher" android:icon="@mipmap/ic_launcher"
android:banner="@drawable/ic_banner" android:banner="@drawable/ic_banner"
android:largeHeap="true" android:largeHeap="true"
android:allowBackup="false" android:allowBackup="false"

View File

@ -101,9 +101,6 @@ import java.util.Locale;
@Override @Override
public void onTimelineChanged(Timeline timeline, Object manifest) { public void onTimelineChanged(Timeline timeline, Object manifest) {
if (timeline == null) {
return;
}
int periodCount = timeline.getPeriodCount(); int periodCount = timeline.getPeriodCount();
int windowCount = timeline.getWindowCount(); int windowCount = timeline.getWindowCount();
Log.d(TAG, "sourceInfo [periodCount=" + periodCount + ", windowCount=" + windowCount); Log.d(TAG, "sourceInfo [periodCount=" + periodCount + ", windowCount=" + windowCount);

View File

@ -70,8 +70,6 @@ import com.google.android.exoplayer2.util.Util;
import java.net.CookieHandler; import java.net.CookieHandler;
import java.net.CookieManager; import java.net.CookieManager;
import java.net.CookiePolicy; import java.net.CookiePolicy;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
/** /**
@ -239,19 +237,9 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
if (drmSchemeUuid != null) { if (drmSchemeUuid != null) {
String drmLicenseUrl = intent.getStringExtra(DRM_LICENSE_URL); String drmLicenseUrl = intent.getStringExtra(DRM_LICENSE_URL);
String[] keyRequestPropertiesArray = intent.getStringArrayExtra(DRM_KEY_REQUEST_PROPERTIES); 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 { try {
drmSessionManager = buildDrmSessionManager(drmSchemeUuid, drmLicenseUrl, drmSessionManager = buildDrmSessionManager(drmSchemeUuid, drmLicenseUrl,
keyRequestProperties); keyRequestPropertiesArray);
} catch (UnsupportedDrmException e) { } catch (UnsupportedDrmException e) {
int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported
: (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME : (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] MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0]
: new ConcatenatingMediaSource(mediaSources); : new ConcatenatingMediaSource(mediaSources);
player.seekTo(resumeWindow, resumePosition); boolean haveResumePosition = resumeWindow != C.INDEX_UNSET;
player.prepare(mediaSource, false, false); if (haveResumePosition) {
player.seekTo(resumeWindow, resumePosition);
}
player.prepare(mediaSource, !haveResumePosition, false);
playerNeedsSource = false; playerNeedsSource = false;
updateButtonVisibilities(); updateButtonVisibilities();
} }
@ -346,12 +337,18 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
} }
private DrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManager(UUID uuid, 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) { if (Util.SDK_INT < 18) {
return null; return null;
} }
HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl, 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, return new DefaultDrmSessionManager<>(uuid,
FrameworkMediaDrm.newInstance(uuid), drmCallback, null, mainHandler, eventLogger); FrameworkMediaDrm.newInstance(uuid), drmCallback, null, mainHandler, eventLogger);
} }
@ -377,7 +374,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
} }
private void clearResumePosition() { private void clearResumePosition() {
resumeWindow = 0; resumeWindow = C.INDEX_UNSET;
resumePosition = C.TIME_UNSET; resumePosition = C.TIME_UNSET;
} }
@ -422,7 +419,12 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
@Override @Override
public void onPositionDiscontinuity() { 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 @Override
@ -461,11 +463,12 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
playerNeedsSource = true; playerNeedsSource = true;
if (isBehindLiveWindow(e)) { if (isBehindLiveWindow(e)) {
clearResumePosition(); clearResumePosition();
initializePlayer();
} else { } else {
updateResumePosition(); updateResumePosition();
updateButtonVisibilities();
showControls();
} }
updateButtonVisibilities();
showControls();
} }
@Override @Override

View File

@ -301,15 +301,18 @@ import java.util.Locale;
private static String buildTrackName(Format format) { private static String buildTrackName(Format format) {
String trackName; String trackName;
if (MimeTypes.isVideo(format.sampleMimeType)) { if (MimeTypes.isVideo(format.sampleMimeType)) {
trackName = joinWithSeparator(joinWithSeparator(buildResolutionString(format), trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(
buildBitrateString(format)), buildTrackIdString(format)); buildResolutionString(format), buildBitrateString(format)), buildTrackIdString(format)),
buildSampleMimeTypeString(format));
} else if (MimeTypes.isAudio(format.sampleMimeType)) { } else if (MimeTypes.isAudio(format.sampleMimeType)) {
trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(buildLanguageString(format), trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(joinWithSeparator(
buildAudioPropertyString(format)), buildBitrateString(format)), buildLanguageString(format), buildAudioPropertyString(format)),
buildTrackIdString(format)); buildBitrateString(format)), buildTrackIdString(format)),
buildSampleMimeTypeString(format));
} else { } else {
trackName = joinWithSeparator(joinWithSeparator(buildLanguageString(format), trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(buildLanguageString(format),
buildBitrateString(format)), buildTrackIdString(format)); buildBitrateString(format)), buildTrackIdString(format)),
buildSampleMimeTypeString(format));
} }
return trackName.length() == 0 ? "unknown" : trackName; return trackName.length() == 0 ? "unknown" : trackName;
} }
@ -342,4 +345,8 @@ import java.util.Locale;
return format.id == null ? "" : ("id:" + format.id); return format.id == null ? "" : ("id:" + format.id);
} }
private static String buildSampleMimeTypeString(Format format) {
return format.sampleMimeType == null ? "" : format.sampleMimeType;
}
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -16,8 +16,7 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <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">ExoPlayer</string>
<string name="application_name">ExoPlayer2 Demo</string>
<string name="video">Video</string> <string name="video">Video</string>

View File

@ -63,6 +63,7 @@ git clone git://source.ffmpeg.org/ffmpeg ffmpeg && cd ffmpeg && \
--enable-decoder=vorbis \ --enable-decoder=vorbis \
--enable-decoder=opus \ --enable-decoder=opus \
--enable-decoder=flac \ --enable-decoder=flac \
--enable-decoder=alac \
&& \ && \
make -j4 && \ make -j4 && \
make install-libs make install-libs

View File

@ -19,8 +19,8 @@ import android.os.Handler;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format; 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.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.BufferProcessor;
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer; import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
import com.google.android.exoplayer2.drm.ExoMediaCrypto; import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.MimeTypes; 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 * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required. * 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 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
public FfmpegAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener) { * before they are output.
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.
*/ */
public FfmpegAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener, public FfmpegAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
AudioCapabilities audioCapabilities) { BufferProcessor... bufferProcessors) {
super(eventHandler, eventListener, audioCapabilities); super(eventHandler, eventListener, bufferProcessors);
} }
@Override @Override

View File

@ -19,6 +19,7 @@ import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.decoder.SimpleDecoder; import com.google.android.exoplayer2.decoder.SimpleDecoder;
import com.google.android.exoplayer2.decoder.SimpleOutputBuffer; import com.google.android.exoplayer2.decoder.SimpleOutputBuffer;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.List; import java.util.List;
@ -88,6 +89,13 @@ import java.util.List;
if (!hasOutputFormat) { if (!hasOutputFormat) {
channelCount = ffmpegGetChannelCount(nativeContext); channelCount = ffmpegGetChannelCount(nativeContext);
sampleRate = ffmpegGetSampleRate(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; hasOutputFormat = true;
} }
outputBuffer.data.position(0); outputBuffer.data.position(0);
@ -123,6 +131,7 @@ import java.util.List;
private static byte[] getExtraData(String mimeType, List<byte[]> initializationData) { private static byte[] getExtraData(String mimeType, List<byte[]> initializationData) {
switch (mimeType) { switch (mimeType) {
case MimeTypes.AUDIO_AAC: case MimeTypes.AUDIO_AAC:
case MimeTypes.AUDIO_ALAC:
case MimeTypes.AUDIO_OPUS: case MimeTypes.AUDIO_OPUS:
return initializationData.get(0); return initializationData.get(0);
case MimeTypes.AUDIO_VORBIS: case MimeTypes.AUDIO_VORBIS:

View File

@ -92,6 +92,8 @@ public final class FfmpegLibrary {
return "amrwb"; return "amrwb";
case MimeTypes.AUDIO_FLAC: case MimeTypes.AUDIO_FLAC:
return "flac"; return "flac";
case MimeTypes.AUDIO_ALAC:
return "alac";
default: default:
return null; return null;
} }

View File

@ -5,7 +5,10 @@
native <methods>; 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.ext.flac.FlacDecoderJni {
*; *;
} }
-keep class com.google.android.exoplayer2.util.FlacStreamInfo {
*;
}

View File

@ -67,7 +67,7 @@ public final class FlacExtractor implements Extractor {
@Override @Override
public void init(ExtractorOutput output) { public void init(ExtractorOutput output) {
extractorOutput = output; extractorOutput = output;
trackOutput = extractorOutput.track(0); trackOutput = extractorOutput.track(0, C.TRACK_TYPE_AUDIO);
extractorOutput.endTracks(); extractorOutput.endTracks();
try { try {
decoderJni = new FlacDecoderJni(); decoderJni = new FlacDecoderJni();

View File

@ -17,8 +17,8 @@ package com.google.android.exoplayer2.ext.flac;
import android.os.Handler; import android.os.Handler;
import com.google.android.exoplayer2.Format; 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.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.BufferProcessor;
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer; import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
import com.google.android.exoplayer2.drm.ExoMediaCrypto; import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.MimeTypes; 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 * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required. * 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 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
public LibflacAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener) { * before they are output.
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.
*/ */
public LibflacAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener, public LibflacAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
AudioCapabilities audioCapabilities) { BufferProcessor... bufferProcessors) {
super(eventHandler, eventListener, audioCapabilities); super(eventHandler, eventListener, bufferProcessors);
} }
@Override @Override

View File

@ -31,7 +31,7 @@ LOCAL_C_INCLUDES := \
LOCAL_SRC_FILES := $(FLAC_SOURCES) LOCAL_SRC_FILES := $(FLAC_SOURCES)
LOCAL_CFLAGS += '-DVERSION="1.3.1"' -DFLAC__NO_MD5 -DFLAC__INTEGER_ONLY_LIBRARY -DFLAC__NO_ASM 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_CFLAGS += -O3 -funroll-loops -finline-functions
LOCAL_LDLIBS := -llog -lz -lm LOCAL_LDLIBS := -llog -lz -lm

View File

@ -453,7 +453,8 @@ int64_t FLACParser::getSeekPosition(int64_t timeUs) {
} }
FLAC__StreamMetadata_SeekPoint* points = mSeekTable->points; 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) { if (points[i].sample_number <= sample) {
return firstFrameOffset + points[i].stream_offset; return firstFrameOffset + points[i].stream_offset;
} }

View File

@ -17,8 +17,8 @@ package com.google.android.exoplayer2.ext.opus;
import android.os.Handler; import android.os.Handler;
import com.google.android.exoplayer2.Format; 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.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.BufferProcessor;
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer; import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.ExoMediaCrypto; 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 * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required. * 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 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 LibopusAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener) { public LibopusAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
super(eventHandler, eventListener); BufferProcessor... bufferProcessors) {
super(eventHandler, eventListener, bufferProcessors);
} }
/** /**
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required. * 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 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 * @param bufferProcessors Optional {@link BufferProcessor}s which will process PCM audio
* default capabilities (no encoded audio passthrough support) should be assumed. * buffers before they are output.
*/ */
public LibopusAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener, public LibopusAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
AudioCapabilities audioCapabilities) { DrmSessionManager<ExoMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys,
super(eventHandler, eventListener, audioCapabilities); BufferProcessor... bufferProcessors) {
} super(eventHandler, eventListener, null, drmSessionManager, playClearSamplesWithoutKeys,
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.
*/
public LibopusAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
AudioCapabilities audioCapabilities, DrmSessionManager<ExoMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys) {
super(eventHandler, eventListener, audioCapabilities, drmSessionManager,
playClearSamplesWithoutKeys);
} }
@Override @Override

View File

@ -213,7 +213,7 @@ import java.util.List;
SimpleOutputBuffer outputBuffer, int sampleRate); SimpleOutputBuffer outputBuffer, int sampleRate);
private native int opusSecureDecode(long decoder, long timeUs, ByteBuffer inputBuffer, private native int opusSecureDecode(long decoder, long timeUs, ByteBuffer inputBuffer,
int inputSize, SimpleOutputBuffer outputBuffer, int sampleRate, 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); int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData);
private native void opusClose(long decoder); private native void opusClose(long decoder);
private native void opusReset(long decoder); private native void opusReset(long decoder);

View File

@ -141,7 +141,7 @@ import java.nio.ByteBuffer;
private native long vpxClose(long context); private native long vpxClose(long context);
private native long vpxDecode(long context, ByteBuffer encoded, int length); private native long vpxDecode(long context, ByteBuffer encoded, int length);
private native long vpxSecureDecode(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); int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData);
private native int vpxGetFrame(long context, VpxOutputBuffer outputBuffer); private native int vpxGetFrame(long context, VpxOutputBuffer outputBuffer);
private native int vpxGetErrorCode(long context); private native int vpxGetErrorCode(long context);

View File

@ -0,0 +1,382 @@
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

View File

@ -6,7 +6,7 @@ numberOfTracks = 1
track 0: track 0:
format: format:
bitrate = -1 bitrate = -1
id = null id = 0
containerMimeType = null containerMimeType = null
sampleMimeType = audio/ac3 sampleMimeType = audio/ac3
maxInputSize = -1 maxInputSize = -1

View File

@ -6,7 +6,7 @@ numberOfTracks = 2
track 0: track 0:
format: format:
bitrate = -1 bitrate = -1
id = null id = 0
containerMimeType = null containerMimeType = null
sampleMimeType = audio/mp4a-latm sampleMimeType = audio/mp4a-latm
maxInputSize = -1 maxInputSize = -1
@ -606,7 +606,7 @@ track 0:
track 1: track 1:
format: format:
bitrate = -1 bitrate = -1
id = null id = 1
containerMimeType = null containerMimeType = null
sampleMimeType = application/id3 sampleMimeType = application/id3
maxInputSize = -1 maxInputSize = -1

View File

@ -6,7 +6,7 @@ numberOfTracks = 2
track 192: track 192:
format: format:
bitrate = -1 bitrate = -1
id = null id = 192
containerMimeType = null containerMimeType = null
sampleMimeType = audio/mpeg-L2 sampleMimeType = audio/mpeg-L2
maxInputSize = 4096 maxInputSize = 4096
@ -45,7 +45,7 @@ track 192:
track 224: track 224:
format: format:
bitrate = -1 bitrate = -1
id = null id = 224
containerMimeType = null containerMimeType = null
sampleMimeType = video/mpeg2 sampleMimeType = video/mpeg2
maxInputSize = -1 maxInputSize = -1

View File

@ -6,7 +6,7 @@ numberOfTracks = 2
track 256: track 256:
format: format:
bitrate = -1 bitrate = -1
id = null id = 1/256
containerMimeType = null containerMimeType = null
sampleMimeType = video/mpeg2 sampleMimeType = video/mpeg2
maxInputSize = -1 maxInputSize = -1
@ -38,7 +38,7 @@ track 256:
track 257: track 257:
format: format:
bitrate = -1 bitrate = -1
id = null id = 1/257
containerMimeType = null containerMimeType = null
sampleMimeType = audio/mpeg-L2 sampleMimeType = audio/mpeg-L2
maxInputSize = 4096 maxInputSize = 4096

View File

@ -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.source.dash.manifest.SegmentBase.SingleSegmentBase;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import org.mockito.Mock; import org.mockito.Mock;
@ -213,11 +214,15 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase {
} }
private static AdaptationSet newAdaptationSets(Representation... representations) { 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) { 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()); return Representation.newInstance("", 0, format, "", new SingleSegmentBase());
} }

View File

@ -25,21 +25,32 @@ import com.google.android.exoplayer2.testutil.TestUtil;
*/ */
public final class FragmentedMp4ExtractorTest extends InstrumentationTestCase { 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 { 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 { 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); 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);
}
};
}
} }

View File

@ -69,8 +69,8 @@ public class AdtsReaderTest extends TestCase {
@Override @Override
protected void setUp() throws Exception { protected void setUp() throws Exception {
FakeExtractorOutput fakeExtractorOutput = new FakeExtractorOutput(); FakeExtractorOutput fakeExtractorOutput = new FakeExtractorOutput();
adtsOutput = fakeExtractorOutput.track(0); adtsOutput = fakeExtractorOutput.track(0, C.TRACK_TYPE_AUDIO);
id3Output = fakeExtractorOutput.track(1); id3Output = fakeExtractorOutput.track(1, C.TRACK_TYPE_METADATA);
adtsReader = new AdtsReader(true); adtsReader = new AdtsReader(true);
TrackIdGenerator idGenerator = new TrackIdGenerator(0, 1); TrackIdGenerator idGenerator = new TrackIdGenerator(0, 1);
adtsReader.createTracks(fakeExtractorOutput, idGenerator); adtsReader.createTracks(fakeExtractorOutput, idGenerator);

View File

@ -16,9 +16,9 @@
package com.google.android.exoplayer2.extractor.ts; package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.extractor.ExtractorOutput; 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.testutil.FakeExtractorOutput;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;

View File

@ -17,11 +17,11 @@ package com.google.android.exoplayer2.extractor.ts;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import android.util.SparseArray; import android.util.SparseArray;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.PositionHolder; 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.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; 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.FakeTrackOutput;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.util.Random; import java.util.Random;
@ -92,7 +93,7 @@ public final class TsExtractorTest extends InstrumentationTestCase {
TrackOutput trackOutput = reader.getTrackOutput(); TrackOutput trackOutput = reader.getTrackOutput();
assertTrue(trackOutput == output.trackOutputs.get(257 /* PID of audio track. */)); assertTrue(trackOutput == output.trackOutputs.get(257 /* PID of audio track. */));
assertEquals( 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); ((FakeTrackOutput) trackOutput).format);
} }
@ -178,8 +179,9 @@ public final class TsExtractorTest extends InstrumentationTestCase {
@Override @Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId()); idGenerator.generateNewId();
output.format(Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0, output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_UNKNOWN);
output.format(Format.createTextSampleFormat(idGenerator.getFormatId(), "mime", null, 0, 0,
language, null, 0)); language, null, 0));
} }

View File

@ -0,0 +1,173 @@
/*
* 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;
}
}

View File

@ -20,6 +20,8 @@ import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.List;
/** /**
* Unit tests for {@link DashManifestParser}. * Unit tests for {@link DashManifestParser}.
@ -70,34 +72,57 @@ public class DashManifestParserTest extends InstrumentationTestCase {
} }
public void testParseCea608AccessibilityChannel() { public void testParseCea608AccessibilityChannel() {
assertEquals(1, DashManifestParser.parseCea608AccessibilityChannel("CC1=eng")); assertEquals(1, DashManifestParser.parseCea608AccessibilityChannel(
assertEquals(2, DashManifestParser.parseCea608AccessibilityChannel("CC2=eng")); buildCea608AccessibilityDescriptors("CC1=eng")));
assertEquals(3, DashManifestParser.parseCea608AccessibilityChannel("CC3=eng")); assertEquals(2, DashManifestParser.parseCea608AccessibilityChannel(
assertEquals(4, DashManifestParser.parseCea608AccessibilityChannel("CC4=eng")); 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("")); buildCea608AccessibilityDescriptors(null)));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel("CC0=eng")); assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel(
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel("CC5=eng")); buildCea608AccessibilityDescriptors("")));
assertEquals(Format.NO_VALUE, assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel(
DashManifestParser.parseCea608AccessibilityChannel("Wrong format")); buildCea608AccessibilityDescriptors("CC0=eng")));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel(
buildCea608AccessibilityDescriptors("CC5=eng")));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel(
buildCea608AccessibilityDescriptors("Wrong format")));
} }
public void testParseCea708AccessibilityChannel() { public void testParseCea708AccessibilityChannel() {
assertEquals(1, DashManifestParser.parseCea708AccessibilityChannel("1=lang:eng")); assertEquals(1, DashManifestParser.parseCea708AccessibilityChannel(
assertEquals(2, DashManifestParser.parseCea708AccessibilityChannel("2=lang:eng")); buildCea708AccessibilityDescriptors("1=lang:eng")));
assertEquals(3, DashManifestParser.parseCea708AccessibilityChannel("3=lang:eng")); assertEquals(2, DashManifestParser.parseCea708AccessibilityChannel(
assertEquals(62, DashManifestParser.parseCea708AccessibilityChannel("62=lang:eng")); buildCea708AccessibilityDescriptors("2=lang:eng")));
assertEquals(63, DashManifestParser.parseCea708AccessibilityChannel("63=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("")); buildCea708AccessibilityDescriptors(null)));
assertEquals(Format.NO_VALUE, assertEquals(Format.NO_VALUE, DashManifestParser.parseCea708AccessibilityChannel(
DashManifestParser.parseCea708AccessibilityChannel("0=lang:eng")); buildCea708AccessibilityDescriptors("")));
assertEquals(Format.NO_VALUE, assertEquals(Format.NO_VALUE, DashManifestParser.parseCea708AccessibilityChannel(
DashManifestParser.parseCea708AccessibilityChannel("64=lang:eng")); buildCea708AccessibilityDescriptors("0=lang:eng")));
assertEquals(Format.NO_VALUE, assertEquals(Format.NO_VALUE, DashManifestParser.parseCea708AccessibilityChannel(
DashManifestParser.parseCea708AccessibilityChannel("Wrong format")); 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));
} }
} }

View File

@ -35,6 +35,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
Uri playlistUri = Uri.parse("https://example.com/test.m3u8"); Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
String playlistString = "#EXTM3U\n" String playlistString = "#EXTM3U\n"
+ "#EXT-X-VERSION:3\n" + "#EXT-X-VERSION:3\n"
+ "#EXT-X-PLAYLIST-TYPE:VOD\n"
+ "#EXT-X-TARGETDURATION:8\n" + "#EXT-X-TARGETDURATION:8\n"
+ "#EXT-X-MEDIA-SEQUENCE:2679\n" + "#EXT-X-MEDIA-SEQUENCE:2679\n"
+ "#EXT-X-DISCONTINUITY-SEQUENCE:4\n" + "#EXT-X-DISCONTINUITY-SEQUENCE:4\n"
@ -71,6 +72,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
assertEquals(HlsPlaylist.TYPE_MEDIA, playlist.type); assertEquals(HlsPlaylist.TYPE_MEDIA, playlist.type);
HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist; HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist;
assertEquals(HlsMediaPlaylist.PLAYLIST_TYPE_VOD, mediaPlaylist.playlistType);
assertEquals(2679, mediaPlaylist.mediaSequence); assertEquals(2679, mediaPlaylist.mediaSequence);
assertEquals(3, mediaPlaylist.version); assertEquals(3, mediaPlaylist.version);

View File

@ -20,9 +20,9 @@ import android.test.InstrumentationTestCase;
import android.test.MoreAsserts; import android.test.MoreAsserts;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.testutil.FakeDataSource; 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.testutil.TestUtil;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.FileDataSource;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
@ -119,9 +119,22 @@ public class CacheDataSourceTest extends InstrumentationTestCase {
C.LENGTH_UNSET, KEY_2))); 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) private void assertCacheAndRead(boolean unboundedRequest, boolean simulateUnknownLength)
throws IOException { throws IOException {
// Read all data from upstream and cache // Read all data from upstream and write to cache
CacheDataSource cacheDataSource = createCacheDataSource(false, simulateUnknownLength); CacheDataSource cacheDataSource = createCacheDataSource(false, simulateUnknownLength);
assertReadDataContentLength(cacheDataSource, unboundedRequest, simulateUnknownLength); assertReadDataContentLength(cacheDataSource, unboundedRequest, simulateUnknownLength);
@ -171,15 +184,27 @@ public class CacheDataSourceTest extends InstrumentationTestCase {
private CacheDataSource createCacheDataSource(boolean setReadException, private CacheDataSource createCacheDataSource(boolean setReadException,
boolean simulateUnknownLength) { 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) { if (setReadException) {
builder.appendReadError(new IOException("Shouldn't read from upstream")); builder.appendReadError(new IOException("Shouldn't read from upstream"));
} }
builder.setSimulateUnknownLength(simulateUnknownLength); FakeDataSource upstream =
builder.appendReadData(TEST_DATA); builder.setSimulateUnknownLength(simulateUnknownLength).appendReadData(TEST_DATA).build();
FakeDataSource upstream = builder.build(); return new CacheDataSource(simpleCache, upstream, new FileDataSource(), cacheWriteDataSink,
return new CacheDataSource(simpleCache, upstream, CacheDataSource.FLAG_BLOCK_ON_CACHE, flags, null);
MAX_CACHE_FILE_SIZE);
} }
} }

View File

@ -515,7 +515,13 @@ public final class C {
* The stereo mode for 360/3D/VR videos. * The stereo mode for 360/3D/VR videos.
*/ */
@Retention(RetentionPolicy.SOURCE) @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 {} public @interface StereoMode {}
/** /**
* Indicates Monoscopic stereo layout, used with 360/3D/VR videos. * 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. * Indicates Left-Right stereo layout, used with 360/3D/VR videos.
*/ */
public static final int STEREO_MODE_LEFT_RIGHT = 2; 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 * Converts a time in microseconds to the corresponding time in milliseconds, preserving

View File

@ -1215,7 +1215,7 @@ import java.io.IOException;
long newLoadingPeriodStartPositionUs; long newLoadingPeriodStartPositionUs;
if (loadingPeriodHolder == null) { if (loadingPeriodHolder == null) {
newLoadingPeriodStartPositionUs = playbackInfo.startPositionUs; newLoadingPeriodStartPositionUs = playbackInfo.positionUs;
} else { } else {
int newLoadingWindowIndex = timeline.getPeriod(newLoadingPeriodIndex, period).windowIndex; int newLoadingWindowIndex = timeline.getPeriod(newLoadingPeriodIndex, period).windowIndex;
if (newLoadingPeriodIndex if (newLoadingPeriodIndex

View File

@ -23,7 +23,7 @@ public interface ExoPlayerLibraryInfo {
/** /**
* The version of the library, expressed as a string. * 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. * 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 * corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding
* integer version 123045006 (123-045-006). * 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} * Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}

View File

@ -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 * 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 * 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 @C.StereoMode
public final int stereoMode; public final int stereoMode;
@ -447,16 +447,19 @@ public final class Format implements Parcelable {
drmInitData, metadata); drmInitData, metadata);
} }
public Format copyWithManifestFormatInfo(Format manifestFormat, public Format copyWithManifestFormatInfo(Format manifestFormat) {
boolean preferManifestDrmInitData) { if (this == manifestFormat) {
// No need to copy from ourselves.
return this;
}
String id = manifestFormat.id; String id = manifestFormat.id;
String codecs = this.codecs == null ? manifestFormat.codecs : this.codecs; String codecs = this.codecs == null ? manifestFormat.codecs : this.codecs;
int bitrate = this.bitrate == NO_VALUE ? manifestFormat.bitrate : this.bitrate; int bitrate = this.bitrate == NO_VALUE ? manifestFormat.bitrate : this.bitrate;
float frameRate = this.frameRate == NO_VALUE ? manifestFormat.frameRate : this.frameRate; float frameRate = this.frameRate == NO_VALUE ? manifestFormat.frameRate : this.frameRate;
@C.SelectionFlags int selectionFlags = this.selectionFlags | manifestFormat.selectionFlags; @C.SelectionFlags int selectionFlags = this.selectionFlags | manifestFormat.selectionFlags;
String language = this.language == null ? manifestFormat.language : this.language; String language = this.language == null ? manifestFormat.language : this.language;
DrmInitData drmInitData = (preferManifestDrmInitData && manifestFormat.drmInitData != null) DrmInitData drmInitData = manifestFormat.drmInitData != null ? manifestFormat.drmInitData
|| this.drmInitData == null ? manifestFormat.drmInitData : this.drmInitData; : this.drmInitData;
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width,
height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode,
channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, selectionFlags, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, selectionFlags,
@ -681,9 +684,6 @@ public final class Format implements Parcelable {
dest.writeParcelable(metadata, 0); dest.writeParcelable(metadata, 0);
} }
/**
* {@link Creator} implementation.
*/
public static final Creator<Format> CREATOR = new Creator<Format>() { public static final Creator<Format> CREATOR = new Creator<Format>() {
@Override @Override

View File

@ -29,6 +29,7 @@ import android.view.SurfaceView;
import android.view.TextureView; import android.view.TextureView;
import com.google.android.exoplayer2.audio.AudioCapabilities; import com.google.android.exoplayer2.audio.AudioCapabilities;
import com.google.android.exoplayer2.audio.AudioRendererEventListener; 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.audio.MediaCodecAudioRenderer;
import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
@ -479,8 +480,8 @@ public class SimpleExoPlayer implements ExoPlayer {
} }
@Override @Override
public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetTimeline) { public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) {
player.prepare(mediaSource, resetPosition, resetTimeline); player.prepare(mediaSource, resetPosition, resetState);
} }
@Override @Override
@ -624,7 +625,7 @@ public class SimpleExoPlayer implements ExoPlayer {
buildVideoRenderers(context, mainHandler, drmSessionManager, extensionRendererMode, buildVideoRenderers(context, mainHandler, drmSessionManager, extensionRendererMode,
componentListener, allowedVideoJoiningTimeMs, out); componentListener, allowedVideoJoiningTimeMs, out);
buildAudioRenderers(context, mainHandler, drmSessionManager, extensionRendererMode, buildAudioRenderers(context, mainHandler, drmSessionManager, extensionRendererMode,
componentListener, out); componentListener, buildBufferProcessors(), out);
buildTextRenderers(context, mainHandler, extensionRendererMode, componentListener, out); buildTextRenderers(context, mainHandler, extensionRendererMode, componentListener, out);
buildMetadataRenderers(context, mainHandler, extensionRendererMode, componentListener, out); buildMetadataRenderers(context, mainHandler, extensionRendererMode, componentListener, out);
buildMiscellaneousRenderers(context, mainHandler, extensionRendererMode, 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 context The {@link Context} associated with the player.
* @param mainHandler A handler associated with the main thread's looper. * @param mainHandler A handler associated with the main thread's looper.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player will * @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 extensionRendererMode The extension renderer mode.
* @param eventListener An event listener. * @param eventListener An event listener.
* @param allowedVideoJoiningTimeMs The maximum duration in milliseconds for which video renderers * @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 context The {@link Context} associated with the player.
* @param mainHandler A handler associated with the main thread's looper. * @param mainHandler A handler associated with the main thread's looper.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player will * @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 extensionRendererMode The extension renderer mode.
* @param eventListener An event listener. * @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. * @param out An array to which the built renderers should be appended.
*/ */
protected void buildAudioRenderers(Context context, Handler mainHandler, protected void buildAudioRenderers(Context context, Handler mainHandler,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@ExtensionRendererMode int extensionRendererMode, AudioRendererEventListener eventListener, @ExtensionRendererMode int extensionRendererMode, AudioRendererEventListener eventListener,
ArrayList<Renderer> out) { BufferProcessor[] bufferProcessors, ArrayList<Renderer> out) {
out.add(new MediaCodecAudioRenderer(MediaCodecSelector.DEFAULT, drmSessionManager, true, 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) { if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) {
return; return;
@ -705,8 +708,9 @@ public class SimpleExoPlayer implements ExoPlayer {
Class<?> clazz = Class<?> clazz =
Class.forName("com.google.android.exoplayer2.ext.opus.LibopusAudioRenderer"); Class.forName("com.google.android.exoplayer2.ext.opus.LibopusAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class, Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class); AudioRendererEventListener.class, BufferProcessor[].class);
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener); Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener,
bufferProcessors);
out.add(extensionRendererIndex++, renderer); out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded LibopusAudioRenderer."); Log.i(TAG, "Loaded LibopusAudioRenderer.");
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
@ -719,8 +723,9 @@ public class SimpleExoPlayer implements ExoPlayer {
Class<?> clazz = Class<?> clazz =
Class.forName("com.google.android.exoplayer2.ext.flac.LibflacAudioRenderer"); Class.forName("com.google.android.exoplayer2.ext.flac.LibflacAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class, Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class); AudioRendererEventListener.class, BufferProcessor[].class);
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener); Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener,
bufferProcessors);
out.add(extensionRendererIndex++, renderer); out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded LibflacAudioRenderer."); Log.i(TAG, "Loaded LibflacAudioRenderer.");
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
@ -733,8 +738,9 @@ public class SimpleExoPlayer implements ExoPlayer {
Class<?> clazz = Class<?> clazz =
Class.forName("com.google.android.exoplayer2.ext.ffmpeg.FfmpegAudioRenderer"); Class.forName("com.google.android.exoplayer2.ext.ffmpeg.FfmpegAudioRenderer");
Constructor<?> constructor = clazz.getConstructor(Handler.class, Constructor<?> constructor = clazz.getConstructor(Handler.class,
AudioRendererEventListener.class); AudioRendererEventListener.class, BufferProcessor[].class);
Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener); Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener,
bufferProcessors);
out.add(extensionRendererIndex++, renderer); out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded FfmpegAudioRenderer."); Log.i(TAG, "Loaded FfmpegAudioRenderer.");
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
@ -787,6 +793,14 @@ public class SimpleExoPlayer implements ExoPlayer {
// Do nothing. // 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. // Internal methods.
private void removeSurfaceCallbacks() { private void removeSurfaceCallbacks() {

View File

@ -25,7 +25,6 @@ import android.os.ConditionVariable;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.Log; import android.util.Log;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
@ -54,7 +53,9 @@ import java.nio.ByteOrder;
* safe to call {@link #handleBuffer(ByteBuffer, long)} after {@link #reset()} without calling * safe to call {@link #handleBuffer(ByteBuffer, long)} after {@link #reset()} without calling
* {@link #configure(String, int, int, int, int)}. * {@link #configure(String, int, int, int, int)}.
* <p> * <p>
* Call {@link #release()} when the instance is no longer required. * Call {@link #handleEndOfStream()} to play out all data when no more input buffers will be
* provided via {@link #handleBuffer(ByteBuffer, long)} until the next {@link #reset}. Call
* {@link #release()} when the instance is no longer required.
*/ */
public final class AudioTrack { public final class AudioTrack {
@ -89,6 +90,21 @@ public final class AudioTrack {
} }
/**
* Thrown when a failure occurs configuring the track.
*/
public static final class ConfigurationException extends Exception {
public ConfigurationException(Throwable cause) {
super(cause);
}
public ConfigurationException(String message) {
super(message);
}
}
/** /**
* Thrown when a failure occurs initializing an {@link android.media.AudioTrack}. * Thrown when a failure occurs initializing an {@link android.media.AudioTrack}.
*/ */
@ -120,13 +136,15 @@ public final class AudioTrack {
public static final class WriteException extends Exception { public static final class WriteException extends Exception {
/** /**
* An error value returned from {@link android.media.AudioTrack#write(byte[], int, int)}. * The error value returned from {@link android.media.AudioTrack#write(byte[], int, int)} or
* {@link android.media.AudioTrack#write(ByteBuffer, int, int)}.
*/ */
public final int errorCode; public final int errorCode;
/** /**
* @param errorCode An error value returned from * @param errorCode The error value returned from
* {@link android.media.AudioTrack#write(byte[], int, int)}. * {@link android.media.AudioTrack#write(byte[], int, int)} or
* {@link android.media.AudioTrack#write(ByteBuffer, int, int)}.
*/ */
public WriteException(int errorCode) { public WriteException(int errorCode) {
super("AudioTrack write failed: " + errorCode); super("AudioTrack write failed: " + errorCode);
@ -212,15 +230,15 @@ public final class AudioTrack {
/** /**
* AudioTrack timestamps are deemed spurious if they are offset from the system clock by more * AudioTrack timestamps are deemed spurious if they are offset from the system clock by more
* than this amount. * than this amount.
* * <p>
* <p>This is a fail safe that should not be required on correctly functioning devices. * This is a fail safe that should not be required on correctly functioning devices.
*/ */
private static final long MAX_AUDIO_TIMESTAMP_OFFSET_US = 5 * C.MICROS_PER_SECOND; private static final long MAX_AUDIO_TIMESTAMP_OFFSET_US = 5 * C.MICROS_PER_SECOND;
/** /**
* AudioTrack latencies are deemed impossibly large if they are greater than this amount. * AudioTrack latencies are deemed impossibly large if they are greater than this amount.
* * <p>
* <p>This is a fail safe that should not be required on correctly functioning devices. * This is a fail safe that should not be required on correctly functioning devices.
*/ */
private static final long MAX_LATENCY_US = 5 * C.MICROS_PER_SECOND; private static final long MAX_LATENCY_US = 5 * C.MICROS_PER_SECOND;
@ -251,6 +269,7 @@ public final class AudioTrack {
public static boolean failOnSpuriousAudioTimestamp = false; public static boolean failOnSpuriousAudioTimestamp = false;
private final AudioCapabilities audioCapabilities; private final AudioCapabilities audioCapabilities;
private final BufferProcessor[] bufferProcessors;
private final Listener listener; private final Listener listener;
private final ConditionVariable releasingConditionVariable; private final ConditionVariable releasingConditionVariable;
private final long[] playheadOffsets; private final long[] playheadOffsets;
@ -264,12 +283,12 @@ public final class AudioTrack {
private android.media.AudioTrack audioTrack; private android.media.AudioTrack audioTrack;
private int sampleRate; private int sampleRate;
private int channelConfig; private int channelConfig;
@C.Encoding
private int encoding;
@C.Encoding
private int outputEncoding;
@C.StreamType @C.StreamType
private int streamType; private int streamType;
@C.Encoding
private int sourceEncoding;
@C.Encoding
private int targetEncoding;
private boolean passthrough; private boolean passthrough;
private int pcmFrameSize; private int pcmFrameSize;
private int bufferSize; private int bufferSize;
@ -295,12 +314,10 @@ public final class AudioTrack {
private long latencyUs; private long latencyUs;
private float volume; private float volume;
private byte[] temporaryBuffer; private ByteBuffer inputBuffer;
private int temporaryBufferOffset; private ByteBuffer outputBuffer;
private ByteBuffer currentSourceBuffer; private byte[] preV21OutputBuffer;
private int preV21OutputBufferOffset;
private ByteBuffer resampledBuffer;
private boolean useResampledBuffer;
private boolean playing; private boolean playing;
private int audioSessionId; private int audioSessionId;
@ -309,11 +326,18 @@ public final class AudioTrack {
private long lastFeedElapsedRealtimeMs; private long lastFeedElapsedRealtimeMs;
/** /**
* @param audioCapabilities The current audio capabilities. * @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 An array of {@link BufferProcessor}s which will process PCM audio
* buffers before they are output. May be empty.
* @param listener Listener for audio track events. * @param listener Listener for audio track events.
*/ */
public AudioTrack(AudioCapabilities audioCapabilities, Listener listener) { public AudioTrack(AudioCapabilities audioCapabilities, BufferProcessor[] bufferProcessors,
Listener listener) {
this.audioCapabilities = audioCapabilities; this.audioCapabilities = audioCapabilities;
this.bufferProcessors = new BufferProcessor[bufferProcessors.length + 1];
this.bufferProcessors[0] = new ResamplingBufferProcessor();
System.arraycopy(bufferProcessors, 0, this.bufferProcessors, 1, bufferProcessors.length);
this.listener = listener; this.listener = listener;
releasingConditionVariable = new ConditionVariable(true); releasingConditionVariable = new ConditionVariable(true);
if (Util.SDK_INT >= 18) { if (Util.SDK_INT >= 18) {
@ -386,7 +410,7 @@ public final class AudioTrack {
// The AudioTrack has started, but we don't have any samples to compute a smoothed position. // The AudioTrack has started, but we don't have any samples to compute a smoothed position.
currentPositionUs = audioTrackUtil.getPlaybackHeadPositionUs() + startMediaTimeUs; currentPositionUs = audioTrackUtil.getPlaybackHeadPositionUs() + startMediaTimeUs;
} else { } else {
// getPlayheadPositionUs() only has a granularity of ~20ms, so we base the position off the // getPlayheadPositionUs() only has a granularity of ~20 ms, so we base the position off the
// system clock (and a smoothed offset between it and the playhead position) so as to // system clock (and a smoothed offset between it and the playhead position) so as to
// prevent jitter in the reported positions. // prevent jitter in the reported positions.
currentPositionUs = systemClockUs + smoothedPlayheadOffsetUs + startMediaTimeUs; currentPositionUs = systemClockUs + smoothedPlayheadOffsetUs + startMediaTimeUs;
@ -410,9 +434,23 @@ public final class AudioTrack {
* {@link C#ENCODING_PCM_32BIT}. * {@link C#ENCODING_PCM_32BIT}.
* @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to infer a * @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to infer a
* suitable buffer size automatically. * suitable buffer size automatically.
* @throws ConfigurationException If an error occurs configuring the track.
*/ */
public void configure(String mimeType, int channelCount, int sampleRate, public void configure(String mimeType, int channelCount, int sampleRate,
@C.PcmEncoding int pcmEncoding, int specifiedBufferSize) { @C.PcmEncoding int pcmEncoding, int specifiedBufferSize) throws ConfigurationException {
boolean passthrough = !MimeTypes.AUDIO_RAW.equals(mimeType);
@C.Encoding int encoding = passthrough ? getEncodingForMimeType(mimeType) : pcmEncoding;
if (!passthrough) {
for (BufferProcessor bufferProcessor : bufferProcessors) {
try {
bufferProcessor.configure(sampleRate, channelCount, encoding);
} catch (BufferProcessor.UnhandledFormatException e) {
throw new ConfigurationException(e);
}
encoding = bufferProcessor.getOutputEncoding();
}
}
int channelConfig; int channelConfig;
switch (channelCount) { switch (channelCount) {
case 1: case 1:
@ -440,7 +478,7 @@ public final class AudioTrack {
channelConfig = C.CHANNEL_OUT_7POINT1_SURROUND; channelConfig = C.CHANNEL_OUT_7POINT1_SURROUND;
break; break;
default: default:
throw new IllegalArgumentException("Unsupported channel count: " + channelCount); throw new ConfigurationException("Unsupported channel count: " + channelCount);
} }
// Workaround for overly strict channel configuration checks on nVidia Shield. // Workaround for overly strict channel configuration checks on nVidia Shield.
@ -458,25 +496,13 @@ public final class AudioTrack {
} }
} }
boolean passthrough = !MimeTypes.AUDIO_RAW.equals(mimeType);
// Workaround for Nexus Player not reporting support for mono passthrough. // Workaround for Nexus Player not reporting support for mono passthrough.
// (See [Internal: b/34268671].) // (See [Internal: b/34268671].)
if (Util.SDK_INT <= 25 && "fugu".equals(Util.DEVICE) && passthrough && channelCount == 1) { if (Util.SDK_INT <= 25 && "fugu".equals(Util.DEVICE) && passthrough && channelCount == 1) {
channelConfig = AudioFormat.CHANNEL_OUT_STEREO; channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
} }
@C.Encoding int sourceEncoding; if (isInitialized() && this.encoding == encoding && this.sampleRate == sampleRate
if (passthrough) {
sourceEncoding = getEncodingForMimeType(mimeType);
} else if (pcmEncoding == C.ENCODING_PCM_8BIT || pcmEncoding == C.ENCODING_PCM_16BIT
|| pcmEncoding == C.ENCODING_PCM_24BIT || pcmEncoding == C.ENCODING_PCM_32BIT) {
sourceEncoding = pcmEncoding;
} else {
throw new IllegalArgumentException("Unsupported PCM encoding: " + pcmEncoding);
}
if (isInitialized() && this.sourceEncoding == sourceEncoding && this.sampleRate == sampleRate
&& this.channelConfig == channelConfig) { && this.channelConfig == channelConfig) {
// We already have an audio track with the correct sample rate, channel config and encoding. // We already have an audio track with the correct sample rate, channel config and encoding.
return; return;
@ -484,28 +510,28 @@ public final class AudioTrack {
reset(); reset();
this.sourceEncoding = sourceEncoding; this.encoding = encoding;
this.passthrough = passthrough; this.passthrough = passthrough;
this.sampleRate = sampleRate; this.sampleRate = sampleRate;
this.channelConfig = channelConfig; this.channelConfig = channelConfig;
targetEncoding = passthrough ? sourceEncoding : C.ENCODING_PCM_16BIT;
pcmFrameSize = 2 * channelCount; // 2 bytes per 16-bit sample * number of channels. pcmFrameSize = 2 * channelCount; // 2 bytes per 16-bit sample * number of channels.
outputEncoding = passthrough ? encoding : C.ENCODING_PCM_16BIT;
if (specifiedBufferSize != 0) { if (specifiedBufferSize != 0) {
bufferSize = specifiedBufferSize; bufferSize = specifiedBufferSize;
} else if (passthrough) { } else if (passthrough) {
// TODO: Set the minimum buffer size using getMinBufferSize when it takes the encoding into // TODO: Set the minimum buffer size using getMinBufferSize when it takes the encoding into
// account. [Internal: b/25181305] // account. [Internal: b/25181305]
if (targetEncoding == C.ENCODING_AC3 || targetEncoding == C.ENCODING_E_AC3) { if (outputEncoding == C.ENCODING_AC3 || outputEncoding == C.ENCODING_E_AC3) {
// AC-3 allows bitrates up to 640 kbit/s. // AC-3 allows bitrates up to 640 kbit/s.
bufferSize = (int) (PASSTHROUGH_BUFFER_DURATION_US * 80 * 1024 / C.MICROS_PER_SECOND); bufferSize = (int) (PASSTHROUGH_BUFFER_DURATION_US * 80 * 1024 / C.MICROS_PER_SECOND);
} else /* (targetEncoding == C.ENCODING_DTS || targetEncoding == C.ENCODING_DTS_HD */ { } else /* (outputEncoding == C.ENCODING_DTS || outputEncoding == C.ENCODING_DTS_HD */ {
// DTS allows an 'open' bitrate, but we assume the maximum listed value: 1536 kbit/s. // DTS allows an 'open' bitrate, but we assume the maximum listed value: 1536 kbit/s.
bufferSize = (int) (PASSTHROUGH_BUFFER_DURATION_US * 192 * 1024 / C.MICROS_PER_SECOND); bufferSize = (int) (PASSTHROUGH_BUFFER_DURATION_US * 192 * 1024 / C.MICROS_PER_SECOND);
} }
} else { } else {
int minBufferSize = int minBufferSize =
android.media.AudioTrack.getMinBufferSize(sampleRate, channelConfig, targetEncoding); android.media.AudioTrack.getMinBufferSize(sampleRate, channelConfig, outputEncoding);
Assertions.checkState(minBufferSize != ERROR_BAD_VALUE); Assertions.checkState(minBufferSize != ERROR_BAD_VALUE);
int multipliedBufferSize = minBufferSize * BUFFER_MULTIPLICATION_FACTOR; int multipliedBufferSize = minBufferSize * BUFFER_MULTIPLICATION_FACTOR;
int minAppBufferSize = (int) durationUsToFrames(MIN_BUFFER_DURATION_US) * pcmFrameSize; int minAppBufferSize = (int) durationUsToFrames(MIN_BUFFER_DURATION_US) * pcmFrameSize;
@ -527,15 +553,15 @@ public final class AudioTrack {
releasingConditionVariable.block(); releasingConditionVariable.block();
if (tunneling) { if (tunneling) {
audioTrack = createHwAvSyncAudioTrackV21(sampleRate, channelConfig, targetEncoding, audioTrack = createHwAvSyncAudioTrackV21(sampleRate, channelConfig, outputEncoding,
bufferSize, audioSessionId); bufferSize, audioSessionId);
} else if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) { } else if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) {
audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig,
targetEncoding, bufferSize, MODE_STREAM); outputEncoding, bufferSize, MODE_STREAM);
} else { } else {
// Re-attach to the same audio session. // Re-attach to the same audio session.
audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig,
targetEncoding, bufferSize, MODE_STREAM, audioSessionId); outputEncoding, bufferSize, MODE_STREAM, audioSessionId);
} }
checkAudioTrackInitialized(); checkAudioTrackInitialized();
@ -607,8 +633,10 @@ public final class AudioTrack {
* @throws InitializationException If an error occurs initializing the track. * @throws InitializationException If an error occurs initializing the track.
* @throws WriteException If an error occurs writing the audio data. * @throws WriteException If an error occurs writing the audio data.
*/ */
@SuppressWarnings("ReferenceEquality")
public boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs) public boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs)
throws InitializationException, WriteException { throws InitializationException, WriteException {
Assertions.checkArgument(inputBuffer == null || buffer == inputBuffer);
if (!isInitialized()) { if (!isInitialized()) {
initialize(); initialize();
if (playing) { if (playing) {
@ -616,26 +644,12 @@ public final class AudioTrack {
} }
} }
boolean hadData = hasData;
hasData = hasPendingData();
if (hadData && !hasData && audioTrack.getPlayState() != PLAYSTATE_STOPPED) {
long elapsedSinceLastFeedMs = SystemClock.elapsedRealtime() - lastFeedElapsedRealtimeMs;
listener.onUnderrun(bufferSize, C.usToMs(bufferSizeUs), elapsedSinceLastFeedMs);
}
boolean result = writeBuffer(buffer, presentationTimeUs);
lastFeedElapsedRealtimeMs = SystemClock.elapsedRealtime();
return result;
}
private boolean writeBuffer(ByteBuffer buffer, long presentationTimeUs) throws WriteException {
boolean isNewSourceBuffer = currentSourceBuffer == null;
Assertions.checkState(isNewSourceBuffer || currentSourceBuffer == buffer);
currentSourceBuffer = buffer;
if (needsPassthroughWorkarounds()) { if (needsPassthroughWorkarounds()) {
// An AC-3 audio track continues to play data written while it is paused. Stop writing so its // An AC-3 audio track continues to play data written while it is paused. Stop writing so its
// buffer empties. See [Internal: b/18899620]. // buffer empties. See [Internal: b/18899620].
if (audioTrack.getPlayState() == PLAYSTATE_PAUSED) { if (audioTrack.getPlayState() == PLAYSTATE_PAUSED) {
// We force an underrun to pause the track, so don't notify the listener in this case.
hasData = false;
return false; return false;
} }
@ -648,27 +662,25 @@ public final class AudioTrack {
} }
} }
if (isNewSourceBuffer) { boolean hadData = hasData;
// We're seeing this buffer for the first time. hasData = hasPendingData();
if (hadData && !hasData && audioTrack.getPlayState() != PLAYSTATE_STOPPED) {
long elapsedSinceLastFeedMs = SystemClock.elapsedRealtime() - lastFeedElapsedRealtimeMs;
listener.onUnderrun(bufferSize, C.usToMs(bufferSizeUs), elapsedSinceLastFeedMs);
}
if (!currentSourceBuffer.hasRemaining()) { if (inputBuffer == null) {
// We are seeing this buffer for the first time.
if (!buffer.hasRemaining()) {
// The buffer is empty. // The buffer is empty.
currentSourceBuffer = null;
return true; return true;
} }
useResampledBuffer = targetEncoding != sourceEncoding;
if (useResampledBuffer) {
Assertions.checkState(targetEncoding == C.ENCODING_PCM_16BIT);
// Resample the buffer to get the data in the target encoding.
resampledBuffer = resampleTo16BitPcm(currentSourceBuffer, sourceEncoding, resampledBuffer);
buffer = resampledBuffer;
}
if (passthrough && framesPerEncodedSample == 0) { if (passthrough && framesPerEncodedSample == 0) {
// If this is the first encoded sample, calculate the sample size in frames. // If this is the first encoded sample, calculate the sample size in frames.
framesPerEncodedSample = getFramesPerEncodedSample(targetEncoding, buffer); framesPerEncodedSample = getFramesPerEncodedSample(outputEncoding, buffer);
} }
if (startMediaTimeState == START_NOT_SET) { if (startMediaTimeState == START_NOT_SET) {
startMediaTimeUs = Math.max(0, presentationTimeUs); startMediaTimeUs = Math.max(0, presentationTimeUs);
startMediaTimeState = START_IN_SYNC; startMediaTimeState = START_IN_SYNC;
@ -690,21 +702,35 @@ public final class AudioTrack {
listener.onPositionDiscontinuity(); listener.onPositionDiscontinuity();
} }
} }
if (Util.SDK_INT < 21) {
// Copy {@code buffer} into {@code temporaryBuffer}. inputBuffer = buffer;
int bytesRemaining = buffer.remaining(); if (!passthrough) {
if (temporaryBuffer == null || temporaryBuffer.length < bytesRemaining) { for (BufferProcessor bufferProcessor : bufferProcessors) {
temporaryBuffer = new byte[bytesRemaining]; buffer = bufferProcessor.handleBuffer(buffer);
} }
int originalPosition = buffer.position(); }
buffer.get(temporaryBuffer, 0, bytesRemaining); outputBuffer = buffer;
buffer.position(originalPosition); if (Util.SDK_INT < 21) {
temporaryBufferOffset = 0; int bytesRemaining = outputBuffer.remaining();
if (preV21OutputBuffer == null || preV21OutputBuffer.length < bytesRemaining) {
preV21OutputBuffer = new byte[bytesRemaining];
}
int originalPosition = outputBuffer.position();
outputBuffer.get(preV21OutputBuffer, 0, bytesRemaining);
outputBuffer.position(originalPosition);
preV21OutputBufferOffset = 0;
} }
} }
buffer = useResampledBuffer ? resampledBuffer : buffer; if (writeOutputBuffer(presentationTimeUs)) {
int bytesRemaining = buffer.remaining(); inputBuffer = null;
return true;
}
return false;
}
private boolean writeOutputBuffer(long presentationTimeUs) throws WriteException {
int bytesRemaining = outputBuffer.remaining();
int bytesWritten = 0; int bytesWritten = 0;
if (Util.SDK_INT < 21) { // passthrough == false if (Util.SDK_INT < 21) { // passthrough == false
// Work out how many bytes we can write without the risk of blocking. // Work out how many bytes we can write without the risk of blocking.
@ -713,18 +739,21 @@ public final class AudioTrack {
int bytesToWrite = bufferSize - bytesPending; int bytesToWrite = bufferSize - bytesPending;
if (bytesToWrite > 0) { if (bytesToWrite > 0) {
bytesToWrite = Math.min(bytesRemaining, bytesToWrite); bytesToWrite = Math.min(bytesRemaining, bytesToWrite);
bytesWritten = audioTrack.write(temporaryBuffer, temporaryBufferOffset, bytesToWrite); bytesWritten = audioTrack.write(preV21OutputBuffer, preV21OutputBufferOffset, bytesToWrite);
if (bytesWritten >= 0) { if (bytesWritten > 0) {
temporaryBufferOffset += bytesWritten; preV21OutputBufferOffset += bytesWritten;
outputBuffer.position(outputBuffer.position() + bytesWritten);
} }
buffer.position(buffer.position() + bytesWritten);
} }
} else if (tunneling) {
bytesWritten = writeNonBlockingWithAvSyncV21(audioTrack, outputBuffer, bytesRemaining,
presentationTimeUs);
} else { } else {
bytesWritten = tunneling bytesWritten = writeNonBlockingV21(audioTrack, outputBuffer, bytesRemaining);
? writeNonBlockingWithAvSyncV21(audioTrack, buffer, bytesRemaining, presentationTimeUs)
: writeNonBlockingV21(audioTrack, buffer, bytesRemaining);
} }
lastFeedElapsedRealtimeMs = SystemClock.elapsedRealtime();
if (bytesWritten < 0) { if (bytesWritten < 0) {
throw new WriteException(bytesWritten); throw new WriteException(bytesWritten);
} }
@ -736,7 +765,6 @@ public final class AudioTrack {
if (passthrough) { if (passthrough) {
submittedEncodedFrames += framesPerEncodedSample; submittedEncodedFrames += framesPerEncodedSample;
} }
currentSourceBuffer = null;
return true; return true;
} }
return false; return false;
@ -812,7 +840,7 @@ public final class AudioTrack {
* audio session id has changed. Enabling tunneling requires platform API version 21 onwards. * audio session id has changed. Enabling tunneling requires platform API version 21 onwards.
* *
* @param tunnelingAudioSessionId The audio session id to use. * @param tunnelingAudioSessionId The audio session id to use.
* @throws IllegalStateException Thrown if enabling tunneling on platform API version < 21. * @throws IllegalStateException Thrown if enabling tunneling on platform API version &lt; 21.
*/ */
public void enableTunnelingV21(int tunnelingAudioSessionId) { public void enableTunnelingV21(int tunnelingAudioSessionId) {
Assertions.checkState(Util.SDK_INT >= 21); Assertions.checkState(Util.SDK_INT >= 21);
@ -880,8 +908,11 @@ public final class AudioTrack {
submittedPcmBytes = 0; submittedPcmBytes = 0;
submittedEncodedFrames = 0; submittedEncodedFrames = 0;
framesPerEncodedSample = 0; framesPerEncodedSample = 0;
currentSourceBuffer = null; inputBuffer = null;
avSyncHeader = null; avSyncHeader = null;
for (BufferProcessor bufferProcessor : bufferProcessors) {
bufferProcessor.flush();
}
bytesUntilNextAvSync = 0; bytesUntilNextAvSync = 0;
startMediaTimeState = START_NOT_SET; startMediaTimeState = START_NOT_SET;
latencyUs = 0; latencyUs = 0;
@ -915,6 +946,9 @@ public final class AudioTrack {
public void release() { public void release() {
reset(); reset();
releaseKeepSessionIdAudioTrack(); releaseKeepSessionIdAudioTrack();
for (BufferProcessor bufferProcessor : bufferProcessors) {
bufferProcessor.release();
}
audioSessionId = C.AUDIO_SESSION_ID_UNSET; audioSessionId = C.AUDIO_SESSION_ID_UNSET;
playing = false; playing = false;
} }
@ -1089,7 +1123,7 @@ public final class AudioTrack {
*/ */
private boolean needsPassthroughWorkarounds() { private boolean needsPassthroughWorkarounds() {
return Util.SDK_INT < 23 return Util.SDK_INT < 23
&& (targetEncoding == C.ENCODING_AC3 || targetEncoding == C.ENCODING_E_AC3); && (outputEncoding == C.ENCODING_AC3 || outputEncoding == C.ENCODING_E_AC3);
} }
/** /**
@ -1124,82 +1158,6 @@ public final class AudioTrack {
sessionId); sessionId);
} }
/**
* Converts the provided buffer into 16-bit PCM.
*
* @param buffer The buffer containing the data to convert.
* @param sourceEncoding The data encoding.
* @param out A buffer into which the output should be written, if its capacity is sufficient.
* @return The 16-bit PCM output. Different to the out parameter if null was passed, or if the
* capacity was insufficient for the output.
*/
private static ByteBuffer resampleTo16BitPcm(ByteBuffer buffer, @C.PcmEncoding int sourceEncoding,
ByteBuffer out) {
int offset = buffer.position();
int limit = buffer.limit();
int size = limit - offset;
int resampledSize;
switch (sourceEncoding) {
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_PCM_16BIT:
case C.ENCODING_INVALID:
case Format.NO_VALUE:
default:
// Never happens.
throw new IllegalStateException();
}
ByteBuffer resampledBuffer = out;
if (resampledBuffer == null || resampledBuffer.capacity() < resampledSize) {
resampledBuffer = ByteBuffer.allocateDirect(resampledSize);
}
resampledBuffer.position(0);
resampledBuffer.limit(resampledSize);
// Samples are little endian.
switch (sourceEncoding) {
case C.ENCODING_PCM_8BIT:
// 8->16 bit resampling. Shift each byte from [0, 256) to [-128, 128) and scale up.
for (int i = offset; i < limit; i++) {
resampledBuffer.put((byte) 0);
resampledBuffer.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 = offset; i < limit; i += 3) {
resampledBuffer.put(buffer.get(i + 1));
resampledBuffer.put(buffer.get(i + 2));
}
break;
case C.ENCODING_PCM_32BIT:
// 32->16 bit resampling. Drop the two least significant bytes.
for (int i = offset; i < limit; i += 4) {
resampledBuffer.put(buffer.get(i + 2));
resampledBuffer.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();
}
resampledBuffer.position(0);
return resampledBuffer;
}
@C.Encoding @C.Encoding
private static int getEncodingForMimeType(String mimeType) { private static int getEncodingForMimeType(String mimeType) {
switch (mimeType) { switch (mimeType) {

View File

@ -0,0 +1,74 @@
/*
* 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();
}

View File

@ -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 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 * @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. * 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, public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys, Handler eventHandler, boolean playClearSamplesWithoutKeys, Handler eventHandler,
AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities) { AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities,
BufferProcessor... bufferProcessors) {
super(C.TRACK_TYPE_AUDIO, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys); 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); eventDispatcher = new EventDispatcher(eventHandler, eventListener);
} }
@ -183,7 +186,8 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
} }
@Override @Override
protected void configureCodec(MediaCodec codec, Format format, MediaCrypto crypto) { protected void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format,
MediaCrypto crypto) {
if (passthroughEnabled) { if (passthroughEnabled) {
// Override the MIME type used to configure the codec if we are using a passthrough decoder. // Override the MIME type used to configure the codec if we are using a passthrough decoder.
passthroughMediaFormat = format.getFrameworkMediaFormatV16(); passthroughMediaFormat = format.getFrameworkMediaFormatV16();
@ -218,14 +222,19 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
} }
@Override @Override
protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat) { protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat)
throws ExoPlaybackException {
boolean passthrough = passthroughMediaFormat != null; boolean passthrough = passthroughMediaFormat != null;
String mimeType = passthrough ? passthroughMediaFormat.getString(MediaFormat.KEY_MIME) String mimeType = passthrough ? passthroughMediaFormat.getString(MediaFormat.KEY_MIME)
: MimeTypes.AUDIO_RAW; : MimeTypes.AUDIO_RAW;
MediaFormat format = passthrough ? passthroughMediaFormat : outputFormat; MediaFormat format = passthrough ? passthroughMediaFormat : outputFormat;
int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); 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());
}
} }
/** /**

View File

@ -0,0 +1,131 @@
/*
* 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;
}
}

View File

@ -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 * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required. * 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 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, public SimpleDecoderAudioRenderer(Handler eventHandler,
AudioRendererEventListener eventListener) { AudioRendererEventListener eventListener, BufferProcessor... bufferProcessors) {
this(eventHandler, eventListener, null); 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 * 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} * permitted to play clear regions of encrypted media files before {@code drmSessionManager}
* has obtained the keys necessary to decrypt encrypted regions of the media. * 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, public SimpleDecoderAudioRenderer(Handler eventHandler,
AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities, AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities,
DrmSessionManager<ExoMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys) { DrmSessionManager<ExoMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys,
BufferProcessor... bufferProcessors) {
super(C.TRACK_TYPE_AUDIO); super(C.TRACK_TYPE_AUDIO);
eventDispatcher = new EventDispatcher(eventHandler, eventListener); eventDispatcher = new EventDispatcher(eventHandler, eventListener);
audioTrack = new AudioTrack(audioCapabilities, new AudioTrackListener()); audioTrack = new AudioTrack(audioCapabilities, bufferProcessors, new AudioTrackListener());
this.drmSessionManager = drmSessionManager; this.drmSessionManager = drmSessionManager;
formatHolder = new FormatHolder(); formatHolder = new FormatHolder();
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys; this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
@ -193,8 +198,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
while (drainOutputBuffer()) {} while (drainOutputBuffer()) {}
while (feedInputBuffer()) {} while (feedInputBuffer()) {}
TraceUtil.endSection(); TraceUtil.endSection();
} catch (AudioTrack.InitializationException | AudioTrack.WriteException } catch (AudioDecoderException | AudioTrack.ConfigurationException
| AudioDecoderException e) { | AudioTrack.InitializationException | AudioTrack.WriteException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex()); throw ExoPlaybackException.createForRenderer(e, getIndex());
} }
decoderCounters.ensureUpdated(); decoderCounters.ensureUpdated();
@ -255,7 +260,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
} }
private boolean drainOutputBuffer() throws ExoPlaybackException, AudioDecoderException, private boolean drainOutputBuffer() throws ExoPlaybackException, AudioDecoderException,
AudioTrack.InitializationException, AudioTrack.WriteException { AudioTrack.ConfigurationException, AudioTrack.InitializationException,
AudioTrack.WriteException {
if (outputBuffer == null) { if (outputBuffer == null) {
outputBuffer = decoder.dequeueOutputBuffer(); outputBuffer = decoder.dequeueOutputBuffer();
if (outputBuffer == null) { if (outputBuffer == null) {

View File

@ -280,6 +280,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
* required. * required.
* *
* <p>{@code mode} must be one of these: * <p>{@code mode} must be one of these:
* <ul>
* <li>{@link #MODE_PLAYBACK}: If {@code offlineLicenseKeySetId} is null, a streaming license is * <li>{@link #MODE_PLAYBACK}: If {@code offlineLicenseKeySetId} is null, a streaming license is
* requested otherwise the offline license is restored. * requested otherwise the offline license is restored.
* <li>{@link #MODE_QUERY}: {@code offlineLicenseKeySetId} can not be null. The offline license * <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. * requested otherwise the offline license is renewed.
* <li>{@link #MODE_RELEASE}: {@code offlineLicenseKeySetId} can not be null. The offline license * <li>{@link #MODE_RELEASE}: {@code offlineLicenseKeySetId} can not be null. The offline license
* is released. * is released.
* </ul>
* *
* @param mode The mode to be set. * @param mode The mode to be set.
* @param offlineLicenseKeySetId The key set id of the license to be used with the given mode. * @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) { private void postKeyRequest(byte[] scope, int keyType) {
KeyRequest keyRequest;
try { try {
keyRequest = mediaDrm.getKeyRequest(scope, schemeInitData, schemeMimeType, keyType, KeyRequest keyRequest = mediaDrm.getKeyRequest(scope, schemeInitData, schemeMimeType, keyType,
optionalKeyRequestParameters); optionalKeyRequestParameters);
postRequestHandler.obtainMessage(MSG_KEYS, keyRequest).sendToTarget(); postRequestHandler.obtainMessage(MSG_KEYS, keyRequest).sendToTarget();
} catch (Exception e) { } catch (Exception e) {
@ -564,7 +565,8 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
} }
} else { } else {
byte[] keySetId = mediaDrm.provideKeyResponse(sessionId, (byte[]) response); 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; offlineLicenseKeySetId = keySetId;
} }
state = STATE_OPENED_WITH_KEYS; state = STATE_OPENED_WITH_KEYS;

View File

@ -31,7 +31,7 @@ public interface DrmSession<T extends ExoMediaCrypto> {
/** Wraps the exception which is the cause of the error state. */ /** Wraps the exception which is the cause of the error state. */
class DrmSessionException extends Exception { class DrmSessionException extends Exception {
DrmSessionException(Exception e) { public DrmSessionException(Exception e) {
super(e); super(e);
} }

View File

@ -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.DataSourceInputStream;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.HttpDataSource; 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 com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; 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 defaultUrl The default license URL.
* @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances. * @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
* @param keyRequestProperties Request properties to set when making key requests, or null. * @param keyRequestProperties Request properties to set when making key requests, or null.
*/ */
@Deprecated
public HttpMediaDrmCallback(String defaultUrl, HttpDataSource.Factory dataSourceFactory, public HttpMediaDrmCallback(String defaultUrl, HttpDataSource.Factory dataSourceFactory,
Map<String, String> keyRequestProperties) { Map<String, String> keyRequestProperties) {
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.defaultUrl = defaultUrl; 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 @Override
public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws IOException { public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws IOException {
String url = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData()); 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 @Override
@ -85,14 +128,14 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback {
if (C.PLAYREADY_UUID.equals(uuid)) { if (C.PLAYREADY_UUID.equals(uuid)) {
requestProperties.putAll(PLAYREADY_KEY_REQUEST_PROPERTIES); requestProperties.putAll(PLAYREADY_KEY_REQUEST_PROPERTIES);
} }
if (keyRequestProperties != null) { synchronized (keyRequestProperties) {
requestProperties.putAll(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) private static byte[] executePost(HttpDataSource.Factory dataSourceFactory, String url,
throws IOException { byte[] data, Map<String, String> requestProperties) throws IOException {
HttpDataSource dataSource = dataSourceFactory.createDataSource(); HttpDataSource dataSource = dataSourceFactory.createDataSource();
if (requestProperties != null) { if (requestProperties != null) {
for (Map.Entry<String, String> requestProperty : requestProperties.entrySet()) { for (Map.Entry<String, String> requestProperty : requestProperties.entrySet()) {

View File

@ -93,7 +93,7 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
public static OfflineLicenseHelper<FrameworkMediaCrypto> newWidevineInstance( public static OfflineLicenseHelper<FrameworkMediaCrypto> newWidevineInstance(
String licenseUrl, Factory httpDataSourceFactory) throws UnsupportedDrmException { String licenseUrl, Factory httpDataSourceFactory) throws UnsupportedDrmException {
return newWidevineInstance( 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); Representation representation = adaptationSet.representations.get(0);
DrmInitData drmInitData = representation.format.drmInitData; DrmInitData drmInitData = representation.format.drmInitData;
if (drmInitData == null) { if (drmInitData == null) {
InitializationChunk initializationChunk = loadInitializationChunk(dataSource, representation); ChunkExtractorWrapper extractorWrapper = newWrappedExtractor(representation.format,
adaptationSet.type);
InitializationChunk initializationChunk = loadInitializationChunk(dataSource, representation,
extractorWrapper);
if (initializationChunk == null) { if (initializationChunk == null) {
return null; return null;
} }
Format sampleFormat = initializationChunk.getSampleFormat(); Format sampleFormat = extractorWrapper.getSampleFormat();
if (sampleFormat != null) { if (sampleFormat != null) {
drmInitData = sampleFormat.drmInitData; drmInitData = sampleFormat.drmInitData;
} }
@ -288,8 +291,9 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
return session; return session;
} }
private static InitializationChunk loadInitializationChunk(final DataSource dataSource, private static InitializationChunk loadInitializationChunk(DataSource dataSource,
final Representation representation) throws IOException, InterruptedException { Representation representation, ChunkExtractorWrapper extractorWrapper)
throws IOException, InterruptedException {
RangedUri rangedUri = representation.getInitializationUri(); RangedUri rangedUri = representation.getInitializationUri();
if (rangedUri == null) { if (rangedUri == null) {
return null; return null;
@ -298,18 +302,17 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
rangedUri.length, representation.getCacheKey()); rangedUri.length, representation.getCacheKey());
InitializationChunk initializationChunk = new InitializationChunk(dataSource, dataSpec, InitializationChunk initializationChunk = new InitializationChunk(dataSource, dataSpec,
representation.format, C.SELECTION_REASON_UNKNOWN, null /* trackSelectionData */, representation.format, C.SELECTION_REASON_UNKNOWN, null /* trackSelectionData */,
newWrappedExtractor(representation.format)); extractorWrapper);
initializationChunk.load(); initializationChunk.load();
return initializationChunk; return initializationChunk;
} }
private static ChunkExtractorWrapper newWrappedExtractor(final Format format) { private static ChunkExtractorWrapper newWrappedExtractor(Format format, int trackType) {
final String mimeType = format.containerMimeType; final String mimeType = format.containerMimeType;
final boolean isWebm = mimeType.startsWith(MimeTypes.VIDEO_WEBM) final boolean isWebm = mimeType.startsWith(MimeTypes.VIDEO_WEBM)
|| mimeType.startsWith(MimeTypes.AUDIO_WEBM); || mimeType.startsWith(MimeTypes.AUDIO_WEBM);
final Extractor extractor = isWebm ? new MatroskaExtractor() : new FragmentedMp4Extractor(); final Extractor extractor = isWebm ? new MatroskaExtractor() : new FragmentedMp4Extractor();
return new ChunkExtractorWrapper(extractor, format, false /* preferManifestDrmInitData */, return new ChunkExtractorWrapper(extractor, format, trackType);
false /* resendFormatOnInit */);
} }
} }

View File

@ -70,6 +70,8 @@ public final class DefaultTrackOutput implements TrackOutput {
private Format downstreamFormat; private Format downstreamFormat;
// Accessed only by the loading thread (or the consuming thread when there is no loading thread). // 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 sampleOffsetUs;
private long totalBytesWritten; private long totalBytesWritten;
private Allocation lastAllocation; 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 * Sets an offset that will be added to the timestamps (and sub-sample timestamps) of samples
* samples subsequently queued to the buffer. The offset is also used to adjust * subsequently queued to the buffer.
* {@link Format#subsampleOffsetUs} for both the {@link Format} passed and those subsequently
* passed to {@link #format(Format)}.
* *
* @param format The format.
* @param sampleOffsetUs The timestamp offset in microseconds. * @param sampleOffsetUs The timestamp offset in microseconds.
*/ */
public void formatWithOffset(Format format, long sampleOffsetUs) { public void setSampleOffsetUs(long sampleOffsetUs) {
this.sampleOffsetUs = sampleOffsetUs; if (this.sampleOffsetUs != sampleOffsetUs) {
format(format); this.sampleOffsetUs = sampleOffsetUs;
pendingFormatAdjustment = true;
}
} }
@Override @Override
public void format(Format format) { public void format(Format format) {
Format adjustedFormat = getAdjustedSampleFormat(format, sampleOffsetUs); Format adjustedFormat = getAdjustedSampleFormat(format, sampleOffsetUs);
boolean formatChanged = infoQueue.format(adjustedFormat); boolean formatChanged = infoQueue.format(adjustedFormat);
lastUnadjustedFormat = format;
pendingFormatAdjustment = false;
if (upstreamFormatChangeListener != null && formatChanged) { if (upstreamFormatChangeListener != null && formatChanged) {
upstreamFormatChangeListener.onUpstreamFormatChanged(adjustedFormat); upstreamFormatChangeListener.onUpstreamFormatChanged(adjustedFormat);
} }
@ -518,6 +521,9 @@ public final class DefaultTrackOutput implements TrackOutput {
@Override @Override
public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset,
byte[] encryptionKey) { byte[] encryptionKey) {
if (pendingFormatAdjustment) {
format(lastUnadjustedFormat);
}
if (!startWriteOperation()) { if (!startWriteOperation()) {
infoQueue.commitSampleTimestamp(timeUs); infoQueue.commitSampleTimestamp(timeUs);
return; return;

View File

@ -102,4 +102,5 @@ public interface Extractor {
* Releases all kept resources. * Releases all kept resources.
*/ */
void release(); void release();
} }

View File

@ -23,17 +23,18 @@ public interface ExtractorOutput {
/** /**
* Called by the {@link Extractor} to get the {@link TrackOutput} for a specific track. * Called by the {@link Extractor} to get the {@link TrackOutput} for a specific track.
* <p> * <p>
* The same {@link TrackOutput} is returned if multiple calls are made with the same * The same {@link TrackOutput} is returned if multiple calls are made with the same {@code id}.
* {@code trackId}.
* *
* @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. * @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 * 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(); void endTracks();

View File

@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.extractor.flv; 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.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
@ -183,10 +184,12 @@ public final class FlvExtractor implements Extractor, SeekMap {
boolean hasAudio = (flags & 0x04) != 0; boolean hasAudio = (flags & 0x04) != 0;
boolean hasVideo = (flags & 0x01) != 0; boolean hasVideo = (flags & 0x01) != 0;
if (hasAudio && audioReader == null) { 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) { 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) { if (metadataReader == null) {
metadataReader = new ScriptTagPayloadReader(null); metadataReader = new ScriptTagPayloadReader(null);

View File

@ -546,11 +546,9 @@ public final class MatroskaExtractor implements Extractor {
} }
break; break;
case ID_TRACK_ENTRY: case ID_TRACK_ENTRY:
if (tracks.get(currentTrack.number) == null && isCodecSupported(currentTrack.codecId)) { if (isCodecSupported(currentTrack.codecId)) {
currentTrack.initializeOutput(extractorOutput, currentTrack.number); currentTrack.initializeOutput(extractorOutput, currentTrack.number);
tracks.put(currentTrack.number, currentTrack); tracks.put(currentTrack.number, currentTrack);
} else {
// We've seen this track entry before, or the codec is unsupported. Do nothing.
} }
currentTrack = null; currentTrack = null;
break; break;
@ -692,6 +690,9 @@ public final class MatroskaExtractor implements Extractor {
case 3: case 3:
currentTrack.stereoMode = C.STEREO_MODE_TOP_BOTTOM; currentTrack.stereoMode = C.STEREO_MODE_TOP_BOTTOM;
break; break;
case 15:
currentTrack.stereoMode = C.STEREO_MODE_STEREO_MESH;
break;
default: default:
break; break;
} }
@ -1525,6 +1526,7 @@ public final class MatroskaExtractor implements Extractor {
throw new ParserException("Unrecognized codec identifier."); throw new ParserException("Unrecognized codec identifier.");
} }
int type;
Format format; Format format;
@C.SelectionFlags int selectionFlags = 0; @C.SelectionFlags int selectionFlags = 0;
selectionFlags |= flagDefault ? C.SELECTION_FLAG_DEFAULT : 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 // TODO: Consider reading the name elements of the tracks and, if present, incorporating them
// into the trackId passed when creating the formats. // into the trackId passed when creating the formats.
if (MimeTypes.isAudio(mimeType)) { if (MimeTypes.isAudio(mimeType)) {
type = C.TRACK_TYPE_AUDIO;
format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null, format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null,
Format.NO_VALUE, maxInputSize, channelCount, sampleRate, pcmEncoding, Format.NO_VALUE, maxInputSize, channelCount, sampleRate, pcmEncoding,
initializationData, drmInitData, selectionFlags, language); initializationData, drmInitData, selectionFlags, language);
} else if (MimeTypes.isVideo(mimeType)) { } else if (MimeTypes.isVideo(mimeType)) {
type = C.TRACK_TYPE_VIDEO;
if (displayUnit == Track.DISPLAY_UNIT_PIXELS) { if (displayUnit == Track.DISPLAY_UNIT_PIXELS) {
displayWidth = displayWidth == Format.NO_VALUE ? width : displayWidth; displayWidth = displayWidth == Format.NO_VALUE ? width : displayWidth;
displayHeight = displayHeight == Format.NO_VALUE ? height : displayHeight; 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, maxInputSize, width, height, Format.NO_VALUE, initializationData,
Format.NO_VALUE, pixelWidthHeightRatio, projectionData, stereoMode, drmInitData); Format.NO_VALUE, pixelWidthHeightRatio, projectionData, stereoMode, drmInitData);
} else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) { } else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) {
type = C.TRACK_TYPE_TEXT;
format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null, format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null,
Format.NO_VALUE, selectionFlags, language, drmInitData); Format.NO_VALUE, selectionFlags, language, drmInitData);
} else if (MimeTypes.APPLICATION_VOBSUB.equals(mimeType) } else if (MimeTypes.APPLICATION_VOBSUB.equals(mimeType)
|| MimeTypes.APPLICATION_PGS.equals(mimeType)) { || MimeTypes.APPLICATION_PGS.equals(mimeType)) {
type = C.TRACK_TYPE_TEXT;
format = Format.createImageSampleFormat(Integer.toString(trackId), mimeType, null, format = Format.createImageSampleFormat(Integer.toString(trackId), mimeType, null,
Format.NO_VALUE, initializationData, language, drmInitData); Format.NO_VALUE, initializationData, language, drmInitData);
} else if (MimeTypes.TEXT_SSA.equals(mimeType)) { } else if (MimeTypes.TEXT_SSA.equals(mimeType)) {
@ -1561,7 +1567,7 @@ public final class MatroskaExtractor implements Extractor {
throw new ParserException("Unexpected MIME type."); throw new ParserException("Unexpected MIME type.");
} }
this.output = output.track(number); this.output = output.track(number, type);
this.output.format(format); this.output.format(format);
} }

View File

@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.extractor.mp3; package com.google.android.exoplayer2.extractor.mp3;
import android.support.annotation.IntDef;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
@ -33,6 +34,8 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** /**
* Extracts data from an MP3 file. * Extracts data from an MP3 file.
@ -51,6 +54,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. * The maximum number of bytes to search when synchronizing, before giving up.
*/ */
@ -72,6 +87,7 @@ public final class Mp3Extractor implements Extractor {
private static final int INFO_HEADER = Util.getIntegerCodeForString("Info"); private static final int INFO_HEADER = Util.getIntegerCodeForString("Info");
private static final int VBRI_HEADER = Util.getIntegerCodeForString("VBRI"); private static final int VBRI_HEADER = Util.getIntegerCodeForString("VBRI");
@Flags private final int flags;
private final long forcedFirstSampleTimestampUs; private final long forcedFirstSampleTimestampUs;
private final ParsableByteArray scratch; private final ParsableByteArray scratch;
private final MpegAudioHeader synchronizedHeader; private final MpegAudioHeader synchronizedHeader;
@ -93,16 +109,27 @@ public final class Mp3Extractor implements Extractor {
* Constructs a new {@link Mp3Extractor}. * Constructs a new {@link Mp3Extractor}.
*/ */
public Mp3Extractor() { public Mp3Extractor() {
this(C.TIME_UNSET); this(0);
} }
/** /**
* Constructs a new {@link Mp3Extractor}. * 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 * @param forcedFirstSampleTimestampUs A timestamp to force for the first sample, or
* {@link C#TIME_UNSET} if forcing is not required. * {@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; this.forcedFirstSampleTimestampUs = forcedFirstSampleTimestampUs;
scratch = new ParsableByteArray(SCRATCH_LENGTH); scratch = new ParsableByteArray(SCRATCH_LENGTH);
synchronizedHeader = new MpegAudioHeader(); synchronizedHeader = new MpegAudioHeader();
@ -118,7 +145,7 @@ public final class Mp3Extractor implements Extractor {
@Override @Override
public void init(ExtractorOutput output) { public void init(ExtractorOutput output) {
extractorOutput = output; extractorOutput = output;
trackOutput = extractorOutput.track(0); trackOutput = extractorOutput.track(0, C.TRACK_TYPE_AUDIO);
extractorOutput.endTracks(); 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 // Repopulate the synchronized header in case we had to skip an invalid seeking header, which
// would give an invalid CBR bitrate. // would give an invalid CBR bitrate.
input.resetPeekPosition(); input.resetPeekPosition();

View File

@ -135,6 +135,7 @@ import java.util.List;
public static final int TYPE_vp09 = Util.getIntegerCodeForString("vp09"); public static final int TYPE_vp09 = Util.getIntegerCodeForString("vp09");
public static final int TYPE_vpcC = Util.getIntegerCodeForString("vpcC"); public static final int TYPE_vpcC = Util.getIntegerCodeForString("vpcC");
public static final int TYPE_camm = Util.getIntegerCodeForString("camm"); public static final int TYPE_camm = Util.getIntegerCodeForString("camm");
public static final int TYPE_alac = Util.getIntegerCodeForString("alac");
public final int type; public final int type;

View File

@ -332,6 +332,9 @@ import java.util.List;
return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags); 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. // Count the number of samples after applying edits.
int editedSampleCount = 0; int editedSampleCount = 0;
int nextSampleIndex = 0; int nextSampleIndex = 0;
@ -342,7 +345,8 @@ import java.util.List;
long duration = Util.scaleLargeTimestamp(track.editListDurations[i], track.timescale, long duration = Util.scaleLargeTimestamp(track.editListDurations[i], track.timescale,
track.movieTimescale); track.movieTimescale);
int startIndex = Util.binarySearchCeil(timestamps, mediaTime, true, true); 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; editedSampleCount += endIndex - startIndex;
copyMetadata |= nextSampleIndex != startIndex; copyMetadata |= nextSampleIndex != startIndex;
nextSampleIndex = endIndex; nextSampleIndex = endIndex;
@ -365,7 +369,7 @@ import java.util.List;
long endMediaTime = mediaTime + Util.scaleLargeTimestamp(duration, track.timescale, long endMediaTime = mediaTime + Util.scaleLargeTimestamp(duration, track.timescale,
track.movieTimescale); track.movieTimescale);
int startIndex = Util.binarySearchCeil(timestamps, mediaTime, true, true); 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) { if (copyMetadata) {
int count = endIndex - startIndex; int count = endIndex - startIndex;
System.arraycopy(offsets, startIndex, editedOffsets, sampleIndex, count); 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_dtsh || childAtomType == Atom.TYPE_dtsl
|| childAtomType == Atom.TYPE_samr || childAtomType == Atom.TYPE_sawb || childAtomType == Atom.TYPE_samr || childAtomType == Atom.TYPE_sawb
|| childAtomType == Atom.TYPE_lpcm || childAtomType == Atom.TYPE_sowt || 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, parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId,
language, isQuickTime, drmInitData, out, i); language, isQuickTime, drmInitData, out, i);
} else if (childAtomType == Atom.TYPE_TTML) { } else if (childAtomType == Atom.TYPE_TTML) {
@ -716,6 +720,9 @@ import java.util.List;
case 2: case 2:
stereoMode = C.STEREO_MODE_LEFT_RIGHT; stereoMode = C.STEREO_MODE_LEFT_RIGHT;
break; break;
case 3:
stereoMode = C.STEREO_MODE_STEREO_MESH;
break;
default: default:
break; break;
} }
@ -839,6 +846,8 @@ import java.util.List;
mimeType = MimeTypes.AUDIO_RAW; mimeType = MimeTypes.AUDIO_RAW;
} else if (atomType == Atom.TYPE__mp3) { } else if (atomType == Atom.TYPE__mp3) {
mimeType = MimeTypes.AUDIO_MPEG; mimeType = MimeTypes.AUDIO_MPEG;
} else if (atomType == Atom.TYPE_alac) {
mimeType = MimeTypes.AUDIO_ALAC;
} }
byte[] initializationData = null; byte[] initializationData = null;
@ -876,6 +885,10 @@ import java.util.List;
out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null, out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null,
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, null, drmInitData, 0, Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, null, drmInitData, 0,
language); language);
} else if (childAtomType == Atom.TYPE_alac) {
initializationData = new byte[childAtomSize];
parent.setPosition(childPosition);
parent.readBytes(initializationData, 0, childAtomSize);
} }
childPosition += childAtomSize; childPosition += childAtomSize;
} }

View File

@ -31,14 +31,15 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom; import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom;
import com.google.android.exoplayer2.extractor.mp4.Atom.LeafAtom; import com.google.android.exoplayer2.extractor.mp4.Atom.LeafAtom;
import com.google.android.exoplayer2.text.cea.CeaUtil;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@ -67,15 +68,13 @@ public final class FragmentedMp4Extractor implements Extractor {
}; };
private static final String TAG = "FragmentedMp4Extractor";
private static final int SAMPLE_GROUP_TYPE_seig = Util.getIntegerCodeForString("seig");
/** /**
* Flags controlling the behavior of the extractor. * Flags controlling the behavior of the extractor.
*/ */
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME, @IntDef(flag = true, value = {FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME,
FLAG_WORKAROUND_IGNORE_TFDT_BOX, FLAG_ENABLE_EMSG_TRACK, FLAG_SIDELOADED}) FLAG_WORKAROUND_IGNORE_TFDT_BOX, FLAG_ENABLE_EMSG_TRACK, FLAG_ENABLE_CEA608_TRACK,
FLAG_SIDELOADED})
public @interface Flags {} public @interface Flags {}
/** /**
* Flag to work around an issue in some video streams where every frame is marked as a sync frame. * Flag to work around an issue in some video streams where every frame is marked as a sync frame.
@ -94,12 +93,19 @@ public final class FragmentedMp4Extractor implements Extractor {
* messages in the stream will be delivered as samples to this track. * messages in the stream will be delivered as samples to this track.
*/ */
public static final int FLAG_ENABLE_EMSG_TRACK = 4; public static final int FLAG_ENABLE_EMSG_TRACK = 4;
/**
* Flag to indicate that the extractor should output a CEA-608 text track. Any CEA-608 messages
* contained within SEI NAL units in the stream will be delivered as samples to this track.
*/
public static final int FLAG_ENABLE_CEA608_TRACK = 8;
/** /**
* Flag to indicate that the {@link Track} was sideloaded, instead of being declared by the MP4 * Flag to indicate that the {@link Track} was sideloaded, instead of being declared by the MP4
* container. * container.
*/ */
private static final int FLAG_SIDELOADED = 8; private static final int FLAG_SIDELOADED = 16;
private static final String TAG = "FragmentedMp4Extractor";
private static final int SAMPLE_GROUP_TYPE_seig = Util.getIntegerCodeForString("seig");
private static final byte[] PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE = private static final byte[] PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE =
new byte[] {-94, 57, 79, 82, 90, -101, 79, 20, -94, 68, 108, 66, 124, 100, -115, -12}; new byte[] {-94, 57, 79, 82, 90, -101, 79, 20, -94, 68, 108, 66, 124, 100, -115, -12};
@ -120,7 +126,8 @@ public final class FragmentedMp4Extractor implements Extractor {
// Temporary arrays. // Temporary arrays.
private final ParsableByteArray nalStartCode; private final ParsableByteArray nalStartCode;
private final ParsableByteArray nalLength; private final ParsableByteArray nalPrefix;
private final ParsableByteArray nalBuffer;
private final ParsableByteArray encryptionSignalByte; private final ParsableByteArray encryptionSignalByte;
// Adjusts sample timestamps. // Adjusts sample timestamps.
@ -146,16 +153,25 @@ public final class FragmentedMp4Extractor implements Extractor {
private int sampleSize; private int sampleSize;
private int sampleBytesWritten; private int sampleBytesWritten;
private int sampleCurrentNalBytesRemaining; private int sampleCurrentNalBytesRemaining;
private boolean processSeiNalUnitPayload;
// Extractor output. // Extractor output.
private ExtractorOutput extractorOutput; private ExtractorOutput extractorOutput;
private TrackOutput eventMessageTrackOutput; private TrackOutput eventMessageTrackOutput;
private TrackOutput cea608TrackOutput;
// Whether extractorOutput.seekMap has been called. // Whether extractorOutput.seekMap has been called.
private boolean haveOutputSeekMap; private boolean haveOutputSeekMap;
public FragmentedMp4Extractor() { public FragmentedMp4Extractor() {
this(0, null); this(0);
}
/**
* @param flags Flags that control the extractor's behavior.
*/
public FragmentedMp4Extractor(@Flags int flags) {
this(flags, null);
} }
/** /**
@ -163,23 +179,24 @@ public final class FragmentedMp4Extractor implements Extractor {
* @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed. * @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
*/ */
public FragmentedMp4Extractor(@Flags int flags, TimestampAdjuster timestampAdjuster) { public FragmentedMp4Extractor(@Flags int flags, TimestampAdjuster timestampAdjuster) {
this(flags, null, timestampAdjuster); this(flags, timestampAdjuster, null);
} }
/** /**
* @param flags Flags that control the extractor's behavior. * @param flags Flags that control the extractor's behavior.
* @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
* @param sideloadedTrack Sideloaded track information, in the case that the extractor * @param sideloadedTrack Sideloaded track information, in the case that the extractor
* will not receive a moov box in the input data. * will not receive a moov box in the input data.
* @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
*/ */
public FragmentedMp4Extractor(@Flags int flags, Track sideloadedTrack, public FragmentedMp4Extractor(@Flags int flags, TimestampAdjuster timestampAdjuster,
TimestampAdjuster timestampAdjuster) { Track sideloadedTrack) {
this.sideloadedTrack = sideloadedTrack;
this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0); this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0);
this.timestampAdjuster = timestampAdjuster; this.timestampAdjuster = timestampAdjuster;
this.sideloadedTrack = sideloadedTrack;
atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE); atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE);
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
nalLength = new ParsableByteArray(4); nalPrefix = new ParsableByteArray(5);
nalBuffer = new ParsableByteArray();
encryptionSignalByte = new ParsableByteArray(1); encryptionSignalByte = new ParsableByteArray(1);
extendedTypeScratch = new byte[16]; extendedTypeScratch = new byte[16];
containerAtoms = new Stack<>(); containerAtoms = new Stack<>();
@ -199,10 +216,10 @@ public final class FragmentedMp4Extractor implements Extractor {
public void init(ExtractorOutput output) { public void init(ExtractorOutput output) {
extractorOutput = output; extractorOutput = output;
if (sideloadedTrack != null) { if (sideloadedTrack != null) {
TrackBundle bundle = new TrackBundle(output.track(0)); TrackBundle bundle = new TrackBundle(output.track(0, sideloadedTrack.type));
bundle.init(sideloadedTrack, new DefaultSampleValues(0, 0, 0, 0)); bundle.init(sideloadedTrack, new DefaultSampleValues(0, 0, 0, 0));
trackBundles.put(0, bundle); trackBundles.put(0, bundle);
maybeInitEventMessageTrack(); maybeInitExtraTracks();
extractorOutput.endTracks(); extractorOutput.endTracks();
} }
} }
@ -410,19 +427,19 @@ public final class FragmentedMp4Extractor implements Extractor {
// We need to create the track bundles. // We need to create the track bundles.
for (int i = 0; i < trackCount; i++) { for (int i = 0; i < trackCount; i++) {
Track track = tracks.valueAt(i); Track track = tracks.valueAt(i);
trackBundles.put(track.id, new TrackBundle(extractorOutput.track(i))); TrackBundle trackBundle = new TrackBundle(extractorOutput.track(i, track.type));
trackBundle.init(track, defaultSampleValuesArray.get(track.id));
trackBundles.put(track.id, trackBundle);
durationUs = Math.max(durationUs, track.durationUs); durationUs = Math.max(durationUs, track.durationUs);
} }
maybeInitEventMessageTrack(); maybeInitExtraTracks();
extractorOutput.endTracks(); extractorOutput.endTracks();
} else { } else {
Assertions.checkState(trackBundles.size() == trackCount); Assertions.checkState(trackBundles.size() == trackCount);
} for (int i = 0; i < trackCount; i++) {
Track track = tracks.valueAt(i);
// Initialization of tracks and default sample values. trackBundles.get(track.id).init(track, defaultSampleValuesArray.get(track.id));
for (int i = 0; i < trackCount; i++) { }
Track track = tracks.valueAt(i);
trackBundles.get(track.id).init(track, defaultSampleValuesArray.get(track.id));
} }
} }
@ -437,13 +454,17 @@ public final class FragmentedMp4Extractor implements Extractor {
} }
} }
private void maybeInitEventMessageTrack() { private void maybeInitExtraTracks() {
if ((flags & FLAG_ENABLE_EMSG_TRACK) == 0) { if ((flags & FLAG_ENABLE_EMSG_TRACK) != 0 && eventMessageTrackOutput == null) {
return; eventMessageTrackOutput = extractorOutput.track(trackBundles.size(), C.TRACK_TYPE_METADATA);
eventMessageTrackOutput.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_EMSG,
Format.OFFSET_SAMPLE_RELATIVE));
}
if ((flags & FLAG_ENABLE_CEA608_TRACK) != 0 && cea608TrackOutput == null) {
cea608TrackOutput = extractorOutput.track(trackBundles.size() + 1, C.TRACK_TYPE_TEXT);
cea608TrackOutput.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608,
null, Format.NO_VALUE, 0, null, null));
} }
eventMessageTrackOutput = extractorOutput.track(trackBundles.size());
eventMessageTrackOutput.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_EMSG,
Format.OFFSET_SAMPLE_RELATIVE));
} }
/** /**
@ -1045,29 +1066,50 @@ public final class FragmentedMp4Extractor implements Extractor {
if (track.nalUnitLengthFieldLength != 0) { if (track.nalUnitLengthFieldLength != 0) {
// Zero the top three bytes of the array that we'll use to decode nal unit lengths, in case // Zero the top three bytes of the array that we'll use to decode nal unit lengths, in case
// they're only 1 or 2 bytes long. // they're only 1 or 2 bytes long.
byte[] nalLengthData = nalLength.data; byte[] nalPrefixData = nalPrefix.data;
nalLengthData[0] = 0; nalPrefixData[0] = 0;
nalLengthData[1] = 0; nalPrefixData[1] = 0;
nalLengthData[2] = 0; nalPrefixData[2] = 0;
int nalUnitLengthFieldLength = track.nalUnitLengthFieldLength; int nalUnitPrefixLength = track.nalUnitLengthFieldLength + 1;
int nalUnitLengthFieldLengthDiff = 4 - track.nalUnitLengthFieldLength; int nalUnitLengthFieldLengthDiff = 4 - track.nalUnitLengthFieldLength;
// NAL units are length delimited, but the decoder requires start code delimited units. // NAL units are length delimited, but the decoder requires start code delimited units.
// Loop until we've written the sample to the track output, replacing length delimiters with // Loop until we've written the sample to the track output, replacing length delimiters with
// start codes as we encounter them. // start codes as we encounter them.
while (sampleBytesWritten < sampleSize) { while (sampleBytesWritten < sampleSize) {
if (sampleCurrentNalBytesRemaining == 0) { if (sampleCurrentNalBytesRemaining == 0) {
// Read the NAL length so that we know where we find the next one. // Read the NAL length so that we know where we find the next one, and its type.
input.readFully(nalLength.data, nalUnitLengthFieldLengthDiff, nalUnitLengthFieldLength); input.readFully(nalPrefixData, nalUnitLengthFieldLengthDiff, nalUnitPrefixLength);
nalLength.setPosition(0); nalPrefix.setPosition(0);
sampleCurrentNalBytesRemaining = nalLength.readUnsignedIntToInt(); sampleCurrentNalBytesRemaining = nalPrefix.readUnsignedIntToInt() - 1;
// Write a start code for the current NAL unit. // Write a start code for the current NAL unit.
nalStartCode.setPosition(0); nalStartCode.setPosition(0);
output.sampleData(nalStartCode, 4); output.sampleData(nalStartCode, 4);
sampleBytesWritten += 4; // Write the NAL unit type byte.
output.sampleData(nalPrefix, 1);
// TODO: Don't try and process the SEI NAL unit if the payload is encrypted.
processSeiNalUnitPayload = cea608TrackOutput != null
&& NalUnitUtil.isNalUnitSei(track.format.sampleMimeType, nalPrefixData[4]);
sampleBytesWritten += 5;
sampleSize += nalUnitLengthFieldLengthDiff; sampleSize += nalUnitLengthFieldLengthDiff;
} else { } else {
// Write the payload of the NAL unit. int writtenBytes;
int writtenBytes = output.sampleData(input, sampleCurrentNalBytesRemaining, false); if (processSeiNalUnitPayload) {
// Read and write the payload of the SEI NAL unit.
nalBuffer.reset(sampleCurrentNalBytesRemaining);
input.readFully(nalBuffer.data, 0, sampleCurrentNalBytesRemaining);
output.sampleData(nalBuffer, sampleCurrentNalBytesRemaining);
writtenBytes = sampleCurrentNalBytesRemaining;
// Unescape and process the SEI NAL unit.
int unescapedLength = NalUnitUtil.unescapeStream(nalBuffer.data, nalBuffer.limit());
// If the format is H.265/HEVC the NAL unit header has two bytes so skip one more byte.
nalBuffer.setPosition(MimeTypes.VIDEO_H265.equals(track.format.sampleMimeType) ? 1 : 0);
nalBuffer.setLimit(unescapedLength);
CeaUtil.consume(fragment.getSamplePresentationTime(sampleIndex) * 1000L, nalBuffer,
cea608TrackOutput);
} else {
// Write the payload of the NAL unit.
writtenBytes = output.sampleData(input, sampleCurrentNalBytesRemaining, false);
}
sampleBytesWritten += writtenBytes; sampleBytesWritten += writtenBytes;
sampleCurrentNalBytesRemaining -= writtenBytes; sampleCurrentNalBytesRemaining -= writtenBytes;
} }

View File

@ -344,7 +344,8 @@ public final class Mp4Extractor implements Extractor, SeekMap {
continue; 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. // 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. // Allow ten source samples per output sample, like the platform extractor.
int maxInputSize = trackSampleTable.maximumSize + 3 * 10; int maxInputSize = trackSampleTable.maximumSize + 3 * 10;

View File

@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.extractor.ogg; package com.google.android.exoplayer2.extractor.ogg;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorInput;
@ -75,7 +76,7 @@ public class OggExtractor implements Extractor {
@Override @Override
public void init(ExtractorOutput output) { public void init(ExtractorOutput output) {
TrackOutput trackOutput = output.track(0); TrackOutput trackOutput = output.track(0, C.TRACK_TYPE_AUDIO);
output.endTracks(); output.endTracks();
// TODO: fix the case if sniff() isn't called // TODO: fix the case if sniff() isn't called
streamReader.init(output, trackOutput); streamReader.init(output, trackOutput);

View File

@ -65,7 +65,7 @@ public final class RawCcExtractor implements Extractor {
@Override @Override
public void init(ExtractorOutput output) { public void init(ExtractorOutput output) {
output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET));
trackOutput = output.track(0); trackOutput = output.track(0, C.TRACK_TYPE_TEXT);
output.endTracks(); output.endTracks();
trackOutput.format(format); trackOutput.format(format);
} }

View File

@ -39,6 +39,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
private final ParsableByteArray headerScratchBytes; private final ParsableByteArray headerScratchBytes;
private final String language; private final String language;
private String trackFormatId;
private TrackOutput output; private TrackOutput output;
private int state; private int state;
@ -84,7 +85,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
@Override @Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator generator) { 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 @Override
@ -180,8 +183,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
headerScratchBits.skipBits(40); headerScratchBits.skipBits(40);
isEac3 = headerScratchBits.readBits(5) == 16; isEac3 = headerScratchBits.readBits(5) == 16;
headerScratchBits.setPosition(headerScratchBits.getPosition() - 45); headerScratchBits.setPosition(headerScratchBits.getPosition() - 45);
format = isEac3 ? Ac3Util.parseEac3SyncframeFormat(headerScratchBits, null, language , null) format = isEac3
: Ac3Util.parseAc3SyncframeFormat(headerScratchBits, null, language, null); ? Ac3Util.parseEac3SyncframeFormat(headerScratchBits, trackFormatId, language , null)
: Ac3Util.parseAc3SyncframeFormat(headerScratchBits, trackFormatId, language, null);
output.format(format); output.format(format);
} }
sampleSize = isEac3 ? Ac3Util.parseEAc3SyncframeSize(headerScratchBits.data) sampleSize = isEac3 ? Ac3Util.parseEAc3SyncframeSize(headerScratchBits.data)

View File

@ -61,6 +61,7 @@ import java.util.Collections;
private final ParsableByteArray id3HeaderBuffer; private final ParsableByteArray id3HeaderBuffer;
private final String language; private final String language;
private String formatId;
private TrackOutput output; private TrackOutput output;
private TrackOutput id3Output; private TrackOutput id3Output;
@ -108,11 +109,14 @@ import java.util.Collections;
@Override @Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { 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) { if (exposeId3) {
id3Output = extractorOutput.track(idGenerator.getNextId()); idGenerator.generateNewId();
id3Output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, null, id3Output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_METADATA);
Format.NO_VALUE, null)); id3Output.format(Format.createSampleFormat(idGenerator.getFormatId(),
MimeTypes.APPLICATION_ID3, null, Format.NO_VALUE, null));
} else { } else {
id3Output = new DummyTrackOutput(); id3Output = new DummyTrackOutput();
} }
@ -300,7 +304,7 @@ import java.util.Collections;
Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig( Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(
audioSpecificConfig); 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, Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first,
Collections.singletonList(audioSpecificConfig), null, 0, language); Collections.singletonList(audioSpecificConfig), null, 0, language);
// In this class a sample is an access unit, but the MediaFormat sample rate specifies the // In this class a sample is an access unit, but the MediaFormat sample rate specifies the

View File

@ -74,10 +74,11 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
case TsExtractor.TS_STREAM_TYPE_H262: case TsExtractor.TS_STREAM_TYPE_H262:
return new PesReader(new H262Reader()); return new PesReader(new H262Reader());
case TsExtractor.TS_STREAM_TYPE_H264: case TsExtractor.TS_STREAM_TYPE_H264:
return isSet(FLAG_IGNORE_H264_STREAM) ? null : new PesReader( return isSet(FLAG_IGNORE_H264_STREAM) ? null
new H264Reader(isSet(FLAG_ALLOW_NON_IDR_KEYFRAMES), isSet(FLAG_DETECT_ACCESS_UNITS))); : new PesReader(new H264Reader(new SeiReader(), isSet(FLAG_ALLOW_NON_IDR_KEYFRAMES),
isSet(FLAG_DETECT_ACCESS_UNITS)));
case TsExtractor.TS_STREAM_TYPE_H265: 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: case TsExtractor.TS_STREAM_TYPE_SPLICE_INFO:
return isSet(FLAG_IGNORE_SPLICE_INFO_STREAM) return isSet(FLAG_IGNORE_SPLICE_INFO_STREAM)
? null : new SectionReader(new SpliceInfoSectionReader()); ? null : new SectionReader(new SpliceInfoSectionReader());

View File

@ -39,6 +39,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
private final ParsableByteArray headerScratchBytes; private final ParsableByteArray headerScratchBytes;
private final String language; private final String language;
private String formatId;
private TrackOutput output; private TrackOutput output;
private int state; private int state;
@ -79,7 +80,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
@Override @Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { 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 @Override
@ -165,7 +168,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
private void parseHeader() { private void parseHeader() {
byte[] frameData = headerScratchBytes.data; byte[] frameData = headerScratchBytes.data;
if (format == null) { if (format == null) {
format = DtsUtil.parseDtsFormat(frameData, null, language, null); format = DtsUtil.parseDtsFormat(frameData, formatId, language, null);
output.format(format); output.format(format);
} }
sampleSize = DtsUtil.getDtsFrameSize(frameData); sampleSize = DtsUtil.getDtsFrameSize(frameData);

View File

@ -37,6 +37,7 @@ import java.util.Collections;
private static final int START_EXTENSION = 0xB5; private static final int START_EXTENSION = 0xB5;
private static final int START_GROUP = 0xB8; private static final int START_GROUP = 0xB8;
private String formatId;
private TrackOutput output; private TrackOutput output;
// Maps (frame_rate_code - 1) indices to values, as defined in ITU-T H.262 Table 6-4. // Maps (frame_rate_code - 1) indices to values, as defined in ITU-T H.262 Table 6-4.
@ -78,7 +79,9 @@ import java.util.Collections;
@Override @Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { 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 @Override
@ -126,7 +129,7 @@ import java.util.Collections;
int bytesAlreadyPassed = lengthToStartCode < 0 ? -lengthToStartCode : 0; int bytesAlreadyPassed = lengthToStartCode < 0 ? -lengthToStartCode : 0;
if (csdBuffer.onStartCode(startCodeValue, bytesAlreadyPassed)) { if (csdBuffer.onStartCode(startCodeValue, bytesAlreadyPassed)) {
// The csd data is complete, so we can decode and output the media format. // 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); output.format(result.first);
frameDurationUs = result.second; frameDurationUs = result.second;
hasOutputFormat = true; hasOutputFormat = true;
@ -166,10 +169,11 @@ import java.util.Collections;
* Parses the {@link Format} and frame duration from a csd buffer. * Parses the {@link Format} and frame duration from a csd buffer.
* *
* @param csdBuffer The 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 * @return A pair consisting of the {@link Format} and the frame duration in microseconds, or
* 0 if the duration could not be determined. * 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); byte[] csdData = Arrays.copyOf(csdBuffer.data, csdBuffer.length);
int firstByte = csdData[4] & 0xFF; int firstByte = csdData[4] & 0xFF;
@ -195,7 +199,7 @@ import java.util.Collections;
break; 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, Format.NO_VALUE, Format.NO_VALUE, width, height, Format.NO_VALUE,
Collections.singletonList(csdData), Format.NO_VALUE, pixelWidthHeightRatio, null); Collections.singletonList(csdData), Format.NO_VALUE, pixelWidthHeightRatio, null);

View File

@ -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_SPS = 7; // Sequence parameter set
private static final int NAL_UNIT_TYPE_PPS = 8; // Picture 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 allowNonIdrKeyframes;
private final boolean detectAccessUnits; private final boolean detectAccessUnits;
private final NalUnitTargetBuffer sps; private final NalUnitTargetBuffer sps;
@ -47,8 +48,8 @@ import java.util.List;
private long totalBytesWritten; private long totalBytesWritten;
private final boolean[] prefixFlags; private final boolean[] prefixFlags;
private String formatId;
private TrackOutput output; private TrackOutput output;
private SeiReader seiReader;
private SampleReader sampleReader; private SampleReader sampleReader;
// State that should not be reset on seek. // State that should not be reset on seek.
@ -61,15 +62,17 @@ import java.util.List;
private final ParsableByteArray seiWrapper; 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 * @param allowNonIdrKeyframes Whether to treat samples consisting of non-IDR I slices as
* synchronization samples (key-frames). * synchronization samples (key-frames).
* @param detectAccessUnits Whether to split the input stream into access units (samples) based on * @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). * slice headers. Pass {@code false} if the stream contains access unit delimiters (AUDs).
*/ */
public H264Reader(boolean allowNonIdrKeyframes, boolean detectAccessUnits) { public H264Reader(SeiReader seiReader, boolean allowNonIdrKeyframes, boolean detectAccessUnits) {
prefixFlags = new boolean[3]; this.seiReader = seiReader;
this.allowNonIdrKeyframes = allowNonIdrKeyframes; this.allowNonIdrKeyframes = allowNonIdrKeyframes;
this.detectAccessUnits = detectAccessUnits; this.detectAccessUnits = detectAccessUnits;
prefixFlags = new boolean[3];
sps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SPS, 128); sps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SPS, 128);
pps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_PPS, 128); pps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_PPS, 128);
sei = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SEI, 128); sei = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SEI, 128);
@ -88,9 +91,11 @@ import java.util.List;
@Override @Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { 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); sampleReader = new SampleReader(output, allowNonIdrKeyframes, detectAccessUnits);
seiReader = new SeiReader(extractorOutput.track(idGenerator.getNextId())); seiReader.createTracks(extractorOutput, idGenerator);
} }
@Override @Override
@ -175,7 +180,7 @@ import java.util.List;
initializationData.add(Arrays.copyOf(pps.nalData, pps.nalLength)); initializationData.add(Arrays.copyOf(pps.nalData, pps.nalLength));
NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(sps.nalData, 3, sps.nalLength); NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(sps.nalData, 3, sps.nalLength);
NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(pps.nalData, 3, pps.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, Format.NO_VALUE, Format.NO_VALUE, spsData.width, spsData.height, Format.NO_VALUE,
initializationData, Format.NO_VALUE, spsData.pixelWidthAspectRatio, null)); initializationData, Format.NO_VALUE, spsData.pixelWidthAspectRatio, null));
hasOutputFormat = true; hasOutputFormat = true;

View File

@ -44,9 +44,11 @@ import java.util.Collections;
private static final int PREFIX_SEI_NUT = 39; private static final int PREFIX_SEI_NUT = 39;
private static final int SUFFIX_SEI_NUT = 40; private static final int SUFFIX_SEI_NUT = 40;
private final SeiReader seiReader;
private String formatId;
private TrackOutput output; private TrackOutput output;
private SampleReader sampleReader; private SampleReader sampleReader;
private SeiReader seiReader;
// State that should not be reset on seek. // State that should not be reset on seek.
private boolean hasOutputFormat; private boolean hasOutputFormat;
@ -66,7 +68,11 @@ import java.util.Collections;
// Scratch variables to avoid allocations. // Scratch variables to avoid allocations.
private final ParsableByteArray seiWrapper; 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]; prefixFlags = new boolean[3];
vps = new NalUnitTargetBuffer(VPS_NUT, 128); vps = new NalUnitTargetBuffer(VPS_NUT, 128);
sps = new NalUnitTargetBuffer(SPS_NUT, 128); sps = new NalUnitTargetBuffer(SPS_NUT, 128);
@ -90,9 +96,11 @@ import java.util.Collections;
@Override @Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { 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); sampleReader = new SampleReader(output);
seiReader = new SeiReader(extractorOutput.track(idGenerator.getNextId())); seiReader.createTracks(extractorOutput, idGenerator);
} }
@Override @Override
@ -183,7 +191,7 @@ import java.util.Collections;
sps.endNalUnit(discardPadding); sps.endNalUnit(discardPadding);
pps.endNalUnit(discardPadding); pps.endNalUnit(discardPadding);
if (vps.isCompleted() && sps.isCompleted() && pps.isCompleted()) { if (vps.isCompleted() && sps.isCompleted() && pps.isCompleted()) {
output.format(parseMediaFormat(vps, sps, pps)); output.format(parseMediaFormat(formatId, vps, sps, pps));
hasOutputFormat = true; hasOutputFormat = true;
} }
} }
@ -205,8 +213,8 @@ import java.util.Collections;
} }
} }
private static Format parseMediaFormat(NalUnitTargetBuffer vps, NalUnitTargetBuffer sps, private static Format parseMediaFormat(String formatId, NalUnitTargetBuffer vps,
NalUnitTargetBuffer pps) { NalUnitTargetBuffer sps, NalUnitTargetBuffer pps) {
// Build codec-specific data. // Build codec-specific data.
byte[] csd = new byte[vps.nalLength + sps.nalLength + pps.nalLength]; byte[] csd = new byte[vps.nalLength + sps.nalLength + pps.nalLength];
System.arraycopy(vps.nalData, 0, csd, 0, vps.nalLength); System.arraycopy(vps.nalData, 0, csd, 0, vps.nalLength);
@ -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, Format.NO_VALUE, picWidthInLumaSamples, picHeightInLumaSamples, Format.NO_VALUE,
Collections.singletonList(csd), Format.NO_VALUE, pixelWidthHeightRatio, null); Collections.singletonList(csd), Format.NO_VALUE, pixelWidthHeightRatio, null);
} }

View File

@ -56,9 +56,10 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
@Override @Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId()); idGenerator.generateNewId();
output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, null, Format.NO_VALUE, output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_METADATA);
null)); output.format(Format.createSampleFormat(idGenerator.getFormatId(), MimeTypes.APPLICATION_ID3,
null, Format.NO_VALUE, null));
} }
@Override @Override

View File

@ -38,6 +38,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
private final MpegAudioHeader header; private final MpegAudioHeader header;
private final String language; private final String language;
private String formatId;
private TrackOutput output; private TrackOutput output;
private int state; private int state;
@ -76,7 +77,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
@Override @Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { 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 @Override
@ -176,9 +179,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
frameSize = header.frameSize; frameSize = header.frameSize;
if (!hasOutputFormat) { if (!hasOutputFormat) {
frameDurationUs = (C.MICROS_PER_SECOND * header.samplesPerFrame) / header.sampleRate; frameDurationUs = (C.MICROS_PER_SECOND * header.samplesPerFrame) / header.sampleRate;
Format format = Format.createAudioSampleFormat(null, header.mimeType, null, Format.NO_VALUE, Format format = Format.createAudioSampleFormat(formatId, header.mimeType, null,
MpegAudioHeader.MAX_FRAME_SIZE_BYTES, header.channels, header.sampleRate, null, null, 0, Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, header.channels, header.sampleRate,
language); null, null, 0, language);
output.format(format); output.format(format);
hasOutputFormat = true; hasOutputFormat = true;
} }

View File

@ -16,12 +16,11 @@
package com.google.android.exoplayer2.extractor.ts; package com.google.android.exoplayer2.extractor.ts;
import android.util.Log; import android.util.Log;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.ExtractorOutput; 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.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
/** /**
* Parses PES packet data and extracts samples. * Parses PES packet data and extracts samples.

View File

@ -23,10 +23,10 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.io.IOException; import java.io.IOException;
/** /**

View File

@ -16,10 +16,10 @@
package com.google.android.exoplayer2.extractor.ts; package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.extractor.ExtractorOutput; 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.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
/** /**
* Reads section data. * Reads section data.

View File

@ -17,8 +17,8 @@ package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.ExtractorOutput; 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.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
/** /**

View File

@ -17,8 +17,10 @@ package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput; 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.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
@ -27,49 +29,17 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
*/ */
/* package */ final class SeiReader { /* package */ final class SeiReader {
private final TrackOutput output; private TrackOutput output;
public SeiReader(TrackOutput output) { public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
this.output = output; idGenerator.generateNewId();
output.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, null, output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT);
Format.NO_VALUE, 0, null, null)); output.format(Format.createTextSampleFormat(idGenerator.getFormatId(),
MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, null, null));
} }
public void consume(long pesTimeUs, ParsableByteArray seiBuffer) { public void consume(long pesTimeUs, ParsableByteArray seiBuffer) {
int b; CeaUtil.consume(pesTimeUs, seiBuffer, output);
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);
}
}
} }
} }

View File

@ -18,10 +18,10 @@ package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
/** /**
* Parses splice info sections as defined by SCTE35. * 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, public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput,
TsPayloadReader.TrackIdGenerator idGenerator) { TsPayloadReader.TrackIdGenerator idGenerator) {
this.timestampAdjuster = timestampAdjuster; this.timestampAdjuster = timestampAdjuster;
output = extractorOutput.track(idGenerator.getNextId()); idGenerator.generateNewId();
output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_SCTE35, null, output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_METADATA);
Format.NO_VALUE, null)); output.format(Format.createSampleFormat(idGenerator.getFormatId(), MimeTypes.APPLICATION_SCTE35,
null, Format.NO_VALUE, null));
} }
@Override @Override

View File

@ -25,16 +25,19 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/** /**
* Facilitates the extraction of data from the MPEG-2 TS container format. * 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 static final int BUFFER_SIZE = TS_PACKET_SIZE * BUFFER_PACKET_COUNT;
private final boolean hlsMode; private final boolean hlsMode;
private final TimestampAdjuster timestampAdjuster; private final List<TimestampAdjuster> timestampAdjusters;
private final ParsableByteArray tsPacketBuffer; private final ParsableByteArray tsPacketBuffer;
private final ParsableBitArray tsScratch; private final ParsableBitArray tsScratch;
private final SparseIntArray continuityCounters; private final SparseIntArray continuityCounters;
@ -89,18 +92,12 @@ public final class TsExtractor implements Extractor {
// Accessed only by the loading thread. // Accessed only by the loading thread.
private ExtractorOutput output; private ExtractorOutput output;
private int remainingPmts;
private boolean tracksEnded; private boolean tracksEnded;
private TsPayloadReader id3Reader; private TsPayloadReader id3Reader;
public TsExtractor() { public TsExtractor() {
this(new TimestampAdjuster(0)); this(new TimestampAdjuster(0), new DefaultTsPayloadReaderFactory(), false);
}
/**
* @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps.
*/
public TsExtractor(TimestampAdjuster timestampAdjuster) {
this(timestampAdjuster, new DefaultTsPayloadReaderFactory(), false);
} }
/** /**
@ -111,7 +108,12 @@ public final class TsExtractor implements Extractor {
*/ */
public TsExtractor(TimestampAdjuster timestampAdjuster, public TsExtractor(TimestampAdjuster timestampAdjuster,
TsPayloadReader.Factory payloadReaderFactory, boolean hlsMode) { 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.payloadReaderFactory = Assertions.checkNotNull(payloadReaderFactory);
this.hlsMode = hlsMode; this.hlsMode = hlsMode;
tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE); tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE);
@ -150,7 +152,10 @@ public final class TsExtractor implements Extractor {
@Override @Override
public void seek(long position, long timeUs) { 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(); tsPacketBuffer.reset();
continuityCounters.clear(); continuityCounters.clear();
// Elementary stream readers' state should be cleared to get consistent behaviours when seeking. // Elementary stream readers' state should be cleared to get consistent behaviours when seeking.
@ -307,8 +312,12 @@ public final class TsExtractor implements Extractor {
} else { } else {
int pid = patScratch.readBits(13); int pid = patScratch.readBits(13);
tsPayloadReaders.put(pid, new SectionReader(new PmtReader(pid))); 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. // See ISO/IEC 13818-1, section 2.4.4.4 for more information on table id assignment.
return; return;
} }
// section_syntax_indicator(1), '0'(1), reserved(2), section_length(12), program_number (16), // TimestampAdjuster assignment.
// reserved (2), version_number (5), current_next_indicator (1), // section_number (8), 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) // last_section_number (8), reserved (3), PCR_PID (13)
sectionData.skipBytes(9); sectionData.skipBytes(5);
// Read program_info_length. // Read program_info_length.
sectionData.readBytes(pmtScratch, 2); 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]); EsInfo dummyEsInfo = new EsInfo(TS_STREAM_TYPE_ID3, null, new byte[0]);
id3Reader = payloadReaderFactory.createPayloadReader(TS_STREAM_TYPE_ID3, dummyEsInfo); id3Reader = payloadReaderFactory.createPayloadReader(TS_STREAM_TYPE_ID3, dummyEsInfo);
id3Reader.init(timestampAdjuster, output, 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(); int remainingEntriesLength = sectionData.bytesLeft();
@ -393,7 +413,8 @@ public final class TsExtractor implements Extractor {
} else { } else {
reader = payloadReaderFactory.createPayloadReader(streamType, esInfo); reader = payloadReaderFactory.createPayloadReader(streamType, esInfo);
if (reader != null) { 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 (hlsMode) {
if (!tracksEnded) { if (!tracksEnded) {
output.endTracks(); output.endTracks();
remainingPmts = 0;
tracksEnded = true;
} }
} else { } else {
tsPayloadReaders.remove(TS_PAT_PID);
tsPayloadReaders.remove(pid); tsPayloadReaders.remove(pid);
output.endTracks(); remainingPmts--;
if (remainingPmts == 0) {
output.endTracks();
tracksEnded = true;
}
} }
tracksEnded = true;
} }
/** /**

View File

@ -17,9 +17,9 @@ package com.google.android.exoplayer2.extractor.ts;
import android.util.SparseArray; import android.util.SparseArray;
import com.google.android.exoplayer2.extractor.ExtractorOutput; 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.TrackOutput;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
/** /**
* Parses TS packet payload data. * Parses TS packet payload data.
@ -81,17 +81,63 @@ public interface TsPayloadReader {
*/ */
final class TrackIdGenerator { final class TrackIdGenerator {
private final int firstId; private static final int ID_UNSET = Integer.MIN_VALUE;
private final int idIncrement;
private int generatedIdCount;
public TrackIdGenerator(int firstId, int idIncrement) { private final String formatIdPrefix;
this.firstId = firstId; private final int firstTrackId;
this.idIncrement = idIncrement; private final int trackIdIncrement;
private int trackId;
private String formatId;
public TrackIdGenerator(int firstTrackId, int trackIdIncrement) {
this(ID_UNSET, firstTrackId, trackIdIncrement);
} }
public int getNextId() { public TrackIdGenerator(int programNumber, int firstTrackId, int trackIdIncrement) {
return firstId + idIncrement * generatedIdCount++; 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;
}
private void maybeThrowUninitializedError() {
if (trackId == ID_UNSET) {
throw new IllegalStateException("generateNewId() must be called before retrieving ids.");
}
} }
} }

View File

@ -60,7 +60,7 @@ public final class WavExtractor implements Extractor, SeekMap {
@Override @Override
public void init(ExtractorOutput output) { public void init(ExtractorOutput output) {
extractorOutput = output; extractorOutput = output;
trackOutput = output.track(0); trackOutput = output.track(0, C.TRACK_TYPE_AUDIO);
wavHeader = null; wavHeader = null;
output.endTracks(); output.endTracks();
} }

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.mediacodec; package com.google.android.exoplayer2.mediacodec;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.graphics.Point;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaCodecInfo.AudioCapabilities; import android.media.MediaCodecInfo.AudioCapabilities;
import android.media.MediaCodecInfo.CodecCapabilities; import android.media.MediaCodecInfo.CodecCapabilities;
@ -23,6 +24,7 @@ import android.media.MediaCodecInfo.CodecProfileLevel;
import android.media.MediaCodecInfo.VideoCapabilities; import android.media.MediaCodecInfo.VideoCapabilities;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
@ -141,39 +143,6 @@ public final class MediaCodecInfo {
return false; return false;
} }
/**
* Whether the decoder supports video with a specified width and height.
* <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.
*/
@TargetApi(21)
public boolean isVideoSizeSupportedV21(int width, int height) {
if (capabilities == null) {
logNoSupport("size.caps");
return false;
}
VideoCapabilities videoCapabilities = capabilities.getVideoCapabilities();
if (videoCapabilities == null) {
logNoSupport("size.vCaps");
return false;
}
if (!videoCapabilities.isSizeSupported(width, height)) {
// 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);
return false;
}
logAssumedSupport("size.rotated, " + width + "x" + height);
}
return true;
}
/** /**
* Whether the decoder supports video with a given width, height and frame rate. * Whether the decoder supports video with a given width, height and frame rate.
* <p> * <p>
@ -181,7 +150,8 @@ public final class MediaCodecInfo {
* *
* @param width Width in pixels. * @param width Width in pixels.
* @param height Height in pixels. * @param height Height in pixels.
* @param frameRate Frame rate in frames per second. * @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. * @return Whether the decoder supports video with the given width, height and frame rate.
*/ */
@TargetApi(21) @TargetApi(21)
@ -195,11 +165,12 @@ public final class MediaCodecInfo {
logNoSupport("sizeAndRate.vCaps"); logNoSupport("sizeAndRate.vCaps");
return false; return false;
} }
if (!videoCapabilities.areSizeAndRateSupported(width, height, frameRate)) { if (!areSizeAndRateSupported(videoCapabilities, width, height, frameRate)) {
// Capabilities are known to be inaccurately reported for vertical resolutions on some devices // 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 // (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. // and height are swapped, we assume that the vertical resolution is also supported.
if (width >= height || !videoCapabilities.areSizeAndRateSupported(height, width, frameRate)) { if (width >= height
|| !areSizeAndRateSupported(videoCapabilities, height, width, frameRate)) {
logNoSupport("sizeAndRate.support, " + width + "x" + height + "x" + frameRate); logNoSupport("sizeAndRate.support, " + width + "x" + height + "x" + frameRate);
return false; return false;
} }
@ -208,6 +179,35 @@ public final class MediaCodecInfo {
return true; return true;
} }
/**
* 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.
* @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 Point alignVideoSizeV21(int width, int height) {
if (capabilities == null) {
logNoSupport("align.caps");
return null;
}
VideoCapabilities videoCapabilities = capabilities.getVideoCapabilities();
if (videoCapabilities == null) {
logNoSupport("align.vCaps");
return null;
}
int widthAlignment = videoCapabilities.getWidthAlignment();
int heightAlignment = videoCapabilities.getHeightAlignment();
return new Point(Util.ceilDivide(width, widthAlignment) * widthAlignment,
Util.ceilDivide(height, heightAlignment) * heightAlignment);
}
/** /**
* Whether the decoder supports audio with a given sample rate. * Whether the decoder supports audio with a given sample rate.
* <p> * <p>
@ -279,6 +279,14 @@ public final class MediaCodecInfo {
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback); 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) { private static boolean isTunneling(CodecCapabilities capabilities) {
return Util.SDK_INT >= 21 && isTunnelingV21(capabilities); return Util.SDK_INT >= 21 && isTunnelingV21(capabilities);
} }

View File

@ -183,6 +183,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private boolean codecNeedsAdaptationWorkaround; private boolean codecNeedsAdaptationWorkaround;
private boolean codecNeedsEosPropagationWorkaround; private boolean codecNeedsEosPropagationWorkaround;
private boolean codecNeedsEosFlushWorkaround; private boolean codecNeedsEosFlushWorkaround;
private boolean codecNeedsEosOutputExceptionWorkaround;
private boolean codecNeedsMonoChannelCountWorkaround; private boolean codecNeedsMonoChannelCountWorkaround;
private boolean codecNeedsAdaptationWorkaroundBuffer; private boolean codecNeedsAdaptationWorkaroundBuffer;
private boolean shouldSkipAdaptationWorkaroundOutputBuffer; private boolean shouldSkipAdaptationWorkaroundOutputBuffer;
@ -201,6 +202,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private boolean inputStreamEnded; private boolean inputStreamEnded;
private boolean outputStreamEnded; private boolean outputStreamEnded;
private boolean waitingForKeys; private boolean waitingForKeys;
private boolean waitingForFirstSyncFrame;
protected DecoderCounters decoderCounters; protected DecoderCounters decoderCounters;
@ -276,11 +278,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
/** /**
* Configures a newly created {@link MediaCodec}. * Configures a newly created {@link MediaCodec}.
* *
* @param codecInfo Information about the {@link MediaCodec} being configured.
* @param codec The {@link MediaCodec} to configure. * @param codec The {@link MediaCodec} to configure.
* @param format The format for which the codec is being configured. * @param format The format for which the codec is being configured.
* @param crypto For drm protected playbacks, a {@link MediaCrypto} to use for decryption. * @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") @SuppressWarnings("deprecation")
protected final void maybeInitCodec() throws ExoPlaybackException { protected final void maybeInitCodec() throws ExoPlaybackException {
@ -338,6 +343,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecNeedsAdaptationWorkaround = codecNeedsAdaptationWorkaround(codecName); codecNeedsAdaptationWorkaround = codecNeedsAdaptationWorkaround(codecName);
codecNeedsEosPropagationWorkaround = codecNeedsEosPropagationWorkaround(codecName); codecNeedsEosPropagationWorkaround = codecNeedsEosPropagationWorkaround(codecName);
codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName); codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName);
codecNeedsEosOutputExceptionWorkaround = codecNeedsEosOutputExceptionWorkaround(codecName);
codecNeedsMonoChannelCountWorkaround = codecNeedsMonoChannelCountWorkaround(codecName, format); codecNeedsMonoChannelCountWorkaround = codecNeedsMonoChannelCountWorkaround(codecName, format);
try { try {
long codecInitializingTimestamp = SystemClock.elapsedRealtime(); long codecInitializingTimestamp = SystemClock.elapsedRealtime();
@ -345,7 +351,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codec = MediaCodec.createByCodecName(codecName); codec = MediaCodec.createByCodecName(codecName);
TraceUtil.endSection(); TraceUtil.endSection();
TraceUtil.beginSection("configureCodec"); TraceUtil.beginSection("configureCodec");
configureCodec(codec, format, mediaCrypto); configureCodec(decoderInfo, codec, format, mediaCrypto);
TraceUtil.endSection(); TraceUtil.endSection();
TraceUtil.beginSection("startCodec"); TraceUtil.beginSection("startCodec");
codec.start(); codec.start();
@ -363,6 +369,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
? (SystemClock.elapsedRealtime() + MAX_CODEC_HOTSWAP_TIME_MS) : C.TIME_UNSET; ? (SystemClock.elapsedRealtime() + MAX_CODEC_HOTSWAP_TIME_MS) : C.TIME_UNSET;
inputIndex = C.INDEX_UNSET; inputIndex = C.INDEX_UNSET;
outputIndex = C.INDEX_UNSET; outputIndex = C.INDEX_UNSET;
waitingForFirstSyncFrame = true;
decoderCounters.decoderInitCount++; decoderCounters.decoderInitCount++;
} }
@ -501,13 +508,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecHotswapDeadlineMs = C.TIME_UNSET; codecHotswapDeadlineMs = C.TIME_UNSET;
inputIndex = C.INDEX_UNSET; inputIndex = C.INDEX_UNSET;
outputIndex = C.INDEX_UNSET; outputIndex = C.INDEX_UNSET;
waitingForFirstSyncFrame = true;
waitingForKeys = false; waitingForKeys = false;
shouldSkipOutputBuffer = false; shouldSkipOutputBuffer = false;
decodeOnlyPresentationTimestamps.clear(); decodeOnlyPresentationTimestamps.clear();
codecNeedsAdaptationWorkaroundBuffer = false; codecNeedsAdaptationWorkaroundBuffer = false;
shouldSkipAdaptationWorkaroundOutputBuffer = false; shouldSkipAdaptationWorkaroundOutputBuffer = false;
if (codecNeedsFlushWorkaround || (codecNeedsEosFlushWorkaround && codecReceivedEos)) { if (codecNeedsFlushWorkaround || (codecNeedsEosFlushWorkaround && codecReceivedEos)) {
// Workaround framework bugs. See [Internal: b/8347958, b/8578467, b/8543366, b/23361053].
releaseCodec(); releaseCodec();
maybeInitCodec(); maybeInitCodec();
} else if (codecReinitializationState != REINITIALIZATION_STATE_NONE) { } else if (codecReinitializationState != REINITIALIZATION_STATE_NONE) {
@ -630,6 +637,16 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
return false; 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(); boolean bufferEncrypted = buffer.isEncrypted();
waitingForKeys = shouldWaitForKeys(bufferEncrypted); waitingForKeys = shouldWaitForKeys(bufferEncrypted);
if (waitingForKeys) { if (waitingForKeys) {
@ -763,8 +780,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* *
* @param codec The {@link MediaCodec} instance. * @param codec The {@link MediaCodec} instance.
* @param outputFormat The new output format. * @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. // Do nothing.
} }
@ -849,7 +868,22 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private boolean drainOutputBuffer(long positionUs, long elapsedRealtimeUs) private boolean drainOutputBuffer(long positionUs, long elapsedRealtimeUs)
throws ExoPlaybackException { throws ExoPlaybackException {
if (outputIndex < 0) { 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) { if (outputIndex >= 0) {
// We've dequeued a buffer. // We've dequeued a buffer.
if (shouldSkipAdaptationWorkaroundOutputBuffer) { if (shouldSkipAdaptationWorkaroundOutputBuffer) {
@ -888,9 +922,27 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
} }
if (processOutputBuffer(positionUs, elapsedRealtimeUs, codec, outputBuffers[outputIndex], boolean processedOutputBuffer;
outputIndex, outputBufferInfo.flags, outputBufferInfo.presentationTimeUs, if (codecNeedsEosOutputExceptionWorkaround && codecReceivedEos) {
shouldSkipOutputBuffer)) { 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); onProcessedOutputBuffer(outputBufferInfo.presentationTimeUs);
outputIndex = C.INDEX_UNSET; outputIndex = C.INDEX_UNSET;
return true; return true;
@ -902,7 +954,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
/** /**
* Processes a new output format. * Processes a new output format.
*/ */
private void processOutputFormat() { private void processOutputFormat() throws ExoPlaybackException {
MediaFormat format = codec.getOutputFormat(); MediaFormat format = codec.getOutputFormat();
if (codecNeedsAdaptationWorkaround if (codecNeedsAdaptationWorkaround
&& format.getInteger(MediaFormat.KEY_WIDTH) == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT && format.getInteger(MediaFormat.KEY_WIDTH) == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT
@ -992,6 +1044,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* <p> * <p>
* If true is returned, the renderer will work around the issue by releasing the decoder and * 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. * instantiating a new one rather than flushing the current instance.
* <p>
* See [Internal: b/8347958, b/8543366].
* *
* @param name The name of the decoder. * @param name The name of the decoder.
* @return True if the decoder is known to fail when flushed. * @return True if the decoder is known to fail when flushed.
@ -1061,6 +1115,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* <p> * <p>
* If true is returned, the renderer will work around the issue by instantiating a new decoder * If true is returned, the renderer will work around the issue by instantiating a new decoder
* when this case occurs. * when this case occurs.
* <p>
* See [Internal: b/8578467, b/23361053].
* *
* @param name The name of the decoder. * @param name The name of the decoder.
* @return True if the decoder is known to behave incorrectly if flushed after receiving an input * @return True if the decoder is known to behave incorrectly if flushed after receiving an input
@ -1073,6 +1129,21 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|| "OMX.amlogic.avc.decoder.awesome.secure".equals(name))); || "OMX.amlogic.avc.decoder.awesome.secure".equals(name)));
} }
/**
* 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 * 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. * to 2 for the given input format, whilst only actually outputting a single channel.

View File

@ -26,7 +26,6 @@ public final class PrivateCommand extends SpliceCommand {
public final long ptsAdjustment; public final long ptsAdjustment;
public final long identifier; public final long identifier;
public final byte[] commandBytes; public final byte[] commandBytes;
private PrivateCommand(long identifier, byte[] commandBytes, long ptsAdjustment) { private PrivateCommand(long identifier, byte[] commandBytes, long ptsAdjustment) {

View File

@ -21,6 +21,7 @@ import com.google.android.exoplayer2.metadata.MetadataDecoderException;
import com.google.android.exoplayer2.metadata.MetadataInputBuffer; import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** /**
@ -37,6 +38,8 @@ public final class SpliceInfoDecoder implements MetadataDecoder {
private final ParsableByteArray sectionData; private final ParsableByteArray sectionData;
private final ParsableBitArray sectionHeader; private final ParsableBitArray sectionHeader;
private TimestampAdjuster timestampAdjuster;
public SpliceInfoDecoder() { public SpliceInfoDecoder() {
sectionData = new ParsableByteArray(); sectionData = new ParsableByteArray();
sectionHeader = new ParsableBitArray(); sectionHeader = new ParsableBitArray();
@ -44,6 +47,13 @@ public final class SpliceInfoDecoder implements MetadataDecoder {
@Override @Override
public Metadata decode(MetadataInputBuffer inputBuffer) throws MetadataDecoderException { 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; ByteBuffer buffer = inputBuffer.data;
byte[] data = buffer.array(); byte[] data = buffer.array();
int size = buffer.limit(); int size = buffer.limit();
@ -69,10 +79,11 @@ public final class SpliceInfoDecoder implements MetadataDecoder {
command = SpliceScheduleCommand.parseFromSection(sectionData); command = SpliceScheduleCommand.parseFromSection(sectionData);
break; break;
case TYPE_SPLICE_INSERT: case TYPE_SPLICE_INSERT:
command = SpliceInsertCommand.parseFromSection(sectionData, ptsAdjustment); command = SpliceInsertCommand.parseFromSection(sectionData, ptsAdjustment,
timestampAdjuster);
break; break;
case TYPE_TIME_SIGNAL: case TYPE_TIME_SIGNAL:
command = TimeSignalCommand.parseFromSection(sectionData, ptsAdjustment); command = TimeSignalCommand.parseFromSection(sectionData, ptsAdjustment, timestampAdjuster);
break; break;
case TYPE_PRIVATE_COMMAND: case TYPE_PRIVATE_COMMAND:
command = PrivateCommand.parseFromSection(sectionData, spliceCommandLength, ptsAdjustment); command = PrivateCommand.parseFromSection(sectionData, spliceCommandLength, ptsAdjustment);

View File

@ -19,6 +19,7 @@ import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -34,6 +35,7 @@ public final class SpliceInsertCommand extends SpliceCommand {
public final boolean programSpliceFlag; public final boolean programSpliceFlag;
public final boolean spliceImmediateFlag; public final boolean spliceImmediateFlag;
public final long programSplicePts; public final long programSplicePts;
public final long programSplicePlaybackPositionUs;
public final List<ComponentSplice> componentSpliceList; public final List<ComponentSplice> componentSpliceList;
public final boolean autoReturn; public final boolean autoReturn;
public final long breakDuration; public final long breakDuration;
@ -43,14 +45,16 @@ public final class SpliceInsertCommand extends SpliceCommand {
private SpliceInsertCommand(long spliceEventId, boolean spliceEventCancelIndicator, private SpliceInsertCommand(long spliceEventId, boolean spliceEventCancelIndicator,
boolean outOfNetworkIndicator, boolean programSpliceFlag, boolean spliceImmediateFlag, boolean outOfNetworkIndicator, boolean programSpliceFlag, boolean spliceImmediateFlag,
long programSplicePts, List<ComponentSplice> componentSpliceList, boolean autoReturn, long programSplicePts, long programSplicePlaybackPositionUs,
long breakDuration, int uniqueProgramId, int availNum, int availsExpected) { List<ComponentSplice> componentSpliceList, boolean autoReturn, long breakDuration,
int uniqueProgramId, int availNum, int availsExpected) {
this.spliceEventId = spliceEventId; this.spliceEventId = spliceEventId;
this.spliceEventCancelIndicator = spliceEventCancelIndicator; this.spliceEventCancelIndicator = spliceEventCancelIndicator;
this.outOfNetworkIndicator = outOfNetworkIndicator; this.outOfNetworkIndicator = outOfNetworkIndicator;
this.programSpliceFlag = programSpliceFlag; this.programSpliceFlag = programSpliceFlag;
this.spliceImmediateFlag = spliceImmediateFlag; this.spliceImmediateFlag = spliceImmediateFlag;
this.programSplicePts = programSplicePts; this.programSplicePts = programSplicePts;
this.programSplicePlaybackPositionUs = programSplicePlaybackPositionUs;
this.componentSpliceList = Collections.unmodifiableList(componentSpliceList); this.componentSpliceList = Collections.unmodifiableList(componentSpliceList);
this.autoReturn = autoReturn; this.autoReturn = autoReturn;
this.breakDuration = breakDuration; this.breakDuration = breakDuration;
@ -66,6 +70,7 @@ public final class SpliceInsertCommand extends SpliceCommand {
programSpliceFlag = in.readByte() == 1; programSpliceFlag = in.readByte() == 1;
spliceImmediateFlag = in.readByte() == 1; spliceImmediateFlag = in.readByte() == 1;
programSplicePts = in.readLong(); programSplicePts = in.readLong();
programSplicePlaybackPositionUs = in.readLong();
int componentSpliceListSize = in.readInt(); int componentSpliceListSize = in.readInt();
List<ComponentSplice> componentSpliceList = new ArrayList<>(componentSpliceListSize); List<ComponentSplice> componentSpliceList = new ArrayList<>(componentSpliceListSize);
for (int i = 0; i < componentSpliceListSize; i++) { for (int i = 0; i < componentSpliceListSize; i++) {
@ -80,7 +85,7 @@ public final class SpliceInsertCommand extends SpliceCommand {
} }
/* package */ static SpliceInsertCommand parseFromSection(ParsableByteArray sectionData, /* package */ static SpliceInsertCommand parseFromSection(ParsableByteArray sectionData,
long ptsAdjustment) { long ptsAdjustment, TimestampAdjuster timestampAdjuster) {
long spliceEventId = sectionData.readUnsignedInt(); long spliceEventId = sectionData.readUnsignedInt();
// splice_event_cancel_indicator(1), reserved(7). // splice_event_cancel_indicator(1), reserved(7).
boolean spliceEventCancelIndicator = (sectionData.readUnsignedByte() & 0x80) != 0; boolean spliceEventCancelIndicator = (sectionData.readUnsignedByte() & 0x80) != 0;
@ -88,7 +93,7 @@ public final class SpliceInsertCommand extends SpliceCommand {
boolean programSpliceFlag = false; boolean programSpliceFlag = false;
boolean spliceImmediateFlag = false; boolean spliceImmediateFlag = false;
long programSplicePts = C.TIME_UNSET; long programSplicePts = C.TIME_UNSET;
ArrayList<ComponentSplice> componentSplices = new ArrayList<>(); List<ComponentSplice> componentSplices = Collections.emptyList();
int uniqueProgramId = 0; int uniqueProgramId = 0;
int availNum = 0; int availNum = 0;
int availsExpected = 0; int availsExpected = 0;
@ -112,7 +117,8 @@ public final class SpliceInsertCommand extends SpliceCommand {
if (!spliceImmediateFlag) { if (!spliceImmediateFlag) {
componentSplicePts = TimeSignalCommand.parseSpliceTime(sectionData, ptsAdjustment); componentSplicePts = TimeSignalCommand.parseSpliceTime(sectionData, ptsAdjustment);
} }
componentSplices.add(new ComponentSplice(componentTag, componentSplicePts)); componentSplices.add(new ComponentSplice(componentTag, componentSplicePts,
timestampAdjuster.adjustTsTimestamp(componentSplicePts)));
} }
} }
if (durationFlag) { if (durationFlag) {
@ -125,7 +131,8 @@ public final class SpliceInsertCommand extends SpliceCommand {
availsExpected = sectionData.readUnsignedByte(); availsExpected = sectionData.readUnsignedByte();
} }
return new SpliceInsertCommand(spliceEventId, spliceEventCancelIndicator, outOfNetworkIndicator, return new SpliceInsertCommand(spliceEventId, spliceEventCancelIndicator, outOfNetworkIndicator,
programSpliceFlag, spliceImmediateFlag, programSplicePts, componentSplices, autoReturn, programSpliceFlag, spliceImmediateFlag, programSplicePts,
timestampAdjuster.adjustTsTimestamp(programSplicePts), componentSplices, autoReturn,
duration, uniqueProgramId, availNum, availsExpected); duration, uniqueProgramId, availNum, availsExpected);
} }
@ -136,19 +143,23 @@ public final class SpliceInsertCommand extends SpliceCommand {
public final int componentTag; public final int componentTag;
public final long componentSplicePts; 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.componentTag = componentTag;
this.componentSplicePts = componentSplicePts; this.componentSplicePts = componentSplicePts;
this.componentSplicePlaybackPositionUs = componentSplicePlaybackPositionUs;
} }
public void writeToParcel(Parcel dest) { public void writeToParcel(Parcel dest) {
dest.writeInt(componentTag); dest.writeInt(componentTag);
dest.writeLong(componentSplicePts); dest.writeLong(componentSplicePts);
dest.writeLong(componentSplicePlaybackPositionUs);
} }
public static ComponentSplice createFromParcel(Parcel in) { 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) (programSpliceFlag ? 1 : 0));
dest.writeByte((byte) (spliceImmediateFlag ? 1 : 0)); dest.writeByte((byte) (spliceImmediateFlag ? 1 : 0));
dest.writeLong(programSplicePts); dest.writeLong(programSplicePts);
dest.writeLong(programSplicePlaybackPositionUs);
int componentSpliceListSize = componentSpliceList.size(); int componentSpliceListSize = componentSpliceList.size();
dest.writeInt(componentSpliceListSize); dest.writeInt(componentSpliceListSize);
for (int i = 0; i < componentSpliceListSize; i++) { for (int i = 0; i < componentSpliceListSize; i++) {

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.metadata.scte35;
import android.os.Parcel; import android.os.Parcel;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.ParsableByteArray; 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. * 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 class TimeSignalCommand extends SpliceCommand {
public final long ptsTime; public final long ptsTime;
public final long playbackPositionUs;
private TimeSignalCommand(long ptsTime) { private TimeSignalCommand(long ptsTime, long playbackPositionUs) {
this.ptsTime = ptsTime; this.ptsTime = ptsTime;
this.playbackPositionUs = playbackPositionUs;
} }
/* package */ static TimeSignalCommand parseFromSection(ParsableByteArray sectionData, /* package */ static TimeSignalCommand parseFromSection(ParsableByteArray sectionData,
long ptsAdjustment) { long ptsAdjustment, TimestampAdjuster timestampAdjuster) {
return new TimeSignalCommand(parseSpliceTime(sectionData, ptsAdjustment)); 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 @Override
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(ptsTime); dest.writeLong(ptsTime);
dest.writeLong(playbackPositionUs);
} }
public static final Creator<TimeSignalCommand> CREATOR = public static final Creator<TimeSignalCommand> CREATOR =
@ -68,7 +74,7 @@ public final class TimeSignalCommand extends SpliceCommand {
@Override @Override
public TimeSignalCommand createFromParcel(Parcel in) { public TimeSignalCommand createFromParcel(Parcel in) {
return new TimeSignalCommand(in.readLong()); return new TimeSignalCommand(in.readLong(), in.readLong());
} }
@Override @Override

View File

@ -381,7 +381,7 @@ import java.io.IOException;
// ExtractorOutput implementation. Called by the loading thread. // ExtractorOutput implementation. Called by the loading thread.
@Override @Override
public TrackOutput track(int id) { public TrackOutput track(int id, int type) {
DefaultTrackOutput trackOutput = sampleQueues.get(id); DefaultTrackOutput trackOutput = sampleQueues.get(id);
if (trackOutput == null) { if (trackOutput == null) {
trackOutput = new DefaultTrackOutput(allocator); trackOutput = new DefaultTrackOutput(allocator);
@ -519,7 +519,7 @@ import java.io.IOException;
} }
private boolean isLoadableExceptionFatal(IOException e) { private boolean isLoadableExceptionFatal(IOException e) {
return e instanceof ExtractorMediaSource.UnrecognizedInputFormatException; return e instanceof UnrecognizedInputFormatException;
} }
private void notifyLoadError(final IOException error) { private void notifyLoadError(final IOException error) {
@ -625,7 +625,7 @@ import java.io.IOException;
length += position; length += position;
} }
input = new DefaultExtractorInput(dataSource, position, length); input = new DefaultExtractorInput(dataSource, position, length);
Extractor extractor = extractorHolder.selectExtractor(input); Extractor extractor = extractorHolder.selectExtractor(input, dataSource.getUri());
if (pendingExtractorSeek) { if (pendingExtractorSeek) {
extractor.seek(position, seekTimeUs); extractor.seek(position, seekTimeUs);
pendingExtractorSeek = false; pendingExtractorSeek = false;
@ -677,13 +677,13 @@ import java.io.IOException;
* later calls. * later calls.
* *
* @param input The {@link ExtractorInput} from which data should be read. * @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}. * @return An initialized extractor for reading {@code input}.
* @throws ExtractorMediaSource.UnrecognizedInputFormatException Thrown if the input format * @throws UnrecognizedInputFormatException Thrown if the input format could not be detected.
* could not be detected.
* @throws IOException Thrown if the input could not be read. * @throws IOException Thrown if the input could not be read.
* @throws InterruptedException Thrown if the thread was interrupted. * @throws InterruptedException Thrown if the thread was interrupted.
*/ */
public Extractor selectExtractor(ExtractorInput input) public Extractor selectExtractor(ExtractorInput input, Uri uri)
throws IOException, InterruptedException { throws IOException, InterruptedException {
if (extractor != null) { if (extractor != null) {
return extractor; return extractor;
@ -701,7 +701,8 @@ import java.io.IOException;
} }
} }
if (extractor == null) { 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); extractor.init(extractorOutput);
return extractor; return extractor;

View File

@ -19,7 +19,6 @@ import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.Extractor; 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.Allocator;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
/** /**
@ -57,18 +55,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. * The default minimum number of times to retry loading prior to failing for on-demand streams.
*/ */

View File

@ -0,0 +1,40 @@
/*
* 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;
}
}

View File

@ -17,7 +17,7 @@ package com.google.android.exoplayer2.source.chunk;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.extractor.DummyTrackOutput;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.ExtractorOutput; 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. * An {@link Extractor} wrapper for loading chunks containing a single track.
* <p> * <p>
* The wrapper allows switching of the {@link SeekMapOutput} and {@link TrackOutput} that receive * The wrapper allows switching of the {@link TrackOutput} that receives parsed data.
* parsed data.
*/ */
public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput { 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; public final Extractor extractor;
private final Format manifestFormat; private final Format manifestFormat;
private final boolean preferManifestDrmInitData; private final int primaryTrackType;
private final boolean resendFormatOnInit;
private boolean extractorInitialized; private boolean extractorInitialized;
private SeekMapOutput seekMapOutput;
private TrackOutput trackOutput; private TrackOutput trackOutput;
private Format sentFormat; private SeekMap seekMap;
private Format sampleFormat;
// Accessed only on the loader thread. // Accessed only on the loader thread.
private boolean seenTrack; private boolean seenTrack;
@ -66,36 +52,44 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
* @param extractor The extractor to wrap. * @param extractor The extractor to wrap.
* @param manifestFormat A manifest defined {@link Format} whose data should be merged into any * @param manifestFormat A manifest defined {@link Format} whose data should be merged into any
* sample {@link Format} output from the {@link Extractor}. * sample {@link Format} output from the {@link Extractor}.
* @param preferManifestDrmInitData Whether {@link DrmInitData} defined in {@code manifestFormat} * @param primaryTrackType The type of the primary track. Typically one of the {@link C}
* should be preferred when the sample and manifest {@link Format}s are merged. * {@code TRACK_TYPE_*} constants.
* @param resendFormatOnInit Whether the extractor should resend the previous {@link Format} when
* it is initialized via {@link #init(SeekMapOutput, TrackOutput)}.
*/ */
public ChunkExtractorWrapper(Extractor extractor, Format manifestFormat, public ChunkExtractorWrapper(Extractor extractor, Format manifestFormat, int primaryTrackType) {
boolean preferManifestDrmInitData, boolean resendFormatOnInit) {
this.extractor = extractor; this.extractor = extractor;
this.manifestFormat = manifestFormat; this.manifestFormat = manifestFormat;
this.preferManifestDrmInitData = preferManifestDrmInitData; this.primaryTrackType = primaryTrackType;
this.resendFormatOnInit = resendFormatOnInit;
} }
/** /**
* Initializes the extractor to output to the provided {@link SeekMapOutput} and * Returns the {@link SeekMap} most recently output by the extractor, or null.
* {@link TrackOutput} instances, and configures it to receive data from a new chunk. */
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 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. * @param trackOutput The {@link TrackOutput} that will receive sample data.
*/ */
public void init(SeekMapOutput seekMapOutput, TrackOutput trackOutput) { public void init(TrackOutput trackOutput) {
this.seekMapOutput = seekMapOutput;
this.trackOutput = trackOutput; this.trackOutput = trackOutput;
if (!extractorInitialized) { if (!extractorInitialized) {
extractor.init(this); extractor.init(this);
extractorInitialized = true; extractorInitialized = true;
} else { } else {
extractor.seek(0, 0); extractor.seek(0, 0);
if (resendFormatOnInit && sentFormat != null) { if (sampleFormat != null && trackOutput != null) {
trackOutput.format(sentFormat); trackOutput.format(sampleFormat);
} }
} }
} }
@ -103,7 +97,10 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
// ExtractorOutput implementation. // ExtractorOutput implementation.
@Override @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); Assertions.checkState(!seenTrack || seenTrackId == id);
seenTrack = true; seenTrack = true;
seenTrackId = id; seenTrackId = id;
@ -117,15 +114,17 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
@Override @Override
public void seekMap(SeekMap seekMap) { public void seekMap(SeekMap seekMap) {
seekMapOutput.seekMap(seekMap); this.seekMap = seekMap;
} }
// TrackOutput implementation. // TrackOutput implementation.
@Override @Override
public void format(Format format) { public void format(Format format) {
sentFormat = format.copyWithManifestFormatInfo(manifestFormat, preferManifestDrmInitData); sampleFormat = format.copyWithManifestFormatInfo(manifestFormat);
trackOutput.format(sentFormat); if (trackOutput != null) {
trackOutput.format(sampleFormat);
}
} }
@Override @Override

View File

@ -20,8 +20,6 @@ import com.google.android.exoplayer2.extractor.DefaultExtractorInput;
import com.google.android.exoplayer2.extractor.DefaultTrackOutput; import com.google.android.exoplayer2.extractor.DefaultTrackOutput;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput; 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.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Assertions; 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. * 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 int chunkCount;
private final long sampleOffsetUs; private final long sampleOffsetUs;
private final ChunkExtractorWrapper extractorWrapper; private final ChunkExtractorWrapper extractorWrapper;
private final Format sampleFormat;
private volatile int bytesLoaded; private volatile int bytesLoaded;
private volatile boolean loadCanceled; private volatile boolean loadCanceled;
@ -56,19 +53,15 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SeekMapOutput
* underlying media are being merged into a single load. * underlying media are being merged into a single load.
* @param sampleOffsetUs An offset to add to the sample timestamps parsed by the extractor. * @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 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, public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat,
int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs, int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs,
int chunkIndex, int chunkCount, long sampleOffsetUs, ChunkExtractorWrapper extractorWrapper, int chunkIndex, int chunkCount, long sampleOffsetUs, ChunkExtractorWrapper extractorWrapper) {
Format sampleFormat) {
super(dataSource, dataSpec, trackFormat, trackSelectionReason, trackSelectionData, startTimeUs, super(dataSource, dataSpec, trackFormat, trackSelectionReason, trackSelectionData, startTimeUs,
endTimeUs, chunkIndex); endTimeUs, chunkIndex);
this.chunkCount = chunkCount; this.chunkCount = chunkCount;
this.sampleOffsetUs = sampleOffsetUs; this.sampleOffsetUs = sampleOffsetUs;
this.extractorWrapper = extractorWrapper; this.extractorWrapper = extractorWrapper;
this.sampleFormat = sampleFormat;
} }
@Override @Override
@ -86,13 +79,6 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SeekMapOutput
return bytesLoaded; return bytesLoaded;
} }
// SeekMapOutput implementation.
@Override
public final void seekMap(SeekMap seekMap) {
// Do nothing.
}
// Loadable implementation. // Loadable implementation.
@Override @Override
@ -116,8 +102,8 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SeekMapOutput
if (bytesLoaded == 0) { if (bytesLoaded == 0) {
// Set the target to ourselves. // Set the target to ourselves.
DefaultTrackOutput trackOutput = getTrackOutput(); DefaultTrackOutput trackOutput = getTrackOutput();
trackOutput.formatWithOffset(sampleFormat, sampleOffsetUs); trackOutput.setSampleOffsetUs(sampleOffsetUs);
extractorWrapper.init(this, trackOutput); extractorWrapper.init(trackOutput);
} }
// Load and decode the sample data. // Load and decode the sample data.
try { try {

Some files were not shown because too many files have changed in this diff Show More