Androidのオーディオの録音と再生

オリジナルリンク:https://www.jianshu.com/p/5966ed7c5baf

オーディオキャプチャ、エンコードの導入は、オーディオを再生するためにAudioStackで、3つのフォーマット(PCM、WAV、AAC)を作成したaudiorecordのオーディオファイル形式を収集することで、業務をトランスコード、ファイルを生成します。

Androidのオーディオの録音と再生

、PCM、WAV、AACのプレゼンテーションファイルのヘッダ

ここでは3つの一般的なオーディオフォーマットを簡単に見:

PCM:PCM(コードModulation--記録パルス符号変調パルス)。PCM録音は音が音を表現するための3つのパラメータ(チャンネル数、サンプリング周波数、サンプリングビット)の使用、記号のパルス列となり、アナログ信号と呼ばれます。PCM信号は、符号化され、それは、任意の圧縮処理を施していません。アナログ信号よりも、より少ないノイズの影響を受けやすいと伝送方式によって歪んであります。広いダイナミックレンジ、非常に良好な衝撃効果音。

WAV:WAVはロスレスオーディオファイル形式は、WAVはRIFF(リソース交換ファイル形式)の仕様を満たしています。すべては、符号化パラメータファイルヘッダのオーディオストリームをWAVファイルのヘッダを持っています。WAVには厳格な規則は、PCMに加えて、オーディオストリームをエンコードされていないだけでなく、ほぼすべてのACMをWAVオーディオストリームをエンコードすることができ、標準のエンコーディングをサポートしています。

WAVは、ロスレスオーディオファイル形式であるPCMエンコーディングが圧縮されていない:簡単に言えば

AAC:AAC(アドバンストオーディオコーディング)、「アドバンスド・オーディオ・コーディング」と呼ばれる中国は、1997年にMPEG-2に基づいてオーディオ符号化技術が登場しました。フラウンホーファーIIS、ドルビーラボラトリーズ、AT&T、ソニー(ソニー)と他の企業によって共同開発され、MP3形式に代わるものです。2000年に、MPEG-4規格が表示され、AACはまた、MPEG-4 AACとして知られている従来のMPEG-2 AACを、区別するために、SBRとPS技術の技術に加え、そのプロパティを再統合しました。彼はMP3に似た音声データファイル圧縮形式のために特別に設計されています。AAC形式の音声ファイルを使用すると、大幅に減らすことができますが、人々は、音質が低下している気分にさせることはありません。

第二に、使用audiorecordのは、生成されたPCM音声ファイルを実装します

audiorecordのAndroidのシステムを理解し、このクラスの特定の使用を説明するために、記録のような機能を実現するために提供され、あなたが見ることができるものなの参照リンクなどの公式文書、。

AndioRecordクラス主な機能は、彼らが収集し、サウンド関連のハードウェアを介した音を録音することができるように、Javaアプリケーションは、オーディオのさまざまなリソースを管理できるようにすることです。この機能は、完了するために、(読み)audiorecordのオブジェクトを「引っ張って」の音声データによって達成されます。記録処理では、アプリケーションは、実行する必要がある三つのクラスは、音声データをされ得る方法を提供audiorecordのタイムリーにデータを記録する方法により、次の3つのaudiorecordのクラスオブジェクトを取得することです。

  • (バイト[]、int型、int)を読みます
  • (ショート[]、INT、INT)読み取り
  • (ByteBufferの、int)を読みます

あなたは、サウンドデータのユーザーフレンドリーなフォーマットを保存するために事前に設定されなければならない方法を使用することを選択したかどうか。

時間の記録を開始し、audiorecordの音関連するバッファを初期化する必要があり、バッファは、主に新しいサウンドデータを保存するために使用されています。このバッファのサイズは、我々は、オブジェクトの構築時に指定さに行くことができます。これは、音声データはトーンが(すなわち、1つは容量の音を録音することができます)どのくらいの時間を記録することができます前に、audiorecordのオブジェクトが(同期)が読み取られていない示しています。音声データは、オーディオ・ハードウェアから読み出され、データサイズは、全記録データのサイズを超えていない(複数回に読み取ることができる)、即ち、それぞれのデータ容量は、バッファの初期化を読み取​​ります。

