顯示具有 Android (English version) 標籤的文章。 顯示所有文章
顯示具有 Android (English version) 標籤的文章。 顯示所有文章

2015年4月6日 星期一

[Android] Why the sound is not playing smoothly when using AudioTrack()?

When we use AudioTrack(), if the tone is too high or too low, the problem may be caused by wrong format or wrong sample length.

What if the sound is not playing smoothly?
Especilly when we encounter the error message similar with the following:
W/AudioTrack(..): releaseBuffer() track 0x6eaf04d0 name=0x1 disabled, restarting
How should we do?

In general, it means that we feed the data too slowly.
For local file, it should not have such problem.
But for streaming source, the input data rate is not constant.
We may encounter the problem of buffer underflow sometimes.

We can either add the buffer mechanism in the network part when receiving streaming data, or can we add buffer when feeding data into AudioTrack.

Let's see the demo for second method - adding buffer for AudioTrack.

private static void queueAudioData(byte[] buf, int size) {  
 if((audioPcmBufferDataCount + size)> audioBufferSize)
 {
  return;  
 }
 if((audioPcmBufferFront + size) > audioBufferSize)
 {
  //rewind
  System.arraycopy(buf, 0, audioPCMData, audioPcmBufferFront, audioBufferSize - audioPcmBufferFront);
  audioPcmBufferDataCount += (audioBufferSize - audioPcmBufferFront);
  size -= (audioBufferSize - audioPcmBufferFront);
  audioPcmBufferFront = 0;      
 }
 System.arraycopy(buf, 0, audioPCMData, audioPcmBufferFront, size);
 audioPcmBufferFront += size;
 audioPcmBufferDataCount += size;
} 

The PCM data from audio decoder is queued to the queue buffer through queueAudioData().
After that, we write the data to AudioTrack in a stable rate.
private class playAudio extends Thread {
 @Override
 public void run() {  
  super.run();
  int len = 512;
  while(!isInterrupted()) {
   try {
    if(audioPcmBufferDataCount < len)
    {
     Thread.sleep(10);
     continue;
    }

    if((audioPcmBufferEnd + len) > audioBufferSize)
     writeSize = audioBufferSize - audioPcmBufferEnd;
    else
     writeSize = len;
    playAudioTrack.write(audioPCMData, audioPcmBufferEnd, writeSize);
    Thread.sleep(1);
    audioPcmBufferDataCount -= writeSize;
    audioPcmBufferEnd += writeSize;
    if(audioPcmBufferEnd >= audioBufferSize)
     audioPcmBufferEnd=0;
    
    if(playAudioTrack.getPlayState()!=AudioTrack.PLAYSTATE_PLAYING) {     
          playAudioTrack.play();
       }
   } catch (InterruptedException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
   }       
  }
 }
}

[Android] How to use MediaExtractor and MediaCodec ? - Audio part

In [Android] How to use MediaExtractor and MediaCodec? - Video part, we learned how to deal with the video data.
Let's check the audio part here.

Basically, we can use the same MediaExtractor to deal with video and audio.
We just need to switch the track dynamically using selectTrack().
However, it seems that we often missed the data somehow.
We will use separate MediaExtractor for video and audio individually.

The process for audio data is similar with that for video.
Audio data do not need to render to SurfaceView and we need an extra AudioTrack() for decoded PCM data.
private MediaExtractor extractorAudio;
private MediaCodec decoderAudio;

extractorAudio = new MediaExtractor();
extractorAudio.setDataSource("myTest.mp4");

for (int i = 0; i < extractorAudio.getTrackCount(); i++) {
 MediaFormat format = extractorAudio.getTrackFormat(i);
 String mime = format.getString(MediaFormat.KEY_MIME); 
 if (mime.startsWith("audio/")) {  
  audioTrack = i;  
  extractorAudio.selectTrack(audioTrack);
  formatAudio = format;        
  decoderAudio = MediaCodec.createDecoderByType(mime);
  sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
  decoderAudio.configure(format, null, null, 0);
  break;
 }
}

if (audioTrack >=0) {
 if(decoderAudio == null)
 {
  Log.e(TAG, "Can't find audio info!");
  return;
 }
 else
 {
   // create our AudioTrack instance
   int minBufferSize = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_CONFIGURATION_STEREO, AudioFormat.ENCODING_PCM_16BIT);
      int bufferSize = 4 * minBufferSize;
  playAudioTrack = new AudioTrack(
    AudioManager.STREAM_MUSIC,
    formatAudio.getInteger(MediaFormat.KEY_SAMPLE_RATE),
    AudioFormat.CHANNEL_OUT_STEREO,
    AudioFormat.ENCODING_PCM_16BIT,
    bufferSize,
    AudioTrack.MODE_STREAM
   );
  playAudioTrack.play();
  decoderAudio.start();
 }
}
Similarly, extractorAudio will find out the audio track according to MIME information.
We pass in the audio format using decoderAudio.configure(). No other parameters are necessary.

The data after decoderAudio are in PCM format.
We need AudioTrack() to play out the sound actually.

