Android音频处理——通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能

时间:2022-09-25 18:17:55

Android音频处理——通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能


音频这方面很博大精深,我这里肯定讲不了什么高级的东西,最多也只是一些基础类知识,首先,我们要介绍一下Android他提供的录音类,实际上他有两个,一个是MediaRecorder,还有一个就是我们今天要用到的AudioRecord,那他们有什么区别呢?

一.区别

MediaRecorder和AudioRecord都可以录制音频,区别是MediaRecorder录制的音频文件是经过压缩后的,需要设置编码器。并且录制的音频文件可以用系统自带的Music播放器播放。

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

PCM可能更加可以理解为音频的源文件

二.优缺点

  • AudioRecord

主要是实现边录边播以及对音频的实时处理,这个特性让他更适合在语音方面有优势

优点:语音的实时处理,可以用代码实现各种音频的封装

缺点:输出是PCM格式文件,如果保存成音频文件,是不能够被播放器播放的,所以必须先写代码实现数据编码以及压缩

  • MediaRecorder

已经集成了录音、编码、压缩等,支持少量的录音音频格式,大概有,aac,amr,3gp等

优点:集成,直接调用相关接口即可,代码量小

缺点:无法实时处理音频;输出的音频格式不是很多,例如没有输出mp3格式文件

三.准备工作

我们要实现的是一个实时的去录音,播放,停止等功能的测试案例,那我们肯定要准备点什么,比如说,我这里先创建一个项目——PCMSample

然后写个布局

layout_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="10dp">

    <Button
        android:id="@+id/startAudio"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/button_bg"
        android:text="开始录音"
        android:textColor="@android:color/white"/>

    <Button
        android:id="@+id/stopAudio"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="10dp"
        android:layout_marginTop="5dp"
        android:background="@drawable/button_bg"
        android:enabled="false"
        android:text="停止录音"
        android:textColor="@android:color/white"/>

    <Button
        android:id="@+id/playAudio"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/button_bg"
        android:enabled="false"
        android:text="播放音频"
        android:textColor="@android:color/white"/>

    <Button
        android:id="@+id/deleteAudio"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:background="@drawable/button_bg"
        android:text="删除PCM"
        android:textColor="@android:color/white"/>

    <ScrollView
        android:id="@+id/mScrollView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginTop="5dp"
        android:layout_weight="1">

        <TextView
            android:id="@+id/tv_audio_succeess"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="初始化完成...."
            android:textColor="@color/colorAccent"/>

    </ScrollView>

</LinearLayout>

可以预览一下

Android音频处理——通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能

这里我给按钮加了一个扁平的效果,实际上写了一个xml,很简单

button_bg.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:state_pressed="true">
        <shape>
            <corners android:radius="30dp"/>
            <solid android:color="@color/colorPrimary"/>
        </shape>
    </item>

    <item android:state_pressed="false">
        <shape>
            <corners android:radius="30dp"/>
            <solid android:color="@color/colorPrimaryDark"/>
        </shape>
    </item>

</selector>

好的,回到正题,我们这里有四个按钮,分别是开始。停止,播放,和删除,我们就是要实现这四个功能,在此之前,我们还需要做的事情就是添加权限,因为我们要录音和写内存卡文件,所有需要这两个权限即可

    <!--录音-->
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <!--读取SD卡-->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

这里初始化什么的就不说了,我们直接进入正题

四.开始录音

开始录音的话,这里,我们定义一个变量isRecording去控制,这样就比较好结束了,而且要注意的是,录音是不能放在UI线程的,你懂的,所以我们可以写一个开始录音的方法

    //开始录音
    public void StartRecord() {
        Log.i(TAG,"开始录音");
        //16K采集率
        int frequency = 16000;
        //格式
        int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
        //16Bit
        int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
        //生成PCM文件
        file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/reverseme.pcm");
        Log.i(TAG,"生成文件");
        //如果存在,就先删除再创建
        if (file.exists())
            file.delete();
            Log.i(TAG,"删除文件");
        try {
            file.createNewFile();
            Log.i(TAG,"创建文件");
        } catch (IOException e) {
            Log.i(TAG,"未能创建");
            throw new IllegalStateException("未能创建" + file.toString());
        }
        try {
            //输出流
            OutputStream os = new FileOutputStream(file);
            BufferedOutputStream bos = new BufferedOutputStream(os);
            DataOutputStream dos = new DataOutputStream(bos);
            int bufferSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding);
            AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, bufferSize);

            short[] buffer = new short[bufferSize];
            audioRecord.startRecording();
            Log.i(TAG, "开始录音");
            isRecording = true;
            while (isRecording) {
                int bufferReadResult = audioRecord.read(buffer, 0, bufferSize);
                for (int i = 0; i < bufferReadResult; i++) {
                    dos.writeShort(buffer[i]);
                }
            }
            audioRecord.stop();
            dos.close();
        } catch (Throwable t) {
            Log.e(TAG, "录音失败");
        }
    }

