Merge pull request #8 from cgutman/hevc_colorspaces

Add support for HEVC and client-specified colorspaces
This commit is contained in:
loki-47-6F-64 2020-01-20 18:44:41 +01:00 committed by GitHub
commit 18f11ec735
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 119 additions and 20 deletions

View File

@ -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

View File

@ -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);

View File

@ -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;
};

View File

@ -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++);

View File

@ -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 &) {

View File

@ -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()) {

View File

@ -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);