Below is the decode part:
ByteBuffer[] inputBuffersAudio=null;
ByteBuffer[] outputBuffersAudio=null;
BufferInfo infoAudio=null;


if (audioTrack >=0)
{
 inputBuffersAudio = decoderAudio.getInputBuffers();
 outputBuffersAudio = decoderAudio.getOutputBuffers();
 infoAudio = new BufferInfo();
}
boolean isEOS = false;
long startMs = System.currentTimeMillis();
long lasAudioStartMs = System.currentTimeMillis();
while (!Thread.interrupted()) { 
 if (audioTrack >=0)
 { 
  if (!isEOS) {
   int inIndex=-1;
   try {
    inIndex = decoderAudio.dequeueInputBuffer(10000);
   } catch (Exception e) {
    e.printStackTrace();    
   }
   if (inIndex >= 0) {    
    ByteBuffer buffer = inputBuffersAudio[inIndex];
    int sampleSize = extractorAudio.readSampleData(buffer, 0);
    if (sampleSize < 0) {
     
     decoderAudio.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
     buffer.clear();
     isEOS = true;
    } else {     
     decoderAudio.queueInputBuffer(inIndex, 0, sampleSize, extractorAudio.getSampleTime(), 0);
     buffer.clear();
     extractorAudio.advance();
    }
    
   }
  }

  int outIndex=-1;
  try {
   outIndex = decoderAudio.dequeueOutputBuffer(infoAudio,10000);
  } catch (Exception e) {
   e.printStackTrace();   
  }

  switch (outIndex) {
  case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
   Log.d(TAG, "INFO_OUTPUT_BUFFERS_CHANGED");
   outputBuffersAudio = decoderAudio.getOutputBuffers();
   break;
  case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
   Log.d(TAG, "New format " + decoderAudio.getOutputFormat());
   playAudioTrack.setPlaybackRate(formatAudio.getInteger(MediaFormat.KEY_SAMPLE_RATE));
   break;
  case MediaCodec.INFO_TRY_AGAIN_LATER:
   Log.d(TAG, "dequeueOutputBuffer timed out!");
   break;
  default:
   if(outIndex>=0)
   {
    ByteBuffer buffer = outputBuffersAudio[outIndex];
    byte[] chunk = new byte[infoAudio.size];
    buffer.get(chunk);
    buffer.clear();
                if(chunk.length>0){         
                 playAudioTrack.write(chunk,0,chunk.length);
                }                
    decoderAudio.releaseOutputBuffer(outIndex, false);
   }
   break;
  }

  // All decoded frames have been rendered, we can stop playing now
  if ((infoAudio.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
   Log.d(TAG, "OutputBuffer BUFFER_FLAG_END_OF_STREAM");
   break;
  }
 }
}

if (audioTrack >=0)
{
 decoderAudio.stop();
 decoderAudio.release();
 playAudioTrack.stop();
}

extractorAudio.release();

The process are similar with video part. No more explanation here.
Need to notice is that for decoderAudio.releaseOutputBuffer(outIndex, false), the second parameter need to set to false. That is, no need to render.

[Android] How to use MediaExtractor and MediaCodec? - Video part

MediaExtractor is used to separate the video data and audio data from media sources.
It can support HTTP streaming or local file.
After separating the video and audio, MediaCodec will take care of the decoding jobs.

Let's check the video part first.
For video, we need to render it after decoding.
Therefore we have to incorporate SurfaceView in our layout.

private MediaExtractor extractorVideo;
private MediaCodec decoderVideo;

extractorVideo = new MediaExtractor();
extractorVideo.setDataSource("myTest.mp4"); 

for (int i = 0; i < extractorVideo.getTrackCount(); i++) {
 MediaFormat format = extractorVideo.getTrackFormat(i);
 String mime = format.getString(MediaFormat.KEY_MIME);
 Log.d(TAG, "mime=>"+mime);
 if (mime.startsWith("video/")) {
  videoTrack = i;  
  extractorVideo.selectTrack(videoTrack);
  decoderVideo = MediaCodec.createDecoderByType(mime);
  decoderVideo.configure(format, surface, null, 0);
  break;
 }
}

if (videoTrack >=0) {
 if(decoderVideo == null)
 {
  Log.e(TAG, "Can't find video info!");
  return;
 }
 else
  decoderVideo.start();
}
extractorVideo  will find out the video track according to the MIME information.
After that, we can pass the video format and surface to render on to decoderVideo using decoderVideo.configure().
decoderVideo will take care of decoding and rendering jobs almost automatically.

Below is the decoding part:
ByteBuffer[] inputBuffersVideo=null;
ByteBuffer[] outputBuffersVideo=null;
BufferInfo infoVideo=null;

if (videoTrack >=0)
{
 inputBuffersVideo = decoderVideo.getInputBuffers();
 outputBuffersVideo = decoderVideo.getOutputBuffers();
 infoVideo = new BufferInfo();
}

boolean isEOS = false;
long startMs = System.currentTimeMillis();

while (!Thread.interrupted()) {
 if (videoTrack >=0)
 {     
  if (!isEOS) {      
   int inIndex=-1;
   try {
    inIndex = decoderVideo.dequeueInputBuffer(10000);
   } catch (Exception e) {
    e.printStackTrace();    
   }

   if (inIndex >= 0) {
    ByteBuffer buffer = inputBuffersVideo[inIndex];
    int sampleSize = extractorVideo.readSampleData(buffer, 0);
    if (sampleSize < 0) {
     // We shouldn't stop the playback at this point, just pass the EOS
     // flag to decoder, we will get it again from the dequeueOutputBuffer     
     decoderVideo.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
     buffer.clear();
     isEOS = true;
    } else {     
     long current = System.currentTimeMillis();
     decoderVideo.queueInputBuffer(inIndex, 0, sampleSize, extractorVideo.getSampleTime(), 0);     
     buffer.clear();
     extractorVideo.advance();
    }
   }
  }
  int outIndex=-1;
  try {
   outIndex = decoderVideo.dequeueOutputBuffer(infoVideo,10000);
  } catch (Exception e) {
   e.printStackTrace();   
  }
  switch (outIndex) {
  case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
   Log.d(TAG, "INFO_OUTPUT_BUFFERS_CHANGED");
   outputBuffersVideo = decoderVideo.getOutputBuffers();
   break;
  case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
   Log.d(TAG, "New format " + decoderVideo.getOutputFormat());
   break;
  case MediaCodec.INFO_TRY_AGAIN_LATER:
   Log.d(TAG, "dequeueOutputBuffer timed out!");
   break;
  default:
   if(outIndex >=0)
   {
    ByteBuffer buffer = outputBuffersVideo[outIndex];    
    buffer.clear();
    decoderVideo.releaseOutputBuffer(outIndex, true);
    // We use a very simple clock to keep the video FPS, or the video
    // playback will be too fast
    while (infoVideo.presentationTimeUs / 1000 > (System.currentTimeMillis() - startMs)) {
     try {      
      sleep(10);
     } catch (InterruptedException e) {
      e.printStackTrace();
      Thread.currentThread().interrupt();
      break;
     }
    }
   }
   break;
  }

  // All decoded frames have been rendered, we can stop playing now
  if ((infoVideo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
   Log.d(TAG, "OutputBuffer BUFFER_FLAG_END_OF_STREAM");
   break;
  }
 }
}
if (videoTrack >=0)
{
 decoderVideo.stop();
 decoderVideo.release();
}

extractorVideo.release();

We can use decoderVideo.dequeueInputBuffer(10000) to get a input buffer first.
Then use extractorVideo.readSampleData(buffer, 0) to feed the video data to buffer from media source.
For decoding, we then use decoderVideo.queueInputBuffer() to queue the data.
decoderVideo will start to decode after that.

We use decoderVideo.dequeueOutputBuffer(infoVideo,10000) to get the result.
If the return value is not -1, it means that decoderVideo has decoded the video data successfully.

To render the decoded data to surface, we use  decoderVideo.releaseOutputBuffer(outIndex, true). The second parameter true means that we need to render.

We have finished the decode and render jobs.
Forward to the next video data again using extractorVideo.advance().

2015年4月1日 星期三

[Android] How to call Native C/C++ functions by JNI mechanism? - part I

Basically, the APIs provided by Android can fulfill most requirements for our coding.
However, we may need to access the native C/C++ functions directly.
Maybe it is for the performance issue, or just for that the functions Android provided are not enough.

If you use Eclipse like me, then you need to install CDT and NDK for your Eclipse.
After that, right-click the mouse on the project which you want to add the JNI interface.
Select the item "Android Tools"/"Add Native Support".
You will find that a new folder "jni" is added in the proejct.
There have one file "x.cpp" and another one "Android.mk".
We have finished the first step so far.

Next, how the Jave program calls JNI functions?
package myTest.com;
 
import android.app.Activity;
import android.os.Bundle;
 
public class MainActivity extends Activity {

hello("my Test");

private native String hello(String s); 
 
 static {
  System.loadLibrary("NativeMyTest");
 } 
}
"NativeMyTest" is the name you entered when you add the "jni" folder as mentioned before.
It will generate "NativeMyTest.so" later.
hello(String s) is the function defined in JNI.

In JNI part:
#include <jni.h>
#include <stdio.h>

 #define LOG_TAG "MainActivity"
#define LOGI(...) __android_log_print(4, LOG_TAG, __VA_ARGS__);

JNIEXPORT void hello(JNIEnv* env, jobject obj, jstring str){ 
 LOGI("hello: %s", str);
}

jint JNI_OnLoad(JavaVM* pVm, void* reserved) {
  JNIEnv* env;
  if ((*pVm)->GetEnv(pVm, (void **)&env, JNI_VERSION_1_6) != JNI_OK) {
  return -1;
  }
 
  JNINativeMethod nm[2];
  nm[0].name = "hello";
  nm[0].signature = "(Ljava/lang/String)V";
  nm[0].fnPtr = (void*)hello;

  jclass cls = (*env)->FindClass(env, "myTest/com/MainActivity");
  (*env)->RegisterNatives(env, cls, nm, 1);
  gJavaVM = pVm;
  return JNI_VERSION_1_6;
}
JNI_OnLoad() is used to load those functions which will be called in Java program.
The parameter in FindClass() need to be the same as the names of Java package and class.

[Android] Google Speech Recognition - Turn off the Beep tone


In [Android] Google Speech Recognition - using RecognizerIntent and 
[Android] Google Speech Recognition - Implement our own UI, we implement speech recognition in different ways.
However, it will have an annoying Beep tone before the recording of voice.
Personally, I think that it is acceptable since it can hint the user.
If you think that it is really annoying, your can turn it off by one the methods mentioned below:
AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
am.setStreamSolo(AudioManager.STREAM_VOICE_CALL, true);
sr.startListening(recognizerIntent);
Or
AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
am.setStreamMute(AudioManager.STREAM_SYSTEM, true);
sr.startListening(recognizerIntent);

[Android] Google Speech Recognition - Implement our own UI

In [Android] Google Speech Recognition - using RecognizerIntent, we show how to do speech recognition with RecognizerIntent.
However, the UI is pre-defined.
Can we implement our own UI for that?

For such purpose, we need to add SpeechRecognizer as show below:
Intent recognizerIntent;
private SpeechRecognizer sr;

sr = SpeechRecognizer.createSpeechRecognizer(this);       
sr.setRecognitionListener(new listener());        
recognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);        
recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
recognizerIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS,5);//5 is the number of results to return 

