Android实现可录音/暂停录音/播放录音的录音软件

  • Post author:
  • Post category:其他




一、功能简介与操作视频


该APP功能完成音频的录制并命名保存与播放功能


1、录制

在音频录制界面点击

开始

按钮即可进行录制,录制过程中可以点击

暂停

按钮暂时停止录制,暂停可以继续录制,点击

停止

按钮结束录制,然后,跳出

命名和保存

框,点击

删除

按钮则不保存此次录音文件,在输入框输入文件名并点击

保存

按钮可以保存此次录音的音频文件


2、查看录音文件列表

在音频录制界面中点击

文件列表

按钮即可跳转查看录音文件列表,该列表显示了

ARecordFiles

文件夹中所有的录音文件,长按某个录音文件可以选择删除或者分享给手机上其他应用,点击某个录音文件,跳转到播放该录音文件界面


3、播放

进入播放界面后,点击

播放

按钮即可进行播放,播放过程中可以点击

暂停

按钮暂时停止播放,暂停可以继续播放,点击

结束

按钮结束播放。一次播放结束后,可以再次点击

播放

按钮重新播放



源码链接



二、文件夹结构

在这里插入图片描述


FileUtil.java

找出

ARecordFiles

文件下所有音频文件然后保存到

ArrayList


Pcm2WavUtil.java

将原始的

.pcm

音频文件转成可直接播放的

.wav

音频文件


PlayerManager.java

与音频播放有关


RecorderManager.java

与音频录制有关


TimerManager.java

与计时器有关


AudioPlay.java

实现具体的音频播放功能,与

activity_audio_play.xml

绑定


FileListAdapter.java

继承了

BaseAdapter

适配器,目的是将文件显示在

listview

控件中,与

activity_filelist

绑定


ListActivity.java

实现具体的文件显示功能,主要用到了

listview

控件,与

activity_list

绑定


MainActivity.java

实现具体的音频录制功能,与

activity_filelist

绑定


ListActivity.java

实现具体的文件显示功能,主要用到了

listview

控件,与

activity_main

绑定



三、实现



3.1 录制

使用java自带的AudioRecord进行录制

实现录音步骤:

①计算缓冲录音数据的字节数组的大小。AudioRecord 需要一个容器来缓冲来自硬件的音频信息。


bufferSize=AudioRecord.getMinBufferSize(AUDIO_SAMPLE_RATE,AUDIO_CHANNEL,AUDIO_ENCODING);

②创建AudioRecord对象


myrecord=new AudioRecord(AUDIO_INPUT,AUDIO_SAMPLE_RATE,AUDIO_CHANNEL,AUDIO_ENCODING,bufferSize);

③初始化一个buffer


buffer = new byte[bufferSize];

④开始录音

myrecord.startRecording();
Recordstatus=Status.STATUS_START;

⑤创建一个数据流,一边从AudioRecord中读取声音数据到初始化的buffer,一边将buffer中数据导入数据流,这里创建一个

新线程

完成该操作,注意使用

Environment.getExternalStorageDirectory()

获取手机存储的根目录