2.1最初のいくつかのグローバル変数と定数のパラメータを宣言する必要があります

主にいくつかのパラメータを宣言使用し、具体的な説明は、コメントを表示することができます。

/指定音频源 这个和MediaRecorder是相同的 MediaRecorder.AudioSource.MIC指的是麦克风
private static final int mAudioSource = MediaRecorder.AudioSource.MIC;
//指定采样率 (MediaRecoder 的采样率通常是8000Hz,16000Hz
//AAC的通常是 44100Hz。 设置采样率为 44100,目前为常用的采样率,官方文档表示这个值可以兼容所有的设置)

private static final int mSampleRateInHz = 44100;
//指定捕获音频的声道数目。在 AudioFormat 类中指定用于此的常量,单声道

private static final int mChannelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
//指定音频量化位数 ,在 AudioFormat 类中指定了以下各种可能的常量。通常我们选择 ENCODING_PCM_16BIT 和 ENCODING_PCM_8BIT
//PCM 代表的是脉冲编码调制,它实际上是原始音频样本。
//因此可以设置每个样本的分辨率为 16 位或者8位,16 位将占用更多的空间和处理能力,表示的音频也更加接近真实。

private static final int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
//指定缓冲区大小。调用AudioRecord类的getMinBufferSize方法可以获得。
private int mBufferSizeInBytes;

// 声明 AudioRecord 对象
private AudioRecord mAudioRecord = null;

2.2バッファサイズを取得し、audiorecordのを作成

//初始化数据,计算最小缓冲区
mBufferSizeInBytes = AudioRecord.getMinBufferSize(mSampleRateInHz, mChannelConfig, mAudioFormat);
//创建AudioRecorder对象
mAudioRecord = new AudioRecord(mAudioSource, mSampleRateInHz, mChannelConfig,
                               mAudioFormat, mBufferSizeInBytes);

2.3ライト・ファイル

    @Override
    public void run() {
        //标记为开始采集状态
        isRecording = true;
        //创建文件
        createFile();

        try {

            //判断AudioRecord未初始化,停止录音的时候释放了,状态就为STATE_UNINITIALIZED
            if (mAudioRecord.getState() == mAudioRecord.STATE_UNINITIALIZED) {
                initData();
            }

            //最小缓冲区
            byte[] buffer = new byte[mBufferSizeInBytes];
            //获取到文件的数据流
            mDataOutputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(mRecordingFile)));

            //开始录音
            mAudioRecord.startRecording();
            //getRecordingState获取当前AudioReroding是否正在采集数据的状态
            while (isRecording && mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
                int bufferReadResult = mAudioRecord.read(buffer, 0, mBufferSizeInBytes);
                for (int i = 0; i < bufferReadResult; i++) {
                    mDataOutputStream.write(buffer[i]);
                }
            }
        } catch (Exception e) {
            Log.e(TAG, "Recording Failed");
        } finally {
            // 停止录音
            stopRecord();
            IOUtil.close(mDataOutputStream);
        }
    }

2.4アクセス要求

アクセス許可の要件:WRITE_EXTERNAL_STORAGE、READ_EXTERNAL_STORAGE(携帯電話の一部は、この権限を申請しなければなりません)、RECORD_AUDIO

    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

2.5取得の概要

そして今、導入の基本的な記録処理が終わったが、今回の問題:

  • 私は、オーディオデータが内部のファイルに出力され、プロセスに従うと、あなたは録音を停止した後、ファイルを開き、再生しないことが判明し、最終的にはなぜですか?

あなたは、完了した処理によれば、データが入力されているが、ちょうど最もオリジナルのオーディオデータの内部になりましたファイルの内容、(中国の説明は「原材料」または「未処理何か」ということである)、この時間はRAWと呼ばれますそれが操作をデコードする方法がわからない、フォーマットが保存されているかわからなかった、プレイヤーが開くようにしましょう。もちろん、再生できません。

  • 次に、どのように私は選手でそれを記録したコンテンツを再生することができますか?

