Android录音功能与录制讯飞听写可转为文字的音频

MediaRecorder与AudioRecord的区别

MediaRecorder和AudioRecord都可以录制音频。
区别是MediaRecorder录制的音频文件是经过压缩后的,需要设置编码器。
并且录制的音频文件可以用系统自带的Music播放器播放。
而AudioRecord录制的是PCM格式的音频文件,需要用AudioTrack来播放,AudioTrack更接近底层。
在用MediaRecorder进行录制音视频时,最终还是会创建AudioRecord用来与AudioFlinger进行交互。

MediaRecorder

MediaRecorder简介

已经集成了录音、编码、压缩等,支持少量的录音音频格式,大概有.aac(API = 16) .amr .3gp
优点:大部分以及集成,直接调用相关接口即可,代码量小
缺点:无法实时处理音频;输出的音频格式不是很多,例如没有输出mp3格式文件

MediaRecorder的代码实现

package com.sinogeo.util;

import java.io.IOException;

import android.media.MediaRecorder;
import android.os.Handler;
import android.util.Log;

/**
 * 录音工具类
 */

public class MediaRecoderUtils {

    private static MediaRecoderUtils mInstance;
    private String filePath;
    private MediaRecorder mMediaRecorder;
    private final String TAG = "MediaRecord";
    public static final int MAX_LENGTH = 1000 * 60 * 10;// 最大录音时长1000*60*10;

    private OnMediaStatusUpdateListener mediaStatusUpdateListener;

    /**需要传入包括文件名的完整文件路径,后缀为.amr*/
    public synchronized static MediaRecoderUtils getInstance(String path) {
        if (mInstance == null) {
            mInstance = new MediaRecoderUtils(path);
        }
        return mInstance;
    }

    private MediaRecoderUtils(String path) {
        this.filePath = path;
    }

    private long startTime;
    private long endTime;

    /**
     * 开始录音 使用amr格式 录音文件
     * 
     * @return
     */
    public void startRecord() {
        // 开始录音
        /* ①Initial:实例化MediaRecorder对象 */
        if (mMediaRecorder == null)
            mMediaRecorder = new MediaRecorder();
        try {
            /* ②setAudioSource/setVedioSource */
            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 设置麦克风
            /* ②设置音频文件的编码:AAC/AMR_NB/AMR_MB/Default 声音的(波形)的采样 */
            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
            /*
             * ②设置输出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default THREE_GPP(3gp格式
             * ,H263视频/ARM音频编码)、MPEG-4、RAW_AMR(只支持音频且音频编码要求为AMR_NB)
             */
            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
            mMediaRecorder.setAudioSamplingRate(16000);
            /* ③准备 */
            mMediaRecorder.setOutputFile(filePath);
            mMediaRecorder.setMaxDuration(MAX_LENGTH);
            mMediaRecorder.prepare();
            /* ④开始 */
            mMediaRecorder.start();
            // AudioRecord audioRecord.
            /* 获取开始时间* */
            startTime = System.currentTimeMillis();
            updateMicStatus();
            Log.i("ACTION_START", "startTime" + startTime);
        } catch (IllegalStateException e) {
            Log.i(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage());
        } catch (IOException e) {
            Log.i(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage());
        }
    }

    /**
     * 停止录音
     */
    public long stopRecord() {
        if (mMediaRecorder == null)
            return 0L;
        endTime = System.currentTimeMillis();
        Log.i("ACTION_END", "endTime" + endTime);
        mMediaRecorder.stop();
        mMediaRecorder.reset();
        mMediaRecorder.release();
        mMediaRecorder = null;
        Log.i("ACTION_LENGTH", "Time" + (endTime - startTime));
        return endTime - startTime;
    }

    private final Handler mHandler = new Handler();

    private Runnable mUpdateMicStatusTimer = new Runnable() {
        public void run() {
            updateMicStatus();
        }
    };

    /**
     * 更新话筒状态
     */
    private int BASE = 1;
    private int SPACE = 100;// 间隔取样时间

