mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-09-30 22:14:55 -07:00
0934cce225
## Link to GitHub Issue or related Pull Request, if one exists fixes #875 ## Description of change Adds `-apistream`, an optional HTTP video stream of the mirrored screen. It listens on the API port +2. Two endpoints, sharing the same `screen`, `fps` and `q` parameters: /stream.mjpg JPEG frames, for clients with no container support /stream.h264 H.264 annex-b, for an app driving MediaCodec or VideoToolbox itself One encoder per connection, fed by a per-screen pump that always hands over the newest frame, so a slow reader drops frames instead of building a backlog. `capture.get_jpg` behaviour is unchanged. Additional documentation for developers: https://github.com/spice2x/spice2x.github.io/wiki/Video-Stream ## Testing
71 lines
2.1 KiB
C++
71 lines
2.1 KiB
C++
#include "stream_format.h"
|
|
|
|
#include <vector>
|
|
|
|
#include "h264_stream.h"
|
|
#include "hooks/graphics/jpeg_encoder.h"
|
|
|
|
namespace api {
|
|
|
|
namespace {
|
|
|
|
#ifdef SPICE_JPEG
|
|
constexpr const char *MJPEG_BOUNDARY = "spice2xframe";
|
|
|
|
// multipart/x-mixed-replace: every frame is a standalone JPEG, no inter-frame state
|
|
class MjpegWriter : public StreamWriter {
|
|
public:
|
|
|
|
explicit MjpegWriter(int quality) : quality(quality) {}
|
|
|
|
std::string content_type() const override {
|
|
return std::string("multipart/x-mixed-replace; boundary=") + MJPEG_BOUNDARY;
|
|
}
|
|
|
|
bool write(const StreamSend &send, const capture_pump::Frame &frame) override {
|
|
this->jpeg.clear();
|
|
if (!jpeg_encoder::encode(
|
|
this->jpeg, frame.pixels.get(),
|
|
frame.width, frame.height, this->quality)) {
|
|
// a frame the encoder rejects is not worth dropping the client over
|
|
return true;
|
|
}
|
|
|
|
const std::string part =
|
|
"--" + std::string(MJPEG_BOUNDARY) + "\r\n"
|
|
"Content-Type: image/jpeg\r\n"
|
|
"Content-Length: " + std::to_string(this->jpeg.size()) + "\r\n"
|
|
"\r\n";
|
|
|
|
return send(part.data(), part.size())
|
|
&& send(this->jpeg.data(), this->jpeg.size())
|
|
&& send("\r\n", 2);
|
|
}
|
|
|
|
private:
|
|
int quality;
|
|
std::vector<uint8_t> jpeg;
|
|
};
|
|
#endif
|
|
}
|
|
|
|
// both parameters go unused on toolchains that compile in neither format
|
|
std::unique_ptr<StreamWriter> make_stream_writer(
|
|
const std::string &path, [[maybe_unused]] int quality, [[maybe_unused]] int fps) {
|
|
|
|
#ifdef SPICE_JPEG
|
|
if (path == "/stream.mjpg") {
|
|
return std::make_unique<MjpegWriter>(quality);
|
|
}
|
|
#endif
|
|
|
|
#ifdef SPICE_H264
|
|
if (path == "/stream.h264") {
|
|
return make_h264_writer(quality, fps);
|
|
}
|
|
#endif
|
|
|
|
return nullptr;
|
|
}
|
|
}
|