new Thread(()->{

            FileOutputStream os=null;

            try {

                //首先创建文件夹
                File dirFile = new File(Environment.getExternalStorageDirectory(),DirName);
                if(!dirFile.exists()){
                    dirFile.mkdirs();
                }
                File myfile=new File(dirFile,finalFileName);

                //如果文件存在,先删除文件
                if(myfile.exists()){
                    myfile.delete();
                }

                //然后创建新文件
                myfile.createNewFile();

                os = new FileOutputStream(myfile);
            }
            catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            catch (Exception e) {
                Log.e("TestFile", "Error on write File:" + e);
            }

            if(os!=null){
                //判断录音的状态
                while(Recordstatus==Status.STATUS_START){
                    int read = myrecord.read(buffer, 0, bufferSize);

                    // 如果读取音频数据没有出现错误,就将数据写入到文件
                    if (AudioRecord.ERROR_INVALID_OPERATION != read) {
                        try {
                            os.write(buffer);
                            os.flush();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                }
                try {
                    os.flush();
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        ).start();

⑥停止录音

		myrecord.stop();
        Recordstatus=Status.STATUS_STOP;

        myrecord.release();
        myrecord=null;
        

        Recordstatus=Status.STATUS_NO_READY;


另外,还可以使用MediaPlayer进行录制。

MediaPlayer比AudioRecord更上层,使用起来更方便、易用,但是AudioRecord偏底层,因此也就更灵活,我可以自由设置

位宽



采样率



通道数



录制时所使用的的麦克风

这些参数。


录制声音MediaRecorder和AudioRecord 区别

<1>.MediaRecorder录制的音频文件是经过压缩后的,需要设置编码器。并且录制的音频文件可以用系统自带的Music播放器播放。

<2>.而AudioRecord录制的是PCM格式的音频文件,需要用AudioTrack来播放,AudioTrack更接近底层。

<3>.在用MediaRecorder进行录制音视频时,最终还是会创建AudioRecord用来与AudioFlinger进行交互。

<4>.MediaRecorder录制的数据是 amr MP3 格式 ,AudioRecord录制出来的是比较原始的PCM音频格式 ,PCM经过编码压缩可以为 amr MP3 AAC。

<5>.如果想简单地做一个录音机,录制成音频文件,则推荐使用 MediaRecorder,而如果需要对音频做进一步的算法处理、或者采用第三方的编码库进行压缩、以及网络传输等应用,则建议使用 AudioRecord



3.2 暂停/继续录制


实现该功能最重要的是对录音状态的控制

构建状态类,控制录音状态
public enum Status {
        //未开始
        STATUS_NO_READY,
        //预备
        STATUS_READY,
        //录音
        STATUS_START,
        //暂停
        STATUS_PAUSE,
        //停止
        STATUS_STOP
    }
    //初始状态为未开始
private Status Recordstatus=Status.STATUS_NO_READY;

每次

录音开始—录音暂停/停止

这一阶段我们称之为一个

周期

,而每个周期都

创建一个临时

.pcm

文件

,保存该周期录制的音频,因此

暂停/继续录制

功能的实现可看成是

将录制分成多个周期

,最终得到多个临时小文件,最后将这些小文件合并成为一个大的

.pcm

文件完成对该功能的实现。

这里注意:

每个周期的临时文件命名不能冲突,否则会出现覆盖,导致某部分音频的缺失,另外要将各个周期的临时文件名

按照创建顺序保存下来

,保证最后音频的顺序性。

//合并所有临时文件成为一个文件
    public void mergeAllFiles(){

        File finalFile =new File(Environment.getExternalStorageDirectory(),DirName+"/"+Afilename);
        FileOutputStream os=null;

        try {
            //创建最终文件
            finalFile.createNewFile();
            os = new FileOutputStream(finalFile);

            //将每个临时文件的内容读出来并保存到最终文件中,同时删除临时文件
            for (String fileName : AllFiles) {
                FileInputStream in =null;
                File infile=new File(Environment.getExternalStorageDirectory(),DirName+"/"+fileName);
                in = new FileInputStream(infile);
                byte[] data = new byte[bufferSize];
                while (in.read(data) != -1) {
                    os.write(data);
                }
                in.close();
                infile.delete();//删除临时文件
            }

            AllFiles.clear();//清除ArrayList
            FileVersion=1;//清除版本号
            os.close();

        }catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        catch (Exception e) {
            Log.e("TestFile", "Error on write File:" + e);
        }

    }



3.3 pcm转wav


pcm与wav:

一般通过麦克风采集的录音数据都是PCM格式的,即不包含头部信息,播放器无法知道音频采样率、位宽等参数,导致无法播放,在pcm文件的数据开头加入WAVE HEAD数据即可转成wav文件


wav头文件:

在这里插入图片描述

偏移地址 命名 内容
00-03 ChunkId “RIFF”
4-07 ChunkSize 下个地址开始到文件尾的总字节数(此Chunk的数据大小)
08-11 fccType “WAVE”
12-15 SubChunkId1 “fmt “,最后一位空格
16-19 SubChunkSize1 一般为16,表示fmt Chunk的数据块大小为16字节,即20-35
20-21 FormatTag 1:表示是PCM 编码
22-23 Channels 声道数,单声道为1,双声道为2
24-27 SamplesPerSec 采样率
28-31 BytesPerSec 码率 :采样率 * 采样位宽 * 声道个数,bytePerSecond = sampleRate * (bitsPerSample / 8) * channels
32-33 BlockAlign 每次采样的大小:位宽*声道数/8
34-35 BitsPerSample 位宽
36-39 SubChunkId2 “data”
40-43 SubChunkSize2 音频数据的长度
44-… data 音频数据


Pcm2WavUtil.java

//pcm转wav工具
public class Pcm2WavUtil {
    private int sampleRateInHz;//采样率
    private int channelConfig;//声道数
    private int audioFormat;//采样位数
    private int mBufferSize;//最小缓冲区大小

    //构造函数
    public Pcm2WavUtil(int sampleRate,int channel,int format,int size){
        sampleRateInHz=sampleRate;
        channelConfig=(channel == AudioFormat.CHANNEL_IN_MONO )? 1 : 2;//声道数
        audioFormat=(format==AudioFormat.ENCODING_PCM_16BIT) ? 16 : 8;//采样位数
        mBufferSize=size;
    }

    //pcm转wav函数
    public  void pcmToWav(String inFilename, String outFilename) {
        FileInputStream in;
        FileOutputStream out;
        long totalAudioLen;
        long totalDataLen;

        long byteRate =  sampleRateInHz *audioFormat *channelConfig  ;//码率
        byte[] data = new byte[mBufferSize];
        try {
            //首先创建文件夹
            File inFile = new File(Environment.getExternalStorageDirectory(),inFilename);
            File outFile =new File(Environment.getExternalStorageDirectory(),outFilename);
            //如果文件存在,先删除文件
            if(outFile.exists()){
                outFile.delete();
            }

            //然后创建新文件
            outFile.createNewFile();

            in = new FileInputStream(inFile);
            out = new FileOutputStream(outFile);
            totalAudioLen = in.getChannel().size();
            totalDataLen = totalAudioLen + 36;

            writeWaveFileHeader(out, totalAudioLen, totalDataLen,
                    byteRate);
            while (in.read(data) != -1) {
                out.write(data);
            }
            in.close();
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 加入wav文件头
     */
    private void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
                                            long totalDataLen, long byteRate)
            throws IOException {
        byte[] header = new byte[44];
        // RIFF/WAVE header
        header[0] = 'R';
        header[1] = 'I';
        header[2] = 'F';
        header[3] = 'F';
        header[4] = (byte) (totalDataLen & 0xff);
        header[5] = (byte) ((totalDataLen >> 8) & 0xff);
        header[6] = (byte) ((totalDataLen >> 16) & 0xff);
        header[7] = (byte) ((totalDataLen >> 24) & 0xff);
        //WAVE
        header[8] = 'W';
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        // 'fmt ' chunk
        header[12] = 'f';
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';
        // 4 bytes: size of 'fmt ' chunk
        header[16] = 16;
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        // format = 1
        header[20] = 1;
        header[21] = 0;
        header[22] = (byte) channelConfig;
        header[23] = 0;
        header[24] = (byte) (sampleRateInHz & 0xff);
        header[25] = (byte) ((sampleRateInHz >> 8) & 0xff);
        header[26] = (byte) ((sampleRateInHz >> 16) & 0xff);
        header[27] = (byte) ((sampleRateInHz >> 24) & 0xff);
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
        // block align
        header[32] = (byte) (channelConfig *audioFormat  / 8);
        header[33] = 0;
        // bits per sample
        header[34] = (byte)audioFormat;
        header[35] = 0;
        //data
        header[36] = 'd';
        header[37] = 'a';
        header[38] = 't';
        header[39] = 'a';
        header[40] = (byte) (totalAudioLen & 0xff);
        header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
        header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
        header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
        out.write(header, 0, 44);
    }

}



3.4 自定义文件名保存

当我们完成音频文件保存时,我们希望自定义要保存的同时,同时系统能够检测输入的文件名是否存在

这里我们使用

带EditText的AlertDialog



但是

在使用AlertDialog时,无论点击“确定”(PositiveButton)还是“取消”(NegativeButton),对话框都会消失,但是在输入文件名为空或者该文件存在,对话框不应该消失,而是应该保留该对话框并提示用户


解决方法:


在创建AlertDialog时setPositiveButton方法的OnClickListener参数需传入null,然后让dialog show出来以后,再通过getButton(AlertDialog.BUTTON_POSITIVE)方法重新得到确定按钮,重设点击事件,这时如果不手动去调dialog.dismiss(),对话框就不会消失了

		final EditText init = new EditText(this);//带EditText的AlertDialog
        AlertDialog myalert = new AlertDialog.Builder(this).setTitle("命名和保存")
                .setIcon(R.mipmap.ic_launcher)
                .setView(init)
                .setPositiveButton("保存", null)
                .setNegativeButton("删除",new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        audiorecord.deleteFile();//点击删除后将最后的临时文件删除
                    }
                }).create();
        myalert.show();
        myalert.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    String input = init.getText().toString();

                    if (input.equals("")) {
                      
                        Toast.makeText(getApplicationContext(), "文件名不能为空!", Toast.LENGTH_SHORT).show();
                        return;
                    }
                    else if(audiorecord.isFile(input)) {//判断文件是否存在
                       
                        Toast.makeText(getApplicationContext(), "该文件已存在!", Toast.LENGTH_SHORT).show();
                        return;
                    }
                    else{
         
                        //转换,必须写在dialog内部!
                        audiorecord.PcmTOWav(input+".wav");

                        Toast.makeText(getApplicationContext(), "文件保存成功!", Toast.LENGTH_SHORT).show();
                        //让AlertDialog消失
                        myalert.dismiss();

                        audiorecord.deleteFile();//将最后的临时文件删除
                        
                        //跳转到文件列表
                        Intent it=new Intent(MainActivity.this,ListActivity.class);
                        startActivity(it);
                    }
                }

        });



3.5 文件列表显示

主要使用

listview

空间显示所有文件

<ListView
        android:layout_width="match_parent"
        android:layout_height="655dp"
        android:id="@+id/listView"></ListView>

一个ListView的创建需要3个元素:

(1)ListView中的每一列的View。

(2)填入View的数据或者图片等。

(3)连接数据与ListView的适配器。


也就是说,要使用ListView,首先要了解什么是适配器。

适配器是一个连接数据和AdapterView(ListView就是一个典型的AdapterView)的桥梁,通过它能有效地实现数据与AdapterView的分离设置,使AdapterView与数据的绑定更加简便,修改更加方便。

因此

FileListAdapter.java

实现了该适配器,使用了BaseAdapter自定义适配器实现ListView

listView = (ListView) findViewById(R.id.listView);
list = FileUtil.getWavFiles();
adapter = new FileListAdapter(this, list);
listView.setAdapter(adapter);



(1)功能1:长按某个Item选择删除该Item对应的文件


这里设置长按监听器,进行删除

listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
                File file= list.get(position);
                String filename=file.getName();
                Toast.makeText(getApplicationContext(),"you choose the file : "+filename,Toast.LENGTH_SHORT).show();
                //弹框确定是否删除
                myDialog(filename);//使用了alertdialog

                return true;
                //关于返回值,若返回False,则是当长按时,既调用onItemLongClick,同时调用onItemLongClick后
                //还会调用onItemClick,就是说会同时调用onItemLongClick,和onItemClick,
                //若返回true,则只调用onItemLongClick
            }
        });



