From 40c86376330d9f47c57fb82a965dc4223ffd1296 Mon Sep 17 00:00:00 2001 From: Giang Minh Date: Sun, 8 Dec 2024 21:52:04 +0700 Subject: [PATCH] docs: add rtp-engine --- docs/contributor-guide/transports/README.md | 8 +- docs/contributor-guide/transports/rtmp.md | 10 - .../transports/rtp-engine.md | 3 + docs/contributor-guide/transports/sip.md | 265 ------------------ docs/getting-started/quick-start/rtmp.md | 19 -- .../getting-started/quick-start/rtp-engine.md | 68 +++++ docs/getting-started/quick-start/sip.md | 53 ---- 7 files changed, 76 insertions(+), 350 deletions(-) delete mode 100644 docs/contributor-guide/transports/rtmp.md create mode 100644 docs/contributor-guide/transports/rtp-engine.md delete mode 100644 docs/contributor-guide/transports/sip.md create mode 100644 docs/getting-started/quick-start/rtp-engine.md delete mode 100644 docs/getting-started/quick-start/sip.md diff --git a/docs/contributor-guide/transports/README.md b/docs/contributor-guide/transports/README.md index e90e405d..aeb433df 100644 --- a/docs/contributor-guide/transports/README.md +++ b/docs/contributor-guide/transports/README.md @@ -3,14 +3,16 @@ We have some types of transports: - [WebRTC](./webrtc.md) -- [SIP](./sip.md) -- [RTMP](./rtmp.md) +- [RTP-Engine](./rtp-engine.md) - [Whip-Whep](./whip-whep.md) Bellow transport will be implemented in next version: +- [RTMP](./rtmp.md) - [Media over Quic](https://quic.video/) - [SRT](https://www.haivision.com/products/srt-secure-reliable-transport/) - [HLS](https://en.wikipedia.org/wiki/HTTP_Live_Streaming) -If you don't find the transport you need, you can implement it by yourself by implementing the `Transport` traits. Please refer to [Architecture](../architecture.md) for more info. +If you don't find the transport you need, you can implement it by yourself by +implementing the `Transport` traits. Please refer to +[Architecture](../architecture.md) for more info. diff --git a/docs/contributor-guide/transports/rtmp.md b/docs/contributor-guide/transports/rtmp.md deleted file mode 100644 index f40be247..00000000 --- a/docs/contributor-guide/transports/rtmp.md +++ /dev/null @@ -1,10 +0,0 @@ -# RTMP - -RTMP transport is implemented by using the rml_rtmp crate. - -We start a tcp-server inside media-server create, then create a transport from incoming tcp stream. - -Each rtmp session is processed in SAN/IO style: - -- RtmpSession: will process incoming data, output event or outgoing data. This part is sync, and not related to I/O -- RtmpTransport: will bridge between RtmpSession and TcpStream. This part is async, and related to I/O diff --git a/docs/contributor-guide/transports/rtp-engine.md b/docs/contributor-guide/transports/rtp-engine.md new file mode 100644 index 00000000..50c7a707 --- /dev/null +++ b/docs/contributor-guide/transports/rtp-engine.md @@ -0,0 +1,3 @@ +# RTP Engine + +RTP Engine is implemented in 'packages/transport_rtpengine/' diff --git a/docs/contributor-guide/transports/sip.md b/docs/contributor-guide/transports/sip.md deleted file mode 100644 index 2c7b25e4..00000000 --- a/docs/contributor-guide/transports/sip.md +++ /dev/null @@ -1,265 +0,0 @@ -# SIP - -The current implementation is incomplete and only finished the most important parts, it is working as a PoC. - -- It can handle incoming calls to join a room or reject them. It can make outgoing calls and put them in a room. -- Audio processing is done with OPUS and PCMA transcoding. - -We create udp server then create a virtual session with iddentify is (dest, call_id), then we process each session independently in separate task, this will be convert into SipTransport. All SIP message is serialize and deserialize rsip crate. - -### Checklist - -Register - -- [x] Client MD5 authentication -- [ ] Server MD5 authentication -- [ ] Client IP rule -- [ ] Server IP rule -- [ ] Unregister - -Call - -- [x] Client MD5 authentication -- [ ] Server MD5 authentication -- [ ] Client IP rule -- [ ] Server IP rule -- [x] Incoming call -- [x] Outgoing call -- [x] Audio transcode -- [x] Call RING -- [x] Call BUSY -- [x] Call CANCEL -- [x] Call END after all other members have left - -Hooks - -- [x] Auth hook -- [x] Register hook -- [x] Unregister hook -- [x] Invite hook - -APIs - -- [x] Invite client session -- [ ] Invite server (with or without authentication) - -### Working flow - -#### Register - -```mermaid -sequenceDiagram - participant sip-client - participant atm0s-sip-server - participant 3rd-hooks - sip-client ->> atm0s-sip-server: Register - atm0s-sip-server ->> 3rd-hooks: post /hooks/auth - 3rd-hooks ->> atm0s-sip-server: return md5(user:realm:password) - atm0s-sip-server ->> sip-client: return OK or ERR - atm0s-sip-server ->> 3rd-hooks: post /hooks/register includes session_id (if above is OK) -``` - -#### Incoming call - -```mermaid -sequenceDiagram - participant sip-client - participant atm0s-sip-server - participant 3rd-hooks - sip-client->>atm0s-sip-server: incoming call - atm0s-sip-server ->> 3rd-hooks: post /hooks/invite includes from, to - 3rd-hooks ->> atm0s-sip-server: return action: Reject, Accept, WaitOthers, includes room info - atm0s-sip-server->atm0s-sip-server: create endpoint and join room if Accept or WaitOthers - atm0s-sip-server ->> sip-client: return Busy if above Reject - atm0s-sip-server ->> sip-client: return Accept if above Accept - atm0s-sip-server ->> sip-client: return Ringing if above WaitOthers -``` - -#### Outgoing call - -Call to client (Zoiper or Linphone app ...) - -```mermaid -sequenceDiagram - participant sip-client - participant atm0s-sip-server - participant 3rd-hooks - 3rd-hooks ->> atm0s-sip-server: post /sip/invite/client includes session_id, room_info - atm0s-sip-server->>sip-client: outgoing call - atm0s-sip-server->atm0s-sip-server: create endpoint and join room as connecting - atm0s-sip-server ->> 3rd-hooks: return call_id - sip-client->>atm0s-sip-server: accept - atm0s-sip-server->atm0s-sip-server: switch endpoint to connected -``` - -#### Reference hooks server - -```rust -use std::{collections::HashMap, net::SocketAddr, sync::Arc}; - -use clap::Parser; -use cluster::rpc::sip::{ - SipIncomingAuthRequest, SipIncomingAuthResponse, SipIncomingInviteRequest, SipIncomingInviteResponse, SipIncomingInviteStrategy, SipIncomingRegisterRequest, SipIncomingRegisterResponse, - SipOutgoingInviteClientRequest, SipOutgoingInviteResponse, -}; -use media_utils::Response; -use parking_lot::Mutex; -use poem::{ - handler, - listener::TcpListener, - post, - web::{Data, Json}, - EndpointExt, Route, Server, -}; -use rand::Rng; - -// generate 16byte hex random string -fn random_room_id() -> String { - let mut rng = rand::thread_rng(); - let mut room_id = String::new(); - for _ in 0..16 { - room_id.push_str(&format!("{:02x}", rng.gen::())); - } - room_id -} - -#[derive(Clone, Default)] -struct Context { - users: Arc>>, - sip_gateway: String, -} - -impl Context { - pub fn generate_auth(&self, username: &str, realm: &str) -> String { - let password = username; - let ha1 = md5::compute(format!("{}:{}:{}", username, realm, password)); - format!("{:x}", ha1) - } - - pub fn register_user(&self, username: &str, session_id: &str) { - self.users.lock().insert(username.to_string(), session_id.to_string()); - } - - pub fn unregister_user(&self, username: &str, session_id: &str) { - if let Some(s) = self.users.lock().get(username) { - if s == session_id { - self.users.lock().remove(username); - } - } - } - - pub fn get_user_session_id(&self, username: &str) -> Option { - self.users.lock().get(username).cloned() - } - - pub async fn make_client_call(&self, room_id: &str, from_number: &str, dest_session_id: &str) -> Result<(), reqwest::Error> { - let client = reqwest::Client::new(); - let res = client - .post(format!("{}/sip/invite/client", self.sip_gateway)) - .json(&SipOutgoingInviteClientRequest { - room_id: room_id.to_string(), - dest_session_id: dest_session_id.to_string(), - from_number: from_number.to_string(), - hook_uri: None, - server_alias: None, - }) - .send() - .await? - .json::>() - .await?; - log::info!("make_client_call: {:?}", res); - Ok(()) - } -} - -#[handler] -fn hook_auth(req: Json, data: Data<&Context>) -> Json { - log::info!("hook_auth: {:?}", req); - Json(SipIncomingAuthResponse { - success: true, - ha1: Some(data.0.generate_auth(&req.0.username, &req.0.realm)), - }) -} - -#[handler] -fn hook_register(req: Json, data: Data<&Context>) -> Json { - log::info!("hook_register: {:?}", req); - data.0.register_user(&req.0.username, &req.0.session_id); - Json(SipIncomingRegisterResponse { success: true }) -} - -#[handler] -fn hook_unregister(req: Json, data: Data<&Context>) -> Json { - log::info!("hook_unregister: {:?}", req); - data.0.unregister_user(&req.0.username, &req.0.session_id); - Json(SipIncomingRegisterResponse { success: true }) -} - -#[handler] -async fn hook_invite(req: Json, data: Data<&Context>) -> Json { - log::info!("hook_invite: {:?}", req); - - if let Some(dest_session_id) = data.0.get_user_session_id(&req.0.to_number) { - let room_id = random_room_id(); - match data.0.make_client_call(&room_id, &req.0.from_number, &dest_session_id).await { - Ok(_) => { - log::info!("make_client_call success: {:?}", room_id); - Json(SipIncomingInviteResponse { - room_id: Some(room_id), - strategy: SipIncomingInviteStrategy::WaitOtherPeers, - }) - } - Err(e) => { - log::error!("make_client_call error: {:?}", e); - Json(SipIncomingInviteResponse { - room_id: None, - strategy: SipIncomingInviteStrategy::Reject, - }) - } - } - } else { - log::warn!("user not found: {:?}", req.0.to_number); - Json(SipIncomingInviteResponse { - room_id: None, - strategy: SipIncomingInviteStrategy::Reject, - }) - } -} - -/// Sip Hooks Server sample. This server acts as a address book for SIP servers -#[derive(Parser, Debug)] -#[command(author, version, about, long_about = None)] -struct Args { - /// Http port - #[arg(env, long, default_value = "0.0.0.0:3000")] - http_addr: SocketAddr, - - /// Sip server gateway - #[arg(env, long, default_value = "http://localhost:8002")] - sip_gateway: String, -} - -#[async_std::main] -async fn main() -> Result<(), std::io::Error> { - let args = Args::parse(); - if std::env::var_os("RUST_LOG").is_none() { - std::env::set_var("RUST_LOG", "info"); - } - tracing_subscriber::fmt::init(); - - let ctx = Context { - sip_gateway: args.sip_gateway, - ..Default::default() - }; - - let app = Route::new() - .at("/hooks/auth", post(hook_auth)) - .at("/hooks/register", post(hook_register)) - .at("/hooks/unregister", post(hook_unregister)) - .at("/hooks/invite", post(hook_invite)) - .data(ctx); - - Server::new(TcpListener::bind(args.http_addr)).run(app).await -} - -``` diff --git a/docs/getting-started/quick-start/rtmp.md b/docs/getting-started/quick-start/rtmp.md index a412013f..eee04f84 100644 --- a/docs/getting-started/quick-start/rtmp.md +++ b/docs/getting-started/quick-start/rtmp.md @@ -1,20 +1 @@ # RTMP - -You can use RTMP to publish to our media server. Currently, we don't support transcoding, so you need to publish with the same resolution and codec that correspond to your viewers. - -Preferred codecs: - -- Video: H264, baseline profile, bitrate 2500kbps -- Audio: AAC, bitrate 128kbps - -URL: `rtmp://{gateway}/live/{token}` - -Demo configuration for OBS: - -![Config OBS](../../imgs/demo-rtmp-config.png) - -Pregenerated token for default secret and room `demo`, peer `publisher`: - -```jwt -eyJhbGciOiJIUzI1NiJ9.eyJyb29tIjoiZGVtbyIsInBlZXIiOiJydG1wIiwicHJvdG9jb2wiOiJSdG1wIiwicHVibGlzaCI6dHJ1ZSwic3Vic2NyaWJlIjpmYWxzZSwidHMiOjE3MDM3NTIzMzU2OTV9.Gj0uCxPwqsFfMFLX8Cufrsyhtb7vedNp3GeUtKQCk3s -``` diff --git a/docs/getting-started/quick-start/rtp-engine.md b/docs/getting-started/quick-start/rtp-engine.md new file mode 100644 index 00000000..92cce810 --- /dev/null +++ b/docs/getting-started/quick-start/rtp-engine.md @@ -0,0 +1,68 @@ +# RTP Engine + +The atm0s-media-server supports RTP transport for integration with external +applications such as SIP Gateways. We have also developed a simple SIP Gateway +that supports basic call flows, including outgoing and incoming calls: +[atm0s-sip-gateway](https://github.com/8xFF/atm0s-media-sip-gateway). + +## Integration Guide + +Our RTP media endpoint and HTTP APIs enable integration with external SIP +Gateways. The integration process involves three main steps: + +1. Creating an RTP token +2. Creating an SDP Offer for outgoing calls or an SDP Answer for incoming calls +3. Deleting the RTP media endpoint after the call ends + +For more detailed API documentation, please visit: + +`http://gateway/token/ui` and `http://gateway/rtpengine/ui` + +### RTPEngine APIs + +The RTPEngine APIs provide endpoints to manage RTP media endpoints for SIP +integration. All endpoints are prefixed with `/rtpengine/`. + +#### Authentication + +Most endpoints require Bearer token authentication. Include your token in the +Authorization header: + +``` +Authorization: Bearer +``` + +#### Endpoints + +##### Create Offer + +- **POST** `/rtpengine/offer` +- **Summary**: Creates an RTPEngine endpoint with an offer +- **Security**: Bearer token required +- **Response**: + - Status: 200 + - Content-Type: application/sdp + - Schema: SDP string + +##### Create Answer + +- **POST** `/rtpengine/answer` +- **Summary**: Creates an RTPEngine endpoint with an answer +- **Security**: Bearer token required +- **Request Body**: + - Content-Type: application/sdp + - Required: true + - Schema: SDP string +- **Response**: + - Status: 200 + - Content-Type: application/sdp + - Schema: SDP string + +##### Delete Connection + +- **DELETE** `/rtpengine/conn/{conn_id}` +- **Summary**: Deletes an RTPEngine connection +- **Response**: + - Status: 200 + - Content-Type: text/plain; charset=utf-8 + - Schema: string diff --git a/docs/getting-started/quick-start/sip.md b/docs/getting-started/quick-start/sip.md deleted file mode 100644 index 8ef07274..00000000 --- a/docs/getting-started/quick-start/sip.md +++ /dev/null @@ -1,53 +0,0 @@ -# SIP integrate - -`We are developing a pure Rust SIP-Gateway, then this document will be updated soon.` - -To avoid complexity, atm0s-media-server only works as an rtpengine replacement. To enable SIP, we need a signaling system that supports the rtpengine ng_control protocol (UDP) with an injected authentication token. - -To demonstrate how it works, we created a simple SIP call integration in [8xFF/atm0s-media-sip-call-sample](https://github.com/8xFF/atm0s-media-sip-call-sample). In this sample: - -- [Drachtio](https://drachtio.org/) works as a SIP signaling layer. -- atm0s-media-server works as a media transport layer (RTP, UDP). - -In the repo, Node.js code handles events from Drachtio and initiates sessions in atm0s-media-server. After that, call legs media data is connected over atm0s-media-server. For allow atm0s-media validate connect request we need to inject atm0s_token in connect requests and connection_id in delete request: - -```ts -const RtpEngine = require("rtpengine-client").Client; -const rtpengine = new RtpEngine(); - -const { conn, sdp } = await rtpengine.offer( - RTP_ENGINE_CONFIG.port, - RTP_ENGINE_CONFIG.host, - { - "call-id": call, - "from-tag": from, - "to-tag": to, - sdp, - "atm0s-token": token, - } -); -``` - -```ts -// conn is got from above offer or answer request -async function rtpDelete( - call: string, - from: string, - to: string | undefined, - conn: string -) { - await rtpengine.delete(RTP_ENGINE_CONFIG.port, RTP_ENGINE_CONFIG.host, { - "call-id": call, - "from-tag": from, - "to-tag": to, - "conn-id": conn, - }); -} -``` - -![Flow](../../imgs/features/sip.excalidraw.png) - -Some limitations: - -- Only PCMA (8kHz, payload type 8) is supported. -- Audio is always transcoded to Opus (48kHz).