Android Open Source - StreamingService P C M Feed






From Project

Back to project page StreamingService.

License

The source code is released under:

Apache License

If you think the Android project StreamingService listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
** File modified from AACDecoder version 0.8
** by Commando Coder Ltd under LGPL.//from w  w w .  j  a  va2  s. c  o m
*/

/*
** AACDecoder - Freeware Advanced Audio (AAC) Decoder for Android
** Copyright (C) 2011 Spolecne s.r.o., http://www.spoledge.com
**  
** This file is a part of AACDecoder.
**
** AACDecoder is free software; you can redistribute it and/or modify
** it under the terms of the GNU Lesser General Public License as published
** by the Free Software Foundation; either version 3 of the License,
** or (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Lesser General Public License for more details.
** 
** You should have received a copy of the GNU Lesser General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.spoledge.aacdecoder;

import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;

import android.util.Log;


/**
 * This is a PCM Feeder which uses arrays (short[]).
 *
 * <pre>
 *  // 44100 Hz, stereo, buffering of 1.5 seconds:
 *  PCMFeed pcmfeed = new PCMFeed( 44100, 2, PCMFeed.msToBytes( 1500 ));
 *
 *  // start the exectuin thread:
 *  new Thread( pcmfeed ).start();
 *
 *  while (...) {
 *      // obtain the PCM data:
 *      short[] samples = ...
 *
 *      // feed the audio buffer; on error break the loop:
 *      if (!pcmfeed.feed( samples, samples.length )) break;
 *  }
 * </pre>
 */
public class PCMFeed implements Runnable, AudioTrack.OnPlaybackPositionUpdateListener {

    /**
     * Constant value when reached a marker position.
     * @see markerReachedAction
     * @since 0.8
     */
    public static final int MARKER_REACHED_ACTION_IGNORE = -1;

    /**
     * Constant value for pausing track when reached a marker position.
     * @see markerReachedAction
     * @since 0.8
     */
    public static final int MARKER_REACHED_ACTION_PAUSE = 1;


    private static final String LOG = "PCMFeed";


    ////////////////////////////////////////////////////////////////////////////
    // Attributes
    ////////////////////////////////////////////////////////////////////////////

    protected int sampleRate;
    protected int channels;
    protected int bufferSizeInMs;
    protected int bufferSizeInBytes;


    /**
     * The callback - may be null.
     */
    protected PlayerCallback playerCallback;

    /**
     * The AudioTrack instance.
     */
    protected AudioTrack audioTrack;

    /**
     * True iff the AudioTrack is playing.
     */
    protected boolean isPlaying;

    /**
     * Flag for stopping immediatelly.
     */
    protected boolean stopped;

    /**
     * Stopped by End-Of-File.
     * @since 0.8
     */
    protected boolean stoppedByEOF;


    /**
     * The local variable in run() method set by method acquireSamples().
     */
    protected short[] lsamples;


    /**
     * The variable set by feed() method and consumed in run().
     */
    protected short[] samples;


    /**
     * The variable set by feed() method and consumed in run().
     */
    protected int samplesCount;


    /**
     * Total samples written to AudioTrack.
     */
    protected int writtenTotal = 0;


    /**
     * The action which to be executed when the marker position is reached.
     * Actually this is used for very short audio data workaround.
     * @since 0.8
     */
    protected int markerReachedAction = MARKER_REACHED_ACTION_IGNORE;

  private float leftVolume = 1;
  private float rightVolume = 1;


    ////////////////////////////////////////////////////////////////////////////
    // Constructors
    ////////////////////////////////////////////////////////////////////////////

    /**
     * Creates a new PCMFeed object.
     * @param sampleRate the sampling rate in Hz (e.g. 44100)
     * @param channels the number of channels - only allowed values are 1 (mono) and 2 (stereo).
     * @param bufferSizeInBytes the size of the audio buffer in bytes
     * @param playerCallback the callback - may be null
     */
    protected PCMFeed( int sampleRate, int channels, int bufferSizeInBytes, PlayerCallback playerCallback ) {
        this.sampleRate = sampleRate;
        this.channels = channels;
        this.bufferSizeInBytes = bufferSizeInBytes;
        this.bufferSizeInMs = bytesToMs( bufferSizeInBytes, sampleRate, channels );
        this.playerCallback = playerCallback;
    }


    ////////////////////////////////////////////////////////////////////////////
    // Public
    ////////////////////////////////////////////////////////////////////////////

    /**
     * Returns the sampling rate.
     */
    public final int getSampleRate() {
        return sampleRate;
    }


    /**
     * Returns the number of channels.
     */
    public final int getChannels() {
        return channels;
    }


    /**
     * Returns the buffer size in bytes.
     */
    public final int getBufferSizeInBytes() {
        return bufferSizeInBytes;
    }


    /**
     * Returns the buffer size in milliseconds.
     */
    public final int getBufferSizeInMs() {
        return bufferSizeInMs;
    }