データファイルの先頭に付加され又はAACとAACヘッドデータとすることができる、すなわち、ファイルヘッダです。一緒にのみ、データファイルの先頭で、最終的にコンテンツ内にあるかを知る権利のプレーヤーは、このように適切に内部の内容を解析し、再生することができます。

三、PCMをWAVに変換しました

WAVE HEADに参加するか、AACデータは、ファイルヘッダ、つまり、することができ、データファイルの先頭で。一緒にのみ、データファイルの先頭で、最終的にコンテンツ内にあるかを知る権利のプレーヤーは、このように適切に内部の内容を解析し、再生することができます。プレイに理解できるAudioTrack上のWAVファイルを記載した特定のヘッダファイル。

public class WAVUtil {

    /**
     * PCM文件转WAV文件
     *
     * @param inPcmFilePath  输入PCM文件路径
     * @param outWavFilePath 输出WAV文件路径
     * @param sampleRate     采样率,例如44100
     * @param channels       声道数 单声道:1或双声道:2
     * @param bitNum         采样位数,8或16
     */
    public static void convertPcm2Wav(String inPcmFilePath, String outWavFilePath, int sampleRate,int channels, int bitNum) {

        FileInputStream in = null;
        FileOutputStream out = null;
        byte[] data = new byte[1024];

        try {
            //采样字节byte率
            long byteRate = sampleRate * channels * bitNum / 8;

            in = new FileInputStream(inPcmFilePath);
            out = new FileOutputStream(outWavFilePath);

            //PCM文件大小
            long totalAudioLen = in.getChannel().size();

            //总大小,由于不包括RIFF和WAV,所以是44 - 8 = 36,在加上PCM文件大小
            long totalDataLen = totalAudioLen + 36;

            writeWaveFileHeader(out, totalAudioLen, totalDataLen, sampleRate, channels, byteRate);

            int length = 0;
            while ((length = in.read(data)) > 0) {
                out.write(data, 0, length);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            IOUtil.close(in,out);
        }
    }

    /**
     * 输出WAV文件
     *
     * @param out           WAV输出文件流
     * @param totalAudioLen 整个音频PCM数据大小
     * @param totalDataLen  整个数据大小
     * @param sampleRate    采样率
     * @param channels      声道数
     * @param byteRate      采样字节byte率
     * @throws IOException
     */
    private static void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,long totalDataLen, int sampleRate, 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) (sampleRate & 0xff);
        header[25] = (byte) ((sampleRate >> 8) & 0xff);
        header[26] = (byte) ((sampleRate >> 16) & 0xff);
        header[27] = (byte) ((sampleRate >> 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) (channels * 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);
    }
}

比較的WAVファイルを生成し、その後、我々は携帯電話を使用すると、この時点で、通常のオープンを再生することができるプレーヤーが付属していますが、私たちは彼のサイズが比較的大きい見つけ、私たちは、とても大きな数分、私たちは通常、mp3を使用していることがわかりAACフォーマット、我々はそれをどのように行うのですか?

四、AACへのPCMファイル形式を

AACファイルの再生を生成します

public class AACUtil {

    ...

