diff options
author | Alexey 'Cluster' Avdyukhin <clusterrr@clusterrr.com> | 2014-07-21 00:03:58 +0400 |
---|---|---|
committer | Alexey 'Cluster' Avdyukhin <clusterrr@clusterrr.com> | 2014-07-21 00:03:58 +0400 |
commit | 32f8528fa1e49322980202ffac8e43fcc51476da (patch) | |
tree | 7716e69ae4568495676f0c105cb5891dff3f719c |
First commit
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 Binary files differnew file mode 100644 index 0000000..a18cbb4 --- /dev/null +++ b/ic_launcher-web.png 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 Binary files differnew file mode 100644 index 0000000..288b665 --- /dev/null +++ b/res/drawable-hdpi/ic_launcher.png diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..6ae570b --- /dev/null +++ b/res/drawable-mdpi/ic_launcher.png diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..d4fb7cd --- /dev/null +++ b/res/drawable-xhdpi/ic_launcher.png diff --git a/res/drawable-xxhdpi/ic_launcher.png b/res/drawable-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 0000000..85a6081 --- /dev/null +++ b/res/drawable-xxhdpi/ic_launcher.png 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> |