api: h.264 video stream (#876)
## 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
This commit is contained in:
@@ -1481,11 +1481,11 @@ void graphics_capture_skip(int screen) {
|
||||
GRAPHICS_CAPTURE_CV[screen].notify_one();
|
||||
}
|
||||
|
||||
bool graphics_capture_receive_jpeg(int screen, std::vector<uint8_t> &out,
|
||||
int quality, int divide, uint64_t *timestamp,
|
||||
bool graphics_capture_receive_raw(int screen, std::shared_ptr<uint8_t[]> &out,
|
||||
int divide, uint64_t *timestamp,
|
||||
int *width, int *height) {
|
||||
|
||||
out.clear();
|
||||
out = nullptr;
|
||||
|
||||
if (screen < 0 || screen >= static_cast<int>(GRAPHICS_CAPTURE_SCREEN_NO)) {
|
||||
return false;
|
||||
@@ -1558,9 +1558,39 @@ bool graphics_capture_receive_jpeg(int screen, std::vector<uint8_t> &out,
|
||||
capture_height = height_new;
|
||||
}
|
||||
|
||||
out = std::move(capture_data);
|
||||
|
||||
// status
|
||||
if (timestamp) {
|
||||
*timestamp = capture_timestamp;
|
||||
}
|
||||
if (width) {
|
||||
*width = capture_width;
|
||||
}
|
||||
if (height) {
|
||||
*height = capture_height;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool graphics_capture_receive_jpeg(int screen, std::vector<uint8_t> &out,
|
||||
int quality, int divide, uint64_t *timestamp,
|
||||
int *width, int *height) {
|
||||
|
||||
out.clear();
|
||||
|
||||
std::shared_ptr<uint8_t[]> pixels;
|
||||
int capture_width = 0;
|
||||
int capture_height = 0;
|
||||
if (!graphics_capture_receive_raw(
|
||||
screen, pixels, divide, timestamp, &capture_width, &capture_height)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// compress
|
||||
const bool success = jpeg_encoder::encode(
|
||||
out, capture_data.get(),
|
||||
out, pixels.get(),
|
||||
capture_width, capture_height, quality);
|
||||
|
||||
if (!success) {
|
||||
@@ -1568,9 +1598,6 @@ bool graphics_capture_receive_jpeg(int screen, std::vector<uint8_t> &out,
|
||||
}
|
||||
|
||||
// status
|
||||
if (timestamp) {
|
||||
*timestamp = capture_timestamp;
|
||||
}
|
||||
if (width) {
|
||||
*width = capture_width;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
@@ -145,6 +146,11 @@ void graphics_capture_trigger(int screen);
|
||||
bool graphics_capture_consume(int *screen);
|
||||
void graphics_capture_enqueue(int screen, uint8_t *data, size_t width, size_t height);
|
||||
void graphics_capture_skip(int screen);
|
||||
// on success `out` owns packed 24bpp RGB pixels, width * height * 3 bytes
|
||||
bool graphics_capture_receive_raw(int screen, std::shared_ptr<uint8_t[]> &out,
|
||||
int divide = 0,
|
||||
uint64_t *timestamp = nullptr,
|
||||
int *width = nullptr, int *height = nullptr);
|
||||
// on success `out` holds the encoded JPEG; its storage is reused across calls
|
||||
bool graphics_capture_receive_jpeg(int screen, std::vector<uint8_t> &out,
|
||||
int quality = 80, int divide = 0,
|
||||
|
||||
Reference in New Issue
Block a user