博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android简易“吹一吹实现”以及录音和播放示例
阅读量:5773 次
发布时间:2019-06-18

本文共 9882 字,大约阅读时间需要 32 分钟。

最近在做一些跟传感器相关的东西,有注意到以前腾讯微博以前出过一个吹一吹交互,虽然和传感器无关,但是感觉也比较有兴趣,就写了一个拙劣的demo,因为接触媒体文件操作比较少,顺带写了一个录音和播放的例子,总结了一下一些小坑的地方,一并在此分享给大家。

主要思路和坑的地方

主要的思路是通过MediaRecorder提供的getMaxAmplitude()函数,获取一段时间内输入的音频最大幅值来进行检测,所以除了吹的动作,其他声音也会被录进来。

“吹”这个动作如果想和其他动作进行区分,其实本质在于吹的时候靠近听筒,即便吹这个动作本身音量不大,但是麦克风看来它的分贝是很大的,所以我们可以通过检测分贝来判断这个动作是否是吹(如果其他声音更大……那……算了不管了)。

这里附上前人的一些参考资料:

一看到这个网站后面是htm,仿佛就明白了这个网站的框架…

这个东西坑的地方在于MediaplayerMediaRecorder这两个东西stop和start的顺序经常是严格被限制的,在退出时如果没有成功释放资源,有时候Activity再启动时,由于上次退出没有stop,再重新start也会抛出异常。

权限添加

主要界面

大概想了一个简单的界面,好吧其实是左下角的音响闪动,忘记修改文字描述了

clipboard.png

布局文件:

主要代码

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;    //用于音频录制    MediaRecorder mediaRecorder;    //用于音频播放    MediaPlayer mediaPlayer;    //录制按钮    Button btnRecord;    //播放按钮    Button btnPlay;    //提示信息    TextView tvTips;    //吹一吹小音箱    ImageView imvSound;    //播放进度条    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(){        //控件初始化        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 ){                    //如果是在录制,点击则停止录制并且播放                    stopRecording();                    startPlay();                }else if (STATUS == PAUSING){                    startPlay();                } else {                    //如果是在播放,点击则暂停                    pausePlay();                }            }        });        btnRecord.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                if (STATUS == PLAYING || STATUS == PAUSING){                    //如果是在播放或者暂停,点击开始录制                    startRecording();                }else {                    //如果在录制,点击开始播放                    stopRecording();                    startPlay();                }            }        });        mediaRecorder = new MediaRecorder();        //设置到达最大录制长度时重头开始录制        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, "未知错误", Toast.LENGTH_SHORT).show();                        finish();                        break;                    case MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED:                        Toast.makeText(SoundActivity.this, "已达到最大录制长度,开始重新录制", Toast.LENGTH_SHORT).show( );                        startRecording();                        break;                    case MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED:                        Toast.makeText(SoundActivity.this, "空间不足,无法录制", Toast.LENGTH_SHORT).show();                        mediaRecorder.stop();                        break;                }            }        });        //默认开始录制        startRecording();        btnRecord.setBackgroundResource(R.drawable.ic_mic_black_24dp);        //默认开始吹一吹检测以及播放进度检测        startCheckSound();    }    @Override    protected void onDestroy() {        super.onDestroy();        if (PLAYING == STATUS){            mediaPlayer.stop();            mediaPlayer.release();        }        if (RECORDING == STATUS){            mediaRecorder.stop();            mediaRecorder.release();        }        //为了防止Activity结束后有时候这个timer还在定时执行任务(很坑)        timer.cancel();    }    public void startRecording(){        if (PLAYING == STATUS){            stopPlay();        }        STATUS = RECORDING;        //设置为录制状态        tvTips.setText("正在录制,点击播放按钮或者麦克风停止录制");        btnRecord.setBackgroundResource(R.drawable.ic_mic_black_24dp);        //开始录制的设置        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, "准备录制文件失败", Toast.LENGTH_SHORT).show();            e.printStackTrace();            finish();        }    }    public void stopRecording(){        if (RECORDING == STATUS){            //说明正在录制,设置停止信息            tvTips.setText("已停止录制,开始播放");            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(){        //定时检测峰值,以及检测播放进度        timer.schedule(new TimerTask() {            @Override            public void run() {                if (mediaRecorder != null) {                    double amplitude = (double)mediaRecorder.getMaxAmplitude();                    double db = 0;                    //计算分贝                    if (amplitude > 1)                        db = 20 * Math.log10(amplitude);                    Message msg = new Message();                    msg.obj = db;                    handler.sendMessage(msg);                    //如果需要检测播放进度可以使用                    //mediaPlayer.getCurrentPosition()/mediaPlayer.getDuration();                }            }        },0,100);    }    ProgressDialog mediaplayerPreparingDialog;    public void startPlay(){        if (RECORDING == STATUS){            //如果是从录制状态开始播放,则重新读取新的录制文件            STATUS = PLAYING;            //设置音频播放器            mediaPlayer = new MediaPlayer();            mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);            mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {                @Override                public void onCompletion(MediaPlayer mp) {                    //播放完设置                    tvTips.setText("播放完毕,可点击麦克风重新录制");                    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, "录音文件已丢失", Toast.LENGTH_SHORT).show();                finish();            }            mediaplayerPreparingDialog.setTitle("正在准备播放录音");            mediaplayerPreparingDialog.show();            mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {                @Override                public void onPrepared(MediaPlayer mp) {                    mediaplayerPreparingDialog.dismiss();                    mediaPlayer.start();                }            });        }else if(PAUSING == STATUS){            //从暂停状态开始播放则直接播放            mediaPlayer.start();        }        //开始播放,设置按钮为暂停        btnPlay.setBackgroundResource(R.drawable.ic_pause_circle_filled_black_24dp);    }    public void pausePlay(){        if (PLAYING == STATUS) {            //暂停播放,设置按钮为开始播放            mediaPlayer.pause();            btnPlay.setBackgroundResource(R.drawable.ic_play_circle_filled_black_24dp);            STATUS = PAUSING;        }    }    public void stopPlay(){        if (mediaPlayer != null) mediaPlayer.stop();    }}

