文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android SDK 之TTS源码及流程分析

2022-06-06 13:52

关注

TTS全称Text  To Speech ,是文本转语音服务,本文将从TTS的简单demo使用,对TTS进行源码分析 涉及的Android SDK 核心源码路径如下:
android.speech.tts.TextToSpeech
android.speech.tts.TextToSpeechService
android.speech.tts.SynthesisCallback  //自定义引擎回到的接口类
demo如下
  private TextToSpeech textToSpeech;
    private void testTTS() {
        textToSpeech = new TextToSpeech(this, new TextToSpeech.OnInitListener() {
            @Override
            public void onInit(int status) {
                if (status == TextToSpeech.SUCCESS) {
                    // setLanguage设置语言
                    int result = textToSpeech.setLanguage(Locale.CHINA);
                    textToSpeech.speak("寓言故事,老鼠和猫的故事,从前有一只老鼠,有一只猫",
                            TextToSpeech.QUEUE_FLUSH, null, "abc");
                    List engines = textToSpeech.getEngines();
                    Log.e(TAG, engines.size() + "/" + engines.get(0).toString());
                }
            }
        });
        textToSpeech.setOnUtteranceProgressListener(new UtteranceProgressListener() {
            @Override
            public void onStart(String utteranceId) {
                Log.e(TAG, "onStart");
            }
            @Override
            public void onDone(String utteranceId) {
                Log.e(TAG, "onDone");
            }
            @Override
            public void onError(String utteranceId) {
                Log.e(TAG, "onError");
            }
        });
    }

执行结果:执行该方法后,语音会播报内容,打印日志如下

2020-04-08 15:29:24.064 22831-22831/com.example.test E/speech: 1/EngineInfo{name=com.samsung.SMT}
2020-04-08 15:29:24.075 22831-22857/com.example.test E/speech: onStart
2020-04-08 15:29:29.946 22831-22857/com.example.test E/speech: onDone

总结:只有一个引擎,是com.samsung.SMT,语音播报前面会回调对应的方法;如果是其它手机会有差别,引用用的引擎不一样。举例:

三星   com.samsung.SMT
荣耀   com.iflytek.speechsuite
红米   com.svox.pico 
原生板子  com.google.android.tts
TTS流程总结 Android SDK关于TTS的代码只是完成了接口的定义,而具体的引擎实现需要各产商单独定义 核心流程就是TextToSpeech开启服务和设置语音合成过程的回调 开启的服务需要继承TextToSpeechService,且action="android.intent.action.TTS_SERVICE",而服务就是自定义引擎的开始 初始化完成后,当调用speak或者synthesizeToFile功能后,最后调用自定义服务的onSynthesizeText(),语音成功过程,通过参数SynthesisCallback完成回调,形成闭环 源码分析

TextToSpeech构造函数如下

  public TextToSpeech(Context context, OnInitListener listener, String engine,
            String packageName, boolean useFallback) {
        mContext = context;
        mInitListener = listener;
        mRequestedEngine = engine;
        mUseFallback = useFallback;
        mEarcons = new HashMap();
        mUtterances = new HashMap();
        mUtteranceProgressListener = null;
        mEnginesHelper = new TtsEngines(mContext);
        initTts();
    }

紧接初始化tts, initTts()

