如何将.pcm文件转换为.wav或.mp3文件?

时间:2016-05-17 16:12:38

标签: android pcm audiotrack

我目前正在开发一款具有录音和播放功能的Android应用程序。我是处理音频的新手,我在编码和格式方面遇到了一些麻烦。

我可以在我的应用程序中录制和播放音频,但在导出时我无法重现音频。我找到的唯一方法是导出我的.pcm文件并使用Audacity进行转换。

这是我录制音频的代码:

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyCtrl">
  <table>
    <thead>
      <tr>
        <th width="180">Column</th>
        <th>Edit</th>
      </tr>
    </thead>
    <tbody>
      <tr ng-repeat="item in data" ng-switch on="editing(item)" >
        <td ng-switch-default ng-bind="item.color"></td>
        <td ng-switch-when='true'>
          <input type="text" ng-model="item.color" />
        </td>
        <td ng-switch-default><button ng-click="switch(item)">edit</button></td>
        <td ng-switch-when='true'><button ng-click="send(item)">send</button></td>
      </tr>
    </tbody>
  </table><br>
  "$scope.data" should never change after hitting edit/send since the flag is no longer on the data item object:
  <code><pre>{{data}}</pre></code>
</div>

要播放录制的音频,代码为:

private Thread recordingThread 
private AudioRecord mRecorder;
private boolean isRecording = false;

