diff --git a/src/main/java/com/sipgate/li/simulator/controller/LiMediaExtractor.java b/src/main/java/com/sipgate/li/simulator/controller/LiMediaExtractor.java new file mode 100644 index 0000000..bd3a94d --- /dev/null +++ b/src/main/java/com/sipgate/li/simulator/controller/LiMediaExtractor.java @@ -0,0 +1,59 @@ +package com.sipgate.li.simulator.controller; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +class LiMediaExtractor { + + private static final int HEADER_SIZE = 12; + private static final Map PAYLOAD_TYPE_MAP = new HashMap<>(); + + static { + PAYLOAD_TYPE_MAP.put(0, "PCMU"); + PAYLOAD_TYPE_MAP.put(3, "GSM"); + PAYLOAD_TYPE_MAP.put(4, "G723"); + PAYLOAD_TYPE_MAP.put(5, "DVI4"); + PAYLOAD_TYPE_MAP.put(6, "DVI4"); + PAYLOAD_TYPE_MAP.put(7, "LPC"); + PAYLOAD_TYPE_MAP.put(8, "PCMA"); + PAYLOAD_TYPE_MAP.put(9, "G.722"); + PAYLOAD_TYPE_MAP.put(10, "L16"); + PAYLOAD_TYPE_MAP.put(11, "L16"); + PAYLOAD_TYPE_MAP.put(12, "QCELP"); + PAYLOAD_TYPE_MAP.put(13, "CN"); + PAYLOAD_TYPE_MAP.put(14, "MPA"); + PAYLOAD_TYPE_MAP.put(15, "G.728"); + PAYLOAD_TYPE_MAP.put(16, "DVI4"); + PAYLOAD_TYPE_MAP.put(17, "DVI4"); + PAYLOAD_TYPE_MAP.put(18, "G.729"); + PAYLOAD_TYPE_MAP.put(25, "CelB"); + PAYLOAD_TYPE_MAP.put(26, "JPEG"); + PAYLOAD_TYPE_MAP.put(28, "nv"); + PAYLOAD_TYPE_MAP.put(31, "H.261"); + PAYLOAD_TYPE_MAP.put(32, "MPV"); + PAYLOAD_TYPE_MAP.put(33, "MP2T"); + PAYLOAD_TYPE_MAP.put(34, "H.263"); + } + + private final Set payloadTypeNames = new HashSet<>(); + + public Set getPayloadTypeNames() { + return payloadTypeNames; + } + + void extractMediaFromRtp(final OutputStream output, final byte[] bytes) { + try { + final var payloadTypeCode = bytes[1] & 0x7F; + payloadTypeNames.add(PAYLOAD_TYPE_MAP.get(payloadTypeCode)); + final var buf = new byte[bytes.length - HEADER_SIZE]; + System.arraycopy(bytes, HEADER_SIZE, buf, 0, bytes.length - HEADER_SIZE); + output.write(buf); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/com/sipgate/li/simulator/controller/X2X3Controller.java b/src/main/java/com/sipgate/li/simulator/controller/X2X3Controller.java index d10744c..d8c2b53 100644 --- a/src/main/java/com/sipgate/li/simulator/controller/X2X3Controller.java +++ b/src/main/java/com/sipgate/li/simulator/controller/X2X3Controller.java @@ -3,8 +3,6 @@ import com.sipgate.li.lib.x2x3.protocol.PduObject; import com.sipgate.li.lib.x2x3.protocol.PduType; import com.sipgate.li.simulator.x2x3.X2X3Memory; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufOutputStream; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; @@ -16,7 +14,6 @@ import java.util.List; import java.util.UUID; import java.util.function.Predicate; -import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; @@ -27,6 +24,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; @RestController @RequestMapping("/x2x3") @@ -104,8 +102,9 @@ public ResponseEntity> getAllX3() throws IOException { // ================================ - @GetMapping(value = "/all/rtp/{xid}", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) - public @ResponseBody byte[] getAllRtp(@PathVariable final UUID xid) throws IOException { + @GetMapping(value = "/all/rtp/{xid}") + public ResponseEntity getAllRtp(@PathVariable final UUID xid) throws IOException { + final var liMediaExtractor = new LiMediaExtractor(); try (final var buf = new ByteArrayOutputStream()) { x2X3Memory .getStorage() @@ -114,17 +113,30 @@ public ResponseEntity> getAllX3() throws IOException { .filter(pdu -> pdu.xid().equals(xid)) .sorted(Comparator.comparingInt(pdu -> pdu.findSequenceNumber().orElse(-1))) .map(PduObject::payload) - .forEach(payload -> writeToBuf(payload, buf)); - return buf.toByteArray(); + .forEach(payload -> liMediaExtractor.extractMediaFromRtp(buf, payload)); + LOGGER.info("Extracted types: {}", liMediaExtractor.getPayloadTypeNames()); + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_OCTET_STREAM) // TODO: set the content type from getPayloadTypeNames()? + .body(buf.toByteArray()); } } - private static void writeToBuf(final byte[] payload, final ByteArrayOutputStream buf) { - try { - buf.write(payload); - } catch (final IOException e) { - throw new RuntimeException(e); - } + @GetMapping(value = "/all/stream/{xid}") + public ResponseEntity getAllStream(@PathVariable final UUID xid) { + final var liMediaExtractor = new LiMediaExtractor(); + // might not work yet like streaming, but it's a start + // - sorting is skipped here, of course. + final StreamingResponseBody responseBody = buf -> + x2X3Memory + .getStorage() + .stream() + .filter(pdu -> PduType.X3_PDU.equals(pdu.pduType())) + .filter(pdu -> pdu.xid().equals(xid)) + .map(PduObject::payload) + .forEach(payload -> liMediaExtractor.extractMediaFromRtp(buf, payload)); + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_OCTET_STREAM) // TODO: set the content type from getPayloadTypeNames()? + .body(responseBody); } // ================================