Android PigMedia Studio Audio Processing Packaging Dependency Library Creation Process Record

Posted by Ralf Jones on Wed, 14 Aug 2019 05:17:42 +0200

Developing PigMedia Studio Dependency Library Process Records (II)

In recent work, I have contacted a lot about the use of MediaPlayer, SoundPool, TextToSpeach, hoping to encapsulate a library with both MediaPlayer and TextToSpeach functions. Starting with this blog, we record the process of creating this dependency library.
Why did you choose this name? Because I think pigs and pigs are cute.

Note: This is a learning process record. The code that has been written will change with the progress. Especially the naming of methods and variables will not be very concerned in the process of completing the function. When it is finished, it will be renamed in an appropriate way. If you need to refer to it directly, please check the final results of the current progress directly.
github address
https://github.com/fytuuu/PigMediaStudio

Links to articles:

Let's proceed with SoundPool encapsulation

First of all, SoundPool has some perceptual knowledge:

  1. SoundPool is lightweight and more suitable for short, intensive audio playback such as games.
  2. App can be added with hints, such as "Hello, Cool Dog" when the most typical cool dog music is turned on.
  3. SoundPool can import resources from apk or file resources (provided read and write rights are granted)
  4. SoundPool uses the MediaPlayer service to decode audio into an original 16-bit PCM stream, which allows applications to compress streams and reduce CPU load and latency caused by decompression when playing audio.
  5. SoundPool, as its name implies, manages multiple audio streams using sound pools. If the maximum number of streams exceeds (which is determined by the developer), SoundPool automatically stops the previously playing stream based on priority.
  6. SoundPool also supports setting parameters such as voice quality, volume, playback ratio, etc.
  7. SoundPool's load() method has at most 1 M space per resource, so it is not suitable for playing background music with equal length and high quality.
  8. SoundPool can not monitor the progress of play, only support (all) pause, (all) continue to play, play.

First of all, I have an understanding of the common methods of SoundPool:

1. Construction Method

Before API 21 (Android 5.0), you can use the following constructor

SoundPool(int maxStreams, int streamType, int srcQuality)
  • maxStreams
    Specifies how many sounds are supported, and the maximum number of streams allowed to exist simultaneously in the SoundPool object

  • streamType
    Specify the sound type and define the following stream types in Audio Manager
    STREAM_VOICE_CALL
    STREAM_SYSTEM
    STREAM_RING
    STREAM_MUSIC
    STREAM_ALARM

  • srcQuality
    Specified sound quality (Sampling Rate Conversion Quality), usually set directly to 0

But in API 21(Android 5.0) and above, this construction method was abandoned and replaced by the SoundPool.Builder() method.

SoundPool.Builder spb = new SoundPool.Builder();
spb.setMaxStreams(10);
spb.setAudioAttributes(null);    //Converting Audio Format
SoundPool sp = spb.build();      //Create SoundPool objects

Therefore, the version of the system should be judged in the process of using, so as to select the appropriate method.

// Version greater than or equal to 21 (Anndroid 5.0)
SoundPool sp = null;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {  
    SoundPool.Builder spb = new SoundPool.Builder();
    spb.setMaxStreams(10);
    spb.setAudioAttributes(null);    //Converting Audio Format
    sp = spb.build();      //Create SoundPool objects 
} else {
    sp = SoundPool(10, AudioManager.STREAM_SYSTEM, 5);
}

2.load() method - load audio resources / local audio files

load() overload method, each method returns an ID of the sound (int type, starting from 1)

load(Context context, int resId, int priority)
load(String path, int priority)
load(FileDescriptor fd, long offset, long length, int priority)
load(AssetFileDescriptor afd, int priority)
  • context
  • resId resource id
  • priority is a useless parameter. It is recommended to set it to 1 to maintain compatibility and future compatibility.
  • path
  • FileDescriptor File Descriptor
  • AssetFileDescriptor Reads a resource file from the asset directory.
  • AssetFileDescriptor descriptor = assetManager.openFd("kugou.mp3")

Note the official hint for the load method

