mirror of
https://github.com/samsonjs/media.git
synced 2026-04-01 10:35:48 +00:00
commit
876fa41b43
35 changed files with 914 additions and 1468 deletions
|
|
@ -42,12 +42,7 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name="com.google.android.exoplayer.demo.simple.SimplePlayerActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:label="@string/application_name"
|
||||
android:theme="@style/PlayerTheme"/>
|
||||
|
||||
<activity android:name="com.google.android.exoplayer.demo.full.FullPlayerActivity"
|
||||
<activity android:name="com.google.android.exoplayer.demo.PlayerActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:label="@string/application_name"
|
||||
android:theme="@style/PlayerTheme"/>
|
||||
|
|
|
|||
|
|
@ -44,16 +44,11 @@ public class DemoUtil {
|
|||
|
||||
public static final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL);
|
||||
|
||||
public static final String CONTENT_TYPE_EXTRA = "content_type";
|
||||
public static final String CONTENT_ID_EXTRA = "content_id";
|
||||
|
||||
public static final int TYPE_DASH = 0;
|
||||
public static final int TYPE_SS = 1;
|
||||
public static final int TYPE_OTHER = 2;
|
||||
public static final int TYPE_HLS = 3;
|
||||
|
||||
public static final boolean EXPOSE_EXPERIMENTAL_FEATURES = false;
|
||||
|
||||
private static final CookieManager defaultCookieManager;
|
||||
|
||||
static {
|
||||
|
|
|
|||
|
|
@ -13,12 +13,12 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.demo.full;
|
||||
package com.google.android.exoplayer.demo;
|
||||
|
||||
import com.google.android.exoplayer.ExoPlayer;
|
||||
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
|
||||
import com.google.android.exoplayer.audio.AudioTrack;
|
||||
import com.google.android.exoplayer.demo.full.player.DemoPlayer;
|
||||
import com.google.android.exoplayer.demo.player.DemoPlayer;
|
||||
import com.google.android.exoplayer.util.VerboseLogUtil;
|
||||
|
||||
import android.media.MediaCodec.CryptoException;
|
||||
|
|
@ -63,8 +63,8 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
|
|||
|
||||
@Override
|
||||
public void onStateChanged(boolean playWhenReady, int state) {
|
||||
Log.d(TAG, "state [" + getSessionTimeString() + ", " + playWhenReady + ", " +
|
||||
getStateString(state) + "]");
|
||||
Log.d(TAG, "state [" + getSessionTimeString() + ", " + playWhenReady + ", "
|
||||
+ getStateString(state) + "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -81,8 +81,8 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
|
|||
|
||||
@Override
|
||||
public void onBandwidthSample(int elapsedMs, long bytes, long bitrateEstimate) {
|
||||
Log.d(TAG, "bandwidth [" + getSessionTimeString() + ", " + bytes +
|
||||
", " + getTimeString(elapsedMs) + ", " + bitrateEstimate + "]");
|
||||
Log.d(TAG, "bandwidth [" + getSessionTimeString() + ", " + bytes + ", "
|
||||
+ getTimeString(elapsedMs) + ", " + bitrateEstimate + "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -104,21 +104,21 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
|
|||
public void onLoadCompleted(int sourceId, long bytesLoaded) {
|
||||
if (VerboseLogUtil.isTagEnabled(TAG)) {
|
||||
long downloadTime = SystemClock.elapsedRealtime() - loadStartTimeMs[sourceId];
|
||||
Log.v(TAG, "loadEnd [" + getSessionTimeString() + ", " + sourceId + ", " +
|
||||
downloadTime + "]");
|
||||
Log.v(TAG, "loadEnd [" + getSessionTimeString() + ", " + sourceId + ", " + downloadTime
|
||||
+ "]");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoFormatEnabled(String formatId, int trigger, int mediaTimeMs) {
|
||||
Log.d(TAG, "videoFormat [" + getSessionTimeString() + ", " + formatId + ", " +
|
||||
Integer.toString(trigger) + "]");
|
||||
Log.d(TAG, "videoFormat [" + getSessionTimeString() + ", " + formatId + ", "
|
||||
+ Integer.toString(trigger) + "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioFormatEnabled(String formatId, int trigger, int mediaTimeMs) {
|
||||
Log.d(TAG, "audioFormat [" + getSessionTimeString() + ", " + formatId + ", " +
|
||||
Integer.toString(trigger) + "]");
|
||||
Log.d(TAG, "audioFormat [" + getSessionTimeString() + ", " + formatId + ", "
|
||||
+ Integer.toString(trigger) + "]");
|
||||
}
|
||||
|
||||
// DemoPlayer.InternalErrorListener
|
||||
|
|
@ -13,21 +13,19 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.demo.full;
|
||||
package com.google.android.exoplayer.demo;
|
||||
|
||||
import com.google.android.exoplayer.ExoPlayer;
|
||||
import com.google.android.exoplayer.VideoSurfaceView;
|
||||
import com.google.android.exoplayer.audio.AudioCapabilities;
|
||||
import com.google.android.exoplayer.audio.AudioCapabilitiesReceiver;
|
||||
import com.google.android.exoplayer.demo.DemoUtil;
|
||||
import com.google.android.exoplayer.demo.R;
|
||||
import com.google.android.exoplayer.demo.full.player.DashRendererBuilder;
|
||||
import com.google.android.exoplayer.demo.full.player.DefaultRendererBuilder;
|
||||
import com.google.android.exoplayer.demo.full.player.DemoPlayer;
|
||||
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder;
|
||||
import com.google.android.exoplayer.demo.full.player.HlsRendererBuilder;
|
||||
import com.google.android.exoplayer.demo.full.player.SmoothStreamingRendererBuilder;
|
||||
import com.google.android.exoplayer.demo.full.player.UnsupportedDrmException;
|
||||
import com.google.android.exoplayer.demo.player.DashRendererBuilder;
|
||||
import com.google.android.exoplayer.demo.player.DefaultRendererBuilder;
|
||||
import com.google.android.exoplayer.demo.player.DemoPlayer;
|
||||
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
|
||||
import com.google.android.exoplayer.demo.player.HlsRendererBuilder;
|
||||
import com.google.android.exoplayer.demo.player.SmoothStreamingRendererBuilder;
|
||||
import com.google.android.exoplayer.demo.player.UnsupportedDrmException;
|
||||
import com.google.android.exoplayer.metadata.TxxxMetadata;
|
||||
import com.google.android.exoplayer.text.CaptionStyleCompat;
|
||||
import com.google.android.exoplayer.text.SubtitleView;
|
||||
|
|
@ -65,11 +63,14 @@ import java.util.Map;
|
|||
/**
|
||||
* An activity that plays media using {@link DemoPlayer}.
|
||||
*/
|
||||
public class FullPlayerActivity extends Activity implements SurfaceHolder.Callback, OnClickListener,
|
||||
public class PlayerActivity extends Activity implements SurfaceHolder.Callback, OnClickListener,
|
||||
DemoPlayer.Listener, DemoPlayer.TextListener, DemoPlayer.Id3MetadataListener,
|
||||
AudioCapabilitiesReceiver.Listener {
|
||||
|
||||
private static final String TAG = "FullPlayerActivity";
|
||||
public static final String CONTENT_TYPE_EXTRA = "content_type";
|
||||
public static final String CONTENT_ID_EXTRA = "content_id";
|
||||
|
||||
private static final String TAG = "PlayerActivity";
|
||||
|
||||
private static final float CAPTION_LINE_HEIGHT_RATIO = 0.0533f;
|
||||
private static final int MENU_GROUP_TRACKS = 1;
|
||||
|
|
@ -110,10 +111,10 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
|
|||
|
||||
Intent intent = getIntent();
|
||||
contentUri = intent.getData();
|
||||
contentType = intent.getIntExtra(DemoUtil.CONTENT_TYPE_EXTRA, DemoUtil.TYPE_OTHER);
|
||||
contentId = intent.getStringExtra(DemoUtil.CONTENT_ID_EXTRA);
|
||||
contentType = intent.getIntExtra(CONTENT_TYPE_EXTRA, DemoUtil.TYPE_OTHER);
|
||||
contentId = intent.getStringExtra(CONTENT_ID_EXTRA);
|
||||
|
||||
setContentView(R.layout.player_activity_full);
|
||||
setContentView(R.layout.player_activity);
|
||||
View root = findViewById(R.id.root);
|
||||
root.setOnTouchListener(new OnTouchListener() {
|
||||
@Override
|
||||
|
|
@ -15,15 +15,17 @@
|
|||
*/
|
||||
package com.google.android.exoplayer.demo;
|
||||
|
||||
import com.google.android.exoplayer.MediaCodecUtil;
|
||||
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
|
||||
import com.google.android.exoplayer.demo.Samples.Sample;
|
||||
import com.google.android.exoplayer.demo.full.FullPlayerActivity;
|
||||
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
|
@ -38,6 +40,8 @@ import android.widget.TextView;
|
|||
*/
|
||||
public class SampleChooserActivity extends Activity {
|
||||
|
||||
private static final String TAG = "SampleChooserActivity";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
|
@ -46,21 +50,25 @@ public class SampleChooserActivity extends Activity {
|
|||
ListView sampleList = (ListView) findViewById(R.id.sample_list);
|
||||
final SampleAdapter sampleAdapter = new SampleAdapter(this);
|
||||
|
||||
sampleAdapter.add(new Header("Simple player"));
|
||||
sampleAdapter.addAll((Object[]) Samples.SIMPLE);
|
||||
sampleAdapter.add(new Header("YouTube DASH"));
|
||||
sampleAdapter.addAll((Object[]) Samples.YOUTUBE_DASH_MP4);
|
||||
sampleAdapter.add(new Header("Widevine GTS DASH"));
|
||||
sampleAdapter.addAll((Object[]) Samples.WIDEVINE_GTS);
|
||||
sampleAdapter.add(new Header("SmoothStreaming"));
|
||||
sampleAdapter.addAll((Object[]) Samples.SMOOTHSTREAMING);
|
||||
sampleAdapter.add(new Header("Misc"));
|
||||
sampleAdapter.addAll((Object[]) Samples.MISC);
|
||||
sampleAdapter.add(new Header("HLS"));
|
||||
sampleAdapter.addAll((Object[]) Samples.HLS);
|
||||
if (DemoUtil.EXPOSE_EXPERIMENTAL_FEATURES) {
|
||||
sampleAdapter.add(new Header("YouTube WebM DASH (Experimental)"));
|
||||
sampleAdapter.addAll((Object[]) Samples.YOUTUBE_DASH_WEBM);
|
||||
sampleAdapter.add(new Header("Misc"));
|
||||
sampleAdapter.addAll((Object[]) Samples.MISC);
|
||||
|
||||
// Add WebM samples if the device has a VP9 decoder.
|
||||
try {
|
||||
if (MediaCodecUtil.getDecoderInfo(MimeTypes.VIDEO_VP9, false) != null) {
|
||||
sampleAdapter.add(new Header("YouTube WebM DASH (Experimental)"));
|
||||
sampleAdapter.addAll((Object[]) Samples.YOUTUBE_DASH_WEBM);
|
||||
}
|
||||
} catch (DecoderQueryException e) {
|
||||
Log.e(TAG, "Failed to query vp9 decoder", e);
|
||||
}
|
||||
|
||||
sampleList.setAdapter(sampleAdapter);
|
||||
|
|
@ -76,12 +84,10 @@ public class SampleChooserActivity extends Activity {
|
|||
}
|
||||
|
||||
private void onSampleSelected(Sample sample) {
|
||||
Class<?> playerActivityClass = sample.fullPlayer ? FullPlayerActivity.class
|
||||
: SimplePlayerActivity.class;
|
||||
Intent mpdIntent = new Intent(this, playerActivityClass)
|
||||
Intent mpdIntent = new Intent(this, PlayerActivity.class)
|
||||
.setData(Uri.parse(sample.uri))
|
||||
.putExtra(DemoUtil.CONTENT_ID_EXTRA, sample.contentId)
|
||||
.putExtra(DemoUtil.CONTENT_TYPE_EXTRA, sample.type);
|
||||
.putExtra(PlayerActivity.CONTENT_ID_EXTRA, sample.contentId)
|
||||
.putExtra(PlayerActivity.CONTENT_TYPE_EXTRA, sample.type);
|
||||
startActivity(mpdIntent);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
package com.google.android.exoplayer.demo;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Holds statically defined sample definitions.
|
||||
*/
|
||||
|
|
@ -26,72 +28,53 @@ package com.google.android.exoplayer.demo;
|
|||
public final String contentId;
|
||||
public final String uri;
|
||||
public final int type;
|
||||
public final boolean fullPlayer;
|
||||
|
||||
public Sample(String name, String contentId, String uri, int type, boolean fullPlayer) {
|
||||
public Sample(String name, String uri, int type) {
|
||||
this(name, name.toLowerCase(Locale.US).replaceAll("\\s", ""), uri, type);
|
||||
}
|
||||
|
||||
public Sample(String name, String contentId, String uri, int type) {
|
||||
this.name = name;
|
||||
this.contentId = contentId;
|
||||
this.uri = uri;
|
||||
this.type = type;
|
||||
this.fullPlayer = fullPlayer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final Sample[] SIMPLE = new Sample[] {
|
||||
new Sample("Google Glass (DASH)", "bf5bb2419360daf1",
|
||||
"http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?"
|
||||
+ "as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&"
|
||||
+ "ipbits=0&expire=19000000000&signature=255F6B3C07C753C88708C07EA31B7A1A10703C8D."
|
||||
+ "2D6A28B21F921D0B245CDCF36F7EB54A2B5ABFC2&key=ik0", DemoUtil.TYPE_DASH, false),
|
||||
new Sample("Google Play (DASH)", "3aa39fa2cc27967f",
|
||||
"http://www.youtube.com/api/manifest/dash/id/3aa39fa2cc27967f/source/youtube?"
|
||||
+ "as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0&"
|
||||
+ "expire=19000000000&signature=7181C59D0252B285D593E1B61D985D5B7C98DE2A."
|
||||
+ "5B445837F55A40E0F28AACAA047982E372D177E2&key=ik0", DemoUtil.TYPE_DASH, false),
|
||||
new Sample("Super speed (SmoothStreaming)", "uid:ss:superspeed",
|
||||
"http://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism",
|
||||
DemoUtil.TYPE_SS, false),
|
||||
new Sample("Apple master playlist (HLS)", "uid:hls:applemaster",
|
||||
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/"
|
||||
+ "bipbop_4x3_variant.m3u8", DemoUtil.TYPE_HLS, false),
|
||||
new Sample("Dizzy (Misc)", "uid:misc:dizzy",
|
||||
"http://html5demos.com/assets/dizzy.mp4", DemoUtil.TYPE_OTHER, false),
|
||||
};
|
||||
|
||||
public static final Sample[] YOUTUBE_DASH_MP4 = new Sample[] {
|
||||
new Sample("Google Glass", "bf5bb2419360daf1",
|
||||
new Sample("Google Glass",
|
||||
"http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?"
|
||||
+ "as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&"
|
||||
+ "ipbits=0&expire=19000000000&signature=255F6B3C07C753C88708C07EA31B7A1A10703C8D."
|
||||
+ "2D6A28B21F921D0B245CDCF36F7EB54A2B5ABFC2&key=ik0", DemoUtil.TYPE_DASH, true),
|
||||
new Sample("Google Play", "3aa39fa2cc27967f",
|
||||
+ "2D6A28B21F921D0B245CDCF36F7EB54A2B5ABFC2&key=ik0", DemoUtil.TYPE_DASH),
|
||||
new Sample("Google Play",
|
||||
"http://www.youtube.com/api/manifest/dash/id/3aa39fa2cc27967f/source/youtube?"
|
||||
+ "as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0&"
|
||||
+ "expire=19000000000&signature=7181C59D0252B285D593E1B61D985D5B7C98DE2A."
|
||||
+ "5B445837F55A40E0F28AACAA047982E372D177E2&key=ik0", DemoUtil.TYPE_DASH, true),
|
||||
+ "5B445837F55A40E0F28AACAA047982E372D177E2&key=ik0", DemoUtil.TYPE_DASH),
|
||||
};
|
||||
|
||||
public static final Sample[] YOUTUBE_DASH_WEBM = new Sample[] {
|
||||
new Sample("Google Glass", "bf5bb2419360daf1",
|
||||
new Sample("Google Glass",
|
||||
"http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?"
|
||||
+ "as=fmp4_audio_clear,webm2_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0&"
|
||||
+ "expire=19000000000&signature=A3EC7EE53ABE601B357F7CAB8B54AD0702CA85A7."
|
||||
+ "446E9C38E47E3EDAF39E0163C390FF83A7944918&key=ik0", DemoUtil.TYPE_DASH, true),
|
||||
new Sample("Google Play", "3aa39fa2cc27967f",
|
||||
+ "446E9C38E47E3EDAF39E0163C390FF83A7944918&key=ik0", DemoUtil.TYPE_DASH),
|
||||
new Sample("Google Play",
|
||||
"http://www.youtube.com/api/manifest/dash/id/3aa39fa2cc27967f/source/youtube?"
|
||||
+ "as=fmp4_audio_clear,webm2_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0&"
|
||||
+ "expire=19000000000&signature=B752B262C6D7262EC4E4EB67901E5D8F7058A81D."
|
||||
+ "C0358CE1E335417D9A8D88FF192F0D5D8F6DA1B6&key=ik0", DemoUtil.TYPE_DASH, true),
|
||||
+ "C0358CE1E335417D9A8D88FF192F0D5D8F6DA1B6&key=ik0", DemoUtil.TYPE_DASH),
|
||||
};
|
||||
|
||||
public static final Sample[] SMOOTHSTREAMING = new Sample[] {
|
||||
new Sample("Super speed", "uid:ss:superspeed",
|
||||
new Sample("Super speed",
|
||||
"http://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism",
|
||||
DemoUtil.TYPE_SS, true),
|
||||
new Sample("Super speed (PlayReady)", "uid:ss:pr:superspeed",
|
||||
DemoUtil.TYPE_SS),
|
||||
new Sample("Super speed (PlayReady)",
|
||||
"http://playready.directtaps.net/smoothstreaming/SSWSS720H264PR/SuperSpeedway_720.ism",
|
||||
DemoUtil.TYPE_SS, true),
|
||||
DemoUtil.TYPE_SS),
|
||||
};
|
||||
|
||||
public static final Sample[] WIDEVINE_GTS = new Sample[] {
|
||||
|
|
@ -99,54 +82,54 @@ package com.google.android.exoplayer.demo;
|
|||
"http://www.youtube.com/api/manifest/dash/id/d286538032258a1c/source/youtube?"
|
||||
+ "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
|
||||
+ "&expire=19000000000&signature=41EA40A027A125A16292E0A5E3277A3B5FA9B938."
|
||||
+ "0BB075C396FFDDC97E526E8F77DC26FF9667D0D6&key=ik0", DemoUtil.TYPE_DASH, true),
|
||||
+ "0BB075C396FFDDC97E526E8F77DC26FF9667D0D6&key=ik0", DemoUtil.TYPE_DASH),
|
||||
new Sample("WV: HDCP not required", "48fcc369939ac96c",
|
||||
"http://www.youtube.com/api/manifest/dash/id/48fcc369939ac96c/source/youtube?"
|
||||
+ "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
|
||||
+ "&expire=19000000000&signature=315911BDCEED0FB0C763455BDCC97449DAAFA9E8."
|
||||
+ "5B41E2EB411F797097A359D6671D2CDE26272373&key=ik0", DemoUtil.TYPE_DASH, true),
|
||||
+ "5B41E2EB411F797097A359D6671D2CDE26272373&key=ik0", DemoUtil.TYPE_DASH),
|
||||
new Sample("WV: HDCP required", "e06c39f1151da3df",
|
||||
"http://www.youtube.com/api/manifest/dash/id/e06c39f1151da3df/source/youtube?"
|
||||
+ "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
|
||||
+ "&expire=19000000000&signature=A47A1E13E7243BD567601A75F79B34644D0DC592."
|
||||
+ "B09589A34FA23527EFC1552907754BB8033870BD&key=ik0", DemoUtil.TYPE_DASH, true),
|
||||
+ "B09589A34FA23527EFC1552907754BB8033870BD&key=ik0", DemoUtil.TYPE_DASH),
|
||||
new Sample("WV: Secure video path required", "0894c7c8719b28a0",
|
||||
"http://www.youtube.com/api/manifest/dash/id/0894c7c8719b28a0/source/youtube?"
|
||||
+ "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
|
||||
+ "&expire=19000000000&signature=2847EE498970F6B45176766CD2802FEB4D4CB7B2."
|
||||
+ "A1CA51EC40A1C1039BA800C41500DD448C03EEDA&key=ik0", DemoUtil.TYPE_DASH, true),
|
||||
+ "A1CA51EC40A1C1039BA800C41500DD448C03EEDA&key=ik0", DemoUtil.TYPE_DASH),
|
||||
new Sample("WV: HDCP + secure video path required", "efd045b1eb61888a",
|
||||
"http://www.youtube.com/api/manifest/dash/id/efd045b1eb61888a/source/youtube?"
|
||||
+ "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
|
||||
+ "&expire=19000000000&signature=61611F115EEEC7BADE5536827343FFFE2D83D14F."
|
||||
+ "2FDF4BFA502FB5865C5C86401314BDDEA4799BD0&key=ik0", DemoUtil.TYPE_DASH, true),
|
||||
+ "2FDF4BFA502FB5865C5C86401314BDDEA4799BD0&key=ik0", DemoUtil.TYPE_DASH),
|
||||
new Sample("WV: 30s license duration", "f9a34cab7b05881a",
|
||||
"http://www.youtube.com/api/manifest/dash/id/f9a34cab7b05881a/source/youtube?"
|
||||
+ "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
|
||||
+ "&expire=19000000000&signature=88DC53943385CED8CF9F37ADD9E9843E3BF621E6."
|
||||
+ "22727BB612D24AA4FACE4EF62726F9461A9BF57A&key=ik0", DemoUtil.TYPE_DASH, true),
|
||||
+ "22727BB612D24AA4FACE4EF62726F9461A9BF57A&key=ik0", DemoUtil.TYPE_DASH),
|
||||
};
|
||||
|
||||
public static final Sample[] HLS = new Sample[] {
|
||||
new Sample("Apple master playlist", "uid:hls:applemaster",
|
||||
new Sample("Apple master playlist",
|
||||
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/"
|
||||
+ "bipbop_4x3_variant.m3u8", DemoUtil.TYPE_HLS, true),
|
||||
new Sample("Apple master playlist advanced", "uid:hls:applemasteradvanced",
|
||||
+ "bipbop_4x3_variant.m3u8", DemoUtil.TYPE_HLS),
|
||||
new Sample("Apple master playlist advanced",
|
||||
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/"
|
||||
+ "bipbop_16x9_variant.m3u8", DemoUtil.TYPE_HLS, true),
|
||||
new Sample("Apple single media playlist", "uid:hls:applesinglemedia",
|
||||
+ "bipbop_16x9_variant.m3u8", DemoUtil.TYPE_HLS),
|
||||
new Sample("Apple single media playlist",
|
||||
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear1/"
|
||||
+ "prog_index.m3u8", DemoUtil.TYPE_HLS, true),
|
||||
+ "prog_index.m3u8", DemoUtil.TYPE_HLS),
|
||||
};
|
||||
|
||||
public static final Sample[] MISC = new Sample[] {
|
||||
new Sample("Dizzy", "uid:misc:dizzy", "http://html5demos.com/assets/dizzy.mp4",
|
||||
DemoUtil.TYPE_OTHER, true),
|
||||
new Sample("Dizzy (https->http redirect)", "uid:misc:dizzy2", "https://goo.gl/MtUDEj",
|
||||
DemoUtil.TYPE_OTHER, true),
|
||||
new Sample("Apple AAC 10s", "uid:misc:appleaacseg", "https://devimages.apple.com.edgekey.net/"
|
||||
new Sample("Dizzy", "http://html5demos.com/assets/dizzy.mp4",
|
||||
DemoUtil.TYPE_OTHER),
|
||||
new Sample("Dizzy (https->http redirect)", "https://goo.gl/MtUDEj",
|
||||
DemoUtil.TYPE_OTHER),
|
||||
new Sample("Apple AAC 10s", "https://devimages.apple.com.edgekey.net/"
|
||||
+ "streaming/examples/bipbop_4x3/gear0/fileSequence0.aac",
|
||||
DemoUtil.TYPE_OTHER, true),
|
||||
DemoUtil.TYPE_OTHER),
|
||||
};
|
||||
|
||||
private Samples() {}
|
||||
|
|
|
|||
|
|
@ -13,9 +13,8 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.demo.full;
|
||||
package com.google.android.exoplayer.demo;
|
||||
|
||||
import com.google.android.exoplayer.demo.DemoUtil;
|
||||
import com.google.android.exoplayer.drm.MediaDrmCallback;
|
||||
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
|
||||
|
||||
|
|
@ -13,9 +13,8 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.demo.full;
|
||||
package com.google.android.exoplayer.demo;
|
||||
|
||||
import com.google.android.exoplayer.demo.DemoUtil;
|
||||
import com.google.android.exoplayer.drm.MediaDrmCallback;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.demo.full.player;
|
||||
package com.google.android.exoplayer.demo.player;
|
||||
|
||||
import com.google.android.exoplayer.Ac3PassthroughAudioTrackRenderer;
|
||||
import com.google.android.exoplayer.DefaultLoadControl;
|
||||
|
|
@ -38,8 +38,8 @@ import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser;
|
|||
import com.google.android.exoplayer.dash.mpd.Period;
|
||||
import com.google.android.exoplayer.dash.mpd.Representation;
|
||||
import com.google.android.exoplayer.demo.DemoUtil;
|
||||
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder;
|
||||
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilderCallback;
|
||||
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
|
||||
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallback;
|
||||
import com.google.android.exoplayer.drm.DrmSessionManager;
|
||||
import com.google.android.exoplayer.drm.MediaDrmCallback;
|
||||
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.demo.full.player;
|
||||
package com.google.android.exoplayer.demo.player;
|
||||
|
||||
import com.google.android.exoplayer.ExoPlaybackException;
|
||||
import com.google.android.exoplayer.MediaCodecTrackRenderer;
|
||||
|
|
@ -82,8 +82,8 @@ import android.widget.TextView;
|
|||
}
|
||||
|
||||
private String getRenderString() {
|
||||
return "ms(" + (currentPositionUs / 1000) + "), " + getQualityString() +
|
||||
", " + renderer.codecCounters.getDebugString();
|
||||
return "ms(" + (currentPositionUs / 1000) + "), " + getQualityString()
|
||||
+ ", " + renderer.codecCounters.getDebugString();
|
||||
}
|
||||
|
||||
private String getQualityString() {
|
||||
|
|
@ -13,13 +13,13 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.demo.full.player;
|
||||
package com.google.android.exoplayer.demo.player;
|
||||
|
||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
||||
import com.google.android.exoplayer.TrackRenderer;
|
||||
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder;
|
||||
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilderCallback;
|
||||
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
|
||||
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallback;
|
||||
import com.google.android.exoplayer.source.DefaultSampleSource;
|
||||
import com.google.android.exoplayer.source.FrameworkSampleExtractor;
|
||||
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.demo.full.player;
|
||||
package com.google.android.exoplayer.demo.player;
|
||||
|
||||
import com.google.android.exoplayer.Ac3PassthroughAudioTrackRenderer;
|
||||
import com.google.android.exoplayer.DummyTrackRenderer;
|
||||
|
|
@ -13,13 +13,13 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.demo.full.player;
|
||||
package com.google.android.exoplayer.demo.player;
|
||||
|
||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
||||
import com.google.android.exoplayer.TrackRenderer;
|
||||
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder;
|
||||
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilderCallback;
|
||||
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
|
||||
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallback;
|
||||
import com.google.android.exoplayer.hls.HlsChunkSource;
|
||||
import com.google.android.exoplayer.hls.HlsPlaylist;
|
||||
import com.google.android.exoplayer.hls.HlsPlaylistParser;
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.demo.full.player;
|
||||
package com.google.android.exoplayer.demo.player;
|
||||
|
||||
import com.google.android.exoplayer.DefaultLoadControl;
|
||||
import com.google.android.exoplayer.LoadControl;
|
||||
|
|
@ -27,8 +27,8 @@ import com.google.android.exoplayer.chunk.ChunkSource;
|
|||
import com.google.android.exoplayer.chunk.FormatEvaluator;
|
||||
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
|
||||
import com.google.android.exoplayer.chunk.MultiTrackChunkSource;
|
||||
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder;
|
||||
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilderCallback;
|
||||
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder;
|
||||
import com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallback;
|
||||
import com.google.android.exoplayer.drm.DrmSessionManager;
|
||||
import com.google.android.exoplayer.drm.MediaDrmCallback;
|
||||
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.demo.full.player;
|
||||
package com.google.android.exoplayer.demo.player;
|
||||
|
||||
/**
|
||||
* Exception thrown when the required level of DRM is not supported.
|
||||
|
|
@ -1,155 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.exoplayer.demo.simple;
|
||||
|
||||
import com.google.android.exoplayer.DefaultLoadControl;
|
||||
import com.google.android.exoplayer.LoadControl;
|
||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
||||
import com.google.android.exoplayer.MediaCodecUtil;
|
||||
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
|
||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
||||
import com.google.android.exoplayer.SampleSource;
|
||||
import com.google.android.exoplayer.chunk.ChunkSampleSource;
|
||||
import com.google.android.exoplayer.chunk.ChunkSource;
|
||||
import com.google.android.exoplayer.chunk.Format;
|
||||
import com.google.android.exoplayer.chunk.FormatEvaluator;
|
||||
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
|
||||
import com.google.android.exoplayer.dash.DashChunkSource;
|
||||
import com.google.android.exoplayer.dash.mpd.AdaptationSet;
|
||||
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription;
|
||||
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser;
|
||||
import com.google.android.exoplayer.dash.mpd.Period;
|
||||
import com.google.android.exoplayer.dash.mpd.Representation;
|
||||
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilder;
|
||||
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback;
|
||||
import com.google.android.exoplayer.upstream.BufferPool;
|
||||
import com.google.android.exoplayer.upstream.DataSource;
|
||||
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
|
||||
import com.google.android.exoplayer.upstream.UriDataSource;
|
||||
import com.google.android.exoplayer.util.ManifestFetcher;
|
||||
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
import android.os.Handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A {@link RendererBuilder} for DASH.
|
||||
*/
|
||||
/* package */ class DashRendererBuilder implements RendererBuilder,
|
||||
ManifestCallback<MediaPresentationDescription> {
|
||||
|
||||
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
|
||||
private static final int VIDEO_BUFFER_SEGMENTS = 200;
|
||||
private static final int AUDIO_BUFFER_SEGMENTS = 60;
|
||||
private static final int LIVE_EDGE_LATENCY_MS = 30000;
|
||||
|
||||
private final SimplePlayerActivity playerActivity;
|
||||
private final String userAgent;
|
||||
private final String url;
|
||||
private final String contentId;
|
||||
|
||||
private RendererBuilderCallback callback;
|
||||
private ManifestFetcher<MediaPresentationDescription> manifestFetcher;
|
||||
|
||||
public DashRendererBuilder(SimplePlayerActivity playerActivity, String userAgent, String url,
|
||||
String contentId) {
|
||||
this.playerActivity = playerActivity;
|
||||
this.userAgent = userAgent;
|
||||
this.url = url;
|
||||
this.contentId = contentId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildRenderers(RendererBuilderCallback callback) {
|
||||
this.callback = callback;
|
||||
MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser();
|
||||
manifestFetcher = new ManifestFetcher<MediaPresentationDescription>(parser, contentId, url,
|
||||
userAgent);
|
||||
manifestFetcher.singleLoad(playerActivity.getMainLooper(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onManifestError(String contentId, IOException e) {
|
||||
callback.onRenderersError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onManifest(String contentId, MediaPresentationDescription manifest) {
|
||||
Period period = manifest.periods.get(0);
|
||||
Handler mainHandler = playerActivity.getMainHandler();
|
||||
LoadControl loadControl = new DefaultLoadControl(new BufferPool(BUFFER_SEGMENT_SIZE));
|
||||
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
|
||||
|
||||
// Determine which video representations we should use for playback.
|
||||
int maxDecodableFrameSize;
|
||||
try {
|
||||
maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize();
|
||||
} catch (DecoderQueryException e) {
|
||||
callback.onRenderersError(e);
|
||||
return;
|
||||
}
|
||||
|
||||
int videoAdaptationSetIndex = period.getAdaptationSetIndex(AdaptationSet.TYPE_VIDEO);
|
||||
List<Representation> videoRepresentations =
|
||||
period.adaptationSets.get(videoAdaptationSetIndex).representations;
|
||||
ArrayList<Integer> videoRepresentationIndexList = new ArrayList<Integer>();
|
||||
for (int i = 0; i < videoRepresentations.size(); i++) {
|
||||
Format format = videoRepresentations.get(i).format;
|
||||
if (format.width * format.height > maxDecodableFrameSize) {
|
||||
// Filtering stream that device cannot play
|
||||
} else if (!format.mimeType.equals(MimeTypes.VIDEO_MP4)
|
||||
&& !format.mimeType.equals(MimeTypes.VIDEO_WEBM)) {
|
||||
// Filtering unsupported mime type
|
||||
} else {
|
||||
videoRepresentationIndexList.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Build the video renderer.
|
||||
final MediaCodecVideoTrackRenderer videoRenderer;
|
||||
if (videoRepresentationIndexList.isEmpty()) {
|
||||
videoRenderer = null;
|
||||
} else {
|
||||
int[] videoRepresentationIndices = Util.toArray(videoRepresentationIndexList);
|
||||
DataSource videoDataSource = new UriDataSource(userAgent, bandwidthMeter);
|
||||
ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher, videoAdaptationSetIndex,
|
||||
videoRepresentationIndices, videoDataSource, new AdaptiveEvaluator(bandwidthMeter),
|
||||
LIVE_EDGE_LATENCY_MS);
|
||||
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
|
||||
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
|
||||
videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource,
|
||||
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, mainHandler, playerActivity, 50);
|
||||
}
|
||||
|
||||
// Build the audio renderer.
|
||||
int audioAdaptationSetIndex = period.getAdaptationSetIndex(AdaptationSet.TYPE_AUDIO);
|
||||
DataSource audioDataSource = new UriDataSource(userAgent, bandwidthMeter);
|
||||
ChunkSource audioChunkSource = new DashChunkSource(manifestFetcher, audioAdaptationSetIndex,
|
||||
new int[] {0}, audioDataSource, new FormatEvaluator.FixedEvaluator(), LIVE_EDGE_LATENCY_MS);
|
||||
SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
|
||||
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
|
||||
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(
|
||||
audioSampleSource);
|
||||
callback.onRenderers(videoRenderer, audioRenderer);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.exoplayer.demo.simple;
|
||||
|
||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
||||
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilder;
|
||||
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback;
|
||||
import com.google.android.exoplayer.source.DefaultSampleSource;
|
||||
import com.google.android.exoplayer.source.FrameworkSampleExtractor;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
import android.net.Uri;
|
||||
|
||||
/**
|
||||
* A {@link RendererBuilder} for streams that can be read using
|
||||
* {@link android.media.MediaExtractor}.
|
||||
*/
|
||||
/* package */ class DefaultRendererBuilder implements RendererBuilder {
|
||||
|
||||
private final SimplePlayerActivity playerActivity;
|
||||
private final Uri uri;
|
||||
|
||||
public DefaultRendererBuilder(SimplePlayerActivity playerActivity, Uri uri) {
|
||||
this.playerActivity = playerActivity;
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildRenderers(RendererBuilderCallback callback) {
|
||||
// Build the video and audio renderers.
|
||||
DefaultSampleSource sampleSource =
|
||||
new DefaultSampleSource(new FrameworkSampleExtractor(playerActivity, uri, null), 2);
|
||||
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
|
||||
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, playerActivity.getMainHandler(),
|
||||
playerActivity, 50);
|
||||
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);
|
||||
|
||||
// Invoke the callback.
|
||||
callback.onRenderers(videoRenderer, audioRenderer);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.exoplayer.demo.simple;
|
||||
|
||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
||||
import com.google.android.exoplayer.TrackRenderer;
|
||||
import com.google.android.exoplayer.demo.full.player.DemoPlayer;
|
||||
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilder;
|
||||
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback;
|
||||
import com.google.android.exoplayer.hls.HlsChunkSource;
|
||||
import com.google.android.exoplayer.hls.HlsPlaylist;
|
||||
import com.google.android.exoplayer.hls.HlsPlaylistParser;
|
||||
import com.google.android.exoplayer.hls.HlsSampleSource;
|
||||
import com.google.android.exoplayer.upstream.DataSource;
|
||||
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
|
||||
import com.google.android.exoplayer.upstream.UriDataSource;
|
||||
import com.google.android.exoplayer.util.ManifestFetcher;
|
||||
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A {@link RendererBuilder} for HLS.
|
||||
*/
|
||||
/* package */ class HlsRendererBuilder implements RendererBuilder,
|
||||
ManifestCallback<HlsPlaylist> {
|
||||
|
||||
private final SimplePlayerActivity playerActivity;
|
||||
private final String userAgent;
|
||||
private final String url;
|
||||
private final String contentId;
|
||||
|
||||
private RendererBuilderCallback callback;
|
||||
|
||||
public HlsRendererBuilder(SimplePlayerActivity playerActivity, String userAgent, String url,
|
||||
String contentId) {
|
||||
this.playerActivity = playerActivity;
|
||||
this.userAgent = userAgent;
|
||||
this.url = url;
|
||||
this.contentId = contentId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildRenderers(RendererBuilderCallback callback) {
|
||||
this.callback = callback;
|
||||
HlsPlaylistParser parser = new HlsPlaylistParser();
|
||||
ManifestFetcher<HlsPlaylist> playlistFetcher =
|
||||
new ManifestFetcher<HlsPlaylist>(parser, contentId, url, userAgent);
|
||||
playlistFetcher.singleLoad(playerActivity.getMainLooper(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onManifestError(String contentId, IOException e) {
|
||||
callback.onRenderersError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onManifest(String contentId, HlsPlaylist manifest) {
|
||||
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
|
||||
|
||||
DataSource dataSource = new UriDataSource(userAgent, bandwidthMeter);
|
||||
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, url, manifest, bandwidthMeter, null,
|
||||
HlsChunkSource.ADAPTIVE_MODE_SPLICE);
|
||||
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, true, 2);
|
||||
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
|
||||
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, playerActivity.getMainHandler(),
|
||||
playerActivity, 50);
|
||||
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);
|
||||
|
||||
TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT];
|
||||
renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer;
|
||||
renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer;
|
||||
callback.onRenderers(videoRenderer, audioRenderer);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,291 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.exoplayer.demo.simple;
|
||||
|
||||
import com.google.android.exoplayer.ExoPlaybackException;
|
||||
import com.google.android.exoplayer.ExoPlayer;
|
||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
||||
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
|
||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
||||
import com.google.android.exoplayer.VideoSurfaceView;
|
||||
import com.google.android.exoplayer.demo.DemoUtil;
|
||||
import com.google.android.exoplayer.demo.R;
|
||||
import com.google.android.exoplayer.util.PlayerControl;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.media.MediaCodec.CryptoException;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.View;
|
||||
import android.view.View.OnTouchListener;
|
||||
import android.widget.MediaController;
|
||||
import android.widget.Toast;
|
||||
|
||||
/**
|
||||
* An activity that plays media using {@link ExoPlayer}.
|
||||
*/
|
||||
public class SimplePlayerActivity extends Activity implements SurfaceHolder.Callback,
|
||||
ExoPlayer.Listener, MediaCodecVideoTrackRenderer.EventListener {
|
||||
|
||||
/**
|
||||
* Builds renderers for the player.
|
||||
*/
|
||||
public interface RendererBuilder {
|
||||
|
||||
void buildRenderers(RendererBuilderCallback callback);
|
||||
|
||||
}
|
||||
|
||||
public static final int RENDERER_COUNT = 2;
|
||||
public static final int TYPE_VIDEO = 0;
|
||||
public static final int TYPE_AUDIO = 1;
|
||||
|
||||
private static final String TAG = "PlayerActivity";
|
||||
|
||||
private MediaController mediaController;
|
||||
private Handler mainHandler;
|
||||
private View shutterView;
|
||||
private VideoSurfaceView surfaceView;
|
||||
|
||||
private ExoPlayer player;
|
||||
private RendererBuilder builder;
|
||||
private RendererBuilderCallback callback;
|
||||
private MediaCodecVideoTrackRenderer videoRenderer;
|
||||
|
||||
private boolean autoPlay = true;
|
||||
private long playerPosition;
|
||||
|
||||
private Uri contentUri;
|
||||
private int contentType;
|
||||
private String contentId;
|
||||
|
||||
// Activity lifecycle
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Intent intent = getIntent();
|
||||
contentUri = intent.getData();
|
||||
contentType = intent.getIntExtra(DemoUtil.CONTENT_TYPE_EXTRA, DemoUtil.TYPE_OTHER);
|
||||
contentId = intent.getStringExtra(DemoUtil.CONTENT_ID_EXTRA);
|
||||
|
||||
mainHandler = new Handler(getMainLooper());
|
||||
builder = getRendererBuilder();
|
||||
|
||||
setContentView(R.layout.player_activity_simple);
|
||||
View root = findViewById(R.id.root);
|
||||
root.setOnTouchListener(new OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View arg0, MotionEvent arg1) {
|
||||
if (arg1.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
toggleControlsVisibility();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
mediaController = new MediaController(this);
|
||||
mediaController.setAnchorView(root);
|
||||
shutterView = findViewById(R.id.shutter);
|
||||
surfaceView = (VideoSurfaceView) findViewById(R.id.surface_view);
|
||||
surfaceView.getHolder().addCallback(this);
|
||||
|
||||
DemoUtil.setDefaultCookieManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
// Setup the player
|
||||
player = ExoPlayer.Factory.newInstance(RENDERER_COUNT, 1000, 5000);
|
||||
player.addListener(this);
|
||||
player.seekTo(playerPosition);
|
||||
// Build the player controls
|
||||
mediaController.setMediaPlayer(new PlayerControl(player));
|
||||
mediaController.setEnabled(true);
|
||||
// Request the renderers
|
||||
callback = new RendererBuilderCallback();
|
||||
builder.buildRenderers(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
// Release the player
|
||||
if (player != null) {
|
||||
playerPosition = player.getCurrentPosition();
|
||||
player.release();
|
||||
player = null;
|
||||
}
|
||||
callback = null;
|
||||
videoRenderer = null;
|
||||
shutterView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
// Public methods
|
||||
|
||||
public Handler getMainHandler() {
|
||||
return mainHandler;
|
||||
}
|
||||
|
||||
// Internal methods
|
||||
|
||||
private void toggleControlsVisibility() {
|
||||
if (mediaController.isShowing()) {
|
||||
mediaController.hide();
|
||||
} else {
|
||||
mediaController.show(0);
|
||||
}
|
||||
}
|
||||
|
||||
private RendererBuilder getRendererBuilder() {
|
||||
String userAgent = DemoUtil.getUserAgent(this);
|
||||
switch (contentType) {
|
||||
case DemoUtil.TYPE_SS:
|
||||
return new SmoothStreamingRendererBuilder(this, userAgent, contentUri.toString(),
|
||||
contentId);
|
||||
case DemoUtil.TYPE_DASH:
|
||||
return new DashRendererBuilder(this, userAgent, contentUri.toString(), contentId);
|
||||
case DemoUtil.TYPE_HLS:
|
||||
return new HlsRendererBuilder(this, userAgent, contentUri.toString(), contentId);
|
||||
default:
|
||||
return new DefaultRendererBuilder(this, contentUri);
|
||||
}
|
||||
}
|
||||
|
||||
private void onRenderers(RendererBuilderCallback callback,
|
||||
MediaCodecVideoTrackRenderer videoRenderer, MediaCodecAudioTrackRenderer audioRenderer) {
|
||||
if (this.callback != callback) {
|
||||
return;
|
||||
}
|
||||
this.callback = null;
|
||||
this.videoRenderer = videoRenderer;
|
||||
player.prepare(videoRenderer, audioRenderer);
|
||||
maybeStartPlayback();
|
||||
}
|
||||
|
||||
private void maybeStartPlayback() {
|
||||
Surface surface = surfaceView.getHolder().getSurface();
|
||||
if (videoRenderer == null || surface == null || !surface.isValid()) {
|
||||
// We're not ready yet.
|
||||
return;
|
||||
}
|
||||
player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface);
|
||||
if (autoPlay) {
|
||||
player.setPlayWhenReady(true);
|
||||
autoPlay = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void onRenderersError(RendererBuilderCallback callback, Exception e) {
|
||||
if (this.callback != callback) {
|
||||
return;
|
||||
}
|
||||
this.callback = null;
|
||||
onError(e);
|
||||
}
|
||||
|
||||
private void onError(Exception e) {
|
||||
Log.e(TAG, "Playback failed", e);
|
||||
Toast.makeText(this, R.string.failed, Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
// ExoPlayer.Listener implementation
|
||||
|
||||
@Override
|
||||
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayWhenReadyCommitted() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerError(ExoPlaybackException e) {
|
||||
onError(e);
|
||||
}
|
||||
|
||||
// MediaCodecVideoTrackRenderer.Listener
|
||||
|
||||
@Override
|
||||
public void onVideoSizeChanged(int width, int height, float pixelWidthHeightRatio) {
|
||||
surfaceView.setVideoWidthHeightRatio(
|
||||
height == 0 ? 1 : (pixelWidthHeightRatio * width) / height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawnToSurface(Surface surface) {
|
||||
shutterView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDroppedFrames(int count, long elapsed) {
|
||||
Log.d(TAG, "Dropped frames: " + count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDecoderInitializationError(DecoderInitializationException e) {
|
||||
// This is for informational purposes only. Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoError(CryptoException e) {
|
||||
// This is for informational purposes only. Do nothing.
|
||||
}
|
||||
|
||||
// SurfaceHolder.Callback implementation
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
maybeStartPlayback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
if (videoRenderer != null) {
|
||||
player.blockingSendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, null);
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ final class RendererBuilderCallback {
|
||||
|
||||
public void onRenderers(MediaCodecVideoTrackRenderer videoRenderer,
|
||||
MediaCodecAudioTrackRenderer audioRenderer) {
|
||||
SimplePlayerActivity.this.onRenderers(this, videoRenderer, audioRenderer);
|
||||
}
|
||||
|
||||
public void onRenderersError(Exception e) {
|
||||
SimplePlayerActivity.this.onRenderersError(this, e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.exoplayer.demo.simple;
|
||||
|
||||
import com.google.android.exoplayer.DefaultLoadControl;
|
||||
import com.google.android.exoplayer.LoadControl;
|
||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
||||
import com.google.android.exoplayer.MediaCodecUtil;
|
||||
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
|
||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
||||
import com.google.android.exoplayer.SampleSource;
|
||||
import com.google.android.exoplayer.chunk.ChunkSampleSource;
|
||||
import com.google.android.exoplayer.chunk.ChunkSource;
|
||||
import com.google.android.exoplayer.chunk.FormatEvaluator;
|
||||
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
|
||||
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilder;
|
||||
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback;
|
||||
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource;
|
||||
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest;
|
||||
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement;
|
||||
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement;
|
||||
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifestParser;
|
||||
import com.google.android.exoplayer.upstream.BufferPool;
|
||||
import com.google.android.exoplayer.upstream.DataSource;
|
||||
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
|
||||
import com.google.android.exoplayer.upstream.UriDataSource;
|
||||
import com.google.android.exoplayer.util.ManifestFetcher;
|
||||
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
import android.os.Handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A {@link RendererBuilder} for SmoothStreaming.
|
||||
*/
|
||||
/* package */ class SmoothStreamingRendererBuilder implements RendererBuilder,
|
||||
ManifestCallback<SmoothStreamingManifest> {
|
||||
|
||||
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
|
||||
private static final int VIDEO_BUFFER_SEGMENTS = 200;
|
||||
private static final int AUDIO_BUFFER_SEGMENTS = 60;
|
||||
private static final int LIVE_EDGE_LATENCY_MS = 30000;
|
||||
|
||||
private final SimplePlayerActivity playerActivity;
|
||||
private final String userAgent;
|
||||
private final String url;
|
||||
private final String contentId;
|
||||
|
||||
private RendererBuilderCallback callback;
|
||||
private ManifestFetcher<SmoothStreamingManifest> manifestFetcher;
|
||||
|
||||
public SmoothStreamingRendererBuilder(SimplePlayerActivity playerActivity, String userAgent,
|
||||
String url, String contentId) {
|
||||
this.playerActivity = playerActivity;
|
||||
this.userAgent = userAgent;
|
||||
this.url = url;
|
||||
this.contentId = contentId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildRenderers(RendererBuilderCallback callback) {
|
||||
this.callback = callback;
|
||||
SmoothStreamingManifestParser parser = new SmoothStreamingManifestParser();
|
||||
manifestFetcher = new ManifestFetcher<SmoothStreamingManifest>(parser, contentId,
|
||||
url + "/Manifest", userAgent);
|
||||
manifestFetcher.singleLoad(playerActivity.getMainLooper(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onManifestError(String contentId, IOException e) {
|
||||
callback.onRenderersError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onManifest(String contentId, SmoothStreamingManifest manifest) {
|
||||
Handler mainHandler = playerActivity.getMainHandler();
|
||||
LoadControl loadControl = new DefaultLoadControl(new BufferPool(BUFFER_SEGMENT_SIZE));
|
||||
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
|
||||
|
||||
// Obtain stream elements for playback.
|
||||
int maxDecodableFrameSize;
|
||||
try {
|
||||
maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize();
|
||||
} catch (DecoderQueryException e) {
|
||||
callback.onRenderersError(e);
|
||||
return;
|
||||
}
|
||||
int audioStreamElementIndex = -1;
|
||||
int videoStreamElementIndex = -1;
|
||||
ArrayList<Integer> videoTrackIndexList = new ArrayList<Integer>();
|
||||
for (int i = 0; i < manifest.streamElements.length; i++) {
|
||||
if (audioStreamElementIndex == -1
|
||||
&& manifest.streamElements[i].type == StreamElement.TYPE_AUDIO) {
|
||||
audioStreamElementIndex = i;
|
||||
} else if (videoStreamElementIndex == -1
|
||||
&& manifest.streamElements[i].type == StreamElement.TYPE_VIDEO) {
|
||||
videoStreamElementIndex = i;
|
||||
StreamElement streamElement = manifest.streamElements[i];
|
||||
for (int j = 0; j < streamElement.tracks.length; j++) {
|
||||
TrackElement trackElement = streamElement.tracks[j];
|
||||
if (trackElement.maxWidth * trackElement.maxHeight <= maxDecodableFrameSize) {
|
||||
videoTrackIndexList.add(j);
|
||||
} else {
|
||||
// The device isn't capable of playing this stream.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
int[] videoTrackIndices = Util.toArray(videoTrackIndexList);
|
||||
|
||||
// Build the video renderer.
|
||||
DataSource videoDataSource = new UriDataSource(userAgent, bandwidthMeter);
|
||||
ChunkSource videoChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
|
||||
videoStreamElementIndex, videoTrackIndices, videoDataSource,
|
||||
new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS);
|
||||
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
|
||||
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
|
||||
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource,
|
||||
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, mainHandler, playerActivity, 50);
|
||||
|
||||
// Build the audio renderer.
|
||||
DataSource audioDataSource = new UriDataSource(userAgent, bandwidthMeter);
|
||||
ChunkSource audioChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
|
||||
audioStreamElementIndex, new int[] {0}, audioDataSource,
|
||||
new FormatEvaluator.FixedEvaluator(), LIVE_EDGE_LATENCY_MS);
|
||||
SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
|
||||
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
|
||||
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(
|
||||
audioSampleSource);
|
||||
callback.onRenderers(videoRenderer, audioRenderer);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright (C) 2014 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.
|
||||
-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:keepScreenOn="true">
|
||||
|
||||
<com.google.android.exoplayer.VideoSurfaceView android:id="@+id/surface_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"/>
|
||||
|
||||
<View android:id="@+id/shutter"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/black"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
|
@ -375,10 +375,13 @@ import java.util.List;
|
|||
}
|
||||
|
||||
private void updatePositionUs() {
|
||||
positionUs = timeSourceTrackRenderer != null &&
|
||||
enabledRenderers.contains(timeSourceTrackRenderer) ?
|
||||
timeSourceTrackRenderer.getCurrentPositionUs() :
|
||||
mediaClock.getPositionUs();
|
||||
if (timeSourceTrackRenderer != null && enabledRenderers.contains(timeSourceTrackRenderer)
|
||||
&& !timeSourceTrackRenderer.isEnded()) {
|
||||
positionUs = timeSourceTrackRenderer.getCurrentPositionUs();
|
||||
mediaClock.setPositionUs(positionUs);
|
||||
} else {
|
||||
positionUs = mediaClock.getPositionUs();
|
||||
}
|
||||
elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package com.google.android.exoplayer.chunk.parser;
|
||||
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.MediaFormat;
|
||||
import com.google.android.exoplayer.ParserException;
|
||||
import com.google.android.exoplayer.SampleHolder;
|
||||
|
|
@ -78,6 +79,11 @@ public interface Extractor {
|
|||
*/
|
||||
public MediaFormat getFormat();
|
||||
|
||||
/**
|
||||
* Returns the duration of the stream in microseconds, or {@link C#UNKNOWN_TIME_US} if unknown.
|
||||
*/
|
||||
public long getDurationUs();
|
||||
|
||||
/**
|
||||
* Returns the pssh information parsed from the stream.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -24,21 +24,18 @@ import com.google.android.exoplayer.chunk.parser.SegmentIndex;
|
|||
import com.google.android.exoplayer.mp4.Atom;
|
||||
import com.google.android.exoplayer.mp4.Atom.ContainerAtom;
|
||||
import com.google.android.exoplayer.mp4.Atom.LeafAtom;
|
||||
import com.google.android.exoplayer.mp4.CommonMp4AtomParsers;
|
||||
import com.google.android.exoplayer.mp4.Mp4Util;
|
||||
import com.google.android.exoplayer.mp4.Track;
|
||||
import com.google.android.exoplayer.upstream.NonBlockingInputStream;
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaExtractor;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
|
@ -67,14 +64,8 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
|
||||
private static final int READ_TERMINATING_RESULTS = RESULT_NEED_MORE_DATA | RESULT_END_OF_STREAM
|
||||
| RESULT_READ_SAMPLE | RESULT_NEED_SAMPLE_HOLDER;
|
||||
private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1};
|
||||
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};
|
||||
/** Channel counts for AC-3 audio, indexed by acmod. (See ETSI TS 102 366.) */
|
||||
private static final int[] AC3_CHANNEL_COUNTS = new int[] {2, 1, 2, 3, 3, 4, 4, 5};
|
||||
/** Nominal bit-rates for AC-3 audio in kbps, indexed by bit_rate_code. (See ETSI TS 102 366.) */
|
||||
private static final int[] AC3_BIT_RATES = new int[] {32, 40, 48, 56, 64, 80, 96, 112, 128, 160,
|
||||
192, 224, 256, 320, 384, 448, 512, 576, 640};
|
||||
|
||||
// Parser states
|
||||
private static final int STATE_READING_ATOM_HEADER = 0;
|
||||
|
|
@ -82,10 +73,6 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
private static final int STATE_READING_ENCRYPTION_DATA = 2;
|
||||
private static final int STATE_READING_SAMPLE = 3;
|
||||
|
||||
// Atom data offsets
|
||||
private static final int ATOM_HEADER_SIZE = 8;
|
||||
private static final int FULL_ATOM_HEADER_SIZE = 12;
|
||||
|
||||
// Atoms that the parser cares about
|
||||
private static final Set<Integer> PARSED_ATOMS;
|
||||
static {
|
||||
|
|
@ -173,7 +160,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
public FragmentedMp4Extractor(int workaroundFlags) {
|
||||
this.workaroundFlags = workaroundFlags;
|
||||
parserState = STATE_READING_ATOM_HEADER;
|
||||
atomHeader = new ParsableByteArray(ATOM_HEADER_SIZE);
|
||||
atomHeader = new ParsableByteArray(Mp4Util.ATOM_HEADER_SIZE);
|
||||
extendedTypeScratch = new byte[16];
|
||||
containerAtoms = new Stack<ContainerAtom>();
|
||||
fragmentRun = new TrackFragment();
|
||||
|
|
@ -210,6 +197,11 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
return track == null ? null : track.mediaFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDurationUs() {
|
||||
return track == null ? C.UNKNOWN_TIME_US : track.durationUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(NonBlockingInputStream inputStream, SampleHolder out)
|
||||
throws ParserException {
|
||||
|
|
@ -227,7 +219,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
results |= readEncryptionData(inputStream);
|
||||
break;
|
||||
default:
|
||||
results |= readOrSkipSample(inputStream, out);
|
||||
results |= readOrSkipSample(inputStream, out);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -276,14 +268,14 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
}
|
||||
|
||||
private int readAtomHeader(NonBlockingInputStream inputStream) {
|
||||
int remainingBytes = ATOM_HEADER_SIZE - atomBytesRead;
|
||||
int remainingBytes = Mp4Util.ATOM_HEADER_SIZE - atomBytesRead;
|
||||
int bytesRead = inputStream.read(atomHeader.data, atomBytesRead, remainingBytes);
|
||||
if (bytesRead == -1) {
|
||||
return RESULT_END_OF_STREAM;
|
||||
}
|
||||
rootAtomBytesRead += bytesRead;
|
||||
atomBytesRead += bytesRead;
|
||||
if (atomBytesRead != ATOM_HEADER_SIZE) {
|
||||
if (atomBytesRead != Mp4Util.ATOM_HEADER_SIZE) {
|
||||
return RESULT_NEED_MORE_DATA;
|
||||
}
|
||||
|
||||
|
|
@ -305,10 +297,10 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
if (CONTAINER_TYPES.contains(atomTypeInteger)) {
|
||||
enterState(STATE_READING_ATOM_HEADER);
|
||||
containerAtoms.add(new ContainerAtom(atomType,
|
||||
rootAtomBytesRead + atomSize - ATOM_HEADER_SIZE));
|
||||
rootAtomBytesRead + atomSize - Mp4Util.ATOM_HEADER_SIZE));
|
||||
} else {
|
||||
atomData = new ParsableByteArray(atomSize);
|
||||
System.arraycopy(atomHeader.data, 0, atomData.data, 0, ATOM_HEADER_SIZE);
|
||||
System.arraycopy(atomHeader.data, 0, atomData.data, 0, Mp4Util.ATOM_HEADER_SIZE);
|
||||
enterState(STATE_READING_ATOM_PAYLOAD);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -371,13 +363,13 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
}
|
||||
|
||||
private void onMoovContainerAtomRead(ContainerAtom moov) {
|
||||
List<Atom> moovChildren = moov.children;
|
||||
List<Atom.LeafAtom> moovChildren = moov.leafChildren;
|
||||
int moovChildrenSize = moovChildren.size();
|
||||
for (int i = 0; i < moovChildrenSize; i++) {
|
||||
Atom child = moovChildren.get(i);
|
||||
LeafAtom child = moovChildren.get(i);
|
||||
if (child.type == Atom.TYPE_pssh) {
|
||||
ParsableByteArray psshAtom = ((LeafAtom) child).data;
|
||||
psshAtom.setPosition(FULL_ATOM_HEADER_SIZE);
|
||||
ParsableByteArray psshAtom = child.data;
|
||||
psshAtom.setPosition(Mp4Util.FULL_ATOM_HEADER_SIZE);
|
||||
UUID uuid = new UUID(psshAtom.readLong(), psshAtom.readLong());
|
||||
int dataSize = psshAtom.readInt();
|
||||
byte[] data = new byte[dataSize];
|
||||
|
|
@ -387,7 +379,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
}
|
||||
ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex);
|
||||
extendsDefaults = parseTrex(mvex.getLeafAtomOfType(Atom.TYPE_trex).data);
|
||||
track = parseTrak(moov.getContainerAtomOfType(Atom.TYPE_trak));
|
||||
track = CommonMp4AtomParsers.parseTrak(moov.getContainerAtomOfType(Atom.TYPE_trak));
|
||||
}
|
||||
|
||||
private void onMoofContainerAtomRead(ContainerAtom moof) {
|
||||
|
|
@ -412,7 +404,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
* Parses a trex atom (defined in 14496-12).
|
||||
*/
|
||||
private static DefaultSampleValues parseTrex(ParsableByteArray trex) {
|
||||
trex.setPosition(FULL_ATOM_HEADER_SIZE + 4);
|
||||
trex.setPosition(Mp4Util.FULL_ATOM_HEADER_SIZE + 4);
|
||||
int defaultSampleDescriptionIndex = trex.readUnsignedIntToInt() - 1;
|
||||
int defaultSampleDuration = trex.readUnsignedIntToInt();
|
||||
int defaultSampleSize = trex.readUnsignedIntToInt();
|
||||
|
|
@ -421,388 +413,6 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
defaultSampleSize, defaultSampleFlags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a trak atom (defined in 14496-12).
|
||||
*/
|
||||
private static Track parseTrak(ContainerAtom trak) {
|
||||
ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia);
|
||||
int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data);
|
||||
Assertions.checkState(trackType == Track.TYPE_AUDIO || trackType == Track.TYPE_VIDEO
|
||||
|| trackType == Track.TYPE_TEXT);
|
||||
|
||||
Pair<Integer, Long> header = parseTkhd(trak.getLeafAtomOfType(Atom.TYPE_tkhd).data);
|
||||
int id = header.first;
|
||||
// TODO: This value should be used to set a duration field on the Track object
|
||||
// instantiated below, however we've found examples where the value is 0. Revisit whether we
|
||||
// should set it anyway (and just have it be wrong for bad media streams).
|
||||
// long duration = header.second;
|
||||
long timescale = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data);
|
||||
ContainerAtom stbl = mdia.getContainerAtomOfType(Atom.TYPE_minf)
|
||||
.getContainerAtomOfType(Atom.TYPE_stbl);
|
||||
|
||||
Pair<MediaFormat, TrackEncryptionBox[]> sampleDescriptions =
|
||||
parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data);
|
||||
return new Track(id, trackType, timescale, sampleDescriptions.first, sampleDescriptions.second);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a tkhd atom (defined in 14496-12).
|
||||
*
|
||||
* @return A {@link Pair} consisting of the track id and duration (in the timescale indicated in
|
||||
* the movie header box). The duration is set to -1 if the duration is unspecified.
|
||||
*/
|
||||
private static Pair<Integer, Long> parseTkhd(ParsableByteArray tkhd) {
|
||||
tkhd.setPosition(ATOM_HEADER_SIZE);
|
||||
int fullAtom = tkhd.readInt();
|
||||
int version = parseFullAtomVersion(fullAtom);
|
||||
|
||||
tkhd.skip(version == 0 ? 8 : 16);
|
||||
|
||||
int trackId = tkhd.readInt();
|
||||
tkhd.skip(4);
|
||||
|
||||
boolean durationUnknown = true;
|
||||
int durationPosition = tkhd.getPosition();
|
||||
int durationByteCount = version == 0 ? 4 : 8;
|
||||
for (int i = 0; i < durationByteCount; i++) {
|
||||
if (tkhd.data[durationPosition + i] != -1) {
|
||||
durationUnknown = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
long duration;
|
||||
if (durationUnknown) {
|
||||
tkhd.skip(durationByteCount);
|
||||
duration = -1;
|
||||
} else {
|
||||
duration = version == 0 ? tkhd.readUnsignedInt() : tkhd.readUnsignedLongToLong();
|
||||
}
|
||||
|
||||
return Pair.create(trackId, duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an hdlr atom (defined in 14496-12).
|
||||
*
|
||||
* @param hdlr The hdlr atom to parse.
|
||||
* @return The track type.
|
||||
*/
|
||||
private static int parseHdlr(ParsableByteArray hdlr) {
|
||||
hdlr.setPosition(FULL_ATOM_HEADER_SIZE + 4);
|
||||
return hdlr.readInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an mdhd atom (defined in 14496-12).
|
||||
*
|
||||
* @param mdhd The mdhd atom to parse.
|
||||
* @return The media timescale, defined as the number of time units that pass in one second.
|
||||
*/
|
||||
private static long parseMdhd(ParsableByteArray mdhd) {
|
||||
mdhd.setPosition(ATOM_HEADER_SIZE);
|
||||
int fullAtom = mdhd.readInt();
|
||||
int version = parseFullAtomVersion(fullAtom);
|
||||
|
||||
mdhd.skip(version == 0 ? 8 : 16);
|
||||
return mdhd.readUnsignedInt();
|
||||
}
|
||||
|
||||
private static Pair<MediaFormat, TrackEncryptionBox[]> parseStsd(ParsableByteArray stsd) {
|
||||
stsd.setPosition(FULL_ATOM_HEADER_SIZE);
|
||||
int numberOfEntries = stsd.readInt();
|
||||
MediaFormat mediaFormat = null;
|
||||
TrackEncryptionBox[] trackEncryptionBoxes = new TrackEncryptionBox[numberOfEntries];
|
||||
for (int i = 0; i < numberOfEntries; i++) {
|
||||
int childStartPosition = stsd.getPosition();
|
||||
int childAtomSize = stsd.readInt();
|
||||
int childAtomType = stsd.readInt();
|
||||
if (childAtomType == Atom.TYPE_avc1 || childAtomType == Atom.TYPE_avc3
|
||||
|| childAtomType == Atom.TYPE_encv) {
|
||||
Pair<MediaFormat, TrackEncryptionBox> avc =
|
||||
parseAvcFromParent(stsd, childStartPosition, childAtomSize);
|
||||
mediaFormat = avc.first;
|
||||
trackEncryptionBoxes[i] = avc.second;
|
||||
} else if (childAtomType == Atom.TYPE_mp4a || childAtomType == Atom.TYPE_enca
|
||||
|| childAtomType == Atom.TYPE_ac_3) {
|
||||
Pair<MediaFormat, TrackEncryptionBox> audioSampleEntry =
|
||||
parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize);
|
||||
mediaFormat = audioSampleEntry.first;
|
||||
trackEncryptionBoxes[i] = audioSampleEntry.second;
|
||||
} else if (childAtomType == Atom.TYPE_TTML) {
|
||||
mediaFormat = MediaFormat.createTtmlFormat();
|
||||
}
|
||||
stsd.setPosition(childStartPosition + childAtomSize);
|
||||
}
|
||||
return Pair.create(mediaFormat, trackEncryptionBoxes);
|
||||
}
|
||||
|
||||
private static Pair<MediaFormat, TrackEncryptionBox> parseAvcFromParent(ParsableByteArray parent,
|
||||
int position, int size) {
|
||||
parent.setPosition(position + ATOM_HEADER_SIZE);
|
||||
|
||||
parent.skip(24);
|
||||
int width = parent.readUnsignedShort();
|
||||
int height = parent.readUnsignedShort();
|
||||
float pixelWidthHeightRatio = 1;
|
||||
parent.skip(50);
|
||||
|
||||
List<byte[]> initializationData = null;
|
||||
TrackEncryptionBox trackEncryptionBox = null;
|
||||
int childPosition = parent.getPosition();
|
||||
while (childPosition - position < size) {
|
||||
parent.setPosition(childPosition);
|
||||
int childStartPosition = parent.getPosition();
|
||||
int childAtomSize = parent.readInt();
|
||||
int childAtomType = parent.readInt();
|
||||
if (childAtomType == Atom.TYPE_avcC) {
|
||||
initializationData = parseAvcCFromParent(parent, childStartPosition);
|
||||
} else if (childAtomType == Atom.TYPE_sinf) {
|
||||
trackEncryptionBox = parseSinfFromParent(parent, childStartPosition, childAtomSize);
|
||||
} else if (childAtomType == Atom.TYPE_pasp) {
|
||||
pixelWidthHeightRatio = parsePaspFromParent(parent, childStartPosition);
|
||||
}
|
||||
childPosition += childAtomSize;
|
||||
}
|
||||
|
||||
MediaFormat format = MediaFormat.createVideoFormat(MimeTypes.VIDEO_H264, MediaFormat.NO_VALUE,
|
||||
width, height, pixelWidthHeightRatio, initializationData);
|
||||
return Pair.create(format, trackEncryptionBox);
|
||||
}
|
||||
|
||||
private static Pair<MediaFormat, TrackEncryptionBox> parseAudioSampleEntry(
|
||||
ParsableByteArray parent, int atomType, int position, int size) {
|
||||
parent.setPosition(position + ATOM_HEADER_SIZE);
|
||||
parent.skip(16);
|
||||
int channelCount = parent.readUnsignedShort();
|
||||
int sampleSize = parent.readUnsignedShort();
|
||||
parent.skip(4);
|
||||
int sampleRate = parent.readUnsignedFixedPoint1616();
|
||||
int bitrate = MediaFormat.NO_VALUE;
|
||||
|
||||
byte[] initializationData = null;
|
||||
TrackEncryptionBox trackEncryptionBox = null;
|
||||
int childPosition = parent.getPosition();
|
||||
while (childPosition - position < size) {
|
||||
parent.setPosition(childPosition);
|
||||
int childStartPosition = parent.getPosition();
|
||||
int childAtomSize = parent.readInt();
|
||||
int childAtomType = parent.readInt();
|
||||
if (atomType == Atom.TYPE_mp4a || atomType == Atom.TYPE_enca) {
|
||||
if (childAtomType == Atom.TYPE_esds) {
|
||||
initializationData = parseEsdsFromParent(parent, childStartPosition);
|
||||
// TODO: Do we really need to do this? See [Internal: b/10903778]
|
||||
// Update sampleRate and channelCount from the AudioSpecificConfig initialization data.
|
||||
Pair<Integer, Integer> audioSpecificConfig =
|
||||
CodecSpecificDataUtil.parseAudioSpecificConfig(initializationData);
|
||||
sampleRate = audioSpecificConfig.first;
|
||||
channelCount = audioSpecificConfig.second;
|
||||
} else if (childAtomType == Atom.TYPE_sinf) {
|
||||
trackEncryptionBox = parseSinfFromParent(parent, childStartPosition, childAtomSize);
|
||||
}
|
||||
} else if (atomType == Atom.TYPE_ac_3 && childAtomType == Atom.TYPE_dac3) {
|
||||
// TODO: Choose the right AC-3 track based on the contents of dac3/dec3.
|
||||
Ac3Format ac3Format =
|
||||
parseAc3SpecificBoxFromParent(parent, childStartPosition);
|
||||
if (ac3Format != null) {
|
||||
sampleRate = ac3Format.sampleRate;
|
||||
channelCount = ac3Format.channelCount;
|
||||
bitrate = ac3Format.bitrate;
|
||||
}
|
||||
|
||||
// TODO: Add support for encrypted AC-3.
|
||||
trackEncryptionBox = null;
|
||||
} else if (atomType == Atom.TYPE_ec_3 && childAtomType == Atom.TYPE_dec3) {
|
||||
sampleRate = parseEc3SpecificBoxFromParent(parent, childStartPosition);
|
||||
trackEncryptionBox = null;
|
||||
}
|
||||
childPosition += childAtomSize;
|
||||
}
|
||||
|
||||
String mimeType;
|
||||
if (atomType == Atom.TYPE_ac_3) {
|
||||
mimeType = MimeTypes.AUDIO_AC3;
|
||||
} else if (atomType == Atom.TYPE_ec_3) {
|
||||
mimeType = MimeTypes.AUDIO_EC3;
|
||||
} else {
|
||||
mimeType = MimeTypes.AUDIO_AAC;
|
||||
}
|
||||
|
||||
MediaFormat format = MediaFormat.createAudioFormat(
|
||||
mimeType, sampleSize, channelCount, sampleRate, bitrate,
|
||||
initializationData == null ? null : Collections.singletonList(initializationData));
|
||||
return Pair.create(format, trackEncryptionBox);
|
||||
}
|
||||
|
||||
private static Ac3Format parseAc3SpecificBoxFromParent(ParsableByteArray parent, int position) {
|
||||
// Start of the dac3 atom (defined in ETSI TS 102 366)
|
||||
parent.setPosition(position + ATOM_HEADER_SIZE);
|
||||
|
||||
// fscod (sample rate code)
|
||||
int fscod = (parent.readUnsignedByte() & 0xC0) >> 6;
|
||||
int sampleRate;
|
||||
switch (fscod) {
|
||||
case 0:
|
||||
sampleRate = 48000;
|
||||
break;
|
||||
case 1:
|
||||
sampleRate = 44100;
|
||||
break;
|
||||
case 2:
|
||||
sampleRate = 32000;
|
||||
break;
|
||||
default:
|
||||
// TODO: The decoder should not use this stream.
|
||||
return null;
|
||||
}
|
||||
|
||||
int nextByte = parent.readUnsignedByte();
|
||||
|
||||
// Map acmod (audio coding mode) onto a channel count.
|
||||
int channelCount = AC3_CHANNEL_COUNTS[(nextByte & 0x38) >> 3];
|
||||
|
||||
// lfeon (low frequency effects on)
|
||||
if ((nextByte & 0x04) != 0) {
|
||||
channelCount++;
|
||||
}
|
||||
|
||||
// Map bit_rate_code onto a bit-rate in kbit/s.
|
||||
int bitrate = AC3_BIT_RATES[((nextByte & 0x03) << 3) + (parent.readUnsignedByte() >> 5)];
|
||||
|
||||
return new Ac3Format(channelCount, sampleRate, bitrate);
|
||||
}
|
||||
|
||||
private static int parseEc3SpecificBoxFromParent(ParsableByteArray parent, int position) {
|
||||
// Start of the dec3 atom (defined in ETSI TS 102 366)
|
||||
parent.setPosition(position + ATOM_HEADER_SIZE);
|
||||
// TODO: Implement parsing for enhanced AC-3 with multiple sub-streams.
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static List<byte[]> parseAvcCFromParent(ParsableByteArray parent, int position) {
|
||||
parent.setPosition(position + ATOM_HEADER_SIZE + 4);
|
||||
// Start of the AVCDecoderConfigurationRecord (defined in 14496-15)
|
||||
int nalUnitLength = (parent.readUnsignedByte() & 0x3) + 1;
|
||||
if (nalUnitLength != 4) {
|
||||
// readSample currently relies on a nalUnitLength of 4.
|
||||
// TODO: Consider handling the case where it isn't.
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
List<byte[]> initializationData = new ArrayList<byte[]>();
|
||||
// TODO: We should try and parse these using CodecSpecificDataUtil.parseSpsNalUnit, and
|
||||
// expose the AVC profile and level somewhere useful; Most likely in MediaFormat.
|
||||
int numSequenceParameterSets = parent.readUnsignedByte() & 0x1F;
|
||||
for (int j = 0; j < numSequenceParameterSets; j++) {
|
||||
initializationData.add(parseChildNalUnit(parent));
|
||||
}
|
||||
int numPictureParamterSets = parent.readUnsignedByte();
|
||||
for (int j = 0; j < numPictureParamterSets; j++) {
|
||||
initializationData.add(parseChildNalUnit(parent));
|
||||
}
|
||||
return initializationData;
|
||||
}
|
||||
|
||||
private static byte[] parseChildNalUnit(ParsableByteArray atom) {
|
||||
int length = atom.readUnsignedShort();
|
||||
int offset = atom.getPosition();
|
||||
atom.skip(length);
|
||||
return CodecSpecificDataUtil.buildNalUnit(atom.data, offset, length);
|
||||
}
|
||||
|
||||
private static TrackEncryptionBox parseSinfFromParent(ParsableByteArray parent, int position,
|
||||
int size) {
|
||||
int childPosition = position + ATOM_HEADER_SIZE;
|
||||
|
||||
TrackEncryptionBox trackEncryptionBox = null;
|
||||
while (childPosition - position < size) {
|
||||
parent.setPosition(childPosition);
|
||||
int childAtomSize = parent.readInt();
|
||||
int childAtomType = parent.readInt();
|
||||
if (childAtomType == Atom.TYPE_frma) {
|
||||
parent.readInt(); // dataFormat.
|
||||
} else if (childAtomType == Atom.TYPE_schm) {
|
||||
parent.skip(4);
|
||||
parent.readInt(); // schemeType. Expect cenc
|
||||
parent.readInt(); // schemeVersion. Expect 0x00010000
|
||||
} else if (childAtomType == Atom.TYPE_schi) {
|
||||
trackEncryptionBox = parseSchiFromParent(parent, childPosition, childAtomSize);
|
||||
}
|
||||
childPosition += childAtomSize;
|
||||
}
|
||||
|
||||
return trackEncryptionBox;
|
||||
}
|
||||
|
||||
private static float parsePaspFromParent(ParsableByteArray parent, int position) {
|
||||
parent.setPosition(position + ATOM_HEADER_SIZE);
|
||||
int hSpacing = parent.readUnsignedIntToInt();
|
||||
int vSpacing = parent.readUnsignedIntToInt();
|
||||
return (float) hSpacing / vSpacing;
|
||||
}
|
||||
|
||||
private static TrackEncryptionBox parseSchiFromParent(ParsableByteArray parent, int position,
|
||||
int size) {
|
||||
int childPosition = position + ATOM_HEADER_SIZE;
|
||||
while (childPosition - position < size) {
|
||||
parent.setPosition(childPosition);
|
||||
int childAtomSize = parent.readInt();
|
||||
int childAtomType = parent.readInt();
|
||||
if (childAtomType == Atom.TYPE_tenc) {
|
||||
parent.skip(4);
|
||||
int firstInt = parent.readInt();
|
||||
boolean defaultIsEncrypted = (firstInt >> 8) == 1;
|
||||
int defaultInitVectorSize = firstInt & 0xFF;
|
||||
byte[] defaultKeyId = new byte[16];
|
||||
parent.readBytes(defaultKeyId, 0, defaultKeyId.length);
|
||||
return new TrackEncryptionBox(defaultIsEncrypted, defaultInitVectorSize, defaultKeyId);
|
||||
}
|
||||
childPosition += childAtomSize;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static byte[] parseEsdsFromParent(ParsableByteArray parent, int position) {
|
||||
parent.setPosition(position + ATOM_HEADER_SIZE + 4);
|
||||
// Start of the ES_Descriptor (defined in 14496-1)
|
||||
parent.skip(1); // ES_Descriptor tag
|
||||
int varIntByte = parent.readUnsignedByte();
|
||||
while (varIntByte > 127) {
|
||||
varIntByte = parent.readUnsignedByte();
|
||||
}
|
||||
parent.skip(2); // ES_ID
|
||||
|
||||
int flags = parent.readUnsignedByte();
|
||||
if ((flags & 0x80 /* streamDependenceFlag */) != 0) {
|
||||
parent.skip(2);
|
||||
}
|
||||
if ((flags & 0x40 /* URL_Flag */) != 0) {
|
||||
parent.skip(parent.readUnsignedShort());
|
||||
}
|
||||
if ((flags & 0x20 /* OCRstreamFlag */) != 0) {
|
||||
parent.skip(2);
|
||||
}
|
||||
|
||||
// Start of the DecoderConfigDescriptor (defined in 14496-1)
|
||||
parent.skip(1); // DecoderConfigDescriptor tag
|
||||
varIntByte = parent.readUnsignedByte();
|
||||
while (varIntByte > 127) {
|
||||
varIntByte = parent.readUnsignedByte();
|
||||
}
|
||||
parent.skip(13);
|
||||
|
||||
// Start of AudioSpecificConfig (defined in 14496-3)
|
||||
parent.skip(1); // AudioSpecificConfig tag
|
||||
varIntByte = parent.readUnsignedByte();
|
||||
int varInt = varIntByte & 0x7F;
|
||||
while (varIntByte > 127) {
|
||||
varIntByte = parent.readUnsignedByte();
|
||||
varInt = varInt << 8;
|
||||
varInt |= varIntByte & 0x7F;
|
||||
}
|
||||
byte[] initializationData = new byte[varInt];
|
||||
parent.readBytes(initializationData, 0, varInt);
|
||||
return initializationData;
|
||||
}
|
||||
|
||||
private static void parseMoof(Track track, DefaultSampleValues extendsDefaults,
|
||||
ContainerAtom moof, TrackFragment out, int workaroundFlags, byte[] extendedTypeScratch) {
|
||||
parseTraf(track, extendsDefaults, moof.getContainerAtomOfType(Atom.TYPE_traf),
|
||||
|
|
@ -836,11 +446,11 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
parseSenc(senc.data, out);
|
||||
}
|
||||
|
||||
int childrenSize = traf.children.size();
|
||||
int childrenSize = traf.leafChildren.size();
|
||||
for (int i = 0; i < childrenSize; i++) {
|
||||
Atom atom = traf.children.get(i);
|
||||
LeafAtom atom = traf.leafChildren.get(i);
|
||||
if (atom.type == Atom.TYPE_uuid) {
|
||||
parseUuid(((LeafAtom) atom).data, out, extendedTypeScratch);
|
||||
parseUuid(atom.data, out, extendedTypeScratch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -848,9 +458,9 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
private static void parseSaiz(TrackEncryptionBox encryptionBox, ParsableByteArray saiz,
|
||||
TrackFragment out) {
|
||||
int vectorSize = encryptionBox.initializationVectorSize;
|
||||
saiz.setPosition(ATOM_HEADER_SIZE);
|
||||
saiz.setPosition(Mp4Util.ATOM_HEADER_SIZE);
|
||||
int fullAtom = saiz.readInt();
|
||||
int flags = parseFullAtomFlags(fullAtom);
|
||||
int flags = Mp4Util.parseFullAtomFlags(fullAtom);
|
||||
if ((flags & 0x01) == 1) {
|
||||
saiz.skip(8);
|
||||
}
|
||||
|
|
@ -885,9 +495,9 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
*/
|
||||
private static DefaultSampleValues parseTfhd(DefaultSampleValues extendsDefaults,
|
||||
ParsableByteArray tfhd) {
|
||||
tfhd.setPosition(ATOM_HEADER_SIZE);
|
||||
tfhd.setPosition(Mp4Util.ATOM_HEADER_SIZE);
|
||||
int fullAtom = tfhd.readInt();
|
||||
int flags = parseFullAtomFlags(fullAtom);
|
||||
int flags = Mp4Util.parseFullAtomFlags(fullAtom);
|
||||
|
||||
tfhd.skip(4); // trackId
|
||||
if ((flags & 0x01 /* base_data_offset_present */) != 0) {
|
||||
|
|
@ -910,13 +520,13 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
/**
|
||||
* Parses a tfdt atom (defined in 14496-12).
|
||||
*
|
||||
* @return baseMediaDecodeTime. The sum of the decode durations of all earlier samples in the
|
||||
* @return baseMediaDecodeTime The sum of the decode durations of all earlier samples in the
|
||||
* media, expressed in the media's timescale.
|
||||
*/
|
||||
private static long parseTfdt(ParsableByteArray tfdt) {
|
||||
tfdt.setPosition(ATOM_HEADER_SIZE);
|
||||
tfdt.setPosition(Mp4Util.ATOM_HEADER_SIZE);
|
||||
int fullAtom = tfdt.readInt();
|
||||
int version = parseFullAtomVersion(fullAtom);
|
||||
int version = Mp4Util.parseFullAtomVersion(fullAtom);
|
||||
return version == 1 ? tfdt.readUnsignedLongToLong() : tfdt.readUnsignedInt();
|
||||
}
|
||||
|
||||
|
|
@ -931,9 +541,9 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
*/
|
||||
private static void parseTrun(Track track, DefaultSampleValues defaultSampleValues,
|
||||
long decodeTime, int workaroundFlags, ParsableByteArray trun, TrackFragment out) {
|
||||
trun.setPosition(ATOM_HEADER_SIZE);
|
||||
trun.setPosition(Mp4Util.ATOM_HEADER_SIZE);
|
||||
int fullAtom = trun.readInt();
|
||||
int flags = parseFullAtomFlags(fullAtom);
|
||||
int flags = Mp4Util.parseFullAtomFlags(fullAtom);
|
||||
|
||||
int sampleCount = trun.readUnsignedIntToInt();
|
||||
if ((flags & 0x01 /* data_offset_present */) != 0) {
|
||||
|
|
@ -991,7 +601,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
|
||||
private static void parseUuid(ParsableByteArray uuid, TrackFragment out,
|
||||
byte[] extendedTypeScratch) {
|
||||
uuid.setPosition(ATOM_HEADER_SIZE);
|
||||
uuid.setPosition(Mp4Util.ATOM_HEADER_SIZE);
|
||||
uuid.readBytes(extendedTypeScratch, 0, 16);
|
||||
|
||||
// Currently this parser only supports Microsoft's PIFF SampleEncryptionBox.
|
||||
|
|
@ -1010,9 +620,9 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
}
|
||||
|
||||
private static void parseSenc(ParsableByteArray senc, int offset, TrackFragment out) {
|
||||
senc.setPosition(ATOM_HEADER_SIZE + offset);
|
||||
senc.setPosition(Mp4Util.ATOM_HEADER_SIZE + offset);
|
||||
int fullAtom = senc.readInt();
|
||||
int flags = parseFullAtomFlags(fullAtom);
|
||||
int flags = Mp4Util.parseFullAtomFlags(fullAtom);
|
||||
|
||||
if ((flags & 0x01 /* override_track_encryption_box_parameters */) != 0) {
|
||||
// TODO: Implement this.
|
||||
|
|
@ -1034,9 +644,9 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
* Parses a sidx atom (defined in 14496-12).
|
||||
*/
|
||||
private static SegmentIndex parseSidx(ParsableByteArray atom) {
|
||||
atom.setPosition(ATOM_HEADER_SIZE);
|
||||
atom.setPosition(Mp4Util.ATOM_HEADER_SIZE);
|
||||
int fullAtom = atom.readInt();
|
||||
int version = parseFullAtomVersion(fullAtom);
|
||||
int version = Mp4Util.parseFullAtomVersion(fullAtom);
|
||||
|
||||
atom.skip(4);
|
||||
long timescale = atom.readUnsignedInt();
|
||||
|
|
@ -1176,17 +786,8 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
inputStream.read(outputData, sampleSize);
|
||||
if (track.type == Track.TYPE_VIDEO) {
|
||||
// The mp4 file contains length-prefixed NAL units, but the decoder wants start code
|
||||
// delimited content. Replace length prefixes with start codes.
|
||||
int sampleOffset = outputData.position() - sampleSize;
|
||||
int position = sampleOffset;
|
||||
while (position < sampleOffset + sampleSize) {
|
||||
outputData.position(position);
|
||||
int length = readUnsignedIntToInt(outputData);
|
||||
outputData.position(position);
|
||||
outputData.put(NAL_START_CODE);
|
||||
position += length + 4;
|
||||
}
|
||||
outputData.position(sampleOffset + sampleSize);
|
||||
// delimited content.
|
||||
Mp4Util.replaceLengthPrefixesWithAvcStartCodes(outputData, sampleSize);
|
||||
}
|
||||
out.size = sampleSize;
|
||||
}
|
||||
|
|
@ -1236,51 +837,4 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the version number out of the additional integer component of a full atom.
|
||||
*/
|
||||
private static int parseFullAtomVersion(int fullAtomInt) {
|
||||
return 0x000000FF & (fullAtomInt >> 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the atom flags out of the additional integer component of a full atom.
|
||||
*/
|
||||
private static int parseFullAtomFlags(int fullAtomInt) {
|
||||
return 0x00FFFFFF & fullAtomInt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an unsigned integer into an integer. This method is suitable for use when it can be
|
||||
* assumed that the top bit will always be set to zero.
|
||||
*
|
||||
* @throws IllegalArgumentException If the top bit of the input data is set.
|
||||
*/
|
||||
private static int readUnsignedIntToInt(ByteBuffer data) {
|
||||
int result = 0xFF & data.get();
|
||||
for (int i = 1; i < 4; i++) {
|
||||
result <<= 8;
|
||||
result |= 0xFF & data.get();
|
||||
}
|
||||
if (result < 0) {
|
||||
throw new IllegalArgumentException("Top bit not zero: " + result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Represents the format for AC-3 audio. */
|
||||
private static final class Ac3Format {
|
||||
|
||||
public final int channelCount;
|
||||
public final int sampleRate;
|
||||
public final int bitrate;
|
||||
|
||||
public Ac3Format(int channelCount, int sampleRate, int bitrate) {
|
||||
this.channelCount = channelCount;
|
||||
this.sampleRate = sampleRate;
|
||||
this.bitrate = bitrate;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package com.google.android.exoplayer.chunk.parser.webm;
|
||||
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.MediaFormat;
|
||||
import com.google.android.exoplayer.ParserException;
|
||||
import com.google.android.exoplayer.SampleHolder;
|
||||
|
|
@ -184,6 +185,11 @@ public final class WebmExtractor implements Extractor {
|
|||
return format;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDurationUs() {
|
||||
return durationUs == UNKNOWN ? C.UNKNOWN_TIME_US : durationUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<UUID, byte[]> getPsshInfo() {
|
||||
// TODO: Parse pssh data from Webm streams.
|
||||
|
|
|
|||
|
|
@ -15,52 +15,64 @@
|
|||
*/
|
||||
package com.google.android.exoplayer.mp4;
|
||||
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class Atom {
|
||||
|
||||
public static final int TYPE_avc1 = 0x61766331;
|
||||
public static final int TYPE_avc3 = 0x61766333;
|
||||
public static final int TYPE_esds = 0x65736473;
|
||||
public static final int TYPE_mdat = 0x6D646174;
|
||||
public static final int TYPE_mp4a = 0x6D703461;
|
||||
public static final int TYPE_ac_3 = 0x61632D33; // ac-3
|
||||
public static final int TYPE_dac3 = 0x64616333;
|
||||
public static final int TYPE_ec_3 = 0x65632D33; // ec-3
|
||||
public static final int TYPE_dec3 = 0x64656333;
|
||||
public static final int TYPE_tfdt = 0x74666474;
|
||||
public static final int TYPE_tfhd = 0x74666864;
|
||||
public static final int TYPE_trex = 0x74726578;
|
||||
public static final int TYPE_trun = 0x7472756E;
|
||||
public static final int TYPE_sidx = 0x73696478;
|
||||
public static final int TYPE_moov = 0x6D6F6F76;
|
||||
public static final int TYPE_trak = 0x7472616B;
|
||||
public static final int TYPE_mdia = 0x6D646961;
|
||||
public static final int TYPE_minf = 0x6D696E66;
|
||||
public static final int TYPE_stbl = 0x7374626C;
|
||||
public static final int TYPE_avcC = 0x61766343;
|
||||
public static final int TYPE_moof = 0x6D6F6F66;
|
||||
public static final int TYPE_traf = 0x74726166;
|
||||
public static final int TYPE_mvex = 0x6D766578;
|
||||
public static final int TYPE_tkhd = 0x746B6864;
|
||||
public static final int TYPE_mdhd = 0x6D646864;
|
||||
public static final int TYPE_hdlr = 0x68646C72;
|
||||
public static final int TYPE_stsd = 0x73747364;
|
||||
public static final int TYPE_pssh = 0x70737368;
|
||||
public static final int TYPE_sinf = 0x73696E66;
|
||||
public static final int TYPE_schm = 0x7363686D;
|
||||
public static final int TYPE_schi = 0x73636869;
|
||||
public static final int TYPE_tenc = 0x74656E63;
|
||||
public static final int TYPE_encv = 0x656E6376;
|
||||
public static final int TYPE_enca = 0x656E6361;
|
||||
public static final int TYPE_frma = 0x66726D61;
|
||||
public static final int TYPE_saiz = 0x7361697A;
|
||||
public static final int TYPE_uuid = 0x75756964;
|
||||
public static final int TYPE_senc = 0x73656E63;
|
||||
public static final int TYPE_pasp = 0x70617370;
|
||||
public static final int TYPE_TTML = 0x54544D4C;
|
||||
public static final int TYPE_avc1 = getAtomTypeInteger("avc1");
|
||||
public static final int TYPE_avc3 = getAtomTypeInteger("avc3");
|
||||
public static final int TYPE_esds = getAtomTypeInteger("esds");
|
||||
public static final int TYPE_mdat = getAtomTypeInteger("mdat");
|
||||
public static final int TYPE_mp4a = getAtomTypeInteger("mp4a");
|
||||
public static final int TYPE_ac_3 = getAtomTypeInteger("ac-3");
|
||||
public static final int TYPE_dac3 = getAtomTypeInteger("dac3");
|
||||
public static final int TYPE_ec_3 = getAtomTypeInteger("ec-3");
|
||||
public static final int TYPE_dec3 = getAtomTypeInteger("dec3");
|
||||
public static final int TYPE_tfdt = getAtomTypeInteger("tfdt");
|
||||
public static final int TYPE_tfhd = getAtomTypeInteger("tfhd");
|
||||
public static final int TYPE_trex = getAtomTypeInteger("trex");
|
||||
public static final int TYPE_trun = getAtomTypeInteger("trun");
|
||||
public static final int TYPE_sidx = getAtomTypeInteger("sidx");
|
||||
public static final int TYPE_moov = getAtomTypeInteger("moov");
|
||||
public static final int TYPE_trak = getAtomTypeInteger("trak");
|
||||
public static final int TYPE_mdia = getAtomTypeInteger("mdia");
|
||||
public static final int TYPE_minf = getAtomTypeInteger("minf");
|
||||
public static final int TYPE_stbl = getAtomTypeInteger("stbl");
|
||||
public static final int TYPE_avcC = getAtomTypeInteger("avcC");
|
||||
public static final int TYPE_moof = getAtomTypeInteger("moof");
|
||||
public static final int TYPE_traf = getAtomTypeInteger("traf");
|
||||
public static final int TYPE_mvex = getAtomTypeInteger("mvex");
|
||||
public static final int TYPE_tkhd = getAtomTypeInteger("tkhd");
|
||||
public static final int TYPE_mdhd = getAtomTypeInteger("mdhd");
|
||||
public static final int TYPE_hdlr = getAtomTypeInteger("hdlr");
|
||||
public static final int TYPE_stsd = getAtomTypeInteger("stsd");
|
||||
public static final int TYPE_pssh = getAtomTypeInteger("pssh");
|
||||
public static final int TYPE_sinf = getAtomTypeInteger("sinf");
|
||||
public static final int TYPE_schm = getAtomTypeInteger("schm");
|
||||
public static final int TYPE_schi = getAtomTypeInteger("schi");
|
||||
public static final int TYPE_tenc = getAtomTypeInteger("tenc");
|
||||
public static final int TYPE_encv = getAtomTypeInteger("encv");
|
||||
public static final int TYPE_enca = getAtomTypeInteger("enca");
|
||||
public static final int TYPE_frma = getAtomTypeInteger("frma");
|
||||
public static final int TYPE_saiz = getAtomTypeInteger("saiz");
|
||||
public static final int TYPE_uuid = getAtomTypeInteger("uuid");
|
||||
public static final int TYPE_senc = getAtomTypeInteger("senc");
|
||||
public static final int TYPE_pasp = getAtomTypeInteger("pasp");
|
||||
public static final int TYPE_TTML = getAtomTypeInteger("TTML");
|
||||
public static final int TYPE_vmhd = getAtomTypeInteger("vmhd");
|
||||
public static final int TYPE_smhd = getAtomTypeInteger("smhd");
|
||||
public static final int TYPE_mp4v = getAtomTypeInteger("mp4v");
|
||||
public static final int TYPE_stts = getAtomTypeInteger("stts");
|
||||
public static final int TYPE_stss = getAtomTypeInteger("stss");
|
||||
public static final int TYPE_stsc = getAtomTypeInteger("stsc");
|
||||
public static final int TYPE_stsz = getAtomTypeInteger("stsz");
|
||||
public static final int TYPE_stco = getAtomTypeInteger("stco");
|
||||
public static final int TYPE_co64 = getAtomTypeInteger("co64");
|
||||
|
||||
public final int type;
|
||||
|
||||
|
|
@ -68,6 +80,12 @@ public abstract class Atom {
|
|||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getAtomTypeString(type);
|
||||
}
|
||||
|
||||
/** An MP4 atom that is a leaf. */
|
||||
public static final class LeafAtom extends Atom {
|
||||
|
||||
public final ParsableByteArray data;
|
||||
|
|
@ -79,43 +97,75 @@ public abstract class Atom {
|
|||
|
||||
}
|
||||
|
||||
/** An MP4 atom that has child atoms. */
|
||||
public static final class ContainerAtom extends Atom {
|
||||
|
||||
public final ArrayList<Atom> children;
|
||||
public final int endByteOffset;
|
||||
public final long endByteOffset;
|
||||
public final List<LeafAtom> leafChildren;
|
||||
public final List<ContainerAtom> containerChildren;
|
||||
|
||||
public ContainerAtom(int type, int endByteOffset) {
|
||||
public ContainerAtom(int type, long endByteOffset) {
|
||||
super(type);
|
||||
|
||||
leafChildren = new ArrayList<LeafAtom>();
|
||||
containerChildren = new ArrayList<ContainerAtom>();
|
||||
this.endByteOffset = endByteOffset;
|
||||
children = new ArrayList<Atom>();
|
||||
}
|
||||
|
||||
public void add(Atom atom) {
|
||||
children.add(atom);
|
||||
public void add(LeafAtom atom) {
|
||||
leafChildren.add(atom);
|
||||
}
|
||||
|
||||
public void add(ContainerAtom atom) {
|
||||
containerChildren.add(atom);
|
||||
}
|
||||
|
||||
public LeafAtom getLeafAtomOfType(int type) {
|
||||
int childrenSize = children.size();
|
||||
int childrenSize = leafChildren.size();
|
||||
for (int i = 0; i < childrenSize; i++) {
|
||||
Atom atom = children.get(i);
|
||||
LeafAtom atom = leafChildren.get(i);
|
||||
if (atom.type == type) {
|
||||
return (LeafAtom) atom;
|
||||
return atom;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ContainerAtom getContainerAtomOfType(int type) {
|
||||
int childrenSize = children.size();
|
||||
int childrenSize = containerChildren.size();
|
||||
for (int i = 0; i < childrenSize; i++) {
|
||||
Atom atom = children.get(i);
|
||||
ContainerAtom atom = containerChildren.get(i);
|
||||
if (atom.type == type) {
|
||||
return (ContainerAtom) atom;
|
||||
return atom;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getAtomTypeString(type)
|
||||
+ " leaves: " + Arrays.toString(leafChildren.toArray(new LeafAtom[0]))
|
||||
+ " containers: " + Arrays.toString(containerChildren.toArray(new ContainerAtom[0]));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static String getAtomTypeString(int type) {
|
||||
return "" + (char) (type >> 24)
|
||||
+ (char) ((type >> 16) & 0xFF)
|
||||
+ (char) ((type >> 8) & 0xFF)
|
||||
+ (char) (type & 0xFF);
|
||||
}
|
||||
|
||||
private static int getAtomTypeInteger(String typeName) {
|
||||
Assertions.checkArgument(typeName.length() == 4);
|
||||
int result = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
result <<= 8;
|
||||
result |= typeName.charAt(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,481 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.exoplayer.mp4;
|
||||
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.MediaFormat;
|
||||
import com.google.android.exoplayer.chunk.parser.mp4.TrackEncryptionBox;
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** Utility methods for parsing MP4 format atom payloads according to ISO 14496-12. */
|
||||
public final class CommonMp4AtomParsers {
|
||||
|
||||
/** Channel counts for AC-3 audio, indexed by acmod. (See ETSI TS 102 366.) */
|
||||
private static final int[] AC3_CHANNEL_COUNTS = new int[] {2, 1, 2, 3, 3, 4, 4, 5};
|
||||
/** Nominal bit-rates for AC-3 audio in kbps, indexed by bit_rate_code. (See ETSI TS 102 366.) */
|
||||
private static final int[] AC3_BIT_RATES = new int[] {32, 40, 48, 56, 64, 80, 96, 112, 128, 160,
|
||||
192, 224, 256, 320, 384, 448, 512, 576, 640};
|
||||
|
||||
/**
|
||||
* Parses a trak atom (defined in 14496-12).
|
||||
*
|
||||
* @return A {@link Track} instance.
|
||||
*/
|
||||
public static Track parseTrak(Atom.ContainerAtom trak) {
|
||||
Atom.ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia);
|
||||
int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data);
|
||||
Assertions.checkState(trackType == Track.TYPE_AUDIO || trackType == Track.TYPE_VIDEO
|
||||
|| trackType == Track.TYPE_TEXT || trackType == Track.TYPE_TIME_CODE);
|
||||
|
||||
Pair<Integer, Long> header = parseTkhd(trak.getLeafAtomOfType(Atom.TYPE_tkhd).data);
|
||||
int id = header.first;
|
||||
long duration = header.second;
|
||||
long timescale = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data);
|
||||
long durationUs;
|
||||
if (duration == -1) {
|
||||
durationUs = C.UNKNOWN_TIME_US;
|
||||
} else {
|
||||
durationUs = Util.scaleLargeTimestamp(duration, C.MICROS_PER_SECOND, timescale);
|
||||
}
|
||||
Atom.ContainerAtom stbl = mdia.getContainerAtomOfType(Atom.TYPE_minf)
|
||||
.getContainerAtomOfType(Atom.TYPE_stbl);
|
||||
|
||||
Pair<MediaFormat, TrackEncryptionBox[]> sampleDescriptions =
|
||||
parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data);
|
||||
return new Track(id, trackType, timescale, durationUs, sampleDescriptions.first,
|
||||
sampleDescriptions.second);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a tkhd atom (defined in 14496-12).
|
||||
*
|
||||
* @return A {@link Pair} consisting of the track id and duration (in the timescale indicated in
|
||||
* the movie header box). The duration is set to -1 if the duration is unspecified.
|
||||
*/
|
||||
private static Pair<Integer, Long> parseTkhd(ParsableByteArray tkhd) {
|
||||
tkhd.setPosition(Mp4Util.ATOM_HEADER_SIZE);
|
||||
int fullAtom = tkhd.readInt();
|
||||
int version = Mp4Util.parseFullAtomVersion(fullAtom);
|
||||
|
||||
tkhd.skip(version == 0 ? 8 : 16);
|
||||
|
||||
int trackId = tkhd.readInt();
|
||||
tkhd.skip(4);
|
||||
|
||||
boolean durationUnknown = true;
|
||||
int durationPosition = tkhd.getPosition();
|
||||
int durationByteCount = version == 0 ? 4 : 8;
|
||||
for (int i = 0; i < durationByteCount; i++) {
|
||||
if (tkhd.data[durationPosition + i] != -1) {
|
||||
durationUnknown = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
long duration;
|
||||
if (durationUnknown) {
|
||||
tkhd.skip(durationByteCount);
|
||||
duration = -1;
|
||||
} else {
|
||||
duration = version == 0 ? tkhd.readUnsignedInt() : tkhd.readUnsignedLongToLong();
|
||||
}
|
||||
|
||||
return Pair.create(trackId, duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an hdlr atom.
|
||||
*
|
||||
* @param hdlr The hdlr atom to parse.
|
||||
* @return The track type.
|
||||
*/
|
||||
private static int parseHdlr(ParsableByteArray hdlr) {
|
||||
hdlr.setPosition(Mp4Util.FULL_ATOM_HEADER_SIZE + 4);
|
||||
return hdlr.readInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an mdhd atom (defined in 14496-12).
|
||||
*
|
||||
* @param mdhd The mdhd atom to parse.
|
||||
* @return The media timescale, defined as the number of time units that pass in one second.
|
||||
*/
|
||||
private static long parseMdhd(ParsableByteArray mdhd) {
|
||||
mdhd.setPosition(Mp4Util.ATOM_HEADER_SIZE);
|
||||
int fullAtom = mdhd.readInt();
|
||||
int version = Mp4Util.parseFullAtomVersion(fullAtom);
|
||||
|
||||
mdhd.skip(version == 0 ? 8 : 16);
|
||||
return mdhd.readUnsignedInt();
|
||||
}
|
||||
|
||||
private static Pair<MediaFormat, TrackEncryptionBox[]> parseStsd(ParsableByteArray stsd) {
|
||||
stsd.setPosition(Mp4Util.FULL_ATOM_HEADER_SIZE);
|
||||
int numberOfEntries = stsd.readInt();
|
||||
MediaFormat mediaFormat = null;
|
||||
TrackEncryptionBox[] trackEncryptionBoxes = new TrackEncryptionBox[numberOfEntries];
|
||||
for (int i = 0; i < numberOfEntries; i++) {
|
||||
int childStartPosition = stsd.getPosition();
|
||||
int childAtomSize = stsd.readInt();
|
||||
Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive");
|
||||
int childAtomType = stsd.readInt();
|
||||
if (childAtomType == Atom.TYPE_avc1 || childAtomType == Atom.TYPE_avc3
|
||||
|| childAtomType == Atom.TYPE_encv) {
|
||||
Pair<MediaFormat, TrackEncryptionBox> avc =
|
||||
parseAvcFromParent(stsd, childStartPosition, childAtomSize);
|
||||
mediaFormat = avc.first;
|
||||
trackEncryptionBoxes[i] = avc.second;
|
||||
} else if (childAtomType == Atom.TYPE_mp4a || childAtomType == Atom.TYPE_enca
|
||||
|| childAtomType == Atom.TYPE_ac_3) {
|
||||
Pair<MediaFormat, TrackEncryptionBox> audioSampleEntry =
|
||||
parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize);
|
||||
mediaFormat = audioSampleEntry.first;
|
||||
trackEncryptionBoxes[i] = audioSampleEntry.second;
|
||||
} else if (childAtomType == Atom.TYPE_TTML) {
|
||||
mediaFormat = MediaFormat.createTtmlFormat();
|
||||
} else if (childAtomType == Atom.TYPE_mp4v) {
|
||||
mediaFormat = parseMp4vFromParent(stsd, childStartPosition, childAtomSize);
|
||||
}
|
||||
stsd.setPosition(childStartPosition + childAtomSize);
|
||||
}
|
||||
return Pair.create(mediaFormat, trackEncryptionBoxes);
|
||||
}
|
||||
|
||||
/** Returns the media format for an avc1 box. */
|
||||
private static Pair<MediaFormat, TrackEncryptionBox> parseAvcFromParent(ParsableByteArray parent,
|
||||
int position, int size) {
|
||||
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
|
||||
|
||||
parent.skip(24);
|
||||
int width = parent.readUnsignedShort();
|
||||
int height = parent.readUnsignedShort();
|
||||
float pixelWidthHeightRatio = 1;
|
||||
parent.skip(50);
|
||||
|
||||
List<byte[]> initializationData = null;
|
||||
TrackEncryptionBox trackEncryptionBox = null;
|
||||
int childPosition = parent.getPosition();
|
||||
while (childPosition - position < size) {
|
||||
parent.setPosition(childPosition);
|
||||
int childStartPosition = parent.getPosition();
|
||||
int childAtomSize = parent.readInt();
|
||||
if (childAtomSize == 0 && parent.getPosition() - position == size) {
|
||||
// Handle optional terminating four zero bytes in MOV files.
|
||||
break;
|
||||
}
|
||||
Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive");
|
||||
int childAtomType = parent.readInt();
|
||||
if (childAtomType == Atom.TYPE_avcC) {
|
||||
initializationData = parseAvcCFromParent(parent, childStartPosition);
|
||||
} else if (childAtomType == Atom.TYPE_sinf) {
|
||||
trackEncryptionBox = parseSinfFromParent(parent, childStartPosition, childAtomSize);
|
||||
} else if (childAtomType == Atom.TYPE_pasp) {
|
||||
pixelWidthHeightRatio = parsePaspFromParent(parent, childStartPosition);
|
||||
}
|
||||
childPosition += childAtomSize;
|
||||
}
|
||||
|
||||
MediaFormat format = MediaFormat.createVideoFormat(MimeTypes.VIDEO_H264, MediaFormat.NO_VALUE,
|
||||
width, height, pixelWidthHeightRatio, initializationData);
|
||||
return Pair.create(format, trackEncryptionBox);
|
||||
}
|
||||
|
||||
private static List<byte[]> parseAvcCFromParent(ParsableByteArray parent, int position) {
|
||||
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE + 4);
|
||||
// Start of the AVCDecoderConfigurationRecord (defined in 14496-15)
|
||||
int nalUnitLength = (parent.readUnsignedByte() & 0x3) + 1;
|
||||
if (nalUnitLength != 4) {
|
||||
// readSample currently relies on a nalUnitLength of 4.
|
||||
// TODO: Consider handling the case where it isn't.
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
List<byte[]> initializationData = new ArrayList<byte[]>();
|
||||
// TODO: We should try and parse these using CodecSpecificDataUtil.parseSpsNalUnit, and
|
||||
// expose the AVC profile and level somewhere useful; Most likely in MediaFormat.
|
||||
int numSequenceParameterSets = parent.readUnsignedByte() & 0x1F;
|
||||
for (int j = 0; j < numSequenceParameterSets; j++) {
|
||||
initializationData.add(Mp4Util.parseChildNalUnit(parent));
|
||||
}
|
||||
int numPictureParameterSets = parent.readUnsignedByte();
|
||||
for (int j = 0; j < numPictureParameterSets; j++) {
|
||||
initializationData.add(Mp4Util.parseChildNalUnit(parent));
|
||||
}
|
||||
return initializationData;
|
||||
}
|
||||
|
||||
private static TrackEncryptionBox parseSinfFromParent(ParsableByteArray parent, int position,
|
||||
int size) {
|
||||
int childPosition = position + Mp4Util.ATOM_HEADER_SIZE;
|
||||
|
||||
TrackEncryptionBox trackEncryptionBox = null;
|
||||
while (childPosition - position < size) {
|
||||
parent.setPosition(childPosition);
|
||||
int childAtomSize = parent.readInt();
|
||||
int childAtomType = parent.readInt();
|
||||
if (childAtomType == Atom.TYPE_frma) {
|
||||
parent.readInt(); // dataFormat.
|
||||
} else if (childAtomType == Atom.TYPE_schm) {
|
||||
parent.skip(4);
|
||||
parent.readInt(); // schemeType. Expect cenc
|
||||
parent.readInt(); // schemeVersion. Expect 0x00010000
|
||||
} else if (childAtomType == Atom.TYPE_schi) {
|
||||
trackEncryptionBox = parseSchiFromParent(parent, childPosition, childAtomSize);
|
||||
}
|
||||
childPosition += childAtomSize;
|
||||
}
|
||||
|
||||
return trackEncryptionBox;
|
||||
}
|
||||
|
||||
private static float parsePaspFromParent(ParsableByteArray parent, int position) {
|
||||
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
|
||||
int hSpacing = parent.readUnsignedIntToInt();
|
||||
int vSpacing = parent.readUnsignedIntToInt();
|
||||
return (float) hSpacing / vSpacing;
|
||||
}
|
||||
|
||||
private static TrackEncryptionBox parseSchiFromParent(ParsableByteArray parent, int position,
|
||||
int size) {
|
||||
int childPosition = position + Mp4Util.ATOM_HEADER_SIZE;
|
||||
while (childPosition - position < size) {
|
||||
parent.setPosition(childPosition);
|
||||
int childAtomSize = parent.readInt();
|
||||
int childAtomType = parent.readInt();
|
||||
if (childAtomType == Atom.TYPE_tenc) {
|
||||
parent.skip(4);
|
||||
int firstInt = parent.readInt();
|
||||
boolean defaultIsEncrypted = (firstInt >> 8) == 1;
|
||||
int defaultInitVectorSize = firstInt & 0xFF;
|
||||
byte[] defaultKeyId = new byte[16];
|
||||
parent.readBytes(defaultKeyId, 0, defaultKeyId.length);
|
||||
return new TrackEncryptionBox(defaultIsEncrypted, defaultInitVectorSize, defaultKeyId);
|
||||
}
|
||||
childPosition += childAtomSize;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Returns the media format for an mp4v box. */
|
||||
private static MediaFormat parseMp4vFromParent(ParsableByteArray parent,
|
||||
int position, int size) {
|
||||
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
|
||||
|
||||
parent.skip(24);
|
||||
int width = parent.readUnsignedShort();
|
||||
int height = parent.readUnsignedShort();
|
||||
parent.skip(50);
|
||||
|
||||
List<byte[]> initializationData = new ArrayList<byte[]>(1);
|
||||
int childPosition = parent.getPosition();
|
||||
while (childPosition - position < size) {
|
||||
parent.setPosition(childPosition);
|
||||
int childStartPosition = parent.getPosition();
|
||||
int childAtomSize = parent.readInt();
|
||||
Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive");
|
||||
int childAtomType = parent.readInt();
|
||||
if (childAtomType == Atom.TYPE_esds) {
|
||||
initializationData.add(parseEsdsFromParent(parent, childStartPosition));
|
||||
}
|
||||
childPosition += childAtomSize;
|
||||
}
|
||||
|
||||
return MediaFormat.createVideoFormat(
|
||||
MimeTypes.VIDEO_MP4V, MediaFormat.NO_VALUE, width, height, initializationData);
|
||||
}
|
||||
|
||||
private static Pair<MediaFormat, TrackEncryptionBox> parseAudioSampleEntry(
|
||||
ParsableByteArray parent, int atomType, int position, int size) {
|
||||
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
|
||||
parent.skip(16);
|
||||
int channelCount = parent.readUnsignedShort();
|
||||
int sampleSize = parent.readUnsignedShort();
|
||||
parent.skip(4);
|
||||
int sampleRate = parent.readUnsignedFixedPoint1616();
|
||||
int bitrate = MediaFormat.NO_VALUE;
|
||||
|
||||
byte[] initializationData = null;
|
||||
TrackEncryptionBox trackEncryptionBox = null;
|
||||
int childPosition = parent.getPosition();
|
||||
while (childPosition - position < size) {
|
||||
parent.setPosition(childPosition);
|
||||
int childStartPosition = parent.getPosition();
|
||||
int childAtomSize = parent.readInt();
|
||||
Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive");
|
||||
int childAtomType = parent.readInt();
|
||||
if (atomType == Atom.TYPE_mp4a || atomType == Atom.TYPE_enca) {
|
||||
if (childAtomType == Atom.TYPE_esds) {
|
||||
initializationData = parseEsdsFromParent(parent, childStartPosition);
|
||||
// TODO: Do we really need to do this? See [Internal: b/10903778]
|
||||
// Update sampleRate and channelCount from the AudioSpecificConfig initialization data.
|
||||
Pair<Integer, Integer> audioSpecificConfig =
|
||||
CodecSpecificDataUtil.parseAudioSpecificConfig(initializationData);
|
||||
sampleRate = audioSpecificConfig.first;
|
||||
channelCount = audioSpecificConfig.second;
|
||||
} else if (childAtomType == Atom.TYPE_sinf) {
|
||||
trackEncryptionBox = parseSinfFromParent(parent, childStartPosition, childAtomSize);
|
||||
}
|
||||
} else if (atomType == Atom.TYPE_ac_3 && childAtomType == Atom.TYPE_dac3) {
|
||||
// TODO: Choose the right AC-3 track based on the contents of dac3/dec3.
|
||||
Ac3Format ac3Format =
|
||||
parseAc3SpecificBoxFromParent(parent, childStartPosition);
|
||||
if (ac3Format != null) {
|
||||
sampleRate = ac3Format.sampleRate;
|
||||
channelCount = ac3Format.channelCount;
|
||||
bitrate = ac3Format.bitrate;
|
||||
}
|
||||
|
||||
// TODO: Add support for encrypted AC-3.
|
||||
trackEncryptionBox = null;
|
||||
} else if (atomType == Atom.TYPE_ec_3 && childAtomType == Atom.TYPE_dec3) {
|
||||
sampleRate = parseEc3SpecificBoxFromParent(parent, childStartPosition);
|
||||
trackEncryptionBox = null;
|
||||
}
|
||||
childPosition += childAtomSize;
|
||||
}
|
||||
|
||||
String mimeType;
|
||||
if (atomType == Atom.TYPE_ac_3) {
|
||||
mimeType = MimeTypes.AUDIO_AC3;
|
||||
} else if (atomType == Atom.TYPE_ec_3) {
|
||||
mimeType = MimeTypes.AUDIO_EC3;
|
||||
} else {
|
||||
mimeType = MimeTypes.AUDIO_AAC;
|
||||
}
|
||||
|
||||
MediaFormat format = MediaFormat.createAudioFormat(
|
||||
mimeType, sampleSize, channelCount, sampleRate, bitrate,
|
||||
initializationData == null ? null : Collections.singletonList(initializationData));
|
||||
return Pair.create(format, trackEncryptionBox);
|
||||
}
|
||||
|
||||
/** Returns codec-specific initialization data contained in an esds box. */
|
||||
private static byte[] parseEsdsFromParent(ParsableByteArray parent, int position) {
|
||||
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE + 4);
|
||||
// Start of the ES_Descriptor (defined in 14496-1)
|
||||
parent.skip(1); // ES_Descriptor tag
|
||||
int varIntByte = parent.readUnsignedByte();
|
||||
while (varIntByte > 127) {
|
||||
varIntByte = parent.readUnsignedByte();
|
||||
}
|
||||
parent.skip(2); // ES_ID
|
||||
|
||||
int flags = parent.readUnsignedByte();
|
||||
if ((flags & 0x80 /* streamDependenceFlag */) != 0) {
|
||||
parent.skip(2);
|
||||
}
|
||||
if ((flags & 0x40 /* URL_Flag */) != 0) {
|
||||
parent.skip(parent.readUnsignedShort());
|
||||
}
|
||||
if ((flags & 0x20 /* OCRstreamFlag */) != 0) {
|
||||
parent.skip(2);
|
||||
}
|
||||
|
||||
// Start of the DecoderConfigDescriptor (defined in 14496-1)
|
||||
parent.skip(1); // DecoderConfigDescriptor tag
|
||||
varIntByte = parent.readUnsignedByte();
|
||||
while (varIntByte > 127) {
|
||||
varIntByte = parent.readUnsignedByte();
|
||||
}
|
||||
parent.skip(13);
|
||||
|
||||
// Start of AudioSpecificConfig (defined in 14496-3)
|
||||
parent.skip(1); // AudioSpecificConfig tag
|
||||
varIntByte = parent.readUnsignedByte();
|
||||
int varInt = varIntByte & 0x7F;
|
||||
while (varIntByte > 127) {
|
||||
varIntByte = parent.readUnsignedByte();
|
||||
varInt = varInt << 8;
|
||||
varInt |= varIntByte & 0x7F;
|
||||
}
|
||||
byte[] initializationData = new byte[varInt];
|
||||
parent.readBytes(initializationData, 0, varInt);
|
||||
return initializationData;
|
||||
}
|
||||
|
||||
private static Ac3Format parseAc3SpecificBoxFromParent(ParsableByteArray parent, int position) {
|
||||
// Start of the dac3 atom (defined in ETSI TS 102 366)
|
||||
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
|
||||
|
||||
// fscod (sample rate code)
|
||||
int fscod = (parent.readUnsignedByte() & 0xC0) >> 6;
|
||||
int sampleRate;
|
||||
switch (fscod) {
|
||||
case 0:
|
||||
sampleRate = 48000;
|
||||
break;
|
||||
case 1:
|
||||
sampleRate = 44100;
|
||||
break;
|
||||
case 2:
|
||||
sampleRate = 32000;
|
||||
break;
|
||||
default:
|
||||
// TODO: The decoder should not use this stream.
|
||||
return null;
|
||||
}
|
||||
|
||||
int nextByte = parent.readUnsignedByte();
|
||||
|
||||
// Map acmod (audio coding mode) onto a channel count.
|
||||
int channelCount = AC3_CHANNEL_COUNTS[(nextByte & 0x38) >> 3];
|
||||
|
||||
// lfeon (low frequency effects on)
|
||||
if ((nextByte & 0x04) != 0) {
|
||||
channelCount++;
|
||||
}
|
||||
|
||||
// Map bit_rate_code onto a bit-rate in kbit/s.
|
||||
int bitrate = AC3_BIT_RATES[((nextByte & 0x03) << 3) + (parent.readUnsignedByte() >> 5)];
|
||||
|
||||
return new Ac3Format(channelCount, sampleRate, bitrate);
|
||||
}
|
||||
|
||||
private static int parseEc3SpecificBoxFromParent(ParsableByteArray parent, int position) {
|
||||
// Start of the dec3 atom (defined in ETSI TS 102 366)
|
||||
parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE);
|
||||
// TODO: Implement parsing for enhanced AC-3 with multiple sub-streams.
|
||||
return 0;
|
||||
}
|
||||
|
||||
private CommonMp4AtomParsers() {
|
||||
// Prevent instantiation.
|
||||
}
|
||||
|
||||
/** Represents the format for AC-3 audio. */
|
||||
private static final class Ac3Format {
|
||||
|
||||
public final int channelCount;
|
||||
public final int sampleRate;
|
||||
public final int bitrate;
|
||||
|
||||
public Ac3Format(int channelCount, int sampleRate, int bitrate) {
|
||||
this.channelCount = channelCount;
|
||||
this.sampleRate = sampleRate;
|
||||
this.bitrate = bitrate;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright (C) 2014 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.exoplayer.mp4;
|
||||
|
||||
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
|
||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Utility methods and constants for parsing fragmented and unfragmented MP4 files.
|
||||
*/
|
||||
public final class Mp4Util {
|
||||
|
||||
/** Size of an atom header, in bytes. */
|
||||
public static final int ATOM_HEADER_SIZE = 8;
|
||||
|
||||
/** Size of a long atom header, in bytes. */
|
||||
public static final int LONG_ATOM_HEADER_SIZE = 16;
|
||||
|
||||
/** Size of a full atom header, in bytes. */
|
||||
public static final int FULL_ATOM_HEADER_SIZE = 12;
|
||||
|
||||
/** Four initial bytes that must prefix H.264/AVC NAL units for decoding. */
|
||||
private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1};
|
||||
|
||||
/** Parses the version number out of the additional integer component of a full atom. */
|
||||
public static int parseFullAtomVersion(int fullAtomInt) {
|
||||
return 0x000000FF & (fullAtomInt >> 24);
|
||||
}
|
||||
|
||||
/** Parses the atom flags out of the additional integer component of a full atom. */
|
||||
public static int parseFullAtomFlags(int fullAtomInt) {
|
||||
return 0x00FFFFFF & fullAtomInt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an unsigned integer into an integer. This method is suitable for use when it can be
|
||||
* assumed that the top bit will always be set to zero.
|
||||
*
|
||||
* @throws IllegalArgumentException If the top bit of the input data is set.
|
||||
*/
|
||||
public static int readUnsignedIntToInt(ByteBuffer data) {
|
||||
int result = 0xFF & data.get();
|
||||
for (int i = 1; i < 4; i++) {
|
||||
result <<= 8;
|
||||
result |= 0xFF & data.get();
|
||||
}
|
||||
if (result < 0) {
|
||||
throw new IllegalArgumentException("Top bit not zero: " + result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces length prefixes of NAL units in {@code buffer} with start code prefixes, within the
|
||||
* {@code size} bytes preceding the buffer's position.
|
||||
*/
|
||||
public static void replaceLengthPrefixesWithAvcStartCodes(ByteBuffer buffer, int size) {
|
||||
int sampleOffset = buffer.position() - size;
|
||||
int position = sampleOffset;
|
||||
while (position < sampleOffset + size) {
|
||||
buffer.position(position);
|
||||
int length = readUnsignedIntToInt(buffer);
|
||||
buffer.position(position);
|
||||
buffer.put(NAL_START_CODE);
|
||||
position += length + 4;
|
||||
}
|
||||
buffer.position(sampleOffset + size);
|
||||
}
|
||||
|
||||
/** Constructs and returns a NAL unit with a start code followed by the data in {@code atom}. */
|
||||
public static byte[] parseChildNalUnit(ParsableByteArray atom) {
|
||||
int length = atom.readUnsignedShort();
|
||||
int offset = atom.getPosition();
|
||||
atom.skip(length);
|
||||
return CodecSpecificDataUtil.buildNalUnit(atom.data, offset, length);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package com.google.android.exoplayer.mp4;
|
||||
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.MediaFormat;
|
||||
import com.google.android.exoplayer.chunk.parser.mp4.TrackEncryptionBox;
|
||||
|
||||
|
|
@ -43,6 +44,10 @@ public final class Track {
|
|||
* Type of a meta track.
|
||||
*/
|
||||
public static final int TYPE_META = 0x6D657461;
|
||||
/**
|
||||
* Type of a time-code track.
|
||||
*/
|
||||
public static final int TYPE_TIME_CODE = 0x746D6364;
|
||||
|
||||
/**
|
||||
* The track identifier.
|
||||
|
|
@ -50,7 +55,8 @@ public final class Track {
|
|||
public final int id;
|
||||
|
||||
/**
|
||||
* One of {@link #TYPE_VIDEO}, {@link #TYPE_AUDIO}, {@link #TYPE_HINT} and {@link #TYPE_META}.
|
||||
* One of {@link #TYPE_VIDEO}, {@link #TYPE_AUDIO}, {@link #TYPE_HINT}, {@link #TYPE_META} and
|
||||
* {@link #TYPE_TIME_CODE}.
|
||||
*/
|
||||
public final int type;
|
||||
|
||||
|
|
@ -59,6 +65,11 @@ public final class Track {
|
|||
*/
|
||||
public final long timescale;
|
||||
|
||||
/**
|
||||
* The duration of the track in microseconds, or {@link C#UNKNOWN_TIME_US} if unknown.
|
||||
*/
|
||||
public final long durationUs;
|
||||
|
||||
/**
|
||||
* The format if {@link #type} is {@link #TYPE_VIDEO} or {@link #TYPE_AUDIO}. Null otherwise.
|
||||
*/
|
||||
|
|
@ -69,11 +80,12 @@ public final class Track {
|
|||
*/
|
||||
public final TrackEncryptionBox[] sampleDescriptionEncryptionBoxes;
|
||||
|
||||
public Track(int id, int type, long timescale, MediaFormat mediaFormat,
|
||||
public Track(int id, int type, long timescale, long durationUs, MediaFormat mediaFormat,
|
||||
TrackEncryptionBox[] sampleDescriptionEncryptionBoxes) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.timescale = timescale;
|
||||
this.durationUs = durationUs;
|
||||
this.mediaFormat = mediaFormat;
|
||||
this.sampleDescriptionEncryptionBoxes = sampleDescriptionEncryptionBoxes;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,8 +167,8 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||
: Track.TYPE_AUDIO;
|
||||
FragmentedMp4Extractor extractor = new FragmentedMp4Extractor(
|
||||
FragmentedMp4Extractor.WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME);
|
||||
extractor.setTrack(new Track(trackIndex, trackType, streamElement.timescale, mediaFormat,
|
||||
trackEncryptionBoxes));
|
||||
extractor.setTrack(new Track(trackIndex, trackType, streamElement.timescale,
|
||||
initialManifest.durationUs, mediaFormat, trackEncryptionBoxes));
|
||||
extractors.put(trackIndex, extractor);
|
||||
}
|
||||
this.maxHeight = maxHeight;
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ public class SubtitleView extends View {
|
|||
@Override
|
||||
public void setBackgroundColor(int color) {
|
||||
backgroundColor = color;
|
||||
invalidate();
|
||||
forceUpdate(false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -134,8 +134,7 @@ public class SubtitleView extends View {
|
|||
public void setText(CharSequence text) {
|
||||
textBuilder.setLength(0);
|
||||
textBuilder.append(text);
|
||||
hasMeasurements = false;
|
||||
requestLayout();
|
||||
forceUpdate(true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -147,9 +146,7 @@ public class SubtitleView extends View {
|
|||
if (textPaint.getTextSize() != size) {
|
||||
textPaint.setTextSize(size);
|
||||
innerPaddingX = (int) (size * INNER_PADDING_RATIO + 0.5f);
|
||||
hasMeasurements = false;
|
||||
requestLayout();
|
||||
invalidate();
|
||||
forceUpdate(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -165,17 +162,22 @@ public class SubtitleView extends View {
|
|||
edgeColor = style.edgeColor;
|
||||
setTypeface(style.typeface);
|
||||
super.setBackgroundColor(style.windowColor);
|
||||
hasMeasurements = false;
|
||||
requestLayout();
|
||||
forceUpdate(true);
|
||||
}
|
||||
|
||||
private void setTypeface(Typeface typeface) {
|
||||
if (textPaint.getTypeface() != typeface) {
|
||||
textPaint.setTypeface(typeface);
|
||||
forceUpdate(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void forceUpdate(boolean needsLayout) {
|
||||
if (needsLayout) {
|
||||
hasMeasurements = false;
|
||||
requestLayout();
|
||||
invalidate();
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -56,20 +56,28 @@ public class WebvttParser implements SubtitleParser {
|
|||
private static final Pattern WEBVTT_METADATA_HEADER =
|
||||
Pattern.compile(WEBVTT_METADATA_HEADER_STRING);
|
||||
|
||||
private static final String WEBVTT_CUE_IDENTIFIER_STRING = "^(?!.*(-->)).*$";
|
||||
private static final Pattern WEBVTT_CUE_IDENTIFIER =
|
||||
Pattern.compile(WEBVTT_CUE_IDENTIFIER_STRING);
|
||||
|
||||
private static final String WEBVTT_TIMESTAMP_STRING = "(\\d+:)?[0-5]\\d:[0-5]\\d\\.\\d{3}";
|
||||
private static final Pattern WEBVTT_TIMESTAMP = Pattern.compile(WEBVTT_TIMESTAMP_STRING);
|
||||
|
||||
private static final Pattern MEDIA_TIMESTAMP_OFFSET = Pattern.compile(OFFSET + "\\d+");
|
||||
private static final Pattern MEDIA_TIMESTAMP = Pattern.compile("MPEGTS:\\d+");
|
||||
|
||||
private static final String WEBVTT_CUE_TAG_STRING = "\\<.*?>";
|
||||
|
||||
private final boolean strictParsing;
|
||||
private final boolean filterTags;
|
||||
|
||||
public WebvttParser() {
|
||||
this(true);
|
||||
this(true, true);
|
||||
}
|
||||
|
||||
public WebvttParser(boolean strictParsing) {
|
||||
public WebvttParser(boolean strictParsing, boolean filterTags) {
|
||||
this.strictParsing = strictParsing;
|
||||
this.filterTags = filterTags;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -137,8 +145,15 @@ public class WebvttParser implements SubtitleParser {
|
|||
|
||||
// process the cues and text
|
||||
while ((line = webvttData.readLine()) != null) {
|
||||
// parse the cue identifier (if present) {
|
||||
Matcher matcher = WEBVTT_CUE_IDENTIFIER.matcher(line);
|
||||
if (matcher.find()) {
|
||||
// ignore the identifier (we currently don't use it) and read the next line
|
||||
line = webvttData.readLine();
|
||||
}
|
||||
|
||||
// parse the cue timestamps
|
||||
Matcher matcher = WEBVTT_TIMESTAMP.matcher(line);
|
||||
matcher = WEBVTT_TIMESTAMP.matcher(line);
|
||||
long startTime;
|
||||
long endTime;
|
||||
String text = "";
|
||||
|
|
@ -159,7 +174,7 @@ public class WebvttParser implements SubtitleParser {
|
|||
|
||||
// parse text
|
||||
while (((line = webvttData.readLine()) != null) && (!line.isEmpty())) {
|
||||
text += line.trim() + "\n";
|
||||
text += processCueText(line.trim()) + "\n";
|
||||
}
|
||||
|
||||
WebvttCue cue = new WebvttCue(startTime, endTime, text);
|
||||
|
|
@ -193,6 +208,19 @@ public class WebvttParser implements SubtitleParser {
|
|||
return startTimeUs;
|
||||
}
|
||||
|
||||
protected String processCueText(String line) {
|
||||
if (filterTags) {
|
||||
line = line.replaceAll(WEBVTT_CUE_TAG_STRING, "");
|
||||
line = line.replaceAll("<", "<");
|
||||
line = line.replaceAll(">", ">");
|
||||
line = line.replaceAll(" ", " ");
|
||||
line = line.replaceAll("&", "&");
|
||||
return line;
|
||||
} else {
|
||||
return line;
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleNoncompliantLine(String line) throws ParserException {
|
||||
if (strictParsing) {
|
||||
throw new ParserException("Unexpected line: " + line);
|
||||
|
|
|
|||
|
|
@ -27,24 +27,27 @@ public class ByteArrayDataSource implements DataSource {
|
|||
|
||||
private final byte[] data;
|
||||
private int readPosition;
|
||||
private int remainingBytes;
|
||||
|
||||
/**
|
||||
* @param data The data to be read.
|
||||
*/
|
||||
public ByteArrayDataSource(byte[] data) {
|
||||
this.data = Assertions.checkNotNull(data);
|
||||
Assertions.checkNotNull(data);
|
||||
Assertions.checkArgument(data.length > 0);
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long open(DataSpec dataSpec) throws IOException {
|
||||
if (dataSpec.length == C.LENGTH_UNBOUNDED) {
|
||||
Assertions.checkArgument(dataSpec.position < data.length);
|
||||
} else {
|
||||
Assertions.checkArgument(dataSpec.position + dataSpec.length <= data.length);
|
||||
}
|
||||
readPosition = (int) dataSpec.position;
|
||||
return (dataSpec.length == C.LENGTH_UNBOUNDED) ? (data.length - dataSpec.position)
|
||||
: dataSpec.length;
|
||||
remainingBytes = (int) ((dataSpec.length == C.LENGTH_UNBOUNDED)
|
||||
? (data.length - dataSpec.position) : dataSpec.length);
|
||||
if (remainingBytes <= 0 || readPosition + remainingBytes > data.length) {
|
||||
throw new IOException("Unsatisfiable range: [" + readPosition + ", " + dataSpec.length
|
||||
+ "], length: " + data.length);
|
||||
}
|
||||
return remainingBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -54,8 +57,13 @@ public class ByteArrayDataSource implements DataSource {
|
|||
|
||||
@Override
|
||||
public int read(byte[] buffer, int offset, int length) throws IOException {
|
||||
if (remainingBytes == 0) {
|
||||
return -1;
|
||||
}
|
||||
length = Math.min(length, remainingBytes);
|
||||
System.arraycopy(data, readPosition, buffer, offset, length);
|
||||
readPosition += length;
|
||||
remainingBytes -= length;
|
||||
return length;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ public class MimeTypes {
|
|||
public static final String VIDEO_WEBM = BASE_TYPE_VIDEO + "/webm";
|
||||
public static final String VIDEO_H264 = BASE_TYPE_VIDEO + "/avc";
|
||||
public static final String VIDEO_VP9 = BASE_TYPE_VIDEO + "/x-vnd.on2.vp9";
|
||||
public static final String VIDEO_MP4V = BASE_TYPE_VIDEO + "/mp4v-es";
|
||||
|
||||
public static final String AUDIO_MP4 = BASE_TYPE_AUDIO + "/mp4";
|
||||
public static final String AUDIO_AAC = BASE_TYPE_AUDIO + "/mp4a-latm";
|
||||
|
|
|
|||
Loading…
Reference in a new issue