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
b9f32539
authored
Nov 27, 2014
by
ojw28
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge pull request #176 from google/dev
dev -> dev-hls
parents
40f31722
165562d8
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
267 additions
and
40 deletions
library/src/main/java/com/google/android/exoplayer/SmoothFrameReleaseTimeHelper.java
library/src/main/java/com/google/android/exoplayer/dash/mpd/SegmentBase.java
library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.java
library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.java
library/src/main/java/com/google/android/exoplayer/util/Util.java
library/src/main/java/com/google/android/exoplayer/SmoothFrameReleaseTimeHelper.java
0 → 100644
View file @
b9f32539
/*
* 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.MediaCodecVideoTrackRenderer.FrameReleaseTimeHelper
;
import
android.annotation.TargetApi
;
import
android.view.Choreographer
;
import
android.view.Choreographer.FrameCallback
;
/**
* Makes a best effort to adjust frame release timestamps for a smoother visual result.
*/
@TargetApi
(
16
)
public
class
SmoothFrameReleaseTimeHelper
implements
FrameReleaseTimeHelper
,
FrameCallback
{
private
static
final
long
CHOREOGRAPHER_SAMPLE_DELAY_MILLIS
=
500
;
private
static
final
long
MAX_ALLOWED_DRIFT_NS
=
20000000
;
private
static
final
long
VSYNC_OFFSET_PERCENTAGE
=
80
;
private
static
final
int
MIN_FRAMES_FOR_ADJUSTMENT
=
6
;
private
final
boolean
usePrimaryDisplayVsync
;
private
final
long
vsyncDurationNs
;
private
final
long
vsyncOffsetNs
;
private
Choreographer
choreographer
;
private
long
sampledVsyncTimeNs
;
private
long
lastUnadjustedFrameTimeUs
;
private
long
adjustedLastFrameTimeNs
;
private
long
pendingAdjustedFrameTimeNs
;
private
boolean
haveSync
;
private
long
syncReleaseTimeNs
;
private
long
syncFrameTimeNs
;
private
int
frameCount
;
/**
* @param primaryDisplayRefreshRate The refresh rate of the default display.
* @param usePrimaryDisplayVsync Whether to snap to the primary display vsync. May not be
* suitable when rendering to secondary displays.
*/
public
SmoothFrameReleaseTimeHelper
(
float
primaryDisplayRefreshRate
,
boolean
usePrimaryDisplayVsync
)
{
this
.
usePrimaryDisplayVsync
=
usePrimaryDisplayVsync
;
if
(
usePrimaryDisplayVsync
)
{
vsyncDurationNs
=
(
long
)
(
1000000000
d
/
primaryDisplayRefreshRate
);
vsyncOffsetNs
=
(
vsyncDurationNs
*
VSYNC_OFFSET_PERCENTAGE
)
/
100
;
}
else
{
vsyncDurationNs
=
-
1
;
vsyncOffsetNs
=
-
1
;
}
}
@Override
public
void
enable
()
{
haveSync
=
false
;
if
(
usePrimaryDisplayVsync
)
{
sampledVsyncTimeNs
=
0
;
choreographer
=
Choreographer
.
getInstance
();
choreographer
.
postFrameCallback
(
this
);
}
}
@Override
public
void
disable
()
{
if
(
usePrimaryDisplayVsync
)
{
choreographer
.
removeFrameCallback
(
this
);
choreographer
=
null
;
}
}
@Override
public
void
doFrame
(
long
vsyncTimeNs
)
{
sampledVsyncTimeNs
=
vsyncTimeNs
;
choreographer
.
postFrameCallbackDelayed
(
this
,
CHOREOGRAPHER_SAMPLE_DELAY_MILLIS
);
}
@Override
public
long
adjustReleaseTime
(
long
unadjustedFrameTimeUs
,
long
unadjustedReleaseTimeNs
)
{
long
unadjustedFrameTimeNs
=
unadjustedFrameTimeUs
*
1000
;
// Until we know better, the adjustment will be a no-op.
long
adjustedFrameTimeNs
=
unadjustedFrameTimeNs
;
long
adjustedReleaseTimeNs
=
unadjustedReleaseTimeNs
;
if
(
haveSync
)
{
// See if we've advanced to the next frame.
if
(
unadjustedFrameTimeUs
!=
lastUnadjustedFrameTimeUs
)
{
frameCount
++;
adjustedLastFrameTimeNs
=
pendingAdjustedFrameTimeNs
;
}
if
(
frameCount
>=
MIN_FRAMES_FOR_ADJUSTMENT
)
{
// We're synced and have waited the required number of frames to apply an adjustment.
// Calculate the average frame time across all the frames we've seen since the last sync.
// This will typically give us a framerate at a finer granularity than the frame times
// themselves (which often only have millisecond granularity).
long
averageFrameTimeNs
=
(
unadjustedFrameTimeNs
-
syncFrameTimeNs
)
/
frameCount
;
// Project the adjusted frame time forward using the average.
long
candidateAdjustedFrameTimeNs
=
adjustedLastFrameTimeNs
+
averageFrameTimeNs
;
if
(
isDriftTooLarge
(
candidateAdjustedFrameTimeNs
,
unadjustedReleaseTimeNs
))
{
haveSync
=
false
;
}
else
{
adjustedFrameTimeNs
=
candidateAdjustedFrameTimeNs
;
adjustedReleaseTimeNs
=
syncReleaseTimeNs
+
adjustedFrameTimeNs
-
syncFrameTimeNs
;
}
}
else
{
// We're synced but haven't waited the required number of frames to apply an adjustment.
// Check drift anyway.
if
(
isDriftTooLarge
(
unadjustedFrameTimeNs
,
unadjustedReleaseTimeNs
))
{
haveSync
=
false
;
}
}
}
// If we need to sync, do so now.
if
(!
haveSync
)
{
syncFrameTimeNs
=
unadjustedFrameTimeNs
;
syncReleaseTimeNs
=
unadjustedReleaseTimeNs
;
frameCount
=
0
;
haveSync
=
true
;
onSynced
();
}
lastUnadjustedFrameTimeUs
=
unadjustedFrameTimeUs
;
pendingAdjustedFrameTimeNs
=
adjustedFrameTimeNs
;
if
(
sampledVsyncTimeNs
==
0
)
{
return
adjustedReleaseTimeNs
;
}
// Find the timestamp of the closest vsync. This is the vsync that we're targeting.
long
snappedTimeNs
=
closestVsync
(
adjustedReleaseTimeNs
,
sampledVsyncTimeNs
,
vsyncDurationNs
);
// Apply an offset so that we release before the target vsync, but after the previous one.
return
snappedTimeNs
-
vsyncOffsetNs
;
}
protected
void
onSynced
()
{
// Do nothing.
}
private
boolean
isDriftTooLarge
(
long
frameTimeNs
,
long
releaseTimeNs
)
{
long
elapsedFrameTimeNs
=
frameTimeNs
-
syncFrameTimeNs
;
long
elapsedReleaseTimeNs
=
releaseTimeNs
-
syncReleaseTimeNs
;
return
Math
.
abs
(
elapsedReleaseTimeNs
-
elapsedFrameTimeNs
)
>
MAX_ALLOWED_DRIFT_NS
;
}
private
static
long
closestVsync
(
long
releaseTime
,
long
sampledVsyncTime
,
long
vsyncDuration
)
{
long
vsyncCount
=
(
releaseTime
-
sampledVsyncTime
)
/
vsyncDuration
;
long
snappedTimeNs
=
sampledVsyncTime
+
(
vsyncDuration
*
vsyncCount
);
long
snappedBeforeNs
;
long
snappedAfterNs
;
if
(
releaseTime
<=
snappedTimeNs
)
{
snappedBeforeNs
=
snappedTimeNs
-
vsyncDuration
;
snappedAfterNs
=
snappedTimeNs
;
}
else
{
snappedBeforeNs
=
snappedTimeNs
;
snappedAfterNs
=
snappedTimeNs
+
vsyncDuration
;
}
long
snappedAfterDiff
=
snappedAfterNs
-
releaseTime
;
long
snappedBeforeDiff
=
releaseTime
-
snappedBeforeNs
;
return
snappedAfterDiff
<
snappedBeforeDiff
?
snappedAfterNs
:
snappedBeforeNs
;
}
}
library/src/main/java/com/google/android/exoplayer/dash/mpd/SegmentBase.java
View file @
b9f32539
...
...
@@ -15,6 +15,8 @@
*/
package
com
.
google
.
android
.
exoplayer
.
dash
.
mpd
;
import
com.google.android.exoplayer.util.Util
;
import
android.net.Uri
;
import
java.util.List
;
...
...
@@ -155,7 +157,7 @@ public abstract class SegmentBase {
}
else
{
unscaledSegmentTime
=
(
sequenceNumber
-
startNumber
)
*
duration
;
}
return
(
unscaledSegmentTime
*
1000000
)
/
timescale
;
return
Util
.
scaleLargeTimestamp
(
unscaledSegmentTime
,
1000000
,
timescale
)
;
}
public
abstract
RangedUri
getSegmentUrl
(
Representation
representation
,
int
index
);
...
...
library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.java
View file @
b9f32539
...
...
@@ -53,19 +53,8 @@ public class SmoothStreamingManifest {
this
.
isLive
=
isLive
;
this
.
protectionElement
=
protectionElement
;
this
.
streamElements
=
streamElements
;
if
(
timescale
>=
MICROS_PER_SECOND
&&
(
timescale
%
MICROS_PER_SECOND
)
==
0
)
{
long
divisionFactor
=
timescale
/
MICROS_PER_SECOND
;
dvrWindowLengthUs
=
dvrWindowLength
/
divisionFactor
;
durationUs
=
duration
/
divisionFactor
;
}
else
if
(
timescale
<
MICROS_PER_SECOND
&&
(
MICROS_PER_SECOND
%
timescale
)
==
0
)
{
long
multiplicationFactor
=
MICROS_PER_SECOND
/
timescale
;
dvrWindowLengthUs
=
dvrWindowLength
*
multiplicationFactor
;
durationUs
=
duration
*
multiplicationFactor
;
}
else
{
double
multiplicationFactor
=
(
double
)
MICROS_PER_SECOND
/
timescale
;
dvrWindowLengthUs
=
(
long
)
(
dvrWindowLength
*
multiplicationFactor
);
durationUs
=
(
long
)
(
duration
*
multiplicationFactor
);
}
dvrWindowLengthUs
=
Util
.
scaleLargeTimestamp
(
dvrWindowLength
,
MICROS_PER_SECOND
,
timescale
);
durationUs
=
Util
.
scaleLargeTimestamp
(
duration
,
MICROS_PER_SECOND
,
timescale
);
}
/**
...
...
@@ -186,26 +175,10 @@ public class SmoothStreamingManifest {
this
.
tracks
=
tracks
;
this
.
chunkCount
=
chunkStartTimes
.
size
();
this
.
chunkStartTimes
=
chunkStartTimes
;
chunkStartTimesUs
=
new
long
[
chunkStartTimes
.
size
()];
if
(
timescale
>=
MICROS_PER_SECOND
&&
(
timescale
%
MICROS_PER_SECOND
)
==
0
)
{
long
divisionFactor
=
timescale
/
MICROS_PER_SECOND
;
for
(
int
i
=
0
;
i
<
chunkStartTimesUs
.
length
;
i
++)
{
chunkStartTimesUs
[
i
]
=
chunkStartTimes
.
get
(
i
)
/
divisionFactor
;
}
lastChunkDurationUs
=
lastChunkDuration
/
divisionFactor
;
}
else
if
(
timescale
<
MICROS_PER_SECOND
&&
(
MICROS_PER_SECOND
%
timescale
)
==
0
)
{
long
multiplicationFactor
=
MICROS_PER_SECOND
/
timescale
;
for
(
int
i
=
0
;
i
<
chunkStartTimesUs
.
length
;
i
++)
{
chunkStartTimesUs
[
i
]
=
chunkStartTimes
.
get
(
i
)
*
multiplicationFactor
;
}
lastChunkDurationUs
=
lastChunkDuration
*
multiplicationFactor
;
}
else
{
double
multiplicationFactor
=
(
double
)
MICROS_PER_SECOND
/
timescale
;
for
(
int
i
=
0
;
i
<
chunkStartTimesUs
.
length
;
i
++)
{
chunkStartTimesUs
[
i
]
=
(
long
)
(
chunkStartTimes
.
get
(
i
)
*
multiplicationFactor
);
}
lastChunkDurationUs
=
(
long
)
(
lastChunkDuration
*
multiplicationFactor
);
}
lastChunkDurationUs
=
Util
.
scaleLargeTimestamp
(
lastChunkDuration
,
MICROS_PER_SECOND
,
timescale
);
chunkStartTimesUs
=
Util
.
scaleLargeTimestamps
(
chunkStartTimes
,
MICROS_PER_SECOND
,
timescale
);
}
/**
...
...
library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.java
View file @
b9f32539
...
...
@@ -450,6 +450,7 @@ public class SmoothStreamingManifestParser implements ManifestParser<SmoothStrea
private
static
final
String
KEY_FRAGMENT_DURATION
=
"d"
;
private
static
final
String
KEY_FRAGMENT_START_TIME
=
"t"
;
private
static
final
String
KEY_FRAGMENT_REPEAT_COUNT
=
"r"
;
private
final
Uri
baseUri
;
private
final
List
<
TrackElement
>
tracks
;
...
...
@@ -504,9 +505,18 @@ public class SmoothStreamingManifestParser implements ManifestParser<SmoothStrea
throw
new
ParserException
(
"Unable to infer start time"
);
}
}
chunkIndex
++;
startTimes
.
add
(
startTime
);
lastChunkDuration
=
parseLong
(
parser
,
KEY_FRAGMENT_DURATION
,
-
1L
);
chunkIndex
++;
// Handle repeated chunks.
long
repeatCount
=
parseLong
(
parser
,
KEY_FRAGMENT_REPEAT_COUNT
,
1L
);
if
(
repeatCount
>
1
&&
lastChunkDuration
==
-
1L
)
{
throw
new
ParserException
(
"Repeated chunk with unspecified duration"
);
}
for
(
int
i
=
1
;
i
<
repeatCount
;
i
++)
{
chunkIndex
++;
startTimes
.
add
(
startTime
+
(
lastChunkDuration
*
i
));
}
}
private
void
parseStreamElementStartTag
(
XmlPullParser
parser
)
throws
ParserException
{
...
...
library/src/main/java/com/google/android/exoplayer/util/Util.java
View file @
b9f32539
...
...
@@ -55,7 +55,8 @@ public final class Util {
+
"([Zz]|((\\+|\\-)(\\d\\d):(\\d\\d)))?"
);
private
static
final
Pattern
XS_DURATION_PATTERN
=
Pattern
.
compile
(
"^PT(([0-9]*)H)?(([0-9]*)M)?(([0-9.]*)S)?$"
);
Pattern
.
compile
(
"^P(([0-9]*)Y)?(([0-9]*)M)?(([0-9]*)D)?"
+
"(T(([0-9]*)H)?(([0-9]*)M)?(([0-9.]*)S)?)?$"
);
private
Util
()
{}
...
...
@@ -274,11 +275,19 @@ public final class Util {
public
static
long
parseXsDuration
(
String
value
)
{
Matcher
matcher
=
XS_DURATION_PATTERN
.
matcher
(
value
);
if
(
matcher
.
matches
())
{
String
hours
=
matcher
.
group
(
2
);
double
durationSeconds
=
(
hours
!=
null
)
?
Double
.
parseDouble
(
hours
)
*
3600
:
0
;
String
minutes
=
matcher
.
group
(
4
);
// Durations containing years and months aren't completely defined. We assume there are
// 30.4368 days in a month, and 365.242 days in a year.
String
years
=
matcher
.
group
(
2
);
double
durationSeconds
=
(
years
!=
null
)
?
Double
.
parseDouble
(
years
)
*
31556908
:
0
;
String
months
=
matcher
.
group
(
4
);
durationSeconds
+=
(
months
!=
null
)
?
Double
.
parseDouble
(
months
)
*
2629739
:
0
;
String
days
=
matcher
.
group
(
6
);
durationSeconds
+=
(
days
!=
null
)
?
Double
.
parseDouble
(
days
)
*
86400
:
0
;
String
hours
=
matcher
.
group
(
9
);
durationSeconds
+=
(
hours
!=
null
)
?
Double
.
parseDouble
(
hours
)
*
3600
:
0
;
String
minutes
=
matcher
.
group
(
11
);
durationSeconds
+=
(
minutes
!=
null
)
?
Double
.
parseDouble
(
minutes
)
*
60
:
0
;
String
seconds
=
matcher
.
group
(
6
);
String
seconds
=
matcher
.
group
(
13
);
durationSeconds
+=
(
seconds
!=
null
)
?
Double
.
parseDouble
(
seconds
)
:
0
;
return
(
long
)
(
durationSeconds
*
1000
);
}
else
{
...
...
@@ -337,4 +346,57 @@ public final class Util {
return
time
;
}
/**
* Scales a large timestamp.
* <p>
* Logically, scaling consists of a multiplication followed by a division. The actual operations
* performed are designed to minimize the probability of overflow.
*
* @param timestamp The timestamp to scale.
* @param multiplier The multiplier.
* @param divisor The divisor.
* @return The scaled timestamp.
*/
public
static
long
scaleLargeTimestamp
(
long
timestamp
,
long
multiplier
,
long
divisor
)
{
if
(
divisor
>=
multiplier
&&
(
divisor
%
multiplier
)
==
0
)
{
long
divisionFactor
=
divisor
/
multiplier
;
return
timestamp
/
divisionFactor
;
}
else
if
(
divisor
<
multiplier
&&
(
multiplier
%
divisor
)
==
0
)
{
long
multiplicationFactor
=
multiplier
/
divisor
;
return
timestamp
*
multiplicationFactor
;
}
else
{
double
multiplicationFactor
=
(
double
)
multiplier
/
divisor
;
return
(
long
)
(
timestamp
*
multiplicationFactor
);
}
}
/**
* Applies {@link #scaleLargeTimestamp(long, long, long)} to a list of unscaled timestamps.
*
* @param timestamps The timestamps to scale.
* @param multiplier The multiplier.
* @param divisor The divisor.
* @return The scaled timestamps.
*/
public
static
long
[]
scaleLargeTimestamps
(
List
<
Long
>
timestamps
,
long
multiplier
,
long
divisor
)
{
long
[]
scaledTimestamps
=
new
long
[
timestamps
.
size
()];
if
(
divisor
>=
multiplier
&&
(
divisor
%
multiplier
)
==
0
)
{
long
divisionFactor
=
divisor
/
multiplier
;
for
(
int
i
=
0
;
i
<
scaledTimestamps
.
length
;
i
++)
{
scaledTimestamps
[
i
]
=
timestamps
.
get
(
i
)
/
divisionFactor
;
}
}
else
if
(
divisor
<
multiplier
&&
(
multiplier
%
divisor
)
==
0
)
{
long
multiplicationFactor
=
multiplier
/
divisor
;
for
(
int
i
=
0
;
i
<
scaledTimestamps
.
length
;
i
++)
{
scaledTimestamps
[
i
]
=
timestamps
.
get
(
i
)
*
multiplicationFactor
;
}
}
else
{
double
multiplicationFactor
=
(
double
)
multiplier
/
divisor
;
for
(
int
i
=
0
;
i
<
scaledTimestamps
.
length
;
i
++)
{
scaledTimestamps
[
i
]
=
(
long
)
(
timestamps
.
get
(
i
)
*
multiplicationFactor
);
}
}
return
scaledTimestamps
;
}
}
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