diff --git a/libraries/decoder_iamf/src/androidTest/java/androidx/media3/decoder/iamf/IamfDecoderTest.java b/libraries/decoder_iamf/src/androidTest/java/androidx/media3/decoder/iamf/IamfDecoderTest.java index 6fe1ffcd19..89ea7880c6 100644 --- a/libraries/decoder_iamf/src/androidTest/java/androidx/media3/decoder/iamf/IamfDecoderTest.java +++ b/libraries/decoder_iamf/src/androidTest/java/androidx/media3/decoder/iamf/IamfDecoderTest.java @@ -18,6 +18,7 @@ package androidx.media3.decoder.iamf; import static com.google.common.truth.Truth.assertThat; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.common.collect.ImmutableList; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -25,6 +26,17 @@ import org.junit.runner.RunWith; /** Test IAMF native functions. */ @RunWith(AndroidJUnit4.class) public final class IamfDecoderTest { + private static final int DEFAULT_BINAURAL_LAYOUT_CHANNEL_COUNT = 2; + + // Sample configOBUs data from sample_iamf.mp4 file. + private static final byte[] IACB_OBUS = { + -8, 6, 105, 97, 109, 102, 0, 0, 0, 15, -56, 1, 105, 112, 99, 109, 64, 0, 0, 1, 16, 0, 0, 62, + -128, 8, 12, -84, 2, 0, -56, 1, 1, 0, 0, 32, 16, 1, 1, 16, 78, 42, 1, 101, 110, 45, 117, 115, 0, + 116, 101, 115, 116, 95, 109, 105, 120, 95, 112, 114, 101, 115, 0, 1, 1, -84, 2, 116, 101, 115, + 116, 95, 115, 117, 98, 95, 109, 105, 120, 95, 48, 95, 97, 117, 100, 105, 111, 95, 101, 108, 101, + 109, 101, 110, 116, 95, 48, 0, 0, 0, 100, -128, 125, -128, 0, 0, 100, -128, 125, -128, 0, 0, 1, + -128, 0, -54, 81, -51, -79 + }; @Before public void setUp() { @@ -32,8 +44,10 @@ public final class IamfDecoderTest { } @Test - public void iamfLayoutBinauralChannelsCountTest() { - IamfDecoder iamf = new IamfDecoder(); - assertThat(iamf.getBinauralLayoutChannelCount()).isEqualTo(2); + public void iamfBinauralLayoutChannelsCount_equalsTwo() throws Exception { + IamfDecoder iamf = new IamfDecoder(ImmutableList.of(IACB_OBUS)); + + assertThat(iamf.getBinauralLayoutChannelCount()) + .isEqualTo(DEFAULT_BINAURAL_LAYOUT_CHANNEL_COUNT); } } diff --git a/libraries/decoder_iamf/src/main/java/androidx/media3/decoder/iamf/IamfDecoder.java b/libraries/decoder_iamf/src/main/java/androidx/media3/decoder/iamf/IamfDecoder.java index cbea45637c..99da0c9587 100644 --- a/libraries/decoder_iamf/src/main/java/androidx/media3/decoder/iamf/IamfDecoder.java +++ b/libraries/decoder_iamf/src/main/java/androidx/media3/decoder/iamf/IamfDecoder.java @@ -21,14 +21,31 @@ import androidx.annotation.VisibleForTesting; import androidx.media3.decoder.DecoderInputBuffer; import androidx.media3.decoder.SimpleDecoder; import androidx.media3.decoder.SimpleDecoderOutputBuffer; +import java.util.List; /** IAMF decoder. */ @VisibleForTesting(otherwise = PACKAGE_PRIVATE) public final class IamfDecoder extends SimpleDecoder { - public IamfDecoder() { + /** + * Creates an IAMF decoder. + * + * @param initializationData ConfigOBUs data for the decoder. + * @throws IamfDecoderException Thrown if an exception occurs when initializing the decoder. + */ + public IamfDecoder(List initializationData) throws IamfDecoderException { super(new DecoderInputBuffer[0], new SimpleDecoderOutputBuffer[0]); + int status = iamfConfigDecoder(initializationData.get(0)); + if (status != 0) { + throw new IamfDecoderException("Failed to configure decoder with returned status: " + status); + } + } + + @Override + public void release() { + super.release(); + iamfClose(); } public int getBinauralLayoutChannelCount() { @@ -42,17 +59,17 @@ public final class IamfDecoder @Override protected DecoderInputBuffer createInputBuffer() { - throw new UnsupportedOperationException(); + return new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT); } @Override protected SimpleDecoderOutputBuffer createOutputBuffer() { - throw new UnsupportedOperationException(); + return new SimpleDecoderOutputBuffer(this::releaseOutputBuffer); } @Override protected IamfDecoderException createUnexpectedDecodeException(Throwable error) { - throw new UnsupportedOperationException(); + return new IamfDecoderException("Unexpected decode error", error); } @Override @@ -62,4 +79,8 @@ public final class IamfDecoder } private native int iamfLayoutBinauralChannelsCount(); + + private native int iamfConfigDecoder(byte[] initializationData); + + private native void iamfClose(); } diff --git a/libraries/decoder_iamf/src/main/java/androidx/media3/decoder/iamf/LibiamfAudioRenderer.java b/libraries/decoder_iamf/src/main/java/androidx/media3/decoder/iamf/LibiamfAudioRenderer.java index 18c0abf511..603f9d2bf2 100644 --- a/libraries/decoder_iamf/src/main/java/androidx/media3/decoder/iamf/LibiamfAudioRenderer.java +++ b/libraries/decoder_iamf/src/main/java/androidx/media3/decoder/iamf/LibiamfAudioRenderer.java @@ -15,25 +15,67 @@ */ package androidx.media3.decoder.iamf; +import android.os.Handler; import androidx.annotation.Nullable; +import androidx.media3.common.C; import androidx.media3.common.Format; +import androidx.media3.common.MimeTypes; +import androidx.media3.common.audio.AudioProcessor; +import androidx.media3.common.util.TraceUtil; import androidx.media3.decoder.CryptoConfig; import androidx.media3.decoder.DecoderException; +import androidx.media3.exoplayer.audio.AudioRendererEventListener; +import androidx.media3.exoplayer.audio.AudioSink; import androidx.media3.exoplayer.audio.DecoderAudioRenderer; /** Decodes and renders audio using the native IAMF decoder. */ public class LibiamfAudioRenderer extends DecoderAudioRenderer { - public LibiamfAudioRenderer() {} + + /** + * Creates a new instance. + * + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @param audioProcessors Optional {@link AudioProcessor}s that will process audio before output. + */ + public LibiamfAudioRenderer( + @Nullable Handler eventHandler, + @Nullable AudioRendererEventListener eventListener, + AudioProcessor... audioProcessors) { + super(eventHandler, eventListener, audioProcessors); + } + + /** + * Creates a new instance. + * + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @param audioSink The sink to which audio will be output. + */ + public LibiamfAudioRenderer( + @Nullable Handler eventHandler, + @Nullable AudioRendererEventListener eventListener, + AudioSink audioSink) { + super(eventHandler, eventListener, audioSink); + } @Override protected int supportsFormatInternal(Format format) { - throw new UnsupportedOperationException(); + return !IamfLibrary.isAvailable() + || !java.util.Objects.equals(format.sampleMimeType, MimeTypes.AUDIO_IAMF) + ? C.FORMAT_UNSUPPORTED_TYPE + : C.FORMAT_HANDLED; } @Override protected IamfDecoder createDecoder(Format format, @Nullable CryptoConfig cryptoConfig) throws DecoderException { - throw new UnsupportedOperationException(); + TraceUtil.beginSection("createIamfDecoder"); + IamfDecoder decoder = new IamfDecoder(format.initializationData); + TraceUtil.endSection(); + return decoder; } @Override @@ -43,6 +85,6 @@ public class LibiamfAudioRenderer extends DecoderAudioRenderer { @Override public String getName() { - throw new UnsupportedOperationException(); + return "LibiamfAudioRenderer"; } } diff --git a/libraries/decoder_iamf/src/main/jni/iamf_jni.cc b/libraries/decoder_iamf/src/main/jni/iamf_jni.cc index fbd7f2fda5..46879c6d2f 100644 --- a/libraries/decoder_iamf/src/main/jni/iamf_jni.cc +++ b/libraries/decoder_iamf/src/main/jni/iamf_jni.cc @@ -53,3 +53,29 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { DECODER_FUNC(jint, iamfLayoutBinauralChannelsCount) { return IAMF_layout_binaural_channels_count(); } + +IAMF_DecoderHandle handle; + +DECODER_FUNC(jint, iamfConfigDecoder, jbyteArray initializationDataArray) { + handle = IAMF_decoder_open(); + IAMF_decoder_peak_limiter_enable(handle, 0); + IAMF_decoder_peak_limiter_set_threshold(handle, -1.0f); + IAMF_decoder_set_normalization_loudness(handle, 0.0f); + IAMF_decoder_set_bit_depth(handle, 16); + IAMF_decoder_set_sampling_rate(handle, 48000); + IAMF_decoder_output_layout_set_binaural(handle); + IAMF_decoder_set_pts(handle, 0, 90000); + + uint32_t* bytes_read = nullptr; + jbyte* initializationDataBytes = + env->GetByteArrayElements(initializationDataArray, 0); + + int status = IAMF_decoder_configure( + handle, reinterpret_cast(initializationDataBytes), + env->GetArrayLength(initializationDataArray), bytes_read); + env->ReleaseByteArrayElements(initializationDataArray, + initializationDataBytes, 0); + return status; +} + +DECODER_FUNC(void, iamfClose) { IAMF_decoder_close(handle); }