Просмотр исходного кода

Merge pull request #8289 from derrod/more-audio-codecs

libobs,obs-ffmpeg: Add option for recording lossless audio
Jim 2 лет назад
Родитель
Сommit
7a30d535aa

+ 8 - 1
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;

+ 1 - 0
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)

+ 5 - 0
plugins/obs-ffmpeg/data/locale/en-US.ini

@@ -1,6 +1,11 @@
 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)"
+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"

+ 14 - 4
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;

+ 152 - 3
plugins/obs-ffmpeg/obs-ffmpeg-audio-encoders.c

@@ -95,6 +95,36 @@ 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 *pcm32_getname(void *unused)
+{
+	UNUSED_PARAMETER(unused);
+	return obs_module_text("FFmpegPCM32BitFloat");
+}
+
+static const char *alac_getname(void *unused)
+{
+	UNUSED_PARAMETER(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;
@@ -207,9 +237,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 +256,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 +343,31 @@ 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 *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);
+}
+
+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)
 {
@@ -418,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;
@@ -453,3 +527,78 @@ 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 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,
+	.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,
+};
+
+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,
+};

+ 10 - 0
plugins/obs-ffmpeg/obs-ffmpeg.c

@@ -36,6 +36,11 @@ 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 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;
 #ifdef ENABLE_HEVC
 extern struct obs_encoder_info hevc_nvenc_encoder_info;
@@ -387,6 +392,11 @@ 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(&pcm32_encoder_info);
+	obs_register_encoder(&alac_encoder_info);
+	obs_register_encoder(&flac_encoder_info);
 #ifndef __APPLE__
 	bool h264 = false;
 	bool hevc = false;