Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/ClusterM/android-speech-recognition.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexey 'Cluster' Avdyukhin <clusterrr@clusterrr.com>2014-07-21 00:03:58 +0400
committerAlexey 'Cluster' Avdyukhin <clusterrr@clusterrr.com>2014-07-21 00:03:58 +0400
commit32f8528fa1e49322980202ffac8e43fcc51476da (patch)
tree7716e69ae4568495676f0c105cb5891dff3f719c
First commit
-rw-r--r--.classpath9
-rw-r--r--.project33
-rw-r--r--AndroidManifest.xml29
-rw-r--r--README.md4
-rw-r--r--ic_launcher-web.pngbin0 -> 51394 bytes
-rw-r--r--proguard-project.txt20
-rw-r--r--project.properties14
-rw-r--r--res/drawable-hdpi/ic_launcher.pngbin0 -> 7658 bytes
-rw-r--r--res/drawable-mdpi/ic_launcher.pngbin0 -> 3777 bytes
-rw-r--r--res/drawable-xhdpi/ic_launcher.pngbin0 -> 12516 bytes
-rw-r--r--res/drawable-xxhdpi/ic_launcher.pngbin0 -> 24777 bytes
-rw-r--r--res/layout/activity_main.xml19
-rw-r--r--res/values/strings.xml9
-rw-r--r--src/com/Cluster/SpeechRecognizer/SpeechRecognizer.java430
-rw-r--r--src/com/example/speechtests/MainActivity.java87
-rw-r--r--src/javaFlacEncoder/ArrayRecycler.java60
-rw-r--r--src/javaFlacEncoder/BlockEncodeRequest.java99
-rw-r--r--src/javaFlacEncoder/BlockThreadManager.java373
-rw-r--r--src/javaFlacEncoder/COPYING502
-rw-r--r--src/javaFlacEncoder/CRC16.java84
-rw-r--r--src/javaFlacEncoder/CRC8.java142
-rw-r--r--src/javaFlacEncoder/ChannelData.java52
-rw-r--r--src/javaFlacEncoder/EncodedElement.java998
-rw-r--r--src/javaFlacEncoder/EncodedElement_32.java419
-rw-r--r--src/javaFlacEncoder/EncodingConfiguration.java196
-rw-r--r--src/javaFlacEncoder/FLACEncoder.java698
-rw-r--r--src/javaFlacEncoder/FLACFileOutputStream.java158
-rw-r--r--src/javaFlacEncoder/FLACOutputStream.java101
-rw-r--r--src/javaFlacEncoder/FLACStreamController.java247
-rw-r--r--src/javaFlacEncoder/FLACStreamIdentifier.java49
-rw-r--r--src/javaFlacEncoder/FLACStreamOutputStream.java114
-rw-r--r--src/javaFlacEncoder/FLAC_MD5.java90
-rw-r--r--src/javaFlacEncoder/Frame.java833
-rw-r--r--src/javaFlacEncoder/FrameHeader.java274
-rw-r--r--src/javaFlacEncoder/FrameThread.java86
-rw-r--r--src/javaFlacEncoder/LPC.java226
-rw-r--r--src/javaFlacEncoder/MetadataBlockHeader.java97
-rw-r--r--src/javaFlacEncoder/MetadataBlockStreamInfo.java105
-rw-r--r--src/javaFlacEncoder/README50
-rw-r--r--src/javaFlacEncoder/RiceEncoder.java227
-rw-r--r--src/javaFlacEncoder/StreamConfiguration.java286
-rw-r--r--src/javaFlacEncoder/Subframe.java122
-rw-r--r--src/javaFlacEncoder/Subframe_Constant.java97
-rw-r--r--src/javaFlacEncoder/Subframe_Fixed.java339
-rw-r--r--src/javaFlacEncoder/Subframe_LPC.java497
-rw-r--r--src/javaFlacEncoder/Subframe_Verbatim.java89
-rw-r--r--src/javaFlacEncoder/UTF8Modified.java131
-rw-r--r--src/javaFlacEncoder/package.html41
48 files changed, 8536 insertions, 0 deletions
diff --git a/.classpath b/.classpath
new file mode 100644
index 0000000..7bc01d9
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="src" path="gen"/>
+ <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+ <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+ <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
+ <classpathentry kind="output" path="bin/classes"/>
+</classpath>
diff --git a/.project b/.project
new file mode 100644
index 0000000..6127756
--- /dev/null
+++ b/.project
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>SpeechTests</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.ApkBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..922cda5
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.voicetests"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk
+ android:minSdkVersion="8"
+ android:targetSdkVersion="20" />
+
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+ <uses-permission android:name="android.permission.INTERNET" />
+
+ <application
+ android:allowBackup="true"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <activity
+ android:name="com.example.speechtests.MainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest> \ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f2dde87
--- /dev/null
+++ b/README.md
@@ -0,0 +1,4 @@
+android-speech-recognition
+==========================
+
+Continuous speech recognition for Android demo
diff --git a/ic_launcher-web.png b/ic_launcher-web.png
new file mode 100644
index 0000000..a18cbb4
--- /dev/null
+++ b/ic_launcher-web.png
Binary files differ
diff --git a/proguard-project.txt b/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/project.properties b/project.properties
new file mode 100644
index 0000000..3409f08
--- /dev/null
+++ b/project.properties
@@ -0,0 +1,14 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-20
diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..288b665
--- /dev/null
+++ b/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..6ae570b
--- /dev/null
+++ b/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..d4fb7cd
--- /dev/null
+++ b/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_launcher.png b/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..85a6081
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/res/layout/activity_main.xml b/res/layout/activity_main.xml
new file mode 100644
index 0000000..0a14b3d
--- /dev/null
+++ b/res/layout/activity_main.xml
@@ -0,0 +1,19 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/calibration" />
+
+ <TextView
+ android:id="@+id/detect_level"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="20dp" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..53a6a82
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">Continuous voice recognition demo</string>
+ <string name="calibration">Tss! Keep quiet and wait few seconds...</string>
+ <string name="speak">Speak to me!</string>
+ <string name="action_settings">Settings</string>
+
+</resources>
diff --git a/src/com/Cluster/SpeechRecognizer/SpeechRecognizer.java b/src/com/Cluster/SpeechRecognizer/SpeechRecognizer.java
new file mode 100644
index 0000000..69e7967
--- /dev/null
+++ b/src/com/Cluster/SpeechRecognizer/SpeechRecognizer.java
@@ -0,0 +1,430 @@
+package com.Cluster.SpeechRecognizer;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javaFlacEncoder.FLACEncoder;
+import javaFlacEncoder.FLACStreamOutputStream;
+import javaFlacEncoder.StreamConfiguration;
+
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.MediaRecorder;
+import android.util.Log;
+
+public class SpeechRecognizer
+{
+ final String API_KEY = "YOUR_API_KEY";
+ final String TAG = "SpeechRecognizer";
+ // Частота дискретизации
+ final int SAMPLE_RATE = 8000;
+ // Длительность анализируемых сэмплов
+ final int TIMER_INTERVAL = 100;
+ // Время, в течении которого анализируется громкость
+ final int MAX_ANALYZE_LENGTH = 3000;
+ // Соотношение громкости речи к громкости фона
+ final float VOLUME_RATIO = 1.25f;
+ // Максимальное кол-во ошибок, после которого увеличивается амплитуда
+ // срабатывания
+ final float MAX_ERROR_COUNT = 3;
+ // Если в течении этого времени нет ошибок, сбрасываем счётчик ошибок
+ final int MAX_NO_ERROR_TIME = 10000;
+
+ // Слушатель
+ VoiceRecognizedListener voiceRecognizedListener = null;
+ // Какой процент значений громкости должен превышать уровень определения
+ float detectRatio = 0.25f;
+ // Сколько нужно молчать перед распознаванием фразы в миллисекундах
+ int maxSilenceLength = 500;
+ // Минимальная длина фразы в миллисекундах
+ int minRecordLength = 500;
+ // Максимальная длина фразы в миллисекундах
+ int maxRecordLength = 5000;
+ // Языка
+ String language = "en-US";
+ // Максимальное кол-во результатов
+ int maxResults = 5;
+
+ // Громкость, с которой должен звучать голос
+ int detectLevel = 32767;
+ // Пишем ли звук в данных момент
+ boolean recording = false;
+ // Длина записи
+ int recordLength = 0;
+ // Длина тишины
+ int silenceLength = 0;
+ // Сколько звука мы проанализировали на громкость
+ int analyzeLength = 0;
+ // Количество ошибок распознавания подряд
+ int errorCount = 0;
+ // Время, в течении которого нет ошибок
+ int noErrorTimer = 0;
+ // Максимальная громкость фона, когда человек молчит
+ int maxSilenceLevel = 0;
+ // Максимальная громкость
+ int maxLevel = 0;
+
+ AudioRecord aRecorder;
+ byte[] buffer;
+ ByteArrayOutputStream record = new ByteArrayOutputStream();
+
+ public float getDetectRatio()
+ {
+ return detectRatio;
+ }
+
+ public void setDetectRatio(float detectRatio)
+ {
+ this.detectRatio = detectRatio;
+ }
+
+ public int getMaxSilenceLength()
+ {
+ return maxSilenceLength;
+ }
+
+ public void setMaxSilenceLength(int maxSilenceLength)
+ {
+ this.maxSilenceLength = maxSilenceLength;
+ }
+
+ public int getMinRecordLength()
+ {
+ return minRecordLength;
+ }
+
+ public void setMinRecordLength(int minRecordLength)
+ {
+ this.minRecordLength = minRecordLength;
+ }
+
+ public int getMaxRecordLength()
+ {
+ return maxRecordLength;
+ }
+
+ public void setMaxRecordLength(int maxRecordLength)
+ {
+ this.maxRecordLength = maxRecordLength;
+ }
+
+ public String getLanguage()
+ {
+ return language;
+ }
+
+ public void setLanguage(String language)
+ {
+ this.language = language;
+ }
+
+ public int getMaxResults()
+ {
+ return maxResults;
+ }
+
+ public void setMaxResults(int maxResults)
+ {
+ this.maxResults = maxResults;
+ }
+
+ public void setVoiceRecognizedListener(VoiceRecognizedListener voiceRecognizedListener)
+ {
+ this.voiceRecognizedListener = voiceRecognizedListener;
+ }
+
+ public int getDetectLevel()
+ {
+ return detectLevel;
+ }
+
+ public void start() throws Exception
+ {
+ if (aRecorder != null)
+ return; // Уже запущены
+ // Параметры захвата
+ int audioSource = MediaRecorder.AudioSource.MIC;
+ final int channelConfig = AudioFormat.CHANNEL_IN_MONO;
+ int nChannels = 1;
+ int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
+ int bSamples = 16;
+ int framePeriod = SAMPLE_RATE * TIMER_INTERVAL / 1000;
+ int bufferSize = framePeriod * 2 * bSamples * nChannels / 8;
+ if (bufferSize < AudioRecord.getMinBufferSize(SAMPLE_RATE, channelConfig, audioFormat))
+ {
+ bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, channelConfig, audioFormat);
+ Log.w(TAG, "Increasing buffer size to " + Integer.toString(bufferSize));
+ }
+
+ // Запускаем захват аудио
+ aRecorder = new AudioRecord(audioSource, SAMPLE_RATE, channelConfig, audioFormat, bufferSize);
+ if (aRecorder.getState() != AudioRecord.STATE_INITIALIZED)
+ {
+ Log.e(TAG, "AudioRecord initialization failed");
+ aRecorder = null;
+ throw new Exception("AudioRecord initialization failed");
+ }
+ aRecorder.setRecordPositionUpdateListener(updateListener);
+ aRecorder.setPositionNotificationPeriod(framePeriod);
+ buffer = new byte[framePeriod * bSamples / 8 * nChannels];
+ aRecorder.startRecording();
+ // Нужно для некоторых старых версий Android
+ aRecorder.read(buffer, 0, buffer.length);
+ Log.i(TAG, "Started");
+ }
+
+ public void stop()
+ {
+ if (aRecorder != null)
+ {
+ aRecorder.stop();
+ aRecorder = null;
+ Log.i(TAG, "Stopped");
+ }
+ }
+
+ private AudioRecord.OnRecordPositionUpdateListener updateListener = new AudioRecord.OnRecordPositionUpdateListener()
+ {
+ @Override
+ public void onPeriodicNotification(AudioRecord recorder)
+ {
+ if (aRecorder == null)
+ return; // Stopped
+ // Читаем данные
+ int len = aRecorder.read(buffer, 0, buffer.length);
+ int maxAmplitude = 0;
+ int detects = 0;
+ // Проходимся вдоль записанного сэмпла
+ for (int p = 0; p < len - 1; p += 2)
+ {
+ // WTF. Конвертируем little-endian signed bytes в int
+ int level = buffer[p + 1] * 256 + ((buffer[p] >= 0) ? buffer[p] : (256 + buffer[p]));
+ int amplitude = Math.abs(level);
+ if (amplitude > maxAmplitude)
+ maxAmplitude = amplitude;
+ if (amplitude > detectLevel)
+ detects++;
+ }
+
+ // Анализ фоновой громоксти
+ // Запоминаем максимальную среднюю громкость
+ if (maxAmplitude > maxLevel)
+ {
+ maxLevel = maxAmplitude;
+ }
+ // Если достаточно тихо, то смотрим - не понизить ли громкость
+ // срабатывания?
+ if (detects == 0)
+ {
+ if (analyzeLength >= MAX_ANALYZE_LENGTH)
+ {
+ if (maxSilenceLevel * VOLUME_RATIO < detectLevel)
+ {
+ Log.i(TAG, "Decreasing detect level from " + detectLevel + " to " + maxSilenceLevel * VOLUME_RATIO);
+ detectLevel = (int) (maxSilenceLevel * VOLUME_RATIO);
+ }
+ analyzeLength = 0;
+ maxSilenceLevel = 0;
+ } else
+ {
+ if (maxAmplitude > maxSilenceLevel)
+ maxSilenceLevel = maxAmplitude;
+ analyzeLength += TIMER_INTERVAL;
+ }
+ }
+
+ // Log.d(TAG, "Data: " + len + ", max: " + maxAmplitude +
+ // ", detects: " + detects);
+
+ // Достигнуто ли нужное количество превышений амплитуды
+ // срабатывания?
+ boolean voiceDetected = (detects > len / 2 * detectRatio);
+
+ if (!recording) // Если запись не идёт...
+ {
+ if (voiceDetected) // И мы обнаружили вспышку амплитуды
+ {
+ // Запускаем запись
+ recording = true;
+ recordLength = TIMER_INTERVAL;
+ Log.d(TAG, "Voice record started");
+ } else
+ {
+ // Если запись не идёт, мы всегда держим в запасе один семпл
+ record.reset();
+ record.write(buffer, 0, len);
+ // Тишина, ошибок точно нет
+ noErrorTimer++;
+ if (noErrorTimer >= MAX_NO_ERROR_TIME) errorCount = 0;
+ }
+ }
+ if (recording) // Если запись идёт (или началась только что)
+ {
+ // Пишем звук в буфер
+ recordLength += TIMER_INTERVAL;
+ record.write(buffer, 0, len);
+
+ // Если в этот раз голос не обнаружили
+ // Или если пишем его уже слишком долго
+ if (!voiceDetected || (maxRecordLength < recordLength))
+ {
+ // Считаем как долго
+ silenceLength += TIMER_INTERVAL;
+ if ((silenceLength >= maxSilenceLength) || (maxRecordLength < recordLength))
+ {
+ // Пора прекражать запись
+ recording = false;
+ Log.d(TAG, "Voice record stopped, length: " + (recordLength - silenceLength));
+ if (recordLength - silenceLength >= minRecordLength)
+ {
+ try
+ {
+ new Thread(new ProceedRecordThread(record.toByteArray())).start();
+ } catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+ } else
+ silenceLength = 0; // Не молчат, обнуляем счётчик тишины
+ }
+ }
+
+ @Override
+ public void onMarkerReached(AudioRecord recorder)
+ {
+ // Не нужно
+ }
+ };
+
+ class ProceedRecordThread implements Runnable
+ {
+ byte[] record;
+
+ public ProceedRecordThread(byte[] record)
+ {
+ this.record = record;
+ }
+
+ public void run()
+ {
+ try
+ {
+ byte[] flac = flacEncode(record);
+ String results[] = request(flac, 5);
+ if ((voiceRecognizedListener != null) && (results.length > 0))
+ voiceRecognizedListener.onVoiceRecognized(results);
+ } catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ byte[] flacEncode(byte[] sampleData) throws IOException
+ {
+ Log.d(TAG, "Encoding...");
+ FLACEncoder flacEncoder = new FLACEncoder();
+ ByteArrayOutputStream flacData = new ByteArrayOutputStream();
+ FLACStreamOutputStream flacOutputStream = new FLACStreamOutputStream(flacData);
+ // FLACFileOutputStream flacOutputStream = new
+ // FLACFileOutputStream("/mnt/sdcard/test.flac");
+ StreamConfiguration streamConfiguration = new StreamConfiguration();
+ streamConfiguration.setSampleRate(SAMPLE_RATE);
+ streamConfiguration.setBitsPerSample(16);
+ streamConfiguration.setChannelCount(1);
+ flacEncoder.setStreamConfiguration(streamConfiguration);
+ flacEncoder.setOutputStream(flacOutputStream);
+ flacEncoder.openFLACStream();
+ int[] sampleDataInt = new int[sampleData.length / 2];
+ for (int p = 0; p < sampleData.length - 1; p += 2)
+ {
+ // WTF, Java? Two little-endian signed bytes to int conversion
+ sampleDataInt[p / 2] = sampleData[p + 1] * 256 + ((sampleData[p] >= 0) ? sampleData[p] : (256 + sampleData[p]));
+ }
+ flacEncoder.addSamples(sampleDataInt, sampleDataInt.length);
+ flacEncoder.encodeSamples(sampleDataInt.length, false);
+ flacEncoder.encodeSamples(flacEncoder.samplesAvailableToEncode(), true);
+ flacOutputStream.close();
+ Log.d(TAG, "Encoded");
+ return flacData.toByteArray();
+ }
+
+ public String[] request(byte[] flac, int maxResults) throws Exception
+ {
+ Log.d(TAG, "Requesting...");
+ final String googleurl = "https://www.google.com/speech-api/v2/recognize?output=json";
+ StringBuilder sb = new StringBuilder(googleurl);
+ sb.append("&key=" + API_KEY);
+ sb.append("&lang=" + language);
+ sb.append("&maxresults=" + maxResults);
+ sb.append("&pfilter=0"); // ;)
+
+ URL url = new URL(sb.toString());
+ URLConnection urlCon = url.openConnection();
+ urlCon.setDoOutput(true);
+ urlCon.setUseCaches(false);
+
+ urlCon.setRequestProperty("Content-Type", "audio/x-flac; rate=" + SAMPLE_RATE);
+ OutputStream outputStream = urlCon.getOutputStream();
+ outputStream.write(flac);
+ outputStream.close();
+ BufferedReader br = new BufferedReader(new InputStreamReader(urlCon.getInputStream(), Charset.forName("UTF-8")));
+
+ StringBuilder result = new StringBuilder();
+ String line;
+ while ((line = br.readLine()) != null)
+ {
+ result.append(line);
+ }
+ br.close();
+ // Log.d(TAG, "Response: " + result.toString());
+ Pattern regex = Pattern.compile("\"transcript\":\"(.+?)\"");
+ Matcher m = regex.matcher(result.toString());
+ List<String> results = new ArrayList<String>();
+ while (m.find())
+ {
+ String r = m.group(1);
+ results.add(r);
+ Log.d(TAG, "Result: " + r);
+ }
+ // Результат пустой, может слишком громко?
+ if (results.isEmpty())
+ {
+ Log.d(TAG, "Google can't understand you");
+ errorCount++;
+ noErrorTimer = 0;
+ if (errorCount >= MAX_ERROR_COUNT) // А бывает иначе?
+ {
+ if (maxLevel > detectLevel)
+ {
+ Log.i(TAG, "Increasing detect level from " + detectLevel + " to " + maxLevel);
+ detectLevel = (int) (maxLevel);
+ }
+ errorCount = 0;
+ maxLevel = 0;
+ }
+ } else
+ {
+ errorCount = 0;
+ maxLevel = 0;
+ }
+ return (String[]) results.toArray(new String[results.size()]);
+ }
+
+ public interface VoiceRecognizedListener
+ {
+ void onVoiceRecognized(String[] results);
+ }
+}
diff --git a/src/com/example/speechtests/MainActivity.java b/src/com/example/speechtests/MainActivity.java
new file mode 100644
index 0000000..1a2fa5a
--- /dev/null
+++ b/src/com/example/speechtests/MainActivity.java
@@ -0,0 +1,87 @@
+package com.example.speechtests;
+
+import com.Cluster.SpeechRecognizer.SpeechRecognizer;
+import com.Cluster.SpeechRecognizer.SpeechRecognizer.VoiceRecognizedListener;
+import com.example.voicetests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.widget.TextView;
+
+public class MainActivity extends Activity implements VoiceRecognizedListener
+{
+ static SpeechRecognizer recognizer;
+ boolean ready = false;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ if (recognizer == null)
+ recognizer = new SpeechRecognizer();
+ recognizer.setLanguage("ru-RU");
+ recognizer.setVoiceRecognizedListener(this);
+ try
+ {
+ recognizer.start();
+ } catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ handlerShowDetectLevel.sendEmptyMessage(0);
+ }
+
+ @Override
+ protected void onDestroy()
+ {
+ recognizer.stop();
+ recognizer = null;
+ super.onDestroy();
+ }
+
+ @Override
+ public void onVoiceRecognized(String[] results)
+ {
+ Message msg = new Message();
+ msg.obj = results;
+ handlerShowResults.sendMessage(msg);
+ }
+
+ Handler handlerShowResults = new Handler()
+ {
+ @Override
+ public void handleMessage(Message msg)
+ {
+ StringBuilder out = new StringBuilder();
+ String[] results = (String[]) msg.obj;
+ for (String result : results)
+ {
+ out.append(result);
+ out.append("\r\n");
+ }
+ ((TextView) findViewById(R.id.text)).setText(out.toString());
+ }
+ };
+
+ Handler handlerShowDetectLevel = new Handler()
+ {
+ @Override
+ public void handleMessage(Message msg)
+ {
+ if (recognizer != null)
+ {
+ int detectLevel = recognizer.getDetectLevel();
+ ((TextView) findViewById(R.id.detect_level)).setText("Current detect volume level: " + detectLevel);
+ if (!ready && detectLevel < 32767)
+ {
+ ready = true;
+ ((TextView) findViewById(R.id.text)).setText(R.string.speak);
+ }
+ }
+ handlerShowDetectLevel.sendEmptyMessageDelayed(0, 500);
+ }
+ };
+}
diff --git a/src/javaFlacEncoder/ArrayRecycler.java b/src/javaFlacEncoder/ArrayRecycler.java
new file mode 100644
index 0000000..e28474c
--- /dev/null
+++ b/src/javaFlacEncoder/ArrayRecycler.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * The purpose of this class is to provide a source for reusable int arrays.
+ * When using large numbers of arrays in succession, it is inefficient to
+ * constantly go in an allocate/free loop. This way, we may pass a single,
+ * thread-safe recycler to all objects. No matter where the arrays end their
+ * life, we can then add it to the same resource store.
+ *
+ * @author Preston Lacey
+ */
+public class ArrayRecycler {
+ LinkedBlockingQueue<int[]> usedIntArrays = null;
+
+ ArrayRecycler() {
+ usedIntArrays = new LinkedBlockingQueue<int[]>();
+ }
+
+ public void add(int[] array) {
+ usedIntArrays.add(array);
+ }
+
+ /**
+ *
+ * @param size
+ * @return
+ */
+ public int[] getArray(int size) {
+ int[] result = usedIntArrays.poll();
+ if(result == null) {
+ result = new int[size];
+ }
+ else if(result.length < size) {
+ usedIntArrays.offer(result);
+ result = new int[size];
+ }
+ return result;
+ }
+}
diff --git a/src/javaFlacEncoder/BlockEncodeRequest.java b/src/javaFlacEncoder/BlockEncodeRequest.java
new file mode 100644
index 0000000..ebd1c8d
--- /dev/null
+++ b/src/javaFlacEncoder/BlockEncodeRequest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+/**
+ * BlockEncodeRequests are used to store a full block and necessary information
+ * to encode such block. It is assumed member variables will be accessed
+ * directly(for speed considerations). This Class simply gathers all values
+ * into a single object, but handles no encoding logic itself.
+ * @author Preston Lacey
+ */
+public class BlockEncodeRequest {
+ /* Sample data, may be interleaved if multiple channels exist.*/
+ volatile int[] samples;
+ /* Number of valid samples in this request */
+ volatile int count;
+ /* Index of samples[] where the first valid sample exists */
+ volatile int start;
+ /* Number of indices to skip between valid samples */
+ volatile int skip;
+ /* Frame-number this block is assigned */
+ volatile long frameNumber;
+ /* Location to store results to. For safety, use an empty element*/
+ volatile EncodedElement result;
+ /* Stores whether the result should be valid */
+ volatile boolean valid;
+ /* Number of elements actually encoded. */
+ volatile int encodedSamples;
+
+ /**
+ * Set all values, preparing this object to be sent to an encoder. Member
+ * variable "valid" is set to false by this call.
+ *
+ * @param samples Sample data, interleaved if multiple channels are used
+ * @param count Number of valid samples
+ * @param start Index of first valid sample
+ * @param skip Number of samples to skip between samples(this should be
+ * equal to number-of-channels minus 1.
+ * @param frameNumber Framenumber assigned to this block.
+ * @param result Location to store result of encode.
+ */
+ public void setAll(int[] samples, int count, int start, int skip,
+ long frameNumber, EncodedElement result) {
+// assert(start == 0);
+ this.samples = samples;
+ this.count = count;
+ this.start = start;
+ this.skip = skip;
+ this.frameNumber = frameNumber;
+ this.result = result;
+ valid = false;
+ this.encodedSamples = 0;
+ }
+
+ public int addInterleavedSamples(int[] newSamples, int offset, int addCount, int max) {
+ //System.err.println("offset:addCount:max :: " +offset+":"+addCount+":"+max);
+ assert(max <= this.samples.length/(this.skip+1));
+ if(max > this.samples.length)
+ max = this.samples.length/(skip+1);
+
+ int remaining = count;
+ int spaceLeft = max - count;
+ int toEncode = (addCount < spaceLeft) ? addCount:spaceLeft;
+ remaining = addCount-toEncode;
+
+ int[] src = newSamples;
+ int[] dest = samples;
+ int destPos = count*(skip+1);
+ int srcPos = offset;
+ int length = toEncode*(skip+1);
+ System.arraycopy(src, srcPos, dest, destPos, length);
+
+ this.count = count+toEncode;
+ return remaining;
+ }
+
+ public boolean isFull(int max) {
+ boolean full = false;
+ if(this.count >= max)
+ full = true;
+ return full;
+ }
+}
diff --git a/src/javaFlacEncoder/BlockThreadManager.java b/src/javaFlacEncoder/BlockThreadManager.java
new file mode 100644
index 0000000..31fed45
--- /dev/null
+++ b/src/javaFlacEncoder/BlockThreadManager.java
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Collections;
+import java.util.concurrent.TimeUnit;
+import java.util.Vector;
+
+/**
+ * BlockThreadManager is used by FLACEncoder(when encoding with threads), to
+ * dispatch BlockEncodeRequests to ThreadFrames which do the actual encode.
+ *
+ *
+ * BlockThreadManager accepts BlockEncodeRequest objects to encode.
+ * For each Frame object given to encode with, a Thread is supplied. This thread
+ * will take a BlockEncodeRequest, encode it, then give it back to the BlockThreadManager
+ * The thread will then take another BlockencodeRequest, and repeat. If no block
+ * encode requests are available, it will block till available.
+ *
+ *
+ * The main thread of the BlockThreadManager will be waiting for additions to
+ * the finished queue. If the item is not the next in line to be written to file,
+ * it will be saved temporarily. If it is the next item to be written, it will
+ * be given back to the FLACEncoder to be written to output, and any saved objects
+ * will be searched to see if more can be written. The main thread will then wait
+ * for the next frame again.
+ *
+ *
+ *
+ *
+ * @author Preston Lacey
+ */
+public class BlockThreadManager implements Runnable{
+ /* unassignedEncodeRequests: Requests waiting to be assigned a thread */
+ LinkedBlockingQueue<BlockEncodeRequest> unassignedEncodeRequests = null;
+
+ /* requests that have just finished encoding, and are ready for writing.
+ * Since run() polls against this for newly finished requests, requests
+ * recieved out of order will be temporarily stored in finishedRequestStore
+ */
+ LinkedBlockingQueue<BlockEncodeRequest> finishedEncodeRequests = null;
+
+ /* pending requests in the order that we must pass them to the FLACEncoder.
+ * The top object is moved to nextTarget until it is found in
+ * finishedEncodeRequests and passed to the encoder. */
+ LinkedBlockingQueue<BlockEncodeRequest> orderedEncodeRequests = null;
+
+ /* frameThreadMap: Keep track of which thread is handling which frame */
+ Map<FrameThread, Thread> frameThreadMap = null;
+
+ /* Thread which watches for finished encodes and alerts FLACEncoder of their
+ * finished state. This thread will die when there is no data to encode, but
+ * should remain valid and unchanged so long as blocks are encoding or
+ * queued. It may therefore be used to monitor/interrupt, an encode process.
+ */
+ volatile Thread managerThread = null;
+
+ /* FLACEncoder object we will send finished requests to */
+ volatile FLACEncoder encoder = null;
+
+ /* Must be false if we've been explicitly told to stop. Adding new requests
+ * will reset this value to true */
+ volatile boolean process = true;
+
+ /* FrameThreads that are not currently assigned to a Thread */
+ Vector<FrameThread> inactiveFrameThreads = null;
+
+ /* Lock ensures that only one FrameThread may get a new request at a time */
+ private final Object getLock = new Object();
+
+ /* Store finished requests that are not yet passed back to the FLACEncoder*/
+ Vector<BlockEncodeRequest> finishedRequestStore = null;
+
+ /* blockWhileQueueExceeds() waits on this lock for changes to queue size */
+ private final Object outstandingCountLock = new Object();
+
+ /* Next request which must be found and returned to the FLACEncoder. */
+ volatile BlockEncodeRequest nextTarget = null;
+
+ /* Number of requests added but not yet returned to FLACEncoder */
+ volatile int outstandingCount = 0;
+
+ /**
+ * Constructor. Must supply a valid FLACEncoder object which will be alerted
+ * when a block is finished encoding.
+ * @param encoder FLACEncoder to use in encoding process.
+ */
+ public BlockThreadManager(FLACEncoder encoder) {
+ this.encoder = encoder;
+ unassignedEncodeRequests = new LinkedBlockingQueue<BlockEncodeRequest>();
+ finishedEncodeRequests = new LinkedBlockingQueue<BlockEncodeRequest>();
+ orderedEncodeRequests = new LinkedBlockingQueue<BlockEncodeRequest>();
+ frameThreadMap = Collections.synchronizedMap(new HashMap<FrameThread, Thread>());
+ inactiveFrameThreads = new Vector<FrameThread>();
+ finishedRequestStore = new Vector<BlockEncodeRequest>();
+ managerThread = null;
+ }
+
+ /**
+ * Get total number of BlockEncodeRequests added to this manager, but not
+ * yet passed back to the FLACEncoder object.
+ * @return number of BlockEncodeRequests remaining in this manager.
+ */
+ synchronized public int getTotalManagedCount() {
+ return outstandingCount;
+ }
+
+ /**
+ * This function is used to help control flow of BlockEncodeRequests into
+ * this manager. It will block so long as their is at least as many
+ * unprocessed blocks waiting to be encoded as the value given.
+ *
+ * @param count Maximum number of outstanding requests that may exist before
+ * this method may return.
+ */
+ public void blockWhileQueueExceeds(int count) {
+ boolean loop = true;
+ boolean interrupted = false;
+ try {
+ do {
+ synchronized(outstandingCountLock) {
+ if(outstandingCount > count) {
+ try {
+ outstandingCountLock.wait();
+ }catch(InterruptedException e) {
+ //ignore interruption, loop again.
+ interrupted = true;
+ }
+ }
+ else
+ loop = false;
+ }
+ }while(loop);
+ }finally {
+ if(interrupted)
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ /**
+ * Add a Frame to this manager, which it will use to encode a block. Each
+ * Frame added allows one more thread to be used for encoding. At least one
+ * Frame must be added for this manager to encode.
+ * @param frame Frame to use for encoding.
+ * @return boolean false if there was an error adding the frame, true
+ * otherwise.
+ */
+ synchronized public boolean addFrameThread(Frame frame) {
+ FrameThread ft = new FrameThread(frame, this);
+ inactiveFrameThreads.add(ft);
+ boolean r = true;
+ startFrameThreads();
+ return r;
+ }
+
+ /**
+ * Start any available FrameThread objects encoding, so long as there are
+ * waiting BlockEncodeRequest objects.
+ *
+ */
+ synchronized private void startFrameThreads() {
+ if(!process)
+ return;
+ int requests = unassignedEncodeRequests.size();
+ int frames = inactiveFrameThreads.size();
+ frames = (requests <= frames) ? requests:frames;
+ for(int i = 0; i < frames; i++) {
+ FrameThread ft = inactiveFrameThreads.remove(0);
+ Thread thread = new Thread(ft);
+ frameThreadMap.put(ft, thread);
+ thread.start();
+ }
+ }
+
+ /**
+ * Notify this manager that a FrameThread has ended it's run() method,
+ * returning the FrameThread object to the manager for use in future Threads.
+ *
+ * @param ft FrameThread object which is ending.
+ */
+ synchronized public void notifyFrameThreadExit(FrameThread ft) {
+ frameThreadMap.remove(ft);
+ inactiveFrameThreads.add(ft);
+ startFrameThreads();
+ }
+
+ /**
+ * Get a BlockEncodeRequest object which is queued for encoding, pausing for
+ * up to 0.5 seconds till one is available. It is expected that this object
+ * will later(after encoding to a FLAC frame) be returned to this manager
+ * through the returnFinishedRequest method. Failure to return the finished
+ * object will cause the encoding process to hang.
+ *
+ * @return BlockEncodingRequest to encode, null if none available.
+ */
+ public BlockEncodeRequest getWaitingRequest() {
+ BlockEncodeRequest result = null;
+ synchronized(getLock) {
+ boolean loop = true;
+ try {
+ while(loop) {
+ result = unassignedEncodeRequests.poll(500, TimeUnit.MILLISECONDS);
+ if(result != null) {
+ synchronized(outstandingCountLock) {
+ outstandingCountLock.notifyAll();
+ }
+ orderedEncodeRequests.add(result);
+ }
+ loop = false;
+ }
+ } catch(InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Notify this manager that it may stop as soon as all currently outstanding
+ * requests are completed. Future calls to addRequest() will clear this stop
+ * state.
+ */
+ synchronized public void stop() {
+ process = false;
+ BlockEncodeRequest temp = new BlockEncodeRequest();
+ temp.setAll(null, -1, -1, -1, -1, null);
+ int count = frameThreadMap.size();
+ for(int i = 0; i < count; i++) {
+ unassignedEncodeRequests.add(temp);
+ }
+ }
+
+ /**
+ * Used to return a finished BlockEncodeRequest from a FrameThread. This
+ * must only be called with a finished request, which was originally added
+ * to this manager through the addRequest() method.
+ *
+ * @param ber finished BlockEncodeRequest that needs passed back to the
+ * FLACEncoder object.
+ *
+ */
+ synchronized public void returnFinishedRequest(BlockEncodeRequest ber) {
+ try {
+ finishedEncodeRequests.put(ber);
+ restartManager();
+ }catch(InterruptedException e) {
+ returnFinishedRequest(ber);
+ Thread.currentThread().interrupt();
+ }
+ }
+
+/**
+ * Waits for the next BlockEncodeRequest that needs to be sent back to the
+ * FLACEncoder for finalizing. If no request is finished, or currently
+ * assigned to an encoding thread, will timeout after 0.5 seconds and end.
+ */
+public void run () {
+ //wait for finished item
+ //send finished item to encoder
+ //loop to top
+ boolean loop = true;
+ boolean interrupted = false;
+ try {
+ while(loop) {
+ try {
+ if(nextTarget == null)
+ nextTarget = orderedEncodeRequests.poll(500,TimeUnit.MILLISECONDS);
+ if(nextTarget == null) {
+ loop = false;
+ }
+ else if(nextTarget.frameNumber < 0) {
+ loop = false;
+ nextTarget = null;
+ orderedEncodeRequests.clear();
+ }
+ else if(finishedRequestStore.remove(nextTarget)) {
+ encoder.blockFinished(nextTarget);
+ nextTarget = null;
+ synchronized(outstandingCountLock) {
+ outstandingCount--;
+ outstandingCountLock.notifyAll();
+ }
+ }
+ else {
+ BlockEncodeRequest ber = finishedEncodeRequests.poll(500, TimeUnit.MILLISECONDS);
+ if(ber == null) {//nothing to process yet, let this thread end.
+ loop = false;
+ }
+ else if(nextTarget == ber) {
+ encoder.blockFinished(ber);
+ nextTarget = null;
+ synchronized(outstandingCountLock) {
+ outstandingCount--;
+ outstandingCountLock.notifyAll();
+ }
+ }
+ else {
+ finishedRequestStore.add(ber);
+ }
+ }
+ }catch(InterruptedException e) {
+ interrupted = true;
+ }
+ }
+ }finally {
+ if(interrupted)
+ Thread.currentThread().interrupt();
+ }
+ synchronized(this) {
+ managerThread = null;
+ restartManager();
+ }
+ }
+
+ /**
+ * Attempt to restart the managerThread if possible/needed. This is the only
+ * function that should *ever* call managerThread.start(). We should call
+ * this at any time a managerThread may be needed but not started. For
+ * example, after returning a BlockEncodeRequest from an encoding thread.
+ */
+ synchronized private void restartManager() {
+ if(managerThread == null && orderedEncodeRequests.size() > 0 ) {
+ managerThread = new Thread(this);
+ managerThread.start();
+ }
+ }
+
+ /**
+ * Add a BlockEncodeRequest to the manager. This will immediately attempt
+ * to assign a request to an encoding thread(which may not occur if no
+ * threads are currently available). Requests are passed back to the
+ * currently set FLACEncoder object when finished and ready to be written
+ * to output.
+ *
+ * @param ber Block request to encode
+ * @return boolean true if block added, false if an error occured.
+ */
+ synchronized public boolean addRequest(BlockEncodeRequest ber) {
+ //add request to the manager(requests are automatically removed when complete)
+ process = true;
+ boolean r = true;
+ try {
+ unassignedEncodeRequests.put(ber);
+ synchronized(outstandingCountLock) {
+ outstandingCount++;
+ }
+ startFrameThreads();
+ }catch(InterruptedException e) {
+ r = false;
+ Thread.currentThread().interrupt();
+ }
+ return r;
+ }
+} \ No newline at end of file
diff --git a/src/javaFlacEncoder/COPYING b/src/javaFlacEncoder/COPYING
new file mode 100644
index 0000000..4362b49
--- /dev/null
+++ b/src/javaFlacEncoder/COPYING
@@ -0,0 +1,502 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/src/javaFlacEncoder/CRC16.java b/src/javaFlacEncoder/CRC16.java
new file mode 100644
index 0000000..7af5792
--- /dev/null
+++ b/src/javaFlacEncoder/CRC16.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+
+/**
+ * Class to calculate a CRC16 checksum.
+ * @author Preston Lacey
+ */
+public class CRC16 {
+ /** For Debugging: Higher level equals more debug statements */
+ public static int DEBUG_LEV = 0;
+
+ /** CRC Divisor: 0x8005(implicit 1 at MSB for 0x18005) */
+ static final int divisorCRC16 = 0x8005;
+
+ /** working checksum stored between calls to update(..) */
+ protected int workingCRC;
+
+ private static final short xorTable[] = generateTable();
+
+ /**
+ * Constructor. Creates a CRC16 object that is ready to be used. Next step
+ * would be to call update(...) with appropriate data.
+ */
+ public CRC16() {
+ reset();
+ }
+
+ /**
+ * Resets stored data, preparing object for a new checksum.
+ */
+ public void reset() {
+ workingCRC = 0;
+ }
+
+ public short checksum() {
+ return (short)(workingCRC & 0xFFFF);
+ }
+
+ public int update(byte input) {
+ workingCRC = (workingCRC << 8)^xorTable[((workingCRC >>> 8)^input) & 0xFF];
+ return workingCRC;
+ }
+
+ public int update(byte[] input, int start, int stop) {
+ for(int i = start; i < stop; i++) {
+ byte b = input[i];
+ workingCRC = (workingCRC << 8) ^ xorTable[((workingCRC >>> 8)^b) & 0xFF];
+ }
+ return workingCRC;
+ }
+
+ private static short[] generateTable() {
+ short[] table = new short[256];
+ for(int i = 0; i < table.length; i++) {
+ int polynomial = divisorCRC16;
+ int xorVal = i << 8;
+ int topmask = 1 << 16;
+ for(int x = 0; x < 8; x++ ) {
+ xorVal = xorVal << 1;
+ if( (xorVal & topmask) > 0) xorVal = (xorVal) ^ polynomial;
+ }
+ table[i] = (short)(xorVal & 0xFFFF);
+ }
+ return table;
+ }
+}
diff --git a/src/javaFlacEncoder/CRC8.java b/src/javaFlacEncoder/CRC8.java
new file mode 100644
index 0000000..573ecc1
--- /dev/null
+++ b/src/javaFlacEncoder/CRC8.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+
+
+/**
+ * Class to calculate a CRC8 checksum.
+ * @author Preston Lacey
+ */
+public class CRC8 {
+
+ /** For Debugging: Higher level equals more debug statements */
+ public static int DEBUG_LEV = 0;
+
+ /** CRC Divisor: 0x107 */
+ static final int divisorCRC8 = 0x107 << 23;
+
+ /** working checksum stored between calls to update(..) */
+ int workingCRC8;
+
+ /** number of valid bits stored in workingCRC8(valid bits are packed towards
+ * high-order side of int */
+ int workingCRC8Count;
+
+ private static final byte[] fake = {0};
+
+ /**
+ * Constructor. Creates a CRC8 object that is ready to be used. Next step
+ * would be to call updateCRC8 with appropriate data.
+ */
+ public CRC8() {
+ reset();
+ }
+
+ /**
+ * Add data to the crc. Immediately creates checksum on given data. Can be
+ * called multiple times as it won't finalize the checksum until the method
+ * checksum() is called.
+ *
+ * @param inSet Array holding data to checksum.
+ * @param start Index of array holding first element
+ * @param end Index to stop at. Last index used will be end-1.
+ * @return intermediate result of checksum to this point. This is *not* a
+ * finalized result, as non-summed data must remain in workingCRC8 until
+ * checksum() is called.
+ */
+ public byte updateCRC8(byte[] inSet, int start, int end) {
+ //we need at least one byte to work on. And cache between calls.
+ // Follow md5 style, updating many times, finalizing once.
+
+ //Copy in saved value(starts out zero).
+ //Shift value, and divisor to top end of int. When we drop below 17 in
+ // working int, "OR" it with another byte, or save and return.
+ // I won't do this, just "assume" it's already there and use instance
+ // variables.
+ //While bits available is more than 8:
+ //while top bit is zero, and >8 bits in buffer. Shift Left.
+ //if >8 bits remain in working int, divide
+ //else if 8 bits remain, "OR" in another byte.
+ //store working int, return;
+ if(DEBUG_LEV > 10 )
+ System.err.println("CRC8::updateCRC8: Begin");
+ if(DEBUG_LEV > 20) {
+ System.err.println("Start:End : "+start+":"+end);
+ }
+ int current = start;
+ int topBit = 0;
+ int topMask = 1<<31;
+ while(current < end) {//this should leave 8 bits in workingCRC8.
+ if(DEBUG_LEV > 40 ) {
+ System.err.println("CRC8::updateCRC8: looping bytes. current: "+current);
+ System.err.println("workingCRC8Count : " +workingCRC8Count);
+ }
+ topBit = workingCRC8 & topMask;
+ while(workingCRC8Count > 8 && topBit == 0) {
+ if(DEBUG_LEV > 40)
+ System.err.println("CRC8::updateCRC8: shifting left");
+ workingCRC8Count--;
+ workingCRC8 = workingCRC8 << 1;
+ topBit = workingCRC8 & topMask;
+ }
+ if( workingCRC8Count > 8 ) {
+ workingCRC8 = workingCRC8 ^ divisorCRC8;
+ }
+ else {//workingCRC8Count < 9
+ if(DEBUG_LEV > 30) {
+ System.err.println("CRC8: Adding byte with workingCRC of: "+
+ (workingCRC8 >>> 24));
+ }
+ int temp = inSet[current++];
+ temp = temp << 24;
+ temp = temp >>> 8;
+ workingCRC8 = workingCRC8 | temp;
+ workingCRC8Count+=8;
+ }
+ }
+ return (byte)(workingCRC8 >>> 24);
+ }
+
+ /**
+ * Finalize the checksum, and return the value. After this is called, you
+ * must call reset() before attempting a new checksum.
+ * @return finalized checksum.
+ */
+ public byte checksum() {
+ if(DEBUG_LEV > 10 )
+ System.err.println("CRC8::checksum : Begin");
+ //add 8 to the count,
+ //call update with a byte constructed to add no more.
+ //byte[] fake = {0};
+ workingCRC8Count += 8;
+ byte val = updateCRC8(fake, 0, 1);
+ if(DEBUG_LEV > 10 )
+ System.err.println("CRC8::checksum : End");
+ return val;
+ }
+
+ /**
+ * Resets all stored data, preparing object for a new checksum.
+ */
+ public void reset() {
+ workingCRC8 = 0;
+ workingCRC8Count = 8;
+ }
+}
diff --git a/src/javaFlacEncoder/ChannelData.java b/src/javaFlacEncoder/ChannelData.java
new file mode 100644
index 0000000..b511e7c
--- /dev/null
+++ b/src/javaFlacEncoder/ChannelData.java
@@ -0,0 +1,52 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package javaFlacEncoder;
+
+/**
+ *
+ * @author preston
+ */
+public class ChannelData {
+ public enum ChannelName {
+ LEFT,
+ RIGHT,
+ MID,
+ SIDE,
+ INDEPENDENT
+ }
+ private int[] samples = null;
+ private int count;
+ private int sampleSize;
+ private ChannelName name;
+
+ public ChannelData(int[] samples, int count, int sampleSize, ChannelName n) {
+ this.count = count;
+ this.samples = samples;
+ this.sampleSize = sampleSize;
+ this.name = n;
+ }
+
+ public int[] getSamples() { return samples; }
+ public int getCount() { return count; }
+ public int getSampleSize() { return sampleSize; }
+ public ChannelName getChannelName() { return name; }
+
+ public int setData(int[] newSamples, int count, int sampleSize, ChannelName n) {
+ samples = newSamples;
+ this.sampleSize = sampleSize;
+ this.name = n;
+ return setCount(count);
+ }
+
+ public int setCount(int count) {
+ this.count = (count <= samples.length) ? count:samples.length;
+ return this.count;
+ }
+
+ public void setChannelName(ChannelName cn) {
+ this.name = cn;
+ }
+}
diff --git a/src/javaFlacEncoder/EncodedElement.java b/src/javaFlacEncoder/EncodedElement.java
new file mode 100644
index 0000000..5ba1883
--- /dev/null
+++ b/src/javaFlacEncoder/EncodedElement.java
@@ -0,0 +1,998 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+
+/**
+ * EncodedElement class provides is used to store data in the proper bitstream
+ * format for FLAC audio. Methods are provided to easily append values to the
+ * bitstream. Conceptually, an EncodedElement is a list structure, in which the
+ * encoded data is stored in a byte array. It is assumed that any data stored by
+ * objects of this class will be retrieved through direct access to the
+ * underlying byte array. This byte array is exposed to outside objects
+ * primarily to allow faster access than "proper" object-oriented design might
+ * allow.
+ *
+ * @author Preston Lacey
+ */
+public class EncodedElement {
+
+ /** For Debugging: Higher level equals more debug statements */
+ static int DEBUG_LEV = 0;
+
+ /** Previous element in list. At current times, this should not be dependend
+ * on to be set */
+ EncodedElement previous = null;
+
+ /** Next element in list. */
+ EncodedElement next = null;
+
+ /** Data stored by this element. Member usableBits should be used to track
+ * the last valid index at a bit level. */
+ byte[] data = null;
+
+ /** Use to track the last valid index of the data array at a bit level. For
+ * example, a value of "10" would mean the first byte and two low-order bits
+ * of the second byte are used. usableBits must always be equal or greater
+ * than offset.
+ */
+ int usableBits = 0;//i.e, the last write location in 'data' array.
+
+ /** Used to signify the index of the first valid bit of the data array. For
+ * purposes of speed, it is not always best to pack the data starting at
+ * bit zero of first byte. offset must always be less than or equal to
+ * usableBits.
+ */
+ protected int offset;
+
+ /**
+ * Constructor, creates an empty element with offset of zero and array size
+ * of 100. This array can be replaced with a call to setData(...).
+ */
+ public EncodedElement() {
+ offset = 0;
+ usableBits = 0;
+ data = new byte[100];
+ }
+
+ /**
+ * Constructor. Creates an EncodedElement with the given size and offset
+ * @param array Byte array to use for this element.
+ * @param off Offset to use for this element. Usablebits will also be set
+ * to this value.
+ */
+ public EncodedElement(byte[] array, int off) {
+ assert(array.length%4 == 0);
+ offset = off;
+ usableBits = off;
+ data = array;
+ }
+
+ public EncodedElement(byte[] array, int off, int usedBits) {
+ assert(array.length%4 == 0);
+ offset = off;
+ usableBits = usedBits;
+ data = array;
+}
+
+ /**
+ * Constructor. Creates an EncodedElement with the given size and offset
+ * @param size Size of data array to use(in bytes)
+ * @param off Offset to use for this element. Usablebits will also be set
+ * to this value.
+ */
+ public EncodedElement(int size, int off) {
+ if (size%4 != 4) size = (size/4+1)*4;
+ data = new byte[size];
+ usableBits = off;
+ offset = off;
+ }
+
+ /**
+ * Completely clear this element and use the given size and offset for the
+ * new data array.
+ *
+ * @param size Size of data array to use(in bytes)
+ * @param off Offset to use for this element. Usablebits will also be set to
+ * this value.
+ */
+ public void clear(int size, int off) {
+ if (size%4 != 4) size = (size/4+1)*4;
+ next = null;
+ previous = null;
+ data = new byte[size];
+ offset = off;
+ for(int i = 0; i < data.length; i++)
+ data[i] = 0;
+ usableBits = off;
+ }
+
+ /**
+ * Completely clear this element and use the given size and offset for the
+ * new data array.
+ *
+ * @param size Size of data array to use(in bytes)
+ * @param off Offset to use for this element. Usablebits will also be set to
+ * this value.
+ * @param keep true to keep current backing array, false to create new one.
+ */
+ public void clear(int size, int off, boolean keep) {
+ if (size%4 != 4) size = (size/4+1)*4;
+ next = null;
+ previous = null;
+ if(!keep)
+ data = new byte[size];
+ offset = off;
+ for(int i = 0; i < data.length; i++)
+ data[i] = 0;
+ usableBits = off;
+ }
+ /**
+ * Set the object previous to this in the list.
+ * @param ele the object to set as previous.
+ * @return <code>void</code>
+ *
+ * Precondition: none
+ * Post-condition: getPrevious() will now return the given object. Any
+ * existing “previous” was lost.
+ */
+ void setPrevious(EncodedElement ele) {
+ previous = ele;
+ }
+
+ /**
+ * Set the object next to this in the list.
+ * @param ele the object to set as next.
+ * @return void
+ * Pre-condition: none
+ * Post-condition: getNext() will now return the given object. Any existing
+ * “next” was lost.
+ */
+ void setNext(EncodedElement ele) {
+ next = ele;
+ }
+
+ /**
+ * Get the object stored as the previous item in this list.
+ *
+ * @return EncodedElement
+ */
+ EncodedElement getPrevious() {
+ return previous;
+ }
+ /**
+ * Get the object stored as the next item in this list.
+ *
+ * @param EncodedElement;
+ */
+ EncodedElement getNext() {
+ return next;
+ }
+
+ /**
+ * Set the byte array stored by this object.
+ *
+ * @param data the byte array to store.
+ *
+ * Pre-condition: None
+ * Post-condition: 'data' is now stored by this object. Any previous data
+ * stored was lost.
+ */
+ void setData(byte[] data) {
+ assert(data.length%4 == 0);
+ this.data = data;
+ }
+
+ /**
+ * Set the number of bits of the given array that are usable data. Data is
+ * packed from the lower indices to higher.
+ *
+ * @param bits the value to store
+ */
+ void setUsableBits(int bits) {
+ usableBits = bits;
+ }
+
+ /**
+ * Get the byte array stored by this object(null if not set).
+ *
+ * @param byte[] the data stored in this byte[] is likely not all usable.
+ * Method getUsableBits() should be used to determine such.
+ */
+ byte[] getData() {
+ return data;
+ }
+
+ /**
+ * get the number of bits usable in the stored array.
+ * @return int
+ */
+ int getUsableBits() {
+ return usableBits;
+ }
+
+ /**
+ * Return the last element of the list given. This is a static funtion to
+ * provide minor speed improvement. Loops through all elements' "next"
+ * pointers, till the last is found.
+ * @param e EncodedElement list to find end of.
+ * @return Final element in list.
+ */
+ protected static EncodedElement getEnd_S(EncodedElement e) {
+ if(e == null)
+ return null;
+ EncodedElement temp = e.next;
+ EncodedElement end = e;
+ while(temp != null) {
+ end = temp;
+ temp = temp.next;
+ }
+ return end;
+ }
+
+ /**
+ * Return the last element of the list given. Loops through all elements'
+ * "next" pointers, till the last is found.
+ * @return last element in this list
+ */
+ public EncodedElement getEnd() {
+ EncodedElement temp = next;
+ EncodedElement end = this;
+ while(temp != null) {
+ end = temp;
+ temp = temp.next;
+ }
+ return end;
+ }
+
+ /**
+ * Attach an element to the end of this list.
+ *
+ * @param e Element to attach.
+ * @return True if element was attached, false otherwise.
+ */
+ public boolean attachEnd(EncodedElement e) {
+ if(DEBUG_LEV > 0)
+ System.err.println("EncodedElement::attachEnd : Begin");
+ boolean attached = true;
+ EncodedElement current = this;
+ while(current.getNext() != null) {
+ current = current.getNext();
+ }
+ current.setNext(e);
+ e.setPrevious(current);
+ if(DEBUG_LEV > 0)
+ System.err.println("EncodedElement::attachEnd : End");
+ return attached;
+ }
+
+ /**
+ * Add a number of bits from a long to the end of this list's data. Will
+ * add a new element if necessary. The bits stored are taken from the lower-
+ * order of input.
+ *
+ * @param input Long containing bits to append to end.
+ * @param bitCount Number of bits to append.
+ * @return EncodedElement which actually contains the appended value.
+ */
+ public EncodedElement addLong(long input, int bitCount) {
+ if(next != null) {
+ EncodedElement end = EncodedElement.getEnd_S(next);
+ return end.addLong(input, bitCount);
+ }
+ else if(data.length*8 <= usableBits+bitCount) {
+ //create child and attach to next.
+ //Set child's offset appropriately(i.e, manually set usable bits)
+ int tOff = usableBits %8;
+ int size = data.length/2+1;
+ //guarantee that our new element can store our given value
+ if(size < bitCount) size = bitCount*10;
+ next = new EncodedElement(size, tOff);
+ //add int to child
+ return next.addLong(input, bitCount);
+ }
+ //At this point, we have the space, and we are the end of the chain.
+ int startPos = this.usableBits;
+ byte[] dest = this.data;
+ EncodedElement.addLong(input, bitCount, startPos, dest);
+ usableBits += bitCount;
+ return this;
+ }
+
+ /**
+ * Add a number of bits from an int to the end of this list's data. Will
+ * add a new element if necessary. The bits stored are taken from the lower-
+ * order of input.
+ *
+ * @param input Int containing bits to append to end.
+ * @param bitCount Number of bits to append.
+ * @return EncodedElement which actually contains the appended value.
+ */
+ public EncodedElement addInt(int input, int bitCount) {
+ if(next != null) {
+ EncodedElement end = EncodedElement.getEnd_S(next);
+ return end.addInt(input, bitCount);
+ }
+ else if(data.length*8 < usableBits+bitCount) {
+ //create child and attach to next.
+ //Set child's offset appropriately(i.e, manually set usable bits)
+ int tOff = usableBits %8;
+ //int size = data.length/2+1;
+ int size = 1000;
+ //guarantee that our new element can store our given value
+ //if(size <= bitCount+tOff) size = (size+tOff+bitCount)*10;
+ next = new EncodedElement(size, tOff);
+ System.err.println("creating next node of size:bitCount "+size+
+ ":"+bitCount+":"+usableBits+":"+data.length);
+ System.err.println("value: "+input);
+ //+this.toString()+"::"+next.toString());
+ //add int to child
+ return next.addInt(input, bitCount);
+ }
+ else {
+ //At this point, we have the space, and we are the end of the chain.
+ int startPos = this.usableBits;
+ byte[] dest = this.data;
+ EncodedElement.addInt_new(input, bitCount, startPos, dest);
+ //EncodedElement.addInt_buf2(input, bitCount, startPos, dest);
+ /*if(startPos/8+8 > dest.length)
+ EncodedElement.addInt(input, bitCount, startPos, dest);
+ else
+ EncodedElement.addInt_new(input, bitCount, startPos, dest);*/
+ usableBits += bitCount;
+ return this;
+ }
+ }
+
+ /**
+ * Append an equal number of bits from each int in an array within given
+ * limits to the end of this list.
+ *
+ * @param inputArray Array storing input values.
+ * @param bitSize number of bits to store from each value.
+ * @param start index of first usable index.
+ * @param skip number of indices to skip between values(in case input data
+ * is interleaved with non-desirable data).
+ * @param countA Number of total indices to store from.
+ * @return EncodedElement containing end of packed data. Data may flow
+ * between multiple EncodedElement's, if an existing element was not large
+ * enough for all values.
+ */
+ public EncodedElement packInt(int[] inputArray, int bitSize,
+ int start, int skip, int countA) {
+ //go to end if we're not there.
+ if(next != null) {
+ EncodedElement end = EncodedElement.getEnd_S(next);
+ return end.packInt(inputArray, bitSize, start, skip, countA);
+ }
+ //calculate how many we can pack into current.
+ int writeCount = (data.length*8 - usableBits) / bitSize;
+ if(writeCount > countA) writeCount = countA;
+ //pack them and update usable bits.
+ EncodedElement.packInt(inputArray, bitSize, usableBits, start, skip, countA, data);
+ usableBits += writeCount * bitSize;
+ //if more remain, create child object and add there
+ countA -= writeCount;
+ if(countA > 0) {
+ int tOff = usableBits %8;
+ int size = data.length/2+1;
+ //guarantee that our new element can store our given value
+ if(size < bitSize*countA) size = bitSize*countA+10;
+ next = new EncodedElement(size, tOff);
+ //add int to child
+ return next.packInt(inputArray, bitSize, start+writeCount*(skip+1), skip, countA);
+ }
+ else {
+ //return last object we write to.
+ return this;
+ }
+ }
+
+ /**
+ * Pack a number of bits from each int of an array(within given limits)to
+ * the end of this list.
+ *
+ * @param inputA Array containing input values.
+ * @param inputBits Array containing number of bits to use for each index
+ * packed. This array should be equal in size to the inputA array.
+ * @param inputOffset Index of first usable index.
+ * @param countA Number of indices to pack.
+ * @return EncodedElement containing end of packed data. Data may flow
+ * between multiple EncodedElement's, if an existing element was not large
+ * enough for all values.
+ */
+ public EncodedElement packIntByBits(int[] inputA, int[] inputBits, int inputOffset,
+ int countA) {
+ //go to end if we're not there.
+ if(next != null) {
+ EncodedElement end = EncodedElement.getEnd_S(next);
+ return end.packIntByBits(inputA, inputBits, inputOffset, countA);
+ }
+ //calculate how many we can pack into current.
+ int writeBitsRemaining = data.length*8 - usableBits;
+ int willWrite = 0;
+ int writeCount = 0;
+ //System.err.println("writeBitsRemaining: " + writeBitsRemaining);
+ for(int i = 0; i < countA; i++) {
+ writeBitsRemaining -= inputBits[inputOffset+i];
+ if(writeBitsRemaining >= 0) {
+ writeCount++;
+ willWrite += inputBits[inputOffset+i];
+ }
+ else
+ break;
+ }
+ //pack them and update usable bits.
+ if(writeCount > 0) {
+ EncodedElement.packIntByBits(inputA, inputBits, inputOffset,
+ writeCount, usableBits, data);
+ //EncodedElement.packIntByBits_newFast(inputA, inputBits, inputOffset,
+ // writeCount, usableBits, data);
+ usableBits += willWrite;
+ }
+ //if more remain, create child object and add there
+ countA -= writeCount;
+ if(countA > 0) {
+ inputOffset += writeCount;
+ int tOff = usableBits %8;
+ int size = data.length/2+1;
+ //guarantee that our new element can store our given value
+ int remainingToWrite = 0;
+ for(int i = 0; i < countA; i++) {
+ remainingToWrite += inputBits[inputOffset+i];
+ }
+ remainingToWrite = remainingToWrite / 8 + 1;
+ if(size < remainingToWrite) size = remainingToWrite+10;
+ //System.err.println("remaining: "+remainingToWrite);
+ //System.err.println("creating size/offset : "+size+":"+tOff);
+ next = new EncodedElement(size, tOff);
+ //add int to child
+ return next.packIntByBits(inputA, inputBits, inputOffset, countA);
+ }
+ else {
+ //System.err.println("returning....done");
+ //return if this is last object we wrote to.
+ return this;
+ }
+ }
+
+ /**
+ * Total number of usable bits stored by this entire list. This sums the
+ * difference of each list element's "usableBits" and "offset".
+ * @return Total valid bits in this list.
+ */
+ public int getTotalBits() {
+ //this total calculates and removes the bits reserved for "offset"
+ // between the different children.
+ int total = 0;
+ EncodedElement iter = this;
+ while(iter != null) {
+ total += iter.usableBits - iter.offset;
+ iter = iter.next;
+ }
+ return total;
+ }
+
+ /**
+ * This method adds a given number of bits of an int to a byte array.
+ * @param value int to store bits from
+ * @param count number of low-order bits to store
+ * @param startPos start bit location in array to begin writing
+ * @param dest array to store bits in. dest MUST have enough space to store
+ * the given data, or this function will fail.
+ */
+ protected static void addInt_new(int value, int count, int startPos, byte[] dest) {
+ if(count <= 0) {
+ return;
+ }
+ int secondInt = 0;
+ int secondCount = 0;
+ boolean doWrite = true;
+ while(doWrite) {
+ secondCount = (startPos%8+count)-32;
+ int mask = (32-count >=32) ? 0:0xFFFFFFFF >>> (32-count);
+ value = value & mask;//clean value
+ boolean onIndex = (startPos%8 == 0);
+ if(secondCount > 0) {
+ value = (secondCount >= 32) ? 0:value>>>secondCount;//shift high-order down to write first
+ secondInt = value;
+ //secondCount = count2;
+ count -= secondCount;
+ }
+ int index = startPos/8;
+ int workingIntCache = 0;
+ int bytesToUse = dest.length-startPos/8;
+ if(bytesToUse > 4) bytesToUse = 4;
+ switch(bytesToUse) {
+ case 4: workingIntCache = dest[index++] << 24 | dest[index++] << 16 | dest[index++] << 8 | dest[index];break;
+ case 3: workingIntCache = dest[index++] << 24 | dest[index++] << 16 | dest[index++] << 8;break;
+ case 2: workingIntCache = dest[index++] << 24 | dest[index++] << 16;break;
+ case 1: workingIntCache = dest[index++] << 24;break;
+ }
+ if(!onIndex) {
+ int shiftCount = 32-(startPos%8+count);
+ value = (shiftCount >= 32) ? 0:value << shiftCount;
+ int workingInt = workingIntCache;
+ mask = (32-startPos%8 >= 32) ? 0:0xFFFFFFFF<<(32-startPos%8);
+ workingInt = workingInt & mask;//clear lower bits
+ workingInt = workingInt | value;
+ shiftCount = (32-count-startPos%8);
+ value = (shiftCount >= 32) ? 0:workingInt>>>shiftCount;
+ }
+ int shiftCount = (32-count-startPos%8);
+ value = (shiftCount >= 32) ? 0: value << shiftCount;//place into upper bits, we fill from top
+ int workingInt = workingIntCache;
+ mask = (count+startPos%8 >= 32) ? 0:0xFFFFFFFF >>> (count+startPos%8);
+ workingInt = workingInt & mask;//clear upper bits
+ workingInt = workingInt | value;
+ index = startPos/8;
+ int tempIndex = index+bytesToUse-1;
+ index+= bytesToUse;
+ switch(bytesToUse) {
+ case 4: dest[tempIndex--] = (byte)(workingInt);
+ case 3: dest[tempIndex--] = (byte)(workingInt >>> 8);
+ case 2: dest[tempIndex--] = (byte)(workingInt >>> 16);
+ case 1: dest[tempIndex--] = (byte)(workingInt >>> 24);
+ }
+ if(secondCount > 0) {
+ //System.err.println("\t\tsecondCount > 0");
+ startPos+=count;
+ count = secondCount;
+ value = secondInt;
+ }
+ else
+ doWrite = false;
+ }
+ }
+
+ /** public static void addIntOld(int input, int count, int startPos, byte[] dest) {
+ if(DEBUG_LEV > 30)
+ System.err.println("EncodedElement::addInt : Begin");
+ int currentByte = startPos/8;
+ int currentOffset = startPos%8;
+ int bitRoom;//how many bits can be placed in current byte
+ int upMask;//to clear upper bits(lower bits auto-cleared by L-shift
+ int downShift;//bits to shift down, isolating top bits of input
+ int upShift;//bits to shift up, packing byte from top.
+ while(count > 0) {
+ //find how many bits can be placed in current byte
+ bitRoom = 8-currentOffset;
+ //get those bits
+ //i.e, take upper 'bitsNeeded' of input, put to lower part of byte.
+ downShift = count-bitRoom;
+ upMask = 255 >>> currentOffset;
+ upShift = 0;
+ if(downShift < 0) {
+ //upMask = 255 >>> bitRoom-count;
+ upShift = bitRoom - count;
+ upMask = 255 >>> (currentOffset+upShift);
+ downShift = 0;
+ }
+ if(DEBUG_LEV > 30) {
+ System.err.println("count:offset:bitRoom:downShift:upShift:" +
+ count+":"+currentOffset+":"+bitRoom+":"+downShift+":"+upShift);
+ }
+ int currentBits = (input >>> downShift) & (upMask);
+ //shift bits back up to match offset
+ currentBits = currentBits << upShift;
+ upMask = (byte)upMask << upShift;
+
+ dest[currentByte] = (byte)(dest[currentByte] & (~upMask));
+ //merge bytes~
+ dest[currentByte] = (byte)(dest[currentByte] | currentBits);
+ //System.out.println("new currentByte: " + dest[currentByte]);
+ count -= bitRoom;
+ currentOffset = 0;
+ currentByte++;
+ }
+ if(DEBUG_LEV > 30)
+ System.err.println("EncodedElement::addInt : End");
+ }
+**/
+
+ /**
+ * This method adds a given number of bits of a long to a byte array.
+ * @param input long to store bits from
+ * @param count number of low-order bits to store
+ * @param startPos start bit location in array to begin writing
+ * @param dest array to store bits in. dest MUST have enough space to store
+ * the given data, or this function will fail.
+ */
+ private static void addLong(long input, int count, int startPos, byte[] dest) {
+ if(DEBUG_LEV > 30)
+ System.err.println("EncodedElement::addLong : Begin");
+ int currentByte = startPos/8;
+ int currentOffset = startPos%8;
+ int bitRoom;//how many bits can be placed in current byte
+ long upMask;//to clear upper bits(lower bits auto-cleared by L-shift
+ int downShift;//bits to shift down, isolating top bits of input
+ int upShift;//bits to shift up, packing byte from top.
+ while(count > 0) {
+ //find how many bits can be placed in current byte
+ bitRoom = 8-currentOffset;
+ //get those bits
+ //i.e, take upper 'bitsNeeded' of input, put to lower part of byte.
+ downShift = count-bitRoom;
+ upMask = 255 >>> currentOffset;
+ upShift = 0;
+ if(downShift < 0) {
+ //upMask = 255 >>> bitRoom-count;
+ upShift = bitRoom - count;
+ upMask = 255 >>> (currentOffset+upShift);
+ downShift = 0;
+ }
+ if(DEBUG_LEV > 30) {
+ System.err.println("count:offset:bitRoom:downShift:upShift:" +
+ count+":"+currentOffset+":"+bitRoom+":"+downShift+":"+upShift);
+ }
+ long currentBits = (input >>> downShift) & (upMask);
+ //shift bits back up to match offset
+ currentBits = currentBits << upShift;
+ upMask = (byte)upMask << upShift;
+
+ dest[currentByte] = (byte)(dest[currentByte] & (~upMask));
+ //merge bytes~
+ dest[currentByte] = (byte)(dest[currentByte] | currentBits);
+ //System.out.println("new currentByte: " + dest[currentByte]);
+ count -= bitRoom;
+ currentOffset = 0;
+ currentByte++;
+ }
+ if(DEBUG_LEV > 30)
+ System.err.println("EncodedElement::addLong : End");
+ }
+
+ /** public static void packIntOLD_WORKING(int[] inputArray, int startBitSize, int startPos,
+ int start, int skip, int count, byte[] dest) {
+ if(DEBUG_LEV > 0)
+ System.err.println("EncodedElement::packInt : Begin");
+ if(DEBUG_LEV > 10)
+ System.err.println("start:skip:count : " +start+":"+skip+":"+count);
+ for(int i = 0; i < count; i++) {
+ addInt(inputArray[i*(skip+1)+start], startBitSize, startPos, dest);
+ startPos+=startBitSize;
+ }
+ }
+**/
+
+ /**
+ * Append an equal number of bits from each int in an array within given
+ * limits to the given byte array.
+ *
+ * @param inputArray Array storing input values.
+ * @param bitSize number of bits to store from each value.
+ * @param start index of first usable index.
+ * @param skip number of indices to skip between values(in case input data
+ * is interleaved with non-desirable data).
+ * @param countA Number of total indices to store from.
+ * @param startPosIn First usable index in destination array(byte
+ * index = startPosIn/8, bit within that byte = startPosIn%8)
+ * @param dest Destination array to store input values in. This array *must*
+ * be large enough to store all values or this method will fail in an
+ * undefined manner.
+ */
+ private static void packInt(int[] inputArray, int bitSize, int startPosIn,
+ int start, int skip, int countA, byte[] dest) {
+ if(DEBUG_LEV > 30)
+ System.err.println("EncodedElement::packInt : Begin");
+ for(int valI = 0; valI < countA; valI++) {
+ //int input = inputArray[valI];
+ int input = inputArray[valI*(skip+1)+start];
+ int count = bitSize;
+ int startPos = startPosIn+valI*bitSize;
+ int currentByte = startPos/8;
+ int currentOffset = startPos%8;
+ int bitRoom;//how many bits can be placed in current byte
+ int upMask;//to clear upper bits(lower bits auto-cleared by L-shift
+ int downShift;//bits to shift down, isolating top bits of input
+ int upShift;//bits to shift up, packing byte from top.
+ while(count > 0) {
+ //find how many bits can be placed in current byte
+ bitRoom = 8-currentOffset;
+ //get those bits
+ //i.e, take upper 'bitsNeeded' of input, put to lower part of byte.
+ downShift = count-bitRoom;
+ //upMask = uRSHFT(255 ,currentOffset);
+ upMask = (currentOffset >= 32) ? 0: 255>>>currentOffset;
+ upShift = 0;
+ if(downShift < 0) {
+ //upMask = 255 >>> bitRoom-count;
+ upShift = bitRoom - count;
+ //upMask = uRSHFT(255,(currentOffset+upShift));
+ upMask = ((currentOffset+upShift) >= 32) ? 0:255>>>(currentOffset+upShift);
+ downShift = 0;
+ }
+ if(DEBUG_LEV > 30) {
+ System.err.println("count:offset:bitRoom:downShift:upShift:" +
+ count+":"+currentOffset+":"+bitRoom+":"+downShift+":"+upShift);
+ }
+ //int currentBits = uRSHFT(input, downShift) & (upMask);
+ int currentBits = (downShift >= 32) ? 0:(input>>>downShift)&upMask;
+ //shift bits back up to match offset
+ //currentBits = lSHFT(currentBits, upShift);
+ currentBits = (upShift >= 32) ? 0:currentBits << upShift;
+
+ //upMask = lSHFT((byte)upMask, upShift);
+ upMask = (upShift >= 32) ? 0:((byte)upMask)<<upShift;
+
+ dest[currentByte] = (byte)(dest[currentByte] & (~upMask));
+ //merge bytes~
+ dest[currentByte] = (byte)(dest[currentByte] | currentBits);
+ //System.out.println("new currentByte: " + dest[currentByte]);
+ count -= bitRoom;
+ currentOffset = 0;
+ currentByte++;
+ }
+ }
+ if(DEBUG_LEV > 30)
+ System.err.println("EncodedElement::packInt: End");
+ }
+
+ /**
+ * Force the usable data stored in this list ends on a a byte boundary, by
+ * padding to the end with zeros.
+ *
+ * @return true if the data was padded, false if it already ended on a byte
+ * boundary.
+ */
+ public boolean padToByte() {
+ boolean padded = false;
+
+ EncodedElement end = EncodedElement.getEnd_S(this);
+ int tempVal = end.usableBits;
+ if(tempVal % 8 != 0) {
+ int toWrite = 8-(tempVal%8);
+ end.addInt(0, toWrite);
+ /* Assert FOR DEVEL ONLY: */
+ assert((this.getTotalBits()+offset) % 8 == 0);
+ padded = true;
+ }
+ return padded;
+ }
+
+ public short getCRC16() {
+ assert(getTotalBits()%8 == 0);
+ assert(offset == 0);
+ CRC16 crc = new CRC16();
+
+ byte[] input = this.data;
+ int stop = this.usableBits/8;
+ crc.update(input, 0, stop);
+ EncodedElement nextEl = this.getNext();
+ if(nextEl != null) {
+ byte partial = (this.usableBits%8==0)? 0:input[stop];
+ //byte partial = input[stop];
+ if(usableBits%8 != 0)
+ System.err.println("UsableBits%8 == "+usableBits%8);
+ nextEl.getCRC16(partial,this.usableBits%8, crc);
+ }
+
+ return crc.checksum();
+ }
+
+ private void getCRC16(byte leadByte, int bitCount, CRC16 crc) {
+ assert(bitCount == offset%8);
+ //combine lead bytes
+ int start = offset/8;
+ int stop = usableBits/8;
+ byte[] input = this.data;
+ if(bitCount > 0) {
+ int inputByteMask = (0xFF >>> bitCount) &0xFF;
+ int leadByteMask = 0xFF <<(8-bitCount);
+ byte fullByte = (byte)(input[start] & inputByteMask);
+ leadByte = (byte)(leadByte & leadByteMask);
+ fullByte = (byte)(fullByte | leadByte);
+ start+=1;
+ crc.update(fullByte);
+ }
+ //getCRC16
+ crc.update(input,start,stop);
+ //pass to next
+ EncodedElement nextEl = getNext();
+ if(nextEl != null) {
+ byte partial = (this.usableBits%8==0)? 0:data[stop];
+ nextEl.getCRC16(partial,this.usableBits%8, crc);
+ }
+ }
+
+ protected void print() {
+ System.err.println("EncodedElement 0: ");
+ System.err.println("\toffset: "+offset);
+ System.err.println("\tusableBits: "+usableBits);
+ System.err.println("\tdataLength: "+data.length);
+ System.err.println("\tlastIndex: "+usableBits/8);
+ System.err.println("\tleftoverBits: "+usableBits%8);
+ if(next != null)
+ next.print(1);
+ }
+ protected void print(int childCount) {
+ System.err.println("EncodedElement "+(childCount++) + ": ");
+ System.err.println("\toffset: "+offset);
+ System.err.println("\tusableBits: "+usableBits);
+ System.err.println("\tdataLength: "+data.length);
+ System.err.println("\tlastIndex: "+usableBits/8);
+ System.err.println("\tleftoverBits: "+usableBits%8);
+ if(next != null)
+ next.print(childCount);
+ }
+
+ protected static int packIntByBitsToByteBoundary(int[] input, int[] inputBits, int inputIndex,
+ int inputCount, int destPos, byte[] dest) {
+ int bitsNeeded = destPos % 8;
+ if(bitsNeeded != 0) bitsNeeded = 8-bitsNeeded;
+ while(bitsNeeded > 0 && inputCount > 0) {
+ int inputVal = input[inputIndex];
+ int inBits = inputBits[inputIndex];
+ //if inBits > bitNeeded. shift value down, write what we need, done
+ //else: take what we can, increment input index, try next
+ if(inBits > bitsNeeded) {
+ inputVal = (inBits-bitsNeeded >=32) ? 0:inputVal>>>(inBits-bitsNeeded);
+ EncodedElement.addInt_new(inputVal, bitsNeeded, destPos, dest);
+ destPos += bitsNeeded;
+ inputBits[inputIndex] = inBits-bitsNeeded;
+ bitsNeeded = 0;
+ }
+ else {
+ if(inBits > 0) {
+ EncodedElement.addInt_new(inputVal, inBits, destPos, dest);
+ destPos += inBits;
+ inputBits[inputIndex] = 0;
+ bitsNeeded -= inBits;
+ }
+ inputIndex++;
+ inputCount--;
+ }
+ }
+ if(inputCount == 0) {
+ inputIndex = -1;
+ }
+ return inputIndex;
+ }
+
+ /**
+ * Pack a number of bits from each int of an array(within given limits)to
+ * the end of this list.
+ *
+ * @param inputA Array containing input values.
+ * @param inputBits Array containing number of bits to use for each index
+ * packed. This array should be equal in size to the inputA array.
+ * @param inputOffset Index of first usable index.
+ * @param countA Number of indices to pack.
+ * @param startPosIn First usable bit-level index in destination array(byte
+ * index = startPosIn/8, bit within that byte = startPosIn%8)
+ * @param dest Destination array to store input values in. This array *must*
+ * be large enough to store all values or this method will fail in an
+ * undefined manner.
+ */
+ protected static void packIntByBits(int[] inputA, int[] inputBits, int inputIndex,
+ int inputCount, int destPos, byte[] dest) {
+ int origInputIndex = inputIndex;
+ inputIndex = packIntByBitsToByteBoundary(inputA, inputBits, inputIndex, inputCount,
+ destPos, dest);
+ if(destPos%8 > 0) destPos = (destPos/8+1)*8;//put dest where we know it should be
+ if(inputIndex < 0)//done, no more to write.
+ return;
+
+ inputCount = inputCount - (inputIndex - origInputIndex);
+ inputCount = EncodedElement.compressIntArrayByBits(inputA, inputBits, inputCount, inputIndex);
+ assert(destPos%8 == 0);//sanity check.
+ if(inputCount >1) {
+ int stopIndex = inputCount-1;
+ EncodedElement.mergeFullOnByte(inputA, stopIndex, dest, destPos/8);
+ destPos += (stopIndex)*32;
+ }
+ if(inputCount >0) {
+ int index = inputCount-1;
+ EncodedElement.addInt_new(inputA[index], inputBits[index], destPos, dest);
+ destPos+=inputBits[index];
+ }
+ }
+
+ protected static int cleanInts(int[] input, int[] inputBits, int inputIndex, int count) {
+ int outIndex = 0;
+ for(int i = inputIndex; i < inputIndex+count; i++) {
+ if(inputBits[i] > 0) {
+ int mask = 0xFFFFFFFF >>>(32-inputBits[i]);
+ inputBits[outIndex] = inputBits[i];
+ input[outIndex++] = input[i] & mask;
+ }
+ }
+ return outIndex;
+ }
+
+ protected static int compressIntArrayByBits(int[] input, int[] inputBits, int inCount,
+ int inputIndex) {
+ inCount = cleanInts(input, inputBits, inputIndex, inCount);
+
+ int outIndex = 0;
+ int workingVal = 0;
+ int workingBits = 0;
+ for(int i = 0; i < inCount; i++) {
+ //look at bits for next number:
+ //if workingBits+bits <= 32, shift int up and OR into workingVal;
+ //if workingBits+bits > 32, shift down and OR into workingVal, add to output, shift leftovers up and set workingVal
+ int bits = inputBits[i];
+ if(bits+workingBits <= 32) {
+ workingBits += bits;
+ workingVal |= (input[i] << (32-workingBits));
+ if(workingBits == 32) {
+ inputBits[outIndex] = workingBits;
+ input[outIndex++] = workingVal;
+ workingBits = 0;
+ workingVal = 0;
+ }
+ }
+ else {
+ workingBits += bits;
+ workingVal |= input[i] >>> (workingBits-32);
+ inputBits[outIndex] = workingBits;
+ input[outIndex++] = workingVal;
+ workingBits = workingBits-32;
+ workingVal = input[i] << (32-workingBits);
+ }
+ }
+
+ if(workingBits >0) {
+ inputBits[outIndex] = workingBits;
+ input[outIndex++] = workingVal>>>(32-workingBits);
+ }
+ else if(workingBits == 0 && outIndex == 0)//nothing written!
+ outIndex = -1;
+ return outIndex;
+ }
+
+ protected static void mergeFullOnByte(int[] input, int inCount, byte[] dest, int destIndex) {
+ //System.err.println("mergeFullOnByte::begin inBitCount: "+inCount+" :: destBitOffset: "+destIndex);
+ int INPUT_WIDTH = Integer.SIZE;
+ int DEST_WIDTH = Byte.SIZE;
+ assert(inCount*(INPUT_WIDTH/DEST_WIDTH) <= dest.length-destIndex);//input must fit fully inside dest
+ for(int i = 0; i < inCount; i++) {
+ int inVal = input[i];
+ dest[destIndex++] = (byte)(inVal >>> 24);
+ dest[destIndex++] = (byte)(inVal >>> 16);
+ dest[destIndex++] = (byte)(inVal >>> 8);
+ dest[destIndex++] = (byte)(inVal);
+ }
+ }
+
+ private static final int uRSHFT2(int value,int count) {
+ if(count >= 32)
+ return 0;
+ else
+ return value >>> count;
+ }
+ private static long uRSHFT_L(long value, int count) {
+ if(count >= 64)
+ return 0;
+ else
+ return value >>> count;
+ }
+ private static final int lSHFT2(int value, int count) {
+ if(count >= 32)
+ return 0;
+ else
+ return (value << count);
+ }
+ private static final long lSHFT_L(long value, int count) {
+ if(count >= 64)
+ return 0;
+ else
+ return value << count;
+ }
+} \ No newline at end of file
diff --git a/src/javaFlacEncoder/EncodedElement_32.java b/src/javaFlacEncoder/EncodedElement_32.java
new file mode 100644
index 0000000..9a6efe2
--- /dev/null
+++ b/src/javaFlacEncoder/EncodedElement_32.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+
+/**
+ * EncodedElement which uses an integer array as a backing, rather than the
+ * byte array.
+ *
+ * @author Preston Lacey
+ */
+@Deprecated
+public class EncodedElement_32 extends EncodedElement {
+ int[] data_32 = null;
+ boolean byteArrayValid = false;
+ public EncodedElement_32() {
+ offset = 0;
+ usableBits = 0;
+ data = null;
+ data_32 = new int[100];
+ }
+
+ public EncodedElement_32(int size, int off) {
+ data = null;
+ usableBits = off;
+ offset = off;
+ data_32 = new int[size];
+ }
+
+ @Override
+ public void clear(int size, int off) {
+ next = null;
+ previous = null;
+ data = null;
+ data_32 = new int[size];
+ offset = off;
+ usableBits = off;
+ byteArrayValid = false;
+ }
+
+
+ /**
+ * This method adds a given number of bits of an int to a byte array.
+ * @param value int to store bits from
+ * @param count number of low-order bits to store
+ * @param startPos start bit location in array to begin writing
+ * @param dest array to store bits in. dest MUST have enough space to store
+ * the given data, or this function will fail.
+ */
+ public static void addInt(int value, int count, int startPos, int[] dest) {
+ assert(count <= 32);
+ assert(count > 0);
+ assert(startPos >= 0);
+ //System.err.println("addInt("+value+", "+count + ", "+startPos+")");
+ /*
+ * Because we're using both 32 bit input and 32 bit output, we only have
+ * two cases to handle:
+ * 1) All bits fit in one index, appropriately shifted up first.
+ * Mask upper bits we'll merge with(Ones in upper).
+ * Mask lower bits we'll merge with(Ones in lower).
+ * destMask = OR Upper and lower masks together.
+ * currentIndex = currentIndex & destMask
+ * destMask = ~destMask;
+ * upshift by 32-count-currentOffset;
+ * inputValue = inputValue & destMask;
+ * currentIndex = currentIndex | inputValue
+ * 2) Some bits merge in top of one index, remaining bits merge in
+ * bottom of second index
+ * A) CurrentIndex
+ * Create inputValueHigh, with high order bits which will enter
+ * currentIndex.
+ * Handle this as we handled case one(pretend it's a new value)
+ * B) CurrentIndex++
+ * We fill from upper edge. No upper mask needed.
+ * upshift value by 32-count;
+ * Mask lower bits we'll merge with(Ones in lower)
+ * currentIndex = currentIndex & destMask
+ * currentIndex = currentIndex | destMask
+ */
+ int currentIndex = startPos/32;
+ int currentOffset = startPos%32;
+
+ int totalSize = count+currentOffset;
+
+ if(totalSize > 32) {
+ //System.err.println("totalSize > 32");
+ int secondIndex = currentIndex + 1;
+ int secondSize = totalSize - 32;
+ int secondValue = value << (32-secondSize);
+ int lowerMask = -1 >>> secondSize;
+ int temp = dest[secondIndex] & lowerMask;
+ dest[secondIndex] = temp | secondValue;
+ totalSize = 32;
+ value = value >>> secondSize;
+ }
+
+ if(totalSize <= 32) {//Case 1
+ int upperMask = -2 << (31-currentOffset);
+ //int lowerMask = Integer.MAX_VALUE >>> (totalSize-1);
+ int lowerMask = 0x7FFFFFFF >>> (totalSize-1);
+ int destMask = upperMask | lowerMask;
+ int temp = dest[currentIndex] & destMask;
+ destMask = ~destMask;
+ value = value << (32-totalSize);
+ value = value & destMask;
+ dest[currentIndex] = temp | value;
+ }
+ }
+
+ public int[] getData32() { return data_32; }
+ private static byte[] convertIntArrayToByteArray(int[] input) {
+ byte[] result = new byte[input.length*4];
+ for(int i = 0; i < input.length; i++) {
+ //byte 3 = byte 0
+ //byte 2 = byte 1
+ //byte 1 = byte 2
+ //byte 0 = byte 3
+ int byteBase = i*4;
+ int value = input[i];
+ result[byteBase+0] = (byte)(value >> 24);
+ result[byteBase+1] = (byte)(value >> 16);
+ result[byteBase+2] = (byte)(value >> 8);
+ result[byteBase+3] = (byte)(value);
+ }
+ return result;
+ }
+
+
+
+ /**
+ * Pack a number of bits from each int of an array(within given limits)to
+ * the end of this list.
+ *
+ * @param inputA Array containing input values.
+ * @param inputBits Array containing number of bits to use for each index
+ * packed. This array should be equal in size to the inputA array.
+ * @param inputOffset Index of first usable index.
+ * @param countA Number of indices to pack.
+ * @param startPosIn First usable bit-level index in destination array(byte
+ * index = startPosIn/8, bit within that byte = startPosIn%8)
+ * @param dest Destination array to store input values in. This array *must*
+ * be large enough to store all values or this method will fail in an
+ * undefined manner.
+ */
+ public static void packIntByBits(int[] inputA, int[] inputBits, int inputOffset,
+ int countA, int startPosIn, int[] dest) {
+ if(DEBUG_LEV > 30)
+ System.err.println("EncodedElement::packIntByBits : Begin");
+ //int offsetCounter = 0;
+ int startPos = startPosIn;//the position to write to in output array
+ int inputStop = countA+inputOffset;
+ for(int valI = inputOffset; valI < inputStop; valI++) {
+ //inputIter = valI+inputOffset;
+ //inputIter += valI;
+ //int input = inputA[valI];//value to encode
+ int value = inputA[valI];
+ int count = inputBits[valI];//bits of value to encode
+ //EncodedElement_32.addInt(input, count, startPos, dest);
+ int currentIndex = startPos/32;
+ int currentOffset = startPos%32;
+
+ int totalSize = count+currentOffset;
+
+ if(totalSize > 32) {
+ //System.err.println("totalSize > 32");
+ int secondIndex = currentIndex + 1;
+ int secondSize = totalSize - 32;
+ int secondValue = value << (32-secondSize);
+ int lowerMask = -1 >>> secondSize;
+ int temp = dest[secondIndex] & lowerMask;
+ dest[secondIndex] = temp | secondValue;
+ totalSize = 32;
+ value = value >>> secondSize;
+ }
+
+ //if(totalSize <= 32) {//Case 1
+ int upperMask = -2 << (31-currentOffset);
+ int lowerMask = 0x7FFFFFFF >>> (totalSize-1);
+ int destMask = upperMask | lowerMask;
+ int temp = dest[currentIndex] & destMask;
+ destMask = ~destMask;
+ value = value << (32-totalSize);
+ value = value & destMask;
+ dest[currentIndex] = temp | value;
+ startPos += count;//startPos must not be referenced again below here!
+ }
+ if(DEBUG_LEV > 30)
+ System.err.println("EncodedElement::addInt : End");
+ }
+
+ /**
+ * Pack a number of bits from each int of an array(within given limits)to
+ * the end of this list.
+ *
+ * @param inputA Array containing input values.
+ * @param inputBits Array containing number of bits to use for each index
+ * packed. This array should be equal in size to the inputA array.
+ * @param inputOffset Index of first usable index.
+ * @param countA Number of indices to pack.
+ * @return EncodedElement containing end of packed data. Data may flow
+ * between multiple EncodedElement's, if an existing element was not large
+ * enough for all values.
+ */
+ @Override
+ public EncodedElement packIntByBits(int[] inputA, int[] inputBits, int inputOffset,
+ int countA) {
+ byteArrayValid = false;
+ //go to end if we're not there.
+ if(next != null) {
+ EncodedElement end = EncodedElement_32.getEnd_S(next);
+ return end.packIntByBits(inputA, inputBits, inputOffset, countA);
+ }
+
+ //calculate how many we can pack into current.
+ int writeBitsRemaining = data_32.length*32 - usableBits;
+ int willWrite = 0;
+ int writeCount = 0;
+ //System.err.println("writeBitsRemaining: " + writeBitsRemaining);
+ for(int i = 0; i < countA; i++) {
+ writeBitsRemaining -= inputBits[inputOffset+i];
+ if(writeBitsRemaining >= 0) {
+ writeCount++;
+ willWrite += inputBits[inputOffset+i];
+ }
+ else
+ break;
+ }
+ //pack them and update usable bits.
+ if(writeCount > 0) {
+ EncodedElement_32.packIntByBits(inputA, inputBits, inputOffset,
+ writeCount, usableBits, data_32);
+ usableBits += willWrite;
+ }
+ //if more remain, create child object and add there
+ countA -= writeCount;
+ if(countA > 0) {
+ inputOffset += writeCount;
+ int tOff = usableBits %32;
+ int size = data_32.length/2+1;
+ //guarantee that our new element can store our given value
+ int remainingToWrite = 0;
+ for(int i = 0; i < countA; i++) {
+ remainingToWrite += inputBits[inputOffset+i];
+ }
+ remainingToWrite = remainingToWrite / 8 + 1;
+ if(size < remainingToWrite) size = remainingToWrite+10;
+ //System.err.println("remaining: "+remainingToWrite);
+ //System.err.println("creating size/offset : "+size+":"+tOff);
+ next = new EncodedElement_32(size, tOff);
+ //add int to child
+ return next.packIntByBits(inputA, inputBits, inputOffset, countA);
+ }
+ else {
+ //System.err.println("returning....done");
+ //return if this is last object we wrote to.
+ return this;
+ }
+ }
+
+ @Override
+ public byte[] getData() {
+ // return convertIntArrayToByteArray(data_32);
+ if(byteArrayValid)
+ return data;
+ byteArrayValid = true;
+ if(data == null)
+ data = new byte[data_32.length*4];
+ byte[] result = data;
+ for(int i = 0; i < data_32.length; i++) {
+ //byte 3 = byte 0
+ //byte 2 = byte 1
+ //byte 1 = byte 2
+ //byte 0 = byte 3
+ int byteBase = i*4;
+ int value = data_32[i];
+ result[byteBase+0] = (byte)(value >> 24);
+ result[byteBase+1] = (byte)(value >> 16);
+ result[byteBase+2] = (byte)(value >> 8);
+ result[byteBase+3] = (byte)(value);
+ }
+ data = result;
+ return result;
+ }
+
+ public EncodedElement convertToEncodedElement() {
+ EncodedElement ele = new EncodedElement();
+ byte[] result = new byte[data_32.length*4];
+ int byteBase = -4;
+ int valueIndex = 0;
+ /*for(int i = 0; i < data_32.length/4; i++) {
+ int value0 = data_32[valueIndex++];
+ int value1 = data_32[valueIndex++];
+ int value2 = data_32[valueIndex++];
+ int value3 = data_32[valueIndex++];
+
+ byteBase += 7;
+ result[byteBase--] = (byte)(value0);
+ value0 = value0 >> 8;
+ result[byteBase--] = (byte)(value0);
+ value0 = value0 >> 8;
+ result[byteBase--] = (byte)(value0);
+ value0 = value0 >> 8;
+ result[byteBase] = (byte)(value0);
+
+ byteBase += 7;
+ result[byteBase--] = (byte)(value1);
+ value1 = value1 >> 8;
+ result[byteBase--] = (byte)(value1);
+ value1 = value1 >> 8;
+ result[byteBase--] = (byte)(value1);
+ value1 = value1 >> 8;
+ result[byteBase] = (byte)(value1);
+
+ byteBase += 7;
+ result[byteBase--] = (byte)(value2);
+ value2 = value2 >> 8;
+ result[byteBase--] = (byte)(value2);
+ value2 = value2 >> 8;
+ result[byteBase--] = (byte)(value2);
+ value2 = value2 >> 8;
+ result[byteBase] = (byte)(value2);
+
+ byteBase += 7;
+ result[byteBase--] = (byte)(value3);
+ value3 = value3 >> 8;
+ result[byteBase--] = (byte)(value3);
+ value3 = value3 >> 8;
+ result[byteBase--] = (byte)(value3);
+ value3 = value3 >> 8;
+ result[byteBase] = (byte)(value3);
+ }*/
+
+ for(int i = 3 ; i < data_32.length*4; i+=7) {
+ //byte 3 = byte 0
+ //byte 2 = byte 1
+ //byte 1 = byte 2
+ //byte 0 = byte 3
+ //int byteBase = i*4+3;
+ //byteBase += 7;
+ int value = data_32[valueIndex++];
+ result[i--] = (byte)(value);
+ value = value >> 8;
+ result[i--] = (byte)(value);
+ value = value >> 8;
+ result[i--] = (byte)(value);
+ value = value >> 8;
+ result[i] = (byte)(value);
+
+ /*result[byteBase++] = (byte)(value >> 24);
+ result[byteBase++] = (byte)(value >> 16);
+ result[byteBase++] = (byte)(value >> 8);
+ result[byteBase] = (byte)(value);*/
+ }
+
+ //ele.data = convertIntArrayToByteArray(data_32);
+ ele.data = result;
+ ele.offset = offset;
+ ele.usableBits = usableBits;
+ return ele;
+ }
+ /**
+ * Add a number of bits from an int to the end of this list's data. Will
+ * add a new element if necessary. The bits stored are taken from the lower-
+ * order of input.
+ *
+ * @param input Int containing bits to append to end.
+ * @param bitCount Number of bits to append.
+ * @return EncodedElement which actually contains the appended value.
+ */
+ @Override
+ public EncodedElement addInt(int input, int bitCount) {
+ byteArrayValid = false;
+ if(next != null) {
+ EncodedElement end = EncodedElement_32.getEnd_S(next);
+ return end.addInt(input, bitCount);
+ }
+ else if(data_32.length*32 < usableBits+bitCount) {
+ //create child and attach to next.
+ //Set child's offset appropriately(i.e, manually set usable bits)
+ int tOff = usableBits %32;
+ //int size = data.length/2+1;
+ int size = 1000;
+ //guarantee that our new element can store our given value
+ //if(size <= bitCount+tOff) size = (size+tOff+bitCount)*10;
+
+ next = new EncodedElement_32(size, tOff);
+ System.err.println("creating next node of size:bitCount "+size+
+ ":"+bitCount+":"+usableBits+":"+data_32.length);
+ System.err.println("value: "+input);
+ //+this.toString()+"::"+next.toString());
+ //add int to child
+ return next.addInt(input, bitCount);
+ }
+ else {
+ //At this point, we have the space, and we are the end of the chain.
+ int startPos = this.usableBits;
+ int[] dest = this.data_32;
+ EncodedElement_32.addInt(input, bitCount, startPos, dest);
+ usableBits += bitCount;
+ return this;
+ }
+ }
+}
diff --git a/src/javaFlacEncoder/EncodingConfiguration.java b/src/javaFlacEncoder/EncodingConfiguration.java
new file mode 100644
index 0000000..437805f
--- /dev/null
+++ b/src/javaFlacEncoder/EncodingConfiguration.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+
+/**
+ * This class defines the configuration options that are allowed to change
+ * within a FLAC stream. Options here may be changed from one frame to the next.
+ * In general, the settings should not need altered, but the option to do so
+ * remains.
+ *
+ * @author Preston Lacey
+ */
+public class EncodingConfiguration implements Cloneable {
+
+ /**
+ * Defines the options for channel configuration to use. LEFT & RIGHT
+ * channels refers to stereo audio as expected. INDEPENDENT channels refer to
+ * each channel encoded separately. SIDE channel is the difference of the
+ * LEFT and RIGHT channels(LEFT-RIGHT). MID channel is the integer average of
+ * LEFT and RIGHT channels( (LEFT+RIGHT)/2 ). Options using MID and/or SIDE
+ * channels may benefit encoding by taking advantage of similarities between
+ * the channels, and are available in FLAC for STEREO streams only. In
+ * general, ENCODER_CHOICE should be chosen.
+ */
+ public enum ChannelConfig {
+ /** Encode channels independently **/
+ INDEPENDENT,
+ /** Encode LEFT and SIDE channels for stereo stream */
+ LEFT_SIDE,
+ /** Encode RIGHT and SIDE channels for stereo stream */
+ RIGHT_SIDE,
+ /** Encode MID and SIDE channels for stereo stream */
+ MID_SIDE,
+ /** Encode all options possible, and take the best(slow)*/
+ EXHAUSTIVE,
+ /** Let the encoder decide which to use.(recommended) */
+ ENCODER_CHOICE
+ };
+
+ /**
+ * Defines the various subframe types that may be used. If you don't know
+ * what these are, choose EXHAUSTIVE(for description of subframe types, see
+ * the flac format documentation at http://flac.sourceforge.net/format.html)
+ */
+ public enum SubframeType {
+ /** Constant subframe, do not choose unless you are sure you're encoding
+ * digital silence */
+ CONSTANT,
+ /** Decent compression, fasted option */
+ FIXED,
+ /** Better compression, slower */
+ LPC,
+ /** No compression, simply wraps unencoded audio into a FLAC stream */
+ VERBATIM,
+ /** Best compression, slightly slower than LPC alone, lets encoder choose
+ * the best(Recommended). */
+ EXHAUSTIVE
+ }
+
+ /** Maximum LPC order possible(as defined by FLAC format) */
+ public static final int MAX_LPC_ORDER = 32;
+ /** Minimum LPC order possible(as defined by FLAC format) */
+ public static final int MIN_LPC_ORDER = 1;
+ /** Maximum Rice Partition order possible(as defined by FLAC Format) */
+ public static final int MAX_RICE_PARTITION_ORDER = 15;
+ /** Default subframe type to use*/
+ public static final SubframeType DEFAULT_SUBFRAME_TYPE = SubframeType.EXHAUSTIVE;
+ /** Default channel configuration */
+ public static final ChannelConfig DEFAULT_CHANNEL_CONFIG = ChannelConfig.ENCODER_CHOICE;
+ /** Default maximum lpc order to use */
+ public static final int DEFAULT_MAX_LPC_ORDER = 12;
+ /** Default minimum lpc order to use */
+ public static final int DEFAULT_MIN_LPC_ORDER = 1;
+ /** Default maximum Rice partition order */
+ public static final int DEFAULT_MAX_RICE_ORDER = 0;
+
+
+ ChannelConfig channelConfig;
+ SubframeType subframeType;
+ int minimumLPCOrder = 1;
+ int maximumLPCOrder = 16;
+ int maximumRicePartitionOrder = 0;
+
+ /**
+ * Constructor, uses defaults for all options. These defaults should be good
+ * for most purposes.
+ */
+ public EncodingConfiguration() {
+ subframeType = DEFAULT_SUBFRAME_TYPE;
+ channelConfig = DEFAULT_CHANNEL_CONFIG;
+ maximumLPCOrder = DEFAULT_MAX_LPC_ORDER;
+ minimumLPCOrder = DEFAULT_MIN_LPC_ORDER;
+ maximumRicePartitionOrder = DEFAULT_MAX_RICE_ORDER;
+ }
+
+ /**
+ * Copy constructor.
+ * @param e EncodingConfiguration object to copy. Must not be null.
+ */
+ public EncodingConfiguration(EncodingConfiguration e) {
+ subframeType = e.subframeType;
+ channelConfig = e.channelConfig;
+ minimumLPCOrder = e.minimumLPCOrder;
+ maximumLPCOrder = e.maximumLPCOrder;
+ maximumRicePartitionOrder = e.maximumRicePartitionOrder;
+ }
+
+ /**
+ * Set the channel configuration to use. Channel configuration refers to the
+ * way multiple channels are processed. See documentation for
+ * {@link ChannelConfig ChannelConfig} for more info on choices.
+ * @param conf Channel configuration to use.
+ */
+ public void setChannelConfig(ChannelConfig conf) {
+ channelConfig = conf;
+ }
+
+ /**
+ * Get the current channel configuration value.
+ * @return current channel configuration value
+ */
+ public ChannelConfig getChannelConfig() {
+ return channelConfig;
+ }
+
+ /**
+ * Set the subframe type to use. This refers to the way each subframe(channel)
+ * is compressed. See documentation for {@link SubframeType SubframeType} for
+ * more info on choices.
+ * @param type
+ */
+ public void setSubframeType(SubframeType type) {
+ subframeType = type;
+ }
+
+ /**
+ * Get the current subframe type
+ * @return current subframe type
+ */
+ public SubframeType getSubframeType() {
+ return subframeType;
+ }
+
+ /**
+ * Get current minimum LPC order
+ * @return current minimum lpc order
+ */
+ public int getMinLPCOrder() { return minimumLPCOrder; }
+
+ /**
+ * Get maximum LPC order
+ * @return current maximum lpc order
+ */
+ public int getMaxLPCOrder() { return maximumLPCOrder; }
+ /**
+ * Set the minimum LPC order. If order given is out of the valid range(as
+ * defined by {@link EncodingConfiguration#MAX_LPC_ORDER MAX_LPC_ORDER} and
+ * {@link EncodingConfiguration#MIN_LPC_ORDER MIN_LPC_ORDER}), it will be
+ * set to the closest valid value instead.
+ * @param order minimum LPC order to use
+ */
+ public void setMinLPCOrder(int order) {
+ minimumLPCOrder = (order < MIN_LPC_ORDER) ? MIN_LPC_ORDER:order;
+ minimumLPCOrder = (minimumLPCOrder > MAX_LPC_ORDER) ?
+ MAX_LPC_ORDER:minimumLPCOrder;
+ }
+ /**
+ * Set the maximum LPC order. If order given is out of the valid range
+ * (as defined by {@link EncodingConfiguration#MAX_LPC_ORDER MAX_LPC_ORDER}
+ * and {@link EncodingConfiguration#MIN_LPC_ORDER MIN_LPC_ORDER}), it will be
+ * set to the closest valid value instead.
+ * @param order maximum LPC order to use
+ */
+ public void setMaxLPCOrder(int order) {
+ maximumLPCOrder = (order < MIN_LPC_ORDER) ? MIN_LPC_ORDER:order;
+ maximumLPCOrder = (maximumLPCOrder > MAX_LPC_ORDER) ?
+ MAX_LPC_ORDER:maximumLPCOrder;
+ }
+}
diff --git a/src/javaFlacEncoder/FLACEncoder.java b/src/javaFlacEncoder/FLACEncoder.java
new file mode 100644
index 0000000..534bfb8
--- /dev/null
+++ b/src/javaFlacEncoder/FLACEncoder.java
@@ -0,0 +1,698 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+import java.security.NoSuchAlgorithmException;
+import java.io.IOException;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.locks.ReentrantLock;
+/**
+ * This class defines a FLAC Encoder with a simple interface for enabling FLAC
+ * encoding support in an application. This class is appropriate for use in the
+ * case where you have raw pcm audio samples that you wish to encode. Currently,
+ * fixed-blocksize only is implemented, and the "Maximum Block Size" set in the
+ * StreamConfiguration object is used as the actual block size.
+ * <br><br><br>
+ * An encoding process is simple, and should follow these steps:<br>
+ * <BLOCKQUOTE>
+ * 1) Set StreamConfiguration to appropriate values. After a stream is opened,
+ * this must not be altered until the stream is closed.<br>
+ * 2) Set FLACOutputStream, object to write results to.<br>
+ * 3) Open FLAC Stream<br>
+ * 4) Set EncodingConfiguration(if defaults are insufficient).<br>
+ * 5) Add samples to encoder<br>
+ * 6) Encode Samples<br>
+ * 7) Close stream<br>
+ * (note: steps 4,5, and 6 may be done repeatedly, in any order, with the
+ * exception that step 4 must not be called while a concurrent step 6 is
+ * executing(as in threaded mode). See related method documentation for info
+ * on concurrent use)
+ * (note: steps 4,5, and 6 may be done repeatedly, in any order, with the
+ * exception that step 4 must not be called while a concurrent step 6 is
+ * executing(as in threaded mode). See related method documentation for info
+ * on concurrent use. For step 7, see the documentation for the
+ * encodeSamples(...) methods' "end" parameter)
+ * </BLOCKQUOTE><br><br>
+ *
+ * @author Preston Lacey
+ */
+public class FLACEncoder {
+
+ /* For debugging, higher level equals more output */
+ int DEBUG_LEV = 0;
+
+ /**
+ * Maximum Threads to use for encoding frames(more threads than this will
+ * exist, these threads are reserved for encoding of frames only).
+ */
+ private int MAX_THREADED_FRAMES = Runtime.getRuntime().availableProcessors();
+
+ /* encodingConfig: Must never stay null(default supplied by constructor) */
+ volatile EncodingConfiguration encodingConfig = null;
+
+ /* streamConfig: Must never stay null(default supplied by constructor) */
+ volatile StreamConfiguration streamConfig = null;
+
+ /* Set true if frames are actively being encoded(can't change settings
+ * while this is true). Use streamLock for changing. */
+ volatile Boolean isEncoding = false;
+
+ /** we set this to true while a flac stream has been opened and not
+ * officially closed. Must use a streamLock to set and get this. Some actions
+ * must not be taken while a stream is opened */
+ volatile boolean flacStreamIsOpen = false;
+
+ /* Lock to use when setting/reading/using flacStreamIsOpen variable */
+ public final ReentrantLock streamLock = new ReentrantLock();
+
+ /* Lock used when adding samples and when samples must not be added.*/
+ private final ReentrantLock sampleLock = new ReentrantLock();
+
+ /* Lock used when handling a finished block */
+ private ReentrantLock blockFinishedLock = new ReentrantLock();
+
+ /* Stores unfilled BlockEncodeRequest(not ready for queue, unless ending stream */
+ volatile private BlockEncodeRequest unfilledRequest = null;
+
+ /* Stores count of inter-frame samples in unfinishedBlock */
+ volatile private int unfinishedBlockUsed = 0;
+
+ /* Frame object used to encode when not using threads */
+ volatile Frame frame = null;
+
+ /* Used to calculate MD5 hash */
+ FLAC_MD5 md5 = null;
+
+ /* threadManager used with threaded encoding */
+ BlockThreadManager threadManager = null;
+
+ /* threagedFrames keeps track of frames given to threadManager. We must still
+ * update the configurations of them as needed. If we ever create new
+ * frames(e.g, when changing stream configuration), we must create a new
+ * threadManager as well.
+ */
+ Frame[] threadedFrames = null;
+
+ /* Contains all logic for writes to the FLACOutputStream */
+ FLACStreamController flacWriter = null;
+
+ /* set when an IOException has occured that invalidates results
+ * in a child encoding thread. IOException temporarily stored by
+ * childException when this is true.*/
+ boolean error = false;
+ /* Throw this if exists, when we can, to notify main thread an exception
+ * occured in a child thread.*/
+ IOException childException = null;
+
+ /* store used encodeRequests so we don't have to reallocate space for them*/
+ LinkedBlockingQueue<BlockEncodeRequest> usedBlockEncodeRequests = null;
+ LinkedBlockingQueue<BlockEncodeRequest> preparedRequests = null;
+ ArrayRecycler recycler = null;
+
+ /**
+ * Constructor which creates a new encoder object with the default settings.
+ * The StreamConfiguration should be reset to match the audio used and an
+ * output stream set, but the default EncodingConfiguration should be ok for
+ * most purposes. When using threaded encoding, the default number of
+ * threads used is equal to FLACEncoder.MAX_THREADED_FRAMES.
+ */
+ public FLACEncoder() {
+ usedBlockEncodeRequests = new LinkedBlockingQueue<BlockEncodeRequest>();
+ preparedRequests = new LinkedBlockingQueue<BlockEncodeRequest>();
+ //usedIntArrays = new LinkedBlockingQueue<int[]>();
+ recycler = new ArrayRecycler();
+ streamConfig = new StreamConfiguration();
+ encodingConfig = new EncodingConfiguration();
+ frame = new Frame(streamConfig);
+ frame.registerConfiguration(encodingConfig);
+
+ this.prepareThreadManager(streamConfig);
+ try {
+ md5 = new FLAC_MD5();
+ //reset();
+ clear();
+ }catch(NoSuchAlgorithmException e) {
+ throw new IllegalStateException("Error! FLACEncoder cannot function" +
+ "without a valid MD5 implementation.",e);
+ }
+ }
+
+ /**
+ * Tell encoder how many threads to use for encoding. More threads than this
+ * will exist, but only the given amount should be in a running state at
+ * any moment(the other threads are simply manager threads, waiting for
+ * encoding-threads to end). A special case is setting "count" to zero; this
+ * will tell the encoder not to use internal threads at all, and all
+ * encoding will be done with the main thread. Otherwise, any encode methods
+ * will return while the encode actually takes place in a separate thread.
+ *
+ * @param count Number of encoding threads to use. Count > 0 means use that
+ * many independent encoding threads, count == 0 means encode in main thread,
+ * count < 0 is ignored.
+ *
+ * @return boolean value represents whether requested count was applied or
+ * not. This may be false if a FLAC stream is currently opened.
+ */
+ public boolean setThreadCount(int count) {
+ boolean result = false;
+ if(count < 0 || flacStreamIsOpen)
+ return false;
+ streamLock.lock();
+ try {
+ if(flacStreamIsOpen)
+ result = false;
+ else {
+ MAX_THREADED_FRAMES = count;
+ prepareThreadManager(streamConfig);
+ result = true;
+ }
+ }finally {
+ streamLock.unlock();
+ }
+ return result;
+ }
+
+ /**
+ * Creates and configures a new BlockThreadManager if needed(or sets to null
+ * if threads turned off.) Method must only be called if flacStreamIsOpen
+ * equals false, and this must not change while executing this method.
+ * @param sc
+ */
+ private void prepareThreadManager(StreamConfiguration sc) {
+ assert(!flacStreamIsOpen);
+ if(MAX_THREADED_FRAMES > 0) {
+ threadManager = new BlockThreadManager(this);
+ threadedFrames = new Frame[MAX_THREADED_FRAMES];
+ for(int i = 0; i < MAX_THREADED_FRAMES; i++) {
+ threadedFrames[i] = new Frame(this.streamConfig);
+ threadManager.addFrameThread(threadedFrames[i]);
+ }
+ }
+ else {
+ threadManager = null;
+ }
+ }
+
+ /**
+ * Get the number of threads this FLACEncoder is currently set to use.
+ * @return number of threads this encoder is currently set to use.
+ */
+ public int getThreadCount() {
+ return this.MAX_THREADED_FRAMES;
+ }
+
+ /**
+ * Set the encoding configuration to that specified. The given encoding
+ * configuration is not stored by this object, but instead copied. This
+ * is to prevent the alteration of the config during an encode process. This
+ * must not be called while an encodeSamples(...) is active, or while
+ * encoding threads are active. If using threaded mode, use a blocking-count
+ * of zero in t_encodeSamples(...)to ensure the underlying encoding threads
+ * have finished before calling this method.
+ *
+ * @param ec EncodingConfiguration to use.
+ * @return true if the configuration was altered; false if the configuration
+ * cannot be altered(such as if another thread is currently encoding).
+ */
+ public boolean setEncodingConfiguration(EncodingConfiguration ec) {
+ boolean changed = false;
+ if(!isEncoding && ec != null) {//don't wait if we're already encoding.
+ streamLock.lock();
+ try {
+ if(!isEncoding) {
+ encodingConfig = ec;
+ frame.registerConfiguration(ec);
+ for(int i = 0; i < MAX_THREADED_FRAMES; i++)
+ threadedFrames[i].registerConfiguration(ec);
+ changed = true;
+ }
+ }finally {
+ streamLock.unlock();
+ }
+ }
+ return changed;
+ }
+
+ /**
+ * Set the stream configuration to that specified. The given stream
+ * configuration is not stored by this object, but instead copied. This
+ * is to prevent the alteration of the config during an encode process.
+ * This method must not be called in the middle of a stream, stream contents
+ * may become invalid. Calling this method clears any data stored by this
+ * encoder. A call to setStreamConfiguration() should be followed next by
+ * setting the output stream if not yet done, and then calling
+ * openFLACStream();
+ *
+ * @param sc StreamConfiguration to use.
+ * @return true if the configuration was altered; false if the configuration
+ * cannot be altered(such as if another thread is currently encoding).
+ */
+ public boolean setStreamConfiguration(StreamConfiguration sc) {
+ boolean changed = false;
+ sc = new StreamConfiguration(sc);
+ if(sc != null) {
+ if(flacStreamIsOpen || isEncoding)
+ changed = false;
+ else {
+ streamLock.lock();
+ try{
+ if(flacStreamIsOpen || isEncoding) {//can't change streamconfig on open stream.
+ changed = false;
+ }
+ else {
+ streamConfig = sc;
+ reset();
+ frame = new Frame(sc);
+ prepareThreadManager(sc);
+ this.setEncodingConfiguration(this.encodingConfig);
+ clear();
+ changed = true;
+ }
+ }finally {
+ streamLock.unlock();
+ }
+ }
+ }
+ return changed;
+ }
+
+ /**
+ * Reset the values to their initial state, in preparation of starting a
+ * new stream. Does *not* clear any stored, unwritten data. To flush stored
+ * samples, call clear().
+ */
+ private void reset() {
+ md5.getMD().reset();
+ if(flacWriter != null)
+ flacWriter = new FLACStreamController(flacWriter.getFLACOutputStream(),streamConfig);
+ }
+
+ /**
+ * Clear all samples stored by this object, but not yet encoded. Should be
+ * called between encoding differrent streams(before more samples are added),
+ * unless you desire to keep unencoded samples. This does NOT reset or close
+ * the active stream.
+ */
+ public final void clear() {
+ unfilledRequest = null;
+ this.preparedRequests.clear();
+ }
+
+ /**
+ * Close the current FLAC stream. Updates the stream header information.
+ * If called on a closed stream, operation is undefined. Do not do this.
+ */
+ private void closeFLACStream() throws IOException {
+ //reset position in output stream to beginning.
+ //re-write the updated stream info.
+ checkForThreadErrors();
+ if(DEBUG_LEV > 0)
+ System.err.println("FLACEncoder::closeFLACStream : Begin");
+ streamLock.lock();
+ try {
+ if(!flacStreamIsOpen)
+ throw new IllegalStateException("Cannot close a non-opened stream");
+ byte[] md5Hash = md5.getMD().digest();
+ flacWriter.closeFLACStream(md5Hash, streamConfig);
+ flacStreamIsOpen = false;
+ } finally {
+ streamLock.unlock();
+ }
+ }
+
+ /**
+ * Begin a new FLAC stream. Prior to calling this, you must have already
+ * set the StreamConfiguration and the output stream, both of which must not
+ * change until encoding is finished and the stream is closed. If this
+ * FLACEncoder object has already been used to encode a stream, unencoded
+ * samples may still be stored. Use clear() to dump them prior to calling
+ * this method(if clear() not called, and samples are instead retained, the
+ * StreamConfiguration must NOT have changed from the prior stream.
+ *
+ * @throws IOException if there is an error writing the headers to output.
+ */
+ public void openFLACStream() throws IOException {
+ streamLock.lock();
+ try {
+ flacWriter.openFLACStream();
+ flacStreamIsOpen = true;
+ }finally {
+ streamLock.unlock();
+ }
+ }
+
+ private BlockEncodeRequest prepareRequest(int blockSize, int channels) {
+ //int[] block = blockQueue.elementAt(0);
+ int[] block = recycler.getArray(blockSize*channels);
+ BlockEncodeRequest ber = usedBlockEncodeRequests.poll();
+ if(ber == null)
+ ber = new BlockEncodeRequest();
+ EncodedElement result = new EncodedElement(1,0);
+ ber.setAll(block, 0, 0, channels-1,0,result);
+ return ber;
+ }
+
+ /**
+ * Add samples to the encoder, so they may then be encoded. This method uses
+ * breaks the samples into blocks, which will then be made available to
+ * encode.
+ *
+ * @param samples Array holding the samples to encode. For all multi-channel
+ * audio, the samples must be interleaved in this array. For example, with
+ * stereo: sample 0 will belong to the first channel, 1 the second, 2 the
+ * first, 3 the second, etc. Samples are interpreted according to the
+ * current configuration(for things such as channel and bits-per-sample).
+ *
+ * @param count Number of interchannel samples to add. For example, with
+ * stero: if this is 4000, then "samples" must contain 4000 left samples and
+ * 4000 right samples, interleaved in the array.
+ *
+ * @return true if samples were added, false otherwise. A value of false may
+ * result if "count" is set to a size that is too large to be valid with the
+ * given array and current configuration.
+ */
+ public void addSamples(int[] samples, int count) {
+ assert(count*streamConfig.getChannelCount() <= samples.length);
+ if(samples.length < count*streamConfig.getChannelCount())
+ throw new IllegalArgumentException("count given exceeds samples array bounds");
+ sampleLock.lock();
+ try {
+ //get number of channels
+ int channels = streamConfig.getChannelCount();
+ int maxBlock = streamConfig.getMaxBlockSize();
+ if(unfilledRequest == null)
+ unfilledRequest = prepareRequest(maxBlock,channels);
+ int remaining = count;
+ int offset = 0;
+ while(remaining > 0) {
+ int newRemaining = unfilledRequest.addInterleavedSamples(samples, offset, remaining, maxBlock);
+ offset += (remaining-newRemaining)*channels;
+ remaining = newRemaining;
+ if(unfilledRequest.isFull(maxBlock)) {
+ this.preparedRequests.add(unfilledRequest);
+ unfilledRequest = null;
+ }
+ if(remaining > 0) {
+ unfilledRequest = prepareRequest(maxBlock, channels);
+ }
+ }
+ }finally {
+ sampleLock.unlock();
+ }
+ }
+
+ private void writeFinishedBlock(BlockEncodeRequest ber) throws IOException {
+ flacWriter.writeBlock(ber);
+ md5.addSamplesToMD5(ber.samples, ber.encodedSamples, ber.skip+1,
+ streamConfig.getBitsPerSample());
+ recycler.add(ber.samples);
+ ber.result = null;
+ ber.samples = null;
+ usedBlockEncodeRequests.add(ber);
+ if(threadManager.getTotalManagedCount() == 1) {//this is the final block
+ streamLock.lock();
+ try {
+ if(threadManager.getTotalManagedCount() == 1)
+ isEncoding = false;
+ }finally {
+ streamLock.unlock();
+ }
+ }
+ }
+ /**
+ * Notify the encoder that a BlockEncodeRequest has finished, and is now
+ * ready to be written to file. The encoder expects that these requests come
+ * back in the same order the encoder sent them out. This is intended to
+ * be used in threading mode only at the moment(sending them to a
+ * BlockThreadManager object)
+ *
+ * @param ber BlockEncodeRequest that is ready to write to file.
+ */
+ protected void blockFinished(BlockEncodeRequest ber) {
+ assert(flacStreamIsOpen);
+ blockFinishedLock.lock();
+ try {
+ writeFinishedBlock(ber);
+ }catch(IOException e) {
+ error = true;
+ if(childException != null)
+ childException = e;
+ }
+ finally {
+ blockFinishedLock.unlock();
+ }
+ }
+
+ /**
+ * Attempts to throw a stored exception that had been caught from a child
+ * thread. This method should be called regularly in any public method to
+ * let the calling thread know a problem occured.
+ * @throws IOException
+ */
+ private void checkForThreadErrors() throws IOException {
+ if(error == true && childException != null) {
+ error = false;
+ IOException temp = childException;
+ childException = null;
+ throw temp;
+ }
+ }
+ /**
+ * Attempt to Encode a certain number of samples(threaded version).
+ * Encodes as close to count as possible. Uses multiple threads to speed up
+ * encoding. If getThreadCount() <= 0, simply calls the non-threaded version,
+ * encodeSamples(...), and blocks until it returns.
+ *
+ * @param count number of samples to attempt to encode. Actual number
+ * encoded may be greater or less if count does not end on a block boundary.
+ * If "end" is false, we may set this value to something absurdly high, such
+ * as Integer.MAX_VALUE to ensure all available, full blocks are encoded.
+ *
+ * @param end true to finalize stream after encode, false otherwise. If set
+ * to true, and return value is greater than or equal to given count, no
+ * more encoding must be attempted until a new stream is began.
+ *
+ * @param blockingCount value is used for flow-control into this encoder.
+ * This method will block until fewer than the given number of blocks remain
+ * queued for encoding.
+ *
+ * @return number of samples encoded. This may be greater or less than
+ * requested count if count does not end on a block boundary. This is NOT an
+ * error condition. If end was set "true", and returned count is less than
+ * requested count, then end was NOT done, if you still wish to end stream,
+ * call this again with end true and a count of of <= samplesAvailableToEncode()
+ *
+ * @throws IOException if there was an error writing the results to output
+ * stream.
+ */
+ public int t_encodeSamples(final int inCount, final boolean end, int blockingCount)
+ throws IOException {
+ int count = inCount;
+ if(MAX_THREADED_FRAMES <= 0)
+ return encodeSamples(count,end);
+ int encodedCount = 0;
+ if(end)
+ sampleLock.lock();//lock to avoid race condition in unfinishedBlock section.
+ try {
+ checkForThreadErrors();
+ streamLock.lock();
+ try {
+ while(count > 0 && preparedRequests.size() > 0) {
+ BlockEncodeRequest ber = preparedRequests.poll();
+ int encodedSamples = ber.count;
+ //ber.frameNumber = nextFrameNumber++;
+ ber.frameNumber = flacWriter.incrementFrameNumber();
+ threadManager.addRequest(ber);
+ isEncoding = true;
+ count -= encodedSamples;
+ encodedCount += encodedSamples;
+ }
+ }finally{
+ streamLock.unlock();
+ }
+ threadManager.blockWhileQueueExceeds(blockingCount);
+ if(end) {
+ streamLock.lock();
+ try {
+ if(count > 0 && unfilledRequest != null && unfilledRequest.count >= count) {
+ int encodedSamples = unfilledRequest.count;
+ threadManager.addRequest(unfilledRequest);
+ unfilledRequest = null;
+ isEncoding = true;
+ count -= encodedSamples;
+ encodedCount += encodedSamples;
+ }
+ }finally {
+ streamLock.unlock();
+ }
+ //block while requests remain!!!!
+ threadManager.blockWhileQueueExceeds(0);
+ threadManager.stop();
+ }
+ //handle "end" setting
+ if(end && encodedCount >= inCount) {//close if all requests were written.
+ closeFLACStream();
+ }
+ }finally {
+ if(end && sampleLock.isHeldByCurrentThread())
+ sampleLock.unlock();
+ }
+
+ return encodedCount;
+ }
+
+ /**
+ * Attempt to Encode a certain number of samples. Encodes as close to count
+ * as possible.
+ *
+ * @param count number of samples to attempt to encode. Actual number
+ * encoded may be greater or less if count does not end on a block boundary.
+ *
+ * @param end true to finalize stream after encode, false otherwise. If set
+ * to true, and return value is greater than or equal to given count, no
+ * more encoding must be attempted until a new stream is began.
+ *
+ * @return number of samples encoded. This may be greater or less than
+ * requested count if count does not end on a block boundary. This is NOT an
+ * error condition. If end was set "true", and returned count is less than
+ * requested count, then end was NOT done, if you still wish to end stream,
+ * call this again with end true and a count of of <= samplesAvailableToEncode()
+ * @throws IOException if there was an error writing the results to file.
+ */
+ public int encodeSamples(int count, final boolean end) throws IOException {
+ int encodedCount = 0;
+ streamLock.lock();
+ try {
+ checkForThreadErrors();
+ int channels = streamConfig.getChannelCount();
+ boolean encodeError = false;
+ while(count > 0 && preparedRequests.size() > 0 && !encodeError) {
+ BlockEncodeRequest ber = preparedRequests.peek();
+ int encodedSamples = encodeRequest(ber,channels);
+ if(encodedSamples < 0) {
+ //ERROR! Return immediately. Do not add results to output.
+ System.err.println("FLACEncoder::encodeSamples : Error in encoding");
+ encodeError = true;
+ break;
+ }
+ preparedRequests.poll();//pop top off now that we've written.
+ encodedCount += encodedSamples;
+ count -= encodedSamples;
+ }
+ //handle "end" setting
+ if(end) {
+ if(threadManager != null)
+ threadManager.stop();
+ //if(end && !encodeError && this.samplesAvailableToEncode() >= count) {
+ if(count > 0 && unfilledRequest != null && unfilledRequest.count >= count) {
+ //handle remaining count
+ BlockEncodeRequest ber = unfilledRequest;
+ int encodedSamples = encodeRequest(ber,channels);
+ if(encodedSamples < 0) {
+ //ERROR! Return immediately. Do not add results to output.
+ System.err.println("FLACEncoder::encodeSamples : (end)Error in encoding");
+ count = -1;
+ }
+ else {
+ count -= encodedSamples;
+ encodedCount += encodedSamples;
+ unfilledRequest = null;
+ }
+ }
+ if(count <= 0) {//close stream if all requested were written.
+ closeFLACStream();
+ }
+ }
+ else if (end == true) {
+ if(DEBUG_LEV > 30)
+ System.err.println("End set but not done. Error possible. "+
+ "This can also happen if number of samples requested to " +
+ "encode exeeds available samples");
+ }
+ }finally {
+ streamLock.unlock();
+ }
+ return encodedCount;
+}
+ private int encodeRequest(BlockEncodeRequest ber, int channels) throws IOException {
+ ber.frameNumber = flacWriter.incrementFrameNumber();
+ int[] block = ber.samples;
+ int encodedSamples = ber.count;
+ EncodedElement result = ber.result;
+ int encoded = frame.encodeSamples(block, encodedSamples, 0, channels-1,
+ result, ber.frameNumber);
+ if(encoded != encodedSamples) {
+ //ERROR! Return immediately. Do not add results to output.
+ System.err.println("FLACEncoder::encodeSamples : Error in encoding");
+ return -1;
+ }
+ ber.encodedSamples = encoded;
+ writeFinishedBlock(ber);
+
+ return encodedSamples;
+ }
+
+ /**
+ * Get number of samples which are ready to encode. More samples may exist
+ * in the encoder as a partial block. Use samplesAvailableToEncode() if you
+ * wish to include those as well.
+ * @return number of samples in full blocks, ready to encode.
+ */
+ public int fullBlockSamplesAvailableToEncode() {
+ int available = 0;
+ int channels = streamConfig.getChannelCount();
+ for(BlockEncodeRequest ber : preparedRequests) {
+ int[] block = ber.samples;
+ available += block.length/channels;
+ }
+ return available;
+ }
+
+ /**
+ * Get number of samples that are available to encode. This includes samples
+ * which are in a partial block(and so would only be written if "end" was
+ * set true in encodeSamples(int count,boolean end);
+ * @return number of samples availble to encode.
+ */
+ public int samplesAvailableToEncode() {
+ int available = 0;
+ //sum all in blockQueue
+ int channels = streamConfig.getChannelCount();
+ for(BlockEncodeRequest ber : preparedRequests) {
+ int[] block = ber.samples;
+ available += block.length/channels;
+ }
+ available += unfilledRequest.count;
+ return available;
+ }
+
+ /**
+ * Set the output stream to use. This must not be called while an encode
+ * process is active, or a flac stream is already opened.
+ * @param fos output stream to use. This must not be null.
+ */
+ public void setOutputStream(FLACOutputStream fos) {
+ if(fos == null)
+ throw new IllegalArgumentException("FLACOutputStream fos must not be null.");
+ if(flacWriter == null)
+ flacWriter = new FLACStreamController(fos,streamConfig);
+ else
+ flacWriter.setFLACOutputStream(fos);
+ }
+}
diff --git a/src/javaFlacEncoder/FLACFileOutputStream.java b/src/javaFlacEncoder/FLACFileOutputStream.java
new file mode 100644
index 0000000..c6f8b9f
--- /dev/null
+++ b/src/javaFlacEncoder/FLACFileOutputStream.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.File;
+import java.nio.channels.FileChannel;
+import java.io.Closeable;
+
+/**
+ * This class provides basic file output for writing from a FLACEncoder.
+ *
+ * @author Preston Lacey
+ */
+public class FLACFileOutputStream implements FLACOutputStream,Closeable{
+
+ FileOutputStream fos = null;
+ long position;
+ long size = 0;
+ boolean valid;
+
+ /**
+ * Constructor. Create a FLACFileOutputStream using the given filename. If
+ * file exists, file will be overwritten.
+ *
+ * @param filename file to connect to output stream.
+ */
+ public FLACFileOutputStream(String filename) throws IOException {
+ position = 0;
+ fos = new FileOutputStream(filename);
+ valid = true;
+ }
+
+ public FLACFileOutputStream(File file) throws IOException {
+ position = 0;
+ fos = new FileOutputStream(file);
+ valid = true;
+ }
+
+ /**
+ * Constructor. Create a FLACFileOutputStream using the given FileOutputStream.
+ * @param fos FileOutputStream to write to, must be open. Current position
+ * of fos will be used as this object's position.
+ * @throws IOException
+ */
+ public FLACFileOutputStream(FileOutputStream fos) throws IOException {
+ FileChannel fc = fos.getChannel();
+ position = fc.position();
+ this.fos = fos;
+ valid = true;
+ }
+
+ /**
+ * Get the status of this file stream(whether the file was successfully open
+ * or not).
+ * @return true if file was successfully opened, false otherwise.
+ */
+ @Deprecated
+ public boolean isValid() { return valid; }
+
+ /**
+ * Attempt to seek to the given location within this stream. It is not
+ * guaranteed that all implementations can or will support seeking. Use the
+ * method canSeek()
+ *
+ * @param pos target position to seek to.
+ * @return current position after seek attempt.
+ */
+ public long seek(long pos) throws IOException {
+ FileChannel fc = fos.getChannel();
+ fc.position(pos);
+ return pos;
+ }
+
+ /**
+ * Write a byte to this stream.
+ * @param data byte to write.
+ * @throws IOException IOException will be raised if an error occurred while
+ * writing.
+ */
+ public void write(byte data) throws IOException {
+ fos.write(data);
+ if(position + 1 > size)
+ size = position+1;
+ position+= 1;
+ }
+ /**
+ * Write the given number of bytes from the byte array. Return number of
+ * bytes written.
+ * @param data array containing bytes to be written.
+ * @param offset start index of array to begin reading from.
+ * @param count number of bytes to write.
+ * @return number of bytes written.
+ * @throws IOException IOException upon a write error.
+ */
+ public int write(byte[] data, int offset, int count) throws IOException {
+ int result = count;
+ try {
+ fos.write(data,offset,count);
+ if(position + count > size)
+ size = position+count;
+ position+= count;
+ }catch(IOException e) {
+ throw e;
+ }
+ return result;
+ }
+
+ /**
+ * Get the number of bytes that have been written in length!
+ * This takes into account seeking to different portions.
+ * @return total length written.
+ */
+ public long size() {
+ return size;
+ }
+
+ /**
+ * Test whether this stream is seekable.
+ * @return true if stream is seekable, false otherwise
+ */
+ public boolean canSeek() {
+ return true;
+ }
+
+ /**
+ * Get the current write position of this stream.
+ * @return current write position.
+ */
+ public long getPos() {
+ return position;
+ }
+
+ /**
+ * Close FileOutputStream owned by this object.
+ * @throws IOException
+ */
+ public void close() throws IOException {
+ fos.close();
+ }
+}
diff --git a/src/javaFlacEncoder/FLACOutputStream.java b/src/javaFlacEncoder/FLACOutputStream.java
new file mode 100644
index 0000000..e3dc56c
--- /dev/null
+++ b/src/javaFlacEncoder/FLACOutputStream.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+
+import java.io.IOException;
+/**
+ * This interface defines a location to write the output of the FLAC
+ * encoder to. We don't want to require that the entire stream is buffered in the
+ * encoder prior to being written, as that could require significant memory and
+ * would make live handling of streams impossible. However, we can't write the
+ * stream headers completely until the entire stream is encoded(specifically
+ * because the MD5 hash which appears at the beginning of the FLAC stream,
+ * isn't known till the last audio value is given to the encoder). Therefore,
+ * the output stream would ideally be seekable, which prevents us from
+ * outputting to just a standard "OutputStream". So we can't guarantee the
+ * stream is seekable, can't write everything in order given, but can't always
+ * buffer till we have the data for the stream headers. This interface allows
+ * the implementation to determine the proper tradeoffs. Following is a
+ * description of how the FLACEncoder class will treat objects of this type
+ * <br><br><BLOCKQUOTE>
+ * If canSeek() returns false: The file will be written as normal, but the
+ * headers will not be updated once the stream is closed. This means the FLAC
+ * file will not contain a count of the total number of samples, nor the MD5
+ * hash of the original input(used for verifying the data).<br>
+ * If canSeek() returns true: Data will be written as it becomes available, and
+ * the encoder will seek() to a point near the beginning of the stream to fix
+ * the stream headers once the stream is closed. <br>
+ * </BLOCKQUOTE>
+ * @author Preston Lacey
+ *
+ *
+ */
+public interface FLACOutputStream {
+
+ /**
+ * Attempt to seek to the given position.
+ *
+ * @param pos target position.
+ * @return current position after seek.
+ */
+ public long seek(long pos) throws IOException;
+
+ /**
+ * Write the given number of bytes from a byte array.
+ *
+ * @param data array containing source bytes to write
+ * @param offset index of source array to begin reading from.
+ * @param count number of bytes to write.
+ * @return number of bytes written.
+ * @throws IOException IOException raised upon write error.
+ */
+ public int write(byte[] data, int offset, int count) throws IOException;
+
+ /**
+ * Get the number of bytes that have been written in length.
+ * This takes into account seeking to different portions.
+ *
+ * @return total writtne length of stream.
+ */
+ public long size();
+
+ /**
+ * Write a single byte to the stream.
+ *
+ * @param data byte to write.
+ * @throws IOException IOException raised upon write error.
+ */
+ public void write(byte data) throws IOException;
+
+ /**
+ * Test whether this object allows seeking.
+ *
+ * @return true if seeking is allowed, false otherwise.
+ */
+ public boolean canSeek();
+
+ /**
+ * Get current write position of this stream. If stream cannot seek, then
+ * this will return 0;
+ *
+ * @return current write position.
+ */
+ public long getPos();
+}
diff --git a/src/javaFlacEncoder/FLACStreamController.java b/src/javaFlacEncoder/FLACStreamController.java
new file mode 100644
index 0000000..a887164
--- /dev/null
+++ b/src/javaFlacEncoder/FLACStreamController.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+import java.util.concurrent.locks.ReentrantLock;
+import java.io.IOException;
+/**
+ *
+ * @author preston
+ */
+public class FLACStreamController {
+ public static int DEBUG_LEV = 0;
+ /** we set this to true while a flac stream has been opened and not
+ * officially closed. Must use a streamLock to set and get this. Some actions
+ * must not be taken while a stream is opened */
+ volatile boolean flacStreamIsOpen = false;
+
+ private final ReentrantLock streamLock = new ReentrantLock();
+
+ /* Object to write results to. Must be set before opening stream */
+ private FLACOutputStream out = null;
+
+ /* contains FLAC_id used in the flac stream header to signify FLAC format */
+ EncodedElement FLAC_id = FLACStreamIdentifier.getIdentifier();
+
+ /* total number of samples encoded to output. Used in stream header */
+ volatile long samplesInStream;
+
+ /* next frame number to use */
+ volatile long nextFrameNumber = 0;
+
+ /* position of header in output stream location(needed so we can update
+ * the header info(md5, minBlockSize, etc), once encoding is done
+ */
+ long streamHeaderPos = 0;
+
+ /* minimum frame size seen so far. Used in the stream header */
+ volatile int minFrameSize = 0x7FFFFFFF;
+
+ /* maximum frame size seen so far. Used in stream header */
+ volatile int maxFrameSize = 0;
+
+ /* minimum block size used so far. Used in stream header */
+ volatile int minBlockSize = 0x7FFFFFFF;
+
+ /* maximum block size used so far. Used in stream header */
+ volatile int maxBlockSize = 0;
+
+ StreamConfiguration streamConfig = null;
+ public FLACStreamController(FLACOutputStream fos, StreamConfiguration sc) {
+ out = fos;
+ streamConfig = new StreamConfiguration(sc);
+ minFrameSize = 0x7FFFFFFF;
+ maxFrameSize = 0;
+ minBlockSize = 0x7FFFFFFF;
+ maxBlockSize = 0;
+ samplesInStream = 0;
+ streamHeaderPos = 0;
+ nextFrameNumber = 0;
+ }
+ public void setFLACOutputStream(FLACOutputStream fos) {
+ streamLock.lock();
+ try {
+ if(flacStreamIsOpen)
+ throw new IllegalStateException("Cannot set new output stream while flac stream is open");
+ out = fos;
+ }finally {
+ streamLock.unlock();
+ }
+ }
+ public FLACOutputStream getFLACOutputStream() { return out; }
+
+
+ /**
+ * Close the current FLAC stream. Updates the stream header information.
+ * If called on a closed stream, operation is undefined. Do not do this.
+ */
+ public void closeFLACStream(byte[] md5Hash, StreamConfiguration streamConfig)
+ throws IOException {
+ //reset position in output stream to beginning.
+ //re-write the updated stream info.
+ streamLock.lock();
+ try {
+ if(!flacStreamIsOpen)
+ throw new IllegalStateException("Error. Cannot close a non-opened stream");
+ StreamConfiguration tempSC = new StreamConfiguration(streamConfig);
+ tempSC.setMaxBlockSize(maxBlockSize);
+ tempSC.setMinBlockSize(minBlockSize);
+ EncodedElement streamInfo = MetadataBlockStreamInfo.getStreamInfo(
+ tempSC, minFrameSize, maxFrameSize, samplesInStream, md5Hash);
+ if(out.canSeek()) {
+ out.seek(streamHeaderPos);
+ this.writeDataToOutput(streamInfo);
+ }
+ flacStreamIsOpen = false;
+ } finally {
+ streamLock.unlock();
+ }
+ }
+
+
+/**
+ * Write the data stored in an EncodedElement to the output stream.
+ * All data will be written along byte boundaries, but the elements in the
+ * given list need not end on byte boundaries. If the data of an element
+ * does not end on a byte boundary, then the space remaining in that last
+ * byte will be used as an offset, and merged(using an "OR"), with the first
+ * byte of the following element.
+ *
+ * @param data
+ * @return
+ * @throws IOException
+ */
+ private int writeDataToOutput(EncodedElement data) throws IOException {
+
+ int writtenBytes = 0;
+ int offset = 0;
+ EncodedElement current = data;
+ int currentByte = 0;
+ byte unfullByte = 0;
+ byte[] eleData = null;
+ int usableBits = 0;
+ int lastByte = 0;
+ while(current != null) {
+ eleData = current.getData();
+ usableBits = current.getUsableBits();
+ currentByte = 0;
+ //if offset is not zero, merge first byte with existing byte
+ if(offset != 0) {
+ unfullByte = (byte)(unfullByte | eleData[currentByte++]);
+ out.write(unfullByte);
+ }
+ //write all full bytes of element.
+ lastByte = usableBits/8;
+ if(lastByte > 0)
+ out.write(eleData, currentByte, lastByte-currentByte);
+ //save non-full byte(if present), and set "offset" for next element.
+ offset = usableBits %8;
+ if(offset != 0) {
+ unfullByte = eleData[lastByte];
+ }
+ //update current.
+ current = current.getNext();
+ }
+ //if non-full byte remains. write.
+ if(offset != 0) {
+ out.write(eleData, lastByte, 1);
+ }
+ return writtenBytes;
+ }
+
+ public long incrementFrameNumber() {
+ return nextFrameNumber++;
+ }
+
+/**
+ * Begin a new FLAC stream. Prior to calling this, you must have already
+ * set the StreamConfiguration and the output stream, both of which must not
+ * change until encoding is finished and the stream is closed. If this
+ * FLACEncoder object has already been used to encode a stream, unencoded
+ * samples may still be stored. Use clear() to dump them prior to calling
+ * this method(if clear() not called, and samples are instead retained, the
+ * StreamConfiguration must NOT have changed from the prior stream.
+ *
+ * @throws IOException if there is an error writing the headers to output.
+ */
+ public void openFLACStream() throws IOException {
+ streamLock.lock();
+ try {
+ //reset all data.
+ reset();
+ flacStreamIsOpen = true;
+ //write FLAC stream identifier
+ out.write(FLAC_id.getData(), 0, FLAC_id.getUsableBits()/8);
+ //write stream headers. These must be updated at close of stream
+ byte[] md5Hash = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};//blank hash. Don't know it yet.
+ EncodedElement streamInfo = MetadataBlockStreamInfo.getStreamInfo(
+ streamConfig, minFrameSize, maxFrameSize, samplesInStream, md5Hash);
+ //mark stream info location(so we can return to it and re-write headers,
+ // assuming stream is seekable. Then write header.
+ int size = streamInfo.getUsableBits()/8;
+ EncodedElement metadataBlockHeader =
+ MetadataBlockHeader.getMetadataBlockHeader(false,
+ MetadataBlockHeader.MetadataBlockType.STREAMINFO, size);
+ this.writeDataToOutput(metadataBlockHeader);
+ streamHeaderPos = out.getPos();
+ out.write(streamInfo.getData(), 0, size);
+ writePaddingToFoolJFlac();
+ }finally {
+ streamLock.unlock();
+ }
+ }
+ private void reset() {
+ minFrameSize = 0x7FFFFFFF;
+ maxFrameSize = 0;
+ minBlockSize = 0x7FFFFFFF;
+ maxBlockSize = 0;
+ samplesInStream = 0;
+ streamHeaderPos = 0;
+ nextFrameNumber = 0;
+ }
+ private void writePaddingToFoolJFlac() throws IOException {
+ int size = 40;
+ byte[] padding = new byte[size];
+ EncodedElement metadataBlockHeader =
+ MetadataBlockHeader.getMetadataBlockHeader(true,
+ MetadataBlockHeader.MetadataBlockType.PADDING, 40);
+ this.writeDataToOutput(metadataBlockHeader);
+ out.write(padding, 0,size);
+ }
+
+ public void writeBlock(BlockEncodeRequest ber) throws IOException {
+ if(!flacStreamIsOpen)
+ throw new IllegalStateException("Cannot write on a non-opened stream");
+ writeDataToOutput(ber.result.getNext());
+ //update encodedCount and count, and blocks, MD5
+ if(ber.count != ber.encodedSamples) {
+ System.err.println("Error encoding frame number: "+
+ ber.frameNumber+", FLAC stream potentially invalid");
+ }
+ samplesInStream += ber.encodedSamples;
+ if(ber.encodedSamples > maxBlockSize)
+ maxBlockSize = ber.encodedSamples;
+ if(ber.encodedSamples < minBlockSize)
+ minBlockSize = ber.encodedSamples;
+ int frameSize = ber.result.getTotalBits()/8;
+ if(frameSize > maxFrameSize) maxFrameSize = frameSize;
+ if(frameSize < minFrameSize) minFrameSize = frameSize;
+ }
+
+}
diff --git a/src/javaFlacEncoder/FLACStreamIdentifier.java b/src/javaFlacEncoder/FLACStreamIdentifier.java
new file mode 100644
index 0000000..60ddf41
--- /dev/null
+++ b/src/javaFlacEncoder/FLACStreamIdentifier.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+
+/**
+ * Provides the stream identifier used at beginning of flac streams.
+ * @author Preston Lacey
+ */
+public class FLACStreamIdentifier {
+ static final byte streamMarkerByte1 = 0x66;
+ static final byte streamMarkerByte2 = 0x4c;
+ static final byte streamMarkerByte3 = 0x61;
+ static final byte streamMarkerByte4 = 0x43;
+ static final byte[] marker = {
+ streamMarkerByte1,
+ streamMarkerByte2,
+ streamMarkerByte3,
+ streamMarkerByte4,
+ };
+
+ /**
+ * Get an EncodedElement containing the marker(which is itself in a byte
+ * array).
+ * @return EncodedElement containing the marker.
+ */
+ public static EncodedElement getIdentifier() {
+ EncodedElement ele = new EncodedElement();
+ ele.setData(marker.clone());
+ ele.setUsableBits(32);
+ return ele;
+ }
+}
diff --git a/src/javaFlacEncoder/FLACStreamOutputStream.java b/src/javaFlacEncoder/FLACStreamOutputStream.java
new file mode 100644
index 0000000..508d408
--- /dev/null
+++ b/src/javaFlacEncoder/FLACStreamOutputStream.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+import java.io.OutputStream;
+import java.io.IOException;
+import java.io.Closeable;
+/**
+ * This class provides basic OutputStream support for writing from a FLACEncoder.
+ *
+ * @author Preston Lacey
+ */
+public class FLACStreamOutputStream implements FLACOutputStream,Closeable {
+
+ OutputStream out = null;
+ long size = 0;
+ boolean valid;
+
+ /**
+ * Constructor. Create a FLACStreamOutputStream using the given OutputStream.
+ * @param out OutputStream to write the FLAC stream to.
+ */
+ public FLACStreamOutputStream(OutputStream out) throws IOException {
+ this.out = out;
+ size = 0;
+ }
+
+ /**
+ * Attempt to seek to the given location within this stream. It is not
+ * guaranteed that all implementations can or will support seeking. Use the
+ * method canSeek()
+ *
+ * @param pos target position to seek to.
+ * @return current position after seek attempt.
+ */
+ public long seek(long pos) {
+ throw new UnsupportedOperationException("seek(long) is not supported on by FLACStreamOutputStream");
+ }
+
+ /**
+ * Write a byte to this stream.
+ * @param data byte to write.
+ * @throws IOException IOException will be raised if an error occurred while
+ * writing.
+ */
+ public void write(byte data) throws IOException {
+ out.write(data);
+ size++;
+ }
+ /**
+ * Write the given number of bytes from the byte array. Return number of
+ * bytes written.
+ * @param data array containing bytes to be written.
+ * @param offset start index of array to begin reading from.
+ * @param count number of bytes to write.
+ * @return number of bytes written.
+ * @throws IOException IOException upon a write error.
+ */
+ public int write(byte[] data, int offset, int count) throws IOException {
+ int result = count;
+ out.write(data,offset,count);
+ size += count;
+ return result;
+ }
+
+ /**
+ * Get the number of bytes that have been written by this object.
+ * @return total length written.
+ */
+ public long size() {
+ return size;
+ }
+
+ /**
+ * Test whether this stream is seekable.
+ * @return true if stream is seekable, false otherwise
+ */
+ public boolean canSeek() {
+ return false;
+ }
+
+ /**
+ * Get the current write position of this stream. If this stream cannot seek,
+ * this will return 0;
+ * @return current write position.
+ */
+ public long getPos() {
+ return 0;
+ }
+
+ /**
+ * Close OutputStream owned by this object.
+ * @throws IOException
+ */
+ public void close() throws IOException {
+ out.close();
+ }
+}
diff --git a/src/javaFlacEncoder/FLAC_MD5.java b/src/javaFlacEncoder/FLAC_MD5.java
new file mode 100644
index 0000000..1577356
--- /dev/null
+++ b/src/javaFlacEncoder/FLAC_MD5.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+
+import java.security.MessageDigest;
+
+
+/**
+ *
+ * @author preston
+ */
+public class FLAC_MD5 {
+ private MessageDigest md = null;
+ private byte[] _dataMD5 = null;
+
+ public FLAC_MD5() throws java.security.NoSuchAlgorithmException {
+ md = MessageDigest.getInstance("md5");
+ }
+
+ public MessageDigest getMD() {
+ return md;
+ }
+
+ /**
+ * Add samples to the MD5 hash.
+ * CURRENTLY ONLY MAY WORK FOR: sample sizes which are divisible by 8. Need
+ * to create some audio to test with.
+ * @param samples
+ * @param count
+ * @param channels
+ */
+ public void addSamplesToMD5(int[] samples, int count, int channels,
+ int sampleSize) {
+ int bytesPerSample = sampleSize/8;
+ if(sampleSize%8 != 0)
+ bytesPerSample++;
+ if(_dataMD5 == null || _dataMD5.length < count*bytesPerSample*channels) {
+ _dataMD5 = new byte[count*bytesPerSample*channels];
+ }
+ byte[] dataMD5 = _dataMD5;
+ splitSamplesToBytes(samples, count*channels, bytesPerSample, dataMD5);
+ md.update(dataMD5, 0, count*bytesPerSample*channels);
+ }
+
+ /* Split Samples to bytes(for sending to MD5)
+ * CURRENTLY ONLY MAY WORK FOR: sample sizes which are divisible by 8. Need
+ * to create some audio to test with.*/
+ private static final void splitSamplesToBytes(int[] samples, int totalSamples,
+ int bytesPerSample, byte[] dataMD5) {
+ int destIndexBase = 0;
+ int i = 0;
+
+ switch(bytesPerSample) {
+ case 3:
+ for(; i < totalSamples; i++) {
+ dataMD5[destIndexBase++] = (byte)(samples[i]);
+ dataMD5[destIndexBase++] = (byte)(samples[i] >> 8);
+ dataMD5[destIndexBase++] = (byte)(samples[i] >> 16);
+ }
+ break;
+ case 2:
+ for(; i < totalSamples; i++) {
+ dataMD5[destIndexBase++] = (byte)(samples[i]);
+ dataMD5[destIndexBase++] = (byte)(samples[i] >> 8);
+ }
+ break;
+ case 1:
+ for(; i < totalSamples; i++) {
+ dataMD5[i] = (byte)samples[i];
+ }
+ }
+ }
+}
diff --git a/src/javaFlacEncoder/Frame.java b/src/javaFlacEncoder/Frame.java
new file mode 100644
index 0000000..af11832
--- /dev/null
+++ b/src/javaFlacEncoder/Frame.java
@@ -0,0 +1,833 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+/**
+ * Handles taking a set of audio samples, and splitting it into the proper
+ * subframes, and returning the resulting encoded data. This object will do any calculations
+ * needed for preparing the “channel configuration” used in encoding, such as mid-side or
+ * left-right(based upon the given configuration).
+ * @author Preston Lacey
+ */
+public class Frame {
+
+ /** For debugging: Higher level equals more output(generally in increments
+ * of 10 */
+ public static int DEBUG_LEV = 0;
+
+ /* track the size of the last encoded frame */
+ private int lastEncodeSize;
+ /* Current EncodingConfiguration used. This must NOT be changed while a
+ * Frame is being encoded, but may be changed between frames */
+ EncodingConfiguration ec;
+
+ /* Current stream configuration */
+ StreamConfiguration sc;
+ /* Number of channels currently configured. This comes from setting the
+ * StreamConfiguration(and so is slightly redundant)
+ */
+ int channels;
+ /* bits per sample as set by the StreamConfiguration...this is redundant */
+ int bitsPerSample;
+ /* Object used to generate the headers needed for FLAC frames */
+ FrameHeader frameHeader;
+ /* Used to calculate CRC16's of each frame */
+ CRC16 crc16;
+ /* Used for calculation of verbatimSubframes */
+ Subframe_Verbatim verbatimSubframe;
+ /* Used for calculation of fixedSubframes */
+ Subframe_Fixed fixedSubframe;
+ /* Used for calculation of lpcSubframes */
+ Subframe_LPC lpcSubframe;
+ /* Used for calculation of constantSubframes */
+ Subframe_Constant constantSubframe;
+ /* Flag tracking whether we need to test for a constant subframe */
+ boolean testConstant;
+ long[] _decorrelationResults = new long[4];
+ boolean[] _constantCandidate = new boolean[4];
+ int[] _midSideSamples = null;
+ ChannelData[] indChanData = null;
+ ChannelData[] msChanData = null;
+ long[] r_sums = new long[4];
+ /**
+ * Constructor. Private to prevent it's use(if a StreamConfiguration isn't
+ * set, then most methods will fail in an undefined fashion.
+ */
+ private Frame() {
+ }
+
+ /**
+ * Constructor. Sets the StreamConfiguration to use at creation of object.
+ * If the StreamConfiguration needs to be changed, you *MUST* create a new
+ * Frame object.
+ *
+ * @param sc StreamConfiguration to use for encoding with this frame.
+ */
+ public Frame(StreamConfiguration sc) {
+ lastEncodeSize = 0;
+ channels = sc.getChannelCount();
+ this.sc = sc;
+ frameHeader = new FrameHeader();
+ crc16 = new CRC16();
+ ec = null;
+ verbatimSubframe = new Subframe_Verbatim(sc);
+ fixedSubframe = new Subframe_Fixed(sc);
+ lpcSubframe = new Subframe_LPC(sc);
+ constantSubframe = new Subframe_Constant(sc);
+ bitsPerSample = sc.getBitsPerSample();
+ testConstant = true;
+ registerConfiguration(new EncodingConfiguration());
+ }
+
+ /**
+ * This method is used to set the encoding configuration. This
+ * configuration can be altered throughout the stream, but cannot be called while an
+ * encoding process is active.
+ *
+ * @param ec encoding configuration to use.
+ * @return <code>true</code> if configuration was changed.
+ * <code>false</code> otherwise(i.e, and encoding process was active
+ * at the time of change)
+ */
+ public boolean registerConfiguration(EncodingConfiguration ec) {
+ boolean changed = false;
+ if(sc.getChannelCount() != 2)
+ ec.setChannelConfig(EncodingConfiguration.ChannelConfig.INDEPENDENT);
+ this.ec = new EncodingConfiguration(ec);
+ verbatimSubframe.registerConfiguration(this.ec);
+ fixedSubframe.registerConfiguration(this.ec);
+ lpcSubframe.registerConfiguration(this.ec);
+ constantSubframe.registerConfiguration(this.ec);
+ changed = true;
+ return changed;
+ }
+
+
+ /**
+ * Encodes samples into the appropriate compressed format, saving the
+ * result in the given “data” EncodedElement list. Encodes 'count' samples,
+ * from index 'start', to index 'start' times 'skip', where “skip” is the
+ * format that samples may be packed in an array. For example, 'samples' may
+ * include both left and right samples of a stereo stream. Therefore, “skip”
+ * would equal 2, resulting in the valid indices for the first channel being
+ * even, and second being odd.
+ * @param samples the audio samples to encode. This array may contain
+ * samples for multiple channels, interleaved; only one of
+ * these channels is encoded by a subframe.
+ * @param count the number of samples to encode.
+ * @param start the index to start at in the array.
+ * @param skip the number of indices to skip between successive samples
+ * (for use when channels are interleaved in the given
+ * array).
+ * @param data the EncodedElement to attach encoded data to. Data in
+ * Encoded Element given is not altered. New data is
+ * attached starting with “data.getNext()”. If “data”
+ * already has a “next” set, it will be lost!
+ * @return int Returns the number of inter-channel samples encoded;
+ * i.e, if block-size is 4000, and it is stereo audio.
+ * There are 8000 samples in this block, but the return
+ * value is “4000”. There is always an equal number of
+ * samples encoded from each channel. This exists primarily
+ * to support dynamic block sizes in the future;
+ * Pre-condition: none
+ * Post-condition: Argument 'data' is the head of a list containing the resulting,
+ * encoded data stream.
+ */
+ public int encodeSamples_OLD(int[] samples, int count, int start, int skip,
+ EncodedElement result, long frameNumber) {
+ //System.err.println("FRAME::encodeSamples: frame#:"+frameNumber);
+ if(DEBUG_LEV > 0) {
+ System.err.println("FRAME::encodeSamples(...)");
+ if(DEBUG_LEV > 10) {
+ System.err.println("\tsamples.length:"+samples.length+":count:"+
+ count+":start:"+start+":skip:"+skip+":frameNumber:"+
+ frameNumber);
+ }
+ }
+ int samplesEncoded = count;
+ testConstant = true;
+ EncodedElement data = null;
+ //choose correct channel configuration. Get data for that config
+ EncodingConfiguration.ChannelConfig chConf = ec.getChannelConfig();
+ if(chConf == EncodingConfiguration.ChannelConfig.INDEPENDENT) {
+ data = new EncodedElement(100,0);
+ int size = encodeIndependent(samples, count, start, skip, data, 0);
+ //int size = encodeMidSide(samples, count, start, skip, data, 0);
+ }
+ else if(chConf == EncodingConfiguration.ChannelConfig.LEFT_SIDE) {
+ data = new EncodedElement(100,0);
+ int size = encodeLeftSide(samples, count, start, skip, data, 0);
+ }
+ else if(chConf == EncodingConfiguration.ChannelConfig.MID_SIDE) {
+ data = new EncodedElement(100,0);
+ int size = Frame.encodeMidSide(samples, count, start, skip, data, 0, this);
+ }
+ else if(chConf == EncodingConfiguration.ChannelConfig.RIGHT_SIDE) {
+ data = new EncodedElement(100,0);
+ int size = encodeRightSide(samples, count, start, skip, data, 0);
+ }
+ else if(chConf == EncodingConfiguration.ChannelConfig.ENCODER_CHOICE) {
+ data = new EncodedElement(100,0);
+ int size = allChannelDecorrelation(samples, count, start, skip, data, 0, this);
+ chConf = ec.channelConfig;
+ ec.channelConfig = EncodingConfiguration.ChannelConfig.ENCODER_CHOICE;
+ }
+ else if(chConf == EncodingConfiguration.ChannelConfig.EXHAUSTIVE) {
+ //encode with all versions.
+ EncodedElement dataLeftSide = new EncodedElement(100,0);
+ ec.channelConfig = EncodingConfiguration.ChannelConfig.LEFT_SIDE;
+ int sizeLeft = encodeLeftSide(samples, count, start, skip, dataLeftSide, 0);
+ ec.channelConfig = EncodingConfiguration.ChannelConfig.MID_SIDE;
+ EncodedElement dataMidSide = new EncodedElement(100,0);
+ int sizeMid = Frame.encodeMidSide(samples, count, start, skip, dataMidSide, 0, this);
+ ec.channelConfig = EncodingConfiguration.ChannelConfig.INDEPENDENT;
+ EncodedElement dataIndependent = new EncodedElement(100,0);
+ int sizeInd = encodeIndependent(samples, count, start, skip, dataIndependent, 0);
+ //choose best
+ ec.channelConfig = chConf;
+ if(sizeLeft <= sizeMid && sizeLeft <= sizeInd) {
+ data = dataLeftSide;
+ chConf = EncodingConfiguration.ChannelConfig.LEFT_SIDE;
+ }
+ else if(sizeMid <= sizeInd) {
+ data = dataMidSide;
+ chConf = EncodingConfiguration.ChannelConfig.MID_SIDE;
+ }
+ else {
+ data = dataIndependent;
+ chConf = EncodingConfiguration.ChannelConfig.INDEPENDENT;
+ }
+ //update header to reflect change
+ }
+
+ //create header element; attach to result
+ EncodedElement header = frameHeader.createHeader(true, count,
+ sc.getSampleRate(), chConf, sc.getBitsPerSample(),
+ frameNumber, channels, new EncodedElement(128, 0));
+ result.setNext(header);
+ //attach data to header
+ header.attachEnd(data);
+ //use "data" to zero-pad to byte boundary.
+ //EncodedElement temp = data.getEnd();
+ data.padToByte();
+ //calculate CRC and affix footer
+ EncodedElement crc16Ele = getCRC16(header);
+ data.attachEnd(crc16Ele);
+ if(DEBUG_LEV > 0)
+ System.err.println("Frame::encodeSamples(...): End");
+ return samplesEncoded;
+ }
+
+ private EncodingConfiguration.ChannelConfig determineConfigUsed(ChannelData[] channels) {
+ EncodingConfiguration.ChannelConfig result = EncodingConfiguration.ChannelConfig.INDEPENDENT;
+ if(channels.length == 2) {
+ ChannelData.ChannelName n1 = channels[0].getChannelName();
+ ChannelData.ChannelName n2 = channels[1].getChannelName();
+ if(n2 == ChannelData.ChannelName.SIDE) {
+ if(n1 == ChannelData.ChannelName.LEFT)
+ result = EncodingConfiguration.ChannelConfig.LEFT_SIDE;
+ else if(n1 == ChannelData.ChannelName.MID)
+ result = EncodingConfiguration.ChannelConfig.MID_SIDE;
+ else {
+ System.err.println("Error in n2, determinConfigUsed");
+ }
+ }
+ if(n1 == ChannelData.ChannelName.SIDE) {
+ if(n2 == ChannelData.ChannelName.RIGHT)
+ result = EncodingConfiguration.ChannelConfig.RIGHT_SIDE;
+ else
+ System.err.println("Error in n1, determinConfigUsed");
+ }
+ }
+ return result;
+ }
+ /**
+ * Encodes samples into the appropriate compressed format, saving the
+ * result in the given “data” EncodedElement list. Encodes 'count' samples,
+ * from index 'start', to index 'start' times 'skip', where “skip” is the
+ * format that samples may be packed in an array. For example, 'samples' may
+ * include both left and right samples of a stereo stream. Therefore, “skip”
+ * would equal 2, resulting in the valid indices for the first channel being
+ * even, and second being odd.
+ * @param samples the audio samples to encode. This array may contain
+ * samples for multiple channels, interleaved; only one of
+ * these channels is encoded by a subframe.
+ * @param count the number of samples to encode.
+ * @param start the index to start at in the array.
+ * @param skip the number of indices to skip between successive samples
+ * (for use when channels are interleaved in the given
+ * array).
+ * @param data the EncodedElement to attach encoded data to. Data in
+ * Encoded Element given is not altered. New data is
+ * attached starting with “data.getNext()”. If “data”
+ * already has a “next” set, it will be lost!
+ * @return int Returns the number of inter-channel samples encoded;
+ * i.e, if block-size is 4000, and it is stereo audio.
+ * There are 8000 samples in this block, but the return
+ * value is “4000”. There is always an equal number of
+ * samples encoded from each channel. This exists primarily
+ * to support dynamic block sizes in the future;
+ * Pre-condition: none
+ * Post-condition: Argument 'data' is the head of a list containing the resulting,
+ * encoded data stream.
+ */
+ public int encodeSamples(int[] samples, int count, int start, int skip,
+ EncodedElement result, long frameNumber) {
+ //System.err.println("FRAME::encodeSamples: frame#:"+frameNumber);
+ if(DEBUG_LEV > 0) {
+ System.err.println("FRAME::encodeSamplesNew(...)");
+ if(DEBUG_LEV > 10) {
+ System.err.println("\tsamples.length:"+samples.length+":count:"+
+ count+":start:"+start+":skip:"+skip+":frameNumber:"+
+ frameNumber);
+ }
+ }
+ int samplesEncoded = count;
+ testConstant = true;
+ EncodedElement data = null;
+ ChannelData[][] chanConfigData = this.getChannelsToEncode(samples, count,
+ sc.getChannelCount(), sc.getBitsPerSample());
+ int size = Integer.MAX_VALUE;
+ EncodingConfiguration.ChannelConfig chConf = EncodingConfiguration.ChannelConfig.INDEPENDENT;
+ for(int i = 0; i < chanConfigData.length; i++) {
+ EncodedElement temp = new EncodedElement();
+ int configSize = encodeChannels(chanConfigData[i], temp);
+ if(configSize < size) {
+ size = configSize;
+ data = temp;
+ chConf = determineConfigUsed(chanConfigData[i]);
+ }
+ }
+
+ //create header element; attach to result
+ EncodedElement header = new EncodedElement(FrameHeader.MAX_HEADER_SIZE, 0);
+ frameHeader.createHeader(true, count, sc.getSampleRate(), chConf,
+ sc.getBitsPerSample(), frameNumber, channels, header);
+ //result.setNext(header);
+ result.attachEnd(header);
+ //attach data to header
+ header.attachEnd(data);
+ //use "data" to zero-pad to byte boundary.
+ //EncodedElement temp = data.getEnd();
+ data.padToByte();
+ //calculate CRC and affix footer
+ EncodedElement crc16Ele = getCRC16(header);
+ data.attachEnd(crc16Ele);
+ if(DEBUG_LEV > 0)
+ System.err.println("Frame::encodeSamples(...): End");
+ return samplesEncoded;
+ }
+
+ EncodedElement getCRC16(EncodedElement header) {
+ EncodedElement crc16Ele = new EncodedElement(2,0);
+ short valNew = header.getCRC16();
+ crc16Ele.addInt(valNew, 16);
+ return crc16Ele;
+ }
+
+ int encodeChannels(ChannelData[] channels, EncodedElement result) {
+ int totalSize = 0;
+ int channelLength = 0;
+ int offset = 0;
+ int count = channels[0].getCount();
+ for(int i = 0; i < channels.length; i++) {
+ EncodedElement temp = new EncodedElement();
+ channelLength = encodeChannel(channels[i].getSamples(),count,0, 0, offset,
+ temp, channels[i].getSampleSize());
+ totalSize += channelLength;
+ result.attachEnd(temp);
+ offset = totalSize % 8;
+ }
+
+ return totalSize;
+ }
+ int encodeIndependent(int[] samples, int count, int start,
+ int skip, EncodedElement result, int offset) {
+ if(DEBUG_LEV > 0) {
+ System.err.println("Frame::encodeIndependent : Begin");
+ System.err.println("start:skip:offset::"+start+":"+skip+":"+offset);
+ }
+ //int startSize = result.getTotalBits();
+ int totalSize = 0;
+ int channelLength = 0;
+ int channelCount = skip+1;
+ int inputOffset = offset;
+ EncodedElement subframes[] = new EncodedElement[channelCount];
+ EncodingConfiguration.ChannelConfig chConf = ec.channelConfig;
+ //encode each subframe, using prior offset in packing
+ for(int i = 0; i < channelCount; i++) {
+ int channelBitsPerSample = this.bitsPerSample;
+ //System.err.println("independent: "+channelBitsPerSample);
+ if(i == 1 && chConf == EncodingConfiguration.ChannelConfig.LEFT_SIDE)
+ channelBitsPerSample++;
+ else if(i == 1 && chConf == EncodingConfiguration.ChannelConfig.MID_SIDE)
+ channelBitsPerSample++;
+ else if(i == 0 && chConf == EncodingConfiguration.ChannelConfig.RIGHT_SIDE)
+ channelBitsPerSample++;
+ subframes[i] = new EncodedElement();
+ //System.err.println("Frame::subframe begin offset: "+offset);
+ channelLength = encodeChannel(samples, count, start+i, skip,
+ offset, subframes[i], channelBitsPerSample);
+ totalSize += channelLength;
+ offset = (inputOffset+totalSize)%8;
+ }
+ //attach first subframe to result
+ result.attachEnd(subframes[0]);
+ //attach all remaining channels together
+ for(int i = 1; i < channelCount; i++) {
+ subframes[i-1].attachEnd(subframes[i]);
+ //result.attachEnd(subframes[i]);
+ }
+ if(DEBUG_LEV > 0)
+ System.err.println("Frame::encodeIndependent : End");
+ //return total bit size(does not include given offset).
+ return totalSize;
+ }
+
+
+ int encodeChannel(int[] samples, int count, int start, int skip, int offset,
+ EncodedElement data, int channelBitsPerSample) {
+ if(DEBUG_LEV > 0)
+ System.err.println("Frame::encodeChannel : Begin");
+ int size = 0;
+ //Calculate subframe using the methods requested.
+ EncodingConfiguration.SubframeType subframeType = ec.getSubframeType();
+ if(subframeType == EncodingConfiguration.SubframeType.VERBATIM) {
+ //use verbatim subframe to encode channel.
+ //note size.
+ //System.err.println("Using verbatim with count: "+count);
+ this.verbatimSubframe.encodeSamples(samples, count, start, skip,
+ data, offset, channelBitsPerSample);
+ size = verbatimSubframe.getEncodedSize();
+ }
+ else if(subframeType == EncodingConfiguration.SubframeType.FIXED) {
+ //System.err.println("channelBitsPerSample: "+channelBitsPerSample);
+ this.fixedSubframe.encodeSamples(samples, count, start, skip, data,
+ offset, channelBitsPerSample);
+ size = fixedSubframe.getEncodedSize();
+ }
+ else if(subframeType == EncodingConfiguration.SubframeType.LPC) {
+ this.lpcSubframe.encodeSamples(samples, count, start, skip, data,
+ offset, channelBitsPerSample);
+ size = lpcSubframe.getEncodedSize();
+ }
+ else if(subframeType == EncodingConfiguration.SubframeType.EXHAUSTIVE) {
+ int conCount = -1;
+ EncodedElement constantEle = new EncodedElement(200,offset);
+ EncodedElement smallest = null;
+ if(testConstant) {
+ //System.err.println("Testing constant");
+ conCount = constantSubframe.encodeSamples(samples, count, start,
+ skip, constantEle,offset, channelBitsPerSample);
+ }
+ //System.err.println("conCount: "+conCount);
+ if(conCount == count) {
+ //System.err.println("Using Constant!");
+ size = constantSubframe.getEncodedSize();
+ smallest = constantEle;
+ }
+ else {
+ //calculate verbatim size:
+ int verbatimSize = verbatimSubframe.estimateSize(count, channelBitsPerSample);
+ fixedSubframe.encodeSamples(samples, count,
+ start, skip, offset, channelBitsPerSample);
+ int fixedSize = fixedSubframe.estimatedSize();
+ //int fixedSize = fixedSubframe.getEncodedSize();
+ lpcSubframe.encodeSamples(samples, count, start, skip,
+ offset, channelBitsPerSample);
+ int lpcSize = (int)lpcSubframe.estimatedSize();
+ if(verbatimSize < lpcSize && verbatimSize < fixedSize) {//verbatim
+ System.err.println("Running verbatim");
+ smallest = new EncodedElement(verbatimSize,offset);
+ verbatimSubframe.encodeSamples(samples, count, start, skip,
+ smallest, offset, channelBitsPerSample);
+ size = verbatimSubframe.getEncodedSize();
+ }
+ else if(lpcSize < fixedSize && lpcSize < verbatimSize){//lpc
+ //size = lpcSize;
+ smallest = lpcSubframe.getData();
+ size = lpcSubframe.getEncodedSize();
+ if(size > lpcSize)
+ System.err.println("Lpc size wrong: exp:real : "+lpcSize+":"+size);
+ }
+ else {//fixed
+ smallest = fixedSubframe.getData();
+ size = fixedSubframe.getEncodedSize();
+ if(size > fixedSize)
+ System.err.println("Fixed size wrong: exp:real : "+fixedSize+":"+size);
+ }
+ }
+ data.data = smallest.data;
+ data.next = smallest.next;
+ data.usableBits = smallest.usableBits;
+ data.offset = smallest.offset;
+ }
+ //return total bit size of encoded subframe.
+ if(DEBUG_LEV > 0)
+ System.err.println("Frame::encodeChannel : End");
+ return size;
+ }
+ /**
+ * Returns the total number of valid bits used in the last encoding(i.e, the
+ * number of compressed bits used). This is here for convenience, as the
+ * calling object may also loop through the resulting EncodingElement from
+ * the encoding process and sum the valid bits.
+ *
+ * @return an integer with value of the number of bits used in last encoding.
+ * Pre-condition: none
+ * Post-condition: none
+ */
+ public int getEncodedSize() {
+ if(DEBUG_LEV > 0)
+ System.err.println("Frame::getEncodedSize : Begin");
+ return lastEncodeSize;
+ }
+
+ private int encodeRightSide(int[] samples, int count, int start, int skip,
+ EncodedElement data, int offset) {
+ int[] rightSide = new int[samples.length];
+ for(int i = 0; i < count; i++) {
+ rightSide[2*i] = samples[2*i]-samples[2*i+1];
+ rightSide[2*i+1] = samples[2*i+1];
+ }
+ return encodeIndependent(rightSide, count, start, skip, data, offset);
+ }
+ private int encodeLeftSide(int[] samples, int count, int start, int skip,
+ EncodedElement data, int offset) {
+ int[] leftSide = new int[samples.length];
+ for(int i = 0; i < count; i++) {
+ leftSide[2*i] = samples[2*i];
+ leftSide[2*i+1] = samples[2*i]-samples[2*i+1];
+ }
+ return encodeIndependent(leftSide, count, start, skip, data, offset);
+ }
+
+ private static void getIndependentChannels(int[] samples, int count,
+ ChannelData[] channels, int sampleSize) {
+ int channelCount = channels.length;
+ int[][]independentSamples = new int[channels.length][];
+ for(int i = 0; i < channelCount; i++) {
+ ChannelData temp = channels[i];
+ if(temp != null)
+ independentSamples[i] = channels[i].getSamples();
+ if(independentSamples[i] == null || independentSamples[i].length < count)
+ independentSamples[i] = new int[count];
+ if(temp != null)
+ channels[i].setData(independentSamples[i], count, sampleSize, ChannelData.ChannelName.INDEPENDENT);
+ else
+ channels[i] = new ChannelData(independentSamples[i], count, sampleSize, ChannelData.ChannelName.INDEPENDENT);
+ }
+
+ for(int i = 0; i < count; i++) {
+ for(int x = 0; x < channelCount; x++) {
+ independentSamples[x][i] = samples[channelCount*i+x];
+ }
+ }
+ }
+
+ private static void getMidSideChannels(int[] samples, int count,
+ ChannelData[] channels, int sampleSize) {
+ ChannelData midData = channels[0];
+ ChannelData sideData = channels[1];
+ int[] midSamples = null;
+ int[] sideSamples = null;
+ if(midData != null)
+ midSamples = midData.getSamples();
+ if(sideData != null)
+ sideSamples = sideData.getSamples();
+ if(midSamples == null || midSamples.length < count) midSamples = new int[count];
+ if(sideSamples == null || sideSamples.length < count) sideSamples = new int[count];
+
+ if(midData != null)
+ channels[0].setData(midSamples, count, sampleSize, ChannelData.ChannelName.MID);
+ else
+ channels[0] = new ChannelData(midSamples, count, sampleSize, ChannelData.ChannelName.MID);
+ if(sideData != null)
+ channels[1].setData(sideSamples, count, sampleSize+1, ChannelData.ChannelName.SIDE);
+ else
+ channels[1] = new ChannelData(sideSamples, count, sampleSize+1, ChannelData.ChannelName.SIDE);
+
+ for(int i = 0; i < count; i++) {
+ int temp = (samples[2*i]+samples[2*i+1]) >> 1;
+ midSamples[i] = temp;
+ sideSamples[i] = (samples[2*i]-samples[2*i+1]);
+ }
+ }
+ private static long[] sumChannelSamplesABS(ChannelData[] channels) {
+ long[] result = new long[channels.length];
+ for(int i = 0; i < result.length; i++) result[i] = 0;
+ int[][] samples = new int[channels.length][];
+ for(int i = 0; i < channels.length; i++)
+ samples[i] = channels[i].getSamples();
+ for(int i = 0; i < channels[0].getCount(); i++) {
+ for(int x = 0; x < channels.length; x++) {
+ int temp = samples[x][i];
+ if(temp < 0)
+ temp = -temp;
+ result[x] += temp;
+ }
+ }
+ return result;
+ }
+ private ChannelData[][] getChannelsToEncode(int[] samples, int count, int channels, int sampleSize) {
+ ChannelData[] independent = indChanData;
+ ChannelData[] midSide = msChanData;
+ //ChannelData[][] results = new ChannelData[channels];
+ ChannelData[][] results = null;
+ EncodingConfiguration.ChannelConfig chConf = ec.getChannelConfig();
+ if(channels != 2)
+ chConf = EncodingConfiguration.ChannelConfig.INDEPENDENT;
+
+ if(chConf != EncodingConfiguration.ChannelConfig.MID_SIDE) {
+ if(independent == null || independent.length != channels)
+ independent = new ChannelData[channels];
+ getIndependentChannels(samples, count, independent, sampleSize);
+ }
+ if(chConf != EncodingConfiguration.ChannelConfig.INDEPENDENT) {
+ if(midSide == null || midSide.length != 2)
+ midSide = new ChannelData[2];
+ getMidSideChannels(samples,count,midSide,sampleSize);
+ }
+ if(chConf == EncodingConfiguration.ChannelConfig.ENCODER_CHOICE) {
+ chConf = selectOptimalChannels(independent,midSide,count);
+ }
+
+ if(chConf == EncodingConfiguration.ChannelConfig.INDEPENDENT) {
+ results = new ChannelData[1][];
+ results[0] = independent;
+ }
+ else if(chConf == EncodingConfiguration.ChannelConfig.LEFT_SIDE) {
+ results = new ChannelData[1][2];
+ independent[0].setChannelName(ChannelData.ChannelName.LEFT);
+ results[0][0] = independent[0];
+ results[0][1] = midSide[1];
+ }
+ else if(chConf == EncodingConfiguration.ChannelConfig.MID_SIDE) {
+ results = new ChannelData[1][2];
+ results[0] = midSide;
+ }
+ else if(chConf == EncodingConfiguration.ChannelConfig.RIGHT_SIDE) {
+ results = new ChannelData[1][2];
+ independent[1].setChannelName(ChannelData.ChannelName.RIGHT);
+ results[0][1] = independent[1];
+ results[0][0] = midSide[1];
+ }
+ else if(chConf == EncodingConfiguration.ChannelConfig.EXHAUSTIVE) {
+ //encode with all versions.
+ results = new ChannelData[4][];
+ results[2] = new ChannelData[2];
+ results[3] = new ChannelData[2];
+ results[0] = independent;
+ results[1] = midSide;
+ results[2][0] = independent[0];//LEFT_SIDE
+ independent[0].setChannelName(ChannelData.ChannelName.LEFT);
+ results[2][1] = midSide[1];
+
+ results[3][0] = midSide[1];//RIGHT_SIDE(better called SIDE_RIGHT)
+ results[3][1] = independent[1];
+ independent[1].setChannelName(ChannelData.ChannelName.RIGHT);
+ }
+ indChanData = independent;
+ msChanData = midSide;
+ return results;
+ }
+
+ private boolean determineTestConstant(long[] sums, ChannelData[] channels) {
+ boolean result = false;
+ int count = channels[0].getCount();
+ for(int i = 0; i < sums.length; i++) {
+ if(sums[i]/count == channels[i].getSamples()[0])
+ result = true;
+ }
+ return result;
+ }
+
+
+ private EncodingConfiguration.ChannelConfig selectOptimalChannels
+ (ChannelData[] independent,ChannelData[] midSide,int count) {
+ EncodingConfiguration.ChannelConfig result;
+ //Calculate sum for each channel.
+ long[] sumInd = sumChannelSamplesABS(independent);
+ long[] sumMS = sumChannelSamplesABS(midSide);
+ boolean ind = determineTestConstant(sumInd, independent);
+ boolean ms = determineTestConstant(sumMS, midSide);
+ testConstant = ind | ms;
+
+ //Check each total channel configuration sum, lowest is winner
+ //long[] r_sums = new long[4];
+ r_sums[0] = sumInd[0]+sumInd[1];//INDEPENDENT
+ r_sums[1] = sumMS[0]+sumMS[1];//MID_SIDE
+ r_sums[2] = sumInd[0]+sumMS[1];//LEFT_SIDE
+ r_sums[3] = sumInd[1]+sumMS[1];//RIGHT_SIDE
+ long lowestVal = r_sums[0];
+ int lowestIndex = 0;
+ for(int i = 0; i < 4; i++) {
+ if( lowestVal > r_sums[i]) {
+ lowestVal = r_sums[i];
+ lowestIndex = i;
+ }
+ }
+ switch(lowestIndex) {
+ case 3: result = EncodingConfiguration.ChannelConfig.RIGHT_SIDE;break;
+ case 2: result = EncodingConfiguration.ChannelConfig.LEFT_SIDE;break;
+ case 1: result = EncodingConfiguration.ChannelConfig.MID_SIDE;break;
+ default: result = EncodingConfiguration.ChannelConfig.INDEPENDENT;
+ }
+ return result;
+ }
+
+ private static int encodeMidSide(int[] samples, int count, int start, int skip,
+ EncodedElement data, int offset, Frame f) {
+ int[] midSide = new int[samples.length];
+ for(int i = 0; i < count; i++) {
+ int temp = (samples[2*i]+samples[2*i+1]) >> 1;
+ // if(temp %2 != 0) temp++;
+ midSide[2*i] = temp;
+ midSide[2*i+1] = (samples[2*i]-samples[2*i+1]);
+
+ //midSide[2*i+1] = 0;
+ }
+ return f.encodeIndependent(midSide, count, start, skip, data, offset);
+ }
+ private static double getVariance(ChannelData chan, double mean) {
+ double variance = 0;
+ int[] samples = chan.getSamples();
+ for(int i = 0; i < chan.getCount(); i++) {
+ double val = mean-(double)samples[i];
+ variance += val*val;
+ }
+ return variance;
+ }
+ private static double getVariance(double mean, int[] samples, int count, int start,
+ int increment) {
+ double var = 0;
+
+ for(int i = 0; i < count; i++) {
+ int loc = start+i*increment;
+ double val = (mean-samples[loc]);
+ var += val*val;
+ }
+
+ return var;
+ }
+ private static int allChannelDecorrelation(int[] samples, int count, int start, int skip,
+ EncodedElement data, int offset, Frame f) {
+ if(DEBUG_LEV > 0) {
+ System.err.println("Frame::allChannelDecorrelation(...)");
+ }
+ //calculate size for each type
+ //int size = 0;
+ /*
+ * sums[0]: left
+ * sums[1]: right
+ * sums[2]: mid
+ * sums[3]: side
+ */
+ //long[] sums = new long[4];
+ long sums0 = 0;
+ long sums1 = 0;
+ long sums2 = 0;
+ long sums3 = 0;
+ //boolean [] constantCandidate = new boolean[4];
+ boolean[] constantCandidate = f._constantCandidate;
+ for(int i = 0; i < 4; i++) {
+ //sums[i] = 0;
+ constantCandidate[i] = false;
+ }
+
+ int[] midSideSamples = f._midSideSamples;
+ if(midSideSamples == null || midSideSamples.length < samples.length) {
+ midSideSamples = new int[samples.length];
+ f._midSideSamples = midSideSamples;
+ }
+ int index = 0;
+ for(int i = 0; i < count; i++) {
+ int temp = (samples[index]+samples[index+1]) >> 1;
+ midSideSamples[index] = temp;
+ midSideSamples[index+1] = (samples[index]-samples[index+1]);
+ index += 2;
+ }
+ index = 0;
+ for(int i = 0; i < count; i++ ) {
+ long temp;
+ temp = samples[index];
+ if(temp < 0) temp = -temp;
+ sums0 += temp;
+ temp = samples[index+1];
+ if(temp < 0) temp = -temp;
+ sums1 += temp;
+ temp = midSideSamples[index];
+ if(temp < 0) temp = -temp;
+ sums2 += temp;
+ temp = midSideSamples[index+1];
+ if(temp < 0) temp = -temp;
+ sums3 += temp;
+ index += 2;
+ }
+ //for(int i = 0; i < sums.length; i++) sums[i] =sums[i]/count;
+ sums0 = sums0/count;
+ sums1 = sums1/count;
+ sums2 = sums2/count;
+ sums3 = sums3/count;
+
+ constantCandidate[0] = (samples[0] == sums0);
+ constantCandidate[1] = (samples[1] == sums1);
+ constantCandidate[2] = (midSideSamples[2] == sums2);
+ constantCandidate[3] = (midSideSamples[3] == sums3);
+
+ //long[] results = new long[4];
+ long[] results = f._decorrelationResults;
+ results[0] = sums0+sums1;//independent
+ results[1] = sums2+sums3;//midSide
+ results[2] = sums0+sums3;//leftSide
+ results[3] = sums3+sums1;//rightSide
+ //choose the best
+ int choice = 0;
+ for(int i = 0; i < 4; i++) {
+ if(results[choice] > results[i]) choice = i;
+ }
+ int encodeResult = -1;
+ if(choice == 0) {
+ f.testConstant = constantCandidate[0] || constantCandidate[1];
+ f.ec.channelConfig = EncodingConfiguration.ChannelConfig.INDEPENDENT;
+ encodeResult = f.encodeIndependent(samples, count, start, skip, data, offset);
+ }
+ else if(choice == 1) {
+ f.testConstant = constantCandidate[2] || constantCandidate[3];
+ f.ec.channelConfig = EncodingConfiguration.ChannelConfig.MID_SIDE;
+ encodeResult = f.encodeIndependent(midSideSamples, count, start, skip, data, offset);
+ }
+ else if(choice == 2) {
+ f.testConstant = constantCandidate[0] || constantCandidate[3];
+ f.ec.channelConfig = EncodingConfiguration.ChannelConfig.LEFT_SIDE;
+ for(int i = 0; i < count; i++) midSideSamples[2*i] = samples[2*i];
+ encodeResult = f.encodeIndependent(midSideSamples, count, start, skip, data, offset);
+ }
+ else {
+ f.testConstant = constantCandidate[1] || constantCandidate[3];
+ f.ec.channelConfig = EncodingConfiguration.ChannelConfig.RIGHT_SIDE;
+ for(int i = 0; i < count; i++) {
+ midSideSamples[2*i] = midSideSamples[2*i+1];
+ midSideSamples[2*i+1] = samples[2*i+1];
+ }
+ encodeResult = f.encodeIndependent(midSideSamples, count, start, skip, data, offset);
+ }
+ return encodeResult;
+ }
+}
diff --git a/src/javaFlacEncoder/FrameHeader.java b/src/javaFlacEncoder/FrameHeader.java
new file mode 100644
index 0000000..1b5dc57
--- /dev/null
+++ b/src/javaFlacEncoder/FrameHeader.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+
+
+/**
+ * This class is used to generate a Frame Header for a FLAC Frame.
+ *
+ * @author Preston Lacey
+ */
+public class FrameHeader {
+
+ /** For Debugging: Higher level equals more debug statements */
+ public static int DEBUG_LEV = 0;
+
+ private static final int definedBlockSizes[] = {
+ -1,
+ 192,
+ 576,
+ 1152,
+ 2304,
+ 4608,
+ -1,
+ -1,
+ 256,
+ 512,
+ 1024,
+ 2048,
+ 4096,
+ 8192,
+ 16384,
+ 32768
+ };
+
+ private static final int definedSampleRates[] = {
+ 0,
+ 88200,
+ 176400,
+ 192000,
+ 8000,
+ 16000,
+ 22050,
+ 24000,
+ 32000,
+ 44100,
+ 48000,
+ 96000,
+ -1,
+ -1,
+ -1,
+ -1
+ };
+
+ /** Maximum size a header can be according to FLAC specification. */
+ public static final int MAX_HEADER_SIZE = 128;//in bytes
+
+ /** Synchronization code used at beginning of frame(low-order 14 bits
+ * used) */
+ public static final short syncCode = 0x3FFE;//14 bits used
+
+ static final byte reserved = 0;//1 bit used; 0 mandatory value
+
+ byte blockingStrategy = 0;//1 bit used; 0=fixed-blocksize. 1=variable
+ byte blockSize = 0xC;//4 bits used; see format docs
+ byte sampleRate = 4;//0;//4 bits used; see format docs
+ //byte channelAssignment = 0;//4 bits used; see format docs
+ byte sampleSize = 4;//3 bits used; see format docs
+
+ static final byte reserved2 = 0;//1 bit used; 0 mandatory value
+ long frameNumber = 0;//8-56 bits used; UTF-8 coded sample number
+ int blockSizeMod = 0;//if(blocksize bits == 011x) 8/16 bit (blocksize-1)
+ int SampleRateMod = 0;//if(sample rate bits == 11xx) 8/16 bit sample rate
+ byte crc8 = 0;
+ CRC8 crcCalculator;
+
+ /**
+ * Constructor creates a new FrameHeader object which is ready to
+ * generate headers. We can't use static functions to do this, since the
+ * process uses a CRC8 object which must be instantiated.
+ *
+ */
+ public FrameHeader() {
+ crcCalculator = new CRC8();
+ }
+
+ /**
+ * Create the header for a frame with the given parameters. Header data is
+ * stored out to an EncodedElement, in the proper form for a FLAC stream.
+ *
+ * @param fixBlock True to use a fixed block size, false to use variable. At
+ * this time, this *must* be set to True, as variable block size is
+ * not yet implemented.
+ * @param blockSize Block Size of this frame.
+ * @param sampleRate Sample rate of this frame.
+ * @param channelAssign Channel assignment used in this frame's encoding.
+ * See EncodingConfiguration class documentation for
+ * more information.
+ * @param sampleSize Bits per sample.
+ * @param frameNumber For fixed block-size encodings, this is the frame-number
+ * starting at zero and incrementing by one. For variable
+ * block encodings, this is the sample number of the
+ * first sample in the frame.
+ * @param channelCount Number of channels in the stream.
+ * @return EncodedElement where the header is saved to.
+ */
+ public EncodedElement createHeader(boolean fixBlock, int blockSize,
+ int sampleRate, EncodingConfiguration.ChannelConfig channelAssign,
+ int sampleSize, long frameNumber, int channelCount, EncodedElement result) {
+ if(DEBUG_LEV > 0 )
+ System.err.println("FrameHeader::createHeader : Begin");
+
+ //EncodedElement result = new EncodedElement();
+ boolean useEndBlockSize = false;
+ boolean useEndSampleRate = false;
+ result.clear(MAX_HEADER_SIZE,0);
+ int blockingStrat = (fixBlock)? 0:1;
+ byte[] encodedFrameNumber = UTF8Modified.convertToExtendedUTF8(frameNumber);
+ //set block size bits
+ byte encodedBlockSize = encodeBlockSize(blockSize);
+ if(encodedBlockSize == 0x6 || encodedBlockSize == 0x7)
+ useEndBlockSize = true;
+
+ //set sample rate bits
+ byte encodedSampleRate = encodeSampleRate(sampleRate);
+ if(encodedSampleRate >= 12 && encodedSampleRate <= 14)
+ useEndSampleRate = true;
+
+ //set channelAssignment bits
+ int channelAssignment = 0;
+ if(channelAssign == EncodingConfiguration.ChannelConfig.INDEPENDENT)
+ channelAssignment = channelCount-1;
+ else if(channelAssign == EncodingConfiguration.ChannelConfig.LEFT_SIDE)
+ channelAssignment = 8;
+ else if(channelAssign == EncodingConfiguration.ChannelConfig.RIGHT_SIDE)
+ channelAssignment = 9;
+ else if(channelAssign == EncodingConfiguration.ChannelConfig.MID_SIDE)
+ channelAssignment = 10;
+
+ //set sample size bits
+ byte encodedSampleSize = 0;
+ switch(sampleSize) {
+ case 8: encodedSampleSize = 0x1; break;
+ case 12: encodedSampleSize = 0x2; break;
+ case 16: encodedSampleSize = 0x4; break;
+ case 20: encodedSampleSize = 0x5; break;
+ case 24: encodedSampleSize = 0x6; break;
+ default: encodedSampleSize = 0x0;
+ }
+
+ result.addInt(syncCode,14);
+ result.addInt(reserved, 1);
+ result.addInt(blockingStrat, 1);
+ result.addInt(encodedBlockSize, 4);
+ result.addInt(encodedSampleRate, 4);
+ result.addInt(channelAssignment, 4);
+ result.addInt(encodedSampleSize, 3);
+ result.addInt(reserved2, 1);
+
+ for(int i = 0; i < encodedFrameNumber.length; i++) {
+ result.addInt(encodedFrameNumber[i], 8);
+ }
+
+ //write blockSize if needed(two formats possible)
+ if(useEndBlockSize) {
+ if(encodedBlockSize == 0x6) {
+ result.addInt(blockSize-1, 8);
+ }
+ else {
+ result.addInt(blockSize-1, 16);
+ }
+ }
+ //write sampleRate if needed(three formats possible)
+ if(useEndSampleRate) {
+ switch(encodedSampleRate) {
+ case 0xC:
+ result.addInt(sampleRate/1000, 8);
+ break;
+ case 0xD:
+ result.addInt(sampleRate, 16);
+ break;
+ case 0xE:
+ result.addInt(sampleRate/10, 16);
+ break;
+ }
+ }
+ if(DEBUG_LEV > 20 )
+ System.err.println("FrameHeader::createHeader : pre-CRC");
+ crcCalculator.reset();
+ crcCalculator.updateCRC8(result.getData(), 0, result.getTotalBits()/8);
+ crc8 = crcCalculator.checksum();
+ if(DEBUG_LEV > 20 )
+ System.err.println("FrameHeader::createHeader : post-CRC");
+ result.addInt(crc8, 8);
+
+ if(DEBUG_LEV > 0 )
+ System.err.println("FrameHeader::createHeader : End");
+ return result;
+ }
+
+ /**
+ * Given a block size, select the proper bit settings to use according to
+ * the FLAC stream.
+ * @param blockSize
+ * @return
+ */
+ private static byte encodeBlockSize(int blockSize) {
+ if(DEBUG_LEV > 0 )
+ System.err.println("FrameHeader::encodeBlockSize : Begin");
+ byte value = 0;
+ int i;
+ for(i = 0; i < definedBlockSizes.length; i++) {
+ if(blockSize == definedBlockSizes[i]) {
+ value = (byte)i;
+ break;
+ }
+ }
+ if(i >= definedBlockSizes.length) {
+ if(blockSize <= 255)
+ value = 0x6;
+ else
+ value = 0x7;
+ }
+
+ if(DEBUG_LEV > 0 )
+ System.err.println("FrameHeader::encodeBlockSize : End");
+
+ return value;
+ }
+
+
+ private static byte encodeSampleRate(int sampleRate) {
+ if(DEBUG_LEV > 0 )
+ System.err.println("FrameHeader::encodeSampleRate : Begin");
+ byte value = 0;
+ int i;
+ for(i = 0; i < definedSampleRates.length; i++) {
+ if(sampleRate == definedSampleRates[i]) {
+ value = (byte)i;
+ break;
+ }
+ }
+ if(i >= definedSampleRates.length) {
+ if(sampleRate % 1000 == 0 && sampleRate < 256000)
+ value = 0xC;
+ else if(sampleRate < 65536)
+ value = 0xD;
+ else if(sampleRate % 10 == 0 && sampleRate <= 655350)
+ value = 0xE;
+ else
+ value = 0x0;
+ }
+ if(DEBUG_LEV > 0 )
+ System.err.println("FrameHeader::encodeSampleRate : End");
+
+ return value;
+ }
+}
diff --git a/src/javaFlacEncoder/FrameThread.java b/src/javaFlacEncoder/FrameThread.java
new file mode 100644
index 0000000..23cf478
--- /dev/null
+++ b/src/javaFlacEncoder/FrameThread.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * The FrameThread class provides threading support for a Frame object, allowing
+ * multi-threaded encodings of FLAC frames. It's job is to repeatedly get a
+ * BlockEncodeRequest from a BlockThreadManager, and encode it.
+ *
+ * @author Preston Lacey
+ */
+public class FrameThread implements Runnable {
+ Frame frame = null;
+ ReentrantLock runLock = null;
+ BlockThreadManager manager = null;
+ /**
+ * Constructor. Private to prevent it's use, as a Frame must be provided for
+ * this FrameThread to be of any use.
+ */
+ private FrameThread() {}
+
+ /**
+ * Constructor. Sets the Frame object that this FrameThread will use for
+ * encodings.
+ *
+ * @param f Frame object to use for encoding.
+ * @param manager BlockThreadManager to use as the BlockEncodeRequest source
+ * and destination.
+ */
+ public FrameThread(Frame f, BlockThreadManager manager) {
+ super();
+ if(f == null)
+ System.err.println("Frame is null. Error.");
+ frame = f;
+ runLock = new ReentrantLock();
+ this.manager = manager;
+ }
+
+ /**
+ * Run method. This FrameThread will get a BlockEncodeRequest from the
+ * BlockThreadManager, encode the block, return it to the manager, then
+ * repeat. If no BlockEncodeRequest is available, or if it recieves a
+ * request with the "frameNumber" field set to a negative value, it will
+ * break the loop and end, notifying the manager it has ended.
+ *
+ */
+ public void run() {
+ boolean process = true;
+ synchronized(this) {
+ BlockEncodeRequest ber = manager.getWaitingRequest();
+ if(ber != null && ber.frameNumber < 0)
+ ber = null;
+ while(ber != null && process) {
+ if(ber.frameNumber < 0) {
+ process = false;
+ }
+ else {//get available BlockEncodeRequest from manager
+ ber.encodedSamples = frame.encodeSamples(ber.samples, ber.count,
+ ber.start, ber.skip, ber.result, ber.frameNumber);
+ ber.valid = true;
+ manager.returnFinishedRequest(ber);
+ ber = manager.getWaitingRequest();
+ }
+ }
+ manager.notifyFrameThreadExit(this);
+ }
+ }
+}
diff --git a/src/javaFlacEncoder/LPC.java b/src/javaFlacEncoder/LPC.java
new file mode 100644
index 0000000..4439fe2
--- /dev/null
+++ b/src/javaFlacEncoder/LPC.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+
+/**
+ * This class is used to calculate LPC Coefficients for a FLAC stream.
+ *
+ * @author Preston Lacey
+ */
+public class LPC {
+ /** The error calculated by the LPC algorithm */
+ protected double rawError;
+ /** The coefficients as calculated by the LPC algorithm */
+ protected double[] rawCoefficients;
+ private double[] tempCoefficients;
+ /** The order of this LPC calculation */
+ protected int order;
+ static int[] tempSum = null;
+ /**
+ * Constructor creates an LPC object of the given order.
+ * @param order Order for this LPC calculation.
+ */
+ public LPC(int order) {
+ this.order = order;
+ rawError = 0;
+ rawCoefficients = new double[order+1];
+ tempCoefficients = new double[order+1];
+ }
+ /**
+ * Get this LPC object's order
+ * @return order used for this LPC calculation.
+ */
+ public int getOrder () { return order; }
+ /**
+ * Get the error for this LPC calculation
+ * @return lpc error
+ */
+ public double getError() { return rawError; }
+
+ /**
+ * Get the calculated LPC Coefficients as an array.
+ * @return lpc coefficients in an array.
+ */
+ public double[] getCoefficients() { return rawCoefficients; }
+
+ /**
+ * Calculate an LPC using the given Auto-correlation data. Static method
+ * used since this is slightly faster than a more strictly object-oriented
+ * approach.
+ *
+ * @param lpc LPC to calculate
+ * @param R Autocorrelation data to use
+ */
+ public static void calculate(LPC lpc, long[] R) {
+ int coeffCount = lpc.order;
+
+ //calculate first iteration directly
+ double[] A = lpc.rawCoefficients;
+ for(int i = 0; i < coeffCount+1; i++) A[i] = 0.0;
+ A[0] = 1;
+ double E = R[0];
+
+ //calculate remaining iterations
+
+ if(R[0] == 0) {
+ for(int i = 0; i < coeffCount+1; i++)
+ A[i] = 0.0;
+ }
+ else {
+ double[] ATemp = lpc.tempCoefficients;
+ for(int i = 0; i < coeffCount+1; i++) ATemp[i] = 0.0;
+
+ for(int k = 0; k < coeffCount; k++) {
+ double lambda = 0.0;
+ double temp = 0;
+ for(int j = 0; j <= k; j++) {
+ temp += A[j]*R[k+1-j];
+ }
+ lambda = -temp/E;
+
+ for(int i = 0; i <= k+1; i++) {
+ ATemp[i] = A[i]+lambda*A[k+1-i];
+ }
+ System.arraycopy(ATemp, 0, A, 0, coeffCount+1);
+ E = (1-lambda*lambda)*E;
+ }
+ }
+ lpc.rawError = E;
+ }
+
+ /**
+ * Calculate an LPC using a prior order LPC's values to save calculations.
+ *
+ * @param lpc LPC to calculate
+ * @param R Auto-correlation data to use.
+ * @param priorLPC Prior order LPC to use(may be any order lower than our
+ * target LPC)
+ *
+ */
+ public static void calculateFromPrior(LPC lpc, long[] R, LPC priorLPC) {
+ int coeffCount = lpc.order;
+
+ //calculate first iteration directly
+ double[] A = lpc.rawCoefficients;
+ for(int i = 0; i < coeffCount+1; i++) A[i] = 0.0;
+ A[0] = 1;
+ double E = R[0];
+ int startIter = 0;
+ if(priorLPC != null && priorLPC.order < lpc.order) {
+ startIter = priorLPC.order;
+ E = priorLPC.rawError;
+ System.arraycopy(priorLPC.rawCoefficients, 0, A, 0, startIter+1);
+ }
+ //calculate remaining iterations
+ if(R[0] == 0) {
+ for(int i = 0; i < coeffCount+1; i++)
+ A[i] = 0.0;
+ }
+ else {
+ double[] ATemp = lpc.tempCoefficients;
+ for(int i = 0; i < coeffCount+1; i++) ATemp[i] = 0.0;
+
+ for(int k = startIter; k < coeffCount; k++) {
+ double lambda = 0.0;
+ double temp = 0.0;
+ for(int j = 0; j <= k; j++) {
+ temp -= A[j]*R[k-j+1];
+ }
+ lambda = temp/E;
+
+ for(int i = 0; i <= k+1; i++) {
+ ATemp[i] = A[i]+lambda*A[k+1-i];
+ }
+ System.arraycopy(ATemp, 0, A, 0, coeffCount+1);
+ E = (1-lambda*lambda)*E;
+ }
+ }
+ lpc.rawError = E;
+ }
+
+ /**
+ * Create auto-correlation coefficients(up to a maxOrder of 32).
+ * @param R Array to put results in.
+ * @param samples Samples to calculate the auto-correlation for.
+ * @param count number of samples to use
+ * @param start index of samples array to start at
+ * @param increment number of indices to increment between valid samples(for
+ * interleaved arrays)
+ * @param maxOrder maximum order to calculate.
+ */
+ public static void createAutoCorrelation(long[] R, int []samples, int count,
+ int start, int increment, int maxOrder) {
+ if(increment == 1 && start == 0) {
+ for(int i = 0; i <= maxOrder; i++) {
+ R[i] = 0;
+ long temp = 0;
+ for(int j = 0; j < count-i; j++) {
+ temp += (long)samples[j]*(long)samples[j+i];
+ }
+ R[i] += temp;
+ }
+ }
+ else {
+ for(int i = 0; i <= maxOrder; i++) {
+ R[i] = 0;
+ int baseIndex = increment*i;
+ long temp = 0;
+ int innerLimit = (count-i)*increment;
+ for(int j = start; j < innerLimit; j+=increment) {
+ temp += (long)samples[j]*(long)samples[j+baseIndex];
+ }
+ R[i] += temp;
+ }
+ }
+ }
+
+ /**
+ * Apply a window function to sample data
+ * @param samples Samples to apply window to. Values in this array are left
+ * unaltered.
+ * @param count number of samples to use
+ * @param start index of samples array to start at
+ * @param increment number of indices to increment between valid samples(for
+ * interleaved arrays)
+ * @param windowedSamples array containing windowed values. Return values
+ * are packed(increment of one).
+ *
+ */
+ public static void window(int[] samples, int count, int start, int increment,
+ int[] windowedSamples) {
+ int[] values = windowedSamples;
+ int loopCount = 0;
+ float halfway = count/2.0f;
+ float hth = halfway*halfway;
+ float windowCount = -halfway;
+ int limit = count*increment+start;
+ for(int i = start; i < limit; i+=increment) {
+ //float innerCount = (windowCount < 0) ? -windowCount:windowCount;
+ float innerCountSquared = windowCount*windowCount;
+ windowCount++;
+ //double val = 1.0-(double)(innerCount/halfway);
+ float val = 1.0f-( innerCountSquared/hth );
+ double temp = ((double)samples[i])*val;
+ temp = (temp >0) ? temp+0.5:temp-0.5;
+ values[loopCount++] = (int)temp;
+ }
+ }
+
+}
diff --git a/src/javaFlacEncoder/MetadataBlockHeader.java b/src/javaFlacEncoder/MetadataBlockHeader.java
new file mode 100644
index 0000000..1a0327d
--- /dev/null
+++ b/src/javaFlacEncoder/MetadataBlockHeader.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+
+/**
+ * The MetadataBlockHeader class is used to creat FLAC compliant Metadata Block
+ * Headers. See the FLAC specification for more information.
+ *
+ * @author Preston Lacey
+ */
+public class MetadataBlockHeader {
+
+ //boolean lastMetadataBlockFlag;//1 bit used
+ //byte blockType;//7 bits used
+ //int length;//24 bits used
+
+ /**
+ * Enum containing the different Metadata block types. See the FLAC spec
+ * for more information on the various types.
+ */
+ public enum MetadataBlockType {
+ /** A meta-block containing stream configuration information */
+ STREAMINFO,
+ /** A meta-block to pad the stream, allowing other meta-data to be
+ * written in the future without re-writing the entire stream.
+ */
+ PADDING,
+ /** Application meta-block*/
+ APPLICATION,
+ /** A meta-block which aids in seeking in the stream */
+ SEEKTABLE,
+ /** A meta-block for tags/comments */
+ VORBIS_COMMENT,
+ /** Cuesheet meta-block */
+ CUESHEET,
+ /** A meta-block to store an image, such as cover-art */
+ PICTURE
+ };
+
+ /**
+ * Constructor. This class defines no instance variables and only static
+ * methods.
+ */
+ public MetadataBlockHeader() {
+
+ }
+
+
+ /**
+ * Create a meta-data block header of the given type, and return the result
+ * in a new EncodedElement(so data is ready to be placed directly in FLAC
+ * stream)
+ *
+ * @param lastBlock True if this is the last meta-block in the stream. False
+ * otherwise.
+ *
+ * @param type enum indicating which type of block we're creating.
+ * @param length Length of the meta-data block which follows this header.
+ * @return EncodedElement containing the header.
+ */
+ public static EncodedElement getMetadataBlockHeader(boolean lastBlock,
+ MetadataBlockType type, int length) {
+ EncodedElement ele = new EncodedElement(4, 0);
+ int encodedLastBlock = (lastBlock) ? 1:0;
+ ele.addInt(encodedLastBlock, 1);
+ int encodedType = 0;
+ MetadataBlockType[] vals = MetadataBlockType.values();
+ for(int i = 0; i < vals.length; i++) {
+ if(vals[i] == type) {
+ encodedType = i;
+ break;
+ }
+ }
+
+ ele.addInt(encodedType, 7);
+ ele.addInt(length, 24);
+ return ele;
+ }
+
+}
diff --git a/src/javaFlacEncoder/MetadataBlockStreamInfo.java b/src/javaFlacEncoder/MetadataBlockStreamInfo.java
new file mode 100644
index 0000000..9e3f502
--- /dev/null
+++ b/src/javaFlacEncoder/MetadataBlockStreamInfo.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+
+/**
+ * MetadataBlockStreamInfo is used to declare the initial stream parameters,
+ * such as Sample Rate, bits per sample, and number of channels, as well as
+ * information on the encoded stream such as total number of samples, minimum
+ * and maximum block and frame sizes, and md5sum of raw audio samples. A
+ * StreamInfo block must be the first meta-data block in a FLAC stream, and only
+ * one StreamInfo block may exist.
+ *
+ * @author Preston Lacey
+ */
+public class MetadataBlockStreamInfo {
+ /** For Debugging: Higher level equals more debug statements */
+ static int DEBUG_LEV = 0;
+ /* int minimumBlockSize = 4096;//32768;//16 bits used; 16 minimum valid
+ int maximumBlockSize = 4096;//32768;//16 bits used; 65535 maximum valid
+ int minimumFrameSize = 0;//24 bits used; zero implies unknown
+ int maximumFrameSize = 0;//24 bits used; zero implies unknown
+ int sampleRate = 8000;//4096;//20 bits used, but 655350Hz max. 0 is invalid.
+ byte numberOfChannels = 1;//3 bits used
+ byte bitsPerSample = 16;//5 bits used. values 4-32 valid
+ long totalSamplesInStream = 0;//36 bits used. 0 implies unknown.
+ byte[] md5Hash;
+ */
+ /**
+ * Constructor. This class defines only static methods and fields.
+ */
+ public MetadataBlockStreamInfo() {
+
+ }
+
+ /**
+ * Create a FLAC StreamInfo metadata block with the given parameters. Because
+ * of the data stored in a StreamInfo block, this should generally be created
+ * only after all encoding is done.
+ *
+ * @param sc StreamConfiguration used in this FLAC stream.
+ * @param minFrameSize Size of smallest frame in FLAC stream.
+ * @param maxFrameSize Size of largest frame in FLAC stream.
+ * @param samplesInStream Total number of inter-channel audio samples in
+ * FLAC stream.
+ * @param md5Hash MD5 hash of the raw audio samples.
+ * @return EncodedElement containing created StreamInfo block.
+ */
+ public static EncodedElement getStreamInfo(StreamConfiguration sc,
+ int minFrameSize, int maxFrameSize, long samplesInStream, byte[] md5Hash) {
+ int bytes = getByteSize();
+ EncodedElement ele = new EncodedElement(bytes, 0);
+ int encodedBitsPerSample = sc.getBitsPerSample()-1;
+ ele.addInt(sc.getMinBlockSize(), 16);
+ ele.addInt(sc.getMaxBlockSize(), 16);
+ ele.addInt(minFrameSize, 24);
+ ele.addInt(maxFrameSize, 24);
+ ele.addInt(sc.getSampleRate(), 20);
+ ele.addInt(sc.getChannelCount()-1, 3);
+ ele.addInt(encodedBitsPerSample, 5);
+ ele.addLong(samplesInStream, 36);
+ for(int i = 0; i < 16; i++) {
+ ele.addInt(md5Hash[i], 8);
+ }
+ return ele;
+ }
+
+ /**
+ * Get the expected size of a properly formed STREAMINFO metadata block.
+ *
+ * @return size of properly formed FLAC STREAMINFO metadata block.
+ */
+ static public int getByteSize() {
+ int size = 0;
+ size += 16;
+ size += 16;
+ size += 24;
+ size += 24;
+ size += 20;
+ size += 3;
+ size += 5;
+ size += 36;
+ size += 64;
+ size += 64;
+ size = size/8;
+ return size;
+ }
+}
+
diff --git a/src/javaFlacEncoder/README b/src/javaFlacEncoder/README
new file mode 100644
index 0000000..24a6ad3
--- /dev/null
+++ b/src/javaFlacEncoder/README
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+The javaFlacEncoder project provides a FLAC encoder implemented in java.
+It is designed to enable easy addition of FLAC encoding support in java
+applications, though a command line file encoding utility is included as well.
+For usage of the command line encoder, see the "Console Usage" section below.
+
+For more information, go to http://javaflacencoder.sourceforge.net.
+See javadocs for information on how to make use of this library in your own
+application.
+
+
+
+Console Usage:
+ <commandName> [options] inputFilename outputFilename
+ note: <commandName> will depend on how your version is packaged, if it's
+ in a tar file, it will be similar to
+ java -classpath <path/to/file.jar> javaFlacEncoder/FLAC_ConsoleFileEncoder
+
+options:
+ -bmin <x> minimum block size, where <x> is an integer in range (16-65535)
+ -bmax <x> maximum block size, where <x> is an integer in range (16-65535)
+ -lpcmin <x> minimum LPC order, where <x> is an integer in range (1-32)
+ -lpcmax <x> maximum LPC order, where <x> is an integer in range (1-32)
+ -Threads <x> Specify whether to use threads. 0 turns threading off, greater
+ than 0 turns threading on
+ -sf <type> Specify which subframe type to use, where <type> may be:
+ exhaustive(this is default and recommended)
+ fixed
+ lpc
+ verbatim;
+
+
diff --git a/src/javaFlacEncoder/RiceEncoder.java b/src/javaFlacEncoder/RiceEncoder.java
new file mode 100644
index 0000000..eabb572
--- /dev/null
+++ b/src/javaFlacEncoder/RiceEncoder.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+
+/**
+ * The RiceEncoder class is used to create FLAC-compliant rice-encoded
+ * residuals.
+ *
+ * @author Preston Lacey
+ */
+public class RiceEncoder {
+ /** For debugging: Higher values equals greater output, generally in
+ * increments of 10 */
+ public static int DEBUG_LEV = 0;
+
+ private static final int POSITIVE = 0;
+ private static final int NEGATIVE = 1;
+ private static final int STOP_BIT = 0xFFFFFFFF;
+ private static final int UNARY_BIT = 0;
+
+ private int[] _dataToEncode = null;
+ private int[] _bitsToEncode = null;
+
+ /**
+ * Constructor. A RiceEncoder object is used(as opposed to potentially
+ * faster static methods), for memory considerations; some temporary,
+ * dynamically created arrays are kept between calls to the encode methods
+ * to prevent frequently allocating and freeing similarly sized arrays.
+ */
+ public RiceEncoder() {
+
+ }
+
+ /**
+ * Create the residual headers for a FLAC stream.
+ *
+ * @param useFiveBitParam Set TRUE if using a five-bit parameter size, FALSE
+ * for a four-bit parameter
+ * @param order Specify order of partitions to be used(actual number of
+ * partitions will be 2^order.
+ * @param ele EncodedElement to write header to.
+ * @return total written size of header.
+ */
+ public static int beginResidual(boolean useFiveBitParam, byte order,
+ EncodedElement ele) {
+ ele = ele.getEnd();
+ int paramSize = (useFiveBitParam) ? 1:0;
+ ele.addInt(paramSize, 2);
+ ele.addInt(order, 4);
+ return 6;
+ }
+
+ public static int encodeRicePartitionEscaped(int[] values, int inputOffset,
+ int inputStep, int inputCount, EncodedElement destEle,
+ int bitParam, boolean fiveBitParam) {
+ if(DEBUG_LEV > 0)
+ System.err.println("RiceEncoder::encode : Begin");
+ /* Currently, we're passing in an EncodedElement with a set byte[], and
+ filling that array. We should therefore ensure that we're not writing
+ too much to it. We *can* add another element to the given one if need.*/
+
+ //write headers(i.e, write the parameter)
+ int bitsWritten = 0;
+ if(fiveBitParam) {
+ destEle.addInt(255, 5);
+ destEle.addInt(16,5);
+ bitsWritten += 10;
+ }
+ else {
+ destEle.addInt(255, 4);
+ destEle.addInt(16,5);
+ bitsWritten += 9;
+ }
+
+ for(int i = 0; i < inputCount; i++) {
+ destEle.addInt(values[i*inputStep+inputOffset], 16);
+ bitsWritten += 16;
+ }
+
+ if(DEBUG_LEV > 0)
+ System.err.println("RiceEncoder::encode : End");
+ return bitsWritten;
+ }
+
+ /**
+ * Rice-encode a set of values, adding necessary headers for FLAC format. This
+ * encodes a single rice-partition. In general, beginResidual(...) should be
+ * used before this method.
+ *
+ * @param values array of integer values to save
+ * @param inputOffset start index in input array
+ * @param inputStep number of values to skip between target values(for
+ * interleaved data.
+ * @param inputCount number of total values to encode
+ * @param bitParam rice-parameter to use. This value should be based upon
+ * the distribution of the input data(roughly speeking, each value
+ * will require at least bitParam+1 bits to save, so this value
+ * should reflect the average magnitude of input values.
+ * @param destEle EncodedElement to save result to.
+ * @param fiveBitParam Set true if this header should use a five-bit
+ * rice-parameter, false for a four bit parameter.
+ * @return total encoded size(including headers)
+ */
+ public int encodeRicePartition(int[] values, int inputOffset,
+ int inputStep, int inputCount, EncodedElement destEle,
+ final int bitParam, boolean fiveBitParam) {
+ //Pack int version of encode partition
+ if(DEBUG_LEV > 0) {
+ System.err.println("RiceEncoder::encode : Begin");
+ System.err.println("-- bitParam: " + bitParam);
+ }
+
+ //write headers(i.e, write the parameter)
+ int startBits = destEle.getTotalBits();
+ if(fiveBitParam) {
+ destEle.addInt(bitParam, 5);
+ }
+ else {
+ destEle.addInt(bitParam, 4);
+ }
+ //encode each input value;
+ if(_dataToEncode == null || _bitsToEncode == null) {
+ _dataToEncode = new int[values.length*4];
+ _bitsToEncode = new int[values.length*4];
+ }
+ int[] dataToEncode = _dataToEncode;
+ int[] bitsToEncode = _bitsToEncode;
+ int nextToEncode = 0;
+ int maxToEncode = dataToEncode.length;
+ int inputIndex = inputOffset-inputStep;
+ final int stopbit = (bitParam >0) ? STOP_BIT << bitParam:STOP_BIT;
+ final int maskParam = (bitParam == 0) ? 0:0xFFFFFFFF>>>(32-bitParam);
+ for(int i = 0; i < inputCount; i++) {
+ inputIndex +=inputStep;
+ int value = values[inputIndex];
+ value = (value < 0) ? -2*value-1:2*value;
+ int upperBits = value >> bitParam;
+ //make sure we won't write to much. Handle if we will.
+ int dataToEncodeSpaceNeeded = (2+upperBits/32);
+ if(upperBits%32 != 0)
+ dataToEncodeSpaceNeeded++;
+ if(dataToEncodeSpaceNeeded+nextToEncode >= maxToEncode) {
+ //write everything we have:
+ destEle.packIntByBits(dataToEncode, bitsToEncode, 0, nextToEncode);
+ nextToEncode = 0;
+ }
+ //write unary upper bits:
+ int count = 0;
+ while(upperBits > 0) {
+ int tempVal = (upperBits > 32) ? 32:upperBits;//can only write 32 bits at a time.
+ dataToEncode[nextToEncode] = UNARY_BIT;
+ bitsToEncode[nextToEncode++] = tempVal;
+ upperBits -= tempVal;
+ count++;
+ }
+ dataToEncode[nextToEncode] = (value&maskParam) | stopbit ;
+ bitsToEncode[nextToEncode++] = bitParam+1;
+ }
+ //System.err.println("end loop");
+ //write remaining data to encode
+ if(nextToEncode > 0) {
+ destEle.packIntByBits(dataToEncode, bitsToEncode, 0, nextToEncode);
+ nextToEncode = 0;
+ }
+ int bitsWritten = destEle.getTotalBits() - startBits;
+ //System.err.println("RiceENcoder encode end:");
+ return bitsWritten;
+ }
+
+ /**
+ * Calculate how large a given set of values will be once it has been
+ * rice-encoded. While this method duplicates much of the process of
+ * rice-encoding, it is faster than an actual encode since the data is not
+ * actually written to the flac bitstream format(a rather costly write).
+ *
+ * @param values array of integer values to save
+ * @param inputOffset start index in input array
+ * @param inputStep number of values to skip between target values(for
+ * interleaved data.
+ * @param inputCount number of total values to encode
+ * @param bitParam rice-parameter to use. This value should be based upon
+ * the distribution of the input data(roughly speeking, each value
+ * will require at least bitParam+1 bits to save, so this value
+ * should reflect the average magnitude of input values.
+ * @return total encoded-size with given data and rice-parameter.
+ */
+ public static int calculateEncodeSize(int[] values, int inputOffset,
+ int inputStep, int inputCount, int bitParam) {
+ //Pack int version of encode partition
+ if(DEBUG_LEV > 0) {
+ System.err.println("RiceEncoder::calculateEncodeSize : Begin");
+ System.err.println("-- bitParam: " + bitParam);
+ }
+ int totalEncodeLength = inputCount*(bitParam+1);
+ int index = inputOffset-inputStep;
+ for(int i = 0; i < inputCount; i++) {
+ index += inputStep;
+ int value = values[index];
+ value = (value < 0) ? -2*value-1:2*value;
+ int upperBits = value >> bitParam;
+ totalEncodeLength += upperBits;
+ }
+ if(bitParam > 14)
+ totalEncodeLength += 5+6;
+ else
+ totalEncodeLength += 4+6;
+ return totalEncodeLength;
+ }
+
+}
diff --git a/src/javaFlacEncoder/StreamConfiguration.java b/src/javaFlacEncoder/StreamConfiguration.java
new file mode 100644
index 0000000..206a167
--- /dev/null
+++ b/src/javaFlacEncoder/StreamConfiguration.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+
+/**
+ * This class defines the configuration options that may not change throughout
+ * a FLAC stream. In general, these setting must be set to match the input
+ * audio used(sample rate, sample size, channels, etc). After a stream has
+ * started, these settings must not change.
+ *
+ * @author Preston Lacey
+ */
+public class StreamConfiguration implements Cloneable {
+
+ /** Maximum Block size allowed(defined by flac spec) */
+ public static final int MAX_BLOCK_SIZE = 65535;
+ /** Minimum block size allowed(defined by flac spec) */
+ public static final int MIN_BLOCK_SIZE = 16 ;
+ /** Maximum channel count allowed(defined by flac spec) */
+ public static final int MAX_CHANNEL_COUNT = 8;
+ /** Minimum sample rate allowed(defined by flac spec) */
+ public static final int MIN_SAMPLE_RATE = 1;
+ /** Maximum sample rate allowed(defined by flac spec) */
+ public static final int MAX_SAMPLE_RATE = 655350;
+ /** Minimum bits per sample allowed(defined by flac spec) */
+ public static final int MIN_BITS_PER_SAMPLE = 4;
+ /** Maximum bits per sample allowed(FLAC spec allows 32, limited to 24 here
+ * due to limits in code) */
+ public static final int MAX_BITS_PER_SAMPLE = 24;
+ /** Default channel count */
+ public static final int DEFAULT_CHANNEL_COUNT = 2;
+ /** Default maximum block size */
+ public static final int DEFAULT_MAX_BLOCK_SIZE = 4096;
+ /** Default minimum block size */
+ public static final int DEFAULT_MIN_BLOCK_SIZE = 4096;
+ /** Default sample rate */
+ public static final int DEFAULT_SAMPLE_RATE = 44100;
+ /** Default sample size */
+ public static final int DEFAULT_BITS_PER_SAMPLE = 16;
+
+ int channelCount;
+ int maxBlockSize;
+ int minBlockSize;
+ int sampleRate;
+ int bitsPerSample;
+
+ /* is the currently set configuration valid? Encoders should not attempt to
+ * use an invalid configuration */
+ boolean validConfig = false;
+
+ /**
+ * Constructor, sets defaults for most values. Some default values must be
+ * changed to match the audio characteristics(channel count, sample rate,
+ * sample size).
+ */
+ public StreamConfiguration() {
+ channelCount = DEFAULT_CHANNEL_COUNT;
+ maxBlockSize = DEFAULT_MAX_BLOCK_SIZE;
+ minBlockSize = DEFAULT_MIN_BLOCK_SIZE;
+ sampleRate = DEFAULT_SAMPLE_RATE;
+ bitsPerSample = DEFAULT_BITS_PER_SAMPLE;
+ validConfig = true;
+ }
+
+ /**
+ * Copy Constructor. No values are altered or verified for sanity
+ * @param sc StreamConfiguration object to copy
+ */
+ public StreamConfiguration(StreamConfiguration sc) {
+ channelCount = sc.channelCount;
+ maxBlockSize = sc.maxBlockSize;
+ minBlockSize = sc.minBlockSize;
+ sampleRate = sc.sampleRate;
+ bitsPerSample = sc.bitsPerSample;
+ validConfig = sc.validConfig;
+ }
+
+ /**
+ * Constructor, allows setting of all options. In general, parameters given
+ * must match the input audio characteristics. minBlock and maxBlock may be
+ * set as desired, though minBlock is expected to be less than or equal to
+ * maxBlock. If minBlock or maxBlock is out of a valid range, it will be
+ * automatically adjusted to the closest valid value.
+ *
+ * @param channelCount number of channels in source audio stream
+ * @param minBlock minimum block to use in FLAC stream.
+ * @param maxBlock maximum block size to use in FLAC stream
+ * @param sampleRate sample rate in Hz of audio stream
+ * @param bitsPerSample sample size of audio stream
+ */
+ public StreamConfiguration(int channelCount, int minBlock, int maxBlock,
+ int sampleRate, int bitsPerSample) {
+ validConfig = true;
+ validConfig &= setChannelCount(channelCount);
+ validConfig &= setSampleRate(sampleRate);
+ validConfig &= setBitsPerSample(bitsPerSample);
+ setMaxBlockSize(maxBlock);
+ setMinBlockSize(minBlock);
+ }
+
+ /**
+ * Test if the current configuration is valid. While most set methods will
+ * ensure the values are in a valid range before setting them, some
+ * settings(such as number of channels), cannot be guessed. This method may
+ * alter current values to make them valid if possible.
+ * @return true if configuration defines a valid FLAC stream, false othwerise.
+ */
+ public boolean isValid() {
+ validConfig = true;
+ setMinBlockSize(minBlockSize);
+ setMaxBlockSize(maxBlockSize);
+ validConfig &= (minBlockSize <= maxBlockSize);
+ validConfig &= setChannelCount(channelCount);
+ validConfig &= setSampleRate(sampleRate);
+ validConfig &= setBitsPerSample(bitsPerSample);
+ return validConfig;
+ }
+
+ /**
+ * Set number of channels in stream. Because this is not a value that may be
+ * guessed and corrected, the value will be set to that given even if it is
+ * not valid.
+ * @param count Number of channels
+ * @return true if the channel count is within the valid range, false
+ * otherwise.
+ */
+ public boolean setChannelCount(int count) {
+ boolean result = count > 0 && count <= MAX_CHANNEL_COUNT;
+ channelCount = count;
+ return result;
+ }
+
+ /**
+ * Get the currently set channel count
+ * @return channel count
+ */
+ public int getChannelCount() {
+ return channelCount;
+ }
+
+ /**
+ * Get the currently set maximum block size
+ * @return maximum block size
+ */
+ public int getMaxBlockSize() {
+ return maxBlockSize;
+ }
+
+ /**
+ * Get the currently set minimum block size
+ * @return minimum block size
+ */
+ public int getMinBlockSize() {
+ return minBlockSize;
+ }
+
+ /**
+ * Get the currently set sample rate
+ * @return sample rate(in Hz)
+ */
+ public int getSampleRate() {
+ return sampleRate;
+ }
+
+ /**
+ * Set the sample rate. Because this is not a value that may be
+ * guessed and corrected, the value will be set to that given even if it is
+ * not valid.
+ * @param rate sample rate(in Hz)
+ * @return true if given rate was within the valid range, false otherwise.
+ */
+ public boolean setSampleRate(int rate) {
+ boolean result = (rate <= MAX_SAMPLE_RATE && rate >= MIN_SAMPLE_RATE);
+ sampleRate = rate;
+ return result;
+ }
+
+ /**
+ * Get the number of bits per sample
+ * @return bits per sample
+ */
+ public int getBitsPerSample() {
+ return bitsPerSample;
+ }
+
+ /**
+ * Set the bits per sample. Because this is not a value that may be
+ * guessed and corrected, the value will be set to that given even if it is
+ * not valid.
+ * @param bitsPerSample number of bits per sample
+ * @return true if value given is within the valid range, false otherwise.
+ */
+ public boolean setBitsPerSample(int bitsPerSample) {
+ boolean result = ((bitsPerSample <= MAX_BITS_PER_SAMPLE) &&
+ (bitsPerSample >= MIN_BITS_PER_SAMPLE) );
+ this.bitsPerSample = bitsPerSample;
+ return result;
+ }
+
+ /**
+ * Set the maximum block size to use. If this value is out of a valid range,
+ * it will be set to the closest valid value. User must ensure that this
+ * value is set above or equal to the minimum block size.
+ * @param size maximum block size to use.
+ * @return actual size set
+ */
+ public int setMaxBlockSize(int size) {
+ maxBlockSize = (size <= MAX_BLOCK_SIZE) ? size:MAX_BLOCK_SIZE;
+ maxBlockSize = (maxBlockSize >= MIN_BLOCK_SIZE) ? maxBlockSize:MIN_BLOCK_SIZE;
+ return maxBlockSize;
+ }
+
+ /**
+ * Set the minimum block size to use. If this value is out of a valid range,
+ * it will be set to the closest valid value. User must ensure that this
+ * value is set below or equal to the maximum block size.
+ * @param size minimum block size to use.
+ * @return actual size set
+ */
+ public int setMinBlockSize(int size) {
+ minBlockSize = (size <= MAX_BLOCK_SIZE) ? size:MAX_BLOCK_SIZE;
+ minBlockSize = (minBlockSize >= MIN_BLOCK_SIZE) ? maxBlockSize:MIN_BLOCK_SIZE;
+ return minBlockSize;
+ }
+
+ /**
+ * Test if stream is Subset compliant. FLAC defines a subset of options to
+ * ensure resulting FLAC streams are streamable. Not all options in that
+ * subset are defined by the StreamConfiguration class, however, but exist
+ * in the EncodingConfiguration class. Therefore, the alternative method
+ * {@link StreamConfiguration#isEncodingSubsetCompliant(javaFlacEncoder.EncodingConfiguration)
+ * isEncodingSubsetCompliant}
+ * should be checked as well to ensure the combined Stream/Encoding
+ * configurations are BOTH valid.
+ * @return true if this configuration is Subset compliant, false otherwise.
+ */
+ public boolean isStreamSubsetCompliant() {
+ boolean result = true;
+ result &= (maxBlockSize < 16384);
+ if(sampleRate <= 48000)
+ result &= (maxBlockSize < 4608);
+ switch(bitsPerSample) {
+ case 8:
+ case 12:
+ case 16:
+ case 20:
+ case 24: break;
+ default: result = false;
+ }
+ return result;
+ }
+
+ /**
+ * Test if this StreamConfiguration and a paired EncodingConfiguration define
+ * a Subset compliant stream. FLAC defines a subset of options to
+ * ensure resulting FLAC streams are streamable.
+ * @param ec EncodingConfiguration object to check against
+ * @return true if these configurations are Subset compliant, false otherwise.
+ */
+ public boolean isEncodingSubsetCompliant(EncodingConfiguration ec) {
+ boolean result = true;
+ result = isStreamSubsetCompliant();
+ if(this.sampleRate <= 48000) {
+ result &= ec.maximumLPCOrder <= 12;
+ result &= ec.maximumRicePartitionOrder <= 8;
+ }
+ return result;
+ }
+}
diff --git a/src/javaFlacEncoder/Subframe.java b/src/javaFlacEncoder/Subframe.java
new file mode 100644
index 0000000..7d0a08d
--- /dev/null
+++ b/src/javaFlacEncoder/Subframe.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+
+/**
+ * Description: This abstract class declares the methods needed to retrieve
+ * encoded data in a standard format(across the different implemented Subframe
+ * types), as well as the generic methods needed to write the subframe header.
+ * It is assumed that objects of this type will be reused in future frames,
+ * rather than being destroyed and made anew. This is to avoid the overhead
+ * associated with creating and destroying objects in Java.
+ * @author Preston Lacey
+ */
+public abstract class Subframe {
+
+ /** StreamConfiguration used for encoding. The same StreamConfiguration
+ * MUST be used throughout an entire FLAC stream */
+ protected StreamConfiguration sc;
+
+ /** EncodingConfiguration used for encoding. This may be altered between
+ * frames, but must be the same for each subframe within that frame */
+ protected EncodingConfiguration ec;
+ /** Store for size of last subframe encoded(in bits). */
+ protected int lastEncodedSize;
+
+ /**
+ * Constructor is private to prevenet it's use, as a subframe is not usable
+ * without first setting a StreamConfiguration(therefore, use other
+ * constructor.
+ */
+ private Subframe() {
+
+ }
+
+ /**
+ * Constructor. Sets StreamConfiguration to use. If the StreamConfiguration
+ * must later be changed, a new Subframe object must be created as well. A
+ * default EncodingConfiguration is created using EncodingConfiguration's
+ * default Constructor. This configuration should create a decent encode,
+ * but may be altered if desired.
+ *
+ * @param sc StreamConfiguration to use for encoding.
+ */
+ public Subframe(StreamConfiguration sc) {
+ this.sc = sc;
+ ec = new EncodingConfiguration();
+ }
+
+ /**
+ * This method is used to set the encoding configuration.
+ * @param ec encoding configuration to use.
+ * @return true if configuration was changed, false otherwise
+ */
+ public boolean registerConfiguration(EncodingConfiguration ec) {
+ this.ec = ec;
+ return true;
+ }
+
+ /**
+ * Encodes samples into the appropriate compressed format, saving the result
+ * in the given “data” EncodedElement list. Encodes 'count' samples, from
+ * index 'start', to index 'start' times 'skip', where “skip” is the format
+ * that samples may be packed in an array. For example, 'samples' may
+ * include both left and right samples of a stereo stream, while this
+ * SubFrame is only encoding the 'right' channel(channel 2). Therefore,
+ * “skip” would equal 2, resulting in the valid indices being only those
+ * where “index mod 2 equals 1”.
+ * @param samples the audio samples to encode. This array may contain
+ * samples for multiple channels, interleaved; only one of these channels is
+ * encoded by a subframe.
+ * @param count the number of samples to encode.
+ * @param start the index to start at in the array.
+ * @param skip the number of indices to skip between successive samples
+ * (for use when channels are interleaved in the given
+ * array).
+ * @param data the EncodedElement to attach encoded data to. Data in
+ * EncodedElement given is not altered. New data is
+ * attached starting with “data.getNext()”. If “data”
+ * already has a “next” set, it will be lost!
+ * @param offset
+ * @param bitsPerSample Number of bits per single-channel sample. This may
+ * differ from the StreamConfiguration's sample size, depending on the
+ * subframe used(i.e, the "side-channel" of a FLAC stream uses one extra bit
+ * compared to the input channels).
+ *
+ * @return number of encoded samples, or negative value indicating
+ * an error has occurred.
+ *
+ */
+ public abstract int encodeSamples(int[] samples, int count, int start, int skip,
+ EncodedElement data, int offset, int bitsPerSample);
+
+ /**
+ * Returns the total number of valid bits used in the last encoding(i.e, the
+ * number of compressed bits used). This is here for convenience, as the
+ * calling object may also loop through the resulting EncodingElement from
+ * the encoding process and sum the valid bits.
+ *
+ * @return an integer with value of the number of bits used in last
+ * encoding.
+ */
+ public int getEncodedSize() {
+ return lastEncodedSize;
+ }
+}
diff --git a/src/javaFlacEncoder/Subframe_Constant.java b/src/javaFlacEncoder/Subframe_Constant.java
new file mode 100644
index 0000000..14a404e
--- /dev/null
+++ b/src/javaFlacEncoder/Subframe_Constant.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+
+/**
+ * Implements the Subframe abstract class, providing encoding support for the
+ * FLAC Constant Subframe.
+ *
+ * @author Preston Lacey
+ */
+public class Subframe_Constant extends Subframe {
+ /** For debugging: Higher values equals greater output, generally by
+ * increments of 10. */
+ public static int DEBUG_LEV = 0;
+ /** Subframe type supported by this implementation. */
+ public static final EncodingConfiguration.SubframeType type =
+ EncodingConfiguration.SubframeType.VERBATIM;
+ int sampleSize = 0;
+
+ /**
+ * Constructor. Sets StreamConfiguration to use. If the StreamConfiguration
+ * must later be changed, a new Subframe object must be created as well.
+ *
+ * @param sc StreamConfiguration to use for encoding.
+ */
+ public Subframe_Constant(StreamConfiguration sc) {
+ super(sc);
+ sampleSize = sc.getBitsPerSample();
+ }
+
+ /**
+ * This method is used to set the encoding configuration.
+ * @param ec encoding configuration to use.
+ * @return true if configuration was changed, false otherwise
+ */
+ @Override
+ public boolean registerConfiguration(EncodingConfiguration ec) {
+ super.registerConfiguration(ec);
+ return true;
+ }
+
+
+ public int encodeSamples(int[] samples, int count, int start, int skip,
+ EncodedElement data, int offset, int bitsPerSample ) {
+ if(DEBUG_LEV > 0) {
+ System.err.println("Subframe_Verbatim::encodeSamples(...)");
+ }
+ int encodedSamples = count;
+ int bits = bitsPerSample+offset+8;
+ int bytesNeeded = bits/8;
+ if(bits%8 != 0)
+ bytesNeeded++;
+ data.clear(bytesNeeded, offset);
+ data.addInt(0,1);
+ data.addInt(0,6);
+ data.addInt(0,1);
+
+ int value = samples[start];
+ int increment = skip+1;
+ int end = start+increment*count;
+ int lastValid = end-increment;//assume all were the same
+ for(int i = start; i < end; i+= increment) {
+ if(samples[i] != value) {
+ lastValid = i-increment;//if one differed, find where
+ break;
+ }
+ }
+ encodedSamples = (lastValid-start)/increment+1;
+ data.addInt(value,bitsPerSample);
+ lastEncodedSize = bits - offset;
+ System.out.flush();
+ if(DEBUG_LEV > 0)
+ System.err.println("Subframe_Verbatim::encodeSamples(...): End");
+ if(DEBUG_LEV > 10) {
+ System.err.println("--: bitsUsed : "+bits+" : Bytes : "+bytesNeeded);
+ }
+ return encodedSamples;
+ }
+
+}
diff --git a/src/javaFlacEncoder/Subframe_Fixed.java b/src/javaFlacEncoder/Subframe_Fixed.java
new file mode 100644
index 0000000..bb3eed4
--- /dev/null
+++ b/src/javaFlacEncoder/Subframe_Fixed.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+
+/**
+ * Implements the Subframe abstract class, providing encoding support for the
+ * FLAC Fixed-predictor Subframe.
+ *
+ * @author Preston Lacey
+ */
+public class Subframe_Fixed extends Subframe {
+ /** For debugging: Higher values equals greater output, generally in
+ * increments of 10 */
+ public static int DEBUG_LEV = 0;
+ /** Subframe type supported by this implementation. */
+ public static final EncodingConfiguration.SubframeType type =
+ EncodingConfiguration.SubframeType.FIXED;
+ int sampleSize = 0;
+ RiceEncoder rice = null;
+ int [] bits;
+ int [] lowOrderBits;
+ long [] sum;
+
+ int _error1[] = null;
+ int _error2[] = null;
+ int _error3[] = null;
+ int _error4[] = null;
+ int _lastCount = 0;
+ int _order;
+ int[] _errors = null;
+ int _offset = 0;
+ int _start = 0;
+ int _skip = 0;
+ int _errorStep = 0;
+ int _totalBits;
+ int[] _samples = null;
+ int _errorOffset = 0;
+ int _errorCount = 0;
+ int _frameSampleSize = 0;
+
+ private static final double LOG_2 = Math.log(2);
+
+ /**
+ * Constructor. Sets StreamConfiguration to use. If the StreamConfiguration
+ * must later be changed, a new Subframe object must be created as well.
+ *
+ * @param sc StreamConfiguration to use for encoding.
+ */
+ public Subframe_Fixed(StreamConfiguration sc) {
+ super(sc);
+ sampleSize = sc.getBitsPerSample();
+ rice = new RiceEncoder();
+ bits = new int[5];
+ lowOrderBits = new int[5];
+ sum = new long[5];
+ _lastCount = -1;
+ }
+
+ /**
+ * This method is used to set the encoding configuration.
+ * @param ec encoding configuration to use.
+ * @return true if configuration was changed, false otherwise
+ */
+ @Override
+ public boolean registerConfiguration(EncodingConfiguration ec) {
+ super.registerConfiguration(ec);
+ return true;
+ }
+
+ public int encodeSamples(int[] samples, int count, int start, int skip,
+ int offset, int unencSampleSize ) {
+ int encodedSamples = count;
+ if(DEBUG_LEV > 0) {
+ System.err.println("Subframe_Fixed::encodeSamples(...) : Begin");
+ if(DEBUG_LEV > 10) {
+ System.err.println("--count : " +count);
+ System.err.println("start:skip:offset:::"+start+":"+skip+":"+offset);
+ }
+ }
+ int increment = skip+1;
+ //create space for results: Need four sets for the 5 different versions,
+ // the e0 is sampe as input samples, so no duplicate needed.
+ if(count != _lastCount) {
+ _error1 = new int[count];
+ _error2 = new int[count];
+ _error3 = new int[count];
+ _error4 = new int[count];
+ _lastCount = count;
+ }
+ int [] error1 = _error1;
+ int [] error2 = _error2;
+ int [] error3 = _error3;
+ int [] error4 = _error4;
+ long sum0 = 0;
+ long sum1 = 0;
+ long sum2 = 0;
+ long sum3 = 0;
+ long sum4 = 0;
+ //apply the algorithm to determine errors, summing abs vals as we go
+ int tempI;
+ int index = start;
+ for(int i = 0; i < count; i++) {
+ tempI = samples[index];
+ if(tempI < 0) tempI = -tempI;
+ sum0 += tempI;
+ index += increment;
+ }
+ for(int i = 1; i < 5; i++) {
+ error1[i] = samples[start+i*increment]-samples[start+(i-1)*increment];
+ tempI = error1[i];
+ tempI = (tempI < 0) ? -tempI:tempI;
+ sum1 += tempI;
+ if(i > 1) {
+ error2[i] = error1[i]-error1[(i-1)];
+ tempI = error2[i];
+ tempI = (tempI < 0) ? -tempI:tempI;
+ sum2 += tempI;
+ }
+ if(i > 2) {
+ error3[i] = error2[i]-error2[(i-1)];
+ tempI = error3[i];
+ tempI = (tempI < 0) ? -tempI:tempI;
+ sum3 += tempI;
+ }
+ if(i > 3) {
+ error4[i] = error3[i]-error3[(i-1)];
+ tempI = error4[i];
+ tempI = (tempI < 0) ? -tempI:tempI;
+ sum4 += tempI;
+ }
+ }
+ index = start+5*increment;
+ for(int i = 5; i < count; i++) {
+ //error1[i] = samples[start+i*increment]-samples[start+(i-1)*increment];
+ error1[i] = samples[index]-samples[index-increment];
+ tempI = error1[i];
+ tempI = (tempI < 0) ? -tempI:tempI;
+ sum1 += tempI;
+ error2[i] = error1[i]-error1[(i-1)];
+ tempI = error2[i];
+ tempI = (tempI < 0) ? -tempI:tempI;
+ sum2 += tempI;
+ error3[i] = error2[i]-error2[(i-1)];
+ tempI = error3[i];
+ tempI = (tempI < 0) ? -tempI:tempI;
+ sum3 += tempI;
+ error4[i] = error3[i]-error3[(i-1)];
+ tempI = error4[i];
+ tempI = (tempI < 0) ? -tempI:tempI;
+ sum4 += tempI;
+ index += increment;
+ }
+ //select best algorithm as indicated by bits needed from sum of values
+ // and number of priming samples needed.
+ int order = 0;
+ long sumsX;
+ for(int i = 0; i < 5; i++) {
+ if(i == 0)
+ sumsX = sum0;
+ else if(i == 1)
+ sumsX = sum1;
+ else if(i == 2)
+ sumsX = sum2;
+ else if(i == 3)
+ sumsX = sum3;
+ else
+ sumsX = sum4;
+
+ double tempLowOrderBits = LOG_2*(sumsX/(count-i));
+ lowOrderBits[i] = (int)(Math.ceil(Math.log(tempLowOrderBits)/LOG_2));
+ if(lowOrderBits[i] < 1)
+ lowOrderBits[i] = 1;
+ else if (lowOrderBits[i] > sampleSize)
+ lowOrderBits[i] = sampleSize;
+ //lowOrderBits[i]++;//DOUBLE CHECK VALIDITY OF THIS. Decreases the bits needed, but "shouldn't"
+ //bits[i] = (int)(Math.log(sum[i])/Math.log(10))*(count-1)+sampleSize*i;
+ bits[i] = (int)(lowOrderBits[i]*(count-i)+sampleSize*i+1);
+ order = (bits[i] < bits[order]) ? i:order;
+ }
+
+ int[] errors = null;
+ int errorCount = count-order;
+ int errorOffset = order;
+ int errorStep = 1;
+ switch(order) {
+ case 0: errors = samples;
+ errorStep+=skip;
+ errorOffset=start;break;
+ case 1: errors = error1;break;
+ case 2: errors = error2;break;
+ case 3: errors = error3;break;
+ case 4: errors = error4;break;
+ }
+ _order = order;
+ _offset = offset;
+ _start = start;
+ _errorStep = errorStep;
+ _errorOffset = errorOffset;
+ _errorCount = errorCount;
+ _skip = skip;
+ _samples = samples;
+ _frameSampleSize = unencSampleSize;
+ _errors = errors;
+ _totalBits = unencSampleSize*order+8+ RiceEncoder.calculateEncodeSize(
+ errors,errorOffset, errorStep, errorCount, lowOrderBits[order]);
+ return encodedSamples;
+ }
+
+ /**
+ * Return the estimated size of the previous encode attempt in bits. Since
+ * returning the data from an encode is costly(due to the rice encoding and FLAC
+ * compliant bit-packing), this allows us to estimate the size first, and
+ * therefore choose another subframe type if this is larger.
+ *
+ * @return estimated size in bits of encoded subframe.
+ */
+ public int estimatedSize() {
+ return _totalBits;
+ }
+
+ public EncodedElement getData() {
+ EncodedElement dataEle = new EncodedElement(_totalBits/8+1,_offset);
+ getData(dataEle);
+ return dataEle;
+ }
+ /**
+ * Get the data from the last encode attempt. Data is returned in an
+ * EncodedElement, properly packed at the bit-level to be added directly to
+ * a FLAC stream.
+ *
+ * @return EncodedElement containing encoded subframe
+ */
+ public EncodedElement getData(EncodedElement dataEle) {
+ //EncodedElement dataEle = new EncodedElement(_totalBits/8+1,_offset);
+ int startSize = dataEle.getTotalBits();
+ int unencSampleSize = _frameSampleSize;
+ //write headers
+ int encodedType = 1<<3 | _order;
+ dataEle.addInt(0, 1);
+ dataEle.addInt(encodedType, 6);
+ dataEle.addInt(0, 1);
+ if(_order > 0) {
+ dataEle.packInt(_samples, unencSampleSize, _start, _skip, _order);
+ }
+
+ //send best data to rice encoder
+ int paramSize = (lowOrderBits[_order] > 14) ? 5:4;
+ boolean fiveBitParam = (paramSize < 5) ? false:true;
+ RiceEncoder.beginResidual(fiveBitParam, (byte)0, dataEle);
+ /*for(int i = 0; i < errorCount; i++) {
+ int error = errors[errorOffset+i*errorStep];
+ if(error >= 32767 || error <= -32767)
+ System.err.println("Error Bound issue?: " + error);
+ }*/
+ rice.encodeRicePartition(_errors, _errorOffset, _errorStep,
+ _errorCount, dataEle, lowOrderBits[_order], fiveBitParam);
+
+ this.lastEncodedSize = dataEle.getTotalBits()-startSize;
+ if(DEBUG_LEV > 0)
+ System.err.println("Subframe_Fixed::encodeSamples(...): End");
+ return dataEle;
+ }
+
+ public int encodeSamples(int[] samples, int count, int start, int skip,
+ EncodedElement dataEle, int offset, int unencSampleSize ) {
+ int encodedSamples = 0;
+ encodedSamples = encodeSamples(samples, count, start, skip, offset, unencSampleSize);
+ dataEle.clear(_totalBits/8+1,offset);
+ getData(dataEle);
+ return encodedSamples;
+ }
+
+ /*private int[] calculatePartitionsCount(int[] errors, int errorOffset, int errorStep,
+ int errorCount) {
+ int maxPartitions = 8;
+ int[][] sums = new int[(int)Math.pow(2,maxPartitions)][];
+ for(int i = 0; i < maxPartitions; i++) {
+ sums[i] = new int[(int)Math.pow(2, i)];
+ }
+ int[] counts = new int[maxPartitions];
+ int[] usedPartitions = new int[maxPartitions];
+ int[] lastPartitionCount = new int[maxPartitions];
+ for(int i = 0; i < maxPartitions; i++) {
+ counts[i] = errorCount/sums[i].length;
+ if(errorCount % sums[i].length != 0) counts[i]++;
+ usedPartitions[i] = errorCount/counts[i];
+ if(errorCount %counts[i] != 0) usedPartitions[i]++;
+ }
+ for(int i = 0; i < maxPartitions; i++) {
+ int temp = errorCount+errorOffset/
+ }
+ int[] sizes = new int[maxPartitions];
+
+ int temp = 0;
+ double log2 = Math.log(2);
+ for(int i = 0; i < errorCount; i++) {
+ temp = errors[errorOffset+i*errorStep];
+ if(temp < 0) temp = -temp;
+ for(int x = 0; x < maxPartitions; x++) {
+ float destDiv = i/counts[x];
+ int destIndex = (int)(i/destDiv);
+ sums[x][destIndex] += temp;
+ }
+
+ }
+ //sum up all bit sizes per partition, choose best size.
+ for(int i = 0; i < maxPartitions; i++) {
+ int tempTotal = 0;
+ sizes[i] = 0;
+
+ for(int x = 0; x < usedPartitions[i]; x++) {
+ tempTotal = (int)(Math.log(sums[i][x])/Math.log(2));
+ float destDiv = (float)errorCount/sums[x].length;
+ int destCount = errorCount/sums[x].length;
+ int destIndex = (int)(i/destDiv);
+ sizes[i] += tempTotal+4+;
+ }
+ }
+ return results;
+ }*/
+
+}
diff --git a/src/javaFlacEncoder/Subframe_LPC.java b/src/javaFlacEncoder/Subframe_LPC.java
new file mode 100644
index 0000000..fa7f28f
--- /dev/null
+++ b/src/javaFlacEncoder/Subframe_LPC.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+
+/**
+ * Implements the Subframe abstract class, providing encoding support for the
+ * FLAC LPC Subframe.
+ *
+ * @author Preston Lacey
+ */
+public class Subframe_LPC extends Subframe {
+ public static long totalTime = 0;
+ private class PartialResult {
+ int[] samples;
+ int start;
+ int increment;
+ int count;
+ int subframeSampleSize;
+
+ int lpcOrder;
+ int lowOrderBits;
+ int totalBits;
+ int precision;
+ int lastCount;
+ }
+
+ /* Following values used frequently, let's calculate just once */
+ private static final double LOGE_2 = Math.log(2);
+ private static final double SQRT_2 = Math.sqrt(2);
+
+ /** Maximum LPC order that is supported by this subframe */
+ public static final int MAX_LPC_ORDER = 32;
+
+ /** For debugging: Higher values equals greater output, generally in
+ * increments of 10 */
+ public static int DEBUG_LEV = 0;
+
+ /** Subframe type implemented by this subframe. */
+ public static final EncodingConfiguration.SubframeType type =
+ EncodingConfiguration.SubframeType.LPC;
+
+ int sampleSize = 0;
+ RiceEncoder rice = null;
+ int _lpcOrder = 0;
+ int _lowOrderBits = 0;
+ int _totalBits = 0;
+ int _precision = 15;
+ int _lastCount = 0;
+ int[] _errors = null;
+ int[] tempErrors = null;
+ int[] _quantizedCoeffs = null;
+ int[] tempCoeffs = null;
+ int _shift = 0;
+ LPC[] lpcs = null;
+ int[] _samples = null;
+ int _offset = 0;
+ int _frameSampleSize;
+ int _start = 0;
+ int _increment = 0;
+ long[] correlations = null;
+ int[] _windowedSamples = null;
+
+ Subframe_LPC(StreamConfiguration sc) {
+ super(sc);
+ sampleSize = sc.getBitsPerSample();
+ rice = new RiceEncoder();
+ lpcs = new LPC[MAX_LPC_ORDER+1];
+ for(int i = 0; i < MAX_LPC_ORDER+1; i++)
+ lpcs[i] = new LPC(i);
+ _lastCount = -1;
+ _quantizedCoeffs = new int[MAX_LPC_ORDER+1];
+ tempCoeffs = new int[MAX_LPC_ORDER+1];
+ }
+
+ /**
+ * This method is used to set the encoding configuration.
+ * @param ec encoding configuration to use.
+ * @return true if configuration was changed, false otherwise
+ */
+ @Override
+ public boolean registerConfiguration(EncodingConfiguration ec) {
+ return super.registerConfiguration(ec);
+ }
+
+ public int encodeSamples(int[] samples, int count, int start, int skip,
+ int offset, int unencSampleSize) {
+ int encodedSamples = count;
+ if(DEBUG_LEV > 0) {
+ System.err.println("Subframe_LPC::encodeSamples(...) : Begin");
+ if(DEBUG_LEV > 10) {
+ System.err.println("--count : " +count);
+ System.err.println("start:skip:offset:::"+start+":"+skip+":"+offset);
+ }
+ }
+ int increment = skip+1;
+ if(count != _lastCount) {
+ _errors = new int[count];
+ tempErrors = new int[count];
+ _lastCount = count;
+ _windowedSamples = new int[count];
+ }
+ int minOrder = ec.getMinLPCOrder();
+ int maxOrder = ec.getMaxLPCOrder();
+ int frameSampleSize = unencSampleSize;
+ int order = -1;
+ int totalBits = 0;
+ long[] R = correlations;
+ if(R == null || R.length < maxOrder+1) {
+ R = new long[maxOrder+1];
+ correlations = R;
+ }
+ LPC.window(samples, count, start, increment, _windowedSamples);
+ LPC.createAutoCorrelation(R, _windowedSamples, count, 0, 1, maxOrder);
+ int[] coefficients = tempCoeffs;
+ int[] errors = tempErrors;
+ int lowOrderBits = 0;
+ int precision = 0;
+ int shift = 0;
+ int watchCount = 2;
+ for(int i = maxOrder; i >= minOrder; i--) {
+ LPC.calculate(lpcs[i], R);
+ int tempTotalBits = partialEncodeLPC(samples, count, start, increment,
+ lpcs[i], this,frameSampleSize);
+ //compare to current order: If last not set or size < last, replace
+ if(tempTotalBits < totalBits || order == -1) {
+ order = i;
+ totalBits = tempTotalBits;
+ lowOrderBits = _lowOrderBits;
+ precision = _precision;
+ shift = _shift;
+ int[] temp = coefficients;
+ coefficients = _quantizedCoeffs;
+ _quantizedCoeffs = temp;
+ temp = errors;
+ errors = _errors;
+ _errors = temp;
+ //priorLPC = lpcs[i];
+ watchCount = 2;
+ }
+ else {
+ if(--watchCount == 0)
+ break;
+ }
+ }
+ _lowOrderBits = lowOrderBits;
+ _precision = precision;
+ _shift = shift;
+ tempCoeffs = _quantizedCoeffs;
+ _quantizedCoeffs = coefficients;
+ tempErrors = _errors;
+ _errors = errors;
+ _samples = samples;
+ _offset = offset;
+ _frameSampleSize = unencSampleSize;
+ _start = start;
+ _increment = increment;
+ _totalBits = totalBits;
+ _lpcOrder = order;
+ return encodedSamples;
+}
+
+ /**
+ * Return the estimated size of the previous encode attempt in bits. Since
+ * returning the data from an encode is costly(due to the rice encoding and FLAC
+ * compliant bit-packing), this allows us to estimate the size first, and
+ * therefore choose another subframe type if this is larger.
+ *
+ * @return estimated size in bits of encoded subframe.
+ */
+ public int estimatedSize() {
+ return _totalBits;
+ }
+
+ /**
+ * Get the data from the last encode attempt. Data is returned in an
+ * EncodedElement, properly packed at the bit-level to be added directly to
+ * a FLAC stream.
+ *
+ * @return EncodedElement containing encoded subframe
+ */
+ public EncodedElement getData() {
+ EncodedElement result = new EncodedElement(_totalBits/8+1,_offset);
+ //result.clear((int)_totalBits+1, _offset);
+ writeLPC(_samples, _lastCount, _start,
+ _increment, result, _frameSampleSize, _lowOrderBits,
+ _precision, _shift, _quantizedCoeffs, _errors, _lpcOrder,rice);
+ int totalBits = result.getTotalBits();
+ this.lastEncodedSize = (int)totalBits;
+
+ if(DEBUG_LEV > 0) {
+ System.err.println("lastencodedSize set: "+this.lastEncodedSize);
+ System.err.println("Subframe_LPC::getData(...): End");
+ }
+ return result;
+ }
+
+ public int encodeSamples(int[] samples, int count, int start, int skip,
+ EncodedElement dataEle, int offset, int unencSampleSize ) {
+ encodeSamples(samples, count, start, skip, offset, unencSampleSize);
+ EncodedElement result = getData();
+ int totalBits = result.getTotalBits();
+ dataEle.data = result.data;
+ dataEle.usableBits = result.usableBits;
+ dataEle.offset = result.offset;
+ dataEle.previous = result.previous;
+ dataEle.next = result.next;
+ this.lastEncodedSize = (int)totalBits;
+
+ return count;
+ }
+
+ private static void writeHeadersAndData(EncodedElement dataEle, int order,
+ int[] coeff, int precision, int shift, int[] samples,
+ int sampleSize, int start, int skip) {
+ //write headers
+ int encodedType = 1<<5 | (order-1);
+ dataEle.addInt(0, 1);
+ dataEle.addInt(encodedType, 6);
+ dataEle.addInt(0, 1);
+ if(order > 0) {
+ dataEle.packInt(samples, sampleSize, start, skip, order);
+ }
+ dataEle.addInt(precision-1, 4);
+ dataEle.addInt(shift, 5);
+ //System.err.println("shift:order:type::"+shift+":"+order+":"+encodedType);
+ for(int i = 1; i <= order; i++) {
+ int val = (int)-coeff[i];
+ dataEle.addInt(val, precision);
+ }
+ }
+
+ /**
+ * Quantize coefficients to integer values of the given precision, and
+ * calculate the shift needed.
+ * @param coefficients values to quantize. These values will not be changed.
+ * @param dest destination for quantized values.
+ * @param order number of values to quantize. First value skipped, coefficients
+ * array must be at least order+1 in length.
+ * @param precision number of signed bits to use for coefficients(must be in range 2-15, inclusive).
+ * @return
+ */
+ private static int quantizeCoefficients(double[] coefficients, int[] dest,
+ int order, int precision) {
+ assert(precision >= 2 && precision <= 15);
+ assert(coefficients.length >= order+1);
+ assert(dest.length >= order+1);
+ if(precision < 2 || precision > 15)
+ throw new IllegalArgumentException("Error! precision must be between 2 and 15, inclusive.");
+
+ int shiftApplied = 0;
+ int maxValAllowed = (1<<(precision-2))-1;//minus an extra bit for sign.
+ int minValAllowed = -1*maxValAllowed-1;
+ double maxVal = 0;
+ for(int i = 1; i <= order; i++) {
+ double temp = coefficients[i];
+ if(temp < 0) temp*= -1;
+ if(temp > maxVal)
+ maxVal = temp;
+ }
+ //find shift to use(by max value)
+ for(shiftApplied = 15; shiftApplied > 0; shiftApplied--) {
+ int temp = (int)(maxVal * (1<<shiftApplied));
+ if(temp <= maxValAllowed)
+ break;
+ }
+ if(maxVal > maxValAllowed) {//no shift should have been applied
+ //ensure max value is not too large, cap all necessary //
+ for(int i = 1; i <= order; i++) {
+ double temp = coefficients[i];
+ if(temp < 0)
+ temp = temp * -1;
+ if(temp > maxValAllowed) {
+ if(coefficients[i] < 0)
+ dest[i] = minValAllowed;
+ else
+ dest[i] = maxValAllowed;
+ }
+ else
+ dest[i] = (int)coefficients[i];
+ }
+ }
+ else {
+ //shift and quantize all values by found shift
+ for(int i = 1; i <= order; i++) {
+ double temp = coefficients[i]*(1<<shiftApplied);
+ temp = (temp > 0) ? temp+0.5:temp-0.5;
+ dest[i] = (int)temp;
+ }
+ }
+ return shiftApplied;
+ }
+
+ private static void writeLPC(int[] samples, int count, int start,
+ int increment, EncodedElement ele, int frameSampleSize, int riceParam,
+ int precision, int shift, int[] coeffs, int[] errors, int order,
+ RiceEncoder rice) {
+ writeHeadersAndData(ele, order, coeffs, precision, shift,
+ samples, frameSampleSize, start, increment-1);
+ int paramSize = (riceParam > 14) ? 5:4;
+ boolean fiveBitParam = (paramSize < 5) ? false:true;
+ RiceEncoder.beginResidual(fiveBitParam, (byte)0, ele);
+ rice.encodeRicePartition(errors, order,1, count-order, ele,
+ riceParam, fiveBitParam);
+ }
+
+ private static int getParam(int[] vals, int end, int start, int max) {
+ long sum = 0;
+ for(int i = start; i < end; i++) {
+ int temp = vals[i];
+ temp = (temp < 0) ? -temp:temp;
+ sum += temp;
+ }
+ float mean = (float)sum/(float)(end-start);
+ double temp = LOGE_2*(mean);
+ if(temp < 1)
+ temp = 0;
+ else
+ temp = Math.ceil(Math.log(temp)/LOGE_2);
+ int param = (int)temp;
+ param++;
+ if(param < 0) {
+ param = 1;
+ System.err.println("Subframe_LPC::param negative?");
+ }
+ else if(param > max)
+ param = max;
+ return param;
+ }
+
+ private static int partialEncodeLPC(int[] samples, int count, int start,
+ int increment, LPC lpc, Subframe_LPC lpcSubframe,
+ int frameSampleSize) {
+ //System.err.println("encodeLPC begin");
+ int order = lpc.order;
+ //double error = (lpc.rawError < 0) ? -lpc.rawError:lpc.rawError;
+ double tempLowOrderBits = 0;
+
+ //following commented out because the getParam() method appears to be
+ //more accurate for high-order lpc's, causing the search to end sooner
+ //and resulting in smaller files. win-win. On second thought, that can't
+ //be why it's quicker. The profile is showing *more* invocatiosn of this
+ //function rather than fewer(by 3000!), which means it's something else
+ //that's causing it to be quicker....strange.
+ /*double deviation = Math.sqrt((int)error/count);
+ double tempBits = LOGE_2*deviation/SQRT_2;
+ tempLowOrderBits = (Math.ceil(Math.log(tempBits)/LOGE_2));
+ if(java.lang.Double.isNaN(tempLowOrderBits)) {
+ System.err.println("tempLowOrderBits is NaN");
+ if(Double.isNaN(deviation))
+ System.err.println("deviation is NaN");
+ System.err.println("Error: "+(int)error/count);
+ System.exit(0);
+ }
+ if(tempLowOrderBits < 1)
+ tempLowOrderBits = 1;
+ else if (tempLowOrderBits > frameSampleSize) {
+ tempLowOrderBits = frameSampleSize;
+ }*/
+ int precision = 15;
+ //calculate total estimated size of frame
+ int headerSize = order*frameSampleSize+precision*order+9+8;
+ int[] coeffs = lpcSubframe._quantizedCoeffs;
+ int shift = quantizeCoefficients(lpc.rawCoefficients, coeffs, order, precision);
+ //for(int i = 0; i <= order; i++)
+ // System.err.println("coef i:val :: "+i+":"+coeffs[i]);
+ //use integer coefficients to predict samples
+ //compare prediction to original, storing error.
+
+ /** We save ~7% by accessing local vars instead of array in next loop */
+ int coeff1 = coeffs[1];
+ int coeff2 = coeffs[2];
+ int coeff3 = coeffs[3];
+ int coeff4 = coeffs[4];
+ int coeff5 = coeffs[5];
+ int coeff6 = coeffs[6];
+ int coeff7 = coeffs[7];
+ int coeff8 = coeffs[8];
+ int coeff9 = coeffs[9];
+ int coeff10 = coeffs[10];
+ int coeff11 = coeffs[11];
+ int coeff12 = coeffs[12];
+
+ int baseIndex = start;
+ int targetSampleBase = start+order*increment-increment;
+ int tempOrder = order;
+ for(int i = order; i < count; i++) {
+ long temp = 0;
+ targetSampleBase += increment;
+ int sampleIndex = baseIndex;
+ baseIndex += increment;
+ if(order > 12) {
+ switch(order) {
+ case 32: temp -= (long)coeffs[32]*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 31: temp -= (long)coeffs[31]*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 30: temp -= (long)coeffs[30]*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 29: temp -= (long)coeffs[29]*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 28: temp -= (long)coeffs[28]*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 27: temp -= (long)coeffs[27]*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 26: temp -= (long)coeffs[26]*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 25: temp -= (long)coeffs[25]*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 24: temp -= (long)coeffs[24]*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 23: temp -= (long)coeffs[23]*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 22: temp -= (long)coeffs[22]*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 21: temp -= (long)coeffs[21]*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 20: temp -= (long)coeffs[20]*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 19: temp -= (long)coeffs[19]*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 18: temp -= (long)coeffs[18]*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 17: temp -= (long)coeffs[17]*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 16: temp -= (long)coeffs[16]*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 15: temp -= (long)coeffs[15]*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 14: temp -= (long)coeffs[14]*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 13: temp -= (long)coeffs[13]*samples[sampleIndex];
+ sampleIndex+=increment;
+ }
+ tempOrder = 12;
+ }
+ switch(tempOrder) {
+ case 12: temp -= (long)coeff12*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 11: temp -= (long)coeff11*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 10: temp -= (long)coeff10*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 9: temp -= (long)coeff9*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 8: temp -= (long)coeff8*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 7: temp -= (long)coeff7*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 6: temp -= (long)coeff6*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 5: temp -= (long)coeff5*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 4: temp -= (long)coeff4*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 3: temp -= (long)coeff3*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 2: temp -= (long)coeff2*samples[sampleIndex];
+ sampleIndex+=increment;
+ case 1: temp -= (long)coeff1*samples[sampleIndex];
+ sampleIndex+=increment;break;
+ default:
+ }
+ temp = temp >> shift;//with precision fixed at 15, this should always
+ //shift back down to int territory for bitsize <=24
+ lpcSubframe._errors[i] = samples[targetSampleBase]-(int)temp;
+ }
+ tempLowOrderBits = getParam(lpcSubframe._errors, count, order,frameSampleSize);
+ int riceSize = RiceEncoder.calculateEncodeSize(lpcSubframe._errors,
+ order, 1, count-order, (int)tempLowOrderBits);
+ int totalSize = headerSize + riceSize;
+
+ lpcSubframe._precision = precision;
+ lpcSubframe._lowOrderBits = (int)tempLowOrderBits;
+ lpcSubframe._shift = shift;
+ lpcSubframe._totalBits = totalSize;
+
+ return totalSize;
+ }
+
+}
diff --git a/src/javaFlacEncoder/Subframe_Verbatim.java b/src/javaFlacEncoder/Subframe_Verbatim.java
new file mode 100644
index 0000000..05573c4
--- /dev/null
+++ b/src/javaFlacEncoder/Subframe_Verbatim.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+
+/**
+ * Implements the Subframe abstract class, providing encoding support for the
+ * FLAC Verbatim Subframe.
+ *
+ * @author Preston Lacey
+ */
+public class Subframe_Verbatim extends Subframe {
+ /** For debugging: Higher value equals more output, typically by increments
+ * of 10 */
+ public static int DEBUG_LEV = 0;
+ /** Subframe type supported by this implementation. */
+ public static final EncodingConfiguration.SubframeType type =
+ EncodingConfiguration.SubframeType.VERBATIM;
+
+ //int sampleSize = 0;
+
+ /**
+ * Constructor. Sets StreamConfiguration to use. If the StreamConfiguration
+ * must later be changed, a new Subframe object must be created as well.
+ * @param sc StreamConfiguration to use for encoding.
+ */
+ Subframe_Verbatim(StreamConfiguration sc) {
+ super(sc);
+ //sampleSize = sc.getBitsPerSample();
+ }
+
+ /**
+ * This method is used to set the encoding configuration.
+ * @param ec encoding configuration to use.
+ * @return true if configuration was changed, false otherwise
+ */
+ @Override
+ public boolean registerConfiguration(EncodingConfiguration ec) {
+ super.registerConfiguration(ec);
+ return true;
+ }
+
+ public int estimateSize(int count, int bitsPerSample) {
+ int estimatedSize=8+count*bitsPerSample;//header size + unencoded data.
+ return estimatedSize;
+ }
+
+ public int encodeSamples(int[] samples, int count, int start, int skip,
+ EncodedElement data, int offset, int bitsPerSample ) {
+ if(DEBUG_LEV > 0) {
+ System.err.println("Subframe_Verbatim::encodeSamples(...)");
+ }
+ int encodedSamples = count;
+ int bits = bitsPerSample*count+offset+1*8;
+ int bytesNeeded = bits/8;
+ if(bits%8 != 0)
+ bytesNeeded++;
+ data.clear(bytesNeeded, offset);
+ data.addInt(0, 1);
+ data.addInt(1, 6);
+ data.addInt(0, 1);
+ data.packInt(samples, bitsPerSample, start, skip, count);
+ lastEncodedSize = bits-offset;
+
+ if(DEBUG_LEV > 0)
+ System.err.println("Subframe_Verbatim::encodeSamples(...): End");
+ if(DEBUG_LEV > 10) {
+ System.err.println("--: bitsUsed : "+bits+" : Bytes : "+bytesNeeded);
+ }
+ return encodedSamples;
+ }
+
+}
diff --git a/src/javaFlacEncoder/UTF8Modified.java b/src/javaFlacEncoder/UTF8Modified.java
new file mode 100644
index 0000000..271b93b
--- /dev/null
+++ b/src/javaFlacEncoder/UTF8Modified.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package javaFlacEncoder;
+
+/**
+ * This is a utility class that provides methods to both encode to and decode
+ * from the extended version of UTF8 used by the FLAC format. All functions
+ * should work with standard UTF8 as well, since this only extends it to handle
+ * larger input values.
+ *
+ * @author Preston Lacey
+ */
+public class UTF8Modified {
+ static final long oneByteLimit = (long)Math.pow(2, 7);
+ static final long twoByteLimit = (long)Math.pow(2, 11);
+ static final long threeByteLimit = (long)Math.pow(2, 16);
+ static final long fourByteLimit = (long)Math.pow(2, 21);
+ static final long fiveByteLimit = (long)Math.pow(2, 26);
+ static final long sixByteLimit = (long)Math.pow(2, 31);
+ static final long sevenByteLimit = (long)Math.pow(2, 36);
+ static long[] limits = {
+ oneByteLimit,
+ twoByteLimit,
+ threeByteLimit,
+ fourByteLimit,
+ fiveByteLimit,
+ sixByteLimit,
+ sevenByteLimit
+ };
+
+ /** For debugging: Higher value equals more output, generally by increments
+ * of 10 */
+ public static int DEBUG_LEV = 0;
+
+ /**
+ * Constructor. This Class provides only static methods and static fields.
+ */
+ public UTF8Modified() {
+ }
+
+ /**
+ * Decode an extended UTF8(as used in FLAC), to a long value.
+ * @param input extended UTF8 encoded value.
+ * @return value represented by the UTF8 input.
+ */
+ public static long decodeFromExtendedUTF8(byte[] input) {
+ int leadOnes = 0;
+ int leadMask = 128;
+ int work = input[0];
+ while((work & leadMask) > 0) {
+ leadOnes++;
+ work = work << 1;
+ }
+ int valMask = 255 >>> (leadOnes+1);
+ long val = input[0] & valMask;
+ for(int i = 1; i < leadOnes; i++) {
+ int midMask = 0x3F;
+ val = val << 6;
+ val = (input[i] & midMask) | val;
+ }
+ return val;
+ }
+
+ /**
+ * Convert a value to an extended UTF8 format(as used in FLAC).
+ * @param value value to convert to extended UTF8(value must be positive
+ * and 36 bits or less in size)
+ * @return extended UTF8 encoded value(array size is equal to the number of
+ * usable bytes)
+ */
+ public static byte[] convertToExtendedUTF8(long value) {
+ //calculate bytes needed
+ int bytesNeeded = 1;
+ for(int i = 0; i < 7; i++) {
+ if(value >= limits[i] ) {
+ bytesNeeded++;
+ }
+ }
+ //create space
+ byte [] result = new byte[bytesNeeded];
+ int byteIndex = 0;
+ int inputIndex = 0;
+ int bytesLeft = bytesNeeded;
+ while(bytesLeft > 1) {
+ int midByteMarker = 0x80;//10 in leftmost bits
+ int midByteMask = 0x3F;//00111111
+ int val = ((int)(value >>> inputIndex) & midByteMask) | midByteMarker;
+ result[byteIndex++] = (byte)val;
+ inputIndex += 6;
+ bytesLeft--;
+ }
+ int onesNeeded = inputIndex/6;
+ if(onesNeeded > 0)
+ onesNeeded++;
+ int startMask = 255 >>> (onesNeeded + 1);
+ int ones = 255 << (8-onesNeeded);
+ int val = ((int)(value >>> inputIndex) & startMask) | ones;
+ result[byteIndex++] = (byte)val;
+
+ byte[] finalResult = new byte[bytesNeeded];
+ for(int i = 0; i < bytesNeeded; i++) {
+ int sourceIndex = bytesNeeded-1-i;
+ int destIndex = i;
+ finalResult[destIndex] = result[sourceIndex];
+ }
+ if(DEBUG_LEV > 10) {
+ System.err.print("input:result_length:result :: " +value+":"+finalResult.length+"::");
+ for(int i = 0; i < finalResult.length; i++)
+ System.err.print(Integer.toHexString(finalResult[i])+":");
+ System.err.println();
+ }
+ return finalResult;
+ }
+}
diff --git a/src/javaFlacEncoder/package.html b/src/javaFlacEncoder/package.html
new file mode 100644
index 0000000..adae558
--- /dev/null
+++ b/src/javaFlacEncoder/package.html
@@ -0,0 +1,41 @@
+<body>
+This package defines a FLAC encoder with a simple interface for enabling FLAC
+encoding support in an application. This class is appropriate for use in the
+case where you have raw pcm audio samples that you wish to encode. Currently,
+fixed-blocksize only is implemented, with the "Maximum Block Size" set in the
+StreamConfiguration object used as the actual block size.
+<br><br>
+To use this library, there are several options:<br>
+ <p>
+ If you're simply needing to convert a file(as opposed to a stream), you may
+ want to use the FLAC_FileEncoder class.<br>
+ </p>
+ <p>
+ For applications using the javax.sound API, this library includes basic support.<br>
+ FLACFileWriter provides the implementation of
+ javax.sound.sampled.spi.AudioFileWriter. After installing a release jar in the
+ appropriate location, the FLAC encoder should be available for use by any application
+ which makes use of the sound api for transcoding purposes. Use this if you need basic
+ encoding support(with default configuration), and have a
+ javax.sound.sampled.AudioInputStream as your source.</p>
+<p>
+ The remaining example is for those who need more control over the encoding
+ process, including supplying a non-default EncodingConfiguration object, or
+ for whom the prior methods are not otherwise suitable. For direct, low-level
+ access, an application should primarily use the classes FLACEncoder,
+ EncodingConfiguration, StreamConfiguration, and FLACOutputStream.
+<br></p>
+An encoding process is simple, and should follow these steps:<br>
+<BLOCKQUOTE>
+1) Set StreamConfiguration to appropriate values. After a stream is opened,
+this must not be altered until the stream is closed.<br>
+2) Set FLACOutputStream, object to write results to.<br>
+3) Open FLAC Stream<br>
+4) Set EncodingConfiguration(if defaults are insufficient).<br>
+5) Add samples to encoder<br>
+6) Encode Samples<br>
+7) Close stream<br>
+(note: steps 4,5, and 6 may be done repeatedly, in any order. However, see
+related method documentation for info on concurrent use)
+</BLOCKQUOTE><br><br>
+</body>