drop library

This commit is contained in:
Alexey Kuznetsov 2017-02-24 18:10:14 +03:00
commit 3234a8e01f
44 changed files with 0 additions and 3610 deletions

View file

@ -1 +0,0 @@
/build

View file

@ -1,130 +0,0 @@
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
}
}
repositories {
jcenter()
mavenLocal()
}
apply plugin: 'com.android.library'
apply plugin: 'maven'
apply plugin: 'signing'
apply plugin: 'com.github.dcendents.android-maven' // 'gradle install' task
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
minSdkVersion 9
targetSdkVersion 25
versionCode 2
versionName "0.0.2"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
task javadoc(type: Javadoc) {
source = android.sourceSets.main.java.srcDirs
classpath += project.files(project.android.getBootClasspath().join(File.pathSeparator))
android.libraryVariants.all { variant ->
classpath += files(variant.javaCompile.classpath.files)
}
}
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
task sourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.srcDirs
}
artifacts {
archives javadocJar, sourcesJar
}
signing {
sign configurations.archives
}
group = "com.github.axet"
archivesBaseName = "android-audio-library"
version = android.defaultConfig.versionName
uploadArchives {
repositories {
mavenDeployer {
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
authentication(userName: ossrhUsername, password: ossrhPassword)
}
snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
authentication(userName: ossrhUsername, password: ossrhPassword)
}
pom.project {
name 'Android Library'
packaging 'jar'
// optionally artifactId can be defined here
description 'Android Simple Widgets and Support classes.'
url 'https://gitlab.com/axet/android-audio-library'
scm {
connection 'scm:git:https://gitlab.com/axet/android-library'
developerConnection 'scm:git:https://gitlab.com/axet/android-library'
url 'https://gitlab.com/axet/android-audio-library'
}
licenses {
license {
name 'GNU LESSER GENERAL PUBLIC LICENSE 3.0'
url 'http://www.gnu.org/copyleft/lesser.html'
}
}
developers {
developer {
id 'axet'
name 'Alexey Kuznetsov'
email 'axet@me.com'
}
}
}
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:design:25.2.0'
compile 'com.android.support:appcompat-v7:25.2.0'
compile 'com.google.android.gms:play-services-appindexing:9.8.0'
compile 'org.apache.commons:commons-math3:3.6.1'
compile 'com.github.axet:android-library:1.9.9' //compile project(':android-library')
compile 'com.github.axet:jebml:0.0.2' // compile project(':jebml')
compile 'com.github.axet:vorbis:1.0.0' // compile project(':vorbis')
testCompile 'junit:junit:4.12'
}

View file

@ -1,17 +0,0 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/axet/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# 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 *;
#}

View file

@ -1,26 +0,0 @@
package com.github.axet.audiolibrary;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumentation test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.github.axet.audiolibrary.test", appContext.getPackageName());
}
}

View file

@ -1,9 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.github.axet.audiolibrary">
<application android:allowBackup="true" android:label="@string/app_name"
android:supportsRtl="true">
</application>
</manifest>

View file

@ -1,113 +0,0 @@
package com.github.axet.audiolibrary.animations;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.Handler;
import android.view.View;
import android.view.animation.Transformation;
import android.widget.ListView;
import com.github.axet.androidlibrary.animations.MarginAnimation;
import com.github.axet.audiolibrary.R;
public class RecordingAnimation extends MarginAnimation {
ListView list;
View convertView;
boolean partial;
Handler handler;
// if we have two concurrent animations on the same listview
// the only one 'expand' should have control of showChild function.
static RecordingAnimation atomicExpander;
public static void apply(final ListView list, final View v, final boolean expand, boolean animate) {
apply(new LateCreator() {
@Override
public MarginAnimation create() {
RecordingAnimation a = new RecordingAnimation(list, v, expand);
if (expand)
atomicExpander = a;
return a;
}
}, v, expand, animate);
}
public RecordingAnimation(ListView list, View v, boolean expand) {
super(v.findViewById(R.id.recording_player), expand);
handler = new Handler();
this.convertView = v;
this.list = list;
}
@Override
public void init() {
super.init();
{
final int paddedTop = list.getListPaddingTop();
final int paddedBottom = list.getHeight() - list.getListPaddingTop() - list.getListPaddingBottom();
partial = false;
partial |= convertView.getTop() < paddedTop;
partial |= convertView.getBottom() > paddedBottom;
}
}
@Override
public void calc(final float i, Transformation t) {
super.calc(i, t);
float ii = expand ? i : 1 - i;
// ViewGroup will crash on null pointer without this post pone.
// seems like some views are removed by RecyvingView when they
// gone off screen.
if (Build.VERSION.SDK_INT >= 19) {
if (!expand && atomicExpander != null && !atomicExpander.hasEnded()) {
// do not showChild;
} else {
handler.post(new Runnable() {
@Override
public void run() {
showChild(i);
}
});
}
}
}
@TargetApi(19)
void showChild(float i) {
final int paddedTop = list.getListPaddingTop();
final int paddedBottom = list.getHeight() - list.getListPaddingTop() - list.getListPaddingBottom();
if (convertView.getTop() < paddedTop) {
int off = convertView.getTop() - paddedTop;
if (partial)
off = (int) (off * i);
list.scrollListBy(off);
}
if (convertView.getBottom() > paddedBottom) {
int off = convertView.getBottom() - paddedBottom;
if (partial)
off = (int) (off * i);
list.scrollListBy(off);
}
}
@Override
public void restore() {
super.restore();
}
@Override
public void end() {
super.end();
}
}

View file

@ -1,82 +0,0 @@
package com.github.axet.audiolibrary.app;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.media.AudioFormat;
import android.preference.PreferenceManager;
import com.github.axet.androidlibrary.app.MainLibrary;
import com.github.axet.audiolibrary.R;
public class MainApplication extends Application {
public static final String PREFERENCE_STORAGE = "storage_path";
public static final String PREFERENCE_RATE = "sample_rate";
public static final String PREFERENCE_CALL = "call";
public static final String PREFERENCE_SILENT = "silence";
public static final String PREFERENCE_ENCODING = "encoding";
public static final String PREFERENCE_LAST = "last_recording";
public static final String PREFERENCE_THEME = "theme";
public static final String PREFERENCE_CHANNELS = "channels";
@Override
public void onCreate() {
super.onCreate();
Context context = this;
context.setTheme(getUserTheme());
}
public int getUserTheme() {
return getTheme(this, R.style.AppThemeLight, R.style.AppThemeDark);
}
public static int getTheme(Context context, int light, int dark) {
final SharedPreferences shared = PreferenceManager.getDefaultSharedPreferences(context);
String theme = shared.getString(PREFERENCE_THEME, "");
if (theme.equals("Theme_Dark")) {
return dark;
} else {
return light;
}
}
public String formatFree(long free, long left) {
String str = "";
long diff = left;
int diffSeconds = (int) (diff / 1000 % 60);
int diffMinutes = (int) (diff / (60 * 1000) % 60);
int diffHours = (int) (diff / (60 * 60 * 1000) % 24);
int diffDays = (int) (diff / (24 * 60 * 60 * 1000));
if (diffDays > 0) {
str = getResources().getQuantityString(R.plurals.days, diffDays, diffDays);
} else if (diffHours > 0) {
str = getResources().getQuantityString(R.plurals.hours, diffHours, diffHours);
} else if (diffMinutes > 0) {
str = getResources().getQuantityString(R.plurals.minutes, diffMinutes, diffMinutes);
} else if (diffSeconds > 0) {
str = getResources().getQuantityString(R.plurals.seconds, diffSeconds, diffSeconds);
}
return getString(R.string.title_header, MainLibrary.formatSize(this, free), str);
}
public static int getChannels(Context context) {
final SharedPreferences shared = PreferenceManager.getDefaultSharedPreferences(context);
int i = Integer.parseInt(shared.getString(MainApplication.PREFERENCE_CHANNELS, "1"));
return i;
}
public static int getMode(Context context) {
switch (getChannels(context)) {
case 1:
return AudioFormat.CHANNEL_IN_MONO;
case 2:
return AudioFormat.CHANNEL_IN_STEREO;
default:
throw new RuntimeException("unknown mode");
}
}
}

View file