(2)功能2:单击某个Item跳转到播放界面


设置单击监听器即可

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {               
                File file= list.get(position);
                String filename=file.getName();
                Intent playView=new Intent(ListActivity.this,AudioPlay.class);
                playView.putExtra("file",filename);
                startActivity(playView);
            }
        });



3.6 播放


使用

MediaPlayer

播放音频

,主要使用如下几个函数

setDataSource(String filename):设置要播放的文件名
prepare():准备(同步)
release():释放MediaPlayer对象
start():开始播放
stop():停止播放
pause():暂停
reset():重置MediaPlayer对象
setOnCompletionListener(MediaPlayer.OnCompletionListener listener): 网络流媒体播放结束监听

还可以使用

AudioTracker

播放音频,但是

AudioTracker

只能播放

.pcm

文件(使用

AudioTracker

相当于使用

AudioRecord

的逆过程),所以对于

.wav

文件需要跳过前44字节的数据。

为了简便使用,这里我们选择了

MediaPlayer

,但是因为本程序中,在

播放自动结束

后,我们还需要控制时钟并且设置播放状态和按钮,因此使用

MediaPlayer

不如

AudioRecord

灵活方便,在这里,我们使用布局

重新跳转回当前播放界面

来实现(目前我只会这样,如果你有更好的方法,可以联系我),如下图

		//监听MediaPlayer播放完成
        mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                //当自动播放完成后,需要重新设置状态,并且暂停计时器,目前我只会这样写:
                //关闭当前页面,跳转到新的播放页面
                stop();
                Intent it=new Intent(mcontext, AudioPlay.class);
                it.putExtra("file",filename);
                it.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);//方法1:关闭当前页面
                mcontext.startActivity(it);


            }
        });


