153a5a1b3Sopenharmony_ci/***
253a5a1b3Sopenharmony_ci  This file is part of PulseAudio.
353a5a1b3Sopenharmony_ci
453a5a1b3Sopenharmony_ci  Copyright 2013 Collabora Ltd.
553a5a1b3Sopenharmony_ci  Author: Arun Raghavan <arun.raghavan@collabora.co.uk>
653a5a1b3Sopenharmony_ci
753a5a1b3Sopenharmony_ci  PulseAudio is free software; you can redistribute it and/or modify
853a5a1b3Sopenharmony_ci  it under the terms of the GNU Lesser General Public License as published
953a5a1b3Sopenharmony_ci  by the Free Software Foundation; either version 2.1 of the License,
1053a5a1b3Sopenharmony_ci  or (at your option) any later version.
1153a5a1b3Sopenharmony_ci
1253a5a1b3Sopenharmony_ci  PulseAudio is distributed in the hope that it will be useful, but
1353a5a1b3Sopenharmony_ci  WITHOUT ANY WARRANTY; without even the implied warranty of
1453a5a1b3Sopenharmony_ci  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1553a5a1b3Sopenharmony_ci  General Public License for more details.
1653a5a1b3Sopenharmony_ci
1753a5a1b3Sopenharmony_ci  You should have received a copy of the GNU Lesser General Public License
1853a5a1b3Sopenharmony_ci  along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
1953a5a1b3Sopenharmony_ci***/
2053a5a1b3Sopenharmony_ci
2153a5a1b3Sopenharmony_ci#ifdef HAVE_CONFIG_H
2253a5a1b3Sopenharmony_ci#include <config.h>
2353a5a1b3Sopenharmony_ci#endif
2453a5a1b3Sopenharmony_ci
2553a5a1b3Sopenharmony_ci#include <math.h>
2653a5a1b3Sopenharmony_ci
2753a5a1b3Sopenharmony_ci#include <pulsecore/log.h>
2853a5a1b3Sopenharmony_ci#include <pulsecore/macro.h>
2953a5a1b3Sopenharmony_ci#include <pulsecore/core-util.h>
3053a5a1b3Sopenharmony_ci
3153a5a1b3Sopenharmony_ci#include "lo-test-util.h"
3253a5a1b3Sopenharmony_ci
3353a5a1b3Sopenharmony_ci/* Keep the frequency high so RMS over ranges of a few ms remains relatively
3453a5a1b3Sopenharmony_ci * high as well */
3553a5a1b3Sopenharmony_ci#define TONE_HZ 4410
3653a5a1b3Sopenharmony_ci
3753a5a1b3Sopenharmony_cistatic void nop_free_cb(void *p) {
3853a5a1b3Sopenharmony_ci}
3953a5a1b3Sopenharmony_ci
4053a5a1b3Sopenharmony_cistatic void underflow_cb(struct pa_stream *s, void *userdata) {
4153a5a1b3Sopenharmony_ci    pa_log_warn("Underflow");
4253a5a1b3Sopenharmony_ci}
4353a5a1b3Sopenharmony_ci
4453a5a1b3Sopenharmony_cistatic void overflow_cb(struct pa_stream *s, void *userdata) {
4553a5a1b3Sopenharmony_ci    pa_log_warn("Overlow");
4653a5a1b3Sopenharmony_ci}
4753a5a1b3Sopenharmony_ci
4853a5a1b3Sopenharmony_ci/*
4953a5a1b3Sopenharmony_ci * We run a simple volume calibration so that we know we can detect the signal
5053a5a1b3Sopenharmony_ci * being played back. We start with the playback stream at 100% volume, and
5153a5a1b3Sopenharmony_ci * capture at 0.
5253a5a1b3Sopenharmony_ci *
5353a5a1b3Sopenharmony_ci * First, we then play a sine wave and increase the capture volume till the
5453a5a1b3Sopenharmony_ci * signal is clearly received.
5553a5a1b3Sopenharmony_ci *
5653a5a1b3Sopenharmony_ci * Next, we play back silence and make sure that the level is low enough to
5753a5a1b3Sopenharmony_ci * distinguish from when playback is happening.
5853a5a1b3Sopenharmony_ci *
5953a5a1b3Sopenharmony_ci * Finally, we hand off to the real read/write callbacks to run the actual
6053a5a1b3Sopenharmony_ci * test.
6153a5a1b3Sopenharmony_ci */
6253a5a1b3Sopenharmony_ci
6353a5a1b3Sopenharmony_cienum {
6453a5a1b3Sopenharmony_ci    CALIBRATION_ONE,
6553a5a1b3Sopenharmony_ci    CALIBRATION_ZERO,
6653a5a1b3Sopenharmony_ci    CALIBRATION_DONE,
6753a5a1b3Sopenharmony_ci};
6853a5a1b3Sopenharmony_ci
6953a5a1b3Sopenharmony_cistatic int cal_state = CALIBRATION_ONE;
7053a5a1b3Sopenharmony_ci
7153a5a1b3Sopenharmony_cistatic void calibrate_write_cb(pa_stream *s, size_t nbytes, void *userdata) {
7253a5a1b3Sopenharmony_ci    pa_lo_test_context *ctx = (pa_lo_test_context *) userdata;
7353a5a1b3Sopenharmony_ci    int i, nsamp = nbytes / ctx->fs;
7453a5a1b3Sopenharmony_ci    float tmp[nsamp][2];
7553a5a1b3Sopenharmony_ci    static int count = 0;
7653a5a1b3Sopenharmony_ci
7753a5a1b3Sopenharmony_ci    /* Write out a sine tone */
7853a5a1b3Sopenharmony_ci    for (i = 0; i < nsamp; i++)
7953a5a1b3Sopenharmony_ci        tmp[i][0] = tmp[i][1] = cal_state == CALIBRATION_ONE ? sinf(count++ * TONE_HZ * 2 * M_PI / ctx->sample_spec.rate) : 0.0f;
8053a5a1b3Sopenharmony_ci
8153a5a1b3Sopenharmony_ci    pa_assert_se(pa_stream_write(s, &tmp, nbytes, nop_free_cb, 0, PA_SEEK_RELATIVE) == 0);
8253a5a1b3Sopenharmony_ci
8353a5a1b3Sopenharmony_ci    if (cal_state == CALIBRATION_DONE)
8453a5a1b3Sopenharmony_ci        pa_stream_set_write_callback(s, ctx->write_cb, ctx);
8553a5a1b3Sopenharmony_ci}
8653a5a1b3Sopenharmony_ci
8753a5a1b3Sopenharmony_cistatic void calibrate_read_cb(pa_stream *s, size_t nbytes, void *userdata) {
8853a5a1b3Sopenharmony_ci    pa_lo_test_context *ctx = (pa_lo_test_context *) userdata;
8953a5a1b3Sopenharmony_ci    static double v = 0;
9053a5a1b3Sopenharmony_ci    static int skip = 0, confirm;
9153a5a1b3Sopenharmony_ci
9253a5a1b3Sopenharmony_ci    pa_cvolume vol;
9353a5a1b3Sopenharmony_ci    pa_operation *o;
9453a5a1b3Sopenharmony_ci    int nsamp;
9553a5a1b3Sopenharmony_ci    float *in;
9653a5a1b3Sopenharmony_ci    size_t l;
9753a5a1b3Sopenharmony_ci
9853a5a1b3Sopenharmony_ci    pa_assert_se(pa_stream_peek(s, (const void **)&in, &l) == 0);
9953a5a1b3Sopenharmony_ci
10053a5a1b3Sopenharmony_ci    nsamp = l / ctx->fs;
10153a5a1b3Sopenharmony_ci
10253a5a1b3Sopenharmony_ci    /* For each state or volume step change, throw out a few samples so we know
10353a5a1b3Sopenharmony_ci     * we're seeing the changed samples. */
10453a5a1b3Sopenharmony_ci    if (skip++ < 100)
10553a5a1b3Sopenharmony_ci        goto out;
10653a5a1b3Sopenharmony_ci    else
10753a5a1b3Sopenharmony_ci        skip = 0;
10853a5a1b3Sopenharmony_ci
10953a5a1b3Sopenharmony_ci    switch (cal_state) {
11053a5a1b3Sopenharmony_ci        case CALIBRATION_ONE:
11153a5a1b3Sopenharmony_ci            /* Try to detect the sine wave. RMS is 0.5, */
11253a5a1b3Sopenharmony_ci            if (pa_rms(in, nsamp) < 0.40f) {
11353a5a1b3Sopenharmony_ci                confirm = 0;
11453a5a1b3Sopenharmony_ci                v += 0.02f;
11553a5a1b3Sopenharmony_ci
11653a5a1b3Sopenharmony_ci                if (v > 1.0) {
11753a5a1b3Sopenharmony_ci                    pa_log_error("Capture signal too weak at 100%% volume (%g). Giving up.", pa_rms(in, nsamp));
11853a5a1b3Sopenharmony_ci                    pa_assert_not_reached();
11953a5a1b3Sopenharmony_ci                }
12053a5a1b3Sopenharmony_ci
12153a5a1b3Sopenharmony_ci                pa_cvolume_set(&vol, ctx->sample_spec.channels, v * PA_VOLUME_NORM);
12253a5a1b3Sopenharmony_ci                o = pa_context_set_source_output_volume(ctx->context, pa_stream_get_index(s), &vol, NULL, NULL);
12353a5a1b3Sopenharmony_ci                pa_assert(o != NULL);
12453a5a1b3Sopenharmony_ci                pa_operation_unref(o);
12553a5a1b3Sopenharmony_ci            } else {
12653a5a1b3Sopenharmony_ci                /* Make sure the signal strength is steadily above our threshold */
12753a5a1b3Sopenharmony_ci                if (++confirm > 5) {
12853a5a1b3Sopenharmony_ci#if 0
12953a5a1b3Sopenharmony_ci                    pa_log_debug(stderr, "Capture volume = %g (%g)", v, pa_rms(in, nsamp));
13053a5a1b3Sopenharmony_ci#endif
13153a5a1b3Sopenharmony_ci                    cal_state = CALIBRATION_ZERO;
13253a5a1b3Sopenharmony_ci                }
13353a5a1b3Sopenharmony_ci            }
13453a5a1b3Sopenharmony_ci
13553a5a1b3Sopenharmony_ci            break;
13653a5a1b3Sopenharmony_ci
13753a5a1b3Sopenharmony_ci        case CALIBRATION_ZERO:
13853a5a1b3Sopenharmony_ci            /* Now make sure silence doesn't trigger a false positive because
13953a5a1b3Sopenharmony_ci             * of noise. */
14053a5a1b3Sopenharmony_ci            if (pa_rms(in, nsamp) > 0.1f) {
14153a5a1b3Sopenharmony_ci                pa_log_warn("Too much noise on capture (%g). Giving up.", pa_rms(in, nsamp));
14253a5a1b3Sopenharmony_ci                pa_assert_not_reached();
14353a5a1b3Sopenharmony_ci            }
14453a5a1b3Sopenharmony_ci
14553a5a1b3Sopenharmony_ci            cal_state = CALIBRATION_DONE;
14653a5a1b3Sopenharmony_ci            pa_stream_set_read_callback(s, ctx->read_cb, ctx);
14753a5a1b3Sopenharmony_ci
14853a5a1b3Sopenharmony_ci            break;
14953a5a1b3Sopenharmony_ci
15053a5a1b3Sopenharmony_ci        default:
15153a5a1b3Sopenharmony_ci            break;
15253a5a1b3Sopenharmony_ci    }
15353a5a1b3Sopenharmony_ci
15453a5a1b3Sopenharmony_ciout:
15553a5a1b3Sopenharmony_ci    pa_stream_drop(s);
15653a5a1b3Sopenharmony_ci}
15753a5a1b3Sopenharmony_ci
15853a5a1b3Sopenharmony_ci/* This routine is called whenever the stream state changes */
15953a5a1b3Sopenharmony_cistatic void stream_state_callback(pa_stream *s, void *userdata) {
16053a5a1b3Sopenharmony_ci    pa_lo_test_context *ctx = (pa_lo_test_context *) userdata;
16153a5a1b3Sopenharmony_ci
16253a5a1b3Sopenharmony_ci    switch (pa_stream_get_state(s)) {
16353a5a1b3Sopenharmony_ci        case PA_STREAM_UNCONNECTED:
16453a5a1b3Sopenharmony_ci        case PA_STREAM_CREATING:
16553a5a1b3Sopenharmony_ci        case PA_STREAM_TERMINATED:
16653a5a1b3Sopenharmony_ci            break;
16753a5a1b3Sopenharmony_ci
16853a5a1b3Sopenharmony_ci        case PA_STREAM_READY: {
16953a5a1b3Sopenharmony_ci            pa_cvolume vol;
17053a5a1b3Sopenharmony_ci            pa_operation *o;
17153a5a1b3Sopenharmony_ci
17253a5a1b3Sopenharmony_ci            /* Set volumes for calibration */
17353a5a1b3Sopenharmony_ci            if (s == ctx->play_stream) {
17453a5a1b3Sopenharmony_ci                pa_cvolume_set(&vol, ctx->sample_spec.channels, PA_VOLUME_NORM);
17553a5a1b3Sopenharmony_ci                o = pa_context_set_sink_input_volume(ctx->context, pa_stream_get_index(s), &vol, NULL, NULL);
17653a5a1b3Sopenharmony_ci            } else {
17753a5a1b3Sopenharmony_ci                pa_cvolume_set(&vol, ctx->sample_spec.channels, pa_sw_volume_from_linear(0.0));
17853a5a1b3Sopenharmony_ci                o = pa_context_set_source_output_volume(ctx->context, pa_stream_get_index(s), &vol, NULL, NULL);
17953a5a1b3Sopenharmony_ci            }
18053a5a1b3Sopenharmony_ci
18153a5a1b3Sopenharmony_ci            if (!o) {
18253a5a1b3Sopenharmony_ci                pa_log_error("Could not set stream volume: %s", pa_strerror(pa_context_errno(ctx->context)));
18353a5a1b3Sopenharmony_ci                pa_assert_not_reached();
18453a5a1b3Sopenharmony_ci            } else
18553a5a1b3Sopenharmony_ci                pa_operation_unref(o);
18653a5a1b3Sopenharmony_ci
18753a5a1b3Sopenharmony_ci            break;
18853a5a1b3Sopenharmony_ci        }
18953a5a1b3Sopenharmony_ci
19053a5a1b3Sopenharmony_ci        case PA_STREAM_FAILED:
19153a5a1b3Sopenharmony_ci        default:
19253a5a1b3Sopenharmony_ci            pa_log_error("Stream error: %s", pa_strerror(pa_context_errno(ctx->context)));
19353a5a1b3Sopenharmony_ci            pa_assert_not_reached();
19453a5a1b3Sopenharmony_ci    }
19553a5a1b3Sopenharmony_ci}
19653a5a1b3Sopenharmony_ci
19753a5a1b3Sopenharmony_ci/* This is called whenever the context status changes */
19853a5a1b3Sopenharmony_cistatic void context_state_callback(pa_context *c, void *userdata) {
19953a5a1b3Sopenharmony_ci    pa_lo_test_context *ctx = (pa_lo_test_context *) userdata;
20053a5a1b3Sopenharmony_ci    pa_mainloop_api *api;
20153a5a1b3Sopenharmony_ci
20253a5a1b3Sopenharmony_ci    switch (pa_context_get_state(c)) {
20353a5a1b3Sopenharmony_ci        case PA_CONTEXT_CONNECTING:
20453a5a1b3Sopenharmony_ci        case PA_CONTEXT_AUTHORIZING:
20553a5a1b3Sopenharmony_ci        case PA_CONTEXT_SETTING_NAME:
20653a5a1b3Sopenharmony_ci            break;
20753a5a1b3Sopenharmony_ci
20853a5a1b3Sopenharmony_ci        case PA_CONTEXT_READY: {
20953a5a1b3Sopenharmony_ci            pa_buffer_attr buffer_attr;
21053a5a1b3Sopenharmony_ci
21153a5a1b3Sopenharmony_ci            pa_thread_make_realtime(4);
21253a5a1b3Sopenharmony_ci
21353a5a1b3Sopenharmony_ci            /* Create playback stream */
21453a5a1b3Sopenharmony_ci            buffer_attr.maxlength = -1;
21553a5a1b3Sopenharmony_ci            buffer_attr.tlength = ctx->sample_spec.rate * ctx->fs * ctx->play_latency / 1000;
21653a5a1b3Sopenharmony_ci            buffer_attr.prebuf = 0; /* Setting prebuf to 0 guarantees us the stream will run synchronously, no matter what */
21753a5a1b3Sopenharmony_ci            buffer_attr.minreq = -1;
21853a5a1b3Sopenharmony_ci            buffer_attr.fragsize = -1;
21953a5a1b3Sopenharmony_ci
22053a5a1b3Sopenharmony_ci            ctx->play_stream = pa_stream_new(c, "loopback: play", &ctx->sample_spec, NULL);
22153a5a1b3Sopenharmony_ci            pa_assert(ctx->play_stream != NULL);
22253a5a1b3Sopenharmony_ci            pa_stream_set_state_callback(ctx->play_stream, stream_state_callback, ctx);
22353a5a1b3Sopenharmony_ci            pa_stream_set_write_callback(ctx->play_stream, calibrate_write_cb, ctx);
22453a5a1b3Sopenharmony_ci            pa_stream_set_underflow_callback(ctx->play_stream, underflow_cb, userdata);
22553a5a1b3Sopenharmony_ci
22653a5a1b3Sopenharmony_ci            pa_stream_connect_playback(ctx->play_stream, getenv("TEST_SINK"), &buffer_attr,
22753a5a1b3Sopenharmony_ci                    PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL);
22853a5a1b3Sopenharmony_ci
22953a5a1b3Sopenharmony_ci            /* Create capture stream */
23053a5a1b3Sopenharmony_ci            buffer_attr.maxlength = -1;
23153a5a1b3Sopenharmony_ci            buffer_attr.tlength = (uint32_t) -1;
23253a5a1b3Sopenharmony_ci            buffer_attr.prebuf = 0;
23353a5a1b3Sopenharmony_ci            buffer_attr.minreq = (uint32_t) -1;
23453a5a1b3Sopenharmony_ci            buffer_attr.fragsize = ctx->sample_spec.rate * ctx->fs * ctx->rec_latency / 1000;
23553a5a1b3Sopenharmony_ci
23653a5a1b3Sopenharmony_ci            ctx->rec_stream = pa_stream_new(c, "loopback: rec", &ctx->sample_spec, NULL);
23753a5a1b3Sopenharmony_ci            pa_assert(ctx->rec_stream != NULL);
23853a5a1b3Sopenharmony_ci            pa_stream_set_state_callback(ctx->rec_stream, stream_state_callback, ctx);
23953a5a1b3Sopenharmony_ci            pa_stream_set_read_callback(ctx->rec_stream, calibrate_read_cb, ctx);
24053a5a1b3Sopenharmony_ci            pa_stream_set_overflow_callback(ctx->rec_stream, overflow_cb, userdata);
24153a5a1b3Sopenharmony_ci
24253a5a1b3Sopenharmony_ci            pa_stream_connect_record(ctx->rec_stream, getenv("TEST_SOURCE"), &buffer_attr,
24353a5a1b3Sopenharmony_ci                    PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE);
24453a5a1b3Sopenharmony_ci
24553a5a1b3Sopenharmony_ci            break;
24653a5a1b3Sopenharmony_ci        }
24753a5a1b3Sopenharmony_ci
24853a5a1b3Sopenharmony_ci        case PA_CONTEXT_TERMINATED:
24953a5a1b3Sopenharmony_ci            api = pa_mainloop_get_api(ctx->mainloop);
25053a5a1b3Sopenharmony_ci            api->quit(api, 0);
25153a5a1b3Sopenharmony_ci            break;
25253a5a1b3Sopenharmony_ci
25353a5a1b3Sopenharmony_ci        case PA_CONTEXT_FAILED:
25453a5a1b3Sopenharmony_ci        default:
25553a5a1b3Sopenharmony_ci            pa_log_error("Context error: %s", pa_strerror(pa_context_errno(c)));
25653a5a1b3Sopenharmony_ci            pa_assert_not_reached();
25753a5a1b3Sopenharmony_ci    }
25853a5a1b3Sopenharmony_ci}
25953a5a1b3Sopenharmony_ci
26053a5a1b3Sopenharmony_ciint pa_lo_test_init(pa_lo_test_context *ctx) {
26153a5a1b3Sopenharmony_ci    /* FIXME: need to deal with non-float samples at some point */
26253a5a1b3Sopenharmony_ci    pa_assert(ctx->sample_spec.format == PA_SAMPLE_FLOAT32);
26353a5a1b3Sopenharmony_ci
26453a5a1b3Sopenharmony_ci    ctx->ss = pa_sample_size(&ctx->sample_spec);
26553a5a1b3Sopenharmony_ci    ctx->fs = pa_frame_size(&ctx->sample_spec);
26653a5a1b3Sopenharmony_ci
26753a5a1b3Sopenharmony_ci    ctx->mainloop = pa_mainloop_new();
26853a5a1b3Sopenharmony_ci    ctx->context = pa_context_new(pa_mainloop_get_api(ctx->mainloop), ctx->context_name);
26953a5a1b3Sopenharmony_ci
27053a5a1b3Sopenharmony_ci    pa_context_set_state_callback(ctx->context, context_state_callback, ctx);
27153a5a1b3Sopenharmony_ci
27253a5a1b3Sopenharmony_ci    /* Connect the context */
27353a5a1b3Sopenharmony_ci    if (pa_context_connect(ctx->context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) {
27453a5a1b3Sopenharmony_ci        pa_log_error("pa_context_connect() failed.");
27553a5a1b3Sopenharmony_ci        goto quit;
27653a5a1b3Sopenharmony_ci    }
27753a5a1b3Sopenharmony_ci
27853a5a1b3Sopenharmony_ci    return 0;
27953a5a1b3Sopenharmony_ci
28053a5a1b3Sopenharmony_ciquit:
28153a5a1b3Sopenharmony_ci    pa_context_unref(ctx->context);
28253a5a1b3Sopenharmony_ci    pa_mainloop_free(ctx->mainloop);
28353a5a1b3Sopenharmony_ci
28453a5a1b3Sopenharmony_ci    return -1;
28553a5a1b3Sopenharmony_ci}
28653a5a1b3Sopenharmony_ci
28753a5a1b3Sopenharmony_ciint pa_lo_test_run(pa_lo_test_context *ctx) {
28853a5a1b3Sopenharmony_ci    int ret;
28953a5a1b3Sopenharmony_ci
29053a5a1b3Sopenharmony_ci    if (pa_mainloop_run(ctx->mainloop, &ret) < 0) {
29153a5a1b3Sopenharmony_ci        pa_log_error("pa_mainloop_run() failed.");
29253a5a1b3Sopenharmony_ci        return -1;
29353a5a1b3Sopenharmony_ci    }
29453a5a1b3Sopenharmony_ci
29553a5a1b3Sopenharmony_ci    return 0;
29653a5a1b3Sopenharmony_ci}
29753a5a1b3Sopenharmony_ci
29853a5a1b3Sopenharmony_civoid pa_lo_test_deinit(pa_lo_test_context *ctx) {
29953a5a1b3Sopenharmony_ci    if (ctx->play_stream) {
30053a5a1b3Sopenharmony_ci        pa_stream_disconnect(ctx->play_stream);
30153a5a1b3Sopenharmony_ci        pa_stream_unref(ctx->play_stream);
30253a5a1b3Sopenharmony_ci    }
30353a5a1b3Sopenharmony_ci
30453a5a1b3Sopenharmony_ci    if (ctx->rec_stream) {
30553a5a1b3Sopenharmony_ci        pa_stream_disconnect(ctx->rec_stream);
30653a5a1b3Sopenharmony_ci        pa_stream_unref(ctx->rec_stream);
30753a5a1b3Sopenharmony_ci    }
30853a5a1b3Sopenharmony_ci
30953a5a1b3Sopenharmony_ci    if (ctx->context)
31053a5a1b3Sopenharmony_ci        pa_context_unref(ctx->context);
31153a5a1b3Sopenharmony_ci
31253a5a1b3Sopenharmony_ci    if (ctx->mainloop)
31353a5a1b3Sopenharmony_ci        pa_mainloop_free(ctx->mainloop);
31453a5a1b3Sopenharmony_ci}
31553a5a1b3Sopenharmony_ci
31653a5a1b3Sopenharmony_cifloat pa_rms(const float *s, int n) {
31753a5a1b3Sopenharmony_ci    float sq = 0;
31853a5a1b3Sopenharmony_ci    int i;
31953a5a1b3Sopenharmony_ci
32053a5a1b3Sopenharmony_ci    for (i = 0; i < n; i++)
32153a5a1b3Sopenharmony_ci        sq += s[i] * s[i];
32253a5a1b3Sopenharmony_ci
32353a5a1b3Sopenharmony_ci    return sqrtf(sq / n);
32453a5a1b3Sopenharmony_ci}
325