Skip to content

Commit

Permalink
add extension for MPEG-H decoding
Browse files Browse the repository at this point in the history
  • Loading branch information
rohitjoins committed Oct 23, 2024
1 parent e677c8d commit 59c1f2b
Show file tree
Hide file tree
Showing 18 changed files with 873 additions and 0 deletions.
2 changes: 2 additions & 0 deletions core_settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ if (gradle.ext.has('androidxMediaEnableMidiModule') && gradle.ext.androidxMediaE
include modulePrefix + 'lib-decoder-midi'
project(modulePrefix + 'lib-decoder-midi').projectDir = new File(rootDir, 'libraries/decoder_midi')
}
include modulePrefix + 'lib-decoder-mpegh'
project(modulePrefix + 'lib-decoder-mpegh').projectDir = new File(rootDir, 'libraries/decoder_mpegh')
include modulePrefix + 'lib-decoder-opus'
project(modulePrefix + 'lib-decoder-opus').projectDir = new File(rootDir, 'libraries/decoder_opus')
include modulePrefix + 'lib-decoder-vp9'
Expand Down
1 change: 1 addition & 0 deletions demos/main/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ dependencies {
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-iamf')
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-vp9')
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-midi')
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-mpegh')
withDecoderExtensionsImplementation project(modulePrefix + 'lib-datasource-rtmp')
}