首先,这里我们了解一下采样率,编码,音频流等基本的概念,剩下的大多是读写流的操作了,我们通过创建一个AudioRecord去写pcm文件,定义一个while循环,用我们刚才定义的isRecording控制,所以,我们的点击事件就

        case R.id.startAudio:
                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        StartRecord();
                        Log.e(TAG,"start");
                    }
                });
                thread.start();
                printLog("开始录音");
                ButtonEnabled(false, true, false);
                break;

这里要注意一下thread.start();开启线程,同时打印出log,具体代码如下

    //打印log
    private void printLog(final String resultString) {
        tv_audio_succeess.post(new Runnable() {
            @Override
            public void run() {
                tv_audio_succeess.append(resultString + "\n");
                mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
            }
        });
    }

这里,我为了防止ANR,所以控制了一下按钮的焦点

    //获取/失去焦点
    private void ButtonEnabled(boolean start, boolean stop, boolean play) {
        startAudio.setEnabled(start);
        stopAudio.setEnabled(stop);
        playAudio.setEnabled(play);
    }

好的,我们运行一下

Android音频处理——通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能

看起来没什么变化,但是你去内存卡中就会发现多了一个pcm文件

Android音频处理——通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能

当然,你只是点击启动录音是不会生成这个pcm文件的,你需要点击停止停止录音的按钮

五.停止录音

停止录音很简单,我们控制通过改变写入流就好了

            case R.id.stopAudio:
                isRecording = false;
                ButtonEnabled(true, false, true);
                printLog("停止录音");
                break;

这样才会生成PCM

六播放音频

现在有了PCM我们可以试着去播放了,写一个播放的方法

   //播放文件
    public void PlayRecord() {
        if(file == null){
            return;
        }
        //读取文件
        int musicLength = (int) (file.length() / 2);
        short[] music = new short[musicLength];
        try {
            InputStream is = new FileInputStream(file);
            BufferedInputStream bis = new BufferedInputStream(is);
            DataInputStream dis = new DataInputStream(bis);
            int i = 0;
            while (dis.available() > 0) {
                music[i] = dis.readShort();
                i++;
            }
            dis.close();
            AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                    16000, AudioFormat.CHANNEL_CONFIGURATION_MONO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    musicLength * 2,
                    AudioTrack.MODE_STREAM);
            audioTrack.play();
            audioTrack.write(music, 0, musicLength);
            audioTrack.stop();
        } catch (Throwable t) {
            Log.e(TAG, "播放失败");
        }
    }

正如上面所说,我们播放需要用到AudioTrack,调用他的play方法以及设置一些参数即可

七.删除音频

删除音频只需要删除这个pcm文件就行

     //删除文件
    private void deleFile() {
        if(file == null){
            return;
        }
        file.delete();
        printLog("文件删除成功");
    }

这就是大致的录音逻辑,虽然看起来很简单,但是这正是现在很多语音和音频的最基础部分,特别是语音,如果你从事语音的工作,我相信你会感谢我的!

好了,最后放上完整的代码:

MainActivity

package com.liuguilin.pcmsample;

