Compare commits

..

380 Commits

Author SHA1 Message Date
dijunkun 34fb3fda92 [fix] use lowercase for nvcodec folder name 2025-05-06 18:32:52 +08:00
dijunkun 1a34c6bc1d [feat] use 'const DecodedFrame*' instead of 'const DecodedFrame&' in Decode() method 2025-04-25 17:30:29 +08:00
dijunkun ab74361fd3 [fix] fix compile error 2025-04-25 17:22:32 +08:00
dijunkun db26753eca [fix] use DecodedFrame ptr in decoding process 2025-04-25 17:19:12 +08:00
dijunkun fb00234498 [fix] fix crash duo to invalid target resolution 2025-04-18 15:34:03 +08:00
dijunkun 38c88e27e0 [feat] use original resolution ratio when upgrade/downgrade source frame 2025-04-16 17:22:04 +08:00
dijunkun df6f4321e8 [feat] use smart pointer for ReceivedFrame when send it into decoder 2025-04-14 16:11:25 +08:00
dijunkun bc3dd680f9 [fix] add override key word 2025-04-11 16:02:51 +08:00
dijunkun ded00d5c19 [fix] need double check for task queue 2025-04-11 15:38:53 +08:00
dijunkun 50fbc9cadf [fix] put resolution converter into task queue 2025-04-11 15:27:13 +08:00
dijunkun d40ca8814c [fix] fix nvcodec crash due to cuda context not being released 2025-04-10 17:10:40 +08:00
dijunkun f48d940b80 [feat] add user id in params 2025-04-09 15:45:30 +08:00
dijunkun 9972047199 [fix] fix audio rtp packet packetization 2025-04-07 15:21:10 +08:00
dijunkun dee2d4f5ca [fix] fix compiling error 2025-04-03 17:19:19 +08:00
dijunkun 772db42494 [fix] use task queue to process rtp packet history 2025-04-03 16:54:28 +08:00
dijunkun a36b352039 [fix] fix frame out of order in pending queue 2025-04-03 15:54:17 +08:00
dijunkun c69818bb1d [feat] use pending_frames_ instead of compelete_video_frame_queue_ in h264 frame assember 2025-04-02 17:34:46 +08:00
dijunkun f361347795 [feat] h264 frame assember refactoring 2025-04-01 18:12:15 +08:00
dijunkun e399260daa [fix] mark original sequence number as sent for rtx packets 2025-04-01 11:17:43 +08:00
dijunkun f9717f8481 [feat] modify the implementation of rtx packet 2025-03-31 17:34:28 +08:00
dijunkun 794d57eb40 [fix] fix crash due to unsupported bitrate limits 2025-03-28 17:37:35 +08:00
dijunkun 506ded3027 [fix] fix h264 frame assember 2025-03-28 15:16:41 +08:00
dijunkun 0d9d3c5eca [feat] complete h264 frame assember 2025-03-27 17:35:27 +08:00
dijunkun 0f07058fb9 [feat] refactor h264 frame assember 2025-03-26 17:39:47 +08:00
dijunkun 28dc9bf62d [feat] use decode thread to decode frame 2025-03-26 11:14:12 +08:00
dijunkun dff7948245 [fix] clear task queue before IceTransportController released 2025-03-25 17:36:33 +08:00
dijunkun bcf01791f7 [feat] use encode thread to encode frame 2025-03-25 17:18:52 +08:00
dijunkun 160ee9feef [fix] fix crash due to thread releasing 2025-03-24 17:39:29 +08:00
dijunkun d17b29dfa4 [fix] fix pacer module crash due to multi-thread 2025-03-21 16:49:43 +08:00
dijunkun 1d41b6499f [fix] audio and data rtp packets do not be sent by pacer module 2025-03-20 23:22:04 +08:00
dijunkun d79bcdf1e5 [feat] nack supported 2025-03-20 20:42:56 +08:00
dijunkun 7def8a9568 [fix] fix pacer rate setting 2025-03-20 15:17:42 +08:00
dijunkun b50175f943 [feat] add new classes EncodedFrame/DecodedFrame/ReceivedFrame for video frame module 2025-03-19 18:36:55 +08:00
dijunkun 1cd9ea1b0e [fix] fix video frame capture timestamp 2025-03-19 14:35:48 +08:00
dijunkun 257581e5e9 [fix] set SendBurstInterval and QueueTimeLimit when packet sender created 2025-03-18 17:38:07 +08:00
dijunkun 2d59c74669 [fix] fix decoding error caused by padding packets 2025-03-18 15:11:10 +08:00
dijunkun fa667df1e0 [feat] use packet sender to send all rtp packets 2025-03-17 18:44:29 +08:00
dijunkun b0306d510c [refactor] move channel module into transport module 2025-03-17 17:19:46 +08:00
dijunkun 5c598be51d [fix] fix padding packets building 2025-03-17 10:28:58 +08:00
dijunkun bd65d87137 [feat] add task queue module 2025-03-14 18:29:07 +08:00
dijunkun d2b45b91e7 [feat] move rtp packet sender out of channel module 2025-03-13 21:11:20 +08:00
dijunkun 23df1f3b60 [feat] add pacing controller module 2025-03-12 18:18:19 +08:00
dijunkun 2bf60a9c81 [fix] fix crash due to invalid XVideoFrame size 2025-03-11 22:57:00 +08:00
dijunkun 092e894622 [feat] bandwidth probing supported 2025-03-11 17:36:07 +08:00
dijunkun 7f2ebbde5a [fix] fix getting wrong resolution when using decoder->GetWidth() and decoder->GetHeight() for nv decoder 2025-03-10 22:41:10 +08:00
dijunkun 8b4ff8cd1f [fix] fix resolution adapter 2025-03-10 11:24:53 +08:00
dijunkun de0386f08a [feat] support FIR 2025-03-07 18:36:55 +08:00
dijunkun 19fc8cda89 [feat] enable resolution downgrading 2025-03-07 17:31:34 +08:00
dijunkun 706beaa2ce [fix] fix crash due to rebuilding h264 frame 2025-03-06 17:23:37 +08:00
dijunkun 84ba2d8339 [fix] fix payload length for sender report and receiver report 2025-03-05 17:49:17 +08:00
dijunkun 02f00642e9 [feat] clean rtp packet buffer queue when ice destroyed 2025-03-05 17:48:33 +08:00
dijunkun 0dbc0236bf [feat] add clear() method to Ringbuffer class 2025-03-05 17:45:51 +08:00
dijunkun cf374a0ff3 [feat] introduce fraction lost into congestion control module 2025-03-04 17:39:54 +08:00
dijunkun ebfeaf4754 [fix] fix receiver report building and parsing 2025-03-04 11:17:19 +08:00
dijunkun fba5993866 [feat] implementation for receiver report 2025-02-28 17:50:17 +08:00
dijunkun 3bb12e3f60 [fix] use ntp timestamp ms as video rtp timestamp 2025-02-28 15:52:21 +08:00
dijunkun 962d856946 [fix] fix sender report building and parsing 2025-02-27 17:30:41 +08:00
dijunkun b7a5066c6b [fix] fix rtcp common header 2025-02-26 17:30:24 +08:00
dijunkun ee70280056 [fix] fix ntp time calculation 2025-02-25 17:32:21 +08:00
dijunkun d1177747fd [feat] add timestamp to sender report 2025-02-21 17:36:32 +08:00
dijunkun a39d0f6652 [feat] use VideoFrameWrapper to store frame info 2025-02-21 16:13:31 +08:00
dijunkun f797cc3f91 [feat] use an adapter layer to convert SystemClock into webrtc Clock 2025-02-20 15:31:58 +08:00
dijunkun 899ab05cda [feat] add clock module 2025-02-19 17:03:33 +08:00
dijunkun cbf9ccc284 [fix] update sr and rr module 2025-02-18 18:39:06 +08:00
dijunkun 4aa9925e56 [feat] update sr and rr implementation 2025-02-17 17:32:20 +08:00
dijunkun 71b9c78dd5 [feat] update rtp packet history module 2025-02-17 17:05:45 +08:00
dijunkun 1ef7c536f1 [feat] add rtp packet history module 2025-02-14 17:30:12 +08:00
dijunkun 7b4bba4166 [feat] implementation for nack generator module 2025-02-13 17:24:18 +08:00
dijunkun 1db57bfc76 [feat] add nack module 2025-02-12 17:35:59 +08:00
dijunkun 36704c7e4c [feat] enable congestion controller set target bitrate to video encoder 2025-02-11 17:25:50 +08:00
dijunkun 7a8be01e7b [fix] move channels initialization into GetRemoteCapabilities() 2025-02-10 17:41:33 +08:00
dijunkun 59275af1ee [fix] remove including headerfile <minwinbase.h> on Windows 2025-02-10 17:40:32 +08:00
dijunkun 0595112d4f [fix] fix sent packet size in congestion control feedbacks 2025-02-10 16:06:19 +08:00
dijunkun 2c48ce12c5 [fix] fix compile error on MacOSX 2025-02-10 15:46:22 +08:00
dijunkun 1f3c93c77a [feat] add robust throughput estimator 2025-02-10 14:23:07 +08:00
dijunkun 61ac3a9971 [fix] fix timestamp in congestion control feedback 2025-02-08 17:59:30 +08:00
dijunkun 8d7068aa32 [feat] enable congestion controller 2025-02-07 17:42:05 +08:00
dijunkun 316a0220a8 [feat] add network controller into video send channel 2025-02-06 17:36:35 +08:00
dijunkun 3accdf2192 [fix] fix crash due to rtp receivers destroy 2025-02-06 17:08:26 +08:00
dijunkun 1d85247785 [fix] use shared ptr to create real time clock 2025-02-06 13:53:58 +08:00
dijunkun dbb31f8298 [fix] fix illegal access to IceTransport's member function in lambda 2025-02-06 11:10:08 +08:00
dijunkun 60d5885b8b [fix] fix nvidia h264 decoder return value 2025-02-06 10:03:23 +08:00
dijunkun 794e33c325 [fix] fix h264 rtp packetization error 2025-02-05 17:28:57 +08:00
dijunkun 2d5749f93a [fix] fix h264 rtp packet parse 2025-01-24 17:54:54 +08:00
dijunkun 7b839ab773 [fix] fix h264 rtp packet packetization and depacketization 2025-01-23 17:28:17 +08:00
dijunkun cd349cd98d [feat] rewrite rtp module 2025-01-22 17:32:24 +08:00
dijunkun ea592f5a58 [fix] fix crash due to rtp extension 2025-01-21 17:30:00 +08:00
dijunkun 477fd1f13b [fix] fix qos module 2025-01-20 18:41:52 +08:00
dijunkun 5bbd182a3f [fix] fix congestion control module 2025-01-17 17:33:13 +08:00
dijunkun 6e2a52e506 [feat] use original webrtc header defines 2025-01-16 17:33:46 +08:00
dijunkun a8e9609736 [feat] implementation for send side congestion controller 2025-01-14 17:31:18 +08:00
dijunkun ba268016e4 [feat] receive and parse congestion control feedback supported 2025-01-13 17:12:28 +08:00
dijunkun 63ed77e43a [feat] congestion control feedback sending support 2025-01-10 17:21:03 +08:00
dijunkun 49b74ffcd6 [fix] fix crash caused by RtpPacketReceived 2025-01-09 17:03:08 +08:00
dijunkun de212a8e75 [feat] implementation for qos module 2025-01-08 17:30:13 +08:00
dijunkun 7a84b25b5c [feat] update qos module 2025-01-07 17:31:14 +08:00
dijunkun 601fedfd76 [fix] fix inbound rtp statistics 2025-01-06 17:18:49 +08:00
dijunkun 0737eee95a [feat] update transport module and channel module 2025-01-06 17:10:56 +08:00
dijunkun eef35ff0d4 [feat] separate rtp send/receive module from ice transport module 2025-01-03 17:34:46 +08:00
dijunkun 7ddcca53e4 [fix] update qos module 2024-12-18 18:07:45 +08:00
dijunkun 2512e1eb15 [feat] update congestion control feedback 2024-12-18 17:27:42 +08:00
dijunkun c6d4b172fc [fix] fix transport feedback module 2024-12-12 16:37:11 +08:00
dijunkun bacf62c6b8 [feat] add rtcp packet module 2024-12-11 17:34:51 +08:00
dijunkun b29f3de868 [feat] SSRC supported for RTP packets 2024-12-09 17:08:22 +08:00
dijunkun 2cb92ddd72 [feat] add rtcp thread in rtp video receiver 2024-12-06 17:26:52 +08:00
dijunkun 348df6a4bf [fix] fix loss rate calculation 2024-12-04 23:33:11 +08:00
dijunkun 6e3c8c488d [feat] add user id in NetStatusReport 2024-12-03 17:20:31 +08:00
dijunkun 49d6307154 [fix] fix loss rate calculation 2024-12-01 17:00:20 +08:00
dijunkun b0fdcd9193 [fix] fix loss rate calculation 2024-11-29 18:20:26 +08:00
dijunkun daef6f19dc [feat] use the io statistics module to collect network information 2024-11-29 17:53:30 +08:00
dijunkun 11d84068a4 [fix] start to send data only when ice status turns to READY 2024-11-28 18:26:00 +08:00
dijunkun 21737c354a [fix] fix crash: ice worker still has IceWorkMsg::Type::Destroy msg which has not been processed when try to stop ice worker 2024-11-27 22:15:45 +08:00
dijunkun 1b7c8905b7 [fix] fix all unused variables and type conversions on MacOSX 2024-11-27 10:56:43 +08:00
dijunkun 826fc2d312 [fix] fix all unused variables and type conversions 2024-11-26 23:30:38 +08:00
dijunkun 7e3856a68d [fix] fix unused variables and type conversions 2024-11-26 17:31:16 +08:00
dijunkun 8d56a76844 [feat] rewrite log module 2024-11-26 16:03:08 +08:00
dijunkun 09c0ab9235 [fix] move codecs from pc into transmission module 2024-11-26 15:06:05 +08:00
dijunkun 98c6501846 [feat] enable to save ice io stream into local files 2024-11-26 00:57:07 +08:00
dijunkun 38fabc9741 [fix] fix the user id of net traffic stats 2024-11-19 20:04:27 +08:00
dijunkun c5c85f0785 [feat] net traffic stats supported 2024-11-18 17:32:42 +08:00
dijunkun 9d2e6f0c2a [fix] fix ice status error during closing ice connection 2024-10-30 17:11:35 +08:00
dijunkun 35d4f522c5 [fix] stop threads when DestroyIceTransmission() called 2024-10-18 17:15:33 +08:00
dijunkun 30c167e6cc [fix] use reliable ice by default 2024-10-16 15:27:21 +08:00
dijunkun b10e41ccab [fix] fix the function which converts yuv420p into nv12 2024-10-16 09:48:33 +08:00
dijunkun 6e622b4ab2 [fix] find out dav1d decode error 2024-10-15 17:36:18 +08:00
dijunkun 9a6def32fd [fix] fix av1 rtp packetizer 2024-10-15 10:29:18 +08:00
dijunkun c13cffb58e [fix] fix OBU_TEMPORAL_DELIMITER and OBU_SEQUENCE_HEADER obu data building 2024-10-14 17:31:08 +08:00
dijunkun 6bda59b1a7 [fix] obu data corrupted after transmission 2024-10-12 17:30:29 +08:00
dijunkun 1407f67d3c [fix] try to fix av1 packets parsering 2024-09-29 17:23:59 +08:00
dijunkun 7388a2c288 [fix] update obu parser 2024-09-27 17:30:24 +08:00
dijunkun 2740f31642 [feat] add aom av1 decoder implementation 2024-09-27 17:29:47 +08:00
dijunkun bbd60570a1 [fix] packetize kObuTypeTemporalDelimiter obu into rtp packets 2024-09-24 16:45:26 +08:00
dijunkun aa1bc1a936 [fix] fix the issue where created video codec is different from the one negotiated 2024-09-23 14:31:07 +08:00
dijunkun c7b934026b [feat] support getting codec name 2024-09-23 14:14:45 +08:00
dijunkun 5eb455b6c8 [feat] support using negotiated sdp to create media codecs 2024-09-23 11:13:53 +08:00
dijunkun f48e5a7350 [fix] fix capabilities negotiation 2024-09-20 17:30:45 +08:00
dijunkun 8083d4b4c9 [fix] fix audio payload type parsing 2024-09-19 17:32:33 +08:00
dijunkun 9d2e122fcc [fix] fix the issue where the media payload type parsing failed 2024-09-19 16:41:06 +08:00
dijunkun f4cf4d826b [feat] support multiple streams in sdp 2024-09-19 16:18:14 +08:00
dijunkun e48b29a2c8 [feat] implentation for negotiation module 2024-09-18 17:29:30 +08:00
dijunkun 0e3da6daf8 [fix] reset openh264 target bitrate 2024-09-18 09:46:45 +08:00
dijunkun a0bccfe53d [fix] increase the default size of the RingBuffer to 1280 to prevent handling overly large encoded video frame 2024-09-13 22:00:47 +08:00
dijunkun c67ce332f6 [fix] add rtp header version checker 2024-09-13 00:33:07 +08:00
dijunkun bbd05bcb8d [fix] fix crash due to accessing to invalid virtual function 2024-09-10 22:32:59 +08:00
dijunkun a8333c622b [fix] fix nvidia encoder crash during reconfigure the resolution 2024-09-10 17:32:43 +08:00
dijunkun 2f16d22ab7 [fix] fix openh264 encoder resolution change failed 2024-09-06 16:57:23 +08:00
dijunkun 255ef0edec [feat] openh264 encoder and dav1d decoder support dynamic resolution 2024-09-06 13:05:57 +08:00
dijunkun c477643aed [feat] support dynamic resolution codec 2024-09-05 17:28:58 +08:00
dijunkun c0c2b18b8b [feat] add statistics module 2024-09-04 17:31:08 +08:00
dijunkun d285d7971a [fix] fix crash when signal server close the connection actively 2024-09-04 17:03:09 +08:00
dijunkun d78dc4585f [feat] enable TURN for answer peer by default 2024-09-03 10:43:13 +08:00
dijunkun fd392922d7 [fix] only offer peer use use id list to create ice agent 2024-09-02 16:54:29 +08:00
dijunkun 98bd477af5 [fix] solve deadlock caused by destroy ice agent 2024-09-02 16:31:33 +08:00
dijunkun 0b11646619 [feat] put ice agent into ice worker thread and use message queue to handle events 2024-08-28 17:30:34 +08:00
dijunkun 0b0e61cdc4 [feat] add config param to control use TURN or not 2024-08-27 17:05:41 +08:00
dijunkun 35f26283de [fix] change video receiver check compelete frame frequency 2024-08-21 17:10:18 +08:00
dijunkun 6ae12771c2 [fix] check the return value is DAV1D_ERR(EAGAIN) or not for method dav1d_get_picture() and dav1d_send_data() 2024-08-20 17:09:55 +08:00
dijunkun 949aa804e7 [fix] set g_threads = 8 and AOME_SET_CPUUSED = 10 for av1 encoder 2024-08-20 16:46:40 +08:00
dijunkun 5536311920 [fix] use 'CAMERA_VIDEO_REAL_TIME' instead of 'SCREEN_CONTENT_REAL_TIME' for openh264 encoder because the latter will cause slow encoding 2024-08-20 16:11:22 +08:00
dijunkun e9be021c0f [fix] allow data sending once ice connected 2024-08-20 10:20:53 +08:00
dijunkun 4eac29b6de [fix] remove libyuv shared library (*.dylib) on MacOSX 2024-08-16 17:17:12 +08:00
dijunkun 61b57dd3fe [fix] fix codec creating on MacOSX 2024-08-16 15:37:35 +08:00
dijunkun d6599abf81 [fix] fix DestroyPeer() method object delete 2024-08-14 16:37:54 +08:00
dijunkun c056bb6f7d [fix] fix websocket ping thread blocking main thread during program exit 2024-08-14 16:36:32 +08:00
dijunkun 5f8e60d1c8 [fix] fix compile error on MacOSX 2024-08-13 16:45:13 +08:00
dijunkun e2e053a285 [fix] release NvCodec DLL only when loaded success 2024-08-13 16:29:37 +08:00
dijunkun 4624d4f27f [fix] fix load NvCodec API failed on Windows 2024-08-13 16:10:00 +08:00
dijunkun 9e30203e90 [fix] fix crash due to codec init failed 2024-08-13 11:13:29 +08:00
dijunkun f02286365c [fix] use dynamic DLL loading for cuda library 2024-08-12 17:26:51 +08:00
dijunkun 1626b482de [fix] fix send error when ice state change from ready to connected 2024-08-09 16:53:48 +08:00
dijunkun 10cb335779 [feat] use trickle ice by default 2024-08-09 10:43:26 +08:00
dijunkun 911f209fda [fix] use proper std::chrono clock 2024-08-09 10:23:21 +08:00
dijunkun a308094fbd [feat] add Login() private method which uses to check/request use_id when websocket opened 2024-08-08 15:13:38 +08:00
dijunkun dd8ab05d0f [feat] LeaveConnection() method needs to specific which transmission wants to leave 2024-08-07 17:32:22 +08:00
dijunkun 8dc96eeb4c [fix] fix crash due to empty pointer 2024-08-06 17:31:29 +08:00
dijunkun 3453d4e0c4 [feat] Use server to generate transmission id and client id 2024-08-06 17:26:46 +08:00
dijunkun 0edeec3d16 [fix] fix crash due to yuv420p convert to nv12 2024-08-02 16:13:49 +08:00
dijunkun 66fc4d3f95 [feat] update dav1d to 1.4.3 2024-07-31 17:41:58 +08:00
dijunkun 54179722e5 [feat] add callback to notify the travsesal mode 2024-07-30 17:31:44 +08:00
dijunkun af49ebe63d [feat] Support trickle ice 2024-07-29 16:47:51 +08:00
dijunkun 15419cc313 [feat] Implementation for trickle ice 2024-07-26 17:34:31 +08:00
dijunkun e73f9b3457 Fix crash: send audio data when encoder has not inited yet 2024-07-24 16:11:40 +08:00
dijunkun 792a286899 Remove log.cpp 2024-07-17 14:44:18 +08:00
dijunkun 28fb35faf5 Check ice status by using ice_transmission::state_ instead of nice_agent_get_component_state() method 2024-06-14 13:43:21 +08:00
dijunkun c575a9170c 1.Use std::move to initialize std::thread; 2.Fix thread cannot exit error 2024-06-13 15:46:05 +08:00
dijunkun eee3b2a95e Do not request member list when host leaves 2024-06-07 16:21:15 +08:00
dijunkun caba77765d Fix symbol export error 2024-06-06 15:22:34 +08:00
dijunkun 5e804349d9 Add destroy method for peer instance 2024-06-06 15:09:25 +08:00
dijunkun 460d644d64 Support read configure params from input directly 2024-06-04 17:38:23 +08:00
dijunkun 0adaf357ec Support user date in peer instance and callback functions 2024-05-30 17:26:49 +08:00
dijunkun 2c640db255 Allow connecting to itself 2024-05-30 16:08:21 +08:00
dijunkun 9122d0d15f Add default constructor and virtual destructor for interface class VideoDecoder 2024-05-28 16:38:36 +08:00
dijunkun 3ea4d0724d Specify the version of thirdparty libraries 2024-05-28 16:26:00 +08:00
dijunkun 5d6861233e Use self compilied openh264 instead of xmake-repo 2024-05-28 15:23:07 +08:00
dijunkun 28f9d9cfd6 Fix priority of '<<' and '+' 2024-05-28 09:57:07 +08:00
dijunkun 5deb52ce2d Update spdlog to v1.14.1 and fix compile error 2024-05-24 15:04:40 +08:00
dijunkun a20927ec6c Fix libyuv build error 2024-05-24 15:03:30 +08:00
dijunkun 3fc8f9f616 Fix compile error on Linux platform and use c++ style header file(<cstddef> instead of <stddef.h>) 2024-05-23 15:04:18 +08:00
dijunkun 5decc4b007 Add description and version information for openfec package 2024-05-22 16:30:52 +08:00
dijunkun fdb8819926 Use remote packages for aom and libyuv 2024-05-22 16:17:05 +08:00
dijunkun c95a2a32dc 1.Not specify vcpkg::libnice version and it makes compiling process easier; 2.Remove ffmpeg from thirdparty 2024-05-22 14:04:43 +08:00
dijunkun 57ff14ada4 Remove dependency on FFmpeg 2024-05-17 17:55:25 +08:00
dijunkun 6df90ff55a Fix AV1 codec on MacOS 2024-05-10 14:44:34 +08:00
dijunkun 1439751ae3 Update config.ini 2024-05-09 17:01:19 +08:00
dijunkun 83861a8ba0 Fix H264 decode error due to sequence number checking 2024-05-09 17:00:21 +08:00
dijunkun c8d21794f5 Remove the temporal delimiter OBU during transmition 2024-05-09 16:53:56 +08:00
dijunkun 6bc8aaabdc 1.Use libyuv instead of ffmpeg to do nv12<->yuv420p convertion;2.Use local package to build libyuv(branch stable 2021.4.28 commit eb6e7bb63738e29efd82ea3cf2a115238a89fa51) 2024-05-08 16:34:53 +08:00
dijunkun e0d2ab5a9f Fix blurred screen caused by yuv to nv12 error 2024-05-08 14:27:57 +08:00
dijunkun 114c80cd72 Complete AV1 frame rtp packetizer and depacketizer 2024-05-07 17:13:14 +08:00
dijunkun 95da7ff52d Fix error during encoding obu packet into rtp packet 2024-05-06 17:21:33 +08:00
dijunkun 182c7dbec6 Finish AV1 rtp packetizer 2024-04-28 16:13:37 +08:00
dijunkun 35857488dc Fix crash caused by obu payload assigning to rtp packet 2024-04-26 17:21:07 +08:00
dijunkun 8d22d1855c Implement single OBU packetizer 2024-04-26 16:38:02 +08:00
dijunkun 76465a95c2 Fix OBU total length error 2024-04-24 17:27:53 +08:00
dijunkun 5deaacab51 Fix OBU header parse error when payload size equals to 0 2024-04-23 11:29:58 +08:00
dijunkun dfc72b5ccc Add parser for OBU packets 2024-04-22 17:32:25 +08:00
dijunkun f5586a7922 Implementation for av1 codec 2024-04-19 17:35:19 +08:00
dijunkun a309627ca3 1.Using c++14; 2.Using {} to initialize std::atomic 2023-12-22 15:46:28 +08:00
dijunkun d6cd6a8099 Use realloc in copy assignment operator 2023-12-22 14:12:56 +08:00
dijunkun 82dafa9782 Add 1 second time interval for retry join transmission 2023-12-18 15:23:44 +08:00
dijunkun 0b80124b3c Fix unused variables warnning 2023-12-13 17:23:20 +08:00
dijunkun d4abc318a4 Use reliable ice by default 2023-12-06 15:22:01 +08:00
dijunkun 2b0a3938bc Use vcpkg libnice and openh264 instead of brew on MacOS 2023-12-05 15:17:00 +08:00
dijunkun 6a6e922fc1 Fix connection status error: send packets only when ice status is ready 2023-12-05 15:12:18 +08:00
dijunkun 936348ba9e Fix h264 frame size error 2023-12-05 15:09:25 +08:00
dijunkun 62f85bb333 Add signal connection status 'NoSuchTransmissionId' 2023-12-04 14:04:17 +08:00
dijunkun 25c345f675 Fix ffmpeg link error on MacOS 2023-12-04 11:19:31 +08:00
dijunkun 13ea652278 Use openh264 as default codec for MacOS 2023-12-01 17:15:57 +08:00
dijunkun 052d479004 Fix crash caused by invalid iterator access 2023-12-01 17:00:49 +08:00
dijunkun f99a9bea13 Support openh264 codec on MacOS 2023-12-01 16:53:21 +08:00
dijunkun 3eef1f8d84 Disable ffmpeg openh264 codec 2023-11-29 22:57:03 -08:00
dijunkun 079ceddae7 Fix api export define error 2023-11-29 22:02:45 -08:00
dijunkun 3a1be00ca5 Opus codec module test pass 2023-11-29 19:16:12 -08:00
dijunkun d79720532d Optimize build script 2023-11-22 17:00:52 +08:00
dijunkun c8d404ea7d Fix compile error on Windows 2023-11-22 15:15:40 +08:00
dijunkun e44c5b1cc7 Add opus codec test 2023-11-21 22:30:25 -08:00
dijunkun 19506af831 Split applications from this project 2023-11-19 23:05:22 -08:00
dijunkun b68d6820bb Auto login and no password by default 2023-11-17 17:27:13 +08:00
dijunkun 256f3611ba Fix mouse mapping error 2023-11-17 17:18:29 +08:00
dijunkun 2a9760c5e5 Fix openh264 compile error on MacOS 2023-11-17 16:59:27 +08:00
dijunkun 0b90feed79 Support generate makefile 2023-11-17 00:38:13 -08:00
dijunkun bd2722f408 Disable FEC by default 2023-11-16 01:15:09 -08:00
dijunkun 4ec26a1fd7 Add closed status for ice 2023-11-16 00:50:55 -08:00
dijunkun 53a5f35fcd Do not refresh display frame if disconnected 2023-11-16 00:38:29 -08:00
dijunkun 9bb5dae126 Fix received_frame flag error in display client 2023-11-16 00:32:52 -08:00
dijunkun d901508adf Fix display error on Linux 2023-11-16 00:27:45 -08:00
dijunkun b0bee226d1 Implementation for FEC decoder module 2023-11-15 22:01:05 -08:00
dijunkun 31b5d7f2e3 Fix rtp header marker bit error 2023-11-14 18:58:47 -08:00
dijunkun 5c0a6646c8 Fix H264 decode error caused by fec packets 2023-11-14 18:55:08 -08:00
dijunkun fd667c8b86 Fix fec module type error 2023-11-14 00:17:14 -08:00
dijunkun e410e4e0c4 Fix OpenFEC compile error on Windows platform 2023-11-14 16:11:57 +08:00
dijunkun 270ad8df43 Implementation for FEC encoder module 2023-11-13 22:46:07 -08:00
dijunkun 2040db4eec Fix OpenFEC compile error on Windows platform 2023-11-10 10:00:09 +08:00
dijunkun 6d89a2aa35 Add library OpenFEC v1.4.2 2023-11-09 01:00:39 -08:00
dijunkun f833a503ae Use hardware codec by default 2023-11-09 14:42:36 +08:00
dijunkun 54529960d4 Set pong timeout interval to 1000ms 2023-11-09 14:38:02 +08:00
dijunkun a4681de246 Provide build option 'server_only' 2023-11-09 14:32:32 +08:00
dijunkun 325ec69f5f Implementation for websocket heartbeat 2023-11-09 10:40:42 +08:00
dijunkun 7409351b66 Fix x11 screen capture memory leakage 2023-11-08 01:20:02 -08:00
dijunkun 007134838b Fix openh264 decoder frame loss 2023-11-08 16:29:16 +08:00
dijunkun 27bfcb7621 Update ffmpeg codec configures 2023-11-08 15:54:08 +08:00
dijunkun 99360f5da2 Use OpenH264 as default ffmpeg codec 2023-11-07 00:11:23 -08:00
dijunkun e456ae3577 OpenH264 supports Windows platform 2023-11-07 15:27:57 +08:00
dijunkun 325f626fb5 Fix openh264 decode error caused by yuv stride 2023-11-06 19:02:39 -08:00
dijunkun 5ae756bf7f Implementation for openh264 codec 2023-11-03 02:31:57 -07:00
dijunkun 6e19e53603 1.ImGUI display success; 2.To do: fix x264 encode green screen 2023-11-02 02:34:14 -07:00
dijunkun 9be5d90a69 Fix ffmpeg link error on Linux platform 2023-11-01 21:42:35 -07:00
dijunkun 821d0cd68b Update x11 capture module 2023-10-31 02:35:53 -07:00
dijunkun 7d2c3dda4e Update linux capture module 2023-10-30 02:33:39 -07:00
dijunkun a79fc87c63 Update linux capture test 2023-10-30 10:25:35 +08:00
dijunkun 64e0b9767b Update linux capture test 2023-10-27 17:33:02 +08:00
dijunkun 344bddb863 Add test for x11grab 2023-10-27 14:44:41 +08:00
dijunkun 888e43b7d0 Fix warning when destroy connection in TURN mode 2023-10-18 14:53:45 +08:00
dijunkun 75e6e93069 Fix MacOSx compile warning by using linker flag '-ld_classic' 2023-10-17 14:52:54 +08:00
dijunkun 5e11e6057a Use function nice_agent_set_relay_info() to set turn info 2023-10-17 14:50:33 +08:00
dijunkun df42aaf225 Nice supports TURN mode 2023-10-17 14:00:12 +08:00
dijunkun 18b2a4a175 Fix nice agent crash 2023-10-17 10:54:40 +08:00
dijunkun ca4f379d3b Libjuice deprecated 2023-10-16 17:27:04 +08:00
dijunkun 027780160e Nice connection supports MacOSx 2023-10-16 13:56:21 +08:00
dijunkun a1940c9cf7 Libnice test pass 2023-10-14 19:13:40 +08:00
dijunkun 5b46218f9b Add libnice as ice agent 2023-10-13 17:34:06 +08:00
dijunkun 48b1d025fc Fix crash caused by server leave and rejoin 2023-10-11 17:30:29 +08:00
dijunkun 34274001dc Add callback for connection status 2023-10-11 17:01:14 +08:00
dijunkun 4261dff416 Not control mouse in DEBUG mode 2023-10-11 16:17:13 +08:00
dijunkun 4a9cc19b23 Add feature password 2023-10-11 16:03:22 +08:00
dijunkun 907daa581f Fix remote id cannot modify error 2023-10-11 15:20:53 +08:00
dijunkun a25b7f0aad Change signal server logfile name 2023-10-11 15:06:54 +08:00
dijunkun 24b94b8ffc Fix default display resolution to 720p and change resize strategy 2023-10-11 14:58:04 +08:00
dijunkun 682fa0ff17 Fix signal server member list error when rejoin same one connection 2023-10-11 14:26:57 +08:00
dijunkun 683235fa3f Fix ice status check error 2023-10-11 09:45:58 +08:00
dijunkun aba06f3289 Fix crash during rejoin connection 2023-10-10 17:09:24 +08:00
dijunkun 4efaccd5e4 Add password for connection 2023-10-10 16:21:04 +08:00
dijunkun 63db76b99c Fix windows compile error 2023-10-10 11:05:15 +08:00
dijunkun 78c2766730 Fix MacOSX compile error 2023-10-10 10:59:36 +08:00
dijunkun 766248be61 Finish remote desk gui 2023-10-10 10:31:32 +08:00
dijunkun 9f0653211e Combine server and client into a single program 2023-10-09 17:31:55 +08:00
dijunkun 50c6a24e26 Add ID input gui 2023-10-09 16:31:47 +08:00
dijunkun 4e06d111fa Fix mouse control 2023-10-09 15:05:10 +08:00
dijunkun 95d2d74979 Eable leave connection 2023-10-09 14:11:29 +08:00
dijunkun 85582f8339 Add main menu using imgui 2023-10-08 17:39:28 +08:00
dijunkun d42f4305b5 Use imgui 2023-10-08 16:33:53 +08:00
dijunkun 8cc04796b1 Rejoin transmission when server leaves 2023-10-08 14:30:54 +08:00
dijunkun 7de31fd78f Fix turn config error 2023-10-08 11:20:23 +08:00
dijunkun 40f3d19af6 Remove dependent for vcpkg::sdl2 2023-10-07 17:33:18 +08:00
dijunkun 8f14230a04 1.Fix rtp packet memory leakage; 2.Retry candidates gathering when failed 2023-10-07 15:48:12 +08:00
dijunkun e2e2fb53ae Make first frame for each connection into I frame 2023-10-07 15:32:06 +08:00
dijunkun bf4f5ca87d Fix cursor map error after resize client window 2023-10-07 15:13:24 +08:00
dijunkun 5c9b20a112 Fix cursor position error 2023-10-07 15:12:09 +08:00
dijunkun 678b38049e Fix error caused by type 'long' has difference size in difference platforms 2023-10-07 14:43:33 +08:00
dijunkun 0a7dd291f1 Fix MacOS compile error 2023-10-07 10:07:45 +08:00
dijunkun 36305eef46 Add shared lib for nvcodec 2023-09-22 17:28:51 +08:00
dijunkun 0e1da712f5 Add -ldl link flag for Linux platform 2023-09-22 15:50:40 +08:00
dijunkun 2299aee315 Fix compile compatibility 2023-09-22 14:50:37 +08:00
dijunkun aa198a4629 Remote desk client supports Linux platform 2023-09-22 14:48:21 +08:00
dijunkun 9355b670b8 Set I frame max qp to 22 for nvidia encoder 2023-09-21 17:02:35 +08:00
dijunkun 156172accb Add hardware accerleration valiation 2023-09-21 11:26:08 +08:00
dijunkun b4318cc8d6 Add global hardware acceleration codec switch 2023-09-21 10:42:16 +08:00
dijunkun a794cd43b9 Use factory to create encoder/decoder 2023-09-20 17:44:29 +08:00
dijunkun 3d4e1effe9 Support ffmpeg soft encode 2023-09-20 16:54:56 +08:00
dijunkun b16b29780b Remote desk client supports MacOS 2023-09-19 17:06:00 +08:00
dijunkun ebd7d87e91 Update ffmpeg decoder 2023-09-15 17:34:40 +08:00
dijunkun 44f787b360 Support ffmpeg soft decode for Winodws 2023-09-15 16:51:40 +08:00
dijunkun caf46a33bf Use lambda to capture video decode result 2023-09-14 17:32:33 +08:00
dijunkun df686461a5 Fix data parse error 2023-09-14 16:32:22 +08:00
dijunkun a0abb7455c Implementation for user data sending 2023-09-13 17:31:02 +08:00
dijunkun e2533d18e4 Implementation for rtcp receiver report 2023-09-13 15:11:25 +08:00
dijunkun ea74495b5a Fix rtcp header parse 2023-09-13 10:49:18 +08:00
dijunkun c1d31790d4 Implementation for rtcp sender report 2023-09-12 17:30:08 +08:00
dijunkun 8c545f7544 Add rtcp module 2023-09-11 17:23:32 +08:00
dijunkun 3a291fe171 Add rtp video statistics module 2023-09-11 16:15:46 +08:00
dijunkun a2d7bb7ff5 Change class name RtpVideoSession to RtpCodec 2023-09-11 15:23:51 +08:00
dijunkun f52142fc00 Start thread after created when use ThreadBase 2023-09-11 15:15:59 +08:00
dijunkun 0899fe2f1d Fix crash caused by multi threads during program termination 2023-09-11 14:45:22 +08:00
dijunkun 79c838629a Implementation for jitter 2023-09-08 17:45:01 +08:00
dijunkun dc11f50d82 Combine Fu-A subframes into complete h264 frame 2023-09-08 16:09:23 +08:00
dijunkun ab71838483 Fix default rtp type decode error 2023-09-07 17:42:09 +08:00
dijunkun b2e725e564 Implementation for FuA encode and decode 2023-09-07 17:20:38 +08:00
dijunkun 58c73f10c6 Nalu slices test pass 2023-09-07 15:46:26 +08:00
dijunkun 952bb02df5 Implementation for NALU 2023-09-06 17:30:58 +08:00
dijunkun cea0cfdb95 1.Implementation for rtp session module; 2.Separate signal server from main project 2023-09-06 11:29:34 +08:00
dijunkun 213318bfa3 Implementation for rtp module 2023-09-05 17:38:10 +08:00
dijunkun 3389dc5751 Add rtp module 2023-09-04 17:31:47 +08:00
dijunkun daf7caf5bb Use one thread to process render and mouse/key events 2023-09-02 00:20:55 +08:00
dijunkun 6989d58e47 Use vcpkg static ffmpeg lib for windows 2023-09-01 21:34:08 +08:00
dijunkun d20df60472 Fix crash caused by remote desk client leaves 2023-09-01 18:08:56 +08:00
dijunkun af285f4b5b Fix kcp transmission error 2023-08-31 17:49:08 +08:00
dijunkun 3c1f7973d0 Use kcp as QoS module 2023-08-30 17:44:22 +08:00
dijunkun a4cd77dcb0 Fix sdl2 resize window 2023-08-30 10:02:58 +08:00
dijunkun 1bef5a1cab Use SDL2 to display BGRA frame 2023-08-30 00:29:08 +08:00
dijunkun dcd5273cf6 Use sdl2 to display capture screen 2023-08-29 17:33:58 +08:00
dijunkun b9b836119f Remove files 2023-08-29 00:21:33 +08:00
dijunkun ed8c81540a wgc dll test pass 2023-08-28 17:19:46 +08:00
dijunkun 8f803cbd4c Add wgc demo 2023-08-28 00:53:55 +08:00
dijunkun 69ee2ed5d5 Add application folder 2023-08-24 17:36:28 +08:00
dijunkun 36f4367c79 Set data receive interface for test 2023-08-24 15:09:20 +08:00
dijunkun 28560cbac3 Remove websocket receive method in class IceTrnasmission 2023-08-24 14:36:34 +08:00
dijunkun dc84a0becf Add Qos module 2023-08-24 14:22:07 +08:00
dijunkun 1c0d80fa3a Optimize PeerConnection::Create() and PeerConnection::Join() 2023-08-24 10:25:23 +08:00
dijunkun e31405c78b Release ice instance when a user leave transmission 2023-08-24 09:57:55 +08:00
dijunkun 2e28ad61f8 Notify other users when a user websocket closed 2023-08-23 17:35:57 +08:00
dijunkun 932944ad86 Remove user from transmission when websocket closed 2023-08-23 17:16:20 +08:00
dijunkun 5a38aabb55 Support mesh connection 2023-08-23 09:43:05 +08:00
dijunkun d5c1c26fc9 Use user id instead of ice username 2023-08-21 17:31:44 +08:00
dijunkun 515f0c06bd Dev version: Support multiple ice connections 2023-08-18 17:35:53 +08:00
dijunkun 3a55dd0938 Support multiple ice connections 2023-08-17 17:31:08 +08:00
dijunkun a9db3d290b Refactor connection create process 2023-08-17 10:20:47 +08:00
dijunkun c46396a18a Use hash in switch method instead of map list 2023-08-16 16:01:53 +08:00
dijunkun 481f6af9e7 Fix answer query offer side sdp error 2023-08-01 10:05:07 +08:00
dijunkun ff8e1ed34d Fix crash caused by the initialization of answer peer ahead of offer peer 2023-07-31 15:21:32 +08:00
dijunkun 7ad8375ddc Fix stun error on answer side 2023-07-31 14:59:02 +08:00
dijunkun 5792ded4ac Modify interface 2023-07-20 09:50:44 +08:00
dijunkun fbfaf9ac75 Use relative path for test config files 2023-07-18 17:33:49 +08:00
dijunkun 5362d9591e Fix stun port error 2023-07-18 17:11:09 +08:00
dijunkun 03cb767f1f Use config to declare signal/stun server info 2023-07-18 16:58:53 +08:00
dijunkun ea9ffbd6d8 Add test interface for ice data transport 2023-07-18 11:10:43 +08:00
dijunkun 8276dd6c20 Fix crash during answer 2023-07-17 10:27:13 +08:00
dijunkun efc1e3bd85 Fix compile error for linux platform 2023-07-14 09:35:48 +08:00
dijunkun 1c064a1cff Define '_WEBSOCKETPP_CPP11_INTERNAL_' only for win 2023-07-14 09:23:56 +08:00
dijunkun 8cd87a2646 Use sourcecode for libjuice 2023-07-13 16:58:20 +08:00
dijunkun ef6a04dc97 Init project 2023-07-13 14:17:34 +08:00
797 changed files with 98125 additions and 113315 deletions
-20
View File
@@ -1,20 +0,0 @@
**
!.dockerignore
!docker/
!docker/linux-build/
!docker/linux-build/**
# Dependency manifests and local xmake package recipes are the only project
# files needed while pre-warming the build image.
!xmake.lua
!xmake/
!xmake/**
!thirdparty/
!thirdparty/**
!submodules/
!submodules/xmake.lua
!submodules/minirtc/
!submodules/minirtc/xmake.lua
!submodules/minirtc/thirdparty/
!submodules/minirtc/thirdparty/**
-3
View File
@@ -1,3 +0,0 @@
*.h linguist-language=C++
*.cpp linguist-language=C++
*.lua linguist-language=Xmake
-35
View File
@@ -1,35 +0,0 @@
---
name: 问题反馈
about: 请在此提交问题报告,以便持续优化产品。
title: ''
labels: bug
assignees: kunkundi
---
**描述问题**
清晰简洁地描述遇到的错误。
**复现步骤**
复现该问题的步骤:
1. 前往 '...'
2. 点击 '....'
3. 出现错误
**预期行为**
清晰简洁地描述你期望发生的行为。
**截图**
如果适用,请添加截图以帮助说明问题。
**桌面端信息(请填写以下内容):**
- 操作系统: [例如 Windows 11]
- 版本: [例如 v1.1.10]
**移动端信息(请填写以下内容):**
- 设备: [例如 iPhone 17]
- 操作系统: [例如 iOS 26.1]
- 浏览器: [例如 系统浏览器、Safari]
**补充信息**
在此添加与问题相关的其他上下文内容。
-28
View File
@@ -1,28 +0,0 @@
---
name: 需求建议
about: 请在此提交功能需求或改进建议,以便后续迭代参考。
title: ''
labels: enhancement
assignees: kunkundi
---
**功能/改进建议描述**
清晰简洁地描述希望新增的功能或改进的内容。
**使用场景 / 背景**
说明该功能或改进的使用场景,以及解决后带来的价值。
**预期效果**
描述你认为最理想的功能表现或改进效果。
**参考示例(可选)**
提供类似功能截图、参考链接或其他说明,帮助更好理解需求。
**优先级(可选)**
- [ ]
- [ ]
- [ ]
**补充信息(可选)**
其他相关信息或特殊要求。
-155
View File
@@ -1,155 +0,0 @@
name: Build Linux Build Image
on:
push:
branches:
- ci/linux-build-image
paths:
- .dockerignore
- .gitmodules
- .github/workflows/build-linux-image.yml
- docker/linux-build/**
- xmake.lua
- xmake/**
- thirdparty/**
- submodules/xmake.lua
- submodules/minirtc
- submodules/minirtc/xmake.lua
- submodules/minirtc/thirdparty/**
workflow_dispatch:
permissions:
contents: read
concurrency:
group: linux-build-image-${{ github.ref }}
cancel-in-progress: true
env:
IMAGE_NAME: crossdesk/ubuntu22.04
STACK_TAG: buildenv-cuda12.6.3-xmake3.0.9-rust1.92.0
XMAKE_VERSION: 3.0.9
RUST_VERSION: 1.92.0
RUSTUP_VERSION: 1.28.2
jobs:
build:
name: Build candidate (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 180
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
platform: linux/amd64
runner: ubuntu-24.04
base_image: nvidia/cuda:12.6.3-devel-ubuntu22.04
- arch: arm64
platform: linux/arm64
runner: ubuntu-24.04-arm
base_image: ubuntu:22.04
steps:
- name: Set candidate tag
id: image
run: |
CANDIDATE_TAG="${IMAGE_NAME}:${STACK_TAG}-${GITHUB_SHA}-${{ matrix.arch }}"
echo "CANDIDATE_TAG=${CANDIDATE_TAG}" >> "${GITHUB_ENV}"
echo "candidate=${CANDIDATE_TAG}" >> "${GITHUB_OUTPUT}"
- name: Checkout source
uses: actions/checkout@v5
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push candidate
uses: docker/build-push-action@v7
with:
context: .
file: docker/linux-build/Dockerfile
platforms: ${{ matrix.platform }}
push: true
pull: true
tags: ${{ steps.image.outputs.candidate }}
cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:${{ env.STACK_TAG }}
cache-to: type=inline
provenance: false
sbom: false
build-args: |
BASE_IMAGE=${{ matrix.base_image }}
XMAKE_VERSION=${{ env.XMAKE_VERSION }}
RUST_VERSION=${{ env.RUST_VERSION }}
RUSTUP_VERSION=${{ env.RUSTUP_VERSION }}
verify:
name: Verify candidate (${{ matrix.arch }})
needs: build
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: ubuntu-24.04
- arch: arm64
runner: ubuntu-24.04-arm
steps:
- name: Set candidate tag
run: |
CANDIDATE_TAG="${IMAGE_NAME}:${STACK_TAG}-${GITHUB_SHA}-${{ matrix.arch }}"
echo "CANDIDATE_TAG=${CANDIDATE_TAG}" >> "${GITHUB_ENV}"
- name: Log in to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Verify tools, architecture, and Ubuntu baseline
run: |
docker pull "${CANDIDATE_TAG}"
docker run --rm \
--env EXPECTED_ARCH="${{ matrix.arch }}" \
"${CANDIDATE_TAG}" \
verify-crossdesk-build-image
publish:
name: Publish multi-arch image
needs: verify
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Promote verified candidates
run: |
AMD64_IMAGE="${IMAGE_NAME}:${STACK_TAG}-${GITHUB_SHA}-amd64"
ARM64_IMAGE="${IMAGE_NAME}:${STACK_TAG}-${GITHUB_SHA}-arm64"
docker buildx imagetools create \
--tag "${IMAGE_NAME}:${STACK_TAG}" \
--tag "${IMAGE_NAME}:sha-${GITHUB_SHA}" \
--tag "${IMAGE_NAME}:latest" \
"${AMD64_IMAGE}" \
"${ARM64_IMAGE}"
- name: Inspect published manifest
run: |
docker buildx imagetools inspect "${IMAGE_NAME}:${STACK_TAG}"
-565
View File
@@ -1,565 +0,0 @@
name: Build and Release
on:
push:
branches:
- "**"
- "!ci/linux-build-image"
tags:
- "*"
workflow_dispatch:
inputs:
patch:
description: "Hotfix patch number, for example 1 or 2. Use 0 for a normal build."
required: false
default: "0"
permissions:
contents: write
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
build-linux:
name: Build Linux (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- arch: amd64
runner: ubuntu-24.04
image: crossdesk/ubuntu22.04:buildenv-cuda12.6.3-xmake3.0.9-rust1.92.0
package_script: ./scripts/linux/pkg_amd64.sh
- arch: arm64
runner: ubuntu-24.04-arm
image: crossdesk/ubuntu22.04:buildenv-cuda12.6.3-xmake3.0.9-rust1.92.0
package_script: ./scripts/linux/pkg_arm64.sh
container:
image: ${{ matrix.image }}
options: --user root
steps:
- name: Extract version number
shell: bash
run: |
VERSION_REF="${GITHUB_REF##*/}"
VERSION_BASE="${VERSION_REF#v}"
PATCH_NUMBER="${{ github.event.inputs.patch }}"
BUILD_DATE_OVERRIDE=""
if [[ ! "${PATCH_NUMBER}" =~ ^[0-9]+$ ]]; then
PATCH_NUMBER=0
fi
if [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)-([0-9]{8})$ ]]; then
VERSION_BASE="${BASH_REMATCH[1]}"
PATCH_NUMBER="${BASH_REMATCH[3]}"
BUILD_DATE_OVERRIDE="${BASH_REMATCH[4]}"
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$ ]]; then
VERSION_BASE="${BASH_REMATCH[1]}"
BUILD_DATE_OVERRIDE="${BASH_REMATCH[3]}"
PATCH_NUMBER="${BASH_REMATCH[4]}"
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})$ ]]; then
VERSION_BASE="${BASH_REMATCH[1]}"
BUILD_DATE_OVERRIDE="${BASH_REMATCH[3]}"
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)$ && "${PATCH_NUMBER}" == "0" ]]; then
VERSION_BASE="${BASH_REMATCH[1]}"
PATCH_NUMBER="${BASH_REMATCH[3]}"
fi
echo "VERSION_BASE=${VERSION_BASE}" >> $GITHUB_ENV
echo "PATCH_NUMBER=${PATCH_NUMBER}" >> $GITHUB_ENV
echo "BUILD_DATE_OVERRIDE=${BUILD_DATE_OVERRIDE}" >> $GITHUB_ENV
- name: Set legal Debian version
shell: bash
run: |
BUILD_DATE="${BUILD_DATE_OVERRIDE}"
if [[ -z "${BUILD_DATE}" ]]; then
BUILD_DATE=$(TZ=Asia/Shanghai date +%Y%m%d)
fi
if [[ ! "${VERSION_BASE}" =~ ^[0-9] ]]; then
VERSION_BASE="0.0.0-${VERSION_BASE}"
fi
if [[ "${PATCH_NUMBER}" != "0" ]]; then
LEGAL_VERSION="v${VERSION_BASE}-${PATCH_NUMBER}-${BUILD_DATE}"
else
LEGAL_VERSION="v${VERSION_BASE}-${BUILD_DATE}"
fi
echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV
echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_ENV
- name: Checkout code
uses: actions/checkout@v5
with:
submodules: recursive
- name: Verify Linux build image
run: verify-crossdesk-build-image
- name: Build CrossDesk
env:
CUDA_PATH: /usr/local/cuda
XMAKE_GLOBALDIR: /data
run: |
xmake f --CROSSDESK_VERSION=${LEGAL_VERSION} --USE_CUDA=true --root -y
xmake b -vy --root crossdesk
- name: Package
run: |
chmod +x ${{ matrix.package_script }}
${{ matrix.package_script }} ${LEGAL_VERSION}
- name: Verify packaged Ubuntu 22.04 glibc baseline
run: |
PACKAGE_FILE="${GITHUB_WORKSPACE}/crossdesk-linux-${{ matrix.arch }}-${LEGAL_VERSION}.deb"
VERIFY_DIR="${RUNNER_TEMP}/crossdesk-glibc-${{ matrix.arch }}"
install -d "${VERIFY_DIR}"
dpkg-deb --extract "${PACKAGE_FILE}" "${VERIFY_DIR}"
verify-crossdesk-glibc-baseline "${VERIFY_DIR}" 2.35
- name: Upload artifact
uses: actions/upload-artifact@v6
with:
name: crossdesk-linux-${{ matrix.arch }}-${{ env.LEGAL_VERSION }}
path: ${{ github.workspace }}/crossdesk-linux-${{ matrix.arch }}-${{ env.LEGAL_VERSION }}.deb
# macOS
build-macos:
name: Build on macOS
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- arch: x64
runner: macos-15-intel
cache-key: intel
out-dir: ./build/macosx/x86_64/release/crossdesk
package_script: ./scripts/macosx/pkg_x64.sh
- arch: arm64
runner: macos-14
cache-key: arm
out-dir: ./build/macosx/arm64/release/crossdesk
package_script: ./scripts/macosx/pkg_arm64.sh
steps:
- name: Extract version number
id: version
shell: bash
run: |
VERSION_REF="${GITHUB_REF##*/}"
VERSION_BASE="${VERSION_REF#v}"
BUILD_DATE=$(TZ=Asia/Shanghai date +%Y%m%d)
PATCH_NUMBER="${{ github.event.inputs.patch }}"
if [[ ! "${PATCH_NUMBER}" =~ ^[0-9]+$ ]]; then
PATCH_NUMBER=0
fi
if [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)-([0-9]{8})$ ]]; then
VERSION_BASE="${BASH_REMATCH[1]}"
PATCH_NUMBER="${BASH_REMATCH[3]}"
BUILD_DATE="${BASH_REMATCH[4]}"
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$ ]]; then
VERSION_BASE="${BASH_REMATCH[1]}"
BUILD_DATE="${BASH_REMATCH[3]}"
PATCH_NUMBER="${BASH_REMATCH[4]}"
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})$ ]]; then
VERSION_BASE="${BASH_REMATCH[1]}"
BUILD_DATE="${BASH_REMATCH[3]}"
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)$ && "${PATCH_NUMBER}" == "0" ]]; then
VERSION_BASE="${BASH_REMATCH[1]}"
PATCH_NUMBER="${BASH_REMATCH[3]}"
fi
if [[ "${PATCH_NUMBER}" != "0" ]]; then
VERSION_NUM="v${VERSION_BASE}-${PATCH_NUMBER}-${BUILD_DATE}"
else
VERSION_NUM="v${VERSION_BASE}-${BUILD_DATE}"
fi
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
echo "VERSION_NUM=${VERSION_NUM}"
echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_ENV
echo "PATCH_NUMBER=${PATCH_NUMBER}" >> $GITHUB_ENV
- name: Cache xmake dependencies
uses: actions/cache@v5
with:
path: ~/.xmake/packages
key: "${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}-${{ github.run_id }}"
restore-keys: |
${{ runner.os }}-xmake-deps-${{ matrix.cache-key }}-
- name: Install xmake
run: brew install xmake
- name: Update xmake repositories
run: xmake repo -u
- name: Checkout code
uses: actions/checkout@v5
- name: Initialize submodules
run: git submodule update --init --recursive
- name: Build CrossDesk
run: |
xmake f --CROSSDESK_VERSION=${VERSION_NUM} --USE_CUDA=true -y
xmake b -vy crossdesk
- name: Package CrossDesk app
run: |
chmod +x ${{ matrix.package_script }}
${{ matrix.package_script }} ${VERSION_NUM}
- name: Upload build artifacts
uses: actions/upload-artifact@v6
with:
name: crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}
path: crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}.pkg
- name: Move files to release dir
run: |
mkdir -p release
cp crossdesk-macos-${{ matrix.arch }}-${{ env.VERSION_NUM }}.pkg release/
# Windows
build-windows-x64:
name: Build on Windows x64
runs-on: windows-2022
env:
XMAKE_GLOBALDIR: D:\xmake_global
steps:
- name: Extract version number
shell: pwsh
run: |
$ref = $env:GITHUB_REF
$version = $ref -replace '^refs/(tags|heads)/', ''
$version = $version -replace '^v', ''
$version = $version -replace '/', '-'
$BUILD_DATE = ([System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId((Get-Date), "China Standard Time")).ToString("yyyyMMdd")
$PATCH_NUMBER = "${{ github.event.inputs.patch }}"
if ($PATCH_NUMBER -notmatch '^[0-9]+$') {
$PATCH_NUMBER = "0"
}
if ($version -match '^([0-9]+(\.[0-9]+){1,3})-([0-9]+)-([0-9]{8})$') {
$version = $Matches[1]
$PATCH_NUMBER = $Matches[3]
$BUILD_DATE = $Matches[4]
} elseif ($version -match '^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$') {
$version = $Matches[1]
$BUILD_DATE = $Matches[3]
$PATCH_NUMBER = $Matches[4]
} elseif ($version -match '^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})$') {
$version = $Matches[1]
$BUILD_DATE = $Matches[3]
} elseif ($version -match '^([0-9]+(\.[0-9]+){1,3})-([0-9]+)$' -and $PATCH_NUMBER -eq "0") {
$version = $Matches[1]
$PATCH_NUMBER = $Matches[3]
}
if ($PATCH_NUMBER -ne "0") {
$VERSION_NUM = "v$version-$PATCH_NUMBER-$BUILD_DATE"
} else {
$VERSION_NUM = "v$version-$BUILD_DATE"
}
echo "VERSION_NUM=$VERSION_NUM" >> $env:GITHUB_ENV
echo "BUILD_DATE=$BUILD_DATE" >> $env:GITHUB_ENV
echo "PATCH_NUMBER=$PATCH_NUMBER" >> $env:GITHUB_ENV
- name: Cache xmake dependencies
uses: actions/cache@v5
with:
path: D:\xmake_global\.xmake\packages
key: "${{ runner.os }}-xmake-deps-intel-${{ github.run_id }}"
restore-keys: |
${{ runner.os }}-xmake-deps-intel-
- name: Install xmake
run: |
Invoke-Expression (Invoke-Webrequest 'https://raw.githubusercontent.com/tboox/xmake/master/scripts/get.ps1' -UseBasicParsing).Content
echo "C:\Users\runneradmin\xmake" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
xmake repo -u
xmake create cuda
Set-Location cuda
xmake g --theme=plain
$cudaPath = ""
$packagesPath = "D:\xmake_global\.xmake\packages"
if (Test-Path $packagesPath) {
Write-Host "Packages directory exists: $packagesPath"
try {
$info = xmake require --info "cuda 12.6.3" 2>$null
if ($null -ne $info -and $info -ne "") {
$cudaPath = (($info | Select-String installdir).ToString() -replace '.*installdir:\s*','').Trim()
}
} catch {}
} else {
Write-Host "Packages directory not found: $packagesPath"
Write-Host "Installing CUDA package..."
xmake require -vy "cuda 12.6.3"
$info = xmake require --info "cuda 12.6.3"
$cudaPath = (($info | Select-String installdir).ToString() -replace '.*installdir:\s*','').Trim()
}
echo "CUDA_PATH=$cudaPath" >> $env:GITHUB_ENV
Write-Host "Resolved CUDA_PATH = $cudaPath"
Pop-Location
- name: Download INetC plugin
shell: powershell
run: |
$url = "https://github.com/DigitalMediaServer/NSIS-INetC-plugin/releases/download/v1.0.5.7/InetC.zip"
$out = "$env:RUNNER_TEMP\InetC.zip"
Invoke-WebRequest -Uri $url -OutFile $out
Expand-Archive $out -DestinationPath "$env:RUNNER_TEMP\InetC" -Force
$source = "$env:RUNNER_TEMP\InetC\x86-unicode\INetC.dll"
$target = "C:\Program Files (x86)\NSIS\Plugins\x86-unicode\INetC.dll"
Write-Host "Copying $source to $target"
Copy-Item $source $target -Force
- name: Checkout code
uses: actions/checkout@v5
- name: Initialize submodules
run: git submodule update --init --recursive
- name: Copy nsProcess plugin to NSIS folder
run: |
$nsisPluginDir = "C:\Program Files (x86)\NSIS\Plugins\x86-unicode"
copy "${{ github.workspace }}\scripts\windows\nsProcess.dll" $nsisPluginDir
- name: Build CrossDesk
run: |
xmake f --CROSSDESK_VERSION=${{ env.VERSION_NUM }} --USE_CUDA=true -y
xmake b -vy crossdesk
- name: Package
shell: pwsh
run: |
& "${{ github.workspace }}\scripts\windows\pkg_x64.ps1" $env:VERSION_NUM
- name: Build Portable CrossDesk
run: |
xmake f --CROSSDESK_VERSION=${{ env.VERSION_NUM }} --USE_CUDA=true --CROSSDESK_PORTABLE=true -y
xmake b -vy crossdesk
- name: Package Portable
shell: pwsh
run: |
$buildDir = "${{ github.workspace }}\build\windows\x64\release"
$portableDir = "${{ github.workspace }}\portable"
New-Item -ItemType Directory -Force -Path $portableDir
$portableFiles = @(
@("crossdesk.exe", "CrossDesk.exe"),
@("crossdesk_service.exe", "crossdesk_service.exe"),
@("crossdesk_session_helper.exe", "crossdesk_session_helper.exe")
)
foreach ($file in $portableFiles) {
$source = Join-Path $buildDir $file[0]
$destination = Join-Path $portableDir $file[1]
if (!(Test-Path $source)) {
throw "Missing portable package file: $source"
}
Copy-Item $source $destination -Force
}
Copy-Item (Join-Path $buildDir "*.dll") $portableDir -Force
foreach ($file in $portableFiles) {
$packagedFile = Join-Path $portableDir $file[1]
if (!(Test-Path $packagedFile)) {
throw "Portable package is missing: $packagedFile"
}
}
Compress-Archive -Path "$portableDir\*" -DestinationPath "${{ github.workspace }}\crossdesk-win-x64-portable-${{ env.VERSION_NUM }}.zip"
- name: Upload artifact
uses: actions/upload-artifact@v6
with:
name: crossdesk-win-x64-${{ env.VERSION_NUM }}
path: ${{ github.workspace }}/scripts/windows/crossdesk-win-x64-${{ env.VERSION_NUM }}.exe
- name: Upload portable artifact
uses: actions/upload-artifact@v6
with:
name: crossdesk-win-x64-portable-${{ env.VERSION_NUM }}
path: ${{ github.workspace }}/crossdesk-win-x64-portable-${{ env.VERSION_NUM }}.zip
release:
name: Publish Release
if: startsWith(github.ref, 'refs/tags/v')
needs:
[build-linux, build-macos, build-windows-x64]
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Download all artifacts
uses: actions/download-artifact@v8
with:
path: artifacts
- name: Extract version number
id: version
shell: bash
run: |
VERSION_REF="${GITHUB_REF##*/}"
VERSION_BASE="${VERSION_REF#v}"
BUILD_DATE=$(TZ=Asia/Shanghai date +%Y%m%d)
PATCH_NUMBER="${{ github.event.inputs.patch }}"
if [[ ! "${PATCH_NUMBER}" =~ ^[0-9]+$ ]]; then
PATCH_NUMBER=0
fi
if [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)-([0-9]{8})$ ]]; then
VERSION_BASE="${BASH_REMATCH[1]}"
PATCH_NUMBER="${BASH_REMATCH[3]}"
BUILD_DATE="${BASH_REMATCH[4]}"
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$ ]]; then
VERSION_BASE="${BASH_REMATCH[1]}"
BUILD_DATE="${BASH_REMATCH[3]}"
PATCH_NUMBER="${BASH_REMATCH[4]}"
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})$ ]]; then
VERSION_BASE="${BASH_REMATCH[1]}"
BUILD_DATE="${BASH_REMATCH[3]}"
elif [[ "${VERSION_BASE}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)$ && "${PATCH_NUMBER}" == "0" ]]; then
VERSION_BASE="${BASH_REMATCH[1]}"
PATCH_NUMBER="${BASH_REMATCH[3]}"
fi
BUILD_DATE_ISO="${BUILD_DATE:0:4}-${BUILD_DATE:4:2}-${BUILD_DATE:6:2}"
if [[ "${PATCH_NUMBER}" != "0" ]]; then
VERSION_NUM="${VERSION_BASE}-${PATCH_NUMBER}-${BUILD_DATE}"
else
VERSION_NUM="${VERSION_BASE}-${BUILD_DATE}"
fi
VERSION_WITH_V="v${VERSION_NUM}"
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_OUTPUT
echo "VERSION_WITH_V=${VERSION_WITH_V}" >> $GITHUB_OUTPUT
echo "VERSION_BASE=${VERSION_BASE}" >> $GITHUB_OUTPUT
echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_OUTPUT
echo "BUILD_DATE_ISO=${BUILD_DATE_ISO}" >> $GITHUB_OUTPUT
echo "PATCH_NUMBER=${PATCH_NUMBER}" >> $GITHUB_OUTPUT
- name: Rename artifacts
run: |
mkdir -p release
cp artifacts/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_WITH_V }}.pkg
cp artifacts/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_WITH_V }}.pkg
cp artifacts/crossdesk-linux-amd64-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-linux-amd64-${{ steps.version.outputs.VERSION_WITH_V }}.deb
cp artifacts/crossdesk-linux-arm64-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-linux-arm64-${{ steps.version.outputs.VERSION_WITH_V }}.deb
cp artifacts/crossdesk-win-x64-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-win-x64-${{ steps.version.outputs.VERSION_WITH_V }}.exe
cp artifacts/crossdesk-win-x64-portable-${{ steps.version.outputs.VERSION_WITH_V }}/* release/crossdesk-win-x64-portable-${{ steps.version.outputs.VERSION_WITH_V }}.zip
- name: List release files
run: ls -lh release/
- name: Upload to Versioned GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version.outputs.VERSION_WITH_V }}
name: Release ${{ steps.version.outputs.VERSION_WITH_V }}
draft: false
prerelease: false
files: release/*
generate_release_notes: false
body: |
Binary release only. Source code is not included.
- name: Create or update 'latest' tag
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -f latest
git push origin latest --force
- name: Upload to GitHub Release (latest)
uses: softprops/action-gh-release@v2
with:
tag_name: latest
name: Latest Release
draft: false
prerelease: false
files: release/*
generate_release_notes: false
- name: Upload artifacts to server
uses: burnett01/rsync-deployments@5.2
with:
switches: -avzr --progress --delete
path: release/*
remote_path: /var/www/html/downloads/
remote_host: ${{ secrets.SERVER_HOST }}
remote_user: ${{ secrets.SERVER_USER }}
remote_key: ${{ secrets.SERVER_KEY }}
- name: Generate version.json
run: |
cat > version.json << EOF
{
"latest_version": "${{ steps.version.outputs.VERSION_NUM }}",
"version": "${{ steps.version.outputs.VERSION_NUM }}",
"releaseDate": "${{ steps.version.outputs.BUILD_DATE_ISO }}",
"patch": ${{ steps.version.outputs.PATCH_NUMBER }},
"releaseName": "",
"releaseNotes": "",
"tagName": "${{ steps.version.outputs.VERSION_WITH_V }}",
"downloads": {
"windows-x64": {
"url": "https://downloads.crossdesk.cn/crossdesk-win-x64-${{ steps.version.outputs.VERSION_WITH_V }}.exe",
"filename": "crossdesk-win-x64-${{ steps.version.outputs.VERSION_WITH_V }}.exe"
},
"windows-x64-portable": {
"url": "https://downloads.crossdesk.cn/crossdesk-win-x64-portable-${{ steps.version.outputs.VERSION_WITH_V }}.zip",
"filename": "crossdesk-win-x64-portable-${{ steps.version.outputs.VERSION_WITH_V }}.zip"
},
"macos-x64": {
"url": "https://downloads.crossdesk.cn/crossdesk-macos-x64-${{ steps.version.outputs.VERSION_WITH_V }}.pkg",
"filename": "crossdesk-macos-x64-${{ steps.version.outputs.VERSION_WITH_V }}.pkg"
},
"macos-arm64": {
"url": "https://downloads.crossdesk.cn/crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_WITH_V }}.pkg",
"filename": "crossdesk-macos-arm64-${{ steps.version.outputs.VERSION_WITH_V }}.pkg"
},
"linux-amd64": {
"url": "https://downloads.crossdesk.cn/crossdesk-linux-amd64-${{ steps.version.outputs.VERSION_WITH_V }}.deb",
"filename": "crossdesk-linux-amd64-${{ steps.version.outputs.VERSION_WITH_V }}.deb"
},
"linux-arm64": {
"url": "https://downloads.crossdesk.cn/crossdesk-linux-arm64-${{ steps.version.outputs.VERSION_WITH_V }}.deb",
"filename": "crossdesk-linux-arm64-${{ steps.version.outputs.VERSION_WITH_V }}.deb"
}
}
}
EOF
cat version.json
- name: Upload version.json to server
uses: burnett01/rsync-deployments@5.2
with:
switches: -avzr --delete
path: version.json
remote_path: /var/www/html/version/
remote_host: ${{ secrets.SERVER_HOST }}
remote_user: ${{ secrets.SERVER_USER }}
remote_key: ${{ secrets.SERVER_KEY }}
-69
View File
@@ -1,69 +0,0 @@
name: Close Inactive Issues
on:
schedule:
# run every day at midnight
- cron: "0 0 * * *"
workflow_dispatch:
permissions:
issues: write
pull-requests: write
contents: read
jobs:
close_inactive_issues:
runs-on: ubuntu-latest
steps:
- name: Check inactive issues and close them
uses: actions/github-script@v7
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
script: |
const inactivePeriod = 7 * 24 * 60 * 60 * 1000; // 7 days
const now = Date.now();
// paginate through all open issues (listForRepo also returns PRs)
const issues = await github.paginate(github.rest.issues.listForRepo, {
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
});
for (const issue of issues) {
// skip pull requests (they are also returned by listForRepo)
if (issue.pull_request) continue;
// skip labeled issues
if (issue.labels.length > 0) {
console.log(`Skipping issue #${issue.number} (Has labels).`);
continue;
}
// last activity time = the issue's own updated_at, which is
// refreshed on comments, labels, etc. This avoids relying on
// fetching comments and is accurate even when comments are edited.
const lastActivityTime = new Date(issue.updated_at).getTime();
// check inactivity
if (now - lastActivityTime > inactivePeriod) {
console.log(`Closing inactive issue: #${issue.number} (No activity for 7 days)`);
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: "This issue has been automatically closed due to inactivity for 7 days."
});
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
state: 'closed',
});
} else {
console.log(`Skipping issue #${issue.number} (Active within 7 days).`);
}
}
-164
View File
@@ -1,164 +0,0 @@
name: Update version.json from Release
on:
release:
types: [published, edited]
permissions:
contents: write
jobs:
update-version-json:
name: Update version.json with release information
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version from tag
id: version
shell: bash
run: |
TAG_NAME="${{ github.event.release.tag_name }}"
TAG_VERSION="${TAG_NAME#v}"
VERSION_FULL="${TAG_VERSION}"
VERSION_BASE="${TAG_VERSION}"
PATCH_NUMBER=0
# Extract date and patch from tags such as v1.2.3-1-20251113.
if [[ "${TAG_VERSION}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]+)-([0-9]{8})$ ]]; then
VERSION_BASE="${BASH_REMATCH[1]}"
PATCH_NUMBER="${BASH_REMATCH[3]}"
DATE_STR="${BASH_REMATCH[4]}"
BUILD_DATE_ISO="${DATE_STR:0:4}-${DATE_STR:4:2}-${DATE_STR:6:2}"
elif [[ "${TAG_VERSION}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$ ]]; then
VERSION_BASE="${BASH_REMATCH[1]}"
DATE_STR="${BASH_REMATCH[3]}"
PATCH_NUMBER="${BASH_REMATCH[4]}"
BUILD_DATE_ISO="${DATE_STR:0:4}-${DATE_STR:4:2}-${DATE_STR:6:2}"
elif [[ "${TAG_VERSION}" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})$ ]]; then
VERSION_BASE="${BASH_REMATCH[1]}"
DATE_STR="${BASH_REMATCH[3]}"
BUILD_DATE_ISO="${DATE_STR:0:4}-${DATE_STR:4:2}-${DATE_STR:6:2}"
else
# Use release published date
BUILD_DATE_ISO=$(echo "${{ github.event.release.published_at }}" | cut -d'T' -f1)
fi
echo "TAG_NAME=${TAG_NAME}" >> $GITHUB_OUTPUT
echo "VERSION_FULL=${VERSION_FULL}" >> $GITHUB_OUTPUT
echo "VERSION_BASE=${VERSION_BASE}" >> $GITHUB_OUTPUT
echo "BUILD_DATE_ISO=${BUILD_DATE_ISO}" >> $GITHUB_OUTPUT
echo "PATCH_NUMBER=${PATCH_NUMBER}" >> $GITHUB_OUTPUT
- name: Install jq
run: sudo apt-get update && sudo apt-get install -y jq
- name: Get release information
id: release_info
run: |
# Use jq to properly escape JSON
RELEASE_BODY="${{ github.event.release.body }}"
RELEASE_NAME="${{ github.event.release.name }}"
# Handle empty values
if [ -z "$RELEASE_BODY" ]; then
RELEASE_BODY=""
fi
if [ -z "$RELEASE_NAME" ]; then
RELEASE_NAME=""
fi
# Save to temporary files for proper handling
echo -n "$RELEASE_BODY" > /tmp/release_body.txt
echo -n "$RELEASE_NAME" > /tmp/release_name.txt
# Use jq to escape JSON strings
RELEASE_BODY_JSON=$(jq -Rs . < /tmp/release_body.txt)
RELEASE_NAME_JSON=$(jq -Rs . < /tmp/release_name.txt)
echo "RELEASE_BODY=${RELEASE_BODY_JSON}" >> $GITHUB_OUTPUT
echo "RELEASE_NAME=${RELEASE_NAME_JSON}" >> $GITHUB_OUTPUT
- name: Download current version.json from server
id: download_version
continue-on-error: true
run: |
# Try to download current version.json from server
curl -f -s "https://version.crossdesk.cn/version.json" -o version.json || echo "Failed to download, will create new one"
- name: Generate or update version.json
run: |
# If version.json exists, try to preserve downloads section
if [ -f version.json ] && jq -e '.downloads' version.json > /dev/null 2>&1; then
EXISTING_DOWNLOADS=$(jq -c '.downloads' version.json)
if [ "$EXISTING_DOWNLOADS" != "null" ] && [ "$EXISTING_DOWNLOADS" != "{}" ]; then
DOWNLOADS_JSON="$EXISTING_DOWNLOADS"
else
DOWNLOADS_JSON=""
fi
else
DOWNLOADS_JSON=""
fi
# If downloads is empty, use default structure
if [ -z "$DOWNLOADS_JSON" ]; then
DOWNLOADS_JSON=$(cat << DOWNLOADS_EOF
{
"windows-x64": {
"url": "https://downloads.crossdesk.cn/crossdesk-win-x64-${{ steps.version.outputs.TAG_NAME }}.exe",
"filename": "crossdesk-win-x64-${{ steps.version.outputs.TAG_NAME }}.exe"
},
"windows-x64-portable": {
"url": "https://downloads.crossdesk.cn/crossdesk-win-x64-portable-${{ steps.version.outputs.TAG_NAME }}.zip",
"filename": "crossdesk-win-x64-portable-${{ steps.version.outputs.TAG_NAME }}.zip"
},
"macos-x64": {
"url": "https://downloads.crossdesk.cn/crossdesk-macos-x64-${{ steps.version.outputs.TAG_NAME }}.pkg",
"filename": "crossdesk-macos-x64-${{ steps.version.outputs.TAG_NAME }}.pkg"
},
"macos-arm64": {
"url": "https://downloads.crossdesk.cn/crossdesk-macos-arm64-${{ steps.version.outputs.TAG_NAME }}.pkg",
"filename": "crossdesk-macos-arm64-${{ steps.version.outputs.TAG_NAME }}.pkg"
},
"linux-amd64": {
"url": "https://downloads.crossdesk.cn/crossdesk-linux-amd64-${{ steps.version.outputs.TAG_NAME }}.deb",
"filename": "crossdesk-linux-amd64-${{ steps.version.outputs.TAG_NAME }}.deb"
},
"linux-arm64": {
"url": "https://downloads.crossdesk.cn/crossdesk-linux-arm64-${{ steps.version.outputs.TAG_NAME }}.deb",
"filename": "crossdesk-linux-arm64-${{ steps.version.outputs.TAG_NAME }}.deb"
}
}
DOWNLOADS_EOF
)
fi
# Generate version.json using cat and heredoc
cat > version.json << EOF
{
"latest_version": "${{ steps.version.outputs.VERSION_FULL }}",
"version": "${{ steps.version.outputs.VERSION_FULL }}",
"releaseDate": "${{ steps.version.outputs.BUILD_DATE_ISO }}",
"patch": ${{ steps.version.outputs.PATCH_NUMBER }},
"releaseName": ${{ steps.release_info.outputs.RELEASE_NAME }},
"releaseNotes": ${{ steps.release_info.outputs.RELEASE_BODY }},
"tagName": "${{ steps.version.outputs.TAG_NAME }}",
"downloads": ${DOWNLOADS_JSON}
}
EOF
cat version.json
- name: Upload version.json to server
uses: burnett01/rsync-deployments@5.2
with:
switches: -avzr --delete
path: version.json
remote_path: /var/www/html/version/
remote_host: ${{ secrets.SERVER_HOST }}
remote_user: ${{ secrets.SERVER_USER }}
remote_key: ${{ secrets.SERVER_KEY }}
+4 -1
View File
@@ -1,9 +1,12 @@
# Xmake cache
.xmake/
build/
thirdparty/ffmpeg/lib/*
.VSCodeCounter/
# MacOS Cache
.DS_Store
# VSCode cache
.vscode
.vscode
projectx.code-workspace
-3
View File
@@ -1,3 +0,0 @@
[submodule "submodules/minirtc"]
path = submodules/minirtc
url = https://github.com/kunkundi/minirtc.git
-674
View File
@@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
+24 -325
View File
@@ -1,335 +1,34 @@
# CrossDesk
# projectx
<a href="https://hellogithub.com/repository/kunkundi/crossdesk" target="_blank"><img src="https://api.hellogithub.com/v1/widgets/recommend.svg?rid=55d41367570345f1838e02fd12be7961&claim_uid=cb0OpZRrBuGVAfL&theme=small" alt="FeaturedHelloGitHub" /></a>
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-brightgreen.svg)]()
[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![GitHub last commit](https://img.shields.io/github/last-commit/kunkundi/crossdesk)](https://github.com/kunkundi/crossdesk/commits/web-client)
[![Build Status](https://github.com/kunkundi/crossdesk/actions/workflows/build.yml/badge.svg)](https://github.com/kunkundi/crossdesk/actions)
[![Docker Pulls](https://img.shields.io/docker/pulls/crossdesk/crossdesk-server)](https://hub.docker.com/r/crossdesk/crossdesk-server/tags)
[![GitHub issues](https://img.shields.io/github/issues/kunkundi/crossdesk.svg)]()
[![GitHub stars](https://img.shields.io/github/stars/kunkundi/crossdesk.svg?style=social)]()
[![GitHub forks](https://img.shields.io/github/forks/kunkundi/crossdesk.svg?style=social)]()
vcpkg/buildtrees/versioning_/versions/pcre/69e232f12c4e3eab4115f0672466a6661978bea2$ vim portfile.cmake
[ [English](README_EN.md) / 中文 ]
- URLS "https://ftp.pcre.org/pub/pcre/pcre-${PCRE_VERSION}.zip"
+ URLS "https://sourceforge.net/projects/pcre/files/pcre/${PCRE_VERSION}/pcre-${PCRE_VERSION}.zip"
PC 客户端
![sup_example](https://github.com/user-attachments/assets/eeb64fbe-1f07-4626-be1c-b77396beb905)
linux
Web 客户端
<p align="center">
<img width="850" height="550" alt="6bddcbed47ffd4b9988a4037c7f4f524" src="https://github.com/user-attachments/assets/e44f73f9-24ac-46a3-a189-b7f8b6669881" />
</p>
sudo apt-get install nvidia-cuda-toolkit
solve <cuda.h>
## 简介
sudo apt-get install libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev libxcb-xfixes0-dev libxv-dev
solve x11
CrossDesk 是一个轻量级的跨平台远程桌面软件,支持 Web 端控制远程设备。
sudo apt-get -y install libasound2-dev libsndio-dev libxcb-shm0-dev
solve asound sndio xcb-shm
CrossDesk 是 [MiniRTC](https://github.com/kunkundi/minirtc.git) 实时音视频传输库的实验性应用。MiniRTC 是一个轻量级的跨平台实时音视频传输库。它具有网络透传([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)),视频软硬编解码(H264/AV1),音频编解码([Opus](https://github.com/xiph/opus)),信令交互,网络拥塞控制,传输加密([SRTP](https://tools.ietf.org/html/rfc3711))等基础能力。
sudo apt-get -y install libasound2-dev libpulse-dev && rebuild
solve error dsp no such audio device
## 系统要求
sudo apt-get install libavcodec-dev libavformat-dev libavutil-dev libavfilter-dev libavdevice-dev
| 平台 | 最低版本 |
|----------------|---------------------------|
| **Windows** | Windows 10 及以上 (64 位) |
| **macOS** | macOS Intel 15.0 及以上 ( 大于 14.0 小于 15.0 的版本可自行编译实现兼容 )<br> macOS Apple Silicon 14.0 及以上 |
| **Linux** | Ubuntu 22.04 及以上 ( 低版本可自行编译实现兼容 ) |
sudo apt remove libssl-dev libglib2.0-dev
## 使用
在菜单栏“对端ID”处输入远端桌面的ID,点击“→”即可发起远程连接。
![usage1](https://github.com/user-attachments/assets/3a4bb59f-c84c-44d2-9a20-11790aac510e)
如果远端桌面设置了连接密码,则本端需填写正确的连接密码才能成功发起远程连接。
![password](https://github.com/user-attachments/assets/1beadcce-640d-4f5c-8e77-51917b5294d5)
发起连接前,可在设置中自定义配置项,如语言、视频编码格式等。
![settings](https://github.com/user-attachments/assets/8bc5468d-7bbb-4e30-95bd-da1f352ac08c)
### Web 客户端
浏览器访问 [CrossDesk Web Client](https://web.crossdesk.cn/)。
输入 **远程设备 ID****密码**,点击连接即可接入远程设备。如图,**iOS Safari 远程控制 Win11**
<img width="645" height="300" alt="_cgi-bin_mmwebwx-bin_webwxgetmsgimg__ MsgID=932911462648581698 skey=@crypt_1f5153b1_b550ca7462b5009ce03c991cca2a92a7 mmweb_appid=wx_webfilehelper" src="https://github.com/user-attachments/assets/a5109e6f-752c-4654-9f4e-7e161bddf43e" />
### Windows 服务(CrossDesk Service
CrossDesk 在 Windows 平台提供本地辅助服务 **CrossDesk Service**,服务名为 `CrossDeskService`。该服务用于锁屏、登录界面和安全桌面等受保护场景下的远程控制增强能力,包括:
- 上报远端当前是否处于锁屏、登录、凭据输入或安全桌面状态;
- 支持从控制端发送 `Ctrl+Alt+Del`SAS);
- 在锁屏、登录和安全桌面阶段转发键盘、鼠标输入。
Windows 安装包会自动打包 `crossdesk_service.exe``crossdesk_session_helper.exe`,并在安装时注册为按需启动的 Windows 服务。CrossDesk 客户端启动时会尝试启动已安装的服务;当本机没有 CrossDesk 客户端进程运行时,服务会自动退出。卸载客户端时会同步停止并移除该服务。
如果是手动编译或手动部署 Windows 版本,请确保 `CrossDesk.exe``crossdesk_service.exe``crossdesk_session_helper.exe` 位于同一目录。安装或卸载服务需要使用管理员权限打开 PowerShell
```
# 安装
.\CrossDesk.exe --service-install
# 启动
.\CrossDesk.exe --service-start
# 查看状态
.\CrossDesk.exe --service-status
.\CrossDesk.exe --service-stop
# 卸载
.\CrossDesk.exe --service-uninstall
```
如果远端 Windows 服务未安装、未启动或暂时不可用,基础远程桌面连接仍可使用,但锁屏、登录界面和安全桌面阶段的控制能力会受限,客户端会提示“远端Windows服务不可用”。
## 如何编译
依赖:
- [xmake](https://xmake.io/#/guide/installation)
- [cmake](https://cmake.org/download/)
### Linux
Linux 构建目前支持 Ubuntu 22.04 及以上版本的 amd64 和 arm64。先安装基础编译依赖:
```bash
sudo apt-get update
sudo apt-get install -y \
git curl unzip build-essential cmake pkg-config binutils dpkg-dev \
libx11-dev libxext-dev libxrender-dev libxft-dev libxrandr-dev \
libxinerama-dev libxcursor-dev libxi-dev libxfixes-dev libxv-dev \
libxtst-dev libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev \
libxcb-shape0-dev libxcb-xkb-dev libxcb-xfixes0-dev libxcb-shm0-dev \
libasound2-dev libsndio-dev libpulse-dev
```
需要启用 Wayland 捕获或 DRM 捕获时,再安装对应依赖:
```bash
sudo apt-get install -y libdbus-1-dev libpipewire-0.3-dev libdrm-dev
```
下载子模块并编译 Release 版本:
```bash
git clone --recurse-submodules https://github.com/kunkundi/crossdesk.git
cd crossdesk
# 已经克隆过仓库时执行这一行
git submodule update --init --recursive
xmake f -m release --USE_CUDA=false -y
xmake b -vy crossdesk
```
amd64 构建结果位于 `build/linux/x86_64/release/crossdesk`arm64 构建结果位于 `build/linux/arm64/release/crossdesk`
启用 Wayland 和 DRM 的配置示例:
```bash
xmake f -m release --USE_WAYLAND=true --USE_DRM=true --USE_CUDA=false -y
xmake b -vy crossdesk
```
生成 Debian 安装包(必须在对应架构上先完成 Release 编译):
```bash
# amd64
./scripts/linux/pkg_amd64.sh 1.0.0
# arm64
./scripts/linux/pkg_arm64.sh 1.0.0
```
打包脚本会将 Slint 共享运行库安装到软件包私有目录 `/usr/lib/crossdesk`,无需用户另外安装 `libslint_cpp.so`
### 通用编译选项
```text
--USE_CUDA=true/false: 启用 CUDA 硬件编解码,默认不启用
--USE_WAYLAND=true/false: 在 Linux 上启用 Wayland/PipeWire 捕获,默认不启用
--USE_DRM=true/false: 在 Linux 上启用 DRM 捕获,默认不启用
--CROSSDESK_PORTABLE=true/false: 构建便携版本,默认不启用
--CROSSDESK_VERSION=xxx: 指定 CrossDesk 的版本
# 示例
xmake f --CROSSDESK_VERSION=1.0.0 --USE_CUDA=true
```
运行:
```bash
xmake r crossdesk
```
### 无 CUDA 环境下的开发支持
对于**未安装 CUDA 环境的 Linux 开发者,如果希望编译后的成果物拥有硬件编解码能力**,这里提供了预配置的 [Ubuntu 22.04 Docker 镜像](https://hub.docker.com/r/crossdesk/ubuntu22.04)。该镜像内置必要的构建依赖,可在容器中开箱即用,无需额外配置即可直接编译项目。
进入容器,下载工程后执行:
```
export CUDA_PATH=/usr/local/cuda
export XMAKE_GLOBALDIR=/data
xmake f --USE_CUDA=true
xmake b --root -vy crossdesk
```
对于**未安装 CUDA 环境的 Windows 开发者**,执行下面的命令安装 CUDA 编译环境:
```
xmake require -vy "cuda 12.6.3"
```
安装完成后执行:
```
xmake require --info "cuda 12.6.3"
```
输出如下:
<img width="860" height="226" alt="Image" src="https://github.com/user-attachments/assets/999ac365-581a-4b9a-806e-05eb3e4cf44d" />
根据上述输出获取到 CUDA 的安装目录,即 installdir 指向的位置。将 CUDA_PATH 加入系统环境变量,或在终端中输入:
```
set CUDA_PATH=path_to_cuda_installdir
```
重新执行:
```
xmake f --USE_CUDA=true
xmake b -vy crossdesk
```
#### 注意
运行时如果客户端状态栏显示 **未连接服务器**,请先在 [CrossDesk 官方网站](https://www.crossdesk.cn/) 安装客户端,以便在环境中安装所需的证书文件。
<img width="256" height="120" alt="image" src="https://github.com/user-attachments/assets/1812f7d6-516b-4b4f-8a3d-98bee505cc5a" />
## 关于 Xmake
#### 安装 Xmake
使用 curl
```
curl -fsSL https://xmake.io/shget.text | bash
```
使用 wget
```
wget https://xmake.io/shget.text -O - | bash
```
使用 powershell
```
irm https://xmake.io/psget.text | iex
```
#### 编译选项
```
# 切换编译模式
xmake f -m debug/release
# 可选编译参数
-r :重新构建目标
-v :显示详细的构建日志
-y :自动确认提示
# 示例
xmake b -vy crossdesk
```
#### 运行选项
```
# 使用调试模式运行
xmake r -d crossdesk
```
更多使用方法可参考 [Xmake官方文档](https://xmake.io/guide/quick-start.html) 。
## 自托管服务器
推荐使用Docker部署CrossDesk Server。
```bash
sudo docker run -d \
--name crossdesk_server \
--network host \
-e EXTERNAL_IP=xxx.xxx.xxx.xxx \
-e INTERNAL_IP=xxx.xxx.xxx.xxx \
-e CROSSDESK_SERVER_PORT=xxxx \
-e COTURN_PORT=xxxx \
-e MIN_PORT=xxxxx \
-e MAX_PORT=xxxxx \
-v /var/lib/crossdesk:/var/lib/crossdesk \
-v /var/log/crossdesk:/var/log/crossdesk \
crossdesk/crossdesk-server:v1.1.6
```
上述命令中,用户需注意的参数如下:
**参数**
- EXTERNAL_IP:服务器公网 IP , 对应 CrossDesk 客户端**自托管服务器配置**中填写的**服务器地址**
- INTERNAL_IP:服务器内网 IP
- CROSSDESK_SERVER_PORT:自托管服务使用的端口,对应 CrossDesk 客户端**自托管服务器配置**中填写的**服务器端口**
- COTURN_PORT: COTURN 服务使用的端口, 对应 CrossDesk 客户端**自托管服务器配置**中填写的**中继服务端口**
- MIN_PORT/MAX_PORTCOTURN 服务使用的端口范围,例如:MIN_PORT=50000, MAX_PORT=60000,范围可根据客户端数量调整。
- `-v /var/lib/crossdesk:/var/lib/crossdesk`:持久化数据库和证书文件到宿主机
- `-v /var/log/crossdesk:/var/log/crossdesk`:持久化日志文件到宿主机
**示例**
```bash
sudo docker run -d \
--name crossdesk_server \
--network host \
-e EXTERNAL_IP=114.114.114.114 \
-e INTERNAL_IP=10.0.0.1 \
-e CROSSDESK_SERVER_PORT=9099 \
-e COTURN_PORT=3478 \
-e MIN_PORT=50000 \
-e MAX_PORT=60000 \
-v /var/lib/crossdesk:/var/lib/crossdesk \
-v /var/log/crossdesk:/var/log/crossdesk \
crossdesk/crossdesk-server:v1.1.6
```
**注意**
- **服务器需开放端口:COTURN_PORT/udpCOTURN_PORT/tcpMIN_PORT-MAX_PORT/udpCROSSDESK_SERVER_PORT/tcp。**
- 如果不挂载 volume,容器删除后数据会丢失
- 证书文件会在首次启动时自动生成并持久化到宿主机的 `/var/lib/crossdesk/certs` 路径下。由于默认使用的是自签证书,无法保障安全性,建议在云服务商申请正式证书放到该目录下并重启服务。
- 数据库文件会自动创建并持久化到宿主机的 `/var/lib/crossdesk/db/crossdesk-server.db` 路径下
- 日志文件会自动创建并持久化到宿主机的 `/var/log/crossdesk/` 路径下
**权限注意**:如果 Docker 自动创建的目录权限不足(属于 root),容器内用户无法写入,会导致:
- 证书生成失败,容器启动脚本会报错退出
- 数据库目录创建失败,程序会抛出异常并崩溃
- 日志目录创建失败,日志文件无法写入(但程序可能继续运行)
**解决方案**:在启动容器前手动设置权限:
```bash
sudo mkdir -p /var/lib/crossdesk /var/log/crossdesk
sudo chown -R $(id -u):$(id -g) /var/lib/crossdesk /var/log/crossdesk
```
### 客户端
1. 点击右上角设置进入设置页面。<br><br>
<img width="600" height="210" alt="image" src="https://github.com/user-attachments/assets/6431131d-b32a-4726-8783-6788f47baa3b" /><br>
2. 点击`自托管服务器配置`按钮。<br><br>
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/24c761a3-1985-4d7e-84be-787383c2afb8" /><br>
3. 输入`服务器地址`(**EXTERNAL_IP**)、`信令服务端口`(**CROSSDESK_SERVER_PORT**)、`中继服务端口`(**COTURN_PORT**),点击确认按钮。
4. 勾选`自托管服务器配置`选项,点击确认按钮保存设置。如果服务端使用的是正式证书,则到此步骤为止,客户端即可显示已连接服务器。
5. 如果使用默认证书(正式证书忽略此步骤),则需要将服务端`/var/lib/crossdesk/certs/`目录下的`api.crossdesk.cn_root.crt`自签根证书下载到运行客户端的机器,并执行下述命令安装证书:
Windows 平台使用**管理员权限**打开 PowerShell 执行
```
certutil -addstore "Root" "C:\path\to\api.crossdesk.cn_root.crt"
```
Linux
```
sudo cp /path/to/api.crossdesk.cn_root.crt /usr/local/share/ca-certificates/api.crossdesk.cn_root.crt
sudo update-ca-certificates
```
macOS
```
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain path/to/api.crossdesk.cn_root.crt
```
### Web 客户端
详情见项目 [CrossDesk Web Client](https://github.com/kunkundi/crossdesk-web-client)。
# 常见问题
见 [常见问题](https://github.com/kunkundi/crossdesk/blob/self-hosted-server/docs/FAQ.md) 。
# 致谢
- 感谢 [HelloGitHub](https://hellogithub.com/) 的推荐与关注。
- 感谢 [阮一峰的科技爱好者周刊](https://github.com/ruanyf/weekly) 的收录与推荐。
- 感谢 [LinuxDo](https://linux.do) 社区的关注、交流与支持,为 CrossDesk 项目的完善提供了帮助。
install:
@echo hello world
install -D build/linux/x86_64/release/remote_desk -t /usr/bin
install -D config/config.ini -t /usr/bin
install -D build/linux/x86_64/release/libprojectx.so -t /usr/lib
install -D thirdparty/nvcodec/Lib/x64/libnvidia-encode.so.1 -t /usr/lib
install -D thirdparty/nvcodec/Lib/x64/libnvidia-encode.so -t /usr/lib
install -D thirdparty/nvcodec/Lib/x64/libnvcuvid.so.1 -t /usr/lib
install -D thirdparty/nvcodec/Lib/x64/libnvcuvid.so -t /usr/lib
-349
View File
@@ -1,349 +0,0 @@
# CrossDesk
<a href="https://hellogithub.com/repository/kunkundi/crossdesk" target="_blank"><img src="https://api.hellogithub.com/v1/widgets/recommend.svg?rid=55d41367570345f1838e02fd12be7961&claim_uid=cb0OpZRrBuGVAfL&theme=small" alt="FeaturedHelloGitHub" /></a>
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-brightgreen.svg)]()
[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![GitHub last commit](https://img.shields.io/github/last-commit/kunkundi/crossdesk)](https://github.com/kunkundi/crossdesk/commits/web-client)
[![Build Status](https://github.com/kunkundi/crossdesk/actions/workflows/build.yml/badge.svg)](https://github.com/kunkundi/crossdesk/actions)
[![Docker Pulls](https://img.shields.io/docker/pulls/crossdesk/crossdesk-server)](https://hub.docker.com/r/crossdesk/crossdesk-server/tags)
[![GitHub issues](https://img.shields.io/github/issues/kunkundi/crossdesk.svg)]()
[![GitHub stars](https://img.shields.io/github/stars/kunkundi/crossdesk.svg?style=social)]()
[![GitHub forks](https://img.shields.io/github/forks/kunkundi/crossdesk.svg?style=social)]()
[ [中文](README.md) / English ]
PC Client
![sup_example](https://github.com/user-attachments/assets/3f17d8f3-7c4a-4b63-bae4-903363628687)
Web Client
<p align="center">
<img width="850" height="550" alt="6bddcbed47ffd4b9988a4037c7f4f524" src="https://github.com/user-attachments/assets/e44f73f9-24ac-46a3-a189-b7f8b6669881" />
</p>
# Intro
CrossDesk is a lightweight cross-platform remote desktop software.
CrossDesk is an experimental application of [MiniRTC](https://github.com/kunkundi/minirtc.git), a lightweight cross-platform real-time audio and video transmission library. MiniRTC provides fundamental capabilities including network traversal ([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)), video software/hardware encoding and decoding (H264/AV1), audio encoding/decoding ([Opus](https://github.com/xiph/opus)), signaling interaction, network congestion control, and transmission encryption ([SRTP](https://tools.ietf.org/html/rfc3711)).
## System Requirements
| Platform | Minimum Version |
|-----------|-----------------|
| **Windows** | Windows 10 or later (64-bit) |
| **macOS** | macOS Intel 15.0 or later *(versions between 14.0 and 15.0 can be built manually for compatibility)*<br>macOS Apple Silicon 14.0 or later |
| **Linux** | Ubuntu 22.04 or later *(older versions can be built manually for compatibility)* |
## Usage
Enter the remote desktop ID in the menu bars “Remote ID” field and click “→” to initiate a remote connection.
![usage1](https://github.com/user-attachments/assets/3a4bb59f-c84c-44d2-9a20-11790aac510e)
If the remote desktop requires a connection password, you must enter the correct password on your side to successfully establish the connection.
![password](https://github.com/user-attachments/assets/1beadcce-640d-4f5c-8e77-51917b5294d5)
Before connecting, you can customize configuration options in the settings, such as language and video encoding format.
![settings](https://github.com/user-attachments/assets/8bc5468d-7bbb-4e30-95bd-da1f352ac08c)
### Web Client
Visit [CrossDesk Web Client](https://web.crossdesk.cn/).
Enter the **Remote Device ID** and **Password**, then click Connect to access the remote device. As shown, **iOS Safari remotely controlling Windows 11**:
<img width="645" height="300" alt="_cgi-bin_mmwebwx-bin_webwxgetmsgimg__ MsgID=932911462648581698 skey=@crypt_1f5153b1_b550ca7462b5009ce03c991cca2a92a7 mmweb_appid=wx_webfilehelper" src="https://github.com/user-attachments/assets/a5109e6f-752c-4654-9f4e-7e161bddf43e" />
### Windows Service (CrossDesk Service)
CrossDesk provides a local helper service on Windows named **CrossDesk Service**. Its service name is `CrossDeskService`. The service improves remote control in protected Windows states such as the lock screen, sign-in UI, credential UI, and secure desktop. It provides:
- Remote status reporting for lock screen, sign-in, credential, and secure desktop states.
- Remote `Ctrl+Alt+Del` (SAS) delivery.
- Keyboard and mouse input forwarding while the remote Windows device is on the lock screen, sign-in UI, or secure desktop.
The Windows installer bundles `crossdesk_service.exe` and `crossdesk_session_helper.exe`, then registers the service as an on-demand Windows service during installation. When the CrossDesk client starts, it tries to start the installed service automatically. When no CrossDesk client process is running on the machine, the service exits automatically. Uninstalling the client also stops and removes the service.
For manual Windows builds or deployments, make sure `CrossDesk.exe`, `crossdesk_service.exe`, and `crossdesk_session_helper.exe` are placed in the same directory. Open PowerShell with administrator privileges to install or uninstall the service:
```
# install
.\CrossDesk.exe --service-install
# start
.\CrossDesk.exe --service-start
# check status
.\CrossDesk.exe --service-status
.\CrossDesk.exe --service-ping
# stop
.\CrossDesk.exe --service-stop
# uninstall
.\CrossDesk.exe --service-uninstall
```
If the remote Windows service is not installed, not running, or temporarily unavailable, the basic remote desktop connection still works, but remote control on the lock screen, sign-in UI, and secure desktop is limited. The client will show “Remote Windows service unavailable”.
## How to build
Requirements:
- [xmake](https://xmake.io/#/guide/installation)
- [cmake](https://cmake.org/download/)
### Linux
Linux builds currently support Ubuntu 22.04 or later on amd64 and arm64. Install the base build dependencies first:
```bash
sudo apt-get update
sudo apt-get install -y \
git curl unzip build-essential cmake pkg-config binutils dpkg-dev \
libx11-dev libxext-dev libxrender-dev libxft-dev libxrandr-dev \
libxinerama-dev libxcursor-dev libxi-dev libxfixes-dev libxv-dev \
libxtst-dev libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev \
libxcb-shape0-dev libxcb-xkb-dev libxcb-xfixes0-dev libxcb-shm0-dev \
libasound2-dev libsndio-dev libpulse-dev
```
Install the optional dependencies when Wayland capture or DRM capture is enabled:
```bash
sudo apt-get install -y libdbus-1-dev libpipewire-0.3-dev libdrm-dev
```
Clone the submodules and build a release binary:
```bash
git clone --recurse-submodules https://github.com/kunkundi/crossdesk.git
cd crossdesk
# Run this when the repository has already been cloned
git submodule update --init --recursive
xmake f -m release --USE_CUDA=false -y
xmake b -vy crossdesk
```
The amd64 binary is written to `build/linux/x86_64/release/crossdesk`; the arm64 binary is written to `build/linux/arm64/release/crossdesk`.
Example configuration with Wayland and DRM capture enabled:
```bash
xmake f -m release --USE_WAYLAND=true --USE_DRM=true --USE_CUDA=false -y
xmake b -vy crossdesk
```
Build a Debian package after compiling a release binary on the matching architecture:
```bash
# amd64
./scripts/linux/pkg_amd64.sh 1.0.0
# arm64
./scripts/linux/pkg_arm64.sh 1.0.0
```
The package scripts install the shared Slint runtime in the private `/usr/lib/crossdesk` directory, so users do not need to install `libslint_cpp.so` separately.
### Common build options
```text
--USE_CUDA=true/false: Enable CUDA hardware codec acceleration, disabled by default
--USE_WAYLAND=true/false: Enable Wayland/PipeWire capture on Linux, disabled by default
--USE_DRM=true/false: Enable DRM capture on Linux, disabled by default
--CROSSDESK_PORTABLE=true/false: Build the portable variant, disabled by default
--CROSSDESK_VERSION=xxx: Set the CrossDesk version
# Example
xmake f --CROSSDESK_VERSION=1.0.0 --USE_CUDA=true
```
Run:
```bash
xmake r crossdesk
```
#### Development Without CUDA Environment
For **Linux developers who do not have a CUDA environment installed and want to enable hardware codec feature**, a preconfigured [Ubuntu 22.04 Docker image](https://hub.docker.com/r/crossdesk/ubuntu22.04) is provided.
This image comes with all required build dependencies and allows you to build the project directly inside the container without any additional setup.
After entering the container, download the project and run:
```
export CUDA_PATH=/usr/local/cuda
export XMAKE_GLOBALDIR=/data
xmake f --USE_CUDA=true
xmake b --root -vy crossdesk
```
For **Windows developers without a CUDA environment** installed, run the following command to install the CUDA build environment:
```
xmake require -vy "cuda 12.6.3"
```
After the installation is complete, execute:
```
xmake require --info "cuda 12.6.3"
```
The output will look like this:
<img width="860" height="226" alt="Image" src="https://github.com/user-attachments/assets/999ac365-581a-4b9a-806e-05eb3e4cf44d" />
From the output above, locate the CUDA installation directory — this is the path pointed to by installdir.
Add this path to your system environment variable CUDA_PATH, or set it in the terminal using:
```
set CUDA_PATH=path_to_cuda_installdir:
```
Then re-run:
```
xmake f --USE_CUDA=true
xmake b -vy crossdesk
```
#### Notice
If the client status bar shows **Disconnected** during runtime, please first install the client from the [CrossDesk official website](https://www.crossdesk.cn/) to ensure the required certificate files are available in the environment.
<img width="256" height="120" alt="image" src="https://github.com/user-attachments/assets/1812f7d6-516b-4b4f-8a3d-98bee505cc5a" />
## About Xmake
#### Installing Xmake
You can install Xmake using one of the following methods:
Using curl:
```
curl -fsSL https://xmake.io/shget.text | bash
```
Using wget:
```
wget https://xmake.io/shget.text -O - | bash
```
Using powershell:
```
irm https://xmake.io/psget.text | iex
```
#### Build Options
```
# Switch build mode
xmake f -m debug/release
# Optional build parameters
-r : Rebuild the target
-v : Show detailed build logs
-y : Automatically confirm prompts
# Example
xmake b -vy crossdesk
```
#### Run Options
```
# Run in debug mode
xmake r -d crossdesk
```
For more information, please refer to the [official Xmake documentation](https://xmake.io/guide/quick-start.html) .
## Self-Hosted Server
It is recommended to deploy CrossDesk Server using Docker.
```
sudo docker run -d \
--name crossdesk_server \
--network host \
-e EXTERNAL_IP=xxx.xxx.xxx.xxx \
-e INTERNAL_IP=xxx.xxx.xxx.xxx \
-e CROSSDESK_SERVER_PORT=xxxx \
-e COTURN_PORT=xxxx \
-e MIN_PORT=xxxxx \
-e MAX_PORT=xxxxx \
-v /var/lib/crossdesk:/var/lib/crossdesk \
-v /var/log/crossdesk:/var/log/crossdesk \
crossdesk/crossdesk-server:v1.1.6
```
The parameters you need to pay attention to are as follows:
**Parameters**
- **EXTERNAL_IP**: The servers public IP. This corresponds to **Server Address** in the CrossDesk clients **Self-Hosted Server Configuration**.
- **INTERNAL_IP**: The servers internal IP.
- **CROSSDESK_SERVER_PORT**: The port used by the self-hosted service. This corresponds to **Server Port** in the CrossDesk clients **Self-Hosted Server Configuration**.
- **COTURN_PORT**: The port used by the COTURN service. This corresponds to **Relay Service Port** in the CrossDesk clients **Self-Hosted Server Configuration**.
- **MIN_PORT / MAX_PORT**: The port range used by the COTURN service. Example: `MIN_PORT=50000`, `MAX_PORT=60000`. Adjust the range depending on the number of clients.
- `-v /var/lib/crossdesk:/var/lib/crossdesk`: Persists database and certificate files on the host machine.
- `-v /var/log/crossdesk:/var/log/crossdesk`: Persists log files on the host machine.
**Example**:
```bash
sudo docker run -d \
--name crossdesk_server \
--network host \
-e EXTERNAL_IP=114.114.114.114 \
-e INTERNAL_IP=10.0.0.1 \
-e CROSSDESK_SERVER_PORT=9099 \
-e COTURN_PORT=3478 \
-e MIN_PORT=50000 \
-e MAX_PORT=60000 \
-v /var/lib/crossdesk:/var/lib/crossdesk \
-v /var/log/crossdesk:/var/log/crossdesk \
crossdesk/crossdesk-server:v1.1.6
```
**Notes**
- **The server must open the following ports: COTURN_PORT/udp, COTURN_PORT/tcp, MIN_PORTMAX_PORT/udp, and CROSSDESK_SERVER_PORT/tcp.**
- If you dont mount volumes, all data will be lost when the container is removed.
- Certificate files will be automatically generated on first startup and persisted to the host at `/var/lib/crossdesk/certs`.As the default certificates are self-signed and cannot guarantee security, it is strongly recommended to apply for a trusted certificate from a cloud provider, deploy it to this directory, and restart the service.
- The database file will be automatically created and stored at `/var/lib/crossdesk/db/crossdesk-server.db`.
- Log files will be created and stored at `/var/log/crossdesk/`.
**Permission Notice**
If the directories automatically created by Docker belong to root and have insufficient write permissions, the container user may not be able to write to them. This can cause:
- Certificate generation failure, leading to startup script errors and container exit.
- Database directory creation failure, causing the program to throw exceptions and crash.
- Log directory creation failure, preventing logs from being written (though the program may continue running).
**Solution:** Manually set permissions before starting the container:
```bash
sudo mkdir -p /var/lib/crossdesk /var/log/crossdesk
sudo chown -R $(id -u):$(id -g) /var/lib/crossdesk /var/log/crossdesk
```
### Server Side
Place **crossdesk.cn.key** and **crossdesk.cn_bundle.crt** into the **/path/to/your/certs** directory.
### Client Side
1. Click the settings icon in the top-right corner to enter the settings page.<br><br>
<img width="600" height="210" alt="image" src="https://github.com/user-attachments/assets/6431131d-b32a-4726-8783-6788f47baa3b" /><br>
2. Click `Self-Hosted Server Configuration` button.<br><br>
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/24c761a3-1985-4d7e-84be-787383c2afb8" /><br>
3. Enter the `Server Address` (**EXTERNAL_IP**), `Signaling Service Port` (**CROSSDESK_SERVER_PORT**), and `Relay Service Port` (**COTURN_PORT**) and click OK button.
4. Check the `Self-hosted server configuration` option and click the OK button to save the settings. If the server is using a valid (official) certificate, the process ends here and the client will show that it is connected to the server.
5. If the default certificate is used (skip this step if an official certificate is used), download the self-signed root certificate `api.crossdesk.cn_root.crt` from the server directory /var/lib/crossdesk/certs/ to the machine running the client, and install the certificate by executing the following command:
On Windows, open PowerShell with **administrator privileges** and execute:
```
certutil -addstore "Root" "C:\path\to\api.crossdesk.cn_root.crt"
```
Linux
```
sudo cp /path/to/api.crossdesk.cn_root.crt /usr/local/share/ca-certificates/api.crossdesk.cn_root.crt
sudo update-ca-certificates
```
macOS
```
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain path/to/api.crossdesk.cn_root.crt
```
### Web Client
See [CrossDesk Web Client](https://github.com/kunkundi/crossdesk-web-client)。
# FAQ
See [FAQ](https://github.com/kunkundi/crosssesk/blob/self-hosted-server/docs/FAQ.md) .
# Acknowledgements
- Thanks to [HelloGitHub](https://hellogithub.com/) for the recommendation and exposure.
- Thanks to [Ruanyf Weekly](https://github.com/ruanyf/weekly) for featuring CrossDesk.
- Thanks to the [LinuxDo](https://linux.do) community for the attention, discussions, and support that helped improve CrossDesk.
+19
View File
@@ -0,0 +1,19 @@
[signal server]
ip = 150.158.81.30
port = 9099
[stun server]
ip = 150.158.81.30
port = 3478
[turn server]
ip = 150.158.81.30
port = 3478
username = dijunkun
password = dijunkunpw
[hardware acceleration]
turn_on = false
[av1 encoding]
turn_on = true
Binary file not shown.
-153
View File
@@ -1,153 +0,0 @@
# syntax=docker/dockerfile:1.7
ARG BASE_IMAGE=nvidia/cuda:12.6.3-devel-ubuntu22.04
FROM ${BASE_IMAGE}
ARG TARGETARCH
ARG XMAKE_VERSION=3.0.9
ARG RUST_VERSION=1.92.0
ARG RUSTUP_VERSION=1.28.2
LABEL org.opencontainers.image.title="CrossDesk Linux build environment" \
org.opencontainers.image.description="Ubuntu 22.04 build environment for CrossDesk" \
org.opencontainers.image.source="https://github.com/kunkundi/crossdesk" \
org.opencontainers.image.licenses="GPL-3.0-only"
ENV DEBIAN_FRONTEND=noninteractive \
TZ=Etc/UTC \
LANG=C.UTF-8 \
LC_ALL=C.UTF-8 \
RUSTUP_HOME=/opt/rustup \
CARGO_HOME=/opt/cargo \
XMAKE_GLOBALDIR=/data \
XMAKE_ROOT=y \
CUDA_PATH=/usr/local/cuda \
PATH=/root/.local/bin:/opt/cargo/bin:/usr/local/bin:${PATH}
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
autoconf \
automake \
binutils \
bison \
build-essential \
ca-certificates \
ccache \
cmake \
curl \
dbus \
dpkg-dev \
file \
flex \
gettext \
git \
libasound2-dev \
libdbus-1-dev \
libdrm-dev \
libegl1-mesa-dev \
libffi-dev \
libfontconfig1-dev \
libfreetype6-dev \
libgl1-mesa-dev \
libmount-dev \
libpulse-dev \
libreadline-dev \
libsndio-dev \
libssl-dev \
libtool \
libx11-dev \
libxcb-xfixes0-dev \
libxcb-randr0-dev \
libxcb-shape0-dev \
libxcb-shm0-dev \
libxcb-xinerama0-dev \
libxcb-xkb-dev \
libxcb-xtest0-dev \
libxcursor-dev \
libxext-dev \
libxfixes-dev \
libxft-dev \
libxi-dev \
libxinerama-dev \
libxkbcommon-dev \
libxrandr-dev \
libxrender-dev \
libxtst-dev \
libxv-dev \
make \
nasm \
ninja-build \
patch \
patchelf \
perl \
pkg-config \
python3 \
python3-pip \
unzip \
xutils-dev \
xz-utils \
yasm \
zip \
zlib1g-dev \
&& rm -rf /var/lib/apt/lists/*
RUN export GITHUB_ACTIONS=true \
&& curl -fsSL https://xmake.io/shget.text \
| bash -s -- "v${XMAKE_VERSION}" \
&& source ~/.xmake/profile \
&& xmake --version | grep -F "v${XMAKE_VERSION}"
RUN case "${TARGETARCH}" in \
amd64) \
rustup_arch="x86_64-unknown-linux-gnu"; \
rustup_sha256="20a06e644b0d9bd2fbdbfd52d42540bdde820ea7df86e92e533c073da0cdd43c" \
;; \
arm64) \
rustup_arch="aarch64-unknown-linux-gnu"; \
rustup_sha256="e3853c5a252fca15252d07cb23a1bdd9377a8c6f3efa01531109281ae47f841c" \
;; \
*) echo "Unsupported architecture: ${TARGETARCH}" >&2; exit 1 ;; \
esac \
&& curl --proto '=https' --tlsv1.2 --fail --show-error --location \
"https://static.rust-lang.org/rustup/archive/${RUSTUP_VERSION}/${rustup_arch}/rustup-init" \
-o /tmp/rustup-init \
&& echo "${rustup_sha256} /tmp/rustup-init" | sha256sum --check - \
&& chmod 0755 /tmp/rustup-init \
&& /tmp/rustup-init -y --no-modify-path --profile minimal \
--default-toolchain "${RUST_VERSION}" \
&& rm -f /tmp/rustup-init \
&& rustc --version \
&& cargo --version
WORKDIR /tmp/crossdesk-dependencies
COPY xmake.lua ./xmake.lua
COPY xmake ./xmake
COPY thirdparty ./thirdparty
COPY submodules/xmake.lua ./submodules/xmake.lua
COPY submodules/minirtc/xmake.lua ./submodules/minirtc/xmake.lua
COPY submodules/minirtc/thirdparty ./submodules/minirtc/thirdparty
# Configure the real project manifests so the image cache stays aligned with
# CrossDesk's exact dependency graph. Source files are intentionally excluded
# from the Docker context; source-only changes therefore reuse this layer.
RUN xmake repo -u --root \
&& xmake f -m release --USE_CUDA=true --root -y \
&& rm -rf /data/.xmake/cache /tmp/crossdesk-dependencies \
&& mkdir -p /workspace
WORKDIR /workspace
COPY docker/linux-build/verify-build-image.sh \
/usr/local/bin/verify-crossdesk-build-image
COPY docker/linux-build/verify-glibc-baseline.sh \
/usr/local/bin/verify-crossdesk-glibc-baseline
RUN chmod 0755 \
/usr/local/bin/verify-crossdesk-build-image \
/usr/local/bin/verify-crossdesk-glibc-baseline \
&& verify-crossdesk-build-image
CMD ["bash"]
-41
View File
@@ -1,41 +0,0 @@
# CrossDesk Linux 构建镜像
这个目录维护 CrossDesk 专用的 Ubuntu 22.04 构建环境。镜像包含 xmake、
Rust、Linux 开发库以及根据项目 `xmake.lua` 提前编译好的依赖。
## 发布方式
推送影响镜像的文件到 `ci/linux-build-image` 分支时,
`build-linux-image.yml` 会在 GitHub 原生 amd64/arm64 runner 上分别构建和验证,
全部通过后再合并为同一个 multi-arch 镜像:
```text
crossdesk/ubuntu22.04:buildenv-cuda12.6.3-xmake3.0.9-rust1.92.0
```
Docker 会根据运行机器自动选择正确架构。amd64 变体以
`nvidia/cuda:12.6.3-devel-ubuntu22.04` 为基础;arm64 变体不包含当前构建
用不到的 CUDA SDK。两个变体都使用 Ubuntu 22.04/glibc 2.35。
因此该镜像生成的 Linux 安装包最低支持 Ubuntu 22.04,不再保证兼容
Ubuntu 20.04。
每次成功发布还会生成 `sha-<完整提交 SHA>`,用于精确回滚;`latest` 和上面的
工具链版本 tag 只会在两个架构都验证成功后更新。
## GitHub 配置
在仓库的 Actions secrets 中配置:
- `DOCKERHUB_USERNAME`:有权推送 `crossdesk/ubuntu22.04` 的 Docker Hub 用户名
- `DOCKERHUB_TOKEN`:该用户的 Docker Hub access token
工作流文件进入默认分支后,也可以从 Actions 页面手动运行。为了避免首次发布
新 tag 时应用构建先于镜像完成,常规 `build.yml` 不响应
`ci/linux-build-image` 分支的 push。第一次启用时,应先推送该分支并等待镜像
发布成功,再把应用构建工作流的修改合并到 `main`
## 更新依赖
修改 Dockerfile、xmake 清单、本地包配方或子模块中的包配方后,推送该分支即可。
升级工具链时应同时修改 Dockerfile 的版本参数、工作流中的 `STACK_TAG`,以及
常规构建工作流引用的镜像 tag。
-57
View File
@@ -1,57 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
EXPECTED_ARCH="${EXPECTED_ARCH:-}"
EXPECTED_XMAKE_VERSION="${EXPECTED_XMAKE_VERSION:-3.0.9}"
EXPECTED_RUST_VERSION="${EXPECTED_RUST_VERSION:-1.92.0}"
# shellcheck disable=SC1091
source /etc/os-release
if [[ "${ID}" != "ubuntu" || "${VERSION_ID}" != "22.04" ]]; then
echo "Expected Ubuntu 22.04, found ${ID:-unknown} ${VERSION_ID:-unknown}" >&2
exit 1
fi
GLIBC_VERSION="$(getconf GNU_LIBC_VERSION | awk '{print $2}')"
if [[ "${GLIBC_VERSION}" != "2.35" ]]; then
echo "Expected glibc 2.35, found ${GLIBC_VERSION}" >&2
exit 1
fi
ACTUAL_ARCH="$(dpkg --print-architecture)"
if [[ -n "${EXPECTED_ARCH}" && "${ACTUAL_ARCH}" != "${EXPECTED_ARCH}" ]]; then
echo "Expected architecture ${EXPECTED_ARCH}, found ${ACTUAL_ARCH}" >&2
exit 1
fi
XMAKE_VERSION_OUTPUT="$(xmake --version 2>&1)"
grep -F "v${EXPECTED_XMAKE_VERSION}" <<<"${XMAKE_VERSION_OUTPUT}" >/dev/null
rustc --version | grep -F "rustc ${EXPECTED_RUST_VERSION}" >/dev/null
cargo --version >/dev/null
cmake --version >/dev/null
g++ --version >/dev/null
pkg-config --exists xft
for package_path in \
"s/slint" \
"l/libdatachannel" \
"o/openssl3" \
"s/spdlog"; do
if [[ ! -d "${XMAKE_GLOBALDIR}/.xmake/packages/${package_path}" ]]; then
echo "Prebuilt xmake package is missing: ${package_path}" >&2
exit 1
fi
done
if ! find "${XMAKE_GLOBALDIR}/.xmake/packages/s/slint" \
-type f -name 'slint-compiler*' -print -quit | grep -q .; then
echo "The prebuilt Slint compiler is missing" >&2
exit 1
fi
if [[ "${ACTUAL_ARCH}" == "amd64" && ! -f "${CUDA_PATH}/include/cuda.h" ]]; then
echo "The amd64 image is missing CUDA headers under ${CUDA_PATH}" >&2
exit 1
fi
echo "Verified CrossDesk build image: Ubuntu ${VERSION_ID}, ${ACTUAL_ARCH}, glibc ${GLIBC_VERSION}"
@@ -1,64 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -lt 1 || $# -gt 2 ]]; then
echo "Usage: $0 <ELF-file-or-directory> [maximum-glibc-version]" >&2
exit 2
fi
INPUT_PATH="$1"
MAXIMUM_VERSION="${2:-2.35}"
verify_elf() {
local elf_file="$1"
local version=""
local highest_version="none"
local -a required_versions=()
mapfile -t required_versions < <(
readelf --version-info "${elf_file}" \
| grep -oE 'GLIBC_[0-9]+(\.[0-9]+)+' \
| sed 's/^GLIBC_//' \
| sort -Vu \
|| true
)
for version in "${required_versions[@]}"; do
if dpkg --compare-versions "${version}" gt "${MAXIMUM_VERSION}"; then
echo "${elf_file} requires GLIBC_${version}; maximum allowed is GLIBC_${MAXIMUM_VERSION}" >&2
return 1
fi
done
if [[ ${#required_versions[@]} -gt 0 ]]; then
highest_version="${required_versions[-1]}"
fi
echo "Verified ${elf_file}: highest required glibc version is ${highest_version}"
}
if [[ -f "${INPUT_PATH}" ]]; then
if ! readelf -h "${INPUT_PATH}" >/dev/null 2>&1; then
echo "Not an ELF file: ${INPUT_PATH}" >&2
exit 1
fi
verify_elf "${INPUT_PATH}"
exit 0
fi
if [[ ! -d "${INPUT_PATH}" ]]; then
echo "File or directory does not exist: ${INPUT_PATH}" >&2
exit 1
fi
ELF_COUNT=0
while IFS= read -r -d '' candidate; do
if readelf -h "${candidate}" >/dev/null 2>&1; then
verify_elf "${candidate}"
ELF_COUNT=$((ELF_COUNT + 1))
fi
done < <(find "${INPUT_PATH}" -type f -print0)
if [[ ${ELF_COUNT} -eq 0 ]]; then
echo "No ELF files found under ${INPUT_PATH}" >&2
exit 1
fi
-33
View File
@@ -1,33 +0,0 @@
# 常见问题(FAQ
欢迎来到 **CrossDesk 常见问题** 页面!
这里整理了用户和开发者最常见的一些疑问。如果你没有找到答案,欢迎在 [Issues](https://github.com/kunkundi/crossdesk/issues) 中反馈。
---
### Q1. 对等连接失败
**A:**
打开设置,勾选 **启用中继服务** 选项,尝试重新发起连接。
<img width="396" height="306" alt="Image" src="https://github.com/user-attachments/assets/fd8db148-c782-4f4d-b874-8f1b2a7ec7d6" />
由于公共中继服务器带宽较小,连接的清晰度流畅度可能会下降,建议自建服务器。 [Issue #8](https://github.com/kunkundi/crossdesk/issues/8)
### Q2. Windows 无 CUDA 环境下编译
**A:**
运行下面的命令安装 CUDA 编译环境。
```
xmake require -vy "cuda 12.6.3"
```
安装完成后执行
```
xmake require --info "cuda 12.6.3"
```
输出如下
<img width="860" height="226" alt="Image" src="https://github.com/user-attachments/assets/999ac365-581a-4b9a-806e-05eb3e4cf44d" />
根据上述输出获取到 CUDA 的安装目录,即 installdir 指向的位置。将 CUDA_PATH 加入系统环境变量,或在终端中输入 set CUDA_PATH=path_to_cuda_installdir,重新执行 xmake b -vy crossdesk 即可。
[Issue #6](https://github.com/kunkundi/crossdesk/issues/6)
---
-207
View File
@@ -1,207 +0,0 @@
# GUI 目录结构与职责说明
本文档说明 `src/gui` 的目录结构、核心类型、依赖方向和代码归属规则,用于避免 `Render``GuiApplication``GuiRuntime` 再次演变成职责混杂的大类。
## 目录结构
```text
src/gui/
├── render.h # 对外稳定入口 Render
├── render.cpp # 创建 GuiApplication 并转发 Run
├── application/ # SDL/ImGui 应用外壳
│ ├── gui_application.h # GuiApplication 声明
│ ├── gui_application.cpp # 初始化、主循环和清理
│ ├── application_state.h # 窗口、交互和 UI 状态
│ ├── sdl_event_dispatch.cpp # SDL 窗口及应用事件分发
│ ├── sdl_events.cpp # 键盘和鼠标事件转换
│ ├── window_lifecycle.cpp # 原生窗口及 ImGui 上下文生命周期
│ └── window_rendering.cpp # 三类窗口的渲染流程
├── runtime/ # 非界面的 GUI 运行时
│ ├── gui_runtime.h/.cpp # 运行时协调接口与公共实现
│ ├── connection_runtime.cpp # 连接、在线探测、超时及会话清理
│ ├── windows_service_runtime.cpp # Windows 服务和安全桌面集成
│ ├── mac_permission_runtime.mm # macOS 权限检查和系统设置调用
│ ├── gui_state.h # ApplicationState 与 RuntimeState 组合点
│ ├── runtime_state.h # 配置、连接、平台和通信状态
│ ├── remote_session.h # 单个远端会话 RemoteSession
│ ├── device_presence_cache.h # 设备在线状态缓存
│ ├── peer_event_handler.h/.cpp # 信令和连接状态回调
│ ├── peer_media_callbacks.cpp # 视频和音频回调
│ ├── peer_data_callbacks.cpp # 控制、剪贴板和文件数据回调
│ └── remote_action_codec.h/.cpp # RemoteAction 编解码
├── features/ # 可独立演进的功能模块
│ ├── clipboard/ # 本地与远端剪贴板同步
│ ├── devices/ # 媒体及输入设备生命周期
│ ├── file_transfer/ # 文件队列、发送、确认和进度
│ ├── input/ # 键盘状态、命令和超时处理
│ └── settings/ # 配置缓存和最近连接别名
├── views/ # ImGui 视图实现
│ ├── panels/ # 主窗口内嵌面板
│ ├── toolbars/ # 标题栏、状态栏和控制栏
│ └── windows/ # 独立窗口和模态对话框
├── platform/ # 原生桌面平台适配
│ └── tray/ # Windows、macOS 和 Linux 托盘
└── assets/ # 字体、图标、布局和本地化资源
```
## 分层关系
```text
Render
└── GuiApplication
├── SDL/ImGui 生命周期
├── views/panels / views/toolbars / views/windows
└── GuiRuntime
├── SessionDeviceManager
├── KeyboardController
├── ClipboardController
├── FileTransferManager
├── SettingsManager
└── PeerEventHandler
```
依赖方向应保持从上向下:
```text
公开入口 → 应用层 → 运行时协调层 → 功能模块 → 底层库
↘ 视图层
```
底层功能模块不应反向依赖具体面板、工具栏或窗口。
## 核心类型
### Render
`Render` 是应用其他部分可见的稳定入口,只负责:
- 管理 `GuiApplication` 生命周期;
-`Run()` 转发给 `GuiApplication`
- 隔离 GUI 内部类型,避免实现细节扩散到其他模块。
不要向 `Render` 添加窗口状态、连接状态或业务方法。
### GuiApplication
`GuiApplication` 是 SDL/ImGui 应用外壳,负责:
- 初始化日志、配置、SDL 和功能模块;
- 创建、销毁原生窗口及 ImGui 上下文;
- 执行主事件循环;
- 分发 SDL 事件;
- 调用面板、工具栏和窗口的绘制方法;
- 在程序退出时按顺序清理资源。
它不应实现连接协议、文件传输、剪贴板或设备控制细节。
### GuiRuntime
`GuiRuntime` 是 GUI 进程的非界面协调层,负责:
- 初始化 MiniRTC Peer 及回调参数;
- 协调连接、远端会话和在线状态;
- 持有各功能 Manager/Controller
- 处理跨功能模块的调用顺序;
- 提供平台集成功能所需的运行时上下文。
`GuiRuntime` 应保持为协调器。新的独立功能应优先创建 Manager 或 Controller,不应直接继续堆积到 `GuiRuntime`
### RemoteSession
`RemoteSession` 表示一个连接中或已连接的远端端点,其生命周期覆盖:
- MiniRTC Peer 和连接状态;
- 视频帧、纹理和渲染区域;
- 远端显示器和控制栏状态;
- 音频、鼠标和键盘控制状态;
- 文件传输状态;
- Windows 服务及安全桌面状态。
`remote_sessions_` 是按远端 ID 索引的会话表。会话查找、连接和清理统一由运行时处理。
## 状态划分
状态定义按生命周期和使用范围划分:
| 文件 | 状态范围 |
| --- | --- |
| `application/application_state.h` | SDL 窗口、渲染上下文、交互标志和 UI 显示状态 |
| `runtime/runtime_state.h` | 配置、Peer、连接表、在线探测和平台集成状态 |
| `runtime/remote_session.h` | 单个远端会话独占的连接、媒体、控制和文件状态 |
| `runtime/gui_state.h` | 仅组合 `ApplicationState``RuntimeState` |
添加状态前应先判断其生命周期:
- 只属于某个远端连接:放入 `RemoteSession`
- 只属于窗口或 UI:放入 `ApplicationState` 对应子状态;
- 属于整个 GUI 运行期:放入 `RuntimeState` 对应子状态;
- 只属于某个功能模块:优先作为 Manager/Controller 的私有成员。
## 主要运行流程
### 启动
```text
Render::Run
→ GuiApplication::Run
→ 初始化路径、日志和配置
→ 初始化 SDL 和功能模块
→ GuiRuntime::CreateConnectionPeer
→ 创建主窗口
→ GuiApplication::MainLoop
```
### 建立远端连接
```text
remote_peer_panel
→ GuiRuntime::ConnectTo
→ 在线状态探测
→ 创建或复用 RemoteSession
→ MiniRTC JoinConnection
→ PeerEventHandler 接收连接状态
→ 创建串流窗口并开始渲染
```
### 接收远端数据
```text
MiniRTC callback
→ PeerEventHandler
├── peer_media_callbacks:视频、音频
├── peer_data_callbacks:控制、文件、剪贴板
└── peer_event_handler:信令、连接状态、网络统计
→ 对应 Manager/Controller
→ 更新 RemoteSession 或运行时状态
```
### 退出和清理
```text
SDL quit / tray exit
→ GuiApplication::Cleanup
→ CloseAllRemoteSessions
→ 停止设备及后台任务
→ 销毁 Peer
→ 销毁 ImGui 上下文和 SDL 窗口
→ SDL_Quit
```
## 新代码归属规则
新增代码时按以下规则选择目录:
| 功能 | 目录 |
| --- | --- |
| SDL 初始化、事件循环、窗口生命周期 | `application/` |
| 连接、会话、Peer 回调和平台运行时 | `runtime/` |
| 视频、音频、鼠标、键盘设备生命周期 | `features/devices/` |
| 键盘协议和按键状态 | `features/input/` |
| 剪贴板同步 | `features/clipboard/` |
| 文件传输 | `features/file_transfer/` |
| 配置持久化 | `features/settings/` |
| 主窗口内嵌区域 | `views/panels/` |
| 标题栏、状态栏、控制栏 | `views/toolbars/` |
| 独立窗口和对话框 | `views/windows/` |
| 系统托盘 | `platform/tray/` |
| 字体、图标、布局和本地化数据 | `assets/` |
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 746 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

-183
View File
@@ -1,183 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -lt 3 || $# -gt 4 ]]; then
echo "Usage: $0 <debian-arch> <xmake-arch> <version> [extra-recommendation]" >&2
exit 2
fi
DEBIAN_ARCH="$1"
XMAKE_ARCH="$2"
INPUT_VERSION="$3"
EXTRA_RECOMMENDATION="${4:-}"
PKG_NAME="crossdesk"
APP_NAME="CrossDesk"
MAINTAINER="Junkun Di <junkun.di@hotmail.com>"
DESCRIPTION="A simple cross-platform remote desktop client."
ALSA_RUNTIME_DEP="libasound2 | libasound2t64"
PORTAL_RUNTIME_RECOMMENDS="xdg-desktop-portal, xdg-desktop-portal-gtk | xdg-desktop-portal-kde | xdg-desktop-portal-wlr"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
normalize_app_version() {
local input="$1"
local prefix=""
local body="$input"
if [[ "$body" == v* ]]; then
prefix="v"
body="${body#v}"
fi
if [[ "$body" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$ ]]; then
echo "${prefix}${BASH_REMATCH[1]}-${BASH_REMATCH[4]}-${BASH_REMATCH[3]}"
else
echo "$input"
fi
}
find_slint_runtime() {
local needed="$1"
local resolved=""
resolved="$(ldd "$BUILD_BINARY" 2>/dev/null | awk -v needed="$needed" \
'$1 == needed && $2 == "=>" && $3 ~ /^\// { print $3; exit }')"
if [[ -n "$resolved" && -f "$resolved" ]]; then
echo "$resolved"
return 0
fi
local package_roots=()
if [[ -n "${XMAKE_GLOBALDIR:-}" ]]; then
package_roots+=("$XMAKE_GLOBALDIR/.xmake/packages")
fi
package_roots+=("$HOME/.xmake/packages" "/data/.xmake/packages")
local package_root=""
local candidate=""
for package_root in "${package_roots[@]}"; do
[[ -d "$package_root/s/slint" ]] || continue
candidate="$(find "$package_root/s/slint" -path "*/lib/$needed" -print -quit 2>/dev/null || true)"
if [[ -n "$candidate" ]]; then
echo "$candidate"
return 0
fi
done
return 1
}
APP_VERSION="$(normalize_app_version "$INPUT_VERSION")"
DEB_VERSION="${APP_VERSION#v}"
if [[ ! "$DEB_VERSION" =~ ^[0-9][0-9A-Za-z.+:~_-]*$ ]]; then
echo "Invalid Debian version: $DEB_VERSION" >&2
exit 2
fi
BUILD_BINARY="$PROJECT_ROOT/build/linux/$XMAKE_ARCH/release/crossdesk"
if [[ ! -x "$BUILD_BINARY" ]]; then
echo "Missing release binary: $BUILD_BINARY" >&2
echo "Build it first with: xmake f -m release -y && xmake b -vy crossdesk" >&2
exit 1
fi
for command_name in readelf ldd dpkg-deb; do
if ! command -v "$command_name" >/dev/null 2>&1; then
echo "Required packaging command is missing: $command_name" >&2
exit 1
fi
done
WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/crossdesk-deb.XXXXXX")"
trap 'rm -rf "$WORK_DIR"' EXIT
DEB_DIR="$WORK_DIR/${PKG_NAME}-${DEB_VERSION}"
DEBIAN_DIR="$DEB_DIR/DEBIAN"
BIN_DIR="$DEB_DIR/usr/bin"
PRIVATE_DIR="$DEB_DIR/usr/lib/$PKG_NAME"
ICON_BASE_DIR="$DEB_DIR/usr/share/icons/hicolor"
DESKTOP_DIR="$DEB_DIR/usr/share/applications"
OUTPUT_FILE="$PROJECT_ROOT/${PKG_NAME}-linux-${DEBIAN_ARCH}-${APP_VERSION}.deb"
install -d "$DEBIAN_DIR" "$BIN_DIR" "$PRIVATE_DIR" "$DESKTOP_DIR"
install -m 0755 "$BUILD_BINARY" "$PRIVATE_DIR/crossdesk-bin"
# Slint is intentionally built as a shared library. Keep that runtime private
# to CrossDesk instead of relying on a non-existent distribution package.
SLINT_NEEDED="$(readelf -d "$BUILD_BINARY" | sed -n \
's/.*Shared library: \[\(libslint_cpp[^]]*\)\].*/\1/p' | head -n 1)"
if [[ -n "$SLINT_NEEDED" ]]; then
SLINT_LIBRARY="$(find_slint_runtime "$SLINT_NEEDED" || true)"
if [[ -z "$SLINT_LIBRARY" ]]; then
echo "Unable to locate the required Slint runtime: $SLINT_NEEDED" >&2
exit 1
fi
install -m 0755 "$SLINT_LIBRARY" "$PRIVATE_DIR/$SLINT_NEEDED"
fi
cat > "$BIN_DIR/$PKG_NAME" <<'EOF'
#!/bin/sh
private_lib_dir="/usr/lib/crossdesk"
if [ -n "${LD_LIBRARY_PATH:-}" ]; then
export LD_LIBRARY_PATH="$private_lib_dir:$LD_LIBRARY_PATH"
else
export LD_LIBRARY_PATH="$private_lib_dir"
fi
exec /usr/lib/crossdesk/crossdesk-bin "$@"
EOF
chmod 0755 "$BIN_DIR/$PKG_NAME"
ln -s "$PKG_NAME" "$BIN_DIR/$APP_NAME"
for size in 16 24 32 48 64 96 128 256; do
install -d "$ICON_BASE_DIR/${size}x${size}/apps"
install -m 0644 "$PROJECT_ROOT/icons/linux/crossdesk_${size}x${size}.png" \
"$ICON_BASE_DIR/${size}x${size}/apps/${PKG_NAME}.png"
done
RECOMMENDS="$PORTAL_RUNTIME_RECOMMENDS"
if [[ -n "$EXTRA_RECOMMENDATION" ]]; then
RECOMMENDS="$RECOMMENDS, $EXTRA_RECOMMENDATION"
fi
cat > "$DEBIAN_DIR/control" <<EOF
Package: $PKG_NAME
Version: $DEB_VERSION
Architecture: $DEBIAN_ARCH
Maintainer: $MAINTAINER
Description: $DESCRIPTION
Depends: libc6 (>= 2.35), libstdc++6 (>= 9), libx11-6, libxext6,
libxrender1, libxft2, libxrandr2, libxfixes3, libxcb1, libxcb-randr0,
libxcb-xtest0, libxcb-xinerama0, libxcb-shape0, libxcb-xkb1,
libxcb-xfixes0, libxv1, libxtst6, $ALSA_RUNTIME_DEP, libsndio7.0,
libxcb-shm0, libpulse0, libdrm2, libdbus-1-3
Recommends: $RECOMMENDS
Priority: optional
Section: utils
EOF
cat > "$DESKTOP_DIR/$PKG_NAME.desktop" <<EOF
[Desktop Entry]
Version=$DEB_VERSION
Name=$APP_NAME
Comment=$DESCRIPTION
Exec=/usr/bin/$PKG_NAME
Icon=$PKG_NAME
Terminal=false
Type=Application
Categories=Utility;
EOF
rm -f "$OUTPUT_FILE"
dpkg-deb --root-owner-group --build "$DEB_DIR" "$OUTPUT_FILE"
dpkg-deb --info "$OUTPUT_FILE" >/dev/null
dpkg-deb --contents "$OUTPUT_FILE" > "$WORK_DIR/package-contents.txt"
if [[ -n "$SLINT_NEEDED" ]] && \
! grep -q "usr/lib/$PKG_NAME/$SLINT_NEEDED" "$WORK_DIR/package-contents.txt"; then
echo "Packaged artifact is missing $SLINT_NEEDED" >&2
exit 1
fi
echo "Deb package created: $OUTPUT_FILE"
-11
View File
@@ -1,11 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [[ $# -ne 1 ]]; then
echo "Usage: $0 <version>" >&2
exit 2
fi
exec bash "$SCRIPT_DIR/package_deb.sh" amd64 x86_64 "$1" nvidia-cuda-toolkit
-11
View File
@@ -1,11 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [[ $# -ne 1 ]]; then
echo "Usage: $0 <version>" >&2
exit 2
fi
exec bash "$SCRIPT_DIR/package_deb.sh" arm64 arm64 "$1"
-212
View File
@@ -1,212 +0,0 @@
#!/bin/bash
set -e
APP_NAME="crossdesk"
APP_NAME_UPPER="CrossDesk"
EXECUTABLE_PATH="./build/macosx/arm64/release/crossdesk"
PLATFORM="macos"
ARCH="arm64"
IDENTIFIER="cn.crossdesk.app"
ICON_PATH="icons/macos/crossdesk.icns"
MACOS_MIN_VERSION="10.12"
normalize_app_version() {
local input="$1"
local prefix=""
local body="$input"
if [[ "$body" == v* ]]; then
prefix="v"
body="${body#v}"
fi
if [[ "$body" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$ ]]; then
echo "${prefix}${BASH_REMATCH[1]}-${BASH_REMATCH[4]}-${BASH_REMATCH[3]}"
else
echo "$input"
fi
}
APP_VERSION="$(normalize_app_version "$1")"
APP_BUNDLE="${APP_NAME_UPPER}.app"
CONTENTS_DIR="${APP_BUNDLE}/Contents"
MACOS_DIR="${CONTENTS_DIR}/MacOS"
RESOURCES_DIR="${CONTENTS_DIR}/Resources"
INSTALLER_TITLE="${APP_NAME_UPPER} ${APP_VERSION}"
PKG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.pkg"
DMG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.dmg"
VOL_NAME="Install ${APP_NAME_UPPER}"
echo "delete old files"
rm -rf "${APP_BUNDLE}" "${PKG_NAME}" "${DMG_NAME}" build_pkg_temp CrossDesk_dmg_temp
mkdir -p build_pkg_temp
mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}"
cp "${EXECUTABLE_PATH}" "${MACOS_DIR}/${APP_NAME_UPPER}"
chmod +x "${MACOS_DIR}/${APP_NAME_UPPER}"
if [ -f "${ICON_PATH}" ]; then
cp "${ICON_PATH}" "${RESOURCES_DIR}/crossedesk.icns"
ICON_KEY="<key>CFBundleIconFile</key><string>crossedesk.icns</string>"
else
ICON_KEY=""
fi
echo "generate Info.plist"
cat > "${CONTENTS_DIR}/Info.plist" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>${APP_NAME_UPPER}</string>
<key>CFBundleDisplayName</key>
<string>${APP_NAME_UPPER}</string>
<key>CFBundleIdentifier</key>
<string>${IDENTIFIER}</string>
<key>CFBundleVersion</key>
<string>${APP_VERSION}</string>
<key>CFBundleShortVersionString</key>
<string>${APP_VERSION}</string>
<key>CFBundleExecutable</key>
<string>${APP_NAME_UPPER}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
${ICON_KEY}
<key>LSMinimumSystemVersion</key>
<string>${MACOS_MIN_VERSION}</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>应用需要访问摄像头</string>
<key>NSMicrophoneUsageDescription</key>
<string>应用需要访问麦克风</string>
<key>NSAppleEventsUsageDescription</key>
<string>应用需要发送 Apple 事件</string>
<key>NSScreenCaptureUsageDescription</key>
<string>应用需要录屏权限以捕获屏幕内容</string>
</dict>
</plist>
EOF
xattr -cr "${APP_BUNDLE}" 2>/dev/null || true
find "${APP_BUNDLE}" -name '._*' -delete
echo ".app created successfully."
mkdir -p build_pkg_scripts
cp scripts/macosx/tcc_postinstall.sh build_pkg_scripts/postinstall
chmod +x build_pkg_scripts/postinstall
mkdir -p build_pkg_resources
cat > build_pkg_resources/welcome.html <<EOF
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
font-size: 13px;
line-height: 1.5;
color: #222;
margin: 0;
padding: 0;
}
h1 {
font-size: 20px;
font-weight: 600;
margin: 0 0 12px;
}
p {
margin: 0 0 10px;
}
</style>
</head>
<body>
<h1>欢迎安装 ${INSTALLER_TITLE}</h1>
<p>CrossDesk 将安装到“应用程序”文件夹。</p>
<p>首次启动时,CrossDesk 会引导你在系统设置中授予必要权限,包括辅助功能、录屏与系统录音等。</p>
<p>为避免旧版本授权残留造成状态误判,安装后可能需要重新授权。</p>
<p>安装完成后,请从“应用程序”文件夹启动 CrossDesk。</p>
</body>
</html>
EOF
echo "building pkg..."
pkgbuild \
--identifier "${IDENTIFIER}" \
--version "${APP_VERSION}" \
--install-location "/Applications" \
--component "${APP_BUNDLE}" \
--scripts build_pkg_scripts \
build_pkg_temp/${APP_NAME}-component.pkg
cat > build_pkg_temp/Distribution <<EOF
<?xml version="1.0" encoding="utf-8"?>
<installer-gui-script minSpecVersion="1">
<title>${INSTALLER_TITLE}</title>
<welcome file="welcome.html" mime-type="text/html"/>
<options customize="never" require-scripts="false" hostArchitectures="arm64"/>
<choices-outline>
<line choice="default">
<line choice="${IDENTIFIER}"/>
</line>
</choices-outline>
<choice id="default" title="${INSTALLER_TITLE}"/>
<choice id="${IDENTIFIER}" title="${INSTALLER_TITLE}" visible="false">
<pkg-ref id="${IDENTIFIER}"/>
</choice>
<pkg-ref id="${IDENTIFIER}" version="${APP_VERSION}" onConclusion="none">crossdesk-component.pkg</pkg-ref>
</installer-gui-script>
EOF
productbuild \
--distribution build_pkg_temp/Distribution \
--package-path build_pkg_temp \
--resources build_pkg_resources \
"${PKG_NAME}"
echo "PKG package created: ${PKG_NAME}"
# Set custom icon for PKG file
if [ -f "${ICON_PATH}" ]; then
echo "Setting custom icon for PKG file..."
# Create a temporary iconset from icns
TEMP_ICON_DIR=$(mktemp -d)
cp "${ICON_PATH}" "${TEMP_ICON_DIR}/icon.icns"
# Use sips to create a png from icns for the icon
sips -s format png "${TEMP_ICON_DIR}/icon.icns" --out "${TEMP_ICON_DIR}/icon.png" 2>/dev/null || true
# Method: Use osascript to set file icon (works on macOS)
osascript <<APPLESCRIPT
use framework "Foundation"
use framework "AppKit"
set iconPath to POSIX file "${TEMP_ICON_DIR}/icon.icns"
set targetPath to POSIX file "$(pwd)/${PKG_NAME}"
set iconImage to current application's NSImage's alloc()'s initWithContentsOfFile:(POSIX path of iconPath)
set workspace to current application's NSWorkspace's sharedWorkspace()
workspace's setIcon:iconImage forFile:(POSIX path of targetPath) options:0
APPLESCRIPT
if [ $? -eq 0 ]; then
echo "Custom icon set successfully for ${PKG_NAME}"
else
echo "Warning: Failed to set custom icon (this is optional)"
fi
rm -rf "${TEMP_ICON_DIR}"
fi
echo "Set icon finished"
rm -rf build_pkg_temp build_pkg_scripts build_pkg_resources ${APP_BUNDLE}
echo "PKG package created successfully."
echo "package ${APP_BUNDLE}"
echo "installer ${PKG_NAME}"
-212
View File
@@ -1,212 +0,0 @@
#!/bin/bash
set -e
APP_NAME="crossdesk"
APP_NAME_UPPER="CrossDesk"
EXECUTABLE_PATH="build/macosx/x86_64/release/crossdesk"
PLATFORM="macos"
ARCH="x64"
IDENTIFIER="cn.crossdesk.app"
ICON_PATH="icons/macos/crossdesk.icns"
MACOS_MIN_VERSION="10.12"
normalize_app_version() {
local input="$1"
local prefix=""
local body="$input"
if [[ "$body" == v* ]]; then
prefix="v"
body="${body#v}"
fi
if [[ "$body" =~ ^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$ ]]; then
echo "${prefix}${BASH_REMATCH[1]}-${BASH_REMATCH[4]}-${BASH_REMATCH[3]}"
else
echo "$input"
fi
}
APP_VERSION="$(normalize_app_version "$1")"
APP_BUNDLE="${APP_NAME_UPPER}.app"
CONTENTS_DIR="${APP_BUNDLE}/Contents"
MACOS_DIR="${CONTENTS_DIR}/MacOS"
RESOURCES_DIR="${CONTENTS_DIR}/Resources"
INSTALLER_TITLE="${APP_NAME_UPPER} ${APP_VERSION}"
PKG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.pkg"
DMG_NAME="${APP_NAME}-${PLATFORM}-${ARCH}-${APP_VERSION}.dmg"
VOL_NAME="Install ${APP_NAME_UPPER}"
echo "delete old files"
rm -rf "${APP_BUNDLE}" "${PKG_NAME}" "${DMG_NAME}" build_pkg_temp CrossDesk_dmg_temp
mkdir -p build_pkg_temp
mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}"
cp "${EXECUTABLE_PATH}" "${MACOS_DIR}/${APP_NAME_UPPER}"
chmod +x "${MACOS_DIR}/${APP_NAME_UPPER}"
if [ -f "${ICON_PATH}" ]; then
cp "${ICON_PATH}" "${RESOURCES_DIR}/crossedesk.icns"
ICON_KEY="<key>CFBundleIconFile</key><string>crossedesk.icns</string>"
else
ICON_KEY=""
fi
echo "generate Info.plist"
cat > "${CONTENTS_DIR}/Info.plist" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>${APP_NAME_UPPER}</string>
<key>CFBundleDisplayName</key>
<string>${APP_NAME_UPPER}</string>
<key>CFBundleIdentifier</key>
<string>${IDENTIFIER}</string>
<key>CFBundleVersion</key>
<string>${APP_VERSION}</string>
<key>CFBundleShortVersionString</key>
<string>${APP_VERSION}</string>
<key>CFBundleExecutable</key>
<string>${APP_NAME_UPPER}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
${ICON_KEY}
<key>LSMinimumSystemVersion</key>
<string>${MACOS_MIN_VERSION}</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>应用需要访问摄像头</string>
<key>NSMicrophoneUsageDescription</key>
<string>应用需要访问麦克风</string>
<key>NSAppleEventsUsageDescription</key>
<string>应用需要发送 Apple 事件</string>
<key>NSScreenCaptureUsageDescription</key>
<string>应用需要录屏权限以捕获屏幕内容</string>
</dict>
</plist>
EOF
xattr -cr "${APP_BUNDLE}" 2>/dev/null || true
find "${APP_BUNDLE}" -name '._*' -delete
echo ".app created successfully."
mkdir -p build_pkg_scripts
cp scripts/macosx/tcc_postinstall.sh build_pkg_scripts/postinstall
chmod +x build_pkg_scripts/postinstall
mkdir -p build_pkg_resources
cat > build_pkg_resources/welcome.html <<EOF
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
font-size: 13px;
line-height: 1.5;
color: #222;
margin: 0;
padding: 0;
}
h1 {
font-size: 20px;
font-weight: 600;
margin: 0 0 12px;
}
p {
margin: 0 0 10px;
}
</style>
</head>
<body>
<h1>欢迎安装 ${INSTALLER_TITLE}</h1>
<p>CrossDesk 将安装到“应用程序”文件夹。</p>
<p>首次启动时,CrossDesk 会引导你在系统设置中授予必要权限,包括辅助功能、录屏与系统录音等。</p>
<p>为避免旧版本授权残留造成状态误判,安装后可能需要重新授权。</p>
<p>安装完成后,请从“应用程序”文件夹启动 CrossDesk。</p>
</body>
</html>
EOF
echo "building pkg..."
pkgbuild \
--identifier "${IDENTIFIER}" \
--version "${APP_VERSION}" \
--install-location "/Applications" \
--component "${APP_BUNDLE}" \
--scripts build_pkg_scripts \
build_pkg_temp/${APP_NAME}-component.pkg
cat > build_pkg_temp/Distribution <<EOF
<?xml version="1.0" encoding="utf-8"?>
<installer-gui-script minSpecVersion="1">
<title>${INSTALLER_TITLE}</title>
<welcome file="welcome.html" mime-type="text/html"/>
<options customize="never" require-scripts="false" hostArchitectures="x86_64"/>
<choices-outline>
<line choice="default">
<line choice="${IDENTIFIER}"/>
</line>
</choices-outline>
<choice id="default" title="${INSTALLER_TITLE}"/>
<choice id="${IDENTIFIER}" title="${INSTALLER_TITLE}" visible="false">
<pkg-ref id="${IDENTIFIER}"/>
</choice>
<pkg-ref id="${IDENTIFIER}" version="${APP_VERSION}" onConclusion="none">crossdesk-component.pkg</pkg-ref>
</installer-gui-script>
EOF
productbuild \
--distribution build_pkg_temp/Distribution \
--package-path build_pkg_temp \
--resources build_pkg_resources \
"${PKG_NAME}"
echo "PKG package created: ${PKG_NAME}"
# Set custom icon for PKG file
if [ -f "${ICON_PATH}" ]; then
echo "Setting custom icon for PKG file..."
# Create a temporary iconset from icns
TEMP_ICON_DIR=$(mktemp -d)
cp "${ICON_PATH}" "${TEMP_ICON_DIR}/icon.icns"
# Use sips to create a png from icns for the icon
sips -s format png "${TEMP_ICON_DIR}/icon.icns" --out "${TEMP_ICON_DIR}/icon.png" 2>/dev/null || true
# Method: Use osascript to set file icon (works on macOS)
osascript <<APPLESCRIPT
use framework "Foundation"
use framework "AppKit"
set iconPath to POSIX file "${TEMP_ICON_DIR}/icon.icns"
set targetPath to POSIX file "$(pwd)/${PKG_NAME}"
set iconImage to current application's NSImage's alloc()'s initWithContentsOfFile:(POSIX path of iconPath)
set workspace to current application's NSWorkspace's sharedWorkspace()
workspace's setIcon:iconImage forFile:(POSIX path of targetPath) options:0
APPLESCRIPT
if [ $? -eq 0 ]; then
echo "Custom icon set successfully for ${PKG_NAME}"
else
echo "Warning: Failed to set custom icon (this is optional)"
fi
rm -rf "${TEMP_ICON_DIR}"
fi
echo "Set icon finished"
rm -rf build_pkg_temp build_pkg_scripts build_pkg_resources ${APP_BUNDLE}
echo "PKG package created successfully."
echo "package ${APP_BUNDLE}"
echo "installer ${PKG_NAME}"
-112
View File
@@ -1,112 +0,0 @@
#!/bin/bash
set -e
APP_IDENTIFIER="cn.crossdesk.app"
# Keep known historical identifiers here. tccutil only resets identifiers that
# Launch Services can currently resolve, so path/db cleanup below remains a
# best-effort fallback for stale entries from unsigned or removed builds.
BUNDLE_IDENTIFIERS=(
"cn.crossdesk.app"
"cn.crossdesk.CrossDesk"
"com.crossdesk.app"
"com.crossdesk.CrossDesk"
"com.kunkundi.crossdesk"
"com.kunkundi.CrossDesk"
)
TCC_SERVICES=(
"ScreenCapture"
"Accessibility"
"Microphone"
"AudioCapture"
)
run_tccutil() {
local user_name="$1"
local user_id="$2"
local service="$3"
local bundle_id="$4"
if [ -n "$user_name" ] && [ -n "$user_id" ]; then
/bin/launchctl asuser "$user_id" \
/usr/bin/sudo -u "$user_name" \
/usr/bin/tccutil reset "$service" "$bundle_id" >/dev/null 2>&1
else
/usr/bin/tccutil reset "$service" "$bundle_id" >/dev/null 2>&1
fi
}
reset_bundle_tcc() {
local user_name="$1"
local user_id="$2"
local bundle_id
local service
for bundle_id in "${BUNDLE_IDENTIFIERS[@]}"; do
if run_tccutil "$user_name" "$user_id" "All" "$bundle_id"; then
continue
fi
for service in "${TCC_SERVICES[@]}"; do
run_tccutil "$user_name" "$user_id" "$service" "$bundle_id" || true
done
done
}
cleanup_tcc_db() {
local db_path="$1"
if [ ! -f "$db_path" ] || ! command -v sqlite3 >/dev/null 2>&1; then
return
fi
/usr/bin/sqlite3 "$db_path" <<'SQL' >/dev/null 2>&1 || true
DELETE FROM access
WHERE service IN (
'kTCCServiceScreenCapture',
'kTCCServiceAccessibility',
'kTCCServiceMicrophone',
'kTCCServiceAudioCapture'
)
AND (
client IN (
'cn.crossdesk.app',
'cn.crossdesk.CrossDesk',
'com.crossdesk.app',
'com.crossdesk.CrossDesk',
'com.kunkundi.crossdesk',
'com.kunkundi.CrossDesk'
)
OR lower(client) LIKE '%crossdesk%'
);
SQL
}
cleanup_user_tcc_db() {
local user_name="$1"
local home_dir
home_dir=$(/usr/bin/dscl . -read "/Users/${user_name}" NFSHomeDirectory 2>/dev/null |
/usr/bin/awk '{print $2}')
if [ -z "$home_dir" ]; then
return
fi
cleanup_tcc_db "${home_dir}/Library/Application Support/com.apple.TCC/TCC.db"
}
CONSOLE_USER=$(/usr/bin/stat -f "%Su" /dev/console 2>/dev/null || true)
if [ -n "$CONSOLE_USER" ] &&
[ "$CONSOLE_USER" != "root" ] &&
[ "$CONSOLE_USER" != "loginwindow" ]; then
CONSOLE_UID=$(/usr/bin/id -u "$CONSOLE_USER" 2>/dev/null || true)
reset_bundle_tcc "$CONSOLE_USER" "$CONSOLE_UID"
cleanup_user_tcc_db "$CONSOLE_USER"
fi
# Also clear any system/root-scoped decisions as a harmless fallback.
reset_bundle_tcc "" ""
cleanup_tcc_db "/Library/Application Support/com.apple.TCC/TCC.db"
exit 0
-32
View File
@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
name="CrossDesk"
type="win32" />
<description>CrossDesk Application</description>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
</assembly>
-11
View File
@@ -1,11 +0,0 @@
// Application icon resource; load by the resource name IDI_ICON1.
IDI_ICON1 ICON "..\\..\\icons\\windows\\crossdesk.ico"
#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1
#define RT_MANIFEST 24
#ifdef CROSSDESK_DEBUG
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "crossdesk_debug.manifest"
#else
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "crossdesk.manifest"
#endif
-32
View File
@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
name="CrossDesk"
type="win32" />
<description>CrossDesk Application</description>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
</assembly>
@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
name="CrossDesk"
type="win32" />
<description>CrossDesk Portable Application</description>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
</assembly>
-8
View File
@@ -1,8 +0,0 @@
// Portable build resource. The app itself runs as the current user; service
// installation raises a separate UAC prompt only when the user chooses it.
IDI_ICON1 ICON "..\\..\\icons\\windows\\crossdesk.ico"
#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1
#define RT_MANIFEST 24
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "crossdesk_portable.manifest"
Binary file not shown.
-237
View File
@@ -1,237 +0,0 @@
; Set search path
!addincludedir "${__FILEDIR__}"
; Installer initial constants
!define PRODUCT_NAME "CrossDesk"
!define PRODUCT_VERSION "${VERSION}"
!define PRODUCT_PUBLISHER "CrossDesk"
!define PRODUCT_WEB_SITE "https://www.crossdesk.cn/"
!define APP_NAME "CrossDesk"
!define UNINSTALL_REG_KEY "CrossDesk"
!define PRODUCT_SERVICE_NAME "CrossDeskService"
; Installer icon path
!define MUI_ICON "${__FILEDIR__}\..\..\icons\windows\crossdesk.ico"
; Compression settings
SetCompressor /FINAL lzma
; Request admin privileges (needed to write HKLM)
RequestExecutionLevel admin
; ------ MUI Modern UI Definition ------
!include "MUI.nsh"
!define MUI_ABORTWARNING
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
; Add run-after-install option
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_TEXT "Run ${PRODUCT_NAME}"
!define MUI_FINISHPAGE_RUN_FUNCTION LaunchApp
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_LANGUAGE "SimpChinese"
!insertmacro MUI_RESERVEFILE_INSTALLOPTIONS
; ------ End of MUI Definition ------
; Include LogicLib for process handling
!include "LogicLib.nsh"
Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
OutFile "crossdesk-win-x64-${PRODUCT_VERSION}.exe"
InstallDir "$PROGRAMFILES\CrossDesk"
InstallDirRegKey HKCU "Software\${PRODUCT_NAME}" "InstallDir"
ShowInstDetails show
Section "MainSection"
; Check if CrossDesk is running
StrCpy $1 "CrossDesk.exe"
nsProcess::_FindProcess "$1"
Pop $R0
${If} $R0 = 0 ;
MessageBox MB_ICONQUESTION|MB_YESNO "CrossDesk is running. Do you want to close it and continue the installation?" IDYES closeApp IDNO cancelInstall
${Else}
Goto installApp
${EndIf}
closeApp:
nsProcess::_KillProcess "$1"
Pop $R0
Sleep 500
Goto installApp
cancelInstall:
SetDetailsPrint both
MessageBox MB_ICONEXCLAMATION|MB_OK "Installation has been aborted."
Abort
installApp:
Call StopInstalledService
SetOutPath "$INSTDIR"
SetOverwrite ifnewer
; Main application executable path
File /oname=CrossDesk.exe "..\..\build\windows\x64\release\crossdesk.exe"
; Bundle service-side binaries required by the Windows service flow
File "..\..\build\windows\x64\release\crossdesk_service.exe"
File "..\..\build\windows\x64\release\crossdesk_session_helper.exe"
; Bundle runtime DLLs from the release output directory
File "..\..\build\windows\x64\release\*.dll"
Call RegisterInstalledService
; Write uninstall information
WriteUninstaller "$INSTDIR\uninstall.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayName" "${PRODUCT_NAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "UninstallString" "$INSTDIR\uninstall.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "DisplayIcon" "$INSTDIR\CrossDesk.exe"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "NoModify" 1
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}" "NoRepair" 1
WriteRegStr HKCU "Software\${PRODUCT_NAME}" "InstallDir" "$INSTDIR"
SectionEnd
Section -AdditionalIcons
; Desktop shortcut
CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\CrossDesk.exe" "" "$INSTDIR\CrossDesk.exe"
; Start menu shortcut
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}.lnk" "$INSTDIR\CrossDesk.exe" "" "$INSTDIR\CrossDesk.exe"
SectionEnd
Section "Uninstall"
; Check if CrossDesk is running
StrCpy $1 "CrossDesk.exe"
nsProcess::_FindProcess "$1"
Pop $R0
${If} $R0 = 0
MessageBox MB_ICONQUESTION|MB_YESNO "CrossDesk is running. Do you want to close it and uninstall?" IDYES closeApp IDNO cancelUninstall
${Else}
Goto uninstallApp
${EndIf}
closeApp:
nsProcess::_KillProcess "$1"
Pop $R0
Sleep 500
Goto uninstallApp
cancelUninstall:
SetDetailsPrint both
MessageBox MB_ICONEXCLAMATION|MB_OK "Uninstallation has been aborted."
Abort
uninstallApp:
Call un.UnregisterInstalledService
; Delete main executable and uninstaller
Delete "$INSTDIR\CrossDesk.exe"
Delete "$INSTDIR\crossdesk_service.exe"
Delete "$INSTDIR\crossdesk_session_helper.exe"
Delete "$INSTDIR\uninstall.exe"
; Recursively delete installation directory
RMDir /r "$INSTDIR"
; Delete desktop and start menu shortcuts
Delete "$DESKTOP\${PRODUCT_NAME}.lnk"
Delete "$SMPROGRAMS\${PRODUCT_NAME}.lnk"
; Delete registry uninstall entry
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINSTALL_REG_KEY}"
; Delete remembered install dir
DeleteRegKey HKCU "Software\${PRODUCT_NAME}"
; Recursively delete CrossDesk folder in user AppData
RMDir /r "$APPDATA\CrossDesk"
RMDir /r "$LOCALAPPDATA\CrossDesk"
SectionEnd
; ------ Functions ------
Function LaunchApp
Exec "$INSTDIR\CrossDesk.exe"
FunctionEnd
Function StopInstalledService
IfFileExists "$INSTDIR\CrossDesk.exe" 0 stop_with_sc
IfFileExists "$INSTDIR\crossdesk_service.exe" 0 stop_with_sc
DetailPrint "Stopping existing CrossDesk service"
ExecWait '"$INSTDIR\CrossDesk.exe" --service-stop' $0
${If} $0 = 0
Return
${EndIf}
stop_with_sc:
DetailPrint "Stopping existing CrossDesk service via Service Control Manager"
ExecWait '"$SYSDIR\sc.exe" stop ${PRODUCT_SERVICE_NAME}' $0
${If} $0 != 0
${AndIf} $0 != 1060
${AndIf} $0 != 1062
MessageBox MB_ICONSTOP|MB_OK "Failed to stop the existing CrossDesk service. The installation will be aborted."
Abort
${EndIf}
Sleep 1500
FunctionEnd
Function RegisterInstalledService
IfFileExists "$INSTDIR\CrossDesk.exe" 0 missing_service_binary
IfFileExists "$INSTDIR\crossdesk_service.exe" 0 missing_service_binary
IfFileExists "$INSTDIR\crossdesk_session_helper.exe" 0 missing_service_binary
DetailPrint "Registering CrossDesk service"
ExecWait '"$INSTDIR\CrossDesk.exe" --service-install' $0
${If} $0 != 0
MessageBox MB_ICONSTOP|MB_OK "Failed to register the CrossDesk service. The installation will be aborted."
Abort
${EndIf}
DetailPrint "CrossDesk service registered for on-demand start"
Return
missing_service_binary:
MessageBox MB_ICONSTOP|MB_OK "CrossDesk service files are missing from the installer package. The installation will be aborted."
Abort
FunctionEnd
Function un.UnregisterInstalledService
IfFileExists "$INSTDIR\CrossDesk.exe" 0 unregister_with_sc
DetailPrint "Stopping CrossDesk service"
ExecWait '"$INSTDIR\CrossDesk.exe" --service-stop' $0
${If} $0 = 0
DetailPrint "Removing CrossDesk service"
ExecWait '"$INSTDIR\CrossDesk.exe" --service-uninstall' $0
${If} $0 = 0
Return
${EndIf}
${EndIf}
unregister_with_sc:
DetailPrint "Removing CrossDesk service via Service Control Manager"
ExecWait '"$SYSDIR\sc.exe" stop ${PRODUCT_SERVICE_NAME}' $0
${If} $0 != 0
${AndIf} $0 != 1060
${AndIf} $0 != 1062
MessageBox MB_ICONSTOP|MB_OK "Failed to stop the CrossDesk service. Uninstall will be aborted."
Abort
${EndIf}
Sleep 1500
ExecWait '"$SYSDIR\sc.exe" delete ${PRODUCT_SERVICE_NAME}' $0
${If} $0 != 0
${AndIf} $0 != 1060
MessageBox MB_ICONSTOP|MB_OK "Failed to remove the CrossDesk service. Uninstall will be aborted."
Abort
${EndIf}
FunctionEnd
-40
View File
@@ -1,40 +0,0 @@
param(
[Parameter(Mandatory = $true)]
[string]$Version
)
$ErrorActionPreference = "Stop"
function Normalize-AppVersion {
param(
[Parameter(Mandatory = $true)]
[string]$InputVersion
)
$prefix = ""
$body = $InputVersion
if ($body.StartsWith("v")) {
$prefix = "v"
$body = $body.Substring(1)
}
if ($body -match '^([0-9]+(\.[0-9]+){1,3})-([0-9]{8})-([0-9]+)$') {
return "${prefix}$($Matches[1])-$($Matches[4])-$($Matches[3])"
}
return $InputVersion
}
$normalizedVersion = Normalize-AppVersion -InputVersion $Version
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
Push-Location $scriptDir
try {
& makensis "/DVERSION=$normalizedVersion" "nsis_script.nsi"
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
} finally {
Pop-Location
}
-342
View File
@@ -1,342 +0,0 @@
#include "daemon.h"
#include <atomic>
#include <chrono>
#include <cstring>
#include <iostream>
#include <thread>
#include <vector>
#ifdef _WIN32
#include <process.h>
#include <tchar.h>
#include <windows.h>
#elif __APPLE__
#include <fcntl.h>
#include <limits.h>
#include <mach-o/dyld.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#else
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cstring>
#endif
#ifndef _WIN32
volatile std::sig_atomic_t Daemon::stop_requested_ = 0;
#endif
namespace {
constexpr int kRestartDelayMs = 1000;
#ifndef _WIN32
constexpr int kWaitPollIntervalMs = 200;
#endif
} // namespace
// get executable file path
static std::string GetExecutablePath() {
#ifdef _WIN32
char path[32768];
DWORD length = GetModuleFileNameA(nullptr, path, sizeof(path));
if (length > 0 && length < sizeof(path)) {
return std::string(path);
}
#elif __APPLE__
char path[PATH_MAX];
uint32_t size = sizeof(path);
if (_NSGetExecutablePath(path, &size) == 0) {
char resolved_path[PATH_MAX];
if (realpath(path, resolved_path) != nullptr) {
return std::string(resolved_path);
}
return std::string(path);
}
#else
char path[PATH_MAX];
ssize_t count = readlink("/proc/self/exe", path, sizeof(path) - 1);
if (count != -1) {
path[count] = '\0';
return std::string(path);
}
#endif
return "";
}
Daemon::Daemon(const std::string& name) : name_(name), running_(false) {}
void Daemon::stop() {
running_.store(false);
#ifndef _WIN32
stop_requested_ = 1;
#endif
}
bool Daemon::isRunning() const {
#ifndef _WIN32
return running_.load() && (stop_requested_ == 0);
#else
return running_.load();
#endif
}
bool Daemon::start(MainLoopFunc loop) {
#ifdef _WIN32
running_.store(true);
return runWithRestart(loop);
#elif __APPLE__
// macOS: Use child process monitoring (like Windows) to preserve GUI
stop_requested_ = 0;
running_.store(true);
return runWithRestart(loop);
#else
// linux: Daemonize first, then run with restart monitoring
stop_requested_ = 0;
// check if running from terminal before fork
bool from_terminal =
(isatty(STDIN_FILENO) != 0) || (isatty(STDOUT_FILENO) != 0);
// first fork: detach from terminal
pid_t pid = fork();
if (pid < 0) {
std::cerr << "Failed to fork daemon process" << std::endl;
return false;
}
if (pid > 0) _exit(0);
if (setsid() < 0) {
std::cerr << "Failed to create new session" << std::endl;
return false;
}
pid = fork();
if (pid < 0) {
std::cerr << "Failed to fork daemon process (second fork)" << std::endl;
return false;
}
if (pid > 0) _exit(0);
umask(0);
if (chdir("/") != 0) {
std::cerr << "Failed to change daemon working directory to /: "
<< std::strerror(errno) << std::endl;
return false;
}
// redirect file descriptors: keep stdout/stderr if from terminal, else
// redirect to /dev/null
int fd = open("/dev/null", O_RDWR);
if (fd >= 0) {
dup2(fd, STDIN_FILENO);
if (!from_terminal) {
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
}
if (fd > 2) close(fd);
}
// set up signal handlers
signal(SIGTERM, [](int) { stop_requested_ = 1; });
signal(SIGINT, [](int) { stop_requested_ = 1; });
// ignore SIGPIPE
signal(SIGPIPE, SIG_IGN);
running_.store(true);
return runWithRestart(loop);
#endif
}
#ifdef _WIN32
static int RunLoopCatchCpp(Daemon::MainLoopFunc& loop) {
try {
loop();
return 0; // normal exit
} catch (const std::exception& e) {
std::cerr << "Exception caught: " << e.what() << std::endl;
return 1; // c++ exception
} catch (...) {
std::cerr << "Unknown exception caught" << std::endl;
return 1; // other exception
}
}
static int RunLoopWithSEH(Daemon::MainLoopFunc& loop) {
__try {
return RunLoopCatchCpp(loop);
} __except (EXCEPTION_EXECUTE_HANDLER) {
// catch system-level crashes (access violation, divide by zero, etc.)
DWORD code = GetExceptionCode();
std::cerr << "System crash detected (SEH exception code: 0x" << std::hex
<< code << std::dec << ")" << std::endl;
return 2; // System crash
}
}
#endif
// run with restart logic: parent monitors child process and restarts on crash
bool Daemon::runWithRestart(MainLoopFunc loop) {
int restart_count = 0;
std::string exe_path = GetExecutablePath();
if (exe_path.empty()) {
std::cerr
<< "Failed to get executable path, falling back to direct execution"
<< std::endl;
while (isRunning()) {
try {
loop();
break;
} catch (...) {
restart_count++;
std::cerr << "Exception caught, restarting... (attempt "
<< restart_count << ")" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs));
}
}
return true;
}
while (isRunning()) {
#ifdef _WIN32
// windows: use CreateProcess to create child process
STARTUPINFOA si = {sizeof(si)};
PROCESS_INFORMATION pi = {0};
std::string cmd_line = "\"" + exe_path + "\" --child";
std::vector<char> cmd_line_buf(cmd_line.begin(), cmd_line.end());
cmd_line_buf.push_back('\0');
BOOL success = CreateProcessA(
nullptr, // executable file path (specified in command line)
cmd_line_buf.data(), // command line arguments
nullptr, // process security attributes
nullptr, // thread security attributes
FALSE, // don't inherit handles
0, // creation flags
nullptr, // environment variables (inherit from parent)
nullptr, // current directory
&si, // startup info
&pi // process information
);
if (!success) {
std::cerr << "Failed to create child process, error: " << GetLastError()
<< std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs));
restart_count++;
continue;
}
while (isRunning()) {
DWORD wait_result = WaitForSingleObject(pi.hProcess, 200);
if (wait_result == WAIT_OBJECT_0) {
break;
}
if (wait_result == WAIT_FAILED) {
std::cerr << "Failed waiting child process, error: " << GetLastError()
<< std::endl;
break;
}
}
if (!isRunning()) {
TerminateProcess(pi.hProcess, 1);
WaitForSingleObject(pi.hProcess, 3000);
}
DWORD exit_code = 0;
GetExitCodeProcess(pi.hProcess, &exit_code);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
if (!isRunning() || exit_code == 0) {
break; // normal exit
}
restart_count++;
std::cerr << "Child process exited with code " << exit_code
<< ", restarting... (attempt " << restart_count << ")"
<< std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs));
#else
// linux: use fork + exec to create child process
pid_t pid = fork();
if (pid == 0) {
execl(exe_path.c_str(), exe_path.c_str(), "--child", nullptr);
_exit(1); // exec failed
} else if (pid > 0) {
int status = 0;
pid_t waited_pid = -1;
while (isRunning()) {
waited_pid = waitpid(pid, &status, WNOHANG);
if (waited_pid == pid) {
break;
}
if (waited_pid < 0 && errno != EINTR) {
break;
}
std::this_thread::sleep_for(
std::chrono::milliseconds(kWaitPollIntervalMs));
}
if (!isRunning() && waited_pid != pid) {
kill(pid, SIGTERM);
waited_pid = waitpid(pid, &status, 0);
}
if (waited_pid < 0) {
if (!isRunning()) {
break;
}
restart_count++;
std::cerr << "waitpid failed, errno: " << errno
<< ", restarting... (attempt " << restart_count << ")"
<< std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs));
continue;
}
if (WIFEXITED(status)) {
int exit_code = WEXITSTATUS(status);
if (!isRunning() || exit_code == 0) {
break; // normal exit
}
restart_count++;
std::cerr << "Child process exited with code " << exit_code
<< ", restarting... (attempt " << restart_count << ")"
<< std::endl;
} else if (WIFSIGNALED(status)) {
if (!isRunning()) {
break;
}
restart_count++;
std::cerr << "Child process crashed with signal " << WTERMSIG(status)
<< ", restarting... (attempt " << restart_count << ")"
<< std::endl;
} else {
restart_count++;
std::cerr << "Child process exited with unknown status, restarting... "
"(attempt "
<< restart_count << ")" << std::endl;
}
std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs));
} else {
std::cerr << "Failed to fork child process" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(kRestartDelayMs));
restart_count++;
}
#endif
}
return true;
}
-37
View File
@@ -1,37 +0,0 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-11-19
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _DAEMON_H_
#define _DAEMON_H_
#include <atomic>
#include <csignal>
#include <functional>
#include <string>
class Daemon {
public:
using MainLoopFunc = std::function<void()>;
Daemon(const std::string& name);
bool start(MainLoopFunc loop);
void stop();
bool isRunning() const;
private:
std::string name_;
bool runWithRestart(MainLoopFunc loop);
#ifndef _WIN32
static volatile std::sig_atomic_t stop_requested_;
#endif
std::atomic<bool> running_;
};
#endif
-231
View File
@@ -1,231 +0,0 @@
#ifdef _WIN32
#ifdef CROSSDESK_DEBUG
#pragma comment(linker, "/subsystem:\"console\"")
#else
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
#endif
#endif
#include <cstring>
#include <filesystem>
#include <iostream>
#include <memory>
#include <string>
#ifdef _WIN32
#include <cstdio>
#include "service_host.h"
#endif
#include "config_center.h"
#include "daemon.h"
#include "path_manager.h"
#include "render.h"
#ifdef _WIN32
namespace {
void EnsureConsoleForCli() {
static bool console_ready = false;
if (console_ready) {
return;
}
if (!AttachConsole(ATTACH_PARENT_PROCESS)) {
DWORD error = GetLastError();
if (error != ERROR_ACCESS_DENIED) {
AllocConsole();
}
}
FILE* stream = nullptr;
freopen_s(&stream, "CONOUT$", "w", stdout);
freopen_s(&stream, "CONOUT$", "w", stderr);
freopen_s(&stream, "CONIN$", "r", stdin);
SetConsoleOutputCP(CP_UTF8);
console_ready = true;
}
void PrintServiceCliUsage() {
std::cout
<< "CrossDesk service management commands\n"
<< " --service-install Install the sibling crossdesk_service.exe\n"
<< " --service-uninstall Remove the installed Windows service\n"
<< " --service-start Start the Windows service\n"
<< " --service-stop Stop the Windows service\n"
<< " --service-sas Ask the service to send Secure Attention "
"Sequence\n"
<< " --service-ping Ping the service over named pipe IPC\n"
<< " --service-status Query service runtime status\n"
<< " --service-help Show this help\n";
}
std::wstring GetCurrentExecutablePathW() {
wchar_t path[MAX_PATH] = {0};
DWORD length = GetModuleFileNameW(nullptr, path, MAX_PATH);
if (length == 0 || length >= MAX_PATH) {
return L"";
}
return std::wstring(path, length);
}
std::filesystem::path GetSiblingServiceExecutablePath() {
std::wstring current_executable = GetCurrentExecutablePathW();
if (current_executable.empty()) {
return {};
}
return std::filesystem::path(current_executable).parent_path() /
L"crossdesk_service.exe";
}
bool IsServiceCliCommand(const char* arg) {
if (arg == nullptr) {
return false;
}
return std::strcmp(arg, "--service-install") == 0 ||
std::strcmp(arg, "--service-uninstall") == 0 ||
std::strcmp(arg, "--service-start") == 0 ||
std::strcmp(arg, "--service-stop") == 0 ||
std::strcmp(arg, "--service-sas") == 0 ||
std::strcmp(arg, "--service-ping") == 0 ||
std::strcmp(arg, "--service-status") == 0 ||
std::strcmp(arg, "--service-help") == 0;
}
void TryStartManagedWindowsService() {
std::filesystem::path service_path = GetSiblingServiceExecutablePath();
if (service_path.empty() || !std::filesystem::exists(service_path)) {
return;
}
if (!crossdesk::IsCrossDeskServiceInstalled()) {
return;
}
crossdesk::StartCrossDeskService();
}
int HandleServiceCliCommand(const std::string& command) {
EnsureConsoleForCli();
if (command == "--service-help") {
PrintServiceCliUsage();
return 0;
}
if (command == "--service-install") {
std::filesystem::path service_path = GetSiblingServiceExecutablePath();
if (service_path.empty()) {
std::cerr << "Failed to locate crossdesk_service.exe" << std::endl;
return 1;
}
if (!std::filesystem::exists(service_path)) {
std::cerr << "Service binary not found: " << service_path.string()
<< std::endl;
return 1;
}
bool success = crossdesk::InstallCrossDeskService(service_path.wstring());
std::cout << (success ? "install ok" : "install failed") << std::endl;
return success ? 0 : 1;
}
if (command == "--service-uninstall") {
bool success = crossdesk::UninstallCrossDeskService();
std::cout << (success ? "uninstall ok" : "uninstall failed") << std::endl;
return success ? 0 : 1;
}
if (command == "--service-start") {
bool success = crossdesk::StartCrossDeskService();
std::cout << (success ? "start ok" : "start failed") << std::endl;
return success ? 0 : 1;
}
if (command == "--service-stop") {
bool success = crossdesk::StopCrossDeskService();
std::cout << (success ? "stop ok" : "stop failed") << std::endl;
return success ? 0 : 1;
}
if (command == "--service-sas") {
std::cout << crossdesk::QueryCrossDeskService("sas") << std::endl;
return 0;
}
if (command == "--service-ping") {
std::cout << crossdesk::QueryCrossDeskService("ping") << std::endl;
return 0;
}
if (command == "--service-status") {
std::cout << crossdesk::QueryCrossDeskService("status") << std::endl;
return 0;
}
PrintServiceCliUsage();
return 1;
}
} // namespace
#endif
int main(int argc, char* argv[]) {
#ifdef _WIN32
if (argc > 1 && IsServiceCliCommand(argv[1])) {
return HandleServiceCliCommand(argv[1]);
}
#endif
// check if running as child process
bool is_child = false;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--child") == 0) {
is_child = true;
break;
}
}
if (is_child) {
// child process: run render directly
crossdesk::Render render;
render.Run();
return 0;
}
#ifdef _WIN32
TryStartManagedWindowsService();
#endif
bool enable_daemon = false;
auto path_manager = std::make_unique<crossdesk::PathManager>("CrossDesk");
if (path_manager) {
std::string cache_path = path_manager->GetCachePath().string();
crossdesk::ConfigCenter config_center(cache_path + "/config.ini");
enable_daemon = config_center.IsEnableDaemon();
}
if (enable_daemon) {
// start daemon with restart monitoring
Daemon daemon("CrossDesk");
// define main loop function: run render and stop daemon on normal exit
Daemon::MainLoopFunc main_loop = [&daemon]() {
crossdesk::Render render;
render.Run();
daemon.stop();
};
// start daemon and return result
bool success = daemon.start(main_loop);
return success ? 0 : 1;
}
// run without daemon: direct execution
crossdesk::Render render;
render.Run();
return 0;
}
-302
View File
@@ -1,302 +0,0 @@
#include "autostart.h"
#include <cstdlib>
#include <filesystem>
#include <fstream>
#ifdef _WIN32
#include <windows.h>
#elif defined(__APPLE__)
#include <limits.h>
#include <mach-o/dyld.h>
#include <unistd.h>
#elif defined(__linux__)
#include <linux/limits.h>
#include <unistd.h>
#endif
namespace crossdesk {
static std::string get_home_dir() {
const char* home = std::getenv("HOME");
if (!home) {
return "";
}
return std::string(home);
}
static bool file_exists(const std::string& path) {
return std::filesystem::exists(path) &&
std::filesystem::is_regular_file(path);
}
static std::string GetExecutablePath() {
#ifdef _WIN32
char path[32768];
DWORD length = GetModuleFileNameA(nullptr, path, sizeof(path));
if (length > 0 && length < sizeof(path)) {
return std::string(path);
}
#elif defined(__APPLE__)
char path[1024];
uint32_t size = sizeof(path);
if (_NSGetExecutablePath(path, &size) == 0) {
char resolved_path[PATH_MAX];
if (realpath(path, resolved_path) != nullptr) {
return std::string(resolved_path);
}
return std::string(path);
}
#elif defined(__linux__)
char path[PATH_MAX];
ssize_t count = readlink("/proc/self/exe", path, sizeof(path) - 1);
if (count != -1) {
path[count] = '\0';
return std::string(path);
}
#endif
return "";
}
// Windows
#ifdef _WIN32
static constexpr const char* WINDOWS_RUN_KEY =
"Software\\Microsoft\\Windows\\CurrentVersion\\Run";
static bool windows_enable(const std::string& appName,
const std::string& exePath) {
if (exePath.empty() || !std::filesystem::exists(exePath)) {
return false;
}
HKEY hKey = nullptr;
// Use KEY_WRITE to ensure we have write permission
LONG result =
RegOpenKeyExA(HKEY_CURRENT_USER, WINDOWS_RUN_KEY, 0, KEY_WRITE, &hKey);
if (result != ERROR_SUCCESS) {
return false;
}
std::string regValue = exePath;
if (!exePath.empty() && exePath.find(' ') != std::string::npos) {
if (exePath.front() != '"' || exePath.back() != '"') {
regValue = "\"" + exePath + "\"";
}
}
// Ensure we close the key even if RegSetValueExA fails
result = RegSetValueExA(hKey, appName.c_str(), 0, REG_SZ,
reinterpret_cast<const BYTE*>(regValue.c_str()),
static_cast<DWORD>(regValue.size() + 1));
RegCloseKey(hKey);
return result == ERROR_SUCCESS;
}
static bool windows_disable(const std::string& appName) {
HKEY hKey = nullptr;
LONG result =
RegOpenKeyExA(HKEY_CURRENT_USER, WINDOWS_RUN_KEY, 0, KEY_WRITE, &hKey);
if (result != ERROR_SUCCESS) {
return false;
}
result = RegDeleteValueA(hKey, appName.c_str());
RegCloseKey(hKey);
// Return true even if the value doesn't exist (already disabled)
return result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND;
}
static bool windows_exists(const std::string& appName) {
HKEY hKey = nullptr;
LONG result =
RegOpenKeyExA(HKEY_CURRENT_USER, WINDOWS_RUN_KEY, 0, KEY_READ, &hKey);
if (result != ERROR_SUCCESS) {
return false;
}
result = RegQueryValueExA(hKey, appName.c_str(), nullptr, nullptr, nullptr,
nullptr);
RegCloseKey(hKey);
return result == ERROR_SUCCESS;
}
#endif
// Linux
#if defined(__linux__)
static std::string linux_desktop_path(const std::string& appName) {
std::string home = get_home_dir();
if (home.empty()) {
return "";
}
return home + "/.config/autostart/" + appName + ".desktop";
}
static bool linux_enable(const std::string& appName,
const std::string& exePath) {
std::string home = get_home_dir();
if (home.empty()) {
return false;
}
std::filesystem::path dir =
std::filesystem::path(home) / ".config" / "autostart";
// Create directory if it doesn't exist
std::error_code ec;
std::filesystem::create_directories(dir, ec);
if (ec) {
return false;
}
std::string path = linux_desktop_path(appName);
if (path.empty()) {
return false;
}
std::ofstream file(path);
if (!file.is_open()) {
return false;
}
file << "[Desktop Entry]\n";
file << "Type=Application\n";
file << "Exec=" << exePath << "\n";
file << "Hidden=false\n";
file << "NoDisplay=false\n";
file << "X-GNOME-Autostart-enabled=true\n";
file << "Terminal=false\n";
file << "StartupNotify=false\n";
file << "Name=" << appName << "\n";
file.close();
return file.good();
}
static bool linux_disable(const std::string& appName) {
std::string path = linux_desktop_path(appName);
if (path.empty()) {
return false;
}
std::error_code ec;
return std::filesystem::remove(path, ec) && !ec;
}
static bool linux_exists(const std::string& appName) {
std::string path = linux_desktop_path(appName);
if (path.empty()) {
return false;
}
return file_exists(path);
}
#endif
// macOS
#ifdef __APPLE__
static std::string mac_plist_path(const std::string& appName) {
std::string home = get_home_dir();
if (home.empty()) {
return "";
}
return home + "/Library/LaunchAgents/" + appName + ".plist";
}
static bool mac_enable(const std::string& appName, const std::string& exePath) {
std::string path = mac_plist_path(appName);
if (path.empty()) {
return false;
}
// Ensure LaunchAgents directory exists
std::filesystem::path dir =
std::filesystem::path(get_home_dir()) / "Library" / "LaunchAgents";
std::error_code ec;
std::filesystem::create_directories(dir, ec);
if (ec) {
return false;
}
std::ofstream file(path);
if (!file.is_open()) {
return false;
}
file << R"(<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>)"
<< appName << R"(</string>
<key>ProgramArguments</key>
<array>
<string>)"
<< exePath << R"(</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>)";
file.close();
return file.good();
}
static bool mac_disable(const std::string& appName) {
std::string path = mac_plist_path(appName);
if (path.empty()) {
return false;
}
std::error_code ec;
return std::filesystem::remove(path, ec) && !ec;
}
static bool mac_exists(const std::string& appName) {
std::string path = mac_plist_path(appName);
if (path.empty()) {
return false;
}
return file_exists(path);
}
#endif
bool EnableAutostart(const std::string& appName) {
std::string exePath = GetExecutablePath();
if (exePath.empty()) {
return false;
}
#ifdef _WIN32
return windows_enable(appName, exePath);
#elif __APPLE__
return mac_enable(appName, exePath);
#else
return linux_enable(appName, exePath);
#endif
}
bool DisableAutostart(const std::string& appName) {
#ifdef _WIN32
return windows_disable(appName);
#elif __APPLE__
return mac_disable(appName);
#else
return linux_disable(appName);
#endif
}
bool IsAutostartEnabled(const std::string& appName) {
#ifdef _WIN32
return windows_exists(appName);
#elif __APPLE__
return mac_exists(appName);
#else
return linux_exists(appName);
#endif
}
} // namespace crossdesk
-21
View File
@@ -1,21 +0,0 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-11-18
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _AUTOSTART_H_
#define _AUTOSTART_H_
#include <string>
namespace crossdesk {
bool EnableAutostart(const std::string& appName);
bool DisableAutostart(const std::string& appName);
bool IsAutostartEnabled(const std::string& appName);
} // namespace crossdesk
#endif
+87
View File
@@ -0,0 +1,87 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-03-14
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _ANY_INVOCABLE_H_
#define _ANY_INVOCABLE_H_
#include <functional>
#include <iostream>
#include <memory>
#include <type_traits>
// 简化版的 AnyInvocable
template <typename Signature>
class AnyInvocable;
template <typename R, typename... Args>
class AnyInvocable<R(Args...)> {
public:
// 默认构造函数
AnyInvocable() = default;
AnyInvocable(std::nullptr_t) noexcept : callable_(nullptr) {}
// 构造函数:接受一个可以调用的对象(排除 nullptr)
template <typename Callable, typename = std::enable_if_t<!std::is_same_v<
std::decay_t<Callable>, std::nullptr_t>>>
AnyInvocable(Callable&& callable)
: callable_(std::make_unique<CallableWrapper<Callable>>(
std::forward<Callable>(callable))) {}
// 调用运算符(支持 void 和非 void 返回类型)
R operator()(Args... args) {
if (!callable_) {
throw std::bad_function_call();
}
if constexpr (std::is_void_v<R>) {
callable_->Invoke(std::forward<Args>(args)...);
} else {
return callable_->Invoke(std::forward<Args>(args)...);
}
}
// 移动构造函数
AnyInvocable(AnyInvocable&&) = default;
// 移动赋值运算符
AnyInvocable& operator=(AnyInvocable&&) = default;
// 判断是否有效
explicit operator bool() const { return static_cast<bool>(callable_); }
private:
// 抽象基类,允许不同类型的可调用对象
struct CallableBase {
virtual ~CallableBase() = default;
virtual R Invoke(Args&&... args) = 0;
};
// 模板派生类:实际存储 callable 对象
template <typename Callable>
struct CallableWrapper : public CallableBase {
CallableWrapper(Callable&& callable)
: callable_(std::forward<Callable>(callable)) {}
R Invoke(Args&&... args) override {
if constexpr (std::is_void_v<R>) {
callable_(std::forward<Args>(args)...);
} else {
return callable_(std::forward<Args>(args)...);
}
}
Callable callable_;
};
std::unique_ptr<CallableBase> callable_;
};
// 简单的包装函数
template <typename R, typename... Args>
AnyInvocable<R(Args...)> MakeMoveOnlyFunction(std::function<R(Args...)>&& f) {
return AnyInvocable<R(Args...)>(std::move(f));
}
#endif // _ANY_INVOCABLE_H_
+319
View File
@@ -0,0 +1,319 @@
/*
* Copyright 2015 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef API_ARRAY_VIEW_H_
#define API_ARRAY_VIEW_H_
#include <algorithm>
#include <array>
#include <cstddef>
#include <iterator>
#include <type_traits>
#include "rtc_base/type_traits.h"
namespace rtc {
// tl;dr: rtc::ArrayView is the same thing as gsl::span from the Guideline
// Support Library.
//
// Many functions read from or write to arrays. The obvious way to do this is
// to use two arguments, a pointer to the first element and an element count:
//
// bool Contains17(const int* arr, size_t size) {
// for (size_t i = 0; i < size; ++i) {
// if (arr[i] == 17)
// return true;
// }
// return false;
// }
//
// This is flexible, since it doesn't matter how the array is stored (C array,
// std::vector, rtc::Buffer, ...), but it's error-prone because the caller has
// to correctly specify the array length:
//
// Contains17(arr, arraysize(arr)); // C array
// Contains17(arr.data(), arr.size()); // std::vector
// Contains17(arr, size); // pointer + size
// ...
//
// It's also kind of messy to have two separate arguments for what is
// conceptually a single thing.
//
// Enter rtc::ArrayView<T>. It contains a T pointer (to an array it doesn't
// own) and a count, and supports the basic things you'd expect, such as
// indexing and iteration. It allows us to write our function like this:
//
// bool Contains17(rtc::ArrayView<const int> arr) {
// for (auto e : arr) {
// if (e == 17)
// return true;
// }
// return false;
// }
//
// And even better, because a bunch of things will implicitly convert to
// ArrayView, we can call it like this:
//
// Contains17(arr); // C array
// Contains17(arr); // std::vector
// Contains17(rtc::ArrayView<int>(arr, size)); // pointer + size
// Contains17(nullptr); // nullptr -> empty ArrayView
// ...
//
// ArrayView<T> stores both a pointer and a size, but you may also use
// ArrayView<T, N>, which has a size that's fixed at compile time (which means
// it only has to store the pointer).
//
// One important point is that ArrayView<T> and ArrayView<const T> are
// different types, which allow and don't allow mutation of the array elements,
// respectively. The implicit conversions work just like you'd hope, so that
// e.g. vector<int> will convert to either ArrayView<int> or ArrayView<const
// int>, but const vector<int> will convert only to ArrayView<const int>.
// (ArrayView itself can be the source type in such conversions, so
// ArrayView<int> will convert to ArrayView<const int>.)
//
// Note: ArrayView is tiny (just a pointer and a count if variable-sized, just
// a pointer if fix-sized) and trivially copyable, so it's probably cheaper to
// pass it by value than by const reference.
namespace array_view_internal {
// Magic constant for indicating that the size of an ArrayView is variable
// instead of fixed.
enum : std::ptrdiff_t { kArrayViewVarSize = -4711 };
// Base class for ArrayViews of fixed nonzero size.
template <typename T, std::ptrdiff_t Size>
class ArrayViewBase {
static_assert(Size > 0, "ArrayView size must be variable or non-negative");
public:
ArrayViewBase(T* data, size_t /* size */) : data_(data) {}
static constexpr size_t size() { return Size; }
static constexpr bool empty() { return false; }
T* data() const { return data_; }
protected:
static constexpr bool fixed_size() { return true; }
private:
T* data_;
};
// Specialized base class for ArrayViews of fixed zero size.
template <typename T>
class ArrayViewBase<T, 0> {
public:
explicit ArrayViewBase(T* /* data */, size_t /* size */) {}
static constexpr size_t size() { return 0; }
static constexpr bool empty() { return true; }
T* data() const { return nullptr; }
protected:
static constexpr bool fixed_size() { return true; }
};
// Specialized base class for ArrayViews of variable size.
template <typename T>
class ArrayViewBase<T, array_view_internal::kArrayViewVarSize> {
public:
ArrayViewBase(T* data, size_t size)
: data_(size == 0 ? nullptr : data), size_(size) {}
size_t size() const { return size_; }
bool empty() const { return size_ == 0; }
T* data() const { return data_; }
protected:
static constexpr bool fixed_size() { return false; }
private:
T* data_;
size_t size_;
};
} // namespace array_view_internal
template <typename T,
std::ptrdiff_t Size = array_view_internal::kArrayViewVarSize>
class ArrayView final : public array_view_internal::ArrayViewBase<T, Size> {
public:
using value_type = T;
using reference = value_type&;
using const_reference = const value_type&;
using pointer = value_type*;
using const_pointer = const value_type*;
using const_iterator = const T*;
// Construct an ArrayView from a pointer and a length.
template <typename U>
ArrayView(U* data, size_t size)
: array_view_internal::ArrayViewBase<T, Size>::ArrayViewBase(data, size) {
}
// Construct an empty ArrayView. Note that fixed-size ArrayViews of size > 0
// cannot be empty.
ArrayView() : ArrayView(nullptr, 0) {}
ArrayView(std::nullptr_t) // NOLINT
: ArrayView() {}
ArrayView(std::nullptr_t, size_t size)
: ArrayView(static_cast<T*>(nullptr), size) {
static_assert(Size == 0 || Size == array_view_internal::kArrayViewVarSize,
"");
}
// Construct an ArrayView from a C-style array.
template <typename U, size_t N>
ArrayView(U (&array)[N]) // NOLINT
: ArrayView(array, N) {
static_assert(Size == N || Size == array_view_internal::kArrayViewVarSize,
"Array size must match ArrayView size");
}
// (Only if size is fixed.) Construct a fixed size ArrayView<T, N> from a
// non-const std::array instance. For an ArrayView with variable size, the
// used ctor is ArrayView(U& u) instead.
template <typename U, size_t N,
typename std::enable_if<
Size == static_cast<std::ptrdiff_t>(N)>::type* = nullptr>
ArrayView(std::array<U, N>& u) // NOLINT
: ArrayView(u.data(), u.size()) {}
// (Only if size is fixed.) Construct a fixed size ArrayView<T, N> where T is
// const from a const(expr) std::array instance. For an ArrayView with
// variable size, the used ctor is ArrayView(U& u) instead.
template <typename U, size_t N,
typename std::enable_if<
Size == static_cast<std::ptrdiff_t>(N)>::type* = nullptr>
ArrayView(const std::array<U, N>& u) // NOLINT
: ArrayView(u.data(), u.size()) {}
// (Only if size is fixed.) Construct an ArrayView from any type U that has a
// static constexpr size() method whose return value is equal to Size, and a
// data() method whose return value converts implicitly to T*. In particular,
// this means we allow conversion from ArrayView<T, N> to ArrayView<const T,
// N>, but not the other way around. We also don't allow conversion from
// ArrayView<T> to ArrayView<T, N>, or from ArrayView<T, M> to ArrayView<T,
// N> when M != N.
template <typename U, typename std::enable_if<
Size != array_view_internal::kArrayViewVarSize &&
HasDataAndSize<U, T>::value>::type* = nullptr>
ArrayView(U& u) // NOLINT
: ArrayView(u.data(), u.size()) {
static_assert(U::size() == Size, "Sizes must match exactly");
}
template <typename U, typename std::enable_if<
Size != array_view_internal::kArrayViewVarSize &&
HasDataAndSize<U, T>::value>::type* = nullptr>
ArrayView(const U& u) // NOLINT(runtime/explicit)
: ArrayView(u.data(), u.size()) {
static_assert(U::size() == Size, "Sizes must match exactly");
}
// (Only if size is variable.) Construct an ArrayView from any type U that
// has a size() method whose return value converts implicitly to size_t, and
// a data() method whose return value converts implicitly to T*. In
// particular, this means we allow conversion from ArrayView<T> to
// ArrayView<const T>, but not the other way around. Other allowed
// conversions include
// ArrayView<T, N> to ArrayView<T> or ArrayView<const T>,
// std::vector<T> to ArrayView<T> or ArrayView<const T>,
// const std::vector<T> to ArrayView<const T>,
// rtc::Buffer to ArrayView<uint8_t> or ArrayView<const uint8_t>, and
// const rtc::Buffer to ArrayView<const uint8_t>.
template <typename U, typename std::enable_if<
Size == array_view_internal::kArrayViewVarSize &&
HasDataAndSize<U, T>::value>::type* = nullptr>
ArrayView(U& u) // NOLINT
: ArrayView(u.data(), u.size()) {}
template <typename U, typename std::enable_if<
Size == array_view_internal::kArrayViewVarSize &&
HasDataAndSize<U, T>::value>::type* = nullptr>
ArrayView(const U& u) // NOLINT(runtime/explicit)
: ArrayView(u.data(), u.size()) {}
// Indexing and iteration. These allow mutation even if the ArrayView is
// const, because the ArrayView doesn't own the array. (To prevent mutation,
// use a const element type.)
T& operator[](size_t idx) const { return this->data()[idx]; }
T* begin() const { return this->data(); }
T* end() const { return this->data() + this->size(); }
const T* cbegin() const { return this->data(); }
const T* cend() const { return this->data() + this->size(); }
std::reverse_iterator<T*> rbegin() const {
return std::make_reverse_iterator(end());
}
std::reverse_iterator<T*> rend() const {
return std::make_reverse_iterator(begin());
}
std::reverse_iterator<const T*> crbegin() const {
return std::make_reverse_iterator(cend());
}
std::reverse_iterator<const T*> crend() const {
return std::make_reverse_iterator(cbegin());
}
ArrayView<T> subview(size_t offset, size_t size) const {
return offset < this->size()
? ArrayView<T>(this->data() + offset,
std::min(size, this->size() - offset))
: ArrayView<T>();
}
ArrayView<T> subview(size_t offset) const {
return subview(offset, this->size());
}
};
// Comparing two ArrayViews compares their (pointer,size) pairs; it does *not*
// dereference the pointers.
template <typename T, std::ptrdiff_t Size1, std::ptrdiff_t Size2>
bool operator==(const ArrayView<T, Size1>& a, const ArrayView<T, Size2>& b) {
return a.data() == b.data() && a.size() == b.size();
}
template <typename T, std::ptrdiff_t Size1, std::ptrdiff_t Size2>
bool operator!=(const ArrayView<T, Size1>& a, const ArrayView<T, Size2>& b) {
return !(a == b);
}
// Variable-size ArrayViews are the size of two pointers; fixed-size ArrayViews
// are the size of one pointer. (And as a special case, fixed-size ArrayViews
// of size 0 require no storage.)
static_assert(sizeof(ArrayView<int>) == 2 * sizeof(int*), "");
static_assert(sizeof(ArrayView<int, 17>) == sizeof(int*), "");
static_assert(std::is_empty<ArrayView<int, 0>>::value, "");
template <typename T>
inline ArrayView<T> MakeArrayView(T* data, size_t size) {
return ArrayView<T>(data, size);
}
// Only for primitive types that have the same size and aligment.
// Allow reinterpret cast of the array view to another primitive type of the
// same size.
// Template arguments order is (U, T, Size) to allow deduction of the template
// arguments in client calls: reinterpret_array_view<target_type>(array_view).
template <typename U, typename T, std::ptrdiff_t Size>
inline ArrayView<U, Size> reinterpret_array_view(ArrayView<T, Size> view) {
static_assert(sizeof(U) == sizeof(T) && alignof(U) == alignof(T),
"ArrayView reinterpret_cast is only supported for casting "
"between views that represent the same chunk of memory.");
static_assert(
std::is_fundamental<T>::value && std::is_fundamental<U>::value,
"ArrayView reinterpret_cast is only supported for casting between "
"fundamental types.");
return ArrayView<U, Size>(reinterpret_cast<U*>(view.data()), view.size());
}
} // namespace rtc
#endif // API_ARRAY_VIEW_H_
+93
View File
@@ -0,0 +1,93 @@
/*
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "api/clock/clock.h"
#include "rtc_base/time_utils.h"
namespace webrtc {
namespace {
int64_t NtpOffsetUsCalledOnce() {
constexpr int64_t kNtpJan1970Sec = 2208988800;
int64_t clock_time = rtc::TimeMicros();
int64_t utc_time = rtc::TimeUTCMicros();
return utc_time - clock_time + kNtpJan1970Sec * rtc::kNumMicrosecsPerSec;
}
NtpTime TimeMicrosToNtp(int64_t time_us) {
static int64_t ntp_offset_us = NtpOffsetUsCalledOnce();
int64_t time_ntp_us = time_us + ntp_offset_us;
// Convert seconds to uint32 through uint64 for a well-defined cast.
// A wrap around, which will happen in 2036, is expected for NTP time.
uint32_t ntp_seconds =
static_cast<uint64_t>(time_ntp_us / rtc::kNumMicrosecsPerSec);
// Scale fractions of the second to NTP resolution.
constexpr int64_t kNtpFractionsInSecond = 1LL << 32;
int64_t us_fractions = time_ntp_us % rtc::kNumMicrosecsPerSec;
uint32_t ntp_fractions =
us_fractions * kNtpFractionsInSecond / rtc::kNumMicrosecsPerSec;
return NtpTime(ntp_seconds, ntp_fractions);
}
} // namespace
class WebrtcClock : public Clock {
public:
WebrtcClock(std::shared_ptr<SystemClock> system_clock)
: system_clock_(system_clock) {}
WebrtcClock() = delete;
Timestamp CurrentTime() override {
return Timestamp::Micros(system_clock_->CurrentTimeUs());
}
NtpTime ConvertTimestampToNtpTime(Timestamp timestamp) override {
int64_t time_us = timestamp.us();
constexpr int64_t kNtpJan1970Sec = 2208988800;
int64_t clock_time = system_clock_->CurrentTimeUs();
int64_t utc_time = system_clock_->CurrentUtcTimeUs();
static int64_t ntp_offset_us =
utc_time - clock_time + kNtpJan1970Sec * rtc::kNumMicrosecsPerSec;
int64_t time_ntp_us = time_us + ntp_offset_us;
// Convert seconds to uint32 through uint64 for a well-defined cast.
// A wrap around, which will happen in 2036, is expected for NTP time.
uint32_t ntp_seconds =
static_cast<uint64_t>(time_ntp_us / rtc::kNumMicrosecsPerSec);
// Scale fractions of the second to NTP resolution.
constexpr int64_t kNtpFractionsInSecond = 1LL << 32;
int64_t us_fractions = time_ntp_us % rtc::kNumMicrosecsPerSec;
uint32_t ntp_fractions =
us_fractions * kNtpFractionsInSecond / rtc::kNumMicrosecsPerSec;
return NtpTime(ntp_seconds, ntp_fractions);
}
private:
std::shared_ptr<SystemClock> system_clock_;
};
Clock* Clock::GetWebrtcClock(std::shared_ptr<SystemClock> system_clock) {
static Clock* const clock = new WebrtcClock(system_clock);
return clock;
}
std::shared_ptr<Clock> Clock::GetWebrtcClockShared(
std::shared_ptr<SystemClock> system_clock) {
return std::make_shared<WebrtcClock>(system_clock);
}
} // namespace webrtc
+76
View File
@@ -0,0 +1,76 @@
/*
* Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef SYSTEM_WRAPPERS_INCLUDE_CLOCK_H_
#define SYSTEM_WRAPPERS_INCLUDE_CLOCK_H_
#include <stdint.h>
#include <atomic>
#include <memory>
#include "api/ntp/ntp_time.h"
#include "api/units/timestamp.h"
#include "clock/system_clock.h"
namespace webrtc {
// January 1970, in NTP seconds.
const uint32_t kNtpJan1970 = 2208988800UL;
// Magic NTP fractional unit.
const double kMagicNtpFractionalUnit = 4.294967296E+9;
// A clock interface that allows reading of absolute and relative timestamps.
class Clock {
public:
virtual ~Clock() {}
// Return a timestamp relative to an unspecified epoch.
virtual Timestamp CurrentTime() = 0;
int64_t TimeInMilliseconds() { return CurrentTime().ms(); }
int64_t TimeInMicroseconds() { return CurrentTime().us(); }
// Retrieve an NTP absolute timestamp (with an epoch of Jan 1, 1900).
NtpTime CurrentNtpTime() { return ConvertTimestampToNtpTime(CurrentTime()); }
int64_t CurrentNtpInMilliseconds() { return CurrentNtpTime().ToMs(); }
// Converts between a relative timestamp returned by this clock, to NTP time.
virtual NtpTime ConvertTimestampToNtpTime(Timestamp timestamp) = 0;
int64_t ConvertTimestampToNtpTimeInMilliseconds(int64_t timestamp_ms) {
return ConvertTimestampToNtpTime(Timestamp::Millis(timestamp_ms)).ToMs();
}
// Converts NtpTime to a Timestamp with UTC epoch.
// A `Minus Infinity` Timestamp is returned if the NtpTime is invalid.
static Timestamp NtpToUtc(NtpTime ntp_time) {
if (!ntp_time.Valid()) {
return Timestamp::MinusInfinity();
}
// Seconds since UTC epoch.
int64_t time = ntp_time.seconds() - kNtpJan1970;
// Microseconds since UTC epoch (not including NTP fraction)
time = time * 1'000'000;
// Fractions part of the NTP time, in microseconds.
int64_t time_fraction =
DivideRoundToNearest(int64_t{ntp_time.fractions()} * 1'000'000,
NtpTime::kFractionsPerSecond);
return Timestamp::Micros(time + time_fraction);
}
// Returns an instance of the real-time system clock implementation.
static Clock* GetWebrtcClock(std::shared_ptr<SystemClock> system_clock);
static std::shared_ptr<Clock> GetWebrtcClockShared(
std::shared_ptr<SystemClock> system_clock);
};
} // namespace webrtc
#endif // SYSTEM_WRAPPERS_INCLUDE_CLOCK_H_
+127
View File
@@ -0,0 +1,127 @@
/*
* Copyright 2016 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef API_FUNCTION_VIEW_H_
#define API_FUNCTION_VIEW_H_
#include <cstddef>
#include <type_traits>
#include <utility>
// Just like std::function, FunctionView will wrap any callable and hide its
// actual type, exposing only its signature. But unlike std::function,
// FunctionView doesn't own its callable---it just points to it. Thus, it's a
// good choice mainly as a function argument when the callable argument will
// not be called again once the function has returned.
//
// Its constructors are implicit, so that callers won't have to convert lambdas
// and other callables to FunctionView<Blah(Blah, Blah)> explicitly. This is
// safe because FunctionView is only a reference to the real callable.
//
// Example use:
//
// void SomeFunction(rtc::FunctionView<int(int)> index_transform);
// ...
// SomeFunction([](int i) { return 2 * i + 1; });
//
// Note: FunctionView is tiny (essentially just two pointers) and trivially
// copyable, so it's probably cheaper to pass it by value than by const
// reference.
namespace rtc {
template <typename T>
class FunctionView; // Undefined.
template <typename RetT, typename... ArgT>
class FunctionView<RetT(ArgT...)> final {
public:
// Constructor for lambdas and other callables; it accepts every type of
// argument except those noted in its enable_if call.
template <
typename F,
typename std::enable_if<
// Not for function pointers; we have another constructor for that
// below.
!std::is_function<typename std::remove_pointer<
typename std::remove_reference<F>::type>::type>::value &&
// Not for nullptr; we have another constructor for that below.
!std::is_same<std::nullptr_t,
typename std::remove_cv<F>::type>::value &&
// Not for FunctionView objects; we have another constructor for that
// (the implicitly declared copy constructor).
!std::is_same<FunctionView,
typename std::remove_cv<typename std::remove_reference<
F>::type>::type>::value>::type* = nullptr>
FunctionView(F&& f)
: call_(CallVoidPtr<typename std::remove_reference<F>::type>) {
f_.void_ptr = &f;
}
// Constructor that accepts function pointers. If the argument is null, the
// result is an empty FunctionView.
template <
typename F,
typename std::enable_if<std::is_function<typename std::remove_pointer<
typename std::remove_reference<F>::type>::type>::value>::type* =
nullptr>
FunctionView(F&& f)
: call_(f ? CallFunPtr<typename std::remove_pointer<F>::type> : nullptr) {
f_.fun_ptr = reinterpret_cast<void (*)()>(f);
}
// Constructor that accepts nullptr. It creates an empty FunctionView.
template <typename F, typename std::enable_if<std::is_same<
std::nullptr_t, typename std::remove_cv<F>::type>::
value>::type* = nullptr>
FunctionView(F&& /* f */) : call_(nullptr) {}
// Default constructor. Creates an empty FunctionView.
FunctionView() : call_(nullptr) {}
RetT operator()(ArgT... args) const {
return call_(f_, std::forward<ArgT>(args)...);
}
// Returns true if we have a function, false if we don't (i.e., we're null).
explicit operator bool() const { return !!call_; }
private:
union VoidUnion {
void* void_ptr;
void (*fun_ptr)();
};
template <typename F>
static RetT CallVoidPtr(VoidUnion vu, ArgT... args) {
return (*static_cast<F*>(vu.void_ptr))(std::forward<ArgT>(args)...);
}
template <typename F>
static RetT CallFunPtr(VoidUnion vu, ArgT... args) {
return (reinterpret_cast<typename std::add_pointer<F>::type>(vu.fun_ptr))(
std::forward<ArgT>(args)...);
}
// A pointer to the callable thing, with type information erased. It's a
// union because we have to use separate types depending on if the callable
// thing is a function pointer or something else.
VoidUnion f_;
// Pointer to a dispatch function that knows the type of the callable thing
// that's stored in f_, and how to call it. A FunctionView object is empty
// (null) iff call_ is null.
RetT (*call_)(VoidUnion, ArgT...);
};
} // namespace rtc
#endif // API_FUNCTION_VIEW_H_
+42
View File
@@ -0,0 +1,42 @@
/*
* Copyright 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef API_MEDIA_TYPES_H_
#define API_MEDIA_TYPES_H_
#include <string>
// The cricket and webrtc have separate definitions for what a media type is.
// They're not compatible. Watch out for this.
namespace cricket {
enum MediaType {
MEDIA_TYPE_AUDIO,
MEDIA_TYPE_VIDEO,
MEDIA_TYPE_DATA,
MEDIA_TYPE_UNSUPPORTED
};
extern const char kMediaTypeAudio[];
extern const char kMediaTypeVideo[];
extern const char kMediaTypeData[];
std::string MediaTypeToString(MediaType type);
} // namespace cricket
namespace webrtc {
enum class MediaType { ANY, AUDIO, VIDEO, DATA };
} // namespace webrtc
#endif // API_MEDIA_TYPES_H_
+51
View File
@@ -0,0 +1,51 @@
/*
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef API_NETWORK_STATE_PREDICTOR_H_
#define API_NETWORK_STATE_PREDICTOR_H_
#include <cstdint>
#include <memory>
#include "api/transport/bandwidth_usage.h"
namespace webrtc {
// TODO(yinwa): work in progress. API in class NetworkStatePredictor should not
// be used by other users until this comment is removed.
// NetworkStatePredictor predict network state based on current network metrics.
// Usage:
// Setup by calling Initialize.
// For each update, call Update. Update returns network state
// prediction.
class NetworkStatePredictor {
public:
virtual ~NetworkStatePredictor() {}
// Returns current network state prediction.
// Inputs: send_time_ms - packet send time.
// arrival_time_ms - packet arrival time.
// network_state - computed network state.
virtual BandwidthUsage Update(int64_t send_time_ms,
int64_t arrival_time_ms,
BandwidthUsage network_state) = 0;
};
class NetworkStatePredictorFactoryInterface {
public:
virtual std::unique_ptr<NetworkStatePredictor>
CreateNetworkStatePredictor() = 0;
virtual ~NetworkStatePredictorFactoryInterface() = default;
};
} // namespace webrtc
#endif // API_NETWORK_STATE_PREDICTOR_H_
+136
View File
@@ -0,0 +1,136 @@
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef SYSTEM_WRAPPERS_INCLUDE_NTP_TIME_H_
#define SYSTEM_WRAPPERS_INCLUDE_NTP_TIME_H_
#include <cmath>
#include <cstdint>
#include <limits>
#include "rtc_base/numerics/safe_conversions.h"
namespace webrtc {
class NtpTime {
public:
static constexpr uint64_t kFractionsPerSecond = 0x100000000;
NtpTime() : value_(0) {}
explicit NtpTime(uint64_t value) : value_(value) {}
NtpTime(uint32_t seconds, uint32_t fractions)
: value_(seconds * kFractionsPerSecond + fractions) {}
NtpTime(const NtpTime&) = default;
NtpTime& operator=(const NtpTime&) = default;
explicit operator uint64_t() const { return value_; }
void Set(uint32_t seconds, uint32_t fractions) {
value_ = seconds * kFractionsPerSecond + fractions;
}
void Reset() { value_ = 0; }
int64_t ToMs() const {
static constexpr double kNtpFracPerMs = 4.294967296E6; // 2^32 / 1000.
const double frac_ms = static_cast<double>(fractions()) / kNtpFracPerMs;
return 1000 * static_cast<int64_t>(seconds()) +
static_cast<int64_t>(frac_ms + 0.5);
}
// NTP standard (RFC1305, section 3.1) explicitly state value 0 is invalid.
bool Valid() const { return value_ != 0; }
uint32_t seconds() const {
return rtc::dchecked_cast<uint32_t>(value_ / kFractionsPerSecond);
}
uint32_t fractions() const {
return rtc::dchecked_cast<uint32_t>(value_ % kFractionsPerSecond);
}
private:
uint64_t value_;
};
inline bool operator==(const NtpTime& n1, const NtpTime& n2) {
return static_cast<uint64_t>(n1) == static_cast<uint64_t>(n2);
}
inline bool operator!=(const NtpTime& n1, const NtpTime& n2) {
return !(n1 == n2);
}
// Converts `int64_t` milliseconds to Q32.32-formatted fixed-point seconds.
// Performs clamping if the result overflows or underflows.
inline int64_t Int64MsToQ32x32(int64_t milliseconds) {
// TODO(bugs.webrtc.org/10893): Change to use `rtc::saturated_cast` once the
// bug has been fixed.
double result =
std::round(milliseconds * (NtpTime::kFractionsPerSecond / 1000.0));
// Explicitly cast values to double to avoid implicit conversion warnings
// The conversion of the std::numeric_limits<int64_t>::max() triggers
// -Wimplicit-int-float-conversion warning in clang 10.0.0 without explicit
// cast
if (result <= static_cast<double>(std::numeric_limits<int64_t>::min())) {
return std::numeric_limits<int64_t>::min();
}
if (result >= static_cast<double>(std::numeric_limits<int64_t>::max())) {
return std::numeric_limits<int64_t>::max();
}
return rtc::dchecked_cast<int64_t>(result);
}
// Converts `int64_t` milliseconds to UQ32.32-formatted fixed-point seconds.
// Performs clamping if the result overflows or underflows.
inline uint64_t Int64MsToUQ32x32(int64_t milliseconds) {
// TODO(bugs.webrtc.org/10893): Change to use `rtc::saturated_cast` once the
// bug has been fixed.
double result =
std::round(milliseconds * (NtpTime::kFractionsPerSecond / 1000.0));
// Explicitly cast values to double to avoid implicit conversion warnings
// The conversion of the std::numeric_limits<int64_t>::max() triggers
// -Wimplicit-int-float-conversion warning in clang 10.0.0 without explicit
// cast
if (result <= static_cast<double>(std::numeric_limits<uint64_t>::min())) {
return std::numeric_limits<uint64_t>::min();
}
if (result >= static_cast<double>(std::numeric_limits<uint64_t>::max())) {
return std::numeric_limits<uint64_t>::max();
}
return rtc::dchecked_cast<uint64_t>(result);
}
// Converts Q32.32-formatted fixed-point seconds to `int64_t` milliseconds.
inline int64_t Q32x32ToInt64Ms(int64_t q32x32) {
return rtc::dchecked_cast<int64_t>(
std::round(q32x32 * (1000.0 / NtpTime::kFractionsPerSecond)));
}
// Converts UQ32.32-formatted fixed-point seconds to `int64_t` milliseconds.
inline int64_t UQ32x32ToInt64Ms(uint64_t q32x32) {
return rtc::dchecked_cast<int64_t>(
std::round(q32x32 * (1000.0 / NtpTime::kFractionsPerSecond)));
}
// Converts UQ32.32-formatted fixed-point seconds to `int64_t` microseconds.
inline int64_t UQ32x32ToInt64Us(uint64_t q32x32) {
return rtc::dchecked_cast<int64_t>(
std::round(q32x32 * (1'000'000.0 / NtpTime::kFractionsPerSecond)));
}
// Converts Q32.32-formatted fixed-point seconds to `int64_t` microseconds.
inline int64_t Q32x32ToInt64Us(int64_t q32x32) {
return rtc::dchecked_cast<int64_t>(
std::round(q32x32 * (1'000'000.0 / NtpTime::kFractionsPerSecond)));
}
} // namespace webrtc
#endif // SYSTEM_WRAPPERS_INCLUDE_NTP_TIME_H_
+59
View File
@@ -0,0 +1,59 @@
/*
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "api/ntp/ntp_time_util.h"
#include <algorithm>
#include <cstdint>
#include "api/units/time_delta.h"
#include "rtc_base/numerics/divide_round.h"
#include "rtc_base/time_utils.h"
namespace webrtc {
uint32_t SaturatedToCompactNtp(TimeDelta delta) {
constexpr uint32_t kMaxCompactNtp = 0xFFFFFFFF;
constexpr int kCompactNtpInSecond = 0x10000;
if (delta <= TimeDelta::Zero()) return 0;
if (delta.us() >=
kMaxCompactNtp * rtc::kNumMicrosecsPerSec / kCompactNtpInSecond)
return kMaxCompactNtp;
// To convert to compact ntp need to divide by 1e6 to get seconds,
// then multiply by 0x10000 to get the final result.
// To avoid float operations, multiplication and division swapped.
return DivideRoundToNearest(delta.us() * kCompactNtpInSecond,
rtc::kNumMicrosecsPerSec);
}
TimeDelta CompactNtpIntervalToTimeDelta(uint32_t compact_ntp_interval) {
// Convert to 64bit value to avoid multiplication overflow.
int64_t value = int64_t{compact_ntp_interval};
if (compact_ntp_interval > 0x8000'0000) {
value -= (int64_t{1} << 32);
}
// To convert to TimeDelta need to divide by 2^16 to get seconds,
// then multiply by 1'000'000 to get microseconds. To avoid float operations,
// multiplication and division are swapped.
int64_t us = DivideRoundToNearest(value * rtc::kNumMicrosecsPerSec, 1 << 16);
return TimeDelta::Micros(us);
}
TimeDelta CompactNtpRttToTimeDelta(uint32_t compact_ntp_interval) {
static constexpr TimeDelta kMinRtt = TimeDelta::Millis(1);
// Interval to convert expected to be positive, e.g. RTT or delay.
// Because interval can be derived from non-monotonic ntp clock,
// it might become negative that is indistinguishable from very large values.
// Since very large RTT/delay is less likely than non-monotonic ntp clock,
// such value is considered negative and converted to minimum value of 1ms.
// Small RTT value is considered too good to be true and increased to 1ms.
return std::max(CompactNtpIntervalToTimeDelta(compact_ntp_interval), kMinRtt);
}
} // namespace webrtc
+61
View File
@@ -0,0 +1,61 @@
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_RTP_RTCP_SOURCE_NTP_TIME_UTIL_H_
#define MODULES_RTP_RTCP_SOURCE_NTP_TIME_UTIL_H_
#include <stdint.h>
#include "api/ntp/ntp_time.h"
#include "api/units/time_delta.h"
#include "rtc_base/numerics/safe_conversions.h"
namespace webrtc {
// Helper function for compact ntp representation:
// RFC 3550, Section 4. Time Format.
// Wallclock time is represented using the timestamp format of
// the Network Time Protocol (NTP).
// ...
// In some fields where a more compact representation is
// appropriate, only the middle 32 bits are used; that is, the low 16
// bits of the integer part and the high 16 bits of the fractional part.
inline uint32_t CompactNtp(NtpTime ntp) {
return (ntp.seconds() << 16) | (ntp.fractions() >> 16);
}
// Converts interval to compact ntp (1/2^16 seconds) resolution.
// Negative values converted to 0, Overlarge values converted to max uint32_t.
uint32_t SaturatedToCompactNtp(TimeDelta delta);
// Convert interval to the NTP time resolution (1/2^32 seconds ~= 0.2 ns).
// For deltas with absolute value larger than 35 minutes result is unspecified.
inline constexpr int64_t ToNtpUnits(TimeDelta delta) {
// For better precision `delta` is taken with best TimeDelta precision (us),
// then multiplaction and conversion to seconds are swapped to avoid float
// arithmetic.
// 2^31 us ~= 35.8 minutes.
return (rtc::saturated_cast<int32_t>(delta.us()) * (int64_t{1} << 32)) /
1'000'000;
}
// Converts interval from compact ntp (1/2^16 seconds) resolution to TimeDelta.
// This interval can be up to ~9.1 hours (2^15 seconds).
// Values close to 2^16 seconds are considered negative.
TimeDelta CompactNtpIntervalToTimeDelta(uint32_t compact_ntp_interval);
// Converts interval from compact ntp (1/2^16 seconds) resolution to TimeDelta.
// This interval can be up to ~9.1 hours (2^15 seconds).
// Values close to 2^16 seconds are considered negative and are converted to
// minimum value of 1ms.
TimeDelta CompactNtpRttToTimeDelta(uint32_t compact_ntp_interval);
} // namespace webrtc
#endif // MODULES_RTP_RTCP_SOURCE_NTP_TIME_UTIL_H_
+67
View File
@@ -0,0 +1,67 @@
/*
* Copyright 2011 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef API_REF_COUNT_H_
#define API_REF_COUNT_H_
namespace webrtc {
// Refcounted objects should implement the following informal interface:
//
// void AddRef() const ;
// RefCountReleaseStatus Release() const;
//
// You may access members of a reference-counted object, including the AddRef()
// and Release() methods, only if you already own a reference to it, or if
// you're borrowing someone else's reference. (A newly created object is a
// special case: the reference count is zero on construction, and the code that
// creates the object should immediately call AddRef(), bringing the reference
// count from zero to one, e.g., by constructing an rtc::scoped_refptr).
//
// AddRef() creates a new reference to the object.
//
// Release() releases a reference to the object; the caller now has one less
// reference than before the call. Returns kDroppedLastRef if the number of
// references dropped to zero because of this (in which case the object destroys
// itself). Otherwise, returns kOtherRefsRemained, to signal that at the precise
// time the caller's reference was dropped, other references still remained (but
// if other threads own references, this may of course have changed by the time
// Release() returns).
//
// The caller of Release() must treat it in the same way as a delete operation:
// Regardless of the return value from Release(), the caller mustn't access the
// object. The object might still be alive, due to references held by other
// users of the object, but the object can go away at any time, e.g., as the
// result of another thread calling Release().
//
// Calling AddRef() and Release() manually is discouraged. It's recommended to
// use rtc::scoped_refptr to manage all pointers to reference counted objects.
// Note that rtc::scoped_refptr depends on compile-time duck-typing; formally
// implementing the below RefCountInterface is not required.
enum class RefCountReleaseStatus { kDroppedLastRef, kOtherRefsRemained };
// Interfaces where refcounting is part of the public api should
// inherit this abstract interface. The implementation of these
// methods is usually provided by the RefCountedObject template class,
// applied as a leaf in the inheritance tree.
class RefCountInterface {
public:
virtual void AddRef() const = 0;
virtual RefCountReleaseStatus Release() const = 0;
// Non-public destructor, because Release() has exclusive responsibility for
// destroying the object.
protected:
virtual ~RefCountInterface() {}
};
} // namespace webrtc
#endif // API_REF_COUNT_H_
+107
View File
@@ -0,0 +1,107 @@
/*
* Copyright 2017 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef API_REF_COUNTED_BASE_H_
#define API_REF_COUNTED_BASE_H_
#include <type_traits>
#include "api/ref_count.h"
#include "rtc_base/ref_counter.h"
namespace webrtc {
class RefCountedBase {
public:
RefCountedBase() = default;
RefCountedBase(const RefCountedBase&) = delete;
RefCountedBase& operator=(const RefCountedBase&) = delete;
void AddRef() const { ref_count_.IncRef(); }
RefCountReleaseStatus Release() const {
const auto status = ref_count_.DecRef();
if (status == RefCountReleaseStatus::kDroppedLastRef) {
delete this;
}
return status;
}
protected:
// Provided for internal webrtc subclasses for corner cases where it's
// necessary to know whether or not a reference is exclusively held.
bool HasOneRef() const { return ref_count_.HasOneRef(); }
virtual ~RefCountedBase() = default;
private:
mutable webrtc::webrtc_impl::RefCounter ref_count_{0};
};
// Template based version of `RefCountedBase` for simple implementations that do
// not need (or want) destruction via virtual destructor or the overhead of a
// vtable.
//
// To use:
// struct MyInt : public rtc::RefCountedNonVirtual<MyInt> {
// int foo_ = 0;
// };
//
// rtc::scoped_refptr<MyInt> my_int(new MyInt());
//
// sizeof(MyInt) on a 32 bit system would then be 8, int + refcount and no
// vtable generated.
template <typename T>
class RefCountedNonVirtual {
public:
RefCountedNonVirtual() = default;
RefCountedNonVirtual(const RefCountedNonVirtual&) = delete;
RefCountedNonVirtual& operator=(const RefCountedNonVirtual&) = delete;
void AddRef() const { ref_count_.IncRef(); }
RefCountReleaseStatus Release() const {
// If you run into this assert, T has virtual methods. There are two
// options:
// 1) The class doesn't actually need virtual methods, the type is complete
// so the virtual attribute(s) can be removed.
// 2) The virtual methods are a part of the design of the class. In this
// case you can consider using `RefCountedBase` instead or alternatively
// use `rtc::RefCountedObject`.
static_assert(!std::is_polymorphic<T>::value,
"T has virtual methods. RefCountedBase is a better fit.");
const auto status = ref_count_.DecRef();
if (status == RefCountReleaseStatus::kDroppedLastRef) {
delete static_cast<const T*>(this);
}
return status;
}
protected:
// Provided for internal webrtc subclasses for corner cases where it's
// necessary to know whether or not a reference is exclusively held.
bool HasOneRef() const { return ref_count_.HasOneRef(); }
~RefCountedNonVirtual() = default;
private:
mutable webrtc::webrtc_impl::RefCounter ref_count_{0};
};
} // namespace webrtc
// Backwards compatibe aliases.
// TODO: https://issues.webrtc.org/42225969 - deprecate and remove.
namespace rtc {
using RefCountedBase = webrtc::RefCountedBase;
template <typename T>
using RefCountedNonVirtual = webrtc::RefCountedNonVirtual<T>;
} // namespace rtc
#endif // API_REF_COUNTED_BASE_H_
+129
View File
@@ -0,0 +1,129 @@
/*
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef _RTP_RTCP_TYPEDEF_H_
#define _RTP_RTCP_TYPEDEF_H_
#include <stddef.h>
#include <array>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <numeric>
#include <optional>
#include <utility>
#include <vector>
#include "api/array_view.h"
#include "api/transport/network_types.h"
#include "api/units/data_rate.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#define RTCP_CNAME_SIZE 256 // RFC 3550 page 44, including null termination
#define IP_PACKET_SIZE 1500 // we assume ethernet
namespace webrtc {
const int kVideoPayloadTypeFrequency = 90000;
// TODO(bugs.webrtc.org/6458): Remove this when all the depending projects are
// updated to correctly set rtp rate for RtcpSender.
const int kBogusRtpRateForAudioRtcp = 8000;
// Minimum RTP header size in bytes.
const uint8_t kRtpHeaderSize = 12;
// This enum must not have any gaps, i.e., all integers between
// kRtpExtensionNone and kRtpExtensionNumberOfExtensions must be valid enum
// entries.
enum RTPExtensionType : int {
kRtpExtensionNone,
kRtpExtensionTransmissionTimeOffset,
kRtpExtensionAudioLevel,
kRtpExtensionCsrcAudioLevel,
kRtpExtensionInbandComfortNoise,
kRtpExtensionAbsoluteSendTime,
kRtpExtensionAbsoluteCaptureTime,
kRtpExtensionVideoRotation,
kRtpExtensionTransportSequenceNumber,
kRtpExtensionTransportSequenceNumber02,
kRtpExtensionPlayoutDelay,
kRtpExtensionVideoContentType,
kRtpExtensionVideoLayersAllocation,
kRtpExtensionVideoTiming,
kRtpExtensionRtpStreamId,
kRtpExtensionRepairedRtpStreamId,
kRtpExtensionMid,
kRtpExtensionGenericFrameDescriptor,
kRtpExtensionGenericFrameDescriptor00 [[deprecated]] =
kRtpExtensionGenericFrameDescriptor,
kRtpExtensionDependencyDescriptor,
kRtpExtensionGenericFrameDescriptor02 [[deprecated]] =
kRtpExtensionDependencyDescriptor,
kRtpExtensionColorSpace,
kRtpExtensionVideoFrameTrackingId,
kRtpExtensionCorruptionDetection,
kRtpExtensionNumberOfExtensions // Must be the last entity in the enum.
};
enum RTCPAppSubTypes { kAppSubtypeBwe = 0x00 };
// TODO(sprang): Make this an enum class once rtcp_receiver has been cleaned up.
enum RTCPPacketType : uint32_t {
kRtcpReport = 0x0001,
kRtcpSr = 0x0002,
kRtcpRr = 0x0004,
kRtcpSdes = 0x0008,
kRtcpBye = 0x0010,
kRtcpPli = 0x0020,
kRtcpNack = 0x0040,
kRtcpFir = 0x0080,
kRtcpTmmbr = 0x0100,
kRtcpTmmbn = 0x0200,
kRtcpSrReq = 0x0400,
kRtcpLossNotification = 0x2000,
kRtcpRemb = 0x10000,
kRtcpTransmissionTimeOffset = 0x20000,
kRtcpXrReceiverReferenceTime = 0x40000,
kRtcpXrDlrrReportBlock = 0x80000,
kRtcpTransportFeedback = 0x100000,
kRtcpXrTargetBitrate = 0x200000,
};
enum class KeyFrameReqMethod : uint8_t {
kNone, // Don't request keyframes.
kPliRtcp, // Request keyframes through Picture Loss Indication.
kFirRtcp // Request keyframes through Full Intra-frame Request.
};
enum RtxMode {
kRtxOff = 0x0,
kRtxRetransmitted = 0x1, // Only send retransmissions over RTX.
kRtxRedundantPayloads = 0x2 // Preventively send redundant payloads
// instead of padding.
};
const size_t kRtxHeaderSize = 2;
// NOTE! `kNumMediaTypes` must be kept in sync with RtpPacketMediaType!
static constexpr size_t kNumMediaTypes = 5;
enum class RtpPacketMediaType : size_t {
kAudio, // Audio media packets.
kVideo, // Video media packets.
kRetransmission, // Retransmisions, sent as response to NACK.
kForwardErrorCorrection, // FEC packets.
kPadding = kNumMediaTypes - 1, // RTX or plain padding sent to maintain BWE.
// Again, don't forget to update `kNumMediaTypes` if you add another value!
};
} // namespace webrtc
#endif // _RTP_RTCP_TYPEDEF_H_
+203
View File
@@ -0,0 +1,203 @@
/*
* Copyright 2011 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
// Originally these classes are from Chromium.
// http://src.chromium.org/viewvc/chrome/trunk/src/base/memory/ref_counted.h?view=markup
//
// A smart pointer class for reference counted objects. Use this class instead
// of calling AddRef and Release manually on a reference counted object to
// avoid common memory leaks caused by forgetting to Release an object
// reference. Sample usage:
//
// class MyFoo : public RefCounted<MyFoo> {
// ...
// };
//
// void some_function() {
// scoped_refptr<MyFoo> foo = make_ref_counted<MyFoo>();
// foo->Method(param);
// // `foo` is released when this function returns
// }
//
// void some_other_function() {
// scoped_refptr<MyFoo> foo = make_ref_counted<MyFoo>();
// ...
// foo = nullptr; // explicitly releases `foo`
// ...
// if (foo)
// foo->Method(param);
// }
//
// The above examples show how scoped_refptr<T> acts like a pointer to T.
// Given two scoped_refptr<T> classes, it is also possible to exchange
// references between the two objects, like so:
//
// {
// scoped_refptr<MyFoo> a = make_ref_counted<MyFoo>();
// scoped_refptr<MyFoo> b;
//
// b.swap(a);
// // now, `b` references the MyFoo object, and `a` references null.
// }
//
// To make both `a` and `b` in the above example reference the same MyFoo
// object, simply use the assignment operator:
//
// {
// scoped_refptr<MyFoo> a = make_ref_counted<MyFoo>();
// scoped_refptr<MyFoo> b;
//
// b = a;
// // now, `a` and `b` each own a reference to the same MyFoo object.
// }
//
#ifndef API_SCOPED_REFPTR_H_
#define API_SCOPED_REFPTR_H_
#include <cstddef>
#include <memory>
#include <utility>
namespace webrtc {
template <class T>
class scoped_refptr {
public:
using element_type = T;
scoped_refptr() : ptr_(nullptr) {}
scoped_refptr(std::nullptr_t) : ptr_(nullptr) {} // NOLINT(runtime/explicit)
explicit scoped_refptr(T* p) : ptr_(p) {}
scoped_refptr(const scoped_refptr<T>& r) : ptr_(r.ptr_) {}
template <typename U>
scoped_refptr(const scoped_refptr<U>& r) : ptr_(r.get()) {}
// Move constructors.
scoped_refptr(scoped_refptr<T>&& r) noexcept : ptr_(std::move(r.ptr_)) {}
template <typename U>
scoped_refptr(scoped_refptr<U>&& r) noexcept : ptr_(std::move(r.ptr_)) {}
~scoped_refptr() = default;
T* get() const { return ptr_.get(); }
explicit operator bool() const { return ptr_ != nullptr; }
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_.get(); }
T* release() {
T* retVal = ptr_.get();
ptr_.reset();
return retVal;
}
scoped_refptr<T>& operator=(T* p) {
ptr_.reset(p);
return *this;
}
scoped_refptr<T>& operator=(const scoped_refptr<T>& r) {
ptr_ = r.ptr_;
return *this;
}
template <typename U>
scoped_refptr<T>& operator=(const scoped_refptr<U>& r) {
ptr_ = r.ptr_;
return *this;
}
scoped_refptr<T>& operator=(scoped_refptr<T>&& r) noexcept {
ptr_ = std::move(r.ptr_);
return *this;
}
template <typename U>
scoped_refptr<T>& operator=(scoped_refptr<U>&& r) noexcept {
ptr_ = std::move(r.ptr_);
return *this;
}
void swap(T** pp) noexcept { std::swap(ptr_, *pp); }
void swap(scoped_refptr<T>& r) noexcept { std::swap(ptr_, r.ptr_); }
protected:
std::shared_ptr<T> ptr_;
};
template <typename T, typename U>
bool operator==(const scoped_refptr<T>& a, const scoped_refptr<U>& b) {
return a.get() == b.get();
}
template <typename T, typename U>
bool operator!=(const scoped_refptr<T>& a, const scoped_refptr<U>& b) {
return !(a == b);
}
template <typename T>
bool operator==(const scoped_refptr<T>& a, std::nullptr_t) {
return a.get() == nullptr;
}
template <typename T>
bool operator!=(const scoped_refptr<T>& a, std::nullptr_t) {
return !(a == nullptr);
}
template <typename T>
bool operator==(std::nullptr_t, const scoped_refptr<T>& a) {
return a.get() == nullptr;
}
template <typename T>
bool operator!=(std::nullptr_t, const scoped_refptr<T>& a) {
return !(a == nullptr);
}
// Comparison with raw pointer.
template <typename T, typename U>
bool operator==(const scoped_refptr<T>& a, const U* b) {
return a.get() == b;
}
template <typename T, typename U>
bool operator!=(const scoped_refptr<T>& a, const U* b) {
return !(a == b);
}
template <typename T, typename U>
bool operator==(const T* a, const scoped_refptr<U>& b) {
return a == b.get();
}
template <typename T, typename U>
bool operator!=(const T* a, const scoped_refptr<U>& b) {
return !(a == b);
}
// Ordered comparison, needed for use as a std::map key.
template <typename T, typename U>
bool operator<(const scoped_refptr<T>& a, const scoped_refptr<U>& b) {
return a.get() < b.get();
}
} // namespace webrtc
namespace rtc {
// Backwards compatible alias.
// TODO: bugs.webrtc.org/42225969 - Deprecate and remove.
using ::webrtc::scoped_refptr;
} // namespace rtc
#endif // API_SCOPED_REFPTR_H_
@@ -0,0 +1,17 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-01-15
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _BANDWIDTH_USAGE_H_
#define _BANDWIDTH_USAGE_H_
enum class BandwidthUsage {
kBwNormal = 0,
kBwUnderusing = 1,
kBwOverusing = 2,
kLast
};
#endif
+42
View File
@@ -0,0 +1,42 @@
/*
* Copyright 2024 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef API_TRANSPORT_ECN_MARKING_H_
#define API_TRANSPORT_ECN_MARKING_H_
namespace webrtc {
// TODO: bugs.webrtc.org/42225697 - L4S support is slowly being developed.
// Help is appreciated.
// L4S Explicit Congestion Notification (ECN) .
// https://www.rfc-editor.org/rfc/rfc9331.html ECT stands for ECN-Capable
// Transport and CE stands for Congestion Experienced.
// RFC-3168, Section 5
// +-----+-----+
// | ECN FIELD |
// +-----+-----+
// ECT CE [Obsolete] RFC 2481 names for the ECN bits.
// 0 0 Not-ECT
// 0 1 ECT(1)
// 1 0 ECT(0)
// 1 1 CE
enum class EcnMarking {
kNotEct = 0, // Not ECN-Capable Transport
kEct1 = 1, // ECN-Capable Transport
kEct0 = 2, // Not used by L4s (or webrtc.)
kCe = 3, // Congestion experienced
};
} // namespace webrtc
#endif // API_TRANSPORT_ECN_MARKING_H_
+124
View File
@@ -0,0 +1,124 @@
/*
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef API_TRANSPORT_NETWORK_CONTROL_H_
#define API_TRANSPORT_NETWORK_CONTROL_H_
#include <memory>
#include <optional>
#include "api/transport/network_types.h"
#include "api/units/data_rate.h"
#include "api/units/time_delta.h"
namespace webrtc {
class TargetTransferRateObserver {
public:
virtual ~TargetTransferRateObserver() = default;
// Called to indicate target transfer rate as well as giving information about
// the current estimate of network parameters.
virtual void OnTargetTransferRate(TargetTransferRate) = 0;
// Called to provide updates to the expected target rate in case it changes
// before the first call to OnTargetTransferRate.
virtual void OnStartRateUpdate(DataRate) {}
};
// Configuration sent to factory create function. The parameters here are
// optional to use for a network controller implementation.
struct NetworkControllerConfig {
explicit NetworkControllerConfig() {}
// The initial constraints to start with, these can be changed at any later
// time by calls to OnTargetRateConstraints. Note that the starting rate
// has to be set initially to provide a starting state for the network
// controller, even though the field is marked as optional.
TargetRateConstraints constraints;
// Initial stream specific configuration, these are changed at any later time
// by calls to OnStreamsConfig.
StreamsConfig stream_based_config;
};
// NetworkControllerInterface is implemented by network controllers. A network
// controller is a class that uses information about network state and traffic
// to estimate network parameters such as round trip time and bandwidth. Network
// controllers does not guarantee thread safety, the interface must be used in a
// non-concurrent fashion.
class NetworkControllerInterface {
public:
virtual ~NetworkControllerInterface() = default;
// Called when network availabilty changes.
virtual NetworkControlUpdate OnNetworkAvailability(NetworkAvailability) = 0;
// Called when the receiving or sending endpoint changes address.
virtual NetworkControlUpdate OnNetworkRouteChange(NetworkRouteChange) = 0;
// Called periodically with a periodicy as specified by
// NetworkControllerFactoryInterface::GetProcessInterval.
virtual NetworkControlUpdate OnProcessInterval(ProcessInterval) = 0;
// Called when remotely calculated bitrate is received.
virtual NetworkControlUpdate OnRemoteBitrateReport(RemoteBitrateReport) = 0;
// Called round trip time has been calculated by protocol specific mechanisms.
virtual NetworkControlUpdate OnRoundTripTimeUpdate(RoundTripTimeUpdate) = 0;
// Called when a packet is sent on the network.
virtual NetworkControlUpdate OnSentPacket(SentPacket) = 0;
// Called when a packet is received from the remote client.
virtual NetworkControlUpdate OnReceivedPacket(ReceivedPacket) = 0;
// Called when the stream specific configuration has been updated.
virtual NetworkControlUpdate OnStreamsConfig(StreamsConfig) = 0;
// Called when target transfer rate constraints has been changed.
virtual NetworkControlUpdate OnTargetRateConstraints(
TargetRateConstraints) = 0;
// Called when a protocol specific calculation of packet loss has been made.
virtual NetworkControlUpdate OnTransportLossReport(TransportLossReport) = 0;
// Called with per packet feedback regarding receive time.
virtual NetworkControlUpdate OnTransportPacketsFeedback(
TransportPacketsFeedback) = 0;
// Called with network state estimate updates.
virtual NetworkControlUpdate OnNetworkStateEstimate(NetworkStateEstimate) = 0;
};
// NetworkControllerFactoryInterface is an interface for creating a network
// controller.
class NetworkControllerFactoryInterface {
public:
virtual ~NetworkControllerFactoryInterface() = default;
// Used to create a new network controller, requires an observer to be
// provided to handle callbacks.
virtual std::unique_ptr<NetworkControllerInterface> Create(
NetworkControllerConfig config) = 0;
// Returns the interval by which the network controller expects
// OnProcessInterval calls.
virtual TimeDelta GetProcessInterval() const = 0;
};
// Under development, subject to change without notice.
class NetworkStateEstimator {
public:
// Gets the current best estimate according to the estimator.
virtual std::optional<NetworkStateEstimate> GetCurrentEstimate() = 0;
// Called with per packet feedback regarding receive time.
// Used when the NetworkStateEstimator runs in the sending endpoint.
virtual void OnTransportPacketsFeedback(const TransportPacketsFeedback&) = 0;
// Called with per packet feedback regarding receive time.
// Used when the NetworkStateEstimator runs in the receiving endpoint.
virtual void OnReceivedPacket(const PacketResult&) {}
// Called when the receiving or sending endpoint changes address.
virtual void OnRouteChange(const NetworkRouteChange&) = 0;
virtual ~NetworkStateEstimator() = default;
};
class NetworkStateEstimatorFactory {
public:
virtual std::unique_ptr<NetworkStateEstimator> Create() = 0;
virtual ~NetworkStateEstimatorFactory() = default;
};
} // namespace webrtc
#endif // API_TRANSPORT_NETWORK_CONTROL_H_
+107
View File
@@ -0,0 +1,107 @@
/*
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "api/transport/network_types.h"
#include <algorithm>
#include <vector>
namespace webrtc {
StreamsConfig::StreamsConfig() = default;
StreamsConfig::StreamsConfig(const StreamsConfig&) = default;
StreamsConfig::~StreamsConfig() = default;
TargetRateConstraints::TargetRateConstraints() = default;
TargetRateConstraints::TargetRateConstraints(const TargetRateConstraints&) =
default;
TargetRateConstraints::~TargetRateConstraints() = default;
NetworkRouteChange::NetworkRouteChange() = default;
NetworkRouteChange::NetworkRouteChange(const NetworkRouteChange&) = default;
NetworkRouteChange::~NetworkRouteChange() = default;
PacketResult::PacketResult() = default;
PacketResult::PacketResult(const PacketResult& other) = default;
PacketResult::~PacketResult() = default;
bool PacketResult::ReceiveTimeOrder::operator()(const PacketResult& lhs,
const PacketResult& rhs) {
if (lhs.receive_time != rhs.receive_time)
return lhs.receive_time < rhs.receive_time;
if (lhs.sent_packet.send_time != rhs.sent_packet.send_time)
return lhs.sent_packet.send_time < rhs.sent_packet.send_time;
return lhs.sent_packet.sequence_number < rhs.sent_packet.sequence_number;
}
TransportPacketsFeedback::TransportPacketsFeedback() = default;
TransportPacketsFeedback::TransportPacketsFeedback(
const TransportPacketsFeedback& other) = default;
TransportPacketsFeedback::~TransportPacketsFeedback() = default;
std::vector<PacketResult> TransportPacketsFeedback::ReceivedWithSendInfo()
const {
std::vector<PacketResult> res;
for (const PacketResult& fb : packet_feedbacks) {
if (fb.IsReceived()) {
res.push_back(fb);
}
}
return res;
}
std::vector<PacketResult> TransportPacketsFeedback::LostWithSendInfo() const {
std::vector<PacketResult> res;
for (const PacketResult& fb : packet_feedbacks) {
if (!fb.IsReceived()) {
res.push_back(fb);
}
}
return res;
}
std::vector<PacketResult> TransportPacketsFeedback::PacketsWithFeedback()
const {
return packet_feedbacks;
}
std::vector<PacketResult> TransportPacketsFeedback::SortedByReceiveTime()
const {
std::vector<PacketResult> res;
for (const PacketResult& fb : packet_feedbacks) {
if (fb.IsReceived()) {
res.push_back(fb);
}
}
std::sort(res.begin(), res.end(), PacketResult::ReceiveTimeOrder());
return res;
}
NetworkControlUpdate::NetworkControlUpdate() = default;
NetworkControlUpdate::NetworkControlUpdate(const NetworkControlUpdate&) =
default;
NetworkControlUpdate::~NetworkControlUpdate() = default;
PacedPacketInfo::PacedPacketInfo() = default;
PacedPacketInfo::PacedPacketInfo(int probe_cluster_id,
int probe_cluster_min_probes,
int probe_cluster_min_bytes)
: probe_cluster_id(probe_cluster_id),
probe_cluster_min_probes(probe_cluster_min_probes),
probe_cluster_min_bytes(probe_cluster_min_bytes) {}
bool PacedPacketInfo::operator==(const PacedPacketInfo& rhs) const {
return send_bitrate == rhs.send_bitrate &&
probe_cluster_id == rhs.probe_cluster_id &&
probe_cluster_min_probes == rhs.probe_cluster_min_probes &&
probe_cluster_min_bytes == rhs.probe_cluster_min_bytes;
}
} // namespace webrtc
+292
View File
@@ -0,0 +1,292 @@
/*
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef API_TRANSPORT_NETWORK_TYPES_H_
#define API_TRANSPORT_NETWORK_TYPES_H_
#include <stdint.h>
#include <cmath>
#include <optional>
#include <vector>
#include "api/transport/ecn_marking.h"
#include "api/units/data_rate.h"
#include "api/units/data_size.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
namespace webrtc {
// Configuration
// Represents constraints and rates related to the currently enabled streams.
// This is used as input to the congestion controller via the StreamsConfig
// struct.
struct BitrateAllocationLimits {
// The total minimum send bitrate required by all sending streams.
DataRate min_allocatable_rate = DataRate::Zero();
// The total maximum allocatable bitrate for all currently available streams.
DataRate max_allocatable_rate = DataRate::Zero();
// The max bitrate to use for padding. The sum of the per-stream max padding
// rate.
DataRate max_padding_rate = DataRate::Zero();
};
// Use StreamsConfig for information about streams that is required for specific
// adjustments to the algorithms in network controllers. Especially useful
// for experiments.
struct StreamsConfig {
StreamsConfig();
StreamsConfig(const StreamsConfig&);
~StreamsConfig();
Timestamp at_time = Timestamp::PlusInfinity();
std::optional<bool> requests_alr_probing;
// If `enable_repeated_initial_probing` is set to true, Probes are sent
// periodically every 1s during the first 5s after the network becomes
// available. The probes ignores max_total_allocated_bitrate.
std::optional<bool> enable_repeated_initial_probing;
std::optional<double> pacing_factor;
// TODO(srte): Use BitrateAllocationLimits here.
std::optional<DataRate> min_total_allocated_bitrate;
std::optional<DataRate> max_padding_rate;
std::optional<DataRate> max_total_allocated_bitrate;
};
struct TargetRateConstraints {
TargetRateConstraints();
TargetRateConstraints(const TargetRateConstraints&);
~TargetRateConstraints();
Timestamp at_time = Timestamp::PlusInfinity();
std::optional<DataRate> min_data_rate;
std::optional<DataRate> max_data_rate;
// The initial bandwidth estimate to base target rate on. This should be used
// as the basis for initial OnTargetTransferRate and OnPacerConfig callbacks.
std::optional<DataRate> starting_rate;
};
// Send side information
struct NetworkAvailability {
Timestamp at_time = Timestamp::PlusInfinity();
bool network_available = false;
};
struct NetworkRouteChange {
NetworkRouteChange();
NetworkRouteChange(const NetworkRouteChange&);
~NetworkRouteChange();
Timestamp at_time = Timestamp::PlusInfinity();
// The TargetRateConstraints are set here so they can be changed synchronously
// when network route changes.
TargetRateConstraints constraints;
};
struct PacedPacketInfo {
PacedPacketInfo();
PacedPacketInfo(int probe_cluster_id, int probe_cluster_min_probes,
int probe_cluster_min_bytes);
bool operator==(const PacedPacketInfo& rhs) const;
// TODO(srte): Move probing info to a separate, optional struct.
static constexpr int kNotAProbe = -1;
DataRate send_bitrate = DataRate::BitsPerSec(0);
int probe_cluster_id = kNotAProbe;
int probe_cluster_min_probes = -1;
int probe_cluster_min_bytes = -1;
int probe_cluster_bytes_sent = 0;
};
struct SentPacket {
Timestamp send_time = Timestamp::PlusInfinity();
// Size of packet with overhead up to IP layer.
DataSize size = DataSize::Zero();
// Size of preceeding packets that are not part of feedback.
DataSize prior_unacked_data = DataSize::Zero();
// Probe cluster id and parameters including bitrate, number of packets and
// number of bytes.
PacedPacketInfo pacing_info;
// True if the packet is an audio packet, false for video, padding, RTX etc.
bool audio = false;
// Transport independent sequence number, any tracked packet should have a
// sequence number that is unique over the whole call and increasing by 1 for
// each packet.
int64_t sequence_number;
// Tracked data in flight when the packet was sent, excluding unacked data.
DataSize data_in_flight = DataSize::Zero();
};
struct ReceivedPacket {
Timestamp send_time = Timestamp::MinusInfinity();
Timestamp receive_time = Timestamp::PlusInfinity();
DataSize size = DataSize::Zero();
};
// Transport level feedback
struct RemoteBitrateReport {
Timestamp receive_time = Timestamp::PlusInfinity();
DataRate bandwidth = DataRate::Infinity();
};
struct RoundTripTimeUpdate {
Timestamp receive_time = Timestamp::PlusInfinity();
TimeDelta round_trip_time = TimeDelta::PlusInfinity();
bool smoothed = false;
};
struct TransportLossReport {
Timestamp receive_time = Timestamp::PlusInfinity();
Timestamp start_time = Timestamp::PlusInfinity();
Timestamp end_time = Timestamp::PlusInfinity();
uint64_t packets_lost_delta = 0;
uint64_t packets_received_delta = 0;
};
// Packet level feedback
struct PacketResult {
class ReceiveTimeOrder {
public:
bool operator()(const PacketResult& lhs, const PacketResult& rhs);
};
PacketResult();
PacketResult(const PacketResult&);
~PacketResult();
inline bool IsReceived() const { return !receive_time.IsPlusInfinity(); }
SentPacket sent_packet;
Timestamp receive_time = Timestamp::PlusInfinity();
EcnMarking ecn = EcnMarking::kNotEct;
};
struct TransportPacketsFeedback {
TransportPacketsFeedback();
TransportPacketsFeedback(const TransportPacketsFeedback& other);
~TransportPacketsFeedback();
Timestamp feedback_time = Timestamp::PlusInfinity();
DataSize data_in_flight = DataSize::Zero();
bool transport_supports_ecn = false;
std::vector<PacketResult> packet_feedbacks;
// Arrival times for messages without send time information.
std::vector<Timestamp> sendless_arrival_times;
std::vector<PacketResult> ReceivedWithSendInfo() const;
std::vector<PacketResult> LostWithSendInfo() const;
std::vector<PacketResult> PacketsWithFeedback() const;
std::vector<PacketResult> SortedByReceiveTime() const;
};
// Network estimation
struct NetworkEstimate {
Timestamp at_time = Timestamp::PlusInfinity();
// Deprecated, use TargetTransferRate::target_rate instead.
DataRate bandwidth = DataRate::Infinity();
TimeDelta round_trip_time = TimeDelta::PlusInfinity();
TimeDelta bwe_period = TimeDelta::PlusInfinity();
float loss_rate_ratio = 0;
};
// Network control
struct PacerConfig {
Timestamp at_time = Timestamp::PlusInfinity();
// Pacer should send at most data_window data over time_window duration.
DataSize data_window = DataSize::Infinity();
TimeDelta time_window = TimeDelta::PlusInfinity();
// Pacer should send at least pad_window data over time_window duration.
DataSize pad_window = DataSize::Zero();
DataRate data_rate() const { return data_window / time_window; }
DataRate pad_rate() const { return pad_window / time_window; }
};
struct ProbeClusterConfig {
Timestamp at_time = Timestamp::PlusInfinity();
DataRate target_data_rate = DataRate::Zero();
// Duration of a probe.
TimeDelta target_duration = TimeDelta::Zero();
// Delta time between sent bursts of packets during probe.
TimeDelta min_probe_delta = TimeDelta::Millis(2);
int32_t target_probe_count = 0;
int32_t id = 0;
};
struct TargetTransferRate {
Timestamp at_time = Timestamp::PlusInfinity();
// The estimate on which the target rate is based on.
NetworkEstimate network_estimate;
DataRate target_rate = DataRate::Zero();
DataRate stable_target_rate = DataRate::Zero();
double cwnd_reduce_ratio = 0;
};
// Contains updates of network controller comand state. Using optionals to
// indicate whether a member has been updated. The array of probe clusters
// should be used to send out probes if not empty.
struct NetworkControlUpdate {
NetworkControlUpdate();
NetworkControlUpdate(const NetworkControlUpdate&);
~NetworkControlUpdate();
bool has_updates() const {
return congestion_window.has_value() || pacer_config.has_value() ||
!probe_cluster_configs.empty() || target_rate.has_value();
}
std::optional<DataSize> congestion_window;
std::optional<PacerConfig> pacer_config;
std::vector<ProbeClusterConfig> probe_cluster_configs;
std::optional<TargetTransferRate> target_rate;
};
// Process control
struct ProcessInterval {
Timestamp at_time = Timestamp::PlusInfinity();
std::optional<DataSize> pacer_queue;
};
// Under development, subject to change without notice.
struct NetworkStateEstimate {
double confidence = NAN;
// The time the estimate was received/calculated.
Timestamp update_time = Timestamp::MinusInfinity();
Timestamp last_receive_time = Timestamp::MinusInfinity();
Timestamp last_send_time = Timestamp::MinusInfinity();
// Total estimated link capacity.
DataRate link_capacity = DataRate::MinusInfinity();
// Used as a safe measure of available capacity.
DataRate link_capacity_lower = DataRate::MinusInfinity();
// Used as limit for increasing bitrate.
DataRate link_capacity_upper = DataRate::MinusInfinity();
TimeDelta pre_link_buffer_delay = TimeDelta::MinusInfinity();
TimeDelta post_link_buffer_delay = TimeDelta::MinusInfinity();
TimeDelta propagation_delay = TimeDelta::MinusInfinity();
// Only for debugging
TimeDelta time_delta = TimeDelta::MinusInfinity();
Timestamp last_feed_time = Timestamp::MinusInfinity();
double cross_delay_rate = NAN;
double spike_delay_rate = NAN;
DataRate link_capacity_std_dev = DataRate::MinusInfinity();
DataRate link_capacity_min = DataRate::MinusInfinity();
double cross_traffic_ratio = NAN;
};
} // namespace webrtc
#endif // API_TRANSPORT_NETWORK_TYPES_H_
+30
View File
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "api/units/data_rate.h"
#include <string>
namespace webrtc {
std::string ToString(DataRate value) {
if (value.IsPlusInfinity()) {
return "+inf bps";
} else if (value.IsMinusInfinity()) {
return "-inf bps";
} else {
if (value.bps() == 0 || value.bps() % 1000 != 0) {
return std::to_string(value.bps()) + " bps";
} else {
return std::to_string(value.kbps()) + " kbps";
}
}
}
} // namespace webrtc
+141
View File
@@ -0,0 +1,141 @@
/*
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef API_UNITS_DATA_RATE_H_
#define API_UNITS_DATA_RATE_H_
#include <cstdint>
#include <limits>
#include <string>
#include <type_traits>
#include "api/units/data_size.h"
#include "api/units/frequency.h"
#include "api/units/time_delta.h"
#include "unit_base.h"
namespace webrtc {
// DataRate is a class that represents a given data rate. This can be used to
// represent bandwidth, encoding bitrate, etc. The internal storage is bits per
// second (bps).
class DataRate final : public rtc_units_impl::RelativeUnit<DataRate> {
public:
template <typename T>
static constexpr DataRate BitsPerSec(T value) {
static_assert(std::is_arithmetic<T>::value, "");
return FromValue(value);
}
template <typename T>
static constexpr DataRate BytesPerSec(T value) {
static_assert(std::is_arithmetic<T>::value, "");
return FromFraction(8, value);
}
template <typename T>
static constexpr DataRate KilobitsPerSec(T value) {
static_assert(std::is_arithmetic<T>::value, "");
return FromFraction(1000, value);
}
static constexpr DataRate Infinity() { return PlusInfinity(); }
constexpr DataRate() = default;
template <typename Sink>
friend void AbslStringify(Sink& sink, DataRate value);
template <typename T = int64_t>
constexpr T bps() const {
return ToValue<T>();
}
template <typename T = int64_t>
constexpr T bytes_per_sec() const {
return ToFraction<8, T>();
}
template <typename T = int64_t>
constexpr T kbps() const {
return ToFraction<1000, T>();
}
constexpr int64_t bps_or(int64_t fallback_value) const {
return ToValueOr(fallback_value);
}
constexpr int64_t kbps_or(int64_t fallback_value) const {
return ToFractionOr<1000>(fallback_value);
}
private:
// Bits per second used internally to simplify debugging by making the value
// more recognizable.
friend class rtc_units_impl::UnitBase<DataRate>;
using RelativeUnit::RelativeUnit;
static constexpr bool one_sided = true;
};
namespace data_rate_impl {
inline constexpr int64_t Microbits(const DataSize& size) {
constexpr int64_t kMaxBeforeConversion =
std::numeric_limits<int64_t>::max() / 8000000;
return size.bytes() * 8000000;
}
inline constexpr int64_t MillibytePerSec(const DataRate& size) {
constexpr int64_t kMaxBeforeConversion =
std::numeric_limits<int64_t>::max() / (1000 / 8);
return size.bps() * (1000 / 8);
}
} // namespace data_rate_impl
inline constexpr DataRate operator/(const DataSize size,
const TimeDelta duration) {
return DataRate::BitsPerSec(data_rate_impl::Microbits(size) / duration.us());
}
inline constexpr TimeDelta operator/(const DataSize size, const DataRate rate) {
return TimeDelta::Micros(data_rate_impl::Microbits(size) / rate.bps());
}
inline constexpr DataSize operator*(const DataRate rate,
const TimeDelta duration) {
int64_t microbits = rate.bps() * duration.us();
return DataSize::Bytes((microbits + 4000000) / 8000000);
}
inline constexpr DataSize operator*(const TimeDelta duration,
const DataRate rate) {
return rate * duration;
}
inline constexpr DataSize operator/(const DataRate rate,
const Frequency frequency) {
int64_t millihertz = frequency.millihertz<int64_t>();
// Note that the value is truncated here reather than rounded, potentially
// introducing an error of .5 bytes if rounding were expected.
return DataSize::Bytes(data_rate_impl::MillibytePerSec(rate) / millihertz);
}
inline constexpr Frequency operator/(const DataRate rate, const DataSize size) {
return Frequency::MilliHertz(data_rate_impl::MillibytePerSec(rate) /
size.bytes());
}
inline constexpr DataRate operator*(const DataSize size,
const Frequency frequency) {
int64_t millibits_per_second =
size.bytes() * 8 * frequency.millihertz<int64_t>();
return DataRate::BitsPerSec((millibits_per_second + 500) / 1000);
}
inline constexpr DataRate operator*(const Frequency frequency,
const DataSize size) {
return size * frequency;
}
std::string ToString(DataRate value);
template <typename Sink>
void AbslStringify(Sink& sink, DataRate value) {
sink.Append(ToString(value));
}
} // namespace webrtc
#endif // API_UNITS_DATA_RATE_H_
+26
View File
@@ -0,0 +1,26 @@
/*
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "api/units/data_size.h"
#include <string>
namespace webrtc {
std::string ToString(DataSize value) {
if (value.IsPlusInfinity()) {
return "+inf bytes";
} else if (value.IsMinusInfinity()) {
return "-inf bytes";
} else {
return std::to_string(value.bytes()) + " bytes";
}
}
} // namespace webrtc
+60
View File
@@ -0,0 +1,60 @@
/*
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef API_UNITS_DATA_SIZE_H_
#define API_UNITS_DATA_SIZE_H_
#include <cstdint>
#include <string>
#include <type_traits>
#include "unit_base.h" // IWYU pragma: export
namespace webrtc {
// DataSize is a class represeting a count of bytes.
class DataSize final : public rtc_units_impl::RelativeUnit<DataSize> {
public:
template <typename T>
static constexpr DataSize Bytes(T value) {
static_assert(std::is_arithmetic<T>::value, "");
return FromValue(value);
}
static constexpr DataSize Infinity() { return PlusInfinity(); }
constexpr DataSize() = default;
template <typename Sink>
friend void AbslStringify(Sink& sink, DataSize value);
template <typename T = int64_t>
constexpr T bytes() const {
return ToValue<T>();
}
constexpr int64_t bytes_or(int64_t fallback_value) const {
return ToValueOr(fallback_value);
}
private:
friend class rtc_units_impl::UnitBase<DataSize>;
using RelativeUnit::RelativeUnit;
static constexpr bool one_sided = true;
};
std::string ToString(DataSize value);
template <typename Sink>
void AbslStringify(Sink& sink, DataSize value) {
sink.Append(ToString(value));
}
} // namespace webrtc
#endif // API_UNITS_DATA_SIZE_H_
+27
View File
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "frequency.h"
#include <cstdint>
#include <string>
namespace webrtc {
std::string ToString(Frequency value) {
if (value.IsPlusInfinity()) {
return "+inf Hz";
} else if (value.IsMinusInfinity()) {
return "-inf Hz";
} else if (value.millihertz<int64_t>() % 1000 != 0) {
return std::to_string(value.hertz<double>()) + " Hz";
} else {
return std::to_string(value.hertz<int64_t>()) + " Hz";
}
}
} // namespace webrtc
+89
View File
@@ -0,0 +1,89 @@
/*
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef API_UNITS_FREQUENCY_H_
#define API_UNITS_FREQUENCY_H_
#include <cstdint>
#include <cstdlib>
#include <limits>
#include <string>
#include <type_traits>
#include "api/units/time_delta.h"
#include "unit_base.h" // IWYU pragma: export
namespace webrtc {
class Frequency final : public rtc_units_impl::RelativeUnit<Frequency> {
public:
template <typename T>
static constexpr Frequency MilliHertz(T value) {
static_assert(std::is_arithmetic<T>::value, "");
return FromValue(value);
}
template <typename T>
static constexpr Frequency Hertz(T value) {
static_assert(std::is_arithmetic<T>::value, "");
return FromFraction(1'000, value);
}
template <typename T>
static constexpr Frequency KiloHertz(T value) {
static_assert(std::is_arithmetic<T>::value, "");
return FromFraction(1'000'000, value);
}
constexpr Frequency() = default;
template <typename Sink>
friend void AbslStringify(Sink& sink, Frequency value);
template <typename T = int64_t>
constexpr T hertz() const {
return ToFraction<1000, T>();
}
template <typename T = int64_t>
constexpr T millihertz() const {
return ToValue<T>();
}
private:
friend class rtc_units_impl::UnitBase<Frequency>;
using RelativeUnit::RelativeUnit;
static constexpr bool one_sided = true;
};
inline constexpr Frequency operator/(int64_t nominator,
const TimeDelta& interval) {
constexpr int64_t kKiloPerMicro = 1000 * 1000000;
return Frequency::MilliHertz(nominator * kKiloPerMicro / interval.us());
}
inline constexpr TimeDelta operator/(int64_t nominator,
const Frequency& frequency) {
constexpr int64_t kMegaPerMilli = 1000000 * 1000;
return TimeDelta::Micros(nominator * kMegaPerMilli / frequency.millihertz());
}
inline constexpr double operator*(Frequency frequency, TimeDelta time_delta) {
return frequency.hertz<double>() * time_delta.seconds<double>();
}
inline constexpr double operator*(TimeDelta time_delta, Frequency frequency) {
return frequency * time_delta;
}
std::string ToString(Frequency value);
template <typename Sink>
void AbslStringify(Sink& sink, Frequency value) {
sink.Append(ToString(value));
}
} // namespace webrtc
#endif // API_UNITS_FREQUENCY_H_
+32
View File
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "api/units/time_delta.h"
#include <string>
namespace webrtc {
std::string ToString(TimeDelta value) {
if (value.IsPlusInfinity()) {
return "+inf ms";
} else if (value.IsMinusInfinity()) {
return "-inf ms";
} else {
if (value.us() == 0 || (value.us() % 1000) != 0)
return std::to_string(value.us()) + " us";
else if (value.ms() % 1000 != 0)
return std::to_string(value.ms()) + " ms";
else
return std::to_string(value.seconds()) + " s";
}
}
} // namespace webrtc
+104
View File
@@ -0,0 +1,104 @@
/*
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef API_UNITS_TIME_DELTA_H_
#define API_UNITS_TIME_DELTA_H_
#include <cstdint>
#include <cstdlib>
#include <string>
#include <type_traits>
#include "unit_base.h" // IWYU pragma: export
namespace webrtc {
// TimeDelta represents the difference between two timestamps. Commonly this can
// be a duration. However since two Timestamps are not guaranteed to have the
// same epoch (they might come from different computers, making exact
// synchronisation infeasible), the duration covered by a TimeDelta can be
// undefined. To simplify usage, it can be constructed and converted to
// different units, specifically seconds (s), milliseconds (ms) and
// microseconds (us).
class TimeDelta final : public rtc_units_impl::RelativeUnit<TimeDelta> {
public:
template <typename T>
static constexpr TimeDelta Minutes(T value) {
static_assert(std::is_arithmetic<T>::value, "");
return Seconds(value * 60);
}
template <typename T>
static constexpr TimeDelta Seconds(T value) {
static_assert(std::is_arithmetic<T>::value, "");
return FromFraction(1'000'000, value);
}
template <typename T>
static constexpr TimeDelta Millis(T value) {
static_assert(std::is_arithmetic<T>::value, "");
return FromFraction(1'000, value);
}
template <typename T>
static constexpr TimeDelta Micros(T value) {
static_assert(std::is_arithmetic<T>::value, "");
return FromValue(value);
}
constexpr TimeDelta() = default;
template <typename Sink>
friend void AbslStringify(Sink& sink, TimeDelta value);
template <typename T = int64_t>
constexpr T seconds() const {
return ToFraction<1000000, T>();
}
template <typename T = int64_t>
constexpr T ms() const {
return ToFraction<1000, T>();
}
template <typename T = int64_t>
constexpr T us() const {
return ToValue<T>();
}
template <typename T = int64_t>
constexpr T ns() const {
return ToMultiple<1000, T>();
}
constexpr int64_t seconds_or(int64_t fallback_value) const {
return ToFractionOr<1000000>(fallback_value);
}
constexpr int64_t ms_or(int64_t fallback_value) const {
return ToFractionOr<1000>(fallback_value);
}
constexpr int64_t us_or(int64_t fallback_value) const {
return ToValueOr(fallback_value);
}
constexpr TimeDelta Abs() const {
return us() < 0 ? TimeDelta::Micros(-us()) : *this;
}
private:
friend class rtc_units_impl::UnitBase<TimeDelta>;
using RelativeUnit::RelativeUnit;
static constexpr bool one_sided = false;
};
std::string ToString(TimeDelta value);
template <typename Sink>
void AbslStringify(Sink& sink, TimeDelta value) {
sink.Append(ToString(value));
}
} // namespace webrtc
#endif // API_UNITS_TIME_DELTA_H_
+30
View File
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "api/units/timestamp.h"
#include <string>
namespace webrtc {
std::string ToString(Timestamp value) {
if (value.IsPlusInfinity()) {
return "+inf ms";
} else if (value.IsMinusInfinity()) {
return "-inf ms";
} else {
if (value.us() == 0 || (value.us() % 1000) != 0)
return std::to_string(value.us()) + " us";
else if (value.ms() % 1000 != 0)
return std::to_string(value.ms()) + " ms";
else
return std::to_string(value.seconds()) + " s";
}
}
} // namespace webrtc
+120
View File
@@ -0,0 +1,120 @@
/*
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef API_UNITS_TIMESTAMP_H_
#define API_UNITS_TIMESTAMP_H_
#include <cstdint>
#include <string>
#include <type_traits>
#include "time_delta.h"
#include "unit_base.h" // IWYU pragma: export
namespace webrtc {
// Timestamp represents the time that has passed since some unspecified epoch.
// The epoch is assumed to be before any represented timestamps, this means that
// negative values are not valid. The most notable feature is that the
// difference of two Timestamps results in a TimeDelta.
class Timestamp final : public rtc_units_impl::UnitBase<Timestamp> {
public:
template <typename T>
static constexpr Timestamp Seconds(T value) {
static_assert(std::is_arithmetic<T>::value, "");
return FromFraction(1'000'000, value);
}
template <typename T>
static constexpr Timestamp Millis(T value) {
static_assert(std::is_arithmetic<T>::value, "");
return FromFraction(1'000, value);
}
template <typename T>
static constexpr Timestamp Micros(T value) {
static_assert(std::is_arithmetic<T>::value, "");
return FromValue(value);
}
Timestamp() = delete;
template <typename Sink>
friend void AbslStringify(Sink& sink, Timestamp value);
template <typename T = int64_t>
constexpr T seconds() const {
return ToFraction<1000000, T>();
}
template <typename T = int64_t>
constexpr T ms() const {
return ToFraction<1000, T>();
}
template <typename T = int64_t>
constexpr T us() const {
return ToValue<T>();
}
constexpr int64_t seconds_or(int64_t fallback_value) const {
return ToFractionOr<1000000>(fallback_value);
}
constexpr int64_t ms_or(int64_t fallback_value) const {
return ToFractionOr<1000>(fallback_value);
}
constexpr int64_t us_or(int64_t fallback_value) const {
return ToValueOr(fallback_value);
}
constexpr Timestamp operator+(const TimeDelta delta) const {
if (IsPlusInfinity() || delta.IsPlusInfinity()) {
return PlusInfinity();
} else if (IsMinusInfinity() || delta.IsMinusInfinity()) {
return MinusInfinity();
}
return Timestamp::Micros(us() + delta.us());
}
constexpr Timestamp operator-(const TimeDelta delta) const {
if (IsPlusInfinity() || delta.IsMinusInfinity()) {
return PlusInfinity();
} else if (IsMinusInfinity() || delta.IsPlusInfinity()) {
return MinusInfinity();
}
return Timestamp::Micros(us() - delta.us());
}
constexpr TimeDelta operator-(const Timestamp other) const {
if (IsPlusInfinity() || other.IsMinusInfinity()) {
return TimeDelta::PlusInfinity();
} else if (IsMinusInfinity() || other.IsPlusInfinity()) {
return TimeDelta::MinusInfinity();
}
return TimeDelta::Micros(us() - other.us());
}
constexpr Timestamp& operator-=(const TimeDelta delta) {
*this = *this - delta;
return *this;
}
constexpr Timestamp& operator+=(const TimeDelta delta) {
*this = *this + delta;
return *this;
}
private:
friend class rtc_units_impl::UnitBase<Timestamp>;
using UnitBase::UnitBase;
static constexpr bool one_sided = true;
};
std::string ToString(Timestamp value);
template <typename Sink>
void AbslStringify(Sink& sink, Timestamp value) {
sink.Append(ToString(value));
}
} // namespace webrtc
#endif // API_UNITS_TIMESTAMP_H_
+275
View File
@@ -0,0 +1,275 @@
/*
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef RTC_BASE_UNITS_UNIT_BASE_H_
#define RTC_BASE_UNITS_UNIT_BASE_H_
#include <stdint.h>
#include <algorithm>
#include <cmath>
#include <limits>
#include <type_traits>
#include "rtc_base/numerics/divide_round.h"
#include "rtc_base/numerics/safe_conversions.h"
namespace webrtc {
namespace rtc_units_impl {
// UnitBase is a base class for implementing custom value types with a specific
// unit. It provides type safety and commonly useful operations. The underlying
// storage is always an int64_t, it's up to the unit implementation to choose
// what scale it represents.
//
// It's used like:
// class MyUnit: public UnitBase<MyUnit> {...};
//
// Unit_T is the subclass representing the specific unit.
template <class Unit_T>
class UnitBase {
public:
UnitBase() = delete;
static constexpr Unit_T Zero() { return Unit_T(0); }
static constexpr Unit_T PlusInfinity() { return Unit_T(PlusInfinityVal()); }
static constexpr Unit_T MinusInfinity() { return Unit_T(MinusInfinityVal()); }
constexpr bool IsZero() const { return value_ == 0; }
constexpr bool IsFinite() const { return !IsInfinite(); }
constexpr bool IsInfinite() const {
return value_ == PlusInfinityVal() || value_ == MinusInfinityVal();
}
constexpr bool IsPlusInfinity() const { return value_ == PlusInfinityVal(); }
constexpr bool IsMinusInfinity() const {
return value_ == MinusInfinityVal();
}
constexpr bool operator==(const UnitBase<Unit_T>& other) const {
return value_ == other.value_;
}
constexpr bool operator!=(const UnitBase<Unit_T>& other) const {
return value_ != other.value_;
}
constexpr bool operator<=(const UnitBase<Unit_T>& other) const {
return value_ <= other.value_;
}
constexpr bool operator>=(const UnitBase<Unit_T>& other) const {
return value_ >= other.value_;
}
constexpr bool operator>(const UnitBase<Unit_T>& other) const {
return value_ > other.value_;
}
constexpr bool operator<(const UnitBase<Unit_T>& other) const {
return value_ < other.value_;
}
constexpr Unit_T RoundTo(const Unit_T& resolution) const {
return Unit_T((value_ + resolution.value_ / 2) / resolution.value_) *
resolution.value_;
}
constexpr Unit_T RoundUpTo(const Unit_T& resolution) const {
return Unit_T((value_ + resolution.value_ - 1) / resolution.value_) *
resolution.value_;
}
constexpr Unit_T RoundDownTo(const Unit_T& resolution) const {
return Unit_T(value_ / resolution.value_) * resolution.value_;
}
protected:
template <typename T, typename std::enable_if<
std::is_integral<T>::value>::type* = nullptr>
static constexpr Unit_T FromValue(T value) {
return Unit_T(rtc::dchecked_cast<int64_t>(value));
}
template <typename T, typename std::enable_if<
std::is_floating_point<T>::value>::type* = nullptr>
static constexpr Unit_T FromValue(T value) {
if (value == std::numeric_limits<T>::infinity()) {
return PlusInfinity();
} else if (value == -std::numeric_limits<T>::infinity()) {
return MinusInfinity();
} else {
return FromValue(rtc::dchecked_cast<int64_t>(value));
}
}
template <typename T, typename std::enable_if<
std::is_integral<T>::value>::type* = nullptr>
static constexpr Unit_T FromFraction(int64_t denominator, T value) {
return Unit_T(rtc::dchecked_cast<int64_t>(value * denominator));
}
template <typename T, typename std::enable_if<
std::is_floating_point<T>::value>::type* = nullptr>
static constexpr Unit_T FromFraction(int64_t denominator, T value) {
return FromValue(value * denominator);
}
template <typename T = int64_t>
constexpr typename std::enable_if<std::is_integral<T>::value, T>::type
ToValue() const {
return rtc::dchecked_cast<T>(value_);
}
template <typename T>
constexpr typename std::enable_if<std::is_floating_point<T>::value, T>::type
ToValue() const {
return IsPlusInfinity() ? std::numeric_limits<T>::infinity()
: IsMinusInfinity() ? -std::numeric_limits<T>::infinity()
: value_;
}
template <typename T>
constexpr T ToValueOr(T fallback_value) const {
return IsFinite() ? value_ : fallback_value;
}
template <int64_t Denominator, typename T = int64_t>
constexpr typename std::enable_if<std::is_integral<T>::value, T>::type
ToFraction() const {
return rtc::dchecked_cast<T>(DivideRoundToNearest(value_, Denominator));
}
template <int64_t Denominator, typename T>
constexpr typename std::enable_if<std::is_floating_point<T>::value, T>::type
ToFraction() const {
return ToValue<T>() * (1 / static_cast<T>(Denominator));
}
template <int64_t Denominator>
constexpr int64_t ToFractionOr(int64_t fallback_value) const {
return IsFinite() ? DivideRoundToNearest(value_, Denominator)
: fallback_value;
}
template <int64_t Factor, typename T = int64_t>
constexpr typename std::enable_if<std::is_integral<T>::value, T>::type
ToMultiple() const {
return rtc::dchecked_cast<T>(ToValue() * Factor);
}
template <int64_t Factor, typename T>
constexpr typename std::enable_if<std::is_floating_point<T>::value, T>::type
ToMultiple() const {
return ToValue<T>() * Factor;
}
explicit constexpr UnitBase(int64_t value) : value_(value) {}
private:
template <class RelativeUnit_T>
friend class RelativeUnit;
static inline constexpr int64_t PlusInfinityVal() {
return std::numeric_limits<int64_t>::max();
}
static inline constexpr int64_t MinusInfinityVal() {
return std::numeric_limits<int64_t>::min();
}
constexpr Unit_T& AsSubClassRef() { return static_cast<Unit_T&>(*this); }
constexpr const Unit_T& AsSubClassRef() const {
return static_cast<const Unit_T&>(*this);
}
int64_t value_;
};
// Extends UnitBase to provide operations for relative units, that is, units
// that have a meaningful relation between values such that a += b is a
// sensible thing to do. For a,b <- same unit.
template <class Unit_T>
class RelativeUnit : public UnitBase<Unit_T> {
public:
constexpr Unit_T Clamped(Unit_T min_value, Unit_T max_value) const {
return std::max(min_value,
std::min(UnitBase<Unit_T>::AsSubClassRef(), max_value));
}
constexpr void Clamp(Unit_T min_value, Unit_T max_value) {
*this = Clamped(min_value, max_value);
}
constexpr Unit_T operator+(const Unit_T other) const {
if (this->IsPlusInfinity() || other.IsPlusInfinity()) {
return this->PlusInfinity();
} else if (this->IsMinusInfinity() || other.IsMinusInfinity()) {
return this->MinusInfinity();
}
return UnitBase<Unit_T>::FromValue(this->ToValue() + other.ToValue());
}
constexpr Unit_T operator-(const Unit_T other) const {
if (this->IsPlusInfinity() || other.IsMinusInfinity()) {
return this->PlusInfinity();
} else if (this->IsMinusInfinity() || other.IsPlusInfinity()) {
return this->MinusInfinity();
}
return UnitBase<Unit_T>::FromValue(this->ToValue() - other.ToValue());
}
constexpr Unit_T& operator+=(const Unit_T other) {
*this = *this + other;
return this->AsSubClassRef();
}
constexpr Unit_T& operator-=(const Unit_T other) {
*this = *this - other;
return this->AsSubClassRef();
}
constexpr double operator/(const Unit_T other) const {
return UnitBase<Unit_T>::template ToValue<double>() /
other.template ToValue<double>();
}
template <typename T,
typename std::enable_if_t<std::is_floating_point_v<T>>* = nullptr>
constexpr Unit_T operator/(T scalar) const {
return UnitBase<Unit_T>::FromValue(std::llround(this->ToValue() / scalar));
}
template <typename T,
typename std::enable_if_t<std::is_integral_v<T>>* = nullptr>
constexpr Unit_T operator/(T scalar) const {
return UnitBase<Unit_T>::FromValue(this->ToValue() / scalar);
}
constexpr Unit_T operator*(double scalar) const {
return UnitBase<Unit_T>::FromValue(std::llround(this->ToValue() * scalar));
}
constexpr Unit_T operator*(int64_t scalar) const {
return UnitBase<Unit_T>::FromValue(this->ToValue() * scalar);
}
constexpr Unit_T operator*(int32_t scalar) const {
return UnitBase<Unit_T>::FromValue(this->ToValue() * scalar);
}
constexpr Unit_T operator*(size_t scalar) const {
return UnitBase<Unit_T>::FromValue(this->ToValue() * scalar);
}
protected:
using UnitBase<Unit_T>::UnitBase;
constexpr RelativeUnit() : UnitBase<Unit_T>(0) {}
};
template <class Unit_T>
inline constexpr Unit_T operator*(double scalar, RelativeUnit<Unit_T> other) {
return other * scalar;
}
template <class Unit_T>
inline constexpr Unit_T operator*(int64_t scalar, RelativeUnit<Unit_T> other) {
return other * scalar;
}
template <class Unit_T>
inline constexpr Unit_T operator*(int32_t scalar, RelativeUnit<Unit_T> other) {
return other * scalar;
}
template <class Unit_T>
inline constexpr Unit_T operator*(size_t scalar, RelativeUnit<Unit_T> other) {
return other * scalar;
}
template <class Unit_T>
inline constexpr Unit_T operator-(RelativeUnit<Unit_T> other) {
if (other.IsPlusInfinity()) return UnitBase<Unit_T>::MinusInfinity();
if (other.IsMinusInfinity()) return UnitBase<Unit_T>::PlusInfinity();
return -1 * other;
}
} // namespace rtc_units_impl
} // namespace webrtc
#endif // RTC_BASE_UNITS_UNIT_BASE_H_
+118
View File
@@ -0,0 +1,118 @@
/*
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "api/video/video_timing.h"
#include <algorithm>
#include <cstdint>
#include <string>
#include "api/array_view.h"
#include "api/units/time_delta.h"
#include "log.h"
#include "rtc_base/numerics/safe_conversions.h"
namespace webrtc {
uint16_t VideoSendTiming::GetDeltaCappedMs(int64_t base_ms, int64_t time_ms) {
if (time_ms < base_ms) {
LOG_ERROR("Delta {} ms expected to be positive", (time_ms - base_ms));
}
return rtc::saturated_cast<uint16_t>(time_ms - base_ms);
}
uint16_t VideoSendTiming::GetDeltaCappedMs(TimeDelta delta) {
if (delta < TimeDelta::Zero()) {
LOG_ERROR("Delta {} ms expected to be positive", delta.ms());
}
return rtc::saturated_cast<uint16_t>(delta.ms());
}
TimingFrameInfo::TimingFrameInfo()
: rtp_timestamp(0),
capture_time_ms(-1),
encode_start_ms(-1),
encode_finish_ms(-1),
packetization_finish_ms(-1),
pacer_exit_ms(-1),
network_timestamp_ms(-1),
network2_timestamp_ms(-1),
receive_start_ms(-1),
receive_finish_ms(-1),
decode_start_ms(-1),
decode_finish_ms(-1),
render_time_ms(-1),
flags(VideoSendTiming::kNotTriggered) {}
int64_t TimingFrameInfo::EndToEndDelay() const {
return capture_time_ms >= 0 ? decode_finish_ms - capture_time_ms : -1;
}
bool TimingFrameInfo::IsLongerThan(const TimingFrameInfo& other) const {
int64_t other_delay = other.EndToEndDelay();
return other_delay == -1 || EndToEndDelay() > other_delay;
}
bool TimingFrameInfo::operator<(const TimingFrameInfo& other) const {
return other.IsLongerThan(*this);
}
bool TimingFrameInfo::operator<=(const TimingFrameInfo& other) const {
return !IsLongerThan(other);
}
bool TimingFrameInfo::IsOutlier() const {
return !IsInvalid() && (flags & VideoSendTiming::kTriggeredBySize);
}
bool TimingFrameInfo::IsTimerTriggered() const {
return !IsInvalid() && (flags & VideoSendTiming::kTriggeredByTimer);
}
bool TimingFrameInfo::IsInvalid() const {
return flags == VideoSendTiming::kInvalid;
}
std::string TimingFrameInfo::ToString() const {
if (IsInvalid()) {
return "";
}
std::ostringstream oss;
oss << rtp_timestamp << ',' << capture_time_ms << ',' << encode_start_ms
<< ',' << encode_finish_ms << ',' << packetization_finish_ms << ','
<< pacer_exit_ms << ',' << network_timestamp_ms << ','
<< network2_timestamp_ms << ',' << receive_start_ms << ','
<< receive_finish_ms << ',' << decode_start_ms << ',' << decode_finish_ms
<< ',' << render_time_ms << ',' << IsOutlier() << ','
<< IsTimerTriggered();
return oss.str();
}
VideoPlayoutDelay::VideoPlayoutDelay(TimeDelta min, TimeDelta max)
: min_(std::clamp(min, TimeDelta::Zero(), kMax)),
max_(std::clamp(max, min_, kMax)) {
if (!(TimeDelta::Zero() <= min && min <= max && max <= kMax)) {
LOG_ERROR("Invalid video playout delay: [{},{}]. Clamped to [{},{}]", min,
max, this->min(), this->max());
}
}
bool VideoPlayoutDelay::Set(TimeDelta min, TimeDelta max) {
if (TimeDelta::Zero() <= min && min <= max && max <= kMax) {
min_ = min;
max_ = max;
return true;
}
return false;
}
} // namespace webrtc
+149
View File
@@ -0,0 +1,149 @@
/*
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef API_VIDEO_VIDEO_TIMING_H_
#define API_VIDEO_VIDEO_TIMING_H_
#include <stdint.h>
#include <limits>
#include <string>
#include "api/units/time_delta.h"
namespace webrtc {
// Video timing timestamps in ms counted from capture_time_ms of a frame.
// This structure represents data sent in video-timing RTP header extension.
struct VideoSendTiming {
enum TimingFrameFlags : uint8_t {
kNotTriggered = 0, // Timing info valid, but not to be transmitted.
// Used on send-side only.
kTriggeredByTimer = 1 << 0, // Frame marked for tracing by periodic timer.
kTriggeredBySize = 1 << 1, // Frame marked for tracing due to size.
kInvalid = std::numeric_limits<uint8_t>::max() // Invalid, ignore!
};
// Returns |time_ms - base_ms| capped at max 16-bit value.
// Used to fill this data structure as per
// https://webrtc.org/experiments/rtp-hdrext/video-timing/ extension stores
// 16-bit deltas of timestamps from packet capture time.
static uint16_t GetDeltaCappedMs(int64_t base_ms, int64_t time_ms);
static uint16_t GetDeltaCappedMs(TimeDelta delta);
uint16_t encode_start_delta_ms;
uint16_t encode_finish_delta_ms;
uint16_t packetization_finish_delta_ms;
uint16_t pacer_exit_delta_ms;
uint16_t network_timestamp_delta_ms;
uint16_t network2_timestamp_delta_ms;
uint8_t flags = TimingFrameFlags::kInvalid;
};
// Used to report precise timings of a 'timing frames'. Contains all important
// timestamps for a lifetime of that specific frame. Reported as a string via
// GetStats(). Only frame which took the longest between two GetStats calls is
// reported.
struct TimingFrameInfo {
TimingFrameInfo();
// Returns end-to-end delay of a frame, if sender and receiver timestamps are
// synchronized, -1 otherwise.
int64_t EndToEndDelay() const;
// Returns true if current frame took longer to process than `other` frame.
// If other frame's clocks are not synchronized, current frame is always
// preferred.
bool IsLongerThan(const TimingFrameInfo& other) const;
// Returns true if flags are set to indicate this frame was marked for tracing
// due to the size being outside some limit.
bool IsOutlier() const;
// Returns true if flags are set to indicate this frame was marked fro tracing
// due to cyclic timer.
bool IsTimerTriggered() const;
// Returns true if the timing data is marked as invalid, in which case it
// should be ignored.
bool IsInvalid() const;
std::string ToString() const;
bool operator<(const TimingFrameInfo& other) const;
bool operator<=(const TimingFrameInfo& other) const;
uint32_t rtp_timestamp; // Identifier of a frame.
// All timestamps below are in local monotonous clock of a receiver.
// If sender clock is not yet estimated, sender timestamps
// (capture_time_ms ... pacer_exit_ms) are negative values, still
// relatively correct.
int64_t capture_time_ms; // Captrue time of a frame.
int64_t encode_start_ms; // Encode start time.
int64_t encode_finish_ms; // Encode completion time.
int64_t packetization_finish_ms; // Time when frame was passed to pacer.
int64_t pacer_exit_ms; // Time when last packet was pushed out of pacer.
// Two in-network RTP processor timestamps: meaning is application specific.
int64_t network_timestamp_ms;
int64_t network2_timestamp_ms;
int64_t receive_start_ms; // First received packet time.
int64_t receive_finish_ms; // Last received packet time.
int64_t decode_start_ms; // Decode start time.
int64_t decode_finish_ms; // Decode completion time.
int64_t render_time_ms; // Proposed render time to insure smooth playback.
uint8_t flags; // Flags indicating validity and/or why tracing was triggered.
};
// Minimum and maximum playout delay values from capture to render.
// These are best effort values.
//
// min = max = 0 indicates that the receiver should try and render
// frame as soon as possible.
//
// min = x, max = y indicates that the receiver is free to adapt
// in the range (x, y) based on network jitter.
// This class ensures invariant 0 <= min <= max <= kMax.
class VideoPlayoutDelay {
public:
// Maximum supported value for the delay limit.
static constexpr TimeDelta kMax = TimeDelta::Millis(10) * 0xFFF;
// Creates delay limits that indicates receiver should try to render frame
// as soon as possible.
static VideoPlayoutDelay Minimal() {
return VideoPlayoutDelay(TimeDelta::Zero(), TimeDelta::Zero());
}
// Creates valid, but unspecified limits.
VideoPlayoutDelay() = default;
VideoPlayoutDelay(const VideoPlayoutDelay&) = default;
VideoPlayoutDelay& operator=(const VideoPlayoutDelay&) = default;
VideoPlayoutDelay(TimeDelta min, TimeDelta max);
bool Set(TimeDelta min, TimeDelta max);
TimeDelta min() const { return min_; }
TimeDelta max() const { return max_; }
friend bool operator==(const VideoPlayoutDelay& lhs,
const VideoPlayoutDelay& rhs) {
return lhs.min_ == rhs.min_ && lhs.max_ == rhs.max_;
}
private:
TimeDelta min_ = TimeDelta::Zero();
TimeDelta max_ = kMax;
};
} // namespace webrtc
#endif // API_VIDEO_VIDEO_TIMING_H_
+390
View File
@@ -0,0 +1,390 @@
/*
* @Author: DI JUNKUN
* @Date: 2024-12-18
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
*/
#ifndef _BYTE_IO_H_
#define _BYTE_IO_H_
// This file contains classes for reading and writing integer types from/to
// byte array representations. Signed/unsigned, partial (whole byte) sizes,
// and big/little endian byte order is all supported.
//
// Usage examples:
//
// uint8_t* buffer = ...;
//
// // Read an unsigned 4 byte integer in big endian format
// uint32_t val = ByteReader<uint32_t>::ReadBigEndian(buffer);
//
// // Read a signed 24-bit (3 byte) integer in little endian format
// int32_t val = ByteReader<int32_t, 3>::ReadLittle(buffer);
//
// // Write an unsigned 8 byte integer in little endian format
// ByteWriter<uint64_t>::WriteLittleEndian(buffer, val);
//
// Write an unsigned 40-bit (5 byte) integer in big endian format
// ByteWriter<uint64_t, 5>::WriteBigEndian(buffer, val);
//
// These classes are implemented as recursive templetizations, intended to make
// it easy for the compiler to completely inline the reading/writing.
#include <stdint.h>
#include <limits>
// According to ISO C standard ISO/IEC 9899, section 6.2.6.2 (2), the three
// representations of signed integers allowed are two's complement, one's
// complement and sign/magnitude. We can detect which is used by looking at
// the two last bits of -1, which will be 11 in two's complement, 10 in one's
// complement and 01 in sign/magnitude.
// TODO(sprang): In the unlikely event that we actually need to support a
// platform that doesn't use two's complement, implement conversion to/from
// wire format.
// Assume the if any one signed integer type is two's complement, then all
// other will be too.
static_assert(
(-1 & 0x03) == 0x03,
"Only two's complement representation of signed integers supported.");
// Plain const char* won't work for static_assert, use #define instead.
#define kSizeErrorMsg "Byte size must be less than or equal to data type size."
// Utility class for getting the unsigned equivalent of a signed type.
template <typename T>
struct UnsignedOf;
// Class for reading integers from a sequence of bytes.
// T = type of integer, B = bytes to read, is_signed = true if signed integer.
// If is_signed is true and B < sizeof(T), sign extension might be needed.
template <typename T, unsigned int B = sizeof(T),
bool is_signed = std::numeric_limits<T>::is_signed>
class ByteReader;
// Specialization of ByteReader for unsigned types.
template <typename T, unsigned int B>
class ByteReader<T, B, false> {
public:
static T ReadBigEndian(const uint8_t* data) {
static_assert(B <= sizeof(T), kSizeErrorMsg);
return InternalReadBigEndian(data);
}
static T ReadLittleEndian(const uint8_t* data) {
static_assert(B <= sizeof(T), kSizeErrorMsg);
return InternalReadLittleEndian(data);
}
private:
static T InternalReadBigEndian(const uint8_t* data) {
T val(0);
for (unsigned int i = 0; i < B; ++i)
val |= static_cast<T>(data[i]) << ((B - 1 - i) * 8);
return val;
}
static T InternalReadLittleEndian(const uint8_t* data) {
T val(0);
for (unsigned int i = 0; i < B; ++i)
val |= static_cast<T>(data[i]) << (i * 8);
return val;
}
};
// Specialization of ByteReader for signed types.
template <typename T, unsigned int B>
class ByteReader<T, B, true> {
public:
typedef typename UnsignedOf<T>::Type U;
static T ReadBigEndian(const uint8_t* data) {
U unsigned_val = ByteReader<T, B, false>::ReadBigEndian(data);
if (B < sizeof(T)) unsigned_val = SignExtend(unsigned_val);
return ReinterpretAsSigned(unsigned_val);
}
static T ReadLittleEndian(const uint8_t* data) {
U unsigned_val = ByteReader<T, B, false>::ReadLittleEndian(data);
if (B < sizeof(T)) unsigned_val = SignExtend(unsigned_val);
return ReinterpretAsSigned(unsigned_val);
}
private:
// As a hack to avoid implementation-specific or undefined behavior when
// bit-shifting or casting signed integers, read as a signed equivalent
// instead and convert to signed. This is safe since we have asserted that
// two's complement for is used.
static T ReinterpretAsSigned(U unsigned_val) {
// An unsigned value with only the highest order bit set (ex 0x80).
const U kUnsignedHighestBitMask = static_cast<U>(1)
<< ((sizeof(U) * 8) - 1);
// A signed value with only the highest bit set. Since this is two's
// complement form, we can use the min value from std::numeric_limits.
const T kSignedHighestBitMask = std::numeric_limits<T>::min();
T val;
if ((unsigned_val & kUnsignedHighestBitMask) != 0) {
// Casting is only safe when unsigned value can be represented in the
// signed target type, so mask out highest bit and mask it back manually.
val = static_cast<T>(unsigned_val & ~kUnsignedHighestBitMask);
val |= kSignedHighestBitMask;
} else {
val = static_cast<T>(unsigned_val);
}
return val;
}
// If number of bytes is less than native data type (eg 24 bit, in int32_t),
// and the most significant bit of the actual data is set, we must sign
// extend the remaining byte(s) with ones so that the correct negative
// number is retained.
// Ex: 0x810A0B -> 0xFF810A0B, but 0x710A0B -> 0x00710A0B
static U SignExtend(const U val) {
const uint8_t kMsb = static_cast<uint8_t>(val >> ((B - 1) * 8));
if ((kMsb & 0x80) != 0) {
// Create a mask where all bits used by the B bytes are set to one,
// for instance 0x00FFFFFF for B = 3. Bit-wise invert that mask (to
// (0xFF000000 in the example above) and add it to the input value.
// The "B % sizeof(T)" is a workaround to undefined values warnings for
// B == sizeof(T), in which case this code won't be called anyway.
const U kUsedBitsMask = (1 << ((B % sizeof(T)) * 8)) - 1;
return ~kUsedBitsMask | val;
}
return val;
}
};
// Class for writing integers to a sequence of bytes
// T = type of integer, B = bytes to write
template <typename T, unsigned int B = sizeof(T),
bool is_signed = std::numeric_limits<T>::is_signed>
class ByteWriter;
// Specialization of ByteWriter for unsigned types.
template <typename T, unsigned int B>
class ByteWriter<T, B, false> {
public:
static void WriteBigEndian(uint8_t* data, T val) {
static_assert(B <= sizeof(T), kSizeErrorMsg);
for (unsigned int i = 0; i < B; ++i) {
data[i] = val >> ((B - 1 - i) * 8);
}
}
static void WriteLittleEndian(uint8_t* data, T val) {
static_assert(B <= sizeof(T), kSizeErrorMsg);
for (unsigned int i = 0; i < B; ++i) {
data[i] = val >> (i * 8);
}
}
};
// Specialization of ByteWriter for signed types.
template <typename T, unsigned int B>
class ByteWriter<T, B, true> {
public:
typedef typename UnsignedOf<T>::Type U;
static void WriteBigEndian(uint8_t* data, T val) {
ByteWriter<U, B, false>::WriteBigEndian(data, ReinterpretAsUnsigned(val));
}
static void WriteLittleEndian(uint8_t* data, T val) {
ByteWriter<U, B, false>::WriteLittleEndian(data,
ReinterpretAsUnsigned(val));
}
private:
static U ReinterpretAsUnsigned(T val) {
// According to ISO C standard ISO/IEC 9899, section 6.3.1.3 (1, 2) a
// conversion from signed to unsigned keeps the value if the new type can
// represent it, and otherwise adds one more than the max value of T until
// the value is in range. For two's complement, this fortunately means
// that the bit-wise value will be intact. Thus, since we have asserted that
// two's complement form is actually used, a simple cast is sufficient.
return static_cast<U>(val);
}
};
// ----- Below follows specializations of UnsignedOf utility class -----
template <>
struct UnsignedOf<int8_t> {
typedef uint8_t Type;
};
template <>
struct UnsignedOf<int16_t> {
typedef uint16_t Type;
};
template <>
struct UnsignedOf<int32_t> {
typedef uint32_t Type;
};
template <>
struct UnsignedOf<int64_t> {
typedef uint64_t Type;
};
// ----- Below follows specializations for unsigned, B in { 1, 2, 4, 8 } -----
// TODO(sprang): Check if these actually help or if generic cases will be
// unrolled to and optimized to similar performance.
// Specializations for single bytes
template <typename T>
class ByteReader<T, 1, false> {
public:
static T ReadBigEndian(const uint8_t* data) {
static_assert(sizeof(T) == 1, kSizeErrorMsg);
return data[0];
}
static T ReadLittleEndian(const uint8_t* data) {
static_assert(sizeof(T) == 1, kSizeErrorMsg);
return data[0];
}
};
template <typename T>
class ByteWriter<T, 1, false> {
public:
static void WriteBigEndian(uint8_t* data, T val) {
static_assert(sizeof(T) == 1, kSizeErrorMsg);
data[0] = val;
}
static void WriteLittleEndian(uint8_t* data, T val) {
static_assert(sizeof(T) == 1, kSizeErrorMsg);
data[0] = val;
}
};
// Specializations for two byte words
template <typename T>
class ByteReader<T, 2, false> {
public:
static T ReadBigEndian(const uint8_t* data) {
static_assert(sizeof(T) >= 2, kSizeErrorMsg);
return (data[0] << 8) | data[1];
}
static T ReadLittleEndian(const uint8_t* data) {
static_assert(sizeof(T) >= 2, kSizeErrorMsg);
return data[0] | (data[1] << 8);
}
};
template <typename T>
class ByteWriter<T, 2, false> {
public:
static void WriteBigEndian(uint8_t* data, T val) {
static_assert(sizeof(T) >= 2, kSizeErrorMsg);
data[0] = val >> 8;
data[1] = val;
}
static void WriteLittleEndian(uint8_t* data, T val) {
static_assert(sizeof(T) >= 2, kSizeErrorMsg);
data[0] = val;
data[1] = val >> 8;
}
};
// Specializations for four byte words.
template <typename T>
class ByteReader<T, 4, false> {
public:
static T ReadBigEndian(const uint8_t* data) {
static_assert(sizeof(T) >= 4, kSizeErrorMsg);
return (Get(data, 0) << 24) | (Get(data, 1) << 16) | (Get(data, 2) << 8) |
Get(data, 3);
}
static T ReadLittleEndian(const uint8_t* data) {
static_assert(sizeof(T) >= 4, kSizeErrorMsg);
return Get(data, 0) | (Get(data, 1) << 8) | (Get(data, 2) << 16) |
(Get(data, 3) << 24);
}
private:
inline static T Get(const uint8_t* data, unsigned int index) {
return static_cast<T>(data[index]);
}
};
// Specializations for four byte words.
template <typename T>
class ByteWriter<T, 4, false> {
public:
static void WriteBigEndian(uint8_t* data, T val) {
static_assert(sizeof(T) >= 4, kSizeErrorMsg);
data[0] = val >> 24;
data[1] = val >> 16;
data[2] = val >> 8;
data[3] = val;
}
static void WriteLittleEndian(uint8_t* data, T val) {
static_assert(sizeof(T) >= 4, kSizeErrorMsg);
data[0] = val;
data[1] = val >> 8;
data[2] = val >> 16;
data[3] = val >> 24;
}
};
// Specializations for eight byte words.
template <typename T>
class ByteReader<T, 8, false> {
public:
static T ReadBigEndian(const uint8_t* data) {
static_assert(sizeof(T) >= 8, kSizeErrorMsg);
return (Get(data, 0) << 56) | (Get(data, 1) << 48) | (Get(data, 2) << 40) |
(Get(data, 3) << 32) | (Get(data, 4) << 24) | (Get(data, 5) << 16) |
(Get(data, 6) << 8) | Get(data, 7);
}
static T ReadLittleEndian(const uint8_t* data) {
static_assert(sizeof(T) >= 8, kSizeErrorMsg);
return Get(data, 0) | (Get(data, 1) << 8) | (Get(data, 2) << 16) |
(Get(data, 3) << 24) | (Get(data, 4) << 32) | (Get(data, 5) << 40) |
(Get(data, 6) << 48) | (Get(data, 7) << 56);
}
private:
inline static T Get(const uint8_t* data, unsigned int index) {
return static_cast<T>(data[index]);
}
};
template <typename T>
class ByteWriter<T, 8, false> {
public:
static void WriteBigEndian(uint8_t* data, T val) {
static_assert(sizeof(T) >= 8, kSizeErrorMsg);
data[0] = val >> 56;
data[1] = val >> 48;
data[2] = val >> 40;
data[3] = val >> 32;
data[4] = val >> 24;
data[5] = val >> 16;
data[6] = val >> 8;
data[7] = val;
}
static void WriteLittleEndian(uint8_t* data, T val) {
static_assert(sizeof(T) >= 8, kSizeErrorMsg);
data[0] = val;
data[1] = val >> 8;
data[2] = val >> 16;
data[3] = val >> 24;
data[4] = val >> 32;
data[5] = val >> 40;
data[6] = val >> 48;
data[7] = val >> 56;
}
};
#endif
+140
View File
@@ -0,0 +1,140 @@
#include "system_clock.h"
#include <time.h>
#include <cstdint>
#include <limits>
#if defined(__POSIX__)
#include <sys/time.h>
#endif
#if defined(__APPLE__)
#include <mach/mach_time.h>
#endif
#if defined(_WIN32)
#include <windows.h>
#endif
int64_t SystemClock::ConvertToNtpTime(int64_t time_us) {
constexpr int64_t kMicrosecondsPerSecond = 1000000;
constexpr uint64_t kNtpFractionalUnit = 0x100000000; // 2^32
uint32_t seconds = static_cast<uint32_t>(time_us / kMicrosecondsPerSecond);
uint32_t fractions =
static_cast<uint32_t>((time_us % kMicrosecondsPerSecond) *
kNtpFractionalUnit / kMicrosecondsPerSecond);
return seconds * kNtpFractionalUnit + fractions;
}
int64_t SystemClock::CurrentTimeNs() {
int64_t ticks = -1; // Default to error case
#if defined(__APPLE__)
static mach_timebase_info_data_t timebase;
if (timebase.denom == 0 && mach_timebase_info(&timebase) != KERN_SUCCESS) {
return -1; // Error case for macOS timebase info retrieval
}
ticks = static_cast<int64_t>(mach_absolute_time() * timebase.numer) /
timebase.denom;
#elif defined(__POSIX__)
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
return -1; // Error case for POSIX clock retrieval
}
ticks = static_cast<int64_t>(ts.tv_sec) * kNumNanosecsPerSec +
static_cast<int64_t>(ts.tv_nsec);
#elif defined(_WIN32)
static volatile LONG last_timegettime = 0;
static volatile int64_t num_wrap_timegettime = 0;
volatile LONG* last_timegettime_ptr = &last_timegettime;
DWORD now = timeGetTime();
DWORD old = InterlockedExchange(last_timegettime_ptr, now);
if (now < old) {
// Handle wraparound (when timeGetTime() wraps around after ~49.7 days)
if (old > 0xf0000000 && now < 0x0fffffff) {
num_wrap_timegettime++;
}
}
// Convert milliseconds to nanoseconds and add wraparound offset
ticks = static_cast<int64_t>(now) + (num_wrap_timegettime << 32);
ticks *= 1000000;
#endif
return ticks;
}
int64_t SystemClock::CurrentTime() { return CurrentTimeNs() / 1000LL; }
int64_t SystemClock::CurrentTimeUs() { return CurrentTimeNs() / 1000LL; }
int64_t SystemClock::CurrentTimeMs() { return CurrentTimeNs() / 1000000LL; }
int64_t SystemClock::CurrentNtpTime() {
return ConvertToNtpTime(CurrentTimeNs());
}
int64_t SystemClock::CurrentNtpTimeMs() {
int64_t ntp_ts = ConvertToNtpTime(CurrentTimeNs());
uint32_t seconds = static_cast<uint32_t>(ntp_ts / 1000000000);
uint32_t fractions = static_cast<uint32_t>(ntp_ts % 1000000000);
static constexpr double kNtpFracPerMs = 4.294967296E6; // 2^32 / 1000.
const double frac_ms = static_cast<double>(fractions) / kNtpFracPerMs;
return 1000 * static_cast<int64_t>(seconds) +
static_cast<int64_t>(frac_ms + 0.5);
}
int64_t SystemClock::CurrentUtcTimeNs() {
#if defined(__POSIX__)
struct timeval time;
gettimeofday(&time, nullptr);
return (static_cast<int64_t>(time.tv_sec) * 1000000000 + time.tv_usec * 1000);
#elif defined(_WIN32)
FILETIME file_time;
GetSystemTimeAsFileTime(&file_time);
int64_t file_time_100ns =
((int64_t)file_time.dwHighDateTime << 32) | file_time.dwLowDateTime;
constexpr int64_t kUnixEpochFileTimeOffsetIn100ns = 116444736000000000LL;
return (file_time_100ns - kUnixEpochFileTimeOffsetIn100ns) * 100;
#elif defined(__APPLE__)
struct timespec ts;
if (clock_gettime(CLOCK_REALTIME, &ts) != 0) {
return -1; // Error case for macOS clock retrieval
}
return static_cast<int64_t>(ts.tv_sec) * 1000000000LL + ts.tv_nsec;
#endif
return 0;
}
int64_t SystemClock::CurrentUtcTimeUs() { return CurrentUtcTimeNs() / 1000LL; }
int64_t SystemClock::CurrentUtcTimeMs() {
return CurrentUtcTimeNs() / 1000000LL;
}
int64_t SystemClock::CurrentUtcTime() {
return CurrentUtcTimeNs() / 1000000000LL;
}
int64_t SystemClock::NtpToUtc(int64_t ntp_time) {
constexpr int64_t kNtpEpochOffset =
2208988800LL; // NTP epoch starts at 1900-01-01, Unix epoch starts at
// 1970-01-01
constexpr int64_t kMicrosecondsPerSecond = 1000000;
constexpr uint64_t kNtpFractionalUnit = 0x100000000; // 2^32
uint32_t seconds = static_cast<uint32_t>(ntp_time / kNtpFractionalUnit);
uint32_t fractions = static_cast<uint32_t>(ntp_time % kNtpFractionalUnit);
int64_t unix_seconds = static_cast<int64_t>(seconds) - kNtpEpochOffset;
int64_t microseconds =
(static_cast<int64_t>(fractions) * kMicrosecondsPerSecond) /
kNtpFractionalUnit;
return unix_seconds * kMicrosecondsPerSecond + microseconds;
}
+40
View File
@@ -0,0 +1,40 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-02-19
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _SYSTEM_CLOCK_H_
#define _SYSTEM_CLOCK_H_
#include <cstdint>
#include <memory>
static const int64_t kNtpEpochOffset = 2208988800LL;
class SystemClock {
public:
SystemClock() = default;
~SystemClock() = default;
int64_t CurrentTime();
int64_t CurrentTimeUs();
int64_t CurrentTimeMs();
int64_t CurrentTimeNs();
int64_t CurrentNtpTime();
int64_t CurrentNtpTimeMs();
int64_t CurrentUtcTime();
int64_t CurrentUtcTimeMs();
int64_t CurrentUtcTimeUs();
int64_t CurrentUtcTimeNs();
int64_t ConvertToNtpTime(int64_t time_us);
int64_t NtpToUtc(int64_t ntp_time);
int64_t CurrentNtpInMilliseconds() { return CurrentNtpTimeMs(); }
};
#endif
+6
View File
@@ -0,0 +1,6 @@
#include "common.h"
int CommonDummy()
{
return 0;
}
+88
View File
@@ -0,0 +1,88 @@
#ifndef _COMMON_H_
#define _COMMON_H_
#include <iostream>
#include <mutex>
#include <random>
#include <unordered_set>
int CommonDummy();
constexpr size_t HASH_STRING_PIECE(const char *string_piece) {
std::size_t result = 0;
while (*string_piece) {
result = (result * 131) + *string_piece++;
}
return result;
}
constexpr size_t operator"" _H(const char *string_piece, size_t) {
return HASH_STRING_PIECE(string_piece);
}
inline const std::string GetIceUsername(const std::string &sdp) {
std::string result = "";
std::string start = "ice-ufrag:";
std::string end = "\r\n";
size_t startPos = sdp.find(start);
size_t endPos = sdp.find(end);
if (startPos != std::string::npos && endPos != std::string::npos) {
result = sdp.substr(startPos + start.length(),
endPos - startPos - start.length());
}
return result;
}
// SSRCManager is used to manage the SSRCs that have been used.
class SSRCManager {
public:
static SSRCManager &Instance() {
static SSRCManager instance;
return instance;
}
void AddSsrc(uint32_t ssrc) {
std::lock_guard<std::mutex> lock(mutex_);
ssrcs_.insert(ssrc);
}
void DeleteSsrc(uint32_t ssrc) {
std::lock_guard<std::mutex> lock(mutex_);
ssrcs_.erase(ssrc);
}
bool Contains(uint32_t ssrc) {
std::lock_guard<std::mutex> lock(mutex_);
return ssrcs_.count(ssrc) > 0;
}
private:
SSRCManager() = default;
~SSRCManager() = default;
SSRCManager(const SSRCManager &) = delete;
SSRCManager &operator=(const SSRCManager &) = delete;
std::unordered_set<uint32_t> ssrcs_;
std::mutex mutex_;
};
inline uint32_t GenerateRandomSSRC() {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<uint32_t> dis(1, 0xFFFFFFFF);
return dis(gen);
}
inline uint32_t GenerateUniqueSsrc() {
uint32_t new_ssrc;
do {
new_ssrc = GenerateRandomSSRC();
} while (SSRCManager::Instance().Contains(new_ssrc));
SSRCManager::Instance().AddSsrc(new_ssrc);
return new_ssrc;
}
#endif
+58
View File
@@ -0,0 +1,58 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-01-22
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _COPY_ON_WRITE_BUFFER_H_
#define _COPY_ON_WRITE_BUFFER_H_
#include <memory>
#include <vector>
class CopyOnWriteBuffer {
public:
CopyOnWriteBuffer() = default;
CopyOnWriteBuffer(size_t size) {
buffer_ = std::make_shared<std::vector<uint8_t>>(size);
}
CopyOnWriteBuffer(const uint8_t* data, size_t size) {
buffer_ = std::make_shared<std::vector<uint8_t>>(data, data + size);
}
CopyOnWriteBuffer(const CopyOnWriteBuffer& other) = default;
CopyOnWriteBuffer(CopyOnWriteBuffer&& other) noexcept = default;
CopyOnWriteBuffer& operator=(const CopyOnWriteBuffer& other) = default;
CopyOnWriteBuffer& operator=(CopyOnWriteBuffer&& other) noexcept = default;
void SetData(const uint8_t* data, size_t size) {
buffer_ = std::make_shared<std::vector<uint8_t>>(data, data + size);
}
void InsertDataAt(size_t offset, const uint8_t* data, size_t size) {
EnsureUnique();
buffer_->insert(buffer_->begin() + offset, data, data + size);
}
const uint8_t* data() const { return buffer_ ? buffer_->data() : nullptr; }
size_t size() const { return buffer_ ? buffer_->size() : 0; }
uint8_t& operator[](size_t index) {
EnsureUnique();
return (*buffer_)[index];
}
const uint8_t& operator[](size_t index) const { return (*buffer_)[index]; }
private:
void EnsureUnique() {
if (buffer_.use_count() != 1) {
buffer_ = std::make_shared<std::vector<uint8_t>>(*buffer_);
}
}
std::shared_ptr<std::vector<uint8_t>> buffer_;
};
#endif
-46
View File
@@ -1,46 +0,0 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-05-15
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _DISPLAY_INFO_H_
#define _DISPLAY_INFO_H_
#include <string>
namespace crossdesk {
class DisplayInfo {
public:
DisplayInfo(std::string name, int left, int top, int right, int bottom)
: name(name), left(left), top(top), right(right), bottom(bottom) {
width = right - left;
height = bottom - top;
}
DisplayInfo(void* handle, std::string name, bool is_primary, int left,
int top, int right, int bottom)
: handle(handle),
name(name),
is_primary(is_primary),
left(left),
top(top),
right(right),
bottom(bottom) {
width = right - left;
height = bottom - top;
}
~DisplayInfo() {}
void* handle = nullptr;
std::string name = "";
bool is_primary = false;
int left = 0;
int top = 0;
int right = 0;
int bottom = 0;
int width = 0;
int height = 0;
};
} // namespace crossdesk
#endif
-21
View File
@@ -1,21 +0,0 @@
#ifndef CROSSDESK_COMMON_FILESYSTEM_UTF8_H_
#define CROSSDESK_COMMON_FILESYSTEM_UTF8_H_
#include <filesystem>
#include <string>
#include <string_view>
namespace crossdesk {
inline std::filesystem::path PathFromUtf8(std::string_view value) {
#if defined(__cpp_char8_t)
const auto *begin = reinterpret_cast<const char8_t *>(value.data());
return std::filesystem::path(std::u8string(begin, begin + value.size()));
#else
return std::filesystem::u8path(value.begin(), value.end());
#endif
}
} // namespace crossdesk
#endif // CROSSDESK_COMMON_FILESYSTEM_UTF8_H_
+65
View File
@@ -0,0 +1,65 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-03-12
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _INLINED_VECTOR_H_
#define _INLINED_VECTOR_H_
#include <array>
#include <initializer_list>
#include <iostream>
#include <vector>
template <typename T, size_t N>
class InlinedVector {
public:
InlinedVector() : size_(0), use_heap_(false) {}
void push_back(const T& value) {
if (!use_heap_ && size_ < N) {
stack_data_[size_] = value;
} else {
if (!use_heap_) {
heap_data_.reserve(N * 2);
for (size_t i = 0; i < size_; ++i) {
heap_data_.push_back(stack_data_[i]);
}
use_heap_ = true;
}
heap_data_.push_back(value);
}
++size_;
}
void assign(size_t n, const T& value) {
clear();
for (size_t i = 0; i < n; ++i) {
push_back(value);
}
}
size_t size() const { return size_; }
T& operator[](size_t index) {
return use_heap_ ? heap_data_[index] : stack_data_[index];
}
const T& operator[](size_t index) const {
return use_heap_ ? heap_data_[index] : stack_data_[index];
}
private:
void clear() {
size_ = 0;
use_heap_ = false;
heap_data_.clear();
}
size_t size_;
bool use_heap_;
std::array<T, N> stack_data_;
std::vector<T> heap_data_;
};
#endif // _INLINED_VECTOR_H_
+36
View File
@@ -0,0 +1,36 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-01-14
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _LIMITS_BASE_H_
#define _LIMITS_BASE_H_
#include <limits>
template <typename T>
bool IsInfinite(const T& value) {
return value == std::numeric_limits<T>::min() ||
value == std::numeric_limits<T>::max();
}
template <typename T>
bool IsFinite(const T& value) {
return !IsInfinite(value);
}
template <typename T>
bool IsPlusFinite(const T& value) {
return value == std::numeric_limits<T>::max();
}
template <typename T>
bool IsMinusFinite(const T& value) {
return value == std::numeric_limits<T>::min();
}
#define INT64_T_MAX std::numeric_limits<int64_t>::max()
#define INT64_T_MIN std::numeric_limits<int64_t>::min()
#endif
+130
View File
@@ -0,0 +1,130 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-01-08
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _MOD_OPS_H_
#define _MOD_OPS_H_
#include <algorithm>
#include <type_traits>
template <unsigned long M> // NOLINT
inline unsigned long Add(unsigned long a, unsigned long b) { // NOLINT
// RTC_DCHECK_LT(a, M);
unsigned long t = M - b % M; // NOLINT
unsigned long res = a - t; // NOLINT
if (t > a) return res + M;
return res;
}
template <unsigned long M> // NOLINT
inline unsigned long Subtract(unsigned long a, unsigned long b) { // NOLINT
// RTC_DCHECK_LT(a, M);
unsigned long sub = b % M; // NOLINT
if (a < sub) return M - (sub - a);
return a - sub;
}
// Calculates the forward difference between two wrapping numbers.
//
// Example:
// uint8_t x = 253;
// uint8_t y = 2;
//
// ForwardDiff(x, y) == 5
//
// 252 253 254 255 0 1 2 3
// #################################################
// | | x | | | | | y | |
// #################################################
// |----->----->----->----->----->
//
// ForwardDiff(y, x) == 251
//
// 252 253 254 255 0 1 2 3
// #################################################
// | | x | | | | | y | |
// #################################################
// -->-----> |----->---
//
// If M > 0 then wrapping occurs at M, if M == 0 then wrapping occurs at the
// largest value representable by T.
template <typename T, T M>
inline typename std::enable_if<(M > 0), T>::type ForwardDiff(T a, T b) {
static_assert(std::is_unsigned<T>::value,
"Type must be an unsigned integer.");
// RTC_DCHECK_LT(a, M);
// RTC_DCHECK_LT(b, M);
return a <= b ? b - a : M - (a - b);
}
template <typename T, T M>
inline typename std::enable_if<(M == 0), T>::type ForwardDiff(T a, T b) {
static_assert(std::is_unsigned<T>::value,
"Type must be an unsigned integer.");
return b - a;
}
template <typename T>
inline T ForwardDiff(T a, T b) {
return ForwardDiff<T, 0>(a, b);
}
// Calculates the reverse difference between two wrapping numbers.
//
// Example:
// uint8_t x = 253;
// uint8_t y = 2;
//
// ReverseDiff(y, x) == 5
//
// 252 253 254 255 0 1 2 3
// #################################################
// | | x | | | | | y | |
// #################################################
// <-----<-----<-----<-----<-----|
//
// ReverseDiff(x, y) == 251
//
// 252 253 254 255 0 1 2 3
// #################################################
// | | x | | | | | y | |
// #################################################
// ---<-----| |<-----<--
//
// If M > 0 then wrapping occurs at M, if M == 0 then wrapping occurs at the
// largest value representable by T.
template <typename T, T M>
inline typename std::enable_if<(M > 0), T>::type ReverseDiff(T a, T b) {
static_assert(std::is_unsigned<T>::value,
"Type must be an unsigned integer.");
// RTC_DCHECK_LT(a, M);
// RTC_DCHECK_LT(b, M);
return b <= a ? a - b : M - (b - a);
}
template <typename T, T M>
inline typename std::enable_if<(M == 0), T>::type ReverseDiff(T a, T b) {
static_assert(std::is_unsigned<T>::value,
"Type must be an unsigned integer.");
return a - b;
}
template <typename T>
inline T ReverseDiff(T a, T b) {
return ReverseDiff<T, 0>(a, b);
}
// Calculates the minimum distance between to wrapping numbers.
//
// The minimum distance is defined as min(ForwardDiff(a, b), ReverseDiff(a, b))
template <typename T, T M = 0>
inline T MinDiff(T a, T b) {
static_assert(std::is_unsigned<T>::value,
"Type must be an unsigned integer.");
return (std::min)(ForwardDiff<T, M>(a, b), ReverseDiff<T, M>(a, b));
}
#endif
+61
View File
@@ -0,0 +1,61 @@
/*
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef MODULES_INCLUDE_MODULE_COMMON_TYPES_PUBLIC_H_
#define MODULES_INCLUDE_MODULE_COMMON_TYPES_PUBLIC_H_
#include <limits>
#include <optional>
namespace webrtc {
template <typename U>
inline bool IsNewer(U value, U prev_value) {
static_assert(!std::numeric_limits<U>::is_signed, "U must be unsigned");
// kBreakpoint is the half-way mark for the type U. For instance, for a
// uint16_t it will be 0x8000, and for a uint32_t, it will be 0x8000000.
constexpr U kBreakpoint = (std::numeric_limits<U>::max() >> 1) + 1;
// Distinguish between elements that are exactly kBreakpoint apart.
// If t1>t2 and |t1-t2| = kBreakpoint: IsNewer(t1,t2)=true,
// IsNewer(t2,t1)=false
// rather than having IsNewer(t1,t2) = IsNewer(t2,t1) = false.
if (value - prev_value == kBreakpoint) {
return value > prev_value;
}
return value != prev_value &&
static_cast<U>(value - prev_value) < kBreakpoint;
}
// NB: Doesn't fulfill strict weak ordering requirements.
// Mustn't be used as std::map Compare function.
inline bool IsNewerSequenceNumber(uint16_t sequence_number,
uint16_t prev_sequence_number) {
return IsNewer(sequence_number, prev_sequence_number);
}
// NB: Doesn't fulfill strict weak ordering requirements.
// Mustn't be used as std::map Compare function.
inline bool IsNewerTimestamp(uint32_t timestamp, uint32_t prev_timestamp) {
return IsNewer(timestamp, prev_timestamp);
}
inline uint16_t LatestSequenceNumber(uint16_t sequence_number1,
uint16_t sequence_number2) {
return IsNewerSequenceNumber(sequence_number1, sequence_number2)
? sequence_number1
: sequence_number2;
}
inline uint32_t LatestTimestamp(uint32_t timestamp1, uint32_t timestamp2) {
return IsNewerTimestamp(timestamp1, timestamp2) ? timestamp1 : timestamp2;
}
} // namespace webrtc
#endif // MODULES_INCLUDE_MODULE_COMMON_TYPES_PUBLIC_H_
-152
View File
@@ -1,152 +0,0 @@
#include "platform.h"
#include <cstdlib>
#include <cstring>
#include "rd_log.h"
#ifdef _WIN32
#include <Winsock2.h>
#include <iphlpapi.h>
#elif __APPLE__
#include <ifaddrs.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <sys/socket.h>
#include <sys/types.h>
#elif __linux__
#include <fcntl.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#endif
namespace crossdesk {
std::string GetMac() {
char mac_addr[16];
int len = 0;
#ifdef _WIN32
IP_ADAPTER_INFO adapterInfo[16];
DWORD bufferSize = sizeof(adapterInfo);
DWORD result = GetAdaptersInfo(adapterInfo, &bufferSize);
if (result == ERROR_SUCCESS) {
PIP_ADAPTER_INFO adapter = adapterInfo;
while (adapter) {
for (UINT i = 0; i < adapter->AddressLength; i++) {
len += sprintf_s(mac_addr + len, sizeof(mac_addr) - len, "%.2X",
adapter->Address[i]);
}
break;
}
}
#elif __APPLE__
std::string if_name = "en0";
struct ifaddrs* addrs;
struct ifaddrs* cursor;
const struct sockaddr_dl* dlAddr;
if (!getifaddrs(&addrs)) {
cursor = addrs;
while (cursor != 0) {
const struct sockaddr_dl* socAddr =
(const struct sockaddr_dl*)cursor->ifa_addr;
if ((cursor->ifa_addr->sa_family == AF_LINK) &&
(socAddr->sdl_type == IFT_ETHER) &&
strcmp(if_name.c_str(), cursor->ifa_name) == 0) {
dlAddr = (const struct sockaddr_dl*)cursor->ifa_addr;
const unsigned char* base =
(const unsigned char*)&dlAddr->sdl_data[dlAddr->sdl_nlen];
for (int i = 0; i < dlAddr->sdl_alen; i++) {
len +=
snprintf(mac_addr + len, sizeof(mac_addr) - len, "%.2X", base[i]);
}
}
cursor = cursor->ifa_next;
}
freeifaddrs(addrs);
}
#elif __linux__
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
return "";
}
struct ifreq ifr;
struct ifconf ifc;
char buf[1024];
ifc.ifc_len = sizeof(buf);
ifc.ifc_buf = buf;
if (ioctl(sock, SIOCGIFCONF, &ifc) < 0) {
close(sock);
return "";
}
struct ifreq* it = ifc.ifc_req;
const struct ifreq* const end = it + (ifc.ifc_len / sizeof(struct ifreq));
for (; it != end; ++it) {
std::strcpy(ifr.ifr_name, it->ifr_name);
if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) {
continue;
}
if (ifr.ifr_flags & IFF_LOOPBACK) {
continue;
}
if (ioctl(sock, SIOCGIFHWADDR, &ifr) < 0) {
continue;
}
std::string mac_address;
for (int i = 0; i < 6; ++i) {
len += sprintf(mac_addr + len, "%.2X", ifr.ifr_hwaddr.sa_data[i] & 0xff);
}
break;
}
close(sock);
#endif
return mac_addr;
}
std::string GetHostName() {
char hostname[256];
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
LOG_ERROR("WSAStartup failed");
return "";
}
if (gethostname(hostname, sizeof(hostname)) == SOCKET_ERROR) {
LOG_ERROR("gethostname failed: {}", WSAGetLastError());
WSACleanup();
return "";
}
WSACleanup();
#else
if (gethostname(hostname, sizeof(hostname)) == -1) {
LOG_ERROR("gethostname failed");
return "";
}
#endif
return hostname;
}
bool IsWaylandSession() {
#if defined(__linux__) && !defined(__APPLE__)
const char* session_type = std::getenv("XDG_SESSION_TYPE");
if (session_type) {
if (std::strcmp(session_type, "wayland") == 0 ||
std::strcmp(session_type, "Wayland") == 0) {
return true;
}
if (std::strcmp(session_type, "x11") == 0 ||
std::strcmp(session_type, "X11") == 0) {
return false;
}
}
const char* wayland_display = std::getenv("WAYLAND_DISPLAY");
return wayland_display && wayland_display[0] != '\0';
#else
return false;
#endif
}
} // namespace crossdesk
-19
View File
@@ -1,19 +0,0 @@
/*
* @Author: DI JUNKUN
* @Date: 2023-12-18
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _PLATFORM_H_
#define _PLATFORM_H_
#include <iostream>
namespace crossdesk {
std::string GetMac();
std::string GetHostName();
bool IsWaylandSession();
} // namespace crossdesk
#endif
-63
View File
@@ -1,63 +0,0 @@
#include "rounded_corner_button.h"
namespace crossdesk {
bool RoundedCornerButton(const char* label, const ImVec2& size, float rounding,
ImDrawFlags round_flags, bool enabled,
ImU32 normal_col, ImU32 hover_col, ImU32 active_col,
ImU32 border_col) {
ImGuiWindow* current_window = ImGui::GetCurrentWindow();
if (current_window->SkipItems) return false;
const ImGuiStyle& style = ImGui::GetStyle();
ImGuiID button_id = current_window->GetID(label);
ImVec2 cursor_pos = current_window->DC.CursorPos;
ImVec2 button_size = ImGui::CalcItemSize(size, 0.0f, 0.0f);
ImRect button_rect(cursor_pos, ImVec2(cursor_pos.x + button_size.x,
cursor_pos.y + button_size.y));
ImGui::ItemSize(button_rect);
if (!ImGui::ItemAdd(button_rect, button_id)) return false;
bool is_hovered = false, is_held = false;
bool is_pressed = false;
if (enabled) {
is_pressed =
ImGui::ButtonBehavior(button_rect, button_id, &is_hovered, &is_held);
}
if (normal_col == 0) normal_col = ImGui::GetColorU32(ImGuiCol_Button);
if (hover_col == 0) hover_col = ImGui::GetColorU32(ImGuiCol_ButtonHovered);
if (active_col == 0) active_col = ImGui::GetColorU32(ImGuiCol_ButtonActive);
if (border_col == 0) border_col = ImGui::GetColorU32(ImGuiCol_Border);
ImU32 fill_color = normal_col;
if (is_held && is_hovered)
fill_color = active_col;
else if (is_hovered)
fill_color = hover_col;
if (!enabled) fill_color = IM_COL32(120, 120, 120, 180);
ImDrawList* window_draw_list = ImGui::GetWindowDrawList();
window_draw_list->AddRectFilled(button_rect.Min, button_rect.Max, fill_color,
rounding, round_flags);
if (style.FrameBorderSize > 0.0f) {
window_draw_list->AddRect(button_rect.Min, button_rect.Max, border_col,
rounding, round_flags, style.FrameBorderSize);
}
ImU32 text_color =
ImGui::GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled);
const char* label_end = ImGui::FindRenderedTextEnd(label);
ImGui::PushStyleColor(ImGuiCol_Text,
ImGui::ColorConvertU32ToFloat4(text_color));
ImGui::RenderTextClipped(button_rect.Min, button_rect.Max, label, label_end,
nullptr, ImVec2(0.5f, 0.5f), &button_rect);
ImGui::PopStyleColor();
return is_pressed;
}
} // namespace crossdesk
-20
View File
@@ -1,20 +0,0 @@
/*
* @Author: DI JUNKUN
* @Date: 2026-02-26
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _ROUNDED_CORNER_BUTTON_H_
#define _ROUNDED_CORNER_BUTTON_H_
#include "imgui.h"
#include "imgui_internal.h"
namespace crossdesk {
bool RoundedCornerButton(const char* label, const ImVec2& size, float rounding,
ImDrawFlags round_flags, bool enabled = true,
ImU32 normal_col = 0, ImU32 hover_col = 0,
ImU32 active_col = 0, ImU32 border_col = 0);
} // namespace crossdesk
#endif
+32
View File
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef RTC_BASE_ARRAYSIZE_H_
#define RTC_BASE_ARRAYSIZE_H_
#include <stddef.h>
// This file defines the arraysize() macro and is derived from Chromium's
// base/macros.h.
// The arraysize(arr) macro returns the # of elements in an array arr.
// The expression is a compile-time constant, and therefore can be
// used in defining new arrays, for example. If you use arraysize on
// a pointer by mistake, you will get a compile-time error.
// This template function declaration is used in defining arraysize.
// Note that the function doesn't need an implementation, as we only
// use its type.
template <typename T, size_t N>
char (&ArraySizeHelper(T (&array)[N]))[N];
#define arraysize(array) (sizeof(ArraySizeHelper(array)))
#endif // RTC_BASE_ARRAYSIZE_H_

Some files were not shown because too many files have changed in this diff Show More