Android simple "blow to achieve" and recording and playback examples

Android simple "blow to achieve" and recording and playback examples

I've been working on something related to sensors recently, and I noticed that Tencent Weibo had previously released a "blow" interaction. Although it has nothing to do with sensors, I was still interested in it, so I wrote a crude demo. Because I rarely work with media file operations, I also wrote an example of recording and playback, and summarized some minor pitfalls, which I would like to share with you here.

Main ideas and pitfalls

The main idea is to use the getMaxAmplitude() function provided by MediaRecorder to obtain the maximum amplitude of the audio input within a period of time for detection, so in addition to the blowing action, other sounds will also be recorded.

If we want to distinguish the action of "blowing" from other actions, the essence is to blow close to the receiver. Even if the volume of the blowing action itself is not loud, its decibel level will be very high to the microphone, so we can determine whether this action is blowing by detecting the decibel level (if other sounds are louder... then... forget it).

Here are some references from previous generations:

http://www.jb51.net/article/6...

As soon as I saw the htm behind this website, I seemed to understand the framework of this website...

The problem with this thing is that the order of stopping and starting Mediaplayer and MediaRecorder is often strictly restricted. If resources are not released successfully when exiting, sometimes when the Activity is restarted, an exception will be thrown when restarting because it was not stopped the last time it exited.

Add permissions

  1. <uses-permission android: name = "android.permission.READ_EXTERNAL_STORAGE" />
  2. <uses-permission android: name = "android.permission.WRITE_EXTERNAL_STORAGE" />
  3. <uses-permission android: name = "android.permission.MOUNT_FORMAT_FILESYSTEMS" />
  4. <uses-permission android: name = "android.permission.RECORD_AUDIO" />

Main interface

I thought of a simple interface. Well, actually the speaker in the lower left corner flashes. I forgot to modify the text description.

Layout file:

  1. <?xml version= "1.0" encoding= "utf-8" ?>
  2. <RelativeLayout xmlns:android= "http://schemas.android.com/apk/res/android"  
  3. xmlns:tools= "http://schemas.android.com/tools"  
  4. android:id= "@+id/activity_sound"  
  5. android:layout_width= "match_parent"  
  6. android:layout_height= "match_parent"  
  7. android:paddingBottom= "@dimen/activity_vertical_margin"  
  8. android:paddingLeft= "@dimen/activity_horizontal_margin"  
  9. android:paddingRight= "@dimen/activity_horizontal_margin"  
  10. android:paddingTop= "@dimen/activity_vertical_margin"  
  11. tools:context= ".SoundActivity" >
  12. <TextView
  13. android:layout_width= "wrap_content"  
  14. android:layout_height= "wrap_content"  
  15. android:text= "@string/introduction_of_sound" />
  16. <LinearLayout
  17. android:layout_width= "match_parent"  
  18. android:layout_height= "wrap_content"  
  19. android:orientation= "vertical"  
  20. android:gravity= "center"  
  21. android:layout_centerInParent= "true" >
  22.  
  23. <Button
  24. android:layout_width= "70dp"  
  25. android:layout_height= "70dp"  
  26. android:id= "@+id/btn_start_record"  
  27. android:background= "@drawable/ic_mic_none_black_24dp"  
  28. />
  29. <TextView
  30. android:layout_width= "wrap_content"  
  31. android:layout_height= "wrap_content"  
  32. android:id= "@+id/tv_record_tips" />
  33.  
  34. <Button
  35. android:layout_width= "70dp"  
  36. android:layout_height= "70dp"  
  37. android:id= "@+id/btn_start_play"  
  38. android:background= "@drawable/ic_play_circle_filled_black_24dp"  
  39. />
  40. </LinearLayout>
  41. <ImageView
  42. android:layout_width= "50dp"  
  43. android:layout_height= "50dp"  
  44. android:src= "@drawable/ic_volume_mute_gray_24dp"  
  45. android:layout_alignParentBottom= "true"  
  46. android:id= "@+id/imv_sound" />
  47. </RelativeLayout>