    public void setOnMediaStatusUpdateListener(OnMediaStatusUpdateListener mediaStatusUpdateListener) {
        this.mediaStatusUpdateListener = mediaStatusUpdateListener;
    }

    private void updateMicStatus() {
        if (mMediaRecorder != null) {
            double ratio = (double) mMediaRecorder.getMaxAmplitude() / BASE;
            double db = 0;// 分贝
            if (ratio > 1) {
                db = 20 * Math.log10(ratio);
                if (null != mediaStatusUpdateListener) {
                    mediaStatusUpdateListener.onUpdate(db);
                }
            }
            mHandler.postDelayed(mUpdateMicStatusTimer, SPACE);
        }
    }

    public interface OnMediaStatusUpdateListener {
        public void onUpdate(double db);
    }
}

//获取实例,传入保存音频的路径,需注意要传入包括完整文件名的路径,例如/storage/emulated/0/Geoprocessing/yinfu.amr
MediaRecoderUtils recoderUtils = MediaRecoderUtils.getInstance(mPath);
//开始
recoderUtils.startRecord();
//结束
recoderUtils.stopRecord();
//如需监听分贝
recoderUtils.setOnMediaStatusUpdateListener(listener);

AudioRecord

AudioRecord简介

主要是实现边录边播(AudioRecord+AudioTrack)以及对音频的实时处理(如会说话的汤姆猫、语音)
优点:语音的实时处理,可以用代码实现各种音频的封装
缺点:输出是PCM语音数据,如果保存成音频文件,是不能够被播放器播放的,所以必须先写代码实现数据编码以及压缩
需注意的是讯飞的听写与转写功能,如果你需要上传音频实现转文字功能,上传的也是AudioRecord生成的录音文件。下图截自讯飞demo中对上传音频转文字功能的音频文件限制。

讯飞听写功能音频转文字的限制,截图自讯飞demo

AudioRecord的代码实现

package com.sinogeo.util;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Handler;
import android.os.Message;

/**
 * @author RenoY3
 * @version 创建时间:2018年5月2日 下午3:21:47 类说明:录音为讯飞可转为文字的音频格式文件
 */
public class AudioRecoderUtils {
    private static AudioRecoderUtils mInstance;
    private AudioRecord recorder;
    // 录音源
    private static int audioSource = MediaRecorder.AudioSource.MIC;
    // 录音的采样频率
    private static int audioRate = 16000;
    // 录音的声道,单声道
    private static int audioChannel = AudioFormat.CHANNEL_IN_MONO;
    // 量化的深度
    private static int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
    // 缓存的大小
    private static int bufferSize = AudioRecord.getMinBufferSize(audioRate, audioChannel, audioFormat);
    // 记录播放状态
    private boolean isRecording = false;
    // 数字信号数组
    private byte[] noteArray;
    // PCM文件
    private File pcmFile;
    // WAV文件
    private File wavFile;
    // 文件输出流
    private OutputStream os;
    // 文件根目录
    private String basePath;
    // wav文件目录
    private String outFileName;
    // pcm文件目录
    private String inFileName;
    // 分贝监听
    private OnAudioStatusUpdateListener audioStatusUpdateListener;

    private AudioRecoderUtils(String path) {
        basePath = path.replace(path.split("/")[path.split("/").length - 1], "");
        outFileName = path;
        inFileName = path.replace(".wav", ".pcm");
        createFile();// 创建文件
        recorder = new AudioRecord(audioSource, audioRate, audioChannel, audioFormat, bufferSize);
    }

    /** 需要传入包括文件名的完整文件路径,后缀为.wav */
    public synchronized static AudioRecoderUtils getInstance(String path) {
        if (mInstance == null) {
            mInstance = new AudioRecoderUtils(path);
        }
        return mInstance;
    }

    // 读取录音数字数据线程
    class WriteThread implements Runnable {
        public void run() {
            writeData();
        }
    }

