Allow replacement of hevc headers

This commit is contained in:
loki
2021-06-20 15:29:51 +02:00
parent 7b86ea9e87
commit 9e7ecf8db2
11 changed files with 578 additions and 168 deletions

View File

@@ -1,10 +1,12 @@
extern "C" {
#include <cbs/cbs_h264.h>
#include <cbs/cbs_h265.h>
#include <cbs/h264_levels.h>
#include <cbs/video_levels.h>
#include <libavcodec/avcodec.h>
#include <libavutil/pixdesc.h>
}
#include "cbs.h"
#include "main.h"
#include "utility.h"
@@ -46,19 +48,16 @@ public:
}
};
util::buffer_t<std::uint8_t> write(std::uint8_t nal, void *uh, AVCodecID codec_id) {
util::buffer_t<std::uint8_t> write(const cbs::ctx_t &cbs_ctx, std::uint8_t nal, void *uh, AVCodecID codec_id) {
cbs::frag_t frag;
auto err = ff_cbs_insert_unit_content(&frag, -1, nal, uh, nullptr);
if(err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
BOOST_LOG(error) << "Could not NAL unit SPS: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
BOOST_LOG(error) << "Could not insert NAL unit SPS: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return {};
}
cbs::ctx_t cbs_ctx;
ff_cbs_init(&cbs_ctx, codec_id, nullptr);
err = ff_cbs_write_fragment_data(cbs_ctx.get(), &frag);
if(err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
@@ -74,6 +73,13 @@ util::buffer_t<std::uint8_t> write(std::uint8_t nal, void *uh, AVCodecID codec_i
return data;
}
util::buffer_t<std::uint8_t> write(std::uint8_t nal, void *uh, AVCodecID codec_id) {
cbs::ctx_t cbs_ctx;
ff_cbs_init(&cbs_ctx, codec_id, nullptr);
return write(cbs_ctx, nal, uh, codec_id);
}
util::buffer_t<std::uint8_t> make_sps_h264(const AVCodecContext *ctx) {
H264RawSPS sps {};
@@ -162,6 +168,80 @@ util::buffer_t<std::uint8_t> make_sps_h264(const AVCodecContext *ctx) {
return write(sps.nal_unit_header.nal_unit_type, (void *)&sps.nal_unit_header, AV_CODEC_ID_H264);
}
hevc_t make_sps_hevc(const AVCodecContext *avctx, const AVPacket *packet) {
cbs::ctx_t ctx;
if(ff_cbs_init(&ctx, AV_CODEC_ID_H265, nullptr)) {
return {};
}
cbs::frag_t frag;
int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
if(err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);
return {};
}
auto vps_p = ((CodedBitstreamH265Context *)ctx->priv_data)->active_vps;
auto sps_p = ((CodedBitstreamH265Context *)ctx->priv_data)->active_sps;
H265RawSPS sps { *sps_p };
H265RawVPS vps { *vps_p };
vps.profile_tier_level.general_profile_compatibility_flag[4] = 1;
sps.profile_tier_level.general_profile_compatibility_flag[4] = 1;
auto &vui = sps.vui;
std::memset(&vui, 0, sizeof(vui));
sps.vui_parameters_present_flag = 1;
// skip sample aspect ratio
vui.video_format = 5;
vui.colour_description_present_flag = 1;
vui.video_signal_type_present_flag = 1;
vui.video_full_range_flag = avctx->color_range == AVCOL_RANGE_JPEG;
vui.colour_primaries = avctx->color_primaries;
vui.transfer_characteristics = avctx->color_trc;
vui.matrix_coefficients = avctx->colorspace;
vui.vui_timing_info_present_flag = vps.vps_timing_info_present_flag;
vui.vui_num_units_in_tick = vps.vps_num_units_in_tick;
vui.vui_time_scale = vps.vps_time_scale;
vui.vui_poc_proportional_to_timing_flag = vps.vps_poc_proportional_to_timing_flag;
vui.vui_num_ticks_poc_diff_one_minus1 = vps.vps_num_ticks_poc_diff_one_minus1;
vui.vui_hrd_parameters_present_flag = 0;
vui.bitstream_restriction_flag = 1;
vui.motion_vectors_over_pic_boundaries_flag = 1;
vui.restricted_ref_pic_lists_flag = 1;
vui.max_bytes_per_pic_denom = 0;
vui.max_bits_per_min_cu_denom = 0;
vui.log2_max_mv_length_horizontal = 15;
vui.log2_max_mv_length_vertical = 15;
cbs::ctx_t write_ctx;
ff_cbs_init(&write_ctx, AV_CODEC_ID_H265, nullptr);
return hevc_t {
nal_t {
write(write_ctx, vps.nal_unit_header.nal_unit_type, (void *)&vps.nal_unit_header, AV_CODEC_ID_H265),
write(ctx, vps_p->nal_unit_header.nal_unit_type, (void *)&vps_p->nal_unit_header, AV_CODEC_ID_H265),
},
nal_t {
write(write_ctx, sps.nal_unit_header.nal_unit_type, (void *)&sps.nal_unit_header, AV_CODEC_ID_H265),
write(ctx, sps_p->nal_unit_header.nal_unit_type, (void *)&sps_p->nal_unit_header, AV_CODEC_ID_H265),
},
};
}
util::buffer_t<std::uint8_t> read_sps_h264(const AVPacket *packet) {
cbs::ctx_t ctx;
if(ff_cbs_init(&ctx, AV_CODEC_ID_H264, nullptr)) {
@@ -182,15 +262,11 @@ util::buffer_t<std::uint8_t> read_sps_h264(const AVPacket *packet) {
return write(h264->nal_unit_type, (void *)h264, AV_CODEC_ID_H264);
}
util::buffer_t<std::uint8_t> make_sps(const AVCodecContext *ctx, int format) {
switch(format) {
case 0:
return make_sps_h264(ctx);
}
BOOST_LOG(warning) << "make_sps: video format ["sv << format << "] not supported"sv;
return {};
h264_t make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet) {
return h264_t {
make_sps_h264(ctx),
read_sps_h264(packet),
};
}
bool validate_sps(const AVPacket *packet, int codec_id) {
@@ -201,7 +277,7 @@ bool validate_sps(const AVPacket *packet, int codec_id) {
cbs::frag_t frag;
int err = ff_cbs_read_packet(ctx.get(), &frag, &*packet);
int err = ff_cbs_read_packet(ctx.get(), &frag, packet);
if(err < 0) {
char err_str[AV_ERROR_MAX_STRING_SIZE] { 0 };
BOOST_LOG(error) << "Couldn't read packet: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err);

View File

@@ -5,18 +5,25 @@
struct AVPacket;
struct AVCodecContext;
namespace cbs {
struct sps_hevc_t {
util::buffer_t<std::uint8_t> vps;
util::buffer_t<std::uint8_t> sps;
struct nal_t {
util::buffer_t<std::uint8_t> _new;
util::buffer_t<std::uint8_t> old;
};
util::buffer_t<std::uint8_t> read_sps_h264(const AVPacket *packet);
sps_hevc_t read_sps_hevc(const AVPacket *packet);
struct hevc_t {
nal_t vps;
nal_t sps;
};
util::buffer_t<std::uint8_t> make_sps_h264(const AVCodecContext *ctx);
sps_hevc_t make_sps_hevc(const AVCodecContext *ctx);
struct h264_t {
nal_t sps;
};
hevc_t make_sps_hevc(const AVCodecContext *ctx, const AVPacket *packet);
h264_t make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet);
/**
* Check if SPS->VUI is present

View File

@@ -549,6 +549,9 @@ int apply_flags(const char *line) {
case '1':
config::sunshine.flags[config::flag::FRESH_STATE].flip();
break;
case '2':
config::sunshine.flags[config::flag::FORCE_VIDEO_HEADER_REPLACE].flip();
break;
case 'p':
config::sunshine.flags[config::flag::CONST_PIN].flip();
break;
@@ -721,6 +724,13 @@ int parse(int argc, char *argv[]) {
return -1;
}
TUPLE_2D_REF(name, val, *var);
auto it = cmd_vars.find(name);
if(it != std::end(cmd_vars)) {
cmd_vars.erase(it);
}
cmd_vars.emplace(std::move(*var));
}
}

View File

@@ -80,9 +80,10 @@ struct input_t {
namespace flag {
enum flag_e : std::size_t {
PIN_STDIN = 0, // Read PIN from stdin instead of http
FRESH_STATE, // Do not load or save state
CONST_PIN, // Use "universal" pin
PIN_STDIN = 0, // Read PIN from stdin instead of http
FRESH_STATE, // Do not load or save state
FORCE_VIDEO_HEADER_REPLACE, // force replacing headers inside video data
CONST_PIN, // Use "universal" pin
FLAG_SIZE
};
}

View File

@@ -63,7 +63,8 @@ void print_help(const char *name) {
<< " flags"sv << std::endl
<< " -0 | Read PIN from stdin"sv << std::endl
<< " -1 | Do not load previously saved state and do retain any state after shutdown"sv << std::endl
<< " | Effectively starting as if for the first time without overwriting any pairings with your devices"sv << std::endl;
<< " | Effectively starting as if for the first time without overwriting any pairings with your devices"sv << std::endl
<< " -2 | Force replacement of headers in video stream" << std::endl;
}
namespace help {

View File

@@ -317,15 +317,20 @@ struct encoder_t {
class session_t {
public:
session_t() = default;
session_t(ctx_t &&ctx, util::wrap_ptr<platf::hwdevice_t> &&device) : ctx { std::move(ctx) }, device { std::move(device) } {}
session_t(ctx_t &&ctx, util::wrap_ptr<platf::hwdevice_t> &&device, int inject) : ctx { std::move(ctx) }, device { std::move(device) }, inject { inject } {}
session_t(session_t &&other) noexcept : ctx { std::move(other.ctx) }, device { std::move(other.device) }, replacements { std::move(other.replacements) } {}
session_t(session_t &&other) noexcept = default;
// Ensure objects are destroyed in the correct order
session_t &operator=(session_t &&other) {
device = std::move(other.device);
ctx = std::move(other.ctx);
replacements = std::move(other.replacements);
sps = std::move(other.sps);
vps = std::move(other.vps);
pps = std::move(other.pps);
inject = other.inject;
return *this;
}
@@ -335,13 +340,12 @@ public:
std::vector<packet_raw_t::replace_t> replacements;
struct nal_t {
util::buffer_t<std::uint8_t> old;
util::buffer_t<std::uint8_t> _new;
};
cbs::nal_t sps;
cbs::nal_t vps;
cbs::nal_t pps;
nal_t sps;
nal_t vps;
// inject sps/vps data into idr pictures
int inject;
};
struct sync_session_ctx_t {
@@ -696,6 +700,8 @@ int encode(int64_t frame_nr, session_t &session, frame_t::pointer frame, packet_
auto &ctx = session.ctx;
auto &sps = session.sps;
auto &vps = session.vps;
auto &pps = session.pps;
/* send the frame to the encoder */
auto ret = avcodec_send_frame(ctx.get(), frame);
@@ -717,8 +723,25 @@ int encode(int64_t frame_nr, session_t &session, frame_t::pointer frame, packet_
return ret;
}
if(sps._new.size() && !sps.old.size()) {
sps.old = cbs::read_sps_h264(packet.get());
if(session.inject) {
if(session.inject == 1) {
auto h264 = cbs::make_sps_h264(ctx.get(), packet.get());
sps = std::move(h264.sps);
}
else {
auto hevc = cbs::make_sps_hevc(ctx.get(), packet.get());
sps = std::move(hevc.sps);
vps = std::move(hevc.vps);
session.replacements.emplace_back(
std::string_view((char *)std::begin(vps.old), vps.old.size()),
std::string_view((char *)std::begin(vps._new), vps._new.size()));
}
session.inject = 0;
session.replacements.emplace_back(
std::string_view((char *)std::begin(sps.old), sps.old.size()),
@@ -833,6 +856,9 @@ std::optional<session_t> make_session(const encoder_t &encoder, const config_t &
sw_fmt = encoder.dynamic_pix_fmt;
}
// Used by cbs::make_sps_hevc
ctx->sw_pix_fmt = sw_fmt;
buffer_t hwdevice_ctx;
if(hardware) {
ctx->pix_fmt = encoder.dev_pix_fmt;
@@ -943,13 +969,10 @@ std::optional<session_t> make_session(const encoder_t &encoder, const config_t &
session_t session {
std::move(ctx),
std::move(device),
};
if(!video_format[encoder_t::VUI_PARAMETERS]) {
if(config.videoFormat == 0) {
session.sps._new = cbs::make_sps_h264(session.ctx.get());
}
}
// 0 ==> don't inject, 1 ==> inject for h264, 2 ==> inject for hevc
(1 - (int)video_format[encoder_t::VUI_PARAMETERS]) * (1 + config.videoFormat),
};
if(!video_format[encoder_t::NALU_PREFIX_5b]) {
auto nalu_prefix = config.videoFormat ? hevc_nalu : h264_nalu;
@@ -1012,7 +1035,7 @@ void encode_run(
next_frame += delay;
// When Moonlight request an IDR frame, send frames even if there is no new captured frame
if(frame_nr > (key_frame_nr + config.framerate) || images->peek()) {
if(frame_nr > key_frame_nr || images->peek()) {
if(auto img = images->pop(delay)) {
session->device->convert(*img);
}
@@ -1474,6 +1497,9 @@ bool validate_encoder(encoder_t &encoder) {
}
}
encoder.h264[encoder_t::VUI_PARAMETERS] = encoder.h264[encoder_t::VUI_PARAMETERS] && !config::sunshine.flags[config::flag::FORCE_VIDEO_HEADER_REPLACE];
encoder.hevc[encoder_t::VUI_PARAMETERS] = encoder.hevc[encoder_t::VUI_PARAMETERS] && !config::sunshine.flags[config::flag::FORCE_VIDEO_HEADER_REPLACE];
if(!encoder.h264[encoder_t::VUI_PARAMETERS]) {
BOOST_LOG(warning) << encoder.name << ": h264 missing sps->vui parameters"sv;
}