Introduction

This blog post describes on how one can set-up a Docker environment to cross-compile VISQOL for Amazon Linux 2.

What is VISQOL

VISQOL stands for Virtual Speech Quality Objective Listener and is an objective, full-reference metric for perceived audio quality.

Why Visqol?

Contrary to PESQ and POLQA, VISQOL is OpenSource and can be used under Apache-2.0 License.

Step 1 - Set Up

To pull the image, just paste docker pull amazonlinux:2 to the command line. To make sure, that we don’t download the image, each time we build the container paste docker tag amazonlinux:2 local-amazonlinux:2. Now we can build from the local image.

Step 2 - Building The Image

To build the image, paste docker build --tag="local-amazonlinux:latest" . to the command line. You could also paste docker build .. I just provided a new tag for versioning.

Step 3 - Run The Image

To run the image paste docker run -it local-amazonlinux:latest to the command line. This opens an interactive session. Make sure you delete the container, after you copied all necessary file from the container image to your host system.

Inside the container

When you started the interactive session within the container, just paste the following lines to the command line in the exact same order

python3.8 -m venv env
source env/bin/activate
python3.8 -m pip install numpy scipy protobuf wheel
bazel build :visqol -c opt
python3.8 -m pip install .

After that, we just have to copy visqol_lib_py.so to the host system via docker cp name-of-the-currently-running-container:/visqol/build/visqol/build ./build.. For that, just open a second terminal window or kill the currently running container and then copy everything to get the container name. For that just paste docker ps -a or docker ps to the command line.

Step 4 - Setting Up Folder structure

Before we try to build visqol, we have to setup our project. Just make a new folder and change into the new working directory, for example path\visqol-docker-cross-compile-folder. In this new directory the following files should be there, namely:

  • .bazelrc
  • Dockerfile
  • file_path.h
  • setup.py
  • test.py.

Additionally, to test, if we can calculate the MOS Score in the Docker environment via VISQOL, we provide two audio files: callee-dump-2023-04-04-09-42-51-dec.wav (‘degraded’ audio) and caller-dump-2023-04-04-09-42-51-enc.wav (‘reference’ audio). The next sub sections describe the content of each file. Just copy them into your folder!

Content .bazelrc

# Most of this file is to allow tensorflow to build.
# Inspired by TensorFlow serving's .bazelrc to build from the source.
# It also may be useful to refer to TensorFlow .bazelrc for more details:
# https://github.com/tensorflow/tensorflow/blob/master/.bazelrc

# Optimizations used for TF Serving release builds.
build:release --copt=-mavx
build:release --copt=-msse4.2

# Options used to build with CUDA.
build:cuda --repo_env TF_NEED_CUDA=1
build:cuda --crosstool_top=@local_config_cuda//crosstool:toolchain
build:cuda --@local_config_cuda//:enable_cuda
build:cuda --define=using_cuda=true --define=using_cuda_nvcc=true
build:cuda --action_env=TF_CUDA_COMPUTE_CAPABILITIES="sm_35,sm_50,sm_60,sm_70,sm_75,compute_80"

# Options used to build with TPU support.
build:tpu --distinct_host_configuration=false
build:tpu --define=with_tpu_support=true --define=framework_shared_object=false

# Please note that MKL on MacOS or windows is still not supported.
# If you would like to use a local MKL instead of downloading, please set the
# environment variable "TF_MKL_ROOT" every time before build.
build:mkl --define=build_with_mkl=true --define=enable_mkl=true --define=build_with_openmp=true
build:mkl --define=tensorflow_mkldnn_contraction_kernel=0

# This config option is used to enable MKL-DNN open source library only,
# without depending on MKL binary version.
build:mkl_open_source_only --define=build_with_mkl_dnn_only=true
build:mkl_open_source_only --define=build_with_mkl=true --define=enable_mkl=true
build:mkl_open_source_only --define=tensorflow_mkldnn_contraction_kernel=0

# Processor native optimizations (depends on build host capabilities).
build:nativeopt --copt=-march=native
build:nativeopt --host_copt=-march=native
build:nativeopt --copt=-O3

build --keep_going
build --verbose_failures=true
build --spawn_strategy=standalone
build --genrule_strategy=standalone

