Audio and Video (I) AudioTrack plays PCM bare data

PCM introduction: PCM is the Abbreviation of English Pulse-code Modulation, Chinese translation is Pulse code modulation. We know in real life, the human ear heard voices are analog signals, PCM is to convert voice from analog to digital signal is a kind of technology, the principle of it simply is to use a fixed frequency of analog signal sampling, the sampling signal on the waveform look like after a sequence of pulse amplitude is differ, The amplitude of these pulses is quantized with a certain precision, and the quantized values are continuously output, transmitted, processed or recorded in the storage medium, all of which constitute the production process of digital audio.

1. Start by preparing a PCM audio clip

A piece of PCM data can be downloaded from the link

2. The AudioTrack instance

AudioTrack provides a service available for audio playback (PCM data)

Sample code:

AudioTrack audioTrack = new AudioTrack.Builder()
        .setAudioAttributes(new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build())
        .setAudioFormat(new AudioFormat.Builder().setSampleRate(8000).setChannelMask(AudioFormat.CHANNEL_IN_STEREO).setEncoding(AudioFormat.ENCODING_PCM_16BIT).build())
        .setTransferMode(AudioTrack.MODE_STREAM)
        .setBufferSizeInBytes(bufferSize)
        .build();
Copy the code

Parameter Description:

  • AudioAttribtes->Usage: Sets properties that describe the intended use of audio signals, such as alarm clocks or ringtones (USAGE_MEDIAMusic or film);

  • AudioAttribtes->ContentType: Sets properties that describe the content type of an audio signal, such as voice or music. (CONTENT_TYPE_MUSICGenre of music);

  • AudioFormat->SampleRate: Audio sampling rate, 16K at 16000Hz, etc

  • AudioFormat->ChannelMask: Channel Settings, for exampleAudioFormat.CHANNEL_IN_STEREODual channel (note: when using dual channel, the sampling rate should be /2, so that the audio may be played at double speed, not normal playback speed)

  • AudioFormat->Encoding: Set the data encoding format,ENCODING_PCM_16BIT(Compatible with all mobile phones, basically use this 16-bit),ENCODING_PCM_8BIT(8)

  • AudioTrack->BufferSizeInBytes: Set the size of the cache subsection, preferably by calling a static methodAudioTrack. GetMinBufferSize (sample rate, channels, digits)Get buffersize
  • AudioTrack->TransferMode: Sets the buffer data transfer mode.
  • It is worth noting that there are two modes:
    • AudioTrack.MODE_STREAMThis mode is set after AudioTrackwrite()Method must be called firstplay()Methods.Read while playing, data will not be directly loaded into memory

    • AudioTrack.MODE_STATICAfter setting, you can startwriteFinally, inplay()Play.The audio data to be played is read into the memory in advance, and then the playback begins

Write: The write() method reads file stream Byte into AudioTrack and passes it to the native layer for processing

3. Play the code

Play PCM in audiotrack. MODE_STREAM mode

// PCM file is 16K sample rate I set dual channel 16000/2 = 8000
int bufferSize = AudioTrack.getMinBufferSize(8000, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);
new Thread(new Runnable() {
    @Override
    public void run(a) {
        try {
            // Get the file input stream which I store in assets
            InputStream dis = getAssets().open("16k16bit.pcm");
            audioTrack.play();
            int length = 0;
            byte a[] = new byte[bufferSize];
            while((length = dis.read(a)) ! = -1) {
                //read puts the read data into the A buffer and writes the A buffer data to the Audiotrack
                audioTrack.write(a, 0, length); }}catch (Exception e) {
            e.printStackTrace();
        } finally {
            audioTrack.stop();
            audioTrack.release();
        }
    }
}).start();
Copy the code

How do you get the time, though? Like current progress and total playback time?

4. Calculate the playback progress and total duration

The AudioTrack class doesn’t have any time methods, but there are several functions that can roughly calculate the current playback progress and total audio duration.

Playback progress:

// Returns the position of the playback header in frames
int play = audioTrack.getPlaybackHeadPosition();
// Returns the current sampling rate
int rate = audioTrack.getPlaybackRate();
float currentPlayTime = playFarme * 1.0 f / rate * 1.0 f;
Copy the code

⚠️ The length of the sound file in the frame. (Calculate this by dividing the “Frame Count” by the “Sample Rate.”) That is, playback progress = current frame position/current sampling rate

PCM audio duration

pcmTimeCount = (int) ((fileSize * 1.0 f)/(16.0 f / 8.0 f)/(2.0 f) / 8000);
Copy the code