//To start to recognize...put the code in onClick() event of some button
sr.startListening(recognizerIntent);  

//To stop to recognize...put the code in onClick() event of some button
//In general, the recognition process will stop automatically after completion.
//If we want to interrupt it or some abnormal situations happened, we can use the codes below..
sr.stopListening();
sr.cancel();
The class listener() used is shown below:
private static final int RECOGNIZER_ERROR_NETWORK_TIMEOUT = 1;//Network timeout
private static final int RECOGNIZER_ERROR_NETWORK = 2;//Other network related errors
private static final int RECOGNIZER_ERROR_AUDIO = 3;//Cannot record the voice
private static final int RECOGNIZER_ERROR_SERVER = 4;//Server is abnormal
private static final int RECOGNIZER_ERROR_CLIENT = 5;//Other abnormal cases in client side
private static final int RECOGNIZER_ERROR_SPEECH_TIMEOUT = 6;//No voice input
private static final int RECOGNIZER_ERROR_NO_MATCH = 7;//No matched result
private static final int RECOGNIZER_ERROR_RECOGNIZER_BUSY = 8;//RecognitionService is busy
private static final int RECOGNIZER_ERROR_INSUFFICIENT_PERMISSIONS = 9;//No sufficient permission

class listener implements RecognitionListener          
{
    public void onReadyForSpeech(Bundle params)
    { 
      //Ready to accept the voice input. We can show some icon to notify the user that he/she can start talking 
    }
    public void onBeginningOfSpeech()
    {  
      //User start to talk. We can show some icon to tell the user that the program has got the input voice.        
    }
    public void onRmsChanged(float rmsdB)
    { 
      //The intensity of input voice, ranged from 0 to 10.
      //We can show some icon according to the intensity.
      //onRmsChanged() will be called frequently. We'd better update the UI if its value varies larger than some range.
    }
    public void onBufferReceived(byte[] buffer)
    {       
    }
    public void onEndOfSpeech()
    {
       //User stop talking. The server will start to recognize.
    }
    public void onError(int error)
    {
       switch(error)
       {
        case RECOGNIZER_ERROR_NETWORK_TIMEOUT:         
         break;
        case RECOGNIZER_ERROR_NETWORK:         
         break;
        case RECOGNIZER_ERROR_AUDIO:         
         break;
        case RECOGNIZER_ERROR_SERVER:         
         break;
        case RECOGNIZER_ERROR_CLIENT:         
         break;
        case RECOGNIZER_ERROR_SPEECH_TIMEOUT:         
         break;
        case RECOGNIZER_ERROR_NO_MATCH:         
         break;
        case RECOGNIZER_ERROR_RECOGNIZER_BUSY:         
         break;
        case RECOGNIZER_ERROR_INSUFFICIENT_PERMISSIONS:         
         break;
       }
    }
    public void onResults(Bundle results)                   
    {
        //The recognition result
        ArrayList data = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);         
        String firstMatched = (String) data.get(0);         
        txtResult.setText(firstMatched);
    }
    public void onPartialResults(Bundle partialResults)
    {
    }
    public void onEvent(int eventType, Bundle params)
    {
    }
}

