diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..5bbac4f --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: [jwinarske] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.gitignore b/.gitignore index d33f720..61c0cc4 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,7 @@ run_assistant_audio run_assistant_file run_assistant_text # Generated files -src/embedded_assistant* \ No newline at end of file +src/embedded_assistant* +build +.DS_Store + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..5ed9732 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,48 @@ +language: cpp + +os: + - linux + - osx + +compiler: + - gcc + - clang + +env: + - BUILD_TYPE=Debug + - BUILD_TYPE=Release + - BUILD_TYPE=MinSizeRel + +addons: + apt: + packages: + - libasound2-dev + +install: + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then DEPS_DIR="${TRAVIS_BUILD_DIR}/deps"; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then mkdir ${DEPS_DIR} && cd ${DEPS_DIR} && pwd; fi + + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then travis_retry wget --no-check-certificate https://github.com/Kitware/CMake/releases/download/v3.17.3/cmake-3.17.3-Linux-x86_64.tar.gz; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo "06bb006122e8e094f942bc9b2d999c92 *cmake-3.17.3-Linux-x86_64.tar.gz" > cmake_md5.txt; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then md5sum -c cmake_md5.txt; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then tar -xvf cmake-3.17.3-Linux-x86_64.tar.gz > /dev/null; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then mv cmake-3.17.3-Linux-x86_64 cmake-install; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then PATH=${DEPS_DIR}/cmake-install:${DEPS_DIR}/cmake-install/bin:$PATH; fi + + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then travis_retry wget --no-check-certificate https://golang.org/dl/go1.14.4.linux-amd64.tar.gz; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo "0ea61d0d7e05bbe454b8b19569ad86c8 *go1.14.4.linux-amd64.tar.gz" > go_md5.txt; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then md5sum -c go_md5.txt; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then tar -xzf go1.14.4.linux-amd64.tar.gz > /dev/null; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export GOROOT=${DEPS_DIR}/go; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then PATH=${DEPS_DIR}/go/bin:$PATH; fi + +before_script: + - cd ${TRAVIS_BUILD_DIR} + - mkdir build && cd build + - cmake .. + -DCMAKE_BUILD_TYPE=${BUILD_TYPE} + -DCMAKE_VERBOSE_MAKEFILE=TRUE + -DCMAKE_STAGING_PREFIX=${TRAVIS_BUILD_DIR}/dist/usr/local + +script: + - cmake --build . --config ${BUILD_TYPE} --target install diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..7b2d852 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,111 @@ +cmake_minimum_required(VERSION 3.10.2) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "MinSizeRel" CACHE STRING "Choose the type of build, options are: Debug, Release, or MinSizeRel." FORCE) + message(STATUS "CMAKE_BUILD_TYPE not set, defaulting to MinSizeRel.") +endif() + +set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${CMAKE_SOURCE_DIR}/cmake") + +if(NOT BUILD_NUMBER) + set(BUILD_NUMBER 0) +endif() +set(ASSISTANT_SDK_CPP_VERSION 0.0.1.${BUILD_NUMBER}) + +set(PACKAGE_NAME assistant-sdk-cpp) +project(${PACKAGE_NAME} VERSION "${ASSISTANT_SDK_CPP_VERSION}" LANGUAGES CXX C) + +message(STATUS "Generator .............. ${CMAKE_GENERATOR}") +message(STATUS "Build Type ............. ${CMAKE_BUILD_TYPE}") +include (target_arch) +get_target_arch(TARGET_ARCH) +message(STATUS "Target ................. ${TARGET_ARCH}") + +include(build_dependencies) + +set(CMAKE_CXX_STANDARD_REQUIRED 11) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_EXTENSIONS OFF) + +set(CMAKE_THREAD_PREFER_PTHREAD TRUE) +include(FindThreads) + +include(googleapis) + +protobuf_generate_grpc_cpp( + ${PROTO_BASE_PATH}/google/api/http.proto + ${PROTO_BASE_PATH}/google/api/annotations.proto + ${PROTO_BASE_PATH}/google/type/latlng.proto + ${PROTO_BASE_PATH}/google/assistant/embedded/v1alpha2/embedded_assistant.proto +) +set(GOOGLEAPIS_CCS + ${_gRPC_PROTO_GENS_DIR}/google/api/http.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/google/api/http.pb.cc + ${_gRPC_PROTO_GENS_DIR}/google/api/annotations.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/google/api/annotations.pb.cc + ${_gRPC_PROTO_GENS_DIR}/google/type/latlng.pb.cc + ${_gRPC_PROTO_GENS_DIR}/google/type/latlng.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/google/assistant/embedded/v1alpha2/embedded_assistant.pb.cc + ${_gRPC_PROTO_GENS_DIR}/google/assistant/embedded/v1alpha2/embedded_assistant.grpc.pb.cc +) +set_source_files_properties(${GOOGLEAPIS_CCS} PROPERTIES GENERATED TRUE) +include_directories(${_gRPC_PROTO_GENS_DIR}) + +set(CORE_SRCS src/base64_encode.cc) +set(AUDIO_INPUT_FILE_SRCS src/audio_input_file.cc) +set(ASSISTANT_AUDIO_SRCS src/run_assistant_audio.cc src/audio_pa.cc) +set(ASSISTANT_FILE_SRCS src/run_assistant_file.cc) +set(ASSISTANT_TEXT_SRCS src/run_assistant_text.cc) + +include_directories( + src + ${CMAKE_CURRENT_BINARY_DIR}/grpc_ext-prefix/src/grpc_ext/third_party/abseil-cpp +) + +if(WIN32 AND MSVC) + add_definitions(-D_WIN32_WINNT=0x600) + set(_ALLTARGETS_LIBRARIES ${_GPERF_LIBRARY}) + list(APPEND EXE_DEPS gperf_ext portaudio_ext) +elseif(APPLE) + set(_ALLTARGETS_LIBRARIES ${CMAKE_DL_LIBS} m ${CMAKE_THREAD_LIBS_INIT}) +elseif(ANDROID) + set(_ALLTARGETS_LIBRARIES ${CMAKE_DL_LIBS} m log) +else() + set(_ALLTARGETS_LIBRARIES ${CMAKE_DL_LIBS} rt m ${CMAKE_THREAD_LIBS_INIT}) +endif() + +set(LDFLAGS + grpc++ grpc address_sorting upb cares gpr + ${_ABSL_LIBRARIES} ${_PROTOBUF_LIBRARIES} + ${_ZLIB_LIBRARIES} ${_SSL_LIBRARIES} + ${_ALLTARGETS_LIBRARIES} +) + +if(NOT HAVE_GETOPT_C) + set(EXT_DEPS gperf_ext) +endif() +set(EXT_DEPS ${EXT_DEPS} portaudio_ext) + +add_executable(run_assistant_text ${GOOGLEAPIS_CCS} ${CORE_SRCS} ${ASSISTANT_TEXT_SRCS}) +target_link_libraries(run_assistant_text ${LDFLAGS}) +add_dependencies(run_assistant_text ${EXT_DEPS}) + +add_executable(run_assistant_file ${GOOGLEAPIS_CCS} ${CORE_SRCS} ${AUDIO_INPUT_FILE_SRCS} ${ASSISTANT_FILE_SRCS}) +target_link_libraries(run_assistant_file ${LDFLAGS}) +add_dependencies(run_assistant_file ${EXT_DEPS}) + +add_executable(run_assistant_audio ${GOOGLEAPIS_CCS} ${CORE_SRCS} ${AUDIO_SRCS} ${ASSISTANT_AUDIO_SRCS}) +target_link_libraries(run_assistant_audio ${LDFLAGS} ${_PORTAUDIO_LIB}) +add_dependencies(run_assistant_audio ${EXT_DEPS}) + +if(APPLE) + target_link_libraries(run_assistant_text "-framework CoreFoundation") + target_link_libraries(run_assistant_file "-framework CoreFoundation") + target_link_libraries(run_assistant_audio "-framework CoreFoundation") +endif() + +install (TARGETS run_assistant_audio run_assistant_text run_assistant_file + RUNTIME DESTINATION bin +) + +add_custom_target(uninstall "${CMAKE_COMMAND}" -P "${CMAKE_SOURCE_DIR}/cmake/make_uninstall.cmake") diff --git a/README.md b/README.md index 942ae1a..b451f12 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # Google Assistant SDK for devices - C++ +### Linux / OSX build status +[![Build Status](https://travis-ci.com/jwinarske/assistant-sdk-cpp.svg?branch=cmake)](https://travis-ci.com/jwinarske/assistant-sdk-cpp) + +### Windows build status +[![Build status](https://ci.appveyor.com/api/projects/status/op7vmksh3p0jkln4/branch/cmake?svg=true)](https://ci.appveyor.com/project/jwinarske/assistant-sdk-cpp/branch/cmake) + ## Requirements This project is officially supported on Ubuntu 14.04. Other Linux distributions may be able to run @@ -29,7 +35,6 @@ sudo rm -rf /usr/local/bin/grpc_* /usr/local/bin/protoc \ ``` git clone https://github.com/googlesamples/assistant-sdk-cpp.git cd assistant-sdk-cpp -export PROJECT_PATH=$(pwd) ``` 2. Install dependencies @@ -39,78 +44,51 @@ sudo apt-get install libasound2-dev # For ALSA sound output sudo apt-get install libcurl4-openssl-dev # CURL development library ``` -3. Build Protocol Buffer, gRPC, and Google APIs -``` -git clone -b $(curl -L https://grpc.io/release) https://github.com/grpc/grpc -GRPC_PATH=${PROJECT_PATH}/grpc -cd ${GRPC_PATH} - -git submodule update --init - -cd third_party/protobuf -./autogen.sh && ./configure && make -sudo make install -sudo ldconfig - -export LDFLAGS="$LDFLAGS -lm" -cd ${GRPC_PATH} -make clean -make -sudo make install -sudo ldconfig - -cd ${PROJECT_PATH} -git clone https://github.com/googleapis/googleapis.git -cd googleapis/ -make LANGUAGE=cpp -``` - -4. Make sure you setup environment variable `$GOOGLEAPIS_GENS_PATH` -``` -export GOOGLEAPIS_GENS_PATH=${PROJECT_PATH}/googleapis/gens -``` - -5. Build this project +3. Build this project ``` -cd ${PROJECT_PATH} -make run_assistant +mkdir build && cd build +cmake .. -DCMAKE_STAGING_PREFIX=`pwd`/dist/usr/local +make install -j ``` -6. Get credentials file. It must be an end-user's credentials. +4. Get credentials file. It must be an end-user's credentials. * Go to the [Actions Console](https://console.actions.google.com/) and register your device model, following [these instructions](https://developers.google.com/assistant/sdk/guides/library/python/embed/register-device) * Move it in this folder and rename it to `client_secret.json` * run `get_credentials.sh` in this folder. It will create the file `credentials.json`. -7. Start one of the `run_assistant` samples: +5. Start one of the `run_assistant` samples: ```bash -./run_assistant_file --input ./resources/weather_in_mountain_view.raw --output ./response.wav --credentials ./credentials.json +export LD_LIBRARY_PATH=`pwd`/dist/usr/local/lib +./dist/usr/local/bin/run_assistant_file --input ../resources/weather_in_mountain_view.raw --output ./response.wav --credentials ../credentials.json aplay ./response.wav --rate=16000 --format=S16_LE ``` On a Linux workstation, you can alternatively use ALSA for audio input: ```bash -./run_assistant_audio --credentials ./credentials.json +export LD_LIBRARY_PATH=`pwd`/dist/usr/local/lib +./dist/usr/local/bin/run_assistant_audio --credentials ../credentials.json ``` You can use a text-based query instead of audio. This allows you to continually enter text queries to the Assistant. ```bash -./run_assistant_text --credentials ./credentials.json +export LD_LIBRARY_PATH=`pwd`/dist/usr/local/lib +./dist/usr/local/bin/run_assistant_text --credentials ../credentials.json ``` This takes input from `cin`, so you can send data to the program when it starts. ```bash -echo "what time is it" | ./run_assistant_text --credentials ./credentials.json +echo "what time is it" | ./dist/usr/local/bin/run_assistant_text --credentials ../credentials.json ``` To change the locale, include a `locale` parameter: ```bash -echo "Bonjour" | ./run_assistant_text --credentials ./credentials.json --locale "fr-FR" +echo "Bonjour" | ./dist/usr/local/bin/run_assistant_text --credentials ../credentials.json --locale "fr-FR" ``` Default Assistant gRPC API endpoint is `embeddedassistant.googleapis.com`. If you want to test with a custom Assistant gRPC API endpoint, you can pass `--api_endpoint CUSTOM_API_ENDPOINT`. @@ -120,7 +98,7 @@ Default Assistant gRPC API endpoint is `embeddedassistant.googleapis.com`. If yo To get a visual output from the Assistant, provide a command to be run alongside every step of the conversation. It will execute that command along along with a provided argument of a temporary HTML file. ```bash -echo "what time is it" | ./run_assistant_text --credentials ./credentials.json --html_out google-chrome +echo "what time is it" | ./dist/usr/local/bin/run_assistant_text --credentials ../credentials.json --html_out google-chrome ``` After you enter text, it will run `google-chrome /tmp/google-assistant-cpp-screen-out.html`. diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..073f264 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,111 @@ +version: 0.1.0.{build} + +shallow_clone: true +clone_depth: 1 + +configuration: +- MinSizeRel +- Release +- Debug + +environment: + DIST_DIR: '%APPVEYOR_BUILD_FOLDER%\dist' + GOPATH: c:\go + + matrix: + + - APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2015' + VCVARSALL: '%ProgramFiles(x86)%\Microsoft Visual Studio 14.0\VC\vcvarsall.bat' + ARCHITECTURE: amd64_x86 + ARCHIVE: VS2015_%CONFIGURATION%_x86_%APPVEYOR_BUILD_NUMBER% + GENERATOR: 'NMake Makefiles' + CMAKE_ARGS: + + - APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2015' + VCVARSALL: '%ProgramFiles(x86)%\Microsoft Visual Studio 14.0\VC\vcvarsall.bat' + ARCHITECTURE: amd64 + ARCHIVE: VS2015_%CONFIGURATION%_x64_%APPVEYOR_BUILD_NUMBER% + GENERATOR: 'NMake Makefiles' + CMAKE_ARGS: + +# - APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2015' +# VCVARSALL: '%ProgramFiles(x86)%\Microsoft Visual Studio 14.0\VC\vcvarsall.bat' +# ARCHITECTURE: amd64_arm +# ARCHIVE: VS2015_%CONFIGURATION%_ARM_%APPVEYOR_BUILD_NUMBER% +# GENERATOR: 'NMake Makefiles' +# CMAKE_ARGS: + + +# - APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2017' +# VCVARSALL: '%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat' +# ARCHITECTURE: amd64_arm +# ARCHIVE: VS2017_%CONFIGURATION%_ARM_%APPVEYOR_BUILD_NUMBER% +# GENERATOR: 'NMake Makefiles' +# CMAKE_ARGS: + +# - APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2017' +# VCVARSALL: '%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat' +# ARCHITECTURE: amd64_arm64 +# ARCHIVE: VS2017_%CONFIGURATION%_ARM64_%APPVEYOR_BUILD_NUMBER% +# GENERATOR: 'NMake Makefiles' +# CMAKE_ARGS: + + - APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2017' + VCVARSALL: '%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat' + ARCHITECTURE: amd64_x86 + ARCHIVE: VS2017_%CONFIGURATION%_x86_%APPVEYOR_BUILD_NUMBER% + GENERATOR: 'NMake Makefiles' + CMAKE_ARGS: + + - APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2017' + VCVARSALL: '%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat' + ARCHITECTURE: amd64 + ARCHIVE: VS2017_%CONFIGURATION%_x86_%APPVEYOR_BUILD_NUMBER% + GENERATOR: 'NMake Makefiles' + CMAKE_ARGS: + +init: + - echo BUILD_NUMBER=%APPVEYOR_BUILD_NUMBER% + +stack: + - go 1.14.4 + +install: + - echo Downloading Yasm... + - mkdir windows_build_tools + - powershell -Command "(New-Object Net.WebClient).DownloadFile('https://www.tortall.net/projects/yasm/releases/yasm-1.3.0-win64.exe', 'windows_build_tools\yasm.exe')" + - set PATH=%cd%\windows_build_tools;%PATH% + - cmake --version + - go version + - yasm --version + +build: + parallel: true + +build_script: + + - if exist "%VCVARSALL%" ( call "%VCVARSALL%" %ARCHITECTURE% ) + + - cd %APPVEYOR_BUILD_FOLDER% + - mkdir build + - cd build + + - cmake %CMAKE_TOOLCHAIN_ARGS% -G"%GENERATOR%" + -DCMAKE_VERBOSE_MAKEFILE=TRUE + -DCMAKE_BUILD_TYPE=%CONFIGURATION% + -DCMAKE_ASM_NASM_COMPILER="yasm.exe" + -DCMAKE_STAGING_PREFIX="%DIST_DIR%\%APPVEYOR_BUILD_WORKER_IMAGE%\${CMAKE_INSTALL_PREFIX}" + -DBUILD_NUMBER=%APPVEYOR_BUILD_NUMBER% %CMAKE_ARGS% + .. + + - set CL=/MP + - cmake --build . --config %CONFIGURATION% --target all + +after_build: + - cd %DIST_DIR% + - 7z a -tzip %ARCHIVE%.zip "%APPVEYOR_BUILD_WORKER_IMAGE%" + - certutil -hashfile %ARCHIVE%.zip MD5 > %ARCHIVE%.md5 + +artifacts: + - path: dist\$(ARCHIVE).zip + - path: dist\$(ARCHIVE).md5 diff --git a/cmake/build_dependencies.cmake b/cmake/build_dependencies.cmake new file mode 100644 index 0000000..4494e02 --- /dev/null +++ b/cmake/build_dependencies.cmake @@ -0,0 +1,194 @@ +include (ExternalProject) +include(CheckFunctionExists) + +if(NOT GRPC_VERSION) + set(GRPC_VERSION v1.30.1) +endif() + +if(ANDROID) + set(GRPC_ANDROID_ARGS + -DRUN_HAVE_STD_REGEX=0 + -DRUN_HAVE_POSIX_REGEX=0 + -DRUN_HAVE_STEADY_CLOCK=0 + -DCMAKE_EXE_LINKER_FLAGS=-llog + ) +endif() + + +set(GRPC_SRC_PATH ${CMAKE_CURRENT_BINARY_DIR}/grpc_ext-prefix/src/grpc_ext) + +if(NOT MSVC) + set(gRPC_ZLIB_PROVIDER package) +else() + set(gRPC_ZLIB_PROVIDER module) +endif() + +ExternalProject_Add(grpc_ext + GIT_REPOSITORY "https://github.com/grpc/grpc" + GIT_TAG ${GRPC_VERSION} + GIT_SHALLOW 1 + PATCH_COMMAND "" + UPDATE_COMMAND "" + BUILD_IN_SOURCE 0 + CMAKE_ARGS + -DANDROID_PLATFORM=${ANDROID_PLATFORM} + -DANDROID_ABI=${ANDROID_ABI} + -DANDROID_STL=${ANDROID_STL} + -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} + -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} + -DCMAKE_STAGING_PREFIX=${CMAKE_STAGING_PREFIX} + -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_VERBOSE_MAKEFILE=${CMAKE_VERBOSE_MAKEFILE} + -DCMAKE_ASM_NASM_COMPILER=${CMAKE_ASM_NASM_COMPILER} + -DgRPC_INSTALL=ON + -DgRPC_BUILD_CODEGEN=ON + -DgRPC_BUILD_CSHARP_EXT=ON + -DgRPC_ZLIB_PROVIDER=${gRPC_ZLIB_PROVIDER} + ${GRPC_ANDROID_ARGS} +) +set(_GRPC_SRC_PATH ${CMAKE_BINARY_DIR}/grpc_ext-prefix/src/grpc_ext) + +set(_GRPC_LIBRARIES_DIR ${_GRPC_SRC_PATH}-build) + +set(_ABSL_LIBRARIES + absl_bad_optional_access + absl_str_format_internal + absl_time + absl_time_zone + absl_civil_time + absl_strings + absl_strings_internal + absl_throw_delegate + absl_int128 + absl_base + absl_spinlock_wait + absl_raw_logging_internal + absl_log_severity + absl_dynamic_annotations +) + +set(_SSL_LIBRARIES_DIRS + ${_GRPC_SRC_PATH}-build/third_party/boringssl/crypto + ${_GRPC_SRC_PATH}-build/third_party/boringssl/ssl +) +set(_SSL_LIBRARIES ssl crypto) + +if(NOT MSVC) + set(_ZLIB_LIBRARIES z) +else() + if(${CMAKE_BUILD_TYPE} STREQUAL "Debug") + set(_ZLIB_LIBRARIES zlibstaticd) + else() + set(_ZLIB_LIBRARIES zlibstatic) + endif() +endif() + +if(${CMAKE_BUILD_TYPE} STREQUAL "Debug") + set(PROTOBUF_LIB protobufd) +else() + set(PROTOBUF_LIB protobuf) +endif() +if(MSVC) + set(PROTOBUF_LIB lib${PROTOBUF_LIB}) +endif() +set(_PROTOBUF_LIBRARIES ${PROTOBUF_LIB}) + + +if(MSVC) + if(${TARGET_ARCH} STREQUAL "x86_64") + set(_PORTAUDIO_LIB portaudio_x64) + else() + set(_PORTAUDIO_LIB portaudio_${TARGET_ARCH}) + endif() +else() + set(_PORTAUDIO_LIB portaudio) +endif() + +if(NOT ANDROID) + ExternalProject_Add(portaudio_ext + GIT_REPOSITORY https://git.assembla.com/portaudio.git + GIT_TAG master + GIT_SHALLOW 1 + UPDATE_COMMAND "" + BUILD_IN_SOURCE 0 + CMAKE_ARGS + -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} + -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} + -DCMAKE_STAGING_PREFIX=${CMAKE_STAGING_PREFIX} + -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_VERBOSE_MAKEFILE=${CMAKE_VERBOSE_MAKEFILE} + -DPA_ENABLE_DEBUG_OUTPUT=OFF + -DPA_BUILD_EXAMPLES=ON + -DPA_BUILD_TESTS=ON + -DPA_DISABLE_INSTALL=OFF + ) +else() + ExternalProject_Add(portaudio_ext + GIT_REPOSITORY https://github.com/Gundersanne/portaudio_opensles.git + GIT_TAG master + GIT_SHALLOW 1 + UPDATE_COMMAND "" + BUILD_IN_SOURCE 0 + CMAKE_ARGS + -DANDROID_PLATFORM=${ANDROID_PLATFORM} + -DANDROID_ABI=${ANDROID_ABI} + -DANDROID_STL=${ANDROID_STL} + -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} + -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} + -DCMAKE_STAGING_PREFIX=${CMAKE_STAGING_PREFIX} + -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_VERBOSE_MAKEFILE=${CMAKE_VERBOSE_MAKEFILE} + -DPA_ENABLE_DEBUG_OUTPUT=OFF + -DPA_BUILD_EXAMPLES=OFF + -DPA_BUILD_TESTS=OFF + -DPA_DISABLE_INSTALL=OFF + ) +endif() + + +check_function_exists(getopt HAVE_GETOPT_C) +if(NOT HAVE_GETOPT_C) + + SET(GPERF_RELEASE 3.1) + ExternalProject_Add(gperf_ext + URL https://github.com/jwinarske/gperf/archive/cmake.zip + URL_HASH "SHA256=2c3ff3ce41f4a97b6bbc432372d3cb738afcf7535705e7c2977c4f6af1c7ff19" + UPDATE_COMMAND "" + BUILD_IN_SOURCE 0 + CMAKE_ARGS + -DANDROID_PLATFORM=${ANDROID_PLATFORM} + -DANDROID_ABI=${ANDROID_ABI} + -DANDROID_STL=${ANDROID_STL} + -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} + -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} + -DCMAKE_STAGING_PREFIX=${CMAKE_STAGING_PREFIX} + -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_VERBOSE_MAKEFILE=${CMAKE_VERBOSE_MAKEFILE} + ) + if(${CMAKE_BUILD_TYPE} STREQUAL "Debug") + set(_GPERF_LIBRARY gpd) + else() + set(_GPERF_LIBRARY gp) + endif() +endif() + + +include_directories( + ${CMAKE_STAGING_PREFIX}/include + ${_GRPC_SRC_PATH} +) + +link_directories( + ${CMAKE_STAGING_PREFIX}/lib + ${CMAKE_STAGING_PREFIX}/lib/static + ${_GRPC_LIBRARIES_DIR} + ${_SSL_LIBRARIES_DIRS} +) + +set(Protobuf_PROTOC_EXECUTABLE ${CMAKE_STAGING_PREFIX}/bin/protoc${CMAKE_EXECUTABLE_SUFFIX}) + +set(PROTO_BASE_PATH ${CMAKE_CURRENT_BINARY_DIR}/grpc_ext-prefix/src/grpc_ext/third_party/googleapis) diff --git a/cmake/googleapis.cmake b/cmake/googleapis.cmake new file mode 100644 index 0000000..9edcaca --- /dev/null +++ b/cmake/googleapis.cmake @@ -0,0 +1,72 @@ +# Set grpc plugin +if(NOT _gRPC_CPP_PLUGIN) + set(_gRPC_CPP_PLUGIN ${CMAKE_STAGING_PREFIX}/bin/grpc_cpp_plugin${CMAKE_EXECUTABLE_SUFFIX}) +endif() + +if(NOT _gRPC_PROTO_GENS_DIR) + set(_gRPC_PROTO_GENS_DIR ${CMAKE_CURRENT_BINARY_DIR}/gens) +endif() + +if(NOT _gRPC_PROTOBUF_WELLKNOWN_INCLUDE_DIR) + set(_gRPC_PROTOBUF_WELLKNOWN_INCLUDE_DIR ${CMAKE_STAGING_PREFIX}/include) +endif() + +# Normalize all paths +file(TO_NATIVE_PATH ${_gRPC_PROTOBUF_WELLKNOWN_INCLUDE_DIR} NATIVE_gRPC_PROTOBUF_WELLKNOWN_INCLUDE_DIR) +file(TO_NATIVE_PATH ${_gRPC_CPP_PLUGIN} NATIVE_gRPC_CPP_PLUGIN) +file(TO_NATIVE_PATH ${_gRPC_PROTO_GENS_DIR} NATIVE_gRPC_PROTO_GENS_DIR) +file(TO_NATIVE_PATH ${PROTO_BASE_PATH} NATIVE_PROTO_BASE_PATH) + +# Create directory for generated .proto files +file(MAKE_DIRECTORY ${NATIVE_gRPC_PROTO_GENS_DIR}) + +# protobuf_generate_grpc_cpp +# -------------------------- +# +# Add custom commands to process ``.proto`` files to C++ using protoc and +# GRPC plugin:: +# +# protobuf_generate_grpc_cpp [...] +# +# ``ARGN`` +# ``.proto`` files +# +function(protobuf_generate_grpc_cpp) + if(NOT ARGN) + message(SEND_ERROR "Error: PROTOBUF_GENERATE_GRPC_CPP() called without any proto files") + return() + endif() + + set(_protobuf_include_path -I . -I ${NATIVE_gRPC_PROTOBUF_WELLKNOWN_INCLUDE_DIR}) + + foreach(FIL ${ARGN}) + + get_filename_component(ABS_FIL ${FIL} ABSOLUTE) + get_filename_component(FIL_WE ${FIL} NAME_WE) + file(RELATIVE_PATH REL_FIL ${PROTO_BASE_PATH} ${ABS_FIL}) + get_filename_component(REL_DIR ${REL_FIL} DIRECTORY) + + set(RELFIL_WE ${_gRPC_PROTO_GENS_DIR}/${REL_DIR}/${FIL_WE}) + + # Normalize paths + file(TO_NATIVE_PATH ${RELFIL_WE} NATIVE_RELFIL_WE) + file(TO_NATIVE_PATH ${REL_FIL} NATIVE_REL_FIL) + + add_custom_command( + OUTPUT ${NATIVE_RELFIL_WE}.grpc.pb.cc + ${NATIVE_RELFIL_WE}.grpc.pb.h + ${NATIVE_RELFIL_WE}_mock.grpc.pb.h + ${NATIVE_RELFIL_WE}.pb.cc + ${NATIVE_RELFIL_WE}.pb.h + COMMAND ${Protobuf_PROTOC_EXECUTABLE} + ARGS --grpc_out=generate_mock_code=true:${NATIVE_gRPC_PROTO_GENS_DIR} + --plugin=protoc-gen-grpc=${NATIVE_gRPC_CPP_PLUGIN} + --cpp_out=${NATIVE_gRPC_PROTO_GENS_DIR} + ${_protobuf_include_path} + ${NATIVE_REL_FIL} + DEPENDS ${ABS_FIL} grpc_ext + WORKING_DIRECTORY ${PROTO_BASE_PATH} + COMMENT "Running gRPC C++ protocol buffer compiler on ${FIL}" + VERBATIM) + endforeach() +endfunction() diff --git a/cmake/make_uninstall.cmake b/cmake/make_uninstall.cmake new file mode 100644 index 0000000..bd8504e --- /dev/null +++ b/cmake/make_uninstall.cmake @@ -0,0 +1,16 @@ +if(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt") + + file(READ "${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt" install_manifest) + string(REGEX REPLACE "[\r\n]" ";" install_manifest "${install_manifest}") + + foreach(file ${install_manifest}) + if(EXISTS "${file}") + message(STATUS "Uninstalling ${file}") + file(REMOVE ${file}) + if (EXISTS "${file}") + message(FATAL_ERROR "Problem removing ${file}, check your permissions") + endif() + endif() + endforeach() + +endif() \ No newline at end of file diff --git a/cmake/target_arch.cmake b/cmake/target_arch.cmake new file mode 100644 index 0000000..38397c6 --- /dev/null +++ b/cmake/target_arch.cmake @@ -0,0 +1,36 @@ + +set(TARGET_ARCH_DETECT_CODE " + + #if defined(_M_ARM) || defined(__arm__) + #error cmake_arch ARM + #elif defined(_M_ARM64) || defined(__aarch64__) + #error cmake_arch ARM64 + #elif defined(_M_AMD64) || defined(__x86_64__) + #error cmake_arch x86_64 + #elif defined(_M_X64) + #error cmake_arch x64 + #elif defined(_M_IX86) || defined(__i386__) + #error cmake_arch x86 + #else + #error cmake_arch unknown + #endif +") + +function(get_target_arch out) + + file(WRITE + "${CMAKE_BINARY_DIR}/target_arch_detect.c" + "${TARGET_ARCH_DETECT_CODE}") + + try_run( + run_result_unused compile_result_unused + "${CMAKE_BINARY_DIR}" "${CMAKE_BINARY_DIR}/target_arch_detect.c" + COMPILE_OUTPUT_VARIABLE TARGET_ARCH) + + # parse compiler output + string(REGEX MATCH "cmake_arch ([a-zA-Z0-9_]+)" TARGET_ARCH "${TARGET_ARCH}") + string(REPLACE "cmake_arch " "" TARGET_ARCH "${TARGET_ARCH}") + + set(${out} "${TARGET_ARCH}" PARENT_SCOPE) + +endfunction() diff --git a/src/audio_input_alsa.cc b/src/audio_input_alsa.cc deleted file mode 100644 index 8636132..0000000 --- a/src/audio_input_alsa.cc +++ /dev/null @@ -1,116 +0,0 @@ -/* -Copyright 2017 Google Inc. - -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 - - https://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. -*/ - -#include "audio_input_alsa.h" - -#include - -#include - -std::unique_ptr AudioInputALSA::GetBackgroundThread() { - return std::unique_ptr(new std::thread([this]() { - // Initialize. - snd_pcm_t* pcm_handle; - int pcm_open_ret = - snd_pcm_open(&pcm_handle, "default", SND_PCM_STREAM_CAPTURE, 0); - if (pcm_open_ret < 0) { - std::cerr << "AudioInputALSA snd_pcm_open returned " << pcm_open_ret - << std::endl; - return; - } - - int pcm_nonblock_ret = snd_pcm_nonblock(pcm_handle, SND_PCM_NONBLOCK); - if (pcm_nonblock_ret < 0) { - std::cerr << "AudioInputALSA snd_pcm_nonblock returned " - << pcm_nonblock_ret << std::endl; - return; - } - - snd_pcm_hw_params_t* pcm_params; - int malloc_param_ret = snd_pcm_hw_params_malloc(&pcm_params); - if (malloc_param_ret < 0) { - std::cerr << "AudioInputALSA snd_pcm_hw_params_malloc returned " - << malloc_param_ret << std::endl; - return; - } - - snd_pcm_hw_params_any(pcm_handle, pcm_params); - int set_param_ret = snd_pcm_hw_params_set_access( - pcm_handle, pcm_params, SND_PCM_ACCESS_RW_INTERLEAVED); - if (set_param_ret < 0) { - std::cerr << "AudioInputALSA snd_pcm_hw_params_set_access returned " - << set_param_ret << std::endl; - return; - } - - set_param_ret = snd_pcm_hw_params_set_format(pcm_handle, pcm_params, - SND_PCM_FORMAT_S16_LE); - if (set_param_ret < 0) { - std::cerr << "AudioInputALSA snd_pcm_hw_params_set_format returned " - << set_param_ret << std::endl; - return; - } - - set_param_ret = snd_pcm_hw_params_set_channels(pcm_handle, pcm_params, 1); - if (set_param_ret < 0) { - std::cerr << "AudioInputALSA snd_pcm_hw_params_set_channels returned " - << set_param_ret << std::endl; - return; - } - - unsigned int rate = 16000; - set_param_ret = - snd_pcm_hw_params_set_rate_near(pcm_handle, pcm_params, &rate, nullptr); - if (set_param_ret < 0) { - std::cerr << "AudioInputALSA snd_pcm_hw_params_set_rate_near returned " - << set_param_ret << std::endl; - return; - } - - set_param_ret = snd_pcm_hw_params(pcm_handle, pcm_params); - if (set_param_ret < 0) { - std::cerr << "AudioInputALSA snd_pcm_hw_params returned " << set_param_ret - << std::endl; - return; - } - snd_pcm_hw_params_free(pcm_params); - - while (is_running_) { - std::shared_ptr> audio_data( - new std::vector(kFramesPerPacket * kBytesPerFrame)); - int pcm_read_ret = - snd_pcm_readi(pcm_handle, &(*audio_data.get())[0], kFramesPerPacket); - if (pcm_read_ret == -EAGAIN) { - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - } else if (pcm_read_ret < 0) { - std::cerr << "AudioInputALSA snd_pcm_readi returned " << pcm_read_ret - << std::endl; - break; - } else if (pcm_read_ret > 0) { - audio_data->resize(kBytesPerFrame * pcm_read_ret); - for (auto& listener : data_listeners_) { - listener(audio_data); - } - } - } - - // Finalize. - snd_pcm_close(pcm_handle); - - // Call |OnStop|. - OnStop(); - })); -} diff --git a/src/audio_input_file.cc b/src/audio_input_file.cc index a4cc0f0..7ad7b80 100644 --- a/src/audio_input_file.cc +++ b/src/audio_input_file.cc @@ -18,11 +18,12 @@ limitations under the License. #include #include +#include std::unique_ptr AudioInputFile::GetBackgroundThread() { return std::unique_ptr(new std::thread([this]() { // Initialize. - std::ifstream file_stream(file_path_); + std::ifstream file_stream(file_path_, std::ifstream::binary); if (!file_stream) { std::cerr << "AudioInputFile cannot open file " << file_path_ << std::endl; diff --git a/src/audio_output_alsa.cc b/src/audio_output_alsa.cc deleted file mode 100644 index 009d163..0000000 --- a/src/audio_output_alsa.cc +++ /dev/null @@ -1,140 +0,0 @@ -/* -Copyright 2017 Google Inc. - -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 - - https://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. -*/ - -#include "audio_output_alsa.h" - -#include - -#include - -bool AudioOutputALSA::Start() { - std::unique_lock lock(isRunningMutex); - - if (isRunning) { - return true; - } - - snd_pcm_t* pcm_handle; - int pcm_open_ret = - snd_pcm_open(&pcm_handle, "default", SND_PCM_STREAM_PLAYBACK, 0); - if (pcm_open_ret < 0) { - std::cerr << "AudioOutputALSA snd_pcm_open returned " << pcm_open_ret - << std::endl; - return false; - } - - snd_pcm_hw_params_t* pcm_params; - int malloc_param_ret = snd_pcm_hw_params_malloc(&pcm_params); - if (malloc_param_ret < 0) { - std::cerr << "AudioOutputALSA snd_pcm_hw_params_malloc returned " - << malloc_param_ret << std::endl; - return false; - } - - snd_pcm_hw_params_any(pcm_handle, pcm_params); - int set_param_ret = snd_pcm_hw_params_set_access( - pcm_handle, pcm_params, SND_PCM_ACCESS_RW_INTERLEAVED); - if (set_param_ret < 0) { - std::cerr << "AudioOutputALSA snd_pcm_hw_params_set_access returned " - << set_param_ret << std::endl; - return false; - } - - set_param_ret = snd_pcm_hw_params_set_format(pcm_handle, pcm_params, - SND_PCM_FORMAT_S16_LE); - if (set_param_ret < 0) { - std::cerr << "AudioOutputALSA snd_pcm_hw_params_set_format returned " - << set_param_ret << std::endl; - return false; - } - - set_param_ret = snd_pcm_hw_params_set_channels(pcm_handle, pcm_params, 1); - if (set_param_ret < 0) { - std::cerr << "AudioOutputALSA snd_pcm_hw_params_set_channels returned " - << set_param_ret << std::endl; - return false; - } - - unsigned int rate = 16000; - set_param_ret = - snd_pcm_hw_params_set_rate_near(pcm_handle, pcm_params, &rate, nullptr); - if (set_param_ret < 0) { - std::cerr << "AudioOutputALSA snd_pcm_hw_params_set_rate_near returned " - << set_param_ret << std::endl; - return false; - } - - set_param_ret = snd_pcm_hw_params(pcm_handle, pcm_params); - if (set_param_ret < 0) { - std::cerr << "AudioOutputALSA snd_pcm_hw_params returned " << set_param_ret - << std::endl; - return false; - } - - snd_pcm_hw_params_free(pcm_params); - - isRunning = true; - alsaThread.reset(new std::thread([this, pcm_handle]() { - while (isRunning) { - std::unique_lock lock(audioDataMutex); - - while (audioData.size() == 0 && isRunning) { - audioDataCv.wait_for(lock, std::chrono::milliseconds(100)); - } - - if (!isRunning) { - break; - } - - std::shared_ptr> data = audioData[0]; - audioData.erase(audioData.begin()); - // 1 channel, S16LE, so 2 bytes each frame. - int frames = data->size() / 2; - int pcm_write_ret = snd_pcm_writei(pcm_handle, &(*data.get())[0], frames); - if (pcm_write_ret < 0) { - int pcm_recover_ret = snd_pcm_recover(pcm_handle, pcm_write_ret, 0); - if (pcm_recover_ret < 0) { - std::cerr << "AudioOutputALSA snd_pcm_recover returns " - << pcm_recover_ret << std::endl; - break; - } - } - } - - // Wait for all data to be consumed. - snd_pcm_drain(pcm_handle); - snd_pcm_close(pcm_handle); - })); - return true; -} - -void AudioOutputALSA::Stop() { - std::unique_lock lick(isRunningMutex); - - if (!isRunning) { - return; - } - - isRunning = false; - alsaThread->join(); - alsaThread.reset(nullptr); -} - -void AudioOutputALSA::Send(std::shared_ptr> data) { - std::unique_lock lock(audioDataMutex); - audioData.push_back(data); - audioDataCv.notify_one(); -} diff --git a/src/audio_output_alsa.h b/src/audio_output_alsa.h deleted file mode 100644 index 4bacc98..0000000 --- a/src/audio_output_alsa.h +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright 2017 Google Inc. - -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 - - https://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. -*/ - -#include -#include -#include -#include - -// Audio output using ALSA. -class AudioOutputALSA { - public: - bool Start(); - - void Stop(); - - void Send(std::shared_ptr> data); - - friend void fill_audio(void* userdata, unsigned char* stream, int len); - - private: - std::vector>> audioData; - std::mutex audioDataMutex; - std::condition_variable audioDataCv; - std::unique_ptr alsaThread; - bool isRunning = false; - std::mutex isRunningMutex; -}; diff --git a/src/audio_pa.cc b/src/audio_pa.cc new file mode 100644 index 0000000..900f576 --- /dev/null +++ b/src/audio_pa.cc @@ -0,0 +1,142 @@ +/* +Copyright 2017 Google Inc. + +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 + + https://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. +*/ + +#include "audio_pa.h" + +#include +#include + +std::unique_ptr AudioPA::GetBackgroundThread() { + return std::unique_ptr(new std::thread([this]() { + + PaError err; + + std::shared_ptr> audio_data( + new std::vector); + + const size_t chunk_size = kFramesPerBuffer * kInputNumChannels * sizeof(short); + + audio_data->resize(chunk_size); + + err = Pa_StartStream( stream ); + if (err != paNoError) goto error; + + while (is_running_) { + + err = Pa_ReadStream(stream, &(*audio_data.get())[0], kFramesPerBuffer); + if(err == paNoError) { + for (auto& listener : data_listeners_) { + listener(audio_data); + } + } + else if(err != paInputOverflowed) { + std::cerr << "Pa_ReadStream error: "; + goto error; + } + } + + // Call |OnStop|. + OnStop(); + return; + +error: + std::cerr << Pa_GetErrorText(err) << std::endl; + + // Call |OnStop|. + OnStop(); + })); +} + + +int AudioPA::Open() { + + PaError err; + PaDeviceIndex devInput, devOutput; + const PaDeviceInfo* inputInfo; + const PaDeviceInfo* outputInfo; + PaStreamParameters inputParameters; + PaStreamParameters outputParameters; + + err = Pa_Initialize(); + if(err != paNoError) goto error; + + devInput = Pa_GetDefaultInputDevice(); + inputInfo = Pa_GetDeviceInfo( devInput ); + std::cout << "Input device # " << devInput << std::endl; + std::cout << " Name: " << inputInfo->name << std::endl; + + inputParameters.device = devInput; + inputParameters.channelCount = kInputNumChannels; + inputParameters.sampleFormat = kSampleFormat; + inputParameters.suggestedLatency = inputInfo->defaultLowInputLatency; + inputParameters.hostApiSpecificStreamInfo = NULL; + + devOutput = Pa_GetDefaultOutputDevice(); + outputInfo = Pa_GetDeviceInfo( devOutput ); + std::cout << "Output device # " << devOutput << std::endl; + std::cout << " Name: " << outputInfo->name << std::endl; + + outputParameters.device = devOutput; + outputParameters.channelCount = kOutputNumChannels; + outputParameters.sampleFormat = kSampleFormat; + outputParameters.suggestedLatency = outputInfo->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_IsFormatSupported( &inputParameters, &outputParameters, kSampleRate); + if( err != paNoError ) goto error; + + err = Pa_OpenStream( + &stream, + &inputParameters, + &outputParameters, + kSampleRate, + kFramesPerBuffer, + paClipOff, + NULL, + NULL ); + if( err != paNoError ) goto error; + + return err; + +error: + if( stream ) { + Pa_AbortStream( stream ); + Pa_CloseStream( stream ); + } + Pa_Terminate(); + std::cerr << Pa_GetErrorText(err) << std::endl; + return (int)err; +} + +int AudioPA::Write( const void *buffer, unsigned long frames ) { + return (int)Pa_WriteStream( stream, buffer, frames ); +} + +void AudioPA::Stop() { + + if( stream ) { + Pa_StopStream( stream ); + } +} + +void AudioPA::Close() { + + if( stream ) { + Pa_AbortStream( stream ); + Pa_CloseStream( stream ); + } + Pa_Terminate(); +} diff --git a/src/audio_input_alsa.h b/src/audio_pa.h similarity index 60% rename from src/audio_input_alsa.h rename to src/audio_pa.h index 68f9180..409267b 100644 --- a/src/audio_input_alsa.h +++ b/src/audio_pa.h @@ -16,15 +16,29 @@ limitations under the License. #include "audio_input.h" -class AudioInputALSA : public AudioInput { +#include + +class AudioPA : public AudioInput { public: - ~AudioInputALSA() override {} + ~AudioPA() override {} virtual std::unique_ptr GetBackgroundThread() override; + int Open(); + int Write(const void *buffer, unsigned long frames); + void Stop(); + void Close(); + private: + + static constexpr int kInputNumChannels = 1; + static constexpr int kOutputNumChannels = 1; + // For 16000Hz, it's about 0.1 second. - static constexpr int kFramesPerPacket = 1600; - // 1 channel, S16LE, so 2 bytes each frame. - static constexpr int kBytesPerFrame = 2; + static constexpr double kSampleRate = 16000; + static constexpr PaSampleFormat kSampleFormat = paInt16; + static constexpr unsigned long kFramesPerBuffer = 512; + + PaStream* stream; + }; diff --git a/src/base64_encode.cc b/src/base64_encode.cc index 888578d..f3819a5 100644 --- a/src/base64_encode.cc +++ b/src/base64_encode.cc @@ -22,7 +22,7 @@ std::string base64_encode(const std::string &in) { std::string out; int val = 0, valb = -6; - for (u_char c : in) { + for (uint8_t c : in) { val = (val << 8) + c; valb += 8; while (valb >= 0) { diff --git a/src/json_util.cc b/src/json_util.cc deleted file mode 100644 index 1911c92..0000000 --- a/src/json_util.cc +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright 2017 Google Inc. - -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 - - https://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. -*/ - -#include "json_util.h" -#include "scope_exit.h" - -extern "C" { -#include -} - -#include -#include - -grpc_json* GetJsonValueOrNullFromDict(grpc_json* dict_node, const char* key) { - if (dict_node->type != GRPC_JSON_OBJECT) { - return nullptr; - } - - grpc_json* child = dict_node->child; - while (child != nullptr) { - if (child->key != nullptr && strcmp(child->key, key) == 0) { - return child; - } - child = child->next; - } - return nullptr; -} - -grpc_json* GetJsonValueOrNullFromArray(grpc_json* array_node, int index) { - if (array_node->type != GRPC_JSON_ARRAY) { - return nullptr; - } - - grpc_json* child = array_node->child; - while (child != nullptr && index != 0) { - child = child->next; - index--; - } - return child; -} diff --git a/src/json_util.h b/src/json_util.h deleted file mode 100644 index 3b6ba78..0000000 --- a/src/json_util.h +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright 2017 Google Inc. - -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 - - https://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 JSON_UTIL_H -#define JSON_UTIL_H - -#include -#include - -#endif diff --git a/src/json_util_test.cc b/src/json_util_test.cc deleted file mode 100644 index f50f0bf..0000000 --- a/src/json_util_test.cc +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright 2017 Google Inc. - -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 - - https://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. -*/ - -#include "json_util.h" - -#include - -bool check_result(const std::string& input_json, - std::unique_ptr intended_result) { - std::unique_ptr result = GetCustomResponseOrNull(input_json); - return (intended_result == nullptr && result == nullptr) || - (intended_result != nullptr && result != nullptr && - *intended_result == *result); -} - -int main() { - std::string invalid_json = ""; - std::unique_ptr intended_result(nullptr); - if (!check_result(invalid_json, std::move(intended_result))) { - std::cerr << "Test failed for invalid JSON" << std::endl; - return 1; - } - - std::string incomplete_json = "{}"; - intended_result.reset(nullptr); - if (!check_result(incomplete_json, std::move(intended_result))) { - std::cerr << "Test failed for incomplete JSON" << std::endl; - return 1; - } - - std::cerr << "Test passed" << std::endl; -} diff --git a/src/run_assistant_audio.cc b/src/run_assistant_audio.cc index aee16b9..b33b88f 100644 --- a/src/run_assistant_audio.cc +++ b/src/run_assistant_audio.cc @@ -26,14 +26,7 @@ limitations under the License. #include #include -#ifdef __linux__ -#define ENABLE_ALSA -#endif - -#ifdef ENABLE_ALSA -#include "audio_input_alsa.h" -#include "audio_output_alsa.h" -#endif +#include "audio_pa.h" #include "google/assistant/embedded/v1alpha2/embedded_assistant.grpc.pb.h" #include "google/assistant/embedded/v1alpha2/embedded_assistant.pb.h" @@ -42,7 +35,6 @@ limitations under the License. #include "audio_input.h" #include "audio_input_file.h" #include "base64_encode.h" -#include "json_util.h" namespace assistant = google::assistant::embedded::v1alpha2; @@ -59,7 +51,6 @@ using grpc::Channel; using grpc::ClientReaderWriter; static const std::string kCredentialsTypeUserAccount = "USER_ACCOUNT"; -static const std::string kALSAAudioInput = "ALSA_INPUT"; static const std::string kLanguageCode = "en-US"; static const std::string kDeviceModelId = "default"; static const std::string kDeviceInstanceId = "default"; @@ -141,11 +132,6 @@ bool GetCommandLineFlags(int argc, char** argv, int main(int argc, char** argv) { std::string credentials_file_path, api_endpoint, locale, html_out_command; -#ifndef ENABLE_ALSA - std::cerr << "ALSA audio input is not supported on this platform." - << std::endl; - return -1; -#endif // Initialize gRPC and DNS resolvers // https://github.com/grpc/grpc/issues/11366#issuecomment-328595941 @@ -155,6 +141,9 @@ int main(int argc, char** argv) { return -1; } + AudioPA audio_sys; + audio_sys.Open(); /* Opens default input and output */ + while (true) { // Create an AssistRequest AssistRequest request; @@ -225,9 +214,8 @@ int main(int argc, char** argv) { << request.ShortDebugString() << std::endl; } stream->Write(request); - - audio_input.reset(new AudioInputALSA()); - + + audio_input.reset(&audio_sys); audio_input->AddDataListener( [stream, &request](std::shared_ptr> data) { request.set_audio_in(&((*data)[0]), data->size()); @@ -236,9 +224,6 @@ int main(int argc, char** argv) { audio_input->AddStopListener([stream]() { stream->WritesDone(); }); audio_input->Start(); - AudioOutputALSA audio_output; - audio_output.Start(); - // Read responses. if (verbose) { std::clog << "assistant_sdk waiting for response ... " << std::endl; @@ -254,13 +239,8 @@ int main(int argc, char** argv) { } if (response.has_audio_out()) { // CUSTOMIZE: play back audio_out here. - - std::shared_ptr> data( - new std::vector); - data->resize(response.audio_out().audio_data().length()); - memcpy(&((*data)[0]), response.audio_out().audio_data().c_str(), - response.audio_out().audio_data().length()); - audio_output.Send(data); + audio_sys.Write(response.audio_out().audio_data().c_str(), + response.audio_out().audio_data().size() / 2); } // CUSTOMIZE: render spoken request on screen for (int i = 0; i < response.speech_results_size(); i++) { @@ -289,8 +269,7 @@ int main(int argc, char** argv) { } } - audio_output.Stop(); - + audio_sys.Stop(); grpc::Status status = stream->Finish(); if (!status.ok()) { // Report the RPC failure. @@ -300,5 +279,7 @@ int main(int argc, char** argv) { } } + audio_sys.Close(); + return 0; } diff --git a/src/run_assistant_file.cc b/src/run_assistant_file.cc index c67c303..c827910 100644 --- a/src/run_assistant_file.cc +++ b/src/run_assistant_file.cc @@ -33,7 +33,6 @@ limitations under the License. #include "audio_input.h" #include "audio_input_file.h" #include "base64_encode.h" -#include "json_util.h" namespace assistant = google::assistant::embedded::v1alpha2; @@ -172,7 +171,7 @@ int main(int argc, char** argv) { return -2; } // Make sure the input file exists - std::ifstream audio_input_file(audio_input_source); + std::ifstream audio_input_file(audio_input_source, std::ifstream::binary); if (!audio_input_file) { std::cerr << "Audio input file \"" << audio_input_source << "\" does not exist." << std::endl; @@ -246,7 +245,8 @@ int main(int argc, char** argv) { std::ofstream audio_output_file; // Make sure to rewrite contents of file audio_output_file.open(audio_output_source, - std::ofstream::out | std::ofstream::trunc); + std::ofstream::out | std::ofstream::trunc | + std::ofstream::binary); // Check whether file was opened correctly if (audio_output_file.fail()) { std::cerr << "error opening file " << audio_output_source << std::endl; diff --git a/src/run_assistant_text.cc b/src/run_assistant_text.cc index 27496d9..5918f04 100644 --- a/src/run_assistant_text.cc +++ b/src/run_assistant_text.cc @@ -33,7 +33,6 @@ limitations under the License. #include "audio_input.h" #include "audio_input_file.h" #include "base64_encode.h" -#include "json_util.h" namespace assistant = google::assistant::embedded::v1alpha2;