Skip to content

Commit

Permalink
[spo] [api] roc-streaminggh-731: Add PLC to C API
Browse files Browse the repository at this point in the history
Built-in PLC support:

- add roc_plc_backend
- add roc_receiver_config.plc_backend

Custom PLC support:

- add plugin.h
- add ROC_ENCODING_ID_MIN, ROC_ENCODING_ID_MAX
- add ROC_PLUGIN_ID_MIN, ROC_PLUGIN_ID_MAX
- add roc_plugin_plc
- add roc_context_register_plc

Extra:

- add API usage example
- add API integration test

Sponsored-by: waspd
  • Loading branch information
gavv committed Jul 17, 2024
1 parent 52652f1 commit c320cb8
Show file tree
Hide file tree
Showing 14 changed files with 1,090 additions and 21 deletions.
22 changes: 22 additions & 0 deletions docs/sphinx/api/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ roc_context

.. doxygenfunction:: roc_context_register_encoding

.. doxygenfunction:: roc_context_register_plc

.. doxygenfunction:: roc_context_close

roc_sender
Expand Down Expand Up @@ -209,6 +211,8 @@ roc_config

.. doxygenenum:: roc_resampler_profile

.. doxygenenum:: roc_plc_backend

.. doxygenstruct:: roc_context_config
:members:

Expand All @@ -234,6 +238,24 @@ roc_metrics
.. doxygenstruct:: roc_receiver_metrics
:members:

roc_plugin
==========

.. code-block:: c
#include <roc/plugin.h>
.. doxygenenumvalue:: ROC_ENCODING_ID_MIN

.. doxygenenumvalue:: ROC_ENCODING_ID_MAX

.. doxygenenumvalue:: ROC_PLUGIN_ID_MIN

.. doxygenenumvalue:: ROC_PLUGIN_ID_MAX

.. doxygenstruct:: roc_plugin_plc
:members:

roc_log
=======