[Android] Google Speech Recognition - using RecognizerIntent

The accuracy of Google Speech Recognition is impressive.
If you want to integrate it into your program, just need to integrate RecognizerIntent as shown below:
private static final int RECOGNITION_REQUEST_CODE = 1234;//This number is only used for recognition tag. Any number is fine.
Intent it = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
it.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
//it.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
it.putExtra(RecognizerIntent.EXTRA_PROMPT, "Pleas speak...");
this.startActivityForResult(it, RECOGNITION_REQUEST_CODE);
The example shown above is to call RecognizerIntent directly.
It will show up the Android defined UI.
The Speech Recognition activity will deal with the recording of voice and start to recognize it automatically.
We don't need to do much effort.
The parameter for EXTRA_LANGUAGE_MODEL can be RecognizerIntent.LANGUAGE_MODEL_FREE_FORM or RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH,
What's the difference?
LANGUAGE_MODEL_FREE_FORM: In general, we will use this parameter for most cases.
LANGUAGE_MODEL_WEB_SEARCH: It is optimized for web search and is suitable for short sentence.
However, it seems that it does not make big difference according to my test result.

Then, how can we get the recognition result?
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);    
    String firstMatched;  

    if(requestCode == RECOGNITION_REQUEST_CODE &amp;&amp; resultCode == RESULT_OK){
        //The recognition result will more than one. They will be sorted so that the nearest matched result come out first.
        ArrayList<string> resultList = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
        firstMatched = (String)resultList.get(0);//We just pick up the first one
    } else {
        firstMatched = "Cannot recognize";
    }
    
    result.setText(firstMatched);
}
In the whole process, make sure that the program can access the Internet.
If you want to do the recognition by off-line, you can down the off-line speech recognition data in advance.
Download the specified language data you want in Settings and then you can proceed speech recognition without Internet available.
However, it seems that the accuracy is poor for off-line mode.

