2025-12-18 23:07:14 +08:00
|
|
|
#include "NetworkSender.h"
|
|
|
|
|
#include "ScreenCapture.h"
|
2025-12-22 13:48:06 +08:00
|
|
|
#include "IddBridge.h"
|
2025-12-18 23:07:14 +08:00
|
|
|
#include "VideoEncoder.h"
|
2025-12-22 14:49:47 +08:00
|
|
|
#include "TcpServer.h"
|
2025-12-18 23:07:14 +08:00
|
|
|
#include <iostream>
|
2025-12-19 15:28:38 +08:00
|
|
|
#include <sstream>
|
|
|
|
|
#include <vector>
|
2025-12-18 23:07:14 +08:00
|
|
|
#include <thread>
|
|
|
|
|
#include <chrono>
|
2025-12-18 23:36:08 +08:00
|
|
|
#include <fstream>
|
2025-12-22 13:48:06 +08:00
|
|
|
#include <windows.h>
|
2025-12-18 23:07:14 +08:00
|
|
|
|
|
|
|
|
int main(int argc, char* argv[]) {
|
2025-12-19 15:28:38 +08:00
|
|
|
std::string ipStr = "127.0.0.1";
|
2025-12-18 23:07:14 +08:00
|
|
|
int port = 8888;
|
2025-12-18 23:36:08 +08:00
|
|
|
std::string outputFileName = "";
|
2025-12-22 13:48:06 +08:00
|
|
|
std::string source = "screen";
|
|
|
|
|
int outputIndex = -1;
|
|
|
|
|
bool iddProducer = false;
|
|
|
|
|
int producerOutputIndex = -1;
|
2025-12-22 14:49:47 +08:00
|
|
|
std::string fileToSend = "";
|
|
|
|
|
std::string folderToSend = "";
|
2025-12-18 23:07:14 +08:00
|
|
|
|
2025-12-19 15:28:38 +08:00
|
|
|
if (argc > 1) ipStr = argv[1];
|
2025-12-18 23:07:14 +08:00
|
|
|
if (argc > 2) port = std::stoi(argv[2]);
|
2025-12-18 23:36:08 +08:00
|
|
|
if (argc > 3) outputFileName = argv[3];
|
2025-12-22 13:48:06 +08:00
|
|
|
for (int i = 4; i < argc; ++i) {
|
|
|
|
|
std::string arg = argv[i];
|
|
|
|
|
if (arg.rfind("--source=", 0) == 0) {
|
|
|
|
|
source = arg.substr(std::string("--source=").size());
|
|
|
|
|
} else if (arg.rfind("--output=", 0) == 0) {
|
|
|
|
|
try {
|
|
|
|
|
outputIndex = std::stoi(arg.substr(std::string("--output=").size()));
|
|
|
|
|
} catch (...) {}
|
|
|
|
|
} else if (arg == "--idd-producer") {
|
|
|
|
|
iddProducer = true;
|
|
|
|
|
} else if (arg.rfind("--producer-output=", 0) == 0) {
|
|
|
|
|
try {
|
|
|
|
|
producerOutputIndex = std::stoi(arg.substr(std::string("--producer-output=").size()));
|
|
|
|
|
} catch (...) {}
|
2025-12-22 14:49:47 +08:00
|
|
|
} else if (arg.rfind("--send-file=", 0) == 0) {
|
|
|
|
|
fileToSend = arg.substr(std::string("--send-file=").size());
|
|
|
|
|
} else if (arg.rfind("--send-folder=", 0) == 0) {
|
|
|
|
|
folderToSend = arg.substr(std::string("--send-folder=").size());
|
2025-12-22 13:48:06 +08:00
|
|
|
}
|
|
|
|
|
}
|
2025-12-18 23:07:14 +08:00
|
|
|
|
2025-12-19 15:28:38 +08:00
|
|
|
// Parse IPs
|
|
|
|
|
std::vector<std::string> ips;
|
|
|
|
|
std::stringstream ss(ipStr);
|
|
|
|
|
std::string item;
|
|
|
|
|
while (std::getline(ss, item, ',')) {
|
|
|
|
|
// Trim spaces might be good but let's assume no spaces for simplicity or user should handle
|
|
|
|
|
if (!item.empty()) {
|
|
|
|
|
ips.push_back(item);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (ips.empty()) ips.push_back("127.0.0.1");
|
|
|
|
|
|
2025-12-18 23:07:14 +08:00
|
|
|
std::cout << "Starting Windows Sender Demo..." << std::endl;
|
2025-12-19 15:28:38 +08:00
|
|
|
std::cout << "Targets: ";
|
|
|
|
|
for (const auto& ip : ips) std::cout << ip << " ";
|
|
|
|
|
std::cout << ", Port: " << port << std::endl;
|
2025-12-22 13:48:06 +08:00
|
|
|
std::cout << "Source: " << source << std::endl;
|
|
|
|
|
if (source == "screen" && outputIndex >= 0) {
|
|
|
|
|
std::cout << "Output Index: " << outputIndex << std::endl;
|
|
|
|
|
}
|
2025-12-18 23:36:08 +08:00
|
|
|
if (!outputFileName.empty()) {
|
|
|
|
|
std::cout << "Output File: " << outputFileName << std::endl;
|
|
|
|
|
}
|
2025-12-22 13:48:06 +08:00
|
|
|
if (source == "idd" && iddProducer) {
|
|
|
|
|
std::cout << "IDD Producer enabled" << std::endl;
|
|
|
|
|
if (producerOutputIndex >= 0) {
|
|
|
|
|
std::cout << "Producer Output Index: " << producerOutputIndex << std::endl;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-18 23:36:08 +08:00
|
|
|
|
2025-12-22 14:49:47 +08:00
|
|
|
// Start TCP Server
|
|
|
|
|
TcpServer tcpServer;
|
|
|
|
|
if (tcpServer.Start(8889)) {
|
|
|
|
|
// Wait for client to connect if we need to send a file immediately
|
|
|
|
|
if (!fileToSend.empty()) {
|
|
|
|
|
std::cout << "Waiting for client to connect to send file: " << fileToSend << "..." << std::endl;
|
|
|
|
|
// Simple polling wait (in a real app, use events/condition variables)
|
|
|
|
|
// But TcpServer::SendFile checks if client is connected.
|
|
|
|
|
// We'll try to send in a loop or thread.
|
|
|
|
|
std::thread([&tcpServer, fileToSend]() {
|
|
|
|
|
// Wait for up to 30 seconds for a client
|
|
|
|
|
for (int i = 0; i < 300; ++i) {
|
|
|
|
|
if (tcpServer.SendFile(fileToSend)) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
|
|
|
}
|
|
|
|
|
}).detach();
|
|
|
|
|
} else if (!folderToSend.empty()) {
|
|
|
|
|
std::cout << "Waiting for client to connect to send folder: " << folderToSend << "..." << std::endl;
|
|
|
|
|
std::thread([&tcpServer, folderToSend]() {
|
|
|
|
|
for (int i = 0; i < 300; ++i) {
|
|
|
|
|
if (tcpServer.SendFolder(folderToSend)) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
|
|
|
}
|
|
|
|
|
}).detach();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
std::cerr << "Failed to start TCP Server on port 8889" << std::endl;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-18 23:36:08 +08:00
|
|
|
// Debug: Open file to save H.264 stream if filename is provided
|
|
|
|
|
std::ofstream outFile;
|
|
|
|
|
if (!outputFileName.empty()) {
|
|
|
|
|
outFile.open(outputFileName, std::ios::binary);
|
|
|
|
|
if (outFile.is_open()) {
|
|
|
|
|
std::cout << "Debug: Saving video stream to '" << outputFileName << "'" << std::endl;
|
|
|
|
|
} else {
|
|
|
|
|
std::cerr << "Warning: Failed to open output file '" << outputFileName << "'" << std::endl;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-18 23:07:14 +08:00
|
|
|
|
2025-12-22 13:48:06 +08:00
|
|
|
std::thread producerThread;
|
|
|
|
|
if (source == "idd" && iddProducer) {
|
|
|
|
|
producerThread = std::thread([producerOutputIndex]() {
|
|
|
|
|
ScreenCapture cap;
|
|
|
|
|
bool okCap = false;
|
|
|
|
|
if (producerOutputIndex >= 0) okCap = cap.InitializeWithOutputIndex(producerOutputIndex);
|
|
|
|
|
else okCap = cap.Initialize();
|
|
|
|
|
if (!okCap) return;
|
|
|
|
|
D3D11_TEXTURE2D_DESC desc = {};
|
|
|
|
|
ComPtr<ID3D11Texture2D> frame;
|
|
|
|
|
while (!cap.CaptureFrame(frame)) {
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
|
|
|
}
|
|
|
|
|
frame->GetDesc(&desc);
|
|
|
|
|
cap.ReleaseFrame();
|
|
|
|
|
D3D11_TEXTURE2D_DESC sdesc = {};
|
|
|
|
|
sdesc.Width = desc.Width;
|
|
|
|
|
sdesc.Height = desc.Height;
|
|
|
|
|
sdesc.MipLevels = 1;
|
|
|
|
|
sdesc.ArraySize = 1;
|
|
|
|
|
sdesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
|
|
|
|
sdesc.SampleDesc.Count = 1;
|
|
|
|
|
sdesc.Usage = D3D11_USAGE_STAGING;
|
|
|
|
|
sdesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
|
|
|
|
ComPtr<ID3D11Texture2D> staging;
|
|
|
|
|
cap.GetDevice()->CreateTexture2D(&sdesc, nullptr, &staging);
|
|
|
|
|
|
|
|
|
|
IddProducer producer;
|
|
|
|
|
if (!producer.Initialize()) return;
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
ComPtr<ID3D11Texture2D> tex;
|
|
|
|
|
if (cap.CaptureFrame(tex)) {
|
|
|
|
|
cap.GetContext()->CopyResource(staging.Get(), tex.Get());
|
|
|
|
|
D3D11_MAPPED_SUBRESOURCE mapped = {};
|
|
|
|
|
if (SUCCEEDED(cap.GetContext()->Map(staging.Get(), 0, D3D11_MAP_READ, 0, &mapped))) {
|
|
|
|
|
producer.SubmitFrame(mapped.pData, desc.Width, desc.Height, (uint32_t)mapped.RowPitch);
|
|
|
|
|
cap.GetContext()->Unmap(staging.Get(), 0);
|
|
|
|
|
}
|
|
|
|
|
cap.ReleaseFrame();
|
|
|
|
|
} else {
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
bool ok = false;
|
2025-12-18 23:07:14 +08:00
|
|
|
ScreenCapture capture;
|
2025-12-22 13:48:06 +08:00
|
|
|
IddBridge idd;
|
|
|
|
|
bool useIdd = (source == "idd");
|
|
|
|
|
if (useIdd) {
|
|
|
|
|
ok = idd.Initialize();
|
|
|
|
|
} else {
|
|
|
|
|
if (outputIndex >= 0) {
|
|
|
|
|
ok = capture.InitializeWithOutputIndex(outputIndex);
|
|
|
|
|
} else {
|
|
|
|
|
ok = capture.Initialize();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!ok) {
|
|
|
|
|
std::cerr << "Failed to initialize capture source" << std::endl;
|
2025-12-18 23:07:14 +08:00
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get screen size
|
|
|
|
|
D3D11_TEXTURE2D_DESC desc;
|
|
|
|
|
ComPtr<ID3D11Texture2D> frame;
|
|
|
|
|
std::cout << "Waiting for first frame..." << std::endl;
|
2025-12-22 13:48:06 +08:00
|
|
|
if (useIdd) {
|
|
|
|
|
while (!idd.CaptureFrame(frame)) {
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
while (!capture.CaptureFrame(frame)) {
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
|
|
|
}
|
2025-12-18 23:07:14 +08:00
|
|
|
}
|
|
|
|
|
frame->GetDesc(&desc);
|
2025-12-22 13:48:06 +08:00
|
|
|
if (useIdd) {
|
|
|
|
|
idd.ReleaseFrame();
|
|
|
|
|
} else {
|
|
|
|
|
capture.ReleaseFrame();
|
|
|
|
|
}
|
2025-12-18 23:07:14 +08:00
|
|
|
|
|
|
|
|
int width = desc.Width;
|
|
|
|
|
int height = desc.Height;
|
|
|
|
|
std::cout << "Screen Size: " << width << "x" << height << std::endl;
|
|
|
|
|
|
|
|
|
|
VideoEncoder encoder;
|
2025-12-22 13:48:06 +08:00
|
|
|
if (!encoder.Initialize(useIdd ? idd.GetDevice() : capture.GetDevice(), width, height, 60, 4000000)) {
|
2025-12-18 23:07:14 +08:00
|
|
|
std::cerr << "Failed to initialize Video Encoder" << std::endl;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NetworkSender sender;
|
2025-12-19 15:28:38 +08:00
|
|
|
if (!sender.Initialize(ips, port)) {
|
2025-12-18 23:07:14 +08:00
|
|
|
std::cerr << "Failed to initialize Network Sender" << std::endl;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::cout << "Streaming started. Press Ctrl+C to stop." << std::endl;
|
|
|
|
|
|
|
|
|
|
int frameCount = 0;
|
|
|
|
|
auto lastTime = std::chrono::high_resolution_clock::now();
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
ComPtr<ID3D11Texture2D> texture;
|
2025-12-22 13:48:06 +08:00
|
|
|
bool got = false;
|
|
|
|
|
if (useIdd) {
|
|
|
|
|
got = idd.CaptureFrame(texture);
|
|
|
|
|
} else {
|
|
|
|
|
got = capture.CaptureFrame(texture);
|
|
|
|
|
}
|
|
|
|
|
if (got) {
|
|
|
|
|
D3D11_TEXTURE2D_DESC tdesc = {};
|
|
|
|
|
texture->GetDesc(&tdesc);
|
|
|
|
|
if (tdesc.Width != (UINT)width || tdesc.Height != (UINT)height) {
|
|
|
|
|
std::cout << "Resolution changed: " << width << "x" << height << " -> " << tdesc.Width << "x" << tdesc.Height << std::endl;
|
|
|
|
|
width = (int)tdesc.Width;
|
|
|
|
|
height = (int)tdesc.Height;
|
|
|
|
|
if (!encoder.Reinitialize(width, height, 60, 4000000)) {
|
|
|
|
|
std::cerr << "Failed to reinitialize encoder on resolution change" << std::endl;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-18 23:07:14 +08:00
|
|
|
std::vector<uint8_t> encodedData;
|
|
|
|
|
bool isKeyFrame = false;
|
|
|
|
|
|
|
|
|
|
if (encoder.EncodeFrame(texture.Get(), encodedData, isKeyFrame)) {
|
|
|
|
|
if (!encodedData.empty()) {
|
|
|
|
|
// Current timestamp in ms
|
|
|
|
|
auto now = std::chrono::high_resolution_clock::now();
|
|
|
|
|
uint64_t timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
|
|
|
|
|
|
|
|
|
|
sender.SendFrame(encodedData, timestamp, width, height, isKeyFrame);
|
|
|
|
|
// std::cout << "Sent frame: " << encodedData.size() << " bytes, Key: " << isKeyFrame << std::endl;
|
2025-12-18 23:36:08 +08:00
|
|
|
|
|
|
|
|
if (outFile.is_open()) {
|
|
|
|
|
outFile.write(reinterpret_cast<const char*>(encodedData.data()), encodedData.size());
|
|
|
|
|
}
|
2025-12-18 23:07:14 +08:00
|
|
|
}
|
|
|
|
|
}
|
2025-12-22 13:48:06 +08:00
|
|
|
if (useIdd) {
|
|
|
|
|
idd.ReleaseFrame();
|
|
|
|
|
} else {
|
|
|
|
|
capture.ReleaseFrame();
|
|
|
|
|
}
|
2025-12-18 23:07:14 +08:00
|
|
|
|
|
|
|
|
frameCount++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto now = std::chrono::high_resolution_clock::now();
|
|
|
|
|
if (std::chrono::duration_cast<std::chrono::seconds>(now - lastTime).count() >= 1) {
|
|
|
|
|
std::cout << "FPS: " << frameCount << std::endl;
|
|
|
|
|
frameCount = 0;
|
|
|
|
|
lastTime = now;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Don't sleep too much, CaptureFrame waits.
|
|
|
|
|
// But if CaptureFrame returns immediately (high refresh rate), we might want to cap it?
|
|
|
|
|
// Desktop Duplication limits itself to screen refresh rate usually.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|