안드로이드 오디오 녹음 및 재생

원본 링크 : https://www.jianshu.com/p/5966ed7c5baf

오디오 캡처, 인코딩의 소개, 오디오를 재생 AudioStack으로, 세 가지 형식 (PCM, WAV, AAC)를 만들어 가지 audiorecord 오디오 파일 형식을 수집하여, 작업을 트랜스 코딩, 파일을 생성합니다.

안드로이드 오디오 녹음 및 재생

A, PCM, WAV, AAC 프리젠 테이션 파일 헤더

세 가지 일반적인 오디오 형식에서 다음 간단히 살펴 :

PCM : PCM (코드 Modulation-- 펄스 코드 변조를 기록 펄스). PCM 기록이 아날로그 신호 라 사운드 심볼의 펄스열이되고, 세 개의 파라미터 (채널 수, 샘플링 주파수 및 샘플링 비트)의 사용은 소리를 표현한다. PCM 신호는 코딩되고, 그것은 어떠한 압축 처리를 시행하지 않았다. 아날로그 신호보다 덜 노이즈에 민감하고 전송 시스템에 의해 왜곡된다. 넓은 동적 범위, 아주 좋은 영향 음향 효과.

WAV : WAV는 무손실 오디오 파일 포맷은 WAV는 RIFF (자원 교환 파일 형식) 사양을 충족한다. 모두는 WAV 파일 헤더, 인코딩 매개 변수 파일 헤더 오디오 스트림을 가지고있다. WAV는 더 단단하고 빠른 규칙, PCM뿐만 아니라, 오디오 스트림을 인코딩하지뿐만 아니라 거의 모든 ACM은 WAV 오디오 스트림을 인코딩 할 수있는 표준 인코딩을 지원합니다.

간단히 말해서 : WAV 무손실 오디오 파일 포맷, PCM 부호화가 압축되지이고

AAC : AAC (고급 오디오 코딩), "고급 오디오 코딩"이라고 중국, 1997 년에 MPEG-2를 기반으로 오디오 코딩 기술을 나타났다. 공동 개발은 Fraunhofer IIS, 돌비 연구소, AT & T, 소니 (소니)와 다른 회사에서, MP3 포맷을 대체하기위한 것입니다. 2000에서 MPEG-4 표준이 나타나면, AAC는, 그 특성을 재 통합 종래의 MPEG-2 AAC, 또한 MPEG-4 AAC라고도를 구별하기 위해, SBR 및 PS 기술 기술에 첨가 하였다. 그는이 MP3와 유사한 오디오 데이터 파일 압축 형식을 위해 특별히 설계되었습니다. AAC는 형식의 사운드 파일을 사용하면 크게 줄일 수 있지만, 사람들이 음질이 감소 느끼게하지 않습니다.

둘째, 사용 PCM 오디오 파일이 생성 구현 가지 audiorecord

가지 audiorecord 안드로이드 시스템을 이해하고이 클래스의 특정 사용을 설명하기 위해, 녹음 등의 기능을 구현하기 위해 제공됩니다, 당신은 무엇을 볼 수있는 등 참조 링크와 같은 공식 문서.

AndioRecord 클래스의 주요 기능은 수집 등 사운드 관련 하드웨어를 통해 소리를 녹음 할 수 있도록 자바 응용 프로그램, 오디오 다양한 자원을 관리 할 수 ​​있도록하는 것입니다. 이 기능은 "당겨"음성 데이터를 통해 달성된다 (읽기) 가지 audiorecord 개체 완료. 기록 처리에서는, 애플리케이션이 할 필요하면 적시에 데이터를 기록하는 방법으로 세 가지 종류가 음성 데이터를 획득하는 방법을 제공 가지 audiorecord 다음 3 가지 audiorecord 클래스 오브젝트를 취득한다 :

  • 읽기 (바이트 [], INT, INT)
  • 판독 (단락 [], INT, INT)
  • 읽기 (ByteBuffer를, INT)

당신은 사운드 데이터의 사용자 친화적 인 포맷을 저장하기 위해 사전에 설정해야하는 방법을 사용하도록 선택 여부.

