1   package org.daisy.pipeline.tts;
2   
3   import java.util.concurrent.Semaphore;
4   
5   import javax.sound.sampled.AudioInputStream;
6   
7   import org.slf4j.Logger;
8   import org.slf4j.LoggerFactory;
9   
10  /**
11   * AudioFootprintMonitor monitors the footprint of the audio data created by the
12   * TTS processors. The audio data goes through two virtual memory spaces: a
13   * space for the TTS and a space for the encoding. The former is bigger and
14   * throws exceptions if there is no space left, rather than possibly deadlocking
15   * on blocking requests. The latter can afford to block on the requests, since
16   * if the space is full, it will be consumed by the encoding threads.
17   */
18  public class AudioFootprintMonitor {
19  
20  	public class MemoryException extends Exception {
21  		public MemoryException(String message) {
22  			super(message);
23  		}
24  
25  		public MemoryException(int bytes) {
26  			super("tried to allocate " + bytes + " bytes (" + bytes / 1000000.0 + "MB)");
27  		}
28  	}
29  
30  	private Logger ServerLogger = LoggerFactory.getLogger(AudioFootprintMonitor.class);
31  
32  	public AudioFootprintMonitor() {
33  		String maxMemProp = System.getProperty("org.daisy.pipeline.tts.maxmem");
34  		long maxMem;
35  		if (maxMemProp != null) {
36  			maxMem = Long.valueOf(maxMemProp) * 1048576;
37  		} else {
38  			maxMem = Runtime.getRuntime().maxMemory();
39  			if (maxMem == Long.MAX_VALUE)
40  				maxMem = 500 * 1048576; //500 MB
41  			else
42  				maxMem /= 2; //50% of the total memory
43  		}
44  		if (maxMem > Integer.MAX_VALUE)
45  			maxMem = Integer.MAX_VALUE;
46  
47  		mSpaceForTTS = (int) (2 * maxMem / 3);
48  		mSpaceForEncoding = (int) (maxMem - mSpaceForTTS);
49  
50  		//the padding prevents big-size acquire() from blocking the small ones
51  		mPaddedEncodingSpace = mSpaceForEncoding - mSpaceForEncoding / 10;
52  
53  		mTTSCounter = new Semaphore(mSpaceForTTS, false);
54  		mEncodingCounter = new Semaphore(mSpaceForEncoding, false);
55  	}
56  
57  	public void acquireTTSMemory(AudioInputStream audio) throws MemoryException {
58  		acquireTTSMemory(getFootprint(audio));
59  	}
60  
61  	public void acquireTTSMemory(int size) throws MemoryException {
62  		if (!mTTSCounter.tryAcquire(size))
63  			throw new MemoryException(size);
64  	}
65  
66  	/**
67  	 * Must not be used by the encoders. They should use releaseEncodersMemory()
68  	 * instead.
69  	 */
70  	public void releaseTTSMemory(AudioInputStream audio) {
71  		releaseTTSMemory(getFootprint(audio));
72  	}
73  
74  	public void releaseTTSMemory(int size) {
75  		mTTSCounter.release(size);
76  	}
77  
78  	public int getSpaceForTTS() {
79  		return mSpaceForTTS;
80  	}
81  
82  	public int getUnreleasedTTSMem() {
83  		return mSpaceForTTS - mTTSCounter.availablePermits();
84  	}
85  
86  	/**
87  	 * Transfer memory from the TTS area to the encoding area. It blocks until
88  	 * there is enough space in the encoding area.
89  	 */
90  	public void transferToEncoding(int ttsMemSize, int encodingMemSize)
91  	        throws InterruptedException {
92  		acquireEncodersMemory(encodingMemSize);
93  		mTTSCounter.release(ttsMemSize);
94  	}
95  
96  	public void acquireEncodersMemory(int size) throws InterruptedException {
97  		if (size > mPaddedEncodingSpace) {
98  			ServerLogger.debug("try to acquire " + size + " bytes, exceeding the max size = "
99  			        + mPaddedEncodingSpace + "MB");
100 			mEncodingCounter.acquire(mPaddedEncodingSpace);
101 		} else
102 			mEncodingCounter.acquire(size);
103 	}
104 
105 	public void releaseEncodersMemory(int size) {
106 		if (size > mPaddedEncodingSpace)
107 			mEncodingCounter.release(mPaddedEncodingSpace);
108 		else
109 			mEncodingCounter.release(size);
110 	}
111 
112 	public int getSpaceForEncoding() {
113 		return mSpaceForEncoding;
114 	}
115 
116 	public int getUnreleasedEncondingMem() {
117 		return mSpaceForEncoding - mEncodingCounter.availablePermits();
118 	}
119 
120 	private int mPaddedEncodingSpace;
121 	private int mSpaceForTTS;
122 	private int mSpaceForEncoding;
123 	private Semaphore mTTSCounter;
124 	private Semaphore mEncodingCounter;
125 
126 	/**
127 	 * Get length of AudioInputStream in number of bytes
128 	 */
129 	public static int getFootprint(AudioInputStream audio) {
130 		return Math.multiplyExact(Math.toIntExact(audio.getFrameLength()),
131                                   audio.getFormat().getFrameSize());
132 	}
133 }