From 9b5246c555ae291779cf3e2c5369d0563c553ae4 Mon Sep 17 00:00:00 2001 From: Idan Sheinberg Date: Mon, 26 Mar 2018 11:09:12 +0300 Subject: [PATCH] Added HE/v2 AAC support Added fdk-aac license to jars containing shared libraries Version bump --- README.md | 43 ++++------------- common.gradle | 2 +- deploy.gradle | 6 +++ .../aac/encoder/util/AACEncodingProfile.java | 14 ++---- .../sheinbergon/aac/sound/AACFileTypes.java | 5 +- .../sheinbergon/aac/sound/AACFileWriter.java | 10 ++-- .../org/sheinbergon/aac/MediaInfoSupport.java | 18 ++++--- .../aac/sound/AACFileWriterTest.java | 47 ++++++++++--------- 8 files changed, 67 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index d8fa58c..e54781a 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # jna-aac-encoder -[![Build Status](https://travis-ci.org/sheinbergon/jna-aac-encoder.svg?branch=master)](https://travis-ci.org/sheinbergon/jna-aac-encoder) [![Coverage Status](https://coveralls.io/repos/github/sheinbergon/jna-aac-encoder/badge.svg)](https://coveralls.io/github/sheinbergon/jna-aac-encoder) [![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.sheinbergon/jna-aac-encoder/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.sheinbergon/jna-aac-encoder) +[![Build Status](https://travis-ci.org/sheinbergon/jna-aac-encoder.svg?branch=master)](https://travis-ci.org/sheinbergon/jna-aac-encoder) [![Coverage Status](https://coveralls.io/repos/github/sheinbergon/jna-aac-encoder/badge.svg)](https://coveralls.io/github/sheinbergon/jna-aac-encoder) [![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) +![Maven metadata URI](https://img.shields.io/maven-metadata/v/http/central.maven.org/maven2/org/sheinbergon/jna-aac-encoder/maven-metadata.xml.svg) This library provides AAC encoding capabilities for the JVM. It utilizes the [FDK AAC](https://github.com/mstorsjo/fdk-aac) library via JNA in order to do so. - ## License **Important!** While this library uses LGPL-3, please see the [FDK AAC license](NOTICE) for additional information @@ -18,14 +18,14 @@ Artifacts are available on maven central: **_Gradle_** ```groovy -compile 'org.sheinbergon:jna-aac-encoder:0.1.1' +compile 'org.sheinbergon:jna-aac-encoder:0.1.2' ``` **_Maven_** ```xml org.sheinbergon jna-aac-encoder - 0.1.1 + 0.1.2 ``` @@ -36,37 +36,16 @@ need to make sure the shared library is installed as part of the runtime OS envi and accessible to JNA. See [this](https://github.com/java-native-access/jna/blob/master/www/FrequentlyAskedQuestions.md#calling-nativeloadlibrary-causes-an-unsatisfiedlinkerror) link for additional information To make things easier, cross-compiled artifacts (containing the shared library) -for both Windows (64bit) and Linux(64bit) are provided through the use of maven _classifiers_: +for both Windows(64bit) and Linux(64bit) are provided through the use of *_classifiers_*: -#### Windows (64 bit) -**_Gradle_** +##### Windows(64 bit) ```groovy -compile 'org.sheinbergon:jna-aac-encoder:0.1.1:win32-x86-64' -``` -**_Maven_** -```xml - - org.sheinbergon - jna-aac-encoder - 0.1.1 - win32-x86-64 - +compile 'org.sheinbergon:jna-aac-encoder:0.1.2:win32-x86-64' ``` -#### Linux (64 bit) -**_Gradle_** +##### Linux(64 bit) ```groovy -compile 'org.sheinbergon:jna-aac-encoder:0.1.1:linux-x86-64' +compile 'org.sheinbergon:jna-aac-encoder:0.1.2:linux-x86-64' ``` -**_Maven_** -```xml - - org.sheinbergon - jna-aac-encoder - 0.1.1 - linux-x86-64 - -``` - 32bit platform won't be supported for now. OSX/Macos toolchain is a bit trickier, so you'll just have to pre-install the dylib in advance. @@ -90,13 +69,11 @@ the encoding process to fail. Additional restrictions: * A maximum of 6 audio input/output channels -* Only the AAC-LC(**L**ow **C**omplaxity) encoding profile is suuported - +* Only the AAC-LC/HE-AAC/HE-AACv2 encoding profiles are suuported ## Roadmap * Improved lower-level interface (with examples). * Performance tests/comparison (JMH). -* Support for AAC HE & HEv2. * Support additiona WAV audio formats. * Meta-data insertion. * MacOS cross-compiling? diff --git a/common.gradle b/common.gradle index 4023c1c..d24bad5 100644 --- a/common.gradle +++ b/common.gradle @@ -1,5 +1,5 @@ group 'org.sheinbergon' -version '0.1.1' +version '0.1.2' sourceCompatibility = 1.9 diff --git a/deploy.gradle b/deploy.gradle index f9952c6..80a21bb 100644 --- a/deploy.gradle +++ b/deploy.gradle @@ -21,6 +21,9 @@ task win64Jar(type: Jar) { include '*.dll' into target } + from("${projectDir}") { + include 'NOTICE' + } rename '.+(fdk-aac).+(\\.dll)', '$1$2' } @@ -34,6 +37,9 @@ task linux64Jar(type: Jar) { include '*.so' into target } + from("${projectDir}") { + include 'NOTICE' + } } void buildLibrary(source, target) { diff --git a/src/main/java/org/sheinbergon/aac/encoder/util/AACEncodingProfile.java b/src/main/java/org/sheinbergon/aac/encoder/util/AACEncodingProfile.java index 2fb52a6..a7b8fef 100644 --- a/src/main/java/org/sheinbergon/aac/encoder/util/AACEncodingProfile.java +++ b/src/main/java/org/sheinbergon/aac/encoder/util/AACEncodingProfile.java @@ -3,19 +3,15 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; - @Getter @RequiredArgsConstructor public enum AACEncodingProfile { - AAC_LC(2, "LC" ), - HE_AAC(5, "HE" ), - HE_AAC_V2(29, "HE_V2" ), - AAC_LD(23, "LD" ); + AAC_LC(2, "LC"), + HE_AAC(5, "HE-AAC"), + HE_AAC_V2(29, "HE-AACv2"); + //AAC_LD(23, "LD" ) - Not yet supported //AAC_ELD(39) - Not yet supported - + private final int aot; private final String profile; } \ No newline at end of file diff --git a/src/main/java/org/sheinbergon/aac/sound/AACFileTypes.java b/src/main/java/org/sheinbergon/aac/sound/AACFileTypes.java index 1207dbd..5ff957f 100644 --- a/src/main/java/org/sheinbergon/aac/sound/AACFileTypes.java +++ b/src/main/java/org/sheinbergon/aac/sound/AACFileTypes.java @@ -2,9 +2,8 @@ import javax.sound.sampled.AudioFileFormat; -/** - * Currently, only AAC-LC is supported - */ public class AACFileTypes { public final static AudioFileFormat.Type AAC_LC = new AudioFileFormat.Type("AAC_LC", "aac"); + public final static AudioFileFormat.Type AAC_HE = new AudioFileFormat.Type("AAC_HE", "aac"); + public final static AudioFileFormat.Type AAC_HE_V2 = new AudioFileFormat.Type("AAC_HE_V2", "aac"); } \ No newline at end of file diff --git a/src/main/java/org/sheinbergon/aac/sound/AACFileWriter.java b/src/main/java/org/sheinbergon/aac/sound/AACFileWriter.java index 64333dd..efba4e3 100644 --- a/src/main/java/org/sheinbergon/aac/sound/AACFileWriter.java +++ b/src/main/java/org/sheinbergon/aac/sound/AACFileWriter.java @@ -24,12 +24,14 @@ public final class AACFileWriter extends AudioFileWriter { private final static Map FILE_TYPES_TO_ENCODING_PROFILES = Map.of( - AACFileTypes.AAC_LC, AACEncodingProfile.AAC_LC); - + AACFileTypes.AAC_LC, AACEncodingProfile.AAC_LC, + AACFileTypes.AAC_HE, AACEncodingProfile.HE_AAC, + AACFileTypes.AAC_HE_V2, AACEncodingProfile.HE_AAC_V2); @Override public AudioFileFormat.Type[] getAudioFileTypes() { - return Stream.of(AACFileTypes.AAC_LC).toArray(AudioFileFormat.Type[]::new); + return Stream.of(AACFileTypes.AAC_LC, AACFileTypes.AAC_HE, AACFileTypes.AAC_HE_V2) + .toArray(AudioFileFormat.Type[]::new); } @Override @@ -45,7 +47,7 @@ public AudioFileFormat.Type[] getAudioFileTypes(AudioInputStream stream) { } } - private static AACEncodingProfile profileByType(AudioFileFormat.Type type) { + static AACEncodingProfile profileByType(AudioFileFormat.Type type) { return Optional.ofNullable(FILE_TYPES_TO_ENCODING_PROFILES.get(type)) .orElseThrow(() -> new IllegalArgumentException("File type " + type + " is not yet supported")); } diff --git a/src/test/java/org/sheinbergon/aac/MediaInfoSupport.java b/src/test/java/org/sheinbergon/aac/MediaInfoSupport.java index d096455..262fd05 100644 --- a/src/test/java/org/sheinbergon/aac/MediaInfoSupport.java +++ b/src/test/java/org/sheinbergon/aac/MediaInfoSupport.java @@ -9,29 +9,35 @@ public class MediaInfoSupport { + + private final static String VALUE_DELIMITER = "/"; private final static String AAC_MEDIAINFO_FORMAT = "AAC"; private final static int AUDIO_STREAM_INDEX = 0; private final static int EXPECTED_AUDIO_STREAMS_COUNT = 1; private final static MediaInfo MEDIA_INFO = new MediaInfo(); + // Mediainfo sometimes provide multiple value delimited by '/'. This extract the first cell only. private static String getParam(String param) { - return MEDIA_INFO.get(MediaInfo.StreamKind.Audio, AUDIO_STREAM_INDEX, param); + return MEDIA_INFO.get(MediaInfo.StreamKind.Audio, AUDIO_STREAM_INDEX, param) + .split(VALUE_DELIMITER)[0] + .trim(); } // Bitrate is not present as part of AAC's encoding metadata, but is rather deduced from the // stream itself. There for, it cannot be parsed by the mediainfo shared library + // TODO - This should probably changed once metadata is added public static void assertAACOutput(File aac, AudioFormat inputFormat, AACEncodingProfile profile) { boolean opened = false; try { if (opened = MEDIA_INFO.open(aac)) { Assertions.assertEquals(MEDIA_INFO.streamCount(MediaInfo.StreamKind.Audio), EXPECTED_AUDIO_STREAMS_COUNT); - Assertions.assertEquals(Float.valueOf(getParam("SamplingRate" )).floatValue(), inputFormat.getSampleRate()); - Assertions.assertEquals(Integer.valueOf(getParam("Channel(s)" )).intValue(), inputFormat.getChannels()); - Assertions.assertEquals(getParam("Format" ), AAC_MEDIAINFO_FORMAT); - Assertions.assertEquals(getParam("Format_Profile" ), profile.getProfile()); + Assertions.assertEquals(Float.valueOf(getParam("SamplingRate")).floatValue(), inputFormat.getSampleRate()); + Assertions.assertEquals(Integer.valueOf(getParam("Channel(s)")).intValue(), inputFormat.getChannels()); + Assertions.assertEquals(getParam("Format"), AAC_MEDIAINFO_FORMAT); + Assertions.assertEquals(getParam("Format_Profile"), profile.getProfile()); } else { - throw new IllegalStateException("Could not open AAC file " + aac + " via the mediainfo shared library" ); + throw new IllegalStateException("Could not open AAC file " + aac + " via the mediainfo shared library"); } } finally { if (opened) { diff --git a/src/test/java/org/sheinbergon/aac/sound/AACFileWriterTest.java b/src/test/java/org/sheinbergon/aac/sound/AACFileWriterTest.java index 7cb41a2..108c9f6 100644 --- a/src/test/java/org/sheinbergon/aac/sound/AACFileWriterTest.java +++ b/src/test/java/org/sheinbergon/aac/sound/AACFileWriterTest.java @@ -1,6 +1,5 @@ package org.sheinbergon.aac.sound; -import org.apache.commons.lang3.math.NumberUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -15,36 +14,38 @@ import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; +import java.util.stream.Stream; -@DisplayName("Java AudioSystem AAC encoding support" ) +@DisplayName("Java AudioSystem AAC encoding support") public class AACFileWriterTest { private final static AudioFileFormat.Type[] NO_FILE_TYPES = new AudioFileFormat.Type[0]; - private final static AudioFileFormat.Type[] AAC_FILE_TYPES = new AudioFileFormat.Type[]{AACFileTypes.AAC_LC}; + private final static AudioFileFormat.Type[] AAC_FILE_TYPES = new AudioFileFormat.Type[]{AACFileTypes.AAC_LC, AACFileTypes.AAC_HE, AACFileTypes.AAC_HE_V2}; + private final static AACEncodingProfile[] AAC_AUDIO_TYPES = new AACEncodingProfile[]{AACEncodingProfile.AAC_LC, AACEncodingProfile.HE_AAC, AACEncodingProfile.HE_AAC_V2}; private final AACFileWriter writer = new AACFileWriter(); @Test - @DisplayName("Supported audio file types" ) + @DisplayName("Supported audio file types") public void supportedTypes() { Assertions.assertArrayEquals(AAC_FILE_TYPES, writer.getAudioFileTypes()); } @Test - @DisplayName("Unsupported audio input" ) + @DisplayName("Unsupported audio input") public void unsupportedAudioInput() throws UnsupportedAudioFileException, IOException { AudioInputStream input = TestSupport.unsupported24bitWAVAudioInputStream(); Assertions.assertArrayEquals(NO_FILE_TYPES, writer.getAudioFileTypes(input)); } @Test - @DisplayName("Supported audio input" ) + @DisplayName("Supported audio input") public void supportedAudioInput() throws UnsupportedAudioFileException, IOException { AudioInputStream input = TestSupport.supportedWAVAudioInputStream(); Assertions.assertArrayEquals(AAC_FILE_TYPES, writer.getAudioFileTypes(input)); } @Test - @DisplayName("Unsupported audio Encoding" ) + @DisplayName("Unsupported audio encoding") public void unsupportedAudioEncoding() throws UnsupportedAudioFileException, IOException { File aac = TestSupport.tempAACOutputFile(); AudioInputStream input = TestSupport.unsupported24bitWAVAudioInputStream(); @@ -59,26 +60,28 @@ public void unsupportedAudioEncoding() throws UnsupportedAudioFileException, IOE }); } - @Test - @DisplayName("Supported audio output stream encoding" ) - public void supportedAudioOutputStreamEncoding() throws UnsupportedAudioFileException, IOException { - File aac = TestSupport.tempAACOutputFile(); - FileOutputStream output = new FileOutputStream(aac); - AudioInputStream input = TestSupport.supportedWAVAudioInputStream(); - try (input; output) { - writer.write(input, AACFileTypes.AAC_LC, output); - Assertions.assertTrue(aac.length() > 0); - MediaInfoSupport.assertAACOutput(aac, input.getFormat(), AACEncodingProfile.AAC_LC); - } finally { - if (aac.exists()) { - Files.delete(aac.toPath()); + @DisplayName("Supported audio types encoding (LC/HE-AAC/HE-AACv2)") + public void supportedAudioOutputStreamEncoding() { + Assertions.assertAll(Stream.of(AAC_FILE_TYPES).map(audioType -> () -> { + File aac = TestSupport.tempAACOutputFile(); + FileOutputStream output = new FileOutputStream(aac); + AudioInputStream input = TestSupport.supportedWAVAudioInputStream(); + try (input; output) { + writer.write(input, audioType, output); + Assertions.assertTrue(aac.length() > 0); + output.flush(); + MediaInfoSupport.assertAACOutput(aac, input.getFormat(), AACFileWriter.profileByType(audioType)); + } finally { + if (aac.exists()) { + Files.delete(aac.toPath()); + } } - } + })); } @Test - @DisplayName("Supported audio file encoding" ) + @DisplayName("Supported audio file encoding") public void supportedAudioFileEncoding() throws UnsupportedAudioFileException, IOException { File aac = TestSupport.tempAACOutputFile(); AudioInputStream input = TestSupport.supportedWAVAudioInputStream();