Formula for calculating the file size: File size Byte= Sampling rate x (sampling bits /8) x Number of channels x Total time Current known quantity: file size, set sampling rate, sampling bits 16 bits, and Number of channels (that is, two channels). You can directly calculate the total time

Monitor real-time progress

SetPlaybackPositionUpdateListener has two callback function onMarkerReached and onPeriodicNotification correspond respectively The listener to raise to inform previously set tag and it has arrived The call listener notifies it periodically of the arrival of the playback header

OnPeriodicNotification and onMarkerReached are called once per xxMS
audioTrack.setPositionNotificationPeriod(1000);

audioTrack.setPlaybackPositionUpdateListener(new AudioTrack.OnPlaybackPositionUpdateListener() {
    @Override
    public void onMarkerReached(AudioTrack track) {}@Override
    public void onPeriodicNotification(AudioTrack track) {
        int playFarme = audioTrack.getPlaybackHeadPosition();
        int rate = audioTrack.getPlaybackRate();
        float currentPlayTime = playFarme * 1.0 f / rate * 1.0 f;
    }
}, mMainHandler);
Copy the code

Overall MainActivity code:

private ActivityMainBinding binding;
private Handler mMainHandler;
private ProgressBar progressbar;
private int pcmTimeCount;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    binding = ActivityMainBinding.inflate(getLayoutInflater());
    setContentView(binding.getRoot());
    progressbar = binding.progressbar;
    mMainHandler = new Handler(Looper.myLooper());
    AudioTrack audioTrack;
    int bufferSize = AudioTrack.getMinBufferSize(8000, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        audioTrack = new AudioTrack.Builder()
                .setAudioAttributes(new AudioAttributes.Builder()
                        .setUsage(AudioAttributes.USAGE_MEDIA)
                        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                        .setLegacyStreamType(AudioManager.STREAM_MUSIC)
                        .build())
                .setAudioFormat(new AudioFormat.Builder().setSampleRate(8000).setChannelMask(AudioFormat.CHANNEL_IN_STEREO).setEncoding(AudioFormat.ENCODING_PCM_16BIT).build())
                .setTransferMode(AudioTrack.MODE_STREAM)
                .setBufferSizeInBytes(bufferSize)
                .build();
    } else {
        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 8000, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT,
                bufferSize, AudioTrack.MODE_STREAM);
    }
    audioTrack.setPlaybackPositionUpdateListener(new AudioTrack.OnPlaybackPositionUpdateListener() {
        @Override
        public void onMarkerReached(AudioTrack track) {}@Override
        public void onPeriodicNotification(AudioTrack track) {
            int playFarme = audioTrack.getPlaybackHeadPosition();
            int rate = audioTrack.getPlaybackRate();
            float currentPlayTime = playFarme * 1.0 f / rate * 1.0 f;
            progressbar.setProgress((int) MathUtils.clamp(Math.ceil(((currentPlayTime / pcmTimeCount) * 100)),0.100));
        }
    }, mMainHandler);
    audioTrack.setPositionNotificationPeriod(1000);
    if(audioTrack.getState() ! = AudioTrack.STATE_UNINITIALIZED && audioTrack.getPlayState() ! = PLAYSTATE_PLAYING) {new Thread(new Runnable() {
            @Override
            public void run(a) {
                try {
                    InputStream dis = getAssets().open("16k16bit.pcm");
                    audioTrack.play();
                    int length = 0;
                    byte a[] = new byte[bufferSize];
                    if (dis.available() > 0) {
                        int fileSize = dis.available();
                        /* according to the calculation formula: data quantity Byte= 44100Hz x (16/8) x 2 x 10s=1764KByte and then converted to the corresponding unit */

                        mMainHandler.post(new Runnable() {
                            @Override
                            public void run(a) {
                                if (fileSize > 0) {
                                    pcmTimeCount = (int) ((fileSize * 1.0 f)/(16.0 f / 8.0 f)/(2.0 f) / 8000); }}}); }while((length = dis.read(a)) ! = -1) {
                        audioTrack.write(a, 0, length);
                    }
                    mMainHandler.post(new Runnable() {
                        @Override
                        public void run(a) {
                            Toast.makeText(MainActivity.this."End of playback", Toast.LENGTH_SHORT).show(); }}); }catch (Exception e) {
                    e.printStackTrace();
                } finally{ audioTrack.stop(); audioTrack.release(); } } }).start(); }}Copy the code

If there is something wrong, please correct it and change it in time.