android.speech.tts.TextToSpeech
private int initTts() {
        // Step 1: Try connecting to the engine that was requested.
        if (mRequestedEngine != null) {
            if (mEnginesHelper.isEngineInstalled(mRequestedEngine)) {
                if (connectToEngine(mRequestedEngine)) {
                    mCurrentEngine = mRequestedEngine;
                    return SUCCESS;
                } else if (!mUseFallback) {
                    mCurrentEngine = null;
                    dispatchOnInit(ERROR);
                    return ERROR;
                }
            } else if (!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.
        final String defaultEngine = 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.
        final String highestRanked = 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.
        mCurrentEngine = null;
        dispatchOnInit(ERROR);
        return ERROR;
    }
  android.speech.tts.TtsEngines
public String getHighestRankedEngineName() {
        final List engines = getEngines();
        if (engines.size() > 0 && engines.get(0).system) {
            return engines.get(0).name;
        }
        return null;
    }

首先尝试连接到用户请求的引擎,其次连接到用户默认的引擎,最后连接到排名最高的引擎(即引擎集合里最前面的引擎),连接引擎如下

android.speech.tts.TextToSpeech
private boolean connectToEngine(String engine) {
        Connection connection = new Connection();
        Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
        intent.setPackage(engine);
        boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
        if (!bound) {
            Log.e(TAG, "Failed to bind to " + engine);
            return false;
        } else {
            Log.i(TAG, "Sucessfully bound to " + engine);
            mConnectingServiceConnection = connection;
            return true;
        }
    }
  
        @SdkConstant(SdkConstantType.SERVICE_ACTION)
        public static final String INTENT_ACTION_TTS_SERVICE =
                "android.intent.action.TTS_SERVICE";

Engine.INTENT_ACTION_TTS_SERVICE的值是"android.intent.action.TTS_SERVICE";其连接的服务的action="android.intent.action.TTS_SERVICE",
且必须继承TxetToSpeechService,开启服务成功后,TTS引擎的初始化就完成了,接下来看什么时候开始回调new TextToSpeech.OnInitListener()
action="android.intent.action.TTS_SERVICE"服务开启后的相关流程查看Connection的onServiceConnected
private class Connection implements ServiceConnection {
 .....
 @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            synchronized(mStartLock) {
                mConnectingServiceConnection = null;
                Log.i(TAG, "Connected to " + name);
                if (mOnSetupConnectionAsyncTask != null) {
                    mOnSetupConnectionAsyncTask.cancel(false);
                }
                mService = ITextToSpeechService.Stub.asInterface(service);
                mServiceConnection = Connection.this;
                mEstablished = false;
                mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask(name);
                mOnSetupConnectionAsyncTask.execute();
            }
        }
  ....
}
android.speech.tts.TextToSpeechService 
 @Override
    public IBinder onBind(Intent intent) {
        if (TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE.equals(intent.getAction())) {
            return mBinder;
        }
        return null;
    }

获取到跨进程的服务返回的IBinder在TextToSpeechService服务中,对应的就是mService,其中跨进程的服务的还有执行异步任务

 private class SetupConnectionAsyncTask extends AsyncTask {
            private final ComponentName mName;
            public SetupConnectionAsyncTask(ComponentName name) {
                mName = name;
            }
            @Override
            protected Integer doInBackground(Void... params) {
                synchronized(mStartLock) {
                    if (isCancelled()) {
                        return null;
                    }
                    try {
                        mService.setCallback(getCallerIdentity(), mCallback);
                        if (mParams.getString(Engine.KEY_PARAM_LANGUAGE) == null) {
                            String[] defaultLanguage = mService.getClientDefaultLanguage();
                            mParams.putString(Engine.KEY_PARAM_LANGUAGE, defaultLanguage[0]);
                            mParams.putString(Engine.KEY_PARAM_COUNTRY, defaultLanguage[1]);
                            mParams.putString(Engine.KEY_PARAM_VARIANT, defaultLanguage[2]);
                            // Get the default voice for the locale.
                            String defaultVoiceName = mService.getDefaultVoiceNameFor(
                                defaultLanguage[0], defaultLanguage[1], defaultLanguage[2]);
                            mParams.putString(Engine.KEY_PARAM_VOICE_NAME, defaultVoiceName);
                        }
                        Log.i(TAG, "Set up connection to " + mName);
                        return SUCCESS;
                    } catch (RemoteException re) {
                        Log.e(TAG, "Error connecting to service, setCallback() failed");
                        return ERROR;
                    }
                }
            }
            @Override
            protected void onPostExecute(Integer result) {
                synchronized(mStartLock) {
                    if (mOnSetupConnectionAsyncTask == this) {
                        mOnSetupConnectionAsyncTask = null;
                    }
                    mEstablished = true;
                    dispatchOnInit(result);
                }
            }
        }
private void dispatchOnInit(int result) {
        synchronized (mStartLock) {
            if (mInitListener != null) {
                mInitListener.onInit(result);
                mInitListener = null;
            }
        }
    }
        private final ITextToSpeechCallback.Stub mCallback =
                new ITextToSpeechCallback.Stub() {
                    public void onStop(String utteranceId, boolean isStarted)
                            throws RemoteException {
                        UtteranceProgressListener listener = mUtteranceProgressListener;
                        if (listener != null) {
                            listener.onStop(utteranceId, isStarted);
                        }
                    };
                    @Override
                    public void onSuccess(String utteranceId) {
                        UtteranceProgressListener listener = mUtteranceProgressListener;
                        if (listener != null) {
                            listener.onDone(utteranceId);
                        }
                    }
                    @Override
                    public void onError(String utteranceId, int errorCode) {
                        UtteranceProgressListener listener = mUtteranceProgressListener;
                        if (listener != null) {
                            listener.onError(utteranceId);
                        }
                    }
                    @Override
                    public void onStart(String utteranceId) {
                        UtteranceProgressListener listener = mUtteranceProgressListener;
                        if (listener != null) {
                            listener.onStart(utteranceId);
                        }
                    }
                    @Override
                    public void onBeginSynthesis(
                            String utteranceId,
                            int sampleRateInHz,
                            int audioFormat,
                            int channelCount) {
                        UtteranceProgressListener listener = mUtteranceProgressListener;
                        if (listener != null) {
                            listener.onBeginSynthesis(
                                    utteranceId, sampleRateInHz, audioFormat, channelCount);
                        }
                    }
                    @Override
                    public void onAudioAvailable(String utteranceId, byte[] audio) {
                        UtteranceProgressListener listener = mUtteranceProgressListener;
                        if (listener != null) {
                            listener.onAudioAvailable(utteranceId, audio);
                        }
                    }
                    @Override
                    public void onRangeStart(String utteranceId, int start, int end, int frame) {
                        UtteranceProgressListener listener = mUtteranceProgressListener;
                        if (listener != null) {
                            listener.onRangeStart(utteranceId, start, end, frame);
                        }
                    }
                };

一个是给TTS服务设置回调,回到设置成功后,执行dispatchOnInit(result)代表引擎设置回调成功,且TextToSpeech初始化成功

其中给TTS服务设置的回调就是语音合成相关事件的侦听器,就是TextToSpeech的setOnUtteranceProgressListener

TTS初始化完成后,就可以直接调用speak,synthesizeToFile,下面流程按speak分析

speak源码如下

public int speak(final CharSequence text,
                     final int queueMode,
                     final Bundle params,
                     final String utteranceId) {
        return runAction(new Action() {
            @Override
            public Integer run(ITextToSpeechService service) throws RemoteException {
                Uri utteranceUri = mUtterances.get(text);
                if (utteranceUri != null) {
                    return service.playAudio(getCallerIdentity(), utteranceUri, queueMode,
                            getParams(params), utteranceId);
                } else {
                    return service.speak(getCallerIdentity(), text, queueMode, getParams(params),
                            utteranceId);
                }
            }
        }, ERROR, "speak");
    }

其中调用runAction方法,最后调用

  public  R runAction(Action action, R errorResult, String method,
                boolean reconnect, boolean onlyEstablishedConnection) {
            synchronized (mStartLock) {
                try {
                    if (mService == null) {
                        Log.w(TAG, method + " failed: not connected to TTS engine");
                        return errorResult;
                    }
                    if (onlyEstablishedConnection && !isEstablished()) {
                        Log.w(TAG, method + " failed: TTS engine connection not fully set up");
                        return errorResult;
                    }
                    return action.run(mService);
                } catch (RemoteException ex) {
                    Log.e(TAG, method + " failed", ex);
                    if (reconnect) {
                        disconnect();
                        initTts();
                    }
                    return errorResult;
                }
            }
        }

其中mService就是跨进程的服务,本过程主要是检查相关服务是否为null,最后还是调用跨进程服务的speak方法,接下来查看TextToSpeechServcie的mBinder的speak方法

    private final ITextToSpeechService.Stub mBinder =
            new ITextToSpeechService.Stub() {
                @Override
                public int speak(
                        IBinder caller,
                        CharSequence text,
                        int queueMode,
                        Bundle params,
                        String utteranceId) {
                    if (!checkNonNull(caller, text, params)) {
                        return TextToSpeech.ERROR;
                    }
                    SpeechItem item =
                            new SynthesisSpeechItem(
                                    caller,
                                    Binder.getCallingUid(),
                                    Binder.getCallingPid(),
                                    params,
                                    utteranceId,
                                    text);
                    return mSynthHandler.enqueueSpeechItem(queueMode, item);
                }
                @Override
                public int synthesizeToFileDescriptor(
                        IBinder caller,
                        CharSequence text,
                        ParcelFileDescriptor fileDescriptor,
                        Bundle params,
                        String utteranceId) {
                    if (!checkNonNull(caller, text, fileDescriptor, params)) {
                        return TextToSpeech.ERROR;
                    }
                    // In test env, ParcelFileDescriptor instance may be EXACTLY the same
                    // one that is used by client. And it will be closed by a client, thus
                    // preventing us from writing anything to it.
                    final ParcelFileDescriptor sameFileDescriptor =
                            ParcelFileDescriptor.adoptFd(fileDescriptor.detachFd());
                    SpeechItem item =
                            new SynthesisToFileOutputStreamSpeechItem(
                                    caller,
                                    Binder.getCallingUid(),
                                    Binder.getCallingPid(),
                                    params,
                                    utteranceId,
                                    text,
                                    new ParcelFileDescriptor.AutoCloseOutputStream(
                                            sameFileDescriptor));
                    return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
                }
                @Override
                public int playAudio(
                        IBinder caller,
                        Uri audioUri,
                        int queueMode,
                        Bundle params,
                        String utteranceId) {
                    if (!checkNonNull(caller, audioUri, params)) {
                        return TextToSpeech.ERROR;
                    }
                    SpeechItem item =
                            new AudioSpeechItem(
                                    caller,
                                    Binder.getCallingUid(),
                                    Binder.getCallingPid(),
                                    params,
                                    utteranceId,
                                    audioUri);
                    return mSynthHandler.enqueueSpeechItem(queueMode, item);
                }
                @Override
                public int playSilence(
                        IBinder caller, long duration, int queueMode, String utteranceId) {
                    if (!checkNonNull(caller)) {
                        return TextToSpeech.ERROR;
                    }
                    SpeechItem item =
                            new SilenceSpeechItem(
                                    caller,
                                    Binder.getCallingUid(),
                                    Binder.getCallingPid(),
                                    utteranceId,
                                    duration);
                    return mSynthHandler.enqueueSpeechItem(queueMode, item);
                }
                @Override
                public boolean isSpeaking() {
                    return mSynthHandler.isSpeaking() || mAudioPlaybackHandler.isSpeaking();
                }
                @Override
                public int stop(IBinder caller) {
                    if (!checkNonNull(caller)) {
                        return TextToSpeech.ERROR;
                    }
                    return mSynthHandler.stopForApp(caller);
                }
                @Override
                public String[] getLanguage() {
                    return onGetLanguage();
                }
                @Override
                public String[] getClientDefaultLanguage() {
                    return getSettingsLocale();
                }
                
                @Override
                public int isLanguageAvailable(String lang, String country, String variant) {
                    if (!checkNonNull(lang)) {
                        return TextToSpeech.ERROR;
                    }
                    return onIsLanguageAvailable(lang, country, variant);
                }
                @Override
                public String[] getFeaturesForLanguage(
                        String lang, String country, String variant) {
                    Set features = onGetFeaturesForLanguage(lang, country, variant);
                    String[] featuresArray = null;
                    if (features != null) {
                        featuresArray = new String[features.size()];
                        features.toArray(featuresArray);
                    } else {
                        featuresArray = new String[0];
                    }
                    return featuresArray;
                }
                
                @Override
                public int loadLanguage(
                        IBinder caller, String lang, String country, String variant) {
                    if (!checkNonNull(lang)) {
                        return TextToSpeech.ERROR;
                    }
                    int retVal = onIsLanguageAvailable(lang, country, variant);
                    if (retVal == TextToSpeech.LANG_AVAILABLE
                            || retVal == TextToSpeech.LANG_COUNTRY_AVAILABLE
                            || retVal == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
                        SpeechItem item =
                                new LoadLanguageItem(
                                        caller,
                                        Binder.getCallingUid(),
                                        Binder.getCallingPid(),
                                        lang,
                                        country,
                                        variant);
                        if (mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item)
                                != TextToSpeech.SUCCESS) {
                            return TextToSpeech.ERROR;
                        }
                    }
                    return retVal;
                }
                @Override
                public List getVoices() {
                    return onGetVoices();
                }
                @Override
                public int loadVoice(IBinder caller, String voiceName) {
                    if (!checkNonNull(voiceName)) {
                        return TextToSpeech.ERROR;
                    }
                    int retVal = onIsValidVoiceName(voiceName);
                    if (retVal == TextToSpeech.SUCCESS) {
                        SpeechItem item =
                                new LoadVoiceItem(
                                        caller,
                                        Binder.getCallingUid(),
                                        Binder.getCallingPid(),
                                        voiceName);
                        if (mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item)
                                != TextToSpeech.SUCCESS) {
                            return TextToSpeech.ERROR;
                        }
                    }
                    return retVal;
                }
                public String getDefaultVoiceNameFor(String lang, String country, String variant) {
                    if (!checkNonNull(lang)) {
                        return null;
                    }
                    int retVal = onIsLanguageAvailable(lang, country, variant);
                    if (retVal == TextToSpeech.LANG_AVAILABLE
                            || retVal == TextToSpeech.LANG_COUNTRY_AVAILABLE
                            || retVal == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
                        return onGetDefaultVoiceNameFor(lang, country, variant);
                    } else {
                        return null;
                    }
                }
                @Override
                public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
                    // Note that passing in a null callback is a valid use case.
                    if (!checkNonNull(caller)) {
                        return;
                    }
                    mCallbacks.setCallback(caller, cb);
                }
                private String intern(String in) {
                    // The input parameter will be non null.
                    return in.intern();
                }
                private boolean checkNonNull(Object... args) {
                    for (Object o : args) {
                        if (o == null) return false;
                    }
                    return true;
                }
            };

