在 Android 中,TTS全称叫做 Text to Speech,从字面就能理解它解决的问题是什么,把文本转为语音服务,意思就是你输入一段文本信息,然后Android 系统可以把这段文字播报出来。这种应用场景目前比较多是在各种语音助手APP上,很多手机系统集成商内部都有内置文本转语音服务,可以读当前页面上的文本信息。同样,在一些阅读类APP上我们也能看到相关服务,打开微信读书,里面就直接可以把当前页面直接用语音方式播放出来,特别适合哪种不方便拿着手机屏幕阅读的场景。
privateintinitTts() { // Step 1: Try connecting to the engine that was requested. if (mRequestedEngine != null) { if (mEnginesHelper.isEngineInstalled(mRequestedEngine)) { if (connectToEngine(mRequestedEngine)) { mCurrentEngine = mRequestedEngine; return SUCCESS; } elseif (!mUseFallback) { mCurrentEngine = null; dispatchOnInit(ERROR); return ERROR; } } elseif (!mUseFallback) { Log.i(TAG, "Requested engine not installed: " + mRequestedEngine); mCurrentEngine = null; dispatchOnInit(ERROR); return ERROR; } }
// Step 2: Try connecting to the user's default engine. finalStringdefaultEngine= getDefaultEngine(); if (defaultEngine != null && !defaultEngine.equals(mRequestedEngine)) { if (connectToEngine(defaultEngine)) { mCurrentEngine = defaultEngine; return SUCCESS; } }
// Step 3: Try connecting to the highest ranked engine in the // system. finalStringhighestRanked= mEnginesHelper.getHighestRankedEngineName(); if (highestRanked != null && !highestRanked.equals(mRequestedEngine) && !highestRanked.equals(defaultEngine)) { if (connectToEngine(highestRanked)) { mCurrentEngine = highestRanked; return SUCCESS; } }
// NOTE: The API currently does not allow the caller to query whether // they are actually connected to any engine. This might fail for various // reasons like if the user disables all her TTS engines.
// Load the engineConfig from the plugin if it has any special configuration // to be loaded. By convention, if an engine wants the TTS framework to pass // in any configuration, it must put it into its content provider which has the URI: // content://<packageName>.providers.SettingsProvider // That content provider must provide a Cursor which returns the String that // is to be passed back to the native .so file for the plugin when getString(0) is // called on it. // Note that the TTS framework does not care what this String data is: it is something // that comes from the engine plugin and is consumed only by the engine plugin itself. StringengineConfig=""; Cursorc= getContentResolver().query(Uri.parse("content://" + getPackageName() + ".providers.SettingsProvider"), null, null, null, null); if (c != null){ c.moveToFirst(); engineConfig = c.getString(0); c.close(); } mNativeSynth = newSynthProxy(soFilename, engineConfig);
// mNativeSynth is used by TextToSpeechService#onCreate so it must be set prior // to that call. // getContentResolver() is also moved prior to super.onCreate(), and it works // because the super method don't sets a field or value that affects getContentResolver(); // (including the content resolver itself). super.onCreate(); }
/** init * Allocates Pico memory block and initializes the Pico system. * synthDoneCBPtr - Pointer to callback function which will receive generated samples * config - the engine configuration parameters, here only contains the non-system path * for the lingware location * return tts_result */ tts_result TtsEngine::init( synthDoneCB_t synthDoneCBPtr, constchar *config ) { if (synthDoneCBPtr == NULL) { ALOGE("Callback pointer is NULL"); return TTS_FAILURE; }
picoMemArea = malloc( PICO_MEM_SIZE ); if (!picoMemArea) { ALOGE("Failed to allocate memory for Pico system"); return TTS_FAILURE; }
pico_Status ret = pico_initialize( picoMemArea, PICO_MEM_SIZE, &picoSystem ); if (PICO_OK != ret) { ALOGE("Failed to initialize Pico system"); free( picoMemArea ); picoMemArea = NULL; return TTS_FAILURE; }
picoSynthDoneCBPtr = synthDoneCBPtr;
picoCurrentLangIndex = -1;
// was the initialization given an alternative path for the lingware location? if ((config != NULL) && (strlen(config) > 0)) { pico_alt_lingware_path = (char*)malloc(strlen(config)); strcpy((char*)pico_alt_lingware_path, config); ALOGV("Alternative lingware path %s", pico_alt_lingware_path); } else { pico_alt_lingware_path = (char*)malloc(strlen(PICO_LINGWARE_PATH) + 1); strcpy((char*)pico_alt_lingware_path, PICO_LINGWARE_PATH); ALOGV("Using predefined lingware path %s", pico_alt_lingware_path); }
/** * Adds a speech item to the queue. * * Called on a service binder thread. */ publicintenqueueSpeechItem(int queueMode, final SpeechItem speechItem) { UtteranceProgressDispatcherutterenceProgress=null; if (speechItem instanceof UtteranceProgressDispatcher) { utterenceProgress = (UtteranceProgressDispatcher) speechItem; }
if (!speechItem.isValid()) { if (utterenceProgress != null) { utterenceProgress.dispatchOnError( TextToSpeech.ERROR_INVALID_REQUEST); } return TextToSpeech.ERROR; }
// The obj is used to remove all callbacks from the given app in // stopForApp(String). // // Note that this string is interned, so the == comparison works. msg.obj = speechItem.getCallerIdentity();
if (sendMessage(msg)) { return TextToSpeech.SUCCESS; } else { Log.w(TAG, "SynthThread has quit"); if (utterenceProgress != null) { utterenceProgress.dispatchOnError(TextToSpeech.ERROR_SERVICE); } return TextToSpeech.ERROR; } }
主要是看 speechItem.play()方法,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/** * Plays the speech item. Blocks until playback is finished. * Must not be called more than once. * * Only called on the synthesis thread. */ publicvoidplay() { synchronized (this) { if (mStarted) { thrownewIllegalStateException("play() called twice"); } mStarted = true; } playImpl(); }
@Override protectedvoidplayImpl() { AbstractSynthesisCallback synthesisCallback; mEventLogger.onRequestProcessingStart(); synchronized (this) { // stop() might have been called before we enter this // synchronized block. if (isStopped()) { return; } mSynthesisCallback = createSynthesisCallback(); synthesisCallback = mSynthesisCallback; }
// Fix for case where client called .start() & .error(), but did not called .done() if (synthesisCallback.hasStarted() && !synthesisCallback.hasFinished()) { synthesisCallback.done(); } }