시간을 기록 시작, 가지 audiorecord 사운드 관련 버퍼를 초기화해야, 버퍼는 주로 새로운 사운드 데이터를 저장하는 데 사용됩니다. 이 버퍼의 크기는, 우리는 객체 생성시 지정에 갈 수 있습니다. 이 음성 데이터는 톤 (즉, 하나의 용량의 사운드를 기록 할 수있다) 시간을 기록 할 수 있기 전에 가지 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 클래스는 안드로이드 플랫폼에서 작업 출력 오디오 데이터를 완료합니다. 두 AudioTrack 데이터 로딩 모드 (MODE_STREAM 및 MODE_STATIC),로드 모드와 두 개의 완전히 다른 사용 시나리오에 대응하는 오디오 스트림의 데이터 타입에 대응.

  • MODE_STREAM :이 모드에서, 기록 데이터를 다시 기록 된 오디오를 AudioTrack된다. 이것은 일반적으로 유사한 파일에 데이터를 기록하는 쓰기 시스템에 의해 호출하지만, 작업의 방법은 AudioTrack의 내부에 제공되는 버퍼에 버퍼에서 데이터를 복사하기 위해 모든 사용자가 필요로하는 어느 정도 지연의 도입 것이다. 두 번째 모델의 도입에이 문제 AudioTrack를 해결합니다.
  • MODE_STATIC :이 모드에서는, 단지 내부 버퍼 AudioTrack에 쓸 수있는 하나의 호출에 의해 전송되는 모든 데이터 전에 재생해야, 당신은 데이터 전송을 수행 할 필요가 없습니다. 이 작은 풋 프린트, 높은 대기 시간 요구 사항 문서를 벨소리로이 모드는 적합하다. 그러나 그것은 또한 그렇지 않으면 시스템이 모든 데이터를 저장하기에 충분한 메모리를 할당 할 수 없습니다, 쓰기 데이터가 너무 많이 할 수있는 단점을 가지고 있습니다.

MediaPlayer를 개발자에 의해 사용하기 위해 자바 API를 제공 둘 다 소리와 AudioTrack을 재생할 수 있습니다. 모두 소리를 재생할 수 있지만, 둘의 차이점을 많이 가지고 있지만, 가장 큰 차이점은 MediaPlayer를가 등등 MP3, AAC, WAV, OGG, MIDI 및 오디오 파일 형식의 다양한 재생할 수 있다는 것입니다. MediaPlayer를는 대응하는 오디오 디코더 프레임 워크 레이어를 생성한다. 그들의 대부분은 PCM 스트림이기 때문에 AudioTrack는 오디오 파일 포맷 WAV, WAV 형식의 오디오 파일을 지원하면 지원되는 파일 형식의 말씀을 비교한다면 AudioTrack 만, 그 디코딩 된 PCM 스트림을 재생할 수 있습니다. 디코더를 생성하지 AudioTrack, 우리는이 wav 파일을 디코딩 할 필요가 없습니다 재생할 수 있습니다.

5.1 오디오 스트림을 입력

AudioTrack 생성자에서이 매개 변수는 AudioManager.STREAM_MUSIC에 노출됩니다. 그것의 의미와 안드로이드 시스템 관리 및 관련 오디오 스트림의 분류.

안드로이드 사운드 시스템은 여러 개의 스트림 유형으로 나누어 져 있습니다, 여기에 몇 가지 일반적인 것들이다 :

STREAM_ALARM : 사운드 경고
음악, 음악 및 기타 등 : STREAM_MUSIC
STREAM_RING : 벨소리
STREAM_SYSTEM : 사운드 시스템, 예를 들어, 낮은 톤, 잠금 화면, 사운드 등
STREAM_VOCIE_CALL을 : 사운드 전화

참고 : 위의 구분 자체가 문제가되지 않는 오디오 데이터의 이러한 유형. 예를 들어 음악 RING 유형과 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 바이트 프레임 같음). 하나의 샘플 포인트를위한 채널은, 사실, 하나 개 이상의 채널이있을 수 있습니다. 하나 개의 샘플의 모든 채널 단위로 표현 된 데이터 량을 분리 할 수 ​​없다는, 그것은 프레임의 개념을 이끈다. 프레임 크기는 점 샘플 채널 수 × 바이트의 수입니다. 또한, 내부 버퍼에 존재하는 사운드 카드 드라이버 또한 프레임 단위의 할당 및 관리에 사용된다.

getMinBufSize 하드웨어의 경우 고려할 후 (예 샘플링 레이트를 지원할지 여부 등을, 하드웨어 자체의 지연 등), 버퍼의 최소 사이즈를 획득. 일반적으로 우리는 그것의 정수 배가 될 것이다 크기를 버퍼 할당합니다.

5.3 빌드 프로세스

각 오디오 스트림은 그 다음 (믹서)를 혼합하여 클래스 AudioTrack 각 모든 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