<commit_before>/*
 * Copyright (C) 2013 Jolla Ltd.
 *
 * Contact: <NAME> <<EMAIL>>
 *
 * These PulseAudio Modules are free software; you can redistribute
 * it and/or modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation
 * version 2.1 of the License.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
 * USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <signal.h>
#include <stdio.h>

#ifdef HAVE_VALGRIND_MEMCHECK_H
#include <valgrind/memcheck.h>
#endif

#include <pulse/rtclock.h>
#include <pulse/timeval.h>
#include <pulse/volume.h>
#include <pulse/xmalloc.h>

#include <pulsecore/core.h>
#include <pulsecore/core-error.h>
#include <pulsecore/i18n.h>
#include <pulsecore/module.h>
#include <pulsecore/memchunk.h>
#include <pulsecore/sink.h>
#include <pulsecore/modargs.h>
#include <pulsecore/core-rtclock.h>
#include <pulsecore/core-util.h>
#include <pulsecore/sample-util.h>
#include <pulsecore/log.h>
#include <pulsecore/macro.h>
#include <pulsecore/thread.h>
#include <pulsecore/thread-mq.h>
#include <pulsecore/rtpoll.h>
#include <pulsecore/time-smoother.h>
#include <pulsecore/refcnt.h>
#include <pulsecore/shared.h>
#include <pulsecore/mutex.h>

#include <hardware/audio.h>
#include <hardware_legacy/audio_policy_conf.h>

#include "droid-util.h"

#include <android-version.h>

#ifndef ANDROID_VERSION_MAJOR
#error "ANDROID_VERSION_* not defined."
#endif

#if ANDROID_VERSION_MAJOR == 4 && ANDROID_VERSION_MINOR == 1
#include "droid-util-41qc.h"
#elif ANDROID_VERSION_MAJOR == 4 && ANDROID_VERSION_MINOR == 2
#include "droid-util-42.h"
#elif ANDROID_VERSION_MAJOR == 4 && ANDROID_VERSION_MINOR == 4
#include "droid-util-44.h"
#else
#error "No valid ANDROID_VERSION found."
#endif

#define CONVERT_FUNC(TABL) \
bool pa_convert_ ## TABL (uint32_t value, pa_conversion_field_t field, uint32_t *to_value) {                    \
    for (unsigned int i = 0; i < sizeof( conversion_table_ ## TABL )/(sizeof(uint32_t)*2); i++) {               \
        if ( conversion_table_ ## TABL [i][field] == value) {                                                   \
            *to_value = conversion_table_ ## TABL [i][!field];                                                  \
            return true;                                                                                        \
        }                                                                                                       \
    }                                                                                                           \
    return false;                                                                                               \
} struct __funny_extra_to_allow_semicolon

/* Creates convert_format convert_channel etc.
 * bool pa_convert_func(uint32_t value, pa_conversion_field_t field, uint32_t *to_value);
 * return true if conversion succesful */
CONVERT_FUNC(format);
CONVERT_FUNC(output_channel);
CONVERT_FUNC(input_channel);

#define DEFAULT_PRIORITY (100)

static void droid_port_free(pa_droid_port *p);

static bool string_convert_num_to_str(const struct string_conversion *list, const uint32_t value, const char **to_str) {
    pa_assert(list);
    pa_assert(to_str);

    for (unsigned int i = 0; list[i].str; i++) {
        if (list[i].value == value) {
            *to_str = list[i].str;
            return true;
        }
    }
    return false;
}

static bool string_convert_str_to_num(const struct string_conversion *list, const char *str, uint32_t *to_value) {
    pa_assert(list);
    pa_assert(str);
    pa_assert(to_value);

    for (unsigned int i = 0; list[i].str; i++) {
        if (pa_streq(list[i].str, str)) {
            *to_value = list[i].value;
            return true;
        }
    }
    return false;
}

