150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert/* 250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * Copyright (C) 2011 The Android Open Source Project 350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * 450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * Licensed under the Apache License, Version 2.0 (the "License"); you may not 550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * use this file except in compliance with the License. You may obtain a copy of 650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * the License at 750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * 850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * http://www.apache.org/licenses/LICENSE-2.0 950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * 1050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * Unless required by applicable law or agreed to in writing, software 1150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 1250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 1350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * License for the specific language governing permissions and limitations under 1450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * the License. 1550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert */ 1650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertpackage android.speech.tts; 1750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert 18453c13f77121641a52d8b422a527feb7b3d3b8abNiels Egbertsimport android.annotation.NonNull; 1950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.media.AudioFormat; 2090d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniakimport android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher; 2150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport android.util.Log; 2250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert 2350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport java.io.IOException; 2450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport java.nio.ByteBuffer; 2550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringertimport java.nio.ByteOrder; 265acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniakimport java.nio.channels.FileChannel; 2750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert 2850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert/** 2950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * Speech synthesis request that writes the audio to a WAV file. 3050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert */ 31e22b69a7de0349b99d3107349d1d3aa72d62c841Narayan Kamathclass FileSynthesisCallback extends AbstractSynthesisCallback { 3250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert 3350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert private static final String TAG = "FileSynthesisRequest"; 3450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert private static final boolean DBG = false; 3550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert 3671e0b4807797c602e7fc787d00d27c4f9c92a507Bjorn Bringert private static final int MAX_AUDIO_BUFFER_SIZE = 8192; 3771e0b4807797c602e7fc787d00d27c4f9c92a507Bjorn Bringert 3850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert private static final int WAV_HEADER_LENGTH = 44; 3950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert private static final short WAV_FORMAT_PCM = 0x0001; 4050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert 4150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert private final Object mStateLock = new Object(); 425acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniak 4350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert private int mSampleRateInHz; 4450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert private int mAudioFormat; 4550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert private int mChannelCount; 465acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniak 475acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniak private FileChannel mFileChannel; 485acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniak 4990d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak private final UtteranceProgressDispatcher mDispatcher; 5090d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak 515acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniak private boolean mStarted = false; 52360eb168d6f9c967543852c7bbab370ef5d8c1bdBjorn Bringert private boolean mDone = false; 5350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert 5490d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak /** Status code of synthesis */ 5590d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak protected int mStatusCode; 5690d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak 57453c13f77121641a52d8b422a527feb7b3d3b8abNiels Egberts FileSynthesisCallback(@NonNull FileChannel fileChannel, 58453c13f77121641a52d8b422a527feb7b3d3b8abNiels Egberts @NonNull UtteranceProgressDispatcher dispatcher, boolean clientIsUsingV2) { 5990d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak super(clientIsUsingV2); 605acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniak mFileChannel = fileChannel; 6190d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak mDispatcher = dispatcher; 62fc4b2890378eb1b6e0b11d60d703eb6854268064Przemyslaw Szczepaniak mStatusCode = TextToSpeech.SUCCESS; 6350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert } 6450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert 6550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert @Override 6650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert void stop() { 6750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert synchronized (mStateLock) { 6890d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak if (mDone) { 6990d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak return; 7090d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak } 71fc4b2890378eb1b6e0b11d60d703eb6854268064Przemyslaw Szczepaniak if (mStatusCode == TextToSpeech.STOPPED) { 7290d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak return; 7390d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak } 7490d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak 75fc4b2890378eb1b6e0b11d60d703eb6854268064Przemyslaw Szczepaniak mStatusCode = TextToSpeech.STOPPED; 7650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert cleanUp(); 77453c13f77121641a52d8b422a527feb7b3d3b8abNiels Egberts mDispatcher.dispatchOnStop(); 7850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert } 7950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert } 8050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert 8150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert /** 8250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * Must be called while holding the monitor on {@link #mStateLock}. 8350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert */ 8450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert private void cleanUp() { 855acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniak closeFile(); 8650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert } 8750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert 8850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert /** 8950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert * Must be called while holding the monitor on {@link #mStateLock}. 9050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert */ 915acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniak private void closeFile() { 9290d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak // File will be closed by the SpeechItem in the speech service. 9390d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak mFileChannel = null; 949c3d7a888d0c5c09f0153e81018ff68aa6e91712Narayan Kamath } 959c3d7a888d0c5c09f0153e81018ff68aa6e91712Narayan Kamath 9650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert @Override 9771e0b4807797c602e7fc787d00d27c4f9c92a507Bjorn Bringert public int getMaxBufferSize() { 9871e0b4807797c602e7fc787d00d27c4f9c92a507Bjorn Bringert return MAX_AUDIO_BUFFER_SIZE; 9971e0b4807797c602e7fc787d00d27c4f9c92a507Bjorn Bringert } 10071e0b4807797c602e7fc787d00d27c4f9c92a507Bjorn Bringert 10171e0b4807797c602e7fc787d00d27c4f9c92a507Bjorn Bringert @Override 10250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert public int start(int sampleRateInHz, int audioFormat, int channelCount) { 10350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert if (DBG) { 10450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert Log.d(TAG, "FileSynthesisRequest.start(" + sampleRateInHz + "," + audioFormat 10550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert + "," + channelCount + ")"); 10650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert } 1079b2b2f50909b0278fee7f26d0070f318fd6b354aNiels Egberts if (audioFormat != AudioFormat.ENCODING_PCM_8BIT && 1089b2b2f50909b0278fee7f26d0070f318fd6b354aNiels Egberts audioFormat != AudioFormat.ENCODING_PCM_16BIT && 109a24b50bee1aca19028477b235862bcd2c37135edNiels Egberts audioFormat != AudioFormat.ENCODING_PCM_FLOAT) { 110a24b50bee1aca19028477b235862bcd2c37135edNiels Egberts Log.e(TAG, "Audio format encoding " + audioFormat + " not supported. Please use one " + 111a24b50bee1aca19028477b235862bcd2c37135edNiels Egberts "of AudioFormat.ENCODING_PCM_8BIT, AudioFormat.ENCODING_PCM_16BIT or " + 112a24b50bee1aca19028477b235862bcd2c37135edNiels Egberts "AudioFormat.ENCODING_PCM_FLOAT"); 113a24b50bee1aca19028477b235862bcd2c37135edNiels Egberts } 114c99ba1c3edf725e070383b27724c9ed63e1e5765Niels Egberts mDispatcher.dispatchOnBeginSynthesis(sampleRateInHz, audioFormat, channelCount); 115a24b50bee1aca19028477b235862bcd2c37135edNiels Egberts 11690d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak FileChannel fileChannel = null; 11750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert synchronized (mStateLock) { 118fc4b2890378eb1b6e0b11d60d703eb6854268064Przemyslaw Szczepaniak if (mStatusCode == TextToSpeech.STOPPED) { 11950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert if (DBG) Log.d(TAG, "Request has been aborted."); 12090d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak return errorCodeOnStop(); 12190d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak } 122fc4b2890378eb1b6e0b11d60d703eb6854268064Przemyslaw Szczepaniak if (mStatusCode != TextToSpeech.SUCCESS) { 12390d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak if (DBG) Log.d(TAG, "Error was raised"); 12450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert return TextToSpeech.ERROR; 12550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert } 1265acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniak if (mStarted) { 12790d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak Log.e(TAG, "Start called twice"); 12890d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak return TextToSpeech.ERROR; 12950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert } 1305acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniak mStarted = true; 13150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert mSampleRateInHz = sampleRateInHz; 13250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert mAudioFormat = audioFormat; 13350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert mChannelCount = channelCount; 1345acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniak 135453c13f77121641a52d8b422a527feb7b3d3b8abNiels Egberts mDispatcher.dispatchOnStart(); 13690d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak fileChannel = mFileChannel; 13790d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak } 13890d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak 13990d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak try { 14090d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak fileChannel.write(ByteBuffer.allocate(WAV_HEADER_LENGTH)); 14150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert return TextToSpeech.SUCCESS; 14290d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak } catch (IOException ex) { 14390d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak Log.e(TAG, "Failed to write wav header to output file descriptor", ex); 14490d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak synchronized (mStateLock) { 14550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert cleanUp(); 146fc4b2890378eb1b6e0b11d60d703eb6854268064Przemyslaw Szczepaniak mStatusCode = TextToSpeech.ERROR_OUTPUT; 14750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert } 14890d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak return TextToSpeech.ERROR; 14950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert } 15050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert } 15150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert 15250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert @Override 15350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert public int audioAvailable(byte[] buffer, int offset, int length) { 15450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert if (DBG) { 15550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert Log.d(TAG, "FileSynthesisRequest.audioAvailable(" + buffer + "," + offset 15650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert + "," + length + ")"); 15750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert } 15890d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak FileChannel fileChannel = null; 15950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert synchronized (mStateLock) { 160fc4b2890378eb1b6e0b11d60d703eb6854268064Przemyslaw Szczepaniak if (mStatusCode == TextToSpeech.STOPPED) { 16150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert if (DBG) Log.d(TAG, "Request has been aborted."); 16290d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak return errorCodeOnStop(); 16390d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak } 164fc4b2890378eb1b6e0b11d60d703eb6854268064Przemyslaw Szczepaniak if (mStatusCode != TextToSpeech.SUCCESS) { 16590d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak if (DBG) Log.d(TAG, "Error was raised"); 16650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert return TextToSpeech.ERROR; 16750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert } 1685acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniak if (mFileChannel == null) { 16950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert Log.e(TAG, "File not open"); 170fc4b2890378eb1b6e0b11d60d703eb6854268064Przemyslaw Szczepaniak mStatusCode = TextToSpeech.ERROR_OUTPUT; 17150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert return TextToSpeech.ERROR; 17250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert } 17390d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak if (!mStarted) { 17490d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak Log.e(TAG, "Start method was not called"); 17550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert return TextToSpeech.ERROR; 17650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert } 17790d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak fileChannel = mFileChannel; 17890d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak } 17990d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak 180c99ba1c3edf725e070383b27724c9ed63e1e5765Niels Egberts final byte[] bufferCopy = new byte[length]; 181c99ba1c3edf725e070383b27724c9ed63e1e5765Niels Egberts System.arraycopy(buffer, offset, bufferCopy, 0, length); 182c99ba1c3edf725e070383b27724c9ed63e1e5765Niels Egberts mDispatcher.dispatchOnAudioAvailable(bufferCopy); 183c99ba1c3edf725e070383b27724c9ed63e1e5765Niels Egberts 18490d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak try { 18590d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak fileChannel.write(ByteBuffer.wrap(buffer, offset, length)); 18690d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak return TextToSpeech.SUCCESS; 18790d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak } catch (IOException ex) { 18890d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak Log.e(TAG, "Failed to write to output file descriptor", ex); 18990d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak synchronized (mStateLock) { 19090d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak cleanUp(); 191fc4b2890378eb1b6e0b11d60d703eb6854268064Przemyslaw Szczepaniak mStatusCode = TextToSpeech.ERROR_OUTPUT; 19290d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak } 19390d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak return TextToSpeech.ERROR; 19450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert } 19550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert } 19650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert 19750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert @Override 19850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert public int done() { 19950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert if (DBG) Log.d(TAG, "FileSynthesisRequest.done()"); 20090d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak FileChannel fileChannel = null; 20190d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak 20290d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak int sampleRateInHz = 0; 20390d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak int audioFormat = 0; 20490d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak int channelCount = 0; 20590d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak 20650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert synchronized (mStateLock) { 2079c3d7a888d0c5c09f0153e81018ff68aa6e91712Narayan Kamath if (mDone) { 20890d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak Log.w(TAG, "Duplicate call to done()"); 20990d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak // This is not an error that would prevent synthesis. Hence no 21090d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak // setStatusCode is set. 2119c3d7a888d0c5c09f0153e81018ff68aa6e91712Narayan Kamath return TextToSpeech.ERROR; 2129c3d7a888d0c5c09f0153e81018ff68aa6e91712Narayan Kamath } 213fc4b2890378eb1b6e0b11d60d703eb6854268064Przemyslaw Szczepaniak if (mStatusCode == TextToSpeech.STOPPED) { 21450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert if (DBG) Log.d(TAG, "Request has been aborted."); 21590d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak return errorCodeOnStop(); 21690d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak } 217453c13f77121641a52d8b422a527feb7b3d3b8abNiels Egberts if (mStatusCode != TextToSpeech.SUCCESS && mStatusCode != TextToSpeech.STOPPED) { 21890d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak mDispatcher.dispatchOnError(mStatusCode); 21950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert return TextToSpeech.ERROR; 22050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert } 2215acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniak if (mFileChannel == null) { 22250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert Log.e(TAG, "File not open"); 22350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert return TextToSpeech.ERROR; 22450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert } 22590d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak mDone = true; 22690d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak fileChannel = mFileChannel; 22790d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak sampleRateInHz = mSampleRateInHz; 22890d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak audioFormat = mAudioFormat; 22990d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak channelCount = mChannelCount; 23090d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak } 23190d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak 23290d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak try { 23390d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak // Write WAV header at start of file 23490d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak fileChannel.position(0); 23590d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak int dataLength = (int) (fileChannel.size() - WAV_HEADER_LENGTH); 23690d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak fileChannel.write( 23790d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak makeWavHeader(sampleRateInHz, audioFormat, channelCount, dataLength)); 23890d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak 23990d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak synchronized (mStateLock) { 2405acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniak closeFile(); 241453c13f77121641a52d8b422a527feb7b3d3b8abNiels Egberts mDispatcher.dispatchOnSuccess(); 24250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert return TextToSpeech.SUCCESS; 24390d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak } 24490d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak } catch (IOException ex) { 24590d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak Log.e(TAG, "Failed to write to output file descriptor", ex); 24690d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak synchronized (mStateLock) { 24750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert cleanUp(); 24850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert } 24990d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak return TextToSpeech.ERROR; 25050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert } 25150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert } 25250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert 25371e0b4807797c602e7fc787d00d27c4f9c92a507Bjorn Bringert @Override 254360eb168d6f9c967543852c7bbab370ef5d8c1bdBjorn Bringert public void error() { 255fc4b2890378eb1b6e0b11d60d703eb6854268064Przemyslaw Szczepaniak error(TextToSpeech.ERROR_SYNTHESIS); 25690d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak } 25790d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak 25890d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak @Override 25990d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak public void error(int errorCode) { 260360eb168d6f9c967543852c7bbab370ef5d8c1bdBjorn Bringert if (DBG) Log.d(TAG, "FileSynthesisRequest.error()"); 261360eb168d6f9c967543852c7bbab370ef5d8c1bdBjorn Bringert synchronized (mStateLock) { 26290d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak if (mDone) { 26390d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak return; 26490d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak } 265360eb168d6f9c967543852c7bbab370ef5d8c1bdBjorn Bringert cleanUp(); 26690d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak mStatusCode = errorCode; 26790d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak } 26890d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak } 26990d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak 27090d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak @Override 27190d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak public boolean hasStarted() { 27290d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak synchronized (mStateLock) { 27390d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak return mStarted; 27490d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak } 27590d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak } 27690d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak 27790d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak @Override 27890d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak public boolean hasFinished() { 27990d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak synchronized (mStateLock) { 28090d15d2371ad85f22254be6985455aa2baa5d15dPrzemyslaw Szczepaniak return mDone; 281360eb168d6f9c967543852c7bbab370ef5d8c1bdBjorn Bringert } 282360eb168d6f9c967543852c7bbab370ef5d8c1bdBjorn Bringert } 283360eb168d6f9c967543852c7bbab370ef5d8c1bdBjorn Bringert 2845acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniak private ByteBuffer makeWavHeader(int sampleRateInHz, int audioFormat, int channelCount, 28571e0b4807797c602e7fc787d00d27c4f9c92a507Bjorn Bringert int dataLength) { 28634a37bdebb3d606dac7c7d1dd7a0effdb59bd3d6Glenn Kasten int sampleSizeInBytes = AudioFormat.getBytesPerSample(audioFormat); 28750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert int byteRate = sampleRateInHz * sampleSizeInBytes * channelCount; 28850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert short blockAlign = (short) (sampleSizeInBytes * channelCount); 28950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert short bitsPerSample = (short) (sampleSizeInBytes * 8); 29050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert 29150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert byte[] headerBuf = new byte[WAV_HEADER_LENGTH]; 29250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert ByteBuffer header = ByteBuffer.wrap(headerBuf); 29350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert header.order(ByteOrder.LITTLE_ENDIAN); 29450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert 29550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert header.put(new byte[]{ 'R', 'I', 'F', 'F' }); 29671e0b4807797c602e7fc787d00d27c4f9c92a507Bjorn Bringert header.putInt(dataLength + WAV_HEADER_LENGTH - 8); // RIFF chunk size 29750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert header.put(new byte[]{ 'W', 'A', 'V', 'E' }); 29850e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert header.put(new byte[]{ 'f', 'm', 't', ' ' }); 29950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert header.putInt(16); // size of fmt chunk 30050e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert header.putShort(WAV_FORMAT_PCM); 30150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert header.putShort((short) channelCount); 30250e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert header.putInt(sampleRateInHz); 30350e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert header.putInt(byteRate); 30450e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert header.putShort(blockAlign); 30550e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert header.putShort(bitsPerSample); 30650e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert header.put(new byte[]{ 'd', 'a', 't', 'a' }); 30750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert header.putInt(dataLength); 3085acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniak header.flip(); 30950e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert 3105acb33af357b56fffb055997718b1e4aa97f53fcPrzemyslaw Szczepaniak return header; 31150e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert } 3125d0ea0fe210db85a8a8a44c63d8fef195e206abbNiels Egberts 3135d0ea0fe210db85a8a8a44c63d8fef195e206abbNiels Egberts @Override 3145d0ea0fe210db85a8a8a44c63d8fef195e206abbNiels Egberts public void rangeStart(int markerInFrames, int start, int end) { 3155d0ea0fe210db85a8a8a44c63d8fef195e206abbNiels Egberts mDispatcher.dispatchOnRangeStart(markerInFrames, start, end); 3165d0ea0fe210db85a8a8a44c63d8fef195e206abbNiels Egberts } 31750e657bb2d005568f5dd8bc1d904d07b0d94018fBjorn Bringert} 318