diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..6a75c0c2 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,70 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ vavi ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ vavi ] + schedule: + - cron: '27 6 * * 2' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'java' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 00000000..ff46ab9f --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,26 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: Java CI with Maven + +on: + push: + branches: [ vavi ] + pull_request: + branches: [ vavi ] + +jobs: + build: + + runs-on: macos-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 8 + uses: actions/setup-java@v2 + with: + java-version: '8' + distribution: 'adopt' + cache: maven + - name: Build with Maven + run: mvn -B package --file pom.xml diff --git a/README.md b/README.md index b5aa77bd..0a8fcbd4 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,19 @@ -# JAADec -**This is a fork of https://sourceforge.net/projects/jaadec/ containing fixes to make it play nice with other Java Sound Providers.** - -The original project was licensed under Public Domain and as such this fork is also licensed under Public Domain. Use as you like! +[![Release](https://jitpack.io/v/umjammer/JAADec.svg)](https://jitpack.io/#umjammer/JAADec) +[![Actions Status](https://github.com/umjammer/JAADec/workflows/Java%20CI/badge.svg)](https://github.com/umjammer/JAADec/actions) -JAAD is an AAC decoder and MP4 demultiplexer library written completely in Java. It uses no native libraries, is platform-independent and portable. It can read MP4 container from almost every input-stream (files, network sockets etc.) and decode AAC-LC (Low Complexity) and HE-AAC (High Efficiency/AAC+). +# JAADec -This library is available on Bintray's `jcenter` as a Maven/Gradle download.
-https://bintray.com/dv8fromtheworld/maven/JAADec/view -

-For Gradle: +**This is a fork of https://sourceforge.net/projects/jaadec/ +containing fixes to make it play nice with other Java Sound Providers.** -```groovy -repositories { - jcenter() -} +The original project was licensed under Public Domain +and as such this fork is also licensed under Public Domain. Use as you like! -dependencies { - compile 'net.sourceforge.jaadec:jaad:0.8.6' -} -``` -

