mirror of
https://github.com/LizardByte/Sunshine.git
synced 2025-02-24 18:39:49 +00:00
Merge pull request #8 from cgutman/hevc_colorspaces
Add support for HEVC and client-specified colorspaces
This commit is contained in:
commit
18f11ec735
@ -86,8 +86,13 @@ qp = 28
|
||||
# Number of threads used by ffmpeg to encode the video
|
||||
threads = 8
|
||||
|
||||
# Allows the client to request HEVC Main or HEVC Main10 video streams.
|
||||
# HEVC is more CPU-intensive to encode, so enabling this may reduce performance.
|
||||
# If set to 0 (default), Sunshine will not advertise support for HEVC
|
||||
# If set to 1, Sunshine will advertise support for HEVC Main profile
|
||||
# If set to 2, Sunshine will advertise support for HEVC Main and Main10 (HDR) profiles
|
||||
# hevc_mode = 2
|
||||
|
||||
# See x264 --fullhelp for the different presets
|
||||
profile = baseline
|
||||
preset = superfast
|
||||
tune = zerolatency
|
||||
|
@ -21,7 +21,7 @@ video_t video {
|
||||
|
||||
4, // threads
|
||||
|
||||
"baseline"s, // profile
|
||||
0, // hevc_mode
|
||||
"superfast"s, // preset
|
||||
"zerolatency"s // tune
|
||||
};
|
||||
@ -158,7 +158,9 @@ void parse_file(const char *file) {
|
||||
int_f(vars, "crf", video.crf);
|
||||
int_f(vars, "qp", video.qp);
|
||||
int_f(vars, "threads", video.threads);
|
||||
string_f(vars, "profile", video.profile);
|
||||
int_between_f(vars, "hevc_mode", video.hevc_mode, {
|
||||
0, 2
|
||||
});
|
||||
string_f(vars, "preset", video.preset);
|
||||
string_f(vars, "tune", video.tune);
|
||||
|
||||
|
@ -12,7 +12,7 @@ struct video_t {
|
||||
|
||||
int threads; // Number threads used by ffmpeg
|
||||
|
||||
std::string profile;
|
||||
int hevc_mode;
|
||||
std::string preset;
|
||||
std::string tune;
|
||||
};
|
||||
|
@ -437,13 +437,21 @@ void serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> res
|
||||
tree.put("root.appversion", VERSION);
|
||||
tree.put("root.GfeVersion", GFE_VERSION);
|
||||
tree.put("root.uniqueid", config::nvhttp.unique_id);
|
||||
tree.put("root.mac", "42:45:F0:65:D6:F4");
|
||||
tree.put("root.mac", "00:00:00:00:00:00");
|
||||
tree.put("root.MaxLumaPixelsHEVC", config::video.hevc_mode > 0 ? "1869449984" : "0");
|
||||
tree.put("root.LocalIP", local_ip);
|
||||
|
||||
if(config::nvhttp.external_ip.empty()) {
|
||||
tree.put("root.ExternalIP", local_ip);
|
||||
if(config::video.hevc_mode == 2) {
|
||||
tree.put("root.ServerCodecModeSupport", "3843");
|
||||
}
|
||||
else if(config::video.hevc_mode == 1) {
|
||||
tree.put("root.ServerCodecModeSupport", "259");
|
||||
}
|
||||
else {
|
||||
tree.put("root.ServerCodecModeSupport", "3");
|
||||
}
|
||||
|
||||
if(!config::nvhttp.external_ip.empty()) {
|
||||
tree.put("root.ExternalIP", config::nvhttp.external_ip);
|
||||
}
|
||||
|
||||
@ -485,7 +493,7 @@ void applist(resp_https_t response, req_https_t request) {
|
||||
pt::ptree desktop;
|
||||
|
||||
apps.put("<xmlattr>.status_code", 200);
|
||||
desktop.put("IsHdrSupported"s, 0);
|
||||
desktop.put("IsHdrSupported"s, config::video.hevc_mode == 2 ? 1 : 0);
|
||||
desktop.put("AppTitle"s, "Desktop");
|
||||
desktop.put("ID"s, 1);
|
||||
|
||||
@ -493,7 +501,7 @@ void applist(resp_https_t response, req_https_t request) {
|
||||
for(auto &proc : proc::proc.get_apps()) {
|
||||
pt::ptree app;
|
||||
|
||||
app.put("IsHdrSupported"s, 0);
|
||||
app.put("IsHdrSupported"s, config::video.hevc_mode == 2 ? 1 : 0);
|
||||
app.put("AppTitle"s, proc.name);
|
||||
app.put("ID"s, x++);
|
||||
|
||||
|
@ -714,13 +714,21 @@ void videoThread(video::idr_event_t idr_events) {
|
||||
|
||||
// make sure moonlight recognizes the nalu code for IDR frames
|
||||
if (packet->flags & AV_PKT_FLAG_KEY) {
|
||||
//TODO: Not all encoders encode their IDR frames with `"\000\000\001e"`
|
||||
auto seq_i_frame_old = "\000\000\001e"sv;
|
||||
auto seq_i_frame = "\000\000\000\001e"sv;
|
||||
|
||||
assert(std::search(std::begin(payload), std::end(payload), std::begin(seq_i_frame), std::end(seq_i_frame)) ==
|
||||
std::end(payload));
|
||||
payload_new = replace(payload, seq_i_frame_old, seq_i_frame);
|
||||
// TODO: Not all encoders encode their IDR frames with the 4 byte NALU prefix
|
||||
if(config.monitor.videoFormat == 0) {
|
||||
auto h264_i_frame_old = "\000\000\001e"sv;
|
||||
auto h264_i_frame = "\000\000\000\001e"sv;
|
||||
assert(std::search(std::begin(payload), std::end(payload), std::begin(h264_i_frame), std::end(h264_i_frame)) ==
|
||||
std::end(payload));
|
||||
payload_new = replace(payload, h264_i_frame_old, h264_i_frame);
|
||||
}
|
||||
else {
|
||||
auto hevc_i_frame_old = "\000\000\001("sv;
|
||||
auto hevc_i_frame = "\000\000\000\001("sv;
|
||||
assert(std::search(std::begin(payload), std::end(payload), std::begin(hevc_i_frame), std::end(hevc_i_frame)) ==
|
||||
std::end(payload));
|
||||
payload_new = replace(payload, hevc_i_frame_old, hevc_i_frame);
|
||||
}
|
||||
|
||||
payload = {(char *) payload_new.data(), payload_new.size()};
|
||||
}
|
||||
@ -869,7 +877,7 @@ void cmd_describe(host_t &host, peer_t peer, msg_t&& req) {
|
||||
option.content = const_cast<char*>(seqn_str.c_str());
|
||||
|
||||
// FIXME: Moonlight will accept the payload, but the value of the option is not correct
|
||||
respond(host, peer, &option, 200, "OK", req->sequenceNumber, "surround-params=NONE"sv);
|
||||
respond(host, peer, &option, 200, "OK", req->sequenceNumber, "sprop-parameter-sets=AAAAAU;surround-params=NONE"sv);
|
||||
}
|
||||
|
||||
void cmd_setup(host_t &host, peer_t peer, msg_t &&req) {
|
||||
@ -976,6 +984,12 @@ void cmd_announce(host_t &host, peer_t peer, msg_t &&req) {
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize any omitted parameters to defaults
|
||||
args.try_emplace("x-nv-video[0].encoderCscMode"sv, "0"sv);
|
||||
args.try_emplace("x-nv-vqos[0].bitStreamFormat"sv, "0"sv);
|
||||
args.try_emplace("x-nv-video[0].dynamicRangeMode"sv, "0"sv);
|
||||
args.try_emplace("x-nv-aqos.packetDuration"sv, "5"sv);
|
||||
|
||||
try {
|
||||
|
||||
auto &config = session.config;
|
||||
@ -991,6 +1005,9 @@ void cmd_announce(host_t &host, peer_t peer, msg_t &&req) {
|
||||
config.monitor.bitrate = util::from_view(args.at("x-nv-vqos[0].bw.maximumBitrateKbps"sv));
|
||||
config.monitor.slicesPerFrame = util::from_view(args.at("x-nv-video[0].videoEncoderSlicesPerFrame"sv));
|
||||
config.monitor.numRefFrames = util::from_view(args.at("x-nv-video[0].maxNumReferenceFrames"sv));
|
||||
config.monitor.encoderCscMode = util::from_view(args.at("x-nv-video[0].encoderCscMode"sv));
|
||||
config.monitor.videoFormat = util::from_view(args.at("x-nv-vqos[0].bitStreamFormat"sv));
|
||||
config.monitor.dynamicRange = util::from_view(args.at("x-nv-video[0].dynamicRangeMode"sv));
|
||||
|
||||
} catch(std::out_of_range &) {
|
||||
|
||||
|
@ -90,7 +90,14 @@ void encodeThread(
|
||||
config_t config) {
|
||||
int framerate = config.framerate;
|
||||
|
||||
auto codec = avcodec_find_encoder(AV_CODEC_ID_H264);
|
||||
AVCodec *codec;
|
||||
|
||||
if(config.videoFormat == 0) {
|
||||
codec = avcodec_find_encoder(AV_CODEC_ID_H264);
|
||||
}
|
||||
else {
|
||||
codec = avcodec_find_encoder(AV_CODEC_ID_HEVC);
|
||||
}
|
||||
|
||||
ctx_t ctx{avcodec_alloc_context3(codec)};
|
||||
|
||||
@ -100,7 +107,53 @@ void encodeThread(
|
||||
ctx->height = config.height;
|
||||
ctx->time_base = AVRational{1, framerate};
|
||||
ctx->framerate = AVRational{framerate, 1};
|
||||
ctx->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||
|
||||
if(config.videoFormat == 0) {
|
||||
ctx->profile = FF_PROFILE_H264_HIGH;
|
||||
}
|
||||
else if(config.dynamicRange == 0) {
|
||||
ctx->profile = FF_PROFILE_HEVC_MAIN;
|
||||
}
|
||||
else {
|
||||
ctx->profile = FF_PROFILE_HEVC_MAIN_10;
|
||||
}
|
||||
|
||||
if(config.dynamicRange == 0) {
|
||||
ctx->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||
}
|
||||
else {
|
||||
ctx->pix_fmt = AV_PIX_FMT_YUV420P10;
|
||||
}
|
||||
|
||||
ctx->color_range = (config.encoderCscMode & 0x1) ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG;
|
||||
|
||||
int swsColorSpace;
|
||||
switch (config.encoderCscMode >> 1) {
|
||||
case 0:
|
||||
default:
|
||||
// Rec. 601
|
||||
ctx->color_primaries = AVCOL_PRI_SMPTE170M;
|
||||
ctx->color_trc = AVCOL_TRC_SMPTE170M;
|
||||
ctx->colorspace = AVCOL_SPC_SMPTE170M;
|
||||
swsColorSpace = SWS_CS_SMPTE170M;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// Rec. 709
|
||||
ctx->color_primaries = AVCOL_PRI_BT709;
|
||||
ctx->color_trc = AVCOL_TRC_BT709;
|
||||
ctx->colorspace = AVCOL_SPC_BT709;
|
||||
swsColorSpace = SWS_CS_ITU709;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// Rec. 2020
|
||||
ctx->color_primaries = AVCOL_PRI_BT2020;
|
||||
ctx->color_trc = AVCOL_TRC_BT2020_10;
|
||||
ctx->colorspace = AVCOL_SPC_BT2020_NCL;
|
||||
swsColorSpace = SWS_CS_BT2020;
|
||||
break;
|
||||
}
|
||||
|
||||
// B-frames delay decoder output, so never use them
|
||||
ctx->max_b_frames = 0;
|
||||
@ -118,7 +171,6 @@ void encodeThread(
|
||||
|
||||
|
||||
AVDictionary *options {nullptr};
|
||||
av_dict_set(&options, "profile", config::video.profile.c_str(), 0);
|
||||
av_dict_set(&options, "preset", config::video.preset.c_str(), 0);
|
||||
av_dict_set(&options, "tune", config::video.tune.c_str(), 0);
|
||||
|
||||
@ -135,6 +187,14 @@ void encodeThread(
|
||||
else {
|
||||
av_dict_set_int(&options, "qp", config::video.qp, 0);
|
||||
}
|
||||
|
||||
if(config.videoFormat == 1) {
|
||||
// x265's Info SEI is so long that it causes the IDR picture data to be
|
||||
// kicked to the 2nd packet in the frame, breaking Moonlight's parsing logic.
|
||||
// It also looks like gop_size isn't passed on to x265, so we have to set
|
||||
// 'keyint=-1' in the parameters ourselves.
|
||||
av_dict_set(&options, "x265-params", "info=0:keyint=-1", 0);
|
||||
}
|
||||
|
||||
ctx->flags |= (AV_CODEC_FLAG_CLOSED_GOP | AV_CODEC_FLAG_LOW_DELAY);
|
||||
ctx->flags2 |= AV_CODEC_FLAG2_FAST;
|
||||
@ -169,6 +229,10 @@ void encodeThread(
|
||||
ctx->width, ctx->height, ctx->pix_fmt,
|
||||
SWS_LANCZOS | SWS_ACCURATE_RND,
|
||||
nullptr, nullptr, nullptr));
|
||||
|
||||
sws_setColorspaceDetails(sws.get(), sws_getCoefficients(SWS_CS_DEFAULT), 0,
|
||||
sws_getCoefficients(swsColorSpace), config.encoderCscMode & 0x1,
|
||||
0, 1 << 16, 1 << 16);
|
||||
}
|
||||
|
||||
if(idr_events->peek()) {
|
||||
|
@ -22,6 +22,9 @@ struct config_t {
|
||||
int bitrate;
|
||||
int slicesPerFrame;
|
||||
int numRefFrames;
|
||||
int encoderCscMode;
|
||||
int videoFormat;
|
||||
int dynamicRange;
|
||||
};
|
||||
|
||||
void capture_display(packet_queue_t packets, idr_event_t idr_events, config_t config);
|
||||
|
Loading…
x
Reference in New Issue
Block a user