里面对应的方法都是textToSpeech调用的方法,跟着流程走,我们查看speak方法,其中传递的SpeechItem实例SynthesisSpeechItem

 
        public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) {
            UtteranceProgressDispatcher utterenceProgress = null;
            if (speechItem instanceof UtteranceProgressDispatcher) {
                utterenceProgress = (UtteranceProgressDispatcher) speechItem;
            }
            if (!speechItem.isValid()) {
                if (utterenceProgress != null) {
                    utterenceProgress.dispatchOnError(
                            TextToSpeech.ERROR_INVALID_REQUEST);
                }
                return TextToSpeech.ERROR;
            }
            if (queueMode == TextToSpeech.QUEUE_FLUSH) {
                stopForApp(speechItem.getCallerIdentity());
            } else if (queueMode == TextToSpeech.QUEUE_DESTROY) {
                stopAll();
            }
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    if (setCurrentSpeechItem(speechItem)) {
                        speechItem.play();
                        removeCurrentSpeechItem();
                    } else {
                        // The item is alreadly flushed. Stopping.
                        speechItem.stop();
                    }
                }
            };
            Message msg = Message.obtain(this, runnable);
            // 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;
            }
        }

一种是根据排队模式停止当前App的语音或者所有App的语音,然后执行speechItem.paly()

   public void play() {
            synchronized (this) {
                if (mStarted) {
                    throw new IllegalStateException("play() called twice");
                }
                mStarted = true;
            }
            playImpl();
        }
   protected abstract void playImpl();

