summaryrefslogtreecommitdiffstats
path: root/libfreerdp/codec/dsp_ffmpeg.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
commita9bcc81f821d7c66f623779fa5147e728eb3c388 (patch)
tree98676963bcdd537ae5908a067a8eb110b93486a6 /libfreerdp/codec/dsp_ffmpeg.c
parentInitial commit. (diff)
downloadfreerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz
freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'libfreerdp/codec/dsp_ffmpeg.c')
-rw-r--r--libfreerdp/codec/dsp_ffmpeg.c846
1 files changed, 846 insertions, 0 deletions
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);
+}