Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ CMakeCache.txt
Makefile
cmake_install.cmake
out
build
.vscode
generated/
19 changes: 13 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
cmake_minimum_required(VERSION 3.0)
project(livekit)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wunused-but-set-parameter -Wformat-security -Wformat-security -Wstrict-null-sentinel -Werror")
set(FFI_PROTO_PATH client-sdk-rust/livekit-ffi/protocol)
set(FFI_PROTO_FILES
${FFI_PROTO_PATH}/handle.proto
Expand All @@ -15,7 +17,7 @@ set(FFI_PROTO_FILES
set(PROTO_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated)
file(MAKE_DIRECTORY ${PROTO_BINARY_DIR})

find_package(Protobuf REQUIRED)
find_package(protobuf REQUIRED CONFIG)

# livekit-proto
add_library(livekit_proto OBJECT ${FFI_PROTO_FILES})
Expand All @@ -32,15 +34,20 @@ protobuf_generate(
)

# livekit
add_library(livekit
include/livekit/room.h
add_library(livekit
include/livekit/ffi_client.h
include/livekit/livekit.h
include/livekit/video_frame.h
include/livekit/track.h
include/livekit/video_source.h
include/livekit/participant.h
include/livekit/room.h
src/ffi_client.cpp
src/video_frame.cpp
src/track.cpp
src/video_source.cpp
src/participant.cpp
src/room.cpp
${PROTO_SRCS}
${PROTO_HEADERS}
${PROTO_FILES}
)


Expand Down
2 changes: 1 addition & 1 deletion client-sdk-rust
119 changes: 110 additions & 9 deletions examples/simple_room/main.cpp
Original file line number Diff line number Diff line change
@@ -1,20 +1,121 @@
/*
* Copyright 2023 LiveKit
*
* Licensed under the Apache License, Version 2.0 (the “License”);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an “AS IS” BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include <cmath>
#include <cstdio>
#include <future>
#include <iostream>
#include <thread>
#include <vector>

#include "livekit/livekit.h"
#include "room.pb.h"
#include "track.pb.h"

using namespace livekit;

int main(int argc, char *argv[])
{
Room room{};
room.Connect("ws://localhost:7880", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjQ5MjM1OTQ4MjMsImlzcyI6IkFQSVRzRWZpZFpqclFvWSIsIm5hbWUiOiJ3ZWIiLCJuYmYiOjE2ODM1OTQ4MjMsInN1YiI6IndlYiIsInZpZGVvIjp7InJvb20iOiJsaXZla2l0LWZmaS10ZXN0Iiwicm9vbUpvaW4iOnRydWV9fQ.voLT9RK3wNYEGdWovPLv1BzyN1v5tpJ59e0DIqIVfiU");
const std::string URL = "ws://localhost:7880";
const std::string TOKEN =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTk3MjM1MzQsImlzcyI6ImRldmtleSIsIm5hbWUiOiJIZW5nc3RhciIsIm5iZiI6MTY5MTA4MzUzNCwic3ViIjoiSGVuZ3N0YXIiLCJ2aWRlbyI6eyJyb29tIjoidGVzdFJvb20iLCJyb29tSm9pbiI6dHJ1ZX19.8y6Qr_Z9UNo3P0QPD_TuXai02s23n8o5GjyWXVHK2RM";

std::vector<int> hsv_to_rgb(float H, float S, float V) {

std::vector<int> rgb(3);
float C = S * V;
float X = C * (1 - std::abs(fmod(H * 6, 2) - 1));
float m = V - C;

float R, G, B;
if (0 <= H && H < 1 / 6.0) {
R = C, G = X, B = 0;
} else if (1 / 6.0 <= H && H < 2 / 6.0) {
R = X, G = C, B = 0;
} else if (2 / 6.0 <= H && H < 3 / 6.0) {
R = 0, G = C, B = X;
} else if (3 / 6.0 <= H && H < 4 / 6.0) {
R = 0, G = X, B = C;
} else if (4 / 6.0 <= H && H < 5 / 6.0) {
R = X, G = 0, B = C;
} else {
R = C, G = 0, B = X;
}

rgb[0] = (R + m) * 255;
rgb[1] = (G + m) * 255;
rgb[2] = (B + m) * 255;

// Should we implement a mechanism to PollEvents/WaitEvents? Like SDL2/glfw
// - So we can remove the useless loop here
// Or is it better to use callback based events?
return rgb;
}

void publish_frames(VideoSource* source) {
ArgbFrame frame(proto::FORMAT_ARGB, 1280, 720);
double framerate = 1.0 / 30;
double hue = 0.0;
while (true) {
std::vector<int> rgb = hsv_to_rgb(hue, 1.0, 1.0);
for (size_t i = 0; i < frame.size; i += 4) {
frame.data[i] = 255;
frame.data[i + 1] = rgb[0];
frame.data[i + 2] = rgb[1];
frame.data[i + 3] = rgb[2];
}

while(true) {
hue += framerate / 3;

if (hue >= 1.0) {
hue = 0.0;
}

return 0;
VideoFrame i420(0, proto::VIDEO_ROTATION_0, frame.ToI420());
source->CaptureFrame(i420);

std::this_thread::sleep_for(
std::chrono::milliseconds(int(framerate * 1000)));
}
}

int main() {
std::shared_ptr<Room> room = Room::Create();
room->Connect(URL, TOKEN);

// TODO Non blocking ?
while (!room->GetLocalParticipant()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should implement a correct way to handle events.
Should we expose all events in a virtual class (observer)?

}

VideoSource source{};
std::thread t(publish_frames, &source);

std::this_thread::sleep_for(std::chrono::seconds(2));

std::shared_ptr<LocalVideoTrack> track =
LocalVideoTrack::CreateVideoTrack("hue", source);

proto::TrackPublishOptions options{};
options.set_source(proto::SOURCE_CAMERA);
options.set_simulcast(true);

std::cout << "Publishing track" << std::endl;
room->GetLocalParticipant()->PublishTrack(track, options);

// Should we implement a mechanism to PollEvents/WaitEvents? Like SDL2/glfw
// - So we can remove the useless loop here
// Or is it better to use callback based events?

while (true) {
}

return 0;
}
80 changes: 43 additions & 37 deletions include/livekit/ffi_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,57 +17,63 @@
#ifndef LIVEKIT_FFI_CLIENT_H
#define LIVEKIT_FFI_CLIENT_H

#include <functional>
#include <iostream>
#include <memory>
#include <functional>
#include <mutex>
#include <unordered_map>

#include "ffi.pb.h"
#include "livekit/proto.h"
#include "livekit_ffi.h"

namespace livekit {
extern "C" void LivekitFfiCallback(const u_int8_t* buf, size_t len);

// The FfiClient is used to communicate with the FFI interface of the Rust SDK
// We use the generated protocol messages to facilitate the communication
class FfiClient {
public:
using ListenerId = int;
using Listener = std::function<void(const proto::FfiEvent&)>;

namespace livekit
{
extern "C" void LivekitFfiCallback(const uint8_t *buf, size_t len);
FfiClient(const FfiClient&) = delete;
FfiClient& operator=(const FfiClient&) = delete;

// The FfiClient is used to communicate with the FFI interface of the Rust SDK
// We use the generated protocol messages to facilitate the communication
class FfiClient
{
public:
using ListenerId = int;
using Listener = std::function<void(const FFIEvent&)>;
static FfiClient& getInstance() {
static FfiClient instance;
return instance;
}

FfiClient(const FfiClient&) = delete;
FfiClient& operator=(const FfiClient&) = delete;
ListenerId AddListener(const Listener& listener);
void RemoveListener(ListenerId id);

static FfiClient& getInstance() {
static FfiClient instance;
return instance;
}

ListenerId AddListener(const Listener& listener);
void RemoveListener(ListenerId id);
proto::FfiResponse SendRequest(const proto::FfiRequest& request) const;

FFIResponse SendRequest(const FFIRequest& request)const;
private:
std::unordered_map<ListenerId, Listener> listeners_;
ListenerId nextListenerId = 1;
mutable std::mutex lock_;

private:
std::unordered_map<ListenerId, Listener> listeners_;
ListenerId nextListenerId = 1;
mutable std::mutex lock_;
FfiClient();
~FfiClient() = default;

FfiClient();
~FfiClient() = default;
void PushEvent(const proto::FfiEvent& event) const;
friend void LivekitFfiCallback(const uint8_t* buf, size_t len);
};

void PushEvent(const FFIEvent& event) const;
friend void LivekitFfiCallback(const uint8_t *buf, size_t len);
};
struct FfiHandle {
public:
FfiHandle(FfiHandleId handleId);
~FfiHandle() = default;

struct FfiHandle {
uintptr_t handle;
FfiHandleId GetHandleId() const { return *handleId_; }
bool IsValid() {
return handleId_ && *handleId_ != INVALID_HANDLE;
}

FfiHandle(uintptr_t handle);
~FfiHandle();
};
}
private:
std::shared_ptr<FfiHandleId> handleId_;
};
} // namespace livekit

#endif /* LIVEKIT_FFI_CLIENT_H */
3 changes: 3 additions & 0 deletions include/livekit/livekit.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@
* limitations under the License.
*/

#include "participant.h"
#include "room.h"
#include "video_frame.h"
#include "video_source.h"
66 changes: 66 additions & 0 deletions include/livekit/participant.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2023 LiveKit
*
* Licensed under the Apache License, Version 2.0 (the “License”);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an “AS IS” BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#ifndef LIVEKIT_PARTICIPANT_H
#define LIVEKIT_PARTICIPANT_H

#include <condition_variable>

#include "livekit/ffi_client.h"
#include "livekit/track.h"
#include "participant.pb.h"

namespace livekit {
class Room;

class Participant {
public:
Participant(const FfiHandle& handle, const proto::ParticipantInfo& info) : handle_(handle), info_(info) {}
Participant(FfiHandle&& handle, proto::ParticipantInfo&& info) : handle_(std::move(handle)), info_(std::move(info)) {}

const std::string& GetSid() const { return info_.sid(); }
const std::string& GetIdentity() const { return info_.identity(); }
const std::string& GetName() const { return info_.name(); }
const std::string& GetMetadata() const { return info_.metadata(); }

protected:
FfiHandle handle_;
proto::ParticipantInfo info_;
};

class LocalParticipant : public Participant {
public:
LocalParticipant(const FfiHandle& handle, const proto::ParticipantInfo& info)
: Participant(handle, info) {}
LocalParticipant(FfiHandle&& handle, proto::ParticipantInfo&& info)
: Participant(std::move(handle), std::move(info)) {}

~LocalParticipant();

void PublishTrack(std::shared_ptr<Track> track,
const proto::TrackPublishOptions& options);

private:
std::condition_variable cv_; // Should we block?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should have a blocking API.
Maybe it is OK to add callback pointers inside the arguments?

uint64_t publishAsyncId_;
FfiClient::ListenerId listenerId_{0};
std::unique_ptr<proto::PublishTrackCallback> publishCallback_;

void OnEvent(const proto::FfiEvent& event);
};

} // namespace livekit
#endif /* LIVEKIT_PARTICIPANT_H */
Loading