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
bec8b44b
authored
Jan 30, 2022
by
Dustin
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
BitmapFactoryVideoRenderer Tests
parent
df9e51de
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
444 additions
and
49 deletions
library/core/build.gradle
library/core/src/main/java/com/google/android/exoplayer2/video/BitmapFactoryVideoRenderer.java
library/core/src/test/java/com/google/android/exoplayer2/video/BitmapFactoryVideoRendererTest.java
library/core/src/test/java/com/google/android/exoplayer2/video/FakeEventListener.java
library/core/src/test/java/com/google/android/exoplayer2/video/ShadowSurfaceExtended.java
library/ui/src/main/java/com/google/android/exoplayer2/ui/CanvasSubtitleOutput.java
testdata/src/test/assets/media/jpeg/image-320-240.jpg
library/core/build.gradle
View file @
bec8b44b
...
@@ -21,7 +21,11 @@ android {
...
@@ -21,7 +21,11 @@ android {
testInstrumentationRunnerArguments
clearPackageData:
'true'
testInstrumentationRunnerArguments
clearPackageData:
'true'
multiDexEnabled
true
multiDexEnabled
true
}
}
testOptions
{
unitTests
.
all
{
jvmArgs
'-noverify'
}
}
buildTypes
{
buildTypes
{
debug
{
debug
{
testCoverageEnabled
=
true
testCoverageEnabled
=
true
...
...
library/core/src/main/java/com/google/android/exoplayer2/video/BitmapFactoryVideoRenderer.java
View file @
bec8b44b
...
@@ -3,13 +3,15 @@ package com.google.android.exoplayer2.video;
...
@@ -3,13 +3,15 @@ package com.google.android.exoplayer2.video;
import
android.graphics.Bitmap
;
import
android.graphics.Bitmap
;
import
android.graphics.BitmapFactory
;
import
android.graphics.BitmapFactory
;
import
android.graphics.Canvas
;
import
android.graphics.Canvas
;
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.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
;
import
androidx.annotation.VisibleForTesting
;
import
androidx.annotation.WorkerThread
;
import
androidx.arch.core.util.Function
;
import
com.google.android.exoplayer2.BaseRenderer
;
import
com.google.android.exoplayer2.BaseRenderer
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.ExoPlaybackException
;
import
com.google.android.exoplayer2.ExoPlaybackException
;
...
@@ -23,11 +25,16 @@ import com.google.android.exoplayer2.util.MimeTypes;
...
@@ -23,11 +25,16 @@ import com.google.android.exoplayer2.util.MimeTypes;
import
java.nio.ByteBuffer
;
import
java.nio.ByteBuffer
;
public
class
BitmapFactoryVideoRenderer
extends
BaseRenderer
{
public
class
BitmapFactoryVideoRenderer
extends
BaseRenderer
{
private
static
final
String
TAG
=
"BitmapFactoryRenderer"
;
static
final
String
TAG
=
"BitmapFactoryRenderer"
;
//Sleep Reasons
static
final
String
STREAM_END
=
"Stream End"
;
static
final
String
STREAM_EMPTY
=
"Stream Empty"
;
static
final
String
RENDER_WAIT
=
"Render Wait"
;
private
static
int
threadId
;
private
static
int
threadId
;
private
final
Rect
rect
=
new
Rect
();
private
final
Rect
rect
=
new
Rect
();
private
final
Point
lastSurface
=
new
Point
();
private
final
RenderRunnable
renderRunnable
=
new
RenderRunnable
();
private
final
RenderRunnable
renderRunnable
=
new
RenderRunnable
();
final
VideoRendererEventListener
.
EventDispatcher
eventDispatcher
;
final
VideoRendererEventListener
.
EventDispatcher
eventDispatcher
;
...
@@ -60,8 +67,17 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
...
@@ -60,8 +67,17 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
throws
ExoPlaybackException
{
throws
ExoPlaybackException
{
decoderCounters
=
new
DecoderCounters
();
decoderCounters
=
new
DecoderCounters
();
eventDispatcher
.
enabled
(
decoderCounters
);
eventDispatcher
.
enabled
(
decoderCounters
);
if
(
mayRenderStartOfStream
)
{
thread
.
start
();
thread
.
start
();
}
}
}
@Override
protected
void
onStarted
()
throws
ExoPlaybackException
{
if
(
thread
.
getState
()
==
Thread
.
State
.
NEW
)
{
thread
.
start
();
}
}
@Override
@Override
protected
void
onDisabled
()
{
protected
void
onDisabled
()
{
...
@@ -74,20 +90,12 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
...
@@ -74,20 +90,12 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
}
}
}
}
private
void
onFormatChanged
(
@NonNull
FormatHolder
formatHolder
)
{
@Nullable
final
Format
format
=
formatHolder
.
format
;
if
(
format
!=
null
)
{
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);
//Log.d(TAG, "Render: us=" + positionUs);
synchronized
(
eventDispatcher
)
{
synchronized
(
renderRunnable
)
{
currentTimeUs
=
positionUs
;
currentTimeUs
=
positionUs
;
eventDispatcher
.
notify
();
renderRunnable
.
notify
();
}
}
}
}
...
@@ -127,7 +135,17 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
...
@@ -127,7 +135,17 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
return
RendererCapabilities
.
create
(
C
.
FORMAT_UNSUPPORTED_TYPE
);
return
RendererCapabilities
.
create
(
C
.
FORMAT_UNSUPPORTED_TYPE
);
}
}
void
renderBitmap
(
final
Bitmap
bitmap
)
{
@WorkerThread
private
void
onFormatChanged
(
@NonNull
FormatHolder
formatHolder
)
{
@Nullable
final
Format
format
=
formatHolder
.
format
;
if
(
format
!=
null
)
{
frameUs
=
(
long
)(
1_000_000L
/
format
.
frameRate
);
eventDispatcher
.
inputFormatChanged
(
format
,
null
);
}
}
@WorkerThread
void
renderBitmap
(
@NonNull
final
Bitmap
bitmap
)
{
@Nullable
@Nullable
final
Surface
surface
=
this
.
surface
;
final
Surface
surface
=
this
.
surface
;
if
(
surface
==
null
)
{
if
(
surface
==
null
)
{
...
@@ -136,30 +154,7 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
...
@@ -136,30 +154,7 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
//Log.d(TAG, "Drawing: " + bitmap.getWidth() + "x" + bitmap.getHeight());
//Log.d(TAG, "Drawing: " + bitmap.getWidth() + "x" + bitmap.getHeight());
final
Canvas
canvas
=
surface
.
lockCanvas
(
null
);
final
Canvas
canvas
=
surface
.
lockCanvas
(
null
);
final
Rect
clipBounds
=
canvas
.
getClipBounds
();
renderBitmap
(
bitmap
,
canvas
);
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
);
surface
.
unlockCanvasAndPost
(
canvas
);
@Nullable
@Nullable
...
@@ -173,12 +168,27 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
...
@@ -173,12 +168,27 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
}
}
}
}
class
RenderRunnable
implements
Runnable
{
@WorkerThread
@VisibleForTesting
void
renderBitmap
(
Bitmap
bitmap
,
Canvas
canvas
)
{
final
VideoSize
videoSize
=
new
VideoSize
(
bitmap
.
getWidth
(),
bitmap
.
getHeight
());
if
(!
videoSize
.
equals
(
lastVideoSize
))
{
lastVideoSize
=
videoSize
;
eventDispatcher
.
videoSizeChanged
(
videoSize
);
}
rect
.
set
(
0
,
0
,
canvas
.
getWidth
(),
canvas
.
getHeight
());
canvas
.
drawBitmap
(
bitmap
,
null
,
rect
,
null
);
}
class
RenderRunnable
implements
Runnable
,
Function
<
String
,
Boolean
>
{
final
DecoderInputBuffer
decoderInputBuffer
=
final
DecoderInputBuffer
decoderInputBuffer
=
new
DecoderInputBuffer
(
DecoderInputBuffer
.
BUFFER_REPLACEMENT_MODE_NORMAL
);
new
DecoderInputBuffer
(
DecoderInputBuffer
.
BUFFER_REPLACEMENT_MODE_NORMAL
);
private
volatile
boolean
running
=
true
;
private
volatile
boolean
running
=
true
;
@VisibleForTesting
Function
<
String
,
Boolean
>
sleepFunction
=
this
;
void
stop
()
{
void
stop
()
{
running
=
false
;
running
=
false
;
thread
.
interrupt
();
thread
.
interrupt
();
...
@@ -197,7 +207,7 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
...
@@ -197,7 +207,7 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
bitmap
=
BitmapFactory
.
decodeByteArray
(
byteBuffer
.
array
(),
byteBuffer
.
arrayOffset
(),
bitmap
=
BitmapFactory
.
decodeByteArray
(
byteBuffer
.
array
(),
byteBuffer
.
arrayOffset
(),
byteBuffer
.
arrayOffset
()
+
byteBuffer
.
position
());
byteBuffer
.
arrayOffset
()
+
byteBuffer
.
position
());
if
(
bitmap
==
null
)
{
if
(
bitmap
==
null
)
{
eventDispatcher
.
videoCodecError
(
new
NullPointerException
(
"Decode bytes failed"
)
);
throw
new
NullPointerException
(
"Decode bytes failed"
);
}
else
{
}
else
{
return
bitmap
;
return
bitmap
;
}
}
...
@@ -212,18 +222,21 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
...
@@ -212,18 +222,21 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
*
*
* @return true if interrupted
* @return true if interrupted
*/
*/
private
boolean
sleep
()
{
public
synchronized
Boolean
apply
(
String
why
)
{
synchronized
(
eventDispatcher
)
{
try
{
try
{
eventDispatcher
.
wait
();
wait
();
return
false
;
return
false
;
}
catch
(
InterruptedException
e
)
{
}
catch
(
InterruptedException
e
)
{
//If we are interrupted, treat as a cancel
//If we are interrupted, treat as a cancel
return
true
;
return
true
;
}
}
}
}
private
boolean
sleep
(
String
why
)
{
return
sleepFunction
.
apply
(
why
);
}
}
@WorkerThread
public
void
run
()
{
public
void
run
()
{
final
FormatHolder
formatHolder
=
getFormatHolder
();
final
FormatHolder
formatHolder
=
getFormatHolder
();
long
start
=
SystemClock
.
uptimeMillis
();
long
start
=
SystemClock
.
uptimeMillis
();
...
@@ -232,10 +245,11 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
...
@@ -232,10 +245,11 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
decoderInputBuffer
.
clear
();
decoderInputBuffer
.
clear
();
final
int
result
=
readSource
(
formatHolder
,
decoderInputBuffer
,
final
int
result
=
readSource
(
formatHolder
,
decoderInputBuffer
,
formatHolder
.
format
==
null
?
SampleStream
.
FLAG_REQUIRE_FORMAT
:
0
);
formatHolder
.
format
==
null
?
SampleStream
.
FLAG_REQUIRE_FORMAT
:
0
);
if
(
result
==
C
.
RESULT_BUFFER_READ
)
{
switch
(
result
)
{
case
C
.
RESULT_BUFFER_READ
:
{
if
(
decoderInputBuffer
.
isEndOfStream
())
{
if
(
decoderInputBuffer
.
isEndOfStream
())
{
//Wait for shutdown or stream to be changed
//Wait for shutdown or stream to be changed
sleep
(
);
sleep
(
STREAM_END
);
continue
;
continue
;
}
}
final
long
leadUs
=
decoderInputBuffer
.
timeUs
-
currentTimeUs
;
final
long
leadUs
=
decoderInputBuffer
.
timeUs
-
currentTimeUs
;
...
@@ -254,7 +268,7 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
...
@@ -254,7 +268,7 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
}
}
while
(
currentTimeUs
<
decoderInputBuffer
.
timeUs
)
{
while
(
currentTimeUs
<
decoderInputBuffer
.
timeUs
)
{
//Log.d(TAG, "Sleep: us=" + currentTimeUs);
//Log.d(TAG, "Sleep: us=" + currentTimeUs);
if
(
sleep
(
))
{
if
(
sleep
(
RENDER_WAIT
))
{
//Sleep was interrupted, discard Bitmap
//Sleep was interrupted, discard Bitmap
continue
main
;
continue
main
;
}
}
...
@@ -262,10 +276,42 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
...
@@ -262,10 +276,42 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
if
(
running
)
{
if
(
running
)
{
renderBitmap
(
bitmap
);
renderBitmap
(
bitmap
);
}
}
}
else
if
(
result
==
C
.
RESULT_FORMAT_READ
)
{
}
break
;
case
C
.
RESULT_FORMAT_READ
:
onFormatChanged
(
formatHolder
);
onFormatChanged
(
formatHolder
);
break
;
case
C
.
RESULT_NOTHING_READ
:
sleep
(
STREAM_EMPTY
);
break
;
}
}
}
}
}
}
}
@VisibleForTesting
(
otherwise
=
VisibleForTesting
.
NONE
)
Rect
getRect
()
{
return
rect
;
}
@Nullable
@VisibleForTesting
DecoderCounters
getDecoderCounters
()
{
return
decoderCounters
;
}
@VisibleForTesting
(
otherwise
=
VisibleForTesting
.
NONE
)
Thread
getThread
()
{
return
thread
;
}
@Nullable
@VisibleForTesting
(
otherwise
=
VisibleForTesting
.
NONE
)
Surface
getSurface
()
{
return
surface
;
}
RenderRunnable
getRenderRunnable
()
{
return
renderRunnable
;
}
}
}
}
library/core/src/test/java/com/google/android/exoplayer2/video/BitmapFactoryVideoRendererTest.java
0 → 100644
View file @
bec8b44b
This diff is collapsed.
Click to expand it.
library/core/src/test/java/com/google/android/exoplayer2/video/FakeEventListener.java
0 → 100644
View file @
bec8b44b
package
com
.
google
.
android
.
exoplayer2
.
video
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.decoder.DecoderCounters
;
public
class
FakeEventListener
implements
VideoRendererEventListener
{
@Nullable
VideoSize
videoSize
;
@Nullable
DecoderCounters
decoderCounters
;
private
long
firstFrameRenderMs
=
Long
.
MIN_VALUE
;
private
int
droppedFrames
;
private
Exception
videoCodecError
;
@Override
public
void
onVideoSizeChanged
(
VideoSize
videoSize
)
{
this
.
videoSize
=
videoSize
;
}
public
boolean
isVideoEnabled
()
{
return
decoderCounters
!=
null
;
}
@Override
public
void
onVideoEnabled
(
DecoderCounters
counters
)
{
decoderCounters
=
counters
;
}
@Override
public
void
onVideoDisabled
(
DecoderCounters
counters
)
{
decoderCounters
=
null
;
}
public
long
getFirstFrameRenderMs
()
{
return
firstFrameRenderMs
;
}
@Override
public
void
onRenderedFirstFrame
(
Object
output
,
long
renderTimeMs
)
{
firstFrameRenderMs
=
renderTimeMs
;
}
public
int
getDroppedFrames
()
{
return
droppedFrames
;
}
@Override
public
void
onDroppedFrames
(
int
count
,
long
elapsedMs
)
{
droppedFrames
+=
count
;
}
public
Exception
getVideoCodecError
()
{
return
videoCodecError
;
}
@Override
public
void
onVideoCodecError
(
Exception
videoCodecError
)
{
this
.
videoCodecError
=
videoCodecError
;
}
}
library/core/src/test/java/com/google/android/exoplayer2/video/ShadowSurfaceExtended.java
0 → 100644
View file @
bec8b44b
package
com
.
google
.
android
.
exoplayer2
.
video
;
import
android.graphics.Bitmap
;
import
android.graphics.Canvas
;
import
android.graphics.Rect
;
import
android.view.Surface
;
import
java.util.concurrent.Semaphore
;
import
java.util.concurrent.TimeUnit
;
import
org.robolectric.annotation.Implements
;
import
org.robolectric.shadow.api.Shadow
;
import
org.robolectric.shadows.ShadowSurface
;
@Implements
(
Surface
.
class
)
public
class
ShadowSurfaceExtended
extends
ShadowSurface
{
private
final
Semaphore
postSemaphore
=
new
Semaphore
(
0
);
private
int
width
;
private
int
height
;
public
static
Surface
newInstance
()
{
return
Shadow
.
newInstanceOf
(
Surface
.
class
);
}
public
void
setSize
(
final
int
width
,
final
int
height
)
{
this
.
width
=
width
;
this
.
height
=
height
;
}
public
Canvas
lockCanvas
(
Rect
canvas
)
{
return
new
Canvas
(
Bitmap
.
createBitmap
(
width
,
height
,
Bitmap
.
Config
.
ARGB_8888
));
}
public
void
unlockCanvasAndPost
(
Canvas
canvas
)
{
postSemaphore
.
release
();
}
public
boolean
waitForPost
(
long
millis
)
{
try
{
return
postSemaphore
.
tryAcquire
(
millis
,
TimeUnit
.
MILLISECONDS
);
}
catch
(
InterruptedException
e
)
{
return
false
;
}
}
}
library/ui/src/main/java/com/google/android/exoplayer2/ui/CanvasSubtitleOutput.java
View file @
bec8b44b
...
@@ -32,7 +32,8 @@ import java.util.List;
...
@@ -32,7 +32,8 @@ import java.util.List;
* A {@link SubtitleView.Output} that uses Android's native layout framework via {@link
* A {@link SubtitleView.Output} that uses Android's native layout framework via {@link
* SubtitlePainter}.
* SubtitlePainter}.
*/
*/
/* package */
final
class
CanvasSubtitleOutput
extends
View
implements
SubtitleView
.
Output
{
/* package */
final
class
CanvasSubtitleOutput
extends
View
implements
SubtitleView
.
Output
{
private
final
List
<
SubtitlePainter
>
painters
;
private
final
List
<
SubtitlePainter
>
painters
;
...
...
testdata/src/test/assets/media/jpeg/image-320-240.jpg
0 → 100644
View file @
bec8b44b
35.2 KB
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