Media和IllegalStateException

这个就是之前提到的由于没有按顺序释放资源或者stop掉这两个破玩意儿,可能会导致的各种错误,所以我很无奈地设置了一个STATUS变量,并且在ActivityOnDestoy里对两个东西进行了stop,其实一般还会使用release释放掉资源…大家随意吧…

QCMediaPlayer mediaplayer NOT present

!!!我就知道,如果你看到这个地方,一定也对这个错误感到莫名其妙。我记得好像上古时期,也就是上次我写这个的时候也被坑了。

论坛上有人说这个东西在4.4以下的系统就容易出现,但是我也只能感觉不明觉厉,我一开始用的是MediaPlayer.create(this,Uri.parse(PATH_NAME))来创建MediaPlayer,于是换成了

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

好吧,然后问题就解决了,我也是无语了。我觉得这个地方是一个很久远的坑了,查原因一时也没查到。我只能推测大概因为create函数创建时没有指定AudioStreamType导致使用了默认的

private int mStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;

在某些设备上可能不支持,于是就出了问题= =好吧,我也不知道还能说啥,就酱…

Vector Asset添加的图标颜色不变化

如上,我的播放按钮啊,音响啊,之类的图标都是通过Vector Asset添加的,这也是一个比较久远的坑了,但是以前也没有记下来,即在Android L以下的版本中,Vector Asset添加的图标,修改颜色时不能使用颜色的引用,而要直接写颜色,例如:

使用

则导致颜色并不会修改,依然是黑色

转载地址:http://nuoux.baihongyu.com/

你可能感兴趣的文章
使用@media实现IE hack的方法
查看>>
oracle体系结构
查看>>
Microsoft Exchange Server 2010与Office 365混合部署升级到Exchange Server 2016混合部署汇总...
查看>>
Proxy服务器配置_Squid
查看>>
【SDN】Openflow协议中对LLDP算法的理解--如何判断非OF区域的存在
查看>>
纯DIV+CSS简单实现Tab选项卡左右切换效果
查看>>
Centos7同时运行多个Tomcat
查看>>
使用CocoaPods过程中的几个问题
查看>>
Spring boot 整合CXF webservice 全部被拦截的问题
查看>>
Pinpoint跨节点统计失败
查看>>
机房带宽暴涨问题分析及解决方法
查看>>
XP 安装ORACLE
查看>>
八、 vSphere 6.7 U1(八):分布式交换机配置(vMotion迁移网段)
查看>>
[转载] 中华典故故事(孙刚)——19 万岁
查看>>
php5编译安装常见错误和解决办法集锦
查看>>
Unable to determine local host from URL REPOSITORY_URL=http://
查看>>
ORACLE配置,修改tnsnames.ora文件实例
查看>>
Workstation服务无法启动导致无法访问文件服务器
查看>>
Linux常用命令(一)
查看>>
一个自动布署.net网站的bat批处理实例
查看>>