Initial Commit

This commit is contained in:
Exil Productions
2025-12-26 21:58:16 +01:00
commit 8c0cf3789f
9 changed files with 3021 additions and 0 deletions

135
.gitignore vendored Normal file
View File

@@ -0,0 +1,135 @@
# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,linux,c,c++,cmake
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,linux,c,c++,cmake
### C ###
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
### C++ ###
# Prerequisites
# Compiled Object files
*.slo
# Precompiled Headers
# Compiled Dynamic libraries
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
# Executables
### CMake ###
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
build
### CMake Patch ###
CMakeUserPresets.json
# External projects
*-prefix/
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,linux,c,c++,cmake
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)

33
CMakeLists.txt Normal file
View File

@@ -0,0 +1,33 @@
cmake_minimum_required(VERSION 3.10)
project(rtmp-cpp)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include_directories(include)
add_library(rtmp SHARED
src/rtmp_server.cpp
src/rtmp_capi.cpp
)
target_include_directories(rtmp PUBLIC
<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
<INSTALL_INTERFACE:include>
)
set_target_properties(rtmp PROPERTIES
PUBLIC_HEADER include/rtmp_capi.h
)
# Example executable
add_executable(rtmp_example example/main.c)
target_link_libraries(rtmp_example rtmp)
install(TARGETS rtmp
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
PUBLIC_HEADER DESTINATION include
)
install(TARGETS rtmp_example DESTINATION bin)

42
README.md Normal file
View File

