@@ -99,7 +99,8 @@ static final class InstanceBudget {
9999 static synchronized boolean tryAcquire (String mime ) {
100100 int u = used .containsKey (mime ) ? used .get (mime ) : 0 ;
101101 Integer max = limit .get (mime );
102- if (max != null && u >= max ) return false ;
102+ // Until the first decoder of a type has told its limit, start one at a time
103+ if (max == null ? u > 0 : u >= max ) return false ;
103104 used .put (mime , u + 1 );
104105 return true ;
105106 }
@@ -131,18 +132,28 @@ static synchronized void reset() {
131132 */
132133 static volatile long declaredBlocksPerSec ;
133134
134- /** Give the instance slot back; called wherever the decoder is detached (decoderLock held). */
135- private void releaseSlot () {
136- if (heldMime != null ) {
137- InstanceBudget .release (heldMime );
138- heldMime = null ;
139- }
135+ /**
136+ * Detach the instance slot from this manager (decoderLock held). The caller gives it back
137+ * with {@link InstanceBudget#release} only after the codec itself is released, so another
138+ * cell cannot start a decoder while the device still holds the maximum.
139+ */
140+ private String takeSlot () {
141+ String m = heldMime ;
142+ heldMime = null ;
143+ return m ;
144+ }
145+
146+ private static void releaseSlot (String mime ) {
147+ if (mime != null ) InstanceBudget .release (mime );
140148 }
141149
142150 /** Learn the codec's instance limit and log its declared throughput, once per type. */
143151 private static void learnLimits (MediaCodec codec , String mime ) {
144- if (Build .VERSION .SDK_INT < Build .VERSION_CODES .M ) return ;
145152 if (InstanceBudget .limit (mime ) >= 0 ) return ;
153+ if (Build .VERSION .SDK_INT < Build .VERSION_CODES .M ) {
154+ InstanceBudget .learn (mime , Integer .MAX_VALUE ); // not declared before Android 6
155+ return ;
156+ }
146157 try {
147158 MediaCodecInfo .CodecCapabilities caps = codec .getCodecInfo ().getCapabilitiesForType (mime );
148159 int max = caps .getMaxSupportedInstances ();
@@ -160,6 +171,7 @@ private static void learnLimits(MediaCodec codec, String mime) {
160171 Log .i (TAG , "Decoder " + codec .getName () + " (" + mime + "): up to " + max
161172 + " instances, declared 1080p frame rate in total " + fps1080 );
162173 } catch (Exception e ) {
174+ InstanceBudget .learn (mime , Integer .MAX_VALUE );
163175 Log .w (TAG , "Decoder limits unknown: " + e .getMessage ());
164176 }
165177 }
@@ -187,13 +199,15 @@ void setEnabled(boolean enabled) {
187199 final MediaCodec target = decoder ;
188200 if (target == null ) return ;
189201 new Thread (() -> {
202+ String slot ;
190203 synchronized (decoderLock ) {
191204 if (decoder != target ) return ; // already replaced or released
192205 decoder = null ;
193- releaseSlot ();
206+ slot = takeSlot ();
194207 retry .onSuccess ();
195208 }
196209 releaseNow (target );
210+ releaseSlot (slot );
197211 }, "decoder-release" ).start ();
198212 }
199213
@@ -215,14 +229,15 @@ void closeDecoder() {
215229 lastFrameMs = 0 ;
216230 if (codec == null ) return ;
217231 decoder = null ;
218- releaseSlot ();
232+ String slot = takeSlot ();
219233 Log .i (TAG , "Close video decoder" );
220234 try {
221235 codec .stop ();
222236 codec .release ();
223237 } catch (Exception e ) {
224238 Log .e (TAG , "Decoder close exception" , e );
225239 }
240+ releaseSlot (slot );
226241 }
227242 }
228243
@@ -254,6 +269,8 @@ void createDecoder(boolean codecH265) {
254269 synchronized (decoderLock ) {
255270 retry .onFailure (nowMs ());
256271 }
272+ // Limit still unknown: another cell is creating the first decoder, just retry
273+ if (limit < 0 ) return ;
257274 Log .w (TAG , "Decoder limit reached: " + limit + " × " + type );
258275 if (listener != null ) listener .onDecoderLimit (type , limit );
259276 return ;
@@ -280,6 +297,7 @@ void createDecoder(boolean codecH265) {
280297 retry .onFailure (nowMs ());
281298 }
282299 Log .e (TAG , "Cannot setup decoder (attempt " + retry .failures () + "): " + e .getMessage ());
300+ if (listener != null ) listener .onDecoderFailed ();
283301 return ;
284302 }
285303
@@ -359,12 +377,13 @@ boolean decodeFrame(Frame buffer, boolean codecH265, long watchdogRef) {
359377 drainOutput (codec );
360378 } catch (Exception e ) {
361379 decoder = null ;
362- releaseSlot ();
380+ String slot = takeSlot ();
363381 retry .onFailure (nowMs ());
364382 Log .e (TAG , "Codec exception, retry in "
365383 + DecoderRetry .delayMs (retry .failures ()) + " ms: " + e .getMessage ());
366384 try { codec .stop (); } catch (Exception ignored ) {}
367385 try { codec .release (); } catch (Exception ignored ) {}
386+ releaseSlot (slot );
368387 if (listener != null ) listener .onDecoderFailed ();
369388 }
370389 }
0 commit comments