static char *list_string(struct string_conversion *list, uint32_t flags) {
    char *str = NULL;
    char *tmp;

#ifdef HAL_V2
    if (flags & AUDIO_DEVICE_BIT_IN)
        flags &= ~AUDIO_DEVICE_BIT_IN;
#endif

    for (unsigned int i = 0; list[i].str; i++) {
#ifdef HAL_V2
        if (list[i].value & AUDIO_DEVICE_BIT_IN) {
            if (popcount(list[i].value & ~AUDIO_DEVICE_BIT_IN) != 1)
                continue;
        } else
#endif
        if (popcount(list[i].value) != 1)
            continue;

        if (flags & list[i].value) {
            if (str) {
                tmp = pa_sprintf_malloc("%s|%s", str, list[i].str);
                pa_xfree(str);
                str = tmp;
            } else {
                str = pa_sprintf_malloc("%s", list[i].str);
            }
        }
    }

    return str;
}


/* Output device */
bool pa_string_convert_output_device_num_to_str(audio_devices_t value, const char **to_str) {
    return string_convert_num_to_str(string_conversion_table_output_device, (uint32_t) value, to_str);
}

bool pa_string_convert_output_device_str_to_num(const char *str, audio_devices_t *to_value) {
    return string_convert_str_to_num(string_conversion_table_output_device, str, (uint32_t*) to_value);
}

char *pa_list_string_output_device(audio_devices_t devices) {
    return list_string(string_conversion_table_output_device, devices);
}

/* Input device */
bool pa_string_convert_input_device_num_to_str(audio_devices_t value, const char **to_str) {
    return string_convert_num_to_str(string_conversion_table_input_device, (uint32_t) value, to_str);
}

bool pa_string_convert_input_device_str_to_num(const char *str, audio_devices_t *to_value) {
    return string_convert_str_to_num(string_conversion_table_input_device, str, (uint32_t*) to_value);
}

char *pa_list_string_input_device(audio_devices_t devices) {
    return list_string(string_conversion_table_input_device, devices);
}

/* Flags */
bool pa_string_convert_flag_num_to_str(audio_output_flags_t value, const char **to_str) {
    return string_convert_num_to_str(string_conversion_table_flag, (uint32_t) value, to_str);
}

bool pa_string_convert_flag_str_to_num(const char *str, audio_output_flags_t *to_value) {
    return string_convert_str_to_num(string_conversion_table_flag, str, (uint32_t*) to_value);
}

char *pa_list_string_flags(audio_output_flags_t flags) {
    return list_string(string_conversion_table_flag, flags);
}

/* Config parser */

#define WHITESPACE "\n\r \t"

static int parse_list(const struct string_conversion *table, const char *str, uint32_t *dst) {
    int count = 0;
    char *entry;
    const char *state = NULL;

    pa_assert(table);
    pa_assert(str);
    pa_assert(dst);

    *dst = 0;

    while ((entry = pa_split(str, "|", &state))) {
        uint32_t d = 0;

        if (!string_convert_str_to_num(table, entry, &d)) {
            pa_log("Unknown entry %s", entry);
            pa_xfree(entry);
            return -1;
        }

        *dst |= d;
        count++;

        pa_xfree(entry);
    }

    return count;
}

static bool parse_sampling_rates(const char *str, uint32_t sampling_rates[32]) {
    pa_assert(str);

    char *entry;
    const char *state = NULL;

    uint32_t pos = 0;
    while ((entry = pa_split(str, "|", &state))) {
        int32_t val;

        if (pos == AUDIO_MAX_SAMPLING_RATES) {
            pa_log("Too many sample rate entries (> %d)", AUDIO_MAX_SAMPLING_RATES);
            pa_xfree(entry);
            return false;
        }

        if (pa_atoi(entry, &val) < 0) {
            pa_log("Bad sample rate value %s", entry);
            pa_xfree(entry);
            return false;
        }

        sampling_rates[pos++] = val;

        pa_xfree(entry);

    }

    sampling_rates[pos] = 0;

    return true;
}