import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ScrollView;
import android.widget.TextView;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    public static final String TAG = "PCMSample";

    //是否在录制
    private boolean isRecording = false;
    //开始录音
    private Button startAudio;
    //结束录音
    private Button stopAudio;
    //播放录音
    private Button playAudio;
    //删除文件
    private Button deleteAudio;

    private ScrollView mScrollView;
    private TextView tv_audio_succeess;

    //pcm文件
    private File file;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
    }

    //初始化View
    private void initView() {

        mScrollView = (ScrollView) findViewById(R.id.mScrollView);
        tv_audio_succeess = (TextView) findViewById(R.id.tv_audio_succeess);
        printLog("初始化成功");
        startAudio = (Button) findViewById(R.id.startAudio);
        startAudio.setOnClickListener(this);
        stopAudio = (Button) findViewById(R.id.stopAudio);
        stopAudio.setOnClickListener(this);
        playAudio = (Button) findViewById(R.id.playAudio);
        playAudio.setOnClickListener(this);
        deleteAudio = (Button) findViewById(R.id.deleteAudio);
        deleteAudio.setOnClickListener(this);
    }

    //点击事件
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.startAudio:
                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        StartRecord();
                        Log.e(TAG,"start");
                    }
                });
                thread.start();
                printLog("开始录音");
                ButtonEnabled(false, true, false);
                break;
            case R.id.stopAudio:
                isRecording = false;
                ButtonEnabled(true, false, true);
                printLog("停止录音");
                break;
            case R.id.playAudio:
                PlayRecord();
                ButtonEnabled(true, false, false);
                printLog("播放录音");
                break;
            case R.id.deleteAudio:
                deleFile();
                break;
        }
    }

    //打印log
    private void printLog(final String resultString) {
        tv_audio_succeess.post(new Runnable() {
            @Override
            public void run() {
                tv_audio_succeess.append(resultString + "\n");
                mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
            }
        });
    }

    //获取/失去焦点
    private void ButtonEnabled(boolean start, boolean stop, boolean play) {
        startAudio.setEnabled(start);
        stopAudio.setEnabled(stop);
        playAudio.setEnabled(play);
    }

    //开始录音
    public void StartRecord() {
        Log.i(TAG,"开始录音");
        //16K采集率
        int frequency = 16000;
        //格式
        int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
        //16Bit
        int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
        //生成PCM文件
        file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/reverseme.pcm");
        Log.i(TAG,"生成文件");
        //如果存在,就先删除再创建
        if (file.exists())
            file.delete();
            Log.i(TAG,"删除文件");
        try {
            file.createNewFile();
            Log.i(TAG,"创建文件");
        } catch (IOException e) {
            Log.i(TAG,"未能创建");
            throw new IllegalStateException("未能创建" + file.toString());
        }
        try {
            //输出流
            OutputStream os = new FileOutputStream(file);
            BufferedOutputStream bos = new BufferedOutputStream(os);
            DataOutputStream dos = new DataOutputStream(bos);
            int bufferSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding);
            AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency, channelConfiguration, audioEncoding, bufferSize);

            short[] buffer = new short[bufferSize];
            audioRecord.startRecording();
            Log.i(TAG, "开始录音");
            isRecording = true;
            while (isRecording) {
                int bufferReadResult = audioRecord.read(buffer, 0, bufferSize);
                for (int i = 0; i < bufferReadResult; i++) {
                    dos.writeShort(buffer[i]);
                }
            }
            audioRecord.stop();
            dos.close();
        } catch (Throwable t) {
            Log.e(TAG, "录音失败");
        }
    }

    //播放文件
    public void PlayRecord() {
        if(file == null){
            return;
        }
        //读取文件
        int musicLength = (int) (file.length() / 2);
        short[] music = new short[musicLength];
        try {
            InputStream is = new FileInputStream(file);
            BufferedInputStream bis = new BufferedInputStream(is);
            DataInputStream dis = new DataInputStream(bis);
            int i = 0;
            while (dis.available() > 0) {
                music[i] = dis.readShort();
                i++;
            }
            dis.close();
            AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                    16000, AudioFormat.CHANNEL_CONFIGURATION_MONO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    musicLength * 2,
                    AudioTrack.MODE_STREAM);
            audioTrack.play();
            audioTrack.write(music, 0, musicLength);
            audioTrack.stop();
        } catch (Throwable t) {
            Log.e(TAG, "播放失败");
        }
    }

    //删除文件
    private void deleFile() {
        if(file == null){
            return;
        }
        file.delete();
        printLog("文件删除成功");
    }

}

如果你想去调试这些pcm文件做音频测试的话,我推荐使用Audacity这个软件,可以看到,我直接点击左上角的file-导入-源文件,然后设置16K

Android音频处理——通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能

这样就可以调试了

Android音频处理——通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能

最后,放一张完整的截图

Android音频处理——通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能

嗯,这篇文章就到这里,还是有些基础了,希望下次能给大家带来高深点的文章吧,有兴趣的加群:555974449

Sample下载:http://download.csdn.net/detail/qq_26787115/9676003

