Search This Blog

Friday, March 23, 2012

Speech Jammer Android App

I saw this whacko japanese article, and decided it might be a fun Android app to build.  I'm still on my Android app jag, maybe someday soon I'll get back to hardware.

http://www2.electronicproducts.com/How_to_build_a_SpeechJammer_Gun-article-fajb_speechjammer_march2012-html.aspx
and
http://arxiv.org/abs/1202.6106v2
Japanese speech jammer gun

Basically if you hear an echo of your own voice within a certain delay range, it bugs you so much, that you stop talking.   This is a way to interrupt a speaker who is droning on and on.  I'll let you figure out when this possibly could be an appropriate action on your part, but hey, that's not my problem.  Maybe if it was your phone you could claim it was some weird accident or tech freak out.  If you have one of these ridiculous devices in the picture, I'm sure you'd find yourself kicked out of whatever venue you were in.

I saw this, and it seems like a silly nerdy project, but it seemed (wrongly) that any android device can record, delay and play back an echo with an adjustable delay.  The only question is if the processing time can be made short enough.  An Arduino could probably do it too.  There may be some minimum delay acheivable, and I may get echos.   I'll have to see.    Of course they had a directional microphone and a directional speaker which I won't have.   But my solution will be free and fun.   Depending on how it works out, I might build an Arduino version with better sound hardware.

The basic app here will record with the mic, process the sound, then play it back.  The fun will be in what processing I do.   This could be a voice changer, or a speech jammer, etc.

First I need to be able to record audio in real time.   I don't want to write audio to the SD card, I just want to fill a small buffer with it and feed it back to AudioTrack.

Took the code from the first answer in this post.  A few tweaks and it compiled.

http://stackoverflow.com/questions/4525206/android-audiorecord-class-process-live-mic-audio-quickly-set-up-callback-func


At first it didn't work, it always failed to initialize Audio recorder, until, duh, I added the permission line to the manifest to allow use of the recorder.  Then recording began to work.

        <uses-permission android:name="android.permission.RECORD_AUDIO"/>

Allright, now it looks like it's recording, no errors, and I printed the buffer size to the Log and it appears to fill.   Now I have to figure out how to access the data and delay and pass it to AudioTrack.

I duplicated the AudioIn Thread class with an AudioOut thread.  I moved the buffer up to be a global variable so both threads could talk to it.  After some horsing around with buffer sizes, I got this code to work, the audio "echo" works and sounds good.  There were some interesting results.


//private short[][]   sound_buffers  = new short[256][160]; 
private short sound[] = new short[1000];



private ToggleButton mToggle;
private TextView mTextView;
public boolean stopped = true;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        mToggle = (ToggleButton) findViewById(R.id.toggleButton1);
        mToggle.setChecked(false);
        mTextView = (TextView) findViewById(R.id.textViewEquation);