/**
     * Load the sound from the specified APK resource.
     *
     * Note that the extension is dropped. For example, if you want to load
     * a sound from the raw resource file "explosion.mp3", you would specify
     * "R.raw.explosion" as the resource ID. Note that this means you cannot
     * have both an "explosion.wav" and an "explosion.mp3" in the res/raw
     * directory.
     *
     * @param context the application context
     * @param resId the resource ID
     * @param priority the priority of the sound. Currently has no effect. Use
     *                 a value of 1 for future compatibility.
     * @return a sound ID. This value can be used to play or unload the sound.
     */
     public int load(Context context, int resId, int priority) {...}

3.play() -- Play audio, set properties

play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)
  • Sound ID number returned by soundID load() (starting from 1)
  • Left Volume Volume Volume Settings
  • Right Volume Right Voice Volume Settings
  • Priority specifies the priority of playing sound. The higher the value, the higher the priority.
  • Loop specifies whether to loop
    - 1 represents an infinite loop
    0 means no loop
    Other values indicate the number of times you want to play again
  • Rate specifies the playback rate, which ranges from 0.5 to 2.0
    1.0 playback rate allows sound to follow its original frequency
    2.0 playback rate allows sound to be played at twice its original frequency
    At 0.5 playback rate, the playback rate is half of the original frequency.

4.release() - for releasing resources

release(int soundID)
release() Method is used to release all SoundPool Object-occupied memory and resources

The core is the load method, which must be loaded before play(), and the completion of the load will return an ID (int type, starting from 1), which corresponds to the resource file. When play(), we need to take the ID of the resource file as a parameter.
So when we complete the load, it will be more convenient to use some sign of resource / file, such as file name or file content as key, ID returned by load as value, and then take value out to play() through key.

So let's start docking.

1. Create the PigSoundPlayer class to control SoundPool

First, we are ready to use HashMap for storing resources and files (without considering the uniqueness of key for the time being). We initialize PigSoundPlayer and set the properties of SoundPool. If not, the default values are maxStream 1, quality 5, and attribute null.

public class PigSoundPlayer {
    private final static String TAG = "PigSoundPlayer";
    //Used to store resource files
    private static HashMap<String, Integer> resourceMap = new HashMap<>();
    //Sound pool
    private static SoundPool soundPool;
    //Single case
    private static PigSoundPlayer pigSoundPlayer;
    //Context Manager
    private static ContextManager contextManager;
    //Constructor Private
    private PigSoundPlayer(){}
    //Initialization
    public static void initSoundPlayer(int maxStreams, AudioAttributes attributes, int quality){
		
        if (pigSoundPlayer == null) pigSoundPlayer = new PigSoundPlayer();
		//Controlling the error value of maxStream
        if (maxStreams<=0) maxStreams = 1;
		//Initialization of SoundPool according to different versions
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
            SoundPool.Builder spb  = new SoundPool.Builder();
            spb.setMaxStreams(maxStreams);
            if (attributes!=null )spb.setAudioAttributes(attributes);
            soundPool = spb.build();
        }else{
            soundPool = new SoundPool(maxStreams, AudioManager.STREAM_SYSTEM,quality);
        }
    }

    public static void initSoundPlayer(int maxStreams, int quality){
        initSoundPlayer(maxStreams,null,quality);
    }

    public static void initSoundPlayer(int maxStreams, AudioAttributes attributes){
        initSoundPlayer(maxStreams,attributes,5);
    }

    public static void initSoundPlayer(int maxStreams){
       initSoundPlayer(maxStreams,null);
    }

    public static void initSoundPlayer(){
        initSoundPlayer(1);
    }

2. We add an internal class Loader for load operation.

The first parameter tag in each method of Loader is the first parameter of resourceMap. It is convenient to find the corresponding resource id from the map by string flag, and of course, it can also get the id directly by return value.

public static class Loader{
        private Loader(){}