[Android] why is my thread not stopped?

In general, the thread body will look like:
private class myThread extends Thread {
 @Override
 public void run() {
  super.run();
  Log.d(TAG,"myThread() entered...");
  while(!isInterrupted()) {
   Thread.sleep(1000);
    }
    catch (InterruptedException e) {   
     e.printStackTrace();    
    }
   }    
  }
  Log.d(TAG,"myThread() exited...");
 }
}
If we want to stop it, we can use interrupt().
For example, if we use mMyThread = new myThread() to establish the thread, we can stop it by mMyThread.interrupt().

We found that Log.d(TAG,"myThread() entered...") will be executed.
It means that mMyThread = new myThread() works normally.
However, Log.d(TAG,"myThread() exited...") is not executed.
Why? 

The reason is that the thread may be in Thread.sleep() when we call interrupt().
It will jump to catch (InterruptedException e) then and the interrupt flag will be resetted. 
When the thread runs back to while(!isInterrupted()), it is still true.

How should we fix it?
private class myThread extends Thread {
 @Override
 public void run() {
  super.run();
  Log.d(TAG,"myThread() entered...");
  while(!isInterrupted()) {
   Thread.sleep(1000);
    }
    catch (InterruptedException e) {   
     e.printStackTrace(); 
     Thread.currentThread().interrupt();
    }
   }    
  }
  Log.d(TAG,"myThread() exited...");
 }
}
Just add "Thread.currentThread().interrupt()" within "catch (InterruptedException e)".
It will set the flag interrupt again.
"Log.d(TAG,"myThread() exited...")" will be executed then.

[Android] How to copy the files to /system or /data folder

For both folders"/system" and "/data",they are read-only modes by default.
If you need to copy files into them, you need some specific commands as shown below:

Process process = Runtime.getRuntime().exec("su");
DataOutputStream output = new DataOutputStream(process.getOutputStream());
output.writeBytes("mount -o remount,rw -t yaffs2 /dev/block/mtdblock3 /system\n");
String sCatCommand = "cat "+Environment.getExternalStorageDirectory()+"/myTest/test.dat"+ "> /system/"+"test.dat"+"\n";
output.writeBytes(sCatCommand);
output.writeBytes("mount -o remount,ro -t yaffs2 /dev/block/mtdblock3 /system\n");
output.writeBytes("exit\n");  
output.flush();
process.waitFor();

The example shown above is to copy the file "/myTest/test.dat" in SD card to "/system" folder.

[Android] About Service - how to keep Service running permanently?

As described in [Android] About Service - will Service be Killed?,the Service will be restarted after being killed.
However, what if the system does not restart it?
Is there any other way that we can keep our Service running permanently?

A simple way is to establish another service called "monitorService".
Its job is to monitor our service myService
When it finds that myService is not in the RUNNING list, it will restart myService.

As described in [Android] How to load my APP automatically after system booting upthere has one class BootUpReceiverClass.java to receive the message from system after booting up.
Actually, we can use it to receive the Broadcast messages as well.
We need to add one intent-filter in AndroidManifest.xml.
<receiver android:enabled="true" android:name="myTest.com.BootUpReceiverClass"
 android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
 <intent-filter>
 <action android:name="android.intent.action.BOOT_COMPLETED" />
 <category android:name="android.intent.category.DEFAULT" />
 </intent-filter>
 <intent-filter>
 <action android:name="myTest.com.BootUpReceiverClass" /> 
 <category android:name="android.intent.category.DEFAULT" />       
 </intent-filter>
</receiver> 