mTextView.setText("OFF");
// Hook up button presses to the appropriate event handlers.
   ((ToggleButton) findViewById(R.id.toggleButton1)).setOnClickListener(mToggleListener);
 
        
    }
    
    
    /** Called when the activity is exited */
    public void onDestroy(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        stopped = true;
mTextView.setText("OFF");
        
    }
    
    /** Called when the activity is paused. */
    public void onPause(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        stopped = true;
mTextView.setText("OFF");
        
    }
    
   



 /**

     * A call-back for when the user presses the toggle button
     */
    OnClickListener mToggleListener = new OnClickListener() {
        public void onClick(View v) {
     

    stopped = true;
    mTextView.setText("OFF");
 
        //check if light is off, if so, turn it on
        if (mToggle.isChecked()) { //turn on the light
        stopped = false;  //allows the loop in the child thread to run
        mTextView.setText("ON");
       
              try {   
              new AudioIn().start();
             } catch(Throwable x) { 
               Log.d("Record","Error reading voice audio",x);
             }
                     
              try {   
              new AudioOut().start();
             } catch(Throwable x) { 
               Log.d("Playback","Error playing voice audio",x);
             }    
                     
        }
        }
    };


    
    
    
    private class AudioIn extends Thread { 


        private AudioIn() { 
                android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
                start();
             }


        @Override
        public void run() { 
               AudioRecord recorder = null;
               int         ix       = 0;
               stopped = false;


               try { // ... initialise


                     int N = AudioRecord.getMinBufferSize(8000,AudioFormat.CHANNEL_IN_MONO,AudioFormat.ENCODING_PCM_16BIT);


                      recorder = new AudioRecord(AudioSource.MIC,
                                                 8000,
                                                 AudioFormat.CHANNEL_IN_MONO,
                                                 AudioFormat.ENCODING_PCM_16BIT,
                                                 N*2);   


                      recorder.startRecording();


                      // ... loop


                      while(!stopped) { 
                         short[] buffer = sound_buffers[ix++ % sound_buffers.length];


                         N = recorder.read(buffer,0,buffer.length);                     
                     }
                } catch(Throwable x) { 
                  Log.d("Record","Error reading voice audio",x);
                } finally { 
                  //close(recorder);
                  recorder.release();
                  stopped = true;
                }
            }


         private void close() { 
             stopped = true;
           }


       }
    
    private class AudioOut extends Thread { 


        private AudioOut() { 
                android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
                start();
             }


        @Override
        public void run() { 
        int sampleRate = 8000;
        AudioTrack player = null;
        int ix  = 0;
        int written = 0;
        stopped = false;


               try { // ... initialise
              
              int N = AudioTrack.getMinBufferSize(8000,AudioFormat.CHANNEL_OUT_MONO,AudioFormat.ENCODING_PCM_16BIT);
              //int N= 10000;
              
              player  = new AudioTrack(AudioManager.STREAM_MUSIC,
                sampleRate, AudioFormat.CHANNEL_OUT_MONO,
                AudioFormat.ENCODING_PCM_16BIT, N,
                AudioTrack.MODE_STREAM);
              
              player.play();


                      // ... loop


                      while(!stopped) { 
                     
                     short[] buffer = sound_buffers[ix++ % sound_buffers.length];


                     written = player.write(buffer, 0, buffer.length);                        
                     }
                } catch(Throwable x) { 
                  Log.d("Playback","Error playing audio",x);
                } finally { 
                  //close
                player.stop();
                stopped = true;
                }
            }


         private void close() { 
             stopped = true;
           }


       }


The interesting result is probably what you might have predicted.  You say a word.   Ten seconds later you hear it played back, which the phone hears and and plays it back again on top of the first sound ten seconds later, then again, and again getting louder each time until is is impossibly loud and you turn it off.  So the processing is going to have to do echo cancelling or only record when it's not playing.  This will require some interesting processing.  I'm not sure how the speaker phone works, or if it just is half duplex and blanks the recording while playing audio.

The delay is about 10 seconds between speaking and hearing your voice back.  That is because there is an array of buffers, providing a lot of lag.  If that is what you are going for, fine, but I wanted a short delay so I replaced the array with a single buffer.

I was a little surprised that this worked just fine.

As a global variable I defined a small buffer

private short sound[] = new short[1000];

Then in the Audio In loop above I loaded it
        recorder.read(sound,0,sound.length);

And finally in Audio out loop above I read it and played it back
        player.write(sound, 0, sound.length);


That worked, and shortened the delay, but now you HAVE to use headphones with the phone or the echos max out the phone in a few seconds. The delay seems to be half a second, and that is as short as it is going to get given that I have the buffers set to min buffer size.

You could use the directional mike and speaker, and hope you don't create feedback. There is probably echo cancelling libraries out there,  However that may be more trouble than this silly app may be worth.   So I'm going to post this dual record/playback thread code and move on to other projects.

No comments:

Post a Comment