From 33e4ffd3fee894c172a827b5b02e7829c8421417 Mon Sep 17 00:00:00 2001 From: derrod Date: Wed, 15 Mar 2023 02:18:06 +0100 Subject: [PATCH 1/5] ffmpeg-mux: Properly support lossless codecs - Do not set sample rate (not required here, but can be 24/32 now) - Only set bit_rate for lossless codecs - Only set frame_size for codecs using a fixed one --- plugins/obs-ffmpeg/ffmpeg-mux/ffmpeg-mux.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/plugins/obs-ffmpeg/ffmpeg-mux/ffmpeg-mux.c b/plugins/obs-ffmpeg/ffmpeg-mux/ffmpeg-mux.c index d6ba35013871cb..c2550905efb9f2 100644 --- a/plugins/obs-ffmpeg/ffmpeg-mux/ffmpeg-mux.c +++ b/plugins/obs-ffmpeg/ffmpeg-mux/ffmpeg-mux.c @@ -526,7 +526,14 @@ static void create_audio_stream(struct ffmpeg_mux *ffm, int idx) const char *name = ffm->params.acodec; int channels; - const AVCodecDescriptor *codec = avcodec_descriptor_get_by_name(name); + const AVCodecDescriptor *codec_desc = + avcodec_descriptor_get_by_name(name); + if (!codec_desc) { + fprintf(stderr, "Couldn't find codec descriptor '%s'\n", name); + return; + } + + const AVCodec *codec = avcodec_find_encoder(codec_desc->id); if (!codec) { fprintf(stderr, "Couldn't find codec '%s'\n", name); return; @@ -547,14 +554,17 @@ static void create_audio_stream(struct ffmpeg_mux *ffm, int idx) context = avcodec_alloc_context3(NULL); context->codec_type = codec->type; context->codec_id = codec->id; - context->bit_rate = (int64_t)ffm->audio[idx].abitrate * 1000; + if (!(codec_desc->props & AV_CODEC_PROP_LOSSLESS)) + context->bit_rate = (int64_t)ffm->audio[idx].abitrate * 1000; + channels = ffm->audio[idx].channels; #if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 24, 100) context->channels = channels; #endif context->sample_rate = ffm->audio[idx].sample_rate; - context->frame_size = ffm->audio[idx].frame_size; - context->sample_fmt = AV_SAMPLE_FMT_S16; + if (!(codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)) + context->frame_size = ffm->audio[idx].frame_size; + context->time_base = stream->time_base; context->extradata = extradata; context->extradata_size = ffm->audio_header[idx].size; From 3ae98511d09f1ebaf5c9cc005a285efd1a26aeff Mon Sep 17 00:00:00 2001 From: derrod Date: Wed, 15 Mar 2023 02:27:59 +0100 Subject: [PATCH 2/5] obs-ffmpeg: Add PCM and ALAC encoders --- plugins/obs-ffmpeg/data/locale/en-US.ini | 3 + .../obs-ffmpeg/obs-ffmpeg-audio-encoders.c | 97 ++++++++++++++++++- plugins/obs-ffmpeg/obs-ffmpeg.c | 6 ++ 3 files changed, 103 insertions(+), 3 deletions(-) diff --git a/plugins/obs-ffmpeg/data/locale/en-US.ini b/plugins/obs-ffmpeg/data/locale/en-US.ini index 6e7d1590335d9b..a88fab8cd864de 100644 --- a/plugins/obs-ffmpeg/data/locale/en-US.ini +++ b/plugins/obs-ffmpeg/data/locale/en-US.ini @@ -1,6 +1,9 @@ FFmpegOutput="FFmpeg Output" FFmpegAAC="FFmpeg AAC" FFmpegOpus="FFmpeg Opus" +FFmpegALAC="FFmpeg ALAC (24-bit)" +FFmpegPCM16Bit="FFmpeg PCM (16-bit)" +FFmpegPCM24Bit="FFmpeg PCM (24-bit)" FFmpegOpts="FFmpeg Options" FFmpegOpts.ToolTip.Source="Allows you to set FFmpeg options. This only accepts options in the option=value format.\nMultiple options can be set by separating them with a space.\nExample: rtsp_transport=tcp rtsp_flags=prefer_tcp" Bitrate="Bitrate" diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-audio-encoders.c b/plugins/obs-ffmpeg/obs-ffmpeg-audio-encoders.c index 80920dcf95b958..bd6f8a149720e0 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-audio-encoders.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-audio-encoders.c @@ -95,6 +95,24 @@ static const char *opus_getname(void *unused) return obs_module_text("FFmpegOpus"); } +static const char *pcm_getname(void *unused) +{ + UNUSED_PARAMETER(unused); + return obs_module_text("FFmpegPCM16Bit"); +} + +static const char *pcm24_getname(void *unused) +{ + UNUSED_PARAMETER(unused); + return obs_module_text("FFmpegPCM24Bit"); +} + +static const char *alac_getname(void *unused) +{ + UNUSED_PARAMETER(unused); + return obs_module_text("FFmpegALAC"); +} + static void enc_destroy(void *data) { struct enc_encoder *enc = data; @@ -207,9 +225,17 @@ static void *enc_create(obs_data_t *settings, obs_encoder_t *encoder, goto fail; } - if (!bitrate) { + const AVCodecDescriptor *codec_desc = + avcodec_descriptor_get(enc->codec->id); + + if (!codec_desc) { + warn("Failed to get codec descriptor"); + goto fail; + } + + if (!bitrate && !(codec_desc->props & AV_CODEC_PROP_LOSSLESS)) { warn("Invalid bitrate specified"); - return NULL; + goto fail; } enc->context = avcodec_alloc_context3(enc->codec); @@ -218,7 +244,12 @@ static void *enc_create(obs_data_t *settings, obs_encoder_t *encoder, goto fail; } - enc->context->bit_rate = bitrate * 1000; + if (codec_desc->props & AV_CODEC_PROP_LOSSLESS) + // Set by encoder on init, not known at this time + enc->context->bit_rate = -1; + else + enc->context->bit_rate = bitrate * 1000; + const struct audio_output_info *aoi; aoi = audio_output_get_info(audio); @@ -300,6 +331,21 @@ static void *opus_create(obs_data_t *settings, obs_encoder_t *encoder) return enc_create(settings, encoder, "libopus", "opus"); } +static void *pcm_create(obs_data_t *settings, obs_encoder_t *encoder) +{ + return enc_create(settings, encoder, "pcm_s16le", NULL); +} + +static void *pcm24_create(obs_data_t *settings, obs_encoder_t *encoder) +{ + return enc_create(settings, encoder, "pcm_s24le", NULL); +} + +static void *alac_create(obs_data_t *settings, obs_encoder_t *encoder) +{ + return enc_create(settings, encoder, "alac", NULL); +} + static bool do_encode(struct enc_encoder *enc, struct encoder_packet *packet, bool *received_packet) { @@ -453,3 +499,48 @@ struct obs_encoder_info opus_encoder_info = { .get_extra_data = enc_extra_data, .get_audio_info = enc_audio_info, }; + +struct obs_encoder_info pcm_encoder_info = { + .id = "ffmpeg_pcm_s16le", + .type = OBS_ENCODER_AUDIO, + .codec = "pcm_s16le", + .get_name = pcm_getname, + .create = pcm_create, + .destroy = enc_destroy, + .encode = enc_encode, + .get_frame_size = enc_frame_size, + .get_defaults = enc_defaults, + .get_properties = enc_properties, + .get_extra_data = enc_extra_data, + .get_audio_info = enc_audio_info, +}; + +struct obs_encoder_info pcm24_encoder_info = { + .id = "ffmpeg_pcm_s24le", + .type = OBS_ENCODER_AUDIO, + .codec = "pcm_s24le", + .get_name = pcm24_getname, + .create = pcm24_create, + .destroy = enc_destroy, + .encode = enc_encode, + .get_frame_size = enc_frame_size, + .get_defaults = enc_defaults, + .get_properties = enc_properties, + .get_extra_data = enc_extra_data, + .get_audio_info = enc_audio_info, +}; + +struct obs_encoder_info alac_encoder_info = { + .id = "ffmpeg_alac", + .type = OBS_ENCODER_AUDIO, + .codec = "alac", + .get_name = alac_getname, + .create = alac_create, + .destroy = enc_destroy, + .encode = enc_encode, + .get_frame_size = enc_frame_size, + .get_defaults = enc_defaults, + .get_properties = enc_properties, + .get_extra_data = enc_extra_data, + .get_audio_info = enc_audio_info, +}; diff --git a/plugins/obs-ffmpeg/obs-ffmpeg.c b/plugins/obs-ffmpeg/obs-ffmpeg.c index d32146cf9f3eba..906f46df8b5237 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg.c @@ -36,6 +36,9 @@ extern struct obs_output_info replay_buffer; extern struct obs_output_info ffmpeg_hls_muxer; extern struct obs_encoder_info aac_encoder_info; extern struct obs_encoder_info opus_encoder_info; +extern struct obs_encoder_info pcm_encoder_info; +extern struct obs_encoder_info pcm24_encoder_info; +extern struct obs_encoder_info alac_encoder_info; extern struct obs_encoder_info h264_nvenc_encoder_info; #ifdef ENABLE_HEVC extern struct obs_encoder_info hevc_nvenc_encoder_info; @@ -387,6 +390,9 @@ bool obs_module_load(void) register_encoder_if_available(&svt_av1_encoder_info, "libsvtav1"); register_encoder_if_available(&aom_av1_encoder_info, "libaom-av1"); obs_register_encoder(&opus_encoder_info); + obs_register_encoder(&pcm_encoder_info); + obs_register_encoder(&pcm24_encoder_info); + obs_register_encoder(&alac_encoder_info); #ifndef __APPLE__ bool h264 = false; bool hevc = false; From cf837fa38ac7c31092a4757e7711ad39cf822cd1 Mon Sep 17 00:00:00 2001 From: derrod Date: Wed, 15 Mar 2023 02:30:32 +0100 Subject: [PATCH 3/5] obs-ffmpeg: Add FLAC encoder --- plugins/obs-ffmpeg/data/locale/en-US.ini | 1 + .../obs-ffmpeg/obs-ffmpeg-audio-encoders.c | 26 +++++++++++++++++++ plugins/obs-ffmpeg/obs-ffmpeg.c | 2 ++ 3 files changed, 29 insertions(+) diff --git a/plugins/obs-ffmpeg/data/locale/en-US.ini b/plugins/obs-ffmpeg/data/locale/en-US.ini index a88fab8cd864de..e75bfcd669bce7 100644 --- a/plugins/obs-ffmpeg/data/locale/en-US.ini +++ b/plugins/obs-ffmpeg/data/locale/en-US.ini @@ -2,6 +2,7 @@ FFmpegOutput="FFmpeg Output" FFmpegAAC="FFmpeg AAC" FFmpegOpus="FFmpeg Opus" FFmpegALAC="FFmpeg ALAC (24-bit)" +FFmpegFLAC="FFmpeg FLAC (16-bit)" FFmpegPCM16Bit="FFmpeg PCM (16-bit)" FFmpegPCM24Bit="FFmpeg PCM (24-bit)" FFmpegOpts="FFmpeg Options" diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-audio-encoders.c b/plugins/obs-ffmpeg/obs-ffmpeg-audio-encoders.c index bd6f8a149720e0..56601330fb0e0d 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-audio-encoders.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-audio-encoders.c @@ -113,6 +113,12 @@ static const char *alac_getname(void *unused) return obs_module_text("FFmpegALAC"); } +static const char *flac_getname(void *unused) +{ + UNUSED_PARAMETER(unused); + return obs_module_text("FFmpegFLAC"); +} + static void enc_destroy(void *data) { struct enc_encoder *enc = data; @@ -346,6 +352,11 @@ static void *alac_create(obs_data_t *settings, obs_encoder_t *encoder) return enc_create(settings, encoder, "alac", NULL); } +static void *flac_create(obs_data_t *settings, obs_encoder_t *encoder) +{ + return enc_create(settings, encoder, "flac", NULL); +} + static bool do_encode(struct enc_encoder *enc, struct encoder_packet *packet, bool *received_packet) { @@ -544,3 +555,18 @@ struct obs_encoder_info alac_encoder_info = { .get_extra_data = enc_extra_data, .get_audio_info = enc_audio_info, }; + +struct obs_encoder_info flac_encoder_info = { + .id = "ffmpeg_flac", + .type = OBS_ENCODER_AUDIO, + .codec = "flac", + .get_name = flac_getname, + .create = flac_create, + .destroy = enc_destroy, + .encode = enc_encode, + .get_frame_size = enc_frame_size, + .get_defaults = enc_defaults, + .get_properties = enc_properties, + .get_extra_data = enc_extra_data, + .get_audio_info = enc_audio_info, +}; diff --git a/plugins/obs-ffmpeg/obs-ffmpeg.c b/plugins/obs-ffmpeg/obs-ffmpeg.c index 906f46df8b5237..07035147cde18d 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg.c @@ -39,6 +39,7 @@ extern struct obs_encoder_info opus_encoder_info; extern struct obs_encoder_info pcm_encoder_info; extern struct obs_encoder_info pcm24_encoder_info; extern struct obs_encoder_info alac_encoder_info; +extern struct obs_encoder_info flac_encoder_info; extern struct obs_encoder_info h264_nvenc_encoder_info; #ifdef ENABLE_HEVC extern struct obs_encoder_info hevc_nvenc_encoder_info; @@ -393,6 +394,7 @@ bool obs_module_load(void) obs_register_encoder(&pcm_encoder_info); obs_register_encoder(&pcm24_encoder_info); obs_register_encoder(&alac_encoder_info); + obs_register_encoder(&flac_encoder_info); #ifndef __APPLE__ bool h264 = false; bool hevc = false; From dcee4fc61a54504d4b29e8870be0ad97ad750b12 Mon Sep 17 00:00:00 2001 From: derrod Date: Wed, 15 Mar 2023 02:31:38 +0100 Subject: [PATCH 4/5] libobs: Allow encoders to request unclamped audio mix This is useful for formats such as 32-bit float PCM which providers greater flexibility in editing by retaining information that would otherwise be clipped. --- libobs/media-io/audio-io.c | 9 ++++++++- libobs/media-io/audio-io.h | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/libobs/media-io/audio-io.c b/libobs/media-io/audio-io.c index 5c3f1acf7be615..6786990e4c2f05 100644 --- a/libobs/media-io/audio-io.c +++ b/libobs/media-io/audio-io.c @@ -59,6 +59,7 @@ static inline void audio_input_free(struct audio_input *input) struct audio_mix { DARRAY(struct audio_input) inputs; float buffer[MAX_AUDIO_CHANNELS][AUDIO_OUTPUT_FRAMES]; + float buffer_unclamped[MAX_AUDIO_CHANNELS][AUDIO_OUTPUT_FRAMES]; }; struct audio_output { @@ -116,8 +117,12 @@ static inline void do_audio_output(struct audio_output *audio, size_t mix_idx, for (size_t i = mix->inputs.num; i > 0; i--) { struct audio_input *input = mix->inputs.array + (i - 1); + float(*buf)[AUDIO_OUTPUT_FRAMES] = + input->conversion.allow_clipping ? mix->buffer_unclamped + : mix->buffer; for (size_t i = 0; i < audio->planes; i++) - data.data[i] = (uint8_t *)mix->buffer[i]; + data.data[i] = (uint8_t *)buf[i]; + data.frames = frames; data.timestamp = timestamp; @@ -142,6 +147,8 @@ static inline void clamp_audio_output(struct audio_output *audio, size_t bytes) for (size_t plane = 0; plane < audio->planes; plane++) { float *mix_data = mix->buffer[plane]; float *mix_end = &mix_data[float_size]; + /* Unclamped mix is copied directly. */ + memcpy(mix->buffer_unclamped[plane], mix_data, bytes); while (mix_data < mix_end) { float val = *mix_data; diff --git a/libobs/media-io/audio-io.h b/libobs/media-io/audio-io.h index ed0387605fb193..77a1fad81662e2 100644 --- a/libobs/media-io/audio-io.h +++ b/libobs/media-io/audio-io.h @@ -105,6 +105,7 @@ struct audio_convert_info { uint32_t samples_per_sec; enum audio_format format; enum speaker_layout speakers; + bool allow_clipping; }; static inline uint32_t get_audio_channels(enum speaker_layout speakers) From 1a51aad5829218bf8ad5d470d62f7511dac5c287 Mon Sep 17 00:00:00 2001 From: derrod Date: Wed, 15 Mar 2023 02:34:35 +0100 Subject: [PATCH 5/5] obs-ffmpeg: Add unclamped 32-bit floating point PCM encoder --- plugins/obs-ffmpeg/data/locale/en-US.ini | 1 + .../obs-ffmpeg/obs-ffmpeg-audio-encoders.c | 32 +++++++++++++++++++ plugins/obs-ffmpeg/obs-ffmpeg.c | 2 ++ 3 files changed, 35 insertions(+) diff --git a/plugins/obs-ffmpeg/data/locale/en-US.ini b/plugins/obs-ffmpeg/data/locale/en-US.ini index e75bfcd669bce7..64371eff7f0380 100644 --- a/plugins/obs-ffmpeg/data/locale/en-US.ini +++ b/plugins/obs-ffmpeg/data/locale/en-US.ini @@ -5,6 +5,7 @@ FFmpegALAC="FFmpeg ALAC (24-bit)" FFmpegFLAC="FFmpeg FLAC (16-bit)" FFmpegPCM16Bit="FFmpeg PCM (16-bit)" FFmpegPCM24Bit="FFmpeg PCM (24-bit)" +FFmpegPCM32BitFloat="FFmpeg PCM (32-bit float)" FFmpegOpts="FFmpeg Options" FFmpegOpts.ToolTip.Source="Allows you to set FFmpeg options. This only accepts options in the option=value format.\nMultiple options can be set by separating them with a space.\nExample: rtsp_transport=tcp rtsp_flags=prefer_tcp" Bitrate="Bitrate" diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-audio-encoders.c b/plugins/obs-ffmpeg/obs-ffmpeg-audio-encoders.c index 56601330fb0e0d..24b686a95d1c34 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-audio-encoders.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-audio-encoders.c @@ -107,6 +107,12 @@ static const char *pcm24_getname(void *unused) return obs_module_text("FFmpegPCM24Bit"); } +static const char *pcm32_getname(void *unused) +{ + UNUSED_PARAMETER(unused); + return obs_module_text("FFmpegPCM32BitFloat"); +} + static const char *alac_getname(void *unused) { UNUSED_PARAMETER(unused); @@ -347,6 +353,11 @@ static void *pcm24_create(obs_data_t *settings, obs_encoder_t *encoder) return enc_create(settings, encoder, "pcm_s24le", NULL); } +static void *pcm32_create(obs_data_t *settings, obs_encoder_t *encoder) +{ + return enc_create(settings, encoder, "pcm_f32le", NULL); +} + static void *alac_create(obs_data_t *settings, obs_encoder_t *encoder) { return enc_create(settings, encoder, "alac", NULL); @@ -475,6 +486,12 @@ static void enc_audio_info(void *data, struct audio_convert_info *info) info->speakers = SPEAKERS_UNKNOWN; } +static void enc_audio_info_float(void *data, struct audio_convert_info *info) +{ + enc_audio_info(data, info); + info->allow_clipping = true; +} + static size_t enc_frame_size(void *data) { struct enc_encoder *enc = data; @@ -541,6 +558,21 @@ struct obs_encoder_info pcm24_encoder_info = { .get_audio_info = enc_audio_info, }; +struct obs_encoder_info pcm32_encoder_info = { + .id = "ffmpeg_pcm_f32le", + .type = OBS_ENCODER_AUDIO, + .codec = "pcm_f32le", + .get_name = pcm32_getname, + .create = pcm32_create, + .destroy = enc_destroy, + .encode = enc_encode, + .get_frame_size = enc_frame_size, + .get_defaults = enc_defaults, + .get_properties = enc_properties, + .get_extra_data = enc_extra_data, + .get_audio_info = enc_audio_info_float, +}; + struct obs_encoder_info alac_encoder_info = { .id = "ffmpeg_alac", .type = OBS_ENCODER_AUDIO, diff --git a/plugins/obs-ffmpeg/obs-ffmpeg.c b/plugins/obs-ffmpeg/obs-ffmpeg.c index 07035147cde18d..b64d648f21cd20 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg.c @@ -38,6 +38,7 @@ extern struct obs_encoder_info aac_encoder_info; extern struct obs_encoder_info opus_encoder_info; extern struct obs_encoder_info pcm_encoder_info; extern struct obs_encoder_info pcm24_encoder_info; +extern struct obs_encoder_info pcm32_encoder_info; extern struct obs_encoder_info alac_encoder_info; extern struct obs_encoder_info flac_encoder_info; extern struct obs_encoder_info h264_nvenc_encoder_info; @@ -393,6 +394,7 @@ bool obs_module_load(void) obs_register_encoder(&opus_encoder_info); obs_register_encoder(&pcm_encoder_info); obs_register_encoder(&pcm24_encoder_info); + obs_register_encoder(&pcm32_encoder_info); obs_register_encoder(&alac_encoder_info); obs_register_encoder(&flac_encoder_info); #ifndef __APPLE__