2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.voice.marytts.internal;
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;
21 import javax.sound.sampled.AudioInputStream;
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;
29 * Implementation of the {@link AudioSource} interface for the {@link MaryTTSService}
31 * @author Kelly Davis - Initial contribution and API
32 * @author Kai Kreuzer - Refactored to updated APIs and moved to openHAB
34 class MaryTTSAudioStream extends FixedLengthAudioStream {
37 * {@link AudioFormat} of this {@link AudioSource}
39 private final AudioFormat audioFormat;
42 * {@link InputStream} of this {@link AudioSource}
44 private InputStream inputStream;
46 private final byte[] rawAudio;
47 private final int length;
50 * Constructs an instance with the passed properties
52 * @param inputStream The InputStream of this instance
53 * @param audioFormat The AudioFormat of this instance
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));
64 private byte[] inputStreamToBytes(InputStream inputStream) throws IOException {
65 ByteArrayOutputStream output = new ByteArrayOutputStream();
67 byte[] buffer = new byte[4096];
68 while (-1 != (n = inputStream.read(buffer))) {
69 output.write(buffer, 0, n);
71 return output.toByteArray();
75 public AudioFormat getFormat() {
76 return this.audioFormat;
80 public int read(byte[] b) throws IOException {
81 return inputStream.read(b, 0, b.length);
85 public int read(byte[] b, int off, int len) throws IOException {
86 return inputStream.read(b, off, len);
90 public int read() throws IOException {
91 return inputStream.read();
95 public long length() {
99 private InputStream getWavHeaderInputStream(int length) throws IOException {
101 // see http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
102 byte[] header = new byte[44];
104 byte format = 0x10; // PCM
107 long srate = (this.audioFormat != null) ? this.audioFormat.getFrequency() : 48000l;
108 long rawLength = length - 36;
109 long bitrate = srate * channel * bits;
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);
133 header[22] = channel;
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);
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);
159 public synchronized void reset() throws IOException {
162 } catch (IOException e) {
164 this.inputStream = new SequenceInputStream(getWavHeaderInputStream(length), new ByteArrayInputStream(rawAudio));
168 public InputStream getClonedStream() throws AudioException {
170 return new SequenceInputStream(getWavHeaderInputStream(length), new ByteArrayInputStream(rawAudio));
171 } catch (IOException e) {
172 throw new AudioException(e);