├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── colors.xml │ │ │ └── styles.xml │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ └── drawable │ │ │ └── ic_launcher_background.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── spotify │ │ └── nfdrivertest_android │ │ └── MainActivity.java ├── proguard-rules.pro └── build.gradle ├── ci ├── ci.json ├── osx.sh ├── windows.ps1 ├── README.md ├── windows.py ├── ios.py ├── androidwindows.py ├── linux.sh ├── android.py ├── osx.py ├── androidlinux.py ├── nfbuildlinux.py ├── linux.py ├── nfbuildwindows.py ├── nfbuild.py ├── build_options.py └── nfbuildosx.py ├── NFDriver.png ├── .gitmodules ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── libraries ├── lame.pch.h └── CMakeLists.txt ├── appveyor.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── gradle.properties ├── settings.gradle ├── .gitignore ├── source ├── cli │ ├── CMakeLists.txt │ └── NFDriverCLI.cpp ├── NFDriverFileAACImplementation.h ├── NFDriverFileMP3Implementation.h ├── NFDriverFileImplementation.h ├── NFDriverAdapter.h ├── CMakeLists.txt ├── NFDriver.cpp ├── NFDriverFileImplementation.cpp ├── NFDriverFileMP3Implementation.cpp ├── NFDriverFileAACImplementation.cpp ├── NFDriverAdapter.cpp └── NFDriver_Windows.cpp ├── tools └── generate-version.py ├── .circleci └── config.yml ├── gradlew.bat ├── CMakeLists.txt ├── gradlew ├── include └── NFDriver │ └── NFDriver.h ├── .clang-format ├── README.md └── LICENSE /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /.externalNativeBuild 3 | -------------------------------------------------------------------------------- /ci/ci.json: -------------------------------------------------------------------------------- 1 | { 2 | "static_analyzer_exceptions": [] 3 | } -------------------------------------------------------------------------------- /NFDriver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nativeformat/NFDriver/HEAD/NFDriver.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libraries/lame"] 2 | path = libraries/lame 3 | url = https://github.com/ffmpegwasm/lame.git 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nativeformat/NFDriver/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | NFDriverTest_Android 3 | 4 | -------------------------------------------------------------------------------- /libraries/lame.pch.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | typedef int A_INT32_T; 5 | typedef float ieee754_float32_t; 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nativeformat/NFDriver/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nativeformat/NFDriver/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nativeformat/NFDriver/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nativeformat/NFDriver/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nativeformat/NFDriver/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nativeformat/NFDriver/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nativeformat/NFDriver/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nativeformat/NFDriver/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nativeformat/NFDriver/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nativeformat/NFDriver/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: 2 | - Visual Studio 2017 3 | 4 | build_script: 5 | - git submodule update --init --recursive 6 | - ps: ci/windows.ps1 7 | 8 | artifacts: 9 | - path: build/output/libNFDriver.zip 10 | name: libNFDriver.zip 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Feb 07 11:47:58 CET 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. NFDriver adheres to [Semantic Versioning](http://semver.org/). 3 | 4 | -- 5 | 6 | ## [1.0.0](https://github.com/spotify/NFDriver/releases/tag/1.0.0) 7 | _Released on 2018-07-30._ 8 | 9 | ### Added 10 | * Initial release of NFDriver. 11 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Contributions are welcomed. Open a pull-request or an issue. 3 | 4 | ## Code of conduct 5 | This project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code. 6 | 7 | [code-of-conduct]: https://github.com/spotify/code-of-conduct/blob/master/code-of-conduct.md 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | include ':app' 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Environment normalization: 2 | /.bundle 3 | /vendor/bundle 4 | 5 | # Xcode 6 | # 7 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 8 | 9 | ## Build generated 10 | build/ 11 | DerivedData 12 | 13 | ## Various settings 14 | *.pbxuser 15 | !default.pbxuser 16 | *.mode1v3 17 | !default.mode1v3 18 | *.mode2v3 19 | !default.mode2v3 20 | *.perspectivev3 21 | !default.perspectivev3 22 | xcuserdata 23 | project.xcworkspace 24 | 25 | ## Other 26 | *.xccheckout 27 | *.moved-aside 28 | *.xcuserstate 29 | *.xcscmblueprint 30 | .DS_Store 31 | 32 | ## Obj-C/Swift specific 33 | *.hmap 34 | *.ipa 35 | 36 | # Carthage 37 | Carthage/Build 38 | 39 | # Ignore changes to our project.xcconfig that gets overridden by the build system 40 | project.xcconfig 41 | 42 | # Python 43 | *.egg* 44 | *.pyc 45 | nfdriver_env 46 | 47 | # Example Projects 48 | nfdrivertest 49 | NFDriverTest_Windows/Debug 50 | NFDriverTest_Windows/Release 51 | NFDriverTest_Windows/.vs 52 | NFDriverTest_Android/build 53 | NFDriverTest_Android/app/build 54 | NFDriverTest_Android/app/.externalNativeBuild 55 | 56 | /.gradle -------------------------------------------------------------------------------- /libraries/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/lame/libmp3lame LAME_SOURCE_LIB) 2 | aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/lame/libmp3lame/vector/ LAME_SOURCE_LIB) 3 | aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/lame/mpglib LAME_SOURCE_LIB) 4 | 5 | if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin" AND NOT IOS AND NOT ANDROID) 6 | set(lame_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/lame/config.h) 7 | #configure_file(${CMAKE_CURRENT_SOURCE_DIR}/lame/config.h.in ${lame_CONFIG_FILE}) 8 | add_library(mp3lame SHARED ${LAME_SOURCE_LIB}) 9 | target_compile_definitions(mp3lame PRIVATE HAVE_STDINT_H=1 HAVE_MPGLIB=1 DECODE_ON_THE_FLY=1 USE_FAST_LOG=1 TAKEHIRO_IEEE754_HACK=1 HAVE_MEMCPY=1) 10 | target_include_directories(mp3lame PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/lame/libmp3lame" "${CMAKE_CURRENT_SOURCE_DIR}/lame/mpglib" "${CMAKE_CURRENT_SOURCE_DIR}/lame") 11 | target_include_directories(mp3lame PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/lame/include") 12 | target_precompile_headers(mp3lame PRIVATE lame.pch.h) 13 | target_compile_options(mp3lame PRIVATE "-Wno-absolute-value" "-Wno-shift-negative-value" "-Wno-tautological-pointer-compare") 14 | set_target_properties(mp3lame PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lame") 15 | endif() 16 | -------------------------------------------------------------------------------- /source/cli/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Spotify AB. 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | if(IOS) 20 | add_executable(NFDriverApp 21 | ${APP_TYPE} 22 | NFDriverCLI.cpp) 23 | set_target_properties(NFDriverApp 24 | PROPERTIES 25 | XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "iPhone Developer: My Name") 26 | target_link_libraries(NFDriverApp NFDriver) 27 | elseif(ANDROID_APP) 28 | add_library(NFDriverApp SHARED NFDriverCLI.cpp) 29 | target_link_libraries(NFDriverApp 30 | log 31 | android 32 | OpenSLES) 33 | else() 34 | add_executable(NFDriverCLI NFDriverCLI.cpp) 35 | target_link_libraries(NFDriverCLI NFDriver) 36 | endif() 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/spotify/nfdrivertest_android/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package com.spotify.nfdrivertest_android; 22 | 23 | import android.support.v7.app.AppCompatActivity; 24 | import android.os.Bundle; 25 | 26 | public class MainActivity extends AppCompatActivity { 27 | static { 28 | System.loadLibrary("NFDriverApp"); 29 | } 30 | 31 | @Override 32 | protected void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | 35 | nativeMain(); 36 | } 37 | 38 | // Check the cpp folder for the native (C++) code. Nothing happens here in Java. 39 | public native void nativeMain(); 40 | } 41 | -------------------------------------------------------------------------------- /ci/osx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2021 Spotify AB. 3 | # 4 | # Licensed to the Apache Software Foundation (ASF) under one 5 | # or more contributor license agreements. See the NOTICE file 6 | # distributed with this work for additional information 7 | # regarding copyright ownership. The ASF licenses this file 8 | # to you under the Apache License, Version 2.0 (the 9 | # "License"); you may not use this file except in compliance 10 | # with the License. You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, 15 | # software distributed under the License is distributed on an 16 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | # KIND, either express or implied. See the License for the 18 | # specific language governing permissions and limitations 19 | # under the License. 20 | 21 | # Exit on any non-zero status 22 | set -e 23 | 24 | # Update submodules 25 | git submodule sync 26 | git submodule update --init --recursive 27 | 28 | # Install system dependencies 29 | brew install clang-format 30 | brew install ninja 31 | brew install cmake 32 | 33 | # Install virtualenv 34 | python3 -m venv nfdriver_env 35 | source nfdriver_env/bin/activate 36 | 37 | # Install Python Packages 38 | pip3 install pyyaml 39 | pip3 install flake8 40 | pip3 install cmakelint 41 | 42 | # Execute our python build tools 43 | if [ -n "$BUILD_IOS" ]; then 44 | python ci/ios.py "$@" 45 | else 46 | if [ -n "$BUILD_ANDROID" ]; then 47 | brew install android-ndk 48 | 49 | python ci/android.py "$@" 50 | else 51 | python ci/osx.py "$@" 52 | fi 53 | fi 54 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | apply plugin: 'com.android.application' 22 | 23 | android { 24 | compileSdkVersion 26 25 | defaultConfig { 26 | applicationId "com.spotify.nfdrivertest_android" 27 | minSdkVersion 19 28 | targetSdkVersion 26 29 | versionCode 1 30 | versionName "1.0" 31 | externalNativeBuild { 32 | cmake { 33 | cppFlags "" 34 | arguments "-DANDROID_APP=1 -DANDROID=1" 35 | } 36 | } 37 | } 38 | 39 | sourceSets { 40 | main { 41 | jniLibs.srcDirs = ['src/main/cpp'] 42 | } 43 | } 44 | 45 | externalNativeBuild { 46 | cmake { 47 | path "../CMakeLists.txt" 48 | } 49 | } 50 | } 51 | 52 | dependencies { 53 | implementation fileTree(dir: 'libs', include: ['*.jar']) 54 | implementation 'com.android.support:appcompat-v7:26.1.0' 55 | } 56 | -------------------------------------------------------------------------------- /ci/windows.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | * Copyright (c) 2018 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | #> 21 | param ( 22 | [string]$build = "windows" 23 | ) 24 | 25 | Write-Host "NFDriver build process starting..." 26 | Write-Host $build 27 | 28 | try 29 | { 30 | # Upgrade pip or else the CI will complain 31 | c:\python27\python.exe -m pip install --upgrade pip 32 | 33 | # Start virtualenv 34 | pip install virtualenv 35 | virtualenv nfdriver_env 36 | 37 | & ./nfdriver_env/Scripts/activate.bat 38 | 39 | # Install Python Packages 40 | & nfdriver_env/Scripts/pip.exe install urllib3 ` 41 | pyyaml ` 42 | flake8 ` 43 | cmakelint 44 | 45 | if($build -eq "android"){ 46 | & nfdriver_env/Scripts/python.exe ci/androidwindows.py 47 | } else { 48 | & nfdriver_env/Scripts/python.exe ci/windows.py build 49 | } 50 | 51 | if($LASTEXITCODE -ne 0){ 52 | exit $LASTEXITCODE 53 | } 54 | 55 | & ./nfdriver_env/Scripts/deactivate.bat 56 | } 57 | catch 58 | { 59 | echo $_.Exception|format-list -force 60 | exit 1 61 | } 62 | -------------------------------------------------------------------------------- /tools/generate-version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | from __future__ import print_function 24 | 25 | import os 26 | import subprocess 27 | import sys 28 | 29 | 30 | def main(): 31 | output_dir = os.path.join(os.path.join('build', 'output')) 32 | if len(sys.argv) > 1: 33 | output_dir = sys.argv[1] 34 | if not os.path.exists(output_dir): 35 | os.makedirs(output_dir) 36 | print('Generating nfdriver_generated_header.h in ' + output_dir) 37 | generated_header_filename = os.path.join(output_dir, 'nfdriver_generated_header.h') 38 | generated_header = open(generated_header_filename, 'w') 39 | generated_header.write('// This is a generated header from generate-version.py\n') 40 | cwd = os.getcwd() 41 | git_count = subprocess.check_output(['git', 'rev-list', '--count', 'HEAD'], cwd = cwd).decode() 42 | git_describe = subprocess.check_output(['git', 'describe', '--always'], cwd = cwd).decode() 43 | generated_header.write('#define NFDRIVER_VERSION "' + str(git_count.strip()) + '-' + str(git_describe.strip()) + '"\n') 44 | 45 | 46 | if __name__ == "__main__": 47 | main() 48 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /source/NFDriverFileAACImplementation.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | namespace nativeformat { 30 | namespace driver { 31 | 32 | class NFDriverFileAACImplementation : public NFDriver { 33 | public: 34 | bool isPlaying() const; 35 | void setPlaying(bool playing); 36 | 37 | NFDriverFileAACImplementation(void *clientdata, 38 | NF_STUTTER_CALLBACK stutter_callback, 39 | NF_RENDER_CALLBACK render_callback, 40 | NF_ERROR_CALLBACK error_callback, 41 | NF_WILL_RENDER_CALLBACK will_render_callback, 42 | NF_DID_RENDER_CALLBACK did_render_callback, 43 | const char *output_destination, 44 | int bitrate); 45 | ~NFDriverFileAACImplementation(); 46 | 47 | private: 48 | void *_clientdata; 49 | const NF_STUTTER_CALLBACK _stutter_callback; 50 | const NF_RENDER_CALLBACK _render_callback; 51 | const NF_ERROR_CALLBACK _error_callback; 52 | const NF_WILL_RENDER_CALLBACK _will_render_callback; 53 | const NF_DID_RENDER_CALLBACK _did_render_callback; 54 | const std::string _output_destination; 55 | const int _bitrate; 56 | 57 | std::shared_ptr _thread; 58 | std::atomic _run; 59 | 60 | static void run(NFDriverFileAACImplementation *driver); 61 | }; 62 | 63 | } // namespace driver 64 | } // namespace nativeformat 65 | -------------------------------------------------------------------------------- /source/NFDriverFileMP3Implementation.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | namespace nativeformat { 30 | namespace driver { 31 | 32 | class NFDriverFileMP3Implementation : public NFDriver { 33 | public: 34 | bool isPlaying() const; 35 | void setPlaying(bool playing); 36 | 37 | NFDriverFileMP3Implementation(void *clientdata, 38 | NF_STUTTER_CALLBACK stutter_callback, 39 | NF_RENDER_CALLBACK render_callback, 40 | NF_ERROR_CALLBACK error_callback, 41 | NF_WILL_RENDER_CALLBACK will_render_callback, 42 | NF_DID_RENDER_CALLBACK did_render_callback, 43 | const char *output_destination, 44 | int bitrate); 45 | ~NFDriverFileMP3Implementation(); 46 | 47 | private: 48 | void *_clientdata; 49 | const NF_STUTTER_CALLBACK _stutter_callback; 50 | const NF_RENDER_CALLBACK _render_callback; 51 | const NF_ERROR_CALLBACK _error_callback; 52 | const NF_WILL_RENDER_CALLBACK _will_render_callback; 53 | const NF_DID_RENDER_CALLBACK _did_render_callback; 54 | const std::string _output_destination; 55 | const int _bitrate; 56 | 57 | std::shared_ptr _thread; 58 | std::atomic _run; 59 | 60 | static void run(NFDriverFileMP3Implementation *driver); 61 | }; 62 | 63 | } // namespace driver 64 | } // namespace nativeformat 65 | -------------------------------------------------------------------------------- /ci/README.md: -------------------------------------------------------------------------------- 1 | # Continuous Integration 2 | Every pull request and merge to master trigger a set of CircleCI builds. See [config.yml](../.circleci/config.yml) for a complete list of builds. 3 | 4 | ## CI Build Scripts 5 | Each CircleCI build is configured to run one of the scripts in this directory, sometimes with additional command line arguments. See the individual CircleCI build configurations to view or edit the exact build pipeline commands. In general, each platform has a shell script that acts as an entry point (e.g. [osx.sh](osx.sh)) and invokes the appropriate python script (e.g. [osx.py](osx.py)). All of the platform-specific python scripts create build objects derived from [nfbuild.py](nfbuild.py). Some of the methods in the `NFBuild` python class are overriden for platform-specific functionality in the other `nfbuild*.py` files. 6 | 7 | ## Using CI Scripts Locally 8 | The ci scripts can be used to download dependencies, generate build files with CMake, compile targets, and run tests in local build environments as well. 9 | 10 | On OS X, the following commands may be useful. They all assume you have already cloned the project and all of its submodules. 11 | 12 | ### Build-in Help 13 | 14 | For a full-list of supported cmdline command, enter: 15 | 16 | ``` 17 | sh ci/osx.sh --help 18 | ``` 19 | 20 | ### Running the linter 21 | This will flag any style errors in python and CMake files, and edit C++ source files in place. It will not compile or run any targets. 22 | ``` 23 | sh ci/osx.sh lint 24 | ``` 25 | 26 | ### Running integration tests 27 | This will run the integration tests locally. 28 | If you want to skip project generation, add `-generateProject=0`. 29 | This command will not destroy the contents of your `cpp/build` folder. 30 | ``` 31 | sh ci/osx.sh local_it 32 | ``` 33 | 34 | ### Custom builds 35 | The CI scripts allow enabling/disabling any step of the build process. 36 | Options groups (or workflows) are specified without a preceding dash, and will activate a group of build steps. 37 | Additionally individual build steps can be turned on or off. For example, the command below runs the address sanitizer 38 | without wiping the build directory: 39 | ``` 40 | sh ci/osx.sh address_sanitizer -makeBuildDirectory=0 41 | ``` 42 | 43 | New build options can be added to the ci script by calling `buildOptions.addOption(optionName, optionHelpDescription)`. 44 | 45 | New workflows can be added by calling `buildOptions.addWorkflow(workflow_name, workflow_help_description, array_of_optionName)`. 46 | 47 | By convention, options should be in camel case, while workflows should use underscores. -------------------------------------------------------------------------------- /source/NFDriverFileImplementation.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | namespace nativeformat { 30 | namespace driver { 31 | 32 | typedef enum : short { 33 | NFDriverFileWAVHeaderAudioFormatPCM = 1, 34 | NFDriverFileWAVHeaderAudioFormatIEEEFloat = 3 35 | } NFDriverFileWAVHeaderAudioFormat; 36 | 37 | class NFDriverFileImplementation : public NFDriver { 38 | public: 39 | bool isPlaying() const; 40 | void setPlaying(bool playing); 41 | 42 | NFDriverFileImplementation(void *clientdata, 43 | NF_STUTTER_CALLBACK stutter_callback, 44 | NF_RENDER_CALLBACK render_callback, 45 | NF_ERROR_CALLBACK error_callback, 46 | NF_WILL_RENDER_CALLBACK will_render_callback, 47 | NF_DID_RENDER_CALLBACK did_render_callback, 48 | const char *output_destination, 49 | NFDriverFileWAVHeaderAudioFormat wav_format); 50 | ~NFDriverFileImplementation(); 51 | 52 | private: 53 | void *_clientdata; 54 | const NF_STUTTER_CALLBACK _stutter_callback; 55 | const NF_RENDER_CALLBACK _render_callback; 56 | const NF_ERROR_CALLBACK _error_callback; 57 | const NF_WILL_RENDER_CALLBACK _will_render_callback; 58 | const NF_DID_RENDER_CALLBACK _did_render_callback; 59 | const std::string _output_destination; 60 | const NFDriverFileWAVHeaderAudioFormat _wav_format; 61 | 62 | std::shared_ptr _thread; 63 | std::atomic _run; 64 | 65 | static void run(NFDriverFileImplementation *driver); 66 | }; 67 | 68 | } // namespace driver 69 | } // namespace nativeformat 70 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | buildlinuxclang: 4 | docker: 5 | - image: ubuntu:bionic 6 | steps: 7 | - checkout 8 | - run: 9 | name: Build Linux with clang 10 | command: sh ci/linux.sh clang_build 11 | - store_artifacts: 12 | path: build/output/libNFDriver.zip 13 | destination: libNFDriver.zip 14 | buildlinuxgcc: 15 | docker: 16 | - image: ubuntu:bionic 17 | steps: 18 | - checkout 19 | - run: 20 | name: Build Linux with gcc 21 | command: sh ci/linux.sh gcc_build 22 | - store_artifacts: 23 | path: build/output/libNFDriver.zip 24 | destination: libNFDriver.zip 25 | buildlinuxandroid: 26 | docker: 27 | - image: ubuntu:bionic 28 | steps: 29 | - checkout 30 | - run: 31 | name: Build Android 32 | command: BUILD_ANDROID=1 sh ci/linux.sh build 33 | - store_artifacts: 34 | path: libNFDriver-androidx86.zip 35 | destination: libNFDriver-androidx86.zip 36 | - store_artifacts: 37 | path: libNFDriver-androidArm64.zip 38 | destination: libNFDriver-androidArm64.zip 39 | buildmac: 40 | macos: 41 | xcode: "11.5.0" 42 | steps: 43 | - checkout 44 | - run: 45 | name: Build OSX 46 | command: sh ci/osx.sh build 47 | - store_artifacts: 48 | path: build/output/libNFDriver.zip 49 | destination: libNFDriver.zip 50 | buildmacios: 51 | macos: 52 | xcode: "11.5.0" 53 | steps: 54 | - checkout 55 | - run: 56 | name: Build iOS 57 | command: BUILD_IOS=1 sh ci/osx.sh build 58 | - store_artifacts: 59 | path: build/output/libNFDriver.zip 60 | destination: libNFDriver.zip 61 | buildmacandroid: 62 | macos: 63 | xcode: "11.5.0" 64 | steps: 65 | - checkout 66 | - run: brew update 67 | - run: git submodule sync 68 | - run: git submodule update --init --recursive 69 | # Android NDK does not pass. 70 | - run: sudo spctl --master-disable 71 | - run: 72 | name: Build Android 73 | command: BUILD_ANDROID=1 sh ci/osx.sh build 74 | - store_artifacts: 75 | path: libNFDriver-androidx86.zip 76 | destination: libNFDriver-androidx86.zip 77 | - store_artifacts: 78 | path: libNFDriver-androidArm64.zip 79 | destination: libNFDriver-androidArm64.zip 80 | 81 | workflows: 82 | version: 2 83 | build: 84 | jobs: 85 | - buildlinuxclang 86 | - buildlinuxgcc 87 | - buildlinuxandroid 88 | - buildmac 89 | - buildmacios 90 | - buildmacandroid 91 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /ci/windows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import sys 24 | 25 | from nfbuildwindows import NFBuildWindows 26 | from build_options import BuildOptions 27 | 28 | 29 | def main(): 30 | buildOptions = BuildOptions() 31 | buildOptions.addOption("installDependencies", "Install dependencies") 32 | buildOptions.addOption("makeBuildDirectory", 33 | "Wipe existing build directory") 34 | buildOptions.addOption("generateProject", "Regenerate project") 35 | buildOptions.addOption("buildTargetCLI", 36 | "Build Target: CLI") 37 | buildOptions.addOption("buildTargetLibrary", "Build Target: Library") 38 | buildOptions.addOption("packageArtifacts", "Package the binary artifacts") 39 | buildOptions.setDefaultWorkflow("Empty workflow", []) 40 | 41 | buildOptions.addWorkflow("build", "Production Build", [ 42 | 'installDependencies', 43 | 'makeBuildDirectory', 44 | 'generateProject', 45 | 'buildTargetCLI', 46 | 'buildTargetLibrary', 47 | 'packageArtifacts' 48 | ]) 49 | 50 | options = buildOptions.parseArgs() 51 | buildOptions.verbosePrintBuildOptions(options) 52 | 53 | library_target = 'NFDriver' 54 | cli_target = 'NFDriverCLI' 55 | nfbuild = NFBuildWindows() 56 | 57 | if buildOptions.checkOption(options, 'installDependencies'): 58 | nfbuild.installDependencies() 59 | 60 | if buildOptions.checkOption(options, 'makeBuildDirectory'): 61 | nfbuild.makeBuildDirectory() 62 | 63 | if buildOptions.checkOption(options, 'generateProject'): 64 | nfbuild.generateProject(ios=True) 65 | 66 | if buildOptions.checkOption(options, 'buildTargetLibrary'): 67 | nfbuild.buildTarget(library_target) 68 | 69 | if buildOptions.checkOption(options, 'buildTargetCLI'): 70 | nfbuild.buildTarget(cli_target) 71 | 72 | if buildOptions.checkOption(options, "packageArtifacts"): 73 | nfbuild.packageArtifacts() 74 | 75 | 76 | if __name__ == "__main__": 77 | main() 78 | -------------------------------------------------------------------------------- /ci/ios.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import sys 24 | 25 | from nfbuildosx import NFBuildOSX 26 | 27 | from build_options import BuildOptions 28 | 29 | 30 | def main(): 31 | buildOptions = BuildOptions() 32 | buildOptions.addOption("installDependencies", "Install dependencies") 33 | buildOptions.addOption("makeBuildDirectory", 34 | "Wipe existing build directory") 35 | buildOptions.addOption("generateProject", "Regenerate xcode project") 36 | buildOptions.addOption("buildTargetIphoneSimulator", 37 | "Build Target: iPhone Simulator") 38 | buildOptions.addOption("buildTargetIphoneOS", "Build Target: iPhone OS") 39 | buildOptions.addOption("staticAnalysis", "Run Static Analysis") 40 | buildOptions.addOption("packageArtifacts", "Package the artifacts produced by the build") 41 | buildOptions.setDefaultWorkflow("Empty workflow", []) 42 | 43 | buildOptions.addWorkflow("build", "Production Build", [ 44 | 'installDependencies', 45 | 'makeBuildDirectory', 46 | 'generateProject', 47 | 'buildTargetIphoneSimulator', 48 | 'buildTargetIphoneOS', 49 | 'staticAnalysis', 50 | 'packageArtifacts' 51 | ]) 52 | 53 | options = buildOptions.parseArgs() 54 | buildOptions.verbosePrintBuildOptions(options) 55 | 56 | library_target = 'NFDriver' 57 | nfbuild = NFBuildOSX() 58 | 59 | if buildOptions.checkOption(options, 'installDependencies'): 60 | nfbuild.installDependencies() 61 | 62 | if buildOptions.checkOption(options, 'makeBuildDirectory'): 63 | nfbuild.makeBuildDirectory() 64 | 65 | if buildOptions.checkOption(options, 'generateProject'): 66 | nfbuild.generateProject(ios=True) 67 | 68 | if buildOptions.checkOption(options, 'buildTargetIphoneSimulator'): 69 | nfbuild.buildTarget(library_target, 70 | sdk='iphonesimulator', 71 | arch='x86_64') 72 | 73 | if buildOptions.checkOption(options, 'buildTargetIphoneOS'): 74 | nfbuild.buildTarget(library_target, sdk='iphoneos', arch='arm64') 75 | 76 | if buildOptions.checkOption(options, 'staticAnalysis'): 77 | nfbuild.staticallyAnalyse(library_target, 78 | include_regex='source/.*') 79 | if buildOptions.checkOption(options, "packageArtifacts"): 80 | nfbuild.packageArtifacts() 81 | 82 | 83 | if __name__ == "__main__": 84 | main() 85 | -------------------------------------------------------------------------------- /source/NFDriverAdapter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include 24 | 25 | namespace nativeformat { 26 | namespace driver { 27 | 28 | struct NFDriverAdapterInternals; 29 | 30 | // This class connects audio I/O to the audio provider (the player for example). 31 | // It will always ask the audio provider for 2 channels interleaved audio, with 32 | // fixed buffer size and fixed samplerate (in NFDriver.h). The class performs 33 | // buffering, resampling and deinterleaving automatically as needed. 34 | 35 | class NFDriverAdapter { 36 | public: 37 | NFDriverAdapter(void *clientdata, 38 | NF_STUTTER_CALLBACK stutter_callback, 39 | NF_RENDER_CALLBACK render_callback, 40 | NF_ERROR_CALLBACK error_callback, 41 | NF_WILL_RENDER_CALLBACK will_render_callback, 42 | NF_DID_RENDER_CALLBACK did_render_callback); 43 | ~NFDriverAdapter(); 44 | 45 | static int getOptimalNumberOfFrames(int samplerate); // Returns with the ideal 46 | // number of frames for 47 | // the specific 48 | // samplerate for minimal 49 | // buffering and latency. 50 | 51 | void setSamplerate(int samplerate); // Thread-safe, can be called in any thread. 52 | bool getFrames(float *outputLeft, 53 | float *outputRight, 54 | int numFrames, 55 | int numChannels); // Should be called in the audio 56 | // processing/rendering callback of the audio 57 | // I/O. 58 | 59 | private: 60 | NFDriverAdapterInternals *internals; 61 | }; 62 | 63 | struct NFSoundCardDriverInternals; 64 | 65 | class NFSoundCardDriver : public NFDriver { 66 | public: 67 | bool isPlaying() const; 68 | void setPlaying(bool playing); 69 | 70 | NFSoundCardDriver(void *clientdata, 71 | NF_STUTTER_CALLBACK stutter_callback, 72 | NF_RENDER_CALLBACK render_callback, 73 | NF_ERROR_CALLBACK error_callback, 74 | NF_WILL_RENDER_CALLBACK will_render_callback, 75 | NF_DID_RENDER_CALLBACK did_render_callback); 76 | ~NFSoundCardDriver(); 77 | 78 | private: 79 | NFSoundCardDriverInternals *internals; 80 | }; 81 | 82 | } // namespace driver 83 | } // namespace nativeformat 84 | -------------------------------------------------------------------------------- /source/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Spotify AB. 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | set(SOURCE_FILES 20 | ../include/NFDriver/NFDriver.h 21 | NFDriverAdapter.h 22 | NFDriverAdapter.cpp 23 | NFDriver.cpp 24 | NFDriverFileImplementation.h 25 | NFDriverFileImplementation.cpp) 26 | set(LINK_LIBRARIES) 27 | 28 | if(${CMAKE_SYSTEM_NAME} MATCHES "Linux" AND NOT ANDROID) 29 | list(APPEND 30 | SOURCE_FILES 31 | NFDriver_Linux.cpp 32 | NFDriverFileMP3Implementation.h 33 | NFDriverFileMP3Implementation.cpp) 34 | find_package(ALSA REQUIRED) 35 | set(THREADS_PREFER_PTHREAD_FLAG ON) 36 | find_package(Threads REQUIRED) 37 | list(APPEND 38 | LINK_LIBRARIES 39 | ${ALSA_LIBRARIES} 40 | ${CMAKE_THREAD_LIBS_INIT} 41 | ${CMAKE_DL_LIBS}) 42 | endif() 43 | 44 | if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin" AND NOT IOS AND NOT ANDROID) 45 | list(APPEND 46 | SOURCE_FILES 47 | NFDriver_MacOSX.cpp 48 | NFDriverFileMP3Implementation.h 49 | NFDriverFileMP3Implementation.cpp 50 | NFDriverFileAACImplementation.h 51 | NFDriverFileAACImplementation.cpp) 52 | find_library(AUDIO_UNIT AudioUnit) 53 | find_library(CORE_AUDIO CoreAudio) 54 | find_library(AUDIO_TOOLBOX AudioToolbox) 55 | find_library(CORE_FOUNDATION CoreFoundation) 56 | list(APPEND 57 | LINK_LIBRARIES 58 | ${AUDIO_UNIT} 59 | ${CORE_AUDIO} 60 | ${AUDIO_TOOLBOX} 61 | ${CORE_FOUNDATION}) 62 | endif() 63 | 64 | if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin" AND IOS AND NOT ANDROID) 65 | list(APPEND 66 | SOURCE_FILES 67 | NFDriver_iOS.mm 68 | NFDriverFileAACImplementation.h 69 | NFDriverFileAACImplementation.cpp) 70 | find_library(AVFOUNDATION AVFoundation) 71 | find_library(CORE_AUDIO CoreAudio) 72 | find_library(AUDIO_TOOLBOX AudioToolbox) 73 | list(APPEND 74 | LINK_LIBRARIES 75 | ${AVFOUNDATION} 76 | ${CORE_AUDIO} 77 | ${AUDIO_TOOLBOX}) 78 | endif() 79 | 80 | if(WIN32 AND NOT ANDROID) 81 | list(APPEND 82 | SOURCE_FILES 83 | NFDriver_Windows.cpp) 84 | list(APPEND 85 | LINK_LIBRARIES 86 | dxva2.lib 87 | evr.lib 88 | mf.lib 89 | mfplat.lib 90 | mfplay.lib 91 | mfreadwrite.lib 92 | mfuuid.lib 93 | mmdevapi.lib) 94 | endif() 95 | 96 | if(ANDROID) 97 | list(APPEND 98 | SOURCE_FILES 99 | NFDriver_Android.cpp) 100 | endif() 101 | 102 | add_library(NFDriver STATIC ${SOURCE_FILES}) 103 | target_include_directories(NFDriver 104 | PUBLIC 105 | ../include 106 | ${CMAKE_BINARY_DIR}/output 107 | ../libraries/lame/include) 108 | target_link_libraries(NFDriver ${LINK_LIBRARIES}) 109 | 110 | if(NOT ANDROID) 111 | add_subdirectory(cli) 112 | endif() 113 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Spotify AB. 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | cmake_minimum_required(VERSION 3.6) 20 | 21 | project(NFDriver) 22 | 23 | set(CMAKE_CXX_STANDARD 11) 24 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 25 | 26 | if(LLVM_STDLIB) 27 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") 28 | endif() 29 | 30 | if(IOS) 31 | set(CMAKE_OSX_SYSROOT iphoneos) 32 | set(CMAKE_OSX_ARCHITECTURES $(ARCHS_STANDARD)) 33 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -x objective-c++") 34 | link_directories(\${HOME}/\${SDKROOT}/lib) 35 | set(MACOSX_BUNDLE_GUI_IDENTIFIER "com.spotify.\${PRODUCT_NAME:identifier}") 36 | set(APP_TYPE MACOSX_BUNDLE) 37 | set(CMAKE_EXE_LINKER_FLAGS "-framework UIKit") 38 | endif() 39 | 40 | if(WIN32) 41 | set(WINDOWS_FLAGS "/W3") 42 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /WX") 43 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /bigobj") 44 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /D_CRT_SECURE_NO_WARNINGS=1") 45 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4003") 46 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4018") 47 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4091") 48 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4200") 49 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4250") 50 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4275") 51 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4355") 52 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4520") 53 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4530") 54 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4996") 55 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4146") 56 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /we4053") 57 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /we4063") 58 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /we4064") 59 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /EHsc") 60 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4503") 61 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /DWIN32") 62 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /DUNICODE") 63 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /D_UNICODE") 64 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /D_WINDOWS") 65 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /DNOMINMAX") 66 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /D_WIN32_IE=0x0700") 67 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /D_WIN32_WINNT=0xA00") 68 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /ZW") 69 | set(WINDOWS_FLAGS "${WINDOWS_FLAGS} /wd4447") 70 | set(WINDOWS_LINKER_FLAGS "${WINDOWS_LINKER_FLAGS} /INCREMENTAL:NO") 71 | set(WINDOWS_LINKER_FLAGS "${WINDOWS_LINKER_FLAGS} /OPT:ICF") 72 | set(WINDOWS_LINKER_FLAGS "${WINDOWS_LINKER_FLAGS} /OPT:REF") 73 | set(CMAKE_C_FLAGS 74 | "${CMAKE_C_FLAGS} ${WINDOWS_FLAGS}") 75 | set(CMAKE_CXX_FLAGS 76 | "${CMAKE_CXX_FLAGS} ${WINDOWS_FLAGS}") 77 | set(CMAKE_EXE_LINKER_FLAGS 78 | "${CMAKE_EXE_LINKER_FLAGS} ${WINDOWS_LINKER_FLAGS}") 79 | endif() 80 | 81 | add_subdirectory(source) 82 | add_subdirectory(libraries) 83 | 84 | execute_process(COMMAND python 85 | "${CMAKE_CURRENT_SOURCE_DIR}/tools/generate-version.py" 86 | ${CMAKE_BINARY_DIR}/output 87 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) 88 | -------------------------------------------------------------------------------- /ci/androidwindows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import sys 24 | 25 | from nfbuildwindows import NFBuildWindows 26 | from build_options import BuildOptions 27 | 28 | 29 | def main(): 30 | buildOptions = BuildOptions() 31 | buildOptions.addOption("installDependencies", "Install dependencies") 32 | 33 | buildOptions.addOption("makeBuildDirectoryX86", 34 | "Wipe existing build directory for X86 build.") 35 | buildOptions.addOption("generateProjectX86", "Regenerate project for X86 build") 36 | 37 | buildOptions.addOption("buildTargetLibraryX86", "Build Target: Library (X86)") 38 | 39 | buildOptions.addOption("makeBuildDirectoryArm64", 40 | "Wipe existing build directory for ARM64 build.") 41 | buildOptions.addOption("generateProjectArm64", "Regenerate project for ARM64 build") 42 | 43 | buildOptions.addOption("buildTargetLibraryArm64", "Build Target: Library (ARM64)") 44 | 45 | buildOptions.setDefaultWorkflow("Empty workflow", []) 46 | 47 | buildOptions.addWorkflow("build", "Production Build", [ 48 | 'installDependencies', 49 | 'makeBuildDirectoryX86', 50 | 'generateProjectX86', 51 | 'buildTargetLibraryX86', 52 | 'makeBuildDirectoryArm64', 53 | 'generateProjectArm64', 54 | 'buildTargetLibraryArm64' 55 | ]) 56 | 57 | buildOptions.addWorkflow("buildX86", "Production Build (X86)", [ 58 | 'installDependencies', 59 | 'makeBuildDirectoryX86', 60 | 'generateProjectX86', 61 | 'buildTargetLibraryX86' 62 | ]) 63 | 64 | buildOptions.addWorkflow("buildArm64", "Production Build (ARM64)", [ 65 | 'installDependencies', 66 | 'makeBuildDirectoryArm64', 67 | 'generateProjectArm64', 68 | 'buildTargetLibraryArm64' 69 | ]) 70 | 71 | 72 | options = buildOptions.parseArgs() 73 | buildOptions.verbosePrintBuildOptions(options) 74 | 75 | library_target = 'NFDriver' 76 | nfbuild = NFBuildWindows() 77 | 78 | if buildOptions.checkOption(options, 'installDependencies'): 79 | nfbuild.installDependencies(android=True) 80 | 81 | if buildOptions.checkOption(options, 'makeBuildDirectoryX86'): 82 | nfbuild.makeBuildDirectory() 83 | 84 | if buildOptions.checkOption(options, 'generateProjectX86'): 85 | nfbuild.generateProject(android=True, android_arm=False) 86 | 87 | if buildOptions.checkOption(options, 'buildTargetLibraryX86'): 88 | nfbuild.buildTarget(library_target) 89 | 90 | if buildOptions.checkOption(options, 'makeBuildDirectoryArm64'): 91 | nfbuild.makeBuildDirectory() 92 | 93 | if buildOptions.checkOption(options, 'generateProjectArm64'): 94 | nfbuild.generateProject(android=False, android_arm=True) 95 | 96 | if buildOptions.checkOption(options, 'buildTargetLibraryArm64'): 97 | nfbuild.buildTarget(library_target) 98 | 99 | 100 | if __name__ == "__main__": 101 | main() 102 | 103 | 104 | -------------------------------------------------------------------------------- /ci/linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2021 Spotify AB. 3 | # 4 | # Licensed to the Apache Software Foundation (ASF) under one 5 | # or more contributor license agreements. See the NOTICE file 6 | # distributed with this work for additional information 7 | # regarding copyright ownership. The ASF licenses this file 8 | # to you under the Apache License, Version 2.0 (the 9 | # "License"); you may not use this file except in compliance 10 | # with the License. You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, 15 | # software distributed under the License is distributed on an 16 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | # KIND, either express or implied. See the License for the 18 | # specific language governing permissions and limitations 19 | # under the License. 20 | 21 | # Exit on any non-zero status 22 | set -e 23 | pwd 24 | 25 | # Install basics 26 | apt-get -q update 27 | apt-get install sudo 28 | 29 | # Install system dependencies 30 | export PYTHON_VERSION="3.7.3" 31 | sudo apt-get update 32 | sudo apt-get install -y --no-install-recommends apt-utils software-properties-common 33 | sudo apt-get install -y --no-install-recommends build-essential \ 34 | zlib1g-dev \ 35 | libncurses5-dev \ 36 | libgdbm-dev \ 37 | libnss3-dev \ 38 | libssl-dev \ 39 | libreadline-dev \ 40 | libffi-dev \ 41 | wget \ 42 | libbz2-dev \ 43 | libsqlite3-dev \ 44 | liblzma-dev 45 | wget --tries=5 "https://www.python.org/ftp/python/$PYTHON_VERSION/Python-$PYTHON_VERSION.tgz" 46 | tar -xf "Python-$PYTHON_VERSION.tgz" 47 | ( 48 | cd "Python-$PYTHON_VERSION" 49 | ./configure --enable-loadable-sqlite-extensions 50 | make -j 8 51 | sudo make altinstall 52 | ) 53 | sudo apt-get install -y --no-install-recommends python3-setuptools \ 54 | libasound2-dev \ 55 | clang-format-3.9 \ 56 | ninja-build \ 57 | clang-3.9 \ 58 | libc++-dev \ 59 | python-virtualenv \ 60 | wget \ 61 | libyaml-dev \ 62 | python-dev \ 63 | python3-dev \ 64 | git \ 65 | unzip \ 66 | cmake \ 67 | git \ 68 | libc++abi-dev 69 | sudo apt-get install -y --reinstall binutils 70 | 71 | # Update submodules 72 | git submodule sync 73 | git submodule update --init --recursive 74 | 75 | # Install virtualenv 76 | python3.7 -m venv nfdriver_env 77 | . nfdriver_env/bin/activate 78 | 79 | # Install Python Packages 80 | pip3 install pyyaml \ 81 | flake8 \ 82 | cmakelint 83 | 84 | # Execute our python build tools 85 | if [ -n "$BUILD_ANDROID" ]; then 86 | # Install Android NDK 87 | wget https://dl.google.com/android/repository/android-ndk-r17b-linux-x86_64.zip 88 | unzip -q android-ndk-r17b-linux-x86_64.zip 89 | mv android-ndk-r17b ~/ndk 90 | chmod +x -R ~/ndk 91 | 92 | python ci/androidlinux.py "$@" 93 | else 94 | python ci/linux.py "$@" 95 | fi 96 | -------------------------------------------------------------------------------- /source/cli/NFDriverCLI.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #include 22 | 23 | #define _USE_MATH_DEFINES 24 | #include 25 | #include 26 | #include 27 | 28 | #if __APPLE__ 29 | #include 30 | 31 | #include "TargetConditionals.h" 32 | #endif 33 | 34 | #ifdef __ANDROID__ 35 | #include 36 | #include 37 | #endif 38 | 39 | #ifndef M_PI 40 | #define M_PI 3.14159265358979323846 41 | #endif 42 | 43 | static void stutterCallback(void *clientdata) { 44 | printf("stutter\n"); 45 | } 46 | 47 | static void errorCallback(void *clientdata, const char *errorMessage, int errorCode) { 48 | printf("error %i: %s\n", errorCode, errorMessage); 49 | } 50 | 51 | static int renderCallback(void *clientdata, float *frames, int numberOfFrames) { 52 | const float *samplerate = reinterpret_cast(clientdata); 53 | const float multiplier = 54 | (2.0f * static_cast(M_PI) * *samplerate) / static_cast(NF_DRIVER_SAMPLERATE); 55 | static unsigned int sinewave = 0; 56 | float audio; 57 | 58 | for (int n = 0; n < numberOfFrames; n++) { 59 | audio = sinf(multiplier * sinewave++); 60 | *frames++ = audio; 61 | *frames++ = audio; 62 | } 63 | 64 | return numberOfFrames; 65 | } 66 | 67 | static void willRenderCallback(void *clientdata) {} 68 | 69 | static void didRenderCallback(void *clientdata) {} 70 | 71 | #ifdef __ANDROID__ 72 | extern "C" JNIEXPORT void JNICALL 73 | Java_com_spotify_nfdrivertest_1android_MainActivity_nativeMain(JNIEnv *env, jobject self) { 74 | #else 75 | int main(int argc, const char *argv[]) { 76 | #endif 77 | 78 | std::cout << "NativeFormat Driver Command Line Interface " << nativeformat::driver::version() 79 | << std::endl; 80 | 81 | #ifdef __ANDROID__ 82 | NFDriver::onAppLaunch(env, self, NULL, errorCallback); 83 | #endif 84 | 85 | #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE || ANDROID 86 | const std::string samplerate_string = "44100.0"; 87 | #else 88 | if (argc < 2) { 89 | std::cout << "Invalid number of arguments: ./NFDriver [samplerate]" << std::endl; 90 | return 1; 91 | } 92 | const std::string samplerate_string = argv[1]; 93 | #endif 94 | 95 | float samplerate = std::stof(samplerate_string); 96 | 97 | nativeformat::driver::NFDriver *driver = 98 | nativeformat::driver::NFDriver::createNFDriver(&samplerate, 99 | stutterCallback, 100 | renderCallback, 101 | errorCallback, 102 | willRenderCallback, 103 | didRenderCallback, 104 | nativeformat::driver::OutputTypeSoundCard); 105 | driver->setPlaying(true); 106 | 107 | #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE 108 | while (true) { 109 | CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true); 110 | } 111 | #elif !ANDROID 112 | std::cout << std::endl << "Press a key to exit..."; 113 | std::cin.get(); 114 | #endif 115 | 116 | driver->setPlaying(false); 117 | 118 | #ifndef __ANDROID__ 119 | return 0; 120 | #endif 121 | } 122 | -------------------------------------------------------------------------------- /ci/android.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import sys 24 | 25 | from nfbuildosx import NFBuildOSX 26 | from build_options import BuildOptions 27 | 28 | 29 | def main(): 30 | buildOptions = BuildOptions() 31 | buildOptions.addOption("installDependencies", "Install dependencies") 32 | 33 | buildOptions.addOption("makeBuildDirectoryX86", 34 | "Wipe existing build directory for X86 build.") 35 | buildOptions.addOption("generateProjectX86", "Regenerate project for X86 build") 36 | 37 | buildOptions.addOption("buildTargetLibraryX86", "Build Target: Library (X86)") 38 | 39 | buildOptions.addOption("makeBuildDirectoryArm64", 40 | "Wipe existing build directory for ARM64 build.") 41 | buildOptions.addOption("generateProjectArm64", "Regenerate project for ARM64 build") 42 | buildOptions.addOption("packageArtifacts", "Package the artifacts produced by the build") 43 | buildOptions.addOption("buildTargetLibraryArm64", "Build Target: Library (ARM64)") 44 | 45 | buildOptions.setDefaultWorkflow("Empty workflow", []) 46 | 47 | buildOptions.addWorkflow("build", "Production Build", [ 48 | 'installDependencies', 49 | 'makeBuildDirectoryX86', 50 | 'generateProjectX86', 51 | 'buildTargetLibraryX86', 52 | 'packageArtifacts', 53 | 'makeBuildDirectoryArm64', 54 | 'generateProjectArm64', 55 | 'buildTargetLibraryArm64', 56 | 'packageArtifacts' 57 | ]) 58 | 59 | buildOptions.addWorkflow("buildX86", "Production Build (X86)", [ 60 | 'installDependencies', 61 | 'makeBuildDirectoryX86', 62 | 'generateProjectX86', 63 | 'buildTargetLibraryX86', 64 | 'packageArtifacts' 65 | ]) 66 | 67 | buildOptions.addWorkflow("buildArm64", "Production Build (ARM64)", [ 68 | 'installDependencies', 69 | 'makeBuildDirectoryArm64', 70 | 'generateProjectArm64', 71 | 'buildTargetLibraryArm64', 72 | 'packageArtifacts' 73 | ]) 74 | 75 | 76 | options = buildOptions.parseArgs() 77 | buildOptions.verbosePrintBuildOptions(options) 78 | 79 | library_target = 'NFDriver' 80 | nfbuild = NFBuildOSX() 81 | 82 | if buildOptions.checkOption(options, 'installDependencies'): 83 | nfbuild.installDependencies(android=True) 84 | 85 | if buildOptions.checkOption(options, 'makeBuildDirectoryX86'): 86 | nfbuild.makeBuildDirectory() 87 | 88 | if buildOptions.checkOption(options, 'generateProjectX86'): 89 | nfbuild.generateProject(android=True, android_arm=False) 90 | 91 | if buildOptions.checkOption(options, 'buildTargetLibraryX86'): 92 | nfbuild.buildTarget(library_target) 93 | 94 | if buildOptions.checkOption(options, 'packageArtifacts'): 95 | nfbuild.packageArtifacts() 96 | 97 | if buildOptions.checkOption(options, 'makeBuildDirectoryArm64'): 98 | nfbuild.makeBuildDirectory() 99 | 100 | if buildOptions.checkOption(options, 'generateProjectArm64'): 101 | nfbuild.generateProject(android=True, android_arm=True) 102 | 103 | if buildOptions.checkOption(options, 'buildTargetLibraryArm64'): 104 | nfbuild.buildTarget(library_target) 105 | 106 | if buildOptions.checkOption(options, 'packageArtifacts'): 107 | nfbuild.packageArtifacts() 108 | 109 | 110 | if __name__ == "__main__": 111 | main() 112 | -------------------------------------------------------------------------------- /ci/osx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import sys 24 | 25 | from nfbuildosx import NFBuildOSX 26 | from build_options import BuildOptions 27 | 28 | 29 | def main(): 30 | buildOptions = BuildOptions() 31 | buildOptions.addOption("installDependencies", "Install dependencies") 32 | 33 | buildOptions.addOption("lintCmake", "Lint cmake files") 34 | buildOptions.addOption("lintCpp", "Lint CPP Files") 35 | buildOptions.addOption("lintCppWithInlineChange", 36 | "Lint CPP Files and fix them") 37 | 38 | buildOptions.addOption("makeBuildDirectory", 39 | "Wipe existing build directory") 40 | buildOptions.addOption("generateProject", "Regenerate xcode project") 41 | 42 | buildOptions.addOption("buildTargetCLI", "Build Target: CLI") 43 | buildOptions.addOption("buildTargetLibrary", "Build Target: Library") 44 | 45 | buildOptions.addOption("staticAnalysis", "Run Static Analysis") 46 | 47 | buildOptions.addOption("makeCLI", "Deploy CLI Binary") 48 | buildOptions.addOption("packageArtifacts", "Package the artifacts produced by the build") 49 | 50 | buildOptions.setDefaultWorkflow("Empty workflow", []) 51 | 52 | buildOptions.addWorkflow("lint", "Run lint workflow", [ 53 | 'installDependencies', 54 | 'lintCmake', 55 | 'lintCppWithInlineChange' 56 | ]) 57 | 58 | buildOptions.addWorkflow("build", "Production Build", [ 59 | 'installDependencies', 60 | 'lintCmake', 61 | 'lintCpp', 62 | 'makeBuildDirectory', 63 | 'generateProject', 64 | 'buildTargetCLI', 65 | 'buildTargetLibrary', 66 | 'staticAnalysis', 67 | 'packageArtifacts' 68 | ]) 69 | 70 | options = buildOptions.parseArgs() 71 | buildOptions.verbosePrintBuildOptions(options) 72 | 73 | nfbuild = NFBuildOSX() 74 | library_target = 'NFDriver' 75 | cli_target = 'NFDriverCLI' 76 | 77 | if buildOptions.checkOption(options, 'installDependencies'): 78 | nfbuild.installDependencies() 79 | 80 | if buildOptions.checkOption(options, 'lintCmake'): 81 | nfbuild.lintCmake() 82 | 83 | if buildOptions.checkOption(options, 'lintCppWithInlineChange'): 84 | nfbuild.lintCPP(make_inline_changes=True) 85 | elif buildOptions.checkOption(options, 'lintCpp'): 86 | nfbuild.lintCPP(make_inline_changes=False) 87 | 88 | if buildOptions.checkOption(options, 'makeBuildDirectory'): 89 | nfbuild.makeBuildDirectory() 90 | 91 | if buildOptions.checkOption(options, 'generateProject'): 92 | nfbuild.generateProject() 93 | 94 | if buildOptions.checkOption(options, 'buildTargetLibrary'): 95 | nfbuild.buildTarget(library_target) 96 | if buildOptions.checkOption(options, 'staticAnalysis'): 97 | nfbuild.staticallyAnalyse(library_target, 98 | include_regex='source/.*') 99 | 100 | if buildOptions.checkOption(options, 'buildTargetCLI'): 101 | nfbuild.buildTarget(cli_target) 102 | if buildOptions.checkOption(options, 'staticAnalysis'): 103 | nfbuild.staticallyAnalyse(cli_target, 104 | include_regex='source/.*') 105 | if buildOptions.checkOption(options, "packageArtifacts"): 106 | nfbuild.packageArtifacts() 107 | 108 | 109 | if __name__ == "__main__": 110 | main() 111 | -------------------------------------------------------------------------------- /ci/androidlinux.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import sys 24 | 25 | from nfbuildlinux import NFBuildLinux 26 | from build_options import BuildOptions 27 | 28 | 29 | def main(): 30 | buildOptions = BuildOptions() 31 | buildOptions.addOption("installDependencies", "Install dependencies") 32 | 33 | buildOptions.addOption("makeBuildDirectoryX86", 34 | "Wipe existing build directory for X86 build.") 35 | buildOptions.addOption("generateProjectX86", "Regenerate project for X86 build") 36 | 37 | buildOptions.addOption("buildTargetLibraryX86", "Build Target: Library (X86)") 38 | 39 | buildOptions.addOption("makeBuildDirectoryArm64", 40 | "Wipe existing build directory for ARM64 build.") 41 | buildOptions.addOption("generateProjectArm64", "Regenerate project for ARM64 build") 42 | buildOptions.addOption("packageArtifacts", "Package the artifacts produced by the build") 43 | buildOptions.addOption("buildTargetLibraryArm64", "Build Target: Library (ARM64)") 44 | 45 | buildOptions.setDefaultWorkflow("Empty workflow", []) 46 | 47 | buildOptions.addWorkflow("build", "Production Build", [ 48 | 'installDependencies', 49 | 'makeBuildDirectoryX86', 50 | 'generateProjectX86', 51 | 'buildTargetLibraryX86', 52 | 'packageArtifacts', 53 | 'makeBuildDirectoryArm64', 54 | 'generateProjectArm64', 55 | 'buildTargetLibraryArm64', 56 | 'packageArtifacts' 57 | ]) 58 | 59 | buildOptions.addWorkflow("buildX86", "Production Build (X86)", [ 60 | 'installDependencies', 61 | 'makeBuildDirectoryX86', 62 | 'generateProjectX86', 63 | 'buildTargetLibraryX86', 64 | 'packageArtifacts' 65 | ]) 66 | 67 | buildOptions.addWorkflow("buildArm64", "Production Build (ARM64)", [ 68 | 'installDependencies', 69 | 'makeBuildDirectoryArm64', 70 | 'generateProjectArm64', 71 | 'buildTargetLibraryArm64', 72 | 'packageArtifacts' 73 | ]) 74 | 75 | 76 | options = buildOptions.parseArgs() 77 | buildOptions.verbosePrintBuildOptions(options) 78 | 79 | library_target = 'NFDriver' 80 | nfbuild = NFBuildLinux() 81 | 82 | if buildOptions.checkOption(options, 'installDependencies'): 83 | nfbuild.installDependencies(android=True) 84 | 85 | if buildOptions.checkOption(options, 'makeBuildDirectoryX86'): 86 | nfbuild.makeBuildDirectory() 87 | 88 | if buildOptions.checkOption(options, 'generateProjectX86'): 89 | nfbuild.generateProject(android=True, android_arm=False) 90 | 91 | if buildOptions.checkOption(options, 'buildTargetLibraryX86'): 92 | nfbuild.buildTarget(library_target) 93 | 94 | if buildOptions.checkOption(options, 'packageArtifacts'): 95 | nfbuild.packageArtifacts() 96 | 97 | if buildOptions.checkOption(options, 'makeBuildDirectoryArm64'): 98 | nfbuild.makeBuildDirectory() 99 | 100 | if buildOptions.checkOption(options, 'generateProjectArm64'): 101 | nfbuild.generateProject(android=True, android_arm=True) 102 | 103 | if buildOptions.checkOption(options, 'buildTargetLibraryArm64'): 104 | nfbuild.buildTarget(library_target) 105 | 106 | if buildOptions.checkOption(options, 'packageArtifacts'): 107 | nfbuild.packageArtifacts() 108 | 109 | 110 | if __name__ == "__main__": 111 | main() 112 | -------------------------------------------------------------------------------- /ci/nfbuildlinux.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import fnmatch 24 | import os 25 | import plistlib 26 | import re 27 | import shutil 28 | import subprocess 29 | import sys 30 | 31 | from distutils import dir_util 32 | from nfbuild import NFBuild 33 | 34 | 35 | class NFBuildLinux(NFBuild): 36 | clang_format_binary = 'clang-format-3.9' 37 | 38 | def __init__(self): 39 | super(self.__class__, self).__init__() 40 | self.project_file = 'build.ninja' 41 | self.cmake_binary = 'cmake' 42 | self.android_ndk_folder = '~/ndk' 43 | self.android = False 44 | self.android_arm = False 45 | 46 | def generateProject(self, 47 | ios=False, 48 | android=False, 49 | android_arm=False, 50 | gcc=False): 51 | self.android = android 52 | self.android_arm = android_arm 53 | cmake_call = [ 54 | self.cmake_binary, 55 | '..', 56 | '-GNinja'] 57 | if android or android_arm: 58 | android_abi = 'x86_64' 59 | android_toolchain_name = 'x86_64-llvm' 60 | if android_arm: 61 | android_abi = 'arm64-v8a' 62 | android_toolchain_name = 'arm64-llvm' 63 | cmake_call.extend([ 64 | '-DANDROID=1', 65 | '-DCMAKE_TOOLCHAIN_FILE=' + self.android_ndk_folder + '/build/cmake/android.toolchain.cmake', 66 | '-DANDROID_NDK=' + self.android_ndk_folder, 67 | '-DANDROID_ABI=' + android_abi, 68 | '-DANDROID_NATIVE_API_LEVEL=21', 69 | '-DANDROID_TOOLCHAIN_NAME=' + android_toolchain_name]) 70 | if gcc: 71 | cmake_call.extend(['-DLLVM_STDLIB=0']) 72 | else: 73 | cmake_call.extend(['-DLLVM_STDLIB=1']) 74 | 75 | cmake_result = subprocess.call(cmake_call, cwd=self.build_directory) 76 | if cmake_result != 0: 77 | sys.exit(cmake_result) 78 | 79 | def buildTarget(self, target, sdk='linux', arch='x87_64'): 80 | result = subprocess.call([ 81 | 'ninja', 82 | '-C', 83 | self.build_directory, 84 | '-f', 85 | self.project_file, 86 | target]) 87 | if result != 0: 88 | sys.exit(result) 89 | 90 | def packageArtifacts(self): 91 | lib_name = 'libNFDriver.a' 92 | cli_name = 'NFDriverCLI' 93 | output_folder = os.path.join(self.build_directory, 'output') 94 | artifacts_folder = os.path.join(output_folder, 'NFDriver') 95 | shutil.copytree('include', os.path.join(artifacts_folder, 'include')) 96 | source_folder = os.path.join(self.build_directory, 'source') 97 | lib_matches = self.find_file(source_folder, lib_name) 98 | cli_matches = self.find_file(source_folder, cli_name) 99 | shutil.copyfile(lib_matches[0], os.path.join(artifacts_folder, lib_name)) 100 | if not self.android: 101 | shutil.copyfile(cli_matches[0], os.path.join(artifacts_folder, cli_name)) 102 | output_zip = os.path.join(output_folder, 'libNFDriver.zip') 103 | self.make_archive(artifacts_folder, output_zip) 104 | if self.android: 105 | final_zip_name = 'libNFDriver-androidx86.zip' 106 | if self.android_arm: 107 | final_zip_name = 'libNFDriver-androidArm64.zip' 108 | shutil.copyfile(output_zip, final_zip_name) 109 | -------------------------------------------------------------------------------- /ci/linux.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2021 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import os 24 | import sys 25 | 26 | from nfbuildlinux import NFBuildLinux 27 | from build_options import BuildOptions 28 | 29 | 30 | def main(): 31 | buildOptions = BuildOptions() 32 | buildOptions.addOption("installDependencies", "Install dependencies") 33 | buildOptions.addOption("lintCmake", "Lint cmake files") 34 | buildOptions.addOption("lintCppWithInlineChange", 35 | "Lint CPP Files and fix them") 36 | 37 | buildOptions.addOption("makeBuildDirectory", 38 | "Wipe existing build directory") 39 | buildOptions.addOption("generateProject", "Regenerate project") 40 | 41 | buildOptions.addOption("buildTargetCLI", "Build Target: CLI") 42 | buildOptions.addOption("buildTargetLibrary", "Build Target: Library") 43 | buildOptions.addOption("packageArtifacts", "Package the binary artifacts") 44 | buildOptions.addOption("gnuToolchain", "Build with gcc and libstdc++") 45 | buildOptions.addOption("llvmToolchain", "Build with clang and libc++") 46 | 47 | buildOptions.setDefaultWorkflow("Empty workflow", []) 48 | 49 | buildOptions.addWorkflow("lint", "Run lint workflow", [ 50 | 'installDependencies', 51 | 'lintCmake', 52 | 'lintCppWithInlineChange' 53 | ]) 54 | 55 | buildOptions.addWorkflow("clang_build", "Production build with clang", [ 56 | 'llvmToolchain', 57 | 'installDependencies', 58 | 'lintCmake', 59 | 'makeBuildDirectory', 60 | 'generateProject', 61 | 'buildTargetLibrary', 62 | 'buildTargetCLI', 63 | 'packageArtifacts' 64 | ]) 65 | 66 | buildOptions.addWorkflow("gcc_build", "Production build with gcc", [ 67 | 'gnuToolchain', 68 | 'installDependencies', 69 | 'lintCmake', 70 | 'makeBuildDirectory', 71 | 'generateProject', 72 | 'buildTargetLibrary', 73 | 'buildTargetCLI', 74 | 'packageArtifacts' 75 | ]) 76 | 77 | options = buildOptions.parseArgs() 78 | buildOptions.verbosePrintBuildOptions(options) 79 | 80 | library_target = 'NFDriver' 81 | cli_target = 'NFDriverCLI' 82 | nfbuild = NFBuildLinux() 83 | 84 | if buildOptions.checkOption(options, 'installDependencies'): 85 | nfbuild.installDependencies() 86 | 87 | if buildOptions.checkOption(options, 'lintCmake'): 88 | nfbuild.lintCmake() 89 | 90 | if buildOptions.checkOption(options, 'lintCppWithInlineChange'): 91 | nfbuild.lintCPP(make_inline_changes=True) 92 | 93 | if buildOptions.checkOption(options, 'makeBuildDirectory'): 94 | nfbuild.makeBuildDirectory() 95 | 96 | if buildOptions.checkOption(options, 'generateProject'): 97 | if buildOptions.checkOption(options, 'gnuToolchain'): 98 | os.environ['CC'] = 'gcc' 99 | os.environ['CXX'] = 'g++' 100 | nfbuild.generateProject(gcc=True) 101 | elif buildOptions.checkOption(options, 'llvmToolchain'): 102 | os.environ['CC'] = 'clang-3.9' 103 | os.environ['CXX'] = 'clang++-3.9' 104 | nfbuild.generateProject(gcc=False) 105 | else: 106 | nfbuild.generateProject() 107 | 108 | if buildOptions.checkOption(options, 'buildTargetCLI'): 109 | nfbuild.buildTarget(cli_target) 110 | 111 | if buildOptions.checkOption(options, 'buildTargetLibrary'): 112 | nfbuild.buildTarget(library_target) 113 | 114 | if buildOptions.checkOption(options, 'packageArtifacts'): 115 | nfbuild.packageArtifacts() 116 | 117 | if __name__ == "__main__": 118 | main() 119 | -------------------------------------------------------------------------------- /ci/nfbuildwindows.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import fnmatch 24 | import os 25 | import plistlib 26 | import re 27 | import shutil 28 | import subprocess 29 | import sys 30 | 31 | from distutils import dir_util 32 | from nfbuild import NFBuild 33 | 34 | 35 | class NFBuildWindows(NFBuild): 36 | def __init__(self): 37 | super(self.__class__, self).__init__() 38 | self.project_file = 'NFDriver.sln' 39 | self.cmake_binary = 'cmake' 40 | self.android = False 41 | 42 | def generateProject(self, 43 | ios=False, 44 | android=False, 45 | android_arm=False): 46 | cmake_call = [ 47 | self.cmake_binary, 48 | '..', 49 | '-G'] 50 | if android or android_arm: 51 | self.android = True 52 | self.build_project = 'build.ninja' 53 | android_abi = 'x86_64' 54 | android_toolchain_name = 'x86_64-llvm' 55 | if android_arm: 56 | android_abi = 'arm64-v8a' 57 | android_toolchain_name = 'arm64-llvm' 58 | cmake_call.extend([ 59 | 'Ninja', 60 | '-DANDROID=1', 61 | '-DCMAKE_TOOLCHAIN_FILE=' + self.android_ndk_folder + '/build/cmake/android.toolchain.cmake', 62 | '-DANDROID_NDK=' + self.android_ndk_folder, 63 | '-DANDROID_ABI=' + android_abi, 64 | '-DANDROID_NATIVE_API_LEVEL=21', 65 | '-DANDROID_TOOLCHAIN_NAME=' + android_toolchain_name]) 66 | else: 67 | cl_exe = 'cl.exe' 68 | rc_exe = 'rc.exe' 69 | link_exe = 'link.exe' 70 | cmake_call.extend([ 71 | 'Visual Studio 15 2017 Win64', 72 | '-DCMAKE_SYSTEM_NAME=WindowsStore', 73 | '-DCMAKE_SYSTEM_VERSION=10.0']) 74 | cmake_result = subprocess.call(cmake_call, cwd=self.build_directory) 75 | if cmake_result != 0: 76 | sys.exit(cmake_result) 77 | 78 | def buildTarget(self, target, sdk='macosx', arch='x86_64'): 79 | result = 0 80 | if self.android: 81 | result = subprocess.call([ 82 | self.ninja_binary, 83 | '-C', 84 | self.build_directory, 85 | '-f', 86 | self.project_file, 87 | target]) 88 | else: 89 | result = subprocess.call([ 90 | 'msbuild.exe', 91 | os.path.join(self.build_directory, 'NFDriver.sln'), 92 | '/t:NFDriver;' + target]) 93 | if result != 0: 94 | sys.exit(result) 95 | 96 | def packageArtifacts(self): 97 | lib_name = 'NFDriver.lib' 98 | cli_name = 'NFDriverCLI.exe' 99 | output_folder = os.path.join(self.build_directory, 'output') 100 | artifacts_folder = os.path.join(output_folder, 'NFDriver') 101 | shutil.copytree('include', os.path.join(artifacts_folder, 'include')) 102 | source_folder = os.path.join(self.build_directory, 'source') 103 | lib_matches = self.find_file(source_folder, lib_name) 104 | cli_matches = self.find_file(source_folder, cli_name) 105 | shutil.copyfile(lib_matches[0], os.path.join(artifacts_folder, lib_name)) 106 | if not self.android: 107 | shutil.copyfile(cli_matches[0], os.path.join(artifacts_folder, cli_name)) 108 | output_zip = os.path.join(output_folder, 'libNFDriver.zip') 109 | self.make_archive(artifacts_folder, output_zip) 110 | if self.android: 111 | final_zip_name = 'libNFDriver-androidx86.zip' 112 | if self.android_arm: 113 | final_zip_name = 'libNFDriver-androidArm64.zip' 114 | shutil.copyfile(output_zip, final_zip_name) 115 | -------------------------------------------------------------------------------- /source/NFDriver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #include 22 | 23 | #include 24 | 25 | #include "NFDriverAdapter.h" 26 | #include "NFDriverFileAACImplementation.h" 27 | #include "NFDriverFileImplementation.h" 28 | #include "NFDriverFileMP3Implementation.h" 29 | #include "nfdriver_generated_header.h" 30 | 31 | namespace nativeformat { 32 | namespace driver { 33 | 34 | const char *version() { 35 | return NFDRIVER_VERSION; 36 | } 37 | 38 | extern const std::string NF_DRIVER_BITRATE_KEY = "bitrate"; 39 | extern const std::string NF_DRIVER_WAV_SIZE_KEY = "wavsize"; 40 | 41 | int bitrateOption(const std::map &options) { 42 | if (options.count(NF_DRIVER_BITRATE_KEY)) { 43 | return std::stoi(options.at(NF_DRIVER_BITRATE_KEY)); 44 | } 45 | return 128; 46 | } 47 | 48 | NFDriverFileWAVHeaderAudioFormat wavsizeOption(const std::map &options) { 49 | if (options.count(NF_DRIVER_WAV_SIZE_KEY)) { 50 | switch (std::stoi(options.at(NF_DRIVER_WAV_SIZE_KEY))) { 51 | case 16: 52 | return NFDriverFileWAVHeaderAudioFormatPCM; 53 | case 32: 54 | return NFDriverFileWAVHeaderAudioFormatIEEEFloat; 55 | default: 56 | assert(false && "Invalid wav size option, must be 16 or 32"); 57 | } 58 | } 59 | return NFDriverFileWAVHeaderAudioFormatIEEEFloat; 60 | } 61 | 62 | NFDriver *NFDriver::createNFDriver(void *clientdata, 63 | NF_STUTTER_CALLBACK stutter_callback, 64 | NF_RENDER_CALLBACK render_callback, 65 | NF_ERROR_CALLBACK error_callback, 66 | NF_WILL_RENDER_CALLBACK will_render_callback, 67 | NF_DID_RENDER_CALLBACK did_render_callback, 68 | OutputType output_type, 69 | const char *output_destination, 70 | std::map options) { 71 | switch (output_type) { 72 | case OutputTypeSoundCard: 73 | return new NFSoundCardDriver(clientdata, 74 | stutter_callback, 75 | render_callback, 76 | error_callback, 77 | will_render_callback, 78 | did_render_callback); 79 | case OutputTypeFile: 80 | return new NFDriverFileImplementation(clientdata, 81 | stutter_callback, 82 | render_callback, 83 | error_callback, 84 | will_render_callback, 85 | did_render_callback, 86 | output_destination, 87 | wavsizeOption(options)); 88 | case OutputTypeMP3File: 89 | #if _WIN32 90 | assert(false && "No support for MP3 file driver on windows."); 91 | #else 92 | return new NFDriverFileMP3Implementation(clientdata, 93 | stutter_callback, 94 | render_callback, 95 | error_callback, 96 | will_render_callback, 97 | did_render_callback, 98 | output_destination, 99 | bitrateOption(options)); 100 | #endif 101 | case OutputTypeAACFile: 102 | #if __APPLE__ 103 | return new NFDriverFileAACImplementation(clientdata, 104 | stutter_callback, 105 | render_callback, 106 | error_callback, 107 | will_render_callback, 108 | did_render_callback, 109 | output_destination, 110 | bitrateOption(options)); 111 | #else 112 | assert(false && "No support for AAC file driver on this platform."); 113 | #endif 114 | } 115 | return 0; 116 | } 117 | 118 | } // namespace driver 119 | } // namespace nativeformat 120 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /ci/nfbuild.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import fnmatch 24 | import json 25 | import os 26 | import pprint 27 | import shutil 28 | import subprocess 29 | import sys 30 | 31 | 32 | class NFBuild(object): 33 | def __init__(self): 34 | ci_json_file = os.path.join('ci', 'ci.json') 35 | self.build_configuration = json.load(open(ci_json_file)) 36 | self.pretty_printer = pprint.PrettyPrinter(indent=4) 37 | self.current_working_directory = os.getcwd() 38 | self.build_directory = 'build' 39 | self.output_directory = os.path.join(self.build_directory, 'output') 40 | self.statically_analyzed_files = [] 41 | self.android = False 42 | 43 | def build_print(self, print_string): 44 | print(print_string) 45 | sys.stdout.flush() 46 | 47 | def makeBuildDirectory(self): 48 | if os.path.exists(self.build_directory): 49 | shutil.rmtree(self.build_directory) 50 | os.makedirs(self.build_directory) 51 | os.makedirs(self.output_directory) 52 | 53 | def installDependencies(self, android=False): 54 | self.android = android 55 | 56 | def generateProject(self, 57 | ios=False, 58 | android=False, 59 | android_arm=False): 60 | assert True, "generateProject should be overridden by subclass" 61 | 62 | def buildTarget(self, target, sdk='macosx'): 63 | assert True, "buildTarget should be overridden by subclass" 64 | 65 | def lintCPPFile(self, filepath, make_inline_changes=False): 66 | current_source = open(filepath, 'r').read() 67 | clang_format_call = [self.clang_format_binary] 68 | if make_inline_changes: 69 | clang_format_call.append('-i') 70 | clang_format_call.append(filepath) 71 | new_source = subprocess.check_output(clang_format_call).decode() 72 | if current_source != new_source and not make_inline_changes: 73 | self.build_print( 74 | filepath + " failed C++ lint, file should look like:") 75 | self.build_print(new_source) 76 | return False 77 | return True 78 | 79 | def lintCPPDirectory(self, directory, make_inline_changes=False): 80 | passed = True 81 | for root, dirnames, filenames in os.walk(directory): 82 | for filename in filenames: 83 | if not filename.endswith(('.cpp', '.h', '.m', '.mm')): 84 | continue 85 | full_filepath = os.path.join(root, filename) 86 | if not self.lintCPPFile(full_filepath, make_inline_changes): 87 | passed = False 88 | return passed 89 | 90 | def lintCPP(self, make_inline_changes=False): 91 | lint_result = self.lintCPPDirectory('source', make_inline_changes) 92 | lint_result &= self.lintCPPDirectory('include', make_inline_changes) 93 | if not lint_result: 94 | sys.exit(1) 95 | 96 | def lintCmakeFile(self, filepath): 97 | self.build_print("Linting: " + filepath) 98 | return subprocess.call(['cmakelint', filepath]) == 0 99 | 100 | def lintCmakeDirectory(self, directory): 101 | passed = True 102 | for root, dirnames, filenames in os.walk(directory): 103 | for filename in filenames: 104 | if not filename.endswith('CMakeLists.txt'): 105 | continue 106 | full_filepath = os.path.join(root, filename) 107 | if not self.lintCmakeFile(full_filepath): 108 | passed = False 109 | return passed 110 | 111 | def lintCmake(self): 112 | lint_result = self.lintCmakeFile('CMakeLists.txt') 113 | lint_result &= self.lintCmakeDirectory('source') 114 | if not lint_result: 115 | sys.exit(1) 116 | 117 | def staticallyAnalyse(self, target, include_regex=None): 118 | assert True, "staticallyAnalyse should be overridden by subclass" 119 | 120 | def buildGradle(self): 121 | exit_code = subprocess.call(['./gradlew', 'assemble']) 122 | if exit_code != 0: 123 | sys.exit(exit_code) 124 | 125 | def packageArtifacts(self): 126 | assert True, "packageArtifacts should be overridden by subclass" 127 | 128 | def make_archive(self, source, destination): 129 | base = os.path.basename(destination) 130 | name = base.split('.')[0] 131 | format = base.split('.')[1] 132 | archive_from = os.path.dirname(source) 133 | archive_to = os.path.basename(source.strip(os.sep)) 134 | print(source, destination, archive_from, archive_to) 135 | shutil.make_archive(name, format, archive_from, archive_to) 136 | shutil.move('%s.%s'%(name,format), destination) 137 | 138 | def find_file(self, directory, file_name, multiple_files=False): 139 | matches = [] 140 | for root, dirnames, filenames in os.walk(directory): 141 | for filename in fnmatch.filter(filenames, file_name): 142 | matches.append(os.path.join(root, filename)) 143 | if not multiple_files: 144 | break 145 | if not multiple_files and len(matches) > 0: 146 | break 147 | return matches 148 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /source/NFDriverFileImplementation.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #include "NFDriverFileImplementation.h" 22 | 23 | #include 24 | #include 25 | 26 | namespace nativeformat { 27 | namespace driver { 28 | 29 | int bytesPerFormat(NFDriverFileWAVHeaderAudioFormat wav_format) { 30 | switch (wav_format) { 31 | case NFDriverFileWAVHeaderAudioFormatPCM: 32 | return sizeof(short); 33 | case NFDriverFileWAVHeaderAudioFormatIEEEFloat: 34 | return sizeof(float); 35 | } 36 | return sizeof(float); 37 | } 38 | 39 | NFDriverFileImplementation::NFDriverFileImplementation(void *clientdata, 40 | NF_STUTTER_CALLBACK stutter_callback, 41 | NF_RENDER_CALLBACK render_callback, 42 | NF_ERROR_CALLBACK error_callback, 43 | NF_WILL_RENDER_CALLBACK will_render_callback, 44 | NF_DID_RENDER_CALLBACK did_render_callback, 45 | const char *output_destination, 46 | NFDriverFileWAVHeaderAudioFormat wav_format) 47 | : _clientdata(clientdata), 48 | _stutter_callback(stutter_callback), 49 | _render_callback(render_callback), 50 | _error_callback(error_callback), 51 | _will_render_callback(will_render_callback), 52 | _did_render_callback(did_render_callback), 53 | _output_destination(output_destination), 54 | _wav_format(wav_format), 55 | _thread(nullptr) {} 56 | 57 | NFDriverFileImplementation::~NFDriverFileImplementation() { 58 | if (isPlaying()) { 59 | setPlaying(false); 60 | } 61 | } 62 | 63 | bool NFDriverFileImplementation::isPlaying() const { 64 | return !!_thread; 65 | } 66 | 67 | void NFDriverFileImplementation::setPlaying(bool playing) { 68 | if (isPlaying() == playing) { 69 | return; 70 | } 71 | 72 | if (!playing) { 73 | _run = false; 74 | if (std::this_thread::get_id() != _thread->get_id()) { 75 | _thread->join(); 76 | } 77 | _thread = nullptr; 78 | } else { 79 | _run = true; 80 | _thread = std::make_shared(&NFDriverFileImplementation::run, this); 81 | } 82 | } 83 | 84 | void NFDriverFileImplementation::run(NFDriverFileImplementation *driver) { 85 | FILE *fhandle = fopen(driver->_output_destination.c_str(), "wb"); 86 | if (fhandle == nullptr) { 87 | driver->_error_callback(driver->_clientdata, "Failed to create file.", 0); 88 | } 89 | 90 | // Write the header. 91 | struct { 92 | unsigned char RIFF[4]; 93 | unsigned int chunkSize; 94 | unsigned char WAVE[4]; 95 | unsigned char FMT[4]; 96 | unsigned int sixteen; 97 | unsigned short int audioFormat; 98 | unsigned short int numChannels; 99 | unsigned int samplerate; 100 | unsigned int byteRate; 101 | unsigned short int blockAlign; 102 | unsigned short int bitsPerSample; 103 | unsigned char DATA[4]; 104 | unsigned int dataSize; 105 | } header; 106 | std::memcpy(header.RIFF, "RIFF", 4); 107 | std::memcpy(header.WAVE, "WAVE", 4); 108 | std::memcpy(header.FMT, "fmt ", 4); 109 | header.sixteen = 16; 110 | header.audioFormat = driver->_wav_format; 111 | header.numChannels = NF_DRIVER_CHANNELS; 112 | header.bitsPerSample = bytesPerFormat(driver->_wav_format) * 8; 113 | header.samplerate = NF_DRIVER_SAMPLERATE; 114 | header.byteRate = header.samplerate * header.numChannels * (header.bitsPerSample / 8); 115 | header.blockAlign = header.numChannels * (header.bitsPerSample / 8); 116 | std::memcpy(header.DATA, "data", 4); 117 | fwrite(&header, 1, sizeof(header), fhandle); 118 | 119 | // Rendering. 120 | const auto buffer_samples = NF_DRIVER_SAMPLE_BLOCK_SIZE * NF_DRIVER_CHANNELS; 121 | float buffer[buffer_samples]; 122 | while (driver->_run) { 123 | for (int i = 0; i < buffer_samples; ++i) { 124 | buffer[i] = 0.0f; 125 | } 126 | driver->_will_render_callback(driver->_clientdata); 127 | const size_t num_frames = static_cast( 128 | driver->_render_callback(driver->_clientdata, buffer, NF_DRIVER_SAMPLE_BLOCK_SIZE)); 129 | if (num_frames < 1) { 130 | driver->_stutter_callback(driver->_clientdata); 131 | } else { 132 | switch (driver->_wav_format) { 133 | case NFDriverFileWAVHeaderAudioFormatPCM: { 134 | std::vector converted_samples(num_frames * NF_DRIVER_CHANNELS); 135 | for (int i = 0; i < converted_samples.size(); ++i) { 136 | converted_samples[i] = 137 | static_cast(buffer[i] * std::numeric_limits::max()); 138 | } 139 | fwrite(converted_samples.data(), sizeof(short), num_frames * NF_DRIVER_CHANNELS, fhandle); 140 | break; 141 | } 142 | case NFDriverFileWAVHeaderAudioFormatIEEEFloat: 143 | fwrite(buffer, sizeof(float), num_frames * NF_DRIVER_CHANNELS, fhandle); 144 | break; 145 | } 146 | } 147 | 148 | driver->_did_render_callback(driver->_clientdata); 149 | } 150 | 151 | // Write the size into the header and close the file. 152 | unsigned int position = 153 | static_cast((static_cast(ftell(fhandle)) - sizeof(header))); 154 | fseek(fhandle, 40, SEEK_SET); 155 | fwrite(&position, 1, 4, fhandle); 156 | position += 36; 157 | fseek(fhandle, 4, SEEK_SET); 158 | fwrite(&position, 1, 4, fhandle); 159 | fclose(fhandle); 160 | } 161 | 162 | } // namespace driver 163 | } // namespace nativeformat 164 | -------------------------------------------------------------------------------- /ci/build_options.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import sys 24 | import argparse 25 | 26 | # Argument parsing abstraction designed for the build context. 27 | # Generates an array of buildOptions from an input arg list. 28 | # Allows options to be grouped into workflows, and workflows 29 | # to be overridden with minor changes. 30 | 31 | # Example commands: 32 | 33 | # run the lint workflow with an override to disable dependencies install: 34 | # python build_options.py lint -installDependencies=0 -v 35 | 36 | # run the default workflow with an override to make it fly. 37 | # python build_options.py -doFlyAway=1 -v 38 | 39 | 40 | class BuildOptions: 41 | 42 | def __init__(self): 43 | self.options = {} 44 | self.workflows = {} 45 | self.verbose = True 46 | 47 | # Define a build option, with documentation. 48 | def addOption(self, option, doc): 49 | self.options[option] = doc 50 | 51 | # Define a workflow, which consists of a group of build options. 52 | # This will be an optional single positional argument. 53 | def addWorkflow(self, workflow, doc, options): 54 | for option in options: 55 | if option not in self.options: 56 | self.flushed_print( 57 | "Error: Workflow %s contains invalid option %s" 58 | % (workflow, option)) 59 | exit(1) 60 | self.workflows[workflow] = { 61 | 'doc': doc, 62 | 'options': {x: '1' for x in options} 63 | } 64 | 65 | # Define the default workflow, if the cmdline input doesn't include 66 | # a position workflow arg. 67 | def setDefaultWorkflow(self, doc, options): 68 | self.addWorkflow("default", doc, options) 69 | 70 | def getOptionDoc(self, option): 71 | return self.options[option] 72 | 73 | def getWorkflowHelp(self): 74 | str = "" 75 | for workflow, data in self.workflows.items(): 76 | str += "%s:\n\t%s\n" % (workflow, data['doc']) 77 | return str 78 | 79 | # Parse input arguments 80 | def parseArgs(self): 81 | parser = argparse.ArgumentParser( 82 | formatter_class=argparse.RawTextHelpFormatter) 83 | 84 | # Verbose is automatically defined 85 | parser.add_argument("-quiet", "-q", 86 | help="Mute build steps", 87 | action='store_true') 88 | 89 | # Can have any number of workflows. If 0, it runs the default workflow. 90 | # If more than 1, the built options get intersected 91 | parser.add_argument("workflows", nargs='*', default=['default'], 92 | help=self.getWorkflowHelp()) 93 | 94 | # Define build options with leading - 95 | for k, v in self.options.items(): 96 | parser.add_argument("-" + k, help=v) 97 | args = parser.parse_args() 98 | argHash = vars(args) 99 | result = {} 100 | 101 | if 'quiet' in argHash and argHash['quiet']: 102 | self.verbose = False 103 | 104 | for workflow in argHash['workflows']: 105 | if workflow not in self.workflows: 106 | self.flushed_print("Error: Specified invalid workflow %s" % 107 | workflow) 108 | exit(1) 109 | # Load options from selected workflows 110 | result.update(self.workflows[workflow]['options']) 111 | 112 | # Apply option overrides to workflow 113 | for option, doc in self.options.items(): 114 | if option in argHash and argHash[option]: 115 | result[option] = argHash[option] 116 | 117 | return [k for k, v in result.items() if v == '1'] 118 | 119 | # Print which build options are enabled. 120 | def verbosePrintBuildOptions(self, args): 121 | if self.verbose: 122 | self.flushed_print("Build Options: " + str(args)) 123 | 124 | # Caller should call this when a build option is being executed 125 | # for verbose build logging. 126 | def verbosePrint(self, option): 127 | if self.verbose: 128 | self.flushed_print("===== %s =====" % self.getOptionDoc(option)) 129 | 130 | # Check if this given build option is defined. If so, print the step has 131 | # begun and return true so caller can run the step. 132 | # Sanity check the input option to catch typos. 133 | def checkOption(self, args, arg, quiet=False): 134 | if arg not in self.options: 135 | self.flushed_print("Error: Checked undefined option %s" % arg) 136 | exit(1) 137 | if arg in args: 138 | if not quiet: 139 | self.verbosePrint(arg) 140 | return True 141 | return False 142 | 143 | def flushed_print(self, str): 144 | print(str) 145 | sys.stdout.flush() 146 | 147 | 148 | # Create a toy version for testing if user executes this file directly 149 | def test_version(): 150 | 151 | buildOptions = BuildOptions() 152 | buildOptions.addOption("option1", "option 1 description") 153 | buildOptions.addOption("option2", "option 2 description") 154 | buildOptions.addOption("option3", "option 3 description") 155 | buildOptions.setDefaultWorkflow("Default workflow description", [ 156 | 'option1' 157 | ]) 158 | buildOptions.addWorkflow("workflow1", "workflow1 description", [ 159 | 'option1', 160 | 'option2' 161 | ]) 162 | buildOptions.addWorkflow("workflow2", "workflow2 description", [ 163 | 'option2', 164 | 'option3' 165 | ]) 166 | 167 | options = buildOptions.parseArgs() 168 | buildOptions.verbosePrintBuildOptions(options) 169 | 170 | if buildOptions.checkOption(options, "option1"): 171 | buildOptions.flushed_print("running option1") 172 | 173 | if buildOptions.checkOption(options, "option2"): 174 | buildOptions.flushed_print("running option2") 175 | 176 | if buildOptions.checkOption(options, "option3"): 177 | buildOptions.flushed_print("running option3") 178 | 179 | 180 | if __name__ == "__main__": 181 | test_version() 182 | -------------------------------------------------------------------------------- /include/NFDriver/NFDriver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #pragma once 22 | 23 | #include 24 | #include 25 | 26 | #if _WIN32 27 | #define WIN32_LEAN_AND_MEAN 28 | #define _USE_MATH_DEFINES 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #endif 35 | 36 | #if __ANDROID__ 37 | #include 38 | #endif 39 | 40 | #define OutputTypeWAV32 OutputTypeFile 41 | 42 | namespace nativeformat { 43 | namespace driver { 44 | 45 | //! Enum that defines the desired output type 46 | /*! This enum will define the output destination. */ 47 | typedef enum { 48 | OutputTypeSoundCard, /*!< Output to hardware (the local sound card). */ 49 | OutputTypeFile, /* Output to a file. */ 50 | OutputTypeMP3File, /* Output to an MP3 file. */ 51 | OutputTypeAACFile /* Output to an AAC file. */ 52 | } OutputType; 53 | 54 | /*! Number of samples to process at a time */ 55 | #define NF_DRIVER_SAMPLE_BLOCK_SIZE 1024 56 | /*! The sample rate of the blocks to be sampled. In units of samples per second */ 57 | #define NF_DRIVER_SAMPLERATE 44100 58 | /*! Number of channels to output a time. 2 means we're outputting stereo */ 59 | #define NF_DRIVER_CHANNELS 2 60 | 61 | /*! 62 | * \brief Callback called when driver stutters. 63 | * 64 | * \param clientdata Client specific data that gets used by the callback. 65 | */ 66 | typedef void (*NF_STUTTER_CALLBACK)(void *clientdata); 67 | /*! 68 | * \brief Callback called before rendering. 69 | * 70 | * \param clientdata Client specific data that gets used by the callback. 71 | */ 72 | typedef void (*NF_WILL_RENDER_CALLBACK)(void *clientdata); 73 | /*! 74 | * \brief Callback called after rendering. 75 | * 76 | * \param clientdata Client specific data that gets used by the callback. 77 | */ 78 | typedef void (*NF_DID_RENDER_CALLBACK)(void *clientdata); 79 | /*! 80 | * \brief Callback that gathers frames to be output. 81 | * 82 | * \param clientdata Client specific data that gets used by the callback. 83 | * \param frames Data that will be subsequently be output to the OutputType of choice. 84 | * \param numberOfFrames Desired number of frames to store in frames. 85 | * \return Number of frames that were stored in frames. 86 | */ 87 | typedef int (*NF_RENDER_CALLBACK)(void *clientdata, float *frames, int numberOfFrames); 88 | /*! 89 | * \brief Callback called when NFDriver experiences an error. 90 | * \param clientdata Client specific data that gets used by the callback. 91 | * \param errorMessage Error message to print when this callback gets called. 92 | * \param errorCode Unique number (preferably) that's associated with the error message. 93 | */ 94 | typedef void (*NF_ERROR_CALLBACK)(void *clientdata, const char *errorMessage, int errorCode); 95 | 96 | /*! 97 | * \brief The version of this library. 98 | * 99 | * \return The version, for example 1.0 in use 100 | */ 101 | extern const char *version(); 102 | /// The key to use when specifying the bitrate to the driver. 103 | extern const std::string NF_DRIVER_BITRATE_KEY; 104 | /// The key to use when specifying what size the WAV samples should be. 105 | extern const std::string NF_DRIVER_WAV_SIZE_KEY; 106 | 107 | /*! 108 | * Interface used tracking state of the audio output. 109 | */ 110 | class NFDriver { 111 | public: 112 | /*! 113 | * \brief Thread-safe function to check whether the driver is currently outputting samples. 114 | * 115 | * \return True if there are samples being output. False otherwise. 116 | */ 117 | virtual bool isPlaying() const = 0; 118 | /*! 119 | * \brief Thread-safe function to set if the driver should output samples. 120 | * 121 | * \param playing True to tell the driver to start outputting samples. 122 | * False if not. 123 | */ 124 | virtual void setPlaying(bool playing) = 0; 125 | /*! \brief Destructor */ 126 | virtual ~NFDriver(){}; 127 | 128 | #if __ANDROID__ 129 | /*! 130 | * \brief Called when using Android and an app has just started. 131 | * 132 | * This function is meant to be used with the Java Native Interface 133 | * (JNI). It will be called when the application using NFDriver has just 134 | * launched. This function should only be called ONCE per app life-cycle. 135 | * \param env JNI Environment 136 | * \param self The object calling this function 137 | * \param clientdata Client specific data that gets used by the callback. 138 | * \param errorCallback Function to call in case NFDriver errors. 139 | */ 140 | static void onAppLaunch(JNIEnv *env, 141 | jobject self, 142 | void *clientdata, 143 | NF_ERROR_CALLBACK errorCallback); 144 | #endif 145 | 146 | /*! 147 | * \brief Factory function to create an NFDriver for the appropriate device. 148 | * 149 | * \param clientdata Client specific data that gets used by the callback. 150 | * \param stutter_callback Function to call if playback stutters. 151 | * \param render_callback Function called when we have samples to output. 152 | * \param error_callback Function called when the driver errors. 153 | * \param will_render_callback Function called before render_callback. 154 | * \param did_render_callback Function called after render_callback. 155 | * \param outputType Desired output destination. 156 | * \param output_destination Name of output destination if it is a 157 | * named device, file etc. 158 | * \param options A map containing options in key value form. 159 | * \return Instance of NFDriver. 160 | */ 161 | static NFDriver *createNFDriver(void *clientdata, 162 | NF_STUTTER_CALLBACK stutter_callback, 163 | NF_RENDER_CALLBACK render_callback, 164 | NF_ERROR_CALLBACK error_callback, 165 | NF_WILL_RENDER_CALLBACK will_render_callback, 166 | NF_DID_RENDER_CALLBACK did_render_callback, 167 | OutputType outputType, 168 | const char *output_destination = nullptr, 169 | std::map options = {}); 170 | }; 171 | 172 | } // namespace driver 173 | } // namespace nativeformat 174 | -------------------------------------------------------------------------------- /source/NFDriverFileMP3Implementation.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #include "NFDriverFileMP3Implementation.h" 22 | 23 | #include 24 | #include 25 | #if _WIN32 26 | #include 27 | #else 28 | #include 29 | #endif 30 | 31 | #include 32 | 33 | namespace nativeformat { 34 | namespace driver { 35 | 36 | NFDriverFileMP3Implementation::NFDriverFileMP3Implementation( 37 | void *clientdata, 38 | NF_STUTTER_CALLBACK stutter_callback, 39 | NF_RENDER_CALLBACK render_callback, 40 | NF_ERROR_CALLBACK error_callback, 41 | NF_WILL_RENDER_CALLBACK will_render_callback, 42 | NF_DID_RENDER_CALLBACK did_render_callback, 43 | const char *output_destination, 44 | int bitrate) 45 | : _clientdata(clientdata), 46 | _stutter_callback(stutter_callback), 47 | _render_callback(render_callback), 48 | _error_callback(error_callback), 49 | _will_render_callback(will_render_callback), 50 | _did_render_callback(did_render_callback), 51 | _output_destination(output_destination), 52 | _bitrate(bitrate), 53 | _thread(nullptr) {} 54 | 55 | NFDriverFileMP3Implementation::~NFDriverFileMP3Implementation() { 56 | if (isPlaying()) { 57 | setPlaying(false); 58 | } 59 | } 60 | 61 | bool NFDriverFileMP3Implementation::isPlaying() const { 62 | return !!_thread; 63 | } 64 | 65 | void NFDriverFileMP3Implementation::setPlaying(bool playing) { 66 | if (isPlaying() == playing) { 67 | return; 68 | } 69 | 70 | if (!playing) { 71 | _run = false; 72 | if (std::this_thread::get_id() != _thread->get_id()) { 73 | _thread->join(); 74 | } 75 | _thread = nullptr; 76 | } else { 77 | _run = true; 78 | _thread = std::make_shared(&NFDriverFileMP3Implementation::run, this); 79 | } 80 | } 81 | 82 | void NFDriverFileMP3Implementation::run(NFDriverFileMP3Implementation *driver) { 83 | // Open LAME lib 84 | std::string lame_lib_path(getenv("LAME_DYLIB")); 85 | #if _WIN32 86 | HINSTANCE lame_handle = LoadLibrary(lame_lib_path.c_str()); 87 | decltype(&lame_init) lame_init_dynamic = 88 | (decltype(&lame_init))GetProcAddress(lame_handle, "lame_init"); 89 | decltype(&lame_set_in_samplerate) lame_set_in_samplerate_dynamic = 90 | (decltype(&lame_set_in_samplerate))GetProcAddress(lame_handle, "lame_set_in_samplerate"); 91 | decltype(&lame_set_VBR) lame_set_VBR_dynamic = 92 | (decltype(&lame_set_VBR))GetProcAddress(lame_handle, "lame_set_VBR"); 93 | decltype(&lame_init_params) lame_init_params_dynamic = 94 | (decltype(&lame_init_params))GetProcAddress(lame_handle, "lame_init_params"); 95 | decltype(&lame_encode_buffer_interleaved_ieee_float) 96 | lame_encode_buffer_interleaved_ieee_float_dynamic = 97 | (decltype(&lame_encode_buffer_interleaved_ieee_float))GetProcAddress( 98 | lame_handle, "lame_encode_buffer_interleaved_ieee_float"); 99 | decltype(&lame_encode_flush) lame_encode_flush_dynamic = 100 | (decltype(&lame_encode_flush))GetProcAddress(lame_handle, "lame_encode_flush"); 101 | decltype(&lame_close) lame_close_dynamic = 102 | (decltype(&lame_close))GetProcAddress(lame_handle, "lame_close"); 103 | decltype(&lame_set_mode) lame_set_mode_dynamic = 104 | (decltype(&lame_set_mode))GetProcAddress(lame_handle, "lame_set_mode"); 105 | decltype(&lame_set_VBR_mean_bitrate_kbps) lame_set_VBR_mean_bitrate_kbps_dynamic = 106 | (decltype(&lame_set_VBR_mean_bitrate_kbps))GetProcAddress(lame_handle, 107 | "lame_set_VBR_mean_bitrate_kbps"); 108 | #else 109 | void *lame_handle = dlopen(lame_lib_path.c_str(), RTLD_LAZY); 110 | decltype(&lame_init) lame_init_dynamic = (decltype(&lame_init))dlsym(lame_handle, "lame_init"); 111 | decltype(&lame_set_in_samplerate) lame_set_in_samplerate_dynamic = 112 | (decltype(&lame_set_in_samplerate))dlsym(lame_handle, "lame_set_in_samplerate"); 113 | decltype(&lame_set_VBR) lame_set_VBR_dynamic = 114 | (decltype(&lame_set_VBR))dlsym(lame_handle, "lame_set_VBR"); 115 | decltype(&lame_init_params) lame_init_params_dynamic = 116 | (decltype(&lame_init_params))dlsym(lame_handle, "lame_init_params"); 117 | decltype(&lame_encode_buffer_interleaved_ieee_float) 118 | lame_encode_buffer_interleaved_ieee_float_dynamic = 119 | (decltype(&lame_encode_buffer_interleaved_ieee_float))dlsym( 120 | lame_handle, "lame_encode_buffer_interleaved_ieee_float"); 121 | decltype(&lame_encode_flush) lame_encode_flush_dynamic = 122 | (decltype(&lame_encode_flush))dlsym(lame_handle, "lame_encode_flush"); 123 | decltype(&lame_close) lame_close_dynamic = 124 | (decltype(&lame_close))dlsym(lame_handle, "lame_close"); 125 | decltype(&lame_set_mode) lame_set_mode_dynamic = 126 | (decltype(&lame_set_mode))dlsym(lame_handle, "lame_set_mode"); 127 | decltype(&lame_set_VBR_mean_bitrate_kbps) lame_set_VBR_mean_bitrate_kbps_dynamic = 128 | (decltype(&lame_set_VBR_mean_bitrate_kbps))dlsym(lame_handle, 129 | "lame_set_VBR_mean_bitrate_kbps"); 130 | #endif 131 | 132 | // Open file 133 | FILE *fhandle = fopen(driver->_output_destination.c_str(), "wb"); 134 | if (fhandle == nullptr) { 135 | driver->_error_callback(driver->_clientdata, "Failed to create file.", 0); 136 | } 137 | 138 | // Open LAME 139 | lame_t lame = lame_init_dynamic(); 140 | lame_set_in_samplerate_dynamic(lame, NF_DRIVER_SAMPLERATE); 141 | lame_set_VBR_dynamic(lame, vbr_default); 142 | lame_set_mode_dynamic(lame, STEREO); 143 | lame_set_VBR_mean_bitrate_kbps_dynamic(lame, driver->_bitrate); 144 | lame_init_params_dynamic(lame); 145 | 146 | // Perform Encoding 147 | unsigned char mp3_buffer[8192]; 148 | do { 149 | const auto buffer_samples = NF_DRIVER_SAMPLE_BLOCK_SIZE * NF_DRIVER_CHANNELS; 150 | float buffer[buffer_samples]; 151 | for (int i = 0; i < buffer_samples; ++i) { 152 | buffer[i] = 0.0f; 153 | } 154 | driver->_will_render_callback(driver->_clientdata); 155 | const size_t num_frames = 156 | (size_t)driver->_render_callback(driver->_clientdata, buffer, NF_DRIVER_SAMPLE_BLOCK_SIZE); 157 | if (num_frames < 1) { 158 | driver->_stutter_callback(driver->_clientdata); 159 | } else { 160 | const auto write = lame_encode_buffer_interleaved_ieee_float_dynamic( 161 | lame, buffer, num_frames, mp3_buffer, sizeof(mp3_buffer)); 162 | fwrite(mp3_buffer, write, 1, fhandle); 163 | } 164 | driver->_did_render_callback(driver->_clientdata); 165 | } while (driver->_run); 166 | const auto write = lame_encode_flush_dynamic(lame, mp3_buffer, sizeof(mp3_buffer)); 167 | fwrite(mp3_buffer, write, 1, fhandle); 168 | 169 | // Cleanup 170 | lame_close_dynamic(lame); 171 | fclose(fhandle); 172 | #ifndef _WIN32 173 | dlclose(lame_handle); 174 | #endif 175 | } 176 | 177 | } // namespace driver 178 | } // namespace nativeformat 179 | -------------------------------------------------------------------------------- /source/NFDriverFileAACImplementation.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #include "NFDriverFileAACImplementation.h" 22 | 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | namespace nativeformat { 29 | namespace driver { 30 | 31 | NFDriverFileAACImplementation::NFDriverFileAACImplementation( 32 | void *clientdata, 33 | NF_STUTTER_CALLBACK stutter_callback, 34 | NF_RENDER_CALLBACK render_callback, 35 | NF_ERROR_CALLBACK error_callback, 36 | NF_WILL_RENDER_CALLBACK will_render_callback, 37 | NF_DID_RENDER_CALLBACK did_render_callback, 38 | const char *output_destination, 39 | int bitrate) 40 | : _clientdata(clientdata), 41 | _stutter_callback(stutter_callback), 42 | _render_callback(render_callback), 43 | _error_callback(error_callback), 44 | _will_render_callback(will_render_callback), 45 | _did_render_callback(did_render_callback), 46 | _output_destination(output_destination), 47 | _bitrate(bitrate), 48 | _thread(nullptr) {} 49 | 50 | NFDriverFileAACImplementation::~NFDriverFileAACImplementation() { 51 | if (isPlaying()) { 52 | setPlaying(false); 53 | } 54 | } 55 | 56 | bool NFDriverFileAACImplementation::isPlaying() const { 57 | return !!_thread; 58 | } 59 | 60 | void NFDriverFileAACImplementation::setPlaying(bool playing) { 61 | if (isPlaying() == playing) { 62 | return; 63 | } 64 | 65 | if (!playing) { 66 | _run = false; 67 | if (std::this_thread::get_id() != _thread->get_id()) { 68 | _thread->join(); 69 | } 70 | _thread = nullptr; 71 | } else { 72 | _run = true; 73 | _thread = std::make_shared(&NFDriverFileAACImplementation::run, this); 74 | } 75 | } 76 | 77 | void NFDriverFileAACImplementation::run(NFDriverFileAACImplementation *driver) { 78 | CFStringRef output_file_str = 79 | CFStringCreateWithCString(NULL, driver->_output_destination.c_str(), kCFStringEncodingUTF8); 80 | CFURLRef output_file_url = CFURLCreateWithFileSystemPath( 81 | kCFAllocatorDefault, output_file_str, kCFURLPOSIXPathStyle, false); 82 | AudioStreamBasicDescription description; 83 | description.mFormatID = kAudioFormatMPEG4AAC; 84 | description.mSampleRate = NF_DRIVER_SAMPLERATE; 85 | description.mFormatFlags = kMPEG4Object_AAC_Main; 86 | description.mChannelsPerFrame = NF_DRIVER_CHANNELS; 87 | description.mBitsPerChannel = 0; 88 | description.mBytesPerFrame = 0; 89 | description.mBytesPerPacket = 0; 90 | description.mFramesPerPacket = 1024; 91 | 92 | // Create audio file 93 | ExtAudioFileRef audio_file; 94 | OSStatus result = noErr; 95 | if ((result = ExtAudioFileCreateWithURL(output_file_url, 96 | kAudioFileM4AType, 97 | &description, 98 | NULL, 99 | kAudioFileFlags_EraseFile, 100 | &audio_file)) != noErr) { 101 | driver->_error_callback(driver->_clientdata, "Failed to create file.", result); 102 | CFRelease(output_file_url); 103 | CFRelease(output_file_str); 104 | return; 105 | } 106 | 107 | // Set the input format 108 | AudioStreamBasicDescription input_format; 109 | input_format.mSampleRate = description.mSampleRate; 110 | input_format.mFormatID = kAudioFormatLinearPCM; 111 | input_format.mFormatFlags = 112 | kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian; 113 | input_format.mChannelsPerFrame = NF_DRIVER_CHANNELS; 114 | input_format.mBitsPerChannel = sizeof(float) * 8; 115 | input_format.mBytesPerFrame = sizeof(float) * NF_DRIVER_CHANNELS; 116 | input_format.mFramesPerPacket = 1; 117 | input_format.mBytesPerPacket = input_format.mBytesPerFrame * input_format.mFramesPerPacket; 118 | const auto input_format_size = sizeof(input_format); 119 | if ((result = ExtAudioFileSetProperty( 120 | audio_file, kExtAudioFileProperty_ClientDataFormat, input_format_size, &input_format)) != 121 | noErr) { 122 | driver->_error_callback(driver->_clientdata, "Failed to set input format on file.", result); 123 | ExtAudioFileDispose(audio_file); 124 | CFRelease(output_file_url); 125 | CFRelease(output_file_str); 126 | return; 127 | } 128 | 129 | // Find the converter 130 | AudioConverterRef converter; 131 | UInt32 converter_format_size = sizeof(converter); 132 | if ((result = ExtAudioFileGetProperty( 133 | audio_file, kExtAudioFileProperty_AudioConverter, &converter_format_size, &converter)) != 134 | noErr) { 135 | driver->_error_callback(driver->_clientdata, "Failed to fetch converter.", result); 136 | ExtAudioFileDispose(audio_file); 137 | CFRelease(output_file_url); 138 | CFRelease(output_file_str); 139 | return; 140 | } 141 | 142 | // Set the bitrate 143 | UInt32 bit_rate = driver->_bitrate * 1000; 144 | if ((result = AudioConverterSetProperty( 145 | converter, kAudioConverterEncodeBitRate, sizeof(bit_rate), &bit_rate)) != noErr) { 146 | driver->_error_callback(driver->_clientdata, "Failed to set bitrate.", result); 147 | ExtAudioFileDispose(audio_file); 148 | CFRelease(output_file_url); 149 | CFRelease(output_file_str); 150 | return; 151 | } 152 | 153 | // Tell the file the converter config changed 154 | CFArrayRef config = nullptr; 155 | if ((result = ExtAudioFileSetProperty( 156 | audio_file, kExtAudioFileProperty_ConverterConfig, sizeof(config), &config)) != noErr) { 157 | driver->_error_callback(driver->_clientdata, "Failed to set converter config.", result); 158 | ExtAudioFileDispose(audio_file); 159 | CFRelease(output_file_url); 160 | CFRelease(output_file_str); 161 | return; 162 | } 163 | 164 | // Create the buffer 165 | AudioBufferList buffer_list; 166 | buffer_list.mNumberBuffers = 1; 167 | buffer_list.mBuffers[0].mNumberChannels = NF_DRIVER_CHANNELS; 168 | buffer_list.mBuffers[0].mDataByteSize = input_format.mBytesPerFrame * NF_DRIVER_SAMPLE_BLOCK_SIZE; 169 | buffer_list.mBuffers[0].mData = malloc(buffer_list.mBuffers[0].mDataByteSize); 170 | 171 | // Run the driver 172 | do { 173 | float *buffer = static_cast(buffer_list.mBuffers[0].mData); 174 | const auto buffer_samples = NF_DRIVER_SAMPLE_BLOCK_SIZE * NF_DRIVER_CHANNELS; 175 | for (int i = 0; i < buffer_samples; ++i) { 176 | buffer[i] = 0.0f; 177 | } 178 | driver->_will_render_callback(driver->_clientdata); 179 | const size_t num_frames = 180 | (size_t)driver->_render_callback(driver->_clientdata, buffer, NF_DRIVER_SAMPLE_BLOCK_SIZE); 181 | if (num_frames < 1) { 182 | driver->_stutter_callback(driver->_clientdata); 183 | } else { 184 | if ((result = ExtAudioFileWrite(audio_file, num_frames, &buffer_list)) != noErr) { 185 | driver->_error_callback(driver->_clientdata, "Failed to write frames to disk.", result); 186 | break; 187 | } 188 | } 189 | driver->_did_render_callback(driver->_clientdata); 190 | } while (driver->_run); 191 | 192 | // Cleanup 193 | free(buffer_list.mBuffers[0].mData); 194 | ExtAudioFileDispose(audio_file); 195 | CFRelease(output_file_url); 196 | CFRelease(output_file_str); 197 | } 198 | 199 | } // namespace driver 200 | } // namespace nativeformat 201 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # This file was generated with clang-format 4.0.0 2 | # $ clang-format -style Google -dump-config > .clang-format 3 | # 4 | # Read all about the available options here: 5 | # http://releases.llvm.org/4.0.0/tools/clang/docs/ClangFormatStyleOptions.html 6 | # 7 | # Spotify specific tweaks: 8 | # 9 | # --- 10 | # Language: Cpp 11 | # 12 | # - Standard Auto -> Cpp11 13 | # A> instead of A > 14 | # 15 | # - ColumnLimit 80 -> 100 16 | # We allow up to 100 characters per line 17 | # 18 | # - PointerAlignment Left -> Right 19 | # Always put '*' and '&' close to variable/function 20 | # Guidelines allows both alignments, but we want right (for legacy reasons) 21 | # 22 | # - DerivePointerAlignment: true -> false 23 | # Always put '*' and '&' close to variable/function 24 | # Guidelines allows both alignments, but we want right (for legacy reasons) 25 | # 26 | # - AllowShortFunctionsOnASingleLine: All -> Inline 27 | # We don't want to put out of class function definitions on a single line. 28 | # Standard allows it, but we prefer to keep the oneliners for methods inside classes. 29 | # 30 | # - BinPackArguments: true -> false 31 | # A function declaration’s or function definition’s parameters will either all be on the same 32 | # line or will have one line each. 33 | # Guidelines allows both true and false, but we like false better so we prefer that. 34 | # 35 | # - BinPackParameters: true -> false 36 | # A function call’s arguments will either be all on the same line or will have one line each. 37 | # Guidelines allows both true and false, but we like false better so we prefer that. 38 | # 39 | # - ForEachMacros: Remove all listed macros 40 | # We don't use foreach macros so clang-format shouldn't special treat any keywords. 41 | # 42 | # - IncludeCategories: 43 | # Tweaked priorities to match our preferred order. 44 | # 45 | # - ObjCSpaceAfterProperty: false -> true 46 | # Matches Spotifys Objective-C coding style, see https://github.com/spotify/ios-style 47 | # 48 | # - ObjCSpaceBeforeProtocolList: false -> true 49 | # Matches Spotifys Objective-C coding style, see https://github.com/spotify/ios-style 50 | # 51 | # --- 52 | # Language: ObjC 53 | # 54 | # Hand tweaked config that aims to match Spotifys Objective-C coding style, 55 | # see https://github.com/spotify/ios-style. 56 | # 57 | --- 58 | Language: Cpp 59 | # BasedOnStyle: Google 60 | AccessModifierOffset: -1 61 | AlignAfterOpenBracket: Align 62 | AlignConsecutiveAssignments: false 63 | AlignConsecutiveDeclarations: false 64 | AlignEscapedNewlinesLeft: true 65 | AlignOperands: true 66 | AlignTrailingComments: true 67 | AllowAllParametersOfDeclarationOnNextLine: true 68 | AllowShortBlocksOnASingleLine: false 69 | AllowShortCaseLabelsOnASingleLine: false 70 | AllowShortFunctionsOnASingleLine: Inline 71 | AllowShortIfStatementsOnASingleLine: true 72 | AllowShortLoopsOnASingleLine: true 73 | AlwaysBreakAfterDefinitionReturnType: None 74 | AlwaysBreakAfterReturnType: None 75 | AlwaysBreakBeforeMultilineStrings: true 76 | AlwaysBreakTemplateDeclarations: true 77 | BinPackArguments: false 78 | BinPackParameters: false 79 | # Note: BraceWrapping is ignored since BreakBeforeBraces isn't set to Custom 80 | BraceWrapping: 81 | AfterClass: false 82 | AfterControlStatement: false 83 | AfterEnum: false 84 | AfterFunction: false 85 | AfterNamespace: false 86 | AfterObjCDeclaration: false 87 | AfterStruct: false 88 | AfterUnion: false 89 | BeforeCatch: false 90 | BeforeElse: false 91 | IndentBraces: false 92 | BreakBeforeBinaryOperators: None 93 | BreakBeforeBraces: Attach 94 | BreakBeforeTernaryOperators: true 95 | BreakConstructorInitializersBeforeComma: false 96 | BreakAfterJavaFieldAnnotations: false 97 | BreakStringLiterals: true 98 | ColumnLimit: 100 99 | CommentPragmas: '^ IWYU pragma:' 100 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 101 | ConstructorInitializerIndentWidth: 4 102 | ContinuationIndentWidth: 4 103 | Cpp11BracedListStyle: true 104 | DerivePointerAlignment: false 105 | DisableFormat: false 106 | ExperimentalAutoDetectBinPacking: false 107 | ForEachMacros: [] 108 | IncludeCategories: 109 | - Regex: '^' 110 | Priority: 3 111 | - Regex: '^' 112 | Priority: 4 113 | - Regex: '^["<]base/.*.h[">]' 114 | Priority: 5 115 | - Regex: '^".*"' 116 | Priority: 6 117 | - Regex: '^<[a-z_]*>' 118 | Priority: 1 119 | - Regex: '^<.*>' 120 | Priority: 2 121 | IncludeIsMainRegex: '([-_](test|unittest))?$' 122 | IndentCaseLabels: true 123 | IndentWidth: 2 124 | IndentWrappedFunctionNames: false 125 | JavaScriptQuotes: Leave 126 | JavaScriptWrapImports: true 127 | KeepEmptyLinesAtTheStartOfBlocks: false 128 | MacroBlockBegin: '' 129 | MacroBlockEnd: '' 130 | MaxEmptyLinesToKeep: 1 131 | NamespaceIndentation: None 132 | ObjCBlockIndentWidth: 2 133 | ObjCSpaceAfterProperty: true 134 | ObjCSpaceBeforeProtocolList: true 135 | PenaltyBreakBeforeFirstCallParameter: 1 136 | PenaltyBreakComment: 300 137 | PenaltyBreakFirstLessLess: 120 138 | PenaltyBreakString: 1000 139 | PenaltyExcessCharacter: 1000000 140 | PenaltyReturnTypeOnItsOwnLine: 200 141 | PointerAlignment: Right 142 | ReflowComments: true 143 | SortIncludes: true 144 | SpaceAfterCStyleCast: false 145 | SpaceAfterTemplateKeyword: true 146 | SpaceBeforeAssignmentOperators: true 147 | SpaceBeforeParens: ControlStatements 148 | SpaceInEmptyParentheses: false 149 | SpacesBeforeTrailingComments: 2 150 | SpacesInAngles: false 151 | SpacesInCStyleCastParentheses: false 152 | SpacesInContainerLiterals: true 153 | SpacesInParentheses: false 154 | SpacesInSquareBrackets: false 155 | Standard: Cpp11 156 | TabWidth: 8 157 | UseTab: Never 158 | 159 | # 160 | # Objective-C 161 | # 162 | --- 163 | Language: ObjC 164 | # BasedOnStyle: Google 165 | AccessModifierOffset: -1 166 | AlignAfterOpenBracket: Align 167 | AlignConsecutiveAssignments: false 168 | AlignConsecutiveDeclarations: false 169 | AlignEscapedNewlinesLeft: true 170 | AlignOperands: true 171 | AlignTrailingComments: true 172 | AllowAllParametersOfDeclarationOnNextLine: true 173 | AllowShortBlocksOnASingleLine: false 174 | AllowShortCaseLabelsOnASingleLine: false 175 | AllowShortFunctionsOnASingleLine: false 176 | AllowShortIfStatementsOnASingleLine: false 177 | AllowShortLoopsOnASingleLine: false 178 | AlwaysBreakAfterDefinitionReturnType: None 179 | AlwaysBreakAfterReturnType: None 180 | AlwaysBreakBeforeMultilineStrings: true 181 | AlwaysBreakTemplateDeclarations: true 182 | BinPackArguments: false 183 | BinPackParameters: false 184 | BraceWrapping: 185 | AfterClass: false 186 | AfterControlStatement: false 187 | AfterEnum: false 188 | AfterFunction: true 189 | AfterNamespace: false 190 | AfterObjCDeclaration: false 191 | AfterStruct: false 192 | AfterUnion: false 193 | BeforeCatch: false 194 | BeforeElse: false 195 | IndentBraces: false 196 | BreakBeforeBinaryOperators: None 197 | BreakBeforeBraces: Custom 198 | BreakBeforeTernaryOperators: true 199 | BreakConstructorInitializersBeforeComma: false 200 | BreakAfterJavaFieldAnnotations: false 201 | BreakStringLiterals: true 202 | ColumnLimit: 120 203 | CommentPragmas: '^ IWYU pragma:' 204 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 205 | ConstructorInitializerIndentWidth: 4 206 | ContinuationIndentWidth: 4 207 | Cpp11BracedListStyle: true 208 | DerivePointerAlignment: false 209 | DisableFormat: false 210 | ExperimentalAutoDetectBinPacking: false 211 | ForEachMacros: [] 212 | IncludeCategories: 213 | - Regex: '^' 214 | Priority: 3 215 | - Regex: '^' 216 | Priority: 4 217 | - Regex: '^["<]base/.*.h[">]' 218 | Priority: 5 219 | - Regex: '^".*"' 220 | Priority: 6 221 | - Regex: '^<[a-z_]*>' 222 | Priority: 1 223 | - Regex: '^<.*>' 224 | Priority: 2 225 | IncludeIsMainRegex: '([-_](test|unittest))?$' 226 | IndentCaseLabels: true 227 | IndentWidth: 4 228 | IndentWrappedFunctionNames: false 229 | JavaScriptQuotes: Leave 230 | JavaScriptWrapImports: true 231 | KeepEmptyLinesAtTheStartOfBlocks: false 232 | MacroBlockBegin: '' 233 | MacroBlockEnd: '' 234 | MaxEmptyLinesToKeep: 1 235 | NamespaceIndentation: None 236 | ObjCBlockIndentWidth: 4 237 | ObjCSpaceAfterProperty: true 238 | ObjCSpaceBeforeProtocolList: true 239 | PenaltyBreakBeforeFirstCallParameter: 1 240 | PenaltyBreakComment: 300 241 | PenaltyBreakFirstLessLess: 120 242 | PenaltyBreakString: 1000 243 | PenaltyExcessCharacter: 1000000 244 | PenaltyReturnTypeOnItsOwnLine: 200 245 | PointerAlignment: Right 246 | ReflowComments: true 247 | SortIncludes: true 248 | SpaceAfterCStyleCast: false 249 | SpaceAfterTemplateKeyword: true 250 | SpaceBeforeAssignmentOperators: true 251 | SpaceBeforeParens: ControlStatements 252 | SpaceInEmptyParentheses: false 253 | SpacesBeforeTrailingComments: 2 254 | SpacesInAngles: false 255 | SpacesInCStyleCastParentheses: false 256 | SpacesInContainerLiterals: true 257 | SpacesInParentheses: false 258 | SpacesInSquareBrackets: false 259 | Standard: Cpp11 260 | TabWidth: 4 261 | UseTab: Never -------------------------------------------------------------------------------- /ci/nfbuildosx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | * Copyright (c) 2018 Spotify AB. 4 | * 5 | * Licensed to the Apache Software Foundation (ASF) under one 6 | * or more contributor license agreements. See the NOTICE file 7 | * distributed with this work for additional information 8 | * regarding copyright ownership. The ASF licenses this file 9 | * to you under the Apache License, Version 2.0 (the 10 | * "License"); you may not use this file except in compliance 11 | * with the License. You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, 16 | * software distributed under the License is distributed on an 17 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | * KIND, either express or implied. See the License for the 19 | * specific language governing permissions and limitations 20 | * under the License. 21 | ''' 22 | 23 | import fnmatch 24 | import os 25 | import plistlib 26 | import re 27 | import shutil 28 | import subprocess 29 | import sys 30 | 31 | from distutils import dir_util 32 | from nfbuild import NFBuild 33 | 34 | 35 | class NFBuildOSX(NFBuild): 36 | def __init__(self): 37 | super(self.__class__, self).__init__() 38 | self.project_file = os.path.join( 39 | self.build_directory, 40 | 'NFDriver.xcodeproj') 41 | self.clang_format_binary = 'clang-format' 42 | self.cmake_binary = 'cmake' 43 | self.android_ndk_folder = '/usr/local/share/android-ndk' 44 | self.ninja_binary = 'ninja' 45 | self.ios = False 46 | self.android = False 47 | self.android_arm = False 48 | 49 | def generateProject(self, 50 | ios=False, 51 | android=False, 52 | android_arm=False): 53 | self.use_ninja = android or android_arm 54 | self.ios = ios 55 | self.android = android 56 | self.android_arm = android_arm 57 | cmake_call = [ 58 | self.cmake_binary, 59 | '..'] 60 | if android or android_arm: 61 | android_abi = 'x86_64' 62 | android_toolchain_name = 'x86_64-llvm' 63 | if android_arm: 64 | android_abi = 'arm64-v8a' 65 | android_toolchain_name = 'arm64-llvm' 66 | cmake_call.extend([ 67 | '-GNinja', 68 | '-DANDROID=1', 69 | '-DCMAKE_TOOLCHAIN_FILE=' + self.android_ndk_folder + '/build/cmake/android.toolchain.cmake', 70 | '-DANDROID_NDK=' + self.android_ndk_folder, 71 | '-DANDROID_ABI=' + android_abi, 72 | '-DANDROID_NATIVE_API_LEVEL=21', 73 | '-DANDROID_TOOLCHAIN_NAME=' + android_toolchain_name]) 74 | self.project_file = 'build.ninja' 75 | else: 76 | cmake_call.extend(['-GXcode']) 77 | if ios: 78 | cmake_call.extend(['-DIOS=1']) 79 | cmake_result = subprocess.call(cmake_call, cwd=self.build_directory) 80 | if cmake_result != 0: 81 | sys.exit(cmake_result) 82 | 83 | def buildTarget(self, target, sdk='macosx', arch='x86_64'): 84 | result = 0 85 | if self.use_ninja: 86 | result = subprocess.call([ 87 | self.ninja_binary, 88 | '-C', 89 | self.build_directory, 90 | '-f', 91 | self.project_file, 92 | target]) 93 | else: 94 | result = subprocess.call([ 95 | 'xcodebuild', 96 | '-project', 97 | self.project_file, 98 | '-target', 99 | target, 100 | '-sdk', 101 | sdk, 102 | '-arch', 103 | arch, 104 | '-configuration', 105 | 'Release', 106 | 'build']) 107 | if result != 0: 108 | sys.exit(result) 109 | 110 | def staticallyAnalyse(self, target, include_regex=None): 111 | diagnostics_key = 'diagnostics' 112 | files_key = 'files' 113 | exceptions_key = 'static_analyzer_exceptions' 114 | static_file_exceptions = [] 115 | static_analyzer_result = subprocess.check_output([ 116 | 'xcodebuild', 117 | '-project', 118 | self.project_file, 119 | '-target', 120 | target, 121 | '-sdk', 122 | 'macosx', 123 | '-configuration', 124 | 'Release', 125 | '-dry-run', 126 | 'analyze']).decode() 127 | analyze_command = '--analyze' 128 | for line in static_analyzer_result.splitlines(): 129 | if analyze_command not in line: 130 | continue 131 | static_analyzer_line_words = line.split() 132 | analyze_command_index = static_analyzer_line_words.index( 133 | analyze_command) 134 | source_file = static_analyzer_line_words[analyze_command_index + 1] 135 | if source_file.startswith(self.current_working_directory): 136 | source_file = source_file[ 137 | len(self.current_working_directory)+1:] 138 | if include_regex is not None: 139 | if not re.match(include_regex, source_file): 140 | continue 141 | if source_file in self.statically_analyzed_files: 142 | continue 143 | self.build_print('Analysing ' + source_file) 144 | stripped_command = line.strip() 145 | clang_result = subprocess.call(stripped_command, shell=True) 146 | if clang_result: 147 | sys.exit(clang_result) 148 | self.statically_analyzed_files.append(source_file) 149 | static_analyzer_check_passed = True 150 | for root, dirnames, filenames in os.walk(self.build_directory): 151 | for filename in fnmatch.filter(filenames, '*.plist'): 152 | full_filepath = os.path.join(root, filename) 153 | static_analyzer_result = {} 154 | with open(full_filepath, "rb") as full_filepath_handle: 155 | static_analyzer_result = plistlib.load(full_filepath_handle) 156 | if 'clang_version' not in static_analyzer_result \ 157 | or files_key not in static_analyzer_result \ 158 | or diagnostics_key not in static_analyzer_result: 159 | continue 160 | if len(static_analyzer_result[files_key]) == 0: 161 | continue 162 | for static_analyzer_file in static_analyzer_result[files_key]: 163 | if static_analyzer_file in static_file_exceptions: 164 | continue 165 | if self.current_working_directory not in static_analyzer_file: 166 | continue 167 | normalised_file = static_analyzer_file[ 168 | len(self.current_working_directory)+1:] 169 | if normalised_file in \ 170 | self.build_configuration[exceptions_key]: 171 | continue 172 | self.build_print('Issues found in: ' + normalised_file) 173 | for static_analyzer_issue in \ 174 | static_analyzer_result[diagnostics_key]: 175 | self.pretty_printer.pprint(static_analyzer_issue) 176 | sys.stdout.flush() 177 | static_analyzer_check_passed = False 178 | if not static_analyzer_check_passed: 179 | sys.exit(1) 180 | 181 | def packageArtifacts(self): 182 | lib_name = 'libNFDriver.a' 183 | cli_name = 'NFDriverCLI' 184 | output_folder = os.path.join(self.build_directory, 'output') 185 | artifacts_folder = os.path.join(output_folder, 'NFDriver') 186 | shutil.copytree('include', os.path.join(artifacts_folder, 'include')) 187 | source_folder = os.path.join(self.build_directory, 'source') 188 | lib_matches = self.find_file(source_folder, lib_name) 189 | cli_matches = self.find_file(source_folder, cli_name) 190 | if not self.android: 191 | lipo_command = ['lipo', '-create'] 192 | for lib_match in lib_matches: 193 | lipo_command.append(lib_match) 194 | lipo_command.extend(['-output', os.path.join(artifacts_folder, lib_name)]) 195 | lipo_result = subprocess.call(lipo_command) 196 | if lipo_result != 0: 197 | sys.exit(lipo_result) 198 | else: 199 | shutil.copyfile(lib_matches[0], os.path.join(artifacts_folder, lib_name)) 200 | if not self.ios and not self.android: 201 | shutil.copyfile(cli_matches[0], os.path.join(artifacts_folder, cli_name)) 202 | output_zip = os.path.join(output_folder, 'libNFDriver.zip') 203 | self.make_archive(artifacts_folder, output_zip) 204 | if self.android: 205 | final_zip_name = 'libNFDriver-androidx86.zip' 206 | if self.android_arm: 207 | final_zip_name = 'libNFDriver-androidArm64.zip' 208 | shutil.copyfile(output_zip, final_zip_name) 209 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NFDriver 2 | 3 | [![CircleCI](https://circleci.com/gh/spotify/NFDriver/tree/master.svg?style=svg)](https://circleci.com/gh/spotify/NFDriver/tree/master) 4 | [![Build status](https://ci.appveyor.com/api/projects/status/m2bn47mr3u26k6jq/branch/master?svg=true)](https://ci.appveyor.com/project/8W9aG/nfdriver/branch/master) 5 | [![License](https://img.shields.io/github/license/spotify/NFDriver.svg)](LICENSE) 6 | [![Readme Score](http://readme-score-api.herokuapp.com/score.svg?url=https://github.com/spotify/nfdriver)](http://clayallsopp.github.io/readme-score?url=https://github.com/spotify/nfdriver) 7 | 8 | A cross platform C++ audio driver with low latency. 9 | 10 | - [x] 📱 iOS 9.0+ 11 | - [x] 💻 OS X 10.11+ 12 | - [x] 🐧 Ubuntu Bionic 18.04+ (clang 3.9 or gcc 7.5) 13 | - [x] 🤖 Android NDK r17b+ 14 | - [x] 🖥️ Microsoft Windows Store 10 15 | 16 | Developed at Spotify 2019-2022, Discontinued and handed over to new maintainers January 2023 17 | 18 | ## Raison D'être :thought_balloon: 19 | 20 | During the development of innovative new audio experiences, we required a driver that would reliably work on a number of different platforms for our experimentation purposes. We noticed that at the time of development no such open source software existed (that managed to support all the platforms we were looking for), so we decided to create a new one. Given that the common language we could use across our experimentation platforms was C++ we decided on that as the language of choice for our interface. It is also worth noting that this wasn't just designed for front end use cases, and as such has the ability to write out WAV files to disk at above real time speeds to support backend rendering use cases. 21 | 22 | ## Architecture :triangular_ruler: 23 | 24 | `NFDriver` is designed as a common C++ interface to write information to different systems sound drivers in a low latency. The API simply allows you to create a driver that will then call the callbacks fed into it every time a new block of audio data is requested. It uses very basic C functions in order to reduce the amount of latency when interfacing to it, and to prevent unwanted locks in some implementations of the C++ 11 STL. It always has a fixed block size of 1024 samples it will ask for at any one time. It also has the ability to report errors, stutters, and give callbacks before and after the rendering of a block. 25 | 26 | You may notice it has a fixed samplerate and number of channels. This was done due to this being the standard configuration in music output, so in order to lower the complexity of the API and the way each wrapper acts with the system we decided to hardcode these values. 27 | 28 | It is designed with 2 major layers: 29 | 30 | - **The Normalisation layer**, which takes input and resamples it to whatever channel format and samplerate the driver expects. 31 | - **The System Layer**, which interfaces to the operating systems audio drivers. 32 | 33 | Our support table looks like so: 34 | 35 | | OS | Underlying Framework | Status | 36 | | ------------- |:------------------------------------------------------------------------------------------------------------:| -------:| 37 | | iOS | [Core Audio](https://developer.apple.com/documentation/coreaudio?changes=_8) | Stable | 38 | | OSX | [Core Audio](https://developer.apple.com/documentation/coreaudio?changes=_8) | Stable | 39 | | Linux | [ALSA](https://en.wikipedia.org/wiki/Advanced_Linux_Sound_Architecture) | Stable | 40 | | Android | [OpenSL ES](https://developer.android.com/ndk/guides/audio/opensl/) | Beta | 41 | | Windows | [Media Foundation](https://docs.microsoft.com/en-us/windows/desktop/medfound/about-the-media-foundation-sdk) | Alpha | 42 | 43 | In terms of bouncing to files, our support table looks like so: 44 | 45 | | Format | Options | Comments | Support | 46 | | ------ | ------------- | -------------------------------------------------------------- | --------------------------------- | 47 | | WAV | wavsize : int | Writes a WAV file to the output destination. | iOS, OSX, Linux, Android, Windows | 48 | | MP3 | bitrate : int | Writes an MP3 file to the output destination. | iOS, OSX, Linux, Android | 49 | | AAC | bitrate : int | Writes an AAC file to the output destination. | iOS, OSX | 50 | 51 | Note that using MP3 will require you to define the environment variable `LAME_DYLIB`. Make sure you allow your users the option to replace this library to comply with its [LGPL License](https://lame.sourceforge.io/license.txt). We do not statically link against LAME so we do not take on its LGPL status and retain our MIT license. 52 | 53 | ## Installation :inbox_tray: 54 | 55 | `NFDriver` is a cmake project, while you can feel free to download the prebuilt static libraries it is recommended to use cmake to install this project into your wider project. In order to add this into a wider Cmake project, simply add the following line to your `CMakeLists.txt` file: 56 | 57 | ``` 58 | add_subdirectory(NFDriver) 59 | ``` 60 | 61 | ### For iOS/OSX 62 | 63 | Generate an Xcode project from the Cmake project like so: 64 | 65 | ```shell 66 | $ mkdir build 67 | $ cd build 68 | $ cmake .. -GXcode 69 | ``` 70 | 71 | ### For Linux 72 | Generate a Ninja project from the Cmake project like so: 73 | 74 | ```shell 75 | $ mkdir build 76 | $ cd build 77 | $ cmake .. -GNinja 78 | ``` 79 | 80 | ### For Android 81 | Use gradle to include the NFDriver project like so: 82 | 83 | ``` 84 | android { 85 | compileSdkVersion 26 86 | defaultConfig { 87 | applicationId "com.spotify.nfdrivertest_android" 88 | minSdkVersion 19 89 | targetSdkVersion 26 90 | versionCode 1 91 | versionName "1.0" 92 | externalNativeBuild { 93 | cmake { 94 | cppFlags "" 95 | arguments "-DANDROID_APP=1 -DANDROID=1" 96 | } 97 | } 98 | } 99 | 100 | sourceSets { 101 | main { 102 | jniLibs.srcDirs = ['src/main/cpp'] 103 | } 104 | } 105 | 106 | externalNativeBuild { 107 | cmake { 108 | path "../CMakeLists.txt" 109 | } 110 | } 111 | } 112 | ``` 113 | 114 | ### For Windows 115 | Generate a Visual Studio project from the Cmake project like so: 116 | 117 | ```shell 118 | $ mkdir build 119 | $ cd build 120 | $ cmake .. -G "Visual Studio 12 2013 Win64" 121 | ``` 122 | 123 | ## Usage example :eyes: 124 | For examples of this in use, see the demo program `src/cli/NFDriverCLI.cpp`. The API is rather small, it basically has a create function, and a stop/start interface on the created class. You feed the create function with the necessary callbacks used for inputting audio data and then press play. 125 | 126 | ```C++ 127 | NF_STUTTER_CALLBACK stutter_callback = [](void *clientdata) { 128 | printf("Driver Stuttered...\n"); 129 | }; 130 | 131 | NF_RENDER_CALLBACK render_callback = [](void *clientdata, float *frames, int numberOfFrames) { 132 | const float samplerate = 2000.0f; 133 | const float multiplier = (2.0f * float(M_PI) * samplerate) / float(NF_DRIVER_SAMPLERATE); 134 | static unsigned int sinewave = 0; 135 | for (int n = 0; n < numberOfFrames; n++) { 136 | float audio = sinf(multiplier * sinewave++); 137 | for (int i = 0; i < NF_DRIVER_CHANNELS; ++i) { 138 | *frames++ = audio; 139 | } 140 | } 141 | return numberOfFrames; 142 | 143 | }; 144 | NF_ERROR_CALLBACK error_callback = [](void *clientdata, const char *errorMessage, int errorCode) { 145 | printf("Driver Error (%d): %s\n", errorCode, errorMessage); 146 | }; 147 | 148 | NF_WILL_RENDER_CALLBACK will_render_callback = [](void *clientdata) {}; 149 | 150 | NF_DID_RENDER_CALLBACK did_render_callback = [](void *clientdata) {}; 151 | 152 | NFDriver *driver = nativeformat::driver::createDriver(nullptr /* Anything client specific to use in the callbacks */, 153 | stutter_callback /* called on stutter */, 154 | render_callback /* called when we have samples to output */, 155 | error_callback /* called upon driver error */, 156 | will_render_callback /* Called before render_callback */, 157 | did_render_callback /* Called after render callback */, 158 | nativeformat::driver::OutputTypeSoundCard); 159 | driver->setPlaying(true); 160 | ``` 161 | 162 | The above will output a sine wave at 2kHz on the audio card. 163 | 164 | ## Contributing :mailbox_with_mail: 165 | Contributions are welcomed, have a look at the [CONTRIBUTING.md](CONTRIBUTING.md) document for more information. 166 | 167 | ## License :memo: 168 | The project is available under the [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0) license. 169 | 170 | ### Acknowledgements 171 | - Icon in readme banner is “[Audio](https://thenounproject.com/search/?q=audio&i=1514798)” by Becris from the Noun Project. 172 | 173 | #### Contributors 174 | * [Will Sackfield](https://github.com/8W9aG) 175 | * [Gabor Szanto](https://github.com/superpoweredSDK) of [Superpowered](https://superpowered.com) 176 | * [Julia Cox](https://github.com/astrocox) 177 | * [Justin Sarma](https://github.com/jsarma) 178 | * [Noah Hilt](https://github.com/noeski) 179 | * [David Rubinstein](https://github.com/drubinstein) 180 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2019-2022 Spotify AB 191 | Copyright 2023 [new maintainer] 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | -------------------------------------------------------------------------------- /source/NFDriverAdapter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #include "NFDriverAdapter.h" 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | namespace nativeformat { 28 | namespace driver { 29 | 30 | // Cross-platform atomic operations. 31 | #if _WIN32 32 | #include 33 | #define ATOMIC_SIGNED_INT long 34 | #define MEMORYBARRIER MemoryBarrier() 35 | #define ATOMICZERO(var) InterlockedAnd(&var, 0) 36 | #else 37 | #define ATOMIC_SIGNED_INT int 38 | #define MEMORYBARRIER __sync_synchronize() 39 | #define ATOMICZERO(var) __sync_fetch_and_and(&var, 0) 40 | #endif 41 | 42 | // Linear resampler stuff. 43 | // Why linear? Because more sophisticated resamplers are killing treble without 44 | // oversampling. The noise of this resampler will typically happen around the 45 | // Nyquist frequency and in the -90 db or lower region. Audiophile bats may 46 | // complain. Humans are not able to notice. 47 | typedef struct resamplerData { 48 | uint64_t *input; // A buffer on the heap to store NF_DRIVER_SAMPLE_BLOCK_SIZE audio. 49 | union { 50 | float f[2]; 51 | uint64_t i; // Makes loads faster a bit. Don't believe the hype, compilers 52 | // are still quite dumb. 53 | } prev; 54 | float rate, slopeCount; 55 | } resamplerData; 56 | 57 | // This linear resampler is not "Superpowered", but still faster than most naive 58 | // implementations. 59 | static int resample(float *output, resamplerData *resampler, int numFrames) { 60 | resamplerData stack = *resampler; // Local copy on the stack, preventing the 61 | // compiler writing back intermediate 62 | // results to memory. 63 | float left, right, invSlopeCount; 64 | int outFrames = 0; 65 | 66 | while (true) { 67 | while (stack.slopeCount > 1.0f) { 68 | numFrames--; 69 | stack.slopeCount -= 1.0f; 70 | 71 | if (!numFrames) { // Quit resampling, writing back the intermediate 72 | // results to memory. 73 | resampler->slopeCount = stack.slopeCount; 74 | resampler->prev.i = stack.prev.i; 75 | return outFrames; 76 | } 77 | 78 | stack.prev.i = *stack.input++; 79 | } 80 | 81 | // Linear resampling, the compiler may recognize that these are primitive 82 | // Assembly instructions. 83 | invSlopeCount = 1.0f - stack.slopeCount; 84 | left = invSlopeCount * stack.prev.f[0]; 85 | right = invSlopeCount * stack.prev.f[1]; 86 | 87 | stack.prev.i = *stack.input; 88 | 89 | *output++ = left + stack.slopeCount * stack.prev.f[0]; 90 | *output++ = right + stack.slopeCount * stack.prev.f[1]; 91 | 92 | stack.slopeCount += stack.rate; 93 | outFrames++; 94 | } 95 | } 96 | 97 | static void makeOutput( 98 | float *input, float **outputLeft, float **outputRight, int numFrames, int numChannels) { 99 | if (numChannels == 1) { // Mono output. 100 | float *mono = *outputLeft; 101 | *outputLeft += numFrames; 102 | 103 | while (numFrames--) { 104 | *mono++ = (input[0] + input[1]) * 0.5f; 105 | input += 2; 106 | } 107 | } else if (*outputRight) { // Stereo non-interleaved output, deinterleave to 108 | // left and right. 109 | float *left = *outputLeft, *right = *outputRight; 110 | *outputLeft += numFrames; 111 | *outputRight += numFrames; 112 | 113 | while (numFrames--) { 114 | *left++ = *input++; 115 | *right++ = *input++; 116 | } 117 | } else if (numChannels > 2) { // Interleaved output with more than 2 channels. 118 | // Can happen with Linux only. 119 | float *output = *outputLeft; 120 | *outputLeft += numFrames * numChannels; 121 | memset(output, 0, static_cast(numFrames * numChannels) * sizeof(float)); 122 | 123 | while (numFrames--) { 124 | *(reinterpret_cast(output)) = *(reinterpret_cast(input)); 125 | output += numChannels; 126 | input += 2; 127 | } 128 | } else { // Stereo interleaved output. 129 | memcpy(*outputLeft, input, static_cast(numFrames) * sizeof(float) * 2); 130 | *outputLeft += numFrames * 2; 131 | } 132 | } 133 | 134 | // Finally, the adapter implementation starts here. 135 | typedef struct NFDriverAdapterInternals { 136 | resamplerData resampler; 137 | void *clientdata; 138 | NF_WILL_RENDER_CALLBACK willRenderCallback; 139 | NF_RENDER_CALLBACK renderCallback; 140 | NF_DID_RENDER_CALLBACK didRenderCallback; 141 | NF_STUTTER_CALLBACK stutterCallback; 142 | float *interleavedBuffer; 143 | int bufferCapacityFrames, framesInBuffer, readPositionFrames, writePositionFrames, 144 | bufferCapacityToEndNeeded; 145 | ATOMIC_SIGNED_INT nextSamplerate; 146 | bool needsResampling; 147 | } NFDriverAdapterInternals; 148 | 149 | NFDriverAdapter::NFDriverAdapter(void *clientdata, 150 | NF_STUTTER_CALLBACK stutter_callback, 151 | NF_RENDER_CALLBACK render_callback, 152 | NF_ERROR_CALLBACK error_callback, 153 | NF_WILL_RENDER_CALLBACK will_render_callback, 154 | NF_DID_RENDER_CALLBACK did_render_callback) { 155 | internals = new NFDriverAdapterInternals; 156 | memset(internals, 0, sizeof(NFDriverAdapterInternals)); 157 | 158 | internals->clientdata = clientdata; 159 | internals->stutterCallback = stutter_callback; 160 | internals->renderCallback = render_callback; 161 | internals->willRenderCallback = will_render_callback; 162 | internals->didRenderCallback = did_render_callback; 163 | 164 | int volatile numBlocks = NF_DRIVER_SAMPLERATE / NF_DRIVER_SAMPLE_BLOCK_SIZE; 165 | internals->bufferCapacityFrames = 166 | numBlocks * NF_DRIVER_SAMPLE_BLOCK_SIZE; // Will be around 1 second big. 167 | internals->interleavedBuffer = 168 | reinterpret_cast(malloc(static_cast(internals->bufferCapacityFrames) * 169 | sizeof(float) * 2)); // 344 kb at 44100 Hz and 1024 frames. 170 | if (!internals->interleavedBuffer) 171 | error_callback(clientdata, "Out of memory in NFDriverAdapter.", 0); 172 | 173 | internals->resampler.input = 174 | reinterpret_cast(malloc(NF_DRIVER_SAMPLE_BLOCK_SIZE * sizeof(uint64_t))); 175 | if (!internals->resampler.input) 176 | error_callback(clientdata, "Out of memory in NFDriverAdapter.", 0); 177 | } 178 | 179 | NFDriverAdapter::~NFDriverAdapter() { 180 | if (internals->interleavedBuffer) free(internals->interleavedBuffer); 181 | if (internals->resampler.input) free(internals->resampler.input); 182 | delete internals; 183 | } 184 | 185 | // Should be called in the audio processing/rendering callback of the audio I/O. 186 | bool NFDriverAdapter::getFrames(float *outputLeft, 187 | float *outputRight, 188 | int numFrames, 189 | int numChannels) { 190 | if (!internals->interleavedBuffer || !internals->resampler.input) return false; 191 | internals->willRenderCallback(internals->clientdata); 192 | 193 | ATOMIC_SIGNED_INT nextSamplerate = 194 | ATOMICZERO(internals->nextSamplerate); // Make it zero, return with the previous value. 195 | if (nextSamplerate != 0) { 196 | internals->needsResampling = (nextSamplerate != NF_DRIVER_SAMPLERATE); 197 | internals->resampler.rate = 198 | static_cast(NF_DRIVER_SAMPLERATE) / static_cast(nextSamplerate); 199 | internals->bufferCapacityToEndNeeded = 200 | internals->needsResampling ? static_cast((static_cast(nextSamplerate) / 201 | static_cast(NF_DRIVER_SAMPLERATE)) * 202 | (NF_DRIVER_SAMPLE_BLOCK_SIZE + 2)) 203 | : NF_DRIVER_SAMPLE_BLOCK_SIZE; 204 | } 205 | 206 | // Render audio if needed. 207 | while (internals->framesInBuffer < numFrames) { 208 | // Do we have enough space in the buffer? 209 | if (internals->bufferCapacityToEndNeeded > 210 | (internals->bufferCapacityFrames - internals->writePositionFrames)) { 211 | // Memmove looks inefficient? This will happen only once in every second, 212 | // and all "virtual memory tricks" will do this anyway behind the curtain. 213 | if (internals->framesInBuffer > 0) 214 | memmove(internals->interleavedBuffer, 215 | internals->interleavedBuffer + internals->readPositionFrames * 2, 216 | static_cast(internals->framesInBuffer) * sizeof(float) * 2); 217 | internals->readPositionFrames = 0; 218 | internals->writePositionFrames = internals->framesInBuffer; 219 | } 220 | 221 | int framesRendered; 222 | if (!internals->needsResampling) { // No resampling needed, render directly 223 | // into our buffer. 224 | framesRendered = internals->renderCallback( 225 | internals->clientdata, 226 | internals->interleavedBuffer + internals->writePositionFrames * 2, 227 | NF_DRIVER_SAMPLE_BLOCK_SIZE); 228 | if (framesRendered <= 0) break; 229 | } else { // Resampling needed, render into the resampler's input buffer, the 230 | // resample into our buffer. 231 | framesRendered = 232 | internals->renderCallback(internals->clientdata, 233 | reinterpret_cast(internals->resampler.input), 234 | NF_DRIVER_SAMPLE_BLOCK_SIZE); 235 | if (framesRendered <= 0) break; 236 | framesRendered = resample(internals->interleavedBuffer + internals->writePositionFrames * 2, 237 | &internals->resampler, 238 | framesRendered); 239 | } 240 | 241 | internals->writePositionFrames += framesRendered; 242 | internals->framesInBuffer += framesRendered; 243 | } 244 | 245 | // Output audio if possible. 246 | bool success = internals->framesInBuffer >= numFrames; 247 | if (success) { 248 | // Output numFrames of audio, or until the end of our buffer. 249 | int framesAvailableToEnd = internals->bufferCapacityFrames - internals->readPositionFrames; 250 | if (framesAvailableToEnd > numFrames) framesAvailableToEnd = numFrames; 251 | 252 | makeOutput(internals->interleavedBuffer + internals->readPositionFrames * 2, 253 | &outputLeft, 254 | &outputRight, 255 | framesAvailableToEnd, 256 | numChannels); 257 | internals->readPositionFrames += framesAvailableToEnd; 258 | if (internals->readPositionFrames >= internals->bufferCapacityFrames) 259 | internals->readPositionFrames = 0; 260 | 261 | // Start from the beginning of our buffer if needed. (Wrap around.) 262 | int moreFrames = numFrames - framesAvailableToEnd; 263 | if (moreFrames > 0) { 264 | makeOutput(internals->interleavedBuffer, &outputLeft, &outputRight, moreFrames, numChannels); 265 | internals->readPositionFrames += moreFrames; 266 | } 267 | 268 | internals->framesInBuffer -= numFrames; 269 | } else 270 | internals->stutterCallback(internals->clientdata); 271 | 272 | internals->didRenderCallback(internals->clientdata); 273 | return success; 274 | } 275 | 276 | void NFDriverAdapter::setSamplerate(int samplerate) { 277 | internals->nextSamplerate = samplerate; 278 | MEMORYBARRIER; 279 | } 280 | 281 | int NFDriverAdapter::getOptimalNumberOfFrames(int samplerate) { 282 | if (samplerate == NF_DRIVER_SAMPLERATE) return NF_DRIVER_SAMPLE_BLOCK_SIZE; 283 | 284 | float rate = static_cast(samplerate) / static_cast(NF_DRIVER_SAMPLERATE); 285 | return int(NF_DRIVER_SAMPLE_BLOCK_SIZE * rate); 286 | } 287 | 288 | } // namespace driver 289 | } // namespace nativeformat 290 | -------------------------------------------------------------------------------- /source/NFDriver_Windows.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Spotify AB. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | #if _WIN32 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "NFDriverAdapter.h" 30 | 31 | namespace nativeformat { 32 | namespace driver { 33 | 34 | typedef struct streamHandlerInternals { 35 | NFDriverAdapter *adapter; 36 | void *clientdata; 37 | NF_ERROR_CALLBACK errorCallback; 38 | IAudioClient3 *client; 39 | IAudioRenderClient *renderClient; 40 | IMFAsyncResult *sampleReadyAsyncResult; 41 | HANDLE sampleReadyEvent; 42 | MFWORKITEM_KEY cancelKey; 43 | DWORD workQueueId; 44 | int bufferSize, numberOfSamples; 45 | bool raw, stop; 46 | } streamHandlerInternals; 47 | 48 | // This class handles the actual audio output stream. 49 | // The reason why this class is separate to NFSoundCardDriver is the 50 | // RuntimeClass stuff. 51 | class streamHandler : public Microsoft::WRL::RuntimeClass< 52 | Microsoft::WRL::RuntimeClassFlags, 53 | Microsoft::WRL::FtmBase, 54 | IMFAsyncCallback, 55 | IActivateAudioInterfaceCompletionHandler> { 56 | public: 57 | bool running; 58 | 59 | streamHandler(void *clientdata, 60 | NF_STUTTER_CALLBACK stutter_callback, 61 | NF_RENDER_CALLBACK render_callback, 62 | NF_ERROR_CALLBACK error_callback, 63 | NF_WILL_RENDER_CALLBACK will_render_callback, 64 | NF_DID_RENDER_CALLBACK did_render_callback, 65 | DWORD workQueueIdentifier, 66 | bool rawProcessingSupported) 67 | : running(false) { 68 | internals = new streamHandlerInternals; 69 | memset(internals, 0, sizeof(streamHandlerInternals)); 70 | internals->clientdata = clientdata; 71 | internals->errorCallback = error_callback; 72 | 73 | internals->stop = false; 74 | internals->workQueueId = workQueueIdentifier; 75 | internals->raw = rawProcessingSupported; 76 | internals->sampleReadyEvent = CreateEventEx(nullptr, nullptr, 0, EVENT_ALL_ACCESS); 77 | internals->adapter = new NFDriverAdapter(clientdata, 78 | stutter_callback, 79 | render_callback, 80 | error_callback, 81 | will_render_callback, 82 | did_render_callback); 83 | } 84 | 85 | STDMETHODIMP GetParameters(DWORD *flags, DWORD *queue) { 86 | *queue = internals->workQueueId; 87 | *flags = 0; 88 | return S_OK; 89 | } 90 | 91 | // This is the audio rendering callback, called by a Media Foundation audio 92 | // thread. 93 | STDMETHODIMP Invoke(IMFAsyncResult *result) { 94 | if (internals->stop) { // Handle stopping. 95 | if (internals->client) internals->client->Stop(); 96 | fail(NULL); 97 | // At this point the stream will never call any callbacks anymore. 98 | return E_FAIL; 99 | } 100 | 101 | // The buffer may have some padding, we'll not always write from the 102 | // beginning. 103 | UINT32 padding; 104 | HRESULT hr = internals->client->GetCurrentPadding(&padding); 105 | if (FAILED(hr)) return hr; 106 | 107 | int framesLeft = internals->bufferSize - padding; 108 | if (framesLeft > 0) { 109 | // Get the buffer to write. 110 | BYTE *buffer; 111 | hr = internals->renderClient->GetBuffer(framesLeft, &buffer); 112 | if (FAILED(hr)) return hr; 113 | 114 | bool silence = true; 115 | int framesWritten = 0; 116 | 117 | while (framesLeft >= internals->numberOfSamples) { 118 | if (!internals->adapter->getFrames((float *)buffer, NULL, internals->numberOfSamples, 2)) 119 | memset(buffer, 0, internals->numberOfSamples * sizeof(float) * 2); 120 | else 121 | silence = false; 122 | 123 | framesLeft -= internals->numberOfSamples; 124 | framesWritten += internals->numberOfSamples; 125 | buffer += internals->numberOfSamples * sizeof(float) * 2; 126 | } 127 | 128 | // Release the buffer and enqueue. 129 | internals->renderClient->ReleaseBuffer(framesWritten, 130 | silence ? AUDCLNT_BUFFERFLAGS_SILENT : 0); 131 | hr = MFPutWaitingWorkItem( 132 | internals->sampleReadyEvent, 0, internals->sampleReadyAsyncResult, &internals->cancelKey); 133 | if (FAILED(hr)) return hr; 134 | } 135 | 136 | return S_OK; 137 | } 138 | 139 | // Called by Windows when the audio interface is activated. 140 | STDMETHOD(ActivateCompleted) 141 | (IActivateAudioInterfaceAsyncOperation *operation) { 142 | if (!internals->sampleReadyEvent) return fail("CreateEventEx failed"); 143 | 144 | // Get the COM interfaces. 145 | IUnknown *audioInterface = nullptr; 146 | HRESULT hrActivateResult = S_OK, 147 | hr = operation->GetActivateResult(&hrActivateResult, &audioInterface); 148 | if (FAILED(hr)) return fail("GetActivateResult failed"); 149 | if (FAILED(hrActivateResult)) return fail("ActiveResult is invalid."); 150 | audioInterface->QueryInterface(IID_PPV_ARGS(&internals->client)); 151 | if (!internals->client) return fail("QueryInterface failed."); 152 | 153 | // Set raw capability (for low-latency). 154 | AudioClientProperties properties = {0}; 155 | properties.cbSize = sizeof(AudioClientProperties); 156 | properties.eCategory = AudioCategory_Media; 157 | if (internals->raw) properties.Options |= AUDCLNT_STREAMOPTIONS_RAW; 158 | if (FAILED(internals->client->SetClientProperties(&properties))) 159 | return fail("SetClientProperties failed."); 160 | 161 | // Get the stream properties (sample rate, buffer size). 162 | WAVEFORMATEX *format; 163 | if (FAILED(internals->client->GetMixFormat(&format))) return fail("GetMixFormat failed."); 164 | UINT32 defaultPeriodFrames, fundamentalPeriodFrames, minPeriodFrames, maxPeriodFrames; 165 | hr = internals->client->GetSharedModeEnginePeriod( 166 | format, &defaultPeriodFrames, &fundamentalPeriodFrames, &minPeriodFrames, &maxPeriodFrames); 167 | if (FAILED(hr)) { 168 | CoTaskMemFree(format); 169 | return fail("GetSharedModeEnginePeriod failed."); 170 | } 171 | internals->adapter->setSamplerate(format->nSamplesPerSec); 172 | internals->numberOfSamples = minPeriodFrames; 173 | format->nChannels = 2; 174 | 175 | // Initialize the stream. 176 | hr = internals->client->InitializeSharedAudioStream( 177 | AUDCLNT_STREAMFLAGS_EVENTCALLBACK, minPeriodFrames, format, nullptr); 178 | CoTaskMemFree(format); 179 | if (FAILED(hr)) return fail("InitializeSharedAudioStream failed."); 180 | 181 | // Get the buffer size. 182 | UINT32 uBufferSize = 0; 183 | if (FAILED(internals->client->GetBufferSize(&uBufferSize))) 184 | return fail("GetBufferSize failed."); 185 | internals->bufferSize = static_cast(uBufferSize); 186 | 187 | // Start the audio rendering. 188 | if (FAILED(internals->client->GetService(__uuidof(IAudioRenderClient), 189 | reinterpret_cast(&internals->renderClient)))) 190 | return fail("GetService failed."); 191 | if (FAILED(MFCreateAsyncResult(nullptr, this, nullptr, &internals->sampleReadyAsyncResult))) 192 | return fail("MFCreateAsyncResult failed."); 193 | if (FAILED(internals->client->SetEventHandle(internals->sampleReadyEvent))) 194 | return fail("SetEventHandle failed."); 195 | if (FAILED(internals->client->Start())) return fail("Start failed."); 196 | if (FAILED(MFPutWaitingWorkItem(internals->sampleReadyEvent, 197 | 0, 198 | internals->sampleReadyAsyncResult, 199 | &internals->cancelKey))) 200 | return fail("MFPutWaitingWorkItem failed."); 201 | 202 | running = true; 203 | return S_OK; 204 | } 205 | 206 | // Delete the handler. The actual operation will happen at Invoke() 207 | // asynchronously. 208 | static void release(Microsoft::WRL::ComPtr stream) { 209 | if (stream == nullptr) return; 210 | streamHandler *handler = *stream.GetAddressOf(); 211 | if (handler->running) handler->internals->stop = true; 212 | } 213 | 214 | private: 215 | streamHandlerInternals *internals; 216 | 217 | HRESULT fail(const char *message) { 218 | if (internals->cancelKey != 0) MFCancelWorkItem(internals->cancelKey); 219 | if (internals->client) internals->client->Release(); 220 | if (internals->renderClient) internals->renderClient->Release(); 221 | if (internals->sampleReadyAsyncResult) internals->sampleReadyAsyncResult->Release(); 222 | if (internals->sampleReadyEvent != INVALID_HANDLE_VALUE) 223 | CloseHandle(internals->sampleReadyEvent); 224 | delete internals->adapter; 225 | if (message) internals->errorCallback(internals->clientdata, message, 0); 226 | delete internals; 227 | return E_FAIL; 228 | } 229 | }; 230 | 231 | typedef struct nativeformat::driver::NFSoundCardDriverInternals { 232 | void *clientdata; 233 | NF_WILL_RENDER_CALLBACK willRenderCallback; 234 | NF_RENDER_CALLBACK renderCallback; 235 | NF_DID_RENDER_CALLBACK didRenderCallback; 236 | NF_STUTTER_CALLBACK stutterCallback; 237 | NF_ERROR_CALLBACK errorCallback; 238 | Microsoft::WRL::ComPtr outputHandler; 239 | long isPlaying; 240 | } NFSoundCardDriverInternals; 241 | 242 | NFSoundCardDriver::NFSoundCardDriver(void *clientdata, 243 | NF_STUTTER_CALLBACK stutter_callback, 244 | NF_RENDER_CALLBACK render_callback, 245 | NF_ERROR_CALLBACK error_callback, 246 | NF_WILL_RENDER_CALLBACK will_render_callback, 247 | NF_DID_RENDER_CALLBACK did_render_callback) { 248 | internals = new NFSoundCardDriverInternals; 249 | memset(internals, 0, sizeof(NFSoundCardDriverInternals)); 250 | internals->clientdata = clientdata; 251 | internals->stutterCallback = stutter_callback; 252 | internals->renderCallback = render_callback; 253 | internals->willRenderCallback = will_render_callback; 254 | internals->didRenderCallback = did_render_callback; 255 | internals->errorCallback = error_callback; 256 | } 257 | 258 | NFSoundCardDriver::~NFSoundCardDriver() { 259 | streamHandler::release(internals->outputHandler); 260 | delete internals; 261 | } 262 | 263 | static void start(NFSoundCardDriverInternals *internals) { 264 | // Getting the default output device. 265 | Platform::String ^ outputDeviceId = Windows::Media::Devices::MediaDevice::GetDefaultAudioRenderId( 266 | Windows::Media::Devices::AudioDeviceRole::Default); 267 | if (!outputDeviceId) { 268 | internals->errorCallback(internals->clientdata, "GetDefaultAudioRenderId failed.", 0); 269 | return; 270 | } 271 | 272 | // Start Media Foundation. 273 | if (FAILED(MFStartup(MF_VERSION, MFSTARTUP_LITE))) { 274 | internals->errorCallback(internals->clientdata, "MFStartup failed.", 0); 275 | return; 276 | } 277 | 278 | // Set up Media Foundation for Pro Audio (proper scheduling). 279 | DWORD taskId = 0, workQueueId = 0; 280 | if (FAILED(MFLockSharedWorkQueue(L"Pro Audio", 0, &taskId, &workQueueId))) { 281 | MFShutdown(); 282 | internals->errorCallback(internals->clientdata, "MFLockSharedWorkQueue failed.", 0); 283 | return; 284 | } 285 | auto properties = ref new Platform::Collections::Vector(); 286 | properties->Append("System.Devices.AudioDevice.RawProcessingSupported"); 287 | 288 | // Create the stream handler asynchronously. COM is fun! (not) 289 | Concurrency::create_task(Windows::Devices::Enumeration::DeviceInformation::CreateFromIdAsync( 290 | outputDeviceId, properties)) 291 | .then([outputDeviceId, internals, workQueueId]( 292 | Windows::Devices::Enumeration::DeviceInformation ^ deviceInformation) { 293 | auto obj = deviceInformation->Properties->Lookup( 294 | "System.Devices.AudioDevice.RawProcessingSupported"); 295 | bool rawProcessingSupported = false; 296 | if (obj) rawProcessingSupported = obj->Equals(true); 297 | 298 | internals->outputHandler = 299 | Microsoft::WRL::Make(internals->clientdata, 300 | internals->stutterCallback, 301 | internals->renderCallback, 302 | internals->errorCallback, 303 | internals->willRenderCallback, 304 | internals->didRenderCallback, 305 | workQueueId, 306 | rawProcessingSupported); 307 | IActivateAudioInterfaceAsyncOperation *asyncOperation; 308 | ActivateAudioInterfaceAsync(outputDeviceId->Data(), 309 | __uuidof(IAudioClient3), 310 | nullptr, 311 | *internals->outputHandler.GetAddressOf(), 312 | &asyncOperation); 313 | }); 314 | } 315 | 316 | static void stop(NFSoundCardDriverInternals *internals) { 317 | streamHandler::release(internals->outputHandler); 318 | internals->outputHandler = nullptr; 319 | MFShutdown(); 320 | } 321 | 322 | bool NFSoundCardDriver::isPlaying() const { 323 | return InterlockedExchangeAdd(&internals->isPlaying, 0) > 0; 324 | } 325 | 326 | void NFSoundCardDriver::setPlaying(bool playing) { 327 | bool changedNow; 328 | if (playing) 329 | changedNow = (InterlockedCompareExchange(&internals->isPlaying, 1, 0) == 0); 330 | else 331 | changedNow = (InterlockedCompareExchange(&internals->isPlaying, 0, 1) == 1); 332 | 333 | if (changedNow) { 334 | if (playing) 335 | start(internals); 336 | else 337 | stop(internals); 338 | } 339 | } 340 | 341 | } // namespace driver 342 | } // namespace nativeformat 343 | 344 | #endif // _win32 345 | --------------------------------------------------------------------------------