@ -1,145 +0,0 @@
package com.github.axet.audiolibrary.app;
import android.media.AudioFormat;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
public class RawSamples {
File in;
InputStream is;
byte[] readBuffer;
OutputStream os;
public RawSamples(File in) {
this.in = in;
}
// open for writing with specified offset to truncate file
public void open(long writeOffset) {
trunk(writeOffset);
try {
os = new BufferedOutputStream(new FileOutputStream(in, true));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// open for reading
//
// bufReadSize - samples count
public void open(int bufReadSize) {
try {
readBuffer = new byte[(int) getBufferLen(bufReadSize)];
is = new FileInputStream(in);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// open for read with initial offset and buffer read size
//
// offset - samples offset
// bufReadSize - samples size
public void open(long offset, int bufReadSize) {
try {
readBuffer = new byte[(int) getBufferLen(bufReadSize)];
is = new FileInputStream(in);
is.skip(offset * (Sound.AUDIO_FORMAT == AudioFormat.ENCODING_PCM_16BIT ? 2 : 1));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public int read(short[] buf) {
try {
int len = is.read(readBuffer);
if (len <= 0)
return 0;
ByteBuffer.wrap(readBuffer, 0, len).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get(buf, 0, (int) getSamples(len));
return (int) getSamples(len);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void write(short val) {
try {
ByteBuffer bb = ByteBuffer.allocate(Short.SIZE / Byte.SIZE);
bb.order(ByteOrder.BIG_ENDIAN);
bb.putShort(val);
os.write(bb.array(), 0, bb.limit());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void write(short[] buf, int len) {
for (int i = 0; i < len; i++) {
write(buf[i]);
}
}
public long getSamples() {
return getSamples(in.length());
}
public static long getSamples(long len) {
return len / (Sound.AUDIO_FORMAT == AudioFormat.ENCODING_PCM_16BIT ? 2 : 1);
}
public static long getBufferLen(long samples) {
return samples * (Sound.AUDIO_FORMAT == AudioFormat.ENCODING_PCM_16BIT ? 2 : 1);
}
public void trunk(long pos) {
try {
FileChannel outChan = new FileOutputStream(in, true).getChannel();
outChan.truncate(getBufferLen(pos));
outChan.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static double getAmplitude(short[] buffer, int offset, int len) {
double sum = 0;
for (int i = offset; i < offset + len; i++) {
sum += buffer[i] * buffer[i];
}
return Math.sqrt(sum / len);
}
public static double getDB(short[] buffer, int offset, int len) {
return getDB(getAmplitude(buffer, offset, len));
}
public static double getDB(double amplitude) {
// https://en.wikipedia.org/wiki/Sound_pressure
return 20.0 * Math.log10(amplitude / 0x7FFF);
}
public void close() {
try {
if (is != null)
is.close();
is = null;
if (os != null)
os.close();
os = null;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -1,451 +0,0 @@
package com.github.axet.audiolibrary.app;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Handler;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.PopupMenu;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import com.github.axet.androidlibrary.animations.RemoveItemAnimation;
import com.github.axet.androidlibrary.app.MainLibrary;
import com.github.axet.androidlibrary.widgets.OpenFileDialog;
import com.github.axet.androidlibrary.widgets.PopupShareActionProvider;
import com.github.axet.audiolibrary.R;
import com.github.axet.audiolibrary.animations.RecordingAnimation;
import com.github.axet.audiolibrary.encoders.Factory;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class Recordings extends ArrayAdapter<File> implements AbsListView.OnScrollListener {
public static String TAG = Recordings.class.getSimpleName();
static final int TYPE_COLLAPSED = 0;
static final int TYPE_EXPANDED = 1;
static final int TYPE_DELETED = 2;
public static class SortFiles implements Comparator<File> {
@Override
public int compare(File file, File file2) {
if (file.isDirectory() && file2.isFile())
return -1;
else if (file.isFile() && file2.isDirectory())
return 1;
else
return file.getPath().compareTo(file2.getPath());
}
}
Handler handler;
Storage storage;
MediaPlayer player;
Runnable updatePlayer;
int selected = -1;
ListView list;
PopupShareActionProvider shareProvider;
int scrollState;
Map<File, Integer> durations = new TreeMap<>();
public Recordings(Context context, ListView list) {
super(context, 0);
this.list = list;
this.handler = new Handler();
this.storage = new Storage(context);
this.list.setOnScrollListener(this);
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
this.scrollState = scrollState;
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
public void scan(File dir) {
setNotifyOnChange(false);
clear();
durations.clear();
List<File> ff = storage.scan(dir);
for (File f : ff) {
if (f.isFile()) {
MediaPlayer mp = null;
try {
mp = MediaPlayer.create(getContext(), Uri.fromFile(f));
} catch (IllegalStateException e) {
Log.d(TAG, f.toString(), e);
}
if (mp != null) {
int d = mp.getDuration();
mp.release();
durations.put(f, d);
add(f);
} else {
Log.e(TAG, f.toString());
}
}
}
sort();
notifyDataSetChanged();
}
public void sort() {
sort(new SortFiles());
}
public void close() {
if (player != null) {
player.release();
player = null;
}
if (updatePlayer != null) {
handler.removeCallbacks(updatePlayer);
updatePlayer = null;
}
}
public void load() {
scan(storage.getStoragePath());
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(getContext());
if (convertView == null) {
convertView = inflater.inflate(R.layout.recording, parent, false);
convertView.setTag(-1);
}
final View view = convertView;
final View base = convertView.findViewById(R.id.recording_base);
if ((int) convertView.getTag() == TYPE_DELETED) {
RemoveItemAnimation.restore(base);
convertView.setTag(-1);
}
final File f = getItem(position);
TextView title = (TextView) convertView.findViewById(R.id.recording_title);
title.setText(f.getName());
SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
TextView time = (TextView) convertView.findViewById(R.id.recording_time);
time.setText(s.format(new Date(f.lastModified())));
TextView dur = (TextView) convertView.findViewById(R.id.recording_duration);
dur.setText(MainLibrary.formatDuration(getContext(), durations.get(f)));
TextView size = (TextView) convertView.findViewById(R.id.recording_size);
size.setText(MainLibrary.formatSize(getContext(), f.length()));
final View playerBase = convertView.findViewById(R.id.recording_player);
playerBase.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
final Runnable delete = new Runnable() {
@Override
public void run() {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle(R.string.delete_recording);
builder.setMessage("...\\" + f.getName() + "\n\n" + getContext().getString(R.string.are_you_sure));
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
playerStop();
dialog.cancel();
RemoveItemAnimation.apply(list, base, new Runnable() {
@Override
public void run() {
f.delete();
view.setTag(TYPE_DELETED);
select(-1);
load();
}
});
}
});
builder.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
builder.show();
}
};
final Runnable rename = new Runnable() {
@Override
public void run() {
final OpenFileDialog.EditTextDialog e = new OpenFileDialog.EditTextDialog(getContext());
e.setTitle(getContext().getString(R.string.rename_recording));
e.setText(Storage.getNameNoExt(f));
e.setPositiveButton(new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String ext = Storage.getExt(f);
String s = String.format("%s.%s", e.getText(), ext);
File ff = new File(f.getParent(), s);
f.renameTo(ff);
load();
}
});
e.show();
}
};
if (selected == position) {
RecordingAnimation.apply(list, convertView, true, scrollState == SCROLL_STATE_IDLE && (int) convertView.getTag() == TYPE_COLLAPSED);
convertView.setTag(TYPE_EXPANDED);
updatePlayerText(convertView, f);
final View play = convertView.findViewById(R.id.recording_player_play);
play.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (player == null) {
playerPlay(playerBase, f);
} else if (player.isPlaying()) {
playerPause(playerBase, f);
} else {
playerPlay(playerBase, f);
}
}
});
final View edit = convertView.findViewById(R.id.recording_player_edit);
edit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
rename.run();
}
});
final View share = convertView.findViewById(R.id.recording_player_share);
share.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
shareProvider = new PopupShareActionProvider(getContext(), share);
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setType(Factory.MP4A);
emailIntent.putExtra(Intent.EXTRA_EMAIL, "");
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f));
emailIntent.putExtra(Intent.EXTRA_SUBJECT, f.getName());
emailIntent.putExtra(Intent.EXTRA_TEXT, getContext().getString(R.string.shared_via, getContext().getString(R.string.app_name)));
shareProvider.setShareIntent(emailIntent);
shareProvider.show();
}
});
View trash = convertView.findViewById(R.id.recording_player_trash);
trash.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
delete.run();
}
});
convertView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
select(-1);
}
});
} else {
RecordingAnimation.apply(list, convertView, false, scrollState == SCROLL_STATE_IDLE && (int) convertView.getTag() == TYPE_EXPANDED);
convertView.setTag(TYPE_COLLAPSED);
convertView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
select(position);
}
});
}
convertView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
PopupMenu popup = new PopupMenu(getContext(), v);
MenuInflater inflater = popup.getMenuInflater();
inflater.inflate(R.menu.menu_context, popup.getMenu());
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
if (item.getItemId() == R.id.action_delete) {
delete.run();
return true;
}
if (item.getItemId() == R.id.action_rename) {
rename.run();
return true;
}
return false;
}
});
popup.show();
return true;
}
});
return convertView;
}
void playerPlay(View v, File f) {
if (player == null)
player = MediaPlayer.create(getContext(), Uri.fromFile(f));
if (player == null) {
Toast.makeText(getContext(), R.string.file_not_found, Toast.LENGTH_SHORT).show();
return;
}
player.start();
updatePlayerRun(v, f);
}
void playerPause(View v, File f) {
if (player != null) {
player.pause();
}
if (updatePlayer != null) {
handler.removeCallbacks(updatePlayer);
updatePlayer = null;
}
updatePlayerText(v, f);
}
void playerStop() {
if (updatePlayer != null) {
handler.removeCallbacks(updatePlayer);
updatePlayer = null;
}
if (player != null) {
player.stop();
player.release();
player = null;
}
}
void updatePlayerRun(final View v, final File f) {
boolean playing = updatePlayerText(v, f);
if (updatePlayer != null) {
handler.removeCallbacks(updatePlayer);
updatePlayer = null;
}
if (!playing) {
playerStop(); // clear player instance
updatePlayerText(v, f); // update length
return;
}
updatePlayer = new Runnable() {
@Override
public void run() {
updatePlayerRun(v, f);
}
};
handler.postDelayed(updatePlayer, 200);
}
boolean updatePlayerText(final View v, final File f) {
ImageView i = (ImageView) v.findViewById(R.id.recording_player_play);
final boolean playing = player != null && player.isPlaying();
i.setImageResource(playing ? R.drawable.ic_pause_black_24dp : R.drawable.ic_play_arrow_black_24dp);
TextView start = (TextView) v.findViewById(R.id.recording_player_start);
SeekBar bar = (SeekBar) v.findViewById(R.id.recording_player_seek);
TextView end = (TextView) v.findViewById(R.id.recording_player_end);
int c = 0;
int d = durations.get(f);
if (player != null) {
c = player.getCurrentPosition();
d = player.getDuration();
}
bar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (!fromUser)
return;
if (player == null)
playerPlay(v, f);
if (player != null) {
player.seekTo(progress);
if (!player.isPlaying())
playerPlay(v, f);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
start.setText(MainLibrary.formatDuration(getContext(), c));
bar.setMax(d);
bar.setKeyProgressIncrement(1);
bar.setProgress(c);
end.setText("-" + MainLibrary.formatDuration(getContext(), d - c));
return playing;
}
public void select(int pos) {
selected = pos;
notifyDataSetChanged();
playerStop();
}
public int getSelected() {
return selected;
}
}

