Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
SDK
/
exoplayer
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
5a3340d6
authored
Dec 12, 2014
by
Oliver Woodman
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Add initial AC3 passthrough support.
parent
81bf68b1
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
373 additions
and
8 deletions
demo/src/main/java/com/google/android/exoplayer/demo/full/FullPlayerActivity.java
demo/src/main/java/com/google/android/exoplayer/demo/full/player/DashRendererBuilder.java
demo/src/main/java/com/google/android/exoplayer/demo/full/player/DemoPlayer.java
library/src/main/java/com/google/android/exoplayer/Ac3PassthroughAudioTrackRenderer.java
demo/src/main/java/com/google/android/exoplayer/demo/full/FullPlayerActivity.java
View file @
5a3340d6
...
...
@@ -17,6 +17,8 @@ package com.google.android.exoplayer.demo.full;
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
;
...
...
@@ -59,7 +61,7 @@ import android.widget.Toast;
* An activity that plays media using {@link DemoPlayer}.
*/
public
class
FullPlayerActivity
extends
Activity
implements
SurfaceHolder
.
Callback
,
OnClickListener
,
DemoPlayer
.
Listener
,
DemoPlayer
.
TextListener
{
DemoPlayer
.
Listener
,
DemoPlayer
.
TextListener
,
AudioCapabilitiesReceiver
.
Listener
{
private
static
final
float
CAPTION_LINE_HEIGHT_RATIO
=
0.0533f
;
private
static
final
int
MENU_GROUP_TRACKS
=
1
;
...
...
@@ -89,6 +91,9 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
private
int
contentType
;
private
String
contentId
;
private
AudioCapabilitiesReceiver
audioCapabilitiesReceiver
;
private
AudioCapabilities
audioCapabilities
;
// Activity lifecycle
@Override
...
...
@@ -112,6 +117,8 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
}
});
audioCapabilitiesReceiver
=
new
AudioCapabilitiesReceiver
(
getApplicationContext
(),
this
);
shutterView
=
findViewById
(
R
.
id
.
shutter
);
debugRootView
=
findViewById
(
R
.
id
.
controls_root
);
...
...
@@ -137,7 +144,9 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
public
void
onResume
()
{
super
.
onResume
();
configureSubtitleView
();
preparePlayer
();
// The player will be prepared on receiving audio capabilities.
audioCapabilitiesReceiver
.
register
();
}
@Override
...
...
@@ -148,6 +157,8 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
}
else
{
player
.
blockingClearSurface
();
}
audioCapabilitiesReceiver
.
unregister
();
}
@Override
...
...
@@ -166,6 +177,17 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
}
}
// AudioCapabilitiesReceiver.Listener methods
@Override
public
void
onAudioCapabilitiesChanged
(
AudioCapabilities
audioCapabilities
)
{
this
.
audioCapabilities
=
audioCapabilities
;
releasePlayer
();
autoPlay
=
true
;
preparePlayer
();
}
// Internal methods
private
RendererBuilder
getRendererBuilder
()
{
...
...
@@ -176,7 +198,7 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
new
SmoothStreamingTestMediaDrmCallback
(),
debugTextView
);
case
DemoUtil
.
TYPE_DASH
:
return
new
DashRendererBuilder
(
userAgent
,
contentUri
.
toString
(),
contentId
,
new
WidevineTestMediaDrmCallback
(
contentId
),
debugTextView
);
new
WidevineTestMediaDrmCallback
(
contentId
),
debugTextView
,
audioCapabilities
);
default
:
return
new
DefaultRendererBuilder
(
this
,
contentUri
,
debugTextView
);
}
...
...
demo/src/main/java/com/google/android/exoplayer/demo/full/player/DashRendererBuilder.java
View file @
5a3340d6
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer
.
demo
.
full
.
player
;
import
com.google.android.exoplayer.Ac3PassthroughAudioTrackRenderer
;
import
com.google.android.exoplayer.DefaultLoadControl
;
import
com.google.android.exoplayer.LoadControl
;
import
com.google.android.exoplayer.MediaCodecAudioTrackRenderer
;
...
...
@@ -22,6 +23,7 @@ import com.google.android.exoplayer.MediaCodecUtil;
import
com.google.android.exoplayer.MediaCodecVideoTrackRenderer
;
import
com.google.android.exoplayer.SampleSource
;
import
com.google.android.exoplayer.TrackRenderer
;
import
com.google.android.exoplayer.audio.AudioCapabilities
;
import
com.google.android.exoplayer.chunk.ChunkSampleSource
;
import
com.google.android.exoplayer.chunk.ChunkSource
;
import
com.google.android.exoplayer.chunk.Format
;
...
...
@@ -84,18 +86,20 @@ public class DashRendererBuilder implements RendererBuilder,
private
final
String
contentId
;
private
final
MediaDrmCallback
drmCallback
;
private
final
TextView
debugTextView
;
private
final
AudioCapabilities
audioCapabilities
;
private
DemoPlayer
player
;
private
RendererBuilderCallback
callback
;
private
ManifestFetcher
<
MediaPresentationDescription
>
manifestFetcher
;
public
DashRendererBuilder
(
String
userAgent
,
String
url
,
String
contentId
,
MediaDrmCallback
drmCallback
,
TextView
debugTextView
)
{
MediaDrmCallback
drmCallback
,
TextView
debugTextView
,
AudioCapabilities
audioCapabilities
)
{
this
.
userAgent
=
userAgent
;
this
.
url
=
url
;
this
.
contentId
=
contentId
;
this
.
drmCallback
=
drmCallback
;
this
.
debugTextView
=
debugTextView
;
this
.
audioCapabilities
=
audioCapabilities
;
}
@Override
...
...
@@ -208,6 +212,7 @@ public class DashRendererBuilder implements RendererBuilder,
}
// Build the audio chunk sources.
boolean
haveAc3Tracks
=
false
;
List
<
ChunkSource
>
audioChunkSourceList
=
new
ArrayList
<
ChunkSource
>();
List
<
String
>
audioTrackNameList
=
new
ArrayList
<
String
>();
if
(
audioAdaptationSet
!=
null
)
{
...
...
@@ -220,6 +225,19 @@ public class DashRendererBuilder implements RendererBuilder,
format
.
audioSamplingRate
+
"Hz)"
);
audioChunkSourceList
.
add
(
new
DashChunkSource
(
manifestFetcher
,
audioAdaptationSetIndex
,
new
int
[]
{
i
},
audioDataSource
,
audioEvaluator
,
LIVE_EDGE_LATENCY_MS
));
haveAc3Tracks
|=
format
.
mimeType
.
equals
(
MimeTypes
.
AUDIO_AC3
)
||
format
.
mimeType
.
equals
(
MimeTypes
.
AUDIO_EC3
);
}
// Filter out non-AC-3 tracks if there is an AC-3 track, to avoid having to switch renderers.
if
(
haveAc3Tracks
)
{
for
(
int
i
=
audioRepresentations
.
size
()
-
1
;
i
>=
0
;
i
--)
{
Format
format
=
audioRepresentations
.
get
(
i
).
format
;
if
(!
format
.
mimeType
.
equals
(
MimeTypes
.
AUDIO_AC3
)
&&
!
format
.
mimeType
.
equals
(
MimeTypes
.
AUDIO_EC3
))
{
audioTrackNameList
.
remove
(
i
);
audioChunkSourceList
.
remove
(
i
);
}
}
}
}
...
...
@@ -238,8 +256,16 @@ public class DashRendererBuilder implements RendererBuilder,
SampleSource
audioSampleSource
=
new
ChunkSampleSource
(
audioChunkSource
,
loadControl
,
AUDIO_BUFFER_SEGMENTS
*
BUFFER_SEGMENT_SIZE
,
true
,
mainHandler
,
player
,
DemoPlayer
.
TYPE_AUDIO
);
audioRenderer
=
new
MediaCodecAudioTrackRenderer
(
audioSampleSource
,
drmSessionManager
,
true
,
mainHandler
,
player
);
// TODO: There needs to be some logic to filter out non-AC3 tracks when selecting to use AC3.
boolean
useAc3Passthrough
=
haveAc3Tracks
&&
audioCapabilities
!=
null
&&
(
audioCapabilities
.
supportsAc3
()
||
audioCapabilities
.
supportsEAc3
());
if
(
useAc3Passthrough
)
{
audioRenderer
=
new
Ac3PassthroughAudioTrackRenderer
(
audioSampleSource
,
mainHandler
,
player
);
}
else
{
audioRenderer
=
new
MediaCodecAudioTrackRenderer
(
audioSampleSource
,
drmSessionManager
,
true
,
mainHandler
,
player
);
}
}
// Build the text chunk sources.
...
...
demo/src/main/java/com/google/android/exoplayer/demo/full/player/DemoPlayer.java
View file @
5a3340d6
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer
.
demo
.
full
.
player
;
import
com.google.android.exoplayer.Ac3PassthroughAudioTrackRenderer
;
import
com.google.android.exoplayer.DummyTrackRenderer
;
import
com.google.android.exoplayer.ExoPlaybackException
;
import
com.google.android.exoplayer.ExoPlayer
;
...
...
@@ -45,8 +46,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
*/
public
class
DemoPlayer
implements
ExoPlayer
.
Listener
,
ChunkSampleSource
.
EventListener
,
DefaultBandwidthMeter
.
EventListener
,
MediaCodecVideoTrackRenderer
.
EventListener
,
MediaCodecAudioTrackRenderer
.
EventListener
,
TextTrackRenderer
.
TextRender
er
,
StreamingDrmSessionManager
.
EventListener
{
MediaCodecAudioTrackRenderer
.
EventListener
,
Ac3PassthroughAudioTrackRenderer
.
EventListen
er
,
TextTrackRenderer
.
TextRenderer
,
StreamingDrmSessionManager
.
EventListener
{
/**
* Builds renderers for the player.
...
...
library/src/main/java/com/google/android/exoplayer/Ac3PassthroughAudioTrackRenderer.java
0 → 100644
View file @
5a3340d6
/*
* 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
;
import
com.google.android.exoplayer.audio.AudioCapabilitiesReceiver
;
import
com.google.android.exoplayer.audio.AudioTrack
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
android.annotation.TargetApi
;
import
android.media.AudioFormat
;
import
android.os.Handler
;
import
java.io.IOException
;
import
java.nio.ByteBuffer
;
/**
* Renders encoded AC-3/enhanced AC-3 data to an {@link AudioTrack} for decoding on the playback
* device.
*
* <p>To determine whether the playback device supports passthrough, receive an audio configuration
* using {@link AudioCapabilitiesReceiver} and check whether the audio capabilities include
* AC-3/enhanced AC-3 passthrough.
*/
@TargetApi
(
21
)
public
final
class
Ac3PassthroughAudioTrackRenderer
extends
TrackRenderer
{
/**
* Interface definition for a callback to be notified of {@link Ac3PassthroughAudioTrackRenderer}
* events.
*/
public
interface
EventListener
{
/**
* Invoked when an {@link AudioTrack} fails to initialize.
*
* @param e The corresponding exception.
*/
void
onAudioTrackInitializationError
(
AudioTrack
.
InitializationException
e
);
}
/**
* The type of a message that can be passed to an instance of this class via
* {@link ExoPlayer#sendMessage} or {@link ExoPlayer#blockingSendMessage}. The message object
* should be a {@link Float} with 0 being silence and 1 being unity gain.
*/
public
static
final
int
MSG_SET_VOLUME
=
1
;
private
static
final
int
SOURCE_STATE_NOT_READY
=
0
;
private
static
final
int
SOURCE_STATE_READY
=
1
;
/** Default buffer size for AC-3 packets from the sample source */
private
static
final
int
DEFAULT_BUFFER_SIZE
=
16384
*
2
;
/** Multiplication factor for the audio track's buffer size. */
private
static
final
int
MIN_BUFFER_MULTIPLICATION_FACTOR
=
3
;
private
final
Handler
eventHandler
;
private
final
EventListener
eventListener
;
private
final
SampleSource
source
;
private
final
SampleHolder
sampleHolder
;
private
final
MediaFormatHolder
formatHolder
;
private
int
trackIndex
;
private
MediaFormat
format
;
private
int
sourceState
;
private
boolean
inputStreamEnded
;
private
boolean
shouldReadInputBuffer
;
private
long
currentPositionUs
;
private
AudioTrack
audioTrack
;
private
int
audioSessionId
;
/**
* Constructs a new track renderer that passes AC-3 samples directly to an audio track.
*
* @param source The upstream source from which the renderer obtains samples.
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
*/
public
Ac3PassthroughAudioTrackRenderer
(
SampleSource
source
,
Handler
eventHandler
,
EventListener
eventListener
)
{
this
.
source
=
Assertions
.
checkNotNull
(
source
);
this
.
eventHandler
=
eventHandler
;
this
.
eventListener
=
eventListener
;
sampleHolder
=
new
SampleHolder
(
SampleHolder
.
BUFFER_REPLACEMENT_MODE_NORMAL
);
sampleHolder
.
data
=
ByteBuffer
.
allocateDirect
(
DEFAULT_BUFFER_SIZE
);
formatHolder
=
new
MediaFormatHolder
();
audioTrack
=
new
AudioTrack
(
MIN_BUFFER_MULTIPLICATION_FACTOR
);
shouldReadInputBuffer
=
true
;
}
@Override
protected
boolean
isTimeSource
()
{
return
true
;
}
@Override
protected
int
doPrepare
()
throws
ExoPlaybackException
{
try
{
boolean
sourcePrepared
=
source
.
prepare
();
if
(!
sourcePrepared
)
{
return
TrackRenderer
.
STATE_UNPREPARED
;
}
}
catch
(
IOException
e
)
{
throw
new
ExoPlaybackException
(
e
);
}
for
(
int
i
=
0
;
i
<
source
.
getTrackCount
();
i
++)
{
// TODO(andrewlewis): Choose best format here after checking playout formats from HDMI config.
if
(
handlesMimeType
(
source
.
getTrackInfo
(
i
).
mimeType
))
{
trackIndex
=
i
;
return
TrackRenderer
.
STATE_PREPARED
;
}
}
return
TrackRenderer
.
STATE_IGNORE
;
}
private
static
boolean
handlesMimeType
(
String
mimeType
)
{
return
MimeTypes
.
AUDIO_AC3
.
equals
(
mimeType
)
||
MimeTypes
.
AUDIO_EC3
.
equals
(
mimeType
);
}
@Override
protected
void
onEnabled
(
long
positionUs
,
boolean
joining
)
{
source
.
enable
(
trackIndex
,
positionUs
);
sourceState
=
SOURCE_STATE_NOT_READY
;
inputStreamEnded
=
false
;
currentPositionUs
=
positionUs
;
}
@Override
protected
void
doSomeWork
(
long
positionUs
,
long
elapsedRealtimeUs
)
throws
ExoPlaybackException
{
try
{
sourceState
=
source
.
continueBuffering
(
positionUs
)
?
(
sourceState
==
SOURCE_STATE_NOT_READY
?
SOURCE_STATE_READY
:
sourceState
)
:
SOURCE_STATE_NOT_READY
;
if
(
format
==
null
)
{
readFormat
();
}
else
{
// Initialize and start the audio track now.
if
(!
audioTrack
.
isInitialized
())
{
int
oldAudioSessionId
=
audioSessionId
;
try
{
audioSessionId
=
audioTrack
.
initialize
(
oldAudioSessionId
);
}
catch
(
AudioTrack
.
InitializationException
e
)
{
notifyAudioTrackInitializationError
(
e
);
throw
new
ExoPlaybackException
(
e
);
}
if
(
getState
()
==
TrackRenderer
.
STATE_STARTED
)
{
audioTrack
.
play
();
}
}
feedInputBuffer
();
}
}
catch
(
IOException
e
)
{
throw
new
ExoPlaybackException
(
e
);
}
}
private
void
readFormat
()
throws
IOException
{
int
result
=
source
.
readData
(
trackIndex
,
currentPositionUs
,
formatHolder
,
sampleHolder
,
false
);
if
(
result
==
SampleSource
.
FORMAT_READ
)
{
format
=
formatHolder
.
format
;
// TODO: For E-AC-3 input, reconfigure with AudioFormat.ENCODING_E_AC3.
audioTrack
.
reconfigure
(
format
.
getFrameworkMediaFormatV16
(),
AudioFormat
.
ENCODING_AC3
,
0
);
}
}
private
void
feedInputBuffer
()
throws
IOException
{
if
(!
audioTrack
.
isInitialized
()
||
inputStreamEnded
)
{
return
;
}
// Get more data if we have run out.
if
(
shouldReadInputBuffer
)
{
sampleHolder
.
data
.
clear
();
int
result
=
source
.
readData
(
trackIndex
,
currentPositionUs
,
formatHolder
,
sampleHolder
,
false
);
sampleHolder
.
data
.
flip
();
shouldReadInputBuffer
=
false
;
if
(
result
==
SampleSource
.
FORMAT_READ
)
{
format
=
formatHolder
.
format
;
}
if
(
result
==
SampleSource
.
END_OF_STREAM
)
{
inputStreamEnded
=
true
;
}
if
(
result
!=
SampleSource
.
SAMPLE_READ
)
{
return
;
}
}
int
handleBufferResult
=
audioTrack
.
handleBuffer
(
sampleHolder
.
data
,
0
,
sampleHolder
.
size
,
sampleHolder
.
timeUs
);
// If we are out of sync, allow currentPositionUs to jump backwards.
if
((
handleBufferResult
&
AudioTrack
.
RESULT_POSITION_DISCONTINUITY
)
!=
0
)
{
currentPositionUs
=
Long
.
MIN_VALUE
;
}
// Get another input buffer if this one was consumed.
shouldReadInputBuffer
=
(
handleBufferResult
&
AudioTrack
.
RESULT_BUFFER_CONSUMED
)
!=
0
;
}
@Override
protected
void
onStarted
()
{
if
(
audioTrack
.
isInitialized
())
{
audioTrack
.
play
();
}
}
@Override
protected
void
onStopped
()
{
if
(
audioTrack
.
isInitialized
())
{
audioTrack
.
pause
();
}
}
@Override
protected
boolean
isEnded
()
{
// We've exhausted the input stream, and the AudioTrack has either played all of the data
// submitted, or has been fed insufficient data to begin playback.
return
inputStreamEnded
&&
(!
audioTrack
.
hasPendingData
()
||
!
audioTrack
.
hasEnoughDataToBeginPlayback
());
}
@Override
protected
boolean
isReady
()
{
return
audioTrack
.
hasPendingData
()
||
(
format
!=
null
&&
sourceState
!=
SOURCE_STATE_NOT_READY
);
}
@Override
protected
long
getCurrentPositionUs
()
{
long
audioTrackCurrentPositionUs
=
audioTrack
.
getCurrentPositionUs
(
isEnded
());
if
(
audioTrackCurrentPositionUs
!=
AudioTrack
.
CURRENT_POSITION_NOT_SET
)
{
// Make sure we don't ever report time moving backwards.
currentPositionUs
=
Math
.
max
(
currentPositionUs
,
audioTrackCurrentPositionUs
);
}
return
currentPositionUs
;
}
@Override
protected
long
getDurationUs
()
{
return
source
.
getTrackInfo
(
trackIndex
).
durationUs
;
}
@Override
protected
long
getBufferedPositionUs
()
{
long
sourceBufferedPosition
=
source
.
getBufferedPositionUs
();
return
sourceBufferedPosition
==
UNKNOWN_TIME_US
||
sourceBufferedPosition
==
END_OF_TRACK_US
?
sourceBufferedPosition
:
Math
.
max
(
sourceBufferedPosition
,
getCurrentPositionUs
());
}
@Override
protected
void
onDisabled
()
{
audioSessionId
=
AudioTrack
.
SESSION_ID_NOT_SET
;
shouldReadInputBuffer
=
true
;
audioTrack
.
reset
();
}
@Override
protected
void
seekTo
(
long
positionUs
)
throws
ExoPlaybackException
{
source
.
seekToUs
(
positionUs
);
sourceState
=
SOURCE_STATE_NOT_READY
;
inputStreamEnded
=
false
;
shouldReadInputBuffer
=
true
;
// TODO: Try and re-use the same AudioTrack instance once [Internal: b/7941810] is fixed.
audioTrack
.
reset
();
currentPositionUs
=
Long
.
MIN_VALUE
;
}
@Override
public
void
handleMessage
(
int
messageType
,
Object
message
)
throws
ExoPlaybackException
{
if
(
messageType
==
MSG_SET_VOLUME
)
{
audioTrack
.
setVolume
((
Float
)
message
);
}
else
{
super
.
handleMessage
(
messageType
,
message
);
}
}
private
void
notifyAudioTrackInitializationError
(
final
AudioTrack
.
InitializationException
e
)
{
if
(
eventHandler
!=
null
&&
eventListener
!=
null
)
{
eventHandler
.
post
(
new
Runnable
()
{
@Override
public
void
run
()
{
eventListener
.
onAudioTrackInitializationError
(
e
);
}
});
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment