lq-audio-studio 4.0.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +2 -1
- package/android/consumer-rules.pro +1 -0
- package/android/src/main/java/com/aozora/audio/AozoraWebRtcApm.java +36 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioProcessing.kt +95 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioRecorderManager.kt +145 -20
- package/android/src/main/java/net/siteed/audiostudio/Pcm16FrameAccumulator.kt +47 -0
- package/android/src/main/java/net/siteed/audiostudio/RecordingConfig.kt +45 -0
- package/android/src/main/java/net/siteed/audiostudio/VoiceProcessingEffects.kt +16 -5
- package/android/src/main/java/net/siteed/audiostudio/WebRtcAudioFrameProcessor.kt +118 -0
- package/android/src/main/jniLibs/arm64-v8a/libaozora_apm.so +0 -0
- package/android/src/main/webrtc-apm-notices/README.md +151 -0
- package/android/src/main/webrtc-apm-notices/THIRD_PARTY_NOTICES.md +25 -0
- package/android/src/main/webrtc-apm-notices/licenses/AUTHORS +7 -0
- package/android/src/main/webrtc-apm-notices/licenses/COPYING +29 -0
- package/android/src/main/webrtc-apm-notices/licenses/android-ndk/NOTICE +9997 -0
- package/android/src/main/webrtc-apm-notices/licenses/android-ndk/NOTICE.toolchain +15302 -0
- package/android/src/main/webrtc-apm-notices/licenses/subprojects/abseil-cpp-20230125.1/AUTHORS +6 -0
- package/android/src/main/webrtc-apm-notices/licenses/subprojects/abseil-cpp-20230125.1/LICENSE +203 -0
- package/android/src/main/webrtc-apm-notices/licenses/subprojects/abseil-cpp-20230125.1/LICENSE.build +19 -0
- package/android/src/main/webrtc-apm-notices/licenses/webrtc/LICENSE +29 -0
- package/android/src/main/webrtc-apm-notices/licenses/webrtc/PATENTS +24 -0
- package/android/src/main/webrtc-apm-notices/licenses/webrtc/common_audio/third_party/ooura/LICENSE +8 -0
- package/android/src/main/webrtc-apm-notices/licenses/webrtc/common_audio/third_party/spl_sqrt_floor/LICENSE +27 -0
- package/android/src/main/webrtc-apm-notices/licenses/webrtc/modules/third_party/fft/LICENSE +25 -0
- package/android/src/main/webrtc-apm-notices/licenses/webrtc/third_party/pffft/LICENSE +1 -0
- package/android/src/main/webrtc-apm-notices/licenses/webrtc/third_party/rnnoise/COPYING +1 -0
- package/android/src/main/webrtc-apm-notices/licenses/wrapper-LICENSE +27 -0
- package/android/src/main/webrtc-apm-notices/manifest.json +150 -0
- package/build/types/AudioStudio.types.d.ts +35 -1
- package/package.json +1 -1
- package/src/AudioStudio.types.ts +47 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
package net.siteed.audiostudio
|
|
2
|
+
|
|
3
|
+
import com.aozora.audio.AozoraWebRtcApm
|
|
4
|
+
|
|
5
|
+
/** Owns one native WebRTC APM instance and its 10 ms capture/render framing. */
|
|
6
|
+
internal class WebRtcAudioFrameProcessor private constructor(
|
|
7
|
+
private var handle: Long,
|
|
8
|
+
val request: AudioProcessingRequest,
|
|
9
|
+
sampleRate: Int,
|
|
10
|
+
) {
|
|
11
|
+
private val frameBytes = sampleRate / 100 * 2
|
|
12
|
+
private val captureFrames = Pcm16FrameAccumulator(frameBytes)
|
|
13
|
+
private val reverseFrames = Pcm16FrameAccumulator(frameBytes)
|
|
14
|
+
|
|
15
|
+
@Volatile
|
|
16
|
+
var reverseStreamConnected: Boolean = false
|
|
17
|
+
private set
|
|
18
|
+
|
|
19
|
+
@Volatile
|
|
20
|
+
var operational: Boolean = true
|
|
21
|
+
private set
|
|
22
|
+
|
|
23
|
+
@Synchronized
|
|
24
|
+
fun processCapture(data: ByteArray, length: Int): ByteArray {
|
|
25
|
+
if (handle == 0L || !operational) return data.copyOf(length)
|
|
26
|
+
return captureFrames.consume(data, length) { processFrame(it, reverse = false) }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@Synchronized
|
|
30
|
+
fun processReverse(data: ByteArray, length: Int, delayMs: Int): Boolean {
|
|
31
|
+
if (handle == 0L || !operational || !request.echoCancellation) return false
|
|
32
|
+
AozoraWebRtcApm.nativeSetStreamDelay(handle, delayMs.coerceIn(0, 500))
|
|
33
|
+
reverseFrames.consume(data, length) { processFrame(it, reverse = true) }
|
|
34
|
+
return reverseStreamConnected
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@Synchronized
|
|
38
|
+
fun flushCapture(): ByteArray = captureFrames.drain()
|
|
39
|
+
|
|
40
|
+
@Synchronized
|
|
41
|
+
fun release() {
|
|
42
|
+
val current = handle
|
|
43
|
+
handle = 0L
|
|
44
|
+
operational = false
|
|
45
|
+
reverseStreamConnected = false
|
|
46
|
+
if (current != 0L) AozoraWebRtcApm.nativeRelease(current)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private fun processFrame(frame: ByteArray, reverse: Boolean): ByteArray {
|
|
50
|
+
val samples = ShortArray(frame.size / 2)
|
|
51
|
+
for (index in samples.indices) {
|
|
52
|
+
val offset = index * 2
|
|
53
|
+
samples[index] = (((frame[offset + 1].toInt() and 0xff) shl 8) or
|
|
54
|
+
(frame[offset].toInt() and 0xff)).toShort()
|
|
55
|
+
}
|
|
56
|
+
val status = if (reverse) {
|
|
57
|
+
AozoraWebRtcApm.nativeProcessReverse(handle, samples)
|
|
58
|
+
} else {
|
|
59
|
+
AozoraWebRtcApm.nativeProcessCapture(handle, samples)
|
|
60
|
+
}
|
|
61
|
+
if (status != 0 && status != WEBRTC_BAD_STREAM_PARAMETER_WARNING) {
|
|
62
|
+
operational = false
|
|
63
|
+
LogUtils.e(CLASS_NAME, "WebRTC APM ${if (reverse) "reverse" else "capture"} failed: $status")
|
|
64
|
+
return frame
|
|
65
|
+
}
|
|
66
|
+
if (reverse) reverseStreamConnected = true
|
|
67
|
+
|
|
68
|
+
val output = ByteArray(frame.size)
|
|
69
|
+
for (index in samples.indices) {
|
|
70
|
+
val value = samples[index].toInt()
|
|
71
|
+
output[index * 2] = (value and 0xff).toByte()
|
|
72
|
+
output[index * 2 + 1] = ((value ushr 8) and 0xff).toByte()
|
|
73
|
+
}
|
|
74
|
+
return output
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
companion object {
|
|
78
|
+
private const val CLASS_NAME = "WebRtcAudioFrameProcessor"
|
|
79
|
+
private const val WEBRTC_BAD_STREAM_PARAMETER_WARNING = -13
|
|
80
|
+
private val SUPPORTED_SAMPLE_RATES = setOf(8000, 16000, 32000, 48000)
|
|
81
|
+
|
|
82
|
+
fun create(
|
|
83
|
+
sampleRate: Int,
|
|
84
|
+
channels: Int,
|
|
85
|
+
encoding: String,
|
|
86
|
+
request: AudioProcessingRequest,
|
|
87
|
+
): WebRtcAudioFrameProcessor? {
|
|
88
|
+
if (!request.anyEnabled) return null
|
|
89
|
+
if (sampleRate !in SUPPORTED_SAMPLE_RATES || channels != 1 || encoding != "pcm_16bit") {
|
|
90
|
+
LogUtils.w(
|
|
91
|
+
CLASS_NAME,
|
|
92
|
+
"WebRTC APM requires mono PCM16 at 8/16/32/48 kHz; got $sampleRate Hz, $channels channel(s), $encoding"
|
|
93
|
+
)
|
|
94
|
+
return null
|
|
95
|
+
}
|
|
96
|
+
if (!AozoraWebRtcApm.isAvailable()) {
|
|
97
|
+
LogUtils.w(CLASS_NAME, "WebRTC APM native library is unavailable for this ABI")
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
val handle = AozoraWebRtcApm.nativeCreate(
|
|
101
|
+
sampleRate,
|
|
102
|
+
channels,
|
|
103
|
+
request.echoCancellation,
|
|
104
|
+
request.noiseSuppression,
|
|
105
|
+
request.automaticGainControl,
|
|
106
|
+
)
|
|
107
|
+
if (handle == 0L) {
|
|
108
|
+
LogUtils.w(CLASS_NAME, "WebRTC APM native processor creation failed")
|
|
109
|
+
return null
|
|
110
|
+
}
|
|
111
|
+
LogUtils.i(
|
|
112
|
+
CLASS_NAME,
|
|
113
|
+
"WebRTC APM enabled (AEC=${request.echoCancellation}, NS=${request.noiseSuppression}, AGC=${request.automaticGainControl})"
|
|
114
|
+
)
|
|
115
|
+
return WebRtcAudioFrameProcessor(handle, request, sampleRate)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# webrtc-apm-deps-build
|
|
2
|
+
|
|
3
|
+
Independent, manual GitHub Actions builds for WebRTC Audio Processing 1.3 and
|
|
4
|
+
the Aozora APM native interface. This repository does not contain the consuming
|
|
5
|
+
application, downloaded toolchains, source caches, or prebuilt binaries.
|
|
6
|
+
|
|
7
|
+
## Outputs
|
|
8
|
+
|
|
9
|
+
| Target | Artifact content | Baseline |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| Android | `app-android/libs/<abi>/libaozora_apm.so` and C header | API 28, NDK r27c; ARM64 default |
|
|
12
|
+
| iOS | `app-ios/Frameworks/AozoraApm.xcframework` | iPhoneOS ARM64, iOS 14.0; no simulator |
|
|
13
|
+
| HarmonyOS | `app-harmony/libs/arm64-v8a/libaozora_apm.so`, required C++ runtime, N-API types and C header | ARM64 Linux-host Native SDK |
|
|
14
|
+
|
|
15
|
+
Android optionally builds the existing ARMv7 and x86 ABIs. No AAR, HAR or APK is
|
|
16
|
+
produced. Each ZIP includes license notices, `manifest.json`, per-file hashes,
|
|
17
|
+
architecture/dependency checks, and a separate archive SHA-256 file.
|
|
18
|
+
|
|
19
|
+
The common processing profile is mono signed PCM16, 10 ms frames, at 8/16/32/48
|
|
20
|
+
kHz. AEC3 is enabled; NS and AGC2 are optional. Render reference, capture
|
|
21
|
+
processing, stream delay and voice-detection state are exposed. The library
|
|
22
|
+
does not capture or play audio by itself.
|
|
23
|
+
|
|
24
|
+
## Manual GitHub builds
|
|
25
|
+
|
|
26
|
+
1. Open Actions, then **Build WebRTC APM native libraries**.
|
|
27
|
+
2. Choose **Run workflow** on the latest `main`.
|
|
28
|
+
3. Select `all` (default), `android`, `ios`, or `harmony`.
|
|
29
|
+
4. Check any combination of `android_arm64` (ARM64), `android_armv7` (ARMv7),
|
|
30
|
+
and `android_x86` (x86). Only ARM64 is checked by default. Select at least
|
|
31
|
+
one ABI when building Android; the checkboxes are ignored for iOS and Harmony.
|
|
32
|
+
Choosing platform `all` builds all three platforms, not all Android ABIs.
|
|
33
|
+
5. Download the platform artifact from that run. Retention is seven days.
|
|
34
|
+
|
|
35
|
+
Platform jobs are independent. One failure does not cancel the others. A
|
|
36
|
+
separate Linux host smoke job exercises the shared C API on every manual run;
|
|
37
|
+
that is not a phone-device test. Native builds are **manual-only**. Push/PR only
|
|
38
|
+
runs Python syntax, configuration, archive-safety and workflow-routing checks.
|
|
39
|
+
Start a new run after fixes; re-running an old record uses its original commit.
|
|
40
|
+
|
|
41
|
+
### Harmony SDK configuration
|
|
42
|
+
|
|
43
|
+
Configure **this repository's** Settings / Secrets and variables / Actions:
|
|
44
|
+
|
|
45
|
+
- Secret `HARMONY_SDK_URL`: authorized HTTPS download URL for the Linux x64
|
|
46
|
+
`commandline-tools-linux-x64-26.0.0.851.zip` bundle. A signed URL may expire.
|
|
47
|
+
- Variable `HARMONY_SDK_SHA256`:
|
|
48
|
+
`ab604bd92721d5cbcafd154e6461d46b9f1b105e7b89eab04a9d046681198082`
|
|
49
|
+
|
|
50
|
+
Secrets in another repository are not automatically inherited. Set the URL
|
|
51
|
+
again here; do not post it in an issue or commit its query parameters. The script
|
|
52
|
+
verifies the archive and only extracts the Native SDK subtree. A `.har` package
|
|
53
|
+
or a Windows/macOS host SDK is not a Linux C/C++ toolchain. A local Linux build
|
|
54
|
+
may instead set `OHOS_SDK_NATIVE_DIR` to an already installed Native SDK.
|
|
55
|
+
|
|
56
|
+
If a run reports `Missing HARMONY_SDK_URL`, open
|
|
57
|
+
[this repository's Actions secrets](https://github.com/restia-aozora/webrtc-apm-deps-build/settings/secrets/actions),
|
|
58
|
+
choose **New repository secret**, enter `HARMONY_SDK_URL` as the name, and
|
|
59
|
+
paste a current, authorized HTTPS SDK download URL as the value without quotes.
|
|
60
|
+
The checksum belongs in the separate `HARMONY_SDK_SHA256` variable; a valid
|
|
61
|
+
checksum does not replace the download URL. This error is a configuration
|
|
62
|
+
preflight failure, not a native compiler failure. Start a new workflow run on
|
|
63
|
+
the updated `main` after configuring the secret to use the current inputs.
|
|
64
|
+
|
|
65
|
+
## Integration
|
|
66
|
+
|
|
67
|
+
### Android
|
|
68
|
+
|
|
69
|
+
Keep the existing Java/Kotlin class `com.aozora.audio.AozoraWebRtcApm` and its six
|
|
70
|
+
native methods: `nativeCreate`, `nativeProcessReverse`, `nativeProcessCapture`,
|
|
71
|
+
`nativeSetStreamDelay`, `nativeGetVoiceDetected`, and `nativeRelease`. Load
|
|
72
|
+
`aozora_apm` and install the `.so` for each supported ABI in the plugin's libs
|
|
73
|
+
directory. Preserve these class and method names in R8/ProGuard rules. Do not
|
|
74
|
+
rename JNI methods or install a library built for a different ABI.
|
|
75
|
+
|
|
76
|
+
### iOS
|
|
77
|
+
|
|
78
|
+
Add and embed/sign `AozoraApm.xcframework` with your application, then import
|
|
79
|
+
`AozoraApm` from Swift. The build only ad-hoc signs the library; it does not sign
|
|
80
|
+
your application or provide an Apple distribution identity.
|
|
81
|
+
|
|
82
|
+
```swift
|
|
83
|
+
import AozoraApm
|
|
84
|
+
let processor = aozora_apm_create(16000, 1, 1, 0)
|
|
85
|
+
// Feed 160 Int16 samples per render/capture call at 16 kHz.
|
|
86
|
+
aozora_apm_destroy(processor)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### HarmonyOS
|
|
90
|
+
|
|
91
|
+
Copy `app-harmony/libs/arm64-v8a` into the native libs location of your consuming
|
|
92
|
+
module, including any packaged `libc++_shared.so`. Add the bundled
|
|
93
|
+
`types/libaozora_apm` local type package as the module dependency named
|
|
94
|
+
`libaozora_apm.so`, then import it from ArkTS. The package's `index.d.ts` describes
|
|
95
|
+
`create`, `processReverse`, `processCapture`, `setStreamDelay`,
|
|
96
|
+
`getVoiceDetected` and `release`. Audio buffers are `Int16Array`, not raw pointer
|
|
97
|
+
numbers. This repository provides the native interface and types, not a full
|
|
98
|
+
UTS recorder implementation or microphone permissions configuration.
|
|
99
|
+
|
|
100
|
+
### Frame and lifetime rules
|
|
101
|
+
|
|
102
|
+
Feed the actual playback-reference PCM to the reverse-stream API and microphone
|
|
103
|
+
PCM to the capture API in matching 10 ms frames. Supply measured playback-path
|
|
104
|
+
delay before capture processing. Processing is in-place. A return of zero or
|
|
105
|
+
the WebRTC bad-stream-parameter warning indicates an accepted frame; other
|
|
106
|
+
negative results indicate errors. Voice state is -1 (unknown), 0 or 1.
|
|
107
|
+
|
|
108
|
+
Do not destroy a C/JNI handle concurrently with processing, and never reuse a
|
|
109
|
+
destroyed pointer/long handle. Release N-API handles explicitly when finished;
|
|
110
|
+
their finalizer also handles garbage collection. The wrapper serializes
|
|
111
|
+
processing on one instance but does not manage application audio threads.
|
|
112
|
+
|
|
113
|
+
## Local builds
|
|
114
|
+
|
|
115
|
+
Use Python 3.12+, Git, and the platform compiler. The script clones a fresh
|
|
116
|
+
pinned upstream commit and never edits an existing source checkout.
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
python -m pip install -r requirements-build.txt
|
|
120
|
+
python -m unittest discover -s tests -p 'test_*.py' -v
|
|
121
|
+
python scripts/build.py android --android-abis arm64-v8a --jobs 3
|
|
122
|
+
python scripts/build.py android --android-abis arm64-v8a armeabi-v7a --jobs 3
|
|
123
|
+
python scripts/build.py ios --jobs 3
|
|
124
|
+
python scripts/build.py harmony --jobs 3
|
|
125
|
+
python scripts/build.py host --jobs 3
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Android requires `ANDROID_NDK_HOME` pointing to NDK `27.2.12479018`. Android
|
|
129
|
+
cross-compilation also supports Windows. iOS requires macOS/Xcode. Harmony and
|
|
130
|
+
the host smoke test require Linux. `--source-repository /path/to/local/clone`
|
|
131
|
+
can use a local clone without modifying it; the exact pinned commit remains
|
|
132
|
+
mandatory. Work files remain in ignored `.work/` for diagnosis; artifacts go to
|
|
133
|
+
ignored `dist/`. Do not upload work directories or SDK archives.
|
|
134
|
+
|
|
135
|
+
## Provenance and validation limits
|
|
136
|
+
|
|
137
|
+
The source commit and dependency hashes match the reference local build.
|
|
138
|
+
The known `absl_crc` dependency fix is applied with an exact-match, fail-closed
|
|
139
|
+
patch. The wrapper compiles inside upstream Meson using the same architecture
|
|
140
|
+
and platform definitions; static dependencies, including private architecture
|
|
141
|
+
libraries, are resolved by Meson's dependency graph rather than a guessed list.
|
|
142
|
+
|
|
143
|
+
Native build scripts check exports, architecture, dynamic dependencies and
|
|
144
|
+
Android 16 KB ELF load alignment. iOS also checks a Swift module import. Those
|
|
145
|
+
checks do **not** establish HBuilderX/UTS integration, 16 KB APK zip alignment,
|
|
146
|
+
device loading, acoustic echo-cancellation quality, or upload quota compliance.
|
|
147
|
+
`manifest.json` explicitly reports device runtime and UTS validation as
|
|
148
|
+
`NOT RUN`. Complete native CI success is known only after an actual platform
|
|
149
|
+
run succeeds; this repository's existence is not evidence of that success.
|
|
150
|
+
|
|
151
|
+
Keep `licenses/` with redistributed artifacts. See `THIRD_PARTY_NOTICES.md`.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
The repository contains build scripts and an APM wrapper, not a vendored WebRTC
|
|
4
|
+
tree or redistributable SDK. Native builds retrieve the pinned dependency source
|
|
5
|
+
in `dependencies.json` and must ship its original notices with their artifacts.
|
|
6
|
+
|
|
7
|
+
- WebRTC Audio Processing: the helloooideeeeea/webrtc-audio-processing fork,
|
|
8
|
+
commit `f8efa8486ac0dd463fb84e4e99be2e072c28adb8`, derived from the freedesktop
|
|
9
|
+
AudioProcessing distribution and the WebRTC project. Preserve its COPYING,
|
|
10
|
+
LICENSE, AUTHORS, PATENTS and included third-party notices.
|
|
11
|
+
- Abseil C++ 20230125.1 and Meson wrapdb port: Apache-2.0 source dependency;
|
|
12
|
+
preserve its LICENSE and any notices from the pinned source/port.
|
|
13
|
+
- The upstream tree includes PFFFT, RNNoise and other components. The build
|
|
14
|
+
collects their license/notice files with source-relative paths rather than
|
|
15
|
+
assuming all embedded code has the top-level license.
|
|
16
|
+
- Android NDK C++ runtime is linked into the Android wrapper. Its upstream
|
|
17
|
+
license notices must accompany redistribution; the build collects the NDK
|
|
18
|
+
runtime/toolchain notice files separately.
|
|
19
|
+
- Harmony SDK and Apple SDK are external toolchains, not published in this
|
|
20
|
+
repository. Only bundle the required target runtime when permitted, preserve
|
|
21
|
+
its notices, and comply with the toolchain provider's distribution terms.
|
|
22
|
+
|
|
23
|
+
`LICENSE` in this repository covers the new wrapper/build code, not third-party
|
|
24
|
+
source, toolchains, or linked dependencies. Do not remove `licenses/` from a
|
|
25
|
+
published native artifact.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Copyright (c) 2011, Google Inc. All rights reserved.
|
|
2
|
+
|
|
3
|
+
Redistribution and use in source and binary forms, with or without
|
|
4
|
+
modification, are permitted provided that the following conditions are
|
|
5
|
+
met:
|
|
6
|
+
|
|
7
|
+
* Redistributions of source code must retain the above copyright
|
|
8
|
+
notice, this list of conditions and the following disclaimer.
|
|
9
|
+
|
|
10
|
+
* Redistributions in binary form must reproduce the above copyright
|
|
11
|
+
notice, this list of conditions and the following disclaimer in
|
|
12
|
+
the documentation and/or other materials provided with the
|
|
13
|
+
distribution.
|
|
14
|
+
|
|
15
|
+
* Neither the name of Google nor the names of its contributors may
|
|
16
|
+
be used to endorse or promote products derived from this software
|
|
17
|
+
without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
20
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
21
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
22
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
23
|
+
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
24
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
25
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
26
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
27
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
28
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|