private void startRecording() {

    mRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
            Constants.RECORDER_SAMPLERATE, Constants.RECORDER_CHANNELS,
            Constants.RECORDER_AUDIO_ENCODING, Constants.BufferElements2Rec * Constants.BytesPerElement);

    mRecorder.startRecording();
    isRecording = true;

    recordingThread = new Thread(new Runnable() {
        public void run() {
            writeAudioDataToFile();
        }
    }, "AudioRecorder Thread");
    recordingThread.start();
}

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

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

    while (isRecording) {
        // gets the voice output from microphone to byte format
        mRecorder.read(sData, 0, Constants.BufferElements2Rec);
        try {
            // // writes the data to file from buffer
            // // stores the voice buffer

            byte bData[] = short2byte(sData);

            os.write(bData, 0, Constants.BufferElements2Rec * Constants.BytesPerElement);

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

Constants类中定义的常量是:

private void startPlaying() {

    new Thread(new Runnable() {
        public void run() {

            try {

                File file = new File(mFileName);

                byte[] audioData = null;

                InputStream inputStream = new FileInputStream(mFileName);
                audioData = new byte[Constants.BufferElements2Rec];

                mPlayer = new AudioTrack(AudioManager.STREAM_MUSIC, Constants.RECORDER_SAMPLERATE,
                        AudioFormat.CHANNEL_OUT_MONO, Constants.RECORDER_AUDIO_ENCODING,
                        Constants.BufferElements2Rec * Constants.BytesPerElement, AudioTrack.MODE_STREAM);


                final float duration = (float) file.length() / Constants.RECORDER_SAMPLERATE / 2;

                Log.i(TAG, "PLAYBACK AUDIO");
                Log.i(TAG, String.valueOf(duration));


                mPlayer.setPositionNotificationPeriod(Constants.RECORDER_SAMPLERATE / 10);
                mPlayer.setNotificationMarkerPosition(Math.round(duration * Constants.RECORDER_SAMPLERATE));

                mPlayer.play();

                int i = 0;
                while ((i = inputStream.read(audioData)) != -1) {
                    try {
                        mPlayer.write(audioData, 0, i);
                    } catch (Exception e) {
                        Log.e(TAG, "Exception: " + e.getLocalizedMessage());
                    }
                }

            } catch (FileNotFoundException fe) {
                Log.e(TAG, "File not found: " + fe.getLocalizedMessage());
            } catch (IOException io) {
                Log.e(TAG, "IO Exception: " + io.getLocalizedMessage());
            }

        }

    }).start();


}

如果我按原样导出文件,我会将其转换为Audacity并播放。但是,我需要以可以自动播放的格式导出它。

我已经看到了实施Lame的答案,目前我正在研究它。我还找到了使用以下方法转换它的答案:

public class Constants {

    final static public int RECORDER_SAMPLERATE = 44100;
    final static public int RECORDER_CHANNELS = AudioFormat.CHANNEL_IN_MONO;
    final static public int RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;

    final static public int BufferElements2Rec = 1024; // want to play 2048 (2K) since 2 bytes we use only 1024
    final static public int BytesPerElement = 2; // 2 bytes in 16bit format


}

但是,这会在导出时播放正确的持续时间,但只是白噪声。

我尝试但无法解决的一些答案:

任何人都可以指出什么是最好的解决方案?它是真的实施跛脚还是可以更直接的方式完成?如果是这样,为什么代码示例将文件转换为白噪声?

4 个答案:

答案 0 :(得分:15)

您已获得大部分代码正确无误。我能看到的唯一问题是将PCM数据写入WAV文件的部分。这应该很简单,因为WAV =元数据+ PCM(按此顺序)。这应该有效:

private void rawToWave(final File rawFile, final File waveFile) throws IOException {

    byte[] rawData = new byte[(int) rawFile.length()];
    DataInputStream input = null;
    try {
        input = new DataInputStream(new FileInputStream(rawFile));
        input.read(rawData);
    } finally {
        if (input != null) {
            input.close();
        }
    }

    DataOutputStream output = null;
    try {
        output = new DataOutputStream(new FileOutputStream(waveFile));
        // WAVE header
        // see http://ccrma.stanford.edu/courses/422/projects/WaveFormat/
        writeString(output, "RIFF"); // chunk id
        writeInt(output, 36 + rawData.length); // chunk size
        writeString(output, "WAVE"); // format
        writeString(output, "fmt "); // subchunk 1 id
        writeInt(output, 16); // subchunk 1 size
        writeShort(output, (short) 1); // audio format (1 = PCM)
        writeShort(output, (short) 1); // number of channels
        writeInt(output, 44100); // sample rate
        writeInt(output, RECORDER_SAMPLERATE * 2); // byte rate
        writeShort(output, (short) 2); // block align
        writeShort(output, (short) 16); // bits per sample
        writeString(output, "data"); // subchunk 2 id
        writeInt(output, rawData.length); // subchunk 2 size
        // Audio data (conversion big endian -> little endian)
        short[] shorts = new short[rawData.length / 2];
        ByteBuffer.wrap(rawData).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(shorts);
        ByteBuffer bytes = ByteBuffer.allocate(shorts.length * 2);
        for (short s : shorts) {
            bytes.putShort(s);
        }

        output.write(fullyReadFileToBytes(rawFile));
    } finally {
        if (output != null) {
            output.close();
        }
    }
}
    byte[] fullyReadFileToBytes(File f) throws IOException {
    int size = (int) f.length();
    byte bytes[] = new byte[size];
    byte tmpBuff[] = new byte[size];
    FileInputStream fis= new FileInputStream(f);
    try { 

        int read = fis.read(bytes, 0, size);
        if (read < size) {
            int remain = size - read;
            while (remain > 0) {
                read = fis.read(tmpBuff, 0, remain);
                System.arraycopy(tmpBuff, 0, bytes, size - remain, read);
                remain -= read;
            } 
        } 
    }  catch (IOException e){
        throw e;
    } finally { 
        fis.close();
    } 

    return bytes;
} 
private void writeInt(final DataOutputStream output, final int value) throws IOException {
    output.write(value >> 0);
    output.write(value >> 8);
    output.write(value >> 16);
    output.write(value >> 24);
}

private void writeShort(final DataOutputStream output, final short value) throws IOException {
    output.write(value >> 0);
    output.write(value >> 8);
}

private void writeString(final DataOutputStream output, final String value) throws IOException {
    for (int i = 0; i < value.length(); i++) {
        output.write(value.charAt(i));
    }
}

如何使用

使用起来非常简单。只需将其称为:

  File f1 = new File("/sdcard/44100Sampling-16bit-mono-mic.pcm"); // The location of your PCM file
  File f2 = new File("/sdcard/44100Sampling-16bit-mono-mic.wav"); // The location where you want your WAV file
  try {
    rawToWave(f1, f2);
} catch (IOException e) {
    e.printStackTrace();
}

这一切是如何运作的

如您所见,WAV标头是WAV和PCM文件格式之间的唯一区别。假设您正在录制16位PCM MONO音频(根据您的代码,您是)。 rawToWave函数只是巧妙地将标题添加到WAV文件中,以便音乐播放器知道打开文件时会发生什么,然后在标题之后,它只会从最后一位开始写入PCM数据。

酷提示

如果您想改变语音音调或制作语音转换器应用,您所要做的就是增加/减少代码中writeInt(output, 44100); // sample rate的值。减小它将告诉玩家以不同的速率播放它,从而改变输出音高。只需要多一点额外的知识&#39;事情。 :)

答案 1 :(得分:1)

为了注册,我解决了使用MediaRecorder而不是Audio Recorder录制可在普通播放器中播放音频的需求。

开始录制:

    MediaRecorder mRecorder = new MediaRecorder();
    mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
    mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
    mRecorder.setAudioEncoder(MediaRecorder.OutputFormat.AMR_NB);
    mRecorder.setOutputFile(Environment.getExternalStorageDirectory()
                .getAbsolutePath() + "/recording.3gp");

    mRecorder.prepare();
    mRecorder.start();

播放录音:

    mPlayer = new MediaPlayer();
    mPlayer.setDataSource(Environment.getExternalStorageDirectory()
                .getAbsolutePath() + "/recording.3gp");

    mPlayer.prepare();
    mPlayer.start();

答案 2 :(得分:1)

我知道现在已经很晚了,你可以使用MediaRecorder。但想到分享我的答案,因为我花了一些时间来找到它。 :)

当您录制音频时,数据readAudioRecord对象的短,然后在存储到.pcm文件之前转换为字节。

现在,当您编写.wav文件时,您将再次执行简短转换。这不是必需的。因此,在您的代码中,如果删除以下块并将rawData直接写入.wav文件的末尾。它会工作得很好。

  short[] shorts = new short[rawData.length / 2];  
  ByteBuffer.wrap(rawData).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(shorts);
    ByteBuffer bytes = ByteBuffer.allocate(shorts.length * 2);
    for (short s : shorts) {
        bytes.putShort(s);
    }

检查删除重复代码块后您将获得的以下代码。

    writeInt(output, rawData.length); // subchunk 2 size
    // removed the duplicate short conversion
    output.write(rawData);

答案 3 :(得分:0)

我尝试了上面的代码来录制音频writeAudioDataToFile()。它可以完美地记录音频并将其转换为.wav format。但是当我播放录制的音频时,速度太快了。 2.5秒内完成5秒音频。然后我发现这是因为有此short2byte()函数。

对于那些同样有问题的人,请勿使用short2byte()并直接在第os.write(sData, 0, Constants.BufferElements2Rec * Constants.BytesPerElement);行中写入sData,其中sData应该为byte[]