    /**
     * 初始化AAC编码器
     */
    private void initAACMediaEncode() {
        try {

            //参数对应-> mime type、采样率、声道数
            MediaFormat encodeFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 16000, 1);
            encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, 64000);//比特率
            encodeFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
            encodeFormat.setInteger(MediaFormat.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_IN_MONO);
            encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
            encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 1024);//作用于inputBuffer的大小

            mediaEncode = MediaCodec.createEncoderByType(encodeType);
            mediaEncode.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (mediaEncode == null) {
            LogUtil.e("create mediaEncode failed");
            return;
        }

        mediaEncode.start();
        encodeInputBuffers = mediaEncode.getInputBuffers();
        encodeOutputBuffers = mediaEncode.getOutputBuffers();
        encodeBufferInfo = new MediaCodec.BufferInfo();
    }

    private boolean codeOver = false;

    /**
     * 开始转码
     * 音频数据{@link #srcPath}先解码成PCM  PCM数据在编码成MediaFormat.MIMETYPE_AUDIO_AAC音频格式
     * mp3->PCM->aac
     */
    public void startAsync() {
        LogUtil.w("start");
        new Thread(new DecodeRunnable()).start();
    }

    /**
     * 解码{@link #srcPath}音频文件 得到PCM数据块
     *
     * @return 是否解码完所有数据
     */
    private void srcAudioFormatToPCM() {
        File file = new File(srcPath);// 指定要读取的文件
        FileInputStream fio = null;
        try {
            fio = new FileInputStream(file);
            byte[] bb = new byte[1024];
            while (!codeOver) {
                if (fio.read(bb) != -1) {
                    LogUtil.e("============   putPCMData ============" + bb.length);
                    dstAudioFormatFromPCM(bb);
                } else {
                    codeOver = true;
                }
            }

            fio.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private byte[] chunkAudio = new byte[0];

    /**
     * 编码PCM数据 得到AAC格式的音频文件
     */
    private void dstAudioFormatFromPCM(byte[] pcmData) {

        int inputIndex;
        ByteBuffer inputBuffer;
        int outputIndex;
        ByteBuffer outputBuffer;

        int outBitSize;
        int outPacketSize;
        byte[] PCMAudio;
        PCMAudio = pcmData;

        encodeInputBuffers = mediaEncode.getInputBuffers();
        encodeOutputBuffers = mediaEncode.getOutputBuffers();
        encodeBufferInfo = new MediaCodec.BufferInfo();

        inputIndex = mediaEncode.dequeueInputBuffer(0);
        inputBuffer = encodeInputBuffers[inputIndex];
        inputBuffer.clear();
        inputBuffer.limit(PCMAudio.length);
        inputBuffer.put(PCMAudio);//PCM数据填充给inputBuffer
        mediaEncode.queueInputBuffer(inputIndex, 0, PCMAudio.length, 0, 0);//通知编码器 编码

        outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 0);

        while (outputIndex > 0) {

            outBitSize = encodeBufferInfo.size;
            outPacketSize = outBitSize + 7;//7为ADT头部的大小
            outputBuffer = encodeOutputBuffers[outputIndex];//拿到输出Buffer
            outputBuffer.position(encodeBufferInfo.offset);
            outputBuffer.limit(encodeBufferInfo.offset + outBitSize);
            chunkAudio = new byte[outPacketSize];
            addADTStoPacket(chunkAudio, outPacketSize);//添加ADTS
            outputBuffer.get(chunkAudio, 7, outBitSize);//将编码得到的AAC数据 取出到byte[]中

            try {
                //录制aac音频文件,保存在手机内存中
                bos.write(chunkAudio, 0, chunkAudio.length);
                bos.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

            outputBuffer.position(encodeBufferInfo.offset);
            mediaEncode.releaseOutputBuffer(outputIndex, false);
            outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 0);

        }

    }

    /**
     * 添加ADTS头
     *
     * @param packet
     * @param packetLen
     */
    private void addADTStoPacket(byte[] packet, int packetLen) {
        int profile = 2; // AAC LC
        int freqIdx = 8; // 16KHz
        int chanCfg = 1; // CPE

        // fill in ADTS data
        packet[0] = (byte) 0xFF;
        packet[1] = (byte) 0xF1;
        packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
        packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));
        packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
        packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
        packet[6] = (byte) 0xFC;

    }

    /**
     * 释放资源
     */
    public void release() {
      ...
    }

    /**
     * 解码线程
     */
    private class DecodeRunnable implements Runnable {

        @Override
        public void run() {
            srcAudioFormatToPCM();
        }
    }

}

五、AudioStackプレイ

AudioTrackクラスは、Androidプラットフォーム上のタスクの出力オーディオデータを完了します。二つAudioTrackデータローディングモード(MODE_STREAMとMODE_STATIC)、負荷モードと2つの完全に異なる使用シナリオに対応するオーディオストリームのデータ・タイプに対応します。

  • MODE_STREAM:このモードでは、書き込みデータがオーディオAudioTrack再び書き込まれます。これは通常、類似したファイルにデータを書き込むために、ライトシステムによって呼び出されますが、作業のこの方法はAudioTrackの内部に設けられたバッファにバッファからデータをコピーするために、すべてのユーザーが必要とするある程度の遅延の導入がします。第2のモデルの導入にこの問題、AudioTrackを解決するには。
  • MODE_STATIC:このモードでは、ちょうどあなたがデータ送信をに従う必要はありません、内部バッファAudioTrackに書き込むための単一の呼び出しによって送信されたデータのすべての前に再生する必要があります。この小さなフットプリント、高い待ち時間要件の文書を着信音としてこのモードが適しています。しかし、それはまた、書き込みデータがそうでなければ、システムがすべてのデータを格納するのに十分なメモリを割り当てることができない、あまりにも多くのことができないで欠点を持っています。

