Hide navigation sidebar
Hide table of contents sidebar
Skip to content
Back to top
Toggle table of contents sidebar

# Examples

This section goes through the various example applications that are provided alongside the `lib_voice`. The examples are meant to demonstrate the basic usage of the APIs. All examples are located in the `examples` directory.

## AEC example

The AEC example demonstrates the use of API to run a frame of data through the AEC. It also shows how to configure AEC to run on one or two threads. `AEC_THREADS` define toggles the AEC to use 1-threaded or 2-threaded scheduling structs:

        #if AEC_THREADS == 1
        aec_task_distribution_t tdist = aec_tdist_chans2_threads1;
        #elif AEC_THREADS == 2
        aec_task_distribution_t tdist = aec_tdist_chans2_threads2;
        #else
        #error "Unsupported number of AEC threads"
        #endif

The build system will automatically create both configs called `app_aec_1th` and `app_aec_2th`.

After the application has decided which thread distribution scheme to run, it allocates memory for the AEC and initialises it:

        // Allocate signal data
        int32_t DWORD_ALIGNED frame_y[AEC_MAX_Y_CHANNELS][AEC_FRAME_ADVANCE];
        int32_t DWORD_ALIGNED frame_x[AEC_MAX_X_CHANNELS][AEC_FRAME_ADVANCE];

        // Initialise AEC
        aec_state_t DWORD_ALIGNED aec_state;
        aec_init(&aec_state,
                AEC_MAX_Y_CHANNELS, AEC_MAX_X_CHANNELS,
                AEC_MAIN_FILTER_PHASES, AEC_SHADOW_FILTER_PHASES, &tdist);

After the AEC has been initialised, it is ready to process data. In this example, `frame_y` and `frame_x` memory is reused for AEC output:

            producer(frame_y, frame_x);
            // Reuse mic data memory for the main filter output
            // Reuse ref data memory for the shadow filter output
            aec_process_frame(&aec_state, frame_y, frame_x, frame_y, frame_x);
            consumer(frame_y);

Upon execution, the example will print “frame done” when the AEC has processed a frame.

## VNR example

The VNR example demonstrates the use of API to run a frame of data through the VNR and get the estimation of how much voice is present in it.

To run the VNR, the application should first allocate memory for its data and initialise it:

      // Allocate input and output memory
      int32_t input[VNR_FRAME_ADVANCE] = {0};
      float_s32_t vnr_out;

      // Initialise VNR
      vnr_state_t vnr;
      vnr_state_init(&vnr);

After the VNR has been initialised, it is ready to process data. The VNR output is in `float_s32_t` format, so there’s an extra step if the user wants it in `float`.

        producer(input);
        vnr_process_frame(&vnr, &vnr_out, input);
        float res = float_s32_to_float(vnr_out);
        consumer(res);

Upon execution, the example will use the pseudo-random generator to get an input data. This data will be run through the VNR and the score will be printed in the terminal. It outputs a number between 0 and 1, 1 being the strongest voice with respect to noise and 0 being the lowest voice compared to noise ratio. The pseudo-random data is not representative of a real signal, so the VNR scores in this example tend to be zero.

## Pipeline example

This example demonstrates how to put together a voice processing pipeline. The pipeline will have AEC, IC, NS and AGC stages. It also demonstrates the use of ADEC module to do a one time estimation and correction for possible reference and loudspeaker delay offsets at start up in order to maximise AEC performance. ADEC processing happens on the same thread as the AEC. The VNR is introduced to give the IC and the AGC information about the speech presence in a frame.

There are two pipelines supported in this example: Standard Architecture and Alternating Architecture. Building one or the other config is controlled via the `ALT_ARCH_MODE` define. The build system will automatically create both configs called `app_pipeline_std_arch` and `app_pipeline_alt_arch`.