播放声音时MediaPlayer和AudioTrack的区别

播放声音可以用MediaPlayer和AudioTrack,两者都提供了java API供应用开发者使用。虽然都可以播放声音,但两者还是有很大的区别的。

<1>.其中最大的区别是MediaPlayer可以播放多种格式的声音文件,例如 MP3,AAC,WAV,OGG,MIDI等。MediaPlayer会在framework层创建对应的音频解码器。

<2>.而AudioTrack只能播放已经解码的PCM流,如果是文件的话只支持wav格式的音频文件,因为wav格式的音频文件大部分都是PCM流。AudioTrack不创建解码器,所以只 能播放不需要解码的wav文件。

<3>当然两者之间还是有紧密的联系,MediaPlayer在framework层还是会创建AudioTrack,把解码后的PCM数流传递给AudioTrack,AudioTrack再传递给AudioFlinger进行混音,然后才传递给硬件播放。



3.7 计时器

主要使用

Chronometer

控件,使用起来操作还是很简单的

		<Chronometer
                 android:layout_width="match_parent"
                 android:layout_height="200dp"
                 android:format="00:00:00"
                 android:gravity="center"
                 android:textSize="70dp"
                 android:id="@+id/timer"/>



3.8 跳转到下一个界面前删除当前界面

有时当我们从当前界面跳转到下一个界面时,我们不希望按返回键又返回到当前界面


