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
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
106 additions
and
93 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;
import
android.graphics.Point
;
import
android.graphics.Rect
;
import
android.os.Handler
;
import
android.os.SystemClock
;
import
android.view.Surface
;
import
androidx.annotation.NonNull
;
import
androidx.annotation.Nullable
;
...
...
@@ -20,27 +21,21 @@ import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import
com.google.android.exoplayer2.source.SampleStream
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
java.nio.ByteBuffer
;
import
java.util.concurrent.ArrayBlockingQueue
;
import
java.util.concurrent.ThreadPoolExecutor
;
import
java.util.concurrent.TimeUnit
;
public
class
BitmapFactoryVideoRenderer
extends
BaseRenderer
{
private
static
final
String
TAG
=
"BitmapFactoryRenderer"
;
final
VideoRendererEventListener
.
EventDispatcher
eventDispatcher
;
@Nullable
Surface
surface
;
private
boolean
firstFrameRendered
;
volatile
Surface
surface
;
private
final
Rect
rect
=
new
Rect
();
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
;
@Nullable
private
ThreadPoolExecutor
renderExecutor
;
@Nullable
private
Thread
thread
;
private
long
currentTimeUs
;
private
long
nextF
rameUs
;
private
long
frameUs
=
Long
.
MIN_VALUE
;
private
boolean
ended
;
private
long
f
rameUs
;
boolean
ended
;
@Nullable
private
DecoderCounters
decoderCounters
;
public
BitmapFactoryVideoRenderer
(
@Nullable
Handler
eventHandler
,
...
...
@@ -58,69 +53,44 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
@Override
protected
void
onEnabled
(
boolean
joining
,
boolean
mayRenderStartOfStream
)
throws
ExoPlaybackException
{
firstFrameRendered
=
ended
=
false
;
renderExecutor
=
new
ThreadPoolExecutor
(
1
,
1
,
0
,
TimeUnit
.
SECONDS
,
new
ArrayBlockingQueue
<>(
3
));
decoderCounters
=
new
DecoderCounters
();
eventDispatcher
.
enabled
(
decoderCounters
);
thread
.
start
();
}
@Override
protected
void
onDisabled
()
{
renderExecutor
.
shutdownNow
();
renderRunnable
.
running
=
false
;
thread
.
interrupt
();
@Nullable
final
DecoderCounters
decoderCounters
=
this
.
decoderCounters
;
if
(
decoderCounters
!=
null
)
{
eventDispatcher
.
disabled
(
decoderCounters
);
}
}
private
void
onFormatChanged
(
@NonNull
FormatHolder
formatHolder
)
{
@Nullable
final
Format
format
=
formatHolder
.
format
;
if
(
format
!=
null
)
{
eventDispatcher
.
inputFormatChanged
(
format
,
null
);
frameUs
=
(
long
)(
1_000_000L
/
format
.
frameRate
);
eventDispatcher
.
inputFormatChanged
(
format
,
null
);
}
}
@Override
public
void
render
(
long
positionUs
,
long
elapsedRealtimeUs
)
throws
ExoPlaybackException
{
//Log.d(TAG, "Render: us=" + positionUs);
synchronized
(
eventDispatcher
)
{
currentTimeUs
=
positionUs
;
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
protected
void
onPositionReset
(
long
positionUs
,
boolean
joining
)
throws
ExoPlaybackException
{
nextFrameUs
=
positionUs
;
@Nullable
final
Thread
thread
=
this
.
thread
;
if
(
thread
!=
null
)
{
thread
.
interrupt
();
}
}
@Override
public
void
handleMessage
(
int
messageType
,
@Nullable
Object
message
)
throws
ExoPlaybackException
{
...
...
@@ -141,7 +111,7 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
@Override
public
boolean
isEnded
()
{
return
ended
&&
renderExecutor
.
getActiveCount
()
==
0
;
return
renderRunnable
.
ended
;
}
@Override
...
...
@@ -154,61 +124,31 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
}
class
RenderRunnable
implements
Runnable
{
@Nullable
private
DecoderInputBuffer
decoderInputBuffer
;
private
final
long
renderUs
;
RenderRunnable
(
@NonNull
final
DecoderInputBuffer
decoderInputBuffer
,
long
renderUs
)
{
this
.
decoderInputBuffer
=
decoderInputBuffer
;
this
.
renderUs
=
renderUs
;
}
private
boolean
maybeDropFrame
(
long
frameUs
)
{
if
(
Math
.
abs
(
frameUs
-
currentTimeUs
)
>
frameUs
)
{
eventDispatcher
.
droppedFrames
(
1
,
frameUs
);
return
true
;
}
return
false
;
}
private
volatile
boolean
ended
;
private
boolean
firstFrameRendered
;
private
volatile
boolean
running
=
true
;
public
void
run
()
{
if
(
maybeDropFrame
(
renderUs
))
{
return
;
}
@Nullable
final
ByteBuffer
byteBuffer
=
decoderInputBuffer
.
data
;
@Nullable
final
Surface
surface
=
BitmapFactoryVideoRenderer
.
this
.
surface
;
if
(
byteBuffer
!=
null
&&
surface
!=
null
)
{
private
Bitmap
decodeInputBuffer
(
final
DecoderInputBuffer
decoderInputBuffer
)
{
@Nullable
final
ByteBuffer
byteBuffer
=
decoderInputBuffer
.
data
;
if
(
byteBuffer
!=
null
)
{
final
Bitmap
bitmap
;
try
{
bitmap
=
BitmapFactory
.
decodeByteArray
(
byteBuffer
.
array
(),
byteBuffer
.
arrayOffset
(),
byteBuffer
.
arrayOffset
()
+
byteBuffer
.
position
());
}
catch
(
Exception
e
)
{
eventDispatcher
.
videoCodecError
(
e
);
return
;
}
bitmap
=
BitmapFactory
.
decodeByteArray
(
byteBuffer
.
array
(),
byteBuffer
.
arrayOffset
(),
byteBuffer
.
arrayOffset
()
+
byteBuffer
.
position
());
if
(
bitmap
==
null
)
{
eventDispatcher
.
videoCodecError
(
new
NullPointerException
(
"Decode bytes failed"
));
return
;
}
decoderInputBuffer
=
null
;
//Wait for time to advance to display the Bitmap
synchronized
(
eventDispatcher
)
{
while
(
currentTimeUs
<
renderUs
)
{
try
{
thread
=
Thread
.
currentThread
();
eventDispatcher
.
wait
();
}
catch
(
InterruptedException
e
)
{
//If we are interrupted, treat as a cancel
return
;
}
finally
{
thread
=
null
;
}
else
{
return
bitmap
;
}
}
catch
(
Exception
e
)
{
eventDispatcher
.
videoCodecError
(
e
);
}
}
if
(
maybeDropFrame
(
renderUs
))
{
return
;
return
null
;
}
private
void
renderBitmap
(
final
Bitmap
bitmap
,
@NonNull
final
Surface
surface
)
{
//Log.d(TAG, "Drawing: " + bitmap.getWidth() + "x" + bitmap.getHeight());
final
Canvas
canvas
=
surface
.
lockCanvas
(
null
);
...
...
@@ -238,12 +178,85 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
canvas
.
drawBitmap
(
bitmap
,
null
,
rect
,
null
);
surface
.
unlockCanvasAndPost
(
canvas
);
@Nullable
final
DecoderCounters
decoderCounters
=
BitmapFactoryVideoRenderer
.
this
.
decoderCounters
;
if
(
decoderCounters
!=
null
)
{
decoderCounters
.
renderedOutputBufferCount
++;
}
if
(!
firstFrameRendered
)
{
firstFrameRendered
=
true
;
eventDispatcher
.
renderedFirstFrame
(
surface
);
}
}
/**
*
* @return true if interrupted
*/
private
boolean
sleep
()
{
synchronized
(
eventDispatcher
)
{
try
{
eventDispatcher
.
wait
();
return
false
;
}
catch
(
InterruptedException
e
)
{
//If we are interrupted, treat as a cancel
return
true
;
}
}
}
public
void
run
()
{
final
FormatHolder
formatHolder
=
getFormatHolder
();
@NonNull
final
DecoderInputBuffer
decoderInputBuffer
=
new
DecoderInputBuffer
(
DecoderInputBuffer
.
BUFFER_REPLACEMENT_MODE_NORMAL
);
long
start
=
SystemClock
.
uptimeMillis
();
main:
while
(
running
)
{
decoderInputBuffer
.
clear
();
final
int
result
=
readSource
(
formatHolder
,
decoderInputBuffer
,
formatHolder
.
format
==
null
?
SampleStream
.
FLAG_REQUIRE_FORMAT
:
0
);
if
(
result
==
C
.
RESULT_BUFFER_READ
)
{
if
(
decoderInputBuffer
.
isEndOfStream
())
{
ended
=
true
;
if
(!
sleep
())
{
ended
=
false
;
}
continue
;
}
final
long
leadUs
=
decoderInputBuffer
.
timeUs
-
currentTimeUs
;
//If we are more than 1/2 a frame behind, skip the next frame
if
(
leadUs
<
-
frameUs
/
2
)
{
eventDispatcher
.
droppedFrames
(
1
,
SystemClock
.
uptimeMillis
()
-
start
);
start
=
SystemClock
.
uptimeMillis
();
continue
;
}
start
=
SystemClock
.
uptimeMillis
();
@Nullable
final
Bitmap
bitmap
=
decodeInputBuffer
(
decoderInputBuffer
);
if
(
bitmap
==
null
)
{
continue
;
}
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