Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TextToSpeech, playEarcon and .wav files

In one of my applications I have an activity which speech synthesises alphanumeric reference strings, letter/number by letter/number e.g "ABC123" sounds as "Ay, bee, sea, one two three". As this is a limited set of sounds I thought it would be good to enable the TTS engine to work without an internet connection by playing prerecorded .wav files of the numbers and letters using the playEarcon method.

I have placed all 36 wav files in the res/raw folder and mapped the resource ids to the letters when initialising the TTS engine. This works well, however the .apk is now much larger as the wav files are stored uncompressed in the apk. I would like to make the apk's size smaller.

In the answer to another question it states that wav files are excluded from compression. ( I don't see why, as they typically zip down to about 40% of the original) One inspecting the apk's internals, this appears to be true.

As the extension of the resource files is not referred to in the code, I tried renaming the wavs, to variously .waw, .abc, .spc. All of these get compressed but unfortunately the playEarcon method produces no sound when invoked unless the extension is .wav.

In short I would like to coerce the TTS engine into playing files without a wav extension, or persuade it to compress the .wav files.

All suggestions will be gratefully received. For what it's worth I'm posting the smallest demonstrable code sample below. My working files are named gb_a.wav, gb_b.wav etc. If the extension is changed, they stop sounding.

public class WavSpeakerActivity extends Activity implements
        RadioGroup.OnCheckedChangeListener, TextToSpeech.OnInitListener {

    static final int mGBLetterResIds[] = { R.raw.gb_a, R.raw.gb_b, R.raw.gb_c,
            R.raw.gb_d, R.raw.gb_e, R.raw.gb_f, R.raw.gb_g, R.raw.gb_h,
            R.raw.gb_i, R.raw.gb_j, R.raw.gb_k, R.raw.gb_l, R.raw.gb_m,
            R.raw.gb_n, R.raw.gb_o, R.raw.gb_p, R.raw.gb_q, R.raw.gb_r,
            R.raw.gb_s, R.raw.gb_t, R.raw.gb_u, R.raw.gb_v, R.raw.gb_w,
            R.raw.gb_x, R.raw.gb_y, R.raw.gb_z };
    static final int mGBNumberResIds[] = { R.raw.gb_zero, R.raw.gb_one,
            R.raw.gb_two, R.raw.gb_three, R.raw.gb_four, R.raw.gb_five,
            R.raw.gb_six, R.raw.gb_seven, R.raw.gb_eight, R.raw.gb_nine };

    static final String mGbStr = "GB";
    static final String mAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    static final String mNumbers = "0123456789";
    private String mPpackageName = null;
    private String mTextToSpeak = null;
    private RadioGroup mRadioGroup = null;// two buttons one sets letters, the other numbers
    private TextToSpeech mTts = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mTts = new TextToSpeech(this, this);
        mRadioGroup = (RadioGroup) findViewById(R.id.radioGroup1);
        mRadioGroup.setOnCheckedChangeListener(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        RadioGroup rg = (RadioGroup) findViewById(R.id.radioGroup1);
        switchText(rg);
        mPpackageName = getPackageName();
    }

    @Override
    public void onDestroy() {
        // Don't forget to shutdown speech engine
        if (mTts != null) {
            mTts.stop();
            mTts.shutdown();
        }
        super.onDestroy();
    }

    private void switchText(RadioGroup rg) {
        // select letters or digits as the String to speak
        int checkedButton = rg.getCheckedRadioButtonId();
        switch (checkedButton) {
            case R.id.alphabet:
                mTextToSpeak = mAlphabet;
                break;
            case R.id.numbers:
                mTextToSpeak = mNumbers;
                break;
        }
    }

    public void myClickHandler(View target) {
        // Just the one button has been clicked - the 'Speak' one
        String earconKey;
        String lang = Locale.UK.getCountry(); // will be "GB", just have UK in this small example
        mTts.setLanguage(Locale.UK); // skip error checking for brevity's sake
        String text = mTextToSpeak.replaceAll("\\s", "");// remove spaces (if any)
        char c;
        for (int i = 0; i < text.length(); i++) {
            c = text.charAt(i);
            if ( Character.isLetter(c) || Character.isDigit(c) ) {
                earconKey = lang + Character.toString(c); // GBA, GBB..GBZ, GB0.. GB9
                mTts.playEarcon(earconKey, TextToSpeech.QUEUE_ADD, null);
            }
        }
    }

    @Override
    public void onInit(int status) {
        // doesn't seem we need to check status or setLanguage if we're just playing earcons
        mapEarCons(); // map letter/digit sounds to resource ids
    }

    private void mapEarCons() {
        String key;
        for (char c = 'A'; c <= 'Z' ; c++){
            key = mGbStr + Character.toString(c); // GBA, GBB .. GBZ
            mTts.addEarcon(key, mPpackageName, mGBLetterResIds[c - 'A'] );// add it
        }
        for (int i = 0 ; i <= 9; i++){
            key = mGbStr + Integer.toString(i); // GB0, GB1 .. GB9
            mTts.addEarcon(key, mPpackageName, mGBNumberResIds[i] );
        }
    }

    @Override
    public void onCheckedChanged(RadioGroup rg, int arg1) { switchText(rg); }
}

. .

like image 974
NickT Avatar asked Feb 15 '12 14:02

NickT


1 Answers

  1. Why wav files aren't tried to be compressed: according wikipedia wav file is a container for data. Most frequently it's used for uncompressed PCM sound, but can also be used to store data compressed with various codecs ( you can find possible values for wFormatTags ( type for stored data ) for example here ).
  2. You can save your resource to the local file system and use addEarcon(String earcon, String filename) instead of addEarcon(String earcon, String packagename, int resourceId).
  3. You can use aapt with -0 wav cmd line switch to make apk with wav files excluded from compressed types.
like image 95
Andrey Starodubtsev Avatar answered Oct 31 '22 06:10

Andrey Starodubtsev