publish client-sdk-native (#12)
* Create README.md * add example * crates & examples * fix syntax * add LICENSE * thirdparty LICENSE * rearrange repo * fix build * egui versions * prepare publish * fix demo compilation * add test ci * Update rust.yml * forgot runs-on * install protoc before building * avoid rate limit * include submodules * updates to readme * cache rust builds Co-authored-by: David Zhao <[email protected]> Co-authored-by: David Zhao <[email protected]>
This commit is contained in:
co-authored by
David Zhao
David Zhao
parent
a927baac94
commit
a07b3451a3
@@ -0,0 +1,24 @@
|
||||
# Using the same .clang-format as libwebrtc
|
||||
# https://github.com/webrtc-sdk/webrtc/blob/m104_release/.clang-format
|
||||
BasedOnStyle: Chromium
|
||||
---
|
||||
Language: Java
|
||||
BasedOnStyle: Google
|
||||
---
|
||||
Language: ObjC
|
||||
BasedOnStyle: Google
|
||||
BinPackParameters: false
|
||||
BinPackArguments: false
|
||||
ColumnLimit: 100
|
||||
ObjCBlockIndentWidth: 2
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AlignOperands: false
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
BreakBeforeTernaryOperators: false
|
||||
IndentWrappedFunctionNames: true
|
||||
ContinuationIndentWidth: 4
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
---
|
||||
Language: Cpp
|
||||
IncludeBlocks: Regroup
|
||||
@@ -0,0 +1 @@
|
||||
/cmake-build-debug
|
||||
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "webrtc-sys"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
homepage = "https://livekit.io"
|
||||
license = "Apache-2.0"
|
||||
description = "Unsafe bindings to libwebrtc"
|
||||
|
||||
[dependencies]
|
||||
cxx = "1.0"
|
||||
log = "0.4"
|
||||
|
||||
[build-dependencies]
|
||||
curl = "0.4"
|
||||
tar = "0.4"
|
||||
flate2 = "1.0"
|
||||
cxx-build = "1.0"
|
||||
glob = "0.3.0"
|
||||
regex = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.9"
|
||||
|
||||
#[lib]
|
||||
#crate-type = ["staticlib", "cdylib"]
|
||||
@@ -0,0 +1,209 @@
|
||||
The build system and the bindings are inspired by arcas-io (https://github.com/arcas-io)
|
||||
The Android build system is inspired by shiguredo (https://github.com/shiguredo/sora-cpp-sdk)
|
||||
|
||||
** littlebearlabs/arcas-io MIT License ""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 arcas-io
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
Footer
|
||||
|
||||
** Shiguredo Apache 2.0 License **
|
||||
Sora C++ SDK
|
||||
|
||||
Copyright 2021-2022, Wandbox LLC (Original Author)
|
||||
|
||||
Copyright 2021-2022, Shiguredo Inc.
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -0,0 +1,288 @@
|
||||
use curl::easy::Easy;
|
||||
use flate2::read::GzDecoder;
|
||||
use regex::Regex;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::path;
|
||||
use std::process::Command;
|
||||
use tar::Archive;
|
||||
|
||||
const WEBRTC_TAG: &str = "m104.5112.06";
|
||||
|
||||
fn download_prebuilt(
|
||||
target_os: &str,
|
||||
target_arch: &str,
|
||||
out_path: std::path::PathBuf,
|
||||
) -> Result<std::path::PathBuf, Box<dyn std::error::Error>> {
|
||||
let target_arch = match target_arch {
|
||||
"aarch64" => "arm64",
|
||||
_ => target_arch,
|
||||
};
|
||||
|
||||
let file_name = format!("webrtc.{}_{}.tar.gz", target_os, target_arch);
|
||||
let file_url = format!(
|
||||
"https://github.com/webrtc-sdk/webrtc-build/releases/download/{}/{}",
|
||||
WEBRTC_TAG, file_name
|
||||
);
|
||||
let file_path = out_path.join(&file_name);
|
||||
|
||||
if !out_path.exists() {
|
||||
fs::create_dir(&out_path)?;
|
||||
}
|
||||
|
||||
// Download the release archive
|
||||
if !file_path.exists() {
|
||||
let file = fs::File::create(&file_path)?;
|
||||
{
|
||||
let mut writer = io::BufWriter::new(file);
|
||||
let mut handle = Easy::new();
|
||||
handle.url(&file_url)?;
|
||||
handle.follow_location(true)?;
|
||||
handle.write_function(move |data| Ok(writer.write(data).unwrap()))?;
|
||||
handle.perform()?;
|
||||
|
||||
let response_code = handle.response_code()?;
|
||||
if response_code != 200 {
|
||||
fs::remove_file(&file_path)?;
|
||||
Err(format!(
|
||||
"Failed to download WebRTC-SDK (Status: {}) {}",
|
||||
response_code, file_url
|
||||
))?
|
||||
}
|
||||
}
|
||||
|
||||
// Extract the archive
|
||||
let file = fs::File::open(&file_path)?;
|
||||
let unzipped = GzDecoder::new(file);
|
||||
let mut a = Archive::new(unzipped);
|
||||
a.unpack(&out_path)?;
|
||||
}
|
||||
|
||||
Ok(out_path.join("webrtc"))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Download the prebuilt WebRTC library.
|
||||
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
|
||||
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||
let install_directory = env::var("OUT_DIR").unwrap() + "/webrtc-sdk";
|
||||
|
||||
let webrtc_dir = download_prebuilt(
|
||||
&target_os,
|
||||
&target_arch,
|
||||
path::PathBuf::from(install_directory),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let webrtc_include = webrtc_dir.join("include");
|
||||
let webrtc_lib = webrtc_dir.join("lib");
|
||||
|
||||
// Just required for the bridge build to succeed.
|
||||
let includes = &[
|
||||
path::PathBuf::from("./include"),
|
||||
webrtc_include.clone(),
|
||||
webrtc_include.join("third_party/abseil-cpp/"),
|
||||
webrtc_include.join("third_party/libyuv/include/"),
|
||||
webrtc_include.join("third_party/libc++/"),
|
||||
// For mac & ios
|
||||
webrtc_include.join("sdk/objc"),
|
||||
webrtc_include.join("sdk/objc/base"),
|
||||
];
|
||||
|
||||
let mut builder = cxx_build::bridges(&[
|
||||
"src/peer_connection.rs",
|
||||
"src/peer_connection_factory.rs",
|
||||
"src/media_stream.rs",
|
||||
"src/data_channel.rs",
|
||||
"src/jsep.rs",
|
||||
"src/candidate.rs",
|
||||
"src/rtp_receiver.rs",
|
||||
"src/rtp_transceiver.rs",
|
||||
"src/rtc_error.rs",
|
||||
"src/webrtc.rs",
|
||||
"src/video_frame.rs",
|
||||
"src/video_frame_buffer.rs",
|
||||
"src/yuv_helper.rs",
|
||||
]);
|
||||
|
||||
builder.file("src/peer_connection.cpp");
|
||||
builder.file("src/peer_connection_factory.cpp");
|
||||
builder.file("src/media_stream.cpp");
|
||||
builder.file("src/data_channel.cpp");
|
||||
builder.file("src/jsep.cpp");
|
||||
builder.file("src/candidate.cpp");
|
||||
builder.file("src/rtp_receiver.cpp");
|
||||
builder.file("src/rtp_transceiver.cpp");
|
||||
builder.file("src/rtc_error.cpp");
|
||||
builder.file("src/webrtc.cpp");
|
||||
|
||||
for include in includes {
|
||||
builder.include(include);
|
||||
}
|
||||
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}",
|
||||
webrtc_lib.canonicalize().unwrap().to_str().unwrap()
|
||||
);
|
||||
|
||||
match &target_os as &str {
|
||||
"windows" => {
|
||||
println!("cargo:rustc-link-lib=dylib=msdmo");
|
||||
println!("cargo:rustc-link-lib=dylib=wmcodecdspuuid");
|
||||
println!("cargo:rustc-link-lib=dylib=dmoguids");
|
||||
println!("cargo:rustc-link-lib=dylib=crypt32");
|
||||
println!("cargo:rustc-link-lib=dylib=iphlpapi");
|
||||
println!("cargo:rustc-link-lib=dylib=ole32");
|
||||
println!("cargo:rustc-link-lib=dylib=secur32");
|
||||
println!("cargo:rustc-link-lib=dylib=winmm");
|
||||
println!("cargo:rustc-link-lib=dylib=ws2_32");
|
||||
println!("cargo:rustc-link-lib=dylib=strmiids");
|
||||
println!("cargo:rustc-link-lib=dylib=d3d11");
|
||||
println!("cargo:rustc-link-lib=dylib=dxgi");
|
||||
println!("cargo:rustc-link-lib=dylib=dwmapi");
|
||||
println!("cargo:rustc-link-lib=static=webrtc");
|
||||
|
||||
builder
|
||||
.flag("/std:c++17")
|
||||
.flag("/EHsc")
|
||||
.define("WEBRTC_WIN", None)
|
||||
//.define("WEBRTC_ENABLE_SYMBOL_EXPORT", None) Not necessary when using WebRTC as a static library
|
||||
.define("NOMINMAX", None);
|
||||
}
|
||||
"macos" => {
|
||||
println!("cargo:rustc-link-lib=framework=Foundation");
|
||||
println!("cargo:rustc-link-lib=framework=AVFoundation");
|
||||
println!("cargo:rustc-link-lib=framework=CoreAudio");
|
||||
println!("cargo:rustc-link-lib=framework=AudioToolbox");
|
||||
println!("cargo:rustc-link-lib=framework=Appkit");
|
||||
println!("cargo:rustc-link-lib=framework=CoreMedia");
|
||||
println!("cargo:rustc-link-lib=framework=CoreGraphics");
|
||||
println!("cargo:rustc-link-lib=framework=VideoToolbox");
|
||||
println!("cargo:rustc-link-lib=framework=CoreVideo");
|
||||
println!("cargo:rustc-link-lib=framework=OpenGL");
|
||||
println!("cargo:rustc-link-lib=framework=Metal");
|
||||
println!("cargo:rustc-link-lib=framework=QuartzCore");
|
||||
println!("cargo:rustc-link-lib=framework=IOKit");
|
||||
println!("cargo:rustc-link-lib=framework=IOSurface");
|
||||
println!("cargo:rustc-link-lib=static=webrtc");
|
||||
println!("cargo:rustc-link-lib=dylib=c++");
|
||||
println!("cargo:rustc-link-lib=clang_rt.osx");
|
||||
|
||||
let output = Command::new("clang")
|
||||
.arg("--print-search-dirs")
|
||||
.output()
|
||||
.ok()
|
||||
.unwrap();
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
for line in stdout.lines() {
|
||||
if line.contains("libraries: =") {
|
||||
let path = line.split('=').nth(1).unwrap();
|
||||
let path = format!("{}/lib/darwin", path);
|
||||
println!("cargo:rustc-link-search={}", path);
|
||||
}
|
||||
}
|
||||
|
||||
builder
|
||||
.flag("-stdlib=libc++")
|
||||
.flag("-std=c++17")
|
||||
.define("WEBRTC_ENABLE_OBJC_SYMBOL_EXPORT", None)
|
||||
.define("WEBRTC_POSIX", None)
|
||||
.define("WEBRTC_MAC", None);
|
||||
}
|
||||
"ios" => {
|
||||
builder
|
||||
.flag("-std=c++17")
|
||||
.file("src/objc_test.mm")
|
||||
.define("WEBRTC_ENABLE_OBJC_SYMBOL_EXPORT", None)
|
||||
.define("WEBRTC_MAC", None)
|
||||
.define("WEBRTC_POSIX", None)
|
||||
.define("WEBRTC_IOS", None);
|
||||
}
|
||||
"android" => {
|
||||
let ndk_env = env::var("ANDROID_NDK_HOME").expect(
|
||||
"ANDROID_NDK_HOME is not set, please set it to the path of your Android NDK",
|
||||
);
|
||||
let android_ndk = path::PathBuf::from(ndk_env);
|
||||
|
||||
let host_os = if cfg!(target_os = "linux") {
|
||||
"linux-x86_64"
|
||||
} else if cfg!(target_os = "macos") {
|
||||
"darwin-x86_64"
|
||||
} else if cfg!(target_os = "windows") {
|
||||
"windows-x86_64"
|
||||
} else {
|
||||
panic!("Unsupported host OS");
|
||||
};
|
||||
|
||||
let toolchain = android_ndk.join(std::format!("toolchains/llvm/prebuilt/{}", host_os));
|
||||
|
||||
// libgcc ( redirects to libunwind )
|
||||
println!(
|
||||
"cargo:rustc-link-search={}",
|
||||
toolchain.join("lib").to_str().unwrap()
|
||||
);
|
||||
|
||||
// Needed when loading the library in the JVM ( System.loadLibrary )
|
||||
println!("cargo:rustc-link-lib=egl");
|
||||
println!("cargo:rustc-link-lib=OpenSLES");
|
||||
|
||||
// Find JNI symbols
|
||||
let readelf_output = std::process::Command::new(toolchain.join("bin/llvm-readelf"))
|
||||
.arg("-Ws")
|
||||
.arg(webrtc_lib.join("/libwebrtc.a"))
|
||||
.output()
|
||||
.expect("failed to run llvm-readelf");
|
||||
|
||||
// Get all JNI symbols
|
||||
let jni_regex = Regex::new(r"(Java_org_webrtc.*)").unwrap();
|
||||
let content = &String::from_utf8_lossy(&readelf_output.stdout);
|
||||
let mut jni_symbols = Vec::new();
|
||||
jni_regex.captures_iter(content).for_each(|cap| {
|
||||
jni_symbols.push(cap.get(1).unwrap().as_str());
|
||||
});
|
||||
|
||||
// JNI Version Script & Keep JNI symbols
|
||||
let vs_path = path::PathBuf::from(env::var("OUT_DIR").unwrap()).join("webrtc_jni.map");
|
||||
let mut vs_file = fs::File::create(&vs_path).unwrap();
|
||||
builder.file("src/jni_onload.cc");
|
||||
println!("cargo:rustc-link-arg=-Wl,--undefined=JNI_OnLoad");
|
||||
|
||||
write!(vs_file, "JNI_WEBRTC {{\n\tglobal: ").unwrap();
|
||||
write!(vs_file, "JNI_OnLoad; ").unwrap();
|
||||
for x in jni_symbols {
|
||||
println!("cargo:rustc-link-arg=-Wl,--undefined={}", x);
|
||||
write!(vs_file, "{}; ", x).unwrap();
|
||||
}
|
||||
write!(vs_file, "\n}};").unwrap();
|
||||
|
||||
println!(
|
||||
"cargo:rustc-link-arg=-Wl,--version-script={}",
|
||||
vs_path.to_str().unwrap()
|
||||
);
|
||||
|
||||
builder
|
||||
.flag("-std=c++17")
|
||||
.define("WEBRTC_LINUX", None)
|
||||
.define("WEBRTC_POSIX", None)
|
||||
.define("WEBRTC_ANDROID", None);
|
||||
}
|
||||
_ => {
|
||||
panic!("Unsupported target, {}", target_os);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(theomonnom) Only add this define when building tests
|
||||
builder.define("LIVEKIT_TEST", None);
|
||||
|
||||
builder.warnings(false).compile("lkwebrtc");
|
||||
|
||||
for entry in glob::glob("./src/**/*.cpp").unwrap() {
|
||||
println!("cargo:rerun-if-changed={}", entry.unwrap().display());
|
||||
}
|
||||
|
||||
for entry in glob::glob("./include/**/*.h").unwrap() {
|
||||
println!("cargo:rerun-if-changed={}", entry.unwrap().display());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
-xc++
|
||||
-std=c++17
|
||||
-Iinclude
|
||||
-Ilibwebrtc/src
|
||||
-Ilibwebrtc/src/third_party/abseil-cpp
|
||||
-Ilibwebrtc/src/third_party/libc++
|
||||
-I../../../target/cxxbridge
|
||||
-DWEBRTC_WIN
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// Created by Théo Monnom on 01/09/2022.
|
||||
//
|
||||
|
||||
#ifndef CLIENT_SDK_NATIVE_CANDIDATE_H
|
||||
#define CLIENT_SDK_NATIVE_CANDIDATE_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "api/candidate.h"
|
||||
|
||||
// cricket::Candidate
|
||||
namespace livekit {
|
||||
|
||||
class Candidate {
|
||||
public:
|
||||
explicit Candidate(const cricket::Candidate& candidate);
|
||||
|
||||
private:
|
||||
cricket::Candidate candidate_;
|
||||
};
|
||||
|
||||
static std::unique_ptr<Candidate> _unique_candidate() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace livekit
|
||||
|
||||
#endif // CLIENT_SDK_NATIVE_CANDIDATE_H
|
||||
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// Created by Théo Monnom on 01/09/2022.
|
||||
//
|
||||
|
||||
#ifndef CLIENT_SDK_NATIVE_DATA_CHANNEL_H
|
||||
#define CLIENT_SDK_NATIVE_DATA_CHANNEL_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "api/data_channel_interface.h"
|
||||
#include "rust/cxx.h"
|
||||
#include "rust_types.h"
|
||||
#include "webrtc.h"
|
||||
|
||||
namespace livekit {
|
||||
using NativeDataChannelInit = webrtc::DataChannelInit;
|
||||
class NativeDataChannelObserver;
|
||||
|
||||
class DataChannel {
|
||||
public:
|
||||
explicit DataChannel(
|
||||
std::shared_ptr<RTCRuntime> rtc_runtime,
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel);
|
||||
|
||||
void register_observer(NativeDataChannelObserver& observer);
|
||||
void unregister_observer();
|
||||
bool send(const DataBuffer& buffer) const;
|
||||
rust::String label() const;
|
||||
DataState state() const;
|
||||
void close() const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<RTCRuntime> rtc_runtime_;
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel_;
|
||||
};
|
||||
|
||||
std::unique_ptr<NativeDataChannelInit> create_data_channel_init(
|
||||
DataChannelInit init);
|
||||
|
||||
static std::unique_ptr<DataChannel> _unique_data_channel() {
|
||||
return nullptr; // Ignore
|
||||
}
|
||||
|
||||
class NativeDataChannelObserver : public webrtc::DataChannelObserver {
|
||||
public:
|
||||
explicit NativeDataChannelObserver(
|
||||
rust::Box<DataChannelObserverWrapper> observer);
|
||||
|
||||
void OnStateChange() override;
|
||||
void OnMessage(const webrtc::DataBuffer& buffer) override;
|
||||
void OnBufferedAmountChange(uint64_t sent_data_size) override;
|
||||
|
||||
private:
|
||||
rust::Box<DataChannelObserverWrapper> observer_;
|
||||
};
|
||||
|
||||
std::unique_ptr<NativeDataChannelObserver> create_native_data_channel_observer(
|
||||
rust::Box<DataChannelObserverWrapper> observer);
|
||||
} // namespace livekit
|
||||
|
||||
#endif // CLIENT_SDK_NATIVE_DATA_CHANNEL_H
|
||||
@@ -0,0 +1,130 @@
|
||||
//
|
||||
// Created by Théo Monnom on 01/09/2022.
|
||||
//
|
||||
|
||||
#ifndef CLIENT_SDK_NATIVE_JSEP_H
|
||||
#define CLIENT_SDK_NATIVE_JSEP_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "api/jsep.h"
|
||||
#include "api/ref_counted_base.h"
|
||||
#include "rust/cxx.h"
|
||||
#include "rust_types.h"
|
||||
|
||||
namespace livekit {
|
||||
|
||||
class IceCandidate {
|
||||
public:
|
||||
explicit IceCandidate(
|
||||
std::unique_ptr<webrtc::IceCandidateInterface> ice_candidate);
|
||||
|
||||
rust::String sdp_mid() const;
|
||||
int sdp_mline_index() const;
|
||||
rust::String candidate() const; // TODO(theomonnom) Return livekit::Candidate
|
||||
// instead of rust::String
|
||||
|
||||
rust::String stringify() const;
|
||||
std::unique_ptr<webrtc::IceCandidateInterface> release();
|
||||
|
||||
private:
|
||||
std::unique_ptr<webrtc::IceCandidateInterface> ice_candidate_;
|
||||
};
|
||||
|
||||
std::unique_ptr<IceCandidate> create_ice_candidate(rust::String sdp_mid,
|
||||
int sdp_mline_index,
|
||||
rust::String sdp);
|
||||
|
||||
static std::unique_ptr<IceCandidate> _unique_ice_candidate() {
|
||||
return nullptr; // Ignore
|
||||
}
|
||||
|
||||
class SessionDescription {
|
||||
public:
|
||||
explicit SessionDescription(
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> session_description);
|
||||
|
||||
rust::String stringify() const;
|
||||
std::unique_ptr<SessionDescription> clone() const;
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> release();
|
||||
|
||||
private:
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> session_description_;
|
||||
};
|
||||
|
||||
std::unique_ptr<SessionDescription> create_session_description(
|
||||
SdpType type,
|
||||
rust::String sdp);
|
||||
|
||||
static std::unique_ptr<SessionDescription> _unique_session_description() {
|
||||
return nullptr; // Ignore
|
||||
}
|
||||
|
||||
// SetCreateSdpObserver
|
||||
|
||||
class NativeCreateSdpObserver
|
||||
: public webrtc::CreateSessionDescriptionObserver {
|
||||
public:
|
||||
explicit NativeCreateSdpObserver(
|
||||
rust::Box<CreateSdpObserverWrapper> observer);
|
||||
|
||||
void OnSuccess(webrtc::SessionDescriptionInterface* desc) override;
|
||||
void OnFailure(webrtc::RTCError error) override;
|
||||
|
||||
private:
|
||||
rust::Box<CreateSdpObserverWrapper> observer_;
|
||||
};
|
||||
|
||||
struct NativeCreateSdpObserverHandle {
|
||||
rtc::scoped_refptr<NativeCreateSdpObserver> observer;
|
||||
};
|
||||
|
||||
std::unique_ptr<NativeCreateSdpObserverHandle>
|
||||
create_native_create_sdp_observer(rust::Box<CreateSdpObserverWrapper> observer);
|
||||
|
||||
// SetLocalSdpObserver
|
||||
|
||||
class NativeSetLocalSdpObserver
|
||||
: public webrtc::SetLocalDescriptionObserverInterface {
|
||||
public:
|
||||
explicit NativeSetLocalSdpObserver(
|
||||
rust::Box<SetLocalSdpObserverWrapper> observer);
|
||||
|
||||
void OnSetLocalDescriptionComplete(webrtc::RTCError error) override;
|
||||
|
||||
private:
|
||||
rust::Box<SetLocalSdpObserverWrapper> observer_;
|
||||
};
|
||||
|
||||
struct NativeSetLocalSdpObserverHandle {
|
||||
rtc::scoped_refptr<NativeSetLocalSdpObserver> observer;
|
||||
};
|
||||
|
||||
std::unique_ptr<NativeSetLocalSdpObserverHandle>
|
||||
create_native_set_local_sdp_observer(
|
||||
rust::Box<SetLocalSdpObserverWrapper> observer);
|
||||
|
||||
// SetRemoteSdpObserver
|
||||
|
||||
class NativeSetRemoteSdpObserver
|
||||
: public webrtc::SetRemoteDescriptionObserverInterface {
|
||||
public:
|
||||
explicit NativeSetRemoteSdpObserver(
|
||||
rust::Box<SetRemoteSdpObserverWrapper> observer);
|
||||
|
||||
void OnSetRemoteDescriptionComplete(webrtc::RTCError error) override;
|
||||
|
||||
private:
|
||||
rust::Box<SetRemoteSdpObserverWrapper> observer_;
|
||||
};
|
||||
|
||||
struct NativeSetRemoteSdpObserverHandle {
|
||||
rtc::scoped_refptr<NativeSetRemoteSdpObserver> observer;
|
||||
};
|
||||
|
||||
std::unique_ptr<NativeSetRemoteSdpObserverHandle>
|
||||
create_native_set_remote_sdp_observer(
|
||||
rust::Box<SetRemoteSdpObserverWrapper> observer);
|
||||
} // namespace livekit
|
||||
|
||||
#endif // CLIENT_SDK_NATIVE_JSEP_H
|
||||
@@ -0,0 +1,123 @@
|
||||
//
|
||||
// Created by Théo Monnom on 31/08/2022.
|
||||
//
|
||||
|
||||
#ifndef CLIENT_SDK_NATIVE_MEDIA_STREAM_INTERFACE_H
|
||||
#define CLIENT_SDK_NATIVE_MEDIA_STREAM_INTERFACE_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "api/media_stream_interface.h"
|
||||
#include "livekit/rust_types.h"
|
||||
#include "rust/cxx.h"
|
||||
|
||||
namespace livekit {
|
||||
|
||||
class NativeVideoFrameSink;
|
||||
|
||||
class MediaStream {
|
||||
public:
|
||||
explicit MediaStream(rtc::scoped_refptr<webrtc::MediaStreamInterface> stream);
|
||||
|
||||
rust::String id() const;
|
||||
|
||||
private:
|
||||
rtc::scoped_refptr<webrtc::MediaStreamInterface> media_stream_;
|
||||
};
|
||||
|
||||
static std::unique_ptr<MediaStream> _unique_media_stream() {
|
||||
return nullptr; // Ignore
|
||||
}
|
||||
|
||||
class MediaStreamTrack {
|
||||
protected:
|
||||
explicit MediaStreamTrack(
|
||||
rtc::scoped_refptr<webrtc::MediaStreamTrackInterface> track);
|
||||
|
||||
public:
|
||||
static std::unique_ptr<MediaStreamTrack> from(
|
||||
rtc::scoped_refptr<webrtc::MediaStreamTrackInterface> track);
|
||||
|
||||
rust::String kind() const;
|
||||
rust::String id() const;
|
||||
|
||||
bool enabled() const;
|
||||
bool set_enabled(bool enable);
|
||||
|
||||
TrackState state() const;
|
||||
|
||||
protected:
|
||||
rtc::scoped_refptr<webrtc::MediaStreamTrackInterface> track_;
|
||||
};
|
||||
|
||||
static std::unique_ptr<MediaStreamTrack> _unique_media_stream_track() {
|
||||
return nullptr; // Ignore
|
||||
}
|
||||
|
||||
class AudioTrack : public MediaStreamTrack {
|
||||
public:
|
||||
explicit AudioTrack(rtc::scoped_refptr<webrtc::AudioTrackInterface> track);
|
||||
};
|
||||
|
||||
static std::unique_ptr<AudioTrack> _unique_audio_track() {
|
||||
return nullptr; // Ignore
|
||||
}
|
||||
|
||||
class VideoTrack : public MediaStreamTrack {
|
||||
public:
|
||||
explicit VideoTrack(rtc::scoped_refptr<webrtc::VideoTrackInterface> track);
|
||||
|
||||
void add_sink(NativeVideoFrameSink& sink);
|
||||
void remove_sink(NativeVideoFrameSink& sink);
|
||||
|
||||
void set_should_receive(bool should_receive);
|
||||
bool should_receive() const;
|
||||
ContentHint content_hint() const;
|
||||
void set_content_hint(ContentHint hint);
|
||||
|
||||
private:
|
||||
webrtc::VideoTrackInterface* track() const {
|
||||
return static_cast<webrtc::VideoTrackInterface*>(track_.get());
|
||||
}
|
||||
};
|
||||
|
||||
static std::unique_ptr<VideoTrack> _unique_video_track() {
|
||||
return nullptr; // Ignore
|
||||
}
|
||||
|
||||
class NativeVideoFrameSink
|
||||
: public rtc::VideoSinkInterface<webrtc::VideoFrame> {
|
||||
public:
|
||||
explicit NativeVideoFrameSink(rust::Box<VideoFrameSinkWrapper> observer);
|
||||
|
||||
void OnFrame(const webrtc::VideoFrame& frame) override;
|
||||
void OnDiscardedFrame() override;
|
||||
void OnConstraintsChanged(
|
||||
const webrtc::VideoTrackSourceConstraints& constraints) override;
|
||||
|
||||
private:
|
||||
rust::Box<VideoFrameSinkWrapper> observer_;
|
||||
};
|
||||
|
||||
std::unique_ptr<NativeVideoFrameSink> create_native_video_frame_sink(
|
||||
rust::Box<VideoFrameSinkWrapper> observer);
|
||||
|
||||
static const MediaStreamTrack* video_to_media(const VideoTrack* track) {
|
||||
return track;
|
||||
}
|
||||
|
||||
static const MediaStreamTrack* audio_to_media(const AudioTrack* track) {
|
||||
return track;
|
||||
}
|
||||
|
||||
static const VideoTrack* media_to_video(const MediaStreamTrack* track) {
|
||||
return static_cast<const VideoTrack*>(track);
|
||||
}
|
||||
|
||||
static const AudioTrack* media_to_audio(const MediaStreamTrack* track) {
|
||||
return static_cast<const AudioTrack*>(track);
|
||||
}
|
||||
|
||||
} // namespace livekit
|
||||
|
||||
#endif // CLIENT_SDK_NATIVE_MEDIA_STREAM_INTERFACE_H
|
||||
@@ -0,0 +1,144 @@
|
||||
//
|
||||
// Created by Théo Monnom on 30/08/2022.
|
||||
//
|
||||
|
||||
#ifndef CLIENT_SDK_NATIVE_PEER_CONNECTION_H
|
||||
#define CLIENT_SDK_NATIVE_PEER_CONNECTION_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "api/peer_connection_interface.h"
|
||||
#include "data_channel.h"
|
||||
#include "jsep.h"
|
||||
#include "rust/cxx.h"
|
||||
#include "rust_types.h"
|
||||
#include "webrtc.h"
|
||||
|
||||
namespace livekit {
|
||||
class NativeAddIceCandidateObserver;
|
||||
|
||||
class PeerConnection {
|
||||
public:
|
||||
explicit PeerConnection(
|
||||
std::shared_ptr<RTCRuntime> rtc_runtime,
|
||||
rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection);
|
||||
|
||||
void create_offer(NativeCreateSdpObserverHandle& observer,
|
||||
RTCOfferAnswerOptions options);
|
||||
void create_answer(NativeCreateSdpObserverHandle& observer,
|
||||
RTCOfferAnswerOptions options);
|
||||
void set_local_description(std::unique_ptr<SessionDescription> desc,
|
||||
NativeSetLocalSdpObserverHandle& observer);
|
||||
void set_remote_description(std::unique_ptr<SessionDescription> desc,
|
||||
NativeSetRemoteSdpObserverHandle& observer);
|
||||
std::unique_ptr<DataChannel> create_data_channel(
|
||||
rust::String label,
|
||||
std::unique_ptr<NativeDataChannelInit> init);
|
||||
void add_ice_candidate(std::unique_ptr<IceCandidate> candidate,
|
||||
NativeAddIceCandidateObserver& observer);
|
||||
std::unique_ptr<SessionDescription> local_description() const;
|
||||
std::unique_ptr<SessionDescription> remote_description() const;
|
||||
SignalingState signaling_state() const;
|
||||
IceGatheringState ice_gathering_state() const;
|
||||
IceConnectionState ice_connection_state() const;
|
||||
void close();
|
||||
|
||||
private:
|
||||
std::shared_ptr<RTCRuntime> rtc_runtime_;
|
||||
rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_;
|
||||
};
|
||||
|
||||
static std::unique_ptr<PeerConnection> _unique_peer_connection() {
|
||||
return nullptr; // Ignore
|
||||
}
|
||||
|
||||
class NativeAddIceCandidateObserver {
|
||||
public:
|
||||
explicit NativeAddIceCandidateObserver(
|
||||
rust::Box<AddIceCandidateObserverWrapper> observer);
|
||||
|
||||
void OnComplete(const RTCError& error);
|
||||
|
||||
private:
|
||||
rust::Box<AddIceCandidateObserverWrapper> observer_;
|
||||
};
|
||||
|
||||
std::unique_ptr<NativeAddIceCandidateObserver>
|
||||
create_native_add_ice_candidate_observer(
|
||||
rust::Box<AddIceCandidateObserverWrapper> observer);
|
||||
|
||||
class NativePeerConnectionObserver : public webrtc::PeerConnectionObserver {
|
||||
public:
|
||||
explicit NativePeerConnectionObserver(
|
||||
std::shared_ptr<RTCRuntime> rtc_runtime,
|
||||
rust::Box<PeerConnectionObserverWrapper> observer);
|
||||
|
||||
void OnSignalingChange(
|
||||
webrtc::PeerConnectionInterface::SignalingState new_state) override;
|
||||
|
||||
void OnAddStream(
|
||||
rtc::scoped_refptr<webrtc::MediaStreamInterface> stream) override;
|
||||
|
||||
void OnRemoveStream(
|
||||
rtc::scoped_refptr<webrtc::MediaStreamInterface> stream) override;
|
||||
|
||||
void OnDataChannel(
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel) override;
|
||||
|
||||
void OnRenegotiationNeeded() override;
|
||||
|
||||
void OnNegotiationNeededEvent(uint32_t event_id) override;
|
||||
|
||||
void OnIceConnectionChange(
|
||||
webrtc::PeerConnectionInterface::IceConnectionState new_state) override;
|
||||
|
||||
void OnStandardizedIceConnectionChange(
|
||||
webrtc::PeerConnectionInterface::IceConnectionState new_state) override;
|
||||
|
||||
void OnConnectionChange(
|
||||
webrtc::PeerConnectionInterface::PeerConnectionState new_state) override;
|
||||
|
||||
void OnIceGatheringChange(
|
||||
webrtc::PeerConnectionInterface::IceGatheringState new_state) override;
|
||||
|
||||
void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override;
|
||||
|
||||
void OnIceCandidateError(const std::string& address,
|
||||
int port,
|
||||
const std::string& url,
|
||||
int error_code,
|
||||
const std::string& error_text) override;
|
||||
|
||||
void OnIceCandidatesRemoved(
|
||||
const std::vector<cricket::Candidate>& candidates) override;
|
||||
|
||||
void OnIceConnectionReceivingChange(bool receiving) override;
|
||||
|
||||
void OnIceSelectedCandidatePairChanged(
|
||||
const cricket::CandidatePairChangeEvent& event) override;
|
||||
|
||||
void OnAddTrack(
|
||||
rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver,
|
||||
const std::vector<rtc::scoped_refptr<webrtc::MediaStreamInterface>>&
|
||||
streams) override;
|
||||
|
||||
void OnTrack(
|
||||
rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver) override;
|
||||
|
||||
void OnRemoveTrack(
|
||||
rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) override;
|
||||
|
||||
void OnInterestingUsage(int usage_pattern) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<RTCRuntime> rtc_runtime_;
|
||||
rust::Box<PeerConnectionObserverWrapper> observer_;
|
||||
};
|
||||
|
||||
std::unique_ptr<NativePeerConnectionObserver>
|
||||
create_native_peer_connection_observer(
|
||||
std::shared_ptr<RTCRuntime> rtc_runtime,
|
||||
rust::Box<PeerConnectionObserverWrapper> observer);
|
||||
} // namespace livekit
|
||||
|
||||
#endif // CLIENT_SDK_NATIVE_PEER_CONNECTION_H
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Created by Théo Monnom on 03/08/2022.
|
||||
//
|
||||
|
||||
#ifndef PEER_CONNECTION_FACTORY_H
|
||||
#define PEER_CONNECTION_FACTORY_H
|
||||
|
||||
#include "api/peer_connection_interface.h"
|
||||
#include "peer_connection.h"
|
||||
#include "rust_types.h"
|
||||
#include "webrtc.h"
|
||||
|
||||
namespace livekit {
|
||||
using NativeRTCConfiguration =
|
||||
webrtc::PeerConnectionInterface::RTCConfiguration;
|
||||
|
||||
class PeerConnectionFactory {
|
||||
public:
|
||||
explicit PeerConnectionFactory(std::shared_ptr<RTCRuntime> rtc_runtime);
|
||||
~PeerConnectionFactory();
|
||||
|
||||
std::unique_ptr<PeerConnection> create_peer_connection(
|
||||
std::unique_ptr<NativeRTCConfiguration> config,
|
||||
NativePeerConnectionObserver& observer) const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<RTCRuntime> rtc_runtime_;
|
||||
rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> peer_factory_;
|
||||
};
|
||||
|
||||
std::unique_ptr<PeerConnectionFactory> create_peer_connection_factory(
|
||||
std::shared_ptr<RTCRuntime> rtc_runtime);
|
||||
std::unique_ptr<NativeRTCConfiguration> create_rtc_configuration(
|
||||
RTCConfiguration conf);
|
||||
} // namespace livekit
|
||||
|
||||
#endif // PEER_CONNECTION_FACTORY_H
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Created by theom on 04/09/2022.
|
||||
//
|
||||
|
||||
#ifndef CLIENT_SDK_NATIVE_RTC_ERROR_H
|
||||
#define CLIENT_SDK_NATIVE_RTC_ERROR_H
|
||||
|
||||
#include "api/rtc_error.h"
|
||||
#include "webrtc-sys/src/rtc_error.rs.h"
|
||||
#include "rust/cxx.h"
|
||||
#include "rust_types.h"
|
||||
|
||||
namespace livekit {
|
||||
|
||||
RTCError to_error(const webrtc::RTCError& error);
|
||||
std::string serialize_error(
|
||||
const RTCError& error); // to be used inside cxx::Exception msg
|
||||
|
||||
#ifdef LIVEKIT_TEST
|
||||
rust::String serialize_deserialize();
|
||||
void throw_error();
|
||||
#endif
|
||||
|
||||
} // namespace livekit
|
||||
|
||||
#endif // CLIENT_SDK_NATIVE_RTC_ERROR_H
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Created by Théo Monnom on 01/09/2022.
|
||||
//
|
||||
|
||||
#ifndef CLIENT_SDK_NATIVE_RTP_RECEIVER_H
|
||||
#define CLIENT_SDK_NATIVE_RTP_RECEIVER_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "api/rtp_receiver_interface.h"
|
||||
#include "livekit/media_stream.h"
|
||||
|
||||
namespace livekit {
|
||||
|
||||
class RtpReceiver {
|
||||
public:
|
||||
explicit RtpReceiver(
|
||||
rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver);
|
||||
|
||||
std::unique_ptr<MediaStreamTrack> track() const;
|
||||
|
||||
private:
|
||||
rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver_;
|
||||
};
|
||||
|
||||
static std::unique_ptr<RtpReceiver> _unique_rtp_receiver() {
|
||||
return nullptr; // Ignore
|
||||
}
|
||||
} // namespace livekit
|
||||
|
||||
#endif // CLIENT_SDK_NATIVE_RTP_RECEIVER_H
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Created by Théo Monnom on 02/09/2022.
|
||||
//
|
||||
|
||||
#ifndef CLIENT_SDK_NATIVE_RTP_TRANSCEIVER_H
|
||||
#define CLIENT_SDK_NATIVE_RTP_TRANSCEIVER_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "api/rtp_transceiver_interface.h"
|
||||
|
||||
namespace livekit {
|
||||
|
||||
class RtpTransceiver {
|
||||
public:
|
||||
explicit RtpTransceiver(
|
||||
rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver);
|
||||
|
||||
private:
|
||||
rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver_;
|
||||
};
|
||||
|
||||
static std::unique_ptr<RtpTransceiver> _unique_rtp_transceiver() {
|
||||
return nullptr; // Ignore
|
||||
}
|
||||
} // namespace livekit
|
||||
|
||||
#endif // CLIENT_SDK_NATIVE_RTP_TRANSCEIVER_H
|
||||
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// Created by Théo Monnom on 30/08/2022.
|
||||
//
|
||||
|
||||
#ifndef RUST_TYPES_H
|
||||
#define RUST_TYPES_H
|
||||
|
||||
#include "api/peer_connection_interface.h"
|
||||
|
||||
namespace livekit {
|
||||
struct RTCConfiguration;
|
||||
struct PeerConnectionObserverWrapper;
|
||||
struct CreateSdpObserverWrapper;
|
||||
struct SetLocalSdpObserverWrapper;
|
||||
struct SetRemoteSdpObserverWrapper;
|
||||
struct DataChannelObserverWrapper;
|
||||
struct AddIceCandidateObserverWrapper;
|
||||
struct VideoFrameSinkWrapper;
|
||||
|
||||
// Shared types
|
||||
enum class PeerConnectionState;
|
||||
enum class SignalingState;
|
||||
enum class IceConnectionState;
|
||||
enum class IceGatheringState;
|
||||
enum class SdpType;
|
||||
enum class DataState;
|
||||
enum class TrackState;
|
||||
enum class ContentHint;
|
||||
enum class VideoRotation;
|
||||
enum class VideoFrameBufferType;
|
||||
struct SdpParseError;
|
||||
struct RTCOfferAnswerOptions;
|
||||
struct RTCError;
|
||||
struct DataChannelInit;
|
||||
struct DataBuffer;
|
||||
} // namespace livekit
|
||||
|
||||
#endif // RUST_TYPES_H
|
||||
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// Created by theom on 14/11/2022.
|
||||
//
|
||||
|
||||
#ifndef LIVEKIT_WEBRTC_VIDEO_FRAME_H
|
||||
#define LIVEKIT_WEBRTC_VIDEO_FRAME_H
|
||||
|
||||
#include "api/video/video_frame.h"
|
||||
#include "livekit/rust_types.h"
|
||||
#include "livekit/video_frame_buffer.h"
|
||||
|
||||
namespace livekit {
|
||||
|
||||
class VideoFrame {
|
||||
public:
|
||||
explicit VideoFrame(const webrtc::VideoFrame& frame)
|
||||
: frame_(std::move(frame)) {}
|
||||
|
||||
int width() const { return frame_.width(); }
|
||||
int height() const { return frame_.height(); }
|
||||
uint32_t size() const { return frame_.size(); }
|
||||
uint16_t id() const { return frame_.id(); }
|
||||
int64_t timestamp_us() const { return frame_.timestamp_us(); }
|
||||
int64_t ntp_time_ms() const { return frame_.ntp_time_ms(); }
|
||||
uint32_t transport_frame_id() const { return frame_.transport_frame_id(); }
|
||||
uint32_t timestamp() const { return frame_.timestamp(); }
|
||||
|
||||
VideoRotation rotation() const {
|
||||
return static_cast<VideoRotation>(frame_.rotation());
|
||||
}
|
||||
|
||||
// TODO(theomonnom) This shouldn't create a new shared_ptr at each call
|
||||
std::unique_ptr<VideoFrameBuffer> video_frame_buffer() const {
|
||||
return std::make_unique<VideoFrameBuffer>(frame_.video_frame_buffer());
|
||||
}
|
||||
|
||||
private:
|
||||
webrtc::VideoFrame frame_;
|
||||
};
|
||||
|
||||
static std::unique_ptr<VideoFrame> _unique_video_frame() {
|
||||
return nullptr; // Ignore
|
||||
}
|
||||
|
||||
|
||||
} // namespace livekit
|
||||
|
||||
#endif // LIVEKIT_WEBRTC_VIDEO_FRAME_H
|
||||
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// Created by theom on 14/11/2022.
|
||||
//
|
||||
|
||||
#ifndef LIVEKIT_WEBRTC_VIDEO_FRAME_BUFFER_H
|
||||
#define LIVEKIT_WEBRTC_VIDEO_FRAME_BUFFER_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "api/video/video_frame_buffer.h"
|
||||
#include "rust_types.h"
|
||||
|
||||
namespace livekit {
|
||||
|
||||
class PlanarYuvBuffer;
|
||||
class PlanarYuv8Buffer;
|
||||
class I420Buffer;
|
||||
|
||||
class VideoFrameBuffer {
|
||||
public:
|
||||
explicit VideoFrameBuffer(rtc::scoped_refptr<webrtc::VideoFrameBuffer> buffer)
|
||||
: buffer_(std::move(buffer)) {}
|
||||
|
||||
VideoFrameBufferType buffer_type() const {
|
||||
return static_cast<VideoFrameBufferType>(buffer_->type());
|
||||
}
|
||||
|
||||
int width() const { return buffer_->width(); }
|
||||
int height() const { return buffer_->height(); }
|
||||
|
||||
std::unique_ptr<I420Buffer> to_i420() {
|
||||
return std::make_unique<I420Buffer>(buffer_->ToI420());
|
||||
}
|
||||
|
||||
std::unique_ptr<I420Buffer> get_i420() {
|
||||
// const_cast is valid here because we take the ownership on the rust side
|
||||
return std::make_unique<I420Buffer>(
|
||||
rtc::scoped_refptr<webrtc::I420BufferInterface>(
|
||||
const_cast<webrtc::I420BufferInterface*>(buffer_->GetI420())));
|
||||
}
|
||||
|
||||
protected:
|
||||
rtc::scoped_refptr<webrtc::VideoFrameBuffer> buffer_;
|
||||
};
|
||||
|
||||
class PlanarYuvBuffer : public VideoFrameBuffer {
|
||||
public:
|
||||
explicit PlanarYuvBuffer(rtc::scoped_refptr<webrtc::PlanarYuvBuffer> buffer)
|
||||
: VideoFrameBuffer(buffer) {}
|
||||
|
||||
int chroma_width() const { return buffer()->ChromaWidth(); }
|
||||
int chroma_height() const { return buffer()->ChromaHeight(); }
|
||||
|
||||
int stride_y() const { return buffer()->StrideY(); }
|
||||
int stride_u() const { return buffer()->StrideU(); }
|
||||
int stride_v() const { return buffer()->StrideV(); }
|
||||
|
||||
private:
|
||||
webrtc::PlanarYuvBuffer* buffer() const {
|
||||
return static_cast<webrtc::PlanarYuvBuffer*>(buffer_.get());
|
||||
}
|
||||
};
|
||||
|
||||
class PlanarYuv8Buffer : public PlanarYuvBuffer {
|
||||
public:
|
||||
explicit PlanarYuv8Buffer(rtc::scoped_refptr<webrtc::PlanarYuv8Buffer> buffer)
|
||||
: PlanarYuvBuffer(buffer) {}
|
||||
|
||||
const uint8_t* data_y() const { return buffer()->DataY(); }
|
||||
const uint8_t* data_u() const { return buffer()->DataU(); }
|
||||
const uint8_t* data_v() const { return buffer()->DataV(); }
|
||||
|
||||
private:
|
||||
webrtc::PlanarYuv8Buffer* buffer() const {
|
||||
return static_cast<webrtc::PlanarYuv8Buffer*>(buffer_.get());
|
||||
}
|
||||
};
|
||||
|
||||
class I420Buffer : public PlanarYuv8Buffer {
|
||||
public:
|
||||
explicit I420Buffer(rtc::scoped_refptr<webrtc::I420BufferInterface> buffer)
|
||||
: PlanarYuv8Buffer(buffer) {}
|
||||
};
|
||||
|
||||
static const VideoFrameBuffer* yuv_to_vfb(const PlanarYuvBuffer* yuv) {
|
||||
return yuv;
|
||||
}
|
||||
|
||||
static const PlanarYuvBuffer* yuv8_to_yuv(const PlanarYuv8Buffer* yuv8) {
|
||||
return yuv8;
|
||||
}
|
||||
|
||||
static const PlanarYuv8Buffer* i420_to_yuv8(const I420Buffer* i420) {
|
||||
return i420;
|
||||
}
|
||||
|
||||
static std::unique_ptr<VideoFrameBuffer> _unique_video_frame_buffer() {
|
||||
return nullptr; // Ignore
|
||||
}
|
||||
|
||||
} // namespace livekit
|
||||
|
||||
#endif // LIVEKIT_WEBRTC_VIDEO_FRAME_BUFFER_H
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// Created by theom on 18/09/2022.
|
||||
//
|
||||
|
||||
#ifndef LIVEKIT_WEBRTC_WEBRTC_H
|
||||
#define LIVEKIT_WEBRTC_WEBRTC_H
|
||||
|
||||
#include "rtc_base/physical_socket_server.h"
|
||||
#include "rtc_base/ssl_adapter.h"
|
||||
|
||||
#ifdef WEBRTC_WIN
|
||||
#include "rtc_base/win32_socket_init.h"
|
||||
#endif
|
||||
|
||||
namespace livekit {
|
||||
|
||||
class RTCRuntime {
|
||||
public:
|
||||
RTCRuntime();
|
||||
~RTCRuntime();
|
||||
|
||||
RTCRuntime(const RTCRuntime&) = delete;
|
||||
RTCRuntime& operator=(const RTCRuntime&) = delete;
|
||||
|
||||
rtc::Thread* network_thread() const;
|
||||
rtc::Thread* worker_thread() const;
|
||||
rtc::Thread* signaling_thread() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<rtc::Thread> network_thread_;
|
||||
std::unique_ptr<rtc::Thread> worker_thread_;
|
||||
std::unique_ptr<rtc::Thread> signaling_thread_;
|
||||
#ifdef WEBRTC_WIN
|
||||
rtc::WinsockInitializer winsock_;
|
||||
rtc::PhysicalSocketServer ss_;
|
||||
rtc::AutoSocketServerThread main_thread_ {&ss_};
|
||||
#endif
|
||||
};
|
||||
|
||||
std::shared_ptr<RTCRuntime> create_rtc_runtime();
|
||||
|
||||
} // namespace livekit
|
||||
|
||||
#endif // LIVEKIT_WEBRTC_WEBRTC_H
|
||||
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// Created by Théo Monnom on 01/12/2022.
|
||||
//
|
||||
|
||||
#ifndef CLIENT_SDK_NATIVE_YUV_HELPER_H
|
||||
#define CLIENT_SDK_NATIVE_YUV_HELPER_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "api/video/yuv_helper.h"
|
||||
|
||||
namespace livekit {
|
||||
|
||||
static void i420_to_abgr(const uint8_t* src_y,
|
||||
int src_stride_y,
|
||||
const uint8_t* src_u,
|
||||
int src_stride_u,
|
||||
const uint8_t* src_v,
|
||||
int src_stride_v,
|
||||
uint8_t* dst_rgba,
|
||||
int dst_stride_abgr,
|
||||
int width,
|
||||
int height) {
|
||||
webrtc::I420ToABGR(src_y, src_stride_y, src_u, src_stride_u, src_v,
|
||||
src_stride_v, dst_rgba, dst_stride_abgr, width, height);
|
||||
}
|
||||
|
||||
} // namespace livekit
|
||||
|
||||
#endif // CLIENT_SDK_NATIVE_YUV_HELPER_H
|
||||
@@ -0,0 +1,11 @@
|
||||
solutions = [
|
||||
{
|
||||
"name" : 'src',
|
||||
"url" : 'https://github.com/webrtc-sdk/webrtc.git',
|
||||
"deps_file" : 'DEPS',
|
||||
"managed" : False,
|
||||
"custom_deps" : {
|
||||
},
|
||||
"custom_vars": {},
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
.cipd
|
||||
src
|
||||
.gclient_*
|
||||
@@ -0,0 +1,19 @@
|
||||
import subprocess
|
||||
|
||||
GN_ARGS = [
|
||||
"is_debug=false",
|
||||
"treat_warnings_as_errors=false",
|
||||
'target_os="win"',
|
||||
'target_cpu="x64"',
|
||||
"rtc_include_tests=false",
|
||||
"rtc_use_h264=false",
|
||||
"is_component_build=false",
|
||||
"rtc_build_examples=false",
|
||||
"use_rtti=true",
|
||||
"rtc_build_tools=false",
|
||||
"use_custom_libcxx=false",
|
||||
"strip_debug_info=true",
|
||||
"symbol_level=0"
|
||||
]
|
||||
|
||||
subprocess.call(["gn", "gen", "out/Default", "--args=" + ' '.join(GN_ARGS)], shell=True)
|
||||
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// Created by Théo Monnom on 01/09/2022.
|
||||
//
|
||||
|
||||
#include "livekit/candidate.h"
|
||||
|
||||
namespace livekit {
|
||||
Candidate::Candidate(const cricket::Candidate& candidate)
|
||||
: candidate_(candidate) {}
|
||||
} // namespace livekit
|
||||
@@ -0,0 +1,10 @@
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
unsafe extern "C++" {
|
||||
include!("livekit/candidate.h");
|
||||
|
||||
type Candidate; // cricket::Candidate
|
||||
|
||||
fn _unique_candidate() -> UniquePtr<Candidate>; // Ignore
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// Created by Théo Monnom on 01/09/2022.
|
||||
//
|
||||
|
||||
#include "livekit/data_channel.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "webrtc-sys/src/data_channel.rs.h"
|
||||
|
||||
namespace livekit {
|
||||
|
||||
DataChannel::DataChannel(
|
||||
std::shared_ptr<RTCRuntime> rtc_runtime,
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel)
|
||||
: rtc_runtime_(std::move(rtc_runtime)),
|
||||
data_channel_(std::move(data_channel)) {}
|
||||
|
||||
void DataChannel::register_observer(NativeDataChannelObserver& observer) {
|
||||
data_channel_->RegisterObserver(&observer);
|
||||
}
|
||||
|
||||
void DataChannel::unregister_observer() {
|
||||
data_channel_->UnregisterObserver();
|
||||
}
|
||||
|
||||
bool DataChannel::send(const DataBuffer& buffer) const {
|
||||
return data_channel_->Send(webrtc::DataBuffer{
|
||||
rtc::CopyOnWriteBuffer(buffer.ptr, buffer.len), buffer.binary});
|
||||
}
|
||||
|
||||
rust::String DataChannel::label() const {
|
||||
return data_channel_->label();
|
||||
}
|
||||
|
||||
DataState DataChannel::state() const {
|
||||
return static_cast<DataState>(data_channel_->state());
|
||||
}
|
||||
|
||||
void DataChannel::close() const {
|
||||
return data_channel_->Close();
|
||||
}
|
||||
|
||||
std::unique_ptr<NativeDataChannelInit> create_data_channel_init(
|
||||
DataChannelInit init) {
|
||||
auto rtc_init = std::make_unique<webrtc::DataChannelInit>();
|
||||
rtc_init->id = init.id;
|
||||
rtc_init->negotiated = init.negotiated;
|
||||
rtc_init->ordered = init.ordered;
|
||||
rtc_init->protocol = init.protocol.c_str();
|
||||
rtc_init->reliable = init.reliable;
|
||||
|
||||
if (init.has_max_retransmit_time)
|
||||
rtc_init->maxRetransmitTime = init.max_retransmit_time;
|
||||
|
||||
if (init.has_max_retransmits)
|
||||
rtc_init->maxRetransmits = init.max_retransmits;
|
||||
|
||||
if (init.has_priority)
|
||||
rtc_init->priority = static_cast<webrtc::Priority>(init.priority);
|
||||
|
||||
return rtc_init;
|
||||
}
|
||||
|
||||
NativeDataChannelObserver::NativeDataChannelObserver(
|
||||
rust::Box<DataChannelObserverWrapper> observer)
|
||||
: observer_(std::move(observer)) {}
|
||||
|
||||
void NativeDataChannelObserver::OnStateChange() {
|
||||
observer_->on_state_change();
|
||||
}
|
||||
|
||||
void NativeDataChannelObserver::OnMessage(const webrtc::DataBuffer& buffer) {
|
||||
DataBuffer data{};
|
||||
data.ptr = buffer.data.data();
|
||||
data.len = buffer.data.size();
|
||||
data.binary = buffer.binary;
|
||||
observer_->on_message(data);
|
||||
}
|
||||
|
||||
void NativeDataChannelObserver::OnBufferedAmountChange(
|
||||
uint64_t sent_data_size) {
|
||||
observer_->on_buffered_amount_change(sent_data_size);
|
||||
}
|
||||
|
||||
std::unique_ptr<NativeDataChannelObserver> create_native_data_channel_observer(
|
||||
rust::Box<DataChannelObserverWrapper> observer) {
|
||||
return std::make_unique<NativeDataChannelObserver>(std::move(observer));
|
||||
}
|
||||
} // namespace livekit
|
||||
@@ -0,0 +1,126 @@
|
||||
use std::fmt::Debug;
|
||||
use std::slice;
|
||||
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum Priority {
|
||||
VeryLow,
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DataChannelInit {
|
||||
#[allow(deprecated)]
|
||||
#[deprecated]
|
||||
pub reliable: bool,
|
||||
pub ordered: bool,
|
||||
pub has_max_retransmit_time: bool,
|
||||
pub max_retransmit_time: i32,
|
||||
pub has_max_retransmits: bool,
|
||||
pub max_retransmits: i32,
|
||||
pub protocol: String,
|
||||
pub negotiated: bool,
|
||||
pub id: i32,
|
||||
pub has_priority: bool,
|
||||
pub priority: Priority,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DataBuffer {
|
||||
pub ptr: *const u8,
|
||||
pub len: usize,
|
||||
pub binary: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum DataState {
|
||||
Connecting,
|
||||
Open,
|
||||
Closing,
|
||||
Closed,
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
type DataChannelObserverWrapper;
|
||||
|
||||
fn on_state_change(self: &DataChannelObserverWrapper);
|
||||
fn on_message(self: &DataChannelObserverWrapper, buffer: DataBuffer);
|
||||
fn on_buffered_amount_change(self: &DataChannelObserverWrapper, sent_data_size: u64);
|
||||
}
|
||||
|
||||
unsafe extern "C++" {
|
||||
include!("livekit/data_channel.h");
|
||||
|
||||
type DataChannel;
|
||||
type NativeDataChannelInit;
|
||||
type NativeDataChannelObserver;
|
||||
|
||||
/// SAFETY
|
||||
/// The observer must live as the datachannel uses it
|
||||
unsafe fn register_observer(
|
||||
self: Pin<&mut DataChannel>,
|
||||
observer: Pin<&mut NativeDataChannelObserver>,
|
||||
);
|
||||
|
||||
fn unregister_observer(self: Pin<&mut DataChannel>);
|
||||
fn send(self: &DataChannel, data: &DataBuffer) -> bool;
|
||||
fn label(self: &DataChannel) -> String;
|
||||
fn state(self: &DataChannel) -> DataState;
|
||||
fn close(self: &DataChannel);
|
||||
|
||||
fn create_data_channel_init(init: DataChannelInit) -> UniquePtr<NativeDataChannelInit>;
|
||||
fn create_native_data_channel_observer(
|
||||
observer: Box<DataChannelObserverWrapper>,
|
||||
) -> UniquePtr<NativeDataChannelObserver>;
|
||||
|
||||
fn _unique_data_channel() -> UniquePtr<DataChannel>; // Ignore
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for ffi::DataChannel {}
|
||||
unsafe impl Sync for ffi::DataChannel {}
|
||||
|
||||
unsafe impl Send for ffi::NativeDataChannelObserver {}
|
||||
unsafe impl Sync for ffi::NativeDataChannelObserver {}
|
||||
|
||||
// DataChannelObserver
|
||||
|
||||
pub trait DataChannelObserver: Send {
|
||||
fn on_state_change(&self);
|
||||
fn on_message(&self, data: &[u8], is_binary: bool);
|
||||
fn on_buffered_amount_change(&self, sent_data_size: u64);
|
||||
}
|
||||
|
||||
pub struct DataChannelObserverWrapper {
|
||||
observer: *mut dyn DataChannelObserver,
|
||||
}
|
||||
|
||||
impl DataChannelObserverWrapper {
|
||||
/// SAFETY
|
||||
/// DataChannelObserver must lives as long as DataChannelObserverWrapper does
|
||||
pub unsafe fn new(observer: *mut dyn DataChannelObserver) -> Self {
|
||||
Self { observer }
|
||||
}
|
||||
|
||||
fn on_state_change(&self) {
|
||||
unsafe {
|
||||
(*self.observer).on_state_change();
|
||||
}
|
||||
}
|
||||
|
||||
fn on_message(&self, buffer: ffi::DataBuffer) {
|
||||
unsafe {
|
||||
let data = slice::from_raw_parts(buffer.ptr, buffer.len);
|
||||
(*self.observer).on_message(data, buffer.binary);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_buffered_amount_change(&self, sent_data_size: u64) {
|
||||
unsafe { (*self.observer).on_buffered_amount_change(sent_data_size) };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
//
|
||||
// Created by Théo Monnom on 01/09/2022.
|
||||
//
|
||||
|
||||
#include "livekit/jsep.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
|
||||
#include "webrtc-sys/src/jsep.rs.h"
|
||||
#include "livekit/rtc_error.h"
|
||||
|
||||
namespace livekit {
|
||||
|
||||
std::string serialize_sdp_error(webrtc::SdpParseError error) {
|
||||
std::stringstream ss;
|
||||
ss << std::hex << std::setfill('0');
|
||||
ss << std::setw(8) << (uint32_t)error.line.length();
|
||||
ss << std::dec << std::setw(1) << error.line;
|
||||
ss << std::dec << std::setw(1) << error.description;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
IceCandidate::IceCandidate(
|
||||
std::unique_ptr<webrtc::IceCandidateInterface> ice_candidate)
|
||||
: ice_candidate_(std::move(ice_candidate)) {}
|
||||
|
||||
rust::String IceCandidate::sdp_mid() const {
|
||||
return ice_candidate_->sdp_mid();
|
||||
}
|
||||
|
||||
int IceCandidate::sdp_mline_index() const {
|
||||
return ice_candidate_->sdp_mline_index();
|
||||
}
|
||||
|
||||
rust::String IceCandidate::candidate() const {
|
||||
return stringify();
|
||||
}
|
||||
|
||||
rust::String IceCandidate::stringify() const {
|
||||
std::string str;
|
||||
ice_candidate_->ToString(&str);
|
||||
return rust::String{str};
|
||||
}
|
||||
|
||||
std::unique_ptr<webrtc::IceCandidateInterface> IceCandidate::release() {
|
||||
return std::move(ice_candidate_);
|
||||
}
|
||||
|
||||
std::unique_ptr<IceCandidate> create_ice_candidate(rust::String sdp_mid,
|
||||
int sdp_mline_index,
|
||||
rust::String sdp) {
|
||||
webrtc::SdpParseError error;
|
||||
auto ice_rtc = webrtc::CreateIceCandidate(sdp_mid.c_str(), sdp_mline_index,
|
||||
sdp.c_str(), &error);
|
||||
if (!ice_rtc) {
|
||||
throw std::runtime_error(serialize_sdp_error(error));
|
||||
}
|
||||
|
||||
return std::make_unique<IceCandidate>(
|
||||
std::unique_ptr<webrtc::IceCandidateInterface>(ice_rtc));
|
||||
}
|
||||
|
||||
SessionDescription::SessionDescription(
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> session_description)
|
||||
: session_description_(std::move(session_description)) {}
|
||||
|
||||
rust::String SessionDescription::stringify() const {
|
||||
std::string str;
|
||||
session_description_->ToString(&str);
|
||||
return rust::String{str};
|
||||
}
|
||||
|
||||
std::unique_ptr<SessionDescription> SessionDescription::clone() const {
|
||||
return std::make_unique<SessionDescription>(session_description_->Clone());
|
||||
}
|
||||
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface>
|
||||
SessionDescription::release() {
|
||||
return std::move(session_description_);
|
||||
}
|
||||
|
||||
std::unique_ptr<SessionDescription> create_session_description(
|
||||
SdpType type,
|
||||
rust::String sdp) {
|
||||
webrtc::SdpParseError error;
|
||||
auto rtc_sdp = webrtc::CreateSessionDescription(
|
||||
static_cast<webrtc::SdpType>(type), sdp.c_str(), &error);
|
||||
if (!rtc_sdp) {
|
||||
throw std::runtime_error(serialize_sdp_error(error));
|
||||
}
|
||||
|
||||
return std::make_unique<SessionDescription>(std::move(rtc_sdp));
|
||||
}
|
||||
|
||||
// CreateSdpObserver
|
||||
|
||||
NativeCreateSdpObserver::NativeCreateSdpObserver(
|
||||
rust::Box<CreateSdpObserverWrapper> observer)
|
||||
: observer_(std::move(observer)) {}
|
||||
|
||||
void NativeCreateSdpObserver::OnSuccess(
|
||||
webrtc::SessionDescriptionInterface* desc) {
|
||||
// We have ownership of desc
|
||||
observer_->on_success(std::make_unique<SessionDescription>(
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface>(desc)));
|
||||
}
|
||||
|
||||
void NativeCreateSdpObserver::OnFailure(webrtc::RTCError error) {
|
||||
observer_->on_failure(to_error(error));
|
||||
}
|
||||
|
||||
std::unique_ptr<NativeCreateSdpObserverHandle>
|
||||
create_native_create_sdp_observer(
|
||||
rust::Box<CreateSdpObserverWrapper> observer) {
|
||||
return std::make_unique<NativeCreateSdpObserverHandle>(
|
||||
NativeCreateSdpObserverHandle{
|
||||
rtc::make_ref_counted<NativeCreateSdpObserver>(std::move(observer))});
|
||||
}
|
||||
|
||||
// SetLocalSdpObserver
|
||||
|
||||
NativeSetLocalSdpObserver::NativeSetLocalSdpObserver(
|
||||
rust::Box<SetLocalSdpObserverWrapper> observer)
|
||||
: observer_(std::move(observer)) {}
|
||||
|
||||
void NativeSetLocalSdpObserver::OnSetLocalDescriptionComplete(
|
||||
webrtc::RTCError error) {
|
||||
observer_->on_set_local_description_complete(to_error(error));
|
||||
}
|
||||
|
||||
std::unique_ptr<NativeSetLocalSdpObserverHandle>
|
||||
create_native_set_local_sdp_observer(
|
||||
rust::Box<SetLocalSdpObserverWrapper> observer) {
|
||||
return std::make_unique<NativeSetLocalSdpObserverHandle>(
|
||||
NativeSetLocalSdpObserverHandle{
|
||||
rtc::make_ref_counted<NativeSetLocalSdpObserver>(
|
||||
std::move(observer))});
|
||||
}
|
||||
|
||||
// SetRemoteSdpObserver
|
||||
|
||||
NativeSetRemoteSdpObserver::NativeSetRemoteSdpObserver(
|
||||
rust::Box<SetRemoteSdpObserverWrapper> observer)
|
||||
: observer_(std::move(observer)) {}
|
||||
|
||||
void NativeSetRemoteSdpObserver::OnSetRemoteDescriptionComplete(
|
||||
webrtc::RTCError error) {
|
||||
observer_->on_set_remote_description_complete(to_error(error));
|
||||
}
|
||||
|
||||
std::unique_ptr<NativeSetRemoteSdpObserverHandle>
|
||||
create_native_set_remote_sdp_observer(
|
||||
rust::Box<SetRemoteSdpObserverWrapper> observer) {
|
||||
return std::make_unique<NativeSetRemoteSdpObserverHandle>(
|
||||
NativeSetRemoteSdpObserverHandle{
|
||||
rtc::make_ref_counted<NativeSetRemoteSdpObserver>(
|
||||
std::move(observer))});
|
||||
}
|
||||
|
||||
} // namespace livekit
|
||||
@@ -0,0 +1,210 @@
|
||||
use std::error::Error;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::str::FromStr;
|
||||
|
||||
use cxx::UniquePtr;
|
||||
|
||||
use crate::rtc_error::ffi::RTCError;
|
||||
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum SdpType {
|
||||
Offer,
|
||||
PrAnswer,
|
||||
Answer,
|
||||
Rollback,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SdpParseError {
|
||||
pub line: String,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
type CreateSdpObserverWrapper;
|
||||
fn on_success(
|
||||
self: &CreateSdpObserverWrapper,
|
||||
session_description: UniquePtr<SessionDescription>,
|
||||
);
|
||||
fn on_failure(self: &CreateSdpObserverWrapper, error: RTCError);
|
||||
|
||||
type SetLocalSdpObserverWrapper;
|
||||
fn on_set_local_description_complete(self: &SetLocalSdpObserverWrapper, error: RTCError);
|
||||
|
||||
type SetRemoteSdpObserverWrapper;
|
||||
fn on_set_remote_description_complete(self: &SetRemoteSdpObserverWrapper, error: RTCError);
|
||||
}
|
||||
|
||||
unsafe extern "C++" {
|
||||
include!("webrtc-sys/src/rtc_error.rs.h");
|
||||
include!("livekit/jsep.h");
|
||||
|
||||
type RTCError = crate::rtc_error::ffi::RTCError;
|
||||
type IceCandidate;
|
||||
type SessionDescription;
|
||||
type NativeCreateSdpObserverHandle;
|
||||
type NativeSetLocalSdpObserverHandle;
|
||||
type NativeSetRemoteSdpObserverHandle;
|
||||
|
||||
fn sdp_mid(self: &IceCandidate) -> String;
|
||||
fn sdp_mline_index(self: &IceCandidate) -> i32;
|
||||
fn candidate(self: &IceCandidate) -> String;
|
||||
fn stringify(self: &IceCandidate) -> String;
|
||||
|
||||
fn stringify(self: &SessionDescription) -> String;
|
||||
fn clone(self: &SessionDescription) -> UniquePtr<SessionDescription>;
|
||||
|
||||
fn create_native_create_sdp_observer(
|
||||
observer: Box<CreateSdpObserverWrapper>,
|
||||
) -> UniquePtr<NativeCreateSdpObserverHandle>;
|
||||
fn create_native_set_local_sdp_observer(
|
||||
observer: Box<SetLocalSdpObserverWrapper>,
|
||||
) -> UniquePtr<NativeSetLocalSdpObserverHandle>;
|
||||
fn create_native_set_remote_sdp_observer(
|
||||
observer: Box<SetRemoteSdpObserverWrapper>,
|
||||
) -> UniquePtr<NativeSetRemoteSdpObserverHandle>;
|
||||
|
||||
fn create_ice_candidate(
|
||||
sdp_mid: String,
|
||||
sdp_mline_index: i32,
|
||||
sdp: String,
|
||||
) -> Result<UniquePtr<IceCandidate>>;
|
||||
fn create_session_description(
|
||||
sdp_type: SdpType,
|
||||
sdp: String,
|
||||
) -> Result<UniquePtr<SessionDescription>>;
|
||||
|
||||
fn _unique_ice_candidate() -> UniquePtr<IceCandidate>; // Ignore
|
||||
fn _unique_session_description() -> UniquePtr<SessionDescription>; // Ignore
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ffi::SdpParseError {}
|
||||
|
||||
impl Display for ffi::SdpParseError {
|
||||
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"SdpParseError occurred {}: {}",
|
||||
self.line, self.description
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for ffi::SessionDescription {}
|
||||
|
||||
unsafe impl Sync for ffi::SessionDescription {}
|
||||
|
||||
unsafe impl Send for ffi::IceCandidate {}
|
||||
|
||||
unsafe impl Sync for ffi::IceCandidate {}
|
||||
|
||||
impl ffi::SdpParseError {
|
||||
/// # Safety
|
||||
/// The value must be correctly encoded
|
||||
pub unsafe fn from(value: &str) -> Self {
|
||||
// Parse the hex encoded error from c++
|
||||
let line_length = u32::from_str_radix(&value[0..8], 16).unwrap() as usize + 8;
|
||||
let line = String::from(&value[8..line_length]);
|
||||
let description = String::from(&value[line_length..]);
|
||||
|
||||
Self { line, description }
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ffi::SdpType {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"offer" => Ok(ffi::SdpType::Offer),
|
||||
"pranswer" => Ok(ffi::SdpType::PrAnswer),
|
||||
"answer" => Ok(ffi::SdpType::Answer),
|
||||
"rollback" => Ok(ffi::SdpType::Rollback),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CreateSdpObserver
|
||||
|
||||
pub struct CreateSdpObserverWrapper {
|
||||
pub on_success: ManuallyDrop<Box<dyn FnOnce(UniquePtr<ffi::SessionDescription>) + Send>>,
|
||||
pub on_failure: ManuallyDrop<Box<dyn FnOnce(RTCError) + Send>>,
|
||||
}
|
||||
|
||||
impl CreateSdpObserverWrapper {
|
||||
fn on_success(&self, session_description: UniquePtr<ffi::SessionDescription>) {
|
||||
unsafe {
|
||||
std::ptr::read(&*self.on_success)(session_description);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_failure(&self, error: RTCError) {
|
||||
unsafe {
|
||||
std::ptr::read(&*self.on_failure)(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetLocalSdpObserver
|
||||
|
||||
pub struct SetLocalSdpObserverWrapper(pub ManuallyDrop<Box<dyn FnOnce(RTCError) + Send>>);
|
||||
|
||||
impl SetLocalSdpObserverWrapper {
|
||||
fn on_set_local_description_complete(&self, error: RTCError) {
|
||||
unsafe {
|
||||
std::ptr::read(&*self.0)(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetRemoteSdpObserver
|
||||
|
||||
pub struct SetRemoteSdpObserverWrapper(pub ManuallyDrop<Box<dyn FnOnce(RTCError) + Send>>);
|
||||
|
||||
impl SetRemoteSdpObserverWrapper {
|
||||
fn on_set_remote_description_complete(&self, error: RTCError) {
|
||||
unsafe {
|
||||
std::ptr::read(&*self.0)(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use log::info;
|
||||
|
||||
use crate::jsep::ffi;
|
||||
|
||||
#[test]
|
||||
fn throw_error() {
|
||||
let sdp_string = "v=0
|
||||
o=- 6549709950142776241 2 IN IP4 127.0.0.1
|
||||
s=-
|
||||
t=0 0
|
||||
======================== ERROR HERE
|
||||
a=group:BUNDLE 0
|
||||
a=extmap-allow-mixed
|
||||
a=msid-semantic: WMS
|
||||
m=application 9 UDP/DTLS/SCTP webrtc-datachannel
|
||||
c=IN IP4 0.0.0.0
|
||||
a=ice-ufrag:Tw7h
|
||||
a=ice-pwd:6XOVUD6HpcB4c1M8EB8jXJE9
|
||||
a=ice-options:trickle
|
||||
a=fingerprint:sha-256 4F:EC:23:59:5D:A5:E6:3E:3E:5D:8A:09:B6:FA:04:AA:19:99:49:67:BD:65:93:06:BB:EE:AC:D5:21:0F:57:D6
|
||||
a=setup:actpass
|
||||
a=mid:0
|
||||
a=sctp-port:5000
|
||||
a=max-message-size:262144
|
||||
";
|
||||
|
||||
let sdp = ffi::create_session_description(ffi::SdpType::Offer, sdp_string.to_string());
|
||||
let err = unsafe { ffi::SdpParseError::from(sdp.err().unwrap().what()) };
|
||||
info!("parse err: {:?}", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
pub mod candidate;
|
||||
pub mod data_channel;
|
||||
pub mod jsep;
|
||||
pub mod media_stream;
|
||||
pub mod peer_connection;
|
||||
pub mod peer_connection_factory;
|
||||
pub mod rtc_error;
|
||||
pub mod rtp_receiver;
|
||||
pub mod rtp_transceiver;
|
||||
pub mod video_frame;
|
||||
pub mod video_frame_buffer;
|
||||
pub mod webrtc;
|
||||
pub mod yuv_helper;
|
||||
|
||||
pub const MEDIA_TYPE_VIDEO: &str = "video";
|
||||
pub const MEDIA_TYPE_AUDIO: &str = "audio";
|
||||
pub const MEDIA_TYPE_DATA: &str = "data";
|
||||
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// Created by Théo Monnom on 31/08/2022.
|
||||
//
|
||||
|
||||
#include "livekit/media_stream.h"
|
||||
|
||||
#include "api/media_stream_interface.h"
|
||||
#include "webrtc-sys/src/media_stream.rs.h"
|
||||
|
||||
namespace livekit {
|
||||
|
||||
MediaStreamTrack::MediaStreamTrack(
|
||||
rtc::scoped_refptr<webrtc::MediaStreamTrackInterface> track)
|
||||
: track_(std::move(track)) {}
|
||||
|
||||
std::unique_ptr<MediaStreamTrack> MediaStreamTrack::from(
|
||||
rtc::scoped_refptr<webrtc::MediaStreamTrackInterface> track) {
|
||||
if (track->kind() == webrtc::MediaStreamTrackInterface::kVideoKind) {
|
||||
return std::make_unique<VideoTrack>(
|
||||
rtc::scoped_refptr<webrtc::VideoTrackInterface>(
|
||||
static_cast<webrtc::VideoTrackInterface*>(track.get())));
|
||||
} else {
|
||||
return std::make_unique<AudioTrack>(
|
||||
rtc::scoped_refptr<webrtc::AudioTrackInterface>(
|
||||
static_cast<webrtc::AudioTrackInterface*>(track.get())));
|
||||
}
|
||||
}
|
||||
|
||||
rust::String MediaStreamTrack::kind() const {
|
||||
return track_->kind();
|
||||
}
|
||||
|
||||
rust::String MediaStreamTrack::id() const {
|
||||
return track_->id();
|
||||
}
|
||||
|
||||
bool MediaStreamTrack::enabled() const {
|
||||
return track_->enabled();
|
||||
}
|
||||
|
||||
bool MediaStreamTrack::set_enabled(bool enable) {
|
||||
return track_->set_enabled(enable);
|
||||
}
|
||||
|
||||
TrackState MediaStreamTrack::state() const {
|
||||
return static_cast<TrackState>(track_->state());
|
||||
}
|
||||
|
||||
MediaStream::MediaStream(
|
||||
rtc::scoped_refptr<webrtc::MediaStreamInterface> stream)
|
||||
: media_stream_(std::move(stream)) {}
|
||||
|
||||
rust::String MediaStream::id() const {
|
||||
return media_stream_->id();
|
||||
}
|
||||
|
||||
AudioTrack::AudioTrack(rtc::scoped_refptr<webrtc::AudioTrackInterface> track)
|
||||
: MediaStreamTrack(std::move(track)) {}
|
||||
|
||||
VideoTrack::VideoTrack(rtc::scoped_refptr<webrtc::VideoTrackInterface> track)
|
||||
: MediaStreamTrack(std::move(track)) {}
|
||||
|
||||
void VideoTrack::add_sink(NativeVideoFrameSink& sink) {
|
||||
track()->AddOrUpdateSink(&sink, rtc::VideoSinkWants());
|
||||
}
|
||||
|
||||
void VideoTrack::remove_sink(NativeVideoFrameSink& sink) {
|
||||
track()->RemoveSink(&sink);
|
||||
}
|
||||
|
||||
void VideoTrack::set_should_receive(bool should_receive) {
|
||||
track()->set_should_receive(should_receive);
|
||||
}
|
||||
|
||||
bool VideoTrack::should_receive() const {
|
||||
return track()->should_receive();
|
||||
}
|
||||
|
||||
ContentHint VideoTrack::content_hint() const {
|
||||
return static_cast<ContentHint>(track()->content_hint());
|
||||
}
|
||||
|
||||
void VideoTrack::set_content_hint(ContentHint hint) {
|
||||
track()->set_content_hint(
|
||||
static_cast<webrtc::VideoTrackInterface::ContentHint>(hint));
|
||||
}
|
||||
|
||||
NativeVideoFrameSink::NativeVideoFrameSink(
|
||||
rust::Box<VideoFrameSinkWrapper> observer)
|
||||
: observer_(std::move(observer)) {}
|
||||
|
||||
void NativeVideoFrameSink::OnFrame(const webrtc::VideoFrame& frame) {
|
||||
observer_->on_frame(std::make_unique<VideoFrame>(frame));
|
||||
}
|
||||
|
||||
void NativeVideoFrameSink::OnDiscardedFrame() {
|
||||
observer_->on_discarded_frame();
|
||||
}
|
||||
|
||||
void NativeVideoFrameSink::OnConstraintsChanged(
|
||||
const webrtc::VideoTrackSourceConstraints& constraints) {
|
||||
VideoTrackSourceConstraints cst;
|
||||
cst.min_fps = constraints.min_fps.value_or(-1);
|
||||
cst.max_fps = constraints.max_fps.value_or(-1);
|
||||
observer_->on_constraints_changed(cst);
|
||||
}
|
||||
|
||||
std::unique_ptr<NativeVideoFrameSink> create_native_video_frame_sink(
|
||||
rust::Box<VideoFrameSinkWrapper> observer) {
|
||||
return std::make_unique<NativeVideoFrameSink>(std::move(observer));
|
||||
}
|
||||
|
||||
} // namespace livekit
|
||||
@@ -0,0 +1,129 @@
|
||||
use cxx::UniquePtr;
|
||||
|
||||
use crate::video_frame::ffi::VideoFrame;
|
||||
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum TrackState {
|
||||
Live,
|
||||
Ended,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum ContentHint {
|
||||
None,
|
||||
Fluid,
|
||||
Detailed,
|
||||
Text,
|
||||
}
|
||||
|
||||
// -1 = optional
|
||||
pub struct VideoTrackSourceConstraints {
|
||||
pub min_fps: f64,
|
||||
pub max_fps: f64,
|
||||
}
|
||||
|
||||
unsafe extern "C++" {
|
||||
include!("livekit/media_stream.h");
|
||||
include!("livekit/video_frame.h");
|
||||
|
||||
type NativeVideoFrameSink;
|
||||
type MediaStreamTrack;
|
||||
type MediaStream;
|
||||
type AudioTrack;
|
||||
type VideoTrack;
|
||||
type VideoFrame = crate::video_frame::ffi::VideoFrame;
|
||||
|
||||
fn id(self: &MediaStream) -> String;
|
||||
|
||||
fn kind(self: &MediaStreamTrack) -> String;
|
||||
fn id(self: &MediaStreamTrack) -> String;
|
||||
fn enabled(self: &MediaStreamTrack) -> bool;
|
||||
fn set_enabled(self: Pin<&mut MediaStreamTrack>, enable: bool) -> bool;
|
||||
fn state(self: &MediaStreamTrack) -> TrackState;
|
||||
|
||||
unsafe fn add_sink(self: Pin<&mut VideoTrack>, sink: Pin<&mut NativeVideoFrameSink>);
|
||||
unsafe fn remove_sink(self: Pin<&mut VideoTrack>, sink: Pin<&mut NativeVideoFrameSink>);
|
||||
|
||||
fn set_should_receive(self: Pin<&mut VideoTrack>, should_receive: bool);
|
||||
fn should_receive(self: &VideoTrack) -> bool;
|
||||
fn content_hint(self: &VideoTrack) -> ContentHint;
|
||||
fn set_content_hint(self: Pin<&mut VideoTrack>, hint: ContentHint);
|
||||
|
||||
fn create_native_video_frame_sink(
|
||||
observer: Box<VideoFrameSinkWrapper>,
|
||||
) -> UniquePtr<NativeVideoFrameSink>;
|
||||
|
||||
unsafe fn video_to_media(track: *const VideoTrack) -> *const MediaStreamTrack;
|
||||
unsafe fn audio_to_media(track: *const AudioTrack) -> *const MediaStreamTrack;
|
||||
unsafe fn media_to_video(track: *const MediaStreamTrack) -> *const VideoTrack;
|
||||
unsafe fn media_to_audio(track: *const MediaStreamTrack) -> *const AudioTrack;
|
||||
|
||||
fn _unique_media_stream_track() -> UniquePtr<MediaStreamTrack>; // Ignore
|
||||
fn _unique_media_stream() -> UniquePtr<MediaStream>; // Ignore
|
||||
fn _unique_audio_track() -> UniquePtr<AudioTrack>; // Ignore
|
||||
fn _unique_video_track() -> UniquePtr<VideoTrack>; // Ignore
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
type VideoFrameSinkWrapper;
|
||||
|
||||
fn on_frame(self: &VideoFrameSinkWrapper, frame: UniquePtr<VideoFrame>);
|
||||
fn on_discarded_frame(self: &VideoFrameSinkWrapper);
|
||||
fn on_constraints_changed(
|
||||
self: &VideoFrameSinkWrapper,
|
||||
constraints: VideoTrackSourceConstraints,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Sync for ffi::MediaStreamTrack {}
|
||||
unsafe impl Send for ffi::MediaStreamTrack {}
|
||||
unsafe impl Sync for ffi::MediaStream {}
|
||||
unsafe impl Send for ffi::MediaStream {}
|
||||
unsafe impl Send for ffi::AudioTrack {}
|
||||
unsafe impl Sync for ffi::AudioTrack {}
|
||||
unsafe impl Send for ffi::VideoTrack {}
|
||||
unsafe impl Sync for ffi::VideoTrack {}
|
||||
unsafe impl Send for ffi::NativeVideoFrameSink {}
|
||||
unsafe impl Sync for ffi::NativeVideoFrameSink {}
|
||||
|
||||
pub trait VideoFrameSink: Send + Sync {
|
||||
fn on_frame(&self, frame: UniquePtr<VideoFrame>);
|
||||
fn on_discarded_frame(&self);
|
||||
fn on_constraints_changed(&self, constraints: ffi::VideoTrackSourceConstraints);
|
||||
}
|
||||
|
||||
pub struct VideoFrameSinkWrapper {
|
||||
observer: *mut dyn VideoFrameSink,
|
||||
}
|
||||
|
||||
impl VideoFrameSinkWrapper {
|
||||
/// # Safety
|
||||
/// VideoFrameSink must lives as long as VideoSinkInterfaceWrapper does
|
||||
pub unsafe fn new(observer: *mut dyn VideoFrameSink) -> Self {
|
||||
Self { observer }
|
||||
}
|
||||
|
||||
fn on_frame(&self, frame: UniquePtr<VideoFrame>) {
|
||||
unsafe {
|
||||
(*self.observer).on_frame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_discarded_frame(&self) {
|
||||
unsafe {
|
||||
(*self.observer).on_discarded_frame();
|
||||
}
|
||||
}
|
||||
|
||||
fn on_constraints_changed(&self, constraints: ffi::VideoTrackSourceConstraints) {
|
||||
unsafe {
|
||||
(*self.observer).on_constraints_changed(constraints);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
//
|
||||
// Created by Théo Monnom on 30/08/2022.
|
||||
//
|
||||
|
||||
#include "livekit/peer_connection.h"
|
||||
#include "livekit/media_stream.h"
|
||||
|
||||
#include "webrtc-sys/src/peer_connection.rs.h"
|
||||
#include "livekit/rtc_error.h"
|
||||
|
||||
namespace livekit {
|
||||
|
||||
inline webrtc::PeerConnectionInterface::RTCOfferAnswerOptions
|
||||
toNativeOfferAnswerOptions(const RTCOfferAnswerOptions& options) {
|
||||
webrtc::PeerConnectionInterface::RTCOfferAnswerOptions rtc_options;
|
||||
rtc_options.offer_to_receive_video = options.offer_to_receive_video;
|
||||
rtc_options.offer_to_receive_audio = options.offer_to_receive_audio;
|
||||
rtc_options.voice_activity_detection = options.voice_activity_detection;
|
||||
rtc_options.ice_restart = options.ice_restart;
|
||||
rtc_options.use_rtp_mux = options.use_rtp_mux;
|
||||
rtc_options.raw_packetization_for_video = options.raw_packetization_for_video;
|
||||
rtc_options.num_simulcast_layers = options.num_simulcast_layers;
|
||||
rtc_options.use_obsolete_sctp_sdp = options.use_obsolete_sctp_sdp;
|
||||
return rtc_options;
|
||||
}
|
||||
|
||||
PeerConnection::PeerConnection(
|
||||
std::shared_ptr<RTCRuntime> rtc_runtime,
|
||||
rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection)
|
||||
: rtc_runtime_(std::move(rtc_runtime)),
|
||||
peer_connection_(std::move(peer_connection)) {}
|
||||
|
||||
void PeerConnection::create_offer(
|
||||
NativeCreateSdpObserverHandle& observer_handle,
|
||||
RTCOfferAnswerOptions options) {
|
||||
peer_connection_->CreateOffer(observer_handle.observer.get(),
|
||||
toNativeOfferAnswerOptions(options));
|
||||
}
|
||||
|
||||
void PeerConnection::create_answer(
|
||||
NativeCreateSdpObserverHandle& observer_handle,
|
||||
RTCOfferAnswerOptions options) {
|
||||
peer_connection_->CreateAnswer(observer_handle.observer.get(),
|
||||
toNativeOfferAnswerOptions(options));
|
||||
}
|
||||
|
||||
void PeerConnection::set_local_description(
|
||||
std::unique_ptr<SessionDescription> desc,
|
||||
NativeSetLocalSdpObserverHandle& observer) {
|
||||
peer_connection_->SetLocalDescription(desc->clone()->release(),
|
||||
observer.observer);
|
||||
}
|
||||
|
||||
void PeerConnection::set_remote_description(
|
||||
std::unique_ptr<SessionDescription> desc,
|
||||
NativeSetRemoteSdpObserverHandle& observer) {
|
||||
peer_connection_->SetRemoteDescription(desc->clone()->release(),
|
||||
observer.observer);
|
||||
}
|
||||
|
||||
std::unique_ptr<DataChannel> PeerConnection::create_data_channel(
|
||||
rust::String label,
|
||||
std::unique_ptr<NativeDataChannelInit> init) {
|
||||
auto result =
|
||||
peer_connection_->CreateDataChannelOrError(label.c_str(), init.get());
|
||||
|
||||
if (!result.ok()) {
|
||||
throw std::runtime_error(serialize_error(to_error(result.error())));
|
||||
}
|
||||
|
||||
return std::make_unique<DataChannel>(rtc_runtime_, result.value());
|
||||
}
|
||||
|
||||
void PeerConnection::add_ice_candidate(
|
||||
std::unique_ptr<IceCandidate> candidate,
|
||||
NativeAddIceCandidateObserver& observer) {
|
||||
peer_connection_->AddIceCandidate(
|
||||
candidate->release(),
|
||||
[&](const webrtc::RTCError& err) { observer.OnComplete(to_error(err)); });
|
||||
}
|
||||
|
||||
std::unique_ptr<SessionDescription> PeerConnection::local_description() const {
|
||||
auto local_description = peer_connection_->local_description();
|
||||
if (local_description)
|
||||
return std::make_unique<SessionDescription>(local_description->Clone());
|
||||
|
||||
return std::unique_ptr<SessionDescription>();
|
||||
}
|
||||
|
||||
std::unique_ptr<SessionDescription> PeerConnection::remote_description() const {
|
||||
auto remote_description = peer_connection_->remote_description();
|
||||
if (remote_description)
|
||||
return std::make_unique<SessionDescription>(remote_description->Clone());
|
||||
|
||||
return std::unique_ptr<SessionDescription>();
|
||||
}
|
||||
|
||||
SignalingState PeerConnection::signaling_state() const {
|
||||
return static_cast<SignalingState>(peer_connection_->signaling_state());
|
||||
}
|
||||
|
||||
IceGatheringState PeerConnection::ice_gathering_state() const {
|
||||
return static_cast<IceGatheringState>(
|
||||
peer_connection_->ice_gathering_state());
|
||||
}
|
||||
|
||||
IceConnectionState PeerConnection::ice_connection_state() const {
|
||||
return static_cast<IceConnectionState>(
|
||||
peer_connection_->ice_connection_state());
|
||||
}
|
||||
|
||||
void PeerConnection::close() {
|
||||
peer_connection_->Close();
|
||||
}
|
||||
|
||||
// AddIceCandidateObserver
|
||||
|
||||
NativeAddIceCandidateObserver::NativeAddIceCandidateObserver(
|
||||
rust::Box<AddIceCandidateObserverWrapper> observer)
|
||||
: observer_(std::move(observer)) {}
|
||||
|
||||
void NativeAddIceCandidateObserver::OnComplete(const RTCError& error) {
|
||||
observer_->on_complete(error);
|
||||
}
|
||||
|
||||
std::unique_ptr<NativeAddIceCandidateObserver>
|
||||
create_native_add_ice_candidate_observer(
|
||||
rust::Box<AddIceCandidateObserverWrapper> observer) {
|
||||
return std::make_unique<NativeAddIceCandidateObserver>(std::move(observer));
|
||||
}
|
||||
|
||||
// PeerConnectionObserver
|
||||
|
||||
NativePeerConnectionObserver::NativePeerConnectionObserver(
|
||||
std::shared_ptr<RTCRuntime> rtc_runtime,
|
||||
rust::Box<PeerConnectionObserverWrapper> observer)
|
||||
: rtc_runtime_(std::move(rtc_runtime)), observer_(std::move(observer)) {}
|
||||
|
||||
void NativePeerConnectionObserver::OnSignalingChange(
|
||||
webrtc::PeerConnectionInterface::SignalingState new_state) {
|
||||
observer_->on_signaling_change(static_cast<SignalingState>(new_state));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnAddStream(
|
||||
rtc::scoped_refptr<webrtc::MediaStreamInterface> stream) {
|
||||
observer_->on_add_stream(std::make_unique<MediaStream>(stream));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnRemoveStream(
|
||||
rtc::scoped_refptr<webrtc::MediaStreamInterface> stream) {
|
||||
observer_->on_remove_stream(std::make_unique<MediaStream>(stream));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnDataChannel(
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel) {
|
||||
observer_->on_data_channel(
|
||||
std::make_unique<DataChannel>(rtc_runtime_, data_channel));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnRenegotiationNeeded() {
|
||||
observer_->on_renegotiation_needed();
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnNegotiationNeededEvent(uint32_t event_id) {
|
||||
observer_->on_negotiation_needed_event(event_id);
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnIceConnectionChange(
|
||||
webrtc::PeerConnectionInterface::IceConnectionState new_state) {
|
||||
observer_->on_ice_connection_change(
|
||||
static_cast<IceConnectionState>(new_state));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnStandardizedIceConnectionChange(
|
||||
webrtc::PeerConnectionInterface::IceConnectionState new_state) {
|
||||
observer_->on_standardized_ice_connection_change(
|
||||
static_cast<IceConnectionState>(new_state));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnConnectionChange(
|
||||
webrtc::PeerConnectionInterface::PeerConnectionState new_state) {
|
||||
observer_->on_connection_change(static_cast<PeerConnectionState>(new_state));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnIceGatheringChange(
|
||||
webrtc::PeerConnectionInterface::IceGatheringState new_state) {
|
||||
observer_->on_ice_gathering_change(static_cast<IceGatheringState>(new_state));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnIceCandidate(
|
||||
const webrtc::IceCandidateInterface* candidate) {
|
||||
auto new_candidate = webrtc::CreateIceCandidate(candidate->sdp_mid(),
|
||||
candidate->sdp_mline_index(),
|
||||
candidate->candidate());
|
||||
observer_->on_ice_candidate(
|
||||
std::make_unique<IceCandidate>(std::move(new_candidate)));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnIceCandidateError(
|
||||
const std::string& address,
|
||||
int port,
|
||||
const std::string& url,
|
||||
int error_code,
|
||||
const std::string& error_text) {
|
||||
observer_->on_ice_candidate_error(address, port, url, error_code, error_text);
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnIceCandidatesRemoved(
|
||||
const std::vector<cricket::Candidate>& candidates) {
|
||||
rust::Vec<CandidatePtr> vec;
|
||||
|
||||
for (const auto& item : candidates) {
|
||||
vec.push_back(CandidatePtr{std::make_unique<Candidate>(item)});
|
||||
}
|
||||
|
||||
observer_->on_ice_candidates_removed(std::move(vec));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnIceConnectionReceivingChange(
|
||||
bool receiving) {
|
||||
observer_->on_ice_connection_receiving_change(receiving);
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnIceSelectedCandidatePairChanged(
|
||||
const cricket::CandidatePairChangeEvent& event) {
|
||||
CandidatePairChangeEvent e;
|
||||
e.selected_candidate_pair.local =
|
||||
std::make_unique<Candidate>(event.selected_candidate_pair.local);
|
||||
e.selected_candidate_pair.remote =
|
||||
std::make_unique<Candidate>(event.selected_candidate_pair.remote);
|
||||
e.last_data_received_ms = event.last_data_received_ms;
|
||||
e.reason = event.reason;
|
||||
e.estimated_disconnected_time_ms = event.estimated_disconnected_time_ms;
|
||||
|
||||
observer_->on_ice_selected_candidate_pair_changed(std::move(e));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnAddTrack(
|
||||
rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver,
|
||||
const std::vector<rtc::scoped_refptr<webrtc::MediaStreamInterface>>&
|
||||
streams) {
|
||||
rust::Vec<MediaStreamPtr> vec;
|
||||
|
||||
for (const auto& item : streams) {
|
||||
vec.push_back(MediaStreamPtr{std::make_unique<MediaStream>(item)});
|
||||
}
|
||||
|
||||
observer_->on_add_track(std::make_unique<RtpReceiver>(receiver),
|
||||
std::move(vec));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnTrack(
|
||||
rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver) {
|
||||
observer_->on_track(std::make_unique<RtpTransceiver>(transceiver));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnRemoveTrack(
|
||||
rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver) {
|
||||
observer_->on_remove_track(std::make_unique<RtpReceiver>(receiver));
|
||||
}
|
||||
|
||||
void NativePeerConnectionObserver::OnInterestingUsage(int usage_pattern) {
|
||||
observer_->on_interesting_usage(usage_pattern);
|
||||
}
|
||||
|
||||
std::unique_ptr<NativePeerConnectionObserver>
|
||||
create_native_peer_connection_observer(
|
||||
std::shared_ptr<RTCRuntime> rtc_runtime,
|
||||
rust::Box<PeerConnectionObserverWrapper> observer) {
|
||||
return std::make_unique<NativePeerConnectionObserver>(rtc_runtime,
|
||||
std::move(observer));
|
||||
}
|
||||
} // namespace livekit
|
||||
@@ -0,0 +1,485 @@
|
||||
use std::fmt::Debug;
|
||||
use std::mem::ManuallyDrop;
|
||||
|
||||
use cxx::UniquePtr;
|
||||
|
||||
use crate::candidate::ffi::Candidate;
|
||||
use crate::data_channel::ffi::DataChannel;
|
||||
use crate::jsep::ffi::IceCandidate;
|
||||
use crate::media_stream::ffi::MediaStream;
|
||||
use crate::rtc_error::ffi::RTCError;
|
||||
use crate::rtp_receiver::ffi::RtpReceiver;
|
||||
use crate::rtp_transceiver::ffi::RtpTransceiver;
|
||||
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
struct CandidatePair {
|
||||
local: UniquePtr<Candidate>,
|
||||
remote: UniquePtr<Candidate>,
|
||||
}
|
||||
|
||||
struct CandidatePairChangeEvent {
|
||||
selected_candidate_pair: CandidatePair,
|
||||
last_data_received_ms: i64,
|
||||
reason: String,
|
||||
estimated_disconnected_time_ms: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum PeerConnectionState {
|
||||
New,
|
||||
Connecting,
|
||||
Connected,
|
||||
Disconnected,
|
||||
Failed,
|
||||
Closed,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum SignalingState {
|
||||
Stable,
|
||||
HaveLocalOffer,
|
||||
HaveLocalPrAnswer,
|
||||
HaveRemoteOffer,
|
||||
HaveRemotePrAnswer,
|
||||
Closed,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum IceConnectionState {
|
||||
IceConnectionNew,
|
||||
IceConnectionChecking,
|
||||
IceConnectionConnected,
|
||||
IceConnectionCompleted,
|
||||
IceConnectionFailed,
|
||||
IceConnectionDisconnected,
|
||||
IceConnectionClosed,
|
||||
IceConnectionMax,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum IceGatheringState {
|
||||
IceGatheringNew,
|
||||
IceGatheringGathering,
|
||||
IceGatheringComplete,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RTCOfferAnswerOptions {
|
||||
offer_to_receive_video: i32,
|
||||
offer_to_receive_audio: i32,
|
||||
voice_activity_detection: bool,
|
||||
ice_restart: bool,
|
||||
use_rtp_mux: bool,
|
||||
raw_packetization_for_video: bool,
|
||||
num_simulcast_layers: i32,
|
||||
use_obsolete_sctp_sdp: bool,
|
||||
}
|
||||
|
||||
// Wrapper to opaque C++ objects
|
||||
// https://github.com/dtolnay/cxx/issues/741
|
||||
struct MediaStreamPtr {
|
||||
pub ptr: UniquePtr<MediaStream>,
|
||||
}
|
||||
|
||||
struct CandidatePtr {
|
||||
pub ptr: UniquePtr<Candidate>,
|
||||
}
|
||||
|
||||
unsafe extern "C++" {
|
||||
include!("livekit/peer_connection.h");
|
||||
include!("livekit/jsep.h");
|
||||
include!("livekit/data_channel.h");
|
||||
include!("livekit/rtp_receiver.h");
|
||||
include!("livekit/rtp_transceiver.h");
|
||||
include!("livekit/media_stream.h");
|
||||
include!("livekit/candidate.h");
|
||||
include!("webrtc-sys/src/rtc_error.rs.h");
|
||||
|
||||
type RTCError = crate::rtc_error::ffi::RTCError;
|
||||
type Candidate = crate::candidate::ffi::Candidate;
|
||||
type IceCandidate = crate::jsep::ffi::IceCandidate;
|
||||
type DataChannel = crate::data_channel::ffi::DataChannel;
|
||||
type RtpReceiver = crate::rtp_receiver::ffi::RtpReceiver;
|
||||
type RtpTransceiver = crate::rtp_transceiver::ffi::RtpTransceiver;
|
||||
type MediaStream = crate::media_stream::ffi::MediaStream;
|
||||
type NativeCreateSdpObserverHandle = crate::jsep::ffi::NativeCreateSdpObserverHandle;
|
||||
type NativeSetLocalSdpObserverHandle = crate::jsep::ffi::NativeSetLocalSdpObserverHandle;
|
||||
type NativeSetRemoteSdpObserverHandle = crate::jsep::ffi::NativeSetRemoteSdpObserverHandle;
|
||||
type NativeDataChannelInit = crate::data_channel::ffi::NativeDataChannelInit;
|
||||
type SessionDescription = crate::jsep::ffi::SessionDescription;
|
||||
type RTCRuntime = crate::webrtc::ffi::RTCRuntime;
|
||||
|
||||
type NativeAddIceCandidateObserver;
|
||||
type NativePeerConnectionObserver;
|
||||
type PeerConnection;
|
||||
|
||||
/// SAFETY
|
||||
/// The observer must live as long as the operation ends
|
||||
unsafe fn create_offer(
|
||||
self: Pin<&mut PeerConnection>,
|
||||
observer: Pin<&mut NativeCreateSdpObserverHandle>,
|
||||
options: RTCOfferAnswerOptions,
|
||||
);
|
||||
|
||||
/// SAFETY
|
||||
/// The observer must live as long as the operation ends
|
||||
unsafe fn create_answer(
|
||||
self: Pin<&mut PeerConnection>,
|
||||
observer: Pin<&mut NativeCreateSdpObserverHandle>,
|
||||
options: RTCOfferAnswerOptions,
|
||||
);
|
||||
|
||||
/// SAFETY
|
||||
/// The observer must live as long as the operation ends
|
||||
unsafe fn set_local_description(
|
||||
self: Pin<&mut PeerConnection>,
|
||||
desc: UniquePtr<SessionDescription>,
|
||||
observer: Pin<&mut NativeSetLocalSdpObserverHandle>,
|
||||
);
|
||||
|
||||
/// SAFETY
|
||||
/// The observer must live as long as the operation ends
|
||||
unsafe fn set_remote_description(
|
||||
self: Pin<&mut PeerConnection>,
|
||||
desc: UniquePtr<SessionDescription>,
|
||||
observer: Pin<&mut NativeSetRemoteSdpObserverHandle>,
|
||||
);
|
||||
|
||||
fn create_data_channel(
|
||||
self: Pin<&mut PeerConnection>,
|
||||
label: String,
|
||||
init: UniquePtr<NativeDataChannelInit>,
|
||||
) -> Result<UniquePtr<DataChannel>>;
|
||||
|
||||
fn add_ice_candidate(
|
||||
self: Pin<&mut PeerConnection>,
|
||||
candidate: UniquePtr<IceCandidate>,
|
||||
observer: Pin<&mut NativeAddIceCandidateObserver>,
|
||||
);
|
||||
|
||||
fn local_description(self: &PeerConnection) -> UniquePtr<SessionDescription>;
|
||||
|
||||
fn remote_description(self: &PeerConnection) -> UniquePtr<SessionDescription>;
|
||||
|
||||
fn signaling_state(self: &PeerConnection) -> SignalingState;
|
||||
|
||||
fn ice_gathering_state(self: &PeerConnection) -> IceGatheringState;
|
||||
|
||||
fn ice_connection_state(self: &PeerConnection) -> IceConnectionState;
|
||||
|
||||
fn close(self: Pin<&mut PeerConnection>);
|
||||
|
||||
fn create_native_peer_connection_observer(
|
||||
rtc_runtime: SharedPtr<RTCRuntime>,
|
||||
observer: Box<PeerConnectionObserverWrapper>,
|
||||
) -> UniquePtr<NativePeerConnectionObserver>;
|
||||
|
||||
fn create_native_add_ice_candidate_observer(
|
||||
observer: Box<AddIceCandidateObserverWrapper>,
|
||||
) -> UniquePtr<NativeAddIceCandidateObserver>;
|
||||
|
||||
fn _unique_peer_connection() -> UniquePtr<PeerConnection>; // Ignore
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
type AddIceCandidateObserverWrapper;
|
||||
|
||||
fn on_complete(self: &AddIceCandidateObserverWrapper, error: RTCError);
|
||||
|
||||
type PeerConnectionObserverWrapper;
|
||||
|
||||
fn on_signaling_change(self: &PeerConnectionObserverWrapper, new_state: SignalingState);
|
||||
fn on_add_stream(self: &PeerConnectionObserverWrapper, stream: UniquePtr<MediaStream>);
|
||||
fn on_remove_stream(self: &PeerConnectionObserverWrapper, stream: UniquePtr<MediaStream>);
|
||||
fn on_data_channel(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
data_channel: UniquePtr<DataChannel>,
|
||||
);
|
||||
fn on_renegotiation_needed(self: &PeerConnectionObserverWrapper);
|
||||
fn on_negotiation_needed_event(self: &PeerConnectionObserverWrapper, event: u32);
|
||||
fn on_ice_connection_change(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
new_state: IceConnectionState,
|
||||
);
|
||||
fn on_standardized_ice_connection_change(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
new_state: IceConnectionState,
|
||||
);
|
||||
fn on_connection_change(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
new_state: PeerConnectionState,
|
||||
);
|
||||
fn on_ice_gathering_change(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
new_state: IceGatheringState,
|
||||
);
|
||||
fn on_ice_candidate(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
candidate: UniquePtr<IceCandidate>,
|
||||
);
|
||||
fn on_ice_candidate_error(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
address: String,
|
||||
port: i32,
|
||||
url: String,
|
||||
error_code: i32,
|
||||
error_text: String,
|
||||
);
|
||||
fn on_ice_candidates_removed(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
removed: Vec<CandidatePtr>,
|
||||
);
|
||||
fn on_ice_connection_receiving_change(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
receiving: bool,
|
||||
);
|
||||
fn on_ice_selected_candidate_pair_changed(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
event: CandidatePairChangeEvent,
|
||||
);
|
||||
fn on_add_track(
|
||||
self: &PeerConnectionObserverWrapper,
|
||||
receiver: UniquePtr<RtpReceiver>,
|
||||
streams: Vec<MediaStreamPtr>,
|
||||
);
|
||||
fn on_track(self: &PeerConnectionObserverWrapper, transceiver: UniquePtr<RtpTransceiver>);
|
||||
fn on_remove_track(self: &PeerConnectionObserverWrapper, receiver: UniquePtr<RtpReceiver>);
|
||||
fn on_interesting_usage(self: &PeerConnectionObserverWrapper, usage_pattern: i32);
|
||||
}
|
||||
}
|
||||
|
||||
// https://webrtc.github.io/webrtc-org/native-code/native-apis/
|
||||
unsafe impl Send for ffi::PeerConnection {}
|
||||
|
||||
unsafe impl Sync for ffi::PeerConnection {}
|
||||
|
||||
unsafe impl Send for ffi::NativePeerConnectionObserver {}
|
||||
|
||||
unsafe impl Sync for ffi::NativePeerConnectionObserver {}
|
||||
|
||||
unsafe impl Sync for ffi::NativeAddIceCandidateObserver {}
|
||||
|
||||
unsafe impl Send for ffi::NativeAddIceCandidateObserver {}
|
||||
|
||||
unsafe impl Sync for ffi::NativeSetRemoteSdpObserverHandle {}
|
||||
|
||||
unsafe impl Send for ffi::NativeSetRemoteSdpObserverHandle {}
|
||||
|
||||
unsafe impl Sync for ffi::NativeSetLocalSdpObserverHandle {}
|
||||
|
||||
unsafe impl Send for ffi::NativeSetLocalSdpObserverHandle {}
|
||||
|
||||
unsafe impl Sync for ffi::NativeCreateSdpObserverHandle {}
|
||||
|
||||
unsafe impl Send for ffi::NativeCreateSdpObserverHandle {}
|
||||
|
||||
impl Default for ffi::RTCOfferAnswerOptions {
|
||||
/*
|
||||
static const int kUndefined = -1;
|
||||
static const int kMaxOfferToReceiveMedia = 1;
|
||||
static const int kOfferToReceiveMediaTrue = 1;
|
||||
*/
|
||||
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
offer_to_receive_video: -1,
|
||||
offer_to_receive_audio: -1,
|
||||
voice_activity_detection: true,
|
||||
ice_restart: false,
|
||||
use_rtp_mux: true,
|
||||
raw_packetization_for_video: false,
|
||||
num_simulcast_layers: 1,
|
||||
use_obsolete_sctp_sdp: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AddIceCandidateObserverWrapper(pub ManuallyDrop<Box<dyn FnOnce(RTCError) + Send>>);
|
||||
|
||||
impl AddIceCandidateObserverWrapper {
|
||||
fn on_complete(&self, error: RTCError) {
|
||||
unsafe {
|
||||
std::ptr::read(&*self.0)(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PeerConnectionObserver: Send + Sync {
|
||||
fn on_signaling_change(&self, new_state: ffi::SignalingState);
|
||||
fn on_add_stream(&self, stream: UniquePtr<MediaStream>);
|
||||
fn on_remove_stream(&self, stream: UniquePtr<MediaStream>);
|
||||
fn on_data_channel(&self, data_channel: UniquePtr<DataChannel>);
|
||||
fn on_renegotiation_needed(&self);
|
||||
fn on_negotiation_needed_event(&self, event: u32);
|
||||
fn on_ice_connection_change(&self, new_state: ffi::IceConnectionState);
|
||||
fn on_standardized_ice_connection_change(&self, new_state: ffi::IceConnectionState);
|
||||
fn on_connection_change(&self, new_state: ffi::PeerConnectionState);
|
||||
fn on_ice_gathering_change(&self, new_state: ffi::IceGatheringState);
|
||||
fn on_ice_candidate(&self, candidate: UniquePtr<IceCandidate>);
|
||||
fn on_ice_candidate_error(
|
||||
&self,
|
||||
address: String,
|
||||
port: i32,
|
||||
url: String,
|
||||
error_code: i32,
|
||||
error_text: String,
|
||||
);
|
||||
fn on_ice_candidates_removed(&self, removed: Vec<UniquePtr<Candidate>>);
|
||||
fn on_ice_connection_receiving_change(&self, receiving: bool);
|
||||
fn on_ice_selected_candidate_pair_changed(&self, event: ffi::CandidatePairChangeEvent);
|
||||
fn on_add_track(&self, receiver: UniquePtr<RtpReceiver>, streams: Vec<UniquePtr<MediaStream>>);
|
||||
fn on_track(&self, transceiver: UniquePtr<RtpTransceiver>);
|
||||
fn on_remove_track(&self, receiver: UniquePtr<RtpReceiver>);
|
||||
fn on_interesting_usage(&self, usage_pattern: i32);
|
||||
}
|
||||
|
||||
// Thread safety is handled inside PeerConnectionObserver
|
||||
pub struct PeerConnectionObserverWrapper {
|
||||
observer: *mut dyn PeerConnectionObserver,
|
||||
}
|
||||
|
||||
impl PeerConnectionObserverWrapper {
|
||||
/// # Safety
|
||||
/// PeerConnectionObserver must lives as long as PeerConnectionObserverWrapper does
|
||||
pub unsafe fn new(observer: *mut dyn PeerConnectionObserver) -> Self {
|
||||
Self { observer }
|
||||
}
|
||||
|
||||
fn on_signaling_change(&self, new_state: ffi::SignalingState) {
|
||||
unsafe {
|
||||
(*self.observer).on_signaling_change(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_add_stream(&self, stream: UniquePtr<MediaStream>) {
|
||||
unsafe {
|
||||
(*self.observer).on_add_stream(stream);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_remove_stream(&self, stream: UniquePtr<MediaStream>) {
|
||||
unsafe {
|
||||
(*self.observer).on_remove_stream(stream);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_data_channel(&self, data_channel: UniquePtr<DataChannel>) {
|
||||
unsafe {
|
||||
(*self.observer).on_data_channel(data_channel);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_renegotiation_needed(&self) {
|
||||
unsafe {
|
||||
(*self.observer).on_renegotiation_needed();
|
||||
}
|
||||
}
|
||||
|
||||
fn on_negotiation_needed_event(&self, event: u32) {
|
||||
unsafe {
|
||||
(*self.observer).on_negotiation_needed_event(event);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_connection_change(&self, new_state: ffi::IceConnectionState) {
|
||||
unsafe {
|
||||
(*self.observer).on_ice_connection_change(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_standardized_ice_connection_change(&self, new_state: ffi::IceConnectionState) {
|
||||
unsafe {
|
||||
(*self.observer).on_standardized_ice_connection_change(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_connection_change(&self, new_state: ffi::PeerConnectionState) {
|
||||
unsafe {
|
||||
(*self.observer).on_connection_change(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_gathering_change(&self, new_state: ffi::IceGatheringState) {
|
||||
unsafe {
|
||||
(*self.observer).on_ice_gathering_change(new_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_candidate(&self, candidate: UniquePtr<IceCandidate>) {
|
||||
unsafe {
|
||||
(*self.observer).on_ice_candidate(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_candidate_error(
|
||||
&self,
|
||||
address: String,
|
||||
port: i32,
|
||||
url: String,
|
||||
error_code: i32,
|
||||
error_text: String,
|
||||
) {
|
||||
unsafe {
|
||||
(*self.observer).on_ice_candidate_error(address, port, url, error_code, error_text);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_candidates_removed(&self, removed: Vec<ffi::CandidatePtr>) {
|
||||
let mut vec = Vec::new();
|
||||
|
||||
for v in removed {
|
||||
vec.push(v.ptr);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
(*self.observer).on_ice_candidates_removed(vec);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_connection_receiving_change(&self, receiving: bool) {
|
||||
unsafe {
|
||||
(*self.observer).on_ice_connection_receiving_change(receiving);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ice_selected_candidate_pair_changed(&self, event: ffi::CandidatePairChangeEvent) {
|
||||
unsafe {
|
||||
(*self.observer).on_ice_selected_candidate_pair_changed(event);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_add_track(&self, receiver: UniquePtr<RtpReceiver>, streams: Vec<ffi::MediaStreamPtr>) {
|
||||
let mut vec = Vec::new();
|
||||
|
||||
for v in streams {
|
||||
vec.push(v.ptr);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
(*self.observer).on_add_track(receiver, vec);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_track(&self, transceiver: UniquePtr<RtpTransceiver>) {
|
||||
unsafe {
|
||||
(*self.observer).on_track(transceiver);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_remove_track(&self, receiver: UniquePtr<RtpReceiver>) {
|
||||
unsafe {
|
||||
(*self.observer).on_remove_track(receiver);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_interesting_usage(&self, usage_pattern: i32) {
|
||||
unsafe {
|
||||
(*self.observer).on_interesting_usage(usage_pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// Created by Théo Monnom on 03/08/2022.
|
||||
//
|
||||
|
||||
#include "livekit/peer_connection_factory.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
|
||||
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
|
||||
#include "api/rtc_event_log/rtc_event_log_factory.h"
|
||||
#include "api/task_queue/default_task_queue_factory.h"
|
||||
#include "api/video_codecs/builtin_video_decoder_factory.h"
|
||||
#include "api/video_codecs/builtin_video_encoder_factory.h"
|
||||
#include "webrtc-sys/src/peer_connection_factory.rs.h"
|
||||
#include "livekit/rtc_error.h"
|
||||
#include "media/engine/webrtc_media_engine.h"
|
||||
|
||||
namespace livekit {
|
||||
|
||||
PeerConnectionFactory::PeerConnectionFactory(
|
||||
std::shared_ptr<RTCRuntime> rtc_runtime)
|
||||
: rtc_runtime_(std::move(rtc_runtime)) {
|
||||
RTC_LOG(LS_INFO) << "PeerConnectionFactory::PeerConnectionFactory()";
|
||||
|
||||
webrtc::PeerConnectionFactoryDependencies dependencies;
|
||||
dependencies.network_thread = rtc_runtime_->network_thread();
|
||||
dependencies.worker_thread = rtc_runtime_->worker_thread();
|
||||
dependencies.signaling_thread = rtc_runtime_->signaling_thread();
|
||||
dependencies.socket_factory = rtc_runtime_->network_thread()->socketserver();
|
||||
dependencies.task_queue_factory = webrtc::CreateDefaultTaskQueueFactory();
|
||||
dependencies.event_log_factory = std::make_unique<webrtc::RtcEventLogFactory>(
|
||||
dependencies.task_queue_factory.get());
|
||||
dependencies.call_factory = webrtc::CreateCallFactory();
|
||||
dependencies.trials = std::make_unique<webrtc::FieldTrialBasedConfig>();
|
||||
|
||||
cricket::MediaEngineDependencies media_deps;
|
||||
media_deps.task_queue_factory = dependencies.task_queue_factory.get();
|
||||
media_deps.video_encoder_factory = webrtc::CreateBuiltinVideoEncoderFactory();
|
||||
media_deps.video_decoder_factory = webrtc::CreateBuiltinVideoDecoderFactory();
|
||||
media_deps.audio_encoder_factory = webrtc::CreateBuiltinAudioEncoderFactory();
|
||||
media_deps.audio_decoder_factory = webrtc::CreateBuiltinAudioDecoderFactory();
|
||||
media_deps.audio_processing = webrtc::AudioProcessingBuilder().Create();
|
||||
media_deps.trials = dependencies.trials.get();
|
||||
|
||||
dependencies.media_engine = cricket::CreateMediaEngine(std::move(media_deps));
|
||||
|
||||
peer_factory_ =
|
||||
webrtc::CreateModularPeerConnectionFactory(std::move(dependencies));
|
||||
|
||||
if (peer_factory_.get() == nullptr) {
|
||||
RTC_LOG_ERR(LS_ERROR) << "Failed to create PeerConnectionFactory";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PeerConnectionFactory::~PeerConnectionFactory() {
|
||||
RTC_LOG(LS_INFO) << "PeerConnectionFactory::~PeerConnectionFactory()";
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerConnection> PeerConnectionFactory::create_peer_connection(
|
||||
std::unique_ptr<webrtc::PeerConnectionInterface::RTCConfiguration> config,
|
||||
NativePeerConnectionObserver& observer) const {
|
||||
webrtc::PeerConnectionDependencies deps{&observer};
|
||||
auto result =
|
||||
peer_factory_->CreatePeerConnectionOrError(*config, std::move(deps));
|
||||
|
||||
if (!result.ok()) {
|
||||
throw std::runtime_error(serialize_error(to_error(result.error())));
|
||||
}
|
||||
|
||||
return std::make_unique<PeerConnection>(rtc_runtime_, result.value());
|
||||
}
|
||||
|
||||
std::unique_ptr<PeerConnectionFactory> create_peer_connection_factory(
|
||||
std::shared_ptr<RTCRuntime> rtc_runtime) {
|
||||
return std::make_unique<PeerConnectionFactory>(std::move(rtc_runtime));
|
||||
}
|
||||
|
||||
std::unique_ptr<NativeRTCConfiguration> create_rtc_configuration(
|
||||
RTCConfiguration conf) {
|
||||
auto rtc =
|
||||
std::make_unique<webrtc::PeerConnectionInterface::RTCConfiguration>();
|
||||
for (auto& item : conf.ice_servers) {
|
||||
webrtc::PeerConnectionInterface::IceServer ice_server;
|
||||
ice_server.username = item.username.c_str();
|
||||
ice_server.password = item.password.c_str();
|
||||
|
||||
for (auto& url : item.urls) {
|
||||
ice_server.urls.emplace_back(url.c_str());
|
||||
}
|
||||
rtc->servers.push_back(ice_server);
|
||||
rtc->continual_gathering_policy =
|
||||
static_cast<webrtc::PeerConnectionInterface::ContinualGatheringPolicy>(
|
||||
conf.continual_gathering_policy);
|
||||
|
||||
rtc->type = static_cast<webrtc::PeerConnectionInterface::IceTransportsType>(
|
||||
conf.ice_transport_type);
|
||||
}
|
||||
|
||||
return rtc;
|
||||
}
|
||||
} // namespace livekit
|
||||
@@ -0,0 +1,60 @@
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ICEServer {
|
||||
pub urls: Vec<String>,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum ContinualGatheringPolicy {
|
||||
GatherOnce,
|
||||
GatherContinually,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum IceTransportsType {
|
||||
None,
|
||||
Relay,
|
||||
NoHost,
|
||||
All,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RTCConfiguration {
|
||||
pub ice_servers: Vec<ICEServer>,
|
||||
pub continual_gathering_policy: ContinualGatheringPolicy,
|
||||
pub ice_transport_type: IceTransportsType,
|
||||
}
|
||||
|
||||
unsafe extern "C++" {
|
||||
include!("livekit/peer_connection_factory.h");
|
||||
|
||||
type PeerConnection = crate::peer_connection::ffi::PeerConnection;
|
||||
type NativePeerConnectionObserver =
|
||||
crate::peer_connection::ffi::NativePeerConnectionObserver;
|
||||
type PeerConnectionFactory;
|
||||
type NativeRTCConfiguration;
|
||||
type RTCRuntime = crate::webrtc::ffi::RTCRuntime;
|
||||
|
||||
fn create_peer_connection_factory(
|
||||
runtime: SharedPtr<RTCRuntime>,
|
||||
) -> UniquePtr<PeerConnectionFactory>;
|
||||
fn create_rtc_configuration(conf: RTCConfiguration) -> UniquePtr<NativeRTCConfiguration>;
|
||||
|
||||
/// # Safety
|
||||
/// The observer must live as long as the PeerConnection
|
||||
unsafe fn create_peer_connection(
|
||||
self: &PeerConnectionFactory,
|
||||
config: UniquePtr<NativeRTCConfiguration>,
|
||||
observer: Pin<&mut NativePeerConnectionObserver>,
|
||||
) -> Result<UniquePtr<PeerConnection>>;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for ffi::PeerConnectionFactory {}
|
||||
|
||||
unsafe impl Sync for ffi::PeerConnectionFactory {}
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// Created by theom on 04/09/2022.
|
||||
//
|
||||
|
||||
#include "livekit/rtc_error.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace livekit {
|
||||
|
||||
RTCError to_error(const webrtc::RTCError& error) {
|
||||
RTCError lk_error;
|
||||
lk_error.error_detail = static_cast<RTCErrorDetailType>(error.error_detail());
|
||||
lk_error.error_type = static_cast<RTCErrorType>(error.type());
|
||||
lk_error.has_sctp_cause_code = error.sctp_cause_code().has_value();
|
||||
lk_error.sctp_cause_code = error.sctp_cause_code().value_or(0);
|
||||
lk_error.message = error.message();
|
||||
return lk_error;
|
||||
}
|
||||
|
||||
std::string serialize_error(const RTCError& error) {
|
||||
std::stringstream ss;
|
||||
ss << std::hex << std::setfill('0');
|
||||
ss << std::setw(8) << (uint32_t)error.error_type;
|
||||
ss << std::setw(8) << (uint32_t)error.error_detail;
|
||||
ss << std::setw(2) << (uint16_t)error.has_sctp_cause_code;
|
||||
ss << std::setw(4) << (uint16_t)error.sctp_cause_code;
|
||||
ss << std::dec << std::setw(1) << std::string(error.message);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
#ifdef LIVEKIT_TEST
|
||||
rust::String serialize_deserialize() {
|
||||
RTCError lk_error;
|
||||
lk_error.error_type = RTCErrorType::InternalError;
|
||||
lk_error.error_detail = RTCErrorDetailType::DataChannelFailure;
|
||||
lk_error.has_sctp_cause_code = true;
|
||||
lk_error.sctp_cause_code = 24;
|
||||
lk_error.message = "this is not a test, I repeat, this is not a test";
|
||||
return serialize_error(lk_error);
|
||||
}
|
||||
|
||||
void throw_error() {
|
||||
RTCError lk_error;
|
||||
lk_error.error_type = RTCErrorType::InvalidModification;
|
||||
lk_error.error_detail = RTCErrorDetailType::None;
|
||||
lk_error.has_sctp_cause_code = false;
|
||||
lk_error.sctp_cause_code = 0;
|
||||
lk_error.message = "exception is thrown!";
|
||||
throw std::runtime_error(serialize_error(lk_error));
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace livekit
|
||||
@@ -0,0 +1,128 @@
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use crate::rtc_error::ffi::RTCErrorType;
|
||||
|
||||
// cxx doesn't support custom Exception type, so we serialize RTCError inside the cxx::Exception "what" string
|
||||
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum RTCErrorType {
|
||||
None,
|
||||
UnsupportedOperation,
|
||||
UnsupportedParameter,
|
||||
InvalidParameter,
|
||||
InvalidRange,
|
||||
SyntaxError,
|
||||
InvalidState,
|
||||
InvalidModification,
|
||||
NetworkError,
|
||||
ResourceExhausted,
|
||||
InternalError,
|
||||
OperationErrorWithData,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum RTCErrorDetailType {
|
||||
None,
|
||||
DataChannelFailure,
|
||||
DtlsFailure,
|
||||
FingerprintFailure,
|
||||
SctpFailure,
|
||||
SdpSyntaxError,
|
||||
HardwareEncoderNotAvailable,
|
||||
HardwareEncoderError,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RTCError {
|
||||
pub error_type: RTCErrorType,
|
||||
pub message: String,
|
||||
pub error_detail: RTCErrorDetailType,
|
||||
pub has_sctp_cause_code: bool,
|
||||
// cxx doesn't support the Option trait
|
||||
pub sctp_cause_code: u16,
|
||||
}
|
||||
}
|
||||
|
||||
impl ffi::RTCError {
|
||||
/// # Safety
|
||||
/// The value must be correctly encoded
|
||||
pub unsafe fn from(value: &str) -> Self {
|
||||
// Parse the hex encoded error from c++
|
||||
let error_type = u32::from_str_radix(&value[0..8], 16).unwrap();
|
||||
let error_detail = u32::from_str_radix(&value[8..16], 16).unwrap();
|
||||
let has_scp_cause_code = u8::from_str_radix(&value[16..18], 16).unwrap();
|
||||
let sctp_cause_code = u16::from_str_radix(&value[18..22], 16).unwrap();
|
||||
let message = String::from(&value[22..]); // msg isn't encoded
|
||||
|
||||
Self {
|
||||
error_type: std::mem::transmute(error_type),
|
||||
error_detail: std::mem::transmute(error_detail),
|
||||
sctp_cause_code,
|
||||
has_sctp_cause_code: has_scp_cause_code == 1,
|
||||
message,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ok(&self) -> bool {
|
||||
self.error_type == RTCErrorType::None
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ffi::RTCError {}
|
||||
|
||||
impl Display for ffi::RTCError {
|
||||
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"RtcError occurred {:?}: {}",
|
||||
self.error_type, self.message
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::rtc_error::ffi::{RTCError, RTCErrorDetailType, RTCErrorType};
|
||||
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
unsafe extern "C++" {
|
||||
include!("livekit/rtc_error.h");
|
||||
|
||||
fn serialize_deserialize() -> String;
|
||||
fn throw_error() -> Result<()>;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_deserialize() {
|
||||
let str = ffi::serialize_deserialize();
|
||||
let error = unsafe { RTCError::from(&str) };
|
||||
|
||||
assert_eq!(error.error_type, RTCErrorType::InternalError);
|
||||
assert_eq!(error.error_detail, RTCErrorDetailType::DataChannelFailure);
|
||||
assert_eq!(error.has_sctp_cause_code, true);
|
||||
assert_eq!(error.sctp_cause_code, 24);
|
||||
assert_eq!(
|
||||
error.message,
|
||||
"this is not a test, I repeat, this is not a test"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn throw_error() {
|
||||
let exc: cxx::Exception = ffi::throw_error().err().unwrap();
|
||||
let error = unsafe { RTCError::from(exc.what()) };
|
||||
|
||||
assert_eq!(error.error_type, RTCErrorType::InvalidModification);
|
||||
assert_eq!(error.error_detail, RTCErrorDetailType::None);
|
||||
assert_eq!(error.has_sctp_cause_code, false);
|
||||
assert_eq!(error.sctp_cause_code, 0);
|
||||
assert_eq!(error.message, "exception is thrown!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Created by Théo Monnom on 01/09/2022.
|
||||
//
|
||||
|
||||
#include "livekit/rtp_receiver.h"
|
||||
|
||||
namespace livekit {
|
||||
RtpReceiver::RtpReceiver(
|
||||
rtc::scoped_refptr<webrtc::RtpReceiverInterface> receiver)
|
||||
: receiver_(std::move(receiver)) {}
|
||||
|
||||
std::unique_ptr<MediaStreamTrack> RtpReceiver::track() const {
|
||||
return MediaStreamTrack::from(receiver_->track());
|
||||
}
|
||||
|
||||
} // namespace livekit
|
||||
@@ -0,0 +1,18 @@
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
unsafe extern "C++" {
|
||||
include!("livekit/rtp_receiver.h");
|
||||
include!("livekit/media_stream.h");
|
||||
|
||||
type MediaStreamTrack = crate::media_stream::ffi::MediaStreamTrack;
|
||||
type RtpReceiver;
|
||||
|
||||
fn track(self: &RtpReceiver) -> UniquePtr<MediaStreamTrack>;
|
||||
|
||||
fn _unique_rtp_receiver() -> UniquePtr<RtpReceiver>; // Ignore
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Sync for ffi::RtpReceiver {}
|
||||
|
||||
unsafe impl Send for ffi::RtpReceiver {}
|
||||
@@ -0,0 +1,11 @@
|
||||
//
|
||||
// Created by Théo Monnom on 02/09/2022.
|
||||
//
|
||||
|
||||
#include "livekit/rtp_transceiver.h"
|
||||
|
||||
namespace livekit {
|
||||
RtpTransceiver::RtpTransceiver(
|
||||
rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver)
|
||||
: transceiver_(std::move(transceiver)) {}
|
||||
} // namespace livekit
|
||||
@@ -0,0 +1,10 @@
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
unsafe extern "C++" {
|
||||
include!("livekit/rtp_transceiver.h");
|
||||
|
||||
type RtpTransceiver;
|
||||
|
||||
fn _unique_rtp_transceiver() -> UniquePtr<RtpTransceiver>; // Ignore
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum VideoRotation {
|
||||
VideoRotation0 = 0,
|
||||
VideoRotation90 = 90,
|
||||
VideoRotation180 = 180,
|
||||
VideoRotation270 = 270,
|
||||
}
|
||||
|
||||
unsafe extern "C++" {
|
||||
include!("livekit/video_frame.h");
|
||||
include!("livekit/video_frame_buffer.h");
|
||||
|
||||
type VideoFrame;
|
||||
type VideoFrameBuffer = crate::video_frame_buffer::ffi::VideoFrameBuffer;
|
||||
|
||||
fn width(self: &VideoFrame) -> i32;
|
||||
fn height(self: &VideoFrame) -> i32;
|
||||
fn size(self: &VideoFrame) -> u32;
|
||||
fn id(self: &VideoFrame) -> u16;
|
||||
fn timestamp_us(self: &VideoFrame) -> i64;
|
||||
fn ntp_time_ms(self: &VideoFrame) -> i64;
|
||||
fn transport_frame_id(self: &VideoFrame) -> u32;
|
||||
fn timestamp(self: &VideoFrame) -> u32;
|
||||
fn rotation(self: &VideoFrame) -> VideoRotation;
|
||||
fn video_frame_buffer(self: &VideoFrame) -> UniquePtr<VideoFrameBuffer>;
|
||||
|
||||
fn _unique_video_frame() -> UniquePtr<VideoFrame>; // Ignore
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
#[derive(Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum VideoFrameBufferType {
|
||||
Native,
|
||||
I420,
|
||||
I420A,
|
||||
I422,
|
||||
I444,
|
||||
I010,
|
||||
NV12,
|
||||
}
|
||||
|
||||
unsafe extern "C++" {
|
||||
include!("livekit/video_frame_buffer.h");
|
||||
|
||||
type VideoFrameBuffer;
|
||||
type PlanarYuvBuffer;
|
||||
type PlanarYuv8Buffer;
|
||||
type I420Buffer;
|
||||
|
||||
fn buffer_type(self: &VideoFrameBuffer) -> VideoFrameBufferType;
|
||||
fn width(self: &VideoFrameBuffer) -> i32;
|
||||
fn height(self: &VideoFrameBuffer) -> i32;
|
||||
|
||||
// Require ownership
|
||||
unsafe fn to_i420(self: Pin<&mut VideoFrameBuffer>) -> UniquePtr<I420Buffer>;
|
||||
unsafe fn get_i420(self: Pin<&mut VideoFrameBuffer>) -> UniquePtr<I420Buffer>;
|
||||
// TODO(theomonnom): Bridge other get_*
|
||||
|
||||
fn chroma_width(self: &PlanarYuvBuffer) -> i32;
|
||||
fn chroma_height(self: &PlanarYuvBuffer) -> i32;
|
||||
fn stride_y(self: &PlanarYuvBuffer) -> i32;
|
||||
fn stride_u(self: &PlanarYuvBuffer) -> i32;
|
||||
fn stride_v(self: &PlanarYuvBuffer) -> i32;
|
||||
|
||||
fn data_y(self: &PlanarYuv8Buffer) -> *const u8;
|
||||
fn data_u(self: &PlanarYuv8Buffer) -> *const u8;
|
||||
fn data_v(self: &PlanarYuv8Buffer) -> *const u8;
|
||||
|
||||
unsafe fn yuv_to_vfb(yuv: *const PlanarYuvBuffer) -> *const VideoFrameBuffer;
|
||||
unsafe fn yuv8_to_yuv(yuv8: *const PlanarYuv8Buffer) -> *const PlanarYuvBuffer;
|
||||
unsafe fn i420_to_yuv8(i420: *const I420Buffer) -> *const PlanarYuv8Buffer;
|
||||
|
||||
fn _unique_video_frame_buffer() -> UniquePtr<VideoFrameBuffer>;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// Created by theom on 18/09/2022.
|
||||
//
|
||||
|
||||
#include "livekit/webrtc.h"
|
||||
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace livekit {
|
||||
RTCRuntime::RTCRuntime() {
|
||||
// rtc::LogMessage::LogToDebug(rtc::LS_INFO);
|
||||
RTC_LOG(LS_INFO) << "RTCRuntime()";
|
||||
RTC_CHECK(rtc::InitializeSSL()) << "Failed to InitializeSSL()";
|
||||
|
||||
network_thread_ = rtc::Thread::CreateWithSocketServer();
|
||||
network_thread_->SetName("network_thread", &network_thread_);
|
||||
network_thread_->Start();
|
||||
worker_thread_ = rtc::Thread::Create();
|
||||
worker_thread_->SetName("worker_thread", &worker_thread_);
|
||||
worker_thread_->Start();
|
||||
signaling_thread_ = rtc::Thread::Create();
|
||||
signaling_thread_->SetName("signaling_thread", &signaling_thread_);
|
||||
signaling_thread_->Start();
|
||||
}
|
||||
|
||||
RTCRuntime::~RTCRuntime() {
|
||||
RTC_LOG(LS_INFO) << "~RTCRuntime()";
|
||||
RTC_CHECK(rtc::CleanupSSL()) << "Failed to CleanupSSL()";
|
||||
}
|
||||
|
||||
rtc::Thread* RTCRuntime::network_thread() const {
|
||||
return network_thread_.get();
|
||||
}
|
||||
|
||||
rtc::Thread* RTCRuntime::worker_thread() const {
|
||||
return worker_thread_.get();
|
||||
}
|
||||
|
||||
rtc::Thread* RTCRuntime::signaling_thread() const {
|
||||
return signaling_thread_.get();
|
||||
}
|
||||
|
||||
std::shared_ptr<RTCRuntime> create_rtc_runtime() {
|
||||
return std::make_shared<RTCRuntime>();
|
||||
}
|
||||
} // namespace livekit
|
||||
@@ -0,0 +1,14 @@
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
unsafe extern "C++" {
|
||||
include!("livekit/webrtc.h");
|
||||
|
||||
type RTCRuntime;
|
||||
|
||||
fn create_rtc_runtime() -> SharedPtr<RTCRuntime>;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for ffi::RTCRuntime {}
|
||||
|
||||
unsafe impl Sync for ffi::RTCRuntime {}
|
||||
@@ -0,0 +1,19 @@
|
||||
#[cxx::bridge(namespace = "livekit")]
|
||||
pub mod ffi {
|
||||
unsafe extern "C++" {
|
||||
include!("livekit/yuv_helper.h");
|
||||
|
||||
unsafe fn i420_to_abgr(
|
||||
src_y: *const u8,
|
||||
src_stride_y: i32,
|
||||
src_u: *const u8,
|
||||
src_stride_u: i32,
|
||||
src_v: *const u8,
|
||||
src_stride_v: i32,
|
||||
dst_abgr: *mut u8,
|
||||
dst_stride_abgr: i32,
|
||||
width: i32,
|
||||
height: i32,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user