Skip to content

Enable multiple audio presentations parsing for AC-4 in MP4 #2512

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import static com.google.common.math.DoubleMath.fuzzyEquals;
import static java.lang.annotation.ElementType.TYPE_USE;

import android.media.AudioPresentation;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.IntDef;
Expand Down Expand Up @@ -116,6 +118,7 @@
* <li>{@link #pcmEncoding}
* <li>{@link #encoderDelay}
* <li>{@link #encoderPadding}
* <li>{@link #audioPresentations}
* </ul>
*
* <h2 id="text-formats">Fields relevant to text formats</h2>
Expand Down Expand Up @@ -193,7 +196,7 @@ public static final class Builder {
private @C.PcmEncoding int pcmEncoding;
private int encoderDelay;
private int encoderPadding;

private List<AudioPresentation> audioPresentations;
// Text specific.

private int accessibilityChannel;
Expand Down Expand Up @@ -239,6 +242,7 @@ public Builder() {
// Provided by the source.
cryptoType = C.CRYPTO_TYPE_NONE;
auxiliaryTrackType = C.AUXILIARY_TRACK_TYPE_UNDEFINED;
audioPresentations = ImmutableList.of();
}

/**
Expand Down Expand Up @@ -286,6 +290,7 @@ private Builder(Format format) {
this.pcmEncoding = format.pcmEncoding;
this.encoderDelay = format.encoderDelay;
this.encoderPadding = format.encoderPadding;
this.audioPresentations = format.audioPresentations;
// Text specific.
this.accessibilityChannel = format.accessibilityChannel;
this.cueReplacementBehavior = format.cueReplacementBehavior;
Expand Down Expand Up @@ -766,6 +771,18 @@ public Builder setEncoderPadding(int encoderPadding) {
return this;
}

/**
* Sets {@link AudioPresentation}. The default value is {@code null}.
*
* @param presentations The {@link Format#audioPresentations}.
* @return The builder.
*/
@CanIgnoreReturnValue
public Builder setAudioPresentations(List<AudioPresentation> presentations) {
this.audioPresentations = ImmutableList.copyOf(presentations);
return this;
}

// Text specific.

/**
Expand Down Expand Up @@ -1104,6 +1121,11 @@ public Format build() {
*/
@UnstableApi public final int encoderPadding;

/** The audio presentations. Will not be null, but may be empty if the container doesn't have
* audio presentations in it.
*/
public final List<AudioPresentation> audioPresentations;

// Text specific.

/** The Accessibility channel, or {@link #NO_VALUE} if not known or applicable. */
Expand Down Expand Up @@ -1209,6 +1231,7 @@ private Format(Builder builder) {
pcmEncoding = builder.pcmEncoding;
encoderDelay = builder.encoderDelay == NO_VALUE ? 0 : builder.encoderDelay;
encoderPadding = builder.encoderPadding == NO_VALUE ? 0 : builder.encoderPadding;
audioPresentations = builder.audioPresentations;
// Text specific.
accessibilityChannel = builder.accessibilityChannel;
cueReplacementBehavior = builder.cueReplacementBehavior;
Expand Down Expand Up @@ -1348,6 +1371,8 @@ public String toString() {
+ channelCount
+ ", "
+ sampleRate
+ ", "
+ audioPresentations
+ "])";
}

Expand Down Expand Up @@ -1394,6 +1419,7 @@ public int hashCode() {
result = 31 * result + pcmEncoding;
result = 31 * result + encoderDelay;
result = 31 * result + encoderPadding;
// [Omitted audioPresentations]
// Text specific.
result = 31 * result + accessibilityChannel;
// Image specific.
Expand Down Expand Up @@ -1447,6 +1473,7 @@ public boolean equals(@Nullable Object obj) {
&& Objects.equals(id, other.id)
&& Objects.equals(label, other.label)
&& labels.equals(other.labels)
&& audioPresentationsEquals(other)
&& Objects.equals(codecs, other.codecs)
&& Objects.equals(containerMimeType, other.containerMimeType)
&& Objects.equals(sampleMimeType, other.sampleMimeType)
Expand Down Expand Up @@ -1480,6 +1507,27 @@ public boolean initializationDataEquals(Format other) {
return true;
}

/**
* Returns whether the {@link #audioPresentations}s belonging to this format and {@code other} are
* equal.
*
* @param other The other format whose {@link #audioPresentations} is being compared.
* @return Whether the {@link #audioPresentations}s belonging to this format and {@code other} are
* equal.
*/
@UnstableApi
public boolean audioPresentationsEquals(Format other) {
if (audioPresentations.size() != other.audioPresentations.size()) {
return false;
}
for (int i = 0; i < audioPresentations.size(); i++) {
if (!audioPresentations.get(i).equals(other.audioPresentations.get(i))) {
return false;
}
}
return true;
}

// Utility methods

/** Returns a prettier {@link String} than {@link #toString()}, intended for logging. */
Expand Down Expand Up @@ -1618,6 +1666,7 @@ public static String toLogString(@Nullable Format format) {
private static final String FIELD_MAX_SUB_LAYERS = Util.intToStringMaxRadix(34);
private static final String FIELD_DECODED_WIDTH = Util.intToStringMaxRadix(35);
private static final String FIELD_DECODED_HEIGHT = Util.intToStringMaxRadix(36);
private static final String FIELD_AUDIO_PRESENTATIONS = Util.intToStringMaxRadix(37);

/**
* Returns a {@link Bundle} representing the information stored in this object. If {@code
Expand Down Expand Up @@ -1672,6 +1721,11 @@ public Bundle toBundle() {
bundle.putInt(FIELD_PCM_ENCODING, pcmEncoding);
bundle.putInt(FIELD_ENCODER_DELAY, encoderDelay);
bundle.putInt(FIELD_ENCODER_PADDING, encoderPadding);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
for (int i = 0; i < audioPresentations.size(); i++) {
bundle.putParcelable(keyForAudioPresentations(i), audioPresentations.get(i));
}
}
// Text specific.
bundle.putInt(FIELD_ACCESSIBILITY_CHANNEL, accessibilityChannel);
// Image specific.
Expand Down Expand Up @@ -1742,12 +1796,21 @@ public static Format fromBundle(Bundle bundle) {
builder.setColorInfo(ColorInfo.fromBundle(colorInfoBundle));
}
// Audio specific.
List<AudioPresentation> presentations = new ArrayList<>();
for (int i = 0; ; i++) {
@Nullable AudioPresentation data = bundle.getParcelable(keyForAudioPresentations(i));
if (data == null) {
break;
}
presentations.add(data);
}
builder
.setChannelCount(bundle.getInt(FIELD_CHANNEL_COUNT, DEFAULT.channelCount))
.setSampleRate(bundle.getInt(FIELD_SAMPLE_RATE, DEFAULT.sampleRate))
.setPcmEncoding(bundle.getInt(FIELD_PCM_ENCODING, DEFAULT.pcmEncoding))
.setEncoderDelay(bundle.getInt(FIELD_ENCODER_DELAY, DEFAULT.encoderDelay))
.setEncoderPadding(bundle.getInt(FIELD_ENCODER_PADDING, DEFAULT.encoderPadding))
.setAudioPresentations(presentations)
// Text specific.
.setAccessibilityChannel(
bundle.getInt(FIELD_ACCESSIBILITY_CHANNEL, DEFAULT.accessibilityChannel))
Expand All @@ -1767,6 +1830,12 @@ private static String keyForInitializationData(int initialisationDataIndex) {
+ Integer.toString(initialisationDataIndex, Character.MAX_RADIX);
}

private static String keyForAudioPresentations(int audioPresentationsIndex) {
return FIELD_AUDIO_PRESENTATIONS
+ "_"
+ Integer.toString(audioPresentationsIndex, Character.MAX_RADIX);
}

/**
* Utility method to get {@code defaultValue} if {@code value} is {@code null}. {@code
* defaultValue} can be {@code null}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;

import android.media.AudioPresentation;
import android.os.Bundle;
import android.os.Looper;
import android.view.Surface;
Expand All @@ -30,6 +31,7 @@
import androidx.annotation.FloatRange;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.text.Cue;
Expand Down Expand Up @@ -859,6 +861,14 @@ default void onMediaItemTransition(
*/
default void onTracksChanged(Tracks tracks) {}

/**
* Called when the audio presentations, available to the player for the current track, are
* changed.
*
* @param audioPresentations The available audio presentations. Never null, but may be empty.
*/
default void onAudioPresentationsChanged(@NonNull List<AudioPresentation> audioPresentations) {}

/**
* Called when the value of {@link Player#getMediaMetadata()} changes.
*
Expand Down Expand Up @@ -1539,7 +1549,8 @@ default void onMetadata(Metadata metadata) {}
EVENT_CUES,
EVENT_METADATA,
EVENT_DEVICE_INFO_CHANGED,
EVENT_DEVICE_VOLUME_CHANGED
EVENT_DEVICE_VOLUME_CHANGED,
EVENT_AUDIO_PRESENTATIONS_CHANGED
})
@interface Event {}

Expand Down Expand Up @@ -1642,6 +1653,9 @@ default void onMetadata(Metadata metadata) {}
/** {@link #getDeviceVolume()} or {@link #isDeviceMuted()} changed. */
int EVENT_DEVICE_VOLUME_CHANGED = 30;

/** Audio presentations associated with the current audio track changed. */
int EVENT_AUDIO_PRESENTATIONS_CHANGED = 31;

/**
* Commands that indicate which method calls are currently permitted on a particular {@code
* Player} instance.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.media.AudioDeviceInfo;
import android.media.AudioPresentation;
import android.media.MediaFormat;
import android.os.Handler;
import android.os.Looper;
Expand Down Expand Up @@ -153,6 +154,9 @@
private final TrackSelector trackSelector;
private final HandlerWrapper playbackInfoUpdateHandler;
private final ExoPlayerImplInternal.PlaybackInfoUpdateListener playbackInfoUpdateListener;
private final HandlerWrapper audioPresentationsUpdateHandler;
private final ExoPlayerImplInternal.AudioPresentationsChangeListener
audioPresentationsChangeListener;
private final ExoPlayerImplInternal internalPlayer;

private final ListenerSet<Listener> listeners;
Expand Down Expand Up @@ -352,6 +356,12 @@ public ExoPlayerImpl(ExoPlayer.Builder builder, @Nullable Player wrappingPlayer)
playbackInfoUpdate ->
playbackInfoUpdateHandler.post(() -> handlePlaybackInfo(playbackInfoUpdate));
playbackInfo = PlaybackInfo.createDummy(emptyTrackSelectorResult);
audioPresentationsUpdateHandler =
clock.createHandler(applicationLooper, /* callback= */ null);
audioPresentationsChangeListener =
audioPresentations ->
playbackInfoUpdateHandler.post(() ->
handleAudioPresentationsChange(audioPresentations));
analyticsCollector.setPlayer(this.wrappingPlayer, applicationLooper);
PlayerId playerId = new PlayerId(builder.playerName);
internalPlayer =
Expand All @@ -377,7 +387,8 @@ public ExoPlayerImpl(ExoPlayer.Builder builder, @Nullable Player wrappingPlayer)
playerId,
builder.playbackLooperProvider,
preloadConfiguration,
frameMetadataListener);
frameMetadataListener,
audioPresentationsChangeListener);
Looper playbackLooper = internalPlayer.getPlaybackLooper();

volume = 1;
Expand Down Expand Up @@ -463,6 +474,12 @@ public ExoPlayerImpl(ExoPlayer.Builder builder, @Nullable Player wrappingPlayer)
}
}

private void handleAudioPresentationsChange(List<AudioPresentation> audioPresentations) {
listeners.queueEvent(
Player.EVENT_AUDIO_PRESENTATIONS_CHANGED,
listener -> listener.onAudioPresentationsChanged(audioPresentations));
}

@Override
public boolean isSleepingForOffload() {
verifyApplicationThread();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import static java.lang.Math.min;

import android.content.Context;
import android.media.AudioPresentation;
import android.media.MediaFormat;
import android.os.Handler;
import android.os.Looper;
Expand Down Expand Up @@ -138,6 +139,10 @@ public interface PlaybackInfoUpdateListener {
void onPlaybackInfoUpdate(ExoPlayerImplInternal.PlaybackInfoUpdate playbackInfo);
}

public interface AudioPresentationsChangeListener {
void onAudioPresentationsChanged(List<AudioPresentation> audioPresentations);
}

// Internal messages
private static final int MSG_SET_PLAY_WHEN_READY = 1;
private static final int MSG_DO_SOME_WORK = 2;
Expand Down Expand Up @@ -176,6 +181,7 @@ public interface PlaybackInfoUpdateListener {
private static final int MSG_SET_SCRUBBING_MODE_ENABLED = 36;
private static final int MSG_SEEK_COMPLETED_IN_SCRUBBING_MODE = 37;
private static final int MSG_SET_SCRUBBING_MODE_PARAMETERS = 38;
private static final int MSG_AUDIO_PRESENTATIONS_CHANGED = 39;

private static final long BUFFERING_MAXIMUM_INTERVAL_MS =
Util.usToMs(Renderer.DEFAULT_DURATION_TO_PROGRESS_US);
Expand Down Expand Up @@ -256,7 +262,7 @@ public interface PlaybackInfoUpdateListener {
private long prewarmingMediaPeriodDiscontinuity = C.TIME_UNSET;
private boolean isPrewarmingDisabledUntilNextTransition;
private float volume;

private final AudioPresentationsChangeListener audioPresentationsChangeListener;
public ExoPlayerImplInternal(
Context context,
Renderer[] renderers,
Expand All @@ -279,7 +285,8 @@ public ExoPlayerImplInternal(
PlayerId playerId,
@Nullable PlaybackLooperProvider playbackLooperProvider,
PreloadConfiguration preloadConfiguration,
VideoFrameMetadataListener videoFrameMetadataListener) {
VideoFrameMetadataListener videoFrameMetadataListener,
AudioPresentationsChangeListener audioPresentationsChangeListener) {
this.playbackInfoUpdateListener = playbackInfoUpdateListener;
this.trackSelector = trackSelector;
this.emptyTrackSelectorResult = emptyTrackSelectorResult;
Expand All @@ -299,6 +306,7 @@ public ExoPlayerImplInternal(
this.analyticsCollector = analyticsCollector;
this.volume = 1f;
this.scrubbingModeParameters = ScrubbingModeParameters.DEFAULT;
this.audioPresentationsChangeListener = audioPresentationsChangeListener;

playbackMaybeBecameStuckAtMs = C.TIME_UNSET;
lastRebufferRealtimeMs = C.TIME_UNSET;
Expand Down Expand Up @@ -615,6 +623,11 @@ public void onPrepared(MediaPeriod source) {
handler.obtainMessage(MSG_PERIOD_PREPARED, source).sendToTarget();
}

@Override
public void onAudioPresentationsChanged(List<AudioPresentation> audioPresentations) {
handler.obtainMessage(MSG_AUDIO_PRESENTATIONS_CHANGED, audioPresentations).sendToTarget();
}

@Override
public void onContinueLoadingRequested(MediaPeriod source) {
handler.obtainMessage(MSG_SOURCE_CONTINUE_LOADING_REQUESTED, source).sendToTarget();
Expand Down Expand Up @@ -735,6 +748,9 @@ public boolean handleMessage(Message msg) {
case MSG_PERIOD_PREPARED:
handlePeriodPrepared((MediaPeriod) msg.obj);
break;
case MSG_AUDIO_PRESENTATIONS_CHANGED:
handleAudioPresentationsChanged((List<AudioPresentation>) msg.obj);
break;
case MSG_SOURCE_CONTINUE_LOADING_REQUESTED:
handleContinueLoadingRequested((MediaPeriod) msg.obj);
break;
Expand Down Expand Up @@ -3008,6 +3024,10 @@ private void handleLoadingPeriodPrepared(MediaPeriodHolder loadingPeriodHolder)
maybeContinueLoading();
}

private void handleAudioPresentationsChanged(List<AudioPresentation> audioPresentations) {
audioPresentationsChangeListener.onAudioPresentationsChanged(audioPresentations);
}

private void handleContinueLoadingRequested(MediaPeriod mediaPeriod) {
if (queue.isLoading(mediaPeriod)) {
queue.reevaluateBuffer(rendererPositionUs);
Expand Down
Loading