Android音频处理——通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能的更多相关文章

  1. Android音频开发之AudioRecord录音实现

    前言: 其实在Android中录音可以用MediaRecord录音,操作比较简单.但是不能对音频进行处理.考虑到项目中做的是实时语音只能选择AudioRecord进行录音. 本文算是对AudioRec ...

  2. AudioRecord 录制播放PCM音频

    AudioRecord 与 MediaRecorder 区别 AudioRecord 基于字节流录制,输出的是pcm数据,未进行压缩,直接保存的pcm文件不能被播放器识别播放. 可以对音频文件进行实时 ...

  3. Android 音视频深入 一 AudioRecord录音生成pcm转换为wav(附源码下载)

    本篇项目地址,名字是AudioRecord录音(能暂停,将pch转换为wav),求starhttps://github.com/979451341/Audio-and-video-learning-m ...

  4. Android音频系统之音频框架

    1.1 音频框架 转载请注明,From LXS, http://blog.csdn.net/uiop78uiop78/article/details/8796492 Android的音频系统在很长一段 ...

  5. Android音频介绍

    这个世界音频设备千变万化,Android也不可能为每种设备都提供支持.Android定义了一个框架,这个框架来适配底层的音频设备.该适配层的定义位于: hardware/libhardware_leg ...

  6. Android音频&colon; 怎样使用AudioTrack播放一个WAV格式文件&quest;

    翻译 By Long Luo 原文链接:Android Audio: Play a WAV file on an AudioTrack 译者注: 1. 因为这是技术文章,所以有些词句使用原文,表达更准 ...

  7. 转:ANDROID音频系统散记之四:4&period;0音频系统HAL初探

    昨天(2011-11-15)发布了Android4.0的源码,今天download下来,开始挺进4.0时代.简单看了一下,发现音频系统方面与2.3的有较多地方不同,下面逐一描述. 一.代码模块位置 1 ...

  8. Android 4&period;4中AudioRecord用例 - 录制系统内置声音

    通过API 19新加的MediaRecorder.AudioSource.REMOTE_SUBMIX參数能够让系统App录制系统内置的声音,也就是扬声器的声音.以下是一个巨简单的样例来演示样例怎样通过 ...

  9. IOS和Android音频开发总结

    最近因为项目需要对声音进行变声,所以边学习边做,发现音频的处理思路并不难,但是做起来还是有些繁琐的(比预期的) 趁着脑子还发热,赶紧把思路总结一下,记录下来. 主要讲三个部分 1,如何变声2,安卓实现 ...

随机推荐

  1. Fragment的生命周期(三)

    自定义lifecycleoffragment布局文件 在main_activity布局中引用自定义的fragment布局 到logcat中查看程勋运行的结果 代码如下: 自定义的fragment布局: ...

  2. Linux网络基本配置

    一.Linux网络配置文件 1.  /etc/sysconfig/network-scripts/ifcfg-eth0 文件 在Red Hat系统中,系统网络设备的配置文件保存在/etc/syscon ...

  3. 不小心改了Xcode系统的头文件,运行报错,解决办法

  4. FPGA入门1

    FPGA入门知识介绍    近几年来,由于现场可编程门阵列(FPGA)的使用非常灵活,又可以无限次的编程,已受到越来越多的电子编程者的喜爱,很多朋友都想学习一些FPGA入门知识准备进行这个行业,现在关 ...

  5. jquery&plus;easy ui 实现表格列头筛选

    示例代码 1.筛选的下拉 <a href="javascript:void(0)" id="filterStatus" class="easyu ...

  6. Python进程池Pool

    ''' 进程池,启动一个进程就要克隆一份数据,假设父进程1G,那么启动进程开销很大 避免启动太多造成系统瘫痪,就有进程池,即同一时间允许的进程数量 ps:线程没有池,因为线程启动开销小,线程有类似信号 ...

  7. DLL 函数中使用结构体指针作函数参数(C&num; 调用 C&plus;&plus; 的 DLL)

    存在的问题: 问题1:C++ 与 C# 同样定义的结构体在内存布局上有时并不一致: 问题2:C# 中引入了垃圾自动回收机制,其垃圾回收器可能会重新定位指针所指向的结构体变量. 解决方案: 问题1方案: ...

  8. SAS数据集推送到sql server 数据库 实现代码段

    libname fdsas ODBC datasrc=fdsas user=fdsas password=fdsas123 preserve_tab_names=yes connection=shar ...

  9. FPGA之初认识

    什么是FPGA FPGA(Field-Programmable Gate Array),即现场可编程门阵列 .看到编程两个字码农就笑了,不就是编程嘛,那可是我们的强项 .且慢,此编程非彼编程 .一定要 ...

  10. 解决Ubuntu&sol;debian的Apt-get 由于依赖关系安装失败的问题

    The following packages have unmet dependencies: libssl-dev: Depends: libssl0.9.8 (= 0.9.8k-7ubuntu8) ...