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