1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16package android.speech.tts;
17
18import android.media.AudioFormat;
19import android.media.AudioTrack;
20import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
21
22import java.util.LinkedList;
23
24/**
25 * Params required to play back a synthesis request.
26 */
27final class SynthesisMessageParams extends MessageParams {
28    private static final long MAX_UNCONSUMED_AUDIO_MS = 500;
29
30    final int mStreamType;
31    final int mSampleRateInHz;
32    final int mAudioFormat;
33    final int mChannelCount;
34    final float mVolume;
35    final float mPan;
36    final EventLogger mLogger;
37
38    final int mBytesPerFrame;
39
40    volatile AudioTrack mAudioTrack;
41    // Written by the synthesis thread, but read on the audio playback
42    // thread.
43    volatile int mBytesWritten;
44    // A "short utterance" is one that uses less bytes than the audio
45    // track buffer size (mAudioBufferSize). In this case, we need to call
46    // AudioTrack#stop() to send pending buffers to the mixer, and slightly
47    // different logic is required to wait for the track to finish.
48    //
49    // Not volatile, accessed only from the audio playback thread.
50    boolean mIsShortUtterance;
51    int mAudioBufferSize;
52    // Always synchronized on "this".
53    int mUnconsumedBytes;
54    volatile boolean mIsError;
55
56    private final LinkedList<ListEntry> mDataBufferList = new LinkedList<ListEntry>();
57
58    SynthesisMessageParams(int streamType, int sampleRate,
59            int audioFormat, int channelCount,
60            float volume, float pan, UtteranceProgressDispatcher dispatcher,
61            String callingApp, EventLogger logger) {
62        super(dispatcher, callingApp);
63
64        mStreamType = streamType;
65        mSampleRateInHz = sampleRate;
66        mAudioFormat = audioFormat;
67        mChannelCount = channelCount;
68        mVolume = volume;
69        mPan = pan;
70        mLogger = logger;
71
72        mBytesPerFrame = getBytesPerFrame(mAudioFormat) * mChannelCount;
73
74        // initially null.
75        mAudioTrack = null;
76        mBytesWritten = 0;
77        mAudioBufferSize = 0;
78        mIsError = false;
79    }
80
81    @Override
82    int getType() {
83        return TYPE_SYNTHESIS;
84    }
85
86    synchronized void addBuffer(byte[] buffer) {
87        long unconsumedAudioMs = 0;
88
89        while ((unconsumedAudioMs = getUnconsumedAudioLengthMs()) > MAX_UNCONSUMED_AUDIO_MS) {
90            try {
91                wait();
92            } catch (InterruptedException ie) {
93                return;
94            }
95        }
96
97        mDataBufferList.add(new ListEntry(buffer));
98        mUnconsumedBytes += buffer.length;
99    }
100
101    synchronized void clearBuffers() {
102        mDataBufferList.clear();
103        mUnconsumedBytes = 0;
104        notifyAll();
105    }
106
107    synchronized ListEntry getNextBuffer() {
108        ListEntry entry = mDataBufferList.poll();
109        if (entry != null) {
110            mUnconsumedBytes -= entry.mBytes.length;
111            notifyAll();
112        }
113
114        return entry;
115    }
116
117    void setAudioTrack(AudioTrack audioTrack) {
118        mAudioTrack = audioTrack;
119    }
120
121    AudioTrack getAudioTrack() {
122        return mAudioTrack;
123    }
124
125    void setIsError(boolean isError) {
126        mIsError = isError;
127    }
128
129    boolean isError() {
130        return mIsError;
131    }
132
133    // Must be called synchronized on this.
134    private long getUnconsumedAudioLengthMs() {
135        final int unconsumedFrames = mUnconsumedBytes / mBytesPerFrame;
136        final long estimatedTimeMs = unconsumedFrames * 1000 / mSampleRateInHz;
137
138        return estimatedTimeMs;
139    }
140
141    private static int getBytesPerFrame(int audioFormat) {
142        if (audioFormat == AudioFormat.ENCODING_PCM_8BIT) {
143            return 1;
144        } else if (audioFormat == AudioFormat.ENCODING_PCM_16BIT) {
145            return 2;
146        }
147
148        return -1;
149    }
150
151    static final class ListEntry {
152        final byte[] mBytes;
153
154        ListEntry(byte[] bytes) {
155            mBytes = bytes;
156        }
157    }
158}
159
160