MediaPlayerのは、開発者が使用するためのJava APIを提供どちらも、音とAudioTrackを再生することができます。両方のサウンドを再生することができますが、両方は違いがたくさんありますが、最大の違いは、MediaPlayerのは、そのようなので、上のMP3、AAC、WAV、OGG、MIDIやなどのオーディオファイル形式、さまざまなを再生することができるということです。MediaPlayerのは、対応するオーディオデコーダフレームワーク層を作成します。それらのほとんどは、PCMストリームがあるので、AudioTrackは音声のみのファイル形式wavファイル、WAV形式の音声ファイルをサポートされますが、サポートされているファイルフォーマットの言葉を比較する場合とAudioTrackにのみ、そのデコードされたPCMストリームを再生します。AudioTrackデコーダを作成しないで、我々は、それはwavファイルをデコードする必要はありません再生することができます。

5.1オーディオストリームを入力

AudioTrackコンストラクタでは、このパラメータはAudioManager.STREAM_MUSICに公開されます。その意味とAndroidのシステム管理および関連オーディオストリームの分類。

Androidのサウンドシステムは、複数のストリームのタイプに分けられ、ここではいくつかの一般的なものは以下のとおりです。

STREAM_ALARM:音の警告
、音楽、音楽やその他のように:STREAM_MUSIC
STREAM_RING:着メロ
STREAM_SYSTEM:サウンドシステム、例えば、低音、ロック画面、音、など
STREAM_VOCIE_CALLを:サウンドを呼び出します

注:上記の部門と自体は問題ではありませんオーディオデータのこれらのタイプ。例MUSICリングタイプの場合とMP3の曲することができます。さらに、オーディオストリームのタイプは、例えば、着信音のプレビューMUSICタイプが提供されてもよい、固定選択基準ではありません。オーディオシステムオーディオ部門と経営戦略関連のオーディオストリームタイプ。

分布とフレームの5.2バッファコンセプト

getMinBufferSize:時間の計算におけるバッファのサイズ分布は、私たちはしばしばあるメソッドを使用します。この機能は、どのくらいのデータ、アプリケーション層の割り当てバッファを決定します。

AudioTrack.getMinBufferSize(8000,//每秒8K个采样点
         AudioFormat.CHANNEL_CONFIGURATION_STEREO,//双声道
           AudioFormat.ENCODING_PCM_16BIT);

フレーム(枠):スタートコードからAudioTrack.getMinBufferSizeトレーサビリティは、非常に重要な概念基礎となるコードで見つけることができます。フレームは、データのどのくらいの量を記述するために使用される単位です。フレームユニット1は、チャネル数×バイトのサンプリングポイント数に等しい(例えばPCM16として、2チャンネルは、2×2 = 4バイトのフレームに等しいです)。唯一つのサンプル点のためのチャネルは、実際には、1つまたは複数のチャネルが存在してもよいです。一つの試料の全てのチャンネルの単位で表されるデータの量を分離することができないことは、それがフレームの概念につながります。フレームサイズは、点サンプリングチャネル数×バイトの数です。さらに、内部バッファ内に存在するサウンドカードドライバでも、ユニットフレームの割り当てと管理として使用されます。

getMinBufSizeは、ハードウェアの場合に検討すると(例えばサンプリングレートをサポートするかどうかなどを、ハードウェア自体、遅延、等)は、バッファの最小サイズを得ました。一般的に我々はそれの整数倍になるバッファサイズを割り当てます。

5.3ビルドプロセス