build --define=grpc_no_ares=true

# Sets the default Apple platform to macOS.
build --apple_platform_type=macos

build -c opt

# ViSQOL is overridding the default c++14 settings in tensorflow.
# TF's .bazelrc claims it c++14 required, but it is likely required
# in the sense that it is backwards compatible with c++17.
build --cxxopt=-std=c++17

# build --linkopt=-lstdc++fs instead od build --linkopt=-ldl
build --host_cxxopt=-std=c++17
build --linkopt=-lstdc++fs
build --linkopt=-ldl

build --experimental_repo_remote_exec

# Enable platform specific config (e.g. by default use --config=windows when on windows, and --config=linux when on linux)
build --enable_platform_specific_config


## Windows config
startup --windows_enable_symlinks
build:windows --enable_runfiles

# These settings below allow for compilation using MSVC
# It would be nice to use clang to compile for Windows as well,
# but the bazel instructions did not work.
build:windows --copt=/D_USE_MATH_DEFINES
build:windows --host_copt=/D_USE_MATH_DEFINES
build:windows --cxxopt=-D_HAS_DEPRECATED_RESULT_OF=1

build:windows --cxxopt=/Zc:__cplusplus
# c++20 needed in MSVC for designated initializers (llvm libc++
# and gnu stc++ provides them in c++17).
build:windows  --cxxopt=/std:c++20
build:windows  --linkopt=-ldl
build:windows  --host_cxxopt=/std:c++20

# Make sure to include as little of windows.h as possible
build:windows --copt=-DWIN32_LEAN_AND_MEAN
build:windows --host_copt=-DWIN32_LEAN_AND_MEAN
build:windows --copt=-DNOGDI
build:windows --host_copt=-DNOGDI

# MSVC (Windows): Standards-conformant preprocessor mode
# See https://docs.microsoft.com/en-us/cpp/preprocessor/preprocessor-experimental-overview
build:windows --copt=/Zc:preprocessor
build:windows --host_copt=/Zc:preprocessor

# Misc build options we need for windows according to tensorflow
build:windows --linkopt=/DEBUG
build:windows --host_linkopt=/DEBUG
build:windows --linkopt=/OPT:REF
build:windows --host_linkopt=/OPT:REF
build:windows --linkopt=/OPT:ICF
build:windows --host_linkopt=/OPT:ICF
# This is a workaround for this magic preprocessor constant/macro not existing
# in MSVC
build:windows --host_copt=-D__PRETTY_FUNCTION__=__FUNCSIG__
build:windows --copt=-D__PRETTY_FUNCTION__=__FUNCSIG__

Content Docker File

FROM local-amazonlinux:2 AS buildstage0

RUN yum install -y sudo 
RUN yum update -y && \
    yum groupinstall 'Development Tools' -y && \
    yum -y install gcc openssl-devel bzip2-devel libffi-devel wget tar which

RUN sudo amazon-linux-extras enable python3.8
RUN yum install -y python38 python38-devel

# Set Up Bazel
FROM buildstage0 as buildstage1
RUN wget https://github.com/bazelbuild/bazelisk/releases/download/v1.8.1/bazelisk-linux-amd64
RUN chmod +x bazelisk-linux-amd64
RUN sudo mv bazelisk-linux-amd64 /usr/local/bin/bazel

# Pull visqol repo
RUN git clone https://github.com/google/visqol.git

# Now we change the working directory!
WORKDIR /visqol

FROM buildstage1 as buildstage2
COPY file_path.h src/include/
COPY .bazelrc /visqol
COPY setup.py /visqol
COPY test.py /visqol
COPY callee.wav /visqol
COPY caller.wav /visqol

Content file_path.h