Expand Down
9 changes: 9 additions & 0 deletions demos/main/src/main/assets/media.exolist.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
[
{
"name": "MPEG-H",
"samples": [
{
"name": "HD (MP4, H265)",
"uri": "https://media.githubusercontent.com/media/Fraunhofer-IIS/mpegh-test-content/main/TRI_Fileset_17_514H_D1_D2_D3_O1_24bit1080p50.mp4"
}
]
},
{
"name": "Clear DASH",
"samples": [
Expand Down
93 changes: 93 additions & 0 deletions libraries/decoder_mpegh/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# MPEG-H decoder module

The MPEG-H module provides `MpeghAudioRenderer`, which uses [mpeghdec
(the Fraunhofer MPEG-H decoding library)](https://github.com/Fraunhofer-IIS/mpeghdec) to decode MPEG-H audio.


## License note

Please note that whilst this code for extending ExoPlayer for MPEG-H 3D Audio
Decoder functionality is licensed under Apache 2.0, using this extension requires
checkout and compile the [Fraunhofer GitHub MPEG-H decoding library](https://github.com/Fraunhofer-IIS/mpeghdec)
as described below. The [Fraunhofer GitHub MPEG-H decoder](https://github.com/Fraunhofer-IIS/mpeghdec) is not licensed
under Apache 2.0, but under the license of the Fraunhofer GitHub MPEG-H
decoder project.

[Apache 2.0]: ../../LICENSE

## Build instructions (Linux, macOS)

To use the module you need to clone this GitHub project and depend on its
modules locally. Instructions for doing this can be found in the
[top level README][].

In addition, it's necessary to fetch mpeghdec library with its
dependencies as follows:

* Set the following environment variables:

```
cd "<path to project checkout>"
MPEGH_MODULE_PATH="$(pwd)/libraries/decoder_mpegh/src/main"
```

* Fetch mpeghdec library:

```
cd "${MPEGH_MODULE_PATH}/jni" && \
git clone https://github.com/Fraunhofer-IIS/mpeghdec.git --branch r2.0.0
```

* [Install CMake][].

Having followed these steps, gradle will build the module automatically when run
on the command line or via Android Studio, using [CMake][] and [Ninja][] to
configure and build mpeghdec and the module's [JNI wrapper library][].

[top level README]: ../../README.md
[Install CMake]: https://developer.android.com/studio/projects/install-ndk
[CMake]: https://cmake.org/
[Ninja]: https://ninja-build.org
[JNI wrapper library]: src/main/jni/mpeghdec_jni.cc

## Build instructions (Windows)

We do not provide support for building this module on Windows, however it should
be possible to follow the Linux instructions in [Windows PowerShell][].

[Windows PowerShell]: https://docs.microsoft.com/en-us/powershell/scripting/getting-started/getting-started-with-windows-powershell

## Using the module with ExoPlayer

Once you've followed the instructions above to check out, build and depend on
the module, the next step is to tell ExoPlayer to use `MpeghAudioRenderer`.
How you do this depends on which player API you're using:

* If you're passing a `DefaultRenderersFactory` to `ExoPlayer.Builder`, you
can enable using the module by setting the `extensionRendererMode` parameter
of the `DefaultRenderersFactory` constructor to
`EXTENSION_RENDERER_MODE_ON`. This will use `MpeghAudioRenderer` for
playback if `MediaCodecAudioRenderer` doesn't support the input format. Pass
`EXTENSION_RENDERER_MODE_PREFER` to give `MpeghAudioRenderer` priority
over `MediaCodecAudioRenderer`.
* If you've subclassed `DefaultRenderersFactory`, add a `MpeghAudioRenderer`
to the output list in `buildAudioRenderers`. ExoPlayer will use the first
`Renderer` in the list that supports the input media format.
* If you've implemented your own `RenderersFactory`, return a
`MpeghAudioRenderer` instance from `createRenderers`. ExoPlayer will use
the first `Renderer` in the returned array that supports the input media
format.
* If you're using `ExoPlayer.Builder`, pass a `MpeghAudioRenderer` in the
array of `Renderer`s. ExoPlayer will use the first `Renderer` in the list
that supports the input media format.

Note: These instructions assume you're using `DefaultTrackSelector`. If you have
a custom track selector the choice of `Renderer` is up to your implementation,
so you need to make sure you are passing an `MpeghAudioRenderer` to the
player, then implement your own logic to use the renderer for a given track.

## Links

* [Troubleshooting using decoding extensions][]

[Troubleshooting using decoding extensions]: https://developer.android.com/media/media3/exoplayer/troubleshooting#how-can-i-get-a-decoding-library-to-load-and-be-used-for-playback
22 changes: 22 additions & 0 deletions libraries/decoder_mpegh/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
apply from: "$gradle.ext.androidxMediaSettingsDir/common_library_config.gradle"

android.namespace 'androidx.media3.decoder.mpegh'

// Configure the native build only if ffmpeg is present to avoid gradle sync
// failures if ffmpeg hasn't been built according to the README instructions.
if (project.file('src/main/jni/mpeghdec').exists()) {
android.externalNativeBuild.cmake.path = 'src/main/jni/CMakeLists.txt'
// Should match cmake_minimum_required.
android.externalNativeBuild.cmake.version = '3.21.0+'
}

dependencies {
implementation project(modulePrefix + 'lib-decoder')
// TODO(b/203752526): Remove this dependency.
implementation project(modulePrefix + 'lib-exoplayer')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
testImplementation project(modulePrefix + 'test-utils')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
}
13 changes: 13 additions & 0 deletions libraries/decoder_mpegh/proguard-rules.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Proguard rules specific to the MPEG-H extension.

# This prevents the names of native methods from being obfuscated.
-keepclasseswithmembernames class * {
native <methods>;
}

# Some members of this class are being accessed from native methods. Keep them unobfuscated.
-keep class androidx.media3.decoder.SimpleDecoderOutputBuffer {
*;
}

-keep class androidx.media3.decoder.mpegh** { *; }
2 changes: 2 additions & 0 deletions libraries/decoder_mpegh/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="androidx.media3.decoder.mpegh"/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package androidx.media3.decoder.mpegh;

import android.os.Handler;
import androidx.annotation.NonNull;
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.UnstableApi;
import androidx.media3.decoder.CryptoConfig;
import androidx.media3.exoplayer.DecoderReuseEvaluation;
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 MPEG-H decoder.
*/
@UnstableApi
public final class MpeghAudioRenderer extends DecoderAudioRenderer<MpeghDecoder> {

private static final String TAG = "MpeghAudioRenderer";

/** The number of input and output buffers. */
private static final int NUM_BUFFERS = 16;

public MpeghAudioRenderer() {
this(null, null);
}

/**
* @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 MpeghAudioRenderer(
Handler eventHandler, AudioRendererEventListener eventListener,
AudioProcessor... audioProcessors) {
super(eventHandler, eventListener, audioProcessors);
}

/**
* @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 MpeghAudioRenderer(
Handler eventHandler, AudioRendererEventListener eventListener, AudioSink audioSink) {
super(eventHandler, eventListener, audioSink);
}

@Override
public String getName() {
return TAG;
}

@Override
protected int supportsFormatInternal(Format format) {
// check if JNI library is available
if (!MpeghLibrary.isAvailable()) {
return C.FORMAT_UNSUPPORTED_TYPE;
}

// check if MIME type is supported
if (!(MimeTypes.AUDIO_MPEGH_MHM1.equalsIgnoreCase(format.sampleMimeType)
|| MimeTypes.AUDIO_MPEGH_MHA1.equalsIgnoreCase(format.sampleMimeType))) {
return C.FORMAT_UNSUPPORTED_TYPE;
}
return C.FORMAT_HANDLED;
}

@Override
protected DecoderReuseEvaluation canReuseDecoder(
@NonNull String decoderName, Format oldFormat, Format newFormat) {

if (oldFormat.sampleMimeType.equals(newFormat.sampleMimeType)
&& oldFormat.sampleMimeType.equals(MimeTypes.AUDIO_MPEGH_MHM1)) {
return new DecoderReuseEvaluation(
decoderName, oldFormat, newFormat,
DecoderReuseEvaluation.REUSE_RESULT_YES_WITHOUT_RECONFIGURATION, 0);
}
return super.canReuseDecoder(decoderName, oldFormat, newFormat);
}

@Override
protected MpeghDecoder createDecoder(@NonNull Format format, CryptoConfig cryptoConfig)
throws MpeghException {

// initialize the decoder
return new MpeghDecoder(NUM_BUFFERS, NUM_BUFFERS, format);
}

@Override
protected Format getOutputFormat(MpeghDecoder decoder) {
Format.Builder formatBuilder = new Format.Builder();
formatBuilder.setChannelCount(decoder.getChannelCount()).setSampleRate(decoder.getSampleRate());
formatBuilder.setSampleMimeType(MimeTypes.AUDIO_RAW).setPcmEncoding(C.ENCODING_PCM_16BIT);
return formatBuilder.build();
}
}
Loading

0 comments on commit 59c1f2b

Please sign in to comment.