@@ -0,0 +1,42 @@
# rtmp-cpp
[![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg)](https://github.com/user/rtmp-cpp)
A lightweight C++ RTMP server library with C-compatible API.
## Features
- Full RTMP protocol implementation (handshake, chunking, AMF0)
- Supports publish and play streams
- Callbacks for connect, publish, play, audio/video data, disconnect
- GOP cache for low-latency playback
- FLV file recording
- Authentication callback
- Stream statistics (bitrate, frames, uptime)
- Connection limits, timeouts, ping/pong
## Quick Start
### Build
```bash
./build.sh
```
This builds `librtmp.so` and example binary `rtmp_example`.
### Run Example
```bash
./build/rtmp_example
```
Server listens on `rtmp://localhost:1935/live/stream`
Test with OBS:
- Server: `rtmp://127.0.0.1/live`
- Stream key: `stream`
Or FFmpeg:
```bash
ffmpeg -re -i input.mp4 -c copy -f flv rtmp://127.0.0.1/live/stream
```
## License
MIT

4
build.sh Normal file
View File

@@ -0,0 +1,4 @@
rm -rf build
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
make -j$(nproc)

43
example/main.c Normal file
View File

@@ -0,0 +1,43 @@
#include "../include/rtmp_capi.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
static void on_connect_cb(const char* ip, void* data)
{
printf("Client connected: %s\n", ip);
}
static void on_publish_cb(const char* ip, const char* app, const char* key,
void* data)
{
printf("Publish from %s: %s/%s\n", ip, app, key);
}
static void on_audio_cb(const char* app, const char* key, const uint8_t* data,
uint32_t len, uint32_t ts, void* ud)
{
printf("Audio data for %s/%s, len=%u, ts=%u\n", app, key, len, ts);
}
int main()
{
rtmp_logger_set_level(RTMP_LOG_INFO);
RtmpServerHandle server = rtmp_server_create(1935);
rtmp_server_set_on_connect(server, on_connect_cb, NULL);
rtmp_server_set_on_publish(server, on_publish_cb, NULL);
rtmp_server_set_on_audio_data(server, on_audio_cb, NULL);
rtmp_server_enable_gop_cache(server, true);
if (rtmp_server_start(server))
{
printf("RTMP Server started on port 1935. Press Ctrl+C to stop.\n");
sleep(300); // 5 min
}
else
{
printf("Failed to start server\n");
}
rtmp_server_stop(server);
rtmp_server_destroy(server);
return 0;
}

112
include/rtmp_capi.h Normal file
View File

@@ -0,0 +1,112 @@
#ifndef RTMP_CAPI_H
#define RTMP_CAPI_H
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef void *RtmpServerHandle;
enum RtmpLogLevel
{
RTMP_LOG_ERROR = 0,
RTMP_LOG_WARN = 1,
RTMP_LOG_INFO = 2,
RTMP_LOG_DEBUG = 3
};
struct RtmpStreamStats
{
uint64_t bytes_sent;
uint64_t bytes_received;
uint32_t video_frames;
uint32_t audio_frames;
uint32_t dropped_frames;
double bitrate_kbps;
double uptime_seconds;
};
typedef void (*RtmpOnConnectCallback)(const char* client_ip, void* user_data);
typedef void (*RtmpOnPublishCallback)(const char* client_ip, const char* app,
const char* stream_key, void* user_data);
typedef void (*RtmpOnPlayCallback)(const char* client_ip, const char* app,
const char* stream_key, void* user_data);
typedef void (*RtmpOnAudioDataCallback)(const char* app, const char* stream_key,
const uint8_t* data, uint32_t length, uint32_t timestamp, void* user_data);
typedef void (*RtmpOnVideoDataCallback)(const char* app, const char* stream_key,
const uint8_t* data, uint32_t length, uint32_t timestamp, void* user_data);
typedef void (*RtmpOnDisconnectCallback)(const char* client_ip, const char* app,
const char* stream_key, bool was_publishing, bool was_playing, void* user_data);
typedef bool (*RtmpAuthCallback)(const char* app, const char* stream_key,
const char* client_ip, void* user_data);
// Create and destroy
RtmpServerHandle rtmp_server_create(int port);
void rtmp_server_destroy(RtmpServerHandle handle);
bool rtmp_server_start(RtmpServerHandle handle);
void rtmp_server_stop(RtmpServerHandle handle);
bool rtmp_server_is_running(RtmpServerHandle handle);
// Callbacks
void rtmp_server_set_on_connect(RtmpServerHandle handle,
RtmpOnConnectCallback cb, void* user_data);
void rtmp_server_set_on_publish(RtmpServerHandle handle,
RtmpOnPublishCallback cb, void* user_data);
void rtmp_server_set_on_play(RtmpServerHandle handle, RtmpOnPlayCallback cb,
void* user_data);
void rtmp_server_set_on_audio_data(RtmpServerHandle handle,
RtmpOnAudioDataCallback cb, void* user_data);
void rtmp_server_set_on_video_data(RtmpServerHandle handle,
RtmpOnVideoDataCallback cb, void* user_data);
void rtmp_server_set_on_disconnect(RtmpServerHandle handle,
RtmpOnDisconnectCallback cb, void* user_data);
void rtmp_server_set_auth_callback(RtmpServerHandle handle, RtmpAuthCallback cb,
void* user_data);
// Configuration
void rtmp_server_enable_gop_cache(RtmpServerHandle handle, bool enable);
void rtmp_server_set_max_publishers_per_stream(RtmpServerHandle handle,
int max);
void rtmp_server_set_max_players_per_stream(RtmpServerHandle handle, int max);
void rtmp_server_set_max_total_connections(RtmpServerHandle handle, int max);
void rtmp_server_set_connection_timeout(RtmpServerHandle handle, int seconds);
void rtmp_server_enable_ping_pong(RtmpServerHandle handle, bool enable,
int interval_seconds);
// Stats
int rtmp_server_get_active_publishers(RtmpServerHandle handle);
int rtmp_server_get_active_players(RtmpServerHandle handle);
int rtmp_server_get_total_connections(RtmpServerHandle handle);
struct RtmpStreamStats rtmp_server_get_stream_stats(RtmpServerHandle handle,
const char* app, const char* stream_key);
// Recording
bool rtmp_server_start_recording(RtmpServerHandle handle, const char* app,
const char* stream_key, const char* filename);
void rtmp_server_stop_recording(RtmpServerHandle handle, const char* app,
const char* stream_key);
bool rtmp_server_is_recording(RtmpServerHandle handle, const char* app,
const char* stream_key);
// Broadcasting
bool rtmp_server_broadcast_audio(RtmpServerHandle handle, const char* app,
const char* stream_key, const uint8_t* data, uint32_t length,
uint32_t timestamp);
bool rtmp_server_broadcast_video(RtmpServerHandle handle, const char* app,
const char* stream_key, const uint8_t* data, uint32_t length,
uint32_t timestamp);
// FIXED: Added missing declaration
bool rtmp_server_broadcast_metadata(RtmpServerHandle handle, const char* app,
const char* stream_key, const uint8_t* data, uint32_t length);
// Logger
void rtmp_logger_set_level(enum RtmpLogLevel level);
#ifdef __cplusplus
}
#endif
#endif // RTMP_CAPI_H

593
include/rtmp_server.h Normal file
View File

