From a9bcc81f821d7c66f623779fa5147e728eb3c388 Mon Sep 17 00:00:00 2001
From: Daniel Baumann <daniel.baumann@progress-linux.org>
Date: Sat, 4 May 2024 03:24:41 +0200
Subject: Adding upstream version 3.3.0+dfsg1.

Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
---
 libfreerdp/codec/dsp_ffmpeg.c | 846 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 846 insertions(+)
 create mode 100644 libfreerdp/codec/dsp_ffmpeg.c

(limited to 'libfreerdp/codec/dsp_ffmpeg.c')

diff --git a/libfreerdp/codec/dsp_ffmpeg.c b/libfreerdp/codec/dsp_ffmpeg.c
new file mode 100644
index 0000000..ff12f07
--- /dev/null
+++ b/libfreerdp/codec/dsp_ffmpeg.c
@@ -0,0 +1,846 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Digital Sound Processing - FFMPEG backend
+ *
+ * Copyright 2018 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2018 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <freerdp/log.h>
+
+#include <libavcodec/avcodec.h>
+#include <libavutil/avutil.h>
+#include <libavutil/opt.h>
+#if defined(SWRESAMPLE_FOUND)
+#include <libswresample/swresample.h>
+#elif defined(AVRESAMPLE_FOUND)
+#include <libavresample/avresample.h>
+#else
+#error "libswresample or libavresample required"
+#endif
+
+#include "dsp.h"
+#include "dsp_ffmpeg.h"
+
+#define TAG FREERDP_TAG("dsp.ffmpeg")
+
+struct S_FREERDP_DSP_CONTEXT
+{
+	AUDIO_FORMAT format;
+
+	BOOL isOpen;
+	BOOL encoder;
+
+	UINT32 bufferedSamples;
+
+	enum AVCodecID id;
+	AVCodec* codec;
+	AVCodecContext* context;
+	AVFrame* frame;
+	AVFrame* resampled;
+	AVFrame* buffered;
+	AVPacket* packet;
+#if defined(SWRESAMPLE_FOUND)
+	SwrContext* rcontext;
+#else
+	AVAudioResampleContext* rcontext;
+#endif
+	wStream* channelmix;
+};
+
+static BOOL ffmpeg_codec_is_filtered(enum AVCodecID id, BOOL encoder)
+{
+	switch (id)
+	{
+#if !defined(WITH_DSP_EXPERIMENTAL)
+
+		case AV_CODEC_ID_ADPCM_IMA_OKI:
+		case AV_CODEC_ID_MP3:
+		case AV_CODEC_ID_ADPCM_MS:
+		case AV_CODEC_ID_G723_1:
+			return TRUE;
+#endif
+
+		case AV_CODEC_ID_NONE:
+			return TRUE;
+
+		case AV_CODEC_ID_GSM_MS:
+		case AV_CODEC_ID_AAC:
+		case AV_CODEC_ID_AAC_LATM:
+#if !defined(WITH_DSP_EXPERIMENTAL)
+			if (encoder)
+				return TRUE;
+#endif
+			return FALSE;
+
+		default:
+			return FALSE;
+	}
+}
+
+static enum AVCodecID ffmpeg_get_avcodec(const AUDIO_FORMAT* format)
+{
+	if (!format)
+		return AV_CODEC_ID_NONE;
+
+	switch (format->wFormatTag)
+	{
+		case WAVE_FORMAT_UNKNOWN:
+			return AV_CODEC_ID_NONE;
+
+		case WAVE_FORMAT_PCM:
+			switch (format->wBitsPerSample)
+			{
+				case 16:
+					return AV_CODEC_ID_PCM_U16LE;
+
+				case 8:
+					return AV_CODEC_ID_PCM_U8;
+
+				default:
+					return AV_CODEC_ID_NONE;
+			}
+
+		case WAVE_FORMAT_DVI_ADPCM:
+			return AV_CODEC_ID_ADPCM_IMA_OKI;
+
+		case WAVE_FORMAT_ADPCM:
+			return AV_CODEC_ID_ADPCM_MS;
+
+		case WAVE_FORMAT_ALAW:
+			return AV_CODEC_ID_PCM_ALAW;
+
+		case WAVE_FORMAT_MULAW:
+			return AV_CODEC_ID_PCM_MULAW;
+
+		case WAVE_FORMAT_GSM610:
+			return AV_CODEC_ID_GSM_MS;
+
+		case WAVE_FORMAT_MSG723:
+			return AV_CODEC_ID_G723_1;
+
+		case WAVE_FORMAT_AAC_MS:
+			return AV_CODEC_ID_AAC;
+
+		case WAVE_FORMAT_OPUS:
+			return AV_CODEC_ID_OPUS;
+
+		default:
+			return AV_CODEC_ID_NONE;
+	}
+}
+
+static int ffmpeg_sample_format(const AUDIO_FORMAT* format)
+{
+	switch (format->wFormatTag)
+	{
+		case WAVE_FORMAT_PCM:
+			switch (format->wBitsPerSample)
+			{
+				case 8:
+					return AV_SAMPLE_FMT_U8;
+
+				case 16:
+					return AV_SAMPLE_FMT_S16;
+
+				default:
+					return FALSE;
+			}
+
+		case WAVE_FORMAT_DVI_ADPCM:
+		case WAVE_FORMAT_ADPCM:
+			return AV_SAMPLE_FMT_S16P;
+
+		case WAVE_FORMAT_MPEGLAYER3:
+		case WAVE_FORMAT_AAC_MS:
+			return AV_SAMPLE_FMT_FLTP;
+
+		case WAVE_FORMAT_OPUS:
+			return AV_SAMPLE_FMT_S16;
+
+		case WAVE_FORMAT_MSG723:
+		case WAVE_FORMAT_GSM610:
+			return AV_SAMPLE_FMT_S16P;
+
+		case WAVE_FORMAT_ALAW:
+			return AV_SAMPLE_FMT_S16;
+
+		default:
+			return FALSE;
+	}
+}
+
+static void ffmpeg_close_context(FREERDP_DSP_CONTEXT* context)
+{
+	if (context)
+	{
+		if (context->context)
+			avcodec_free_context(&context->context);
+
+		if (context->frame)
+			av_frame_free(&context->frame);
+
+		if (context->resampled)
+			av_frame_free(&context->resampled);
+
+		if (context->buffered)
+			av_frame_free(&context->buffered);
+
+		if (context->packet)
+			av_packet_free(&context->packet);
+
+		if (context->rcontext)
+		{
+#if defined(SWRESAMPLE_FOUND)
+			swr_free(&context->rcontext);
+#else
+			avresample_free(&context->rcontext);
+#endif
+		}
+
+		context->id = AV_CODEC_ID_NONE;
+		context->codec = NULL;
+		context->isOpen = FALSE;
+		context->context = NULL;
+		context->frame = NULL;
+		context->resampled = NULL;
+		context->packet = NULL;
+		context->rcontext = NULL;
+	}
+}
+
+static BOOL ffmpeg_open_context(FREERDP_DSP_CONTEXT* context)
+{
+	int ret = 0;
+
+	if (!context || context->isOpen)
+		return FALSE;
+
+	const AUDIO_FORMAT* format = &context->format;
+
+	if (!format)
+		return FALSE;
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+	const int layout = av_get_default_channel_layout(format->nChannels);
+#endif
+	context->id = ffmpeg_get_avcodec(format);
+
+	if (ffmpeg_codec_is_filtered(context->id, context->encoder))
+		goto fail;
+
+	if (context->encoder)
+		context->codec = avcodec_find_encoder(context->id);
+	else
+		context->codec = avcodec_find_decoder(context->id);
+
+	if (!context->codec)
+		goto fail;
+
+	context->context = avcodec_alloc_context3(context->codec);
+
+	if (!context->context)
+		goto fail;
+
+	switch (context->id)
+	{
+		/* We need support for multichannel and sample rates != 8000 */
+		case AV_CODEC_ID_GSM_MS:
+			context->context->strict_std_compliance = FF_COMPLIANCE_UNOFFICIAL;
+			break;
+
+		case AV_CODEC_ID_AAC:
+			context->context->profile = FF_PROFILE_AAC_MAIN;
+			break;
+
+		default:
+			break;
+	}
+
+	context->context->max_b_frames = 1;
+	context->context->delay = 0;
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+	context->context->channels = format->nChannels;
+	context->context->channel_layout = layout;
+#else
+	av_channel_layout_default(&context->context->ch_layout, format->nChannels);
+#endif
+	context->context->sample_rate = format->nSamplesPerSec;
+	context->context->block_align = format->nBlockAlign;
+	context->context->bit_rate = format->nAvgBytesPerSec * 8;
+	context->context->sample_fmt = ffmpeg_sample_format(format);
+	context->context->time_base = av_make_q(1, context->context->sample_rate);
+
+	if ((ret = avcodec_open2(context->context, context->codec, NULL)) < 0)
+	{
+		const char* err = av_err2str(ret);
+		WLog_ERR(TAG, "Error avcodec_open2 %s [%d]", err, ret);
+		goto fail;
+	}
+
+	context->packet = av_packet_alloc();
+
+	if (!context->packet)
+		goto fail;
+
+	context->frame = av_frame_alloc();
+
+	if (!context->frame)
+		goto fail;
+
+	context->resampled = av_frame_alloc();
+
+	if (!context->resampled)
+		goto fail;
+
+	context->buffered = av_frame_alloc();
+
+	if (!context->buffered)
+		goto fail;
+
+#if defined(SWRESAMPLE_FOUND)
+	context->rcontext = swr_alloc();
+#else
+	context->rcontext = avresample_alloc_context();
+#endif
+
+	if (!context->rcontext)
+		goto fail;
+
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+	context->frame->channel_layout = layout;
+	context->frame->channels = format->nChannels;
+#else
+	av_channel_layout_default(&context->frame->ch_layout, format->nChannels);
+#endif
+	context->frame->sample_rate = format->nSamplesPerSec;
+	context->frame->format = AV_SAMPLE_FMT_S16;
+
+	if (context->encoder)
+	{
+		context->resampled->format = context->context->sample_fmt;
+		context->resampled->sample_rate = context->context->sample_rate;
+	}
+	else
+	{
+		context->resampled->format = AV_SAMPLE_FMT_S16;
+		context->resampled->sample_rate = format->nSamplesPerSec;
+	}
+
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+	context->resampled->channel_layout = layout;
+	context->resampled->channels = format->nChannels;
+#else
+	av_channel_layout_default(&context->resampled->ch_layout, format->nChannels);
+#endif
+
+	if (context->context->frame_size > 0)
+	{
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+		context->buffered->channel_layout = context->resampled->channel_layout;
+		context->buffered->channels = context->resampled->channels;
+#else
+		av_channel_layout_copy(&context->buffered->ch_layout, &context->resampled->ch_layout);
+#endif
+		context->buffered->format = context->resampled->format;
+		context->buffered->nb_samples = context->context->frame_size;
+
+		if ((ret = av_frame_get_buffer(context->buffered, 1)) < 0)
+			goto fail;
+	}
+
+	context->isOpen = TRUE;
+	return TRUE;
+fail:
+	ffmpeg_close_context(context);
+	return FALSE;
+}
+
+#if defined(SWRESAMPLE_FOUND)
+static BOOL ffmpeg_resample_frame(SwrContext* context, AVFrame* in, AVFrame* out)
+{
+	int ret = 0;
+
+	if (!swr_is_initialized(context))
+	{
+		if ((ret = swr_config_frame(context, out, in)) < 0)
+		{
+			const char* err = av_err2str(ret);
+			WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+			return FALSE;
+		}
+
+		if ((ret = (swr_init(context))) < 0)
+		{
+			const char* err = av_err2str(ret);
+			WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+			return FALSE;
+		}
+	}
+
+	if ((ret = swr_convert_frame(context, out, in)) < 0)
+	{
+		const char* err = av_err2str(ret);
+		WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+#else
+static BOOL ffmpeg_resample_frame(AVAudioResampleContext* context, AVFrame* in, AVFrame* out)
+{
+	int ret;
+
+	if (!avresample_is_open(context))
+	{
+		if ((ret = avresample_config(context, out, in)) < 0)
+		{
+			const char* err = av_err2str(ret);
+			WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+			return FALSE;
+		}
+
+		if ((ret = (avresample_open(context))) < 0)
+		{
+			const char* err = av_err2str(ret);
+			WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+			return FALSE;
+		}
+	}
+
+	if ((ret = avresample_convert_frame(context, out, in)) < 0)
+	{
+		const char* err = av_err2str(ret);
+		WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+#endif
+
+static BOOL ffmpeg_encode_frame(AVCodecContext* context, AVFrame* in, AVPacket* packet,
+                                wStream* out)
+{
+	if (in->format == AV_SAMPLE_FMT_FLTP)
+	{
+		uint8_t** pp = in->extended_data;
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+		const int nr_channels = in->channels;
+#else
+		const int nr_channels = in->ch_layout.nb_channels;
+#endif
+
+		for (int y = 0; y < nr_channels; y++)
+		{
+			float* data = (float*)pp[y];
+			for (int x = 0; x < in->nb_samples; x++)
+			{
+				const float val1 = data[x];
+				if (isnan(val1))
+					data[x] = 0.0f;
+				else if (isinf(val1))
+				{
+					if (val1 < 0.0f)
+						data[x] = -1.0f;
+					else
+						data[x] = 1.0f;
+				}
+			}
+		}
+	}
+	/* send the packet with the compressed data to the encoder */
+	int ret = avcodec_send_frame(context, in);
+
+	if (ret < 0)
+	{
+		const char* err = av_err2str(ret);
+		WLog_ERR(TAG, "Error submitting the packet to the encoder %s [%d]", err, ret);
+		return FALSE;
+	}
+
+	/* read all the output frames (in general there may be any number of them */
+	while (ret >= 0)
+	{
+		ret = avcodec_receive_packet(context, packet);
+
+		if ((ret == AVERROR(EAGAIN)) || (ret == AVERROR_EOF))
+			return TRUE;
+		else if (ret < 0)
+		{
+			const char* err = av_err2str(ret);
+			WLog_ERR(TAG, "Error during encoding %s [%d]", err, ret);
+			return FALSE;
+		}
+
+		if (!Stream_EnsureRemainingCapacity(out, packet->size))
+			return FALSE;
+
+		Stream_Write(out, packet->data, packet->size);
+		av_packet_unref(packet);
+	}
+
+	return TRUE;
+}
+
+static BOOL ffmpeg_fill_frame(AVFrame* frame, const AUDIO_FORMAT* inputFormat, const BYTE* data,
+                              size_t size)
+{
+	int ret = 0;
+	int bpp = 0;
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+	frame->channels = inputFormat->nChannels;
+	frame->channel_layout = av_get_default_channel_layout(frame->channels);
+#else
+	av_channel_layout_default(&frame->ch_layout, inputFormat->nChannels);
+#endif
+	frame->sample_rate = inputFormat->nSamplesPerSec;
+	frame->format = ffmpeg_sample_format(inputFormat);
+
+	bpp = av_get_bytes_per_sample(frame->format);
+	frame->nb_samples = size / inputFormat->nChannels / bpp;
+
+	if ((ret = avcodec_fill_audio_frame(frame, inputFormat->nChannels, frame->format, data, size,
+	                                    1)) < 0)
+	{
+		const char* err = av_err2str(ret);
+		WLog_ERR(TAG, "Error during audio frame fill %s [%d]", err, ret);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+#if defined(SWRESAMPLE_FOUND)
+static BOOL ffmpeg_decode(AVCodecContext* dec_ctx, AVPacket* pkt, AVFrame* frame,
+                          SwrContext* resampleContext, AVFrame* resampled, wStream* out)
+#else
+static BOOL ffmpeg_decode(AVCodecContext* dec_ctx, AVPacket* pkt, AVFrame* frame,
+                          AVAudioResampleContext* resampleContext, AVFrame* resampled, wStream* out)
+#endif
+{
+	int ret = 0;
+	/* send the packet with the compressed data to the decoder */
+	ret = avcodec_send_packet(dec_ctx, pkt);
+
+	if (ret < 0)
+	{
+		const char* err = av_err2str(ret);
+		WLog_ERR(TAG, "Error submitting the packet to the decoder %s [%d]", err, ret);
+		return FALSE;
+	}
+
+	/* read all the output frames (in general there may be any number of them */
+	while (ret >= 0)
+	{
+		ret = avcodec_receive_frame(dec_ctx, frame);
+
+		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
+			return TRUE;
+		else if (ret < 0)
+		{
+			const char* err = av_err2str(ret);
+			WLog_ERR(TAG, "Error during decoding %s [%d]", err, ret);
+			return FALSE;
+		}
+
+#if defined(SWRESAMPLE_FOUND)
+		if (!swr_is_initialized(resampleContext))
+		{
+			if ((ret = swr_config_frame(resampleContext, resampled, frame)) < 0)
+			{
+#else
+		if (!avresample_is_open(resampleContext))
+		{
+			if ((ret = avresample_config(resampleContext, resampled, frame)) < 0)
+			{
+#endif
+				const char* err = av_err2str(ret);
+				WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+				return FALSE;
+			}
+
+#if defined(SWRESAMPLE_FOUND)
+			if ((ret = (swr_init(resampleContext))) < 0)
+#else
+			if ((ret = (avresample_open(resampleContext))) < 0)
+#endif
+			{
+				const char* err = av_err2str(ret);
+				WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+				return FALSE;
+			}
+		}
+
+#if defined(SWRESAMPLE_FOUND)
+		if ((ret = swr_convert_frame(resampleContext, resampled, frame)) < 0)
+#else
+		if ((ret = avresample_convert_frame(resampleContext, resampled, frame)) < 0)
+#endif
+		{
+			const char* err = av_err2str(ret);
+			WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret);
+			return FALSE;
+		}
+
+		{
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+			const size_t channels = resampled->channels;
+#else
+			const size_t channels = resampled->ch_layout.nb_channels;
+#endif
+			const size_t data_size = channels * resampled->nb_samples * 2;
+			Stream_EnsureRemainingCapacity(out, data_size);
+			Stream_Write(out, resampled->data[0], data_size);
+		}
+	}
+
+	return TRUE;
+}
+
+BOOL freerdp_dsp_ffmpeg_supports_format(const AUDIO_FORMAT* format, BOOL encode)
+{
+	enum AVCodecID id = ffmpeg_get_avcodec(format);
+
+	if (ffmpeg_codec_is_filtered(id, encode))
+		return FALSE;
+
+	if (encode)
+		return avcodec_find_encoder(id) != NULL;
+	else
+		return avcodec_find_decoder(id) != NULL;
+}
+
+FREERDP_DSP_CONTEXT* freerdp_dsp_ffmpeg_context_new(BOOL encode)
+{
+	FREERDP_DSP_CONTEXT* context = NULL;
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100)
+	avcodec_register_all();
+#endif
+	context = calloc(1, sizeof(FREERDP_DSP_CONTEXT));
+
+	if (!context)
+		return NULL;
+
+	context->channelmix = Stream_New(NULL, 1024);
+	if (!context->channelmix)
+	{
+		WINPR_PRAGMA_DIAG_PUSH
+		WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+		freerdp_dsp_ffmpeg_context_free(context);
+		WINPR_PRAGMA_DIAG_POP
+		return NULL;
+	}
+	context->encoder = encode;
+	return context;
+}
+
+void freerdp_dsp_ffmpeg_context_free(FREERDP_DSP_CONTEXT* context)
+{
+	if (context)
+	{
+		ffmpeg_close_context(context);
+		Stream_Free(context->channelmix, TRUE);
+		free(context);
+	}
+}
+
+BOOL freerdp_dsp_ffmpeg_context_reset(FREERDP_DSP_CONTEXT* context,
+                                      const AUDIO_FORMAT* targetFormat)
+{
+	if (!context || !targetFormat)
+		return FALSE;
+
+	ffmpeg_close_context(context);
+	context->format = *targetFormat;
+	return ffmpeg_open_context(context);
+}
+
+static BOOL freerdp_dsp_channel_mix(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size,
+                                    const AUDIO_FORMAT* srcFormat, const BYTE** data,
+                                    size_t* length, AUDIO_FORMAT* dstFormat)
+{
+	UINT32 bpp = 0;
+	size_t samples = 0;
+
+	if (!context || !data || !length || !dstFormat)
+		return FALSE;
+
+	if (srcFormat->wFormatTag != WAVE_FORMAT_PCM)
+		return FALSE;
+
+	bpp = srcFormat->wBitsPerSample > 8 ? 2 : 1;
+	samples = size / bpp / srcFormat->nChannels;
+
+	*dstFormat = *srcFormat;
+	if (context->format.nChannels == srcFormat->nChannels)
+	{
+		*data = src;
+		*length = size;
+		return TRUE;
+	}
+
+	Stream_SetPosition(context->channelmix, 0);
+
+	/* Destination has more channels than source */
+	if (context->format.nChannels > srcFormat->nChannels)
+	{
+		switch (srcFormat->nChannels)
+		{
+			case 1:
+				if (!Stream_EnsureCapacity(context->channelmix, size * 2))
+					return FALSE;
+
+				for (UINT32 x = 0; x < samples; x++)
+				{
+					for (UINT32 y = 0; y < bpp; y++)
+						Stream_Write_UINT8(context->channelmix, src[x * bpp + y]);
+
+					for (UINT32 y = 0; y < bpp; y++)
+						Stream_Write_UINT8(context->channelmix, src[x * bpp + y]);
+				}
+
+				Stream_SealLength(context->channelmix);
+				*data = Stream_Buffer(context->channelmix);
+				*length = Stream_Length(context->channelmix);
+				dstFormat->nChannels = 2;
+				return TRUE;
+
+			case 2:  /* We only support stereo, so we can not handle this case. */
+			default: /* Unsupported number of channels */
+				WLog_WARN(TAG, "unsupported source channel count %" PRIu16, srcFormat->nChannels);
+				return FALSE;
+		}
+	}
+
+	/* Destination has less channels than source */
+	switch (srcFormat->nChannels)
+	{
+		case 2:
+			if (!Stream_EnsureCapacity(context->channelmix, size / 2))
+				return FALSE;
+
+			/* Simply drop second channel.
+			 * TODO: Calculate average */
+			for (UINT32 x = 0; x < samples; x++)
+			{
+				for (UINT32 y = 0; y < bpp; y++)
+					Stream_Write_UINT8(context->channelmix, src[2 * x * bpp + y]);
+			}
+
+			Stream_SealLength(context->channelmix);
+			*data = Stream_Buffer(context->channelmix);
+			*length = Stream_Length(context->channelmix);
+			dstFormat->nChannels = 1;
+			return TRUE;
+
+		case 1:  /* Invalid, do we want to use a 0 channel sound? */
+		default: /* Unsupported number of channels */
+			WLog_WARN(TAG, "unsupported channel count %" PRIu16, srcFormat->nChannels);
+			return FALSE;
+	}
+
+	return FALSE;
+}
+
+BOOL freerdp_dsp_ffmpeg_encode(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* format,
+                               const BYTE* data, size_t length, wStream* out)
+{
+	AUDIO_FORMAT fmt = { 0 };
+
+	if (!context || !format || !data || !out || !context->encoder)
+		return FALSE;
+
+	if (!context || !data || !out)
+		return FALSE;
+
+	/* https://github.com/FreeRDP/FreeRDP/issues/7607
+	 *
+	 * we get noisy data with channel transformation, so do it ourselves.
+	 */
+	if (!freerdp_dsp_channel_mix(context, data, length, format, &data, &length, &fmt))
+		return FALSE;
+
+	/* Create input frame */
+	if (!ffmpeg_fill_frame(context->frame, format, data, length))
+		return FALSE;
+
+	/* Resample to desired format. */
+	if (!ffmpeg_resample_frame(context->rcontext, context->frame, context->resampled))
+		return FALSE;
+
+	if (context->context->frame_size <= 0)
+	{
+		return ffmpeg_encode_frame(context->context, context->resampled, context->packet, out);
+	}
+	else
+	{
+		int copied = 0;
+		int rest = context->resampled->nb_samples;
+
+		do
+		{
+			int inSamples = rest;
+
+			if ((inSamples < 0) || (context->bufferedSamples > (UINT32)(INT_MAX - inSamples)))
+				return FALSE;
+
+			if (inSamples + (int)context->bufferedSamples > context->context->frame_size)
+				inSamples = context->context->frame_size - (int)context->bufferedSamples;
+
+#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100)
+			const int channels = context->context->channels;
+#else
+			const int channels = context->context->ch_layout.nb_channels;
+#endif
+			const int rc =
+			    av_samples_copy(context->buffered->extended_data, context->resampled->extended_data,
+			                    (int)context->bufferedSamples, copied, inSamples, channels,
+			                    context->context->sample_fmt);
+			if (rc < 0)
+				return FALSE;
+			rest -= inSamples;
+			copied += inSamples;
+			context->bufferedSamples += (UINT32)inSamples;
+
+			if (context->context->frame_size <= (int)context->bufferedSamples)
+			{
+				/* Encode in desired format. */
+				if (!ffmpeg_encode_frame(context->context, context->buffered, context->packet, out))
+					return FALSE;
+
+				context->bufferedSamples = 0;
+			}
+		} while (rest > 0);
+
+		return TRUE;
+	}
+}
+
+BOOL freerdp_dsp_ffmpeg_decode(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* srcFormat,
+                               const BYTE* data, size_t length, wStream* out)
+{
+	if (!context || !srcFormat || !data || !out || context->encoder)
+		return FALSE;
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100)
+	av_init_packet(context->packet);
+#endif
+	context->packet->data = (uint8_t*)data;
+	context->packet->size = length;
+	return ffmpeg_decode(context->context, context->packet, context->frame, context->rcontext,
+	                     context->resampled, out);
+}
-- 
cgit v1.2.3