]> git.basschouten.com Git - openhab-addons.git/blob
5c7070120425bcac5980ba918c65cffe7c4bbe7d
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.ByteArrayOutputStream;
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.io.SequenceInputStream;
20
21 import javax.sound.sampled.AudioInputStream;
22
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         // The length of an AudioInputStream is expressed in sample frames, not bytes so readAllBytes() cannot be used.
58         rawAudio = inputStreamToBytes(inputStream);
59         this.length = rawAudio.length + 36;
60         this.audioFormat = audioFormat;
61         this.inputStream = new SequenceInputStream(getWavHeaderInputStream(length), new ByteArrayInputStream(rawAudio));
62     }
63
64     private byte[] inputStreamToBytes(InputStream inputStream) throws IOException {
65         ByteArrayOutputStream output = new ByteArrayOutputStream();
66         int n = 0;
67         byte[] buffer = new byte[4096];
68         while (-1 != (n = inputStream.read(buffer))) {
69             output.write(buffer, 0, n);
70         }
71         return output.toByteArray();
72     }
73
74     @Override
75     public AudioFormat getFormat() {
76         return this.audioFormat;
77     }
78
79     @Override
80     public int read(byte[] b) throws IOException {
81         return inputStream.read(b, 0, b.length);
82     }
83
84     @Override
85     public int read(byte[] b, int off, int len) throws IOException {
86         return inputStream.read(b, off, len);
87     }
88
89     @Override
90     public int read() throws IOException {
91         return inputStream.read();
92     }
93
94     @Override
95     public long length() {
96         return length;
97     }
98
99     private InputStream getWavHeaderInputStream(int length) throws IOException {
100         // WAVE header
101         // see http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
102         byte[] header = new byte[44];
103
104         byte format = 0x10; // PCM
105         byte bits = 16;
106         byte channel = 1;
107         long srate = (this.audioFormat != null) ? this.audioFormat.getFrequency() : 48000l;
108         long rawLength = length - 36;
109         long bitrate = srate * channel * bits;
110
111         header[0] = 'R';
112         header[1] = 'I';
113         header[2] = 'F';
114         header[3] = 'F';
115         header[4] = (byte) (length & 0xff);
116         header[5] = (byte) ((length >> 8) & 0xff);
117         header[6] = (byte) ((length >> 16) & 0xff);
118         header[7] = (byte) ((length >> 24) & 0xff);
119         header[8] = 'W';
120         header[9] = 'A';
121         header[10] = 'V';
122         header[11] = 'E';
123         header[12] = 'f';
124         header[13] = 'm';
125         header[14] = 't';
126         header[15] = ' ';
127         header[16] = format;
128         header[17] = 0;
129         header[18] = 0;
130         header[19] = 0;
131         header[20] = 1;
132         header[21] = 0;
133         header[22] = channel;
134         header[23] = 0;
135         header[24] = (byte) (srate & 0xff);
136         header[25] = (byte) ((srate >> 8) & 0xff);
137         header[26] = (byte) ((srate >> 16) & 0xff);
138         header[27] = (byte) ((srate >> 24) & 0xff);
139         header[28] = (byte) ((bitrate / 8) & 0xff);
140         header[29] = (byte) (((bitrate / 8) >> 8) & 0xff);
141         header[30] = (byte) (((bitrate / 8) >> 16) & 0xff);
142         header[31] = (byte) (((bitrate / 8) >> 24) & 0xff);
143         header[32] = (byte) ((channel * bits) / 8);
144         header[33] = 0;
145         header[34] = 16;
146         header[35] = 0;
147         header[36] = 'd';
148         header[37] = 'a';
149         header[38] = 't';
150         header[39] = 'a';
151         header[40] = (byte) (rawLength & 0xff);
152         header[41] = (byte) ((rawLength >> 8) & 0xff);
153         header[42] = (byte) ((rawLength >> 16) & 0xff);
154         header[43] = (byte) ((rawLength >> 24) & 0xff);
155         return new ByteArrayInputStream(header);
156     }
157
158     @Override
159     public synchronized void reset() throws IOException {
160         try {
161             inputStream.close();
162         } catch (IOException e) {
163         }
164         this.inputStream = new SequenceInputStream(getWavHeaderInputStream(length), new ByteArrayInputStream(rawAudio));
165     }
166
167     @Override
168     public InputStream getClonedStream() throws AudioException {
169         try {
170             return new SequenceInputStream(getWavHeaderInputStream(length), new ByteArrayInputStream(rawAudio));
171         } catch (IOException e) {
172             throw new AudioException(e);
173         }
174     }
175 }