    /**
     * This is called by main thread when a new data are available.
     *
     * @param samples the array containing the PCM data
     * @param n the length of the PCM data
     * @return true if ok, false if the execution thread is not responding
     */
    public synchronized boolean feed( short[] samples, int n ) {
        while (this.samples != null && !stopped) {
            try { wait(); } catch (InterruptedException e) {}
        }

        this.samples = samples;
        this.samplesCount = n;

        notify();

        return !stopped;
    }


    /**
     * Stops the PCM feeder immediatelly.
     * This method just asynchronously notifies the execution thread.
     * This can be called in any state.
     */
    public synchronized void stop() {
        stop( false );
    }


    /**
     * Stops the PCM feeder.
     * This method just asynchronously notifies the execution thread.
     * This can be called in any state.
     * @param eof if true then stops after no data are available (eof);
     *      otherwise stops immediatelly
     * @since 0.8
     */
    public synchronized void stop( boolean eof ) {
        if (eof) {
            stoppedByEOF = true;
        }
        else {
            stopped = true;
            try {
                if (isPlaying) audioTrack.pause();
            }
            catch (Exception e) {
                //e.printStackTrace();
            }
        }

        notify();
    }


    /**
     * Converts milliseconds to bytes of buffer.
     * @param ms the time in milliseconds
     * @return the size of the buffer in bytes
     */
    public static int msToBytes( int ms, int sampleRate, int channels ) {
        return (int)(((long) ms) * sampleRate * channels / 500);
    }


    /**
     * Converts milliseconds to samples of buffer.
     * @param ms the time in milliseconds
     * @return the size of the buffer in samples
     */
    public static int msToSamples( int ms, int sampleRate, int channels ) {
        return (int)(((long) ms) * sampleRate * channels / 1000);
    }


    /**
     * Converts bytes of buffer to milliseconds.
     * @param bytes the size of the buffer in bytes
     * @return the time in milliseconds
     */
    public static int bytesToMs( int bytes, int sampleRate, int channels ) {
        return (int)(500L * bytes / (sampleRate * channels));
    }


    /**
     * Converts samples of buffer to milliseconds.
     * @param samples the size of the buffer in samples (all channels)
     * @return the time in milliseconds
     */
    public static int samplesToMs( int samples, int sampleRate, int channels ) {
        return (int)(1000L * samples / (sampleRate * channels));
    }


    ////////////////////////////////////////////////////////////////////////////
    // OnPlaybackPositionUpdateListener
    ////////////////////////////////////////////////////////////////////////////

    /**
     * Called on the listener to notify it that the previously set marker
     * has been reached by the playback head.
     */
    public void onMarkerReached( AudioTrack track ) {
        Log.d( LOG, "onMarkerReached()" );

        if (markerReachedAction == MARKER_REACHED_ACTION_PAUSE) {
            track.pause();
        }
    }


    /**
     * Called on the listener to periodically notify it that the playback head
     * has reached a multiple of the notification period. 
     */
    public void onPeriodicNotification( AudioTrack track ) {
        if (playerCallback != null) {
            int buffered = 0;

            try {
                buffered = writtenTotal - track.getPlaybackHeadPosition()*channels;
            }
            catch (IllegalStateException e) {
                Log.e( LOG, "onPeriodicNotification(): illegal state=" + track.getPlayState());

                return;
            }

            int ms = samplesToMs( buffered, sampleRate, channels );

            playerCallback.playerPCMFeedBuffer( isPlaying, ms, bufferSizeInMs );
        }
    }


    ////////////////////////////////////////////////////////////////////////////
    // Runnable
    ////////////////////////////////////////////////////////////////////////////

    /**
     * The main execution loop which should be executed in its own thread.
     */
    public void run() {
        Log.d( LOG, "run(): sampleRate=" + sampleRate + ", channels=" + channels
            + ", bufferSizeInBytes=" + bufferSizeInBytes
            + " (" + bufferSizeInMs + " ms)");

        isPlaying = false;

        AudioTrack atrack = null;

        try {
            atrack = new AudioTrack(
                                AudioManager.STREAM_MUSIC,
                                sampleRate,
                                channels == 1 ?
                                    AudioFormat.CHANNEL_CONFIGURATION_MONO :
                                    AudioFormat.CHANNEL_CONFIGURATION_STEREO,
                                AudioFormat.ENCODING_PCM_16BIT,
                                bufferSizeInBytes,
                                AudioTrack.MODE_STREAM );
            atrack.setStereoVolume(leftVolume, rightVolume);

            atrack.setPlaybackPositionUpdateListener( this );
            atrack.setPositionNotificationPeriod( msToSamples( 200, sampleRate, channels ));

            this.audioTrack = atrack;

            if (playerCallback != null) playerCallback.playerAudioTrackCreated( atrack );
        }
        catch (Throwable t) {
            Log.e( LOG, "Cannot create AudioTrack: " + t );
            stop();
            if (playerCallback != null) playerCallback.playerException( t );
        }

        while (!stopped) {
            // fetch the samples into our "local" variable lsamples:
            int ln = acquireSamples();

            if (stopped || ln == 0) {
                releaseSamples();
                break;
            }

            // samples written to AudioTrack in this round:
            int writtenNow = 0;

            do {
                if (writtenNow != 0) {
                    Log.d( LOG, "too fast for playback, sleeping...");
                    try { Thread.sleep( 50 ); } catch (InterruptedException e) {}
                }

                int written = atrack.write( lsamples, writtenNow, ln );

                if (written < 0) {
                    Log.e( LOG, "error in playback feed: " + written );
                    stopped = true;
                    break;
                }

                writtenTotal += written;
                int buffered = writtenTotal - atrack.getPlaybackHeadPosition()*channels;

                // Log.d( LOG, "PCM fed by " + ln + " and written " + written + " samples - buffered " + buffered);

                if (!stopped && !isPlaying) {
                    if (buffered*2 >= bufferSizeInBytes) {
                        Log.d( LOG, "start of AudioTrack - buffered " + buffered + " samples");
                        atrack.play();
                        isPlaying = true;
                    }
                    else {
                        Log.d( LOG, "start buffer not filled enough - AudioTrack not started yet");
                    }
                }

                writtenNow += written;
                ln -= written;
            } while (ln > 0 && !stopped);

            releaseSamples();
        }

        // Play the rest of the file:
        if (!stopped && stoppedByEOF) waitForLastTone();

        // Stop playing:
        if (isPlaying) atrack.pause();
        atrack.flush();
        atrack.release();

        stopped = true;

        Log.d( LOG, "run() stopped." );
    }