View file

@ -1,61 +0,0 @@
package com.github.axet.audiolibrary.app;
import android.content.Context;
import android.content.SharedPreferences;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.preference.PreferenceManager;
public class Sound extends com.github.axet.androidlibrary.sound.Sound {
public static int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
// quite root gives me 20db
public static int NOISE_DB = 20;
// max 90 dB detection for android mic
public static int MAXIMUM_DB = 90;
public Sound(Context context) {
super(context);
}
public void silent() {
SharedPreferences shared = PreferenceManager.getDefaultSharedPreferences(context);
if (shared.getBoolean(MainApplication.PREFERENCE_SILENT, false)) {
super.silent();
}
}
public void unsilent() {
SharedPreferences shared = PreferenceManager.getDefaultSharedPreferences(context);
if (shared.getBoolean(MainApplication.PREFERENCE_SILENT, false)) {
super.unsilent();
}
}
public AudioTrack generateTrack(int sampleRate, short[] buf, int len) {
int end = len;
int c = 0;
switch (MainApplication.getChannels(context)) {
case 1:
c = AudioFormat.CHANNEL_OUT_MONO;
break;
case 2:
c = AudioFormat.CHANNEL_OUT_STEREO;
break;
default:
throw new RuntimeException("unknown mode");
}
// old phones bug.
// http://stackoverflow.com/questions/27602492
//
// with MODE_STATIC setNotificationMarkerPosition not called
AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, c, AUDIO_FORMAT, len * (Short.SIZE / 8), AudioTrack.MODE_STREAM);
track.write(buf, 0, len);
if (track.setNotificationMarkerPosition(end) != AudioTrack.SUCCESS)
throw new RuntimeException("unable to set marker");
return track;
}
}

View file