Expand Down
2 changes: 2 additions & 0 deletions src/internal_modules/roc_status/code_to_str.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ const char* code_to_str(StatusCode code) {
return "NoRoute";
case StatusNoDriver:
return "NoDriver";
case StatusNoPlugin:
return "NoPlugin";
case StatusErrDevice:
return "ErrDevice";
case StatusErrFile:
Expand Down
8 changes: 8 additions & 0 deletions src/internal_modules/roc_status/status_code.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ enum StatusCode {
//! supports only wav files.
StatusNoDriver,

//! No plugin found.
//! @remarks
//! Indicates that plugin lookup or initialization failed.
//! @note
//! Example: we're trying to create PLC plugin, but use-provided callback
//! failed to allocate it.
StatusNoPlugin,

//! Failure with audio device.
//! @remarks
//! Indicates that error occurred when working with audio device.
Expand Down
171 changes: 171 additions & 0 deletions src/public_api/examples/plugin_plc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/*
* Register custom Packet loss concealment (PLC) plugin.
*
* PLC allows to reduce distortion caused by packet losses by replacing
* gaps with interpolated data. It is used only when FEC wasn't able to
* repair lost packets.
*
* Building:
* cc plugin_plc.c -lroc
*
* Running:
* ./a.out
*
* License:
* public domain
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <roc/context.h>
#include <roc/log.h>
#include <roc/receiver.h>

/* Any number in range [ROC_PLUGIN_ID_MIN; ROC_PLUGIN_ID_MAX] */
#define MY_PLC_PLUGIN_ID ROC_PLUGIN_ID_MIN + 1

#define MY_SAMPLE_RATE 44100
#define MY_CHANNEL_COUNT 2
#define MY_LOOKAHEAD_SIZE 4410 /* 100 ms */

#define oops() \
do { \
fprintf(stderr, "oops: failure on %s:%d\n", __FILE__, __LINE__); \
fprintf(stderr, "exiting!\n"); \
exit(1); \
} while (0)

/* PLC plugin instance.
* roc_receiver will create an instance for every connection. */
struct my_plc {
/* Here we could put state needed for interpolation. */
unsigned int history_frame_counter;
unsigned int lost_frame_counter;
};

/* Create plugin instance. */
static void* my_plc_new(roc_plugin_plc* plugin) {
return calloc(1, sizeof(struct my_plc));
}

/* Delete plugin instance. */
static void my_plc_delete(void* plugin_instance) {
struct my_plc* plc = (struct my_plc*)plugin_instance;

free(plc);
}

/* Get look-ahead length - how many samples after the lost frame
* do we need for interpolation.
* Returned value is measured as the number of samples per channel,
* e.g. if sample rate is 44100Hz, length 4410 is 100ms */
static unsigned int my_plc_lookahead_len(void* plugin_instance) {
return MY_LOOKAHEAD_SIZE;
}

/* Called when next frame is good (no loss). */
static void my_plc_process_history(void* plugin_instance,
const roc_frame* history_frame) {
struct my_plc* plc = (struct my_plc*)plugin_instance;

/* Here we can copy samples from history_frame to ring buffer.
* In this example we just ignore frame. */
plc->history_frame_counter++;
}

/* Called when next frame is lost and we must fill it with interpolated data.
*
* lost_frame is the frame to be filled (we must fill its buffer with the
* interpolated samples)
*
* lookahead_frame contains samples going after the lost frame, which we can
* use to improve interpolation results. Its size may vary from 0 to MY_LOOKAHEAD_SIZE.
*/
static void my_plc_process_loss(void* plugin_instance,
roc_frame* lost_frame,
const roc_frame* lookahead_frame) {
struct my_plc* plc = (struct my_plc*)plugin_instance;

/* Here we can implement interpolation.
* In this example we just fill frame with constants.
* Samples are float because we use ROC_FORMAT_PCM_FLOAT32.
* There are two channels because we use ROC_CHANNEL_LAYOUT_STEREO. */
float* lost_samples = lost_frame->samples;
size_t lost_sample_count =
lost_frame->samples_size / sizeof(float) / MY_CHANNEL_COUNT;

for (unsigned ns = 0; ns < lost_sample_count; ns++) {
for (unsigned c = 0; c < MY_CHANNEL_COUNT; c++) {
lost_samples[0] = 0.123f; /* left channel */
lost_samples[1] = 0.456f; /* right channel */
}
lost_samples += MY_CHANNEL_COUNT;
}

plc->lost_frame_counter++;
}

int main() {
roc_log_set_level(ROC_LOG_INFO);

/* Create context. */
roc_context_config context_config;
memset(&context_config, 0, sizeof(context_config));

roc_context* context = NULL;
if (roc_context_open(&context_config, &context) != 0) {
oops();
}

/* Register plugin. */
roc_plugin_plc plc_plugin;
memset(&plc_plugin, 0, sizeof(plc_plugin));

plc_plugin.new_cb = &my_plc_new;
plc_plugin.delete_cb = &my_plc_delete;
plc_plugin.lookahead_len_cb = &my_plc_lookahead_len;
plc_plugin.process_history_cb = &my_plc_process_history;
plc_plugin.process_loss_cb = &my_plc_process_loss;

if (roc_context_register_plc(context, MY_PLC_PLUGIN_ID, &plc_plugin) != 0) {
oops();
}

/* Prepare receiver config. */
roc_receiver_config receiver_config;
memset(&receiver_config, 0, sizeof(receiver_config));

/* Setup frame format.
* This format applies to frames that we read from receiver, as well as to
* the frames passed to PLC plugin. */
receiver_config.frame_encoding.rate = MY_SAMPLE_RATE;
receiver_config.frame_encoding.format = ROC_FORMAT_PCM_FLOAT32;
receiver_config.frame_encoding.channels = ROC_CHANNEL_LAYOUT_STEREO;

/* Enable PLC plugin. */
receiver_config.plc_backend = (roc_plc_backend)MY_PLC_PLUGIN_ID;

/* Create receiver. */
roc_receiver* receiver = NULL;
if (roc_receiver_open(context, &receiver_config, &receiver) != 0) {
oops();
}

/*
* Here we can run receiver loop.
*/

/* Destroy receiver. */
if (roc_receiver_close(receiver) != 0) {
oops();
}

/* Destroy context. */
if (roc_context_close(context) != 0) {
oops();
}

return 0;
}
48 changes: 38 additions & 10 deletions src/public_api/include/roc/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -569,8 +569,8 @@ typedef enum roc_resampler_backend {
* stage is needed, and this becomes fastest possible backend working almost as fast
* as memcpy().
*
* When frame and packet rates are different, usage of this backend compared to
* \c ROC_RESAMPLER_BACKEND_SPEEX allows to sacrify some quality, but somewhat
* When frame and packet rates are different, usage of this backend, compared to
* \c ROC_RESAMPLER_BACKEND_SPEEX, allows to sacrify some quality, but somewhat
* improve scaling precision and CPU usage in return.
*
* This backend is available only when SpeexDSP was enabled at build time.
Expand Down Expand Up @@ -600,6 +600,25 @@ typedef enum roc_resampler_profile {
ROC_RESAMPLER_PROFILE_LOW = 3
} roc_resampler_profile;

/** PLC backend.
*
* Packet loss concealment (PLC), is used to reduce distortion caused by lost
* packets by filling gaps with interpolated or extrapolated data.
*
* PLC is used when a packet was lost and FEC was not able to recover it.
*/
typedef enum roc_plc_backend {
/** No PLC.
* Gaps are filled with zeros (silence).
*/
ROC_PLC_BACKEND_DISABLE = -1,

/** Default backend.
* Current default is \c ROC_PLC_BACKEND_DISABLE.
*/
ROC_PLC_BACKEND_DEFAULT = 0,
} roc_plc_backend;

/** Context configuration.
*
* It is safe to memset() this struct with zeros to get a default config. It is also
Expand Down Expand Up @@ -652,15 +671,14 @@ typedef struct roc_sender_config {
* automatically.
*
* If zero, sender selects packet encoding automatically based on \c frame_encoding.
* This automatic selection matches only encodings that have exact same sample rate
* and channel layout, and hence don't require conversions. If you need conversions,
* you should set packet encoding explicitly.
* This automatic selection matches only encodings that have exact same sample rate,
* channel layout, and format, hence don't require conversions. If you need
* conversions, you should set packet encoding explicitly.
*
* If you want to force specific packet encoding, and built-in set of encodings is
* not enough, you can use \ref roc_context_register_encoding() to register custom
* encoding, and set \c packet_encoding to registered identifier. If you use signaling
* protocol like RTSP, it's enough to register in just on sender; otherwise, you
* need to do the same on receiver as well.
* You can use \ref roc_context_register_encoding() to register custom encoding, and
* set \c packet_encoding to registered identifier. If you use signaling protocol like
* RTSP, it's enough to register in just on sender; otherwise, you need to do the same
* on receiver as well.
*/
roc_packet_encoding packet_encoding;

Expand Down Expand Up @@ -854,6 +872,16 @@ typedef struct roc_receiver_config {
*/
roc_resampler_profile resampler_profile;

/** PLC backend.
* Allows to reduce distortion cased by packet loss.
*
* If zero, default backend is used (\ref ROC_PLC_BACKEND_DEFAULT).
*
* You can use \ref roc_context_register_plc() to register custom PLC implementation,
* and set \c plc_backend to registered identifier.
*/
roc_plc_backend plc_backend;

/** Target latency, in nanoseconds.
*
* How latency is calculated depends on \c latency_tuner_backend field.
Expand Down
33 changes: 28 additions & 5 deletions src/public_api/include/roc/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#include "roc/config.h"
#include "roc/platform.h"
#include "roc/plugin.h"

#ifdef __cplusplus
extern "C" {
Expand Down Expand Up @@ -57,27 +58,26 @@ typedef struct roc_context roc_context;
* - returns a negative value if there are not enough resources
*
* **Ownership**
* - doesn't take or share the ownership of \p config; it may be safely deallocated
* after the function returns
* - passes the ownership of \p result to the user; the user is responsible to call
* roc_context_close() to free it
*/
ROC_API int roc_context_open(const roc_context_config* config, roc_context** result);

/** Register custom encoding.
*
* Registers \p encoding with given \p encoding_id. Registered encodings complement
* Registers \p encoding with given \p encoding_id. Registered encodings extend
* built-in encodings defined by \ref roc_packet_encoding enum. Whenever you need to
* specify packet encoding, you can use both built-in and registered encodings.
*
* On sender, you should register custom encoding and set to \c packet_encoding field
* of \c roc_sender_config, if you need to force specific encoding of packets, but
* built-in set of encodings is not enough.
* of \c roc_sender_config, if you need to force specific encoding of packets.
*
* On receiver, you should register custom encoding with same id and specification,
* if you did so on sender, and you're not using any signaling protocol (like RTSP)
* that is capable of automatic exchange of encoding information.
*
* In case of RTP, encoding id is mapped directly to payload type field (PT).
*
* **Parameters**
* - \p context should point to an opened context
* - \p encoding_id should be in range [ROC_ENCODING_ID_MIN; ROC_ENCODING_ID_MAX]
Expand All @@ -96,6 +96,29 @@ ROC_API int roc_context_register_encoding(roc_context* context,
int encoding_id,
const roc_media_encoding* encoding);

/** Register custom PLC backend.
*
* Registers plugin that implements custom PLC backend. Registered backends extend
* built-in PLC backends defined by \ref roc_plc_backend enum. Whenever you need to
* specify PLC backend, you can use both built-in and registered backends.
*
* **Parameters**
* - \p context should point to an opened context
* - \p plugin_id should be in range [ROC_PLUGIN_ID_MIN; ROC_PLUGIN_ID_MAX]
* - \p plugin should point to plugin callback table
*
* **Returns**
* - returns zero if plugin was successfully registered
* - returns a negative value if the arguments are invalid
* - returns a negative value if plugin with given identifier already exists
*
* **Ownership**
* - stores \p plugin pointer internally for later use; \p plugin should remain valid
* until \p context is closed
*/
ROC_API int
roc_context_register_plc(roc_context* context, int plugin_id, roc_plugin_plc* plugin);

/** Close the context.
*
* Stops any started background threads, deinitializes and deallocates the context.
Expand Down
Loading

0 comments on commit c320cb8

Please sign in to comment.