]> git.basschouten.com Git - openhab-addons.git/blob
61cd026529df35f01f84374fdeb49697fe6b864b
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.voice.marytts.internal;
14
15 import java.io.ByteArrayInputStream;
16 import java.io.IOException;
17 import java.io.InputStream;
18 import java.io.SequenceInputStream;
19
20 import javax.sound.sampled.AudioInputStream;
21
22 import org.apache.commons.io.IOUtils;
23 import org.openhab.core.audio.AudioException;
24 import org.openhab.core.audio.AudioFormat;
25 import org.openhab.core.audio.AudioSource;
26 import org.openhab.core.audio.FixedLengthAudioStream;
27
28 /**
29  * Implementation of the {@link AudioSource} interface for the {@link MaryTTSService}
30  *
31  * @author Kelly Davis - Initial contribution and API
32  * @author Kai Kreuzer - Refactored to updated APIs and moved to openHAB
33  */
34 class MaryTTSAudioStream extends FixedLengthAudioStream {
35
36     /**
37      * {@link AudioFormat} of this {@link AudioSource}
38      */
39     private final AudioFormat audioFormat;
40
41     /**
42      * {@link InputStream} of this {@link AudioSource}
43      */
44     private InputStream inputStream;
45
46     private final byte[] rawAudio;
47     private final int length;
48
49     /**
50      * Constructs an instance with the passed properties
51      *
52      * @param inputStream The InputStream of this instance
53      * @param audioFormat The AudioFormat of this instance
54      * @throws IOException
55      */
56     public MaryTTSAudioStream(AudioInputStream inputStream, AudioFormat audioFormat) throws IOException {
57         rawAudio = IOUtils.toByteArray(inputStream);
58         this.length = rawAudio.length + 36;
59         this.inputStream = new SequenceInputStream(getWavHeaderInputStream(length), new ByteArrayInputStream(rawAudio));
60         this.audioFormat = audioFormat;
61     }
62
63     @Override
64     public AudioFormat getFormat() {
65         return this.audioFormat;
66     }
67
68     @Override
69     public int read(byte[] b) throws IOException {
70         return inputStream.read(b, 0, b.length);
71     }
72
73     @Override
74     public int read(byte[] b, int off, int len) throws IOException {
75         return inputStream.read(b, off, len);
76     }
77
78     @Override
79     public int read() throws IOException {
80         return inputStream.read();
81     }
82
83     @Override
84     public long length() {
85         return length;
86     }
87
88     private InputStream getWavHeaderInputStream(int length) throws IOException {
89         // WAVE header
90         // see http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
91         byte[] header = new byte[44];
92
93         byte format = 0x10; // PCM
94         byte bits = 16;
95         byte channel = 1;
96         long srate = (this.audioFormat != null) ? this.audioFormat.getFrequency() : 48000l;
97         long rawLength = length - 36;
98         long bitrate = srate * channel * bits;
99
100         header[0] = 'R';
101         header[1] = 'I';
102         header[2] = 'F';
103         header[3] = 'F';
104         header[4] = (byte) (length & 0xff);
105         header[5] = (byte) ((length >> 8) & 0xff);
106         header[6] = (byte) ((length >> 16) & 0xff);
107         header[7] = (byte) ((length >> 24) & 0xff);
108         header[8] = 'W';
109         header[9] = 'A';
110         header[10] = 'V';
111         header[11] = 'E';
112         header[12] = 'f';
113         header[13] = 'm';
114         header[14] = 't';
115         header[15] = ' ';
116         header[16] = format;
117         header[17] = 0;
118         header[18] = 0;
119         header[19] = 0;
120         header[20] = 1;
121         header[21] = 0;
122         header[22] = channel;
123         header[23] = 0;
124         header[24] = (byte) (srate & 0xff);
125         header[25] = (byte) ((srate >> 8) & 0xff);
126         header[26] = (byte) ((srate >> 16) & 0xff);
127         header[27] = (byte) ((srate >> 24) & 0xff);
128         header[28] = (byte) ((bitrate / 8) & 0xff);
129         header[29] = (byte) (((bitrate / 8) >> 8) & 0xff);
130         header[30] = (byte) (((bitrate / 8) >> 16) & 0xff);
131         header[31] = (byte) (((bitrate / 8) >> 24) & 0xff);
132         header[32] = (byte) ((channel * bits) / 8);
133         header[33] = 0;
134         header[34] = 16;
135         header[35] = 0;
136         header[36] = 'd';
137         header[37] = 'a';
138         header[38] = 't';
139         header[39] = 'a';
140         header[40] = (byte) (rawLength & 0xff);
141         header[41] = (byte) ((rawLength >> 8) & 0xff);
142         header[42] = (byte) ((rawLength >> 16) & 0xff);
143         header[43] = (byte) ((rawLength >> 24) & 0xff);
144         return new ByteArrayInputStream(header);
145     }
146
147     @Override
148     public synchronized void reset() throws IOException {
149         IOUtils.closeQuietly(inputStream);
150         this.inputStream = new SequenceInputStream(getWavHeaderInputStream(length), new ByteArrayInputStream(rawAudio));
151     }
152
153     @Override
154     public InputStream getClonedStream() throws AudioException {
155         try {
156             return new SequenceInputStream(getWavHeaderInputStream(length), new ByteArrayInputStream(rawAudio));
157         } catch (IOException e) {
158             throw new AudioException(e);
159         }
160     }
161 }