For monitorService part, we have one thread monitorThread to check if "myTest.com.myServiceClass" is running or not.
If not, it will call sendBroadcast() to send the event to "myTest.com.BootUpReceiverClass".
private boolean isMyServiceRunning(String className) {
  try
  {
      ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
      for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {       
          if (className.equals(service.service.getClassName())) {
              return true;
          }
      }
  }catch (Exception e) {   
   e.printStackTrace();   
  }
     return false;
 }
 private class monitorThread extends Thread {
  @Override
  public void run() {
   super.run();   
   while(!isInterrupted()) {
    try {     
     if(!isMyServiceRunning("myTest.com.myServiceClass"))
     {      
      try
      {
       Intent intent = new Intent("myTest.com.BootUpReceiverClass");
       if(intent!=null)
       {        
           sendBroadcast(intent);
                 Thread.sleep(15000);
       }
      }catch (Exception e) {       
       e.printStackTrace();       
      }
     } 
     else
     {      
      Thread.sleep(3000);
     }     
    } catch (InterruptedException e) {     
     e.printStackTrace();     
    }
   }   
  }
 }
What if monitorService is killed?
To avoid such condition, we need to add a similar mechanism in myService.
It will check if monitorService is running or not.
If not, just restart it.
The codes are not posted here since they are similar.

In general, it is enough to check mutually for every 2~5 seconds.
You can check if both myService and monitorService are there in the RUNNING list.
Try to kill myService manually and check if it will be restarted.
On the other hand, try to kill monitorService manually and check if it will be restarted.

What if both monitorService and myService are killed at the same time?
It is hard to do that manually. You can try it by yourself.
The possible way is to kill them both by system.
It means that the system loading should be heavy.
Is such case, our service will not run smoothly even it survives.

[Android] About Service - will Service be Killed?

Yes, Service will be killed, especially in Low Memory condition.
How about when the system loading is not heavy? It is still possible to be killed.
Let us discuss some setting about Service:

1. The return value of  onStartCommand()
START_STICKY:In the demo we shown before, we used this value.
When the Service is killed, it will be restarted if the system loading is allowed.
However, the intent passed in may be null.
If we need to get data from the intent, we'd better make some check first.
@Override
 public int onStartCommand(Intent intent, int flags, int startId) {
  if(intent != null)
  {
   String sData1=Intent.getStringExtra("DATA_KEY1");
   int iData2=Intent.getIntExtra("DATA_KEY2",0);
   mServiceThread = new ServiceThread();
   mServiceThread.start();
   }
   else
   {
     Intent i = new Intent(xxx);
     sendBroadcast(i);
   }
    return  START_STICKY ; 
 }      
If you need these data to start the Service, you will need the mechanism sendBroadcast().
You can send a broadcast event to the Activity where you start the Service.
We can start the Service again from there and pass the necessary data at the same time.

From my experiment, the Service will be restarted about every 30 minutes (The value will be different for each device), especially when the Service is in idle status.
For example, if you create a service for network communication and when there are no any packets input, the service may be killed and then restarted soon.
You can see that in the Apps RUNNING list, the service disappears for few seconds.

START_NOT_STICKY: When the Service is killed, it is terminated directly and will not be restarted.
START_REDELIVER_INTENT: When the Service is killed, it will be restarted and pass in the last intent.
That is, the intent will not be null.
However, the system will be hung up if I use this parameter. 
I am not sure the reason.

2. Adjust AndroidManifest.xml
Some people suggest that we can increase the Service priority by the following setting:
<intent-filter android:priority="1000"></intent-filter>
As experimented, the Service may restart every 60 minutes.

Some ones try to restart the Service itself in onDestroy().
However, when the Service is killed, the onDestroy() is not called always.
I didn't try this method.

Anyway, it seems that the Service will be restarted somehow.
Is there any way for prevent Service being restarted?
You may try to set the Service as Foreground Service:
Notification notification = new Notification(R.drawable.launch, "Your Application Name", System.currentTimeMillis());  
Intent notificationIntent = new Intent(this, YourMainActivity.class);  
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);  
notification.setLatestEventInfo(this, "Your Application Name", "Your Service Name", pendingIntent);  
startForeground(1, notification);
If you set the Service as Foreground mode, you will see your defined icon in the status bar.
As experimented, the Service never restarted within few days.

[Android] About Service - how to pass data to Service

We may need to pass some information to Service sometimes.
For example, the file to play, the control commands, and so on.

There have few ways we can use.
First method is to use the way of passing data between Intent.
When we start the Service, we can include the data at the same time.
Intent i = new Intent(this,myServiceClass.class);
i.putExtra("DATA_KEY1","my data");
i.putExtra("DATA_KEY2",1234);
startService(i);
In the case above, we put string "my data" and number 1234 in the intent.

In the Service, how can it retrieve these data?
@Override
 public int onStartCommand(Intent intent, int flags, int startId) {
  String sData1=Intent.getStringExtra("DATA_KEY1");
  int iData2=Intent.getIntExtra("DATA_KEY2",0);
  mServiceThread = new ServiceThread();
  mServiceThread.start();
     return  START_STICKY ; 
 }   
