mirror of
https://github.com/androidx/media.git
synced 2025-04-29 22:36:54 +08:00
Merge Issue: androidx/media#1826: add extension for MPEG-H decoding
Imported from GitHub PR https://github.com/androidx/media/pull/1826 Merge 6b59a1602b022ebc44411ae3440e274c51c223a7 into b5615d5e919b297def6450b45320a3165c34548c COPYBARA_INTEGRATE_REVIEW=https://github.com/androidx/media/pull/1826 from androidx:mpegh_extension 6b59a1602b022ebc44411ae3440e274c51c223a7 PiperOrigin-RevId: 689417378
This commit is contained in:
parent
757f223d8a
commit
af1b5b5102
@ -52,6 +52,9 @@
|
|||||||
* Smooth Streaming Extension:
|
* Smooth Streaming Extension:
|
||||||
* RTSP Extension:
|
* RTSP Extension:
|
||||||
* Decoder Extensions (FFmpeg, VP9, AV1, etc.):
|
* Decoder Extensions (FFmpeg, VP9, AV1, etc.):
|
||||||
|
* Add the MPEG-H decoder module which uses the native MPEG-H decoder
|
||||||
|
module to decode MPEG-H audio
|
||||||
|
([#1826](https://github.com/androidx/media/pull/1826)).
|
||||||
* MIDI extension:
|
* MIDI extension:
|
||||||
* Leanback extension:
|
* Leanback extension:
|
||||||
* Cast Extension:
|
* Cast Extension:
|
||||||
|
@ -83,6 +83,8 @@ if (gradle.ext.has('androidxMediaEnableMidiModule') && gradle.ext.androidxMediaE
|
|||||||
include modulePrefix + 'lib-decoder-midi'
|
include modulePrefix + 'lib-decoder-midi'
|
||||||
project(modulePrefix + 'lib-decoder-midi').projectDir = new File(rootDir, 'libraries/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'
|
include modulePrefix + 'lib-decoder-opus'
|
||||||
project(modulePrefix + 'lib-decoder-opus').projectDir = new File(rootDir, 'libraries/decoder_opus')
|
project(modulePrefix + 'lib-decoder-opus').projectDir = new File(rootDir, 'libraries/decoder_opus')
|
||||||
include modulePrefix + 'lib-decoder-vp9'
|
include modulePrefix + 'lib-decoder-vp9'
|
||||||
|
@ -90,6 +90,7 @@ dependencies {
|
|||||||
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-iamf')
|
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-iamf')
|
||||||
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-vp9')
|
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-vp9')
|
||||||
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-midi')
|
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-midi')
|
||||||
|
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-mpegh')
|
||||||
withDecoderExtensionsImplementation project(modulePrefix + 'lib-datasource-rtmp')
|
withDecoderExtensionsImplementation project(modulePrefix + 'lib-datasource-rtmp')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -762,6 +762,10 @@
|
|||||||
{
|
{
|
||||||
"name": "Immersive Audio Format Sample (MP4, IAMF)",
|
"name": "Immersive Audio Format Sample (MP4, IAMF)",
|
||||||
"uri": "https://github.com/AOMediaCodec/libiamf/raw/main/tests/test_000036_s.mp4"
|
"uri": "https://github.com/AOMediaCodec/libiamf/raw/main/tests/test_000036_s.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "MPEG-H 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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
89
libraries/decoder_mpegh/README.md
Normal file
89
libraries/decoder_mpegh/README.md
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
# MPEG-H decoder module
|
||||||
|
|
||||||
|
The MPEG-H module provides `MpeghAudioRenderer`, which uses the libmpegh native
|
||||||
|
library to decode MPEG-H audio.
|
||||||
|
|
||||||
|
## License note
|
||||||
|
|
||||||
|
Please note that while the code in this repository is licensed under
|
||||||
|
[Apache 2.0][], using this module also requires building and including the
|
||||||
|
[Fraunhofer GitHub MPEG-H decoder][] which is licensed under the Fraunhofer
|
||||||
|
GitHub MPEG-H decoder project.
|
||||||
|
|
||||||
|
[Apache 2.0]: ../../LICENSE
|
||||||
|
[Fraunhofer GitHub MPEG-H decoder]: https://github.com/Fraunhofer-IIS/mpeghdec
|
||||||
|
|
||||||
|
## 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 libmpegh library as follows:
|
||||||
|
|
||||||
|
* Set the following environment variables:
|
||||||
|
|
||||||
|
```
|
||||||
|
cd "<path to project checkout>"
|
||||||
|
MPEGH_MODULE_PATH="$(pwd)/libraries/decoder_mpegh/src/main"
|
||||||
|
```
|
||||||
|
|
||||||
|
* Fetch libmpegh library:
|
||||||
|
|
||||||
|
```
|
||||||
|
cd "${MPEGH_MODULE_PATH}/jni" && \
|
||||||
|
git clone https://github.com/Fraunhofer-IIS/mpeghdec.git --branch r2.0.0 libmpegh
|
||||||
|
```
|
||||||
|
|
||||||
|
* [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
|
65
libraries/decoder_mpegh/build.gradle
Normal file
65
libraries/decoder_mpegh/build.gradle
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// Copyright 2024 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
apply from: "$gradle.ext.androidxMediaSettingsDir/common_library_config.gradle"
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace 'androidx.media3.decoder.mpegh'
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
androidTest.assets.srcDir '../test_data/src/test/assets'
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
targets "mpeghJNI"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(Internal: b/372449691): Remove packagingOptions once AGP is updated
|
||||||
|
// to version 8.5.1 or higher.
|
||||||
|
packagingOptions {
|
||||||
|
jniLibs {
|
||||||
|
useLegacyPackaging true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure the native build only if libmpegh is present to avoid gradle sync
|
||||||
|
// failures if libmpegh hasn't been built according to the README instructions.
|
||||||
|
if (project.file('src/main/jni/libmpegh').exists()) {
|
||||||
|
android.externalNativeBuild.cmake {
|
||||||
|
path = 'src/main/jni/CMakeLists.txt'
|
||||||
|
version = '3.21.0+'
|
||||||
|
if (project.hasProperty('externalNativeBuildDir')) {
|
||||||
|
if (!new File(externalNativeBuildDir).isAbsolute()) {
|
||||||
|
ext.externalNativeBuildDir =
|
||||||
|
new File(rootDir, it.externalNativeBuildDir)
|
||||||
|
}
|
||||||
|
buildStagingDirectory = "${externalNativeBuildDir}/${project.name}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
libraries/decoder_mpegh/proguard-rules.txt
Normal file
13
libraries/decoder_mpegh/proguard-rules.txt
Normal file
@ -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** { *; }
|
17
libraries/decoder_mpegh/src/main/AndroidManifest.xml
Normal file
17
libraries/decoder_mpegh/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright 2024 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<manifest package="androidx.media3.decoder.mpegh"/>
|
@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package androidx.media3.decoder.mpegh;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
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.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;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/** 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;
|
||||||
|
|
||||||
|
/* Creates a new instance. */
|
||||||
|
public MpeghAudioRenderer() {
|
||||||
|
this(/* eventHandler= */ null, /* eventListener= */ null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 MpeghAudioRenderer(
|
||||||
|
Handler eventHandler,
|
||||||
|
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 MpeghAudioRenderer(
|
||||||
|
Handler eventHandler, AudioRendererEventListener eventListener, AudioSink audioSink) {
|
||||||
|
super(eventHandler, eventListener, audioSink);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @C.FormatSupport 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 (!(Objects.equals(format.sampleMimeType, MimeTypes.AUDIO_MPEGH_MHM1)
|
||||||
|
|| Objects.equals(format.sampleMimeType, MimeTypes.AUDIO_MPEGH_MHA1))) {
|
||||||
|
return C.FORMAT_UNSUPPORTED_TYPE;
|
||||||
|
}
|
||||||
|
return C.FORMAT_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected DecoderReuseEvaluation canReuseDecoder(
|
||||||
|
String decoderName, Format oldFormat, Format newFormat) {
|
||||||
|
if (Objects.equals(oldFormat.sampleMimeType, newFormat.sampleMimeType)
|
||||||
|
&& Objects.equals(oldFormat.sampleMimeType, MimeTypes.AUDIO_MPEGH_MHM1)) {
|
||||||
|
return new DecoderReuseEvaluation(
|
||||||
|
decoderName,
|
||||||
|
oldFormat,
|
||||||
|
newFormat,
|
||||||
|
DecoderReuseEvaluation.REUSE_RESULT_YES_WITHOUT_RECONFIGURATION,
|
||||||
|
/* discardReasons= */ 0);
|
||||||
|
}
|
||||||
|
return super.canReuseDecoder(decoderName, oldFormat, newFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected MpeghDecoder createDecoder(Format format, CryptoConfig cryptoConfig)
|
||||||
|
throws MpeghDecoderException {
|
||||||
|
TraceUtil.beginSection("createMpeghDecoder");
|
||||||
|
MpeghDecoder decoder = new MpeghDecoder(format, NUM_BUFFERS, NUM_BUFFERS);
|
||||||
|
TraceUtil.endSection();
|
||||||
|
return decoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Format getOutputFormat(MpeghDecoder decoder) {
|
||||||
|
return new Format.Builder()
|
||||||
|
.setChannelCount(decoder.getChannelCount())
|
||||||
|
.setSampleRate(decoder.getSampleRate())
|
||||||
|
.setSampleMimeType(MimeTypes.AUDIO_RAW)
|
||||||
|
.setPcmEncoding(C.ENCODING_PCM_16BIT)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package androidx.media3.decoder.mpegh;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.media3.common.Format;
|
||||||
|
import androidx.media3.common.MimeTypes;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
|
import androidx.media3.decoder.DecoderInputBuffer;
|
||||||
|
import androidx.media3.decoder.SimpleDecoder;
|
||||||
|
import androidx.media3.decoder.SimpleDecoderOutputBuffer;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/** MPEG-H decoder. */
|
||||||
|
@UnstableApi
|
||||||
|
public final class MpeghDecoder
|
||||||
|
extends SimpleDecoder<DecoderInputBuffer, SimpleDecoderOutputBuffer, MpeghDecoderException> {
|
||||||
|
|
||||||
|
/** The default input buffer size. */
|
||||||
|
private static final int DEFAULT_INPUT_BUFFER_SIZE = 2048 * 6;
|
||||||
|
|
||||||
|
private static final int TARGET_LAYOUT_CICP = 2;
|
||||||
|
|
||||||
|
private final ByteBuffer tmpOutputBuffer;
|
||||||
|
|
||||||
|
private MpeghDecoderJni decoder;
|
||||||
|
private long outPtsUs;
|
||||||
|
private int outChannels;
|
||||||
|
private int outSampleRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an MPEG-H decoder.
|
||||||
|
*
|
||||||
|
* @param format The input {@link Format}.
|
||||||
|
* @param numInputBuffers The number of input buffers.
|
||||||
|
* @param numOutputBuffers The number of output buffers.
|
||||||
|
* @throws MpeghDecoderException If an exception occurs when initializing the decoder.
|
||||||
|
*/
|
||||||
|
public MpeghDecoder(Format format, int numInputBuffers, int numOutputBuffers)
|
||||||
|
throws MpeghDecoderException {
|
||||||
|
super(new DecoderInputBuffer[numInputBuffers], new SimpleDecoderOutputBuffer[numOutputBuffers]);
|
||||||
|
if (!MpeghLibrary.isAvailable()) {
|
||||||
|
throw new MpeghDecoderException("Failed to load decoder native libraries.");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] configData = new byte[0];
|
||||||
|
if (!format.initializationData.isEmpty()
|
||||||
|
&& Objects.equals(format.sampleMimeType, MimeTypes.AUDIO_MPEGH_MHA1)) {
|
||||||
|
configData = format.initializationData.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the native MPEG-H decoder.
|
||||||
|
decoder = new MpeghDecoderJni();
|
||||||
|
decoder.init(TARGET_LAYOUT_CICP, configData, configData.length);
|
||||||
|
|
||||||
|
int initialInputBufferSize =
|
||||||
|
format.maxInputSize != Format.NO_VALUE ? format.maxInputSize : DEFAULT_INPUT_BUFFER_SIZE;
|
||||||
|
setInitialInputBufferSize(initialInputBufferSize);
|
||||||
|
|
||||||
|
// Allocate memory for the temporary output of the native MPEG-H decoder.
|
||||||
|
tmpOutputBuffer =
|
||||||
|
ByteBuffer.allocateDirect(
|
||||||
|
3072 * 24 * 6
|
||||||
|
* 2); // MAX_FRAME_LENGTH * MAX_NUM_CHANNELS * MAX_NUM_FRAMES * BYTES_PER_SAMPLE
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "libmpegh";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected DecoderInputBuffer createInputBuffer() {
|
||||||
|
return new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SimpleDecoderOutputBuffer createOutputBuffer() {
|
||||||
|
return new SimpleDecoderOutputBuffer(this::releaseOutputBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected MpeghDecoderException createUnexpectedDecodeException(Throwable error) {
|
||||||
|
return new MpeghDecoderException("Unexpected decode error", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
protected MpeghDecoderException decode(
|
||||||
|
DecoderInputBuffer inputBuffer, SimpleDecoderOutputBuffer outputBuffer, boolean reset) {
|
||||||
|
if (reset) {
|
||||||
|
try {
|
||||||
|
decoder.flush();
|
||||||
|
} catch (MpeghDecoderException e) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the data from the input buffer.
|
||||||
|
ByteBuffer inputData = Util.castNonNull(inputBuffer.data);
|
||||||
|
int inputSize = inputData.limit();
|
||||||
|
long inputPtsUs = inputBuffer.timeUs;
|
||||||
|
|
||||||
|
// Process/decode the incoming data.
|
||||||
|
try {
|
||||||
|
decoder.process(inputData, inputSize, inputPtsUs);
|
||||||
|
} catch (MpeghDecoderException e) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get as many decoded samples as possible.
|
||||||
|
int outputSize = 0;
|
||||||
|
int numBytes = 0;
|
||||||
|
int cnt = 0;
|
||||||
|
tmpOutputBuffer.clear();
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
outputSize = decoder.getSamples(tmpOutputBuffer, numBytes);
|
||||||
|
} catch (MpeghDecoderException e) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
// To concatenate possible additional audio frames, increase the write position.
|
||||||
|
numBytes += outputSize;
|
||||||
|
|
||||||
|
if (cnt == 0 && outputSize > 0) {
|
||||||
|
// Only use the first frame for info about PTS, number of channels and sample rate.
|
||||||
|
outPtsUs = decoder.getPts();
|
||||||
|
outChannels = decoder.getNumChannels();
|
||||||
|
outSampleRate = decoder.getSamplerate();
|
||||||
|
}
|
||||||
|
|
||||||
|
cnt++;
|
||||||
|
} while (outputSize > 0);
|
||||||
|
|
||||||
|
int outputSizeTotal = numBytes;
|
||||||
|
tmpOutputBuffer.limit(outputSizeTotal);
|
||||||
|
|
||||||
|
if (outputSizeTotal > 0) {
|
||||||
|
// There is output data available
|
||||||
|
|
||||||
|
// initialize the output buffer
|
||||||
|
outputBuffer.clear();
|
||||||
|
outputBuffer.init(outPtsUs, outputSizeTotal);
|
||||||
|
|
||||||
|
// copy temporary output to output buffer
|
||||||
|
outputBuffer.data.asShortBuffer().put(tmpOutputBuffer.asShortBuffer());
|
||||||
|
outputBuffer.data.rewind();
|
||||||
|
} else {
|
||||||
|
// if no output data is available signalize that only decoding/processing was possible
|
||||||
|
outputBuffer.shouldBeSkipped = true;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
super.release();
|
||||||
|
if (decoder != null) {
|
||||||
|
decoder.destroy();
|
||||||
|
decoder = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the channel count of output audio. */
|
||||||
|
public int getChannelCount() {
|
||||||
|
return outChannels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the sample rate of output audio. */
|
||||||
|
public int getSampleRate() {
|
||||||
|
return outSampleRate;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package androidx.media3.decoder.mpegh;
|
||||||
|
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.decoder.DecoderException;
|
||||||
|
|
||||||
|
/** Thrown when an MPEG-H decoder error occurs. */
|
||||||
|
@UnstableApi
|
||||||
|
public class MpeghDecoderException extends DecoderException {
|
||||||
|
|
||||||
|
public MpeghDecoderException(String message) {
|
||||||
|
super(message, new Throwable());
|
||||||
|
}
|
||||||
|
|
||||||
|
public MpeghDecoderException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package androidx.media3.decoder.mpegh;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/** JNI wrapper for the libmpegh MPEG-H decoder. */
|
||||||
|
public class MpeghDecoderJni {
|
||||||
|
|
||||||
|
private long decoderHandle; // used by JNI only to hold the native context.
|
||||||
|
|
||||||
|
public MpeghDecoderJni() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the native MPEG-H decoder.
|
||||||
|
*
|
||||||
|
* @param cicpIndex The desired target layout CICP index.
|
||||||
|
* @param mhaConfig The byte array holding the audio specific configuration for MHA content.
|
||||||
|
* @param mhaConfigLength Length of audio specific configuration.
|
||||||
|
* @throws MpeghDecoderException If initialization fails.
|
||||||
|
*/
|
||||||
|
public native void init(int cicpIndex, byte[] mhaConfig, int mhaConfigLength)
|
||||||
|
throws MpeghDecoderException;
|
||||||
|
|
||||||
|
/** Destroys the native MPEG-H decoder. */
|
||||||
|
public native void destroy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes data (access units) and corresponding PTS inside of the native MPEG-H decoder.
|
||||||
|
*
|
||||||
|
* @param inputBuffer The direct byte buffer holding the access unit.
|
||||||
|
* @param inputLength The length of the direct byte buffer.
|
||||||
|
* @param timestampUs The presentation timestamp of the access unit, in microseconds.
|
||||||
|
* @throws MpeghDecoderException If processing fails.
|
||||||
|
*/
|
||||||
|
public native void process(ByteBuffer inputBuffer, int inputLength, long timestampUs)
|
||||||
|
throws MpeghDecoderException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains decoded samples from the native MPEG-H decoder and writes them into {@code buffer} at
|
||||||
|
* position {@code writePos}.
|
||||||
|
*
|
||||||
|
* <p>NOTE: The decoder returns the samples as 16bit values.
|
||||||
|
*
|
||||||
|
* @param buffer The direct byte buffer to write the decoded samples to.
|
||||||
|
* @param writePos The start position in the byte buffer to write the decoded samples to.
|
||||||
|
* @return The number of bytes written to buffer.
|
||||||
|
* @throws MpeghDecoderException If obtaining samples fails.
|
||||||
|
*/
|
||||||
|
public native int getSamples(ByteBuffer buffer, int writePos) throws MpeghDecoderException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flushes the native MPEG-H decoder and writes available output samples into a sample queue.
|
||||||
|
*
|
||||||
|
* @throws MpeghDecoderException If flushing fails.
|
||||||
|
*/
|
||||||
|
public native void flushAndGet() throws MpeghDecoderException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the number of output channels from the native MPEG-H decoder.
|
||||||
|
*
|
||||||
|
* <p>NOTE: This information belongs to the last audio frame obtained from {@link
|
||||||
|
* #getSamples(ByteBuffer, int)} or {@link #flushAndGet()}.
|
||||||
|
*
|
||||||
|
* @return The number of output channels.
|
||||||
|
*/
|
||||||
|
public native int getNumChannels();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the output sample rate from the native MPEG-H decoder.
|
||||||
|
*
|
||||||
|
* <p>NOTE: This information belongs to the last audio frame obtained from {@link
|
||||||
|
* #getSamples(ByteBuffer, int)} or {@link #flushAndGet()}.
|
||||||
|
*
|
||||||
|
* @return The output sample rate.
|
||||||
|
*/
|
||||||
|
public native int getSamplerate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the PTS from the native MPEG-H decoder, in microseconds.
|
||||||
|
*
|
||||||
|
* <p>NOTE: This information belongs to the last audio frame obtained from {@link
|
||||||
|
* #getSamples(ByteBuffer, int)} or {@link #flushAndGet()}.
|
||||||
|
*
|
||||||
|
* @return The output presentation timestamp.
|
||||||
|
*/
|
||||||
|
public native long getPts();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flushes the native MPEG-H decoder.
|
||||||
|
*
|
||||||
|
* @throws MpeghDecoderException If flushing fails.
|
||||||
|
*/
|
||||||
|
public native void flush() throws MpeghDecoderException;
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package androidx.media3.decoder.mpegh;
|
||||||
|
|
||||||
|
import androidx.media3.common.MediaLibraryInfo;
|
||||||
|
import androidx.media3.common.util.LibraryLoader;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
|
/** Configures and queries the underlying native library. */
|
||||||
|
@UnstableApi
|
||||||
|
public final class MpeghLibrary {
|
||||||
|
|
||||||
|
static {
|
||||||
|
MediaLibraryInfo.registerModule("media3.decoder.mpegh");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final LibraryLoader LOADER =
|
||||||
|
new LibraryLoader("mpeghJNI") {
|
||||||
|
@Override
|
||||||
|
protected void loadLibrary(String name) {
|
||||||
|
System.loadLibrary(name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private MpeghLibrary() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override the names of the MPEG-H native libraries. If an application wishes to call this
|
||||||
|
* method, it must do so before calling any other method defined by this class, and before
|
||||||
|
* instantiating a {@link MpeghAudioRenderer} instance.
|
||||||
|
*
|
||||||
|
* @param libraries The names of the MPEG-H native libraries.
|
||||||
|
*/
|
||||||
|
public static void setLibraries(String... libraries) {
|
||||||
|
LOADER.setLibraries(libraries);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns whether the underlying library is available, loading it if necessary. */
|
||||||
|
public static boolean isAvailable() {
|
||||||
|
return LOADER.isAvailable();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
@NonNullApi
|
||||||
|
package androidx.media3.decoder.mpegh;
|
||||||
|
|
||||||
|
import androidx.media3.common.util.NonNullApi;
|
57
libraries/decoder_mpegh/src/main/jni/CMakeLists.txt
Normal file
57
libraries/decoder_mpegh/src/main/jni/CMakeLists.txt
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#
|
||||||
|
# Copyright 2024 The Android Open Source Project
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.21.0 FATAL_ERROR)
|
||||||
|
|
||||||
|
# Enable C++11 features.
|
||||||
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
|
|
||||||
|
# Define project name for your JNI module
|
||||||
|
project(libmpeghJNI C CXX)
|
||||||
|
|
||||||
|
if(${ANDROID_ABI} MATCHES "armeabi-v7a")
|
||||||
|
add_compile_options("-mfpu=neon")
|
||||||
|
add_compile_options("-marm")
|
||||||
|
add_compile_options("-fPIC")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(libmpegh_jni_root "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
|
|
||||||
|
# Build libmpegh.
|
||||||
|
add_subdirectory("${libmpegh_jni_root}/libmpegh"
|
||||||
|
EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
|
# Add the include directory from libmpegh.
|
||||||
|
include_directories ("${libmpegh_jni_root}/libmpegh/include")
|
||||||
|
|
||||||
|
# Build libmpeghJNI.
|
||||||
|
add_library(mpeghJNI
|
||||||
|
SHARED
|
||||||
|
mpegh_jni.cpp)
|
||||||
|
|
||||||
|
# Locate NDK log library.
|
||||||
|
find_library(android_log_lib log)
|
||||||
|
|
||||||
|
# Link libmpeghJNI against used libraries.
|
||||||
|
target_link_libraries(mpeghJNI
|
||||||
|
PRIVATE android
|
||||||
|
PRIVATE mpeghdec
|
||||||
|
PRIVATE ${android_log_lib})
|
||||||
|
|
||||||
|
# Enable 16 KB ELF alignment.
|
||||||
|
target_link_options(mpeghJNI
|
||||||
|
PRIVATE "-Wl,-z,max-page-size=16384")
|
||||||
|
|
255
libraries/decoder_mpegh/src/main/jni/mpegh_jni.cpp
Normal file
255
libraries/decoder_mpegh/src/main/jni/mpegh_jni.cpp
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <android/log.h>
|
||||||
|
#include <jni.h>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "../include/mpeghdecoder.h"
|
||||||
|
|
||||||
|
#define LOG_TAG "mpeghdec_jni"
|
||||||
|
#define LOGE(...) \
|
||||||
|
((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
|
||||||
|
#define LOGW(...) \
|
||||||
|
((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
|
||||||
|
#define LOGI(...) \
|
||||||
|
((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
|
||||||
|
#define LOGD(...) \
|
||||||
|
((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
|
||||||
|
|
||||||
|
#define DECODER_FUNC(RETURN_TYPE, NAME, ...) \
|
||||||
|
extern "C" { \
|
||||||
|
JNIEXPORT RETURN_TYPE \
|
||||||
|
Java_androidx_media3_decoder_mpegh_MpeghDecoderJni_##NAME( \
|
||||||
|
JNIEnv *env, jobject obj, ##__VA_ARGS__); \
|
||||||
|
} \
|
||||||
|
JNIEXPORT RETURN_TYPE \
|
||||||
|
Java_androidx_media3_decoder_mpegh_MpeghDecoderJni_##NAME( \
|
||||||
|
JNIEnv *env, jobject obj, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
#define EXCEPTION_PATH "androidx/media3/decoder/mpegh/MpeghDecoderException"
|
||||||
|
|
||||||
|
#define MAX_NUM_FRAMES (6)
|
||||||
|
#define MAX_FRAME_LENGTH (3072)
|
||||||
|
#define MAX_NUM_CHANNELS (24)
|
||||||
|
#define BYTES_PER_SAMPLE (2)
|
||||||
|
#define MAX_OUTBUF_SIZE_SAMPLES \
|
||||||
|
(MAX_NUM_FRAMES * MAX_FRAME_LENGTH * MAX_NUM_CHANNELS)
|
||||||
|
|
||||||
|
typedef struct DECODER_CONTEXT {
|
||||||
|
int32_t outSampleRate;
|
||||||
|
int32_t outNumChannels;
|
||||||
|
int64_t outPts;
|
||||||
|
|
||||||
|
HANDLE_MPEGH_DECODER_CONTEXT handle;
|
||||||
|
int32_t samples[MAX_OUTBUF_SIZE_SAMPLES];
|
||||||
|
} DECODER_CONTEXT;
|
||||||
|
|
||||||
|
jfieldID getHandleFieldID(JNIEnv *env, jobject obj) {
|
||||||
|
jclass cls = env->GetObjectClass(obj);
|
||||||
|
return env->GetFieldID(cls, "decoderHandle", "J");
|
||||||
|
}
|
||||||
|
|
||||||
|
void setContext(JNIEnv *env, jobject obj, DECODER_CONTEXT *ctx) {
|
||||||
|
jfieldID decoderHandle_fid = getHandleFieldID(env, obj);
|
||||||
|
env->SetLongField(obj, decoderHandle_fid, (jlong)ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
DECODER_CONTEXT *getContext(JNIEnv *env, jobject obj) {
|
||||||
|
jfieldID decoderHandle_fid = getHandleFieldID(env, obj);
|
||||||
|
return (DECODER_CONTEXT *)env->GetLongField(obj, decoderHandle_fid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Method: init
|
||||||
|
* will be used to initialize the JNI MPEG-H decoder wrapper.
|
||||||
|
*/
|
||||||
|
DECODER_FUNC(void, init, jint cicpIndex, jbyteArray mhaConfig,
|
||||||
|
jint mhaConfigLength) {
|
||||||
|
// create JNI decoder wrapper context
|
||||||
|
auto *ctx = (DECODER_CONTEXT *)calloc(1, sizeof(DECODER_CONTEXT));
|
||||||
|
if (ctx == nullptr) {
|
||||||
|
LOGE("Unable to allocate memory for DECODER_CONTEXT!");
|
||||||
|
jclass atscExCls = env->FindClass(EXCEPTION_PATH);
|
||||||
|
env->ThrowNew(atscExCls, "cannot create DECODER_CONTEXT");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create MPEG-H decoder
|
||||||
|
ctx->handle = mpeghdecoder_init(cicpIndex);
|
||||||
|
if (ctx->handle == nullptr) {
|
||||||
|
LOGE("Cannot create mpeghdecoder with CICP = %d!", cicpIndex);
|
||||||
|
jclass atscExCls = env->FindClass(EXCEPTION_PATH);
|
||||||
|
env->ThrowNew(atscExCls, "Cannot create mpeghdecoder");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mhaConfigLength > 0) {
|
||||||
|
auto *cData = (jbyte *)calloc(mhaConfigLength, sizeof(jbyte));
|
||||||
|
env->GetByteArrayRegion(mhaConfig, 0, mhaConfigLength, cData);
|
||||||
|
|
||||||
|
MPEGH_DECODER_ERROR result = mpeghdecoder_setMhaConfig(
|
||||||
|
ctx->handle, (unsigned char *)cData, (uint32_t)mhaConfigLength);
|
||||||
|
free(cData);
|
||||||
|
if (result != MPEGH_DEC_OK) {
|
||||||
|
LOGE("Cannot set MHA config!");
|
||||||
|
jclass atscExCls = env->FindClass(EXCEPTION_PATH);
|
||||||
|
env->ThrowNew(atscExCls, "Cannot set MHA config");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// store the wrapper context in JNI env
|
||||||
|
setContext(env, obj, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Method: destroy
|
||||||
|
* will be called to destroy the JNI MPEG-H decoder wrapper.
|
||||||
|
*/
|
||||||
|
DECODER_FUNC(void, destroy) {
|
||||||
|
DECODER_CONTEXT *ctx = getContext(env, obj);
|
||||||
|
|
||||||
|
mpeghdecoder_destroy(ctx->handle);
|
||||||
|
free(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Method: process
|
||||||
|
* will be called to pass the received MHAS frame to the decoder.
|
||||||
|
*/
|
||||||
|
DECODER_FUNC(void, process, jobject inputBuffer, jint inputLength,
|
||||||
|
jlong timestampUs) {
|
||||||
|
DECODER_CONTEXT *ctx = getContext(env, obj);
|
||||||
|
|
||||||
|
// get memory pointer to the buffer of the corresponding JAVA input parameter
|
||||||
|
auto *inData = (const uint8_t *)env->GetDirectBufferAddress(inputBuffer);
|
||||||
|
auto inDataLen = (uint32_t)inputLength;
|
||||||
|
auto ptsIn = (uint64_t)timestampUs;
|
||||||
|
|
||||||
|
MPEGH_DECODER_ERROR result =
|
||||||
|
mpeghdecoder_process(ctx->handle, inData, inDataLen, ptsIn * 1000);
|
||||||
|
if (result != MPEGH_DEC_OK) {
|
||||||
|
LOGW("Unable to feed new data with return value = %d", result);
|
||||||
|
jclass atscExCls = env->FindClass(EXCEPTION_PATH);
|
||||||
|
env->ThrowNew(atscExCls, "Unable to feed new data!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Method: getSamples
|
||||||
|
* will be called to receive the decoded PCM.
|
||||||
|
*/
|
||||||
|
DECODER_FUNC(jint, getSamples, jobject buffer, jint writePos) {
|
||||||
|
DECODER_CONTEXT *ctx = getContext(env, obj);
|
||||||
|
|
||||||
|
// get memory pointer to the buffer of the corresponding JAVA input parameter
|
||||||
|
auto *outData = (uint8_t *)env->GetDirectBufferAddress(buffer);
|
||||||
|
if (outData == nullptr) {
|
||||||
|
LOGE("not possible to get direct byte buffer!");
|
||||||
|
jclass atscExCls = env->FindClass(EXCEPTION_PATH);
|
||||||
|
env->ThrowNew(atscExCls, "not possible to get direct byte buffer!");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int outNumSamples = 0;
|
||||||
|
MPEGH_DECODER_OUTPUT_INFO outInfo;
|
||||||
|
|
||||||
|
MPEGH_DECODER_ERROR result = mpeghdecoder_getSamples(
|
||||||
|
ctx->handle, ctx->samples, MAX_OUTBUF_SIZE_SAMPLES, &outInfo);
|
||||||
|
|
||||||
|
if (result == MPEGH_DEC_OK) {
|
||||||
|
outNumSamples = outInfo.numSamplesPerChannel;
|
||||||
|
if (outNumSamples > 0) {
|
||||||
|
for (int i = 0; i < outNumSamples * outInfo.numChannels; i++) {
|
||||||
|
ctx->samples[i] = ctx->samples[i] >> 16;
|
||||||
|
outData[writePos + i * 2 + 0] = (ctx->samples[i] >> 8) & 0xFF;
|
||||||
|
outData[writePos + i * 2 + 1] = (ctx->samples[i]) & 0xFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx->outSampleRate = outInfo.sampleRate;
|
||||||
|
ctx->outNumChannels = outInfo.numChannels;
|
||||||
|
ctx->outPts = outInfo.pts / 1000;
|
||||||
|
} else {
|
||||||
|
ctx->outSampleRate = -1;
|
||||||
|
ctx->outNumChannels = -1;
|
||||||
|
ctx->outPts = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return outNumSamples * ctx->outNumChannels * BYTES_PER_SAMPLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Method: flushAndGet
|
||||||
|
* will be called to force the decoder to flush the internal PCM buffer and
|
||||||
|
* write available output samples into a sample queue.
|
||||||
|
*/
|
||||||
|
DECODER_FUNC(void, flushAndGet) {
|
||||||
|
DECODER_CONTEXT *ctx = getContext(env, obj);
|
||||||
|
|
||||||
|
MPEGH_DECODER_ERROR result = mpeghdecoder_flushAndGet(ctx->handle);
|
||||||
|
if (result != MPEGH_DEC_OK) {
|
||||||
|
LOGE("Unable to flush data with return value = %d", result);
|
||||||
|
jclass atscExCls = env->FindClass(EXCEPTION_PATH);
|
||||||
|
env->ThrowNew(atscExCls, "Unable to flush data!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Method: getNumChannels
|
||||||
|
* will be called to receive the number of output channels.
|
||||||
|
*/
|
||||||
|
DECODER_FUNC(jint, getNumChannels) {
|
||||||
|
DECODER_CONTEXT *ctx = getContext(env, obj);
|
||||||
|
return ctx->outNumChannels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Method: getSamplerate
|
||||||
|
* will be called to receive the output sample rate.
|
||||||
|
*/
|
||||||
|
DECODER_FUNC(jint, getSamplerate) {
|
||||||
|
// get wrapper context from JNI env
|
||||||
|
DECODER_CONTEXT *ctx = getContext(env, obj);
|
||||||
|
return ctx->outSampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Method: getPts
|
||||||
|
* will be called to receive the output PTS.
|
||||||
|
*/
|
||||||
|
DECODER_FUNC(jlong, getPts) {
|
||||||
|
DECODER_CONTEXT *ctx = getContext(env, obj);
|
||||||
|
return ctx->outPts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Method: flush
|
||||||
|
* will be called to force the decoder to flush the internal PCM buffer.
|
||||||
|
*/
|
||||||
|
DECODER_FUNC(void, flush) {
|
||||||
|
DECODER_CONTEXT *ctx = getContext(env, obj);
|
||||||
|
|
||||||
|
MPEGH_DECODER_ERROR result = mpeghdecoder_flush(ctx->handle);
|
||||||
|
if (result != MPEGH_DEC_OK) {
|
||||||
|
LOGE("Unable to flush data with return value = %d", result);
|
||||||
|
jclass atscExCls = env->FindClass(EXCEPTION_PATH);
|
||||||
|
env->ThrowNew(atscExCls, "Unable to flush data!");
|
||||||
|
}
|
||||||
|
}
|
19
libraries/decoder_mpegh/src/test/AndroidManifest.xml
Normal file
19
libraries/decoder_mpegh/src/test/AndroidManifest.xml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright 2024 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<manifest package="androidx.media3.decoder.mpegh.test">
|
||||||
|
<uses-sdk/>
|
||||||
|
</manifest>
|
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package androidx.media3.decoder.mpegh;
|
||||||
|
|
||||||
|
import androidx.media3.common.C;
|
||||||
|
import androidx.media3.test.utils.DefaultRenderersFactoryAsserts;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/** Unit test for {@link DefaultRenderersFactoryTest} with {@link MpeghAudioRenderer}. */
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public final class DefaultRenderersFactoryTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createRenderers_instantiatesMpeghAudioRenderer() {
|
||||||
|
DefaultRenderersFactoryAsserts.assertExtensionRendererCreated(
|
||||||
|
MpeghAudioRenderer.class, C.TRACK_TYPE_AUDIO);
|
||||||
|
}
|
||||||
|
}
|
@ -33,6 +33,10 @@
|
|||||||
-keepclassmembers class androidx.media3.decoder.midi.MidiRenderer {
|
-keepclassmembers class androidx.media3.decoder.midi.MidiRenderer {
|
||||||
<init>(android.content.Context);
|
<init>(android.content.Context);
|
||||||
}
|
}
|
||||||
|
-dontnote androidx.media3.decoder.mpegh.MpeghAudioRenderer
|
||||||
|
-keepclassmembers class androidx.media3.decoder.mpegh.MpeghAudioRenderer {
|
||||||
|
<init>(android.os.Handler, androidx.media3.exoplayer.audio.AudioRendererEventListener, androidx.media3.exoplayer.audio.AudioSink);
|
||||||
|
}
|
||||||
|
|
||||||
# Constructors accessed via reflection in DefaultDownloaderFactory
|
# Constructors accessed via reflection in DefaultDownloaderFactory
|
||||||
-dontnote androidx.media3.exoplayer.dash.offline.DashDownloader
|
-dontnote androidx.media3.exoplayer.dash.offline.DashDownloader
|
||||||
|
@ -567,6 +567,25 @@ public class DefaultRenderersFactory implements RenderersFactory {
|
|||||||
// The extension is present, but instantiation failed.
|
// The extension is present, but instantiation failed.
|
||||||
throw new IllegalStateException("Error instantiating IAMF extension", e);
|
throw new IllegalStateException("Error instantiating IAMF extension", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Full class names used for constructor args so the LINT rule triggers if any of them move.
|
||||||
|
Class<?> clazz = Class.forName("androidx.media3.decoder.mpegh.MpeghAudioRenderer");
|
||||||
|
Constructor<?> constructor =
|
||||||
|
clazz.getConstructor(
|
||||||
|
android.os.Handler.class,
|
||||||
|
androidx.media3.exoplayer.audio.AudioRendererEventListener.class,
|
||||||
|
androidx.media3.exoplayer.audio.AudioSink.class);
|
||||||
|
Renderer renderer =
|
||||||
|
(Renderer) constructor.newInstance(eventHandler, eventListener, audioSink);
|
||||||
|
out.add(extensionRendererIndex++, renderer);
|
||||||
|
Log.i(TAG, "Loaded MpeghAudioRenderer.");
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
// Expected if the app was built without the extension.
|
||||||
|
} catch (Exception e) {
|
||||||
|
// The extension is present, but instantiation failed.
|
||||||
|
throw new IllegalStateException("Error instantiating MPEG-H extension", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user