list = new SimpleTextReader("foo.txt").asStringList();
49 | ```
50 |
51 | **Read as byte array:**
52 | Reads the content as a byte array
53 | ```
54 | byte[] bytes = new SimpleTextReader("foo.txt").asByteArray();
55 | ```
56 |
57 | **Get a Reader:**
58 | Returns a new BufferedReader for the file.
59 | ```
60 | Reader r = new SimpleTextReader("foo.txt").getReader();
61 | ```
62 |
63 | **Get a LineIterator:**
64 | This method retruns a LineIterator.
65 | ```
66 | LineIterator li = new SimpleTextReader("foo.txt").getLineIterator();
67 | while(li.hasNext())
68 | out.println(li.next().toUpperCase());
69 | li.close();
70 | ```
71 | It is suggested to do the iteration in a try-finally block and close the LineITerator silently in the finally block. An example of suggested usage is shown below:
72 | ```
73 | LineIterator li = new SimpleTextReader("foo.txt").getLineIterator();
74 | try {
75 | while(li.hasNext())
76 | out.println(li.next().toUpperCase());
77 | } finally {
78 | IOs.closeSilently(li);
79 | }
80 | ```
81 | This way input stream used in the LineITerator will be properly closed if the iteration is not done completely or interrupted.
82 |
83 | **Get an iterable reader:**
84 | It returns a IterableLineReader backed by a LineIterator. It is suitable for using in enhanced for loops. If iteration is completed, the underlying file rsources will automatically be closed.
85 | ```
86 | for(String s: new SimpleTextReader("foo.txt").getIterableReader())
87 | out.println(s);
88 | ```
89 | However, for safety, it is suggested to close the IterableLineReader in a finally block.
90 | ```
91 | IterableLineReader ilr = new SimpleTextReader("foo.txt").getIterableReader();
92 | try {
93 | for(String s: ilr)
94 | out.println(s);
95 | } finally {
96 | IOs.closeSilently(ilr);
97 | }
98 | ```
99 |
100 | **Line Count:**
101 | Counts the lines in a file. Note that it will not count lines which are ignored if a constraint is applied during SimpleTextReader creation.
102 | ```
103 | int lineCount = new SimpleTextReader("foo.txt").countLines();
104 | ```
105 |
106 | **Clone**
107 | It is possible to clone a SimpleTextReader for another input stream or file.
108 | ```
109 | public SimpleTextReader cloneForStream(InputStream is)
110 | public SimpleTextReader cloneForFile(File file)
111 | ```
112 | those methods will use the attributes of a SimpleTextReader and clone it for a provided input stream or file.
--------------------------------------------------------------------------------
/jhelper/src/main/java/com/hwangjr/jhelper/LineIterator.java:
--------------------------------------------------------------------------------
1 | package com.hwangjr.jhelper;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.Closeable;
5 | import java.io.IOException;
6 | import java.io.InputStream;
7 | import java.io.Reader;
8 | import java.util.Iterator;
9 | import java.util.NoSuchElementException;
10 |
11 | /**
12 | * An Iterator over the lines in a Reader.
13 | *
14 | * LineIterator holds a reference to an open Reader.
15 | * if there hasNext() returns false, it automatically closes the reader. if somewhat
16 | * an early return is possible iterator or the reader should be closed by calling {@link #close()} method.
17 | *
18 | * The recommended usage pattern is:
19 | *
20 | * LineIterator it = new LineIterator(Files.getReader("filename", "UTF-8"));
21 | * try {
22 | * while (it.hasNext()) {
23 | * String line = it.next();
24 | * /// do something with line
25 | * }
26 | * } finally {
27 | * it.close();
28 | * }
29 | *
30 | *
31 | * This class uses code from Apache commons io LineIterator class. however, it's behavior is slightly different.
32 | */
33 | public class LineIterator implements Iterator, Closeable {
34 |
35 | private final BufferedReader bufferedReader;
36 | /**
37 | * The current line.
38 | */
39 | private String cachedLine;
40 | /**
41 | * A flag indicating if the iterator has been fully read.
42 | */
43 | private boolean finished = false;
44 |
45 | private boolean trim = false;
46 |
47 | private Filter filters[] = new Filter[0];
48 |
49 | public LineIterator(InputStream is) {
50 | Preconditions.checkNotNull(is, "InputStream cannot be null!");
51 | this.bufferedReader = IOs.getReader(is);
52 | }
53 |
54 | public LineIterator(Reader reader) {
55 | Preconditions.checkNotNull(reader, "Reader cannot be null!");
56 | if (reader instanceof BufferedReader)
57 | this.bufferedReader = (BufferedReader) reader;
58 | else
59 | this.bufferedReader = new BufferedReader(reader);
60 | }
61 |
62 | public LineIterator(Reader reader, boolean trim, Filter... filters) {
63 | Preconditions.checkNotNull(reader, "Reader cannot be null!");
64 | if (reader instanceof BufferedReader) {
65 | this.bufferedReader = (BufferedReader) reader;
66 | } else
67 | this.bufferedReader = new BufferedReader(reader);
68 | if (filters != null && filters.length > 0)
69 | this.filters = filters;
70 | this.trim = trim;
71 | }
72 |
73 | public boolean hasNext() {
74 | if (cachedLine != null) {
75 | return true;
76 | } else if (finished) {
77 | close();
78 | return false;
79 | } else {
80 | try {
81 | String line;
82 | do {
83 | line = bufferedReader.readLine();
84 | if (line != null && trim) {
85 | line = line.trim();
86 | }
87 | }
88 | while (line != null && filters.length > 0 && !StringFilters.canPassAll(line, filters));
89 |
90 | if (line == null) {
91 | finished = true;
92 | close();
93 | return false;
94 | } else {
95 | cachedLine = line;
96 | return true;
97 | }
98 | } catch (IOException ioe) {
99 | close();
100 | throw new IllegalStateException(ioe.toString());
101 | }
102 | }
103 | }
104 |
105 | public String next() {
106 | if (!hasNext()) {
107 | close();
108 | throw new NoSuchElementException("No more lines");
109 | }
110 | String currentLine = cachedLine;
111 | cachedLine = null;
112 | return currentLine;
113 | }
114 |
115 | public void remove() {
116 | throw new UnsupportedOperationException("remove() is not implemented in LineIterator class.");
117 | }
118 |
119 | public void close() {
120 | IOs.closeSilently(bufferedReader);
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/recordhelper/src/main/java/com/hwangjr/recordhelper/video/VideoPlaybackManager.java:
--------------------------------------------------------------------------------
1 | package com.hwangjr.recordhelper.video;
2 |
3 | import android.content.Context;
4 | import android.media.MediaPlayer;
5 | import android.media.MediaPlayer.OnCompletionListener;
6 | import android.media.MediaPlayer.OnPreparedListener;
7 | import android.view.SurfaceHolder;
8 | import android.widget.MediaController;
9 | import android.widget.MediaController.MediaPlayerControl;
10 |
11 | /*
12 | * Controls video playback
13 | */
14 | public class VideoPlaybackManager implements SurfaceHolder.Callback, OnPreparedListener, MediaPlayerControl, OnCompletionListener {
15 | private MediaPlayerManager playerManager;
16 | private MediaController controller;
17 | private PlaybackHandler playbackHandler;
18 | private boolean isPlayerPrepared, isSurfaceCreated;
19 |
20 | public VideoPlaybackManager(Context ctx, AdaptiveSurfaceView videoView, PlaybackHandler playbackHandler) {
21 | videoView.getHolder().addCallback(this);
22 |
23 | this.playerManager = new MediaPlayerManager();
24 | this.playerManager.getPlayer().setOnPreparedListener(this);
25 | this.playerManager.getPlayer().setOnCompletionListener(this);
26 |
27 | this.controller = new MediaController(ctx);
28 | this.controller.setMediaPlayer(this);
29 | this.controller.setAnchorView(videoView);
30 |
31 | this.playbackHandler = playbackHandler;
32 | }
33 |
34 | public void setupPlayback(String fileName) {
35 | playerManager.setupPlayback(fileName);
36 | }
37 |
38 | public void showMediaController() {
39 | if (!controller.isEnabled()) {
40 | controller.setEnabled(true);
41 | }
42 | controller.show();
43 | }
44 |
45 | public void hideMediaController() {
46 | controller.hide();
47 | controller.setEnabled(false);
48 | }
49 |
50 | public MediaPlayerManager getPlayerManager() {
51 | return playerManager;
52 | }
53 |
54 | public void dispose() {
55 | playerManager.releasePlayer();
56 | controller = null;
57 | playbackHandler = null;
58 | }
59 |
60 | //surface holder callbacks ******************************************************************
61 |
62 | @Override
63 | public void surfaceCreated(SurfaceHolder holder) {
64 | isSurfaceCreated = true;
65 | playerManager.setDisplay(holder);
66 | if (isPlayerPrepared && isSurfaceCreated) {
67 | playbackHandler.onPreparePlayback();
68 | }
69 | }
70 |
71 | @Override
72 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
73 | }
74 |
75 | @Override
76 | public void surfaceDestroyed(SurfaceHolder holder) {
77 | playerManager.setDisplay(null);
78 | }
79 |
80 | //media player and controller callbacks *****************************************************
81 |
82 | public void onPrepared(MediaPlayer mp) {
83 | isPlayerPrepared = true;
84 | if (isPlayerPrepared && isSurfaceCreated) {
85 | playbackHandler.onPreparePlayback();
86 | }
87 | }
88 |
89 | @Override
90 | public void start() {
91 | playerManager.startPlaying();
92 | }
93 |
94 | @Override
95 | public void pause() {
96 | playerManager.pausePlaying();
97 | }
98 |
99 | @Override
100 | public void seekTo(int arg0) {
101 | playerManager.seekTo(arg0);
102 | }
103 |
104 | @Override
105 | public void onCompletion(MediaPlayer mp) {
106 | playerManager.seekTo(0);
107 | }
108 |
109 | @Override
110 | public boolean isPlaying() {
111 | return playerManager.isPlaying();
112 | }
113 |
114 | @Override
115 | public int getCurrentPosition() {
116 | return playerManager.getCurrentPosition();
117 | }
118 |
119 | @Override
120 | public int getDuration() {
121 | return playerManager.getDuration();
122 | }
123 |
124 | @Override
125 | public boolean canPause() {
126 | return true;
127 | }
128 |
129 | @Override
130 | public boolean canSeekBackward() {
131 | return true;
132 | }
133 |
134 | @Override
135 | public boolean canSeekForward() {
136 | return true;
137 | }
138 |
139 | @Override
140 | public int getAudioSessionId() {
141 | return 0;
142 | }
143 |
144 | @Override
145 | public int getBufferPercentage() {
146 | return 0;
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/recordhelper/src/main/java/com/hwangjr/recordhelper/audio/AudioPlaybackManager.java:
--------------------------------------------------------------------------------
1 | package com.hwangjr.recordhelper.audio;
2 |
3 | import android.content.Context;
4 | import android.media.MediaPlayer;
5 | import android.media.MediaPlayer.OnCompletionListener;
6 | import android.media.MediaPlayer.OnPreparedListener;
7 | import android.view.ViewTreeObserver.OnGlobalLayoutListener;
8 | import android.widget.MediaController;
9 | import android.widget.MediaController.MediaPlayerControl;
10 |
11 | import com.hwangjr.recordhelper.video.MediaPlayerManager;
12 | import com.hwangjr.recordhelper.video.PlaybackHandler;
13 | import com.hwangjr.recordhelper.visualizer.VisualizerView;
14 |
15 | /*
16 | * Controls audio playback
17 | */
18 | public class AudioPlaybackManager implements OnGlobalLayoutListener, OnPreparedListener, MediaPlayerControl, OnCompletionListener {
19 | private VisualizerView visualizerView;
20 | private MediaPlayerManager playerManager;
21 | private MediaController controller;
22 | private PlaybackHandler playbackHandler;
23 | private boolean isPlayerPrepared, isSurfaceCreated;
24 |
25 | public AudioPlaybackManager(Context ctx, VisualizerView visualizerView, PlaybackHandler playbackHandler) {
26 | this.playerManager = new MediaPlayerManager();
27 | this.playerManager.getPlayer().setOnPreparedListener(this);
28 | this.playerManager.getPlayer().setOnCompletionListener(this);
29 |
30 | this.visualizerView = visualizerView;
31 | this.visualizerView.link(this.playerManager.getPlayer());
32 | this.visualizerView.getViewTreeObserver().addOnGlobalLayoutListener(this);
33 |
34 | this.controller = new MediaController(ctx);
35 | this.controller.setMediaPlayer(this);
36 | this.controller.setAnchorView(visualizerView);
37 |
38 | this.playbackHandler = playbackHandler;
39 | }
40 |
41 | public void setupPlayback(String fileName) {
42 | playerManager.setupPlayback(fileName);
43 | }
44 |
45 | public void showMediaController() {
46 | if (!controller.isEnabled()) {
47 | controller.setEnabled(true);
48 | }
49 | controller.show();
50 | }
51 |
52 | public void hideMediaController() {
53 | controller.hide();
54 | controller.setEnabled(false);
55 | }
56 |
57 | private void releaseVisualizer() {
58 | visualizerView.release();
59 | visualizerView = null;
60 | }
61 |
62 | public void dispose() {
63 | playerManager.releasePlayer();
64 | releaseVisualizer();
65 | controller = null;
66 | playbackHandler = null;
67 | }
68 |
69 | //visualizer setup callback *****************************************************************
70 |
71 | @Override
72 | public void onGlobalLayout() {
73 | isSurfaceCreated = true;
74 | if (isPlayerPrepared && isSurfaceCreated) {
75 | playbackHandler.onPreparePlayback();
76 | }
77 | }
78 |
79 | //media player and controller callbacks *****************************************************
80 |
81 | @Override
82 | public void onPrepared(MediaPlayer mp) {
83 | isPlayerPrepared = true;
84 | if (isPlayerPrepared && isSurfaceCreated) {
85 | playbackHandler.onPreparePlayback();
86 | }
87 | }
88 |
89 | @Override
90 | public void start() {
91 | playerManager.startPlaying();
92 | }
93 |
94 | @Override
95 | public void pause() {
96 | playerManager.pausePlaying();
97 | }
98 |
99 | @Override
100 | public void seekTo(int arg0) {
101 | playerManager.seekTo(arg0);
102 | }
103 |
104 | @Override
105 | public void onCompletion(MediaPlayer mp) {
106 | playerManager.seekTo(0);
107 | }
108 |
109 | @Override
110 | public boolean isPlaying() {
111 | return playerManager.isPlaying();
112 | }
113 |
114 | @Override
115 | public int getCurrentPosition() {
116 | return playerManager.getCurrentPosition();
117 | }
118 |
119 | @Override
120 | public int getDuration() {
121 | return playerManager.getDuration();
122 | }
123 |
124 | @Override
125 | public boolean canPause() {
126 | return true;
127 | }
128 |
129 | @Override
130 | public boolean canSeekBackward() {
131 | return true;
132 | }
133 |
134 | @Override
135 | public boolean canSeekForward() {
136 | return true;
137 | }
138 |
139 | @Override
140 | public int getAudioSessionId() {
141 | return 0;
142 | }
143 |
144 | @Override
145 | public int getBufferPercentage() {
146 | return 0;
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/jhelper/docs/Strings.md:
--------------------------------------------------------------------------------
1 | # Introduction #
2 |
3 | Strings class has static helper methods for easing several String operations. some methods in this class are copied and slightly modified from Apache commons-lang library.
4 |
5 | # Details #
6 |
7 | ## Verification ##
8 | **isNullOrEmpty** checks if a string is null or emprty (zero length String).
9 | ```
10 | Strings.isNullOrEmpty(""); // true
11 | Strings.isNullOrEmpty(null); // true
12 | ```
13 |
14 | **allNullOrEmpty** checks if all the strings sent is null or empty
15 | ```
16 | Strings.allNullOrEmpty("", null, null); //true
17 | Strings.allNullOrEmpty(null, "aaa"); //false
18 | ```
19 |
20 | **hasText** returns true if there is any character in the string other than white space. if String is null, empty or consist of only white space characters, returns false
21 | ```
22 | Strings.hasText(null); // false
23 | Strings.hasText(""); // false
24 | Strings.hasText(" \t"); // false
25 | Strings.hasText("a "); // true
26 | ```
27 |
28 | **allHasText** checks if all the Strings send to the method has text in it. it fails if any is empty, null, or contain only white space.
29 | ```
30 | Strings.allHasText(" ", "hello", "world"); // false
31 | Strings.hasText("a","b"); // true
32 | ```
33 |
34 | ## Manipulation ##
35 | **leftTrim** and **rightTrim** are self explanatory. Trims the white space from the beginning or the end of the String.
36 |
37 | **containsNone** checks if string has any of the given invalid characters. if string is null, returns true.
38 |
39 | ```
40 | Strings.containsNone(null, *) // true
41 | Strings.containsNone(*, null) // true
42 | Strings.containsNone("", *) // true
43 | Strings.containsNone("ab", "") // true
44 | Strings.containsNone("ab1", "xyz") // true
45 | Strings.containsNone("abz", "xyz") // false
46 | ```
47 |
48 | **reverse** reverses the string. if null, returns null.
49 | ```
50 | Strings.reverse(null) // null
51 | Strings.reverse("hello") // "olleh"
52 | ```
53 |
54 | **insertFromLeft** inserts a string with given intervals to the string. Negative interval will result an IllegalArgumentException.
55 | ```
56 | insertFromLeft("0123456789",0,"-") // "0123456789"
57 | insertFromLeft("0123456789",1,"-") // "0-1-2-3-4-5-6-7-8-9"
58 | insertFromLeft("0123456789",2,"-") // "01-23-45-67-89"
59 | insertFromLeft("0123456789",3,"-") // "012-345-678-9"
60 | insertFromLeft("0123456789",2,"--") // "01--23--45--67--89"
61 | ```
62 |
63 | **insertFromRight** inserts a string with given interval.
64 | ```
65 | insertFromRight("0123456789",3,"-") // "0-123-456-789"
66 | insertFromRight("0123456789",3,"--") // "0--123--456--789"
67 | ```
68 |
69 | **eliminateWhiteSpaces** eliminates ALL white spaces in a string.
70 | ```
71 | eliminateWhiteSpaces("as d ") // "asd"
72 | eliminateWhiteSpaces("as \t \r \f d ") // "asd"
73 | ```
74 |
75 | **whiteSpacesToSingleSpace** reduces white space character(s) to a single "space" character.
76 |
77 | ```
78 | whiteSpacesToSingleSpace("as d ") // "as d "
79 | whiteSpacesToSingleSpace("as \t d") // "as d"
80 | ```
81 |
82 | **repeat** creates a String by repeating desired String or character.
83 |
84 | ```
85 | repeat('c', -1) // ""
86 | repeat('c', 3) // "ccc"
87 | repeat('c', 0) // ""
88 |
89 | repeat("ab", -1) // ""
90 | repeat("ab", 3) // "ababab"
91 | repeat("ab", 0) // ""
92 | ```
93 |
94 | **subStringUntilFirst** gets the sub string of a string until the first ocurrance of a given string.
95 |
96 | ```
97 | subStringUntilFirst("hello", "el") // "h"
98 | subStringUntilFirst("hellohello", "el") // "h"
99 | subStringUntilFirst("hello", "") // "hello"
100 | subStringUntilFirst("hello", "el") // ""
101 | subStringUntilFirst("", "el") // ""
102 | subStringUntilFirst(null, "el") // null
103 | ```
104 |
105 | **subStringUntilLast** gets the sub string of a string after the first ocurrance of a given string.
106 |
107 | ```
108 | subStringUntilLast("hello", "el") // "h"
109 | subStringUntilLast("hellohello", "el") // "helloh"
110 | ```
111 |
112 | **subStringAfterFirst** gets the sub string of a string until the last ocurrance of a given string.
113 |
114 | ```
115 | subStringAfterFirst("hello", "el") // "lo"
116 | subStringAfterFirst("hellohello", "el") // "lohello"
117 | ```
118 |
119 | **subStringAfterLast** gets the sub string of a string after the last ocurrance of a given string.
120 |
121 | ```
122 | subStringAfterLast("hello", "el") // "lo"
123 | subStringAfterLast("hellohello", "el") // "lo"
124 | ```
125 |
126 | **separateGrams** this is a special method. it separates a String into neighbouring characters sequences.
127 |
128 | ```
129 | separateGrams("hello", 2) // {"he", "el", "ll", "lo"}
130 | separateGrams("hello", 3) // {"hel", "ell", "llo"}
131 | ```
--------------------------------------------------------------------------------
/soundhelper/src/main/java/com/hwangjr/soundhelper/pcm/PcmAudioFormat.java:
--------------------------------------------------------------------------------
1 | package com.hwangjr.soundhelper.pcm;
2 |
3 | /**
4 | * Represents paramters for raw pcm audio sample data.
5 | * Channels represents mono or stereo data. mono=1, stereo=2
6 | */
7 | public class PcmAudioFormat {
8 |
9 | /**
10 | * Sample frequency in sample/sec.
11 | */
12 | private final int sampleRate;
13 | /**
14 | * the amount of bits representing samples.
15 | */
16 | private final int sampleSizeInBits;
17 | /**
18 | * How many bytes are required for representing samples
19 | */
20 | private final int bytesRequiredPerSample;
21 | /**
22 | * channels. For now only 1 or two channels are allowed.
23 | */
24 | private final int channels;
25 | /**
26 | * if data is represented as big endian or little endian.
27 | */
28 | protected final boolean bigEndian;
29 | /**
30 | * if data is signed or unsigned.
31 | */
32 | private final boolean signed;
33 |
34 | protected PcmAudioFormat(int sampleRate, int sampleSizeInBits, int channels, boolean bigEndian, boolean signed) {
35 |
36 | if (sampleRate < 1)
37 | throw new IllegalArgumentException("sampleRate cannot be less than one. But it is:" + sampleRate);
38 | this.sampleRate = sampleRate;
39 |
40 | if (sampleSizeInBits < 2 || sampleSizeInBits > 31) {
41 | throw new IllegalArgumentException("sampleSizeInBits must be between (including) 2-31. But it is:" + sampleSizeInBits);
42 | }
43 | this.sampleSizeInBits = sampleSizeInBits;
44 |
45 | if (channels < 1 || channels > 2) {
46 | throw new IllegalArgumentException("channels must be 1 or 2. But it is:" + channels);
47 | }
48 | this.channels = channels;
49 |
50 | this.bigEndian = bigEndian;
51 | this.signed = signed;
52 | if (sampleSizeInBits % 8 == 0)
53 | bytesRequiredPerSample = sampleSizeInBits / 8;
54 | else
55 | bytesRequiredPerSample = sampleSizeInBits / 8 + 1;
56 | }
57 |
58 | /**
59 | * This is a builder class. By default it generates little endian, mono, signed, 16 bits per sample.
60 | */
61 | public static class Builder {
62 | private int _sampleRate;
63 | private int _sampleSizeInBits = 16;
64 | private int _channels = 1;
65 | private boolean _bigEndian = false;
66 | private boolean _signed = true;
67 |
68 | public Builder(int sampleRate) {
69 | this._sampleRate = sampleRate;
70 | }
71 |
72 | public Builder channels(int channels) {
73 | this._channels = channels;
74 | return this;
75 | }
76 |
77 | public Builder bigEndian() {
78 | this._bigEndian = true;
79 | return this;
80 | }
81 |
82 | public Builder unsigned() {
83 | this._signed = false;
84 | return this;
85 | }
86 |
87 | public Builder sampleSizeInBits(int sampleSizeInBits) {
88 | this._sampleSizeInBits = sampleSizeInBits;
89 | return this;
90 | }
91 |
92 | public PcmAudioFormat build() {
93 | return new PcmAudioFormat(_sampleRate, _sampleSizeInBits, _channels, _bigEndian, _signed);
94 | }
95 | }
96 |
97 | PcmAudioFormat mono16BitSignedLittleEndian(int sampleRate) {
98 | return new PcmAudioFormat(sampleRate, 16, 1, false, true);
99 | }
100 |
101 | public int getSampleRate() {
102 | return sampleRate;
103 | }
104 |
105 | public int getChannels() {
106 | return channels;
107 | }
108 |
109 | public int getSampleSizeInBits() {
110 | return sampleSizeInBits;
111 | }
112 |
113 | /**
114 | * returns the required bytes for the sample bit size. Such that, if 4 or 8 bit samples are used.
115 | * it returns 1, if 12 bit used 2 returns.
116 | *
117 | * @return required byte amount for the sample size in bits.
118 | */
119 | public int getBytePerSample() {
120 | return bytesRequiredPerSample;
121 | }
122 |
123 | public boolean isBigEndian() {
124 | return bigEndian;
125 | }
126 |
127 | public boolean isSigned() {
128 | return signed;
129 | }
130 |
131 | public int sampleCountForMiliseconds(double miliseconds) {
132 | return (int) ((double) sampleRate * miliseconds / 1000d);
133 | }
134 |
135 | public String toString() {
136 | return "[ Sample Rate:" + sampleRate + " , SampleSizeInBits:" + sampleSizeInBits +
137 | ", channels:" + channels + ", signed:" + signed + ", bigEndian:" + bigEndian + " ]";
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/jhelper/docs/IOs.md:
--------------------------------------------------------------------------------
1 | IoOperations are handled by IOs class
2 |
3 | # Introduction #
4 |
5 | IOs class provides many IO related operation with static methods. Methods in the IOs class are lower level than other high level helpers such as SimpleTextReader, SimpleTextWriter.
6 |
7 | # Details #
8 |
9 | **Silent close operations**
10 | In Java, many times in finally blocks we will need to close resources. If those close operations are throwing checked exceptions, it makes the code look cluttered. For making the code cleaner, closeSilently(Closeable... closeables) method can be used. For exmaple:
11 |
12 | ```
13 | InputStream is = ...
14 | OutputStream os = ...
15 | try{
16 | ....
17 | } finally {
18 | IOs.closeSilently(is, os);
19 | }
20 | ```
21 |
22 | this method will accept zero or more, null or non-null Closeable objects. if an exception occurs during close, it will only produce a System.err(..) output.
23 |
24 | **Copy Streams** Can copy an input stream to a given output stream. It keeps the output stream open once the operation is finished. For this operation, SimpleFileWriter class may be preferred because it can close the streams by default.
25 | ```
26 | URL url= new URL("http://www.google.com");
27 | IOs.copy(url.openStream(), new FileOutputStream("google.txt"));
28 | ```
29 |
30 | ```
31 | String readAsString(BufferedReader reader)
32 | ```
33 | reads content from a BufferedReader to a single string.
34 |
35 | ```
36 | List readAsStringList(BufferedReader reader)
37 | ```
38 | reads content from a BufferedReader to a String List.
39 |
40 | ```
41 | List readAsStringList(BufferedReader reader, boolean trim, Filter... filters)
42 | ```
43 | reads content from BufferedReader to a string list. It applieas trimming or input Filters to the input.
44 |
45 | ```
46 | BufferedReader getReader(InputStream is)
47 | ```
48 | gets a BufferedReader from a stream.
49 |
50 | ```
51 | BufferedReader getReader(InputStream is, String charset)
52 | ```
53 | gets a BufferedReader from a stream using the charset. However, if charset is UTF-8 it checks explicitly if it contains optional UTF-8 BOM data. if it exist, it will skip those bytes. Java libraries by default assumes UTF-8 BOM data does not exist.
54 |
55 | ```
56 | BufferedWriter getWriter(OutputStream os)
57 | ```
58 | gets a BufferedReader for an output stream.
59 |
60 | ```
61 | BufferedWriter getWriter(OutputStream os, String charset)
62 | ```
63 | returns a BufferedWriter for the output stream.
64 |
65 | ```
66 | IterableLineReader getIterableReader(InputStream is)
67 | ```
68 | gets an IterableReader from an input stream. IterableReaders are suitable for using in while loops. it uses default character encoding.
69 |
70 | ```
71 | LineIterator getLineIterator(InputStream is)
72 | ```
73 | gets a LineIterator from an input stream. LineIterators are suitable using in enhanced For loops. it uses default character encoding.
74 |
75 | ```
76 | IterableLineReader getIterableReader(InputStream is, String charset)
77 | ```
78 | Same as above but for a given character encoding.
79 |
80 | ```
81 | LineIterator getLineIterator(InputStream is, String charset)
82 | ```
83 | same as above. but for a given character encoding.
84 |
85 | ```
86 | void copy(InputStream is, OutputStream os)
87 | ```
88 | copies one stream to another. it closes both stream after copy operation is finished.
89 |
90 | ```
91 | boolean contentEquals(InputStream is1, InputStream is2)
92 | ```
93 | checks two input streams content is exactly equal. it closes both streams after the check.
94 |
95 | ```
96 | byte[] calculateMD5(InputStream is)
97 | ```
98 | calculates MD5 hash value of an input stream.
99 |
100 | ```
101 | boolean containsUTF8Bom(InputStream is)
102 | ```
103 | checks if stream's beginning contains UTF-8 BOM data.
104 |
105 | ```
106 | byte[] readAsByteArray(InputStream is)
107 | ```
108 | reads a stream as byte array.
109 |
110 | ```
111 | void writeLines(Collection lines, OutputStream output)
112 | ```
113 | writes a collection of a string to an output String. output stream will not be closed after the write operation.
114 |
115 | ```
116 | void writeLines(Collection lines, OutputStream output, String encoding)
117 | ```
118 | same as above but for a given character encoding.
119 |
120 | ```
121 | void writeString(String s, OutputStream output)
122 | ```
123 | Writes a string to an output stream.
124 |
125 | ```
126 | void writeString(String s, OutputStream output, String encoding)
127 | ```
128 | Writes a string to an output stream with given encoding.
129 |
130 | ```
131 | InputStream getClassPathResourceAsStream(String resource)
132 | ```
133 | gets a classpath resource as an input stream.
134 |
135 | ```
136 | void writeToStringLines( Collection> lines, OutputStream os, String encoding)
137 | ```
138 | writes toString values for a Collections of items to an output stream using the given encoding.
139 |
140 | ```
141 | hexDump(InputStream is, OutputStream os, int column, long amount)
142 | ```
143 | creates hex values for the bytes of an input stream and writes them 'amount' of them together as a line to an output stream.
--------------------------------------------------------------------------------
/jhelper/src/main/java/com/hwangjr/jhelper/Doubles.java:
--------------------------------------------------------------------------------
1 | package com.hwangjr.jhelper;
2 |
3 | import static java.lang.Math.abs;
4 |
5 | public class Doubles {
6 |
7 | /**
8 | * finds the maximum value of an array.
9 | *
10 | * @param darray input array
11 | * @return maximum value.
12 | * @throws IllegalArgumentException if array is empty or null.
13 | */
14 | public static double max(double... darray) {
15 | validateArray(darray);
16 | double max = darray[0];
17 | for (int i = 1; i < darray.length; i++) {
18 | if (darray[i] > max)
19 | max = darray[i];
20 | }
21 | return max;
22 | }
23 |
24 | public static double min(double... darray) {
25 | validateArray(darray);
26 | double min = darray[0];
27 | for (int i = 1; i < darray.length; i++) {
28 | if (darray[i] > min)
29 | min = darray[i];
30 | }
31 | return min;
32 | }
33 |
34 | private static void validateArray(double... darray) {
35 | if (darray == null) {
36 | throw new IllegalArgumentException("array is null!");
37 | } else if (darray.length == 0)
38 | throw new IllegalArgumentException("array is empty!");
39 | }
40 |
41 | public static double maxIndex(double... darray) {
42 | validateArray(darray);
43 | double max = darray[0];
44 | int index = 0;
45 | for (int i = 1; i < darray.length; i++) {
46 | if (darray[i] > max) {
47 | max = darray[i];
48 | index = i;
49 | }
50 | }
51 | return index;
52 | }
53 |
54 | public static int minIndex(double... darray) {
55 | validateArray(darray);
56 | double min = darray[0];
57 | int minIndex = 0;
58 | for (int i = 1; i < darray.length; i++) {
59 | if (darray[i] > min) {
60 | min = darray[i];
61 | minIndex = i;
62 | }
63 | }
64 | return minIndex;
65 | }
66 |
67 | public static double sum(double... darray) {
68 | double sum = 0;
69 | for (double v : darray) {
70 | sum += v;
71 | }
72 | return sum;
73 | }
74 |
75 | public static double[] sum(double[] a1, double[] a2) {
76 | validate(a1, a2);
77 | double[] diff = new double[a1.length];
78 | for (int i = 0; i < a1.length; i++) {
79 | diff[i] = a1[i] - a2[i];
80 | }
81 | return diff;
82 | }
83 |
84 | public static double[] substract(double[] a1, double[] a2) {
85 | validate(a1, a2);
86 | double[] diff = new double[a1.length];
87 | for (int i = 0; i < a1.length; i++) {
88 | diff[i] = a1[i] - a2[i];
89 | }
90 | return diff;
91 | }
92 |
93 | public static double[] multiply(double[] a1, double[] a2) {
94 | validate(a1, a2);
95 | double[] mul = new double[a1.length];
96 | for (int i = 0; i < a1.length; i++) {
97 | mul[i] = a1[i] * a2[i];
98 | }
99 | return mul;
100 | }
101 |
102 | public static void multiplyInPlace(double[] a1, double[] a2) {
103 | validate(a1, a2);
104 | for (int i = 0; i < a1.length; i++) {
105 | a1[i] = a1[i] * a2[i];
106 | }
107 | }
108 |
109 | public static double[] multiply(double[] a1, double b) {
110 | validateArray(a1);
111 | double[] mul = new double[a1.length];
112 | for (int i = 0; i < a1.length; i++) {
113 | mul[i] = a1[i] * b;
114 | }
115 | return mul;
116 | }
117 |
118 | public static void multiplyInPlace(double[] a1, double b) {
119 | validateArray(a1);
120 | for (int i = 0; i < a1.length; i++) {
121 | a1[i] = a1[i] * b;
122 | }
123 | }
124 |
125 | public static double mean(double... darray) {
126 | return sum(darray) / darray.length;
127 | }
128 |
129 | public double absoluteSumOfDifferences(double[] a1, double[] a2) {
130 | validate(a1, a2);
131 | double sum = 0;
132 | for (int i = 0; i < a1.length; i++) {
133 | sum += abs(a1[i] - a2[i]);
134 | }
135 | return sum;
136 | }
137 |
138 | public static double[] absoluteDifference(double[] a1, double[] a2) {
139 | validate(a1, a2);
140 | double[] diff = new double[a1.length];
141 | for (int i = 0; i < a1.length; i++) {
142 | diff[i] += abs(a1[i] - a2[i]);
143 | }
144 | return diff;
145 | }
146 |
147 | private static void validate(double[] a1, double[] a2) {
148 | if (a1 == null)
149 | throw new NullPointerException("first array is null!");
150 | if (a2 == null)
151 | throw new NullPointerException("second array is null!");
152 | if (a1.length != a2.length)
153 | throw new IllegalArgumentException("Array sizes must be equal. But, first:"
154 | + a1.length + ", and second:" + a2.length);
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/soundhelper/src/main/java/com/hwangjr/soundhelper/pcm/RiffHeaderData.java:
--------------------------------------------------------------------------------
1 | package com.hwangjr.soundhelper.pcm;
2 |
3 | import com.hwangjr.jhelper.IOs;
4 |
5 | import java.io.ByteArrayOutputStream;
6 | import java.io.DataInputStream;
7 | import java.io.File;
8 | import java.io.FileInputStream;
9 | import java.io.IOException;
10 |
11 | import static com.hwangjr.jhelper.Bytes.toByteArray;
12 | import static com.hwangjr.jhelper.Bytes.toInt;
13 |
14 | class RiffHeaderData {
15 |
16 | public static final int PCM_RIFF_HEADER_SIZE = 44;
17 | public static final int RIFF_CHUNK_SIZE_INDEX = 4;
18 | public static final int RIFF_SUBCHUNK2_SIZE_INDEX = 40;
19 |
20 | private final PcmAudioFormat format;
21 | private final int totalSamplesInByte;
22 |
23 | public RiffHeaderData(PcmAudioFormat format, int totalSamplesInByte) {
24 | this.format = format;
25 | this.totalSamplesInByte = totalSamplesInByte;
26 | }
27 |
28 | public double timeSeconds() {
29 | return (double) totalSamplesInByte / format.getBytePerSample() / format.getSampleRate();
30 | }
31 |
32 | public RiffHeaderData(DataInputStream dis) throws IOException {
33 |
34 | try {
35 | byte[] buf4 = new byte[4];
36 | byte[] buf2 = new byte[2];
37 |
38 | dis.skipBytes(4 + 4 + 4 + 4 + 4 + 2);
39 |
40 | dis.readFully(buf2);
41 | final int channels = toInt(buf2, false);
42 |
43 | dis.readFully(buf4);
44 | final int sampleRate = toInt(buf4, false);
45 |
46 | dis.skipBytes(4 + 2);
47 |
48 | dis.readFully(buf2);
49 | final int sampleSizeInBits = toInt(buf2, false);
50 |
51 | dis.skipBytes(4);
52 |
53 | dis.readFully(buf4);
54 | totalSamplesInByte = toInt(buf4, false);
55 |
56 | format = new WavAudioFormat.Builder().
57 | channels(channels).
58 | sampleRate(sampleRate).
59 | sampleSizeInBits(sampleSizeInBits).
60 | build();
61 | } finally {
62 | IOs.closeSilently(dis);
63 | }
64 | }
65 |
66 | public RiffHeaderData(File file) throws IOException {
67 | this(new DataInputStream(new FileInputStream(file)));
68 | }
69 |
70 | public byte[] asByteArray() {
71 | ByteArrayOutputStream baos = null;
72 | try {
73 | baos = new ByteArrayOutputStream();
74 | // ChunkID (the String "RIFF") 4 Bytes
75 | baos.write(toByteArray(0x52494646, true));
76 | // ChunkSize (Whole file size in byte minus 8 bytes ) , or (4 + (8 + SubChunk1Size) + (8 + SubChunk2Size))
77 | // little endian 4 Bytes.
78 | baos.write(toByteArray(36 + totalSamplesInByte, false));
79 | // Format (the String "WAVE") 4 Bytes big endian
80 | baos.write(toByteArray(0x57415645, true));
81 |
82 | // Subchunk1
83 | // Subchunk1ID (the String "fmt ") 4 bytes big endian.
84 | baos.write(toByteArray(0x666d7420, true));
85 | // Subchunk1Size. 16 for the PCM. little endian 4 bytes.
86 | baos.write(toByteArray(16, false));
87 | // AudioFormat , for PCM = 1, Little endian 2 Bytes.
88 | baos.write(toByteArray((short) 1, false));
89 | // Number of channels Mono = 1, Stereo = 2 Little Endian , 2 bytes.
90 | int channels = format.getChannels();
91 | baos.write(toByteArray((short) channels, false));
92 | // SampleRate (8000, 44100 etc.) little endian, 4 bytes
93 | int sampleRate = format.getSampleRate();
94 | baos.write(toByteArray(sampleRate, false));
95 | // byte rate (SampleRate * NumChannels * BitsPerSample/8) little endian, 4 bytes.
96 | baos.write(toByteArray(channels * sampleRate * format.getBytePerSample(), false));
97 | // Block Allign == NumChannels * BitsPerSample/8 The number of bytes for one sample including all channels. LE, 2 bytes
98 | baos.write(toByteArray((short) (channels * format.getBytePerSample()), false));
99 | // BitsPerSample (8, 16 etc.) LE, 2 bytes
100 | baos.write(toByteArray((short) format.getSampleSizeInBits(), false));
101 |
102 | // Subchunk2
103 | // SubChunk2ID (String "data") 4 bytes.
104 | baos.write(toByteArray(0x64617461, true));
105 | // Subchunk2Size == NumSamples * NumChannels * BitsPerSample/8. This is the number of bytes in the data.
106 | // You can also think of this as the size of the read of the subchunk following this number. LE, 4 bytes.
107 | baos.write(toByteArray(totalSamplesInByte, false));
108 |
109 | return baos.toByteArray();
110 | } catch (IOException e) {
111 | e.printStackTrace();
112 | return new byte[0];
113 | } finally {
114 | IOs.closeSilently(baos);
115 | }
116 | }
117 |
118 | public PcmAudioFormat getFormat() {
119 | return format;
120 | }
121 |
122 | public int getTotalSamplesInByte() {
123 | return totalSamplesInByte;
124 | }
125 |
126 | public int getSampleCount() {
127 | return totalSamplesInByte / format.getBytePerSample();
128 | }
129 |
130 | public String toString() {
131 | return "[ Format: " + format.toString() + " , totalSamplesInByte:" + totalSamplesInByte + "]";
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hwangjr/recordhelper/app/AudioRecordingActivity.java:
--------------------------------------------------------------------------------
1 | package com.hwangjr.recordhelper.app;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.os.Bundle;
8 | import android.view.View;
9 | import android.view.View.OnClickListener;
10 | import android.widget.Button;
11 |
12 | import com.hwangjr.recordhelper.app.utils.NotificationUtils;
13 | import com.hwangjr.recordhelper.app.utils.StorageUtils;
14 | import com.hwangjr.recordhelper.audio.AudioRecordingHandler;
15 | import com.hwangjr.recordhelper.audio.AudioRecordingThread;
16 | import com.hwangjr.recordhelper.visualizer.VisualizerView;
17 | import com.hwangjr.recordhelper.visualizer.renderer.BarGraphRenderer;
18 |
19 | public class AudioRecordingActivity extends Activity {
20 | private static String fileName = null;
21 |
22 | private Button recordBtn, playBtn;
23 | private VisualizerView visualizerView;
24 |
25 | private AudioRecordingThread recordingThread;
26 | private boolean startRecording = true;
27 |
28 | @Override
29 | protected void onCreate(Bundle savedInstanceState) {
30 | super.onCreate(savedInstanceState);
31 | setContentView(R.layout.audio_rec);
32 |
33 | if (!StorageUtils.checkExternalStorageAvailable()) {
34 | NotificationUtils.showInfoDialog(this, getString(R.string.noExtStorageAvailable));
35 | return;
36 | }
37 | fileName = StorageUtils.getFileName(true);
38 |
39 | recordBtn = (Button) findViewById(R.id.recordBtn);
40 | recordBtn.setOnClickListener(new OnClickListener() {
41 | @Override
42 | public void onClick(View v) {
43 | record();
44 | }
45 | });
46 |
47 | playBtn = (Button) findViewById(R.id.playBtn);
48 | playBtn.setOnClickListener(new OnClickListener() {
49 | @Override
50 | public void onClick(View v) {
51 | play();
52 | }
53 | });
54 |
55 | visualizerView = (VisualizerView) findViewById(R.id.visualizerView);
56 | setupVisualizer();
57 | }
58 |
59 | @Override
60 | protected void onPause() {
61 | super.onPause();
62 |
63 | recordStop();
64 | }
65 |
66 | @Override
67 | protected void onDestroy() {
68 | recordStop();
69 | releaseVisualizer();
70 |
71 | super.onDestroy();
72 | }
73 |
74 | private void setupVisualizer() {
75 | Paint paint = new Paint();
76 | paint.setStrokeWidth(5f);
77 | paint.setAntiAlias(true);
78 | paint.setColor(Color.argb(200, 227, 69, 53));
79 | BarGraphRenderer barGraphRendererBottom = new BarGraphRenderer(2, paint, false);
80 | visualizerView.addRenderer(barGraphRendererBottom);
81 | }
82 |
83 | private void releaseVisualizer() {
84 | visualizerView.release();
85 | visualizerView = null;
86 | }
87 |
88 | private void record() {
89 | if (startRecording) {
90 | recordStart();
91 | } else {
92 | recordStop();
93 | }
94 | }
95 |
96 | private void recordStart() {
97 | startRecording();
98 | startRecording = false;
99 | recordBtn.setText(R.string.stopRecordBtn);
100 | playBtn.setEnabled(false);
101 | }
102 |
103 | private void recordStop() {
104 | stopRecording();
105 | startRecording = true;
106 | recordBtn.setText(R.string.recordBtn);
107 | playBtn.setEnabled(true);
108 | }
109 |
110 | private void startRecording() {
111 | recordingThread = new AudioRecordingThread(fileName, new AudioRecordingHandler() {
112 | @Override
113 | public void onFftDataCapture(final byte[] bytes) {
114 | runOnUiThread(new Runnable() {
115 | public void run() {
116 | if (visualizerView != null) {
117 | visualizerView.updateVisualizerFFT(bytes);
118 | }
119 | }
120 | });
121 | }
122 |
123 | @Override
124 | public void onRecordSuccess() {
125 | }
126 |
127 | @Override
128 | public void onRecordingError() {
129 | runOnUiThread(new Runnable() {
130 | public void run() {
131 | recordStop();
132 | NotificationUtils.showInfoDialog(AudioRecordingActivity.this, getString(R.string.recordingError));
133 | }
134 | });
135 | }
136 |
137 | @Override
138 | public void onRecordSaveError() {
139 | runOnUiThread(new Runnable() {
140 | public void run() {
141 | recordStop();
142 | NotificationUtils.showInfoDialog(AudioRecordingActivity.this, getString(R.string.saveRecordError));
143 | }
144 | });
145 | }
146 | });
147 | recordingThread.start();
148 | }
149 |
150 | private void stopRecording() {
151 | if (recordingThread != null) {
152 | recordingThread.stopRecording();
153 | recordingThread = null;
154 | }
155 | }
156 |
157 | private void play() {
158 | Intent i = new Intent(AudioRecordingActivity.this, AudioPlaybackActivity.class);
159 | i.putExtra(VideoPlaybackActivity.FileNameArg, fileName);
160 | startActivityForResult(i, 0);
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/recordhelper/src/main/java/com/hwangjr/recordhelper/fft/Complex.java:
--------------------------------------------------------------------------------
1 | package com.hwangjr.recordhelper.fft;
2 |
3 | /*************************************************************************
4 | * Compilation: javac Complex.java
5 | * Execution: java Complex
6 | *
7 | * Data type for complex numbers.
8 | *
9 | * The data type is "immutable" so once you create and initialize
10 | * a Complex object, you cannot change it. The "final" keyword
11 | * when declaring re and im enforces this rule, making it a
12 | * compile-time error to change the .re or .im fields after
13 | * they've been initialized.
14 | *
15 | * % java Complex
16 | * a = 5.0 + 6.0i
17 | * b = -3.0 + 4.0i
18 | * Re(a) = 5.0
19 | * Im(a) = 6.0
20 | * b + a = 2.0 + 10.0i
21 | * a - b = 8.0 + 2.0i
22 | * a * b = -39.0 + 2.0i
23 | * b * a = -39.0 + 2.0i
24 | * a / b = 0.36 - 1.52i
25 | * (a / b) * b = 5.0 + 6.0i
26 | * conj(a) = 5.0 - 6.0i
27 | * |a| = 7.810249675906654
28 | * tan(a) = -6.685231390246571E-6 + 1.0000103108981198i
29 | *************************************************************************/
30 |
31 | public class Complex {
32 | private final double re; // the real part
33 | private final double im; // the imaginary part
34 |
35 | // create a new object with the given real and imaginary parts
36 | public Complex(double real, double imag) {
37 | re = real;
38 | im = imag;
39 | }
40 |
41 | // return a string representation of the invoking Complex object
42 | public String toString() {
43 | if (im == 0) return re + "";
44 | if (re == 0) return im + "i";
45 | if (im < 0) return re + " - " + (-im) + "i";
46 | return re + " + " + im + "i";
47 | }
48 |
49 | // return abs/modulus/magnitude and angle/phase/argument
50 | public double abs() {
51 | return Math.hypot(re, im);
52 | } // Math.sqrt(re*re + im*im)
53 |
54 | public double phase() {
55 | return Math.atan2(im, re);
56 | } // between -pi and pi
57 |
58 | // return a new Complex object whose value is (this + b)
59 | public Complex plus(Complex b) {
60 | Complex a = this; // invoking object
61 | double real = a.re + b.re;
62 | double imag = a.im + b.im;
63 | return new Complex(real, imag);
64 | }
65 |
66 | // return a new Complex object whose value is (this - b)
67 | public Complex minus(Complex b) {
68 | Complex a = this;
69 | double real = a.re - b.re;
70 | double imag = a.im - b.im;
71 | return new Complex(real, imag);
72 | }
73 |
74 | // return a new Complex object whose value is (this * b)
75 | public Complex times(Complex b) {
76 | Complex a = this;
77 | double real = a.re * b.re - a.im * b.im;
78 | double imag = a.re * b.im + a.im * b.re;
79 | return new Complex(real, imag);
80 | }
81 |
82 | // scalar multiplication
83 | // return a new object whose value is (this * alpha)
84 | public Complex times(double alpha) {
85 | return new Complex(alpha * re, alpha * im);
86 | }
87 |
88 | // return a new Complex object whose value is the conjugate of this
89 | public Complex conjugate() {
90 | return new Complex(re, -im);
91 | }
92 |
93 | // return a new Complex object whose value is the reciprocal of this
94 | public Complex reciprocal() {
95 | double scale = re * re + im * im;
96 | return new Complex(re / scale, -im / scale);
97 | }
98 |
99 | // return the real or imaginary part
100 | public double re() {
101 | return re;
102 | }
103 |
104 | public double im() {
105 | return im;
106 | }
107 |
108 | // return a / b
109 | public Complex divides(Complex b) {
110 | Complex a = this;
111 | return a.times(b.reciprocal());
112 | }
113 |
114 | // return a new Complex object whose value is the complex exponential of this
115 | public Complex exp() {
116 | return new Complex(Math.exp(re) * Math.cos(im), Math.exp(re) * Math.sin(im));
117 | }
118 |
119 | // return a new Complex object whose value is the complex sine of this
120 | public Complex sin() {
121 | return new Complex(Math.sin(re) * Math.cosh(im), Math.cos(re) * Math.sinh(im));
122 | }
123 |
124 | // return a new Complex object whose value is the complex cosine of this
125 | public Complex cos() {
126 | return new Complex(Math.cos(re) * Math.cosh(im), -Math.sin(re) * Math.sinh(im));
127 | }
128 |
129 | // return a new Complex object whose value is the complex tangent of this
130 | public Complex tan() {
131 | return sin().divides(cos());
132 | }
133 |
134 |
135 | // a static version of plus
136 | public static Complex plus(Complex a, Complex b) {
137 | double real = a.re + b.re;
138 | double imag = a.im + b.im;
139 | Complex sum = new Complex(real, imag);
140 | return sum;
141 | }
142 |
143 |
144 | // sample client for testing
145 | public static void main(String[] args) {
146 | Complex a = new Complex(5.0, 6.0);
147 | Complex b = new Complex(-3.0, 4.0);
148 |
149 | System.out.println("a = " + a);
150 | System.out.println("b = " + b);
151 | System.out.println("Re(a) = " + a.re());
152 | System.out.println("Im(a) = " + a.im());
153 | System.out.println("b + a = " + b.plus(a));
154 | System.out.println("a - b = " + a.minus(b));
155 | System.out.println("a * b = " + a.times(b));
156 | System.out.println("b * a = " + b.times(a));
157 | System.out.println("a / b = " + a.divides(b));
158 | System.out.println("(a / b) * b = " + a.divides(b).times(b));
159 | System.out.println("conj(a) = " + a.conjugate());
160 | System.out.println("|a| = " + a.abs());
161 | System.out.println("tan(a) = " + a.tan());
162 | }
163 |
164 | }
165 |
--------------------------------------------------------------------------------
/recordhelper/src/main/java/com/hwangjr/recordhelper/audio/AudioRecordingThread.java:
--------------------------------------------------------------------------------
1 | package com.hwangjr.recordhelper.audio;
2 |
3 | import android.media.AudioFormat;
4 | import android.media.AudioRecord;
5 | import android.media.MediaRecorder.AudioSource;
6 |
7 | import com.hwangjr.recordhelper.fft.Complex;
8 | import com.hwangjr.recordhelper.fft.FFT;
9 | import com.hwangjr.soundhelper.pcm.PcmAudioHelper;
10 | import com.hwangjr.soundhelper.pcm.WavAudioFormat;
11 |
12 | import java.io.File;
13 | import java.io.FileNotFoundException;
14 | import java.io.FileOutputStream;
15 | import java.io.IOException;
16 |
17 | /*
18 | * Takes a portion of PCM encoded audio signal (from microphone while recording),
19 | * transforms it using FFT, passes it to a visualizer and saves to a file.
20 | * In the end converts stored audio from a temporary RAW file to WAV.
21 | */
22 | public class AudioRecordingThread extends Thread {
23 | private static final String FILE_NAME = "audiorecordtest.raw";
24 | private static final int SAMPLING_RATE = 44100;
25 | private static final int FFT_POINTS = 1024;
26 | private static final int MAGIC_SCALE = 10;
27 |
28 | private String fileName_wav;
29 | private String fileName_raw;
30 |
31 | private int bufferSize;
32 | private byte[] audioBuffer;
33 |
34 | private boolean isRecording = true;
35 |
36 | private AudioRecordingHandler handler = null;
37 |
38 | public AudioRecordingThread(String fileWavName, AudioRecordingHandler handler) {
39 | this.fileName_wav = fileWavName;
40 | this.fileName_raw = getRawName(fileWavName);
41 | this.handler = handler;
42 |
43 | bufferSize = AudioRecord.getMinBufferSize(SAMPLING_RATE,
44 | AudioFormat.CHANNEL_IN_MONO,
45 | AudioFormat.ENCODING_PCM_16BIT);
46 | audioBuffer = new byte[bufferSize];
47 | }
48 |
49 | @Override
50 | public void run() {
51 | FileOutputStream out = prepareWriting();
52 | if (out == null) {
53 | return;
54 | }
55 |
56 | AudioRecord record = new AudioRecord(AudioSource.VOICE_RECOGNITION, /*AudioSource.MIC*/
57 | SAMPLING_RATE,
58 | AudioFormat.CHANNEL_IN_MONO,
59 | AudioFormat.ENCODING_PCM_16BIT,
60 | bufferSize);
61 | record.startRecording();
62 |
63 | int read = 0;
64 | while (isRecording) {
65 | read = record.read(audioBuffer, 0, bufferSize);
66 |
67 | if ((read == AudioRecord.ERROR_INVALID_OPERATION) ||
68 | (read == AudioRecord.ERROR_BAD_VALUE) ||
69 | (read <= 0)) {
70 | continue;
71 | }
72 |
73 | proceed();
74 | write(out);
75 | }
76 |
77 | record.stop();
78 | record.release();
79 |
80 | finishWriting(out);
81 | convertRawToWav();
82 | }
83 |
84 | private void proceed() {
85 | double temp;
86 | Complex[] y;
87 | Complex[] complexSignal = new Complex[FFT_POINTS];
88 |
89 | for (int i = 0; i < FFT_POINTS; i++) {
90 | temp = (double) ((audioBuffer[2 * i] & 0xFF) | (audioBuffer[2 * i + 1] << 8)) / 32768.0F;
91 | complexSignal[i] = new Complex(temp * MAGIC_SCALE, 0d);
92 | }
93 |
94 | y = FFT.fft(complexSignal);
95 |
96 | /*
97 | * See http://developer.android.com/reference/android/media/audiofx/Visualizer.html#getFft(byte[]) for format explanation
98 | */
99 |
100 | final byte[] y_byte = new byte[y.length * 2];
101 | y_byte[0] = (byte) y[0].re();
102 | y_byte[1] = (byte) y[y.length - 1].re();
103 | for (int i = 1; i < y.length - 1; i++) {
104 | y_byte[i * 2] = (byte) y[i].re();
105 | y_byte[i * 2 + 1] = (byte) y[i].im();
106 | }
107 |
108 | if (handler != null) {
109 | handler.onFftDataCapture(y_byte);
110 | }
111 | }
112 |
113 | private FileOutputStream prepareWriting() {
114 | File file = new File(fileName_raw);
115 | if (file.exists()) {
116 | file.delete();
117 | }
118 |
119 | FileOutputStream out = null;
120 | try {
121 | out = new FileOutputStream(fileName_raw, true);
122 | } catch (FileNotFoundException e) {
123 | e.printStackTrace();
124 | if (handler != null) {
125 | handler.onRecordingError();
126 | }
127 | }
128 | return out;
129 | }
130 |
131 | private void write(FileOutputStream out) {
132 | try {
133 | out.write(audioBuffer);
134 | } catch (IOException e) {
135 | e.printStackTrace();
136 | if (handler != null) {
137 | handler.onRecordingError();
138 | }
139 | }
140 | }
141 |
142 | private void finishWriting(FileOutputStream out) {
143 | try {
144 | out.close();
145 | } catch (IOException e) {
146 | e.printStackTrace();
147 | if (handler != null) {
148 | handler.onRecordingError();
149 | }
150 | }
151 | }
152 |
153 | private String getRawName(String fileWavName) {
154 | return String.format("%s/%s", getFileDir(fileWavName), FILE_NAME);
155 | }
156 |
157 | private String getFileDir(String fileWavName) {
158 | File file = new File(fileWavName);
159 | String dir = file.getParent();
160 | return (dir == null) ? "" : dir;
161 | }
162 |
163 | private void convertRawToWav() {
164 | File file_raw = new File(fileName_raw);
165 | if (!file_raw.exists()) {
166 | return;
167 | }
168 | File file_wav = new File(fileName_wav);
169 | if (file_wav.exists()) {
170 | file_wav.delete();
171 | }
172 | try {
173 | PcmAudioHelper.convertRawToWav(WavAudioFormat.mono16Bit(SAMPLING_RATE), file_raw, file_wav);
174 | file_raw.delete();
175 | if (handler != null) {
176 | handler.onRecordSuccess();
177 | }
178 | } catch (IOException e) {
179 | e.printStackTrace();
180 | if (handler != null) {
181 | handler.onRecordSaveError();
182 | }
183 | }
184 | }
185 |
186 | public synchronized void stopRecording() {
187 | isRecording = false;
188 | }
189 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | RecordHelper
2 | =============================
3 | This is a simple record library from [steelkiwi/AndroidRecording](https://github.com/steelkiwi/AndroidRecording), it seems that she don't contribute to the repo anymore.
4 |
5 | This project is build on android studio ide and explore all dependence for modules, enjoy it.
6 |
7 | Content below is the source readme, i have modify the package. if u have some issues, welcome to pull to me.
8 |
9 | Download
10 | --------
11 | ```
12 | allprojects {
13 | repositories {
14 | maven {
15 | url "https://jitpack.io"
16 | }
17 | }
18 | }
19 | ```
20 | ```
21 | dependencies {
22 | compile 'com.github.steelkiwi:AndroidRecording:master'
23 | }
24 | ```
25 |
26 | Android Recording Library
27 | =======================================
28 |
29 | Android Recording library offers convenient tools for audio/video recording and playback.
30 |
31 | For audio it uses:
32 | * AudioRecord to capture and save audio signal from microphone
33 | * MediaPlayer with MediaController to play recorded audio
34 | * custom Visualizer (like bar chart) to represent audio signal on screen while recording and during playback
35 |
36 | For video it uses:
37 | * Camera and MediaRecorder to record a video of specified resolution
38 | * MediaPlayer with MediaController to play recorded video
39 | * custom SurfaceView with adjustable size to properly display Camera preview and recorded video (in portrait and landscape modes)
40 |
41 | Record audio: how to use
42 | ------------------------
43 |
44 | 1. Setup VisualizerView
45 |
46 | ```xml
47 |
51 |
52 | ```
53 |
54 | ```java
55 | visualizerView = (VisualizerView) findViewById(R.id.visualizerView);
56 | setupVisualizer();
57 |
58 | ...
59 |
60 | private void setupVisualizer() {
61 | Paint paint = new Paint();
62 | paint.setStrokeWidth(5f); //set bar width
63 | paint.setAntiAlias(true);
64 | paint.setColor(Color.argb(200, 227, 69, 53)); //set bar color
65 | BarGraphRenderer barGraphRendererBottom = new BarGraphRenderer(2, paint, false);
66 | visualizerView.addRenderer(barGraphRendererBottom);
67 | }
68 | ```
69 |
70 | 2. Start recording thread
71 |
72 | ```java
73 | private void startRecording() {
74 | recordingThread = new AudioRecordingThread(fileName, new AudioRecordingHandler() { //pass file name where to store the recorded audio
75 | @Override
76 | public void onFftDataCapture(final byte[] bytes) {
77 | runOnUiThread(new Runnable() {
78 | public void run() {
79 | if (visualizerView != null) {
80 | visualizerView.updateVisualizerFFT(bytes); //update VisualizerView with new audio portion
81 | }
82 | }
83 | });
84 | }
85 |
86 | @Override
87 | public void onRecordSuccess() {}
88 |
89 | @Override
90 | public void onRecordingError() {}
91 |
92 | @Override
93 | public void onRecordSaveError() {}
94 | });
95 | recordingThread.start();
96 | }
97 | ```
98 |
99 | 3. When done, stop recording
100 |
101 | ```java
102 | private void stopRecording() {
103 | if (recordingThread != null) {
104 | recordingThread.stopRecording();
105 | recordingThread = null;
106 | }
107 | }
108 | ```
109 |
110 | Play audio: how to use
111 | ------------------------
112 |
113 | 1. Setup VisualizerView
114 |
115 | 2. Setup AudioPlaybackManager. It will attach MediaPlayer to a VisualizerView
116 |
117 | ```java
118 | playbackManager = new AudioPlaybackManager(this, visualizerView, playbackHandler);
119 | playbackManager.setupPlayback(fileName); //pass file name of the recorded audio
120 | ```
121 |
122 | 3. Use onscreen MediaController to play/pause/stop/rewind audio
123 |
124 |
125 | Record video: how to use
126 | ------------------------
127 |
128 | 1. Setup custom SurfaceView (AdaptiveSurfaceView)
129 |
130 | ```xml
131 |
135 | ```
136 |
137 | 2. Setup RecordingManager
138 |
139 | ```java
140 | videoView = (AdaptiveSurfaceView) findViewById(R.id.videoView);
141 | recordingManager = new VideoRecordingManager(videoView, recordingHandler); //pass reference to custom SurfaceView
142 | ```
143 |
144 | 3. Choose desired video resolution and pass it to RecordingManager, it will adjust size of AdaptiveSurfaceView to properly display Camera output
145 |
146 | ```java
147 | recordingManager.setPreviewSize(videoSize);
148 | ```
149 |
150 | 4. To start recording use
151 |
152 | ```java
153 | recordingManager.startRecording(fileName, videoSize)
154 | ```
155 |
156 | 5. To stop recording use
157 |
158 | ```java
159 | recordingManager.stopRecording()
160 | ```
161 |
162 | Play video: how to use
163 | ------------------------
164 |
165 | 1. Setup custom SurfaceView (AdaptiveSurfaceView)
166 |
167 | 2. Setup VideoPlaybackManager. It will attach MediaPlayer to a VisualizerView
168 |
169 | ```java
170 | playbackManager = new VideoPlaybackManager(this, videoView, playbackHandler);
171 | playbackManager.setupPlayback(fileName); //pass file name of the recorded video
172 | ```
173 |
174 | 3. Use onscreen MediaController to play/pause/stop/rewind video
175 |
176 |
177 | ====================================
178 |
179 | For more details, please, see the demo project.
180 |
181 | License
182 | --------
183 |
184 | Copyright 2015 HwangJR, Inc.
185 |
186 | Licensed under the Apache License, Version 2.0 (the "License");
187 | you may not use this file except in compliance with the License.
188 | You may obtain a copy of the License at
189 |
190 | http://www.apache.org/licenses/LICENSE-2.0
191 |
192 | Unless required by applicable law or agreed to in writing, software
193 | distributed under the License is distributed on an "AS IS" BASIS,
194 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
195 | See the License for the specific language governing permissions and
196 | limitations under the License.
197 |
--------------------------------------------------------------------------------
/soundhelper/src/main/java/com/hwangjr/soundhelper/pcm/PcmMonoInputStream.java:
--------------------------------------------------------------------------------
1 | package com.hwangjr.soundhelper.pcm;
2 |
3 | import com.hwangjr.jhelper.Bytes;
4 | import com.hwangjr.jhelper.IOs;
5 |
6 | import java.io.Closeable;
7 | import java.io.DataInputStream;
8 | import java.io.IOException;
9 | import java.io.InputStream;
10 |
11 | public class PcmMonoInputStream extends InputStream implements Closeable {
12 |
13 | private final PcmAudioFormat format;
14 | private final DataInputStream dis;
15 | /**
16 | * this is used for normalization.
17 | */
18 | private final int maxPositiveIntegerForSampleSize;
19 |
20 |
21 | public PcmMonoInputStream(PcmAudioFormat format, InputStream is) {
22 | if (format.getChannels() != 1)
23 | throw new IllegalArgumentException("Only mono streams are supported.");
24 | this.format = format;
25 | this.dis = new DataInputStream(is);
26 | this.maxPositiveIntegerForSampleSize = 0x7fffffff >>> (32 - format.getSampleSizeInBits());
27 | }
28 |
29 | public int read() throws IOException {
30 | return dis.read();
31 | }
32 |
33 | public int[] readSamplesAsIntArray(int amount) throws IOException {
34 | byte[] bytez = new byte[amount * format.getBytePerSample()];
35 | int readAmount = dis.read(bytez);
36 | if (readAmount == -1)
37 | return new int[0];
38 | return Bytes.toReducedBitIntArray(
39 | bytez,
40 | readAmount,
41 | format.getBytePerSample(),
42 | format.getSampleSizeInBits(),
43 | format.isBigEndian());
44 | }
45 |
46 | public int[] readAll() throws IOException {
47 | byte[] all = IOs.readAsByteArray(dis);
48 | return Bytes.toReducedBitIntArray(
49 | all,
50 | all.length,
51 | format.getBytePerSample(),
52 | format.getSampleSizeInBits(),
53 | format.isBigEndian());
54 | }
55 |
56 | private static final int BYTE_BUFFER_SIZE = 4096;
57 |
58 | /**
59 | * reads samples as byte array. if there is not enough data for the amount of samples, remaining data is returned
60 | * anyway. if the byte amount is not an order of bytes required for sample (such as 51 bytes left but 16 bit samples)
61 | * an IllegalStateException is thrown.
62 | *
63 | * @param amount amount of samples to read.
64 | * @return byte array.
65 | * @throws IOException if there is an IO error.
66 | * @throws IllegalStateException if the amount of bytes read is not an order of correct.
67 | */
68 | public byte[] readSamplesAsByteArray(int amount) throws IOException {
69 |
70 | byte[] bytez = new byte[amount * format.getBytePerSample()];
71 | int readCount = dis.read(bytez);
72 | if (readCount != bytez.length) {
73 | validateReadCount(readCount);
74 | byte[] result = new byte[readCount];
75 | System.arraycopy(bytez, 0, result, 0, readCount);
76 | return result;
77 | } else
78 | return bytez;
79 | }
80 |
81 | private void validateReadCount(int readCount) {
82 | if (readCount % format.getBytePerSample() != 0)
83 | throw new IllegalStateException("unexpected amounts of bytes read from the input stream. " +
84 | "Byte count must be an order of:" + format.getBytePerSample());
85 | }
86 |
87 | public int[] readSamplesAsIntArray(int frameStart, int frameEnd) throws IOException {
88 | skipSamples(frameStart * format.getBytePerSample());
89 | return readSamplesAsIntArray(frameEnd - frameStart);
90 | }
91 |
92 | /**
93 | * skips samples from the stream. if end of file is reached, it returns the amount that is actually skipped.
94 | *
95 | * @param skipAmount amount of samples to skip
96 | * @return actual skipped sample count.
97 | * @throws IOException if there is a problem while skipping.
98 | */
99 | public int skipSamples(int skipAmount) throws IOException {
100 | long actualSkipped = dis.skip(skipAmount * format.getBytePerSample());
101 | return (int) actualSkipped / format.getBytePerSample();
102 | }
103 |
104 | public double[] readSamplesNormalized(int amount) throws IOException {
105 | return normalize(readSamplesAsIntArray(amount));
106 | }
107 |
108 | public double[] readSamplesNormalized() throws IOException {
109 | return normalize(readAll());
110 | }
111 |
112 | private double[] normalize(int[] original) {
113 | if (original.length == 0)
114 | return new double[0];
115 | double[] normalized = new double[original.length];
116 | for (int i = 0; i < normalized.length; i++) {
117 | normalized[i] = (double) original[i] / maxPositiveIntegerForSampleSize;
118 | }
119 | return normalized;
120 | }
121 |
122 | public void close() throws IOException {
123 | dis.close();
124 | }
125 |
126 | /**
127 | * finds the byte location of a given time. if time is negative, exception is thrown.
128 | *
129 | * @param second second information
130 | * @return the byte location in the samples.
131 | */
132 | public int calculateSampleByteIndex(double second) {
133 |
134 | if (second < 0)
135 | throw new IllegalArgumentException("Time information cannot be negative.");
136 |
137 | int loc = (int) (second * format.getSampleRate() * format.getBytePerSample());
138 |
139 | //byte alignment.
140 | if (loc % format.getBytePerSample() != 0) {
141 | loc += (format.getBytePerSample() - loc % format.getBytePerSample());
142 | }
143 | return loc;
144 | }
145 |
146 | /**
147 | * calcualates the time informationn for a given sample.
148 | *
149 | * @param sampleIndex sample index.
150 | * @return approximate seconds information for the given sample.
151 | */
152 | public double calculateSampleTime(int sampleIndex) {
153 | if (sampleIndex < 0)
154 | throw new IllegalArgumentException("sampleIndex information cannot be negative:" + sampleIndex);
155 |
156 | return (double) sampleIndex / format.getSampleRate();
157 | }
158 |
159 | public PcmAudioFormat getFormat() {
160 | return format;
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hwangjr/recordhelper/app/VideoRecordingActivity.java:
--------------------------------------------------------------------------------
1 | package com.hwangjr.recordhelper.app;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.Activity;
5 | import android.content.Intent;
6 | import android.hardware.Camera.Size;
7 | import android.os.Build;
8 | import android.os.Bundle;
9 | import android.view.View;
10 | import android.view.View.OnClickListener;
11 | import android.widget.AdapterView;
12 | import android.widget.AdapterView.OnItemSelectedListener;
13 | import android.widget.Button;
14 | import android.widget.ImageButton;
15 | import android.widget.Spinner;
16 | import android.widget.Toast;
17 |
18 | import com.hwangjr.recordhelper.app.utils.NotificationUtils;
19 | import com.hwangjr.recordhelper.app.utils.StorageUtils;
20 | import com.hwangjr.recordhelper.video.AdaptiveSurfaceView;
21 | import com.hwangjr.recordhelper.video.CameraHelper;
22 | import com.hwangjr.recordhelper.video.VideoRecordingHandler;
23 | import com.hwangjr.recordhelper.video.VideoRecordingManager;
24 |
25 | import java.util.List;
26 |
27 | public class VideoRecordingActivity extends Activity {
28 | private static String fileName = null;
29 |
30 | private Button recordBtn, playBtn;
31 | private ImageButton switchBtn;
32 | private Spinner videoSizeSpinner;
33 |
34 | private Size videoSize = null;
35 | private VideoRecordingManager recordingManager;
36 |
37 | private VideoRecordingHandler recordingHandler = new VideoRecordingHandler() {
38 | @Override
39 | public boolean onPrepareRecording() {
40 | if (videoSizeSpinner == null) {
41 | initVideoSizeSpinner();
42 | return true;
43 | }
44 | return false;
45 | }
46 |
47 | @Override
48 | public Size getVideoSize() {
49 | return videoSize;
50 | }
51 |
52 | @Override
53 | public int getDisplayRotation() {
54 | return VideoRecordingActivity.this.getWindowManager().getDefaultDisplay().getRotation();
55 | }
56 | };
57 |
58 | @Override
59 | protected void onCreate(Bundle savedInstanceState) {
60 | super.onCreate(savedInstanceState);
61 | setContentView(R.layout.video_rec);
62 |
63 | if (!StorageUtils.checkExternalStorageAvailable()) {
64 | NotificationUtils.showInfoDialog(this, getString(R.string.noExtStorageAvailable));
65 | return;
66 | }
67 | fileName = StorageUtils.getFileName(false);
68 |
69 | AdaptiveSurfaceView videoView = (AdaptiveSurfaceView) findViewById(R.id.videoView);
70 | recordingManager = new VideoRecordingManager(videoView, recordingHandler);
71 |
72 | recordBtn = (Button) findViewById(R.id.recordBtn);
73 | recordBtn.setOnClickListener(new OnClickListener() {
74 | @Override
75 | public void onClick(View v) {
76 | record();
77 | }
78 | });
79 |
80 | switchBtn = (ImageButton) findViewById(R.id.switchBtn);
81 | if (recordingManager.getCameraManager().hasMultipleCameras()) {
82 | switchBtn.setOnClickListener(new OnClickListener() {
83 | @Override
84 | public void onClick(View v) {
85 | switchCamera();
86 | }
87 | });
88 | } else {
89 | switchBtn.setVisibility(View.GONE);
90 | }
91 |
92 | playBtn = (Button) findViewById(R.id.playBtn);
93 | playBtn.setOnClickListener(new OnClickListener() {
94 | @Override
95 | public void onClick(View v) {
96 | play();
97 | }
98 | });
99 | }
100 |
101 | @Override
102 | protected void onDestroy() {
103 | recordingManager.dispose();
104 | recordingHandler = null;
105 |
106 | super.onDestroy();
107 | }
108 |
109 | @SuppressLint("NewApi")
110 | private void initVideoSizeSpinner() {
111 | videoSizeSpinner = (Spinner) findViewById(R.id.videoSizeSpinner);
112 | if (Build.VERSION.SDK_INT >= 11) {
113 | List sizes = CameraHelper.getCameraSupportedVideoSizes(recordingManager.getCameraManager().getCamera());
114 | videoSizeSpinner.setAdapter(new SizeAdapter(sizes));
115 | videoSizeSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
116 | @Override
117 | public void onItemSelected(AdapterView> arg0, View arg1, int arg2, long arg3) {
118 | videoSize = (Size) arg0.getItemAtPosition(arg2);
119 | recordingManager.setPreviewSize(videoSize);
120 | }
121 |
122 | @Override
123 | public void onNothingSelected(AdapterView> arg0) {
124 | }
125 | });
126 | videoSize = (Size) videoSizeSpinner.getItemAtPosition(0);
127 | } else {
128 | videoSizeSpinner.setVisibility(View.GONE);
129 | }
130 | }
131 |
132 | @SuppressLint("NewApi")
133 | private void updateVideoSizes() {
134 | if (Build.VERSION.SDK_INT >= 11) {
135 | ((SizeAdapter) videoSizeSpinner.getAdapter()).set(CameraHelper.getCameraSupportedVideoSizes(recordingManager.getCameraManager().getCamera()));
136 | videoSizeSpinner.setSelection(0);
137 | videoSize = (Size) videoSizeSpinner.getItemAtPosition(0);
138 | recordingManager.setPreviewSize(videoSize);
139 | }
140 | }
141 |
142 | private void switchCamera() {
143 | recordingManager.getCameraManager().switchCamera();
144 | updateVideoSizes();
145 | }
146 |
147 | private void record() {
148 | if (recordingManager.stopRecording()) {
149 | recordBtn.setText(R.string.recordBtn);
150 | switchBtn.setEnabled(true);
151 | playBtn.setEnabled(true);
152 | videoSizeSpinner.setEnabled(true);
153 | } else {
154 | startRecording();
155 | }
156 | }
157 |
158 | private void startRecording() {
159 | if (recordingManager.startRecording(fileName, videoSize)) {
160 | recordBtn.setText(R.string.stopRecordBtn);
161 | switchBtn.setEnabled(false);
162 | playBtn.setEnabled(false);
163 | videoSizeSpinner.setEnabled(false);
164 | return;
165 | }
166 | Toast.makeText(this, getString(R.string.videoRecordingError), Toast.LENGTH_LONG).show();
167 | }
168 |
169 | private void play() {
170 | Intent i = new Intent(VideoRecordingActivity.this, VideoPlaybackActivity.class);
171 | i.putExtra(VideoPlaybackActivity.FileNameArg, fileName);
172 | startActivityForResult(i, 0);
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/recordhelper/src/main/java/com/hwangjr/recordhelper/fft/FFT.java:
--------------------------------------------------------------------------------
1 | package com.hwangjr.recordhelper.fft;
2 |
3 | /*************************************************************************
4 | * Compilation: javac FFT.java
5 | * Execution: java FFT N
6 | * Dependencies: Complex.java
7 | *
8 | * Compute the FFT and inverse FFT of a length N complex sequence.
9 | * Bare bones implementation that runs in O(N log N) time. Our goal
10 | * is to optimize the clarity of the code, rather than performance.
11 | *
12 | * Limitations
13 | * -----------
14 | * - assumes N is a power of 2
15 | *
16 | * - not the most memory efficient algorithm (because it uses
17 | * an object type for representing complex numbers and because
18 | * it re-allocates memory for the subarray, instead of doing
19 | * in-place or reusing a single temporary array)
20 | *************************************************************************/
21 |
22 | public class FFT {
23 |
24 | // compute the FFT of x[], assuming its length is a power of 2
25 | public static Complex[] fft(Complex[] x) {
26 | int N = x.length;
27 |
28 | // base case
29 | if (N == 1) return new Complex[]{x[0]};
30 |
31 | // radix 2 Cooley-Tukey FFT
32 | if (N % 2 != 0) {
33 | throw new RuntimeException("N is not a power of 2");
34 | }
35 |
36 | // fft of even terms
37 | Complex[] even = new Complex[N / 2];
38 | for (int k = 0; k < N / 2; k++) {
39 | even[k] = x[2 * k];
40 | }
41 | Complex[] q = fft(even);
42 |
43 | // fft of odd terms
44 | Complex[] odd = even; // reuse the array
45 | for (int k = 0; k < N / 2; k++) {
46 | odd[k] = x[2 * k + 1];
47 | }
48 | Complex[] r = fft(odd);
49 |
50 | // combine
51 | Complex[] y = new Complex[N];
52 | for (int k = 0; k < N / 2; k++) {
53 | double kth = -2 * k * Math.PI / N;
54 | Complex wk = new Complex(Math.cos(kth), Math.sin(kth));
55 | y[k] = q[k].plus(wk.times(r[k]));
56 | y[k + N / 2] = q[k].minus(wk.times(r[k]));
57 | }
58 | return y;
59 | }
60 |
61 |
62 | // compute the inverse FFT of x[], assuming its length is a power of 2
63 | public static Complex[] ifft(Complex[] x) {
64 | int N = x.length;
65 | Complex[] y = new Complex[N];
66 |
67 | // take conjugate
68 | for (int i = 0; i < N; i++) {
69 | y[i] = x[i].conjugate();
70 | }
71 |
72 | // compute forward FFT
73 | y = fft(y);
74 |
75 | // take conjugate again
76 | for (int i = 0; i < N; i++) {
77 | y[i] = y[i].conjugate();
78 | }
79 |
80 | // divide by N
81 | for (int i = 0; i < N; i++) {
82 | y[i] = y[i].times(1.0 / N);
83 | }
84 |
85 | return y;
86 |
87 | }
88 |
89 | // compute the circular convolution of x and y
90 | public static Complex[] cconvolve(Complex[] x, Complex[] y) {
91 |
92 | // should probably pad x and y with 0s so that they have same length
93 | // and are powers of 2
94 | if (x.length != y.length) {
95 | throw new RuntimeException("Dimensions don't agree");
96 | }
97 |
98 | int N = x.length;
99 |
100 | // compute FFT of each sequence
101 | Complex[] a = fft(x);
102 | Complex[] b = fft(y);
103 |
104 | // point-wise multiply
105 | Complex[] c = new Complex[N];
106 | for (int i = 0; i < N; i++) {
107 | c[i] = a[i].times(b[i]);
108 | }
109 |
110 | // compute inverse FFT
111 | return ifft(c);
112 | }
113 |
114 |
115 | // compute the linear convolution of x and y
116 | public static Complex[] convolve(Complex[] x, Complex[] y) {
117 | Complex ZERO = new Complex(0, 0);
118 |
119 | Complex[] a = new Complex[2 * x.length];
120 | for (int i = 0; i < x.length; i++) a[i] = x[i];
121 | for (int i = x.length; i < 2 * x.length; i++) a[i] = ZERO;
122 |
123 | Complex[] b = new Complex[2 * y.length];
124 | for (int i = 0; i < y.length; i++) b[i] = y[i];
125 | for (int i = y.length; i < 2 * y.length; i++) b[i] = ZERO;
126 |
127 | return cconvolve(a, b);
128 | }
129 |
130 | // display an array of Complex numbers to standard output
131 | public static void show(Complex[] x, String title) {
132 | System.out.println(title);
133 | System.out.println("-------------------");
134 | for (int i = 0; i < x.length; i++) {
135 | System.out.println(x[i]);
136 | }
137 | System.out.println();
138 | }
139 |
140 |
141 | /*********************************************************************
142 | * Test client and sample execution
143 | *
144 | * % java FFT 4
145 | * x
146 | * -------------------
147 | * -0.03480425839330703
148 | * 0.07910192950176387
149 | * 0.7233322451735928
150 | * 0.1659819820667019
151 | *
152 | * y = fft(x)
153 | * -------------------
154 | * 0.9336118983487516
155 | * -0.7581365035668999 + 0.08688005256493803i
156 | * 0.44344407521182005
157 | * -0.7581365035668999 - 0.08688005256493803i
158 | *
159 | * z = ifft(y)
160 | * -------------------
161 | * -0.03480425839330703
162 | * 0.07910192950176387 + 2.6599344570851287E-18i
163 | * 0.7233322451735928
164 | * 0.1659819820667019 - 2.6599344570851287E-18i
165 | *
166 | * c = cconvolve(x, x)
167 | * -------------------
168 | * 0.5506798633981853
169 | * 0.23461407150576394 - 4.033186818023279E-18i
170 | * -0.016542951108772352
171 | * 0.10288019294318276 + 4.033186818023279E-18i
172 | *
173 | * d = convolve(x, x)
174 | * -------------------
175 | * 0.001211336402308083 - 3.122502256758253E-17i
176 | * -0.005506167987577068 - 5.058885073636224E-17i
177 | * -0.044092969479563274 + 2.1934338938072244E-18i
178 | * 0.10288019294318276 - 3.6147323062478115E-17i
179 | * 0.5494685269958772 + 3.122502256758253E-17i
180 | * 0.240120239493341 + 4.655566391833896E-17i
181 | * 0.02755001837079092 - 2.1934338938072244E-18i
182 | * 4.01805098805014E-17i
183 | *********************************************************************/
184 |
185 | public static void main(String[] args) {
186 | int N = Integer.parseInt(args[0]);
187 | Complex[] x = new Complex[N];
188 |
189 | // original data
190 | for (int i = 0; i < N; i++) {
191 | x[i] = new Complex(i, 0);
192 | x[i] = new Complex(-2 * Math.random() + 1, 0);
193 | }
194 | show(x, "x");
195 |
196 | // FFT of original data
197 | Complex[] y = fft(x);
198 | show(y, "y = fft(x)");
199 |
200 | // take inverse FFT
201 | Complex[] z = ifft(y);
202 | show(z, "z = ifft(y)");
203 |
204 | // circular convolution of x with itself
205 | Complex[] c = cconvolve(x, x);
206 | show(c, "c = cconvolve(x, x)");
207 |
208 | // linear convolution of x with itself
209 | Complex[] d = convolve(x, x);
210 | show(d, "d = convolve(x, x)");
211 | }
212 |
213 | }
--------------------------------------------------------------------------------
/recordhelper/src/main/java/com/hwangjr/recordhelper/visualizer/VisualizerView.java:
--------------------------------------------------------------------------------
1 | package com.hwangjr.recordhelper.visualizer;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.Bitmap.Config;
6 | import android.graphics.Canvas;
7 | import android.graphics.Color;
8 | import android.graphics.Matrix;
9 | import android.graphics.Paint;
10 | import android.graphics.PorterDuff.Mode;
11 | import android.graphics.PorterDuffXfermode;
12 | import android.graphics.Rect;
13 | import android.media.MediaPlayer;
14 | import android.media.audiofx.Visualizer;
15 | import android.util.AttributeSet;
16 | import android.view.View;
17 |
18 | import com.hwangjr.recordhelper.visualizer.renderer.Renderer;
19 |
20 | import java.util.HashSet;
21 | import java.util.Set;
22 |
23 | /**
24 | * A class that draws visualizations of data received from a
25 | * {@link Visualizer.OnDataCaptureListener#onWaveFormDataCapture } and
26 | * {@link Visualizer.OnDataCaptureListener#onFftDataCapture }
27 | */
28 | public class VisualizerView extends View {
29 | public static final String TAG = "VisualizerView";
30 |
31 | private byte[] mBytes;
32 | private byte[] mFFTBytes;
33 | private AudioData mAudioData = null;
34 | private FFTData mFftData = null;
35 | private Rect mRect = new Rect();
36 | private Matrix mMatrix = new Matrix();
37 | private Visualizer mVisualizer;
38 |
39 | private Set mRenderers;
40 |
41 | private Paint mFlashPaint = new Paint();
42 | private Paint mFadePaint = new Paint();
43 |
44 | public VisualizerView(Context context, AttributeSet attrs, int defStyle) {
45 | super(context, attrs);
46 | init();
47 | }
48 |
49 | public VisualizerView(Context context, AttributeSet attrs) {
50 | this(context, attrs, 0);
51 | }
52 |
53 | public VisualizerView(Context context) {
54 | this(context, null, 0);
55 | }
56 |
57 | private void init() {
58 | mBytes = null;
59 | mFFTBytes = null;
60 |
61 | mFlashPaint.setColor(Color.argb(122, 255, 255, 255));
62 | mFadePaint.setColor(Color.argb(238, 255, 255, 255)); // Adjust alpha to change how quickly the image fades
63 | mFadePaint.setXfermode(new PorterDuffXfermode(Mode.MULTIPLY));
64 |
65 | mRenderers = new HashSet();
66 | }
67 |
68 | /**
69 | * Links the visualizer to a player
70 | *
71 | * @param player - MediaPlayer instance to link to
72 | */
73 | public void link(MediaPlayer player) {
74 | if (player == null) {
75 | throw new NullPointerException("Cannot link to null MediaPlayer");
76 | }
77 |
78 | // Create the Visualizer object and attach it to our media player.
79 | mVisualizer = new Visualizer(player.getAudioSessionId());
80 | mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);
81 |
82 | // Pass through Visualizer data to VisualizerView
83 | Visualizer.OnDataCaptureListener captureListener = new Visualizer.OnDataCaptureListener() {
84 | @Override
85 | public void onWaveFormDataCapture(Visualizer visualizer, byte[] bytes,
86 | int samplingRate) {
87 | updateVisualizer(bytes);
88 | }
89 |
90 | @Override
91 | public void onFftDataCapture(Visualizer visualizer, byte[] bytes,
92 | int samplingRate) {
93 | updateVisualizerFFT(bytes);
94 | }
95 | };
96 |
97 | mVisualizer.setDataCaptureListener(captureListener,
98 | Visualizer.getMaxCaptureRate() / 2, true, true);
99 |
100 | // Enabled Visualizer and disable when we're done with the stream
101 | mVisualizer.setEnabled(true);
102 | player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
103 | @Override
104 | public void onCompletion(MediaPlayer mediaPlayer) {
105 | mVisualizer.setEnabled(false);
106 | }
107 | });
108 | }
109 |
110 | public void addRenderer(Renderer renderer) {
111 | if (renderer != null) {
112 | mRenderers.add(renderer);
113 | }
114 | }
115 |
116 | public void clearRenderers() {
117 | mRenderers.clear();
118 | }
119 |
120 | /**
121 | * Call to release the resources used by VisualizerView. Like with the
122 | * MediaPlayer it is good practice to call this method
123 | */
124 | public void release() {
125 | if (mVisualizer != null) {
126 | mVisualizer.release();
127 | }
128 | }
129 |
130 | /**
131 | * Pass data to the visualizer. Typically this will be obtained from the
132 | * Android Visualizer.OnDataCaptureListener call back. See
133 | * {@link Visualizer.OnDataCaptureListener#onWaveFormDataCapture }
134 | *
135 | * @param bytes
136 | */
137 | public void updateVisualizer(byte[] bytes) {
138 | mBytes = bytes;
139 | invalidate();
140 | }
141 |
142 | /**
143 | * Pass FFT data to the visualizer. Typically this will be obtained from the
144 | * Android Visualizer.OnDataCaptureListener call back. See
145 | * {@link Visualizer.OnDataCaptureListener#onFftDataCapture }
146 | *
147 | * @param bytes
148 | */
149 | public void updateVisualizerFFT(byte[] bytes) {
150 | mFFTBytes = bytes;
151 | invalidate();
152 | }
153 |
154 | boolean mFlash = false;
155 |
156 | /**
157 | * Call this to make the visualizer flash. Useful for flashing at the start
158 | * of a song/loop etc...
159 | */
160 | public void flash() {
161 | mFlash = true;
162 | invalidate();
163 | }
164 |
165 | Bitmap mCanvasBitmap;
166 | Canvas mCanvas;
167 |
168 |
169 | @Override
170 | protected void onDraw(Canvas canvas) {
171 | super.onDraw(canvas);
172 |
173 | // Create canvas once we're ready to draw
174 | mRect.set(0, 0, getWidth(), getHeight());
175 |
176 | if (mCanvasBitmap == null) {
177 | mCanvasBitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Config.ARGB_8888);
178 | }
179 | if (mCanvas == null) {
180 | mCanvas = new Canvas(mCanvasBitmap);
181 | }
182 |
183 | mCanvas.drawColor(Color.TRANSPARENT);
184 |
185 | if (mBytes != null) {
186 | // Render all audio renderers
187 | if (mAudioData == null) {
188 | mAudioData = new AudioData();
189 | }
190 | mAudioData.setBytes(mBytes);
191 | for (Renderer r : mRenderers) {
192 | r.render(mCanvas, mAudioData, mRect);
193 | }
194 | }
195 |
196 | if (mFFTBytes != null) {
197 | // Render all FFT renderers
198 | if (mFftData == null) {
199 | mFftData = new FFTData();
200 | }
201 | mFftData.setBytes(mFFTBytes);
202 | for (Renderer r : mRenderers) {
203 | r.render(mCanvas, mFftData, mRect);
204 | }
205 | }
206 |
207 | // Fade out old contents
208 | mCanvas.drawPaint(mFadePaint);
209 |
210 | if (mFlash) {
211 | mFlash = false;
212 | mCanvas.drawPaint(mFlashPaint);
213 | }
214 |
215 | mMatrix.reset();
216 | canvas.drawBitmap(mCanvasBitmap, mMatrix, null);
217 | }
218 | }
--------------------------------------------------------------------------------