We can get the passed data in onStartCommand().
That is, sData1 will be "my data" and iData2 will be 1234.

We can call startService() repeatedly and pass the desired data.
Once we call startService(), the function onStartCommand() will be executed accordingly.
Note that in the above case, onStartCommand() will run new ServiceThread() and mServiceThread.start().
We need to prevent them being executed duplicatedly if we call startService() few times.

Another common way is to use the bindService mechanism.
public class myService  extends Service {
 private ServiceThread mServiceThread; 
 @Override
 public IBinder onBind(Intent intent) {
  // TODO Auto-generated method stub
  return new myServiceLocalBinder();
 }
 
 @Override
 public void onCreate() {
 // TODO Auto-generated method stub
 super.onCreate();
 }
  
 @Override
 public int onStartCommand(Intent intent, int flags, int startId) {
  mServiceThread = new ServiceThread();
  mServiceThread.start();
     return  START_STICKY ; 
 }     
  
 public class myServiceLocalBinder extends Binder {  
  myService getService() {  
            // Return this instance of LocalService so clients can call public methods  
            return myService.this;  
        }     
    }

 public void SendData2Service(String s, int i)
 {
   //your action here
 }
  
  
 @Override
 public void onDestroy() {
  Log.d(TAG,"onDestroy()");
  if (mServiceThread != null)
   mServiceThread.interrupt();
 }
  
 private class ServiceThread extends Thread {
  @Override
  public void run() {
   super.run();
   //your action here
  }
 } 
}
Then we need to modify the way how we start the Service:
myServiceClass myService=null; 

private ServiceConnection sMyServiceConnection = new ServiceConnection() {    
 @Override  
 public void onServiceDisconnected(ComponentName name) {  
 }    
 
 @Override
 public void onServiceConnected(ComponentName name, IBinder service) {
  myService = ((myServiceClass.myServiceLocalBinder) service).getService();  
 }  
};  

 public void StartMyService() {
   Intent i = new Intent(this, myService.class);  
   bindService(i, sMyServiceConnection , BIND_AUTO_CREATE);
   startService(i);
 }
 
 public void StopMyService(){
  Intent i = new Intent(this,myServiceClass.class);
 try{
     unbindService(sMyServiceConnection);
     stopService(i);
  } catch (IllegalArgumentException e){              
 } 
 }
When we want to pass the data to Service, we can use myService.SendData2Service().

2015年3月31日 星期二

[Android] About Service - how to establish?

In general, we use Activity to construct our program, including the interaction with user and what need to do.
So...why do we need what called Service?

Image that you may need to play music in background or communicate through TCP/UDP and so on.
Of course, you can new a thread to run these jobs in background.
But, what if we want these functions to be available even we have closed our activity?
In this case, you will need the Service mechanism.

First, we need to create service class:
public class myService  extends Service {
 private ServiceThread mServiceThread; 
 @Override
 public IBinder onBind(Intent intent) {
  // TODO Auto-generated method stub
  return null;
 }
 
 @Override
 public void onCreate() {
 // TODO Auto-generated method stub
 super.onCreate();
 }
  
 @Override
 public int onStartCommand(Intent intent, int flags, int startId) {
  mServiceThread = new ServiceThread();
  mServiceThread.start();
     return  START_STICKY ; 
 }     
  

 @Override
 public void onDestroy() {
  Log.d(TAG,"onDestroy()");
  if (mServiceThread != null)
   mServiceThread.interrupt();
 }
  
 private class ServiceThread extends Thread {
  @Override
  public void run() {
   super.run();
   //your action here
  }
 } 
}
We new  ServiceThread() here because we need our service to run some jobs repeatedly.
If you only want to do some simple actions and just need to do them once, you can add the related codes in onStartCommand() directly.

After that, we need to register our service in AndroidManifest.xml.
<service android:name="myPackageName.myService"> </service >
We have established our Service now.

How to call it?
Assume that you have on button in the activity, and we want to start the service when we press it.
public void onClick(View v) {
Intent i = new Intent(this, myService.class);  
startService(i);
}
On the other hand, if we want to stop the Service?
public void onClick(View v) {
Intent i = new Intent(this, myService.class);  
stopService(i);
}

To check if we have established our service or not, we can open the RUNNING list in Settings/Apps.
There should have the name of our service.

[Android] How to carry files using Assets directory

Sometimes, we need to access our own files after installing the APP.
For example, we may want to play the default MP3 file in background.

The simple way is just put the files you want in the directory assets.
During your development stage, those files in this directory will not dealt with.
When you build the apk, they will be included at the same time.

Then, how can our program access them after installing the apk?
Some people may use the path "file://android_asset/" to access them.
However, the suggested method is to access them by AssetManager.

Assume that you put a file called "t.mp3" in the assets directory.
AssetManager assetManager = getAssets();     
InputStream in = null;
OutputStream out = null;
File sdCardDir = Environment.getExternalStorageDirectory();    
in = assetManager.open("t.mp3");
out = new FileOutputStream(new File(sdCardDir+"/myFolder", "t.mp3"));    
byte[] buffer = new byte[1024];
int read;
while((read = in.read(buffer)) != -1)
{
  out.write(buffer, 0, read);
}    
in.close();
in = null;
out.flush();
out.close();
out = null;