/*
 * Copyright 2019 Google LLC, Andrew Hines
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef VISQOL_INCLUDE_FILEPATH_H
#define VISQOL_INCLUDE_FILEPATH_H

#include <experimental/filesystem>
#include <fstream>
#include <string>

#include "absl/strings/string_view.h"

namespace Visqol {
class FilePath {
 public:
  FilePath() {}

  FilePath(const FilePath& other) { path_ = other.path_; }

  FilePath(absl::string_view path) {
    path_ = std::experimental::filesystem::path(std::string(path)).string();
  }

  const std::string Path() const { return path_; }

  bool Exists() const { return std::experimental::filesystem::exists(path_); }

  static std::string currentWorkingDir() {
    return std::experimental::filesystem::current_path().string();
  }

 private:
  std::string path_;
};

struct ReferenceDegradedPathPair {
  FilePath reference;
  FilePath degraded;
};
}  // namespace Visqol

#endif  // VISQOL_INCLUDE_FILEPATH_H

Content setup.py

import os
from setuptools import setup

os.system("bazel build -c opt //:similarity_result_py_pb2")
os.system("bazel build -c opt //:visqol_config_py_pb2")
os.system("bazel build -c opt //python:visqol_lib_py.so")

setup(
    name="visqol",
    version="3.3.3",
    url="https://github.com/google/visqol",
    description="An objective, full-reference metric for perceived audio quality.",
    packages=["visqol", "visqol.model", "visqol.pb2"],
    package_dir={
        "visqol": "bazel-bin/python",
        "visqol.model": "model",
        "visqol.pb2": "bazel-bin",
        "external": "bazel-bin/external"
    },
    package_data={
        "visqol": ["visqol_lib_py.so"],
        "visqol.model": [
            "lattice_tcditugenmeetpackhref_ls2_nl60_lr12_bs2048_learn.005_ep2400_train1_7_raw.tflite",
            "libsvm_nu_svr_model.txt"
        ]
    }
)

Content test.py

import os

import numpy as np
# import soundfile as sf
from scipy.io import wavfile

from visqol import visqol_lib_py
from visqol.pb2 import visqol_config_pb2
from visqol.pb2 import similarity_result_pb2

config = visqol_config_pb2.VisqolConfig()

mode = "speech"
if mode == "audio":
    config.audio.sample_rate = 48000
    config.options.use_speech_scoring = False
    svr_model_path = "libsvm_nu_svr_model.txt"
elif mode == "speech":
    config.audio.sample_rate = 16000
    config.options.use_speech_scoring = True
    svr_model_path = "lattice_tcditugenmeetpackhref_ls2_nl60_lr12_bs2048_learn.005_ep2400_train1_7_raw.tflite"
else:
    raise ValueError(f"Unrecognized mode: {mode}")

config.options.svr_model_path = os.path.join(
    os.path.dirname(visqol_lib_py.__file__), "model", svr_model_path)

api = visqol_lib_py.VisqolApi()
api.Create(config)

# Both files have actually a sample rate of fs=8000Hz
# So normally we would have to resample to 16000Hz 
# (based on the official documentation)
# 
# ---------------------------------------------------------
# [1] mos-result w.out 'manual' resampling (enc-dec):
# moslqo: 4.683368925457326
# 
# [2] mos-result w.out 'manual' resampling (enc-enc):
# moslqo: 4.999999943193998
#
# [3] mos-result w.out 'manual' resampling (dec-dec):
# moslqo: 4.999999943193998
reference: str = "caller.wav"
degraded: str = "callee.wav"

# Option 1:
_, ref_file_scipy = wavfile.read(reference)
_, deg_file_scipy = wavfile.read(degraded)
print(f"Type of reg_file_scipy: {ref_file_scipy.dtype}")
print(f"Type of deg_file_scipy: {deg_file_scipy.dtype}")
print(np.iinfo(np.int16).max, np.iinfo(np.int16).min)

# Here we cast to a float64 and normalize the array values such that the range lies
# between [-1, 1)
similarity_result_scipy = api.Measure(
    ref_file_scipy.astype(dtype=np.float64, copy=False), 
    deg_file_scipy.astype(dtype=np.float64, copy=False)
)

print(f"MOS w. scipy: {similarity_result_scipy.moslqo: 10}")

Important Notes:

The reason why I invoked the bazel build commands receptively the python3.8 commands manually inside the container was just for debugging purposes. You could definitely automate everything! I just wanted to make sure that I have control over each step in the building process.

Update - 20.09.2023:

I composed a script named invoke.sh, which automatically compiles, executes and duplicates all the mandatory processes. You can find the script at https://github.com/sipfront/visqol-docker-cross-compile

comments powered by Disqus