【问题标题】:Recording .Wav with Android AudioRecorder使用 Android AudioRecorder 录制 .Wav
【发布时间】:2013-06-16 01:08:33
【问题描述】:

我已经阅读了很多关于 Android 的 AudioRecorder 的页面。您可以在问题下方看到它们的列表。

我正在尝试使用 AudioRecorder 录制音频,但效果不佳。

public class MainActivity extends Activity {

AudioRecord ar = null;
int buffsize = 0;

int blockSize = 256;
boolean isRecording = false;
private Thread recordingThread = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);


}

public void baslat(View v)
{
            // when click to START 
    buffsize = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
    ar = new AudioRecord(MediaRecorder.AudioSource.MIC, 44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, buffsize);

    ar.startRecording();

    isRecording = true;
    recordingThread = new Thread(new Runnable() {
        public void run() {
            writeAudioDataToFile();
        }
    }, "AudioRecorder Thread");
    recordingThread.start();
}
public void durdur(View v)
{
            // When click to STOP
    ar.stop();
    isRecording = false;
}

private void writeAudioDataToFile() {
    // Write the output audio in byte

    String filePath = "/sdcard/voice8K16bitmono.wav";
    short sData[] = new short[buffsize/2];

    FileOutputStream os = null;
    try {
        os = new FileOutputStream(filePath);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }

    while (isRecording) {
        // gets the voice output from microphone to byte format

        ar.read(sData, 0, buffsize/2);
        Log.d("eray","Short wirting to file" + sData.toString());
        try {
            // // writes the data to file from buffer
            // // stores the voice buffer
            byte bData[] = short2byte(sData);
            os.write(bData, 0, buffsize);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    try {
        os.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

private byte[] short2byte(short[] sData) {
    int shortArrsize = sData.length;
    byte[] bytes = new byte[shortArrsize * 2];
    for (int i = 0; i < shortArrsize; i++) {
        bytes[i * 2] = (byte) (sData[i] & 0x00FF);
        bytes[(i * 2) + 1] = (byte) (sData[i] >> 8);
        sData[i] = 0;
    }
    return bytes;

}

它正在创建一个 .wav 文件,但是当我尝试收听它时,它没有打开。我收到“文件不支持”错误。我尝试过使用很多媒体播放器应用程序播放该文件。

注意:我必须使用 AudioRecorder 而不是 MediaRecorder,因为我的应用在录制时会执行另一个进程(显示均衡器)。

这是我读过的关于这个主题的页面列表:

  1. http://developer.android.com/reference/android/media/AudioRecord.html#read(short[],%20int,%20int)
  2. Android AudioRecord example
  3. http://audiorecordandroid.blogspot.in
  4. AudioRecord object not initializing
  5. Recording a wav file from the mic in Android - problems
  6. http://i-liger.com/article/android-wav-audio-recording
  7. Creating a WAV file from raw PCM data using the Android SDK
  8. Capturing Sound for Analysis and Visualizing Frequencies in Android

有很多不同的方法可以解决这个问题。我已经尝试了很多,但对我没有任何作用。我已经在这个问题上工作了大约 6 个小时,所以我希望得到一个明确的答案,最好是一些示例代码。

【问题讨论】:

  • 您写入文件的只是 PCM(音频)数据。有效的 WAV 文件还包含您需要生成的 a header(正如您链接到的旧问题之一中所建议的那样)。
  • 什么是 ringdroid api?也许你可以从中得到一些想法,有一个 wav 读/写类:code.google.com/p/ringdroid/source/browse/trunk/src/com/…
  • @Michael ,哪一个?
  • @Eray: This one.
  • @Michael ,你能提供一个示例代码吗?这个答案是理论上的,我在实践中做不到:) 我怎样才能为我的文件发送一个 wav 头?

标签: android audiorecord


【解决方案1】:

我昨天写了一个简单的(你应该阅读的,而不是专业标准的)课程来做到这一点,它有效。

private class Wave {
    private final int LONGINT = 4;
    private final int SMALLINT = 2;
    private final int INTEGER = 4;
    private final int ID_STRING_SIZE = 4;
    private final int WAV_RIFF_SIZE = LONGINT + ID_STRING_SIZE;
    private final int WAV_FMT_SIZE = (4 * SMALLINT) + (INTEGER * 2) + LONGINT + ID_STRING_SIZE;
    private final int WAV_DATA_SIZE = ID_STRING_SIZE + LONGINT;
    private final int WAV_HDR_SIZE = WAV_RIFF_SIZE + ID_STRING_SIZE + WAV_FMT_SIZE + WAV_DATA_SIZE;
    private final short PCM = 1;
    private final int SAMPLE_SIZE = 2;
    int cursor, nSamples;
    byte[] output;

    public Wave(int sampleRate, short nChannels, short[] data, int start, int end) {
        nSamples = end - start + 1;
        cursor = 0;
        output = new byte[nSamples * SMALLINT + WAV_HDR_SIZE];
        buildHeader(sampleRate, nChannels);
        writeData(data, start, end);
    }

    // ------------------------------------------------------------
    private void buildHeader(int sampleRate, short nChannels) {
        write("RIFF");
        write(output.length);
        write("WAVE");
        writeFormat(sampleRate, nChannels);
    }

    // ------------------------------------------------------------
    public void writeFormat(int sampleRate, short nChannels) {
        write("fmt ");
        write(WAV_FMT_SIZE - WAV_DATA_SIZE);
        write(PCM);
        write(nChannels);
        write(sampleRate);
        write(nChannels * sampleRate * SAMPLE_SIZE);
        write((short) (nChannels * SAMPLE_SIZE));
        write((short) 16);
    }

    // ------------------------------------------------------------
    public void writeData(short[] data, int start, int end) {
        write("data");
        write(nSamples * SMALLINT);
        for (int i = start; i <= end; write(data[i++])) ;
    }

    // ------------------------------------------------------------
    private void write(byte b) {
        output[cursor++] = b;
    }

    // ------------------------------------------------------------
    private void write(String id) {
        if (id.length() != ID_STRING_SIZE)
            Utils.logError("String " + id + " must have four characters.");
        else {
            for (int i = 0; i < ID_STRING_SIZE; ++i) write((byte) id.charAt(i));
        }
    }

    // ------------------------------------------------------------
    private void write(int i) {
        write((byte) (i & 0xFF));
        i >>= 8;
        write((byte) (i & 0xFF));
        i >>= 8;
        write((byte) (i & 0xFF));
        i >>= 8;
        write((byte) (i & 0xFF));
    }

    // ------------------------------------------------------------
    private void write(short i) {
        write((byte) (i & 0xFF));
        i >>= 8;
        write((byte) (i & 0xFF));
    }

    // ------------------------------------------------------------
    public boolean wroteToFile(String filename) {
        boolean ok = false;

        try {
            File path = new File(getFilesDir(), filename);
            FileOutputStream outFile = new FileOutputStream(path);
            outFile.write(output);
            outFile.close();
            ok = true;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            ok = false;
        } catch (IOException e) {
            ok = false;
            e.printStackTrace();
        }
        return ok;
    }
}

希望对你有帮助

【讨论】:

  • 如何调用这个类?我已经使用 AudioRecord 录制了音频并将文件写入 pcm 文件。
  • 确保您的声音样本在一个短裤数组中,如果是立体声样本则交错,然后调用:Wave wave=new Wave(SAMPLE_RATE,NUM_CHANNELS,outputSignal,0,outputSignal.length-1) ; if(wave.wroteToFile("your_filename.wav")) log("点击写入成功。"); else log("点击写入失败。");
  • 如果您可以将VOIP呼叫中的数据放入一个短裤数组中,那么它将起作用;但我怀疑有更好的方法。
  • 您可以将其用于字节(将其添加到类中): public Wave(int sampleRate, short nChannels, byte[] data, int start, int end) { int size = data.length;短 [] 短数组 = 新短 [大小]; for (int index = 0; index
【解决方案2】:

您可能会发现此OMRECORDER 有助于记录.WAV 格式。

如果.aac 与您合作,请查看此WhatsappAudioRecorder

点击开始录制按钮:

  1. 初始化新线程。
  2. 创建带有.aac 扩展名的文件。
  3. 创建文件的输出流。
  4. 设置输出
  5. SetListener 并执行线程。

OnStopClick :

  1. 中断线程,音频将保存在文件中。

Here is full gist of for reference :

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaRecorder;
import android.os.Build;
import android.util.Log;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;

public class AudioRecordThread implements Runnable {

    private static final String TAG = AudioRecordThread.class.getSimpleName();

    private static final int SAMPLE_RATE = 44100;
    private static final int SAMPLE_RATE_INDEX = 4;
    private static final int CHANNELS = 1;
    private static final int BIT_RATE = 32000;

    private final int bufferSize;
    private final MediaCodec mediaCodec;
    private final AudioRecord audioRecord;
    private final OutputStream outputStream;

    private OnRecorderFailedListener onRecorderFailedListener;


    AudioRecordThread(OutputStream outputStream, OnRecorderFailedListener onRecorderFailedListener) throws IOException {

        this.bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
        this.audioRecord = createAudioRecord(this.bufferSize);
        this.mediaCodec = createMediaCodec(this.bufferSize);
        this.outputStream = outputStream;
        this.onRecorderFailedListener = onRecorderFailedListener;

        this.mediaCodec.start();

        try {
            audioRecord.startRecording();
        } catch (Exception e) {
            Log.w(TAG, e);
            mediaCodec.release();
            throw new IOException(e);
        }
    }

    @Override
    public void run() {
        if (onRecorderFailedListener != null) {
            Log.d(TAG, "onRecorderStarted");
            onRecorderFailedListener.onRecorderStarted();
        }
        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
        ByteBuffer[] codecInputBuffers = mediaCodec.getInputBuffers();
        ByteBuffer[] codecOutputBuffers = mediaCodec.getOutputBuffers();

        try {
            while (!Thread.interrupted()) {

                boolean success = handleCodecInput(audioRecord, mediaCodec, codecInputBuffers, Thread.currentThread().isAlive());
                if (success)
                    handleCodecOutput(mediaCodec, codecOutputBuffers, bufferInfo, outputStream);
            }
        } catch (IOException e) {
            Log.w(TAG, e);
        } finally {
            mediaCodec.stop();
            audioRecord.stop();

            mediaCodec.release();
            audioRecord.release();

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


    private boolean handleCodecInput(AudioRecord audioRecord,
                                     MediaCodec mediaCodec, ByteBuffer[] codecInputBuffers,
                                     boolean running) throws IOException {
        byte[] audioRecordData = new byte[bufferSize];
        int length = audioRecord.read(audioRecordData, 0, audioRecordData.length);

        if (length == AudioRecord.ERROR_BAD_VALUE ||
                length == AudioRecord.ERROR_INVALID_OPERATION ||
                length != bufferSize) {

            if (length != bufferSize) {
                if (onRecorderFailedListener != null) {
                    Log.d(TAG, "length != BufferSize calling onRecordFailed");
                    onRecorderFailedListener.onRecorderFailed();
                }
                return false;
            }
        }

        int codecInputBufferIndex = mediaCodec.dequeueInputBuffer(10 * 1000);

        if (codecInputBufferIndex >= 0) {
            ByteBuffer codecBuffer = codecInputBuffers[codecInputBufferIndex];
            codecBuffer.clear();
            codecBuffer.put(audioRecordData);
            mediaCodec.queueInputBuffer(codecInputBufferIndex, 0, length, 0, running ? 0 : MediaCodec.BUFFER_FLAG_END_OF_STREAM);
        }

        return true;
    }

    private void handleCodecOutput(MediaCodec mediaCodec,
                                   ByteBuffer[] codecOutputBuffers,
                                   MediaCodec.BufferInfo bufferInfo,
                                   OutputStream outputStream)
            throws IOException {
        int codecOutputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);

        while (codecOutputBufferIndex != MediaCodec.INFO_TRY_AGAIN_LATER) {
            if (codecOutputBufferIndex >= 0) {
                ByteBuffer encoderOutputBuffer = codecOutputBuffers[codecOutputBufferIndex];

                encoderOutputBuffer.position(bufferInfo.offset);
                encoderOutputBuffer.limit(bufferInfo.offset + bufferInfo.size);

                if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
                    byte[] header = createAdtsHeader(bufferInfo.size - bufferInfo.offset);


                    outputStream.write(header);

                    byte[] data = new byte[encoderOutputBuffer.remaining()];
                    encoderOutputBuffer.get(data);
                    outputStream.write(data);
                }

                encoderOutputBuffer.clear();

                mediaCodec.releaseOutputBuffer(codecOutputBufferIndex, false);
            } else if (codecOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                codecOutputBuffers = mediaCodec.getOutputBuffers();
            }

            codecOutputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
        }
    }


    private byte[] createAdtsHeader(int length) {
        int frameLength = length + 7;
        byte[] adtsHeader = new byte[7];

        adtsHeader[0] = (byte) 0xFF; // Sync Word
        adtsHeader[1] = (byte) 0xF1; // MPEG-4, Layer (0), No CRC
        adtsHeader[2] = (byte) ((MediaCodecInfo.CodecProfileLevel.AACObjectLC - 1) << 6);
        adtsHeader[2] |= (((byte) SAMPLE_RATE_INDEX) << 2);
        adtsHeader[2] |= (((byte) CHANNELS) >> 2);
        adtsHeader[3] = (byte) (((CHANNELS & 3) << 6) | ((frameLength >> 11) & 0x03));
        adtsHeader[4] = (byte) ((frameLength >> 3) & 0xFF);
        adtsHeader[5] = (byte) (((frameLength & 0x07) << 5) | 0x1f);
        adtsHeader[6] = (byte) 0xFC;

        return adtsHeader;
    }

    private AudioRecord createAudioRecord(int bufferSize) {
        AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE,
                AudioFormat.CHANNEL_IN_MONO,
                AudioFormat.ENCODING_PCM_16BIT, bufferSize * 10);

        if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
            Log.d(TAG, "Unable to initialize AudioRecord");
            throw new RuntimeException("Unable to initialize AudioRecord");
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            if (android.media.audiofx.NoiseSuppressor.isAvailable()) {
                android.media.audiofx.NoiseSuppressor noiseSuppressor = android.media.audiofx.NoiseSuppressor
                        .create(audioRecord.getAudioSessionId());
                if (noiseSuppressor != null) {
                    noiseSuppressor.setEnabled(true);
                }
            }
        }


        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            if (android.media.audiofx.AutomaticGainControl.isAvailable()) {
                android.media.audiofx.AutomaticGainControl automaticGainControl = android.media.audiofx.AutomaticGainControl
                        .create(audioRecord.getAudioSessionId());
                if (automaticGainControl != null) {
                    automaticGainControl.setEnabled(true);
                }
            }
        }


        return audioRecord;
    }

    private MediaCodec createMediaCodec(int bufferSize) throws IOException {
        MediaCodec mediaCodec = MediaCodec.createEncoderByType("audio/mp4a-latm");
        MediaFormat mediaFormat = new MediaFormat();

        mediaFormat.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
        mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE);
        mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, CHANNELS);
        mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, bufferSize);
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
        mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);

        try {
            mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        } catch (Exception e) {
            Log.w(TAG, e);
            mediaCodec.release();
            throw new IOException(e);
        }

        return mediaCodec;
    }

    interface OnRecorderFailedListener {
        void onRecorderFailed();

        void onRecorderStarted();
    }
}

