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
1ff78292
authored
Jan 28, 2022
by
Dustin
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Fix BitmapFactoryVideoRenderer sync issues
parent
1d85bf24
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
140 additions
and
127 deletions
library/core/src/main/java/com/google/android/exoplayer2/video/BitmapFactoryVideoRenderer.java
library/core/src/main/java/com/google/android/exoplayer2/video/BitmapFactoryVideoRenderer.java
View file @
1ff78292
...
@@ -6,6 +6,7 @@ import android.graphics.Canvas;
...
@@ -6,6 +6,7 @@ import android.graphics.Canvas;
import
android.graphics.Point
;
import
android.graphics.Point
;
import
android.graphics.Rect
;
import
android.graphics.Rect
;
import
android.os.Handler
;
import
android.os.Handler
;
import
android.os.SystemClock
;
import
android.view.Surface
;
import
android.view.Surface
;
import
androidx.annotation.NonNull
;
import
androidx.annotation.NonNull
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.Nullable
;
...
@@ -20,27 +21,21 @@ import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
...
@@ -20,27 +21,21 @@ import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import
com.google.android.exoplayer2.source.SampleStream
;
import
com.google.android.exoplayer2.source.SampleStream
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
java.nio.ByteBuffer
;
import
java.nio.ByteBuffer
;
import
java.util.concurrent.ArrayBlockingQueue
;
import
java.util.concurrent.ThreadPoolExecutor
;
import
java.util.concurrent.TimeUnit
;
public
class
BitmapFactoryVideoRenderer
extends
BaseRenderer
{
public
class
BitmapFactoryVideoRenderer
extends
BaseRenderer
{
private
static
final
String
TAG
=
"BitmapFactoryRenderer"
;
private
static
final
String
TAG
=
"BitmapFactoryRenderer"
;
final
VideoRendererEventListener
.
EventDispatcher
eventDispatcher
;
final
VideoRendererEventListener
.
EventDispatcher
eventDispatcher
;
@Nullable
@Nullable
Surface
surface
;
volatile
Surface
surface
;
private
boolean
firstFrameRendered
;
private
final
Rect
rect
=
new
Rect
();
private
final
Rect
rect
=
new
Rect
();
private
final
Point
lastSurface
=
new
Point
();
private
final
Point
lastSurface
=
new
Point
();
private
final
RenderRunnable
renderRunnable
=
new
RenderRunnable
();
private
final
Thread
thread
=
new
Thread
(
renderRunnable
,
"BitmapFactoryVideoRenderer"
);
private
VideoSize
lastVideoSize
=
VideoSize
.
UNKNOWN
;
private
VideoSize
lastVideoSize
=
VideoSize
.
UNKNOWN
;
@Nullable
private
ThreadPoolExecutor
renderExecutor
;
@Nullable
private
Thread
thread
;
private
long
currentTimeUs
;
private
long
currentTimeUs
;
private
long
nextF
rameUs
;
private
long
f
rameUs
;
private
long
frameUs
=
Long
.
MIN_VALUE
;
boolean
ended
;
private
boolean
ended
;
@Nullable
private
DecoderCounters
decoderCounters
;
private
DecoderCounters
decoderCounters
;
public
BitmapFactoryVideoRenderer
(
@Nullable
Handler
eventHandler
,
public
BitmapFactoryVideoRenderer
(
@Nullable
Handler
eventHandler
,
...
@@ -58,68 +53,43 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
...
@@ -58,68 +53,43 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
@Override
@Override
protected
void
onEnabled
(
boolean
joining
,
boolean
mayRenderStartOfStream
)
protected
void
onEnabled
(
boolean
joining
,
boolean
mayRenderStartOfStream
)
throws
ExoPlaybackException
{
throws
ExoPlaybackException
{
firstFrameRendered
=
ended
=
false
;
renderExecutor
=
new
ThreadPoolExecutor
(
1
,
1
,
0
,
TimeUnit
.
SECONDS
,
new
ArrayBlockingQueue
<>(
3
));
decoderCounters
=
new
DecoderCounters
();
decoderCounters
=
new
DecoderCounters
();
eventDispatcher
.
enabled
(
decoderCounters
);
eventDispatcher
.
enabled
(
decoderCounters
);
thread
.
start
();
}
}
@Override
@Override
protected
void
onDisabled
()
{
protected
void
onDisabled
()
{
renderExecutor
.
shutdownNow
();
renderRunnable
.
running
=
false
;
eventDispatcher
.
disabled
(
decoderCounters
);
thread
.
interrupt
();
@Nullable
final
DecoderCounters
decoderCounters
=
this
.
decoderCounters
;
if
(
decoderCounters
!=
null
)
{
eventDispatcher
.
disabled
(
decoderCounters
);
}
}
}
private
void
onFormatChanged
(
@NonNull
FormatHolder
formatHolder
)
{
private
void
onFormatChanged
(
@NonNull
FormatHolder
formatHolder
)
{
@Nullable
final
Format
format
=
formatHolder
.
format
;
@Nullable
final
Format
format
=
formatHolder
.
format
;
if
(
format
!=
null
)
{
if
(
format
!=
null
)
{
eventDispatcher
.
inputFormatChanged
(
format
,
null
);
frameUs
=
(
long
)(
1_000_000L
/
format
.
frameRate
);
frameUs
=
(
long
)(
1_000_000L
/
format
.
frameRate
);
eventDispatcher
.
inputFormatChanged
(
format
,
null
);
}
}
}
}
@Override
@Override
public
void
render
(
long
positionUs
,
long
elapsedRealtimeUs
)
throws
ExoPlaybackException
{
public
void
render
(
long
positionUs
,
long
elapsedRealtimeUs
)
throws
ExoPlaybackException
{
//Log.d(TAG, "Render: us=" + positionUs);
synchronized
(
eventDispatcher
)
{
synchronized
(
eventDispatcher
)
{
currentTimeUs
=
positionUs
;
currentTimeUs
=
positionUs
;
eventDispatcher
.
notify
();
eventDispatcher
.
notify
();
}
}
if
(
renderExecutor
.
getActiveCount
()
>
0
)
{
//Handle decoder overrun
if
(
positionUs
>
nextFrameUs
)
{
long
us
=
(
positionUs
-
nextFrameUs
)
+
frameUs
;
long
dropped
=
us
/
frameUs
;
eventDispatcher
.
droppedFrames
((
int
)
dropped
,
us
);
nextFrameUs
+=
frameUs
*
dropped
;
}
return
;
}
final
FormatHolder
formatHolder
=
getFormatHolder
();
final
DecoderInputBuffer
decoderInputBuffer
=
new
DecoderInputBuffer
(
DecoderInputBuffer
.
BUFFER_REPLACEMENT_MODE_NORMAL
);
final
int
result
=
readSource
(
formatHolder
,
decoderInputBuffer
,
frameUs
==
Long
.
MIN_VALUE
?
SampleStream
.
FLAG_REQUIRE_FORMAT
:
0
);
if
(
result
==
C
.
RESULT_BUFFER_READ
)
{
renderExecutor
.
execute
(
new
RenderRunnable
(
decoderInputBuffer
,
nextFrameUs
));
if
(
decoderInputBuffer
.
isEndOfStream
())
{
ended
=
true
;
}
else
{
nextFrameUs
+=
frameUs
;
}
}
else
if
(
result
==
C
.
RESULT_FORMAT_READ
)
{
onFormatChanged
(
formatHolder
);
}
}
}
@Override
@Override
protected
void
onPositionReset
(
long
positionUs
,
boolean
joining
)
throws
ExoPlaybackException
{
protected
void
onPositionReset
(
long
positionUs
,
boolean
joining
)
throws
ExoPlaybackException
{
nextFrameUs
=
positionUs
;
thread
.
interrupt
();
@Nullable
final
Thread
thread
=
this
.
thread
;
if
(
thread
!=
null
)
{
thread
.
interrupt
();
}
}
}
@Override
@Override
...
@@ -141,7 +111,7 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
...
@@ -141,7 +111,7 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
@Override
@Override
public
boolean
isEnded
()
{
public
boolean
isEnded
()
{
return
ended
&&
renderExecutor
.
getActiveCount
()
==
0
;
return
renderRunnable
.
ended
;
}
}
@Override
@Override
...
@@ -154,96 +124,139 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
...
@@ -154,96 +124,139 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
}
}
class
RenderRunnable
implements
Runnable
{
class
RenderRunnable
implements
Runnable
{
@Nullable
private
volatile
boolean
ended
;
private
DecoderInputBuffer
decoderInputBuffer
;
private
boolean
firstFrameRendered
;
private
final
long
renderUs
;
private
volatile
boolean
running
=
true
;
RenderRunnable
(
@NonNull
final
DecoderInputBuffer
decoderInputBuffer
,
long
renderUs
)
{
this
.
decoderInputBuffer
=
decoderInputBuffer
;
this
.
renderUs
=
renderUs
;
}
private
boolean
maybeDropFrame
(
long
frameUs
)
{
@Nullable
if
(
Math
.
abs
(
frameUs
-
currentTimeUs
)
>
frameUs
)
{
private
Bitmap
decodeInputBuffer
(
final
DecoderInputBuffer
decoderInputBuffer
)
{
eventDispatcher
.
droppedFrames
(
1
,
frameUs
);
@Nullable
final
ByteBuffer
byteBuffer
=
decoderInputBuffer
.
data
;
return
true
;
if
(
byteBuffer
!=
null
)
{
final
Bitmap
bitmap
;
try
{
bitmap
=
BitmapFactory
.
decodeByteArray
(
byteBuffer
.
array
(),
byteBuffer
.
arrayOffset
(),
byteBuffer
.
arrayOffset
()
+
byteBuffer
.
position
());
if
(
bitmap
==
null
)
{
eventDispatcher
.
videoCodecError
(
new
NullPointerException
(
"Decode bytes failed"
));
}
else
{
return
bitmap
;
}
}
catch
(
Exception
e
)
{
eventDispatcher
.
videoCodecError
(
e
);
}
}
}
return
false
;
return
null
;
}
}
public
void
run
()
{
private
void
renderBitmap
(
final
Bitmap
bitmap
,
@NonNull
final
Surface
surface
)
{
if
(
maybeDropFrame
(
renderUs
))
{
//Log.d(TAG, "Drawing: " + bitmap.getWidth() + "x" + bitmap.getHeight());
return
;
final
Canvas
canvas
=
surface
.
lockCanvas
(
null
);
final
Rect
clipBounds
=
canvas
.
getClipBounds
();
final
VideoSize
videoSize
=
new
VideoSize
(
bitmap
.
getWidth
(),
bitmap
.
getHeight
());
final
boolean
videoSizeChanged
;
if
(
videoSize
.
equals
(
lastVideoSize
))
{
videoSizeChanged
=
false
;
}
else
{
lastVideoSize
=
videoSize
;
eventDispatcher
.
videoSizeChanged
(
videoSize
);
videoSizeChanged
=
true
;
}
}
if
(
lastSurface
.
x
!=
clipBounds
.
width
()
||
lastSurface
.
y
!=
clipBounds
.
height
()
||
videoSizeChanged
)
{
lastSurface
.
x
=
clipBounds
.
width
();
lastSurface
.
y
=
clipBounds
.
height
();
final
float
scaleX
=
lastSurface
.
x
/
(
float
)
videoSize
.
width
;
final
float
scaleY
=
lastSurface
.
y
/
(
float
)
videoSize
.
height
;
final
float
scale
=
Math
.
min
(
scaleX
,
scaleY
);
final
float
width
=
videoSize
.
width
*
scale
;
final
float
height
=
videoSize
.
height
*
scale
;
final
int
x
=
(
int
)(
lastSurface
.
x
-
width
)
/
2
;
final
int
y
=
(
int
)(
lastSurface
.
y
-
height
)
/
2
;
rect
.
set
(
x
,
y
,
x
+
(
int
)
width
,
y
+
(
int
)
height
);
}
canvas
.
drawBitmap
(
bitmap
,
null
,
rect
,
null
);
surface
.
unlockCanvasAndPost
(
canvas
);
@Nullable
@Nullable
final
ByteBuffer
byteBuffer
=
decoderInputBuffer
.
data
;
final
DecoderCounters
decoderCounters
=
BitmapFactoryVideoRenderer
.
this
.
decoderCounters
;
@Nullable
if
(
decoderCounters
!=
null
)
{
final
Surface
surface
=
BitmapFactoryVideoRenderer
.
this
.
surface
;
decoderCounters
.
renderedOutputBufferCount
++;
if
(
byteBuffer
!=
null
&&
surface
!=
null
)
{
}
final
Bitmap
bitmap
;
if
(!
firstFrameRendered
)
{
firstFrameRendered
=
true
;
eventDispatcher
.
renderedFirstFrame
(
surface
);
}
}
/**
*
* @return true if interrupted
*/
private
boolean
sleep
()
{
synchronized
(
eventDispatcher
)
{
try
{
try
{
bitmap
=
BitmapFactory
.
decodeByteArray
(
byteBuffer
.
array
(),
byteBuffer
.
arrayOffset
(),
byteBuffer
.
arrayOffset
()
+
byteBuffer
.
position
());
eventDispatcher
.
wait
();
}
catch
(
Exception
e
)
{
return
false
;
eventDispatcher
.
videoCodecError
(
e
);
}
catch
(
InterruptedException
e
)
{
return
;
//If we are interrupted, treat as a cancel
return
true
;
}
}
if
(
bitmap
==
null
)
{
}
eventDispatcher
.
videoCodecError
(
new
NullPointerException
(
"Decode bytes failed"
));
}
return
;
}
public
void
run
()
{
decoderInputBuffer
=
null
;
final
FormatHolder
formatHolder
=
getFormatHolder
();
//Wait for time to advance to display the Bitmap
@NonNull
synchronized
(
eventDispatcher
)
{
final
DecoderInputBuffer
decoderInputBuffer
=
while
(
currentTimeUs
<
renderUs
)
{
new
DecoderInputBuffer
(
DecoderInputBuffer
.
BUFFER_REPLACEMENT_MODE_NORMAL
);
try
{
long
start
=
SystemClock
.
uptimeMillis
();
thread
=
Thread
.
currentThread
();
main:
eventDispatcher
.
wait
();
while
(
running
)
{
}
catch
(
InterruptedException
e
)
{
decoderInputBuffer
.
clear
();
//If we are interrupted, treat as a cancel
final
int
result
=
readSource
(
formatHolder
,
decoderInputBuffer
,
return
;
formatHolder
.
format
==
null
?
SampleStream
.
FLAG_REQUIRE_FORMAT
:
0
);
}
finally
{
if
(
result
==
C
.
RESULT_BUFFER_READ
)
{
thread
=
null
;
if
(
decoderInputBuffer
.
isEndOfStream
())
{
ended
=
true
;
if
(!
sleep
())
{
ended
=
false
;
}
}
continue
;
}
}
}
final
long
leadUs
=
decoderInputBuffer
.
timeUs
-
currentTimeUs
;
if
(
maybeDropFrame
(
renderUs
))
{
//If we are more than 1/2 a frame behind, skip the next frame
return
;
if
(
leadUs
<
-
frameUs
/
2
)
{
}
eventDispatcher
.
droppedFrames
(
1
,
SystemClock
.
uptimeMillis
()
-
start
);
//Log.d(TAG, "Drawing: " + bitmap.getWidth() + "x" + bitmap.getHeight());
start
=
SystemClock
.
uptimeMillis
();
final
Canvas
canvas
=
surface
.
lockCanvas
(
null
);
continue
;
}
final
Rect
clipBounds
=
canvas
.
getClipBounds
();
start
=
SystemClock
.
uptimeMillis
();
final
VideoSize
videoSize
=
new
VideoSize
(
bitmap
.
getWidth
(),
bitmap
.
getHeight
());
final
boolean
videoSizeChanged
;
if
(
videoSize
.
equals
(
lastVideoSize
))
{
videoSizeChanged
=
false
;
}
else
{
lastVideoSize
=
videoSize
;
eventDispatcher
.
videoSizeChanged
(
videoSize
);
videoSizeChanged
=
true
;
}
if
(
lastSurface
.
x
!=
clipBounds
.
width
()
||
lastSurface
.
y
!=
clipBounds
.
height
()
||
videoSizeChanged
)
{
lastSurface
.
x
=
clipBounds
.
width
();
lastSurface
.
y
=
clipBounds
.
height
();
final
float
scaleX
=
lastSurface
.
x
/
(
float
)
videoSize
.
width
;
final
float
scaleY
=
lastSurface
.
y
/
(
float
)
videoSize
.
height
;
final
float
scale
=
Math
.
min
(
scaleX
,
scaleY
);
final
float
width
=
videoSize
.
width
*
scale
;
final
float
height
=
videoSize
.
height
*
scale
;
final
int
x
=
(
int
)(
lastSurface
.
x
-
width
)
/
2
;
final
int
y
=
(
int
)(
lastSurface
.
y
-
height
)
/
2
;
rect
.
set
(
x
,
y
,
x
+
(
int
)
width
,
y
+
(
int
)
height
);
}
canvas
.
drawBitmap
(
bitmap
,
null
,
rect
,
null
);
surface
.
unlockCanvasAndPost
(
canvas
);
@Nullable
decoderCounters
.
renderedOutputBufferCount
++;
final
Bitmap
bitmap
=
decodeInputBuffer
(
decoderInputBuffer
);
if
(!
firstFrameRendered
)
{
if
(
bitmap
==
null
)
{
firstFrameRendered
=
true
;
continue
;
eventDispatcher
.
renderedFirstFrame
(
surface
);
}
while
(
currentTimeUs
<
decoderInputBuffer
.
timeUs
)
{
//Log.d(TAG, "Sleep: us=" + currentTimeUs);
if
(
sleep
())
{
continue
main
;
}
if
(!
running
)
{
break
main
;
}
}
@Nullable
final
Surface
surface
=
BitmapFactoryVideoRenderer
.
this
.
surface
;
if
(
surface
!=
null
)
{
renderBitmap
(
bitmap
,
surface
);
}
}
else
if
(
result
==
C
.
RESULT_FORMAT_READ
)
{
onFormatChanged
(
formatHolder
);
}
}
}
}
ended
=
true
;
}
}
}
}
}
}
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