Skip to content

Commit 7b9308e

Browse files
Merge pull request #2794 from androidx:media_codec_param
PiperOrigin-RevId: 831470310
2 parents 79c6523 + a11c8cb commit 7b9308e

18 files changed

+1372
-3
lines changed

RELEASENOTES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
* Common Library:
66
* ExoPlayer:
7+
* Add API for setting and observing `MediaCodec` parameters dynamically
8+
([#2794](https://github.com/androidx/media/pull/2794)).
79
* CompositionPlayer:
810
* Transformer:
911
* Track Selection:
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
/*
2+
* Copyright 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package androidx.media3.exoplayer;
17+
18+
import android.media.MediaFormat;
19+
import android.os.Bundle;
20+
import androidx.annotation.Nullable;
21+
import androidx.annotation.RequiresApi;
22+
import androidx.annotation.RestrictTo;
23+
import androidx.media3.common.util.NullableType;
24+
import androidx.media3.common.util.UnstableApi;
25+
import com.google.errorprone.annotations.CanIgnoreReturnValue;
26+
import java.nio.ByteBuffer;
27+
import java.util.Collections;
28+
import java.util.HashMap;
29+
import java.util.Map;
30+
import java.util.Set;
31+
32+
/** An immutable collection of parameters to be applied to a codec. */
33+
@UnstableApi
34+
public final class CodecParameters {
35+
36+
/** An empty {@link CodecParameters} instance. */
37+
public static final CodecParameters EMPTY = new CodecParameters.Builder().build();
38+
39+
private final Map<String, @NullableType Object> params;
40+
41+
private CodecParameters(Map<String, @NullableType Object> params) {
42+
this.params = Collections.unmodifiableMap(params);
43+
}
44+
45+
/** Returns a new {@link Builder} initialized with the values of this instance. */
46+
public Builder buildUpon() {
47+
return new Builder(this);
48+
}
49+
50+
/** A builder for {@link CodecParameters} instances. */
51+
public static final class Builder {
52+
private final Map<String, @NullableType Object> params;
53+
54+
/** Creates an empty builder. */
55+
public Builder() {
56+
params = new HashMap<>();
57+
}
58+
59+
private Builder(CodecParameters codecParameters) {
60+
this.params = new HashMap<>(codecParameters.params);
61+
}
62+
63+
/**
64+
* Sets an integer parameter value.
65+
*
66+
* @param key The parameter key.
67+
* @param value The integer value.
68+
* @return This builder.
69+
*/
70+
@CanIgnoreReturnValue
71+
public Builder setInteger(String key, int value) {
72+
params.put(key, value);
73+
return this;
74+
}
75+
76+
/**
77+
* Sets a long parameter value.
78+
*
79+
* @param key The parameter key.
80+
* @param value The long value.
81+
* @return This builder.
82+
*/
83+
@CanIgnoreReturnValue
84+
public Builder setLong(String key, long value) {
85+
params.put(key, value);
86+
return this;
87+
}
88+
89+
/**
90+
* Sets a float parameter value.
91+
*
92+
* @param key The parameter key.
93+
* @param value The float value.
94+
* @return This builder.
95+
*/
96+
@CanIgnoreReturnValue
97+
public Builder setFloat(String key, float value) {
98+
params.put(key, value);
99+
return this;
100+
}
101+
102+
/**
103+
* Sets a string parameter value.
104+
*
105+
* @param key The parameter key.
106+
* @param value The string value, which may be {@code null}.
107+
* @return This builder.
108+
*/
109+
@CanIgnoreReturnValue
110+
public Builder setString(String key, @Nullable String value) {
111+
params.put(key, value);
112+
return this;
113+
}
114+
115+
/**
116+
* Sets a byte buffer parameter value.
117+
*
118+
* @param key The parameter key.
119+
* @param value The {@link ByteBuffer} value, which may be {@code null}.
120+
* @return This builder.
121+
*/
122+
@CanIgnoreReturnValue
123+
public Builder setByteBuffer(String key, @Nullable ByteBuffer value) {
124+
if (value == null) {
125+
params.put(key, null);
126+
} else {
127+
ByteBuffer clone = ByteBuffer.allocate(value.remaining());
128+
clone.put(value.duplicate());
129+
clone.flip();
130+
params.put(key, clone);
131+
}
132+
return this;
133+
}
134+
135+
/**
136+
* Removes a parameter from this builder, preventing it from being applied.
137+
*
138+
* <p>Note: This does not reset a parameter on a live codec. To do so, explicitly set the
139+
* parameter to its default value.
140+
*
141+
* @param key The key of the parameter to remove.
142+
* @return This builder.
143+
*/
144+
@CanIgnoreReturnValue
145+
public Builder remove(String key) {
146+
params.remove(key);
147+
return this;
148+
}
149+
150+
/** Builds the {@link CodecParameters} instance. */
151+
public CodecParameters build() {
152+
return new CodecParameters(params);
153+
}
154+
}
155+
156+
/**
157+
* Creates a new {@link Builder} initialized with parameters from the provided {@link
158+
* MediaFormat}.
159+
*
160+
* @param mediaFormat The {@link MediaFormat} to extract parameters from.
161+
* @param keys The set of keys to include.
162+
* @return A new {@link Builder} instance.
163+
*/
164+
@RequiresApi(29)
165+
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
166+
public static Builder createFrom(MediaFormat mediaFormat, Set<String> keys) {
167+
Builder builder = new Builder();
168+
for (String key : keys) {
169+
if (mediaFormat.containsKey(key)) {
170+
int type = mediaFormat.getValueTypeForKey(key);
171+
switch (type) {
172+
case MediaFormat.TYPE_INTEGER:
173+
builder.setInteger(key, mediaFormat.getInteger(key));
174+
break;
175+
case MediaFormat.TYPE_LONG:
176+
builder.setLong(key, mediaFormat.getLong(key));
177+
break;
178+
case MediaFormat.TYPE_FLOAT:
179+
builder.setFloat(key, mediaFormat.getFloat(key));
180+
break;
181+
case MediaFormat.TYPE_STRING:
182+
builder.setString(key, mediaFormat.getString(key));
183+
break;
184+
case MediaFormat.TYPE_BYTE_BUFFER:
185+
builder.setByteBuffer(key, mediaFormat.getByteBuffer(key));
186+
break;
187+
default:
188+
break;
189+
}
190+
}
191+
}
192+
return builder;
193+
}
194+
195+
/**
196+
* Retrieves a parameter value by its key.
197+
*
198+
* <p><b>Note:</b> If the returned value is a {@link ByteBuffer}, it must be treated as read-only.
199+
* Modifying the returned {@link ByteBuffer} will affect this {@link CodecParameters} instance and
200+
* may lead to unexpected behavior.
201+
*
202+
* @param key A string representing the key of the codec parameter.
203+
* @return The value of the requested parameter, or {@code null} if the key is not present.
204+
*/
205+
@Nullable
206+
public Object get(String key) {
207+
return params.get(key);
208+
}
209+
210+
/** Returns a {@link Set} of the keys contained in this instance. */
211+
public Set<String> keySet() {
212+
return params.keySet();
213+
}
214+
215+
@Override
216+
public boolean equals(@Nullable Object obj) {
217+
if (this == obj) {
218+
return true;
219+
}
220+
if (!(obj instanceof CodecParameters)) {
221+
return false;
222+
}
223+
CodecParameters other = (CodecParameters) obj;
224+
return this.params.equals(other.params);
225+
}
226+
227+
@Override
228+
public int hashCode() {
229+
return params.hashCode();
230+
}
231+
232+
/** Converts the parameters from this instance to a {@link Bundle}. */
233+
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
234+
public Bundle toBundle() {
235+
Bundle bundle = new Bundle();
236+
for (Map.Entry<String, @NullableType Object> entry : params.entrySet()) {
237+
String key = entry.getKey();
238+
Object value = entry.getValue();
239+
if (value == null) {
240+
continue;
241+
}
242+
if (value instanceof Integer) {
243+
bundle.putInt(key, (Integer) value);
244+
} else if (value instanceof Long) {
245+
bundle.putLong(key, (Long) value);
246+
} else if (value instanceof Float) {
247+
bundle.putFloat(key, (Float) value);
248+
} else if (value instanceof String) {
249+
bundle.putString(key, (String) value);
250+
} else if (value instanceof ByteBuffer) {
251+
ByteBuffer byteBuffer = (ByteBuffer) value;
252+
byte[] bytes = new byte[byteBuffer.remaining()];
253+
byteBuffer.duplicate().get(bytes);
254+
bundle.putByteArray(key, bytes);
255+
}
256+
}
257+
return bundle;
258+
}
259+
260+
/** Applies the parameters from this instance to the given {@link MediaFormat}. */
261+
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
262+
public void applyTo(MediaFormat mediaFormat) {
263+
for (Map.Entry<String, @NullableType Object> entry : params.entrySet()) {
264+
String key = entry.getKey();
265+
Object value = entry.getValue();
266+
if (value == null) {
267+
mediaFormat.setString(key, null);
268+
continue;
269+
}
270+
if (value instanceof Integer) {
271+
mediaFormat.setInteger(key, (Integer) value);
272+
} else if (value instanceof Long) {
273+
mediaFormat.setLong(key, (Long) value);
274+
} else if (value instanceof Float) {
275+
mediaFormat.setFloat(key, (Float) value);
276+
} else if (value instanceof String) {
277+
mediaFormat.setString(key, (String) value);
278+
} else if (value instanceof ByteBuffer) {
279+
mediaFormat.setByteBuffer(key, (ByteBuffer) value);
280+
}
281+
}
282+
}
283+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package androidx.media3.exoplayer;
17+
18+
import androidx.media3.common.util.UnstableApi;
19+
20+
/** A listener for changes to codec parameters. */
21+
@UnstableApi
22+
public interface CodecParametersChangeListener {
23+
24+
/**
25+
* Called when one or more of the parameters this listener was registered for have changed.
26+
*
27+
* <p>The provided {@link CodecParameters} object represents the new state and contains entries
28+
* for the keys this listener is subscribed to, but only if they are currently reported by the
29+
* codec.
30+
*
31+
* @param codecParameters A {@link CodecParameters} instance containing the current values for the
32+
* keys this listener is subscribed to.
33+
*/
34+
void onCodecParametersChanged(CodecParameters codecParameters);
35+
}

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayer.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2025,4 +2025,46 @@ void setVideoChangeFrameRateStrategy(
20252025
*/
20262026
@UnstableApi
20272027
void setImageOutput(@Nullable ImageOutput imageOutput);
2028+
2029+
/**
2030+
* Sets a collection of parameters on the underlying audio codecs.
2031+
*
2032+
* <p>This method is asynchronous. The parameters will be applied to the audio renderers on the
2033+
* playback thread.
2034+
*
2035+
* <p>The default {@link MediaCodec} based renderers only support this feature on API level 29 and
2036+
* above. If an underlying decoder does not support a parameter, it will be ignored.
2037+
*
2038+
* @param codecParameters The {@link CodecParameters} to set.
2039+
*/
2040+
@UnstableApi
2041+
void setAudioCodecParameters(CodecParameters codecParameters);
2042+
2043+
/**
2044+
* Adds a listener for audio codec parameter changes.
2045+
*
2046+
* <p>The listener will be called on the application thread. Upon registration, the listener will
2047+
* be immediately called with the last known values for the subscribed keys.
2048+
*
2049+
* <p>The default {@link MediaCodec} based renderers only support this feature on API level 29 and
2050+
* above.
2051+
*
2052+
* <p><b>Note:</b> When used with {@link MediaCodec}, observing vendor-specific parameter changes
2053+
* requires API level 31 or higher. On API levels 29 and 30, any requested vendor-specific keys
2054+
* will be ignored.
2055+
*
2056+
* @param listener The {@link CodecParametersChangeListener} to add.
2057+
* @param keys The list of parameter keys to subscribe to.
2058+
*/
2059+
@UnstableApi
2060+
void addAudioCodecParametersChangeListener(
2061+
CodecParametersChangeListener listener, List<String> keys);
2062+
2063+
/**
2064+
* Removes a listener for audio codec parameter changes.
2065+
*
2066+
* @param listener The {@link CodecParametersChangeListener} to remove.
2067+
*/
2068+
@UnstableApi
2069+
void removeAudioCodecParametersChangeListener(CodecParametersChangeListener listener);
20282070
}

0 commit comments

Comments
 (0)