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 - <uses-permission android: name = "android.permission.READ_EXTERNAL_STORAGE" />
- <uses-permission android: name = "android.permission.WRITE_EXTERNAL_STORAGE" />
- <uses-permission android: name = "android.permission.MOUNT_FORMAT_FILESYSTEMS" />
- <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: - <?xml version= "1.0" encoding= "utf-8" ?>
- <RelativeLayout xmlns:android= "http://schemas.android.com/apk/res/android"
- xmlns:tools= "http://schemas.android.com/tools"
- android:id= "@+id/activity_sound"
- android:layout_width= "match_parent"
- android:layout_height= "match_parent"
- android:paddingBottom= "@dimen/activity_vertical_margin"
- android:paddingLeft= "@dimen/activity_horizontal_margin"
- android:paddingRight= "@dimen/activity_horizontal_margin"
- android:paddingTop= "@dimen/activity_vertical_margin"
- tools:context= ".SoundActivity" >
- <TextView
- android:layout_width= "wrap_content"
- android:layout_height= "wrap_content"
- android:text= "@string/introduction_of_sound" />
- <LinearLayout
- android:layout_width= "match_parent"
- android:layout_height= "wrap_content"
- android:orientation= "vertical"
- android:gravity= "center"
- android:layout_centerInParent= "true" >
-
- <Button
- android:layout_width= "70dp"
- android:layout_height= "70dp"
- android:id= "@+id/btn_start_record"
- android:background= "@drawable/ic_mic_none_black_24dp"
- />
- <TextView
- android:layout_width= "wrap_content"
- android:layout_height= "wrap_content"
- android:id= "@+id/tv_record_tips" />
-
- <Button
- android:layout_width= "70dp"
- android:layout_height= "70dp"
- android:id= "@+id/btn_start_play"
- android:background= "@drawable/ic_play_circle_filled_black_24dp"
- />
- </LinearLayout>
- <ImageView
- android:layout_width= "50dp"
- android:layout_height= "50dp"
- android:src= "@drawable/ic_volume_mute_gray_24dp"
- android:layout_alignParentBottom= "true"
- android:id= "@+id/imv_sound" />
- </RelativeLayout>
Main code - import android.app.ProgressDialog;
- import android.media.AudioManager;
- import android.media.MediaPlayer;
- import android.media.MediaRecorder;
- import android.os.Environment;
- import android.os.Handler;
- import android.os.Message;
- import android.support.v7.app.AppCompatActivity;
- import android.os.Bundle;
- import android. view . View ;
- import android.widget.Button;
- import android.widget.ImageView;
- import android.widget.TextView;
- import android.widget.Toast;
-
- import java.io.IOException;
- import java.util.Timer;
- import java.util.TimerTask;
-
-
- public class SoundActivity extends AppCompatActivity {
- static final int RECORDING = 1;
- static final int PLAYING = 2;
- static final int PAUSING = 3;
- static String TAG = "SoundActivity" ;
- static int STATUS = RECORDING;
- //For audio recording
- MediaRecorder mediaRecorder;
- //For audio playback
- MediaPlayer mediaPlayer;
-
- //Record button
- Button btnRecord;
- //Play button
- Button btnPlay;
- //Prompt information
- TextView tvTips;
- //Blow the small speaker
- ImageView imvSound;
- //Play progress bar
- static String PATH_NAME = Environment.getExternalStorageDirectory().getAbsolutePath() + "/SensorDemoRecorder.mp3" ;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_sound);
- init();
- }
-
- public void init(){
- //Control initialization
- btnPlay = (Button)findViewById(R.id.btn_start_play);
- btnRecord = (Button)findViewById(R.id.btn_start_record);
- tvTips = (TextView)findViewById(R.id.tv_record_tips);
- imvSound = (ImageView)findViewById(R.id.imv_sound);
- mediaplayerPreparingDialog = new ProgressDialog(this);
- btnPlay.setOnClickListener(new View .OnClickListener() {
- @Override
- public void onClick( View v) {
- if (STATUS == RECORDING ){
- //If it is recording, click to stop recording and play
- stopRecording();
- startPlay();
- } else if (STATUS == PAUSING){
- startPlay();
- } else {
- //If it is playing, click to pause
- pausePlay();
- }
- }
- });
- btnRecord.setOnClickListener(new View .OnClickListener() {
- @Override
- public void onClick( View v) {
- if (STATUS == PLAYING || STATUS == PAUSING){
- //If it is playing or paused, click to start recording
- startRecording();
- } else {
- //If recording, click to start playing
- stopRecording();
- startPlay();
- }
- }
- });
-
- mediaRecorder = new MediaRecorder();
- //Set to restart recording when the maximum recording length is reached
- mediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
- @Override
- public void onInfo(MediaRecorder mr, int what, int extra) {
- switch (what){
- case MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN:
- Toast.makeText(SoundActivity.this, "Unknown error" , Toast.LENGTH_SHORT).show();
- finish();
- break;
- case MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED:
- Toast.makeText(SoundActivity.this, "The maximum recording length has been reached, start re-recording" , Toast.LENGTH_SHORT).show() );
- startRecording();
- break;
- case MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED:
- Toast.makeText(SoundActivity.this, "Not enough space to record" , Toast.LENGTH_SHORT).show();
- mediaRecorder.stop();
- break;
-
- }
- }
- });
- //Start recording by default
- startRecording();
- btnRecord.setBackgroundResource(R.drawable.ic_mic_black_24dp);
- //Start blowing detection and playback progress detection by default
- startCheckSound();
- }
-
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- if (PLAYING == STATUS){
- mediaPlayer.stop();
- mediaPlayer.release();
- }
- if (RECORDING == STATUS){
- mediaRecorder.stop();
- mediaRecorder.release();
- }
- //To prevent the timer from executing tasks after the Activity ends (very tricky)
- timer.cancel();
- }
-
- public void startRecording(){
- if (PLAYING == STATUS){
- stopPlay();
- }
- STATUS = RECORDING;
- //Set to recording mode
- tvTips.setText( "Recording, click the play button or microphone to stop recording" );
- btnRecord.setBackgroundResource(R.drawable.ic_mic_black_24dp);
-
- //Start recording settings
- mediaRecorder.reset(); // You can reuse the object by going back to setAudioSource() step
- mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
- mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
- mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
- try{
- mediaRecorder.setOutputFile(PATH_NAME);
- mediaRecorder.prepare ();
- mediaRecorder.start(); // Recording is now started
- }catch (IOException e){
- Toast.makeText(this, "Failed to prepare recording file" , Toast.LENGTH_SHORT).show();
- e.printStackTrace();
- finish();
- }
- }
-
- public void stopRecording(){
- if (RECORDING == STATUS){
- //Indicates that recording is in progress, set stop information
- tvTips.setText( "Recording stopped, playback started" );
- btnRecord.setBackgroundResource(R.drawable.ic_mic_none_black_24dp);
- mediaRecorder.stop();
- }
- }
-
-
-
- Handler handler= new Handler(new Handler.Callback() {
- @Override
- public boolean handleMessage(Message msg) {
- if (( Double )msg.obj > 70){
- imvSound.setImageResource(R.drawable.ic_volume_mute_valid_24dp);
- } else {
- imvSound.setImageResource(R.drawable.ic_volume_mute_gray_24dp);
- }
- return false ;
- }
- });
-
- Timer timer = new Timer();
- public void startCheckSound(){
- //Timed detection of peak values and playback progress
- timer.schedule(new TimerTask() {
- @Override
- public void run() {
- if (mediaRecorder != null ) {
- double amplitude = ( double )mediaRecorder.getMaxAmplitude();
- double db = 0;
- //Calculate decibels
- if (amplitude > 1)
- db = 20 * Math.log10(amplitude);
- Message msg = new Message();
- msg.obj = db;
- handler.sendMessage(msg);
- //If you need to detect the playback progress, you can use
- //mediaPlayer.getCurrentPosition()/mediaPlayer.getDuration();
- }
- }
- },0,100);
- }
-
-
- ProgressDialog mediaplayerPreparingDialog;
- public void startPlay(){
- if (RECORDING == STATUS){
- //If the playback starts from the recording state, re-read the new recording file
- STATUS = PLAYING;
- //Set up the audio player
- mediaPlayer = new MediaPlayer();
- mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
- mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
- @Override
- public void onCompletion(MediaPlayer mp) {
- //Set up after playing
- tvTips.setText( "After playing, click the microphone to re-record" );
- btnPlay.setBackgroundResource(R.drawable.ic_play_circle_filled_black_24dp);
- }
- });
- try {
- mediaPlayer.setDataSource(PATH_NAME);
- mediaPlayer.prepareAsync();
- } catch (IOException e) {
- e.printStackTrace();
- Toast.makeText(this, "The recording file has been lost" , Toast.LENGTH_SHORT).show();
- finish();
- }
- mediaplayerPreparingDialog.setTitle( "Preparing to play recording" );
- mediaplayerPreparingDialog.show();
-
- mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
- @Override
- public void onPrepared(MediaPlayer mp) {
- mediaplayerPreparingDialog.dismiss();
- mediaPlayer.start();
- }
- });
-
- } else if(PAUSING == STATUS){
- //Start playing from the paused state and play directly
- mediaPlayer.start();
- }
- //Start playing, set the button to pause
- btnPlay.setBackgroundResource(R.drawable.ic_pause_circle_filled_black_24dp);
-
- }
-
- public void pausePlay(){
- if (PLAYING == STATUS) {
- // Pause playback and set the button to start playback
- mediaPlayer.pause();
- btnPlay.setBackgroundResource(R.drawable.ic_play_circle_filled_black_24dp);
- STATUS = PAUSING;
- }
- }
-
- public void stopPlay(){
- if (mediaPlayer != null ) mediaPlayer.stop();
- }
- }
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 - mediaPlayer = new MediaPlayer();
- mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
- 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 - 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: - <vector xmlns:android= "http://schemas.android.com/apk/res/android"
- android:width= "24dp"
- android:height= "24dp"
- android:viewportWidth= "24.0"
- android:viewportHeight= "24.0" >
- <path
- android:fillColor= "#3F51B5"
- android:pathData= "***" />
- </vector>
use - <vector xmlns:android= "http://schemas.android.com/apk/res/android"
- android:width= "24dp"
- android:height= "24dp"
- android:viewportWidth= "24.0"
- android:viewportHeight= "24.0" >
- <path
- android:fillColor= "@color/colorPrimary"
- android:pathData= "***" />
- </vector>
The color will not be changed and will remain black. |