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
3183183d
authored
Aug 30, 2021
by
apodob
Committed by
kim-vde
Sep 02, 2021
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Add ExoplayerCuesDecoder that decodes text/x-exoplayer-cues
PiperOrigin-RevId: 393723394
parent
75a69082
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
342 additions
and
0 deletions
library/core/src/main/java/com/google/android/exoplayer2/text/ExoplayerCuesDecoder.java
library/core/src/test/java/com/google/android/exoplayer2/text/ExoplayerCuesDecoderTest.java
library/core/src/main/java/com/google/android/exoplayer2/text/ExoplayerCuesDecoder.java
0 → 100644
View file @
3183183d
/*
* Copyright 2021 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
.
exoplayer2
.
text
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkArgument
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkNotNull
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkState
;
import
androidx.annotation.IntDef
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.common.collect.ImmutableList
;
import
java.lang.annotation.Retention
;
import
java.lang.annotation.RetentionPolicy
;
import
java.util.ArrayDeque
;
import
java.util.Deque
;
import
java.util.List
;
/**
* A {@link SubtitleDecoder} that decodes subtitle samples of type {@link
* MimeTypes#TEXT_EXOPLAYER_CUES}
*/
public
final
class
ExoplayerCuesDecoder
implements
SubtitleDecoder
{
@IntDef
(
value
=
{
INPUT_BUFFER_AVAILABLE
,
INPUT_BUFFER_DEQUEUED
,
INPUT_BUFFER_QUEUED
})
@Retention
(
RetentionPolicy
.
SOURCE
)
private
@interface
InputBufferState
{}
private
static
final
int
INPUT_BUFFER_AVAILABLE
=
0
;
private
static
final
int
INPUT_BUFFER_DEQUEUED
=
1
;
private
static
final
int
INPUT_BUFFER_QUEUED
=
2
;
private
static
final
int
OUTPUT_BUFFERS_COUNT
=
2
;
private
final
CueDecoder
cueDecoder
;
private
final
SubtitleInputBuffer
inputBuffer
;
private
final
Deque
<
SubtitleOutputBuffer
>
availableOutputBuffers
;
@InputBufferState
private
int
inputBufferState
;
private
boolean
released
;
public
ExoplayerCuesDecoder
()
{
cueDecoder
=
new
CueDecoder
();
inputBuffer
=
new
SubtitleInputBuffer
();
availableOutputBuffers
=
new
ArrayDeque
<>();
for
(
int
i
=
0
;
i
<
OUTPUT_BUFFERS_COUNT
;
i
++)
{
availableOutputBuffers
.
addFirst
(
new
SimpleSubtitleOutputBuffer
(
this
::
releaseOutputBuffer
));
}
inputBufferState
=
INPUT_BUFFER_AVAILABLE
;
}
@Override
public
String
getName
()
{
return
"ExoplayerCuesDecoder"
;
}
@Nullable
@Override
public
SubtitleInputBuffer
dequeueInputBuffer
()
throws
SubtitleDecoderException
{
checkState
(!
released
);
if
(
inputBufferState
!=
INPUT_BUFFER_AVAILABLE
)
{
return
null
;
}
inputBufferState
=
INPUT_BUFFER_DEQUEUED
;
return
inputBuffer
;
}
@Override
public
void
queueInputBuffer
(
SubtitleInputBuffer
inputBuffer
)
throws
SubtitleDecoderException
{
checkState
(!
released
);
checkState
(
inputBufferState
==
INPUT_BUFFER_DEQUEUED
);
checkArgument
(
this
.
inputBuffer
==
inputBuffer
);
inputBufferState
=
INPUT_BUFFER_QUEUED
;
}
@Nullable
@Override
public
SubtitleOutputBuffer
dequeueOutputBuffer
()
throws
SubtitleDecoderException
{
checkState
(!
released
);
if
(
inputBufferState
!=
INPUT_BUFFER_QUEUED
||
availableOutputBuffers
.
isEmpty
())
{
return
null
;
}
SingleEventSubtitle
subtitle
=
new
SingleEventSubtitle
(
inputBuffer
.
timeUs
,
cueDecoder
.
decode
(
checkNotNull
(
inputBuffer
.
data
).
array
()));
SubtitleOutputBuffer
outputBuffer
=
availableOutputBuffers
.
removeFirst
();
outputBuffer
.
setContent
(
inputBuffer
.
timeUs
,
subtitle
,
/* subsampleOffsetUs=*/
0
);
inputBuffer
.
clear
();
inputBufferState
=
INPUT_BUFFER_AVAILABLE
;
return
outputBuffer
;
}
@Override
public
void
flush
()
{
checkState
(!
released
);
inputBuffer
.
clear
();
inputBufferState
=
INPUT_BUFFER_AVAILABLE
;
}
@Override
public
void
release
()
{
released
=
true
;
}
@Override
public
void
setPositionUs
(
long
positionUs
)
{
// Do nothing
}
private
void
releaseOutputBuffer
(
SubtitleOutputBuffer
outputBuffer
)
{
checkState
(
availableOutputBuffers
.
size
()
<
OUTPUT_BUFFERS_COUNT
);
checkArgument
(!
availableOutputBuffers
.
contains
(
outputBuffer
));
outputBuffer
.
clear
();
availableOutputBuffers
.
addFirst
(
outputBuffer
);
}
private
static
final
class
SingleEventSubtitle
implements
Subtitle
{
private
final
long
timeUs
;
private
final
ImmutableList
<
Cue
>
cues
;
public
SingleEventSubtitle
(
long
timeUs
,
ImmutableList
<
Cue
>
cues
)
{
this
.
timeUs
=
timeUs
;
this
.
cues
=
cues
;
}
@Override
public
int
getNextEventTimeIndex
(
long
timeUs
)
{
return
this
.
timeUs
>
timeUs
?
0
:
C
.
INDEX_UNSET
;
}
@Override
public
int
getEventTimeCount
()
{
return
1
;
}
@Override
public
long
getEventTime
(
int
index
)
{
checkArgument
(
index
==
0
);
return
timeUs
;
}
@Override
public
List
<
Cue
>
getCues
(
long
timeUs
)
{
return
(
timeUs
>=
this
.
timeUs
)
?
cues
:
ImmutableList
.
of
();
}
}
}
library/core/src/test/java/com/google/android/exoplayer2/text/ExoplayerCuesDecoderTest.java
0 → 100644
View file @
3183183d
/*
* Copyright 2021 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
.
exoplayer2
.
text
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
static
org
.
junit
.
Assert
.
assertThrows
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
com.google.common.collect.ImmutableList
;
import
java.util.ArrayList
;
import
java.util.List
;
import
org.junit.After
;
import
org.junit.Before
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
/** Test for {@link ExoplayerCuesDecoder} */
@RunWith
(
AndroidJUnit4
.
class
)
public
class
ExoplayerCuesDecoderTest
{
private
ExoplayerCuesDecoder
decoder
;
private
static
final
byte
[]
ENCODED_CUES
=
new
CueEncoder
().
encode
(
ImmutableList
.
of
(
new
Cue
.
Builder
().
setText
(
"text"
).
build
()));
@Before
public
void
setUp
()
{
decoder
=
new
ExoplayerCuesDecoder
();
}
@After
public
void
tearDown
()
{
decoder
.
release
();
}
@Test
public
void
decoder_outputsSubtitle
()
throws
Exception
{
SubtitleInputBuffer
inputBuffer
=
decoder
.
dequeueInputBuffer
();
writeDataToInputBuffer
(
inputBuffer
,
/* timeUs=*/
1000
,
ENCODED_CUES
);
decoder
.
queueInputBuffer
(
inputBuffer
);
SubtitleOutputBuffer
outputBuffer
=
decoder
.
dequeueOutputBuffer
();
assertThat
(
outputBuffer
.
getCues
(
/* timeUs=*/
999
)).
isEmpty
();
assertThat
(
outputBuffer
.
getCues
(
1001
)).
hasSize
(
1
);
assertThat
(
outputBuffer
.
getCues
(
/* timeUs=*/
1000
)).
hasSize
(
1
);
assertThat
(
outputBuffer
.
getCues
(
/* timeUs=*/
1000
).
get
(
0
).
text
.
toString
()).
isEqualTo
(
"text"
);
outputBuffer
.
release
();
}
@Test
public
void
dequeueOutputBuffer_returnsNullWhenInputBufferIsNotQueued
()
throws
Exception
{
// Returns null before input buffer has been dequeued
assertThat
(
decoder
.
dequeueOutputBuffer
()).
isNull
();
SubtitleInputBuffer
inputBuffer
=
decoder
.
dequeueInputBuffer
();
// Returns null before input has been queued
assertThat
(
decoder
.
dequeueOutputBuffer
()).
isNull
();
writeDataToInputBuffer
(
inputBuffer
,
/* timeUs=*/
1000
,
ENCODED_CUES
);
decoder
.
queueInputBuffer
(
inputBuffer
);
// Returns buffer when the input buffer is queued and output buffer is available
assertThat
(
decoder
.
dequeueOutputBuffer
()).
isNotNull
();
// Returns null before next input buffer is queued
assertThat
(
decoder
.
dequeueOutputBuffer
()).
isNull
();
}
@Test
public
void
dequeueOutputBuffer_releasedOutputAndQueuedNextInput_returnsOutputBuffer
()
throws
Exception
{
SubtitleInputBuffer
inputBuffer
=
decoder
.
dequeueInputBuffer
();
writeDataToInputBuffer
(
inputBuffer
,
/* timeUs=*/
1000
,
ENCODED_CUES
);
decoder
.
queueInputBuffer
(
inputBuffer
);
SubtitleOutputBuffer
outputBuffer
=
decoder
.
dequeueOutputBuffer
();
exhaustAllOutputBuffers
(
decoder
);
assertThat
(
decoder
.
dequeueOutputBuffer
()).
isNull
();
outputBuffer
.
release
();
assertThat
(
decoder
.
dequeueOutputBuffer
()).
isNotNull
();
}
@Test
public
void
dequeueInputBuffer_withQueuedInput_returnsNull
()
throws
Exception
{
SubtitleInputBuffer
inputBuffer
=
decoder
.
dequeueInputBuffer
();
writeDataToInputBuffer
(
inputBuffer
,
/* timeUs=*/
1000
,
ENCODED_CUES
);
decoder
.
queueInputBuffer
(
inputBuffer
);
assertThat
(
decoder
.
dequeueInputBuffer
()).
isNull
();
}
@Test
public
void
queueInputBuffer_queueingInputBufferThatDoesNotComeFromDecoder_fails
()
{
assertThrows
(
IllegalStateException
.
class
,
()
->
decoder
.
queueInputBuffer
(
new
SubtitleInputBuffer
()));
}
@Test
public
void
queueInputBuffer_calledTwice_fails
()
throws
Exception
{
SubtitleInputBuffer
inputBuffer
=
decoder
.
dequeueInputBuffer
();
decoder
.
queueInputBuffer
(
inputBuffer
);
assertThrows
(
IllegalStateException
.
class
,
()
->
decoder
.
queueInputBuffer
(
inputBuffer
));
}
@Test
public
void
releaseOutputBuffer_calledTwice_fails
()
throws
Exception
{
SubtitleInputBuffer
inputBuffer
=
decoder
.
dequeueInputBuffer
();
writeDataToInputBuffer
(
inputBuffer
,
/* timeUs=*/
1000
,
ENCODED_CUES
);
decoder
.
queueInputBuffer
(
inputBuffer
);
SubtitleOutputBuffer
outputBuffer
=
decoder
.
dequeueOutputBuffer
();
outputBuffer
.
release
();
assertThrows
(
IllegalStateException
.
class
,
outputBuffer:
:
release
);
}
@Test
public
void
flush_doesNotInfluenceOutputBufferAvailability
()
throws
Exception
{
SubtitleInputBuffer
inputBuffer
=
decoder
.
dequeueInputBuffer
();
writeDataToInputBuffer
(
inputBuffer
,
/* timeUs=*/
1000
,
ENCODED_CUES
);
decoder
.
queueInputBuffer
(
inputBuffer
);
SubtitleOutputBuffer
outputBuffer
=
decoder
.
dequeueOutputBuffer
();
assertThat
(
outputBuffer
).
isNotNull
();
exhaustAllOutputBuffers
(
decoder
);
decoder
.
flush
();
inputBuffer
=
decoder
.
dequeueInputBuffer
();
writeDataToInputBuffer
(
inputBuffer
,
/* timeUs=*/
1000
,
ENCODED_CUES
);
assertThat
(
decoder
.
dequeueOutputBuffer
()).
isNull
();
}
@Test
public
void
flush_makesAllInputBuffersAvailable
()
throws
Exception
{
List
<
SubtitleInputBuffer
>
inputBuffers
=
new
ArrayList
<>();
SubtitleInputBuffer
inputBuffer
=
decoder
.
dequeueInputBuffer
();
while
(
inputBuffer
!=
null
)
{
inputBuffers
.
add
(
inputBuffer
);
inputBuffer
=
decoder
.
dequeueInputBuffer
();
}
for
(
int
i
=
0
;
i
<
inputBuffers
.
size
();
i
++)
{
writeDataToInputBuffer
(
inputBuffers
.
get
(
i
),
/* timeUs=*/
1000
,
ENCODED_CUES
);
decoder
.
queueInputBuffer
(
inputBuffers
.
get
(
i
));
}
decoder
.
flush
();
for
(
int
i
=
0
;
i
<
inputBuffers
.
size
();
i
++)
{
assertThat
(
decoder
.
dequeueInputBuffer
().
data
.
position
()).
isEqualTo
(
0
);
}
}
private
void
exhaustAllOutputBuffers
(
ExoplayerCuesDecoder
decoder
)
throws
SubtitleDecoderException
{
SubtitleInputBuffer
inputBuffer
;
do
{
inputBuffer
=
decoder
.
dequeueInputBuffer
();
if
(
inputBuffer
!=
null
)
{
writeDataToInputBuffer
(
inputBuffer
,
/* timeUs=*/
1000
,
ENCODED_CUES
);
decoder
.
queueInputBuffer
(
inputBuffer
);
}
}
while
(
decoder
.
dequeueOutputBuffer
()
!=
null
);
}
private
void
writeDataToInputBuffer
(
SubtitleInputBuffer
inputBuffer
,
long
timeUs
,
byte
[]
data
)
{
inputBuffer
.
timeUs
=
timeUs
;
inputBuffer
.
ensureSpaceForWrite
(
data
.
length
);
inputBuffer
.
data
.
put
(
data
);
}
}
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