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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
!/cmake
!/data
!/src
!/ssp_connector
!.clang-format
!.cmake-format.json
!.gitignore
Expand Down
19 changes: 14 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ project(${_name} VERSION ${_version})

set(CMAKE_COMPILE_WARNING_AS_ERROR OFF)

set(ENABLE_FRONTEND_API "Use obs-frontend-api for UI functionality" OFF)
set(ENABLE_QT "Use Qt functionality" ON)
option(ENABLE_FRONTEND_API "Use obs-frontend-api for UI functionality" OFF)
option(ENABLE_QT "Use Qt functionality" ON)

include(compilerconfig)
include(defaults)
Expand Down Expand Up @@ -64,7 +64,7 @@ target_sources(${CMAKE_PROJECT_NAME} PRIVATE ${obs-ssp_SOURCES})
# /!\ TAKE NOTE: No need to edit things past this point /!\

# --- Platform-independent build settings ---
find_package(FFmpeg REQUIRED COMPONENTS AVCODEC AVUTIL)
find_package(FFmpeg REQUIRED COMPONENTS avcodec avutil)
add_subdirectory(thirdpty)

target_include_directories(
Expand All @@ -78,10 +78,19 @@ add_subdirectory(ssp_connector)

if(OS_MACOS)
install(TARGETS ssp-connector DESTINATION "./${CMAKE_PROJECT_NAME}.plugin/Contents/MacOS")
install(FILES ${LIBSSP_LIBRARY} DESTINATION "./${CMAKE_PROJECT_NAME}.plugin/Contents/Frameworks")
if(LIBSSP_LIBRARY)
install(FILES ${LIBSSP_LIBRARY} DESTINATION "./${CMAKE_PROJECT_NAME}.plugin/Contents/Frameworks")
endif()
elseif(OS_WINDOWS)
install(TARGETS ssp-connector DESTINATION "obs-plugins/64bit")
install(FILES ${LIBSSP_LIBRARY} DESTINATION "obs-plugins/64bit")
if(LIBSSP_LIBRARY)
install(FILES ${LIBSSP_LIBRARY} DESTINATION "obs-plugins/64bit")
endif()
elseif(OS_LINUX)
install(TARGETS ssp-connector DESTINATION ${CMAKE_INSTALL_BINDIR})
if(LIBSSP_LIBRARY)
install(FILES ${LIBSSP_LIBRARY} DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif()
endif()

set_target_properties_plugin(${CMAKE_PROJECT_NAME} PROPERTIES OUTPUT_NAME ${_name})
153 changes: 137 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,153 @@
obs-ssp
==============
# obs-ssp

Network A/V in OBS Studio with Simple Stream Protocol(SSP).
OBS Studio plugin for streaming from Z CAM cameras via **SSP (Simple Stream Protocol)**.

[简体中文](./README-zh.md)
This fork adds a **native SSP client** — a clean-room, open-source replacement for the
closed-source `libssp.so`. The protocol was reverse-engineered from live Z CAM E2-M4
wire captures, [libssp-py](https://pypi.org/project/libssp-py/), and symbol analysis.

## What's New (This Fork)

- **Native SSP connector** — no closed-source `libssp` dependency
- **Linux aarch64 support** — builds on ARM64 (Asahi Linux, Raspberry Pi, etc.)
- **Clean-room library** (`ssp.c` + `ssp.h`) — embeddable in other projects
- **Wire protocol documentation** — complete packet specifications in `ssp_connector/SSP_PROTOCOL.md`
- **Cross-platform socket abstraction** — POSIX and Windows via `ssp_platform.h`
- **Unit tests** — protocol parsing and byte helper verification

## Features
- **SSP Source** : receive video and audio from ZCam cameras to OBS.

![obs-ssp](./images/obs-ssp.png)
- **SSP Source** in OBS — receive live video and audio from Z CAM cameras
- H.264 and H.265 video, AAC and PCM audio
- Multiple stream styles (default, main, secondary)
- Automatic SHA1 challenge-response authentication
- Heartbeat keep-alive with 3-second interval

## Supported Cameras

Any Z CAM camera with SSP streaming enabled:

| Camera | Device Name | Verified |
|--------|-------------|----------|
| Z CAM E2-M4 | elephant | Yes (live captures) |
| Z CAM E2 | — | Expected compatible |
| Z CAM E2-S6 | — | Expected compatible |
| Z CAM E2-F6 | — | Expected compatible |
| Z CAM E2-F8 | — | Expected compatible |

## Building

### Prerequisites

- **OBS Studio** 28+ development headers
- **CMake** 3.16+
- **OpenSSL** development libraries (for native SSP)

### Linux (aarch64 — automatic native SSP)

```bash
# Install dependencies (Fedora)
sudo dnf install obs-studio-devel openssl-devel cmake gcc

# Configure and build
cmake -B build -S . -DCMAKE_BUILD_TYPE=Release
cmake --build build
```

On Linux aarch64, the native SSP connector is selected automatically (no
closed-source libssp binary exists for this architecture).

### Linux (x86_64 — opt-in native SSP)

## Downloads
Binaries for Windows, and macOS are available in the [Releases](https://github.com/summershrimp/obs-ssp/releases) section.
```bash
cmake -B build -S . -DUSE_NATIVE_SSP=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build
```

## Users group
### Windows

Discord(English only): https://discord.gg/uFpTbh3AVC
```powershell
cmake -B build -S . -DUSE_NATIVE_SSP=ON
cmake --build build --config Release
```

QQ(Chinese only):
### macOS

![QQ](./images/qq-group.png)
```bash
cmake -B build -S . -DUSE_NATIVE_SSP=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build
```

### Building Tests

## Automated Builds
[![Build Status](https://xm1994.visualstudio.com/obs-ssp/_apis/build/status/summershrimp.obs-ssp?branchName=master)](https://xm1994.visualstudio.com/obs-ssp/_build/latest?definitionId=1&branchName=master)
```bash
cmake -B build -S . -DUSE_NATIVE_SSP=ON -DSSP_BUILD_TESTS=ON
cmake --build build
./build/ssp_connector/test-ssp
```

[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fsummershrimp%2Fobs-ssp.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fsummershrimp%2Fobs-ssp?ref=badge_shield)
## Architecture

```
obs-ssp/
src/ # OBS plugin (source, properties, output)
ssp_connector/
include/ssp/ssp.h # Public library API
ssp.c # SSP client library (no globals, callback-driven)
ssp_platform.h # POSIX / Windows socket abstraction
ssp_connector_main.c # CLI bridge (library -> IPC -> OBS plugin)
ssp_connector_proto.h # IPC protocol structs (packed Message format)
ssp_native.c # Original monolithic implementation (preserved)
test_ssp.c # Unit tests
SSP_PROTOCOL.md # Complete wire protocol specification
main.cpp # Original libssp-based connector
```

### Library vs CLI

The SSP client is split into two layers:

1. **`ssp.c` / `ssp.h`** — Embeddable C library with a clean callback API:
```c
ssp_client_t *client = ssp_client_create();
ssp_client_set_target(client, "192.168.1.100", 9999);
ssp_client_set_callbacks(client, &callbacks);
ssp_client_connect(client); // blocks until stop or disconnect
ssp_client_destroy(client);
```

2. **`ssp_connector_main.c`** — Thin CLI that bridges library callbacks to the
OBS plugin's packed IPC format over stdout.

## Protocol Overview

SSP uses TCP (default port 9999) with big-endian length-prefixed packets.

```
Client Camera
| |
|<-------- INIT (0x64) ---------| challenge + device name
|-------- HANDSHAKE (0xC8) ---->| username + auth token
|<------ HANDSHAKE RESP --------| OK / error
|---- STREAM_START (0xCA) ----->| stream style
|<------ METADATA (0x6E) -------| 17 x BE32 fields
|<-------- VIDEO (0x6F) --------| pts + NAL data
|<-------- AUDIO (0x70) --------| pts + ADTS data
|---- HEARTBEAT (0xCB) -------->| every ~3 seconds
```

Full protocol documentation: [`ssp_connector/SSP_PROTOCOL.md`](ssp_connector/SSP_PROTOCOL.md)

## License
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fsummershrimp%2Fobs-ssp.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fsummershrimp%2Fobs-ssp?ref=badge_large)

BSD-3-Clause

Native SSP implementation: Copyright (c) 2026, Hedonistic, LLC
Original obs-ssp plugin: Copyright (c) 2015-2021, Yibai Zhang

## Credits

- **Yibai Zhang** ([@summershrimp](https://github.com/summershrimp)) — original obs-ssp plugin
- **Hedonistic, LLC** — native SSP client, protocol reverse engineering, Linux aarch64 support
- [libssp-py](https://pypi.org/project/libssp-py/) — reference for authentication algorithm
- [Z CAM](https://www.z-cam.com/) — camera manufacturer
5 changes: 4 additions & 1 deletion cmake/common/helpers_common.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ macro(find_qt)
add_library(Qt::${component} INTERFACE IMPORTED)
set_target_properties(Qt::${component} PROPERTIES INTERFACE_LINK_LIBRARIES Qt${_QT_VERSION}::${component})
endif()
set_property(TARGET Qt::${component} PROPERTY INTERFACE_COMPILE_FEATURES "")
get_target_property(_is_alias Qt::${component} ALIASED_TARGET)
if(NOT _is_alias)
set_property(TARGET Qt::${component} PROPERTY INTERFACE_COMPILE_FEATURES "")
endif()
endforeach()

endmacro()
Expand Down
17 changes: 17 additions & 0 deletions scripts/zcam-rtsp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash
# Low-latency GStreamer RTSP pipeline for ZCAM
# Usage: ./zcam-rtsp.sh [camera_ip]

CAMERA_IP="${1:-100.100.100.50}"
RTSP_URL="rtsp://${CAMERA_IP}/live_stream"

echo "Connecting to ZCAM at ${RTSP_URL}..."
echo "Press Ctrl+C to stop"

gst-launch-1.0 -v \
rtspsrc location="${RTSP_URL}" latency=0 buffer-mode=auto protocols=tcp \
! rtph264depay \
! h264parse \
! avdec_h264 \
! videoconvert \
! waylandsink sync=false
2 changes: 1 addition & 1 deletion src/obs-ssp-source.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ static void ssp_on_video_data(struct imf::SspH264Data *video, ssp_connection *s)
}
}

int64_t ts = video->pts;
long long ts = video->pts;
bool got_output;
bool success = ffmpeg_decode_video(&s->vdecoder, video->data,
video->len, &ts, VIDEO_CS_DEFAULT,
Expand Down
11 changes: 11 additions & 0 deletions src/ssp-client-iso.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ along with this program; If not, see <https://www.gnu.org/licenses/>

#ifdef _WIN32
#include <windows.h>
#else
#include <signal.h>
#endif

#if defined(__APPLE__)
Expand Down Expand Up @@ -227,6 +229,15 @@ void SSPClientIso::Stop()
blog(LOG_INFO, "ssp client stopping...");
this->statusLock.lock();
this->running = false;

/* Kill the connector process first so its stdout closes,
* which unblocks the fread() in the worker thread. Without
* this, worker.join() deadlocks because the worker is stuck
* in a blocking pipe read that never returns. */
if (this->pipe) {
os_process_pipe_signal(this->pipe, SIGTERM);
}

if (this->worker.joinable()) {
this->worker.join();
}
Expand Down
8 changes: 8 additions & 0 deletions src/util/pipe-posix.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

#include <stdio.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
Expand Down Expand Up @@ -142,6 +143,13 @@ os_process_pipe_t *os_process_pipe_create2(const os_process_args_t *args,
return os_process_pipe_create_internal(argv[0], argv, type);
}

int os_process_pipe_signal(os_process_pipe_t *pp, int sig)
{
if (!pp || pp->pid <= 0)
return -1;
return kill(pp->pid, sig);
}

int os_process_pipe_destroy(os_process_pipe_t *pp)
{
int ret = 0;
Expand Down
8 changes: 8 additions & 0 deletions src/util/pipe-windows.c
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,14 @@ os_process_pipe_t *os_process_pipe_create2(const os_process_args_t *args,
return ret;
}

int os_process_pipe_signal(os_process_pipe_t *pp, int sig)
{
(void)sig;
if (!pp)
return -1;
return TerminateProcess(pp->process, 1) ? 0 : -1;
}

int os_process_pipe_destroy(os_process_pipe_t *pp)
{
int ret = 0;
Expand Down
1 change: 1 addition & 0 deletions src/util/pipe.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ EXPORT os_process_pipe_t *os_process_pipe_create(const char *cmd_line,
EXPORT os_process_pipe_t *os_process_pipe_create2(const os_process_args_t *args,
const char *type);
EXPORT int os_process_pipe_destroy(os_process_pipe_t *pp);
EXPORT int os_process_pipe_signal(os_process_pipe_t *pp, int sig);

EXPORT size_t os_process_pipe_read(os_process_pipe_t *pp, uint8_t *data,
size_t len);
Expand Down
Loading
Loading