Main code

  1. import android.app.ProgressDialog;
  2. import android.media.AudioManager;
  3. import android.media.MediaPlayer;
  4. import android.media.MediaRecorder;
  5. import android.os.Environment;
  6. import android.os.Handler;
  7. import android.os.Message;
  8. import android.support.v7.app.AppCompatActivity;
  9. import android.os.Bundle;
  10. import android. view . View ;
  11. import android.widget.Button;
  12. import android.widget.ImageView;
  13. import android.widget.TextView;
  14. import android.widget.Toast;
  15.  
  16. import java.io.IOException;
  17. import java.util.Timer;
  18. import java.util.TimerTask;
  19.  
  20.  
  21. public class SoundActivity extends AppCompatActivity {
  22. static final int RECORDING = 1;
  23. static final int PLAYING = 2;
  24. static final int PAUSING = 3;
  25. static String TAG = "SoundActivity" ;
  26. static   int STATUS = RECORDING;
  27. //For audio recording
  28. MediaRecorder mediaRecorder;
  29. //For audio playback
  30. MediaPlayer mediaPlayer;
  31.  
  32. //Record button
  33. Button btnRecord;
  34. //Play button
  35. Button btnPlay;
  36. //Prompt information
  37. TextView tvTips;
  38. //Blow the small speaker
  39. ImageView imvSound;
  40. //Play progress bar
  41. static String PATH_NAME = Environment.getExternalStorageDirectory().getAbsolutePath() + "/SensorDemoRecorder.mp3" ;
  42. @Override
  43. protected void onCreate(Bundle savedInstanceState) {
  44. super.onCreate(savedInstanceState);
  45. setContentView(R.layout.activity_sound);
  46. init();
  47. }
  48.  
  49. public void init(){
  50. //Control initialization
  51. btnPlay = (Button)findViewById(R.id.btn_start_play);
  52. btnRecord = (Button)findViewById(R.id.btn_start_record);
  53. tvTips = (TextView)findViewById(R.id.tv_record_tips);
  54. imvSound = (ImageView)findViewById(R.id.imv_sound);
  55. mediaplayerPreparingDialog = new ProgressDialog(this);
  56. btnPlay.setOnClickListener(new View .OnClickListener() {
  57. @Override
  58. public void onClick( View v) {
  59. if (STATUS == RECORDING ){
  60. //If it is recording, click to stop recording and play
  61. stopRecording();
  62. startPlay();
  63. } else if (STATUS == PAUSING){
  64. startPlay();
  65. } else {
  66. //If it is playing, click to pause
  67. pausePlay();
  68. }
  69. }
  70. });
  71. btnRecord.setOnClickListener(new View .OnClickListener() {
  72. @Override
  73. public void onClick( View v) {
  74. if (STATUS == PLAYING || STATUS == PAUSING){
  75. //If it is playing or paused, click to start recording
  76. startRecording();
  77. } else {
  78. //If recording, click to start playing
  79. stopRecording();
  80. startPlay();
  81. }
  82. }
  83. });
  84.  
  85. mediaRecorder = new MediaRecorder();
  86. //Set to restart recording when the maximum recording length is reached
  87. mediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
  88. @Override
  89. public void onInfo(MediaRecorder mr, int what, int extra) {
  90. switch (what){
  91. case MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN:
  92. Toast.makeText(SoundActivity.this, "Unknown error" , Toast.LENGTH_SHORT).show();
  93. finish();
  94. break;
  95. case MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED:
  96. Toast.makeText(SoundActivity.this, "The maximum recording length has been reached, start re-recording" , Toast.LENGTH_SHORT).show() );
  97. startRecording();
  98. break;
  99. case MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED:
  100. Toast.makeText(SoundActivity.this, "Not enough space to record" , Toast.LENGTH_SHORT).show();
  101. mediaRecorder.stop();
  102. break;
  103.  
  104. }
  105. }
  106. });
  107. //Start recording by default
  108. startRecording();
  109. btnRecord.setBackgroundResource(R.drawable.ic_mic_black_24dp);
  110. //Start blowing detection and playback progress detection by default
  111. startCheckSound();
  112. }
  113.  
  114.  
  115. @Override
  116. protected void onDestroy() {
  117. super.onDestroy();
  118. if (PLAYING == STATUS){
  119. mediaPlayer.stop();
  120. mediaPlayer.release();
  121. }
  122. if (RECORDING == STATUS){
  123. mediaRecorder.stop();
  124. mediaRecorder.release();
  125. }
  126. //To prevent the timer from executing tasks after the Activity ends (very tricky)
  127. timer.cancel();
  128. }
  129.  
  130. public void startRecording(){
  131. if (PLAYING == STATUS){
  132. stopPlay();
  133. }
  134. STATUS = RECORDING;
  135. //Set to recording mode
  136. tvTips.setText( "Recording, click the play button or microphone to stop recording" );
  137. btnRecord.setBackgroundResource(R.drawable.ic_mic_black_24dp);
  138.  
  139. //Start recording settings
  140. mediaRecorder.reset(); // You can reuse the object by going back to setAudioSource() step
  141. mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
  142. mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
  143. mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
  144. try{
  145. mediaRecorder.setOutputFile(PATH_NAME);
  146. mediaRecorder.prepare ();
  147. mediaRecorder.start(); // Recording is now started
  148. }catch (IOException e){
  149. Toast.makeText(this, "Failed to prepare recording file" , Toast.LENGTH_SHORT).show();
  150. e.printStackTrace();
  151. finish();
  152. }
  153. }
  154.  
  155. public void stopRecording(){
  156. if (RECORDING == STATUS){
  157. //Indicates that recording is in progress, set stop information
  158. tvTips.setText( "Recording stopped, playback started" );
  159. btnRecord.setBackgroundResource(R.drawable.ic_mic_none_black_24dp);
  160. mediaRecorder.stop();
  161. }
  162. }
  163.  
  164.  
  165.  
  166. Handler handler= new Handler(new Handler.Callback() {
  167. @Override
  168. public boolean handleMessage(Message msg) {
  169. if (( Double )msg.obj > 70){
  170. imvSound.setImageResource(R.drawable.ic_volume_mute_valid_24dp);
  171. } else {
  172. imvSound.setImageResource(R.drawable.ic_volume_mute_gray_24dp);
  173. }
  174. return   false ;
  175. }
  176. });
  177.  
  178. Timer timer = new Timer();
  179. public void startCheckSound(){
  180. //Timed detection of peak values ​​and playback progress
  181. timer.schedule(new TimerTask() {
  182. @Override
  183. public void run() {
  184. if (mediaRecorder != null ) {
  185. double amplitude = ( double )mediaRecorder.getMaxAmplitude();
  186. double db = 0;
  187. //Calculate decibels
  188. if (amplitude > 1)
  189. db = 20 * Math.log10(amplitude);
  190. Message msg = new Message();
  191. msg.obj = db;
  192. handler.sendMessage(msg);
  193. //If you need to detect the playback progress, you can use
  194. //mediaPlayer.getCurrentPosition()/mediaPlayer.getDuration();
  195. }
  196. }
  197. },0,100);
  198. }
  199.  
  200.  
  201. ProgressDialog mediaplayerPreparingDialog;
  202. public void startPlay(){
  203. if (RECORDING == STATUS){
  204. //If the playback starts from the recording state, re-read the new recording file
  205. STATUS = PLAYING;
  206. //Set up the audio player
  207. mediaPlayer = new MediaPlayer();
  208. mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
  209. mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
  210. @Override
  211. public void onCompletion(MediaPlayer mp) {
  212. //Set up after playing
  213. tvTips.setText( "After playing, click the microphone to re-record" );
  214. btnPlay.setBackgroundResource(R.drawable.ic_play_circle_filled_black_24dp);
  215. }
  216. });
  217. try {
  218. mediaPlayer.setDataSource(PATH_NAME);
  219. mediaPlayer.prepareAsync();
  220. } catch (IOException e) {
  221. e.printStackTrace();
  222. Toast.makeText(this, "The recording file has been lost" , Toast.LENGTH_SHORT).show();
  223. finish();
  224. }
  225. mediaplayerPreparingDialog.setTitle( "Preparing to play recording" );
  226. mediaplayerPreparingDialog.show();
  227.  
  228. mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
  229. @Override
  230. public void onPrepared(MediaPlayer mp) {
  231. mediaplayerPreparingDialog.dismiss();
  232. mediaPlayer.start();
  233. }
  234. });
  235.  
  236. } else if(PAUSING == STATUS){
  237. //Start playing from the paused state and play directly
  238. mediaPlayer.start();
  239. }
  240. //Start playing, set the button to pause
  241. btnPlay.setBackgroundResource(R.drawable.ic_pause_circle_filled_black_24dp);
  242.  
  243. }
  244.  
  245. public void pausePlay(){
  246. if (PLAYING == STATUS) {
  247. // Pause playback and set the button to start playback
  248. mediaPlayer.pause();
  249. btnPlay.setBackgroundResource(R.drawable.ic_play_circle_filled_black_24dp);
  250. STATUS = PAUSING;
  251. }
  252. }
  253.  
  254. public void stopPlay(){
  255. if (mediaPlayer != null ) mediaPlayer.stop();
  256. }
  257. }