    ////////////////////////////////////////////////////////////////////////////
    // Protected
    ////////////////////////////////////////////////////////////////////////////

    /**
     * Acquires samples into variable lsamples.
     * @return the actual size (in shorts) of the lsamples
     */
    protected synchronized int acquireSamples() {
        while (samplesCount == 0 && !(stopped || stoppedByEOF)) {
            try { wait(); } catch (InterruptedException e) {}
        }

        // copy to local vars
        lsamples = samples;
        int ln = samplesCount;

        // clear the instance vars
        samples = null;
        samplesCount = 0;

        notify();

        return ln;
    }


    /**
     * Releases the lsamples variable.
     * This method is called always after processing the acquired lsamples.
     */
    protected void releaseSamples() {
        // nothing to be done
    }


    /**
     * Waits for the last tone.
     */
    protected void waitForLastTone() {
        // very small files are not even started
        // we try to start them now, but Android is waiting
        // in STREAM mode for more data - so we write dummy data to the track
        // and set the notification marker:

        if (!isPlaying) {
            // we've found that 2 seconds is enough:
            int minSamples = msToSamples( 2000, sampleRate, channels );

            if (writtenTotal < minSamples) {
                Log.d( LOG, "Filling dummy data to audio buffer" );
                short[] dummy = new short[ minSamples - writtenTotal ];

                // once is enough - it means that buffer is probably full:
                audioTrack.write( dummy, 0, dummy.length );
                audioTrack.setNotificationMarkerPosition( writtenTotal / channels );
                markerReachedAction = MARKER_REACHED_ACTION_PAUSE;
            }

            Log.d( LOG, "start of AudioTrack" );
            audioTrack.play();
            isPlaying = true;
        }

        Log.i( LOG, "Waiting for the end of the music" );

        int lastBuffered = writtenTotal;
        int lastDiff = writtenTotal;
        int count = 5;

        do {
            // sleep a while - it is safer than waiting - avoiding deadlock:
            try { Thread.sleep( 100 ); } catch (InterruptedException e) {}

            // obtain the current buffer
            int buffered = 0;

            try {
                buffered = writtenTotal - audioTrack.getPlaybackHeadPosition()*channels;
            }
            catch (IllegalStateException e) {
                Log.e( LOG, "waitForLastTone(): illegal state=" + audioTrack.getPlayState());

                break;
            }

            lastDiff = lastBuffered - buffered;
            lastBuffered = buffered;

            if (lastDiff == 0) count--;
            else count = 5;

            //Log.d( LOG, "WAIT-EOF: " + count + ", " + lastDiff + ", " + lastBuffered );
        }
        while ( count > 0 && (lastBuffered > 0 || lastDiff > 0));
    }

    public void setVolume(float leftVolume,float rightVolume) {
      if(audioTrack != null) {
        audioTrack.setStereoVolume(leftVolume, rightVolume);
      }
      this.leftVolume = leftVolume;
      this.rightVolume = rightVolume;
    }
}




Java Source Code List

com.commandocoder.streaming.StreamingException.java
com.commandocoder.streaming.StreamingListener.java
com.commandocoder.streaming.StreamingService.java
com.spoledge.aacdecoder.AACPlayer.java
com.spoledge.aacdecoder.BufferReader.java
com.spoledge.aacdecoder.Decoder.java
com.spoledge.aacdecoder.FlashAACInputStream.java
com.spoledge.aacdecoder.FlashAACPlayer.java
com.spoledge.aacdecoder.IcyInputStream.java
com.spoledge.aacdecoder.IcyURLConnection.java
com.spoledge.aacdecoder.IcyURLStreamHandler.java
com.spoledge.aacdecoder.MP3Player.java
com.spoledge.aacdecoder.MultiPlayer.java
com.spoledge.aacdecoder.PCMFeed.java
com.spoledge.aacdecoder.PlayerCallback.java