@ -1,283 +0,0 @@
package com.github.axet.audiolibrary.app;
import android.Manifest;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.StatFs;
import android.preference.PreferenceManager;
import android.support.v4.content.ContextCompat;
import com.github.axet.audiolibrary.encoders.Factory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class Storage {
public static final String TMP_REC = "recorind.data";
public static final String RECORDINGS = "recordings";
protected Context context;
public Storage(Context context) {
this.context = context;
}
public static final String[] PERMISSIONS = new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE};
public boolean permitted(String[] ss) {
if (Build.VERSION.SDK_INT < 11)
return true;
for (String s : ss) {
if (ContextCompat.checkSelfPermission(context, s) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
public File getLocalStorage() {
return new File(context.getApplicationInfo().dataDir, RECORDINGS);
}
public boolean isLocalStorageEmpty() {
return getLocalStorage().listFiles().length == 0;
}
public boolean isExternalStoragePermitted() {
return permitted(PERMISSIONS);
}
public boolean recordingPending() {
return getTempRecording().exists();
}
public File getStoragePath() {
SharedPreferences shared = PreferenceManager.getDefaultSharedPreferences(context);
String path = shared.getString(MainApplication.PREFERENCE_STORAGE, "");
if (permitted(PERMISSIONS)) {
return new File(path);
} else {
return getLocalStorage();
}
}
public void migrateLocalStorage() {
if (!permitted(PERMISSIONS)) {
return;
}
SharedPreferences shared = PreferenceManager.getDefaultSharedPreferences(context);
String path = shared.getString(MainApplication.PREFERENCE_STORAGE, "");
File l = getLocalStorage();
File t = new File(path);
t.mkdirs();
File[] ff = l.listFiles();
if (ff == null)
return;
for (File f : ff) {
File tt = getNextFile(t, f);
move(f, tt);
}
}
public File getNewFile() {
SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd HH.mm.ss");
SharedPreferences shared = PreferenceManager.getDefaultSharedPreferences(context);
String ext = shared.getString(MainApplication.PREFERENCE_ENCODING, "");
File parent = getStoragePath();
if (!parent.exists()) {
if (!parent.mkdirs())
throw new RuntimeException("Unable to create: " + parent);
}
return getNextFile(parent, s.format(new Date()), ext);
}
public static String getNameNoExt(File f) {
String fileName = f.getName();
int i = fileName.lastIndexOf('.');
if (i > 0) {
fileName = fileName.substring(0, i);
}
return fileName;
}
public static String getExt(File f) {
String fileName = f.getName();
int i = fileName.lastIndexOf('.');
if (i > 0) {
return fileName.substring(i + 1);
}
return "";
}
File getNextFile(File parent, File f) {
String fileName = f.getName();
String extension = "";
int i = fileName.lastIndexOf('.');
if (i > 0) {
extension = fileName.substring(i + 1);
fileName = fileName.substring(0, i);
}
return getNextFile(parent, fileName, extension);
}
protected File getNextFile(File parent, String name, String ext) {
String fileName;
if (ext.isEmpty())
fileName = name;
else
fileName = String.format("%s.%s", name, ext);
File file = new File(parent, fileName);
int i = 1;
while (file.exists()) {
fileName = String.format("%s (%d).%s", name, i, ext);
file = new File(parent, fileName);
i++;
}
// try {
// file.createNewFile();
// } catch (IOException e) {
// throw new RuntimeException("Unable to create: " + file, e);
// }
return file;
}
public List<File> scan(File dir) {
ArrayList<File> list = new ArrayList<>();
File[] ff = dir.listFiles();
if (ff == null)
return list;
for (File f : ff) {
if (f.length() > 0) {
String[] ee = Factory.getEncodingValues(context);
String n = f.getName().toLowerCase();
for (String e : ee) {
if (n.endsWith("." + e))
list.add(f);
}
}
}
return list;
}
// get average recording miliseconds based on compression format
public long average(long free) {
final SharedPreferences shared = PreferenceManager.getDefaultSharedPreferences(context);
int rate = Integer.parseInt(shared.getString(MainApplication.PREFERENCE_RATE, ""));
String ext = shared.getString(MainApplication.PREFERENCE_ENCODING, "");
int m = MainApplication.getChannels(context);
long perSec = Factory.getEncoderRate(ext, rate) * m;
return free / perSec * 1000;
}
public long getFree(File f) {
while (!f.exists())
f = f.getParentFile();
StatFs fsi = new StatFs(f.getPath());
if (Build.VERSION.SDK_INT < 18)
return fsi.getBlockSize() * (long) fsi.getAvailableBlocks();
else
return fsi.getBlockSizeLong() * fsi.getAvailableBlocksLong();
}
public File getTempRecording() {
File internal = new File(context.getApplicationInfo().dataDir, TMP_REC);
if (internal.exists())
return internal;
// Starting in KITKAT, no permissions are required to read or write to the returned path;
// it's always accessible to the calling app.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
if (!permitted(PERMISSIONS))
return internal;
}
File c = context.getExternalCacheDir();
if (c == null) // some old phones 11 with disabled sdcard return null
return internal;
File external = new File(c, TMP_REC);
if (external.exists())
return external;
long freeI = getFree(internal);
long freeE = getFree(external);
if (freeI > freeE)
return internal;
else
return external;
}
public FileOutputStream open(File f) {
File tmp = f;
File parent = tmp.getParentFile();
if (!parent.exists() && !parent.mkdirs()) {
throw new RuntimeException("unable to create: " + parent);
}
if (!parent.isDirectory())
throw new RuntimeException("target is not a dir: " + parent);
try {
return new FileOutputStream(tmp, true);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void delete(File f) {
f.delete();
}
public void move(File f, File to) {
try {
InputStream in = new FileInputStream(f);
OutputStream out = new FileOutputStream(to);
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
f.delete();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -1,7 +0,0 @@
package com.github.axet.audiolibrary.encoders;
public interface Encoder {
void encode(short[] buf, int len);
void close();
}

View file

@ -1,13 +0,0 @@
package com.github.axet.audiolibrary.encoders;
public class EncoderInfo {
public int channels;
public int sampleRate;
public int bps;
public EncoderInfo(int channels, int sampleRate, int bps) {
this.channels = channels;
this.sampleRate = sampleRate;
this.bps = bps;
}
}

View file

@ -1,96 +0,0 @@
package com.github.axet.audiolibrary.encoders;
import android.content.Context;
import android.media.AudioFormat;
import android.os.Build;
import com.github.axet.audiolibrary.R;
import com.github.axet.audiolibrary.app.RawSamples;
import com.github.axet.audiolibrary.app.Sound;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
public class Factory {
public static String MP4 = "audio/mp4";
public static String MP4A = "audio/mp4a-latm";
public static CharSequence[] getEncodingTexts(Context context) {
String[] aa = context.getResources().getStringArray(R.array.encodings_text);
ArrayList<String> ll = new ArrayList<>(Arrays.asList(aa));
if (Build.VERSION.SDK_INT >= 18)
ll.add(".m4a");
if (Build.VERSION.SDK_INT >= 16)
ll.add(".mka");
ll.add(".ogg");
return ll.toArray(new String[]{});
}
public static String[] getEncodingValues(Context context) {
String[] aa = context.getResources().getStringArray(R.array.encodings_values);
ArrayList<String> ll = new ArrayList<>(Arrays.asList(aa));
if (Build.VERSION.SDK_INT >= 18)
ll.add("m4a");
if (Build.VERSION.SDK_INT >= 16)
ll.add("mka");
ll.add("ogg");
return ll.toArray(new String[]{});
}
public static Encoder getEncoder(String ext, EncoderInfo info, File out) {
if (ext.equals("wav")) {
return new FormatWAV(info, out);
}
if (ext.equals("3gp")) {
return new Format3GP(info, out);
}
if (ext.equals("m4a")) {
return new FormatM4A(info, out);
}
if (ext.equals("mka")) {
return new FormatMKA(info, out);
}
if (ext.equals("ogg")) {
return new FormatOGG(info, out);
}
return null;
}
public static long getEncoderRate(String ext, int rate) {
if (ext.equals("m4a")) {
long y1 = 365723; // one minute sample 16000Hz
long x1 = 16000; // at 16000
long y2 = 493743; // one minute sample
long x2 = 44000; // at 44000
long x = rate;
long y = (x - x1) * (y2 - y1) / (x2 - x1) + y1;
return y / 60;
}
if (ext.equals("mka")) { // same codec as m4a, but different container
long y1 = 365723; // one minute sample 16000Hz
long x1 = 16000; // at 16000
long y2 = 493743; // one minute sample
long x2 = 44000; // at 44000
long x = rate;
long y = (x - x1) * (y2 - y1) / (x2 - x1) + y1;
return y / 60;
}
if (ext.equals("ogg")) {
long y1 = 174892; // one minute sample 16000Hz
long x1 = 16000; // at 16000
long y2 = 405565; // one minute sample
long x2 = 44000; // at 44000
long x = rate;
long y = (x - x1) * (y2 - y1) / (x2 - x1) + y1;
return y / 60;
}
// default raw
int c = Sound.AUDIO_FORMAT == AudioFormat.ENCODING_PCM_16BIT ? 2 : 1;
return c * rate;
}
}

View file

@ -1,89 +0,0 @@
package com.github.axet.audiolibrary.encoders;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
import com.github.axet.audiolibrary.app.RawSamples;
import java.io.File;
public class FileEncoder {
public static final String TAG = FileEncoder.class.getSimpleName();
Context context;
Handler handler;
File in;
Encoder encoder;
Thread thread;
long samples;
long cur;
Throwable t;
public FileEncoder(Context context, File in, Encoder encoder) {
this.context = context;
this.in = in;
this.encoder = encoder;
handler = new Handler();
}
public void run(final Runnable progress, final Runnable done, final Runnable error) {
thread = new Thread(new Runnable() {
@Override
public void run() {
cur = 0;
RawSamples rs = new RawSamples(in);
samples = rs.getSamples();
short[] buf = new short[1000];
rs.open(buf.length);
try {
while (!Thread.currentThread().isInterrupted()) {
int len = rs.read(buf);
if (len <= 0) {
break;
} else {
encoder.encode(buf, len);
handler.post(progress);
synchronized (thread) {
cur += len;
}
}
}
encoder.close();
if (rs != null) {
rs.close();
}
handler.post(done);
} catch (RuntimeException e) {
Log.e(TAG, "Exception", e);
t = e;
handler.post(error);
}
}
});
thread.start();
}
public int getProgress() {
synchronized (thread) {
return (int) (cur * 100 / samples);
}
}
public Throwable getException() {
return t;
}
public void close() {
if (thread != null) {
thread.interrupt();
thread = null;
}
}
}

View file

@ -1,36 +0,0 @@
package com.github.axet.audiolibrary.encoders;
import android.annotation.TargetApi;
import android.media.MediaFormat;
import java.io.File;
@TargetApi(21)
public class Format3GP extends MuxerMP4 {
public Format3GP(EncoderInfo info, File out) {
MediaFormat format = new MediaFormat();
// for high bitrate AMR_WB
{
// final int kBitRates[] = {6600, 8850, 12650, 14250, 15850, 18250, 19850, 23050, 23850};
// format.setString(MediaFormat.KEY_MIME, "audio/amr-wb");
// format.setInteger(MediaFormat.KEY_SAMPLE_RATE, info.sampleRate);
// format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, info.channels);
// format.setInteger(MediaFormat.KEY_BIT_RATE, 23850); // set maximum
}
// for low bitrate, AMR_NB
{
// final int kBitRates[] = {4750, 5150, 5900, 6700, 7400, 7950, 10200, 12200};
format.setString(MediaFormat.KEY_MIME, "audio/3gpp");
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, info.sampleRate); // 8000 only supported
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, info.channels);
format.setInteger(MediaFormat.KEY_BIT_RATE, 12200); // set maximum
}
create(info, format, out);
}
}

View file

@ -1,24 +0,0 @@
package com.github.axet.audiolibrary.encoders;
import android.annotation.TargetApi;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import java.io.File;
import java.util.Map;
@TargetApi(18)
public class FormatM4A extends MuxerMP4 {
public FormatM4A(EncoderInfo info, File out) {
Map<String, MediaCodecInfo> map = MuxerMP4.findEncoder(Factory.MP4);
if (map.isEmpty())
throw new RuntimeException("mp4 not supported");
MediaFormat format = MuxerMP4.getDefault(Factory.MP4A, map);
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, info.sampleRate);
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, info.channels);
format.setInteger(MediaFormat.KEY_BIT_RATE, 64000);
create(info, format, out);
}
}

View file

@ -1,187 +0,0 @@
package com.github.axet.audiolibrary.encoders;
import android.annotation.TargetApi;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.os.Build;
import org.ebml.io.FileDataWriter;
import org.ebml.matroska.MatroskaFileFrame;
import org.ebml.matroska.MatroskaFileTrack;
import org.ebml.matroska.MatroskaFileWriter;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
@TargetApi(16) // mp4/aac codec
public class FormatMKA implements Encoder {
public static final String KEY_AAC_SBR_MODE = "aac-sbr-mode"; // MediaFormat.KEY_AAC_SBR_MODE
EncoderInfo info;
MediaCodec encoder;
long NumSamples;
ByteBuffer input;
int inputIndex;
MatroskaFileWriter writer;
MatroskaFileTrack track;
MatroskaFileTrack.MatroskaAudioTrack audio;
MatroskaFileFrame old;
public FormatMKA(EncoderInfo info, File out) {
MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, Factory.MP4A);
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, info.sampleRate);
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, info.channels);
format.setInteger(MediaFormat.KEY_BIT_RATE, 64000);
format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectHE);
format.setInteger(KEY_AAC_SBR_MODE, 0);
create(info, format, out);
}
public void create(EncoderInfo info, MediaFormat format, File out) {
this.info = info;
try {
encoder = MediaCodec.createEncoderByType(format.getString(MediaFormat.KEY_MIME));
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
encoder.start();
writer = new MatroskaFileWriter(new FileDataWriter(out.getAbsolutePath()));
audio = new MatroskaFileTrack.MatroskaAudioTrack();
audio.setSamplingFrequency(info.sampleRate);
audio.setOutputSamplingFrequency(info.sampleRate);
audio.setBitDepth(info.bps);
audio.setChannels((short) info.channels);
track = new MatroskaFileTrack();
track.setCodecID("A_AAC");
track.setAudio(audio);
track.setTrackType(MatroskaFileTrack.TrackType.AUDIO);
writer.addTrack(track);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void encode(short[] buf, int len) {
for (int offset = 0; offset < len; offset++) {
if (input == null) {
inputIndex = encoder.dequeueInputBuffer(-1);
if (inputIndex < 0)
throw new RuntimeException("unable to open encoder input buffer");
if (Build.VERSION.SDK_INT >= 21)
input = encoder.getInputBuffer(inputIndex);
else
input = encoder.getInputBuffers()[inputIndex];
input.clear();
}
input.putShort(buf[offset]);
if (!input.hasRemaining()) {
queue();
}
}
}
void queue() {
if (input == null)
return;
encoder.queueInputBuffer(inputIndex, 0, input.position(), getCurrentTimeStamp(), 0);
NumSamples += input.position() / info.channels / (Short.SIZE / 8);
input = null;
while (encode())
;// do encode()
}
public static ByteBuffer clone(ByteBuffer original) {
ByteBuffer clone = ByteBuffer.allocate(original.capacity());
original.rewind();//copy from the beginning
clone.put(original);
original.rewind();
clone.flip();
return clone;
}
public static final int BUFFER_FLAG_KEY_FRAME = 1; // MediaCodec.BUFFER_FLAG_KEY_FRAME
boolean encode() {
MediaCodec.BufferInfo outputInfo = new MediaCodec.BufferInfo();
int outputIndex = encoder.dequeueOutputBuffer(outputInfo, 0);
if (outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER)
return false;
if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { // never get called on API 16
return true;
}
if (outputIndex >= 0) {
ByteBuffer output;
if (Build.VERSION.SDK_INT >= 21)
output = encoder.getOutputBuffer(outputIndex);
else
output = encoder.getOutputBuffers()[outputIndex];
output.position(outputInfo.offset);
output.limit(outputInfo.offset + outputInfo.size);
old(outputInfo.presentationTimeUs / 1000);
if ((outputInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
track.setCodecPrivate(clone(output));
writer.flush();
encoder.releaseOutputBuffer(outputIndex, false);
} else {
MatroskaFileFrame frame = new MatroskaFileFrame();
frame.setKeyFrame((outputInfo.flags & BUFFER_FLAG_KEY_FRAME) == BUFFER_FLAG_KEY_FRAME);
frame.setTimecode(outputInfo.presentationTimeUs / 1000);
frame.setTrackNo(track.getTrackNo());
frame.setData(clone(output));
encoder.releaseOutputBuffer(outputIndex, false);
old = frame;
}
}
return true;
}
void old(long cur) {
if (old != null) {
old.setDuration(cur - old.getTimecode());
writer.addFrame(old);
writer.flush();
old = null;
}
}
public void close() {
end();
encoder.release();
writer.close();
}
long getCurrentTimeStamp() {
return NumSamples * 1000 * 1000 / info.sampleRate;
}
public void end() {
if (input != null) {
queue();
}
int inputIndex = encoder.dequeueInputBuffer(-1);
if (inputIndex >= 0) {
ByteBuffer input;
if (Build.VERSION.SDK_INT >= 21)
input = encoder.getInputBuffer(inputIndex);
else
input = encoder.getInputBuffers()[inputIndex];
input.clear();
encoder.queueInputBuffer(inputIndex, 0, 0, getCurrentTimeStamp(), MediaCodec.BUFFER_FLAG_END_OF_STREAM);
}
while (encode())
;// do encode()
old(getCurrentTimeStamp() / 1000);
writer.setDuration(getCurrentTimeStamp() / 1000);
encoder.stop();
}
public EncoderInfo getInfo() {
return info;
}
}

View file

@ -1,43 +0,0 @@
package com.github.axet.audiolibrary.encoders;
import com.github.axet.vorbisjni.Vorbis;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class FormatOGG implements Encoder {
FileOutputStream writer;
Vorbis vorbis;
public FormatOGG(EncoderInfo info, File out) {
vorbis = new Vorbis();
vorbis.open(info.channels, info.sampleRate, 0.4f);
try {
writer = new FileOutputStream(out);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void encode(short[] buf, int len) {
byte[] bb = vorbis.encode(buf, len);
try {
writer.write(bb);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void close() {
try {
byte[] bb = vorbis.encode(null, 0);
writer.write(bb);
} catch (IOException e) {
throw new RuntimeException(e);
}
vorbis.close();
}
}

View file

@ -1,133 +0,0 @@
package com.github.axet.audiolibrary.encoders;
// based on http://soundfile.sapp.org/doc/WaveFormat/
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class FormatWAV implements Encoder {
int NumSamples;
EncoderInfo info;
int BytesPerSample;
RandomAccessFile outFile;
ByteOrder order = ByteOrder.LITTLE_ENDIAN;
public FormatWAV(EncoderInfo info, File out) {
this.info = info;
NumSamples = 0;
BytesPerSample = info.bps / 8;
try {
outFile = new RandomAccessFile(out, "rw");
} catch (IOException e) {
throw new RuntimeException(e);
}
save();
}
public void save() {
int SubChunk1Size = 16;
int SubChunk2Size = NumSamples * info.channels * BytesPerSample;
int ChunkSize = 4 + (8 + SubChunk1Size) + (8 + SubChunk2Size);
write("RIFF", ByteOrder.BIG_ENDIAN);
write(ChunkSize, order);
write("WAVE", ByteOrder.BIG_ENDIAN);
int ByteRate = info.sampleRate * info.channels * BytesPerSample;
short AudioFormat = 1; // PCM = 1 (i.e. Linear quantization)
int BlockAlign = BytesPerSample * info.channels;
write("fmt ", ByteOrder.BIG_ENDIAN);
write(SubChunk1Size, order);
write((short)AudioFormat, order); //short
write((short) info.channels, order); // short
write(info.sampleRate, order);
write(ByteRate, order);
write((short)BlockAlign, order); // short
write((short)info.bps, order); // short
write("data", ByteOrder.BIG_ENDIAN);
write(SubChunk2Size, order);
}
void write(String str, ByteOrder order) {
try {
byte[] cc = str.getBytes("UTF-8");
ByteBuffer bb = ByteBuffer.allocate(cc.length);
bb.order(order);
bb.put(cc);
bb.flip();
outFile.write(bb.array());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
void write(int i, ByteOrder order) {
ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE / Byte.SIZE);
bb.order(order);
bb.putInt(i);
bb.flip();
try {
outFile.write(bb.array());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
void write(short i, ByteOrder order) {
ByteBuffer bb = ByteBuffer.allocate(Short.SIZE / Byte.SIZE);
bb.order(order);
bb.putShort(i);
bb.flip();
try {
outFile.write(bb.array());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void encode(short[] buf, int buflen) {
NumSamples += buflen / info.channels;
try {
ByteBuffer bb = ByteBuffer.allocate(buflen * (Short.SIZE / Byte.SIZE));
bb.order(order);
for (int i = 0; i < buflen; i++)
bb.putShort(buf[i]);
bb.flip();
outFile.write(bb.array());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void end() {
try {
outFile.seek(0);
save();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void close() {
try {
outFile.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public EncoderInfo getInfo() {
return info;
}
}

View file

@ -1,182 +0,0 @@
package com.github.axet.audiolibrary.encoders;
import android.annotation.TargetApi;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.os.Build;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@TargetApi(18) // depends on MediaMuxer
public class MuxerMP4 implements Encoder {
EncoderInfo info;
MediaCodec encoder;
MediaMuxer muxer;
int audioTrackIndex;
long NumSamples;
ByteBuffer input;
int inputIndex;
public static Map<String, MediaCodecInfo> findEncoder(String mime) {
Map<String, MediaCodecInfo> map = new HashMap<>();
mime = mime.toLowerCase();
int numCodecs = MediaCodecList.getCodecCount();
for (int i = 0; i < numCodecs; i++) {
MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
if (!codecInfo.isEncoder()) {
continue;
}
String[] types = codecInfo.getSupportedTypes();
for (int j = 0; j < types.length; j++) {
String t = types[j].toLowerCase();
if (t.startsWith(mime)) {
map.put(t, codecInfo);
}
}
}
return map;
}
public static String prefered(String pref, Map<String, MediaCodecInfo> map) {
pref = pref.toLowerCase();
Iterator i = map.keySet().iterator();
while (i.hasNext()) {
String m = (String) i.next();
m = m.toLowerCase();
if (m.startsWith(pref))
return m;
}
i = map.keySet().iterator();
if (i.hasNext()) {
return (String) i.next();
}
return null;
}
public static MediaFormat getDefault(String pref, Map<String, MediaCodecInfo> map) {
String p = prefered(pref, map);
if (Build.VERSION.SDK_INT >= 21) {
return map.get(p).getCapabilitiesForType(p).getDefaultFormat();
} else {
MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, pref);
return format;
}
}
public void create(EncoderInfo info, MediaFormat format, File out) {
this.info = info;
try {
encoder = MediaCodec.createEncoderByType(format.getString(MediaFormat.KEY_MIME));
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
encoder.start();
muxer = new MediaMuxer(out.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void encode(short[] buf, int len) {
for (int offset = 0; offset < len; offset++) {
if (input == null) {
inputIndex = encoder.dequeueInputBuffer(-1);
if (inputIndex < 0)
throw new RuntimeException("unable to open encoder input buffer");
if (Build.VERSION.SDK_INT >= 21)
input = encoder.getInputBuffer(inputIndex);
else
input = encoder.getInputBuffers()[inputIndex];
input.clear();
}
input.putShort(buf[offset]);
if (!input.hasRemaining()) {
queue();
}
}
}
void queue() {
if (input == null)
return;
encoder.queueInputBuffer(inputIndex, 0, input.position(), getCurrentTimeStamp(), 0);
NumSamples += input.position() / info.channels / (Short.SIZE / 8);
input = null;
while (encode())
;// do encode()
}
boolean encode() {
MediaCodec.BufferInfo outputInfo = new MediaCodec.BufferInfo();
int outputIndex = encoder.dequeueOutputBuffer(outputInfo, 0);
if (outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER)
return false;
if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
audioTrackIndex = muxer.addTrack(encoder.getOutputFormat());
muxer.start();
return true;
}
if (outputIndex >= 0) {
ByteBuffer output;
if (Build.VERSION.SDK_INT >= 21)
output = encoder.getOutputBuffer(outputIndex);
else
output = encoder.getOutputBuffers()[outputIndex];
output.position(outputInfo.offset);
output.limit(outputInfo.offset + outputInfo.size);
muxer.writeSampleData(audioTrackIndex, output, outputInfo);
encoder.releaseOutputBuffer(outputIndex, false);
}
return true;
}
public void close() {
end();
encoder.release();
muxer.release();
}
long getCurrentTimeStamp() {
return NumSamples * 1000 * 1000 / info.sampleRate;
}
public void end() {
if (input != null) {
queue();
}
int inputIndex = encoder.dequeueInputBuffer(-1);
if (inputIndex >= 0) {
ByteBuffer input;
if (Build.VERSION.SDK_INT >= 21)
input = encoder.getInputBuffer(inputIndex);
else
input = encoder.getInputBuffers()[inputIndex];
input.clear();
encoder.queueInputBuffer(inputIndex, 0, 0, getCurrentTimeStamp(), MediaCodec.BUFFER_FLAG_END_OF_STREAM);
}
while (encode())
;// do encode()
encoder.stop();
muxer.stop();
}
public EncoderInfo getInfo() {
return info;
}
}

View file

@ -1,100 +0,0 @@
package com.github.axet.audiolibrary.widgets;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import com.github.axet.androidlibrary.widgets.ThemeUtils;
public class FFTBarView extends FFTView {
public static final String TAG = FFTBarView.class.getSimpleName();
int barCount;
float barWidth;
float barDeli;
public FFTBarView(Context context) {
this(context, null);
}
public FFTBarView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FFTBarView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
create();
}
void create() {
super.create();
}
public void setBuffer(double[] buf) {
super.setBuffer(buf);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// set initial width
int w = ThemeUtils.dp2px(getContext(), 15);
int d = ThemeUtils.dp2px(getContext(), 4);
int s = w + d;
int mw = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
// get count of bars and delimeters
int dc = (mw - w) / s;
int bc = dc + 1;
// get rate
float k = w / d;
// get one part of (bar+del) size
float e = mw / (bc * k + dc);
barCount = bc;
barWidth = e * k;
barDeli = e;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
@Override
public void onDraw(Canvas canvas) {
if (barCount == 0)
return;
int h = getHeight() - getPaddingTop() - getPaddingBottom();
float left = getPaddingLeft();
for (int i = 0; i < barCount; i++) {
double max = 0;
if (buffer != null) {
int step = buffer.length / barCount;
int offset = i * step;
int end = Math.min(offset + step, buffer.length);
for (int k = offset; k < end; k++) {
double s = buffer[k];
max = Math.max(max, s);
}
}
float y = getPaddingTop() + h - h * ((float) max / 0x7fff) - ThemeUtils.dp2px(getContext(), 1);
if (y < getPaddingTop())
y = getPaddingTop();
canvas.drawRect(left, y, left + barWidth, getPaddingTop() + h, paint);
left += barWidth + barDeli;
}
}
}

View file

@ -1,89 +0,0 @@
package com.github.axet.audiolibrary.widgets;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import com.github.axet.audiolibrary.app.RawSamples;
import com.github.axet.audiolibrary.app.Sound;
public class FFTChartView extends FFTView {
public static final String TAG = FFTChartView.class.getSimpleName();
public FFTChartView(Context context) {
this(context, null);
}
public FFTChartView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FFTChartView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
create();
}
void create() {
super.create();
}
public void setBuffer(double[] buf) {
super.setBuffer(buf);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
@Override
public void onDraw(Canvas canvas) {
if (buffer == null)
return;
canvas.drawColor(Color.RED);
int h = getHeight();
float startX = 0, startY = h;
int w = getWidth() - getPaddingLeft() - getPaddingRight();
float step = w / (float) buffer.length;
double min = Integer.MAX_VALUE;
double max = Integer.MIN_VALUE;
for (int i = 0; i < buffer.length; i++) {
double v = buffer[i];
min = Math.min(v, min);
max = Math.max(v, max);
v = (Sound.MAXIMUM_DB + v) / Sound.MAXIMUM_DB;
float endX = startX;
float endY = (float) (h - h * v);
canvas.drawLine(startX, startY, endX, endY, paint);
startX = endX + step;
startY = endY;
}
String tMin = "" + min;
canvas.drawText(tMin, 0, getHeight(), textPaint);
String tMax = "" + max;
textPaint.getTextBounds(tMax, 0, tMax.length(), textBounds);
canvas.drawText("" + max, w - textBounds.width(), getHeight(), textPaint);
}
}

View file

@ -1,146 +0,0 @@
package com.github.axet.audiolibrary.widgets;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import com.github.axet.androidlibrary.widgets.ThemeUtils;
import org.apache.commons.math3.complex.Complex;
import org.apache.commons.math3.transform.DftNormalization;
import org.apache.commons.math3.transform.FastFourierTransformer;
import org.apache.commons.math3.transform.TransformType;
public class FFTView extends View {
public static final String TAG = FFTView.class.getSimpleName();
Paint paint;
double[] buffer;
Paint textPaint;
Rect textBounds;
public FFTView(Context context) {
this(context, null);
}
public FFTView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FFTView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
create();
}
void create() {
paint = new Paint();
paint.setColor(0xff0433AE);
paint.setStrokeWidth(ThemeUtils.dp2px(getContext(), 1));
textBounds = new Rect();
textPaint = new Paint();
textPaint.setColor(Color.GRAY);
textPaint.setAntiAlias(true);
textPaint.setTextSize(20f);
if (isInEditMode()) {
short[] b = simple();
b = generateSound(16000, 4000, 100);
buffer = fft(b, 0, b.length);
//buffer = RawSamples.generateSound(16000, 4000, 100);
//buffer = RawSamples.fft(buffer, 0, buffer.length);
}
}
public void setBuffer(double[] buf) {
buffer = buf;
}
public static short[] generateSound(int sampleRate, int freqHz, int durationMs) {
int count = sampleRate * durationMs / 1000;
short[] samples = new short[count];
for (int i = 0; i < count; i++) {
short sample = (short) (Math.sin(2 * Math.PI * i / (sampleRate / freqHz)) * 0x7FFF);
samples[i] = sample;
}
return samples;
}
public static double[] asDouble(short[] buffer, int offset, int len) {
double[] dd = new double[len];
for (int i = 0; i < len; i++) {
dd[i] = buffer[offset + i] / (float) 0x7fff;
}
return dd;
}
public static double[] fft(short[] buffer, int offset, int len) {
int len2 = (int) Math.pow(2, Math.ceil(Math.log(len) / Math.log(2)));
final double[][] dataRI = new double[][]{
new double[len2], new double[len2]
};
double[] dataR = dataRI[0];
double[] dataI = dataRI[1];
double powerInput = 0;
for (int i = 0; i < len; i++) {
dataR[i] = buffer[offset + i] / (float) 0x7fff;
powerInput += dataR[i] * dataR[i];
}
powerInput = Math.sqrt(powerInput / len);
FastFourierTransformer.transformInPlace(dataRI, DftNormalization.STANDARD, TransformType.FORWARD);
double[] data = new double[len2 / 2];
data[0] = 10 * Math.log10(Math.pow(new Complex(dataR[0], dataI[0]).abs() / len2, 2));
double powerOutput = 0;
for (int i = 1; i < data.length; i++) {
Complex c = new Complex(dataR[i], dataI[i]);
double p = c.abs();
p = p / len2;
p = p * p;
p = p * 2;
double dB = 10 * Math.log10(p);
powerOutput += p;
data[i] = dB;
}
powerOutput = Math.sqrt(powerOutput);
// if(powerInput != powerOutput) {
// throw new RuntimeException("in " + powerInput + " out " + powerOutput);
// }
return data;
}
public static short[] simple() {
int sampleRate = 1000;
int count = sampleRate;
short[] samples = new short[count];
for (int i = 0; i < count; i++) {
double x = i / (double) count;
double y = 0;
//y += 0.6 * Math.sin(20 * 2 * Math.PI * x);
//y += 0.4 * Math.sin(50 * 2 * Math.PI * x);
//y += 0.2 * Math.sin(80 * 2 * Math.PI * x);
y += Math.sin(100 * 2 * Math.PI * x);
y += Math.sin(200 * 2 * Math.PI * x);
y += Math.sin(300 * 2 * Math.PI * x);
// max = 2.2;
samples[i] = (short) (y / 3 * 0x7fff);
}
return samples;
}
}

View file

@ -1,693 +0,0 @@
package com.github.axet.audiolibrary.widgets;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import com.github.axet.androidlibrary.widgets.ThemeUtils;
import com.github.axet.audiolibrary.R;
import com.github.axet.audiolibrary.app.RawSamples;
import com.github.axet.audiolibrary.app.Sound;
import java.util.LinkedList;
import java.util.List;
public class PitchView extends ViewGroup {
public static final String TAG = PitchView.class.getSimpleName();
// pitch delimiter length in dp
public static final float PITCH_DELIMITER = 1f;
// pitch length in dp
public static final float PITCH_WIDTH = 2f;
// update pitchview in milliseconds
public static final int UPDATE_SPEED = 10;
// edit update time
public static final int EDIT_UPDATE_SPEED = 250;
// 'pitch length' in milliseconds (100ms)
//
// in other words how many milliseconds do we need to show whole pitch.
int pitchTime;
List<Double> data = new LinkedList<>();
// how many pitches we can fit on screen
int pitchScreenCount;
// how many pitches we should fit in memory
int pitchMemCount;
// pitch delimiter length in px
int pitchDlimiter;
// pitch length in px
int pitchWidth;
// pitch length in pn + pitch delimiter length in px
int pitchSize;
PitchGraphView graph;
PitchCurrentView current;
long time = 0;
// how many samples were cut from begining of 'data' list
long samples = 0;
Runnable edit;
// index
int editPos = -1;
boolean editFlash = false;
// current playing position in samples
float playPos = -1;
Runnable play;
Runnable draw;
float offset = 0;
Handler handler;
public static class HandlerUpdate implements Runnable {
long start;
long updateSpeed;
Handler handler;
Runnable run;
public static HandlerUpdate start(HandlerUpdate r, Handler handler, Runnable run, long updateSpeed) {
r.run = run;
r.start = System.currentTimeMillis();
r.updateSpeed = updateSpeed;
r.handler = handler;
// post instead of draw.run() so 'start' will measure actual queue time
handler.postDelayed(r, updateSpeed);
return r;
}
public static HandlerUpdate start(Handler handler, Runnable run, long updateSpeed) {
HandlerUpdate r = new HandlerUpdate();
return start(r, handler, run, updateSpeed);
}
public static void stop(Handler handler, Runnable run) {
handler.removeCallbacks(run);
}
@Override
public void run() {
long cur = System.currentTimeMillis();
long diff = cur - start;
start = cur;
long delay = updateSpeed + (updateSpeed - diff);
if (delay > updateSpeed)
delay = updateSpeed;
post(delay);
}
void post(long delay) {
this.run.run();
if (delay > 0)
this.handler.postDelayed(this, delay);
else
this.handler.post(this);
}
}
// if CPU speed not enough skip frames
public static class FallbackUpdate extends HandlerUpdate {
Runnable fallback;
long slow;
public static FallbackUpdate start(Handler handler, Runnable run, Runnable fallback, long updateSpeed) {
FallbackUpdate r = new FallbackUpdate();
r.fallback = fallback;
r.slow = updateSpeed / 4;
return (FallbackUpdate) start(r, handler, run, updateSpeed);
}
@Override
void post(long delay) {
if (delay < slow) {
this.fallback.run();
} else {
this.run.run();
}
if (delay > 0)
this.handler.postDelayed(this, delay);
else
this.handler.post(this);
}
}
public class PitchGraphView extends View {
Paint paint;
Paint paintRed;
Paint editPaint;
Paint playPaint;
Paint cutColor;
public PitchGraphView(Context context) {
this(context, null);
}
public PitchGraphView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PitchGraphView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
paint = new Paint();
paint.setColor(getThemeColor(R.attr.colorPrimary));
paint.setStrokeWidth(pitchWidth);
paintRed = new Paint();
paintRed.setColor(Color.RED);
paintRed.setStrokeWidth(pitchWidth);
cutColor = new Paint();
cutColor.setColor(getThemeColor(android.R.attr.textColorHint));
cutColor.setStrokeWidth(pitchWidth);
editPaint = new Paint();
editPaint.setColor(getThemeColor(R.attr.colorPrimaryDark));
editPaint.setStrokeWidth(pitchWidth);
playPaint = new Paint();
playPaint.setColor(getThemeColor(R.attr.colorPrimaryDark));
playPaint.setStrokeWidth(pitchWidth / 2);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int w = MeasureSpec.getSize(widthMeasureSpec);
pitchScreenCount = w / pitchSize + 1;
pitchMemCount = pitchScreenCount + 1;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
fit(pitchScreenCount);
}
public void calc() {
if (data.size() >= pitchMemCount) {
long cur = System.currentTimeMillis();
float tick = (cur - time) / (float) pitchTime;
// force clear queue
if (data.size() > pitchMemCount + 1) {
tick = 0;
time = cur;
fit(pitchMemCount);
}
if (tick > 1) {
if (data.size() > pitchMemCount) {
tick -= 1;
time += pitchTime;
} else if (data.size() == pitchMemCount) {
tick = 0;
time = cur;
}
fit(data.size() - 1);
}
offset = pitchSize * tick;
}
}
@Override
public void onDraw(Canvas canvas) {
int m = Math.min(pitchMemCount, data.size());
// if (edit != null) {
// float x = editPos * pitchSize + pitchSize / 2f;
// canvas.drawRect(x, 0, getWidth(), getHeight(), bg_cut);
// }
for (int i = 0; i < m; i++) {
double dB = filterDB(i);
float left = (float) dB;
float right = (float) dB;
float mid = getHeight() / 2f;
float x = -offset + i * pitchSize + pitchSize / 2f;
Paint p = paint;
if (getDB(i) < 0) {
p = paintRed;
left = 1;
right = 1;
}
if (editPos != -1 && i >= editPos)
p = cutColor;
// left channel pitch
canvas.drawLine(x, mid, x, mid - mid * left - 1, p);
// right channel pitch
canvas.drawLine(x, mid, x, mid + mid * right + 1, p);
}
// paint edit mark
if (editPos != -1 && editFlash) {
float x = editPos * pitchSize + pitchSize / 2f;
canvas.drawLine(x, 0, x, getHeight(), editPaint);
}
// paint play mark
if (playPos > 0) {
float x = playPos * pitchSize + pitchSize / 2f;
canvas.drawLine(x, 0, x, getHeight(), playPaint);
}
}
}
public class PitchCurrentView extends View {
Paint paint;
Paint textPaint;
String text;
Rect textBounds;
double dB;
public PitchCurrentView(Context context) {
this(context, null);
}
public PitchCurrentView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PitchCurrentView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
text = "100 " + getContext().getString(R.string.db);
textBounds = new Rect();
textPaint = new Paint();
textPaint.setColor(Color.GRAY);
textPaint.setAntiAlias(true);
textPaint.setTextSize(20f);
paint = new Paint();
paint.setColor(getThemeColor(R.attr.colorPrimary));
paint.setStrokeWidth(pitchWidth);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int w = MeasureSpec.getSize(widthMeasureSpec);
textPaint.getTextBounds(this.text, 0, this.text.length(), textBounds);
int h = getPaddingTop();
h += textBounds.height();
h += ThemeUtils.dp2px(getContext(), 2);
h += pitchWidth + getPaddingBottom();
setMeasuredDimension(w, h);
}
public void setText(String text) {
this.text = text;
textPaint.getTextBounds(this.text, 0, this.text.length(), textBounds);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
void update(int end) {
dB = getDB(end) / Sound.MAXIMUM_DB;
String str = "";
str = Integer.toString((int) getDB(end)) + " " + getContext().getString(R.string.db);
setText(str);
}
@Override
public void onDraw(Canvas canvas) {
if (data.size() > 0) {
current.update(getEnd());
}
float y = getPaddingTop() + textBounds.height();
int x = getWidth() / 2 - textBounds.width() / 2;
canvas.drawText(text, x, y, textPaint);
y += ThemeUtils.dp2px(getContext(), 2);
float left = (float) dB;
float right = (float) dB;
float mid = getWidth() / 2f;
y += pitchWidth / 2;
canvas.drawLine(mid, y, mid - mid * left - 1, y, paint);
canvas.drawLine(mid, y, mid + mid * right + 1, y, paint);
}
}
public PitchView(Context context) {
this(context, null);
}
public PitchView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PitchView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
create();
}
void create() {
handler = new Handler();
pitchDlimiter = ThemeUtils.dp2px(getContext(), PITCH_DELIMITER);
pitchWidth = ThemeUtils.dp2px(getContext(), PITCH_WIDTH);
pitchSize = pitchWidth + pitchDlimiter;
pitchTime = pitchSize * UPDATE_SPEED;
graph = new PitchGraphView(getContext());
addView(graph);
// fft = new FFTChartView(getContext()) {
// @Override
// public void onDraw(Canvas canvas) {
// if (data.size() > 0) {
// short[] buf = dataSamples.get(getEnd());
// double[] d = FFTView.fft(buf, 0, buf.length);
// //double[] d = asDouble(buf, 0, buf.length);
// fft.setBuffer(d);
// }
//
// super.onDraw(canvas);
// }
// };
// fft.setPadding(0, ThemeUtils.dp2px(2), 0, 0);
// addView(fft);
current = new PitchCurrentView(getContext());
current.setPadding(0, ThemeUtils.dp2px(getContext(), 2), 0, 0);
addView(current);
if (isInEditMode()) {
for (int i = 0; i < 3000; i++) {
data.add(-Math.sin(i) * Sound.MAXIMUM_DB);
}
}
time = System.currentTimeMillis();
}
public int getThemeColor(int id) {
return ThemeUtils.getThemeColor(getContext(), id);
}
public int getMaxPitchCount(int width) {
int pitchScreenCount = width / pitchSize + 1;
int pitchMemCount = pitchScreenCount + 1;
return pitchMemCount;
}
public void clear(long s) {
data.clear();
samples = s;
offset = 0;
edit = null;
draw = null;
play = null;
}
public void fit(int max) {
if (max < 0) // -1
return;
if (data.size() > max) {
int cut = data.size() - max;
data.subList(0, cut).clear();
samples += cut;
int m = data.size() - 1;
// screen rotate may cause play/edit offsets off screen
if (editPos > m)
editPos = m;
if (playPos > m)
playPos = m;
}
}
public void add(double a) {
data.add(a);
}
public void drawCalc() {
graph.calc();
draw();
}
public void drawEnd() {
fit(pitchMemCount);
offset = 0;
draw();
}
public int getEnd() {
int end = data.size() - 1;
if (editPos != -1) {
end = editPos;
}
if (playPos > 0) {
end = (int) playPos;
}
return end;
}
public double getDB(int i) {
double db = data.get(i);
db = Sound.MAXIMUM_DB + db;
return db;
}
public double filterDB(int i) {
double db = getDB(i);
// do not show below NOISE_DB
db = db - Sound.NOISE_DB;
if (db < 0)
db = 0;
int rest = Sound.MAXIMUM_DB - Sound.NOISE_DB;
db = db / rest;
return db;
}
public void draw() {
graph.invalidate();
current.invalidate();
}
public int getPitchTime() {
return pitchTime;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int ww = getMeasuredWidth() - getPaddingRight() - getPaddingLeft();
int hh = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
current.measure(MeasureSpec.makeMeasureSpec(ww, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(hh, MeasureSpec.AT_MOST));
hh = hh - current.getMeasuredHeight();
graph.measure(MeasureSpec.makeMeasureSpec(ww, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(hh, MeasureSpec.AT_MOST));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
graph.layout(getPaddingLeft(), getPaddingTop(),
getPaddingLeft() + graph.getMeasuredWidth(), getPaddingTop() + graph.getMeasuredHeight());
current.layout(getPaddingLeft(), graph.getBottom(),
getPaddingLeft() + current.getMeasuredWidth(), graph.getBottom() + current.getMeasuredHeight());
}
public void stop() {
if (edit != null)
HandlerUpdate.stop(handler, edit);
edit = null;
if (draw != null)
HandlerUpdate.stop(handler, draw);
draw = null;
if (play != null)
HandlerUpdate.stop(handler, play);
play = null;
draw();
}
public long edit(float offset) {
if (offset < 0)
editPos = -1;
else
editPos = ((int) offset) / pitchSize;
playPos = -1;
if (editPos >= pitchScreenCount)
editPos = pitchScreenCount - 1;
if (editPos >= data.size())
editPos = data.size() - 1;
if (draw != null) {
HandlerUpdate.stop(handler, draw);
draw = null;
}
if (play != null) {
HandlerUpdate.stop(handler, play);
play = null;
}
draw();
edit();
return samples + editPos;
}
public void edit() {
if (edit == null) {
editFlash = true;
edit = FallbackUpdate.start(handler, new Runnable() {
@Override
public void run() {
draw();
editFlash = !editFlash;
}
}, new Runnable() {
@Override
public void run() {
graph.calc();
editFlash = !editFlash;
}
}, EDIT_UPDATE_SPEED);
}
}
public void record() {
if (edit != null)
HandlerUpdate.stop(handler, edit);
edit = null;
editPos = -1;
if (play != null)
HandlerUpdate.stop(handler, play);
play = null;
playPos = -1;
if (draw == null) {
time = System.currentTimeMillis();
draw = FallbackUpdate.start(handler, new Runnable() {
@Override
public void run() {
drawCalc();
}
}, new Runnable() {
@Override
public void run() {
graph.calc();
}
}, UPDATE_SPEED);
}
}
// current paying pos in actual samples
public void play(float pos) {
if (pos < 0) {
playPos = -1;
if (play != null) {
HandlerUpdate.stop(handler, play);
play = null;
}
if (edit == null) {
edit();
}
return;
}
playPos = pos - samples;
editFlash = true;
int max = data.size() - 1;
if (playPos > max)
playPos = max;
if (edit != null)
HandlerUpdate.stop(handler, edit);
edit = null;
if (draw != null)
HandlerUpdate.stop(handler, draw);
draw = null;
if (play == null) {
time = System.currentTimeMillis();
play = FallbackUpdate.start(handler, new Runnable() {
@Override
public void run() {
draw();
}
}, new Runnable() {
@Override
public void run() {
graph.calc();
}
}, UPDATE_SPEED);
}
}
}

View file

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>

View file

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
</vector>

View file

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M12,14c1.66,0 2.99,-1.34 2.99,-3L15,5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6c0,1.66 1.34,3 3,3zm5.3,-3c0,3 -2.54,5.1 -5.3,5.1S6.7,14 6.7,11H5c0,3.41 2.72,6.23 6,6.72V21h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z"/>
</vector>

View file

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z"/>
</vector>

View file

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M8,5v14l11,-7z"/>
</vector>

View file

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
</vector>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/colorAccentDark" />
<padding
android:bottom="5dp"
android:left="5dp"
android:right="5dp"
android:top="5dp" />
</shape>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/colorAccentLight" />
<padding
android:bottom="5dp"
android:left="5dp"
android:right="5dp"
android:top="5dp" />
</shape>

View file

@ -1,60 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="64dp"
android:background="?android:windowBackground">
<ImageView
android:layout_width="@dimen/notification_large_icon_width"
android:layout_height="@dimen/notification_large_icon_height"
android:src="@drawable/ic_mic_24dp"
android:tint="?android:attr/colorForeground" />
<LinearLayout
android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="top"
android:layout_marginLeft="@dimen/notification_large_icon_width"
android:layout_marginStart="@dimen/notification_large_icon_width"
android:layout_marginTop="5dp"
android:minHeight="@dimen/notification_large_icon_height"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/notification_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textColor="?android:attr/colorForeground" />
<TextView
android:id="@+id/notification_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textColor="?android:attr/textColorHint"
android:textSize="@dimen/notification_subtext_size" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageButton
android:id="@+id/notification_pause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_pause_black_24dp" />
</LinearLayout>
</LinearLayout>
</FrameLayout>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/AppThemeDark">
<include layout="@layout/notifictaion_recording" />
</FrameLayout>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/AppThemeLight">
<include layout="@layout/notifictaion_recording" />
</FrameLayout>

View file

@ -1,166 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".activities.RecordingActivity">
<LinearLayout
android:id="@+id/recording_base"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.github.axet.androidlibrary.widgets.EqualLinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="left"
android:orientation="vertical">
<com.github.axet.androidlibrary.widgets.PathMax
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/recording_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="2016-02-01.wav"
android:textColor="?attr/titleColor"
android:textSize="20dp" />
</com.github.axet.androidlibrary.widgets.PathMax>
<TextView
android:id="@+id/recording_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="2016 02 01" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right"
android:orientation="vertical">
<TextView
android:id="@+id/recording_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="00:05" />
<TextView
android:id="@+id/recording_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="0.1 MB" />
</LinearLayout>
</com.github.axet.androidlibrary.widgets.EqualLinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/recording_player"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:background="?attr/secondBackground"
android:orientation="vertical"
android:padding="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:id="@+id/recording_player_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="01:01"
android:textColor="?android:textColorSecondary" />
<SeekBar
android:id="@+id/recording_player_seek"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="5dp"
android:progress="60" />
<TextView
android:id="@+id/recording_player_end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="01:01" />
</LinearLayout>
<LinearLayout
android:id="@+id/recording_detail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/recording_player_play"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_play_arrow_black_24dp"
android:tint="?attr/colorAccent" />
<View
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/recording_player_edit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_create_black_24dp"
android:tint="?attr/colorAccent" />
<View
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/recording_player_share"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_share_black_24dp"
android:tint="?attr/colorAccent" />
<View
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/recording_player_trash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_delete_black_24dp"
android:tint="?attr/colorAccent" />
</LinearLayout>
</LinearLayout>
</FrameLayout>
</LinearLayout>
</FrameLayout>

View file

@ -1,15 +0,0 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".activities.MainActivity">
<item
android:id="@+id/action_rename"
android:orderInCategory="100"
android:title="@string/rename"
app:showAsAction="never" />
<item
android:id="@+id/action_delete"
android:orderInCategory="200"
android:title="@string/delete"
app:showAsAction="never" />
</menu>

View file

@ -1,17 +0,0 @@
<resources>
<string-array name="encodings_text">
<item>.wav (по умолчанию)</item>
</string-array>
<string name="title_header">%1$s свободно ~ %2$s</string>
<string name="db">дБ</string>
<string name="no">Нет</string>
<string name="yes">Да</string>
<string name="are_you_sure">Вы уверены?</string>
<string name="delete_recording">Удалить запись</string>
<string name="rename_recording">Переименовать запись</string>
<string name="shared_via">"Создано с помощью: %1$s"</string>
<string name="file_not_found">Файл не найден</string>
<string name="rename">Переименовать</string>
<string name="delete">Удалить</string>
</resources>

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="titleColor" format="color" />
<attr name="secondBackground" format="color" />
<attr name="roundButton" format="reference" />
</resources>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="secondBackground">#33333333</color>
<color name="colorAccentLight">#FF4081</color>
<color name="colorAccentDark">#a4a4a4</color>
</resources>

View file

@ -1,23 +0,0 @@
<resources>
<string name="app_name">Audio Library</string>
<string-array name="encodings_text">
<item>.wav (default)</item>
</string-array>
<string-array name="encodings_values" translatable="false">
<item>wav</item>
</string-array>
<string name="title_header">%1$s free ~ %2$s left</string>
<string name="db">dB</string>
<string name="no">No</string>
<string name="yes">Yes</string>
<string name="are_you_sure">"Are you sure ? "</string>
<string name="delete_recording">Delete Recording</string>
<string name="rename_recording">Rename Recording</string>
<string name="shared_via">"Shared via %1$s"</string>
<string name="file_not_found">File not found</string>
<string name="rename">Rename</string>
<string name="delete">Delete</string>
</resources>

View file

@ -1,48 +0,0 @@
<resources>
<style name="AppThemeLight" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccentLight</item>
<item name="titleColor">@color/colorPrimary</item>
<item name="secondBackground">#c2c2c2</item>
<item name="roundButton">@drawable/round_button_light</item>
<item name="alertDialogTheme">@style/AppThemeDialogLight</item>
</style>
<style name="AppThemeDialogLight" parent="@style/Theme.AppCompat.Light.Dialog.Alert">
<item name="colorAccent">@color/colorAccentLight</item>
</style>
<style name="AppThemeLight.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="AppThemeLight.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar">
<item name="android:textColorPrimary">@android:color/white</item>
</style>
<style name="AppThemeLight.PopupOverlay" parent="ThemeOverlay.AppCompat.Light">
</style>
<style name="AppThemeDark" parent="Theme.AppCompat">
<item name="colorPrimaryDark">@color/primary_dark_material_dark</item>
<item name="colorPrimary">@color/primary_material_dark</item>
<item name="colorAccent">@color/colorAccentDark</item>
<item name="titleColor">@android:color/primary_text_dark</item>
<item name="secondBackground">#151515</item>
<item name="roundButton">@drawable/round_button_dark</item>
<item name="alertDialogTheme">@style/AppThemeDialogDark</item>
</style>
<style name="AppThemeDialogDark" parent="@style/Theme.AppCompat.Dialog.Alert">
<item name="colorAccent">@color/colorAccentDark</item>
</style>
<style name="AppThemeDark.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
</resources>

View file

@ -1,17 +0,0 @@
package com.github.axet.audiolibrary;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}