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 #
### r2.1.1 ###
### r2.2.0 ###
Bugfix release only. Users of r2.1.0 and r2.0.x should proactively update to
this version.
* Demo app: Automatic recovery from BehindLiveWindowException, plus improved
handling of pausing and resuming live streams
([#2344](https://github.com/google/ExoPlayer/issues/2344)).
* AndroidTV: Added Support for tunneled video playback
([#1688](https://github.com/google/ExoPlayer/issues/1688)).
* DRM: Renamed StreamingDrmSessionManager to DefaultDrmSessionManager and
added support for using offline licenses
([#876](https://github.com/google/ExoPlayer/issues/876)).
* DRM: Introduce OfflineLicenseHelper to help with offline license acquisition,
renewal and release.
* UI: Updated player control assets. Added vector drawables for use on API level
21 and above.
* UI: Made player control seek bar work correctly with key events if focusable
([#2278](https://github.com/google/ExoPlayer/issues/2278)).
* HLS: Improved support for streams that use EXT-X-DISCONTINUITY without
EXT-X-DISCONTINUITY-SEQUENCE
([#1789](https://github.com/google/ExoPlayer/issues/1789)).
* HLS: Support for EXT-X-START tag
([#1544](https://github.com/google/ExoPlayer/issues/1544)).
* HLS: Check #EXTM3U header is present when parsing the playlist. Fail
gracefully if not ([#2301](https://github.com/google/ExoPlayer/issues/2301)).
* HLS: Fix memory leak
([#2319](https://github.com/google/ExoPlayer/issues/2319)).
* HLS: Fix non-seamless first adaptation where master playlist omits resolution
tags ([#2096](https://github.com/google/ExoPlayer/issues/2096)).
* HLS: Fix handling of WebVTT subtitle renditions with non-standard segment file
extensions ([#2025](https://github.com/google/ExoPlayer/issues/2025) and
[#2355](https://github.com/google/ExoPlayer/issues/2355)).
* HLS: Better handle inconsistent HLS playlist update
([#2249](https://github.com/google/ExoPlayer/issues/2249)).
* DASH: Don't overflow when dealing with large segment numbers
([#2311](https://github.com/google/ExoPlayer/issues/2311)).
* DASH: Fix propagation of language from the manifest
([#2335](https://github.com/google/ExoPlayer/issues/2335)).
* SmoothStreaming: Work around "Offset to sample data was negative" failures
([#2292](https://github.com/google/ExoPlayer/issues/2292),
[#2101](https://github.com/google/ExoPlayer/issues/2101) and
[#1152](https://github.com/google/ExoPlayer/issues/1152)).
* MP3/ID3: Added support for parsing Chapter and URL link frames
([#2316](https://github.com/google/ExoPlayer/issues/2316)).
* MP3/ID3: Handle ID3 frames that end with empty text field
([#2309](https://github.com/google/ExoPlayer/issues/2309)).
* Added ClippingMediaSource for playing clipped portions of media
([#1988](https://github.com/google/ExoPlayer/issues/1988)).
* Added convenience methods to query whether the current window is dynamic and
seekable ([#2320](https://github.com/google/ExoPlayer/issues/2320)).
* Support setting of default headers on HttpDataSource.Factory implementations
([#2166](https://github.com/google/ExoPlayer/issues/2166)).
* Fixed cache failures when using an encrypted cache content index.
* Fix visual artifacts when switching output surface
([#2093](https://github.com/google/ExoPlayer/issues/2093)).
* Fix gradle + proguard configurations.
* Fix player position when replacing the MediaSource
([#2369](https://github.com/google/ExoPlayer/issues/2369)).
* Misc bug fixes, including
[#2330](https://github.com/google/ExoPlayer/issues/2330),
[#2269](https://github.com/google/ExoPlayer/issues/2269),
[#2252](https://github.com/google/ExoPlayer/issues/2252),
[#2264](https://github.com/google/ExoPlayer/issues/2264) and
[#2290](https://github.com/google/ExoPlayer/issues/2290).
### r2.1.1 ###
* Fix some subtitle types (e.g. WebVTT) being displayed out of sync
([#2208](https://github.com/google/ExoPlayer/issues/2208)).
@ -52,9 +112,9 @@ this version.
* Improved flexibility of SimpleExoPlayer
([#2102](https://github.com/google/ExoPlayer/issues/2102)).
* Fix issue where only the audio of a video would play due to capability
detection issues ([#2007](https://github.com/google/ExoPlayer/issues/2007))
([#2034](https://github.com/google/ExoPlayer/issues/2034))
([#2157](https://github.com/google/ExoPlayer/issues/2157)).
detection issues ([#2007](https://github.com/google/ExoPlayer/issues/2007),
[#2034](https://github.com/google/ExoPlayer/issues/2034) and
[#2157](https://github.com/google/ExoPlayer/issues/2157)).
* Fix issues that could cause ExtractorMediaSource based playbacks to get stuck
buffering ([#1962](https://github.com/google/ExoPlayer/issues/1962)).
* Correctly set SimpleExoPlayerView surface aspect ratio when an active player
@ -186,6 +246,14 @@ in all V2 releases. This cannot be assumed for changes in r1.5.12 and later,
however it can be assumed that all such changes are included in the most recent
V2 release.
### r1.5.14 ###
* Fixed cache failures when using an encrypted cache content index.
* SmoothStreaming: Work around "Offset to sample data was negative" failures
([#2292](https://github.com/google/ExoPlayer/issues/2292),
[#2101](https://github.com/google/ExoPlayer/issues/2101) and
[#1152](https://github.com/google/ExoPlayer/issues/1152)).
### r1.5.13 ###
* Improvements to the upstream cache package.

View File

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

View File

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

View File

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

View File

@ -70,8 +70,6 @@ import com.google.android.exoplayer2.util.Util;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
@ -239,19 +237,9 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
if (drmSchemeUuid != null) {
String drmLicenseUrl = intent.getStringExtra(DRM_LICENSE_URL);
String[] keyRequestPropertiesArray = intent.getStringArrayExtra(DRM_KEY_REQUEST_PROPERTIES);
Map<String, String> keyRequestProperties;
if (keyRequestPropertiesArray == null || keyRequestPropertiesArray.length < 2) {
keyRequestProperties = null;
} else {
keyRequestProperties = new HashMap<>();
for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) {
keyRequestProperties.put(keyRequestPropertiesArray[i],
keyRequestPropertiesArray[i + 1]);
}
}
try {
drmSessionManager = buildDrmSessionManager(drmSchemeUuid, drmLicenseUrl,
keyRequestProperties);
keyRequestPropertiesArray);
} catch (UnsupportedDrmException e) {
int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported
: (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
@ -317,8 +305,11 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
}
MediaSource mediaSource = mediaSources.length == 1 ? mediaSources[0]
: new ConcatenatingMediaSource(mediaSources);
player.seekTo(resumeWindow, resumePosition);
player.prepare(mediaSource, false, false);
boolean haveResumePosition = resumeWindow != C.INDEX_UNSET;
if (haveResumePosition) {
player.seekTo(resumeWindow, resumePosition);
}
player.prepare(mediaSource, !haveResumePosition, false);
playerNeedsSource = false;
updateButtonVisibilities();
}
@ -346,12 +337,18 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
}
private DrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManager(UUID uuid,
String licenseUrl, Map<String, String> keyRequestProperties) throws UnsupportedDrmException {
String licenseUrl, String[] keyRequestPropertiesArray) throws UnsupportedDrmException {
if (Util.SDK_INT < 18) {
return null;
}
HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl,
buildHttpDataSourceFactory(false), keyRequestProperties);
buildHttpDataSourceFactory(false));
if (keyRequestPropertiesArray != null) {
for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) {
drmCallback.setKeyRequestProperty(keyRequestPropertiesArray[i],
keyRequestPropertiesArray[i + 1]);
}
}
return new DefaultDrmSessionManager<>(uuid,
FrameworkMediaDrm.newInstance(uuid), drmCallback, null, mainHandler, eventLogger);
}
@ -377,7 +374,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
}
private void clearResumePosition() {
resumeWindow = 0;
resumeWindow = C.INDEX_UNSET;
resumePosition = C.TIME_UNSET;
}
@ -422,7 +419,12 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
@Override
public void onPositionDiscontinuity() {
// Do nothing.
if (playerNeedsSource) {
// This will only occur if the user has performed a seek whilst in the error state. Update the
// resume position so that if the user then retries, playback will resume from the position to
// which they seeked.
updateResumePosition();
}
}
@Override
@ -461,11 +463,12 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
playerNeedsSource = true;
if (isBehindLiveWindow(e)) {
clearResumePosition();
initializePlayer();
} else {
updateResumePosition();
updateButtonVisibilities();
showControls();
}
updateButtonVisibilities();
showControls();
}
@Override

View File

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

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">
<!-- The user visible name of the application. [CHAR LIMIT=20] -->
<string name="application_name">ExoPlayer2 Demo</string>
<string name="application_name">ExoPlayer</string>
<string name="video">Video</string>

View File

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

View File

@ -19,8 +19,8 @@ import android.os.Handler;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.AudioCapabilities;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.BufferProcessor;
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.MimeTypes;
@ -43,21 +43,12 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer {
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
*/
public FfmpegAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener) {
super(eventHandler, eventListener);
}
/**
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param audioCapabilities The audio capabilities for playback on this device. May be null if the
* default capabilities (no encoded audio passthrough support) should be assumed.
* @param bufferProcessors Optional {@link BufferProcessor}s which will process PCM audio buffers
* before they are output.
*/
public FfmpegAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
AudioCapabilities audioCapabilities) {
super(eventHandler, eventListener, audioCapabilities);
BufferProcessor... bufferProcessors) {
super(eventHandler, eventListener, bufferProcessors);
}
@Override

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.SimpleOutputBuffer;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.nio.ByteBuffer;
import java.util.List;
@ -88,6 +89,13 @@ import java.util.List;
if (!hasOutputFormat) {
channelCount = ffmpegGetChannelCount(nativeContext);
sampleRate = ffmpegGetSampleRate(nativeContext);
if (sampleRate == 0 && "alac".equals(codecName)) {
// ALAC decoder did not set the sample rate in earlier versions of FFMPEG.
// See https://trac.ffmpeg.org/ticket/6096
ParsableByteArray parsableExtraData = new ParsableByteArray(extraData);
parsableExtraData.setPosition(extraData.length - 4);
sampleRate = parsableExtraData.readUnsignedIntToInt();
}
hasOutputFormat = true;
}
outputBuffer.data.position(0);
@ -123,6 +131,7 @@ import java.util.List;
private static byte[] getExtraData(String mimeType, List<byte[]> initializationData) {
switch (mimeType) {
case MimeTypes.AUDIO_AAC:
case MimeTypes.AUDIO_ALAC:
case MimeTypes.AUDIO_OPUS:
return initializationData.get(0);
case MimeTypes.AUDIO_VORBIS:

View File

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

View File

@ -5,7 +5,10 @@
native <methods>;
}
# Some members of this class are being accessed from native methods. Keep them unobfuscated.
# Some members of these classes are being accessed from native methods. Keep them unobfuscated.
-keep class com.google.android.exoplayer2.ext.flac.FlacDecoderJni {
*;
}
-keep class com.google.android.exoplayer2.util.FlacStreamInfo {
*;
}

View File

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

View File

@ -17,8 +17,8 @@ package com.google.android.exoplayer2.ext.flac;
import android.os.Handler;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.AudioCapabilities;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.BufferProcessor;
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.MimeTypes;
@ -38,21 +38,12 @@ public class LibflacAudioRenderer extends SimpleDecoderAudioRenderer {
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
*/
public LibflacAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener) {
super(eventHandler, eventListener);
}
/**
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param audioCapabilities The audio capabilities for playback on this device. May be null if the
* default capabilities (no encoded audio passthrough support) should be assumed.
* @param bufferProcessors Optional {@link BufferProcessor}s which will process PCM audio buffers
* before they are output.
*/
public LibflacAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
AudioCapabilities audioCapabilities) {
super(eventHandler, eventListener, audioCapabilities);
BufferProcessor... bufferProcessors) {
super(eventHandler, eventListener, bufferProcessors);
}
@Override

View File

@ -31,7 +31,7 @@ LOCAL_C_INCLUDES := \
LOCAL_SRC_FILES := $(FLAC_SOURCES)
LOCAL_CFLAGS += '-DVERSION="1.3.1"' -DFLAC__NO_MD5 -DFLAC__INTEGER_ONLY_LIBRARY -DFLAC__NO_ASM
LOCAL_CFLAGS += -D_REENTRANT -DPIC -DU_COMMON_IMPLEMENTATION -fPIC
LOCAL_CFLAGS += -D_REENTRANT -DPIC -DU_COMMON_IMPLEMENTATION -fPIC -DHAVE_SYS_PARAM_H
LOCAL_CFLAGS += -O3 -funroll-loops -finline-functions
LOCAL_LDLIBS := -llog -lz -lm

View File

@ -453,7 +453,8 @@ int64_t FLACParser::getSeekPosition(int64_t timeUs) {
}
FLAC__StreamMetadata_SeekPoint* points = mSeekTable->points;
for (unsigned i = mSeekTable->num_points - 1; i >= 0; i--) {
for (unsigned i = mSeekTable->num_points; i > 0; ) {
i--;
if (points[i].sample_number <= sample) {
return firstFrameOffset + points[i].stream_offset;
}

View File

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

View File

@ -213,7 +213,7 @@ import java.util.List;
SimpleOutputBuffer outputBuffer, int sampleRate);
private native int opusSecureDecode(long decoder, long timeUs, ByteBuffer inputBuffer,
int inputSize, SimpleOutputBuffer outputBuffer, int sampleRate,
ExoMediaCrypto wvCrypto, int inputMode, byte[] key, byte[] iv,
ExoMediaCrypto mediaCrypto, int inputMode, byte[] key, byte[] iv,
int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData);
private native void opusClose(long decoder);
private native void opusReset(long decoder);

View File

@ -141,7 +141,7 @@ import java.nio.ByteBuffer;
private native long vpxClose(long context);
private native long vpxDecode(long context, ByteBuffer encoded, int length);
private native long vpxSecureDecode(long context, ByteBuffer encoded, int length,
ExoMediaCrypto wvCrypto, int inputMode, byte[] key, byte[] iv,
ExoMediaCrypto mediaCrypto, int inputMode, byte[] key, byte[] iv,
int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData);
private native int vpxGetFrame(long context, VpxOutputBuffer outputBuffer);
private native int vpxGetErrorCode(long context);

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:
format:
bitrate = -1
id = null
id = 0
containerMimeType = null
sampleMimeType = audio/ac3
maxInputSize = -1

View File

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

View File

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

View File

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

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.testutil.TestUtil;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.Arrays;
import java.util.HashMap;
import org.mockito.Mock;
@ -213,11 +214,15 @@ public class OfflineLicenseHelperTest extends InstrumentationTestCase {
}
private static AdaptationSet newAdaptationSets(Representation... representations) {
return new AdaptationSet(0, C.TRACK_TYPE_VIDEO, Arrays.asList(representations));
return new AdaptationSet(0, C.TRACK_TYPE_VIDEO, Arrays.asList(representations), null);
}
private static Representation newRepresentations(DrmInitData drmInitData) {
Format format = Format.createVideoSampleFormat("", "", "", 0, 0, 0, 0, 0, null, drmInitData);
Format format = Format.createVideoContainerFormat("id", MimeTypes.VIDEO_MP4,
MimeTypes.VIDEO_H264, "", Format.NO_VALUE, 1024, 768, Format.NO_VALUE, null, 0);
if (drmInitData != null) {
format = format.copyWithDrmInitData(drmInitData);
}
return Representation.newInstance("", 0, format, "", new SingleSegmentBase());
}

View File

@ -25,21 +25,32 @@ import com.google.android.exoplayer2.testutil.TestUtil;
*/
public final class FragmentedMp4ExtractorTest extends InstrumentationTestCase {
private static final TestUtil.ExtractorFactory EXTRACTOR_FACTORY =
new TestUtil.ExtractorFactory() {
@Override
public Extractor create() {
return new FragmentedMp4Extractor();
}
};
public void testSample() throws Exception {
TestUtil.assertOutput(EXTRACTOR_FACTORY, "mp4/sample_fragmented.mp4", getInstrumentation());
TestUtil.assertOutput(getExtractorFactory(), "mp4/sample_fragmented.mp4", getInstrumentation());
}
public void testSampleWithSeiPayloadParsing() throws Exception {
// Enabling the CEA-608 track enables SEI payload parsing.
TestUtil.assertOutput(getExtractorFactory(FragmentedMp4Extractor.FLAG_ENABLE_CEA608_TRACK),
"mp4/sample_fragmented_sei.mp4", getInstrumentation());
}
public void testAtomWithZeroSize() throws Exception {
TestUtil.assertThrows(EXTRACTOR_FACTORY, "mp4/sample_fragmented_zero_size_atom.mp4",
TestUtil.assertThrows(getExtractorFactory(), "mp4/sample_fragmented_zero_size_atom.mp4",
getInstrumentation(), ParserException.class);
}
private static TestUtil.ExtractorFactory getExtractorFactory() {
return getExtractorFactory(0);
}
private static TestUtil.ExtractorFactory getExtractorFactory(final int flags) {
return new TestUtil.ExtractorFactory() {
@Override
public Extractor create() {
return new FragmentedMp4Extractor(flags, null);
}
};
}
}

View File

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

View File

@ -16,9 +16,9 @@
package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

View File

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

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.testutil.TestUtil;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
/**
* Unit tests for {@link DashManifestParser}.
@ -70,34 +72,57 @@ public class DashManifestParserTest extends InstrumentationTestCase {
}
public void testParseCea608AccessibilityChannel() {
assertEquals(1, DashManifestParser.parseCea608AccessibilityChannel("CC1=eng"));
assertEquals(2, DashManifestParser.parseCea608AccessibilityChannel("CC2=eng"));
assertEquals(3, DashManifestParser.parseCea608AccessibilityChannel("CC3=eng"));
assertEquals(4, DashManifestParser.parseCea608AccessibilityChannel("CC4=eng"));
assertEquals(1, DashManifestParser.parseCea608AccessibilityChannel(
buildCea608AccessibilityDescriptors("CC1=eng")));
assertEquals(2, DashManifestParser.parseCea608AccessibilityChannel(
buildCea608AccessibilityDescriptors("CC2=eng")));
assertEquals(3, DashManifestParser.parseCea608AccessibilityChannel(
buildCea608AccessibilityDescriptors("CC3=eng")));
assertEquals(4, DashManifestParser.parseCea608AccessibilityChannel(
buildCea608AccessibilityDescriptors("CC4=eng")));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel(null));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel(""));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel("CC0=eng"));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel("CC5=eng"));
assertEquals(Format.NO_VALUE,
DashManifestParser.parseCea608AccessibilityChannel("Wrong format"));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel(
buildCea608AccessibilityDescriptors(null)));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel(
buildCea608AccessibilityDescriptors("")));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel(
buildCea608AccessibilityDescriptors("CC0=eng")));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel(
buildCea608AccessibilityDescriptors("CC5=eng")));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel(
buildCea608AccessibilityDescriptors("Wrong format")));
}
public void testParseCea708AccessibilityChannel() {
assertEquals(1, DashManifestParser.parseCea708AccessibilityChannel("1=lang:eng"));
assertEquals(2, DashManifestParser.parseCea708AccessibilityChannel("2=lang:eng"));
assertEquals(3, DashManifestParser.parseCea708AccessibilityChannel("3=lang:eng"));
assertEquals(62, DashManifestParser.parseCea708AccessibilityChannel("62=lang:eng"));
assertEquals(63, DashManifestParser.parseCea708AccessibilityChannel("63=lang:eng"));
assertEquals(1, DashManifestParser.parseCea708AccessibilityChannel(
buildCea708AccessibilityDescriptors("1=lang:eng")));
assertEquals(2, DashManifestParser.parseCea708AccessibilityChannel(
buildCea708AccessibilityDescriptors("2=lang:eng")));
assertEquals(3, DashManifestParser.parseCea708AccessibilityChannel(
buildCea708AccessibilityDescriptors("3=lang:eng")));
assertEquals(62, DashManifestParser.parseCea708AccessibilityChannel(
buildCea708AccessibilityDescriptors("62=lang:eng")));
assertEquals(63, DashManifestParser.parseCea708AccessibilityChannel(
buildCea708AccessibilityDescriptors("63=lang:eng")));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea708AccessibilityChannel(null));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea708AccessibilityChannel(""));
assertEquals(Format.NO_VALUE,
DashManifestParser.parseCea708AccessibilityChannel("0=lang:eng"));
assertEquals(Format.NO_VALUE,
DashManifestParser.parseCea708AccessibilityChannel("64=lang:eng"));
assertEquals(Format.NO_VALUE,
DashManifestParser.parseCea708AccessibilityChannel("Wrong format"));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea708AccessibilityChannel(
buildCea708AccessibilityDescriptors(null)));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea708AccessibilityChannel(
buildCea708AccessibilityDescriptors("")));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea708AccessibilityChannel(
buildCea708AccessibilityDescriptors("0=lang:eng")));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea708AccessibilityChannel(
buildCea708AccessibilityDescriptors("64=lang:eng")));
assertEquals(Format.NO_VALUE, DashManifestParser.parseCea708AccessibilityChannel(
buildCea708AccessibilityDescriptors("Wrong format")));
}
private static List<SchemeValuePair> buildCea608AccessibilityDescriptors(String value) {
return Collections.singletonList(new SchemeValuePair("urn:scte:dash:cc:cea-608:2015", value));
}
private static List<SchemeValuePair> buildCea708AccessibilityDescriptors(String value) {
return Collections.singletonList(new SchemeValuePair("urn:scte:dash:cc:cea-708:2015", value));
}
}

View File

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

View File

@ -20,9 +20,9 @@ import android.test.InstrumentationTestCase;
import android.test.MoreAsserts;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.testutil.FakeDataSource;
import com.google.android.exoplayer2.testutil.FakeDataSource.Builder;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.FileDataSource;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
@ -119,9 +119,22 @@ public class CacheDataSourceTest extends InstrumentationTestCase {
C.LENGTH_UNSET, KEY_2)));
}
public void testIgnoreCacheForUnsetLengthRequests() throws Exception {
CacheDataSource cacheDataSource = createCacheDataSource(false, true,
CacheDataSource.FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS);
assertReadData(cacheDataSource, true, 0, C.LENGTH_UNSET);
MoreAsserts.assertEmpty(simpleCache.getKeys());
}
public void testReadOnlyCache() throws Exception {
CacheDataSource cacheDataSource = createCacheDataSource(false, false, 0, null);
assertReadDataContentLength(cacheDataSource, false, false);
assertEquals(0, cacheDir.list().length);
}
private void assertCacheAndRead(boolean unboundedRequest, boolean simulateUnknownLength)
throws IOException {
// Read all data from upstream and cache
// Read all data from upstream and write to cache
CacheDataSource cacheDataSource = createCacheDataSource(false, simulateUnknownLength);
assertReadDataContentLength(cacheDataSource, unboundedRequest, simulateUnknownLength);
@ -171,15 +184,27 @@ public class CacheDataSourceTest extends InstrumentationTestCase {
private CacheDataSource createCacheDataSource(boolean setReadException,
boolean simulateUnknownLength) {
Builder builder = new Builder();
return createCacheDataSource(setReadException, simulateUnknownLength,
CacheDataSource.FLAG_BLOCK_ON_CACHE);
}
private CacheDataSource createCacheDataSource(boolean setReadException,
boolean simulateUnknownLength, @CacheDataSource.Flags int flags) {
return createCacheDataSource(setReadException, simulateUnknownLength, flags,
new CacheDataSink(simpleCache, MAX_CACHE_FILE_SIZE));
}
private CacheDataSource createCacheDataSource(boolean setReadException,
boolean simulateUnknownLength, @CacheDataSource.Flags int flags,
CacheDataSink cacheWriteDataSink) {
FakeDataSource.Builder builder = new FakeDataSource.Builder();
if (setReadException) {
builder.appendReadError(new IOException("Shouldn't read from upstream"));
}
builder.setSimulateUnknownLength(simulateUnknownLength);
builder.appendReadData(TEST_DATA);
FakeDataSource upstream = builder.build();
return new CacheDataSource(simpleCache, upstream, CacheDataSource.FLAG_BLOCK_ON_CACHE,
MAX_CACHE_FILE_SIZE);
FakeDataSource upstream =
builder.setSimulateUnknownLength(simulateUnknownLength).appendReadData(TEST_DATA).build();
return new CacheDataSource(simpleCache, upstream, new FileDataSource(), cacheWriteDataSink,
flags, null);
}
}

View File

@ -515,7 +515,13 @@ public final class C {
* The stereo mode for 360/3D/VR videos.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, STEREO_MODE_MONO, STEREO_MODE_TOP_BOTTOM, STEREO_MODE_LEFT_RIGHT})
@IntDef({
Format.NO_VALUE,
STEREO_MODE_MONO,
STEREO_MODE_TOP_BOTTOM,
STEREO_MODE_LEFT_RIGHT,
STEREO_MODE_STEREO_MESH
})
public @interface StereoMode {}
/**
* Indicates Monoscopic stereo layout, used with 360/3D/VR videos.
@ -529,6 +535,11 @@ public final class C {
* Indicates Left-Right stereo layout, used with 360/3D/VR videos.
*/
public static final int STEREO_MODE_LEFT_RIGHT = 2;
/**
* Indicates a stereo layout where the left and right eyes have separate meshes,
* used with 360/3D/VR videos.
*/
public static final int STEREO_MODE_STEREO_MESH = 3;
/**
* Converts a time in microseconds to the corresponding time in milliseconds, preserving

View File

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

View File

@ -23,7 +23,7 @@ public interface ExoPlayerLibraryInfo {
/**
* The version of the library, expressed as a string.
*/
String VERSION = "2.1.1";
String VERSION = "2.2.0";
/**
* The version of the library, expressed as an integer.
@ -32,7 +32,7 @@ public interface ExoPlayerLibraryInfo {
* corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding
* integer version 123045006 (123-045-006).
*/
int VERSION_INT = 2001001;
int VERSION_INT = 2002000;
/**
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}

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

View File

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

View File

@ -25,7 +25,6 @@ import android.os.ConditionVariable;
import android.os.SystemClock;
import android.util.Log;
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.MimeTypes;
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
* {@link #configure(String, int, int, int, int)}.
* <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 {
@ -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}.
*/
@ -120,13 +136,15 @@ public final class AudioTrack {
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;
/**
* @param errorCode An error value returned from
* {@link android.media.AudioTrack#write(byte[], int, int)}.
* @param errorCode The error value returned from
* {@link android.media.AudioTrack#write(byte[], int, int)} or
* {@link android.media.AudioTrack#write(ByteBuffer, int, int)}.
*/
public WriteException(int 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
* than this amount.
*
* <p>This is a fail safe that should not be required on correctly functioning devices.
* <p>
* 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;
/**
* AudioTrack latencies are deemed impossibly large if they are greater than this amount.
*
* <p>This is a fail safe that should not be required on correctly functioning devices.
* <p>
* 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;
@ -251,6 +269,7 @@ public final class AudioTrack {
public static boolean failOnSpuriousAudioTimestamp = false;
private final AudioCapabilities audioCapabilities;
private final BufferProcessor[] bufferProcessors;
private final Listener listener;
private final ConditionVariable releasingConditionVariable;
private final long[] playheadOffsets;
@ -264,12 +283,12 @@ public final class AudioTrack {
private android.media.AudioTrack audioTrack;
private int sampleRate;
private int channelConfig;
@C.Encoding
private int encoding;
@C.Encoding
private int outputEncoding;
@C.StreamType
private int streamType;
@C.Encoding
private int sourceEncoding;
@C.Encoding
private int targetEncoding;
private boolean passthrough;
private int pcmFrameSize;
private int bufferSize;
@ -295,12 +314,10 @@ public final class AudioTrack {
private long latencyUs;
private float volume;
private byte[] temporaryBuffer;
private int temporaryBufferOffset;
private ByteBuffer currentSourceBuffer;
private ByteBuffer resampledBuffer;
private boolean useResampledBuffer;
private ByteBuffer inputBuffer;
private ByteBuffer outputBuffer;
private byte[] preV21OutputBuffer;
private int preV21OutputBufferOffset;
private boolean playing;
private int audioSessionId;
@ -309,11 +326,18 @@ public final class AudioTrack {
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.
*/
public AudioTrack(AudioCapabilities audioCapabilities, Listener listener) {
public AudioTrack(AudioCapabilities audioCapabilities, BufferProcessor[] bufferProcessors,
Listener listener) {
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;
releasingConditionVariable = new ConditionVariable(true);
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.
currentPositionUs = audioTrackUtil.getPlaybackHeadPositionUs() + startMediaTimeUs;
} 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
// prevent jitter in the reported positions.
currentPositionUs = systemClockUs + smoothedPlayheadOffsetUs + startMediaTimeUs;
@ -410,9 +434,23 @@ public final class AudioTrack {
* {@link C#ENCODING_PCM_32BIT}.
* @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to infer a
* suitable buffer size automatically.
* @throws ConfigurationException If an error occurs configuring the track.
*/
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;
switch (channelCount) {
case 1:
@ -440,7 +478,7 @@ public final class AudioTrack {
channelConfig = C.CHANNEL_OUT_7POINT1_SURROUND;
break;
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.
@ -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.
// (See [Internal: b/34268671].)
if (Util.SDK_INT <= 25 && "fugu".equals(Util.DEVICE) && passthrough && channelCount == 1) {
channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
}
@C.Encoding int sourceEncoding;
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
if (isInitialized() && this.encoding == encoding && this.sampleRate == sampleRate
&& this.channelConfig == channelConfig) {
// We already have an audio track with the correct sample rate, channel config and encoding.
return;
@ -484,28 +510,28 @@ public final class AudioTrack {
reset();
this.sourceEncoding = sourceEncoding;
this.encoding = encoding;
this.passthrough = passthrough;
this.sampleRate = sampleRate;
this.channelConfig = channelConfig;
targetEncoding = passthrough ? sourceEncoding : C.ENCODING_PCM_16BIT;
pcmFrameSize = 2 * channelCount; // 2 bytes per 16-bit sample * number of channels.
outputEncoding = passthrough ? encoding : C.ENCODING_PCM_16BIT;
if (specifiedBufferSize != 0) {
bufferSize = specifiedBufferSize;
} else if (passthrough) {
// TODO: Set the minimum buffer size using getMinBufferSize when it takes the encoding into
// 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.
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.
bufferSize = (int) (PASSTHROUGH_BUFFER_DURATION_US * 192 * 1024 / C.MICROS_PER_SECOND);
}
} else {
int minBufferSize =
android.media.AudioTrack.getMinBufferSize(sampleRate, channelConfig, targetEncoding);
android.media.AudioTrack.getMinBufferSize(sampleRate, channelConfig, outputEncoding);
Assertions.checkState(minBufferSize != ERROR_BAD_VALUE);
int multipliedBufferSize = minBufferSize * BUFFER_MULTIPLICATION_FACTOR;
int minAppBufferSize = (int) durationUsToFrames(MIN_BUFFER_DURATION_US) * pcmFrameSize;
@ -527,15 +553,15 @@ public final class AudioTrack {
releasingConditionVariable.block();
if (tunneling) {
audioTrack = createHwAvSyncAudioTrackV21(sampleRate, channelConfig, targetEncoding,
audioTrack = createHwAvSyncAudioTrackV21(sampleRate, channelConfig, outputEncoding,
bufferSize, audioSessionId);
} else if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) {
audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig,
targetEncoding, bufferSize, MODE_STREAM);
outputEncoding, bufferSize, MODE_STREAM);
} else {
// Re-attach to the same audio session.
audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig,
targetEncoding, bufferSize, MODE_STREAM, audioSessionId);
outputEncoding, bufferSize, MODE_STREAM, audioSessionId);
}
checkAudioTrackInitialized();
@ -607,8 +633,10 @@ public final class AudioTrack {
* @throws InitializationException If an error occurs initializing the track.
* @throws WriteException If an error occurs writing the audio data.
*/
@SuppressWarnings("ReferenceEquality")
public boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs)
throws InitializationException, WriteException {
Assertions.checkArgument(inputBuffer == null || buffer == inputBuffer);
if (!isInitialized()) {
initialize();
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()) {
// An AC-3 audio track continues to play data written while it is paused. Stop writing so its
// buffer empties. See [Internal: b/18899620].
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;
}
@ -648,27 +662,25 @@ public final class AudioTrack {
}
}
if (isNewSourceBuffer) {
// We're seeing this buffer for the first time.
boolean hadData = hasData;
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.
currentSourceBuffer = null;
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 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) {
startMediaTimeUs = Math.max(0, presentationTimeUs);
startMediaTimeState = START_IN_SYNC;
@ -690,21 +702,35 @@ public final class AudioTrack {
listener.onPositionDiscontinuity();
}
}
if (Util.SDK_INT < 21) {
// Copy {@code buffer} into {@code temporaryBuffer}.
int bytesRemaining = buffer.remaining();
if (temporaryBuffer == null || temporaryBuffer.length < bytesRemaining) {
temporaryBuffer = new byte[bytesRemaining];
inputBuffer = buffer;
if (!passthrough) {
for (BufferProcessor bufferProcessor : bufferProcessors) {
buffer = bufferProcessor.handleBuffer(buffer);
}
int originalPosition = buffer.position();
buffer.get(temporaryBuffer, 0, bytesRemaining);
buffer.position(originalPosition);
temporaryBufferOffset = 0;
}
outputBuffer = buffer;
if (Util.SDK_INT < 21) {
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;
int bytesRemaining = buffer.remaining();
if (writeOutputBuffer(presentationTimeUs)) {
inputBuffer = null;
return true;
}
return false;
}
private boolean writeOutputBuffer(long presentationTimeUs) throws WriteException {
int bytesRemaining = outputBuffer.remaining();
int bytesWritten = 0;
if (Util.SDK_INT < 21) { // passthrough == false
// 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;
if (bytesToWrite > 0) {
bytesToWrite = Math.min(bytesRemaining, bytesToWrite);
bytesWritten = audioTrack.write(temporaryBuffer, temporaryBufferOffset, bytesToWrite);
if (bytesWritten >= 0) {
temporaryBufferOffset += bytesWritten;
bytesWritten = audioTrack.write(preV21OutputBuffer, preV21OutputBufferOffset, bytesToWrite);
if (bytesWritten > 0) {
preV21OutputBufferOffset += bytesWritten;
outputBuffer.position(outputBuffer.position() + bytesWritten);
}
buffer.position(buffer.position() + bytesWritten);
}
} else if (tunneling) {
bytesWritten = writeNonBlockingWithAvSyncV21(audioTrack, outputBuffer, bytesRemaining,
presentationTimeUs);
} else {
bytesWritten = tunneling
? writeNonBlockingWithAvSyncV21(audioTrack, buffer, bytesRemaining, presentationTimeUs)
: writeNonBlockingV21(audioTrack, buffer, bytesRemaining);
bytesWritten = writeNonBlockingV21(audioTrack, outputBuffer, bytesRemaining);
}
lastFeedElapsedRealtimeMs = SystemClock.elapsedRealtime();
if (bytesWritten < 0) {
throw new WriteException(bytesWritten);
}
@ -736,7 +765,6 @@ public final class AudioTrack {
if (passthrough) {
submittedEncodedFrames += framesPerEncodedSample;
}
currentSourceBuffer = null;
return true;
}
return false;
@ -812,7 +840,7 @@ public final class AudioTrack {
* audio session id has changed. Enabling tunneling requires platform API version 21 onwards.
*
* @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) {
Assertions.checkState(Util.SDK_INT >= 21);
@ -880,8 +908,11 @@ public final class AudioTrack {
submittedPcmBytes = 0;
submittedEncodedFrames = 0;
framesPerEncodedSample = 0;
currentSourceBuffer = null;
inputBuffer = null;
avSyncHeader = null;
for (BufferProcessor bufferProcessor : bufferProcessors) {
bufferProcessor.flush();
}
bytesUntilNextAvSync = 0;
startMediaTimeState = START_NOT_SET;
latencyUs = 0;
@ -915,6 +946,9 @@ public final class AudioTrack {
public void release() {
reset();
releaseKeepSessionIdAudioTrack();
for (BufferProcessor bufferProcessor : bufferProcessors) {
bufferProcessor.release();
}
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
playing = false;
}
@ -1089,7 +1123,7 @@ public final class AudioTrack {
*/
private boolean needsPassthroughWorkarounds() {
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);
}
/**
* 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
private static int getEncodingForMimeType(String 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 audioCapabilities The audio capabilities for playback on this device. May be null if the
* default capabilities (no encoded audio passthrough support) should be assumed.
* @param bufferProcessors Optional {@link BufferProcessor}s which will process PCM audio buffers
* before they are output.
*/
public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys, Handler eventHandler,
AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities) {
AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities,
BufferProcessor... bufferProcessors) {
super(C.TRACK_TYPE_AUDIO, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys);
audioTrack = new AudioTrack(audioCapabilities, new AudioTrackListener());
audioTrack = new AudioTrack(audioCapabilities, bufferProcessors, new AudioTrackListener());
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
}
@ -183,7 +186,8 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
}
@Override
protected void configureCodec(MediaCodec codec, Format format, MediaCrypto crypto) {
protected void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format,
MediaCrypto crypto) {
if (passthroughEnabled) {
// Override the MIME type used to configure the codec if we are using a passthrough decoder.
passthroughMediaFormat = format.getFrameworkMediaFormatV16();
@ -218,14 +222,19 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
}
@Override
protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat) {
protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat)
throws ExoPlaybackException {
boolean passthrough = passthroughMediaFormat != null;
String mimeType = passthrough ? passthroughMediaFormat.getString(MediaFormat.KEY_MIME)
: MimeTypes.AUDIO_RAW;
MediaFormat format = passthrough ? passthroughMediaFormat : outputFormat;
int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
audioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding, 0);
try {
audioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding, 0);
} catch (AudioTrack.ConfigurationException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
}
/**

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
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param bufferProcessors Optional {@link BufferProcessor}s which will process PCM audio buffers
* before they are output.
*/
public SimpleDecoderAudioRenderer(Handler eventHandler,
AudioRendererEventListener eventListener) {
this(eventHandler, eventListener, null);
AudioRendererEventListener eventListener, BufferProcessor... bufferProcessors) {
this(eventHandler, eventListener, null, null, false, bufferProcessors);
}
/**
@ -133,13 +135,16 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
* begin in parallel with key acquisition. This parameter specifies whether the renderer is
* permitted to play clear regions of encrypted media files before {@code drmSessionManager}
* has obtained the keys necessary to decrypt encrypted regions of the media.
* @param bufferProcessors Optional {@link BufferProcessor}s which will process PCM audio
* buffers before they are output.
*/
public SimpleDecoderAudioRenderer(Handler eventHandler,
AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities,
DrmSessionManager<ExoMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys) {
DrmSessionManager<ExoMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys,
BufferProcessor... bufferProcessors) {
super(C.TRACK_TYPE_AUDIO);
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
audioTrack = new AudioTrack(audioCapabilities, new AudioTrackListener());
audioTrack = new AudioTrack(audioCapabilities, bufferProcessors, new AudioTrackListener());
this.drmSessionManager = drmSessionManager;
formatHolder = new FormatHolder();
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
@ -193,8 +198,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
while (drainOutputBuffer()) {}
while (feedInputBuffer()) {}
TraceUtil.endSection();
} catch (AudioTrack.InitializationException | AudioTrack.WriteException
| AudioDecoderException e) {
} catch (AudioDecoderException | AudioTrack.ConfigurationException
| AudioTrack.InitializationException | AudioTrack.WriteException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
decoderCounters.ensureUpdated();
@ -255,7 +260,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
}
private boolean drainOutputBuffer() throws ExoPlaybackException, AudioDecoderException,
AudioTrack.InitializationException, AudioTrack.WriteException {
AudioTrack.ConfigurationException, AudioTrack.InitializationException,
AudioTrack.WriteException {
if (outputBuffer == null) {
outputBuffer = decoder.dequeueOutputBuffer();
if (outputBuffer == null) {

View File

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

View File

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

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.DataSpec;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource.Factory;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.util.HashMap;
@ -57,21 +59,62 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback {
}
/**
* @deprecated Use {@link HttpMediaDrmCallback#HttpMediaDrmCallback(String, Factory)}. Request
* properties can be set by calling {@link #setKeyRequestProperty(String, String)}.
* @param defaultUrl The default license URL.
* @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
* @param keyRequestProperties Request properties to set when making key requests, or null.
*/
@Deprecated
public HttpMediaDrmCallback(String defaultUrl, HttpDataSource.Factory dataSourceFactory,
Map<String, String> keyRequestProperties) {
this.dataSourceFactory = dataSourceFactory;
this.defaultUrl = defaultUrl;
this.keyRequestProperties = keyRequestProperties;
this.keyRequestProperties = new HashMap<>();
if (keyRequestProperties != null) {
this.keyRequestProperties.putAll(keyRequestProperties);
}
}
/**
* Sets a header for key requests made by the callback.
*
* @param name The name of the header field.
* @param value The value of the field.
*/
public void setKeyRequestProperty(String name, String value) {
Assertions.checkNotNull(name);
Assertions.checkNotNull(value);
synchronized (keyRequestProperties) {
keyRequestProperties.put(name, value);
}
}
/**
* Clears a header for key requests made by the callback.
*
* @param name The name of the header field.
*/
public void clearKeyRequestProperty(String name) {
Assertions.checkNotNull(name);
synchronized (keyRequestProperties) {
keyRequestProperties.remove(name);
}
}
/**
* Clears all headers for key requests made by the callback.
*/
public void clearAllKeyRequestProperties() {
synchronized (keyRequestProperties) {
keyRequestProperties.clear();
}
}
@Override
public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws IOException {
String url = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData());
return executePost(url, new byte[0], null);
return executePost(dataSourceFactory, url, new byte[0], null);
}
@Override
@ -85,14 +128,14 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback {
if (C.PLAYREADY_UUID.equals(uuid)) {
requestProperties.putAll(PLAYREADY_KEY_REQUEST_PROPERTIES);
}
if (keyRequestProperties != null) {
synchronized (keyRequestProperties) {
requestProperties.putAll(keyRequestProperties);
}
return executePost(url, request.getData(), requestProperties);
return executePost(dataSourceFactory, url, request.getData(), requestProperties);
}
private byte[] executePost(String url, byte[] data, Map<String, String> requestProperties)
throws IOException {
private static byte[] executePost(HttpDataSource.Factory dataSourceFactory, String url,
byte[] data, Map<String, String> requestProperties) throws IOException {
HttpDataSource dataSource = dataSourceFactory.createDataSource();
if (requestProperties != null) {
for (Map.Entry<String, String> requestProperty : requestProperties.entrySet()) {

View File

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

View File

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

View File

@ -102,4 +102,5 @@ public interface Extractor {
* Releases all kept resources.
*/
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.
* <p>
* The same {@link TrackOutput} is returned if multiple calls are made with the same
* {@code trackId}.
* The same {@link TrackOutput} is returned if multiple calls are made with the same {@code id}.
*
* @param trackId A track identifier.
* @param id A track identifier.
* @param type The type of the track. Typically one of the {@link com.google.android.exoplayer2.C}
* {@code TRACK_TYPE_*} constants.
* @return The {@link TrackOutput} for the given track identifier.
*/
TrackOutput track(int trackId);
TrackOutput track(int id, int type);
/**
* Called when all tracks have been identified, meaning no new {@code trackId} values will be
* passed to {@link #track(int)}.
* passed to {@link #track(int, int)}.
*/
void endTracks();

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.extractor.flv;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
@ -183,10 +184,12 @@ public final class FlvExtractor implements Extractor, SeekMap {
boolean hasAudio = (flags & 0x04) != 0;
boolean hasVideo = (flags & 0x01) != 0;
if (hasAudio && audioReader == null) {
audioReader = new AudioTagPayloadReader(extractorOutput.track(TAG_TYPE_AUDIO));
audioReader = new AudioTagPayloadReader(
extractorOutput.track(TAG_TYPE_AUDIO, C.TRACK_TYPE_AUDIO));
}
if (hasVideo && videoReader == null) {
videoReader = new VideoTagPayloadReader(extractorOutput.track(TAG_TYPE_VIDEO));
videoReader = new VideoTagPayloadReader(
extractorOutput.track(TAG_TYPE_VIDEO, C.TRACK_TYPE_VIDEO));
}
if (metadataReader == null) {
metadataReader = new ScriptTagPayloadReader(null);

View File

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

View File

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

View File

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

View File

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

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.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom;
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.MimeTypes;
import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.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.
*/
@Retention(RetentionPolicy.SOURCE)
@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 {}
/**
* 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.
*/
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
* 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 =
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.
private final ParsableByteArray nalStartCode;
private final ParsableByteArray nalLength;
private final ParsableByteArray nalPrefix;
private final ParsableByteArray nalBuffer;
private final ParsableByteArray encryptionSignalByte;
// Adjusts sample timestamps.
@ -146,16 +153,25 @@ public final class FragmentedMp4Extractor implements Extractor {
private int sampleSize;
private int sampleBytesWritten;
private int sampleCurrentNalBytesRemaining;
private boolean processSeiNalUnitPayload;
// Extractor output.
private ExtractorOutput extractorOutput;
private TrackOutput eventMessageTrackOutput;
private TrackOutput cea608TrackOutput;
// Whether extractorOutput.seekMap has been called.
private boolean haveOutputSeekMap;
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.
*/
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 timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
* @param sideloadedTrack Sideloaded track information, in the case that the extractor
* 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,
TimestampAdjuster timestampAdjuster) {
this.sideloadedTrack = sideloadedTrack;
public FragmentedMp4Extractor(@Flags int flags, TimestampAdjuster timestampAdjuster,
Track sideloadedTrack) {
this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0);
this.timestampAdjuster = timestampAdjuster;
this.sideloadedTrack = sideloadedTrack;
atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE);
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
nalLength = new ParsableByteArray(4);
nalPrefix = new ParsableByteArray(5);
nalBuffer = new ParsableByteArray();
encryptionSignalByte = new ParsableByteArray(1);
extendedTypeScratch = new byte[16];
containerAtoms = new Stack<>();
@ -199,10 +216,10 @@ public final class FragmentedMp4Extractor implements Extractor {
public void init(ExtractorOutput output) {
extractorOutput = output;
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));
trackBundles.put(0, bundle);
maybeInitEventMessageTrack();
maybeInitExtraTracks();
extractorOutput.endTracks();
}
}
@ -410,19 +427,19 @@ public final class FragmentedMp4Extractor implements Extractor {
// We need to create the track bundles.
for (int i = 0; i < trackCount; 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);
}
maybeInitEventMessageTrack();
maybeInitExtraTracks();
extractorOutput.endTracks();
} else {
Assertions.checkState(trackBundles.size() == trackCount);
}
// Initialization of tracks and default sample values.
for (int i = 0; i < trackCount; i++) {
Track track = tracks.valueAt(i);
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() {
if ((flags & FLAG_ENABLE_EMSG_TRACK) == 0) {
return;
private void maybeInitExtraTracks() {
if ((flags & FLAG_ENABLE_EMSG_TRACK) != 0 && eventMessageTrackOutput == null) {
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) {
// 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.
byte[] nalLengthData = nalLength.data;
nalLengthData[0] = 0;
nalLengthData[1] = 0;
nalLengthData[2] = 0;
int nalUnitLengthFieldLength = track.nalUnitLengthFieldLength;
byte[] nalPrefixData = nalPrefix.data;
nalPrefixData[0] = 0;
nalPrefixData[1] = 0;
nalPrefixData[2] = 0;
int nalUnitPrefixLength = track.nalUnitLengthFieldLength + 1;
int nalUnitLengthFieldLengthDiff = 4 - track.nalUnitLengthFieldLength;
// 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
// start codes as we encounter them.
while (sampleBytesWritten < sampleSize) {
if (sampleCurrentNalBytesRemaining == 0) {
// Read the NAL length so that we know where we find the next one.
input.readFully(nalLength.data, nalUnitLengthFieldLengthDiff, nalUnitLengthFieldLength);
nalLength.setPosition(0);
sampleCurrentNalBytesRemaining = nalLength.readUnsignedIntToInt();
// Read the NAL length so that we know where we find the next one, and its type.
input.readFully(nalPrefixData, nalUnitLengthFieldLengthDiff, nalUnitPrefixLength);
nalPrefix.setPosition(0);
sampleCurrentNalBytesRemaining = nalPrefix.readUnsignedIntToInt() - 1;
// Write a start code for the current NAL unit.
nalStartCode.setPosition(0);
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;
} else {
// Write the payload of the NAL unit.
int writtenBytes = output.sampleData(input, sampleCurrentNalBytesRemaining, false);
int writtenBytes;
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;
sampleCurrentNalBytesRemaining -= writtenBytes;
}

View File

@ -344,7 +344,8 @@ public final class Mp4Extractor implements Extractor, SeekMap {
continue;
}
Mp4Track mp4Track = new Mp4Track(track, trackSampleTable, extractorOutput.track(i));
Mp4Track mp4Track = new Mp4Track(track, trackSampleTable,
extractorOutput.track(i, track.type));
// Each sample has up to three bytes of overhead for the start code that replaces its length.
// Allow ten source samples per output sample, like the platform extractor.
int maxInputSize = trackSampleTable.maximumSize + 3 * 10;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@ -44,9 +44,11 @@ import java.util.Collections;
private static final int PREFIX_SEI_NUT = 39;
private static final int SUFFIX_SEI_NUT = 40;
private final SeiReader seiReader;
private String formatId;
private TrackOutput output;
private SampleReader sampleReader;
private SeiReader seiReader;
// State that should not be reset on seek.
private boolean hasOutputFormat;
@ -66,7 +68,11 @@ import java.util.Collections;
// Scratch variables to avoid allocations.
private final ParsableByteArray seiWrapper;
public H265Reader() {
/**
* @param seiReader An SEI reader for consuming closed caption channels.
*/
public H265Reader(SeiReader seiReader) {
this.seiReader = seiReader;
prefixFlags = new boolean[3];
vps = new NalUnitTargetBuffer(VPS_NUT, 128);
sps = new NalUnitTargetBuffer(SPS_NUT, 128);
@ -90,9 +96,11 @@ import java.util.Collections;
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
idGenerator.generateNewId();
formatId = idGenerator.getFormatId();
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_VIDEO);
sampleReader = new SampleReader(output);
seiReader = new SeiReader(extractorOutput.track(idGenerator.getNextId()));
seiReader.createTracks(extractorOutput, idGenerator);
}
@Override
@ -183,7 +191,7 @@ import java.util.Collections;
sps.endNalUnit(discardPadding);
pps.endNalUnit(discardPadding);
if (vps.isCompleted() && sps.isCompleted() && pps.isCompleted()) {
output.format(parseMediaFormat(vps, sps, pps));
output.format(parseMediaFormat(formatId, vps, sps, pps));
hasOutputFormat = true;
}
}
@ -205,8 +213,8 @@ import java.util.Collections;
}
}
private static Format parseMediaFormat(NalUnitTargetBuffer vps, NalUnitTargetBuffer sps,
NalUnitTargetBuffer pps) {
private static Format parseMediaFormat(String formatId, NalUnitTargetBuffer vps,
NalUnitTargetBuffer sps, NalUnitTargetBuffer pps) {
// Build codec-specific data.
byte[] csd = new byte[vps.nalLength + sps.nalLength + pps.nalLength];
System.arraycopy(vps.nalData, 0, csd, 0, vps.nalLength);
@ -311,7 +319,7 @@ import java.util.Collections;
}
}
return Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H265, null, Format.NO_VALUE,
return Format.createVideoSampleFormat(formatId, MimeTypes.VIDEO_H265, null, Format.NO_VALUE,
Format.NO_VALUE, picWidthInLumaSamples, picHeightInLumaSamples, Format.NO_VALUE,
Collections.singletonList(csd), Format.NO_VALUE, pixelWidthHeightRatio, null);
}

View File

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

View File

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

View File

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

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.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.io.IOException;
/**

View File

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

View File

@ -17,8 +17,8 @@ package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import com.google.android.exoplayer2.util.Util;
/**

View File

@ -17,8 +17,10 @@ package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.text.cea.Cea608Decoder;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.text.cea.CeaUtil;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
@ -27,49 +29,17 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
*/
/* package */ final class SeiReader {
private final TrackOutput output;
private TrackOutput output;
public SeiReader(TrackOutput output) {
this.output = output;
output.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, null,
Format.NO_VALUE, 0, null, null));
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
idGenerator.generateNewId();
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT);
output.format(Format.createTextSampleFormat(idGenerator.getFormatId(),
MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, null, null));
}
public void consume(long pesTimeUs, ParsableByteArray seiBuffer) {
int b;
while (seiBuffer.bytesLeft() > 1 /* last byte will be rbsp_trailing_bits */) {
// Parse payload type.
int payloadType = 0;
do {
b = seiBuffer.readUnsignedByte();
payloadType += b;
} while (b == 0xFF);
// Parse payload size.
int payloadSize = 0;
do {
b = seiBuffer.readUnsignedByte();
payloadSize += b;
} while (b == 0xFF);
// Process the payload.
if (Cea608Decoder.isSeiMessageCea608(payloadType, payloadSize, seiBuffer)) {
// Ignore country_code (1) + provider_code (2) + user_identifier (4)
// + user_data_type_code (1).
seiBuffer.skipBytes(8);
// Ignore first three bits: reserved (1) + process_cc_data_flag (1) + zero_bit (1).
int ccCount = seiBuffer.readUnsignedByte() & 0x1F;
// Ignore em_data (1)
seiBuffer.skipBytes(1);
// Each data packet consists of 24 bits: marker bits (5) + cc_valid (1) + cc_type (2)
// + cc_data_1 (8) + cc_data_2 (8).
int sampleLength = ccCount * 3;
output.sampleData(seiBuffer, sampleLength);
output.sampleMetadata(pesTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleLength, 0, null);
// Ignore trailing information in SEI, if any.
seiBuffer.skipBytes(payloadSize - (10 + ccCount * 3));
} else {
seiBuffer.skipBytes(payloadSize);
}
}
CeaUtil.consume(pesTimeUs, seiBuffer, output);
}
}

View File

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

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

View File

@ -17,9 +17,9 @@ package com.google.android.exoplayer2.extractor.ts;
import android.util.SparseArray;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
/**
* Parses TS packet payload data.
@ -81,17 +81,63 @@ public interface TsPayloadReader {
*/
final class TrackIdGenerator {
private final int firstId;
private final int idIncrement;
private int generatedIdCount;
private static final int ID_UNSET = Integer.MIN_VALUE;
public TrackIdGenerator(int firstId, int idIncrement) {
this.firstId = firstId;
this.idIncrement = idIncrement;
private final String formatIdPrefix;
private final int firstTrackId;
private final int trackIdIncrement;
private int trackId;
private String formatId;
public TrackIdGenerator(int firstTrackId, int trackIdIncrement) {
this(ID_UNSET, firstTrackId, trackIdIncrement);
}
public int getNextId() {
return firstId + idIncrement * generatedIdCount++;
public TrackIdGenerator(int programNumber, int firstTrackId, int trackIdIncrement) {
this.formatIdPrefix = programNumber != ID_UNSET ? programNumber + "/" : "";
this.firstTrackId = firstTrackId;
this.trackIdIncrement = trackIdIncrement;
trackId = ID_UNSET;
}
/**
* Generates a new set of track and track format ids. Must be called before {@code get*}
* methods.
*/
public void generateNewId() {
trackId = trackId == ID_UNSET ? firstTrackId : trackId + trackIdIncrement;
formatId = formatIdPrefix + trackId;
}
/**
* Returns the last generated track id. Must be called after the first {@link #generateNewId()}
* call.
*
* @return The last generated track id.
*/
public int getTrackId() {
maybeThrowUninitializedError();
return trackId;
}
/**
* Returns the last generated format id, with the format {@code "programNumber/trackId"}. If no
* {@code programNumber} was provided, the {@code trackId} alone is used as format id. Must be
* called after the first {@link #generateNewId()} call.
*
* @return The last generated format id, with the format {@code "programNumber/trackId"}. If no
* {@code programNumber} was provided, the {@code trackId} alone is used as
* format id.
*/
public String getFormatId() {
maybeThrowUninitializedError();
return formatId;
}
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
public void init(ExtractorOutput output) {
extractorOutput = output;
trackOutput = output.track(0);
trackOutput = output.track(0, C.TRACK_TYPE_AUDIO);
wavHeader = null;
output.endTracks();
}

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.mediacodec;
import android.annotation.TargetApi;
import android.graphics.Point;
import android.media.MediaCodec;
import android.media.MediaCodecInfo.AudioCapabilities;
import android.media.MediaCodecInfo.CodecCapabilities;
@ -23,6 +24,7 @@ import android.media.MediaCodecInfo.CodecProfileLevel;
import android.media.MediaCodecInfo.VideoCapabilities;
import android.util.Log;
import android.util.Pair;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
@ -141,39 +143,6 @@ public final class MediaCodecInfo {
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.
* <p>
@ -181,7 +150,8 @@ public final class MediaCodecInfo {
*
* @param width Width 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.
*/
@TargetApi(21)
@ -195,11 +165,12 @@ public final class MediaCodecInfo {
logNoSupport("sizeAndRate.vCaps");
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
// (b/31387661). If the video is vertical and the capabilities indicate support if the width
// and height are swapped, we assume that the vertical resolution is also supported.
if (width >= height || !videoCapabilities.areSizeAndRateSupported(height, width, frameRate)) {
if (width >= height
|| !areSizeAndRateSupported(videoCapabilities, height, width, frameRate)) {
logNoSupport("sizeAndRate.support, " + width + "x" + height + "x" + frameRate);
return false;
}
@ -208,6 +179,35 @@ public final class MediaCodecInfo {
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.
* <p>
@ -279,6 +279,14 @@ public final class MediaCodecInfo {
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback);
}
@TargetApi(21)
private static boolean areSizeAndRateSupported(VideoCapabilities capabilities, int width,
int height, double frameRate) {
return frameRate == Format.NO_VALUE || frameRate <= 0
? capabilities.isSizeSupported(width, height)
: capabilities.areSizeAndRateSupported(width, height, frameRate);
}
private static boolean isTunneling(CodecCapabilities capabilities) {
return Util.SDK_INT >= 21 && isTunnelingV21(capabilities);
}

View File

@ -183,6 +183,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private boolean codecNeedsAdaptationWorkaround;
private boolean codecNeedsEosPropagationWorkaround;
private boolean codecNeedsEosFlushWorkaround;
private boolean codecNeedsEosOutputExceptionWorkaround;
private boolean codecNeedsMonoChannelCountWorkaround;
private boolean codecNeedsAdaptationWorkaroundBuffer;
private boolean shouldSkipAdaptationWorkaroundOutputBuffer;
@ -201,6 +202,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private boolean inputStreamEnded;
private boolean outputStreamEnded;
private boolean waitingForKeys;
private boolean waitingForFirstSyncFrame;
protected DecoderCounters decoderCounters;
@ -276,11 +278,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
/**
* Configures a newly created {@link MediaCodec}.
*
* @param codecInfo Information about the {@link MediaCodec} being configured.
* @param codec The {@link MediaCodec} to configure.
* @param format The format for which the codec is being configured.
* @param crypto For drm protected playbacks, a {@link MediaCrypto} to use for decryption.
* @throws DecoderQueryException If an error occurs querying {@code codecInfo}.
*/
protected abstract void configureCodec(MediaCodec codec, Format format, MediaCrypto crypto);
protected abstract void configureCodec(MediaCodecInfo codecInfo, MediaCodec codec, Format format,
MediaCrypto crypto) throws DecoderQueryException;
@SuppressWarnings("deprecation")
protected final void maybeInitCodec() throws ExoPlaybackException {
@ -338,6 +343,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecNeedsAdaptationWorkaround = codecNeedsAdaptationWorkaround(codecName);
codecNeedsEosPropagationWorkaround = codecNeedsEosPropagationWorkaround(codecName);
codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName);
codecNeedsEosOutputExceptionWorkaround = codecNeedsEosOutputExceptionWorkaround(codecName);
codecNeedsMonoChannelCountWorkaround = codecNeedsMonoChannelCountWorkaround(codecName, format);
try {
long codecInitializingTimestamp = SystemClock.elapsedRealtime();
@ -345,7 +351,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codec = MediaCodec.createByCodecName(codecName);
TraceUtil.endSection();
TraceUtil.beginSection("configureCodec");
configureCodec(codec, format, mediaCrypto);
configureCodec(decoderInfo, codec, format, mediaCrypto);
TraceUtil.endSection();
TraceUtil.beginSection("startCodec");
codec.start();
@ -363,6 +369,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
? (SystemClock.elapsedRealtime() + MAX_CODEC_HOTSWAP_TIME_MS) : C.TIME_UNSET;
inputIndex = C.INDEX_UNSET;
outputIndex = C.INDEX_UNSET;
waitingForFirstSyncFrame = true;
decoderCounters.decoderInitCount++;
}
@ -501,13 +508,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecHotswapDeadlineMs = C.TIME_UNSET;
inputIndex = C.INDEX_UNSET;
outputIndex = C.INDEX_UNSET;
waitingForFirstSyncFrame = true;
waitingForKeys = false;
shouldSkipOutputBuffer = false;
decodeOnlyPresentationTimestamps.clear();
codecNeedsAdaptationWorkaroundBuffer = false;
shouldSkipAdaptationWorkaroundOutputBuffer = false;
if (codecNeedsFlushWorkaround || (codecNeedsEosFlushWorkaround && codecReceivedEos)) {
// Workaround framework bugs. See [Internal: b/8347958, b/8578467, b/8543366, b/23361053].
releaseCodec();
maybeInitCodec();
} else if (codecReinitializationState != REINITIALIZATION_STATE_NONE) {
@ -630,6 +637,16 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
}
return false;
}
if (waitingForFirstSyncFrame && !buffer.isKeyFrame()) {
buffer.clear();
if (codecReconfigurationState == RECONFIGURATION_STATE_QUEUE_PENDING) {
// The buffer we just cleared contained reconfiguration data. We need to re-write this
// data into a subsequent buffer (if there is one).
codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
}
return true;
}
waitingForFirstSyncFrame = false;
boolean bufferEncrypted = buffer.isEncrypted();
waitingForKeys = shouldWaitForKeys(bufferEncrypted);
if (waitingForKeys) {
@ -763,8 +780,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
*
* @param codec The {@link MediaCodec} instance.
* @param outputFormat The new output format.
* @throws ExoPlaybackException Thrown if an error occurs handling the new output format.
*/
protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat) {
protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat)
throws ExoPlaybackException {
// Do nothing.
}
@ -849,7 +868,22 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private boolean drainOutputBuffer(long positionUs, long elapsedRealtimeUs)
throws ExoPlaybackException {
if (outputIndex < 0) {
outputIndex = codec.dequeueOutputBuffer(outputBufferInfo, getDequeueOutputBufferTimeoutUs());
if (codecNeedsEosOutputExceptionWorkaround && codecReceivedEos) {
try {
outputIndex = codec.dequeueOutputBuffer(outputBufferInfo,
getDequeueOutputBufferTimeoutUs());
} catch (IllegalStateException e) {
processEndOfStream();
if (outputStreamEnded) {
// Release the codec, as it's in an error state.
releaseCodec();
}
return false;
}
} else {
outputIndex = codec.dequeueOutputBuffer(outputBufferInfo,
getDequeueOutputBufferTimeoutUs());
}
if (outputIndex >= 0) {
// We've dequeued a buffer.
if (shouldSkipAdaptationWorkaroundOutputBuffer) {
@ -888,9 +922,27 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
}
}
if (processOutputBuffer(positionUs, elapsedRealtimeUs, codec, outputBuffers[outputIndex],
outputIndex, outputBufferInfo.flags, outputBufferInfo.presentationTimeUs,
shouldSkipOutputBuffer)) {
boolean processedOutputBuffer;
if (codecNeedsEosOutputExceptionWorkaround && codecReceivedEos) {
try {
processedOutputBuffer = processOutputBuffer(positionUs, elapsedRealtimeUs, codec,
outputBuffers[outputIndex], outputIndex, outputBufferInfo.flags,
outputBufferInfo.presentationTimeUs, shouldSkipOutputBuffer);
} catch (IllegalStateException e) {
processEndOfStream();
if (outputStreamEnded) {
// Release the codec, as it's in an error state.
releaseCodec();
}
return false;
}
} else {
processedOutputBuffer = processOutputBuffer(positionUs, elapsedRealtimeUs, codec,
outputBuffers[outputIndex], outputIndex, outputBufferInfo.flags,
outputBufferInfo.presentationTimeUs, shouldSkipOutputBuffer);
}
if (processedOutputBuffer) {
onProcessedOutputBuffer(outputBufferInfo.presentationTimeUs);
outputIndex = C.INDEX_UNSET;
return true;
@ -902,7 +954,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
/**
* Processes a new output format.
*/
private void processOutputFormat() {
private void processOutputFormat() throws ExoPlaybackException {
MediaFormat format = codec.getOutputFormat();
if (codecNeedsAdaptationWorkaround
&& format.getInteger(MediaFormat.KEY_WIDTH) == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT
@ -992,6 +1044,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* <p>
* If true is returned, the renderer will work around the issue by releasing the decoder and
* instantiating a new one rather than flushing the current instance.
* <p>
* See [Internal: b/8347958, b/8543366].
*
* @param name The name of the decoder.
* @return True if the decoder is known to fail when flushed.
@ -1061,6 +1115,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* <p>
* If true is returned, the renderer will work around the issue by instantiating a new decoder
* when this case occurs.
* <p>
* See [Internal: b/8578467, b/23361053].
*
* @param name The name of the decoder.
* @return True if the decoder is known to behave incorrectly if flushed after receiving an input
@ -1073,6 +1129,21 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|| "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
* 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 identifier;
public final byte[] commandBytes;
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.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.nio.ByteBuffer;
/**
@ -37,6 +38,8 @@ public final class SpliceInfoDecoder implements MetadataDecoder {
private final ParsableByteArray sectionData;
private final ParsableBitArray sectionHeader;
private TimestampAdjuster timestampAdjuster;
public SpliceInfoDecoder() {
sectionData = new ParsableByteArray();
sectionHeader = new ParsableBitArray();
@ -44,6 +47,13 @@ public final class SpliceInfoDecoder implements MetadataDecoder {
@Override
public Metadata decode(MetadataInputBuffer inputBuffer) throws MetadataDecoderException {
// Internal timestamps adjustment.
if (timestampAdjuster == null
|| inputBuffer.subsampleOffsetUs != timestampAdjuster.getTimestampOffsetUs()) {
timestampAdjuster = new TimestampAdjuster(inputBuffer.timeUs);
timestampAdjuster.adjustSampleTimestamp(inputBuffer.timeUs - inputBuffer.subsampleOffsetUs);
}
ByteBuffer buffer = inputBuffer.data;
byte[] data = buffer.array();
int size = buffer.limit();
@ -69,10 +79,11 @@ public final class SpliceInfoDecoder implements MetadataDecoder {
command = SpliceScheduleCommand.parseFromSection(sectionData);
break;
case TYPE_SPLICE_INSERT:
command = SpliceInsertCommand.parseFromSection(sectionData, ptsAdjustment);
command = SpliceInsertCommand.parseFromSection(sectionData, ptsAdjustment,
timestampAdjuster);
break;
case TYPE_TIME_SIGNAL:
command = TimeSignalCommand.parseFromSection(sectionData, ptsAdjustment);
command = TimeSignalCommand.parseFromSection(sectionData, ptsAdjustment, timestampAdjuster);
break;
case TYPE_PRIVATE_COMMAND:
command = PrivateCommand.parseFromSection(sectionData, spliceCommandLength, ptsAdjustment);

View File

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

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.metadata.scte35;
import android.os.Parcel;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
/**
* Represents a time signal command as defined in SCTE35, Section 9.3.4.
@ -25,14 +26,18 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
public final class TimeSignalCommand extends SpliceCommand {
public final long ptsTime;
public final long playbackPositionUs;
private TimeSignalCommand(long ptsTime) {
private TimeSignalCommand(long ptsTime, long playbackPositionUs) {
this.ptsTime = ptsTime;
this.playbackPositionUs = playbackPositionUs;
}
/* package */ static TimeSignalCommand parseFromSection(ParsableByteArray sectionData,
long ptsAdjustment) {
return new TimeSignalCommand(parseSpliceTime(sectionData, ptsAdjustment));
long ptsAdjustment, TimestampAdjuster timestampAdjuster) {
long ptsTime = parseSpliceTime(sectionData, ptsAdjustment);
long playbackPositionUs = timestampAdjuster.adjustTsTimestamp(ptsTime);
return new TimeSignalCommand(ptsTime, playbackPositionUs);
}
/**
@ -61,6 +66,7 @@ public final class TimeSignalCommand extends SpliceCommand {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(ptsTime);
dest.writeLong(playbackPositionUs);
}
public static final Creator<TimeSignalCommand> CREATOR =
@ -68,7 +74,7 @@ public final class TimeSignalCommand extends SpliceCommand {
@Override
public TimeSignalCommand createFromParcel(Parcel in) {
return new TimeSignalCommand(in.readLong());
return new TimeSignalCommand(in.readLong(), in.readLong());
}
@Override

View File

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

View File

@ -19,7 +19,6 @@ import android.net.Uri;
import android.os.Handler;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.Extractor;
@ -27,7 +26,6 @@ import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
/**
@ -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.
*/

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

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.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper.SeekMapOutput;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Assertions;
@ -31,12 +29,11 @@ import java.io.IOException;
/**
* A {@link BaseMediaChunk} that uses an {@link Extractor} to decode sample data.
*/
public class ContainerMediaChunk extends BaseMediaChunk implements SeekMapOutput {
public class ContainerMediaChunk extends BaseMediaChunk {
private final int chunkCount;
private final long sampleOffsetUs;
private final ChunkExtractorWrapper extractorWrapper;
private final Format sampleFormat;
private volatile int bytesLoaded;
private volatile boolean loadCanceled;
@ -56,19 +53,15 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SeekMapOutput
* underlying media are being merged into a single load.
* @param sampleOffsetUs An offset to add to the sample timestamps parsed by the extractor.
* @param extractorWrapper A wrapped extractor to use for parsing the data.
* @param sampleFormat The {@link Format} of the samples in the chunk, if known. May be null if
* the data is known to define its own sample format.
*/
public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat,
int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs,
int chunkIndex, int chunkCount, long sampleOffsetUs, ChunkExtractorWrapper extractorWrapper,
Format sampleFormat) {
int chunkIndex, int chunkCount, long sampleOffsetUs, ChunkExtractorWrapper extractorWrapper) {
super(dataSource, dataSpec, trackFormat, trackSelectionReason, trackSelectionData, startTimeUs,
endTimeUs, chunkIndex);
this.chunkCount = chunkCount;
this.sampleOffsetUs = sampleOffsetUs;
this.extractorWrapper = extractorWrapper;
this.sampleFormat = sampleFormat;
}
@Override
@ -86,13 +79,6 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SeekMapOutput
return bytesLoaded;
}
// SeekMapOutput implementation.
@Override
public final void seekMap(SeekMap seekMap) {
// Do nothing.
}
// Loadable implementation.
@Override
@ -116,8 +102,8 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SeekMapOutput
if (bytesLoaded == 0) {
// Set the target to ourselves.
DefaultTrackOutput trackOutput = getTrackOutput();
trackOutput.formatWithOffset(sampleFormat, sampleOffsetUs);
extractorWrapper.init(this, trackOutput);
trackOutput.setSampleOffsetUs(sampleOffsetUs);
extractorWrapper.init(trackOutput);
}
// Load and decode the sample data.
try {

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