Add medium and high quality, better livestreaming options, fix global header (especially for streaming)

This commit is contained in:
dec05eba 2022-10-04 00:09:53 +02:00
parent 270a8636ae
commit c354d88583

View File

@ -97,6 +97,8 @@ struct WindowPixmap {
}; };
enum class VideoQuality { enum class VideoQuality {
MEDIUM,
HIGH,
VERY_HIGH, VERY_HIGH,
ULTRA ULTRA
}; };
@ -587,9 +589,8 @@ static AVCodecContext* create_audio_codec_context(AVFormatContext *av_format_con
codec_context->framerate.num = fps; codec_context->framerate.num = fps;
codec_context->framerate.den = 1; codec_context->framerate.den = 1;
// Some formats want stream headers to be seperate
if (av_format_context->oformat->flags & AVFMT_GLOBALHEADER)
av_format_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; av_format_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
return codec_context; return codec_context;
} }
@ -597,7 +598,7 @@ static AVCodecContext* create_audio_codec_context(AVFormatContext *av_format_con
static AVCodecContext *create_video_codec_context(AVFormatContext *av_format_context, static AVCodecContext *create_video_codec_context(AVFormatContext *av_format_context,
VideoQuality video_quality, VideoQuality video_quality,
int record_width, int record_height, int record_width, int record_height,
int fps, VideoCodec video_codec) { int fps, VideoCodec video_codec, bool is_livestream) {
const AVCodec *codec = avcodec_find_encoder_by_name(video_codec == VideoCodec::H265 ? "hevc_nvenc" : "h264_nvenc"); const AVCodec *codec = avcodec_find_encoder_by_name(video_codec == VideoCodec::H265 ? "hevc_nvenc" : "h264_nvenc");
if (!codec) { if (!codec) {
codec = avcodec_find_encoder_by_name(video_codec == VideoCodec::H265 ? "nvenc_hevc" : "nvenc_h264"); codec = avcodec_find_encoder_by_name(video_codec == VideoCodec::H265 ? "nvenc_hevc" : "nvenc_h264");
@ -617,7 +618,6 @@ static AVCodecContext *create_video_codec_context(AVFormatContext *av_format_con
codec_context->codec_id = codec->id; codec_context->codec_id = codec->id;
codec_context->width = record_width & ~1; codec_context->width = record_width & ~1;
codec_context->height = record_height & ~1; codec_context->height = record_height & ~1;
codec_context->bit_rate = 12500000 + (codec_context->width * codec_context->height) / 2;
// Timebase: This is the fundamental unit of time (in seconds) in terms // Timebase: This is the fundamental unit of time (in seconds) in terms
// of which frame timestamps are represented. For fixed-fps content, // of which frame timestamps are represented. For fixed-fps content,
// timebase should be 1/framerate and timestamp increments should be // timebase should be 1/framerate and timestamp increments should be
@ -629,16 +629,23 @@ static AVCodecContext *create_video_codec_context(AVFormatContext *av_format_con
codec_context->sample_aspect_ratio.num = 0; codec_context->sample_aspect_ratio.num = 0;
codec_context->sample_aspect_ratio.den = 0; codec_context->sample_aspect_ratio.den = 0;
// High values reeduce file size but increases time it takes to seek // High values reeduce file size but increases time it takes to seek
if(is_livestream) {
codec_context->flags |= (AV_CODEC_FLAG_CLOSED_GOP | AV_CODEC_FLAG_LOW_DELAY);
codec_context->flags2 |= AV_CODEC_FLAG2_FAST;
codec_context->gop_size = fps * 2; codec_context->gop_size = fps * 2;
//codec_context->keyint_min = fps * 20; //codec_context->gop_size = std::numeric_limits<int>::max();
//codec_context->keyint_min = std::numeric_limits<int>::max();
} else {
codec_context->gop_size = fps * 2;
}
codec_context->max_b_frames = 0; codec_context->max_b_frames = 0;
codec_context->pix_fmt = AV_PIX_FMT_CUDA; codec_context->pix_fmt = AV_PIX_FMT_CUDA;
codec_context->color_range = AVCOL_RANGE_JPEG; codec_context->color_range = AVCOL_RANGE_JPEG;
if(video_codec == VideoCodec::H265) if(video_codec == VideoCodec::H265)
codec_context->codec_tag = MKTAG('h', 'v', 'c', '1'); codec_context->codec_tag = MKTAG('h', 'v', 'c', '1');
switch(video_quality) { switch(video_quality) {
case VideoQuality::VERY_HIGH: case VideoQuality::MEDIUM:
codec_context->bit_rate = 10000000 + (codec_context->width * codec_context->height) / 2; codec_context->bit_rate = 5000000 + (codec_context->width * codec_context->height) / 2;
/* /*
if(use_hevc) { if(use_hevc) {
codec_context->qmin = 20; codec_context->qmin = 20;
@ -653,20 +660,14 @@ static AVCodecContext *create_video_codec_context(AVFormatContext *av_format_con
//codec_context->profile = FF_PROFILE_H264_HIGH; //codec_context->profile = FF_PROFILE_H264_HIGH;
//av_opt_set(codec_context->priv_data, "preset", "p4", 0); //av_opt_set(codec_context->priv_data, "preset", "p4", 0);
break; break;
case VideoQuality::HIGH:
codec_context->bit_rate = 8000000 + (codec_context->width * codec_context->height) / 2;
break;
case VideoQuality::VERY_HIGH:
codec_context->bit_rate = 10000000 + (codec_context->width * codec_context->height) / 2;
break;
case VideoQuality::ULTRA: case VideoQuality::ULTRA:
/* codec_context->bit_rate = 12500000 + (codec_context->width * codec_context->height) / 2;
if(use_hevc) {
codec_context->qmin = 17;
codec_context->qmax = 30;
} else {
codec_context->qmin = 5;
codec_context->qmax = 15;
}
*/
//av_opt_set(codec_context->priv_data, "preset", "slow", 0);
//av_opt_set(codec_context->priv_data, "profile", "high", 0);
//codec_context->profile = FF_PROFILE_H264_HIGH;
//av_opt_set(codec_context->priv_data, "preset", "p5", 0);
break; break;
} }
//codec_context->profile = FF_PROFILE_H264_MAIN; //codec_context->profile = FF_PROFILE_H264_MAIN;
@ -692,9 +693,12 @@ static AVCodecContext *create_video_codec_context(AVFormatContext *av_format_con
codec_context->bit_rate = 0; codec_context->bit_rate = 0;
#endif #endif
// Some formats want stream headers to be seperate codec_context->rc_max_rate = codec_context->bit_rate;
if (av_format_context->oformat->flags & AVFMT_GLOBALHEADER) codec_context->rc_min_rate = codec_context->bit_rate;
codec_context->rc_buffer_size = codec_context->bit_rate / 10;
av_format_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; av_format_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
return codec_context; return codec_context;
} }
@ -743,7 +747,7 @@ static AVBufferRef* dummy_hw_frame_init(size_t size) {
static void open_video(AVCodecContext *codec_context, static void open_video(AVCodecContext *codec_context,
WindowPixmap &window_pixmap, AVBufferRef **device_ctx, WindowPixmap &window_pixmap, AVBufferRef **device_ctx,
CUgraphicsResource *cuda_graphics_resource, CUcontext cuda_context, bool use_nvfbc, VideoQuality video_quality) { CUgraphicsResource *cuda_graphics_resource, CUcontext cuda_context, bool use_nvfbc, VideoQuality video_quality, bool is_livestream) {
int ret; int ret;
*device_ctx = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_CUDA); *device_ctx = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_CUDA);
@ -791,6 +795,14 @@ static void open_video(AVCodecContext *codec_context,
AVDictionary *options = nullptr; AVDictionary *options = nullptr;
switch(video_quality) { switch(video_quality) {
case VideoQuality::MEDIUM:
av_dict_set_int(&options, "qp", 36, 0);
//av_dict_set(&options, "preset", "hq", 0);
break;
case VideoQuality::HIGH:
av_dict_set_int(&options, "qp", 31, 0);
//av_dict_set(&options, "preset", "hq", 0);
break;
case VideoQuality::VERY_HIGH: case VideoQuality::VERY_HIGH:
av_dict_set_int(&options, "qp", 28, 0); av_dict_set_int(&options, "qp", 28, 0);
//av_dict_set(&options, "preset", "hq", 0); //av_dict_set(&options, "preset", "hq", 0);
@ -801,6 +813,13 @@ static void open_video(AVCodecContext *codec_context,
break; break;
} }
if(is_livestream) {
av_dict_set_int(&options, "zerolatency", 1, 0);
av_dict_set(&options, "preset", "llhq", 0);
}
av_opt_set(&options, "rc", "vbr", 0);
ret = avcodec_open2(codec_context, codec_context->codec, &options); ret = avcodec_open2(codec_context, codec_context->codec, &options);
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Error: Could not open video codec: %s\n", fprintf(stderr, "Error: Could not open video codec: %s\n",
@ -844,7 +863,7 @@ static void usage() {
fprintf(stderr, " -c Container format for output file, for example mp4, or flv.\n"); fprintf(stderr, " -c Container format for output file, for example mp4, or flv.\n");
fprintf(stderr, " -f Framerate to record at.\n"); fprintf(stderr, " -f Framerate to record at.\n");
fprintf(stderr, " -a Audio device to record from (pulse audio device). Can be specified multiple times. Each time this is specified a new audio track is added for the specified audio device. A name can be given to the audio input device by prefixing the audio input with <name>/, for example \"dummy/alsa_output.pci-0000_00_1b.0.analog-stereo.monitor\". Optional, no audio track is added by default.\n"); fprintf(stderr, " -a Audio device to record from (pulse audio device). Can be specified multiple times. Each time this is specified a new audio track is added for the specified audio device. A name can be given to the audio input device by prefixing the audio input with <name>/, for example \"dummy/alsa_output.pci-0000_00_1b.0.analog-stereo.monitor\". Optional, no audio track is added by default.\n");
fprintf(stderr, " -q Video quality. Should be either 'very_high' or 'ultra'. 'very_high' is the recommended, especially when live streaming or when you have a slower harddrive. Optional, set to 'very_high' be default.\n"); fprintf(stderr, " -q Video quality. Should be either 'medium', 'high', 'very_high' or 'ultra'. 'high' is the recommended option when live streaming or when you have a slower harddrive. Optional, set to 'very_high' be default.\n");
fprintf(stderr, " -r Replay buffer size in seconds. If this is set, then only the last seconds as set by this option will be stored" fprintf(stderr, " -r Replay buffer size in seconds. If this is set, then only the last seconds as set by this option will be stored"
" and the video will only be saved when the gpu-screen-recorder is closed. This feature is similar to Nvidia's instant replay feature." " and the video will only be saved when the gpu-screen-recorder is closed. This feature is similar to Nvidia's instant replay feature."
" This option has be between 5 and 1200. Note that the replay buffer size will not always be precise, because of keyframes. Optional, disabled by default.\n"); " This option has be between 5 and 1200. Note that the replay buffer size will not always be precise, because of keyframes. Optional, disabled by default.\n");
@ -993,7 +1012,6 @@ static void save_replay_async(AVCodecContext *video_codec_context, int video_str
avformat_alloc_output_context2(&av_format_context, nullptr, container_format.c_str(), nullptr); avformat_alloc_output_context2(&av_format_context, nullptr, container_format.c_str(), nullptr);
av_format_context->flags |= AVFMT_FLAG_GENPTS; av_format_context->flags |= AVFMT_FLAG_GENPTS;
if (av_format_context->oformat->flags & AVFMT_GLOBALHEADER)
av_format_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; av_format_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
AVStream *video_stream = create_stream(av_format_context, video_codec_context); AVStream *video_stream = create_stream(av_format_context, video_codec_context);
@ -1068,6 +1086,17 @@ static AudioInput parse_audio_input_arg(const char *str) {
return audio_input; return audio_input;
} }
// TODO: Does this match all livestreaming cases?
static bool is_livestream_path(const char *str) {
const int len = strlen(str);
if((len >= 7 && memcmp(str, "http://", 7) == 0) || (len >= 8 && memcmp(str, "https://", 8) == 0))
return true;
else if((len >= 7 && memcmp(str, "rtmp://", 7) == 0) || (len >= 8 && memcmp(str, "rtmps://", 8) == 0))
return true;
else
return false;
}
int main(int argc, char **argv) { int main(int argc, char **argv) {
signal(SIGTERM, term_handler); signal(SIGTERM, term_handler);
signal(SIGINT, int_handler); signal(SIGINT, int_handler);
@ -1181,14 +1210,17 @@ int main(int argc, char **argv) {
if(!quality_str) if(!quality_str)
quality_str = "very_high"; quality_str = "very_high";
// medium and high exist for backwards compatibility
VideoQuality quality; VideoQuality quality;
if(strcmp(quality_str, "medium") == 0 || strcmp(quality_str, "very_high") == 0) { if(strcmp(quality_str, "medium") == 0) {
quality = VideoQuality::MEDIUM;
} else if(strcmp(quality_str, "high") == 0) {
quality = VideoQuality::HIGH;
} else if(strcmp(quality_str, "very_high") == 0) {
quality = VideoQuality::VERY_HIGH; quality = VideoQuality::VERY_HIGH;
} else if(strcmp(quality_str, "high") == 0 || strcmp(quality_str, "ultra") == 0) { } else if(strcmp(quality_str, "ultra") == 0) {
quality = VideoQuality::ULTRA; quality = VideoQuality::ULTRA;
} else { } else {
fprintf(stderr, "Error: -q should either be either 'very_high' or 'ultra', got: '%s'\n", quality_str); fprintf(stderr, "Error: -q should either be either 'medium', 'high', 'very_high' or 'ultra', got: '%s'\n", quality_str);
usage(); usage();
} }
@ -1399,6 +1431,8 @@ int main(int argc, char **argv) {
av_format_context->flags |= AVFMT_FLAG_GENPTS; av_format_context->flags |= AVFMT_FLAG_GENPTS;
const AVOutputFormat *output_format = av_format_context->oformat; const AVOutputFormat *output_format = av_format_context->oformat;
const bool is_livestream = is_livestream_path(filename);
//bool use_hevc = strcmp(window_str, "screen") == 0 || strcmp(window_str, "screen-direct") == 0; //bool use_hevc = strcmp(window_str, "screen") == 0 || strcmp(window_str, "screen-direct") == 0;
if(video_codec != VideoCodec::H264 && strcmp(container_format, "flv") == 0) { if(video_codec != VideoCodec::H264 && strcmp(container_format, "flv") == 0) {
video_codec = VideoCodec::H264; video_codec = VideoCodec::H264;
@ -1408,13 +1442,13 @@ int main(int argc, char **argv) {
AVStream *video_stream = nullptr; AVStream *video_stream = nullptr;
std::vector<AudioTrack> audio_tracks; std::vector<AudioTrack> audio_tracks;
AVCodecContext *video_codec_context = create_video_codec_context(av_format_context, quality, record_width, record_height, fps, video_codec); AVCodecContext *video_codec_context = create_video_codec_context(av_format_context, quality, record_width, record_height, fps, video_codec, is_livestream);
if(replay_buffer_size_secs == -1) if(replay_buffer_size_secs == -1)
video_stream = create_stream(av_format_context, video_codec_context); video_stream = create_stream(av_format_context, video_codec_context);
AVBufferRef *device_ctx; AVBufferRef *device_ctx;
CUgraphicsResource cuda_graphics_resource; CUgraphicsResource cuda_graphics_resource;
open_video(video_codec_context, window_pixmap, &device_ctx, &cuda_graphics_resource, cu_ctx, !src_window_id, quality); open_video(video_codec_context, window_pixmap, &device_ctx, &cuda_graphics_resource, cu_ctx, !src_window_id, quality, is_livestream);
if(video_stream) if(video_stream)
avcodec_parameters_from_context(video_stream->codecpar, video_codec_context); avcodec_parameters_from_context(video_stream->codecpar, video_codec_context);