From e4eb16eb152badf8ec6153af37d280d5eea97bda Mon Sep 17 00:00:00 2001 From: pedroSG94 Date: Sun, 22 Sep 2024 01:31:24 +0200 Subject: [PATCH 01/13] add ktor socket and migrate SRT --- common/build.gradle.kts | 2 + .../com/pedro/common/socket/StreamSocket.kt | 74 +++++++++ .../pedro/common/socket/TcpStreamSocket.kt | 150 ++++++++++++++++++ .../pedro/common/socket/UdpStreamSocket.kt | 70 ++++++++ gradle/libs.versions.toml | 3 + .../{SocketTest.kt => StreamSocketTest.kt} | 2 +- ...tpSocketTest.kt => RtpStreamSocketTest.kt} | 2 +- .../pedro/srt/mpeg2ts/packets/H26XPacket.kt | 1 - .../java/com/pedro/srt/srt/CommandsManager.kt | 2 +- .../java/com/pedro/srt/utils/SrtSocket.kt | 46 ++---- 10 files changed, 315 insertions(+), 37 deletions(-) create mode 100644 common/src/main/java/com/pedro/common/socket/StreamSocket.kt create mode 100644 common/src/main/java/com/pedro/common/socket/TcpStreamSocket.kt create mode 100644 common/src/main/java/com/pedro/common/socket/UdpStreamSocket.kt rename rtmp/src/test/java/com/pedro/rtmp/utils/{SocketTest.kt => StreamSocketTest.kt} (97%) rename rtsp/src/test/java/com/pedro/rtsp/rtp/{RtpSocketTest.kt => RtpStreamSocketTest.kt} (99%) diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 73772f094..7ee12644a 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -50,6 +50,8 @@ afterEvaluate { } dependencies { + implementation(libs.ktor.network) + implementation(libs.ktor.network.tls) implementation(libs.androidx.annotation) implementation(libs.kotlinx.coroutines.android) testImplementation(libs.kotlinx.coroutines.test) diff --git a/common/src/main/java/com/pedro/common/socket/StreamSocket.kt b/common/src/main/java/com/pedro/common/socket/StreamSocket.kt new file mode 100644 index 000000000..4928124a2 --- /dev/null +++ b/common/src/main/java/com/pedro/common/socket/StreamSocket.kt @@ -0,0 +1,74 @@ +/* + * + * * Copyright (C) 2024 pedroSG94. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package com.pedro.common.socket + +import io.ktor.network.selector.SelectorManager +import io.ktor.network.sockets.ReadWriteSocket +import io.ktor.network.sockets.isClosed +import io.ktor.network.sockets.openReadChannel +import io.ktor.network.sockets.openWriteChannel +import io.ktor.utils.io.ByteReadChannel +import io.ktor.utils.io.ByteWriteChannel +import io.ktor.utils.io.readFully +import io.ktor.utils.io.writeByte +import io.ktor.utils.io.writeFully +import io.ktor.utils.io.writeStringUtf8 +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.io.IOException +import java.net.InetAddress +import java.net.InetSocketAddress + +/** + * Created by pedro on 22/9/24. + */ +abstract class StreamSocket( + private val host: String, + private val port: Int +) { + + private var selectorManager = SelectorManager(Dispatchers.IO) + protected var socket: ReadWriteSocket? = null + private var address: InetAddress? = null + + abstract suspend fun buildSocketConfigAndConnect(selectorManager: SelectorManager): ReadWriteSocket + abstract suspend fun closeResources() + + suspend fun connect() { + selectorManager = SelectorManager(Dispatchers.IO) + val socket = buildSocketConfigAndConnect(selectorManager) + + address = InetSocketAddress(host, port).address + this.socket = socket + } + + suspend fun close() = withContext(Dispatchers.IO) { + try { + address = null + closeResources() + socket?.close() + selectorManager.close() + } catch (ignored: Exception) {} + } + + fun isConnected(): Boolean = socket?.isClosed != true + + fun isReachable(): Boolean = address?.isReachable(5000) ?: false + +} \ No newline at end of file diff --git a/common/src/main/java/com/pedro/common/socket/TcpStreamSocket.kt b/common/src/main/java/com/pedro/common/socket/TcpStreamSocket.kt new file mode 100644 index 000000000..a792c8f4a --- /dev/null +++ b/common/src/main/java/com/pedro/common/socket/TcpStreamSocket.kt @@ -0,0 +1,150 @@ +/* + * + * * Copyright (C) 2024 pedroSG94. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package com.pedro.common.socket + +import io.ktor.network.selector.SelectorManager +import io.ktor.network.sockets.InetSocketAddress +import io.ktor.network.sockets.ReadWriteSocket +import io.ktor.network.sockets.Socket +import io.ktor.network.sockets.aSocket +import io.ktor.network.sockets.openReadChannel +import io.ktor.network.sockets.openWriteChannel +import io.ktor.network.tls.CertificateAndKey +import io.ktor.network.tls.tls +import io.ktor.utils.io.ByteReadChannel +import io.ktor.utils.io.ByteWriteChannel +import io.ktor.utils.io.readFully +import io.ktor.utils.io.writeByte +import io.ktor.utils.io.writeFully +import io.ktor.utils.io.writeStringUtf8 +import java.io.IOException +import java.security.SecureRandom +import javax.net.ssl.TrustManager +import kotlin.coroutines.coroutineContext + +/** + * Created by pedro on 22/9/24. + */ +class TcpStreamSocket( + private val host: String, + private val port: Int, + private val secured: Boolean, + private val certificate: TrustManager +): StreamSocket(host, port) { + + private val timeout = 5000L + private var input: ByteReadChannel? = null + private var output: ByteWriteChannel? = null + + override suspend fun buildSocketConfigAndConnect(selectorManager: SelectorManager): ReadWriteSocket { + val builder = aSocket(selectorManager).tcp() + val socket = if (secured) { + builder.connect(remoteAddress = InetSocketAddress(host, port)) { socketTimeout = timeout }.tls( + coroutineContext = coroutineContext + ) { + trustManager = certificate + random = SecureRandom() + } + } else { + builder.connect(host, port) { socketTimeout = timeout } + } + input = socket.openReadChannel() + output = socket.openWriteChannel(autoFlush = false) + return socket + } + + override suspend fun closeResources() { + input = null + output = null + } + + suspend fun flush() { + output?.flush() + } + + suspend fun write(b: Int) { + output?.writeByte(b) + } + + suspend fun write(b: ByteArray) { + output?.writeFully(b) + } + + suspend fun write(b: ByteArray, offset: Int, size: Int) { + output?.writeFully(b, offset, size) + } + + suspend fun writeUInt16(b: Int) { + output?.writeByte(b ushr 8) + output?.writeByte(b) + } + + suspend fun writeUInt24(b: Int) { + output?.writeByte(b ushr 16) + output?.writeByte(b ushr 8) + output?.writeByte(b) + } + + suspend fun writeUInt32(b: Int) { + output?.writeByte(b ushr 24) + output?.writeByte(b ushr 16) + output?.writeByte(b ushr 8) + output?.writeByte(b) + } + + suspend fun writeUInt32LittleEndian(b: Int) { + output?.writeByte(b) + output?.writeByte(b ushr 8) + output?.writeByte(b ushr 16) + output?.writeByte(b ushr 24) + } + + suspend fun read(): Int { + return input?.readByte()?.toInt() ?: throw IOException("read with socket closed") + } + + suspend fun readUInt16(): Int { + return read() and 0xff shl 8 or (read() and 0xff) + } + + suspend fun readUInt24(): Int { + return read() and 0xff shl 16 or (read() and 0xff shl 8) or (read() and 0xff) + } + + suspend fun readUInt32(): Int { + return read() and 0xff shl 24 or (read() and 0xff shl 16) or (read() and 0xff shl 8) or (read() and 0xff) + } + + suspend fun readUInt32LittleEndian(): Int { + return Integer.reverseBytes(readUInt32()) + } + + suspend fun readUntil(b: ByteArray) { + return input?.readFully(b) ?: throw IOException("read with socket closed") + } + + suspend fun readString(): String { + val limit = input?.availableForRead ?: throw IOException("read with socket closed") + return input?.readUTF8Line(limit) ?: throw IOException("read with socket closed") + } + + suspend fun writeString(string: String) { + output?.writeStringUtf8(string) + } +} \ No newline at end of file diff --git a/common/src/main/java/com/pedro/common/socket/UdpStreamSocket.kt b/common/src/main/java/com/pedro/common/socket/UdpStreamSocket.kt new file mode 100644 index 000000000..bb67584c3 --- /dev/null +++ b/common/src/main/java/com/pedro/common/socket/UdpStreamSocket.kt @@ -0,0 +1,70 @@ +/* + * + * * Copyright (C) 2024 pedroSG94. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package com.pedro.common.socket + +import io.ktor.network.selector.SelectorManager +import io.ktor.network.sockets.ConnectedDatagramSocket +import io.ktor.network.sockets.Datagram +import io.ktor.network.sockets.InetSocketAddress +import io.ktor.network.sockets.ReadWriteSocket +import io.ktor.network.sockets.aSocket +import io.ktor.utils.io.core.ByteReadPacket +import io.ktor.utils.io.core.readBytes + +/** + * Created by pedro on 22/9/24. + */ +class UdpStreamSocket( + private val host: String, + private val port: Int, + private val sourcePort: Int? = null, + private val broadcastMode: Boolean = false, + private val receiveSize: Int? = null +): StreamSocket(host, port) { + + private val address = InetSocketAddress(host, port) + private val udpSocket by lazy { + socket as ConnectedDatagramSocket + } + + override suspend fun buildSocketConfigAndConnect(selectorManager: SelectorManager): ReadWriteSocket { + val builder = aSocket(selectorManager).udp() + val localAddress = if (sourcePort == null) null else InetSocketAddress("localhost", sourcePort) + return builder.connect( + remoteAddress = address, + localAddress = localAddress + ) { + broadcast = broadcastMode + receiveBufferSize = receiveSize ?: 0 + } + } + + override suspend fun closeResources() { } + + suspend fun readPacket(): ByteArray { + val packet = udpSocket.receive().packet + val length = packet.remaining.toInt() + return packet.readBytes().sliceArray(0 until length) + } + + suspend fun writePacket(bytes: ByteArray) { + val datagram = Datagram(ByteReadPacket(bytes), address) + udpSocket.send(datagram) + } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c064bc9c3..575c2b40f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,6 +18,7 @@ annotation = "1.8.2" coroutines = "1.9.0" junit = "4.13.2" mockito = "5.4.0" +ktor = "2.3.12" uvcandroid = "1.0.7" [libraries] @@ -31,6 +32,8 @@ androidx-multidex = { module = "androidx.multidex:multidex", version.ref = "mult junit = { module = "junit:junit", version.ref = "junit" } kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" } +ktor-network = { module = "io.ktor:ktor-network", version.ref = "ktor" } +ktor-network-tls = { module = "io.ktor:ktor-network-tls", version.ref = "ktor" } mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockito" } uvcandroid = { module = "com.herohan:UVCAndroid", version.ref = "uvcandroid" } diff --git a/rtmp/src/test/java/com/pedro/rtmp/utils/SocketTest.kt b/rtmp/src/test/java/com/pedro/rtmp/utils/StreamSocketTest.kt similarity index 97% rename from rtmp/src/test/java/com/pedro/rtmp/utils/SocketTest.kt rename to rtmp/src/test/java/com/pedro/rtmp/utils/StreamSocketTest.kt index 4fd759071..0fe3b7fc3 100644 --- a/rtmp/src/test/java/com/pedro/rtmp/utils/SocketTest.kt +++ b/rtmp/src/test/java/com/pedro/rtmp/utils/StreamSocketTest.kt @@ -19,7 +19,7 @@ package com.pedro.rtmp.utils import com.pedro.rtmp.utils.socket.TcpSocket import org.junit.Test -class SocketTest { +class StreamSocketTest { @Test fun `check tcp socket error with socket not connected`() { diff --git a/rtsp/src/test/java/com/pedro/rtsp/rtp/RtpSocketTest.kt b/rtsp/src/test/java/com/pedro/rtsp/rtp/RtpStreamSocketTest.kt similarity index 99% rename from rtsp/src/test/java/com/pedro/rtsp/rtp/RtpSocketTest.kt rename to rtsp/src/test/java/com/pedro/rtsp/rtp/RtpStreamSocketTest.kt index f9d8b5055..1931969af 100644 --- a/rtsp/src/test/java/com/pedro/rtsp/rtp/RtpSocketTest.kt +++ b/rtsp/src/test/java/com/pedro/rtsp/rtp/RtpStreamSocketTest.kt @@ -39,7 +39,7 @@ import java.net.MulticastSocket * Created by pedro on 9/9/23. */ @RunWith(MockitoJUnitRunner::class) -class RtpSocketTest { +class RtpStreamSocketTest { @Mock private lateinit var multicastSocketMocked: MulticastSocket diff --git a/srt/src/main/java/com/pedro/srt/mpeg2ts/packets/H26XPacket.kt b/srt/src/main/java/com/pedro/srt/mpeg2ts/packets/H26XPacket.kt index ddd639deb..14db51ce1 100644 --- a/srt/src/main/java/com/pedro/srt/mpeg2ts/packets/H26XPacket.kt +++ b/srt/src/main/java/com/pedro/srt/mpeg2ts/packets/H26XPacket.kt @@ -17,7 +17,6 @@ package com.pedro.srt.mpeg2ts.packets import android.media.MediaCodec -import android.os.Build import android.util.Log import com.pedro.common.isKeyframe import com.pedro.common.removeInfo diff --git a/srt/src/main/java/com/pedro/srt/srt/CommandsManager.kt b/srt/src/main/java/com/pedro/srt/srt/CommandsManager.kt index 710975163..28a5e310d 100644 --- a/srt/src/main/java/com/pedro/srt/srt/CommandsManager.kt +++ b/srt/src/main/java/com/pedro/srt/srt/CommandsManager.kt @@ -93,7 +93,7 @@ class CommandsManager { } @Throws(IOException::class) - fun readHandshake(socket: SrtSocket?): Handshake { + suspend fun readHandshake(socket: SrtSocket?): Handshake { val handshakeBuffer = socket?.readBuffer() ?: throw IOException("read buffer failed, socket disconnected") val handshake = SrtPacket.getSrtPacket(handshakeBuffer) if (handshake is Handshake) { diff --git a/srt/src/main/java/com/pedro/srt/utils/SrtSocket.kt b/srt/src/main/java/com/pedro/srt/utils/SrtSocket.kt index f08b76677..7fb757c1d 100644 --- a/srt/src/main/java/com/pedro/srt/utils/SrtSocket.kt +++ b/srt/src/main/java/com/pedro/srt/utils/SrtSocket.kt @@ -16,58 +16,38 @@ package com.pedro.srt.utils +import com.pedro.common.socket.UdpStreamSocket import com.pedro.srt.srt.packets.SrtPacket -import java.net.DatagramPacket -import java.net.DatagramSocket -import java.net.InetAddress /** * Created by pedro on 22/8/23. */ -class SrtSocket(private val host: String, private val port: Int) { +class SrtSocket(host: String, port: Int) { - private val TAG = "SrtSocket" - private var socket: DatagramSocket? = null - private var packetSize = Constants.MTU - private val timeout = 5000 + private val socket = UdpStreamSocket(host, port, receiveSize = Constants.MTU) - fun connect() { - val address = InetAddress.getByName(host) - socket = DatagramSocket() - socket?.connect(address, port) - socket?.soTimeout = timeout + suspend fun connect() { + socket.connect() } - fun close() { - if (socket?.isClosed == false) { - socket?.disconnect() - socket?.close() - socket = null - } + suspend fun close() { + socket.close() } fun isConnected(): Boolean { - return socket?.isConnected ?: false + return socket.isConnected() } fun isReachable(): Boolean { - return socket?.inetAddress?.isReachable(5000) ?: false + return socket.isReachable() } - fun setPacketSize(size: Int) { - packetSize = size - } - - fun write(srtPacket: SrtPacket) { + suspend fun write(srtPacket: SrtPacket) { val buffer = srtPacket.getData() - val udpPacket = DatagramPacket(buffer, buffer.size) - socket?.send(udpPacket) + socket.writePacket(buffer) } - fun readBuffer(): ByteArray { - val buffer = ByteArray(packetSize) - val udpPacket = DatagramPacket(buffer, buffer.size) - socket?.receive(udpPacket) - return udpPacket.data.sliceArray(0 until udpPacket.length) + suspend fun readBuffer(): ByteArray { + return socket.readPacket() } } \ No newline at end of file From 4e9fe51759ffdc21b74de9c110bfaa341051193c Mon Sep 17 00:00:00 2001 From: pedroSG94 Date: Sun, 22 Sep 2024 01:42:59 +0200 Subject: [PATCH 02/13] migrate UDP protocol and fix udp sourceport --- .../pedro/common/socket/UdpStreamSocket.kt | 10 ++-- .../java/com/pedro/srt/utils/SrtSocket.kt | 12 +--- .../java/com/pedro/udp/utils/UdpSocket.kt | 57 +++++-------------- 3 files changed, 23 insertions(+), 56 deletions(-) diff --git a/common/src/main/java/com/pedro/common/socket/UdpStreamSocket.kt b/common/src/main/java/com/pedro/common/socket/UdpStreamSocket.kt index bb67584c3..aaf16b725 100644 --- a/common/src/main/java/com/pedro/common/socket/UdpStreamSocket.kt +++ b/common/src/main/java/com/pedro/common/socket/UdpStreamSocket.kt @@ -31,11 +31,11 @@ import io.ktor.utils.io.core.readBytes * Created by pedro on 22/9/24. */ class UdpStreamSocket( - private val host: String, - private val port: Int, + host: String, + port: Int, private val sourcePort: Int? = null, - private val broadcastMode: Boolean = false, - private val receiveSize: Int? = null + private val receiveSize: Int? = null, + private val broadcastMode: Boolean = false ): StreamSocket(host, port) { private val address = InetSocketAddress(host, port) @@ -45,7 +45,7 @@ class UdpStreamSocket( override suspend fun buildSocketConfigAndConnect(selectorManager: SelectorManager): ReadWriteSocket { val builder = aSocket(selectorManager).udp() - val localAddress = if (sourcePort == null) null else InetSocketAddress("localhost", sourcePort) + val localAddress = if (sourcePort == null) null else InetSocketAddress("0.0.0.0", sourcePort) return builder.connect( remoteAddress = address, localAddress = localAddress diff --git a/srt/src/main/java/com/pedro/srt/utils/SrtSocket.kt b/srt/src/main/java/com/pedro/srt/utils/SrtSocket.kt index 7fb757c1d..4c1ceaf21 100644 --- a/srt/src/main/java/com/pedro/srt/utils/SrtSocket.kt +++ b/srt/src/main/java/com/pedro/srt/utils/SrtSocket.kt @@ -34,20 +34,14 @@ class SrtSocket(host: String, port: Int) { socket.close() } - fun isConnected(): Boolean { - return socket.isConnected() - } + fun isConnected() = socket.isConnected() - fun isReachable(): Boolean { - return socket.isReachable() - } + fun isReachable() = socket.isReachable() suspend fun write(srtPacket: SrtPacket) { val buffer = srtPacket.getData() socket.writePacket(buffer) } - suspend fun readBuffer(): ByteArray { - return socket.readPacket() - } + suspend fun readBuffer() = socket.readPacket() } \ No newline at end of file diff --git a/udp/src/main/java/com/pedro/udp/utils/UdpSocket.kt b/udp/src/main/java/com/pedro/udp/utils/UdpSocket.kt index 7c282e46d..55245cbcf 100644 --- a/udp/src/main/java/com/pedro/udp/utils/UdpSocket.kt +++ b/udp/src/main/java/com/pedro/udp/utils/UdpSocket.kt @@ -16,64 +16,37 @@ package com.pedro.udp.utils +import com.pedro.common.socket.UdpStreamSocket import com.pedro.srt.mpeg2ts.MpegTsPacket import com.pedro.srt.mpeg2ts.MpegTsPacketizer -import java.net.DatagramPacket -import java.net.DatagramSocket -import java.net.InetAddress -import java.net.MulticastSocket /** * Created by pedro on 6/3/24. */ -class UdpSocket(private val host: String, private val type: UdpType, private val port: Int) { +class UdpSocket(host: String, type: UdpType, port: Int) { - private var socket: DatagramSocket? = null - private var packetSize = MpegTsPacketizer.packetSize - private val timeout = 5000 + private val socket = UdpStreamSocket( + host, port, receiveSize = MpegTsPacketizer.packetSize, + broadcastMode = type == UdpType.BROADCAST + ) - fun connect() { - val address = InetAddress.getByName(host) - socket = when (type) { - UdpType.UNICAST -> DatagramSocket() - UdpType.MULTICAST -> MulticastSocket() - UdpType.BROADCAST -> DatagramSocket().apply { broadcast = true } - } - socket?.connect(address, port) - socket?.soTimeout = timeout + suspend fun connect() { + socket.connect() } - fun close() { - if (socket?.isClosed == false) { - socket?.disconnect() - socket?.close() - socket = null - } + suspend fun close() { + socket.close() } - fun isConnected(): Boolean { - return socket?.isConnected ?: false - } + suspend fun isConnected() = socket.isConnected() - fun isReachable(): Boolean { - return socket?.inetAddress?.isReachable(5000) ?: false - } + fun isReachable() = socket.isReachable() - fun setPacketSize(size: Int) { - packetSize = size - } - - fun write(mpegTsPacket: MpegTsPacket): Int { + suspend fun write(mpegTsPacket: MpegTsPacket): Int { val buffer = mpegTsPacket.buffer - val udpPacket = DatagramPacket(buffer, buffer.size) - socket?.send(udpPacket) + socket.writePacket(buffer) return buffer.size } - fun readBuffer(): ByteArray { - val buffer = ByteArray(packetSize) - val udpPacket = DatagramPacket(buffer, buffer.size) - socket?.receive(udpPacket) - return udpPacket.data.sliceArray(0 until udpPacket.length) - } + suspend fun readBuffer() = socket.readPacket() } \ No newline at end of file From d9645b3b8fbbcbb9f56972b02eed321dfcf81ca9 Mon Sep 17 00:00:00 2001 From: pedroSG94 Date: Sun, 22 Sep 2024 20:32:36 +0200 Subject: [PATCH 03/13] migrate rtsp protocl to ktor --- .../com/pedro/common/socket/StreamSocket.kt | 2 - .../pedro/common/socket/TcpStreamSocket.kt | 15 ++- .../com/pedro/rtsp/rtcp/BaseSenderReport.kt | 14 ++- .../com/pedro/rtsp/rtcp/SenderReportTcp.kt | 22 ++-- .../com/pedro/rtsp/rtcp/SenderReportUdp.kt | 53 ++++----- .../com/pedro/rtsp/rtp/packets/AacPacket.kt | 2 +- .../com/pedro/rtsp/rtp/packets/Av1Packet.kt | 2 +- .../com/pedro/rtsp/rtp/packets/BasePacket.kt | 7 -- .../com/pedro/rtsp/rtp/packets/G711Packet.kt | 2 +- .../com/pedro/rtsp/rtp/packets/H264Packet.kt | 6 +- .../com/pedro/rtsp/rtp/packets/H265Packet.kt | 4 +- .../com/pedro/rtsp/rtp/packets/OpusPacket.kt | 2 +- .../pedro/rtsp/rtp/sockets/BaseRtpSocket.kt | 14 ++- .../pedro/rtsp/rtp/sockets/RtpSocketTcp.kt | 29 +++-- .../pedro/rtsp/rtp/sockets/RtpSocketUdp.kt | 51 ++++---- .../main/java/com/pedro/rtsp/rtsp/RtpFrame.kt | 7 +- .../java/com/pedro/rtsp/rtsp/RtspClient.kt | 109 +++++++----------- .../java/com/pedro/rtsp/rtsp/RtspSender.kt | 26 ++--- .../rtsp/rtsp/commands/CommandsManager.kt | 8 +- .../com/pedro/rtsp/rtcp/RtcpReportTest.kt | 2 +- .../com/pedro/rtsp/rtp/RtpStreamSocketTest.kt | 4 +- 21 files changed, 158 insertions(+), 223 deletions(-) diff --git a/common/src/main/java/com/pedro/common/socket/StreamSocket.kt b/common/src/main/java/com/pedro/common/socket/StreamSocket.kt index 4928124a2..7e67c0272 100644 --- a/common/src/main/java/com/pedro/common/socket/StreamSocket.kt +++ b/common/src/main/java/com/pedro/common/socket/StreamSocket.kt @@ -53,7 +53,6 @@ abstract class StreamSocket( suspend fun connect() { selectorManager = SelectorManager(Dispatchers.IO) val socket = buildSocketConfigAndConnect(selectorManager) - address = InetSocketAddress(host, port).address this.socket = socket } @@ -70,5 +69,4 @@ abstract class StreamSocket( fun isConnected(): Boolean = socket?.isClosed != true fun isReachable(): Boolean = address?.isReachable(5000) ?: false - } \ No newline at end of file diff --git a/common/src/main/java/com/pedro/common/socket/TcpStreamSocket.kt b/common/src/main/java/com/pedro/common/socket/TcpStreamSocket.kt index a792c8f4a..c8710ff08 100644 --- a/common/src/main/java/com/pedro/common/socket/TcpStreamSocket.kt +++ b/common/src/main/java/com/pedro/common/socket/TcpStreamSocket.kt @@ -21,15 +21,14 @@ package com.pedro.common.socket import io.ktor.network.selector.SelectorManager import io.ktor.network.sockets.InetSocketAddress import io.ktor.network.sockets.ReadWriteSocket -import io.ktor.network.sockets.Socket import io.ktor.network.sockets.aSocket import io.ktor.network.sockets.openReadChannel import io.ktor.network.sockets.openWriteChannel -import io.ktor.network.tls.CertificateAndKey import io.ktor.network.tls.tls import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.ByteWriteChannel import io.ktor.utils.io.readFully +import io.ktor.utils.io.readUTF8Line import io.ktor.utils.io.writeByte import io.ktor.utils.io.writeFully import io.ktor.utils.io.writeStringUtf8 @@ -44,8 +43,8 @@ import kotlin.coroutines.coroutineContext class TcpStreamSocket( private val host: String, private val port: Int, - private val secured: Boolean, - private val certificate: TrustManager + private val secured: Boolean = false, + private val certificate: TrustManager? = null ): StreamSocket(host, port) { private val timeout = 5000L @@ -139,12 +138,12 @@ class TcpStreamSocket( return input?.readFully(b) ?: throw IOException("read with socket closed") } - suspend fun readString(): String { - val limit = input?.availableForRead ?: throw IOException("read with socket closed") - return input?.readUTF8Line(limit) ?: throw IOException("read with socket closed") + suspend fun readLine(): String? { + val input = input ?: throw IOException("read with socket closed") + return input.readUTF8Line() } - suspend fun writeString(string: String) { + suspend fun write(string: String) { output?.writeStringUtf8(string) } } \ No newline at end of file diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtcp/BaseSenderReport.kt b/rtsp/src/main/java/com/pedro/rtsp/rtcp/BaseSenderReport.kt index fa95e34c9..18c559b83 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtcp/BaseSenderReport.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtcp/BaseSenderReport.kt @@ -17,12 +17,12 @@ package com.pedro.rtsp.rtcp import com.pedro.common.TimeUtils +import com.pedro.common.socket.TcpStreamSocket import com.pedro.rtsp.rtsp.Protocol import com.pedro.rtsp.rtsp.RtpFrame import com.pedro.rtsp.utils.RtpConstants import com.pedro.rtsp.utils.setLong import java.io.IOException -import java.io.OutputStream /** * Created by pedro on 7/11/18. @@ -41,11 +41,15 @@ abstract class BaseSenderReport internal constructor() { companion object { @JvmStatic - fun getInstance(protocol: Protocol, videoSourcePort: Int, audioSourcePort: Int): BaseSenderReport { + fun getInstance( + protocol: Protocol, host: String, + videoSourcePort: Int, audioSourcePort: Int, + videoServerPort: Int, audioServerPort: Int, + ): BaseSenderReport { return if (protocol === Protocol.TCP) { SenderReportTcp() } else { - SenderReportUdp(videoSourcePort, audioSourcePort) + SenderReportUdp(host, videoSourcePort, audioSourcePort, videoServerPort, audioServerPort) } } } @@ -82,7 +86,7 @@ abstract class BaseSenderReport internal constructor() { } @Throws(IOException::class) - abstract fun setDataStream(outputStream: OutputStream, host: String) + abstract suspend fun setSocket(socket: TcpStreamSocket) @Throws(IOException::class) suspend fun update(rtpFrame: RtpFrame): Boolean { @@ -139,7 +143,7 @@ abstract class BaseSenderReport internal constructor() { audioBuffer.setLong(audioOctetCount, 24, 28) } - abstract fun close() + abstract suspend fun close() private fun setData(buffer: ByteArray, ntpts: Long, rtpts: Long) { val hb = ntpts / 1000000000 diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtcp/SenderReportTcp.kt b/rtsp/src/main/java/com/pedro/rtsp/rtcp/SenderReportTcp.kt index 11e55f299..9b9cd6242 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtcp/SenderReportTcp.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtcp/SenderReportTcp.kt @@ -16,22 +16,22 @@ package com.pedro.rtsp.rtcp +import com.pedro.common.socket.TcpStreamSocket import com.pedro.rtsp.rtsp.RtpFrame import com.pedro.rtsp.utils.RtpConstants import java.io.IOException -import java.io.OutputStream /** * Created by pedro on 8/11/18. */ class SenderReportTcp : BaseSenderReport() { - private var outputStream: OutputStream? = null + private var socket: TcpStreamSocket? = null private val tcpHeader: ByteArray = byteArrayOf('$'.code.toByte(), 0, 0, RtpConstants.REPORT_PACKET_LENGTH.toByte()) @Throws(IOException::class) - override fun setDataStream(outputStream: OutputStream, host: String) { - this.outputStream = outputStream + override suspend fun setSocket(socket: TcpStreamSocket) { + this.socket = socket } @Throws(IOException::class) @@ -39,15 +39,13 @@ class SenderReportTcp : BaseSenderReport() { sendReportTCP(buffer, rtpFrame.channelIdentifier) } - override fun close() {} + override suspend fun close() {} @Throws(IOException::class) - private fun sendReportTCP(buffer: ByteArray, channelIdentifier: Int) { - synchronized(RtpConstants.lock) { - tcpHeader[1] = (2 * channelIdentifier + 1).toByte() - outputStream?.write(tcpHeader) - outputStream?.write(buffer, 0, RtpConstants.REPORT_PACKET_LENGTH) - outputStream?.flush() - } + private suspend fun sendReportTCP(buffer: ByteArray, channelIdentifier: Int) { + tcpHeader[1] = (2 * channelIdentifier + 1).toByte() + socket?.write(tcpHeader) + socket?.write(buffer, 0, RtpConstants.REPORT_PACKET_LENGTH) + socket?.flush() } } \ No newline at end of file diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtcp/SenderReportUdp.kt b/rtsp/src/main/java/com/pedro/rtsp/rtcp/SenderReportUdp.kt index 94319e699..b3e6ab0d9 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtcp/SenderReportUdp.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtcp/SenderReportUdp.kt @@ -16,58 +16,49 @@ package com.pedro.rtsp.rtcp +import com.pedro.common.socket.TcpStreamSocket +import com.pedro.common.socket.UdpStreamSocket import com.pedro.rtsp.rtsp.RtpFrame -import com.pedro.rtsp.utils.RtpConstants import java.io.IOException -import java.io.OutputStream -import java.net.DatagramPacket -import java.net.InetAddress -import java.net.MulticastSocket /** * Created by pedro on 8/11/18. */ class SenderReportUdp( + host: String, videoSourcePort: Int, audioSourcePort: Int, - private var multicastSocketVideo: MulticastSocket? = null, - private var multicastSocketAudio: MulticastSocket? = null + videoServerPort: Int, audioServerPort: Int, ) : BaseSenderReport() { - private val datagramPacket = DatagramPacket(byteArrayOf(0), 1) - - init { - if (multicastSocketVideo == null) multicastSocketVideo = MulticastSocket(videoSourcePort) - multicastSocketVideo?.timeToLive = 64 - if (multicastSocketAudio == null) multicastSocketAudio = MulticastSocket(audioSourcePort) - multicastSocketAudio?.timeToLive = 64 - } + private val videoSocket = UdpStreamSocket( + host, videoServerPort, videoSourcePort + ) + private val audioSocket = UdpStreamSocket( + host, audioServerPort, audioSourcePort + ) @Throws(IOException::class) - override fun setDataStream(outputStream: OutputStream, host: String) { - datagramPacket.address = InetAddress.getByName(host) + override suspend fun setSocket(socket: TcpStreamSocket) { + videoSocket.connect() + audioSocket.connect() } @Throws(IOException::class) override suspend fun sendReport(buffer: ByteArray, rtpFrame: RtpFrame) { - sendReportUDP(buffer, rtpFrame.rtcpPort, rtpFrame.isVideoFrame()) + sendReportUDP(buffer, rtpFrame.isVideoFrame()) } - override fun close() { - multicastSocketVideo?.close() - multicastSocketAudio?.close() + override suspend fun close() { + videoSocket.close() + audioSocket.close() } @Throws(IOException::class) - private fun sendReportUDP(buffer: ByteArray, port: Int, isVideo: Boolean) { - synchronized(RtpConstants.lock) { - datagramPacket.data = buffer - datagramPacket.port = port - datagramPacket.length = RtpConstants.REPORT_PACKET_LENGTH - if (isVideo) { - multicastSocketVideo?.send(datagramPacket) - } else { - multicastSocketAudio?.send(datagramPacket) - } + private suspend fun sendReportUDP(buffer: ByteArray,isVideo: Boolean) { + if (isVideo) { + videoSocket.writePacket(buffer) + } else { + audioSocket.writePacket(buffer) } } } \ No newline at end of file diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/AacPacket.kt b/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/AacPacket.kt index f282626f3..3d2947154 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/AacPacket.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/AacPacket.kt @@ -72,7 +72,7 @@ class AacPacket( buffer[RtpConstants.RTP_HEADER_LENGTH + 3] = buffer[RtpConstants.RTP_HEADER_LENGTH + 3] and 0xF8.toByte() buffer[RtpConstants.RTP_HEADER_LENGTH + 3] = buffer[RtpConstants.RTP_HEADER_LENGTH + 3] or 0x00 updateSeq(buffer) - val rtpFrame = RtpFrame(buffer, rtpTs, RtpConstants.RTP_HEADER_LENGTH + size + 4, rtpPort, rtcpPort, channelIdentifier) + val rtpFrame = RtpFrame(buffer, rtpTs, RtpConstants.RTP_HEADER_LENGTH + size + 4, channelIdentifier) sum += size frames.add(rtpFrame) } diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/Av1Packet.kt b/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/Av1Packet.kt index df1d41cdf..a1d1df5ca 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/Av1Packet.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/Av1Packet.kt @@ -96,7 +96,7 @@ class Av1Packet: BasePacket( val oSize = if (isFirstPacket) obuList.size else 1 buffer[RtpConstants.RTP_HEADER_LENGTH] = generateAv1AggregationHeader(bufferInfo.isKeyframe(), isFirstPacket, isLastPacket, oSize) updateSeq(buffer) - val rtpFrame = RtpFrame(buffer, rtpTs, buffer.size, rtpPort, rtcpPort, channelIdentifier) + val rtpFrame = RtpFrame(buffer, rtpTs, buffer.size, channelIdentifier) frames.add(rtpFrame) } callback(frames) diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/BasePacket.kt b/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/BasePacket.kt index 6bc297f86..a932a8faf 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/BasePacket.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/BasePacket.kt @@ -30,8 +30,6 @@ import kotlin.experimental.or abstract class BasePacket(private val clock: Long, private val payloadType: Int) { protected var channelIdentifier: Int = 0 - protected var rtpPort = 0 - protected var rtcpPort = 0 private var seq = 0L private var ssrc = 0L protected val maxPacketSize = RtpConstants.MTU - 28 @@ -43,11 +41,6 @@ abstract class BasePacket(private val clock: Long, private val payloadType: Int) callback: (List) -> Unit ) - fun setPorts(rtpPort: Int, rtcpPort: Int) { - this.rtpPort = rtpPort - this.rtcpPort = rtcpPort - } - open fun reset() { seq = 0 ssrc = 0 diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/G711Packet.kt b/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/G711Packet.kt index 3ef9747d7..37be354c9 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/G711Packet.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/G711Packet.kt @@ -54,7 +54,7 @@ class G711Packet( markPacket(buffer) val rtpTs = updateTimeStamp(buffer, ts) updateSeq(buffer) - val rtpFrame = RtpFrame(buffer, rtpTs, RtpConstants.RTP_HEADER_LENGTH + size , rtpPort, rtcpPort, channelIdentifier) + val rtpFrame = RtpFrame(buffer, rtpTs, RtpConstants.RTP_HEADER_LENGTH + size , channelIdentifier) sum += size frames.add(rtpFrame) } diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/H264Packet.kt b/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/H264Packet.kt index b4ffd3888..a84465418 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/H264Packet.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/H264Packet.kt @@ -71,7 +71,7 @@ class H264Packet( markPacket(buffer) //mark end frame System.arraycopy(it, 0, buffer, RtpConstants.RTP_HEADER_LENGTH, it.size) updateSeq(buffer) - val rtpFrame = RtpFrame(buffer, rtpTs, it.size + RtpConstants.RTP_HEADER_LENGTH, rtpPort, rtcpPort, channelIdentifier) + val rtpFrame = RtpFrame(buffer, rtpTs, it.size + RtpConstants.RTP_HEADER_LENGTH, channelIdentifier) frames.add(rtpFrame) sendKeyFrame = true } ?: run { @@ -87,7 +87,7 @@ class H264Packet( val rtpTs = updateTimeStamp(buffer, ts) markPacket(buffer) //mark end frame updateSeq(buffer) - val rtpFrame = RtpFrame(buffer, rtpTs, buffer.size, rtpPort, rtcpPort, channelIdentifier) + val rtpFrame = RtpFrame(buffer, rtpTs, buffer.size, channelIdentifier) frames.add(rtpFrame) } else { // Set FU-A header @@ -116,7 +116,7 @@ class H264Packet( markPacket(buffer) //mark end frame } updateSeq(buffer) - val rtpFrame = RtpFrame(buffer, rtpTs, buffer.size, rtpPort, rtcpPort, channelIdentifier) + val rtpFrame = RtpFrame(buffer, rtpTs, buffer.size, channelIdentifier) frames.add(rtpFrame) // Switch start bit header[1] = header[1] and 0x7F diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/H265Packet.kt b/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/H265Packet.kt index abbb32d84..6b3e6a21d 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/H265Packet.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/H265Packet.kt @@ -63,7 +63,7 @@ class H265Packet: BasePacket( val rtpTs = updateTimeStamp(buffer, ts) markPacket(buffer) //mark end frame updateSeq(buffer) - val rtpFrame = RtpFrame(buffer, rtpTs, buffer.size, rtpPort, rtcpPort, channelIdentifier) + val rtpFrame = RtpFrame(buffer, rtpTs, buffer.size, channelIdentifier) frames.add(rtpFrame) } else { //Set PayloadHdr (16bit type=49) @@ -98,7 +98,7 @@ class H265Packet: BasePacket( markPacket(buffer) //mark end frame } updateSeq(buffer) - val rtpFrame = RtpFrame(buffer, rtpTs, buffer.size, rtpPort, rtcpPort, channelIdentifier) + val rtpFrame = RtpFrame(buffer, rtpTs, buffer.size, channelIdentifier) frames.add(rtpFrame) // Switch start bit header[2] = header[2] and 0x7F diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/OpusPacket.kt b/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/OpusPacket.kt index cab19320c..e6e4441c0 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/OpusPacket.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtp/packets/OpusPacket.kt @@ -54,7 +54,7 @@ class OpusPacket( markPacket(buffer) val rtpTs = updateTimeStamp(buffer, ts) updateSeq(buffer) - val rtpFrame = RtpFrame(buffer, rtpTs, RtpConstants.RTP_HEADER_LENGTH + size , rtpPort, rtcpPort, channelIdentifier) + val rtpFrame = RtpFrame(buffer, rtpTs, RtpConstants.RTP_HEADER_LENGTH + size , channelIdentifier) sum += size frames.add(rtpFrame) } diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/BaseRtpSocket.kt b/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/BaseRtpSocket.kt index 599686c8b..37a1856e1 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/BaseRtpSocket.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/BaseRtpSocket.kt @@ -16,10 +16,10 @@ package com.pedro.rtsp.rtp.sockets +import com.pedro.common.socket.TcpStreamSocket import com.pedro.rtsp.rtsp.Protocol import com.pedro.rtsp.rtsp.RtpFrame import java.io.IOException -import java.io.OutputStream /** * Created by pedro on 7/11/18. @@ -28,20 +28,24 @@ abstract class BaseRtpSocket { companion object { @JvmStatic - fun getInstance(protocol: Protocol, videoSourcePort: Int, audioSourcePort: Int): BaseRtpSocket { + fun getInstance( + protocol: Protocol, host: String, + videoSourcePort: Int, audioSourcePort: Int, + videoServerPort: Int, audioServerPort: Int, + ): BaseRtpSocket { return if (protocol === Protocol.TCP) { RtpSocketTcp() } else { - RtpSocketUdp(videoSourcePort, audioSourcePort) + RtpSocketUdp(host, videoSourcePort, audioSourcePort, videoServerPort, audioServerPort) } } } @Throws(IOException::class) - abstract fun setDataStream(outputStream: OutputStream, host: String) + abstract suspend fun setSocket(socket: TcpStreamSocket) @Throws(IOException::class) abstract suspend fun sendFrame(rtpFrame: RtpFrame) - abstract fun close() + abstract suspend fun close() } \ No newline at end of file diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/RtpSocketTcp.kt b/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/RtpSocketTcp.kt index 7c629af74..7cf44579d 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/RtpSocketTcp.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/RtpSocketTcp.kt @@ -16,22 +16,21 @@ package com.pedro.rtsp.rtp.sockets +import com.pedro.common.socket.TcpStreamSocket import com.pedro.rtsp.rtsp.RtpFrame -import com.pedro.rtsp.utils.RtpConstants import java.io.IOException -import java.io.OutputStream /** * Created by pedro on 7/11/18. */ class RtpSocketTcp : BaseRtpSocket() { - private var outputStream: OutputStream? = null + private var socket: TcpStreamSocket? = null private val tcpHeader: ByteArray = byteArrayOf('$'.code.toByte(), 0, 0, 0) @Throws(IOException::class) - override fun setDataStream(outputStream: OutputStream, host: String) { - this.outputStream = outputStream + override suspend fun setSocket(socket: TcpStreamSocket) { + this.socket = socket } @Throws(IOException::class) @@ -39,18 +38,16 @@ class RtpSocketTcp : BaseRtpSocket() { sendFrameTCP(rtpFrame) } - override fun close() {} + override suspend fun close() {} @Throws(IOException::class) - private fun sendFrameTCP(rtpFrame: RtpFrame) { - synchronized(RtpConstants.lock) { - val len = rtpFrame.length - tcpHeader[1] = (2 * rtpFrame.channelIdentifier).toByte() - tcpHeader[2] = (len shr 8).toByte() - tcpHeader[3] = (len and 0xFF).toByte() - outputStream?.write(tcpHeader) - outputStream?.write(rtpFrame.buffer, 0, len) - outputStream?.flush() - } + private suspend fun sendFrameTCP(rtpFrame: RtpFrame) { + val len = rtpFrame.length + tcpHeader[1] = (2 * rtpFrame.channelIdentifier).toByte() + tcpHeader[2] = (len shr 8).toByte() + tcpHeader[3] = (len and 0xFF).toByte() + socket?.write(tcpHeader) + socket?.write(rtpFrame.buffer, 0, len) + socket?.flush() } } \ No newline at end of file diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/RtpSocketUdp.kt b/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/RtpSocketUdp.kt index 3eb688629..6601f0e12 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/RtpSocketUdp.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/RtpSocketUdp.kt @@ -16,35 +16,31 @@ package com.pedro.rtsp.rtp.sockets +import com.pedro.common.socket.TcpStreamSocket +import com.pedro.common.socket.UdpStreamSocket import com.pedro.rtsp.rtsp.RtpFrame -import com.pedro.rtsp.utils.RtpConstants import java.io.IOException -import java.io.OutputStream -import java.net.DatagramPacket -import java.net.InetAddress -import java.net.MulticastSocket /** * Created by pedro on 7/11/18. */ class RtpSocketUdp( + host: String, videoSourcePort: Int, audioSourcePort: Int, - private var multicastSocketVideo: MulticastSocket? = null, - private var multicastSocketAudio: MulticastSocket? = null + videoServerPort: Int, audioServerPort: Int, ) : BaseRtpSocket() { - private val datagramPacket = DatagramPacket(byteArrayOf(0), 1) - - init { - if (multicastSocketVideo == null) multicastSocketVideo = MulticastSocket(videoSourcePort) - multicastSocketVideo?.timeToLive = 64 - if (multicastSocketAudio == null) multicastSocketAudio = MulticastSocket(audioSourcePort) - multicastSocketAudio?.timeToLive = 64 - } + private val videoSocket = UdpStreamSocket( + host, videoServerPort, videoSourcePort + ) + private val audioSocket = UdpStreamSocket( + host, audioServerPort, audioSourcePort + ) @Throws(IOException::class) - override fun setDataStream(outputStream: OutputStream, host: String) { - datagramPacket.address = InetAddress.getByName(host) + override suspend fun setSocket(socket: TcpStreamSocket) { + videoSocket.connect() + audioSocket.connect() } @Throws(IOException::class) @@ -52,22 +48,17 @@ class RtpSocketUdp( sendFrameUDP(rtpFrame) } - override fun close() { - multicastSocketVideo?.close() - multicastSocketAudio?.close() + override suspend fun close() { + videoSocket.close() + audioSocket.close() } @Throws(IOException::class) - private fun sendFrameUDP(rtpFrame: RtpFrame) { - synchronized(RtpConstants.lock) { - datagramPacket.data = rtpFrame.buffer - datagramPacket.port = rtpFrame.rtpPort - datagramPacket.length = rtpFrame.length - if (rtpFrame.isVideoFrame()) { - multicastSocketVideo?.send(datagramPacket) - } else { - multicastSocketAudio?.send(datagramPacket) - } + private suspend fun sendFrameUDP(rtpFrame: RtpFrame) { + if (rtpFrame.isVideoFrame()) { + videoSocket.writePacket(rtpFrame.buffer) + } else { + audioSocket.writePacket(rtpFrame.buffer) } } } \ No newline at end of file diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtpFrame.kt b/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtpFrame.kt index b98d62c92..c1120acfa 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtpFrame.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtpFrame.kt @@ -21,8 +21,7 @@ import com.pedro.rtsp.utils.RtpConstants /** * Created by pedro on 7/11/18. */ -data class RtpFrame(val buffer: ByteArray, val timeStamp: Long, val length: Int, - val rtpPort: Int, val rtcpPort: Int, val channelIdentifier: Int) { +data class RtpFrame(val buffer: ByteArray, val timeStamp: Long, val length: Int, val channelIdentifier: Int) { fun isVideoFrame(): Boolean = channelIdentifier == RtpConstants.trackVideo @@ -35,8 +34,6 @@ data class RtpFrame(val buffer: ByteArray, val timeStamp: Long, val length: Int, if (!buffer.contentEquals(other.buffer)) return false if (timeStamp != other.timeStamp) return false if (length != other.length) return false - if (rtpPort != other.rtpPort) return false - if (rtcpPort != other.rtcpPort) return false if (channelIdentifier != other.channelIdentifier) return false return true @@ -46,8 +43,6 @@ data class RtpFrame(val buffer: ByteArray, val timeStamp: Long, val length: Int, var result = buffer.contentHashCode() result = 31 * result + timeStamp.hashCode() result = 31 * result + length - result = 31 * result + rtpPort - result = 31 * result + rtcpPort result = 31 * result + channelIdentifier return result } diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt b/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt index e108ee4a9..16937c4a0 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt @@ -20,10 +20,10 @@ import android.media.MediaCodec import android.util.Log import com.pedro.common.AudioCodec import com.pedro.common.ConnectChecker -import com.pedro.common.TLSSocketFactory import com.pedro.common.UrlParser import com.pedro.common.VideoCodec import com.pedro.common.onMainThread +import com.pedro.common.socket.TcpStreamSocket import com.pedro.rtsp.rtsp.commands.CommandsManager import com.pedro.rtsp.rtsp.commands.Method import com.pedro.rtsp.utils.RtpConstants @@ -38,13 +38,9 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.withTimeoutOrNull import java.io.* -import java.net.InetSocketAddress -import java.net.Socket -import java.net.SocketAddress import java.net.SocketTimeoutException import java.net.URISyntaxException import java.nio.ByteBuffer -import java.security.GeneralSecurityException import javax.net.ssl.TrustManager /** @@ -57,9 +53,7 @@ class RtspClient(private val connectChecker: ConnectChecker) { private val validSchemes = arrayOf("rtsp", "rtsps") //sockets objects - private var connectionSocket: Socket? = null - private var reader: BufferedReader? = null - private var writer: BufferedWriter? = null + private var socket: TcpStreamSocket? = null private var scope = CoroutineScope(Dispatchers.IO) private var scopeRetry = CoroutineScope(Dispatchers.IO) private var job: Job? = null @@ -222,8 +216,12 @@ class RtspClient(private val connectChecker: ConnectChecker) { val error = runCatching { commandsManager.setUrl(host, port, "/$path") rtspSender.setSocketsInfo(commandsManager.protocol, + host, commandsManager.videoClientPorts, - commandsManager.audioClientPorts) + commandsManager.audioClientPorts, + commandsManager.videoServerPorts, + commandsManager.audioServerPorts + ) if (!commandsManager.audioDisabled) { rtspSender.setAudioInfo(commandsManager.sampleRate) } @@ -242,31 +240,16 @@ class RtspClient(private val connectChecker: ConnectChecker) { } rtspSender.setVideoInfo(commandsManager.sps!!, commandsManager.pps, commandsManager.vps) } - if (!tlsEnabled) { - connectionSocket = Socket() - val socketAddress: SocketAddress = InetSocketAddress(host, port) - connectionSocket?.connect(socketAddress, 5000) - } else { - try { - val socketFactory = TLSSocketFactory(certificates) - connectionSocket = socketFactory.createSocket(host, port) - } catch (e: GeneralSecurityException) { - throw IOException("Create SSL socket failed: ${e.message}") - } - } - connectionSocket?.soTimeout = 5000 - val reader = BufferedReader(InputStreamReader(connectionSocket?.getInputStream())) - val outputStream = connectionSocket?.getOutputStream() - val writer = BufferedWriter(OutputStreamWriter(outputStream)) - this@RtspClient.reader = reader - this@RtspClient.writer = writer - writer.write(commandsManager.createOptions()) - writer.flush() - commandsManager.getResponse(reader, Method.OPTIONS) - writer.write(commandsManager.createAnnounce()) - writer.flush() + val socket = TcpStreamSocket(host, port, tlsEnabled) + this@RtspClient.socket = socket + socket.connect() + socket.write(commandsManager.createOptions()) + socket.flush() + commandsManager.getResponse(socket, Method.OPTIONS) + socket.write(commandsManager.createAnnounce()) + socket.flush() //check if you need credential for stream, if you need try connect with credential - val announceResponse = commandsManager.getResponse(reader, Method.ANNOUNCE) + val announceResponse = commandsManager.getResponse(socket, Method.ANNOUNCE) when (announceResponse.status) { 403 -> { onMainThread { @@ -282,9 +265,9 @@ class RtspClient(private val connectChecker: ConnectChecker) { } return@launch } else { - writer.write(commandsManager.createAnnounceWithAuth(announceResponse.text)) - writer.flush() - when (commandsManager.getResponse(reader, Method.ANNOUNCE).status) { + socket.write(commandsManager.createAnnounceWithAuth(announceResponse.text)) + socket.flush() + when (commandsManager.getResponse(socket, Method.ANNOUNCE).status) { 401 -> { onMainThread { connectChecker.onAuthError() @@ -316,9 +299,9 @@ class RtspClient(private val connectChecker: ConnectChecker) { } } if (!commandsManager.videoDisabled) { - writer.write(commandsManager.createSetup(RtpConstants.trackVideo)) - writer.flush() - val setupVideoStatus = commandsManager.getResponse(reader, Method.SETUP).status + socket.write(commandsManager.createSetup(RtpConstants.trackVideo)) + socket.flush() + val setupVideoStatus = commandsManager.getResponse(socket, Method.SETUP).status if (setupVideoStatus != 200) { onMainThread { connectChecker.onConnectionFailed("Error configure stream, setup video $setupVideoStatus") @@ -327,9 +310,9 @@ class RtspClient(private val connectChecker: ConnectChecker) { } } if (!commandsManager.audioDisabled) { - writer.write(commandsManager.createSetup(RtpConstants.trackAudio)) - writer.flush() - val setupAudioStatus = commandsManager.getResponse(reader, Method.SETUP).status + socket.write(commandsManager.createSetup(RtpConstants.trackAudio)) + socket.flush() + val setupAudioStatus = commandsManager.getResponse(socket, Method.SETUP).status if (setupAudioStatus != 200) { onMainThread { connectChecker.onConnectionFailed("Error configure stream, setup audio $setupAudioStatus") @@ -337,26 +320,16 @@ class RtspClient(private val connectChecker: ConnectChecker) { return@launch } } - writer.write(commandsManager.createRecord()) - writer.flush() - val recordStatus = commandsManager.getResponse(reader, Method.RECORD).status + socket.write(commandsManager.createRecord()) + socket.flush() + val recordStatus = commandsManager.getResponse(socket, Method.RECORD).status if (recordStatus != 200) { onMainThread { connectChecker.onConnectionFailed("Error configure stream, record $recordStatus") } return@launch } - outputStream?.let { out -> - rtspSender.setDataStream(out, host) - } - val videoPorts = commandsManager.videoServerPorts - val audioPorts = commandsManager.audioServerPorts - if (!commandsManager.videoDisabled) { - rtspSender.setVideoPorts(videoPorts[0], videoPorts[1]) - } - if (!commandsManager.audioDisabled) { - rtspSender.setAudioPorts(audioPorts[0], audioPorts[1]) - } + rtspSender.setSocket(socket) rtspSender.start() reTries = numRetry onMainThread { @@ -381,9 +354,9 @@ class RtspClient(private val connectChecker: ConnectChecker) { val error = runCatching { if (isAlive()) { delay(2000) - reader?.let { r -> - if (r.ready()) { - val command = commandsManager.getResponse(r) + socket?.let { socket -> + if (socket.isConnected()) { + val command = commandsManager.getResponse(socket) //Do something depend of command if required } } @@ -404,10 +377,10 @@ class RtspClient(private val connectChecker: ConnectChecker) { Send a heartbeat to know if server is alive using Echo Protocol. Your firewall could block it. */ - private fun isAlive(): Boolean { - val connected = connectionSocket?.isConnected ?: false + private suspend fun isAlive(): Boolean { + val connected = socket?.isConnected() ?: false if (!checkServerAlive) return connected - val reachable = connectionSocket?.inetAddress?.isReachable(5000) ?: false + val reachable = socket?.isReachable() ?: false return if (connected && !reachable) false else connected } @@ -421,15 +394,11 @@ class RtspClient(private val connectChecker: ConnectChecker) { if (isStreaming) rtspSender.stop() val error = runCatching { withTimeoutOrNull(100) { - writer?.write(commandsManager.createTeardown()) - writer?.flush() + socket?.write(commandsManager.createTeardown()) + socket?.flush() } - connectionSocket?.close() - reader?.close() - reader = null - writer?.close() - writer = null - connectionSocket = null + socket?.close() + socket = null Log.i(TAG, "write teardown success") }.exceptionOrNull() if (error != null) { diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspSender.kt b/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspSender.kt index 90f7876c8..1edbea697 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspSender.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspSender.kt @@ -23,6 +23,7 @@ import com.pedro.common.BitrateManager import com.pedro.common.ConnectChecker import com.pedro.common.VideoCodec import com.pedro.common.onMainThread +import com.pedro.common.socket.TcpStreamSocket import com.pedro.common.trySend import com.pedro.rtsp.rtcp.BaseSenderReport import com.pedro.rtsp.rtp.packets.* @@ -40,7 +41,6 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.runInterruptible import java.io.IOException -import java.io.OutputStream import java.nio.ByteBuffer import java.util.* import java.util.concurrent.* @@ -81,9 +81,13 @@ class RtspSender( } @Throws(IOException::class) - fun setSocketsInfo(protocol: Protocol, videoSourcePorts: IntArray, audioSourcePorts: IntArray) { - rtpSocket = BaseRtpSocket.getInstance(protocol, videoSourcePorts[0], audioSourcePorts[0]) - baseSenderReport = BaseSenderReport.getInstance(protocol, videoSourcePorts[1], audioSourcePorts[1]) + suspend fun setSocketsInfo( + protocol: Protocol, host: String, + videoSourcePorts: IntArray, audioSourcePorts: IntArray, + videoServerPorts: IntArray, audioServerPorts: IntArray, + ) { + rtpSocket = BaseRtpSocket.getInstance(protocol, host, videoSourcePorts[0], audioSourcePorts[0], videoServerPorts[0], audioServerPorts[0]) + baseSenderReport = BaseSenderReport.getInstance(protocol, host, videoSourcePorts[1], audioSourcePorts[1], videoServerPorts[1], audioServerPorts[1]) } fun setVideoInfo(sps: ByteArray, pps: ByteArray?, vps: ByteArray?) { @@ -109,17 +113,9 @@ class RtspSender( } @Throws(IOException::class) - fun setDataStream(outputStream: OutputStream, host: String) { - rtpSocket?.setDataStream(outputStream, host) - baseSenderReport?.setDataStream(outputStream, host) - } - - fun setVideoPorts(rtpPort: Int, rtcpPort: Int) { - videoPacket?.setPorts(rtpPort, rtcpPort) - } - - fun setAudioPorts(rtpPort: Int, rtcpPort: Int) { - audioPacket?.setPorts(rtpPort, rtcpPort) + suspend fun setSocket(socket: TcpStreamSocket) { + rtpSocket?.setSocket(socket) + baseSenderReport?.setSocket(socket) } fun sendVideoFrame(h264Buffer: ByteBuffer, info: MediaCodec.BufferInfo) { diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtsp/commands/CommandsManager.kt b/rtsp/src/main/java/com/pedro/rtsp/rtsp/commands/CommandsManager.kt index f56bf6a3a..95ec2d15a 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtsp/commands/CommandsManager.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtsp/commands/CommandsManager.kt @@ -21,6 +21,7 @@ import com.pedro.common.AudioCodec import com.pedro.common.TimeUtils import com.pedro.common.VideoCodec import com.pedro.common.getMd5Hash +import com.pedro.common.socket.TcpStreamSocket import com.pedro.rtsp.rtsp.Protocol import com.pedro.rtsp.rtsp.commands.SdpBody.createAV1Body import com.pedro.rtsp.rtsp.commands.SdpBody.createAacBody @@ -31,7 +32,6 @@ import com.pedro.rtsp.rtsp.commands.SdpBody.createOpusBody import com.pedro.rtsp.utils.RtpConstants import com.pedro.rtsp.utils.encodeToString import com.pedro.rtsp.utils.getData -import java.io.BufferedReader import java.io.IOException import java.nio.ByteBuffer import java.util.regex.Pattern @@ -58,7 +58,7 @@ open class CommandsManager { private val timeStamp: Long var sampleRate = 32000 var isStereo = true - var protocol: Protocol = Protocol.TCP + var protocol: Protocol = Protocol.UDP var videoDisabled = false var audioDisabled = false private val commandParser = CommandParser() @@ -252,10 +252,10 @@ open class CommandsManager { } @Throws(IOException::class) - fun getResponse(reader: BufferedReader, method: Method = Method.UNKNOWN): Command { + suspend fun getResponse(socket: TcpStreamSocket, method: Method = Method.UNKNOWN): Command { var response = "" var line: String? - while (reader.readLine().also { line = it } != null) { + while (socket.readLine().also { line = it } != null) { response += "${line ?: ""}\n" //end of response if ((line?.length ?: 0) < 3) break diff --git a/rtsp/src/test/java/com/pedro/rtsp/rtcp/RtcpReportTest.kt b/rtsp/src/test/java/com/pedro/rtsp/rtcp/RtcpReportTest.kt index 7caafbf6a..86d9ba6a4 100644 --- a/rtsp/src/test/java/com/pedro/rtsp/rtcp/RtcpReportTest.kt +++ b/rtsp/src/test/java/com/pedro/rtsp/rtcp/RtcpReportTest.kt @@ -65,7 +65,7 @@ class RtcpReportTest { fun `GIVEN multiple video or audio rtp frames WHEN update rtcp tcp send THEN send only 1 of video and 1 of audio each 3 seconds`() = runTest { Utils.useStatics(listOf(timeUtilsMocked)) { val senderReportTcp = BaseSenderReport.getInstance(Protocol.TCP, 0, 1) - senderReportTcp.setDataStream(outputMocked, "127.0.0.1") + senderReportTcp.setSocket(outputMocked, "127.0.0.1") senderReportTcp.setSSRC(0, 1) val fakeFrameVideo = RtpFrame(byteArrayOf(0x00, 0x00, 0x00), 0, 3, 0, 0, RtpConstants.trackVideo) val fakeFrameAudio = RtpFrame(byteArrayOf(0x00, 0x00, 0x00), 0, 3, 0, 0, RtpConstants.trackAudio) diff --git a/rtsp/src/test/java/com/pedro/rtsp/rtp/RtpStreamSocketTest.kt b/rtsp/src/test/java/com/pedro/rtsp/rtp/RtpStreamSocketTest.kt index 1931969af..700554170 100644 --- a/rtsp/src/test/java/com/pedro/rtsp/rtp/RtpStreamSocketTest.kt +++ b/rtsp/src/test/java/com/pedro/rtsp/rtp/RtpStreamSocketTest.kt @@ -49,7 +49,7 @@ class RtpStreamSocketTest { @Test fun `GIVEN multiple video or audio rtp frames WHEN update rtcp tcp send THEN send only 1 of video and 1 of audio each 3 seconds`() = runTest { val senderReportTcp = BaseRtpSocket.getInstance(Protocol.TCP, 0, 1) - senderReportTcp.setDataStream(outputMocked, "127.0.0.1") + senderReportTcp.setSocket(outputMocked, "127.0.0.1") val fakeFrameVideo = RtpFrame(byteArrayOf(0x00, 0x00, 0x00), 0, 3, 0, 0, RtpConstants.trackVideo) val fakeFrameAudio = RtpFrame(byteArrayOf(0x00, 0x00, 0x00), 0, 3, 0, 0, RtpConstants.trackAudio) (0 until 10).forEach { value -> @@ -65,7 +65,7 @@ class RtpStreamSocketTest { @Test fun `GIVEN multiple video or audio rtp frames WHEN update rtcp udp send THEN send only 1 of video and 1 of audio each 3 seconds`() = runTest { val senderReportUdp = RtpSocketUdp(11111, 11112, multicastSocketMocked, multicastSocketMocked) - senderReportUdp.setDataStream(outputMocked, "127.0.0.1") + senderReportUdp.setSocket(outputMocked, "127.0.0.1") val fakeFrameVideo = RtpFrame(byteArrayOf(0x00, 0x00, 0x00), 0, 3, 0, 0, RtpConstants.trackVideo) val fakeFrameAudio = RtpFrame(byteArrayOf(0x00, 0x00, 0x00), 0, 3, 0, 0, RtpConstants.trackAudio) (0 until 10).forEach { value -> From f461eee3273904ab939fcc424748fc557af76c91 Mon Sep 17 00:00:00 2001 From: pedroSG94 Date: Sun, 22 Sep 2024 20:44:38 +0200 Subject: [PATCH 04/13] fix rtcp udp packets --- rtsp/src/main/java/com/pedro/rtsp/rtcp/BaseSenderReport.kt | 4 ++-- rtsp/src/main/java/com/pedro/rtsp/rtcp/SenderReportUdp.kt | 7 ++++--- .../main/java/com/pedro/rtsp/rtp/sockets/RtpSocketUdp.kt | 5 +++-- rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt | 2 +- rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspSender.kt | 2 +- .../java/com/pedro/rtsp/rtsp/commands/CommandsManager.kt | 2 +- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtcp/BaseSenderReport.kt b/rtsp/src/main/java/com/pedro/rtsp/rtcp/BaseSenderReport.kt index 18c559b83..6674b0764 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtcp/BaseSenderReport.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtcp/BaseSenderReport.kt @@ -30,8 +30,8 @@ import java.io.IOException abstract class BaseSenderReport internal constructor() { private val interval: Long = 3000 - private val videoBuffer = ByteArray(RtpConstants.MTU) - private val audioBuffer = ByteArray(RtpConstants.MTU) + private val videoBuffer = ByteArray(RtpConstants.REPORT_PACKET_LENGTH) + private val audioBuffer = ByteArray(RtpConstants.REPORT_PACKET_LENGTH) private var videoTime: Long = 0 private var audioTime: Long = 0 private var videoPacketCount = 0L diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtcp/SenderReportUdp.kt b/rtsp/src/main/java/com/pedro/rtsp/rtcp/SenderReportUdp.kt index b3e6ab0d9..c57220e19 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtcp/SenderReportUdp.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtcp/SenderReportUdp.kt @@ -19,6 +19,7 @@ package com.pedro.rtsp.rtcp import com.pedro.common.socket.TcpStreamSocket import com.pedro.common.socket.UdpStreamSocket import com.pedro.rtsp.rtsp.RtpFrame +import com.pedro.rtsp.utils.RtpConstants import java.io.IOException /** @@ -31,10 +32,10 @@ class SenderReportUdp( ) : BaseSenderReport() { private val videoSocket = UdpStreamSocket( - host, videoServerPort, videoSourcePort + host, videoServerPort, videoSourcePort, receiveSize = RtpConstants.REPORT_PACKET_LENGTH ) private val audioSocket = UdpStreamSocket( - host, audioServerPort, audioSourcePort + host, audioServerPort, audioSourcePort, receiveSize = RtpConstants.REPORT_PACKET_LENGTH ) @Throws(IOException::class) @@ -54,7 +55,7 @@ class SenderReportUdp( } @Throws(IOException::class) - private suspend fun sendReportUDP(buffer: ByteArray,isVideo: Boolean) { + private suspend fun sendReportUDP(buffer: ByteArray, isVideo: Boolean) { if (isVideo) { videoSocket.writePacket(buffer) } else { diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/RtpSocketUdp.kt b/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/RtpSocketUdp.kt index 6601f0e12..40dacba2d 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/RtpSocketUdp.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/RtpSocketUdp.kt @@ -19,6 +19,7 @@ package com.pedro.rtsp.rtp.sockets import com.pedro.common.socket.TcpStreamSocket import com.pedro.common.socket.UdpStreamSocket import com.pedro.rtsp.rtsp.RtpFrame +import com.pedro.rtsp.utils.RtpConstants import java.io.IOException /** @@ -31,10 +32,10 @@ class RtpSocketUdp( ) : BaseRtpSocket() { private val videoSocket = UdpStreamSocket( - host, videoServerPort, videoSourcePort + host, videoServerPort, videoSourcePort, receiveSize = RtpConstants.MTU ) private val audioSocket = UdpStreamSocket( - host, audioServerPort, audioSourcePort + host, audioServerPort, audioSourcePort, receiveSize = RtpConstants.MTU ) @Throws(IOException::class) diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt b/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt index 16937c4a0..a6e46d674 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt @@ -377,7 +377,7 @@ class RtspClient(private val connectChecker: ConnectChecker) { Send a heartbeat to know if server is alive using Echo Protocol. Your firewall could block it. */ - private suspend fun isAlive(): Boolean { + private fun isAlive(): Boolean { val connected = socket?.isConnected() ?: false if (!checkServerAlive) return connected val reachable = socket?.isReachable() ?: false diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspSender.kt b/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspSender.kt index 1edbea697..401208329 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspSender.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspSender.kt @@ -81,7 +81,7 @@ class RtspSender( } @Throws(IOException::class) - suspend fun setSocketsInfo( + fun setSocketsInfo( protocol: Protocol, host: String, videoSourcePorts: IntArray, audioSourcePorts: IntArray, videoServerPorts: IntArray, audioServerPorts: IntArray, diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtsp/commands/CommandsManager.kt b/rtsp/src/main/java/com/pedro/rtsp/rtsp/commands/CommandsManager.kt index 95ec2d15a..ab77ea259 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtsp/commands/CommandsManager.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtsp/commands/CommandsManager.kt @@ -58,7 +58,7 @@ open class CommandsManager { private val timeStamp: Long var sampleRate = 32000 var isStereo = true - var protocol: Protocol = Protocol.UDP + var protocol: Protocol = Protocol.TCP var videoDisabled = false var audioDisabled = false private val commandParser = CommandParser() From 537c93f0b529955e11ebcc7388dccbfa5e9d4e19 Mon Sep 17 00:00:00 2001 From: pedroSG94 Date: Sun, 22 Sep 2024 20:51:25 +0200 Subject: [PATCH 05/13] update tls certificate method --- rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt b/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt index a6e46d674..0981821ad 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt @@ -66,7 +66,7 @@ class RtspClient(private val connectChecker: ConnectChecker) { //for secure transport private var tlsEnabled = false - private var certificates: Array? = null + private var certificates: TrustManager? = null private val commandsManager: CommandsManager = CommandsManager() private val rtspSender: RtspSender = RtspSender(connectChecker, commandsManager) private var url: String? = null @@ -90,7 +90,7 @@ class RtspClient(private val connectChecker: ConnectChecker) { /** * Add certificates for TLS connection */ - fun addCertificates(certificates: Array?) { + fun addCertificates(certificates: TrustManager?) { this.certificates = certificates } @@ -240,7 +240,7 @@ class RtspClient(private val connectChecker: ConnectChecker) { } rtspSender.setVideoInfo(commandsManager.sps!!, commandsManager.pps, commandsManager.vps) } - val socket = TcpStreamSocket(host, port, tlsEnabled) + val socket = TcpStreamSocket(host, port, tlsEnabled, certificates) this@RtspClient.socket = socket socket.connect() socket.write(commandsManager.createOptions()) From be98bef1d5ed61ee287c874b3c6d757368ab130b Mon Sep 17 00:00:00 2001 From: pedroSG94 Date: Sun, 22 Sep 2024 21:28:32 +0200 Subject: [PATCH 06/13] migrating rtmp --- .../util/streamclient/GenericStreamClient.kt | 2 +- .../util/streamclient/RtmpStreamClient.kt | 2 +- .../util/streamclient/RtspStreamClient.kt | 2 +- .../com/pedro/rtmp/rtmp/CommandsManager.kt | 62 ++--- .../pedro/rtmp/rtmp/CommandsManagerAmf0.kt | 40 ++-- .../pedro/rtmp/rtmp/CommandsManagerAmf3.kt | 40 ++-- .../java/com/pedro/rtmp/rtmp/Handshake.kt | 42 ++-- .../java/com/pedro/rtmp/rtmp/RtmpClient.kt | 8 +- .../pedro/rtmp/rtmp/message/BasicHeader.kt | 11 +- .../com/pedro/rtmp/rtmp/message/RtmpHeader.kt | 62 ++--- .../pedro/rtmp/rtmp/message/RtmpMessage.kt | 34 +-- .../com/pedro/rtmp/utils/socket/RtmpSocket.kt | 26 ++- .../com/pedro/rtmp/utils/socket/TcpSocket.kt | 99 ++++---- .../rtmp/utils/socket/TcpTunneledSocket.kt | 220 ++++++++++++------ 14 files changed, 358 insertions(+), 292 deletions(-) diff --git a/library/src/main/java/com/pedro/library/util/streamclient/GenericStreamClient.kt b/library/src/main/java/com/pedro/library/util/streamclient/GenericStreamClient.kt index 02ba6e339..c6a3e6200 100644 --- a/library/src/main/java/com/pedro/library/util/streamclient/GenericStreamClient.kt +++ b/library/src/main/java/com/pedro/library/util/streamclient/GenericStreamClient.kt @@ -45,7 +45,7 @@ class GenericStreamClient( /** * Add certificates for TLS connection */ - fun addCertificates(certificates: Array?) { + fun addCertificates(certificates: TrustManager?) { rtmpClient.addCertificates(certificates) rtspClient.addCertificates(certificates) } diff --git a/library/src/main/java/com/pedro/library/util/streamclient/RtmpStreamClient.kt b/library/src/main/java/com/pedro/library/util/streamclient/RtmpStreamClient.kt index 6640cc17a..166ff8c64 100644 --- a/library/src/main/java/com/pedro/library/util/streamclient/RtmpStreamClient.kt +++ b/library/src/main/java/com/pedro/library/util/streamclient/RtmpStreamClient.kt @@ -40,7 +40,7 @@ class RtmpStreamClient( /** * Add certificates for TLS connection */ - fun addCertificates(certificates: Array?) { + fun addCertificates(certificates: TrustManager?) { rtmpClient.addCertificates(certificates) } diff --git a/library/src/main/java/com/pedro/library/util/streamclient/RtspStreamClient.kt b/library/src/main/java/com/pedro/library/util/streamclient/RtspStreamClient.kt index 168e25a2e..1dee2172e 100644 --- a/library/src/main/java/com/pedro/library/util/streamclient/RtspStreamClient.kt +++ b/library/src/main/java/com/pedro/library/util/streamclient/RtspStreamClient.kt @@ -31,7 +31,7 @@ class RtspStreamClient( /** * Add certificates for TLS connection */ - fun addCertificates(certificates: Array?) { + fun addCertificates(certificates: TrustManager?) { rtspClient.addCertificates(certificates) } diff --git a/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManager.kt b/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManager.kt index 02c6afdca..8933d9538 100644 --- a/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManager.kt +++ b/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManager.kt @@ -93,13 +93,12 @@ abstract class CommandsManager { @Throws(IOException::class) suspend fun sendChunkSize(socket: RtmpSocket) { writeSync.withLock { - val output = socket.getOutStream() if (RtmpConfig.writeChunkSize != RtmpConfig.DEFAULT_CHUNK_SIZE) { val chunkSize = SetChunkSize(RtmpConfig.writeChunkSize) chunkSize.header.timeStamp = getCurrentTimestamp() chunkSize.header.messageStreamId = streamId - chunkSize.writeHeader(output) - chunkSize.writeBody(output) + chunkSize.writeHeader(socket) + chunkSize.writeBody(socket) socket.flush() Log.i(TAG, "send $chunkSize") } else { @@ -111,8 +110,7 @@ abstract class CommandsManager { @Throws(IOException::class) suspend fun sendConnect(auth: String, socket: RtmpSocket) { writeSync.withLock { - val output = socket.getOutStream() - sendConnect(auth, output) + sendConnectImp(auth, socket) socket.flush() } } @@ -120,16 +118,14 @@ abstract class CommandsManager { @Throws(IOException::class) suspend fun createStream(socket: RtmpSocket) { writeSync.withLock { - val output = socket.getOutStream() - createStream(output) + createStreamImp(socket) socket.flush() } } @Throws(IOException::class) - fun readMessageResponse(socket: RtmpSocket): RtmpMessage { - val input = socket.getInputStream() - val message = RtmpMessage.getRtmpMessage(input, readChunkSize, sessionHistory) + suspend fun readMessageResponse(socket: RtmpSocket): RtmpMessage { + val message = RtmpMessage.getRtmpMessage(socket, readChunkSize, sessionHistory) sessionHistory.setReadHeader(message.header) Log.i(TAG, "read $message") bytesRead += message.header.getPacketLength() @@ -139,8 +135,7 @@ abstract class CommandsManager { @Throws(IOException::class) suspend fun sendMetadata(socket: RtmpSocket) { writeSync.withLock { - val output = socket.getOutStream() - sendMetadata(output) + sendMetadataImp(socket) socket.flush() } } @@ -148,8 +143,7 @@ abstract class CommandsManager { @Throws(IOException::class) suspend fun sendPublish(socket: RtmpSocket) { writeSync.withLock { - val output = socket.getOutStream() - sendPublish(output) + sendPublishImp(socket) socket.flush() } } @@ -157,20 +151,18 @@ abstract class CommandsManager { @Throws(IOException::class) suspend fun sendWindowAcknowledgementSize(socket: RtmpSocket) { writeSync.withLock { - val output = socket.getOutStream() val windowAcknowledgementSize = WindowAcknowledgementSize(RtmpConfig.acknowledgementWindowSize, getCurrentTimestamp()) - windowAcknowledgementSize.writeHeader(output) - windowAcknowledgementSize.writeBody(output) + windowAcknowledgementSize.writeHeader(socket) + windowAcknowledgementSize.writeBody(socket) socket.flush() } } suspend fun sendPong(event: Event, socket: RtmpSocket) { writeSync.withLock { - val output = socket.getOutStream() val pong = UserControl(Type.PONG_REPLY, event) - pong.writeHeader(output) - pong.writeBody(output) + pong.writeHeader(socket) + pong.writeBody(socket) socket.flush() Log.i(TAG, "send pong") } @@ -179,8 +171,7 @@ abstract class CommandsManager { @Throws(IOException::class) suspend fun sendClose(socket: RtmpSocket) { writeSync.withLock { - val output = socket.getOutStream() - sendClose(output) + sendCloseImp(socket) socket.flush() } } @@ -190,11 +181,10 @@ abstract class CommandsManager { if (bytesRead >= RtmpConfig.acknowledgementWindowSize) { acknowledgementSequence += bytesRead bytesRead -= RtmpConfig.acknowledgementWindowSize - val output = socket.getOutStream() val acknowledgement = Acknowledgement(acknowledgementSequence) - acknowledgement.writeHeader(output) - acknowledgement.writeBody(output) - output.flush() + acknowledgement.writeHeader(socket) + acknowledgement.writeBody(socket) + socket.flush() Log.i(TAG, "send $acknowledgement") } } @@ -203,13 +193,12 @@ abstract class CommandsManager { @Throws(IOException::class) suspend fun sendVideoPacket(flvPacket: FlvPacket, socket: RtmpSocket): Int { writeSync.withLock { - val output = socket.getOutStream() if (incrementalTs) { flvPacket.timeStamp = ((TimeUtils.getCurrentTimeNano() / 1000 - startTs) / 1000) } val video = Video(flvPacket, streamId) - video.writeHeader(output) - video.writeBody(output) + video.writeHeader(socket) + video.writeBody(socket) socket.flush(true) return video.header.getPacketLength() //get packet size with header included to calculate bps } @@ -218,23 +207,22 @@ abstract class CommandsManager { @Throws(IOException::class) suspend fun sendAudioPacket(flvPacket: FlvPacket, socket: RtmpSocket): Int { writeSync.withLock { - val output = socket.getOutStream() if (incrementalTs) { flvPacket.timeStamp = ((TimeUtils.getCurrentTimeNano() / 1000 - startTs) / 1000) } val audio = Audio(flvPacket, streamId) - audio.writeHeader(output) - audio.writeBody(output) + audio.writeHeader(socket) + audio.writeBody(socket) socket.flush(true) return audio.header.getPacketLength() //get packet size with header included to calculate bps } } - abstract fun sendConnect(auth: String, output: OutputStream) - abstract fun createStream(output: OutputStream) - abstract fun sendMetadata(output: OutputStream) - abstract fun sendPublish(output: OutputStream) - abstract fun sendClose(output: OutputStream) + abstract suspend fun sendConnectImp(auth: String, socket: RtmpSocket) + abstract suspend fun createStreamImp(socket: RtmpSocket) + abstract suspend fun sendMetadataImp(socket: RtmpSocket) + abstract suspend fun sendPublishImp(socket: RtmpSocket) + abstract suspend fun sendCloseImp(socket: RtmpSocket) fun reset() { startTs = 0 diff --git a/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManagerAmf0.kt b/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManagerAmf0.kt index 5a4d81f23..49304e25f 100644 --- a/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManagerAmf0.kt +++ b/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManagerAmf0.kt @@ -32,10 +32,10 @@ import com.pedro.rtmp.rtmp.chunk.ChunkType import com.pedro.rtmp.rtmp.message.BasicHeader import com.pedro.rtmp.rtmp.message.command.CommandAmf0 import com.pedro.rtmp.rtmp.message.data.DataAmf0 -import java.io.OutputStream +import com.pedro.rtmp.utils.socket.RtmpSocket class CommandsManagerAmf0: CommandsManager() { - override fun sendConnect(auth: String, output: OutputStream) { + override suspend fun sendConnectImp(auth: String, socket: RtmpSocket) { val connect = CommandAmf0("connect", ++commandId, getCurrentTimestamp(), streamId, BasicHeader(ChunkType.TYPE_0, ChunkStreamId.OVER_CONNECTION.mark)) val connectInfo = AmfObject() @@ -58,20 +58,20 @@ class CommandsManagerAmf0: CommandsManager() { connectInfo.setProperty("objectEncoding", 0.0) connect.addData(connectInfo) - connect.writeHeader(output) - connect.writeBody(output) + connect.writeHeader(socket) + connect.writeBody(socket) sessionHistory.setPacket(commandId, "connect") Log.i(TAG, "send $connect") } - override fun createStream(output: OutputStream) { + override suspend fun createStreamImp(socket: RtmpSocket) { val releaseStream = CommandAmf0("releaseStream", ++commandId, getCurrentTimestamp(), streamId, BasicHeader(ChunkType.TYPE_0, ChunkStreamId.OVER_STREAM.mark)) releaseStream.addData(AmfNull()) releaseStream.addData(AmfString(streamName)) - releaseStream.writeHeader(output) - releaseStream.writeBody(output) + releaseStream.writeHeader(socket) + releaseStream.writeBody(socket) sessionHistory.setPacket(commandId, "releaseStream") Log.i(TAG, "send $releaseStream") @@ -80,8 +80,8 @@ class CommandsManagerAmf0: CommandsManager() { fcPublish.addData(AmfNull()) fcPublish.addData(AmfString(streamName)) - fcPublish.writeHeader(output) - fcPublish.writeBody(output) + fcPublish.writeHeader(socket) + fcPublish.writeBody(socket) sessionHistory.setPacket(commandId, "FCPublish") Log.i(TAG, "send $fcPublish") @@ -89,13 +89,13 @@ class CommandsManagerAmf0: CommandsManager() { BasicHeader(ChunkType.TYPE_0, ChunkStreamId.OVER_CONNECTION.mark)) createStream.addData(AmfNull()) - createStream.writeHeader(output) - createStream.writeBody(output) + createStream.writeHeader(socket) + createStream.writeBody(socket) sessionHistory.setPacket(commandId, "createStream") Log.i(TAG, "send $createStream") } - override fun sendMetadata(output: OutputStream) { + override suspend fun sendMetadataImp(socket: RtmpSocket) { val name = "@setDataFrame" val metadata = DataAmf0(name, getCurrentTimestamp(), streamId) metadata.addData(AmfString("onMetaData")) @@ -129,12 +129,12 @@ class CommandsManagerAmf0: CommandsManager() { amfEcmaArray.setProperty("filesize", 0.0) metadata.addData(amfEcmaArray) - metadata.writeHeader(output) - metadata.writeBody(output) + metadata.writeHeader(socket) + metadata.writeBody(socket) Log.i(TAG, "send $metadata") } - override fun sendPublish(output: OutputStream) { + override suspend fun sendPublishImp(socket: RtmpSocket) { val name = "publish" val publish = CommandAmf0(name, ++commandId, getCurrentTimestamp(), streamId, BasicHeader(ChunkType.TYPE_0, ChunkStreamId.OVER_STREAM.mark)) @@ -142,19 +142,19 @@ class CommandsManagerAmf0: CommandsManager() { publish.addData(AmfString(streamName)) publish.addData(AmfString("live")) - publish.writeHeader(output) - publish.writeBody(output) + publish.writeHeader(socket) + publish.writeBody(socket) sessionHistory.setPacket(commandId, name) Log.i(TAG, "send $publish") } - override fun sendClose(output: OutputStream) { + override suspend fun sendCloseImp(socket: RtmpSocket) { val name = "closeStream" val closeStream = CommandAmf0(name, ++commandId, getCurrentTimestamp(), streamId, BasicHeader(ChunkType.TYPE_0, ChunkStreamId.OVER_STREAM.mark)) closeStream.addData(AmfNull()) - closeStream.writeHeader(output) - closeStream.writeBody(output) + closeStream.writeHeader(socket) + closeStream.writeBody(socket) sessionHistory.setPacket(commandId, name) Log.i(TAG, "send $closeStream") } diff --git a/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManagerAmf3.kt b/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManagerAmf3.kt index 28dd2eeb4..060c21054 100644 --- a/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManagerAmf3.kt +++ b/rtmp/src/main/java/com/pedro/rtmp/rtmp/CommandsManagerAmf3.kt @@ -31,10 +31,10 @@ import com.pedro.rtmp.rtmp.chunk.ChunkType import com.pedro.rtmp.rtmp.message.BasicHeader import com.pedro.rtmp.rtmp.message.command.CommandAmf3 import com.pedro.rtmp.rtmp.message.data.DataAmf3 -import java.io.OutputStream +import com.pedro.rtmp.utils.socket.RtmpSocket class CommandsManagerAmf3: CommandsManager() { - override fun sendConnect(auth: String, output: OutputStream) { + override suspend fun sendConnectImp(auth: String, socket: RtmpSocket) { val connect = CommandAmf3("connect", ++commandId, getCurrentTimestamp(), streamId, BasicHeader(ChunkType.TYPE_0, ChunkStreamId.OVER_CONNECTION.mark)) val connectInfo = Amf3Object() @@ -57,20 +57,20 @@ class CommandsManagerAmf3: CommandsManager() { connectInfo.setProperty("objectEncoding", 3.0) connect.addData(connectInfo) - connect.writeHeader(output) - connect.writeBody(output) + connect.writeHeader(socket) + connect.writeBody(socket) sessionHistory.setPacket(commandId, "connect") Log.i(TAG, "send $connect") } - override fun createStream(output: OutputStream) { + override suspend fun createStreamImp(socket: RtmpSocket) { val releaseStream = CommandAmf3("releaseStream", ++commandId, getCurrentTimestamp(), streamId, BasicHeader(ChunkType.TYPE_0, ChunkStreamId.OVER_STREAM.mark)) releaseStream.addData(Amf3Null()) releaseStream.addData(Amf3String(streamName)) - releaseStream.writeHeader(output) - releaseStream.writeBody(output) + releaseStream.writeHeader(socket) + releaseStream.writeBody(socket) sessionHistory.setPacket(commandId, "releaseStream") Log.i(TAG, "send $releaseStream") @@ -79,8 +79,8 @@ class CommandsManagerAmf3: CommandsManager() { fcPublish.addData(Amf3Null()) fcPublish.addData(Amf3String(streamName)) - fcPublish.writeHeader(output) - fcPublish.writeBody(output) + fcPublish.writeHeader(socket) + fcPublish.writeBody(socket) sessionHistory.setPacket(commandId, "FCPublish") Log.i(TAG, "send $fcPublish") @@ -88,13 +88,13 @@ class CommandsManagerAmf3: CommandsManager() { BasicHeader(ChunkType.TYPE_0, ChunkStreamId.OVER_CONNECTION.mark)) createStream.addData(Amf3Null()) - createStream.writeHeader(output) - createStream.writeBody(output) + createStream.writeHeader(socket) + createStream.writeBody(socket) sessionHistory.setPacket(commandId, "createStream") Log.i(TAG, "send $createStream") } - override fun sendMetadata(output: OutputStream) { + override suspend fun sendMetadataImp(socket: RtmpSocket) { val name = "@setDataFrame" val metadata = DataAmf3(name, getCurrentTimestamp(), streamId) metadata.addData(Amf3String("onMetaData")) @@ -120,12 +120,12 @@ class CommandsManagerAmf3: CommandsManager() { amfEcmaArray.setProperty("filesize", 0.0) metadata.addData(amfEcmaArray) - metadata.writeHeader(output) - metadata.writeBody(output) + metadata.writeHeader(socket) + metadata.writeBody(socket) Log.i(TAG, "send $metadata") } - override fun sendPublish(output: OutputStream) { + override suspend fun sendPublishImp(socket: RtmpSocket) { val name = "publish" val publish = CommandAmf3(name, ++commandId, getCurrentTimestamp(), streamId, BasicHeader(ChunkType.TYPE_0, ChunkStreamId.OVER_STREAM.mark)) @@ -133,19 +133,19 @@ class CommandsManagerAmf3: CommandsManager() { publish.addData(Amf3String(streamName)) publish.addData(Amf3String("live")) - publish.writeHeader(output) - publish.writeBody(output) + publish.writeHeader(socket) + publish.writeBody(socket) sessionHistory.setPacket(commandId, name) Log.i(TAG, "send $publish") } - override fun sendClose(output: OutputStream) { + override suspend fun sendCloseImp(socket: RtmpSocket) { val name = "closeStream" val closeStream = CommandAmf3(name, ++commandId, getCurrentTimestamp(), streamId, BasicHeader(ChunkType.TYPE_0, ChunkStreamId.OVER_STREAM.mark)) closeStream.addData(Amf3Null()) - closeStream.writeHeader(output) - closeStream.writeBody(output) + closeStream.writeHeader(socket) + closeStream.writeBody(socket) sessionHistory.setPacket(commandId, name) Log.i(TAG, "send $closeStream") } diff --git a/rtmp/src/main/java/com/pedro/rtmp/rtmp/Handshake.kt b/rtmp/src/main/java/com/pedro/rtmp/rtmp/Handshake.kt index 2426c8791..f2e7b4509 100644 --- a/rtmp/src/main/java/com/pedro/rtmp/rtmp/Handshake.kt +++ b/rtmp/src/main/java/com/pedro/rtmp/rtmp/Handshake.kt @@ -75,31 +75,27 @@ class Handshake { private var timestampC1 = 0 @Throws(IOException::class) - fun sendHandshake(socket: RtmpSocket): Boolean { - var output = socket.getOutStream() - writeC0(output) - val c1 = writeC1(output) + suspend fun sendHandshake(socket: RtmpSocket): Boolean { + writeC0(socket) + val c1 = writeC1(socket) socket.flush() - var input = socket.getInputStream() - readS0(input) - val s1 = readS1(input) - output = socket.getOutStream() - writeC2(output, s1) + readS0(socket) + val s1 = readS1(socket) + writeC2(socket, s1) socket.flush() - input = socket.getInputStream() - readS2(input, c1) + readS2(socket, c1) return true } @Throws(IOException::class) - private fun writeC0(output: OutputStream) { + private suspend fun writeC0(socket: RtmpSocket) { Log.i(TAG, "writing C0") - output.write(protocolVersion) + socket.write(protocolVersion) Log.i(TAG, "C0 write successful") } @Throws(IOException::class) - private fun writeC1(output: OutputStream): ByteArray { + private suspend fun writeC1(socket: RtmpSocket): ByteArray { Log.i(TAG, "writing C1") val c1 = ByteArray(handshakeSize) @@ -125,22 +121,22 @@ class Handshake { randomData[i] = uInt8 } System.arraycopy(randomData, 0, c1, 8, randomData.size) - output.write(c1) + socket.write(c1) Log.i(TAG, "C1 write successful") return c1 } @Throws(IOException::class) - private fun writeC2(output: OutputStream, s1: ByteArray) { + private suspend fun writeC2(socket: RtmpSocket, s1: ByteArray) { Log.i(TAG, "writing C2") - output.write(s1) + socket.write(s1) Log.i(TAG, "C2 write successful") } @Throws(IOException::class) - private fun readS0(input: InputStream): ByteArray { + private suspend fun readS0(socket: RtmpSocket): ByteArray { Log.i(TAG, "reading S0") - val response = input.read() + val response = socket.read() if (response == protocolVersion || response == 72) { Log.i(TAG, "read S0 successful") return byteArrayOf(response.toByte()) @@ -150,19 +146,19 @@ class Handshake { } @Throws(IOException::class) - private fun readS1(input: InputStream): ByteArray { + private suspend fun readS1(socket: RtmpSocket): ByteArray { Log.i(TAG, "reading S1") val s1 = ByteArray(handshakeSize) - input.readUntil(s1) + socket.readUntil(s1) Log.i(TAG, "read S1 successful") return s1 } @Throws(IOException::class) - private fun readS2(input: InputStream, c1: ByteArray): ByteArray { + private suspend fun readS2(socket: RtmpSocket, c1: ByteArray): ByteArray { Log.i(TAG, "reading S2") val s2 = ByteArray(handshakeSize) - input.readUntil(s2) + socket.readUntil(s2) //S2 should be equals to C1 but we can skip this if (!s2.contentEquals(c1)) { Log.e(TAG, "S2 content is different that C1") diff --git a/rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpClient.kt b/rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpClient.kt index a0d76495a..1b87a35b4 100644 --- a/rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpClient.kt +++ b/rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpClient.kt @@ -71,7 +71,7 @@ class RtmpClient(private val connectChecker: ConnectChecker) { private var url: String? = null private var tlsEnabled = false - private var certificates: Array? = null + private var certificates: TrustManager? = null private var tunneled = false private var doingRetry = false @@ -95,7 +95,7 @@ class RtmpClient(private val connectChecker: ConnectChecker) { /** * Add certificates for TLS connection */ - fun addCertificates(certificates: Array?) { + fun addCertificates(certificates: TrustManager?) { this.certificates = certificates } @@ -308,7 +308,7 @@ class RtmpClient(private val connectChecker: ConnectChecker) { } @Throws(IOException::class) - private fun establishConnection(): Boolean { + private suspend fun establishConnection(): Boolean { val socket = if (tunneled) { TcpTunneledSocket(commandsManager.host, commandsManager.port, tlsEnabled) } else { @@ -491,7 +491,7 @@ class RtmpClient(private val connectChecker: ConnectChecker) { } } - fun closeConnection() { + suspend fun closeConnection() { socket?.close() commandsManager.reset() } diff --git a/rtmp/src/main/java/com/pedro/rtmp/rtmp/message/BasicHeader.kt b/rtmp/src/main/java/com/pedro/rtmp/rtmp/message/BasicHeader.kt index a4fb8555f..73e0a6f77 100644 --- a/rtmp/src/main/java/com/pedro/rtmp/rtmp/message/BasicHeader.kt +++ b/rtmp/src/main/java/com/pedro/rtmp/rtmp/message/BasicHeader.kt @@ -17,6 +17,7 @@ package com.pedro.rtmp.rtmp.message import com.pedro.rtmp.rtmp.chunk.ChunkType +import com.pedro.rtmp.utils.socket.RtmpSocket import java.io.IOException import java.io.InputStream import kotlin.experimental.and @@ -59,17 +60,17 @@ import kotlin.experimental.and class BasicHeader(val chunkType: ChunkType, val chunkStreamId: Int) { companion object { - fun parseBasicHeader(input: InputStream): BasicHeader { - val byte = input.read().toByte() + suspend fun parseBasicHeader(socket: RtmpSocket): BasicHeader { + val byte = socket.read().toByte() val chunkTypeValue = 0xff and byte.toInt() ushr 6 val chunkType = ChunkType.entries.find { it.mark.toInt() == chunkTypeValue } ?: throw IOException("Unknown chunk type value: $chunkTypeValue") var chunkStreamIdValue = (byte and 0x3F).toInt() if (chunkStreamIdValue > 63) throw IOException("Unknown chunk stream id value: $chunkStreamIdValue") if (chunkStreamIdValue == 0) { //Basic header 2 bytes - chunkStreamIdValue = input.read() - 64 + chunkStreamIdValue = socket.read() - 64 } else if (chunkStreamIdValue == 1) { //Basic header 3 bytes - val a = input.read() - val b = input.read() + val a = socket.read() + val b = socket.read() val value = b and 0xff shl 8 and a chunkStreamIdValue = value - 64 } diff --git a/rtmp/src/main/java/com/pedro/rtmp/rtmp/message/RtmpHeader.kt b/rtmp/src/main/java/com/pedro/rtmp/rtmp/message/RtmpHeader.kt index b934c0fc2..7d0eb86c4 100644 --- a/rtmp/src/main/java/com/pedro/rtmp/rtmp/message/RtmpHeader.kt +++ b/rtmp/src/main/java/com/pedro/rtmp/rtmp/message/RtmpHeader.kt @@ -18,6 +18,7 @@ package com.pedro.rtmp.rtmp.message import com.pedro.rtmp.rtmp.chunk.ChunkType import com.pedro.rtmp.utils.* +import com.pedro.rtmp.utils.socket.RtmpSocket import java.io.IOException import java.io.InputStream import java.io.OutputStream @@ -41,9 +42,9 @@ class RtmpHeader(var basicHeader: BasicHeader) { * Check ChunkType class to know header structure */ @Throws(IOException::class) - fun readHeader(input: InputStream, commandSessionHistory: CommandSessionHistory, + suspend fun readHeader(socket: RtmpSocket, commandSessionHistory: CommandSessionHistory, timestamp: Int = 0): RtmpHeader { - val basicHeader = BasicHeader.parseBasicHeader(input) + val basicHeader = BasicHeader.parseBasicHeader(socket) var timeStamp = timestamp var messageLength = 0 var messageType: MessageType? = null @@ -51,25 +52,25 @@ class RtmpHeader(var basicHeader: BasicHeader) { val lastHeader = commandSessionHistory.getLastReadHeader(basicHeader.chunkStreamId) when (basicHeader.chunkType) { ChunkType.TYPE_0 -> { - timeStamp = input.readUInt24() - messageLength = input.readUInt24() - messageType = RtmpMessage.getMarkType(input.read()) - messageStreamId = input.readUInt32LittleEndian() + timeStamp = socket.readUInt24() + messageLength = socket.readUInt24() + messageType = RtmpMessage.getMarkType(socket.read()) + messageStreamId = socket.readUInt32LittleEndian() //extended timestamp if (timeStamp >= 0xffffff) { - timeStamp = input.readUInt32() + timeStamp = socket.readUInt32() } } ChunkType.TYPE_1 -> { if (lastHeader != null) { messageStreamId = lastHeader.messageStreamId } - timeStamp = input.readUInt24() - messageLength = input.readUInt24() - messageType = RtmpMessage.getMarkType(input.read()) + timeStamp = socket.readUInt24() + messageLength = socket.readUInt24() + messageType = RtmpMessage.getMarkType(socket.read()) //extended timestamp if (timeStamp >= 0xffffff) { - timeStamp = input.readUInt32() + timeStamp = socket.readUInt32() } } ChunkType.TYPE_2 -> { @@ -78,10 +79,10 @@ class RtmpHeader(var basicHeader: BasicHeader) { messageType = lastHeader.messageType messageStreamId = lastHeader.messageStreamId } - timeStamp = input.readUInt24() + timeStamp = socket.readUInt24() //extended timestamp if (timeStamp >= 0xffffff) { - timeStamp = input.readUInt32() + timeStamp = socket.readUInt32() } } ChunkType.TYPE_3 -> { @@ -93,7 +94,7 @@ class RtmpHeader(var basicHeader: BasicHeader) { } //extended timestamp if (timeStamp >= 0xffffff) { - timeStamp = input.readUInt32() + timeStamp = socket.readUInt32() } //No header to read } @@ -108,52 +109,51 @@ class RtmpHeader(var basicHeader: BasicHeader) { } @Throws(IOException::class) - fun writeHeader(output: OutputStream) { - writeHeader(basicHeader, output) + suspend fun writeHeader(socket: RtmpSocket) { + writeHeader(basicHeader, socket) } /** * Check ChunkType class to know header structure */ - @Throws(IOException::class) - fun writeHeader(basicHeader: BasicHeader, output: OutputStream) { + suspend fun writeHeader(basicHeader: BasicHeader, socket: RtmpSocket) { // Write basic header byte - output.write((basicHeader.chunkType.mark.toInt() shl 6) or basicHeader.chunkStreamId) + socket.write((basicHeader.chunkType.mark.toInt() shl 6) or basicHeader.chunkStreamId) when (basicHeader.chunkType) { ChunkType.TYPE_0 -> { - output.writeUInt24(min(timeStamp, 0xffffff)) - output.writeUInt24(messageLength) + socket.writeUInt24(min(timeStamp, 0xffffff)) + socket.writeUInt24(messageLength) messageType?.let { messageType -> - output.write(messageType.mark.toInt()) + socket.write(messageType.mark.toInt()) } - output.writeUInt32LittleEndian(messageStreamId) + socket.writeUInt32LittleEndian(messageStreamId) //extended timestamp if (timeStamp > 0xffffff) { - output.writeUInt32(timeStamp) + socket.writeUInt32(timeStamp) } } ChunkType.TYPE_1 -> { - output.writeUInt24(min(timeStamp, 0xffffff)) - output.writeUInt24(messageLength) + socket.writeUInt24(min(timeStamp, 0xffffff)) + socket.writeUInt24(messageLength) messageType?.let { messageType -> - output.write(messageType.mark.toInt()) + socket.write(messageType.mark.toInt()) } //extended timestamp if (timeStamp > 0xffffff) { - output.writeUInt32(timeStamp) + socket.writeUInt32(timeStamp) } } ChunkType.TYPE_2 -> { - output.writeUInt24(min(timeStamp, 0xffffff)) + socket.writeUInt24(min(timeStamp, 0xffffff)) //extended timestamp if (timeStamp > 0xffffff) { - output.writeUInt32(timeStamp) + socket.writeUInt32(timeStamp) } } ChunkType.TYPE_3 -> { //extended timestamp if (timeStamp > 0xffffff) { - output.writeUInt32(timeStamp) + socket.writeUInt32(timeStamp) } } } diff --git a/rtmp/src/main/java/com/pedro/rtmp/rtmp/message/RtmpMessage.kt b/rtmp/src/main/java/com/pedro/rtmp/rtmp/message/RtmpMessage.kt index 5e73430c4..b5797cf5d 100644 --- a/rtmp/src/main/java/com/pedro/rtmp/rtmp/message/RtmpMessage.kt +++ b/rtmp/src/main/java/com/pedro/rtmp/rtmp/message/RtmpMessage.kt @@ -26,7 +26,7 @@ import com.pedro.rtmp.rtmp.message.shared.SharedObjectAmf0 import com.pedro.rtmp.rtmp.message.shared.SharedObjectAmf3 import com.pedro.rtmp.utils.CommandSessionHistory import com.pedro.rtmp.utils.RtmpConfig -import com.pedro.rtmp.utils.readUntil +import com.pedro.rtmp.utils.socket.RtmpSocket import java.io.* /** @@ -46,9 +46,9 @@ abstract class RtmpMessage(basicHeader: BasicHeader) { private const val TAG = "RtmpMessage" @Throws(IOException::class) - fun getRtmpMessage(input: InputStream, chunkSize: Int, + suspend fun getRtmpMessage(socket: RtmpSocket, chunkSize: Int, commandSessionHistory: CommandSessionHistory): RtmpMessage { - val header = RtmpHeader.readHeader(input, commandSessionHistory) + val header = RtmpHeader.readHeader(socket, commandSessionHistory) val rtmpMessage = when (header.messageType) { MessageType.SET_CHUNK_SIZE -> SetChunkSize() MessageType.ABORT -> Abort() @@ -70,9 +70,11 @@ abstract class RtmpMessage(basicHeader: BasicHeader) { rtmpMessage.updateHeader(header) //we have multiple chunk wait until we have full body on stream and discard chunk header val bodyInput = if (header.messageLength > chunkSize) { - getInputWithoutChunks(input, header, chunkSize, commandSessionHistory) + getInputWithoutChunks(socket, header, chunkSize, commandSessionHistory) } else { - input + val bytes = ByteArray(header.messageLength) + socket.readUntil(bytes) + ByteArrayInputStream(bytes) } rtmpMessage.readBody(bodyInput) return rtmpMessage @@ -82,7 +84,7 @@ abstract class RtmpMessage(basicHeader: BasicHeader) { return MessageType.entries.find { it.mark.toInt() == type } ?: throw IOException("Unknown rtmp message type: $type") } - private fun getInputWithoutChunks(input: InputStream, header: RtmpHeader, chunkSize: Int, + private suspend fun getInputWithoutChunks(socket: RtmpSocket, header: RtmpHeader, chunkSize: Int, commandSessionHistory: CommandSessionHistory): InputStream { val packetStore = ByteArrayOutputStream() var bytesRead = 0 @@ -91,12 +93,12 @@ abstract class RtmpMessage(basicHeader: BasicHeader) { if (header.messageLength - bytesRead < chunkSize) { //last chunk chunk = ByteArray(header.messageLength - bytesRead) - input.readUntil(chunk) + socket.readUntil(chunk) } else { chunk = ByteArray(chunkSize) - input.readUntil(chunk) + socket.readUntil(chunk) //skip chunk header to discard it, set packet ts to indicate if you need read extended ts - RtmpHeader.readHeader(input, commandSessionHistory, header.timeStamp) + RtmpHeader.readHeader(socket, commandSessionHistory, header.timeStamp) } bytesRead += chunk.size packetStore.write(chunk) @@ -113,13 +115,11 @@ abstract class RtmpMessage(basicHeader: BasicHeader) { header.timeStamp = rtmpHeader.timeStamp } - @Throws(IOException::class) - fun writeHeader(output: OutputStream) { - header.writeHeader(output) + suspend fun writeHeader(socket: RtmpSocket) { + header.writeHeader(socket) } - @Throws(IOException::class) - fun writeBody(output: OutputStream) { + suspend fun writeBody(socket: RtmpSocket) { val chunkSize = RtmpConfig.writeChunkSize val bytes = storeBody() var pos = 0 @@ -127,13 +127,13 @@ abstract class RtmpMessage(basicHeader: BasicHeader) { while (length > chunkSize) { // Write packet for chunk - output.write(bytes, pos, chunkSize) + socket.write(bytes, pos, chunkSize) length -= chunkSize pos += chunkSize // Write header for remain chunk - header.writeHeader(BasicHeader(ChunkType.TYPE_3, header.basicHeader.chunkStreamId), output) + header.writeHeader(BasicHeader(ChunkType.TYPE_3, header.basicHeader.chunkStreamId), socket) } - output.write(bytes, pos, length) + socket.write(bytes, pos, length) } abstract fun readBody(input: InputStream) diff --git a/rtmp/src/main/java/com/pedro/rtmp/utils/socket/RtmpSocket.kt b/rtmp/src/main/java/com/pedro/rtmp/utils/socket/RtmpSocket.kt index 968ed44df..e3aa49608 100644 --- a/rtmp/src/main/java/com/pedro/rtmp/utils/socket/RtmpSocket.kt +++ b/rtmp/src/main/java/com/pedro/rtmp/utils/socket/RtmpSocket.kt @@ -16,9 +16,6 @@ package com.pedro.rtmp.utils.socket -import java.io.InputStream -import java.io.OutputStream - /** * Created by pedro on 5/4/22. * Socket implementation that accept: @@ -30,13 +27,22 @@ import java.io.OutputStream */ abstract class RtmpSocket { - protected val timeout = 5000 - - abstract fun getOutStream(): OutputStream - abstract fun getInputStream(): InputStream - abstract fun flush(isPacket: Boolean = false) - abstract fun connect() - abstract fun close() + abstract suspend fun flush(isPacket: Boolean = false) + abstract suspend fun connect() + abstract suspend fun close() abstract fun isConnected(): Boolean abstract fun isReachable(): Boolean + abstract suspend fun write(b: Int) + abstract suspend fun write(b: ByteArray) + abstract suspend fun write(b: ByteArray, offset: Int, size: Int) + abstract suspend fun writeUInt16(b: Int) + abstract suspend fun writeUInt24(b: Int) + abstract suspend fun writeUInt32(b: Int) + abstract suspend fun writeUInt32LittleEndian(b: Int) + abstract suspend fun read(): Int + abstract suspend fun readUInt16(): Int + abstract suspend fun readUInt24(): Int + abstract suspend fun readUInt32(): Int + abstract suspend fun readUInt32LittleEndian(): Int + abstract suspend fun readUntil(b: ByteArray) } \ No newline at end of file diff --git a/rtmp/src/main/java/com/pedro/rtmp/utils/socket/TcpSocket.kt b/rtmp/src/main/java/com/pedro/rtmp/utils/socket/TcpSocket.kt index fc781cf26..8e199efd5 100644 --- a/rtmp/src/main/java/com/pedro/rtmp/utils/socket/TcpSocket.kt +++ b/rtmp/src/main/java/com/pedro/rtmp/utils/socket/TcpSocket.kt @@ -16,70 +16,73 @@ package com.pedro.rtmp.utils.socket -import com.pedro.common.TLSSocketFactory -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.io.IOException -import java.io.InputStream -import java.io.OutputStream -import java.net.InetSocketAddress -import java.net.Socket -import java.net.SocketAddress -import java.security.GeneralSecurityException +import com.pedro.common.socket.TcpStreamSocket import javax.net.ssl.TrustManager /** * Created by pedro on 5/4/22. */ class TcpSocket( - private val host: String, - private val port: Int, - private val secured: Boolean, - private val certificates: Array? + host: String, port: Int, secured: Boolean, certificates: TrustManager? ): RtmpSocket() { - private var socket: Socket = Socket() - private var input = ByteArrayInputStream(byteArrayOf()).buffered() - private var output = ByteArrayOutputStream().buffered() + private val socket = TcpStreamSocket(host, port, secured, certificates) - override fun getOutStream(): OutputStream = output + override suspend fun flush(isPacket: Boolean) { + socket.flush() + } + + override suspend fun connect() { + socket.connect() + } + + override suspend fun close() { + socket.close() + } - override fun getInputStream(): InputStream = input + override fun isConnected(): Boolean = socket.isConnected() - override fun flush(isPacket: Boolean) { - getOutStream().flush() + override fun isReachable(): Boolean = socket.isReachable() + + override suspend fun write(b: Int) { + socket.write(b) } - override fun connect() { - if (secured) { - try { - val socketFactory = TLSSocketFactory(certificates) - socket = socketFactory.createSocket(host, port) - } catch (e: GeneralSecurityException) { - throw IOException("Create SSL socket failed: ${e.message}") - } - } else { - socket = Socket() - val socketAddress: SocketAddress = InetSocketAddress(host, port) - socket.connect(socketAddress, timeout) - } - output = socket.getOutputStream().buffered() - input = socket.getInputStream().buffered() - socket.soTimeout = timeout + override suspend fun write(b: ByteArray) { + socket.write(b) } - override fun close() { - try { - if (socket.isConnected) { - socket.getInputStream().close() - input.close() - output.close() - socket.close() - } - } catch (ignored: Exception) {} + override suspend fun write(b: ByteArray, offset: Int, size: Int) { + socket.write(b, offset, size) } - override fun isConnected(): Boolean = socket.isConnected + override suspend fun writeUInt16(b: Int) { + socket.writeUInt16(b) + } - override fun isReachable(): Boolean = socket.inetAddress?.isReachable(5000) ?: false + override suspend fun writeUInt24(b: Int) { + socket.writeUInt24(b) + } + + override suspend fun writeUInt32(b: Int) { + socket.writeUInt32(b) + } + + override suspend fun writeUInt32LittleEndian(b: Int) { + socket.writeUInt32LittleEndian(b) + } + + override suspend fun read(): Int = socket.read() + + override suspend fun readUInt16(): Int = socket.readUInt16() + + override suspend fun readUInt24(): Int = socket.readUInt24() + + override suspend fun readUInt32(): Int = socket.readUInt32() + + override suspend fun readUInt32LittleEndian(): Int = socket.readUInt32LittleEndian() + + override suspend fun readUntil(b: ByteArray) { + socket.readUntil(b) + } } \ No newline at end of file diff --git a/rtmp/src/main/java/com/pedro/rtmp/utils/socket/TcpTunneledSocket.kt b/rtmp/src/main/java/com/pedro/rtmp/utils/socket/TcpTunneledSocket.kt index dd1568e07..f71ebeaf6 100644 --- a/rtmp/src/main/java/com/pedro/rtmp/utils/socket/TcpTunneledSocket.kt +++ b/rtmp/src/main/java/com/pedro/rtmp/utils/socket/TcpTunneledSocket.kt @@ -46,78 +46,78 @@ class TcpTunneledSocket(private val host: String, private val port: Int, private //send video/audio packets in packs of 10 on each HTTP request. private val maxStoredPackets = 10 - override fun getOutStream(): OutputStream = output - - override fun getInputStream(): InputStream { - synchronized(sync) { - val start = TimeUtils.getCurrentTimeMillis() - while (input.available() <= 1 && connected) { - val i = index.addAndGet(1) - val bytes = requestRead("idle/$connectionId/$i", secured) - input = ByteArrayInputStream(bytes, 1, bytes.size) - if (TimeUtils.getCurrentTimeMillis() - start >= timeout) { - throw SocketTimeoutException("couldn't receive a valid packet") - } - } - } - return input - } - - override fun flush(isPacket: Boolean) { - synchronized(sync) { - if (isPacket && storedPackets < maxStoredPackets) { - storedPackets++ - return - } - if (!connected) return - val i = index.addAndGet(1) - val bytes = output.toByteArray() - output.reset() - requestWrite("send/$connectionId/$i", secured, bytes) - storedPackets = 0 - } - } - - override fun connect() { - synchronized(sync) { - try { - //optional in few servers - requestWrite("fcs/ident2", secured, byteArrayOf(0x00)) - } catch (ignored: IOException) { } - try { - val openResult = requestRead("open/1", secured) - connectionId = String(openResult).trimIndent() - requestWrite("idle/$connectionId/${index.get()}", secured, byteArrayOf(0x00)) - connected = true - Log.i(TAG, "Connection success") - } catch (e: IOException) { - Log.e(TAG, "Connection failed: ${e.message}") - connected = false - } - } - } - - override fun close() { - Log.i(TAG, "closing tunneled socket...") - connected = false - synchronized(sync) { - Thread { - try { - requestWrite("close/$connectionId", secured, byteArrayOf(0x00)) - Log.i(TAG, "Close success") - } catch (e: IOException) { - Log.e(TAG, "Close request failed: ${e.message}") - } finally { - index.set(0) - connectionId = "" - } - }.start() - } - } - - override fun isConnected(): Boolean = connected - - override fun isReachable(): Boolean = connected +// override fun getOutStream(): OutputStream = output +// +// override fun getInputStream(): InputStream { +// synchronized(sync) { +// val start = TimeUtils.getCurrentTimeMillis() +// while (input.available() <= 1 && connected) { +// val i = index.addAndGet(1) +// val bytes = requestRead("idle/$connectionId/$i", secured) +// input = ByteArrayInputStream(bytes, 1, bytes.size) +// if (TimeUtils.getCurrentTimeMillis() - start >= timeout) { +// throw SocketTimeoutException("couldn't receive a valid packet") +// } +// } +// } +// return input +// } +// +// override fun flush(isPacket: Boolean) { +// synchronized(sync) { +// if (isPacket && storedPackets < maxStoredPackets) { +// storedPackets++ +// return +// } +// if (!connected) return +// val i = index.addAndGet(1) +// val bytes = output.toByteArray() +// output.reset() +// requestWrite("send/$connectionId/$i", secured, bytes) +// storedPackets = 0 +// } +// } +// +// override fun connect() { +// synchronized(sync) { +// try { +// //optional in few servers +// requestWrite("fcs/ident2", secured, byteArrayOf(0x00)) +// } catch (ignored: IOException) { } +// try { +// val openResult = requestRead("open/1", secured) +// connectionId = String(openResult).trimIndent() +// requestWrite("idle/$connectionId/${index.get()}", secured, byteArrayOf(0x00)) +// connected = true +// Log.i(TAG, "Connection success") +// } catch (e: IOException) { +// Log.e(TAG, "Connection failed: ${e.message}") +// connected = false +// } +// } +// } +// +// override fun close() { +// Log.i(TAG, "closing tunneled socket...") +// connected = false +// synchronized(sync) { +// Thread { +// try { +// requestWrite("close/$connectionId", secured, byteArrayOf(0x00)) +// Log.i(TAG, "Close success") +// } catch (e: IOException) { +// Log.e(TAG, "Close request failed: ${e.message}") +// } finally { +// index.set(0) +// connectionId = "" +// } +// }.start() +// } +// } +// +// override fun isConnected(): Boolean = connected +// +// override fun isReachable(): Boolean = connected @Throws(IOException::class) private fun requestWrite(path: String, secured: Boolean, data: ByteArray) { @@ -162,8 +162,80 @@ class TcpTunneledSocket(private val host: String, private val port: Int, private socket.addRequestProperty(key, value) } socket.doOutput = true - socket.connectTimeout = timeout - socket.readTimeout = timeout + socket.connectTimeout = 5000 + socket.readTimeout = 5000 return socket } + + override suspend fun flush(isPacket: Boolean) { + TODO("Not yet implemented") + } + + override suspend fun connect() { + TODO("Not yet implemented") + } + + override suspend fun close() { + TODO("Not yet implemented") + } + + override fun isConnected(): Boolean { + TODO("Not yet implemented") + } + + override fun isReachable(): Boolean { + TODO("Not yet implemented") + } + + override suspend fun write(b: Int) { + TODO("Not yet implemented") + } + + override suspend fun write(b: ByteArray) { + TODO("Not yet implemented") + } + + override suspend fun write(b: ByteArray, offset: Int, size: Int) { + TODO("Not yet implemented") + } + + override suspend fun writeUInt16(b: Int) { + TODO("Not yet implemented") + } + + override suspend fun writeUInt24(b: Int) { + TODO("Not yet implemented") + } + + override suspend fun writeUInt32(b: Int) { + TODO("Not yet implemented") + } + + override suspend fun writeUInt32LittleEndian(b: Int) { + TODO("Not yet implemented") + } + + override suspend fun read(): Int { + TODO("Not yet implemented") + } + + override suspend fun readUInt16(): Int { + TODO("Not yet implemented") + } + + override suspend fun readUInt24(): Int { + TODO("Not yet implemented") + } + + override suspend fun readUInt32(): Int { + TODO("Not yet implemented") + } + + override suspend fun readUInt32LittleEndian(): Int { + TODO("Not yet implemented") + } + + override suspend fun readUntil(b: ByteArray) { + TODO("Not yet implemented") + } } \ No newline at end of file From e654446b553853658a6e3452c1808b372cf2f3d5 Mon Sep 17 00:00:00 2001 From: pedroSG94 Date: Sun, 22 Sep 2024 21:35:46 +0200 Subject: [PATCH 07/13] adding http socket --- .../pedro/common/socket/HttpStreamSocket.kt | 35 +++++++++++++++++++ .../com/pedro/common/socket/StreamSocket.kt | 9 ----- .../pedro/common/socket/TcpStreamSocket.kt | 5 +-- 3 files changed, 36 insertions(+), 13 deletions(-) create mode 100644 common/src/main/java/com/pedro/common/socket/HttpStreamSocket.kt diff --git a/common/src/main/java/com/pedro/common/socket/HttpStreamSocket.kt b/common/src/main/java/com/pedro/common/socket/HttpStreamSocket.kt new file mode 100644 index 000000000..cc2bc054d --- /dev/null +++ b/common/src/main/java/com/pedro/common/socket/HttpStreamSocket.kt @@ -0,0 +1,35 @@ +/* + * + * * Copyright (C) 2024 pedroSG94. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package com.pedro.common.socket + +import javax.net.ssl.TrustManager + +/** + * Created by pedro on 22/9/24. + */ +class HttpStreamSocket( + private val host: String, + private val port: Int, + private val secured: Boolean = false, + private val certificate: TrustManager? = null +) { + + + +} \ No newline at end of file diff --git a/common/src/main/java/com/pedro/common/socket/StreamSocket.kt b/common/src/main/java/com/pedro/common/socket/StreamSocket.kt index 7e67c0272..be5dfd7e7 100644 --- a/common/src/main/java/com/pedro/common/socket/StreamSocket.kt +++ b/common/src/main/java/com/pedro/common/socket/StreamSocket.kt @@ -21,17 +21,8 @@ package com.pedro.common.socket import io.ktor.network.selector.SelectorManager import io.ktor.network.sockets.ReadWriteSocket import io.ktor.network.sockets.isClosed -import io.ktor.network.sockets.openReadChannel -import io.ktor.network.sockets.openWriteChannel -import io.ktor.utils.io.ByteReadChannel -import io.ktor.utils.io.ByteWriteChannel -import io.ktor.utils.io.readFully -import io.ktor.utils.io.writeByte -import io.ktor.utils.io.writeFully -import io.ktor.utils.io.writeStringUtf8 import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import java.io.IOException import java.net.InetAddress import java.net.InetSocketAddress diff --git a/common/src/main/java/com/pedro/common/socket/TcpStreamSocket.kt b/common/src/main/java/com/pedro/common/socket/TcpStreamSocket.kt index c8710ff08..778443252 100644 --- a/common/src/main/java/com/pedro/common/socket/TcpStreamSocket.kt +++ b/common/src/main/java/com/pedro/common/socket/TcpStreamSocket.kt @@ -108,10 +108,7 @@ class TcpStreamSocket( } suspend fun writeUInt32LittleEndian(b: Int) { - output?.writeByte(b) - output?.writeByte(b ushr 8) - output?.writeByte(b ushr 16) - output?.writeByte(b ushr 24) + writeUInt32(Integer.reverseBytes(b)) } suspend fun read(): Int { From 20e7b47a5748d98e2069dd88d296bd255002a5ce Mon Sep 17 00:00:00 2001 From: pedroSG94 Date: Sun, 22 Sep 2024 22:02:07 +0200 Subject: [PATCH 08/13] update tcp tunneled implementation --- .../pedro/common/socket/HttpStreamSocket.kt | 35 ---- .../rtmp/utils/socket/TcpTunneledSocket.kt | 189 +++++++++--------- 2 files changed, 90 insertions(+), 134 deletions(-) delete mode 100644 common/src/main/java/com/pedro/common/socket/HttpStreamSocket.kt diff --git a/common/src/main/java/com/pedro/common/socket/HttpStreamSocket.kt b/common/src/main/java/com/pedro/common/socket/HttpStreamSocket.kt deleted file mode 100644 index cc2bc054d..000000000 --- a/common/src/main/java/com/pedro/common/socket/HttpStreamSocket.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * - * * Copyright (C) 2024 pedroSG94. - * * - * * Licensed under the Apache License, Version 2.0 (the "License"); - * * you may not use this file except in compliance with the License. - * * You may obtain a copy of the License at - * * - * * http://www.apache.org/licenses/LICENSE-2.0 - * * - * * Unless required by applicable law or agreed to in writing, software - * * distributed under the License is distributed on an "AS IS" BASIS, - * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * * See the License for the specific language governing permissions and - * * limitations under the License. - * - */ - -package com.pedro.common.socket - -import javax.net.ssl.TrustManager - -/** - * Created by pedro on 22/9/24. - */ -class HttpStreamSocket( - private val host: String, - private val port: Int, - private val secured: Boolean = false, - private val certificate: TrustManager? = null -) { - - - -} \ No newline at end of file diff --git a/rtmp/src/main/java/com/pedro/rtmp/utils/socket/TcpTunneledSocket.kt b/rtmp/src/main/java/com/pedro/rtmp/utils/socket/TcpTunneledSocket.kt index f71ebeaf6..145ef6375 100644 --- a/rtmp/src/main/java/com/pedro/rtmp/utils/socket/TcpTunneledSocket.kt +++ b/rtmp/src/main/java/com/pedro/rtmp/utils/socket/TcpTunneledSocket.kt @@ -18,6 +18,17 @@ package com.pedro.rtmp.utils.socket import android.util.Log import com.pedro.common.TimeUtils +import com.pedro.rtmp.utils.readUInt16 +import com.pedro.rtmp.utils.readUInt24 +import com.pedro.rtmp.utils.readUInt32 +import com.pedro.rtmp.utils.readUInt32LittleEndian +import com.pedro.rtmp.utils.readUntil +import com.pedro.rtmp.utils.writeUInt16 +import com.pedro.rtmp.utils.writeUInt24 +import com.pedro.rtmp.utils.writeUInt32 +import com.pedro.rtmp.utils.writeUInt32LittleEndian +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import java.io.* import java.net.HttpURLConnection import java.net.SocketTimeoutException @@ -36,6 +47,7 @@ class TcpTunneledSocket(private val host: String, private val port: Int, private "Content-Type" to "application/x-fcs", "User-Agent" to "Shockwave Flash" ) + private val timeout = 5000 private var connectionId: String = "" private var connected = false private var index = AtomicLong(0) @@ -46,78 +58,21 @@ class TcpTunneledSocket(private val host: String, private val port: Int, private //send video/audio packets in packs of 10 on each HTTP request. private val maxStoredPackets = 10 -// override fun getOutStream(): OutputStream = output -// -// override fun getInputStream(): InputStream { -// synchronized(sync) { -// val start = TimeUtils.getCurrentTimeMillis() -// while (input.available() <= 1 && connected) { -// val i = index.addAndGet(1) -// val bytes = requestRead("idle/$connectionId/$i", secured) -// input = ByteArrayInputStream(bytes, 1, bytes.size) -// if (TimeUtils.getCurrentTimeMillis() - start >= timeout) { -// throw SocketTimeoutException("couldn't receive a valid packet") -// } -// } -// } -// return input -// } -// -// override fun flush(isPacket: Boolean) { -// synchronized(sync) { -// if (isPacket && storedPackets < maxStoredPackets) { -// storedPackets++ -// return -// } -// if (!connected) return -// val i = index.addAndGet(1) -// val bytes = output.toByteArray() -// output.reset() -// requestWrite("send/$connectionId/$i", secured, bytes) -// storedPackets = 0 -// } -// } -// -// override fun connect() { -// synchronized(sync) { -// try { -// //optional in few servers -// requestWrite("fcs/ident2", secured, byteArrayOf(0x00)) -// } catch (ignored: IOException) { } -// try { -// val openResult = requestRead("open/1", secured) -// connectionId = String(openResult).trimIndent() -// requestWrite("idle/$connectionId/${index.get()}", secured, byteArrayOf(0x00)) -// connected = true -// Log.i(TAG, "Connection success") -// } catch (e: IOException) { -// Log.e(TAG, "Connection failed: ${e.message}") -// connected = false -// } -// } -// } -// -// override fun close() { -// Log.i(TAG, "closing tunneled socket...") -// connected = false -// synchronized(sync) { -// Thread { -// try { -// requestWrite("close/$connectionId", secured, byteArrayOf(0x00)) -// Log.i(TAG, "Close success") -// } catch (e: IOException) { -// Log.e(TAG, "Close request failed: ${e.message}") -// } finally { -// index.set(0) -// connectionId = "" -// } -// }.start() -// } -// } -// -// override fun isConnected(): Boolean = connected -// -// override fun isReachable(): Boolean = connected + private fun getInputStream(requiredBytes: Int): InputStream { + if (input.available() >= requiredBytes) return input + synchronized(sync) { + val start = TimeUtils.getCurrentTimeMillis() + while (input.available() <= 1 && connected) { + val i = index.addAndGet(1) + val bytes = requestRead("idle/$connectionId/$i", secured) + input = ByteArrayInputStream(bytes, 1, bytes.size) + if (TimeUtils.getCurrentTimeMillis() - start >= timeout) { + throw SocketTimeoutException("couldn't receive a valid packet") + } + } + } + return input + } @Throws(IOException::class) private fun requestWrite(path: String, secured: Boolean, data: ByteArray) { @@ -162,80 +117,116 @@ class TcpTunneledSocket(private val host: String, private val port: Int, private socket.addRequestProperty(key, value) } socket.doOutput = true - socket.connectTimeout = 5000 - socket.readTimeout = 5000 + socket.connectTimeout = timeout + socket.readTimeout = timeout return socket } override suspend fun flush(isPacket: Boolean) { - TODO("Not yet implemented") + synchronized(sync) { + if (isPacket && storedPackets < maxStoredPackets) { + storedPackets++ + return + } + if (!connected) return + val i = index.addAndGet(1) + val bytes = output.toByteArray() + output.reset() + requestWrite("send/$connectionId/$i", secured, bytes) + storedPackets = 0 + } } override suspend fun connect() { - TODO("Not yet implemented") + synchronized(sync) { + try { + //optional in few servers + requestWrite("fcs/ident2", secured, byteArrayOf(0x00)) + } catch (ignored: IOException) { } + try { + val openResult = requestRead("open/1", secured) + connectionId = String(openResult).trimIndent() + requestWrite("idle/$connectionId/${index.get()}", secured, byteArrayOf(0x00)) + connected = true + Log.i(TAG, "Connection success") + } catch (e: IOException) { + Log.e(TAG, "Connection failed: ${e.message}") + connected = false + } + } } override suspend fun close() { - TODO("Not yet implemented") + Log.i(TAG, "closing tunneled socket...") + connected = false + synchronized(sync) { + Thread { + try { + requestWrite("close/$connectionId", secured, byteArrayOf(0x00)) + Log.i(TAG, "Close success") + } catch (e: IOException) { + Log.e(TAG, "Close request failed: ${e.message}") + } finally { + index.set(0) + connectionId = "" + } + }.start() + } } - override fun isConnected(): Boolean { - TODO("Not yet implemented") - } + override fun isConnected(): Boolean = connected - override fun isReachable(): Boolean { - TODO("Not yet implemented") - } + override fun isReachable(): Boolean = connected - override suspend fun write(b: Int) { - TODO("Not yet implemented") + override suspend fun write(b: Int) = withContext(Dispatchers.IO) { + output.write(b) } - override suspend fun write(b: ByteArray) { - TODO("Not yet implemented") + override suspend fun write(b: ByteArray) = withContext(Dispatchers.IO) { + output.write(b) } override suspend fun write(b: ByteArray, offset: Int, size: Int) { - TODO("Not yet implemented") + output.write(b, offset, size) } override suspend fun writeUInt16(b: Int) { - TODO("Not yet implemented") + output.writeUInt16(b) } override suspend fun writeUInt24(b: Int) { - TODO("Not yet implemented") + output.writeUInt24(b) } override suspend fun writeUInt32(b: Int) { - TODO("Not yet implemented") + output.writeUInt32(b) } override suspend fun writeUInt32LittleEndian(b: Int) { - TODO("Not yet implemented") + output.writeUInt32LittleEndian(b) } - override suspend fun read(): Int { - TODO("Not yet implemented") + override suspend fun read(): Int = withContext(Dispatchers.IO) { + getInputStream(1).read() } override suspend fun readUInt16(): Int { - TODO("Not yet implemented") + return getInputStream(1).readUInt16() } override suspend fun readUInt24(): Int { - TODO("Not yet implemented") + return getInputStream(1).readUInt24() } override suspend fun readUInt32(): Int { - TODO("Not yet implemented") + return getInputStream(1).readUInt32() } override suspend fun readUInt32LittleEndian(): Int { - TODO("Not yet implemented") + return getInputStream(1).readUInt32LittleEndian() } override suspend fun readUntil(b: ByteArray) { - TODO("Not yet implemented") + return getInputStream(b.size).readUntil(b) } } \ No newline at end of file From c5f3715bb2dc4834c534b839e950502ef6b85c05 Mon Sep 17 00:00:00 2001 From: pedroSG94 Date: Mon, 23 Sep 2024 21:07:51 +0200 Subject: [PATCH 09/13] add connection failed parse --- .../java/com/pedro/common/ConnectionFailed.kt | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 common/src/main/java/com/pedro/common/ConnectionFailed.kt diff --git a/common/src/main/java/com/pedro/common/ConnectionFailed.kt b/common/src/main/java/com/pedro/common/ConnectionFailed.kt new file mode 100644 index 000000000..a52ec38d2 --- /dev/null +++ b/common/src/main/java/com/pedro/common/ConnectionFailed.kt @@ -0,0 +1,46 @@ +/* + * + * * Copyright (C) 2024 pedroSG94. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package com.pedro.common + +/** + * Created by pedro on 23/9/24. + */ +enum class ConnectionFailed { + ENDPOINT_MALFORMED, REFUSED, CLOSED_BY_SERVER, NO_INTERNET, UNKNOWN; + + companion object { + fun parse(reason: String): ConnectionFailed { + return if ( + reason.contains("network is unreachable", ignoreCase = true) || + reason.contains("software caused connection abort", ignoreCase = true) || + reason.contains("no route to host", ignoreCase = true) + ) { + NO_INTERNET + } else if (reason.contains("broken pipe", ignoreCase = true)) { + CLOSED_BY_SERVER + } else if (reason.contains("connection refused", ignoreCase = true)) { + REFUSED + } else if (reason.contains("endpoint malformed", ignoreCase = true)) { + ENDPOINT_MALFORMED + } else { + UNKNOWN + } + } + } +} \ No newline at end of file From 6ac053588d2fdda83e100f5191eb5c9dad7400f6 Mon Sep 17 00:00:00 2001 From: pedroSG94 Date: Mon, 23 Sep 2024 21:13:37 +0200 Subject: [PATCH 10/13] add timeout to connection failed --- common/src/main/java/com/pedro/common/ConnectionFailed.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/com/pedro/common/ConnectionFailed.kt b/common/src/main/java/com/pedro/common/ConnectionFailed.kt index a52ec38d2..cc4b3e9c4 100644 --- a/common/src/main/java/com/pedro/common/ConnectionFailed.kt +++ b/common/src/main/java/com/pedro/common/ConnectionFailed.kt @@ -22,7 +22,7 @@ package com.pedro.common * Created by pedro on 23/9/24. */ enum class ConnectionFailed { - ENDPOINT_MALFORMED, REFUSED, CLOSED_BY_SERVER, NO_INTERNET, UNKNOWN; + ENDPOINT_MALFORMED, TIMEOUT, REFUSED, CLOSED_BY_SERVER, NO_INTERNET, UNKNOWN; companion object { fun parse(reason: String): ConnectionFailed { @@ -38,6 +38,11 @@ enum class ConnectionFailed { REFUSED } else if (reason.contains("endpoint malformed", ignoreCase = true)) { ENDPOINT_MALFORMED + } else if ( + reason.contains("timeout", ignoreCase = true) || + reason.contains("timed out", ignoreCase = true) + ) { + TIMEOUT } else { UNKNOWN } From af0dc61de4624071c82850b6a460ec56eb4664b6 Mon Sep 17 00:00:00 2001 From: pedroSG94 Date: Tue, 24 Sep 2024 12:07:28 +0200 Subject: [PATCH 11/13] fix tests --- .../java/com/pedro/rtmp/FakeRtmpSocket.kt | 103 ++++++++++++++++++ .../com/pedro/rtmp/rtmp/message/AbortTest.kt | 25 +++-- .../rtmp/rtmp/message/AcknowledgementTest.kt | 25 +++-- .../pedro/rtmp/rtmp/message/AudioVideoTest.kt | 29 +++-- .../pedro/rtmp/rtmp/message/CommandTest.kt | 25 +++-- .../com/pedro/rtmp/rtmp/message/DataTest.kt | 25 +++-- .../rtmp/rtmp/message/SetChunkSizeTest.kt | 25 +++-- .../rtmp/rtmp/message/SetPeerBandwidthTest.kt | 25 +++-- .../rtmp/rtmp/message/UserControlTest.kt | 25 +++-- .../message/WindowAcknowledgementSizeTest.kt | 25 +++-- .../com/pedro/rtmp/utils/StreamSocketTest.kt | 31 ------ .../com/pedro/rtsp/rtcp/BaseSenderReport.kt | 9 +- .../com/pedro/rtsp/rtcp/SenderReportUdp.kt | 13 +-- .../pedro/rtsp/rtp/sockets/BaseRtpSocket.kt | 10 +- .../pedro/rtsp/rtp/sockets/RtpSocketUdp.kt | 13 +-- .../com/pedro/rtsp/rtcp/RtcpReportTest.kt | 33 +++--- .../java/com/pedro/rtsp/rtp/AacPacketTest.kt | 3 +- .../java/com/pedro/rtsp/rtp/Av1PacketTest.kt | 3 +- .../java/com/pedro/rtsp/rtp/G711PacketTest.kt | 3 +- .../java/com/pedro/rtsp/rtp/H264PacketTest.kt | 12 +- .../java/com/pedro/rtsp/rtp/H265PacketTest.kt | 8 +- .../java/com/pedro/rtsp/rtp/OpusPacketTest.kt | 3 +- .../com/pedro/rtsp/rtp/RtpStreamSocketTest.kt | 31 +++--- 23 files changed, 315 insertions(+), 189 deletions(-) create mode 100644 rtmp/src/test/java/com/pedro/rtmp/FakeRtmpSocket.kt delete mode 100644 rtmp/src/test/java/com/pedro/rtmp/utils/StreamSocketTest.kt diff --git a/rtmp/src/test/java/com/pedro/rtmp/FakeRtmpSocket.kt b/rtmp/src/test/java/com/pedro/rtmp/FakeRtmpSocket.kt new file mode 100644 index 000000000..3704dfb29 --- /dev/null +++ b/rtmp/src/test/java/com/pedro/rtmp/FakeRtmpSocket.kt @@ -0,0 +1,103 @@ +/* + * + * * Copyright (C) 2024 pedroSG94. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package com.pedro.rtmp + +import com.pedro.rtmp.utils.readUInt16 +import com.pedro.rtmp.utils.readUInt24 +import com.pedro.rtmp.utils.readUInt32 +import com.pedro.rtmp.utils.readUInt32LittleEndian +import com.pedro.rtmp.utils.readUntil +import com.pedro.rtmp.utils.socket.RtmpSocket +import com.pedro.rtmp.utils.writeUInt16 +import com.pedro.rtmp.utils.writeUInt24 +import com.pedro.rtmp.utils.writeUInt32 +import com.pedro.rtmp.utils.writeUInt32LittleEndian +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream + +/** + * Created by pedro on 24/9/24. + */ +class FakeRtmpSocket: RtmpSocket() { + + var input = ByteArrayInputStream(byteArrayOf()) + private set + val output = ByteArrayOutputStream() + private var connected = false + + fun setInputBytes(byteArray: ByteArray) { + input = ByteArrayInputStream(byteArray) + } + + override suspend fun flush(isPacket: Boolean) { + output.flush() + } + + override suspend fun connect() { + connected = true + } + + override suspend fun close() { + connected = false + } + + override fun isConnected(): Boolean = connected + + override fun isReachable(): Boolean = connected + + override suspend fun write(b: Int) { + output.write(b) + } + + override suspend fun write(b: ByteArray) { + output.write(b) + } + + override suspend fun write(b: ByteArray, offset: Int, size: Int) { + output.write(b, offset, size) + } + + override suspend fun writeUInt16(b: Int) { + output.writeUInt16(b) + } + + override suspend fun writeUInt24(b: Int) { + output.writeUInt24(b) + } + + override suspend fun writeUInt32(b: Int) { + output.writeUInt32(b) + } + + override suspend fun writeUInt32LittleEndian(b: Int) { + output.writeUInt32LittleEndian(b) + } + + override suspend fun read(): Int = input.read() + + override suspend fun readUInt16(): Int = input.readUInt16() + + override suspend fun readUInt24(): Int = input.readUInt24() + + override suspend fun readUInt32(): Int = input.readUInt32() + + override suspend fun readUInt32LittleEndian(): Int = input.readUInt32LittleEndian() + + override suspend fun readUntil(b: ByteArray) = input.readUntil(b) +} \ No newline at end of file diff --git a/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/AbortTest.kt b/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/AbortTest.kt index e3d926e2f..89f21204e 100644 --- a/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/AbortTest.kt +++ b/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/AbortTest.kt @@ -16,14 +16,15 @@ package com.pedro.rtmp.rtmp.message +import com.pedro.rtmp.FakeRtmpSocket import com.pedro.rtmp.utils.CommandSessionHistory import com.pedro.rtmp.utils.RtmpConfig +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue +import org.junit.Before import org.junit.Test -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream /** * Created by pedro on 10/9/23. @@ -31,27 +32,33 @@ import java.io.ByteArrayOutputStream class AbortTest { private val commandSessionHistory = CommandSessionHistory() + private lateinit var socket: FakeRtmpSocket + + @Before + fun setup() { + socket = FakeRtmpSocket() + } @Test - fun `GIVEN a buffer WHEN read rtmp message THEN get expected abort packet`() { + fun `GIVEN a buffer WHEN read rtmp message THEN get expected abort packet`() = runTest { val buffer = byteArrayOf(2, 0, 0, 0, 0, 0, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0) + socket.setInputBytes(buffer) val abort = Abort() - val message = RtmpMessage.getRtmpMessage(ByteArrayInputStream(buffer), RtmpConfig.DEFAULT_CHUNK_SIZE, commandSessionHistory) + val message = RtmpMessage.getRtmpMessage(socket, RtmpConfig.DEFAULT_CHUNK_SIZE, commandSessionHistory) assertTrue(message is Abort) assertEquals(abort.toString(), (message as Abort).toString()) } @Test - fun `GIVEN an abort packet WHEN write into a buffer THEN get expected buffer`() { + fun `GIVEN an abort packet WHEN write into a buffer THEN get expected buffer`() = runTest { val expectedBuffer = byteArrayOf(2, 0, 0, 0, 0, 0, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0) - val output = ByteArrayOutputStream() val abort = Abort() - abort.writeHeader(output) - abort.writeBody(output) + abort.writeHeader(socket) + abort.writeBody(socket) - assertArrayEquals(expectedBuffer, output.toByteArray()) + assertArrayEquals(expectedBuffer, socket.output.toByteArray()) } } \ No newline at end of file diff --git a/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/AcknowledgementTest.kt b/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/AcknowledgementTest.kt index d3c795a23..e77863975 100644 --- a/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/AcknowledgementTest.kt +++ b/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/AcknowledgementTest.kt @@ -16,14 +16,15 @@ package com.pedro.rtmp.rtmp.message +import com.pedro.rtmp.FakeRtmpSocket import com.pedro.rtmp.utils.CommandSessionHistory import com.pedro.rtmp.utils.RtmpConfig +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue +import org.junit.Before import org.junit.Test -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream /** * Created by pedro on 10/9/23. @@ -31,27 +32,33 @@ import java.io.ByteArrayOutputStream class AcknowledgementTest { private val commandSessionHistory = CommandSessionHistory() + private lateinit var socket: FakeRtmpSocket + + @Before + fun setup() { + socket = FakeRtmpSocket() + } @Test - fun `GIVEN a buffer WHEN read rtmp message THEN get expected acknowledgement packet`() { + fun `GIVEN a buffer WHEN read rtmp message THEN get expected acknowledgement packet`() = runTest { val buffer = byteArrayOf(2, 0, 0, 0, 0, 0, 4, 3, 0, 0, 0, 0, 0, 0, 0, 5) + socket.setInputBytes(buffer) val acknowledgement = Acknowledgement(5) - val message = RtmpMessage.getRtmpMessage(ByteArrayInputStream(buffer), RtmpConfig.DEFAULT_CHUNK_SIZE, commandSessionHistory) + val message = RtmpMessage.getRtmpMessage(socket, RtmpConfig.DEFAULT_CHUNK_SIZE, commandSessionHistory) assertTrue(message is Acknowledgement) assertEquals(acknowledgement.toString(), (message as Acknowledgement).toString()) } @Test - fun `GIVEN an acknowledgement packet WHEN write into a buffer THEN get expected buffer`() { + fun `GIVEN an acknowledgement packet WHEN write into a buffer THEN get expected buffer`() = runTest { val expectedBuffer = byteArrayOf(2, 0, 0, 0, 0, 0, 4, 3, 0, 0, 0, 0, 0, 0, 0, 5) - val output = ByteArrayOutputStream() val acknowledgement = Acknowledgement(5) - acknowledgement.writeHeader(output) - acknowledgement.writeBody(output) + acknowledgement.writeHeader(socket) + acknowledgement.writeBody(socket) - assertArrayEquals(expectedBuffer, output.toByteArray()) + assertArrayEquals(expectedBuffer, socket.output.toByteArray()) } } \ No newline at end of file diff --git a/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/AudioVideoTest.kt b/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/AudioVideoTest.kt index 441d4b705..8ff30a1ad 100644 --- a/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/AudioVideoTest.kt +++ b/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/AudioVideoTest.kt @@ -16,21 +16,29 @@ package com.pedro.rtmp.rtmp.message +import com.pedro.rtmp.FakeRtmpSocket import com.pedro.rtmp.flv.FlvPacket import com.pedro.rtmp.flv.FlvType +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertArrayEquals +import org.junit.Before import org.junit.Test -import java.io.ByteArrayOutputStream /** * Created by pedro on 10/9/23. */ class AudioVideoTest { + private lateinit var socket: FakeRtmpSocket + + @Before + fun setup() { + socket = FakeRtmpSocket() + } + @Test - fun `GIVEN an audio packet WHEN write into a buffer THEN get expected buffer`() { + fun `GIVEN an audio packet WHEN write into a buffer THEN get expected buffer`() = runTest { val expectedBuffer = byteArrayOf(7, 18, -42, -121, 0, 0, 100, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) - val output = ByteArrayOutputStream() val fakePacket = FlvPacket( buffer = ByteArray(100) { 0x00 }, @@ -39,16 +47,15 @@ class AudioVideoTest { type = FlvType.AUDIO ) val audio = Audio(fakePacket) - audio.writeHeader(output) - audio.writeBody(output) + audio.writeHeader(socket) + audio.writeBody(socket) - assertArrayEquals(expectedBuffer, output.toByteArray()) + assertArrayEquals(expectedBuffer, socket.output.toByteArray()) } @Test - fun `GIVEN a video packet WHEN write into a buffer THEN get expected buffer`() { + fun `GIVEN a video packet WHEN write into a buffer THEN get expected buffer`() = runTest { val expectedBuffer = byteArrayOf(6, 18, -42, -121, 0, 0, 100, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) - val output = ByteArrayOutputStream() val fakePacket = FlvPacket( buffer = ByteArray(100) { 0x00 }, @@ -57,9 +64,9 @@ class AudioVideoTest { type = FlvType.VIDEO ) val video = Video(fakePacket) - video.writeHeader(output) - video.writeBody(output) + video.writeHeader(socket) + video.writeBody(socket) - assertArrayEquals(expectedBuffer, output.toByteArray()) + assertArrayEquals(expectedBuffer, socket.output.toByteArray()) } } \ No newline at end of file diff --git a/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/CommandTest.kt b/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/CommandTest.kt index b1b8d27ff..7c65560eb 100644 --- a/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/CommandTest.kt +++ b/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/CommandTest.kt @@ -16,17 +16,18 @@ package com.pedro.rtmp.rtmp.message +import com.pedro.rtmp.FakeRtmpSocket import com.pedro.rtmp.amf.v0.AmfNumber import com.pedro.rtmp.amf.v0.AmfString import com.pedro.rtmp.rtmp.message.command.CommandAmf0 import com.pedro.rtmp.utils.CommandSessionHistory import com.pedro.rtmp.utils.RtmpConfig +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue +import org.junit.Before import org.junit.Test -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream /** * Created by pedro on 10/9/23. @@ -34,32 +35,38 @@ import java.io.ByteArrayOutputStream class CommandTest { private val commandSessionHistory = CommandSessionHistory() + private lateinit var socket: FakeRtmpSocket + + @Before + fun setup() { + socket = FakeRtmpSocket() + } @Test - fun `GIVEN a buffer WHEN read rtmp message THEN get expected command amf0 packet`() { + fun `GIVEN a buffer WHEN read rtmp message THEN get expected command amf0 packet`() = runTest { val buffer = byteArrayOf(3, 0, 0, 0, 0, 0, 34, 20, 0, 0, 0, 0, 2, 0, 4, 116, 101, 115, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 6, 114, 97, 110, 100, 111, 109, 0, 64, 52, 0, 0, 0, 0, 0, 0) + socket.setInputBytes(buffer) val commandAmf0 = CommandAmf0("test") commandAmf0.addData(AmfString("random")) commandAmf0.addData(AmfNumber(20.0)) - val message = RtmpMessage.getRtmpMessage(ByteArrayInputStream(buffer), RtmpConfig.DEFAULT_CHUNK_SIZE, commandSessionHistory) + val message = RtmpMessage.getRtmpMessage(socket, RtmpConfig.DEFAULT_CHUNK_SIZE, commandSessionHistory) assertTrue(message is CommandAmf0) assertEquals(commandAmf0.toString(), (message as CommandAmf0).toString()) } @Test - fun `GIVEN a command amf0 packet WHEN write into a buffer THEN get expected buffer`() { + fun `GIVEN a command amf0 packet WHEN write into a buffer THEN get expected buffer`() = runTest { val expectedBuffer = byteArrayOf(3, 0, 0, 0, 0, 0, 34, 20, 0, 0, 0, 0, 2, 0, 4, 116, 101, 115, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 6, 114, 97, 110, 100, 111, 109, 0, 64, 52, 0, 0, 0, 0, 0, 0) - val output = ByteArrayOutputStream() val commandAmf0 = CommandAmf0("test") commandAmf0.addData(AmfString("random")) commandAmf0.addData(AmfNumber(20.0)) - commandAmf0.writeHeader(output) - commandAmf0.writeBody(output) + commandAmf0.writeHeader(socket) + commandAmf0.writeBody(socket) - assertArrayEquals(expectedBuffer, output.toByteArray()) + assertArrayEquals(expectedBuffer, socket.output.toByteArray()) } } \ No newline at end of file diff --git a/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/DataTest.kt b/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/DataTest.kt index dcda431cc..d9d143762 100644 --- a/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/DataTest.kt +++ b/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/DataTest.kt @@ -16,17 +16,18 @@ package com.pedro.rtmp.rtmp.message +import com.pedro.rtmp.FakeRtmpSocket import com.pedro.rtmp.amf.v0.AmfNumber import com.pedro.rtmp.amf.v0.AmfString import com.pedro.rtmp.rtmp.message.data.DataAmf0 import com.pedro.rtmp.utils.CommandSessionHistory import com.pedro.rtmp.utils.RtmpConfig +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue +import org.junit.Before import org.junit.Test -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream /** * Created by pedro on 10/9/23. @@ -34,32 +35,38 @@ import java.io.ByteArrayOutputStream class DataTest { private val commandSessionHistory = CommandSessionHistory() + private lateinit var socket: FakeRtmpSocket + + @Before + fun setup() { + socket = FakeRtmpSocket() + } @Test - fun `GIVEN a buffer WHEN read rtmp message THEN get expected data amf0 packet`() { + fun `GIVEN a buffer WHEN read rtmp message THEN get expected data amf0 packet`() = runTest { val buffer = byteArrayOf(3, 0, 0, 0, 0, 0, 25, 18, 0, 0, 0, 0, 2, 0, 4, 116, 101, 115, 116, 2, 0, 6, 114, 97, 110, 100, 111, 109, 0, 64, 52, 0, 0, 0, 0, 0, 0) + socket.setInputBytes(buffer) val dataAmf0 = DataAmf0("test") dataAmf0.addData(AmfString("random")) dataAmf0.addData(AmfNumber(20.0)) - val message = RtmpMessage.getRtmpMessage(ByteArrayInputStream(buffer), RtmpConfig.DEFAULT_CHUNK_SIZE, commandSessionHistory) + val message = RtmpMessage.getRtmpMessage(socket, RtmpConfig.DEFAULT_CHUNK_SIZE, commandSessionHistory) assertTrue(message is DataAmf0) assertEquals(dataAmf0.toString(), (message as DataAmf0).toString()) } @Test - fun `GIVEN a data amf0 packet WHEN write into a buffer THEN get expected buffer`() { + fun `GIVEN a data amf0 packet WHEN write into a buffer THEN get expected buffer`() = runTest { val expectedBuffer = byteArrayOf(3, 0, 0, 0, 0, 0, 25, 18, 0, 0, 0, 0, 2, 0, 4, 116, 101, 115, 116, 2, 0, 6, 114, 97, 110, 100, 111, 109, 0, 64, 52, 0, 0, 0, 0, 0, 0) - val output = ByteArrayOutputStream() val dataAmf0 = DataAmf0("test") dataAmf0.addData(AmfString("random")) dataAmf0.addData(AmfNumber(20.0)) - dataAmf0.writeHeader(output) - dataAmf0.writeBody(output) + dataAmf0.writeHeader(socket) + dataAmf0.writeBody(socket) - assertArrayEquals(expectedBuffer, output.toByteArray()) + assertArrayEquals(expectedBuffer, socket.output.toByteArray()) } } \ No newline at end of file diff --git a/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/SetChunkSizeTest.kt b/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/SetChunkSizeTest.kt index a0289ea75..e7bdd35fe 100644 --- a/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/SetChunkSizeTest.kt +++ b/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/SetChunkSizeTest.kt @@ -16,14 +16,15 @@ package com.pedro.rtmp.rtmp.message +import com.pedro.rtmp.FakeRtmpSocket import com.pedro.rtmp.utils.CommandSessionHistory import com.pedro.rtmp.utils.RtmpConfig +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue +import org.junit.Before import org.junit.Test -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream /** * Created by pedro on 10/9/23. @@ -31,27 +32,33 @@ import java.io.ByteArrayOutputStream class SetChunkSizeTest { private val commandSessionHistory = CommandSessionHistory() + private lateinit var socket: FakeRtmpSocket + + @Before + fun setup() { + socket = FakeRtmpSocket() + } @Test - fun `GIVEN a buffer WHEN read rtmp message THEN get expected set chunk size packet`() { + fun `GIVEN a buffer WHEN read rtmp message THEN get expected set chunk size packet`() = runTest { val buffer = byteArrayOf(2, 0, 0, 0, 0, 0, 4, 1, 0, 0, 0, 0, 0, 0, 1, 0) + socket.setInputBytes(buffer) val setChunkSize = SetChunkSize(256) - val message = RtmpMessage.getRtmpMessage(ByteArrayInputStream(buffer), RtmpConfig.DEFAULT_CHUNK_SIZE, commandSessionHistory) + val message = RtmpMessage.getRtmpMessage(socket, RtmpConfig.DEFAULT_CHUNK_SIZE, commandSessionHistory) assertTrue(message is SetChunkSize) assertEquals(setChunkSize.toString(), (message as SetChunkSize).toString()) } @Test - fun `GIVEN a set chunk size packet WHEN write into a buffer THEN get expected buffer`() { + fun `GIVEN a set chunk size packet WHEN write into a buffer THEN get expected buffer`() = runTest { val expectedBuffer = byteArrayOf(2, 0, 0, 0, 0, 0, 4, 1, 0, 0, 0, 0, 0, 0, 1, 0) - val output = ByteArrayOutputStream() val setChunkSize = SetChunkSize(256) - setChunkSize.writeHeader(output) - setChunkSize.writeBody(output) + setChunkSize.writeHeader(socket) + setChunkSize.writeBody(socket) - assertArrayEquals(expectedBuffer, output.toByteArray()) + assertArrayEquals(expectedBuffer, socket.output.toByteArray()) } } \ No newline at end of file diff --git a/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/SetPeerBandwidthTest.kt b/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/SetPeerBandwidthTest.kt index e127bbdd5..7b08e14fc 100644 --- a/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/SetPeerBandwidthTest.kt +++ b/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/SetPeerBandwidthTest.kt @@ -16,14 +16,15 @@ package com.pedro.rtmp.rtmp.message +import com.pedro.rtmp.FakeRtmpSocket import com.pedro.rtmp.utils.CommandSessionHistory import com.pedro.rtmp.utils.RtmpConfig +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue +import org.junit.Before import org.junit.Test -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream /** * Created by pedro on 10/9/23. @@ -31,27 +32,33 @@ import java.io.ByteArrayOutputStream class SetPeerBandwidthTest { private val commandSessionHistory = CommandSessionHistory() + private lateinit var socket: FakeRtmpSocket + + @Before + fun setup() { + socket = FakeRtmpSocket() + } @Test - fun `GIVEN a buffer WHEN read rtmp message THEN get expected set peer bandwidth packet`() { + fun `GIVEN a buffer WHEN read rtmp message THEN get expected set peer bandwidth packet`() = runTest { val buffer = byteArrayOf(2, 0, 0, 0, 0, 0, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 2) + socket.setInputBytes(buffer) val setPeerBandwidth = SetPeerBandwidth() - val message = RtmpMessage.getRtmpMessage(ByteArrayInputStream(buffer), RtmpConfig.DEFAULT_CHUNK_SIZE, commandSessionHistory) + val message = RtmpMessage.getRtmpMessage(socket, RtmpConfig.DEFAULT_CHUNK_SIZE, commandSessionHistory) assertTrue(message is SetPeerBandwidth) assertEquals(setPeerBandwidth.toString(), (message as SetPeerBandwidth).toString()) } @Test - fun `GIVEN a set peer bandwidth packet WHEN write into a buffer THEN get expected buffer`() { + fun `GIVEN a set peer bandwidth packet WHEN write into a buffer THEN get expected buffer`() = runTest { val expectedBuffer = byteArrayOf(2, 0, 0, 0, 0, 0, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 2) - val output = ByteArrayOutputStream() val setPeerBandwidth = SetPeerBandwidth() - setPeerBandwidth.writeHeader(output) - setPeerBandwidth.writeBody(output) + setPeerBandwidth.writeHeader(socket) + setPeerBandwidth.writeBody(socket) - assertArrayEquals(expectedBuffer, output.toByteArray()) + assertArrayEquals(expectedBuffer, socket.output.toByteArray()) } } \ No newline at end of file diff --git a/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/UserControlTest.kt b/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/UserControlTest.kt index e33851693..991c42134 100644 --- a/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/UserControlTest.kt +++ b/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/UserControlTest.kt @@ -16,15 +16,16 @@ package com.pedro.rtmp.rtmp.message +import com.pedro.rtmp.FakeRtmpSocket import com.pedro.rtmp.rtmp.message.control.UserControl import com.pedro.rtmp.utils.CommandSessionHistory import com.pedro.rtmp.utils.RtmpConfig +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue +import org.junit.Before import org.junit.Test -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream /** * Created by pedro on 10/9/23. @@ -32,27 +33,33 @@ import java.io.ByteArrayOutputStream class UserControlTest { private val commandSessionHistory = CommandSessionHistory() + private lateinit var socket: FakeRtmpSocket + + @Before + fun setup() { + socket = FakeRtmpSocket() + } @Test - fun `GIVEN a buffer WHEN read rtmp message THEN get expected user control packet`() { + fun `GIVEN a buffer WHEN read rtmp message THEN get expected user control packet`() = runTest { val buffer = byteArrayOf(2, 0, 0, 0, 0, 0, 6, 4, 0, 0, 0, 0, 0, 6, -1, -1, -1, -1) + socket.setInputBytes(buffer) val userControl = UserControl() - val message = RtmpMessage.getRtmpMessage(ByteArrayInputStream(buffer), RtmpConfig.DEFAULT_CHUNK_SIZE, commandSessionHistory) + val message = RtmpMessage.getRtmpMessage(socket, RtmpConfig.DEFAULT_CHUNK_SIZE, commandSessionHistory) assertTrue(message is UserControl) assertEquals(userControl.toString(), (message as UserControl).toString()) } @Test - fun `GIVEN an user control packet WHEN write into a buffer THEN get expected buffer`() { + fun `GIVEN an user control packet WHEN write into a buffer THEN get expected buffer`() = runTest { val expectedBuffer = byteArrayOf(2, 0, 0, 0, 0, 0, 6, 4, 0, 0, 0, 0, 0, 6, -1, -1, -1, -1) - val output = ByteArrayOutputStream() val userControl = UserControl() - userControl.writeHeader(output) - userControl.writeBody(output) + userControl.writeHeader(socket) + userControl.writeBody(socket) - assertArrayEquals(expectedBuffer, output.toByteArray()) + assertArrayEquals(expectedBuffer, socket.output.toByteArray()) } } \ No newline at end of file diff --git a/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/WindowAcknowledgementSizeTest.kt b/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/WindowAcknowledgementSizeTest.kt index cf48ce7e2..2d5b4b7c2 100644 --- a/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/WindowAcknowledgementSizeTest.kt +++ b/rtmp/src/test/java/com/pedro/rtmp/rtmp/message/WindowAcknowledgementSizeTest.kt @@ -16,14 +16,15 @@ package com.pedro.rtmp.rtmp.message +import com.pedro.rtmp.FakeRtmpSocket import com.pedro.rtmp.utils.CommandSessionHistory import com.pedro.rtmp.utils.RtmpConfig +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue +import org.junit.Before import org.junit.Test -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream /** * Created by pedro on 10/9/23. @@ -31,27 +32,33 @@ import java.io.ByteArrayOutputStream class WindowAcknowledgementSizeTest { private val commandSessionHistory = CommandSessionHistory() + private lateinit var socket: FakeRtmpSocket + + @Before + fun setup() { + socket = FakeRtmpSocket() + } @Test - fun `GIVEN a buffer WHEN read rtmp message THEN get expected window acknowledgement size packet`() { + fun `GIVEN a buffer WHEN read rtmp message THEN get expected window acknowledgement size packet`() = runTest { val buffer = byteArrayOf(2, 18, -42, -121, 0, 0, 4, 5, 0, 0, 0, 0, 0, 0, 1, 0) + socket.setInputBytes(buffer) val windowAcknowledgementSize = WindowAcknowledgementSize(256, 1234567) - val message = RtmpMessage.getRtmpMessage(ByteArrayInputStream(buffer), RtmpConfig.DEFAULT_CHUNK_SIZE, commandSessionHistory) + val message = RtmpMessage.getRtmpMessage(socket, RtmpConfig.DEFAULT_CHUNK_SIZE, commandSessionHistory) assertTrue(message is WindowAcknowledgementSize) assertEquals(windowAcknowledgementSize.toString(), (message as WindowAcknowledgementSize).toString()) } @Test - fun `GIVEN a window acknowledgement size packet WHEN write into a buffer THEN get expected buffer`() { + fun `GIVEN a window acknowledgement size packet WHEN write into a buffer THEN get expected buffer`() = runTest { val expectedBuffer = byteArrayOf(2, 18, -42, -121, 0, 0, 4, 5, 0, 0, 0, 0, 0, 0, 1, 0) - val output = ByteArrayOutputStream() val windowAcknowledgementSize = WindowAcknowledgementSize(256, 1234567) - windowAcknowledgementSize.writeHeader(output) - windowAcknowledgementSize.writeBody(output) + windowAcknowledgementSize.writeHeader(socket) + windowAcknowledgementSize.writeBody(socket) - assertArrayEquals(expectedBuffer, output.toByteArray()) + assertArrayEquals(expectedBuffer, socket.output.toByteArray()) } } \ No newline at end of file diff --git a/rtmp/src/test/java/com/pedro/rtmp/utils/StreamSocketTest.kt b/rtmp/src/test/java/com/pedro/rtmp/utils/StreamSocketTest.kt deleted file mode 100644 index 0fe3b7fc3..000000000 --- a/rtmp/src/test/java/com/pedro/rtmp/utils/StreamSocketTest.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2024 pedroSG94. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.pedro.rtmp.utils - -import com.pedro.rtmp.utils.socket.TcpSocket -import org.junit.Test - -class StreamSocketTest { - - @Test - fun `check tcp socket error with socket not connected`() { - val socket = TcpSocket("127.0.0.1", 1935, false, null) - socket.getOutStream().write(0) - socket.getInputStream() - socket.close() - } -} \ No newline at end of file diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtcp/BaseSenderReport.kt b/rtsp/src/main/java/com/pedro/rtsp/rtcp/BaseSenderReport.kt index 6674b0764..48014dc00 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtcp/BaseSenderReport.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtcp/BaseSenderReport.kt @@ -18,6 +18,7 @@ package com.pedro.rtsp.rtcp import com.pedro.common.TimeUtils import com.pedro.common.socket.TcpStreamSocket +import com.pedro.common.socket.UdpStreamSocket import com.pedro.rtsp.rtsp.Protocol import com.pedro.rtsp.rtsp.RtpFrame import com.pedro.rtsp.utils.RtpConstants @@ -49,7 +50,13 @@ abstract class BaseSenderReport internal constructor() { return if (protocol === Protocol.TCP) { SenderReportTcp() } else { - SenderReportUdp(host, videoSourcePort, audioSourcePort, videoServerPort, audioServerPort) + val videoSocket = UdpStreamSocket( + host, videoServerPort, videoSourcePort, receiveSize = RtpConstants.REPORT_PACKET_LENGTH + ) + val audioSocket = UdpStreamSocket( + host, audioServerPort, audioSourcePort, receiveSize = RtpConstants.REPORT_PACKET_LENGTH + ) + SenderReportUdp(videoSocket, audioSocket) } } } diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtcp/SenderReportUdp.kt b/rtsp/src/main/java/com/pedro/rtsp/rtcp/SenderReportUdp.kt index c57220e19..4f9125449 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtcp/SenderReportUdp.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtcp/SenderReportUdp.kt @@ -19,25 +19,16 @@ package com.pedro.rtsp.rtcp import com.pedro.common.socket.TcpStreamSocket import com.pedro.common.socket.UdpStreamSocket import com.pedro.rtsp.rtsp.RtpFrame -import com.pedro.rtsp.utils.RtpConstants import java.io.IOException /** * Created by pedro on 8/11/18. */ class SenderReportUdp( - host: String, - videoSourcePort: Int, audioSourcePort: Int, - videoServerPort: Int, audioServerPort: Int, + private val videoSocket: UdpStreamSocket, + private val audioSocket: UdpStreamSocket, ) : BaseSenderReport() { - private val videoSocket = UdpStreamSocket( - host, videoServerPort, videoSourcePort, receiveSize = RtpConstants.REPORT_PACKET_LENGTH - ) - private val audioSocket = UdpStreamSocket( - host, audioServerPort, audioSourcePort, receiveSize = RtpConstants.REPORT_PACKET_LENGTH - ) - @Throws(IOException::class) override suspend fun setSocket(socket: TcpStreamSocket) { videoSocket.connect() diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/BaseRtpSocket.kt b/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/BaseRtpSocket.kt index 37a1856e1..1aba6f48e 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/BaseRtpSocket.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/BaseRtpSocket.kt @@ -17,8 +17,10 @@ package com.pedro.rtsp.rtp.sockets import com.pedro.common.socket.TcpStreamSocket +import com.pedro.common.socket.UdpStreamSocket import com.pedro.rtsp.rtsp.Protocol import com.pedro.rtsp.rtsp.RtpFrame +import com.pedro.rtsp.utils.RtpConstants import java.io.IOException /** @@ -36,7 +38,13 @@ abstract class BaseRtpSocket { return if (protocol === Protocol.TCP) { RtpSocketTcp() } else { - RtpSocketUdp(host, videoSourcePort, audioSourcePort, videoServerPort, audioServerPort) + val videoSocket = UdpStreamSocket( + host, videoServerPort, videoSourcePort, receiveSize = RtpConstants.MTU + ) + val audioSocket = UdpStreamSocket( + host, audioServerPort, audioSourcePort, receiveSize = RtpConstants.MTU + ) + RtpSocketUdp(videoSocket, audioSocket) } } } diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/RtpSocketUdp.kt b/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/RtpSocketUdp.kt index 40dacba2d..25b7ddb72 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/RtpSocketUdp.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtp/sockets/RtpSocketUdp.kt @@ -19,25 +19,16 @@ package com.pedro.rtsp.rtp.sockets import com.pedro.common.socket.TcpStreamSocket import com.pedro.common.socket.UdpStreamSocket import com.pedro.rtsp.rtsp.RtpFrame -import com.pedro.rtsp.utils.RtpConstants import java.io.IOException /** * Created by pedro on 7/11/18. */ class RtpSocketUdp( - host: String, - videoSourcePort: Int, audioSourcePort: Int, - videoServerPort: Int, audioServerPort: Int, + private val videoSocket: UdpStreamSocket, + private val audioSocket: UdpStreamSocket, ) : BaseRtpSocket() { - private val videoSocket = UdpStreamSocket( - host, videoServerPort, videoSourcePort, receiveSize = RtpConstants.MTU - ) - private val audioSocket = UdpStreamSocket( - host, audioServerPort, audioSourcePort, receiveSize = RtpConstants.MTU - ) - @Throws(IOException::class) override suspend fun setSocket(socket: TcpStreamSocket) { videoSocket.connect() diff --git a/rtsp/src/test/java/com/pedro/rtsp/rtcp/RtcpReportTest.kt b/rtsp/src/test/java/com/pedro/rtsp/rtcp/RtcpReportTest.kt index 86d9ba6a4..ef7cef06a 100644 --- a/rtsp/src/test/java/com/pedro/rtsp/rtcp/RtcpReportTest.kt +++ b/rtsp/src/test/java/com/pedro/rtsp/rtcp/RtcpReportTest.kt @@ -17,6 +17,8 @@ package com.pedro.rtsp.rtcp import com.pedro.common.TimeUtils +import com.pedro.common.socket.TcpStreamSocket +import com.pedro.common.socket.UdpStreamSocket import com.pedro.rtsp.Utils import com.pedro.rtsp.rtsp.Protocol import com.pedro.rtsp.rtsp.RtpFrame @@ -45,9 +47,10 @@ import java.net.MulticastSocket class RtcpReportTest { @Mock - private lateinit var multicastSocketMocked: MulticastSocket + private lateinit var udpSocket: UdpStreamSocket @Mock - private lateinit var outputMocked: OutputStream + private lateinit var tcpSocket: TcpStreamSocket + private val timeUtilsMocked = Mockito.mockStatic(TimeUtils::class.java) private var fakeTime = 7502849023L @@ -64,11 +67,11 @@ class RtcpReportTest { @Test fun `GIVEN multiple video or audio rtp frames WHEN update rtcp tcp send THEN send only 1 of video and 1 of audio each 3 seconds`() = runTest { Utils.useStatics(listOf(timeUtilsMocked)) { - val senderReportTcp = BaseSenderReport.getInstance(Protocol.TCP, 0, 1) - senderReportTcp.setSocket(outputMocked, "127.0.0.1") + val senderReportTcp = BaseSenderReport.getInstance(Protocol.TCP, "127.0.0.1", 0, 1, 2, 3) + senderReportTcp.setSocket(tcpSocket) senderReportTcp.setSSRC(0, 1) - val fakeFrameVideo = RtpFrame(byteArrayOf(0x00, 0x00, 0x00), 0, 3, 0, 0, RtpConstants.trackVideo) - val fakeFrameAudio = RtpFrame(byteArrayOf(0x00, 0x00, 0x00), 0, 3, 0, 0, RtpConstants.trackAudio) + val fakeFrameVideo = RtpFrame(byteArrayOf(0x00, 0x00, 0x00), 0, 3, RtpConstants.trackVideo) + val fakeFrameAudio = RtpFrame(byteArrayOf(0x00, 0x00, 0x00), 0, 3, RtpConstants.trackAudio) (0..10).forEach { value -> val frame = if (value % 2 == 0) fakeFrameVideo else fakeFrameAudio @@ -76,7 +79,7 @@ class RtcpReportTest { } val resultValue = argumentCaptor() withContext(Dispatchers.IO) { - verify(outputMocked, times((2))).write(resultValue.capture()) + verify(tcpSocket, times((2))).write(resultValue.capture()) } fakeTime += 3_000 //wait until next interval (0..10).forEach { value -> @@ -84,7 +87,7 @@ class RtcpReportTest { senderReportTcp.update(frame) } withContext(Dispatchers.IO) { - verify(outputMocked, times((4))).write(resultValue.capture()) + verify(tcpSocket, times((4))).write(resultValue.capture()) } } } @@ -92,18 +95,18 @@ class RtcpReportTest { @Test fun `GIVEN multiple video or audio rtp frames WHEN update rtcp udp send THEN send only 1 of video and 1 of audio each 3 seconds`() = runTest { Utils.useStatics(listOf(timeUtilsMocked)) { - val senderReportUdp = SenderReportUdp(11111, 11112, multicastSocketMocked, multicastSocketMocked) - senderReportUdp.setDataStream(outputMocked, "127.0.0.1") + val senderReportUdp = SenderReportUdp(udpSocket, udpSocket) + senderReportUdp.setSocket(tcpSocket) senderReportUdp.setSSRC(0, 1) - val fakeFrameVideo = RtpFrame(byteArrayOf(0x00, 0x00, 0x00), 0, 3, 0, 0, RtpConstants.trackVideo) - val fakeFrameAudio = RtpFrame(byteArrayOf(0x00, 0x00, 0x00), 0, 3, 0, 0, RtpConstants.trackAudio) + val fakeFrameVideo = RtpFrame(byteArrayOf(0x00, 0x00, 0x00), 0, 3, RtpConstants.trackVideo) + val fakeFrameAudio = RtpFrame(byteArrayOf(0x00, 0x00, 0x00), 0, 3, RtpConstants.trackAudio) (0..10).forEach { value -> val frame = if (value % 2 == 0) fakeFrameVideo else fakeFrameAudio senderReportUdp.update(frame) } - val resultValue = argumentCaptor() + val resultValue = argumentCaptor() withContext(Dispatchers.IO) { - verify(multicastSocketMocked, times((2))).send(resultValue.capture()) + verify(udpSocket, times((2))).writePacket(resultValue.capture()) } fakeTime += 3_000 //wait until next interval (0..10).forEach { value -> @@ -111,7 +114,7 @@ class RtcpReportTest { senderReportUdp.update(frame) } withContext(Dispatchers.IO) { - verify(multicastSocketMocked, times((4))).send(resultValue.capture()) + verify(udpSocket, times((4))).writePacket(resultValue.capture()) } } } diff --git a/rtsp/src/test/java/com/pedro/rtsp/rtp/AacPacketTest.kt b/rtsp/src/test/java/com/pedro/rtsp/rtp/AacPacketTest.kt index 9a7c3f4c3..28dacf4aa 100644 --- a/rtsp/src/test/java/com/pedro/rtsp/rtp/AacPacketTest.kt +++ b/rtsp/src/test/java/com/pedro/rtsp/rtp/AacPacketTest.kt @@ -40,7 +40,6 @@ class AacPacketTest { info.size = fakeAac.size info.flags = 1 val aacPacket = AacPacket(44100) - aacPacket.setPorts(1, 2) aacPacket.setSSRC(123456789) val frames = mutableListOf() aacPacket.createAndSendPacket(ByteBuffer.wrap(fakeAac), info) { @@ -50,7 +49,7 @@ class AacPacketTest { val expectedRtp = byteArrayOf(-128, -31, 0, 1, 0, 83, 19, 92, 7, 91, -51, 21, 0, 16, 9, 96).plus(fakeAac) val expectedTimeStamp = 5444444L val expectedSize = RtpConstants.RTP_HEADER_LENGTH + info.size + 4 - val packetResult = RtpFrame(expectedRtp, expectedTimeStamp, expectedSize, 1, 2, RtpConstants.trackAudio) + val packetResult = RtpFrame(expectedRtp, expectedTimeStamp, expectedSize, RtpConstants.trackAudio) assertEquals(1, frames.size) assertEquals(packetResult, frames[0]) } diff --git a/rtsp/src/test/java/com/pedro/rtsp/rtp/Av1PacketTest.kt b/rtsp/src/test/java/com/pedro/rtsp/rtp/Av1PacketTest.kt index 273aff808..29e6b1cc3 100644 --- a/rtsp/src/test/java/com/pedro/rtsp/rtp/Av1PacketTest.kt +++ b/rtsp/src/test/java/com/pedro/rtsp/rtp/Av1PacketTest.kt @@ -45,7 +45,6 @@ class Av1PacketTest { val frames = mutableListOf() val av1Packet = Av1Packet() - av1Packet.setPorts(1, 2) av1Packet.setSSRC(123456789) av1Packet.createAndSendPacket(ByteBuffer.wrap(av1data), info) { rtpFrames -> frames.addAll(rtpFrames) @@ -54,7 +53,7 @@ class Av1PacketTest { val expectedRtp = byteArrayOf(-128, -32, 0, 1, 0, -87, -118, -57, 7, 91, -51, 21, 24).plus(av1data) val expectedTimeStamp = 11111111L val expectedSize = RtpConstants.RTP_HEADER_LENGTH + info.size + 1 - val packetResult = RtpFrame(expectedRtp, expectedTimeStamp, expectedSize, 1, 2, RtpConstants.trackVideo) + val packetResult = RtpFrame(expectedRtp, expectedTimeStamp, expectedSize, RtpConstants.trackVideo) assertEquals(1, frames.size) assertEquals(packetResult, frames[0]) } diff --git a/rtsp/src/test/java/com/pedro/rtsp/rtp/G711PacketTest.kt b/rtsp/src/test/java/com/pedro/rtsp/rtp/G711PacketTest.kt index 6e5c89338..0262cc074 100644 --- a/rtsp/src/test/java/com/pedro/rtsp/rtp/G711PacketTest.kt +++ b/rtsp/src/test/java/com/pedro/rtsp/rtp/G711PacketTest.kt @@ -41,7 +41,6 @@ class G711PacketTest { info.size = fakeG711.size info.flags = 1 val g711Packet = G711Packet(8000) - g711Packet.setPorts(1, 2) g711Packet.setSSRC(123456789) val frames = mutableListOf() g711Packet.createAndSendPacket(ByteBuffer.wrap(fakeG711), info) { @@ -51,7 +50,7 @@ class G711PacketTest { val expectedRtp = byteArrayOf(-128, -120, 0, 1, 0, 15, 18, 6, 7, 91, -51, 21).plus(fakeG711) val expectedTimeStamp = 987654L val expectedSize = RtpConstants.RTP_HEADER_LENGTH + info.size - val packetResult = RtpFrame(expectedRtp, expectedTimeStamp, expectedSize, 1, 2, RtpConstants.trackAudio) + val packetResult = RtpFrame(expectedRtp, expectedTimeStamp, expectedSize, RtpConstants.trackAudio) assertEquals(1, frames.size) assertEquals(packetResult, frames[0]) } diff --git a/rtsp/src/test/java/com/pedro/rtsp/rtp/H264PacketTest.kt b/rtsp/src/test/java/com/pedro/rtsp/rtp/H264PacketTest.kt index e1c7bfe44..48fbea38a 100644 --- a/rtsp/src/test/java/com/pedro/rtsp/rtp/H264PacketTest.kt +++ b/rtsp/src/test/java/com/pedro/rtsp/rtp/H264PacketTest.kt @@ -46,7 +46,6 @@ class H264PacketTest { val fakeSps = byteArrayOf(0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04) val fakePps = byteArrayOf(0x00, 0x00, 0x00, 0x01, 0x0A, 0x0B, 0x0C) val h264Packet = H264Packet(fakeSps, fakePps) - h264Packet.setPorts(1, 2) h264Packet.setSSRC(123456789) val frames = mutableListOf() h264Packet.createAndSendPacket(ByteBuffer.wrap(fakeH264), info) { @@ -57,8 +56,8 @@ class H264PacketTest { val expectedStapA = byteArrayOf(-128, -32, 0, 1, 0, -87, -118, -57, 7, 91, -51, 21, 24, 0, 7, 0, 0, 0, 1, 2, 3, 4, 0, 7, 0, 0, 0, 1, 10, 11, 12) val expectedTimeStamp = 11111111L val expectedSize = RtpConstants.RTP_HEADER_LENGTH + 1 + info.size - header.size - val expectedStapAResult = RtpFrame(expectedStapA, expectedTimeStamp, fakePps.size + fakePps.size + 5 + RtpConstants.RTP_HEADER_LENGTH, 1, 2, RtpConstants.trackVideo) - val expectedPacketResult = RtpFrame(expectedRtp, expectedTimeStamp, expectedSize, 1, 2, RtpConstants.trackVideo) + val expectedStapAResult = RtpFrame(expectedStapA, expectedTimeStamp, fakePps.size + fakePps.size + 5 + RtpConstants.RTP_HEADER_LENGTH, RtpConstants.trackVideo) + val expectedPacketResult = RtpFrame(expectedRtp, expectedTimeStamp, expectedSize, RtpConstants.trackVideo) assertNotEquals(0, frames.size) assertTrue(frames.size == 2) @@ -81,7 +80,6 @@ class H264PacketTest { val fakeSps = byteArrayOf(0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04) val fakePps = byteArrayOf(0x00, 0x00, 0x00, 0x01, 0x0A, 0x0B, 0x0C) val h264Packet = H264Packet(fakeSps, fakePps) - h264Packet.setPorts(1, 2) h264Packet.setSSRC(123456789) val frames = mutableListOf() h264Packet.createAndSendPacket(ByteBuffer.wrap(fakeH264), info) { @@ -98,10 +96,10 @@ class H264PacketTest { val expectedTimeStamp = 11111111L val expectedSize = chunk1.size + RtpConstants.RTP_HEADER_LENGTH + 2 val expectedSize2 = chunk2.size + RtpConstants.RTP_HEADER_LENGTH + 2 - val expectedStapAResult = RtpFrame(expectedStapA, expectedTimeStamp, fakePps.size + fakePps.size + 5 + RtpConstants.RTP_HEADER_LENGTH, 1, 2, RtpConstants.trackVideo) + val expectedStapAResult = RtpFrame(expectedStapA, expectedTimeStamp, fakePps.size + fakePps.size + 5 + RtpConstants.RTP_HEADER_LENGTH, RtpConstants.trackVideo) - val expectedPacketResult = RtpFrame(expectedRtp, expectedTimeStamp, expectedSize, 1, 2, RtpConstants.trackVideo) - val expectedPacketResult2 = RtpFrame(expectedRtp2, expectedTimeStamp, expectedSize2, 1, 2, RtpConstants.trackVideo) + val expectedPacketResult = RtpFrame(expectedRtp, expectedTimeStamp, expectedSize, RtpConstants.trackVideo) + val expectedPacketResult2 = RtpFrame(expectedRtp2, expectedTimeStamp, expectedSize2, RtpConstants.trackVideo) assertNotEquals(0, frames.size) assertTrue(frames.size == 3) diff --git a/rtsp/src/test/java/com/pedro/rtsp/rtp/H265PacketTest.kt b/rtsp/src/test/java/com/pedro/rtsp/rtp/H265PacketTest.kt index 585d69e73..42b996a6b 100644 --- a/rtsp/src/test/java/com/pedro/rtsp/rtp/H265PacketTest.kt +++ b/rtsp/src/test/java/com/pedro/rtsp/rtp/H265PacketTest.kt @@ -44,7 +44,6 @@ class H265PacketTest { info.flags = 1 val h265Packet = H265Packet() - h265Packet.setPorts(1, 2) h265Packet.setSSRC(123456789) val frames = mutableListOf() h265Packet.createAndSendPacket(ByteBuffer.wrap(fakeH265), info) { @@ -54,7 +53,7 @@ class H265PacketTest { val expectedRtp = byteArrayOf(-128, -32, 0, 1, 0, -87, -118, -57, 7, 91, -51, 21, 5, 0).plus(fakeH265.copyOfRange(header.size, fakeH265.size)) val expectedTimeStamp = 11111111L val expectedSize = RtpConstants.RTP_HEADER_LENGTH + 2 + info.size - header.size - val expectedPacketResult = RtpFrame(expectedRtp, expectedTimeStamp, expectedSize, 1, 2, RtpConstants.trackVideo) + val expectedPacketResult = RtpFrame(expectedRtp, expectedTimeStamp, expectedSize, RtpConstants.trackVideo) assertNotEquals(0, frames.size) assertTrue(frames.size == 1) @@ -74,7 +73,6 @@ class H265PacketTest { info.flags = 1 val h265Packet = H265Packet() - h265Packet.setPorts(1, 2) h265Packet.setSSRC(123456789) val frames = mutableListOf() h265Packet.createAndSendPacket(ByteBuffer.wrap(fakeH265), info) { @@ -91,8 +89,8 @@ class H265PacketTest { val expectedSize = chunk1.size + RtpConstants.RTP_HEADER_LENGTH + 3 val expectedSize2 = chunk2.size + RtpConstants.RTP_HEADER_LENGTH + 3 - val expectedPacketResult = RtpFrame(expectedRtp, expectedTimeStamp, expectedSize, 1, 2, RtpConstants.trackVideo) - val expectedPacketResult2 = RtpFrame(expectedRtp2, expectedTimeStamp, expectedSize2, 1, 2, RtpConstants.trackVideo) + val expectedPacketResult = RtpFrame(expectedRtp, expectedTimeStamp, expectedSize, RtpConstants.trackVideo) + val expectedPacketResult2 = RtpFrame(expectedRtp2, expectedTimeStamp, expectedSize2, RtpConstants.trackVideo) assertNotEquals(0, frames.size) assertTrue(frames.size == 2) diff --git a/rtsp/src/test/java/com/pedro/rtsp/rtp/OpusPacketTest.kt b/rtsp/src/test/java/com/pedro/rtsp/rtp/OpusPacketTest.kt index 9d8cc69fb..41806c6d5 100644 --- a/rtsp/src/test/java/com/pedro/rtsp/rtp/OpusPacketTest.kt +++ b/rtsp/src/test/java/com/pedro/rtsp/rtp/OpusPacketTest.kt @@ -40,7 +40,6 @@ class OpusPacketTest { info.size = fakeOpus.size info.flags = 1 val opusPacket = OpusPacket(8000) - opusPacket.setPorts(1, 2) opusPacket.setSSRC(123456789) val frames = mutableListOf() opusPacket.createAndSendPacket(ByteBuffer.wrap(fakeOpus), info) { @@ -50,7 +49,7 @@ class OpusPacketTest { val expectedRtp = byteArrayOf(-128, -31, 0, 1, 0, 15, 18, 6, 7, 91, -51, 21).plus(fakeOpus) val expectedTimeStamp = 987654L val expectedSize = RtpConstants.RTP_HEADER_LENGTH + info.size - val packetResult = RtpFrame(expectedRtp, expectedTimeStamp, expectedSize, 1, 2, RtpConstants.trackAudio) + val packetResult = RtpFrame(expectedRtp, expectedTimeStamp, expectedSize, RtpConstants.trackAudio) assertEquals(1, frames.size) assertEquals(packetResult, frames[0]) } diff --git a/rtsp/src/test/java/com/pedro/rtsp/rtp/RtpStreamSocketTest.kt b/rtsp/src/test/java/com/pedro/rtsp/rtp/RtpStreamSocketTest.kt index 700554170..e098427d4 100644 --- a/rtsp/src/test/java/com/pedro/rtsp/rtp/RtpStreamSocketTest.kt +++ b/rtsp/src/test/java/com/pedro/rtsp/rtp/RtpStreamSocketTest.kt @@ -16,6 +16,8 @@ package com.pedro.rtsp.rtp +import com.pedro.common.socket.TcpStreamSocket +import com.pedro.common.socket.UdpStreamSocket import com.pedro.rtsp.rtp.sockets.BaseRtpSocket import com.pedro.rtsp.rtp.sockets.RtpSocketUdp import com.pedro.rtsp.rtsp.Protocol @@ -31,9 +33,6 @@ import org.mockito.junit.MockitoJUnitRunner import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.times import org.mockito.kotlin.verify -import java.io.OutputStream -import java.net.DatagramPacket -import java.net.MulticastSocket /** * Created by pedro on 9/9/23. @@ -42,39 +41,39 @@ import java.net.MulticastSocket class RtpStreamSocketTest { @Mock - private lateinit var multicastSocketMocked: MulticastSocket + private lateinit var udpSocket: UdpStreamSocket @Mock - private lateinit var outputMocked: OutputStream + private lateinit var tcpSocket: TcpStreamSocket @Test fun `GIVEN multiple video or audio rtp frames WHEN update rtcp tcp send THEN send only 1 of video and 1 of audio each 3 seconds`() = runTest { - val senderReportTcp = BaseRtpSocket.getInstance(Protocol.TCP, 0, 1) - senderReportTcp.setSocket(outputMocked, "127.0.0.1") - val fakeFrameVideo = RtpFrame(byteArrayOf(0x00, 0x00, 0x00), 0, 3, 0, 0, RtpConstants.trackVideo) - val fakeFrameAudio = RtpFrame(byteArrayOf(0x00, 0x00, 0x00), 0, 3, 0, 0, RtpConstants.trackAudio) + val senderReportTcp = BaseRtpSocket.getInstance(Protocol.TCP, "127.0.0.1", 0, 1, 2, 3) + senderReportTcp.setSocket(tcpSocket) + val fakeFrameVideo = RtpFrame(byteArrayOf(0x00, 0x00, 0x00), 0, 3, RtpConstants.trackVideo) + val fakeFrameAudio = RtpFrame(byteArrayOf(0x00, 0x00, 0x00), 0, 3, RtpConstants.trackAudio) (0 until 10).forEach { value -> val frame = if (value % 2 == 0) fakeFrameVideo else fakeFrameAudio senderReportTcp.sendFrame(frame) } val resultValue = argumentCaptor() withContext(Dispatchers.IO) { - verify(outputMocked, times((10))).write(resultValue.capture()) + verify(tcpSocket, times((10))).write(resultValue.capture()) } } @Test fun `GIVEN multiple video or audio rtp frames WHEN update rtcp udp send THEN send only 1 of video and 1 of audio each 3 seconds`() = runTest { - val senderReportUdp = RtpSocketUdp(11111, 11112, multicastSocketMocked, multicastSocketMocked) - senderReportUdp.setSocket(outputMocked, "127.0.0.1") - val fakeFrameVideo = RtpFrame(byteArrayOf(0x00, 0x00, 0x00), 0, 3, 0, 0, RtpConstants.trackVideo) - val fakeFrameAudio = RtpFrame(byteArrayOf(0x00, 0x00, 0x00), 0, 3, 0, 0, RtpConstants.trackAudio) + val senderReportUdp = RtpSocketUdp(udpSocket, udpSocket) + senderReportUdp.setSocket(tcpSocket) + val fakeFrameVideo = RtpFrame(byteArrayOf(0x00, 0x00, 0x00), 0, 3, RtpConstants.trackVideo) + val fakeFrameAudio = RtpFrame(byteArrayOf(0x00, 0x00, 0x00), 0, 3, RtpConstants.trackAudio) (0 until 10).forEach { value -> val frame = if (value % 2 == 0) fakeFrameVideo else fakeFrameAudio senderReportUdp.sendFrame(frame) } - val resultValue = argumentCaptor() + val resultValue = argumentCaptor() withContext(Dispatchers.IO) { - verify(multicastSocketMocked, times((10))).send(resultValue.capture()) + verify(udpSocket, times((10))).writePacket(resultValue.capture()) } } } \ No newline at end of file From ea83e4ed6429291cbdfd8246cf33bb3442848327 Mon Sep 17 00:00:00 2001 From: pedroSG94 Date: Tue, 24 Sep 2024 18:21:37 +0200 Subject: [PATCH 12/13] fix propagate valid error message --- common/src/main/java/com/pedro/common/Extensions.kt | 4 ++++ .../java/com/pedro/common/socket/TcpStreamSocket.kt | 10 ++++++---- rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpClient.kt | 9 ++++++--- rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpSender.kt | 3 ++- .../com/pedro/rtmp/utils/socket/TcpTunneledSocket.kt | 4 ++-- rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt | 7 ++++--- rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspSender.kt | 3 ++- srt/src/main/java/com/pedro/srt/srt/SrtClient.kt | 8 +++++--- srt/src/main/java/com/pedro/srt/srt/SrtSender.kt | 3 ++- udp/src/main/java/com/pedro/udp/UdpClient.kt | 3 ++- udp/src/main/java/com/pedro/udp/UdpSender.kt | 3 ++- 11 files changed, 37 insertions(+), 20 deletions(-) diff --git a/common/src/main/java/com/pedro/common/Extensions.kt b/common/src/main/java/com/pedro/common/Extensions.kt index 7ff3b12a1..0d4dfdf5a 100644 --- a/common/src/main/java/com/pedro/common/Extensions.kt +++ b/common/src/main/java/com/pedro/common/Extensions.kt @@ -135,4 +135,8 @@ fun String.getIndexes(char: Char): Array { val indexes = mutableListOf() forEachIndexed { index, c -> if (c == char) indexes.add(index) } return indexes.toTypedArray() +} + +fun Throwable.validMessage(): String { + return (message ?: "").ifEmpty { javaClass.simpleName } } \ No newline at end of file diff --git a/common/src/main/java/com/pedro/common/socket/TcpStreamSocket.kt b/common/src/main/java/com/pedro/common/socket/TcpStreamSocket.kt index 778443252..153498372 100644 --- a/common/src/main/java/com/pedro/common/socket/TcpStreamSocket.kt +++ b/common/src/main/java/com/pedro/common/socket/TcpStreamSocket.kt @@ -32,7 +32,7 @@ import io.ktor.utils.io.readUTF8Line import io.ktor.utils.io.writeByte import io.ktor.utils.io.writeFully import io.ktor.utils.io.writeStringUtf8 -import java.io.IOException +import java.net.ConnectException import java.security.SecureRandom import javax.net.ssl.TrustManager import kotlin.coroutines.coroutineContext @@ -112,7 +112,8 @@ class TcpStreamSocket( } suspend fun read(): Int { - return input?.readByte()?.toInt() ?: throw IOException("read with socket closed") + val input = input ?: throw ConnectException("Read with socket closed, broken pipe") + return input.readByte().toInt() } suspend fun readUInt16(): Int { @@ -132,11 +133,12 @@ class TcpStreamSocket( } suspend fun readUntil(b: ByteArray) { - return input?.readFully(b) ?: throw IOException("read with socket closed") + val input = input ?: throw ConnectException("Read with socket closed, broken pipe") + return input.readFully(b) } suspend fun readLine(): String? { - val input = input ?: throw IOException("read with socket closed") + val input = input ?: throw ConnectException("Read with socket closed, broken pipe") return input.readUTF8Line() } diff --git a/rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpClient.kt b/rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpClient.kt index 1b87a35b4..390ca1530 100644 --- a/rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpClient.kt +++ b/rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpClient.kt @@ -20,10 +20,12 @@ import android.media.MediaCodec import android.util.Log import com.pedro.common.AudioCodec import com.pedro.common.ConnectChecker +import com.pedro.common.ConnectionFailed import com.pedro.common.TimeUtils import com.pedro.common.UrlParser import com.pedro.common.VideoCodec import com.pedro.common.onMainThread +import com.pedro.common.validMessage import com.pedro.rtmp.amf.AmfVersion import com.pedro.rtmp.rtmp.message.* import com.pedro.rtmp.rtmp.message.command.Command @@ -269,7 +271,7 @@ class RtmpClient(private val connectChecker: ConnectChecker) { if (error != null) { Log.e(TAG, "connection error", error) onMainThread { - connectChecker.onConnectionFailed("Error configure stream, ${error.message}") + connectChecker.onConnectionFailed("Error configure stream, ${error.validMessage()}") } return@launch } @@ -281,6 +283,7 @@ class RtmpClient(private val connectChecker: ConnectChecker) { while (scope.isActive && isStreaming) { val error = runCatching { if (isAlive()) { + delay(2000) //ignore packet after connect if tunneled to avoid spam idle if (!tunneled) handleMessages() } else { @@ -290,7 +293,7 @@ class RtmpClient(private val connectChecker: ConnectChecker) { scope.cancel() } }.exceptionOrNull() - if (error != null && error !is SocketTimeoutException) { + if (ConnectionFailed.parse(error?.validMessage() ?: "") != ConnectionFailed.TIMEOUT) { scope.cancel() } } @@ -491,7 +494,7 @@ class RtmpClient(private val connectChecker: ConnectChecker) { } } - suspend fun closeConnection() { + private suspend fun closeConnection() { socket?.close() commandsManager.reset() } diff --git a/rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpSender.kt b/rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpSender.kt index 7de6f83b6..25bc41c86 100644 --- a/rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpSender.kt +++ b/rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpSender.kt @@ -24,6 +24,7 @@ import com.pedro.common.ConnectChecker import com.pedro.common.VideoCodec import com.pedro.common.onMainThread import com.pedro.common.trySend +import com.pedro.common.validMessage import com.pedro.rtmp.flv.BasePacket import com.pedro.rtmp.flv.FlvPacket import com.pedro.rtmp.flv.FlvType @@ -183,7 +184,7 @@ class RtmpSender( }.exceptionOrNull() if (error != null) { onMainThread { - connectChecker.onConnectionFailed("Error send packet, " + error.message) + connectChecker.onConnectionFailed("Error send packet, ${error.validMessage()}") } Log.e(TAG, "send error: ", error) return@launch diff --git a/rtmp/src/main/java/com/pedro/rtmp/utils/socket/TcpTunneledSocket.kt b/rtmp/src/main/java/com/pedro/rtmp/utils/socket/TcpTunneledSocket.kt index 145ef6375..181cdd789 100644 --- a/rtmp/src/main/java/com/pedro/rtmp/utils/socket/TcpTunneledSocket.kt +++ b/rtmp/src/main/java/com/pedro/rtmp/utils/socket/TcpTunneledSocket.kt @@ -83,7 +83,7 @@ class TcpTunneledSocket(private val host: String, private val port: Int, private val bytes = socket.inputStream.readBytes() if (bytes.size > 1) input = ByteArrayInputStream(bytes, 1, bytes.size) val success = socket.responseCode == HttpURLConnection.HTTP_OK - if (!success) throw IOException("send packet failed: ${socket.responseMessage}") + if (!success) throw IOException("send packet failed: ${socket.responseMessage}, broken pipe") } finally { socket.disconnect() } @@ -96,7 +96,7 @@ class TcpTunneledSocket(private val host: String, private val port: Int, private socket.connect() val data = socket.inputStream.readBytes() val success = socket.responseCode == HttpURLConnection.HTTP_OK - if (!success) throw IOException("receive packet failed: ${socket.responseMessage}") + if (!success) throw IOException("receive packet failed: ${socket.responseMessage}, broken pipe") return data } finally { socket.disconnect() diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt b/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt index 0981821ad..983ecd6f3 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt @@ -20,10 +20,12 @@ import android.media.MediaCodec import android.util.Log import com.pedro.common.AudioCodec import com.pedro.common.ConnectChecker +import com.pedro.common.ConnectionFailed import com.pedro.common.UrlParser import com.pedro.common.VideoCodec import com.pedro.common.onMainThread import com.pedro.common.socket.TcpStreamSocket +import com.pedro.common.validMessage import com.pedro.rtsp.rtsp.commands.CommandsManager import com.pedro.rtsp.rtsp.commands.Method import com.pedro.rtsp.utils.RtpConstants @@ -38,7 +40,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.withTimeoutOrNull import java.io.* -import java.net.SocketTimeoutException import java.net.URISyntaxException import java.nio.ByteBuffer import javax.net.ssl.TrustManager @@ -340,7 +341,7 @@ class RtspClient(private val connectChecker: ConnectChecker) { if (error != null) { Log.e(TAG, "connection error", error) onMainThread { - connectChecker.onConnectionFailed("Error configure stream, ${error.message}") + connectChecker.onConnectionFailed("Error configure stream, ${error.validMessage()}") } return@launch } @@ -367,7 +368,7 @@ class RtspClient(private val connectChecker: ConnectChecker) { scope.cancel() } }.exceptionOrNull() - if (error != null && error !is SocketTimeoutException) { + if (ConnectionFailed.parse(error?.validMessage() ?: "") != ConnectionFailed.TIMEOUT) { scope.cancel() } } diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspSender.kt b/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspSender.kt index 401208329..600c01c69 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspSender.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspSender.kt @@ -25,6 +25,7 @@ import com.pedro.common.VideoCodec import com.pedro.common.onMainThread import com.pedro.common.socket.TcpStreamSocket import com.pedro.common.trySend +import com.pedro.common.validMessage import com.pedro.rtsp.rtcp.BaseSenderReport import com.pedro.rtsp.rtp.packets.* import com.pedro.rtsp.rtp.sockets.BaseRtpSocket @@ -195,7 +196,7 @@ class RtspSender( }.exceptionOrNull() if (error != null) { onMainThread { - connectChecker.onConnectionFailed("Error send packet, ${error.message}") + connectChecker.onConnectionFailed("Error send packet, ${error.validMessage()}") } Log.e(TAG, "send error: ", error) return@launch diff --git a/srt/src/main/java/com/pedro/srt/srt/SrtClient.kt b/srt/src/main/java/com/pedro/srt/srt/SrtClient.kt index bd875e358..1f7616bc5 100644 --- a/srt/src/main/java/com/pedro/srt/srt/SrtClient.kt +++ b/srt/src/main/java/com/pedro/srt/srt/SrtClient.kt @@ -20,9 +20,11 @@ import android.media.MediaCodec import android.util.Log import com.pedro.common.AudioCodec import com.pedro.common.ConnectChecker +import com.pedro.common.ConnectionFailed import com.pedro.common.UrlParser import com.pedro.common.VideoCodec import com.pedro.common.onMainThread +import com.pedro.common.validMessage import com.pedro.srt.srt.packets.ControlPacket import com.pedro.srt.srt.packets.DataPacket import com.pedro.srt.srt.packets.SrtPacket @@ -51,7 +53,6 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeoutOrNull import java.io.IOException -import java.net.SocketTimeoutException import java.net.URISyntaxException import java.nio.ByteBuffer @@ -241,7 +242,7 @@ class SrtClient(private val connectChecker: ConnectChecker) { if (error != null) { Log.e(TAG, "connection error", error) onMainThread { - connectChecker.onConnectionFailed("Error configure stream, ${error.message}") + connectChecker.onConnectionFailed("Error configure stream, ${error.validMessage()}") } return@launch } @@ -301,6 +302,7 @@ class SrtClient(private val connectChecker: ConnectChecker) { while (scope.isActive && isStreaming) { val error = runCatching { if (isAlive()) { + delay(2000) //ignore packet after connect if tunneled to avoid spam idle handleMessages() } else { @@ -310,7 +312,7 @@ class SrtClient(private val connectChecker: ConnectChecker) { scope.cancel() } }.exceptionOrNull() - if (error != null && error !is SocketTimeoutException) { + if (ConnectionFailed.parse(error?.validMessage() ?: "") != ConnectionFailed.TIMEOUT) { scope.cancel() } } diff --git a/srt/src/main/java/com/pedro/srt/srt/SrtSender.kt b/srt/src/main/java/com/pedro/srt/srt/SrtSender.kt index 8659ba4cb..023328ea9 100644 --- a/srt/src/main/java/com/pedro/srt/srt/SrtSender.kt +++ b/srt/src/main/java/com/pedro/srt/srt/SrtSender.kt @@ -23,6 +23,7 @@ import com.pedro.common.BitrateManager import com.pedro.common.ConnectChecker import com.pedro.common.onMainThread import com.pedro.common.trySend +import com.pedro.common.validMessage import com.pedro.srt.mpeg2ts.MpegTsPacket import com.pedro.srt.mpeg2ts.MpegTsPacketizer import com.pedro.srt.mpeg2ts.Pid @@ -171,7 +172,7 @@ class SrtSender( }.exceptionOrNull() if (error != null) { onMainThread { - connectChecker.onConnectionFailed("Error send packet, " + error.message) + connectChecker.onConnectionFailed("Error send packet, ${error.validMessage()}") } Log.e(TAG, "send error: ", error) return@launch diff --git a/udp/src/main/java/com/pedro/udp/UdpClient.kt b/udp/src/main/java/com/pedro/udp/UdpClient.kt index e58d49705..5b52faa03 100644 --- a/udp/src/main/java/com/pedro/udp/UdpClient.kt +++ b/udp/src/main/java/com/pedro/udp/UdpClient.kt @@ -23,6 +23,7 @@ import com.pedro.common.ConnectChecker import com.pedro.common.UrlParser import com.pedro.common.VideoCodec import com.pedro.common.onMainThread +import com.pedro.common.validMessage import com.pedro.udp.utils.UdpSocket import com.pedro.udp.utils.UdpType import kotlinx.coroutines.CoroutineScope @@ -169,7 +170,7 @@ class UdpClient(private val connectChecker: ConnectChecker) { if (error != null) { Log.e(TAG, "connection error", error) onMainThread { - connectChecker.onConnectionFailed("Error configure stream, ${error.message}") + connectChecker.onConnectionFailed("Error configure stream, ${error.validMessage()}") } return@launch } diff --git a/udp/src/main/java/com/pedro/udp/UdpSender.kt b/udp/src/main/java/com/pedro/udp/UdpSender.kt index 635e649cc..b748b0bff 100644 --- a/udp/src/main/java/com/pedro/udp/UdpSender.kt +++ b/udp/src/main/java/com/pedro/udp/UdpSender.kt @@ -23,6 +23,7 @@ import com.pedro.common.BitrateManager import com.pedro.common.ConnectChecker import com.pedro.common.onMainThread import com.pedro.common.trySend +import com.pedro.common.validMessage import com.pedro.srt.mpeg2ts.MpegTsPacket import com.pedro.srt.mpeg2ts.MpegTsPacketizer import com.pedro.srt.mpeg2ts.Pid @@ -171,7 +172,7 @@ class UdpSender( }.exceptionOrNull() if (error != null) { onMainThread { - connectChecker.onConnectionFailed("Error send packet, " + error.message) + connectChecker.onConnectionFailed("Error send packet, ${error.validMessage()}") } Log.e(TAG, "send error: ", error) return@launch From 1512feb9e53cea33f9f83a5335cceb254583a884 Mon Sep 17 00:00:00 2001 From: pedroSG94 Date: Tue, 24 Sep 2024 18:28:55 +0200 Subject: [PATCH 13/13] avoid parse no errors --- rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpClient.kt | 2 +- rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt | 2 +- srt/src/main/java/com/pedro/srt/srt/SrtClient.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpClient.kt b/rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpClient.kt index 390ca1530..f72f17901 100644 --- a/rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpClient.kt +++ b/rtmp/src/main/java/com/pedro/rtmp/rtmp/RtmpClient.kt @@ -293,7 +293,7 @@ class RtmpClient(private val connectChecker: ConnectChecker) { scope.cancel() } }.exceptionOrNull() - if (ConnectionFailed.parse(error?.validMessage() ?: "") != ConnectionFailed.TIMEOUT) { + if (error != null && ConnectionFailed.parse(error.validMessage()) != ConnectionFailed.TIMEOUT) { scope.cancel() } } diff --git a/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt b/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt index 983ecd6f3..34c90db3f 100644 --- a/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt +++ b/rtsp/src/main/java/com/pedro/rtsp/rtsp/RtspClient.kt @@ -368,7 +368,7 @@ class RtspClient(private val connectChecker: ConnectChecker) { scope.cancel() } }.exceptionOrNull() - if (ConnectionFailed.parse(error?.validMessage() ?: "") != ConnectionFailed.TIMEOUT) { + if (error != null && ConnectionFailed.parse(error.validMessage()) != ConnectionFailed.TIMEOUT) { scope.cancel() } } diff --git a/srt/src/main/java/com/pedro/srt/srt/SrtClient.kt b/srt/src/main/java/com/pedro/srt/srt/SrtClient.kt index 1f7616bc5..3c852162e 100644 --- a/srt/src/main/java/com/pedro/srt/srt/SrtClient.kt +++ b/srt/src/main/java/com/pedro/srt/srt/SrtClient.kt @@ -312,7 +312,7 @@ class SrtClient(private val connectChecker: ConnectChecker) { scope.cancel() } }.exceptionOrNull() - if (ConnectionFailed.parse(error?.validMessage() ?: "") != ConnectionFailed.TIMEOUT) { + if (error != null && ConnectionFailed.parse(error.validMessage()) != ConnectionFailed.TIMEOUT) { scope.cancel() } }