【讨论】:

    【解决方案3】:

    首先你需要知道wav文件有它的格式——header。所以你不能只将纯数据写入 .wav 文件。

    第二个wav文件头包含文件的长度。所以你需要在录制后写标题。

    我的解决方案是,用户 AudioRecorder 录制 pcm 文件。

        byte[] audiodata = new byte[bufferSizeInBytes];
    
        FileOutputStream fos = null;
        int readsize = 0;
        try {
            fos = new FileOutputStream(pcmFileName, true);
        } catch (FileNotFoundException e) {
            Log.e("AudioRecorder", e.getMessage());
        }
    
        status = Status.STATUS_START;
        while (status == Status.STATUS_START && audioRecord != null) {
            readsize = audioRecord.read(audiodata, 0, bufferSizeInBytes);
            if (AudioRecord.ERROR_INVALID_OPERATION != readsize && fos != null) {
    
                if (readsize > 0 && readsize <= audiodata.length)
                        fos.write(audiodata, 0, readsize);
                } catch (IOException e) {
                    Log.e("AudioRecorder", e.getMessage());
                }
            }
        }
        try {
            if (fos != null) {
                fos.close();
            }
        } catch (IOException e) {
            Log.e("AudioRecorder", e.getMessage());
        }
    

    然后将其转换为 wav 文件。

        byte buffer[] = null;
        int TOTAL_SIZE = 0;
        File file = new File(pcmPath);
        if (!file.exists()) {
            return false;
        }
        TOTAL_SIZE = (int) file.length();
    
        WaveHeader header = new WaveHeader();
    
        header.fileLength = TOTAL_SIZE + (44 - 8);
        header.FmtHdrLeth = 16;
        header.BitsPerSample = 16;
        header.Channels = 1;
        header.FormatTag = 0x0001;
        header.SamplesPerSec = 8000;
        header.BlockAlign = (short) (header.Channels * header.BitsPerSample / 8);
        header.AvgBytesPerSec = header.BlockAlign * header.SamplesPerSec;
        header.DataHdrLeth = TOTAL_SIZE;
    
        byte[] h = null;
        try {
            h = header.getHeader();
        } catch (IOException e1) {
            Log.e("PcmToWav", e1.getMessage());
            return false;
        }
    
        if (h.length != 44) 
            return false;
    
    
        File destfile = new File(destinationPath);
        if (destfile.exists())
            destfile.delete();
    
    
        try {
            buffer = new byte[1024 * 4]; // Length of All Files, Total Size
            InputStream inStream = null;
            OutputStream ouStream = null;
    
            ouStream = new BufferedOutputStream(new FileOutputStream(
                    destinationPath));
            ouStream.write(h, 0, h.length);
            inStream = new BufferedInputStream(new FileInputStream(file));
            int size = inStream.read(buffer);
            while (size != -1) {
                ouStream.write(buffer);
                size = inStream.read(buffer);
            }
            inStream.close();
            ouStream.close();
        } catch (FileNotFoundException e) {
            Log.e("PcmToWav", e.getMessage());
            return false;
        } catch (IOException ioe) {
            Log.e("PcmToWav", ioe.getMessage());
            return false;
        }
        if (deletePcmFile) {
            file.delete();
        }
        Log.i("PcmToWav", "makePCMFileToWAVFile  success!" + new SimpleDateFormat("yyyy-MM-dd hh:mm").format(new Date()));
        return true;
    

    【讨论】:

      【解决方案4】:

      我会将此添加为评论,但我还没有足够的 Stackoverflow 代表点...

      Opiatefuchs's link 带您查看示例代码,该示例代码向您展示了创建 .wav 文件所需的确切标题格式。我自己一直在研究那个代码。很有帮助。

      【讨论】:

      • 该代码中有 307 行...标题的哪一部分?我将复制它并在我的项目中使用。
      • 对我来说,从来没有像复制代码那么简单。我快速浏览了源文件。其中有 ReadFile 和 WriteFile 方法。两者都在处理标题。花点时间浏览一下,它对你来说应该很明显。您需要花费一些时间来弄清楚逻辑。哦,顺便说一句,我记得我也使用过 Wikipedia(只需查找“.wav”)。
      • 对我来说仍然很复杂...尝试使用 wavIO 类但仍然创建的 .wav 文件不起作用。例如我不知道什么是 ChunkSize..
      • 很抱歉,但我自己没有卷起袖子再次进入这段代码,我剩下的就是:如果你的源代码太复杂,那就把它归结为基础。我提到的方法显示了标题。您知道代码正在使用流式调用,考虑到此线程顶部的代码,您对此很熟悉。如果我正在解决这个问题,并且已经在上面编写了您的代码,那么我会努力让一些工作正常......(继续下一条评论)
      • ... 为此,我将编写一个“header-write”方法,该方法使用与 Ringdroid 代码中相同的字节数组标题格式,然后从您的东西中调用它。我会修补它,直到媒体播放器“得到它”。一旦我使用工作代码进行操作,我就会开始构建适当的类。
      【解决方案5】:

      PCMAudioHelper 解决了我的问题。我会修改这个答案并解释它,但首先我必须对这个类做一些测试。

      【讨论】:

      • 我看了看这些东西。绝对有趣,尤其是 RiffHeaderData.java。感谢您发布您的发现。我已将它添加到我的“前往”列表中。
      • @UpLate ,这是我的荣幸!我今天将编辑我的答案以获取详细信息。
      • @Eray 当你有机会时,你介意分享你的解决方案箱
      • 链接断开了吗?
      • 这个项目已经归档,我认为它不再需要维护了。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-12-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-29
      • 2014-04-17
      • 2014-07-27
      相关资源
      最近更新 更多