From 5dc292ce6497a7276eeef79ece67101f3b31f573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Haibo=20Chen=28=E9=99=88=E6=B5=B7=E5=8D=9A=29?= <495810242@qq.com> Date: Fri, 11 Jul 2025 20:18:40 +0800 Subject: [PATCH] NEW PROTOCOL: Support viewing stream over RTSP. v7.0.47 (#4333) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Introduce This PR adds support for viewing streams via the RTSP protocol. Note that it only supports viewing streams, not publishing streams via RTSP. Currently, only publishing via RTMP is supported, which is then converted to RTSP. Further work is needed to support publishing RTC/SRT streams and converting them to RTSP. ## Usage Build and run SRS with RTSP support: ``` cd srs/trunk && ./configure --rtsp=on && make -j16 ./objs/srs -c conf/rtsp.conf ``` Push stream via RTMP by FFmpeg: ``` ffmpeg -re -i doc/source.flv -c copy -f flv rtmp://localhost/live/livestream ``` View the stream via RTSP protocol, try UDP first, then use TCP: ``` ffplay -i rtsp://localhost:8554/live/livestream ``` Or specify the transport protocol with TCP: ``` ffplay -rtsp_transport tcp -i rtsp://localhost:8554/live/livestream ``` ## Unit Test Run utest for RTSP: ``` ./configure --utest=on & make utest -j16 ./objs/srs_utest ``` ## Regression Test You need to start SRS for regression testing. ``` ./objs/srs -c conf/regression-test-for-clion.conf ``` Then run regression tests for RTSP. ``` cd srs/trunk/3rdparty/srs-bench go test ./srs -mod=vendor -v -count=1 -run=TestRtmpPublish_RtspPlay ``` ## Blackbox Test For blackbox testing, SRS will be started by utest, so there is no need to start SRS manually. ``` cd srs/trunk/3rdparty/srs-bench go test ./blackbox -mod=vendor -v -count=1 -run=TestFast_RtmpPublish_RtspPlay_Basic ``` ## UDP Transport As UDP requires port allocation, this PR doesn't support delivering media stream via UDP transport, so it will fail if you try to use UDP as transport: ``` ffplay -rtsp_transport udp -i rtsp://localhost:8554/live/livestream [rtsp @ 0x7fbc99a14880] method SETUP failed: 461 Unsupported Transport rtsp://localhost:8554/live/livestream: Protocol not supported [2025-07-05 21:30:52.738][WARN][14916][7d7gf623][35] RTSP: setup failed: code=2057 (RtspTransportNotSupported) : UDP transport not supported, only TCP/interleaved mode is supported ``` There are no plans to support UDP transport for RTSP. In the real world, UDP is rarely used; the vast majority of RTSP traffic uses TCP. ## Play Before Publish RTSP supports audio with AAC and OPUS codecs, which is significantly different from RTMP or WebRTC. RTSP uses commands to exchange SDP and specify the audio track to play, unlike WHEP or HTTP-FLV, which use the query string of the URL. RTSP depends on the player’s behavior, making it very difficult to use and describe. Considering the feature that allows playing the stream before publishing it, it requires generating some default parameters in the SDP. For OPUS, the sample rate is 48 kHz with 2 channels, while AAC is more complex, especially regarding the sample rate, which may be 44.1 kHz, 32 kHz, or 48 kHz. Therefore, for RTSP, we cannot support play-then-publish. Instead, there must already be a stream when playing it, so that the audio codec is determined. ## Opus Codec No Opus codec support for RTSP, because for RTC2RTSP, it always converts RTC to RTMP frames, then converts them to RTSP packets. Therefore, the audio codec is always AAC after converting RTC to RTMP. This means the bridge architecture needs some changes. We need a new bridge that binds to the target protocol. For example, RTC2RTMP converts the audio codec, but RTC2RTSP keeps the original audio codec. Furthermore, the RTC2RTMP bridge should also support bypassing the Opus codec if we use enhanced-RTMP, which supports the Opus audio codec. I think it should be configurable to either transcode or bypass the audio codec. However, this is not relevant to RTSP. ## AI Contributor Below commits are contributed by AI: * [AI: Remove support for media transport via UDP.](https://github.com/ossrs/srs/pull/4333/commits/755686229f0d3910f058e6f75993112a68c5f60a) * [AI: Add crutial logs for each RTSP stage.](https://github.com/ossrs/srs/pull/4333/commits/9c8cbe7bdefda19087f87fdb5e041a8934e4db1d) * [AI: Support AAC doec for RTSP.](https://github.com/ossrs/srs/pull/4333/commits/7d7cc12bae269850011d4757eae635a61de99f36) * [AI: Add option --rtsp for RTSP.](https://github.com/ossrs/srs/pull/4333/commits/f67414d9ee98da39cda1f7d47cdf793c0e5a8412) * [AI: Extract SrsRtpVideoBuilder for RTC and RTSP.](https://github.com/ossrs/srs/pull/4333/commits/562e76b90469a1b016857eb23b090e8e45b52de3) --------- Co-authored-by: Jacob Su Co-authored-by: winlin --- .../3rdparty/srs-bench/blackbox/rtsp_test.go | 119 + trunk/3rdparty/srs-bench/blackbox/util.go | 13 +- trunk/3rdparty/srs-bench/go.mod | 2 + trunk/3rdparty/srs-bench/go.sum | 4 + trunk/3rdparty/srs-bench/srs/rtmp_test.go | 80 +- trunk/3rdparty/srs-bench/srs/util.go | 89 +- .../bluenviron/gortsplib/v4/.dockerignore | 1 + .../bluenviron/gortsplib/v4/.gitignore | 1 + .../bluenviron/gortsplib/v4/.golangci.yml | 73 + .../bluenviron/gortsplib/v4/LICENSE | 21 + .../bluenviron/gortsplib/v4/Makefile | 26 + .../bluenviron/gortsplib/v4/README.md | 177 ++ .../gortsplib/v4/async_processor.go | 58 + .../bluenviron/gortsplib/v4/client.go | 2256 +++++++++++++++++ .../bluenviron/gortsplib/v4/client_format.go | 159 ++ .../bluenviron/gortsplib/v4/client_media.go | 361 +++ .../bluenviron/gortsplib/v4/client_reader.go | 79 + .../bluenviron/gortsplib/v4/client_stats.go | 7 + .../gortsplib/v4/client_udp_listener.go | 188 ++ .../bluenviron/gortsplib/v4/constants.go | 9 + .../bluenviron/gortsplib/v4/empty_timer.go | 11 + .../bluenviron/gortsplib/v4/pkg/auth/auth.go | 2 + .../bluenviron/gortsplib/v4/pkg/auth/nonce.go | 17 + .../gortsplib/v4/pkg/auth/sender.go | 89 + .../gortsplib/v4/pkg/auth/validate.go | 33 + .../gortsplib/v4/pkg/auth/verify.go | 135 + .../gortsplib/v4/pkg/auth/www_authenticate.go | 51 + .../bluenviron/gortsplib/v4/pkg/base/body.go | 54 + .../gortsplib/v4/pkg/base/header.go | 148 ++ .../v4/pkg/base/interleaved_frame.go | 72 + .../bluenviron/gortsplib/v4/pkg/base/path.go | 17 + .../gortsplib/v4/pkg/base/request.go | 173 ++ .../gortsplib/v4/pkg/base/response.go | 254 ++ .../bluenviron/gortsplib/v4/pkg/base/url.go | 107 + .../bluenviron/gortsplib/v4/pkg/base/utils.go | 34 + .../v4/pkg/bytecounter/bytecounter.go | 54 + .../bluenviron/gortsplib/v4/pkg/conn/conn.go | 105 + .../gortsplib/v4/pkg/description/media.go | 218 ++ .../gortsplib/v4/pkg/description/session.go | 164 ++ .../bluenviron/gortsplib/v4/pkg/format/ac3.go | 101 + .../bluenviron/gortsplib/v4/pkg/format/av1.go | 124 + .../gortsplib/v4/pkg/format/format.go | 234 ++ .../gortsplib/v4/pkg/format/g711.go | 134 + .../gortsplib/v4/pkg/format/g722.go | 76 + .../gortsplib/v4/pkg/format/g726.go | 73 + .../gortsplib/v4/pkg/format/generic.go | 111 + .../gortsplib/v4/pkg/format/h264.go | 227 ++ .../gortsplib/v4/pkg/format/h265.go | 240 ++ .../gortsplib/v4/pkg/format/lpcm.go | 143 ++ .../gortsplib/v4/pkg/format/mjpeg.go | 74 + .../gortsplib/v4/pkg/format/mpeg1_audio.go | 74 + .../gortsplib/v4/pkg/format/mpeg1_video.go | 74 + .../gortsplib/v4/pkg/format/mpeg4_audio.go | 343 +++ .../gortsplib/v4/pkg/format/mpeg4_video.go | 133 + .../gortsplib/v4/pkg/format/mpegts.go | 48 + .../gortsplib/v4/pkg/format/opus.go | 199 ++ .../gortsplib/v4/pkg/format/rtpac3/decoder.go | 149 ++ .../gortsplib/v4/pkg/format/rtpac3/encoder.go | 201 ++ .../gortsplib/v4/pkg/format/rtpac3/rtpac3.go | 2 + .../gortsplib/v4/pkg/format/rtpav1/decoder.go | 202 ++ .../gortsplib/v4/pkg/format/rtpav1/encoder.go | 171 ++ .../gortsplib/v4/pkg/format/rtpav1/rtpav1.go | 2 + .../v4/pkg/format/rtph264/decoder.go | 306 +++ .../v4/pkg/format/rtph264/encoder.go | 248 ++ .../v4/pkg/format/rtph264/rtph264.go | 2 + .../v4/pkg/format/rtph265/decoder.go | 220 ++ .../v4/pkg/format/rtph265/encoder.go | 247 ++ .../v4/pkg/format/rtph265/rtph265.go | 2 + .../v4/pkg/format/rtplpcm/decoder.go | 33 + .../v4/pkg/format/rtplpcm/encoder.go | 124 + .../v4/pkg/format/rtplpcm/rtplpcm.go | 2 + .../v4/pkg/format/rtpmjpeg/decoder.go | 304 +++ .../v4/pkg/format/rtpmjpeg/encoder.go | 279 ++ .../v4/pkg/format/rtpmjpeg/header_jpeg.go | 49 + .../rtpmjpeg/header_quantization_table.go | 59 + .../format/rtpmjpeg/header_restart_marker.go | 26 + .../v4/pkg/format/rtpmjpeg/rtpmjpeg.go | 6 + .../v4/pkg/format/rtpmpeg1audio/decoder.go | 127 + .../v4/pkg/format/rtpmpeg1audio/encoder.go | 196 ++ .../pkg/format/rtpmpeg1audio/rtpmpeg1audio.go | 2 + .../v4/pkg/format/rtpmpeg1video/decoder.go | 163 ++ .../v4/pkg/format/rtpmpeg1video/encoder.go | 239 ++ .../pkg/format/rtpmpeg1video/rtpmpeg1video.go | 2 + .../v4/pkg/format/rtpmpeg4audio/decoder.go | 62 + .../format/rtpmpeg4audio/decoder_generic.go | 201 ++ .../pkg/format/rtpmpeg4audio/decoder_latm.go | 63 + .../v4/pkg/format/rtpmpeg4audio/encoder.go | 88 + .../format/rtpmpeg4audio/encoder_generic.go | 196 ++ .../pkg/format/rtpmpeg4audio/encoder_latm.go | 76 + .../rtpmpeg4audio/payload_length_info.go | 38 + .../pkg/format/rtpmpeg4audio/rtpmpeg4audio.go | 2 + .../v4/pkg/format/rtpmpeg4video/decoder.go | 82 + .../v4/pkg/format/rtpmpeg4video/encoder.go | 108 + .../pkg/format/rtpmpeg4video/rtpmpeg4video.go | 2 + .../v4/pkg/format/rtpsimpleaudio/decoder.go | 18 + .../v4/pkg/format/rtpsimpleaudio/encoder.go | 89 + .../format/rtpsimpleaudio/rtpsimpleaudio.go | 2 + .../gortsplib/v4/pkg/format/rtpvp8/decoder.go | 113 + .../gortsplib/v4/pkg/format/rtpvp8/encoder.go | 98 + .../gortsplib/v4/pkg/format/rtpvp8/rtpvp8.go | 2 + .../gortsplib/v4/pkg/format/rtpvp9/decoder.go | 108 + .../gortsplib/v4/pkg/format/rtpvp9/encoder.go | 115 + .../gortsplib/v4/pkg/format/rtpvp9/rtpvp9.go | 2 + .../gortsplib/v4/pkg/format/speex.go | 79 + .../gortsplib/v4/pkg/format/vorbis.go | 92 + .../bluenviron/gortsplib/v4/pkg/format/vp8.go | 112 + .../bluenviron/gortsplib/v4/pkg/format/vp9.go | 124 + .../gortsplib/v4/pkg/headers/authenticate.go | 186 ++ .../gortsplib/v4/pkg/headers/authorization.go | 179 ++ .../gortsplib/v4/pkg/headers/keyval.go | 77 + .../gortsplib/v4/pkg/headers/range.go | 354 +++ .../gortsplib/v4/pkg/headers/rtpinfo.go | 101 + .../gortsplib/v4/pkg/headers/session.go | 71 + .../gortsplib/v4/pkg/headers/transport.go | 379 +++ .../gortsplib/v4/pkg/liberrors/client.go | 343 +++ .../gortsplib/v4/pkg/liberrors/liberrors.go | 2 + .../gortsplib/v4/pkg/liberrors/server.go | 284 +++ .../gortsplib/v4/pkg/multicast/multi_conn.go | 180 ++ .../v4/pkg/multicast/multi_conn_lin.go | 233 ++ .../gortsplib/v4/pkg/multicast/multicast.go | 43 + .../gortsplib/v4/pkg/multicast/single_conn.go | 108 + .../v4/pkg/multicast/single_conn_lin.go | 172 ++ .../gortsplib/v4/pkg/ringbuffer/ringbuffer.go | 106 + .../v4/pkg/rtcpreceiver/rtcpreceiver.go | 314 +++ .../gortsplib/v4/pkg/rtcpsender/rtcpsender.go | 187 ++ .../v4/pkg/rtplossdetector/lossdetector.go | 38 + .../v4/pkg/rtpreorderer/reorderer.go | 142 ++ .../gortsplib/v4/pkg/rtptime/encoder.go | 61 + .../v4/pkg/rtptime/global_decoder.go | 81 + .../v4/pkg/rtptime/global_decoder2.go | 110 + .../gortsplib/v4/pkg/rtptime/rtptime.go | 2 + .../bluenviron/gortsplib/v4/pkg/sdp/sdp.go | 743 ++++++ .../gortsplib/v4/restrict_network.go | 17 + .../bluenviron/gortsplib/v4/server.go | 533 ++++ .../bluenviron/gortsplib/v4/server_conn.go | 528 ++++ .../gortsplib/v4/server_conn_reader.go | 138 + .../bluenviron/gortsplib/v4/server_handler.go | 245 ++ .../gortsplib/v4/server_multicast_writer.go | 90 + .../bluenviron/gortsplib/v4/server_session.go | 1593 ++++++++++++ .../gortsplib/v4/server_session_format.go | 163 ++ .../gortsplib/v4/server_session_media.go | 342 +++ .../bluenviron/gortsplib/v4/server_stream.go | 378 +++ .../gortsplib/v4/server_stream_format.go | 69 + .../gortsplib/v4/server_stream_media.go | 76 + .../gortsplib/v4/server_stream_stats.go | 36 + .../gortsplib/v4/server_tcp_listener.go | 42 + .../gortsplib/v4/server_udp_listener.go | 186 ++ .../bluenviron/gortsplib/v4/stats_conn.go | 9 + .../bluenviron/gortsplib/v4/stats_session.go | 76 + .../bluenviron/gortsplib/v4/transport.go | 25 + .../bluenviron/mediacommon/v2/LICENSE | 21 + .../mediacommon/v2/pkg/bits/read.go | 121 + .../mediacommon/v2/pkg/bits/write.go | 26 + .../mediacommon/v2/pkg/codecs/ac3/ac3.go | 7 + .../mediacommon/v2/pkg/codecs/ac3/bsi.go | 74 + .../v2/pkg/codecs/ac3/sync_info.go | 94 + .../mediacommon/v2/pkg/codecs/av1/av1.go | 10 + .../v2/pkg/codecs/av1/bitstream.go | 86 + .../v2/pkg/codecs/av1/is_random_access.go | 35 + .../mediacommon/v2/pkg/codecs/av1/leb128.go | 73 + .../v2/pkg/codecs/av1/obu_header.go | 35 + .../mediacommon/v2/pkg/codecs/av1/obu_type.go | 9 + .../v2/pkg/codecs/av1/sequence_header.go | 550 ++++ .../mediacommon/v2/pkg/codecs/h264/annexb.go | 147 ++ .../mediacommon/v2/pkg/codecs/h264/avcc.go | 100 + .../v2/pkg/codecs/h264/dts_extractor.go | 248 ++ .../pkg/codecs/h264/emulation_prevention.go | 36 + .../mediacommon/v2/pkg/codecs/h264/h264.go | 12 + .../v2/pkg/codecs/h264/is_random_access.go | 12 + .../v2/pkg/codecs/h264/nalu_type.go | 84 + .../mediacommon/v2/pkg/codecs/h264/sps.go | 809 ++++++ .../v2/pkg/codecs/h265/dts_extractor.go | 245 ++ .../mediacommon/v2/pkg/codecs/h265/h265.go | 11 + .../v2/pkg/codecs/h265/is_random_access.go | 13 + .../v2/pkg/codecs/h265/nalu_type.go | 100 + .../mediacommon/v2/pkg/codecs/h265/pps.go | 54 + .../mediacommon/v2/pkg/codecs/h265/sps.go | 979 +++++++ .../pkg/codecs/jpeg/define_huffman_table.go | 20 + .../codecs/jpeg/define_quantization_table.go | 61 + .../codecs/jpeg/define_restart_interval.go | 20 + .../mediacommon/v2/pkg/codecs/jpeg/jpeg.go | 14 + .../v2/pkg/codecs/jpeg/start_of_frame1.go | 83 + .../v2/pkg/codecs/jpeg/start_of_image.go | 10 + .../v2/pkg/codecs/jpeg/start_of_scan.go | 28 + .../v2/pkg/codecs/mpeg1audio/frame_header.go | 202 ++ .../v2/pkg/codecs/mpeg1audio/mpeg1_audio.go | 2 + .../v2/pkg/codecs/mpeg4audio/adts.go | 153 ++ .../mpeg4audio/audio_specific_config.go | 261 ++ .../v2/pkg/codecs/mpeg4audio/mpeg4_audio.go | 10 + .../v2/pkg/codecs/mpeg4audio/object_type.go | 12 + .../v2/pkg/codecs/mpeg4audio/sample_rates.go | 33 + .../codecs/mpeg4audio/stream_mux_config.go | 324 +++ .../pkg/codecs/mpeg4video/is_valid_config.go | 48 + .../v2/pkg/codecs/mpeg4video/mpeg4_video.go | 24 + .../mediacommon/v2/pkg/codecs/vp8/vp8.go | 7 + .../mediacommon/v2/pkg/codecs/vp9/header.go | 227 ++ .../mediacommon/v2/pkg/codecs/vp9/vp9.go | 7 + trunk/3rdparty/srs-bench/vendor/modules.txt | 45 + trunk/Dockerfile.test | 2 +- trunk/auto/auto_headers.sh | 6 + trunk/auto/options.sh | 5 + trunk/conf/console.conf | 8 + trunk/conf/full.conf | 27 + trunk/conf/regression-test-for-clion.conf | 8 + trunk/conf/regression-test.conf | 8 + trunk/conf/rtsp.conf | 52 + trunk/configure | 13 +- trunk/doc/CHANGELOG.md | 1 + trunk/ide/srs_clion/CMakeLists.txt | 4 +- trunk/src/app/srs_app_config.cpp | 111 +- trunk/src/app/srs_app_config.hpp | 10 + trunk/src/app/srs_app_heartbeat.cpp | 9 + trunk/src/app/srs_app_latest_version.cpp | 1 + trunk/src/app/srs_app_rtc_sdp.cpp | 8 + trunk/src/app/srs_app_rtc_sdp.hpp | 8 + trunk/src/app/srs_app_rtc_source.cpp | 277 +- trunk/src/app/srs_app_rtc_source.hpp | 8 +- trunk/src/app/srs_app_rtmp_conn.cpp | 47 +- trunk/src/app/srs_app_rtsp_conn.cpp | 934 +++++++ trunk/src/app/srs_app_rtsp_conn.hpp | 181 ++ trunk/src/app/srs_app_rtsp_source.cpp | 1079 ++++++++ trunk/src/app/srs_app_rtsp_source.hpp | 256 ++ trunk/src/app/srs_app_server.cpp | 44 + trunk/src/app/srs_app_server.hpp | 2 + trunk/src/app/srs_app_stream_bridge.cpp | 58 + trunk/src/app/srs_app_stream_bridge.hpp | 24 + trunk/src/app/srs_app_threads.cpp | 7 + trunk/src/core/srs_core_version7.hpp | 2 +- trunk/src/kernel/srs_kernel_codec.cpp | 8 +- trunk/src/kernel/srs_kernel_error.hpp | 18 +- trunk/src/kernel/srs_kernel_rtc_rtp.hpp | 10 + trunk/src/protocol/srs_protocol_rtp.cpp | 305 +++ trunk/src/protocol/srs_protocol_rtp.hpp | 43 + .../src/protocol/srs_protocol_rtsp_stack.cpp | 750 ++++++ .../src/protocol/srs_protocol_rtsp_stack.hpp | 384 +++ trunk/src/utest/srs_utest_config.cpp | 13 + trunk/src/utest/srs_utest_protocol.cpp | 635 +++++ trunk/src/utest/srs_utest_rtc2.cpp | 8 +- 238 files changed, 32809 insertions(+), 293 deletions(-) create mode 100644 trunk/3rdparty/srs-bench/blackbox/rtsp_test.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/.dockerignore create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/.gitignore create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/.golangci.yml create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/LICENSE create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/Makefile create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/README.md create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/async_processor.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client_format.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client_media.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client_reader.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client_stats.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client_udp_listener.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/constants.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/empty_timer.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/auth.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/nonce.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/sender.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/validate.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/verify.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/www_authenticate.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/body.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/header.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/interleaved_frame.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/path.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/request.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/response.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/url.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/utils.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/bytecounter/bytecounter.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/conn/conn.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/description/media.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/description/session.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/ac3.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/av1.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/format.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/g711.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/g722.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/g726.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/generic.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/h264.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/h265.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/lpcm.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mjpeg.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mpeg1_audio.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mpeg1_video.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mpeg4_audio.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mpeg4_video.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mpegts.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/opus.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpac3/decoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpac3/encoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpac3/rtpac3.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1/decoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1/encoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1/rtpav1.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph264/decoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph264/encoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph264/rtph264.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph265/decoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph265/encoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph265/rtph265.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm/decoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm/encoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm/rtplpcm.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/decoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/encoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/header_jpeg.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/header_quantization_table.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/header_restart_marker.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/rtpmjpeg.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1audio/decoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1audio/encoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1audio/rtpmpeg1audio.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1video/decoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1video/encoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1video/rtpmpeg1video.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/decoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/decoder_generic.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/decoder_latm.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/encoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/encoder_generic.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/encoder_latm.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/payload_length_info.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/rtpmpeg4audio.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4video/decoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4video/encoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4video/rtpmpeg4video.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio/decoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio/encoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio/rtpsimpleaudio.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8/decoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8/encoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8/rtpvp8.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9/decoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9/encoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9/rtpvp9.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/speex.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/vorbis.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/vp8.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/vp9.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/authenticate.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/authorization.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/keyval.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/range.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/rtpinfo.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/session.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/transport.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/liberrors/client.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/liberrors/liberrors.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/liberrors/server.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/multicast/multi_conn.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/multicast/multi_conn_lin.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/multicast/multicast.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/multicast/single_conn.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/multicast/single_conn_lin.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/ringbuffer/ringbuffer.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtcpreceiver/rtcpreceiver.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtcpsender/rtcpsender.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtplossdetector/lossdetector.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtpreorderer/reorderer.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtptime/encoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtptime/global_decoder.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtptime/global_decoder2.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtptime/rtptime.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/sdp/sdp.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/restrict_network.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_conn.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_conn_reader.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_handler.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_multicast_writer.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_session.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_session_format.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_session_media.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_stream.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_stream_format.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_stream_media.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_stream_stats.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_tcp_listener.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_udp_listener.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/stats_conn.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/stats_session.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/transport.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/LICENSE create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/bits/read.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/bits/write.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/ac3/ac3.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/ac3/bsi.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/ac3/sync_info.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/av1.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/bitstream.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/is_random_access.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/leb128.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/obu_header.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/obu_type.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/sequence_header.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/annexb.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/avcc.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/dts_extractor.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/emulation_prevention.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/h264.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/is_random_access.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/nalu_type.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/sps.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/dts_extractor.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/h265.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/is_random_access.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/nalu_type.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/pps.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/sps.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/define_huffman_table.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/define_quantization_table.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/define_restart_interval.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/jpeg.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/start_of_frame1.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/start_of_image.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/start_of_scan.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg1audio/frame_header.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg1audio/mpeg1_audio.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/adts.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/audio_specific_config.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/mpeg4_audio.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/object_type.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/sample_rates.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/stream_mux_config.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video/is_valid_config.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video/mpeg4_video.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/vp8/vp8.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/vp9/header.go create mode 100644 trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/vp9/vp9.go create mode 100644 trunk/conf/rtsp.conf create mode 100644 trunk/src/app/srs_app_rtsp_conn.cpp create mode 100644 trunk/src/app/srs_app_rtsp_conn.hpp create mode 100644 trunk/src/app/srs_app_rtsp_source.cpp create mode 100644 trunk/src/app/srs_app_rtsp_source.hpp create mode 100644 trunk/src/protocol/srs_protocol_rtp.cpp create mode 100644 trunk/src/protocol/srs_protocol_rtp.hpp create mode 100644 trunk/src/protocol/srs_protocol_rtsp_stack.cpp create mode 100644 trunk/src/protocol/srs_protocol_rtsp_stack.hpp diff --git a/trunk/3rdparty/srs-bench/blackbox/rtsp_test.go b/trunk/3rdparty/srs-bench/blackbox/rtsp_test.go new file mode 100644 index 000000000..62dcdbe1e --- /dev/null +++ b/trunk/3rdparty/srs-bench/blackbox/rtsp_test.go @@ -0,0 +1,119 @@ +// The MIT License (MIT) +// +// # Copyright (c) 2025 Winlin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +package blackbox + +import ( + "context" + "fmt" + "math/rand" + "os" + "path" + "sync" + "testing" + "time" + + "github.com/ossrs/go-oryx-lib/errors" + "github.com/ossrs/go-oryx-lib/logger" +) + +func TestFast_RtmpPublish_RtspPlay_Basic(t *testing.T) { + // This case is run in parallel. + t.Parallel() + + // Setup the max timeout for this case. + ctx, cancel := context.WithTimeout(logger.WithContext(context.Background()), time.Duration(*srsTimeout)*time.Millisecond) + defer cancel() + + // Check a set of errors. + var r0, r1, r2, r3, r4, r5 error + defer func(ctx context.Context) { + if err := filterTestError(ctx.Err(), r0, r1, r2, r3, r4, r5); err != nil { + t.Errorf("Fail for err %+v", err) + } else { + logger.Tf(ctx, "test done with err %+v", err) + } + }(ctx) + + var wg sync.WaitGroup + defer wg.Wait() + + // Start SRS server and wait for it to be ready. + svr := NewSRSServer(func(v *srsServer) { + v.envs = []string{ + "SRS_RTSP_SERVER_ENABLED=on", + "SRS_VHOST_RTSP_ENABLED=on", + "SRS_VHOST_RTSP_RTMP_TO_RTSP=on", + } + }) + wg.Add(1) + go func() { + defer wg.Done() + r0 = svr.Run(ctx, cancel) + }() + + // Start FFmpeg to publish stream. + streamID := fmt.Sprintf("stream-%v-%v", os.Getpid(), rand.Int()) + streamURL := fmt.Sprintf("rtmp://localhost:%v/live/%v", svr.RTMPPort(), streamID) + ffmpeg := NewFFmpeg(func(v *ffmpegClient) { + v.args = []string{ + "-stream_loop", "-1", "-re", "-i", *srsPublishAvatar, "-c", "copy", "-f", "flv", streamURL, + } + }) + wg.Add(1) + go func() { + defer wg.Done() + <-svr.ReadyCtx().Done() + r1 = ffmpeg.Run(ctx, cancel) + }() + + // Start FFprobe to detect and verify stream. + duration := time.Duration(*srsFFprobeDuration) * time.Millisecond + ffprobe := NewFFprobe(func(v *ffprobeClient) { + v.dvrFile = path.Join(svr.WorkDir(), "objs", fmt.Sprintf("srs-ffprobe-%v.mp4", streamID)) + v.streamURL = fmt.Sprintf("rtsp://localhost:%v/live/%v", svr.RTSPPort(), streamID) + v.duration, v.timeout = duration, time.Duration(*srsFFprobeTimeout)*time.Millisecond + }) + wg.Add(1) + go func() { + defer wg.Done() + <-svr.ReadyCtx().Done() + r2 = ffprobe.Run(ctx, cancel) + }() + + // Fast quit for probe done. + select { + case <-ctx.Done(): + case <-ffprobe.ProbeDoneCtx().Done(): + defer cancel() + + str, m := ffprobe.Result() + if len(m.Streams) != 2 { + r3 = errors.Errorf("invalid streams=%v, %v, %v", len(m.Streams), m.String(), str) + } + + if ts := 90; m.Format.ProbeScore < ts { + r4 = errors.Errorf("low score=%v < %v, %v, %v", m.Format.ProbeScore, ts, m.String(), str) + } + if dv := m.Duration(); dv < duration { + r5 = errors.Errorf("short duration=%v < %v, %v, %v", dv, duration, m.String(), str) + } + } +} diff --git a/trunk/3rdparty/srs-bench/blackbox/util.go b/trunk/3rdparty/srs-bench/blackbox/util.go index 3dd76ae7b..ff9d91424 100644 --- a/trunk/3rdparty/srs-bench/blackbox/util.go +++ b/trunk/3rdparty/srs-bench/blackbox/util.go @@ -422,6 +422,8 @@ type SRSServer interface { APIPort() int // SRTPort is the SRT UDP port. SRTPort() int + // RTSPPort is the RTSP port. + RTSPPort() int } // srsServer is a SRS server instance. @@ -450,6 +452,8 @@ type srsServer struct { httpListen int // SRT UDP server listen port. srtListen int + // RTSP server listen port. + rtspListen int // The envs from user. envs []string @@ -476,6 +480,7 @@ func NewSRSServer(opts ...func(v *srsServer)) SRSServer { v.apiListen = allocator.Allocate() v.httpListen = allocator.Allocate() v.srtListen = allocator.Allocate() + v.rtspListen = allocator.Allocate() // Do cleanup. v.process.onDispose = func(ctx context.Context, bs *backendService) error { @@ -483,7 +488,7 @@ func NewSRSServer(opts ...func(v *srsServer)) SRSServer { allocator.Free(v.apiListen) allocator.Free(v.httpListen) allocator.Free(v.srtListen) - + allocator.Free(v.rtspListen) if _, err := os.Stat(v.workDir); err == nil { os.RemoveAll(v.workDir) } @@ -520,6 +525,10 @@ func (v *srsServer) SRTPort() int { return v.srtListen } +func (v *srsServer) RTSPPort() int { + return v.rtspListen +} + func (v *srsServer) WorkDir() string { return v.workDir } @@ -575,6 +584,8 @@ func (v *srsServer) Run(ctx context.Context, cancel context.CancelFunc) error { fmt.Sprintf("SRS_HTTP_SERVER_LISTEN=%v", v.httpListen), // Setup the SRT server listen port. fmt.Sprintf("SRS_SRT_SERVER_LISTEN=%v", v.srtListen), + // Setup the RTSP server listen port. + fmt.Sprintf("SRS_RTSP_SERVER_LISTEN=%v", v.rtspListen), }...) // Rewrite envs by case. if v.envs != nil { diff --git a/trunk/3rdparty/srs-bench/go.mod b/trunk/3rdparty/srs-bench/go.mod index 176db4483..7ce4f16b7 100644 --- a/trunk/3rdparty/srs-bench/go.mod +++ b/trunk/3rdparty/srs-bench/go.mod @@ -3,6 +3,7 @@ module github.com/ossrs/srs-bench go 1.23.0 require ( + github.com/bluenviron/gortsplib/v4 v4.13.1 github.com/ghettovoice/gosip v0.0.0-20220929080231-de8ba881be83 github.com/google/gopacket v1.1.19 github.com/haivision/srtgo v0.0.0-20230627061225-a70d53fcd618 @@ -21,6 +22,7 @@ require ( ) require ( + github.com/bluenviron/mediacommon/v2 v2.1.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/ws v1.1.0-rc.1 // indirect diff --git a/trunk/3rdparty/srs-bench/go.sum b/trunk/3rdparty/srs-bench/go.sum index 0872ce64e..f680ef9c1 100644 --- a/trunk/3rdparty/srs-bench/go.sum +++ b/trunk/3rdparty/srs-bench/go.sum @@ -1,3 +1,7 @@ +github.com/bluenviron/gortsplib/v4 v4.13.1 h1:FpkfzLTWgeC4C3ytfNZ9ezes+MA4ZSp8rAvxJP5RnLY= +github.com/bluenviron/gortsplib/v4 v4.13.1/go.mod h1:nJVGKKG8KEkt7KKuckZIXQ1FHevSbvdV7y5UcpLmORw= +github.com/bluenviron/mediacommon/v2 v2.1.0 h1:NtlRCaAo7gnCcO+EHHeFJxSIt+v8uNbvJqlH1Gk2PZM= +github.com/bluenviron/mediacommon/v2 v2.1.0/go.mod h1:iHEz1SFIet6zBwAQoh1a92vTQ3dV3LpVFbom6/SLz3k= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/discoviking/fsm v0.0.0-20150126104936-f4a273feecca/go.mod h1:W+3LQaEkN8qAwwcw0KC546sUEnX86GIT8CcMLZC4mG0= diff --git a/trunk/3rdparty/srs-bench/srs/rtmp_test.go b/trunk/3rdparty/srs-bench/srs/rtmp_test.go index 9dd38da0a..cdf3cae43 100644 --- a/trunk/3rdparty/srs-bench/srs/rtmp_test.go +++ b/trunk/3rdparty/srs-bench/srs/rtmp_test.go @@ -30,11 +30,13 @@ import ( "testing" "time" + "github.com/bluenviron/gortsplib/v4/pkg/description" "github.com/ossrs/go-oryx-lib/avc" "github.com/ossrs/go-oryx-lib/flv" "github.com/ossrs/go-oryx-lib/logger" "github.com/ossrs/go-oryx-lib/rtmp" "github.com/pion/interceptor" + "github.com/pion/rtp" "github.com/pkg/errors" ) @@ -190,6 +192,82 @@ func TestRtmpPublish_RtcPlay_AVC(t *testing.T) { } } +func TestRtmpPublish_RtspPlay(t *testing.T) { + ctx := logger.WithContext(context.Background()) + ctx, cancel := context.WithTimeout(ctx, time.Duration(*srsTimeout)*time.Millisecond) + + var r0, r1 error + err := func() (err error) { + streamSuffix := fmt.Sprintf("rtmp-regression-%v-%v", os.Getpid(), rand.Int()) + rtmpUrl := fmt.Sprintf("rtmp://%v/live/%v", *srsServer, streamSuffix) + + // TODO: 8554 + rtspUrl := fmt.Sprintf("rtsp://%v:8554/live/%v", *srsServer, streamSuffix) + + // Publisher connect to a RTMP stream. + publisher := NewRTMPPublisher() + defer publisher.Close() + + if err := publisher.Publish(ctx, rtmpUrl); err != nil { + return err + } + player := NewRTSPPlayer() + defer player.Close() + + // Run publisher and player + var wg sync.WaitGroup + defer wg.Wait() + + publisherReady, publisherReadyCancel := context.WithCancel(context.Background()) + wg.Add(1) + go func() { + defer wg.Done() + time.Sleep(30 * time.Millisecond) + publisherReadyCancel() + }() + + wg.Add(1) + go func() { + defer wg.Done() + <-publisherReady.Done() + + if err := player.Play(ctx, rtspUrl); err != nil { + r1 = err + cancel() + return + } + + var nnPackets int + player.onRTPPacket = func(media *description.Media, format uint8, packet *rtp.Packet) error { + logger.Tf(ctx, "RTSP: recv rtp packet #%v, payload type=%v, size=%vB", + nnPackets, packet.PayloadType, len(packet.Payload)) + if nnPackets += 1; nnPackets > 50 { + cancel() + } + // TODO: Further validate the RTP packets. + return nil + } + }() + + wg.Add(1) + go func() { + defer wg.Done() + publisher.onSendPacket = func(m *rtmp.Message) error { + time.Sleep(1 * time.Millisecond) + return nil + } + if r0 = publisher.Ingest(ctx, *srsPublishAvatar); r0 != nil { + cancel() + } + }() + + return nil + }() + if err := filterTestError(ctx.Err(), err, r0, r1); err != nil { + t.Errorf("err %+v", err) + } +} + func TestRtmpPublish_MultipleSequences(t *testing.T) { ctx := logger.WithContext(context.Background()) ctx, cancel := context.WithTimeout(ctx, time.Duration(*srsTimeout)*time.Millisecond) @@ -235,7 +313,7 @@ func TestRtmpPublish_MultipleSequences(t *testing.T) { } // Ingore the duplicated sps/pps. - if isAvccrEquals(previousAvccr, avccr) { + if IsAvccrEquals(previousAvccr, avccr) { return nil } previousAvccr = avccr diff --git a/trunk/3rdparty/srs-bench/srs/util.go b/trunk/3rdparty/srs-bench/srs/util.go index eb60286a2..9ce080fca 100644 --- a/trunk/3rdparty/srs-bench/srs/util.go +++ b/trunk/3rdparty/srs-bench/srs/util.go @@ -44,6 +44,10 @@ import ( "sync" "time" + "github.com/bluenviron/gortsplib/v4" + "github.com/bluenviron/gortsplib/v4/pkg/base" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/ossrs/go-oryx-lib/amf0" "github.com/ossrs/go-oryx-lib/avc" "github.com/ossrs/go-oryx-lib/flv" @@ -2220,7 +2224,90 @@ func (v *FLVPlayer) consume(ctx context.Context) (err error) { } } -func isAvccrEquals(a, b *avc.AVCDecoderConfigurationRecord) bool { +// RTSPPlayer +type RTSPPlayer struct { + rtspUrl string + + client *gortsplib.Client + + ctx context.Context + cancel context.CancelFunc + + onRTPPacket func(media *description.Media, format uint8, packet *rtp.Packet) error +} + +func NewRTSPPlayer() *RTSPPlayer { + return &RTSPPlayer{} +} + +func (v *RTSPPlayer) Close() error { + if v.cancel != nil { + v.cancel() + } + if v.client != nil { + v.client.Close() + } + return nil +} + +func (v *RTSPPlayer) Play(ctx context.Context, rtspUrl string) error { + v.rtspUrl = rtspUrl + v.ctx, v.cancel = context.WithCancel(ctx) + + transport := gortsplib.Transport(gortsplib.TransportTCP) + v.client = &gortsplib.Client{ + Transport: &transport, + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, + } + + u, err := base.ParseURL(rtspUrl) + if err != nil { + return errors.Wrapf(err, "parse url %v", rtspUrl) + } + + err = v.client.Start(u.Scheme, u.Host) + if err != nil { + return errors.Wrapf(err, "connect rtsp %v", rtspUrl) + } + + desc, _, err := v.client.Describe(u) + if err != nil { + return errors.Wrapf(err, "describe rtsp %v", rtspUrl) + } + + err = v.client.SetupAll(desc.BaseURL, desc.Medias) + if err != nil { + return errors.Wrapf(err, "setup rtsp %v", rtspUrl) + } + + v.client.OnPacketRTPAny(func(media *description.Media, forma format.Format, packet *rtp.Packet) { + if v.onRTPPacket != nil { + if err := v.onRTPPacket(media, uint8(forma.ClockRate()), packet); err != nil { + logger.Wf(ctx, "rtsp: on rtp error %+v", err) + } + } + }) + + _, err = v.client.Play(nil) + if err != nil { + return errors.Wrapf(err, "play rtsp %v", rtspUrl) + } + + go func() { + err := v.client.Wait() + if err != nil { + logger.Wf(ctx, "rtsp: client error %+v", err) + } + if v.cancel != nil { + v.cancel() + } + }() + + return nil +} + +func IsAvccrEquals(a, b *avc.AVCDecoderConfigurationRecord) bool { if a == nil || b == nil { return false } diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/.dockerignore b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/.dockerignore new file mode 100644 index 000000000..6b8710a71 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/.dockerignore @@ -0,0 +1 @@ +.git diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/.gitignore b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/.gitignore new file mode 100644 index 000000000..1273da773 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/.gitignore @@ -0,0 +1 @@ +/coverage*.txt diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/.golangci.yml b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/.golangci.yml new file mode 100644 index 000000000..4bfbdcf65 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/.golangci.yml @@ -0,0 +1,73 @@ +linters: + enable: + - asciicheck + - bidichk + - bodyclose + #- contextcheck + - copyloopvar + - dupl + - errorlint + - gochecknoinits + - gocritic + - gofmt + - gofumpt + - lll + - misspell + - nilerr + - prealloc + - predeclared + - revive + - usestdlibvars + - unconvert + #- usetesting + - tparallel + - wastedassign + - whitespace + +issues: + exclude-use-default: false + +linters-settings: + errcheck: + exclude-functions: + - io.Copy + - (io.Closer).Close + - (io.Writer).Write + - (hash.Hash).Write + - (net.Conn).Close + - (net.Conn).SetReadDeadline + - (net.Conn).SetWriteDeadline + - (*net.TCPConn).SetKeepAlive + - (*net.TCPConn).SetKeepAlivePeriod + - (*net.TCPConn).SetNoDelay + - (net.Listener).Close + - (net.PacketConn).Close + - (net.PacketConn).SetReadDeadline + - (net.PacketConn).SetWriteDeadline + - (net/http.ResponseWriter).Write + - (*net/http.Server).Serve + - (*net/http.Server).ServeTLS + - (*net/http.Server).Shutdown + - os.Chdir + - os.Mkdir + - os.MkdirAll + - os.Remove + - os.RemoveAll + - os.Setenv + - os.Unsetenv + - (*os.File).WriteString + - (*os.File).Close + - (github.com/datarhei/gosrt.Conn).Close + - (github.com/datarhei/gosrt.Conn).SetReadDeadline + - (github.com/datarhei/gosrt.Conn).SetWriteDeadline + - (*github.com/bluenviron/gortsplib/v4.Client).Close + - (*github.com/bluenviron/gortsplib/v4.Server).Close + - (*github.com/bluenviron/gortsplib/v4.ServerSession).Close + - (*github.com/bluenviron/gortsplib/v4.ServerStream).Close + - (*github.com/bluenviron/gortsplib/v4.ServerConn).Close + + govet: + enable-all: true + disable: + - fieldalignment + - reflectvaluecompare diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/LICENSE b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/LICENSE new file mode 100644 index 000000000..023966dad --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 aler9 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/Makefile b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/Makefile new file mode 100644 index 000000000..112928df5 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/Makefile @@ -0,0 +1,26 @@ +BASE_IMAGE = golang:1.24-alpine3.20 +LINT_IMAGE = golangci/golangci-lint:v1.64.5 + +.PHONY: $(shell ls) + +help: + @echo "usage: make [action]" + @echo "" + @echo "available actions:" + @echo "" + @echo " mod-tidy run go mod tidy" + @echo " format format source files" + @echo " test run tests" + @echo " test-32 run tests on a 32-bit system" + @echo " test-e2e run end-to-end tests" + @echo " lint run linter" + @echo " bench run benchmarks" + @echo "" + +blank := +define NL + +$(blank) +endef + +include scripts/*.mk diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/README.md b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/README.md new file mode 100644 index 000000000..70d6730b5 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/README.md @@ -0,0 +1,177 @@ +# gortsplib + +[![Test](https://github.com/bluenviron/gortsplib/actions/workflows/test.yml/badge.svg)](https://github.com/bluenviron/gortsplib/actions/workflows/test.yml) +[![Lint](https://github.com/bluenviron/gortsplib/actions/workflows/lint.yml/badge.svg)](https://github.com/bluenviron/gortsplib/actions/workflows/lint.yml) +[![Go Report Card](https://goreportcard.com/badge/github.com/bluenviron/gortsplib)](https://goreportcard.com/report/github.com/bluenviron/gortsplib) +[![CodeCov](https://codecov.io/gh/bluenviron/gortsplib/branch/main/graph/badge.svg)](https://app.codecov.io/gh/bluenviron/gortsplib/tree/main) +[![PkgGoDev](https://pkg.go.dev/badge/github.com/bluenviron/gortsplib/v4)](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4#pkg-index) + +RTSP 1.0 client and server library for the Go programming language, written for [MediaMTX](https://github.com/bluenviron/mediamtx). + +Go ≥ 1.21 is required. + +Features: + +* Client + * Query servers about available media streams + * Read media streams from a server ("play") + * Read streams with the UDP, UDP-multicast or TCP transport protocol + * Read TLS-encrypted streams (TCP only) + * Switch transport protocol automatically + * Read selected media streams + * Pause or seek without disconnecting from the server + * Write to ONVIF back channels + * Get PTS (relative) timestamp of incoming packets + * Get NTP (absolute) timestamp of incoming packets + * Write media streams to a server ("record") + * Write streams with the UDP or TCP transport protocol + * Write TLS-encrypted streams (TCP only) + * Switch transport protocol automatically + * Pause without disconnecting from the server +* Server + * Handle requests from clients + * Validate client credentials + * Read media streams from clients ("record") + * Read streams with the UDP or TCP transport protocol + * Read TLS-encrypted streams (TCP only) + * Get PTS (relative) timestamp of incoming packets + * Get NTP (absolute) timestamp of incoming packets + * Serve media streams to clients ("play") + * Write streams with the UDP, UDP-multicast or TCP transport protocol + * Write TLS-encrypted streams (TCP only) + * Compute and provide SSRC, RTP-Info to clients +* Utilities + * Parse RTSP elements + * Encode/decode RTP packets into/from codec-specific frames + +## Table of contents + +* [Examples](#examples) +* [API Documentation](#api-documentation) +* [RTP Payload Formats](#rtp-payload-formats) +* [Specifications](#specifications) +* [Related projects](#related-projects) + +## Examples + +* [client-query](examples/client-query/main.go) +* [client-play](examples/client-play/main.go) +* [client-play-timestamp](examples/client-play-timestamp/main.go) +* [client-play-options](examples/client-play-options/main.go) +* [client-play-pause](examples/client-play-pause/main.go) +* [client-play-to-record](examples/client-play-to-record/main.go) +* [client-play-backchannel](examples/client-play-backchannel/main.go) +* [client-play-format-av1](examples/client-play-format-av1/main.go) +* [client-play-format-av1-to-jpeg](examples/client-play-format-av1-to-jpeg/main.go) +* [client-play-format-g711](examples/client-play-format-g711/main.go) +* [client-play-format-h264](examples/client-play-format-h264/main.go) +* [client-play-format-h264-to-jpeg](examples/client-play-format-h264-to-jpeg/main.go) +* [client-play-format-h264-to-disk](examples/client-play-format-h264-to-disk/main.go) +* [client-play-format-h264-mpeg4audio-to-disk](examples/client-play-format-h264-mpeg4audio-to-disk/main.go) +* [client-play-format-h265](examples/client-play-format-h265/main.go) +* [client-play-format-h265-to-jpeg](examples/client-play-format-h265-to-jpeg/main.go) +* [client-play-format-h265-to-disk](examples/client-play-format-h265-to-disk/main.go) +* [client-play-format-lpcm](examples/client-play-format-lpcm/main.go) +* [client-play-format-mjpeg](examples/client-play-format-mjpeg/main.go) +* [client-play-format-mpeg4audio](examples/client-play-format-mpeg4audio/main.go) +* [client-play-format-mpeg4audio-to-disk](examples/client-play-format-mpeg4audio-to-disk/main.go) +* [client-play-format-opus](examples/client-play-format-opus/main.go) +* [client-play-format-opus-to-disk](examples/client-play-format-opus-to-disk/main.go) +* [client-play-format-vp8](examples/client-play-format-vp8/main.go) +* [client-play-format-vp9](examples/client-play-format-vp9/main.go) +* [client-record-options](examples/client-record-options/main.go) +* [client-record-pause](examples/client-record-pause/main.go) +* [client-record-format-av1](examples/client-record-format-av1/main.go) +* [client-record-format-g711](examples/client-record-format-g711/main.go) +* [client-record-format-h264](examples/client-record-format-h264/main.go) +* [client-record-format-h264-from-disk](examples/client-record-format-h264-from-disk/main.go) +* [client-record-format-h265](examples/client-record-format-h265/main.go) +* [client-record-format-lpcm](examples/client-record-format-lpcm/main.go) +* [client-record-format-mjpeg](examples/client-record-format-mjpeg/main.go) +* [client-record-format-mpeg4audio](examples/client-record-format-mpeg4audio/main.go) +* [client-record-format-opus](examples/client-record-format-opus/main.go) +* [client-record-format-vp8](examples/client-record-format-vp8/main.go) +* [client-record-format-vp9](examples/client-record-format-vp9/main.go) +* [server](examples/server/main.go) +* [server-tls](examples/server-tls/main.go) +* [server-auth](examples/server-auth/main.go) +* [server-h264-to-disk](examples/server-h264-to-disk/main.go) +* [server-h264-from-disk](examples/server-h264-from-disk/main.go) +* [proxy](examples/proxy/main.go) + +## API Documentation + +[Click to open the API Documentation](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4#pkg-index) + +## RTP Payload Formats + +In RTSP, media streams are transmitted by using RTP packets, which are encoded in a specific, codec-dependent, format. This library supports formats for the following codecs: + +### Video + +|codec|documentation|encoder and decoder available| +|------|-------------|-----------------------------| +|AV1|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#AV1)|:heavy_check_mark:| +|VP9|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#VP9)|:heavy_check_mark:| +|VP8|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#VP8)|:heavy_check_mark:| +|H265|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#H265)|:heavy_check_mark:| +|H264|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#H264)|:heavy_check_mark:| +|MPEG-4 Video (H263, Xvid)|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#MPEG4Video)|:heavy_check_mark:| +|MPEG-1/2 Video|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#MPEG1Video)|:heavy_check_mark:| +|M-JPEG|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#MJPEG)|:heavy_check_mark:| + +### Audio + +|codec|documentation|encoder and decoder available| +|------|-------------|-----------------------------| +|Opus|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#Opus)|:heavy_check_mark:| +|Vorbis|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#Vorbis)|| +|MPEG-4 Audio (AAC)|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#MPEG4Audio)|:heavy_check_mark:| +|MPEG-1/2 Audio (MP3)|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#MPEG1Audio)|:heavy_check_mark:| +|AC-3|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#AC3)|:heavy_check_mark:| +|Speex|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#Speex)|| +|G726|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#G726)|| +|G722|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#G722)|:heavy_check_mark:| +|G711 (PCMA, PCMU)|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#G711)|:heavy_check_mark:| +|LPCM|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#LPCM)|:heavy_check_mark:| + +### Other + +|codec|documentation|encoder and decoder available| +|------|-------------|-----------------------------| +|MPEG-TS|[link](https://pkg.go.dev/github.com/bluenviron/gortsplib/v4/pkg/format#MPEGTS)|| + +## Specifications + +|name|area| +|----|----| +|[RFC2326, RTSP 1.0](https://datatracker.ietf.org/doc/html/rfc2326)|protocol| +|[RFC7826, RTSP 2.0](https://datatracker.ietf.org/doc/html/rfc7826)|protocol| +|[RFC8866, SDP: Session Description Protocol](https://datatracker.ietf.org/doc/html/rfc8866)|SDP| +|[RTP Payload Format For AV1 (v1.0)](https://aomediacodec.github.io/av1-rtp-spec/)|payload formats / AV1| +|[RTP Payload Format for VP9 Video](https://datatracker.ietf.org/doc/html/draft-ietf-payload-vp9-16)|payload formats / VP9| +|[RFC7741, RTP Payload Format for VP8 Video](https://datatracker.ietf.org/doc/html/rfc7741)|payload formats / VP8| +|[RFC7798, RTP Payload Format for High Efficiency Video Coding (HEVC)](https://datatracker.ietf.org/doc/html/rfc7798)|payload formats / H265| +|[RFC6184, RTP Payload Format for H.264 Video](https://datatracker.ietf.org/doc/html/rfc6184)|payload formats / H264| +|[RFC3640, RTP Payload Format for Transport of MPEG-4 Elementary Streams](https://datatracker.ietf.org/doc/html/rfc3640)|payload formats / MPEG-4 audio, MPEG-4 video| +|[RFC2250, RTP Payload Format for MPEG1/MPEG2 Video](https://datatracker.ietf.org/doc/html/rfc2250)|payload formats / MPEG-1 video, MPEG-2 audio, MPEG-TS| +|[RFC2435, RTP Payload Format for JPEG-compressed Video](https://datatracker.ietf.org/doc/html/rfc2435)|payload formats / M-JPEG| +|[RFC7587, RTP Payload Format for the Opus Speech and Audio Codec](https://datatracker.ietf.org/doc/html/rfc7587)|payload formats / Opus| +|[Multiopus in libwebrtc](https://webrtc-review.googlesource.com/c/src/+/129768)|payload formats / Opus| +|[RFC5215, RTP Payload Format for Vorbis Encoded Audio](https://datatracker.ietf.org/doc/html/rfc5215)|payload formats / Vorbis| +|[RFC4184, RTP Payload Format for AC-3 Audio](https://datatracker.ietf.org/doc/html/rfc4184)|payload formats / AC-3| +|[RFC6416, RTP Payload Format for MPEG-4 Audio/Visual Streams](https://datatracker.ietf.org/doc/html/rfc6416)|payload formats / MPEG-4 audio| +|[RFC5574, RTP Payload Format for the Speex Codec](https://datatracker.ietf.org/doc/html/rfc5574)|payload formats / Speex| +|[RFC3551, RTP Profile for Audio and Video Conferences with Minimal Control](https://datatracker.ietf.org/doc/html/rfc3551)|payload formats / G726, G722, G711, LPCM| +|[RFC3190, RTP Payload Format for 12-bit DAT Audio and 20- and 24-bit Linear Sampled Audio](https://datatracker.ietf.org/doc/html/rfc3190)|payload formats / LPCM| +|[Codec specifications](https://github.com/bluenviron/mediacommon#specifications)|codecs| +|[Golang project layout](https://github.com/golang-standards/project-layout)|project layout| + +## Related projects + +* [MediaMTX](https://github.com/bluenviron/mediamtx) +* [gohlslib](https://github.com/bluenviron/gohlslib) +* [mediacommon](https://github.com/bluenviron/mediacommon) +* [pion/sdp (SDP library used internally)](https://github.com/pion/sdp) +* [pion/rtp (RTP library used internally)](https://github.com/pion/rtp) +* [pion/rtcp (RTCP library used internally)](https://github.com/pion/rtcp) diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/async_processor.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/async_processor.go new file mode 100644 index 000000000..7a5e12d62 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/async_processor.go @@ -0,0 +1,58 @@ +package gortsplib + +import ( + "github.com/bluenviron/gortsplib/v4/pkg/ringbuffer" +) + +// this is an asynchronous queue processor +// that allows to detach the routine that is reading a stream +// from the routine that is writing a stream. +type asyncProcessor struct { + bufferSize int + + running bool + buffer *ringbuffer.RingBuffer + stopError error + + chStopped chan struct{} +} + +func (w *asyncProcessor) initialize() { + w.buffer, _ = ringbuffer.New(uint64(w.bufferSize)) +} + +func (w *asyncProcessor) close() { + if w.running { + w.buffer.Close() + <-w.chStopped + } +} + +func (w *asyncProcessor) start() { + w.running = true + w.chStopped = make(chan struct{}) + go w.run() +} + +func (w *asyncProcessor) run() { + w.stopError = w.runInner() + close(w.chStopped) +} + +func (w *asyncProcessor) runInner() error { + for { + tmp, ok := w.buffer.Pull() + if !ok { + return nil + } + + err := tmp.(func() error)() + if err != nil { + return err + } + } +} + +func (w *asyncProcessor) push(cb func() error) bool { + return w.buffer.Push(cb) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client.go new file mode 100644 index 000000000..7e2b1c4aa --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client.go @@ -0,0 +1,2256 @@ +/* +Package gortsplib is a RTSP 1.0 library for the Go programming language. + +Examples are available at https://github.com/bluenviron/gortsplib/tree/main/examples +*/ +package gortsplib + +import ( + "context" + "crypto/tls" + "fmt" + "log" + "net" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/pion/rtcp" + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/auth" + "github.com/bluenviron/gortsplib/v4/pkg/base" + "github.com/bluenviron/gortsplib/v4/pkg/bytecounter" + "github.com/bluenviron/gortsplib/v4/pkg/conn" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/headers" + "github.com/bluenviron/gortsplib/v4/pkg/liberrors" + "github.com/bluenviron/gortsplib/v4/pkg/rtcpreceiver" + "github.com/bluenviron/gortsplib/v4/pkg/rtcpsender" + "github.com/bluenviron/gortsplib/v4/pkg/rtptime" + "github.com/bluenviron/gortsplib/v4/pkg/sdp" +) + +const ( + clientUserAgent = "gortsplib" +) + +// avoid an int64 overflow and preserve resolution by splitting division into two parts: +// first add the integer part, then the decimal part. +func multiplyAndDivide(v, m, d time.Duration) time.Duration { + secs := v / d + dec := v % d + return (secs*m + dec*m/d) +} + +// convert an URL into an address, in particular: +// * add default port +// * handle IPv6 with or without square brackets. +// Adapted from net/http: +// https://cs.opensource.google/go/go/+/refs/tags/go1.20.5:src/net/http/transport.go;l=2747 +func canonicalAddr(u *base.URL) string { + addr := u.Hostname() + + port := u.Port() + if port == "" { + if u.Scheme == "rtsp" { + port = "554" + } else { // rtsps + port = "322" + } + } + + return net.JoinHostPort(addr, port) +} + +func isAnyPort(p int) bool { + return p == 0 || p == 1 +} + +func findBaseURL(sd *sdp.SessionDescription, res *base.Response, u *base.URL) (*base.URL, error) { + // use global control attribute + if control, ok := sd.Attribute("control"); ok && control != "*" { + ret, err := base.ParseURL(control) + if err != nil { + return nil, fmt.Errorf("invalid control attribute: '%v'", control) + } + + // add credentials + ret.User = u.User + + return ret, nil + } + + // use Content-Base + if cb, ok := res.Header["Content-Base"]; ok { + if len(cb) != 1 { + return nil, fmt.Errorf("invalid Content-Base: '%v'", cb) + } + + if strings.HasPrefix(cb[0], "/") { + // parse as a relative path + ret, err := base.ParseURL(u.Scheme + "://" + u.Host + cb[0]) + if err != nil { + return nil, fmt.Errorf("invalid Content-Base: '%v'", cb) + } + + // add credentials + ret.User = u.User + + return ret, nil + } + + ret, err := base.ParseURL(cb[0]) + if err != nil { + return nil, fmt.Errorf("invalid Content-Base: '%v'", cb) + } + + // add credentials + ret.User = u.User + + return ret, nil + } + + // use URL of request + return u, nil +} + +func prepareForAnnounce(desc *description.Session) { + for i, media := range desc.Medias { + media.Control = "trackID=" + strconv.FormatInt(int64(i), 10) + } +} + +func supportsGetParameter(header base.Header) bool { + pub, ok := header["Public"] + if !ok || len(pub) != 1 { + return false + } + + for _, m := range strings.Split(pub[0], ",") { + if base.Method(strings.Trim(m, " ")) == base.GetParameter { + return true + } + } + return false +} + +type clientState int + +const ( + clientStateInitial clientState = iota + clientStatePrePlay + clientStatePlay + clientStatePreRecord + clientStateRecord +) + +func (s clientState) String() string { + switch s { + case clientStateInitial: + return "initial" + case clientStatePrePlay: + return "prePlay" + case clientStatePlay: + return "play" + case clientStatePreRecord: + return "preRecord" + case clientStateRecord: + return "record" + } + return "unknown" +} + +type optionsReq struct { + url *base.URL + res chan clientRes +} + +type describeReq struct { + url *base.URL + res chan clientRes +} + +type announceReq struct { + url *base.URL + desc *description.Session + res chan clientRes +} + +type setupReq struct { + baseURL *base.URL + media *description.Media + rtpPort int + rtcpPort int + res chan clientRes +} + +type playReq struct { + ra *headers.Range + res chan clientRes +} + +type recordReq struct { + res chan clientRes +} + +type pauseReq struct { + res chan clientRes +} + +type clientRes struct { + sd *description.Session // describe only + res *base.Response + err error +} + +// ClientOnRequestFunc is the prototype of Client.OnRequest. +type ClientOnRequestFunc func(*base.Request) + +// ClientOnResponseFunc is the prototype of Client.OnResponse. +type ClientOnResponseFunc func(*base.Response) + +// ClientOnTransportSwitchFunc is the prototype of Client.OnTransportSwitch. +type ClientOnTransportSwitchFunc func(err error) + +// ClientOnPacketLostFunc is the prototype of Client.OnPacketLost. +// +// Deprecated: replaced by ClientOnPacketsLostFunc +type ClientOnPacketLostFunc func(err error) + +// ClientOnPacketsLostFunc is the prototype of Client.OnPacketsLost. +type ClientOnPacketsLostFunc func(lost uint64) + +// ClientOnDecodeErrorFunc is the prototype of Client.OnDecodeError. +type ClientOnDecodeErrorFunc func(err error) + +// OnPacketRTPFunc is the prototype of the callback passed to OnPacketRTP(). +type OnPacketRTPFunc func(*rtp.Packet) + +// OnPacketRTPAnyFunc is the prototype of the callback passed to OnPacketRTP(Any). +type OnPacketRTPAnyFunc func(*description.Media, format.Format, *rtp.Packet) + +// OnPacketRTCPFunc is the prototype of the callback passed to OnPacketRTCP(). +type OnPacketRTCPFunc func(rtcp.Packet) + +// OnPacketRTCPAnyFunc is the prototype of the callback passed to OnPacketRTCPAny(). +type OnPacketRTCPAnyFunc func(*description.Media, rtcp.Packet) + +// Client is a RTSP client. +type Client struct { + // + // RTSP parameters (all optional) + // + // timeout of read operations. + // It defaults to 10 seconds. + ReadTimeout time.Duration + // timeout of write operations. + // It defaults to 10 seconds. + WriteTimeout time.Duration + // a TLS configuration to connect to TLS (RTSPS) servers. + // It defaults to nil. + TLSConfig *tls.Config + // enable communication with servers which don't provide UDP server ports + // or use different server ports than the announced ones. + // This can be a security issue. + // It defaults to false. + AnyPortEnable bool + // transport protocol (UDP, Multicast or TCP). + // If nil, it is chosen automatically (first UDP, then, if it fails, TCP). + // It defaults to nil. + Transport *Transport + // If the client is reading with UDP, it must receive + // at least a packet within this timeout, otherwise it switches to TCP. + // It defaults to 3 seconds. + InitialUDPReadTimeout time.Duration + // Size of the queue of outgoing packets. + // It defaults to 256. + WriteQueueSize int + // maximum size of outgoing RTP / RTCP packets. + // This must be less than the UDP MTU (1472 bytes). + // It defaults to 1472. + MaxPacketSize int + // user agent header. + // It defaults to "gortsplib" + UserAgent string + // disable automatic RTCP sender reports. + DisableRTCPSenderReports bool + // explicitly request back channels to the server. + RequestBackChannels bool + // pointer to a variable that stores received bytes. + // + // Deprecated: use Client.Stats() + BytesReceived *uint64 + // pointer to a variable that stores sent bytes. + // + // Deprecated: use Client.Stats() + BytesSent *uint64 + + // + // system functions (all optional) + // + // function used to initialize the TCP client. + // It defaults to (&net.Dialer{}).DialContext. + DialContext func(ctx context.Context, network, address string) (net.Conn, error) + // function used to initialize UDP listeners. + // It defaults to net.ListenPacket. + ListenPacket func(network, address string) (net.PacketConn, error) + + // + // callbacks (all optional) + // + // called when sending a request to the server. + OnRequest ClientOnRequestFunc + // called when receiving a response from the server. + OnResponse ClientOnResponseFunc + // called when receiving a request from the server. + OnServerRequest ClientOnRequestFunc + // called when sending a response to the server. + OnServerResponse ClientOnResponseFunc + // called when the transport protocol changes. + OnTransportSwitch ClientOnTransportSwitchFunc + // called when the client detects lost packets. + // + // Deprecated: replaced by OnPacketsLost + OnPacketLost ClientOnPacketLostFunc + // called when the client detects lost packets. + OnPacketsLost ClientOnPacketsLostFunc + // called when a non-fatal decode error occurs. + OnDecodeError ClientOnDecodeErrorFunc + + // + // private + // + + timeNow func() time.Time + senderReportPeriod time.Duration + receiverReportPeriod time.Duration + checkTimeoutPeriod time.Duration + + connURL *base.URL + ctx context.Context + ctxCancel func() + state clientState + nconn net.Conn + conn *conn.Conn + session string + sender *auth.Sender + cseq int + optionsSent bool + useGetParameter bool + lastDescribeURL *base.URL + baseURL *base.URL + effectiveTransport *Transport + backChannelSetupped bool + stdChannelSetupped bool + setuppedMedias map[*description.Media]*clientMedia + tcpCallbackByChannel map[int]readFunc + lastRange *headers.Range + checkTimeoutTimer *time.Timer + checkTimeoutInitial bool + tcpLastFrameTime *int64 + keepalivePeriod time.Duration + keepaliveTimer *time.Timer + closeError error + writer *asyncProcessor + writerMutex sync.RWMutex + reader *clientReader + timeDecoder *rtptime.GlobalDecoder2 + mustClose bool + tcpFrame *base.InterleavedFrame + tcpBuffer []byte + bytesReceived *uint64 + bytesSent *uint64 + + // in + chOptions chan optionsReq + chDescribe chan describeReq + chAnnounce chan announceReq + chSetup chan setupReq + chPlay chan playReq + chRecord chan recordReq + chPause chan pauseReq + + // out + done chan struct{} +} + +// Start initializes the connection to a server. +func (c *Client) Start(scheme string, host string) error { + // RTSP parameters + if c.ReadTimeout == 0 { + c.ReadTimeout = 10 * time.Second + } + if c.WriteTimeout == 0 { + c.WriteTimeout = 10 * time.Second + } + if c.InitialUDPReadTimeout == 0 { + c.InitialUDPReadTimeout = 3 * time.Second + } + if c.WriteQueueSize == 0 { + c.WriteQueueSize = 256 + } else if (c.WriteQueueSize & (c.WriteQueueSize - 1)) != 0 { + return fmt.Errorf("WriteQueueSize must be a power of two") + } + if c.MaxPacketSize == 0 { + c.MaxPacketSize = udpMaxPayloadSize + } else if c.MaxPacketSize > udpMaxPayloadSize { + return fmt.Errorf("MaxPacketSize must be less than %d", udpMaxPayloadSize) + } + if c.UserAgent == "" { + c.UserAgent = clientUserAgent + } + + // system functions + if c.DialContext == nil { + c.DialContext = (&net.Dialer{}).DialContext + } + if c.ListenPacket == nil { + c.ListenPacket = net.ListenPacket + } + + // callbacks + if c.OnRequest == nil { + c.OnRequest = func(*base.Request) { + } + } + if c.OnResponse == nil { + c.OnResponse = func(*base.Response) { + } + } + if c.OnServerRequest == nil { + c.OnServerRequest = func(*base.Request) { + } + } + if c.OnServerResponse == nil { + c.OnServerResponse = func(*base.Response) { + } + } + if c.OnTransportSwitch == nil { + c.OnTransportSwitch = func(err error) { + log.Println(err.Error()) + } + } + if c.OnPacketLost != nil { + c.OnPacketsLost = func(lost uint64) { + c.OnPacketLost(liberrors.ErrClientRTPPacketsLost{Lost: uint(lost)}) //nolint:staticcheck + } + } + if c.OnPacketsLost == nil { + c.OnPacketsLost = func(lost uint64) { + log.Printf("%d RTP %s lost", + lost, + func() string { + if lost == 1 { + return "packet" + } + return "packets" + }()) + } + } + if c.OnDecodeError == nil { + c.OnDecodeError = func(err error) { + log.Println(err.Error()) + } + } + + // private + if c.timeNow == nil { + c.timeNow = time.Now + } + if c.senderReportPeriod == 0 { + c.senderReportPeriod = 10 * time.Second + } + if c.receiverReportPeriod == 0 { + // some cameras require a maximum of 5secs between keepalives + c.receiverReportPeriod = 5 * time.Second + } + if c.checkTimeoutPeriod == 0 { + c.checkTimeoutPeriod = 1 * time.Second + } + + ctx, ctxCancel := context.WithCancel(context.Background()) + + c.connURL = &base.URL{ + Scheme: scheme, + Host: host, + } + c.ctx = ctx + c.ctxCancel = ctxCancel + c.checkTimeoutTimer = emptyTimer() + c.keepalivePeriod = 30 * time.Second + c.keepaliveTimer = emptyTimer() + + if c.BytesReceived != nil { + c.bytesReceived = c.BytesReceived + } else { + c.bytesReceived = new(uint64) + } + if c.BytesSent != nil { + c.bytesSent = c.BytesSent + } else { + c.bytesSent = new(uint64) + } + + c.chOptions = make(chan optionsReq) + c.chDescribe = make(chan describeReq) + c.chAnnounce = make(chan announceReq) + c.chSetup = make(chan setupReq) + c.chPlay = make(chan playReq) + c.chRecord = make(chan recordReq) + c.chPause = make(chan pauseReq) + c.done = make(chan struct{}) + + go c.run() + + return nil +} + +// StartRecording connects to the address and starts publishing given media. +func (c *Client) StartRecording(address string, desc *description.Session) error { + u, err := base.ParseURL(address) + if err != nil { + return err + } + + err = c.Start(u.Scheme, u.Host) + if err != nil { + return err + } + + _, err = c.Announce(u, desc) + if err != nil { + c.Close() + return err + } + + err = c.SetupAll(u, desc.Medias) + if err != nil { + c.Close() + return err + } + + _, err = c.Record() + if err != nil { + c.Close() + return err + } + + return nil +} + +// Close closes all client resources and waits for them to close. +func (c *Client) Close() { + c.ctxCancel() + <-c.done +} + +// Wait waits until all client resources are closed. +// This can happen when a fatal error occurs or when Close() is called. +func (c *Client) Wait() error { + <-c.done + return c.closeError +} + +func (c *Client) run() { + defer close(c.done) + + c.closeError = c.runInner() + + c.ctxCancel() + + c.doClose() +} + +func (c *Client) runInner() error { + for { + chReaderResponse := func() chan *base.Response { + if c.reader != nil { + return c.reader.chResponse + } + return nil + }() + + chReaderRequest := func() chan *base.Request { + if c.reader != nil { + return c.reader.chRequest + } + return nil + }() + + chReaderError := func() chan error { + if c.reader != nil { + return c.reader.chError + } + return nil + }() + + chWriterError := func() chan struct{} { + if c.writer != nil { + return c.writer.chStopped + } + return nil + }() + + select { + case req := <-c.chOptions: + res, err := c.doOptions(req.url) + req.res <- clientRes{res: res, err: err} + + if c.mustClose { + return err + } + + case req := <-c.chDescribe: + sd, res, err := c.doDescribe(req.url) + req.res <- clientRes{sd: sd, res: res, err: err} + + if c.mustClose { + return err + } + + case req := <-c.chAnnounce: + res, err := c.doAnnounce(req.url, req.desc) + req.res <- clientRes{res: res, err: err} + + if c.mustClose { + return err + } + + case req := <-c.chSetup: + res, err := c.doSetup(req.baseURL, req.media, req.rtpPort, req.rtcpPort) + req.res <- clientRes{res: res, err: err} + + if c.mustClose { + return err + } + + case req := <-c.chPlay: + res, err := c.doPlay(req.ra) + req.res <- clientRes{res: res, err: err} + + if c.mustClose { + return err + } + + case req := <-c.chRecord: + res, err := c.doRecord() + req.res <- clientRes{res: res, err: err} + + if c.mustClose { + return err + } + + case req := <-c.chPause: + res, err := c.doPause() + req.res <- clientRes{res: res, err: err} + + if c.mustClose { + return err + } + + case <-c.checkTimeoutTimer.C: + err := c.doCheckTimeout() + if err != nil { + return err + } + c.checkTimeoutTimer = time.NewTimer(c.checkTimeoutPeriod) + + case <-c.keepaliveTimer.C: + err := c.doKeepAlive() + if err != nil { + return err + } + c.keepaliveTimer = time.NewTimer(c.keepalivePeriod) + + case <-chWriterError: + return c.writer.stopError + + case err := <-chReaderError: + c.reader = nil + return err + + case res := <-chReaderResponse: + c.OnResponse(res) + // these are responses to keepalives, ignore them. + + case req := <-chReaderRequest: + err := c.handleServerRequest(req) + if err != nil { + return err + } + + case <-c.ctx.Done(): + return liberrors.ErrClientTerminated{} + } + } +} + +func (c *Client) waitResponse(requestCseqStr string) (*base.Response, error) { + t := time.NewTimer(c.ReadTimeout) + defer t.Stop() + + for { + select { + case <-t.C: + return nil, liberrors.ErrClientRequestTimedOut{} + + case err := <-c.reader.chError: + c.reader = nil + return nil, err + + case res := <-c.reader.chResponse: + c.OnResponse(res) + + // accept response if CSeq equals request CSeq, or if CSeq is not present + if cseq, ok := res.Header["CSeq"]; !ok || len(cseq) != 1 || strings.TrimSpace(cseq[0]) == requestCseqStr { + return res, nil + } + + case req := <-c.reader.chRequest: + err := c.handleServerRequest(req) + if err != nil { + return nil, err + } + + case <-c.ctx.Done(): + return nil, liberrors.ErrClientTerminated{} + } + } +} + +func (c *Client) handleServerRequest(req *base.Request) error { + c.OnServerRequest(req) + + if req.Method != base.Options { + return liberrors.ErrClientUnhandledMethod{Method: req.Method} + } + + h := base.Header{ + "User-Agent": base.HeaderValue{c.UserAgent}, + } + + if cseq, ok := req.Header["CSeq"]; ok { + h["CSeq"] = cseq + } + + res := &base.Response{ + StatusCode: base.StatusOK, + Header: h, + } + + c.OnServerResponse(res) + + c.nconn.SetWriteDeadline(time.Now().Add(c.WriteTimeout)) + return c.conn.WriteResponse(res) +} + +func (c *Client) doClose() { + if c.state == clientStatePlay || c.state == clientStateRecord { + c.destroyWriter() + c.stopTransportRoutines() + } + + if c.nconn != nil && c.baseURL != nil { + header := base.Header{} + + if c.backChannelSetupped { + header["Require"] = base.HeaderValue{"www.onvif.org/ver20/backchannel"} + } + + c.do(&base.Request{ //nolint:errcheck + Method: base.Teardown, + URL: c.baseURL, + Header: header, + }, true) + } + + if c.reader != nil { + c.nconn.Close() + c.reader.wait() + c.reader = nil + c.nconn = nil + c.conn = nil + } else if c.nconn != nil { + c.nconn.Close() + c.nconn = nil + c.conn = nil + } + + for _, cm := range c.setuppedMedias { + cm.close() + } +} + +func (c *Client) reset() { + c.doClose() + + c.state = clientStateInitial + c.session = "" + c.sender = nil + c.cseq = 0 + c.optionsSent = false + c.useGetParameter = false + c.baseURL = nil + c.effectiveTransport = nil + c.backChannelSetupped = false + c.stdChannelSetupped = false + c.setuppedMedias = nil + c.tcpCallbackByChannel = nil +} + +func (c *Client) checkState(allowed map[clientState]struct{}) error { + if _, ok := allowed[c.state]; ok { + return nil + } + + allowedList := make([]fmt.Stringer, len(allowed)) + i := 0 + for a := range allowed { + allowedList[i] = a + i++ + } + + return liberrors.ErrClientInvalidState{AllowedList: allowedList, State: c.state} +} + +func (c *Client) trySwitchingProtocol() error { + c.OnTransportSwitch(liberrors.ErrClientSwitchToTCP{}) + + prevConnURL := c.connURL + prevBaseURL := c.baseURL + prevMedias := c.setuppedMedias + + c.reset() + + v := TransportTCP + c.effectiveTransport = &v + c.connURL = prevConnURL + + // some Hikvision cameras require a describe before a setup + _, _, err := c.doDescribe(c.lastDescribeURL) + if err != nil { + return err + } + + for i, cm := range prevMedias { + _, err = c.doSetup(prevBaseURL, cm.media, 0, 0) + if err != nil { + return err + } + + c.setuppedMedias[i].onPacketRTCP = cm.onPacketRTCP + for j, tr := range cm.formats { + c.setuppedMedias[i].formats[j].onPacketRTP = tr.onPacketRTP + } + } + + _, err = c.doPlay(c.lastRange) + if err != nil { + return err + } + + return nil +} + +func (c *Client) trySwitchingProtocol2(medi *description.Media, baseURL *base.URL) (*base.Response, error) { + c.OnTransportSwitch(liberrors.ErrClientSwitchToTCP2{}) + + prevConnURL := c.connURL + + c.reset() + + v := TransportTCP + c.effectiveTransport = &v + c.connURL = prevConnURL + + // some Hikvision cameras require a describe before a setup + _, _, err := c.doDescribe(c.lastDescribeURL) + if err != nil { + return nil, err + } + + return c.doSetup(baseURL, medi, 0, 0) +} + +func (c *Client) startTransportRoutines() { + c.timeDecoder = &rtptime.GlobalDecoder2{} + c.timeDecoder.Initialize() + + for _, cm := range c.setuppedMedias { + cm.start() + } + + if *c.effectiveTransport == TransportTCP { + c.tcpFrame = &base.InterleavedFrame{} + c.tcpBuffer = make([]byte, c.MaxPacketSize+4) + } + + if c.state == clientStatePlay && c.stdChannelSetupped { + c.keepaliveTimer = time.NewTimer(c.keepalivePeriod) + + switch *c.effectiveTransport { + case TransportUDP: + c.checkTimeoutTimer = time.NewTimer(c.InitialUDPReadTimeout) + c.checkTimeoutInitial = true + + case TransportUDPMulticast: + c.checkTimeoutTimer = time.NewTimer(c.checkTimeoutPeriod) + + default: // TCP + c.checkTimeoutTimer = time.NewTimer(c.checkTimeoutPeriod) + v := c.timeNow().Unix() + c.tcpLastFrameTime = &v + } + } + + if *c.effectiveTransport == TransportTCP { + c.reader.setAllowInterleavedFrames(true) + } +} + +func (c *Client) stopTransportRoutines() { + if c.reader != nil { + c.reader.setAllowInterleavedFrames(false) + } + + c.checkTimeoutTimer = emptyTimer() + c.keepaliveTimer = emptyTimer() + + for _, cm := range c.setuppedMedias { + cm.stop() + } + + c.timeDecoder = nil +} + +func (c *Client) createWriter() { + c.writerMutex.Lock() + + c.writer = &asyncProcessor{ + bufferSize: func() int { + if c.state == clientStateRecord || c.backChannelSetupped { + return c.WriteQueueSize + } + + // when reading, buffer is only used to send RTCP receiver reports, + // that are much smaller than RTP packets and are sent at a fixed interval. + // decrease RAM consumption by allocating less buffers. + return 8 + }(), + } + + c.writer.initialize() + + c.writerMutex.Unlock() +} + +func (c *Client) startWriter() { + c.writer.start() +} + +func (c *Client) destroyWriter() { + c.writer.close() + + c.writerMutex.Lock() + c.writer = nil + c.writerMutex.Unlock() +} + +func (c *Client) connOpen() error { + if c.nconn != nil { + return nil + } + + if c.connURL.Scheme != "rtsp" && c.connURL.Scheme != "rtsps" { + return liberrors.ErrClientUnsupportedScheme{Scheme: c.connURL.Scheme} + } + + if c.connURL.Scheme == "rtsps" && c.Transport != nil && *c.Transport != TransportTCP { + return liberrors.ErrClientRTSPSTCP{} + } + + dialCtx, dialCtxCancel := context.WithTimeout(c.ctx, c.ReadTimeout) + defer dialCtxCancel() + + nconn, err := c.DialContext(dialCtx, "tcp", canonicalAddr(c.connURL)) + if err != nil { + return err + } + + if c.connURL.Scheme == "rtsps" { + tlsConfig := c.TLSConfig + if tlsConfig == nil { + tlsConfig = &tls.Config{} + } + tlsConfig.ServerName = c.connURL.Hostname() + + nconn = tls.Client(nconn, tlsConfig) + } + + c.nconn = nconn + bc := bytecounter.New(c.nconn, c.bytesReceived, c.bytesSent) + c.conn = conn.NewConn(bc) + c.reader = &clientReader{ + c: c, + } + c.reader.start() + + return nil +} + +func (c *Client) do(req *base.Request, skipResponse bool) (*base.Response, error) { + if !c.optionsSent && req.Method != base.Options { + _, err := c.doOptions(req.URL) + if err != nil { + return nil, err + } + } + + if req.Header == nil { + req.Header = make(base.Header) + } + + if c.session != "" { + req.Header["Session"] = base.HeaderValue{c.session} + } + + c.cseq++ + cseqStr := strconv.FormatInt(int64(c.cseq), 10) + req.Header["CSeq"] = base.HeaderValue{cseqStr} + + req.Header["User-Agent"] = base.HeaderValue{c.UserAgent} + + if c.sender != nil { + c.sender.AddAuthorization(req) + } + + c.OnRequest(req) + + c.nconn.SetWriteDeadline(time.Now().Add(c.WriteTimeout)) + err := c.conn.WriteRequest(req) + if err != nil { + return nil, err + } + + if skipResponse { + return nil, nil + } + + res, err := c.waitResponse(cseqStr) + if err != nil { + c.mustClose = true + return nil, err + } + + // get session from response + if v, ok := res.Header["Session"]; ok { + var sx headers.Session + err = sx.Unmarshal(v) + if err != nil { + return nil, liberrors.ErrClientSessionHeaderInvalid{Err: err} + } + c.session = sx.Session + + if sx.Timeout != nil && *sx.Timeout > 0 { + c.keepalivePeriod = time.Duration(*sx.Timeout) * time.Second * 8 / 10 + } + } + + // send request again with authentication + if res.StatusCode == base.StatusUnauthorized && req.URL.User != nil && c.sender == nil { + pass, _ := req.URL.User.Password() + user := req.URL.User.Username() + + sender := &auth.Sender{ + WWWAuth: res.Header["WWW-Authenticate"], + User: user, + Pass: pass, + } + err = sender.Initialize() + if err != nil { + return nil, liberrors.ErrClientAuthSetup{Err: err} + } + c.sender = sender + + return c.do(req, skipResponse) + } + + return res, nil +} + +func (c *Client) atLeastOneUDPPacketHasBeenReceived() bool { + for _, ct := range c.setuppedMedias { + lft := atomic.LoadInt64(ct.udpRTPListener.lastPacketTime) + if lft != 0 { + return true + } + + lft = atomic.LoadInt64(ct.udpRTCPListener.lastPacketTime) + if lft != 0 { + return true + } + } + return false +} + +func (c *Client) isInUDPTimeout() bool { + now := c.timeNow() + for _, ct := range c.setuppedMedias { + lft := time.Unix(atomic.LoadInt64(ct.udpRTPListener.lastPacketTime), 0) + if now.Sub(lft) < c.ReadTimeout { + return false + } + + lft = time.Unix(atomic.LoadInt64(ct.udpRTCPListener.lastPacketTime), 0) + if now.Sub(lft) < c.ReadTimeout { + return false + } + } + return true +} + +func (c *Client) isInTCPTimeout() bool { + now := c.timeNow() + lft := time.Unix(atomic.LoadInt64(c.tcpLastFrameTime), 0) + return now.Sub(lft) >= c.ReadTimeout +} + +func (c *Client) doCheckTimeout() error { + if *c.effectiveTransport == TransportUDP || + *c.effectiveTransport == TransportUDPMulticast { + if c.checkTimeoutInitial && !c.backChannelSetupped && c.Transport == nil { + c.checkTimeoutInitial = false + + if !c.atLeastOneUDPPacketHasBeenReceived() { + err := c.trySwitchingProtocol() + if err != nil { + return err + } + } + } else if c.isInUDPTimeout() { + return liberrors.ErrClientUDPTimeout{} + } + } else if c.isInTCPTimeout() { + return liberrors.ErrClientTCPTimeout{} + } + + return nil +} + +func (c *Client) doKeepAlive() error { + // some cameras do not reply to keepalives, do not wait for responses. + _, err := c.do(&base.Request{ + Method: func() base.Method { + // the VLC integrated rtsp server requires GET_PARAMETER + if c.useGetParameter { + return base.GetParameter + } + return base.Options + }(), + // use the stream base URL, otherwise some cameras do not reply + URL: c.baseURL, + }, true) + return err +} + +func (c *Client) doOptions(u *base.URL) (*base.Response, error) { + err := c.checkState(map[clientState]struct{}{ + clientStateInitial: {}, + clientStatePrePlay: {}, + clientStatePreRecord: {}, + }) + if err != nil { + return nil, err + } + + err = c.connOpen() + if err != nil { + return nil, err + } + + res, err := c.do(&base.Request{ + Method: base.Options, + URL: u, + }, false) + if err != nil { + return nil, err + } + + if res.StatusCode != base.StatusOK { + // since this method is not implemented by every RTSP server, + // return an error only if status code is not 404 + if res.StatusCode == base.StatusNotFound { + return res, nil + } + return nil, liberrors.ErrClientBadStatusCode{Code: res.StatusCode, Message: res.StatusMessage} + } + + c.optionsSent = true + c.useGetParameter = supportsGetParameter(res.Header) + + return res, nil +} + +// Options sends an OPTIONS request. +func (c *Client) Options(u *base.URL) (*base.Response, error) { + cres := make(chan clientRes) + select { + case c.chOptions <- optionsReq{url: u, res: cres}: + res := <-cres + return res.res, res.err + + case <-c.done: + return nil, c.closeError + } +} + +func (c *Client) doDescribe(u *base.URL) (*description.Session, *base.Response, error) { + err := c.checkState(map[clientState]struct{}{ + clientStateInitial: {}, + clientStatePrePlay: {}, + clientStatePreRecord: {}, + }) + if err != nil { + return nil, nil, err + } + + err = c.connOpen() + if err != nil { + return nil, nil, err + } + + header := base.Header{ + "Accept": base.HeaderValue{"application/sdp"}, + } + + if c.RequestBackChannels { + header["Require"] = base.HeaderValue{"www.onvif.org/ver20/backchannel"} + } + + res, err := c.do(&base.Request{ + Method: base.Describe, + URL: u, + Header: header, + }, false) + if err != nil { + return nil, nil, err + } + + if res.StatusCode != base.StatusOK { + // redirect + if res.StatusCode >= base.StatusMovedPermanently && + res.StatusCode <= base.StatusUseProxy && + len(res.Header["Location"]) == 1 { + c.reset() + + var ru *base.URL + ru, err = base.ParseURL(res.Header["Location"][0]) + if err != nil { + return nil, nil, err + } + + if u.User != nil { + ru.User = u.User + } + + c.connURL = &base.URL{ + Scheme: ru.Scheme, + Host: ru.Host, + } + + return c.doDescribe(ru) + } + + return nil, res, liberrors.ErrClientBadStatusCode{Code: res.StatusCode, Message: res.StatusMessage} + } + + ct, ok := res.Header["Content-Type"] + if !ok || len(ct) != 1 { + return nil, nil, liberrors.ErrClientContentTypeMissing{} + } + + // strip encoding information from Content-Type header + ct = base.HeaderValue{strings.Split(ct[0], ";")[0]} + + if ct[0] != "application/sdp" { + return nil, nil, liberrors.ErrClientContentTypeUnsupported{CT: ct} + } + + var ssd sdp.SessionDescription + err = ssd.Unmarshal(res.Body) + if err != nil { + return nil, nil, liberrors.ErrClientSDPInvalid{Err: err} + } + + var desc description.Session + err = desc.Unmarshal(&ssd) + if err != nil { + return nil, nil, liberrors.ErrClientSDPInvalid{Err: err} + } + + baseURL, err := findBaseURL(&ssd, res, u) + if err != nil { + return nil, nil, err + } + desc.BaseURL = baseURL + + c.lastDescribeURL = u + + return &desc, res, nil +} + +// Describe sends a DESCRIBE request. +func (c *Client) Describe(u *base.URL) (*description.Session, *base.Response, error) { + cres := make(chan clientRes) + select { + case c.chDescribe <- describeReq{url: u, res: cres}: + res := <-cres + return res.sd, res.res, res.err + + case <-c.done: + return nil, nil, c.closeError + } +} + +func (c *Client) doAnnounce(u *base.URL, desc *description.Session) (*base.Response, error) { + err := c.checkState(map[clientState]struct{}{ + clientStateInitial: {}, + }) + if err != nil { + return nil, err + } + + err = c.connOpen() + if err != nil { + return nil, err + } + + prepareForAnnounce(desc) + + byts, err := desc.Marshal(false) + if err != nil { + return nil, err + } + + res, err := c.do(&base.Request{ + Method: base.Announce, + URL: u, + Header: base.Header{ + "Content-Type": base.HeaderValue{"application/sdp"}, + }, + Body: byts, + }, false) + if err != nil { + return nil, err + } + + if res.StatusCode != base.StatusOK { + return nil, liberrors.ErrClientBadStatusCode{ + Code: res.StatusCode, Message: res.StatusMessage, + } + } + + c.baseURL = u.Clone() + c.state = clientStatePreRecord + + return res, nil +} + +// Announce sends an ANNOUNCE request. +func (c *Client) Announce(u *base.URL, desc *description.Session) (*base.Response, error) { + cres := make(chan clientRes) + select { + case c.chAnnounce <- announceReq{url: u, desc: desc, res: cres}: + res := <-cres + return res.res, res.err + + case <-c.done: + return nil, c.closeError + } +} + +func (c *Client) doSetup( + baseURL *base.URL, + medi *description.Media, + rtpPort int, + rtcpPort int, +) (*base.Response, error) { + err := c.checkState(map[clientState]struct{}{ + clientStateInitial: {}, + clientStatePrePlay: {}, + clientStatePreRecord: {}, + }) + if err != nil { + return nil, err + } + + err = c.connOpen() + if err != nil { + return nil, err + } + + if c.baseURL != nil && *baseURL != *c.baseURL { + return nil, liberrors.ErrClientCannotSetupMediasDifferentURLs{} + } + + th := headers.Transport{ + Mode: func() *headers.TransportMode { + if c.state == clientStatePreRecord { + v := headers.TransportModeRecord + return &v + } + // when playing, omit mode, since it causes errors with some servers. + return nil + }(), + } + + cm := &clientMedia{ + c: c, + media: medi, + } + cm.initialize() + + if c.effectiveTransport == nil { + if c.connURL.Scheme == "rtsps" { // always use TCP if encrypted + v := TransportTCP + c.effectiveTransport = &v + } else if c.Transport != nil { // take transport from config + c.effectiveTransport = c.Transport + } + } + + var desiredTransport Transport + if c.effectiveTransport != nil { + desiredTransport = *c.effectiveTransport + } else { + desiredTransport = TransportUDP + } + + switch desiredTransport { + case TransportUDP: + if (rtpPort == 0 && rtcpPort != 0) || + (rtpPort != 0 && rtcpPort == 0) { + return nil, liberrors.ErrClientUDPPortsZero{} + } + + if rtpPort != 0 && rtcpPort != (rtpPort+1) { + return nil, liberrors.ErrClientUDPPortsNotConsecutive{} + } + + err = cm.createUDPListeners( + false, + nil, + net.JoinHostPort("", strconv.FormatInt(int64(rtpPort), 10)), + net.JoinHostPort("", strconv.FormatInt(int64(rtcpPort), 10)), + ) + if err != nil { + return nil, err + } + + v1 := headers.TransportDeliveryUnicast + th.Delivery = &v1 + th.Protocol = headers.TransportProtocolUDP + th.ClientPorts = &[2]int{cm.udpRTPListener.port(), cm.udpRTCPListener.port()} + + case TransportUDPMulticast: + v1 := headers.TransportDeliveryMulticast + th.Delivery = &v1 + th.Protocol = headers.TransportProtocolUDP + + case TransportTCP: + v1 := headers.TransportDeliveryUnicast + th.Delivery = &v1 + th.Protocol = headers.TransportProtocolTCP + ch := c.findFreeChannelPair() + th.InterleavedIDs = &[2]int{ch, ch + 1} + } + + mediaURL, err := medi.URL(baseURL) + if err != nil { + cm.close() + return nil, err + } + + header := base.Header{ + "Transport": th.Marshal(), + } + + if medi.IsBackChannel { + header["Require"] = base.HeaderValue{"www.onvif.org/ver20/backchannel"} + } + + res, err := c.do(&base.Request{ + Method: base.Setup, + URL: mediaURL, + Header: header, + }, false) + if err != nil { + cm.close() + return nil, err + } + + if res.StatusCode != base.StatusOK { + cm.close() + + // switch transport automatically + if res.StatusCode == base.StatusUnsupportedTransport && + c.effectiveTransport == nil { + c.OnTransportSwitch(liberrors.ErrClientSwitchToTCP2{}) + v := TransportTCP + c.effectiveTransport = &v + return c.doSetup(baseURL, medi, 0, 0) + } + + return nil, liberrors.ErrClientBadStatusCode{Code: res.StatusCode, Message: res.StatusMessage} + } + + var thRes headers.Transport + err = thRes.Unmarshal(res.Header["Transport"]) + if err != nil { + cm.close() + return nil, liberrors.ErrClientTransportHeaderInvalid{Err: err} + } + + switch desiredTransport { + case TransportUDP, TransportUDPMulticast: + if thRes.Protocol == headers.TransportProtocolTCP { + cm.close() + + // switch transport automatically + if c.effectiveTransport == nil && + c.Transport == nil { + c.baseURL = baseURL + return c.trySwitchingProtocol2(medi, baseURL) + } + + return nil, liberrors.ErrClientServerRequestedTCP{} + } + } + + switch desiredTransport { + case TransportUDP: + if thRes.Delivery != nil && *thRes.Delivery != headers.TransportDeliveryUnicast { + cm.close() + return nil, liberrors.ErrClientTransportHeaderInvalidDelivery{} + } + + serverPortsValid := thRes.ServerPorts != nil && !isAnyPort(thRes.ServerPorts[0]) && !isAnyPort(thRes.ServerPorts[1]) + + if (c.state == clientStatePreRecord || !c.AnyPortEnable) && !serverPortsValid { + cm.close() + return nil, liberrors.ErrClientServerPortsNotProvided{} + } + + var readIP net.IP + if thRes.Source != nil { + readIP = *thRes.Source + } else { + readIP = c.nconn.RemoteAddr().(*net.TCPAddr).IP + } + + if serverPortsValid { + if !c.AnyPortEnable { + cm.udpRTPListener.readPort = thRes.ServerPorts[0] + } + cm.udpRTPListener.writeAddr = &net.UDPAddr{ + IP: c.nconn.RemoteAddr().(*net.TCPAddr).IP, + Zone: c.nconn.RemoteAddr().(*net.TCPAddr).Zone, + Port: thRes.ServerPorts[0], + } + } + cm.udpRTPListener.readIP = readIP + + if serverPortsValid { + if !c.AnyPortEnable { + cm.udpRTCPListener.readPort = thRes.ServerPorts[1] + } + cm.udpRTCPListener.writeAddr = &net.UDPAddr{ + IP: c.nconn.RemoteAddr().(*net.TCPAddr).IP, + Zone: c.nconn.RemoteAddr().(*net.TCPAddr).Zone, + Port: thRes.ServerPorts[1], + } + } + cm.udpRTCPListener.readIP = readIP + + case TransportUDPMulticast: + if thRes.Delivery == nil || *thRes.Delivery != headers.TransportDeliveryMulticast { + return nil, liberrors.ErrClientTransportHeaderInvalidDelivery{} + } + + if thRes.Ports == nil { + return nil, liberrors.ErrClientTransportHeaderNoPorts{} + } + + if thRes.Destination == nil { + return nil, liberrors.ErrClientTransportHeaderNoDestination{} + } + + var readIP net.IP + if thRes.Source != nil { + readIP = *thRes.Source + } else { + readIP = c.nconn.RemoteAddr().(*net.TCPAddr).IP + } + + err = cm.createUDPListeners( + true, + readIP, + net.JoinHostPort(thRes.Destination.String(), strconv.FormatInt(int64(thRes.Ports[0]), 10)), + net.JoinHostPort(thRes.Destination.String(), strconv.FormatInt(int64(thRes.Ports[1]), 10)), + ) + if err != nil { + return nil, err + } + + cm.udpRTPListener.readIP = readIP + cm.udpRTPListener.readPort = thRes.Ports[0] + cm.udpRTPListener.writeAddr = &net.UDPAddr{ + IP: *thRes.Destination, + Port: thRes.Ports[0], + } + + cm.udpRTCPListener.readIP = readIP + cm.udpRTCPListener.readPort = thRes.Ports[1] + cm.udpRTCPListener.writeAddr = &net.UDPAddr{ + IP: *thRes.Destination, + Port: thRes.Ports[1], + } + + case TransportTCP: + if thRes.Protocol != headers.TransportProtocolTCP { + return nil, liberrors.ErrClientServerRequestedUDP{} + } + + if thRes.Delivery != nil && *thRes.Delivery != headers.TransportDeliveryUnicast { + return nil, liberrors.ErrClientTransportHeaderInvalidDelivery{} + } + + if thRes.InterleavedIDs == nil { + return nil, liberrors.ErrClientTransportHeaderNoInterleavedIDs{} + } + + if (thRes.InterleavedIDs[0] + 1) != thRes.InterleavedIDs[1] { + return nil, liberrors.ErrClientTransportHeaderInvalidInterleavedIDs{} + } + + if c.isChannelPairInUse(thRes.InterleavedIDs[0]) { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, liberrors.ErrClientTransportHeaderInterleavedIDsInUse{} + } + + cm.tcpChannel = thRes.InterleavedIDs[0] + } + + if c.setuppedMedias == nil { + c.setuppedMedias = make(map[*description.Media]*clientMedia) + } + + c.setuppedMedias[medi] = cm + + c.baseURL = baseURL + c.effectiveTransport = &desiredTransport + + if medi.IsBackChannel { + c.backChannelSetupped = true + } else { + c.stdChannelSetupped = true + } + + if c.state == clientStateInitial { + c.state = clientStatePrePlay + } + + return res, nil +} + +func (c *Client) isChannelPairInUse(channel int) bool { + for _, cm := range c.setuppedMedias { + if (cm.tcpChannel+1) == channel || cm.tcpChannel == channel || cm.tcpChannel == (channel+1) { + return true + } + } + return false +} + +func (c *Client) findFreeChannelPair() int { + for i := 0; ; i += 2 { // prefer even channels + if !c.isChannelPairInUse(i) { + return i + } + } +} + +// Setup sends a SETUP request. +// rtpPort and rtcpPort are used only if transport is UDP. +// if rtpPort and rtcpPort are zero, they are chosen automatically. +func (c *Client) Setup( + baseURL *base.URL, + media *description.Media, + rtpPort int, + rtcpPort int, +) (*base.Response, error) { + cres := make(chan clientRes) + select { + case c.chSetup <- setupReq{ + baseURL: baseURL, + media: media, + rtpPort: rtpPort, + rtcpPort: rtcpPort, + res: cres, + }: + res := <-cres + return res.res, res.err + + case <-c.done: + return nil, c.closeError + } +} + +// SetupAll setups all the given medias. +func (c *Client) SetupAll(baseURL *base.URL, medias []*description.Media) error { + for _, m := range medias { + _, err := c.Setup(baseURL, m, 0, 0) + if err != nil { + return err + } + } + return nil +} + +func (c *Client) doPlay(ra *headers.Range) (*base.Response, error) { + err := c.checkState(map[clientState]struct{}{ + clientStatePrePlay: {}, + }) + if err != nil { + return nil, err + } + + c.state = clientStatePlay + c.startTransportRoutines() + c.createWriter() + + // Range is mandatory in Parrot Streaming Server + if ra == nil { + ra = &headers.Range{ + Value: &headers.RangeNPT{ + Start: 0, + }, + } + } + + header := base.Header{ + "Range": ra.Marshal(), + } + + if c.backChannelSetupped { + header["Require"] = base.HeaderValue{"www.onvif.org/ver20/backchannel"} + } + + res, err := c.do(&base.Request{ + Method: base.Play, + URL: c.baseURL, + Header: header, + }, false) + if err != nil { + c.destroyWriter() + c.stopTransportRoutines() + c.state = clientStatePrePlay + return nil, err + } + + if res.StatusCode != base.StatusOK { + c.destroyWriter() + c.stopTransportRoutines() + c.state = clientStatePrePlay + return nil, liberrors.ErrClientBadStatusCode{ + Code: res.StatusCode, Message: res.StatusMessage, + } + } + + // open the firewall by sending empty packets to the counterpart. + // do this before sending the request. + // don't do this with multicast, otherwise the RTP packet is going to be broadcasted + // to all listeners, including us, messing up the stream. + if *c.effectiveTransport == TransportUDP { + for _, cm := range c.setuppedMedias { + byts, _ := (&rtp.Packet{Header: rtp.Header{Version: 2}}).Marshal() + cm.udpRTPListener.write(byts) //nolint:errcheck + + byts, _ = (&rtcp.ReceiverReport{}).Marshal() + cm.udpRTCPListener.write(byts) //nolint:errcheck + } + } + + c.startWriter() + + c.lastRange = ra + + return res, nil +} + +// Play sends a PLAY request. +// This can be called only after Setup(). +func (c *Client) Play(ra *headers.Range) (*base.Response, error) { + cres := make(chan clientRes) + select { + case c.chPlay <- playReq{ra: ra, res: cres}: + res := <-cres + return res.res, res.err + + case <-c.done: + return nil, c.closeError + } +} + +func (c *Client) doRecord() (*base.Response, error) { + err := c.checkState(map[clientState]struct{}{ + clientStatePreRecord: {}, + }) + if err != nil { + return nil, err + } + + c.state = clientStateRecord + c.startTransportRoutines() + c.createWriter() + + res, err := c.do(&base.Request{ + Method: base.Record, + URL: c.baseURL, + }, false) + if err != nil { + c.destroyWriter() + c.stopTransportRoutines() + c.state = clientStatePreRecord + return nil, err + } + + if res.StatusCode != base.StatusOK { + c.destroyWriter() + c.stopTransportRoutines() + c.state = clientStatePreRecord + return nil, liberrors.ErrClientBadStatusCode{ + Code: res.StatusCode, Message: res.StatusMessage, + } + } + + c.startWriter() + + return nil, nil +} + +// Record sends a RECORD request. +// This can be called only after Announce() and Setup(). +func (c *Client) Record() (*base.Response, error) { + cres := make(chan clientRes) + select { + case c.chRecord <- recordReq{res: cres}: + res := <-cres + return res.res, res.err + + case <-c.done: + return nil, c.closeError + } +} + +func (c *Client) doPause() (*base.Response, error) { + err := c.checkState(map[clientState]struct{}{ + clientStatePlay: {}, + clientStateRecord: {}, + }) + if err != nil { + return nil, err + } + + c.destroyWriter() + + res, err := c.do(&base.Request{ + Method: base.Pause, + URL: c.baseURL, + }, false) + if err != nil { + c.createWriter() + c.startWriter() + return nil, err + } + + if res.StatusCode != base.StatusOK { + c.createWriter() + c.startWriter() + return nil, liberrors.ErrClientBadStatusCode{ + Code: res.StatusCode, Message: res.StatusMessage, + } + } + + c.stopTransportRoutines() + + switch c.state { + case clientStatePlay: + c.state = clientStatePrePlay + case clientStateRecord: + c.state = clientStatePreRecord + } + + return res, nil +} + +// Pause sends a PAUSE request. +// This can be called only after Play() or Record(). +func (c *Client) Pause() (*base.Response, error) { + cres := make(chan clientRes) + select { + case c.chPause <- pauseReq{res: cres}: + res := <-cres + return res.res, res.err + + case <-c.done: + return nil, c.closeError + } +} + +// Seek asks the server to re-start the stream from a specific timestamp. +func (c *Client) Seek(ra *headers.Range) (*base.Response, error) { + _, err := c.Pause() + if err != nil { + return nil, err + } + + return c.Play(ra) +} + +// OnPacketRTPAny sets a callback that is called when a RTP packet is read from any setupped media. +func (c *Client) OnPacketRTPAny(cb OnPacketRTPAnyFunc) { + for _, cm := range c.setuppedMedias { + cmedia := cm.media + for _, forma := range cm.media.Formats { + c.OnPacketRTP(cm.media, forma, func(pkt *rtp.Packet) { + cb(cmedia, forma, pkt) + }) + } + } +} + +// OnPacketRTCPAny sets a callback that is called when a RTCP packet is read from any setupped media. +func (c *Client) OnPacketRTCPAny(cb OnPacketRTCPAnyFunc) { + for _, cm := range c.setuppedMedias { + cmedia := cm.media + c.OnPacketRTCP(cm.media, func(pkt rtcp.Packet) { + cb(cmedia, pkt) + }) + } +} + +// OnPacketRTP sets a callback that is called when a RTP packet is read. +func (c *Client) OnPacketRTP(medi *description.Media, forma format.Format, cb OnPacketRTPFunc) { + cm := c.setuppedMedias[medi] + ct := cm.formats[forma.PayloadType()] + ct.onPacketRTP = cb +} + +// OnPacketRTCP sets a callback that is called when a RTCP packet is read. +func (c *Client) OnPacketRTCP(medi *description.Media, cb OnPacketRTCPFunc) { + cm := c.setuppedMedias[medi] + cm.onPacketRTCP = cb +} + +// WritePacketRTP writes a RTP packet to the server. +func (c *Client) WritePacketRTP(medi *description.Media, pkt *rtp.Packet) error { + return c.WritePacketRTPWithNTP(medi, pkt, c.timeNow()) +} + +// WritePacketRTPWithNTP writes a RTP packet to the server. +// ntp is the absolute time of the packet, and is sent with periodic RTCP sender reports. +func (c *Client) WritePacketRTPWithNTP(medi *description.Media, pkt *rtp.Packet, ntp time.Time) error { + byts := make([]byte, c.MaxPacketSize) + n, err := pkt.MarshalTo(byts) + if err != nil { + return err + } + byts = byts[:n] + + select { + case <-c.done: + return c.closeError + default: + } + + c.writerMutex.RLock() + defer c.writerMutex.RUnlock() + + if c.writer == nil { + return nil + } + + cm := c.setuppedMedias[medi] + cf := cm.formats[pkt.PayloadType] + + cf.rtcpSender.ProcessPacket(pkt, ntp, cf.format.PTSEqualsDTS(pkt)) + + ok := c.writer.push(func() error { + return cf.writePacketRTPInQueue(byts) + }) + if !ok { + return liberrors.ErrClientWriteQueueFull{} + } + + return nil +} + +// WritePacketRTCP writes a RTCP packet to the server. +func (c *Client) WritePacketRTCP(medi *description.Media, pkt rtcp.Packet) error { + byts, err := pkt.Marshal() + if err != nil { + return err + } + + select { + case <-c.done: + return c.closeError + default: + } + + c.writerMutex.RLock() + defer c.writerMutex.RUnlock() + + if c.writer == nil { + return nil + } + + cm := c.setuppedMedias[medi] + + ok := c.writer.push(func() error { + return cm.writePacketRTCPInQueue(byts) + }) + if !ok { + return liberrors.ErrClientWriteQueueFull{} + } + + return nil +} + +// PacketPTS returns the PTS of an incoming RTP packet. +// It is computed by decoding the packet timestamp and sychronizing it with other tracks. +// +// Deprecated: replaced by PacketPTS2. +func (c *Client) PacketPTS(medi *description.Media, pkt *rtp.Packet) (time.Duration, bool) { + cm := c.setuppedMedias[medi] + ct := cm.formats[pkt.PayloadType] + + v, ok := c.timeDecoder.Decode(ct.format, pkt) + if !ok { + return 0, false + } + + return multiplyAndDivide(time.Duration(v), time.Second, time.Duration(ct.format.ClockRate())), true +} + +// PacketPTS2 returns the PTS of an incoming RTP packet. +// It is computed by decoding the packet timestamp and sychronizing it with other tracks. +func (c *Client) PacketPTS2(medi *description.Media, pkt *rtp.Packet) (int64, bool) { + cm := c.setuppedMedias[medi] + ct := cm.formats[pkt.PayloadType] + return c.timeDecoder.Decode(ct.format, pkt) +} + +// PacketNTP returns the NTP timestamp of an incoming RTP packet. +// The NTP timestamp is computed from RTCP sender reports. +func (c *Client) PacketNTP(medi *description.Media, pkt *rtp.Packet) (time.Time, bool) { + cm := c.setuppedMedias[medi] + ct := cm.formats[pkt.PayloadType] + return ct.rtcpReceiver.PacketNTP(pkt.Timestamp) +} + +// Stats returns client statistics. +func (c *Client) Stats() *ClientStats { + return &ClientStats{ + Conn: StatsConn{ + BytesReceived: atomic.LoadUint64(c.bytesReceived), + BytesSent: atomic.LoadUint64(c.bytesSent), + }, + Session: StatsSession{ + BytesReceived: func() uint64 { + v := uint64(0) + for _, sm := range c.setuppedMedias { + v += atomic.LoadUint64(sm.bytesReceived) + } + return v + }(), + BytesSent: func() uint64 { + v := uint64(0) + for _, sm := range c.setuppedMedias { + v += atomic.LoadUint64(sm.bytesSent) + } + return v + }(), + RTPPacketsReceived: func() uint64 { + v := uint64(0) + for _, sm := range c.setuppedMedias { + for _, f := range sm.formats { + v += atomic.LoadUint64(f.rtpPacketsReceived) + } + } + return v + }(), + RTPPacketsSent: func() uint64 { + v := uint64(0) + for _, sm := range c.setuppedMedias { + for _, f := range sm.formats { + v += atomic.LoadUint64(f.rtpPacketsSent) + } + } + return v + }(), + RTPPacketsLost: func() uint64 { + v := uint64(0) + for _, sm := range c.setuppedMedias { + for _, f := range sm.formats { + v += atomic.LoadUint64(f.rtpPacketsLost) + } + } + return v + }(), + RTPPacketsInError: func() uint64 { + v := uint64(0) + for _, sm := range c.setuppedMedias { + v += atomic.LoadUint64(sm.rtpPacketsInError) + } + return v + }(), + RTPPacketsJitter: func() float64 { + v := float64(0) + n := float64(0) + for _, sm := range c.setuppedMedias { + for _, fo := range sm.formats { + if fo.rtcpReceiver != nil { + stats := fo.rtcpReceiver.Stats() + if stats != nil { + v += stats.Jitter + n++ + } + } + } + } + if n != 0 { + return v / n + } + return 0 + }(), + RTCPPacketsReceived: func() uint64 { + v := uint64(0) + for _, sm := range c.setuppedMedias { + v += atomic.LoadUint64(sm.rtcpPacketsReceived) + } + return v + }(), + RTCPPacketsSent: func() uint64 { + v := uint64(0) + for _, sm := range c.setuppedMedias { + v += atomic.LoadUint64(sm.rtcpPacketsSent) + } + return v + }(), + RTCPPacketsInError: func() uint64 { + v := uint64(0) + for _, sm := range c.setuppedMedias { + v += atomic.LoadUint64(sm.rtcpPacketsInError) + } + return v + }(), + Medias: func() map[*description.Media]StatsSessionMedia { //nolint:dupl + ret := make(map[*description.Media]StatsSessionMedia, len(c.setuppedMedias)) + + for med, sm := range c.setuppedMedias { + ret[med] = StatsSessionMedia{ + BytesReceived: atomic.LoadUint64(sm.bytesReceived), + BytesSent: atomic.LoadUint64(sm.bytesSent), + RTPPacketsInError: atomic.LoadUint64(sm.rtpPacketsInError), + RTCPPacketsReceived: atomic.LoadUint64(sm.rtcpPacketsReceived), + RTCPPacketsSent: atomic.LoadUint64(sm.rtcpPacketsSent), + RTCPPacketsInError: atomic.LoadUint64(sm.rtcpPacketsInError), + Formats: func() map[format.Format]StatsSessionFormat { + ret := make(map[format.Format]StatsSessionFormat, len(sm.formats)) + + for _, fo := range sm.formats { + recvStats := func() *rtcpreceiver.Stats { + if fo.rtcpReceiver != nil { + return fo.rtcpReceiver.Stats() + } + return nil + }() + sentStats := func() *rtcpsender.Stats { + if fo.rtcpSender != nil { + return fo.rtcpSender.Stats() + } + return nil + }() + + ret[fo.format] = StatsSessionFormat{ //nolint:dupl + RTPPacketsReceived: atomic.LoadUint64(fo.rtpPacketsReceived), + RTPPacketsSent: atomic.LoadUint64(fo.rtpPacketsSent), + RTPPacketsLost: atomic.LoadUint64(fo.rtpPacketsLost), + LocalSSRC: func() uint32 { + if fo.rtcpReceiver != nil { + return *fo.rtcpReceiver.LocalSSRC + } + if sentStats != nil { + return sentStats.LocalSSRC + } + return 0 + }(), + RemoteSSRC: func() uint32 { + if recvStats != nil { + return recvStats.RemoteSSRC + } + return 0 + }(), + RTPPacketsLastSequenceNumber: func() uint16 { + if recvStats != nil { + return recvStats.LastSequenceNumber + } + if sentStats != nil { + return sentStats.LastSequenceNumber + } + return 0 + }(), + RTPPacketsLastRTP: func() uint32 { + if recvStats != nil { + return recvStats.LastRTP + } + if sentStats != nil { + return sentStats.LastRTP + } + return 0 + }(), + RTPPacketsLastNTP: func() time.Time { + if recvStats != nil { + return recvStats.LastNTP + } + if sentStats != nil { + return sentStats.LastNTP + } + return time.Time{} + }(), + RTPPacketsJitter: func() float64 { + if recvStats != nil { + return recvStats.Jitter + } + return 0 + }(), + } + } + + return ret + }(), + } + } + + return ret + }(), + }, + } +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client_format.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client_format.go new file mode 100644 index 000000000..2f903fd50 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client_format.go @@ -0,0 +1,159 @@ +package gortsplib + +import ( + "sync/atomic" + "time" + + "github.com/pion/rtcp" + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/rtcpreceiver" + "github.com/bluenviron/gortsplib/v4/pkg/rtcpsender" + "github.com/bluenviron/gortsplib/v4/pkg/rtplossdetector" + "github.com/bluenviron/gortsplib/v4/pkg/rtpreorderer" +) + +type clientFormat struct { + cm *clientMedia + format format.Format + onPacketRTP OnPacketRTPFunc + + udpReorderer *rtpreorderer.Reorderer // play + tcpLossDetector *rtplossdetector.LossDetector // play + rtcpReceiver *rtcpreceiver.RTCPReceiver // play + rtcpSender *rtcpsender.RTCPSender // record or back channel + writePacketRTPInQueue func([]byte) error + rtpPacketsReceived *uint64 + rtpPacketsSent *uint64 + rtpPacketsLost *uint64 +} + +func (cf *clientFormat) initialize() { + cf.rtpPacketsReceived = new(uint64) + cf.rtpPacketsSent = new(uint64) + cf.rtpPacketsLost = new(uint64) +} + +func (cf *clientFormat) start() { + if cf.cm.udpRTPListener != nil { + cf.writePacketRTPInQueue = cf.writePacketRTPInQueueUDP + } else { + cf.writePacketRTPInQueue = cf.writePacketRTPInQueueTCP + } + + if cf.cm.c.state == clientStateRecord || cf.cm.media.IsBackChannel { + cf.rtcpSender = &rtcpsender.RTCPSender{ + ClockRate: cf.format.ClockRate(), + Period: cf.cm.c.senderReportPeriod, + TimeNow: cf.cm.c.timeNow, + WritePacketRTCP: func(pkt rtcp.Packet) { + if !cf.cm.c.DisableRTCPSenderReports { + cf.cm.c.WritePacketRTCP(cf.cm.media, pkt) //nolint:errcheck + } + }, + } + cf.rtcpSender.Initialize() + } else { + if cf.cm.udpRTPListener != nil { + cf.udpReorderer = &rtpreorderer.Reorderer{} + cf.udpReorderer.Initialize() + } else { + cf.tcpLossDetector = &rtplossdetector.LossDetector{} + } + + cf.rtcpReceiver = &rtcpreceiver.RTCPReceiver{ + ClockRate: cf.format.ClockRate(), + Period: cf.cm.c.receiverReportPeriod, + TimeNow: cf.cm.c.timeNow, + WritePacketRTCP: func(pkt rtcp.Packet) { + if cf.cm.udpRTPListener != nil { + cf.cm.c.WritePacketRTCP(cf.cm.media, pkt) //nolint:errcheck + } + }, + } + err := cf.rtcpReceiver.Initialize() + if err != nil { + panic(err) + } + } +} + +func (cf *clientFormat) stop() { + if cf.rtcpReceiver != nil { + cf.rtcpReceiver.Close() + cf.rtcpReceiver = nil + } + + if cf.rtcpSender != nil { + cf.rtcpSender.Close() + } +} + +func (cf *clientFormat) readPacketRTPUDP(pkt *rtp.Packet) { + packets, lost := cf.udpReorderer.Process(pkt) + if lost != 0 { + cf.handlePacketsLost(uint64(lost)) + // do not return + } + + now := cf.cm.c.timeNow() + + for _, pkt := range packets { + cf.handlePacketRTP(pkt, now) + } +} + +func (cf *clientFormat) readPacketRTPTCP(pkt *rtp.Packet) { + lost := cf.tcpLossDetector.Process(pkt) + if lost != 0 { + cf.handlePacketsLost(uint64(lost)) + // do not return + } + + now := cf.cm.c.timeNow() + + cf.handlePacketRTP(pkt, now) +} + +func (cf *clientFormat) handlePacketRTP(pkt *rtp.Packet, now time.Time) { + err := cf.rtcpReceiver.ProcessPacket(pkt, now, cf.format.PTSEqualsDTS(pkt)) + if err != nil { + cf.cm.onPacketRTPDecodeError(err) + return + } + + atomic.AddUint64(cf.rtpPacketsReceived, 1) + + cf.onPacketRTP(pkt) +} + +func (cf *clientFormat) handlePacketsLost(lost uint64) { + atomic.AddUint64(cf.rtpPacketsLost, lost) + cf.cm.c.OnPacketsLost(lost) +} + +func (cf *clientFormat) writePacketRTPInQueueUDP(payload []byte) error { + err := cf.cm.udpRTPListener.write(payload) + if err != nil { + return err + } + + atomic.AddUint64(cf.cm.bytesSent, uint64(len(payload))) + atomic.AddUint64(cf.rtpPacketsSent, 1) + return nil +} + +func (cf *clientFormat) writePacketRTPInQueueTCP(payload []byte) error { + cf.cm.c.tcpFrame.Channel = cf.cm.tcpChannel + cf.cm.c.tcpFrame.Payload = payload + cf.cm.c.nconn.SetWriteDeadline(time.Now().Add(cf.cm.c.WriteTimeout)) + err := cf.cm.c.conn.WriteInterleavedFrame(cf.cm.c.tcpFrame, cf.cm.c.tcpBuffer) + if err != nil { + return err + } + + atomic.AddUint64(cf.cm.bytesSent, uint64(len(payload))) + atomic.AddUint64(cf.rtpPacketsSent, 1) + return nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client_media.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client_media.go new file mode 100644 index 000000000..c6c4f3bab --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client_media.go @@ -0,0 +1,361 @@ +package gortsplib + +import ( + "net" + "sync/atomic" + "time" + + "github.com/pion/rtcp" + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/liberrors" +) + +type clientMedia struct { + c *Client + media *description.Media + + onPacketRTCP OnPacketRTCPFunc + formats map[uint8]*clientFormat + tcpChannel int + udpRTPListener *clientUDPListener + udpRTCPListener *clientUDPListener + writePacketRTCPInQueue func([]byte) error + bytesReceived *uint64 + bytesSent *uint64 + rtpPacketsInError *uint64 + rtcpPacketsReceived *uint64 + rtcpPacketsSent *uint64 + rtcpPacketsInError *uint64 +} + +func (cm *clientMedia) initialize() { + cm.onPacketRTCP = func(rtcp.Packet) {} + cm.bytesReceived = new(uint64) + cm.bytesSent = new(uint64) + cm.rtpPacketsInError = new(uint64) + cm.rtcpPacketsReceived = new(uint64) + cm.rtcpPacketsSent = new(uint64) + cm.rtcpPacketsInError = new(uint64) + + cm.formats = make(map[uint8]*clientFormat) + + for _, forma := range cm.media.Formats { + f := &clientFormat{ + cm: cm, + format: forma, + onPacketRTP: func(*rtp.Packet) {}, + } + f.initialize() + cm.formats[forma.PayloadType()] = f + } +} + +func (cm *clientMedia) close() { + if cm.udpRTPListener != nil { + cm.udpRTPListener.close() + cm.udpRTCPListener.close() + } +} + +func (cm *clientMedia) createUDPListeners( + multicastEnable bool, + multicastSourceIP net.IP, + rtpAddress string, + rtcpAddress string, +) error { + if rtpAddress != ":0" { + l1 := &clientUDPListener{ + c: cm.c, + multicastEnable: multicastEnable, + multicastSourceIP: multicastSourceIP, + address: rtpAddress, + } + err := l1.initialize() + if err != nil { + return err + } + + l2 := &clientUDPListener{ + c: cm.c, + multicastEnable: multicastEnable, + multicastSourceIP: multicastSourceIP, + address: rtcpAddress, + } + err = l2.initialize() + if err != nil { + l1.close() + return err + } + + cm.udpRTPListener, cm.udpRTCPListener = l1, l2 + return nil + } + + var err error + cm.udpRTPListener, cm.udpRTCPListener, err = createUDPListenerPair(cm.c) + return err +} + +func (cm *clientMedia) start() { + if cm.udpRTPListener != nil { + cm.writePacketRTCPInQueue = cm.writePacketRTCPInQueueUDP + + if cm.c.state == clientStateRecord || cm.media.IsBackChannel { + cm.udpRTPListener.readFunc = cm.readPacketRTPUDPRecord + cm.udpRTCPListener.readFunc = cm.readPacketRTCPUDPRecord + } else { + cm.udpRTPListener.readFunc = cm.readPacketRTPUDPPlay + cm.udpRTCPListener.readFunc = cm.readPacketRTCPUDPPlay + } + } else { + cm.writePacketRTCPInQueue = cm.writePacketRTCPInQueueTCP + + if cm.c.tcpCallbackByChannel == nil { + cm.c.tcpCallbackByChannel = make(map[int]readFunc) + } + + if cm.c.state == clientStateRecord || cm.media.IsBackChannel { + cm.c.tcpCallbackByChannel[cm.tcpChannel] = cm.readPacketRTPTCPRecord + cm.c.tcpCallbackByChannel[cm.tcpChannel+1] = cm.readPacketRTCPTCPRecord + } else { + cm.c.tcpCallbackByChannel[cm.tcpChannel] = cm.readPacketRTPTCPPlay + cm.c.tcpCallbackByChannel[cm.tcpChannel+1] = cm.readPacketRTCPTCPPlay + } + } + + for _, ct := range cm.formats { + ct.start() + } + + if cm.udpRTPListener != nil { + cm.udpRTPListener.start() + cm.udpRTCPListener.start() + } +} + +func (cm *clientMedia) stop() { + if cm.udpRTPListener != nil { + cm.udpRTPListener.stop() + cm.udpRTCPListener.stop() + } + + for _, ct := range cm.formats { + ct.stop() + } +} + +func (cm *clientMedia) findFormatWithSSRC(ssrc uint32) *clientFormat { + for _, format := range cm.formats { + stats := format.rtcpReceiver.Stats() + if stats != nil && stats.RemoteSSRC == ssrc { + return format + } + } + return nil +} + +func (cm *clientMedia) writePacketRTCPInQueueUDP(payload []byte) error { + err := cm.udpRTCPListener.write(payload) + if err != nil { + return err + } + + atomic.AddUint64(cm.bytesSent, uint64(len(payload))) + atomic.AddUint64(cm.rtcpPacketsSent, 1) + return nil +} + +func (cm *clientMedia) writePacketRTCPInQueueTCP(payload []byte) error { + cm.c.tcpFrame.Channel = cm.tcpChannel + 1 + cm.c.tcpFrame.Payload = payload + cm.c.nconn.SetWriteDeadline(time.Now().Add(cm.c.WriteTimeout)) + err := cm.c.conn.WriteInterleavedFrame(cm.c.tcpFrame, cm.c.tcpBuffer) + if err != nil { + return err + } + + atomic.AddUint64(cm.bytesSent, uint64(len(payload))) + atomic.AddUint64(cm.rtcpPacketsSent, 1) + return nil +} + +func (cm *clientMedia) readPacketRTPTCPPlay(payload []byte) bool { + atomic.AddUint64(cm.bytesReceived, uint64(len(payload))) + + now := cm.c.timeNow() + atomic.StoreInt64(cm.c.tcpLastFrameTime, now.Unix()) + + pkt := &rtp.Packet{} + err := pkt.Unmarshal(payload) + if err != nil { + cm.onPacketRTPDecodeError(err) + return false + } + + forma, ok := cm.formats[pkt.PayloadType] + if !ok { + cm.onPacketRTPDecodeError(liberrors.ErrClientRTPPacketUnknownPayloadType{PayloadType: pkt.PayloadType}) + return false + } + + forma.readPacketRTPTCP(pkt) + + return true +} + +func (cm *clientMedia) readPacketRTCPTCPPlay(payload []byte) bool { + atomic.AddUint64(cm.bytesReceived, uint64(len(payload))) + + now := cm.c.timeNow() + atomic.StoreInt64(cm.c.tcpLastFrameTime, now.Unix()) + + if len(payload) > udpMaxPayloadSize { + cm.onPacketRTCPDecodeError(liberrors.ErrClientRTCPPacketTooBig{L: len(payload), Max: udpMaxPayloadSize}) + return false + } + + packets, err := rtcp.Unmarshal(payload) + if err != nil { + cm.onPacketRTCPDecodeError(err) + return false + } + + atomic.AddUint64(cm.rtcpPacketsReceived, uint64(len(packets))) + + for _, pkt := range packets { + if sr, ok := pkt.(*rtcp.SenderReport); ok { + format := cm.findFormatWithSSRC(sr.SSRC) + if format != nil { + format.rtcpReceiver.ProcessSenderReport(sr, now) + } + } + + cm.onPacketRTCP(pkt) + } + + return true +} + +func (cm *clientMedia) readPacketRTPTCPRecord(_ []byte) bool { + return false +} + +func (cm *clientMedia) readPacketRTCPTCPRecord(payload []byte) bool { + atomic.AddUint64(cm.bytesReceived, uint64(len(payload))) + + if len(payload) > udpMaxPayloadSize { + cm.onPacketRTCPDecodeError(liberrors.ErrClientRTCPPacketTooBig{L: len(payload), Max: udpMaxPayloadSize}) + return false + } + + packets, err := rtcp.Unmarshal(payload) + if err != nil { + cm.onPacketRTCPDecodeError(err) + return false + } + + atomic.AddUint64(cm.rtcpPacketsReceived, uint64(len(packets))) + + for _, pkt := range packets { + cm.onPacketRTCP(pkt) + } + + return true +} + +func (cm *clientMedia) readPacketRTPUDPPlay(payload []byte) bool { + atomic.AddUint64(cm.bytesReceived, uint64(len(payload))) + + if len(payload) == (udpMaxPayloadSize + 1) { + cm.onPacketRTPDecodeError(liberrors.ErrClientRTPPacketTooBigUDP{}) + return false + } + + pkt := &rtp.Packet{} + err := pkt.Unmarshal(payload) + if err != nil { + cm.onPacketRTPDecodeError(err) + return false + } + + forma, ok := cm.formats[pkt.PayloadType] + if !ok { + cm.onPacketRTPDecodeError(liberrors.ErrClientRTPPacketUnknownPayloadType{PayloadType: pkt.PayloadType}) + return false + } + + forma.readPacketRTPUDP(pkt) + + return true +} + +func (cm *clientMedia) readPacketRTCPUDPPlay(payload []byte) bool { + atomic.AddUint64(cm.bytesReceived, uint64(len(payload))) + + if len(payload) == (udpMaxPayloadSize + 1) { + cm.onPacketRTCPDecodeError(liberrors.ErrClientRTCPPacketTooBigUDP{}) + return false + } + + packets, err := rtcp.Unmarshal(payload) + if err != nil { + cm.onPacketRTCPDecodeError(err) + return false + } + + now := cm.c.timeNow() + + atomic.AddUint64(cm.rtcpPacketsReceived, uint64(len(packets))) + + for _, pkt := range packets { + if sr, ok := pkt.(*rtcp.SenderReport); ok { + format := cm.findFormatWithSSRC(sr.SSRC) + if format != nil { + format.rtcpReceiver.ProcessSenderReport(sr, now) + } + } + + cm.onPacketRTCP(pkt) + } + + return true +} + +func (cm *clientMedia) readPacketRTPUDPRecord(_ []byte) bool { + return false +} + +func (cm *clientMedia) readPacketRTCPUDPRecord(payload []byte) bool { + atomic.AddUint64(cm.bytesReceived, uint64(len(payload))) + + if len(payload) == (udpMaxPayloadSize + 1) { + cm.onPacketRTCPDecodeError(liberrors.ErrClientRTCPPacketTooBigUDP{}) + return false + } + + packets, err := rtcp.Unmarshal(payload) + if err != nil { + cm.onPacketRTCPDecodeError(err) + return false + } + + atomic.AddUint64(cm.rtcpPacketsReceived, uint64(len(packets))) + + for _, pkt := range packets { + cm.onPacketRTCP(pkt) + } + + return true +} + +func (cm *clientMedia) onPacketRTPDecodeError(err error) { + atomic.AddUint64(cm.rtpPacketsInError, 1) + cm.c.OnDecodeError(err) +} + +func (cm *clientMedia) onPacketRTCPDecodeError(err error) { + atomic.AddUint64(cm.rtcpPacketsInError, 1) + cm.c.OnDecodeError(err) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client_reader.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client_reader.go new file mode 100644 index 000000000..f64d115c2 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client_reader.go @@ -0,0 +1,79 @@ +package gortsplib + +import ( + "sync" + + "github.com/bluenviron/gortsplib/v4/pkg/base" + "github.com/bluenviron/gortsplib/v4/pkg/liberrors" +) + +type clientReader struct { + c *Client + + mutex sync.Mutex + allowInterleavedFrames bool + + chResponse chan *base.Response + chRequest chan *base.Request + chError chan error +} + +func (r *clientReader) start() { + r.chResponse = make(chan *base.Response) + r.chRequest = make(chan *base.Request) + r.chError = make(chan error) + + go r.run() +} + +func (r *clientReader) setAllowInterleavedFrames(v bool) { + r.mutex.Lock() + defer r.mutex.Unlock() + r.allowInterleavedFrames = v +} + +func (r *clientReader) wait() { + for { + select { + case <-r.chError: + return + + case <-r.chResponse: + case <-r.chRequest: + } + } +} + +func (r *clientReader) run() { + r.chError <- r.runInner() +} + +func (r *clientReader) runInner() error { + for { + what, err := r.c.conn.Read() + if err != nil { + return err + } + + switch what := what.(type) { + case *base.Response: + r.chResponse <- what + + case *base.Request: + r.chRequest <- what + + case *base.InterleavedFrame: + r.mutex.Lock() + + if !r.allowInterleavedFrames { + r.mutex.Unlock() + return liberrors.ErrClientUnexpectedFrame{} + } + + if cb, ok := r.c.tcpCallbackByChannel[what.Channel]; ok { + cb(what.Payload) + } + r.mutex.Unlock() + } + } +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client_stats.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client_stats.go new file mode 100644 index 000000000..22451ea3f --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client_stats.go @@ -0,0 +1,7 @@ +package gortsplib + +// ClientStats are client statistics +type ClientStats struct { + Conn StatsConn + Session StatsSession +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client_udp_listener.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client_udp_listener.go new file mode 100644 index 000000000..0c7bb2dec --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/client_udp_listener.go @@ -0,0 +1,188 @@ +package gortsplib + +import ( + "crypto/rand" + "math/big" + "net" + "strconv" + "sync/atomic" + "time" + + "github.com/bluenviron/gortsplib/v4/pkg/multicast" +) + +func int64Ptr(v int64) *int64 { + return &v +} + +func randInRange(maxVal int) (int, error) { + b := big.NewInt(int64(maxVal + 1)) + n, err := rand.Int(rand.Reader, b) + if err != nil { + return 0, err + } + return int(n.Int64()), nil +} + +func createUDPListenerPair(c *Client) (*clientUDPListener, *clientUDPListener, error) { + // choose two consecutive ports in range 65535-10000 + // RTP port must be even and RTCP port odd + for { + v, err := randInRange((65535 - 10000) / 2) + if err != nil { + return nil, nil, err + } + + rtpPort := v*2 + 10000 + rtcpPort := rtpPort + 1 + + rtpListener := &clientUDPListener{ + c: c, + multicastEnable: false, + multicastSourceIP: nil, + address: net.JoinHostPort("", strconv.FormatInt(int64(rtpPort), 10)), + } + err = rtpListener.initialize() + if err != nil { + continue + } + + rtcpListener := &clientUDPListener{ + c: c, + multicastEnable: false, + multicastSourceIP: nil, + address: net.JoinHostPort("", strconv.FormatInt(int64(rtcpPort), 10)), + } + err = rtcpListener.initialize() + if err != nil { + rtpListener.close() + continue + } + + return rtpListener, rtcpListener, nil + } +} + +type packetConn interface { + net.PacketConn + SetReadBuffer(int) error +} + +type clientUDPListener struct { + c *Client + multicastEnable bool + multicastSourceIP net.IP + address string + + pc packetConn + readFunc readFunc + readIP net.IP + readPort int + writeAddr *net.UDPAddr + + running bool + lastPacketTime *int64 + + done chan struct{} +} + +func (u *clientUDPListener) initialize() error { + if u.multicastEnable { + intf, err := multicast.InterfaceForSource(u.multicastSourceIP) + if err != nil { + return err + } + + u.pc, err = multicast.NewSingleConn(intf, u.address, u.c.ListenPacket) + if err != nil { + return err + } + } else { + tmp, err := u.c.ListenPacket(restrictNetwork("udp", u.address)) + if err != nil { + return err + } + u.pc = tmp.(*net.UDPConn) + } + + err := u.pc.SetReadBuffer(udpKernelReadBufferSize) + if err != nil { + u.pc.Close() + return err + } + + u.lastPacketTime = int64Ptr(0) + return nil +} + +func (u *clientUDPListener) close() { + if u.running { + u.stop() + } + u.pc.Close() +} + +func (u *clientUDPListener) port() int { + return u.pc.LocalAddr().(*net.UDPAddr).Port +} + +func (u *clientUDPListener) start() { + u.running = true + u.pc.SetReadDeadline(time.Time{}) + u.done = make(chan struct{}) + go u.run() +} + +func (u *clientUDPListener) stop() { + u.pc.SetReadDeadline(time.Now()) + <-u.done + u.running = false +} + +func (u *clientUDPListener) run() { + defer close(u.done) + + var buf []byte + + createNewBuffer := func() { + buf = make([]byte, udpMaxPayloadSize+1) + } + + createNewBuffer() + + for { + n, addr, err := u.pc.ReadFrom(buf) + if err != nil { + return + } + + uaddr := addr.(*net.UDPAddr) + + if !u.readIP.Equal(uaddr.IP) { + continue + } + + // in case of anyPortEnable, store the port of the first packet we receive. + // this reduces security issues + if u.c.AnyPortEnable && u.readPort == 0 { + u.readPort = uaddr.Port + } else if u.readPort != uaddr.Port { + continue + } + + now := u.c.timeNow() + atomic.StoreInt64(u.lastPacketTime, now.Unix()) + + if u.readFunc(buf[:n]) { + createNewBuffer() + } + } +} + +func (u *clientUDPListener) write(payload []byte) error { + // no mutex is needed here since Write() has an internal lock. + // https://github.com/golang/go/issues/27203#issuecomment-534386117 + u.pc.SetWriteDeadline(time.Now().Add(u.c.WriteTimeout)) + _, err := u.pc.WriteTo(payload, u.writeAddr) + return err +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/constants.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/constants.go new file mode 100644 index 000000000..9aa3da013 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/constants.go @@ -0,0 +1,9 @@ +package gortsplib + +const ( + // same size as GStreamer's rtspsrc + udpKernelReadBufferSize = 0x80000 + + // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) + udpMaxPayloadSize = 1472 +) diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/empty_timer.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/empty_timer.go new file mode 100644 index 000000000..a1e495aef --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/empty_timer.go @@ -0,0 +1,11 @@ +package gortsplib + +import ( + "time" +) + +func emptyTimer() *time.Timer { + t := time.NewTimer(0) + <-t.C + return t +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/auth.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/auth.go new file mode 100644 index 000000000..7a995fa7f --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/auth.go @@ -0,0 +1,2 @@ +// Package auth contains utilities to perform authentication. +package auth diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/nonce.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/nonce.go new file mode 100644 index 000000000..5d41cb9e9 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/nonce.go @@ -0,0 +1,17 @@ +package auth + +import ( + "crypto/rand" + "encoding/hex" +) + +// GenerateNonce generates a nonce that can be used in Validate(). +func GenerateNonce() (string, error) { + byts := make([]byte, 16) + _, err := rand.Read(byts) + if err != nil { + return "", err + } + + return hex.EncodeToString(byts), nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/sender.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/sender.go new file mode 100644 index 000000000..1efcf38fb --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/sender.go @@ -0,0 +1,89 @@ +package auth + +import ( + "fmt" + + "github.com/bluenviron/gortsplib/v4/pkg/base" + "github.com/bluenviron/gortsplib/v4/pkg/headers" +) + +// NewSender allocates a Sender. +// +// Deprecated: replaced by Sender.Initialize(). +func NewSender(wwwAuth base.HeaderValue, user string, pass string) (*Sender, error) { + s := &Sender{ + WWWAuth: wwwAuth, + User: user, + Pass: pass, + } + err := s.Initialize() + return s, err +} + +// Sender allows to send credentials. +// It requires a WWW-Authenticate header (provided by the server) +// and a set of credentials. +type Sender struct { + WWWAuth base.HeaderValue + User string + Pass string + + authHeader *headers.Authenticate +} + +// Initialize initializes a Sender. +func (se *Sender) Initialize() error { + for _, v := range se.WWWAuth { + var auth headers.Authenticate + err := auth.Unmarshal(base.HeaderValue{v}) + if err != nil { + continue // ignore unrecognized headers + } + + if se.authHeader == nil || + (auth.Algorithm != nil && *auth.Algorithm == headers.AuthAlgorithmSHA256) || + (se.authHeader.Method == headers.AuthMethodBasic) { + se.authHeader = &auth + } + } + + if se.authHeader == nil { + return fmt.Errorf("no authentication methods available") + } + + return nil +} + +// AddAuthorization adds the Authorization header to a Request. +func (se *Sender) AddAuthorization(req *base.Request) { + urStr := req.URL.CloneWithoutCredentials().String() + + h := headers.Authorization{ + Method: se.authHeader.Method, + } + + h.Username = se.User + + if se.authHeader.Method == headers.AuthMethodBasic { + h.BasicPass = se.Pass + } else { // digest + h.Realm = se.authHeader.Realm + h.Nonce = se.authHeader.Nonce + h.URI = urStr + h.Algorithm = se.authHeader.Algorithm + + if se.authHeader.Algorithm == nil || *se.authHeader.Algorithm == headers.AuthAlgorithmMD5 { + h.Response = md5Hex(md5Hex(se.User+":"+se.authHeader.Realm+":"+se.Pass) + ":" + + se.authHeader.Nonce + ":" + md5Hex(string(req.Method)+":"+urStr)) + } else { // sha256 + h.Response = sha256Hex(sha256Hex(se.User+":"+se.authHeader.Realm+":"+se.Pass) + ":" + + se.authHeader.Nonce + ":" + sha256Hex(string(req.Method)+":"+urStr)) + } + } + + if req.Header == nil { + req.Header = make(base.Header) + } + + req.Header["Authorization"] = h.Marshal() +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/validate.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/validate.go new file mode 100644 index 000000000..23acc7013 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/validate.go @@ -0,0 +1,33 @@ +package auth + +import ( + "github.com/bluenviron/gortsplib/v4/pkg/base" +) + +// ValidateMethod is a validation method. +// +// Deprecated: replaced by VerifyMethod +type ValidateMethod = VerifyMethod + +// validation methods. +// +// Deprecated. +const ( + ValidateMethodBasic = VerifyMethodBasic + ValidateMethodDigestMD5 = VerifyMethodDigestMD5 + ValidateMethodSHA256 = VerifyMethodDigestSHA256 +) + +// Validate validates a request sent by a client. +// +// Deprecated: replaced by Verify. +func Validate( + req *base.Request, + user string, + pass string, + methods []ValidateMethod, + realm string, + nonce string, +) error { + return Verify(req, user, pass, methods, realm, nonce) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/verify.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/verify.go new file mode 100644 index 000000000..af298e6fa --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/verify.go @@ -0,0 +1,135 @@ +package auth + +import ( + "crypto/md5" + "crypto/sha256" + "encoding/hex" + "fmt" + "regexp" + + "github.com/bluenviron/gortsplib/v4/pkg/base" + "github.com/bluenviron/gortsplib/v4/pkg/headers" +) + +var reControlAttribute = regexp.MustCompile("^(.+/)trackID=[0-9]+$") + +func md5Hex(in string) string { + h := md5.New() + h.Write([]byte(in)) + return hex.EncodeToString(h.Sum(nil)) +} + +func sha256Hex(in string) string { + h := sha256.New() + h.Write([]byte(in)) + return hex.EncodeToString(h.Sum(nil)) +} + +func contains(list []VerifyMethod, item VerifyMethod) bool { + for _, i := range list { + if i == item { + return true + } + } + return false +} + +func urlMatches(expected string, received string, isSetup bool) bool { + if received == expected { + return true + } + + // in SETUP requests, VLC uses the base URL of the stream + // instead of the URL of the track. + // Strip the control attribute to obtain the URL of the stream. + if isSetup { + if m := reControlAttribute.FindStringSubmatch(expected); m != nil && received == m[1] { + return true + } + } + + return false +} + +// VerifyMethod is a validation method. +type VerifyMethod int + +// validation methods. +const ( + VerifyMethodBasic VerifyMethod = iota + VerifyMethodDigestMD5 + VerifyMethodDigestSHA256 +) + +// Verify verifies a request sent by a client. +func Verify( + req *base.Request, + user string, + pass string, + methods []VerifyMethod, + realm string, + nonce string, +) error { + if methods == nil { + // disable VerifyMethodDigestSHA256 unless explicitly set + // since it prevents FFmpeg from authenticating + methods = []VerifyMethod{VerifyMethodBasic, VerifyMethodDigestMD5} + } + + var auth headers.Authorization + err := auth.Unmarshal(req.Header["Authorization"]) + if err != nil { + return err + } + + switch { + case auth.Method == headers.AuthMethodDigest && + (contains(methods, VerifyMethodDigestMD5) && + (auth.Algorithm == nil || *auth.Algorithm == headers.AuthAlgorithmMD5) || + contains(methods, VerifyMethodDigestSHA256) && + auth.Algorithm != nil && *auth.Algorithm == headers.AuthAlgorithmSHA256): + if auth.Nonce != nonce { + return fmt.Errorf("wrong nonce") + } + + if auth.Realm != realm { + return fmt.Errorf("wrong realm") + } + + if auth.Username != user { + return fmt.Errorf("authentication failed") + } + + if !urlMatches(req.URL.String(), auth.URI, req.Method == base.Setup) { + return fmt.Errorf("wrong URL") + } + + var response string + + if auth.Algorithm == nil || *auth.Algorithm == headers.AuthAlgorithmMD5 { + response = md5Hex(md5Hex(user+":"+realm+":"+pass) + + ":" + nonce + ":" + md5Hex(string(req.Method)+":"+auth.URI)) + } else { // sha256 + response = sha256Hex(sha256Hex(user+":"+realm+":"+pass) + + ":" + nonce + ":" + sha256Hex(string(req.Method)+":"+auth.URI)) + } + + if auth.Response != response { + return fmt.Errorf("authentication failed") + } + + case auth.Method == headers.AuthMethodBasic && contains(methods, VerifyMethodBasic): + if auth.Username != user { + return fmt.Errorf("authentication failed") + } + + if auth.BasicPass != pass { + return fmt.Errorf("authentication failed") + } + + default: + return fmt.Errorf("no supported authentication methods found") + } + + return nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/www_authenticate.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/www_authenticate.go new file mode 100644 index 000000000..076f3d78a --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/auth/www_authenticate.go @@ -0,0 +1,51 @@ +package auth + +import ( + "github.com/bluenviron/gortsplib/v4/pkg/base" + "github.com/bluenviron/gortsplib/v4/pkg/headers" +) + +// GenerateWWWAuthenticate generates a WWW-Authenticate header. +func GenerateWWWAuthenticate(methods []ValidateMethod, realm string, nonce string) base.HeaderValue { + if methods == nil { + // disable VerifyMethodDigestSHA256 unless explicitly set + // since it prevents FFmpeg from authenticating + methods = []VerifyMethod{VerifyMethodBasic, VerifyMethodDigestMD5} + } + + var ret base.HeaderValue + + for _, m := range methods { + var a base.HeaderValue + + switch m { + case ValidateMethodBasic: + a = headers.Authenticate{ + Method: headers.AuthMethodBasic, + Realm: realm, + }.Marshal() + + case ValidateMethodDigestMD5: + aa := headers.AuthAlgorithmMD5 + a = headers.Authenticate{ + Method: headers.AuthMethodDigest, + Realm: realm, + Nonce: nonce, + Algorithm: &aa, + }.Marshal() + + default: // sha256 + aa := headers.AuthAlgorithmSHA256 + a = headers.Authenticate{ + Method: headers.AuthMethodDigest, + Realm: realm, + Nonce: nonce, + Algorithm: &aa, + }.Marshal() + } + + ret = append(ret, a...) + } + + return ret +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/body.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/body.go new file mode 100644 index 000000000..cc47f0356 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/body.go @@ -0,0 +1,54 @@ +package base + +import ( + "bufio" + "fmt" + "io" + "strconv" +) + +const ( + rtspMaxContentLength = 128 * 1024 +) + +type body []byte + +func (b *body) unmarshal(header Header, rb *bufio.Reader) error { + cls, ok := header["Content-Length"] + if !ok || len(cls) != 1 { + *b = nil + return nil + } + + cl, err := strconv.ParseUint(cls[0], 10, 64) + if err != nil { + return fmt.Errorf("invalid Content-Length") + } + + if cl > rtspMaxContentLength { + return fmt.Errorf("Content-Length exceeds %d (it's %d)", + rtspMaxContentLength, cl) + } + + *b = make([]byte, cl) + n, err := io.ReadFull(rb, *b) + if err != nil && n != len(*b) { + return err + } + + return nil +} + +func (b body) marshalSize() int { + return len(b) +} + +func (b body) marshalTo(buf []byte) int { + return copy(buf, b) +} + +func (b body) marshal() []byte { + buf := make([]byte, b.marshalSize()) + b.marshalTo(buf) + return buf +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/header.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/header.go new file mode 100644 index 000000000..4e925fad2 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/header.go @@ -0,0 +1,148 @@ +package base + +import ( + "bufio" + "fmt" + "net/http" + "sort" + "strings" +) + +const ( + headerMaxEntryCount = 255 + headerMaxKeyLength = 512 + headerMaxValueLength = 2048 +) + +func headerKeyNormalize(in string) string { + switch strings.ToLower(in) { + case "rtp-info": + return "RTP-Info" + + case "www-authenticate": + return "WWW-Authenticate" + + case "cseq": + return "CSeq" + } + return http.CanonicalHeaderKey(in) +} + +// HeaderValue is an header value. +type HeaderValue []string + +// Header is a RTSP reader, present in both Requests and Responses. +type Header map[string]HeaderValue + +func (h *Header) unmarshal(br *bufio.Reader) error { + *h = make(Header) + count := 0 + + for { + byt, err := br.ReadByte() + if err != nil { + return err + } + + if byt == '\r' { + err = readByteEqual(br, '\n') + if err != nil { + return err + } + break + } + + if count >= headerMaxEntryCount { + return fmt.Errorf("headers count exceeds %d", headerMaxEntryCount) + } + + key := string([]byte{byt}) + byts, err := readBytesLimited(br, ':', headerMaxKeyLength-1) + if err != nil { + return fmt.Errorf("value is missing") + } + + key += string(byts[:len(byts)-1]) + key = headerKeyNormalize(key) + + // https://tools.ietf.org/html/rfc2616 + // The field value MAY be preceded by any amount of spaces + for { + byt, err = br.ReadByte() + if err != nil { + return err + } + + if byt != ' ' { + break + } + } + br.UnreadByte() //nolint:errcheck + + byts, err = readBytesLimited(br, '\r', headerMaxValueLength) + if err != nil { + return err + } + val := string(byts[:len(byts)-1]) + + err = readByteEqual(br, '\n') + if err != nil { + return err + } + + (*h)[key] = append((*h)[key], val) + count++ + } + + return nil +} + +func (h Header) marshalSize() int { + // sort headers by key + // in order to obtain deterministic results + keys := make([]string, len(h)) + for key := range h { + keys = append(keys, key) + } + sort.Strings(keys) + + n := 0 + + for _, key := range keys { + for _, val := range h[key] { + n += len([]byte(key + ": " + val + "\r\n")) + } + } + + n += 2 + + return n +} + +func (h Header) marshalTo(buf []byte) int { + // sort headers by key + // in order to obtain deterministic results + keys := make([]string, len(h)) + for key := range h { + keys = append(keys, key) + } + sort.Strings(keys) + + pos := 0 + + for _, key := range keys { + for _, val := range h[key] { + pos += copy(buf[pos:], []byte(key+": "+val+"\r\n")) + } + } + + pos += copy(buf[pos:], []byte("\r\n")) + + return pos +} + +func (h Header) marshal() []byte { + buf := make([]byte, h.marshalSize()) + h.marshalTo(buf) + return buf +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/interleaved_frame.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/interleaved_frame.go new file mode 100644 index 000000000..88aee8d0a --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/interleaved_frame.go @@ -0,0 +1,72 @@ +package base + +import ( + "bufio" + "fmt" + "io" +) + +const ( + // InterleavedFrameMagicByte is the first byte of an interleaved frame. + InterleavedFrameMagicByte = 0x24 +) + +// InterleavedFrame is an interleaved frame, and allows to transfer binary data +// within RTSP/TCP connections. It is used to send and receive RTP and RTCP packets with TCP. +type InterleavedFrame struct { + // channel ID + Channel int + + // payload + Payload []byte +} + +// Unmarshal decodes an interleaved frame. +func (f *InterleavedFrame) Unmarshal(br *bufio.Reader) error { + var header [4]byte + _, err := io.ReadFull(br, header[:]) + if err != nil { + return err + } + + if header[0] != InterleavedFrameMagicByte { + return fmt.Errorf("invalid magic byte (0x%.2x)", header[0]) + } + + // it's useless to check payloadLen since it's limited to 65535 + payloadLen := int(uint16(header[2])<<8 | uint16(header[3])) + + f.Channel = int(header[1]) + f.Payload = make([]byte, payloadLen) + + _, err = io.ReadFull(br, f.Payload) + return err +} + +// MarshalSize returns the size of an InterleavedFrame. +func (f InterleavedFrame) MarshalSize() int { + return 4 + len(f.Payload) +} + +// MarshalTo writes an InterleavedFrame. +func (f InterleavedFrame) MarshalTo(buf []byte) (int, error) { + pos := 0 + + pos += copy(buf[pos:], []byte{0x24, byte(f.Channel)}) + + payloadLen := len(f.Payload) + buf[pos] = byte(payloadLen >> 8) + buf[pos+1] = byte(payloadLen) + pos += 2 + + pos += copy(buf[pos:], f.Payload) + + return pos, nil +} + +// Marshal writes an InterleavedFrame. +func (f InterleavedFrame) Marshal() ([]byte, error) { + buf := make([]byte, f.MarshalSize()) + _, err := f.MarshalTo(buf) + return buf, err +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/path.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/path.go new file mode 100644 index 000000000..537adc2c1 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/path.go @@ -0,0 +1,17 @@ +package base + +import ( + "strings" +) + +// PathSplitQuery splits a path from a query. +// +// Deprecated: not useful anymore. +func PathSplitQuery(pathAndQuery string) (string, string) { + i := strings.Index(pathAndQuery, "?") + if i >= 0 { + return pathAndQuery[:i], pathAndQuery[i+1:] + } + + return pathAndQuery, "" +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/request.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/request.go new file mode 100644 index 000000000..eb5c5a386 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/request.go @@ -0,0 +1,173 @@ +// Package base contains the primitives of the RTSP protocol. +package base + +import ( + "bufio" + "fmt" + "strconv" +) + +const ( + rtspProtocol10 = "RTSP/1.0" + requestMaxMethodLength = 64 + requestMaxURLLength = 2048 + requestMaxProtocolLength = 64 +) + +// Method is the method of a RTSP request. +type Method string + +// methods. +const ( + Announce Method = "ANNOUNCE" + Describe Method = "DESCRIBE" + GetParameter Method = "GET_PARAMETER" + Options Method = "OPTIONS" + Pause Method = "PAUSE" + Play Method = "PLAY" + Record Method = "RECORD" + Setup Method = "SETUP" + SetParameter Method = "SET_PARAMETER" + Teardown Method = "TEARDOWN" +) + +// Request is a RTSP request. +type Request struct { + // request method + Method Method + + // request url + URL *URL + + // map of header values + Header Header + + // optional body + Body []byte +} + +// Unmarshal reads a request. +func (req *Request) Unmarshal(br *bufio.Reader) error { + byts, err := readBytesLimited(br, ' ', requestMaxMethodLength) + if err != nil { + return err + } + req.Method = Method(byts[:len(byts)-1]) + + if req.Method == "" { + return fmt.Errorf("empty method") + } + + byts, err = readBytesLimited(br, ' ', requestMaxURLLength) + if err != nil { + return err + } + rawURL := string(byts[:len(byts)-1]) + + if rawURL != "*" { + var ur *URL + ur, err = ParseURL(rawURL) + if err != nil { + return fmt.Errorf("invalid URL (%v)", rawURL) + } + req.URL = ur + } else { + req.URL = nil + } + + byts, err = readBytesLimited(br, '\r', requestMaxProtocolLength) + if err != nil { + return err + } + proto := byts[:len(byts)-1] + + if string(proto) != rtspProtocol10 { + return fmt.Errorf("expected '%s', got %v", rtspProtocol10, proto) + } + + err = readByteEqual(br, '\n') + if err != nil { + return err + } + + err = req.Header.unmarshal(br) + if err != nil { + return err + } + + err = (*body)(&req.Body).unmarshal(req.Header, br) + if err != nil { + return err + } + + return nil +} + +// MarshalSize returns the size of a Request. +func (req Request) MarshalSize() int { + n := len(req.Method) + 1 + + if req.URL != nil { + n += len(req.URL.CloneWithoutCredentials().String()) + } else { + n++ + } + + n += 1 + len(rtspProtocol10) + 2 + + if len(req.Body) != 0 { + req.Header["Content-Length"] = HeaderValue{strconv.FormatInt(int64(len(req.Body)), 10)} + } + + n += req.Header.marshalSize() + + n += body(req.Body).marshalSize() + + return n +} + +// MarshalTo writes a Request. +func (req Request) MarshalTo(buf []byte) (int, error) { + pos := 0 + + pos += copy(buf[pos:], []byte(req.Method)) + buf[pos] = ' ' + pos++ + + if req.URL != nil { + pos += copy(buf[pos:], []byte(req.URL.CloneWithoutCredentials().String())) + } else { + pos += copy(buf[pos:], []byte("*")) + } + + buf[pos] = ' ' + pos++ + pos += copy(buf[pos:], rtspProtocol10) + buf[pos] = '\r' + pos++ + buf[pos] = '\n' + pos++ + + if len(req.Body) != 0 { + req.Header["Content-Length"] = HeaderValue{strconv.FormatInt(int64(len(req.Body)), 10)} + } + + pos += req.Header.marshalTo(buf[pos:]) + + pos += body(req.Body).marshalTo(buf[pos:]) + + return pos, nil +} + +// Marshal writes a Request. +func (req Request) Marshal() ([]byte, error) { + buf := make([]byte, req.MarshalSize()) + _, err := req.MarshalTo(buf) + return buf, err +} + +// String implements fmt.Stringer. +func (req Request) String() string { + buf, _ := req.Marshal() + return string(buf) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/response.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/response.go new file mode 100644 index 000000000..8f6b4ced3 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/response.go @@ -0,0 +1,254 @@ +package base + +import ( + "bufio" + "fmt" + "strconv" +) + +// StatusCode is the status code of a RTSP response. +type StatusCode int + +// status codes. +const ( + StatusContinue StatusCode = 100 + StatusOK StatusCode = 200 + StatusMovedPermanently StatusCode = 301 + StatusFound StatusCode = 302 + StatusSeeOther StatusCode = 303 + StatusNotModified StatusCode = 304 + StatusUseProxy StatusCode = 305 + StatusBadRequest StatusCode = 400 + StatusUnauthorized StatusCode = 401 + StatusPaymentRequired StatusCode = 402 + StatusForbidden StatusCode = 403 + StatusNotFound StatusCode = 404 + StatusMethodNotAllowed StatusCode = 405 + StatusNotAcceptable StatusCode = 406 + StatusProxyAuthRequired StatusCode = 407 + StatusRequestTimeout StatusCode = 408 + StatusGone StatusCode = 410 + StatusPreconditionFailed StatusCode = 412 + StatusRequestEntityTooLarge StatusCode = 413 + StatusRequestURITooLong StatusCode = 414 + StatusUnsupportedMediaType StatusCode = 415 + StatusParameterNotUnderstood StatusCode = 451 + StatusNotEnoughBandwidth StatusCode = 453 + StatusSessionNotFound StatusCode = 454 + StatusMethodNotValidInThisState StatusCode = 455 + StatusHeaderFieldNotValidForResource StatusCode = 456 + StatusInvalidRange StatusCode = 457 + StatusParameterIsReadOnly StatusCode = 458 + StatusAggregateOperationNotAllowed StatusCode = 459 + StatusOnlyAggregateOperationAllowed StatusCode = 460 + StatusUnsupportedTransport StatusCode = 461 + StatusDestinationUnreachable StatusCode = 462 + StatusDestinationProhibited StatusCode = 463 + StatusDataTransportNotReadyYet StatusCode = 464 + StatusNotificationReasonUnknown StatusCode = 465 + StatusKeyManagementError StatusCode = 466 + StatusConnectionAuthorizationRequired StatusCode = 470 + StatusConnectionCredentialsNotAccepted StatusCode = 471 + StatusFailureToEstablishSecureConnection StatusCode = 472 + StatusInternalServerError StatusCode = 500 + StatusNotImplemented StatusCode = 501 + StatusBadGateway StatusCode = 502 + StatusServiceUnavailable StatusCode = 503 + StatusGatewayTimeout StatusCode = 504 + StatusRTSPVersionNotSupported StatusCode = 505 + StatusOptionNotSupported StatusCode = 551 + StatusProxyUnavailable StatusCode = 553 +) + +// StatusMessages contains the status messages associated with each status code. +var StatusMessages = statusMessages + +var statusMessages = map[StatusCode]string{ + StatusContinue: "Continue", + + StatusOK: "OK", + + StatusMovedPermanently: "Moved Permanently", + StatusFound: "Found", + StatusSeeOther: "See Other", + StatusNotModified: "Not Modified", + StatusUseProxy: "Use Proxy", + + StatusBadRequest: "Bad Request", + StatusUnauthorized: "Unauthorized", + StatusPaymentRequired: "Payment Required", + StatusForbidden: "Forbidden", + StatusNotFound: "Not Found", + StatusMethodNotAllowed: "Method Not Allowed", + StatusNotAcceptable: "Not Acceptable", + StatusProxyAuthRequired: "Proxy Auth Required", + StatusRequestTimeout: "Request Timeout", + StatusGone: "Gone", + StatusPreconditionFailed: "Precondition Failed", + StatusRequestEntityTooLarge: "Request Entity Too Large", + StatusRequestURITooLong: "Request URI Too Long", + StatusUnsupportedMediaType: "Unsupported Media Type", + StatusParameterNotUnderstood: "Parameter Not Understood", + StatusNotEnoughBandwidth: "Not Enough Bandwidth", + StatusSessionNotFound: "Session Not Found", + StatusMethodNotValidInThisState: "Method Not Valid In This State", + StatusHeaderFieldNotValidForResource: "Header Field Not Valid for Resource", + StatusInvalidRange: "Invalid Range", + StatusParameterIsReadOnly: "Parameter Is Read-Only", + StatusAggregateOperationNotAllowed: "Aggregate Operation Not Allowed", + StatusOnlyAggregateOperationAllowed: "Only Aggregate Operation Allowed", + StatusUnsupportedTransport: "Unsupported Transport", + StatusDestinationUnreachable: "Destination Unreachable", + StatusDestinationProhibited: "Destination Prohibited", + StatusDataTransportNotReadyYet: "Data Transport Not Ready Yet", + StatusNotificationReasonUnknown: "Notification Reason Unknown", + StatusKeyManagementError: "Key Management Error", + StatusConnectionAuthorizationRequired: "Connection Authorization Required", + StatusConnectionCredentialsNotAccepted: "Connection Credentials Not Accepted", + StatusFailureToEstablishSecureConnection: "Failure to Establish Secure Connection", + + StatusInternalServerError: "Internal Server Error", + StatusNotImplemented: "Not Implemented", + StatusBadGateway: "Bad Gateway", + StatusServiceUnavailable: "Service Unavailable", + StatusGatewayTimeout: "Gateway Timeout", + StatusRTSPVersionNotSupported: "RTSP Version Not Supported", + StatusOptionNotSupported: "Option Not Supported", + StatusProxyUnavailable: "Proxy Unavailable", +} + +// Response is a RTSP response. +type Response struct { + // numeric status code + StatusCode StatusCode + + // status message + StatusMessage string + + // map of header values + Header Header + + // optional body + Body []byte +} + +// Unmarshal reads a response. +func (res *Response) Unmarshal(br *bufio.Reader) error { + byts, err := readBytesLimited(br, ' ', 255) + if err != nil { + return err + } + proto := byts[:len(byts)-1] + + if string(proto) != rtspProtocol10 { + return fmt.Errorf("expected '%s', got %v", rtspProtocol10, proto) + } + + byts, err = readBytesLimited(br, ' ', 4) + if err != nil { + return err + } + statusCodeStr := string(byts[:len(byts)-1]) + + tmp, err := strconv.ParseUint(statusCodeStr, 10, 31) + if err != nil { + return fmt.Errorf("unable to parse status code") + } + res.StatusCode = StatusCode(tmp) + + byts, err = readBytesLimited(br, '\r', 255) + if err != nil { + return err + } + res.StatusMessage = string(byts[:len(byts)-1]) + + if len(res.StatusMessage) == 0 { + return fmt.Errorf("empty status message") + } + + err = readByteEqual(br, '\n') + if err != nil { + return err + } + + err = res.Header.unmarshal(br) + if err != nil { + return err + } + + err = (*body)(&res.Body).unmarshal(res.Header, br) + if err != nil { + return err + } + + return nil +} + +// MarshalSize returns the size of a Response. +func (res Response) MarshalSize() int { + n := 0 + + if res.StatusMessage == "" { + if status, ok := statusMessages[res.StatusCode]; ok { + res.StatusMessage = status + } + } + + n += len(rtspProtocol10) + 1 + len(strconv.FormatInt(int64(res.StatusCode), 10)) + 1 + len(res.StatusMessage) + 2 + + if len(res.Body) != 0 { + res.Header["Content-Length"] = HeaderValue{strconv.FormatInt(int64(len(res.Body)), 10)} + } + + n += res.Header.marshalSize() + + n += body(res.Body).marshalSize() + + return n +} + +// MarshalTo writes a Response. +func (res Response) MarshalTo(buf []byte) (int, error) { + if res.StatusMessage == "" { + if status, ok := statusMessages[res.StatusCode]; ok { + res.StatusMessage = status + } + } + + pos := 0 + + pos += copy(buf[pos:], []byte(rtspProtocol10)) + buf[pos] = ' ' + pos++ + pos += copy(buf[pos:], []byte(strconv.FormatInt(int64(res.StatusCode), 10))) + buf[pos] = ' ' + pos++ + pos += copy(buf[pos:], []byte(res.StatusMessage)) + buf[pos] = '\r' + pos++ + buf[pos] = '\n' + pos++ + + if len(res.Body) != 0 { + res.Header["Content-Length"] = HeaderValue{strconv.FormatInt(int64(len(res.Body)), 10)} + } + + pos += res.Header.marshalTo(buf[pos:]) + + pos += body(res.Body).marshalTo(buf[pos:]) + + return pos, nil +} + +// Marshal writes a Response. +func (res Response) Marshal() ([]byte, error) { + buf := make([]byte, res.MarshalSize()) + _, err := res.MarshalTo(buf) + return buf, err +} + +// String implements fmt.Stringer. +func (res Response) String() string { + buf, _ := res.Marshal() + return string(buf) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/url.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/url.go new file mode 100644 index 000000000..5f9154532 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/url.go @@ -0,0 +1,107 @@ +package base + +import ( + "fmt" + "net/url" + "regexp" + "strings" +) + +// URL is a RTSP URL. +// This is basically an HTTP URL with some additional functions to handle +// control attributes. +type URL url.URL + +var escapeRegexp = regexp.MustCompile(`^(.+?)://(.*?)@(.*?)/(.*?)$`) + +// ParseURL parses a RTSP URL. +func ParseURL(s string) (*URL, error) { + // https://github.com/golang/go/issues/30611 + m := escapeRegexp.FindStringSubmatch(s) + if m != nil { + m[3] = strings.ReplaceAll(m[3], "%25", "%") + m[3] = strings.ReplaceAll(m[3], "%", "%25") + s = m[1] + "://" + m[2] + "@" + m[3] + "/" + m[4] + } + + u, err := url.Parse(s) + if err != nil { + return nil, err + } + + if u.Scheme != "rtsp" && u.Scheme != "rtsps" { + return nil, fmt.Errorf("unsupported scheme '%s'", u.Scheme) + } + + if u.Opaque != "" { + return nil, fmt.Errorf("URLs with opaque data are not supported") + } + + if u.Fragment != "" { + return nil, fmt.Errorf("URLs with fragments are not supported") + } + + return (*URL)(u), nil +} + +// String implements fmt.Stringer. +func (u *URL) String() string { + return (*url.URL)(u).String() +} + +// Clone clones a URL. +func (u *URL) Clone() *URL { + return (*URL)(&url.URL{ + Scheme: u.Scheme, + User: u.User, + Host: u.Host, + Path: u.Path, + RawPath: u.RawPath, + ForceQuery: u.ForceQuery, + RawQuery: u.RawQuery, + }) +} + +// CloneWithoutCredentials clones a URL without its credentials. +func (u *URL) CloneWithoutCredentials() *URL { + return (*URL)(&url.URL{ + Scheme: u.Scheme, + Host: u.Host, + Path: u.Path, + RawPath: u.RawPath, + ForceQuery: u.ForceQuery, + RawQuery: u.RawQuery, + }) +} + +// RTSPPathAndQuery returns the path and query of a RTSP URL. +// +// Deprecated: not useful anymore. +func (u *URL) RTSPPathAndQuery() (string, bool) { + var pathAndQuery string + if u.RawPath != "" { + pathAndQuery = u.RawPath + } else { + pathAndQuery = u.Path + } + if u.RawQuery != "" { + pathAndQuery += "?" + u.RawQuery + } + + return pathAndQuery, true +} + +// Hostname returns u.Host, stripping any valid port number if present. +// +// If the result is enclosed in square brackets, as literal IPv6 addresses are, +// the square brackets are removed from the result. +func (u *URL) Hostname() string { + return (*url.URL)(u).Hostname() +} + +// Port returns the port part of u.Host, without the leading colon. +// +// If u.Host doesn't contain a valid numeric port, Port returns an empty string. +func (u *URL) Port() string { + return (*url.URL)(u).Port() +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/utils.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/utils.go new file mode 100644 index 000000000..d0c43dc2b --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/base/utils.go @@ -0,0 +1,34 @@ +package base + +import ( + "bufio" + "fmt" +) + +func readByteEqual(rb *bufio.Reader, cmp byte) error { + byt, err := rb.ReadByte() + if err != nil { + return err + } + + if byt != cmp { + return fmt.Errorf("expected '%c', got '%c'", cmp, byt) + } + + return nil +} + +func readBytesLimited(rb *bufio.Reader, delim byte, n int) ([]byte, error) { + for i := 1; i <= n; i++ { + byts, err := rb.Peek(i) + if err != nil { + return nil, err + } + + if byts[len(byts)-1] == delim { + rb.Discard(len(byts)) //nolint:errcheck + return byts, nil + } + } + return nil, fmt.Errorf("buffer length exceeds %d", n) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/bytecounter/bytecounter.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/bytecounter/bytecounter.go new file mode 100644 index 000000000..abd59d97f --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/bytecounter/bytecounter.go @@ -0,0 +1,54 @@ +// Package bytecounter contains a io.ReadWriter wrapper that allows to count read and written bytes. +package bytecounter + +import ( + "io" + "sync/atomic" +) + +// ByteCounter is a io.ReadWriter wrapper that allows to count read and written bytes. +type ByteCounter struct { + rw io.ReadWriter + received *uint64 + sent *uint64 +} + +// New allocates a ByteCounter. +func New(rw io.ReadWriter, received *uint64, sent *uint64) *ByteCounter { + if received == nil { + received = new(uint64) + } + if sent == nil { + sent = new(uint64) + } + + return &ByteCounter{ + rw: rw, + received: received, + sent: sent, + } +} + +// Read implements io.ReadWriter. +func (bc *ByteCounter) Read(p []byte) (int, error) { + n, err := bc.rw.Read(p) + atomic.AddUint64(bc.received, uint64(n)) + return n, err +} + +// Write implements io.ReadWriter. +func (bc *ByteCounter) Write(p []byte) (int, error) { + n, err := bc.rw.Write(p) + atomic.AddUint64(bc.sent, uint64(n)) + return n, err +} + +// BytesReceived returns the number of bytes received. +func (bc *ByteCounter) BytesReceived() uint64 { + return atomic.LoadUint64(bc.received) +} + +// BytesSent returns the number of bytes sent. +func (bc *ByteCounter) BytesSent() uint64 { + return atomic.LoadUint64(bc.sent) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/conn/conn.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/conn/conn.go new file mode 100644 index 000000000..2fc08eec9 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/conn/conn.go @@ -0,0 +1,105 @@ +// Package conn contains a RTSP connection implementation. +package conn + +import ( + "bufio" + "io" + + "github.com/bluenviron/gortsplib/v4/pkg/base" +) + +const ( + readBufferSize = 4096 +) + +// Conn is a RTSP connection. +type Conn struct { + w io.Writer + br *bufio.Reader + + // reuse interleaved frames. they should never be passed to secondary routines + fr base.InterleavedFrame +} + +// NewConn allocates a Conn. +func NewConn(rw io.ReadWriter) *Conn { + return &Conn{ + w: rw, + br: bufio.NewReaderSize(rw, readBufferSize), + } +} + +// Read reads a Request, a Response or an Interleaved frame. +func (c *Conn) Read() (interface{}, error) { + for { + byts, err := c.br.Peek(2) + if err != nil { + return nil, err + } + + if byts[0] == base.InterleavedFrameMagicByte { + return c.ReadInterleavedFrame() + } + + if byts[0] == 'R' && byts[1] == 'T' { + return c.ReadResponse() + } + + if (byts[0] == 'A' && byts[1] == 'N') || + (byts[0] == 'D' && byts[1] == 'E') || + (byts[0] == 'G' && byts[1] == 'E') || + (byts[0] == 'O' && byts[1] == 'P') || + (byts[0] == 'P' && byts[1] == 'A') || + (byts[0] == 'P' && byts[1] == 'L') || + (byts[0] == 'R' && byts[1] == 'E') || + (byts[0] == 'S' && byts[1] == 'E') || + (byts[0] == 'T' && byts[1] == 'E') { + return c.ReadRequest() + } + + if _, err := c.br.Discard(1); err != nil { + return nil, err + } + } +} + +// ReadRequest reads a Request. +func (c *Conn) ReadRequest() (*base.Request, error) { + var req base.Request + err := req.Unmarshal(c.br) + return &req, err +} + +// ReadResponse reads a Response. +func (c *Conn) ReadResponse() (*base.Response, error) { + var res base.Response + err := res.Unmarshal(c.br) + return &res, err +} + +// ReadInterleavedFrame reads a InterleavedFrame. +func (c *Conn) ReadInterleavedFrame() (*base.InterleavedFrame, error) { + err := c.fr.Unmarshal(c.br) + return &c.fr, err +} + +// WriteRequest writes a request. +func (c *Conn) WriteRequest(req *base.Request) error { + buf, _ := req.Marshal() + _, err := c.w.Write(buf) + return err +} + +// WriteResponse writes a response. +func (c *Conn) WriteResponse(res *base.Response) error { + buf, _ := res.Marshal() + _, err := c.w.Write(buf) + return err +} + +// WriteInterleavedFrame writes an interleaved frame. +func (c *Conn) WriteInterleavedFrame(fr *base.InterleavedFrame, buf []byte) error { + n, _ := fr.MarshalTo(buf) + _, err := c.w.Write(buf[:n]) + return err +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/description/media.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/description/media.go new file mode 100644 index 000000000..2e2d9540c --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/description/media.go @@ -0,0 +1,218 @@ +// Package description contains objects to describe streams. +package description + +import ( + "fmt" + "reflect" + "sort" + "strconv" + "strings" + "unicode" + + psdp "github.com/pion/sdp/v3" + + "github.com/bluenviron/gortsplib/v4/pkg/base" + "github.com/bluenviron/gortsplib/v4/pkg/format" +) + +func getAttribute(attributes []psdp.Attribute, key string) string { + for _, attr := range attributes { + if attr.Key == key { + return attr.Value + } + } + return "" +} + +func isBackChannel(attributes []psdp.Attribute) bool { + for _, attr := range attributes { + if attr.Key == "sendonly" { + return true + } + } + return false +} + +func sortedKeys(fmtp map[string]string) []string { + keys := make([]string, len(fmtp)) + i := 0 + for key := range fmtp { + keys[i] = key + i++ + } + sort.Strings(keys) + return keys +} + +func isAlphaNumeric(v string) bool { + for _, r := range v { + if !unicode.IsLetter(r) && !unicode.IsNumber(r) { + return false + } + } + return true +} + +// MediaType is the type of a media stream. +type MediaType string + +// media types. +const ( + MediaTypeVideo MediaType = "video" + MediaTypeAudio MediaType = "audio" + MediaTypeApplication MediaType = "application" +) + +// Media is a media stream. +// It contains one or more formats. +type Media struct { + // Media type. + Type MediaType + + // Media ID (optional). + ID string + + // Whether this media is a back channel. + IsBackChannel bool + + // Control attribute. + Control string + + // Formats contained into the media. + Formats []format.Format +} + +// Unmarshal decodes the media from the SDP format. +func (m *Media) Unmarshal(md *psdp.MediaDescription) error { + m.Type = MediaType(md.MediaName.Media) + + m.ID = getAttribute(md.Attributes, "mid") + if m.ID != "" && !isAlphaNumeric(m.ID) { + return fmt.Errorf("invalid mid: %v", m.ID) + } + + m.IsBackChannel = isBackChannel(md.Attributes) + m.Control = getAttribute(md.Attributes, "control") + + m.Formats = nil + + for _, payloadType := range md.MediaName.Formats { + format, err := format.Unmarshal(md, payloadType) + if err != nil { + return err + } + + m.Formats = append(m.Formats, format) + } + + if m.Formats == nil { + return fmt.Errorf("no formats found") + } + + return nil +} + +// Marshal encodes the media in SDP format. +func (m Media) Marshal() *psdp.MediaDescription { + md := &psdp.MediaDescription{ + MediaName: psdp.MediaName{ + Media: string(m.Type), + Protos: []string{"RTP", "AVP"}, + }, + } + + if m.ID != "" { + md.Attributes = append(md.Attributes, psdp.Attribute{ + Key: "mid", + Value: m.ID, + }) + } + + if m.IsBackChannel { + md.Attributes = append(md.Attributes, psdp.Attribute{ + Key: "sendonly", + }) + } + + md.Attributes = append(md.Attributes, psdp.Attribute{ + Key: "control", + Value: m.Control, + }) + + for _, forma := range m.Formats { + typ := strconv.FormatUint(uint64(forma.PayloadType()), 10) + md.MediaName.Formats = append(md.MediaName.Formats, typ) + + rtpmap := forma.RTPMap() + if rtpmap != "" { + md.Attributes = append(md.Attributes, psdp.Attribute{ + Key: "rtpmap", + Value: typ + " " + rtpmap, + }) + } + + fmtp := forma.FMTP() + if len(fmtp) != 0 { + tmp := make([]string, len(fmtp)) + for i, key := range sortedKeys(fmtp) { + tmp[i] = key + "=" + fmtp[key] + } + + md.Attributes = append(md.Attributes, psdp.Attribute{ + Key: "fmtp", + Value: typ + " " + strings.Join(tmp, "; "), + }) + } + } + + return md +} + +// URL returns the absolute URL of the media. +func (m Media) URL(contentBase *base.URL) (*base.URL, error) { + if contentBase == nil { + return nil, fmt.Errorf("Content-Base header not provided") + } + + // no control attribute, use base URL + if m.Control == "" { + return contentBase, nil + } + + // control attribute contains an absolute path + if strings.HasPrefix(m.Control, "rtsp://") || + strings.HasPrefix(m.Control, "rtsps://") { + ur, err := base.ParseURL(m.Control) + if err != nil { + return nil, err + } + + // copy host and credentials + ur.Host = contentBase.Host + ur.User = contentBase.User + return ur, nil + } + + // control attribute contains a relative control attribute + // insert the control attribute at the end of the URL + // if there's a query, insert it after the query + // otherwise insert it after the path + strURL := contentBase.String() + if m.Control[0] != '?' && m.Control[0] != '/' && !strings.HasSuffix(strURL, "/") { + strURL += "/" + } + + ur, _ := base.ParseURL(strURL + m.Control) + return ur, nil +} + +// FindFormat finds a certain format among all the formats in the media. +func (m Media) FindFormat(forma interface{}) bool { + for _, formak := range m.Formats { + if reflect.TypeOf(formak) == reflect.TypeOf(forma).Elem() { + reflect.ValueOf(forma).Elem().Set(reflect.ValueOf(formak)) + return true + } + } + return false +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/description/session.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/description/session.go new file mode 100644 index 000000000..4a77f1cd8 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/description/session.go @@ -0,0 +1,164 @@ +package description + +import ( + "fmt" + "strings" + + psdp "github.com/pion/sdp/v3" + + "github.com/bluenviron/gortsplib/v4/pkg/base" + "github.com/bluenviron/gortsplib/v4/pkg/sdp" +) + +func atLeastOneHasMID(medias []*Media) bool { + for _, media := range medias { + if media.ID != "" { + return true + } + } + return false +} + +func atLeastOneDoesntHaveMID(medias []*Media) bool { + for _, media := range medias { + if media.ID == "" { + return true + } + } + return false +} + +func hasMediaWithID(medias []*Media, id string) bool { + for _, media := range medias { + if media.ID == id { + return true + } + } + return false +} + +// SessionFECGroup is a FEC group. +type SessionFECGroup []string + +// Session is the description of a RTSP stream. +type Session struct { + // Base URL of the stream (read only). + BaseURL *base.URL + + // Title of the stream (optional). + Title string + + // FEC groups (RFC5109). + FECGroups []SessionFECGroup + + // Media streams. + Medias []*Media +} + +// FindFormat finds a certain format among all the formats in all the medias of the stream. +// If the format is found, it is inserted into forma, and its media is returned. +func (d *Session) FindFormat(forma interface{}) *Media { + for _, media := range d.Medias { + ok := media.FindFormat(forma) + if ok { + return media + } + } + return nil +} + +// Unmarshal decodes the description from SDP. +func (d *Session) Unmarshal(ssd *sdp.SessionDescription) error { + d.Title = string(ssd.SessionName) + if d.Title == " " { + d.Title = "" + } + + d.Medias = make([]*Media, len(ssd.MediaDescriptions)) + + for i, md := range ssd.MediaDescriptions { + var m Media + err := m.Unmarshal(md) + if err != nil { + return fmt.Errorf("media %d is invalid: %w", i+1, err) + } + + if m.ID != "" && hasMediaWithID(d.Medias[:i], m.ID) { + return fmt.Errorf("duplicate media IDs") + } + + d.Medias[i] = &m + } + + if atLeastOneHasMID(d.Medias) && atLeastOneDoesntHaveMID(d.Medias) { + return fmt.Errorf("media IDs sent partially") + } + + for _, attr := range ssd.Attributes { + if attr.Key == "group" && strings.HasPrefix(attr.Value, "FEC ") { + group := SessionFECGroup(strings.Split(attr.Value[len("FEC "):], " ")) + + for _, id := range group { + if !hasMediaWithID(d.Medias, id) { + return fmt.Errorf("FEC group points to an invalid media ID: %v", id) + } + } + + d.FECGroups = append(d.FECGroups, group) + } + } + + return nil +} + +// Marshal encodes the description in SDP. +func (d Session) Marshal(multicast bool) ([]byte, error) { + var sessionName psdp.SessionName + if d.Title != "" { + sessionName = psdp.SessionName(d.Title) + } else { + // RFC 4566: If a session has no meaningful name, the + // value "s= " SHOULD be used (i.e., a single space as the session name). + sessionName = psdp.SessionName(" ") + } + + var address string + if multicast { + address = "224.1.0.0" + } else { + address = "0.0.0.0" + } + + sout := &sdp.SessionDescription{ + SessionName: sessionName, + Origin: psdp.Origin{ + Username: "-", + NetworkType: "IN", + AddressType: "IP4", + UnicastAddress: "127.0.0.1", + }, + // required by Darwin Streaming Server + ConnectionInformation: &psdp.ConnectionInformation{ + NetworkType: "IN", + AddressType: "IP4", + Address: &psdp.Address{Address: address}, + }, + TimeDescriptions: []psdp.TimeDescription{ + {Timing: psdp.Timing{StartTime: 0, StopTime: 0}}, + }, + MediaDescriptions: make([]*psdp.MediaDescription, len(d.Medias)), + } + + for i, media := range d.Medias { + sout.MediaDescriptions[i] = media.Marshal() + } + + for _, group := range d.FECGroups { + sout.Attributes = append(sout.Attributes, psdp.Attribute{ + Key: "group", + Value: "FEC " + strings.Join(group, " "), + }) + } + + return sout.Marshal() +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/ac3.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/ac3.go new file mode 100644 index 000000000..3253202a9 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/ac3.go @@ -0,0 +1,101 @@ +package format + +import ( + "strconv" + "strings" + + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpac3" +) + +// AC3 is the RTP format for the AC-3 codec. +// Specification: https://datatracker.ietf.org/doc/html/rfc4184 +type AC3 struct { + PayloadTyp uint8 + SampleRate int + ChannelCount int +} + +func (f *AC3) unmarshal(ctx *unmarshalContext) error { + f.PayloadTyp = ctx.payloadType + + tmp := strings.SplitN(ctx.clock, "/", 2) + + tmp1, err := strconv.ParseUint(tmp[0], 10, 31) + if err != nil { + return err + } + f.SampleRate = int(tmp1) + + if len(tmp) >= 2 { + tmp1, err := strconv.ParseUint(tmp[1], 10, 31) + if err != nil { + return err + } + f.ChannelCount = int(tmp1) + } else { + // RFC4184: If the "channels" parameter + // is omitted, a default maximum value of 6 is implied. + f.ChannelCount = 6 + } + + return nil +} + +// Codec implements Format. +func (f *AC3) Codec() string { + return "AC-3" +} + +// ClockRate implements Format. +func (f *AC3) ClockRate() int { + return f.SampleRate +} + +// PayloadType implements Format. +func (f *AC3) PayloadType() uint8 { + return f.PayloadTyp +} + +// RTPMap implements Format. +func (f *AC3) RTPMap() string { + return "AC3/" + strconv.FormatInt(int64(f.SampleRate), 10) + + "/" + strconv.FormatInt(int64(f.ChannelCount), 10) +} + +// FMTP implements Format. +func (f *AC3) FMTP() map[string]string { + return nil +} + +// PTSEqualsDTS implements Format. +func (f *AC3) PTSEqualsDTS(*rtp.Packet) bool { + return true +} + +// CreateDecoder creates a decoder able to decode the content of the format. +func (f *AC3) CreateDecoder() (*rtpac3.Decoder, error) { + d := &rtpac3.Decoder{} + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + +// CreateEncoder creates an encoder able to encode the content of the format. +func (f *AC3) CreateEncoder() (*rtpac3.Encoder, error) { + e := &rtpac3.Encoder{ + PayloadType: f.PayloadTyp, + } + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/av1.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/av1.go new file mode 100644 index 000000000..7b311b1ec --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/av1.go @@ -0,0 +1,124 @@ +package format //nolint:dupl + +import ( + "fmt" + "strconv" + + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1" +) + +// AV1 is the RTP format for the AV1 codec. +// Specification: https://aomediacodec.github.io/av1-rtp-spec/ +type AV1 struct { + PayloadTyp uint8 + LevelIdx *int + Profile *int + Tier *int +} + +func (f *AV1) unmarshal(ctx *unmarshalContext) error { + f.PayloadTyp = ctx.payloadType + + for key, val := range ctx.fmtp { + switch key { + case "level-idx": + n, err := strconv.ParseUint(val, 10, 31) + if err != nil { + return fmt.Errorf("invalid level-idx: %v", val) + } + + v2 := int(n) + f.LevelIdx = &v2 + + case "profile": + n, err := strconv.ParseUint(val, 10, 31) + if err != nil { + return fmt.Errorf("invalid profile: %v", val) + } + + v2 := int(n) + f.Profile = &v2 + + case "tier": + n, err := strconv.ParseUint(val, 10, 31) + if err != nil { + return fmt.Errorf("invalid tier: %v", val) + } + + v2 := int(n) + f.Tier = &v2 + } + } + + return nil +} + +// Codec implements Format. +func (f *AV1) Codec() string { + return "AV1" +} + +// ClockRate implements Format. +func (f *AV1) ClockRate() int { + return 90000 +} + +// PayloadType implements Format. +func (f *AV1) PayloadType() uint8 { + return f.PayloadTyp +} + +// RTPMap implements Format. +func (f *AV1) RTPMap() string { + return "AV1/90000" +} + +// FMTP implements Format. +func (f *AV1) FMTP() map[string]string { + fmtp := make(map[string]string) + + if f.LevelIdx != nil { + fmtp["level-idx"] = strconv.FormatInt(int64(*f.LevelIdx), 10) + } + if f.Profile != nil { + fmtp["profile"] = strconv.FormatInt(int64(*f.Profile), 10) + } + if f.Tier != nil { + fmtp["tier"] = strconv.FormatInt(int64(*f.Tier), 10) + } + + return fmtp +} + +// PTSEqualsDTS implements Format. +func (f *AV1) PTSEqualsDTS(*rtp.Packet) bool { + return true +} + +// CreateDecoder creates a decoder able to decode the content of the format. +func (f *AV1) CreateDecoder() (*rtpav1.Decoder, error) { + d := &rtpav1.Decoder{} + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + +// CreateEncoder creates an encoder able to encode the content of the format. +func (f *AV1) CreateEncoder() (*rtpav1.Encoder, error) { + e := &rtpav1.Encoder{ + PayloadType: f.PayloadTyp, + } + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/format.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/format.go new file mode 100644 index 000000000..b63eb78ad --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/format.go @@ -0,0 +1,234 @@ +// Package format contains RTP format definitions, decoders and encoders. +package format + +import ( + "regexp" + "strconv" + "strings" + + "github.com/pion/rtp" + psdp "github.com/pion/sdp/v3" +) + +var ( + smartPayloadTypeRegexp = regexp.MustCompile("^smart/[0-9]/[0-9]+$") + smartRtpmapRegexp = regexp.MustCompile("^([0-9]+) (.+)/[0-9]+$") +) + +func replaceSmartPayloadType(payloadType string, attributes []psdp.Attribute) string { + re1 := smartPayloadTypeRegexp.FindStringSubmatch(payloadType) + if re1 != nil { + for _, attr := range attributes { + if attr.Key == "rtpmap" { + re2 := smartRtpmapRegexp.FindStringSubmatch(attr.Value) + if re2 != nil { + return re2[1] + } + } + } + } + return payloadType +} + +func getFormatAttribute(attributes []psdp.Attribute, payloadType uint8, key string) string { + for _, attr := range attributes { + if attr.Key == key { + v := strings.TrimSpace(attr.Value) + if parts := strings.SplitN(v, " ", 2); len(parts) == 2 { + if tmp, err := strconv.ParseUint(parts[0], 10, 8); err == nil && uint8(tmp) == payloadType { + return parts[1] + } + } + } + } + return "" +} + +func getCodecAndClock(rtpMap string) (string, string) { + parts2 := strings.SplitN(rtpMap, "/", 2) + if len(parts2) != 2 { + return "", "" + } + + return strings.ToLower(parts2[0]), parts2[1] +} + +func decodeFMTP(enc string) map[string]string { + if enc == "" { + return nil + } + + ret := make(map[string]string) + + for _, kv := range strings.Split(enc, ";") { + kv = strings.Trim(kv, " ") + + if len(kv) == 0 { + continue + } + + tmp := strings.SplitN(kv, "=", 2) + if len(tmp) != 2 { + continue + } + + ret[strings.ToLower(tmp[0])] = tmp[1] + } + + return ret +} + +type unmarshalContext struct { + mediaType string + payloadType uint8 + clock string + codec string + rtpMap string + fmtp map[string]string +} + +// Format is a media format. +// It defines the payload type of RTP packets and how to encode/decode them. +type Format interface { + unmarshal(ctx *unmarshalContext) error + + // Codec returns the codec name. + Codec() string + + // ClockRate returns the clock rate. + ClockRate() int + + // PayloadType returns the payload type. + PayloadType() uint8 + + // RTPMap returns the rtpmap attribute. + RTPMap() string + + // FMTP returns the fmtp attribute. + FMTP() map[string]string + + // PTSEqualsDTS checks whether PTS is equal to DTS in RTP packets. + PTSEqualsDTS(*rtp.Packet) bool +} + +// Unmarshal decodes a format from a media description. +func Unmarshal(md *psdp.MediaDescription, payloadTypeStr string) (Format, error) { + mediaType := md.MediaName.Media + payloadTypeStr = replaceSmartPayloadType(payloadTypeStr, md.Attributes) + + tmp, err := strconv.ParseUint(payloadTypeStr, 10, 8) + if err != nil { + return nil, err + } + payloadType := uint8(tmp) + + rtpMap := getFormatAttribute(md.Attributes, payloadType, "rtpmap") + fmtp := decodeFMTP(getFormatAttribute(md.Attributes, payloadType, "fmtp")) + codec, clock := getCodecAndClock(rtpMap) + + format := func() Format { + switch { + /* + * dynamic payload types + **/ + + // video + + case codec == "av1" && clock == "90000" && payloadType >= 96 && payloadType <= 127: + return &AV1{} + + case codec == "vp9" && clock == "90000" && payloadType >= 96 && payloadType <= 127: + return &VP9{} + + case codec == "vp8" && clock == "90000" && payloadType >= 96 && payloadType <= 127: + return &VP8{} + + case codec == "h265" && clock == "90000" && payloadType >= 96 && payloadType <= 127: + return &H265{} + + case codec == "h264" && clock == "90000" && ((payloadType >= 96 && payloadType <= 127) || payloadType == 35): + return &H264{} + + case codec == "mp4v-es" && clock == "90000" && payloadType >= 96 && payloadType <= 127: + return &MPEG4Video{} + + // audio + + case codec == "opus", codec == "multiopus" && payloadType >= 96 && payloadType <= 127: + return &Opus{} + + case codec == "vorbis" && payloadType >= 96 && payloadType <= 127: + return &Vorbis{} + + case (codec == "mpeg4-generic" || codec == "mp4a-latm") && payloadType >= 96 && payloadType <= 127: + return &MPEG4Audio{} + + case codec == "ac3" && payloadType >= 96 && payloadType <= 127: + return &AC3{} + + case codec == "speex" && payloadType >= 96 && payloadType <= 127: + return &Speex{} + + case (codec == "g726-16" || + codec == "g726-24" || + codec == "g726-32" || + codec == "g726-40" || + codec == "aal2-g726-16" || + codec == "aal2-g726-24" || + codec == "aal2-g726-32" || + codec == "aal2-g726-40") && clock == "8000" && payloadType >= 96 && payloadType <= 127: + return &G726{} + + case codec == "pcma", codec == "pcmu" && payloadType >= 96 && payloadType <= 127: + return &G711{} + + case codec == "l8", codec == "l16", codec == "l24" && payloadType >= 96 && payloadType <= 127: + return &LPCM{} + + /* + * static payload types + **/ + + // video + + case payloadType == 32: + return &MPEG1Video{} + + case payloadType == 26: + return &MJPEG{} + + case payloadType == 33: + return &MPEGTS{} + + // audio + + case payloadType == 14: + return &MPEG1Audio{} + + case payloadType == 9: + return &G722{} + + case payloadType == 0, payloadType == 8: + return &G711{} + + case payloadType == 10, payloadType == 11: + return &LPCM{} + } + + return &Generic{} + }() + + err = format.unmarshal(&unmarshalContext{ + mediaType: mediaType, + payloadType: payloadType, + clock: clock, + codec: codec, + rtpMap: rtpMap, + fmtp: fmtp, + }) + if err != nil { + return nil, err + } + + return format, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/g711.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/g711.go new file mode 100644 index 000000000..f2f93883c --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/g711.go @@ -0,0 +1,134 @@ +package format + +import ( + "strconv" + "strings" + + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm" +) + +// G711 is the RTP format for the G711 codec, encoded with mu-law or A-law. +// Specification: https://datatracker.ietf.org/doc/html/rfc3551 +type G711 struct { + PayloadTyp uint8 + MULaw bool + SampleRate int + ChannelCount int +} + +func (f *G711) unmarshal(ctx *unmarshalContext) error { + f.PayloadTyp = ctx.payloadType + + if ctx.payloadType == 0 { + f.MULaw = true + f.SampleRate = 8000 + f.ChannelCount = 1 + return nil + } + + if ctx.payloadType == 8 { + f.MULaw = false + f.SampleRate = 8000 + f.ChannelCount = 1 + return nil + } + + f.MULaw = (ctx.codec == "pcmu") + + tmp := strings.SplitN(ctx.clock, "/", 2) + + tmp1, err := strconv.ParseUint(tmp[0], 10, 31) + if err != nil { + return err + } + f.SampleRate = int(tmp1) + + if len(tmp) >= 2 { + tmp1, err := strconv.ParseUint(tmp[1], 10, 31) + if err != nil { + return err + } + f.ChannelCount = int(tmp1) + } else { + f.ChannelCount = 1 + } + + return nil +} + +// Codec implements Format. +func (f *G711) Codec() string { + return "G711" +} + +// ClockRate implements Format. +func (f *G711) ClockRate() int { + return f.SampleRate +} + +// PayloadType implements Format. +func (f *G711) PayloadType() uint8 { + return f.PayloadTyp +} + +// RTPMap implements Format. +func (f *G711) RTPMap() string { + ret := "" + + if f.MULaw { + ret += "PCMU" + } else { + ret += "PCMA" + } + + ret += "/" + strconv.FormatInt(int64(f.SampleRate), 10) + + if f.ChannelCount != 1 { + ret += "/" + strconv.FormatInt(int64(f.ChannelCount), 10) + } + + return ret +} + +// FMTP implements Format. +func (f *G711) FMTP() map[string]string { + return nil +} + +// PTSEqualsDTS implements Format. +func (f *G711) PTSEqualsDTS(*rtp.Packet) bool { + return true +} + +// CreateDecoder creates a decoder able to decode the content of the format. +func (f *G711) CreateDecoder() (*rtplpcm.Decoder, error) { + d := &rtplpcm.Decoder{ + BitDepth: 8, + ChannelCount: f.ChannelCount, + } + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + +// CreateEncoder creates an encoder able to encode the content of the format. +func (f *G711) CreateEncoder() (*rtplpcm.Encoder, error) { + e := &rtplpcm.Encoder{ + PayloadType: f.PayloadType(), + BitDepth: 8, + ChannelCount: f.ChannelCount, + } + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/g722.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/g722.go new file mode 100644 index 000000000..86d7d996d --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/g722.go @@ -0,0 +1,76 @@ +package format + +import ( + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio" +) + +// G722 is the RTP format for the G722 codec. +// Specification: https://datatracker.ietf.org/doc/html/rfc3551 +type G722 struct { + // in Go, empty structs share the same pointer, + // therefore they cannot be used as map keys + // or in equality operations. Prevent this. + unused int //nolint:unused +} + +func (f *G722) unmarshal(_ *unmarshalContext) error { + return nil +} + +// Codec implements Format. +func (f *G722) Codec() string { + return "G722" +} + +// ClockRate implements Format. +func (f *G722) ClockRate() int { + return 8000 +} + +// PayloadType implements Format. +func (f *G722) PayloadType() uint8 { + return 9 +} + +// RTPMap implements Format. +func (f *G722) RTPMap() string { + return "G722/8000" +} + +// FMTP implements Format. +func (f *G722) FMTP() map[string]string { + return nil +} + +// PTSEqualsDTS implements Format. +func (f *G722) PTSEqualsDTS(*rtp.Packet) bool { + return true +} + +// CreateDecoder creates a decoder able to decode the content of the format. +func (f *G722) CreateDecoder() (*rtpsimpleaudio.Decoder, error) { + d := &rtpsimpleaudio.Decoder{} + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + +// CreateEncoder creates an encoder able to encode the content of the format. +func (f *G722) CreateEncoder() (*rtpsimpleaudio.Encoder, error) { + e := &rtpsimpleaudio.Encoder{ + PayloadType: 9, + } + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/g726.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/g726.go new file mode 100644 index 000000000..1c41725ad --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/g726.go @@ -0,0 +1,73 @@ +package format + +import ( + "strconv" + "strings" + + "github.com/pion/rtp" +) + +// G726 is the RTP format for the G726 codec. +// Specification: https://datatracker.ietf.org/doc/html/rfc3551 +type G726 struct { + PayloadTyp uint8 + BitRate int + BigEndian bool +} + +func (f *G726) unmarshal(ctx *unmarshalContext) error { + f.PayloadTyp = ctx.payloadType + + if strings.HasPrefix(ctx.codec, "aal2-") { + f.BigEndian = true + } + + switch { + case strings.HasSuffix(ctx.codec, "-16"): + f.BitRate = 16 + case strings.HasSuffix(ctx.codec, "-24"): + f.BitRate = 24 + case strings.HasSuffix(ctx.codec, "-32"): + f.BitRate = 32 + default: + f.BitRate = 40 + } + + return nil +} + +// Codec implements Format. +func (f *G726) Codec() string { + return "G726" +} + +// ClockRate implements Format. +func (f *G726) ClockRate() int { + return 8000 +} + +// PayloadType implements Format. +func (f *G726) PayloadType() uint8 { + return f.PayloadTyp +} + +// RTPMap implements Format. +func (f *G726) RTPMap() string { + codec := "" + + if f.BigEndian { + codec += "AAL2-" + } + + return codec + "G726-" + strconv.FormatInt(int64(f.BitRate), 10) + "/8000" +} + +// FMTP implements Format. +func (f *G726) FMTP() map[string]string { + return nil +} + +// PTSEqualsDTS implements Format. +func (f *G726) PTSEqualsDTS(*rtp.Packet) bool { + return true +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/generic.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/generic.go new file mode 100644 index 000000000..270d973a8 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/generic.go @@ -0,0 +1,111 @@ +package format + +import ( + "fmt" + "strconv" + "strings" + + "github.com/pion/rtp" +) + +func findClockRate(payloadType uint8, rtpMap string, isApplication bool) (int, error) { + // get clock rate from payload type + // https://en.wikipedia.org/wiki/RTP_payload_formats + switch payloadType { + case 0, 1, 2, 3, 4, 5, 7, 8, 9, 12, 13, 15, 18: + return 8000, nil + + case 6: + return 16000, nil + + case 10, 11: + return 44100, nil + + case 14, 25, 26, 28, 31, 32, 33, 34: + return 90000, nil + + case 16: + return 11025, nil + + case 17: + return 22050, nil + } + + // get clock rate from rtpmap + // https://tools.ietf.org/html/rfc4566 + // a=rtpmap: / [/] + if rtpMap != "" { + if tmp := strings.Split(rtpMap, "/"); len(tmp) >= 2 { + v, err := strconv.ParseUint(tmp[1], 10, 31) + if err != nil { + return 0, err + } + return int(v), nil + } + } + + // application format without clock rate. + // do not throw an error, but return zero, that disables RTCP sender and receiver reports. + if isApplication || rtpMap != "" { + return 0, nil + } + + return 0, fmt.Errorf("clock rate not found") +} + +// Generic is a generic RTP format. +type Generic struct { + PayloadTyp uint8 + RTPMa string + FMT map[string]string + + // clock rate of the format. Filled when calling Init(). + ClockRat int +} + +// Init computes the clock rate of the format. It is mandatory to call it. +func (f *Generic) Init() error { + var err error + f.ClockRat, err = findClockRate(f.PayloadTyp, f.RTPMa, true) + return err +} + +func (f *Generic) unmarshal(ctx *unmarshalContext) error { + f.PayloadTyp = ctx.payloadType + f.RTPMa = ctx.rtpMap + f.FMT = ctx.fmtp + + var err error + f.ClockRat, err = findClockRate(f.PayloadTyp, f.RTPMa, ctx.mediaType == "application") + return err +} + +// Codec implements Format. +func (f *Generic) Codec() string { + return "Generic" +} + +// ClockRate implements Format. +func (f *Generic) ClockRate() int { + return f.ClockRat +} + +// PayloadType implements Format. +func (f *Generic) PayloadType() uint8 { + return f.PayloadTyp +} + +// RTPMap implements Format. +func (f *Generic) RTPMap() string { + return f.RTPMa +} + +// FMTP implements Format. +func (f *Generic) FMTP() map[string]string { + return f.FMT +} + +// PTSEqualsDTS implements Format. +func (f *Generic) PTSEqualsDTS(*rtp.Packet) bool { + return true +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/h264.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/h264.go new file mode 100644 index 000000000..87115b9cd --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/h264.go @@ -0,0 +1,227 @@ +package format + +import ( + "bytes" + "encoding/base64" + "encoding/hex" + "fmt" + "strconv" + "strings" + "sync" + + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/format/rtph264" + "github.com/bluenviron/mediacommon/v2/pkg/codecs/h264" +) + +// H264 is the RTP format for the H264 codec. +// Specification: https://datatracker.ietf.org/doc/html/rfc6184 +type H264 struct { + PayloadTyp uint8 + SPS []byte + PPS []byte + PacketizationMode int + + mutex sync.RWMutex +} + +func (f *H264) unmarshal(ctx *unmarshalContext) error { + f.PayloadTyp = ctx.payloadType + + for key, val := range ctx.fmtp { + switch key { + case "sprop-parameter-sets": + tmp := strings.Split(val, ",") + if len(tmp) >= 2 { + sps, err := base64.StdEncoding.DecodeString(tmp[0]) + if err != nil { + return fmt.Errorf("invalid sprop-parameter-sets (%v)", val) + } + + // some cameras ship parameters with Annex-B prefix + sps = bytes.TrimPrefix(sps, []byte{0, 0, 0, 1}) + + pps, err := base64.StdEncoding.DecodeString(tmp[1]) + if err != nil { + return fmt.Errorf("invalid sprop-parameter-sets (%v)", val) + } + + // some cameras ship parameters with Annex-B prefix + pps = bytes.TrimPrefix(pps, []byte{0, 0, 0, 1}) + + var spsp h264.SPS + err = spsp.Unmarshal(sps) + if err != nil { + continue + } + + f.SPS = sps + f.PPS = pps + } + + case "packetization-mode": + tmp, err := strconv.ParseUint(val, 10, 31) + if err != nil { + return fmt.Errorf("invalid packetization-mode (%v)", val) + } + + f.PacketizationMode = int(tmp) + } + } + + return nil +} + +// Codec implements Format. +func (f *H264) Codec() string { + return "H264" +} + +// ClockRate implements Format. +func (f *H264) ClockRate() int { + return 90000 +} + +// PayloadType implements Format. +func (f *H264) PayloadType() uint8 { + return f.PayloadTyp +} + +// RTPMap implements Format. +func (f *H264) RTPMap() string { + return "H264/90000" +} + +// FMTP implements Format. +func (f *H264) FMTP() map[string]string { + f.mutex.RLock() + defer f.mutex.RUnlock() + + fmtp := make(map[string]string) + + if f.PacketizationMode != 0 { + fmtp["packetization-mode"] = strconv.FormatInt(int64(f.PacketizationMode), 10) + } + + var tmp []string + if f.SPS != nil { + tmp = append(tmp, base64.StdEncoding.EncodeToString(f.SPS)) + } + if f.PPS != nil { + tmp = append(tmp, base64.StdEncoding.EncodeToString(f.PPS)) + } + if tmp != nil { + fmtp["sprop-parameter-sets"] = strings.Join(tmp, ",") + } + if len(f.SPS) >= 4 { + fmtp["profile-level-id"] = strings.ToUpper(hex.EncodeToString(f.SPS[1:4])) + } + + return fmtp +} + +// PTSEqualsDTS implements Format. +func (f *H264) PTSEqualsDTS(pkt *rtp.Packet) bool { + if len(pkt.Payload) == 0 { + return false + } + + typ := h264.NALUType(pkt.Payload[0] & 0x1F) + + switch typ { + case h264.NALUTypeIDR, h264.NALUTypeSPS, h264.NALUTypePPS: + return true + + case 24: // STAP-A + payload := pkt.Payload[1:] + + for { + if len(payload) < 2 { + return false + } + + size := uint16(payload[0])<<8 | uint16(payload[1]) + payload = payload[2:] + + if size == 0 || int(size) > len(payload) { + return false + } + + var nalu []byte + nalu, payload = payload[:size], payload[size:] + + typ = h264.NALUType(nalu[0] & 0x1F) + switch typ { + case h264.NALUTypeIDR, h264.NALUTypeSPS, h264.NALUTypePPS: + return true + } + + if len(payload) == 0 { + break + } + } + + case 28: // FU-A + if len(pkt.Payload) < 2 { + return false + } + + start := pkt.Payload[1] >> 7 + if start != 1 { + return false + } + + typ := h264.NALUType(pkt.Payload[1] & 0x1F) + switch typ { + case h264.NALUTypeIDR, h264.NALUTypeSPS, h264.NALUTypePPS: + return true + } + } + + return false +} + +// CreateDecoder creates a decoder able to decode the content of the format. +func (f *H264) CreateDecoder() (*rtph264.Decoder, error) { + d := &rtph264.Decoder{ + PacketizationMode: f.PacketizationMode, + } + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + +// CreateEncoder creates an encoder able to encode the content of the format. +func (f *H264) CreateEncoder() (*rtph264.Encoder, error) { + e := &rtph264.Encoder{ + PayloadType: f.PayloadTyp, + PacketizationMode: f.PacketizationMode, + } + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil +} + +// SafeSetParams sets the codec parameters. +func (f *H264) SafeSetParams(sps []byte, pps []byte) { + f.mutex.Lock() + defer f.mutex.Unlock() + f.SPS = sps + f.PPS = pps +} + +// SafeParams returns the codec parameters. +func (f *H264) SafeParams() ([]byte, []byte) { + f.mutex.RLock() + defer f.mutex.RUnlock() + return f.SPS, f.PPS +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/h265.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/h265.go new file mode 100644 index 000000000..39931903e --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/h265.go @@ -0,0 +1,240 @@ +package format + +import ( + "bytes" + "encoding/base64" + "fmt" + "strconv" + "sync" + + "github.com/bluenviron/mediacommon/v2/pkg/codecs/h265" + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/format/rtph265" +) + +// H265 is the RTP format for the H265 codec. +// Specification: https://datatracker.ietf.org/doc/html/rfc7798 +type H265 struct { + PayloadTyp uint8 + VPS []byte + SPS []byte + PPS []byte + MaxDONDiff int + + mutex sync.RWMutex +} + +func (f *H265) unmarshal(ctx *unmarshalContext) error { + f.PayloadTyp = ctx.payloadType + + for key, val := range ctx.fmtp { + switch key { + case "sprop-vps": + var err error + f.VPS, err = base64.StdEncoding.DecodeString(val) + if err != nil { + return fmt.Errorf("invalid sprop-vps (%v)", ctx.fmtp) + } + + // some cameras ship parameters with Annex-B prefix + f.VPS = bytes.TrimPrefix(f.VPS, []byte{0, 0, 0, 1}) + + case "sprop-sps": + var err error + f.SPS, err = base64.StdEncoding.DecodeString(val) + if err != nil { + return fmt.Errorf("invalid sprop-sps (%v)", ctx.fmtp) + } + + // some cameras ship parameters with Annex-B prefix + f.SPS = bytes.TrimPrefix(f.SPS, []byte{0, 0, 0, 1}) + + var spsp h265.SPS + err = spsp.Unmarshal(f.SPS) + if err != nil { + return fmt.Errorf("invalid SPS: %w", err) + } + + case "sprop-pps": + var err error + f.PPS, err = base64.StdEncoding.DecodeString(val) + if err != nil { + return fmt.Errorf("invalid sprop-pps (%v)", ctx.fmtp) + } + + // some cameras ship parameters with Annex-B prefix + f.PPS = bytes.TrimPrefix(f.PPS, []byte{0, 0, 0, 1}) + + var ppsp h265.PPS + err = ppsp.Unmarshal(f.PPS) + if err != nil { + return fmt.Errorf("invalid PPS: %w", err) + } + + case "sprop-max-don-diff": + tmp, err := strconv.ParseUint(val, 10, 31) + if err != nil { + return fmt.Errorf("invalid sprop-max-don-diff (%v)", ctx.fmtp) + } + f.MaxDONDiff = int(tmp) + } + } + + return nil +} + +// Codec implements Format. +func (f *H265) Codec() string { + return "H265" +} + +// ClockRate implements Format. +func (f *H265) ClockRate() int { + return 90000 +} + +// PayloadType implements Format. +func (f *H265) PayloadType() uint8 { + return f.PayloadTyp +} + +// RTPMap implements Format. +func (f *H265) RTPMap() string { + return "H265/90000" +} + +// FMTP implements Format. +func (f *H265) FMTP() map[string]string { + f.mutex.RLock() + defer f.mutex.RUnlock() + + fmtp := make(map[string]string) + if f.VPS != nil { + fmtp["sprop-vps"] = base64.StdEncoding.EncodeToString(f.VPS) + } + if f.SPS != nil { + fmtp["sprop-sps"] = base64.StdEncoding.EncodeToString(f.SPS) + } + if f.PPS != nil { + fmtp["sprop-pps"] = base64.StdEncoding.EncodeToString(f.PPS) + } + if f.MaxDONDiff != 0 { + fmtp["sprop-max-don-diff"] = strconv.FormatInt(int64(f.MaxDONDiff), 10) + } + + return fmtp +} + +// PTSEqualsDTS implements Format. +func (f *H265) PTSEqualsDTS(pkt *rtp.Packet) bool { + if len(pkt.Payload) == 0 { + return false + } + + typ := h265.NALUType((pkt.Payload[0] >> 1) & 0b111111) + + switch typ { + case h265.NALUType_IDR_W_RADL, h265.NALUType_IDR_N_LP, h265.NALUType_CRA_NUT, + h265.NALUType_VPS_NUT, h265.NALUType_SPS_NUT, h265.NALUType_PPS_NUT: + return true + + case h265.NALUType_AggregationUnit: + if len(pkt.Payload) < 4 { + return false + } + + payload := pkt.Payload[2:] + + for { + size := uint16(payload[0])<<8 | uint16(payload[1]) + payload = payload[2:] + + if size == 0 || int(size) > len(payload) { + return false + } + + var nalu []byte + nalu, payload = payload[:size], payload[size:] + + typ = h265.NALUType((nalu[0] >> 1) & 0b111111) + switch typ { + case h265.NALUType_IDR_W_RADL, h265.NALUType_IDR_N_LP, h265.NALUType_CRA_NUT, + h265.NALUType_VPS_NUT, h265.NALUType_SPS_NUT, h265.NALUType_PPS_NUT: + return true + } + + if len(payload) == 0 { + break + } + + if len(payload) < 2 { + return false + } + } + + case h265.NALUType_FragmentationUnit: + if len(pkt.Payload) < 3 { + return false + } + + start := pkt.Payload[2] >> 7 + if start != 1 { + return false + } + + typ := h265.NALUType(pkt.Payload[2] & 0b111111) + switch typ { + case h265.NALUType_IDR_W_RADL, h265.NALUType_IDR_N_LP, h265.NALUType_CRA_NUT, + h265.NALUType_VPS_NUT, h265.NALUType_SPS_NUT, h265.NALUType_PPS_NUT: + return true + } + } + + return false +} + +// CreateDecoder creates a decoder able to decode the content of the format. +func (f *H265) CreateDecoder() (*rtph265.Decoder, error) { + d := &rtph265.Decoder{ + MaxDONDiff: f.MaxDONDiff, + } + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + +// CreateEncoder creates an encoder able to encode the content of the format. +func (f *H265) CreateEncoder() (*rtph265.Encoder, error) { + e := &rtph265.Encoder{ + PayloadType: f.PayloadTyp, + MaxDONDiff: f.MaxDONDiff, + } + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil +} + +// SafeSetParams sets the codec parameters. +func (f *H265) SafeSetParams(vps []byte, sps []byte, pps []byte) { + f.mutex.Lock() + defer f.mutex.Unlock() + f.VPS = vps + f.SPS = sps + f.PPS = pps +} + +// SafeParams returns the codec parameters. +func (f *H265) SafeParams() ([]byte, []byte, []byte) { + f.mutex.RLock() + defer f.mutex.RUnlock() + return f.VPS, f.SPS, f.PPS +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/lpcm.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/lpcm.go new file mode 100644 index 000000000..a83d96a36 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/lpcm.go @@ -0,0 +1,143 @@ +package format + +import ( + "strconv" + "strings" + + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm" +) + +// LPCM is the RTP format for the LPCM codec. +// Specification: https://datatracker.ietf.org/doc/html/rfc3190 +// Specification: https://datatracker.ietf.org/doc/html/rfc3551 +type LPCM struct { + PayloadTyp uint8 + BitDepth int + SampleRate int + ChannelCount int +} + +func (f *LPCM) unmarshal(ctx *unmarshalContext) error { + f.PayloadTyp = ctx.payloadType + + if ctx.payloadType == 10 { + f.BitDepth = 16 + f.SampleRate = 44100 + f.ChannelCount = 2 + return nil + } + + if ctx.payloadType == 11 { + f.BitDepth = 16 + f.SampleRate = 44100 + f.ChannelCount = 1 + return nil + } + + switch ctx.codec { + case "l8": + f.BitDepth = 8 + + case "l16": + f.BitDepth = 16 + + case "l24": + f.BitDepth = 24 + } + + tmp := strings.SplitN(ctx.clock, "/", 2) + + tmp1, err := strconv.ParseUint(tmp[0], 10, 31) + if err != nil { + return err + } + f.SampleRate = int(tmp1) + + if len(tmp) >= 2 { + tmp1, err := strconv.ParseUint(tmp[1], 10, 31) + if err != nil { + return err + } + f.ChannelCount = int(tmp1) + } else { + f.ChannelCount = 1 + } + + return nil +} + +// Codec implements Format. +func (f *LPCM) Codec() string { + return "LPCM" +} + +// ClockRate implements Format. +func (f *LPCM) ClockRate() int { + return f.SampleRate +} + +// PayloadType implements Format. +func (f *LPCM) PayloadType() uint8 { + return f.PayloadTyp +} + +// RTPMap implements Format. +func (f *LPCM) RTPMap() string { + var codec string + switch f.BitDepth { + case 8: + codec = "L8" + + case 16: + codec = "L16" + + case 24: + codec = "L24" + } + + return codec + "/" + strconv.FormatInt(int64(f.SampleRate), 10) + + "/" + strconv.FormatInt(int64(f.ChannelCount), 10) +} + +// FMTP implements Format. +func (f *LPCM) FMTP() map[string]string { + return nil +} + +// PTSEqualsDTS implements Format. +func (f *LPCM) PTSEqualsDTS(*rtp.Packet) bool { + return true +} + +// CreateDecoder creates a decoder able to decode the content of the format. +func (f *LPCM) CreateDecoder() (*rtplpcm.Decoder, error) { + d := &rtplpcm.Decoder{ + BitDepth: f.BitDepth, + ChannelCount: f.ChannelCount, + } + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + +// CreateEncoder creates an encoder able to encode the content of the format. +func (f *LPCM) CreateEncoder() (*rtplpcm.Encoder, error) { + e := &rtplpcm.Encoder{ + PayloadType: f.PayloadTyp, + BitDepth: f.BitDepth, + ChannelCount: f.ChannelCount, + } + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mjpeg.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mjpeg.go new file mode 100644 index 000000000..21275c69b --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mjpeg.go @@ -0,0 +1,74 @@ +package format //nolint:dupl + +import ( + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg" +) + +// MJPEG is the RTP format for the Motion-JPEG codec. +// Specification: https://datatracker.ietf.org/doc/html/rfc2435 +type MJPEG struct { + // in Go, empty structs share the same pointer, + // therefore they cannot be used as map keys + // or in equality operations. Prevent this. + unused int //nolint:unused +} + +func (f *MJPEG) unmarshal(_ *unmarshalContext) error { + return nil +} + +// Codec implements Format. +func (f *MJPEG) Codec() string { + return "M-JPEG" +} + +// ClockRate implements Format. +func (f *MJPEG) ClockRate() int { + return 90000 +} + +// PayloadType implements Format. +func (f *MJPEG) PayloadType() uint8 { + return 26 +} + +// RTPMap implements Format. +func (f *MJPEG) RTPMap() string { + return "JPEG/90000" +} + +// FMTP implements Format. +func (f *MJPEG) FMTP() map[string]string { + return nil +} + +// PTSEqualsDTS implements Format. +func (f *MJPEG) PTSEqualsDTS(*rtp.Packet) bool { + return true +} + +// CreateDecoder creates a decoder able to decode the content of the format. +func (f *MJPEG) CreateDecoder() (*rtpmjpeg.Decoder, error) { + d := &rtpmjpeg.Decoder{} + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + +// CreateEncoder creates an encoder able to encode the content of the format. +func (f *MJPEG) CreateEncoder() (*rtpmjpeg.Encoder, error) { + e := &rtpmjpeg.Encoder{} + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mpeg1_audio.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mpeg1_audio.go new file mode 100644 index 000000000..603cde9e3 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mpeg1_audio.go @@ -0,0 +1,74 @@ +package format //nolint:dupl + +import ( + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1audio" +) + +// MPEG1Audio is the RTP format for a MPEG-1/2 Audio codec. +// Specification: https://datatracker.ietf.org/doc/html/rfc2250 +type MPEG1Audio struct { + // in Go, empty structs share the same pointer, + // therefore they cannot be used as map keys + // or in equality operations. Prevent this. + unused int //nolint:unused +} + +func (f *MPEG1Audio) unmarshal(_ *unmarshalContext) error { + return nil +} + +// Codec implements Format. +func (f *MPEG1Audio) Codec() string { + return "MPEG-1/2 Audio" +} + +// ClockRate implements Format. +func (f *MPEG1Audio) ClockRate() int { + return 90000 +} + +// PayloadType implements Format. +func (f *MPEG1Audio) PayloadType() uint8 { + return 14 +} + +// RTPMap implements Format. +func (f *MPEG1Audio) RTPMap() string { + return "" +} + +// FMTP implements Format. +func (f *MPEG1Audio) FMTP() map[string]string { + return nil +} + +// PTSEqualsDTS implements Format. +func (f *MPEG1Audio) PTSEqualsDTS(*rtp.Packet) bool { + return true +} + +// CreateDecoder creates a decoder able to decode the content of the format. +func (f *MPEG1Audio) CreateDecoder() (*rtpmpeg1audio.Decoder, error) { + d := &rtpmpeg1audio.Decoder{} + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + +// CreateEncoder creates an encoder able to encode the content of the format. +func (f *MPEG1Audio) CreateEncoder() (*rtpmpeg1audio.Encoder, error) { + e := &rtpmpeg1audio.Encoder{} + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mpeg1_video.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mpeg1_video.go new file mode 100644 index 000000000..93d4d1584 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mpeg1_video.go @@ -0,0 +1,74 @@ +package format //nolint:dupl + +import ( + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1video" +) + +// MPEG1Video is the RTP format for a MPEG-1/2 Video codec. +// Specification: https://datatracker.ietf.org/doc/html/rfc2250 +type MPEG1Video struct { + // in Go, empty structs share the same pointer, + // therefore they cannot be used as map keys + // or in equality operations. Prevent this. + unused int //nolint:unused +} + +func (f *MPEG1Video) unmarshal(_ *unmarshalContext) error { + return nil +} + +// Codec implements Format. +func (f *MPEG1Video) Codec() string { + return "MPEG-1/2 Video" +} + +// ClockRate implements Format. +func (f *MPEG1Video) ClockRate() int { + return 90000 +} + +// PayloadType implements Format. +func (f *MPEG1Video) PayloadType() uint8 { + return 32 +} + +// RTPMap implements Format. +func (f *MPEG1Video) RTPMap() string { + return "" +} + +// FMTP implements Format. +func (f *MPEG1Video) FMTP() map[string]string { + return nil +} + +// PTSEqualsDTS implements Format. +func (f *MPEG1Video) PTSEqualsDTS(*rtp.Packet) bool { + return true +} + +// CreateDecoder creates a decoder able to decode the content of the format. +func (f *MPEG1Video) CreateDecoder() (*rtpmpeg1video.Decoder, error) { + d := &rtpmpeg1video.Decoder{} + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + +// CreateEncoder creates an encoder able to encode the content of the format. +func (f *MPEG1Video) CreateEncoder() (*rtpmpeg1video.Encoder, error) { + e := &rtpmpeg1video.Encoder{} + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mpeg4_audio.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mpeg4_audio.go new file mode 100644 index 000000000..91c8b7300 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mpeg4_audio.go @@ -0,0 +1,343 @@ +package format + +import ( + "encoding/hex" + "fmt" + "strconv" + "strings" + + "github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio" + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio" +) + +// MPEG4Audio is the RTP format for a MPEG-4 Audio codec. +// Specification: https://datatracker.ietf.org/doc/html/rfc3640 +// Specification: https://datatracker.ietf.org/doc/html/rfc6416#section-7.3 +type MPEG4Audio struct { + // payload type of packets. + PayloadTyp uint8 + + // use LATM format (RFC6416) instead of generic format (RFC3640). + LATM bool + + // profile level ID. + ProfileLevelID int + + // generic only + Config *mpeg4audio.Config + SizeLength int + IndexLength int + IndexDeltaLength int + + // LATM only + Bitrate *int + CPresent bool + StreamMuxConfig *mpeg4audio.StreamMuxConfig + SBREnabled *bool +} + +func (f *MPEG4Audio) unmarshal(ctx *unmarshalContext) error { + f.PayloadTyp = ctx.payloadType + f.LATM = (ctx.codec != "mpeg4-generic") + + if !f.LATM { + for key, val := range ctx.fmtp { + switch key { + case "streamtype": + if val != "5" { // AudioStream in ISO 14496-1 + return fmt.Errorf("streamtype of AAC must be 5") + } + + case "mode": + if strings.ToLower(val) != "aac-hbr" && strings.ToLower(val) != "aac_hbr" { + return fmt.Errorf("unsupported AAC mode: %v", val) + } + + case "profile-level-id": + tmp, err := strconv.ParseUint(val, 10, 31) + if err != nil { + return fmt.Errorf("invalid profile-level-id: %v", val) + } + + f.ProfileLevelID = int(tmp) + + case "config": + enc, err := hex.DecodeString(val) + if err != nil { + return fmt.Errorf("invalid AAC config: %v", val) + } + + f.Config = &mpeg4audio.Config{} + err = f.Config.Unmarshal(enc) + if err != nil { + return fmt.Errorf("invalid AAC config: %v", val) + } + + case "sizelength": + n, err := strconv.ParseUint(val, 10, 31) + if err != nil || n > 100 { + return fmt.Errorf("invalid AAC SizeLength: %v", val) + } + f.SizeLength = int(n) + + case "indexlength": + n, err := strconv.ParseUint(val, 10, 31) + if err != nil || n > 100 { + return fmt.Errorf("invalid AAC IndexLength: %v", val) + } + f.IndexLength = int(n) + + case "indexdeltalength": + n, err := strconv.ParseUint(val, 10, 31) + if err != nil || n > 100 { + return fmt.Errorf("invalid AAC IndexDeltaLength: %v", val) + } + f.IndexDeltaLength = int(n) + } + } + + if f.Config == nil { + return fmt.Errorf("config is missing") + } + + if f.SizeLength == 0 { + return fmt.Errorf("sizelength is missing") + } + } else { + // default value set by specification + f.ProfileLevelID = 30 + f.CPresent = true + + for key, val := range ctx.fmtp { + switch key { + case "profile-level-id": + tmp, err := strconv.ParseUint(val, 10, 31) + if err != nil { + return fmt.Errorf("invalid profile-level-id: %v", val) + } + + f.ProfileLevelID = int(tmp) + + case "bitrate": + tmp, err := strconv.ParseUint(val, 10, 31) + if err != nil { + return fmt.Errorf("invalid bitrate: %v", val) + } + + v := int(tmp) + f.Bitrate = &v + + case "cpresent": + f.CPresent = (val == "1") + + case "config": + enc, err := hex.DecodeString(val) + if err != nil { + return fmt.Errorf("invalid AAC config: %v", val) + } + + f.StreamMuxConfig = &mpeg4audio.StreamMuxConfig{} + err = f.StreamMuxConfig.Unmarshal(enc) + if err != nil { + return fmt.Errorf("invalid AAC config: %w", err) + } + + case "sbr-enabled": + v := (val == "1") + f.SBREnabled = &v + } + } + + if f.CPresent { + if f.StreamMuxConfig != nil { + return fmt.Errorf("config and cpresent can't be used at the same time") + } + } else { + if f.StreamMuxConfig == nil { + return fmt.Errorf("config is missing") + } + } + } + + return nil +} + +// Codec implements Format. +func (f *MPEG4Audio) Codec() string { + return "MPEG-4 Audio" +} + +// ClockRate implements Format. +func (f *MPEG4Audio) ClockRate() int { + if !f.LATM { + return f.Config.SampleRate + } + if f.CPresent { + return 16000 + } + return f.StreamMuxConfig.Programs[0].Layers[0].AudioSpecificConfig.SampleRate +} + +// PayloadType implements Format. +func (f *MPEG4Audio) PayloadType() uint8 { + return f.PayloadTyp +} + +// RTPMap implements Format. +func (f *MPEG4Audio) RTPMap() string { + if !f.LATM { + sampleRate := f.Config.SampleRate + if f.Config.ExtensionSampleRate != 0 { + sampleRate = f.Config.ExtensionSampleRate + } + + channelCount := f.Config.ChannelCount + if f.Config.ExtensionType == mpeg4audio.ObjectTypePS { + channelCount = 2 + } + + return "mpeg4-generic/" + strconv.FormatInt(int64(sampleRate), 10) + + "/" + strconv.FormatInt(int64(channelCount), 10) + } + + if f.CPresent { + return "MP4A-LATM/16000/1" + } + + aoc := f.StreamMuxConfig.Programs[0].Layers[0].AudioSpecificConfig + + sampleRate := aoc.SampleRate + if aoc.ExtensionSampleRate != 0 { + sampleRate = aoc.ExtensionSampleRate + } + + channelCount := aoc.ChannelCount + if aoc.ExtensionType == mpeg4audio.ObjectTypePS { + channelCount = 2 + } + + return "MP4A-LATM/" + strconv.FormatInt(int64(sampleRate), 10) + + "/" + strconv.FormatInt(int64(channelCount), 10) +} + +// FMTP implements Format. +func (f *MPEG4Audio) FMTP() map[string]string { + if !f.LATM { + enc, err := f.Config.Marshal() + if err != nil { + return nil + } + + profileLevelID := f.ProfileLevelID + if profileLevelID == 0 { // support legacy definition which didn't include profile-level-id + profileLevelID = 1 + } + + fmtp := map[string]string{ + "streamtype": "5", + "mode": "AAC-hbr", + "profile-level-id": strconv.FormatInt(int64(profileLevelID), 10), + } + + if f.SizeLength > 0 { + fmtp["sizelength"] = strconv.FormatInt(int64(f.SizeLength), 10) + } + + if f.IndexLength > 0 { + fmtp["indexlength"] = strconv.FormatInt(int64(f.IndexLength), 10) + } + + if f.IndexDeltaLength > 0 { + fmtp["indexdeltalength"] = strconv.FormatInt(int64(f.IndexDeltaLength), 10) + } + + fmtp["config"] = hex.EncodeToString(enc) + + return fmtp + } + + fmtp := map[string]string{ + "profile-level-id": strconv.FormatInt(int64(f.ProfileLevelID), 10), + } + + if f.Bitrate != nil { + fmtp["bitrate"] = strconv.FormatInt(int64(*f.Bitrate), 10) + } + + if f.CPresent { + fmtp["cpresent"] = "1" + } else { + fmtp["cpresent"] = "0" + + enc, err := f.StreamMuxConfig.Marshal() + if err != nil { + return nil + } + + fmtp["config"] = hex.EncodeToString(enc) + fmtp["object"] = strconv.FormatInt(int64(f.StreamMuxConfig.Programs[0].Layers[0].AudioSpecificConfig.Type), 10) + } + + if f.SBREnabled != nil { + if *f.SBREnabled { + fmtp["SBR-enabled"] = "1" + } else { + fmtp["SBR-enabled"] = "0" + } + } + + return fmtp +} + +// PTSEqualsDTS implements Format. +func (f *MPEG4Audio) PTSEqualsDTS(*rtp.Packet) bool { + return true +} + +// CreateDecoder creates a decoder able to decode the content of the format. +func (f *MPEG4Audio) CreateDecoder() (*rtpmpeg4audio.Decoder, error) { + d := &rtpmpeg4audio.Decoder{ + LATM: f.LATM, + SizeLength: f.SizeLength, + IndexLength: f.IndexLength, + IndexDeltaLength: f.IndexDeltaLength, + } + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + +// CreateEncoder creates an encoder able to encode the content of the format. +func (f *MPEG4Audio) CreateEncoder() (*rtpmpeg4audio.Encoder, error) { + e := &rtpmpeg4audio.Encoder{ + LATM: f.LATM, + PayloadType: f.PayloadTyp, + SizeLength: f.SizeLength, + IndexLength: f.IndexLength, + IndexDeltaLength: f.IndexDeltaLength, + } + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil +} + +// GetConfig returns the MPEG-4 Audio configuration. +func (f *MPEG4Audio) GetConfig() *mpeg4audio.Config { + if !f.LATM { + return f.Config + } + if f.CPresent { + return nil + } + return f.StreamMuxConfig.Programs[0].Layers[0].AudioSpecificConfig +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mpeg4_video.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mpeg4_video.go new file mode 100644 index 000000000..8c09bf18c --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mpeg4_video.go @@ -0,0 +1,133 @@ +package format + +import ( + "encoding/hex" + "fmt" + "strconv" + "strings" + "sync" + + "github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video" + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4video" +) + +// MPEG4Video is the RTP format for a MPEG-4 Video codec. +// Specification: https://datatracker.ietf.org/doc/html/rfc6416#section-7.1 +type MPEG4Video struct { + PayloadTyp uint8 + ProfileLevelID int + Config []byte + + mutex sync.RWMutex +} + +func (f *MPEG4Video) unmarshal(ctx *unmarshalContext) error { + f.PayloadTyp = ctx.payloadType + f.ProfileLevelID = 1 // default value imposed by specification + + for key, val := range ctx.fmtp { + switch key { + case "profile-level-id": + tmp, err := strconv.ParseUint(val, 10, 31) + if err != nil { + return fmt.Errorf("invalid profile-level-id: %v", val) + } + + f.ProfileLevelID = int(tmp) + + case "config": + var err error + f.Config, err = hex.DecodeString(val) + if err != nil { + return fmt.Errorf("invalid config: %v", val) + } + + err = mpeg4video.IsValidConfig(f.Config) + if err != nil { + return fmt.Errorf("invalid config: %w", err) + } + } + } + + return nil +} + +// Codec implements Format. +func (f *MPEG4Video) Codec() string { + return "MPEG-4 Video" +} + +// ClockRate implements Format. +func (f *MPEG4Video) ClockRate() int { + return 90000 +} + +// PayloadType implements Format. +func (f *MPEG4Video) PayloadType() uint8 { + return f.PayloadTyp +} + +// RTPMap implements Format. +func (f *MPEG4Video) RTPMap() string { + return "MP4V-ES/90000" +} + +// FMTP implements Format. +func (f *MPEG4Video) FMTP() map[string]string { + fmtp := map[string]string{ + "profile-level-id": strconv.FormatInt(int64(f.ProfileLevelID), 10), + } + + if f.Config != nil { + fmtp["config"] = strings.ToUpper(hex.EncodeToString(f.Config)) + } + + return fmtp +} + +// PTSEqualsDTS implements Format. +func (f *MPEG4Video) PTSEqualsDTS(*rtp.Packet) bool { + return true +} + +// CreateDecoder creates a decoder able to decode the content of the format. +func (f *MPEG4Video) CreateDecoder() (*rtpmpeg4video.Decoder, error) { + d := &rtpmpeg4video.Decoder{} + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + +// CreateEncoder creates an encoder able to encode the content of the format. +func (f *MPEG4Video) CreateEncoder() (*rtpmpeg4video.Encoder, error) { + e := &rtpmpeg4video.Encoder{ + PayloadType: f.PayloadTyp, + } + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil +} + +// SafeSetParams sets the codec parameters. +func (f *MPEG4Video) SafeSetParams(config []byte) { + f.mutex.Lock() + defer f.mutex.Unlock() + f.Config = config +} + +// SafeParams returns the codec parameters. +func (f *MPEG4Video) SafeParams() []byte { + f.mutex.RLock() + defer f.mutex.RUnlock() + return f.Config +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mpegts.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mpegts.go new file mode 100644 index 000000000..3baf5703f --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/mpegts.go @@ -0,0 +1,48 @@ +package format //nolint:dupl + +import ( + "github.com/pion/rtp" +) + +// MPEGTS is the RTP format for MPEG-TS. +// Specification: https://datatracker.ietf.org/doc/html/rfc2250 +type MPEGTS struct { + // in Go, empty structs share the same pointer, + // therefore they cannot be used as map keys + // or in equality operations. Prevent this. + unused int //nolint:unused +} + +func (f *MPEGTS) unmarshal(_ *unmarshalContext) error { + return nil +} + +// Codec implements Format. +func (f *MPEGTS) Codec() string { + return "MPEG-TS" +} + +// ClockRate implements Format. +func (f *MPEGTS) ClockRate() int { + return 90000 +} + +// PayloadType implements Format. +func (f *MPEGTS) PayloadType() uint8 { + return 33 +} + +// RTPMap implements Format. +func (f *MPEGTS) RTPMap() string { + return "MP2T/90000" +} + +// FMTP implements Format. +func (f *MPEGTS) FMTP() map[string]string { + return nil +} + +// PTSEqualsDTS implements Format. +func (f *MPEGTS) PTSEqualsDTS(*rtp.Packet) bool { + return true +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/opus.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/opus.go new file mode 100644 index 000000000..09902a4a6 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/opus.go @@ -0,0 +1,199 @@ +package format + +import ( + "fmt" + "strconv" + "strings" + + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio" +) + +// Opus is the RTP format for the Opus codec. +// Specification: https://datatracker.ietf.org/doc/html/rfc7587 +// Specification: https://webrtc-review.googlesource.com/c/src/+/129768 +type Opus struct { + PayloadTyp uint8 + ChannelCount int + + // + // Deprecated: replaced by ChannelCount. + IsStereo bool +} + +func (f *Opus) unmarshal(ctx *unmarshalContext) error { + f.PayloadTyp = ctx.payloadType + + if ctx.codec == "opus" { + tmp := strings.SplitN(ctx.clock, "/", 2) + if len(tmp) != 2 { + return fmt.Errorf("invalid clock (%v)", ctx.clock) + } + + sampleRate, err := strconv.ParseUint(tmp[0], 10, 31) + if err != nil || sampleRate != 48000 { + return fmt.Errorf("invalid sample rate: '%s", tmp[0]) + } + + channelCount, err := strconv.ParseUint(tmp[1], 10, 31) + if err != nil || channelCount != 2 { + return fmt.Errorf("invalid channel count: '%s'", tmp[1]) + } + + // assume mono + f.ChannelCount = 1 + f.IsStereo = false + + for key, val := range ctx.fmtp { + if key == "sprop-stereo" { + if val == "1" { + f.ChannelCount = 2 + f.IsStereo = true + } + } + } + } else { + tmp := strings.SplitN(ctx.clock, "/", 2) + if len(tmp) != 2 { + return fmt.Errorf("invalid clock (%v)", ctx.clock) + } + + sampleRate, err := strconv.ParseUint(tmp[0], 10, 31) + if err != nil || sampleRate != 48000 { + return fmt.Errorf("invalid sample rate: '%s'", tmp[0]) + } + + channelCount, err := strconv.ParseUint(tmp[1], 10, 31) + if err != nil { + return fmt.Errorf("invalid channel count: '%s'", tmp[1]) + } + + f.ChannelCount = int(channelCount) + } + + return nil +} + +// Codec implements Format. +func (f *Opus) Codec() string { + return "Opus" +} + +// ClockRate implements Format. +func (f *Opus) ClockRate() int { + // RFC7587: the RTP timestamp is incremented with a 48000 Hz + // clock rate for all modes of Opus and all sampling rates. + return 48000 +} + +// PayloadType implements Format. +func (f *Opus) PayloadType() uint8 { + return f.PayloadTyp +} + +// RTPMap implements Format. +func (f *Opus) RTPMap() string { + if f.ChannelCount <= 2 { + // RFC7587: The RTP clock rate in "a=rtpmap" MUST be 48000, and the + // number of channels MUST be 2. + return "opus/48000/2" + } + + return "multiopus/48000/" + strconv.FormatUint(uint64(f.ChannelCount), 10) +} + +// FMTP implements Format. +func (f *Opus) FMTP() map[string]string { + if f.ChannelCount <= 2 { + return map[string]string{ + "sprop-stereo": func() string { + if f.ChannelCount == 2 || (f.ChannelCount == 0 && f.IsStereo) { + return "1" + } + return "0" + }(), + } + } + + switch f.ChannelCount { + case 3: + return map[string]string{ + "num_streams": "2", + "coupled_streams": "1", + "channel_mapping": "0,2,1", + "sprop-maxcapturerate": "48000", + } + + case 4: + return map[string]string{ + "num_streams": "2", + "coupled_streams": "2", + "channel_mapping": "0,1,2,3", + "sprop-maxcapturerate": "48000", + } + + case 5: + return map[string]string{ + "num_streams": "3", + "coupled_streams": "2", + "channel_mapping": "0,4,1,2,3", + "sprop-maxcapturerate": "48000", + } + + case 6: + return map[string]string{ + "num_streams": "4", + "coupled_streams": "2", + "channel_mapping": "0,4,1,2,3,5", + "sprop-maxcapturerate": "48000", + } + + case 7: + return map[string]string{ + "num_streams": "4", + "coupled_streams": "3", + "channel_mapping": "0,4,1,2,3,5,6", + "sprop-maxcapturerate": "48000", + } + + default: // assume 8 + return map[string]string{ + "num_streams": "5", + "coupled_streams": "3", + "channel_mapping": "0,6,1,4,5,2,3,7", + "sprop-maxcapturerate": "48000", + } + } +} + +// PTSEqualsDTS implements Format. +func (f *Opus) PTSEqualsDTS(*rtp.Packet) bool { + return true +} + +// CreateDecoder creates a decoder able to decode the content of the format. +func (f *Opus) CreateDecoder() (*rtpsimpleaudio.Decoder, error) { + d := &rtpsimpleaudio.Decoder{} + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + +// CreateEncoder creates an encoder able to encode the content of the format. +func (f *Opus) CreateEncoder() (*rtpsimpleaudio.Encoder, error) { + e := &rtpsimpleaudio.Encoder{ + PayloadType: f.PayloadTyp, + } + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpac3/decoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpac3/decoder.go new file mode 100644 index 000000000..60bce2988 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpac3/decoder.go @@ -0,0 +1,149 @@ +package rtpac3 + +import ( + "errors" + "fmt" + + "github.com/pion/rtp" + + "github.com/bluenviron/mediacommon/v2/pkg/codecs/ac3" +) + +// ErrMorePacketsNeeded is returned when more packets are needed. +var ErrMorePacketsNeeded = errors.New("need more packets") + +// ErrNonStartingPacketAndNoPrevious is returned when we received a non-starting +// packet of a fragmented frame and we didn't received anything before. +// It's normal to receive this when decoding a stream that has been already +// running for some time. +var ErrNonStartingPacketAndNoPrevious = errors.New( + "received a non-starting fragment without any previous starting fragment") + +func joinFragments(fragments [][]byte, size int) []byte { + ret := make([]byte, size) + n := 0 + for _, p := range fragments { + n += copy(ret[n:], p) + } + return ret +} + +// Decoder is a AC-3 decoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc4184 +type Decoder struct { + firstPacketReceived bool + fragments [][]byte + fragmentsSize int + fragmentsExpected int + fragmentNextSeqNum uint16 +} + +// Init initializes the decoder. +func (d *Decoder) Init() error { + return nil +} + +func (d *Decoder) resetFragments() { + d.fragments = d.fragments[:0] + d.fragmentsSize = 0 +} + +// Decode decodes frames from a RTP packet. +// It returns the frames and the PTS of the first frame. +func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, error) { + if len(pkt.Payload) < 2 { + d.resetFragments() + return nil, fmt.Errorf("payload is too short") + } + + mbz := pkt.Payload[0] >> 2 + ft := pkt.Payload[0] & 0b11 + + if mbz != 0 { + d.resetFragments() + return nil, fmt.Errorf("invalid MBZ: %v", mbz) + } + + var frames [][]byte + + switch ft { + case 0: + d.resetFragments() + d.firstPacketReceived = true + + buf := pkt.Payload[2:] + + for { + var syncInfo ac3.SyncInfo + err := syncInfo.Unmarshal(buf) + if err != nil { + return nil, err + } + size := syncInfo.FrameSize() + + if len(buf) < size { + return nil, fmt.Errorf("payload is too short") + } + + frames = append(frames, buf[:size]) + buf = buf[size:] + + if len(buf) == 0 { + break + } + } + + case 1, 2: + d.resetFragments() + + var syncInfo ac3.SyncInfo + err := syncInfo.Unmarshal(pkt.Payload[2:]) + if err != nil { + return nil, err + } + size := syncInfo.FrameSize() + + le := len(pkt.Payload[2:]) + d.fragmentsSize = le + d.fragmentsExpected = size - le + d.fragments = append(d.fragments, pkt.Payload[2:]) + d.fragmentNextSeqNum = pkt.SequenceNumber + 1 + d.firstPacketReceived = true + + return nil, ErrMorePacketsNeeded + + case 3: + if d.fragmentsSize == 0 { + if !d.firstPacketReceived { + return nil, ErrNonStartingPacketAndNoPrevious + } + return nil, fmt.Errorf("received a subsequent fragment without previous fragments") + } + + if pkt.SequenceNumber != d.fragmentNextSeqNum { + d.resetFragments() + return nil, fmt.Errorf("discarding frame since a RTP packet is missing") + } + + le := len(pkt.Payload[2:]) + d.fragmentsSize += le + d.fragmentsExpected -= le + + if d.fragmentsExpected < 0 { + d.resetFragments() + return nil, fmt.Errorf("fragment is too big") + } + + d.fragments = append(d.fragments, pkt.Payload[2:]) + d.fragmentNextSeqNum++ + + if d.fragmentsExpected > 0 { + return nil, ErrMorePacketsNeeded + } + + frames = [][]byte{joinFragments(d.fragments, d.fragmentsSize)} + d.resetFragments() + } + + return frames, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpac3/encoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpac3/encoder.go new file mode 100644 index 000000000..6f4e34a7f --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpac3/encoder.go @@ -0,0 +1,201 @@ +package rtpac3 + +import ( + "crypto/rand" + + "github.com/pion/rtp" + + "github.com/bluenviron/mediacommon/v2/pkg/codecs/ac3" +) + +const ( + rtpVersion = 2 + defaultPayloadMaxSize = 1460 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header) +) + +func randUint32() (uint32, error) { + var b [4]byte + _, err := rand.Read(b[:]) + if err != nil { + return 0, err + } + return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil +} + +func packetCount(avail, le int) int { + n := le / avail + if (le % avail) != 0 { + n++ + } + return n +} + +// Encoder is a AC-3 encoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc4184 +type Encoder struct { + // payload type of packets. + PayloadType uint8 + + // SSRC of packets (optional). + // It defaults to a random value. + SSRC *uint32 + + // initial sequence number of packets (optional). + // It defaults to a random value. + InitialSequenceNumber *uint16 + + // maximum size of packet payloads (optional). + // It defaults to 1460. + PayloadMaxSize int + + sequenceNumber uint16 +} + +// Init initializes the encoder. +func (e *Encoder) Init() error { + if e.SSRC == nil { + v, err := randUint32() + if err != nil { + return err + } + e.SSRC = &v + } + if e.InitialSequenceNumber == nil { + v, err := randUint32() + if err != nil { + return err + } + v2 := uint16(v) + e.InitialSequenceNumber = &v2 + } + if e.PayloadMaxSize == 0 { + e.PayloadMaxSize = defaultPayloadMaxSize + } + + e.sequenceNumber = *e.InitialSequenceNumber + return nil +} + +// Encode encodes frames into RTP packets. +func (e *Encoder) Encode(frames [][]byte) ([]*rtp.Packet, error) { + var rets []*rtp.Packet + var batch [][]byte + timestamp := uint32(0) + + // split frames into batches + for _, frame := range frames { + if e.lenAggregated(batch, frame) <= e.PayloadMaxSize { + // add to existing batch + batch = append(batch, frame) + } else { + // write current batch + if batch != nil { + pkts, err := e.writeBatch(batch, timestamp) + if err != nil { + return nil, err + } + rets = append(rets, pkts...) + timestamp += uint32(len(batch)) * ac3.SamplesPerFrame + } + + // initialize new batch + batch = [][]byte{frame} + } + } + + // write last batch + pkts, err := e.writeBatch(batch, timestamp) + if err != nil { + return nil, err + } + rets = append(rets, pkts...) + + return rets, nil +} + +func (e *Encoder) writeBatch(frames [][]byte, timestamp uint32) ([]*rtp.Packet, error) { + if len(frames) != 1 || e.lenAggregated(frames, nil) < e.PayloadMaxSize { + return e.writeAggregated(frames, timestamp) + } + + return e.writeFragmented(frames[0], timestamp) +} + +func (e *Encoder) writeFragmented(frame []byte, timestamp uint32) ([]*rtp.Packet, error) { + avail := e.PayloadMaxSize - 4 + le := len(frame) + packetCount := packetCount(avail, le) + + ret := make([]*rtp.Packet, packetCount) + le = avail + + ft := uint8(2) + if avail >= (len(frame) * 5 / 8) { + ft = 1 + } + + for i := range ret { + if i == (packetCount - 1) { + le = len(frame) + } + + payload := make([]byte, 2+le) + payload[0] = ft + payload[1] = uint8(packetCount) + + n := copy(payload[2:], frame) + frame = frame[n:] + + ret[i] = &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: e.PayloadType, + SequenceNumber: e.sequenceNumber, + Timestamp: timestamp, + SSRC: *e.SSRC, + Marker: i == (packetCount - 1), + }, + Payload: payload, + } + + e.sequenceNumber++ + ft = 3 + } + + return ret, nil +} + +func (e *Encoder) lenAggregated(frames [][]byte, addFrame []byte) int { + n := 2 + len(addFrame) + for _, frame := range frames { + n += len(frame) + } + return n +} + +func (e *Encoder) writeAggregated(frames [][]byte, timestamp uint32) ([]*rtp.Packet, error) { + payload := make([]byte, e.lenAggregated(frames, nil)) + + payload[1] = uint8(len(frames)) + + n := 2 + for _, frame := range frames { + n += copy(payload[n:], frame) + } + + pkt := &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: e.PayloadType, + SequenceNumber: e.sequenceNumber, + Timestamp: timestamp, + SSRC: *e.SSRC, + Marker: true, + }, + Payload: payload, + } + + e.sequenceNumber++ + + return []*rtp.Packet{pkt}, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpac3/rtpac3.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpac3/rtpac3.go new file mode 100644 index 000000000..2024084df --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpac3/rtpac3.go @@ -0,0 +1,2 @@ +// Package rtpac3 contains a RTP/AC-3 decoder and encoder. +package rtpac3 diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1/decoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1/decoder.go new file mode 100644 index 000000000..f5dd9abc2 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1/decoder.go @@ -0,0 +1,202 @@ +package rtpav1 + +import ( + "errors" + "fmt" + + "github.com/bluenviron/mediacommon/v2/pkg/codecs/av1" + "github.com/pion/rtp" +) + +// ErrMorePacketsNeeded is returned when more packets are needed. +var ErrMorePacketsNeeded = errors.New("need more packets") + +// ErrNonStartingPacketAndNoPrevious is returned when we received a non-starting +// packet of a fragmented NALU and we didn't received anything before. +// It's normal to receive this when decoding a stream that has been already +// running for some time. +var ErrNonStartingPacketAndNoPrevious = errors.New( + "received a non-starting fragment without any previous starting fragment") + +func joinFragments(fragments [][]byte, size int) []byte { + ret := make([]byte, size) + n := 0 + for _, p := range fragments { + n += copy(ret[n:], p) + } + return ret +} + +func tuSize(tu [][]byte) int { + s := 0 + for _, obu := range tu { + s += len(obu) + } + return s +} + +// Decoder is a RTP/AV1 decoder. +// Specification: https://aomediacodec.github.io/av1-rtp-spec/ +type Decoder struct { + firstPacketReceived bool + fragments [][]byte + fragmentsSize int + fragmentNextSeqNum uint16 + + // for Decode() + frameBuffer [][]byte + frameBufferLen int + frameBufferSize int +} + +// Init initializes the decoder. +func (d *Decoder) Init() error { + return nil +} + +func (d *Decoder) resetFragments() { + d.fragments = d.fragments[:0] + d.fragmentsSize = 0 +} + +func (d *Decoder) decodeOBUs(pkt *rtp.Packet) ([][]byte, error) { + if len(pkt.Payload) < 2 { + return nil, fmt.Errorf("invalid payload size") + } + + z := (pkt.Payload[0] & 0b10000000) != 0 + y := (pkt.Payload[0] & 0b01000000) != 0 + w := (pkt.Payload[0] >> 4) & 0b11 + payload := pkt.Payload[1:] + var obus [][]byte + + for len(payload) > 0 { + var obu []byte + + if w == 0 || byte(len(obus)) < (w-1) { + var size av1.LEB128 + n, err := size.Unmarshal(payload) + if err != nil { + d.resetFragments() + return nil, err + } + payload = payload[n:] + + if size == 0 || len(payload) < int(size) { + d.resetFragments() + return nil, fmt.Errorf("invalid OBU size") + } + + obu, payload = payload[:size], payload[size:] + } else { + obu, payload = payload, nil + } + + obus = append(obus, obu) + } + + if w != 0 && len(obus) != int(w) { + return nil, fmt.Errorf("invalid W field") + } + + // first OBU is continuation of previous one + if z { + if d.fragmentsSize == 0 { + if !d.firstPacketReceived { + return nil, ErrNonStartingPacketAndNoPrevious + } + + return nil, fmt.Errorf("received a subsequent fragment without previous fragments") + } + + d.firstPacketReceived = true + + if pkt.SequenceNumber != d.fragmentNextSeqNum { + d.resetFragments() + return nil, fmt.Errorf("discarding frame since a RTP packet is missing") + } + + d.fragmentsSize += len(obus[0]) + + if d.fragmentsSize > av1.MaxTemporalUnitSize { + errSize := d.fragmentsSize + d.resetFragments() + return nil, fmt.Errorf("temporal unit size (%d) is too big, maximum is %d", + errSize, av1.MaxTemporalUnitSize) + } + + d.fragments = append(d.fragments, obus[0]) + d.fragmentNextSeqNum++ + + if len(obus) == 1 && y { + return nil, ErrMorePacketsNeeded + } + + obus[0] = joinFragments(d.fragments, d.fragmentsSize) + d.resetFragments() + } else { + d.firstPacketReceived = true + } + + // last OBU will continue in next packet + if y { + var obu []byte + obu, obus = obus[len(obus)-1], obus[:len(obus)-1] + + d.fragmentsSize = len(obu) + d.fragments = append(d.fragments, obu) + d.fragmentNextSeqNum = pkt.SequenceNumber + 1 + + if len(obus) == 0 { + return nil, ErrMorePacketsNeeded + } + } + + return obus, nil +} + +// Decode decodes a temporal unit from a RTP packet. +func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, error) { + obus, err := d.decodeOBUs(pkt) + if err != nil { + return nil, err + } + l := len(obus) + + if (d.frameBufferLen + l) > av1.MaxOBUsPerTemporalUnit { + errCount := d.frameBufferLen + l + d.frameBuffer = nil + d.frameBufferLen = 0 + d.frameBufferSize = 0 + return nil, fmt.Errorf("OBU count (%d) exceeds maximum allowed (%d)", + errCount, av1.MaxOBUsPerTemporalUnit) + } + + addSize := tuSize(obus) + + if (d.frameBufferSize + addSize) > av1.MaxTemporalUnitSize { + errSize := d.frameBufferSize + addSize + d.frameBuffer = nil + d.frameBufferLen = 0 + d.frameBufferSize = 0 + return nil, fmt.Errorf("temporal unit size (%d) is too big, maximum is %d", + errSize, av1.MaxOBUsPerTemporalUnit) + } + + d.frameBuffer = append(d.frameBuffer, obus...) + d.frameBufferLen += l + d.frameBufferSize += addSize + + if !pkt.Marker { + return nil, ErrMorePacketsNeeded + } + + ret := d.frameBuffer + + // do not reuse frameBuffer to avoid race conditions + d.frameBuffer = nil + d.frameBufferLen = 0 + d.frameBufferSize = 0 + + return ret, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1/encoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1/encoder.go new file mode 100644 index 000000000..161616fea --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1/encoder.go @@ -0,0 +1,171 @@ +package rtpav1 + +import ( + "crypto/rand" + + "github.com/bluenviron/mediacommon/v2/pkg/codecs/av1" + "github.com/pion/rtp" +) + +const ( + rtpVersion = 2 + defaultPayloadMaxSize = 1460 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header) +) + +func randUint32() (uint32, error) { + var b [4]byte + _, err := rand.Read(b[:]) + if err != nil { + return 0, err + } + return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil +} + +// Encoder is a RTP/AV1 encoder. +// Specification: https://aomediacodec.github.io/av1-rtp-spec/ +type Encoder struct { + // payload type of packets. + PayloadType uint8 + + // SSRC of packets (optional). + // It defaults to a random value. + SSRC *uint32 + + // initial sequence number of packets (optional). + // It defaults to a random value. + InitialSequenceNumber *uint16 + + // maximum size of packet payloads (optional). + // It defaults to 1460. + PayloadMaxSize int + + sequenceNumber uint16 +} + +// Init initializes the encoder. +func (e *Encoder) Init() error { + if e.SSRC == nil { + v, err := randUint32() + if err != nil { + return err + } + e.SSRC = &v + } + if e.InitialSequenceNumber == nil { + v, err := randUint32() + if err != nil { + return err + } + v2 := uint16(v) + e.InitialSequenceNumber = &v2 + } + if e.PayloadMaxSize == 0 { + e.PayloadMaxSize = defaultPayloadMaxSize + } + + e.sequenceNumber = *e.InitialSequenceNumber + return nil +} + +// Encode encodes OBUs into RTP packets. +func (e *Encoder) Encode(obus [][]byte) ([]*rtp.Packet, error) { + var curPacket *rtp.Packet + var packets []*rtp.Packet + obusInPacket := 0 + + createNewPacket := func(z bool) { + curPacket = &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: e.PayloadType, + SequenceNumber: e.sequenceNumber, + SSRC: *e.SSRC, + }, + Payload: []byte{0}, + } + e.sequenceNumber++ + packets = append(packets, curPacket) + obusInPacket = 0 + + if z { + curPacket.Payload[0] |= 1 << 7 + } + } + + finalizeCurPacket := func(y bool) { + if y { + curPacket.Payload[0] |= 1 << 6 + } + } + + createNewPacket(false) + + maxFragmentedLEBSize := av1.LEB128(e.PayloadMaxSize).MarshalSize() + + for i, obu := range obus { + for { + avail := e.PayloadMaxSize - len(curPacket.Payload) + obuLen := len(obu) + omitSize := (i == (len(obus)-1) && obusInPacket < 3) + + var obuLenLEB av1.LEB128 + var obuLenLEBSize int + var needed int + + if omitSize { + needed = obuLen + } else { + obuLenLEB = av1.LEB128(obuLen) + obuLenLEBSize = obuLenLEB.MarshalSize() + needed = obuLen + obuLenLEBSize + } + + if needed <= avail { + if omitSize { + curPacket.Payload[0] |= byte((obusInPacket + 1) << 4) // W + curPacket.Payload = append(curPacket.Payload, obu...) + } else { + buf := make([]byte, obuLenLEBSize) + obuLenLEB.MarshalTo(buf) + curPacket.Payload = append(curPacket.Payload, buf...) + curPacket.Payload = append(curPacket.Payload, obu...) + obusInPacket++ + } + break + } + + if omitSize { + if avail > 0 { + curPacket.Payload[0] |= byte((obusInPacket + 1) << 4) // W + curPacket.Payload = append(curPacket.Payload, obu[:avail]...) + obu = obu[avail:] + } + } else { + if avail > maxFragmentedLEBSize { + fragmentLen := avail - maxFragmentedLEBSize + fragmentLenLEB := av1.LEB128(fragmentLen) + fragmentLenLEBSize := fragmentLenLEB.MarshalSize() + + buf := make([]byte, fragmentLenLEBSize) + fragmentLenLEB.MarshalTo(buf) + curPacket.Payload = append(curPacket.Payload, buf...) + curPacket.Payload = append(curPacket.Payload, obu[:fragmentLen]...) + obu = obu[fragmentLen:] + } + } + + finalizeCurPacket(true) + createNewPacket(true) + } + } + + finalizeCurPacket(false) + + if av1.IsRandomAccess2(obus) { + packets[0].Payload[0] |= 1 << 3 + } + + packets[len(packets)-1].Marker = true + + return packets, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1/rtpav1.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1/rtpav1.go new file mode 100644 index 000000000..161b1affc --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1/rtpav1.go @@ -0,0 +1,2 @@ +// Package rtpav1 contains a RTP/AV1 decoder and encoder. +package rtpav1 diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph264/decoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph264/decoder.go new file mode 100644 index 000000000..725ecd5ac --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph264/decoder.go @@ -0,0 +1,306 @@ +package rtph264 + +import ( + "bytes" + "errors" + "fmt" + + "github.com/pion/rtp" + + "github.com/bluenviron/mediacommon/v2/pkg/codecs/h264" +) + +// ErrMorePacketsNeeded is returned when more packets are needed. +var ErrMorePacketsNeeded = errors.New("need more packets") + +// ErrNonStartingPacketAndNoPrevious is returned when we received a non-starting +// packet of a fragmented NALU and we didn't received anything before. +// It's normal to receive this when decoding a stream that has been already +// running for some time. +var ErrNonStartingPacketAndNoPrevious = errors.New( + "received a non-starting fragment without any previous starting fragment") + +func joinFragments(fragments [][]byte, size int) []byte { + ret := make([]byte, size) + n := 0 + for _, p := range fragments { + n += copy(ret[n:], p) + } + return ret +} + +func isAllZero(buf []byte) bool { + for _, b := range buf { + if b != 0 { + return false + } + } + return true +} + +func auSize(au [][]byte) int { + s := 0 + for _, nalu := range au { + s += len(nalu) + } + return s +} + +// Decoder is a RTP/H264 decoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc6184 +type Decoder struct { + // indicates the packetization mode. + PacketizationMode int + + firstPacketReceived bool + fragments [][]byte + fragmentsSize int + fragmentNextSeqNum uint16 + annexBMode bool + + // for Decode() + frameBuffer [][]byte + frameBufferLen int + frameBufferSize int + frameBufferTimestamp uint32 +} + +// Init initializes the decoder. +func (d *Decoder) Init() error { + if d.PacketizationMode >= 2 { + return fmt.Errorf("PacketizationMode >= 2 is not supported") + } + return nil +} + +func (d *Decoder) resetFragments() { + d.fragments = d.fragments[:0] + d.fragmentsSize = 0 +} + +func (d *Decoder) decodeNALUs(pkt *rtp.Packet) ([][]byte, error) { + if len(pkt.Payload) < 1 { + d.resetFragments() + return nil, fmt.Errorf("payload is too short") + } + + typ := h264.NALUType(pkt.Payload[0] & 0x1F) + var nalus [][]byte + + switch typ { + case h264.NALUTypeFUA: + if len(pkt.Payload) < 2 { + return nil, fmt.Errorf("invalid FU-A packet (invalid size)") + } + + start := pkt.Payload[1] >> 7 + end := (pkt.Payload[1] >> 6) & 0x01 + + if start == 1 { + d.resetFragments() + + nri := (pkt.Payload[0] >> 5) & 0x03 + typ := pkt.Payload[1] & 0x1F + d.fragmentsSize = len(pkt.Payload[1:]) + d.fragments = append(d.fragments, []byte{(nri << 5) | typ}, pkt.Payload[2:]) + d.fragmentNextSeqNum = pkt.SequenceNumber + 1 + d.firstPacketReceived = true + + // RFC 6184 clearly states: + // + // A fragmented NAL unit MUST NOT be transmitted in one FU; that is, the + // Start bit and End bit MUST NOT both be set to one in the same FU + // header. + // + // However, some vendors camera (e.g. CostarHD) have been observed to nevertheless + // emit one fragmented NAL unit for sufficiently small P-frames. + if end != 0 { + nalus = [][]byte{joinFragments(d.fragments, d.fragmentsSize)} + d.resetFragments() + break + } + + return nil, ErrMorePacketsNeeded + } + + if d.fragmentsSize == 0 { + if !d.firstPacketReceived { + return nil, ErrNonStartingPacketAndNoPrevious + } + + return nil, fmt.Errorf("invalid FU-A packet (non-starting)") + } + + if pkt.SequenceNumber != d.fragmentNextSeqNum { + d.resetFragments() + return nil, fmt.Errorf("discarding frame since a RTP packet is missing") + } + + d.fragmentsSize += len(pkt.Payload[2:]) + + if d.fragmentsSize > h264.MaxAccessUnitSize { + errSize := d.fragmentsSize + d.resetFragments() + return nil, fmt.Errorf("NALU size (%d) is too big, maximum is %d", + errSize, h264.MaxAccessUnitSize) + } + + d.fragments = append(d.fragments, pkt.Payload[2:]) + d.fragmentNextSeqNum++ + + if end != 1 { + return nil, ErrMorePacketsNeeded + } + + nalus = [][]byte{joinFragments(d.fragments, d.fragmentsSize)} + d.resetFragments() + + case h264.NALUTypeSTAPA: + d.resetFragments() + + payload := pkt.Payload[1:] + + for { + if len(payload) < 2 { + return nil, fmt.Errorf("invalid STAP-A packet (invalid size)") + } + + size := uint16(payload[0])<<8 | uint16(payload[1]) + payload = payload[2:] + + if size == 0 { + // discard padding + if isAllZero(payload) { + break + } + + return nil, fmt.Errorf("invalid STAP-A packet (invalid size)") + } + + if int(size) > len(payload) { + return nil, fmt.Errorf("invalid STAP-A packet (invalid size)") + } + + nalus = append(nalus, payload[:size]) + payload = payload[size:] + + if len(payload) == 0 { + break + } + } + + if nalus == nil { + return nil, fmt.Errorf("STAP-A packet doesn't contain any NALU") + } + + d.firstPacketReceived = true + + case h264.NALUTypeSTAPB, h264.NALUTypeMTAP16, + h264.NALUTypeMTAP24, h264.NALUTypeFUB: + d.resetFragments() + d.firstPacketReceived = true + return nil, fmt.Errorf("packet type not supported (%v)", typ) + + default: + d.resetFragments() + d.firstPacketReceived = true + nalus = [][]byte{pkt.Payload} + } + + nalus, err := d.removeAnnexB(nalus) + if err != nil { + return nil, err + } + + return nalus, nil +} + +// Decode decodes an access unit from a RTP packet. +func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, error) { + nalus, err := d.decodeNALUs(pkt) + if err != nil { + return nil, err + } + l := len(nalus) + + // support splitting access units by timestamp. + // (some cameras do not use the Marker field, like the FLIR M400) + if d.frameBuffer != nil && pkt.Timestamp != d.frameBufferTimestamp { + ret := d.frameBuffer + d.resetFrameBuffer() + + err = d.addToFrameBuffer(nalus, l, pkt.Timestamp) + if err != nil { + return nil, err + } + + return ret, nil + } + + err = d.addToFrameBuffer(nalus, l, pkt.Timestamp) + if err != nil { + return nil, err + } + + if !pkt.Marker { + return nil, ErrMorePacketsNeeded + } + + ret := d.frameBuffer + d.resetFrameBuffer() + + return ret, nil +} + +func (d *Decoder) resetFrameBuffer() { + d.frameBuffer = nil // do not reuse frameBuffer to avoid race conditions + d.frameBufferLen = 0 + d.frameBufferSize = 0 +} + +func (d *Decoder) addToFrameBuffer(nalus [][]byte, l int, ts uint32) error { + if (d.frameBufferLen + l) > h264.MaxNALUsPerAccessUnit { + errCount := d.frameBufferLen + l + d.resetFrameBuffer() + return fmt.Errorf("NALU count (%d) exceeds maximum allowed (%d)", + errCount, h264.MaxNALUsPerAccessUnit) + } + + addSize := auSize(nalus) + + if (d.frameBufferSize + addSize) > h264.MaxAccessUnitSize { + errSize := d.frameBufferSize + addSize + d.resetFrameBuffer() + return fmt.Errorf("access unit size (%d) is too big, maximum is %d", + errSize, h264.MaxAccessUnitSize) + } + + d.frameBuffer = append(d.frameBuffer, nalus...) + d.frameBufferLen += l + d.frameBufferSize += addSize + d.frameBufferTimestamp = ts + return nil +} + +// some cameras / servers wrap NALUs into Annex-B +func (d *Decoder) removeAnnexB(nalus [][]byte) ([][]byte, error) { + if len(nalus) == 1 { + nalu := nalus[0] + + if !d.annexBMode && bytes.Contains(nalu, []byte{0x00, 0x00, 0x00, 0x01}) { + d.annexBMode = true + } + + if d.annexBMode { + if !bytes.HasPrefix(nalu, []byte{0x00, 0x00, 0x00, 0x01}) { + nalu = append([]byte{0x00, 0x00, 0x00, 0x01}, nalu...) + } + + var annexb h264.AnnexB + err := annexb.Unmarshal(nalu) + return annexb, err + } + } + + return nalus, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph264/encoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph264/encoder.go new file mode 100644 index 000000000..55938ea25 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph264/encoder.go @@ -0,0 +1,248 @@ +package rtph264 + +import ( + "crypto/rand" + "fmt" + + "github.com/pion/rtp" + + "github.com/bluenviron/mediacommon/v2/pkg/codecs/h264" +) + +const ( + rtpVersion = 2 + defaultPayloadMaxSize = 1460 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header) +) + +func randUint32() (uint32, error) { + var b [4]byte + _, err := rand.Read(b[:]) + if err != nil { + return 0, err + } + return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil +} + +func lenAggregated(nalus [][]byte, addNALU []byte) int { + n := 1 // header + + for _, nalu := range nalus { + n += 2 // size + n += len(nalu) // nalu + } + + if addNALU != nil { + n += 2 // size + n += len(addNALU) // nalu + } + + return n +} + +func packetCount(avail, le int) int { + n := le / avail + if (le % avail) != 0 { + n++ + } + return n +} + +// Encoder is a RTP/H264 encoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc6184 +type Encoder struct { + // payload type of packets. + PayloadType uint8 + + // SSRC of packets (optional). + // It defaults to a random value. + SSRC *uint32 + + // initial sequence number of packets (optional). + // It defaults to a random value. + InitialSequenceNumber *uint16 + + // maximum size of packet payloads (optional). + // It defaults to 1460. + PayloadMaxSize int + + PacketizationMode int + + sequenceNumber uint16 +} + +// Init initializes the encoder. +func (e *Encoder) Init() error { + if e.PacketizationMode >= 2 { + return fmt.Errorf("PacketizationMode >= 2 is not supported") + } + + if e.SSRC == nil { + v, err := randUint32() + if err != nil { + return err + } + e.SSRC = &v + } + if e.InitialSequenceNumber == nil { + v, err := randUint32() + if err != nil { + return err + } + v2 := uint16(v) + e.InitialSequenceNumber = &v2 + } + if e.PayloadMaxSize == 0 { + e.PayloadMaxSize = defaultPayloadMaxSize + } + + e.sequenceNumber = *e.InitialSequenceNumber + return nil +} + +// Encode encodes an access unit into RTP/H264 packets. +func (e *Encoder) Encode(au [][]byte) ([]*rtp.Packet, error) { + var rets []*rtp.Packet + var batch [][]byte + + // split NALUs into batches + for _, nalu := range au { + if lenAggregated(batch, nalu) <= e.PayloadMaxSize { + // add to existing batch + batch = append(batch, nalu) + } else { + // write current batch + if batch != nil { + pkts, err := e.writeBatch(batch, false) + if err != nil { + return nil, err + } + rets = append(rets, pkts...) + } + + // initialize new batch + batch = [][]byte{nalu} + } + } + + // write final batch + // marker is used to indicate when all NALUs with same PTS have been sent + pkts, err := e.writeBatch(batch, true) + if err != nil { + return nil, err + } + rets = append(rets, pkts...) + + return rets, nil +} + +func (e *Encoder) writeBatch(nalus [][]byte, marker bool) ([]*rtp.Packet, error) { + if len(nalus) == 1 { + // the NALU fits into a single RTP packet + if len(nalus[0]) < e.PayloadMaxSize { + return e.writeSingle(nalus[0], marker) + } + + // split the NALU into multiple fragmentation packet + return e.writeFragmented(nalus[0], marker) + } + + return e.writeAggregated(nalus, marker) +} + +func (e *Encoder) writeSingle(nalu []byte, marker bool) ([]*rtp.Packet, error) { + pkt := &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: e.PayloadType, + SequenceNumber: e.sequenceNumber, + SSRC: *e.SSRC, + Marker: marker, + }, + Payload: nalu, + } + + e.sequenceNumber++ + + return []*rtp.Packet{pkt}, nil +} + +func (e *Encoder) writeFragmented(nalu []byte, marker bool) ([]*rtp.Packet, error) { + // use only FU-A, not FU-B, since we always use non-interleaved mode + // (packetization-mode=1) + avail := e.PayloadMaxSize - 2 + le := len(nalu) - 1 + packetCount := packetCount(avail, le) + + ret := make([]*rtp.Packet, packetCount) + + nri := (nalu[0] >> 5) & 0x03 + typ := nalu[0] & 0x1F + nalu = nalu[1:] // remove header + le = avail + start := uint8(1) + end := uint8(0) + + for i := range ret { + if i == (packetCount - 1) { + end = 1 + le = len(nalu) + } + + data := make([]byte, 2+le) + data[0] = (nri << 5) | uint8(h264.NALUTypeFUA) + data[1] = (start << 7) | (end << 6) | typ + copy(data[2:], nalu) + nalu = nalu[le:] + + ret[i] = &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: e.PayloadType, + SequenceNumber: e.sequenceNumber, + SSRC: *e.SSRC, + Marker: (i == (packetCount-1) && marker), + }, + Payload: data, + } + + e.sequenceNumber++ + start = 0 + } + + return ret, nil +} + +func (e *Encoder) writeAggregated(nalus [][]byte, marker bool) ([]*rtp.Packet, error) { + payload := make([]byte, lenAggregated(nalus, nil)) + + // header + payload[0] = uint8(h264.NALUTypeSTAPA) + pos := 1 + + for _, nalu := range nalus { + // size + naluLen := len(nalu) + payload[pos] = uint8(naluLen >> 8) + payload[pos+1] = uint8(naluLen) + pos += 2 + + // nalu + copy(payload[pos:], nalu) + pos += naluLen + } + + pkt := &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: e.PayloadType, + SequenceNumber: e.sequenceNumber, + SSRC: *e.SSRC, + Marker: marker, + }, + Payload: payload, + } + + e.sequenceNumber++ + + return []*rtp.Packet{pkt}, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph264/rtph264.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph264/rtph264.go new file mode 100644 index 000000000..ab0c3f6fd --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph264/rtph264.go @@ -0,0 +1,2 @@ +// Package rtph264 contains a RTP/H264 decoder and encoder. +package rtph264 diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph265/decoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph265/decoder.go new file mode 100644 index 000000000..837af18ec --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph265/decoder.go @@ -0,0 +1,220 @@ +package rtph265 + +import ( + "errors" + "fmt" + + "github.com/pion/rtp" + + "github.com/bluenviron/mediacommon/v2/pkg/codecs/h265" +) + +// ErrMorePacketsNeeded is returned when more packets are needed. +var ErrMorePacketsNeeded = errors.New("need more packets") + +// ErrNonStartingPacketAndNoPrevious is returned when we received a non-starting +// packet of a fragmented NALU and we didn't received anything before. +// It's normal to receive this when decoding a stream that has been already +// running for some time. +var ErrNonStartingPacketAndNoPrevious = errors.New( + "received a non-starting fragment without any previous starting fragment") + +func joinFragments(fragments [][]byte, size int) []byte { + ret := make([]byte, size) + n := 0 + for _, p := range fragments { + n += copy(ret[n:], p) + } + return ret +} + +func auSize(au [][]byte) int { + s := 0 + for _, nalu := range au { + s += len(nalu) + } + return s +} + +// Decoder is a RTP/H265 decoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc7798 +type Decoder struct { + // indicates that NALUs have an additional field that specifies the decoding order. + MaxDONDiff int + + firstPacketReceived bool + fragments [][]byte + fragmentsSize int + fragmentNextSeqNum uint16 + + // for Decode() + frameBuffer [][]byte + frameBufferLen int + frameBufferSize int +} + +// Init initializes the decoder. +func (d *Decoder) Init() error { + if d.MaxDONDiff != 0 { + return fmt.Errorf("MaxDONDiff != 0 is not supported (yet)") + } + return nil +} + +func (d *Decoder) resetFragments() { + d.fragments = d.fragments[:0] + d.fragmentsSize = 0 +} + +func (d *Decoder) decodeNALUs(pkt *rtp.Packet) ([][]byte, error) { + if len(pkt.Payload) < 2 { + d.resetFragments() + return nil, fmt.Errorf("payload is too short") + } + + typ := h265.NALUType((pkt.Payload[0] >> 1) & 0b111111) + var nalus [][]byte + + switch typ { + case h265.NALUType_AggregationUnit: + d.resetFragments() + + payload := pkt.Payload[2:] + + for { + if len(payload) < 2 { + return nil, fmt.Errorf("invalid aggregation unit (invalid size)") + } + + size := uint16(payload[0])<<8 | uint16(payload[1]) + payload = payload[2:] + + if size == 0 || int(size) > len(payload) { + return nil, fmt.Errorf("invalid aggregation unit (invalid size)") + } + + nalus = append(nalus, payload[:size]) + payload = payload[size:] + + if len(payload) == 0 { + break + } + } + + d.firstPacketReceived = true + + case h265.NALUType_FragmentationUnit: + if len(pkt.Payload) < 3 { + d.resetFragments() + return nil, fmt.Errorf("payload is too short") + } + + start := pkt.Payload[2] >> 7 + end := (pkt.Payload[2] >> 6) & 0x01 + + if start == 1 { + d.resetFragments() + + if end != 0 { + return nil, fmt.Errorf("invalid fragmentation unit (can't contain both a start and end bit)") + } + + typ := pkt.Payload[2] & 0b111111 + head := uint16(pkt.Payload[0]&0b10000001)<<8 | uint16(typ)<<9 | uint16(pkt.Payload[1]) + d.fragmentsSize = len(pkt.Payload[1:]) + d.fragments = append(d.fragments, []byte{byte(head >> 8), byte(head)}, pkt.Payload[3:]) + d.fragmentNextSeqNum = pkt.SequenceNumber + 1 + d.firstPacketReceived = true + + return nil, ErrMorePacketsNeeded + } + + if d.fragmentsSize == 0 { + if !d.firstPacketReceived { + return nil, ErrNonStartingPacketAndNoPrevious + } + + return nil, fmt.Errorf("invalid fragmentation unit (non-starting)") + } + + if pkt.SequenceNumber != d.fragmentNextSeqNum { + d.resetFragments() + return nil, fmt.Errorf("discarding frame since a RTP packet is missing") + } + + d.fragmentsSize += len(pkt.Payload[3:]) + + if d.fragmentsSize > h265.MaxAccessUnitSize { + errSize := d.fragmentsSize + d.resetFragments() + return nil, fmt.Errorf("NALU size (%d) is too big, maximum is %d", + errSize, h265.MaxAccessUnitSize) + } + + d.fragments = append(d.fragments, pkt.Payload[3:]) + d.fragmentNextSeqNum++ + + if end != 1 { + return nil, ErrMorePacketsNeeded + } + + nalus = [][]byte{joinFragments(d.fragments, d.fragmentsSize)} + d.resetFragments() + + case h265.NALUType_PACI: + d.resetFragments() + return nil, fmt.Errorf("PACI packets are not supported (yet)") + + default: + d.resetFragments() + nalus = [][]byte{pkt.Payload} + } + + return nalus, nil +} + +// Decode decodes an access unit from a RTP packet. +func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, error) { + nalus, err := d.decodeNALUs(pkt) + if err != nil { + return nil, err + } + l := len(nalus) + + if (d.frameBufferLen + l) > h265.MaxNALUsPerAccessUnit { + errCount := d.frameBufferLen + l + d.frameBuffer = nil + d.frameBufferLen = 0 + d.frameBufferSize = 0 + return nil, fmt.Errorf("NALU count (%d) exceeds maximum allowed (%d)", + errCount, h265.MaxNALUsPerAccessUnit) + } + + addSize := auSize(nalus) + + if (d.frameBufferSize + addSize) > h265.MaxAccessUnitSize { + errSize := d.frameBufferSize + addSize + d.frameBuffer = nil + d.frameBufferLen = 0 + d.frameBufferSize = 0 + return nil, fmt.Errorf("access unit size (%d) is too big, maximum is %d", + errSize, h265.MaxAccessUnitSize) + } + + d.frameBuffer = append(d.frameBuffer, nalus...) + d.frameBufferLen += l + d.frameBufferSize += addSize + + if !pkt.Marker { + return nil, ErrMorePacketsNeeded + } + + ret := d.frameBuffer + + // do not reuse frameBuffer to avoid race conditions + d.frameBuffer = nil + d.frameBufferLen = 0 + d.frameBufferSize = 0 + + return ret, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph265/encoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph265/encoder.go new file mode 100644 index 000000000..c0d789146 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph265/encoder.go @@ -0,0 +1,247 @@ +package rtph265 + +import ( + "crypto/rand" + "fmt" + + "github.com/pion/rtp" +) + +const ( + rtpVersion = 2 + defaultPayloadMaxSize = 1460 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header) +) + +func randUint32() (uint32, error) { + var b [4]byte + _, err := rand.Read(b[:]) + if err != nil { + return 0, err + } + return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil +} + +func packetCount(avail, le int) int { + n := le / avail + if (le % avail) != 0 { + n++ + } + return n +} + +// Encoder is a RTP/H265 encoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc7798 +type Encoder struct { + // payload type of packets. + PayloadType uint8 + + // SSRC of packets (optional). + // It defaults to a random value. + SSRC *uint32 + + // initial sequence number of packets (optional). + // It defaults to a random value. + InitialSequenceNumber *uint16 + + // maximum size of packet payloads (optional). + // It defaults to 1460. + PayloadMaxSize int + + // indicates that NALUs have an additional field that specifies the decoding order. + MaxDONDiff int + + sequenceNumber uint16 +} + +// Init initializes the encoder. +func (e *Encoder) Init() error { + if e.MaxDONDiff != 0 { + return fmt.Errorf("MaxDONDiff != 0 is not supported (yet)") + } + + if e.SSRC == nil { + v, err := randUint32() + if err != nil { + return err + } + e.SSRC = &v + } + if e.InitialSequenceNumber == nil { + v, err := randUint32() + if err != nil { + return err + } + v2 := uint16(v) + e.InitialSequenceNumber = &v2 + } + if e.PayloadMaxSize == 0 { + e.PayloadMaxSize = defaultPayloadMaxSize + } + + e.sequenceNumber = *e.InitialSequenceNumber + return nil +} + +// Encode encodes an access unit into RTP/H265 packets. +func (e *Encoder) Encode(au [][]byte) ([]*rtp.Packet, error) { + var rets []*rtp.Packet + var batch [][]byte + + // split NALUs into batches + for _, nalu := range au { + if e.lenAggregationUnit(batch, nalu) <= e.PayloadMaxSize { + // add to existing batch + batch = append(batch, nalu) + } else { + // write batch + if batch != nil { + pkts, err := e.writeBatch(batch, false) + if err != nil { + return nil, err + } + rets = append(rets, pkts...) + } + + // initialize new batch + batch = [][]byte{nalu} + } + } + + // write final batch + // marker is used to indicate that the entire access unit has been sent + pkts, err := e.writeBatch(batch, true) + if err != nil { + return nil, err + } + rets = append(rets, pkts...) + + return rets, nil +} + +func (e *Encoder) writeBatch(nalus [][]byte, marker bool) ([]*rtp.Packet, error) { + if len(nalus) == 1 { + // the NALU fits into a single RTP packet + if len(nalus[0]) < e.PayloadMaxSize { + return e.writeSingle(nalus[0], marker) + } + + // split the NALU into multiple fragmentation packet + return e.writeFragmentationUnits(nalus[0], marker) + } + + return e.writeAggregationUnit(nalus, marker) +} + +func (e *Encoder) writeSingle(nalu []byte, marker bool) ([]*rtp.Packet, error) { + pkt := &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: e.PayloadType, + SequenceNumber: e.sequenceNumber, + SSRC: *e.SSRC, + Marker: marker, + }, + Payload: nalu, + } + + e.sequenceNumber++ + + return []*rtp.Packet{pkt}, nil +} + +func (e *Encoder) writeFragmentationUnits(nalu []byte, marker bool) ([]*rtp.Packet, error) { + avail := e.PayloadMaxSize - 3 + le := len(nalu) - 2 + packetCount := packetCount(avail, le) + + ret := make([]*rtp.Packet, packetCount) + + head := nalu[:2] + nalu = nalu[2:] + le = avail + start := uint8(1) + end := uint8(0) + + for i := range ret { + if i == (packetCount - 1) { + le = len(nalu) + end = 1 + } + + data := make([]byte, 3+le) + data[0] = head[0]&0b10000001 | 49<<1 + data[1] = head[1] + data[2] = (start << 7) | (end << 6) | (head[0]>>1)&0b111111 + copy(data[3:], nalu) + nalu = nalu[le:] + + ret[i] = &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: e.PayloadType, + SequenceNumber: e.sequenceNumber, + SSRC: *e.SSRC, + Marker: (i == (packetCount-1) && marker), + }, + Payload: data, + } + + e.sequenceNumber++ + start = 0 + } + + return ret, nil +} + +func (e *Encoder) lenAggregationUnit(nalus [][]byte, addNALU []byte) int { + ret := 2 // header + + for _, nalu := range nalus { + ret += 2 // size + ret += len(nalu) // nalu + } + + if addNALU != nil { + ret += 2 // size + ret += len(addNALU) // nalu + } + + return ret +} + +func (e *Encoder) writeAggregationUnit(nalus [][]byte, marker bool) ([]*rtp.Packet, error) { + payload := make([]byte, e.lenAggregationUnit(nalus, nil)) + + // header + h := uint16(48) << 9 + payload[0] = byte(h >> 8) + payload[1] = byte(h) + pos := 2 + + for _, nalu := range nalus { + // size + naluLen := len(nalu) + payload[pos] = uint8(naluLen >> 8) + payload[pos+1] = uint8(naluLen) + pos += 2 + + // nalu + copy(payload[pos:], nalu) + pos += naluLen + } + + pkt := &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: e.PayloadType, + SequenceNumber: e.sequenceNumber, + SSRC: *e.SSRC, + Marker: marker, + }, + Payload: payload, + } + + e.sequenceNumber++ + + return []*rtp.Packet{pkt}, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph265/rtph265.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph265/rtph265.go new file mode 100644 index 000000000..7e1474cd4 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtph265/rtph265.go @@ -0,0 +1,2 @@ +// Package rtph265 contains a RTP/H265 decoder and encoder. +package rtph265 diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm/decoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm/decoder.go new file mode 100644 index 000000000..535a472fd --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm/decoder.go @@ -0,0 +1,33 @@ +package rtplpcm + +import ( + "fmt" + + "github.com/pion/rtp" +) + +// Decoder is a RTP/LPCM decoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc3190 +type Decoder struct { + BitDepth int + ChannelCount int + + sampleSize int +} + +// Init initializes the decoder. +func (d *Decoder) Init() error { + d.sampleSize = d.BitDepth * d.ChannelCount / 8 + return nil +} + +// Decode decodes audio samples from a RTP packet. +// It returns audio samples and PTS of the first sample. +func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, error) { + plen := len(pkt.Payload) + if (plen % d.sampleSize) != 0 { + return nil, fmt.Errorf("received payload of wrong size") + } + + return pkt.Payload, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm/encoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm/encoder.go new file mode 100644 index 000000000..099fa57d9 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm/encoder.go @@ -0,0 +1,124 @@ +package rtplpcm + +import ( + "crypto/rand" + "fmt" + + "github.com/pion/rtp" +) + +const ( + rtpVersion = 2 + defaultPayloadMaxSize = 1460 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header) +) + +func randUint32() (uint32, error) { + var b [4]byte + _, err := rand.Read(b[:]) + if err != nil { + return 0, err + } + return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil +} + +// Encoder is a RTP/LPCM encoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc3190 +type Encoder struct { + // payload type of packets. + PayloadType uint8 + + // bit depth. + BitDepth int + + // channel count. + ChannelCount int + + // SSRC of packets (optional). + // It defaults to a random value. + SSRC *uint32 + + // initial sequence number of packets (optional). + // It defaults to a random value. + InitialSequenceNumber *uint16 + + // maximum size of packet payloads (optional). + // It defaults to 1460. + PayloadMaxSize int + + sequenceNumber uint16 + sampleSize int + maxPayloadSize int +} + +// Init initializes the encoder. +func (e *Encoder) Init() error { + if e.SSRC == nil { + v, err := randUint32() + if err != nil { + return err + } + e.SSRC = &v + } + if e.InitialSequenceNumber == nil { + v, err := randUint32() + if err != nil { + return err + } + v2 := uint16(v) + e.InitialSequenceNumber = &v2 + } + if e.PayloadMaxSize == 0 { + e.PayloadMaxSize = defaultPayloadMaxSize + } + + e.sequenceNumber = *e.InitialSequenceNumber + e.sampleSize = e.BitDepth * e.ChannelCount / 8 + e.maxPayloadSize = (e.PayloadMaxSize / e.sampleSize) * e.sampleSize + return nil +} + +func (e *Encoder) packetCount(slen int) int { + n := (slen / e.maxPayloadSize) + if (slen % e.maxPayloadSize) != 0 { + n++ + } + return n +} + +// Encode encodes audio samples into RTP packets. +func (e *Encoder) Encode(samples []byte) ([]*rtp.Packet, error) { + slen := len(samples) + if (slen % e.sampleSize) != 0 { + return nil, fmt.Errorf("invalid samples") + } + + packetCount := e.packetCount(slen) + ret := make([]*rtp.Packet, packetCount) + pos := 0 + payloadSize := e.maxPayloadSize + timestamp := uint32(0) + + for i := range ret { + if payloadSize > len(samples[pos:]) { + payloadSize = len(samples[pos:]) + } + + ret[i] = &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: e.PayloadType, + SequenceNumber: e.sequenceNumber, + Timestamp: timestamp, + SSRC: *e.SSRC, + Marker: false, + }, + Payload: samples[pos : pos+payloadSize], + } + + e.sequenceNumber++ + pos += payloadSize + timestamp += uint32(payloadSize / e.sampleSize) + } + + return ret, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm/rtplpcm.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm/rtplpcm.go new file mode 100644 index 000000000..c600ef3fd --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm/rtplpcm.go @@ -0,0 +1,2 @@ +// Package rtplpcm contains a RTP/LPCM decoder and encoder. +package rtplpcm diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/decoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/decoder.go new file mode 100644 index 000000000..366c9bece --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/decoder.go @@ -0,0 +1,304 @@ +package rtpmjpeg + +import ( + "errors" + "fmt" + + "github.com/pion/rtp" + + "github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg" +) + +// ErrMorePacketsNeeded is returned when more packets are needed. +var ErrMorePacketsNeeded = errors.New("need more packets") + +// ErrNonStartingPacketAndNoPrevious is returned when we received a non-starting +// fragment of an image and we didn't received anything before. +// It's normal to receive this when decoding a stream that has been already +// running for some time. +var ErrNonStartingPacketAndNoPrevious = errors.New( + "received a non-starting fragment without any previous starting fragment") + +var lumDcCodeLens = []byte{ + 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, +} + +var lumDcSymbols = []byte{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, +} + +var lumAcCodelens = []byte{ + 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d, +} + +var lumAcSymbols = []byte{ //nolint:dupl + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, + 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, + 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, + 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, + 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, + 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa, +} + +var chmDcCodelens = []byte{ + 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, +} + +var chmDcSymbols = []byte{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, +} + +var chmAcCodelens = []byte{ + 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77, +} + +var chmAcSymbols = []byte{ //nolint:dupl + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, + 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, + 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, + 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, + 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, + 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, + 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, + 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa, +} + +var lumaQuantizers = []int{ + 0x10, 0x0b, 0x0c, 0x0e, 0x0c, 0x0a, 0x10, 0x0e, + 0x0d, 0x0e, 0x12, 0x11, 0x10, 0x13, 0x18, 0x28, + 0x1a, 0x18, 0x16, 0x16, 0x18, 0x31, 0x23, 0x25, + 0x1d, 0x28, 0x3a, 0x33, 0x3d, 0x3c, 0x39, 0x33, + 0x38, 0x37, 0x40, 0x48, 0x5c, 0x4e, 0x40, 0x44, + 0x57, 0x45, 0x37, 0x38, 0x50, 0x6d, 0x51, 0x57, + 0x5f, 0x62, 0x67, 0x68, 0x67, 0x3e, 0x4d, 0x71, + 0x79, 0x70, 0x64, 0x78, 0x5c, 0x65, 0x67, 0x63, +} + +var chromaQuantizers = []int{ + 0x11, 0x12, 0x12, 0x18, 0x15, 0x18, 0x2f, 0x1a, + 0x1a, 0x2f, 0x63, 0x42, 0x38, 0x42, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, +} + +func makeQuantizationTables(q uint8) [][]byte { + var scale int + if q < 50 { + scale = 5000 / int(q) + } else { + scale = 200 - 2*int(q) + } + + tables := make([][]byte, 2) + + tables[0] = make([]byte, 64) + + for i := 0; i < 64; i++ { + v := (lumaQuantizers[i]*scale + 50) / 100 + if v > 255 { + v = 255 + } else if v == 0 { + v = 1 + } + tables[0][i] = byte(v) + } + + tables[1] = make([]byte, 64) + + for i := 0; i < 64; i++ { + v := (chromaQuantizers[i]*scale + 50) / 100 + if v > 255 { + v = 255 + } else if v == 0 { + v = 1 + } + tables[1][i] = byte(v) + } + + return tables +} + +func joinFragments(fragments [][]byte, size int) []byte { + ret := make([]byte, size) + n := 0 + for _, p := range fragments { + n += copy(ret[n:], p) + } + return ret +} + +// Decoder is a RTP/M-JPEG decoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc2435 +type Decoder struct { + firstPacketReceived bool + fragments [][]byte + fragmentsSize int + firstJpegHeader *headerJPEG + quantizationTables [][]byte +} + +// Init initializes the decoder. +func (d *Decoder) Init() error { + return nil +} + +func (d *Decoder) resetFragments() { + d.fragments = d.fragments[:0] + d.fragmentsSize = 0 +} + +// Decode decodes an image from a RTP packet. +func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, error) { + byts := pkt.Payload + + var jh headerJPEG + n, err := jh.unmarshal(byts) + if err != nil { + return nil, err + } + byts = byts[n:] + + if jh.Width > maxDimension { + return nil, fmt.Errorf("width of %d is not supported", jh.Width) + } + + if jh.Height > maxDimension { + return nil, fmt.Errorf("height of %d is not supported", jh.Height) + } + + if jh.FragmentOffset == 0 { + d.resetFragments() + d.firstPacketReceived = true + + if jh.Quantization >= 128 { + var hqt headerQuantizationTable + n, err := hqt.unmarshal(byts) + if err != nil { + return nil, err + } + d.quantizationTables = hqt.Tables + byts = byts[n:] + } else { + d.quantizationTables = makeQuantizationTables(jh.Quantization) + } + + d.fragments = append(d.fragments, byts) + d.fragmentsSize = len(byts) + d.firstJpegHeader = &jh + } else { + if int(jh.FragmentOffset) != d.fragmentsSize { + if !d.firstPacketReceived { + return nil, ErrNonStartingPacketAndNoPrevious + } + + d.resetFragments() + return nil, fmt.Errorf("received wrong fragment") + } + + d.fragmentsSize += len(byts) + d.fragments = append(d.fragments, byts) + } + + if !pkt.Marker { + return nil, ErrMorePacketsNeeded + } + + if d.fragmentsSize < 2 { + return nil, fmt.Errorf("invalid data") + } + + data := joinFragments(d.fragments, d.fragmentsSize) + d.resetFragments() + + var buf []byte + + buf = jpeg.StartOfImage{}.Marshal(buf) + + var dqt jpeg.DefineQuantizationTable + id := uint8(0) + for _, table := range d.quantizationTables { + dqt.Tables = append(dqt.Tables, jpeg.QuantizationTable{ + ID: id, + Data: table, + }) + id++ + } + buf = dqt.Marshal(buf) + + buf = jpeg.StartOfFrame1{ + Type: d.firstJpegHeader.Type, + Width: d.firstJpegHeader.Width, + Height: d.firstJpegHeader.Height, + QuantizationTableCount: id, + }.Marshal(buf) + + buf = jpeg.DefineHuffmanTable{ + Codes: lumDcCodeLens, + Symbols: lumDcSymbols, + TableNumber: 0, + TableClass: 0, + }.Marshal(buf) + + buf = jpeg.DefineHuffmanTable{ + Codes: lumAcCodelens, + Symbols: lumAcSymbols, + TableNumber: 0, + TableClass: 1, + }.Marshal(buf) + + buf = jpeg.DefineHuffmanTable{ + Codes: chmDcCodelens, + Symbols: chmDcSymbols, + TableNumber: 1, + TableClass: 0, + }.Marshal(buf) + + buf = jpeg.DefineHuffmanTable{ + Codes: chmAcCodelens, + Symbols: chmAcSymbols, + TableNumber: 1, + TableClass: 1, + }.Marshal(buf) + + buf = jpeg.StartOfScan{}.Marshal(buf) + + buf = append(buf, data...) + + if data[len(data)-2] != 0xFF || data[len(data)-1] != jpeg.MarkerEndOfImage { + buf = append(buf, []byte{0xFF, jpeg.MarkerEndOfImage}...) + } + + return buf, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/encoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/encoder.go new file mode 100644 index 000000000..b3bbdf60f --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/encoder.go @@ -0,0 +1,279 @@ +package rtpmjpeg + +import ( + "crypto/rand" + "fmt" + "sort" + + "github.com/pion/rtp" + + "github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg" +) + +const ( + rtpVersion = 2 + defaultPayloadMaxSize = 1460 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header) +) + +func randUint32() (uint32, error) { + var b [4]byte + _, err := rand.Read(b[:]) + if err != nil { + return 0, err + } + return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil +} + +// Encoder is a RTP/M-JPEG encoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc2435 +type Encoder struct { + // SSRC of packets (optional). + // It defaults to a random value. + SSRC *uint32 + + // initial sequence number of packets (optional). + // It defaults to a random value. + InitialSequenceNumber *uint16 + + // maximum size of packet payloads (optional). + // It defaults to 1460. + PayloadMaxSize int + + sequenceNumber uint16 +} + +// Init initializes the encoder. +func (e *Encoder) Init() error { + if e.SSRC == nil { + v, err := randUint32() + if err != nil { + return err + } + e.SSRC = &v + } + if e.InitialSequenceNumber == nil { + v, err := randUint32() + if err != nil { + return err + } + v2 := uint16(v) + e.InitialSequenceNumber = &v2 + } + if e.PayloadMaxSize == 0 { + e.PayloadMaxSize = defaultPayloadMaxSize + } + + e.sequenceNumber = *e.InitialSequenceNumber + return nil +} + +// Encode encodes an image into RTP/M-JPEG packets. +func (e *Encoder) Encode(image []byte) ([]*rtp.Packet, error) { + l := len(image) + if l < 2 || image[0] != 0xFF || image[1] != jpeg.MarkerStartOfImage { + return nil, fmt.Errorf("SOI not found") + } + + image = image[2:] + var sof *jpeg.StartOfFrame1 + var dri *jpeg.DefineRestartInterval + quantizationTables := make(map[uint8][]byte) + var data []byte + +outer: + for { + if len(image) < 2 { + break + } + + h0, h1 := image[0], image[1] + image = image[2:] + + if h0 != 0xFF { + return nil, fmt.Errorf("invalid image") + } + + switch h1 { + case 0xE0, 0xE1, 0xE2, // JFIF + jpeg.MarkerDefineHuffmanTable, + jpeg.MarkerComment: + mlen := int(image[0])<<8 | int(image[1]) + if len(image) < mlen { + return nil, fmt.Errorf("image is too short") + } + image = image[mlen:] + + case jpeg.MarkerDefineQuantizationTable: + mlen := int(image[0])<<8 | int(image[1]) + if len(image) < mlen { + return nil, fmt.Errorf("image is too short") + } + + var dqt jpeg.DefineQuantizationTable + err := dqt.Unmarshal(image[2:mlen]) + if err != nil { + return nil, err + } + image = image[mlen:] + + for _, t := range dqt.Tables { + quantizationTables[t.ID] = t.Data + } + + case jpeg.MarkerDefineRestartInterval: + mlen := int(image[0])<<8 | int(image[1]) + if len(image) < mlen { + return nil, fmt.Errorf("image is too short") + } + + dri = &jpeg.DefineRestartInterval{} + err := dri.Unmarshal(image[2:mlen]) + if err != nil { + return nil, err + } + image = image[mlen:] + + case jpeg.MarkerStartOfFrame1: + mlen := int(image[0])<<8 | int(image[1]) + if len(image) < mlen { + return nil, fmt.Errorf("image is too short") + } + + sof = &jpeg.StartOfFrame1{} + err := sof.Unmarshal(image[2:mlen]) + if err != nil { + return nil, err + } + image = image[mlen:] + + if sof.Width > maxDimension { + return nil, fmt.Errorf("an image with width of %d can't be sent with RTSP", sof.Width) + } + + if sof.Height > maxDimension { + return nil, fmt.Errorf("an image with height of %d can't be sent with RTSP", sof.Height) + } + + if (sof.Width % 8) != 0 { + return nil, fmt.Errorf("width must be multiple of 8") + } + + if (sof.Height % 8) != 0 { + return nil, fmt.Errorf("height must be multiple of 8") + } + + case jpeg.MarkerStartOfScan: + mlen := int(image[0])<<8 | int(image[1]) + if len(image) < mlen { + return nil, fmt.Errorf("image is too short") + } + + var sos jpeg.StartOfScan + err := sos.Unmarshal(image[2:mlen]) + if err != nil { + return nil, err + } + image = image[mlen:] + + data = image + break outer + + default: + return nil, fmt.Errorf("unknown marker: 0x%.2x", h1) + } + } + + if sof == nil { + return nil, fmt.Errorf("SOF not found") + } + + if sof.Type > 63 { + return nil, fmt.Errorf("JPEG type %d is not supported", sof.Type) + } + + if len(data) == 0 { + return nil, fmt.Errorf("image data not found") + } + + jh := headerJPEG{ + TypeSpecific: 0, + Type: sof.Type, + Quantization: 255, + Width: sof.Width, + Height: sof.Height, + } + + if dri != nil { + jh.Type += 64 + } + + first := true + offset := 0 + var ret []*rtp.Packet + + for { + var buf []byte + + jh.FragmentOffset = uint32(offset) + buf = jh.marshal(buf) + + if dri != nil { + buf = headerRestartMarker{ + Interval: dri.Interval, + Count: 0xFFFF, + }.marshal(buf) + } + + if first { + first = false + + qth := headerQuantizationTable{} + + // gather and sort tables IDs + ids := make([]uint8, len(quantizationTables)) + i := 0 + for id := range quantizationTables { + ids[i] = id + i++ + } + sort.Slice(ids, func(i, j int) bool { + return ids[i] < ids[j] + }) + + // add tables sorted by ID + for _, id := range ids { + qth.Tables = append(qth.Tables, quantizationTables[id]) + } + + buf = qth.marshal(buf) + } + + remaining := e.PayloadMaxSize - len(buf) + ldata := len(data) + if remaining > ldata { + remaining = ldata + } + + buf = append(buf, data[:remaining]...) + data = data[remaining:] + offset += remaining + + ret = append(ret, &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: 26, + SequenceNumber: e.sequenceNumber, + SSRC: *e.SSRC, + Marker: len(data) == 0, + }, + Payload: buf, + }) + e.sequenceNumber++ + + if len(data) == 0 { + break + } + } + + return ret, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/header_jpeg.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/header_jpeg.go new file mode 100644 index 000000000..377111445 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/header_jpeg.go @@ -0,0 +1,49 @@ +package rtpmjpeg + +import ( + "fmt" +) + +type headerJPEG struct { + TypeSpecific uint8 + FragmentOffset uint32 + Type uint8 + Quantization uint8 + Width int + Height int +} + +func (h *headerJPEG) unmarshal(byts []byte) (int, error) { + if len(byts) < 8 { + return 0, fmt.Errorf("buffer is too short") + } + + h.TypeSpecific = byts[0] + h.FragmentOffset = uint32(byts[1])<<16 | uint32(byts[2])<<8 | uint32(byts[3]) + + h.Type = byts[4] + if h.Type > 63 { + return 0, fmt.Errorf("type %d is not supported", h.Type) + } + + h.Quantization = byts[5] + if h.Quantization == 0 || + (h.Quantization > 99 && h.Quantization < 127) { + return 0, fmt.Errorf("quantization %d is invalid", h.Quantization) + } + + h.Width = int(byts[6]) * 8 + h.Height = int(byts[7]) * 8 + + return 8, nil +} + +func (h headerJPEG) marshal(byts []byte) []byte { + byts = append(byts, h.TypeSpecific) + byts = append(byts, []byte{byte(h.FragmentOffset >> 16), byte(h.FragmentOffset >> 8), byte(h.FragmentOffset)}...) + byts = append(byts, h.Type) + byts = append(byts, h.Quantization) + byts = append(byts, byte(h.Width/8)) + byts = append(byts, byte(h.Height/8)) + return byts +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/header_quantization_table.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/header_quantization_table.go new file mode 100644 index 000000000..851798292 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/header_quantization_table.go @@ -0,0 +1,59 @@ +package rtpmjpeg + +import ( + "fmt" +) + +type headerQuantizationTable struct { + MBZ uint8 + Precision uint8 + Tables [][]byte +} + +func (h *headerQuantizationTable) unmarshal(byts []byte) (int, error) { + if len(byts) < 4 { + return 0, fmt.Errorf("buffer is too short") + } + + h.MBZ = byts[0] + h.Precision = byts[1] + if h.Precision != 0 { + return 0, fmt.Errorf("precision %d is not supported", h.Precision) + } + + length := int(byts[2])<<8 | int(byts[3]) + switch length { + case 64, 128: + default: + return 0, fmt.Errorf("table length %d is not supported", length) + } + + if (len(byts) - 4) < length { + return 0, fmt.Errorf("buffer is too short") + } + + tableCount := length / 64 + h.Tables = make([][]byte, tableCount) + n := 0 + + for i := 0; i < tableCount; i++ { + h.Tables[i] = byts[4+n : 4+64+n] + n += 64 + } + + return 4 + length, nil +} + +func (h headerQuantizationTable) marshal(byts []byte) []byte { + byts = append(byts, h.MBZ) + byts = append(byts, h.Precision) + + l := len(h.Tables) * 64 + byts = append(byts, []byte{byte(l >> 8), byte(l)}...) + + for i := 0; i < len(h.Tables); i++ { + byts = append(byts, h.Tables[i]...) + } + + return byts +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/header_restart_marker.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/header_restart_marker.go new file mode 100644 index 000000000..8dbbdeeb9 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/header_restart_marker.go @@ -0,0 +1,26 @@ +package rtpmjpeg + +import ( + "fmt" +) + +type headerRestartMarker struct { + Interval uint16 + Count uint16 +} + +func (h *headerRestartMarker) unmarshal(byts []byte) (int, error) { + if len(byts) < 4 { + return 0, fmt.Errorf("buffer is too short") + } + + h.Interval = uint16(byts[0])<<8 | uint16(byts[1]) + h.Count = uint16(byts[2])<<8 | uint16(byts[3]) + return 4, nil +} + +func (h headerRestartMarker) marshal(byts []byte) []byte { + byts = append(byts, []byte{byte(h.Interval >> 8), byte(h.Interval)}...) + byts = append(byts, []byte{byte(h.Count >> 8), byte(h.Count)}...) + return byts +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/rtpmjpeg.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/rtpmjpeg.go new file mode 100644 index 000000000..43a6bdf04 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg/rtpmjpeg.go @@ -0,0 +1,6 @@ +// Package rtpmjpeg contains a RTP/M-JPEG decoder and encoder. +package rtpmjpeg + +const ( + maxDimension = 2040 +) diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1audio/decoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1audio/decoder.go new file mode 100644 index 000000000..41c9202f0 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1audio/decoder.go @@ -0,0 +1,127 @@ +package rtpmpeg1audio + +import ( + "errors" + "fmt" + + "github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg1audio" + "github.com/pion/rtp" +) + +// ErrMorePacketsNeeded is returned when more packets are needed. +var ErrMorePacketsNeeded = errors.New("need more packets") + +// ErrNonStartingPacketAndNoPrevious is returned when we received a non-starting +// packet of a fragmented frame and we didn't received anything before. +// It's normal to receive this when decoding a stream that has been already +// running for some time. +var ErrNonStartingPacketAndNoPrevious = errors.New( + "received a non-starting fragment without any previous starting fragment") + +func joinFragments(fragments [][]byte, size int) []byte { + ret := make([]byte, size) + n := 0 + for _, p := range fragments { + n += copy(ret[n:], p) + } + return ret +} + +// Decoder is a RTP/MPEG-1/2 Audio decoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc2250 +type Decoder struct { + firstPacketReceived bool + fragments [][]byte + fragmentsSize int + fragmentsExpected int +} + +// Init initializes the decoder. +func (d *Decoder) Init() error { + return nil +} + +func (d *Decoder) resetFragments() { + d.fragments = d.fragments[:0] + d.fragmentsSize = 0 +} + +// Decode decodes frames from a RTP packet. +func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, error) { + if len(pkt.Payload) < 5 { + d.resetFragments() + return nil, fmt.Errorf("payload is too short") + } + + mbz := uint16(pkt.Payload[0])<<8 | uint16(pkt.Payload[1]) + if mbz != 0 { + d.resetFragments() + return nil, fmt.Errorf("invalid MBZ: %v", mbz) + } + + offset := uint16(pkt.Payload[2])<<8 | uint16(pkt.Payload[3]) + + var frames [][]byte + + if offset == 0 { + d.resetFragments() + d.firstPacketReceived = true + + buf := pkt.Payload[4:] + for { + var h mpeg1audio.FrameHeader + err := h.Unmarshal(buf) + if err != nil { + return nil, err + } + + fl := h.FrameLen() + bl := len(buf) + if bl >= fl { + frames = append(frames, buf[:fl]) + buf = buf[fl:] + if len(buf) == 0 { + break + } + } else { + if len(frames) != 0 { + return nil, fmt.Errorf("invalid packet") + } + + d.fragments = append(d.fragments, buf) + d.fragmentsSize = bl + d.fragmentsExpected = fl - bl + return nil, ErrMorePacketsNeeded + } + } + } else { + if int(offset) != d.fragmentsSize { + if !d.firstPacketReceived { + return nil, ErrNonStartingPacketAndNoPrevious + } + + d.resetFragments() + return nil, fmt.Errorf("unexpected offset %v, expected %v", offset, d.fragmentsSize) + } + + bl := len(pkt.Payload[4:]) + d.fragmentsSize += bl + d.fragmentsExpected -= bl + + if d.fragmentsExpected < 0 { + d.resetFragments() + return nil, fmt.Errorf("fragment is too big") + } + + d.fragments = append(d.fragments, pkt.Payload[4:]) + + if d.fragmentsExpected > 0 { + return nil, ErrMorePacketsNeeded + } + + frames = [][]byte{joinFragments(d.fragments, d.fragmentsSize)} + d.resetFragments() + } + + return frames, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1audio/encoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1audio/encoder.go new file mode 100644 index 000000000..4aa12172d --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1audio/encoder.go @@ -0,0 +1,196 @@ +package rtpmpeg1audio + +import ( + "crypto/rand" + + "github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg1audio" + "github.com/pion/rtp" +) + +const ( + rtpVersion = 2 + defaultPayloadMaxSize = 1460 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header) +) + +func randUint32() (uint32, error) { + var b [4]byte + _, err := rand.Read(b[:]) + if err != nil { + return 0, err + } + return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil +} + +func lenAggregated(frames [][]byte, frame []byte) int { + n := 4 + len(frame) + for _, fr := range frames { + n += len(fr) + } + return n +} + +func packetCount(avail, le int) int { + n := le / avail + if (le % avail) != 0 { + n++ + } + return n +} + +// Encoder is a RTP/MPEG-1/2 Audio encoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc2250 +type Encoder struct { + // SSRC of packets (optional). + // It defaults to a random value. + SSRC *uint32 + + // initial sequence number of packets (optional). + // It defaults to a random value. + InitialSequenceNumber *uint16 + + // maximum size of packet payloads (optional). + // It defaults to 1460. + PayloadMaxSize int + + sequenceNumber uint16 +} + +// Init initializes the encoder. +func (e *Encoder) Init() error { + if e.SSRC == nil { + v, err := randUint32() + if err != nil { + return err + } + e.SSRC = &v + } + if e.InitialSequenceNumber == nil { + v, err := randUint32() + if err != nil { + return err + } + v2 := uint16(v) + e.InitialSequenceNumber = &v2 + } + if e.PayloadMaxSize == 0 { + e.PayloadMaxSize = defaultPayloadMaxSize + } + + e.sequenceNumber = *e.InitialSequenceNumber + return nil +} + +// Encode encodes frames into RTP packets. +func (e *Encoder) Encode(frames [][]byte) ([]*rtp.Packet, error) { + var rets []*rtp.Packet + var batch [][]byte + timestamp := uint32(0) + + for _, frame := range frames { + if lenAggregated(batch, frame) <= e.PayloadMaxSize { + batch = append(batch, frame) + } else { + // write current batch + if batch != nil { + pkts, err := e.writeBatch(batch, timestamp) + if err != nil { + return nil, err + } + rets = append(rets, pkts...) + + for _, frame := range batch { + var h mpeg1audio.FrameHeader + err := h.Unmarshal(frame) + if err != nil { + return nil, err + } + + timestamp += uint32(h.SampleCount()) + } + } + + // initialize new batch + batch = [][]byte{frame} + } + } + + // write last batch + pkts, err := e.writeBatch(batch, timestamp) + if err != nil { + return nil, err + } + rets = append(rets, pkts...) + + return rets, nil +} + +func (e *Encoder) writeBatch(frames [][]byte, timestamp uint32) ([]*rtp.Packet, error) { + if len(frames) != 1 || lenAggregated(frames, nil) < e.PayloadMaxSize { + return e.writeAggregated(frames, timestamp) + } + + return e.writeFragmented(frames[0], timestamp) +} + +func (e *Encoder) writeFragmented(frame []byte, timestamp uint32) ([]*rtp.Packet, error) { + avail := e.PayloadMaxSize - 4 + le := len(frame) + packetCount := packetCount(avail, le) + + ret := make([]*rtp.Packet, packetCount) + pos := 0 + le = avail + + for i := range ret { + if i == (packetCount - 1) { + le = len(frame) - pos + } + + payload := make([]byte, 4+le) + payload[2] = byte(pos >> 8) + payload[3] = byte(pos) + + pos += copy(payload[4:], frame[pos:]) + + ret[i] = &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: 14, + SequenceNumber: e.sequenceNumber, + Timestamp: timestamp, + SSRC: *e.SSRC, + Marker: true, + }, + Payload: payload, + } + + e.sequenceNumber++ + } + + return ret, nil +} + +func (e *Encoder) writeAggregated(frames [][]byte, timestamp uint32) ([]*rtp.Packet, error) { + payload := make([]byte, lenAggregated(frames, nil)) + + n := 4 + for _, frame := range frames { + n += copy(payload[n:], frame) + } + + pkt := &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: 14, + SequenceNumber: e.sequenceNumber, + Timestamp: timestamp, + SSRC: *e.SSRC, + Marker: true, + }, + Payload: payload, + } + + e.sequenceNumber++ + + return []*rtp.Packet{pkt}, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1audio/rtpmpeg1audio.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1audio/rtpmpeg1audio.go new file mode 100644 index 000000000..dbc87fc31 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1audio/rtpmpeg1audio.go @@ -0,0 +1,2 @@ +// Package rtpmpeg1audio contains a RTP/MPEG-1/2 Audio decoder and encoder. +package rtpmpeg1audio diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1video/decoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1video/decoder.go new file mode 100644 index 000000000..9309df34f --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1video/decoder.go @@ -0,0 +1,163 @@ +package rtpmpeg1video + +import ( + "errors" + "fmt" + + "github.com/pion/rtp" +) + +const ( + maxFrameSize = 1 * 1024 * 1024 +) + +// ErrMorePacketsNeeded is returned when more packets are needed. +var ErrMorePacketsNeeded = errors.New("need more packets") + +// ErrNonStartingPacketAndNoPrevious is returned when we received a non-starting +// packet of a fragmented frame and we didn't received anything before. +// It's normal to receive this when decoding a stream that has been already +// running for some time. +var ErrNonStartingPacketAndNoPrevious = errors.New( + "received a non-starting fragment without any previous starting fragment") + +func joinFragments(fragments [][]byte, size int) []byte { + ret := make([]byte, size) + n := 0 + for _, p := range fragments { + n += copy(ret[n:], p) + } + return ret +} + +// Decoder is a RTP/MPEG-1/2 Video decoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc2250 +type Decoder struct { + fragments [][]byte + fragmentsSize int + fragmentNextSeqNum uint16 + + sliceBuffer [][]byte + sliceBufferSize int +} + +// Init initializes the decoder. +func (d *Decoder) Init() error { + return nil +} + +func (d *Decoder) resetFragments() { + d.fragments = d.fragments[:0] + d.fragmentsSize = 0 +} + +func (d *Decoder) decodeSlice(pkt *rtp.Packet) ([]byte, error) { + if len(pkt.Payload) < 4 { + d.resetFragments() + return nil, fmt.Errorf("payload is too short") + } + + mbz := pkt.Payload[0] >> 3 + if mbz != 0 { + d.resetFragments() + return nil, fmt.Errorf("invalid MBZ: %v", mbz) + } + + t := (pkt.Payload[0] >> 2) & 0x01 + if t != 0 { + d.resetFragments() + return nil, fmt.Errorf("MPEG-2 video-specific header extension is not supported yet") + } + + an := pkt.Payload[2] >> 7 + if an != 0 { + d.resetFragments() + return nil, fmt.Errorf("AN not supported yet") + } + + n := (pkt.Payload[2] >> 6) & 0x01 + if n != 0 { + d.resetFragments() + return nil, fmt.Errorf("N not supported yet") + } + + b := (pkt.Payload[2] >> 4) & 0x01 + e := (pkt.Payload[2] >> 3) & 0x01 + + switch { + case b == 1 && e == 1: + return pkt.Payload[4:], nil + + case b == 1: + d.fragments = d.fragments[:0] + d.fragments = append(d.fragments, pkt.Payload[4:]) + d.fragmentsSize = len(pkt.Payload[4:]) + d.fragmentNextSeqNum = pkt.SequenceNumber + 1 + return nil, ErrMorePacketsNeeded + + case e == 1: + if d.fragmentsSize == 0 { + return nil, ErrNonStartingPacketAndNoPrevious + } + + if pkt.SequenceNumber != d.fragmentNextSeqNum { + d.resetFragments() + return nil, fmt.Errorf("discarding frame since a RTP packet is missing") + } + + d.fragments = append(d.fragments, pkt.Payload[4:]) + d.fragmentsSize += len(pkt.Payload[4:]) + + slice := joinFragments(d.fragments, d.fragmentsSize) + d.resetFragments() + return slice, nil + + default: + if d.fragmentsSize == 0 { + return nil, ErrNonStartingPacketAndNoPrevious + } + + if pkt.SequenceNumber != d.fragmentNextSeqNum { + d.resetFragments() + return nil, fmt.Errorf("discarding frame since a RTP packet is missing") + } + + d.fragments = append(d.fragments, pkt.Payload[4:]) + d.fragmentsSize += len(pkt.Payload[4:]) + d.fragmentNextSeqNum++ + return nil, ErrMorePacketsNeeded + } +} + +// Decode decodes frames from a RTP packet. +func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, error) { + slice, err := d.decodeSlice(pkt) + if err != nil { + return nil, err + } + + addSize := len(slice) + + if (d.sliceBufferSize + addSize) > maxFrameSize { + errSize := d.sliceBufferSize + addSize + d.sliceBuffer = nil + d.sliceBufferSize = 0 + return nil, fmt.Errorf("frame size (%d) is too big, maximum is %d", + errSize, maxFrameSize) + } + + d.sliceBuffer = append(d.sliceBuffer, slice) + d.sliceBufferSize += addSize + + if !pkt.Marker { + return nil, ErrMorePacketsNeeded + } + + ret := joinFragments(d.sliceBuffer, d.sliceBufferSize) + + // do not reuse sliceBuffer to avoid race conditions + d.sliceBuffer = nil + d.sliceBufferSize = 0 + + return ret, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1video/encoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1video/encoder.go new file mode 100644 index 000000000..d0e838be9 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1video/encoder.go @@ -0,0 +1,239 @@ +package rtpmpeg1video + +import ( + "bytes" + "crypto/rand" + + "github.com/pion/rtp" +) + +const ( + rtpVersion = 2 + defaultPayloadMaxSize = 1460 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header) +) + +func randUint32() (uint32, error) { + var b [4]byte + _, err := rand.Read(b[:]) + if err != nil { + return 0, err + } + return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil +} + +func lenAggregated(slices [][]byte, slice []byte) int { + n := 4 + len(slice) + for _, fr := range slices { + n += len(fr) + } + return n +} + +func packetCount(avail, le int) int { + n := le / avail + if (le % avail) != 0 { + n++ + } + return n +} + +// Encoder is a RTP/MPEG-1/2 Video encoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc2250 +type Encoder struct { + // SSRC of packets (optional). + // It defaults to a random value. + SSRC *uint32 + + // initial sequence number of packets (optional). + // It defaults to a random value. + InitialSequenceNumber *uint16 + + // maximum size of packet payloads (optional). + // It defaults to 1460. + PayloadMaxSize int + + sequenceNumber uint16 +} + +// Init initializes the encoder. +func (e *Encoder) Init() error { + if e.SSRC == nil { + v, err := randUint32() + if err != nil { + return err + } + e.SSRC = &v + } + if e.InitialSequenceNumber == nil { + v, err := randUint32() + if err != nil { + return err + } + v2 := uint16(v) + e.InitialSequenceNumber = &v2 + } + if e.PayloadMaxSize == 0 { + e.PayloadMaxSize = defaultPayloadMaxSize + } + + e.sequenceNumber = *e.InitialSequenceNumber + return nil +} + +// Encode encodes frames into RTP packets. +func (e *Encoder) Encode(frame []byte) ([]*rtp.Packet, error) { + var rets []*rtp.Packet + var batch [][]byte + + var temporalReference uint16 + beginOfSequence := uint8(0) + var frameType uint8 + + for { + var slice []byte + end := bytes.Index(frame[4:], []byte{0, 0, 1}) + if end >= 0 { + slice, frame = frame[:end+4], frame[end+4:] + } else { + slice, frame = frame, nil + } + + if lenAggregated(batch, slice) <= e.PayloadMaxSize { + batch = append(batch, slice) + } else { + // write current batch + if batch != nil { + pkts, err := e.writeBatch(batch, + temporalReference, + beginOfSequence, + frameType) + if err != nil { + return nil, err + } + rets = append(rets, pkts...) + beginOfSequence = 0 + } + + // initialize new batch + batch = [][]byte{slice} + } + + switch slice[3] { + case 0: + temporalReference = uint16(slice[4])<<2 | uint16(slice[5])>>6 + frameType = (slice[5] >> 3) & 0b111 + + case 0xB8: + beginOfSequence = 1 + } + + if frame == nil { + break + } + } + + // write last batch + pkts, err := e.writeBatch(batch, + temporalReference, + beginOfSequence, + frameType) + if err != nil { + return nil, err + } + rets = append(rets, pkts...) + + rets[len(rets)-1].Marker = true + + return rets, nil +} + +func (e *Encoder) writeBatch( + slices [][]byte, + temporalReference uint16, + beginOfSequence uint8, + frameType uint8, +) ([]*rtp.Packet, error) { + if len(slices) != 1 || lenAggregated(slices, nil) < e.PayloadMaxSize { + return e.writeAggregated(slices, temporalReference, beginOfSequence, frameType) + } + + return e.writeFragmented(slices[0], temporalReference, beginOfSequence, frameType) +} + +func (e *Encoder) writeFragmented( + slice []byte, + temporalReference uint16, + beginOfSequence uint8, + frameType uint8, +) ([]*rtp.Packet, error) { + avail := e.PayloadMaxSize - 4 + le := len(slice) + packetCount := packetCount(avail, le) + + ret := make([]*rtp.Packet, packetCount) + le = avail + start := uint8(1) + end := uint8(0) + + for i := range ret { + if i == (packetCount - 1) { + le = len(slice) + end = 1 + } + + payload := make([]byte, 4+le) + payload[0] = byte(temporalReference >> 8) + payload[1] = byte(temporalReference) + payload[2] = beginOfSequence<<5 | start<<4 | end<<3 | frameType + copy(payload[4:], slice) + slice = slice[le:] + + ret[i] = &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: 32, + SequenceNumber: e.sequenceNumber, + SSRC: *e.SSRC, + }, + Payload: payload, + } + + e.sequenceNumber++ + start = 0 + beginOfSequence = 0 + } + + return ret, nil +} + +func (e *Encoder) writeAggregated( + slices [][]byte, + temporalReference uint16, + beginOfSequence uint8, + frameType uint8, +) ([]*rtp.Packet, error) { + payload := make([]byte, lenAggregated(slices, nil)) + + payload[0] = byte(temporalReference >> 8) + payload[1] = byte(temporalReference) + payload[2] = beginOfSequence<<5 | 1<<4 | 1<<3 | frameType + + n := 4 + for _, slice := range slices { + n += copy(payload[n:], slice) + } + + pkt := &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: 32, + SequenceNumber: e.sequenceNumber, + SSRC: *e.SSRC, + }, + Payload: payload, + } + + e.sequenceNumber++ + + return []*rtp.Packet{pkt}, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1video/rtpmpeg1video.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1video/rtpmpeg1video.go new file mode 100644 index 000000000..be95588f3 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1video/rtpmpeg1video.go @@ -0,0 +1,2 @@ +// Package rtpmpeg1video contains a RTP/MPEG-1/2 Video decoder and encoder. +package rtpmpeg1video diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/decoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/decoder.go new file mode 100644 index 000000000..4d980e8e9 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/decoder.go @@ -0,0 +1,62 @@ +package rtpmpeg4audio + +import ( + "errors" + + "github.com/pion/rtp" +) + +// ErrMorePacketsNeeded is returned when more packets are needed. +var ErrMorePacketsNeeded = errors.New("need more packets") + +func joinFragments(fragments [][]byte, size int) []byte { + ret := make([]byte, size) + n := 0 + for _, p := range fragments { + n += copy(ret[n:], p) + } + return ret +} + +// Decoder is a RTP/MPEG-4 Audio decoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc3640 +// Specification: https://datatracker.ietf.org/doc/html/rfc6416#section-7.3 +type Decoder struct { + // use RFC6416 (LATM) instead of RFC3640 (generic). + LATM bool + + // Generic-only + // The number of bits in which the AU-size field is encoded in the AU-header. + SizeLength int + // The number of bits in which the AU-Index is encoded in the first AU-header. + IndexLength int + // The number of bits in which the AU-Index-delta field is encoded in any non-first AU-header. + IndexDeltaLength int + + firstAUParsed bool + adtsMode bool + fragments [][]byte + fragmentsSize int + fragmentsExpected int + fragmentNextSeqNum uint16 +} + +// Init initializes the decoder. +func (d *Decoder) Init() error { + return nil +} + +func (d *Decoder) resetFragments() { + d.fragments = d.fragments[:0] + d.fragmentsSize = 0 +} + +// Decode decodes AUs from a RTP packet. +// It returns the AUs and the PTS of the first AU. +// The PTS of subsequent AUs can be calculated by adding time.Second*mpeg4audio.SamplesPerAccessUnit/clockRate. +func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, error) { + if !d.LATM { + return d.decodeGeneric(pkt) + } + return d.decodeLATM(pkt) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/decoder_generic.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/decoder_generic.go new file mode 100644 index 000000000..db69ae7ae --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/decoder_generic.go @@ -0,0 +1,201 @@ +package rtpmpeg4audio + +import ( + "fmt" + + "github.com/bluenviron/mediacommon/v2/pkg/bits" + "github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio" + "github.com/pion/rtp" +) + +func (d *Decoder) decodeGeneric(pkt *rtp.Packet) ([][]byte, error) { + if len(pkt.Payload) < 2 { + d.resetFragments() + return nil, fmt.Errorf("payload is too short") + } + + // AU-headers-length (16 bits) + headersLen := int(uint16(pkt.Payload[0])<<8 | uint16(pkt.Payload[1])) + if headersLen == 0 { + d.resetFragments() + return nil, fmt.Errorf("invalid AU-headers-length") + } + payload := pkt.Payload[2:] + + // AU-headers + dataLens, err := d.readAUHeaders(payload, headersLen) + if err != nil { + d.resetFragments() + return nil, err + } + + pos := (headersLen / 8) + if (headersLen % 8) != 0 { + pos++ + } + payload = payload[pos:] + + var aus [][]byte + + if d.fragmentsSize == 0 { + d.resetFragments() + + if pkt.Marker { + // AUs + aus = make([][]byte, len(dataLens)) + for i, dataLen := range dataLens { + if len(payload) < int(dataLen) { + return nil, fmt.Errorf("payload is too short") + } + + aus[i] = payload[:dataLen] + payload = payload[dataLen:] + } + } else { + if len(dataLens) != 1 { + return nil, fmt.Errorf("a fragmented packet can only contain one AU") + } + + if len(payload) < int(dataLens[0]) { + return nil, fmt.Errorf("payload is too short") + } + + d.fragmentsSize = int(dataLens[0]) + d.fragments = append(d.fragments, payload[:dataLens[0]]) + d.fragmentNextSeqNum = pkt.SequenceNumber + 1 + return nil, ErrMorePacketsNeeded + } + } else { + // we are decoding a fragmented AU + if len(dataLens) != 1 { + d.resetFragments() + return nil, fmt.Errorf("a fragmented packet can only contain one AU") + } + + if len(payload) < int(dataLens[0]) { + d.resetFragments() + return nil, fmt.Errorf("payload is too short") + } + + if pkt.SequenceNumber != d.fragmentNextSeqNum { + d.resetFragments() + return nil, fmt.Errorf("discarding frame since a RTP packet is missing") + } + + d.fragmentsSize += int(dataLens[0]) + + if d.fragmentsSize > mpeg4audio.MaxAccessUnitSize { + errSize := d.fragmentsSize + d.resetFragments() + return nil, fmt.Errorf("access unit size (%d) is too big, maximum is %d", + errSize, mpeg4audio.MaxAccessUnitSize) + } + + d.fragments = append(d.fragments, payload[:dataLens[0]]) + d.fragmentNextSeqNum++ + + if !pkt.Marker { + return nil, ErrMorePacketsNeeded + } + + aus = [][]byte{joinFragments(d.fragments, d.fragmentsSize)} + d.resetFragments() + } + + return d.removeADTS(aus) +} + +func (d *Decoder) readAUHeaders(buf []byte, headersLen int) ([]uint64, error) { + firstRead := false + + count := 0 + for i := 0; i < headersLen; { + if i == 0 { + i += d.SizeLength + i += d.IndexLength + } else { + i += d.SizeLength + i += d.IndexDeltaLength + } + count++ + } + + dataLens := make([]uint64, count) + + pos := 0 + i := 0 + + for headersLen > 0 { + dataLen, err := bits.ReadBits(buf, &pos, d.SizeLength) + if err != nil { + return nil, err + } + headersLen -= d.SizeLength + + if !firstRead { + firstRead = true + if d.IndexLength > 0 { + auIndex, err := bits.ReadBits(buf, &pos, d.IndexLength) + if err != nil { + return nil, err + } + headersLen -= d.IndexLength + + if auIndex != 0 { + return nil, fmt.Errorf("AU-index different than zero is not supported") + } + } + } else if d.IndexDeltaLength > 0 { + auIndexDelta, err := bits.ReadBits(buf, &pos, d.IndexDeltaLength) + if err != nil { + return nil, err + } + headersLen -= d.IndexDeltaLength + + if auIndexDelta != 0 { + return nil, fmt.Errorf("AU-index-delta different than zero is not supported") + } + } + + dataLens[i] = dataLen + i++ + } + + return dataLens, nil +} + +// some cameras wrap AUs into ADTS +func (d *Decoder) removeADTS(aus [][]byte) ([][]byte, error) { + if !d.firstAUParsed { + d.firstAUParsed = true + + if len(aus) == 1 && len(aus[0]) >= 2 { + if aus[0][0] == 0xFF && (aus[0][1]&0xF0) == 0xF0 { + var pkts mpeg4audio.ADTSPackets + err := pkts.Unmarshal(aus[0]) + if err == nil && len(pkts) == 1 { + d.adtsMode = true + aus[0] = pkts[0].AU + } + } + } + } else if d.adtsMode { + if len(aus) != 1 { + return nil, fmt.Errorf("multiple AUs in ADTS mode are not supported") + } + + var pkts mpeg4audio.ADTSPackets + err := pkts.Unmarshal(aus[0]) + if err != nil { + return nil, fmt.Errorf("unable to decode ADTS: %w", err) + } + + if len(pkts) != 1 { + return nil, fmt.Errorf("multiple ADTS packets are not supported") + } + + aus[0] = pkts[0].AU + } + + return aus, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/decoder_latm.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/decoder_latm.go new file mode 100644 index 000000000..cdaec8ecb --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/decoder_latm.go @@ -0,0 +1,63 @@ +package rtpmpeg4audio + +import ( + "fmt" + + "github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio" + "github.com/pion/rtp" +) + +func (d *Decoder) decodeLATM(pkt *rtp.Packet) ([][]byte, error) { + var au []byte + buf := pkt.Payload + + if d.fragmentsSize == 0 { + pl, n, err := payloadLengthInfoDecode(buf) + if err != nil { + return nil, err + } + + buf = buf[n:] + bl := len(buf) + + if pl <= bl { + au = buf[:pl] + // there could be other data, due to otherDataPresent. Ignore it. + } else { + if pl > mpeg4audio.MaxAccessUnitSize { + errSize := pl + d.resetFragments() + return nil, fmt.Errorf("access unit size (%d) is too big, maximum is %d", + errSize, mpeg4audio.MaxAccessUnitSize) + } + + d.fragments = append(d.fragments, buf) + d.fragmentsSize = pl + d.fragmentsExpected = pl - bl + d.fragmentNextSeqNum = pkt.SequenceNumber + 1 + return nil, ErrMorePacketsNeeded + } + } else { + if pkt.SequenceNumber != d.fragmentNextSeqNum { + d.resetFragments() + return nil, fmt.Errorf("discarding frame since a RTP packet is missing") + } + + bl := len(buf) + + if d.fragmentsExpected > bl { + d.fragments = append(d.fragments, buf) + d.fragmentsExpected -= bl + d.fragmentNextSeqNum++ + return nil, ErrMorePacketsNeeded + } + + d.fragments = append(d.fragments, buf[:d.fragmentsExpected]) + // there could be other data, due to otherDataPresent. Ignore it. + + au = joinFragments(d.fragments, d.fragmentsSize) + d.resetFragments() + } + + return [][]byte{au}, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/encoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/encoder.go new file mode 100644 index 000000000..9558033a5 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/encoder.go @@ -0,0 +1,88 @@ +package rtpmpeg4audio + +import ( + "crypto/rand" + + "github.com/pion/rtp" +) + +const ( + rtpVersion = 2 + defaultPayloadMaxSize = 1460 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header) +) + +func randUint32() (uint32, error) { + var b [4]byte + _, err := rand.Read(b[:]) + if err != nil { + return 0, err + } + return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil +} + +// Encoder is a RTP/MPEG-4 audio encoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc3640 +// Specification: https://datatracker.ietf.org/doc/html/rfc6416#section-7.3 +type Encoder struct { + // payload type of packets. + PayloadType uint8 + + // use RFC6416 (LATM) instead of RFC3640 (generic). + LATM bool + + // The number of bits in which the AU-size field is encoded in the AU-header. + SizeLength int + + // The number of bits in which the AU-Index is encoded in the first AU-header. + IndexLength int + + // The number of bits in which the AU-Index-delta field is encoded in any non-first AU-header. + IndexDeltaLength int + + // SSRC of packets (optional). + // It defaults to a random value. + SSRC *uint32 + + // initial sequence number of packets (optional). + // It defaults to a random value. + InitialSequenceNumber *uint16 + + // maximum size of packet payloads (optional). + // It defaults to 1460. + PayloadMaxSize int + + sequenceNumber uint16 +} + +// Init initializes the encoder. +func (e *Encoder) Init() error { + if e.SSRC == nil { + v, err := randUint32() + if err != nil { + return err + } + e.SSRC = &v + } + if e.InitialSequenceNumber == nil { + v, err := randUint32() + if err != nil { + return err + } + v2 := uint16(v) + e.InitialSequenceNumber = &v2 + } + if e.PayloadMaxSize == 0 { + e.PayloadMaxSize = defaultPayloadMaxSize + } + + e.sequenceNumber = *e.InitialSequenceNumber + return nil +} + +// Encode encodes AUs into RTP packets. +func (e *Encoder) Encode(aus [][]byte) ([]*rtp.Packet, error) { + if !e.LATM { + return e.encodeGeneric(aus) + } + return e.encodeLATM(aus) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/encoder_generic.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/encoder_generic.go new file mode 100644 index 000000000..5c56bcda3 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/encoder_generic.go @@ -0,0 +1,196 @@ +package rtpmpeg4audio + +import ( + "github.com/pion/rtp" + + "github.com/bluenviron/mediacommon/v2/pkg/bits" + "github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio" +) + +func packetCountGeneric(avail, le int) int { + n := le / avail + if (le % avail) != 0 { + n++ + } + return n +} + +func (e *Encoder) encodeGeneric(aus [][]byte) ([]*rtp.Packet, error) { + var rets []*rtp.Packet + var batch [][]byte + timestamp := uint32(0) + + // split AUs into batches + for _, au := range aus { + if e.lenGenericAggregated(batch, au) <= e.PayloadMaxSize { + // add to existing batch + batch = append(batch, au) + } else { + // write current batch + if batch != nil { + pkts, err := e.writeGenericBatch(batch, timestamp) + if err != nil { + return nil, err + } + rets = append(rets, pkts...) + timestamp += uint32(len(batch)) * mpeg4audio.SamplesPerAccessUnit + } + + // initialize new batch + batch = [][]byte{au} + } + } + + // write last batch + pkts, err := e.writeGenericBatch(batch, timestamp) + if err != nil { + return nil, err + } + rets = append(rets, pkts...) + + return rets, nil +} + +func (e *Encoder) writeGenericBatch(aus [][]byte, timestamp uint32) ([]*rtp.Packet, error) { + if len(aus) != 1 || e.lenGenericAggregated(aus, nil) < e.PayloadMaxSize { + return e.writeGenericAggregated(aus, timestamp) + } + + return e.writeGenericFragmented(aus[0], timestamp) +} + +func (e *Encoder) writeGenericFragmented(au []byte, timestamp uint32) ([]*rtp.Packet, error) { + auHeadersLen := e.SizeLength + e.IndexLength + auHeadersLenBytes := auHeadersLen / 8 + if (auHeadersLen % 8) != 0 { + auHeadersLenBytes++ + } + + avail := e.PayloadMaxSize - 2 - auHeadersLenBytes + le := len(au) + packetCount := packetCountGeneric(avail, le) + + ret := make([]*rtp.Packet, packetCount) + le = avail + + for i := range ret { + if i == (packetCount - 1) { + le = len(au) + } + + payload := make([]byte, 2+auHeadersLenBytes+le) + + // AU-headers-length + payload[0] = byte(auHeadersLen >> 8) + payload[1] = byte(auHeadersLen) + + // AU-headers + pos := 0 + bits.WriteBitsUnsafe(payload[2:], &pos, uint64(le), e.SizeLength) + bits.WriteBitsUnsafe(payload[2:], &pos, 0, e.IndexLength) + + // AU + copy(payload[2+auHeadersLenBytes:], au) + au = au[le:] + + ret[i] = &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: e.PayloadType, + SequenceNumber: e.sequenceNumber, + Timestamp: timestamp, + SSRC: *e.SSRC, + Marker: (i == packetCount-1), + }, + Payload: payload, + } + + e.sequenceNumber++ + } + + return ret, nil +} + +func (e *Encoder) lenGenericAggregated(aus [][]byte, addAU []byte) int { + n := 2 // AU-headers-length + + // AU-headers + auHeadersLen := 0 + i := 0 + for range aus { + if i == 0 { + auHeadersLen += e.SizeLength + e.IndexLength + } else { + auHeadersLen += e.SizeLength + e.IndexDeltaLength + } + i++ + } + if addAU != nil { + if i == 0 { + auHeadersLen += e.SizeLength + e.IndexLength + } else { + auHeadersLen += e.SizeLength + e.IndexDeltaLength + } + } + n += auHeadersLen / 8 + if (auHeadersLen % 8) != 0 { + n++ + } + + // AU + for _, au := range aus { + n += len(au) + } + n += len(addAU) + + return n +} + +func (e *Encoder) writeGenericAggregated(aus [][]byte, timestamp uint32) ([]*rtp.Packet, error) { + payload := make([]byte, e.lenGenericAggregated(aus, nil)) + + // AU-headers + written := 0 + pos := 0 + for i, au := range aus { + bits.WriteBitsUnsafe(payload[2:], &pos, uint64(len(au)), e.SizeLength) + written += e.SizeLength + if i == 0 { + bits.WriteBitsUnsafe(payload[2:], &pos, 0, e.IndexLength) + written += e.IndexLength + } else { + bits.WriteBitsUnsafe(payload[2:], &pos, 0, e.IndexDeltaLength) + written += e.IndexDeltaLength + } + } + pos = 2 + (written / 8) + if (written % 8) != 0 { + pos++ + } + + // AU-headers-length + payload[0] = byte(written >> 8) + payload[1] = byte(written) + + // AUs + for _, au := range aus { + auLen := copy(payload[pos:], au) + pos += auLen + } + + pkt := &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: e.PayloadType, + SequenceNumber: e.sequenceNumber, + Timestamp: timestamp, + SSRC: *e.SSRC, + Marker: true, + }, + Payload: payload, + } + + e.sequenceNumber++ + + return []*rtp.Packet{pkt}, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/encoder_latm.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/encoder_latm.go new file mode 100644 index 000000000..16db07e8a --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/encoder_latm.go @@ -0,0 +1,76 @@ +package rtpmpeg4audio + +import ( + "github.com/pion/rtp" + + "github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio" +) + +func (e *Encoder) packetCountLATM(auLen int, plil int) int { + totalLen := plil + auLen + n := totalLen / e.PayloadMaxSize + if (totalLen % e.PayloadMaxSize) != 0 { + n++ + } + return n +} + +func (e *Encoder) encodeLATM(aus [][]byte) ([]*rtp.Packet, error) { + var rets []*rtp.Packet + + for i, au := range aus { + timestamp := uint32(i) * mpeg4audio.SamplesPerAccessUnit + + add, err := e.encodeLATMSingle(au, timestamp) + if err != nil { + return nil, err + } + rets = append(rets, add...) + } + + return rets, nil +} + +func (e *Encoder) encodeLATMSingle(au []byte, timestamp uint32) ([]*rtp.Packet, error) { + auLen := len(au) + plil := payloadLengthInfoEncodeSize(auLen) + packetCount := e.packetCountLATM(auLen, plil) + + ret := make([]*rtp.Packet, packetCount) + le := e.PayloadMaxSize - plil + + for i := range ret { + if i == (packetCount - 1) { + le = len(au) + } + + var payload []byte + + if i == 0 { + payload = make([]byte, plil+le) + payloadLengthInfoEncode(plil, auLen, payload) + copy(payload[plil:], au[:le]) + au = au[le:] + le = e.PayloadMaxSize + } else { + payload = au[:le] + au = au[le:] + } + + ret[i] = &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: e.PayloadType, + SequenceNumber: e.sequenceNumber, + Timestamp: timestamp, + SSRC: *e.SSRC, + Marker: (i == packetCount-1), + }, + Payload: payload, + } + + e.sequenceNumber++ + } + + return ret, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/payload_length_info.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/payload_length_info.go new file mode 100644 index 000000000..ab248a7aa --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/payload_length_info.go @@ -0,0 +1,38 @@ +package rtpmpeg4audio + +import ( + "fmt" +) + +func payloadLengthInfoDecode(buf []byte) (int, int, error) { + lb := len(buf) + l := 0 + n := 0 + + for { + if (lb - n) == 0 { + return 0, 0, fmt.Errorf("not enough bytes") + } + + b := buf[n] + n++ + l += int(b) + + if b != 255 { + break + } + } + + return l, n, nil +} + +func payloadLengthInfoEncodeSize(auLen int) int { + return auLen/255 + 1 +} + +func payloadLengthInfoEncode(plil int, auLen int, buf []byte) { + for i := 0; i < (plil - 1); i++ { + buf[i] = 255 + } + buf[plil-1] = byte(auLen % 255) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/rtpmpeg4audio.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/rtpmpeg4audio.go new file mode 100644 index 000000000..6698abbd3 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio/rtpmpeg4audio.go @@ -0,0 +1,2 @@ +// Package rtpmpeg4audio contains a RTP/MPEG-4 Audio decoder and encoder. +package rtpmpeg4audio diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4video/decoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4video/decoder.go new file mode 100644 index 000000000..b3a569545 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4video/decoder.go @@ -0,0 +1,82 @@ +package rtpmpeg4video + +import ( + "errors" + "fmt" + + "github.com/pion/rtp" + + "github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video" +) + +// ErrMorePacketsNeeded is returned when more packets are needed. +var ErrMorePacketsNeeded = errors.New("need more packets") + +func joinFragments(fragments [][]byte, size int) []byte { + ret := make([]byte, size) + n := 0 + for _, p := range fragments { + n += copy(ret[n:], p) + } + return ret +} + +// Decoder is a RTP/MPEG-4 Video decoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc6416 +type Decoder struct { + fragments [][]byte + fragmentsSize int + fragmentNextSeqNum uint16 +} + +// Init initializes the decoder. +func (d *Decoder) Init() error { + return nil +} + +func (d *Decoder) resetFragments() { + d.fragments = d.fragments[:0] + d.fragmentsSize = 0 +} + +// Decode decodes a frame from a RTP packet. +func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, error) { + var frame []byte + + if d.fragmentsSize == 0 { + if pkt.Marker { + frame = pkt.Payload + } else { + d.fragmentsSize = len(pkt.Payload) + d.fragments = append(d.fragments, pkt.Payload) + d.fragmentNextSeqNum = pkt.SequenceNumber + 1 + return nil, ErrMorePacketsNeeded + } + } else { + if pkt.SequenceNumber != d.fragmentNextSeqNum { + d.resetFragments() + return nil, fmt.Errorf("discarding frame since a RTP packet is missing") + } + + d.fragmentsSize += len(pkt.Payload) + + if d.fragmentsSize > mpeg4video.MaxFrameSize { + errSize := d.fragmentsSize + d.resetFragments() + return nil, fmt.Errorf("frame size (%d) is too big, maximum is %d", + errSize, mpeg4video.MaxFrameSize) + } + + d.fragments = append(d.fragments, pkt.Payload) + d.fragmentNextSeqNum++ + + if !pkt.Marker { + return nil, ErrMorePacketsNeeded + } + + frame = joinFragments(d.fragments, d.fragmentsSize) + d.resetFragments() + } + + return frame, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4video/encoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4video/encoder.go new file mode 100644 index 000000000..bf3fa0d2e --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4video/encoder.go @@ -0,0 +1,108 @@ +package rtpmpeg4video + +import ( + "crypto/rand" + + "github.com/pion/rtp" +) + +const ( + rtpVersion = 2 + defaultPayloadMaxSize = 1460 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header) +) + +func randUint32() (uint32, error) { + var b [4]byte + _, err := rand.Read(b[:]) + if err != nil { + return 0, err + } + return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil +} + +func packetCount(avail, le int) int { + n := le / avail + if (le % avail) != 0 { + n++ + } + return n +} + +// Encoder is a RTP/MPEG-4 Video encoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc6416 +type Encoder struct { + // payload type of packets. + PayloadType uint8 + + // SSRC of packets (optional). + // It defaults to a random value. + SSRC *uint32 + + // initial sequence number of packets (optional). + // It defaults to a random value. + InitialSequenceNumber *uint16 + + // maximum size of packet payloads (optional). + // It defaults to 1460. + PayloadMaxSize int + + sequenceNumber uint16 +} + +// Init initializes the encoder. +func (e *Encoder) Init() error { + if e.SSRC == nil { + v, err := randUint32() + if err != nil { + return err + } + e.SSRC = &v + } + if e.InitialSequenceNumber == nil { + v, err := randUint32() + if err != nil { + return err + } + v2 := uint16(v) + e.InitialSequenceNumber = &v2 + } + if e.PayloadMaxSize == 0 { + e.PayloadMaxSize = defaultPayloadMaxSize + } + + e.sequenceNumber = *e.InitialSequenceNumber + return nil +} + +// Encode encodes a frame into RTP packets. +func (e *Encoder) Encode(frame []byte) ([]*rtp.Packet, error) { + avail := e.PayloadMaxSize + le := len(frame) + packetCount := packetCount(avail, le) + + ret := make([]*rtp.Packet, packetCount) + pos := 0 + le = avail + + for i := range ret { + if i == (packetCount - 1) { + le = len(frame[pos:]) + } + + ret[i] = &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: e.PayloadType, + SequenceNumber: e.sequenceNumber, + SSRC: *e.SSRC, + Marker: (i == packetCount-1), + }, + Payload: frame[pos : pos+le], + } + + pos += le + e.sequenceNumber++ + } + + return ret, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4video/rtpmpeg4video.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4video/rtpmpeg4video.go new file mode 100644 index 000000000..1acba5b70 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4video/rtpmpeg4video.go @@ -0,0 +1,2 @@ +// Package rtpmpeg4video contains a RTP/MPEG-4 Video decoder and encoder. +package rtpmpeg4video diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio/decoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio/decoder.go new file mode 100644 index 000000000..d53dcce59 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio/decoder.go @@ -0,0 +1,18 @@ +package rtpsimpleaudio + +import ( + "github.com/pion/rtp" +) + +// Decoder is a RTP/simple audio decoder. +type Decoder struct{} + +// Init initializes the decoder. +func (d *Decoder) Init() error { + return nil +} + +// Decode decodes an audio frame from a RTP packet. +func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, error) { + return pkt.Payload, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio/encoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio/encoder.go new file mode 100644 index 000000000..0270285b6 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio/encoder.go @@ -0,0 +1,89 @@ +package rtpsimpleaudio + +import ( + "crypto/rand" + "fmt" + + "github.com/pion/rtp" +) + +const ( + rtpVersion = 2 + defaultPayloadMaxSize = 1460 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header) +) + +func randUint32() (uint32, error) { + var b [4]byte + _, err := rand.Read(b[:]) + if err != nil { + return 0, err + } + return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil +} + +// Encoder is a RTP/simple audio encoder. +type Encoder struct { + // payload type of packets. + PayloadType uint8 + + // SSRC of packets (optional). + // It defaults to a random value. + SSRC *uint32 + + // initial sequence number of packets (optional). + // It defaults to a random value. + InitialSequenceNumber *uint16 + + // maximum size of packet payloads (optional). + // It defaults to 1460. + PayloadMaxSize int + + sequenceNumber uint16 +} + +// Init initializes the encoder. +func (e *Encoder) Init() error { + if e.SSRC == nil { + v, err := randUint32() + if err != nil { + return err + } + e.SSRC = &v + } + if e.InitialSequenceNumber == nil { + v, err := randUint32() + if err != nil { + return err + } + v2 := uint16(v) + e.InitialSequenceNumber = &v2 + } + if e.PayloadMaxSize == 0 { + e.PayloadMaxSize = defaultPayloadMaxSize + } + + e.sequenceNumber = *e.InitialSequenceNumber + return nil +} + +// Encode encodes an audio frame into a RTP packet. +func (e *Encoder) Encode(frame []byte) (*rtp.Packet, error) { + if len(frame) > e.PayloadMaxSize { + return nil, fmt.Errorf("frame is too big") + } + + pkt := &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: e.PayloadType, + SequenceNumber: e.sequenceNumber, + SSRC: *e.SSRC, + Marker: false, + }, + Payload: frame, + } + + e.sequenceNumber++ + + return pkt, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio/rtpsimpleaudio.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio/rtpsimpleaudio.go new file mode 100644 index 000000000..047a90010 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio/rtpsimpleaudio.go @@ -0,0 +1,2 @@ +// Package rtpsimpleaudio contains a RTP decoder and encoder for audio codecs that fit in a single packet. +package rtpsimpleaudio diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8/decoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8/decoder.go new file mode 100644 index 000000000..df6da538c --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8/decoder.go @@ -0,0 +1,113 @@ +package rtpvp8 + +import ( + "errors" + "fmt" + + "github.com/bluenviron/mediacommon/v2/pkg/codecs/vp8" + "github.com/pion/rtp" + "github.com/pion/rtp/codecs" +) + +// ErrMorePacketsNeeded is returned when more packets are needed. +var ErrMorePacketsNeeded = errors.New("need more packets") + +// ErrNonStartingPacketAndNoPrevious is returned when we received a non-starting +// packet of a fragmented frame and we didn't received anything before. +// It's normal to receive this when decoding a stream that has been already +// running for some time. +var ErrNonStartingPacketAndNoPrevious = errors.New( + "received a non-starting fragment without any previous starting fragment") + +func joinFragments(fragments [][]byte, size int) []byte { + ret := make([]byte, size) + n := 0 + for _, p := range fragments { + n += copy(ret[n:], p) + } + return ret +} + +// Decoder is a RTP/VP8 decoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc7741 +type Decoder struct { + firstPacketReceived bool + fragmentsSize int + fragments [][]byte + fragmentNextSeqNum uint16 +} + +// Init initializes the decoder. +func (d *Decoder) Init() error { + return nil +} + +func (d *Decoder) resetFragments() { + d.fragments = d.fragments[:0] + d.fragmentsSize = 0 +} + +// Decode decodes a VP8 frame from a RTP packet. +func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, error) { + var vpkt codecs.VP8Packet + _, err := vpkt.Unmarshal(pkt.Payload) + if err != nil { + d.resetFragments() + return nil, err + } + + if vpkt.PID != 0 { + d.resetFragments() + return nil, fmt.Errorf("packets containing single partitions are not supported") + } + + var frame []byte + + if vpkt.S == 1 { + d.resetFragments() + d.firstPacketReceived = true + + if !pkt.Marker { + d.fragmentsSize = len(vpkt.Payload) + d.fragments = append(d.fragments, vpkt.Payload) + d.fragmentNextSeqNum = pkt.SequenceNumber + 1 + return nil, ErrMorePacketsNeeded + } + + frame = vpkt.Payload + } else { + if d.fragmentsSize == 0 { + if !d.firstPacketReceived { + return nil, ErrNonStartingPacketAndNoPrevious + } + + return nil, fmt.Errorf("received a non-starting fragment") + } + + if pkt.SequenceNumber != d.fragmentNextSeqNum { + d.resetFragments() + return nil, fmt.Errorf("discarding frame since a RTP packet is missing") + } + + d.fragmentsSize += len(vpkt.Payload) + + if d.fragmentsSize > vp8.MaxFrameSize { + errSize := d.fragmentsSize + d.resetFragments() + return nil, fmt.Errorf("frame size (%d) is too big, maximum is %d", + errSize, vp8.MaxFrameSize) + } + + d.fragments = append(d.fragments, vpkt.Payload) + d.fragmentNextSeqNum++ + + if !pkt.Marker { + return nil, ErrMorePacketsNeeded + } + + frame = joinFragments(d.fragments, d.fragmentsSize) + d.resetFragments() + } + + return frame, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8/encoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8/encoder.go new file mode 100644 index 000000000..eb2c23043 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8/encoder.go @@ -0,0 +1,98 @@ +package rtpvp8 + +import ( + "crypto/rand" + "fmt" + + "github.com/pion/rtp" + "github.com/pion/rtp/codecs" +) + +const ( + rtpVersion = 2 + defaultPayloadMaxSize = 1460 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header) +) + +func randUint32() (uint32, error) { + var b [4]byte + _, err := rand.Read(b[:]) + if err != nil { + return 0, err + } + return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil +} + +// Encoder is a RTP/VP8 encoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc7741 +type Encoder struct { + // payload type of packets. + PayloadType uint8 + + // SSRC of packets (optional). + // It defaults to a random value. + SSRC *uint32 + + // initial sequence number of packets (optional). + // It defaults to a random value. + InitialSequenceNumber *uint16 + + // maximum size of packet payloads (optional). + // It defaults to 1460. + PayloadMaxSize int + + sequenceNumber uint16 + vp codecs.VP8Payloader +} + +// Init initializes the encoder. +func (e *Encoder) Init() error { + if e.SSRC == nil { + v, err := randUint32() + if err != nil { + return err + } + e.SSRC = &v + } + if e.InitialSequenceNumber == nil { + v, err := randUint32() + if err != nil { + return err + } + v2 := uint16(v) + e.InitialSequenceNumber = &v2 + } + if e.PayloadMaxSize == 0 { + e.PayloadMaxSize = defaultPayloadMaxSize + } + + e.sequenceNumber = *e.InitialSequenceNumber + return nil +} + +// Encode encodes a VP8 frame into RTP/VP8 packets. +func (e *Encoder) Encode(frame []byte) ([]*rtp.Packet, error) { + payloads := e.vp.Payload(uint16(e.PayloadMaxSize), frame) + if payloads == nil { + return nil, fmt.Errorf("payloader failed") + } + + plen := len(payloads) + ret := make([]*rtp.Packet, plen) + + for i, payload := range payloads { + ret[i] = &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: e.PayloadType, + SequenceNumber: e.sequenceNumber, + SSRC: *e.SSRC, + Marker: i == (plen - 1), + }, + Payload: payload, + } + + e.sequenceNumber++ + } + + return ret, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8/rtpvp8.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8/rtpvp8.go new file mode 100644 index 000000000..3094c477a --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8/rtpvp8.go @@ -0,0 +1,2 @@ +// Package rtpvp8 contains a RTP/VP8 decoder and encoder. +package rtpvp8 diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9/decoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9/decoder.go new file mode 100644 index 000000000..628d17589 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9/decoder.go @@ -0,0 +1,108 @@ +package rtpvp9 + +import ( + "errors" + "fmt" + + "github.com/bluenviron/mediacommon/v2/pkg/codecs/vp9" + "github.com/pion/rtp" + "github.com/pion/rtp/codecs" +) + +// ErrMorePacketsNeeded is returned when more packets are needed. +var ErrMorePacketsNeeded = errors.New("need more packets") + +// ErrNonStartingPacketAndNoPrevious is returned when we received a non-starting +// packet of a fragmented frame and we didn't received anything before. +// It's normal to receive this when decoding a stream that has been already +// running for some time. +var ErrNonStartingPacketAndNoPrevious = errors.New( + "received a non-starting fragment without any previous starting fragment") + +func joinFragments(fragments [][]byte, size int) []byte { + ret := make([]byte, size) + n := 0 + for _, p := range fragments { + n += copy(ret[n:], p) + } + return ret +} + +// Decoder is a RTP/VP9 decoder. +// Specification: https://datatracker.ietf.org/doc/html/draft-ietf-payload-vp9-16 +type Decoder struct { + firstPacketReceived bool + fragmentsSize int + fragments [][]byte + fragmentNextSeqNum uint16 +} + +// Init initializes the decoder. +func (d *Decoder) Init() error { + return nil +} + +func (d *Decoder) resetFragments() { + d.fragments = d.fragments[:0] + d.fragmentsSize = 0 +} + +// Decode decodes a VP9 frame from a RTP packet. +func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, error) { + var vpkt codecs.VP9Packet + _, err := vpkt.Unmarshal(pkt.Payload) + if err != nil { + d.resetFragments() + return nil, err + } + + var frame []byte + + if vpkt.B { + d.resetFragments() + d.firstPacketReceived = true + + if !vpkt.E { + d.fragmentsSize = len(vpkt.Payload) + d.fragments = append(d.fragments, vpkt.Payload) + d.fragmentNextSeqNum = pkt.SequenceNumber + 1 + return nil, ErrMorePacketsNeeded + } + + frame = vpkt.Payload + } else { + if d.fragmentsSize == 0 { + if !d.firstPacketReceived { + return nil, ErrNonStartingPacketAndNoPrevious + } + + return nil, fmt.Errorf("received a non-starting fragment") + } + + if pkt.SequenceNumber != d.fragmentNextSeqNum { + d.resetFragments() + return nil, fmt.Errorf("discarding frame since a RTP packet is missing") + } + + d.fragmentsSize += len(vpkt.Payload) + + if d.fragmentsSize > vp9.MaxFrameSize { + errSize := d.fragmentsSize + d.resetFragments() + return nil, fmt.Errorf("frame size (%d) is too big, maximum is %d", + errSize, vp9.MaxFrameSize) + } + + d.fragments = append(d.fragments, vpkt.Payload) + d.fragmentNextSeqNum++ + + if !vpkt.E { + return nil, ErrMorePacketsNeeded + } + + frame = joinFragments(d.fragments, d.fragmentsSize) + d.resetFragments() + } + + return frame, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9/encoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9/encoder.go new file mode 100644 index 000000000..f222e0c07 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9/encoder.go @@ -0,0 +1,115 @@ +package rtpvp9 + +import ( + "crypto/rand" + "fmt" + + "github.com/pion/rtp" + "github.com/pion/rtp/codecs" +) + +const ( + rtpVersion = 2 + defaultPayloadMaxSize = 1460 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header) +) + +func randUint32() (uint32, error) { + var b [4]byte + _, err := rand.Read(b[:]) + if err != nil { + return 0, err + } + return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil +} + +// Encoder is a RTP/VP9 encoder. +// Specification: https://datatracker.ietf.org/doc/html/draft-ietf-payload-vp9-16 +type Encoder struct { + // payload type of packets. + PayloadType uint8 + + // SSRC of packets (optional). + // It defaults to a random value. + SSRC *uint32 + + // initial sequence number of packets (optional). + // It defaults to a random value. + InitialSequenceNumber *uint16 + + // maximum size of packet payloads (optional). + // It defaults to 1460. + PayloadMaxSize int + + // initial picture ID of frames (optional). + // It defaults to a random value. + InitialPictureID *uint16 + + sequenceNumber uint16 + vp codecs.VP9Payloader +} + +// Init initializes the encoder. +func (e *Encoder) Init() error { + if e.SSRC == nil { + v, err := randUint32() + if err != nil { + return err + } + e.SSRC = &v + } + if e.InitialSequenceNumber == nil { + v, err := randUint32() + if err != nil { + return err + } + v2 := uint16(v) + e.InitialSequenceNumber = &v2 + } + if e.PayloadMaxSize == 0 { + e.PayloadMaxSize = defaultPayloadMaxSize + } + if e.InitialPictureID == nil { + v, err := randUint32() + if err != nil { + return err + } + v2 := uint16(v) + e.InitialPictureID = &v2 + } + + e.sequenceNumber = *e.InitialSequenceNumber + + e.vp.InitialPictureIDFn = func() uint16 { + return *e.InitialPictureID + } + + return nil +} + +// Encode encodes a VP9 frame into RTP/VP9 packets. +func (e *Encoder) Encode(frame []byte) ([]*rtp.Packet, error) { + payloads := e.vp.Payload(uint16(e.PayloadMaxSize), frame) + if payloads == nil { + return nil, fmt.Errorf("payloader failed") + } + + plen := len(payloads) + ret := make([]*rtp.Packet, plen) + + for i, payload := range payloads { + ret[i] = &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: e.PayloadType, + SequenceNumber: e.sequenceNumber, + SSRC: *e.SSRC, + Marker: i == (plen - 1), + }, + Payload: payload, + } + + e.sequenceNumber++ + } + + return ret, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9/rtpvp9.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9/rtpvp9.go new file mode 100644 index 000000000..c89659eef --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9/rtpvp9.go @@ -0,0 +1,2 @@ +// Package rtpvp9 contains a RTP/VP9 decoder and encoder. +package rtpvp9 diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/speex.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/speex.go new file mode 100644 index 000000000..9dab74b21 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/speex.go @@ -0,0 +1,79 @@ +package format + +import ( + "fmt" + "strconv" + + "github.com/pion/rtp" +) + +// Speex is the RTP format for the Speex codec. +// Specification: https://datatracker.ietf.org/doc/html/rfc5574 +type Speex struct { + PayloadTyp uint8 + SampleRate int + VBR *bool +} + +func (f *Speex) unmarshal(ctx *unmarshalContext) error { + f.PayloadTyp = ctx.payloadType + + sampleRate, err := strconv.ParseUint(ctx.clock, 10, 31) + if err != nil { + return err + } + f.SampleRate = int(sampleRate) + + for key, val := range ctx.fmtp { + if key == "vbr" { + if val != "on" && val != "off" { + return fmt.Errorf("invalid vbr value: %v", val) + } + + v := (val == "on") + f.VBR = &v + } + } + + return nil +} + +// Codec implements Format. +func (f *Speex) Codec() string { + return "Speex" +} + +// ClockRate implements Format. +func (f *Speex) ClockRate() int { + return f.SampleRate +} + +// PayloadType implements Format. +func (f *Speex) PayloadType() uint8 { + return f.PayloadTyp +} + +// RTPMap implements Format. +func (f *Speex) RTPMap() string { + return "speex/" + strconv.FormatInt(int64(f.SampleRate), 10) +} + +// FMTP implements Format. +func (f *Speex) FMTP() map[string]string { + fmtp := make(map[string]string) + + if f.VBR != nil { + if *f.VBR { + fmtp["vbr"] = "on" + } else { + fmtp["vbr"] = "off" + } + } + + return fmtp +} + +// PTSEqualsDTS implements Format. +func (f *Speex) PTSEqualsDTS(*rtp.Packet) bool { + return true +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/vorbis.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/vorbis.go new file mode 100644 index 000000000..9d5d8017b --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/vorbis.go @@ -0,0 +1,92 @@ +package format + +import ( + "encoding/base64" + "fmt" + "strconv" + "strings" + + "github.com/pion/rtp" +) + +// Vorbis is the RTP format for the Vorbis codec. +// Specification: https://datatracker.ietf.org/doc/html/rfc5215 +type Vorbis struct { + PayloadTyp uint8 + SampleRate int + ChannelCount int + Configuration []byte +} + +func (f *Vorbis) unmarshal(ctx *unmarshalContext) error { + f.PayloadTyp = ctx.payloadType + + tmp := strings.SplitN(ctx.clock, "/", 2) + if len(tmp) != 2 { + return fmt.Errorf("invalid clock (%v)", ctx.clock) + } + + sampleRate, err := strconv.ParseUint(tmp[0], 10, 31) + if err != nil { + return err + } + f.SampleRate = int(sampleRate) + + channelCount, err := strconv.ParseUint(tmp[1], 10, 31) + if err != nil { + return err + } + f.ChannelCount = int(channelCount) + + for key, val := range ctx.fmtp { + if key == "configuration" { + conf, err := base64.StdEncoding.DecodeString(val) + if err != nil { + return fmt.Errorf("invalid config: %v", val) + } + + f.Configuration = conf + } + } + + if f.Configuration == nil { + return fmt.Errorf("config is missing") + } + + return nil +} + +// Codec implements Format. +func (f *Vorbis) Codec() string { + return "Vorbis" +} + +// ClockRate implements Format. +func (f *Vorbis) ClockRate() int { + return f.SampleRate +} + +// PayloadType implements Format. +func (f *Vorbis) PayloadType() uint8 { + return f.PayloadTyp +} + +// RTPMap implements Format. +func (f *Vorbis) RTPMap() string { + return "VORBIS/" + strconv.FormatInt(int64(f.SampleRate), 10) + + "/" + strconv.FormatInt(int64(f.ChannelCount), 10) +} + +// FMTP implements Format. +func (f *Vorbis) FMTP() map[string]string { + fmtp := map[string]string{ + "configuration": base64.StdEncoding.EncodeToString(f.Configuration), + } + + return fmtp +} + +// PTSEqualsDTS implements Format. +func (f *Vorbis) PTSEqualsDTS(*rtp.Packet) bool { + return true +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/vp8.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/vp8.go new file mode 100644 index 000000000..cf9337972 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/vp8.go @@ -0,0 +1,112 @@ +package format + +import ( + "fmt" + "strconv" + + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8" +) + +// VP8 is the RTP format for the VP8 codec. +// Specification: https://datatracker.ietf.org/doc/html/rfc7741 +type VP8 struct { + PayloadTyp uint8 + MaxFR *int + MaxFS *int +} + +func (f *VP8) unmarshal(ctx *unmarshalContext) error { + f.PayloadTyp = ctx.payloadType + + for key, val := range ctx.fmtp { + switch key { + case "max-fr": + n, err := strconv.ParseUint(val, 10, 31) + if err != nil { + return fmt.Errorf("invalid max-fr: %v", val) + } + + v2 := int(n) + f.MaxFR = &v2 + + case "max-fs": + n, err := strconv.ParseUint(val, 10, 31) + if err != nil { + return fmt.Errorf("invalid max-fs: %v", val) + } + + v2 := int(n) + f.MaxFS = &v2 + } + } + + return nil +} + +// Codec implements Format. +func (f *VP8) Codec() string { + return "VP8" +} + +// ClockRate implements Format. +func (f *VP8) ClockRate() int { + return 90000 +} + +// PayloadType implements Format. +func (f *VP8) PayloadType() uint8 { + return f.PayloadTyp +} + +// RTPMap implements Format. +func (f *VP8) RTPMap() string { + return "VP8/90000" +} + +// FMTP implements Format. +func (f *VP8) FMTP() map[string]string { + fmtp := make(map[string]string) + + if f.MaxFR != nil { + fmtp["max-fr"] = strconv.FormatInt(int64(*f.MaxFR), 10) + } + + if f.MaxFS != nil { + fmtp["max-fs"] = strconv.FormatInt(int64(*f.MaxFS), 10) + } + + return fmtp +} + +// PTSEqualsDTS implements Format. +func (f *VP8) PTSEqualsDTS(*rtp.Packet) bool { + return true +} + +// CreateDecoder creates a decoder able to decode the content of the format. +func (f *VP8) CreateDecoder() (*rtpvp8.Decoder, error) { + d := &rtpvp8.Decoder{} + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + +// CreateEncoder creates an encoder able to encode the content of the format. +func (f *VP8) CreateEncoder() (*rtpvp8.Encoder, error) { + e := &rtpvp8.Encoder{ + PayloadType: f.PayloadTyp, + } + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/vp9.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/vp9.go new file mode 100644 index 000000000..bc42443d5 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/format/vp9.go @@ -0,0 +1,124 @@ +package format //nolint:dupl + +import ( + "fmt" + "strconv" + + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9" +) + +// VP9 is the RTP format for the VP9 codec. +// Specification: https://datatracker.ietf.org/doc/html/draft-ietf-payload-vp9-16 +type VP9 struct { + PayloadTyp uint8 + MaxFR *int + MaxFS *int + ProfileID *int +} + +func (f *VP9) unmarshal(ctx *unmarshalContext) error { + f.PayloadTyp = ctx.payloadType + + for key, val := range ctx.fmtp { + switch key { + case "max-fr": + n, err := strconv.ParseUint(val, 10, 31) + if err != nil { + return fmt.Errorf("invalid max-fr: %v", val) + } + + v2 := int(n) + f.MaxFR = &v2 + + case "max-fs": + n, err := strconv.ParseUint(val, 10, 31) + if err != nil { + return fmt.Errorf("invalid max-fs: %v", val) + } + + v2 := int(n) + f.MaxFS = &v2 + + case "profile-id": + n, err := strconv.ParseUint(val, 10, 31) + if err != nil { + return fmt.Errorf("invalid profile-id: %v", val) + } + + v2 := int(n) + f.ProfileID = &v2 + } + } + + return nil +} + +// Codec implements Format. +func (f *VP9) Codec() string { + return "VP9" +} + +// ClockRate implements Format. +func (f *VP9) ClockRate() int { + return 90000 +} + +// PayloadType implements Format. +func (f *VP9) PayloadType() uint8 { + return f.PayloadTyp +} + +// RTPMap implements Format. +func (f *VP9) RTPMap() string { + return "VP9/90000" +} + +// FMTP implements Format. +func (f *VP9) FMTP() map[string]string { + fmtp := make(map[string]string) + + if f.MaxFR != nil { + fmtp["max-fr"] = strconv.FormatInt(int64(*f.MaxFR), 10) + } + if f.MaxFS != nil { + fmtp["max-fs"] = strconv.FormatInt(int64(*f.MaxFS), 10) + } + if f.ProfileID != nil { + fmtp["profile-id"] = strconv.FormatInt(int64(*f.ProfileID), 10) + } + + return fmtp +} + +// PTSEqualsDTS implements Format. +func (f *VP9) PTSEqualsDTS(*rtp.Packet) bool { + return true +} + +// CreateDecoder creates a decoder able to decode the content of the format. +func (f *VP9) CreateDecoder() (*rtpvp9.Decoder, error) { + d := &rtpvp9.Decoder{} + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + +// CreateEncoder creates an encoder able to encode the content of the format. +func (f *VP9) CreateEncoder() (*rtpvp9.Encoder, error) { + e := &rtpvp9.Encoder{ + PayloadType: f.PayloadTyp, + } + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/authenticate.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/authenticate.go new file mode 100644 index 000000000..3a010a932 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/authenticate.go @@ -0,0 +1,186 @@ +// Package headers contains various RTSP headers. +package headers + +import ( + "fmt" + "strings" + + "github.com/bluenviron/gortsplib/v4/pkg/base" +) + +// AuthMethod is an authentication method. +type AuthMethod int + +// authentication methods. +const ( + AuthMethodBasic AuthMethod = iota + AuthMethodDigest +) + +// AuthAlgorithm is a digest algorithm. +type AuthAlgorithm int + +// digest algorithms. +const ( + AuthAlgorithmMD5 AuthAlgorithm = iota + AuthAlgorithmSHA256 +) + +func parseAuthAlgorithm(v string) (AuthAlgorithm, error) { + switch { + case strings.ToLower(v) == "md5": + return AuthAlgorithmMD5, nil + + case strings.ToLower(v) == "sha-256": + return AuthAlgorithmSHA256, nil + + default: + return 0, fmt.Errorf("unrecognized algorithm: %v", v) + } +} + +// Authenticate is a WWW-Authenticate header. +type Authenticate struct { + // authentication method + Method AuthMethod + + // realm + Realm string + + // + // Digest authentication fields + // + + // nonce + Nonce string + + // opaque + Opaque *string + + // stale + Stale *string + + // algorithm + Algorithm *AuthAlgorithm +} + +// Unmarshal decodes a WWW-Authenticate header. +func (h *Authenticate) Unmarshal(v base.HeaderValue) error { + if len(v) == 0 { + return fmt.Errorf("value not provided") + } + + if len(v) > 1 { + return fmt.Errorf("value provided multiple times (%v)", v) + } + + v0 := v[0] + + i := strings.IndexByte(v0, ' ') + if i < 0 { + return fmt.Errorf("unable to split between method and keys (%v)", v0) + } + method, v0 := v0[:i], v0[i+1:] + + switch method { + case "Basic": + h.Method = AuthMethodBasic + + case "Digest": + h.Method = AuthMethodDigest + + default: + return fmt.Errorf("invalid method (%s)", method) + } + + if h.Method == AuthMethodBasic { + kvs, err := keyValParse(v0, ',') + if err != nil { + return err + } + + realmReceived := false + + for k, rv := range kvs { + v := rv + + if k == "realm" { + h.Realm = v + realmReceived = true + } + } + + if !realmReceived { + return fmt.Errorf("realm is missing") + } + } else { // digest + kvs, err := keyValParse(v0, ',') + if err != nil { + return err + } + + realmReceived := false + nonceReceived := false + + for k, rv := range kvs { + v := rv + + switch k { + case "realm": + h.Realm = v + realmReceived = true + + case "nonce": + h.Nonce = v + nonceReceived = true + + case "opaque": + h.Opaque = &v + + case "stale": + h.Stale = &v + + case "algorithm": + a, err := parseAuthAlgorithm(v) + if err != nil { + return err + } + h.Algorithm = &a + } + } + + if !realmReceived || !nonceReceived { + return fmt.Errorf("one or more digest fields are missing") + } + } + + return nil +} + +// Marshal encodes a WWW-Authenticate header. +func (h Authenticate) Marshal() base.HeaderValue { + if h.Method == AuthMethodBasic { + return base.HeaderValue{"Basic " + + "realm=\"" + h.Realm + "\""} + } + + ret := "Digest realm=\"" + h.Realm + "\", nonce=\"" + h.Nonce + "\"" + + if h.Opaque != nil { + ret += ", opaque=\"" + *h.Opaque + "\"" + } + + if h.Stale != nil { + ret += ", stale=\"" + *h.Stale + "\"" + } + + if h.Algorithm != nil { + if *h.Algorithm == AuthAlgorithmMD5 { + ret += ", algorithm=\"MD5\"" + } else { + ret += ", algorithm=\"SHA-256\"" + } + } + + return base.HeaderValue{ret} +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/authorization.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/authorization.go new file mode 100644 index 000000000..866fdd392 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/authorization.go @@ -0,0 +1,179 @@ +package headers + +import ( + "encoding/base64" + "fmt" + "strings" + + "github.com/bluenviron/gortsplib/v4/pkg/base" +) + +// Authorization is an Authorization header. +type Authorization struct { + // authentication method + Method AuthMethod + + // username + Username string + + // + // Basic authentication fields + // + + // user + // + // Deprecated: replaced by Username. + BasicUser string + + // password + BasicPass string + + // + // Digest authentication fields + // + + // realm + Realm string + + // nonce + Nonce string + + // URI + URI string + + // response + Response string + + // opaque + Opaque *string + + // algorithm + Algorithm *AuthAlgorithm +} + +// Unmarshal decodes an Authorization header. +func (h *Authorization) Unmarshal(v base.HeaderValue) error { + if len(v) == 0 { + return fmt.Errorf("value not provided") + } + + if len(v) > 1 { + return fmt.Errorf("value provided multiple times (%v)", v) + } + + v0 := v[0] + + i := strings.IndexByte(v0, ' ') + if i < 0 { + return fmt.Errorf("unable to split between method and keys (%v)", v0) + } + method, v0 := v0[:i], v0[i+1:] + + switch method { + case "Basic": + h.Method = AuthMethodBasic + + case "Digest": + h.Method = AuthMethodDigest + + default: + return fmt.Errorf("invalid method (%s)", method) + } + + if h.Method == AuthMethodBasic { + tmp, err := base64.StdEncoding.DecodeString(v0) + if err != nil { + return fmt.Errorf("invalid value") + } + + tmp2 := strings.Split(string(tmp), ":") + if len(tmp2) != 2 { + return fmt.Errorf("invalid value") + } + + h.Username, h.BasicPass = tmp2[0], tmp2[1] + h.BasicUser = h.Username + } else { // digest + kvs, err := keyValParse(v0, ',') + if err != nil { + return err + } + + realmReceived := false + usernameReceived := false + nonceReceived := false + uriReceived := false + responseReceived := false + + for k, rv := range kvs { + v := rv + + switch k { + case "realm": + h.Realm = v + realmReceived = true + + case "username": + h.Username = v + usernameReceived = true + + case "nonce": + h.Nonce = v + nonceReceived = true + + case "uri": + h.URI = v + uriReceived = true + + case "response": + h.Response = v + responseReceived = true + + case "opaque": + h.Opaque = &v + + case "algorithm": + a, err := parseAuthAlgorithm(v) + if err != nil { + return err + } + h.Algorithm = &a + } + } + + if !realmReceived || !usernameReceived || !nonceReceived || !uriReceived || !responseReceived { + return fmt.Errorf("one or more digest fields are missing") + } + } + + return nil +} + +// Marshal encodes an Authorization header. +func (h Authorization) Marshal() base.HeaderValue { + if h.Method == AuthMethodBasic { + if h.BasicUser != "" { + h.Username = h.BasicUser + } + return base.HeaderValue{"Basic " + + base64.StdEncoding.EncodeToString([]byte(h.Username+":"+h.BasicPass))} + } + + ret := "Digest " + + "username=\"" + h.Username + "\", realm=\"" + h.Realm + "\", " + + "nonce=\"" + h.Nonce + "\", uri=\"" + h.URI + "\", response=\"" + h.Response + "\"" + + if h.Opaque != nil { + ret += ", opaque=\"" + *h.Opaque + "\"" + } + + if h.Algorithm != nil { + if *h.Algorithm == AuthAlgorithmMD5 { + ret += ", algorithm=\"MD5\"" + } else { + ret += ", algorithm=\"SHA-256\"" + } + } + + return base.HeaderValue{ret} +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/keyval.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/keyval.go new file mode 100644 index 000000000..bd72e27a5 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/keyval.go @@ -0,0 +1,77 @@ +package headers + +import ( + "fmt" +) + +func readKey(str string, separator byte) (string, string) { + i := 0 + for { + if i >= len(str) || str[i] == '=' || str[i] == separator { + break + } + + i++ + } + return str[:i], str[i:] +} + +func readValue(origstr string, str string, separator byte) (string, string, error) { + if len(str) > 0 && str[0] == '"' { + i := 1 + for { + if i >= len(str) { + return "", "", fmt.Errorf("apexes not closed (%v)", origstr) + } + + if str[i] == '"' { + return str[1:i], str[i+1:], nil + } + + i++ + } + } + + i := 0 + for { + if i >= len(str) || str[i] == separator { + return str[:i], str[i:], nil + } + i++ + } +} + +func keyValParse(str string, separator byte) (map[string]string, error) { + ret := make(map[string]string) + origstr := str + + for len(str) > 0 { + var k string + k, str = readKey(str, separator) + + if len(str) > 0 && str[0] == '=' { + var v string + var err error + v, str, err = readValue(origstr, str[1:], separator) + if err != nil { + return nil, err + } + + ret[k] = v + } else { + ret[k] = "" + } + + // skip separator + if len(str) > 0 && str[0] == separator { + str = str[1:] + } + + // skip spaces + for len(str) > 0 && str[0] == ' ' { + str = str[1:] + } + } + + return ret, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/range.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/range.go new file mode 100644 index 000000000..a6665eced --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/range.go @@ -0,0 +1,354 @@ +package headers + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/bluenviron/gortsplib/v4/pkg/base" +) + +func leadingZero(v uint) string { + ret := "" + if v < 10 { + ret += "0" + } + ret += strconv.FormatUint(uint64(v), 10) + return ret +} + +// RangeSMPTETime is a time expressed in SMPTE unit. +type RangeSMPTETime struct { + Time time.Duration + Frame uint + Subframe uint +} + +func (t *RangeSMPTETime) unmarshal(s string) error { + parts := strings.Split(s, ":") + if len(parts) != 3 && len(parts) != 4 { + return fmt.Errorf("invalid SMPTE time (%v)", s) + } + + tmp, err := strconv.ParseUint(parts[0], 10, 64) + if err != nil { + return err + } + hours := tmp + + tmp, err = strconv.ParseUint(parts[1], 10, 64) + if err != nil { + return err + } + mins := tmp + + tmp, err = strconv.ParseUint(parts[2], 10, 64) + if err != nil { + return err + } + seconds := tmp + + t.Time = time.Duration(seconds+mins*60+hours*3600) * time.Second + + if len(parts) == 4 { + parts = strings.Split(parts[3], ".") + if len(parts) == 2 { + tmp, err := strconv.ParseUint(parts[0], 10, 64) + if err != nil { + return err + } + t.Frame = uint(tmp) + + tmp, err = strconv.ParseUint(parts[1], 10, 64) + if err != nil { + return err + } + t.Subframe = uint(tmp) + } else { + tmp, err := strconv.ParseUint(parts[0], 10, 64) + if err != nil { + return err + } + t.Frame = uint(tmp) + } + } + + return nil +} + +func (t RangeSMPTETime) marshal() string { + d := uint64(t.Time.Seconds()) + hours := d / 3600 + d %= 3600 + mins := d / 60 + secs := d % 60 + + ret := strconv.FormatUint(hours, 10) + ":" + leadingZero(uint(mins)) + ":" + leadingZero(uint(secs)) + + if t.Frame > 0 || t.Subframe > 0 { + ret += ":" + leadingZero(t.Frame) + + if t.Subframe > 0 { + ret += "." + leadingZero(t.Subframe) + } + } + + return ret +} + +// RangeSMPTE is a range expressed in SMPTE unit. +type RangeSMPTE struct { + Start RangeSMPTETime + End *RangeSMPTETime +} + +func (r *RangeSMPTE) unmarshal(start string, end string) error { + err := r.Start.unmarshal(start) + if err != nil { + return err + } + + if end != "" { + var v RangeSMPTETime + err := v.unmarshal(end) + if err != nil { + return err + } + r.End = &v + } + + return nil +} + +func (r RangeSMPTE) marshal() string { + ret := "smpte=" + r.Start.marshal() + "-" + if r.End != nil { + ret += r.End.marshal() + } + return ret +} + +func unmarshalRangeNPTTime(d *time.Duration, s string) error { + parts := strings.Split(s, ":") + if len(parts) > 3 { + return fmt.Errorf("invalid NPT time (%v)", s) + } + + var hours uint64 + if len(parts) == 3 { + tmp, err := strconv.ParseUint(parts[0], 10, 64) + if err != nil { + return err + } + hours = tmp + parts = parts[1:] + } + + var mins uint64 + if len(parts) >= 2 { + tmp, err := strconv.ParseUint(parts[0], 10, 64) + if err != nil { + return err + } + mins = tmp + parts = parts[1:] + } + + tmp, err := strconv.ParseFloat(parts[0], 64) + if err != nil { + return err + } + seconds := tmp + + *d = time.Duration(seconds*float64(time.Second)) + + time.Duration(mins*60+hours*3600)*time.Second + + return nil +} + +func marshalRangeNPTTime(d time.Duration) string { + return strconv.FormatFloat(d.Seconds(), 'f', -1, 64) +} + +// RangeNPT is a range expressed in NPT units. +type RangeNPT struct { + Start time.Duration + End *time.Duration +} + +func (r *RangeNPT) unmarshal(start string, end string) error { + err := unmarshalRangeNPTTime(&r.Start, start) + if err != nil { + return err + } + + if end != "" { + var v time.Duration + err := unmarshalRangeNPTTime(&v, end) + if err != nil { + return err + } + r.End = &v + } + + return nil +} + +func (r RangeNPT) marshal() string { + ret := "npt=" + marshalRangeNPTTime(r.Start) + "-" + if r.End != nil { + ret += marshalRangeNPTTime(*r.End) + } + return ret +} + +func unmarshalRangeUTCTime(t *time.Time, s string) error { + tmp, err := time.Parse("20060102T150405Z", s) + if err != nil { + return err + } + *t = tmp + return nil +} + +func marshalRangeUTCTime(t time.Time) string { + return t.Format("20060102T150405Z") +} + +// RangeUTC is a range expressed in UTC units. +type RangeUTC struct { + Start time.Time + End *time.Time +} + +func (r *RangeUTC) unmarshal(start string, end string) error { + err := unmarshalRangeUTCTime(&r.Start, start) + if err != nil { + return err + } + + if end != "" { + var v time.Time + err := unmarshalRangeUTCTime(&v, end) + if err != nil { + return err + } + r.End = &v + } + + return nil +} + +func (r RangeUTC) marshal() string { + ret := "clock=" + marshalRangeUTCTime(r.Start) + "-" + if r.End != nil { + ret += marshalRangeUTCTime(*r.End) + } + return ret +} + +// RangeValue can be +// - RangeSMPTE +// - RangeNPT +// - RangeUTC +type RangeValue interface { + unmarshal(string, string) error + marshal() string +} + +func rangeValueUnmarshal(s RangeValue, v string) error { + parts := strings.Split(v, "-") + if len(parts) != 2 { + return fmt.Errorf("invalid value (%v)", v) + } + + return s.unmarshal(parts[0], parts[1]) +} + +// Range is a Range header. +type Range struct { + // range expressed in a certain unit. + Value RangeValue + + // time at which the operation is to be made effective. + Time *time.Time +} + +// Unmarshal decodes a Range header. +func (h *Range) Unmarshal(v base.HeaderValue) error { + if len(v) == 0 { + return fmt.Errorf("value not provided") + } + + if len(v) > 1 { + return fmt.Errorf("value provided multiple times (%v)", v) + } + + v0 := v[0] + + kvs, err := keyValParse(v0, ';') + if err != nil { + return err + } + + specFound := false + + for k, v := range kvs { + switch k { + case "smpte": + s := &RangeSMPTE{} + err := rangeValueUnmarshal(s, v) + if err != nil { + return err + } + + specFound = true + h.Value = s + + case "npt": + s := &RangeNPT{} + err := rangeValueUnmarshal(s, v) + if err != nil { + return err + } + + specFound = true + h.Value = s + + case "clock": + s := &RangeUTC{} + err := rangeValueUnmarshal(s, v) + if err != nil { + return err + } + + specFound = true + h.Value = s + + case "time": + var t time.Time + err := unmarshalRangeUTCTime(&t, v) + if err != nil { + return err + } + + h.Time = &t + } + } + + if !specFound { + return fmt.Errorf("value not found (%v)", v[0]) + } + + return nil +} + +// Marshal encodes a Range header. +func (h Range) Marshal() base.HeaderValue { + v := h.Value.marshal() + if h.Time != nil { + v += ";time=" + marshalRangeUTCTime(*h.Time) + } + return base.HeaderValue{v} +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/rtpinfo.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/rtpinfo.go new file mode 100644 index 000000000..a87604a77 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/rtpinfo.go @@ -0,0 +1,101 @@ +package headers + +import ( + "fmt" + "strconv" + "strings" + + "github.com/bluenviron/gortsplib/v4/pkg/base" +) + +// RTPInfoEntry is an entry of a RTP-Info header. +type RTPInfoEntry struct { + URL string + SequenceNumber *uint16 + Timestamp *uint32 +} + +// RTPInfo is a RTP-Info header. +type RTPInfo []*RTPInfoEntry + +// Unmarshal decodes a RTP-Info header. +func (h *RTPInfo) Unmarshal(v base.HeaderValue) error { + if len(v) == 0 { + return fmt.Errorf("value not provided") + } + + if len(v) > 1 { + return fmt.Errorf("value provided multiple times (%v)", v) + } + + for _, part := range strings.Split(v[0], ",") { + e := &RTPInfoEntry{} + + // remove leading spaces + part = strings.TrimLeft(part, " ") + + kvs, err := keyValParse(part, ';') + if err != nil { + return err + } + + urlReceived := false + + for k, v := range kvs { + switch k { + case "url": + e.URL = v + urlReceived = true + + case "seq": + vi, err := strconv.ParseUint(v, 10, 16) + if err != nil { + return err + } + vi2 := uint16(vi) + e.SequenceNumber = &vi2 + + case "rtptime": + vi, err := strconv.ParseUint(v, 10, 32) + if err != nil { + return err + } + vi2 := uint32(vi) + e.Timestamp = &vi2 + + default: + // ignore non-standard keys + } + } + + if !urlReceived { + return fmt.Errorf("URL is missing") + } + + *h = append(*h, e) + } + + return nil +} + +// Marshal encodes a RTP-Info header. +func (h RTPInfo) Marshal() base.HeaderValue { + rets := make([]string, len(h)) + + for i, e := range h { + var tmp []string + tmp = append(tmp, "url="+e.URL) + + if e.SequenceNumber != nil { + tmp = append(tmp, "seq="+strconv.FormatUint(uint64(*e.SequenceNumber), 10)) + } + + if e.Timestamp != nil { + tmp = append(tmp, "rtptime="+strconv.FormatUint(uint64(*e.Timestamp), 10)) + } + + rets[i] = strings.Join(tmp, ";") + } + + return base.HeaderValue{strings.Join(rets, ",")} +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/session.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/session.go new file mode 100644 index 000000000..683b3c198 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/session.go @@ -0,0 +1,71 @@ +package headers + +import ( + "fmt" + "strconv" + "strings" + + "github.com/bluenviron/gortsplib/v4/pkg/base" +) + +// Session is a Session header. +type Session struct { + // session id + Session string + + // (optional) a timeout + Timeout *uint +} + +// Unmarshal decodes a Session header. +func (h *Session) Unmarshal(v base.HeaderValue) error { + if len(v) == 0 { + return fmt.Errorf("value not provided") + } + + if len(v) > 1 { + return fmt.Errorf("value provided multiple times (%v)", v) + } + + v0 := v[0] + + i := strings.IndexByte(v0, ';') + if i < 0 { + h.Session = v0 + return nil + } + + h.Session = v0[:i] + v0 = v0[i+1:] + + v0 = strings.TrimLeft(v0, " ") + + kvs, err := keyValParse(v0, ';') + if err != nil { + return err + } + + for k, v := range kvs { + if k == "timeout" { + iv, err := strconv.ParseUint(v, 10, 32) + if err != nil { + return err + } + uiv := uint(iv) + h.Timeout = &uiv + } + } + + return nil +} + +// Marshal encodes a Session header. +func (h Session) Marshal() base.HeaderValue { + ret := h.Session + + if h.Timeout != nil { + ret += ";timeout=" + strconv.FormatUint(uint64(*h.Timeout), 10) + } + + return base.HeaderValue{ret} +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/transport.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/transport.go new file mode 100644 index 000000000..e53d2fe56 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/headers/transport.go @@ -0,0 +1,379 @@ +package headers + +import ( + "encoding/hex" + "fmt" + "net" + "strconv" + "strings" + + "github.com/bluenviron/gortsplib/v4/pkg/base" +) + +func parsePorts(val string) (*[2]int, error) { + ports := strings.Split(val, "-") + if len(ports) == 2 { + port1, err := strconv.ParseUint(ports[0], 10, 31) + if err != nil { + return &[2]int{0, 0}, fmt.Errorf("invalid ports (%v)", val) + } + + port2, err := strconv.ParseUint(ports[1], 10, 31) + if err != nil { + return &[2]int{0, 0}, fmt.Errorf("invalid ports (%v)", val) + } + + return &[2]int{int(port1), int(port2)}, nil + } + + if len(ports) == 1 { + port1, err := strconv.ParseUint(ports[0], 10, 31) + if err != nil { + return &[2]int{0, 0}, fmt.Errorf("invalid ports (%v)", val) + } + + return &[2]int{int(port1), int(port1 + 1)}, nil + } + + return &[2]int{0, 0}, fmt.Errorf("invalid ports (%v)", val) +} + +// TransportProtocol is a transport protocol. +type TransportProtocol int + +// transport protocols. +const ( + TransportProtocolUDP TransportProtocol = iota + TransportProtocolTCP +) + +// String implements fmt.Stringer. +func (p TransportProtocol) String() string { + if p == TransportProtocolUDP { + return "RTP/AVP" + } + return "RTP/AVP/TCP" +} + +// TransportDelivery is a delivery method. +type TransportDelivery int + +// transport delivery methods. +const ( + TransportDeliveryUnicast TransportDelivery = iota + TransportDeliveryMulticast +) + +// String implements fmt.Stringer. +func (d TransportDelivery) String() string { + if d == TransportDeliveryUnicast { + return "unicast" + } + return "multicast" +} + +// TransportMode is a transport mode. +type TransportMode int + +const ( + // TransportModePlay is the "play" transport mode + TransportModePlay TransportMode = iota + + // TransportModeRecord is the "record" transport mode + TransportModeRecord +) + +func (m *TransportMode) unmarshal(v string) error { + str := strings.ToLower(v) + + switch str { + case "play": + *m = TransportModePlay + return nil + + // receive is an old alias for record, used by ffmpeg with the + // -listen flag, and by Darwin Streaming Server + case "record", "receive": + *m = TransportModeRecord + return nil + + default: + return fmt.Errorf("invalid transport mode: '%s'", str) + } +} + +// String implements fmt.Stringer. +func (m TransportMode) String() string { + if m == TransportModePlay { + return "play" + } + return "record" +} + +// Transport is a Transport header. +type Transport struct { + // protocol of the stream + Protocol TransportProtocol + + // (optional) delivery method of the stream + Delivery *TransportDelivery + + // (optional) Source IP + Source *net.IP + + // (optional) destination IP + Destination *net.IP + + // (optional) interleaved frame ids + InterleavedIDs *[2]int + + // (optional) TTL + TTL *uint + + // (optional) ports + Ports *[2]int + + // (optional) client ports + ClientPorts *[2]int + + // (optional) server ports + ServerPorts *[2]int + + // (optional) SSRC of the packets of the stream + SSRC *uint32 + + // (optional) mode + Mode *TransportMode +} + +// Unmarshal decodes a Transport header. +func (h *Transport) Unmarshal(v base.HeaderValue) error { + if len(v) == 0 { + return fmt.Errorf("value not provided") + } + + if len(v) > 1 { + return fmt.Errorf("value provided multiple times (%v)", v) + } + + v0 := v[0] + + kvs, err := keyValParse(v0, ';') + if err != nil { + return err + } + + protocolFound := false + + for k, rv := range kvs { + v := rv + + switch k { + case "RTP/AVP", "RTP/AVP/UDP": + h.Protocol = TransportProtocolUDP + protocolFound = true + + case "RTP/AVP/TCP": + h.Protocol = TransportProtocolTCP + protocolFound = true + + case "unicast": + v := TransportDeliveryUnicast + h.Delivery = &v + + case "multicast": + v := TransportDeliveryMulticast + h.Delivery = &v + + case "source": + if v != "" { + ip := net.ParseIP(v) + if ip == nil { + addrs, err2 := net.LookupHost(v) + if err2 != nil { + return fmt.Errorf("invalid source (%v)", v) + } + ip = net.ParseIP(addrs[0]) + if ip == nil { + return fmt.Errorf("invalid source (%v)", v) + } + } + h.Source = &ip + } + + case "destination": + if v != "" { + ip := net.ParseIP(v) + if ip == nil { + return fmt.Errorf("invalid destination (%v)", v) + } + h.Destination = &ip + } + + case "interleaved": + ports, err2 := parsePorts(v) + if err2 != nil { + return err2 + } + h.InterleavedIDs = ports + + case "ttl": + tmp, err2 := strconv.ParseUint(v, 10, 32) + if err2 != nil { + return err2 + } + vu := uint(tmp) + h.TTL = &vu + + case "port": + ports, err2 := parsePorts(v) + if err2 != nil { + return err2 + } + h.Ports = ports + + case "client_port": + ports, err2 := parsePorts(v) + if err2 != nil { + return err2 + } + h.ClientPorts = ports + + case "server_port": + ports, err2 := parsePorts(v) + if err2 != nil { + return err2 + } + h.ServerPorts = ports + + case "ssrc": + v = strings.TrimLeft(v, " ") + + if (len(v) % 2) != 0 { + v = "0" + v + } + + if tmp, err2 := hex.DecodeString(v); err2 == nil && len(tmp) <= 4 { + var ssrc [4]byte + copy(ssrc[4-len(tmp):], tmp) + v := uint32(ssrc[0])<<24 | uint32(ssrc[1])<<16 | uint32(ssrc[2])<<8 | uint32(ssrc[3]) + h.SSRC = &v + } + + case "mode": + var m TransportMode + err = m.unmarshal(v) + if err != nil { + return err + } + h.Mode = &m + + default: + // ignore non-standard keys + } + } + + if !protocolFound { + return fmt.Errorf("protocol not found (%v)", v[0]) + } + + return nil +} + +// Marshal encodes a Transport header. +func (h Transport) Marshal() base.HeaderValue { + var rets []string + + rets = append(rets, h.Protocol.String()) + + if h.Delivery != nil { + rets = append(rets, h.Delivery.String()) + } + + if h.Source != nil { + rets = append(rets, "source="+h.Source.String()) + } + + if h.Destination != nil { + rets = append(rets, "destination="+h.Destination.String()) + } + + if h.InterleavedIDs != nil { + rets = append(rets, "interleaved="+strconv.FormatInt(int64(h.InterleavedIDs[0]), 10)+ + "-"+strconv.FormatInt(int64(h.InterleavedIDs[1]), 10)) + } + + if h.Ports != nil { + rets = append(rets, "port="+strconv.FormatInt(int64(h.Ports[0]), 10)+ + "-"+strconv.FormatInt(int64(h.Ports[1]), 10)) + } + + if h.TTL != nil { + rets = append(rets, "ttl="+strconv.FormatUint(uint64(*h.TTL), 10)) + } + + if h.ClientPorts != nil { + rets = append(rets, "client_port="+strconv.FormatInt(int64(h.ClientPorts[0]), 10)+ + "-"+strconv.FormatInt(int64(h.ClientPorts[1]), 10)) + } + + if h.ServerPorts != nil { + rets = append(rets, "server_port="+strconv.FormatInt(int64(h.ServerPorts[0]), 10)+ + "-"+strconv.FormatInt(int64(h.ServerPorts[1]), 10)) + } + + if h.SSRC != nil { + tmp := make([]byte, 4) + tmp[0] = byte(*h.SSRC >> 24) + tmp[1] = byte(*h.SSRC >> 16) + tmp[2] = byte(*h.SSRC >> 8) + tmp[3] = byte(*h.SSRC) + rets = append(rets, "ssrc="+strings.ToUpper(hex.EncodeToString(tmp))) + } + + if h.Mode != nil { + rets = append(rets, "mode="+h.Mode.String()) + } + + return base.HeaderValue{strings.Join(rets, ";")} +} + +// Transports is a Transport header with multiple transports. +type Transports []Transport + +// Unmarshal decodes a Transport header. +func (ts *Transports) Unmarshal(v base.HeaderValue) error { + if len(v) == 0 { + return fmt.Errorf("value not provided") + } + + if len(v) > 1 { + return fmt.Errorf("value provided multiple times (%v)", v) + } + + v0 := v[0] + transports := strings.Split(v0, ",") // , separated per RFC2326 section 12.39 + *ts = make([]Transport, len(transports)) + + for i, transport := range transports { + var tr Transport + err := tr.Unmarshal(base.HeaderValue{strings.TrimLeft(transport, " ")}) + if err != nil { + return err + } + (*ts)[i] = tr + } + + return nil +} + +// Marshal encodes a Transport header. +func (ts Transports) Marshal() base.HeaderValue { + vals := make([]string, len(ts)) + + for i, th := range ts { + vals[i] = th.Marshal()[0] + } + + return base.HeaderValue{strings.Join(vals, ",")} +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/liberrors/client.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/liberrors/client.go new file mode 100644 index 000000000..418057222 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/liberrors/client.go @@ -0,0 +1,343 @@ +package liberrors + +import ( + "fmt" + + "github.com/bluenviron/gortsplib/v4/pkg/base" +) + +// ErrClientTerminated is an error that can be returned by a client. +type ErrClientTerminated struct{} + +// Error implements the error interface. +func (e ErrClientTerminated) Error() string { + return "terminated" +} + +// ErrClientInvalidState is an error that can be returned by a client. +type ErrClientInvalidState struct { + AllowedList []fmt.Stringer + State fmt.Stringer +} + +// Error implements the error interface. +func (e ErrClientInvalidState) Error() string { + return fmt.Sprintf("must be in state %v, while is in state %v", + e.AllowedList, e.State) +} + +// ErrClientSessionHeaderInvalid is an error that can be returned by a client. +type ErrClientSessionHeaderInvalid struct { + Err error +} + +// Error implements the error interface. +func (e ErrClientSessionHeaderInvalid) Error() string { + return fmt.Sprintf("invalid session header: %v", e.Err) +} + +// ErrClientBadStatusCode is an error that can be returned by a client. +type ErrClientBadStatusCode struct { + Code base.StatusCode + Message string +} + +// Error implements the error interface. +func (e ErrClientBadStatusCode) Error() string { + return fmt.Sprintf("bad status code: %d (%s)", e.Code, e.Message) +} + +// ErrClientContentTypeMissing is an error that can be returned by a client. +type ErrClientContentTypeMissing struct{} + +// Error implements the error interface. +func (e ErrClientContentTypeMissing) Error() string { + return "Content-Type header is missing" +} + +// ErrClientContentTypeUnsupported is an error that can be returned by a client. +type ErrClientContentTypeUnsupported struct { + CT base.HeaderValue +} + +// Error implements the error interface. +func (e ErrClientContentTypeUnsupported) Error() string { + return fmt.Sprintf("unsupported Content-Type header '%v'", e.CT) +} + +// ErrClientCannotSetupMediasDifferentURLs is an error that can be returned by a client. +type ErrClientCannotSetupMediasDifferentURLs struct{} + +// Error implements the error interface. +func (e ErrClientCannotSetupMediasDifferentURLs) Error() string { + return "cannot setup medias with different base URLs" +} + +// ErrClientUDPPortsZero is an error that can be returned by a client. +type ErrClientUDPPortsZero struct{} + +// Error implements the error interface. +func (e ErrClientUDPPortsZero) Error() string { + return "rtpPort and rtcpPort must be both zero or non-zero" +} + +// ErrClientUDPPortsNotConsecutive is an error that can be returned by a client. +type ErrClientUDPPortsNotConsecutive struct{} + +// Error implements the error interface. +func (e ErrClientUDPPortsNotConsecutive) Error() string { + return "rtcpPort must be rtpPort + 1" +} + +// ErrClientServerPortsNotProvided is an error that can be returned by a client. +type ErrClientServerPortsNotProvided struct{} + +// Error implements the error interface. +func (e ErrClientServerPortsNotProvided) Error() string { + return "server ports have not been provided. Use AnyPortEnable to communicate with this server" +} + +// ErrClientTransportHeaderInvalid is an error that can be returned by a client. +type ErrClientTransportHeaderInvalid struct { + Err error +} + +// Error implements the error interface. +func (e ErrClientTransportHeaderInvalid) Error() string { + return fmt.Sprintf("invalid transport header: %v", e.Err) +} + +// ErrClientServerRequestedTCP is an error that can be returned by a client. +type ErrClientServerRequestedTCP struct{} + +// Error implements the error interface. +func (e ErrClientServerRequestedTCP) Error() string { + return "server wants to use the TCP transport protocol" +} + +// ErrClientServerRequestedUDP is an error that can be returned by a client. +type ErrClientServerRequestedUDP struct{} + +// Error implements the error interface. +func (e ErrClientServerRequestedUDP) Error() string { + return "server wants to use the UDP transport protocol" +} + +// ErrClientTransportHeaderInvalidDelivery is an error that can be returned by a client. +type ErrClientTransportHeaderInvalidDelivery struct{} + +// Error implements the error interface. +func (e ErrClientTransportHeaderInvalidDelivery) Error() string { + return "transport header contains an invalid delivery value" +} + +// ErrClientTransportHeaderNoPorts is an error that can be returned by a client. +type ErrClientTransportHeaderNoPorts struct{} + +// Error implements the error interface. +func (e ErrClientTransportHeaderNoPorts) Error() string { + return "transport header does not contain ports" +} + +// ErrClientTransportHeaderNoDestination is an error that can be returned by a client. +type ErrClientTransportHeaderNoDestination struct{} + +// Error implements the error interface. +func (e ErrClientTransportHeaderNoDestination) Error() string { + return "transport header does not contain a destination" +} + +// ErrClientTransportHeaderNoInterleavedIDs is an error that can be returned by a client. +type ErrClientTransportHeaderNoInterleavedIDs struct{} + +// Error implements the error interface. +func (e ErrClientTransportHeaderNoInterleavedIDs) Error() string { + return "transport header does not contain interleaved IDs" +} + +// ErrClientTransportHeaderInvalidInterleavedIDs is an error that can be returned by a client. +type ErrClientTransportHeaderInvalidInterleavedIDs struct{} + +// Error implements the error interface. +func (e ErrClientTransportHeaderInvalidInterleavedIDs) Error() string { + return "invalid interleaved IDs" +} + +// ErrClientTransportHeaderInterleavedIDsInUse is an error that can be returned by a client. +type ErrClientTransportHeaderInterleavedIDsInUse struct{} + +// Error implements the error interface. +func (e ErrClientTransportHeaderInterleavedIDsInUse) Error() string { + return "interleaved IDs are in use" +} + +// ErrClientUDPTimeout is an error that can be returned by a client. +type ErrClientUDPTimeout struct{} + +// Error implements the error interface. +func (e ErrClientUDPTimeout) Error() string { + return "UDP timeout" +} + +// ErrClientTCPTimeout is an error that can be returned by a client. +type ErrClientTCPTimeout struct{} + +// Error implements the error interface. +func (e ErrClientTCPTimeout) Error() string { + return "TCP timeout" +} + +// ErrClientRTPInfoInvalid is an error that can be returned by a client. +type ErrClientRTPInfoInvalid struct { + Err error +} + +// Error implements the error interface. +func (e ErrClientRTPInfoInvalid) Error() string { + return fmt.Sprintf("invalid RTP-Info: %v", e.Err) +} + +// ErrClientUnexpectedFrame is an error that can be returned by a client. +type ErrClientUnexpectedFrame struct{} + +// Error implements the error interface. +func (e ErrClientUnexpectedFrame) Error() string { + return "received unexpected interleaved frame" +} + +// ErrClientRequestTimedOut is an error that can be returned by a client. +type ErrClientRequestTimedOut struct{} + +// Error implements the error interface. +func (e ErrClientRequestTimedOut) Error() string { + return "request timed out" +} + +// ErrClientUnsupportedScheme is an error that can be returned by a client. +type ErrClientUnsupportedScheme struct { + Scheme string +} + +// Error implements the error interface. +func (e ErrClientUnsupportedScheme) Error() string { + return fmt.Sprintf("unsupported scheme: %v", e.Scheme) +} + +// ErrClientRTSPSTCP is an error that can be returned by a client. +type ErrClientRTSPSTCP struct{} + +// Error implements the error interface. +func (e ErrClientRTSPSTCP) Error() string { + return "RTSPS can be used only with TCP" +} + +// ErrClientUnhandledMethod is an error that can be returned by a client. +type ErrClientUnhandledMethod struct { + Method base.Method +} + +// Error implements the error interface. +func (e ErrClientUnhandledMethod) Error() string { + return fmt.Sprintf("unhandled method: %v", e.Method) +} + +// ErrClientWriteQueueFull is an error that can be returned by a client. +type ErrClientWriteQueueFull struct{} + +// Error implements the error interface. +func (e ErrClientWriteQueueFull) Error() string { + return "write queue is full" +} + +// ErrClientRTPPacketsLost is an error that can be returned by a client. +// +// Deprecated: will be removed in next version. +type ErrClientRTPPacketsLost struct { + Lost uint +} + +// Error implements the error interface. +func (e ErrClientRTPPacketsLost) Error() string { + return fmt.Sprintf("%d RTP %s lost", + e.Lost, + func() string { + if e.Lost == 1 { + return "packet" + } + return "packets" + }()) +} + +// ErrClientRTPPacketUnknownPayloadType is an error that can be returned by a client. +type ErrClientRTPPacketUnknownPayloadType struct { + PayloadType uint8 +} + +// Error implements the error interface. +func (e ErrClientRTPPacketUnknownPayloadType) Error() string { + return fmt.Sprintf("received RTP packet with unknown payload type: %d", e.PayloadType) +} + +// ErrClientRTCPPacketTooBig is an error that can be returned by a client. +type ErrClientRTCPPacketTooBig struct { + L int + Max int +} + +// Error implements the error interface. +func (e ErrClientRTCPPacketTooBig) Error() string { + return fmt.Sprintf("RTCP packet size (%d) is greater than maximum allowed (%d)", + e.L, e.Max) +} + +// ErrClientRTPPacketTooBigUDP is an error that can be returned by a client. +type ErrClientRTPPacketTooBigUDP struct{} + +// Error implements the error interface. +func (e ErrClientRTPPacketTooBigUDP) Error() string { + return "RTP packet is too big to be read with UDP" +} + +// ErrClientRTCPPacketTooBigUDP is an error that can be returned by a client. +type ErrClientRTCPPacketTooBigUDP struct{} + +// Error implements the error interface. +func (e ErrClientRTCPPacketTooBigUDP) Error() string { + return "RTCP packet is too big to be read with UDP" +} + +// ErrClientSwitchToTCP is an error that can be returned by a client. +type ErrClientSwitchToTCP struct{} + +// Error implements the error interface. +func (e ErrClientSwitchToTCP) Error() string { + return "no UDP packets received, switching to TCP" +} + +// ErrClientSwitchToTCP2 is an error that can be returned by a client. +type ErrClientSwitchToTCP2 struct{} + +// Error implements the error interface. +func (e ErrClientSwitchToTCP2) Error() string { + return "switching to TCP because server requested it" +} + +// ErrClientAuthSetup is an error that can be returned by a client. +type ErrClientAuthSetup struct { + Err error +} + +// Error implements the error interface. +func (e ErrClientAuthSetup) Error() string { + return fmt.Sprintf("unable to setup authentication: %s", e.Err) +} + +// ErrClientSDPInvalid is an error that can be returned by a client. +type ErrClientSDPInvalid struct { + Err error +} + +// Error implements the error interface. +func (e ErrClientSDPInvalid) Error() string { + return fmt.Sprintf("invalid SDP: %v", e.Err) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/liberrors/liberrors.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/liberrors/liberrors.go new file mode 100644 index 000000000..60cc731f2 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/liberrors/liberrors.go @@ -0,0 +1,2 @@ +// Package liberrors contains errors returned by the library. +package liberrors diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/liberrors/server.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/liberrors/server.go new file mode 100644 index 000000000..932f58d35 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/liberrors/server.go @@ -0,0 +1,284 @@ +package liberrors + +import ( + "fmt" + "net" + + "github.com/bluenviron/gortsplib/v4/pkg/headers" +) + +// ErrServerTerminated is an error that can be returned by a server. +type ErrServerTerminated = ErrClientTerminated + +// ErrServerSessionNotFound is an error that can be returned by a server. +type ErrServerSessionNotFound struct{} + +// Error implements the error interface. +func (e ErrServerSessionNotFound) Error() string { + return "session not found" +} + +// ErrServerSessionTimedOut is an error that can be returned by a server. +type ErrServerSessionTimedOut struct{} + +// Error implements the error interface. +func (e ErrServerSessionTimedOut) Error() string { + return "session timed out" +} + +// ErrServerCSeqMissing is an error that can be returned by a server. +type ErrServerCSeqMissing struct{} + +// Error implements the error interface. +func (e ErrServerCSeqMissing) Error() string { + return "CSeq is missing" +} + +// ErrServerInvalidState is an error that can be returned by a server. +type ErrServerInvalidState struct { + AllowedList []fmt.Stringer + State fmt.Stringer +} + +// Error implements the error interface. +func (e ErrServerInvalidState) Error() string { + return fmt.Sprintf("must be in state %v, while is in state %v", + e.AllowedList, e.State) +} + +// ErrServerInvalidPath is an error that can be returned by a server. +type ErrServerInvalidPath struct{} + +// Error implements the error interface. +func (e ErrServerInvalidPath) Error() string { + return "invalid path" +} + +// ErrServerContentTypeMissing is an error that can be returned by a server. +type ErrServerContentTypeMissing = ErrClientContentTypeMissing + +// ErrServerContentTypeUnsupported is an error that can be returned by a server. +type ErrServerContentTypeUnsupported = ErrClientContentTypeUnsupported + +// ErrServerSDPInvalid is an error that can be returned by a server. +type ErrServerSDPInvalid = ErrClientSDPInvalid + +// ErrServerTransportHeaderInvalid is an error that can be returned by a server. +type ErrServerTransportHeaderInvalid = ErrClientTransportHeaderInvalid + +// ErrServerMediaAlreadySetup is an error that can be returned by a server. +type ErrServerMediaAlreadySetup struct{} + +// Error implements the error interface. +func (e ErrServerMediaAlreadySetup) Error() string { + return "media has already been setup" +} + +// ErrServerMediaNotFound is an error that can be returned by a server. +type ErrServerMediaNotFound struct{} + +// Error implements the error interface. +func (e ErrServerMediaNotFound) Error() string { + return "media not found" +} + +// ErrServerTransportHeaderInvalidMode is an error that can be returned by a server. +type ErrServerTransportHeaderInvalidMode struct { + Mode *headers.TransportMode +} + +// Error implements the error interface. +func (e ErrServerTransportHeaderInvalidMode) Error() string { + m := "null" + if e.Mode != nil { + m = e.Mode.String() + } + return fmt.Sprintf("transport header contains a invalid mode (%v)", m) +} + +// ErrServerTransportHeaderNoClientPorts is an error that can be returned by a server. +type ErrServerTransportHeaderNoClientPorts struct{} + +// Error implements the error interface. +func (e ErrServerTransportHeaderNoClientPorts) Error() string { + return "transport header does not contain client ports" +} + +// ErrServerTransportHeaderInvalidInterleavedIDs is an error that can be returned by a server. +type ErrServerTransportHeaderInvalidInterleavedIDs struct{} + +// Error implements the error interface. +func (e ErrServerTransportHeaderInvalidInterleavedIDs) Error() string { + return "invalid interleaved IDs" +} + +// ErrServerTransportHeaderInterleavedIDsInUse is an error that can be returned by a server. +type ErrServerTransportHeaderInterleavedIDsInUse struct{} + +// Error implements the error interface. +func (e ErrServerTransportHeaderInterleavedIDsInUse) Error() string { + return "interleaved IDs are in use" +} + +// ErrServerMediasDifferentPaths is an error that can be returned by a server. +type ErrServerMediasDifferentPaths struct{} + +// Error implements the error interface. +func (e ErrServerMediasDifferentPaths) Error() string { + return "can't setup medias with different paths" +} + +// ErrServerMediasDifferentProtocols is an error that can be returned by a server. +type ErrServerMediasDifferentProtocols struct{} + +// Error implements the error interface. +func (e ErrServerMediasDifferentProtocols) Error() string { + return "can't setup medias with different protocols" +} + +// ErrServerNoMediasSetup is an error that can be returned by a server. +type ErrServerNoMediasSetup struct{} + +// Error implements the error interface. +func (e ErrServerNoMediasSetup) Error() string { + return "no medias have been setup" +} + +// ErrServerNotAllAnnouncedMediasSetup is an error that can be returned by a server. +type ErrServerNotAllAnnouncedMediasSetup struct{} + +// Error implements the error interface. +func (e ErrServerNotAllAnnouncedMediasSetup) Error() string { + return "not all announced medias have been setup" +} + +// ErrServerLinkedToOtherSession is an error that can be returned by a server. +type ErrServerLinkedToOtherSession struct{} + +// Error implements the error interface. +func (e ErrServerLinkedToOtherSession) Error() string { + return "connection is linked to another session" +} + +// ErrServerSessionTornDown is an error that can be returned by a server. +type ErrServerSessionTornDown struct { + Author net.Addr +} + +// Error implements the error interface. +func (e ErrServerSessionTornDown) Error() string { + return fmt.Sprintf("torn down by %v", e.Author) +} + +// ErrServerSessionLinkedToOtherConn is an error that can be returned by a server. +type ErrServerSessionLinkedToOtherConn struct{} + +// Error implements the error interface. +func (e ErrServerSessionLinkedToOtherConn) Error() string { + return "session is linked to another connection" +} + +// ErrServerInvalidSession is an error that can be returned by a server. +type ErrServerInvalidSession struct{} + +// Error implements the error interface. +func (e ErrServerInvalidSession) Error() string { + return "invalid session" +} + +// ErrServerPathHasChanged is an error that can be returned by a server. +type ErrServerPathHasChanged struct { + Prev string + Cur string +} + +// Error implements the error interface. +func (e ErrServerPathHasChanged) Error() string { + return fmt.Sprintf("path has changed, was '%s', now is '%s'", e.Prev, e.Cur) +} + +// ErrServerCannotUseSessionCreatedByOtherIP is an error that can be returned by a server. +type ErrServerCannotUseSessionCreatedByOtherIP struct{} + +// Error implements the error interface. +func (e ErrServerCannotUseSessionCreatedByOtherIP) Error() string { + return "cannot use a session created with a different IP" +} + +// ErrServerUDPPortsAlreadyInUse is an error that can be returned by a server. +type ErrServerUDPPortsAlreadyInUse struct { + Port int +} + +// Error implements the error interface. +func (e ErrServerUDPPortsAlreadyInUse) Error() string { + return fmt.Sprintf("UDP ports %d and %d are already assigned to another reader with the same IP", + e.Port, e.Port+1) +} + +// ErrServerSessionNotInUse is an error that can be returned by a server. +type ErrServerSessionNotInUse struct{} + +// Error implements the error interface. +func (e ErrServerSessionNotInUse) Error() string { + return "not in use" +} + +// ErrServerUnexpectedFrame is an error that can be returned by a server. +type ErrServerUnexpectedFrame = ErrClientUnexpectedFrame + +// ErrServerUnexpectedResponse is an error that can be returned by a server. +type ErrServerUnexpectedResponse struct{} + +// Error implements the error interface. +func (e ErrServerUnexpectedResponse) Error() string { + return "received unexpected response" +} + +// ErrServerWriteQueueFull is an error that can be returned by a server. +type ErrServerWriteQueueFull = ErrClientWriteQueueFull + +// ErrServerRTPPacketsLost is an error that can be returned by a server. +// +// Deprecated: will be removed in next version. +type ErrServerRTPPacketsLost = ErrClientRTPPacketsLost + +// ErrServerRTPPacketUnknownPayloadType is an error that can be returned by a server. +type ErrServerRTPPacketUnknownPayloadType = ErrClientRTPPacketUnknownPayloadType + +// ErrServerRTCPPacketTooBig is an error that can be returned by a server. +type ErrServerRTCPPacketTooBig = ErrClientRTCPPacketTooBig + +// ErrServerRTPPacketTooBigUDP is an error that can be returned by a server. +type ErrServerRTPPacketTooBigUDP = ErrClientRTPPacketTooBigUDP + +// ErrServerRTCPPacketTooBigUDP is an error that can be returned by a server. +type ErrServerRTCPPacketTooBigUDP = ErrClientRTCPPacketTooBigUDP + +// ErrServerStreamClosed is an error that can be returned by a server. +type ErrServerStreamClosed struct{} + +// Error implements the error interface. +func (e ErrServerStreamClosed) Error() string { + return "stream is closed" +} + +// ErrServerInvalidSetupPath is an error that can be returned by a server. +type ErrServerInvalidSetupPath struct{} + +// Error implements the error interface. +func (ErrServerInvalidSetupPath) Error() string { + return "invalid SETUP path. " + + "This typically happens when VLC fails a request, and then switches to an " + + "unsupported RTSP dialect" +} + +// ErrServerAuth is an error that can be returned by a server. +// If a client did not provide credentials, it will be asked for +// credentials instead of being kicked out. +type ErrServerAuth struct{} + +// Error implements the error interface. +func (e ErrServerAuth) Error() string { + return "authentication error" +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/multicast/multi_conn.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/multicast/multi_conn.go new file mode 100644 index 000000000..c7ad89e03 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/multicast/multi_conn.go @@ -0,0 +1,180 @@ +//go:build !linux + +package multicast + +import ( + "fmt" + "net" + "strconv" + "time" + + "golang.org/x/net/ipv4" +) + +// MultiConn is a multicast connection +// that works in parallel on all interfaces. +type MultiConn struct { + addr *net.UDPAddr + readConn *net.UDPConn + readConnIP *ipv4.PacketConn + writeConns []*net.UDPConn + writeConnIPs []*ipv4.PacketConn +} + +// NewMultiConn allocates a MultiConn. +func NewMultiConn( + address string, + readOnly bool, + listenPacket func(network, address string) (net.PacketConn, error), +) (Conn, error) { + addr, err := net.ResolveUDPAddr("udp4", address) + if err != nil { + return nil, err + } + + tmp, err := listenPacket("udp4", "224.0.0.0:"+strconv.FormatInt(int64(addr.Port), 10)) + if err != nil { + return nil, err + } + readConn := tmp.(*net.UDPConn) + + intfs, err := net.Interfaces() + if err != nil { + readConn.Close() //nolint:errcheck + return nil, err + } + + readConnIP := ipv4.NewPacketConn(readConn) + + var enabledInterfaces []*net.Interface //nolint:prealloc + + for _, intf := range intfs { + if (intf.Flags & net.FlagMulticast) == 0 { + continue + } + cintf := intf + + err = readConnIP.JoinGroup(&cintf, &net.UDPAddr{IP: addr.IP}) + if err != nil { + continue + } + + enabledInterfaces = append(enabledInterfaces, &cintf) + } + + if enabledInterfaces == nil { + readConn.Close() //nolint:errcheck + return nil, fmt.Errorf("no multicast-capable interfaces found") + } + + var writeConns []*net.UDPConn + var writeConnIPs []*ipv4.PacketConn + + if !readOnly { + writeConns = make([]*net.UDPConn, len(enabledInterfaces)) + writeConnIPs = make([]*ipv4.PacketConn, len(enabledInterfaces)) + + for i, intf := range enabledInterfaces { + tmp, err := listenPacket("udp4", "224.0.0.0:"+strconv.FormatInt(int64(addr.Port), 10)) + if err != nil { + for j := 0; j < i; j++ { + writeConns[j].Close() //nolint:errcheck + } + readConn.Close() //nolint:errcheck + return nil, err + } + writeConn := tmp.(*net.UDPConn) + + writeConnIP := ipv4.NewPacketConn(writeConn) + + err = writeConnIP.SetMulticastInterface(intf) + if err != nil { + for j := 0; j < i; j++ { + writeConns[j].Close() //nolint:errcheck + } + readConn.Close() //nolint:errcheck + return nil, err + } + + err = writeConnIP.SetMulticastTTL(multicastTTL) + if err != nil { + for j := 0; j < i; j++ { + writeConns[j].Close() //nolint:errcheck + } + readConn.Close() //nolint:errcheck + return nil, err + } + + writeConns[i] = writeConn + writeConnIPs[i] = writeConnIP + } + } + + return &MultiConn{ + addr: addr, + readConn: readConn, + readConnIP: readConnIP, + writeConns: writeConns, + writeConnIPs: writeConnIPs, + }, nil +} + +// Close implements Conn. +func (c *MultiConn) Close() error { + for _, c := range c.writeConns { + c.Close() //nolint:errcheck + } + c.readConn.Close() //nolint:errcheck + return nil +} + +// SetReadBuffer implements Conn. +func (c *MultiConn) SetReadBuffer(bytes int) error { + return c.readConn.SetReadBuffer(bytes) +} + +// LocalAddr implements Conn. +func (c *MultiConn) LocalAddr() net.Addr { + return c.readConn.LocalAddr() +} + +// SetDeadline implements Conn. +func (c *MultiConn) SetDeadline(_ time.Time) error { + panic("unimplemented") +} + +// SetReadDeadline implements Conn. +func (c *MultiConn) SetReadDeadline(t time.Time) error { + return c.readConn.SetReadDeadline(t) +} + +// SetWriteDeadline implements Conn. +func (c *MultiConn) SetWriteDeadline(t time.Time) error { + var err error + for _, c := range c.writeConns { + err2 := c.SetWriteDeadline(t) + if err == nil { + err = err2 + } + } + return err +} + +// WriteTo implements Conn. +func (c *MultiConn) WriteTo(b []byte, addr net.Addr) (int, error) { + var n int + var err error + for _, c := range c.writeConns { + var err2 error + n, err2 = c.WriteTo(b, addr) + if err == nil { + err = err2 + } + } + return n, err +} + +// ReadFrom implements Conn. +func (c *MultiConn) ReadFrom(b []byte) (int, net.Addr, error) { + return c.readConn.ReadFrom(b) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/multicast/multi_conn_lin.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/multicast/multi_conn_lin.go new file mode 100644 index 000000000..ac638bbcb --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/multicast/multi_conn_lin.go @@ -0,0 +1,233 @@ +//go:build linux + +package multicast + +import ( + "fmt" + "net" + "os" + "syscall" + "time" +) + +// MultiConn is a multicast connection +// that works in parallel on all interfaces. +type MultiConn struct { + addr *net.UDPAddr + readFile *os.File + readConn net.PacketConn + writeFiles []*os.File + writeConns []net.PacketConn +} + +// NewMultiConn allocates a MultiConn. +func NewMultiConn( + address string, + readOnly bool, + _ func(network, address string) (net.PacketConn, error), +) (Conn, error) { + addr, err := net.ResolveUDPAddr("udp4", address) + if err != nil { + return nil, err + } + + readSock, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_UDP) + if err != nil { + return nil, err + } + + err = syscall.SetsockoptInt(readSock, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) + if err != nil { + syscall.Close(readSock) //nolint:errcheck + return nil, err + } + + var lsa syscall.SockaddrInet4 + lsa.Port = addr.Port + copy(lsa.Addr[:], addr.IP.To4()) + err = syscall.Bind(readSock, &lsa) + if err != nil { + syscall.Close(readSock) //nolint:errcheck + return nil, err + } + + intfs, err := net.Interfaces() + if err != nil { + syscall.Close(readSock) //nolint:errcheck + return nil, err + } + + var enabledInterfaces []*net.Interface //nolint:prealloc + for _, intf := range intfs { + if (intf.Flags & net.FlagMulticast) == 0 { + continue + } + cintf := intf + + var mreq syscall.IPMreq + copy(mreq.Multiaddr[:], addr.IP.To4()) + err = setIPMreqInterface(&mreq, &cintf) + if err != nil { + continue + } + + err = syscall.SetsockoptIPMreq(readSock, syscall.IPPROTO_IP, syscall.IP_ADD_MEMBERSHIP, &mreq) + if err != nil { + continue + } + + enabledInterfaces = append(enabledInterfaces, &cintf) + } + + if enabledInterfaces == nil { + syscall.Close(readSock) //nolint:errcheck + return nil, fmt.Errorf("no multicast-capable interfaces found") + } + + var writeFiles []*os.File + var writeConns []net.PacketConn + + if !readOnly { + writeSocks := make([]int, len(enabledInterfaces)) + + for i, intf := range enabledInterfaces { + writeSock, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_UDP) + if err != nil { + for j := 0; j < i; j++ { + syscall.Close(writeSocks[j]) //nolint:errcheck + } + syscall.Close(readSock) //nolint:errcheck + return nil, err + } + + err = syscall.SetsockoptInt(writeSock, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) + if err != nil { + syscall.Close(writeSock) //nolint:errcheck + for j := 0; j < i; j++ { + syscall.Close(writeSocks[j]) //nolint:errcheck + } + syscall.Close(readSock) //nolint:errcheck + return nil, err + } + + var lsa syscall.SockaddrInet4 + lsa.Port = addr.Port + copy(lsa.Addr[:], addr.IP.To4()) + err = syscall.Bind(writeSock, &lsa) + if err != nil { + syscall.Close(writeSock) //nolint:errcheck + for j := 0; j < i; j++ { + syscall.Close(writeSocks[j]) //nolint:errcheck + } + syscall.Close(readSock) //nolint:errcheck + return nil, err + } + + var mreqn syscall.IPMreqn + mreqn.Ifindex = int32(intf.Index) + + err = syscall.SetsockoptIPMreqn(writeSock, syscall.IPPROTO_IP, syscall.IP_MULTICAST_IF, &mreqn) + if err != nil { + syscall.Close(writeSock) //nolint:errcheck + for j := 0; j < i; j++ { + syscall.Close(writeSocks[j]) //nolint:errcheck + } + syscall.Close(readSock) //nolint:errcheck + return nil, err + } + + err = syscall.SetsockoptInt(writeSock, syscall.IPPROTO_IP, syscall.IP_MULTICAST_TTL, multicastTTL) + if err != nil { + syscall.Close(writeSock) //nolint:errcheck + for j := 0; j < i; j++ { + syscall.Close(writeSocks[j]) //nolint:errcheck + } + syscall.Close(readSock) //nolint:errcheck + return nil, err + } + + writeSocks[i] = writeSock + } + + writeFiles = make([]*os.File, len(writeSocks)) + writeConns = make([]net.PacketConn, len(writeSocks)) + + for i, writeSock := range writeSocks { + writeFiles[i] = os.NewFile(uintptr(writeSock), "") + writeConns[i], _ = net.FilePacketConn(writeFiles[i]) + } + } + + readFile := os.NewFile(uintptr(readSock), "") + readConn, _ := net.FilePacketConn(readFile) + + return &MultiConn{ + addr: addr, + readFile: readFile, + readConn: readConn, + writeFiles: writeFiles, + writeConns: writeConns, + }, nil +} + +// Close implements Conn. +func (c *MultiConn) Close() error { + for i, writeConn := range c.writeConns { + writeConn.Close() + c.writeFiles[i].Close() + } + c.readConn.Close() + c.readFile.Close() + return nil +} + +// SetReadBuffer implements Conn. +func (c *MultiConn) SetReadBuffer(bytes int) error { + return syscall.SetsockoptInt(int(c.readFile.Fd()), syscall.SOL_SOCKET, syscall.SO_RCVBUF, bytes) +} + +// LocalAddr implements Conn. +func (c *MultiConn) LocalAddr() net.Addr { + return c.readConn.LocalAddr() +} + +// SetDeadline implements Conn. +func (c *MultiConn) SetDeadline(_ time.Time) error { + panic("unimplemented") +} + +// SetReadDeadline implements Conn. +func (c *MultiConn) SetReadDeadline(t time.Time) error { + return c.readConn.SetReadDeadline(t) +} + +// SetWriteDeadline implements Conn. +func (c *MultiConn) SetWriteDeadline(t time.Time) error { + var err error + for _, c := range c.writeConns { + err2 := c.SetWriteDeadline(t) + if err == nil { + err = err2 + } + } + return err +} + +// WriteTo implements Conn. +func (c *MultiConn) WriteTo(b []byte, addr net.Addr) (int, error) { + var n int + var err error + for _, c := range c.writeConns { + var err2 error + n, err2 = c.WriteTo(b, addr) + if err == nil { + err = err2 + } + } + return n, err +} + +// ReadFrom implements Conn. +func (c *MultiConn) ReadFrom(b []byte) (int, net.Addr, error) { + return c.readConn.ReadFrom(b) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/multicast/multicast.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/multicast/multicast.go new file mode 100644 index 000000000..ae8221efe --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/multicast/multicast.go @@ -0,0 +1,43 @@ +// Package multicast contains multicast connections. +package multicast + +import ( + "fmt" + "net" +) + +// Conn is a Multicast connection. +type Conn interface { + net.PacketConn + SetReadBuffer(int) error +} + +// InterfaceForSource returns a multicast-capable interface that can communicate with given IP. +func InterfaceForSource(ip net.IP) (*net.Interface, error) { + if ip.Equal(net.ParseIP("127.0.0.1")) { + return nil, fmt.Errorf("IP 127.0.0.1 can't be used as source of a multicast stream. Use the LAN IP of your PC") + } + + intfs, err := net.Interfaces() + if err != nil { + return nil, err + } + + for _, intf := range intfs { + if (intf.Flags & net.FlagMulticast) == 0 { + continue + } + + addrs, err := intf.Addrs() + if err == nil { + for _, addr := range addrs { + _, ipnet, err := net.ParseCIDR(addr.String()) + if err == nil && ipnet.Contains(ip) { + return &intf, nil + } + } + } + } + + return nil, fmt.Errorf("found no interface that is multicast-capable and can communicate with IP %v", ip) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/multicast/single_conn.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/multicast/single_conn.go new file mode 100644 index 000000000..fd99e8acd --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/multicast/single_conn.go @@ -0,0 +1,108 @@ +//go:build !linux + +package multicast + +import ( + "net" + "strconv" + "time" + + "golang.org/x/net/ipv4" +) + +const ( + // same size as GStreamer's rtspsrc + multicastTTL = 16 +) + +// SingleConn is a multicast connection +// that works on a single interface. +type SingleConn struct { + addr *net.UDPAddr + conn *net.UDPConn + connIP *ipv4.PacketConn +} + +// NewSingleConn allocates a SingleConn. +func NewSingleConn( + intf *net.Interface, + address string, + listenPacket func(network, address string) (net.PacketConn, error), +) (Conn, error) { + addr, err := net.ResolveUDPAddr("udp4", address) + if err != nil { + return nil, err + } + + tmp, err := listenPacket("udp4", "224.0.0.0:"+strconv.FormatInt(int64(addr.Port), 10)) + if err != nil { + return nil, err + } + conn := tmp.(*net.UDPConn) + + connIP := ipv4.NewPacketConn(conn) + + err = connIP.JoinGroup(intf, &net.UDPAddr{IP: addr.IP}) + if err != nil { + conn.Close() //nolint:errcheck + return nil, err + } + + err = connIP.SetMulticastInterface(intf) + if err != nil { + conn.Close() //nolint:errcheck + return nil, err + } + + err = connIP.SetMulticastTTL(multicastTTL) + if err != nil { + conn.Close() //nolint:errcheck + return nil, err + } + + return &SingleConn{ + addr: addr, + conn: conn, + connIP: connIP, + }, nil +} + +// Close implements Conn. +func (c *SingleConn) Close() error { + return c.conn.Close() +} + +// SetReadBuffer implements Conn. +func (c *SingleConn) SetReadBuffer(bytes int) error { + return c.conn.SetReadBuffer(bytes) +} + +// LocalAddr implements Conn. +func (c *SingleConn) LocalAddr() net.Addr { + return c.conn.LocalAddr() +} + +// SetDeadline implements Conn. +func (c *SingleConn) SetDeadline(_ time.Time) error { + panic("unimplemented") +} + +// SetReadDeadline implements Conn. +func (c *SingleConn) SetReadDeadline(t time.Time) error { + return c.conn.SetReadDeadline(t) +} + +// SetWriteDeadline implements Conn. +func (c *SingleConn) SetWriteDeadline(t time.Time) error { + return c.conn.SetWriteDeadline(t) +} + +// WriteTo implements Conn. +func (c *SingleConn) WriteTo(b []byte, addr net.Addr) (int, error) { + return c.conn.WriteTo(b, addr) +} + +// ReadFrom implements Conn. +func (c *SingleConn) ReadFrom(b []byte) (int, net.Addr, error) { + return c.conn.ReadFrom(b) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/multicast/single_conn_lin.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/multicast/single_conn_lin.go new file mode 100644 index 000000000..ab5cb1640 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/multicast/single_conn_lin.go @@ -0,0 +1,172 @@ +//go:build linux + +package multicast + +import ( + "fmt" + "net" + "os" + "syscall" + "time" +) + +const ( + // same size as GStreamer's rtspsrc + multicastTTL = 16 +) + +// https://cs.opensource.google/go/x/net/+/refs/tags/v0.15.0:ipv4/sys_asmreq.go;l=51 +func setIPMreqInterface(mreq *syscall.IPMreq, ifi *net.Interface) error { + if ifi == nil { + return nil + } + ifat, err := ifi.Addrs() + if err != nil { + return err + } + for _, ifa := range ifat { + switch ifa := ifa.(type) { + case *net.IPAddr: + if ip := ifa.IP.To4(); ip != nil { + copy(mreq.Interface[:], ip) + return nil + } + case *net.IPNet: + if ip := ifa.IP.To4(); ip != nil { + copy(mreq.Interface[:], ip) + return nil + } + } + } + return fmt.Errorf("no such interface") +} + +// SingleConn is a multicast connection +// that works on a single interface. +type SingleConn struct { + addr *net.UDPAddr + file *os.File + conn net.PacketConn +} + +// NewSingleConn allocates a SingleConn. +func NewSingleConn( + intf *net.Interface, + address string, + _ func(network, address string) (net.PacketConn, error), +) (Conn, error) { + addr, err := net.ResolveUDPAddr("udp4", address) + if err != nil { + return nil, err + } + + sock, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_UDP) + if err != nil { + return nil, err + } + + err = syscall.SetsockoptInt(sock, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) + if err != nil { + syscall.Close(sock) //nolint:errcheck + return nil, err + } + + err = syscall.SetsockoptString(sock, syscall.SOL_SOCKET, syscall.SO_BINDTODEVICE, intf.Name) + if err != nil { + syscall.Close(sock) //nolint:errcheck + return nil, err + } + + var lsa syscall.SockaddrInet4 + lsa.Port = addr.Port + copy(lsa.Addr[:], addr.IP.To4()) + err = syscall.Bind(sock, &lsa) + if err != nil { + syscall.Close(sock) //nolint:errcheck + return nil, err + } + + var mreq syscall.IPMreq + copy(mreq.Multiaddr[:], addr.IP.To4()) + err = setIPMreqInterface(&mreq, intf) + if err != nil { + syscall.Close(sock) //nolint:errcheck + return nil, err + } + + err = syscall.SetsockoptIPMreq(sock, syscall.IPPROTO_IP, syscall.IP_ADD_MEMBERSHIP, &mreq) + if err != nil { + syscall.Close(sock) //nolint:errcheck + return nil, err + } + + var mreqn syscall.IPMreqn + mreqn.Ifindex = int32(intf.Index) + + err = syscall.SetsockoptIPMreqn(sock, syscall.IPPROTO_IP, syscall.IP_MULTICAST_IF, &mreqn) + if err != nil { + syscall.Close(sock) //nolint:errcheck + return nil, err + } + + err = syscall.SetsockoptInt(sock, syscall.IPPROTO_IP, syscall.IP_MULTICAST_TTL, multicastTTL) + if err != nil { + syscall.Close(sock) //nolint:errcheck + return nil, err + } + + file := os.NewFile(uintptr(sock), "") + conn, err := net.FilePacketConn(file) + if err != nil { + file.Close() + return nil, err + } + + return &SingleConn{ + addr: addr, + file: file, + conn: conn, + }, nil +} + +// Close implements Conn. +func (c *SingleConn) Close() error { + c.conn.Close() + c.file.Close() + return nil +} + +// SetReadBuffer implements Conn. +func (c *SingleConn) SetReadBuffer(bytes int) error { + return syscall.SetsockoptInt(int(c.file.Fd()), syscall.SOL_SOCKET, syscall.SO_RCVBUF, bytes) +} + +// LocalAddr implements Conn. +func (c *SingleConn) LocalAddr() net.Addr { + return c.conn.LocalAddr() +} + +// SetDeadline implements Conn. +func (c *SingleConn) SetDeadline(_ time.Time) error { + panic("unimplemented") +} + +// SetReadDeadline implements Conn. +func (c *SingleConn) SetReadDeadline(t time.Time) error { + return c.conn.SetReadDeadline(t) +} + +// SetWriteDeadline implements Conn. +func (c *SingleConn) SetWriteDeadline(t time.Time) error { + return c.conn.SetWriteDeadline(t) +} + +// WriteTo implements Conn. +func (c *SingleConn) WriteTo(b []byte, addr net.Addr) (int, error) { + return c.conn.WriteTo(b, addr) +} + +// ReadFrom implements Conn. +func (c *SingleConn) ReadFrom(b []byte) (int, net.Addr, error) { + return c.conn.ReadFrom(b) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/ringbuffer/ringbuffer.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/ringbuffer/ringbuffer.go new file mode 100644 index 000000000..364aea319 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/ringbuffer/ringbuffer.go @@ -0,0 +1,106 @@ +// Package ringbuffer contains a ring buffer. +package ringbuffer + +import ( + "fmt" + "sync" +) + +// RingBuffer is a ring buffer. +type RingBuffer struct { + size uint64 + mutex sync.Mutex + cond *sync.Cond + buffer []interface{} + readIndex uint64 + writeIndex uint64 + closed bool +} + +// New allocates a RingBuffer. +func New(size uint64) (*RingBuffer, error) { + // when writeIndex overflows, if size is not a power of + // two, only a portion of the buffer is used. + if (size & (size - 1)) != 0 { + return nil, fmt.Errorf("size must be a power of two") + } + + r := &RingBuffer{ + size: size, + buffer: make([]interface{}, size), + } + + r.cond = sync.NewCond(&r.mutex) + + return r, nil +} + +// Close makes Pull() return false. +func (r *RingBuffer) Close() { + r.mutex.Lock() + + r.closed = true + + // discard pending data to make Pull() exit immediately + for i := uint64(0); i < r.size; i++ { + r.buffer[i] = nil + } + + r.mutex.Unlock() + r.cond.Broadcast() +} + +// Reset restores Pull() behavior after a Close(). +func (r *RingBuffer) Reset() { + for i := uint64(0); i < r.size; i++ { + r.buffer[i] = nil + } + + r.writeIndex = 0 + r.readIndex = 0 + r.closed = false +} + +// Push pushes data at the end of the buffer. +func (r *RingBuffer) Push(data interface{}) bool { + r.mutex.Lock() + + if r.buffer[r.writeIndex] != nil { + r.mutex.Unlock() + return false + } + + r.buffer[r.writeIndex] = data + r.writeIndex = (r.writeIndex + 1) % r.size + + r.mutex.Unlock() + + r.cond.Broadcast() + + return true +} + +// Pull pulls data from the beginning of the buffer. +func (r *RingBuffer) Pull() (interface{}, bool) { + for { + r.mutex.Lock() + + data := r.buffer[r.readIndex] + + if data != nil { + r.buffer[r.readIndex] = nil + r.readIndex = (r.readIndex + 1) % r.size + r.mutex.Unlock() + return data, true + } + + if r.closed { + r.mutex.Unlock() + return nil, false + } + + r.cond.Wait() + + r.mutex.Unlock() + } +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtcpreceiver/rtcpreceiver.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtcpreceiver/rtcpreceiver.go new file mode 100644 index 000000000..931bc671b --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtcpreceiver/rtcpreceiver.go @@ -0,0 +1,314 @@ +// Package rtcpreceiver contains a utility to generate RTCP receiver reports. +package rtcpreceiver + +import ( + "crypto/rand" + "fmt" + "sync" + "time" + + "github.com/pion/rtcp" + "github.com/pion/rtp" +) + +// seconds since 1st January 1900 +// higher 32 bits are the integer part, lower 32 bits are the fractional part +func ntpTimeRTCPToGo(v uint64) time.Time { + nano := int64((v>>32)*1000000000+(v&0xFFFFFFFF)) - 2208988800*1000000000 + return time.Unix(0, nano) +} + +func randUint32() (uint32, error) { + var b [4]byte + _, err := rand.Read(b[:]) + if err != nil { + return 0, err + } + return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil +} + +// RTCPReceiver is a utility to generate RTCP receiver reports. +type RTCPReceiver struct { + ClockRate int + LocalSSRC *uint32 + Period time.Duration + TimeNow func() time.Time + WritePacketRTCP func(rtcp.Packet) + + mutex sync.RWMutex + + // data from RTP packets + firstRTPPacketReceived bool + timeInitialized bool + sequenceNumberCycles uint16 + lastSequenceNumber uint16 + remoteSSRC uint32 + lastTimeRTP uint32 + lastTimeSystem time.Time + totalLost uint32 + totalLostSinceReport uint32 + totalSinceReport uint32 + jitter float64 + + // data from RTCP packets + firstSenderReportReceived bool + lastSenderReportTimeNTP uint64 + lastSenderReportTimeRTP uint32 + lastSenderReportTimeSystem time.Time + + terminate chan struct{} + done chan struct{} +} + +// New allocates a RTCPReceiver. +// +// Deprecated: replaced by Initialize(). +func New( + clockRate int, + receiverSSRC *uint32, + period time.Duration, + timeNow func() time.Time, + writePacketRTCP func(rtcp.Packet), +) (*RTCPReceiver, error) { + rr := &RTCPReceiver{ + ClockRate: clockRate, + LocalSSRC: receiverSSRC, + Period: period, + TimeNow: timeNow, + WritePacketRTCP: writePacketRTCP, + } + err := rr.Initialize() + if err != nil { + return nil, err + } + + return rr, nil +} + +// Initialize initializes RTCPReceiver. +func (rr *RTCPReceiver) Initialize() error { + if rr.LocalSSRC == nil { + v, err := randUint32() + if err != nil { + return err + } + rr.LocalSSRC = &v + } + + if rr.TimeNow == nil { + rr.TimeNow = time.Now + } + + rr.terminate = make(chan struct{}) + rr.done = make(chan struct{}) + + go rr.run() + + return nil +} + +func (rr *RTCPReceiver) run() { + defer close(rr.done) + + t := time.NewTicker(rr.Period) + defer t.Stop() + + for { + select { + case <-t.C: + report := rr.report() + if report != nil { + rr.WritePacketRTCP(report) + } + + case <-rr.terminate: + return + } + } +} + +func (rr *RTCPReceiver) report() rtcp.Packet { + rr.mutex.Lock() + defer rr.mutex.Unlock() + + if !rr.firstRTPPacketReceived { + return nil + } + + system := rr.TimeNow() + + report := &rtcp.ReceiverReport{ + SSRC: *rr.LocalSSRC, + Reports: []rtcp.ReceptionReport{ + { + SSRC: rr.remoteSSRC, + LastSequenceNumber: uint32(rr.sequenceNumberCycles)<<16 | uint32(rr.lastSequenceNumber), + // equivalent to taking the integer part after multiplying the + // loss fraction by 256 + FractionLost: uint8(float64(rr.totalLostSinceReport*256) / float64(rr.totalSinceReport)), + TotalLost: rr.totalLost, + Jitter: uint32(rr.jitter), + }, + }, + } + + if rr.firstSenderReportReceived { + // middle 32 bits out of 64 in the NTP timestamp of last sender report + report.Reports[0].LastSenderReport = uint32(rr.lastSenderReportTimeNTP >> 16) + + // delay, expressed in units of 1/65536 seconds, between + // receiving the last SR packet from source SSRC_n and sending this + // reception report block + report.Reports[0].Delay = uint32(system.Sub(rr.lastSenderReportTimeSystem).Seconds() * 65536) + } + + rr.totalLostSinceReport = 0 + rr.totalSinceReport = 0 + + return report +} + +// Close closes the RTCPReceiver. +func (rr *RTCPReceiver) Close() { + close(rr.terminate) + <-rr.done +} + +// ProcessPacket extracts the needed data from RTP packets. +func (rr *RTCPReceiver) ProcessPacket(pkt *rtp.Packet, system time.Time, ptsEqualsDTS bool) error { + rr.mutex.Lock() + defer rr.mutex.Unlock() + + // first packet + if !rr.firstRTPPacketReceived { + rr.firstRTPPacketReceived = true + rr.totalSinceReport = 1 + rr.lastSequenceNumber = pkt.SequenceNumber + rr.remoteSSRC = pkt.SSRC + + if ptsEqualsDTS { + rr.timeInitialized = true + rr.lastTimeRTP = pkt.Timestamp + rr.lastTimeSystem = system + } + + // subsequent packets + } else { + if pkt.SSRC != rr.remoteSSRC { + return fmt.Errorf("received packet with wrong SSRC %d, expected %d", pkt.SSRC, rr.remoteSSRC) + } + + diff := int32(pkt.SequenceNumber) - int32(rr.lastSequenceNumber) + + // overflow + if diff < -0x0FFF { + rr.sequenceNumberCycles++ + } + + // detect lost packets + if pkt.SequenceNumber != (rr.lastSequenceNumber + 1) { + rr.totalLost += uint32(uint16(diff) - 1) + rr.totalLostSinceReport += uint32(uint16(diff) - 1) + + // allow up to 24 bits + if rr.totalLost > 0xFFFFFF { + rr.totalLost = 0xFFFFFF + } + if rr.totalLostSinceReport > 0xFFFFFF { + rr.totalLostSinceReport = 0xFFFFFF + } + } + + rr.totalSinceReport += uint32(uint16(diff)) + rr.lastSequenceNumber = pkt.SequenceNumber + + if ptsEqualsDTS { + if rr.timeInitialized { + // update jitter + // https://tools.ietf.org/html/rfc3550#page-39 + D := system.Sub(rr.lastTimeSystem).Seconds()*float64(rr.ClockRate) - + (float64(pkt.Timestamp) - float64(rr.lastTimeRTP)) + if D < 0 { + D = -D + } + rr.jitter += (D - rr.jitter) / 16 + } + + rr.timeInitialized = true + rr.lastTimeRTP = pkt.Timestamp + rr.lastTimeSystem = system + } + } + + return nil +} + +// ProcessSenderReport extracts the needed data from RTCP sender reports. +func (rr *RTCPReceiver) ProcessSenderReport(sr *rtcp.SenderReport, system time.Time) { + rr.mutex.Lock() + defer rr.mutex.Unlock() + + rr.firstSenderReportReceived = true + rr.lastSenderReportTimeNTP = sr.NTPTime + rr.lastSenderReportTimeRTP = sr.RTPTime + rr.lastSenderReportTimeSystem = system +} + +func (rr *RTCPReceiver) packetNTPUnsafe(ts uint32) (time.Time, bool) { + if !rr.firstSenderReportReceived { + return time.Time{}, false + } + + timeDiff := int32(ts - rr.lastSenderReportTimeRTP) + timeDiffGo := (time.Duration(timeDiff) * time.Second) / time.Duration(rr.ClockRate) + + return ntpTimeRTCPToGo(rr.lastSenderReportTimeNTP).Add(timeDiffGo), true +} + +// PacketNTP returns the NTP timestamp of the packet. +func (rr *RTCPReceiver) PacketNTP(ts uint32) (time.Time, bool) { + rr.mutex.Lock() + defer rr.mutex.Unlock() + + return rr.packetNTPUnsafe(ts) +} + +// SenderSSRC returns the SSRC of outgoing RTP packets. +// +// Deprecated: replaced by Stats(). +func (rr *RTCPReceiver) SenderSSRC() (uint32, bool) { + stats := rr.Stats() + if stats == nil { + return 0, false + } + return stats.RemoteSSRC, true +} + +// Stats are statistics. +type Stats struct { + RemoteSSRC uint32 + LastSequenceNumber uint16 + LastRTP uint32 + LastNTP time.Time + Jitter float64 +} + +// Stats returns statistics. +func (rr *RTCPReceiver) Stats() *Stats { + rr.mutex.RLock() + defer rr.mutex.RUnlock() + + if !rr.firstRTPPacketReceived { + return nil + } + + ntp, _ := rr.packetNTPUnsafe(rr.lastTimeRTP) + + return &Stats{ + RemoteSSRC: rr.remoteSSRC, + LastSequenceNumber: rr.lastSequenceNumber, + LastRTP: rr.lastTimeRTP, + LastNTP: ntp, + Jitter: rr.jitter, + } +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtcpsender/rtcpsender.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtcpsender/rtcpsender.go new file mode 100644 index 000000000..929d7a6ad --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtcpsender/rtcpsender.go @@ -0,0 +1,187 @@ +// Package rtcpsender contains a utility to generate RTCP sender reports. +package rtcpsender + +import ( + "sync" + "time" + + "github.com/pion/rtcp" + "github.com/pion/rtp" +) + +// seconds since 1st January 1900 +// higher 32 bits are the integer part, lower 32 bits are the fractional part +func ntpTimeGoToRTCP(v time.Time) uint64 { + s := uint64(v.UnixNano()) + 2208988800*1000000000 + return (s/1000000000)<<32 | (s % 1000000000) +} + +// RTCPSender is a utility to generate RTCP sender reports. +type RTCPSender struct { + ClockRate int + Period time.Duration + TimeNow func() time.Time + WritePacketRTCP func(rtcp.Packet) + + mutex sync.RWMutex + + // data from RTP packets + firstRTPPacketSent bool + lastTimeRTP uint32 + lastTimeNTP time.Time + lastTimeSystem time.Time + localSSRC uint32 + lastSequenceNumber uint16 + packetCount uint32 + octetCount uint32 + + terminate chan struct{} + done chan struct{} +} + +// New allocates a RTCPSender. +// +// Deprecated: replaced by Initialize(). +func New( + clockRate int, + period time.Duration, + timeNow func() time.Time, + writePacketRTCP func(rtcp.Packet), +) *RTCPSender { + rs := &RTCPSender{ + ClockRate: clockRate, + Period: period, + TimeNow: timeNow, + WritePacketRTCP: writePacketRTCP, + } + rs.Initialize() + + return rs +} + +// Initialize initializes a RTCPSender. +func (rs *RTCPSender) Initialize() { + if rs.TimeNow == nil { + rs.TimeNow = time.Now + } + + rs.terminate = make(chan struct{}) + rs.done = make(chan struct{}) + + go rs.run() +} + +func (rs *RTCPSender) run() { + defer close(rs.done) + + t := time.NewTicker(rs.Period) + defer t.Stop() + + for { + select { + case <-t.C: + report := rs.report() + if report != nil { + rs.WritePacketRTCP(report) + } + + case <-rs.terminate: + return + } + } +} + +func (rs *RTCPSender) report() rtcp.Packet { + rs.mutex.Lock() + defer rs.mutex.Unlock() + + if !rs.firstRTPPacketSent { + return nil + } + + systemTimeDiff := rs.TimeNow().Sub(rs.lastTimeSystem) + ntpTime := rs.lastTimeNTP.Add(systemTimeDiff) + rtpTime := rs.lastTimeRTP + uint32(systemTimeDiff.Seconds()*float64(rs.ClockRate)) + + return &rtcp.SenderReport{ + SSRC: rs.localSSRC, + NTPTime: ntpTimeGoToRTCP(ntpTime), + RTPTime: rtpTime, + PacketCount: rs.packetCount, + OctetCount: rs.octetCount, + } +} + +// Close closes the RTCPSender. +func (rs *RTCPSender) Close() { + close(rs.terminate) + <-rs.done +} + +// ProcessPacket extracts data from RTP packets. +func (rs *RTCPSender) ProcessPacket(pkt *rtp.Packet, ntp time.Time, ptsEqualsDTS bool) { + rs.mutex.Lock() + defer rs.mutex.Unlock() + + if ptsEqualsDTS { + rs.firstRTPPacketSent = true + rs.lastTimeRTP = pkt.Timestamp + rs.lastTimeNTP = ntp + rs.lastTimeSystem = rs.TimeNow() + rs.localSSRC = pkt.SSRC + } + + rs.lastSequenceNumber = pkt.SequenceNumber + + rs.packetCount++ + rs.octetCount += uint32(len(pkt.Payload)) +} + +// SenderSSRC returns the SSRC of outgoing RTP packets. +// +// Deprecated: replaced by Stats(). +func (rs *RTCPSender) SenderSSRC() (uint32, bool) { + stats := rs.Stats() + if stats == nil { + return 0, false + } + + return stats.LocalSSRC, true +} + +// LastPacketData returns metadata of the last RTP packet. +// +// Deprecated: replaced by Stats(). +func (rs *RTCPSender) LastPacketData() (uint16, uint32, time.Time, bool) { + stats := rs.Stats() + if stats == nil { + return 0, 0, time.Time{}, false + } + + return stats.LastSequenceNumber, stats.LastRTP, stats.LastNTP, true +} + +// Stats are statistics. +type Stats struct { + LocalSSRC uint32 + LastSequenceNumber uint16 + LastRTP uint32 + LastNTP time.Time +} + +// Stats returns statistics. +func (rs *RTCPSender) Stats() *Stats { + rs.mutex.RLock() + defer rs.mutex.RUnlock() + + if !rs.firstRTPPacketSent { + return nil + } + + return &Stats{ + LocalSSRC: rs.localSSRC, + LastSequenceNumber: rs.lastSequenceNumber, + LastRTP: rs.lastTimeRTP, + LastNTP: rs.lastTimeNTP, + } +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtplossdetector/lossdetector.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtplossdetector/lossdetector.go new file mode 100644 index 000000000..219af5701 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtplossdetector/lossdetector.go @@ -0,0 +1,38 @@ +// Package rtplossdetector implements an algorithm that detects lost packets. +package rtplossdetector + +import ( + "github.com/pion/rtp" +) + +// LossDetector detects lost packets. +type LossDetector struct { + initialized bool + expectedSeqNum uint16 +} + +// New allocates a LossDetector. +// +// Deprecated: Useless. +func New() *LossDetector { + return &LossDetector{} +} + +// Process processes a RTP packet. +// It returns the number of lost packets. +func (r *LossDetector) Process(pkt *rtp.Packet) uint { + if !r.initialized { + r.initialized = true + r.expectedSeqNum = pkt.SequenceNumber + 1 + return 0 + } + + if pkt.SequenceNumber != r.expectedSeqNum { + diff := pkt.SequenceNumber - r.expectedSeqNum + r.expectedSeqNum = pkt.SequenceNumber + 1 + return uint(diff) + } + + r.expectedSeqNum = pkt.SequenceNumber + 1 + return 0 +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtpreorderer/reorderer.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtpreorderer/reorderer.go new file mode 100644 index 000000000..00043b0fd --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtpreorderer/reorderer.go @@ -0,0 +1,142 @@ +// Package rtpreorderer implements a filter to reorder incoming RTP packets. +package rtpreorderer + +import ( + "github.com/pion/rtp" +) + +const ( + bufferSize = 64 +) + +// Reorderer filters incoming RTP packets, in order to +// - order packets +// - remove duplicate packets +type Reorderer struct { + initialized bool + expectedSeqNum uint16 + buffer []*rtp.Packet + absPos uint16 + negativeCount int +} + +// New allocates a Reorderer. +// +// Deprecated: replaced by Initialize(). +func New() *Reorderer { + r := &Reorderer{} + r.Initialize() + return r +} + +// Initialize initializes a Reorderer. +func (r *Reorderer) Initialize() { + r.buffer = make([]*rtp.Packet, bufferSize) +} + +// Process processes a RTP packet. +// It returns a sequence of ordered packets and the number of lost packets. +func (r *Reorderer) Process(pkt *rtp.Packet) ([]*rtp.Packet, uint) { + if !r.initialized { + r.initialized = true + r.expectedSeqNum = pkt.SequenceNumber + 1 + return []*rtp.Packet{pkt}, 0 + } + + relPos := int16(pkt.SequenceNumber - r.expectedSeqNum) + + // packet is a duplicate or has been sent + // before the first packet processed by Reorderer. + // discard. + if relPos < 0 { + r.negativeCount++ + + // stream has been resetted, therefore reset reorderer too + if r.negativeCount > bufferSize { + r.negativeCount = 0 + + // clear buffer + for i := uint16(0); i < bufferSize; i++ { + p := (r.absPos + i) & (bufferSize - 1) + r.buffer[p] = nil + } + + // reset position + r.expectedSeqNum = pkt.SequenceNumber + 1 + return []*rtp.Packet{pkt}, 0 + } + + return nil, 0 + } + r.negativeCount = 0 + + // there's a missing packet and buffer is full. + // return entire buffer and clear it. + if relPos >= bufferSize { + n := 1 + for i := uint16(0); i < bufferSize; i++ { + p := (r.absPos + i) & (bufferSize - 1) + if r.buffer[p] != nil { + n++ + } + } + + ret := make([]*rtp.Packet, n) + pos := 0 + + for i := uint16(0); i < bufferSize; i++ { + p := (r.absPos + i) & (bufferSize - 1) + if r.buffer[p] != nil { + ret[pos], r.buffer[p] = r.buffer[p], nil + pos++ + } + } + + ret[pos] = pkt + + r.expectedSeqNum = pkt.SequenceNumber + 1 + return ret, uint(int(relPos) - n + 1) + } + + // there's a missing packet + if relPos != 0 { + p := (r.absPos + uint16(relPos)) & (bufferSize - 1) + + // current packet is a duplicate. discard + if r.buffer[p] != nil { + return nil, 0 + } + + // put current packet in buffer + r.buffer[p] = pkt + return nil, 0 + } + + // all packets have been received correctly. + // return them + + n := uint16(1) + for { + p := (r.absPos + n) & (bufferSize - 1) + if r.buffer[p] == nil { + break + } + n++ + } + + ret := make([]*rtp.Packet, n) + + ret[0] = pkt + r.absPos++ + r.absPos &= (bufferSize - 1) + + for i := uint16(1); i < n; i++ { + ret[i], r.buffer[r.absPos] = r.buffer[r.absPos], nil + r.absPos++ + r.absPos &= (bufferSize - 1) + } + + r.expectedSeqNum = pkt.SequenceNumber + n + + return ret, 0 +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtptime/encoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtptime/encoder.go new file mode 100644 index 000000000..cf5103f4e --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtptime/encoder.go @@ -0,0 +1,61 @@ +package rtptime + +import ( + "crypto/rand" + "time" +) + +func divCeil(n, d uint64) uint64 { + v := n / d + if (n % d) != 0 { + v++ + } + return v +} + +func randUint32() (uint32, error) { + var b [4]byte + _, err := rand.Read(b[:]) + if err != nil { + return 0, err + } + return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil +} + +// Encoder is a RTP timestamp encoder. +// +// Deprecated: not used anymore. +type Encoder struct { + // Clock rate. + ClockRate int + + // (optional) initial timestamp. + // It defaults to a random value. + InitialTimestamp *uint32 + + clockRateTD time.Duration + initialTimestampTD time.Duration +} + +// Initialize initializes an Encoder. +func (e *Encoder) Initialize() error { + e.clockRateTD = time.Duration(e.ClockRate) + + if e.InitialTimestamp == nil { + v, err := randUint32() + if err != nil { + return err + } + e.InitialTimestamp = &v + } + + e.initialTimestampTD = time.Duration(divCeil(uint64(*e.InitialTimestamp)*uint64(time.Second), uint64(e.ClockRate))) + + return nil +} + +// Encode encodes a timestamp. +func (e *Encoder) Encode(ts time.Duration) uint32 { + ts += e.initialTimestampTD + return uint32(multiplyAndDivide(ts, e.clockRateTD, time.Second)) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtptime/global_decoder.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtptime/global_decoder.go new file mode 100644 index 000000000..a11cad46a --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtptime/global_decoder.go @@ -0,0 +1,81 @@ +package rtptime + +import ( + "time" + + "github.com/pion/rtp" +) + +var timeNow = time.Now + +// avoid an int64 overflow and preserve resolution by splitting division into two parts: +// first add the integer part, then the decimal part. +func multiplyAndDivide(v, m, d time.Duration) time.Duration { + secs := v / d + dec := v % d + return (secs*m + dec*m/d) +} + +type globalDecoderTrackData struct { + startPTS time.Duration + clockRate time.Duration + overall time.Duration + prev uint32 +} + +func newGlobalDecoderTrackData( + startPTS time.Duration, + clockRate int, + startTimestamp uint32, +) *globalDecoderTrackData { + return &globalDecoderTrackData{ + startPTS: startPTS, + clockRate: time.Duration(clockRate), + prev: startTimestamp, + } +} + +func (d *globalDecoderTrackData) decode(ts uint32) time.Duration { + diff := int32(ts - d.prev) + d.prev = ts + d.overall += time.Duration(diff) + + return d.startPTS + multiplyAndDivide(d.overall, time.Second, d.clockRate) +} + +// GlobalDecoderTrack is a track (RTSP format or WebRTC track) of a GlobalDecoder. +// +// Deprecated: replaced by GlobalDecoderTrack2 +type GlobalDecoderTrack interface { + ClockRate() int + PTSEqualsDTS(*rtp.Packet) bool +} + +// GlobalDecoder is a RTP timestamp decoder. +// +// Deprecated: replaced by GlobalDecoder2. +type GlobalDecoder struct { + wrapped *GlobalDecoder2 +} + +// NewGlobalDecoder allocates a GlobalDecoder. +// +// Deprecated: replaced by NewGlobalDecoder2. +func NewGlobalDecoder() *GlobalDecoder { + return &GlobalDecoder{ + wrapped: NewGlobalDecoder2(), + } +} + +// Decode decodes a timestamp. +func (d *GlobalDecoder) Decode( + track GlobalDecoderTrack, + pkt *rtp.Packet, +) (time.Duration, bool) { + v, ok := d.wrapped.Decode(track, pkt) + if !ok { + return 0, false + } + + return multiplyAndDivide(time.Duration(v), time.Second, time.Duration(track.ClockRate())), true +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtptime/global_decoder2.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtptime/global_decoder2.go new file mode 100644 index 000000000..30f689882 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtptime/global_decoder2.go @@ -0,0 +1,110 @@ +package rtptime + +import ( + "sync" + "time" + + "github.com/pion/rtp" +) + +// avoid an int64 overflow and preserve resolution by splitting division into two parts: +// first add the integer part, then the decimal part. +func multiplyAndDivide2(v, m, d int64) int64 { + secs := v / d + dec := v % d + return (secs*m + dec*m/d) +} + +type globalDecoder2TrackData struct { + overall int64 + prev uint32 +} + +func (d *globalDecoder2TrackData) decode(ts uint32) int64 { + d.overall += int64(int32(ts - d.prev)) + d.prev = ts + return d.overall +} + +// GlobalDecoder2Track is a track (RTSP format or WebRTC track) of GlobalDecoder2. +type GlobalDecoder2Track interface { + ClockRate() int + PTSEqualsDTS(*rtp.Packet) bool +} + +// NewGlobalDecoder2 allocates a GlobalDecoder. +// +// Deprecated: replaced by GlobalDecoder2.Initialize(). +func NewGlobalDecoder2() *GlobalDecoder2 { + d := &GlobalDecoder2{} + d.Initialize() + return d +} + +// GlobalDecoder2 is a RTP timestamp decoder. +type GlobalDecoder2 struct { + mutex sync.Mutex + leadingTrack GlobalDecoderTrack + startNTP time.Time + startPTS int64 + startPTSClockRate int64 + tracks map[GlobalDecoder2Track]*globalDecoder2TrackData +} + +// Initialize initializes a GlobalDecoder2. +func (d *GlobalDecoder2) Initialize() { + d.tracks = make(map[GlobalDecoder2Track]*globalDecoder2TrackData) +} + +// Decode decodes a timestamp. +func (d *GlobalDecoder2) Decode( + track GlobalDecoder2Track, + pkt *rtp.Packet, +) (int64, bool) { + if track.ClockRate() == 0 { + return 0, false + } + + d.mutex.Lock() + defer d.mutex.Unlock() + + df, ok := d.tracks[track] + + // never seen before track + if !ok { + if !track.PTSEqualsDTS(pkt) { + return 0, false + } + + now := timeNow() + + if d.leadingTrack == nil { + d.leadingTrack = track + d.startNTP = now + d.startPTS = 0 + d.startPTSClockRate = int64(track.ClockRate()) + } + + // start from the PTS of the leading track + startPTS := multiplyAndDivide2(d.startPTS, int64(track.ClockRate()), d.startPTSClockRate) + startPTS += multiplyAndDivide2(int64(now.Sub(d.startNTP)), int64(track.ClockRate()), int64(time.Second)) + + d.tracks[track] = &globalDecoder2TrackData{ + overall: startPTS, + prev: pkt.Timestamp, + } + + return startPTS, true + } + + pts := df.decode(pkt.Timestamp) + + // update startNTP / startPTS + if d.leadingTrack == track && track.PTSEqualsDTS(pkt) { + now := timeNow() + d.startNTP = now + d.startPTS = pts + } + + return pts, true +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtptime/rtptime.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtptime/rtptime.go new file mode 100644 index 000000000..1f120317d --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/rtptime/rtptime.go @@ -0,0 +1,2 @@ +// Package rtptime contains a time decoder and encoder. +package rtptime diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/sdp/sdp.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/sdp/sdp.go new file mode 100644 index 000000000..2a645a09a --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/pkg/sdp/sdp.go @@ -0,0 +1,743 @@ +// Package sdp contains a SDP encoder/decoder compatible with most RTSP implementations. +package sdp + +import ( + "errors" + "fmt" + "net/url" + "strconv" + "strings" + + psdp "github.com/pion/sdp/v3" +) + +// SessionDescription is a SDP session description. +type SessionDescription psdp.SessionDescription + +// Attribute returns the value of an attribute and if it exists +func (s *SessionDescription) Attribute(key string) (string, bool) { + return (*psdp.SessionDescription)(s).Attribute(key) +} + +// Marshal encodes a SessionDescription. +func (s *SessionDescription) Marshal() ([]byte, error) { + return (*psdp.SessionDescription)(s).Marshal() +} + +var ( + errSDPInvalidSyntax = errors.New("sdp: invalid syntax") + errSDPInvalidNumericValue = errors.New("sdp: invalid numeric value") + errSDPInvalidValue = errors.New("sdp: invalid value") + errSDPInvalidPortValue = errors.New("sdp: invalid port value") +) + +func indexOf(element string, data []string) int { + for k, v := range data { + if element == v { + return k + } + } + return -1 +} + +func anyOf(element string, data ...string) bool { + for _, v := range data { + if element == v { + return true + } + } + return false +} + +func parsePort(value string) (int, error) { + port, err := strconv.Atoi(value) + if err != nil { + return 0, fmt.Errorf("%w `%v`", errSDPInvalidPortValue, port) + } + + if port < 0 || port > 65536 { + return 0, fmt.Errorf("%w -- out of range `%v`", errSDPInvalidPortValue, port) + } + + return port, nil +} + +func (s *SessionDescription) unmarshalProtocolVersion(value string) error { + if value != "0" { + return fmt.Errorf("invalid version") + } + + return nil +} + +func (s *SessionDescription) unmarshalSessionName(value string) error { + s.SessionName = psdp.SessionName(value) + return nil +} + +func stringsReverseIndexByte(s string, b byte) int { + for i := len(s) - 2; i >= 0; i-- { + if s[i] == b { + return i + } + } + return -1 +} + +// This is rewritten from scratch to make it compatible with most RTSP +// implementations. +func (s *SessionDescription) unmarshalOrigin(value string) error { + value = strings.Replace(value, " IN IPV4 ", " IN IP4 ", 1) + + if strings.HasSuffix(value, "IN IP4") { + value += " " + } + + i := strings.Index(value, " IN IP4 ") + if i < 0 { + i = strings.Index(value, " IN IP6 ") + if i < 0 { + return fmt.Errorf("%w `o=%v`", errSDPInvalidSyntax, value) + } + } + + s.Origin.NetworkType = value[i+1 : i+3] + s.Origin.AddressType = value[i+4 : i+7] + s.Origin.UnicastAddress = strings.TrimSpace(value[i+8:]) + value = value[:i] + + i = stringsReverseIndexByte(value, ' ') + if i < 0 { + return fmt.Errorf("%w `o=%v`", errSDPInvalidSyntax, value) + } + + var tmp string + tmp, value = value[i+1:], value[:i] + + if i = strings.Index(tmp, "."); i >= 0 { + tmp = tmp[:i] + } + tmp = strings.TrimPrefix(tmp, "-") + + var err error + s.Origin.SessionVersion, err = strconv.ParseUint(tmp, 10, 64) + if err != nil { + return fmt.Errorf("%w `%v`", errSDPInvalidNumericValue, tmp) + } + + if value == "-0" { // live reporter app + value = "- 0" + } + + i = stringsReverseIndexByte(value, ' ') + if i < 0 { + return nil + } + + tmp, value = value[i+1:], value[:i] + + switch { + case strings.HasPrefix(tmp, "0x"), strings.HasPrefix(tmp, "0X"): + s.Origin.SessionID, err = strconv.ParseUint(tmp[2:], 16, 64) + case strings.ContainsAny(tmp, "abcdefABCDEF"): + s.Origin.SessionID, err = strconv.ParseUint(tmp, 16, 64) + default: + if i := strings.Index(tmp, "."); i >= 0 { + tmp = tmp[:i] + } + tmp = strings.TrimPrefix(tmp, "-") + + s.Origin.SessionID, err = strconv.ParseUint(tmp, 10, 64) + } + if err != nil { + return fmt.Errorf("%w `%v`", errSDPInvalidNumericValue, tmp) + } + + s.Origin.Username = value + + return nil +} + +func (s *SessionDescription) unmarshalSessionInformation(value string) error { + sessionInformation := psdp.Information(value) + s.SessionInformation = &sessionInformation + return nil +} + +func (s *SessionDescription) unmarshalURI(value string) error { + var err error + s.URI, err = url.Parse(value) + if err != nil { + return err + } + + return nil +} + +func (s *SessionDescription) unmarshalEmail(value string) error { + emailAddress := psdp.EmailAddress(value) + s.EmailAddress = &emailAddress + return nil +} + +func (s *SessionDescription) unmarshalPhone(value string) error { + phoneNumber := psdp.PhoneNumber(value) + s.PhoneNumber = &phoneNumber + return nil +} + +func unmarshalConnectionInformation(value string) (*psdp.ConnectionInformation, error) { + value = strings.Replace(value, "IN IPV4 ", "IN IP4 ", 1) + + if strings.HasPrefix(value, "IN c=IN") { + value = value[len("IN c="):] + } + + fields := strings.Fields(value) + if len(fields) < 2 { + return nil, fmt.Errorf("%w `c=%v`", errSDPInvalidSyntax, fields) + } + + // Set according to currently registered with IANA + // https://tools.ietf.org/html/rfc4566#section-8.2.6 + if i := indexOf(strings.ToUpper(fields[0]), []string{"IN"}); i == -1 { + return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, fields[0]) + } + + // Set according to currently registered with IANA + // https://tools.ietf.org/html/rfc4566#section-8.2.7 + if i := indexOf(fields[1], []string{"IP4", "IP6"}); i == -1 { + return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, fields[1]) + } + + connAddr := new(psdp.Address) + if len(fields) > 2 { + connAddr.Address = fields[2] + } + + return &psdp.ConnectionInformation{ + NetworkType: strings.ToUpper(fields[0]), + AddressType: fields[1], + Address: connAddr, + }, nil +} + +func (s *SessionDescription) unmarshalSessionConnectionInformation(value string) error { + var err error + s.ConnectionInformation, err = unmarshalConnectionInformation(value) + if err != nil { + return fmt.Errorf("%w `c=%v`", errSDPInvalidSyntax, value) + } + + return nil +} + +func unmarshalBandwidth(value string) (*psdp.Bandwidth, error) { + parts := strings.Split(value, ":") + if len(parts) != 2 { + return nil, fmt.Errorf("%w `b=%v`", errSDPInvalidValue, parts) + } + + experimental := strings.HasPrefix(parts[0], "X-") + if experimental { + parts[0] = strings.TrimPrefix(parts[0], "X-") + } else if !anyOf(parts[0], "CT", "AS", "TIAS", "RS", "RR") { + // Set according to currently registered with IANA + // https://tools.ietf.org/html/rfc4566#section-5.8 + // https://tools.ietf.org/html/rfc3890#section-6.2 + // https://tools.ietf.org/html/rfc3556#section-2 + return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, parts[0]) + } + + bandwidth, err := strconv.ParseUint(parts[1], 10, 64) + if err != nil { + return nil, fmt.Errorf("%w `%v`", errSDPInvalidNumericValue, parts[1]) + } + + return &psdp.Bandwidth{ + Experimental: experimental, + Type: parts[0], + Bandwidth: bandwidth, + }, nil +} + +func (s *SessionDescription) unmarshalSessionBandwidth(value string) error { + bandwidth, err := unmarshalBandwidth(value) + if err != nil { + return fmt.Errorf("%w `b=%v`", errSDPInvalidValue, value) + } + s.Bandwidth = append(s.Bandwidth, *bandwidth) + + return nil +} + +func (s *SessionDescription) unmarshalTimeZones(value string) error { + // These fields are transimitted in pairs + // z= .... + // so we are making sure that there are actually multiple of 2 total. + fields := strings.Fields(value) + if len(fields)%2 != 0 { + return fmt.Errorf("%w `t=%v`", errSDPInvalidSyntax, fields) + } + + for i := 0; i < len(fields); i += 2 { + var timeZone psdp.TimeZone + + var err error + timeZone.AdjustmentTime, err = strconv.ParseUint(fields[i], 10, 64) + if err != nil { + return fmt.Errorf("%w `%v`", errSDPInvalidValue, fields) + } + + timeZone.Offset, err = parseTimeUnits(fields[i+1]) + if err != nil { + return err + } + + s.TimeZones = append(s.TimeZones, timeZone) + } + + return nil +} + +func (s *SessionDescription) unmarshalSessionEncryptionKey(value string) error { + encryptionKey := psdp.EncryptionKey(value) + s.EncryptionKey = &encryptionKey + return nil +} + +func (s *SessionDescription) unmarshalSessionAttribute(value string) error { + i := strings.IndexRune(value, ':') + var a psdp.Attribute + if i > 0 { + a = psdp.NewAttribute(value[:i], value[i+1:]) + } else { + a = psdp.NewPropertyAttribute(value) + } + + s.Attributes = append(s.Attributes, a) + return nil +} + +func (s *SessionDescription) unmarshalTiming(value string) error { + if value == "now-" { + // special case for some FLIR cameras with invalid timing element + value = "0 0" + } + fields := strings.Fields(value) + if len(fields) < 2 { + return fmt.Errorf("%w `t=%v`", errSDPInvalidSyntax, fields) + } + + td := psdp.TimeDescription{} + + var err error + td.Timing.StartTime, err = strconv.ParseUint(fields[0], 10, 64) + if err != nil { + return fmt.Errorf("%w `%v`", errSDPInvalidNumericValue, fields[1]) + } + + td.Timing.StopTime, err = strconv.ParseUint(fields[1], 10, 64) + if err != nil { + return fmt.Errorf("%w `%v`", errSDPInvalidNumericValue, fields[1]) + } + + s.TimeDescriptions = append(s.TimeDescriptions, td) + + return nil +} + +func parseTimeUnits(value string) (int64, error) { + // Some time offsets in the protocol can be provided with a shorthand + // notation. This code ensures to convert it to NTP timestamp format. + // d - days (86400 seconds) + // h - hours (3600 seconds) + // m - minutes (60 seconds) + // s - seconds (allowed for completeness) + switch value[len(value)-1:] { + case "d": + num, err := strconv.ParseInt(value[:len(value)-1], 10, 64) + if err != nil { + return 0, fmt.Errorf("%w `%v`", errSDPInvalidValue, value) + } + return num * 86400, nil + case "h": + num, err := strconv.ParseInt(value[:len(value)-1], 10, 64) + if err != nil { + return 0, fmt.Errorf("%w `%v`", errSDPInvalidValue, value) + } + return num * 3600, nil + case "m": + num, err := strconv.ParseInt(value[:len(value)-1], 10, 64) + if err != nil { + return 0, fmt.Errorf("%w `%v`", errSDPInvalidValue, value) + } + return num * 60, nil + } + + num, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return 0, fmt.Errorf("%w `%v`", errSDPInvalidValue, value) + } + + return num, nil +} + +func (s *SessionDescription) unmarshalRepeatTimes(value string) error { + fields := strings.Fields(value) + if len(fields) < 3 { + return fmt.Errorf("%w `r=%v`", errSDPInvalidSyntax, value) + } + + latestTimeDesc := &s.TimeDescriptions[len(s.TimeDescriptions)-1] + + newRepeatTime := psdp.RepeatTime{} + var err error + newRepeatTime.Interval, err = parseTimeUnits(fields[0]) + if err != nil { + return fmt.Errorf("%w `%v`", errSDPInvalidValue, fields) + } + + newRepeatTime.Duration, err = parseTimeUnits(fields[1]) + if err != nil { + return fmt.Errorf("%w `%v`", errSDPInvalidValue, fields) + } + + for i := 2; i < len(fields); i++ { + offset, err := parseTimeUnits(fields[i]) + if err != nil { + return fmt.Errorf("%w `%v`", errSDPInvalidValue, fields) + } + newRepeatTime.Offsets = append(newRepeatTime.Offsets, offset) + } + latestTimeDesc.RepeatTimes = append(latestTimeDesc.RepeatTimes, newRepeatTime) + + return nil +} + +func (s *SessionDescription) unmarshalMediaDescription(value string) error { + fields := strings.Fields(value) + if len(fields) < 4 { + return fmt.Errorf("%w `m=%v`", errSDPInvalidSyntax, fields) + } + + newMediaDesc := &psdp.MediaDescription{} + + // + // Set according to currently registered with IANA + // https://tools.ietf.org/html/rfc4566#section-5.14 + if fields[0] != "video" && + fields[0] != "audio" && + fields[0] != "application" && + !strings.HasPrefix(fields[0], "application/") && + fields[0] != "metadata" && + fields[0] != "text" { + return fmt.Errorf("%w `%v`", errSDPInvalidValue, fields[0]) + } + newMediaDesc.MediaName.Media = fields[0] + + // + parts := strings.Split(fields[1], "/") + var err error + newMediaDesc.MediaName.Port.Value, err = parsePort(parts[0]) + if err != nil { + return fmt.Errorf("%w `%v`", errSDPInvalidPortValue, parts[0]) + } + + if len(parts) > 1 { + portRange, err := strconv.Atoi(parts[1]) + if err != nil { + return fmt.Errorf("%w `%v`", errSDPInvalidValue, parts) + } + newMediaDesc.MediaName.Port.Range = &portRange + } + + // + // Set according to currently registered with IANA + // https://tools.ietf.org/html/rfc4566#section-5.14 + for _, proto := range strings.Split(fields[2], "/") { + if i := indexOf(proto, []string{ + "UDP", "RTP", "AVP", "SAVP", "SAVPF", + "MP2T", "TLS", "DTLS", "SCTP", "AVPF", "TCP", + }); i == -1 { + return fmt.Errorf("%w `%v`", errSDPInvalidNumericValue, fields[2]) + } + newMediaDesc.MediaName.Protos = append(newMediaDesc.MediaName.Protos, proto) + } + + // ... + for i := 3; i < len(fields); i++ { + newMediaDesc.MediaName.Formats = append(newMediaDesc.MediaName.Formats, fields[i]) + } + + s.MediaDescriptions = append(s.MediaDescriptions, newMediaDesc) + + return nil +} + +func (s *SessionDescription) unmarshalMediaTitle(value string) error { + latestMediaDesc := s.MediaDescriptions[len(s.MediaDescriptions)-1] + mediaTitle := psdp.Information(value) + latestMediaDesc.MediaTitle = &mediaTitle + return nil +} + +func (s *SessionDescription) unmarshalMediaConnectionInformation(value string) error { + if strings.HasPrefix(value, "SM ") { + return nil + } + + latestMediaDesc := s.MediaDescriptions[len(s.MediaDescriptions)-1] + var err error + latestMediaDesc.ConnectionInformation, err = unmarshalConnectionInformation(value) + if err != nil { + return fmt.Errorf("%w `c=%v`", errSDPInvalidSyntax, value) + } + + return nil +} + +func (s *SessionDescription) unmarshalMediaBandwidth(value string) error { + latestMediaDesc := s.MediaDescriptions[len(s.MediaDescriptions)-1] + bandwidth, err := unmarshalBandwidth(value) + if err != nil { + return fmt.Errorf("%w `b=%v`", errSDPInvalidSyntax, value) + } + latestMediaDesc.Bandwidth = append(latestMediaDesc.Bandwidth, *bandwidth) + return nil +} + +func (s *SessionDescription) unmarshalMediaEncryptionKey(value string) error { + latestMediaDesc := s.MediaDescriptions[len(s.MediaDescriptions)-1] + encryptionKey := psdp.EncryptionKey(value) + latestMediaDesc.EncryptionKey = &encryptionKey + return nil +} + +func (s *SessionDescription) unmarshalMediaAttribute(value string) error { + i := strings.IndexRune(value, ':') + var a psdp.Attribute + if i > 0 { + a = psdp.NewAttribute(value[:i], value[i+1:]) + } else { + a = psdp.NewPropertyAttribute(value) + } + + latestMediaDesc := s.MediaDescriptions[len(s.MediaDescriptions)-1] + latestMediaDesc.Attributes = append(latestMediaDesc.Attributes, a) + return nil +} + +type unmarshalState int + +const ( + stateInitial unmarshalState = iota + stateSession + stateMedia + stateTimeDescription +) + +func (s *SessionDescription) unmarshalSession(state *unmarshalState, key byte, val string) error { + switch key { + case 'o': + err := s.unmarshalOrigin(val) + if err != nil { + return err + } + + case 's': + err := s.unmarshalSessionName(val) + if err != nil { + return err + } + + case 'i': + err := s.unmarshalSessionInformation(val) + if err != nil { + return err + } + + case 'u': + err := s.unmarshalURI(val) + if err != nil { + return err + } + + case 'e': + err := s.unmarshalEmail(val) + if err != nil { + return err + } + + case 'p': + err := s.unmarshalPhone(val) + if err != nil { + return err + } + + case 'c': + err := s.unmarshalSessionConnectionInformation(val) + if err != nil { + return err + } + + case 'b': + err := s.unmarshalSessionBandwidth(val) + if err != nil { + return err + } + + case 'z': + err := s.unmarshalTimeZones(val) + if err != nil { + return err + } + + case 'k': + err := s.unmarshalSessionEncryptionKey(val) + if err != nil { + return err + } + + case 'a': + err := s.unmarshalSessionAttribute(val) + if err != nil { + return err + } + + case 't': + err := s.unmarshalTiming(val) + if err != nil { + return err + } + *state = stateTimeDescription + + case 'm': + err := s.unmarshalMediaDescription(val) + if err != nil { + return err + } + *state = stateMedia + + default: + return fmt.Errorf("invalid key: %c", key) + } + + return nil +} + +func (s *SessionDescription) unmarshalMedia(key byte, val string) error { + switch key { + case 'm': + err := s.unmarshalMediaDescription(val) + if err != nil { + return err + } + + case 'i': + err := s.unmarshalMediaTitle(val) + if err != nil { + return err + } + + case 'c': + err := s.unmarshalMediaConnectionInformation(val) + if err != nil { + return err + } + + case 'b': + err := s.unmarshalMediaBandwidth(val) + if err != nil { + return err + } + + case 'k': + err := s.unmarshalMediaEncryptionKey(val) + if err != nil { + return err + } + + case 'a': + err := s.unmarshalMediaAttribute(val) + if err != nil { + return err + } + + default: + return fmt.Errorf("invalid key: %c", key) + } + + return nil +} + +// Unmarshal decodes a SessionDescription. +// This is rewritten from scratch to guarantee compatibility with most RTSP +// implementations. +func (s *SessionDescription) Unmarshal(byts []byte) error { + str := string(byts) + + state := stateInitial + + for _, line := range strings.Split(strings.ReplaceAll(str, "\r", ""), "\n") { + if line == "" { + continue + } + + if len(line) < 2 || line[1] != '=' { + return fmt.Errorf("invalid line: (%s)", line) + } + + key := line[0] + val := line[2:] + + switch state { + case stateInitial: + switch key { + case 'v': + err := s.unmarshalProtocolVersion(val) + if err != nil { + return err + } + state = stateSession + + default: + state = stateSession + err := s.unmarshalSession(&state, key, val) + if err != nil { + return err + } + } + + case stateSession: + err := s.unmarshalSession(&state, key, val) + if err != nil { + return err + } + + case stateMedia: + err := s.unmarshalMedia(key, val) + if err != nil { + return err + } + + case stateTimeDescription: + switch key { + case 'r': + err := s.unmarshalRepeatTimes(val) + if err != nil { + return err + } + + default: + state = stateSession + err := s.unmarshalSession(&state, key, val) + if err != nil { + return err + } + } + } + } + + return nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/restrict_network.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/restrict_network.go new file mode 100644 index 000000000..b191d8fad --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/restrict_network.go @@ -0,0 +1,17 @@ +package gortsplib + +import ( + "net" +) + +// do not listen on IPv6 when address is 0.0.0.0. +func restrictNetwork(network string, address string) (string, string) { + host, _, err := net.SplitHostPort(address) + if err == nil { + if host == "0.0.0.0" { + return network + "4", address + } + } + + return network, address +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server.go new file mode 100644 index 000000000..3a21f4d83 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server.go @@ -0,0 +1,533 @@ +package gortsplib + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "strconv" + "sync" + "time" + + "github.com/bluenviron/gortsplib/v4/pkg/auth" + "github.com/bluenviron/gortsplib/v4/pkg/base" + "github.com/bluenviron/gortsplib/v4/pkg/liberrors" +) + +const ( + serverHeader = "gortsplib" + serverAuthRealm = "ipcam" +) + +func extractPort(address string) (int, error) { + _, tmp, err := net.SplitHostPort(address) + if err != nil { + return 0, err + } + + tmp2, err := strconv.ParseUint(tmp, 10, 16) + if err != nil { + return 0, err + } + + return int(tmp2), nil +} + +type sessionRequestRes struct { + ss *ServerSession + res *base.Response + err error +} + +type sessionRequestReq struct { + sc *ServerConn + req *base.Request + id string + create bool + res chan sessionRequestRes +} + +type chGetMulticastIPReq struct { + res chan net.IP +} + +// Server is a RTSP server. +type Server struct { + // + // RTSP parameters (all optional except RTSPAddress) + // + // the RTSP address of the server, to accept connections and send and receive + // packets with the TCP transport. + RTSPAddress string + // a port to send and receive RTP packets with the UDP transport. + // If UDPRTPAddress and UDPRTCPAddress are filled, the server can support the UDP transport. + UDPRTPAddress string + // a port to send and receive RTCP packets with the UDP transport. + // If UDPRTPAddress and UDPRTCPAddress are filled, the server can support the UDP transport. + UDPRTCPAddress string + // a range of multicast IPs to use with the UDP-multicast transport. + // If MulticastIPRange, MulticastRTPPort, MulticastRTCPPort are filled, the server + // can support the UDP-multicast transport. + MulticastIPRange string + // a port to send RTP packets with the UDP-multicast transport. + // If MulticastIPRange, MulticastRTPPort, MulticastRTCPPort are filled, the server + // can support the UDP-multicast transport. + MulticastRTPPort int + // a port to send RTCP packets with the UDP-multicast transport. + // If MulticastIPRange, MulticastRTPPort, MulticastRTCPPort are filled, the server + // can support the UDP-multicast transport. + MulticastRTCPPort int + // timeout of read operations. + // It defaults to 10 seconds + ReadTimeout time.Duration + // timeout of write operations. + // It defaults to 10 seconds + WriteTimeout time.Duration + // a TLS configuration to accept TLS (RTSPS) connections. + TLSConfig *tls.Config + // Size of the queue of outgoing packets. + // It defaults to 256. + WriteQueueSize int + // maximum size of outgoing RTP / RTCP packets. + // This must be less than the UDP MTU (1472 bytes). + // It defaults to 1472. + MaxPacketSize int + // disable automatic RTCP sender reports. + DisableRTCPSenderReports bool + // authentication methods. + // It defaults to plain and digest+MD5. + AuthMethods []auth.VerifyMethod + + // + // handler (optional) + // + // an handler to handle server events. + // It may implement one or more of the ServerHandler* interfaces. + Handler ServerHandler + + // + // system functions (all optional) + // + // function used to initialize the TCP listener. + // It defaults to net.Listen. + Listen func(network string, address string) (net.Listener, error) + // function used to initialize UDP listeners. + // It defaults to net.ListenPacket. + ListenPacket func(network, address string) (net.PacketConn, error) + + // + // private + // + + timeNow func() time.Time + senderReportPeriod time.Duration + receiverReportPeriod time.Duration + sessionTimeout time.Duration + checkStreamPeriod time.Duration + + ctx context.Context + ctxCancel func() + wg sync.WaitGroup + multicastNet *net.IPNet + multicastNextIP net.IP + tcpListener *serverTCPListener + udpRTPListener *serverUDPListener + udpRTCPListener *serverUDPListener + sessions map[string]*ServerSession + conns map[*ServerConn]struct{} + closeError error + + // in + chNewConn chan net.Conn + chAcceptErr chan error + chCloseConn chan *ServerConn + chHandleRequest chan sessionRequestReq + chCloseSession chan *ServerSession + chGetMulticastIP chan chGetMulticastIPReq +} + +// Start starts the server. +func (s *Server) Start() error { + // RTSP parameters + if s.ReadTimeout == 0 { + s.ReadTimeout = 10 * time.Second + } + if s.WriteTimeout == 0 { + s.WriteTimeout = 10 * time.Second + } + if s.WriteQueueSize == 0 { + s.WriteQueueSize = 256 + } else if (s.WriteQueueSize & (s.WriteQueueSize - 1)) != 0 { + return fmt.Errorf("WriteQueueSize must be a power of two") + } + if s.MaxPacketSize == 0 { + s.MaxPacketSize = udpMaxPayloadSize + } else if s.MaxPacketSize > udpMaxPayloadSize { + return fmt.Errorf("MaxPacketSize must be less than %d", udpMaxPayloadSize) + } + if len(s.AuthMethods) == 0 { + // disable VerifyMethodDigestSHA256 unless explicitly set + // since it prevents FFmpeg from authenticating + s.AuthMethods = []auth.VerifyMethod{auth.VerifyMethodBasic, auth.VerifyMethodDigestMD5} + } + + // system functions + if s.Listen == nil { + s.Listen = net.Listen + } + if s.ListenPacket == nil { + s.ListenPacket = net.ListenPacket + } + + // private + if s.timeNow == nil { + s.timeNow = time.Now + } + if s.senderReportPeriod == 0 { + s.senderReportPeriod = 10 * time.Second + } + if s.receiverReportPeriod == 0 { + s.receiverReportPeriod = 10 * time.Second + } + if s.sessionTimeout == 0 { + s.sessionTimeout = 1 * 60 * time.Second + } + if s.checkStreamPeriod == 0 { + s.checkStreamPeriod = 1 * time.Second + } + + if s.TLSConfig != nil && s.UDPRTPAddress != "" { + return fmt.Errorf("TLS can't be used with UDP") + } + + if s.TLSConfig != nil && s.MulticastIPRange != "" { + return fmt.Errorf("TLS can't be used with UDP-multicast") + } + + if s.RTSPAddress == "" { + return fmt.Errorf("RTSPAddress not provided") + } + + if (s.UDPRTPAddress != "" && s.UDPRTCPAddress == "") || + (s.UDPRTPAddress == "" && s.UDPRTCPAddress != "") { + return fmt.Errorf("UDPRTPAddress and UDPRTCPAddress must be used together") + } + + if s.UDPRTPAddress != "" { + rtpPort, err := extractPort(s.UDPRTPAddress) + if err != nil { + return err + } + + rtcpPort, err := extractPort(s.UDPRTCPAddress) + if err != nil { + return err + } + + if (rtpPort % 2) != 0 { + return fmt.Errorf("RTP port must be even") + } + + if rtcpPort != (rtpPort + 1) { + return fmt.Errorf("RTP and RTCP ports must be consecutive") + } + + s.udpRTPListener = &serverUDPListener{ + listenPacket: s.ListenPacket, + writeTimeout: s.WriteTimeout, + multicastEnable: false, + address: s.UDPRTPAddress, + } + err = s.udpRTPListener.initialize() + if err != nil { + return err + } + + s.udpRTCPListener = &serverUDPListener{ + listenPacket: s.ListenPacket, + writeTimeout: s.WriteTimeout, + multicastEnable: false, + address: s.UDPRTCPAddress, + } + err = s.udpRTCPListener.initialize() + if err != nil { + s.udpRTPListener.close() + return err + } + } + + if s.MulticastIPRange != "" && (s.MulticastRTPPort == 0 || s.MulticastRTCPPort == 0) || + (s.MulticastRTPPort != 0 && (s.MulticastRTCPPort == 0 || s.MulticastIPRange == "")) || + s.MulticastRTCPPort != 0 && (s.MulticastRTPPort == 0 || s.MulticastIPRange == "") { + if s.udpRTPListener != nil { + s.udpRTPListener.close() + } + if s.udpRTCPListener != nil { + s.udpRTCPListener.close() + } + return fmt.Errorf("MulticastIPRange, MulticastRTPPort and MulticastRTCPPort must be used together") + } + + if s.MulticastIPRange != "" { + if (s.MulticastRTPPort % 2) != 0 { + if s.udpRTPListener != nil { + s.udpRTPListener.close() + } + if s.udpRTCPListener != nil { + s.udpRTCPListener.close() + } + return fmt.Errorf("RTP port must be even") + } + + if s.MulticastRTCPPort != (s.MulticastRTPPort + 1) { + if s.udpRTPListener != nil { + s.udpRTPListener.close() + } + if s.udpRTCPListener != nil { + s.udpRTCPListener.close() + } + return fmt.Errorf("RTP and RTCP ports must be consecutive") + } + + var err error + _, s.multicastNet, err = net.ParseCIDR(s.MulticastIPRange) + if err != nil { + if s.udpRTPListener != nil { + s.udpRTPListener.close() + } + if s.udpRTCPListener != nil { + s.udpRTCPListener.close() + } + return err + } + + s.multicastNextIP = s.multicastNet.IP + } + + s.ctx, s.ctxCancel = context.WithCancel(context.Background()) + + s.sessions = make(map[string]*ServerSession) + s.conns = make(map[*ServerConn]struct{}) + s.chNewConn = make(chan net.Conn) + s.chAcceptErr = make(chan error) + s.chCloseConn = make(chan *ServerConn) + s.chHandleRequest = make(chan sessionRequestReq) + s.chCloseSession = make(chan *ServerSession) + s.chGetMulticastIP = make(chan chGetMulticastIPReq) + + s.tcpListener = &serverTCPListener{ + s: s, + } + err := s.tcpListener.initialize() + if err != nil { + if s.udpRTPListener != nil { + s.udpRTPListener.close() + } + if s.udpRTCPListener != nil { + s.udpRTCPListener.close() + } + s.ctxCancel() + return err + } + + s.wg.Add(1) + go s.run() + + return nil +} + +// Close closes all the server resources and waits for them to close. +func (s *Server) Close() { + s.ctxCancel() + s.wg.Wait() +} + +// Wait waits until all server resources are closed. +// This can happen when a fatal error occurs or when Close() is called. +func (s *Server) Wait() error { + s.wg.Wait() + return s.closeError +} + +func (s *Server) run() { + defer s.wg.Done() + + s.closeError = s.runInner() + + s.ctxCancel() + + if s.udpRTCPListener != nil { + s.udpRTCPListener.close() + } + + if s.udpRTPListener != nil { + s.udpRTPListener.close() + } + + s.tcpListener.close() +} + +func (s *Server) runInner() error { + for { + select { + case err := <-s.chAcceptErr: + return err + + case nconn := <-s.chNewConn: + sc := &ServerConn{ + s: s, + nconn: nconn, + } + sc.initialize() + s.conns[sc] = struct{}{} + + case sc := <-s.chCloseConn: + if _, ok := s.conns[sc]; !ok { + continue + } + delete(s.conns, sc) + sc.Close() + + case req := <-s.chHandleRequest: + if ss, ok := s.sessions[req.id]; ok { + if !req.sc.ip().Equal(ss.author.ip()) || + req.sc.zone() != ss.author.zone() { + req.res <- sessionRequestRes{ + res: &base.Response{ + StatusCode: base.StatusBadRequest, + }, + err: liberrors.ErrServerCannotUseSessionCreatedByOtherIP{}, + } + continue + } + + select { + case ss.chHandleRequest <- req: + case <-ss.ctx.Done(): + req.res <- sessionRequestRes{ + res: &base.Response{ + StatusCode: base.StatusBadRequest, + }, + err: liberrors.ErrServerTerminated{}, + } + } + } else { + if !req.create { + req.res <- sessionRequestRes{ + res: &base.Response{ + StatusCode: base.StatusSessionNotFound, + }, + err: liberrors.ErrServerSessionNotFound{}, + } + continue + } + + ss := &ServerSession{ + s: s, + author: req.sc, + } + ss.initialize() + s.sessions[ss.secretID] = ss + + select { + case ss.chHandleRequest <- req: + case <-ss.ctx.Done(): + req.res <- sessionRequestRes{ + res: &base.Response{ + StatusCode: base.StatusBadRequest, + }, + err: liberrors.ErrServerTerminated{}, + } + } + } + + case ss := <-s.chCloseSession: + if sss, ok := s.sessions[ss.secretID]; !ok || sss != ss { + continue + } + delete(s.sessions, ss.secretID) + ss.Close() + + case req := <-s.chGetMulticastIP: + ip32 := uint32(s.multicastNextIP[0])<<24 | uint32(s.multicastNextIP[1])<<16 | + uint32(s.multicastNextIP[2])<<8 | uint32(s.multicastNextIP[3]) + mask := uint32(s.multicastNet.Mask[0])<<24 | uint32(s.multicastNet.Mask[1])<<16 | + uint32(s.multicastNet.Mask[2])<<8 | uint32(s.multicastNet.Mask[3]) + ip32 = (ip32 & mask) | ((ip32 + 1) & ^mask) + ip := make(net.IP, 4) + ip[0] = byte(ip32 >> 24) + ip[1] = byte(ip32 >> 16) + ip[2] = byte(ip32 >> 8) + ip[3] = byte(ip32) + s.multicastNextIP = ip + req.res <- ip + + case <-s.ctx.Done(): + return liberrors.ErrServerTerminated{} + } + } +} + +// StartAndWait starts the server and waits until a fatal error. +func (s *Server) StartAndWait() error { + err := s.Start() + if err != nil { + return err + } + defer s.Close() + + return s.Wait() +} + +func (s *Server) getMulticastIP() (net.IP, error) { + res := make(chan net.IP) + select { + case s.chGetMulticastIP <- chGetMulticastIPReq{res: res}: + return <-res, nil + + case <-s.ctx.Done(): + return nil, liberrors.ErrServerTerminated{} + } +} + +func (s *Server) newConn(nconn net.Conn) { + select { + case s.chNewConn <- nconn: + case <-s.ctx.Done(): + nconn.Close() + } +} + +func (s *Server) acceptErr(err error) { + select { + case s.chAcceptErr <- err: + case <-s.ctx.Done(): + } +} + +func (s *Server) closeConn(sc *ServerConn) { + select { + case s.chCloseConn <- sc: + case <-s.ctx.Done(): + } +} + +func (s *Server) closeSession(ss *ServerSession) { + select { + case s.chCloseSession <- ss: + case <-s.ctx.Done(): + } +} + +func (s *Server) handleRequest(req sessionRequestReq) (*base.Response, *ServerSession, error) { + select { + case s.chHandleRequest <- req: + res := <-req.res + return res.res, res.ss, res.err + + case <-s.ctx.Done(): + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, req.sc.session, liberrors.ErrServerTerminated{} + } +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_conn.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_conn.go new file mode 100644 index 000000000..50f24d853 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_conn.go @@ -0,0 +1,528 @@ +package gortsplib + +import ( + "context" + "crypto/tls" + "errors" + "net" + gourl "net/url" + "strconv" + "strings" + "time" + + "github.com/bluenviron/gortsplib/v4/pkg/auth" + "github.com/bluenviron/gortsplib/v4/pkg/base" + "github.com/bluenviron/gortsplib/v4/pkg/bytecounter" + "github.com/bluenviron/gortsplib/v4/pkg/conn" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/headers" + "github.com/bluenviron/gortsplib/v4/pkg/liberrors" +) + +func getSessionID(header base.Header) string { + if h, ok := header["Session"]; ok && len(h) == 1 { + return h[0] + } + return "" +} + +func serverSideDescription(d *description.Session) *description.Session { + out := &description.Session{ + Title: d.Title, + FECGroups: d.FECGroups, + Medias: make([]*description.Media, len(d.Medias)), + } + + for i, medi := range d.Medias { + out.Medias[i] = &description.Media{ + Type: medi.Type, + ID: medi.ID, + IsBackChannel: medi.IsBackChannel, + // we have to use trackID=number in order to support clients + // like the Grandstream GXV3500. + Control: "trackID=" + strconv.FormatInt(int64(i), 10), + Formats: medi.Formats, + } + } + + return out +} + +func credentialsProvided(req *base.Request) bool { + var auth headers.Authorization + err := auth.Unmarshal(req.Header["Authorization"]) + return err == nil && auth.Username != "" +} + +type readReq struct { + req *base.Request + res chan error +} + +// ServerConn is a server-side RTSP connection. +type ServerConn struct { + s *Server + nconn net.Conn + + ctx context.Context + ctxCancel func() + userData interface{} + remoteAddr *net.TCPAddr + bc *bytecounter.ByteCounter + conn *conn.Conn + session *ServerSession + reader *serverConnReader + authNonce string + + // in + chRemoveSession chan *ServerSession + + // out + done chan struct{} +} + +func (sc *ServerConn) initialize() { + ctx, ctxCancel := context.WithCancel(sc.s.ctx) + + if sc.s.TLSConfig != nil { + sc.nconn = tls.Server(sc.nconn, sc.s.TLSConfig) + } + + sc.bc = bytecounter.New(sc.nconn, nil, nil) + sc.ctx = ctx + sc.ctxCancel = ctxCancel + sc.remoteAddr = sc.nconn.RemoteAddr().(*net.TCPAddr) + sc.chRemoveSession = make(chan *ServerSession) + sc.done = make(chan struct{}) + + sc.s.wg.Add(1) + go sc.run() +} + +// Close closes the ServerConn. +func (sc *ServerConn) Close() { + sc.ctxCancel() +} + +// NetConn returns the underlying net.Conn. +func (sc *ServerConn) NetConn() net.Conn { + return sc.nconn +} + +// BytesReceived returns the number of read bytes. +// +// Deprecated: replaced by Stats() +func (sc *ServerConn) BytesReceived() uint64 { + return sc.bc.BytesReceived() +} + +// BytesSent returns the number of written bytes. +// +// Deprecated: replaced by Stats() +func (sc *ServerConn) BytesSent() uint64 { + return sc.bc.BytesSent() +} + +// SetUserData sets some user data associated with the connection. +func (sc *ServerConn) SetUserData(v interface{}) { + sc.userData = v +} + +// UserData returns some user data associated with the connection. +func (sc *ServerConn) UserData() interface{} { + return sc.userData +} + +// Session returns associated session. +func (sc *ServerConn) Session() *ServerSession { + return sc.session +} + +// Stats returns connection statistics. +func (sc *ServerConn) Stats() *StatsConn { + return &StatsConn{ + BytesReceived: sc.bc.BytesReceived(), + BytesSent: sc.bc.BytesSent(), + } +} + +// VerifyCredentials verifies credentials provided by the user. +func (sc *ServerConn) VerifyCredentials( + req *base.Request, + expectedUser string, + expectedPass string, +) bool { + // we do not support using an empty string as user + // since it interferes with credentialsProvided() + if expectedUser == "" { + return false + } + + if sc.authNonce == "" { + n, err := auth.GenerateNonce() + if err != nil { + return false + } + sc.authNonce = n + } + + err := auth.Verify( + req, + expectedUser, + expectedPass, + sc.s.AuthMethods, + serverAuthRealm, + sc.authNonce) + + return (err == nil) +} + +func (sc *ServerConn) handleAuthError(req *base.Request, res *base.Response) error { + // if credentials have not been provided, clear error and send the WWW-Authenticate header. + if !credentialsProvided(req) { + res.Header["WWW-Authenticate"] = auth.GenerateWWWAuthenticate(sc.s.AuthMethods, serverAuthRealm, sc.authNonce) + return nil + } + + // if credentials have been provided (and are wrong), close the connection. + return liberrors.ErrServerAuth{} +} + +func (sc *ServerConn) ip() net.IP { + return sc.remoteAddr.IP +} + +func (sc *ServerConn) zone() string { + return sc.remoteAddr.Zone +} + +func (sc *ServerConn) run() { + defer sc.s.wg.Done() + defer close(sc.done) + + if h, ok := sc.s.Handler.(ServerHandlerOnConnOpen); ok { + h.OnConnOpen(&ServerHandlerOnConnOpenCtx{ + Conn: sc, + }) + } + + sc.conn = conn.NewConn(sc.bc) + sc.reader = &serverConnReader{ + sc: sc, + } + sc.reader.initialize() + + err := sc.runInner() + + sc.ctxCancel() + + sc.nconn.Close() + + if sc.reader != nil { + sc.reader.wait() + } + + if sc.session != nil { + sc.session.removeConn(sc) + } + + sc.s.closeConn(sc) + + if h, ok := sc.s.Handler.(ServerHandlerOnConnClose); ok { + h.OnConnClose(&ServerHandlerOnConnCloseCtx{ + Conn: sc, + Error: err, + }) + } +} + +func (sc *ServerConn) runInner() error { + for { + select { + case req := <-sc.reader.chRequest: + req.res <- sc.handleRequestOuter(req.req) + + case err := <-sc.reader.chError: + sc.reader = nil + return err + + case ss := <-sc.chRemoveSession: + if sc.session == ss { + sc.session = nil + } + + case <-sc.ctx.Done(): + return liberrors.ErrServerTerminated{} + } + } +} + +func (sc *ServerConn) handleRequestInner(req *base.Request) (*base.Response, error) { + if cseq, ok := req.Header["CSeq"]; !ok || len(cseq) != 1 { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, liberrors.ErrServerCSeqMissing{} + } + + if req.Method != base.Options && req.URL == nil { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, liberrors.ErrServerInvalidPath{} + } + + sxID := getSessionID(req.Header) + + var path string + var query string + + switch req.Method { + case base.Describe, base.GetParameter, base.SetParameter: + path, query = getPathAndQuery(req.URL, false) + } + + switch req.Method { + case base.Options: + if sxID != "" { + return sc.handleRequestInSession(sxID, req, false) + } + + var methods []string + if _, ok := sc.s.Handler.(ServerHandlerOnDescribe); ok { + methods = append(methods, string(base.Describe)) + } + if _, ok := sc.s.Handler.(ServerHandlerOnAnnounce); ok { + methods = append(methods, string(base.Announce)) + } + if _, ok := sc.s.Handler.(ServerHandlerOnSetup); ok { + methods = append(methods, string(base.Setup)) + } + if _, ok := sc.s.Handler.(ServerHandlerOnPlay); ok { + methods = append(methods, string(base.Play)) + } + if _, ok := sc.s.Handler.(ServerHandlerOnRecord); ok { + methods = append(methods, string(base.Record)) + } + if _, ok := sc.s.Handler.(ServerHandlerOnPause); ok { + methods = append(methods, string(base.Pause)) + } + methods = append(methods, string(base.GetParameter)) + if _, ok := sc.s.Handler.(ServerHandlerOnSetParameter); ok { + methods = append(methods, string(base.SetParameter)) + } + methods = append(methods, string(base.Teardown)) + + return &base.Response{ + StatusCode: base.StatusOK, + Header: base.Header{ + "Public": base.HeaderValue{strings.Join(methods, ", ")}, + }, + }, nil + + case base.Describe: + if h, ok := sc.s.Handler.(ServerHandlerOnDescribe); ok { + res, stream, err := h.OnDescribe(&ServerHandlerOnDescribeCtx{ + Conn: sc, + Request: req, + Path: path, + Query: query, + }) + + if res.StatusCode == base.StatusOK { + if stream == nil && len(res.Body) == 0 { + panic("stream should be not nil or response body should be nonempty when StatusCode is StatusOK") + } + + if res.Header == nil { + res.Header = make(base.Header) + } + + res.Header["Content-Base"] = base.HeaderValue{req.URL.String() + "/"} + res.Header["Content-Type"] = base.HeaderValue{"application/sdp"} + + if stream == nil { + return res, err + } + + // VLC uses multicast if the SDP contains a multicast address. + // therefore, we introduce a special query (vlcmulticast) that allows + // to return a SDP that contains a multicast address. + multicast := false + if sc.s.MulticastIPRange != "" { + if q, err2 := gourl.ParseQuery(query); err2 == nil { + if _, ok := q["vlcmulticast"]; ok { + multicast = true + } + } + } + + byts, _ := serverSideDescription(stream.Desc).Marshal(multicast) + res.Body = byts + } + + return res, err + } + + case base.Announce: + if _, ok := sc.s.Handler.(ServerHandlerOnAnnounce); ok { + return sc.handleRequestInSession(sxID, req, true) + } + + case base.Setup: + if _, ok := sc.s.Handler.(ServerHandlerOnSetup); ok { + return sc.handleRequestInSession(sxID, req, true) + } + + case base.Play: + if sxID != "" { + if _, ok := sc.s.Handler.(ServerHandlerOnPlay); ok { + return sc.handleRequestInSession(sxID, req, false) + } + } + + case base.Record: + if sxID != "" { + if _, ok := sc.s.Handler.(ServerHandlerOnRecord); ok { + return sc.handleRequestInSession(sxID, req, false) + } + } + + case base.Pause: + if sxID != "" { + if _, ok := sc.s.Handler.(ServerHandlerOnPause); ok { + return sc.handleRequestInSession(sxID, req, false) + } + } + + case base.Teardown: + if sxID != "" { + return sc.handleRequestInSession(sxID, req, false) + } + + case base.GetParameter: + if sxID != "" { + return sc.handleRequestInSession(sxID, req, false) + } + + if h, ok := sc.s.Handler.(ServerHandlerOnGetParameter); ok { + return h.OnGetParameter(&ServerHandlerOnGetParameterCtx{ + Conn: sc, + Request: req, + Path: path, + Query: query, + }) + } + + case base.SetParameter: + if sxID != "" { + return sc.handleRequestInSession(sxID, req, false) + } + + if h, ok := sc.s.Handler.(ServerHandlerOnSetParameter); ok { + return h.OnSetParameter(&ServerHandlerOnSetParameterCtx{ + Conn: sc, + Request: req, + Path: path, + Query: query, + }) + } + } + + return &base.Response{ + StatusCode: base.StatusNotImplemented, + }, nil +} + +func (sc *ServerConn) handleRequestOuter(req *base.Request) error { + if h, ok := sc.s.Handler.(ServerHandlerOnRequest); ok { + h.OnRequest(sc, req) + } + + res, err := sc.handleRequestInner(req) + + if res.Header == nil { + res.Header = make(base.Header) + } + + // handle auth errors + var eerr1 liberrors.ErrServerAuth + if errors.As(err, &eerr1) { + err = sc.handleAuthError(req, res) + } + + // add cseq + var eerr2 liberrors.ErrServerCSeqMissing + if !errors.As(err, &eerr2) { + res.Header["CSeq"] = req.Header["CSeq"] + } + + // add server + res.Header["Server"] = base.HeaderValue{serverHeader} + + if h, ok := sc.s.Handler.(ServerHandlerOnResponse); ok { + h.OnResponse(sc, res) + } + + sc.nconn.SetWriteDeadline(time.Now().Add(sc.s.WriteTimeout)) + err2 := sc.conn.WriteResponse(res) + if err == nil && err2 != nil { + err = err2 + } + + return err +} + +func (sc *ServerConn) handleRequestInSession( + sxID string, + req *base.Request, + create bool, +) (*base.Response, error) { + // handle directly in Session + if sc.session != nil { + // session ID is optional in SETUP and ANNOUNCE requests, since + // client may not have received the session ID yet due to multiple reasons: + // * requests can be retries after code 301 + // * SETUP requests comes after ANNOUNCE response, that don't contain the session ID + if sxID != "" { + // the connection can't communicate with two sessions at once. + if sxID != sc.session.secretID { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, liberrors.ErrServerLinkedToOtherSession{} + } + } + + cres := make(chan sessionRequestRes) + sreq := sessionRequestReq{ + sc: sc, + req: req, + id: sxID, + create: create, + res: cres, + } + + res, session, err := sc.session.handleRequest(sreq) + sc.session = session + return res, err + } + + // otherwise, pass through Server + cres := make(chan sessionRequestRes) + sreq := sessionRequestReq{ + sc: sc, + req: req, + id: sxID, + create: create, + res: cres, + } + + res, session, err := sc.s.handleRequest(sreq) + sc.session = session + return res, err +} + +func (sc *ServerConn) removeSession(ss *ServerSession) { + select { + case sc.chRemoveSession <- ss: + case <-sc.ctx.Done(): + } +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_conn_reader.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_conn_reader.go new file mode 100644 index 000000000..4329881a9 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_conn_reader.go @@ -0,0 +1,138 @@ +package gortsplib + +import ( + "errors" + "fmt" + "time" + + "github.com/bluenviron/gortsplib/v4/pkg/base" + "github.com/bluenviron/gortsplib/v4/pkg/liberrors" +) + +type switchReadFuncError struct { + tcp bool +} + +func (switchReadFuncError) Error() string { + return "switching read function" +} + +func isSwitchReadFuncError(err error) bool { + var eerr switchReadFuncError + return errors.As(err, &eerr) +} + +type serverConnReader struct { + sc *ServerConn + + chRequest chan readReq + chError chan error +} + +func (cr *serverConnReader) initialize() { + cr.chRequest = make(chan readReq) + cr.chError = make(chan error) + + go cr.run() +} + +func (cr *serverConnReader) wait() { + for { + select { + case <-cr.chError: + return + + case req := <-cr.chRequest: + req.res <- fmt.Errorf("terminated") + } + } +} + +func (cr *serverConnReader) run() { + readFunc := cr.readFuncStandard + + for { + err := readFunc() + + var eerr switchReadFuncError + if errors.As(err, &eerr) { + if eerr.tcp { + readFunc = cr.readFuncTCP + } else { + readFunc = cr.readFuncStandard + } + continue + } + + cr.chError <- err + break + } +} + +func (cr *serverConnReader) readFuncStandard() error { + // reset deadline + cr.sc.nconn.SetReadDeadline(time.Time{}) + + for { + what, err := cr.sc.conn.Read() + if err != nil { + return err + } + + switch what := what.(type) { + case *base.Request: + cres := make(chan error) + req := readReq{req: what, res: cres} + cr.chRequest <- req + + err := <-cres + if err != nil { + return err + } + + case *base.Response: + return liberrors.ErrServerUnexpectedResponse{} + + case *base.InterleavedFrame: + return liberrors.ErrServerUnexpectedFrame{} + } + } +} + +func (cr *serverConnReader) readFuncTCP() error { + // reset deadline + cr.sc.nconn.SetReadDeadline(time.Time{}) + + cr.sc.session.asyncStartWriter() + + for { + if cr.sc.session.state == ServerSessionStateRecord { + cr.sc.nconn.SetReadDeadline(time.Now().Add(cr.sc.s.ReadTimeout)) + } + + what, err := cr.sc.conn.Read() + if err != nil { + return err + } + + switch what := what.(type) { + case *base.Request: + cres := make(chan error) + req := readReq{req: what, res: cres} + cr.chRequest <- req + + err := <-cres + if err != nil { + return err + } + + case *base.Response: + return liberrors.ErrServerUnexpectedResponse{} + + case *base.InterleavedFrame: + if cb, ok := cr.sc.session.tcpCallbackByChannel[what.Channel]; ok { + cb(what.Payload) + } + } + } +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_handler.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_handler.go new file mode 100644 index 000000000..389dcc166 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_handler.go @@ -0,0 +1,245 @@ +package gortsplib + +import ( + "github.com/bluenviron/gortsplib/v4/pkg/base" + "github.com/bluenviron/gortsplib/v4/pkg/description" +) + +// ServerHandler is the interface implemented by all the server handlers. +type ServerHandler interface{} + +// ServerHandlerOnConnOpenCtx is the context of OnConnOpen. +type ServerHandlerOnConnOpenCtx struct { + Conn *ServerConn +} + +// ServerHandlerOnConnOpen can be implemented by a ServerHandler. +type ServerHandlerOnConnOpen interface { + // called when a connection is opened. + OnConnOpen(*ServerHandlerOnConnOpenCtx) +} + +// ServerHandlerOnConnCloseCtx is the context of OnConnClose. +type ServerHandlerOnConnCloseCtx struct { + Conn *ServerConn + Error error +} + +// ServerHandlerOnConnClose can be implemented by a ServerHandler. +type ServerHandlerOnConnClose interface { + // called when a connection is closed. + OnConnClose(*ServerHandlerOnConnCloseCtx) +} + +// ServerHandlerOnSessionOpenCtx is the context OnSessionOpen. +type ServerHandlerOnSessionOpenCtx struct { + Session *ServerSession + Conn *ServerConn +} + +// ServerHandlerOnSessionOpen can be implemented by a ServerHandler. +type ServerHandlerOnSessionOpen interface { + // called when a session is opened. + OnSessionOpen(*ServerHandlerOnSessionOpenCtx) +} + +// ServerHandlerOnSessionCloseCtx is the context of ServerHandlerOnSessionClose. +type ServerHandlerOnSessionCloseCtx struct { + Session *ServerSession + Error error +} + +// ServerHandlerOnSessionClose can be implemented by a ServerHandler. +type ServerHandlerOnSessionClose interface { + // called when a session is closed. + OnSessionClose(*ServerHandlerOnSessionCloseCtx) +} + +// ServerHandlerOnRequest can be implemented by a ServerHandler. +type ServerHandlerOnRequest interface { + // called when receiving a request from a connection. + OnRequest(*ServerConn, *base.Request) +} + +// ServerHandlerOnResponse can be implemented by a ServerHandler. +type ServerHandlerOnResponse interface { + // called when sending a response to a connection. + OnResponse(*ServerConn, *base.Response) +} + +// ServerHandlerOnDescribeCtx is the context of OnDescribe. +type ServerHandlerOnDescribeCtx struct { + Conn *ServerConn + Request *base.Request + Path string + Query string +} + +// ServerHandlerOnDescribe can be implemented by a ServerHandler. +type ServerHandlerOnDescribe interface { + // called when receiving a DESCRIBE request. + OnDescribe(*ServerHandlerOnDescribeCtx) (*base.Response, *ServerStream, error) +} + +// ServerHandlerOnAnnounceCtx is the context of OnAnnounce. +type ServerHandlerOnAnnounceCtx struct { + Session *ServerSession + Conn *ServerConn + Request *base.Request + Path string + Query string + Description *description.Session +} + +// ServerHandlerOnAnnounce can be implemented by a ServerHandler. +type ServerHandlerOnAnnounce interface { + // called when receiving an ANNOUNCE request. + OnAnnounce(*ServerHandlerOnAnnounceCtx) (*base.Response, error) +} + +// ServerHandlerOnSetupCtx is the context of OnSetup. +type ServerHandlerOnSetupCtx struct { + Session *ServerSession + Conn *ServerConn + Request *base.Request + Path string + Query string + Transport Transport +} + +// ServerHandlerOnSetup can be implemented by a ServerHandler. +type ServerHandlerOnSetup interface { + // called when receiving a SETUP request. + // must return a Response and a stream. + // the stream is needed to + // - add the session the the stream's readers + // - send the stream SSRC to the session + OnSetup(*ServerHandlerOnSetupCtx) (*base.Response, *ServerStream, error) +} + +// ServerHandlerOnPlayCtx is the context of OnPlay. +type ServerHandlerOnPlayCtx struct { + Session *ServerSession + Conn *ServerConn + Request *base.Request + Path string + Query string +} + +// ServerHandlerOnPlay can be implemented by a ServerHandler. +type ServerHandlerOnPlay interface { + // called when receiving a PLAY request. + OnPlay(*ServerHandlerOnPlayCtx) (*base.Response, error) +} + +// ServerHandlerOnRecordCtx is the context of OnRecord. +type ServerHandlerOnRecordCtx struct { + Session *ServerSession + Conn *ServerConn + Request *base.Request + Path string + Query string +} + +// ServerHandlerOnRecord can be implemented by a ServerHandler. +type ServerHandlerOnRecord interface { + // called when receiving a RECORD request. + OnRecord(*ServerHandlerOnRecordCtx) (*base.Response, error) +} + +// ServerHandlerOnPauseCtx is the context of OnPause. +type ServerHandlerOnPauseCtx struct { + Session *ServerSession + Conn *ServerConn + Request *base.Request + Path string + Query string +} + +// ServerHandlerOnPause can be implemented by a ServerHandler. +type ServerHandlerOnPause interface { + // called when receiving a PAUSE request. + OnPause(*ServerHandlerOnPauseCtx) (*base.Response, error) +} + +// ServerHandlerOnGetParameterCtx is the context of OnGetParameter. +type ServerHandlerOnGetParameterCtx struct { + Session *ServerSession + Conn *ServerConn + Request *base.Request + Path string + Query string +} + +// ServerHandlerOnGetParameter can be implemented by a ServerHandler. +type ServerHandlerOnGetParameter interface { + // called when receiving a GET_PARAMETER request. + OnGetParameter(*ServerHandlerOnGetParameterCtx) (*base.Response, error) +} + +// ServerHandlerOnSetParameterCtx is the context of OnSetParameter. +type ServerHandlerOnSetParameterCtx struct { + Session *ServerSession + Conn *ServerConn + Request *base.Request + Path string + Query string +} + +// ServerHandlerOnSetParameter can be implemented by a ServerHandler. +type ServerHandlerOnSetParameter interface { + // called when receiving a SET_PARAMETER request. + OnSetParameter(*ServerHandlerOnSetParameterCtx) (*base.Response, error) +} + +// ServerHandlerOnPacketLostCtx is the context of OnPacketLost. +// +// Deprecated: replaced by ServerHandlerOnPacketsLostCtx +type ServerHandlerOnPacketLostCtx struct { + Session *ServerSession + Error error +} + +// ServerHandlerOnPacketLost can be implemented by a ServerHandler. +// +// Deprecated: replaced by ServerHandlerOnPacketsLost +type ServerHandlerOnPacketLost interface { + // called when the server detects lost packets. + OnPacketLost(*ServerHandlerOnPacketLostCtx) +} + +// ServerHandlerOnPacketsLostCtx is the context of OnPacketsLost. +type ServerHandlerOnPacketsLostCtx struct { + Session *ServerSession + Lost uint64 +} + +// ServerHandlerOnPacketsLost can be implemented by a ServerHandler. +type ServerHandlerOnPacketsLost interface { + // called when the server detects lost packets. + OnPacketsLost(*ServerHandlerOnPacketsLostCtx) +} + +// ServerHandlerOnDecodeErrorCtx is the context of OnDecodeError. +type ServerHandlerOnDecodeErrorCtx struct { + Session *ServerSession + Error error +} + +// ServerHandlerOnDecodeError can be implemented by a ServerHandler. +type ServerHandlerOnDecodeError interface { + // called when a non-fatal decode error occurs. + OnDecodeError(*ServerHandlerOnDecodeErrorCtx) +} + +// ServerHandlerOnStreamWriteErrorCtx is the context of OnStreamWriteError. +type ServerHandlerOnStreamWriteErrorCtx struct { + Session *ServerSession + Error error +} + +// ServerHandlerOnStreamWriteError can be implemented by a ServerHandler. +type ServerHandlerOnStreamWriteError interface { + // called when a ServerStream is unable to write packets to a session. + OnStreamWriteError(*ServerHandlerOnStreamWriteErrorCtx) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_multicast_writer.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_multicast_writer.go new file mode 100644 index 000000000..e8bb1d8ce --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_multicast_writer.go @@ -0,0 +1,90 @@ +package gortsplib + +import ( + "net" + + "github.com/bluenviron/gortsplib/v4/pkg/liberrors" +) + +type serverMulticastWriter struct { + s *Server + + rtpl *serverUDPListener + rtcpl *serverUDPListener + writer *asyncProcessor + rtpAddr *net.UDPAddr + rtcpAddr *net.UDPAddr +} + +func (h *serverMulticastWriter) initialize() error { + ip, err := h.s.getMulticastIP() + if err != nil { + return err + } + + rtpl, rtcpl, err := createUDPListenerMulticastPair( + h.s.ListenPacket, + h.s.WriteTimeout, + h.s.MulticastRTPPort, + h.s.MulticastRTCPPort, + ip, + ) + if err != nil { + return err + } + + rtpAddr := &net.UDPAddr{ + IP: rtpl.ip(), + Port: rtpl.port(), + } + + rtcpAddr := &net.UDPAddr{ + IP: rtcpl.ip(), + Port: rtcpl.port(), + } + + h.rtpl = rtpl + h.rtcpl = rtcpl + h.rtpAddr = rtpAddr + h.rtcpAddr = rtcpAddr + + h.writer = &asyncProcessor{ + bufferSize: h.s.WriteQueueSize, + } + h.writer.initialize() + h.writer.start() + + return nil +} + +func (h *serverMulticastWriter) close() { + h.rtpl.close() + h.rtcpl.close() + h.writer.close() +} + +func (h *serverMulticastWriter) ip() net.IP { + return h.rtpl.ip() +} + +func (h *serverMulticastWriter) writePacketRTP(byts []byte) error { + ok := h.writer.push(func() error { + return h.rtpl.write(byts, h.rtpAddr) + }) + if !ok { + return liberrors.ErrServerWriteQueueFull{} + } + + return nil +} + +func (h *serverMulticastWriter) writePacketRTCP(byts []byte) error { + ok := h.writer.push(func() error { + return h.rtcpl.write(byts, h.rtcpAddr) + }) + if !ok { + return liberrors.ErrServerWriteQueueFull{} + } + + return nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_session.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_session.go new file mode 100644 index 000000000..beca57a87 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_session.go @@ -0,0 +1,1593 @@ +package gortsplib + +import ( + "context" + "fmt" + "log" + "net" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/google/uuid" + "github.com/pion/rtcp" + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/base" + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/headers" + "github.com/bluenviron/gortsplib/v4/pkg/liberrors" + "github.com/bluenviron/gortsplib/v4/pkg/rtcpreceiver" + "github.com/bluenviron/gortsplib/v4/pkg/rtcpsender" + "github.com/bluenviron/gortsplib/v4/pkg/rtptime" + "github.com/bluenviron/gortsplib/v4/pkg/sdp" +) + +type readFunc func([]byte) bool + +func stringsReverseIndex(s, substr string) int { + for i := len(s) - 1 - len(substr); i >= 0; i-- { + if s[i:i+len(substr)] == substr { + return i + } + } + return -1 +} + +// used for all methods except SETUP +func getPathAndQuery(u *base.URL, isAnnounce bool) (string, string) { + if !isAnnounce { + // FFmpeg format + if strings.HasSuffix(u.RawQuery, "/") { + return u.Path, u.RawQuery[:len(u.RawQuery)-1] + } + + // GStreamer format + if len(u.Path) > 1 && strings.HasSuffix(u.Path, "/") { + return u.Path[:len(u.Path)-1], u.RawQuery + } + } + + return u.Path, u.RawQuery +} + +// used for SETUP when playing +func getPathAndQueryAndTrackID(u *base.URL) (string, string, string, error) { + // FFmpeg format + i := stringsReverseIndex(u.RawQuery, "/trackID=") + if i >= 0 { + path := u.Path + query := u.RawQuery[:i] + trackID := u.RawQuery[i+len("/trackID="):] + return path, query, trackID, nil + } + + // GStreamer format + i = stringsReverseIndex(u.Path, "/trackID=") + if i >= 0 { + path := u.Path[:i] + query := u.RawQuery + trackID := u.Path[i+len("/trackID="):] + return path, query, trackID, nil + } + + // no track ID and a trailing slash. + // this happens when trying to read a MPEG-TS stream with FFmpeg. + if strings.HasSuffix(u.RawQuery, "/") { + return u.Path, u.RawQuery[:len(u.RawQuery)-1], "0", nil + } + if len(u.Path) >= 1 && strings.HasSuffix(u.Path[1:], "/") { + return u.Path[:len(u.Path)-1], u.RawQuery, "0", nil + } + + // special case for empty path + if u.Path == "" || u.Path == "/" { + return u.Path, u.RawQuery, "0", nil + } + + // no slash at the end of the path. + return "", "", "", liberrors.ErrServerInvalidSetupPath{} +} + +// used for SETUP when recording +func findMediaByURL( + medias []*description.Media, + path string, + query string, + u *base.URL, +) *description.Media { + for _, media := range medias { + if strings.HasPrefix(media.Control, "rtsp://") || + strings.HasPrefix(media.Control, "rtsps://") { + if media.Control == u.String() { + return media + } + } else { + // FFmpeg format + u1 := &base.URL{ + Scheme: u.Scheme, + Host: u.Host, + Path: path, + RawQuery: query, + } + if query != "" { + u1.RawQuery += "/" + media.Control + } else { + u1.Path += "/" + media.Control + } + if u1.String() == u.String() { + return media + } + + // GStreamer format + u2 := &base.URL{ + Scheme: u.Scheme, + Host: u.Host, + Path: path + "/" + media.Control, + RawQuery: query, + } + if u2.String() == u.String() { + return media + } + } + } + + return nil +} + +func findMediaByTrackID(medias []*description.Media, trackID string) *description.Media { + if trackID == "" { + return medias[0] + } + + tmp, err := strconv.ParseUint(trackID, 10, 31) + if err != nil { + return nil + } + id := int(tmp) + + if len(medias) <= id { + return nil + } + + return medias[id] +} + +func findFirstSupportedTransportHeader(s *Server, tsh headers.Transports) *headers.Transport { + // Per RFC2326 section 12.39, client specifies transports in order of preference. + // Filter out the ones we don't support and then pick first supported transport. + for _, tr := range tsh { + isMulticast := tr.Delivery != nil && *tr.Delivery == headers.TransportDeliveryMulticast + if tr.Protocol == headers.TransportProtocolUDP && + ((!isMulticast && s.udpRTPListener == nil) || + (isMulticast && s.MulticastIPRange == "")) { + continue + } + return &tr + } + return nil +} + +func generateRTPInfo( + now time.Time, + setuppedMediasOrdered []*serverSessionMedia, + setuppedStream *ServerStream, + setuppedPath string, + u *base.URL, +) (headers.RTPInfo, bool) { + var ri headers.RTPInfo + + for _, sm := range setuppedMediasOrdered { + entry := setuppedStream.rtpInfoEntry(sm.media, now) + if entry == nil { + entry = &headers.RTPInfoEntry{} + } + entry.URL = (&base.URL{ + Scheme: u.Scheme, + Host: u.Host, + Path: setuppedPath + "/trackID=" + + strconv.FormatInt(int64(setuppedStream.medias[sm.media].trackID), 10), + }).String() + ri = append(ri, entry) + } + + if len(ri) == 0 { + return nil, false + } + + return ri, true +} + +// ServerSessionState is a state of a ServerSession. +type ServerSessionState int + +// states. +const ( + ServerSessionStateInitial ServerSessionState = iota + ServerSessionStatePrePlay + ServerSessionStatePlay + ServerSessionStatePreRecord + ServerSessionStateRecord +) + +// String implements fmt.Stringer. +func (s ServerSessionState) String() string { + switch s { + case ServerSessionStateInitial: + return "initial" + case ServerSessionStatePrePlay: + return "prePlay" + case ServerSessionStatePlay: + return "play" + case ServerSessionStatePreRecord: + return "preRecord" + case ServerSessionStateRecord: + return "record" + } + return "unknown" +} + +// ServerSession is a server-side RTSP session. +type ServerSession struct { + s *Server + author *ServerConn + + secretID string // must not be shared, allows to take ownership of the session + ctx context.Context + ctxCancel func() + userData interface{} + conns map[*ServerConn]struct{} + state ServerSessionState + setuppedMedias map[*description.Media]*serverSessionMedia + setuppedMediasOrdered []*serverSessionMedia + tcpCallbackByChannel map[int]readFunc + setuppedTransport *Transport + setuppedStream *ServerStream // read + setuppedPath string + setuppedQuery string + lastRequestTime time.Time + tcpConn *ServerConn + announcedDesc *description.Session // publish + udpLastPacketTime *int64 // publish + udpCheckStreamTimer *time.Timer + writer *asyncProcessor + writerMutex sync.RWMutex + timeDecoder *rtptime.GlobalDecoder2 + tcpFrame *base.InterleavedFrame + tcpBuffer []byte + + // in + chHandleRequest chan sessionRequestReq + chRemoveConn chan *ServerConn + chAsyncStartWriter chan struct{} +} + +func (ss *ServerSession) initialize() { + ctx, ctxCancel := context.WithCancel(ss.s.ctx) + + // use an UUID without dashes, since dashes confuse some clients. + secretID := strings.ReplaceAll(uuid.New().String(), "-", "") + + ss.secretID = secretID + ss.ctx = ctx + ss.ctxCancel = ctxCancel + ss.conns = make(map[*ServerConn]struct{}) + ss.lastRequestTime = ss.s.timeNow() + ss.udpCheckStreamTimer = emptyTimer() + + ss.chHandleRequest = make(chan sessionRequestReq) + ss.chRemoveConn = make(chan *ServerConn) + ss.chAsyncStartWriter = make(chan struct{}) + + ss.s.wg.Add(1) + go ss.run() +} + +// Close closes the ServerSession. +func (ss *ServerSession) Close() { + ss.ctxCancel() +} + +// BytesReceived returns the number of read bytes. +// +// Deprecated: replaced by Stats() +func (ss *ServerSession) BytesReceived() uint64 { + v := uint64(0) + for _, sm := range ss.setuppedMedias { + v += atomic.LoadUint64(sm.bytesReceived) + } + return v +} + +// BytesSent returns the number of written bytes. +// +// Deprecated: replaced by Stats() +func (ss *ServerSession) BytesSent() uint64 { + v := uint64(0) + for _, sm := range ss.setuppedMedias { + v += atomic.LoadUint64(sm.bytesSent) + } + return v +} + +// State returns the state of the session. +func (ss *ServerSession) State() ServerSessionState { + return ss.state +} + +// SetuppedTransport returns the transport negotiated during SETUP. +func (ss *ServerSession) SetuppedTransport() *Transport { + return ss.setuppedTransport +} + +// SetuppedStream returns the stream associated with the session. +func (ss *ServerSession) SetuppedStream() *ServerStream { + return ss.setuppedStream +} + +// SetuppedPath returns the path sent during SETUP or ANNOUNCE. +func (ss *ServerSession) SetuppedPath() string { + return ss.setuppedPath +} + +// SetuppedQuery returns the query sent during SETUP or ANNOUNCE. +func (ss *ServerSession) SetuppedQuery() string { + return ss.setuppedQuery +} + +// AnnouncedDescription returns the announced stream description. +func (ss *ServerSession) AnnouncedDescription() *description.Session { + return ss.announcedDesc +} + +// SetuppedMedias returns the setupped medias. +func (ss *ServerSession) SetuppedMedias() []*description.Media { + ret := make([]*description.Media, len(ss.setuppedMedias)) + for i, sm := range ss.setuppedMediasOrdered { + ret[i] = sm.media + } + return ret +} + +// SetUserData sets some user data associated with the session. +func (ss *ServerSession) SetUserData(v interface{}) { + ss.userData = v +} + +// UserData returns some user data associated with the session. +func (ss *ServerSession) UserData() interface{} { + return ss.userData +} + +// Stats returns server session statistics. +func (ss *ServerSession) Stats() *StatsSession { + return &StatsSession{ + BytesReceived: func() uint64 { + v := uint64(0) + for _, sm := range ss.setuppedMedias { + v += atomic.LoadUint64(sm.bytesReceived) + } + return v + }(), + BytesSent: func() uint64 { + v := uint64(0) + for _, sm := range ss.setuppedMedias { + v += atomic.LoadUint64(sm.bytesSent) + } + return v + }(), + RTPPacketsReceived: func() uint64 { + v := uint64(0) + for _, sm := range ss.setuppedMedias { + for _, f := range sm.formats { + v += atomic.LoadUint64(f.rtpPacketsReceived) + } + } + return v + }(), + RTPPacketsSent: func() uint64 { + v := uint64(0) + for _, sm := range ss.setuppedMedias { + for _, f := range sm.formats { + v += atomic.LoadUint64(f.rtpPacketsSent) + } + } + return v + }(), + RTPPacketsLost: func() uint64 { + v := uint64(0) + for _, sm := range ss.setuppedMedias { + for _, f := range sm.formats { + v += atomic.LoadUint64(f.rtpPacketsLost) + } + } + return v + }(), + RTPPacketsInError: func() uint64 { + v := uint64(0) + for _, sm := range ss.setuppedMedias { + v += atomic.LoadUint64(sm.rtpPacketsInError) + } + return v + }(), + RTPPacketsJitter: func() float64 { + v := float64(0) + n := float64(0) + for _, sm := range ss.setuppedMedias { + for _, fo := range sm.formats { + if fo.rtcpReceiver != nil { + stats := fo.rtcpReceiver.Stats() + if stats != nil { + v += stats.Jitter + n++ + } + } + } + } + if n != 0 { + return v / n + } + return 0 + }(), + RTCPPacketsReceived: func() uint64 { + v := uint64(0) + for _, sm := range ss.setuppedMedias { + v += atomic.LoadUint64(sm.rtcpPacketsReceived) + } + return v + }(), + RTCPPacketsSent: func() uint64 { + v := uint64(0) + for _, sm := range ss.setuppedMedias { + v += atomic.LoadUint64(sm.rtcpPacketsSent) + } + return v + }(), + RTCPPacketsInError: func() uint64 { + v := uint64(0) + for _, sm := range ss.setuppedMedias { + v += atomic.LoadUint64(sm.rtcpPacketsInError) + } + return v + }(), + Medias: func() map[*description.Media]StatsSessionMedia { //nolint:dupl + ret := make(map[*description.Media]StatsSessionMedia, len(ss.setuppedMedias)) + + for med, sm := range ss.setuppedMedias { + ret[med] = StatsSessionMedia{ + BytesReceived: atomic.LoadUint64(sm.bytesReceived), + BytesSent: atomic.LoadUint64(sm.bytesSent), + RTPPacketsInError: atomic.LoadUint64(sm.rtpPacketsInError), + RTCPPacketsReceived: atomic.LoadUint64(sm.rtcpPacketsReceived), + RTCPPacketsSent: atomic.LoadUint64(sm.rtcpPacketsSent), + RTCPPacketsInError: atomic.LoadUint64(sm.rtcpPacketsInError), + Formats: func() map[format.Format]StatsSessionFormat { + ret := make(map[format.Format]StatsSessionFormat, len(sm.formats)) + + for _, fo := range sm.formats { + recvStats := func() *rtcpreceiver.Stats { + if fo.rtcpReceiver != nil { + return fo.rtcpReceiver.Stats() + } + return nil + }() + rtcpSender := func() *rtcpsender.RTCPSender { + if ss.setuppedStream != nil { + return ss.setuppedStream.medias[med].formats[fo.format.PayloadType()].rtcpSender + } + return nil + }() + sentStats := func() *rtcpsender.Stats { + if rtcpSender != nil { + return rtcpSender.Stats() + } + return nil + }() + + ret[fo.format] = StatsSessionFormat{ //nolint:dupl + RTPPacketsReceived: atomic.LoadUint64(fo.rtpPacketsReceived), + RTPPacketsSent: atomic.LoadUint64(fo.rtpPacketsSent), + RTPPacketsLost: atomic.LoadUint64(fo.rtpPacketsLost), + LocalSSRC: func() uint32 { + if fo.rtcpReceiver != nil { + return *fo.rtcpReceiver.LocalSSRC + } + if sentStats != nil { + return sentStats.LocalSSRC + } + return 0 + }(), + RemoteSSRC: func() uint32 { + if recvStats != nil { + return recvStats.RemoteSSRC + } + return 0 + }(), + RTPPacketsLastSequenceNumber: func() uint16 { + if recvStats != nil { + return recvStats.LastSequenceNumber + } + if sentStats != nil { + return sentStats.LastSequenceNumber + } + return 0 + }(), + RTPPacketsLastRTP: func() uint32 { + if recvStats != nil { + return recvStats.LastRTP + } + if sentStats != nil { + return sentStats.LastRTP + } + return 0 + }(), + RTPPacketsLastNTP: func() time.Time { + if recvStats != nil { + return recvStats.LastNTP + } + if sentStats != nil { + return sentStats.LastNTP + } + return time.Time{} + }(), + RTPPacketsJitter: func() float64 { + if recvStats != nil { + return recvStats.Jitter + } + return 0 + }(), + } + } + + return ret + }(), + } + } + + return ret + }(), + } +} + +func (ss *ServerSession) onStreamWriteError(err error) { + if h, ok := ss.s.Handler.(ServerHandlerOnStreamWriteError); ok { + h.OnStreamWriteError(&ServerHandlerOnStreamWriteErrorCtx{ + Session: ss, + Error: err, + }) + } else { + log.Println(err.Error()) + } +} + +func (ss *ServerSession) checkState(allowed map[ServerSessionState]struct{}) error { + if _, ok := allowed[ss.state]; ok { + return nil + } + + allowedList := make([]fmt.Stringer, len(allowed)) + i := 0 + for a := range allowed { + allowedList[i] = a + i++ + } + return liberrors.ErrServerInvalidState{AllowedList: allowedList, State: ss.state} +} + +func (ss *ServerSession) createWriter() { + ss.writerMutex.Lock() + + ss.writer = &asyncProcessor{ + bufferSize: func() int { + if ss.state == ServerSessionStatePrePlay { + return ss.s.WriteQueueSize + } + + // when recording, writeBuffer is only used to send RTCP receiver reports, + // that are much smaller than RTP packets and are sent at a fixed interval. + // decrease RAM consumption by allocating less buffers. + return 8 + }(), + } + + ss.writer.initialize() + + ss.writerMutex.Unlock() +} + +func (ss *ServerSession) startWriter() { + ss.writer.start() +} + +func (ss *ServerSession) destroyWriter() { + ss.writer.close() + + ss.writerMutex.Lock() + ss.writer = nil + ss.writerMutex.Unlock() +} + +func (ss *ServerSession) run() { + defer ss.s.wg.Done() + + if h, ok := ss.s.Handler.(ServerHandlerOnSessionOpen); ok { + h.OnSessionOpen(&ServerHandlerOnSessionOpenCtx{ + Session: ss, + Conn: ss.author, + }) + } + + err := ss.runInner() + + ss.ctxCancel() + + // close all associated connections, both UDP and TCP + // except for the ones that called TEARDOWN + // (that are detached from the session just after the request) + for sc := range ss.conns { + sc.Close() + + // make sure that OnFrame() is never called after OnSessionClose() + <-sc.done + + sc.removeSession(ss) + } + + if ss.setuppedStream != nil { + ss.setuppedStream.readerSetInactive(ss) + ss.setuppedStream.readerRemove(ss) + } + + for _, sm := range ss.setuppedMedias { + sm.stop() + } + + if ss.writer != nil { + ss.destroyWriter() + } + + ss.s.closeSession(ss) + + if h, ok := ss.s.Handler.(ServerHandlerOnSessionClose); ok { + h.OnSessionClose(&ServerHandlerOnSessionCloseCtx{ + Session: ss, + Error: err, + }) + } +} + +func (ss *ServerSession) runInner() error { + for { + chWriterError := func() chan struct{} { + if ss.writer != nil { + return ss.writer.chStopped + } + return nil + }() + + select { + case req := <-ss.chHandleRequest: + ss.lastRequestTime = ss.s.timeNow() + + if _, ok := ss.conns[req.sc]; !ok { + ss.conns[req.sc] = struct{}{} + } + + res, err := ss.handleRequestInner(req.sc, req.req) + + returnedSession := ss + + if err == nil || isSwitchReadFuncError(err) { + // ANNOUNCE responses don't contain the session header. + if req.req.Method != base.Announce && + req.req.Method != base.Teardown { + if res.Header == nil { + res.Header = make(base.Header) + } + + res.Header["Session"] = headers.Session{ + Session: ss.secretID, + Timeout: func() *uint { + // timeout controls the sending of RTCP keepalives. + // these are needed only when the client is playing + // and transport is UDP or UDP-multicast. + if (ss.state == ServerSessionStatePrePlay || + ss.state == ServerSessionStatePlay) && + (*ss.setuppedTransport == TransportUDP || + *ss.setuppedTransport == TransportUDPMulticast) { + v := uint(ss.s.sessionTimeout / time.Second) + return &v + } + return nil + }(), + }.Marshal() + } + + // after a TEARDOWN, session must be unpaired with the connection + if req.req.Method == base.Teardown { + delete(ss.conns, req.sc) + returnedSession = nil + } + } + + savedMethod := req.req.Method + + req.res <- sessionRequestRes{ + res: res, + err: err, + ss: returnedSession, + } + + if (err == nil || isSwitchReadFuncError(err)) && savedMethod == base.Teardown { + return liberrors.ErrServerSessionTornDown{Author: req.sc.NetConn().RemoteAddr()} + } + + case sc := <-ss.chRemoveConn: + delete(ss.conns, sc) + + // if session is not in state RECORD or PLAY, or transport is TCP, + // and there are no associated connections, + // close the session. + if ((ss.state != ServerSessionStateRecord && + ss.state != ServerSessionStatePlay) || + *ss.setuppedTransport == TransportTCP) && + len(ss.conns) == 0 { + return liberrors.ErrServerSessionNotInUse{} + } + + case <-ss.chAsyncStartWriter: + if (ss.state == ServerSessionStateRecord || + ss.state == ServerSessionStatePlay) && + *ss.setuppedTransport == TransportTCP { + ss.startWriter() + } + + case <-ss.udpCheckStreamTimer.C: + now := ss.s.timeNow() + + lft := atomic.LoadInt64(ss.udpLastPacketTime) + + // in case of RECORD, timeout happens when no RTP or RTCP packets are being received + if ss.state == ServerSessionStateRecord { + if now.Sub(time.Unix(lft, 0)) >= ss.s.ReadTimeout { + return liberrors.ErrServerSessionTimedOut{} + } + + // in case of PLAY, timeout happens when no RTSP keepalives and no RTCP packets are being received + } else if now.Sub(ss.lastRequestTime) >= ss.s.sessionTimeout && + now.Sub(time.Unix(lft, 0)) >= ss.s.sessionTimeout { + return liberrors.ErrServerSessionTimedOut{} + } + + ss.udpCheckStreamTimer = time.NewTimer(ss.s.checkStreamPeriod) + + case <-chWriterError: + return ss.writer.stopError + + case <-ss.ctx.Done(): + return liberrors.ErrServerTerminated{} + } + } +} + +func (ss *ServerSession) handleRequestInner(sc *ServerConn, req *base.Request) (*base.Response, error) { + if ss.tcpConn != nil && sc != ss.tcpConn { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, liberrors.ErrServerSessionLinkedToOtherConn{} + } + + var path string + var query string + + switch req.Method { + case base.Announce: + path, query = getPathAndQuery(req.URL, true) + case base.Pause, base.GetParameter, base.SetParameter, base.Play, base.Record: + path, query = getPathAndQuery(req.URL, false) + } + + switch req.Method { + case base.Options: + var methods []string + if _, ok := sc.s.Handler.(ServerHandlerOnDescribe); ok { + methods = append(methods, string(base.Describe)) + } + if _, ok := sc.s.Handler.(ServerHandlerOnAnnounce); ok { + methods = append(methods, string(base.Announce)) + } + if _, ok := sc.s.Handler.(ServerHandlerOnSetup); ok { + methods = append(methods, string(base.Setup)) + } + if _, ok := sc.s.Handler.(ServerHandlerOnPlay); ok { + methods = append(methods, string(base.Play)) + } + if _, ok := sc.s.Handler.(ServerHandlerOnRecord); ok { + methods = append(methods, string(base.Record)) + } + if _, ok := sc.s.Handler.(ServerHandlerOnPause); ok { + methods = append(methods, string(base.Pause)) + } + methods = append(methods, string(base.GetParameter)) + if _, ok := sc.s.Handler.(ServerHandlerOnSetParameter); ok { + methods = append(methods, string(base.SetParameter)) + } + methods = append(methods, string(base.Teardown)) + + return &base.Response{ + StatusCode: base.StatusOK, + Header: base.Header{ + "Public": base.HeaderValue{strings.Join(methods, ", ")}, + }, + }, nil + + case base.Announce: + err := ss.checkState(map[ServerSessionState]struct{}{ + ServerSessionStateInitial: {}, + }) + if err != nil { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, err + } + + ct, ok := req.Header["Content-Type"] + if !ok || len(ct) != 1 { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, liberrors.ErrServerContentTypeMissing{} + } + + if ct[0] != "application/sdp" { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, liberrors.ErrServerContentTypeUnsupported{CT: ct} + } + + var ssd sdp.SessionDescription + err = ssd.Unmarshal(req.Body) + if err != nil { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, liberrors.ErrServerSDPInvalid{Err: err} + } + + var desc description.Session + err = desc.Unmarshal(&ssd) + if err != nil { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, liberrors.ErrServerSDPInvalid{Err: err} + } + + res, err := ss.s.Handler.(ServerHandlerOnAnnounce).OnAnnounce(&ServerHandlerOnAnnounceCtx{ + Session: ss, + Conn: sc, + Request: req, + Path: path, + Query: query, + Description: &desc, + }) + + if res.StatusCode == base.StatusOK { + ss.state = ServerSessionStatePreRecord + ss.setuppedPath = path + ss.setuppedQuery = query + ss.announcedDesc = &desc + } + + return res, err + + case base.Setup: + err := ss.checkState(map[ServerSessionState]struct{}{ + ServerSessionStateInitial: {}, + ServerSessionStatePrePlay: {}, + ServerSessionStatePreRecord: {}, + }) + if err != nil { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, err + } + + var transportHeaders headers.Transports + err = transportHeaders.Unmarshal(req.Header["Transport"]) + if err != nil { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, liberrors.ErrServerTransportHeaderInvalid{Err: err} + } + + inTH := findFirstSupportedTransportHeader(ss.s, transportHeaders) + if inTH == nil { + return &base.Response{ + StatusCode: base.StatusUnsupportedTransport, + }, nil + } + + var trackID string + + switch ss.state { + case ServerSessionStateInitial, ServerSessionStatePrePlay: // play + path, query, trackID, err = getPathAndQueryAndTrackID(req.URL) + if err != nil { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, err + } + + if ss.state == ServerSessionStatePrePlay && path != ss.setuppedPath { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, liberrors.ErrServerMediasDifferentPaths{} + } + + default: // record + path = ss.setuppedPath + query = ss.setuppedQuery + } + + var transport Transport + + if inTH.Protocol == headers.TransportProtocolUDP { + if inTH.Delivery != nil && *inTH.Delivery == headers.TransportDeliveryMulticast { + transport = TransportUDPMulticast + } else { + transport = TransportUDP + } + } else { + transport = TransportTCP + } + + if ss.setuppedTransport != nil && *ss.setuppedTransport != transport { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, liberrors.ErrServerMediasDifferentProtocols{} + } + + switch transport { + case TransportUDP: + if inTH.ClientPorts == nil { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, liberrors.ErrServerTransportHeaderNoClientPorts{} + } + + case TransportTCP: + if inTH.InterleavedIDs != nil { + if (inTH.InterleavedIDs[0] + 1) != inTH.InterleavedIDs[1] { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, liberrors.ErrServerTransportHeaderInvalidInterleavedIDs{} + } + + if ss.isChannelPairInUse(inTH.InterleavedIDs[0]) { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, liberrors.ErrServerTransportHeaderInterleavedIDsInUse{} + } + } + } + + switch ss.state { + case ServerSessionStateInitial, ServerSessionStatePrePlay: // play + if inTH.Mode != nil && *inTH.Mode != headers.TransportModePlay { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, liberrors.ErrServerTransportHeaderInvalidMode{Mode: inTH.Mode} + } + + default: // record + if transport == TransportUDPMulticast { + return &base.Response{ + StatusCode: base.StatusUnsupportedTransport, + }, nil + } + + if inTH.Mode == nil || *inTH.Mode != headers.TransportModeRecord { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, liberrors.ErrServerTransportHeaderInvalidMode{Mode: inTH.Mode} + } + } + + res, stream, err := ss.s.Handler.(ServerHandlerOnSetup).OnSetup(&ServerHandlerOnSetupCtx{ + Session: ss, + Conn: sc, + Request: req, + Path: path, + Query: query, + Transport: transport, + }) + + // workaround to prevent a bug in rtspclientsink + // that makes impossible for the client to receive the response + // and send frames. + // this was causing problems during unit tests. + if ua, ok := req.Header["User-Agent"]; ok && len(ua) == 1 && + strings.HasPrefix(ua[0], "GStreamer") { + select { + case <-time.After(1 * time.Second): + case <-ss.ctx.Done(): + } + } + + if ss.state == ServerSessionStatePreRecord && stream != nil { + panic("stream must be nil when handling publishers") + } + + if res.StatusCode == base.StatusOK { + var medi *description.Media + + switch ss.state { + case ServerSessionStateInitial, ServerSessionStatePrePlay: // play + if stream == nil { + panic("stream cannot be nil when StatusCode is StatusOK") + } + + medi = findMediaByTrackID(stream.Desc.Medias, trackID) + default: // record + medi = findMediaByURL(ss.announcedDesc.Medias, path, query, req.URL) + } + + if medi == nil { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, liberrors.ErrServerMediaNotFound{} + } + + if _, ok := ss.setuppedMedias[medi]; ok { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, liberrors.ErrServerMediaAlreadySetup{} + } + + ss.setuppedTransport = &transport + + if ss.state == ServerSessionStateInitial { + err = stream.readerAdd(ss, + inTH.ClientPorts, + ) + if err != nil { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, err + } + + ss.state = ServerSessionStatePrePlay + ss.setuppedPath = path + ss.setuppedQuery = query + ss.setuppedStream = stream + } + + th := headers.Transport{} + + if ss.state == ServerSessionStatePrePlay { + if stream != ss.setuppedStream { + panic("stream cannot be different than the one returned in previous OnSetup call") + } + + ssrc, ok := stream.localSSRC(medi) + if ok { + th.SSRC = &ssrc + } + } + + if res.Header == nil { + res.Header = make(base.Header) + } + + sm := &serverSessionMedia{ + ss: ss, + media: medi, + onPacketRTCP: func(_ rtcp.Packet) {}, + } + sm.initialize() + + switch transport { + case TransportUDP: + sm.udpRTPReadPort = inTH.ClientPorts[0] + sm.udpRTCPReadPort = inTH.ClientPorts[1] + + sm.udpRTPWriteAddr = &net.UDPAddr{ + IP: ss.author.ip(), + Zone: ss.author.zone(), + Port: sm.udpRTPReadPort, + } + + sm.udpRTCPWriteAddr = &net.UDPAddr{ + IP: ss.author.ip(), + Zone: ss.author.zone(), + Port: sm.udpRTCPReadPort, + } + + th.Protocol = headers.TransportProtocolUDP + de := headers.TransportDeliveryUnicast + th.Delivery = &de + th.ClientPorts = inTH.ClientPorts + th.ServerPorts = &[2]int{sc.s.udpRTPListener.port(), sc.s.udpRTCPListener.port()} + + case TransportUDPMulticast: + th.Protocol = headers.TransportProtocolUDP + de := headers.TransportDeliveryMulticast + th.Delivery = &de + v := uint(127) + th.TTL = &v + d := stream.medias[medi].multicastWriter.ip() + th.Destination = &d + th.Ports = &[2]int{ss.s.MulticastRTPPort, ss.s.MulticastRTCPPort} + + default: // TCP + if inTH.InterleavedIDs != nil { + sm.tcpChannel = inTH.InterleavedIDs[0] + } else { + sm.tcpChannel = ss.findFreeChannelPair() + } + + th.Protocol = headers.TransportProtocolTCP + de := headers.TransportDeliveryUnicast + th.Delivery = &de + th.InterleavedIDs = &[2]int{sm.tcpChannel, sm.tcpChannel + 1} + } + + if ss.setuppedMedias == nil { + ss.setuppedMedias = make(map[*description.Media]*serverSessionMedia) + } + ss.setuppedMedias[medi] = sm + ss.setuppedMediasOrdered = append(ss.setuppedMediasOrdered, sm) + + res.Header["Transport"] = th.Marshal() + } + + return res, err + + case base.Play: + // play can be sent twice, allow calling it even if we're already playing + err := ss.checkState(map[ServerSessionState]struct{}{ + ServerSessionStatePrePlay: {}, + ServerSessionStatePlay: {}, + }) + if err != nil { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, err + } + + if ss.State() == ServerSessionStatePrePlay && path != ss.setuppedPath { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, liberrors.ErrServerPathHasChanged{Prev: ss.setuppedPath, Cur: path} + } + + if ss.state != ServerSessionStatePlay && + *ss.setuppedTransport != TransportUDPMulticast { + ss.createWriter() + } + + res, err := sc.s.Handler.(ServerHandlerOnPlay).OnPlay(&ServerHandlerOnPlayCtx{ + Session: ss, + Conn: sc, + Request: req, + Path: path, + Query: query, + }) + + if res.StatusCode == base.StatusOK { + if ss.state != ServerSessionStatePlay { + ss.state = ServerSessionStatePlay + + v := ss.s.timeNow().Unix() + ss.udpLastPacketTime = &v + + ss.timeDecoder = &rtptime.GlobalDecoder2{} + ss.timeDecoder.Initialize() + + for _, sm := range ss.setuppedMedias { + sm.start() + } + + if *ss.setuppedTransport == TransportTCP { + ss.tcpFrame = &base.InterleavedFrame{} + ss.tcpBuffer = make([]byte, ss.s.MaxPacketSize+4) + } + + switch *ss.setuppedTransport { + case TransportUDP: + ss.udpCheckStreamTimer = time.NewTimer(ss.s.checkStreamPeriod) + ss.startWriter() + + case TransportUDPMulticast: + ss.udpCheckStreamTimer = time.NewTimer(ss.s.checkStreamPeriod) + + default: // TCP + ss.tcpConn = sc + err = switchReadFuncError{true} + // startWriter() is called by ServerConn, through chAsyncStartWriter, + // after the response has been sent + } + + ss.setuppedStream.readerSetActive(ss) + + rtpInfo, ok := generateRTPInfo( + ss.s.timeNow(), + ss.setuppedMediasOrdered, + ss.setuppedStream, + ss.setuppedPath, + req.URL) + + if ok { + if res.Header == nil { + res.Header = make(base.Header) + } + res.Header["RTP-Info"] = rtpInfo.Marshal() + } + } + } else { + if ss.state != ServerSessionStatePlay && + *ss.setuppedTransport != TransportUDPMulticast { + ss.destroyWriter() + } + } + + return res, err + + case base.Record: + err := ss.checkState(map[ServerSessionState]struct{}{ + ServerSessionStatePreRecord: {}, + }) + if err != nil { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, err + } + + if len(ss.setuppedMedias) != len(ss.announcedDesc.Medias) { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, liberrors.ErrServerNotAllAnnouncedMediasSetup{} + } + + if path != ss.setuppedPath { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, liberrors.ErrServerPathHasChanged{Prev: ss.setuppedPath, Cur: path} + } + + ss.createWriter() + + res, err := ss.s.Handler.(ServerHandlerOnRecord).OnRecord(&ServerHandlerOnRecordCtx{ + Session: ss, + Conn: sc, + Request: req, + Path: path, + Query: query, + }) + + if res.StatusCode == base.StatusOK { + ss.state = ServerSessionStateRecord + + v := ss.s.timeNow().Unix() + ss.udpLastPacketTime = &v + + ss.timeDecoder = &rtptime.GlobalDecoder2{} + ss.timeDecoder.Initialize() + + for _, sm := range ss.setuppedMedias { + sm.start() + } + + if *ss.setuppedTransport == TransportTCP { + ss.tcpFrame = &base.InterleavedFrame{} + ss.tcpBuffer = make([]byte, ss.s.MaxPacketSize+4) + } + + switch *ss.setuppedTransport { + case TransportUDP: + ss.udpCheckStreamTimer = time.NewTimer(ss.s.checkStreamPeriod) + ss.startWriter() + + default: // TCP + ss.tcpConn = sc + err = switchReadFuncError{true} + // startWriter() is called by ServerConn, through chAsyncStartWriter, + // after the response has been sent + } + } else { + ss.destroyWriter() + } + + return res, err + + case base.Pause: + err := ss.checkState(map[ServerSessionState]struct{}{ + ServerSessionStatePrePlay: {}, + ServerSessionStatePlay: {}, + ServerSessionStatePreRecord: {}, + ServerSessionStateRecord: {}, + }) + if err != nil { + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, err + } + + res, err := ss.s.Handler.(ServerHandlerOnPause).OnPause(&ServerHandlerOnPauseCtx{ + Session: ss, + Conn: sc, + Request: req, + Path: path, + Query: query, + }) + + if res.StatusCode == base.StatusOK { + if ss.state == ServerSessionStatePlay || ss.state == ServerSessionStateRecord { + ss.destroyWriter() + + if ss.setuppedStream != nil { + ss.setuppedStream.readerSetInactive(ss) + } + + for _, sm := range ss.setuppedMedias { + sm.stop() + } + + ss.timeDecoder = nil + + switch ss.state { + case ServerSessionStatePlay: + ss.state = ServerSessionStatePrePlay + + switch *ss.setuppedTransport { + case TransportUDP: + ss.udpCheckStreamTimer = emptyTimer() + + case TransportUDPMulticast: + ss.udpCheckStreamTimer = emptyTimer() + + default: // TCP + err = switchReadFuncError{false} + ss.tcpConn = nil + } + + case ServerSessionStateRecord: + switch *ss.setuppedTransport { + case TransportUDP: + ss.udpCheckStreamTimer = emptyTimer() + + default: // TCP + err = switchReadFuncError{false} + ss.tcpConn = nil + } + + ss.state = ServerSessionStatePreRecord + } + } + } + + return res, err + + case base.Teardown: + var err error + if (ss.state == ServerSessionStatePlay || ss.state == ServerSessionStateRecord) && + *ss.setuppedTransport == TransportTCP { + err = switchReadFuncError{false} + } + + return &base.Response{ + StatusCode: base.StatusOK, + }, err + + case base.GetParameter: + if h, ok := sc.s.Handler.(ServerHandlerOnGetParameter); ok { + return h.OnGetParameter(&ServerHandlerOnGetParameterCtx{ + Session: ss, + Conn: sc, + Request: req, + Path: path, + Query: query, + }) + } + + // GET_PARAMETER is used like a ping when reading, and sometimes + // also when publishing; reply with 200 + return &base.Response{ + StatusCode: base.StatusOK, + Header: base.Header{ + "Content-Type": base.HeaderValue{"text/parameters"}, + }, + Body: []byte{}, + }, nil + + case base.SetParameter: + if h, ok := sc.s.Handler.(ServerHandlerOnSetParameter); ok { + return h.OnSetParameter(&ServerHandlerOnSetParameterCtx{ + Session: ss, + Conn: sc, + Request: req, + Path: path, + Query: query, + }) + } + } + + return &base.Response{ + StatusCode: base.StatusNotImplemented, + }, nil +} + +func (ss *ServerSession) isChannelPairInUse(channel int) bool { + for _, sm := range ss.setuppedMedias { + if (sm.tcpChannel+1) == channel || sm.tcpChannel == channel || sm.tcpChannel == (channel+1) { + return true + } + } + return false +} + +func (ss *ServerSession) findFreeChannelPair() int { + for i := 0; ; i += 2 { // prefer even channels + if !ss.isChannelPairInUse(i) { + return i + } + } +} + +// OnPacketRTPAny sets a callback that is called when a RTP packet is read from any setupped media. +func (ss *ServerSession) OnPacketRTPAny(cb OnPacketRTPAnyFunc) { + for _, sm := range ss.setuppedMedias { + cmedia := sm.media + for _, forma := range sm.media.Formats { + ss.OnPacketRTP(sm.media, forma, func(pkt *rtp.Packet) { + cb(cmedia, forma, pkt) + }) + } + } +} + +// OnPacketRTCPAny sets a callback that is called when a RTCP packet is read from any setupped media. +func (ss *ServerSession) OnPacketRTCPAny(cb OnPacketRTCPAnyFunc) { + for _, sm := range ss.setuppedMedias { + cmedia := sm.media + ss.OnPacketRTCP(sm.media, func(pkt rtcp.Packet) { + cb(cmedia, pkt) + }) + } +} + +// OnPacketRTP sets a callback that is called when a RTP packet is read. +func (ss *ServerSession) OnPacketRTP(medi *description.Media, forma format.Format, cb OnPacketRTPFunc) { + sm := ss.setuppedMedias[medi] + st := sm.formats[forma.PayloadType()] + st.onPacketRTP = cb +} + +// OnPacketRTCP sets a callback that is called when a RTCP packet is read. +func (ss *ServerSession) OnPacketRTCP(medi *description.Media, cb OnPacketRTCPFunc) { + sm := ss.setuppedMedias[medi] + sm.onPacketRTCP = cb +} + +func (ss *ServerSession) writePacketRTP(medi *description.Media, payloadType uint8, byts []byte) error { + sm := ss.setuppedMedias[medi] + sf := sm.formats[payloadType] + + ss.writerMutex.RLock() + defer ss.writerMutex.RUnlock() + + if ss.writer == nil { + return nil + } + + ok := ss.writer.push(func() error { + return sf.writePacketRTPInQueue(byts) + }) + if !ok { + return liberrors.ErrServerWriteQueueFull{} + } + + return nil +} + +// WritePacketRTP writes a RTP packet to the session. +func (ss *ServerSession) WritePacketRTP(medi *description.Media, pkt *rtp.Packet) error { + byts := make([]byte, ss.s.MaxPacketSize) + n, err := pkt.MarshalTo(byts) + if err != nil { + return err + } + byts = byts[:n] + + return ss.writePacketRTP(medi, pkt.PayloadType, byts) +} + +func (ss *ServerSession) writePacketRTCP(medi *description.Media, byts []byte) error { + sm := ss.setuppedMedias[medi] + + ss.writerMutex.RLock() + defer ss.writerMutex.RUnlock() + + if ss.writer == nil { + return nil + } + + ok := ss.writer.push(func() error { + return sm.writePacketRTCPInQueue(byts) + }) + if !ok { + return liberrors.ErrServerWriteQueueFull{} + } + + return nil +} + +// WritePacketRTCP writes a RTCP packet to the session. +func (ss *ServerSession) WritePacketRTCP(medi *description.Media, pkt rtcp.Packet) error { + byts, err := pkt.Marshal() + if err != nil { + return err + } + + return ss.writePacketRTCP(medi, byts) +} + +// PacketPTS returns the PTS of an incoming RTP packet. +// It is computed by decoding the packet timestamp and sychronizing it with other tracks. +// +// Deprecated: replaced by PacketPTS2. +func (ss *ServerSession) PacketPTS(medi *description.Media, pkt *rtp.Packet) (time.Duration, bool) { + sm := ss.setuppedMedias[medi] + sf := sm.formats[pkt.PayloadType] + + v, ok := ss.timeDecoder.Decode(sf.format, pkt) + if !ok { + return 0, false + } + + return multiplyAndDivide(time.Duration(v), time.Second, time.Duration(sf.format.ClockRate())), true +} + +// PacketPTS2 returns the PTS of an incoming RTP packet. +// It is computed by decoding the packet timestamp and sychronizing it with other tracks. +func (ss *ServerSession) PacketPTS2(medi *description.Media, pkt *rtp.Packet) (int64, bool) { + sm := ss.setuppedMedias[medi] + sf := sm.formats[pkt.PayloadType] + return ss.timeDecoder.Decode(sf.format, pkt) +} + +// PacketNTP returns the NTP timestamp of an incoming RTP packet. +// The NTP timestamp is computed from RTCP sender reports. +func (ss *ServerSession) PacketNTP(medi *description.Media, pkt *rtp.Packet) (time.Time, bool) { + sm := ss.setuppedMedias[medi] + sf := sm.formats[pkt.PayloadType] + return sf.rtcpReceiver.PacketNTP(pkt.Timestamp) +} + +func (ss *ServerSession) handleRequest(req sessionRequestReq) (*base.Response, *ServerSession, error) { + select { + case ss.chHandleRequest <- req: + res := <-req.res + return res.res, res.ss, res.err + + case <-ss.ctx.Done(): + return &base.Response{ + StatusCode: base.StatusBadRequest, + }, req.sc.session, liberrors.ErrServerTerminated{} + } +} + +func (ss *ServerSession) removeConn(sc *ServerConn) { + select { + case ss.chRemoveConn <- sc: + case <-ss.ctx.Done(): + } +} + +func (ss *ServerSession) asyncStartWriter() { + select { + case ss.chAsyncStartWriter <- struct{}{}: + case <-ss.ctx.Done(): + } +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_session_format.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_session_format.go new file mode 100644 index 000000000..865a81cd1 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_session_format.go @@ -0,0 +1,163 @@ +package gortsplib + +import ( + "log" + "sync/atomic" + "time" + + "github.com/pion/rtcp" + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/liberrors" + "github.com/bluenviron/gortsplib/v4/pkg/rtcpreceiver" + "github.com/bluenviron/gortsplib/v4/pkg/rtplossdetector" + "github.com/bluenviron/gortsplib/v4/pkg/rtpreorderer" +) + +type serverSessionFormat struct { + sm *serverSessionMedia + format format.Format + onPacketRTP OnPacketRTPFunc + + udpReorderer *rtpreorderer.Reorderer + tcpLossDetector *rtplossdetector.LossDetector + rtcpReceiver *rtcpreceiver.RTCPReceiver + writePacketRTPInQueue func([]byte) error + rtpPacketsReceived *uint64 + rtpPacketsSent *uint64 + rtpPacketsLost *uint64 +} + +func (sf *serverSessionFormat) initialize() { + sf.rtpPacketsReceived = new(uint64) + sf.rtpPacketsSent = new(uint64) + sf.rtpPacketsLost = new(uint64) +} + +func (sf *serverSessionFormat) start() { + switch *sf.sm.ss.setuppedTransport { + case TransportUDP, TransportUDPMulticast: + sf.writePacketRTPInQueue = sf.writePacketRTPInQueueUDP + + default: + sf.writePacketRTPInQueue = sf.writePacketRTPInQueueTCP + } + + if sf.sm.ss.state != ServerSessionStatePlay { + if *sf.sm.ss.setuppedTransport == TransportUDP || *sf.sm.ss.setuppedTransport == TransportUDPMulticast { + sf.udpReorderer = &rtpreorderer.Reorderer{} + sf.udpReorderer.Initialize() + } else { + sf.tcpLossDetector = &rtplossdetector.LossDetector{} + } + + sf.rtcpReceiver = &rtcpreceiver.RTCPReceiver{ + ClockRate: sf.format.ClockRate(), + Period: sf.sm.ss.s.receiverReportPeriod, + TimeNow: sf.sm.ss.s.timeNow, + WritePacketRTCP: func(pkt rtcp.Packet) { + if *sf.sm.ss.setuppedTransport == TransportUDP || *sf.sm.ss.setuppedTransport == TransportUDPMulticast { + sf.sm.ss.WritePacketRTCP(sf.sm.media, pkt) //nolint:errcheck + } + }, + } + err := sf.rtcpReceiver.Initialize() + if err != nil { + panic(err) + } + } +} + +func (sf *serverSessionFormat) stop() { + if sf.rtcpReceiver != nil { + sf.rtcpReceiver.Close() + sf.rtcpReceiver = nil + } +} + +func (sf *serverSessionFormat) readPacketRTPUDP(pkt *rtp.Packet, now time.Time) { + packets, lost := sf.udpReorderer.Process(pkt) + if lost != 0 { + sf.onPacketRTPLost(uint64(lost)) + // do not return + } + + for _, pkt := range packets { + sf.handlePacketRTP(pkt, now) + } +} + +func (sf *serverSessionFormat) readPacketRTPTCP(pkt *rtp.Packet) { + lost := sf.tcpLossDetector.Process(pkt) + if lost != 0 { + sf.onPacketRTPLost(uint64(lost)) + // do not return + } + + now := sf.sm.ss.s.timeNow() + + sf.handlePacketRTP(pkt, now) +} + +func (sf *serverSessionFormat) handlePacketRTP(pkt *rtp.Packet, now time.Time) { + err := sf.rtcpReceiver.ProcessPacket(pkt, now, sf.format.PTSEqualsDTS(pkt)) + if err != nil { + sf.sm.onPacketRTPDecodeError(err) + return + } + + atomic.AddUint64(sf.rtpPacketsReceived, 1) + + sf.onPacketRTP(pkt) +} + +func (sf *serverSessionFormat) onPacketRTPLost(lost uint64) { + atomic.AddUint64(sf.rtpPacketsLost, lost) + + if h, ok := sf.sm.ss.s.Handler.(ServerHandlerOnPacketsLost); ok { + h.OnPacketsLost(&ServerHandlerOnPacketsLostCtx{ + Session: sf.sm.ss, + Lost: lost, + }) + } else if h, ok := sf.sm.ss.s.Handler.(ServerHandlerOnPacketLost); ok { + h.OnPacketLost(&ServerHandlerOnPacketLostCtx{ + Session: sf.sm.ss, + Error: liberrors.ErrServerRTPPacketsLost{Lost: uint(lost)}, //nolint:staticcheck + }) + } else { + log.Printf("%d RTP %s lost", + lost, + func() string { + if lost == 1 { + return "packet" + } + return "packets" + }()) + } +} + +func (sf *serverSessionFormat) writePacketRTPInQueueUDP(payload []byte) error { + err := sf.sm.ss.s.udpRTPListener.write(payload, sf.sm.udpRTPWriteAddr) + if err != nil { + return err + } + + atomic.AddUint64(sf.sm.bytesSent, uint64(len(payload))) + atomic.AddUint64(sf.rtpPacketsSent, 1) + return nil +} + +func (sf *serverSessionFormat) writePacketRTPInQueueTCP(payload []byte) error { + sf.sm.ss.tcpFrame.Channel = sf.sm.tcpChannel + sf.sm.ss.tcpFrame.Payload = payload + sf.sm.ss.tcpConn.nconn.SetWriteDeadline(time.Now().Add(sf.sm.ss.s.WriteTimeout)) + err := sf.sm.ss.tcpConn.conn.WriteInterleavedFrame(sf.sm.ss.tcpFrame, sf.sm.ss.tcpBuffer) + if err != nil { + return err + } + + atomic.AddUint64(sf.sm.bytesSent, uint64(len(payload))) + atomic.AddUint64(sf.rtpPacketsSent, 1) + return nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_session_media.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_session_media.go new file mode 100644 index 000000000..dfe382738 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_session_media.go @@ -0,0 +1,342 @@ +package gortsplib + +import ( + "log" + "net" + "sync/atomic" + "time" + + "github.com/pion/rtcp" + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/liberrors" +) + +type serverSessionMedia struct { + ss *ServerSession + media *description.Media + onPacketRTCP OnPacketRTCPFunc + + tcpChannel int + udpRTPReadPort int + udpRTPWriteAddr *net.UDPAddr + udpRTCPReadPort int + udpRTCPWriteAddr *net.UDPAddr + formats map[uint8]*serverSessionFormat // record only + writePacketRTCPInQueue func([]byte) error + bytesReceived *uint64 + bytesSent *uint64 + rtpPacketsInError *uint64 + rtcpPacketsReceived *uint64 + rtcpPacketsSent *uint64 + rtcpPacketsInError *uint64 +} + +func (sm *serverSessionMedia) initialize() { + sm.bytesReceived = new(uint64) + sm.bytesSent = new(uint64) + sm.rtpPacketsInError = new(uint64) + sm.rtcpPacketsReceived = new(uint64) + sm.rtcpPacketsSent = new(uint64) + sm.rtcpPacketsInError = new(uint64) + + sm.formats = make(map[uint8]*serverSessionFormat) + + for _, forma := range sm.media.Formats { + f := &serverSessionFormat{ + sm: sm, + format: forma, + onPacketRTP: func(*rtp.Packet) {}, + } + f.initialize() + sm.formats[forma.PayloadType()] = f + } +} + +func (sm *serverSessionMedia) start() { + // allocate udpRTCPReceiver before udpRTCPListener + // otherwise udpRTCPReceiver.LastSSRC() can't be called. + for _, sf := range sm.formats { + sf.start() + } + + switch *sm.ss.setuppedTransport { + case TransportUDP, TransportUDPMulticast: + sm.writePacketRTCPInQueue = sm.writePacketRTCPInQueueUDP + + if *sm.ss.setuppedTransport == TransportUDP { + if sm.ss.state == ServerSessionStatePlay { + // firewall opening is performed with RTCP sender reports generated by ServerStream + + // readers can send RTCP packets only + sm.ss.s.udpRTCPListener.addClient(sm.ss.author.ip(), sm.udpRTCPReadPort, sm.readPacketRTCPUDPPlay) + } else { + // open the firewall by sending empty packets to the counterpart. + byts, _ := (&rtp.Packet{Header: rtp.Header{Version: 2}}).Marshal() + sm.ss.s.udpRTPListener.write(byts, sm.udpRTPWriteAddr) //nolint:errcheck + + byts, _ = (&rtcp.ReceiverReport{}).Marshal() + sm.ss.s.udpRTCPListener.write(byts, sm.udpRTCPWriteAddr) //nolint:errcheck + + sm.ss.s.udpRTPListener.addClient(sm.ss.author.ip(), sm.udpRTPReadPort, sm.readPacketRTPUDPRecord) + sm.ss.s.udpRTCPListener.addClient(sm.ss.author.ip(), sm.udpRTCPReadPort, sm.readPacketRTCPUDPRecord) + } + } + + case TransportTCP: + sm.writePacketRTCPInQueue = sm.writePacketRTCPInQueueTCP + + if sm.ss.tcpCallbackByChannel == nil { + sm.ss.tcpCallbackByChannel = make(map[int]readFunc) + } + + if sm.ss.state == ServerSessionStatePlay { + sm.ss.tcpCallbackByChannel[sm.tcpChannel] = sm.readPacketRTPTCPPlay + sm.ss.tcpCallbackByChannel[sm.tcpChannel+1] = sm.readPacketRTCPTCPPlay + } else { + sm.ss.tcpCallbackByChannel[sm.tcpChannel] = sm.readPacketRTPTCPRecord + sm.ss.tcpCallbackByChannel[sm.tcpChannel+1] = sm.readPacketRTCPTCPRecord + } + } +} + +func (sm *serverSessionMedia) stop() { + if *sm.ss.setuppedTransport == TransportUDP { + sm.ss.s.udpRTPListener.removeClient(sm.ss.author.ip(), sm.udpRTPReadPort) + sm.ss.s.udpRTCPListener.removeClient(sm.ss.author.ip(), sm.udpRTCPReadPort) + } + + for _, sf := range sm.formats { + sf.stop() + } +} + +func (sm *serverSessionMedia) findFormatWithSSRC(ssrc uint32) *serverSessionFormat { + for _, format := range sm.formats { + stats := format.rtcpReceiver.Stats() + if stats != nil && stats.RemoteSSRC == ssrc { + return format + } + } + return nil +} + +func (sm *serverSessionMedia) writePacketRTCPInQueueUDP(payload []byte) error { + err := sm.ss.s.udpRTCPListener.write(payload, sm.udpRTCPWriteAddr) + if err != nil { + return err + } + + atomic.AddUint64(sm.bytesSent, uint64(len(payload))) + atomic.AddUint64(sm.rtcpPacketsSent, 1) + return nil +} + +func (sm *serverSessionMedia) writePacketRTCPInQueueTCP(payload []byte) error { + sm.ss.tcpFrame.Channel = sm.tcpChannel + 1 + sm.ss.tcpFrame.Payload = payload + sm.ss.tcpConn.nconn.SetWriteDeadline(time.Now().Add(sm.ss.s.WriteTimeout)) + err := sm.ss.tcpConn.conn.WriteInterleavedFrame(sm.ss.tcpFrame, sm.ss.tcpBuffer) + if err != nil { + return err + } + + atomic.AddUint64(sm.bytesSent, uint64(len(payload))) + atomic.AddUint64(sm.rtcpPacketsSent, 1) + return nil +} + +func (sm *serverSessionMedia) readPacketRTCPUDPPlay(payload []byte) bool { + atomic.AddUint64(sm.bytesReceived, uint64(len(payload))) + + if len(payload) == (udpMaxPayloadSize + 1) { + sm.onPacketRTCPDecodeError(liberrors.ErrServerRTCPPacketTooBigUDP{}) + return false + } + + packets, err := rtcp.Unmarshal(payload) + if err != nil { + sm.onPacketRTCPDecodeError(err) + return false + } + + now := sm.ss.s.timeNow() + atomic.StoreInt64(sm.ss.udpLastPacketTime, now.Unix()) + + atomic.AddUint64(sm.rtcpPacketsReceived, uint64(len(packets))) + + for _, pkt := range packets { + sm.onPacketRTCP(pkt) + } + + return true +} + +func (sm *serverSessionMedia) readPacketRTPUDPRecord(payload []byte) bool { + atomic.AddUint64(sm.bytesReceived, uint64(len(payload))) + + if len(payload) == (udpMaxPayloadSize + 1) { + sm.onPacketRTPDecodeError(liberrors.ErrServerRTPPacketTooBigUDP{}) + return false + } + + pkt := &rtp.Packet{} + err := pkt.Unmarshal(payload) + if err != nil { + sm.onPacketRTPDecodeError(err) + return false + } + + forma, ok := sm.formats[pkt.PayloadType] + if !ok { + sm.onPacketRTPDecodeError(liberrors.ErrServerRTPPacketUnknownPayloadType{PayloadType: pkt.PayloadType}) + return false + } + + now := sm.ss.s.timeNow() + atomic.StoreInt64(sm.ss.udpLastPacketTime, now.Unix()) + + forma.readPacketRTPUDP(pkt, now) + + return true +} + +func (sm *serverSessionMedia) readPacketRTCPUDPRecord(payload []byte) bool { + atomic.AddUint64(sm.bytesReceived, uint64(len(payload))) + + if len(payload) == (udpMaxPayloadSize + 1) { + sm.onPacketRTCPDecodeError(liberrors.ErrServerRTCPPacketTooBigUDP{}) + return false + } + + packets, err := rtcp.Unmarshal(payload) + if err != nil { + sm.onPacketRTCPDecodeError(err) + return false + } + + now := sm.ss.s.timeNow() + atomic.StoreInt64(sm.ss.udpLastPacketTime, now.Unix()) + + atomic.AddUint64(sm.rtcpPacketsReceived, uint64(len(packets))) + + for _, pkt := range packets { + if sr, ok := pkt.(*rtcp.SenderReport); ok { + format := sm.findFormatWithSSRC(sr.SSRC) + if format != nil { + format.rtcpReceiver.ProcessSenderReport(sr, now) + } + } + + sm.onPacketRTCP(pkt) + } + + return true +} + +func (sm *serverSessionMedia) readPacketRTPTCPPlay(_ []byte) bool { + return false +} + +func (sm *serverSessionMedia) readPacketRTCPTCPPlay(payload []byte) bool { + atomic.AddUint64(sm.bytesReceived, uint64(len(payload))) + + if len(payload) > udpMaxPayloadSize { + sm.onPacketRTCPDecodeError(liberrors.ErrServerRTCPPacketTooBig{L: len(payload), Max: udpMaxPayloadSize}) + return false + } + + packets, err := rtcp.Unmarshal(payload) + if err != nil { + sm.onPacketRTCPDecodeError(err) + return false + } + + atomic.AddUint64(sm.rtcpPacketsReceived, uint64(len(packets))) + + for _, pkt := range packets { + sm.onPacketRTCP(pkt) + } + + return true +} + +func (sm *serverSessionMedia) readPacketRTPTCPRecord(payload []byte) bool { + atomic.AddUint64(sm.bytesReceived, uint64(len(payload))) + + pkt := &rtp.Packet{} + err := pkt.Unmarshal(payload) + if err != nil { + sm.onPacketRTPDecodeError(err) + return false + } + + forma, ok := sm.formats[pkt.PayloadType] + if !ok { + sm.onPacketRTPDecodeError(liberrors.ErrServerRTPPacketUnknownPayloadType{PayloadType: pkt.PayloadType}) + return false + } + + forma.readPacketRTPTCP(pkt) + + return true +} + +func (sm *serverSessionMedia) readPacketRTCPTCPRecord(payload []byte) bool { + atomic.AddUint64(sm.bytesReceived, uint64(len(payload))) + + if len(payload) > udpMaxPayloadSize { + sm.onPacketRTCPDecodeError(liberrors.ErrServerRTCPPacketTooBig{L: len(payload), Max: udpMaxPayloadSize}) + return false + } + + packets, err := rtcp.Unmarshal(payload) + if err != nil { + sm.onPacketRTCPDecodeError(err) + return false + } + + now := sm.ss.s.timeNow() + + atomic.AddUint64(sm.rtcpPacketsReceived, uint64(len(packets))) + + for _, pkt := range packets { + if sr, ok := pkt.(*rtcp.SenderReport); ok { + format := sm.findFormatWithSSRC(sr.SSRC) + if format != nil { + format.rtcpReceiver.ProcessSenderReport(sr, now) + } + } + + sm.onPacketRTCP(pkt) + } + + return true +} + +func (sm *serverSessionMedia) onPacketRTPDecodeError(err error) { + atomic.AddUint64(sm.rtpPacketsInError, 1) + + if h, ok := sm.ss.s.Handler.(ServerHandlerOnDecodeError); ok { + h.OnDecodeError(&ServerHandlerOnDecodeErrorCtx{ + Session: sm.ss, + Error: err, + }) + } else { + log.Println(err.Error()) + } +} + +func (sm *serverSessionMedia) onPacketRTCPDecodeError(err error) { + atomic.AddUint64(sm.rtcpPacketsInError, 1) + + if h, ok := sm.ss.s.Handler.(ServerHandlerOnDecodeError); ok { + h.OnDecodeError(&ServerHandlerOnDecodeErrorCtx{ + Session: sm.ss, + Error: err, + }) + } else { + log.Println(err.Error()) + } +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_stream.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_stream.go new file mode 100644 index 000000000..574d112d1 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_stream.go @@ -0,0 +1,378 @@ +package gortsplib + +import ( + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/pion/rtcp" + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/headers" + "github.com/bluenviron/gortsplib/v4/pkg/liberrors" +) + +func firstFormat(formats map[uint8]*serverStreamFormat) *serverStreamFormat { + var firstKey uint8 + for key := range formats { + firstKey = key + break + } + + return formats[firstKey] +} + +// NewServerStream allocates a ServerStream. +// +// Deprecated: replaced by ServerStream.Initialize(). +func NewServerStream(s *Server, desc *description.Session) *ServerStream { + st := &ServerStream{ + Server: s, + Desc: desc, + } + err := st.Initialize() + if err != nil { + panic(err) + } + return st +} + +// ServerStream represents a data stream. +// This is in charge of +// - distributing the stream to each reader +// - allocating multicast listeners +// - gathering infos about the stream in order to generate SSRC and RTP-Info +type ServerStream struct { + Server *Server + Desc *description.Session + + mutex sync.RWMutex + readers map[*ServerSession]struct{} + multicastReaderCount int + activeUnicastReaders map[*ServerSession]struct{} + medias map[*description.Media]*serverStreamMedia + closed bool +} + +// Initialize initializes a ServerStream. +func (st *ServerStream) Initialize() error { + if st.Server == nil || st.Server.sessions == nil { + return fmt.Errorf("server not present or not initialized") + } + + st.readers = make(map[*ServerSession]struct{}) + st.activeUnicastReaders = make(map[*ServerSession]struct{}) + + st.medias = make(map[*description.Media]*serverStreamMedia, len(st.Desc.Medias)) + for i, medi := range st.Desc.Medias { + sm := &serverStreamMedia{ + st: st, + media: medi, + trackID: i, + } + sm.initialize() + st.medias[medi] = sm + } + + return nil +} + +// Close closes a ServerStream. +func (st *ServerStream) Close() { + st.mutex.Lock() + st.closed = true + st.mutex.Unlock() + + for ss := range st.readers { + ss.Close() + } + + for _, sm := range st.medias { + sm.close() + } +} + +// BytesSent returns the number of written bytes. +// +// Deprecated: replaced by Stats() +func (st *ServerStream) BytesSent() uint64 { + v := uint64(0) + for _, me := range st.medias { + v += atomic.LoadUint64(me.bytesSent) + } + return v +} + +// Description returns the description of the stream. +// +// Deprecated: use ServerStream.Desc. +func (st *ServerStream) Description() *description.Session { + return st.Desc +} + +// Stats returns stream statistics. +func (st *ServerStream) Stats() *ServerStreamStats { + return &ServerStreamStats{ + BytesSent: func() uint64 { + v := uint64(0) + for _, me := range st.medias { + v += atomic.LoadUint64(me.bytesSent) + } + return v + }(), + RTPPacketsSent: func() uint64 { + v := uint64(0) + for _, me := range st.medias { + for _, f := range me.formats { + v += atomic.LoadUint64(f.rtpPacketsSent) + } + } + return v + }(), + RTCPPacketsSent: func() uint64 { + v := uint64(0) + for _, me := range st.medias { + v += atomic.LoadUint64(me.rtcpPacketsSent) + } + return v + }(), + Medias: func() map[*description.Media]ServerStreamStatsMedia { + ret := make(map[*description.Media]ServerStreamStatsMedia, len(st.medias)) + + for med, sm := range st.medias { + ret[med] = ServerStreamStatsMedia{ + BytesSent: atomic.LoadUint64(sm.bytesSent), + RTCPPacketsSent: atomic.LoadUint64(sm.rtcpPacketsSent), + Formats: func() map[format.Format]ServerStreamStatsFormat { + ret := make(map[format.Format]ServerStreamStatsFormat) + + for _, fo := range sm.formats { + ret[fo.format] = ServerStreamStatsFormat{ + RTPPacketsSent: atomic.LoadUint64(fo.rtpPacketsSent), + } + } + + return ret + }(), + } + } + + return ret + }(), + } +} + +func (st *ServerStream) localSSRC(medi *description.Media) (uint32, bool) { + st.mutex.Lock() + defer st.mutex.Unlock() + + sm := st.medias[medi] + + // localSSRC() is used to fill SSRC inside the Transport header. + // if there are multiple formats inside a single media stream, + // do not return anything, since Transport headers don't support multiple SSRCs. + if len(sm.formats) > 1 { + return 0, false + } + + stats := firstFormat(sm.formats).rtcpSender.Stats() + if stats == nil { + return 0, false + } + + return stats.LocalSSRC, true +} + +func (st *ServerStream) rtpInfoEntry(medi *description.Media, now time.Time) *headers.RTPInfoEntry { + st.mutex.Lock() + defer st.mutex.Unlock() + + sm := st.medias[medi] + + // if there are multiple formats inside a single media stream, + // do not generate a RTP-Info entry, since RTP-Info doesn't support + // multiple sequence numbers / timestamps. + if len(sm.formats) > 1 { + return nil + } + + format := firstFormat(sm.formats) + + stats := format.rtcpSender.Stats() + if stats == nil { + return nil + } + + clockRate := format.format.ClockRate() + if clockRate == 0 { + return nil + } + + // sequence number of the first packet of the stream + seqNum := stats.LastSequenceNumber + 1 + + // RTP timestamp corresponding to the time value in + // the Range response header. + // remove a small quantity in order to avoid DTS > PTS + ts := uint32(uint64(stats.LastRTP) + + uint64(now.Sub(stats.LastNTP).Seconds()*float64(clockRate)) - + uint64(clockRate)/10) + + return &headers.RTPInfoEntry{ + SequenceNumber: &seqNum, + Timestamp: &ts, + } +} + +func (st *ServerStream) readerAdd( + ss *ServerSession, + clientPorts *[2]int, +) error { + st.mutex.Lock() + defer st.mutex.Unlock() + + if st.closed { + return liberrors.ErrServerStreamClosed{} + } + + switch *ss.setuppedTransport { + case TransportUDP: + // check whether UDP ports and IP are already assigned to another reader + for r := range st.readers { + if *r.setuppedTransport == TransportUDP && + r.author.ip().Equal(ss.author.ip()) && + r.author.zone() == ss.author.zone() { + for _, rt := range r.setuppedMedias { + if rt.udpRTPReadPort == clientPorts[0] { + return liberrors.ErrServerUDPPortsAlreadyInUse{Port: rt.udpRTPReadPort} + } + } + } + } + + case TransportUDPMulticast: + if st.multicastReaderCount == 0 { + for _, media := range st.medias { + mw := &serverMulticastWriter{ + s: st.Server, + } + err := mw.initialize() + if err != nil { + return err + } + media.multicastWriter = mw + } + } + st.multicastReaderCount++ + } + + st.readers[ss] = struct{}{} + + return nil +} + +func (st *ServerStream) readerRemove(ss *ServerSession) { + st.mutex.Lock() + defer st.mutex.Unlock() + + if st.closed { + return + } + + delete(st.readers, ss) + + if *ss.setuppedTransport == TransportUDPMulticast { + st.multicastReaderCount-- + if st.multicastReaderCount == 0 { + for _, media := range st.medias { + media.multicastWriter.close() + media.multicastWriter = nil + } + } + } +} + +func (st *ServerStream) readerSetActive(ss *ServerSession) { + st.mutex.Lock() + defer st.mutex.Unlock() + + if st.closed { + return + } + + if *ss.setuppedTransport == TransportUDPMulticast { + for medi, sm := range ss.setuppedMedias { + streamMedia := st.medias[medi] + streamMedia.multicastWriter.rtcpl.addClient( + ss.author.ip(), streamMedia.multicastWriter.rtcpl.port(), sm.readPacketRTCPUDPPlay) + } + } else { + st.activeUnicastReaders[ss] = struct{}{} + } +} + +func (st *ServerStream) readerSetInactive(ss *ServerSession) { + st.mutex.Lock() + defer st.mutex.Unlock() + + if st.closed { + return + } + + if *ss.setuppedTransport == TransportUDPMulticast { + for medi := range ss.setuppedMedias { + streamMedia := st.medias[medi] + streamMedia.multicastWriter.rtcpl.removeClient(ss.author.ip(), streamMedia.multicastWriter.rtcpl.port()) + } + } else { + delete(st.activeUnicastReaders, ss) + } +} + +// WritePacketRTP writes a RTP packet to all the readers of the stream. +func (st *ServerStream) WritePacketRTP(medi *description.Media, pkt *rtp.Packet) error { + return st.WritePacketRTPWithNTP(medi, pkt, st.Server.timeNow()) +} + +// WritePacketRTPWithNTP writes a RTP packet to all the readers of the stream. +// ntp is the absolute time of the packet, and is sent with periodic RTCP sender reports. +func (st *ServerStream) WritePacketRTPWithNTP(medi *description.Media, pkt *rtp.Packet, ntp time.Time) error { + byts := make([]byte, st.Server.MaxPacketSize) + n, err := pkt.MarshalTo(byts) + if err != nil { + return err + } + byts = byts[:n] + + st.mutex.RLock() + defer st.mutex.RUnlock() + + if st.closed { + return liberrors.ErrServerStreamClosed{} + } + + sm := st.medias[medi] + sf := sm.formats[pkt.PayloadType] + return sf.writePacketRTP(byts, pkt, ntp) +} + +// WritePacketRTCP writes a RTCP packet to all the readers of the stream. +func (st *ServerStream) WritePacketRTCP(medi *description.Media, pkt rtcp.Packet) error { + byts, err := pkt.Marshal() + if err != nil { + return err + } + + st.mutex.RLock() + defer st.mutex.RUnlock() + + if st.closed { + return liberrors.ErrServerStreamClosed{} + } + + sm := st.medias[medi] + return sm.writePacketRTCP(byts) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_stream_format.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_stream_format.go new file mode 100644 index 000000000..0498ae501 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_stream_format.go @@ -0,0 +1,69 @@ +package gortsplib + +import ( + "sync/atomic" + "time" + + "github.com/pion/rtcp" + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v4/pkg/format" + "github.com/bluenviron/gortsplib/v4/pkg/rtcpsender" +) + +type serverStreamFormat struct { + sm *serverStreamMedia + format format.Format + + rtcpSender *rtcpsender.RTCPSender + rtpPacketsSent *uint64 +} + +func (sf *serverStreamFormat) initialize() { + sf.rtpPacketsSent = new(uint64) + + sf.rtcpSender = &rtcpsender.RTCPSender{ + ClockRate: sf.format.ClockRate(), + Period: sf.sm.st.Server.senderReportPeriod, + TimeNow: sf.sm.st.Server.timeNow, + WritePacketRTCP: func(pkt rtcp.Packet) { + if !sf.sm.st.Server.DisableRTCPSenderReports { + sf.sm.st.WritePacketRTCP(sf.sm.media, pkt) //nolint:errcheck + } + }, + } + sf.rtcpSender.Initialize() +} + +func (sf *serverStreamFormat) writePacketRTP(byts []byte, pkt *rtp.Packet, ntp time.Time) error { + sf.rtcpSender.ProcessPacket(pkt, ntp, sf.format.PTSEqualsDTS(pkt)) + + le := uint64(len(byts)) + + // send unicast + for r := range sf.sm.st.activeUnicastReaders { + if _, ok := r.setuppedMedias[sf.sm.media]; ok { + err := r.writePacketRTP(sf.sm.media, pkt.PayloadType, byts) + if err != nil { + r.onStreamWriteError(err) + continue + } + + atomic.AddUint64(sf.sm.bytesSent, le) + atomic.AddUint64(sf.rtpPacketsSent, 1) + } + } + + // send multicast + if sf.sm.multicastWriter != nil { + err := sf.sm.multicastWriter.writePacketRTP(byts) + if err != nil { + return err + } + + atomic.AddUint64(sf.sm.bytesSent, le) + atomic.AddUint64(sf.rtpPacketsSent, 1) + } + + return nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_stream_media.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_stream_media.go new file mode 100644 index 000000000..26bfc8d79 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_stream_media.go @@ -0,0 +1,76 @@ +package gortsplib + +import ( + "sync/atomic" + + "github.com/bluenviron/gortsplib/v4/pkg/description" +) + +type serverStreamMedia struct { + st *ServerStream + media *description.Media + trackID int + + formats map[uint8]*serverStreamFormat + multicastWriter *serverMulticastWriter + bytesSent *uint64 + rtcpPacketsSent *uint64 +} + +func (sm *serverStreamMedia) initialize() { + sm.bytesSent = new(uint64) + sm.rtcpPacketsSent = new(uint64) + + sm.formats = make(map[uint8]*serverStreamFormat) + for _, forma := range sm.media.Formats { + sf := &serverStreamFormat{ + sm: sm, + format: forma, + } + sf.initialize() + sm.formats[forma.PayloadType()] = sf + } +} + +func (sm *serverStreamMedia) close() { + for _, tr := range sm.formats { + if tr.rtcpSender != nil { + tr.rtcpSender.Close() + } + } + + if sm.multicastWriter != nil { + sm.multicastWriter.close() + } +} + +func (sm *serverStreamMedia) writePacketRTCP(byts []byte) error { + le := len(byts) + + // send unicast + for r := range sm.st.activeUnicastReaders { + if _, ok := r.setuppedMedias[sm.media]; ok { + err := r.writePacketRTCP(sm.media, byts) + if err != nil { + r.onStreamWriteError(err) + continue + } + + atomic.AddUint64(sm.bytesSent, uint64(le)) + atomic.AddUint64(sm.rtcpPacketsSent, 1) + } + } + + // send multicast + if sm.multicastWriter != nil { + err := sm.multicastWriter.writePacketRTCP(byts) + if err != nil { + return err + } + + atomic.AddUint64(sm.bytesSent, uint64(le)) + atomic.AddUint64(sm.rtcpPacketsSent, 1) + } + + return nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_stream_stats.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_stream_stats.go new file mode 100644 index 000000000..6e845a6ef --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_stream_stats.go @@ -0,0 +1,36 @@ +package gortsplib + +import ( + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" +) + +// ServerStreamStatsFormat are stream format statistics. +type ServerStreamStatsFormat struct { + // number of sent RTP packets + RTPPacketsSent uint64 +} + +// ServerStreamStatsMedia are stream media statistics. +type ServerStreamStatsMedia struct { + // sent bytes + BytesSent uint64 + // number of sent RTCP packets + RTCPPacketsSent uint64 + + // format statistics + Formats map[format.Format]ServerStreamStatsFormat +} + +// ServerStreamStats are stream statistics. +type ServerStreamStats struct { + // sent bytes + BytesSent uint64 + // number of sent RTP packets + RTPPacketsSent uint64 + // number of sent RTCP packets + RTCPPacketsSent uint64 + + // media statistics + Medias map[*description.Media]ServerStreamStatsMedia +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_tcp_listener.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_tcp_listener.go new file mode 100644 index 000000000..e90244c70 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_tcp_listener.go @@ -0,0 +1,42 @@ +package gortsplib + +import ( + "net" +) + +type serverTCPListener struct { + s *Server + + ln net.Listener +} + +func (sl *serverTCPListener) initialize() error { + var err error + sl.ln, err = sl.s.Listen(restrictNetwork("tcp", sl.s.RTSPAddress)) + if err != nil { + return err + } + + sl.s.wg.Add(1) + go sl.run() + + return nil +} + +func (sl *serverTCPListener) close() { + sl.ln.Close() +} + +func (sl *serverTCPListener) run() { + defer sl.s.wg.Done() + + for { + nconn, err := sl.ln.Accept() + if err != nil { + sl.s.acceptErr(err) + return + } + + sl.s.newConn(nconn) + } +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_udp_listener.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_udp_listener.go new file mode 100644 index 000000000..96dd756b8 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/server_udp_listener.go @@ -0,0 +1,186 @@ +package gortsplib + +import ( + "net" + "strconv" + "sync" + "time" + + "github.com/bluenviron/gortsplib/v4/pkg/multicast" +) + +type clientAddr struct { + ip [net.IPv6len]byte // use a fixed-size array to enable the equality operator + port int +} + +func (p *clientAddr) fill(ip net.IP, port int) { + p.port = port + + if len(ip) == net.IPv4len { + copy(p.ip[0:], []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}) // v4InV6Prefix + copy(p.ip[12:], ip) + } else { + copy(p.ip[:], ip) + } +} + +func createUDPListenerMulticastPair( + listenPacket func(network, address string) (net.PacketConn, error), + writeTimeout time.Duration, + multicastRTPPort int, + multicastRTCPPort int, + ip net.IP, +) (*serverUDPListener, *serverUDPListener, error) { + rtpl := &serverUDPListener{ + listenPacket: listenPacket, + writeTimeout: writeTimeout, + multicastEnable: true, + address: net.JoinHostPort(ip.String(), strconv.FormatInt(int64(multicastRTPPort), 10)), + } + err := rtpl.initialize() + if err != nil { + return nil, nil, err + } + + rtcpl := &serverUDPListener{ + listenPacket: listenPacket, + writeTimeout: writeTimeout, + multicastEnable: true, + address: net.JoinHostPort(ip.String(), strconv.FormatInt(int64(multicastRTCPPort), 10)), + } + err = rtcpl.initialize() + if err != nil { + rtpl.close() + return nil, nil, err + } + + return rtpl, rtcpl, nil +} + +type serverUDPListener struct { + listenPacket func(network, address string) (net.PacketConn, error) + writeTimeout time.Duration + multicastEnable bool + address string + + pc packetConn + listenIP net.IP + clientsMutex sync.RWMutex + clients map[clientAddr]readFunc + + done chan struct{} +} + +func (u *serverUDPListener) initialize() error { + if u.multicastEnable { + var err error + u.pc, err = multicast.NewMultiConn(u.address, false, u.listenPacket) + if err != nil { + return err + } + + host, _, err := net.SplitHostPort(u.address) + if err != nil { + return err + } + u.listenIP = net.ParseIP(host) + } else { + tmp, err := u.listenPacket(restrictNetwork("udp", u.address)) + if err != nil { + return err + } + u.pc = tmp.(*net.UDPConn) + u.listenIP = tmp.LocalAddr().(*net.UDPAddr).IP + } + + err := u.pc.SetReadBuffer(udpKernelReadBufferSize) + if err != nil { + u.pc.Close() + return err + } + + u.clients = make(map[clientAddr]readFunc) + u.done = make(chan struct{}) + + go u.run() + + return nil +} + +func (u *serverUDPListener) close() { + u.pc.Close() + <-u.done +} + +func (u *serverUDPListener) ip() net.IP { + return u.listenIP +} + +func (u *serverUDPListener) port() int { + return u.pc.LocalAddr().(*net.UDPAddr).Port +} + +func (u *serverUDPListener) run() { + defer close(u.done) + + var buf []byte + + createNewBuffer := func() { + buf = make([]byte, udpMaxPayloadSize+1) + } + + createNewBuffer() + + for { + n, addr2, err := u.pc.ReadFrom(buf) + if err != nil { + break + } + addr := addr2.(*net.UDPAddr) + + func() { + u.clientsMutex.RLock() + defer u.clientsMutex.RUnlock() + + var ca clientAddr + ca.fill(addr.IP, addr.Port) + cb, ok := u.clients[ca] + if !ok { + return + } + + if cb(buf[:n]) { + createNewBuffer() + } + }() + } +} + +func (u *serverUDPListener) write(buf []byte, addr *net.UDPAddr) error { + // no mutex is needed here since Write() has an internal lock. + // https://github.com/golang/go/issues/27203#issuecomment-534386117 + u.pc.SetWriteDeadline(time.Now().Add(u.writeTimeout)) + _, err := u.pc.WriteTo(buf, addr) + return err +} + +func (u *serverUDPListener) addClient(ip net.IP, port int, cb readFunc) { + var addr clientAddr + addr.fill(ip, port) + + u.clientsMutex.Lock() + defer u.clientsMutex.Unlock() + + u.clients[addr] = cb +} + +func (u *serverUDPListener) removeClient(ip net.IP, port int) { + var addr clientAddr + addr.fill(ip, port) + + u.clientsMutex.Lock() + defer u.clientsMutex.Unlock() + + delete(u.clients, addr) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/stats_conn.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/stats_conn.go new file mode 100644 index 000000000..635519b93 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/stats_conn.go @@ -0,0 +1,9 @@ +package gortsplib + +// StatsConn are connection statistics. +type StatsConn struct { + // received bytes + BytesReceived uint64 + // sent bytes + BytesSent uint64 +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/stats_session.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/stats_session.go new file mode 100644 index 000000000..5540ef628 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/stats_session.go @@ -0,0 +1,76 @@ +package gortsplib + +import ( + "time" + + "github.com/bluenviron/gortsplib/v4/pkg/description" + "github.com/bluenviron/gortsplib/v4/pkg/format" +) + +// StatsSessionFormat are session format statistics. +type StatsSessionFormat struct { + // number of RTP packets correctly received and processed + RTPPacketsReceived uint64 + // number of sent RTP packets + RTPPacketsSent uint64 + // number of lost RTP packets + RTPPacketsLost uint64 + // mean jitter of received RTP packets + RTPPacketsJitter float64 + // local SSRC + LocalSSRC uint32 + // remote SSRC + RemoteSSRC uint32 + // last sequence number of incoming/outgoing RTP packets + RTPPacketsLastSequenceNumber uint16 + // last RTP time of incoming/outgoing RTP packets + RTPPacketsLastRTP uint32 + // last NTP time of incoming/outgoing NTP packets + RTPPacketsLastNTP time.Time +} + +// StatsSessionMedia are session media statistics. +type StatsSessionMedia struct { + // received bytes + BytesReceived uint64 + // sent bytes + BytesSent uint64 + // number of RTP packets that could not be processed + RTPPacketsInError uint64 + // number of RTCP packets correctly received and processed + RTCPPacketsReceived uint64 + // number of sent RTCP packets + RTCPPacketsSent uint64 + // number of RTCP packets that could not be processed + RTCPPacketsInError uint64 + + // format statistics + Formats map[format.Format]StatsSessionFormat +} + +// StatsSession are session statistics. +type StatsSession struct { + // received bytes + BytesReceived uint64 + // sent bytes + BytesSent uint64 + // number of RTP packets correctly received and processed + RTPPacketsReceived uint64 + // number of sent RTP packets + RTPPacketsSent uint64 + // number of lost RTP packets + RTPPacketsLost uint64 + // number of RTP packets that could not be processed + RTPPacketsInError uint64 + // mean jitter of received RTP packets + RTPPacketsJitter float64 + // number of RTCP packets correctly received and processed + RTCPPacketsReceived uint64 + // number of sent RTCP packets + RTCPPacketsSent uint64 + // number of RTCP packets that could not be processed + RTCPPacketsInError uint64 + + // media statistics + Medias map[*description.Media]StatsSessionMedia +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/transport.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/transport.go new file mode 100644 index 000000000..470f7d7ce --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/gortsplib/v4/transport.go @@ -0,0 +1,25 @@ +package gortsplib + +// Transport is a RTSP transport protocol. +type Transport int + +// transport protocols. +const ( + TransportUDP Transport = iota + TransportUDPMulticast + TransportTCP +) + +var transportLabels = map[Transport]string{ + TransportUDP: "UDP", + TransportUDPMulticast: "UDP-multicast", + TransportTCP: "TCP", +} + +// String implements fmt.Stringer. +func (t Transport) String() string { + if l, ok := transportLabels[t]; ok { + return l + } + return "unknown" +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/LICENSE b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/LICENSE new file mode 100644 index 000000000..e06e71e4c --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 aler9 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/bits/read.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/bits/read.go new file mode 100644 index 000000000..77a694f72 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/bits/read.go @@ -0,0 +1,121 @@ +// Package bits contains functions to read/write bits from/to buffers. +package bits + +import ( + "fmt" +) + +// HasSpace checks whether buffer has space for N bits. +func HasSpace(buf []byte, pos int, n int) error { + if n > ((len(buf) * 8) - pos) { + return fmt.Errorf("not enough bits") + } + return nil +} + +// ReadBits reads N bits. +func ReadBits(buf []byte, pos *int, n int) (uint64, error) { + err := HasSpace(buf, *pos, n) + if err != nil { + return 0, err + } + + return ReadBitsUnsafe(buf, pos, n), nil +} + +// ReadBitsUnsafe reads N bits. +func ReadBitsUnsafe(buf []byte, pos *int, n int) uint64 { + res := 8 - (*pos & 0x07) + if n < res { + v := uint64((buf[*pos>>0x03] >> (res - n)) & (1<>0x03] & (1<= 8 { + v = (v << 8) | uint64(buf[*pos>>0x03]) + *pos += 8 + n -= 8 + } + + if n > 0 { + v = (v << n) | uint64(buf[*pos>>0x03]>>(8-n)) + *pos += n + } + + return v +} + +// ReadGolombUnsigned reads an unsigned golomb-encoded value. +func ReadGolombUnsigned(buf []byte, pos *int) (uint32, error) { + buflen := len(buf) + leadingZeros := uint32(0) + + for { + if (buflen*8 - *pos) == 0 { + return 0, fmt.Errorf("not enough bits") + } + + b := (buf[*pos>>0x03] >> (7 - (*pos & 0x07))) & 0x01 + *pos++ + if b != 0 { + break + } + + leadingZeros++ + if leadingZeros > 32 { + return 0, fmt.Errorf("invalid value") + } + } + + if (buflen*8 - *pos) < int(leadingZeros) { + return 0, fmt.Errorf("not enough bits") + } + + codeNum := uint32(0) + + for n := leadingZeros; n > 0; n-- { + b := (buf[*pos>>0x03] >> (7 - (*pos & 0x07))) & 0x01 + *pos++ + codeNum |= uint32(b) << (n - 1) + } + + codeNum = (1 << leadingZeros) - 1 + codeNum + + return codeNum, nil +} + +// ReadGolombSigned reads a signed golomb-encoded value. +func ReadGolombSigned(buf []byte, pos *int) (int32, error) { + v, err := ReadGolombUnsigned(buf, pos) + if err != nil { + return 0, err + } + + vi := int32(v) + if (vi & 0x01) != 0 { + return (vi + 1) / 2, nil + } + return -vi / 2, nil +} + +// ReadFlag reads a boolean flag. +func ReadFlag(buf []byte, pos *int) (bool, error) { + err := HasSpace(buf, *pos, 1) + if err != nil { + return false, err + } + + return ReadFlagUnsafe(buf, pos), nil +} + +// ReadFlagUnsafe reads a boolean flag. +func ReadFlagUnsafe(buf []byte, pos *int) bool { + b := (buf[*pos>>0x03] >> (7 - (*pos & 0x07))) & 0x01 + *pos++ + return b == 1 +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/bits/write.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/bits/write.go new file mode 100644 index 000000000..ca7823a80 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/bits/write.go @@ -0,0 +1,26 @@ +package bits + +// WriteBitsUnsafe writes N bits. +func WriteBitsUnsafe(buf []byte, pos *int, v uint64, n int) { + res := 8 - (*pos & 0x07) + if n < res { + buf[*pos>>0x03] |= byte(v << (res - n)) + *pos += n + return + } + + buf[*pos>>3] |= byte(v >> (n - res)) + *pos += res + n -= res + + for n >= 8 { + buf[*pos>>3] = byte(v >> (n - 8)) + *pos += 8 + n -= 8 + } + + if n > 0 { + buf[*pos>>3] = byte((v & (1<> 3 + if b.Bsid != 0x08 { + return fmt.Errorf("invalid bsid") + } + + b.Bsmod = buf[0] & 0b111 + + buf = buf[1:] + pos := 0 + + tmp := bits.ReadBitsUnsafe(buf, &pos, 3) + b.Acmod = uint8(tmp) + + if ((b.Acmod & 0x1) != 0) && (b.Acmod != 0x1) { + bits.ReadBitsUnsafe(buf, &pos, 2) // cmixlev + } + + if (b.Acmod & 0x4) != 0 { + bits.ReadBitsUnsafe(buf, &pos, 2) // surmixlev + } + + if b.Acmod == 0x2 { + bits.ReadBitsUnsafe(buf, &pos, 2) // dsurmod + } + + b.LfeOn = bits.ReadFlagUnsafe(buf, &pos) + + return nil +} + +// ChannelCount returns the channel count. +func (b BSI) ChannelCount() int { + var n int + switch b.Acmod { + case 0b001: + n = 1 + case 0b010, 0b000: + n = 2 + case 0b011, 0b100: + n = 3 + case 0b101, 0b110: + n = 4 + default: + n = 5 + } + + if b.LfeOn { + return n + 1 + } + return n +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/ac3/sync_info.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/ac3/sync_info.go new file mode 100644 index 000000000..e6acf586b --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/ac3/sync_info.go @@ -0,0 +1,94 @@ +package ac3 + +import ( + "fmt" +) + +// ATSC, AC-3, Table 5.18 +var frameSizes = [][]int{ + {64, 69, 96}, + {64, 70, 96}, + {80, 87, 120}, + {80, 88, 120}, + {96, 104, 144}, + {96, 105, 144}, + {112, 121, 168}, + {112, 122, 168}, + {128, 139, 192}, + {128, 140, 192}, + {160, 174, 240}, + {160, 175, 240}, + {192, 208, 288}, + {192, 209, 288}, + {224, 243, 336}, + {224, 244, 336}, + {256, 278, 384}, + {256, 279, 384}, + {320, 348, 480}, + {320, 349, 480}, + {384, 417, 576}, + {384, 418, 576}, + {448, 487, 672}, + {448, 488, 672}, + {512, 557, 768}, + {512, 558, 768}, + {640, 696, 960}, + {640, 697, 960}, + {768, 835, 1152}, + {768, 836, 1152}, + {896, 975, 1344}, + {896, 976, 1344}, + {1024, 1114, 1536}, + {1024, 1115, 1536}, + {1152, 1253, 1728}, + {1152, 1254, 1728}, + {1280, 1393, 1920}, + {1280, 1394, 1920}, +} + +// SyncInfo is a synchronization information. +// Specification: ATSC, AC-3, Table 5.1 +type SyncInfo struct { + Fscod uint8 + Frmsizecod uint8 +} + +// Unmarshal decodes a SyncInfo. +func (s *SyncInfo) Unmarshal(frame []byte) error { + if len(frame) < 5 { + return fmt.Errorf("not enough bits") + } + + if frame[0] != 0x0B || frame[1] != 0x77 { + return fmt.Errorf("invalid sync word") + } + + s.Fscod = frame[4] >> 6 + if s.Fscod >= 3 { + return fmt.Errorf("invalid fscod") + } + + s.Frmsizecod = frame[4] & 0x3f + if s.Frmsizecod >= 38 { + return fmt.Errorf("invalid frmsizecod") + } + + return nil +} + +// FrameSize returns the frame size. +func (s SyncInfo) FrameSize() int { + return frameSizes[s.Frmsizecod][s.Fscod] * 2 +} + +// SampleRate returns the frame sample rate. +func (s SyncInfo) SampleRate() int { + switch s.Fscod { + case 0: + return 48000 + case 1: + return 44100 + default: + return 32000 + } +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/av1.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/av1.go new file mode 100644 index 000000000..674ebf312 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/av1.go @@ -0,0 +1,10 @@ +// Package av1 contains utilities to work with the AV1 codec. +package av1 + +const ( + // MaxTemporalUnitSize is the maximum size of a temporal unit. + MaxTemporalUnitSize = 3 * 1024 * 1024 + + // MaxOBUsPerTemporalUnit is the maximum number of OBUs per temporal unit. + MaxOBUsPerTemporalUnit = 10 +) diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/bitstream.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/bitstream.go new file mode 100644 index 000000000..daa4bde93 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/bitstream.go @@ -0,0 +1,86 @@ +package av1 + +import ( + "fmt" +) + +// Bitstream is an AV1 bitstream. +// Specification: https://aomediacodec.github.io/av1-spec/#low-overhead-bitstream-format +type Bitstream [][]byte + +// Unmarshal decodes a Bitstream. +func (bs *Bitstream) Unmarshal(buf []byte) error { + for { + var h OBUHeader + err := h.Unmarshal(buf) + if err != nil { + return err + } + + if !h.HasSize { + return fmt.Errorf("OBU size not present") + } + + var size LEB128 + n, err := size.Unmarshal(buf[1:]) + if err != nil { + return err + } + + obuLen := 1 + n + int(size) + if len(buf) < obuLen { + return fmt.Errorf("not enough bytes") + } + + var obu []byte + obu, buf = buf[:obuLen], buf[obuLen:] + + *bs = append(*bs, obu) + + if len(buf) == 0 { + break + } + } + + return nil +} + +// Marshal encodes a Bitstream. +func (bs Bitstream) Marshal() ([]byte, error) { + n := 0 + + for _, obu := range bs { + n += len(obu) + + var h OBUHeader + err := h.Unmarshal(obu) + if err != nil { + return nil, err + } + + if !h.HasSize { + size := len(obu) - 1 + n += LEB128(uint32(size)).MarshalSize() + } + } + + buf := make([]byte, n) + n = 0 + + for _, obu := range bs { + var h OBUHeader + h.Unmarshal(obu) //nolint:errcheck + + if !h.HasSize { + buf[n] = obu[0] | 0b00000010 + n++ + size := len(obu) - 1 + n += LEB128(uint32(size)).MarshalTo(buf[n:]) + n += copy(buf[n:], obu[1:]) + } else { + n += copy(buf[n:], obu) + } + } + + return buf, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/is_random_access.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/is_random_access.go new file mode 100644 index 000000000..400b5e7d4 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/is_random_access.go @@ -0,0 +1,35 @@ +package av1 + +import ( + "fmt" +) + +// IsRandomAccess2 checks whether a temporal unit can be randomly accessed. +func IsRandomAccess2(tu [][]byte) bool { + for _, obu := range tu { + var h OBUHeader + err := h.Unmarshal(obu) + if err == nil && h.Type == OBUTypeSequenceHeader { + return true + } + } + + return false +} + +// IsRandomAccess checks whether a temporal unit can be randomly accessed. +// +// Deprecated: replaced by IsRandomAccess2. +func IsRandomAccess(tu [][]byte) (bool, error) { + if len(tu) == 0 { + return false, fmt.Errorf("temporal unit is empty") + } + + var h OBUHeader + err := h.Unmarshal(tu[0]) + if err != nil { + return false, err + } + + return (h.Type == OBUTypeSequenceHeader), nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/leb128.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/leb128.go new file mode 100644 index 000000000..39920106e --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/leb128.go @@ -0,0 +1,73 @@ +package av1 + +import ( + "fmt" +) + +// LEB128 is a unsigned integer that can be decoded/encoded from/to the LEB128 format. +// Specification: https://aomediacodec.github.io/av1-spec/#leb128 +type LEB128 uint32 + +// Unmarshal decodes an unsigned integer from the LEB128 format. +// It returns the number of consumed bytes. +func (l *LEB128) Unmarshal(buf []byte) (int, error) { + *l = 0 + n := 0 + + for i := 0; i < 8; i++ { + if len(buf) == 0 { + return 0, fmt.Errorf("not enough bytes") + } + + var b byte + b, buf = buf[0], buf[1:] + + *l |= (LEB128(b&0b01111111) << (i * 7)) + n++ + + if (b & 0b10000000) == 0 { + break + } + } + + return n, nil +} + +// MarshalSize returns the marshal size in bytes of the unsigned integer in LEB128 format. +func (l LEB128) MarshalSize() int { + n := 0 + + for { + l >>= 7 + n++ + + if l <= 0 { + break + } + } + + return n +} + +// MarshalTo encodes the unsigned integer with the LEB128 format. +// It returns the number of consumed bytes. +func (l LEB128) MarshalTo(buf []byte) int { + n := 0 + + for { + curbyte := byte(l) & 0b01111111 + l >>= 7 + + if l <= 0 { + buf[n] = curbyte + n++ + break + } + + curbyte |= 0b10000000 + buf[n] = curbyte + n++ + } + + return n +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/obu_header.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/obu_header.go new file mode 100644 index 000000000..c831a05e2 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/obu_header.go @@ -0,0 +1,35 @@ +package av1 + +import ( + "fmt" +) + +// OBUHeader is a OBU header. +// Specification: https://aomediacodec.github.io/av1-spec/#obu-header-syntax +type OBUHeader struct { + Type OBUType + HasSize bool +} + +// Unmarshal decodes a OBUHeader. +func (h *OBUHeader) Unmarshal(buf []byte) error { + if len(buf) < 1 { + return fmt.Errorf("not enough bytes") + } + + forbidden := (buf[0] >> 7) != 0 + if forbidden { + return fmt.Errorf("forbidden bit is set") + } + + h.Type = OBUType(buf[0] >> 3) + + extensionFlag := ((buf[0] >> 2) & 0b1) != 0 + if extensionFlag { + return fmt.Errorf("extension flag is not supported yet") + } + + h.HasSize = ((buf[0] >> 1) & 0b1) != 0 + + return nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/obu_type.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/obu_type.go new file mode 100644 index 000000000..9eca2b3fa --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/obu_type.go @@ -0,0 +1,9 @@ +package av1 + +// OBUType is an OBU type. +type OBUType uint8 + +// OBU types. +const ( + OBUTypeSequenceHeader OBUType = 1 +) diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/sequence_header.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/sequence_header.go new file mode 100644 index 000000000..ecbdccb1a --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/av1/sequence_header.go @@ -0,0 +1,550 @@ +package av1 + +import ( + "fmt" + + "github.com/bluenviron/mediacommon/v2/pkg/bits" +) + +// SequenceHeader_ColorPrimaries is a ColorPrimaries value. +type SequenceHeader_ColorPrimaries uint8 //nolint:revive + +const ( + SequenceHeader_ColorPrimaries_CP_BT_709 SequenceHeader_ColorPrimaries = 1 //nolint:revive + SequenceHeader_ColorPrimaries_CP_UNSPECIFIED SequenceHeader_ColorPrimaries = 2 //nolint:revive +) + +// SequenceHeader_TransferCharacteristics is a TransferCharacteristics value. +type SequenceHeader_TransferCharacteristics uint8 //nolint:revive + +const ( + SequenceHeader_TransferCharacteristics_TC_UNSPECIFIED SequenceHeader_TransferCharacteristics = 2 //nolint:revive + SequenceHeader_TransferCharacteristics_TC_SRGB SequenceHeader_TransferCharacteristics = 13 //nolint:revive +) + +// SequenceHeader_MatrixCoefficients is a MatrixCoefficients value. +type SequenceHeader_MatrixCoefficients uint8 //nolint:revive + +const ( + SequenceHeader_MatrixCoefficients_MC_IDENTITY SequenceHeader_MatrixCoefficients = 0 //nolint:revive + SequenceHeader_MatrixCoefficients_MC_UNSPECIFIED SequenceHeader_MatrixCoefficients = 2 //nolint:revive +) + +// SequenceHeader_ChromaSamplePosition is a ChromaSamplePosition value. +type SequenceHeader_ChromaSamplePosition uint8 //nolint:revive + +const ( + SequenceHeader_ChromaSamplePosition_CSP_UNKNOWN SequenceHeader_ChromaSamplePosition = 0 //nolint:revive +) + +// SequenceHeader_SeqForceScreenContentTools is a SeqForceScreenContentTools value. +type SequenceHeader_SeqForceScreenContentTools uint8 //nolint:revive + +const ( + SequenceHeader_SeqForceScreenContentTools_SELECT_SCREEN_CONTENT_TOOLS SequenceHeader_SeqForceScreenContentTools = 2 //nolint:revive,lll +) + +// SequenceHeader_SeqForceIntegerMv is a SeqForceIntegerMv value. +type SequenceHeader_SeqForceIntegerMv uint8 //nolint:revive + +const ( + SequenceHeader_SeqForceIntegerMv_SELECT_INTEGER_MV SequenceHeader_SeqForceIntegerMv = 2 //nolint:revive +) + +// SequenceHeader_ColorConfig is a color configuration of a sequence header. +type SequenceHeader_ColorConfig struct { //nolint:revive + HighBitDepth bool + TwelveBit bool + BitDepth int + MonoChrome bool + ColorDescriptionPresentFlag bool + ColorPrimaries SequenceHeader_ColorPrimaries + TransferCharacteristics SequenceHeader_TransferCharacteristics + MatrixCoefficients SequenceHeader_MatrixCoefficients + ColorRange bool + SubsamplingX bool + SubsamplingY bool + ChromaSamplePosition SequenceHeader_ChromaSamplePosition +} + +func (c *SequenceHeader_ColorConfig) unmarshal(seqProfile uint8, buf []byte, pos *int) error { + var err error + c.HighBitDepth, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if seqProfile == 2 && c.HighBitDepth { + c.TwelveBit, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if c.TwelveBit { + c.BitDepth = 12 + } else { + c.BitDepth = 10 + } + } else if seqProfile <= 2 { + if c.HighBitDepth { + c.BitDepth = 10 + } else { + c.BitDepth = 8 + } + } + + if seqProfile == 1 { + c.MonoChrome = false + } else { + c.MonoChrome, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + } + + c.ColorDescriptionPresentFlag, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if c.ColorDescriptionPresentFlag { + err = bits.HasSpace(buf, *pos, 24) + if err != nil { + return err + } + + c.ColorPrimaries = SequenceHeader_ColorPrimaries(bits.ReadBitsUnsafe(buf, pos, 8)) + c.TransferCharacteristics = SequenceHeader_TransferCharacteristics(bits.ReadBitsUnsafe(buf, pos, 8)) + c.MatrixCoefficients = SequenceHeader_MatrixCoefficients(bits.ReadBitsUnsafe(buf, pos, 8)) + } else { + c.ColorPrimaries = SequenceHeader_ColorPrimaries_CP_UNSPECIFIED + c.TransferCharacteristics = SequenceHeader_TransferCharacteristics_TC_UNSPECIFIED + c.MatrixCoefficients = SequenceHeader_MatrixCoefficients_MC_UNSPECIFIED + } + + switch { + case c.MonoChrome: + c.ColorRange, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + c.SubsamplingX = true + c.SubsamplingY = true + c.ChromaSamplePosition = SequenceHeader_ChromaSamplePosition_CSP_UNKNOWN + case c.ColorPrimaries == SequenceHeader_ColorPrimaries_CP_BT_709 && + c.TransferCharacteristics == SequenceHeader_TransferCharacteristics_TC_SRGB && + c.MatrixCoefficients == SequenceHeader_MatrixCoefficients_MC_IDENTITY: + c.ColorRange = true + c.SubsamplingX = false + c.SubsamplingY = false + default: + c.ColorRange, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + switch { + case seqProfile == 0: + c.SubsamplingX = true + c.SubsamplingY = true + case seqProfile == 1: + c.SubsamplingX = false + c.SubsamplingY = false + default: + if c.BitDepth == 12 { + c.SubsamplingX, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if c.SubsamplingX { + c.SubsamplingY, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + } else { + c.SubsamplingY = false + } + } else { + c.SubsamplingX = true + c.SubsamplingY = false + } + } + + if c.SubsamplingX && c.SubsamplingY { + tmp, err := bits.ReadBits(buf, pos, 2) + if err != nil { + return err + } + c.ChromaSamplePosition = SequenceHeader_ChromaSamplePosition(tmp) + } + } + + return nil +} + +// SequenceHeader_TimingInfo is the timing_info() struct in the AV1 specification. +type SequenceHeader_TimingInfo struct { //nolint:revive + NumUnitsInDisplayTick uint32 + TimeScale uint32 + EqualPictureInterval bool + NumTicksPerPictureMinus1 uint32 +} + +func (t *SequenceHeader_TimingInfo) unmarshal(buf []byte, pos *int) error { + err := bits.HasSpace(buf, *pos, 65) + if err != nil { + return err + } + + t.NumUnitsInDisplayTick = uint32(bits.ReadBitsUnsafe(buf, pos, 32)) + t.TimeScale = uint32(bits.ReadBitsUnsafe(buf, pos, 32)) + t.EqualPictureInterval = bits.ReadFlagUnsafe(buf, pos) + + if t.EqualPictureInterval { + t.NumTicksPerPictureMinus1, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + } else { + t.NumTicksPerPictureMinus1 = 0 + } + + return nil +} + +// SequenceHeader is a AV1 Sequence header OBU. +// Specification: https://aomediacodec.github.io/av1-spec/#sequence-header-obu-syntax +type SequenceHeader struct { + SeqProfile uint8 + StillPicture bool + ReducedStillPictureHeader bool + TimingInfo *SequenceHeader_TimingInfo + DecoderModelInfoPresentFlag bool + InitialDisplayDelayPresentFlag bool + OperatingPointsCntMinus1 uint8 + OperatingPointIdc []uint16 + SeqLevelIdx []uint8 + SeqTier []bool + DecoderModelPresentForThisOp []bool + InitialDisplayPresentForThisOp []bool + InitialDisplayDelayMinus1 []uint8 + MaxFrameWidthMinus1 uint32 + MaxFrameHeightMinus1 uint32 + FrameIDNumbersPresentFlag bool + DeltaFrameIDLengthMinus2 uint8 + AdditionalFrameIDLengthMinus1 uint8 + Use128x128Superblock bool + EnableFilterIntra bool + EnableIntraEdgeFilter bool + EnableInterintraCompound bool + EnableMaskedCompound bool + EnableWarpedMotion bool + EnableDualFilter bool + EnableOrderHint bool + EnableJntComp bool + EnableRefFrameMvs bool + SeqChooseScreenContentTools bool + SeqForceScreenContentTools SequenceHeader_SeqForceScreenContentTools + SeqChooseIntegerMv bool + SeqForceIntegerMv SequenceHeader_SeqForceIntegerMv + OrderHintBitsMinus1 uint8 + EnableSuperRes bool + EnableCdef bool + EnableRestoration bool + ColorConfig SequenceHeader_ColorConfig + FilmGrainParamsPresent bool +} + +// Unmarshal decodes a SequenceHeader. +func (h *SequenceHeader) Unmarshal(buf []byte) error { + var oh OBUHeader + err := oh.Unmarshal(buf) + if err != nil { + return err + } + buf = buf[1:] + + if oh.HasSize { + var size LEB128 + var n int + n, err = size.Unmarshal(buf) + if err != nil { + return err + } + + buf = buf[n:] + if len(buf) != int(size) { + return fmt.Errorf("wrong buffer size: expected %d, got %d", size, len(buf)) + } + } + + pos := 0 + + err = bits.HasSpace(buf, pos, 5) + if err != nil { + return err + } + + h.SeqProfile = uint8(bits.ReadBitsUnsafe(buf, &pos, 3)) + h.StillPicture = bits.ReadFlagUnsafe(buf, &pos) + h.ReducedStillPictureHeader = bits.ReadFlagUnsafe(buf, &pos) + + if h.ReducedStillPictureHeader { + h.TimingInfo = nil + h.DecoderModelInfoPresentFlag = false + h.InitialDisplayDelayPresentFlag = false + h.OperatingPointsCntMinus1 = 0 + h.OperatingPointIdc = []uint16{0} + + err = bits.HasSpace(buf, pos, 5) + if err != nil { + return err + } + + h.SeqLevelIdx = []uint8{uint8(bits.ReadBitsUnsafe(buf, &pos, 5))} + h.SeqTier = []bool{false} + h.DecoderModelPresentForThisOp = []bool{false} + h.InitialDisplayPresentForThisOp = []bool{false} + } else { + var timingInfoPresentFlag bool + timingInfoPresentFlag, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + if timingInfoPresentFlag { + h.TimingInfo = &SequenceHeader_TimingInfo{} + err = h.TimingInfo.unmarshal(buf, &pos) + if err != nil { + return err + } + + h.DecoderModelInfoPresentFlag, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + if h.DecoderModelInfoPresentFlag { + return fmt.Errorf("decoder_model_info_present_flag is not supported yet") + } + } else { + h.TimingInfo = nil + h.DecoderModelInfoPresentFlag = false + } + + err = bits.HasSpace(buf, pos, 6) + if err != nil { + return err + } + + h.InitialDisplayDelayPresentFlag = bits.ReadFlagUnsafe(buf, &pos) + h.OperatingPointsCntMinus1 = uint8(bits.ReadBitsUnsafe(buf, &pos, 5)) + + h.OperatingPointIdc = make([]uint16, h.OperatingPointsCntMinus1+1) + h.SeqLevelIdx = make([]uint8, h.OperatingPointsCntMinus1+1) + h.SeqTier = make([]bool, h.OperatingPointsCntMinus1+1) + h.DecoderModelPresentForThisOp = make([]bool, h.OperatingPointsCntMinus1+1) + h.InitialDisplayPresentForThisOp = make([]bool, h.OperatingPointsCntMinus1+1) + h.InitialDisplayDelayMinus1 = make([]uint8, h.OperatingPointsCntMinus1+1) + + for i := uint8(0); i <= h.OperatingPointsCntMinus1; i++ { + err = bits.HasSpace(buf, pos, 17) + if err != nil { + return err + } + + h.OperatingPointIdc[i] = uint16(bits.ReadBitsUnsafe(buf, &pos, 12)) + h.SeqLevelIdx[i] = uint8(bits.ReadBitsUnsafe(buf, &pos, 5)) + + if h.SeqLevelIdx[i] > 7 { + h.SeqTier[i], err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + } else { + h.SeqTier[i] = false + } + + if h.DecoderModelInfoPresentFlag { + return fmt.Errorf("decoder_model_info_present_flag is not supported yet") + } + h.DecoderModelPresentForThisOp[i] = false + + if h.InitialDisplayDelayPresentFlag { + h.InitialDisplayPresentForThisOp[i], err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + if h.InitialDisplayPresentForThisOp[i] { + var tmp uint64 + tmp, err = bits.ReadBits(buf, &pos, 4) + if err != nil { + return err + } + h.InitialDisplayDelayMinus1[i] = uint8(tmp) + } + return fmt.Errorf("initial_display_delay_present_flag is not supported yet") + } + } + } + + err = bits.HasSpace(buf, pos, 8) + if err != nil { + return err + } + + frameWidthBitsMinus1 := int(bits.ReadBitsUnsafe(buf, &pos, 4)) + frameHeightBitsMinus1 := int(bits.ReadBitsUnsafe(buf, &pos, 4)) + + n1 := (frameWidthBitsMinus1 + 1) + n2 := (frameHeightBitsMinus1 + 1) + + err = bits.HasSpace(buf, pos, n1+n2) + if err != nil { + return err + } + + h.MaxFrameWidthMinus1 = uint32(bits.ReadBitsUnsafe(buf, &pos, n1)) + h.MaxFrameHeightMinus1 = uint32(bits.ReadBitsUnsafe(buf, &pos, n2)) + + if h.ReducedStillPictureHeader { + h.FrameIDNumbersPresentFlag = false + } else { + h.FrameIDNumbersPresentFlag, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + if h.FrameIDNumbersPresentFlag { + err = bits.HasSpace(buf, pos, 7) + if err != nil { + return err + } + + h.DeltaFrameIDLengthMinus2 = uint8(bits.ReadBitsUnsafe(buf, &pos, 4)) + h.AdditionalFrameIDLengthMinus1 = uint8(bits.ReadBitsUnsafe(buf, &pos, 3)) + } + } + + err = bits.HasSpace(buf, pos, 3) + if err != nil { + return err + } + + h.Use128x128Superblock = bits.ReadFlagUnsafe(buf, &pos) + h.EnableFilterIntra = bits.ReadFlagUnsafe(buf, &pos) + h.EnableIntraEdgeFilter = bits.ReadFlagUnsafe(buf, &pos) + + if h.ReducedStillPictureHeader { + h.EnableInterintraCompound = false + h.EnableMaskedCompound = false + h.EnableWarpedMotion = false + h.EnableDualFilter = false + h.EnableOrderHint = false + h.EnableJntComp = false + h.EnableRefFrameMvs = false + h.SeqForceScreenContentTools = SequenceHeader_SeqForceScreenContentTools_SELECT_SCREEN_CONTENT_TOOLS + h.SeqForceIntegerMv = SequenceHeader_SeqForceIntegerMv_SELECT_INTEGER_MV + } else { + err = bits.HasSpace(buf, pos, 5) + if err != nil { + return err + } + + h.EnableInterintraCompound = bits.ReadFlagUnsafe(buf, &pos) + h.EnableMaskedCompound = bits.ReadFlagUnsafe(buf, &pos) + h.EnableWarpedMotion = bits.ReadFlagUnsafe(buf, &pos) + h.EnableDualFilter = bits.ReadFlagUnsafe(buf, &pos) + h.EnableOrderHint = bits.ReadFlagUnsafe(buf, &pos) + + if h.EnableOrderHint { + err = bits.HasSpace(buf, pos, 2) + if err != nil { + return err + } + + h.EnableJntComp = bits.ReadFlagUnsafe(buf, &pos) + h.EnableRefFrameMvs = bits.ReadFlagUnsafe(buf, &pos) + } + + h.SeqChooseScreenContentTools, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + if h.SeqChooseScreenContentTools { + h.SeqForceScreenContentTools = SequenceHeader_SeqForceScreenContentTools_SELECT_SCREEN_CONTENT_TOOLS + } else { + var tmp uint64 + tmp, err = bits.ReadBits(buf, &pos, 1) + if err != nil { + return err + } + h.SeqForceScreenContentTools = SequenceHeader_SeqForceScreenContentTools(tmp) + } + + if h.SeqForceScreenContentTools > 0 { + h.SeqChooseIntegerMv, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + if h.SeqChooseIntegerMv { + h.SeqForceIntegerMv = SequenceHeader_SeqForceIntegerMv_SELECT_INTEGER_MV + } else { + var tmp uint64 + tmp, err = bits.ReadBits(buf, &pos, 1) + if err != nil { + return err + } + h.SeqForceIntegerMv = SequenceHeader_SeqForceIntegerMv(tmp) + } + } else { + h.SeqForceIntegerMv = SequenceHeader_SeqForceIntegerMv_SELECT_INTEGER_MV + } + + if h.EnableOrderHint { + var tmp uint64 + tmp, err = bits.ReadBits(buf, &pos, 3) + if err != nil { + return err + } + h.OrderHintBitsMinus1 = uint8(tmp) + } + } + + err = bits.HasSpace(buf, pos, 3) + if err != nil { + return err + } + + h.EnableSuperRes = bits.ReadFlagUnsafe(buf, &pos) + h.EnableCdef = bits.ReadFlagUnsafe(buf, &pos) + h.EnableRestoration = bits.ReadFlagUnsafe(buf, &pos) + + err = h.ColorConfig.unmarshal(h.SeqProfile, buf, &pos) + if err != nil { + return err + } + + err = bits.HasSpace(buf, pos, 1) + if err != nil { + return err + } + + h.FilmGrainParamsPresent = bits.ReadFlagUnsafe(buf, &pos) + + return nil +} + +// Width returns the video width. +func (h SequenceHeader) Width() int { + return int(h.MaxFrameWidthMinus1 + 1) +} + +// Height returns the video height. +func (h SequenceHeader) Height() int { + return int(h.MaxFrameHeightMinus1 + 1) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/annexb.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/annexb.go new file mode 100644 index 000000000..b7eb6d9d1 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/annexb.go @@ -0,0 +1,147 @@ +package h264 + +import ( + "errors" + "fmt" +) + +// ErrAnnexBNoNALUs is returned by AnnexBUnmarshal when no NALUs have been decoded. +var ErrAnnexBNoNALUs = errors.New("Annex-B unit doesn't contain any NALU") + +// ErrAnnexBNoInitialDelimiter is returned by AnnexBUnmarshal when the initial delimiter is not found. +var ErrAnnexBNoInitialDelimiter = errors.New("initial delimiter not found") + +// countNalUnits counts the number of NAL units in the Annex-B stream. +func countNalUnits(buf []byte) (int, error) { + n := 0 + i := 0 + start := 0 + auSize := 0 + + for i < len(buf) { + lim := 4 + if lim > len(buf)-i { + lim = len(buf) - i + } + data := buf[i : i+lim] + + switch { + case len(data) >= 3 && data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x01: + if i > start { + auSize += i - start + if auSize > MaxAccessUnitSize { + return 0, fmt.Errorf("access unit size (%d) is too big, maximum is %d", auSize, MaxAccessUnitSize) + } + n++ + } + i += 3 + start = i + case len(data) >= 4 && data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x00 && data[3] == 0x01: + if i > start { + auSize += i - start + if auSize > MaxAccessUnitSize { + return 0, fmt.Errorf("access unit size (%d) is too big, maximum is %d", auSize, MaxAccessUnitSize) + } + n++ + } + i += 4 + start = i + default: + i++ + } + } + + if i > start { + if (auSize + i - start) > MaxAccessUnitSize { + return 0, fmt.Errorf("access unit size (%d) is too big, maximum is %d", auSize+i-start, MaxAccessUnitSize) + } + n++ + } + + return n, nil +} + +func hasInitialDelimiter(buf []byte) bool { + if len(buf) < 4 { + return false + } + return buf[0] == 0x00 && buf[1] == 0x00 && (buf[2] == 0x00 && buf[3] == 0x01) || (buf[2] == 0x01) +} + +// AnnexB is an access unit that can be decoded/encoded from/to the Annex-B stream format. +// Specification: ITU-T Rec. H.264, Annex B +type AnnexB [][]byte + +// Unmarshal decodes an access unit from the Annex-B stream format. +func (a *AnnexB) Unmarshal(buf []byte) error { + count, err := countNalUnits(buf) + if err != nil { + return err + } + + if count == 0 { + return ErrAnnexBNoNALUs + } + + if !hasInitialDelimiter(buf) { + return ErrAnnexBNoInitialDelimiter + } + + *a = make([][]byte, 0, count) + i := 0 + start := 0 + + for i < len(buf) { + lim := 4 + if lim > len(buf)-i { + lim = len(buf) - i + } + data := buf[i : i+lim] + + switch { + case len(data) >= 3 && data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x01: + // Is this a NALU with a 3 byte start code prefix + if i > start { + *a = append(*a, buf[start:i]) + } + i += 3 + start = i + case len(data) >= 4 && data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x00 && data[3] == 0x01: + // OR is this a NALU with a 4 byte start code prefix + if i > start { + *a = append(*a, buf[start:i]) + } + i += 4 + start = i + default: + i++ + } + } + + if i > start { + *a = append(*a, buf[start:i]) + } + + return nil +} + +func (a AnnexB) marshalSize() int { + n := 0 + for _, nalu := range a { + n += 4 + len(nalu) + } + return n +} + +// Marshal encodes an access unit into the Annex-B stream format. +func (a AnnexB) Marshal() ([]byte, error) { + buf := make([]byte, a.marshalSize()) + pos := 0 + + for _, nalu := range a { + pos += copy(buf[pos:], []byte{0x00, 0x00, 0x00, 0x01}) + pos += copy(buf[pos:], nalu) + } + + return buf, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/avcc.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/avcc.go new file mode 100644 index 000000000..1bf621504 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/avcc.go @@ -0,0 +1,100 @@ +package h264 + +import ( + "errors" + "fmt" +) + +// ErrAVCCNoNALUs is returned by AVCCUnmarshal when no NALUs have been decoded. +var ErrAVCCNoNALUs = errors.New("AVCC unit doesn't contain any NALU") + +// AVCC is an access unit that can be decoded/encoded from/to the Annex-B stream format. +// Specification: ISO 14496-15, section 5.3.4.2.1 +type AVCC [][]byte + +// Unmarshal decodes an access unit from the AVCC stream format. +func (a *AVCC) Unmarshal(buf []byte) error { + bl := len(buf) + pos := 0 + n := 0 + auSize := 0 + + for { + if (bl - pos) < 4 { + return fmt.Errorf("invalid length") + } + + l := int(uint32(buf[pos])<<24 | uint32(buf[pos+1])<<16 | uint32(buf[pos+2])<<8 | uint32(buf[pos+3])) + pos += 4 + + if l != 0 { + if (auSize + l) > MaxAccessUnitSize { + return fmt.Errorf("access unit size (%d) is too big, maximum is %d", auSize+l, MaxAccessUnitSize) + } + + if (bl - pos) < l { + return fmt.Errorf("invalid length") + } + + auSize += l + n++ + pos += l + } + + if (bl - pos) == 0 { + break + } + } + + if n == 0 { + return ErrAVCCNoNALUs + } + + if n > MaxNALUsPerAccessUnit { + return fmt.Errorf("NALU count (%d) exceeds maximum allowed (%d)", + n, MaxNALUsPerAccessUnit) + } + + *a = make([][]byte, n) + pos = 0 + + for i := 0; i < n; { + l := int(uint32(buf[pos])<<24 | uint32(buf[pos+1])<<16 | uint32(buf[pos+2])<<8 | uint32(buf[pos+3])) + pos += 4 + + if l != 0 { + (*a)[i] = buf[pos : pos+l] + pos += l + i++ + } + } + + return nil +} + +func (a AVCC) marshalSize() int { + n := 0 + for _, nalu := range a { + n += 4 + len(nalu) + } + return n +} + +// Marshal encodes an access unit into the AVCC stream format. +func (a AVCC) Marshal() ([]byte, error) { + buf := make([]byte, a.marshalSize()) + pos := 0 + + for _, nalu := range a { + naluLen := len(nalu) + buf[pos] = byte(naluLen >> 24) + buf[pos+1] = byte(naluLen >> 16) + buf[pos+2] = byte(naluLen >> 8) + buf[pos+3] = byte(naluLen) + pos += 4 + + pos += copy(buf[pos:], nalu) + } + + return buf, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/dts_extractor.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/dts_extractor.go new file mode 100644 index 000000000..a069d00c8 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/dts_extractor.go @@ -0,0 +1,248 @@ +package h264 + +import ( + "bytes" + "fmt" + + "github.com/bluenviron/mediacommon/v2/pkg/bits" +) + +const ( + maxReorderedFrames = 10 + /* + (max_size(first_mb_in_slice) + max_size(slice_type) + max_size(pic_parameter_set_id) + + max_size(frame_num) + max_size(pic_order_cnt_lsb)) * 4 / 3 = + (3 * max_size(golomb) + (max(Log2MaxFrameNumMinus4) + 4) / 8 + (max(Log2MaxPicOrderCntLsbMinus4) + 4) / 8) * 4 / 3 = + (3 * 4 + 2 + 2) * 4 / 3 = 22 + */ + maxBytesToGetPOC = 22 +) + +func getPictureOrderCount(buf []byte, sps *SPS, idr bool) (uint32, error) { + buf = buf[1:] + lb := len(buf) + + if lb > maxBytesToGetPOC { + lb = maxBytesToGetPOC + } + + buf = EmulationPreventionRemove(buf[:lb]) + pos := 0 + + _, err := bits.ReadGolombUnsigned(buf, &pos) // first_mb_in_slice + if err != nil { + return 0, err + } + + _, err = bits.ReadGolombUnsigned(buf, &pos) // slice_type + if err != nil { + return 0, err + } + + _, err = bits.ReadGolombUnsigned(buf, &pos) // pic_parameter_set_id + if err != nil { + return 0, err + } + + _, err = bits.ReadBits(buf, &pos, int(sps.Log2MaxFrameNumMinus4+4)) // frame_num + if err != nil { + return 0, err + } + + if idr { + _, err = bits.ReadGolombUnsigned(buf, &pos) // idr_pic_id + if err != nil { + return 0, err + } + } + + picOrderCntLsb, err := bits.ReadBits(buf, &pos, int(sps.Log2MaxPicOrderCntLsbMinus4+4)) + if err != nil { + return 0, err + } + + return uint32(picOrderCntLsb), nil +} + +func getPictureOrderCountDiff(a uint32, b uint32, sps *SPS) int32 { + maxVal := uint32(1 << (sps.Log2MaxPicOrderCntLsbMinus4 + 4)) + d := (a - b) & (maxVal - 1) + if d > (maxVal / 2) { + return int32(d) - int32(maxVal) + } + return int32(d) +} + +// DTSExtractor computes DTS from PTS. +type DTSExtractor struct { + sps []byte + spsp *SPS + prevDTSFilled bool + prevDTS int64 + expectedPOC uint32 + reorderedFrames int + pauseDTS int + pocIncrement int +} + +// Initialize initializes a DTSExtractor. +func (d *DTSExtractor) Initialize() { + d.pocIncrement = 2 +} + +// NewDTSExtractor allocates a DTSExtractor. +// +// Deprecated: replaced by DTSExtractor.Initialize. +func NewDTSExtractor() *DTSExtractor { + d := &DTSExtractor{} + d.Initialize() + return d +} + +func (d *DTSExtractor) extractInner(au [][]byte, pts int64) (int64, bool, error) { + var idr []byte + var nonIDR []byte + // a value of 00 indicates that the content of the NAL unit is not + // used to reconstruct reference pictures for inter picture + // prediction. Such NAL units can be discarded without risking + // the integrity of the reference pictures. Values greater than + // 00 indicate that the decoding of the NAL unit is required to + // maintain the integrity of the reference pictures. + nonZeroNalRefIDFound := false + + for _, nalu := range au { + typ := NALUType(nalu[0] & 0x1F) + nonZeroNalRefIDFound = nonZeroNalRefIDFound || ((nalu[0] & 0x60) > 0) + switch typ { + case NALUTypeSPS: + if !bytes.Equal(d.sps, nalu) { + var spsp SPS + err := spsp.Unmarshal(nalu) + if err != nil { + return 0, false, fmt.Errorf("invalid SPS: %w", err) + } + d.sps = nalu + d.spsp = &spsp + + // reset state + d.reorderedFrames = 0 + d.pocIncrement = 2 + } + + case NALUTypeIDR: + idr = nalu + + case NALUTypeNonIDR: + nonIDR = nalu + } + } + + if d.spsp == nil { + return 0, false, fmt.Errorf("SPS not received yet") + } + + if d.spsp.PicOrderCntType == 2 || !d.spsp.FrameMbsOnlyFlag { + return pts, false, nil + } + + if d.spsp.PicOrderCntType == 1 { + return 0, false, fmt.Errorf("pic_order_cnt_type = 1 is not supported yet") + } + + // Implicit processing of PicOrderCountType 0 + switch { + case idr != nil: + d.pauseDTS = 0 + + var err error + d.expectedPOC, err = getPictureOrderCount(idr, d.spsp, true) + if err != nil { + return 0, false, err + } + + if !d.prevDTSFilled || d.reorderedFrames == 0 { + return pts, false, nil + } + + return d.prevDTS + (pts-d.prevDTS)/int64(d.reorderedFrames+1), false, nil + + case nonIDR != nil: + d.expectedPOC += uint32(d.pocIncrement) + d.expectedPOC &= ((1 << (d.spsp.Log2MaxPicOrderCntLsbMinus4 + 4)) - 1) + + if d.pauseDTS > 0 { + d.pauseDTS-- + return d.prevDTS + 90, true, nil + } + + poc, err := getPictureOrderCount(nonIDR, d.spsp, false) + if err != nil { + return 0, false, err + } + + if d.pocIncrement == 2 && (poc%2) != 0 { + d.pocIncrement = 1 + d.expectedPOC /= 2 + } + + pocDiff := int(getPictureOrderCountDiff(poc, d.expectedPOC, d.spsp)) / d.pocIncrement + limit := -(d.reorderedFrames + 1) + + // this happens when there are B-frames immediately following an IDR frame + if pocDiff < limit { + increase := limit - pocDiff + if (d.reorderedFrames + increase) > maxReorderedFrames { + return 0, false, fmt.Errorf("too many reordered frames (%d)", d.reorderedFrames+increase) + } + + d.reorderedFrames += increase + d.pauseDTS = increase + return d.prevDTS + 90, true, nil + } + + if pocDiff == limit { + return pts, false, nil + } + + if pocDiff > d.reorderedFrames { + increase := pocDiff - d.reorderedFrames + if (d.reorderedFrames + increase) > maxReorderedFrames { + return 0, false, fmt.Errorf("too many reordered frames (%d)", d.reorderedFrames+increase) + } + + d.reorderedFrames += increase + d.pauseDTS = increase - 1 + return d.prevDTS + 90, false, nil + } + + return d.prevDTS + (pts-d.prevDTS)/int64(pocDiff+d.reorderedFrames+1), false, nil + + case !nonZeroNalRefIDFound: + return d.prevDTS, false, nil + + default: + return 0, false, fmt.Errorf("access unit doesn't contain an IDR or non-IDR NALU") + } +} + +// Extract extracts the DTS of an access unit. +func (d *DTSExtractor) Extract(au [][]byte, pts int64) (int64, error) { + dts, skipChecks, err := d.extractInner(au, pts) + if err != nil { + return 0, err + } + + if !skipChecks && dts > pts { + return 0, fmt.Errorf("DTS is greater than PTS") + } + + if d.prevDTSFilled && dts < d.prevDTS { + return 0, fmt.Errorf("DTS is not monotonically increasing, was %v, now is %v", + d.prevDTS, dts) + } + + d.prevDTS = dts + d.prevDTSFilled = true + + return dts, err +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/emulation_prevention.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/emulation_prevention.go new file mode 100644 index 000000000..f06c3904c --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/emulation_prevention.go @@ -0,0 +1,36 @@ +package h264 + +// EmulationPreventionRemove removes emulation prevention bytes from a NALU. +// Specification: ITU-T Rec. H.264, 7.4.1 NAL unit semantics +func EmulationPreventionRemove(nalu []byte) []byte { + // 0x00 0x00 0x03 0x00 -> 0x00 0x00 0x00 + // 0x00 0x00 0x03 0x01 -> 0x00 0x00 0x01 + // 0x00 0x00 0x03 0x02 -> 0x00 0x00 0x02 + // 0x00 0x00 0x03 0x03 -> 0x00 0x00 0x03 + + l := len(nalu) + n := l + + for i := 2; i < l; i++ { + if nalu[i-2] == 0 && nalu[i-1] == 0 && nalu[i] == 3 { + n-- + i += 2 + } + } + + ret := make([]byte, n) + pos := 0 + start := 0 + + for i := 2; i < l; i++ { + if nalu[i-2] == 0 && nalu[i-1] == 0 && nalu[i] == 3 { + pos += copy(ret[pos:], nalu[start:i]) + start = i + 1 + i += 2 + } + } + + copy(ret[pos:], nalu[start:]) + + return ret +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/h264.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/h264.go new file mode 100644 index 000000000..aea95937f --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/h264.go @@ -0,0 +1,12 @@ +// Package h264 contains utilities to work with the H264 codec. +package h264 + +const ( + // MaxAccessUnitSize is the maximum size of an access unit. + // With a 50 Mbps 2160p60 H264 video, the maximum size does not seem to exceed 8 MiB. + MaxAccessUnitSize = 8 * 1024 * 1024 + + // MaxNALUsPerAccessUnit is the maximum number of NALUs per access unit. + // with x264, tune=zerolatency and 4K resolution, NALU count is lower than 25. + MaxNALUsPerAccessUnit = 25 +) diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/is_random_access.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/is_random_access.go new file mode 100644 index 000000000..c1f174462 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/is_random_access.go @@ -0,0 +1,12 @@ +package h264 + +// IsRandomAccess checks whether the access unit can be randomly accessed. +func IsRandomAccess(au [][]byte) bool { + for _, nalu := range au { + typ := NALUType(nalu[0] & 0x1F) + if typ == NALUTypeIDR { + return true + } + } + return false +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/nalu_type.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/nalu_type.go new file mode 100644 index 000000000..30f580a14 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/nalu_type.go @@ -0,0 +1,84 @@ +package h264 + +import ( + "fmt" +) + +// NALUType is the type of a NALU. +// Specification: ITU-T Rec. H.264, Table 7-1 +type NALUType uint8 + +// NALU types. +const ( + NALUTypeNonIDR NALUType = 1 + NALUTypeDataPartitionA NALUType = 2 + NALUTypeDataPartitionB NALUType = 3 + NALUTypeDataPartitionC NALUType = 4 + NALUTypeIDR NALUType = 5 + NALUTypeSEI NALUType = 6 + NALUTypeSPS NALUType = 7 + NALUTypePPS NALUType = 8 + NALUTypeAccessUnitDelimiter NALUType = 9 + NALUTypeEndOfSequence NALUType = 10 + NALUTypeEndOfStream NALUType = 11 + NALUTypeFillerData NALUType = 12 + NALUTypeSPSExtension NALUType = 13 + NALUTypePrefix NALUType = 14 + NALUTypeSubsetSPS NALUType = 15 + NALUTypeReserved16 NALUType = 16 + NALUTypeReserved17 NALUType = 17 + NALUTypeReserved18 NALUType = 18 + NALUTypeSliceLayerWithoutPartitioning NALUType = 19 + NALUTypeSliceExtension NALUType = 20 + NALUTypeSliceExtensionDepth NALUType = 21 + NALUTypeReserved22 NALUType = 22 + NALUTypeReserved23 NALUType = 23 + + // additional NALU types for RTP/H264 + NALUTypeSTAPA NALUType = 24 + NALUTypeSTAPB NALUType = 25 + NALUTypeMTAP16 NALUType = 26 + NALUTypeMTAP24 NALUType = 27 + NALUTypeFUA NALUType = 28 + NALUTypeFUB NALUType = 29 +) + +var naluTypeLabels = map[NALUType]string{ + NALUTypeNonIDR: "NonIDR", + NALUTypeDataPartitionA: "DataPartitionA", + NALUTypeDataPartitionB: "DataPartitionB", + NALUTypeDataPartitionC: "DataPartitionC", + NALUTypeIDR: "IDR", + NALUTypeSEI: "SEI", + NALUTypeSPS: "SPS", + NALUTypePPS: "PPS", + NALUTypeAccessUnitDelimiter: "AccessUnitDelimiter", + NALUTypeEndOfSequence: "EndOfSequence", + NALUTypeEndOfStream: "EndOfStream", + NALUTypeFillerData: "FillerData", + NALUTypeSPSExtension: "SPSExtension", + NALUTypePrefix: "Prefix", + NALUTypeSubsetSPS: "SubsetSPS", + NALUTypeReserved16: "Reserved16", + NALUTypeReserved17: "Reserved17", + NALUTypeReserved18: "Reserved18", + NALUTypeSliceLayerWithoutPartitioning: "SliceLayerWithoutPartitioning", + NALUTypeSliceExtension: "SliceExtension", + NALUTypeSliceExtensionDepth: "SliceExtensionDepth", + NALUTypeReserved22: "Reserved22", + NALUTypeReserved23: "Reserved23", + NALUTypeSTAPA: "STAP-A", + NALUTypeSTAPB: "STAP-B", + NALUTypeMTAP16: "MTAP-16", + NALUTypeMTAP24: "MTAP-24", + NALUTypeFUA: "FU-A", + NALUTypeFUB: "FU-B", +} + +// String implements fmt.Stringer. +func (nt NALUType) String() string { + if l, ok := naluTypeLabels[nt]; ok { + return l + } + return fmt.Sprintf("unknown (%d)", nt) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/sps.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/sps.go new file mode 100644 index 000000000..e6a998310 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h264/sps.go @@ -0,0 +1,809 @@ +package h264 + +import ( + "fmt" + + "github.com/bluenviron/mediacommon/v2/pkg/bits" +) + +const ( + maxRefFrames = 255 +) + +func readScalingList(buf []byte, pos *int, size int) ([]int32, bool, error) { + lastScale := int32(8) + nextScale := int32(8) + scalingList := make([]int32, size) + var useDefaultScalingMatrixFlag bool + + for j := 0; j < size; j++ { + if nextScale != 0 { + deltaScale, err := bits.ReadGolombSigned(buf, pos) + if err != nil { + return nil, false, err + } + + nextScale = (lastScale + deltaScale + 256) % 256 + useDefaultScalingMatrixFlag = (j == 0 && nextScale == 0) + } + + if nextScale == 0 { + scalingList[j] = lastScale + } else { + scalingList[j] = nextScale + } + + lastScale = scalingList[j] + } + + return scalingList, useDefaultScalingMatrixFlag, nil +} + +// SPS_HRD is a hypotetical reference decoder. +type SPS_HRD struct { //nolint:revive + CpbCntMinus1 uint32 + BitRateScale uint8 + CpbSizeScale uint8 + BitRateValueMinus1 []uint32 + CpbSizeValueMinus1 []uint32 + CbrFlag []bool + InitialCpbRemovalDelayLengthMinus1 uint8 + CpbRemovalDelayLengthMinus1 uint8 + DpbOutputDelayLengthMinus1 uint8 + TimeOffsetLength uint8 +} + +func (h *SPS_HRD) unmarshal(buf []byte, pos *int) error { + var err error + h.CpbCntMinus1, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + err = bits.HasSpace(buf, *pos, 8) + if err != nil { + return err + } + + if h.CpbCntMinus1 > 31 { + return fmt.Errorf("invalid cpb_cnt_minus1") + } + + h.BitRateScale = uint8(bits.ReadBitsUnsafe(buf, pos, 4)) + h.CpbSizeScale = uint8(bits.ReadBitsUnsafe(buf, pos, 4)) + + h.BitRateValueMinus1 = make([]uint32, h.CpbCntMinus1+1) + h.CpbSizeValueMinus1 = make([]uint32, h.CpbCntMinus1+1) + h.CbrFlag = make([]bool, h.CpbCntMinus1+1) + + for i := uint32(0); i <= h.CpbCntMinus1; i++ { + h.BitRateValueMinus1[i], err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + h.CpbSizeValueMinus1[i], err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + h.CbrFlag[i], err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + } + + err = bits.HasSpace(buf, *pos, 5+5+5+5) + if err != nil { + return err + } + + h.InitialCpbRemovalDelayLengthMinus1 = uint8(bits.ReadBitsUnsafe(buf, pos, 5)) + h.CpbRemovalDelayLengthMinus1 = uint8(bits.ReadBitsUnsafe(buf, pos, 5)) + h.DpbOutputDelayLengthMinus1 = uint8(bits.ReadBitsUnsafe(buf, pos, 5)) + h.TimeOffsetLength = uint8(bits.ReadBitsUnsafe(buf, pos, 5)) + + return nil +} + +// SPS_TimingInfo is a timing info. +type SPS_TimingInfo struct { //nolint:revive + NumUnitsInTick uint32 + TimeScale uint32 + FixedFrameRateFlag bool +} + +func (t *SPS_TimingInfo) unmarshal(buf []byte, pos *int) error { + err := bits.HasSpace(buf, *pos, 32+32+1) + if err != nil { + return err + } + + t.NumUnitsInTick = uint32(bits.ReadBitsUnsafe(buf, pos, 32)) + t.TimeScale = uint32(bits.ReadBitsUnsafe(buf, pos, 32)) + t.FixedFrameRateFlag = bits.ReadFlagUnsafe(buf, pos) + + return nil +} + +// SPS_BitstreamRestriction are bitstream restriction infos. +type SPS_BitstreamRestriction struct { //nolint:revive + MotionVectorsOverPicBoundariesFlag bool + MaxBytesPerPicDenom uint32 + MaxBitsPerMbDenom uint32 + Log2MaxMvLengthHorizontal uint32 + Log2MaxMvLengthVertical uint32 + MaxNumReorderFrames uint32 + MaxDecFrameBuffering uint32 +} + +func (r *SPS_BitstreamRestriction) unmarshal(buf []byte, pos *int) error { + var err error + r.MotionVectorsOverPicBoundariesFlag, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + r.MaxBytesPerPicDenom, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + r.MaxBitsPerMbDenom, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + r.Log2MaxMvLengthHorizontal, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + r.Log2MaxMvLengthVertical, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + r.MaxNumReorderFrames, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + r.MaxDecFrameBuffering, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + return nil +} + +// SPS_VUI is a video usability information. +type SPS_VUI struct { //nolint:revive + AspectRatioInfoPresentFlag bool + + // AspectRatioInfoPresentFlag == true + AspectRatioIdc uint8 + SarWidth uint16 + SarHeight uint16 + + OverscanInfoPresentFlag bool + + // OverscanInfoPresentFlag == true + OverscanAppropriateFlag bool + VideoSignalTypePresentFlag bool + + // VideoSignalTypePresentFlag == true + VideoFormat uint8 + VideoFullRangeFlag bool + ColourDescriptionPresentFlag bool + + // ColourDescriptionPresentFlag == true + ColourPrimaries uint8 + TransferCharacteristics uint8 + MatrixCoefficients uint8 + + ChromaLocInfoPresentFlag bool + + // ChromaLocInfoPresentFlag == true + ChromaSampleLocTypeTopField uint32 + ChromaSampleLocTypeBottomField uint32 + + TimingInfo *SPS_TimingInfo + NalHRD *SPS_HRD + VclHRD *SPS_HRD + + LowDelayHrdFlag bool + PicStructPresentFlag bool + BitstreamRestriction *SPS_BitstreamRestriction +} + +func (v *SPS_VUI) unmarshal(buf []byte, pos *int) error { + var err error + v.AspectRatioInfoPresentFlag, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if v.AspectRatioInfoPresentFlag { + var tmp uint64 + tmp, err = bits.ReadBits(buf, pos, 8) + if err != nil { + return err + } + v.AspectRatioIdc = uint8(tmp) + + if v.AspectRatioIdc == 255 { // Extended_SAR + err = bits.HasSpace(buf, *pos, 32) + if err != nil { + return err + } + + v.SarWidth = uint16(bits.ReadBitsUnsafe(buf, pos, 16)) + v.SarHeight = uint16(bits.ReadBitsUnsafe(buf, pos, 16)) + } + } + + v.OverscanInfoPresentFlag, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if v.OverscanInfoPresentFlag { + v.OverscanAppropriateFlag, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + } + + v.VideoSignalTypePresentFlag, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if v.VideoSignalTypePresentFlag { + err = bits.HasSpace(buf, *pos, 5) + if err != nil { + return err + } + + v.VideoFormat = uint8(bits.ReadBitsUnsafe(buf, pos, 3)) + v.VideoFullRangeFlag = bits.ReadFlagUnsafe(buf, pos) + v.ColourDescriptionPresentFlag = bits.ReadFlagUnsafe(buf, pos) + + if v.ColourDescriptionPresentFlag { + err = bits.HasSpace(buf, *pos, 24) + if err != nil { + return err + } + + v.ColourPrimaries = uint8(bits.ReadBitsUnsafe(buf, pos, 8)) + v.TransferCharacteristics = uint8(bits.ReadBitsUnsafe(buf, pos, 8)) + v.MatrixCoefficients = uint8(bits.ReadBitsUnsafe(buf, pos, 8)) + } + } + + v.ChromaLocInfoPresentFlag, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if v.ChromaLocInfoPresentFlag { + v.ChromaSampleLocTypeTopField, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + v.ChromaSampleLocTypeBottomField, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + } + + timingInfoPresentFlag, err := bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if timingInfoPresentFlag { + v.TimingInfo = &SPS_TimingInfo{} + err = v.TimingInfo.unmarshal(buf, pos) + if err != nil { + return err + } + } + + nalHrdParametersPresentFlag, err := bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if nalHrdParametersPresentFlag { + v.NalHRD = &SPS_HRD{} + err = v.NalHRD.unmarshal(buf, pos) + if err != nil { + return err + } + } + + vclHrdParametersPresentFlag, err := bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if vclHrdParametersPresentFlag { + v.VclHRD = &SPS_HRD{} + err = v.VclHRD.unmarshal(buf, pos) + if err != nil { + return err + } + } + + if nalHrdParametersPresentFlag || vclHrdParametersPresentFlag { + v.LowDelayHrdFlag, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + } + + v.PicStructPresentFlag, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + bitstreamRestrictionFlag, err := bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if bitstreamRestrictionFlag { + v.BitstreamRestriction = &SPS_BitstreamRestriction{} + err := v.BitstreamRestriction.unmarshal(buf, pos) + if err != nil { + return err + } + } + + return nil +} + +// SPS_FrameCropping is the frame cropping part of a SPS. +type SPS_FrameCropping struct { //nolint:revive + LeftOffset uint32 + RightOffset uint32 + TopOffset uint32 + BottomOffset uint32 +} + +func (c *SPS_FrameCropping) unmarshal(buf []byte, pos *int) error { + var err error + c.LeftOffset, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + c.RightOffset, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + c.TopOffset, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + c.BottomOffset, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + return nil +} + +// SPS is a H264 sequence parameter set. +// Specification: ITU-T Rec. H.264, 7.3.2.1.1 +type SPS struct { + ProfileIdc uint8 + ConstraintSet0Flag bool + ConstraintSet1Flag bool + ConstraintSet2Flag bool + ConstraintSet3Flag bool + ConstraintSet4Flag bool + ConstraintSet5Flag bool + LevelIdc uint8 + ID uint32 + + // only for selected ProfileIdcs + ChromaFormatIdc uint32 + SeparateColourPlaneFlag bool + BitDepthLumaMinus8 uint32 + BitDepthChromaMinus8 uint32 + QpprimeYZeroTransformBypassFlag bool + + // seqScalingListPresentFlag == true + ScalingList4x4 [][]int32 + UseDefaultScalingMatrix4x4Flag []bool + ScalingList8x8 [][]int32 + UseDefaultScalingMatrix8x8Flag []bool + + Log2MaxFrameNumMinus4 uint32 + PicOrderCntType uint32 + + // PicOrderCntType == 0 + Log2MaxPicOrderCntLsbMinus4 uint32 + + // PicOrderCntType == 1 + DeltaPicOrderAlwaysZeroFlag bool + OffsetForNonRefPic int32 + OffsetForTopToBottomField int32 + OffsetForRefFrames []int32 + + MaxNumRefFrames uint32 + GapsInFrameNumValueAllowedFlag bool + PicWidthInMbsMinus1 uint32 + PicHeightInMapUnitsMinus1 uint32 + FrameMbsOnlyFlag bool + + // FrameMbsOnlyFlag == false + MbAdaptiveFrameFieldFlag bool + + Direct8x8InferenceFlag bool + FrameCropping *SPS_FrameCropping + VUI *SPS_VUI +} + +// Unmarshal decodes a SPS from bytes. +func (s *SPS) Unmarshal(buf []byte) error { + if len(buf) < 1 { + return fmt.Errorf("not enough bits") + } + + if NALUType(buf[0]&0x1F) != NALUTypeSPS { + return fmt.Errorf("not a SPS") + } + + buf = EmulationPreventionRemove(buf[1:]) + + if len(buf) < 3 { + return fmt.Errorf("not enough bits") + } + + s.ProfileIdc = buf[0] + s.ConstraintSet0Flag = (buf[1] >> 7) == 1 + s.ConstraintSet1Flag = (buf[1] >> 6 & 0x01) == 1 + s.ConstraintSet2Flag = (buf[1] >> 5 & 0x01) == 1 + s.ConstraintSet3Flag = (buf[1] >> 4 & 0x01) == 1 + s.ConstraintSet4Flag = (buf[1] >> 3 & 0x01) == 1 + s.ConstraintSet5Flag = (buf[1] >> 2 & 0x01) == 1 + s.LevelIdc = buf[2] + + buf = buf[3:] + pos := 0 + + var err error + s.ID, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + switch s.ProfileIdc { + case 100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 139, 134, 135: + s.ChromaFormatIdc, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + if s.ChromaFormatIdc == 3 { + s.SeparateColourPlaneFlag, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + } else { + s.SeparateColourPlaneFlag = false + } + + s.BitDepthLumaMinus8, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + s.BitDepthChromaMinus8, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + s.QpprimeYZeroTransformBypassFlag, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + var seqScalingMatrixPresentFlag bool + seqScalingMatrixPresentFlag, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + if seqScalingMatrixPresentFlag { + var lim int + if s.ChromaFormatIdc != 3 { + lim = 8 + } else { + lim = 12 + } + + for i := 0; i < lim; i++ { + var seqScalingListPresentFlag bool + seqScalingListPresentFlag, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + if seqScalingListPresentFlag { + if i < 6 { + var scalingList []int32 + var useDefaultScalingMatrixFlag bool + scalingList, useDefaultScalingMatrixFlag, err = readScalingList(buf, &pos, 16) + if err != nil { + return err + } + + s.ScalingList4x4 = append(s.ScalingList4x4, scalingList) + s.UseDefaultScalingMatrix4x4Flag = append(s.UseDefaultScalingMatrix4x4Flag, + useDefaultScalingMatrixFlag) + } else { + var scalingList []int32 + var useDefaultScalingMatrixFlag bool + scalingList, useDefaultScalingMatrixFlag, err = readScalingList(buf, &pos, 64) + if err != nil { + return err + } + + s.ScalingList8x8 = append(s.ScalingList8x8, scalingList) + s.UseDefaultScalingMatrix8x8Flag = append(s.UseDefaultScalingMatrix8x8Flag, + useDefaultScalingMatrixFlag) + } + } + } + } + + default: + s.ChromaFormatIdc = 1 + s.SeparateColourPlaneFlag = false + s.BitDepthLumaMinus8 = 0 + s.BitDepthChromaMinus8 = 0 + s.QpprimeYZeroTransformBypassFlag = false + } + + s.Log2MaxFrameNumMinus4, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + s.PicOrderCntType, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + switch s.PicOrderCntType { + case 0: + s.Log2MaxPicOrderCntLsbMinus4, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + s.DeltaPicOrderAlwaysZeroFlag = false + s.OffsetForNonRefPic = 0 + s.OffsetForTopToBottomField = 0 + s.OffsetForRefFrames = nil + + case 1: + s.Log2MaxPicOrderCntLsbMinus4 = 0 + + s.DeltaPicOrderAlwaysZeroFlag, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + s.OffsetForNonRefPic, err = bits.ReadGolombSigned(buf, &pos) + if err != nil { + return err + } + + s.OffsetForTopToBottomField, err = bits.ReadGolombSigned(buf, &pos) + if err != nil { + return err + } + + var numRefFramesInPicOrderCntCycle uint32 + numRefFramesInPicOrderCntCycle, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + if numRefFramesInPicOrderCntCycle > maxRefFrames { + return fmt.Errorf("num_ref_frames_in_pic_order_cnt_cycle exceeds %d", maxRefFrames) + } + + s.OffsetForRefFrames = make([]int32, numRefFramesInPicOrderCntCycle) + for i := uint32(0); i < numRefFramesInPicOrderCntCycle; i++ { + var v int32 + v, err = bits.ReadGolombSigned(buf, &pos) + if err != nil { + return err + } + + s.OffsetForRefFrames[i] = v + } + + case 2: + s.Log2MaxPicOrderCntLsbMinus4 = 0 + s.DeltaPicOrderAlwaysZeroFlag = false + s.OffsetForNonRefPic = 0 + s.OffsetForTopToBottomField = 0 + s.OffsetForRefFrames = nil + + default: + return fmt.Errorf("invalid pic_order_cnt_type: %d", s.PicOrderCntType) + } + + s.MaxNumRefFrames, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + s.GapsInFrameNumValueAllowedFlag, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + s.PicWidthInMbsMinus1, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + s.PicHeightInMapUnitsMinus1, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + s.FrameMbsOnlyFlag, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + if !s.FrameMbsOnlyFlag { + s.MbAdaptiveFrameFieldFlag, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + } else { + s.MbAdaptiveFrameFieldFlag = false + } + + s.Direct8x8InferenceFlag, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + frameCroppingFlag, err := bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + if frameCroppingFlag { + s.FrameCropping = &SPS_FrameCropping{} + err = s.FrameCropping.unmarshal(buf, &pos) + if err != nil { + return err + } + } else { + s.FrameCropping = nil + } + + vuiParametersPresentFlag, err := bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + if vuiParametersPresentFlag { + s.VUI = &SPS_VUI{} + err := s.VUI.unmarshal(buf, &pos) + if err != nil { + return err + } + } else { + s.VUI = nil + } + + return nil +} + +// Width returns the video width. +func (s SPS) Width() int { + var subWidthC uint32 + switch { + case s.ChromaFormatIdc == 1 && !s.SeparateColourPlaneFlag: + subWidthC = 2 + + case s.ChromaFormatIdc == 2 && !s.SeparateColourPlaneFlag: + subWidthC = 2 + + case s.ChromaFormatIdc == 3 && !s.SeparateColourPlaneFlag: + subWidthC = 1 + } + + var chromaArrayType uint32 + if !s.SeparateColourPlaneFlag { + chromaArrayType = s.ChromaFormatIdc + } else { + chromaArrayType = 0 + } + + var cropUnitX uint32 + if chromaArrayType == 0 { + cropUnitX = 0 + } else { + cropUnitX = subWidthC + } + + picWidthInSamplesL := ((s.PicWidthInMbsMinus1 + 1) * 16) + + if s.FrameCropping != nil { + return int(picWidthInSamplesL - cropUnitX*(s.FrameCropping.LeftOffset+s.FrameCropping.RightOffset)) + } + + return int(picWidthInSamplesL) +} + +// Height returns the video height. +func (s SPS) Height() int { + var subHeightC uint32 + switch { + case s.ChromaFormatIdc == 1 && !s.SeparateColourPlaneFlag: + subHeightC = 2 + + case s.ChromaFormatIdc == 2 && !s.SeparateColourPlaneFlag: + subHeightC = 1 + + case s.ChromaFormatIdc == 3 && !s.SeparateColourPlaneFlag: + subHeightC = 1 + } + + var frameMbsOnlyFlagUint32 uint32 + if s.FrameMbsOnlyFlag { + frameMbsOnlyFlagUint32 = 1 + } + + var chromaArrayType uint32 + if !s.SeparateColourPlaneFlag { + chromaArrayType = s.ChromaFormatIdc + } else { + chromaArrayType = 0 + } + + var cropUnitY uint32 + if chromaArrayType == 0 { + cropUnitY = 2 - frameMbsOnlyFlagUint32 + } else { + cropUnitY = subHeightC * (2 - frameMbsOnlyFlagUint32) + } + + frameHeightInMbs := (2 - frameMbsOnlyFlagUint32) * (s.PicHeightInMapUnitsMinus1 + 1) + + if s.FrameCropping != nil { + return int(16*frameHeightInMbs - cropUnitY*(s.FrameCropping.TopOffset+s.FrameCropping.BottomOffset)) + } + + picHeightInMbs := frameHeightInMbs // / (1 + s.FieldPicFlag) + picHeightInSamplesL := picHeightInMbs * 16 + + return int(picHeightInSamplesL) +} + +// FPS returns the frames per second of the video. +func (s SPS) FPS() float64 { + if s.VUI == nil || s.VUI.TimingInfo == nil { + return 0 + } + + return float64(s.VUI.TimingInfo.TimeScale) / (2 * float64(s.VUI.TimingInfo.NumUnitsInTick)) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/dts_extractor.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/dts_extractor.go new file mode 100644 index 000000000..5865865ae --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/dts_extractor.go @@ -0,0 +1,245 @@ +package h265 + +import ( + "fmt" + "math" + + "github.com/bluenviron/mediacommon/v2/pkg/bits" + "github.com/bluenviron/mediacommon/v2/pkg/codecs/h264" +) + +const ( + maxBytesToGetPOC = 12 +) + +func getPTSDTSDiff(buf []byte, sps *SPS, pps *PPS) (uint32, error) { + typ := NALUType((buf[0] >> 1) & 0b111111) + + buf = buf[1:] + lb := len(buf) + + if lb > maxBytesToGetPOC { + lb = maxBytesToGetPOC + } + + buf = h264.EmulationPreventionRemove(buf[:lb]) + pos := 8 + + firstSliceSegmentInPicFlag, err := bits.ReadFlag(buf, &pos) + if err != nil { + return 0, err + } + + if !firstSliceSegmentInPicFlag { + return 0, fmt.Errorf("first_slice_segment_in_pic_flag = 0 is not supported") + } + + if typ >= NALUType_BLA_W_LP && typ <= NALUType_RSV_IRAP_VCL23 { + _, err = bits.ReadFlag(buf, &pos) // no_output_of_prior_pics_flag + if err != nil { + return 0, err + } + } + + _, err = bits.ReadGolombUnsigned(buf, &pos) // slice_pic_parameter_set_id + if err != nil { + return 0, err + } + + if pps.NumExtraSliceHeaderBits > 0 { + err = bits.HasSpace(buf, pos, int(pps.NumExtraSliceHeaderBits)) + if err != nil { + return 0, err + } + pos += int(pps.NumExtraSliceHeaderBits) + } + + sliceType, err := bits.ReadGolombUnsigned(buf, &pos) // slice_type + if err != nil { + return 0, err + } + + if pps.OutputFlagPresentFlag { + _, err = bits.ReadFlag(buf, &pos) // pic_output_flag + if err != nil { + return 0, err + } + } + + if sps.SeparateColourPlaneFlag { + _, err = bits.ReadBits(buf, &pos, 2) // colour_plane_id + if err != nil { + return 0, err + } + } + + _, err = bits.ReadBits(buf, &pos, int(sps.Log2MaxPicOrderCntLsbMinus4+4)) // pic_order_cnt_lsb + if err != nil { + return 0, err + } + + shortTermRefPicSetSpsFlag, err := bits.ReadFlag(buf, &pos) + if err != nil { + return 0, err + } + + var rps *SPS_ShortTermRefPicSet + + if !shortTermRefPicSetSpsFlag { + rps = &SPS_ShortTermRefPicSet{} + err = rps.unmarshal(buf, &pos, uint32(len(sps.ShortTermRefPicSets)), + uint32(len(sps.ShortTermRefPicSets)), sps.ShortTermRefPicSets) + if err != nil { + return 0, err + } + } else { + if len(sps.ShortTermRefPicSets) == 0 { + return 0, fmt.Errorf("invalid short_term_ref_pic_set_idx") + } + + b := int(math.Ceil(math.Log2(float64(len(sps.ShortTermRefPicSets))))) + tmp, err := bits.ReadBits(buf, &pos, b) + if err != nil { + return 0, err + } + shortTermRefPicSetIdx := int(tmp) + + if len(sps.ShortTermRefPicSets) <= shortTermRefPicSetIdx { + return 0, fmt.Errorf("invalid short_term_ref_pic_set_idx") + } + + rps = sps.ShortTermRefPicSets[shortTermRefPicSetIdx] + } + + var v uint32 + + if sliceType == 0 { // B-frame + if typ == NALUType_TRAIL_N || typ == NALUType_RASL_N { + v = sps.MaxNumReorderPics[0] - uint32(len(rps.DeltaPocS1)) + } else if typ == NALUType_TRAIL_R || typ == NALUType_RASL_R { + if len(rps.DeltaPocS0) == 0 { + return 0, fmt.Errorf("invalid DeltaPocS0") + } + v = uint32(-rps.DeltaPocS0[0]-1+int32(sps.MaxNumReorderPics[0])) - uint32(len(rps.DeltaPocS1)) + } + } else { // I or P-frame + if len(rps.DeltaPocS0) == 0 { + return 0, fmt.Errorf("invalid DeltaPocS0") + } + v = uint32(-rps.DeltaPocS0[0] - 1 + int32(sps.MaxNumReorderPics[0])) + } + + return v, nil +} + +// DTSExtractor computes DTS from PTS. +type DTSExtractor struct { + spsp *SPS + ppsp *PPS + prevDTSFilled bool + prevDTS int64 +} + +// Initialize initializes a DTSExtractor. +func (d *DTSExtractor) Initialize() { +} + +// NewDTSExtractor allocates a DTSExtractor. +// +// Deprecated: replaced by DTSExtractor.Initialize. +func NewDTSExtractor() *DTSExtractor { + return &DTSExtractor{} +} + +func (d *DTSExtractor) extractInner(au [][]byte, pts int64) (int64, error) { + var idr []byte + var nonIDR []byte + + for _, nalu := range au { + typ := NALUType((nalu[0] >> 1) & 0b111111) + switch typ { + case NALUType_SPS_NUT: + var spsp SPS + err := spsp.Unmarshal(nalu) + if err != nil { + return 0, fmt.Errorf("invalid SPS: %w", err) + } + d.spsp = &spsp + + case NALUType_PPS_NUT: + var ppsp PPS + err := ppsp.Unmarshal(nalu) + if err != nil { + return 0, fmt.Errorf("invalid PPS: %w", err) + } + d.ppsp = &ppsp + + case NALUType_IDR_W_RADL, NALUType_IDR_N_LP: + idr = nalu + + case NALUType_TRAIL_N, NALUType_TRAIL_R, NALUType_CRA_NUT, NALUType_RASL_N, NALUType_RASL_R: + nonIDR = nalu + } + } + + if d.spsp == nil { + return 0, fmt.Errorf("SPS not received yet") + } + + if d.ppsp == nil { + return 0, fmt.Errorf("PPS not received yet") + } + + if len(d.spsp.MaxNumReorderPics) != 1 || d.spsp.MaxNumReorderPics[0] == 0 { + return pts, nil + } + + if d.spsp.VUI == nil || d.spsp.VUI.TimingInfo == nil { + return pts, nil + } + + var samplesDiff uint32 + + switch { + case idr != nil: + samplesDiff = d.spsp.MaxNumReorderPics[0] + + case nonIDR != nil: + var err error + samplesDiff, err = getPTSDTSDiff(nonIDR, d.spsp, d.ppsp) + if err != nil { + return 0, err + } + + default: + return 0, fmt.Errorf("access unit doesn't contain an IDR or non-IDR NALU") + } + + timeDiff := int64(samplesDiff) * 90000 * + int64(d.spsp.VUI.TimingInfo.NumUnitsInTick) / int64(d.spsp.VUI.TimingInfo.TimeScale) + dts := pts - timeDiff + + return dts, nil +} + +// Extract extracts the DTS of a access unit. +func (d *DTSExtractor) Extract(au [][]byte, pts int64) (int64, error) { + dts, err := d.extractInner(au, pts) + if err != nil { + return 0, err + } + + if dts > pts { + return 0, fmt.Errorf("DTS is greater than PTS") + } + + if d.prevDTSFilled && dts < d.prevDTS { + return 0, fmt.Errorf("DTS is not monotonically increasing, was %v, now is %v", + d.prevDTS, dts) + } + + d.prevDTSFilled = true + d.prevDTS = dts + + return dts, err +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/h265.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/h265.go new file mode 100644 index 000000000..056ea84f3 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/h265.go @@ -0,0 +1,11 @@ +// Package h265 contains utilities to work with the H265 codec. +package h265 + +const ( + // MaxAccessUnitSize is the maximum size of an access unit. + // With a 50 Mbps 2160p60 H265 video, the maximum size does not seem to exceed 8 MiB. + MaxAccessUnitSize = 8 * 1024 * 1024 + + // MaxNALUsPerAccessUnit is the maximum number of NALUs per access unit. + MaxNALUsPerAccessUnit = 21 +) diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/is_random_access.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/is_random_access.go new file mode 100644 index 000000000..837a0aa36 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/is_random_access.go @@ -0,0 +1,13 @@ +package h265 + +// IsRandomAccess checks whether the access unit can be randomly accessed. +func IsRandomAccess(au [][]byte) bool { + for _, nalu := range au { + typ := NALUType((nalu[0] >> 1) & 0b111111) + switch typ { + case NALUType_IDR_W_RADL, NALUType_IDR_N_LP, NALUType_CRA_NUT: + return true + } + } + return false +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/nalu_type.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/nalu_type.go new file mode 100644 index 000000000..384fba88f --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/nalu_type.go @@ -0,0 +1,100 @@ +package h265 + +import ( + "fmt" +) + +// NALUType is the type of a NALU. +// Specification: ITU-T Rec. H.265, Table 7-1 +type NALUType uint8 + +// NALU types. +const ( + NALUType_TRAIL_N NALUType = 0 //nolint:revive + NALUType_TRAIL_R NALUType = 1 //nolint:revive + NALUType_TSA_N NALUType = 2 //nolint:revive + NALUType_TSA_R NALUType = 3 //nolint:revive + NALUType_STSA_N NALUType = 4 //nolint:revive + NALUType_STSA_R NALUType = 5 //nolint:revive + NALUType_RADL_N NALUType = 6 //nolint:revive + NALUType_RADL_R NALUType = 7 //nolint:revive + NALUType_RASL_N NALUType = 8 //nolint:revive + NALUType_RASL_R NALUType = 9 //nolint:revive + NALUType_RSV_VCL_N10 NALUType = 10 //nolint:revive + NALUType_RSV_VCL_N12 NALUType = 12 //nolint:revive + NALUType_RSV_VCL_N14 NALUType = 14 //nolint:revive + NALUType_RSV_VCL_R11 NALUType = 11 //nolint:revive + NALUType_RSV_VCL_R13 NALUType = 13 //nolint:revive + NALUType_RSV_VCL_R15 NALUType = 15 //nolint:revive + NALUType_BLA_W_LP NALUType = 16 //nolint:revive + NALUType_BLA_W_RADL NALUType = 17 //nolint:revive + NALUType_BLA_N_LP NALUType = 18 //nolint:revive + NALUType_IDR_W_RADL NALUType = 19 //nolint:revive + NALUType_IDR_N_LP NALUType = 20 //nolint:revive + NALUType_CRA_NUT NALUType = 21 //nolint:revive + NALUType_RSV_IRAP_VCL22 NALUType = 22 //nolint:revive + NALUType_RSV_IRAP_VCL23 NALUType = 23 //nolint:revive + NALUType_VPS_NUT NALUType = 32 //nolint:revive + NALUType_SPS_NUT NALUType = 33 //nolint:revive + NALUType_PPS_NUT NALUType = 34 //nolint:revive + NALUType_AUD_NUT NALUType = 35 //nolint:revive + NALUType_EOS_NUT NALUType = 36 //nolint:revive + NALUType_EOB_NUT NALUType = 37 //nolint:revive + NALUType_FD_NUT NALUType = 38 //nolint:revive + NALUType_PREFIX_SEI_NUT NALUType = 39 //nolint:revive + NALUType_SUFFIX_SEI_NUT NALUType = 40 //nolint:revive + + // additional NALU types for RTP/H265 + NALUType_AggregationUnit NALUType = 48 //nolint:revive + NALUType_FragmentationUnit NALUType = 49 //nolint:revive + NALUType_PACI NALUType = 50 //nolint:revive +) + +var naluTypeLabels = map[NALUType]string{ + NALUType_TRAIL_N: "TRAIL_N", + NALUType_TRAIL_R: "TRAIL_R", + NALUType_TSA_N: "TSA_N", + NALUType_TSA_R: "TSA_R", + NALUType_STSA_N: "STSA_N", + NALUType_STSA_R: "STSA_R:", + NALUType_RADL_N: "RADL_N", + NALUType_RADL_R: "RADL_R", + NALUType_RASL_N: "RASL_N", + NALUType_RASL_R: "RASL_R", + NALUType_RSV_VCL_N10: "RSV_VCL_N10", + NALUType_RSV_VCL_N12: "RSV_VCL_N12", + NALUType_RSV_VCL_N14: "RSV_VCL_N14", + NALUType_RSV_VCL_R11: "RSV_VCL_R11", + NALUType_RSV_VCL_R13: "RSV_VCL_R13", + NALUType_RSV_VCL_R15: "RSV_VCL_R15", + NALUType_BLA_W_LP: "BLA_W_LP", + NALUType_BLA_W_RADL: "BLA_W_RADL", + NALUType_BLA_N_LP: "BLA_N_LP", + NALUType_IDR_W_RADL: "IDR_W_RADL", + NALUType_IDR_N_LP: "IDR_N_LP", + NALUType_CRA_NUT: "CRA_NUT", + NALUType_RSV_IRAP_VCL22: "RSV_IRAP_VCL22", + NALUType_RSV_IRAP_VCL23: "RSV_IRAP_VCL23", + NALUType_VPS_NUT: "VPS_NUT", + NALUType_SPS_NUT: "SPS_NUT", + NALUType_PPS_NUT: "PPS_NUT", + NALUType_AUD_NUT: "AUD_NUT", + NALUType_EOS_NUT: "EOS_NUT", + NALUType_EOB_NUT: "EOB_NUT", + NALUType_FD_NUT: "FD_NUT", + NALUType_PREFIX_SEI_NUT: "PrefixSEINUT", + NALUType_SUFFIX_SEI_NUT: "SuffixSEINUT", + + // additional NALU types for RTP/H265 + NALUType_AggregationUnit: "AggregationUnit", + NALUType_FragmentationUnit: "FragmentationUnit", + NALUType_PACI: "PACI", +} + +// String implements fmt.Stringer. +func (nt NALUType) String() string { + if l, ok := naluTypeLabels[nt]; ok { + return l + } + return fmt.Sprintf("unknown (%d)", nt) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/pps.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/pps.go new file mode 100644 index 000000000..1fe3609b1 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/pps.go @@ -0,0 +1,54 @@ +package h265 + +import ( + "fmt" + + "github.com/bluenviron/mediacommon/v2/pkg/bits" + "github.com/bluenviron/mediacommon/v2/pkg/codecs/h264" +) + +// PPS is a H265 picture parameter set. +// Specification: ITU-T Rec. H.265, 7.3.2.3.1 +type PPS struct { + ID uint32 + SPSID uint32 + DependentSliceSegmentsEnabledFlag bool + OutputFlagPresentFlag bool + NumExtraSliceHeaderBits uint8 +} + +// Unmarshal decodes a PPS. +func (p *PPS) Unmarshal(buf []byte) error { + if len(buf) < 2 { + return fmt.Errorf("not enough bits") + } + + if NALUType((buf[0]>>1)&0b111111) != NALUType_PPS_NUT { + return fmt.Errorf("not a PPS") + } + + buf = h264.EmulationPreventionRemove(buf[1:]) + pos := 8 + + var err error + p.ID, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + p.SPSID, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + err = bits.HasSpace(buf, pos, 5) + if err != nil { + return err + } + + p.DependentSliceSegmentsEnabledFlag = bits.ReadFlagUnsafe(buf, &pos) + p.OutputFlagPresentFlag = bits.ReadFlagUnsafe(buf, &pos) + p.NumExtraSliceHeaderBits = uint8(bits.ReadBitsUnsafe(buf, &pos, 3)) + + return nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/sps.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/sps.go new file mode 100644 index 000000000..82f695b15 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/h265/sps.go @@ -0,0 +1,979 @@ +package h265 + +import ( + "fmt" + + "github.com/bluenviron/mediacommon/v2/pkg/bits" + "github.com/bluenviron/mediacommon/v2/pkg/codecs/h264" +) + +const ( + maxNegativePics = 255 + maxPositivePics = 255 + maxShortTermRefPics = 64 +) + +var subWidthC = []uint32{ + 1, + 2, + 2, + 1, +} + +var subHeightC = []uint32{ + 1, + 2, + 1, + 1, +} + +// SPS_ScalingListData is a scaling list data. +type SPS_ScalingListData struct { //nolint:revive + ScalingListPredModeFlag [4][6]bool + ScalingListPredmatrixIDDelta [4][6]uint32 + ScalingListDcCoefMinus8 [4][6]int32 +} + +func (d *SPS_ScalingListData) unmarshal(buf []byte, pos *int) error { + for sizeID := 0; sizeID < 4; sizeID++ { + var matrixIDIncr int + if sizeID == 3 { + matrixIDIncr = 3 + } else { + matrixIDIncr = 1 + } + + for matrixID := 0; matrixID < 6; matrixID += matrixIDIncr { + var err error + d.ScalingListPredModeFlag[sizeID][matrixID], err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if !d.ScalingListPredModeFlag[sizeID][matrixID] { + d.ScalingListPredmatrixIDDelta[sizeID][matrixID], err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + } else { + coefNum := min(64, 1<<(4+(sizeID<<1))) + + if sizeID > 1 { + d.ScalingListDcCoefMinus8[sizeID-2][matrixID], err = bits.ReadGolombSigned(buf, pos) + if err != nil { + return err + } + } + + for i := 0; i < coefNum; i++ { + _, err = bits.ReadGolombSigned(buf, pos) // scalingListDeltaCoef + if err != nil { + return err + } + } + } + } + } + + return nil +} + +// SPS_Window is a window. +type SPS_Window struct { //nolint:revive + LeftOffset uint32 + RightOffset uint32 + TopOffset uint32 + BottomOffset uint32 +} + +func (w *SPS_Window) unmarshal(buf []byte, pos *int) error { + var err error + w.LeftOffset, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + w.RightOffset, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + w.TopOffset, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + w.BottomOffset, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + return nil +} + +// SPS_TimingInfo is a timing info. +type SPS_TimingInfo struct { //nolint:revive + NumUnitsInTick uint32 + TimeScale uint32 + POCProportionalToTimingFlag bool + + // POCProportionalToTimingFlag == true + NumTicksPOCDiffOneMinus1 uint32 +} + +func (t *SPS_TimingInfo) unmarshal(buf []byte, pos *int) error { + err := bits.HasSpace(buf, *pos, 32+32+1) + if err != nil { + return err + } + + t.NumUnitsInTick = uint32(bits.ReadBitsUnsafe(buf, pos, 32)) + t.TimeScale = uint32(bits.ReadBitsUnsafe(buf, pos, 32)) + t.POCProportionalToTimingFlag = bits.ReadFlagUnsafe(buf, pos) + + if t.POCProportionalToTimingFlag { + t.NumTicksPOCDiffOneMinus1, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + } + + return nil +} + +// SPS_VUI is a video usability information. +type SPS_VUI struct { //nolint:revive + AspectRatioInfoPresentFlag bool + + // AspectRatioInfoPresentFlag == true + AspectRatioIdc uint8 + SarWidth uint16 + SarHeight uint16 + + OverscanInfoPresentFlag bool + + // OverscanInfoPresentFlag == true + OverscanAppropriateFlag bool + VideoSignalTypePresentFlag bool + + // VideoSignalTypePresentFlag == true + VideoFormat uint8 + VideoFullRangeFlag bool + ColourDescriptionPresentFlag bool + + // ColourDescriptionPresentFlag == true + ColourPrimaries uint8 + TransferCharacteristics uint8 + MatrixCoefficients uint8 + + ChromaLocInfoPresentFlag bool + + // ChromaLocInfoPresentFlag == true + ChromaSampleLocTypeTopField uint32 + ChromaSampleLocTypeBottomField uint32 + + NeutralChromaIndicationFlag bool + FieldSeqFlag bool + FrameFieldInfoPresentFlag bool + DefaultDisplayWindow *SPS_Window + TimingInfo *SPS_TimingInfo +} + +func (v *SPS_VUI) unmarshal(buf []byte, pos *int) error { + var err error + v.AspectRatioInfoPresentFlag, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if v.AspectRatioInfoPresentFlag { + var tmp uint64 + tmp, err = bits.ReadBits(buf, pos, 8) + if err != nil { + return err + } + v.AspectRatioIdc = uint8(tmp) + + if v.AspectRatioIdc == 255 { // EXTENDED_SAR + err = bits.HasSpace(buf, *pos, 32) + if err != nil { + return err + } + + v.SarWidth = uint16(bits.ReadBitsUnsafe(buf, pos, 16)) + v.SarHeight = uint16(bits.ReadBitsUnsafe(buf, pos, 16)) + } + } + + v.OverscanInfoPresentFlag, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if v.OverscanInfoPresentFlag { + v.OverscanAppropriateFlag, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + } + + v.VideoSignalTypePresentFlag, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if v.VideoSignalTypePresentFlag { + err = bits.HasSpace(buf, *pos, 5) + if err != nil { + return err + } + + v.VideoFormat = uint8(bits.ReadBitsUnsafe(buf, pos, 3)) + v.VideoFullRangeFlag = bits.ReadFlagUnsafe(buf, pos) + v.ColourDescriptionPresentFlag = bits.ReadFlagUnsafe(buf, pos) + + if v.ColourDescriptionPresentFlag { + err = bits.HasSpace(buf, *pos, 24) + if err != nil { + return err + } + + v.ColourPrimaries = uint8(bits.ReadBitsUnsafe(buf, pos, 8)) + v.TransferCharacteristics = uint8(bits.ReadBitsUnsafe(buf, pos, 8)) + v.MatrixCoefficients = uint8(bits.ReadBitsUnsafe(buf, pos, 8)) + } + } + + v.ChromaLocInfoPresentFlag, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if v.ChromaLocInfoPresentFlag { + v.ChromaSampleLocTypeTopField, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + v.ChromaSampleLocTypeBottomField, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + } + + v.NeutralChromaIndicationFlag, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + v.FieldSeqFlag, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + v.FrameFieldInfoPresentFlag, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + defaultDisplayWindowFlag, err := bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if defaultDisplayWindowFlag { + v.DefaultDisplayWindow = &SPS_Window{} + err = v.DefaultDisplayWindow.unmarshal(buf, pos) + if err != nil { + return err + } + } else { + v.DefaultDisplayWindow = nil + } + + timingInfoPresentFlag, err := bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if timingInfoPresentFlag { + v.TimingInfo = &SPS_TimingInfo{} + err := v.TimingInfo.unmarshal(buf, pos) + if err != nil { + return err + } + } else { + v.TimingInfo = nil + } + + return nil +} + +// SPS_ProfileTierLevel is a profile level tier of a SPS. +type SPS_ProfileTierLevel struct { //nolint:revive + GeneralProfileSpace uint8 + GeneralTierFlag uint8 + GeneralProfileIdc uint8 + GeneralProfileCompatibilityFlag [32]bool + GeneralProgressiveSourceFlag bool + GeneralInterlacedSourceFlag bool + GeneralNonPackedConstraintFlag bool + GeneralFrameOnlyConstraintFlag bool + GeneralMax12bitConstraintFlag bool + GeneralMax10bitConstraintFlag bool + GeneralMax8bitConstraintFlag bool + GeneralMax422ChromeConstraintFlag bool + GeneralMax420ChromaConstraintFlag bool + GeneralMaxMonochromeConstraintFlag bool + GeneralIntraConstraintFlag bool + GeneralOnePictureOnlyConstraintFlag bool + GeneralLowerBitRateConstraintFlag bool + GeneralMax14BitConstraintFlag bool + GeneralLevelIdc uint8 + SubLayerProfilePresentFlag []bool + SubLayerLevelPresentFlag []bool +} + +func (p *SPS_ProfileTierLevel) unmarshal(buf []byte, pos *int, maxSubLayersMinus1 uint8) error { + err := bits.HasSpace(buf, *pos, 8+32+12+34+8) + if err != nil { + return err + } + + p.GeneralProfileSpace = uint8(bits.ReadBitsUnsafe(buf, pos, 2)) + p.GeneralTierFlag = uint8(bits.ReadBitsUnsafe(buf, pos, 1)) + p.GeneralProfileIdc = uint8(bits.ReadBitsUnsafe(buf, pos, 5)) + + for j := 0; j < 32; j++ { + p.GeneralProfileCompatibilityFlag[j] = bits.ReadFlagUnsafe(buf, pos) + } + + p.GeneralProgressiveSourceFlag = bits.ReadFlagUnsafe(buf, pos) + p.GeneralInterlacedSourceFlag = bits.ReadFlagUnsafe(buf, pos) + p.GeneralNonPackedConstraintFlag = bits.ReadFlagUnsafe(buf, pos) + p.GeneralFrameOnlyConstraintFlag = bits.ReadFlagUnsafe(buf, pos) + p.GeneralMax12bitConstraintFlag = bits.ReadFlagUnsafe(buf, pos) + p.GeneralMax10bitConstraintFlag = bits.ReadFlagUnsafe(buf, pos) + p.GeneralMax8bitConstraintFlag = bits.ReadFlagUnsafe(buf, pos) + p.GeneralMax422ChromeConstraintFlag = bits.ReadFlagUnsafe(buf, pos) + p.GeneralMax420ChromaConstraintFlag = bits.ReadFlagUnsafe(buf, pos) + p.GeneralMaxMonochromeConstraintFlag = bits.ReadFlagUnsafe(buf, pos) + p.GeneralIntraConstraintFlag = bits.ReadFlagUnsafe(buf, pos) + p.GeneralOnePictureOnlyConstraintFlag = bits.ReadFlagUnsafe(buf, pos) + p.GeneralLowerBitRateConstraintFlag = bits.ReadFlagUnsafe(buf, pos) + + if p.GeneralProfileIdc == 5 || + p.GeneralProfileIdc == 9 || + p.GeneralProfileIdc == 10 || + p.GeneralProfileIdc == 11 || + p.GeneralProfileCompatibilityFlag[5] || + p.GeneralProfileCompatibilityFlag[9] || + p.GeneralProfileCompatibilityFlag[10] || + p.GeneralProfileCompatibilityFlag[11] { + p.GeneralMax14BitConstraintFlag = bits.ReadFlagUnsafe(buf, pos) + *pos += 34 + } else { + *pos += 35 + } + + p.GeneralLevelIdc = uint8(bits.ReadBitsUnsafe(buf, pos, 8)) + + if maxSubLayersMinus1 > 0 { + p.SubLayerProfilePresentFlag = make([]bool, maxSubLayersMinus1) + p.SubLayerLevelPresentFlag = make([]bool, maxSubLayersMinus1) + + err := bits.HasSpace(buf, *pos, int(2*maxSubLayersMinus1)) + if err != nil { + return err + } + + for j := uint8(0); j < maxSubLayersMinus1; j++ { + p.SubLayerProfilePresentFlag[j] = bits.ReadFlagUnsafe(buf, pos) + p.SubLayerLevelPresentFlag[j] = bits.ReadFlagUnsafe(buf, pos) + } + } + + if maxSubLayersMinus1 > 0 { + err := bits.HasSpace(buf, *pos, int(8-maxSubLayersMinus1)*2) + if err != nil { + return err + } + + *pos += int(8-maxSubLayersMinus1) * 2 + } + + for i := uint8(0); i < maxSubLayersMinus1; i++ { + if p.SubLayerProfilePresentFlag[i] { + return fmt.Errorf("SubLayerProfilePresentFlag not supported yet") + } + + if p.SubLayerLevelPresentFlag[i] { + return fmt.Errorf("SubLayerLevelPresentFlag not supported yet") + } + } + + return nil +} + +// SPS_ShortTermRefPicSet is a short-term reference picture set. +type SPS_ShortTermRefPicSet struct { //nolint:revive + InterRefPicSetPredictionFlag bool + DeltaIdxMinus1 uint32 + DeltaRpsSign bool + AbsDeltaRpsMinus1 uint32 + NumNegativePics uint32 + NumPositivePics uint32 + DeltaPocS0 []int32 + UsedByCurrPicS0Flag []bool + DeltaPocS1 []int32 + UsedByCurrPicS1Flag []bool +} + +func (r *SPS_ShortTermRefPicSet) unmarshal(buf []byte, pos *int, stRpsIdx uint32, + numShortTermRefPicSets uint32, shortTermRefPicSets []*SPS_ShortTermRefPicSet, +) error { + var err error + + if stRpsIdx != 0 { + r.InterRefPicSetPredictionFlag, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + } + + if r.InterRefPicSetPredictionFlag { + if stRpsIdx == numShortTermRefPicSets { + r.DeltaIdxMinus1, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + } + + r.DeltaRpsSign, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + r.AbsDeltaRpsMinus1, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + var s int32 + if r.DeltaRpsSign { + s = 1 + } + deltaRps := (1 - 2*s) * (int32(r.AbsDeltaRpsMinus1) + 1) + + refRpsIdx := stRpsIdx - (r.DeltaIdxMinus1 + 1) + if refRpsIdx >= uint32(len(shortTermRefPicSets)) { + return fmt.Errorf("invalid refRpsIdx") + } + + refRPS := shortTermRefPicSets[refRpsIdx] + numDeltaPocs := refRPS.NumNegativePics + refRPS.NumPositivePics + usedByCurrPicFlag := make([]bool, numDeltaPocs+1) + + useDeltaFlag := make([]bool, numDeltaPocs+1) + for i := range useDeltaFlag { + useDeltaFlag[i] = true + } + + for j := uint32(0); j <= numDeltaPocs; j++ { + usedByCurrPicFlag[j], err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if !usedByCurrPicFlag[j] { + useDeltaFlag[j], err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + } + } + + i := uint32(0) + + for j := (int32(refRPS.NumPositivePics) - 1); j >= 0; j-- { + dPoc := refRPS.DeltaPocS1[j] + deltaRps + if dPoc < 0 && useDeltaFlag[refRPS.NumNegativePics+uint32(j)] { + r.DeltaPocS0 = append(r.DeltaPocS0, dPoc) + r.UsedByCurrPicS0Flag = append(r.UsedByCurrPicS0Flag, usedByCurrPicFlag[refRPS.NumNegativePics+uint32(j)]) + i++ + } + } + + if deltaRps < 0 && useDeltaFlag[numDeltaPocs] { + r.DeltaPocS0 = append(r.DeltaPocS0, deltaRps) + r.UsedByCurrPicS0Flag = append(r.UsedByCurrPicS0Flag, usedByCurrPicFlag[numDeltaPocs]) + i++ + } + + for j := uint32(0); j < refRPS.NumNegativePics; j++ { + dPoc := refRPS.DeltaPocS0[j] + deltaRps + if dPoc < 0 && useDeltaFlag[j] { + r.DeltaPocS0 = append(r.DeltaPocS0, dPoc) + r.UsedByCurrPicS0Flag = append(r.UsedByCurrPicS0Flag, usedByCurrPicFlag[j]) + i++ + } + } + + r.NumNegativePics = i + + i = uint32(0) + + for j := (int32(refRPS.NumNegativePics) - 1); j >= 0; j-- { + dPoc := refRPS.DeltaPocS0[j] + deltaRps + if dPoc > 0 && useDeltaFlag[j] { + r.DeltaPocS1 = append(r.DeltaPocS1, dPoc) + r.UsedByCurrPicS1Flag = append(r.UsedByCurrPicS1Flag, usedByCurrPicFlag[j]) + i++ + } + } + + if deltaRps > 0 && useDeltaFlag[numDeltaPocs] { + r.DeltaPocS1 = append(r.DeltaPocS1, deltaRps) + r.UsedByCurrPicS1Flag = append(r.UsedByCurrPicS1Flag, usedByCurrPicFlag[numDeltaPocs]) + i++ + } + + for j := uint32(0); j < refRPS.NumPositivePics; j++ { + dPoc := refRPS.DeltaPocS1[j] + deltaRps + if dPoc > 0 && useDeltaFlag[refRPS.NumNegativePics+j] { + r.DeltaPocS1 = append(r.DeltaPocS1, dPoc) + r.UsedByCurrPicS1Flag = append(r.UsedByCurrPicS1Flag, usedByCurrPicFlag[refRPS.NumNegativePics+j]) + i++ + } + } + + r.NumPositivePics = i + } else { + r.NumNegativePics, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + r.NumPositivePics, err = bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + if r.NumNegativePics > 0 { + if r.NumNegativePics > maxNegativePics { + return fmt.Errorf("num_negative_pics exceeds %d", maxNegativePics) + } + + r.DeltaPocS0 = make([]int32, r.NumNegativePics) + r.UsedByCurrPicS0Flag = make([]bool, r.NumNegativePics) + + for i := uint32(0); i < r.NumNegativePics; i++ { + deltaPocS0Minus1, err := bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + if i == 0 { + r.DeltaPocS0[i] = -int32(deltaPocS0Minus1 + 1) + } else { + r.DeltaPocS0[i] = r.DeltaPocS0[i-1] - (int32(deltaPocS0Minus1) + 1) + } + + r.UsedByCurrPicS0Flag[i], err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + } + } + + if r.NumPositivePics > 0 { + if r.NumPositivePics > maxPositivePics { + return fmt.Errorf("num_positive_pics exceeds %d", maxPositivePics) + } + + r.DeltaPocS1 = make([]int32, r.NumPositivePics) + r.UsedByCurrPicS1Flag = make([]bool, r.NumPositivePics) + + for i := uint32(0); i < r.NumPositivePics; i++ { + deltaPocS1Minus1, err := bits.ReadGolombUnsigned(buf, pos) + if err != nil { + return err + } + + if i == 0 { + r.DeltaPocS1[i] = int32(deltaPocS1Minus1) + 1 + } else { + r.DeltaPocS1[i] = r.DeltaPocS1[i-1] + int32(deltaPocS1Minus1) + 1 + } + + r.UsedByCurrPicS1Flag[i], err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + } + } + } + + return nil +} + +// SPS is a H265 sequence parameter set. +// Specification: ITU-T Rec. H.265, 7.3.2.2.1 +type SPS struct { + VPSID uint8 + MaxSubLayersMinus1 uint8 + TemporalIDNestingFlag bool + ProfileTierLevel SPS_ProfileTierLevel + ID uint8 + ChromaFormatIdc uint32 + SeparateColourPlaneFlag bool + PicWidthInLumaSamples uint32 + PicHeightInLumaSamples uint32 + ConformanceWindow *SPS_Window + BitDepthLumaMinus8 uint32 + BitDepthChromaMinus8 uint32 + Log2MaxPicOrderCntLsbMinus4 uint32 + SubLayerOrderingInfoPresentFlag bool + MaxDecPicBufferingMinus1 []uint32 + MaxNumReorderPics []uint32 + MaxLatencyIncreasePlus1 []uint32 + Log2MinLumaCodingBlockSizeMinus3 uint32 + Log2DiffMaxMinLumaCodingBlockSize uint32 + Log2MinLumaTransformBlockSizeMinus2 uint32 + Log2DiffMaxMinLumaTransformBlockSize uint32 + MaxTransformHierarchyDepthInter uint32 + MaxTransformHierarchyDepthIntra uint32 + ScalingListEnabledFlag bool + ScalingListData *SPS_ScalingListData + AmpEnabledFlag bool + SampleAdaptiveOffsetEnabledFlag bool + PcmEnabledFlag bool + + // PcmEnabledFlag == true + PcmSampleBitDepthLumaMinus1 uint8 + PcmSampleBitDepthChromaMinus1 uint8 + Log2MinPcmLumaCodingBlockSizeMinus3 uint32 + Log2DiffMaxMinPcmLumaCodingBlockSize uint32 + PcmLoopFilterDisabledFlag bool + + ShortTermRefPicSets []*SPS_ShortTermRefPicSet + LongTermRefPicsPresentFlag bool + TemporalMvpEnabledFlag bool + StrongIntraSmoothingEnabledFlag bool + VUI *SPS_VUI +} + +// Unmarshal decodes a SPS from bytes. +func (s *SPS) Unmarshal(buf []byte) error { + if len(buf) < 2 { + return fmt.Errorf("not enough bits") + } + + if NALUType((buf[0]>>1)&0b111111) != NALUType_SPS_NUT { + return fmt.Errorf("not a SPS") + } + + buf = h264.EmulationPreventionRemove(buf[1:]) + pos := 8 + + err := bits.HasSpace(buf, pos, 8) + if err != nil { + return err + } + + s.VPSID = uint8(bits.ReadBitsUnsafe(buf, &pos, 4)) + s.MaxSubLayersMinus1 = uint8(bits.ReadBitsUnsafe(buf, &pos, 3)) + s.TemporalIDNestingFlag = bits.ReadFlagUnsafe(buf, &pos) + + err = s.ProfileTierLevel.unmarshal(buf, &pos, s.MaxSubLayersMinus1) + if err != nil { + return err + } + + tmp2, err := bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + s.ID = uint8(tmp2) + + s.ChromaFormatIdc, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + // this prevents a panic in Marshal() + if s.ChromaFormatIdc > 3 { + return fmt.Errorf("invalid chroma_format_idc") + } + + if s.ChromaFormatIdc == 3 { + s.SeparateColourPlaneFlag, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + } + + s.PicWidthInLumaSamples, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + s.PicHeightInLumaSamples, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + conformanceWindowFlag, err := bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + if conformanceWindowFlag { + s.ConformanceWindow = &SPS_Window{} + err = s.ConformanceWindow.unmarshal(buf, &pos) + if err != nil { + return err + } + } else { + s.ConformanceWindow = nil + } + + s.BitDepthLumaMinus8, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + s.BitDepthChromaMinus8, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + s.Log2MaxPicOrderCntLsbMinus4, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + s.SubLayerOrderingInfoPresentFlag, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + var start uint8 + if s.SubLayerOrderingInfoPresentFlag { + start = 0 + } else { + start = s.MaxSubLayersMinus1 + } + + s.MaxDecPicBufferingMinus1 = make([]uint32, s.MaxSubLayersMinus1+1) + s.MaxNumReorderPics = make([]uint32, s.MaxSubLayersMinus1+1) + s.MaxLatencyIncreasePlus1 = make([]uint32, s.MaxSubLayersMinus1+1) + + for i := start; i <= s.MaxSubLayersMinus1; i++ { + s.MaxDecPicBufferingMinus1[i], err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + s.MaxNumReorderPics[i], err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + s.MaxLatencyIncreasePlus1[i], err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + } + + s.Log2MinLumaCodingBlockSizeMinus3, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + s.Log2DiffMaxMinLumaCodingBlockSize, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + s.Log2MinLumaTransformBlockSizeMinus2, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + s.Log2DiffMaxMinLumaTransformBlockSize, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + s.MaxTransformHierarchyDepthInter, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + s.MaxTransformHierarchyDepthIntra, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + s.ScalingListEnabledFlag, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + if s.ScalingListEnabledFlag { + var scalingListDataPresentFlag bool + scalingListDataPresentFlag, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + if scalingListDataPresentFlag { + s.ScalingListData = &SPS_ScalingListData{} + err = s.ScalingListData.unmarshal(buf, &pos) + if err != nil { + return err + } + } + } + + s.AmpEnabledFlag, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + s.SampleAdaptiveOffsetEnabledFlag, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + s.PcmEnabledFlag, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + if s.PcmEnabledFlag { + err = bits.HasSpace(buf, pos, 8) + if err != nil { + return err + } + + s.PcmSampleBitDepthLumaMinus1 = uint8(bits.ReadBitsUnsafe(buf, &pos, 4)) + s.PcmSampleBitDepthChromaMinus1 = uint8(bits.ReadBitsUnsafe(buf, &pos, 4)) + + s.Log2MinPcmLumaCodingBlockSizeMinus3, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + s.Log2DiffMaxMinPcmLumaCodingBlockSize, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + s.PcmLoopFilterDisabledFlag, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + } + + numShortTermRefPicSets, err := bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + if numShortTermRefPicSets > 0 { + if numShortTermRefPicSets > maxShortTermRefPics { + return fmt.Errorf("num_short_term_ref_pic_sets exceeds %d", maxShortTermRefPics) + } + + s.ShortTermRefPicSets = make([]*SPS_ShortTermRefPicSet, numShortTermRefPicSets) + + for i := uint32(0); i < numShortTermRefPicSets; i++ { + s.ShortTermRefPicSets[i] = &SPS_ShortTermRefPicSet{} + err = s.ShortTermRefPicSets[i].unmarshal(buf, &pos, i, numShortTermRefPicSets, s.ShortTermRefPicSets) + if err != nil { + return err + } + } + } else { + s.ShortTermRefPicSets = nil + } + + s.LongTermRefPicsPresentFlag, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + if s.LongTermRefPicsPresentFlag { + var numLongTermRefPicsSPS uint32 + numLongTermRefPicsSPS, err = bits.ReadGolombUnsigned(buf, &pos) + if err != nil { + return err + } + + if numLongTermRefPicsSPS > 0 { + return fmt.Errorf("long term ref pics inside SPS are not supported yet") + } + } + + s.TemporalMvpEnabledFlag, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + s.StrongIntraSmoothingEnabledFlag, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + vuiParametersPresentFlag, err := bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + if vuiParametersPresentFlag { + s.VUI = &SPS_VUI{} + err := s.VUI.unmarshal(buf, &pos) + if err != nil { + return err + } + } else { + s.VUI = nil + } + + return nil +} + +// Width returns the video width. +func (s SPS) Width() int { + width := s.PicWidthInLumaSamples + + if s.ConformanceWindow != nil { + cropUnitX := subWidthC[s.ChromaFormatIdc] + width -= (s.ConformanceWindow.LeftOffset + s.ConformanceWindow.RightOffset) * cropUnitX + } + + return int(width) +} + +// Height returns the video height. +func (s SPS) Height() int { + height := s.PicHeightInLumaSamples + + if s.ConformanceWindow != nil { + cropUnitY := subHeightC[s.ChromaFormatIdc] + height -= (s.ConformanceWindow.TopOffset + s.ConformanceWindow.BottomOffset) * cropUnitY + } + + return int(height) +} + +// FPS returns the frames per second of the video. +func (s SPS) FPS() float64 { + if s.VUI == nil || s.VUI.TimingInfo == nil { + return 0 + } + + return float64(s.VUI.TimingInfo.TimeScale) / float64(s.VUI.TimingInfo.NumUnitsInTick) +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/define_huffman_table.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/define_huffman_table.go new file mode 100644 index 000000000..bc46dac34 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/define_huffman_table.go @@ -0,0 +1,20 @@ +package jpeg + +// DefineHuffmanTable is a DHT marker. +type DefineHuffmanTable struct { + Codes []byte + Symbols []byte + TableNumber int + TableClass int +} + +// Marshal encodes the marker. +func (m DefineHuffmanTable) Marshal(buf []byte) []byte { + buf = append(buf, []byte{0xFF, MarkerDefineHuffmanTable}...) + s := 3 + len(m.Codes) + len(m.Symbols) + buf = append(buf, []byte{byte(s >> 8), byte(s)}...) // length + buf = append(buf, []byte{byte(m.TableClass<<4) | byte(m.TableNumber)}...) + buf = append(buf, m.Codes...) + buf = append(buf, m.Symbols...) + return buf +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/define_quantization_table.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/define_quantization_table.go new file mode 100644 index 000000000..b141fc695 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/define_quantization_table.go @@ -0,0 +1,61 @@ +package jpeg + +import ( + "fmt" +) + +// QuantizationTable is a DQT quantization table. +type QuantizationTable struct { + ID uint8 + Precision uint8 + Data []byte +} + +// DefineQuantizationTable is a DQT marker. +type DefineQuantizationTable struct { + Tables []QuantizationTable +} + +// Unmarshal decodes the marker. +func (m *DefineQuantizationTable) Unmarshal(buf []byte) error { + for len(buf) != 0 { + id := buf[0] & 0x0F + precision := buf[0] >> 4 + buf = buf[1:] + if precision != 0 { + return fmt.Errorf("Precision %d is not supported", precision) + } + + if len(buf) < 64 { + return fmt.Errorf("image is too short") + } + + m.Tables = append(m.Tables, QuantizationTable{ + ID: id, + Precision: precision, + Data: buf[:64], + }) + buf = buf[64:] + } + + return nil +} + +// Marshal encodes the marker. +func (m DefineQuantizationTable) Marshal(buf []byte) []byte { + buf = append(buf, []byte{0xFF, MarkerDefineQuantizationTable}...) + + // length + s := 2 + for _, t := range m.Tables { + s += 1 + len(t.Data) + } + buf = append(buf, []byte{byte(s >> 8), byte(s)}...) + + for _, t := range m.Tables { + buf = append(buf, []byte{(t.ID)}...) + buf = append(buf, t.Data...) + } + + return buf +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/define_restart_interval.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/define_restart_interval.go new file mode 100644 index 000000000..50a3026ab --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/define_restart_interval.go @@ -0,0 +1,20 @@ +package jpeg + +import ( + "fmt" +) + +// DefineRestartInterval is a DRI marker. +type DefineRestartInterval struct { + Interval uint16 +} + +// Unmarshal decodes the marker. +func (m *DefineRestartInterval) Unmarshal(buf []byte) error { + if len(buf) != 2 { + return fmt.Errorf("unsupported DRI size of %d", len(buf)) + } + + m.Interval = uint16(buf[0])<<8 | uint16(buf[1]) + return nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/jpeg.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/jpeg.go new file mode 100644 index 000000000..ce2408adc --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/jpeg.go @@ -0,0 +1,14 @@ +// Package jpeg contains utilities to work with the JPEG codec. +package jpeg + +// standard JPEG markers. +const ( + MarkerStartOfImage = 0xD8 + MarkerDefineQuantizationTable = 0xDB + MarkerDefineHuffmanTable = 0xC4 + MarkerDefineRestartInterval = 0xDD + MarkerStartOfFrame1 = 0xC0 + MarkerStartOfScan = 0xDA + MarkerEndOfImage = 0xD9 + MarkerComment = 0xFE +) diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/start_of_frame1.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/start_of_frame1.go new file mode 100644 index 000000000..8eb2a25eb --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/start_of_frame1.go @@ -0,0 +1,83 @@ +package jpeg + +import ( + "fmt" +) + +// StartOfFrame1 is a SOF1 marker. +type StartOfFrame1 struct { + Type uint8 + Width int + Height int + QuantizationTableCount uint8 // write only +} + +// Unmarshal decodes the marker. +func (m *StartOfFrame1) Unmarshal(buf []byte) error { + if len(buf) != 15 { + return fmt.Errorf("unsupported SOF size of %d", len(buf)) + } + + precision := buf[0] + if precision != 8 { + return fmt.Errorf("precision %d is not supported", precision) + } + + m.Height = int(buf[1])<<8 | int(buf[2]) + m.Width = int(buf[3])<<8 | int(buf[4]) + + components := buf[5] + if components != 3 { + return fmt.Errorf("number of components = %d is not supported", components) + } + + samp0 := buf[7] + switch samp0 { + case 0x21: + m.Type = 0 + + case 0x22: + m.Type = 1 + + default: + return fmt.Errorf("samp0 %x is not supported", samp0) + } + + samp1 := buf[10] + if samp1 != 0x11 { + return fmt.Errorf("samp1 %x is not supported", samp1) + } + + samp2 := buf[13] + if samp2 != 0x11 { + return fmt.Errorf("samp2 %x is not supported", samp2) + } + + return nil +} + +// Marshal encodes the marker. +func (m StartOfFrame1) Marshal(buf []byte) []byte { + buf = append(buf, []byte{0xFF, MarkerStartOfFrame1}...) + buf = append(buf, []byte{0, 17}...) // length + buf = append(buf, []byte{8}...) // precision + buf = append(buf, []byte{byte(m.Height >> 8), byte(m.Height)}...) // height + buf = append(buf, []byte{byte(m.Width >> 8), byte(m.Width)}...) // width + buf = append(buf, []byte{3}...) // components + if (m.Type & 0x3f) == 0 { // component 0 + buf = append(buf, []byte{0x00, 0x21, 0}...) + } else { + buf = append(buf, []byte{0x00, 0x22, 0}...) + } + + var secondQuantizationTable byte + if m.QuantizationTableCount == 2 { + secondQuantizationTable = 1 + } else { + secondQuantizationTable = 0 + } + + buf = append(buf, []byte{1, 0x11, secondQuantizationTable}...) // component 1 + buf = append(buf, []byte{2, 0x11, secondQuantizationTable}...) // component 2 + return buf +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/start_of_image.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/start_of_image.go new file mode 100644 index 000000000..9a6ea7a5e --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/start_of_image.go @@ -0,0 +1,10 @@ +package jpeg + +// StartOfImage is a SOI marker. +type StartOfImage struct{} + +// Marshal encodes the marker. +func (StartOfImage) Marshal(buf []byte) []byte { + buf = append(buf, []byte{0xFF, MarkerStartOfImage}...) + return buf +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/start_of_scan.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/start_of_scan.go new file mode 100644 index 000000000..da78cb746 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg/start_of_scan.go @@ -0,0 +1,28 @@ +package jpeg + +import ( + "fmt" +) + +// StartOfScan is a SOS marker. +type StartOfScan struct{} + +// Unmarshal decodes the marker. +func (StartOfScan) Unmarshal(buf []byte) error { + if len(buf) != 10 { + return fmt.Errorf("unsupported SOS size of %d", len(buf)) + } + return nil +} + +// Marshal encodes the marker. +func (StartOfScan) Marshal(buf []byte) []byte { + buf = append(buf, []byte{0xFF, MarkerStartOfScan}...) + buf = append(buf, []byte{0, 12}...) // length + buf = append(buf, []byte{3}...) // components + buf = append(buf, []byte{0, 0}...) // component 0 + buf = append(buf, []byte{1, 0x11}...) // component 1 + buf = append(buf, []byte{2, 0x11}...) // component 2 + buf = append(buf, []byte{0, 63, 0}...) + return buf +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg1audio/frame_header.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg1audio/frame_header.go new file mode 100644 index 000000000..5e4c7a1de --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg1audio/frame_header.go @@ -0,0 +1,202 @@ +package mpeg1audio + +import ( + "fmt" +) + +// http://www.mp3-tech.org/programmer/frame_header.html +var bitrates = [][][]int{ + // MPEG-1 + { + // layer 1 + {}, + // layer 2 + { + 32000, + 48000, + 56000, + 64000, + 80000, + 96000, + 112000, + 128000, + 160000, + 192000, + 224000, + 256000, + 320000, + 384000, + }, + // layer 3 + { + 32000, + 40000, + 48000, + 56000, + 64000, + 80000, + 96000, + 112000, + 128000, + 160000, + 192000, + 224000, + 256000, + 320000, + }, + }, + // MPEG-2 + { + // layer 1 + {}, + // layer 2 + { + 8000, + 16000, + 24000, + 32000, + 40000, + 48000, + 56000, + 64000, + 80000, + 96000, + 112000, + 128000, + 144000, + 160000, + }, + // layer 3 + { + 8000, + 16000, + 24000, + 32000, + 40000, + 48000, + 56000, + 64000, + 80000, + 96000, + 112000, + 128000, + 144000, + 160000, + }, + }, +} + +var sampleRates = [][]int{ + // MPEG-1 + { + 44100, + 48000, + 32000, + }, + // MPEG-2 + { + 22050, + 24000, + 16000, + }, +} + +var samplesPerFrame = [][]int{ + // MPEG-1 + { + 384, + 1152, + 1152, + }, + // MPEG-2 + { + 384, + 1152, + 576, + }, +} + +// ChannelMode is a channel mode of a MPEG-1/2 audio frame. +type ChannelMode int + +// standard channel modes. +const ( + ChannelModeStereo ChannelMode = 0 + ChannelModeJointStereo ChannelMode = 1 + ChannelModeDualChannel ChannelMode = 2 + ChannelModeMono ChannelMode = 3 +) + +// FrameHeader is the header of a MPEG-1/2 audio frame. +// Specification: ISO 11172-3, 2.4.1.3 +type FrameHeader struct { + MPEG2 bool + Layer uint8 + Bitrate int + SampleRate int + Padding bool + ChannelMode ChannelMode +} + +// Unmarshal decodes a FrameHeader. +func (h *FrameHeader) Unmarshal(buf []byte) error { + if len(buf) < 5 { + return fmt.Errorf("not enough bytes") + } + + syncWord := uint16(buf[0])<<4 | uint16(buf[1])>>4 + if syncWord != 0x0FFF { + return fmt.Errorf("sync word not found: %x", syncWord) + } + + h.MPEG2 = ((buf[1] >> 3) & 0x01) == 0 + + var mpegIndex int + if h.MPEG2 { + mpegIndex = 1 + } else { + mpegIndex = 0 + } + + h.Layer = 4 - ((buf[1] >> 1) & 0b11) + if h.Layer <= 1 || h.Layer >= 4 { + return fmt.Errorf("unsupported MPEG layer: %v", h.Layer) + } + + bitrateIndex := (buf[2] >> 4) + if bitrateIndex == 0 || bitrateIndex >= 15 { + return fmt.Errorf("invalid bitrate") + } + h.Bitrate = bitrates[mpegIndex][h.Layer-1][bitrateIndex-1] + + sampleRateIndex := (buf[2] >> 2) & 0b11 + if sampleRateIndex >= 3 { + return fmt.Errorf("invalid sample rate") + } + h.SampleRate = sampleRates[mpegIndex][sampleRateIndex] + + h.Padding = ((buf[2] >> 1) & 0b1) != 0 + h.ChannelMode = ChannelMode(buf[3] >> 6) + + return nil +} + +// FrameLen returns the length of the frame associated with the header. +func (h FrameHeader) FrameLen() int { + if h.Padding { + return (144 * h.Bitrate / h.SampleRate) + 1 + } + return (144 * h.Bitrate / h.SampleRate) +} + +// SampleCount returns the number of samples contained into the frame. +func (h FrameHeader) SampleCount() int { + var mpegIndex int + if h.MPEG2 { + mpegIndex = 1 + } else { + mpegIndex = 0 + } + + return samplesPerFrame[mpegIndex][h.Layer-1] +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg1audio/mpeg1_audio.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg1audio/mpeg1_audio.go new file mode 100644 index 000000000..67b0f4e91 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg1audio/mpeg1_audio.go @@ -0,0 +1,2 @@ +// Package mpeg1audio contains utilities to work with MPEG-1/2 audio codecs. +package mpeg1audio diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/adts.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/adts.go new file mode 100644 index 000000000..499bf2c68 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/adts.go @@ -0,0 +1,153 @@ +package mpeg4audio + +import ( + "fmt" +) + +// ADTSPacket is an ADTS packet. +// Specification: ISO 14496-3, Table 1.A.5 +type ADTSPacket struct { + Type ObjectType + SampleRate int + ChannelCount int + AU []byte +} + +// ADTSPackets is a group of ADTS packets. +type ADTSPackets []*ADTSPacket + +// Unmarshal decodes an ADTS stream into ADTS packets. +func (ps *ADTSPackets) Unmarshal(buf []byte) error { + // refs: https://wiki.multimedia.cx/index.php/ADTS + + bl := len(buf) + pos := 0 + + for { + if (bl - pos) < 8 { + return fmt.Errorf("invalid length") + } + + syncWord := (uint16(buf[pos]) << 4) | (uint16(buf[pos+1]) >> 4) + if syncWord != 0xfff { + return fmt.Errorf("invalid syncword") + } + + protectionAbsent := buf[pos+1] & 0x01 + if protectionAbsent != 1 { + return fmt.Errorf("CRC is not supported") + } + + pkt := &ADTSPacket{} + + pkt.Type = ObjectType((buf[pos+2] >> 6) + 1) + switch pkt.Type { + case ObjectTypeAACLC: + default: + return fmt.Errorf("unsupported audio type: %d", pkt.Type) + } + + sampleRateIndex := (buf[pos+2] >> 2) & 0x0F + switch { + case sampleRateIndex <= 12: + pkt.SampleRate = sampleRates[sampleRateIndex] + + default: + return fmt.Errorf("invalid sample rate index: %d", sampleRateIndex) + } + + channelConfig := ((buf[pos+2] & 0x01) << 2) | ((buf[pos+3] >> 6) & 0x03) + switch { + case channelConfig >= 1 && channelConfig <= 6: + pkt.ChannelCount = int(channelConfig) + + case channelConfig == 7: + pkt.ChannelCount = 8 + + default: + return fmt.Errorf("invalid channel configuration: %d", channelConfig) + } + + frameLen := int(((uint16(buf[pos+3])&0x03)<<11)| + (uint16(buf[pos+4])<<3)| + ((uint16(buf[pos+5])>>5)&0x07)) - 7 + + if frameLen <= 0 { + return fmt.Errorf("invalid FrameLen") + } + + if frameLen > MaxAccessUnitSize { + return fmt.Errorf("access unit size (%d) is too big, maximum is %d", frameLen, MaxAccessUnitSize) + } + + frameCount := buf[pos+6] & 0x03 + if frameCount != 0 { + return fmt.Errorf("frame count greater than 1 is not supported") + } + + if len(buf[pos+7:]) < frameLen { + return fmt.Errorf("invalid frame length") + } + + pkt.AU = buf[pos+7 : pos+7+frameLen] + pos += 7 + frameLen + + *ps = append(*ps, pkt) + + if (bl - pos) == 0 { + break + } + } + + return nil +} + +func (ps ADTSPackets) marshalSize() int { + n := 0 + for _, pkt := range ps { + n += 7 + len(pkt.AU) + } + return n +} + +// Marshal encodes ADTS packets into an ADTS stream. +func (ps ADTSPackets) Marshal() ([]byte, error) { + buf := make([]byte, ps.marshalSize()) + pos := 0 + + for _, pkt := range ps { + sampleRateIndex, ok := reverseSampleRates[pkt.SampleRate] + if !ok { + return nil, fmt.Errorf("invalid sample rate: %d", pkt.SampleRate) + } + + var channelConfig int + switch { + case pkt.ChannelCount >= 1 && pkt.ChannelCount <= 6: + channelConfig = pkt.ChannelCount + + case pkt.ChannelCount == 8: + channelConfig = 7 + + default: + return nil, fmt.Errorf("invalid channel count (%d)", pkt.ChannelCount) + } + + frameLen := len(pkt.AU) + 7 + + fullness := 0x07FF // like ffmpeg does + + buf[pos+0] = 0xFF + buf[pos+1] = 0xF1 + buf[pos+2] = uint8((int(pkt.Type-1) << 6) | (sampleRateIndex << 2) | ((channelConfig >> 2) & 0x01)) + buf[pos+3] = uint8((channelConfig&0x03)<<6 | (frameLen>>11)&0x03) + buf[pos+4] = uint8((frameLen >> 3) & 0xFF) + buf[pos+5] = uint8((frameLen&0x07)<<5 | ((fullness >> 6) & 0x1F)) + buf[pos+6] = uint8((fullness & 0x3F) << 2) + pos += 7 + + pos += copy(buf[pos:], pkt.AU) + } + + return buf, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/audio_specific_config.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/audio_specific_config.go new file mode 100644 index 000000000..c9b5181fe --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/audio_specific_config.go @@ -0,0 +1,261 @@ +package mpeg4audio + +import ( + "fmt" + + "github.com/bluenviron/mediacommon/v2/pkg/bits" +) + +// Config is an alias for AudioSpecificConfig. +type Config = AudioSpecificConfig + +// AudioSpecificConfig is an AudioSpecificConfig. +// Specification: ISO 14496-3, 1.6.2.1 +type AudioSpecificConfig struct { + Type ObjectType + SampleRate int + ChannelCount int + + // SBR / PS specific + ExtensionType ObjectType + ExtensionSampleRate int + + FrameLengthFlag bool + DependsOnCoreCoder bool + CoreCoderDelay uint16 +} + +// Unmarshal decodes a Config. +func (c *AudioSpecificConfig) Unmarshal(buf []byte) error { + pos := 0 + return c.UnmarshalFromPos(buf, &pos) +} + +// UnmarshalFromPos decodes a Config. +func (c *AudioSpecificConfig) UnmarshalFromPos(buf []byte, pos *int) error { + tmp, err := bits.ReadBits(buf, pos, 5) + if err != nil { + return err + } + c.Type = ObjectType(tmp) + + switch c.Type { + case ObjectTypeAACLC, ObjectTypeSBR, ObjectTypePS: + default: + return fmt.Errorf("unsupported object type: %d", c.Type) + } + + sampleRateIndex, err := bits.ReadBits(buf, pos, 4) + if err != nil { + return err + } + + switch { + case sampleRateIndex <= 12: + c.SampleRate = sampleRates[sampleRateIndex] + + case sampleRateIndex == 0x0F: + tmp, err = bits.ReadBits(buf, pos, 24) + if err != nil { + return err + } + c.SampleRate = int(tmp) + + default: + return fmt.Errorf("invalid sample rate index (%d)", sampleRateIndex) + } + + channelConfig, err := bits.ReadBits(buf, pos, 4) + if err != nil { + return err + } + + switch { + case channelConfig == 0: + return fmt.Errorf("not yet supported") + + case channelConfig >= 1 && channelConfig <= 6: + c.ChannelCount = int(channelConfig) + + case channelConfig == 7: + c.ChannelCount = 8 + + default: + return fmt.Errorf("invalid channel configuration (%d)", channelConfig) + } + + if c.Type == ObjectTypeSBR || c.Type == ObjectTypePS { + c.ExtensionType = c.Type + + var extensionSamplingFrequencyIndex uint64 + extensionSamplingFrequencyIndex, err = bits.ReadBits(buf, pos, 4) + if err != nil { + return err + } + + switch { + case extensionSamplingFrequencyIndex <= 12: + c.ExtensionSampleRate = sampleRates[extensionSamplingFrequencyIndex] + + case extensionSamplingFrequencyIndex == 0x0F: + tmp, err = bits.ReadBits(buf, pos, 24) + if err != nil { + return err + } + c.ExtensionSampleRate = int(tmp) + + default: + return fmt.Errorf("invalid extension sample rate index (%d)", extensionSamplingFrequencyIndex) + } + + tmp, err = bits.ReadBits(buf, pos, 5) + if err != nil { + return err + } + c.Type = ObjectType(tmp) + + if c.Type != ObjectTypeAACLC { + return fmt.Errorf("unsupported object type: %d", c.Type) + } + } + + c.FrameLengthFlag, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + c.DependsOnCoreCoder, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if c.DependsOnCoreCoder { + tmp, err = bits.ReadBits(buf, pos, 14) + if err != nil { + return err + } + c.CoreCoderDelay = uint16(tmp) + } + + extensionFlag, err := bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if extensionFlag { + return fmt.Errorf("unsupported") + } + + return nil +} + +func (c AudioSpecificConfig) marshalSizeBits() int { + n := 5 + 4 + 2 + 1 + + _, ok := reverseSampleRates[c.SampleRate] + if !ok { + n += 28 + } else { + n += 4 + } + + if c.ExtensionType == ObjectTypeSBR || c.ExtensionType == ObjectTypePS { + _, ok := reverseSampleRates[c.ExtensionSampleRate] + if !ok { + n += 28 + } else { + n += 4 + } + n += 5 + } + + if c.DependsOnCoreCoder { + n += 14 + } + + return n +} + +func (c AudioSpecificConfig) marshalSize() int { + n := c.marshalSizeBits() + + ret := n / 8 + if (n % 8) != 0 { + ret++ + } + + return ret +} + +// Marshal encodes a Config. +func (c AudioSpecificConfig) Marshal() ([]byte, error) { + buf := make([]byte, c.marshalSize()) + pos := 0 + + err := c.marshalTo(buf, &pos) + if err != nil { + return nil, err + } + + return buf, nil +} + +func (c AudioSpecificConfig) marshalTo(buf []byte, pos *int) error { + if c.ExtensionType == ObjectTypeSBR || c.ExtensionType == ObjectTypePS { + bits.WriteBitsUnsafe(buf, pos, uint64(c.ExtensionType), 5) + } else { + bits.WriteBitsUnsafe(buf, pos, uint64(c.Type), 5) + } + + sampleRateIndex, ok := reverseSampleRates[c.SampleRate] + if !ok { + bits.WriteBitsUnsafe(buf, pos, uint64(15), 4) + bits.WriteBitsUnsafe(buf, pos, uint64(c.SampleRate), 24) + } else { + bits.WriteBitsUnsafe(buf, pos, uint64(sampleRateIndex), 4) + } + + var channelConfig int + switch { + case c.ChannelCount >= 1 && c.ChannelCount <= 6: + channelConfig = c.ChannelCount + + case c.ChannelCount == 8: + channelConfig = 7 + + default: + return fmt.Errorf("invalid channel count (%d)", c.ChannelCount) + } + bits.WriteBitsUnsafe(buf, pos, uint64(channelConfig), 4) + + if c.ExtensionType == ObjectTypeSBR || c.ExtensionType == ObjectTypePS { + sampleRateIndex, ok := reverseSampleRates[c.ExtensionSampleRate] + if !ok { + bits.WriteBitsUnsafe(buf, pos, uint64(0x0F), 4) + bits.WriteBitsUnsafe(buf, pos, uint64(c.ExtensionSampleRate), 24) + } else { + bits.WriteBitsUnsafe(buf, pos, uint64(sampleRateIndex), 4) + } + bits.WriteBitsUnsafe(buf, pos, uint64(c.Type), 5) + } + + if c.FrameLengthFlag { + bits.WriteBitsUnsafe(buf, pos, 1, 1) + } else { + bits.WriteBitsUnsafe(buf, pos, 0, 1) + } + + if c.DependsOnCoreCoder { + bits.WriteBitsUnsafe(buf, pos, 1, 1) + } else { + bits.WriteBitsUnsafe(buf, pos, 0, 1) + } + + if c.DependsOnCoreCoder { + bits.WriteBitsUnsafe(buf, pos, uint64(c.CoreCoderDelay), 14) + } + + *pos++ // extensionFlag + + return nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/mpeg4_audio.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/mpeg4_audio.go new file mode 100644 index 000000000..9d7ddcbe1 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/mpeg4_audio.go @@ -0,0 +1,10 @@ +// Package mpeg4audio contains utilities to work with MPEG-4 audio codecs. +package mpeg4audio + +const ( + // MaxAccessUnitSize is the maximum size of an access unit. + MaxAccessUnitSize = 5 * 1024 + + // SamplesPerAccessUnit is the number of samples contained inside an access unit. + SamplesPerAccessUnit = 1024 +) diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/object_type.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/object_type.go new file mode 100644 index 000000000..e84603992 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/object_type.go @@ -0,0 +1,12 @@ +package mpeg4audio + +// ObjectType is a MPEG-4 Audio object type. +// Specification: ISO 14496-3, Table 1.17 +type ObjectType int + +// supported types. +const ( + ObjectTypeAACLC ObjectType = 2 + ObjectTypeSBR ObjectType = 5 + ObjectTypePS ObjectType = 29 +) diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/sample_rates.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/sample_rates.go new file mode 100644 index 000000000..660b86e27 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/sample_rates.go @@ -0,0 +1,33 @@ +package mpeg4audio + +var sampleRates = []int{ + 96000, + 88200, + 64000, + 48000, + 44100, + 32000, + 24000, + 22050, + 16000, + 12000, + 11025, + 8000, + 7350, +} + +var reverseSampleRates = map[int]int{ + 96000: 0, + 88200: 1, + 64000: 2, + 48000: 3, + 44100: 4, + 32000: 5, + 24000: 6, + 22050: 7, + 16000: 8, + 12000: 9, + 11025: 10, + 8000: 11, + 7350: 12, +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/stream_mux_config.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/stream_mux_config.go new file mode 100644 index 000000000..348b9c641 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio/stream_mux_config.go @@ -0,0 +1,324 @@ +package mpeg4audio + +import ( + "fmt" + + "github.com/bluenviron/mediacommon/v2/pkg/bits" +) + +// StreamMuxConfigLayer is a layer of a StreamMuxConfig. +type StreamMuxConfigLayer struct { + AudioSpecificConfig *AudioSpecificConfig + FrameLengthType uint + LatmBufferFullness uint + FrameLength uint + CELPframeLengthTableIndex uint + HVXCframeLengthTableIndex bool +} + +// StreamMuxConfigProgram is a program of a StreamMuxConfig. +type StreamMuxConfigProgram struct { + Layers []*StreamMuxConfigLayer +} + +// StreamMuxConfig is a StreamMuxConfig. +// Specification: ISO 14496-3, Table 1.42 +type StreamMuxConfig struct { + NumSubFrames uint + Programs []*StreamMuxConfigProgram + OtherDataPresent bool + OtherDataLenBits uint32 + CRCCheckPresent bool + CRCCheckSum uint8 +} + +// Unmarshal decodes a StreamMuxConfig. +func (c *StreamMuxConfig) Unmarshal(buf []byte) error { + pos := 0 + + err := bits.HasSpace(buf, pos, 12) + if err != nil { + return err + } + + audioMuxVersion := bits.ReadFlagUnsafe(buf, &pos) + if audioMuxVersion { + return fmt.Errorf("audioMuxVersion = 1 is not supported") + } + + allStreamsSameTimeFraming := bits.ReadFlagUnsafe(buf, &pos) + if !allStreamsSameTimeFraming { + return fmt.Errorf("allStreamsSameTimeFraming = 0 is not supported") + } + + c.NumSubFrames = uint(bits.ReadBitsUnsafe(buf, &pos, 6)) + numProgram := uint(bits.ReadBitsUnsafe(buf, &pos, 4)) + + c.Programs = make([]*StreamMuxConfigProgram, numProgram+1) + + for prog := uint(0); prog <= numProgram; prog++ { + p := &StreamMuxConfigProgram{} + c.Programs[prog] = p + + var numLayer uint64 + numLayer, err = bits.ReadBits(buf, &pos, 3) + if err != nil { + return err + } + + p.Layers = make([]*StreamMuxConfigLayer, numLayer+1) + + for lay := uint(0); lay <= uint(numLayer); lay++ { + l := &StreamMuxConfigLayer{} + p.Layers[lay] = l + + var useSameConfig bool + + if prog == 0 && lay == 0 { + useSameConfig = false + } else { + useSameConfig, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + } + + if !useSameConfig { + l.AudioSpecificConfig = &AudioSpecificConfig{} + err = l.AudioSpecificConfig.UnmarshalFromPos(buf, &pos) + if err != nil { + return err + } + } + + var tmp uint64 + tmp, err = bits.ReadBits(buf, &pos, 3) + if err != nil { + // support truncated configs + l.LatmBufferFullness = 255 + c.Programs = c.Programs[:prog+1] + p.Layers = p.Layers[:lay+1] + return nil //nolint:nilerr + } + l.FrameLengthType = uint(tmp) + + switch l.FrameLengthType { + case 0: + tmp, err = bits.ReadBits(buf, &pos, 8) + if err != nil { + return err + } + l.LatmBufferFullness = uint(tmp) + + case 1: + tmp, err = bits.ReadBits(buf, &pos, 9) + if err != nil { + return err + } + l.FrameLength = uint(tmp) + + case 4, 5, 3: + tmp, err = bits.ReadBits(buf, &pos, 6) + if err != nil { + return err + } + l.CELPframeLengthTableIndex = uint(tmp) + + case 6, 7: + l.HVXCframeLengthTableIndex, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + } + } + } + + c.OtherDataPresent, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + if c.OtherDataPresent { + for { + c.OtherDataLenBits *= 256 + + err = bits.HasSpace(buf, pos, 9) + if err != nil { + return err + } + + otherDataLenEsc := bits.ReadFlagUnsafe(buf, &pos) + otherDataLenTmp := uint32(bits.ReadBitsUnsafe(buf, &pos, 8)) + c.OtherDataLenBits += otherDataLenTmp + + if !otherDataLenEsc { + break + } + } + } + + c.CRCCheckPresent, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + if c.CRCCheckPresent { + tmp, err := bits.ReadBits(buf, &pos, 8) + if err != nil { + return err + } + c.CRCCheckSum = uint8(tmp) + } + + return nil +} + +func (c StreamMuxConfig) marshalSize() int { + n := 12 + + for prog, p := range c.Programs { + n += 3 + + for lay, l := range p.Layers { + if prog != 0 || lay != 0 { + n++ + } + + if l.AudioSpecificConfig != nil { + n += l.AudioSpecificConfig.marshalSizeBits() + } + + n += 3 + + switch l.FrameLengthType { + case 0: + n += 8 + + case 1: + n += 9 + + case 4, 5, 3: + n += 6 + + case 6, 7: + n++ + } + } + } + + n++ // otherDataPresent + + if c.OtherDataPresent { + tmp := c.OtherDataLenBits + for { + tmp /= 256 + n += 9 + + if tmp == 0 { + break + } + } + } + + n++ // crcCheckPresent + + if c.CRCCheckPresent { + n += 8 + } + + ret := n / 8 + if (n % 8) != 0 { + ret++ + } + + return ret +} + +// Marshal encodes a StreamMuxConfig. +func (c StreamMuxConfig) Marshal() ([]byte, error) { + buf := make([]byte, c.marshalSize()) + pos := 0 + + bits.WriteBitsUnsafe(buf, &pos, 0, 1) // audioMuxVersion + bits.WriteBitsUnsafe(buf, &pos, 1, 1) // allStreamsSameTimeFraming + bits.WriteBitsUnsafe(buf, &pos, uint64(c.NumSubFrames), 6) + bits.WriteBitsUnsafe(buf, &pos, uint64(len(c.Programs)-1), 4) + + for prog, p := range c.Programs { + bits.WriteBitsUnsafe(buf, &pos, uint64(len(p.Layers)-1), 3) + + for lay, l := range p.Layers { + if prog != 0 || lay != 0 { + if l.AudioSpecificConfig != nil { + bits.WriteBitsUnsafe(buf, &pos, 0, 1) + } else { + bits.WriteBitsUnsafe(buf, &pos, 1, 1) + } + } + + if l.AudioSpecificConfig != nil { + err := l.AudioSpecificConfig.marshalTo(buf, &pos) + if err != nil { + return nil, err + } + } + + bits.WriteBitsUnsafe(buf, &pos, uint64(l.FrameLengthType), 3) + + switch l.FrameLengthType { + case 0: + bits.WriteBitsUnsafe(buf, &pos, uint64(l.LatmBufferFullness), 8) + + case 1: + bits.WriteBitsUnsafe(buf, &pos, uint64(l.FrameLength), 9) + + case 4, 5, 3: + bits.WriteBitsUnsafe(buf, &pos, uint64(l.CELPframeLengthTableIndex), 6) + + case 6, 7: + if l.HVXCframeLengthTableIndex { + bits.WriteBitsUnsafe(buf, &pos, 1, 1) + } else { + bits.WriteBitsUnsafe(buf, &pos, 0, 1) + } + } + } + } + + if c.OtherDataPresent { + bits.WriteBitsUnsafe(buf, &pos, 1, 1) + + var lenBytes []byte + tmp := c.OtherDataLenBits + + for { + mod := tmp % 256 + tmp -= mod + tmp /= 256 + lenBytes = append(lenBytes, uint8(mod)) + + if tmp == 0 { + break + } + } + + for i := len(lenBytes) - 1; i > 0; i-- { + bits.WriteBitsUnsafe(buf, &pos, 1, 1) + bits.WriteBitsUnsafe(buf, &pos, uint64(lenBytes[i]), 8) + } + + bits.WriteBitsUnsafe(buf, &pos, 0, 1) + bits.WriteBitsUnsafe(buf, &pos, uint64(lenBytes[0]), 8) + } else { + bits.WriteBitsUnsafe(buf, &pos, 0, 1) + } + + if c.CRCCheckPresent { + bits.WriteBitsUnsafe(buf, &pos, 1, 1) + bits.WriteBitsUnsafe(buf, &pos, uint64(c.CRCCheckSum), 8) + } else { + bits.WriteBitsUnsafe(buf, &pos, 0, 1) + } + + return buf, nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video/is_valid_config.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video/is_valid_config.go new file mode 100644 index 000000000..41e5c8c0f --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video/is_valid_config.go @@ -0,0 +1,48 @@ +package mpeg4video + +import ( + "bytes" + "fmt" +) + +// IsValidConfig checks whether a MPEG-4 Video configuration is valid. +func IsValidConfig(config []byte) error { + if !bytes.HasPrefix(config, []byte{0, 0, 1, byte(VisualObjectSequenceStartCode)}) { + return fmt.Errorf("doesn't start with visual_object_sequence_start_code") + } + + videoObjectFound := false + videoObjectLayerFound := false + + for i := 4; i < (len(config) - 4); i++ { + if bytes.Equal(config[i:i+3], []byte{0, 0, 1}) { + startCode := StartCode(config[i+3]) + + switch { + case startCode >= VideoObjectStartCodeFirst && startCode <= VideoObjectStartCodeLast: + videoObjectFound = true + + case startCode >= VideoObjectLayerStartCodeFirst && startCode <= VideoObjectLayerStartCodeLast: + videoObjectLayerFound = true + + case startCode == VisualObjectStartCode, + startCode == UserDataStartCode: + + default: + return fmt.Errorf("unexpected start code: %x", startCode) + } + + i += 3 + } + } + + if !videoObjectFound { + return fmt.Errorf("video object not found") + } + + if !videoObjectLayerFound { + return fmt.Errorf("video object layer not found") + } + + return nil +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video/mpeg4_video.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video/mpeg4_video.go new file mode 100644 index 000000000..49f3c7045 --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video/mpeg4_video.go @@ -0,0 +1,24 @@ +// Package mpeg4video contains utilities to work with MPEG-4 part 2 video codecs. +package mpeg4video + +const ( + // MaxFrameSize is the maximum size of a frame. + MaxFrameSize = 1 * 1024 * 1024 +) + +// StartCode is a MPEG-4 Video start code. +// Specification: ISO 14496-2, Table 6-3 +type StartCode uint8 + +// start codes. +const ( + VideoObjectStartCodeFirst StartCode = 0x00 + VideoObjectStartCodeLast StartCode = 0x1F + VisualObjectSequenceStartCode StartCode = 0xB0 + VideoObjectLayerStartCodeFirst StartCode = 0x20 + VideoObjectLayerStartCodeLast StartCode = 0x2F + UserDataStartCode StartCode = 0xB2 + GroupOfVOPStartCode StartCode = 0xB3 + VisualObjectStartCode StartCode = 0xB5 + VOPStartCode StartCode = 0xB6 +) diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/vp8/vp8.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/vp8/vp8.go new file mode 100644 index 000000000..3cee8f1ee --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/vp8/vp8.go @@ -0,0 +1,7 @@ +// Package vp8 contains utilities to work with the VP8 codec. +package vp8 + +const ( + // MaxFrameSize is the maximum size of a frame. + MaxFrameSize = 2 * 1024 * 1024 +) diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/vp9/header.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/vp9/header.go new file mode 100644 index 000000000..ad52d5a9a --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/vp9/header.go @@ -0,0 +1,227 @@ +package vp9 + +import ( + "fmt" + + "github.com/bluenviron/mediacommon/v2/pkg/bits" +) + +// Header_ColorConfig is the color_config member of an header. +type Header_ColorConfig struct { //nolint:revive + TenOrTwelveBit bool + BitDepth uint8 + ColorSpace uint8 + ColorRange bool + SubsamplingX bool + SubsamplingY bool +} + +func (c *Header_ColorConfig) unmarshal(profile uint8, buf []byte, pos *int) error { + if profile >= 2 { + var err error + c.TenOrTwelveBit, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if c.TenOrTwelveBit { + c.BitDepth = 12 + } else { + c.BitDepth = 10 + } + } else { + c.BitDepth = 8 + } + + tmp, err := bits.ReadBits(buf, pos, 3) + if err != nil { + return err + } + c.ColorSpace = uint8(tmp) + + if c.ColorSpace != 7 { + var err error + c.ColorRange, err = bits.ReadFlag(buf, pos) + if err != nil { + return err + } + + if profile == 1 || profile == 3 { + err := bits.HasSpace(buf, *pos, 3) + if err != nil { + return err + } + + c.SubsamplingX = bits.ReadFlagUnsafe(buf, pos) + c.SubsamplingY = bits.ReadFlagUnsafe(buf, pos) + *pos++ + } else { + c.SubsamplingX = true + c.SubsamplingY = true + } + } else { + c.ColorRange = true + + if profile == 1 || profile == 3 { + c.SubsamplingX = false + c.SubsamplingY = false + + err := bits.HasSpace(buf, *pos, 1) + if err != nil { + return err + } + *pos++ + } + } + + return nil +} + +// Header_FrameSize is the frame_size member of an header. +type Header_FrameSize struct { //nolint:revive + FrameWidthMinus1 uint16 + FrameHeightMinus1 uint16 +} + +func (s *Header_FrameSize) unmarshal(buf []byte, pos *int) error { + err := bits.HasSpace(buf, *pos, 32) + if err != nil { + return err + } + + s.FrameWidthMinus1 = uint16(bits.ReadBitsUnsafe(buf, pos, 16)) + s.FrameHeightMinus1 = uint16(bits.ReadBitsUnsafe(buf, pos, 16)) + return nil +} + +// Header is a VP9 Frame header. +// Specification: +// https://storage.googleapis.com/downloads.webmproject.org/docs/vp9/vp9-bitstream-specification-v0.6-20160331-draft.pdf +type Header struct { + Profile uint8 + ShowExistingFrame bool + FrameToShowMapIdx uint8 + NonKeyFrame bool + ShowFrame bool + ErrorResilientMode bool + ColorConfig *Header_ColorConfig + FrameSize *Header_FrameSize +} + +// Unmarshal decodes a Header. +func (h *Header) Unmarshal(buf []byte) error { + pos := 0 + + err := bits.HasSpace(buf, pos, 4) + if err != nil { + return err + } + + frameMarker := bits.ReadBitsUnsafe(buf, &pos, 2) + if frameMarker != 2 { + return fmt.Errorf("invalid frame marker") + } + + profileLowBit := uint8(bits.ReadBitsUnsafe(buf, &pos, 1)) + profileHighBit := uint8(bits.ReadBitsUnsafe(buf, &pos, 1)) + h.Profile = profileHighBit<<1 + profileLowBit + + if h.Profile == 3 { + err = bits.HasSpace(buf, pos, 1) + if err != nil { + return err + } + pos++ + } + + h.ShowExistingFrame, err = bits.ReadFlag(buf, &pos) + if err != nil { + return err + } + + if h.ShowExistingFrame { + var tmp uint64 + tmp, err = bits.ReadBits(buf, &pos, 3) + if err != nil { + return err + } + h.FrameToShowMapIdx = uint8(tmp) + return nil + } + + err = bits.HasSpace(buf, pos, 3) + if err != nil { + return err + } + + h.NonKeyFrame = bits.ReadFlagUnsafe(buf, &pos) + h.ShowFrame = bits.ReadFlagUnsafe(buf, &pos) + h.ErrorResilientMode = bits.ReadFlagUnsafe(buf, &pos) + + if !h.NonKeyFrame { + err := bits.HasSpace(buf, pos, 24) + if err != nil { + return err + } + + frameSyncByte0 := uint8(bits.ReadBitsUnsafe(buf, &pos, 8)) + if frameSyncByte0 != 0x49 { + return fmt.Errorf("wrong frame_sync_byte_0") + } + + frameSyncByte1 := uint8(bits.ReadBitsUnsafe(buf, &pos, 8)) + if frameSyncByte1 != 0x83 { + return fmt.Errorf("wrong frame_sync_byte_1") + } + + frameSyncByte2 := uint8(bits.ReadBitsUnsafe(buf, &pos, 8)) + if frameSyncByte2 != 0x42 { + return fmt.Errorf("wrong frame_sync_byte_2") + } + + h.ColorConfig = &Header_ColorConfig{} + err = h.ColorConfig.unmarshal(h.Profile, buf, &pos) + if err != nil { + return err + } + + h.FrameSize = &Header_FrameSize{} + err = h.FrameSize.unmarshal(buf, &pos) + if err != nil { + return err + } + } + + return nil +} + +// Width returns the video width. +func (h Header) Width() int { + if h.FrameSize == nil { + return 0 + } + return int(h.FrameSize.FrameWidthMinus1) + 1 +} + +// Height returns the video height. +func (h Header) Height() int { + if h.FrameSize == nil { + return 0 + } + return int(h.FrameSize.FrameHeightMinus1) + 1 +} + +// ChromaSubsampling returns the chroma subsampling format, in ISO-BMFF/vpcC format. +func (h Header) ChromaSubsampling() uint8 { + if h.ColorConfig == nil { + return 1 + } + switch { + case !h.ColorConfig.SubsamplingX && !h.ColorConfig.SubsamplingY: + return 3 // 4:4:4 + case h.ColorConfig.SubsamplingX && !h.ColorConfig.SubsamplingY: + return 2 // 4:2:2 + default: + return 1 // 4:2:0 colocated with luma + } +} diff --git a/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/vp9/vp9.go b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/vp9/vp9.go new file mode 100644 index 000000000..1fed4f73c --- /dev/null +++ b/trunk/3rdparty/srs-bench/vendor/github.com/bluenviron/mediacommon/v2/pkg/codecs/vp9/vp9.go @@ -0,0 +1,7 @@ +// Package vp9 contains utilities to work with the VP9 codec. +package vp9 + +const ( + // MaxFrameSize is the maximum size of a frame. + MaxFrameSize = 2 * 1024 * 1024 +) diff --git a/trunk/3rdparty/srs-bench/vendor/modules.txt b/trunk/3rdparty/srs-bench/vendor/modules.txt index 2c6673217..3b629f45c 100644 --- a/trunk/3rdparty/srs-bench/vendor/modules.txt +++ b/trunk/3rdparty/srs-bench/vendor/modules.txt @@ -1,3 +1,48 @@ +# github.com/bluenviron/gortsplib/v4 v4.13.1 +## explicit; go 1.21.0 +github.com/bluenviron/gortsplib/v4 +github.com/bluenviron/gortsplib/v4/pkg/auth +github.com/bluenviron/gortsplib/v4/pkg/base +github.com/bluenviron/gortsplib/v4/pkg/bytecounter +github.com/bluenviron/gortsplib/v4/pkg/conn +github.com/bluenviron/gortsplib/v4/pkg/description +github.com/bluenviron/gortsplib/v4/pkg/format +github.com/bluenviron/gortsplib/v4/pkg/format/rtpac3 +github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1 +github.com/bluenviron/gortsplib/v4/pkg/format/rtph264 +github.com/bluenviron/gortsplib/v4/pkg/format/rtph265 +github.com/bluenviron/gortsplib/v4/pkg/format/rtplpcm +github.com/bluenviron/gortsplib/v4/pkg/format/rtpmjpeg +github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1audio +github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg1video +github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4audio +github.com/bluenviron/gortsplib/v4/pkg/format/rtpmpeg4video +github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio +github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8 +github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9 +github.com/bluenviron/gortsplib/v4/pkg/headers +github.com/bluenviron/gortsplib/v4/pkg/liberrors +github.com/bluenviron/gortsplib/v4/pkg/multicast +github.com/bluenviron/gortsplib/v4/pkg/ringbuffer +github.com/bluenviron/gortsplib/v4/pkg/rtcpreceiver +github.com/bluenviron/gortsplib/v4/pkg/rtcpsender +github.com/bluenviron/gortsplib/v4/pkg/rtplossdetector +github.com/bluenviron/gortsplib/v4/pkg/rtpreorderer +github.com/bluenviron/gortsplib/v4/pkg/rtptime +github.com/bluenviron/gortsplib/v4/pkg/sdp +# github.com/bluenviron/mediacommon/v2 v2.1.0 +## explicit; go 1.21.0 +github.com/bluenviron/mediacommon/v2/pkg/bits +github.com/bluenviron/mediacommon/v2/pkg/codecs/ac3 +github.com/bluenviron/mediacommon/v2/pkg/codecs/av1 +github.com/bluenviron/mediacommon/v2/pkg/codecs/h264 +github.com/bluenviron/mediacommon/v2/pkg/codecs/h265 +github.com/bluenviron/mediacommon/v2/pkg/codecs/jpeg +github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg1audio +github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio +github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video +github.com/bluenviron/mediacommon/v2/pkg/codecs/vp8 +github.com/bluenviron/mediacommon/v2/pkg/codecs/vp9 # github.com/ghettovoice/gosip v0.0.0-20220929080231-de8ba881be83 ## explicit; go 1.13 github.com/ghettovoice/gosip/log diff --git a/trunk/Dockerfile.test b/trunk/Dockerfile.test index cd7bde7c5..4b10ffad1 100644 --- a/trunk/Dockerfile.test +++ b/trunk/Dockerfile.test @@ -18,7 +18,7 @@ WORKDIR /srs/trunk # Note that we must enable the gcc7 or link failed. # Please note that we must disable the ffmpeg-opus, as it negatively impacts performance. We may consider # enabling it in the future when support for multi-threading transcoding is available. -RUN ./configure --srt=on --gb28181=on --srt=on --apm=on --utest=on --ffmpeg-opus=off --build-cache=on +RUN ./configure --srt=on --gb28181=on --srt=on --rtsp=on --apm=on --utest=on --ffmpeg-opus=off --build-cache=on RUN make utest ${MAKEARGS} # Build benchmark tool. diff --git a/trunk/auto/auto_headers.sh b/trunk/auto/auto_headers.sh index f15bbb8b2..6fac1b2c3 100755 --- a/trunk/auto/auto_headers.sh +++ b/trunk/auto/auto_headers.sh @@ -80,6 +80,12 @@ else srs_undefine_macro "SRS_RTC" $SRS_AUTO_HEADERS_H fi +if [[ $SRS_RTSP == YES ]]; then + srs_define_macro "SRS_RTSP" $SRS_AUTO_HEADERS_H +else + srs_undefine_macro "SRS_RTSP" $SRS_AUTO_HEADERS_H +fi + if [[ $SRS_FFMPEG_FIT == YES ]]; then srs_define_macro "SRS_FFMPEG_FIT" $SRS_AUTO_HEADERS_H else diff --git a/trunk/auto/options.sh b/trunk/auto/options.sh index 1b36046cd..72459cc42 100755 --- a/trunk/auto/options.sh +++ b/trunk/auto/options.sh @@ -6,6 +6,7 @@ help=no SRS_HDS=NO SRS_SRT=YES SRS_RTC=YES +SRS_RTSP=NO # SRS_H265 is always enabled, no longer configurable SRS_H265=RESERVED SRS_GB28181=NO @@ -184,6 +185,7 @@ Features: --utest=on|off Whether build the utest. Default: $(value2switch $SRS_UTEST) --srt=on|off Whether build the SRT. Default: $(value2switch $SRS_SRT) --rtc=on|off Whether build the WebRTC. Default: $(value2switch $SRS_RTC) + --rtsp=on|off Whether build the RTSP (requires RTC). Default: $(value2switch $SRS_RTSP) --gb28181=on|off Whether build the GB28181. Default: $(value2switch $SRS_GB28181) --cxx11=on|off Whether enable the C++11. Default: $(value2switch $SRS_CXX11) --cxx14=on|off Whether enable the C++14. Default: $(value2switch $SRS_CXX14) @@ -345,6 +347,7 @@ function parse_user_option() { --apm) SRS_APM=$(switch2value $value) ;; --srt) SRS_SRT=$(switch2value $value) ;; --rtc) SRS_RTC=$(switch2value $value) ;; + --rtsp) SRS_RTSP=$(switch2value $value) ;; --simulator) SRS_SIMULATOR=$(switch2value $value) ;; --generate-objs) SRS_GENERATE_OBJS=$(switch2value $value) ;; --single-thread) SRS_SINGLE_THREAD=$(switch2value $value) ;; @@ -537,6 +540,7 @@ function apply_auto_options() { if [[ $SRS_RTC == YES && $SRS_FFMPEG_FIT == RESERVED ]]; then SRS_FFMPEG_FIT=YES fi + if [[ $SRS_USE_SYS_FFMPEG == YES && $SRS_SHARED_FFMPEG == RESERVED ]]; then SRS_SHARED_FFMPEG=YES fi @@ -661,6 +665,7 @@ function regenerate_options() { SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --srt=$(value2switch $SRS_SRT)" SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --sys-srt=$(value2switch $SRS_USE_SYS_SRT)" SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --rtc=$(value2switch $SRS_RTC)" + SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --rtsp=$(value2switch $SRS_RTSP)" SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --gb28181=$(value2switch $SRS_GB28181)" SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --simulator=$(value2switch $SRS_SIMULATOR)" diff --git a/trunk/conf/console.conf b/trunk/conf/console.conf index 0c3c11bdb..064e71f17 100644 --- a/trunk/conf/console.conf +++ b/trunk/conf/console.conf @@ -19,6 +19,10 @@ rtc_server { # @see https://ossrs.io/lts/en-us/docs/v7/doc/webrtc#config-candidate candidate $CANDIDATE; } +rtsp_server { + enabled on; + listen 8554; +} vhost __defaultVhost__ { hls { enabled on; @@ -34,4 +38,8 @@ vhost __defaultVhost__ { # @see https://ossrs.io/lts/en-us/docs/v7/doc/webrtc#rtc-to-rtmp rtc_to_rtmp on; } + rtsp { + enabled on; + rtmp_to_rtsp on; + } } diff --git a/trunk/conf/full.conf b/trunk/conf/full.conf index 78ec9e261..cd7df4e7f 100644 --- a/trunk/conf/full.conf +++ b/trunk/conf/full.conf @@ -609,6 +609,33 @@ vhost rtc.vhost.srs.com { } } +############################################################################################# +# RTSP server sections +############################################################################################# +rtsp_server { + # Whether enable RTSP server. + # Overwrite by env SRS_RTSP_SERVER_ENABLED + # default: off + enabled on; + # The listen port for RTSP server. + # Overwrite by env SRS_RTSP_SERVER_LISTEN + # default: 554 + listen 8554; +} + +vhost rtsp.vhost.srs.com { + rtsp { + # Whether enable RTSP server. + # Overwrite by env SRS_VHOST_RTSP_ENABLED for all vhosts. + # default: off + enabled on; + # Whether transmux RTMP to RTSP. + # Overwrite by env SRS_VHOST_RTSP_RTMP_TO_RTSP for all vhosts. + # default: on + rtmp_to_rtsp on; + } +} + ############################################################################################# # Stream converter sections ############################################################################################# diff --git a/trunk/conf/regression-test-for-clion.conf b/trunk/conf/regression-test-for-clion.conf index 953fe5f83..dbd870f9c 100644 --- a/trunk/conf/regression-test-for-clion.conf +++ b/trunk/conf/regression-test-for-clion.conf @@ -37,6 +37,10 @@ rtc_server { listen 8000; candidate $CANDIDATE; } +rtsp_server { + enabled on; + listen 8554; +} vhost __defaultVhost__ { rtc { @@ -45,6 +49,10 @@ vhost __defaultVhost__ { keep_bframe off; rtc_to_rtmp on; } + rtsp { + enabled on; + rtmp_to_rtsp on; + } play { atc on; } diff --git a/trunk/conf/regression-test.conf b/trunk/conf/regression-test.conf index 87dac9255..61434b4ec 100644 --- a/trunk/conf/regression-test.conf +++ b/trunk/conf/regression-test.conf @@ -33,6 +33,10 @@ http_api { stats { network 0; } +rtsp_server { + enabled on; + listen 8554; +} rtc_server { enabled on; listen 8000; @@ -46,6 +50,10 @@ vhost __defaultVhost__ { keep_bframe off; rtc_to_rtmp on; } + rtsp { + enabled on; + rtmp_to_rtsp on; + } play { atc on; } diff --git a/trunk/conf/rtsp.conf b/trunk/conf/rtsp.conf new file mode 100644 index 000000000..d73fe0351 --- /dev/null +++ b/trunk/conf/rtsp.conf @@ -0,0 +1,52 @@ + +listen 1935; +max_connections 1000; +daemon off; +srs_log_tank console; + +http_server { + enabled on; + listen 8080; + dir ./objs/nginx/html; +} + +http_api { + enabled on; + listen 1985; +} +stats { + network 0; +} + +rtsp_server { + enabled on; + listen 8554; +} + +rtc_server { + enabled on; + listen 8000; # UDP port + # @see https://ossrs.io/lts/en-us/docs/v7/doc/webrtc#config-candidate + candidate $CANDIDATE; +} + +vhost __defaultVhost__ { + rtsp { + enabled on; + rtmp_to_rtsp on; + } + rtc { + enabled on; + # @see https://ossrs.io/lts/en-us/docs/v7/doc/webrtc#rtmp-to-rtc + rtmp_to_rtc on; + # @see https://ossrs.io/lts/en-us/docs/v7/doc/webrtc#rtc-to-rtmp + rtc_to_rtmp on; + # for rtsp + keep_bframe on; + } + http_remux { + enabled on; + mount [vhost]/[app]/[stream].flv; + } +} + diff --git a/trunk/configure b/trunk/configure index 7e59afbcd..cbe49c8f3 100755 --- a/trunk/configure +++ b/trunk/configure @@ -302,7 +302,10 @@ if [[ $SRS_SRT == YES ]]; then ModuleLibIncs+=(${LibSRTRoot}) fi if [[ $SRS_RTC == YES ]]; then - MODULE_FILES+=("srs_protocol_rtc_stun") + MODULE_FILES+=("srs_protocol_rtc_stun" "srs_protocol_rtp") +fi +if [[ $SRS_RTSP == YES ]]; then + MODULE_FILES+=("srs_protocol_rtsp_stack") fi PROTOCOL_INCS="src/protocol"; MODULE_DIR=${PROTOCOL_INCS} . $SRS_WORKDIR/auto/modules.sh PROTOCOL_OBJS="${MODULE_OBJS[@]}" @@ -339,6 +342,9 @@ if [[ $SRS_RTC == YES ]]; then MODULE_FILES+=("srs_app_rtc_conn" "srs_app_rtc_dtls" "srs_app_rtc_sdp" "srs_app_rtc_network" "srs_app_rtc_queue" "srs_app_rtc_server" "srs_app_rtc_source" "srs_app_rtc_api") fi +if [[ $SRS_RTSP == YES ]]; then + MODULE_FILES+=("srs_app_rtsp_source" "srs_app_rtsp_conn") +fi if [[ $SRS_APM == YES ]]; then MODULE_FILES+=("srs_app_tencentcloud") fi @@ -747,6 +753,11 @@ if [[ $SRS_RTC == YES ]]; then else echo -e "${GREEN}Warning: RTC is disabled.${BLACK}" fi +if [[ $SRS_RTSP == YES ]]; then + echo -e "${YELLOW}Experiment: RTSP is enabled (requires RTC).${BLACK}" +else + echo -e "${GREEN}Warning: RTSP is disabled.${BLACK}" +fi if [[ $SRS_HTTPS == YES ]]; then echo -e "${YELLOW}Experiment: HTTPS is enabled. https://github.com/ossrs/srs/issues/1657${BLACK}" else diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index 58c11e4f4..2a2ba9626 100644 --- a/trunk/doc/CHANGELOG.md +++ b/trunk/doc/CHANGELOG.md @@ -7,6 +7,7 @@ The changelog for SRS. ## SRS 7.0 Changelog +* v7.0, 2025-07-11, Merge [#4333](https://github.com/ossrs/srs/pull/4333): NEW PROTOCOL: Support viewing stream over RTSP. v7.0.47 (#4333) * v7.0, 2025-07-10, Merge [#4414](https://github.com/ossrs/srs/pull/4414): Fix H.264 B-frame detection logic to comply with specification. v7.0.46 (#4414) * v7.0, 2025-07-04, Merge [#4412](https://github.com/ossrs/srs/pull/4412): Refine code and add tests for #4289. v7.0.45 (#4412) * v7.0, 2025-07-04, Merge [#4413](https://github.com/ossrs/srs/pull/4413): RTMP2RTC: Support dual video track for bridge. v7.0.44 (#4413) diff --git a/trunk/ide/srs_clion/CMakeLists.txt b/trunk/ide/srs_clion/CMakeLists.txt index 6b55c6652..f5bf2ba70 100755 --- a/trunk/ide/srs_clion/CMakeLists.txt +++ b/trunk/ide/srs_clion/CMakeLists.txt @@ -27,11 +27,11 @@ ProcessorCount(JOBS) # We should always configure SRS for switching between branches. IF (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") EXECUTE_PROCESS( - COMMAND ./configure --osx --srt=on --gb28181=on --apm=on --h265=on --hds=on --utest=on --ffmpeg-opus=off --jobs=${JOBS} + COMMAND ./configure --osx --srt=on --gb28181=on --rtsp=on --apm=on --h265=on --hds=on --utest=on --ffmpeg-opus=off --jobs=${JOBS} WORKING_DIRECTORY ${SRS_DIR} RESULT_VARIABLE ret) ELSE () EXECUTE_PROCESS( - COMMAND ./configure --srt=on --gb28181=on --apm=on --h265=on --hds=on --utest=on --ffmpeg-opus=off --jobs=${JOBS} + COMMAND ./configure --srt=on --gb28181=on --rtsp=on --apm=on --h265=on --hds=on --utest=on --ffmpeg-opus=off --jobs=${JOBS} WORKING_DIRECTORY ${SRS_DIR} RESULT_VARIABLE ret) ENDIF () if(NOT ret EQUAL 0) diff --git a/trunk/src/app/srs_app_config.cpp b/trunk/src/app/srs_app_config.cpp index d74f4b410..3f469876d 100644 --- a/trunk/src/app/srs_app_config.cpp +++ b/trunk/src/app/srs_app_config.cpp @@ -2348,7 +2348,7 @@ srs_error_t SrsConfig::check_normal_config() && n != "inotify_auto_reload" && n != "auto_reload_for_docker" && n != "tcmalloc_release_rate" && n != "query_latest_version" && n != "first_wait_for_qlv" && n != "threads" && n != "circuit_breaker" && n != "is_full" && n != "in_docker" && n != "tencentcloud_cls" - && n != "exporter" + && n != "exporter" && n != "rtsp_server" ) { return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "illegal directive %s", n.c_str()); } @@ -2435,6 +2435,15 @@ srs_error_t SrsConfig::check_normal_config() } } } + if (true) { + SrsConfDirective* conf = root->get("rtsp_server"); + for (int i = 0; conf && i < (int)conf->directives.size(); i++) { + string n = conf->at(i)->name; + if (n != "enabled" && n != "listen") { + return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "illegal rtsp_server.%s", n.c_str()); + } + } + } if (true) { SrsConfDirective* conf = root->get("exporter"); for (int i = 0; conf && i < (int)conf->directives.size(); i++) { @@ -2595,7 +2604,8 @@ srs_error_t SrsConfig::check_normal_config() && n != "play" && n != "publish" && n != "cluster" && n != "security" && n != "http_remux" && n != "dash" && n != "http_static" && n != "hds" && n != "exec" - && n != "in_ack_size" && n != "out_ack_size" && n != "rtc" && n != "srt") { + && n != "in_ack_size" && n != "out_ack_size" && n != "rtc" && n != "srt" + && n != "rtsp") { return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "illegal vhost.%s", n.c_str()); } // for each sub directives of vhost. @@ -2755,6 +2765,13 @@ srs_error_t SrsConfig::check_normal_config() return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "illegal vhost.srt.%s of %s", m.c_str(), vhost->arg0().c_str()); } } + } else if (n == "rtsp") { + for (int j = 0; j < (int)conf->directives.size(); j++) { + string m = conf->at(j)->name; + if (m != "enabled" && m != "rtmp_to_rtsp") { + return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "illegal vhost.srt.%s of %s", m.c_str(), vhost->arg0().c_str()); + } + } } } } @@ -4069,6 +4086,96 @@ std::string SrsConfig::get_stream_caster_sip_candidate(SrsConfDirective* conf) return conf->arg0(); } +bool SrsConfig::get_rtsp_server_enabled() +{ + SrsConfDirective* conf = root->get("rtsp_server"); + return get_rtsp_server_enabled(conf); +} + +bool SrsConfig::get_rtsp_server_enabled(SrsConfDirective* conf) +{ + SRS_OVERWRITE_BY_ENV_BOOL("srs.rtsp_server.enabled"); // SRS_RTSP_SERVER_ENABLED + + static bool DEFAULT = false; + + if (!conf) { + return DEFAULT; + } + + conf = conf->get("enabled"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return SRS_CONF_PREFER_FALSE(conf->arg0()); +} + +int SrsConfig::get_rtsp_server_listen() +{ + SRS_OVERWRITE_BY_ENV_INT("srs.rtsp_server.listen"); // SRS_RTSP_SERVER_LISTEN + + SrsConfDirective* conf = root->get("rtsp_server"); + + static int DEFAULT = 554; + + if (!conf) { + return DEFAULT; + } + + conf = conf->get("listen"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return ::atoi(conf->arg0().c_str()); +} + +SrsConfDirective* SrsConfig::get_rtsp(string vhost) +{ + SrsConfDirective* conf = get_vhost(vhost); + return conf? conf->get("rtsp") : NULL; +} + +bool SrsConfig::get_rtsp_enabled(string vhost) +{ + SRS_OVERWRITE_BY_ENV_BOOL("srs.vhost.rtsp.enabled"); // SRS_VHOST_RTSP_ENABLED + + static bool DEFAULT = false; + + SrsConfDirective* conf = get_rtsp(vhost); + + if (!conf) { + return DEFAULT; + } + + conf = conf->get("enabled"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return SRS_CONF_PREFER_FALSE(conf->arg0()); +} + +bool SrsConfig::get_rtsp_from_rtmp(string vhost) +{ + SRS_OVERWRITE_BY_ENV_BOOL("srs.vhost.rtsp.rtmp_to_rtsp"); // SRS_VHOST_RTSP_RTMP_TO_RTSP + + static bool DEFAULT = true; + + SrsConfDirective* conf = get_rtsp(vhost); + + if (!conf) { + return DEFAULT; + } + + conf = conf->get("rtmp_to_rtsp"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return SRS_CONF_PREFER_TRUE(conf->arg0()); +} + bool SrsConfig::get_rtc_server_enabled() { SrsConfDirective* conf = root->get("rtc_server"); diff --git a/trunk/src/app/srs_app_config.hpp b/trunk/src/app/srs_app_config.hpp index 9bbe52a1e..74e7617a0 100644 --- a/trunk/src/app/srs_app_config.hpp +++ b/trunk/src/app/srs_app_config.hpp @@ -508,6 +508,16 @@ public: virtual srs_utime_t get_stream_caster_sip_reinvite(SrsConfDirective* conf); // Get the candidate for SDP. virtual std::string get_stream_caster_sip_candidate(SrsConfDirective* conf); +// rtsp section +public: + virtual bool get_rtsp_server_enabled(); + virtual bool get_rtsp_server_enabled(SrsConfDirective* conf); + virtual int get_rtsp_server_listen(); +public: + SrsConfDirective* get_rtsp(std::string vhost); + bool get_rtsp_enabled(std::string vhost); + bool get_rtsp_from_rtmp(std::string vhost); + // rtc section public: virtual bool get_rtc_server_enabled(); diff --git a/trunk/src/app/srs_app_heartbeat.cpp b/trunk/src/app/srs_app_heartbeat.cpp index f7548573d..73428244d 100644 --- a/trunk/src/app/srs_app_heartbeat.cpp +++ b/trunk/src/app/srs_app_heartbeat.cpp @@ -119,6 +119,15 @@ srs_error_t SrsHttpHeartbeat::do_heartbeat() o->append(SrsJsonAny::str(srs_fmt("udp://0.0.0.0:%d", endpoint).c_str())); } + // For RTSP listen endpoints. + if (_srs_config->get_rtsp_server_enabled()) { + SrsJsonArray* o = SrsJsonAny::array(); + obj->set("rtsp", o); + + int endpoint = _srs_config->get_rtsp_server_listen(); + o->append(SrsJsonAny::str(srs_fmt("rtsp://0.0.0.0:%d", endpoint).c_str())); + } + // For WebRTC listen endpoints. if (_srs_config->get_rtc_server_enabled()) { SrsJsonArray* o = SrsJsonAny::array(); diff --git a/trunk/src/app/srs_app_latest_version.cpp b/trunk/src/app/srs_app_latest_version.cpp index 20dc4adec..f06534df6 100644 --- a/trunk/src/app/srs_app_latest_version.cpp +++ b/trunk/src/app/srs_app_latest_version.cpp @@ -63,6 +63,7 @@ void srs_build_features(stringstream& ss) SRS_CHECK_FEATURE3(!string(SRS_PACKAGER).empty(), "packager", SRS_PACKAGER, ss); SRS_CHECK_FEATURE2(SRS_CROSSBUILD_BOOL, "cross", ss); + SRS_CHECK_FEATURE2(SRS_RTC_BOOL && _srs_config->get_rtsp_server_enabled(), "rtsp", ss); SRS_CHECK_FEATURE2(SRS_RTC_BOOL && _srs_config->get_rtc_server_enabled(), "rtc", ss); SRS_CHECK_FEATURE2(SRS_SRT_BOOL && _srs_config->get_srt_enabled(), "srt", ss); SRS_CHECK_FEATURE2(_srs_config->get_http_api_enabled(), "api", ss); diff --git a/trunk/src/app/srs_app_rtc_sdp.cpp b/trunk/src/app/srs_app_rtc_sdp.cpp index 4d8028a81..256deef76 100644 --- a/trunk/src/app/srs_app_rtc_sdp.cpp +++ b/trunk/src/app/srs_app_rtc_sdp.cpp @@ -470,6 +470,10 @@ srs_error_t SrsMediaDesc::encode(std::ostringstream& os) os << "a=rtcp-rsize" << kCRLF; } + if (!control_.empty()) { + os << "a=control:" << control_ << kCRLF; + } + for (std::vector::iterator iter = payload_types_.begin(); iter != payload_types_.end(); ++iter) { if ((err = iter->encode(os)) != srs_success) { return srs_error_wrap(err, "encode media payload failed"); @@ -896,6 +900,10 @@ srs_error_t SrsSdp::encode(std::ostringstream& os) return srs_error_wrap(err, "encode session info failed"); } + if (!control_.empty()) { + os << "a=control:" << control_ << kCRLF; + } + for (std::vector::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) { if ((err = (*iter).encode(os)) != srs_success) { return srs_error_wrap(err, "encode media description failed"); diff --git a/trunk/src/app/srs_app_rtc_sdp.hpp b/trunk/src/app/srs_app_rtc_sdp.hpp index 261555124..994a4237a 100644 --- a/trunk/src/app/srs_app_rtc_sdp.hpp +++ b/trunk/src/app/srs_app_rtc_sdp.hpp @@ -175,6 +175,10 @@ public: bool sendrecv_; bool inactive_; + // Control URL, ONLY for RTSP, media control. + // @see rfc2326-1998-rtsp.pdf, page 159 + std::string control_; + std::string mid_; std::string msid_; std::string msid_tracker_; @@ -255,6 +259,10 @@ public: std::string msid_semantic_; std::vector msids_; + // Control URL, ONLY for RTSP. + // @see rfc2326-1998-rtsp.pdf, page 159 + std::string control_; + // m-line, media sessions std::vector media_descs_; diff --git a/trunk/src/app/srs_app_rtc_source.cpp b/trunk/src/app/srs_app_rtc_source.cpp index 82cc40fa8..99ba4c512 100644 --- a/trunk/src/app/srs_app_rtc_source.cpp +++ b/trunk/src/app/srs_app_rtc_source.cpp @@ -37,6 +37,7 @@ #include #include +#include // The NACK sent by us(SFU). SrsPps* _srs_pps_snack = NULL; @@ -53,25 +54,13 @@ SrsPps* _srs_pps_rmnack = NULL; extern SrsPps* _srs_pps_aloss2; -const int kAudioChannel = 2; -const int kAudioSamplerate = 48000; +static const int kAudioChannel = 2; +static const int kAudioSamplerate = 48000; -const int kVideoSamplerate = 90000; +static const int kVideoSamplerate = 90000; using namespace std; -#ifdef SRS_FFMPEG_FIT -// The RTP payload max size, reserved some paddings for SRTP as such: -// kRtpPacketSize = kRtpMaxPayloadSize + paddings -// For example, if kRtpPacketSize is 1500, recommend to set kRtpMaxPayloadSize to 1400, -// which reserves 100 bytes for SRTP or paddings. -// otherwise, the kRtpPacketSize must less than MTU, in webrtc source code, -// the rtp max size is assigned by kVideoMtu = 1200. -// so we set kRtpMaxPayloadSize = 1200. -// see @doc https://groups.google.com/g/discuss-webrtc/c/gH5ysR3SoZI -const int kRtpMaxPayloadSize = kRtpPacketSize - 300; -#endif - // the time to cleanup source. #define SRS_RTC_SOURCE_CLEANUP (3 * SRS_UTIME_SECONDS) @@ -873,13 +862,11 @@ SrsRtcRtpBuilder::SrsRtcRtpBuilder(SrsFrameToRtcBridge* bridge, SrsSharedPtr descs = source_->get_track_desc("video", codec_name); + uint32_t video_ssrc = 0; + uint8_t video_payload_type = 0; if (!descs.empty()) { // Note we must use the PT of source, see https://github.com/ossrs/srs/pull/3079 SrsRtcTrackDescription* track = descs.at(0); - video_ssrc_ = track->ssrc_; - video_payload_type_ = track->media_->pt_; + video_ssrc = track->ssrc_; + video_payload_type = track->media_->pt_; } else { - video_payload_type_ = kVideoPayloadType; + video_payload_type = kVideoPayloadType; + } + + SrsFormat* format = meta->vsh_format(); + if ((err = video_builder_->initialize(format, video_ssrc, video_payload_type)) != srs_success) { + return srs_error_wrap(err, "initialize video builder"); } srs_trace("RTMP2RTC: Initialize video track with codec=%s, ssrc=%u, pt=%d", - codec_name.c_str(), video_ssrc_, video_payload_type_); + codec_name.c_str(), video_ssrc, video_payload_type); return err; } @@ -1299,63 +1294,7 @@ srs_error_t SrsRtcRtpBuilder::package_stap_a(SrsSharedPtrMessage* msg, SrsRtpPac return err; } - pkt->header.set_payload_type(video_payload_type_); - pkt->header.set_ssrc(video_ssrc_); - pkt->frame_type = SrsFrameTypeVideo; - pkt->header.set_marker(false); - pkt->header.set_sequence(video_sequence++); - pkt->header.set_timestamp(msg->timestamp * 90); - - ISrsRtpPayloader* stap = NULL; - vector*> params; - int size = 0; - - if (format->vcodec->id == SrsVideoCodecIdHEVC) { - for (size_t i = 0; i < format->vcodec->hevc_dec_conf_record_.nalu_vec.size(); i++) { - const SrsHevcHvccNalu& nalu = format->vcodec->hevc_dec_conf_record_.nalu_vec[i]; - if (nalu.nal_unit_type == SrsHevcNaluType_VPS - || nalu.nal_unit_type == SrsHevcNaluType_SPS - || nalu.nal_unit_type == SrsHevcNaluType_PPS) { - const SrsHevcNalData& nal_data = nalu.nal_data_vec[0]; - params.push_back(&(vector&)nal_data.nal_unit_data); - size += nal_data.nal_unit_length; - } - } - - stap = new SrsRtpSTAPPayloadHevc(); - pkt->set_payload(stap, SrsRtpPacketPayloadTypeSTAPHevc); - pkt->nalu_type = kStapHevc; - } else if (format->vcodec->id == SrsVideoCodecIdAVC) { - params.push_back(&format->vcodec->sequenceParameterSetNALUnit); - params.push_back(&format->vcodec->pictureParameterSetNALUnit); - size = format->vcodec->sequenceParameterSetNALUnit.size() + format->vcodec->pictureParameterSetNALUnit.size(); - - stap = new SrsRtpSTAPPayload(); - pkt->set_payload(stap, SrsRtpPacketPayloadTypeSTAP); - pkt->nalu_type = kStapA; - } - - if (size == 0) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "vps/sps/pps empty"); - } - char* payload = pkt->wrap(size); - - for (vector*>::iterator it = params.begin(); it != params.end(); ++it) { - vector* param = *it; - SrsSample* sample = new SrsSample(); - sample->bytes = payload; - sample->size = param->size(); - if (format->vcodec->id == SrsVideoCodecIdHEVC) { - static_cast(stap)->nalus.push_back(sample); - } else { - static_cast(stap)->nalus.push_back(sample); - } - - memcpy(payload, (char*)param->data(), param->size()); - payload += (int)param->size(); - } - - return err; + return video_builder_->package_stap_a(msg, pkt); } srs_error_t SrsRtcRtpBuilder::package_nalus(SrsSharedPtrMessage* msg, const vector& samples, vector& pkts) @@ -1366,130 +1305,14 @@ srs_error_t SrsRtcRtpBuilder::package_nalus(SrsSharedPtrMessage* msg, const vect if (!format || !format->vcodec) { return err; } - bool is_hevc = format->vcodec->id == SrsVideoCodecIdHEVC; - - SrsRtpRawNALUs* raw_raw = new SrsRtpRawNALUs(); - uint8_t first_nalu_type = 0; - - for (int i = 0; i < (int)samples.size(); i++) { - SrsSample* sample = samples[i]; - - if (!sample->size) { - continue; - } - - if (first_nalu_type == 0) { - first_nalu_type = is_hevc ? uint8_t(SrsHevcNaluTypeParse(sample->bytes[0])) : uint8_t(SrsAvcNaluTypeParse(sample->bytes[0])); - } - - raw_raw->push_back(sample->copy()); - } - - // Ignore empty. - int nn_bytes = raw_raw->nb_bytes(); - if (nn_bytes <= 0) { - srs_freep(raw_raw); - return err; - } - - if (nn_bytes < kRtpMaxPayloadSize) { - // Package NALUs in a single RTP packet. - SrsRtpPacket* pkt = new SrsRtpPacket(); - pkts.push_back(pkt); - - pkt->header.set_payload_type(video_payload_type_); - pkt->header.set_ssrc(video_ssrc_); - pkt->frame_type = SrsFrameTypeVideo; - pkt->nalu_type = first_nalu_type; - pkt->header.set_sequence(video_sequence++); - pkt->header.set_timestamp(msg->timestamp * 90); - pkt->set_payload(raw_raw, SrsRtpPacketPayloadTypeNALU); - pkt->wrap(msg); - } else { - // We must free it, should never use RTP packets to free it, - // because more than one RTP packet will refer to it. - SrsUniquePtr raw(raw_raw); - - int header_size = is_hevc ? SrsHevcNaluHeaderSize : SrsAvcNaluHeaderSize; - - // Package NALUs in FU-A RTP packets. - int fu_payload_size = kRtpMaxPayloadSize; - - // The first byte is store in FU-A header. - uint8_t header = raw->skip_bytes(header_size); - - int nb_left = nn_bytes - header_size; - - int num_of_packet = 1 + (nn_bytes - 1) / fu_payload_size; - for (int i = 0; i < num_of_packet; ++i) { - int packet_size = srs_min(nb_left, fu_payload_size); - - SrsRtpPacket* pkt = new SrsRtpPacket(); - pkts.push_back(pkt); - - pkt->header.set_payload_type(video_payload_type_); - pkt->header.set_ssrc(video_ssrc_); - pkt->frame_type = SrsFrameTypeVideo; - pkt->nalu_type = kFuA; - pkt->header.set_sequence(video_sequence++); - pkt->header.set_timestamp(msg->timestamp * 90); - - if (is_hevc) { - SrsRtpFUAPayloadHevc* fua = new SrsRtpFUAPayloadHevc(); - if ((err = raw->read_samples(fua->nalus, packet_size)) != srs_success) { - srs_freep(fua); - return srs_error_wrap(err, "read hevc samples %d bytes, left %d, total %d", packet_size, nb_left, nn_bytes); - } - fua->nalu_type = SrsHevcNaluTypeParse(header); - fua->start = bool(i == 0); - fua->end = bool(i == num_of_packet - 1); - - pkt->set_payload(fua, SrsRtpPacketPayloadTypeFUAHevc); - } else { - SrsRtpFUAPayload* fua = new SrsRtpFUAPayload(); - if ((err = raw->read_samples(fua->nalus, packet_size)) != srs_success) { - srs_freep(fua); - return srs_error_wrap(err, "read samples %d bytes, left %d, total %d", packet_size, nb_left, nn_bytes); - } - fua->nalu_type = SrsAvcNaluTypeParse(header); - fua->start = bool(i == 0); - fua->end = bool(i == num_of_packet - 1); - - pkt->set_payload(fua, SrsRtpPacketPayloadTypeFUA); - } - - pkt->wrap(msg); - - nb_left -= packet_size; - } - } - - return err; + + return video_builder_->package_nalus(msg, samples, pkts); } // Single NAL Unit Packet @see https://tools.ietf.org/html/rfc6184#section-5.6 srs_error_t SrsRtcRtpBuilder::package_single_nalu(SrsSharedPtrMessage* msg, SrsSample* sample, vector& pkts) { - srs_error_t err = srs_success; - - SrsRtpPacket* pkt = new SrsRtpPacket(); - pkts.push_back(pkt); - - pkt->header.set_payload_type(video_payload_type_); - pkt->header.set_ssrc(video_ssrc_); - pkt->frame_type = SrsFrameTypeVideo; - pkt->header.set_sequence(video_sequence++); - pkt->header.set_timestamp(msg->timestamp * 90); - - SrsRtpRawPayload* raw = new SrsRtpRawPayload(); - pkt->set_payload(raw, SrsRtpPacketPayloadTypeRaw); - - raw->payload = sample->bytes; - raw->nn_payload = sample->size; - - pkt->wrap(msg); - - return err; + return video_builder_->package_single_nalu(msg, sample, pkts); } srs_error_t SrsRtcRtpBuilder::package_fu_a(SrsSharedPtrMessage* msg, SrsSample* sample, int fu_payload_size, vector& pkts) @@ -1501,60 +1324,7 @@ srs_error_t SrsRtcRtpBuilder::package_fu_a(SrsSharedPtrMessage* msg, SrsSample* return err; } - bool is_hevc = format->vcodec->id == SrsVideoCodecIdHEVC; - int header_size = is_hevc ? SrsHevcNaluHeaderSize : SrsAvcNaluHeaderSize; - srs_assert(sample->size >= header_size); - - char* p = sample->bytes + header_size; - int nb_left = sample->size - header_size; - uint8_t header = sample->bytes[0]; - - int num_of_packet = 1 + (nb_left - 1) / fu_payload_size; - for (int i = 0; i < num_of_packet; ++i) { - int packet_size = srs_min(nb_left, fu_payload_size); - - SrsRtpPacket* pkt = new SrsRtpPacket(); - pkts.push_back(pkt); - - pkt->header.set_payload_type(video_payload_type_); - pkt->header.set_ssrc(video_ssrc_); - pkt->frame_type = SrsFrameTypeVideo; - pkt->header.set_sequence(video_sequence++); - pkt->header.set_timestamp(msg->timestamp * 90); - pkt->nalu_type = is_hevc ? kFuHevc : kFuA; - - if (is_hevc) { - // H265 FU-A header - SrsRtpFUAPayloadHevc2* fua = new SrsRtpFUAPayloadHevc2(); - pkt->set_payload(fua, SrsRtpPacketPayloadTypeFUAHevc2); - - fua->nalu_type = SrsHevcNaluTypeParse(header); - fua->start = bool(i == 0); - fua->end = bool(i == num_of_packet - 1); - - fua->payload = p; - fua->size = packet_size; - } else { - // H264 FU-A header - SrsRtpFUAPayload2* fua = new SrsRtpFUAPayload2(); - pkt->set_payload(fua, SrsRtpPacketPayloadTypeFUA2); - - fua->nri = (SrsAvcNaluType)header; - fua->nalu_type = SrsAvcNaluTypeParse(header); - fua->start = bool(i == 0); - fua->end = bool(i == num_of_packet - 1); - - fua->payload = p; - fua->size = packet_size; - } - - pkt->wrap(msg); - - p += packet_size; - nb_left -= packet_size; - } - - return err; + return video_builder_->package_fu_a(msg, sample, fu_payload_size, pkts); } srs_error_t SrsRtcRtpBuilder::consume_packets(vector& pkts) @@ -2668,11 +2438,11 @@ SrsMediaPayloadType SrsVideoPayload::generate_media_payload_type_h265() return media_payload_type; } +// level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f srs_error_t SrsVideoPayload::set_h264_param_desc(std::string fmtp) { srs_error_t err = srs_success; - // For example: level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f std::vector attributes = split_str(fmtp, ";"); for (size_t i = 0; i < attributes.size(); ++i) { @@ -2762,6 +2532,7 @@ SrsAudioPayload* SrsAudioPayload::copy() cp->rtcp_fbs_ = rtcp_fbs_; cp->channel_ = channel_; cp->opus_param_ = opus_param_; + cp->aac_config_hex_ = aac_config_hex_; return cp; } diff --git a/trunk/src/app/srs_app_rtc_source.hpp b/trunk/src/app/srs_app_rtc_source.hpp index 535d59ef9..32056c452 100644 --- a/trunk/src/app/srs_app_rtc_source.hpp +++ b/trunk/src/app/srs_app_rtc_source.hpp @@ -43,6 +43,7 @@ class SrsJsonObject; class SrsErrorPithyPrint; class SrsRtcFrameBuilder; class SrsLiveSource; +class SrsRtpVideoBuilder; // Firefox defaults as 109, Chrome is 111. const int kAudioPayloadType = 111; @@ -276,6 +277,8 @@ private: SrsRtmpFormat* format; // The metadata cache. SrsMetaCache* meta; + // The video builder, convert frame to RTP packets. + SrsRtpVideoBuilder* video_builder_; private: SrsAudioCodecId latest_codec_; SrsAudioTranscoder* codec_; @@ -283,12 +286,9 @@ private: bool keep_avc_nalu_sei; bool merge_nalus; uint16_t audio_sequence; - uint16_t video_sequence; private: uint32_t audio_ssrc_; - uint32_t video_ssrc_; uint8_t audio_payload_type_; - uint8_t video_payload_type_; private: SrsSharedPtr source_; // Lazy initialization flags @@ -498,6 +498,8 @@ class SrsAudioPayload : public SrsCodecPayload public: int channel_; SrsOpusParameter opus_param_; + // AAC configuration hex string for SDP fmtp line + std::string aac_config_hex_; public: SrsAudioPayload(); SrsAudioPayload(uint8_t pt, std::string encode_name, int sample, int channel); diff --git a/trunk/src/app/srs_app_rtmp_conn.cpp b/trunk/src/app/srs_app_rtmp_conn.cpp index b49b3fc6a..434427c81 100644 --- a/trunk/src/app/srs_app_rtmp_conn.cpp +++ b/trunk/src/app/srs_app_rtmp_conn.cpp @@ -40,6 +40,9 @@ using namespace std; #include #include #include +#ifdef SRS_RTSP +#include +#endif // the timeout in srs_utime_t to wait encoder to republish // if timeout, close the connection. @@ -1110,21 +1113,43 @@ srs_error_t SrsRtmpConn::acquire_publish(SrsSharedPtr source) } #endif - // Bridge to RTC streaming. -#if defined(SRS_RTC) && defined(SRS_FFMPEG_FIT) - if (rtc.get() && _srs_config->get_rtc_from_rtmp(req->vhost)) { - SrsCompositeBridge* bridge = new SrsCompositeBridge(); - bridge->append(new SrsFrameToRtcBridge(rtc)); - - if ((err = bridge->initialize(req)) != srs_success) { - srs_freep(bridge); - return srs_error_wrap(err, "bridge init"); +#ifdef SRS_RTSP + // RTSP only support viewer, so we don't need to check it. + SrsSharedPtr rtsp; + bool rtsp_server_enabled = _srs_config->get_rtsp_server_enabled(); + bool rtsp_enabled = _srs_config->get_rtsp_enabled(req->vhost); + if (rtsp_server_enabled && rtsp_enabled && !info->edge) { + if ((err = _srs_rtsp_sources->fetch_or_create(req, rtsp)) != srs_success) { + return srs_error_wrap(err, "create source"); } - - source->set_bridge(bridge); } #endif + // Bridge to RTC streaming. + // TODO: FIXME: Need to convert RTMP to SRT. + SrsCompositeBridge* bridge = new SrsCompositeBridge(); + +#if defined(SRS_RTC) && defined(SRS_FFMPEG_FIT) + if (rtc.get() && _srs_config->get_rtc_from_rtmp(req->vhost)) { + bridge->append(new SrsFrameToRtcBridge(rtc)); + } +#endif + +#ifdef SRS_RTSP + if (rtsp.get() && _srs_config->get_rtsp_from_rtmp(req->vhost)) { + bridge->append(new SrsFrameToRtspBridge(rtsp)); + } +#endif + + if (bridge->empty()) { + srs_freep(bridge); + } else if ((err = bridge->initialize(req)) != srs_success) { + srs_freep(bridge); + return srs_error_wrap(err, "bridge init"); + } + + source->set_bridge(bridge); + // Start publisher now. if (info->edge) { err = source->on_edge_start_publish(); diff --git a/trunk/src/app/srs_app_rtsp_conn.cpp b/trunk/src/app/srs_app_rtsp_conn.cpp new file mode 100644 index 000000000..393e95976 --- /dev/null +++ b/trunk/src/app/srs_app_rtsp_conn.cpp @@ -0,0 +1,934 @@ +// +// Copyright (c) 2013-2025 The SRS Authors +// +// SPDX-License-Identifier: MIT +// + +#include + +using namespace std; + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern SrsPps* _srs_pps_snack; +extern SrsPps* _srs_pps_snack2; +extern SrsPps* _srs_pps_snack3; +extern SrsPps* _srs_pps_snack4; + +extern SrsPps* _srs_pps_rnack; +extern SrsPps* _srs_pps_rnack2; + +extern SrsPps* _srs_pps_pub; +extern SrsPps* _srs_pps_conn; + +SrsRtspPlayStream::SrsRtspPlayStream(SrsRtspConnection* s, const SrsContextId& cid) : source_(new SrsRtspSource()) +{ + cid_ = cid; + trd_ = NULL; + + req_ = NULL; + + is_started = false; + session_ = s; + + cache_ssrc0_ = cache_ssrc1_ = cache_ssrc2_ = 0; + cache_track0_ = cache_track1_ = cache_track2_ = NULL; +} + +SrsRtspPlayStream::~SrsRtspPlayStream() +{ + srs_freep(trd_); + srs_freep(req_); + + if (true) { + std::map::iterator it; + for (it = audio_tracks_.begin(); it != audio_tracks_.end(); ++it) { + srs_freep(it->second); + } + } + + if (true) { + std::map::iterator it; + for (it = video_tracks_.begin(); it != video_tracks_.end(); ++it) { + srs_freep(it->second); + } + } + + // update the statistic when client coveried. + SrsStatistic* stat = SrsStatistic::instance(); + // TODO: FIXME: Should finger out the err. + stat->on_disconnect(cid_.c_str(), srs_success); +} + +srs_error_t SrsRtspPlayStream::initialize(SrsRequest* req, std::map sub_relations) +{ + srs_error_t err = srs_success; + + req_ = req->copy(); + + // We must do stat the client before hooks, because hooks depends on it. + SrsStatistic* stat = SrsStatistic::instance(); + if ((err = stat->on_client(cid_.c_str(), req_, session_, SrsRtcConnPlay)) != srs_success) { + return srs_error_wrap(err, "RTSP: stat client"); + } + + if ((err = _srs_rtsp_sources->fetch_or_create(req_, source_)) != srs_success) { + return srs_error_wrap(err, "RTSP: fetch source failed"); + } + + for (map::iterator it = sub_relations.begin(); it != sub_relations.end(); ++it) { + uint32_t ssrc = it->first; + SrsRtcTrackDescription* desc = it->second; + + if (desc->type_ == "audio") { + SrsRtspAudioSendTrack* track = new SrsRtspAudioSendTrack(session_, desc); + audio_tracks_.insert(make_pair(ssrc, track)); + } + + if (desc->type_ == "video") { + SrsRtspVideoSendTrack* track = new SrsRtspVideoSendTrack(session_, desc); + video_tracks_.insert(make_pair(ssrc, track)); + } + } + + return err; +} + +// TODO: Remove it for RTSP? +void SrsRtspPlayStream::on_stream_change(SrsRtcSourceDescription* desc) +{ + if (!desc) return; + + // Refresh the relation for audio. + // TODO: FIXME: Match by label? + if (desc && desc->audio_track_desc_ && audio_tracks_.size() == 1) { + if (!audio_tracks_.empty()) { + uint32_t ssrc = desc->audio_track_desc_->ssrc_; + SrsRtspAudioSendTrack* track = audio_tracks_.begin()->second; + + if (track->track_desc_->media_->pt_of_publisher_ != desc->audio_track_desc_->media_->pt_) { + track->track_desc_->media_->pt_of_publisher_ = desc->audio_track_desc_->media_->pt_; + } + + if (desc->audio_track_desc_->red_ && track->track_desc_->red_ && + track->track_desc_->red_->pt_of_publisher_ != desc->audio_track_desc_->red_->pt_) { + track->track_desc_->red_->pt_of_publisher_ = desc->audio_track_desc_->red_->pt_; + } + + audio_tracks_.clear(); + audio_tracks_.insert(make_pair(ssrc, track)); + } + } + + // Refresh the relation for video. + // TODO: FIMXE: Match by label? + if (desc && desc->video_track_descs_.size() == 1) { + if (!video_tracks_.empty()) { + SrsRtcTrackDescription* vdesc = desc->video_track_descs_.at(0); + uint32_t ssrc = vdesc->ssrc_; + SrsRtspVideoSendTrack* track = video_tracks_.begin()->second; + + if (track->track_desc_->media_->pt_of_publisher_ != vdesc->media_->pt_) { + track->track_desc_->media_->pt_of_publisher_ = vdesc->media_->pt_; + } + + if (vdesc->red_ && track->track_desc_->red_ && + track->track_desc_->red_->pt_of_publisher_ != vdesc->red_->pt_) { + track->track_desc_->red_->pt_of_publisher_ = vdesc->red_->pt_; + } + + video_tracks_.clear(); + video_tracks_.insert(make_pair(ssrc, track)); + } + } +} + +const SrsContextId& SrsRtspPlayStream::context_id() +{ + return cid_; +} + +srs_error_t SrsRtspPlayStream::start() +{ + srs_error_t err = srs_success; + + // If player coroutine allocated, we think the player is started. + // To prevent play multiple times for this play stream. + // @remark Allow start multiple times, for DTLS may retransmit the final packet. + if (is_started) { + return err; + } + + srs_freep(trd_); + trd_ = new SrsFastCoroutine("rtsp_sender", this, cid_); + + if ((err = trd_->start()) != srs_success) { + return srs_error_wrap(err, "rtsp_sender"); + } + + is_started = true; + + return err; +} + +void SrsRtspPlayStream::stop() +{ + if (trd_) { + trd_->stop(); + } +} + +srs_error_t SrsRtspPlayStream::cycle() +{ + srs_error_t err = srs_success; + + SrsSharedPtr& source = source_; + srs_assert(source.get()); + + SrsRtspConsumer* consumer_raw = NULL; + if ((err = source->create_consumer(consumer_raw)) != srs_success) { + return srs_error_wrap(err, "create consumer, source=%s", req_->get_stream_url().c_str()); + } + + srs_assert(consumer_raw); + SrsUniquePtr consumer(consumer_raw); + + consumer->set_handler(this); + + // TODO: FIXME: Dumps the SPS/PPS from gop cache, without other frames. + if ((err = source->consumer_dumps(consumer.get())) != srs_success) { + return srs_error_wrap(err, "dumps consumer, url=%s", req_->get_stream_url().c_str()); + } + + // TODO: FIXME: Add cost in ms. + SrsContextId cid = source->source_id(); + srs_trace("RTSP: start play url=%s, source_id=%s/%s", req_->get_stream_url().c_str(), + cid.c_str(), source->pre_source_id().c_str()); + + SrsUniquePtr epp(new SrsErrorPithyPrint()); + + // For RTSP, donot use merged write. + int mw_msgs = 1; + while (true) { + if ((err = trd_->pull()) != srs_success) { + return srs_error_wrap(err, "RTSP sender thread"); + } + + // Wait for amount of packets. + SrsRtpPacket* pkt = NULL; + consumer->dump_packet(&pkt); + if (!pkt) { + // TODO: FIXME: We should check the quit event. + consumer->wait(mw_msgs); + continue; + } + + // Send-out the RTP packet and do cleanup + // @remark Note that the pkt might be set to NULL. + if ((err = send_packet(pkt)) != srs_success) { + uint32_t nn = 0; + if (epp->can_print(err, &nn)) { + srs_warn("play send packets=%u, nn=%u/%u, err: %s", 1, epp->nn_count, nn, srs_error_desc(err).c_str()); + } + srs_freep(err); + } + + // Free the packet. + // @remark Note that the pkt might be set to NULL. + srs_freep(pkt); + } +} + +srs_error_t SrsRtspPlayStream::send_packet(SrsRtpPacket*& pkt) +{ + srs_error_t err = srs_success; + + uint32_t ssrc = pkt->header.get_ssrc(); + + // Try to find track from cache. + SrsRtspSendTrack* track = NULL; + if (cache_ssrc0_ == ssrc) { + track = cache_track0_; + } else if (cache_ssrc1_ == ssrc) { + track = cache_track1_; + } else if (cache_ssrc2_ == ssrc) { + track = cache_track2_; + } + + // Find by original tracks and build fast cache. + if (!track) { + if (pkt->is_audio()) { + map::iterator it = audio_tracks_.find(ssrc); + if (it != audio_tracks_.end()) { + track = it->second; + } + } else { + map::iterator it = video_tracks_.find(ssrc); + if (it != video_tracks_.end()) { + track = it->second; + } + } + + if (track && !cache_ssrc2_) { + if (!cache_ssrc0_) { + cache_ssrc0_ = ssrc; + cache_track0_ = track; + } else if (!cache_ssrc1_) { + cache_ssrc1_ = ssrc; + cache_track1_ = track; + } else if (!cache_ssrc2_) { + cache_ssrc2_ = ssrc; + cache_track2_ = track; + } + } + } + + // Ignore if no track found. + if (!track) { + srs_warn("RTSP: Drop for ssrc %u not found", ssrc); + return err; + } + + // Consume packet by track. + if ((err = track->on_rtp(pkt)) != srs_success) { + return srs_error_wrap(err, "audio track, SSRC=%u, SEQ=%u", ssrc, pkt->header.get_sequence()); + } + + return err; +} + +void SrsRtspPlayStream::set_all_tracks_status(bool status) +{ + std::ostringstream merged_log; + + // set video track status + if (true) { + std::map::iterator it; + for (it = video_tracks_.begin(); it != video_tracks_.end(); ++it) { + SrsRtspVideoSendTrack* track = it->second; + + bool previous = track->set_track_status(status); + merged_log << "{track: " << track->get_track_id() << ", is_active: " << previous << "=>" << status << "},"; + } + } + + // set audio track status + if (true) { + std::map::iterator it; + for (it = audio_tracks_.begin(); it != audio_tracks_.end(); ++it) { + SrsRtspAudioSendTrack* track = it->second; + + bool previous = track->set_track_status(status); + merged_log << "{track: " << track->get_track_id() << ", is_active: " << previous << "=>" << status << "},"; + } + } + + srs_trace("RTSP: Init tracks %s ok", merged_log.str().c_str()); +} + +SrsRtspConnection::SrsRtspConnection(ISrsResourceManager* cm, ISrsProtocolReadWriter* skt, std::string cip, int port) +{ + manager_ = cm; + cid_ = _srs_context->generate_id(); + _srs_context->set_id(cid_); + + // Initialize timeout management fields from SrsRtspConnection2 + last_stun_time = 0; + session_timeout = 0; + disposing_ = false; + + request_ = new SrsRequest(); + request_->ip = cip; + ip_ = cip; + port_ = port; + rtsp_ = new SrsRtspStack(skt); + trd_ = new SrsSTCoroutine("rtsp", this, _srs_context->get_id()); + + // Initialize merged SrsRtspSession members + skt_ = skt; + source_ = NULL; + player_ = NULL; + + cache_iov_ = new iovec(); + cache_iov_->iov_base = new char[kRtpPacketSize]; + cache_iov_->iov_len = kRtpPacketSize; + cache_buffer_ = new SrsBuffer((char*)cache_iov_->iov_base, kRtpPacketSize); + + delta_ = new SrsEphemeralDelta(); + security_ = new SrsSecurity(); + + _srs_rtsp_manager->subscribe(this); +} + +SrsRtspConnection::~SrsRtspConnection() +{ + _srs_rtsp_manager->unsubscribe(this); + + srs_freep(request_); + srs_freep(rtsp_); + srs_freep(trd_); + + // Cleanup merged SrsRtspSession members + for (std::map::iterator it = tracks_.begin(); it != tracks_.end(); ++it) { + srs_freep(it->second); + } + tracks_.clear(); + + for (std::map::iterator it = networks_.begin(); it != networks_.end(); ++it) { + srs_freep(it->second); + } + networks_.clear(); + + srs_freep(delta_); + srs_freep(security_); + srs_freep(player_); + + if (true) { + char* iov_base = (char*)cache_iov_->iov_base; + srs_freepa(iov_base); + srs_freep(cache_iov_); + } + srs_freep(cache_buffer_); +} + +srs_error_t SrsRtspConnection::do_send_packet(SrsRtpPacket* pkt) +{ + srs_error_t err = srs_success; + + uint32_t ssrc = pkt->header.get_ssrc(); + ISrsStreamWriter* network = networks_[ssrc]; + if (!network) { + return srs_error_new(ERROR_RTSP_NO_TRACK, "network not found for ssrc: %u", ssrc); + } + + iovec* iov = cache_iov_; + cache_buffer_->skip(-1 * cache_buffer_->pos()); + + // Marshal packet to bytes in iovec. + if (true) { + if ((err = pkt->encode(cache_buffer_)) != srs_success) { + return srs_error_wrap(err, "encode packet"); + } + iov->iov_len = cache_buffer_->pos(); + } + + ssize_t write = 0; + if ((err = network->write(iov->iov_base, iov->iov_len, &write)) != srs_success) { + return srs_error_wrap(err, "send rtp packet"); + } + + delta_->add_delta(0, write); + + return err; +} + +ISrsKbpsDelta* SrsRtspConnection::delta() +{ + return delta_; +} + +std::string SrsRtspConnection::desc() +{ + return "Rtsp"; +} + +const SrsContextId& SrsRtspConnection::get_id() +{ + return cid_; +} + +std::string SrsRtspConnection::remote_ip() +{ + return ip_; +} + +void SrsRtspConnection::expire() +{ + trd_->interrupt(); +} + +srs_error_t SrsRtspConnection::start() +{ + srs_error_t err = srs_success; + + if ((err = trd_->start()) != srs_success) { + return srs_error_wrap(err, "coroutine"); + } + + return err; +} + +srs_error_t SrsRtspConnection::cycle() +{ + srs_error_t err = srs_success; + + // Serve the client. + err = do_cycle(); + + // Update statistic when done. + SrsStatistic* stat = SrsStatistic::instance(); + stat->kbps_add_delta(get_id().c_str(), delta()); + + do_teardown(); + + // Notify manager to remove it. + // Note that we create this object, so we use manager to remove it. + manager_->remove(this); + + // success. + if (err == srs_success) { + srs_trace("RTSP: client finished."); + return err; + } + + // It maybe success with message. + if (srs_error_code(err) == ERROR_SUCCESS) { + srs_trace("RTSP: client finished%s.", srs_error_summary(err).c_str()); + srs_freep(err); + return err; + } + + // client close peer. + // TODO: FIXME: Only reset the error when client closed it. + if (srs_is_client_gracefully_close(err)) { + srs_warn("RTSP: client disconnect peer. ret=%d", srs_error_code(err)); + } else if (srs_is_server_gracefully_close(err)) { + srs_warn("RTSP: server disconnect. ret=%d", srs_error_code(err)); + } else { + srs_error("RTSP: serve error %s", srs_error_desc(err).c_str()); + } + + srs_freep(err); + return srs_success; +} + +srs_error_t SrsRtspConnection::do_cycle() +{ + srs_error_t err = srs_success; + srs_trace("RTSP: client ip=%s, port=%d", ip_.c_str(), port_); + + // consume all rtsp messages. + while (true) { + if ((err = trd_->pull()) != srs_success) { + return srs_error_wrap(err, "rtsp cycle"); + } + + SrsRtspRequest* req_raw = NULL; + if ((err = rtsp_->recv_message(&req_raw)) != srs_success) { + return srs_error_wrap(err, "recv message"); + } + SrsUniquePtr req(req_raw); + + if (req->is_options()) { + srs_trace("RTSP: OPTIONS cseq=%ld, url=%s, client=%s:%d", req->seq, req->uri.c_str(), ip_.c_str(), port_); + SrsUniquePtr res(new SrsRtspOptionsResponse((int)req->seq)); + if ((err = rtsp_->send_message(res.get())) != srs_success) { + return srs_error_wrap(err, "response option"); + } + } else if (req->is_describe()) { + // create session. + if (session_id_.empty()) { + session_id_ = srs_random_str(8); + } + + SrsUniquePtr res(new SrsRtspDescribeResponse((int)req->seq)); + res->session = session_id_; + + std::string sdp; + if ((err = do_describe(req.get(), sdp)) != srs_success) { + res->status = SRS_CONSTS_RTSP_InternalServerError; + if (srs_error_code(err) == ERROR_RTSP_NO_TRACK) { + res->status = SRS_CONSTS_RTSP_NotFound; + } else if (srs_error_code(err) == ERROR_SYSTEM_SECURITY_DENY) { + res->status = SRS_CONSTS_RTSP_Forbidden; + } + srs_warn("RTSP: DESCRIBE failed: %s", srs_error_desc(err).c_str()); + srs_error_reset(err); + } + + res->sdp = sdp; + if ((err = rtsp_->send_message(res.get())) != srs_success) { + return srs_error_wrap(err, "response describe"); + } + + // Filter the \r\n to \\r\\n for JSON. + std::string local_sdp_escaped = srs_string_replace(sdp.c_str(), "\r\n", "\\r\\n"); + srs_trace("RTSP: DESCRIBE cseq=%ld, session=%s, sdp: %s", req->seq, session_id_.c_str(), local_sdp_escaped.c_str()); + } else if (req->is_setup()) { + srs_assert(req->transport); + + SrsUniquePtr res(new SrsRtspSetupResponse((int)req->seq)); + res->session = session_id_; + + uint32_t ssrc = 0; + if ((err = do_setup(req.get(), &ssrc)) != srs_success) { + if (srs_error_code(err) == ERROR_RTSP_TRANSPORT_NOT_SUPPORTED) { + res->status = SRS_CONSTS_RTSP_UnsupportedTransport; + srs_warn("RTSP: SETUP failed: %s", srs_error_summary(err).c_str()); + } else { + res->status = SRS_CONSTS_RTSP_InternalServerError; + srs_warn("RTSP: SETUP failed: %s", srs_error_desc(err).c_str()); + } + srs_error_reset(err); + } + + res->transport->copy(req->transport); + res->session = session_id_; + res->ssrc = srs_int2str(ssrc); + res->client_port_min = req->transport->client_port_min; + res->client_port_max = req->transport->client_port_max; + // TODO: FIXME: listen local port + res->local_port_min = 0; + res->local_port_max = 0; + if ((err = rtsp_->send_message(res.get())) != srs_success) { + return srs_error_wrap(err, "response setup"); + } + srs_trace("RTSP: SETUP cseq=%ld, session=%s, transport=%s/%s/%s, ssrc=%u, client_port=%d-%d", + req->seq, session_id_.c_str(), req->transport->transport.c_str(), req->transport->profile.c_str(), + req->transport->lower_transport.c_str(), ssrc, req->transport->client_port_min, req->transport->client_port_max); + } else if (req->is_play()) { + SrsUniquePtr res(new SrsRtspResponse((int)req->seq)); + res->session = session_id_; + if ((err = rtsp_->send_message(res.get())) != srs_success) { + return srs_error_wrap(err, "response record"); + } + + if ((err = do_play(req.get(), this)) != srs_success) { + return srs_error_wrap(err, "prepare play"); + } + srs_trace("RTSP: PLAY cseq=%ld, session=%s, streaming started", req->seq, session_id_.c_str()); + } else if (req->is_teardown()) { + SrsUniquePtr res(new SrsRtspResponse((int)req->seq)); + res->session = session_id_; + if ((err = rtsp_->send_message(res.get())) != srs_success) { + return srs_error_wrap(err, "response teardown"); + } + + if ((err = do_teardown()) != srs_success) { + return srs_error_wrap(err, "teardown"); + } + srs_trace("RTSP: TEARDOWN cseq=%ld, session=%s, streaming stopped", req->seq, session_id_.c_str()); + } + } + + return err; +} + +void SrsRtspConnection::on_before_dispose(ISrsResource* c) +{ + if (disposing_) { + return; + } + + SrsRtspConnection* session = dynamic_cast(c); + if (session == this) { + disposing_ = true; + } + + if (session && session == this) { + _srs_context->set_id(cid_); + srs_trace("RTSP: session detach from [%s](%s), disposing=%d", c->get_id().c_str(), + c->desc().c_str(), disposing_); + } +} + +void SrsRtspConnection::on_disposing(ISrsResource* c) +{ + if (disposing_) { + return; + } +} + +void SrsRtspConnection::switch_to_context() +{ + _srs_context->set_id(cid_); +} + +const SrsContextId& SrsRtspConnection::context_id() +{ + return cid_; +} + +bool SrsRtspConnection::is_alive() +{ + return last_stun_time + session_timeout > srs_get_system_time(); +} + +void SrsRtspConnection::alive() +{ + last_stun_time = srs_get_system_time(); +} + +srs_error_t SrsRtspConnection::do_describe(SrsRtspRequest* req, std::string& sdp) +{ + srs_error_t err = srs_success; + srs_parse_rtmp_url(req->uri, request_->tcUrl, request_->stream); + + srs_discovery_tc_url(request_->tcUrl, request_->schema, request_->host, request_->vhost, + request_->app, request_->stream, request_->port, request_->param); + + // discovery vhost, resolve the vhost from config + SrsConfDirective* parsed_vhost = _srs_config->get_vhost(request_->vhost); + if (parsed_vhost) { + request_->vhost = parsed_vhost->arg0(); + } + + if ((err = security_->check(SrsRtcConnPlay, ip_, request_)) != srs_success) { + return srs_error_wrap(err, "RTSP: security check"); + } + + if ((err = http_hooks_on_play(request_)) != srs_success) { + return srs_error_wrap(err, "RTSP: http_hooks_on_play"); + } + + if ((err = _srs_rtsp_sources->fetch_or_create(request_, source_)) != srs_success) { + return srs_error_wrap(err, "create source"); + } + + SrsSdp local_sdp; + local_sdp.version_ = "0"; + local_sdp.username_ = "SRS RTSP Server"; + local_sdp.session_id_ = "0"; + local_sdp.session_version_ = "0"; + local_sdp.nettype_ = "IN"; + local_sdp.addrtype_ = "IP4"; + local_sdp.unicast_address_ = "0.0.0.0"; + local_sdp.session_name_ = "Play"; + local_sdp.control_ = req->uri; + local_sdp.ice_lite_ = ""; // Disable this line. + + uint32_t track_id = 0; + SrsRtcTrackDescription* audio_desc = source_->audio_desc(); + if (audio_desc) { + SrsRtcTrackDescription* audio_track_desc = audio_desc->copy(); + audio_track_desc->id_ = srs_int2str(track_id); + tracks_.insert(std::make_pair(audio_track_desc->ssrc_, audio_track_desc)); + + SrsMediaDesc media_audio("audio"); + media_audio.port_ = 0; // Port 0 indicates no UDP transport available + media_audio.protos_ = "RTP/AVP"; // MUST be RTP/AVP + media_audio.control_ = req->uri + "/trackID=" + srs_int2str(track_id); + media_audio.recvonly_ = true; + media_audio.rtcp_mux_ = true; + + media_audio.payload_types_.push_back(SrsMediaPayloadType(audio_track_desc->media_->pt_)); + SrsMediaPayloadType& ps_audio = media_audio.payload_types_.at(0); + ps_audio.encoding_name_ = audio_track_desc->media_->name_; + ps_audio.clock_rate_ = audio_track_desc->media_->sample_; + + // if the payload is opus, and the encoding_param_ is channel + SrsAudioPayload* ap = dynamic_cast(audio_track_desc->media_); + if (ap) { + ps_audio.encoding_param_ = srs_int2str(ap->channel_); + + // Append the AAC config hex to the fmtp line. + if (ap->name_ == "MPEG4-GENERIC" && !ap->aac_config_hex_.empty()) { + // streamtype=5 - Mandatory (indicates audio stream) + // mode=AAC-hbr - Mandatory (AAC High Bit Rate mode) + // sizelength=13 - Mandatory, defaults to 13 bits for AAC + // indexlength=3 - Mandatory, defaults to 3 bits + // profile-level-id=1 - Optional, defaults to 1 (AAC Main Profile) + // indexdeltalength=3 - Optional, defaults to 3 bits + ps_audio.format_specific_param_ = "streamtype=5;mode=AAC-hbr;sizelength=13;indexlength=3"; + ps_audio.format_specific_param_ += ";config=" + ap->aac_config_hex_; + srs_trace("RTSP: Added AAC fmtp: %s", ps_audio.format_specific_param_.c_str()); + } + } + + local_sdp.media_descs_.push_back(media_audio); + track_id++; + } + + SrsRtcTrackDescription* video_desc = source_->video_desc(); + if (video_desc) { + SrsRtcTrackDescription* video_track_desc = video_desc->copy(); + video_track_desc->id_ = srs_int2str(track_id); + tracks_.insert(std::make_pair(video_track_desc->ssrc_, video_track_desc)); + + SrsMediaDesc media_video("video"); + media_video.port_ = 0; // Port 0 indicates no UDP transport available + media_video.protos_ = "RTP/AVP"; // MUST be RTP/AVP + media_video.control_ = req->uri + "/trackID=" + srs_int2str(track_id); + media_video.recvonly_ = true; + media_video.rtcp_mux_ = true; + + media_video.payload_types_.push_back(SrsMediaPayloadType(video_track_desc->media_->pt_)); + SrsMediaPayloadType& ps_video = media_video.payload_types_.at(0); + ps_video.encoding_name_ = video_track_desc->media_->name_; + ps_video.clock_rate_ = video_track_desc->media_->sample_; + + local_sdp.media_descs_.push_back(media_video); + track_id++; + } + + if (track_id == 0) { + return srs_error_new(ERROR_RTSP_NO_TRACK, "no track found"); + } + + std::ostringstream ss; + if ((err = local_sdp.encode(ss)) != srs_success) { + return srs_error_wrap(err, "encode sdp"); + } + + sdp = ss.str(); + return srs_success; +} + +srs_error_t SrsRtspConnection::do_setup(SrsRtspRequest* req, uint32_t* pssrc) +{ + srs_error_t err = srs_success; + + uint32_t ssrc = 0; + if ((err = get_ssrc_by_stream_id(req->stream_id, &ssrc)) != srs_success) { + return srs_error_wrap(err, "get ssrc by stream_id"); + } + + // Only support TCP transport, reject UDP + // This ensures better firewall/NAT compatibility and eliminates port allocation complexity + if (req->transport->lower_transport != "TCP") { + return srs_error_new(ERROR_RTSP_TRANSPORT_NOT_SUPPORTED, + "UDP transport not supported, only TCP/interleaved mode is supported"); + } + + SrsRtspTcpNetwork* network = new SrsRtspTcpNetwork(skt_, req->transport->interleaved_min); + networks_[ssrc] = network; + + *pssrc = ssrc; + + return srs_success; +} + +srs_error_t SrsRtspConnection::do_play(SrsRtspRequest* req, SrsRtspConnection* conn) +{ + srs_error_t err = srs_success; + + srs_freep(player_); + player_ = new SrsRtspPlayStream(conn, cid_); + + if ((err = player_->initialize(request_, tracks_)) != srs_success) { + srs_freep(player_); + return srs_error_wrap(err, "SrsRtspPlayStream init"); + } + player_->set_all_tracks_status(true); + if ((err = player_->start()) != srs_success) { + return srs_error_wrap(err, "start play"); + } + + srs_trace("RTSP: Subscriber url=%s established", req->uri.c_str()); + + return err; +} + +srs_error_t SrsRtspConnection::do_teardown() +{ + if (player_) { + player_->stop(); + srs_freep(player_); + } + + return srs_success; +} + +srs_error_t SrsRtspConnection::http_hooks_on_play(SrsRequest* req) +{ + srs_error_t err = srs_success; + + if (!_srs_config->get_vhost_http_hooks_enabled(req->vhost)) { + return err; + } + + // the http hooks will cause context switch, + // so we must copy all hooks for the on_connect may freed. + // @see https://github.com/ossrs/srs/issues/475 + std::vector hooks; + + if (true) { + SrsConfDirective* conf = _srs_config->get_vhost_on_play(req->vhost); + + if (!conf) { + return err; + } + + hooks = conf->args; + } + + for (int i = 0; i < (int)hooks.size(); i++) { + std::string url = hooks.at(i); + if ((err = SrsHttpHooks::on_play(url, req)) != srs_success) { + return srs_error_wrap(err, "on_play %s", url.c_str()); + } + } + + return err; +} + +srs_error_t SrsRtspConnection::get_ssrc_by_stream_id(uint32_t stream_id, uint32_t* ssrc) +{ + for (std::map::iterator it = tracks_.begin(); it != tracks_.end(); ++it) { + if (it->second->id_ == srs_int2str(stream_id)) { + *ssrc = it->second->ssrc_; + return srs_success; + } + } + return srs_error_new(ERROR_RTSP_NO_TRACK, "track not found for stream_id: %u", stream_id); +} + +SrsRtspTcpNetwork::SrsRtspTcpNetwork(ISrsProtocolReadWriter* skt, int ch) : skt_(skt), channel_(ch) +{ +} + +SrsRtspTcpNetwork::~SrsRtspTcpNetwork() +{ +} + +srs_error_t SrsRtspTcpNetwork::write(void* buf, size_t size, ssize_t* nwrite) +{ + srs_error_t err = srs_success; + + srs_assert(size <= 65535); + + // Encode and send 4 bytes size, in network order. + const int kRtpTcpPacketHeaderSize = 4; + char header[kRtpTcpPacketHeaderSize]; + + // Use SrsBuffer to handle endianness properly + SrsBuffer hb(header, kRtpTcpPacketHeaderSize); + hb.write_1bytes(0x24); // Magic byte '$' + hb.write_1bytes(uint8_t(channel_)); // Channel number + hb.write_2bytes(uint16_t(size)); // Packet size in network order + + if((err = skt_->write(header, kRtpTcpPacketHeaderSize, NULL)) != srs_success) { + return srs_error_wrap(err, "RTSP tcp write len(%d)", size); + } + + if ((err = skt_->write(buf, size, nwrite)) != srs_success) { + return srs_error_wrap(err, "RTSP send rtp packet"); + } + + // Add the size of the header to the write count. + *nwrite += kRtpTcpPacketHeaderSize; + + return err; +} + diff --git a/trunk/src/app/srs_app_rtsp_conn.hpp b/trunk/src/app/srs_app_rtsp_conn.hpp new file mode 100644 index 000000000..102f382f7 --- /dev/null +++ b/trunk/src/app/srs_app_rtsp_conn.hpp @@ -0,0 +1,181 @@ +// +// Copyright (c) 2013-2025 The SRS Authors +// +// SPDX-License-Identifier: MIT +// + +#ifndef SRS_APP_RTSP_CONN_HPP +#define SRS_APP_RTSP_CONN_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +class SrsRequest; +class SrsRtpPacket; +class SrsRtspSource; +class SrsRtspAudioSendTrack; +class SrsRtspVideoSendTrack; +class SrsRtspSendTrack; +class SrsEphemeralDelta; +class SrsRtspConnection; +class SrsSecurity; +class SrsRtspRequest; +class SrsRtspStack; + +// A RTSP play stream, client pull and play stream from SRS. +class SrsRtspPlayStream : public ISrsCoroutineHandler, public ISrsRtcSourceChangeCallback +{ +private: + SrsContextId cid_; + SrsFastCoroutine* trd_; + SrsRtspConnection* session_; +private: + SrsRequest* req_; + SrsSharedPtr source_; + // key: publish_ssrc, value: send track to process rtp/rtcp + std::map audio_tracks_; + std::map video_tracks_; +private: + // Fast cache for tracks. + uint32_t cache_ssrc0_; + uint32_t cache_ssrc1_; + uint32_t cache_ssrc2_; + SrsRtspSendTrack* cache_track0_; + SrsRtspSendTrack* cache_track1_; + SrsRtspSendTrack* cache_track2_; +private: + // Whether player started. + bool is_started; +public: + SrsRtspPlayStream(SrsRtspConnection* s, const SrsContextId& cid); + virtual ~SrsRtspPlayStream(); +public: + srs_error_t initialize(SrsRequest* request, std::map sub_relations); +// Interface ISrsRtcSourceChangeCallback +public: + void on_stream_change(SrsRtcSourceDescription* desc); +public: + virtual const SrsContextId& context_id(); +public: + virtual srs_error_t start(); + virtual void stop(); +public: + virtual srs_error_t cycle(); +private: + srs_error_t send_packet(SrsRtpPacket*& pkt); +public: + // Directly set the status of track, generally for init to set the default value. + void set_all_tracks_status(bool status); +}; + +class SrsRtspConnection : public ISrsResource, public ISrsDisposingHandler, public ISrsExpire, public ISrsCoroutineHandler, public ISrsStartable +{ +private: + bool disposing_; +private: + // TODO: FIXME: Rename it. + // The timeout of session, keep alive by STUN ping pong. + srs_utime_t session_timeout; + // TODO: FIXME: Rename it. + srs_utime_t last_stun_time; + SrsContextId cid_; + SrsRequest* request_; + // The manager object to manage the connection. + ISrsResourceManager* manager_; + // Each connection start a green thread, + // when thread stop, the connection will be delete by server. + SrsCoroutine* trd_; + // The ip and port of client. + std::string ip_; + int port_; + SrsRtspStack* rtsp_; + std::string session_id_; + SrsSharedPtr source_; + SrsEphemeralDelta* delta_; + ISrsProtocolReadWriter* skt_; + SrsSecurity* security_; + iovec* cache_iov_; + SrsBuffer* cache_buffer_; + // key: ssrc + std::map tracks_; + // key: ssrc + std::map networks_; + SrsRtspPlayStream* player_; +public: + SrsRtspConnection(ISrsResourceManager* cm, ISrsProtocolReadWriter* skt, std::string cip, int port); + virtual ~SrsRtspConnection(); +// interface ISrsDisposingHandler +public: + virtual void on_before_dispose(ISrsResource* c); + virtual void on_disposing(ISrsResource* c); +public: + virtual srs_error_t do_send_packet(SrsRtpPacket* pkt); +public: + ISrsKbpsDelta* delta(); +private: + virtual srs_error_t do_describe(SrsRtspRequest* req, std::string& sdp); + virtual srs_error_t do_setup(SrsRtspRequest* req, uint32_t* ssrc); + virtual srs_error_t do_play(SrsRtspRequest* req, SrsRtspConnection* conn); + virtual srs_error_t do_teardown(); +// Interface ISrsResource. +public: + virtual std::string desc(); + virtual const SrsContextId& get_id(); +// Interface ISrsConnection. +public: + virtual std::string remote_ip(); +// Interface ISrsStartable +public: + // Start the client green thread. + // when server get a client from listener, + // 1. server will create an concrete connection(for instance, RTMP connection), + // 2. then add connection to its connection manager, + // 3. start the client thread by invoke this start() + // when client cycle thread stop, invoke the on_thread_stop(), which will use server + // To remove the client by server->remove(this). + virtual srs_error_t start(); +// Interface ISrsCoroutineHandler +public: + virtual srs_error_t cycle(); +// Interface ISrsExpire. +public: + virtual void expire(); +public: + void switch_to_context(); + const SrsContextId& context_id(); +public: + bool is_alive(); + void alive(); +private: + srs_error_t do_cycle(); +private: + srs_error_t http_hooks_on_play(SrsRequest* req); + srs_error_t get_ssrc_by_stream_id(uint32_t stream_id, uint32_t* ssrc); +}; + +class SrsRtspTcpNetwork : public ISrsStreamWriter +{ +private: + ISrsProtocolReadWriter* skt_; + int channel_; +public: + SrsRtspTcpNetwork(ISrsProtocolReadWriter* skt, int ch); + virtual ~SrsRtspTcpNetwork(); +// Interface ISrsStreamWriter. +public: + virtual srs_error_t write(void* buf, size_t size, ssize_t* nwrite); +}; + +#endif + diff --git a/trunk/src/app/srs_app_rtsp_source.cpp b/trunk/src/app/srs_app_rtsp_source.cpp new file mode 100644 index 000000000..c6bbd25e4 --- /dev/null +++ b/trunk/src/app/srs_app_rtsp_source.cpp @@ -0,0 +1,1079 @@ +// +// Copyright (c) 2013-2025 The SRS Authors +// +// SPDX-License-Identifier: MIT +// + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +extern SrsPps* _srs_pps_aloss2; + +static const int kVideoSamplerate = 90000; + +// the time to cleanup source. +#define SRS_RTSP_SOURCE_CLEANUP (3 * SRS_UTIME_SECONDS) + +SrsRtspConsumer::SrsRtspConsumer(SrsRtspSource* s) +{ + source_ = s; + should_update_source_id = false; + handler_ = NULL; + + mw_wait = srs_cond_new(); + mw_min_msgs = 0; + mw_waiting = false; +} + +SrsRtspConsumer::~SrsRtspConsumer() +{ + source_->on_consumer_destroy(this); + + vector::iterator it; + for (it = queue.begin(); it != queue.end(); ++it) { + SrsRtpPacket* pkt = *it; + srs_freep(pkt); + } + + srs_cond_destroy(mw_wait); +} + +void SrsRtspConsumer::update_source_id() +{ + should_update_source_id = true; +} + +srs_error_t SrsRtspConsumer::enqueue(SrsRtpPacket* pkt) +{ + srs_error_t err = srs_success; + + queue.push_back(pkt); + + if (mw_waiting) { + if ((int)queue.size() > mw_min_msgs) { + srs_cond_signal(mw_wait); + mw_waiting = false; + return err; + } + } + + return err; +} + +srs_error_t SrsRtspConsumer::dump_packet(SrsRtpPacket** ppkt) +{ + srs_error_t err = srs_success; + + // TODO: FIXME: Refine performance by ring buffer. + if (!queue.empty()) { + *ppkt = queue.front(); + queue.erase(queue.begin()); + } + + return err; +} + +void SrsRtspConsumer::wait(int nb_msgs) +{ + mw_min_msgs = nb_msgs; + + // when duration ok, signal to flush. + if ((int)queue.size() > mw_min_msgs) { + return; + } + + // the enqueue will notify this cond. + mw_waiting = true; + + // use cond block wait for high performance mode. + srs_cond_wait(mw_wait); +} + +void SrsRtspConsumer::on_stream_change(SrsRtcSourceDescription* desc) +{ + if (handler_) { + handler_->on_stream_change(desc); + } +} + +SrsRtspSourceManager::SrsRtspSourceManager() +{ + lock = srs_mutex_new(); + timer_ = new SrsHourGlass("sources", this, 1 * SRS_UTIME_SECONDS); +} + +SrsRtspSourceManager::~SrsRtspSourceManager() +{ + srs_mutex_destroy(lock); + srs_freep(timer_); +} + +srs_error_t SrsRtspSourceManager::initialize() +{ + return setup_ticks(); +} + +srs_error_t SrsRtspSourceManager::setup_ticks() +{ + srs_error_t err = srs_success; + + if ((err = timer_->tick(1, 3 * SRS_UTIME_SECONDS)) != srs_success) { + return srs_error_wrap(err, "tick"); + } + + if ((err = timer_->start()) != srs_success) { + return srs_error_wrap(err, "timer"); + } + + return err; +} + +srs_error_t SrsRtspSourceManager::notify(int event, srs_utime_t interval, srs_utime_t tick) +{ + srs_error_t err = srs_success; + + std::map< std::string, SrsSharedPtr >::iterator it; + for (it = pool.begin(); it != pool.end();) { + SrsSharedPtr& source = it->second; + + // When source expired, remove it. + // @see https://github.com/ossrs/srs/issues/713 + if (source->stream_is_dead()) { + SrsContextId cid = source->source_id(); + if (cid.empty()) cid = source->pre_source_id(); + srs_trace("RTSP: cleanup die source, id=[%s], total=%d", cid.c_str(), (int)pool.size()); + pool.erase(it++); + } else { + ++it; + } + } + + return err; +} + +srs_error_t SrsRtspSourceManager::fetch_or_create(SrsRequest* r, SrsSharedPtr& pps) +{ + srs_error_t err = srs_success; + + // Use lock to protect coroutine switch. + // @bug https://github.com/ossrs/srs/issues/1230 + SrsLocker(lock); + + string stream_url = r->get_stream_url(); + std::map< std::string, SrsSharedPtr >::iterator it = pool.find(stream_url); + + if (it != pool.end()) { + SrsSharedPtr source = it->second; + + // we always update the request of resource, + // for origin auth is on, the token in request maybe invalid, + // and we only need to update the token of request, it's simple. + source->update_auth(r); + pps = source; + + return err; + } + + SrsSharedPtr source = SrsSharedPtr(new SrsRtspSource()); + srs_trace("new rtsp source, stream_url=%s", stream_url.c_str()); + + if ((err = source->initialize(r)) != srs_success) { + return srs_error_wrap(err, "init source %s", r->get_stream_url().c_str()); + } + + pool[stream_url] = source; + pps = source; + + return err; +} + +SrsSharedPtr SrsRtspSourceManager::fetch(SrsRequest* r) +{ + // Use lock to protect coroutine switch. + // @bug https://github.com/ossrs/srs/issues/1230 + SrsLocker(lock); + + string stream_url = r->get_stream_url(); + std::map< std::string, SrsSharedPtr >::iterator it = pool.find(stream_url); + + SrsSharedPtr source; + if (it == pool.end()) { + return source; + } + + source = it->second; + return source; +} + +SrsRtspSourceManager* _srs_rtsp_sources = NULL; + +SrsResourceManager* _srs_rtsp_manager = NULL; + +SrsRtspSource::SrsRtspSource() +{ + is_created_ = false; + is_delivering_packets_ = false; + + audio_desc_ = NULL; + video_desc_ = NULL; + + req = NULL; + + stream_die_at_ = 0; +} + +SrsRtspSource::~SrsRtspSource() +{ + // never free the consumers, + // for all consumers are auto free. + consumers.clear(); + + srs_freep(req); + srs_freep(audio_desc_); + srs_freep(video_desc_); + + SrsContextId cid = _source_id; + if (cid.empty()) cid = _pre_source_id; + srs_trace("free rtc source id=[%s]", cid.c_str()); +} + +srs_error_t SrsRtspSource::initialize(SrsRequest* r) +{ + srs_error_t err = srs_success; + + req = r->copy(); + + return err; +} + +bool SrsRtspSource::stream_is_dead() +{ + // still publishing? + if (is_created_) { + return false; + } + + // has any consumers? + if (!consumers.empty()) { + return false; + } + + // Delay cleanup source. + srs_utime_t now = srs_get_system_time(); + if (now < stream_die_at_ + SRS_RTSP_SOURCE_CLEANUP) { + return false; + } + + return true; +} + +void SrsRtspSource::update_auth(SrsRequest* r) +{ + req->update_auth(r); +} + +srs_error_t SrsRtspSource::on_source_changed() +{ + srs_error_t err = srs_success; + + // Update context id if changed. + bool id_changed = false; + const SrsContextId& id = _srs_context->get_id(); + if (_source_id.compare(id)) { + id_changed = true; + + if (_pre_source_id.empty()) { + _pre_source_id = id; + } + _source_id = id; + } + + // Build stream description. + SrsUniquePtr stream_desc(new SrsRtcSourceDescription()); + if (audio_desc_) { + stream_desc->audio_track_desc_ = audio_desc_->copy(); + } + if (video_desc_) { + stream_desc->video_track_descs_.push_back(video_desc_->copy()); + } + + // Notify all consumers. + std::vector::iterator it; + for (it = consumers.begin(); it != consumers.end(); ++it) { + SrsRtspConsumer* consumer = *it; + + // Notify if context id changed. + if (id_changed) { + consumer->update_source_id(); + } + + // Notify about stream description. + consumer->on_stream_change(stream_desc.get()); + } + + return err; +} + +SrsContextId SrsRtspSource::source_id() +{ + return _source_id; +} + +SrsContextId SrsRtspSource::pre_source_id() +{ + return _pre_source_id; +} + +srs_error_t SrsRtspSource::create_consumer(SrsRtspConsumer*& consumer) +{ + srs_error_t err = srs_success; + + consumer = new SrsRtspConsumer(this); + consumers.push_back(consumer); + + stream_die_at_ = 0; + + // TODO: FIXME: Implements edge cluster. + + return err; +} + +srs_error_t SrsRtspSource::consumer_dumps(SrsRtspConsumer* consumer, bool ds, bool dm, bool dg) +{ + srs_error_t err = srs_success; + + // print status. + srs_trace("create rtsp consumer, no gop cache"); + + return err; +} + +void SrsRtspSource::on_consumer_destroy(SrsRtspConsumer* consumer) +{ + std::vector::iterator it; + it = std::find(consumers.begin(), consumers.end(), consumer); + if (it != consumers.end()) { + it = consumers.erase(it); + } + + // TODO: When all consumers finished, notify publisher to handle it. + + // Destroy and cleanup source when no publishers and consumers. + if (!is_created_ && consumers.empty()) { + stream_die_at_ = srs_get_system_time(); + } +} + +bool SrsRtspSource::can_publish() +{ + // TODO: FIXME: Should check the status of bridge. + + return !is_created_; +} + +void SrsRtspSource::set_stream_created() +{ + srs_assert(!is_created_ && !is_delivering_packets_); + is_created_ = true; +} + +srs_error_t SrsRtspSource::on_publish() +{ + srs_error_t err = srs_success; + + // update the request object. + srs_assert(req); + + // For RTC, DTLS is done, and we are ready to deliver packets. + // @note For compatible with RTMP, we also set the is_created_, it MUST be created here. + is_created_ = true; + is_delivering_packets_ = true; + + // Notify the consumers about stream change event. + if ((err = on_source_changed()) != srs_success) { + return srs_error_wrap(err, "source id change"); + } + + SrsStatistic* stat = SrsStatistic::instance(); + stat->on_stream_publish(req, _source_id.c_str()); + + return err; +} + +void SrsRtspSource::on_unpublish() +{ + // ignore when already unpublished. + if (!is_created_) { + return; + } + + srs_trace("cleanup when unpublish, created=%u, deliver=%u", is_created_, is_delivering_packets_); + + is_created_ = false; + is_delivering_packets_ = false; + + if (!_source_id.empty()) { + _pre_source_id = _source_id; + } + _source_id = SrsContextId(); + + SrsStatistic* stat = SrsStatistic::instance(); + stat->on_stream_close(req); + + // Destroy and cleanup source when no publishers and consumers. + if (consumers.empty()) { + stream_die_at_ = srs_get_system_time(); + } +} + +srs_error_t SrsRtspSource::on_rtp(SrsRtpPacket* pkt) +{ + srs_error_t err = srs_success; + + // If circuit-breaker is dying, drop packet. + if (_srs_circuit_breaker->hybrid_dying_water_level()) { + _srs_pps_aloss2->sugar += (int64_t)consumers.size(); + return err; + } + + for (int i = 0; i < (int)consumers.size(); i++) { + SrsRtspConsumer* consumer = consumers.at(i); + if ((err = consumer->enqueue(pkt->copy())) != srs_success) { + return srs_error_wrap(err, "consume message"); + } + } + + return err; +} + +SrsRtcTrackDescription* SrsRtspSource::audio_desc() +{ + return audio_desc_; +} + +void SrsRtspSource::set_audio_desc(SrsRtcTrackDescription* audio_desc) +{ + srs_freep(audio_desc_); + audio_desc_ = audio_desc->copy(); +} + +SrsRtcTrackDescription* SrsRtspSource::video_desc() +{ + return video_desc_; +} + +void SrsRtspSource::set_video_desc(SrsRtcTrackDescription* video_desc) +{ + srs_freep(video_desc_); + video_desc_ = video_desc->copy(); +} + +SrsRtspRtpBuilder::SrsRtspRtpBuilder(SrsFrameToRtspBridge* bridge, SrsSharedPtr source) +{ + bridge_ = bridge; + source_ = source; + + req = NULL; + format = new SrsRtmpFormat(); + meta = new SrsMetaCache(); + video_builder_ = new SrsRtpVideoBuilder(); + audio_sequence = 0; + + // Initialize with default values - will be set during lazy initialization + audio_ssrc_ = 0; + audio_payload_type_ = 0; + audio_sample_rate_ = 0; + + // Lazy initialization flags + audio_initialized_ = false; + video_initialized_ = false; +} + +SrsRtspRtpBuilder::~SrsRtspRtpBuilder() +{ + srs_freep(format); + srs_freep(meta); + srs_freep(video_builder_); +} + +srs_error_t SrsRtspRtpBuilder::initialize_audio_track(SrsAudioCodecId codec) +{ + srs_error_t err = srs_success; + + // RTSP behavior: Build track description from real audio format, not default values + // This is different from RTC which uses default track descriptions + + // Create audio track description from actual format data + SrsUniquePtr audio_desc(new SrsRtcTrackDescription()); + audio_desc->type_ = "audio"; + audio_desc->id_ = "audio-" + srs_random_str(8); + audio_desc->direction_ = "recvonly"; + + // Generate SSRC for this track + audio_ssrc_ = SrsRtcSSRCGenerator::instance()->generate_ssrc(); + audio_desc->ssrc_ = audio_ssrc_; + + int sample_rate = srs_flv_srates[format->acodec->sound_rate]; + audio_sample_rate_ = sample_rate; + + // Build payload from actual audio format + if (codec == SrsAudioCodecIdOpus) { + // For Opus, use actual format parameters if available + int channels = (format->acodec->sound_type == SrsAudioChannelsStereo) ? 2 : 1; + audio_payload_type_ = kAudioPayloadType; + audio_desc->media_ = new SrsAudioPayload(audio_payload_type_, "opus", sample_rate, channels); + } else if (codec == SrsAudioCodecIdAAC) { + // For AAC, extract parameters from format + int channels = format->acodec->aac_channels; + audio_payload_type_ = kAudioPayloadType; + + // Note: Use "MPEG4-GENERIC" instead of "AAC" for RTSP/SDP compliance + // RFC 3640 specifies that AAC should be advertised as "MPEG4-GENERIC" in SDP rtpmap + // "AAC" is non-standard and not widely supported by RTSP clients + SrsAudioPayload* aac_payload = new SrsAudioPayload(audio_payload_type_, "MPEG4-GENERIC", sample_rate, channels); + + // AAC requires AudioSpecificConfig in SDP fmtp line + // Build the config string from AAC sequence header + const std::vector& asc = format->acodec->aac_extra_data; + if (!asc.empty()) { + int hex_len = asc.size() * 2; + SrsUniquePtr hex_buf(new char[hex_len + 1]); + srs_data_to_hex(hex_buf.get(), (const uint8_t*)asc.data(), asc.size()); + + hex_buf.get()[hex_len] = '\0'; // Null terminate + std::string config_hex = std::string(hex_buf.get()); + + // Set the AAC configuration directly in the audio payload + aac_payload->aac_config_hex_ = config_hex; + srs_trace("RTSP: AAC config hex: %s", config_hex.c_str()); + } + + audio_desc->media_ = aac_payload; + } else { + return srs_error_new(ERROR_RTC_RTP_MUXER, "unsupported audio codec %d", codec); + } + + // Extract info for logging before setting to source + int sample_rate_for_log = audio_desc->media_->sample_; + int channels_for_log = (audio_desc->media_->type_ == "audio") ? ((SrsAudioPayload*)audio_desc->media_)->channel_ : 0; + + // Set the audio description to source + source_->set_audio_desc(audio_desc.get()); + + srs_trace("RTSP: Initialize audio track from format - codec=%s, ssrc=%u, pt=%d, sample_rate=%d, channels=%d", + srs_audio_codec_id2str(codec).c_str(), audio_ssrc_, audio_payload_type_, + sample_rate_for_log, channels_for_log); + + return err; +} + +srs_error_t SrsRtspRtpBuilder::initialize_video_track(SrsVideoCodecId codec) +{ + srs_error_t err = srs_success; + + // RTSP behavior: Build track description from real video format, not default values + // This is different from RTC which uses default track descriptions + + std::string codec_name = srs_video_codec_id2str(codec); + + // Create video track description from actual format data + SrsUniquePtr video_desc(new SrsRtcTrackDescription()); + video_desc->type_ = "video"; + video_desc->id_ = "video-" + codec_name + "-" + srs_random_str(8); + video_desc->direction_ = "recvonly"; + + // Generate SSRC for this track + uint32_t video_ssrc = SrsRtcSSRCGenerator::instance()->generate_ssrc(); + video_desc->ssrc_ = video_ssrc; + + // Build payload from actual video format + uint8_t video_payload_type = 0; + if (codec == SrsVideoCodecIdAVC) { + // H.264 track with actual format parameters + video_payload_type = kVideoPayloadType; + SrsVideoPayload* h264_payload = new SrsVideoPayload(video_payload_type, "H264", kVideoSamplerate); + h264_payload->set_h264_param_desc("level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f"); + video_desc->media_ = h264_payload; + + } else if (codec == SrsVideoCodecIdHEVC) { + // H.265 track with actual format parameters + video_payload_type = KVideoPayloadTypeHevc; + SrsVideoPayload* h265_payload = new SrsVideoPayload(video_payload_type, "H265", kVideoSamplerate); + h265_payload->set_h265_param_desc("level-id=156;profile-id=1;tier-flag=0;tx-mode=SRST"); + video_desc->media_ = h265_payload; + + } else { + return srs_error_new(ERROR_RTC_RTP_MUXER, "unsupported video codec %d", codec); + } + + SrsFormat* format = meta->vsh_format(); + if ((err = video_builder_->initialize(format, video_ssrc, video_payload_type)) != srs_success) { + return srs_error_wrap(err, "initialize video builder"); + } + + // Set the video description to source + source_->set_video_desc(video_desc.get()); + + srs_trace("RTSP: Initialize video track from format - codec=%s, ssrc=%u, pt=%d, sample_rate=%d", + codec_name.c_str(), video_ssrc, video_payload_type, kVideoSamplerate); + + return err; +} + +srs_error_t SrsRtspRtpBuilder::initialize(SrsRequest* r) +{ + srs_error_t err = srs_success; + + req = r; + + if ((err = format->initialize()) != srs_success) { + return srs_error_wrap(err, "format initialize"); + } + + // Setup the SPS/PPS parsing strategy. + format->try_annexb_first = _srs_config->try_annexb_first(r->vhost); + + srs_trace("RTSP bridge from RTMP, try_annexb_first=%d", format->try_annexb_first); + + return err; +} + +srs_error_t SrsRtspRtpBuilder::on_publish() +{ + srs_error_t err = srs_success; + + // Reset the metadata cache, to make VLC happy when disable/enable stream. + // @see https://github.com/ossrs/srs/issues/1630#issuecomment-597979448 + meta->clear(); + + return err; +} + +void SrsRtspRtpBuilder::on_unpublish() +{ + // Reset the metadata cache, to make VLC happy when disable/enable stream. + // @see https://github.com/ossrs/srs/issues/1630#issuecomment-597979448 + meta->update_previous_vsh(); + meta->update_previous_ash(); +} + +srs_error_t SrsRtspRtpBuilder::on_frame(SrsSharedPtrMessage* frame) +{ + if (frame->is_audio()) { + return on_audio(frame); + } else if (frame->is_video()) { + return on_video(frame); + } + return srs_success; +} + +srs_error_t SrsRtspRtpBuilder::on_audio(SrsSharedPtrMessage* msg) +{ + srs_error_t err = srs_success; + + if ((err = format->on_audio(msg)) != srs_success) { + return srs_error_wrap(err, "format consume audio"); + } + + // Ignore if no format->acodec, it means the codec is not parsed, or unknown codec. + // @issue https://github.com/ossrs/srs/issues/1506#issuecomment-562079474 + if (!format->acodec) { + return err; + } + + // support audio codec: aac/opus + SrsAudioCodecId acodec = format->acodec->id; + if (acodec != SrsAudioCodecIdAAC && acodec != SrsAudioCodecIdOpus) { + return err; + } + + // Initialize audio track on first packet with actual codec + if (!audio_initialized_) { + if ((err = initialize_audio_track(acodec)) != srs_success) { + return srs_error_wrap(err, "init audio track"); + } + audio_initialized_ = true; + } + + // Skip empty audio frames + if (format->audio->nb_samples == 0) { + return err; + } + + // Convert to RTP packet. + SrsUniquePtr pkt(new SrsRtpPacket()); + + if (acodec == SrsAudioCodecIdAAC) { + if ((err = package_aac(format->audio, pkt.get())) != srs_success) { + return srs_error_wrap(err, "package aac"); + } + } else { + return srs_error_new(ERROR_NOT_IMPLEMENTED, "codec %d not implemented", acodec); + } + + if ((err = bridge_->on_rtp(pkt.get())) != srs_success) { + return srs_error_wrap(err, "consume audio packet"); + } + + return err; +} + +srs_error_t SrsRtspRtpBuilder::package_aac(SrsAudioFrame* audio, SrsRtpPacket* pkt) +{ + srs_error_t err = srs_success; + + srs_assert(audio->nb_samples); + + // For RTSP, audio TBN is not fixed, but use the sample rate, so we + // need to convert FLV TBN(1000) to the sample rate TBN. + int64_t dts = (int64_t)audio->dts; + dts *= (int64_t)audio_sample_rate_; + dts /= 1000; + + pkt->header.set_payload_type(audio_payload_type_); + pkt->header.set_ssrc(audio_ssrc_); + pkt->frame_type = SrsFrameTypeAudio; + pkt->header.set_marker(true); + pkt->header.set_sequence(audio_sequence++); + pkt->header.set_timestamp(dts); + + SrsRtpRawPayload* raw = new SrsRtpRawPayload(); + pkt->set_payload(raw, SrsRtpPacketPayloadTypeRaw); + + // For AAC, we need to package according to RFC 3640 (MPEG-4 Audio) + // Use AAC-hbr mode with AU-headers + // Calculate total size for all AU samples + int total_au_size = 0; + for (int i = 0; i < audio->nb_samples; i++) { + total_au_size += audio->samples[i].size; + } + + // AU-headers: 16 bits per AU (13 bits for size + 3 bits for index) + int au_headers_length = audio->nb_samples * 16; // bits + int au_headers_bytes = (au_headers_length + 7) / 8; // convert to bytes + int payload_size = 2 + au_headers_bytes + total_au_size; // AU-headers-length(2) + AU-headers + AU data + + // Use SrsBuffer for proper byte marshaling + SrsUniquePtr payload(new char[payload_size]); + SrsBuffer buffer(payload.get(), payload_size); + + // AU-headers-length (16 bits) - this is the length in BITS, not bytes + buffer.write_2bytes(au_headers_length); + + // Write AU-headers for each sample + for (int i = 0; i < audio->nb_samples; i++) { + // AU-header: AU-size(13 bits) + AU-index(3 bits) = 16 bits + // According to RFC 3640, AU-size comes first (MSB), then AU-index (LSB) + uint16_t au_size = audio->samples[i].size & 0x1FFF; // 13 bits mask + uint16_t au_index = i & 0x07; // 3 bits mask + buffer.write_2bytes((au_size << 3) | au_index); + } + + // Copy all AAC AU data + for (int i = 0; i < audio->nb_samples; i++) { + buffer.write_bytes(audio->samples[i].bytes, audio->samples[i].size); + } + + // Wrap the payload in the RTP packet + raw->payload = pkt->wrap(payload.get(), payload_size); + raw->nn_payload = payload_size; + + return err; +} + +static void free_packets(vector* pkts) { + if (!pkts) return; + + for (size_t i = 0; i < pkts->size(); i++) { + srs_freep((*pkts)[i]); + } + pkts->clear(); +} + +srs_error_t SrsRtspRtpBuilder::on_video(SrsSharedPtrMessage* msg) +{ + srs_error_t err = srs_success; + + // cache the sequence header if h264 + bool is_sequence_header = SrsFlvVideo::sh(msg->payload, msg->size); + if (is_sequence_header && (err = meta->update_vsh(msg)) != srs_success) { + return srs_error_wrap(err, "meta update video"); + } + + if ((err = format->on_video(msg)) != srs_success) { + return srs_error_wrap(err, "format consume video"); + } + + // Ignore if no format->vcodec, it means the codec is not parsed, or unsupport/unknown codec + // such as H.263 codec + if (!format->vcodec) { + return err; + } + + // support video codec: h264/h265 + SrsVideoCodecId vcodec = format->vcodec->id; + if (vcodec != SrsVideoCodecIdAVC && vcodec != SrsVideoCodecIdHEVC) { + return err; + } + + // Initialize video track on first packet with actual codec + if (!video_initialized_) { + if ((err = initialize_video_track(vcodec)) != srs_success) { + return srs_error_wrap(err, "init video track"); + } + video_initialized_ = true; + } + + bool has_idr = false; + vector samples; + if ((err = filter(msg, format, has_idr, samples)) != srs_success) { + return srs_error_wrap(err, "filter video"); + } + int nn_samples = (int)samples.size(); + + // Well, for each IDR, we append a SPS/PPS before it, which is packaged in STAP-A. + if (has_idr) { + SrsUniquePtr pkt(new SrsRtpPacket()); + + if ((err = package_stap_a(msg, pkt.get())) != srs_success) { + return srs_error_wrap(err, "package stap-a"); + } + + if ((err = bridge_->on_rtp(pkt.get())) != srs_success) { + return srs_error_wrap(err, "consume sps/pps"); + } + } + + // If merge Nalus, we pcakges all NALUs(samples) as one NALU, in a RTP or FUA packet. + vector pkts; + // auto free when exit + SrsUniquePtr> pkts_ptr(&pkts, free_packets); + + // By default, we package each NALU(sample) to a RTP or FUA packet. + for (int i = 0; i < nn_samples; i++) { + SrsSample* sample = samples[i]; + + if (sample->size <= kRtpMaxPayloadSize) { + if ((err = package_single_nalu(msg, sample, pkts)) != srs_success) { + return srs_error_wrap(err, "package single nalu"); + } + } else { + if ((err = package_fu_a(msg, sample, kRtpMaxPayloadSize, pkts)) != srs_success) { + return srs_error_wrap(err, "package fu-a"); + } + } + } + + if (!pkts.empty()) { + pkts.back()->header.set_marker(true); + } + + return consume_packets(pkts); +} + +srs_error_t SrsRtspRtpBuilder::filter(SrsSharedPtrMessage* msg, SrsFormat* format, bool& has_idr, vector& samples) +{ + srs_error_t err = srs_success; + + // If IDR, we will insert SPS/PPS before IDR frame. + if (format->video && format->video->has_idr) { + has_idr = true; + } + + // Update samples to shared frame. + for (int i = 0; i < format->video->nb_samples; ++i) { + SrsSample* sample = &format->video->samples[i]; + samples.push_back(sample); + } + + return err; +} + +srs_error_t SrsRtspRtpBuilder::package_stap_a(SrsSharedPtrMessage* msg, SrsRtpPacket* pkt) +{ + srs_error_t err = srs_success; + + SrsFormat* format = meta->vsh_format(); + if (!format || !format->vcodec) { + return err; + } + + return video_builder_->package_stap_a(msg, pkt); +} + +srs_error_t SrsRtspRtpBuilder::package_nalus(SrsSharedPtrMessage* msg, const vector& samples, vector& pkts) +{ + srs_error_t err = srs_success; + + SrsFormat* format = meta->vsh_format(); + if (!format || !format->vcodec) { + return err; + } + + return video_builder_->package_nalus(msg, samples, pkts); +} + +// Single NAL Unit Packet @see https://tools.ietf.org/html/rfc6184#section-5.6 +srs_error_t SrsRtspRtpBuilder::package_single_nalu(SrsSharedPtrMessage* msg, SrsSample* sample, vector& pkts) +{ + return video_builder_->package_single_nalu(msg, sample, pkts); +} + +srs_error_t SrsRtspRtpBuilder::package_fu_a(SrsSharedPtrMessage* msg, SrsSample* sample, int fu_payload_size, vector& pkts) +{ + srs_error_t err = srs_success; + + SrsFormat* format = meta->vsh_format(); + if (!format || !format->vcodec) { + return err; + } + + return video_builder_->package_fu_a(msg, sample, fu_payload_size, pkts); +} + +srs_error_t SrsRtspRtpBuilder::consume_packets(vector& pkts) +{ + srs_error_t err = srs_success; + + // TODO: FIXME: Consume a range of packets. + for (int i = 0; i < (int)pkts.size(); i++) { + SrsRtpPacket* pkt = pkts[i]; + if ((err = bridge_->on_rtp(pkt)) != srs_success) { + err = srs_error_wrap(err, "consume sps/pps"); + break; + } + } + + return err; +} + +SrsRtspSendTrack::SrsRtspSendTrack(SrsRtspConnection* session, SrsRtcTrackDescription* track_desc, bool is_audio) +{ + session_ = session; + track_desc_ = track_desc->copy(); +} + +SrsRtspSendTrack::~SrsRtspSendTrack() +{ + srs_freep(track_desc_); +} + +bool SrsRtspSendTrack::has_ssrc(uint32_t ssrc) +{ + return track_desc_->has_ssrc(ssrc); +} + +// TODO: FIXME: Should refine logs, set tracks in a time. +bool SrsRtspSendTrack::set_track_status(bool active) +{ + bool previous_status = track_desc_->is_active_; + track_desc_->is_active_ = active; + return previous_status; +} + +bool SrsRtspSendTrack::get_track_status() +{ + return track_desc_->is_active_; +} + +std::string SrsRtspSendTrack::get_track_id() +{ + return track_desc_->id_; +} + +SrsRtspAudioSendTrack::SrsRtspAudioSendTrack(SrsRtspConnection* session, SrsRtcTrackDescription* track_desc) + : SrsRtspSendTrack(session, track_desc, true) +{ +} + +SrsRtspAudioSendTrack::~SrsRtspAudioSendTrack() +{ +} + +srs_error_t SrsRtspAudioSendTrack::on_rtp(SrsRtpPacket* pkt) +{ + srs_error_t err = srs_success; + + if (!track_desc_->is_active_) { + return err; + } + + pkt->header.set_ssrc(track_desc_->ssrc_); + + // Should update PT, because subscriber may use different PT to publisher. + if (track_desc_->media_ && pkt->header.get_payload_type() == track_desc_->media_->pt_of_publisher_) { + // If PT is media from publisher, change to PT of media for subscriber. + pkt->header.set_payload_type(track_desc_->media_->pt_); + } else if (track_desc_->red_ && pkt->header.get_payload_type() == track_desc_->red_->pt_of_publisher_) { + // If PT is RED from publisher, change to PT of RED for subscriber. + pkt->header.set_payload_type(track_desc_->red_->pt_); + } else { + // TODO: FIXME: Should update PT for RTX. + } + + if ((err = session_->do_send_packet(pkt)) != srs_success) { + return srs_error_wrap(err, "raw send"); + } + + srs_info("RTSP: Send audio ssrc=%d, seqno=%d, keyframe=%d, ts=%u", pkt->header.get_ssrc(), + pkt->header.get_sequence(), pkt->is_keyframe(), pkt->header.get_timestamp()); + + return err; +} + +SrsRtspVideoSendTrack::SrsRtspVideoSendTrack(SrsRtspConnection* session, SrsRtcTrackDescription* track_desc) + : SrsRtspSendTrack(session, track_desc, false) +{ +} + +SrsRtspVideoSendTrack::~SrsRtspVideoSendTrack() +{ +} + +srs_error_t SrsRtspVideoSendTrack::on_rtp(SrsRtpPacket* pkt) +{ + srs_error_t err = srs_success; + + if (!track_desc_->is_active_) { + return err; + } + + pkt->header.set_ssrc(track_desc_->ssrc_); + + // Should update PT, because subscriber may use different PT to publisher. + if (track_desc_->media_ && pkt->header.get_payload_type() == track_desc_->media_->pt_of_publisher_) { + // If PT is media from publisher, change to PT of media for subscriber. + pkt->header.set_payload_type(track_desc_->media_->pt_); + } else if (track_desc_->red_ && pkt->header.get_payload_type() == track_desc_->red_->pt_of_publisher_) { + // If PT is RED from publisher, change to PT of RED for subscriber. + pkt->header.set_payload_type(track_desc_->red_->pt_); + } else { + // TODO: FIXME: Should update PT for RTX. + } + + if ((err = session_->do_send_packet(pkt)) != srs_success) { + return srs_error_wrap(err, "raw send"); + } + + srs_info("RTSP: Send video ssrc=%d, seqno=%d, keyframe=%d, ts=%u", pkt->header.get_ssrc(), + pkt->header.get_sequence(), pkt->is_keyframe(), pkt->header.get_timestamp()); + + return err; +} + diff --git a/trunk/src/app/srs_app_rtsp_source.hpp b/trunk/src/app/srs_app_rtsp_source.hpp new file mode 100644 index 000000000..11b6d0abe --- /dev/null +++ b/trunk/src/app/srs_app_rtsp_source.hpp @@ -0,0 +1,256 @@ +// +// Copyright (c) 2013-2025 The SRS Authors +// +// SPDX-License-Identifier: MIT +// + +#ifndef SRS_APP_RTSP_SOURCE_HPP +#define SRS_APP_RTSP_SOURCE_HPP + +#include + +#include +#include +#include + +#include +#include +#include + +class SrsRequest; +class SrsRtpPacket; +class SrsRtspSource; +class SrsRtspConsumer; +class SrsRtcTrackDescription; +class SrsRtcSourceDescription; +class SrsResourceManager; +class SrsRtspConnection; +class SrsRtpVideoBuilder; + +// The RTSP stream consumer, consume packets from RTSP stream source. +class SrsRtspConsumer +{ +private: + // Because source references to this object, so we should directly use the source ptr. + SrsRtspSource* source_; +private: + std::vector queue; + // when source id changed, notice all consumers + bool should_update_source_id; + // The cond wait for mw. + srs_cond_t mw_wait; + bool mw_waiting; + int mw_min_msgs; +private: + // The callback for stream change event. + ISrsRtcSourceChangeCallback* handler_; +public: + SrsRtspConsumer(SrsRtspSource* s); + virtual ~SrsRtspConsumer(); +public: + // When source id changed, notice client to print. + virtual void update_source_id(); + // Put RTP packet into queue. + // @note We do not drop packet here, but drop it in sender. + srs_error_t enqueue(SrsRtpPacket* pkt); + // For RTSP, we only got one packet, because there is not many packets in queue. + virtual srs_error_t dump_packet(SrsRtpPacket** ppkt); + // Wait for at-least some messages incoming in queue. + virtual void wait(int nb_msgs); +public: + void set_handler(ISrsRtcSourceChangeCallback* h) { handler_ = h; } // SrsRtspConsumer::set_handler() + void on_stream_change(SrsRtcSourceDescription* desc); +}; + +class SrsRtspSourceManager : public ISrsHourGlass +{ +private: + srs_mutex_t lock; + std::map< std::string, SrsSharedPtr > pool; + SrsHourGlass* timer_; +public: + SrsRtspSourceManager(); + virtual ~SrsRtspSourceManager(); +public: + virtual srs_error_t initialize(); +// interface ISrsHourGlass +private: + virtual srs_error_t setup_ticks(); + virtual srs_error_t notify(int event, srs_utime_t interval, srs_utime_t tick); +public: + // create source when fetch from cache failed. + // @param r the client request. + // @param pps the matched source, if success never be NULL. + virtual srs_error_t fetch_or_create(SrsRequest* r, SrsSharedPtr& pps); +public: + // Get the exists source, NULL when not exists. + virtual SrsSharedPtr fetch(SrsRequest* r); +}; + +// The global RTSP source manager. +extern SrsRtspSourceManager* _srs_rtsp_sources; + +extern SrsResourceManager* _srs_rtsp_manager; + +// A Source is a stream, to publish and to play with, binding to SrsRtspPlayStream. +class SrsRtspSource +{ +private: + // For publish, it's the publish client id. + // For edge, it's the edge ingest id. + // when source id changed, for example, the edge reconnect, + // invoke the on_source_changed() to let all clients know. + SrsContextId _source_id; + // previous source id. + SrsContextId _pre_source_id; + SrsRequest* req; + // Steam description for this steam. + SrsRtcTrackDescription* audio_desc_; + SrsRtcTrackDescription* video_desc_; +private: + // To delivery stream to clients. + std::vector consumers; + // Whether stream is created, that is, SDP is done. + bool is_created_; + // Whether stream is delivering data, that is, DTLS is done. + bool is_delivering_packets_; +private: + // The last die time, while die means neither publishers nor players. + srs_utime_t stream_die_at_; +public: + SrsRtspSource(); + virtual ~SrsRtspSource(); +public: + virtual srs_error_t initialize(SrsRequest* r); +public: + // Whether stream is dead, which is no publisher or player. + virtual bool stream_is_dead(); +public: + // Update the authentication information in request. + virtual void update_auth(SrsRequest* r); +private: + // The stream source changed. + virtual srs_error_t on_source_changed(); +public: + // Get current source id. + virtual SrsContextId source_id(); + virtual SrsContextId pre_source_id(); +public: + // Create consumer + // @param consumer, output the create consumer. + virtual srs_error_t create_consumer(SrsRtspConsumer*& consumer); + // Dumps packets in cache to consumer. + // @param ds, whether dumps the sequence header. + // @param dm, whether dumps the metadata. + // @param dg, whether dumps the gop cache. + virtual srs_error_t consumer_dumps(SrsRtspConsumer* consumer, bool ds = true, bool dm = true, bool dg = true); + virtual void on_consumer_destroy(SrsRtspConsumer* consumer); + // Whether we can publish stream to the source, return false if it exists. + // @remark Note that when SDP is done, we set the stream is not able to publish. + virtual bool can_publish(); + // For RTSP, the stream is created when SDP is done, and then do DTLS + virtual void set_stream_created(); + // When start publish stream. + virtual srs_error_t on_publish(); + // When stop publish stream. + virtual void on_unpublish(); +public: + // Consume the shared RTP packet, user must free it. + srs_error_t on_rtp(SrsRtpPacket* pkt); +public: + SrsRtcTrackDescription* audio_desc(); + void set_audio_desc(SrsRtcTrackDescription* audio_desc); + SrsRtcTrackDescription* video_desc(); + void set_video_desc(SrsRtcTrackDescription* video_desc); +}; + +// Convert AV frame to RTSP RTP packets. +class SrsRtspRtpBuilder +{ +private: + SrsRequest* req; + SrsFrameToRtspBridge* bridge_; + // The format, codec information. + SrsRtmpFormat* format; + // The metadata cache. + SrsMetaCache* meta; + // The video builder, convert frame to RTP packets. + SrsRtpVideoBuilder* video_builder_; +private: + uint16_t audio_sequence; + uint32_t audio_ssrc_; + uint8_t audio_payload_type_; + int audio_sample_rate_; +private: + SrsSharedPtr source_; + // Lazy initialization flags + bool audio_initialized_; + bool video_initialized_; +public: + SrsRtspRtpBuilder(SrsFrameToRtspBridge* bridge, SrsSharedPtr source); + virtual ~SrsRtspRtpBuilder(); +private: + // Lazy initialization methods + srs_error_t initialize_audio_track(SrsAudioCodecId codec); + srs_error_t initialize_video_track(SrsVideoCodecId codec); +public: + virtual srs_error_t initialize(SrsRequest* r); + virtual srs_error_t on_publish(); + virtual void on_unpublish(); + virtual srs_error_t on_frame(SrsSharedPtrMessage* frame); +private: + virtual srs_error_t on_audio(SrsSharedPtrMessage* msg); +private: + srs_error_t package_aac(SrsAudioFrame* audio, SrsRtpPacket* pkt); +private: + virtual srs_error_t on_video(SrsSharedPtrMessage* msg); +private: + srs_error_t filter(SrsSharedPtrMessage* msg, SrsFormat* format, bool& has_idr, std::vector& samples); + srs_error_t package_stap_a(SrsSharedPtrMessage* msg, SrsRtpPacket* pkt); + srs_error_t package_nalus(SrsSharedPtrMessage* msg, const std::vector& samples, std::vector& pkts); + srs_error_t package_single_nalu(SrsSharedPtrMessage* msg, SrsSample* sample, std::vector& pkts); + srs_error_t package_fu_a(SrsSharedPtrMessage* msg, SrsSample* sample, int fu_payload_size, std::vector& pkts); + srs_error_t consume_packets(std::vector& pkts); +}; + +class SrsRtspSendTrack +{ +public: + // send track description + SrsRtcTrackDescription* track_desc_; +protected: + // The owner connection for this track. + SrsRtspConnection* session_; +public: + SrsRtspSendTrack(SrsRtspConnection* session, SrsRtcTrackDescription* track_desc, bool is_audio); + virtual ~SrsRtspSendTrack(); +public: + // SrsRtspSendTrack::set_nack_no_copy + bool has_ssrc(uint32_t ssrc); + bool set_track_status(bool active); + bool get_track_status(); + std::string get_track_id(); +public: + virtual srs_error_t on_rtp(SrsRtpPacket* pkt) = 0; +}; + +class SrsRtspAudioSendTrack : public SrsRtspSendTrack +{ +public: + SrsRtspAudioSendTrack(SrsRtspConnection* session, SrsRtcTrackDescription* track_desc); + virtual ~SrsRtspAudioSendTrack(); +public: + virtual srs_error_t on_rtp(SrsRtpPacket* pkt); +}; + +class SrsRtspVideoSendTrack : public SrsRtspSendTrack +{ +public: + SrsRtspVideoSendTrack(SrsRtspConnection* session, SrsRtcTrackDescription* track_desc); + virtual ~SrsRtspVideoSendTrack(); +public: + virtual srs_error_t on_rtp(SrsRtpPacket* pkt); +}; + +#endif + diff --git a/trunk/src/app/srs_app_server.cpp b/trunk/src/app/srs_app_server.cpp index d1d5c7fbe..78e505f8d 100644 --- a/trunk/src/app/srs_app_server.cpp +++ b/trunk/src/app/srs_app_server.cpp @@ -48,6 +48,10 @@ using namespace std; #ifdef SRS_SRT #include #endif +#ifdef SRS_RTSP +#include +#include +#endif SrsSignalManager* SrsSignalManager::instance = NULL; @@ -341,6 +345,9 @@ SrsServer::SrsServer() http_listener_ = new SrsTcpListener(this); https_listener_ = new SrsTcpListener(this); webrtc_listener_ = new SrsTcpListener(this); +#ifdef SRS_RTSP + rtsp_listener_ = new SrsTcpListener(this); +#endif stream_caster_flv_listener_ = new SrsHttpFlvListener(); stream_caster_mpegts_ = new SrsUdpCasterListener(); exporter_listener_ = new SrsTcpListener(this); @@ -398,6 +405,9 @@ void SrsServer::destroy() srs_freep(http_listener_); srs_freep(https_listener_); srs_freep(webrtc_listener_); +#ifdef SRS_RTSP + srs_freep(rtsp_listener_); +#endif srs_freep(stream_caster_flv_listener_); srs_freep(stream_caster_mpegts_); srs_freep(exporter_listener_); @@ -417,6 +427,9 @@ void SrsServer::dispose() http_listener_->close(); https_listener_->close(); webrtc_listener_->close(); +#ifdef SRS_RTSP + rtsp_listener_->close(); +#endif stream_caster_flv_listener_->close(); stream_caster_mpegts_->close(); exporter_listener_->close(); @@ -448,6 +461,9 @@ void SrsServer::gracefully_dispose() http_listener_->close(); https_listener_->close(); webrtc_listener_->close(); +#ifdef SRS_RTSP + rtsp_listener_->close(); +#endif stream_caster_flv_listener_->close(); stream_caster_mpegts_->close(); exporter_listener_->close(); @@ -630,6 +646,16 @@ srs_error_t SrsServer::listen() } #endif +#ifdef SRS_RTSP + // Start RTSP listener. RTC is a critical dependency. + if (_srs_config->get_rtsp_server_enabled()) { + rtsp_listener_->set_endpoint(srs_int2str(_srs_config->get_rtsp_server_listen()))->set_label("RTSP"); + if ((err = rtsp_listener_->listen()) != srs_success) { + return srs_error_wrap(err, "rtsp listen"); + } + } +#endif + // Start all listeners for stream caster. std::vector confs = _srs_config->get_stream_casters(); for (vector::iterator it = confs.begin(); it != confs.end(); ++it) { @@ -838,6 +864,12 @@ srs_error_t SrsServer::start(SrsWaitGroup* wg) } #endif +#ifdef SRS_RTSP + if ((err = _srs_rtsp_sources->initialize()) != srs_success) { + return srs_error_wrap(err, "rtsp sources"); + } +#endif + if ((err = trd_->start()) != srs_success) { return srs_error_wrap(err, "start"); } @@ -1137,6 +1169,14 @@ void SrsServer::resample_kbps() continue; } +#ifdef SRS_RTSP + SrsRtspConnection* rtsp = dynamic_cast(c); + if (rtsp) { + stat->kbps_add_delta(c->get_id().c_str(), rtsp->delta()); + continue; + } +#endif + #ifdef SRS_RTC SrsRtcTcpConn* tcp = dynamic_cast(c); if (tcp) { @@ -1244,6 +1284,10 @@ srs_error_t SrsServer::do_on_tcp_client(ISrsListener* listener, srs_netfd_t& stf #ifdef SRS_RTC } else if (listener == webrtc_listener_) { resource = new SrsRtcTcpConn(new SrsTcpConnection(stfd2), ip, port); +#endif +#ifdef SRS_RTSP + } else if (listener == rtsp_listener_) { + resource = new SrsRtspConnection(this, new SrsTcpConnection(stfd2), ip, port); #endif } else if (listener == exporter_listener_) { // TODO: FIXME: Maybe should support https metrics. diff --git a/trunk/src/app/srs_app_server.hpp b/trunk/src/app/srs_app_server.hpp index 78e9b8b6c..801990458 100644 --- a/trunk/src/app/srs_app_server.hpp +++ b/trunk/src/app/srs_app_server.hpp @@ -130,6 +130,8 @@ private: SrsTcpListener* https_listener_; // WebRTC over TCP listener. Please note that there is always a UDP listener by RTC server. SrsTcpListener* webrtc_listener_; + // RTSP listener, over TCP. + SrsTcpListener* rtsp_listener_; // Stream Caster for push over HTTP-FLV. SrsHttpFlvListener* stream_caster_flv_listener_; // Stream Caster for push over MPEGTS-UDP diff --git a/trunk/src/app/srs_app_stream_bridge.cpp b/trunk/src/app/srs_app_stream_bridge.cpp index cc9bf0e89..41aba6e96 100644 --- a/trunk/src/app/srs_app_stream_bridge.cpp +++ b/trunk/src/app/srs_app_stream_bridge.cpp @@ -13,6 +13,9 @@ #include #include #include +#ifdef SRS_RTSP +#include +#endif #include using namespace std; @@ -134,6 +137,61 @@ srs_error_t SrsFrameToRtcBridge::on_rtp(SrsRtpPacket* pkt) #endif +#ifdef SRS_RTSP +SrsFrameToRtspBridge::SrsFrameToRtspBridge(SrsSharedPtr source) +{ + source_ = source; + + // Use lazy initialization - no need to determine codec/track parameters here + rtp_builder_ = new SrsRtspRtpBuilder(this, source); +} + +SrsFrameToRtspBridge::~SrsFrameToRtspBridge() +{ + srs_freep(rtp_builder_); +} + +srs_error_t SrsFrameToRtspBridge::initialize(SrsRequest* r) +{ + return rtp_builder_->initialize(r); +} + +srs_error_t SrsFrameToRtspBridge::on_publish() +{ + srs_error_t err = srs_success; + + // TODO: FIXME: Should sync with bridge? + if ((err = source_->on_publish()) != srs_success) { + return srs_error_wrap(err, "source publish"); + } + + if ((err = rtp_builder_->on_publish()) != srs_success) { + return srs_error_wrap(err, "rtp builder publish"); + } + + return err; +} + +void SrsFrameToRtspBridge::on_unpublish() +{ + rtp_builder_->on_unpublish(); + + // @remark This bridge might be disposed here, so never use it. + // TODO: FIXME: Should sync with bridge? + source_->on_unpublish(); +} + +srs_error_t SrsFrameToRtspBridge::on_frame(SrsSharedPtrMessage* frame) +{ + return rtp_builder_->on_frame(frame); +} + +srs_error_t SrsFrameToRtspBridge::on_rtp(SrsRtpPacket* pkt) +{ + return source_->on_rtp(pkt); +} +#endif + SrsCompositeBridge::SrsCompositeBridge() { } diff --git a/trunk/src/app/srs_app_stream_bridge.hpp b/trunk/src/app/srs_app_stream_bridge.hpp index dfa4857c8..5daf8981a 100644 --- a/trunk/src/app/srs_app_stream_bridge.hpp +++ b/trunk/src/app/srs_app_stream_bridge.hpp @@ -22,6 +22,8 @@ class SrsRtmpFormat; class SrsMetaCache; class SrsRtpPacket; class SrsRtcRtpBuilder; +class SrsRtspSource; +class SrsRtspRtpBuilder; // A stream bridge is used to convert stream via different protocols, such as bridge for RTMP and RTC. Generally, we use // frame as message for bridge. A frame is a audio or video frame, such as an I/B/P frame, a general frame for decoder. @@ -77,12 +79,34 @@ public: }; #endif +#ifdef SRS_RTSP +// A bridge to covert AV frame to RTSP stream. +class SrsFrameToRtspBridge : public ISrsStreamBridge +{ +private: + SrsSharedPtr source_; +private: + SrsRtspRtpBuilder* rtp_builder_; +public: + SrsFrameToRtspBridge(SrsSharedPtr source); + virtual ~SrsFrameToRtspBridge(); +public: + virtual srs_error_t initialize(SrsRequest* r); + virtual srs_error_t on_publish(); + virtual void on_unpublish(); + virtual srs_error_t on_frame(SrsSharedPtrMessage* frame); + srs_error_t on_rtp(SrsRtpPacket* pkt); +}; +#endif + // A bridge chain, a set of bridges. class SrsCompositeBridge : public ISrsStreamBridge { public: SrsCompositeBridge(); virtual ~SrsCompositeBridge(); +public: + bool empty() { return bridges_.empty(); } // SrsCompositeBridge::empty() public: srs_error_t initialize(SrsRequest* r); public: diff --git a/trunk/src/app/srs_app_threads.cpp b/trunk/src/app/srs_app_threads.cpp index 12e4587a6..aee7a39e8 100644 --- a/trunk/src/app/srs_app_threads.cpp +++ b/trunk/src/app/srs_app_threads.cpp @@ -28,6 +28,9 @@ #ifdef SRS_GB28181 #include #endif +#ifdef SRS_RTSP +#include +#endif #include #include @@ -331,6 +334,10 @@ srs_error_t srs_global_initialize() _srs_rtc_manager = new SrsResourceManager("RTC", true); _srs_rtc_dtls_certificate = new SrsDtlsCertificate(); #endif +#ifdef SRS_RTSP + _srs_rtsp_sources = new SrsRtspSourceManager(); + _srs_rtsp_manager = new SrsResourceManager("RTSP", true); +#endif #ifdef SRS_GB28181 _srs_gb_manager = new SrsResourceManager("GB", true); #endif diff --git a/trunk/src/core/srs_core_version7.hpp b/trunk/src/core/srs_core_version7.hpp index 74c774f4f..deb6c501d 100644 --- a/trunk/src/core/srs_core_version7.hpp +++ b/trunk/src/core/srs_core_version7.hpp @@ -9,6 +9,6 @@ #define VERSION_MAJOR 7 #define VERSION_MINOR 0 -#define VERSION_REVISION 46 +#define VERSION_REVISION 47 #endif \ No newline at end of file diff --git a/trunk/src/kernel/srs_kernel_codec.cpp b/trunk/src/kernel/srs_kernel_codec.cpp index 5ac35c6a9..9971a7dc3 100644 --- a/trunk/src/kernel/srs_kernel_codec.cpp +++ b/trunk/src/kernel/srs_kernel_codec.cpp @@ -908,7 +908,7 @@ srs_error_t SrsFormat::on_audio(int64_t timestamp, char* data, int size) uint8_t v = buffer->read_1bytes(); SrsAudioCodecId codec = (SrsAudioCodecId)((v >> 4) & 0x0f); - if (codec != SrsAudioCodecIdMP3 && codec != SrsAudioCodecIdAAC) { + if (codec != SrsAudioCodecIdMP3 && codec != SrsAudioCodecIdAAC && codec != SrsAudioCodecIdOpus) { return err; } @@ -929,9 +929,11 @@ srs_error_t SrsFormat::on_audio(int64_t timestamp, char* data, int size) if (codec == SrsAudioCodecIdMP3) { return audio_mp3_demux(buffer.get(), timestamp, fresh); + } else if (codec == SrsAudioCodecIdAAC) { + return audio_aac_demux(buffer.get(), timestamp); + } else { + return srs_error_new(ERROR_NOT_IMPLEMENTED, "opus demuxer not implemented"); } - - return audio_aac_demux(buffer.get(), timestamp); } srs_error_t SrsFormat::on_video(int64_t timestamp, char* data, int size) diff --git a/trunk/src/kernel/srs_kernel_error.hpp b/trunk/src/kernel/srs_kernel_error.hpp index afc7f904f..e19ac622f 100644 --- a/trunk/src/kernel/srs_kernel_error.hpp +++ b/trunk/src/kernel/srs_kernel_error.hpp @@ -108,7 +108,8 @@ XX(ERROR_SYSTEM_FILE_NOT_OPEN , 1095, "FileNotOpen", "File is not opened") \ XX(ERROR_SYSTEM_FILE_SETVBUF , 1096, "FileSetVBuf", "Failed to set file vbuf") \ XX(ERROR_NO_SOURCE , 1097, "NoSource", "No source found") \ - XX(ERROR_STREAM_DISPOSING , 1098, "StreamDisposing", "Stream is disposing") + XX(ERROR_STREAM_DISPOSING , 1098, "StreamDisposing", "Stream is disposing") \ + XX(ERROR_NOT_IMPLEMENTED , 1099, "NotImplemented", "Feature is not implemented") /**************************************************/ /* RTMP protocol error. */ @@ -155,12 +156,6 @@ XX(ERROR_OpenSslComputeSharedKey , 2039, "SslShareKey", "Failed to get shared key of SSL") \ XX(ERROR_RTMP_MIC_CHUNKSIZE_CHANGED , 2040, "RtmpMicChunk", "Invalid RTMP mic for chunk size changed") \ XX(ERROR_RTMP_MIC_CACHE_OVERFLOW , 2041, "RtmpMicCache", "Invalid RTMP mic for cache overflow") \ - XX(ERROR_RTSP_TOKEN_NOT_NORMAL , 2042, "RtspToken", "Invalid RTSP token state not normal") \ - XX(ERROR_RTSP_REQUEST_HEADER_EOF , 2043, "RtspHeaderEof", "Invalid RTSP request for header EOF") \ - XX(ERROR_RTP_HEADER_CORRUPT , 2044, "RtspHeaderCorrupt", "Invalid RTSP RTP packet for header corrupt") \ - XX(ERROR_RTP_TYPE96_CORRUPT , 2045, "RtspP96Corrupt", "Invalid RTSP RTP packet for P96 corrupt") \ - XX(ERROR_RTP_TYPE97_CORRUPT , 2046, "RtspP97Corrupt", "Invalid RTSP RTP packet for P97 corrupt") \ - XX(ERROR_RTSP_AUDIO_CONFIG , 2047, "RtspAudioConfig", "RTSP no audio sequence header config") \ XX(ERROR_RTMP_STREAM_NOT_FOUND , 2048, "StreamNotFound", "Request stream is not found") \ XX(ERROR_RTMP_CLIENT_NOT_FOUND , 2049, "ClientNotFound", "Request client is not found") \ XX(ERROR_OpenSslCreateHMAC , 2050, "SslCreateHmac", "Failed to create HMAC for SSL") \ @@ -340,7 +335,7 @@ /**************************************************/ -/* RTC protocol error. */ +/* RTC/RTSP protocol error. */ #define SRS_ERRNO_MAP_RTC(XX) \ XX(ERROR_RTC_PORT , 5000, "RtcPort", "Invalid RTC config for listen port") \ XX(ERROR_RTP_PACKET_CREATE , 5001, "RtcPacketCreate", "Failed to create RTP packet for RTC") \ @@ -379,7 +374,12 @@ XX(ERROR_RTC_TCP_STUN , 5034, "RtcTcpSession", "RTC TCP packet is invalid for session not found") \ XX(ERROR_RTC_TCP_UNIQUE , 5035, "RtcUnique", "RTC only support one UDP or TCP network") \ XX(ERROR_RTC_INVALID_SESSION , 5036, "RtcInvalidSession", "Invalid request for no RTC session matched") \ - XX(ERROR_RTC_INVALID_ICE , 5037, "RtcInvalidIce", "Invalid ICE ufrag or pwd") + XX(ERROR_RTC_INVALID_ICE , 5037, "RtcInvalidIce", "Invalid ICE ufrag or pwd") \ + XX(ERROR_RTSP_TRANSPORT_NOT_SUPPORTED , 5038, "RtspTransportNotSupported", "RTSP transport not supported, only TCP/interleaved mode is supported") \ + XX(ERROR_RTSP_NO_TRACK , 5039, "RtspNoTrack", "Drop RTSP packet for track not found") \ + XX(ERROR_RTSP_TOKEN_NOT_NORMAL , 5040, "RtspToken", "Invalid RTSP token state not normal") \ + XX(ERROR_RTSP_REQUEST_HEADER_EOF , 5041, "RtspHeaderEof", "Invalid RTSP request for header EOF") \ + XX(ERROR_RTSP_NEED_MORE_DATA , 5042, "RtspNeedMoreData", "Need more data to complete RTCP frame parsing") /**************************************************/ /* SRT protocol error. */ diff --git a/trunk/src/kernel/srs_kernel_rtc_rtp.hpp b/trunk/src/kernel/srs_kernel_rtc_rtp.hpp index 09d48008b..509886007 100644 --- a/trunk/src/kernel/srs_kernel_rtc_rtp.hpp +++ b/trunk/src/kernel/srs_kernel_rtc_rtp.hpp @@ -31,6 +31,16 @@ class SrsRtpPacket; // The RTP packet max size, should never exceed this size. const int kRtpPacketSize = 1500; +// The RTP payload max size, reserved some paddings for SRTP as such: +// kRtpPacketSize = kRtpMaxPayloadSize + paddings +// For example, if kRtpPacketSize is 1500, recommend to set kRtpMaxPayloadSize to 1400, +// which reserves 100 bytes for SRTP or paddings. +// otherwise, the kRtpPacketSize must less than MTU, in webrtc source code, +// the rtp max size is assigned by kVideoMtu = 1200. +// so we set kRtpMaxPayloadSize = 1200. +// see @doc https://groups.google.com/g/discuss-webrtc/c/gH5ysR3SoZI +const int kRtpMaxPayloadSize = kRtpPacketSize - 300; + const int kRtpHeaderFixedSize = 12; const uint8_t kRtpMarker = 0x80; diff --git a/trunk/src/protocol/srs_protocol_rtp.cpp b/trunk/src/protocol/srs_protocol_rtp.cpp new file mode 100644 index 000000000..b56e8adb2 --- /dev/null +++ b/trunk/src/protocol/srs_protocol_rtp.cpp @@ -0,0 +1,305 @@ +// +// Copyright (c) 2013-2025 The SRS Authors +// +// SPDX-License-Identifier: MIT +// + +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +SrsRtpVideoBuilder::SrsRtpVideoBuilder() +{ + video_sequence_ = 0; + video_ssrc_ = 0; + video_payload_type_ = 0; + format_ = NULL; +} + +SrsRtpVideoBuilder::~SrsRtpVideoBuilder() +{ +} + +srs_error_t SrsRtpVideoBuilder::initialize(SrsFormat* format, uint32_t ssrc, uint8_t payload_type) +{ + format_ = format; + video_ssrc_ = ssrc; + video_payload_type_ = payload_type; + return srs_success; +} + +srs_error_t SrsRtpVideoBuilder::package_stap_a(SrsSharedPtrMessage* msg, SrsRtpPacket* pkt) +{ + srs_error_t err = srs_success; + + SrsFormat* format = format_; + if (!format || !format->vcodec) { + return err; + } + + pkt->header.set_payload_type(video_payload_type_); + pkt->header.set_ssrc(video_ssrc_); + pkt->frame_type = SrsFrameTypeVideo; + pkt->header.set_marker(false); + pkt->header.set_sequence(video_sequence_++); + pkt->header.set_timestamp(msg->timestamp * 90); + + ISrsRtpPayloader* stap = NULL; + vector*> params; + int size = 0; + + if (format->vcodec->id == SrsVideoCodecIdHEVC) { + for (size_t i = 0; i < format->vcodec->hevc_dec_conf_record_.nalu_vec.size(); i++) { + const SrsHevcHvccNalu& nalu = format->vcodec->hevc_dec_conf_record_.nalu_vec[i]; + if (nalu.nal_unit_type == SrsHevcNaluType_VPS + || nalu.nal_unit_type == SrsHevcNaluType_SPS + || nalu.nal_unit_type == SrsHevcNaluType_PPS) { + const SrsHevcNalData& nal_data = nalu.nal_data_vec[0]; + params.push_back(&(vector&)nal_data.nal_unit_data); + size += nal_data.nal_unit_length; + } + } + + stap = new SrsRtpSTAPPayloadHevc(); + pkt->set_payload(stap, SrsRtpPacketPayloadTypeSTAPHevc); + pkt->nalu_type = kStapHevc; + } else if (format->vcodec->id == SrsVideoCodecIdAVC) { + params.push_back(&format->vcodec->sequenceParameterSetNALUnit); + params.push_back(&format->vcodec->pictureParameterSetNALUnit); + size = format->vcodec->sequenceParameterSetNALUnit.size() + format->vcodec->pictureParameterSetNALUnit.size(); + + stap = new SrsRtpSTAPPayload(); + pkt->set_payload(stap, SrsRtpPacketPayloadTypeSTAP); + pkt->nalu_type = kStapA; + } + + if (size == 0) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "vps/sps/pps empty"); + } + char* payload = pkt->wrap(size); + + for (vector*>::iterator it = params.begin(); it != params.end(); ++it) { + vector* param = *it; + SrsSample* sample = new SrsSample(); + sample->bytes = payload; + sample->size = param->size(); + if (format->vcodec->id == SrsVideoCodecIdHEVC) { + static_cast(stap)->nalus.push_back(sample); + } else { + static_cast(stap)->nalus.push_back(sample); + } + + memcpy(payload, (char*)param->data(), param->size()); + payload += (int)param->size(); + } + + return err; +} + +srs_error_t SrsRtpVideoBuilder::package_nalus(SrsSharedPtrMessage* msg, const vector& samples, vector& pkts) +{ + srs_error_t err = srs_success; + + SrsFormat* format = format_; + if (!format || !format->vcodec) { + return err; + } + bool is_hevc = format->vcodec->id == SrsVideoCodecIdHEVC; + + SrsRtpRawNALUs* raw_raw = new SrsRtpRawNALUs(); + uint8_t first_nalu_type = 0; + + for (int i = 0; i < (int)samples.size(); i++) { + SrsSample* sample = samples[i]; + + if (!sample->size) { + continue; + } + + if (first_nalu_type == 0) { + first_nalu_type = is_hevc ? uint8_t(SrsHevcNaluTypeParse(sample->bytes[0])) : uint8_t(SrsAvcNaluTypeParse(sample->bytes[0])); + } + + raw_raw->push_back(sample->copy()); + } + + // Ignore empty. + int nn_bytes = raw_raw->nb_bytes(); + if (nn_bytes <= 0) { + srs_freep(raw_raw); + return err; + } + + if (nn_bytes < kRtpMaxPayloadSize) { + // Package NALUs in a single RTP packet. + SrsRtpPacket* pkt = new SrsRtpPacket(); + pkts.push_back(pkt); + + pkt->header.set_payload_type(video_payload_type_); + pkt->header.set_ssrc(video_ssrc_); + pkt->frame_type = SrsFrameTypeVideo; + pkt->nalu_type = first_nalu_type; + pkt->header.set_sequence(video_sequence_++); + pkt->header.set_timestamp(msg->timestamp * 90); + pkt->set_payload(raw_raw, SrsRtpPacketPayloadTypeNALU); + pkt->wrap(msg); + } else { + // We must free it, should never use RTP packets to free it, + // because more than one RTP packet will refer to it. + SrsUniquePtr raw(raw_raw); + + int header_size = is_hevc ? SrsHevcNaluHeaderSize : SrsAvcNaluHeaderSize; + + // Package NALUs in FU-A RTP packets. + int fu_payload_size = kRtpMaxPayloadSize; + + // The first byte is store in FU-A header. + uint8_t header = raw->skip_bytes(header_size); + + int nb_left = nn_bytes - header_size; + + int num_of_packet = 1 + (nn_bytes - 1) / fu_payload_size; + for (int i = 0; i < num_of_packet; ++i) { + int packet_size = srs_min(nb_left, fu_payload_size); + + SrsRtpPacket* pkt = new SrsRtpPacket(); + pkts.push_back(pkt); + + pkt->header.set_payload_type(video_payload_type_); + pkt->header.set_ssrc(video_ssrc_); + pkt->frame_type = SrsFrameTypeVideo; + pkt->nalu_type = kFuA; + pkt->header.set_sequence(video_sequence_++); + pkt->header.set_timestamp(msg->timestamp * 90); + + if (is_hevc) { + SrsRtpFUAPayloadHevc* fua = new SrsRtpFUAPayloadHevc(); + if ((err = raw->read_samples(fua->nalus, packet_size)) != srs_success) { + srs_freep(fua); + return srs_error_wrap(err, "read hevc samples %d bytes, left %d, total %d", packet_size, nb_left, nn_bytes); + } + fua->nalu_type = SrsHevcNaluTypeParse(header); + fua->start = bool(i == 0); + fua->end = bool(i == num_of_packet - 1); + + pkt->set_payload(fua, SrsRtpPacketPayloadTypeFUAHevc); + } else { + SrsRtpFUAPayload* fua = new SrsRtpFUAPayload(); + if ((err = raw->read_samples(fua->nalus, packet_size)) != srs_success) { + srs_freep(fua); + return srs_error_wrap(err, "read samples %d bytes, left %d, total %d", packet_size, nb_left, nn_bytes); + } + fua->nalu_type = SrsAvcNaluTypeParse(header); + fua->start = bool(i == 0); + fua->end = bool(i == num_of_packet - 1); + + pkt->set_payload(fua, SrsRtpPacketPayloadTypeFUA); + } + + pkt->wrap(msg); + + nb_left -= packet_size; + } + } + + return err; +} + +// Single NAL Unit Packet @see https://tools.ietf.org/html/rfc6184#section-5.6 +srs_error_t SrsRtpVideoBuilder::package_single_nalu(SrsSharedPtrMessage* msg, SrsSample* sample, vector& pkts) +{ + srs_error_t err = srs_success; + + SrsRtpPacket* pkt = new SrsRtpPacket(); + pkts.push_back(pkt); + + pkt->header.set_payload_type(video_payload_type_); + pkt->header.set_ssrc(video_ssrc_); + pkt->frame_type = SrsFrameTypeVideo; + pkt->header.set_sequence(video_sequence_++); + pkt->header.set_timestamp(msg->timestamp * 90); + + SrsRtpRawPayload* raw = new SrsRtpRawPayload(); + pkt->set_payload(raw, SrsRtpPacketPayloadTypeRaw); + + raw->payload = sample->bytes; + raw->nn_payload = sample->size; + + pkt->wrap(msg); + + return err; +} + +srs_error_t SrsRtpVideoBuilder::package_fu_a(SrsSharedPtrMessage* msg, SrsSample* sample, int fu_payload_size, vector& pkts) +{ + srs_error_t err = srs_success; + + SrsFormat* format = format_; + if (!format || !format->vcodec) { + return err; + } + + bool is_hevc = format->vcodec->id == SrsVideoCodecIdHEVC; + int header_size = is_hevc ? SrsHevcNaluHeaderSize : SrsAvcNaluHeaderSize; + srs_assert(sample->size >= header_size); + + char* p = sample->bytes + header_size; + int nb_left = sample->size - header_size; + uint8_t header = sample->bytes[0]; + + int num_of_packet = 1 + (nb_left - 1) / fu_payload_size; + for (int i = 0; i < num_of_packet; ++i) { + int packet_size = srs_min(nb_left, fu_payload_size); + + SrsRtpPacket* pkt = new SrsRtpPacket(); + pkts.push_back(pkt); + + pkt->header.set_payload_type(video_payload_type_); + pkt->header.set_ssrc(video_ssrc_); + pkt->frame_type = SrsFrameTypeVideo; + pkt->header.set_sequence(video_sequence_++); + pkt->header.set_timestamp(msg->timestamp * 90); + pkt->nalu_type = is_hevc ? kFuHevc : kFuA; + + if (is_hevc) { + // H265 FU-A header + SrsRtpFUAPayloadHevc2* fua = new SrsRtpFUAPayloadHevc2(); + pkt->set_payload(fua, SrsRtpPacketPayloadTypeFUAHevc2); + + fua->nalu_type = SrsHevcNaluTypeParse(header); + fua->start = bool(i == 0); + fua->end = bool(i == num_of_packet - 1); + + fua->payload = p; + fua->size = packet_size; + } else { + // H264 FU-A header + SrsRtpFUAPayload2* fua = new SrsRtpFUAPayload2(); + pkt->set_payload(fua, SrsRtpPacketPayloadTypeFUA2); + + fua->nri = (SrsAvcNaluType)header; + fua->nalu_type = SrsAvcNaluTypeParse(header); + fua->start = bool(i == 0); + fua->end = bool(i == num_of_packet - 1); + + fua->payload = p; + fua->size = packet_size; + } + + pkt->wrap(msg); + + p += packet_size; + nb_left -= packet_size; + } + + return err; +} + diff --git a/trunk/src/protocol/srs_protocol_rtp.hpp b/trunk/src/protocol/srs_protocol_rtp.hpp new file mode 100644 index 000000000..c6e4211d8 --- /dev/null +++ b/trunk/src/protocol/srs_protocol_rtp.hpp @@ -0,0 +1,43 @@ +// +// Copyright (c) 2013-2025 The SRS Authors +// +// SPDX-License-Identifier: MIT +// + +#ifndef SRS_PROTOCOL_RTP_HPP +#define SRS_PROTOCOL_RTP_HPP + +#include + +#include + +#include +#include +#include +#include + +class SrsSharedPtrMessage; +class SrsSample; +class SrsRtpPacket; +class SrsFormat; + +// RTP video builder for packaging video NALUs into RTP packets +class SrsRtpVideoBuilder +{ +private: + uint16_t video_sequence_; + uint32_t video_ssrc_; + uint8_t video_payload_type_; + SrsFormat* format_; +public: + SrsRtpVideoBuilder(); + virtual ~SrsRtpVideoBuilder(); +public: + srs_error_t initialize(SrsFormat* format, uint32_t ssrc, uint8_t payload_type); + srs_error_t package_stap_a(SrsSharedPtrMessage* msg, SrsRtpPacket* pkt); + srs_error_t package_nalus(SrsSharedPtrMessage* msg, const std::vector& samples, std::vector& pkts); + srs_error_t package_single_nalu(SrsSharedPtrMessage* msg, SrsSample* sample, std::vector& pkts); + srs_error_t package_fu_a(SrsSharedPtrMessage* msg, SrsSample* sample, int fu_payload_size, std::vector& pkts); +}; + +#endif diff --git a/trunk/src/protocol/srs_protocol_rtsp_stack.cpp b/trunk/src/protocol/srs_protocol_rtsp_stack.cpp new file mode 100644 index 000000000..78994d805 --- /dev/null +++ b/trunk/src/protocol/srs_protocol_rtsp_stack.cpp @@ -0,0 +1,750 @@ +// +// Copyright (c) 2013-2025 The SRS Authors +// +// SPDX-License-Identifier: MIT +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +using namespace std; + +#define SRS_RTSP_BUFFER 4096 + +// Forward declaration of RTCP detection function +extern bool srs_is_rtcp(const uint8_t* data, size_t len); + +// get the status text of code. +string srs_generate_rtsp_status_text(int status) +{ + static std::map _status_map; + if (_status_map.empty()) { + _status_map[SRS_CONSTS_RTSP_Continue] = SRS_CONSTS_RTSP_Continue_str; + _status_map[SRS_CONSTS_RTSP_OK] = SRS_CONSTS_RTSP_OK_str; + _status_map[SRS_CONSTS_RTSP_Created] = SRS_CONSTS_RTSP_Created_str; + _status_map[SRS_CONSTS_RTSP_LowOnStorageSpace] = SRS_CONSTS_RTSP_LowOnStorageSpace_str; + _status_map[SRS_CONSTS_RTSP_MultipleChoices] = SRS_CONSTS_RTSP_MultipleChoices_str; + _status_map[SRS_CONSTS_RTSP_MovedPermanently] = SRS_CONSTS_RTSP_MovedPermanently_str; + _status_map[SRS_CONSTS_RTSP_MovedTemporarily] = SRS_CONSTS_RTSP_MovedTemporarily_str; + _status_map[SRS_CONSTS_RTSP_SeeOther] = SRS_CONSTS_RTSP_SeeOther_str; + _status_map[SRS_CONSTS_RTSP_NotModified] = SRS_CONSTS_RTSP_NotModified_str; + _status_map[SRS_CONSTS_RTSP_UseProxy] = SRS_CONSTS_RTSP_UseProxy_str; + _status_map[SRS_CONSTS_RTSP_BadRequest] = SRS_CONSTS_RTSP_BadRequest_str; + _status_map[SRS_CONSTS_RTSP_Unauthorized] = SRS_CONSTS_RTSP_Unauthorized_str; + _status_map[SRS_CONSTS_RTSP_PaymentRequired] = SRS_CONSTS_RTSP_PaymentRequired_str; + _status_map[SRS_CONSTS_RTSP_Forbidden] = SRS_CONSTS_RTSP_Forbidden_str; + _status_map[SRS_CONSTS_RTSP_NotFound] = SRS_CONSTS_RTSP_NotFound_str; + _status_map[SRS_CONSTS_RTSP_MethodNotAllowed] = SRS_CONSTS_RTSP_MethodNotAllowed_str; + _status_map[SRS_CONSTS_RTSP_NotAcceptable] = SRS_CONSTS_RTSP_NotAcceptable_str; + _status_map[SRS_CONSTS_RTSP_ProxyAuthenticationRequired] = SRS_CONSTS_RTSP_ProxyAuthenticationRequired_str; + _status_map[SRS_CONSTS_RTSP_RequestTimeout] = SRS_CONSTS_RTSP_RequestTimeout_str; + _status_map[SRS_CONSTS_RTSP_Gone] = SRS_CONSTS_RTSP_Gone_str; + _status_map[SRS_CONSTS_RTSP_LengthRequired] = SRS_CONSTS_RTSP_LengthRequired_str; + _status_map[SRS_CONSTS_RTSP_PreconditionFailed] = SRS_CONSTS_RTSP_PreconditionFailed_str; + _status_map[SRS_CONSTS_RTSP_RequestEntityTooLarge] = SRS_CONSTS_RTSP_RequestEntityTooLarge_str; + _status_map[SRS_CONSTS_RTSP_RequestURITooLarge] = SRS_CONSTS_RTSP_RequestURITooLarge_str; + _status_map[SRS_CONSTS_RTSP_UnsupportedMediaType] = SRS_CONSTS_RTSP_UnsupportedMediaType_str; + _status_map[SRS_CONSTS_RTSP_ParameterNotUnderstood] = SRS_CONSTS_RTSP_ParameterNotUnderstood_str; + _status_map[SRS_CONSTS_RTSP_ConferenceNotFound] = SRS_CONSTS_RTSP_ConferenceNotFound_str; + _status_map[SRS_CONSTS_RTSP_NotEnoughBandwidth] = SRS_CONSTS_RTSP_NotEnoughBandwidth_str; + _status_map[SRS_CONSTS_RTSP_SessionNotFound] = SRS_CONSTS_RTSP_SessionNotFound_str; + _status_map[SRS_CONSTS_RTSP_MethodNotValidInThisState] = SRS_CONSTS_RTSP_MethodNotValidInThisState_str; + _status_map[SRS_CONSTS_RTSP_HeaderFieldNotValidForResource] = SRS_CONSTS_RTSP_HeaderFieldNotValidForResource_str; + _status_map[SRS_CONSTS_RTSP_InvalidRange] = SRS_CONSTS_RTSP_InvalidRange_str; + _status_map[SRS_CONSTS_RTSP_ParameterIsReadOnly] = SRS_CONSTS_RTSP_ParameterIsReadOnly_str; + _status_map[SRS_CONSTS_RTSP_AggregateOperationNotAllowed] = SRS_CONSTS_RTSP_AggregateOperationNotAllowed_str; + _status_map[SRS_CONSTS_RTSP_OnlyAggregateOperationAllowed] = SRS_CONSTS_RTSP_OnlyAggregateOperationAllowed_str; + _status_map[SRS_CONSTS_RTSP_UnsupportedTransport] = SRS_CONSTS_RTSP_UnsupportedTransport_str; + _status_map[SRS_CONSTS_RTSP_DestinationUnreachable] = SRS_CONSTS_RTSP_DestinationUnreachable_str; + _status_map[SRS_CONSTS_RTSP_InternalServerError] = SRS_CONSTS_RTSP_InternalServerError_str; + _status_map[SRS_CONSTS_RTSP_NotImplemented] = SRS_CONSTS_RTSP_NotImplemented_str; + _status_map[SRS_CONSTS_RTSP_BadGateway] = SRS_CONSTS_RTSP_BadGateway_str; + _status_map[SRS_CONSTS_RTSP_ServiceUnavailable] = SRS_CONSTS_RTSP_ServiceUnavailable_str; + _status_map[SRS_CONSTS_RTSP_GatewayTimeout] = SRS_CONSTS_RTSP_GatewayTimeout_str; + _status_map[SRS_CONSTS_RTSP_RTSPVersionNotSupported] = SRS_CONSTS_RTSP_RTSPVersionNotSupported_str; + _status_map[SRS_CONSTS_RTSP_OptionNotSupported] = SRS_CONSTS_RTSP_OptionNotSupported_str; + } + + std::string status_text; + if (_status_map.find(status) == _status_map.end()) { + status_text = "Status Unknown"; + } else { + status_text = _status_map[status]; + } + + return status_text; +} + +std::string srs_generate_rtsp_method_str(SrsRtspMethod method) +{ + switch (method) { + case SrsRtspMethodDescribe: return SRS_RTSP_METHOD_DESCRIBE; + case SrsRtspMethodAnnounce: return SRS_RTSP_METHOD_ANNOUNCE; + case SrsRtspMethodGetParameter: return SRS_RTSP_METHOD_GET_PARAMETER; + case SrsRtspMethodOptions: return SRS_RTSP_METHOD_OPTIONS; + case SrsRtspMethodPause: return SRS_RTSP_METHOD_PAUSE; + case SrsRtspMethodPlay: return SRS_RTSP_METHOD_PLAY; + case SrsRtspMethodRecord: return SRS_RTSP_METHOD_RECORD; + case SrsRtspMethodRedirect: return SRS_RTSP_METHOD_REDIRECT; + case SrsRtspMethodSetup: return SRS_RTSP_METHOD_SETUP; + case SrsRtspMethodSetParameter: return SRS_RTSP_METHOD_SET_PARAMETER; + case SrsRtspMethodTeardown: return SRS_RTSP_METHOD_TEARDOWN; + default: return "Unknown"; + } +} + +SrsRtspTransport::SrsRtspTransport() +{ + client_port_min = 0; + client_port_max = 0; + interleaved_min = 0; + interleaved_max = 0; +} + +SrsRtspTransport::~SrsRtspTransport() +{ +} + +srs_error_t SrsRtspTransport::parse(string attr) +{ + srs_error_t err = srs_success; + + size_t pos = string::npos; + std::string token = attr; + + while (!token.empty()) { + std::string item = token; + if ((pos = item.find(";")) != string::npos) { + item = token.substr(0, pos); + token = token.substr(pos + 1); + } else { + token = ""; + } + + std::string item_key = item, item_value; + if ((pos = item.find("=")) != string::npos) { + item_key = item.substr(0, pos); + item_value = item.substr(pos + 1); + } + + if (transport.empty() && item.find("=") == string::npos && item_key != "unicast" && item_key != "multicast") { + transport = item_key; + if ((pos = transport.find("/")) != string::npos) { + profile = transport.substr(pos + 1); + transport = transport.substr(0, pos); + } + if ((pos = profile.find("/")) != string::npos) { + lower_transport = profile.substr(pos + 1); + profile = profile.substr(0, pos); + } + } + + if (item_key == "unicast" || item_key == "multicast") { + cast_type = item_key; + } else if (item_key == "interleaved") { + interleaved = item_value; + if ((pos = interleaved.find("-")) != string::npos) { + interleaved_min = ::atoi(interleaved.substr(0, pos).c_str()); + interleaved_max = ::atoi(interleaved.substr(pos + 1).c_str()); + } + } else if (item_key == "mode") { + mode = item_value; + } else if (item_key == "client_port") { + std::string sport = item_value; + std::string eport = item_value; + if ((pos = eport.find("-")) != string::npos) { + sport = eport.substr(0, pos); + eport = eport.substr(pos + 1); + } + client_port_min = ::atoi(sport.c_str()); + client_port_max = ::atoi(eport.c_str()); + } + } + + return err; +} + +void SrsRtspTransport::copy(SrsRtspTransport *src) +{ + transport = src->transport; + profile = src->profile; + lower_transport = src->lower_transport; + cast_type = src->cast_type; + interleaved = src->interleaved; + mode = src->mode; +} + +SrsRtspRequest::SrsRtspRequest() +{ + seq = 0; + content_length = 0; + stream_id = 0; + transport = NULL; +} + +SrsRtspRequest::~SrsRtspRequest() +{ + srs_freep(transport); +} + +bool SrsRtspRequest::is_options() +{ + return method == SRS_RTSP_METHOD_OPTIONS; +} + +bool SrsRtspRequest::is_describe() +{ + return method == SRS_RTSP_METHOD_DESCRIBE; +} + +bool SrsRtspRequest::is_setup() +{ + return method == SRS_RTSP_METHOD_SETUP; +} + +bool SrsRtspRequest::is_play() +{ + return method == SRS_RTSP_METHOD_PLAY; +} + +bool SrsRtspRequest::is_teardown() +{ + return method == SRS_RTSP_METHOD_TEARDOWN; +} + +SrsRtspResponse::SrsRtspResponse(int cseq) +{ + seq = cseq; + status = SRS_CONSTS_RTSP_OK; +} + +SrsRtspResponse::~SrsRtspResponse() +{ +} + +srs_error_t SrsRtspResponse::encode(stringstream& ss) +{ + srs_error_t err = srs_success; + + // status line + ss << SRS_RTSP_VERSION << SRS_RTSP_SP + << status << SRS_RTSP_SP + << srs_generate_rtsp_status_text(status) << SRS_RTSP_CRLF; + + // cseq + ss << SRS_RTSP_TOKEN_CSEQ << ":" << SRS_RTSP_SP << seq << SRS_RTSP_CRLF; + + // others. + ss << "Cache-Control: no-store" << SRS_RTSP_CRLF + << "Pragma: no-cache" << SRS_RTSP_CRLF + << "Server: " << RTMP_SIG_SRS_SERVER << SRS_RTSP_CRLF; + + // session if specified. + if (!session.empty()) { + ss << SRS_RTSP_TOKEN_SESSION << ":" << SRS_RTSP_SP << session << SRS_RTSP_CRLF; + } + + if ((err = encode_header(ss)) != srs_success) { + return srs_error_wrap(err, "encode header"); + }; + + // header EOF. + ss << SRS_RTSP_CRLF; + + return err; +} + +srs_error_t SrsRtspResponse::encode_header(std::stringstream& ss) +{ + return srs_success; +} + +SrsRtspOptionsResponse::SrsRtspOptionsResponse(int cseq) : SrsRtspResponse(cseq) +{ + methods = (SrsRtspMethod)(SrsRtspMethodDescribe | SrsRtspMethodOptions + | SrsRtspMethodPlay | SrsRtspMethodSetup | SrsRtspMethodTeardown); +} + +SrsRtspOptionsResponse::~SrsRtspOptionsResponse() +{ +} + +srs_error_t SrsRtspOptionsResponse::encode_header(stringstream& ss) +{ + static const SrsRtspMethod rtsp_methods[] = { + SrsRtspMethodDescribe, + SrsRtspMethodGetParameter, + SrsRtspMethodOptions, + SrsRtspMethodPause, + SrsRtspMethodPlay, + SrsRtspMethodRedirect, + SrsRtspMethodSetup, + SrsRtspMethodSetParameter, + SrsRtspMethodTeardown, + }; + + ss << SRS_RTSP_TOKEN_PUBLIC << ":" << SRS_RTSP_SP; + + bool appended = false; + int nb_methods = (int)(sizeof(rtsp_methods) / sizeof(SrsRtspMethod)); + for (int i = 0; i < nb_methods; i++) { + SrsRtspMethod method = rtsp_methods[i]; + if (((int)methods & (int)method) == 0) { + continue; + } + + if (appended) { + ss << ", "; + } + ss << srs_generate_rtsp_method_str(method); + appended = true; + } + ss << SRS_RTSP_CRLF; + + return srs_success; +} + +SrsRtspDescribeResponse::SrsRtspDescribeResponse(int cseq) : SrsRtspResponse(cseq) +{ +} + +SrsRtspDescribeResponse::~SrsRtspDescribeResponse() +{ +} + +srs_error_t SrsRtspDescribeResponse::encode_header(stringstream& ss) +{ + ss << SRS_RTSP_TOKEN_CONTENT_TYPE << ":" << SRS_RTSP_SP << "application/sdp" << SRS_RTSP_CRLF; + // WILL add CRLF to the end of sdp in SrsRtspResponse::encode, so add 2. + ss << SRS_RTSP_TOKEN_CONTENT_LENGTH << ":" << SRS_RTSP_SP << sdp.length() + 2 << SRS_RTSP_CRLF; + ss << SRS_RTSP_CRLF; + ss << sdp; + return srs_success; +} + +SrsRtspSetupResponse::SrsRtspSetupResponse(int seq) : SrsRtspResponse(seq) +{ + transport = new SrsRtspTransport(); + local_port_min = 0; + local_port_max = 0; + + client_port_min = 0; + client_port_max = 0; +} + +SrsRtspSetupResponse::~SrsRtspSetupResponse() +{ + srs_freep(transport); +} + +srs_error_t SrsRtspSetupResponse::encode_header(stringstream& ss) +{ + ss << SRS_RTSP_TOKEN_TRANSPORT << ":" << SRS_RTSP_SP; + ss << transport->transport << "/" << transport->profile; + if (!transport->lower_transport.empty()) { + ss << "/" << transport->lower_transport; + } + + if (!transport->cast_type.empty()) { + ss << ";" << transport->cast_type; + } + + if (!transport->interleaved.empty()) { + ss << ";interleaved=" << transport->interleaved; + } + + if (transport->lower_transport != "TCP") { + ss << ";client_port=" << client_port_min << "-" << client_port_max; + ss << ";server_port=" << local_port_min << "-" << local_port_max; + } + + ss << ";ssrc=" << ssrc << ";mode=\"play\""; + + ss << SRS_RTSP_CRLF; + + return srs_success; +} + +SrsRtspPlayResponse::SrsRtspPlayResponse(int cseq) : SrsRtspResponse(cseq) +{ +} + +SrsRtspPlayResponse::~SrsRtspPlayResponse() +{ +} + +srs_error_t SrsRtspPlayResponse::encode_header(stringstream& ss) +{ + return srs_success; +} + +SrsRtspStack::SrsRtspStack(ISrsProtocolReadWriter* s) +{ + buf = new SrsSimpleStream(); + skt = s; +} + +SrsRtspStack::~SrsRtspStack() +{ + srs_freep(buf); +} + +srs_error_t SrsRtspStack::recv_message(SrsRtspRequest** preq) +{ + srs_error_t err = srs_success; + + SrsRtspRequest* req = new SrsRtspRequest(); + if ((err = do_recv_message(req)) != srs_success) { + srs_freep(req); + return srs_error_wrap(err, "recv message"); + } + + *preq = req; + + return err; +} + +srs_error_t SrsRtspStack::send_message(SrsRtspResponse* res) +{ + srs_error_t err = srs_success; + + std::stringstream ss; + // encode the message to string. + if ((err = res->encode(ss)) != srs_success) { + return srs_error_wrap(err, "encode message"); + } + + std::string str = ss.str(); + srs_assert(!str.empty()); + + if ((err = skt->write((char*)str.c_str(), (int)str.length(), NULL)) != srs_success) { + return srs_error_wrap(err, "write message"); + } + + return err; +} + +srs_error_t SrsRtspStack::do_recv_message(SrsRtspRequest* req) +{ + srs_error_t err = srs_success; + + // Parse RTSP request line: "METHOD URI VERSION" + // Example: "PLAY rtsp://example.com/stream RTSP/1.0" + + // Parse the RTSP method (PLAY, SETUP, DESCRIBE, etc.) + if ((err = recv_token_normal(req->method)) != srs_success) { + return srs_error_wrap(err, "method"); + } + + // Parse the request URI (resource path or full URL) + if ((err = recv_token_normal(req->uri)) != srs_success) { + return srs_error_wrap(err, "uri"); + } + + // Parse the RTSP version (typically "RTSP/1.0") + if ((err = recv_token_eof(req->version)) != srs_success) { + return srs_error_wrap(err, "version"); + } + + // Parse RTSP headers in "Name: Value" format + // Example headers: + // CSeq: 1 + // Content-Type: application/sdp + // Content-Length: 460 + // Transport: RTP/AVP;unicast;client_port=8000-8001 + // Session: 12345678 + for (;;) { + // Parse the header name (before the colon) + std::string token; + if ((err = recv_token_normal(token)) != srs_success) { + if (srs_error_code(err) == ERROR_RTSP_REQUEST_HEADER_EOF) { + srs_error_reset(err); + break; // End of headers reached (empty line) + } + return srs_error_wrap(err, "recv token"); + } + + // Parse the header value (after the colon) based on header name + if (token == SRS_RTSP_TOKEN_CSEQ) { + // CSeq: sequence number for request/response matching + std::string seq; + if ((err = recv_token_eof(seq)) != srs_success) { + return srs_error_wrap(err, "seq"); + } + req->seq = ::atoll(seq.c_str()); + } else if (token == SRS_RTSP_TOKEN_CONTENT_TYPE) { + // Content-Type: MIME type of the message body (e.g., application/sdp) + std::string ct; + if ((err = recv_token_eof(ct)) != srs_success) { + return srs_error_wrap(err, "ct"); + } + req->content_type = ct; + } else if (token == SRS_RTSP_TOKEN_CONTENT_LENGTH) { + // Content-Length: size of the message body in bytes + std::string cl; + if ((err = recv_token_eof(cl)) != srs_success) { + return srs_error_wrap(err, "cl"); + } + req->content_length = ::atoll(cl.c_str()); + } else if (token == SRS_RTSP_TOKEN_TRANSPORT) { + // Transport: RTP transport parameters (protocol, ports, etc.) + std::string transport; + if ((err = recv_token_eof(transport)) != srs_success) { + return srs_error_wrap(err, "transport"); + } + if (!req->transport) { + req->transport = new SrsRtspTransport(); + } + if ((err = req->transport->parse(transport)) != srs_success) { + return srs_error_wrap(err, "parse transport=%s", transport.c_str()); + } + } else if (token == SRS_RTSP_TOKEN_SESSION) { + // Session: session identifier for maintaining state + if ((err = recv_token_eof(req->session)) != srs_success) { + return srs_error_wrap(err, "session"); + } + } else if (token == SRS_RTSP_TOKEN_ACCEPT) { + // Accept: acceptable media types for the response + if ((err = recv_token_eof(req->accept)) != srs_success) { + return srs_error_wrap(err, "accept"); + } + } else if (token == SRS_RTSP_TOKEN_USER_AGENT) { + // User-Agent: client software identification + if ((err = recv_token_util_eof(req->user_agent)) != srs_success) { + return srs_error_wrap(err, "user_agent"); + } + } else if (token == SRS_RTSP_TOKEN_RANGE) { + // Range: time range for playback (e.g., npt=0-30) + if ((err = recv_token_eof(req->range)) != srs_success) { + return srs_error_wrap(err, "range"); + } + } else { + // unknown header name, parse util EOF. + std::string value; + if ((err = recv_token_util_eof(value)) != srs_success) { + return srs_error_wrap(err, "state"); + } + srs_trace("rtsp: ignore header %s=%s", token.c_str(), value.c_str()); + } + } + + // for setup, parse the stream id from uri. + if (req->is_setup()) { + size_t pos = string::npos; + std::string stream_id = srs_path_basename(req->uri); + if ((pos = stream_id.find("=")) != string::npos) { + stream_id = stream_id.substr(pos + 1); + } + req->stream_id = ::atoi(stream_id.c_str()); + srs_info("rtsp: setup stream id=%d", req->stream_id); + } + + return err; +} + +srs_error_t SrsRtspStack::recv_token_normal(std::string& token) +{ + srs_error_t err = srs_success; + + SrsRtspTokenState state; + + if ((err = recv_token(token, state)) != srs_success) { + if (srs_error_code(err) == ERROR_RTSP_REQUEST_HEADER_EOF) { + return srs_error_wrap(err, "EOF"); + } + return srs_error_wrap(err, "recv token"); + } + + if (state != SrsRtspTokenStateNormal) { + return srs_error_new(ERROR_RTSP_TOKEN_NOT_NORMAL, "invalid state=%d", state); + } + + return err; +} + +srs_error_t SrsRtspStack::recv_token_eof(std::string& token) +{ + srs_error_t err = srs_success; + + SrsRtspTokenState state; + + if ((err = recv_token(token, state)) != srs_success) { + if (srs_error_code(err) == ERROR_RTSP_REQUEST_HEADER_EOF) { + return srs_error_wrap(err, "EOF"); + } + return srs_error_wrap(err, "recv token"); + } + + if (state != SrsRtspTokenStateEOF) { + return srs_error_new(ERROR_RTSP_TOKEN_NOT_NORMAL, "invalid state=%d", state); + } + + return err; +} + +srs_error_t SrsRtspStack::recv_token_util_eof(std::string& token, int* pconsumed) +{ + srs_error_t err = srs_success; + + SrsRtspTokenState state; + + // use 0x00 as ignore the normal token flag. + if ((err = recv_token(token, state, 0x00, pconsumed)) != srs_success) { + if (srs_error_code(err) == ERROR_RTSP_REQUEST_HEADER_EOF) { + return srs_error_wrap(err, "EOF"); + } + return srs_error_wrap(err, "recv token"); + } + + if (state != SrsRtspTokenStateEOF) { + return srs_error_new(ERROR_RTSP_TOKEN_NOT_NORMAL, "invalid state=%d", state); + } + + return err; +} + +srs_error_t SrsRtspStack::recv_token(std::string& token, SrsRtspTokenState& state, char normal_ch, int* pconsumed) +{ + srs_error_t err = srs_success; + + // whatever, default to error state. + state = SrsRtspTokenStateError; + + // when buffer is empty, append bytes first. + bool append_bytes = buf->length() == 0; + + // parse util token. + for (;;) { + // append bytes if required. + if (append_bytes) { + append_bytes = false; + + char buffer[SRS_RTSP_BUFFER]; + ssize_t nb_read = 0; + if ((err = skt->read(buffer, SRS_RTSP_BUFFER, &nb_read)) != srs_success) { + return srs_error_wrap(err, "recv data"); + } + + buf->append(buffer, (int)nb_read); + } + + // Try to detect and consume any RTCP frames from the buffer + while (buf->length() > 0) { + srs_error_t rtcp_err = try_consume_rtcp_frame(); + + if (rtcp_err == srs_success) { + // Successfully consumed an RTCP frame, continue to check for more + continue; + } else if (srs_error_code(rtcp_err) == ERROR_RTSP_NEED_MORE_DATA) { + // Need more data to complete RTCP frame, let the outer loop read more + srs_freep(rtcp_err); + append_bytes = true; + break; + } else { + // Not an RTCP frame or other error, break and try RTSP parsing + srs_freep(rtcp_err); + break; + } + } + + // parse one by one. + char* start = buf->bytes(); + char* end = start + buf->length(); + char* p = start; + + // find util SP/CR/LF, max 2 EOF, to finger out the EOF of message. + for (; p < end && p[0] != normal_ch && p[0] != SRS_RTSP_CR && p[0] != SRS_RTSP_LF; p++) { + } + + // matched. + if (p < end) { + // finger out the state. + if (p[0] == normal_ch) { + state = SrsRtspTokenStateNormal; + } else { + state = SrsRtspTokenStateEOF; + } + + // got the token. + int nb_token = (int)(p - start); + // trim last ':' character. + if (nb_token && p[-1] == ':') { + nb_token--; + } + if (nb_token) { + token.append(start, nb_token); + } else { + err = srs_error_new(ERROR_RTSP_REQUEST_HEADER_EOF, "EOF"); + } + + // ignore SP/CR/LF + for (int i = 0; i < 2 && p < end && (p[0] == normal_ch || p[0] == SRS_RTSP_CR || p[0] == SRS_RTSP_LF); p++, i++) { + } + + // consume the token bytes. + srs_assert(p - start); + buf->erase((int)(p - start)); + if (pconsumed) { + *pconsumed = (int)(p - start); + } + break; + } + + // append more and parse again. + append_bytes = true; + } + + return err; +} + +srs_error_t SrsRtspStack::try_consume_rtcp_frame() +{ + // Need at least 4 bytes for RTCP over TCP header: $ + channel + length + if (buf->length() < 4) { + // Not enough data, let caller read more + return srs_error_new(ERROR_RTSP_NEED_MORE_DATA, "need more data for rtcp header"); + } + + char* data = buf->bytes(); + + // Check for RTCP over TCP format: $ + channel + length(2 bytes) + if (data[0] == '$') { + uint8_t channel = (uint8_t)data[1]; + uint16_t payload_length = (uint16_t(data[2]) << 8) | uint16_t(data[3]); + int total_frame_size = 4 + payload_length; // 4-byte header + payload + + // Check if we have the complete frame + if (buf->length() < total_frame_size) { + // Not enough data for complete frame, let caller read more + return srs_error_new(ERROR_RTSP_NEED_MORE_DATA, "need more data for complete rtcp frame"); + } + + // Check if the payload is RTCP (starts at offset 4) + if (payload_length >= 8 && srs_is_rtcp((const uint8_t*)(data + 4), payload_length)) { + // This is an RTCP packet in RTSP over TCP format + srs_trace("RTSP: Consuming RTCP packet(%d), channel=%d, size=%d bytes", + (uint8_t)data[5], channel, payload_length); + buf->erase(total_frame_size); + return srs_success; + } else { + // Unknown interleaved frame, consume it anyway to avoid blocking RTSP parsing + srs_trace("RTSP: Consuming unknown interleaved frame, channel=%d, size=%d bytes", + channel, payload_length); + buf->erase(total_frame_size); + return srs_success; + } + } + + // Not an interleaved frame (RTP/RTCP over TCP) + return srs_error_new(ERROR_RTSP_TOKEN_NOT_NORMAL, "not interleaved frame"); +} diff --git a/trunk/src/protocol/srs_protocol_rtsp_stack.hpp b/trunk/src/protocol/srs_protocol_rtsp_stack.hpp new file mode 100644 index 000000000..79de30a13 --- /dev/null +++ b/trunk/src/protocol/srs_protocol_rtsp_stack.hpp @@ -0,0 +1,384 @@ +// +// Copyright (c) 2013-2025 The SRS Authors +// +// SPDX-License-Identifier: MIT +// + +#ifndef SRS_PROTOCOL_RTSP_HPP +#define SRS_PROTOCOL_RTSP_HPP + +#include +#include + +#include +#include + +class SrsBuffer; +class SrsSimpleStream; +class SrsAudioFrame; +class ISrsProtocolReadWriter; + +// From rtsp specification +// CR = +#define SRS_RTSP_CR SRS_CONSTS_CR // 0x0D +// LF = +#define SRS_RTSP_LF SRS_CONSTS_LF // 0x0A +// SP = +#define SRS_RTSP_SP ' ' // 0x20 + +// 4 RTSP Message, @see rfc2326-1998-rtsp.pdf, page 37 +// Lines are terminated by CRLF, but +// receivers should be prepared to also interpret CR and LF by +// themselves as line terminators. +#define SRS_RTSP_CRLF "\r\n" // 0x0D0A +#define SRS_RTSP_CRLFCRLF "\r\n\r\n" // 0x0D0A0D0A + +// RTSP token +#define SRS_RTSP_TOKEN_CSEQ "CSeq" +#define SRS_RTSP_TOKEN_PUBLIC "Public" +#define SRS_RTSP_TOKEN_CONTENT_TYPE "Content-Type" +#define SRS_RTSP_TOKEN_CONTENT_LENGTH "Content-Length" +#define SRS_RTSP_TOKEN_TRANSPORT "Transport" +#define SRS_RTSP_TOKEN_SESSION "Session" +#define SRS_RTSP_TOKEN_ACCEPT "Accept" +#define SRS_RTSP_TOKEN_USER_AGENT "User-Agent" +#define SRS_RTSP_TOKEN_RANGE "Range" + +// RTSP methods +#define SRS_RTSP_METHOD_OPTIONS "OPTIONS" +#define SRS_RTSP_METHOD_DESCRIBE "DESCRIBE" +#define SRS_RTSP_METHOD_ANNOUNCE "ANNOUNCE" +#define SRS_RTSP_METHOD_SETUP "SETUP" +#define SRS_RTSP_METHOD_PLAY "PLAY" +#define SRS_RTSP_METHOD_PAUSE "PAUSE" +#define SRS_RTSP_METHOD_TEARDOWN "TEARDOWN" +#define SRS_RTSP_METHOD_GET_PARAMETER "GET_PARAMETER" +#define SRS_RTSP_METHOD_SET_PARAMETER "SET_PARAMETER" +#define SRS_RTSP_METHOD_REDIRECT "REDIRECT" +#define SRS_RTSP_METHOD_RECORD "RECORD" + +// RTSP-Version +#define SRS_RTSP_VERSION "RTSP/1.0" + +// 10 Method Definitions, @see rfc2326-1998-rtsp.pdf, page 57 +// The method token indicates the method to be performed on the resource +// identified by the Request-URI. The method is case-sensitive. New +// methods may be defined in the future. Method names may not start with +// a $ character (decimal 24) and must be a token. Methods are +// summarized in Table 2. +// Notes on Table 2: PAUSE is recommended, but not required in that a +// fully functional server can be built that does not support this +// method, for example, for live feeds. If a server does not support a +// particular method, it MUST return "501 Not Implemented" and a client +// SHOULD not try this method again for this server. +enum SrsRtspMethod +{ + SrsRtspMethodDescribe = 0x0001, + SrsRtspMethodAnnounce = 0x0002, + SrsRtspMethodGetParameter = 0x0004, + SrsRtspMethodOptions = 0x0008, + SrsRtspMethodPause = 0x0010, + SrsRtspMethodPlay = 0x0020, + SrsRtspMethodRecord = 0x0040, + SrsRtspMethodRedirect = 0x0080, + SrsRtspMethodSetup = 0x0100, + SrsRtspMethodSetParameter = 0x0200, + SrsRtspMethodTeardown = 0x0400, +}; + +// The state of rtsp token. +enum SrsRtspTokenState +{ + // Parse token failed, default state. + SrsRtspTokenStateError = 100, + // When SP follow the token. + SrsRtspTokenStateNormal = 101, + // When CRLF follow the token. + SrsRtspTokenStateEOF = 102, +}; + +// The rtsp transport. +// 12.39 Transport, @see rfc2326-1998-rtsp.pdf, page 115 +// This request header indicates which transport protocol is to be used +// and configures its parameters such as destination address, +// compression, multicast time-to-live and destination port for a single +// stream. It sets those values not already determined by a presentation +// description. +class SrsRtspTransport +{ +public: + // The syntax for the transport specifier is + // transport/profile/lower-transport + std::string transport; + std::string profile; + std::string lower_transport; + // unicast | multicast + // mutually exclusive indication of whether unicast or multicast + // delivery will be attempted. Default value is multicast. + // Clients that are capable of handling both unicast and + // multicast transmission MUST indicate such capability by + // including two full transport-specs with separate parameters + // For each. + std::string cast_type; + // The interleaved parameter implies mixing the media stream with + // the control stream in whatever protocol is being used by the + // control stream, using the mechanism defined in Section 10.12. + // The argument provides the channel number to be used in the $ + // statement. This parameter may be specified as a range, e.g., + // interleaved=4-5 in cases where the transport choice for the + // media stream requires it. + std::string interleaved; + int interleaved_min; + int interleaved_max; + // The mode parameter indicates the methods to be supported for + // this session. Valid values are PLAY and RECORD. If not + // provided, the default is PLAY. + std::string mode; + // This parameter provides the unicast RTP/RTCP port pair on + // which the client has chosen to receive media data and control + // information. It is specified as a range, e.g., + // client_port=3456-3457. + // where client will use port in: + // [client_port_min, client_port_max) + int client_port_min; + int client_port_max; +public: + SrsRtspTransport(); + virtual ~SrsRtspTransport(); +public: + // Parse a line of token for transport. + virtual srs_error_t parse(std::string attr); + // Copy the transport from src. + virtual void copy(SrsRtspTransport* src); +}; + +// The rtsp request message. +// 6 Request, @see rfc2326-1998-rtsp.pdf, page 39 +// A request message from a client to a server or vice versa includes, +// within the first line of that message, the method to be applied to +// The resource, the identifier of the resource, and the protocol +// version in use. +// Request = Request-Line ; Section 6.1 +// // ( general-header ; Section 5 +// | request-header ; Section 6.2 +// | entity-header ) ; Section 8.1 +// CRLF +// [ message-body ] ; Section 4.3 +class SrsRtspRequest +{ +public: + // 6.1 Request Line + // Request-Line = Method SP Request-URI SP RTSP-Version CRLF + std::string method; + std::string uri; + std::string version; + // 12.17 CSeq + // The CSeq field specifies the sequence number for an RTSP requestresponse + // pair. This field MUST be present in all requests and + // responses. For every RTSP request containing the given sequence + // number, there will be a corresponding response having the same + // number. Any retransmitted request must contain the same sequence + // number as the original (i.e. the sequence number is not incremented + // For retransmissions of the same request). + long seq; + // 12.16 Content-Type, @see rfc2326-1998-rtsp.pdf, page 99 + // See [H14.18]. Note that the content types suitable for RTSP are + // likely to be restricted in practice to presentation descriptions and + // parameter-value types. + std::string content_type; + // 12.14 Content-Length, @see rfc2326-1998-rtsp.pdf, page 99 + // This field contains the length of the content of the method (i.e. + // after the double CRLF following the last header). Unlike HTTP, it + // MUST be included in all messages that carry content beyond the header + // portion of the message. If it is missing, a default value of zero is + // assumed. It is interpreted according to [H14.14]. + long content_length; + // The session id. + std::string session; + + // The transport in setup, NULL for no transport. + SrsRtspTransport* transport; + // For setup message, parse the stream id from uri. + int stream_id; + + std::string accept; + std::string user_agent; + std::string range; +public: + SrsRtspRequest(); + virtual ~SrsRtspRequest(); +public: + virtual bool is_options(); + virtual bool is_describe(); + virtual bool is_setup(); + virtual bool is_play(); + virtual bool is_teardown(); +}; + +// The rtsp response message. +// 7 Response, @see rfc2326-1998-rtsp.pdf, page 43 +// [H6] applies except that HTTP-Version is replaced by RTSP-Version. +// Also, RTSP defines additional status codes and does not define some +// HTTP codes. The valid response codes and the methods they can be used +// with are defined in Table 1. +// After receiving and interpreting a request message, the recipient +// responds with an RTSP response message. +// Response = Status-Line ; Section 7.1 +// // ( general-header ; Section 5 +// | response-header ; Section 7.1.2 +// | entity-header ) ; Section 8.1 +// CRLF +// [ message-body ] ; Section 4.3 +class SrsRtspResponse +{ +public: + // 7.1 Status-Line + // The first line of a Response message is the Status-Line, consisting + // of the protocol version followed by a numeric status code, and the + // textual phrase associated with the status code, with each element + // separated by SP characters. No CR or LF is allowed except in the + // final CRLF sequence. + // Status-Line = RTSP-Version SP Status-Code SP Reason-Phrase CRLF + // @see about the version of rtsp, see SRS_RTSP_VERSION + // @see about the status of rtsp, see SRS_CONSTS_RTSP_OK + int status; + // 12.17 CSeq, @see rfc2326-1998-rtsp.pdf, page 99 + // The CSeq field specifies the sequence number for an RTSP requestresponse + // pair. This field MUST be present in all requests and + // responses. For every RTSP request containing the given sequence + // number, there will be a corresponding response having the same + // number. Any retransmitted request must contain the same sequence + // number as the original (i.e. the sequence number is not incremented + // For retransmissions of the same request). + long seq; + // The session id. + std::string session; +public: + SrsRtspResponse(int cseq); + virtual ~SrsRtspResponse(); +public: + // Encode message to string. + virtual srs_error_t encode(std::stringstream& ss); +protected: + // Sub classes override this to encode the headers. + virtual srs_error_t encode_header(std::stringstream& ss); +}; + +// 10.1 OPTIONS, @see rfc2326-1998-rtsp.pdf, page 59 +// The behavior is equivalent to that described in [H9.2]. An OPTIONS +// request may be issued at any time, e.g., if the client is about to +// try a nonstandard request. It does not influence server state. +class SrsRtspOptionsResponse : public SrsRtspResponse +{ +public: + // Join of SrsRtspMethod + SrsRtspMethod methods; +public: + SrsRtspOptionsResponse(int cseq); + virtual ~SrsRtspOptionsResponse(); +protected: + virtual srs_error_t encode_header(std::stringstream& ss); +}; + +// 10.2 DESCRIBE, @see rfc2326-1998-rtsp.pdf, page 61 +class SrsRtspDescribeResponse : public SrsRtspResponse +{ +public: + // The sdp in describe. + std::string sdp; +public: + SrsRtspDescribeResponse(int cseq); + virtual ~SrsRtspDescribeResponse(); +protected: + virtual srs_error_t encode_header(std::stringstream& ss); +}; + +// 10.4 SETUP, @see rfc2326-1998-rtsp.pdf, page 65 +// The SETUP request for a URI specifies the transport mechanism to be +// used for the streamed media. A client can issue a SETUP request for a +// stream that is already playing to change transport parameters, which +// a server MAY allow. If it does not allow this, it MUST respond with +// error "455 Method Not Valid In This State". For the benefit of any +// intervening firewalls, a client must indicate the transport +// parameters even if it has no influence over these parameters, for +// example, where the server advertises a fixed multicast address. +class SrsRtspSetupResponse : public SrsRtspResponse +{ +public: + // The client specified port. + int client_port_min; + int client_port_max; + // The client will use the port in: + // [local_port_min, local_port_max) + int local_port_min; + int local_port_max; + + SrsRtspTransport* transport; + // The ssrc of the stream. + std::string ssrc; +public: + SrsRtspSetupResponse(int cseq); + virtual ~SrsRtspSetupResponse(); +protected: + virtual srs_error_t encode_header(std::stringstream& ss); +}; + +// 10.5 PLAY, @see rfc2326-1998-rtsp.pdf, page 67 +class SrsRtspPlayResponse : public SrsRtspResponse +{ +public: + SrsRtspPlayResponse(int cseq); + virtual ~SrsRtspPlayResponse(); +protected: + virtual srs_error_t encode_header(std::stringstream& ss); +}; + +// The rtsp protocol stack to parse the rtsp packets. +class SrsRtspStack +{ +private: + // The cached bytes buffer. + SrsSimpleStream* buf; + // The underlayer socket object, send/recv bytes. + ISrsProtocolReadWriter* skt; +public: + SrsRtspStack(ISrsProtocolReadWriter* s); + virtual ~SrsRtspStack(); +public: + // Recv rtsp message from underlayer io. + // @param preq the output rtsp request message, which user must free it. + // @return an int error code. + // ERROR_RTSP_REQUEST_HEADER_EOF indicates request header EOF. + virtual srs_error_t recv_message(SrsRtspRequest** preq); + // Try to detect and consume RTCP frame from buffered data. + // @return srs_success if RTCP frame is consumed successfully. + // ERROR_RTSP_NEED_MORE_DATA if more data is needed to complete the frame. + // ERROR_RTSP_TOKEN_NOT_NORMAL if the data is not an RTCP interleaved frame. + virtual srs_error_t try_consume_rtcp_frame(); + // Send rtsp message over underlayer io. + // @param res the rtsp response message, which user should never free it. + // @return an int error code. + virtual srs_error_t send_message(SrsRtspResponse* res); +private: + // Recv the rtsp message. + virtual srs_error_t do_recv_message(SrsRtspRequest* req); + // Read a normal token from io, error when token state is not normal. + virtual srs_error_t recv_token_normal(std::string& token); + // Read a normal token from io, error when token state is not eof. + virtual srs_error_t recv_token_eof(std::string& token); + // Read the token util got eof, for example, to read the response status Reason-Phrase + // @param pconsumed, output the token parsed length. NULL to ignore. + virtual srs_error_t recv_token_util_eof(std::string& token, int* pconsumed = NULL); + // Read a token from io, split by SP, endswith CRLF: + // token1 SP token2 SP ... tokenN CRLF + // @param token, output the read token. + // @param state, output the token parse state. + // @param normal_ch, the char to indicates the normal token. + // the SP use to indicates the normal token, @see SRS_RTSP_SP + // the 0x00 use to ignore normal token flag. @see recv_token_util_eof + // @param pconsumed, output the token parsed length. NULL to ignore. + virtual srs_error_t recv_token(std::string& token, SrsRtspTokenState& state, char normal_ch = SRS_RTSP_SP, int* pconsumed = NULL); +}; + +#endif + diff --git a/trunk/src/utest/srs_utest_config.cpp b/trunk/src/utest/srs_utest_config.cpp index 4a907dd56..3c6561567 100644 --- a/trunk/src/utest/srs_utest_config.cpp +++ b/trunk/src/utest/srs_utest_config.cpp @@ -4383,6 +4383,19 @@ VOID TEST(ConfigEnvTest, CheckEnvValuesVhostSrt) } } +VOID TEST(ConfigEnvTest, CheckEnvValuesRtspServer) +{ + if (true) { + MockSrsConfig conf; + + SrsSetEnvConfig(rtsp_server_enabled, "SRS_RTSP_SERVER_ENABLED", "on"); + EXPECT_TRUE(conf.get_rtsp_server_enabled()); + + SrsSetEnvConfig(rtsp_server_listen, "SRS_RTSP_SERVER_LISTEN", "554"); + EXPECT_EQ(554, conf.get_rtsp_server_listen()); + } +} + VOID TEST(ConfigEnvTest, CheckEnvValuesRtcServer) { if (true) { diff --git a/trunk/src/utest/srs_utest_protocol.cpp b/trunk/src/utest/srs_utest_protocol.cpp index b03a0b867..951c97e6c 100644 --- a/trunk/src/utest/srs_utest_protocol.cpp +++ b/trunk/src/utest/srs_utest_protocol.cpp @@ -12,6 +12,7 @@ using namespace std; #include #include #include +#include #include #include #include @@ -21,6 +22,10 @@ using namespace std; #include #include +#ifdef SRS_RTC +#include +#endif + MockEmptyIO::MockEmptyIO() { } @@ -3616,3 +3621,633 @@ VOID TEST(ProtocolRTMPTest, RTMPHandshakeBytes) EXPECT_TRUE(bytes.s0s1s2 != NULL); } +#ifdef SRS_RTSP +VOID TEST(ProtocolRTSPTest, RTSPRequest) +{ + srs_error_t err = srs_success; + + MockBufferIO bio; + SrsRtspStack stack(&bio); + + // OPTIONS + if (true) { + const char* options_req = + "OPTIONS rtsp://server.example.com/stream RTSP/1.0\r\n" + "CSeq: 1\r\n" + "User-Agent: SRS RTSP Client\r\n\r\n"; + bio.in_buffer.append(options_req, strlen(options_req)); + + SrsRtspRequest* req = NULL; + HELPER_ASSERT_SUCCESS(stack.recv_message(&req)); + SrsUniquePtr req_uptr(req); + + EXPECT_TRUE(req->is_options()); + EXPECT_STREQ("OPTIONS", req->method.c_str()); + EXPECT_STREQ("rtsp://server.example.com/stream", req->uri.c_str()); + EXPECT_STREQ("RTSP/1.0", req->version.c_str()); + EXPECT_EQ(1, req->seq); + EXPECT_STREQ("SRS RTSP Client", req->user_agent.c_str()); + + SrsRtspOptionsResponse* res = new SrsRtspOptionsResponse(req->seq); + HELPER_ASSERT_SUCCESS(stack.send_message(res)); + + string response = std::string(bio.out_buffer.bytes(), bio.out_buffer.length()); + EXPECT_TRUE(response.find("RTSP/1.0 200 OK") != string::npos); + EXPECT_TRUE(response.find("CSeq: 1") != string::npos); + EXPECT_TRUE(response.find("Public:") != string::npos); + bio.out_buffer.erase(bio.out_buffer.length()); + } + + // DESCRIBE + if (true) { + const char* describe_req = + "DESCRIBE rtsp://server.example.com/stream RTSP/1.0\r\n" + "CSeq: 2\r\n" + "Accept: application/sdp\r\n\r\n"; + bio.in_buffer.append(describe_req, strlen(describe_req)); + + SrsRtspRequest* req = NULL; + HELPER_ASSERT_SUCCESS(stack.recv_message(&req)); + SrsUniquePtr req_uptr(req); + + EXPECT_TRUE(req->is_describe()); + EXPECT_STREQ("DESCRIBE", req->method.c_str()); + EXPECT_EQ(2, req->seq); + EXPECT_STREQ("application/sdp", req->accept.c_str()); + + SrsRtspDescribeResponse* res = new SrsRtspDescribeResponse(req->seq); + res->sdp = "v=0\r\n" + "o=- 123456 0 IN IP4 127.0.0.1\r\n" + "s=SRS RTSP Server\r\n" + "c=IN IP4 127.0.0.1\r\n" + "t=0 0\r\n" + "m=video 0 RTP/AVP 96\r\n" + "a=rtpmap:96 H264/90000\r\n"; + HELPER_ASSERT_SUCCESS(stack.send_message(res)); + + string response = std::string(bio.out_buffer.bytes(), bio.out_buffer.length()); + EXPECT_TRUE(response.find("RTSP/1.0 200 OK") != string::npos); + EXPECT_TRUE(response.find("CSeq: 2") != string::npos); + EXPECT_TRUE(response.find("Content-Type: application/sdp") != string::npos); + EXPECT_TRUE(response.find("m=video 0 RTP/AVP 96") != string::npos); + bio.out_buffer.erase(bio.out_buffer.length()); + } + + // SETUP + if (true) { + const char* setup_req = + "SETUP rtsp://server.example.com/stream/trackID=0 RTSP/1.0\r\n" + "CSeq: 3\r\n" + "Transport: RTP/AVP;unicast;client_port=9000-9001\r\n\r\n"; + bio.in_buffer.append(setup_req, strlen(setup_req)); + + SrsRtspRequest* req = NULL; + HELPER_ASSERT_SUCCESS(stack.recv_message(&req)); + SrsUniquePtr req_uptr(req); + + EXPECT_TRUE(req->is_setup()); + EXPECT_STREQ("SETUP", req->method.c_str()); + EXPECT_EQ(3, req->seq); + EXPECT_TRUE(req->transport != NULL); + EXPECT_STREQ("RTP", req->transport->transport.c_str()); + EXPECT_STREQ("AVP", req->transport->profile.c_str()); + EXPECT_STREQ("unicast", req->transport->cast_type.c_str()); + EXPECT_EQ(9000, req->transport->client_port_min); + EXPECT_EQ(9001, req->transport->client_port_max); + EXPECT_EQ(0, req->stream_id); + + SrsRtspSetupResponse* res = new SrsRtspSetupResponse(req->seq); + res->session = "12345678"; + res->client_port_min = 9000; + res->client_port_max = 9001; + res->local_port_min = 5000; + res->local_port_max = 5001; + res->transport->transport = "RTP"; + res->transport->profile = "AVP"; + res->transport->cast_type = "unicast"; + res->ssrc = "1234ABCD"; + HELPER_ASSERT_SUCCESS(stack.send_message(res)); + + string response = std::string(bio.out_buffer.bytes(), bio.out_buffer.length()); + EXPECT_TRUE(response.find("RTSP/1.0 200 OK") != string::npos); + EXPECT_TRUE(response.find("CSeq: 3") != string::npos); + EXPECT_TRUE(response.find("Transport: RTP/AVP;unicast;client_port=9000-9001;server_port=5000-5001;ssrc=1234ABCD;mode=\"play\"") != string::npos); + bio.out_buffer.erase(bio.out_buffer.length()); + } + + // PLAY + if (true) { + const char* play_req = + "PLAY rtsp://server.example.com/stream RTSP/1.0\r\n" + "CSeq: 4\r\n" + "Session: 12345678\r\n" + "Range: npt=0.000-\r\n\r\n"; + bio.in_buffer.append(play_req, strlen(play_req)); + + SrsRtspRequest* req = NULL; + HELPER_ASSERT_SUCCESS(stack.recv_message(&req)); + SrsUniquePtr req_uptr(req); + + EXPECT_TRUE(req->is_play()); + EXPECT_STREQ("PLAY", req->method.c_str()); + EXPECT_EQ(4, req->seq); + EXPECT_STREQ("12345678", req->session.c_str()); + EXPECT_STREQ("npt=0.000-", req->range.c_str()); + + SrsRtspPlayResponse* res = new SrsRtspPlayResponse(req->seq); + res->session = req->session; + HELPER_ASSERT_SUCCESS(stack.send_message(res)); + + string response = std::string(bio.out_buffer.bytes(), bio.out_buffer.length()); + EXPECT_TRUE(response.find("RTSP/1.0 200 OK") != string::npos); + EXPECT_TRUE(response.find("CSeq: 4") != string::npos); + EXPECT_TRUE(response.find("Session: 12345678") != string::npos); + bio.out_buffer.erase(bio.out_buffer.length()); + } + + // TEARDOWN + if (true) { + const char* teardown_req = + "TEARDOWN rtsp://server.example.com/stream RTSP/1.0\r\n" + "CSeq: 5\r\n" + "Session: 12345678\r\n\r\n"; + bio.in_buffer.append(teardown_req, strlen(teardown_req)); + + SrsRtspRequest* req = NULL; + HELPER_ASSERT_SUCCESS(stack.recv_message(&req)); + SrsUniquePtr req_uptr(req); + + EXPECT_TRUE(req->is_teardown()); + EXPECT_STREQ("TEARDOWN", req->method.c_str()); + EXPECT_EQ(5, req->seq); + EXPECT_STREQ("12345678", req->session.c_str()); + + SrsRtspResponse* res = new SrsRtspResponse(req->seq); + res->session = req->session; + HELPER_ASSERT_SUCCESS(stack.send_message(res)); + + string response = std::string(bio.out_buffer.bytes(), bio.out_buffer.length()); + EXPECT_TRUE(response.find("RTSP/1.0 200 OK") != string::npos); + EXPECT_TRUE(response.find("CSeq: 5") != string::npos); + EXPECT_TRUE(response.find("Session: 12345678") != string::npos); + bio.out_buffer.erase(bio.out_buffer.length()); + } +} + +// Test TCP-only transport support +VOID TEST(ProtocolRTSPTest, RTSPTcpOnlyTransport) +{ + srs_error_t err = srs_success; + + MockBufferIO bio; + SrsRtspStack stack(&bio); + + // Test TCP transport (should succeed) + if (true) { + const char* tcp_setup_req = + "SETUP rtsp://server.example.com/stream/trackID=0 RTSP/1.0\r\n" + "CSeq: 3\r\n" + "Transport: RTP/AVP/TCP;unicast;interleaved=0-1\r\n\r\n"; + bio.in_buffer.append(tcp_setup_req, strlen(tcp_setup_req)); + + SrsRtspRequest* req = NULL; + HELPER_ASSERT_SUCCESS(stack.recv_message(&req)); + SrsUniquePtr req_uptr(req); + + EXPECT_TRUE(req->is_setup()); + EXPECT_STREQ("SETUP", req->method.c_str()); + EXPECT_EQ(3, req->seq); + EXPECT_TRUE(req->transport != NULL); + EXPECT_STREQ("RTP", req->transport->transport.c_str()); + EXPECT_STREQ("AVP", req->transport->profile.c_str()); + EXPECT_STREQ("TCP", req->transport->lower_transport.c_str()); + EXPECT_STREQ("unicast", req->transport->cast_type.c_str()); + EXPECT_EQ(0, req->transport->interleaved_min); + EXPECT_EQ(1, req->transport->interleaved_max); + + SrsRtspSetupResponse* res = new SrsRtspSetupResponse(req->seq); + res->session = "12345678"; + res->transport->copy(req->transport); + res->ssrc = "1234ABCD"; + HELPER_ASSERT_SUCCESS(stack.send_message(res)); + + string response = std::string(bio.out_buffer.bytes(), bio.out_buffer.length()); + EXPECT_TRUE(response.find("RTSP/1.0 200 OK") != string::npos); + EXPECT_TRUE(response.find("CSeq: 3") != string::npos); + EXPECT_TRUE(response.find("Transport: RTP/AVP/TCP;unicast;interleaved=0-1;ssrc=1234ABCD;mode=\"play\"") != string::npos); + bio.out_buffer.erase(bio.out_buffer.length()); + } + + // Test UDP transport (should be rejected with 461 Unsupported Transport) + if (true) { + const char* udp_setup_req = + "SETUP rtsp://server.example.com/stream/trackID=0 RTSP/1.0\r\n" + "CSeq: 4\r\n" + "Transport: RTP/AVP;unicast;client_port=9000-9001\r\n\r\n"; + bio.in_buffer.append(udp_setup_req, strlen(udp_setup_req)); + + SrsRtspRequest* req = NULL; + HELPER_ASSERT_SUCCESS(stack.recv_message(&req)); + SrsUniquePtr req_uptr(req); + + EXPECT_TRUE(req->is_setup()); + EXPECT_STREQ("SETUP", req->method.c_str()); + EXPECT_EQ(4, req->seq); + EXPECT_TRUE(req->transport != NULL); + EXPECT_STREQ("RTP", req->transport->transport.c_str()); + EXPECT_STREQ("AVP", req->transport->profile.c_str()); + EXPECT_STREQ("", req->transport->lower_transport.c_str()); // UDP has empty lower_transport + EXPECT_STREQ("unicast", req->transport->cast_type.c_str()); + EXPECT_EQ(9000, req->transport->client_port_min); + EXPECT_EQ(9001, req->transport->client_port_max); + + // Simulate server rejecting UDP transport + SrsRtspSetupResponse* res = new SrsRtspSetupResponse(req->seq); + res->status = SRS_CONSTS_RTSP_UnsupportedTransport; + HELPER_ASSERT_SUCCESS(stack.send_message(res)); + + string response = std::string(bio.out_buffer.bytes(), bio.out_buffer.length()); + EXPECT_TRUE(response.find("RTSP/1.0 461 Unsupported Transport") != string::npos); + EXPECT_TRUE(response.find("CSeq: 4") != string::npos); + bio.out_buffer.erase(bio.out_buffer.length()); + } +} + +#ifdef SRS_RTC +// Test SDP advertisement of TCP-only transport +VOID TEST(ProtocolRTSPTest, RTSPSdpTcpOnlyAdvertisement) +{ + srs_error_t err; + + // Test that SDP properly advertises TCP-only transport + if (true) { + // Simulate SDP generation for TCP-only RTSP + SrsSdp sdp; + sdp.version_ = "0"; + sdp.username_ = "SRS RTSP Server"; + sdp.session_name_ = "Play"; + sdp.session_info_.setup_ = "passive"; // TCP-only indication + + // Add audio media with TCP transport + SrsMediaDesc media_audio("audio"); + media_audio.port_ = 0; // Port 0 = no UDP + media_audio.protos_ = "RTP/AVP/TCP"; // TCP transport + media_audio.session_info_.setup_ = "passive"; + media_audio.payload_types_.push_back(SrsMediaPayloadType(111)); + sdp.media_descs_.push_back(media_audio); + + // Add video media with TCP transport + SrsMediaDesc media_video("video"); + media_video.port_ = 0; // Port 0 = no UDP + media_video.protos_ = "RTP/AVP/TCP"; // TCP transport + media_video.session_info_.setup_ = "passive"; + media_video.payload_types_.push_back(SrsMediaPayloadType(96)); + sdp.media_descs_.push_back(media_video); + + // Encode SDP + std::ostringstream ss; + err = sdp.encode(ss); + HELPER_EXPECT_SUCCESS(err); + + string sdp_content = ss.str(); + + // Verify TCP-only indicators in SDP + EXPECT_TRUE(sdp_content.find("a=setup:passive") != string::npos); // Session-level TCP setup + EXPECT_TRUE(sdp_content.find("m=audio 0 RTP/AVP/TCP") != string::npos); // Audio TCP transport + EXPECT_TRUE(sdp_content.find("m=video 0 RTP/AVP/TCP") != string::npos); // Video TCP transport + + // Verify no UDP port allocation + EXPECT_FALSE(sdp_content.find("m=audio 9") != string::npos); // No UDP audio ports + EXPECT_FALSE(sdp_content.find("m=video 9") != string::npos); // No UDP video ports + + srs_trace("Generated TCP-only SDP:\n%s", sdp_content.c_str()); + } +} +#endif + +// Invalid RTSP Request +VOID TEST(ProtocolRTSPTest, RTSPInvalidRequest) +{ + srs_error_t err = srs_success; + + MockBufferIO bio; + SrsRtspStack stack(&bio); + + if (true) { + // Invalid request missing RTSP version + const char* invalid_req = + "OPTIONS rtsp://server.example.com/stream\r\n" + "CSeq: 1\r\n\r\n"; + bio.in_buffer.append(invalid_req, strlen(invalid_req)); + + SrsRtspRequest* req = NULL; + err = stack.recv_message(&req); + EXPECT_TRUE(err != srs_success); + srs_freep(req); + srs_error_reset(err); + + bio.in_buffer.erase(bio.in_buffer.length()); + } + + if (true) { + // Invalid method name + const char* invalid_method_req = + "INVALID_METHOD rtsp://server.example.com/stream RTSP/1.0\r\n" + "CSeq: 1\r\n\r\n"; + bio.in_buffer.append(invalid_method_req, strlen(invalid_method_req)); + + SrsRtspRequest* req = NULL; + err = stack.recv_message(&req); + EXPECT_TRUE(err != srs_success); + srs_freep(req); + srs_error_reset(err); + + bio.in_buffer.erase(bio.in_buffer.length()); + } + + if (true) { + // Missing CSeq header + const char* missing_cseq_req = + "DESCRIBE rtsp://server.example.com/stream RTSP/1.0\r\n" + "Accept: application/sdp\r\n\r\n"; + bio.in_buffer.append(missing_cseq_req, strlen(missing_cseq_req)); + + SrsRtspRequest* req = NULL; + err = stack.recv_message(&req); + EXPECT_TRUE(err != srs_success); + srs_freep(req); + srs_error_reset(err); + + bio.in_buffer.erase(bio.in_buffer.length()); + } + + if (true) { + // SETUP request missing Transport header + const char* missing_transport_req = + "SETUP rtsp://server.example.com/stream/trackID=0 RTSP/1.0\r\n" + "CSeq: 3\r\n\r\n"; + bio.in_buffer.append(missing_transport_req, strlen(missing_transport_req)); + + SrsRtspRequest* req = NULL; + HELPER_ASSERT_SUCCESS(stack.recv_message(&req)); + SrsUniquePtr req_uptr(req); + + EXPECT_TRUE(req->is_setup()); + EXPECT_TRUE(req->transport == NULL); + } + + if (true) { + // Invalid client port format + const char* invalid_port_req = + "SETUP rtsp://server.example.com/stream/trackID=0 RTSP/1.0\r\n" + "CSeq: 3\r\n" + "Transport: RTP/AVP;unicast;client_port=invalid\r\n\r\n"; + bio.in_buffer.append(invalid_port_req, strlen(invalid_port_req)); + + SrsRtspRequest* req = NULL; + HELPER_ASSERT_SUCCESS(stack.recv_message(&req)); + SrsUniquePtr req_uptr(req); + + EXPECT_TRUE(req->is_setup()); + EXPECT_TRUE(req->transport != NULL); + // Invalid port will be parsed as 0 + EXPECT_EQ(0, req->transport->client_port_min); + EXPECT_EQ(0, req->transport->client_port_max); + } + + if (true) { + // Missing transport protocol + const char* missing_proto_req = + "SETUP rtsp://server.example.com/stream/trackID=0 RTSP/1.0\r\n" + "CSeq: 4\r\n" + "Transport: ;unicast;client_port=9000-9001\r\n\r\n"; + bio.in_buffer.append(missing_proto_req, strlen(missing_proto_req)); + + SrsRtspRequest* req = NULL; + HELPER_ASSERT_SUCCESS(stack.recv_message(&req)); + SrsUniquePtr req_uptr(req); + + EXPECT_TRUE(req->is_setup()); + EXPECT_TRUE(req->transport != NULL); + // Transport field is empty + EXPECT_STREQ("", req->transport->transport.c_str()); + EXPECT_STREQ("", req->transport->profile.c_str()); + EXPECT_STREQ("", req->transport->lower_transport.c_str()); + EXPECT_STREQ("unicast", req->transport->cast_type.c_str()); + EXPECT_EQ(9000, req->transport->client_port_min); + EXPECT_EQ(9001, req->transport->client_port_max); + } + + if (true) { + // Try PLAY without session ID + const char* no_session_play_req = + "PLAY rtsp://server.example.com/stream RTSP/1.0\r\n" + "CSeq: 4\r\n" + "Range: npt=0.000-\r\n\r\n"; + bio.in_buffer.append(no_session_play_req, strlen(no_session_play_req)); + + SrsRtspRequest* req = NULL; + HELPER_ASSERT_SUCCESS(stack.recv_message(&req)); + SrsUniquePtr req_uptr(req); + + EXPECT_TRUE(req->is_play()); + EXPECT_STREQ("", req->session.c_str()); // 会话ID为空 + + // Should return 454 Session Not Found error + SrsRtspResponse* res = new SrsRtspResponse(req->seq); + res->status = SRS_CONSTS_RTSP_SessionNotFound; + HELPER_ASSERT_SUCCESS(stack.send_message(res)); + + string response = std::string(bio.out_buffer.bytes(), bio.out_buffer.length()); + EXPECT_TRUE(response.find("RTSP/1.0 454 Session Not Found") != string::npos); + EXPECT_TRUE(response.find("CSeq: 4") != string::npos); + bio.out_buffer.erase(bio.out_buffer.length()); + } + + if (true) { + // Use invalid session ID + const char* invalid_session_req = + "PLAY rtsp://server.example.com/stream RTSP/1.0\r\n" + "CSeq: 5\r\n" + "Session: invalid_session_id\r\n\r\n"; + bio.in_buffer.append(invalid_session_req, strlen(invalid_session_req)); + + SrsRtspRequest* req = NULL; + HELPER_ASSERT_SUCCESS(stack.recv_message(&req)); + SrsUniquePtr req_uptr(req); + + EXPECT_TRUE(req->is_play()); + EXPECT_STREQ("invalid_session_id", req->session.c_str()); + + // Should return 454 Session Not Found error + SrsRtspResponse* res = new SrsRtspResponse(req->seq); + res->status = SRS_CONSTS_RTSP_SessionNotFound; + HELPER_ASSERT_SUCCESS(stack.send_message(res)); + + string response = std::string(bio.out_buffer.bytes(), bio.out_buffer.length()); + EXPECT_TRUE(response.find("RTSP/1.0 454 Session Not Found") != string::npos); + EXPECT_TRUE(response.find("CSeq: 5") != string::npos); + bio.out_buffer.erase(bio.out_buffer.length()); + } + + if (true) { + // Request with custom headers + const char* custom_headers_req = + "OPTIONS rtsp://server.example.com/stream RTSP/1.0\r\n" + "CSeq: 7\r\n" + "X-Custom-Header1: CustomValue1\r\n" + "X-Custom-Header2: CustomValue2\r\n" + "X-Custom-Header3: CustomValue3\r\n" + "User-Agent: CustomUserAgent\r\n\r\n"; + bio.in_buffer.append(custom_headers_req, strlen(custom_headers_req)); + + SrsRtspRequest* req = NULL; + HELPER_ASSERT_SUCCESS(stack.recv_message(&req)); + SrsUniquePtr req_uptr(req); + + EXPECT_TRUE(req->is_options()); + EXPECT_EQ(7, req->seq); + // Custom headers will be ignored + EXPECT_STREQ("CustomUserAgent", req->user_agent.c_str()); + + SrsRtspOptionsResponse* res = new SrsRtspOptionsResponse(req->seq); + HELPER_ASSERT_SUCCESS(stack.send_message(res)); + + string response = std::string(bio.out_buffer.bytes(), bio.out_buffer.length()); + EXPECT_TRUE(response.find("RTSP/1.0 200 OK") != string::npos); + EXPECT_TRUE(response.find("CSeq: 7") != string::npos); + bio.out_buffer.erase(bio.out_buffer.length()); + } + + if (true) { + // Use invalid RTSP version + const char* invalid_version_req = + "OPTIONS rtsp://server.example.com/stream RTSP/2.0\r\n" + "CSeq: 1\r\n\r\n"; + bio.in_buffer.append(invalid_version_req, strlen(invalid_version_req)); + + SrsRtspRequest* req = NULL; + HELPER_ASSERT_SUCCESS(stack.recv_message(&req)); + SrsUniquePtr req_uptr(req); + + EXPECT_TRUE(req->is_options()); + EXPECT_STREQ("RTSP/2.0", req->version.c_str()); // Different version but still parsed + + // Should return RTSP version not supported error + SrsRtspResponse* res = new SrsRtspResponse(req->seq); + res->status = SRS_CONSTS_RTSP_RTSPVersionNotSupported; + HELPER_ASSERT_SUCCESS(stack.send_message(res)); + + string response = std::string(bio.out_buffer.bytes(), bio.out_buffer.length()); + EXPECT_TRUE(response.find("RTSP/1.0 505 RTSP Version Not Supported") != string::npos); + EXPECT_TRUE(response.find("CSeq: 1") != string::npos); + bio.out_buffer.erase(bio.out_buffer.length()); + } +} + +VOID TEST(ProtocolRTSPTest, RTSPConsumeRTCPThenRTSP) +{ + srs_error_t err = srs_success; + + MockBufferIO bio; + SrsRtspStack stack(&bio); + + if (true) { + // Create data with RTCP packet followed by RTSP message + string combined_data; + + // RTCP RR payload + unsigned char rtcp_payload[] = { + 0x81, // V=2, P=0, RC=1 + 0xC9, // PT=201 (RR) + 0x00, 0x07, // Length=7 (32-bit words) + 0x12, 0x34, 0x56, 0x78, // SSRC of packet sender + 0x87, 0x65, 0x43, 0x21, // SSRC_1 (source being reported on) + 0x00, // fraction lost + 0x00, 0x00, 0x00, // cumulative number of packets lost + 0x00, 0x00, 0x12, 0x34, // extended highest sequence number + 0x00, 0x00, 0x00, 0x10, // interarrival jitter + 0x00, 0x00, 0x00, 0x20, // last SR timestamp (LSR) + 0x00, 0x00, 0x00, 0x30 // delay since last SR (DLSR) + }; + + // Create RTSP over TCP frame: $ + channel + length + payload + unsigned char tcp_frame[4 + sizeof(rtcp_payload)]; + tcp_frame[0] = '$'; // Magic byte + tcp_frame[1] = 1; // Channel 1 (RTCP) + tcp_frame[2] = (sizeof(rtcp_payload) >> 8) & 0xFF; // Length high byte + tcp_frame[3] = sizeof(rtcp_payload) & 0xFF; // Length low byte + memcpy(tcp_frame + 4, rtcp_payload, sizeof(rtcp_payload)); + + // RTSP OPTIONS message + string rtsp_msg = "OPTIONS rtsp://example.com/stream RTSP/1.0\r\n" + "CSeq: 1\r\n" + "\r\n"; + + // Combine RTCP frame and RTSP data + combined_data.append((char*)tcp_frame, sizeof(tcp_frame)); + combined_data.append(rtsp_msg); + + bio.in_buffer.append(combined_data.c_str(), combined_data.length()); + + // Should successfully receive RTSP message after consuming RTCP + SrsRtspRequest* req = NULL; + HELPER_EXPECT_SUCCESS(stack.recv_message(&req)); + EXPECT_TRUE(req != NULL); + if (req != NULL) { + EXPECT_TRUE(req->is_options()); + EXPECT_EQ(1, req->seq); + } + + srs_freep(req); + bio.in_buffer.erase(bio.in_buffer.length()); + } +} + +VOID TEST(ProtocolRTSPTest, RTSPNotRTCPPacket) +{ + srs_error_t err = srs_success; + + MockBufferIO bio; + SrsRtspStack stack(&bio); + + if (true) { + // Regular RTSP message (not RTCP) + const char* rtsp_msg = "OPTIONS rtsp://example.com/stream RTSP/1.0\r\n" + "CSeq: 1\r\n" + "\r\n"; + bio.in_buffer.append(rtsp_msg, strlen(rtsp_msg)); + + // Should fail to consume as RTCP + HELPER_EXPECT_FAILED(stack.try_consume_rtcp_frame()); + + bio.in_buffer.erase(bio.in_buffer.length()); + } +} + +VOID TEST(ProtocolRTSPTest, RTSPIncompleteRTCPPacket) +{ + srs_error_t err = srs_success; + + MockBufferIO bio; + SrsRtspStack stack(&bio); + + if (true) { + // Create incomplete RTSP over TCP frame (only partial header) + unsigned char incomplete_frame[] = { + '$', // Magic byte + 1, // Channel + 0x00, // Length high byte + // Missing length low byte and payload + }; + + bio.in_buffer.append((char*)incomplete_frame, sizeof(incomplete_frame)); + + // Should fail due to incomplete frame + HELPER_EXPECT_FAILED(stack.try_consume_rtcp_frame()); + + bio.in_buffer.erase(bio.in_buffer.length()); + } +} +#endif + diff --git a/trunk/src/utest/srs_utest_rtc2.cpp b/trunk/src/utest/srs_utest_rtc2.cpp index cb70fc5f5..db1cfec78 100644 --- a/trunk/src/utest/srs_utest_rtc2.cpp +++ b/trunk/src/utest/srs_utest_rtc2.cpp @@ -342,14 +342,14 @@ VOID TEST(KernelRTC2Test, SrsCodecPayloadPerformanceCache) payload.name_ = "H264"; // First call does parsing and caching - auto start = std::chrono::high_resolution_clock::now(); + std::chrono::high_resolution_clock::now(); int8_t codec1 = payload.codec(true); - auto end1 = std::chrono::high_resolution_clock::now(); + std::chrono::high_resolution_clock::now(); // Subsequent calls should be faster (cached) - auto start2 = std::chrono::high_resolution_clock::now(); + std::chrono::high_resolution_clock::now(); int8_t codec2 = payload.codec(true); - auto end2 = std::chrono::high_resolution_clock::now(); + std::chrono::high_resolution_clock::now(); // Both should return the same result EXPECT_EQ(codec1, codec2);