[gnome-remote-desktop] rdp: Add RDP DSP class



commit c2461dfc2ff4a4b8b606033ccd6823b172df9ef0
Author: Pascal Nowack <Pascal Nowack gmx de>
Date:   Mon May 23 06:17:46 2022 +0200

    rdp: Add RDP DSP class
    
    Add a class for processing audio data. The main purpose of this class
    will be to encode raw audio data for audio output redirection.
    Currently, only the AAC codec is supported, but in the future support
    for other codecs might be added too.

 .gitlab-ci.yml    |   3 +-
 config.h.meson    |   3 +
 meson.build       |  14 +++
 meson_options.txt |   5 +
 src/grd-rdp-dsp.c | 293 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/grd-rdp-dsp.h |  49 +++++++++
 src/meson.build   |   8 ++
 7 files changed, 374 insertions(+), 1 deletion(-)
---
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1b480ece..7fa29cc9 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,7 +9,7 @@ stages:
 .gnome-remote-desktop.fedora:35@common:
   variables:
     FDO_DISTRIBUTION_VERSION: 36
-    BASE_TAG: '2022-07-28.2'
+    BASE_TAG: '2022-07-28.3'
     FDO_UPSTREAM_REPO: GNOME/gnome-remote-desktop
     FDO_DISTRIBUTION_EXEC: |
       dnf -y update && dnf -y upgrade &&
@@ -23,6 +23,7 @@ stages:
                      'pkgconfig(gudev-1.0)' &&
       dnf install -y 'pkgconfig(ffnvcodec)' &&
       dnf install -y 'pkgconfig(fuse3)' &&
+      dnf install -y 'pkgconfig(fdk-aac)' &&
       dnf install -y asciidoc &&
 
       # To test
diff --git a/config.h.meson b/config.h.meson
index e9f9ea85..9eed37d4 100644
--- a/config.h.meson
+++ b/config.h.meson
@@ -6,6 +6,9 @@
 /* The prefix for our gettext translation domains. */
 #mesondefine GETTEXT_PACKAGE
 
+/* Defined if FDK AAC is available */
+#mesondefine HAVE_FDK_AAC
+
 /* Defined if RDP backend enabled */
 #mesondefine HAVE_RDP
 
diff --git a/meson.build b/meson.build
index 1a30f4e3..d26e075d 100644
--- a/meson.build
+++ b/meson.build
@@ -26,6 +26,7 @@ libsecret_dep = dependency('libsecret-1')
 pipewire_dep = dependency('libpipewire-0.3', version: '>= 0.3.49')
 systemd_dep = dependency('systemd', required: get_option('systemd'))
 
+have_fdk_aac = get_option('fdk_aac')
 have_rdp = get_option('rdp')
 have_vnc = get_option('vnc')
 
@@ -33,6 +34,10 @@ if not have_rdp and not have_vnc
   error('Must enable at least one backend')
 endif
 
+if have_fdk_aac and not have_rdp
+  error('Support for audio output redirection using FDK AAC requires the RDP backend')
+endif
+
 if have_rdp
   add_global_arguments('-D_GNU_SOURCE', language : 'c')
 
@@ -45,6 +50,10 @@ if have_rdp
   fuse_dep = dependency('fuse3', version: fuse_req)
   winpr_dep = dependency('winpr2', version: freerdp_req)
   xkbcommon_dep = dependency('xkbcommon', version: xkbcommon_req)
+
+  if have_fdk_aac
+    fdk_aac_dep = dependency('fdk-aac')
+  endif
 endif
 
 if have_vnc
@@ -66,6 +75,7 @@ cdata.set_quoted('VERSION', meson.project_version())
 
 cdata.set('HAVE_RDP', have_rdp)
 cdata.set('HAVE_VNC', have_vnc)
+cdata.set('HAVE_FDK_AAC', have_fdk_aac)
 
 cdata.set_quoted('GRD_DATA_DIR', grd_datadir)
 
