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
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
474 additions
and
79 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 {
testInstrumentationRunnerArguments
clearPackageData:
'true'
multiDexEnabled
true
}
testOptions
{
unitTests
.
all
{
jvmArgs
'-noverify'
}
}
buildTypes
{
debug
{
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;
import
android.graphics.Bitmap
;
import
android.graphics.BitmapFactory
;
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
;
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.C
;
import
com.google.android.exoplayer2.ExoPlaybackException
;
...
...
@@ -23,11 +25,16 @@ import com.google.android.exoplayer2.util.MimeTypes;
import
java.nio.ByteBuffer
;
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
final
Rect
rect
=
new
Rect
();
private
final
Point
lastSurface
=
new
Point
();
private
final
RenderRunnable
renderRunnable
=
new
RenderRunnable
();
final
VideoRendererEventListener
.
EventDispatcher
eventDispatcher
;
...
...
@@ -60,7 +67,16 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
throws
ExoPlaybackException
{
decoderCounters
=
new
DecoderCounters
();
eventDispatcher
.
enabled
(
decoderCounters
);
thread
.
start
();
if
(
mayRenderStartOfStream
)
{
thread
.
start
();
}
}
@Override
protected
void
onStarted
()
throws
ExoPlaybackException
{
if
(
thread
.
getState
()
==
Thread
.
State
.
NEW
)
{
thread
.
start
();
}
}
@Override
...
...
@@ -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
public
void
render
(
long
positionUs
,
long
elapsedRealtimeUs
)
throws
ExoPlaybackException
{
//Log.d(TAG, "Render: us=" + positionUs);
synchronized
(
eventDispatcher
)
{
synchronized
(
renderRunnable
)
{
currentTimeUs
=
positionUs
;
eventDispatcher
.
notify
();
renderRunnable
.
notify
();
}
}
...
...
@@ -127,7 +135,17 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
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
final
Surface
surface
=
this
.
surface
;
if
(
surface
==
null
)
{
...
...
@@ -136,30 +154,7 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
//Log.d(TAG, "Drawing: " + bitmap.getWidth() + "x" + bitmap.getHeight());
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
);
renderBitmap
(
bitmap
,
canvas
);
surface
.
unlockCanvasAndPost
(
canvas
);
@Nullable
...
...
@@ -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
=
new
DecoderInputBuffer
(
DecoderInputBuffer
.
BUFFER_REPLACEMENT_MODE_NORMAL
);
private
volatile
boolean
running
=
true
;
@VisibleForTesting
Function
<
String
,
Boolean
>
sleepFunction
=
this
;
void
stop
()
{
running
=
false
;
thread
.
interrupt
();
...
...
@@ -197,7 +207,7 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
bitmap
=
BitmapFactory
.
decodeByteArray
(
byteBuffer
.
array
(),
byteBuffer
.
arrayOffset
(),
byteBuffer
.
arrayOffset
()
+
byteBuffer
.
position
());
if
(
bitmap
==
null
)
{
eventDispatcher
.
videoCodecError
(
new
NullPointerException
(
"Decode bytes failed"
)
);
throw
new
NullPointerException
(
"Decode bytes failed"
);
}
else
{
return
bitmap
;
}
...
...
@@ -212,18 +222,21 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
*
* @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
synchronized
Boolean
apply
(
String
why
)
{
try
{
wait
();
return
false
;
}
catch
(
InterruptedException
e
)
{
//If we are interrupted, treat as a cancel
return
true
;
}
}
private
boolean
sleep
(
String
why
)
{
return
sleepFunction
.
apply
(
why
);
}
@WorkerThread
public
void
run
()
{
final
FormatHolder
formatHolder
=
getFormatHolder
();
long
start
=
SystemClock
.
uptimeMillis
();
...
...
@@ -232,40 +245,73 @@ public class BitmapFactoryVideoRenderer extends BaseRenderer {
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
())
{
//Wait for shutdown or stream to be changed
sleep
();
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
);
switch
(
result
)
{
case
C
.
RESULT_BUFFER_READ
:
{
if
(
decoderInputBuffer
.
isEndOfStream
())
{
//Wait for shutdown or stream to be changed
sleep
(
STREAM_END
);
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
();
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
())
{
//Sleep was interrupted, discard Bitmap
continue
main
;
@Nullable
final
Bitmap
bitmap
=
decodeInputBuffer
(
decoderInputBuffer
);
if
(
bitmap
==
null
)
{
continue
;
}
while
(
currentTimeUs
<
decoderInputBuffer
.
timeUs
)
{
//Log.d(TAG, "Sleep: us=" + currentTimeUs);
if
(
sleep
(
RENDER_WAIT
))
{
//Sleep was interrupted, discard Bitmap
continue
main
;
}
}
if
(
running
)
{
renderBitmap
(
bitmap
);
}
}
if
(
running
)
{
renderBitmap
(
bitmap
);
}
}
else
if
(
result
==
C
.
RESULT_FORMAT_READ
)
{
break
;
case
C
.
RESULT_FORMAT_READ
:
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
package
com
.
google
.
android
.
exoplayer2
.
video
;
import
static
com
.
google
.
android
.
exoplayer2
.
testutil
.
FakeSampleStream
.
FakeSampleStreamItem
.
END_OF_STREAM_ITEM
;
import
static
com
.
google
.
android
.
exoplayer2
.
testutil
.
FakeSampleStream
.
FakeSampleStreamItem
.
oneByteSample
;
import
static
com
.
google
.
android
.
exoplayer2
.
testutil
.
FakeSampleStream
.
FakeSampleStreamItem
.
sample
;
import
android.content.Context
;
import
android.graphics.Bitmap
;
import
android.graphics.Canvas
;
import
android.graphics.Rect
;
import
android.os.Handler
;
import
android.os.Looper
;
import
android.view.Surface
;
import
androidx.arch.core.util.Function
;
import
androidx.test.core.app.ApplicationProvider
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.ExoPlaybackException
;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.PlaybackException
;
import
com.google.android.exoplayer2.Renderer
;
import
com.google.android.exoplayer2.RendererConfiguration
;
import
com.google.android.exoplayer2.drm.DrmSessionEventListener
;
import
com.google.android.exoplayer2.drm.DrmSessionManager
;
import
com.google.android.exoplayer2.testutil.FakeSampleStream
;
import
com.google.android.exoplayer2.testutil.TestUtil
;
import
com.google.android.exoplayer2.upstream.DefaultAllocator
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.common.collect.ImmutableList
;
import
java.io.IOException
;
import
org.junit.After
;
import
org.junit.Assert
;
import
org.junit.Before
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.robolectric.annotation.Config
;
import
org.robolectric.shadow.api.Shadow
;
import
org.robolectric.shadows.ShadowBitmapFactory
;
import
org.robolectric.shadows.ShadowLooper
;
@RunWith
(
AndroidJUnit4
.
class
)
@Config
(
shadows
=
{
ShadowSurfaceExtended
.
class
})
public
class
BitmapFactoryVideoRendererTest
{
private
final
static
Format
FORMAT_MJPEG
=
new
Format
.
Builder
().
setSampleMimeType
(
MimeTypes
.
VIDEO_MJPEG
).
setWidth
(
320
).
setHeight
(
240
).
setFrameRate
(
15
f
).
build
();
FakeEventListener
fakeEventListener
=
new
FakeEventListener
();
BitmapFactoryVideoRenderer
bitmapFactoryVideoRenderer
;
@Before
public
void
before
()
{
fakeEventListener
=
new
FakeEventListener
();
final
Handler
handler
=
new
Handler
(
Looper
.
getMainLooper
());
bitmapFactoryVideoRenderer
=
new
BitmapFactoryVideoRenderer
(
handler
,
fakeEventListener
);
}
@After
public
void
after
()
{
//Kill the Thread
bitmapFactoryVideoRenderer
.
onDisabled
();
}
@Test
public
void
getName
()
{
Assert
.
assertEquals
(
BitmapFactoryVideoRenderer
.
TAG
,
bitmapFactoryVideoRenderer
.
getName
());
}
@Test
public
void
onEnabled_givenMayRenderStartOfStream
()
throws
PlaybackException
{
bitmapFactoryVideoRenderer
.
onEnabled
(
false
,
true
);
ShadowLooper
.
idleMainLooper
();
Assert
.
assertNotNull
(
bitmapFactoryVideoRenderer
.
getDecoderCounters
());
Assert
.
assertEquals
(
Thread
.
State
.
RUNNABLE
,
bitmapFactoryVideoRenderer
.
getThread
().
getState
());
Assert
.
assertTrue
(
fakeEventListener
.
isVideoEnabled
());
}
@Test
public
void
onStarted_givenThreadNotStarted
()
throws
PlaybackException
{
bitmapFactoryVideoRenderer
.
onStarted
();
ShadowLooper
.
idleMainLooper
();
Assert
.
assertEquals
(
Thread
.
State
.
RUNNABLE
,
bitmapFactoryVideoRenderer
.
getThread
().
getState
());
}
@Test
public
void
onDisabled_givenOnEnabled
()
throws
PlaybackException
,
InterruptedException
{
onEnabled_givenMayRenderStartOfStream
();
bitmapFactoryVideoRenderer
.
onDisabled
();
ShadowLooper
.
idleMainLooper
();
Assert
.
assertFalse
(
fakeEventListener
.
isVideoEnabled
());
//Ensure Thread is shutdown
bitmapFactoryVideoRenderer
.
getThread
().
join
(
500L
);
Assert
.
assertTrue
(
bitmapFactoryVideoRenderer
.
isEnded
());
}
private
FakeSampleStream
getSampleStream
()
throws
IOException
{
final
Context
context
=
ApplicationProvider
.
getApplicationContext
();
final
byte
[]
bytes
=
TestUtil
.
getByteArray
(
context
,
"media/jpeg/image-320-240.jpg"
);
FakeSampleStream
fakeSampleStream
=
new
FakeSampleStream
(
new
DefaultAllocator
(
/* trimOnReset= */
true
,
/* individualAllocationSize= */
1024
),
/* mediaSourceEventDispatcher= */
null
,
DrmSessionManager
.
DRM_UNSUPPORTED
,
new
DrmSessionEventListener
.
EventDispatcher
(),
/* initialFormat= */
FORMAT_MJPEG
,
ImmutableList
.
of
(
sample
(
0L
,
C
.
BUFFER_FLAG_KEY_FRAME
,
bytes
),
END_OF_STREAM_ITEM
));
return
fakeSampleStream
;
}
private
Surface
setSurface
()
throws
ExoPlaybackException
{
final
Surface
surface
=
ShadowSurfaceExtended
.
newInstance
();
final
ShadowSurfaceExtended
shadowSurfaceExtended
=
Shadow
.
extract
(
surface
);
shadowSurfaceExtended
.
setSize
(
1080
,
1920
);
bitmapFactoryVideoRenderer
.
handleMessage
(
Renderer
.
MSG_SET_VIDEO_OUTPUT
,
surface
);
return
surface
;
}
@Test
public
void
handleMessage_givenSurface
()
throws
ExoPlaybackException
{
final
Surface
surface
=
setSurface
();
Assert
.
assertSame
(
surface
,
bitmapFactoryVideoRenderer
.
getSurface
());
bitmapFactoryVideoRenderer
.
handleMessage
(
Renderer
.
MSG_SET_VIDEO_OUTPUT
,
null
);
Assert
.
assertNull
(
bitmapFactoryVideoRenderer
.
getSurface
());
}
@Test
public
void
isReady_givenSurface
()
throws
ExoPlaybackException
{
Assert
.
assertFalse
(
bitmapFactoryVideoRenderer
.
isReady
());
setSurface
();
Assert
.
assertTrue
(
bitmapFactoryVideoRenderer
.
isReady
());
}
@Test
public
void
render_givenJpegAndSurface
()
throws
IOException
,
ExoPlaybackException
{
final
Surface
surface
=
setSurface
();
final
ShadowSurfaceExtended
shadowSurfaceExtended
=
Shadow
.
extract
(
surface
);
FakeSampleStream
fakeSampleStream
=
getSampleStream
();
fakeSampleStream
.
writeData
(
0L
);
bitmapFactoryVideoRenderer
.
enable
(
RendererConfiguration
.
DEFAULT
,
new
Format
[]{
FORMAT_MJPEG
},
fakeSampleStream
,
0L
,
false
,
true
,
0L
,
0L
);
bitmapFactoryVideoRenderer
.
render
(
0L
,
0L
);
// This test actually decodes the JPEG (very cool!),
// May need to bump up timers for slow machines
Assert
.
assertTrue
(
shadowSurfaceExtended
.
waitForPost
(
500L
));
}
@Test
public
void
supportsFormat_givenMjpegFormat
()
throws
ExoPlaybackException
{
Assert
.
assertEquals
(
C
.
FORMAT_HANDLED
,
bitmapFactoryVideoRenderer
.
supportsFormat
(
FORMAT_MJPEG
)
&
C
.
FORMAT_HANDLED
);
}
@Test
public
void
supportsFormat_givenMp4vFormat
()
throws
ExoPlaybackException
{
final
Format
format
=
new
Format
.
Builder
().
setSampleMimeType
(
MimeTypes
.
VIDEO_MP4V
).
build
();
Assert
.
assertEquals
(
0
,
bitmapFactoryVideoRenderer
.
supportsFormat
(
format
)
&
C
.
FORMAT_HANDLED
);
}
@Test
public
void
renderBitmap_given4by3BitmapAnd16by9Canvas
()
{
final
Bitmap
bitmap
=
Bitmap
.
createBitmap
(
FORMAT_MJPEG
.
width
,
FORMAT_MJPEG
.
height
,
Bitmap
.
Config
.
ARGB_8888
);
final
Bitmap
canvasBitmap
=
Bitmap
.
createBitmap
(
1080
,
1920
,
Bitmap
.
Config
.
ARGB_8888
);
final
Canvas
canvas
=
new
Canvas
(
canvasBitmap
);
bitmapFactoryVideoRenderer
.
renderBitmap
(
bitmap
,
canvas
);
ShadowLooper
.
idleMainLooper
();
final
Rect
rect
=
bitmapFactoryVideoRenderer
.
getRect
();
Assert
.
assertEquals
(
canvas
.
getWidth
(),
rect
.
width
());
Assert
.
assertEquals
(
canvas
.
getHeight
(),
rect
.
height
());
final
VideoSize
videoSize
=
fakeEventListener
.
videoSize
;
Assert
.
assertEquals
(
bitmap
.
getWidth
(),
videoSize
.
width
);
bitmapFactoryVideoRenderer
.
renderBitmap
(
bitmap
,
canvas
);
ShadowLooper
.
idleMainLooper
();
Assert
.
assertSame
(
videoSize
,
fakeEventListener
.
videoSize
);
}
@Test
public
void
RenderRunnable_run_givenLateFrame
()
throws
IOException
,
ExoPlaybackException
{
final
Function
<
String
,
Boolean
>
sleep
=
why
->
{
throw
new
RuntimeException
(
why
);};
FakeSampleStream
fakeSampleStream
=
getSampleStream
();
fakeSampleStream
.
writeData
(
0L
);
//Don't enable so the Thread is not running
bitmapFactoryVideoRenderer
.
replaceStream
(
new
Format
[]{
FORMAT_MJPEG
},
fakeSampleStream
,
0L
,
0L
);
BitmapFactoryVideoRenderer
.
RenderRunnable
renderRunnable
=
bitmapFactoryVideoRenderer
.
getRenderRunnable
();
renderRunnable
.
sleepFunction
=
sleep
;
bitmapFactoryVideoRenderer
.
render
(
1_000_000L
,
0L
);
try
{
renderRunnable
.
run
();
}
catch
(
RuntimeException
e
)
{
Assert
.
assertEquals
(
BitmapFactoryVideoRenderer
.
STREAM_EMPTY
,
e
.
getMessage
());
}
ShadowLooper
.
idleMainLooper
();
Assert
.
assertEquals
(
1
,
fakeEventListener
.
getDroppedFrames
());
}
@Test
public
void
RenderRunnable_run_givenBadJpeg
()
throws
IOException
,
ExoPlaybackException
{
final
Function
<
String
,
Boolean
>
sleep
=
why
->
{
throw
new
RuntimeException
(
why
);};
FakeSampleStream
fakeSampleStream
=
new
FakeSampleStream
(
new
DefaultAllocator
(
/* trimOnReset= */
true
,
/* individualAllocationSize= */
1024
),
/* mediaSourceEventDispatcher= */
null
,
DrmSessionManager
.
DRM_UNSUPPORTED
,
new
DrmSessionEventListener
.
EventDispatcher
(),
/* initialFormat= */
FORMAT_MJPEG
,
ImmutableList
.
of
(
oneByteSample
(
0L
,
C
.
BUFFER_FLAG_KEY_FRAME
),
END_OF_STREAM_ITEM
));
fakeSampleStream
.
writeData
(
0L
);
//Don't enable so the Thread is not running
bitmapFactoryVideoRenderer
.
replaceStream
(
new
Format
[]{
FORMAT_MJPEG
},
fakeSampleStream
,
0L
,
0L
);
BitmapFactoryVideoRenderer
.
RenderRunnable
renderRunnable
=
bitmapFactoryVideoRenderer
.
getRenderRunnable
();
renderRunnable
.
sleepFunction
=
sleep
;
bitmapFactoryVideoRenderer
.
render
(
0L
,
0L
);
// There is a bug in Robolectric where it doesn't handle null images,
// so we won't get our Exception
ShadowBitmapFactory
.
setAllowInvalidImageData
(
false
);
try
{
renderRunnable
.
run
();
}
catch
(
RuntimeException
e
)
{
Assert
.
assertEquals
(
BitmapFactoryVideoRenderer
.
STREAM_EMPTY
,
e
.
getMessage
());
}
ShadowLooper
.
idleMainLooper
();
Assert
.
assertTrue
(
fakeEventListener
.
getVideoCodecError
()
instanceof
NullPointerException
);
}
}
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;
* A {@link SubtitleView.Output} that uses Android's native layout framework via {@link
* SubtitlePainter}.
*/
/* package */
final
class
CanvasSubtitleOutput
extends
View
implements
SubtitleView
.
Output
{
/* package */
final
class
CanvasSubtitleOutput
extends
View
implements
SubtitleView
.
Output
{
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