static bool parse_formats(const char *str, audio_format_t *formats) {
    pa_assert(str);
    pa_assert(formats);

    return parse_list(string_conversion_table_format, str, formats) > 0;
}

static int parse_channels(const char *str, bool in_output, audio_channel_mask_t *channels) {
    pa_assert(str);
    pa_assert(channels);

    /* Needs to be probed later */
    if (pa_streq(str, "dynamic")) {
        *channels = 0;
        return true;
    }

    if (in_output)
        return parse_list(string_conversion_table_output_channels, str, channels);
    else
        return parse_list(string_conversion_table_input_channels, str, channels);
}

static bool parse_devices(const char *str, bool in_output, audio_devices_t *devices) {
    pa_assert(str);
    pa_assert(devices);

    if (in_output)
        return parse_list(string_conversion_table_output_device, str, devices) > 0;
    else
        return parse_list(string_conversion_table_input_device, str, devices) > 0;
}

static bool parse_flags(const char *str, audio_output_flags_t *flags) {
    pa_assert(str);
    pa_assert(flags);

    return parse_list(string_conversion_table_flag, str, flags) > 0;
}

bool pa_parse_droid_audio_config(const char *filename, pa_droid_config_audio *config) {
    FILE *f;
    int n = 0;
    bool ret = true;

    enum config_loc {
        IN_ROOT = 0,
        IN_GLOBAL = 1,
        IN_HW_MODULES = 1,
        IN_MODULE = 2,
        IN_OUTPUT_INPUT = 3,
        IN_CONFIG = 4
    } loc = IN_ROOT;


    bool in_global = false;
    bool in_output = true;

    pa_droid_config_hw_module *module = NULL;
    pa_droid_config_output *output = NULL;
    pa_droid_config_input *input = NULL;

    pa_assert(filename);
    pa_assert(config);

    memset(config, 0, sizeof(pa_droid_config_audio));

    f = fopen(filename, "r");

    if (!f) {
        pa_log_info("Failed to open config file (%s): %s", filename, pa_cstrerror(errno));
        ret = false;
        goto finish;
    }

    pa_lock_fd(fileno(f), 1);

    while (!feof(f)) {
        char ln[512];
        char *d, *v, *val;

        if (!fgets(ln, sizeof(ln), f))
            break;

        n++;

        pa_strip_nl(ln);

        if (ln[0] == '#' || !*ln )
            continue;

        /* Enter section */
        if (ln[strlen(ln)-1] == '{') {
            d = ln+strspn(ln, WHITESPACE);
            v = d;
            d = v+strcspn(v, WHITESPACE);
            d[0] = '\0';

            if (!*v) {
                pa_log(__FILE__ ": [%s:%u] failed to parse line - too few words", filename, n);
                goto finish;
            }

            switch (loc) {
                case IN_ROOT:
                    if (pa_streq(v, GLOBAL_CONFIG_TAG)) {
                        in_global = true;
                        loc = IN_GLOBAL;
                    }
                    else if (pa_streq(v, AUDIO_HW_MODULE_TAG))
                        loc = IN_HW_MODULES;
                    else {
                        pa_log(__FILE__ ": [%s:%u] failed to parse line - unknown field (%s)", filename, n, v);
                        ret = false;
                        goto finish;
                    }
                    break;

                case IN_HW_MODULES:
                    module = &config->hw_modules[config->hw_modules_size];
                    config->hw_modules_size++;
                    strncpy(module->name, v, AUDIO_HARDWARE_MODULE_ID_MAX_LEN);
                    module->config = config;
                    loc = IN_MODULE;
                    pa_log_debug("config: New module: %s", module->name);
                    break;

                case IN_MODULE:
                    if (pa_streq(v, OUTPUTS_TAG)) {
                        loc = IN_OUTPUT_INPUT;
                        in_output = true;
                    } else if (pa_streq(v, INPUTS_TAG)) {
                        loc = IN_OUTPUT_INPUT;
                        in_output = false;
                    } else {
                        pa_log(__FILE__ ": [%s:%u] failed to parse line - unknown field (%s)", filename, n, v);
                        ret = false;
                        goto finish;
                    }
                    break;

                case IN_OUTPUT_INPUT:
                    pa_assert(module);

                    if (in_output) {
                        output = &module->outputs[module->outputs_size];
                        module->outputs_size++;
                        strncpy(output->name, v, AUDIO_HARDWARE_MODULE_ID_MAX_LEN);
                        output->module = module;
                        loc = IN_CONFIG;
                        pa_log_debug("config: %s: New output: %s", module->name, output->name);
                    } else {
                        input = &module->inputs[module->inputs_size];
                        module->inputs_size++;
                        strncpy(input->name, v, AUDIO_HARDWARE_MODULE_ID_MAX_LEN);
                        input->module = module;
                        loc = IN_CONFIG;
                        pa_log_debug("config: %s: New input: %s", module->name, input->name);
                    }
                    break;

                case IN_CONFIG:
                    pa_log(__FILE__ ": [%s:%u] failed to parse line - unknown field in config (%s)", filename, n, v);
                    ret = false;
                    goto finish;
            }

            continue;
        }

        /* Exit section */
        if (ln[strlen(ln)-1] == '}') {
            if (loc == IN_ROOT) {
                pa_log(__FILE__ ": [%s:%u] failed to parse line - extra closing bracket", filename, n);
                ret = false;
                goto finish;
            }

            loc--;
            if (loc == IN_MODULE) {
                if (in_output)
                    output = NULL;
                else
                    input = NULL;
            }
            if (loc == IN_ROOT)
                module = NULL;

            in_global = false;

            continue;
        }

        /* Parse global configuration */
        if (in_global) {
            bool success = false;

            d = ln+strspn(ln, WHITESPACE);
            v = d;
            d = v+strcspn(v, WHITESPACE);

            val = d+strspn(d, WHITESPACE);
            d[0] = '\0';
            d = val+strcspn(val, WHITESPACE);
            d[0] = '\0';

            if (pa_streq(v, ATTACHED_OUTPUT_DEVICES_TAG))
                success = parse_devices(val, true, &config->global_config.attached_output_devices);
            else if (pa_streq(v, DEFAULT_OUTPUT_DEVICE_TAG))
                success = parse_devices(val, true, &config->global_config.default_output_device);
            else if (pa_streq(v, ATTACHED_INPUT_DEVICES_TAG))
                success = parse_devices(val, false, &config->global_config.attached_input_devices);
            else if (pa_streq(v, SPEAKER_DRC_ENABLED_TAG))
                /* TODO - Add support for dynamic range control */
                success = true; /* Do not fail while parsing speaker_drc_enabled entry */
            else {
                pa_log(__FILE__ ": [%s:%u] failed to parse line - unknown config entry %s", filename, n, v);
                success = false;
            }

            if (!success) {
                ret = false;
                goto finish;
            }
        }

        /* Parse per-output or per-input configuration */
        if (loc == IN_CONFIG) {
            bool success = false;

            pa_assert(module);

            d = ln+strspn(ln, WHITESPACE);
            v = d;
            d = v+strcspn(v, WHITESPACE);

            val = d+strspn(d, WHITESPACE);
            d[0] = '\0';
            d = val+strcspn(val, WHITESPACE);<commit_msg>[util] speaker_drc_enabled is only from HAL v2<commit_after>/*
 * Copyright (C) 2013 Jolla Ltd.
 *
 * Contact: <NAME> <<EMAIL>>
 *
 * These PulseAudio Modules are free software; you can redistribute
 * it and/or modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation
 * version 2.1 of the License.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
 * USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <signal.h>
#include <stdio.h>

#ifdef HAVE_VALGRIND_MEMCHECK_H
#include <valgrind/memcheck.h>
#endif

#include <pulse/rtclock.h>
#include <pulse/timeval.h>
#include <pulse/volume.h>
#include <pulse/xmalloc.h>

#include <pulsecore/core.h>
#include <pulsecore/core-error.h>
#include <pulsecore/i18n.h>
#include <pulsecore/module.h>
#include <pulsecore/memchunk.h>
#include <pulsecore/sink.h>
#include <pulsecore/modargs.h>
#include <pulsecore/core-rtclock.h>
#include <pulsecore/core-util.h>
#include <pulsecore/sample-util.h>
#include <pulsecore/log.h>
#include <pulsecore/macro.h>
#include <pulsecore/thread.h>
#include <pulsecore/thread-mq.h>
#include <pulsecore/rtpoll.h>
#include <pulsecore/time-smoother.h>
#include <pulsecore/refcnt.h>
#include <pulsecore/shared.h>
#include <pulsecore/mutex.h>

#include <hardware/audio.h>
#include <hardware_legacy/audio_policy_conf.h>

#include "droid-util.h"

#include <android-version.h>

#ifndef ANDROID_VERSION_MAJOR
#error "ANDROID_VERSION_* not defined."
#endif

#if ANDROID_VERSION_MAJOR == 4 && ANDROID_VERSION_MINOR == 1
#include "droid-util-41qc.h"
#elif ANDROID_VERSION_MAJOR == 4 && ANDROID_VERSION_MINOR == 2
#include "droid-util-42.h"
#elif ANDROID_VERSION_MAJOR == 4 && ANDROID_VERSION_MINOR == 4
#include "droid-util-44.h"
#else
#error "No valid ANDROID_VERSION found."
#endif

#define CONVERT_FUNC(TABL) \
bool pa_convert_ ## TABL (uint32_t value, pa_conversion_field_t field, uint32_t *to_value) {                    \
    for (unsigned int i = 0; i < sizeof( conversion_table_ ## TABL )/(sizeof(uint32_t)*2); i++) {               \
        if ( conversion_table_ ## TABL [i][field] == value) {                                                   \
            *to_value = conversion_table_ ## TABL [i][!field];                                                  \
            return true;                                                                                        \
        }                                                                                                       \
    }                                                                                                           \
    return false;                                                                                               \
} struct __funny_extra_to_allow_semicolon

/* Creates convert_format convert_channel etc.
 * bool pa_convert_func(uint32_t value, pa_conversion_field_t field, uint32_t *to_value);
 * return true if conversion succesful */
CONVERT_FUNC(format);
CONVERT_FUNC(output_channel);
CONVERT_FUNC(input_channel);

#define DEFAULT_PRIORITY (100)

static void droid_port_free(pa_droid_port *p);

static bool string_convert_num_to_str(const struct string_conversion *list, const uint32_t value, const char **to_str) {
    pa_assert(list);
    pa_assert(to_str);

    for (unsigned int i = 0; list[i].str; i++) {
        if (list[i].value == value) {
            *to_str = list[i].str;
            return true;
        }
    }
    return false;
}

static bool string_convert_str_to_num(const struct string_conversion *list, const char *str, uint32_t *to_value) {
    pa_assert(list);
    pa_assert(str);
    pa_assert(to_value);

    for (unsigned int i = 0; list[i].str; i++) {
        if (pa_streq(list[i].str, str)) {
            *to_value = list[i].value;
            return true;
        }
    }
    return false;
}

static char *list_string(struct string_conversion *list, uint32_t flags) {
    char *str = NULL;
    char *tmp;

#ifdef HAL_V2
    if (flags & AUDIO_DEVICE_BIT_IN)
        flags &= ~AUDIO_DEVICE_BIT_IN;
#endif

    for (unsigned int i = 0; list[i].str; i++) {
#ifdef HAL_V2
        if (list[i].value & AUDIO_DEVICE_BIT_IN) {
            if (popcount(list[i].value & ~AUDIO_DEVICE_BIT_IN) != 1)
                continue;
        } else
#endif
        if (popcount(list[i].value) != 1)
            continue;

        if (flags & list[i].value) {
            if (str) {
                tmp = pa_sprintf_malloc("%s|%s", str, list[i].str);
                pa_xfree(str);
                str = tmp;
            } else {
                str = pa_sprintf_malloc("%s", list[i].str);
            }
        }
    }

    return str;
}


/* Output device */
bool pa_string_convert_output_device_num_to_str(audio_devices_t value, const char **to_str) {
    return string_convert_num_to_str(string_conversion_table_output_device, (uint32_t) value, to_str);
}

bool pa_string_convert_output_device_str_to_num(const char *str, audio_devices_t *to_value) {
    return string_convert_str_to_num(string_conversion_table_output_device, str, (uint32_t*) to_value);
}

char *pa_list_string_output_device(audio_devices_t devices) {
    return list_string(string_conversion_table_output_device, devices);
}

/* Input device */
bool pa_string_convert_input_device_num_to_str(audio_devices_t value, const char **to_str) {
    return string_convert_num_to_str(string_conversion_table_input_device, (uint32_t) value, to_str);
}

bool pa_string_convert_input_device_str_to_num(const char *str, audio_devices_t *to_value) {
    return string_convert_str_to_num(string_conversion_table_input_device, str, (uint32_t*) to_value);
}

char *pa_list_string_input_device(audio_devices_t devices) {
    return list_string(string_conversion_table_input_device, devices);
}

/* Flags */
bool pa_string_convert_flag_num_to_str(audio_output_flags_t value, const char **to_str) {
    return string_convert_num_to_str(string_conversion_table_flag, (uint32_t) value, to_str);
}

bool pa_string_convert_flag_str_to_num(const char *str, audio_output_flags_t *to_value) {
    return string_convert_str_to_num(string_conversion_table_flag, str, (uint32_t*) to_value);
}

char *pa_list_string_flags(audio_output_flags_t flags) {
    return list_string(string_conversion_table_flag, flags);
}

/* Config parser */

#define WHITESPACE "\n\r \t"

static int parse_list(const struct string_conversion *table, const char *str, uint32_t *dst) {
    int count = 0;
    char *entry;
    const char *state = NULL;

    pa_assert(table);
    pa_assert(str);
    pa_assert(dst);

    *dst = 0;

    while ((entry = pa_split(str, "|", &state))) {
        uint32_t d = 0;

        if (!string_convert_str_to_num(table, entry, &d)) {
            pa_log("Unknown entry %s", entry);
            pa_xfree(entry);
            return -1;
        }

        *dst |= d;
        count++;

        pa_xfree(entry);
    }

    return count;
}

static bool parse_sampling_rates(const char *str, uint32_t sampling_rates[32]) {
    pa_assert(str);

    char *entry;
    const char *state = NULL;

    uint32_t pos = 0;
    while ((entry = pa_split(str, "|", &state))) {
        int32_t val;

        if (pos == AUDIO_MAX_SAMPLING_RATES) {
            pa_log("Too many sample rate entries (> %d)", AUDIO_MAX_SAMPLING_RATES);
            pa_xfree(entry);
            return false;
        }

        if (pa_atoi(entry, &val) < 0) {
            pa_log("Bad sample rate value %s", entry);
            pa_xfree(entry);
            return false;
        }

        sampling_rates[pos++] = val;

        pa_xfree(entry);

    }

    sampling_rates[pos] = 0;

    return true;
}

static bool parse_formats(const char *str, audio_format_t *formats) {
    pa_assert(str);
    pa_assert(formats);

    return parse_list(string_conversion_table_format, str, formats) > 0;
}

static int parse_channels(const char *str, bool in_output, audio_channel_mask_t *channels) {
    pa_assert(str);
    pa_assert(channels);

    /* Needs to be probed later */
    if (pa_streq(str, "dynamic")) {
        *channels = 0;
        return true;
    }

    if (in_output)
        return parse_list(string_conversion_table_output_channels, str, channels);
    else
        return parse_list(string_conversion_table_input_channels, str, channels);
}

static bool parse_devices(const char *str, bool in_output, audio_devices_t *devices) {
    pa_assert(str);
    pa_assert(devices);

    if (in_output)
        return parse_list(string_conversion_table_output_device, str, devices) > 0;
    else
        return parse_list(string_conversion_table_input_device, str, devices) > 0;
}

static bool parse_flags(const char *str, audio_output_flags_t *flags) {
    pa_assert(str);
    pa_assert(flags);

    return parse_list(string_conversion_table_flag, str, flags) > 0;
}

bool pa_parse_droid_audio_config(const char *filename, pa_droid_config_audio *config) {
    FILE *f;
    int n = 0;
    bool ret = true;

    enum config_loc {
        IN_ROOT = 0,
        IN_GLOBAL = 1,
        IN_HW_MODULES = 1,
        IN_MODULE = 2,
        IN_OUTPUT_INPUT = 3,
        IN_CONFIG = 4
    } loc = IN_ROOT;


    bool in_global = false;
    bool in_output = true;

    pa_droid_config_hw_module *module = NULL;
    pa_droid_config_output *output = NULL;
    pa_droid_config_input *input = NULL;

    pa_assert(filename);
    pa_assert(config);

    memset(config, 0, sizeof(pa_droid_config_audio));

    f = fopen(filename, "r");

    if (!f) {
        pa_log_info("Failed to open config file (%s): %s", filename, pa_cstrerror(errno));
        ret = false;
        goto finish;
    }

    pa_lock_fd(fileno(f), 1);

    while (!feof(f)) {
        char ln[512];
        char *d, *v, *val;

        if (!fgets(ln, sizeof(ln), f))
            break;

        n++;

        pa_strip_nl(ln);

        if (ln[0] == '#' || !*ln )
            continue;

        /* Enter section */
        if (ln[strlen(ln)-1] == '{') {
            d = ln+strspn(ln, WHITESPACE);
            v = d;
            d = v+strcspn(v, WHITESPACE);
            d[0] = '\0';

            if (!*v) {
                pa_log(__FILE__ ": [%s:%u] failed to parse line - too few words", filename, n);
                goto finish;
            }

            switch (loc) {
                case IN_ROOT:
                    if (pa_streq(v, GLOBAL_CONFIG_TAG)) {
                        in_global = true;
                        loc = IN_GLOBAL;
                    }
                    else if (pa_streq(v, AUDIO_HW_MODULE_TAG))
                        loc = IN_HW_MODULES;
                    else {
                        pa_log(__FILE__ ": [%s:%u] failed to parse line - unknown field (%s)", filename, n, v);
                        ret = false;
                        goto finish;
                    }
                    break;

                case IN_HW_MODULES:
                    module = &config->hw_modules[config->hw_modules_size];
                    config->hw_modules_size++;
                    strncpy(module->name, v, AUDIO_HARDWARE_MODULE_ID_MAX_LEN);
                    module->config = config;
                    loc = IN_MODULE;
                    pa_log_debug("config: New module: %s", module->name);
                    break;

                case IN_MODULE:
                    if (pa_streq(v, OUTPUTS_TAG)) {
                        loc = IN_OUTPUT_INPUT;
                        in_output = true;
                    } else if (pa_streq(v, INPUTS_TAG)) {
                        loc = IN_OUTPUT_INPUT;
                        in_output = false;
                    } else {
                        pa_log(__FILE__ ": [%s:%u] failed to parse line - unknown field (%s)", filename, n, v);
                        ret = false;
                        goto finish;
                    }
                    break;

                case IN_OUTPUT_INPUT:
                    pa_assert(module);

                    if (in_output) {
                        output = &module->outputs[module->outputs_size];
                        module->outputs_size++;
                        strncpy(output->name, v, AUDIO_HARDWARE_MODULE_ID_MAX_LEN);
                        output->module = module;
                        loc = IN_CONFIG;
                        pa_log_debug("config: %s: New output: %s", module->name, output->name);
                    } else {
                        input = &module->inputs[module->inputs_size];
                        module->inputs_size++;
                        strncpy(input->name, v, AUDIO_HARDWARE_MODULE_ID_MAX_LEN);
                        input->module = module;
                        loc = IN_CONFIG;
                        pa_log_debug("config: %s: New input: %s", module->name, input->name);
                    }
                    break;

                case IN_CONFIG:
                    pa_log(__FILE__ ": [%s:%u] failed to parse line - unknown field in config (%s)", filename, n, v);
                    ret = false;
                    goto finish;
            }

            continue;
        }

        /* Exit section */
        if (ln[strlen(ln)-1] == '}') {
            if (loc == IN_ROOT) {
                pa_log(__FILE__ ": [%s:%u] failed to parse line - extra closing bracket", filename, n);
                ret = false;
                goto finish;
            }

            loc--;
            if (loc == IN_MODULE) {
                if (in_output)
                    output = NULL;
                else
                    input = NULL;
            }
            if (loc == IN_ROOT)
                module = NULL;

            in_global = false;

            continue;
        }

        /* Parse global configuration */
        if (in_global) {
            bool success = false;

            d = ln+strspn(ln, WHITESPACE);
            v = d;
            d = v+strcspn(v, WHITESPACE);

            val = d+strspn(d, WHITESPACE);
            d[0] = '\0';
            d = val+strcspn(val, WHITESPACE);
            d[0] = '\0';

            if (pa_streq(v, ATTACHED_OUTPUT_DEVICES_TAG))
                success = parse_devices(val, true, &config->global_config.attached_output_devices);
            else if (pa_streq(v, DEFAULT_OUTPUT_DEVICE_TAG))
                success = parse_devices(val, true, &config->global_config.default_output_device);
            else if (pa_streq(v, ATTACHED_INPUT_DEVICES_TAG))
                success = parse_devices(val, false, &config->global_config.attached_input_devices);
#ifdef HAL_V2
            // SPEAKER_DRC_ENABLED_TAG is only from HAL v2
            else if (pa_streq(v, SPEAKER_DRC_ENABLED_TAG))
                /* TODO - Add support for dynamic range control */
                success = true; /* Do not fail while parsing speaker_drc_enabled entry */
#endif
            else {
                pa_log(__FILE__ ": [%s:%u] failed to parse line - unknown config entry %s", filename, n, v);
                success = false;
            }

            if (!success) {
                ret = false;
                goto finish;
            }
        }

        /* Parse per-output or per-input configuration */
        if (loc == IN_CONFIG) {
            bool success = false;

            pa_assert(module);

            d = ln+strspn(ln, WHITESPACE);
            v = d;
            d = v+strcspn(v, WHITESPACE);

            val = d+strspn(d, WHITESPACE);
            d[0] = '\0';
            d = val+strcspn(val, WHITESPACE);
{'fraction_non_alphanumeric': 0.10598160963124467, 'fraction_numerical': 0.007552058646949158, 'mean_word_length': 1.9306417260857487, 'pattern_counts': {'":': 12, '<': 73, '<?xml version=': 0, '>': 125, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 42, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2812882', 'n_tokens_mistral': 10198, 'n_tokens_neox': 9712, 'n_words': 2252}