随后调用speechItem.实现类的playImpl,实现类是SynthesisSpeechItem,它的playImpl方法如下

   @Override
        protected void playImpl() {
            AbstractSynthesisCallback synthesisCallback;
            mEventLogger.onRequestProcessingStart();
            synchronized (this) {
                // stop() might have been called before we enter this
                // synchronized block.
                if (isStopped()) {
                    return;
                }
                mSynthesisCallback = createSynthesisCallback();
                synthesisCallback = mSynthesisCallback;
            }
            TextToSpeechService.this.onSynthesizeText(mSynthesisRequest, synthesisCallback);
            // Fix for case where client called .start() & .error(), but did not called .done()
            if (synthesisCallback.hasStarted() && !synthesisCallback.hasFinished()) {
                synthesisCallback.done();
            }
        }
   protected AbstractSynthesisCallback createSynthesisCallback() {
            return new PlaybackSynthesisCallback(getAudioParams(),
                    mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger, false);
        }
 
    protected abstract void onSynthesizeText(SynthesisRequest request, SynthesisCallback callback);

一个是创建回调,一个是调用onSyntehsizeText方法;核心方法就在这,参数一个是请求的所有参数,一个是回调,但是是TextToSpeechService的抽象方法;然后看回调对象

android.speech.tts.SynthesisCallback

  int start(
      int sampleRateInHz,
      @SupportedAudioFormat int audioFormat,
      @IntRange(from = 1, to = 2) int channelCount);

  int audioAvailable(byte[] buffer, int offset, int length);
  
  int done();