@@ -0,0 +1,593 @@
#ifndef RTMP_SERVER_H
#define RTMP_SERVER_H
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <functional>
#include <cstdint>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>
#include <iostream>
#include <thread>
#include <mutex>
#include <queue>
#include <chrono>
#include <fstream>
namespace rtmp
{
// RTMP Message Types
enum class MessageType : uint8_t
{
SET_CHUNK_SIZE = 1,
ABORT_MESSAGE = 2,
ACKNOWLEDGEMENT = 3,
USER_CONTROL = 4,
WINDOW_ACK_SIZE = 5,
SET_PEER_BANDWIDTH = 6,
AUDIO = 8,
VIDEO = 9,
DATA_AMF3 = 15,
SHARED_OBJECT_AMF3 = 16,
COMMAND_AMF3 = 17,
DATA_AMF0 = 18,
SHARED_OBJECT_AMF0 = 19,
COMMAND_AMF0 = 20,
AGGREGATE = 22
};
// User Control Message Types
enum class UserControlType : uint16_t
{
STREAM_BEGIN = 0,
STREAM_EOF = 1,
STREAM_DRY = 2,
SET_BUFFER_LENGTH = 3,
STREAM_IS_RECORDED = 4,
PING_REQUEST = 6,
PING_RESPONSE = 7
};
// AMF0 Data Types
enum class AMF0Type : uint8_t
{
NUMBER = 0x00,
BOOLEAN = 0x01,
STRING = 0x02,
OBJECT = 0x03,
NULL_TYPE = 0x05,
UNDEFINED = 0x06,
ECMA_ARRAY = 0x08,
OBJECT_END = 0x09
};
// Log Levels
enum class LogLevel
{
ERROR = 0,
WARN = 1,
INFO = 2,
DEBUG = 3
};
// AMF0 Value
class AMF0Value
{
public:
AMF0Type type;
double number;
bool boolean;
std::string string;
std::map<std::string, std::shared_ptr<AMF0Value>> object;
AMF0Value() : type(AMF0Type::NULL_TYPE), number(0), boolean(false) {}
};
// RTMP Chunk Header
struct ChunkHeader
{
uint8_t fmt;
uint32_t csid;
uint32_t timestamp;
uint32_t msg_length;
uint8_t msg_type_id;
uint32_t msg_stream_id;
bool has_extended_timestamp;
};
// RTMP Message
struct RTMPMessage
{
ChunkHeader header;
std::vector<uint8_t> payload;
};
// Stream Information
struct StreamInfo
{
std::string app;
std::string stream_key;
bool is_publishing;
bool is_playing;
int client_fd;
uint32_t stream_id;
std::string client_ip;
};
// Stream Statistics
struct StreamStatistics
{
uint64_t bytes_sent = 0;
uint64_t bytes_received = 0;
uint32_t video_frames = 0;
uint32_t audio_frames = 0;
uint32_t dropped_frames = 0;
std::chrono::steady_clock::time_point start_time;
StreamStatistics() : start_time(std::chrono::steady_clock::now()) {}
double getBitrate() const
{
auto now = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::seconds>(
now - start_time).count();
if (duration == 0) return 0;
return (bytes_sent * 8.0) / duration / 1000.0; // kbps
}
double getUptime() const
{
auto now = std::chrono::steady_clock::now();
return std::chrono::duration_cast<std::chrono::seconds>(
now - start_time).count();
}
};
// GOP Cache for instant playback
class GOPCache
{
public:
void addVideoFrame(const std::vector<uint8_t> &data, uint32_t timestamp);
void addAudioFrame(const std::vector<uint8_t> &data, uint32_t timestamp);
void addMetadata(const std::vector<uint8_t> &data);
void sendToPlayer(class RTMPSession* session);
void clear();
bool hasKeyframe() const
{
return has_keyframe;
}
private:
struct CachedFrame
{
MessageType type;
std::vector<uint8_t> data;
uint32_t timestamp;
};
std::vector<CachedFrame> frames;
std::vector<uint8_t> metadata;
bool has_keyframe = false;
std::mutex cache_mutex;
bool isKeyframe(const std::vector<uint8_t> &data);
};
// FLV File Recorder
class FLVRecorder
{
public:
FLVRecorder(const std::string& filename);
~FLVRecorder();
bool start();
void stop();
bool isRecording() const
{
return recording;
}
void writeAudioFrame(const std::vector<uint8_t> &data, uint32_t timestamp);
void writeVideoFrame(const std::vector<uint8_t> &data, uint32_t timestamp);
void writeMetadata(const std::map<std::string, std::shared_ptr<AMF0Value>>
&metadata);
private:
std::string filename;
std::ofstream file;
bool recording = false;
uint32_t last_timestamp = 0;
std::mutex file_mutex;
void writeFLVHeader();
void writeFLVTag(uint8_t tag_type, const std::vector<uint8_t> &data,
uint32_t timestamp);
std::vector<uint8_t> encodeMetadata(const
std::map<std::string, std::shared_ptr<AMF0Value>> &metadata);
};
// RTMP Client Session
class RTMPSession
{
public:
RTMPSession(int fd, const std::string& client_ip);
~RTMPSession();
bool handshake();
bool receiveChunk();
bool sendMessage(const RTMPMessage& msg);
bool sendChunk(uint32_t csid, uint32_t timestamp, uint8_t msg_type,
uint32_t stream_id, const std::vector<uint8_t> &data);
int getFd() const
{
return client_fd;
}
const StreamInfo &getStreamInfo() const
{
return stream_info;
}
StreamInfo &getStreamInfo()
{
return stream_info;
}
void setChunkSize(uint32_t size)
{
chunk_size = size;
}
uint32_t getChunkSize() const
{
return chunk_size;
}
// Acknowledgement handling
void onBytesReceived(size_t bytes);
bool shouldSendAck() const;
void sendAcknowledgement();
// Ping/Pong
void sendPing(uint32_t timestamp);
void sendPong(uint32_t timestamp);
// Statistics
StreamStatistics &getStats()
{
return stats;
}
const StreamStatistics &getStats() const
{
return stats;
}
// Message queue access for server
std::queue<RTMPMessage> &getMessageQueue()
{
return message_queue;
}
std::mutex &getQueueMutex()
{
return queue_mutex;
}
// Last activity tracking
std::chrono::steady_clock::time_point getLastActivity() const
{
return last_activity;
}
void updateActivity()
{
last_activity = std::chrono::steady_clock::now();
}
// AMF0 public access for server
std::shared_ptr<AMF0Value> decodeAMF0(const uint8_t* data, size_t len,
size_t &offset);
std::vector<uint8_t> encodeAMF0(const AMF0Value& value);
// FIXED: Moved to public section for RTMPServer access
bool sendErrorResponse(const std::string& command, double transaction_id,
const std::string& description);
private:
int client_fd;
uint32_t chunk_size;
uint32_t window_ack_size;
uint32_t peer_bandwidth;
uint32_t bytes_received;
uint32_t last_ack_sent;
std::map<uint32_t, ChunkHeader> prev_headers;
std::map<uint32_t, std::vector<uint8_t>> incomplete_chunks;
StreamInfo stream_info;
std::queue<RTMPMessage> message_queue;
std::mutex queue_mutex;
StreamStatistics stats;
std::chrono::steady_clock::time_point last_activity;
bool readExactly(uint8_t* buf, size_t len);
bool writeExactly(const uint8_t* buf, size_t len);
bool parseChunkHeader(ChunkHeader& header);
bool processMessage(const RTMPMessage& msg);
bool handleCommand(const RTMPMessage& msg);
bool handleAudioMessage(const RTMPMessage& msg);
bool handleVideoMessage(const RTMPMessage& msg);
bool handleDataMessage(const RTMPMessage& msg);
bool handleUserControl(const RTMPMessage& msg);
bool handleAcknowledgement(const RTMPMessage& msg);
// AMF0 Encoding/Decoding
std::vector<uint8_t> encodeAMF0String(const std::string& str);
std::vector<uint8_t> encodeAMF0Number(double num);
std::vector<uint8_t> encodeAMF0Object(const
std::map<std::string, std::shared_ptr<AMF0Value>> &obj);
// Command handlers
bool handleConnect(const std::vector<std::shared_ptr<AMF0Value>> &args);
bool handleReleaseStream(const std::vector<std::shared_ptr<AMF0Value>> &args);
bool handleFCPublish(const std::vector<std::shared_ptr<AMF0Value>> &args);
bool handleCreateStream(const std::vector<std::shared_ptr<AMF0Value>> &args);
bool handlePublish(const std::vector<std::shared_ptr<AMF0Value>> &args);
bool handlePlay(const std::vector<std::shared_ptr<AMF0Value>> &args);
bool handleDeleteStream(const std::vector<std::shared_ptr<AMF0Value>> &args);
// Response helpers
bool sendConnectResponse(double transaction_id);
bool sendCreateStreamResponse(double transaction_id, double stream_id);
bool sendPublishResponse();
bool sendPlayResponse();
};
// Callback types
using OnConnectCallback = std::function<void(std::shared_ptr<RTMPSession>)>;
using OnPublishCallback =
std::function<void(std::shared_ptr<RTMPSession>, const std::string& app, const std::string& stream_key)>;
using OnPlayCallback =
std::function<void(std::shared_ptr<RTMPSession>, const std::string& app, const std::string& stream_key)>;
using OnAudioDataCallback =
std::function<void(std::shared_ptr<RTMPSession>, const std::vector<uint8_t>& data, uint32_t timestamp)>;
using OnVideoDataCallback =
std::function<void(std::shared_ptr<RTMPSession>, const std::vector<uint8_t>& data, uint32_t timestamp)>;
using OnMetaDataCallback =
std::function<void(std::shared_ptr<RTMPSession>, const std::map<std::string, std::shared_ptr<AMF0Value>>& metadata)>;
using OnDisconnectCallback = std::function<void(std::shared_ptr<RTMPSession>)>;
using AuthCallback =
std::function<bool(const std::string& app, const std::string& stream_key, const std::string& client_ip)>;
// Logger
class Logger
{
public:
static Logger &getInstance()
{
static Logger instance;
return instance;
}
void setLevel(LogLevel level)
{
current_level = level;
}
LogLevel getLevel() const
{
return current_level;
}
void error(const std::string& msg);
void warn(const std::string& msg);
void info(const std::string& msg);
void debug(const std::string& msg);
private:
Logger() : current_level(LogLevel::INFO) {}
LogLevel current_level;
std::mutex log_mutex;
void log(LogLevel level, const std::string& msg);
};
// RTMP Server
class RTMPServer
{
public:
RTMPServer(int port = 1935);
~RTMPServer();
bool start();
void stop();
bool isRunning() const
{
return running;
}
// Callbacks
void setOnConnect(OnConnectCallback cb)
{
on_connect = cb;
}
void setOnPublish(OnPublishCallback cb)
{
on_publish = cb;
}
void setOnPlay(OnPlayCallback cb)
{
on_play = cb;
}
void setOnAudioData(OnAudioDataCallback cb)
{
on_audio_data = cb;
}
void setOnVideoData(OnVideoDataCallback cb)
{
on_video_data = cb;
}
void setOnMetaData(OnMetaDataCallback cb)
{
on_metadata = cb;
}
void setOnDisconnect(OnDisconnectCallback cb)
{
on_disconnect = cb;
}
void setAuthCallback(AuthCallback cb)
{
auth_callback = cb;
}
// GOP Cache
void enableGOPCache(bool enable)
{
use_gop_cache = enable;
}
bool isGOPCacheEnabled() const
{
return use_gop_cache;
}
// Recording
bool startRecording(const std::string& app, const std::string& stream_key,
const std::string& filename);
void stopRecording(const std::string& app, const std::string& stream_key);
bool isRecording(const std::string& app, const std::string& stream_key) const;
// Statistics
StreamStatistics getStreamStats(const std::string& app,
const std::string& stream_key) const;
std::vector<std::pair<std::string, StreamStatistics>> getAllStreamStats() const;
int getActivePublishers() const;
int getActivePlayers() const;
int getTotalConnections() const;
// Connection limits
void setMaxPublishersPerStream(int max)
{
max_publishers_per_stream = max;
}
void setMaxPlayersPerStream(int max)
{
max_players_per_stream = max;
}
void setMaxTotalConnections(int max)
{
max_total_connections = max;
}
int getMaxPublishersPerStream() const
{
return max_publishers_per_stream;
}
int getMaxPlayersPerStream() const
{
return max_players_per_stream;
}
int getMaxTotalConnections() const
{
return max_total_connections;
}
// Ping/Pong
void enablePingPong(bool enable, int interval_seconds = 30);
bool isPingPongEnabled() const
{
return ping_enabled;
}
// Timeout handling
void setConnectionTimeout(int seconds)
{
connection_timeout = seconds;
}
int getConnectionTimeout() const
{
return connection_timeout;
}
// Broadcasting
bool sendAudioToPlayers(const std::string& app, const std::string& stream_key,
const std::vector<uint8_t> &data, uint32_t timestamp);
bool sendVideoToPlayers(const std::string& app, const std::string& stream_key,
const std::vector<uint8_t> &data, uint32_t timestamp);
void sendMetadataToPlayers(const std::string& app,
const std::string& stream_key,
const std::vector<uint8_t> &data);
private:
int port;
int server_fd;
bool running;
std::thread accept_thread;
std::thread ping_thread;
std::thread timeout_thread;
std::vector<std::thread> client_threads;
std::vector<std::shared_ptr<RTMPSession>> sessions;
mutable std::mutex sessions_mutex; // FIXED: Added mutable
// Callbacks
OnConnectCallback on_connect;
OnPublishCallback on_publish;
OnPlayCallback on_play;
OnAudioDataCallback on_audio_data;
OnVideoDataCallback on_video_data;
OnMetaDataCallback on_metadata;
OnDisconnectCallback on_disconnect;
AuthCallback auth_callback;
// GOP Caches
bool use_gop_cache = true;
std::map<std::string, std::shared_ptr<GOPCache>> gop_caches;
std::mutex gop_mutex;
// Recorders
std::map<std::string, std::shared_ptr<FLVRecorder>> recorders;
mutable std::mutex recorder_mutex; // FIXED: Added mutable
// Statistics
std::map<std::string, StreamStatistics> stream_stats;
mutable std::mutex stats_mutex;
// Connection limits
int max_publishers_per_stream = 1;
int max_players_per_stream = 1000;
int max_total_connections = 1000;
// Ping/Pong
bool ping_enabled = false;
int ping_interval = 30;
// Timeout
int connection_timeout = 60;
void acceptClients();
void handleClient(std::shared_ptr<RTMPSession> session);
void removeSession(std::shared_ptr<RTMPSession> session);
void processMediaMessages(std::shared_ptr<RTMPSession> session);
void pingClientsRoutine();
void timeoutCheckRoutine();
std::string makeStreamKey(const std::string& app,
const std::string& stream) const
{
return app + "/" + stream;
}
int countPublishers(const std::string& app,
const std::string& stream_key) const;
int countPlayers(const std::string& app, const std::string& stream_key) const;
bool checkConnectionLimits(const std::string& app,
const std::string& stream_key, bool is_publisher) const;
};
// Utility macros
#define LOG_ERROR(msg) rtmp::Logger::getInstance().error(msg)
#define LOG_WARN(msg) rtmp::Logger::getInstance().warn(msg)
#define LOG_INFO(msg) rtmp::Logger::getInstance().info(msg)
#define LOG_DEBUG(msg) rtmp::Logger::getInstance().debug(msg)
} // namespace rtmp
#endif // RTMP_SERVER_H