各オーディオストリームは、(ミキサー)を混合することにより、クラスAudioTrackの1つのインスタンス、作成時にAudioFlinger AudioTrackに登録それぞれ、全てAudioFlinger AudioTrackに相当し、次いで再生のためAudioHardwareに搬送、最大電流アンドロイド一方32個のオーディオストリームを作成し、それは同時にのデータストリーム32 AudioTrackを処理するまで、ミキサーを言うことです。

public class AudioTrackManager {
    ...
    //音频流类型
    private static final int mStreamType = AudioManager.STREAM_MUSIC;
    //指定采样率 (MediaRecoder 的采样率通常是8000Hz AAC的通常是44100Hz。 设置采样率为44100,目前为常用的采样率,官方文档表示这个值可以兼容所有的设置)
    private static final int mSampleRateInHz = 44100;
    //指定捕获音频的声道数目。在AudioFormat类中指定用于此的常量
    private static final int mChannelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO; //单声道
    //指定音频量化位数 ,在AudioFormaat类中指定了以下各种可能的常量。通常我们选择ENCODING_PCM_16BIT和ENCODING_PCM_8BIT PCM代表的是脉冲编码调制,它实际上是原始音频样本。
    //因此可以设置每个样本的分辨率为16位或者8位,16位将占用更多的空间和处理能力,表示的音频也更加接近真实。
    private static final int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
    //指定缓冲区大小。调用AudioRecord类的getMinBufferSize方法可以获得。
    private int mMinBufferSize;
    //STREAM的意思是由用户在应用程序通过write方式把数据一次一次得写到audiotrack中。这个和我们在socket中发送数据一样,
    // 应用层从某个地方获取数据,例如通过编解码得到PCM数据,然后write到audiotrack。
    private static int mMode = AudioTrack.MODE_STREAM;

    private void initData() {
        //根据采样率,采样精度,单双声道来得到frame的大小。
        mMinBufferSize = AudioTrack.getMinBufferSize(mSampleRateInHz, mChannelConfig, mAudioFormat);//计算最小缓冲区
        //注意,按照数字音频的知识,这个算出来的是一秒钟buffer的大小。
        //创建AudioTrack
        mAudioTrack = new AudioTrack(mStreamType, mSampleRateInHz, mChannelConfig,
                mAudioFormat, mMinBufferSize, mMode);
    }

    /**
     * 启动播放线程
     */
    private void startThread() {
        destroyThread();
        isStart = true;
        if (mRecordThread == null) {
            mRecordThread = new Thread(recordRunnable);
            mRecordThread.start();
        }
    }

    /**
     * 播放线程
     */
    private Runnable recordRunnable = new Runnable() {
        @Override
        public void run() {
            try {
                //设置线程的优先级
             android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
                byte[] tempBuffer = new byte[mMinBufferSize];
                int readCount = 0;
                while (mDis.available() > 0) {
                    readCount = mDis.read(tempBuffer);
                    if (readCount == AudioTrack.ERROR_INVALID_OPERATION || readCount == AudioTrack.ERROR_BAD_VALUE) {
                        continue;
                    }
                    //一边播放一边写入语音数据
                    if (readCount != 0 && readCount != -1) {
                        //判断AudioTrack未初始化,停止播放的时候释放了,状态就为STATE_UNINITIALIZED
                        if (mAudioTrack.getState() == mAudioTrack.STATE_UNINITIALIZED) {
                            initData();
                        }
                        mAudioTrack.play();
                        mAudioTrack.write(tempBuffer, 0, readCount);
                    }
                }
                //播放完就停止播放
                stopPlay();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    };

    /**
     * 启动播放
     *
     * @param path
     */
    public void startPlay(String path) {
        try {
            setPath(path);
            startThread();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 停止播放
     */
    public void stopPlay() {
        try {
            destroyThread();//销毁线程
            if (mAudioTrack != null) {
                if (mAudioTrack.getState() == AudioRecord.STATE_INITIALIZED) {//初始化成功
                    mAudioTrack.stop();//停止播放
                }
                if (mAudioTrack != null) {
                    mAudioTrack.release();//释放audioTrack资源
                }
            }
            if (mDis != null) {
                mDis.close();//关闭数据输入流
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

おすすめ

転載: blog.51cto.com/14541311/2437488