Media and IllegalStateException

This is the various errors mentioned before that may be caused by not releasing resources in order or stopping these two things, so I have no choice but to set a STATUS variable and stop the two things in the OnDestoy of Activity. In fact, release is usually used to release resources... You can do whatever you want...

QCMediaPlayer mediaplayer NOT present

I know that if you see this, you will be puzzled by this error. I remember that I was also fooled in ancient times, when I wrote this last time.

Someone on the forum said that this problem is easy to appear in systems below 4.4, but I can only feel that it is not clear. I used MediaPlayer.create(this,Uri.parse(PATH_NAME)) to create MediaPlayer at first, so I changed it to

  1. mediaPlayer = new MediaPlayer();
  2. mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
  3. mediaPlayer.setDataSource(PATH_NAME);

Well, then the problem was solved, and I was speechless. I think this is a long-standing pitfall, and I couldn't find the cause for a while. I can only guess that it was probably because the create function did not specify the AudioStreamType when creating it, resulting in the use of the default

  1. private int mStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;

It may not be supported on some devices, so there is a problem = = Well, I don’t know what else to say, that’s it…

The color of the icon added by Vector Asset does not change

As mentioned above, my play button, speaker, and other icons are all added through Vector Asset. This is also a relatively old pit, but I didn’t write it down before. That is, in versions below Android L, icons added by Vector Asset cannot use color references when modifying colors, but must write colors directly, for example:

  1. <vector xmlns:android= "http://schemas.android.com/apk/res/android"  
  2. android:width= "24dp"  
  3. android:height= "24dp"  
  4. android:viewportWidth= "24.0"  
  5. android:viewportHeight= "24.0" >
  6. <path
  7. android:fillColor= "#3F51B5"  
  8. android:pathData= "***" />
  9. </vector>

use

  1. <vector xmlns:android= "http://schemas.android.com/apk/res/android"  
  2. android:width= "24dp"  
  3. android:height= "24dp"  
  4. android:viewportWidth= "24.0"  
  5. android:viewportHeight= "24.0" >
  6. <path
  7. android:fillColor= "@color/colorPrimary"  
  8. android:pathData= "***" />
  9. </vector>

The color will not be changed and will remain black.

<<:  Taobao Mobile: My view on Weex and Weex open source

>>:  Zhao Bin, founder of Agora.io: Live streaming is the new trend, and the real-time Internet industry is growing at a massive scale

Recommend

Does Baidu promotion deduction have anything to do with promotion ranking?

Baidu promotion deductions generally refer to ill...

See if you can overcome these ten hurdles like Jack Ma?

Alibaba Group is about to set a global financing ...

B station promotion and fan-attracting skills

In summer, I believe it is not uncommon for every...

User operation: What else can you do without fission users to attract new users?

Without fission, how can we achieve healthy user ...

Which way to the moon?

Recently, the "Artemis 1" has come to a...

Is it true that Pinduoduo video can withdraw money? How to play?

Now many e-commerce platforms, including short vi...