16
16
package androidx .media3 .container ;
17
17
18
18
import static androidx .annotation .RestrictTo .Scope .LIBRARY_GROUP ;
19
+ import static androidx .media3 .common .util .Assertions .checkArgument ;
19
20
import static androidx .media3 .common .util .Assertions .checkState ;
20
21
import static androidx .media3 .common .util .Util .castNonNull ;
21
22
23
+ import androidx .annotation .Nullable ;
22
24
import androidx .annotation .RestrictTo ;
23
25
import androidx .media3 .common .C ;
24
26
import androidx .media3 .common .util .ParsableByteArray ;
25
27
import androidx .media3 .common .util .UnstableApi ;
26
28
import java .util .ArrayDeque ;
27
- import java .util .Deque ;
29
+ import java .util .ArrayList ;
30
+ import java .util .List ;
28
31
import java .util .PriorityQueue ;
29
- import java .util .concurrent .atomic .AtomicLong ;
30
32
31
33
/** A queue of SEI messages, ordered by presentation timestamp. */
32
34
@ UnstableApi
@@ -40,18 +42,17 @@ public interface SeiConsumer {
40
42
}
41
43
42
44
private final SeiConsumer seiConsumer ;
43
- private final AtomicLong tieBreakGenerator = new AtomicLong ();
44
45
45
- /**
46
- * Pool of re-usable {@link SeiMessage} objects to avoid repeated allocations. Elements should be
47
- * added and removed from the 'tail' of the queue (with {@link Deque#push(Object)} and {@link
48
- * Deque#pop()}), to avoid unnecessary array copying.
49
- */
50
- private final ArrayDeque <SeiMessage > unusedSeiMessages ;
46
+ /** Pool of re-usable {@link ParsableByteArray} objects to avoid repeated allocations. */
47
+ private final ArrayDeque <ParsableByteArray > unusedParsableByteArrays ;
51
48
52
- private final PriorityQueue <SeiMessage > pendingSeiMessages ;
49
+ /** Pool of re-usable {@link SampleSeiMessages} objects to avoid repeated allocations. */
50
+ private final ArrayDeque <SampleSeiMessages > unusedSampleSeiMessages ;
51
+
52
+ private final PriorityQueue <SampleSeiMessages > pendingSeiMessages ;
53
53
54
54
private int reorderingQueueSize ;
55
+ @ Nullable private SampleSeiMessages lastQueuedMessage ;
55
56
56
57
/**
57
58
* Creates an instance, initially with no max size.
@@ -62,16 +63,24 @@ public interface SeiConsumer {
62
63
*/
63
64
public ReorderingSeiMessageQueue (SeiConsumer seiConsumer ) {
64
65
this .seiConsumer = seiConsumer ;
65
- unusedSeiMessages = new ArrayDeque <>();
66
+ unusedParsableByteArrays = new ArrayDeque <>();
67
+ unusedSampleSeiMessages = new ArrayDeque <>();
66
68
pendingSeiMessages = new PriorityQueue <>();
67
69
reorderingQueueSize = C .LENGTH_UNSET ;
68
70
}
69
71
70
72
/**
71
73
* Sets the max size of the re-ordering queue.
72
74
*
75
+ * <p>The size is defined in terms of the number of unique presentation timestamps, rather than
76
+ * the number of messages. This ensures that properties like H.264's {@code
77
+ * max_number_reorder_frames} can be used to set this max size in the case of multiple SEI
78
+ * messages per sample (where multiple SEI messages therefore have the same presentation
79
+ * timestamp).
80
+ *
73
81
* <p>When the queue exceeds this size during a call to {@link #add(long, ParsableByteArray)}, the
74
- * least message is passed to the {@link SeiConsumer} provided during construction.
82
+ * messages associated with the least timestamp are passed to the {@link SeiConsumer} provided
83
+ * during construction.
75
84
*
76
85
* <p>If the new size is larger than the number of elements currently in the queue, items are
77
86
* removed from the head of the queue (least first) and passed to the {@link SeiConsumer} provided
@@ -86,7 +95,7 @@ public void setMaxSize(int reorderingQueueSize) {
86
95
/**
87
96
* Returns the maximum size of this queue, or {@link C#LENGTH_UNSET} if it is unbounded.
88
97
*
89
- * <p>See {@link #setMaxSize(int)}.
98
+ * <p>See {@link #setMaxSize(int)} for details on how size is defined .
90
99
*/
91
100
public int getMaxSize () {
92
101
return reorderingQueueSize ;
@@ -95,12 +104,16 @@ public int getMaxSize() {
95
104
/**
96
105
* Adds a message to the queue.
97
106
*
98
- * <p>If this causes the queue to exceed its {@linkplain #setMaxSize(int) max size}, the least
99
- * message (which may be the one passed to this method) is passed to the {@link SeiConsumer}
100
- * provided during construction.
107
+ * <p>If this causes the queue to exceed its {@linkplain #setMaxSize(int) max size}, messages
108
+ * associated with the least timestamp (which may be the message passed to this method) are passed
109
+ * to the {@link SeiConsumer} provided during construction.
110
+ *
111
+ * <p>Messages with matching timestamps must be added consecutively (this will naturally happen
112
+ * when parsing messages from a container).
101
113
*
102
114
* @param presentationTimeUs The presentation time of the SEI message.
103
- * @param seiBuffer The SEI data. The data will be copied, so the provided object can be re-used.
115
+ * @param seiBuffer The SEI data. The data will be copied, so the provided object can be re-used
116
+ * after this method returns.
104
117
*/
105
118
public void add (long presentationTimeUs , ParsableByteArray seiBuffer ) {
106
119
if (reorderingQueueSize == 0
@@ -110,15 +123,42 @@ && presentationTimeUs < castNonNull(pendingSeiMessages.peek()).presentationTimeU
110
123
seiConsumer .consume (presentationTimeUs , seiBuffer );
111
124
return ;
112
125
}
113
- SeiMessage seiMessage =
114
- unusedSeiMessages .isEmpty () ? new SeiMessage () : unusedSeiMessages .poll ();
115
- seiMessage .reset (presentationTimeUs , tieBreakGenerator .getAndIncrement (), seiBuffer );
116
- pendingSeiMessages .add (seiMessage );
126
+ // Make a local copy of the SEI data so we can store it in the queue and allow the seiBuffer
127
+ // parameter to be safely re-used after this add() method returns.
128
+ ParsableByteArray seiBufferCopy = copy (seiBuffer );
129
+ if (lastQueuedMessage != null && presentationTimeUs == lastQueuedMessage .presentationTimeUs ) {
130
+ lastQueuedMessage .nalBuffers .add (seiBufferCopy );
131
+ return ;
132
+ }
133
+ SampleSeiMessages sampleSeiMessages =
134
+ unusedSampleSeiMessages .isEmpty () ? new SampleSeiMessages () : unusedSampleSeiMessages .pop ();
135
+ sampleSeiMessages .init (presentationTimeUs , seiBufferCopy );
136
+ pendingSeiMessages .add (sampleSeiMessages );
137
+ lastQueuedMessage = sampleSeiMessages ;
117
138
if (reorderingQueueSize != C .LENGTH_UNSET ) {
118
139
flushQueueDownToSize (reorderingQueueSize );
119
140
}
120
141
}
121
142
143
+ /**
144
+ * Copies {@code input} into a {@link ParsableByteArray} instance from {@link
145
+ * #unusedParsableByteArrays}, or a new instance if that is empty.
146
+ */
147
+ private ParsableByteArray copy (ParsableByteArray input ) {
148
+ ParsableByteArray result =
149
+ unusedParsableByteArrays .isEmpty ()
150
+ ? new ParsableByteArray ()
151
+ : unusedParsableByteArrays .pop ();
152
+ result .reset (input .bytesLeft ());
153
+ System .arraycopy (
154
+ /* src= */ input .getData (),
155
+ /* srcPos= */ input .getPosition (),
156
+ /* dest= */ result .getData (),
157
+ /* destPos= */ 0 ,
158
+ /* length= */ result .bytesLeft ());
159
+ return result ;
160
+ }
161
+
122
162
/**
123
163
* Empties the queue, passing all messages (least first) to the {@link SeiConsumer} provided
124
164
* during construction.
@@ -129,47 +169,42 @@ public void flush() {
129
169
130
170
private void flushQueueDownToSize (int targetSize ) {
131
171
while (pendingSeiMessages .size () > targetSize ) {
132
- SeiMessage seiMessage = castNonNull (pendingSeiMessages .poll ());
133
- seiConsumer .consume (seiMessage .presentationTimeUs , seiMessage .data );
134
- unusedSeiMessages .push (seiMessage );
172
+ SampleSeiMessages sampleSeiMessages = castNonNull (pendingSeiMessages .poll ());
173
+ for (int i = 0 ; i < sampleSeiMessages .nalBuffers .size (); i ++) {
174
+ seiConsumer .consume (
175
+ sampleSeiMessages .presentationTimeUs , sampleSeiMessages .nalBuffers .get (i ));
176
+ unusedParsableByteArrays .push (sampleSeiMessages .nalBuffers .get (i ));
177
+ }
178
+ sampleSeiMessages .nalBuffers .clear ();
179
+ if (lastQueuedMessage != null
180
+ && lastQueuedMessage .presentationTimeUs == sampleSeiMessages .presentationTimeUs ) {
181
+ lastQueuedMessage = null ;
182
+ }
183
+ unusedSampleSeiMessages .push (sampleSeiMessages );
135
184
}
136
185
}
137
186
138
- /** Holds data from a SEI sample with its presentation timestamp. */
139
- private static final class SeiMessage implements Comparable <SeiMessage > {
140
-
141
- private final ParsableByteArray data ;
142
-
143
- private long presentationTimeUs ;
187
+ /** Holds the presentation timestamp of a sample and the data from associated SEI messages. */
188
+ private static final class SampleSeiMessages implements Comparable <SampleSeiMessages > {
144
189
145
- /**
146
- * {@link PriorityQueue} breaks ties arbitrarily. This field ensures that insertion order is
147
- * preserved when messages have the same {@link #presentationTimeUs}.
148
- */
149
- private long tieBreak ;
190
+ public final List <ParsableByteArray > nalBuffers ;
191
+ public long presentationTimeUs ;
150
192
151
- public SeiMessage () {
193
+ public SampleSeiMessages () {
152
194
presentationTimeUs = C .TIME_UNSET ;
153
- data = new ParsableByteArray ();
195
+ nalBuffers = new ArrayList <> ();
154
196
}
155
197
156
- public void reset (long presentationTimeUs , long tieBreak , ParsableByteArray nalBuffer ) {
157
- checkState (presentationTimeUs != C .TIME_UNSET );
198
+ public void init (long presentationTimeUs , ParsableByteArray nalBuffer ) {
199
+ checkArgument (presentationTimeUs != C .TIME_UNSET );
200
+ checkState (this .nalBuffers .isEmpty ());
158
201
this .presentationTimeUs = presentationTimeUs ;
159
- this .tieBreak = tieBreak ;
160
- this .data .reset (nalBuffer .bytesLeft ());
161
- System .arraycopy (
162
- /* src= */ nalBuffer .getData (),
163
- /* srcPos= */ nalBuffer .getPosition (),
164
- /* dest= */ data .getData (),
165
- /* destPos= */ 0 ,
166
- /* length= */ nalBuffer .bytesLeft ());
202
+ this .nalBuffers .add (nalBuffer );
167
203
}
168
204
169
205
@ Override
170
- public int compareTo (SeiMessage other ) {
171
- int timeComparison = Long .compare (this .presentationTimeUs , other .presentationTimeUs );
172
- return timeComparison != 0 ? timeComparison : Long .compare (this .tieBreak , other .tieBreak );
206
+ public int compareTo (SampleSeiMessages other ) {
207
+ return Long .compare (this .presentationTimeUs , other .presentationTimeUs );
173
208
}
174
209
}
175
210
}
0 commit comments