        public int load(String tag,int resId,int priority){
            int id = -1;
            if (pigSoundPlayer != null){
                id = soundPool.load(contextManager.getApplicationContext(),
                        resId,
                        priority);
                Log.d(TAG,"loadMusic result"+id);
                resourceMap.put(tag,id);
            }
            return id;
        }

        public int load(String tag,String path,int priority){
            int id = -1;
            if (pigSoundPlayer != null){
                id = soundPool.load(path,priority);
                Log.d(TAG,"loadMusic result"+id);
                resourceMap.put(tag,id);
            }
            return id;
        }

        public int load(String tag,FileDescriptor fd,long offset,long length,int priority){
            int id = -1;
            if (pigSoundPlayer != null){
                id = soundPool.load(fd,offset,length,priority);
                resourceMap.put(tag,id);
            }
            Log.d(TAG,"loadMusic result"+id);
            return id;
        }

        public int load(String tag, AssetFileDescriptor afd,int priority){
            int id = -1;
            if (pigSoundPlayer != null){
                id = soundPool.load(afd,priority);
                resourceMap.put(tag,id);
            }
            Log.d(TAG,"loadMusic result"+id);
            return id;
        }

    }

3. Next we add Loader's method and playback method.

The play() method usually has a sound size of 1.0f in both left and right channels, so a simple method is given, which only needs to give ID and loop or only id, loop and rate.

public class PigSoundPlayer{
	//The preceding code is omitted
	//Pre-loader
    private static Loader loader;

	//Get the corresponding handler
    public static Loader getLoader(Context context){
        if(contextManager == null){
            contextManager = new ContextManager(context);
        }
        if (loader == null){
            loader = new Loader();
        }
        return loader;
    }

     public static int play(int soundId,int loop){
        return play(soundId,1.0f,1.0f,1,loop,1.0f);
    }
    
    public static int play(int soundId,int loop,float rate){
        return play(soundId,1.0f,1.0f,1,loop,rate);
    }

    public static int play(int soundId,float leftVolume,float rightVolume,int priority,int loop,float rate){
        if (pigSoundPlayer == null){
            return 0;
        }
        return soundPool.play(soundId, leftVolume, rightVolume, priority, loop, rate);
    }
    
    public static int play(String tag,int loop){
        return play(tag,1.0f,1.0f,1,loop,1.0f);
    }

    public static int play(String tag,int loop,float rate){
        return play(tag,1.0f,1.0f,1,loop,rate);
    }

    public static int play(String tag,float leftVolumn,float rightVolume,int priority,int loop,float rate){
        if (pigSoundPlayer == null){
            return 0;
        }
        int resid = 0;
        if(resourceMap.get(tag)!=null){
            resid = resourceMap.get(tag);
            return soundPool.play(resid,leftVolumn,rightVolume,priority,loop,rate);
        }else{
            return 0;
        }
    }

    public static class Loader{
        private Loader(){}

        public int load(String tag,int resId,int priority){
            int id = -1;
            if (pigSoundPlayer != null){
                id = soundPool.load(contextManager.getApplicationContext(),
                        resId,
                        priority);
                Log.d(TAG,"loadMusic result"+id);
                resourceMap.put(tag,id);
            }
            return id;
        }

        public int load(String tag,String path,int priority){
            int id = -1;
            if (pigSoundPlayer != null){
                id = soundPool.load(path,priority);
                Log.d(TAG,"loadMusic result"+id);
                resourceMap.put(tag,id);
            }
            return id;
        }

        public int load(String tag,FileDescriptor fd,long offset,long length,int priority){
            int id = -1;
            if (pigSoundPlayer != null){
                id = soundPool.load(fd,offset,length,priority);
                resourceMap.put(tag,id);
            }
            Log.d(TAG,"loadMusic result"+id);
            return id;
        }

        public int load(String tag, AssetFileDescriptor afd,int priority){
            int id = -1;
            if (pigSoundPlayer != null){
                id = soundPool.load(afd,priority);
                resourceMap.put(tag,id);
            }
            Log.d(TAG,"loadMusic result"+id);
            return id;
        }

    }
}

The ContextManager is relatively simple, so paste the code directly here.

