diff --git a/docs/examples/srtp_user.cc b/docs/examples/srtp_user.cc index 0ed2000..edfc8ca 100644 --- a/docs/examples/srtp_user.cc +++ b/docs/examples/srtp_user.cc @@ -1,21 +1,47 @@ #include #include -#define PAYLOAD_MAXLEN 256 -#define KEY_SIZE 16 -#define SALT_SIZE 14 -/* Key and salt for the SRTP session of sender and receiver - * - * NOTE: uvgRTP only supports 128 bit keys and 112 bit salts */ -uint8_t key[KEY_SIZE] = { 0 }; -uint8_t salt[SALT_SIZE] = { 0 }; +// This example includes both sending and receiving SRTP streams. +// Normally these are done on separate machines -void thread_func(void) +constexpr char SENDER_ADDRESS[] = "127.0.0.1"; +constexpr uint16_t LOCAL_PORT = 8888; + +constexpr char RECEIVER_ADDRESS[] = "127.0.0.1"; +constexpr uint16_t REMOTE_PORT = 8890; + +enum Key_length{SRTP_128 = 128, SRTP_196 = 196, SRTP_256 = 256}; + +constexpr Key_length KEY_SIZE = SRTP_256; +constexpr int KEY_SIZE_BYTES = KEY_SIZE/8; + +constexpr int SALT_SIZE = 112; +constexpr int SALT_SIZE_BYTES = SALT_SIZE/8; + +constexpr auto EXAMPLE_DURATION = std::chrono::milliseconds(10000); + +constexpr int FRAME_RATE = 30; // fps +constexpr int SEND_TEST_PACKETS = (EXAMPLE_DURATION.count() - 1000)/1000*FRAME_RATE; +constexpr int PACKET_INTERVAL_MS = 1000/FRAME_RATE; +constexpr int RECEIVER_WAIT_TIME_MS = 100; + +void process_frame(uvgrtp::frame::rtp_frame *frame) +{ + std::string payload = std::string((char*)frame->payload, frame->payload_len); + + std::cout << "Received SRTP frame. Payload: " << payload << std::endl; + + /* When we receive a frame, the ownership of the frame belongs to us and + * when we're done with it, we need to deallocate the frame */ + (void)uvgrtp::frame::dealloc_frame(frame); +} + +void receive_func(uint8_t key[KEY_SIZE_BYTES], uint8_t salt[SALT_SIZE_BYTES]) { /* See sending.cc for more details */ uvgrtp::context ctx; - uvgrtp::session *sess = ctx.create_session("127.0.0.1"); + uvgrtp::session *receiver_session = ctx.create_session(SENDER_ADDRESS); /* Enable SRTP and let user manage keys */ unsigned flags = RCE_SRTP | RCE_SRTP_KMNGMNT_USER; @@ -24,63 +50,131 @@ void thread_func(void) * * If 192- or 256-bit key size is specified in the flags, add_srtp_ctx() expects * the key paramter to be 24 or 32 bytes long, respectively. */ - if (0) - flags |= RCE_SRTP_KEYSIZE_192; + flags |= RCE_SRTP_KEYSIZE_256; /* See sending.cc for more details about create_stream() */ - uvgrtp::media_stream *recv = sess->create_stream(8889, 8888, RTP_FORMAT_GENERIC, flags); + uvgrtp::media_stream *recv = receiver_session->create_stream(REMOTE_PORT, LOCAL_PORT, + RTP_FORMAT_GENERIC, flags); - /* Before anything else can be done, - * add_srtp_ctx() must be called with the SRTP key and salt. - * - * All calls to "recv" that try to modify and or/use the newly - * created media stream before calling add_srtp_ctx() will fail */ - recv->add_srtp_ctx(key, salt); + // Receive frames by pulling for EXAMPLE_DURATION milliseconds + if (recv) + { + /* Before anything else can be done, + * add_srtp_ctx() must be called with the SRTP key and salt. + * + * All calls to "recv" that try to modify and or/use the newly + * created media stream before calling add_srtp_ctx() will fail */ + recv->add_srtp_ctx(key, salt); - for (;;) { - auto frame = recv->pull_frame(); - fprintf(stderr, "Message: '%s'\n", frame->payload); + std::cout << "Start receiving frames for " << EXAMPLE_DURATION.count() << " ms" << std::endl; + auto start = std::chrono::steady_clock::now(); - /* the frame must be destroyed manually */ - (void)uvgrtp::frame::dealloc_frame(frame); + uvgrtp::frame::rtp_frame *frame = nullptr; + while (std::chrono::steady_clock::now() - start < EXAMPLE_DURATION) + { + /* You can specify a timeout for the operation and if the a frame is not received + * within that time limit, pull_frame() returns a nullptr + * + * The parameter tells how long time a frame is waited in milliseconds */ + + frame = recv->pull_frame(RECEIVER_WAIT_TIME_MS); + if (frame) + { + process_frame(frame); + } + } + + receiver_session->destroy_stream(recv); + } + + if (receiver_session) + { + ctx.destroy_session(receiver_session); } } int main(void) { + uvgrtp::context ctx; + + if (!ctx.crypto_enabled()) + { + std::cerr << "Cannot run SRTP example if crypto is not included in uvgRTP!" + << std::endl; + return EXIT_FAILURE; + } + + /* Key and salt for the SRTP session of sender and receiver + * + * NOTE: uvgRTP supports 128, 196 and 256 bit keys and 112 bit salts */ + uint8_t key[KEY_SIZE_BYTES] = { 0 }; + uint8_t salt[SALT_SIZE_BYTES] = { 0 }; + /* initialize SRTP key and salt */ - for (int i = 0; i < KEY_SIZE; ++i) + for (int i = 0; i < KEY_SIZE_BYTES; ++i) key[i] = i; - for (int i = 0; i < SALT_SIZE; ++i) + for (int i = 0; i < SALT_SIZE_BYTES; ++i) salt[i] = i * 2; + std::cout << "Starting uvgRTP SRTP user provided encryption key example. Using key:" + << key << " and salt: " << salt << std::endl; + /* Create separate thread for the receiver */ - new std::thread(thread_func); + std::thread receiver(receive_func, key, salt); + + receiver.detach(); /* See sending.cc for more details */ - uvgrtp::context ctx; - uvgrtp::session *sess = ctx.create_session("127.0.0.1"); + + uvgrtp::session *sender_session = ctx.create_session(RECEIVER_ADDRESS); /* Enable SRTP and let user manage keys */ - unsigned flags = RCE_SRTP | RCE_SRTP_KMNGMNT_USER; + unsigned flags = RCE_SRTP | RCE_SRTP_KMNGMNT_USER | RCE_SRTP_KEYSIZE_256; /* See sending.cc for more details about create_stream() */ - uvgrtp::media_stream *send = sess->create_stream(8888, 8889, RTP_FORMAT_GENERIC, flags); + uvgrtp::media_stream *send = sender_session->create_stream(LOCAL_PORT, REMOTE_PORT, + RTP_FORMAT_GENERIC, flags); - /* Before anything else can be done, - * add_srtp_ctx() must be called with the SRTP key and salt. - * - * All calls to "send" that try to modify and or/use the newly - * created media stream before calling add_srtp_ctx() will fail */ - send->add_srtp_ctx(key, salt); + if (send) + { + /* Before anything else can be done, + * add_srtp_ctx() must be called with the SRTP key and salt. + * + * All calls to "send" that try to modify and or/use the newly + * created media stream before calling add_srtp_ctx() will fail + * if SRTP is enabled */ + send->add_srtp_ctx(key, salt); - /* All media is now encrypted/decrypted automatically */ - char *message = (char *)"Hello, world!"; - size_t msg_len = strlen(message); + /* All media is now encrypted/decrypted automatically */ + char *message = (char *)"Hello, world!"; + size_t msg_len = strlen(message); - for (;;) { - send->push_frame((uint8_t *)message, msg_len, RTP_NO_FLAGS); - std::this_thread::sleep_for(std::chrono::milliseconds(500)); + auto start = std::chrono::steady_clock::now(); + for (unsigned int i = 0; i < SEND_TEST_PACKETS; ++i) + { + std::cout << "Sending frame " << i + 1 << '/' << SEND_TEST_PACKETS << std::endl; + + if (send->push_frame((uint8_t *)message, msg_len, RTP_NO_FLAGS) != RTP_OK) + { + std::cerr << "Failed to send frame" << std::endl; + } + + // wait until it is time to send the next frame. Included only for + // demostration purposes since you can use uvgRTP to send packets as fast as desired + auto time_since_start = std::chrono::steady_clock::now() - start; + auto next_frame_time = (i + 1)*std::chrono::milliseconds(PACKET_INTERVAL_MS); + if (next_frame_time > time_since_start) + { + std::this_thread::sleep_for(next_frame_time - time_since_start); + } + } + + sender_session->destroy_stream(send); } + + if (sender_session) + ctx.destroy_session(sender_session); + + return EXIT_SUCCESS; } diff --git a/docs/examples/uvgRTP_examples.pro b/docs/examples/uvgRTP_examples.pro index 5f41a24..741efab 100644 --- a/docs/examples/uvgRTP_examples.pro +++ b/docs/examples/uvgRTP_examples.pro @@ -68,13 +68,12 @@ selectSRTPUser { TARGET = SRTPUser SOURCES += \ srtp_user.cc \ -} -selectSRTPZRTP { - message("Building ZRTP + SRTP example.") - TARGET = Timestamp - SOURCES += \ - custom_timestamps.cc \ + win32-msvc{ + LIBS += -lcryptlib + } else { + LIBS += -lcryptopp + } } selectZRTPMultistream { @@ -82,6 +81,12 @@ selectZRTPMultistream { TARGET = ZRTPMultistream SOURCES += \ zrtp_multistream.cc \ + + win32-msvc{ + LIBS += -lcryptlib + } else { + LIBS += -lcryptopp + } } DISTFILES += \ @@ -95,18 +100,16 @@ LIBS += -luvgrtp win32-msvc{ message("Detected MSVC compiler") - # find the libraries if uvgrtp has been built according to instructions + # find the libraries if uvgrtp has been built using CMake instructions CONFIG(debug, debug|release) { - LIBRARY_FOLDER = -L$$PWD/../../build/Debug + UVGRTP_LIB_FOLDER = -L$$PWD/../../build/Debug } else:CONFIG(release, debug|release) { - LIBRARY_FOLDER = -L$$PWD/../../build/Release + UVGRTP_LIB_FOLDER = -L$$PWD/../../build/Release } LIBS += -lws2_32 LIBS += -ladvapi32 - LIBS += $${LIBRARY_FOLDER} - message("Using library folder:" $${LIBRARY_FOLDER}) + LIBS += $${UVGRTP_LIB_FOLDER} + message("Using the following folder for uvgRTP library:" $${UVGRTP_LIB_FOLDER}) } - - diff --git a/docs/examples/zrtp_multistream.cc b/docs/examples/zrtp_multistream.cc index 1af514f..a254073 100644 --- a/docs/examples/zrtp_multistream.cc +++ b/docs/examples/zrtp_multistream.cc @@ -2,62 +2,250 @@ #include #include -void thread_func(void) -{ - /* See sending.cc for more details */ - uvgrtp::context ctx; - uvgrtp::session *sess = ctx.create_session("127.0.0.1"); +constexpr char SENDER_ADDRESS[] = "127.0.0.1"; +constexpr uint16_t SENDER_VIDEO_PORT = 8888; +constexpr uint16_t SENDER_AUDIO_PORT = 8890; - /* Enable SRTP and use ZRTP to manage keys */ - unsigned flags = RCE_SRTP | RCE_SRTP_KMNGMNT_ZRTP; +constexpr char RECEIVER_ADDRESS[] = "127.0.0.1"; +constexpr uint16_t RECEIVER_VIDEO_PORT = 7776; +constexpr uint16_t RECEIVER_AUDIO_PORT = 7778; - /* Keys creates using Diffie-Hellman mode */ - uvgrtp::media_stream *video = sess->create_stream(8889, 8888, RTP_FORMAT_GENERIC, flags); +constexpr int VIDEO_PAYLOAD_SIZE = 4000; +constexpr int AUDIO_PAYLOAD_SIZE = 100; - /* Keys created using Multistream mode */ - uvgrtp::media_stream *audio = sess->create_stream(7778, 7777, RTP_FORMAT_GENERIC, flags); +constexpr auto EXAMPLE_RUN_TIME_S = std::chrono::seconds(10); +constexpr auto RECEIVER_WAIT_TIME_MS = std::chrono::milliseconds(50); + +constexpr auto AUDIO_FRAME_INTERVAL_MS = std::chrono::milliseconds(20); + +constexpr auto VIDEO_FRAME_INTERVAL_MS = std::chrono::milliseconds(1000/60); // 60 fps video + +void audio_receiver(uvgrtp::session* receiver_session, int flags, std::shared_ptr print_mutex); +void video_receiver(uvgrtp::session* receiver_session, int flags, std::shared_ptr print_mutex); + +void audio_sender(uvgrtp::session* sender_session, int flags, std::shared_ptr print_mutex); +void video_sender(uvgrtp::session* sender_session, int flags, std::shared_ptr print_mutex); - for (;;) { - auto frame = video->pull_frame(); - fprintf(stderr, "Message: '%s'\n", frame->payload); - (void)uvgrtp::frame::dealloc_frame(frame); - frame = audio->pull_frame(); - fprintf(stderr, "Message: '%s'\n", frame->payload); - (void)uvgrtp::frame::dealloc_frame(frame); - } -} int main(void) { + std::cout << "Starting uvgRTP SRTP together with ZRTP example" << std::endl; + + /* Enable SRTP and use ZRTP to manage keys for both sender and receiver*/ + unsigned rce_flags = RCE_SRTP | RCE_SRTP_KMNGMNT_ZRTP; + + uvgrtp::context receiver_ctx; + + if (!receiver_ctx.crypto_enabled()) + { + std::cerr << "Cannot run SRTP example if crypto is not included in uvgRTP!" + << std::endl; + return EXIT_FAILURE; + } + + uvgrtp::session *receiver_session = receiver_ctx.create_session(SENDER_ADDRESS); + + std::shared_ptr print_mutex = std::shared_ptr (new std::mutex); + /* Create separate thread for the receiver * * Because we're using ZRTP for SRTP key management, * the receiver and sender must communicate with each other * before the actual media communication starts */ - new std::thread(thread_func); + std::thread a_receiver(audio_receiver, receiver_session, rce_flags, print_mutex); + std::thread v_receiver(video_receiver, receiver_session, rce_flags, print_mutex); /* See sending.cc for more details */ - uvgrtp::context ctx; - uvgrtp::session *sess = ctx.create_session("127.0.0.1"); + uvgrtp::context sender_ctx; + uvgrtp::session *sender_session = sender_ctx.create_session(RECEIVER_ADDRESS); - /* Enable SRTP and use ZRTP to manage keys */ - unsigned flags = RCE_SRTP | RCE_SRTP_KMNGMNT_ZRTP; + std::thread a_sender(audio_sender, sender_session, rce_flags, print_mutex); + std::thread v_sender(video_sender, sender_session, rce_flags, print_mutex); - /* Initialize ZRTP and negotiate the keys used to encrypt the media */ - uvgrtp::media_stream *video = sess->create_stream(8888, 8889, RTP_FORMAT_GENERIC, flags); + // wait until all threads have ended + if (a_receiver.joinable()) + { + a_receiver.join(); + } + if (v_receiver.joinable()) + { + v_receiver.join(); + } + if (a_sender.joinable()) + { + a_sender.join(); + } + if (v_sender.joinable()) + { + v_sender.join(); + } + + if (sender_session) + sender_ctx.destroy_session(sender_session); + + if (receiver_session) + sender_ctx.destroy_session(receiver_session); + + std::cout << "ZRTP example finished" << std::endl; + + return EXIT_SUCCESS; +} + + +void audio_receiver(uvgrtp::session* receiver_session, int flags, std::shared_ptr print_mutex) +{ + /* Keys created using Multistream mode */ + uvgrtp::media_stream *receiver_audio_strm = + receiver_session->create_stream(RECEIVER_AUDIO_PORT, SENDER_AUDIO_PORT, + RTP_FORMAT_GENERIC, flags); + + if (receiver_audio_strm) + { + uvgrtp::frame::rtp_frame *frame = nullptr; + + std::cout << "Start receiving audio frames for " << EXAMPLE_RUN_TIME_S.count() + << " seconds" << std::endl; + auto start = std::chrono::steady_clock::now(); + + while (std::chrono::steady_clock::now() - start < EXAMPLE_RUN_TIME_S) + { + /* You can specify a timeout for the operation and if the a frame is not received + * within that time limit, pull_frame() returns a nullptr + * + * The parameter tells how long time a frame is waited in milliseconds */ + + frame = receiver_audio_strm->pull_frame(RECEIVER_WAIT_TIME_MS.count()); + + if (frame) + { + print_mutex->lock(); + std::cout << "Received an audio frame. Payload size: " << frame->payload_len << std::endl; + print_mutex->unlock(); + + // Process audio frame here + + (void)uvgrtp::frame::dealloc_frame(frame); + } + } + + receiver_session->destroy_stream(receiver_audio_strm); + } +} + +void video_receiver(uvgrtp::session* receiver_session, int flags, std::shared_ptr print_mutex) +{ + /* Keys creates using Diffie-Hellman mode */ + uvgrtp::media_stream *receiver_video_strm = + receiver_session->create_stream(RECEIVER_VIDEO_PORT, SENDER_VIDEO_PORT, + RTP_FORMAT_H266, flags); + + if (receiver_video_strm) + { + uvgrtp::frame::rtp_frame *frame = nullptr; + + std::cout << "Start receiving audio frames for " << EXAMPLE_RUN_TIME_S.count() + << " seconds" << std::endl; + auto start = std::chrono::steady_clock::now(); + + while (std::chrono::steady_clock::now() - start < EXAMPLE_RUN_TIME_S) + { + /* You can specify a timeout for the operation and if the a frame is not received + * within that time limit, pull_frame() returns a nullptr + * + * The parameter tells how long time a frame is waited in milliseconds */ + + frame = receiver_video_strm->pull_frame(RECEIVER_WAIT_TIME_MS.count()); + + if (frame) + { + print_mutex->lock(); + std::cout << "Received a video frame. Payload size: " << frame->payload_len << std::endl; + print_mutex->unlock(); + + // Process video frame here + + (void)uvgrtp::frame::dealloc_frame(frame); + } + } + + receiver_session->destroy_stream(receiver_video_strm); + } +} + +void audio_sender(uvgrtp::session* sender_session, int flags, std::shared_ptr print_mutex) +{ /* The first call to create_stream() creates keys for the session using Diffie-Hellman * key exchange and all subsequent calls to create_stream() initialize keys for the * stream using Multistream mode */ - uvgrtp::media_stream *audio = sess->create_stream(7777, 7778, RTP_FORMAT_GENERIC, flags); + uvgrtp::media_stream *sender_audio_strm = sender_session->create_stream(SENDER_AUDIO_PORT, + RECEIVER_AUDIO_PORT, + RTP_FORMAT_GENERIC, flags); - char *message = (char *)"Hello, world!"; - size_t msg_len = strlen(message); + if (sender_audio_strm) + { + auto start = std::chrono::steady_clock::now(); - for (;;) { - video->push_frame((uint8_t *)message, msg_len, RTP_NO_FLAGS); - audio->push_frame((uint8_t *)message, msg_len, RTP_NO_FLAGS); - std::this_thread::sleep_for(std::chrono::milliseconds(500)); + for (int i = 0; std::chrono::steady_clock::now() < (start + EXAMPLE_RUN_TIME_S); ++i) + { + print_mutex->lock(); + std::cout << "Sending audio frame" << std::endl; + print_mutex->unlock(); + + std::unique_ptr dummy_audio_frame = std::unique_ptr(new uint8_t[AUDIO_PAYLOAD_SIZE]); + + if (sender_audio_strm->push_frame(std::move(dummy_audio_frame), AUDIO_PAYLOAD_SIZE, RTP_NO_FLAGS) != RTP_OK) + { + std::cerr << "Failed to send audio frame" << std::endl; + } + + // wait until it is time to send the next frame. Included only for + // demostration purposes since you can use uvgRTP to send packets as fast as desired + auto time_since_start = std::chrono::steady_clock::now() - start; + auto next_frame_time = (i + 1)*std::chrono::milliseconds(AUDIO_FRAME_INTERVAL_MS); + if (next_frame_time > time_since_start) + { + std::this_thread::sleep_for(next_frame_time - time_since_start); + } + } } } + +void video_sender(uvgrtp::session* sender_session, int flags, std::shared_ptr print_mutex) +{ + /* Initialize ZRTP and negotiate the keys used to encrypt the media */ + uvgrtp::media_stream *sender_video_strm = sender_session->create_stream(SENDER_VIDEO_PORT, + RECEIVER_VIDEO_PORT, + RTP_FORMAT_H266, flags); + + if (sender_video_strm) + { + auto start = std::chrono::steady_clock::now(); + + for (int i = 0; std::chrono::steady_clock::now() < (start + EXAMPLE_RUN_TIME_S); ++i) + { + print_mutex->lock(); + std::cout << "Sending video frame" << std::endl; + print_mutex->unlock(); + + std::unique_ptr dummy_audio_frame = + std::unique_ptr(new uint8_t[VIDEO_PAYLOAD_SIZE]); + + if (sender_video_strm->push_frame(std::move(dummy_audio_frame), + VIDEO_PAYLOAD_SIZE, RTP_NO_FLAGS) != RTP_OK) + { + std::cerr << "Failed to send video frame" << std::endl; + } + + // wait until it is time to send the next frame. Included only for + // demostration purposes since you can use uvgRTP to send packets as fast as desired + auto time_since_start = std::chrono::steady_clock::now() - start; + auto next_frame_time = (i + 1)*std::chrono::milliseconds(VIDEO_FRAME_INTERVAL_MS); + if (next_frame_time > time_since_start) + { + std::this_thread::sleep_for(next_frame_time - time_since_start); + } + } + } +}