Allow replacement of hevc headers
This commit is contained in:
108
sunshine/cbs.cpp
108
sunshine/cbs.cpp
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user