方法一(使用setflags):

Intent it = new Intent();
it.setClass(ListActivity.this, ListActivity.class);
it.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);//方法1:关闭当前页面
startActivity(it);


方法二(使用finish):

Intent it = new Intent();
it.setClass(ListActivity.this, ListActivity.class);
startActivity(it);
finish();



3.9 分享录制好的音频文件到其他应用(比如QQ,微信)


Android7.0以上推荐使用

FileProvider



Step 1:在AndroidManifest.xml中<application>标签下声明一个provider

<provider
    android:authorities="com.example.myaudiorecorder.fileprovider"
    android:name="androidx.core.content.FileProvider"
    android:grantUriPermissions="true"
    android:exported="false">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/filepaths"/>
</provider>

注意:


authorities

:app的包名.fileProvider,我的app包就是

com.example.myaudiorecorder



grantUriPermissions

:必须是true,表示授予 URI 临时访问权限


exported



true

: The provider is available to other applications.

false

: The provider is not available to other applications.


resource

:自定义的xml文件(下面会介绍)



Step2:在res目录下新建一个xml文件夹,并且新建一个file_paths的xml文件

在这里插入图片描述



Step3:打开file_paths.xml文件,添加指定的分享目录

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path path="ARecordFiles" name="ARecordFiles" />
</paths>


在paths节点内部支持以下几个子节点,分别为:


子节点

对应路径

例子
files_path Context.getFilesDir()
cache_path Context.getCacheDir()
external_path Environment.getExternalStorageDirectory() /storage/emulated/0/
external_files_path Context.getExternalFilesDir()
externa_cache_path Context.getExternalCacheDir()

我的文件路径是

Environment.getExternalStorageDirectory()

,所以paths子节点name是

external_path

。path=”ARecordFiles”是我文件存放的路径,即

Environment.getExternalStorageDirectory()/ARecordFiles



如果路径没写对,分享的时候会报“获取资源失败”



Step4:使用FileProvider的API