    // 开始录音
    public void startRecord() {
        isRecording = true;
        recorder.startRecording();
        new Thread(new WriteThread()).start();
    }

    // 停止录音
    public void stopRecord() {
        isRecording = false;
        recorder.stop();
        convertWaveFile();
    }

    // 将数据写入文件夹,文件的写入没有做优化
    private void writeData() {
        noteArray = new byte[bufferSize];
        // 建立文件输出流
        try {
            os = new BufferedOutputStream(new FileOutputStream(pcmFile));
        } catch (IOException e) {
        }
        while (isRecording == true) {
            int recordSize = recorder.read(noteArray, 0, bufferSize);
            long v = 0;
            for (int i = 0; i < noteArray.length; i++) {
                v += noteArray[i] * noteArray[i];
            }
            // 平方和除以数据总长度,得到音量大小。
            double mean = v / (double) recordSize;
            // 此为音量值
            double volume = 10 * Math.log10(mean);
            if (audioStatusUpdateListener != null) {
                mHandler.sendEmptyMessage((int)volume);
            }
            if (recordSize > 0) {
                try {
                    os.write(noteArray);
                } catch (IOException e) {

                }
            }
        }
        if (os != null) {
            try {
                os.close();
            } catch (IOException e) {

            }
        }
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            audioStatusUpdateListener.onUpdate(msg.what);
        }
    };

    // 这里得到可播放的音频文件
    private void convertWaveFile() {
        FileInputStream in = null;
        FileOutputStream out = null;
        long totalAudioLen = 0;
        long totalDataLen = totalAudioLen + 36;
        long longSampleRate = AudioRecoderUtils.audioRate;
        int channels = 1;
        long byteRate = 16 * AudioRecoderUtils.audioRate * channels / 8;
        byte[] data = new byte[bufferSize];
        try {
            in = new FileInputStream(inFileName);
            out = new FileOutputStream(outFileName);
            totalAudioLen = in.getChannel().size();
            // 由于不包括RIFF和WAV
            totalDataLen = totalAudioLen + 36;
            WriteWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
            while (in.read(data) != -1) {
                out.write(data);
            }
            in.close();
            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /*
     * 任何一种文件在头部添加相应的头文件才能够确定的表示这种文件的格式,wave是RIFF文件结构,每一部分为一个chunk,其中有RIFF WAVE
     * chunk, FMT Chunk,Fact chunk,Data chunk,其中Fact chunk是可以选择的,
     */
    private void WriteWaveFileHeader(FileOutputStream out, long totalAudioLen, long totalDataLen, long longSampleRate,
            int channels, long byteRate) throws IOException {
        byte[] header = new byte[44];
        header[0] = 'R'; // RIFF
        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);
        header[8] = 'W';// WAVE
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        // FMT Chunk
        header[12] = 'f'; // 'fmt '
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';// 过渡字节
        // 数据大小
        header[16] = 16; // 4 bytes: size of 'fmt ' chunk
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        // 编码方式 10H为PCM编码格式
        header[20] = 1; // format = 1
        header[21] = 0;
        // 通道数
        header[22] = (byte) channels;
        header[23] = 0;
        // 采样率,每个通道的播放速度
        header[24] = (byte) (longSampleRate & 0xff);
        header[25] = (byte) ((longSampleRate >> 8) & 0xff);
        header[26] = (byte) ((longSampleRate >> 16) & 0xff);
        header[27] = (byte) ((longSampleRate >> 24) & 0xff);
        // 音频数据传送速率,采样率*通道数*采样深度/8
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
        // 确定系统一次要处理多少个这样字节的数据,确定缓冲区,通道数*采样位数
        header[32] = (byte) (1 * 16 / 8);
        header[33] = 0;
        // 每个样本的数据位数
        header[34] = 16;
        header[35] = 0;
        // Data chunk
        header[36] = 'd';// data
        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);
    }

    // 创建文件夹,首先创建目录,然后创建对应的文件
    private void createFile() {
        File baseFile = new File(basePath);
        if (!baseFile.exists()){
            baseFile.mkdirs();
        }
        pcmFile = new File(inFileName);
        wavFile = new File(outFileName);
        if (pcmFile.exists()) {
            pcmFile.delete();
        }
        if (wavFile.exists()) {
            wavFile.delete();
        }
        try {
            pcmFile.createNewFile();
            wavFile.createNewFile();
        } catch (IOException e) {

        }
    }

    public void setOnAudioStatusUpdateListener(OnAudioStatusUpdateListener audioStatusUpdateListener) {
        this.audioStatusUpdateListener = audioStatusUpdateListener;
    }

    public interface OnAudioStatusUpdateListener {
        public void onUpdate(double db);
    }
}

