Analog to digital conversion of sound

Acoustic signal from analog signal to digital signal, through sampling, quantization, coding three steps

The sampling

Sampling is the discretization of signals along the time axis, as shown in the figure below

quantitative

Quantization refers to the discretization of signals on the amplitude axis. Generally, 16bit or 8bit is used to represent a sampling of sound. Taking 16bit as an example, the value range is [-32768, 32767], so 65536 layers are divided on the amplitude axis, as shown in the figure below

coding

Encoding is to record the sampled and quantized digital data in a certain format, such as sequential storage (PCM) or compressed storage (AAC, MP3), etc.

PCM

PCM: Pulse Code Modulation data, is uncompressed original audio data. In general, PCM data has the following indicators: sampling rate, sampleFormat, and number of channels. Take CD sound quality as an example: quantization format is 16 bits (2 bytes), sampling rate is 44100, number of sound channels is 2, if the sampling PCM format is stored, then storing a 1-minute audio will occupy 44100 * 2 * 2 * 60=10.09MB. Another important indicator of sound format is the bit rate, which is the number of bits in a second. Generally, the higher the bit rate, the better the sound quality. Take the CD sound quality above as an example. The bit rate is 44100 * 16 * 2 = 1378.125 KBPS

The sample

Let’s use an example to understand:

AudioRecord record PCM

public class PCMConfig {

    public static final int SAMPLE_RATE_IN_HZ = 44100;// The sampling rate is 44.1khz
    public static final int CHANNEL = AudioFormat.CHANNEL_IN_STEREO;// Double channel (left and right channel)
    public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;// Each sample point is 16 bits, i.e. 2 bytes
}

public class PCMRecorder {

    private AudioRecord mAudioRecord;
    private int mBufferSize;

    public PCMRecorder(a) {
        mBufferSize = AudioRecord.getMinBufferSize(PCMConfig.SAMPLE_RATE_IN_HZ, 
            PCMConfig.CHANNEL,
            PCMConfig.AUDIO_FORMAT);
        mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
            PCMConfig.SAMPLE_RATE_IN_HZ,
            PCMConfig.CHANNEL,
            PCMConfig.AUDIO_FORMAT,
            mBufferSize);
    }
    
    public void startRecord(String outputPath) {
        fos = new FileOutputStream(outputPath);
        mAudioRecord.startRecording();
        byte[] byteBuffer = new byte[mBufferSize];
        while (mRecording) {
            int len = mAudioRecord.read(byteBuffer, 0, byteBuffer.length);
            fos.write(byteBuffer, 0, len); } fos.flush(); mAudioRecord.stop(); }}Copy the code

Above code, we first set the AudioRecorder sampling rate, sound track, quantitative indicators, and then through the AudioRecorder read method cycle will be collected by the microphone audio data written to the file, until the user actively end recording, so we get a PCM original audio file. Now let’s look at how to view and play PCM files

View and play PCM

There is software for viewing PCM files on the PC, Adobe Audition, a professional audio editing software for a fee, and Audacity, a free open-source audio editing software, on Windows. Let’s use Audacity to open up the PCM file we just recorded and take a look at it, just to get a sense of the audio data.

We can also use AudioTrack to play PCM directly on Android devices

public class PCMPlayer {
    
    public void play(File pcmFile) {
        AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                PCMConfig.SAMPLE_RATE_IN_HZ,
                PCMConfig.CHANNEL,
                PCMConfig.AUDIO_FORMAT,
                (int) pcmFile.length(),
                AudioTrack.MODE_STATIC);
        FileInputStream fis = new FileInputStream(pcmFile);
        byte[] buffer = new byte[1024 * 1024];
        int len = 0;
        while( (len = fis.read(buffer)) ! = -1) {
            audioTrack.write(buffer, 0, len); } audioTrack.play(); }}Copy the code

Halve the PCM volume

public static boolean halfVolume(File pcmFile) {
        byte[] audioData = FileUtils.toByteArray(pcmFile);
        FileOutputStream fos = null;
        try {
            File output = new File(pcmFile.getParentFile(), pcmFile.getName() + ".half");
            fos = new FileOutputStream(output);
            int length = audioData.length;
            for (int i = 0; i < length - 2; i += 2) {
                // Take 2 bytes at a time to indicate the volume
                int volume = Util.toUnsignedInt16(audioData[i], audioData[i + 1]);
                // The volume is halved
                volume = volume / 2;
                byte[] bytes = Util.unsignedInt16ToByteArray(volume);
                audioData[i] = bytes[0];
                audioData[i + 1] = bytes[1];
            }
            fos.write(audioData);
            fos.flush();
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(fos);
        }
        return false;
}
Copy the code

The effect is shown below.

Separate the left and right channels of PCM

public static boolean splitChannel(File pcmFile) {
        byte[] audioData = FileUtils.toByteArray(pcmFile);
        if (audioData == null) {
            return false;
        }
        FileOutputStream fosLeft = null;
        FileOutputStream fosRight = null;
        try {
            File leftOutput = new File(pcmFile.getParentFile(), pcmFile.getName() + ".left");
            fosLeft = new FileOutputStream(leftOutput);
            File rightOutput = new File(pcmFile.getParentFile(), pcmFile.getName() + ".right");
            fosRight  =new FileOutputStream(rightOutput);

            ByteArrayOutputStream bosLeft = new ByteArrayOutputStream((int) (pcmFile.length() / 2));
            ByteArrayOutputStream bosRight = new ByteArrayOutputStream((int) (pcmFile.length() / 2));

            int len = audioData.length;
            for (int i = 0; i < len - 4; i += 4) {
                // Write the left channel sampling point
                bosLeft.write(audioData, i, 2);
                // Write the right track sampling point
                bosRight.write(audioData, i + 2.2);
            }
            fosLeft.write(bosLeft.toByteArray());
            fosRight.write(bosRight.toByteArray());
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(fosLeft);
            IOUtils.closeQuietly(fosRight);
        }
        return false;
    }
Copy the code

It can be seen from the above code that the left and right channel data is sorted sequentially

16 bit 16 bit 16 bit | | | | 16 bit |… | left track right sound channel | | left channel right sound channel | |…

Double PCM speed

public static boolean doubleSpeed(File pcmFile) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream(pcmFile);
            File output = new File(pcmFile.getParentFile(), pcmFile.getName() + ".speed");
            fos = new FileOutputStream(output);

            ByteArrayOutputStream bos = new ByteArrayOutputStream((int) pcmFile.length() / 2);
            byte[] buffer = new byte[1024 * 1024];
            int len = 0;
            while( (len = fis.read(buffer)) ! = -1) {
                for (int i = 0; i < len - 4; i += 4) {
                    int index = i / 4;
                    // Select only the even digits of the time axis
                    if (index % 2= =0) {
                        bos.write(buffer, i, 4);
                    }
                }
            }
            fos.write(bos.toByteArray());
            fos.flush();
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(fis);
            IOUtils.closeQuietly(fos);
        }
        return false;
    }
Copy the code

Source address: Gitee:gitee.com/huaisu2020/… Github:github.com/xh2009cn/An…