private void share(String filename){
        //创建要分享的文件
        File file=new File(Environment.getExternalStorageDirectory(),"ARecordFiles/"+filename);
        Intent share = new Intent(Intent.ACTION_SEND);
        Uri uri;

        //Android7.0版本以上使用FileProvider
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
            uri = FileProvider.getUriForFile(
                    getApplicationContext(),
                    "com.example.myaudiorecorder.fileprovider",
                    file);//第二个参数为你的包名.fileprovider
        }
        else{
            uri = Uri.fromFile(file);
        }

        share.setType("*/*");//此处可发送多种文件

        share.putExtra(Intent.EXTRA_STREAM, uri);

        share.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        share.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        share.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//这一句一定得写

        startActivity(share);
    
}


核心代码为这一行:


uri = FileProvider.getUriForFile(getApplicationContext(),"com.example.myaudiorecorder.fileprovider",file);

  • 第二个参数就是我们在

    androidManife.xml

    中的provider的参数

    authorities
  • 第三个参数是指定的文件File


效果展示:



3.10 长按listview中某个Item弹出选择菜单,选择删除或者分享文件

一共有

两种方法

,分别是使用

ContextMenu



PopupMenu


方法1:使用ContextMenu

registerForContextMenu(listView);//进行注册
	@Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo){
        super.onCreateContextMenu(menu, v, menuInfo);
        AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
        filename=list.get(info.position).getName();//保存选中的文件名
        Toast.makeText(getApplicationContext(),"you choose the file : "+filename,Toast.LENGTH_SHORT).show();
        menu.add(0,0,0,"删除");
        menu.add(0,1,0,"分享");
    }
    //方法1对应
    @Override
    public boolean onContextItemSelected(MenuItem item) {

        switch (item.getItemId()) {
            case 0: {
                myDialog(filename);
                break;
            }
            case 1:{
                share(filename);
                break;
            }
        }
        return true;
    }


方法2:使用PopupMenu

 		listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
                File file= list.get(position);
                filename=file.getName();
                Toast.makeText(getApplicationContext(),"you choose the file : "+filename,Toast.LENGTH_SHORT).show();

                showPopupMenu(view);
                return true;
            }
        });
	private void showPopupMenu(View v){
        //定义PopupMenu对象
        PopupMenu popupMenu = new PopupMenu(this, v);
        //设置PopupMenu对象的布局
        popupMenu.getMenuInflater().inflate(R.menu.menu, popupMenu.getMenu());
        //设置PopupMenu的点击事件
        popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                switch(item.getItemId()){
                    case R.id.deleteFile:{
                        myDialog(filename);
                        break;
                    }
                    case R.id.shareFile:{
                        share(filename);
                        break;
                    }
                }

                return true;
            }
        });

        //显示菜单
        popupMenu.show();

    }



menu

文件夹下新建

menu.xml

文件

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/deleteFile" android:title="删除"></item>
    <item android:id="@+id/shareFile" android:title="分享"></item>

</menu>


实验证明,ContextMenu更好,可以直接在所按下之处的正下方显示,但是PopupMenu在Item的正下方显示


效果显示:


在这里插入图片描述



四、权限添加

使用前要在

AndroidMainfest.xml

添加如下权限:

<!-- 录音权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- 往SDCard写入数据权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 在SDCard中创建与删除文件权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<!--SDCard读取数据权限-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />



五、总结

总的来说,实现录音软件并不难,会用AudioRecord就行,如果追求简单,使用MediaRecorder就行,这个类更容易使用。不过我开发app主要是为

FMCW测距

提供双麦克风数据,因此AudioRecord对我来说更有用。

最后,在播放录音时,因为已经转成了WAV文件,所以再使用AudioTrack就没必要了,MediaPlayer更易使用,并且自带的pause函数,对于暂停播放功能的实现更容易。但是唯一的缺点就是在播放自动结束的时候对状态的恢复很麻烦,导致PlayerManager和AudioPlayer耦合度增大,PlayerManager类需要知道页面跳转关系,因此不可作为可复用的组件来使用了,这一点上AudioTrack更好。

自己实现的录音软件没有手机自带的好,环境噪音更大,这是因为手机自带的录音软件使用了一个麦克风进行降噪,但是自制录音软件可以自定义采样率,单声道还是双声道这些,也就更灵活。



版权声明:本文为dsaaf21原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。