Merge branch 'audiorecorder-1.4.4'
|
|
@ -8,8 +8,8 @@ android {
|
|||
applicationId "com.github.axet.audiorecorder"
|
||||
minSdkVersion 9
|
||||
targetSdkVersion 23
|
||||
versionCode 105
|
||||
versionName "1.4.3"
|
||||
versionCode 106
|
||||
versionName "1.4.4"
|
||||
}
|
||||
signingConfigs {
|
||||
release {
|
||||
|
|
@ -43,12 +43,7 @@ android {
|
|||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
testCompile 'junit:junit:4.12'
|
||||
compile 'com.android.support:appcompat-v7:25.1.1'
|
||||
compile 'com.android.support:support-v4:25.1.1'
|
||||
compile 'com.android.support:design:25.1.1'
|
||||
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.7' //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')
|
||||
compile 'com.android.support:appcompat-v7:25.2.0'
|
||||
compile 'com.android.support:support-v4:25.2.0'
|
||||
compile 'com.github.axet:android-audio-library:0.0.2' // compile project(':android-audio-library')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,6 @@ package com.github.axet.audiorecorder;
|
|||
|
||||
import android.app.Application;
|
||||
import android.test.ApplicationTestCase;
|
||||
import android.util.Log;
|
||||
|
||||
import com.github.axet.audiorecorder.app.RawSamples;
|
||||
|
||||
/**
|
||||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
<activity
|
||||
android:name=".activities.MainActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:label="@string/title_activity_main"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleInstance"
|
||||
android:theme="@style/AppThemeLight.NoActionBar">
|
||||
<intent-filter>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
package com.github.axet.audiorecorder.activities;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.media.MediaPlayer;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
|
@ -18,67 +15,39 @@ import android.support.design.widget.FloatingActionButton;
|
|||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.PopupMenu;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
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.LibraryApplication;
|
||||
import com.github.axet.androidlibrary.widgets.OpenFileDialog;
|
||||
import com.github.axet.androidlibrary.widgets.PopupShareActionProvider;
|
||||
import com.github.axet.audiolibrary.app.Recordings;
|
||||
import com.github.axet.audiolibrary.app.Storage;
|
||||
import com.github.axet.audiorecorder.R;
|
||||
import com.github.axet.audiorecorder.animations.RecordingAnimation;
|
||||
import com.github.axet.audiorecorder.app.MainApplication;
|
||||
import com.github.axet.audiorecorder.app.Storage;
|
||||
import com.github.axet.audiorecorder.encoders.Factory;
|
||||
import com.google.android.gms.appindexing.Action;
|
||||
import com.google.android.gms.appindexing.AppIndex;
|
||||
import com.google.android.gms.common.api.GoogleApiClient;
|
||||
|
||||
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 MainActivity extends AppCompatActivity implements AbsListView.OnScrollListener {
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
public final static String TAG = MainActivity.class.getSimpleName();
|
||||
|
||||
static final int TYPE_COLLAPSED = 0;
|
||||
static final int TYPE_EXPANDED = 1;
|
||||
static final int TYPE_DELETED = 2;
|
||||
|
||||
/**
|
||||
* ATTENTION: This was auto-generated to implement the App Indexing API.
|
||||
* See https://g.co/AppIndexing/AndroidStudio for more information.
|
||||
*/
|
||||
private GoogleApiClient client;
|
||||
FloatingActionButton fab;
|
||||
Handler handler = new Handler();
|
||||
|
||||
final int[] ALL = {TYPE_COLLAPSED, TYPE_EXPANDED};
|
||||
|
||||
int scrollState;
|
||||
|
||||
ListView list;
|
||||
Recordings recordings;
|
||||
Storage storage;
|
||||
ListView list;
|
||||
Handler handler;
|
||||
PopupShareActionProvider shareProvider;
|
||||
|
||||
int themeId;
|
||||
|
||||
|
|
@ -89,381 +58,6 @@ public class MainActivity extends AppCompatActivity implements AbsListView.OnScr
|
|||
context.startActivity(i);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
public class Recordings extends ArrayAdapter<File> {
|
||||
MediaPlayer player;
|
||||
Runnable updatePlayer;
|
||||
int selected = -1;
|
||||
|
||||
Map<File, Integer> durations = new TreeMap<>();
|
||||
|
||||
public Recordings(Context context) {
|
||||
super(context, 0);
|
||||
}
|
||||
|
||||
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(new SortFiles());
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (player != null) {
|
||||
player.release();
|
||||
player = null;
|
||||
}
|
||||
if (updatePlayer != null) {
|
||||
handler.removeCallbacks(updatePlayer);
|
||||
updatePlayer = null;
|
||||
}
|
||||
}
|
||||
|
||||
@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(LibraryApplication.formatDuration(getContext(), durations.get(f)));
|
||||
|
||||
TextView size = (TextView) convertView.findViewById(R.id.recording_size);
|
||||
size.setText(LibraryApplication.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" + 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(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, getString(R.string.shared_via, 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(MainActivity.this, 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.pause : R.drawable.play);
|
||||
|
||||
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(LibraryApplication.formatDuration(getContext(), c));
|
||||
bar.setMax(d);
|
||||
bar.setKeyProgressIncrement(1);
|
||||
bar.setProgress(c);
|
||||
end.setText("-" + LibraryApplication.formatDuration(getContext(), d - c));
|
||||
|
||||
return playing;
|
||||
}
|
||||
|
||||
public void select(int pos) {
|
||||
selected = pos;
|
||||
notifyDataSetChanged();
|
||||
playerStop();
|
||||
}
|
||||
}
|
||||
|
||||
public void setAppTheme(int id) {
|
||||
super.setTheme(id);
|
||||
themeId = id;
|
||||
|
|
@ -480,25 +74,19 @@ public class MainActivity extends AppCompatActivity implements AbsListView.OnScr
|
|||
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
storage = new Storage(this);
|
||||
|
||||
// ATTENTION: This was auto-generated to implement the App Indexing API.
|
||||
// See https://g.co/AppIndexing/AndroidStudio for more information.
|
||||
client = new GoogleApiClient.Builder(this).addApi(AppIndex.API).build();
|
||||
|
||||
storage = new Storage(this);
|
||||
handler = new Handler();
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
// if (Build.VERSION.SDK_INT >= 16)
|
||||
// toolbar.setBackground(new ColorDrawable(MainApplication.getActionbarColor(this)));
|
||||
// else
|
||||
// toolbar.setBackgroundDrawable(new ColorDrawable(MainApplication.getActionbarColor(this)));
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
fab = (FloatingActionButton) findViewById(R.id.fab);
|
||||
fab.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
fab.setClickable(false);
|
||||
recordings.select(-1);
|
||||
RecordingActivity.startActivity(MainActivity.this, false);
|
||||
// Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
|
||||
|
|
@ -506,10 +94,8 @@ public class MainActivity extends AppCompatActivity implements AbsListView.OnScr
|
|||
}
|
||||
});
|
||||
|
||||
recordings = new Recordings(this);
|
||||
|
||||
list = (ListView) findViewById(R.id.list);
|
||||
list.setOnScrollListener(this);
|
||||
recordings = new Recordings(this, list);
|
||||
list.setAdapter(recordings);
|
||||
list.setEmptyView(findViewById(R.id.empty_list));
|
||||
|
||||
|
|
@ -525,11 +111,6 @@ public class MainActivity extends AppCompatActivity implements AbsListView.OnScr
|
|||
}
|
||||
}
|
||||
|
||||
// load recordings
|
||||
void load() {
|
||||
recordings.scan(storage.getStoragePath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
|
|
@ -578,16 +159,14 @@ public class MainActivity extends AppCompatActivity implements AbsListView.OnScr
|
|||
}
|
||||
|
||||
if (permitted(PERMISSIONS))
|
||||
load();
|
||||
recordings.load();
|
||||
else
|
||||
load();
|
||||
recordings.load();
|
||||
|
||||
checkPending();
|
||||
|
||||
updateHeader();
|
||||
|
||||
fab.setClickable(true);
|
||||
|
||||
final int selected = getLastRecording();
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
|
|
@ -631,7 +210,7 @@ public class MainActivity extends AppCompatActivity implements AbsListView.OnScr
|
|||
case 1:
|
||||
if (permitted(permissions)) {
|
||||
storage.migrateLocalStorage();
|
||||
load();
|
||||
recordings.load();
|
||||
checkPending();
|
||||
} else {
|
||||
Toast.makeText(this, R.string.not_permitted, Toast.LENGTH_SHORT).show();
|
||||
|
|
@ -666,15 +245,6 @@ public class MainActivity extends AppCompatActivity implements AbsListView.OnScr
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrollStateChanged(AbsListView view, int scrollState) {
|
||||
this.scrollState = scrollState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
|
@ -682,7 +252,7 @@ public class MainActivity extends AppCompatActivity implements AbsListView.OnScr
|
|||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
list.smoothScrollToPosition(recordings.selected);
|
||||
list.smoothScrollToPosition(recordings.getSelected());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,18 +35,18 @@ import android.widget.TextView;
|
|||
import android.widget.Toast;
|
||||
|
||||
import com.github.axet.androidlibrary.animations.MarginBottomAnimation;
|
||||
import com.github.axet.androidlibrary.app.LibraryApplication;
|
||||
import com.github.axet.androidlibrary.app.MainLibrary;
|
||||
import com.github.axet.audiorecorder.R;
|
||||
import com.github.axet.audiorecorder.app.MainApplication;
|
||||
import com.github.axet.audiorecorder.app.RawSamples;
|
||||
import com.github.axet.audiorecorder.app.Sound;
|
||||
import com.github.axet.audiorecorder.app.Storage;
|
||||
import com.github.axet.audiorecorder.encoders.Encoder;
|
||||
import com.github.axet.audiorecorder.encoders.EncoderInfo;
|
||||
import com.github.axet.audiorecorder.encoders.Factory;
|
||||
import com.github.axet.audiorecorder.encoders.FileEncoder;
|
||||
import com.github.axet.audiolibrary.app.RawSamples;
|
||||
import com.github.axet.audiolibrary.app.Sound;
|
||||
import com.github.axet.audiolibrary.app.Storage;
|
||||
import com.github.axet.audiolibrary.encoders.Encoder;
|
||||
import com.github.axet.audiolibrary.encoders.EncoderInfo;
|
||||
import com.github.axet.audiolibrary.encoders.Factory;
|
||||
import com.github.axet.audiolibrary.encoders.FileEncoder;
|
||||
import com.github.axet.audiorecorder.services.RecordingService;
|
||||
import com.github.axet.audiorecorder.widgets.PitchView;
|
||||
import com.github.axet.audiolibrary.widgets.PitchView;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
|
@ -426,7 +426,7 @@ public class RecordingActivity extends AppCompatActivity {
|
|||
|
||||
int rate = Integer.parseInt(shared.getString(MainApplication.PREFERENCE_RATE, ""));
|
||||
int m = MainApplication.getChannels(this);
|
||||
int c = RawSamples.AUDIO_FORMAT == AudioFormat.ENCODING_PCM_16BIT ? 2 : 1;
|
||||
int c = Sound.AUDIO_FORMAT == AudioFormat.ENCODING_PCM_16BIT ? 2 : 1;
|
||||
|
||||
long perSec = (c * m * rate);
|
||||
long sec = free / perSec * 1000;
|
||||
|
|
@ -439,7 +439,7 @@ public class RecordingActivity extends AppCompatActivity {
|
|||
final ImageView playButton = (ImageView) box.findViewById(R.id.recording_play);
|
||||
|
||||
if (show) {
|
||||
playButton.setImageResource(R.drawable.pause);
|
||||
playButton.setImageResource(R.drawable.ic_pause_black_24dp);
|
||||
|
||||
playIndex = editSample;
|
||||
|
||||
|
|
@ -478,7 +478,7 @@ public class RecordingActivity extends AppCompatActivity {
|
|||
play = null;
|
||||
}
|
||||
pitch.play(-1);
|
||||
playButton.setImageResource(R.drawable.play);
|
||||
playButton.setImageResource(R.drawable.ic_play_arrow_black_24dp);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -563,7 +563,7 @@ public class RecordingActivity extends AppCompatActivity {
|
|||
|
||||
sound.silent();
|
||||
|
||||
pause.setImageResource(R.drawable.ic_pause_24dp);
|
||||
pause.setImageResource(R.drawable.ic_pause_black_24dp);
|
||||
|
||||
pitch.record();
|
||||
|
||||
|
|
@ -588,12 +588,12 @@ public class RecordingActivity extends AppCompatActivity {
|
|||
|
||||
rs.open(samplesTime);
|
||||
|
||||
int min = AudioRecord.getMinBufferSize(sampleRate, MainApplication.getMode(RecordingActivity.this), RawSamples.AUDIO_FORMAT);
|
||||
int min = AudioRecord.getMinBufferSize(sampleRate, MainApplication.getMode(RecordingActivity.this), Sound.AUDIO_FORMAT);
|
||||
if (min <= 0) {
|
||||
throw new RuntimeException("Unable to initialize AudioRecord: Bad audio values");
|
||||
}
|
||||
|
||||
recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate, MainApplication.getMode(RecordingActivity.this), RawSamples.AUDIO_FORMAT, min * 2);
|
||||
recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate, MainApplication.getMode(RecordingActivity.this), Sound.AUDIO_FORMAT, min * 2);
|
||||
if (recorder.getState() != AudioRecord.STATE_INITIALIZED) {
|
||||
throw new RuntimeException("Unable to initialize AudioRecord");
|
||||
}
|
||||
|
|
@ -630,7 +630,7 @@ public class RecordingActivity extends AppCompatActivity {
|
|||
if (stableRefresh || diff >= s) {
|
||||
stableRefresh = true;
|
||||
|
||||
rs.write(buffer);
|
||||
rs.write(buffer, readSize);
|
||||
|
||||
int ps = samplesUpdate * MainApplication.getChannels(RecordingActivity.this);
|
||||
for (int i = 0; i < readSize; i += ps) {
|
||||
|
|
@ -717,13 +717,12 @@ public class RecordingActivity extends AppCompatActivity {
|
|||
|
||||
void updateSamples(long samplesTime) {
|
||||
long ms = samplesTime / sampleRate * 1000;
|
||||
time.setText(LibraryApplication.formatDuration(this, ms));
|
||||
time.setText(MainLibrary.formatDuration(this, ms));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
|
||||
switch (requestCode) {
|
||||
case 1:
|
||||
if (permitted(permissions)) {
|
||||
|
|
@ -764,8 +763,7 @@ public class RecordingActivity extends AppCompatActivity {
|
|||
|
||||
EncoderInfo getInfo() {
|
||||
final int channels = MainApplication.getChannels(this);
|
||||
final int bps = RawSamples.AUDIO_FORMAT == AudioFormat.ENCODING_PCM_16BIT ? 16 : 8;
|
||||
|
||||
final int bps = Sound.AUDIO_FORMAT == AudioFormat.ENCODING_PCM_16BIT ? 16 : 8;
|
||||
return new EncoderInfo(channels, sampleRate, bps);
|
||||
}
|
||||
|
||||
|
|
@ -860,7 +858,6 @@ public class RecordingActivity extends AppCompatActivity {
|
|||
@Override
|
||||
public void finish() {
|
||||
super.finish();
|
||||
|
||||
MainActivity.startActivity(this);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import android.widget.Toast;
|
|||
|
||||
import com.github.axet.audiorecorder.R;
|
||||
import com.github.axet.audiorecorder.app.MainApplication;
|
||||
import com.github.axet.audiorecorder.encoders.Factory;
|
||||
import com.github.axet.audiolibrary.encoders.Factory;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
|
|
|
|||
|
|
@ -1,114 +0,0 @@
|
|||
package com.github.axet.audiorecorder.animations;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.animation.Transformation;
|
||||
import android.widget.ListView;
|
||||
|
||||
import com.github.axet.androidlibrary.animations.MarginAnimation;
|
||||
import com.github.axet.audiorecorder.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();
|
||||
}
|
||||
}
|
||||
|
|
@ -6,87 +6,16 @@ import android.content.SharedPreferences;
|
|||
import android.media.AudioFormat;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import com.github.axet.androidlibrary.app.LibraryApplication;
|
||||
import com.github.axet.androidlibrary.app.MainLibrary;
|
||||
import com.github.axet.androidlibrary.widgets.ThemeUtils;
|
||||
import com.github.axet.audiorecorder.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";
|
||||
public class MainApplication extends com.github.axet.audiolibrary.app.MainApplication {
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
PreferenceManager.setDefaultValues(this, R.xml.pref_general, false);
|
||||
|
||||
Context context = this;
|
||||
context.setTheme(getUserTheme());
|
||||
}
|
||||
|
||||
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 static int getActionbarColor(Context context) {
|
||||
int colorId = MainApplication.getTheme(context, R.attr.colorPrimary, R.attr.secondBackground);
|
||||
int color = ThemeUtils.getThemeColor(context, colorId);
|
||||
return color;
|
||||
}
|
||||
|
||||
public int getUserTheme() {
|
||||
return getTheme(this, R.style.AppThemeLight, R.style.AppThemeDark);
|
||||
}
|
||||
|
||||
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, LibraryApplication.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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,161 +0,0 @@
|
|||
package com.github.axet.audiorecorder.app;
|
||||
|
||||
import android.media.AudioFormat;
|
||||
import android.util.Log;
|
||||
|
||||
import com.github.axet.audiorecorder.activities.RecordingActivity;
|
||||
|
||||
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;
|
||||
import org.apache.commons.math3.util.MathArrays;
|
||||
|
||||
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 {
|
||||
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;
|
||||
|
||||
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 * (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) {
|
||||
for (int i = 0; i < buf.length; i++) {
|
||||
write(buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public long getSamples() {
|
||||
return getSamples(in.length());
|
||||
}
|
||||
|
||||
public static long getSamples(long len) {
|
||||
return len / (AUDIO_FORMAT == AudioFormat.ENCODING_PCM_16BIT ? 2 : 1);
|
||||
}
|
||||
|
||||
public static long getBufferLen(long samples) {
|
||||
return samples * (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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
package com.github.axet.audiorecorder.app;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.AudioTrack;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Sound extends com.github.axet.androidlibrary.sound.Sound {
|
||||
|
||||
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, RawSamples.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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,284 +0,0 @@
|
|||
package com.github.axet.audiorecorder.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.audiorecorder.R;
|
||||
import com.github.axet.audiorecorder.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";
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package com.github.axet.audiorecorder.encoders;
|
||||
|
||||
public interface Encoder {
|
||||
void encode(short[] buf, int len);
|
||||
|
||||
void close();
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package com.github.axet.audiorecorder.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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
package com.github.axet.audiorecorder.encoders;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioFormat;
|
||||
import android.os.Build;
|
||||
|
||||
import com.github.axet.audiorecorder.R;
|
||||
import com.github.axet.audiorecorder.app.RawSamples;
|
||||
|
||||
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 = RawSamples.AUDIO_FORMAT == AudioFormat.ENCODING_PCM_16BIT ? 2 : 1;
|
||||
return c * rate;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
package com.github.axet.audiorecorder.encoders;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import com.github.axet.audiorecorder.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
package com.github.axet.audiorecorder.encoders;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.media.MediaCodecList;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package com.github.axet.audiorecorder.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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,187 +0,0 @@
|
|||
package com.github.axet.audiorecorder.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, 64 * 1024);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
package com.github.axet.audiorecorder.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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
package com.github.axet.audiorecorder.encoders;
|
||||
|
||||
// based on http://soundfile.sapp.org/doc/WaveFormat/
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,182 +0,0 @@
|
|||
package com.github.axet.audiorecorder.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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -68,7 +68,6 @@ public class RecordingService extends Service {
|
|||
public void onCreate() {
|
||||
super.onCreate();
|
||||
Log.d(TAG, "onCreate");
|
||||
|
||||
receiver = new RecordingReceiver();
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(Intent.ACTION_SCREEN_ON);
|
||||
|
|
@ -139,14 +138,19 @@ public class RecordingService extends Service {
|
|||
R.layout.notifictaion_recording_light,
|
||||
R.layout.notifictaion_recording_dark));
|
||||
|
||||
String title = getString(R.string.recording_title);
|
||||
String text = ".../" + targetFile;
|
||||
|
||||
view.setOnClickPendingIntent(R.id.status_bar_latest_event_content, main);
|
||||
view.setTextViewText(R.id.notification_text, ".../" + targetFile);
|
||||
view.setTextViewText(R.id.notification_text, text);
|
||||
view.setOnClickPendingIntent(R.id.notification_pause, pe);
|
||||
view.setImageViewResource(R.id.notification_pause, !recording ? R.drawable.play : R.drawable.pause);
|
||||
view.setImageViewResource(R.id.notification_pause, !recording ? R.drawable.ic_play_arrow_black_24dp : R.drawable.ic_pause_black_24dp);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
|
||||
.setOngoing(true)
|
||||
.setContentTitle(getString(R.string.recording_title))
|
||||
.setContentTitle(title)
|
||||
.setContentText(text)
|
||||
.setTicker(title)
|
||||
.setSmallIcon(R.drawable.ic_mic_24dp)
|
||||
.setContent(view);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,106 +0,0 @@
|
|||
package com.github.axet.audiorecorder.widgets;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
|
||||
import com.github.axet.androidlibrary.widgets.ThemeUtils;
|
||||
import com.github.axet.audiorecorder.app.RawSamples;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
package com.github.axet.audiorecorder.widgets;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
|
||||
import com.github.axet.audiorecorder.app.RawSamples;
|
||||
|
||||
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 = (RawSamples.MAXIMUM_DB + v) / RawSamples.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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
package com.github.axet.audiorecorder.widgets;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
|
||||
import com.github.axet.androidlibrary.widgets.ThemeUtils;
|
||||
import com.github.axet.audiorecorder.app.RawSamples;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,693 +0,0 @@
|
|||
package com.github.axet.audiorecorder.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.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.github.axet.androidlibrary.widgets.ThemeUtils;
|
||||
import com.github.axet.audiorecorder.R;
|
||||
import com.github.axet.audiorecorder.app.RawSamples;
|
||||
|
||||
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) / RawSamples.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) * RawSamples.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 = RawSamples.MAXIMUM_DB + db;
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
public double filterDB(int i) {
|
||||
double db = getDB(i);
|
||||
|
||||
// do not show below NOISE_DB
|
||||
db = db - RawSamples.NOISE_DB;
|
||||
|
||||
if (db < 0)
|
||||
db = 0;
|
||||
|
||||
int rest = RawSamples.MAXIMUM_DB - RawSamples.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 376 B |
|
Before Width: | Height: | Size: 321 B |
|
Before Width: | Height: | Size: 233 B |
|
Before Width: | Height: | Size: 368 B |
|
Before Width: | Height: | Size: 149 B |
|
Before Width: | Height: | Size: 441 B |
|
Before Width: | Height: | Size: 513 B |
|
Before Width: | Height: | Size: 296 B |
|
Before Width: | Height: | Size: 222 B |
|
Before Width: | Height: | Size: 182 B |
|
Before Width: | Height: | Size: 250 B |
|
Before Width: | Height: | Size: 127 B |
|
Before Width: | Height: | Size: 295 B |
|
Before Width: | Height: | Size: 349 B |
|
Before Width: | Height: | Size: 415 B |
|
Before Width: | Height: | Size: 412 B |
|
Before Width: | Height: | Size: 278 B |
|
Before Width: | Height: | Size: 467 B |
|
Before Width: | Height: | Size: 164 B |
|
Before Width: | Height: | Size: 552 B |
|
Before Width: | Height: | Size: 624 B |
|
Before Width: | Height: | Size: 686 B |
|
Before Width: | Height: | Size: 579 B |
|
Before Width: | Height: | Size: 383 B |
|
Before Width: | Height: | Size: 669 B |
|
Before Width: | Height: | Size: 226 B |
|
Before Width: | Height: | Size: 893 B |
|
Before Width: | Height: | Size: 976 B |
|
Before Width: | Height: | Size: 766 B |
|
Before Width: | Height: | Size: 497 B |
|
Before Width: | Height: | Size: 875 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
|
@ -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>
|
||||
|
|
@ -4,6 +4,6 @@
|
|||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFffffff"
|
||||
android:pathData="M6,19h4V5H6v14zm8,-14v14h4V5h-4z"/>
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
|
||||
</vector>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<com.github.axet.audiorecorder.widgets.PitchView
|
||||
<com.github.axet.audiolibrary.widgets.PitchView
|
||||
android:id="@+id/recording_pitch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="120dp"
|
||||
|
|
@ -65,7 +65,7 @@
|
|||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:background="?attr/roundButton"
|
||||
android:src="@drawable/play" />
|
||||
android:src="@drawable/ic_play_arrow_black_24dp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/recording_edit_done"
|
||||
|
|
@ -110,14 +110,15 @@
|
|||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:background="?attr/roundButton"
|
||||
android:src="@drawable/ic_pause_24dp" />
|
||||
android:src="@drawable/ic_pause_black_24dp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/recording_done"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:background="?attr/roundButton"
|
||||
android:src="@drawable/ic_done" />
|
||||
android:tint="@android:color/white"
|
||||
android:src="@drawable/ic_done_black_24dp" />
|
||||
|
||||
</com.github.axet.androidlibrary.widgets.EqualLinearLayout>
|
||||
</LinearLayout>
|
||||
|
|
|
|||
|
|
@ -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="Audio Recorder"
|
||||
android:textColor="?android:attr/colorForeground" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notification_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Audio Recorder"
|
||||
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/pause" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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/play"
|
||||
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/share"
|
||||
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/trash"
|
||||
android:tint="?attr/colorAccent" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
|
@ -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>
|
||||
|
|
@ -2,14 +2,14 @@
|
|||
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_show_folder"
|
||||
android:orderInCategory="100"
|
||||
android:title="@string/open_recording_folder"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:orderInCategory="100"
|
||||
android:title="@string/action_settings"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/action_show_folder"
|
||||
android:orderInCategory="100"
|
||||
android:title="@string/open_recording_folder"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
||||
|
|
|
|||
|
|
@ -16,26 +16,14 @@
|
|||
<item>8 kHz (телефон)</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="encodings_text">
|
||||
<item>.wav (по умолчанию)</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="channels_text">
|
||||
<item>Моно (по умолчанию)</item>
|
||||
<item>Стерео</item>
|
||||
</string-array>
|
||||
|
||||
<string name="title_activity_main">Аудио Рекордер</string>
|
||||
<string name="action_settings">Настройки</string>
|
||||
<string name="not_permitted">Доступ запрещен</string>
|
||||
<string name="no_folder_app">Программа для просмотра папок не установлена</string>
|
||||
<string name="file_not_found">Файл не найден</string>
|
||||
<string name="shared_via">"Создано с помощью: %1$s"</string>
|
||||
<string name="rename_recording">Переименовать запись</string>
|
||||
<string name="no">Нет</string>
|
||||
<string name="yes">Да</string>
|
||||
<string name="are_you_sure">Вы уверены?</string>
|
||||
<string name="delete_recording">Удалить запись</string>
|
||||
<string name="hold_by_call">пауза (звонок)</string>
|
||||
<string name="encoding">кодировка</string>
|
||||
<string name="pause">пауза</string>
|
||||
|
|
@ -45,10 +33,6 @@
|
|||
<string name="recording">запись</string>
|
||||
<string name="encoding_title">Кодирование...</string>
|
||||
<string name="recording_title">Запись</string>
|
||||
<string name="db">дБ</string>
|
||||
<string name="title_header">%1$s свободно ~ %2$s</string>
|
||||
<string name="rename">Переименовать</string>
|
||||
<string name="delete">Удалить</string>
|
||||
<string name="open_recording_folder">Открыть папку с записями</string>
|
||||
<string name="recording_list_is_empty">Список записей пуст.\n\nНажмите на \'Микрофон\' чтобы начать запись.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<attr name="titleColor" format="color" />
|
||||
<attr name="secondBackground" format="color" />
|
||||
<attr name="roundButton" format="reference" />
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,3 @@
|
|||
<?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>
|
||||
|
|
|
|||
|
|
@ -21,14 +21,6 @@
|
|||
<item>8000</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="encodings_text">
|
||||
<item>.wav (default)</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="encodings_values" translatable="false">
|
||||
<item>wav</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="themes_text">
|
||||
<item>Theme White (default)</item>
|
||||
<item>Theme Dark</item>
|
||||
|
|
@ -49,17 +41,9 @@
|
|||
<item>2</item>
|
||||
</string-array>
|
||||
|
||||
<string name="title_activity_main">Audio Recorder</string>
|
||||
<string name="action_settings">Settings</string>
|
||||
<string name="not_permitted">Not permitted</string>
|
||||
<string name="no_folder_app">No folder view application installed</string>
|
||||
<string name="file_not_found">File not found</string>
|
||||
<string name="shared_via">"Shared via %1$s"</string>
|
||||
<string name="rename_recording">Rename Recording</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="hold_by_call">pause (hold by call)</string>
|
||||
<string name="encoding">encoding</string>
|
||||
<string name="pause">pause</string>
|
||||
|
|
@ -69,10 +53,6 @@
|
|||
<string name="recording">recording</string>
|
||||
<string name="encoding_title">Encoding...</string>
|
||||
<string name="recording_title">Recording</string>
|
||||
<string name="title_header">%1$s free ~ %2$s left</string>
|
||||
<string name="db">dB</string>
|
||||
<string name="rename">Rename</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="open_recording_folder">Open Recording Folder</string>
|
||||
<string name="recording_list_is_empty">Recording List is Empty\n\nClick Record to Start Recording</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,48 +1,2 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
include ':app', ':android-library', ':jebml'
|
||||
include ':app', ':android-library', ':jebml', ':android-audio-library'
|
||||