调用方法与上面类似,录制完成后会生成.wav和.pcm格式的俩个内容相同的音频文件:
//获取实例,传入保存音频的路径,需注意要传入包括完整文件名的路径,例如/storage/emulated/0/Geoprocessing/yinfu.wav
AudioRecoderUtils recoderUtils = AudioRecoderUtils.getInstance(mPath);
//开始
recoderUtils.startRecord();
//结束
recoderUtils.stopRecord();
//如需监听分贝
recoderUtils.setOnAudioStatusUpdateListener(listener);

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在 Android 应用中使用语音功能,需要进行以下几个步骤: 1. 注册开放平台账号,并创建一个应用。 2. 下载语音 SDK,并将其添加到你的 Android 项目中。 3. 在开放平台上申请语音识别、语音合成等 API 的授权,并获取 AppId、API Key 和 API Secret。 4. 在你的 Android 项目中添加语音 SDK 的初始化代码,并将 AppId、API Key 和 API Secret 配置到初始化代码中。 5. 编写代码调用语音 SDK 的API,实现语音识别、语音合成等功能。 具体步骤可以参考开放平台提供的文档和示例代码。以下是一个简单的示例代码,演示了如何使用语音 SDK 实现语音听写和语音合成功能: ```java // 初始化语音 SDK SpeechUtility.createUtility(this, SpeechConstant.APPID + "=your_app_id"); // 创建语音听写对象 SpeechRecognizer recognizer = SpeechRecognizer.createRecognizer(this, null); // 设置听写参数 RecognizerParameter params = new RecognizerParameter(); params.setLanguage("zh_cn"); params.setVadEos("1000"); // 开始语音听写 recognizer.startListening(new RecognizerListener() { @Override public void onVolumeChanged(int volume) { // 处理音量变化事件 } @Override public void onResult(RecognizerResult result, boolean isLast) { // 处理语音识别结果 } @Override public void onError(SpeechError error) { // 处理语音识别错误 } @Override public void onEvent(int eventType, int arg1, int arg2, Bundle obj) { // 处理语音识别事件 } }); // 创建语音合成对象 SpeechSynthesizer synthesizer = SpeechSynthesizer.createSynthesizer(this, null); // 设置合成参数 SynthesizerParameter params = new SynthesizerParameter(); params.setLanguage("zh_cn"); params.setVoiceName("aisjiuxu"); // 开始语音合成 synthesizer.startSpeaking("你好,欢迎使用语音功能", new SynthesizerListener() { @Override public void onSpeakBegin() { // 处理语音合成开始事件 } @Override public void onSpeakProgress(int progress, int beginPos, int endPos) { // 处理语音合成进度事件 } @Override public void onSpeakPaused() { // 处理语音合成暂停事件 } @Override public void onSpeakResumed() { // 处理语音合成恢复事件 } @Override public void onSpeakCompleted() { // 处理语音合成完成事件 } @Override public void onEvent(int eventType, int arg1, int arg2, Bundle obj) { // 处理语音合成事件 } }); ``` 需要注意的是,语音 SDK 是一个第三方库,需要在项目中添加相关依赖和权限,并且需要遵循开放平台的使用规范和条款。另外,语音功能需要联网才能使用,因此需要确保设备连接了互联网。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值