To create the pipeline, the application must first initialise all the individual components:

        // Initialise AEC, DE, ADEC stages
        aec_conf_t aec_de_mode_conf, aec_non_de_mode_conf;
    #if ALT_ARCH_MODE
        aec_non_de_mode_conf.num_y_channels = 1;
        aec_non_de_mode_conf.num_x_channels = AP_MAX_X_CHANNELS;
        aec_non_de_mode_conf.num_main_filt_phases = 15;
        aec_non_de_mode_conf.num_shadow_filt_phases = AEC_SHADOW_FILTER_PHASES;
    #else
        aec_non_de_mode_conf.num_y_channels = AP_MAX_Y_CHANNELS;
        aec_non_de_mode_conf.num_x_channels = AP_MAX_X_CHANNELS;
        aec_non_de_mode_conf.num_main_filt_phases = AEC_MAIN_FILTER_PHASES;
        aec_non_de_mode_conf.num_shadow_filt_phases = AEC_SHADOW_FILTER_PHASES;
    #endif
        aec_non_de_mode_conf.tdist = &aec_tdist_chans2_threads1;

        aec_de_mode_conf.num_y_channels = 1;
        aec_de_mode_conf.num_x_channels = 1;
        aec_de_mode_conf.num_main_filt_phases = 30;
        aec_de_mode_conf.num_shadow_filt_phases = 0;
        aec_de_mode_conf.tdist = &aec_tdist_chans2_threads1;

        // Disable ADEC's automatic mode. We only want to estimate and correct for the delay at startup
        adec_config_t adec_conf;
        adec_conf.bypass = 1; // Bypass automatic DE correction
    #if DISABLE_INITIAL_DELAY_EST
        // Do not force a DE correction cycle on startup
        adec_conf.force_de_cycle_trigger = 0;
    #else
        // Force a delay correction cycle, so that delay correction happens once after initialisation.
        // Make sure this is set back to 0 after adec has requested a transition into DE mode once,
        // to stop any further delay correction (automatic or forced) by ADEC
        adec_conf.force_de_cycle_trigger = 1;
    #endif
        stage1_init(&state->stage_1_state, &aec_de_mode_conf, &aec_non_de_mode_conf, &adec_conf);

        // Initialise IC, VNR
        ic_init(&state->ic_state);

        // Initialise NS
        ns_init(&state->ns_state);

        // Initialise AGC
        agc_config_t agc_conf_asr = AGC_PROFILE_ASR;
        agc_init(&state->agc_state, &agc_conf_asr);

After the pipeline has been initialised, the data can be run through it. All the modules in the example can run on separate threads. To exchange information between the pipeline stages the metadata struct will need to be created to be populated and consumed by the different modules.

        /** Stage1 - AEC, DE, ADEC*/
        // stage1 will not process the frame in-place,
        // since mic input is needed to overwrite the output in certain cases
        int32_t stage_1_out[AEC_MAX_Y_CHANNELS][AP_FRAME_ADVANCE];

        stage1_process_frame(&state->stage_1_state, &stage_1_out[0], &md.max_ref_energy,
                &md.aec_corr_factor, &md.ref_active_flag, input_y_data, input_x_data);

        // Bypass IC if the reference is high in the alt arch mode
    #if ALT_ARCH_MODE
        if(md.ref_active_flag) {
            state->ic_state.config_params.bypass = 1;
        }
        else {
            state->ic_state.config_params.bypass = 0;
        }
    #endif
        /** IC and VNR*/
        int32_t ic_output[AP_FRAME_ADVANCE];
        float_s32_t input_vnr_pred;

        ic_process_frame(&state->ic_state, input_data[0], input_data[1], ic_output, &input_vnr_pred);
        md.vnr_pred_flag = input_vnr_pred;

        /** NS*/
        int32_t ns_output[AP_FRAME_ADVANCE];

        ns_process_frame(&state->ns_state, ns_output, ic_output);

        /** AGC*/
        agc_meta_data_t agc_md;
        agc_md.aec_ref_power = md.max_ref_energy;
        agc_md.vnr_flag = md.vnr_pred_flag;
        agc_md.ref_active_flag = md.ref_active_flag;
        agc_md.aec_corr_factor = md.aec_corr_factor;

        agc_process_frame(&state->agc_state, output_data, ns_output, &agc_md);

Upon execution, the example will print “frame done” when the AEC has processed a frame.

## Building the example

This section assumes that the XMOS XTC Tools have been downloaded and installed. It also assumes that the Python is installed and available. The required versions are specified in the accompanying `README`.

Installation instructions can be found here.

Special attention should be paid to the section on Installation of Required Third-Party Tools.

The application is built using the xcommon-cmake build system, which is provided with the XTC tools and is based on CMake.

The `lib_voice` software ZIP package should be downloaded and extracted to a chosen working directory.

To configure the build, the following commands should be run from an XTC command prompt:

    cd lib_voice
    pip install -r requirements.txt
    cd examples/app_aec
    cmake -G "Unix Makefiles" -B build

Note

The `pip install -r requirements.txt` stage has to happen before the `cmake` configuration as it will fetch the xmos-ai-tools.

If any dependencies are missing they will be retrieved automatically during this step.

The application binaries should then be built using `xmake`:

    xmake -j -C build

Binary artifacts (.xe files) will be generated under the appropriate subdirectories of the `app_aec/bin` directory — one for each supported build configuration.

For subsequent builds, the `cmake` step may be omitted. If `CMakeLists.txt` or other build files are modified, `cmake` will be re-run automatically by `xmake` as needed.

## Running the example

From an XTC command prompt, the following command should be run from the `examples/app_aec` directory:

    xrun --io ./bin/2th/app_aec_2th.xe