The method shown above is to copy the file "t.mp3" to directory "/myFolder".
Then we can access it by FileInputStream or another file access methods.

Of course, there have many different ways.
For example, if you want to play it directly:
AssetFileDescriptor fileDescriptor = getAssets().openFd("t.mp3");
MediaPlayer mp = new MediaPlayer();
mp.setDataSource(fileDescriptor.getFileDescriptor());

[Android] How to load my APP automatically after system booting up

In order to start the program automatically after system booting up, we need to:

Assume that MainActivity is the start up Activity.
We need to add a new class extended from BroadcastReceiver to accept the broadcasted messages.
 public class BootUpReceiverClass extends BroadcastReceiver {
     @Override
     public void onReceive(Context context, Intent intent) {      
         Intent i = new Intent(context, MainActivity.class);  
            i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(i);          
         }
    }

Then, we need to add a permission in AndroidManifest.xml.
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

After that, we can a receiver tag within <application> </application>

   
       
       
      
    

[Android] MediaPlayer RTSP streaming

For MediaPlayer, if we want to play internet streaming,
we only need to set the internet address in setDataSource().
But, we if it needs account and password?
There are two possible types:
1. Basic Authentication

Context context = getApplicationContext();  
sUrl = "rtsp://xxxx";  
sAccount = "xx";  
sPassword = "xx";  
Uri source = Uri.parse(sUrl);  
Map<String, String> headers = getRtspHeaders();  
mediaPlayer.setDataSource(context, source, headers);

Map<String, String> getRtspHeaders() {  
  Map<String, String> headers = new HashMap<String, String>();  
   String basicAuthValue = getBasicAuthValue(sAccount, sPassword);  
   headers.put("Authorization", basicAuthValue);  
   return headers;  
}  
private String getBasicAuthValue(String usr, String pwd) {  
   String credentials = usr + ":" + pwd;  
   int flags = Base64.URL_SAFE | Base64.NO_WRAP;  
   byte[] bytes = credentials.getBytes();  
   return "Basic " + Base64.encodeToString(bytes, flags);       
}    

The above codes show us how to deal with basic authentication.
There has another simpler way - add the account and password in the path.
e.g.  sUrl = "rtsp://admin:pwd@xxxx";


2. Digest Authentication
Originally, I thought that we can use sUrl = "rtsp://admin:pwd@xxxx" for digest authentication as well.
However, I always got the error message "error 100, Media server died".
When I googled it, the answer was that "MedisPlayer does not support rtsp digest authentication" or something like that.
One day, I thought that I might use Wireshark to check the packets for what happened.
As the result shown above, it seems that the format is correct in RTSP DESCRIBE packet.
It means that it does support digest authentication type.
So...why did we get the reply "401 unauthorized"?
(The above graph may be captured not by using MediaPlayer. But the analysis discussed here is for MediaPlayer indeed)
Check the DESCRIBE packet again... username is correct, uri is correct, nonce is correct...
Is response the suspect?
response= md5(md5(username:realm:password):nonce:md5(uri))
I calculated it manually. It did have something wrong.
The response in DESCEIBE packet is not as what expected.
To find out the reason, I need to investigate the Android kernel codes.
The related codes is in ARTSPConnection.cpp.
Finally, I found that when it calculated response, the realm it used is not from RTSP server Reply packet.
Instead, it was hard coded as "Streaming Server".
No wonder the response was not correct.
Although the reason is clear, I can do nothing since I cannot rebuild my Android kernel.
It seems that I cannot use MediaPlayer for RTSP Digest Authentication
Need to find another way...

[Android] The basic concept of using MediaPlayer

If we want to play music or multimedia files, the simple way is to use MediaPlayer.
We can use in this way:

private MediaPlayer mp = new MediaPlayer();
mp.setDataSource("/sdcard/test.mp3");//Set the source
mp.prepare();
mp.start();

If the source is from internet streaming, it can also support it.
For example:
mp.setDataSource("rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov");
MediaPlayer support HTTP and RTSP streaming both.

If we use MediaPlayer to play video files, we will need to incorporate SurfaceView.
Notice that new MediaPlayer() must be declared in surfaceCreated(), as shown below:
@Overridepublic void surfaceCreated(SurfaceHolder sh) {
         mp= new MediaPlayer();
         mp.setDisplay(surfaceHolder);   
         mp.setOnPreparedListener(this);      
    }
@Override
     public void surfaceDestroyed(SurfaceHolder arg0) {
         mp.release();
     }

@Override
public void onPrepared(MediaPlayer mp) {
        mp.start();
     }
If we have one button to control the play action, we can add the codes below in onClick() event of that button.
mp.setDataSource("your source path");
mp.prepareAsync();  

MediaPlayer can support many formats, such as MP3,MPEG2,H.264,3gp....