PageRenderTime 35ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 0ms

/servers/media/chassis/src/main/java/org/mobicents/media/server/connection/Connections.java

http://mobicents.googlecode.com/
Java | 849 lines | 469 code | 124 blank | 256 comment | 50 complexity | d5e7fa53ed8413137679091bdb06506f 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.connection;
  23. import java.util.ArrayList;
  24. import org.mobicents.media.CheckPoint;
  25. import org.mobicents.media.MediaSink;
  26. import org.mobicents.media.MediaSource;
  27. import org.mobicents.media.server.BaseEndpointImpl;
  28. import org.mobicents.media.server.component.Mixer;
  29. import org.mobicents.media.server.component.Splitter;
  30. import org.mobicents.media.server.component.audio.AudioMixer;
  31. import org.mobicents.media.server.component.video.VideoMixer;
  32. import org.mobicents.media.server.impl.PipeImpl;
  33. import org.mobicents.media.server.impl.rtp.RTPManager;
  34. import org.mobicents.media.server.scheduler.Scheduler;
  35. import org.mobicents.media.server.spi.Connection;
  36. import org.mobicents.media.server.spi.ConnectionMode;
  37. import org.mobicents.media.server.spi.ConnectionType;
  38. import org.mobicents.media.server.spi.FormatNotSupportedException;
  39. import org.mobicents.media.server.spi.MediaType;
  40. import org.mobicents.media.server.spi.ModeNotSupportedException;
  41. import org.mobicents.media.server.spi.ResourceUnavailableException;
  42. import org.mobicents.media.server.spi.dsp.DspFactory;
  43. import org.mobicents.media.server.spi.format.AudioFormat;
  44. import org.mobicents.media.server.spi.format.FormatFactory;
  45. import org.mobicents.media.server.spi.format.Formats;
  46. import org.mobicents.media.server.spi.format.VideoFormat;
  47. /**
  48. * Implements connection management subsystem.
  49. *
  50. * Procedure of joining endponts must work very fast however dynamic connection
  51. * creation upon request cause long and unpredictable delays. Preallocated
  52. * connection objects gives better result.
  53. *
  54. *
  55. * @author kulikov
  56. */
  57. public class Connections {
  58. //endpoint running connections
  59. protected BaseEndpointImpl endpoint;
  60. //job scheduler
  61. protected Scheduler scheduler;
  62. //pool of local connections
  63. protected ArrayList<BaseConnection> localConnections;
  64. //pool of RTP connections
  65. protected ArrayList<BaseConnection> rtpConnections;
  66. //list of currently active connections
  67. protected ArrayList<BaseConnection> activeConnections;
  68. //used for referecning connection
  69. private BaseConnection connection;
  70. //audio channel joining connections with endpoint
  71. protected Channel audioChannel;
  72. //video channel joining connections with endpoint
  73. protected Channel videoChannel;
  74. /**
  75. * active local channels
  76. */
  77. private ArrayList<LocalChannel> localChannels = new ArrayList();
  78. //intermediate audio and video formats.
  79. private Formats audioFormats = new Formats();
  80. private Formats videoFormats = new Formats();
  81. protected RTPManager rtpManager;
  82. /** Signaling processors factory */
  83. protected DspFactory dspFactory;
  84. /**
  85. * Creates new connections subsystem.
  86. *
  87. * @param endpoint the endpoint running connections
  88. * @param poolSize the number of available connections.
  89. */
  90. public Connections(BaseEndpointImpl endpoint, int poolSize) throws Exception {
  91. this.endpoint = endpoint;
  92. this.scheduler = endpoint.getScheduler();
  93. this.rtpManager = endpoint.getRtpManager();
  94. this.dspFactory = endpoint.getDspFactory();
  95. audioFormats.add(FormatFactory.createAudioFormat("linear", 8000, 16, 1));
  96. videoFormats.add(FormatFactory.createVideoFormat("unknown"));
  97. //fill pool with objects.
  98. int count = 1;
  99. //prepare local connections
  100. localConnections = new ArrayList(poolSize);
  101. for (int i = 0; i < poolSize; i++) {
  102. localConnections.add(new LocalConnectionImpl(Integer.toString(count++), this));
  103. }
  104. //prepare rtp connections
  105. rtpConnections = new ArrayList(poolSize);
  106. for (int i = 0; i < poolSize; i++) {
  107. rtpConnections.add(new RtpConnectionImpl(Integer.toString(count++), this));
  108. }
  109. //create holder for active connections
  110. activeConnections = new ArrayList(2 * poolSize);
  111. //creates transmission channels (between endpoint and connections)
  112. audioChannel = new Channel(new AudioMixer(scheduler), new Splitter(scheduler), MediaType.AUDIO);
  113. videoChannel = new Channel(new VideoMixer(scheduler), new Splitter(scheduler), MediaType.VIDEO);
  114. }
  115. /**
  116. * Creates connection with specified type.
  117. *
  118. * @param type the type of connection
  119. * @return connection instance.
  120. */
  121. public synchronized Connection createConnection(ConnectionType type) throws ResourceUnavailableException {
  122. switch (type) {
  123. case LOCAL:
  124. return poll(localConnections);
  125. case RTP:
  126. return poll(rtpConnections);
  127. default:
  128. throw new ResourceUnavailableException("Unknown connection type");
  129. }
  130. }
  131. /**
  132. * Gets the intermediate audio format.
  133. *
  134. * @return the audio format descriptor.
  135. */
  136. public AudioFormat getAudioFormat() {
  137. return (AudioFormat) this.audioFormats.get(0);
  138. }
  139. /**
  140. * Sets the intermediate audio format.
  141. *
  142. * @param audioFormat the audio format descriptor.
  143. */
  144. public void setAudioFormat(AudioFormat audioFormat) {
  145. this.audioFormats.clean();
  146. this.audioFormats.add(audioFormat);
  147. }
  148. /**
  149. * Gets the intermediate video format.
  150. *
  151. * @return the video format descriptor.
  152. */
  153. public VideoFormat getVideoFormat() {
  154. return (VideoFormat) this.videoFormats.get(0);
  155. }
  156. /**
  157. * Sets the intermediate video format.
  158. *
  159. * @param videoFormat the video format descriptor.
  160. */
  161. public void setVideoFormat(VideoFormat videoFormat) {
  162. this.videoFormats.clean();
  163. this.videoFormats.add(videoFormat);
  164. }
  165. /**
  166. * Gets intermediate as collection.
  167. *
  168. * @param mediaType the media type
  169. * @return the collection wich contains single element with intermediate format.
  170. */
  171. protected Formats getFormats(MediaType mediaType) {
  172. switch (mediaType) {
  173. case AUDIO:
  174. return audioFormats;
  175. case VIDEO:
  176. return videoFormats;
  177. default:
  178. return null;
  179. }
  180. }
  181. /**
  182. * Polls connection from specified pool.
  183. *
  184. * @param pool the pool to poll.
  185. * @return connection instance.
  186. */
  187. private BaseConnection poll(ArrayList<BaseConnection> pool) throws ResourceUnavailableException {
  188. if (pool.isEmpty()) {
  189. throw new ResourceUnavailableException("Connections limit exceeded");
  190. }
  191. connection = pool.remove(0);
  192. activeConnections.add(connection);
  193. return connection;
  194. }
  195. /**
  196. * Closes all activities connections.
  197. */
  198. public void release() {
  199. ArrayList<BaseConnection> temp = new ArrayList();
  200. temp.addAll(activeConnections);
  201. for (BaseConnection con : temp) {
  202. con.close();
  203. }
  204. }
  205. /**
  206. * Get access to the mixer.
  207. *
  208. * @param mediaType the type of the mixer: audio or video
  209. * @return mixer component
  210. */
  211. public Mixer getMixer(MediaType mediaType) {
  212. switch (mediaType) {
  213. case AUDIO:
  214. }
  215. return audioChannel.mixer;
  216. }
  217. /**
  218. * Gets access to the splitter.
  219. *
  220. * @param mediaType the type of the splitter: audio or video
  221. * @return splitter component.
  222. */
  223. public Splitter getSplitter(MediaType mediaType) {
  224. return audioChannel.splitter;
  225. }
  226. /**
  227. * Gets the channel of specified media type
  228. *
  229. * @param mediaType the media type of stream
  230. * @return the channel streaming media with mediType content
  231. */
  232. private Channel getChannel(MediaType mediaType) {
  233. switch (mediaType) {
  234. case AUDIO:
  235. return this.audioChannel;
  236. case VIDEO:
  237. return this.videoChannel;
  238. default:
  239. return null;
  240. }
  241. }
  242. /**
  243. * Determines transmission mode between endpoint and connections.
  244. *
  245. * This method is called when mode of any connection has been changed.
  246. * It checks modes of all active connections and determines endpoint
  247. * transition mode as a combination of connections modes.
  248. */
  249. public synchronized void updateMode(MediaType mediaType) throws ModeNotSupportedException {
  250. //originaly everything is false
  251. boolean send = false;
  252. boolean recv = false;
  253. boolean loop = false;
  254. //check mode for each active connection
  255. for (int i = 0; i < activeConnections.size(); i++) {
  256. //if found connection with loopback mode no need to search more
  257. if (loop) {
  258. break;
  259. }
  260. connection = activeConnections.get(i);
  261. switch (connection.getMode(mediaType)) {
  262. case SEND_ONLY:
  263. send = true;
  264. break;
  265. case RECV_ONLY:
  266. recv = true;
  267. break;
  268. case SEND_RECV:
  269. case CONFERENCE:
  270. send = true;
  271. recv = true;
  272. break;
  273. case LOOPBACK:
  274. loop = true;
  275. break;
  276. }
  277. }
  278. //loopback mode detected
  279. if (loop) {
  280. getChannel(mediaType).setMode(ConnectionMode.LOOPBACK);
  281. return;
  282. }
  283. //send only detected
  284. if (send && !recv) {
  285. getChannel(mediaType).setMode(ConnectionMode.SEND_ONLY);
  286. return;
  287. }
  288. //receive only detected
  289. if (!send && recv) {
  290. getChannel(mediaType).setMode(ConnectionMode.RECV_ONLY);
  291. return;
  292. }
  293. //full duplex detected
  294. if (send && recv) {
  295. getChannel(mediaType).setMode(ConnectionMode.SEND_RECV);
  296. return;
  297. }
  298. //inactivity
  299. if (!send && !recv) {
  300. getChannel(mediaType).setMode(null);
  301. return;
  302. }
  303. }
  304. protected void addToConference(BaseConnection connection) {
  305. for (BaseConnection c : activeConnections) {
  306. if (c.getMode(MediaType.AUDIO) == ConnectionMode.CONFERENCE && connection != c) {
  307. LocalChannel channel = new LocalChannel();
  308. channel.join(connection, c);
  309. localChannels.add(channel);
  310. }
  311. }
  312. }
  313. protected void removeFromConference(BaseConnection connection) {
  314. LocalChannel channel = null;
  315. for (LocalChannel c : localChannels) {
  316. if (c.match(connection)) {
  317. channel = c;
  318. break;
  319. }
  320. }
  321. if (channel != null) {
  322. localChannels.remove(channel);
  323. channel.unjoin();
  324. }
  325. }
  326. public String report() {
  327. StringBuilder builder = new StringBuilder();
  328. builder.append(audioChannel.splitter.report());
  329. builder.append(audioChannel.mixer.report());
  330. return builder.toString();
  331. }
  332. /**
  333. * Reads data from specified check point.
  334. *
  335. * @param n the identifier of check point
  336. * @return the data collected by checkpoint.
  337. */
  338. public CheckPoint getCheckPoint(MediaType mediaType, int n) {
  339. switch (n) {
  340. case 1:
  341. MediaSource s = endpoint.getSource(mediaType);
  342. return s == null? new CheckPointImpl(0,0) :
  343. new CheckPointImpl(s.getPacketsTransmitted(), s.getBytesTransmitted());
  344. case 2:
  345. MediaSink sink = endpoint.getSink(mediaType);
  346. return sink == null? new CheckPointImpl(0,0) :
  347. new CheckPointImpl(sink.getPacketsReceived(), sink.getBytesReceived());
  348. case 3:
  349. sink = this.getChannel(mediaType).splitter.getInput();
  350. return new CheckPointImpl(sink.getPacketsReceived(), sink.getBytesReceived());
  351. case 4:
  352. s = this.getChannel(mediaType).mixer.getOutput();
  353. return new CheckPointImpl(s.getPacketsTransmitted(), s.getBytesTransmitted());
  354. default:
  355. throw new IllegalArgumentException("Unknown check point");
  356. }
  357. }
  358. /**
  359. * Transmission channel between connections and endpoint
  360. */
  361. protected class Channel {
  362. protected Mixer mixer;
  363. protected Splitter splitter;
  364. //current transmission mode
  365. private Mode mode;
  366. //available transmission modes
  367. private Mode sendOnly;
  368. private Mode recvOnly;
  369. private Mode sendRecv;
  370. private Mode loopback;
  371. //the media type of this channel
  372. private MediaType mediaType;
  373. /**
  374. * Creates new channel.
  375. *
  376. * @param mixer the mixer used for mixing signals from connections
  377. * @param splitter the splitter for splitting signals between connections
  378. * @param mediaType the media type of the stream
  379. */
  380. protected Channel(Mixer mixer, Splitter splitter, MediaType mediaType) {
  381. this.mixer = mixer;
  382. this.splitter = splitter;
  383. this.mediaType = mediaType;
  384. //prepare transmission modes
  385. sendOnly = new SendOnly(this);
  386. recvOnly = new RecvOnly(this);
  387. sendRecv = new SendRecv(this);
  388. loopback = new Loopback(this);
  389. }
  390. /**
  391. * Gets the media type of this channel
  392. *
  393. * @return the media type identifier
  394. */
  395. protected MediaType getMediaType() {
  396. return mediaType;
  397. }
  398. /**
  399. * Enables transmission in specified mode.
  400. *
  401. * @param mode the mode of transmission
  402. * @throws ModeNotSupportedException
  403. */
  404. protected void setMode(ConnectionMode mode) throws ModeNotSupportedException {
  405. //switch transmission of
  406. if (this.mode != null) {
  407. this.mode.off();
  408. }
  409. //inactive mode?
  410. if (mode == null) {
  411. return;
  412. }
  413. //change mode value
  414. switch (mode) {
  415. case SEND_ONLY:
  416. this.mode = sendOnly;
  417. break;
  418. case RECV_ONLY:
  419. this.mode = recvOnly;
  420. break;
  421. case SEND_RECV:
  422. this.mode = sendRecv;
  423. break;
  424. case LOOPBACK:
  425. this.mode = loopback;
  426. break;
  427. }
  428. //switch transmission on
  429. if (this.mode != null) {
  430. this.mode.on();
  431. }
  432. }
  433. }
  434. /**
  435. * Transmission mode
  436. */
  437. private abstract class Mode {
  438. //transmission channel
  439. protected Channel channel;
  440. /**
  441. * Creates mode.
  442. *
  443. * @param channel transmission channel
  444. */
  445. protected Mode(Channel channel) {
  446. this.channel = channel;
  447. }
  448. /**
  449. * Switches off transmission in this mode
  450. */
  451. protected abstract void off();
  452. /**
  453. * Switches on transmission in this mode
  454. */
  455. protected abstract void on() throws ModeNotSupportedException;
  456. /**
  457. * The number of frames received by endpoint.
  458. *
  459. * @return the number of frames.
  460. */
  461. protected abstract int rxPackets();
  462. /**
  463. * The number of frames transmitted by endpoint.
  464. *
  465. * @return the number of frames.
  466. */
  467. protected abstract int txPackets();
  468. }
  469. /**
  470. * This mode defines transmission from endpoint to connections.
  471. */
  472. private class SendOnly extends Mode {
  473. private long mediaTime;
  474. private MediaSource source;
  475. //pipe for joining endpoint and connections
  476. private PipeImpl pipe = new PipeImpl();
  477. /**
  478. * Creates new mode switch.
  479. *
  480. * @param channel the channel to wich this switch applies
  481. */
  482. protected SendOnly(Channel channel) {
  483. super(channel);
  484. }
  485. @Override
  486. protected void off() {
  487. pipe.stop();
  488. pipe.disconnect();
  489. mediaTime = source.getMediaTime();
  490. }
  491. @Override
  492. protected void on() throws ModeNotSupportedException {
  493. //get the endpoint source
  494. source = endpoint.getSource(channel.getMediaType());
  495. source.setMediaTime(mediaTime);
  496. //check that endpoint has source
  497. if (source == null) {
  498. throw new ModeNotSupportedException("SEND_ONLY");
  499. }
  500. //assign formats
  501. try {
  502. channel.splitter.getInput().setFormats(source.getFormats());
  503. } catch (FormatNotSupportedException e) {
  504. throw new ModeNotSupportedException(e.getMessage());
  505. }
  506. //join source with channel
  507. pipe.connect(source);
  508. pipe.connect(channel.splitter.getInput());
  509. pipe.start();
  510. }
  511. @Override
  512. protected int rxPackets() {
  513. return 0;
  514. }
  515. @Override
  516. protected int txPackets() {
  517. return pipe.getTxPackets();
  518. }
  519. }
  520. /**
  521. * This mode defines the transmission to the endpoint
  522. */
  523. private class RecvOnly extends Mode {
  524. //transmission pipe
  525. private PipeImpl pipe = new PipeImpl();
  526. /**
  527. * Creates this switch
  528. * @param channel the transmission channel
  529. */
  530. protected RecvOnly(Channel channel) {
  531. super(channel);
  532. }
  533. @Override
  534. protected void off() {
  535. channel.mixer.stop();
  536. pipe.stop();
  537. pipe.disconnect();
  538. }
  539. @Override
  540. protected void on() throws ModeNotSupportedException {
  541. //get the sink at the endpoint side
  542. MediaSink sink = endpoint.getSink(channel.getMediaType());
  543. //check that sink is present
  544. if (sink == null) {
  545. throw new ModeNotSupportedException("RECV_ONLY");
  546. }
  547. //assign formats (enable transcoding if required)
  548. try {
  549. channel.mixer.getOutput().setFormats(sink.getFormats());
  550. } catch (FormatNotSupportedException e) {
  551. throw new ModeNotSupportedException(e.getMessage());
  552. }
  553. //join sink with channel and start transmission
  554. pipe.connect(sink);
  555. pipe.connect(channel.mixer.getOutput());
  556. channel.mixer.start();
  557. pipe.start();
  558. }
  559. @Override
  560. protected int rxPackets() {
  561. return pipe.getRxPackets();
  562. }
  563. @Override
  564. protected int txPackets() {
  565. return 0;
  566. }
  567. }
  568. /**
  569. * This mode defines transmission from and to endpoint.
  570. */
  571. private class SendRecv extends Mode {
  572. private Mode sendOnly;
  573. private Mode recvOnly;
  574. /**
  575. * Creates switch.
  576. *
  577. * @param channel transmission channel
  578. */
  579. protected SendRecv (Channel channel) {
  580. super(channel);
  581. sendOnly = new SendOnly(channel);
  582. recvOnly = new RecvOnly(channel);
  583. }
  584. @Override
  585. protected void off() {
  586. sendOnly.off();
  587. recvOnly.off();
  588. }
  589. @Override
  590. protected void on() throws ModeNotSupportedException {
  591. recvOnly.on();
  592. sendOnly.on();
  593. }
  594. @Override
  595. protected int rxPackets() {
  596. return recvOnly.rxPackets();
  597. }
  598. @Override
  599. protected int txPackets() {
  600. return sendOnly.txPackets();
  601. }
  602. }
  603. /**
  604. * In this mode the endpoint transmits media to itself.
  605. */
  606. private class Loopback extends Mode {
  607. private PipeImpl pipe = new PipeImpl();
  608. /**
  609. * Creates switch.
  610. *
  611. * @param channel not used.
  612. */
  613. protected Loopback(Channel channel) {
  614. super(channel);
  615. }
  616. @Override
  617. protected void off() {
  618. pipe.stop();
  619. pipe.disconnect();
  620. }
  621. @Override
  622. protected void on() throws ModeNotSupportedException {
  623. //get the sink from endpoint
  624. MediaSink sink = endpoint.getSink(channel.getMediaType());
  625. //get the source from endpoint
  626. MediaSource source = endpoint.getSource(channel.getMediaType());
  627. //check that both are present
  628. if (sink == null || source == null) {
  629. throw new ModeNotSupportedException("LOOPBACK");
  630. }
  631. //join sink and source and start transmission
  632. pipe.connect(sink);
  633. pipe.connect(source);
  634. pipe.start();
  635. }
  636. @Override
  637. protected int rxPackets() {
  638. return pipe.getRxPackets();
  639. }
  640. @Override
  641. protected int txPackets() {
  642. return pipe.getRxPackets();
  643. }
  644. }
  645. /**
  646. * Implements connection's check points
  647. */
  648. private class CheckPointImpl implements CheckPoint {
  649. private int frames, bytes;
  650. /**
  651. * Creates check point instance.
  652. *
  653. * @param frames
  654. * @param bytes
  655. */
  656. protected CheckPointImpl(long frames, long bytes) {
  657. this.frames = (int) frames;
  658. this.bytes = (int) bytes;
  659. }
  660. /**
  661. * (Non Java-doc.)
  662. *
  663. * @see org.mobicents.media.CheckPoint#getFrames()
  664. */
  665. public int getFrames() {
  666. return this.frames;
  667. }
  668. /**
  669. * (Non Java-doc.)
  670. *
  671. * @see org.mobicents.media.CheckPoint#getBytes()
  672. */
  673. public int getBytes() {
  674. return this.bytes;
  675. }
  676. @Override
  677. public String toString() {
  678. return String.format("frame=%d, bytes=%d", frames, bytes);
  679. }
  680. }
  681. /**
  682. * Channel for joining connections in CNF mode.
  683. */
  684. private class LocalChannel {
  685. private Party party1 = new Party();
  686. private Party party2 = new Party();
  687. private PipeImpl audioRxPipe = new PipeImpl();
  688. private PipeImpl audioTxPipe = new PipeImpl();
  689. private void join(BaseConnection connection1, BaseConnection connection2) {
  690. party1.connection = connection1;
  691. party2.connection = connection2;
  692. party1.source = connection1.audioChannel.splitter.newOutput();
  693. party2.sink = connection2.audioChannel.mixer.newInput();
  694. audioRxPipe.connect(party2.sink);
  695. audioRxPipe.connect(party1.source);
  696. party2.source = connection2.audioChannel.splitter.newOutput();
  697. party1.sink = connection1.audioChannel.mixer.newInput();
  698. audioTxPipe.connect(party1.sink);
  699. audioTxPipe.connect(party2.source);
  700. audioRxPipe.start();
  701. audioTxPipe.start();
  702. }
  703. public boolean match(BaseConnection connection) {
  704. return connection == party1.connection || connection == party2.connection;
  705. }
  706. public void unjoin() {
  707. audioTxPipe.stop();
  708. audioTxPipe.stop();
  709. party1.release();
  710. party2.release();
  711. }
  712. public void setDebug(boolean isDebug) {
  713. audioRxPipe.setDebug(isDebug);
  714. }
  715. private class Party {
  716. private BaseConnection connection;
  717. private MediaSource source;
  718. private MediaSink sink;
  719. private void release() {
  720. connection.audioChannel.splitter.release(source);
  721. connection.audioChannel.mixer.release(sink);
  722. }
  723. }
  724. }
  725. }