android.speech.tts.PlaybackSynthesisCallback
 @Override
    public int audioAvailable(byte[] buffer, int offset, int length) {
        if (DBG) Log.d(TAG, "audioAvailable(byte[" + buffer.length + "]," + offset + "," + length
                + ")");
        if (length > getMaxBufferSize() || length <= 0) {
            throw new IllegalArgumentException("buffer is too large or of zero length (" +
                    + length + " bytes)");
        }
        SynthesisPlaybackQueueItem item = null;
        synchronized (mStateLock) {
            if (mItem == null) {
                mStatusCode = TextToSpeech.ERROR_OUTPUT;
                return TextToSpeech.ERROR;
            }
            if (mStatusCode != TextToSpeech.SUCCESS) {
                if (DBG) Log.d(TAG, "Error was raised");
                return TextToSpeech.ERROR;
            }
            if (mStatusCode == TextToSpeech.STOPPED) {
                return errorCodeOnStop();
            }
            item = mItem;
        }
        // Sigh, another copy.
        final byte[] bufferCopy = new byte[length];
        System.arraycopy(buffer, offset, bufferCopy, 0, length);
        mDispatcher.dispatchOnAudioAvailable(bufferCopy);
        // Might block on mItem.this, if there are too many buffers waiting to
        // be consumed.
        try {
            item.put(bufferCopy);
        } catch (InterruptedException ie) {
            synchronized (mStateLock) {
                mStatusCode = TextToSpeech.ERROR_OUTPUT;
                return TextToSpeech.ERROR;
            }
        }
        mLogger.onEngineDataReceived();
        return TextToSpeech.SUCCESS;
    }
 @Override
    public int done() {
        if (DBG) Log.d(TAG, "done()");
        int statusCode = 0;
        SynthesisPlaybackQueueItem item = null;
        synchronized (mStateLock) {
            if (mDone) {
                Log.w(TAG, "Duplicate call to done()");
                // Not an error that would prevent synthesis. Hence no
                // setStatusCode
                return TextToSpeech.ERROR;
            }
            if (mStatusCode == TextToSpeech.STOPPED) {
                if (DBG) Log.d(TAG, "Request has been aborted.");
                return errorCodeOnStop();
            }
            mDone = true;
            if (mItem == null) {
                // .done() was called before .start. Treat it as successful synthesis
                // for a client, despite service bad implementation.
                Log.w(TAG, "done() was called before start() call");
                if (mStatusCode == TextToSpeech.SUCCESS) {
                    mDispatcher.dispatchOnSuccess();
                } else {
                    mDispatcher.dispatchOnError(mStatusCode);
                }
                mLogger.onEngineComplete();
                return TextToSpeech.ERROR;
            }
            item = mItem;
            statusCode = mStatusCode;
        }

所以真正的实现是在它的子类里实现,在语音引擎完成过程,必须回调对应的方法,后续我们只跟踪对应的done()方法,最后通过mDispatcher.dispatchOnSuccess()继续回调,跟踪最后调用的是TextToSpeech设置的监听结束


 @Override
public void onSuccess(String utteranceId) {
    UtteranceProgressListener listener = mUtteranceProgressListener;
            if (listener != null) {
                 listener.onDone(utteranceId);
               }
}

至此整个TTS服务整个调用过程结束

自定义引擎服务--完成文本转语音的功能,且完成对应的回调
public class TtsService extends TextToSpeechService {
    @Override
    protected int onIsLanguageAvailable(String lang, String country, String variant) {
        return 0;
    }
    @Override
    protected String[] onGetLanguage() {
        return new String[0];
    }
    @Override
    protected int onLoadLanguage(String lang, String country, String variant) {
        return 0;
    }
    @Override
    protected void onStop() {
    }
    @Override
    protected void onSynthesizeText(SynthesisRequest request, SynthesisCallback callback) {
    }
}

继承的服务必须设置action="android.intent.action.TTS_SERVICE",且方法onSynthesizeText()是定义引擎开始的地方,而完成回到需要使用参数SynthesisCallback完成闭环,里面涉及跟C语言的交互完成,中间的过程可以参考博客含有TTS引擎实现的流程


作者:小马二号


阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