PageRenderTime 26ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/servers/media/resources/recorder/src/main/java/org/mobicents/media/server/impl/resource/audio/AudioRecorderImpl.java

http://mobicents.googlecode.com/
Java | 510 lines | 277 code | 93 blank | 140 comment | 18 complexity | a246f8548fbde4e21669b701043496b2 MD5 | raw file
Possible License(s): LGPL-3.0, GPL-3.0, LGPL-2.1, GPL-2.0, CC-BY-SA-3.0, CC0-1.0, Apache-2.0, BSD-3-Clause
  1. /*
  2. * JBoss, Home of Professional Open Source
  3. * Copyright 2011, Red Hat, Inc. and individual contributors
  4. * by the @authors tag. See the copyright.txt in the distribution for a
  5. * full listing of individual contributors.
  6. *
  7. * This is free software; you can redistribute it and/or modify it
  8. * under the terms of the GNU Lesser General Public License as
  9. * published by the Free Software Foundation; either version 2.1 of
  10. * the License, or (at your option) any later version.
  11. *
  12. * This software is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public
  18. * License along with this software; if not, write to the Free
  19. * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
  20. * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
  21. */
  22. package org.mobicents.media.server.impl.resource.audio;
  23. import java.io.File;
  24. import java.io.FileInputStream;
  25. import java.io.FileOutputStream;
  26. import java.io.IOException;
  27. import java.io.InputStream;
  28. import java.io.OutputStream;
  29. import org.mobicents.media.server.impl.AbstractSink;
  30. import org.mobicents.media.server.scheduler.Scheduler;
  31. import org.mobicents.media.server.scheduler.Task;
  32. import org.mobicents.media.server.spi.format.AudioFormat;
  33. import org.mobicents.media.server.spi.format.FormatFactory;
  34. import org.mobicents.media.server.spi.format.Formats;
  35. import org.mobicents.media.server.spi.listener.Listeners;
  36. import org.mobicents.media.server.spi.listener.TooManyListenersException;
  37. import org.mobicents.media.server.spi.memory.Frame;
  38. import org.mobicents.media.server.spi.recorder.Recorder;
  39. import org.mobicents.media.server.spi.recorder.RecorderEvent;
  40. import org.mobicents.media.server.spi.recorder.RecorderListener;
  41. /**
  42. *
  43. * @author kulikov
  44. */
  45. public class AudioRecorderImpl extends AbstractSink implements Recorder {
  46. private final static AudioFormat LINEAR = FormatFactory.createAudioFormat("linear", 8000, 16, 1);
  47. private final static Formats formats = new Formats();
  48. private final static int SILENCE_LEVEL = 10;
  49. static {
  50. formats.add(LINEAR);
  51. }
  52. private String recordDir;
  53. private FileOutputStream fout;
  54. //file for recording
  55. private File file;
  56. //temp file for raw data
  57. private File temp;
  58. //if set ti true the record will terminate recording when silence detected
  59. private long postSpeechTimer = -1L;
  60. //total non-interraptible silence time
  61. private long silence;
  62. //samples
  63. private byte[] data;
  64. private int offset;
  65. private int len;
  66. private KillRecording killRecording;
  67. private Scheduler scheduler;
  68. //maximum recrding time. -1 means until stopped.
  69. private long maxRecordTime = -1;
  70. //length in time of recording
  71. private long time;
  72. //listener
  73. private Listeners<RecorderListener> listeners = new Listeners();
  74. //events
  75. private RecorderEventImpl recorderStarted;
  76. private RecorderEventImpl recorderStopped;
  77. private RecorderEventImpl recorderFailed;
  78. //event sender task
  79. private EventSender eventSender;
  80. //event qualifier
  81. private int qualifier;
  82. public AudioRecorderImpl(Scheduler scheduler) {
  83. super("recorder", scheduler);
  84. this.scheduler = scheduler;
  85. killRecording = new KillRecording(scheduler);
  86. //initialize events
  87. recorderStarted = new RecorderEventImpl(RecorderEvent.START, this);
  88. recorderStopped = new RecorderEventImpl(RecorderEvent.STOP, this);
  89. recorderFailed = new RecorderEventImpl(RecorderEvent.FAILED, this);
  90. //initialize event sender task
  91. eventSender = new EventSender(scheduler);
  92. }
  93. @Override
  94. public void start() {
  95. super.start();
  96. //send event
  97. fireEvent(recorderStarted);
  98. }
  99. @Override
  100. public void stop() {
  101. if (!this.isStarted()) {
  102. return;
  103. }
  104. super.stop();
  105. this.maxRecordTime = -1;
  106. this.time = 0;
  107. try {
  108. writeToWaveFile();
  109. } catch (IOException e) {
  110. }
  111. //send event
  112. recorderStopped.setQualifier(qualifier);
  113. fireEvent(recorderStopped);
  114. //clean qualifier
  115. this.qualifier = 0;
  116. this.maxRecordTime = -1L;
  117. this.postSpeechTimer = -1L;
  118. }
  119. /**
  120. * (Non Java-doc.)
  121. *
  122. * @see org.mobicents.media.server.spi.resource.Recorder;
  123. */
  124. public void setPostSpeechTimer(long value) {
  125. this.postSpeechTimer = value;
  126. }
  127. /**
  128. * (Non Java-doc.)
  129. *
  130. * @see org.mobicents.media.server.spi.resource.Recorder;
  131. */
  132. public void setMaxRecordTime(long maxRecordTime) {
  133. this.maxRecordTime = maxRecordTime;
  134. }
  135. /**
  136. * Fires specified event
  137. *
  138. * @param event the event to fire.
  139. */
  140. private void fireEvent(RecorderEventImpl event) {
  141. eventSender.setDeadLine(scheduler.getClock().getTime() + 1L);
  142. eventSender.event = event;
  143. scheduler.submit(eventSender);
  144. }
  145. @Override
  146. public void onMediaTransfer(Frame frame) throws IOException {
  147. //update time
  148. time += frame.getDuration();
  149. //extract data
  150. data = frame.getData();
  151. offset = frame.getOffset();
  152. len = frame.getLength();
  153. //write raw data as integer
  154. for (int i = offset + 1; i < len; i+= 2) {
  155. fout.write(data[i - 1]);
  156. fout.write(data[i]);
  157. }
  158. if (this.postSpeechTimer > 0) {
  159. //detecting silence
  160. if (this.checkForSilence(data, offset, len)) {
  161. //silence frame detected, update selence total time
  162. this.silence += frame.getDuration();
  163. //check that silence does not exceed the limit yet
  164. if (this.silence > postSpeechTimer) {
  165. this.qualifier = RecorderEvent.NO_SPEECH;
  166. killRecording.setDeadLine(killRecording.scheduler().getClock().getTime() + 1L);
  167. scheduler.submit(killRecording);
  168. }
  169. } else {
  170. //reset silence time
  171. this.silence = 0;
  172. }
  173. }
  174. //check max time and stop recording if exeeds limit
  175. if (this.maxRecordTime > 0 && time >= this.maxRecordTime) {
  176. //set qualifier
  177. this.qualifier = RecorderEvent.MAX_DURATION_EXCEEDED;
  178. killRecording.setDeadLine(killRecording.scheduler().getClock().getTime() + 1L);
  179. scheduler.submit(killRecording);
  180. }
  181. }
  182. @Override
  183. public Formats getNativeFormats() {
  184. return formats;
  185. }
  186. public void setRecordDir(String recordDir) {
  187. this.recordDir = recordDir;
  188. }
  189. public void setRecordFile(String uri, boolean append) throws IOException {
  190. //calculate the full path
  191. String path = uri.startsWith("file:") ? uri.replaceAll("file://", "") :
  192. this.recordDir + "/" + uri;
  193. //create file for recording and temp file
  194. file = new File(path);
  195. temp = new File(path + "~");
  196. //open stream to temporary file
  197. fout = new FileOutputStream(temp);
  198. //if append specified and file really exist copy data from the current
  199. //file to temp
  200. if (append && file.exists()) {
  201. System.out.println("..............>>>>>Copying samples from " + file);
  202. copySamples(file, fout);
  203. }
  204. }
  205. /**
  206. * Writes samples to file following WAVE format.
  207. *
  208. * @throws IOException
  209. */
  210. private void writeToWaveFile() throws IOException {
  211. System.out.println("!!!!!!!!!! Writting to file......................") ;
  212. //stop called on inactive recorder
  213. if (fout == null) {
  214. return;
  215. }
  216. fout.flush();
  217. fout.close();
  218. FileInputStream fin = new FileInputStream(temp);
  219. fout = new FileOutputStream(file);
  220. int size = fin.available();
  221. System.out.println("!!!!!!!!!! Size=" + size) ;
  222. //RIFF
  223. fout.write((byte) 0x52);
  224. fout.write((byte) 0x49);
  225. fout.write((byte) 0x46);
  226. fout.write((byte) 0x46);
  227. int length = size + 36;
  228. //Length
  229. fout.write((byte) (length));
  230. fout.write((byte) (length >> 8));
  231. fout.write((byte) (length >> 16));
  232. fout.write((byte) (length >> 24));
  233. //WAVE
  234. fout.write((byte) 0x57);
  235. fout.write((byte) 0x41);
  236. fout.write((byte) 0x56);
  237. fout.write((byte) 0x45);
  238. //fmt
  239. fout.write((byte) 0x66);
  240. fout.write((byte) 0x6d);
  241. fout.write((byte) 0x74);
  242. fout.write((byte) 0x20);
  243. fout.write((byte) 0x10);
  244. fout.write((byte) 0x00);
  245. fout.write((byte) 0x00);
  246. fout.write((byte) 0x00);
  247. //format - PCM
  248. fout.write((byte) 0x01);
  249. fout.write((byte) 0x00);
  250. //format - MONO
  251. fout.write((byte) 0x01);
  252. fout.write((byte) 0x00);
  253. //sample rate:8000
  254. fout.write((byte) 0x40);
  255. fout.write((byte) 0x1F);
  256. fout.write((byte) 0x00);
  257. fout.write((byte) 0x00);
  258. //byte rate
  259. fout.write((byte) 0x80);
  260. fout.write((byte) 0x3E);
  261. fout.write((byte) 0x00);
  262. fout.write((byte) 0x00);
  263. //Block align
  264. fout.write((byte) 0x02);
  265. fout.write((byte) 0x00);
  266. //Bits per sample: 16
  267. fout.write((byte) 0x10);
  268. fout.write((byte) 0x00);
  269. //"data"
  270. fout.write((byte) 0x64);
  271. fout.write((byte) 0x61);
  272. fout.write((byte) 0x74);
  273. fout.write((byte) 0x61);
  274. //len
  275. fout.write((byte) (size));
  276. fout.write((byte) (size >> 8));
  277. fout.write((byte) (size >> 16));
  278. fout.write((byte) (size >> 24));
  279. copyData(fin, 0, fout);
  280. fout.flush();
  281. fout.close();
  282. fin.close();
  283. temp.delete();
  284. }
  285. /**
  286. * Copies samples from source wav file to temporary raw destination.
  287. *
  288. * @param src wav source file
  289. * @param dst raw destination file.
  290. */
  291. private void copySamples(File src, FileOutputStream out) throws IOException {
  292. FileInputStream in = new FileInputStream(src);
  293. try {
  294. this.copyData(in, 44, out);
  295. } finally {
  296. in.close();
  297. }
  298. }
  299. /**
  300. * Copies data from specified input to specified destination.
  301. *
  302. * @param in the input of data
  303. * @param offset the first position of data to read
  304. * @param out destination
  305. * @throws IOException
  306. */
  307. private void copyData(InputStream in, int offset, OutputStream out) throws IOException {
  308. //skip header
  309. in.skip(offset);
  310. //copy samples
  311. byte[] buff = new byte[8192];
  312. int length = 0;
  313. /* while (in.available() > 0) {
  314. //read data from source
  315. length = in.read(buff);
  316. //write data to the destination
  317. out.write(buff, 0, length);
  318. }
  319. *
  320. */
  321. int count = 0;
  322. int b = -1;
  323. while ((b = in.read()) != -1) {
  324. out.write(b);
  325. count++;
  326. }
  327. System.out.println("Was copied " + count + " bytes");
  328. }
  329. /**
  330. * Checks does the frame contains sound or silence.
  331. *
  332. * @param data buffer with samples
  333. * @param offset the position of first sample in buffer
  334. * @param len the number if samples
  335. * @return true if silence detected
  336. */
  337. private boolean checkForSilence(byte[] data, int offset, int len) {
  338. for (int i = offset; i < len - 1; i += 2) {
  339. int s = (data[i] & 0xff) | (data[i + 1] << 8);
  340. if (s > SILENCE_LEVEL) {
  341. return false;
  342. }
  343. }
  344. return true;
  345. }
  346. /* (non-Javadoc)
  347. * @see org.mobicents.media.server.impl.AbstractSink#getInterface(java.lang.Class)
  348. */
  349. @Override
  350. public <T> T getInterface(Class<T> interfaceType) {
  351. if (interfaceType.equals(Recorder.class)) {
  352. return (T) this;
  353. } else {
  354. return null;
  355. }
  356. }
  357. /**
  358. * (Non Java-doc.)
  359. *
  360. * @see org.mobicents.media.server.spi.recorder.Recorder#addListener(org.mobicents.media.server.spi.recorder.RecorderListener)
  361. */
  362. public void addListener(RecorderListener listener) throws TooManyListenersException {
  363. listeners.add(listener);
  364. }
  365. /**
  366. * (Non Java-doc.)
  367. *
  368. * @see org.mobicents.media.server.spi.recorder.Recorder#removeListener(org.mobicents.media.server.spi.recorder.RecorderListener)
  369. */
  370. public void removeListener(RecorderListener listener) {
  371. listeners.remove(listener);
  372. }
  373. /**
  374. * Asynchronous recorder stopper.
  375. */
  376. private class KillRecording extends Task {
  377. public KillRecording(Scheduler scheduler) {
  378. super(scheduler);
  379. }
  380. @Override
  381. public long getPriority() {
  382. return 0;
  383. }
  384. @Override
  385. public long getDuration() {
  386. return 0;
  387. }
  388. @Override
  389. public long perform() {
  390. stop();
  391. return 0;
  392. }
  393. }
  394. /**
  395. * Asynchronous recorder stopper.
  396. */
  397. private class EventSender extends Task {
  398. protected RecorderEventImpl event;
  399. public EventSender(Scheduler scheduler) {
  400. super(scheduler);
  401. }
  402. @Override
  403. public long getPriority() {
  404. return 0;
  405. }
  406. @Override
  407. public long getDuration() {
  408. return 0;
  409. }
  410. @Override
  411. public long perform() {
  412. listeners.dispatch(event);
  413. return 0;
  414. }
  415. }
  416. }