@@ -117,6 +127,10 @@ output = [
   '        RDP...................... ' + have_rdp.to_string(),
   '        VNC...................... ' + have_vnc.to_string(),
   '',
+  '    Options for the RDP backend:',
+  '',
+  '        Support for audio output redirection using FDK AAC........' + have_fdk_aac.to_string(),
+  '',
   '  Now type \'ninja -C ' + meson.build_root() + '\' to build ' + meson.project_name(),
   '',
   '',
diff --git a/meson_options.txt b/meson_options.txt
index 2897f305..7ccaefb8 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -3,6 +3,11 @@ option('man',
        value: true,
        description: 'Generate man pages')
 
+option('fdk_aac',
+       type: 'boolean',
+       value: true,
+       description: 'Audio output redirection for the RDP backend')
+
 option('rdp',
        type: 'boolean',
        value: true,
diff --git a/src/grd-rdp-dsp.c b/src/grd-rdp-dsp.c
new file mode 100644
index 00000000..01d87f54
--- /dev/null
+++ b/src/grd-rdp-dsp.c
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2022 Pascal Nowack
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "grd-rdp-dsp.h"
+
+#include <gio/gio.h>
+
+#ifdef HAVE_FDK_AAC
+#include <fdk-aac/aacenc_lib.h>
+#endif /* HAVE_FDK_AAC */
+
+struct _GrdRdpDsp
+{
+  GObject parent;
+
+#ifdef HAVE_FDK_AAC
+  HANDLE_AACENCODER aac_encoder;
+#endif /* HAVE_FDK_AAC */
+};
+
+G_DEFINE_TYPE (GrdRdpDsp, grd_rdp_dsp, G_TYPE_OBJECT)
+
+#ifdef HAVE_FDK_AAC
+static gboolean
+encode_aac (GrdRdpDsp  *rdp_dsp,
+            int16_t    *input_data,
+            int         input_size,
+            int         input_elem_size,
+            uint8_t   **output_data,
+            uint32_t   *output_size)
+{
+  AACENC_BufferIdentifier ident_in;
+  AACENC_BufferIdentifier ident_out;
+  AACENC_BufDesc buffer_descriptor_in = {};
+  AACENC_BufDesc buffer_descriptor_out = {};
+  AACENC_InArgs args_in = {};
+  AACENC_OutArgs args_out = {};
+  int output_allocation_size;
+  int output_elem_size;
+  AACENC_ERROR aac_error;
+
+  *output_size = 0;
+
+  ident_in = IN_AUDIO_DATA;
+
+  buffer_descriptor_in.numBufs = 1;
+  buffer_descriptor_in.bufs = (void **) &input_data;
+  buffer_descriptor_in.bufferIdentifiers = (int *) &ident_in;
+  buffer_descriptor_in.bufSizes = &input_size;
+  buffer_descriptor_in.bufElSizes = &input_elem_size;
+
+  ident_out = OUT_BITSTREAM_DATA;
+  output_elem_size = sizeof (uint8_t);
+
+  output_allocation_size = input_size;
+  *output_data = g_malloc0 (output_allocation_size);
+
+  buffer_descriptor_out.numBufs = 1;
+  buffer_descriptor_out.bufs = (void **) output_data;
+  buffer_descriptor_out.bufferIdentifiers = (int *) &ident_out;
+  buffer_descriptor_out.bufSizes = &output_allocation_size;
+  buffer_descriptor_out.bufElSizes = &output_elem_size;
+
+  args_in.numInSamples = input_size / input_elem_size;
+
+  aac_error = aacEncEncode (rdp_dsp->aac_encoder, &buffer_descriptor_in,
+                            &buffer_descriptor_out, &args_in, &args_out);
+  if (aac_error != AACENC_OK)
+    {
+      g_warning ("[RDP.DSP] Failed to AAC encode samples");
+      g_clear_pointer (output_data, g_free);
+      return FALSE;
+    }
+
+  *output_size = args_out.numOutBytes;
+
+  return TRUE;
+}
+#endif /* HAVE_FDK_AAC */
+
+gboolean
+grd_rdp_dsp_encode (GrdRdpDsp       *rdp_dsp,
+                    GrdRdpDspCodec   codec,
+                    int16_t         *input_data,
+                    uint32_t         input_size,
+                    uint32_t         input_elem_size,
+                    uint8_t        **output_data,
+                    uint32_t        *output_size)
+{
+  switch (codec)
+    {
+    case GRD_RDP_DSP_CODEC_NONE:
+      g_assert_not_reached ();
+      return FALSE;
+    case GRD_RDP_DSP_CODEC_AAC:
+#ifdef HAVE_FDK_AAC
+      return encode_aac (rdp_dsp, input_data, input_size, input_elem_size,
+                         output_data, output_size);
+#else
+      g_assert_not_reached ();
+      return FALSE;
+#endif /* HAVE_FDK_AAC */
+    }
+
+  g_assert_not_reached ();
+
+  return FALSE;
+}
+
+#ifdef HAVE_FDK_AAC
+static gboolean
+create_aac_encoder (GrdRdpDsp  *rdp_dsp,
+                    uint32_t    n_samples_per_sec,
+                    uint32_t    n_channels,
+                    uint32_t    bitrate,
+                    GError    **error)
+{
+  AACENC_InfoStruct info = {};
+  AACENC_ERROR aac_error;
+
+  /*
+   * Assert n_channels == 2 due to (currently) hardcoded
+   * AACENC_CHANNELMODE setting
+   */
+  g_assert (n_channels == 2);
+
+  aac_error = aacEncOpen (&rdp_dsp->aac_encoder, 0, n_channels);
+  if (!rdp_dsp->aac_encoder)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Failed to create AAC encoder. AAC error %i", aac_error);
+      return FALSE;
+    }
+
+  aac_error = aacEncoder_SetParam (rdp_dsp->aac_encoder, AACENC_AOT,
+                                   AOT_AAC_LC);
+  if (aac_error != AACENC_OK)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Failed to set AAC encoder param AACENC_AOT. "
+                   "AAC error %i", aac_error);
+      return FALSE;
+    }
+
+  aac_error = aacEncoder_SetParam (rdp_dsp->aac_encoder, AACENC_BITRATE,
+                                   bitrate);
+  if (aac_error != AACENC_OK)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Failed to set AAC encoder param AACENC_BITRATE. "
+                   "AAC error %i", aac_error);
+      return FALSE;
+    }
+
+  aac_error = aacEncoder_SetParam (rdp_dsp->aac_encoder, AACENC_SAMPLERATE,
+                                   n_samples_per_sec);
+  if (aac_error != AACENC_OK)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Failed to set AAC encoder param AACENC_SAMPLERATE. "
+                   "AAC error %i", aac_error);
+      return FALSE;
+    }
+
+  aac_error = aacEncoder_SetParam (rdp_dsp->aac_encoder, AACENC_CHANNELMODE,
+                                   MODE_2);
+  if (aac_error != AACENC_OK)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Failed to set AAC encoder param AACENC_CHANNELMODE. "
+                   "AAC error %i", aac_error);
+      return FALSE;
+    }
+
+  aac_error = aacEncoder_SetParam (rdp_dsp->aac_encoder, AACENC_CHANNELORDER,
+                                   1);
+  if (aac_error != AACENC_OK)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Failed to set AAC encoder param AACENC_CHANNELORDER. "
+                   "AAC error %i", aac_error);
+      return FALSE;
+    }
+
+  aac_error = aacEncoder_SetParam (rdp_dsp->aac_encoder, AACENC_TRANSMUX,
+                                   TT_MP4_RAW);
+  if (aac_error != AACENC_OK)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Failed to set AAC encoder param AACENC_TRANSMUX. "
+                   "AAC error %i", aac_error);
+      return FALSE;
+    }
+
+  aac_error = aacEncoder_SetParam (rdp_dsp->aac_encoder, AACENC_AFTERBURNER, 1);
+  if (aac_error != AACENC_OK)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Failed to set AAC encoder param AACENC_AFTERBURNER. "
+                   "AAC error %i", aac_error);
+      return FALSE;
+    }
+
+  aac_error = aacEncEncode (rdp_dsp->aac_encoder, NULL, NULL, NULL, NULL);
+  if (aac_error != AACENC_OK)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Failed to initialize AAC encoder. AAC error %i", aac_error);
+      return FALSE;
+    }
+
+  aac_error = aacEncInfo (rdp_dsp->aac_encoder, &info);
+  if (aac_error != AACENC_OK)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Failed to aquire AAC encoder info. AAC error %i",
+                   aac_error);
+      return FALSE;
+    }
+
+  g_debug ("[RDP.DSP] AAC encoder: maxOutBufBytes: %u, maxAncBytes: %u, "
+           "inBufFillLevel: %u, inputChannels: %u, frameLength: %u, "
+           "nDelay: %u, nDelayCore: %u",
+           info.maxOutBufBytes, info.maxAncBytes, info.inBufFillLevel,
+           info.inputChannels, info.frameLength, info.nDelay, info.nDelayCore);
+
+  return TRUE;
+}
+#endif /* HAVE_FDK_AAC */
+
+GrdRdpDsp *
+grd_rdp_dsp_new (uint32_t   n_samples_per_sec,
+                 uint32_t   n_channels,
+                 uint32_t   bitrate_aac,
+                 GError   **error)
+{
+  g_autoptr (GrdRdpDsp) rdp_dsp = NULL;
+
+  rdp_dsp = g_object_new (GRD_TYPE_RDP_DSP, NULL);
+
+#ifdef HAVE_FDK_AAC
+  if (!create_aac_encoder (rdp_dsp, n_samples_per_sec, n_channels, bitrate_aac,
+                           error))
+    return NULL;
+#endif /* HAVE_FDK_AAC */
+
+  return g_steal_pointer (&rdp_dsp);
+}
+
+static void
+grd_rdp_dsp_dispose (GObject *object)
+{
+#ifdef HAVE_FDK_AAC
+  GrdRdpDsp *rdp_dsp = GRD_RDP_DSP (object);
+#endif /* HAVE_FDK_AAC */
+
+#ifdef HAVE_FDK_AAC
+  aacEncClose (&rdp_dsp->aac_encoder);
+#endif /* HAVE_FDK_AAC */
+
+  G_OBJECT_CLASS (grd_rdp_dsp_parent_class)->dispose (object);
+}
+
+static void
+grd_rdp_dsp_init (GrdRdpDsp *rdp_dsp)
+{
+}
+
+static void
+grd_rdp_dsp_class_init (GrdRdpDspClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = grd_rdp_dsp_dispose;
+}
diff --git a/src/grd-rdp-dsp.h b/src/grd-rdp-dsp.h
new file mode 100644
index 00000000..96c6c720
--- /dev/null
+++ b/src/grd-rdp-dsp.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 Pascal Nowack
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef GRD_RDP_DSP_H
+#define GRD_RDP_DSP_H
+
+#include <glib-object.h>
+#include <stdint.h>
+
+#define GRD_TYPE_RDP_DSP (grd_rdp_dsp_get_type ())
+G_DECLARE_FINAL_TYPE (GrdRdpDsp, grd_rdp_dsp,
+                      GRD, RDP_DSP, GObject)
+
+typedef enum _GrdRdpDspCodec
+{
+  GRD_RDP_DSP_CODEC_NONE,
+  GRD_RDP_DSP_CODEC_AAC,
+} GrdRdpDspCodec;
+
+GrdRdpDsp *grd_rdp_dsp_new (uint32_t   n_samples_per_sec,
+                            uint32_t   n_channels,
+                            uint32_t   bitrate_aac,
+                            GError   **error);
+
+gboolean grd_rdp_dsp_encode (GrdRdpDsp       *rdp_dsp,
+                             GrdRdpDspCodec   codec,
+                             int16_t         *input_data,
+                             uint32_t         input_size,
+                             uint32_t         input_elem_size,
+                             uint8_t        **output_data,
+                             uint32_t        *output_size);
+
+#endif /* GRD_RDP_DSP_H */
diff --git a/src/meson.build b/src/meson.build
index 31556d06..9f1b49ce 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -66,6 +66,8 @@ if have_rdp
     'grd-rdp-damage-detector-memcmp.h',
     'grd-rdp-display-control.c',
     'grd-rdp-display-control.h',
+    'grd-rdp-dsp.c',
+    'grd-rdp-dsp.h',
     'grd-rdp-event-queue.c',
     'grd-rdp-event-queue.h',
     'grd-rdp-frame-info.h',
@@ -106,6 +108,12 @@ if have_rdp
     winpr_dep,
     xkbcommon_dep,
   ]
+
+  if (have_fdk_aac)
+    deps += [
+      fdk_aac_dep,
+    ]
+  endif
 endif
 
 if have_vnc


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]