public class ContextManager {
    private Context context;
    private PigSoundPlayer pigSoundPlayer;

    public ContextManager(Context context){
        this.context = context.getApplicationContext();
    }

    public Context getApplicationContext(){
        return context;
    }

    public void setContext(Context context){
        this.context  = context;
    }
}

So far, our PigSoundPlayer is ready to use
Initialize the Application by calling the PigSoundPlayer.initSoundPlayer() method in onCreate, or before playback or storage.
Later, if you need to load resources, call PigSoundPlayer. getLoader (getApplication Context (). load (file. getAbsolutePath, 1)
If you need to play, call
int stremId = PigSoundPlayer.play(id,0);

4. Let's reconsider the suspension and continuation of play.

Let's look at the pause and continuation methods in SoundPool
First, the pause method is used to pause the playback stream corresponding to the streamID, where the streamID is the return value of the play() method, and the stream ID returned by each play() method corresponds to the stream that currently plays audio.

 /**
     * Pause a playback stream.
     *
     * Pause the stream specified by the streamID. This is the
     * value returned by the play() function. If the stream is
     * playing, it will be paused. If the stream is not playing
     * (e.g. is stopped or was previously paused), calling this
     * function will have no effect.
     *
     * @param streamID a streamID returned by the play() function
     */
    public native final void pause(int streamID);

If the stream of the streamID has been suspended before, it will continue to play. If it has not been suspended, then calling this method will not work.

	 /**
     * Resume a playback stream.
     *
     * Resume the stream specified by the streamID. This
     * is the value returned by the play() function. If the stream
     * is paused, this will resume playback. If the stream was not
     * previously paused, calling this function will have no effect.
     *
     * @param streamID a streamID returned by the play() function
     */
    public native final void resume(int streamID);

All pauses, auto automatically means flag all automatically paused streams. When autoResume() is called, all streams that are autoPause() will continue to play.

	/**
     * Pause all active streams.
     *
     * Pause all streams that are currently playing. This function
     * iterates through all the active streams and pauses any that
     * are playing. It also sets a flag so that any streams that
     * are playing can be resumed by calling autoResume().
     */
    public native final void autoPause();

I haven't tried it here. I guess I'll just continue playing stream s that are suspended by autoPause().

	 /**
     * Resume all previously active streams.
     *
     * Automatically resumes all streams that were paused in previous
     * calls to autoPause().
     */
    public native final void autoResume();

Stop, close resources

	/**
     * Stop a playback stream.
     *
     * Stop the stream specified by the streamID. This
     * is the value returned by the play() function. If the stream
     * is playing, it will be stopped. It also releases any native
     * resources associated with this stream. If the stream is not
     * playing, it will have no effect.
     *
     * @param streamID a streamID returned by the play() function
     */
    public native final void stop(int streamID);

Of course, there are also setVolume setPriority setLoop setRate and other methods, not all posted.
Note here that rate ranges from 0f to 2.0f
We encapsulate these methods in our PigSoundPlayer

	//...
	
    //suspend
    public static void pause(int streamID){
        soundPool.pause(streamID);
    }
    //Continue playing
    public static void resume(int streamId){
        soundPool.resume(streamId);
    }
    //All suspension of broadcasting
    public static void autoPause(){
        soundPool.autoPause();
    }
    //All continue to play
    public static void autoResume(){
        soundPool.autoResume();
    }
    //Stop it
    public static void stop(int streamID){
        soundPool.stop(streamID);
    }
    //set volume
    public static void setVolume(int streamID, int leftVolume, int rightVolume){
        soundPool.setVolume(streamID,leftVolume,rightVolume);
    }
    //Setting Priority
    public static void setPriority(int streamID, int priority){
        soundPool.setPriority(streamID,priority);
    }
    //Setting Rate
    public static void setRate(int streamID, float rate){
        soundPool.setRate(streamID,rate);
    }
    //Setting the loop
    public static void setLoop(int streamID,int loop){
        soundPool.setLoop(streamID,loop);
    }
    
	//...

Topics: Android MediaPlayer github Attribute