-For Maven: +JAAD is an AAC decoder and MP4 demultiplexer library written completely in Java. +It uses no native libraries, is platform-independent and portable. +It can read MP4 container from almost every input-stream (files, network sockets etc.) +and decode AAC-LC (Low Complexity) and HE-AAC (High Efficiency/AAC+). -```xml - - - central - bintray - http://jcenter.bintray.com - - +## Install - - - net.sourceforge.jaadec - jaad - 0.8.6 - - -``` + * https://jitpack.io/v/umjammer/JAADec \ No newline at end of file diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 03b79180..00000000 --- a/build.gradle +++ /dev/null @@ -1,32 +0,0 @@ -apply plugin: 'java' - -group 'net.sourceforge.jaadec' -version '0.8.6' - -sourceCompatibility = 1.5 - -jar { - baseName = project.name - manifest { - attributes 'Manifest-Version' : '1.0' - attributes 'Implementation-Version': version - attributes 'Main-Class': "net.sourceforge.jaad.Radio" - } -} - -task sourcesJar(type: Jar) { - classifier = 'sources' - from sourceSets.main.allSource -} - -artifacts { - archives sourcesJar -} - -tasks.withType(Zip) { - task -> - - task.doLast { - ant.checksum file: it.archivePath - } -} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 30d399d8..00000000 Binary files a/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 8fbfc6d6..00000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Fri Feb 19 23:04:48 JST 2016 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-bin.zip diff --git a/gradlew b/gradlew deleted file mode 100644 index 91a7e269..00000000 --- a/gradlew +++ /dev/null @@ -1,164 +0,0 @@ -#!/usr/bin/env bash - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn ( ) { - echo "$*" -} - -die ( ) { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; -esac - -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" - -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat deleted file mode 100644 index 8a0b282a..00000000 --- a/gradlew.bat +++ /dev/null @@ -1,90 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/pom.xml b/pom.xml index a983b57e..d8f81603 100644 --- a/pom.xml +++ b/pom.xml @@ -2,9 +2,11 @@ 4.0.0 + net.sourceforge.jaadec jaad - 0.8.7 + 0.8.8 + JAAD is an AAC decoder and MP4 demultiplexer library written completely in Java. It uses no native libraries, is platform-independent and portable. It can read MP4 container from almost every input-stream (files, network sockets etc.) and decode AAC-LC (Low Complexity) and HE-AAC (High Efficiency/AAC+). https://github.com/DV8FromTheWorld/JAADec @@ -35,4 +37,73 @@ scm:git:git@github.com:DV8FromTheWorld/JAADec.git scm:git:git@github.com:DV8FromTheWorld/JAADec.git + + + + + maven-compiler-plugin + + 8 + 8 + UTF-8 + + + + maven-surefire-plugin + 3.0.0-M5 + + + -Djava.util.logging.config.file=${project.build.testOutputDirectory}/logging.properties + -Dvavi.test=true + + false + always + + + + + + + + jitpack.io + https://jitpack.io + + + + + + + org.junit + junit-bom + 5.8.1 + pom + import + + + + + + + com.github.umjammer + vavi-sound + 1.0.12 + test + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + + diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index afd6501e..00000000 --- a/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -rootProject.name = 'jaad' - diff --git a/src/main/java/net/sourceforge/jaad/aac/Decoder.java b/src/main/java/net/sourceforge/jaad/aac/Decoder.java index 294baaf4..577dafb0 100644 --- a/src/main/java/net/sourceforge/jaad/aac/Decoder.java +++ b/src/main/java/net/sourceforge/jaad/aac/Decoder.java @@ -16,15 +16,17 @@ */ public class Decoder implements Constants { - static { - for(Handler h : LOGGER.getHandlers()) { - LOGGER.removeHandler(h); - } - LOGGER.setLevel(Level.WARNING); - - final ConsoleHandler h = new ConsoleHandler(); - h.setLevel(Level.ALL); - LOGGER.addHandler(h); + static { + if (System.getProperty("java.util.logging.config.file", "").isEmpty()) { + for(Handler h : LOGGER.getHandlers()) { + LOGGER.removeHandler(h); + } + LOGGER.setLevel(Level.WARNING); + + final ConsoleHandler h = new ConsoleHandler(); + h.setLevel(Level.ALL); + LOGGER.addHandler(h); + } } private final DecoderConfig config; private final SyntacticElements syntacticElements; diff --git a/src/main/java/net/sourceforge/jaad/mp4/MP4Container.java b/src/main/java/net/sourceforge/jaad/mp4/MP4Container.java index 489b5d17..694affcb 100644 --- a/src/main/java/net/sourceforge/jaad/mp4/MP4Container.java +++ b/src/main/java/net/sourceforge/jaad/mp4/MP4Container.java @@ -48,15 +48,15 @@ public class MP4Container { static { - Logger log = Logger.getLogger("MP4 API"); - for(Handler h : log.getHandlers()) { - log.removeHandler(h); - } - log.setLevel(Level.WARNING); - - final ConsoleHandler h = new ConsoleHandler(); - h.setLevel(Level.ALL); - log.addHandler(h); +// Logger log = Logger.getLogger("MP4 API"); +// for(Handler h : log.getHandlers()) { +// log.removeHandler(h); +// } +// log.setLevel(Level.WARNING); +// +// final ConsoleHandler h = new ConsoleHandler(); +// h.setLevel(Level.ALL); +// log.addHandler(h); } private final MP4InputStream in; private final List boxes; diff --git a/src/main/java/net/sourceforge/jaad/mp4/boxes/BoxFactory.java b/src/main/java/net/sourceforge/jaad/mp4/boxes/BoxFactory.java index 9bf09f47..9d96e6c3 100644 --- a/src/main/java/net/sourceforge/jaad/mp4/boxes/BoxFactory.java +++ b/src/main/java/net/sourceforge/jaad/mp4/boxes/BoxFactory.java @@ -23,14 +23,16 @@ public class BoxFactory implements BoxTypes { private static final Logger LOGGER = Logger.getLogger("MP4 Boxes"); static { - for(Handler h : LOGGER.getHandlers()) { - LOGGER.removeHandler(h); - } - LOGGER.setLevel(Level.WARNING); + if (System.getProperty("java.util.logging.config.file", "").isEmpty()) { + for (Handler h : LOGGER.getHandlers()) { + LOGGER.removeHandler(h); + } + LOGGER.setLevel(Level.WARNING); - final ConsoleHandler h = new ConsoleHandler(); - h.setLevel(Level.ALL); - LOGGER.addHandler(h); + final ConsoleHandler h = new ConsoleHandler(); + h.setLevel(Level.ALL); + LOGGER.addHandler(h); + } } private static final Map> BOX_CLASSES = new HashMap>(); private static final Map[]> BOX_MULTIPLE_CLASSES = new HashMap[]>(); @@ -340,6 +342,7 @@ public static Box parseBox(Box parent, MP4InputStream in) throws IOException { long type = in.readBytes(4); if(size==1) size = in.readBytes(8); if(type==EXTENDED_TYPE) in.skipBytes(16); + LOGGER.finest("type: " + typeToString(type) + ", " + size); //error protection if(parent!=null) { @@ -347,7 +350,7 @@ public static Box parseBox(Box parent, MP4InputStream in) throws IOException { if(size>parentLeft) throw new IOException("error while decoding box '"+typeToString(type)+"' at offset "+offset+": box too large for parent"); } - Logger.getLogger("MP4 Boxes").finest(typeToString(type)); + LOGGER.finest("type: " + typeToString(type)); final BoxImpl box = forType(type, in.getOffset()); box.setParams(parent, size, type, offset); box.decode(in); diff --git a/src/main/java/net/sourceforge/jaad/mp4/boxes/impl/meta/ITunesMetadataBox.java b/src/main/java/net/sourceforge/jaad/mp4/boxes/impl/meta/ITunesMetadataBox.java index da1a6bc0..768010a7 100644 --- a/src/main/java/net/sourceforge/jaad/mp4/boxes/impl/meta/ITunesMetadataBox.java +++ b/src/main/java/net/sourceforge/jaad/mp4/boxes/impl/meta/ITunesMetadataBox.java @@ -1,13 +1,13 @@ package net.sourceforge.jaad.mp4.boxes.impl.meta; import java.io.IOException; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; + import net.sourceforge.jaad.mp4.MP4InputStream; -import net.sourceforge.jaad.mp4.boxes.BoxTypes; import net.sourceforge.jaad.mp4.boxes.FullBox; /** @@ -101,7 +101,7 @@ public byte[] getData() { */ public String getText() { //first four bytes are padding (zero) - return new String(data, 0, data.length, Charset.forName("UTF-8")); + return new String(data, 0, data.length, StandardCharsets.UTF_8); } /** diff --git a/src/main/java/net/sourceforge/jaad/spi/javasound/AACAudioFileReader.java b/src/main/java/net/sourceforge/jaad/spi/javasound/AACAudioFileReader.java index 81154394..07a6a983 100644 --- a/src/main/java/net/sourceforge/jaad/spi/javasound/AACAudioFileReader.java +++ b/src/main/java/net/sourceforge/jaad/spi/javasound/AACAudioFileReader.java @@ -1,13 +1,16 @@ package net.sourceforge.jaad.spi.javasound; -import net.sourceforge.jaad.adts.ADTSDemultiplexer; -import net.sourceforge.jaad.aac.syntax.BitStream; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; +import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.util.HashMap; +import java.util.List; +import java.util.logging.Logger; + import javax.sound.sampled.AudioFileFormat; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; @@ -15,18 +18,63 @@ import javax.sound.sampled.UnsupportedAudioFileException; import javax.sound.sampled.spi.AudioFileReader; +import net.sourceforge.jaad.aac.AACException; +import net.sourceforge.jaad.aac.Decoder; +import net.sourceforge.jaad.aac.syntax.BitStream; +import net.sourceforge.jaad.adts.ADTSDemultiplexer; +import net.sourceforge.jaad.mp4.MP4Container; +import net.sourceforge.jaad.mp4.api.AudioTrack; +import net.sourceforge.jaad.mp4.api.Movie; +import net.sourceforge.jaad.mp4.api.Track; + public class AACAudioFileReader extends AudioFileReader { - public static final AudioFileFormat.Type AAC = new AudioFileFormat.Type("AAC", "aac"); - public static final AudioFileFormat.Type MP4 = new AudioFileFormat.Type("MP4", "mp4"); - private static final AudioFormat.Encoding AAC_ENCODING = new AudioFormat.Encoding("AAC"); + private static Logger logger = Logger.getLogger(AACAudioFileReader.class.getName()); + + public static final AudioFileFormat.Type AAC = new AudioFileFormat.Type("AAC", "aac"); + public static final AudioFileFormat.Type MP4 = new AudioFileFormat.Type("MP4", "mp4"); + public static final AudioFormat.Encoding AAC_ENCODING = new AudioFormat.Encoding("AAC"); + + private static class LimitedInputStream extends FilterInputStream { + static final String ERROR_MESSAGE_REACHED_TO_LIMIT = "stop reading, prevent form eof"; + protected LimitedInputStream(InputStream in) throws IOException { + super(in); +logger.fine("limit: " + in.available()); + } + private void check(int r) throws IOException { + if (in.available() < r) { +logger.fine("reached to limit"); + throw new IOException(ERROR_MESSAGE_REACHED_TO_LIMIT); + } + } + @Override + public int read() throws IOException { + check(1); + return super.read(); + } + @Override + public int read(byte[] b) throws IOException { + check(b.length); + return super.read(b); + } + @Override + public int read(byte[] b, int off, int len) throws IOException { + check(len); + return super.read(b, off, len); + } + @Override + public long skip(long n) throws IOException { + check((int) n); + return super.skip(n); + } + } @Override public AudioFileFormat getAudioFileFormat(InputStream in) throws UnsupportedAudioFileException, IOException { try { if(!in.markSupported()) in = new BufferedInputStream(in); - in.mark(4); - return getAudioFileFormat(in, AudioSystem.NOT_SPECIFIED); + in.mark(1000); + return getAudioFileFormat(new LimitedInputStream(in), AudioSystem.NOT_SPECIFIED); } finally { in.reset(); @@ -50,90 +98,139 @@ public AudioFileFormat getAudioFileFormat(File file) throws UnsupportedAudioFile try { in = new BufferedInputStream(new FileInputStream(file)); in.mark(1000); - final AudioFileFormat aff = getAudioFileFormat(in, (int) file.length()); - in.reset(); + final AudioFileFormat aff = getAudioFileFormat(new LimitedInputStream(in), (int) file.length()); return aff; } finally { + in.reset(); if(in!=null) in.close(); } } private AudioFileFormat getAudioFileFormat(InputStream in, int mediaLength) throws UnsupportedAudioFileException, IOException { - final byte[] head = new byte[12]; - in.read(head); - boolean canHandle = false; - if(new String(head, 4, 4).equals("ftyp")) - canHandle = true; - //This code is pulled directly from MP3-SPI. - else if ((head[0] == 'R') && (head[1] == 'I') && (head[2] == 'F') && (head[3] == 'F') && (head[8] == 'W') && (head[9] == 'A') && (head[10] == 'V') && (head[11] == 'E')) - { - canHandle = false; //RIFF/WAV stream found - } - else if ((head[0] == '.') && (head[1] == 's') && (head[2] == 'n') && (head[3] == 'd')) - { - canHandle = false; //AU stream found - } - else if ((head[0] == 'F') && (head[1] == 'O') && (head[2] == 'R') && (head[3] == 'M') && (head[8] == 'A') && (head[9] == 'I') && (head[10] == 'F') && (head[11] == 'F')) - { - canHandle = false; //AIFF stream found - } - else if (((head[0] == 'M') | (head[0] == 'm')) && ((head[1] == 'A') | (head[1] == 'a')) && ((head[2] == 'C') | (head[2] == 'c'))) - { - canHandle = false; //APE stream found - } - else if (((head[0] == 'F') | (head[0] == 'f')) && ((head[1] == 'L') | (head[1] == 'l')) && ((head[2] == 'A') | (head[2] == 'a')) && ((head[3] == 'C') | (head[3] == 'c'))) - { - canHandle = false; //FLAC stream found - } - else if (((head[0] == 'I') | (head[0] == 'i')) && ((head[1] == 'C') | (head[1] == 'c')) && ((head[2] == 'Y') | (head[2] == 'y'))) - { - canHandle = false; //Shoutcast / ICE stream ? - } - else if (((head[0] == 'O') | (head[0] == 'o')) && ((head[1] == 'G') | (head[1] == 'g')) && ((head[2] == 'G') | (head[2] == 'g'))) - { - canHandle = false; //Ogg stream ? - } - else { - final BitStream bit = new BitStream(head); - try { + try { + final byte[] head = new byte[12]; + in.read(head); + boolean canHandle = false; + AudioFileFormat.Type type = AAC; + if (new String(head, 4, 4).equals("ftyp")) { + in.reset(); + in.mark(1000); + + // in position should be zero + MP4Container cont = new MP4Container(in); + Movie movie = cont.getMovie(); + List tracks = movie.getTracks(AudioTrack.AudioCodec.AAC); + if (tracks.isEmpty()) throw new UnsupportedAudioFileException("movie does not contain any AAC track"); + Track track = (AudioTrack) tracks.get(0); + new Decoder(track.getDecoderSpecificInfo()); + + canHandle = true; + type = MP4; + //This code is pulled directly from MP3-SPI. + } else if ((head[0] == 'R') && (head[1] == 'I') && (head[2] == 'F') && (head[3] == 'F') && (head[8] == 'W') && (head[9] == 'A') && (head[10] == 'V') && (head[11] == 'E')) + { + canHandle = false; //RIFF/WAV stream found + } + else if ((head[0] == '.') && (head[1] == 's') && (head[2] == 'n') && (head[3] == 'd')) + { + canHandle = false; //AU stream found + } + else if ((head[0] == 'F') && (head[1] == 'O') && (head[2] == 'R') && (head[3] == 'M') && (head[8] == 'A') && (head[9] == 'I') && (head[10] == 'F') && (head[11] == 'F')) + { + canHandle = false; //AIFF stream found + } + else if (((head[0] == 'M') | (head[0] == 'm')) && ((head[1] == 'A') | (head[1] == 'a')) && ((head[2] == 'C') | (head[2] == 'c'))) + { + canHandle = false; //APE stream found + } + else if (((head[0] == 'F') | (head[0] == 'f')) && ((head[1] == 'L') | (head[1] == 'l')) && ((head[2] == 'A') | (head[2] == 'a')) && ((head[3] == 'C') | (head[3] == 'c'))) + { + canHandle = false; //FLAC stream found + } + else if (((head[0] == 'I') | (head[0] == 'i')) && ((head[1] == 'C') | (head[1] == 'c')) && ((head[2] == 'Y') | (head[2] == 'y'))) + { + canHandle = false; //Shoutcast / ICE stream ? + } + else if (((head[0] == 'O') | (head[0] == 'o')) && ((head[1] == 'G') | (head[1] == 'g')) && ((head[2] == 'G') | (head[2] == 'g'))) + { + canHandle = false; //Ogg stream ? + } + else { ADTSDemultiplexer adts = new ADTSDemultiplexer(in); - canHandle = true; - } - catch(Exception e) { - canHandle = false; - } - } - if(canHandle) { - final AudioFormat format = new AudioFormat(AAC_ENCODING, AudioSystem.NOT_SPECIFIED, AudioSystem.NOT_SPECIFIED, mediaLength, AudioSystem.NOT_SPECIFIED, AudioSystem.NOT_SPECIFIED, true); - return new AudioFileFormat(AAC, format, AudioSystem.NOT_SPECIFIED); - } - else throw new UnsupportedAudioFileException(); + new Decoder(adts.getDecoderSpecificInfo()); + + canHandle = true; + } + + if(canHandle) { + AudioFileFormat.Type afft = type; + AudioFormat format = new AudioFormat(AAC_ENCODING, AudioSystem.NOT_SPECIFIED, AudioSystem.NOT_SPECIFIED, AudioSystem.NOT_SPECIFIED, AudioSystem.NOT_SPECIFIED, AudioSystem.NOT_SPECIFIED, true, new HashMap() {{ put("type", afft); }}); +logger.fine("DEFINED: " + type); + return new AudioFileFormat(type, format, mediaLength); + } else { + throw new UnsupportedAudioFileException("no match sequence"); + } + } catch (Exception e) { +logger.fine(e.getClass().getSimpleName() + ": " + e.getMessage()); + throw (UnsupportedAudioFileException) new UnsupportedAudioFileException().initCause(e); + } } //================================================ @Override public AudioInputStream getAudioInputStream(InputStream in) throws UnsupportedAudioFileException, IOException { + boolean needReset = false; try { if(!in.markSupported()) in = new BufferedInputStream(in); - in.mark(1000); - final AudioFileFormat aff = getAudioFileFormat(in, AudioSystem.NOT_SPECIFIED); - in.reset(); - return new MP4AudioInputStream(in, aff.getFormat(), aff.getFrameLength()); + synchronized (this) { + in.mark(1000); +logger.finer("mark: " + in.available()); + needReset = true; + } + final AudioFileFormat aff = getAudioFileFormat(new LimitedInputStream(in), AudioSystem.NOT_SPECIFIED); + synchronized (this) { +logger.finer("before reset: " + in.available()); + in.reset(); +logger.finer("after reset: " + in.available()); + needReset = false; + } + synchronized (this) { + in.mark(in.available()); +logger.finer("mark2: " + in.available()); + needReset = true; + } + // in position should be zero +logger.fine("format: " + aff.getFormat()); + return new AudioInputStream(in, aff.getFormat(), aff.getFrameLength()); } catch(UnsupportedAudioFileException e) { - in.reset(); throw e; } catch(IOException e) { - if (e.getMessage().equals(MP4AudioInputStream.ERROR_MESSAGE_AAC_TRACK_NOT_FOUND)) { - throw new UnsupportedAudioFileException(MP4AudioInputStream.ERROR_MESSAGE_AAC_TRACK_NOT_FOUND); + if (e.getMessage().equals(LimitedInputStream.ERROR_MESSAGE_REACHED_TO_LIMIT)) { +logger.fine(LimitedInputStream.ERROR_MESSAGE_REACHED_TO_LIMIT); + throw new UnsupportedAudioFileException(e.getMessage()); } else if (net.sourceforge.jaad.mp4.MP4Exception.class.isInstance(e)) { +logger.fine(e.toString()); throw new UnsupportedAudioFileException(e.getMessage()); } else { - in.reset(); +logger.info(e.toString()); throw e; } + } catch(Exception e) { +logger.info(e.toString()); + throw e; + } finally { +logger.fine("reset?: " + needReset + ", available: " + in.available()); + try { + if (needReset) { + in.reset(); + } +logger.fine("finally available: " + in.available()); + } catch(IOException e) { +logger.info(e.toString()); + } } } @@ -168,4 +265,6 @@ public AudioInputStream getAudioInputStream(File file) throws UnsupportedAudioFi throw e; } } + + } diff --git a/src/main/java/net/sourceforge/jaad/spi/javasound/AacFormatConversionProvider.java b/src/main/java/net/sourceforge/jaad/spi/javasound/AacFormatConversionProvider.java new file mode 100644 index 00000000..205fe245 --- /dev/null +++ b/src/main/java/net/sourceforge/jaad/spi/javasound/AacFormatConversionProvider.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2022 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package net.sourceforge.jaad.spi.javasound; + +import java.io.IOException; +import java.util.logging.Logger; + +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.UnsupportedAudioFileException; +import javax.sound.sampled.spi.FormatConversionProvider; + + +/** + * AACFormatConversionProvider. + * + * @author Naohide Sano (nsano) + * @version 0.00 220220 nsano initial version
+ */ +public class AacFormatConversionProvider extends FormatConversionProvider { + + private static Logger logger = Logger.getLogger(AacFormatConversionProvider.class.getName()); + + @Override + public AudioFormat.Encoding[] getSourceEncodings() { + return new AudioFormat.Encoding[] { AACAudioFileReader.AAC_ENCODING }; + } + + @Override + public AudioFormat.Encoding[] getTargetEncodings() { + return new AudioFormat.Encoding[] { AudioFormat.Encoding.PCM_SIGNED }; + } + + @Override + public AudioFormat.Encoding[] getTargetEncodings(AudioFormat sourceFormat) { + if (sourceFormat.getEncoding() == AACAudioFileReader.AAC_ENCODING) { + return new AudioFormat.Encoding[] { AudioFormat.Encoding.PCM_SIGNED }; + } else { + return new AudioFormat.Encoding[0]; + } + } + + @Override + public AudioFormat[] getTargetFormats(AudioFormat.Encoding targetEncoding, AudioFormat sourceFormat) { + if (sourceFormat.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED)) { + return new AudioFormat[0]; + } else if (sourceFormat.getEncoding() == AACAudioFileReader.AAC_ENCODING && targetEncoding.equals(AudioFormat.Encoding.PCM_SIGNED)) { + return new AudioFormat[] { + // TODO signed, endian should be free (means add more 3 patterns) + new AudioFormat(sourceFormat.getSampleRate(), + 16, // sample size in bits + sourceFormat.getChannels(), + true, // signed + false) // little endian (for PCM wav) + }; + } else { + return new AudioFormat[0]; + } + } + + @Override + public AudioInputStream getAudioInputStream(AudioFormat.Encoding targetEncoding, AudioInputStream sourceStream) { + try { + if (isConversionSupported(targetEncoding, sourceStream.getFormat())) { + AudioFormat[] formats = getTargetFormats(targetEncoding, sourceStream.getFormat()); + if (formats != null && formats.length > 0) { + AudioFormat sourceFormat = sourceStream.getFormat(); + AudioFormat targetFormat = formats[0]; + if (sourceFormat.equals(targetFormat)) { +logger.info("same1: " + sourceFormat); + return sourceStream; + } else if (sourceFormat.getEncoding() == AACAudioFileReader.AAC_ENCODING && targetFormat.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED)) { + Object type = sourceFormat.getProperty("type"); +logger.info("convert1: " + type); + if (type != null && AudioFileFormat.Type.class.cast(type) == AACAudioFileReader.MP4) { + return new MP4AudioInputStream(sourceStream, targetFormat, AudioSystem.NOT_SPECIFIED); + } else { + return new AACAudioInputStream(sourceStream, targetFormat, AudioSystem.NOT_SPECIFIED); + } + } else if (sourceFormat.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED) && targetFormat.getEncoding() == AACAudioFileReader.AAC_ENCODING) { + throw new IllegalArgumentException("unable to convert " + sourceFormat.toString() + " to " + targetFormat.toString()); + } else { + throw new IllegalArgumentException("unable to convert " + sourceFormat.toString() + " to " + targetFormat.toString()); + } + } else { + throw new IllegalArgumentException("target format not found"); + } + } else { + throw new IllegalArgumentException("conversion not supported"); + } + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public AudioInputStream getAudioInputStream(AudioFormat targetFormat, AudioInputStream sourceStream) { + try { + if (isConversionSupported(targetFormat, sourceStream.getFormat())) { + AudioFormat[] formats = getTargetFormats(targetFormat.getEncoding(), sourceStream.getFormat()); + if (formats != null && formats.length > 0) { + AudioFormat sourceFormat = sourceStream.getFormat(); + if (sourceFormat.equals(targetFormat)) { +logger.info("same2: " + sourceFormat); + return sourceStream; + } else if (sourceFormat.getEncoding() == AACAudioFileReader.AAC_ENCODING && + targetFormat.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED)) { + Object type = sourceFormat.getProperty("type"); +logger.info("convert2: " + type); + if (type != null && AudioFileFormat.Type.class.cast(type) == AACAudioFileReader.MP4) { + return new MP4AudioInputStream(sourceStream, targetFormat, AudioSystem.NOT_SPECIFIED); + } else { + return new AACAudioInputStream(sourceStream, targetFormat, AudioSystem.NOT_SPECIFIED); + } + } else if (sourceFormat.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED) && targetFormat.getEncoding() == AACAudioFileReader.AAC_ENCODING) { + throw new IllegalArgumentException("unable to convert " + sourceFormat.toString() + " to " + targetFormat.toString()); + } else { + throw new IllegalArgumentException("unable to convert " + sourceFormat.toString() + " to " + targetFormat.toString()); + } + } else { + throw new IllegalArgumentException("target format not found"); + } + } else { + throw new IllegalArgumentException("conversion not supported"); + } + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } +} + +/* */ diff --git a/src/main/java/net/sourceforge/jaad/spi/javasound/MP4AudioInputStream.java b/src/main/java/net/sourceforge/jaad/spi/javasound/MP4AudioInputStream.java index 964d77df..c03e9f1c 100644 --- a/src/main/java/net/sourceforge/jaad/spi/javasound/MP4AudioInputStream.java +++ b/src/main/java/net/sourceforge/jaad/spi/javasound/MP4AudioInputStream.java @@ -20,14 +20,15 @@ class MP4AudioInputStream extends AsynchronousAudioInputStream { private AudioFormat audioFormat; private byte[] saved; - static final String ERROR_MESSAGE_AAC_TRACK_NOT_FOUND = "movie does not contain any AAC track"; - + /** + * @throws IllegalArgumentException movie does not contain any AAC track + */ MP4AudioInputStream(InputStream in, AudioFormat format, long length) throws IOException { super(in, format, length); final MP4Container cont = new MP4Container(in); final Movie movie = cont.getMovie(); final List tracks = movie.getTracks(AudioTrack.AudioCodec.AAC); - if(tracks.isEmpty()) throw new IOException(ERROR_MESSAGE_AAC_TRACK_NOT_FOUND); + if(tracks.isEmpty()) throw new IllegalArgumentException("movie does not contain any AAC track"); track = (AudioTrack) tracks.get(0); decoder = new Decoder(track.getDecoderSpecificInfo()); diff --git a/src/main/java/net/sourceforge/jaad/spi/javasound/PcmAudioInputStream.java b/src/main/java/net/sourceforge/jaad/spi/javasound/PcmAudioInputStream.java new file mode 100644 index 00000000..87fb4892 --- /dev/null +++ b/src/main/java/net/sourceforge/jaad/spi/javasound/PcmAudioInputStream.java @@ -0,0 +1,63 @@ +package net.sourceforge.jaad.spi.javasound; + +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Logger; + +import javax.sound.sampled.AudioFormat; + +import net.sourceforge.jaad.aac.Decoder; +import net.sourceforge.jaad.aac.SampleBuffer; +import net.sourceforge.jaad.adts.ADTSDemultiplexer; + +class PcmAudioInputStream extends AsynchronousAudioInputStream { + + private static Logger logger = Logger.getLogger(PcmAudioInputStream.class.getName()); + + private final ADTSDemultiplexer adts; + private final Decoder decoder; + private final SampleBuffer sampleBuffer; + private AudioFormat audioFormat = null; + private byte[] saved; + + PcmAudioInputStream(InputStream in, AudioFormat format, long length) throws IOException { + super(in, format, length); + adts = new ADTSDemultiplexer(in); + decoder = new Decoder(adts.getDecoderSpecificInfo()); + sampleBuffer = new SampleBuffer(); + } + + @Override + public AudioFormat getFormat() { + if(audioFormat==null) { + //read first frame + try { + decoder.decodeFrame(adts.readNextFrame(), sampleBuffer); + audioFormat = new AudioFormat(sampleBuffer.getSampleRate(), sampleBuffer.getBitsPerSample(), sampleBuffer.getChannels(), true, true); +logger.info("format: " + audioFormat); + saved = sampleBuffer.getData(); + } + catch(IOException e) { + return null; + } + } + return audioFormat; + } + + public void execute() { + try { + if(saved==null) { + decoder.decodeFrame(adts.readNextFrame(), sampleBuffer); + buffer.write(sampleBuffer.getData()); + } + else { + buffer.write(saved); + saved = null; + } + } + catch(IOException e) { + buffer.close(); + return; + } + } +} diff --git a/src/main/resources/META-INF/services/javax.sound.sampled.spi.FormatConversionProvider b/src/main/resources/META-INF/services/javax.sound.sampled.spi.FormatConversionProvider new file mode 100644 index 00000000..238c3814 --- /dev/null +++ b/src/main/resources/META-INF/services/javax.sound.sampled.spi.FormatConversionProvider @@ -0,0 +1 @@ +net.sourceforge.jaad.spi.javasound.AacFormatConversionProvider diff --git a/src/test/java/Test1.java b/src/test/java/Test1.java new file mode 100644 index 00000000..43585d19 --- /dev/null +++ b/src/test/java/Test1.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2022 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +import java.io.EOFException; +import java.io.InputStream; +import java.util.List; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.SourceDataLine; + +import org.junit.jupiter.api.Test; + +import vavi.util.Debug; + +import static vavi.sound.SoundUtil.volume; +import static vavix.util.DelayedWorker.later; + +import net.sourceforge.jaad.aac.Decoder; +import net.sourceforge.jaad.aac.SampleBuffer; +import net.sourceforge.jaad.adts.ADTSDemultiplexer; +import net.sourceforge.jaad.mp4.MP4Container; +import net.sourceforge.jaad.mp4.api.AudioTrack; +import net.sourceforge.jaad.mp4.api.Frame; +import net.sourceforge.jaad.mp4.api.Movie; +import net.sourceforge.jaad.mp4.api.Track; + + +/** + * test basic functions. + * + * @author Naohide Sano (umjammer) + * @version 0.00 2022/02/19 umjammer initial version
+ */ +public class Test1 { + + static long time; + + static { + time = Boolean.valueOf(System.getProperty("vavi.test", "false")) ? 10 * 1000 : 1000 * 1000; + } + + @Test + void decodeMP4() throws Exception { + InputStream in = Test1.class.getResourceAsStream("/test.m4a"); + SourceDataLine line = null; + // create container + MP4Container cont = new MP4Container(in); + Movie movie = cont.getMovie(); + // find AAC track + List tracks = movie.getTracks(AudioTrack.AudioCodec.AAC); + AudioTrack track = (AudioTrack) tracks.get(0); + + // create audio format + AudioFormat aufmt = new AudioFormat( + track.getSampleRate(), track.getSampleSize(), track.getChannelCount(), true, true); + line = AudioSystem.getSourceDataLine(aufmt); + line.open(); + volume(line, .2f); + line.start(); + + // create AAC decoder + final Decoder dec = new Decoder(track.getDecoderSpecificInfo()); + + // decode + Frame frame; + final SampleBuffer buf = new SampleBuffer(); + while (track.hasMoreFrames() && !later(time).come()) { + frame = track.readNextFrame(); + dec.decodeFrame(frame.getData(), buf); + byte[] b = buf.getData(); + line.write(b, 0, b.length); + } + } + + @Test + void decodeAAC() throws Exception { + InputStream in = Test1.class.getResourceAsStream("/test.aac"); + SourceDataLine line = null; + ADTSDemultiplexer adts = new ADTSDemultiplexer(in); + Decoder dec = new Decoder(adts.getDecoderSpecificInfo()); + SampleBuffer buf = new SampleBuffer(); + while (!later(time).come()) { + try { + byte[] b = adts.readNextFrame(); + dec.decodeFrame(b, buf); + + if (line == null) { + // need to read the first frame for determine rate, ch etc. + AudioFormat aufmt = new AudioFormat( + buf.getSampleRate(), buf.getBitsPerSample(), buf.getChannels(), true, true); +Debug.println("IN: " + aufmt); + line = AudioSystem.getSourceDataLine(aufmt); + line.open(); + volume(line, .2f); + line.start(); + } + b = buf.getData(); + line.write(b, 0, b.length); + } catch (EOFException e) { + break; + } + } + } +} + +/* */ diff --git a/src/test/java/net/sourceforge/jaad/spi/javasound/AacFormatConversionProviderTest.java b/src/test/java/net/sourceforge/jaad/spi/javasound/AacFormatConversionProviderTest.java new file mode 100644 index 00000000..c5b2a4c2 --- /dev/null +++ b/src/test/java/net/sourceforge/jaad/spi/javasound/AacFormatConversionProviderTest.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2022 by Naohide Sano, All rights reserved. + * + * Programmed by Naohide Sano + */ + +package net.sourceforge.jaad.spi.javasound; + +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.SourceDataLine; +import javax.sound.sampled.UnsupportedAudioFileException; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import vavi.util.Debug; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static vavi.sound.SoundUtil.volume; +import static vavix.util.DelayedWorker.later; + +class AacFormatConversionProviderTest { + + static final String inFile1 = "/test.aac"; + static final String inFile = "/test.mid"; + static final String inFile2 = "/test.m4a"; + static final String inFile4 = "/alac.m4a"; + static final String inFile3 = "/test.caf"; + + static long time; + + @BeforeAll + static void setup() throws Exception { + time = Boolean.valueOf(System.getProperty("vavi.test", "false")) ? 10 * 1000 : 1000 * 1000; + } + + @Test + public void test1() throws Exception { + + Path path = Paths.get("src/test/resources", inFile); + + assertThrows(UnsupportedAudioFileException.class, () -> { + new AACAudioFileReader().getAudioInputStream(new BufferedInputStream(new FileInputStream(path.toFile()))); + }); + + assertThrows(UnsupportedAudioFileException.class, () -> { + new AACAudioFileReader().getAudioInputStream(path.toFile()); + }); + + assertThrows(UnsupportedAudioFileException.class, () -> { + new AACAudioFileReader().getAudioInputStream(path.toUri().toURL()); + }); + } + + @Test + public void test11() throws Exception { + + Path path = Paths.get(AacFormatConversionProviderTest.class.getResource(inFile).toURI()); + + assertThrows(UnsupportedAudioFileException.class, () -> { + new AACAudioFileReader().getAudioInputStream(new BufferedInputStream(Files.newInputStream(path))); + }); + } + + @Test + @DisplayName("movie does not contain any AAC track") + public void test12() throws Exception { + + Path path = Paths.get(AacFormatConversionProviderTest.class.getResource(inFile4).toURI()); + + assertThrows(UnsupportedAudioFileException.class, () -> { + new AACAudioFileReader().getAudioInputStream(new BufferedInputStream(Files.newInputStream(path))); + }); + } + + @Test + @DisplayName("xxx") + public void test13() throws Exception { + + Path path = Paths.get(AacFormatConversionProviderTest.class.getResource(inFile3).toURI()); + + assertThrows(UnsupportedAudioFileException.class, () -> { + new AACAudioFileReader().getAudioInputStream(new BufferedInputStream(Files.newInputStream(path))); + }); + } + + @Test + @DisplayName("aac -> pcm") + public void test2() throws Exception { + + // + Path path = Paths.get(AacFormatConversionProviderTest.class.getResource(inFile1).toURI()); +Debug.println("file: " + path); + AudioInputStream aacAis = AudioSystem.getAudioInputStream(path.toFile()); +Debug.println("INS: " + aacAis); + AudioFormat inAudioFormat = aacAis.getFormat(); +Debug.println("INF: " + inAudioFormat); + AudioFormat outAudioFormat = new AudioFormat( + AudioSystem.NOT_SPECIFIED, + 16, + AudioSystem.NOT_SPECIFIED, + true, + false); + + assertTrue(AudioSystem.isConversionSupported(outAudioFormat, inAudioFormat)); + + AudioInputStream pcmAis = AudioSystem.getAudioInputStream(outAudioFormat, aacAis); +Debug.println("OUTS: " + pcmAis); +Debug.println("OUT: " + pcmAis.getFormat()); + DataLine.Info info = new DataLine.Info(SourceDataLine.class, pcmAis.getFormat()); + SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info); + line.open(pcmAis.getFormat()); + volume(line, .05d); + line.start(); + + + byte[] buf = new byte[1024]; + while (!later(time).come()) { + int r = pcmAis.read(buf, 0, 1024); + if (r < 0) { + break; + } + line.write(buf, 0, r); + } + line.drain(); + line.stop(); + line.close(); + } + + @Test + @DisplayName("mp4 -> pcm") + public void test3() throws Exception { + + // + Path path = Paths.get(AacFormatConversionProviderTest.class.getResource(inFile2).toURI()); +Debug.println("file: " + path); + AudioInputStream aacAis = AudioSystem.getAudioInputStream(path.toFile()); +Debug.println("INS: " + aacAis); + AudioFormat inAudioFormat = aacAis.getFormat(); +Debug.println("INF: " + inAudioFormat); + AudioFormat outAudioFormat = new AudioFormat( + inAudioFormat.getSampleRate(), + 16, + inAudioFormat.getChannels(), + true, + false); + + assertTrue(AudioSystem.isConversionSupported(outAudioFormat, inAudioFormat)); + + AudioInputStream pcmAis = AudioSystem.getAudioInputStream(outAudioFormat, aacAis); +Debug.println("OUTS: " + pcmAis); +Debug.println("OUT: " + pcmAis.getFormat()); + DataLine.Info info = new DataLine.Info(SourceDataLine.class, pcmAis.getFormat()); + SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info); + line.open(pcmAis.getFormat()); + volume(line, .05d); + line.start(); + + + byte[] buf = new byte[1024]; + while (!later(time).come()) { + int r = pcmAis.read(buf, 0, 1024); + if (r < 0) { + break; + } + line.write(buf, 0, r); + } + line.drain(); + line.stop(); + line.close(); + } +} + +/* */ diff --git a/src/test/resources/alac.m4a b/src/test/resources/alac.m4a new file mode 100644 index 00000000..0d21f07d Binary files /dev/null and b/src/test/resources/alac.m4a differ diff --git a/src/test/resources/logging.properties b/src/test/resources/logging.properties new file mode 100644 index 00000000..5999cda2 --- /dev/null +++ b/src/test/resources/logging.properties @@ -0,0 +1,19 @@ +handlers=java.util.logging.ConsoleHandler +.level=ALL +# Limit the message that are printed on the console to INFO and above. +java.util.logging.ConsoleHandler.level=FINER +#java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +# %1:date/time %2:className and methodName %3logName %4:level %5:message %6:errormessage +#java.util.logging.SimpleFormatter.format=%1$tY/%1$tm/%1$td %1$tH:%1$tM:%1$tS.%1$tL [%4$s] %5$s %6$s%n + +# HttpUrlConnection +#java.util.logging.ConsoleHandler.level=ALL +#sun.net.www.protocol.http.HttpURLConnection.level=ALL +org.uva.emulation.level=WARNING +org.junit.level=WARNING +org.rococoa.level=INFO +com.sun.jna.level=WARNING +javax.management.level=WARNING + +java.util.logging.ConsoleHandler.formatter=vavi.util.logging.VaviFormatter +#java.util.logging.ConsoleHandler.formatter=vavi.util.logging.BetterFormatter diff --git a/src/test/resources/test.aac b/src/test/resources/test.aac new file mode 100644 index 00000000..862c4946 Binary files /dev/null and b/src/test/resources/test.aac differ diff --git a/src/test/resources/test.caf b/src/test/resources/test.caf new file mode 100644 index 00000000..2f6c0254 Binary files /dev/null and b/src/test/resources/test.caf differ diff --git a/src/test/resources/test.m4a b/src/test/resources/test.m4a new file mode 100644 index 00000000..9d32b322 Binary files /dev/null and b/src/test/resources/test.m4a differ diff --git a/src/test/resources/test.mid b/src/test/resources/test.mid new file mode 100644 index 00000000..9a8fb31c Binary files /dev/null and b/src/test/resources/test.mid differ