Skip to content

Instantly share code, notes, and snippets.

@timleland
Created March 10, 2025 15:25
Show Gist options
  • Save timleland/4c55cbf55b3d98c019177a4ea36860a1 to your computer and use it in GitHub Desktop.
Save timleland/4c55cbf55b3d98c019177a4ea36860a1 to your computer and use it in GitHub Desktop.
RTSP Home Dashboard Live Streamer
#include <opencv2/opencv.hpp>
#include <SDL2/SDL.h>
#include <thread>
#include <atomic>
#include <vector>
#include <mutex>
#include <iostream>
std::vector<std::string> rtsp_urls = {
"rtsp://URL",
"rtsp://URL",
"rtsp://URL",
"rtsp://URL",
};
std::atomic<bool> running(true);
int screenWidth = 1920, screenHeight = 1080;
struct CameraFeed
{
cv::VideoCapture cap;
cv::Mat frame;
std::atomic<bool> isConnected;
std::mutex lock;
};
// Camera feeds
std::vector<CameraFeed> cameras(4);
void captureThread(int index)
{
while (running)
{
if (!cameras[index].cap.isOpened())
{
std::cout << "[Camera " << index + 1 << "] Connecting...\n";
cameras[index].cap.open(rtsp_urls[index], cv::CAP_FFMPEG);
if (!cameras[index].cap.isOpened())
{
std::cout << "[Camera " << index + 1 << "] Connection failed. Retrying in 2 seconds...\n";
std::this_thread::sleep_for(std::chrono::seconds(2));
continue;
}
cameras[index].cap.set(cv::CAP_PROP_BUFFERSIZE, 1);
cameras[index].cap.set(cv::CAP_PROP_FPS, 20);
}
cv::Mat frame;
if (cameras[index].cap.read(frame))
{
std::lock_guard<std::mutex> guard(cameras[index].lock);
cameras[index].frame = frame;
cameras[index].isConnected = true;
}
else
{
cameras[index].isConnected = false;
std::cout << "[Camera " << index + 1 << "] Disconnected. Reconnecting...\n";
cameras[index].cap.release();
std::this_thread::sleep_for(std::chrono::seconds(2));
}
}
}
int main()
{
if (SDL_Init(SDL_INIT_VIDEO) < 0)
{
std::cerr << "SDL initialization failed! SDL_Error: " << SDL_GetError() << std::endl;
return -1;
}
SDL_DisplayMode dm;
if (SDL_GetCurrentDisplayMode(0, &dm) == 0)
{
screenWidth = dm.w;
screenHeight = dm.h;
}
else
{
std::cerr << "SDL_GetCurrentDisplayMode failed: " << SDL_GetError() << std::endl;
}
SDL_Window *window = SDL_CreateWindow("RTSP Viewer", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
screenWidth, screenHeight, SDL_WINDOW_FULLSCREEN);
if (!window)
{
std::cerr << "SDL_CreateWindow failed: " << SDL_GetError() << std::endl;
return -1;
}
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
int camWidth = screenWidth / 2;
int camHeight = screenHeight / 2;
std::vector<std::thread> threads;
for (int i = 0; i < 4; i++)
{
cameras[i].isConnected = false;
threads.emplace_back(captureThread, i);
}
SDL_Event e;
bool quit = false;
while (!quit)
{
while (SDL_PollEvent(&e))
{
if (e.type == SDL_QUIT)
quit = true;
}
SDL_RenderClear(renderer);
for (int i = 0; i < 4; i++)
{
cv::Mat frame;
{
std::lock_guard<std::mutex> guard(cameras[i].lock);
if (!cameras[i].frame.empty())
{
frame = cameras[i].frame.clone();
}
}
if (!frame.empty())
{
// Fix Color: Convert BGR to RGB
cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB);
cv::resize(frame, frame, cv::Size(camWidth, camHeight));
// Correct pixel format and bit masking
SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(
frame.data, frame.cols, frame.rows, 24, frame.step,
0x000000FF, 0x0000FF00, 0x00FF0000, 0);
SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_FreeSurface(surface);
SDL_Rect destRect = {(i % 2) * camWidth, (i / 2) * camHeight, camWidth, camHeight};
SDL_RenderCopy(renderer, texture, NULL, &destRect);
SDL_DestroyTexture(texture);
}
}
SDL_RenderPresent(renderer);
SDL_Delay(10);
}
running = false;
for (auto &t : threads)
t.join();
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment