1 package org.daisy.pipeline.tts.calabash.impl;
2
3 import java.io.File;
4 import java.util.Map;
5 import java.util.concurrent.BlockingQueue;
6
7 import javax.sound.sampled.AudioFileFormat;
8 import javax.sound.sampled.AudioInputStream;
9
10 import org.daisy.common.messaging.MessageAppender;
11 import org.daisy.common.messaging.MessageBuilder;
12 import org.daisy.pipeline.audio.AudioClip;
13 import org.daisy.pipeline.audio.AudioEncoder;
14 import org.daisy.pipeline.audio.AudioServices;
15 import org.daisy.pipeline.tts.AudioFootprintMonitor;
16 import org.daisy.pipeline.tts.TTSLog;
17 import org.daisy.pipeline.tts.TTSTimeout;
18 import org.daisy.pipeline.tts.TTSLog.ErrorCode;
19
20
21
22
23
24 public class EncodingThread {
25
26 class EncodingException extends RuntimeException {
27 public EncodingException(String message, Throwable cause) {
28 super(message, cause);
29 }
30 public EncodingException(Throwable t) {
31 super(t);
32 }
33 public EncodingException(String message) {
34 super(message);
35 }
36 }
37
38 private Thread mThread;
39 private Throwable criticalError;
40
41 void start(final AudioFileFormat.Type fileType, final AudioServices encoderRegistry,
42 final BlockingQueue<ContiguousPCM> inputPCM, final AudioFootprintMonitor audioFootprintMonitor,
43 Map<String, String> TTSproperties, final TTSLog ttslog, MessageAppender messageAppender) {
44
45
46
47 float encodingSpeed = 2.0f;
48 String speedProp = "org.daisy.pipeline.tts.encoding.speed";
49 String speedParam = TTSproperties.get(speedProp);
50 if (speedParam != null) {
51 try {
52 encodingSpeed = Float.valueOf(speedParam);
53 } catch (NumberFormatException e) {
54 ttslog.addGeneralError(
55 ErrorCode.WARNING,
56 "wrong format for property " + speedProp + ". A float is expected, not " + speedParam,
57 e);
58 }
59 }
60
61
62
63 AudioEncoder encoder = encoderRegistry.newEncoder(fileType, TTSproperties).orElse(null);
64 if (encoder == null) {
65 ttslog.addGeneralError(ErrorCode.CRITICAL_ERROR, "No audio encoder found");
66 }
67
68 final AudioEncoder fencoder = encoder;
69 final float fEncodingSpeed = encodingSpeed;
70 final TTSTimeout timeout = new TTSTimeout();
71
72 mThread = new Thread() {
73 @Override
74 public void run() {
75
76
77 MessageAppender messageThread = messageAppender != null
78 ? messageAppender.append(new MessageBuilder())
79 : null;
80 try {
81 while (!interrupted()) {
82 ContiguousPCM job;
83 try {
84 job = inputPCM.take();
85 } catch (InterruptedException e) {
86 ttslog.addGeneralError(
87 ErrorCode.CRITICAL_ERROR, "encoding thread has been interrupted");
88 break;
89 }
90 if (job.isEndOfQueue()) {
91
92 break;
93 }
94 int jobSize = job.sizeInBytes();
95
96 if (fencoder != null) {
97 AudioInputStream audio = job.getAudio();
98 float secs = jobSize / audio.getFormat().getFrameRate();
99 int maxTime = (int) (1.0 + secs / fEncodingSpeed);
100 timeout.enableForCurrentThread(maxTime);
101 try {
102 File encodedFile = new File(
103 job.getDestinationDirectory(),
104 job.getDestinationFilePrefix() + "." + fileType.getExtension());
105 AudioClip clip = fencoder.encode(
106 audio,
107 fileType,
108 encodedFile);
109 audio.close();
110 job.getDestinationFile().set(clip);
111 } catch (InterruptedException e) {
112 ttslog.addGeneralError(
113 ErrorCode.CRITICAL_ERROR,
114 "timeout while encoding audio to " + job.getDestinationFilePrefix(),
115 e);
116 audioFootprintMonitor.releaseEncodersMemory(jobSize);
117 throw new EncodingException(e);
118 } catch (Throwable t) {
119 ttslog.addGeneralError(
120 ErrorCode.CRITICAL_ERROR,
121 "error while encoding audio to " + job.getDestinationFilePrefix(),
122 t);
123 audioFootprintMonitor.releaseEncodersMemory(jobSize);
124 throw new EncodingException(t);
125 } finally {
126 timeout.disable();
127 }
128 }
129 audioFootprintMonitor.releaseEncodersMemory(jobSize);
130 }
131 } finally {
132 timeout.close();
133 if (messageThread != null)
134 messageThread.close();
135 }
136 }
137 };
138 mThread.setUncaughtExceptionHandler(
139 (thread, throwable) -> { criticalError = throwable; }
140 );
141 mThread.start();
142 }
143
144 void waitToFinish() throws EncodingException {
145 try {
146 mThread.join();
147 } catch (InterruptedException e) {
148 throw new RuntimeException();
149 }
150 if (criticalError != null) {
151 if (criticalError instanceof EncodingException)
152 throw (EncodingException)criticalError;
153 else
154 throw new RuntimeException("coding error", criticalError);
155 }
156 }
157 }