294
src/rtmp_capi.cpp Normal file
View File

@@ -0,0 +1,294 @@
#include "rtmp_capi.h"
#include "rtmp_server.h"
#include <cstdlib>
#include <vector>
#include <string>
namespace rtmp_capi_internal
{
struct RtmpServerImpl
{
rtmp::RTMPServer *server;
RtmpOnConnectCallback on_connect_cb;
void *on_connect_userdata;
RtmpOnPublishCallback on_publish_cb;
void *on_publish_userdata;
RtmpOnPlayCallback on_play_cb;
void *on_play_userdata;
RtmpOnAudioDataCallback on_audio_cb;
void *on_audio_userdata;
RtmpOnVideoDataCallback on_video_cb;
void *on_video_userdata;
RtmpOnDisconnectCallback on_disconnect_cb;
void *on_disconnect_userdata;
RtmpAuthCallback auth_cb;
void *auth_userdata;
RtmpServerImpl() : server(nullptr), on_connect_cb(nullptr),
on_connect_userdata(nullptr),
on_publish_cb(nullptr), on_publish_userdata(nullptr), on_play_cb(nullptr),
on_play_userdata(nullptr),
on_audio_cb(nullptr), on_audio_userdata(nullptr), on_video_cb(nullptr),
on_video_userdata(nullptr),
on_disconnect_cb(nullptr), on_disconnect_userdata(nullptr), auth_cb(nullptr),
auth_userdata(nullptr) {}
};
}
using Impl = rtmp_capi_internal::RtmpServerImpl;
extern "C" {
RtmpServerHandle rtmp_server_create(int port)
{
Impl* impl = new Impl();
impl->server = new rtmp::RTMPServer(port);
impl->server->setOnConnect([impl](std::shared_ptr<rtmp::RTMPSession> session)
{
if (!impl || !impl->on_connect_cb) return;
const auto& info = session->getStreamInfo();
impl->on_connect_cb(info.client_ip.c_str(), impl->on_connect_userdata);
});
impl->server->setOnPublish([impl](std::shared_ptr<rtmp::RTMPSession> session,
const std::string & app, const std::string & stream_key)
{
if (!impl || !impl->on_publish_cb) return;
const auto& info = session->getStreamInfo();
impl->on_publish_cb(info.client_ip.c_str(), app.c_str(), stream_key.c_str(),
impl->on_publish_userdata);
});
impl->server->setOnPlay([impl](std::shared_ptr<rtmp::RTMPSession> session,
const std::string & app, const std::string & stream_key)
{
if (!impl || !impl->on_play_cb) return;
const auto& info = session->getStreamInfo();
impl->on_play_cb(info.client_ip.c_str(), app.c_str(), stream_key.c_str(),
impl->on_play_userdata);
});
impl->server->setOnAudioData([impl](std::shared_ptr<rtmp::RTMPSession> session,
const std::vector<uint8_t> &data, uint32_t timestamp)
{
if (!impl || !impl->on_audio_cb) return;
const auto& info = session->getStreamInfo();
impl->on_audio_cb(info.app.c_str(), info.stream_key.c_str(), data.data(),
static_cast<uint32_t>(data.size()), timestamp, impl->on_audio_userdata);
});
impl->server->setOnVideoData([impl](std::shared_ptr<rtmp::RTMPSession> session,
const std::vector<uint8_t> &data, uint32_t timestamp)
{
if (!impl || !impl->on_video_cb) return;
const auto& info = session->getStreamInfo();
impl->on_video_cb(info.app.c_str(), info.stream_key.c_str(), data.data(),
static_cast<uint32_t>(data.size()), timestamp, impl->on_video_userdata);
});
impl->server->setOnDisconnect([impl](std::shared_ptr<rtmp::RTMPSession>
session)
{
if (!impl || !impl->on_disconnect_cb) return;
const auto& info = session->getStreamInfo();
impl->on_disconnect_cb(info.client_ip.c_str(), info.app.c_str(),
info.stream_key.c_str(), info.is_publishing, info.is_playing,
impl->on_disconnect_userdata);
});
impl->server->setAuthCallback([impl](const std::string & app,
const std::string & stream_key, const std::string & client_ip) -> bool
{
if (!impl || !impl->auth_cb) return true;
return impl->auth_cb(app.c_str(), stream_key.c_str(), client_ip.c_str(), impl->auth_userdata);
});
return impl;
}
// add all other functions as above
void rtmp_server_destroy(RtmpServerHandle handle)
{
if (!handle) return;
Impl* impl = static_cast<Impl *>(handle);
delete impl->server;
delete impl;
}
bool rtmp_server_start(RtmpServerHandle handle)
{
if (!handle) return false;
Impl* impl = static_cast<Impl *>(handle);
return impl->server->start();
}
void rtmp_server_stop(RtmpServerHandle handle)
{
if (!handle) return;
Impl* impl = static_cast<Impl *>(handle);
impl->server->stop();
}
bool rtmp_server_is_running(RtmpServerHandle handle)
{
if (!handle) return false;
Impl* impl = static_cast<Impl *>(handle);
return impl->server->isRunning();
}
void rtmp_server_set_on_connect(RtmpServerHandle handle,
RtmpOnConnectCallback cb, void* user_data)
{
if (!handle) return;
Impl* impl = static_cast<Impl *>(handle);
impl->on_connect_cb = cb;
impl->on_connect_userdata = user_data;
}
void rtmp_server_set_on_publish(RtmpServerHandle handle,
RtmpOnPublishCallback cb, void* user_data)
{
if (!handle) return;
Impl* impl = static_cast<Impl *>(handle);
impl->on_publish_cb = cb;
impl->on_publish_userdata = user_data;
}
void rtmp_server_set_on_play(RtmpServerHandle handle, RtmpOnPlayCallback cb,
void* user_data)
{
if (!handle) return;
Impl* impl = static_cast<Impl *>(handle);
impl->on_play_cb = cb;
impl->on_play_userdata = user_data;
}
void rtmp_server_set_on_audio_data(RtmpServerHandle handle,
RtmpOnAudioDataCallback cb, void* user_data)
{
if (!handle) return;
Impl* impl = static_cast<Impl *>(handle);
impl->on_audio_cb = cb;
impl->on_audio_userdata = user_data;
}
void rtmp_server_set_on_video_data(RtmpServerHandle handle,
RtmpOnVideoDataCallback cb, void* user_data)
{
if (!handle) return;
Impl* impl = static_cast<Impl *>(handle);
impl->on_video_cb = cb;
impl->on_video_userdata = user_data;
}
void rtmp_server_set_on_disconnect(RtmpServerHandle handle,
RtmpOnDisconnectCallback cb, void* user_data)
{
if (!handle) return;
Impl* impl = static_cast<Impl *>(handle);
impl->on_disconnect_cb = cb;
impl->on_disconnect_userdata = user_data;
}
void rtmp_server_set_auth_callback(RtmpServerHandle handle, RtmpAuthCallback cb,
void* user_data)
{
if (!handle) return;
Impl* impl = static_cast<Impl *>(handle);
impl->auth_cb = cb;
impl->auth_userdata = user_data;
}
void rtmp_server_enable_gop_cache(RtmpServerHandle handle, bool enable)
{
if (!handle) return;
static_cast<Impl *>(handle)->server->enableGOPCache(enable);
}
void rtmp_server_set_max_publishers_per_stream(RtmpServerHandle handle,
int max)
{
if (!handle) return;
static_cast<Impl *>(handle)->server->setMaxPublishersPerStream(max);
}
void rtmp_server_set_max_players_per_stream(RtmpServerHandle handle, int max)
{
if (!handle) return;
static_cast<Impl *>(handle)->server->setMaxPlayersPerStream(max);
}
void rtmp_server_set_max_total_connections(RtmpServerHandle handle, int max)
{
if (!handle) return;
static_cast<Impl *>(handle)->server->setMaxTotalConnections(max);
}
void rtmp_server_set_connection_timeout(RtmpServerHandle handle, int seconds)
{
if (!handle) return;
static_cast<Impl *>(handle)->server->setConnectionTimeout(seconds);
}
void rtmp_server_enable_ping_pong(RtmpServerHandle handle, bool enable,
int interval_seconds)
{
if (!handle) return;
static_cast<Impl *>(handle)->server->enablePingPong(enable, interval_seconds);
}
int rtmp_server_get_active_publishers(RtmpServerHandle handle)
{
if (!handle) return 0;
return static_cast<Impl *>(handle)->server->getActivePublishers();
}
int rtmp_server_get_active_players(RtmpServerHandle handle)
{
if (!handle) return 0;
return static_cast<Impl *>(handle)->server->getActivePlayers();
}
int rtmp_server_get_total_connections(RtmpServerHandle handle)
{
if (!handle) return 0;
return static_cast<Impl *>(handle)->server->getTotalConnections();
}
struct RtmpStreamStats rtmp_server_get_stream_stats(RtmpServerHandle handle,
const char* app, const char* stream_key)
{
struct RtmpStreamStats stats = {0};
if (!handle || !app || !stream_key) return stats;
Impl* impl = static_cast<Impl *>(handle);
auto cstats = impl->server->getStreamStats(app, stream_key);
stats.bytes_sent = cstats.bytes_sent;
stats.bytes_received = cstats.bytes_received;
stats.video_frames = cstats.video_frames;
stats.audio_frames = cstats.audio_frames;
stats.dropped_frames = cstats.dropped_frames;
stats.bitrate_kbps = cstats.getBitrate();
stats.uptime_seconds = cstats.getUptime();
return stats;
}
bool rtmp_server_start_recording(RtmpServerHandle handle, const char* app,
const char* stream_key, const char* filename)
{
if (!handle || !app || !stream_key || !filename) return false;
return static_cast<Impl *>(handle)->server->startRecording(app, stream_key,
filename);
}
void rtmp_server_stop_recording(RtmpServerHandle handle, const char* app,
const char* stream_key)
{
if (!handle || !app || !stream_key) return;
static_cast<Impl *>(handle)->server->stopRecording(app, stream_key);
}
bool rtmp_server_is_recording(RtmpServerHandle handle, const char* app,
const char* stream_key)
{
if (!handle || !app || !stream_key) return false;
return static_cast<Impl *>(handle)->server->isRecording(app, stream_key);
}
bool rtmp_server_broadcast_audio(RtmpServerHandle handle, const char* app,
const char* stream_key, const uint8_t* data, uint32_t length,
uint32_t timestamp)
{
if (!handle || !app || !stream_key || !data || length == 0) return false;
Impl* impl = static_cast<Impl *>(handle);
std::vector<uint8_t> vec(data, data + length);
return impl->server->sendAudioToPlayers(app, stream_key, vec, timestamp);
}
bool rtmp_server_broadcast_video(RtmpServerHandle handle, const char* app,
const char* stream_key, const uint8_t* data, uint32_t length,
uint32_t timestamp)
{
if (!handle || !app || !stream_key || !data || length == 0) return false;
Impl* impl = static_cast<Impl *>(handle);
std::vector<uint8_t> vec(data, data + length);
return impl->server->sendVideoToPlayers(app, stream_key, vec, timestamp);
}
bool rtmp_server_broadcast_metadata(RtmpServerHandle handle, const char* app,
const char* stream_key, const uint8_t* data, uint32_t length)
{
if (!handle || !app || !stream_key || !data || length == 0) return false;
Impl* impl = static_cast<Impl *>(handle);
std::vector<uint8_t> vec(data, data + length);
impl->server->sendMetadataToPlayers(app, stream_key, vec);
return true;
}
void rtmp_logger_set_level(RtmpLogLevel level)
{
rtmp::Logger::getInstance().setLevel((rtmp::LogLevel)level);
}
}

1765
src/rtmp_server.cpp Normal file

File diff suppressed because it is too large Load Diff