@siteed/expo-audio-studio 2.4.1 → 2.5.0
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/CHANGELOG.md +10 -1
- package/README.md +25 -0
- package/android/src/main/java/net/siteed/audiostream/AudioAnalysisData.kt +22 -0
- package/android/src/main/java/net/siteed/audiostream/AudioDeviceManager.kt +1501 -0
- package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +10 -5
- package/android/src/main/java/net/siteed/audiostream/AudioNotificationsManager.kt +27 -25
- package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +73 -71
- package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +576 -252
- package/android/src/main/java/net/siteed/audiostream/Constants.kt +17 -1
- package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +419 -155
- package/android/src/main/java/net/siteed/audiostream/LogUtils.kt +65 -0
- package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +9 -1
- package/build/AudioAnalysis/AudioAnalysis.types.js.map +1 -1
- package/build/AudioDeviceManager.d.ts +107 -0
- package/build/AudioDeviceManager.d.ts.map +1 -0
- package/build/AudioDeviceManager.js +493 -0
- package/build/AudioDeviceManager.js.map +1 -0
- package/build/AudioRecorder.provider.d.ts.map +1 -1
- package/build/AudioRecorder.provider.js +3 -0
- package/build/AudioRecorder.provider.js.map +1 -1
- package/build/ExpoAudioStream.types.d.ts +90 -1
- package/build/ExpoAudioStream.types.d.ts.map +1 -1
- package/build/ExpoAudioStream.types.js +7 -1
- package/build/ExpoAudioStream.types.js.map +1 -1
- package/build/ExpoAudioStream.web.d.ts +37 -0
- package/build/ExpoAudioStream.web.d.ts.map +1 -1
- package/build/ExpoAudioStream.web.js +399 -54
- package/build/ExpoAudioStream.web.js.map +1 -1
- package/build/ExpoAudioStreamModule.d.ts.map +1 -1
- package/build/ExpoAudioStreamModule.js +20 -0
- package/build/ExpoAudioStreamModule.js.map +1 -1
- package/build/WebRecorder.web.d.ts +63 -10
- package/build/WebRecorder.web.d.ts.map +1 -1
- package/build/WebRecorder.web.js +277 -68
- package/build/WebRecorder.web.js.map +1 -1
- package/build/hooks/useAudioDevices.d.ts +14 -0
- package/build/hooks/useAudioDevices.d.ts.map +1 -0
- package/build/hooks/useAudioDevices.js +151 -0
- package/build/hooks/useAudioDevices.js.map +1 -0
- package/build/index.d.ts +2 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4 -0
- package/build/index.js.map +1 -1
- package/build/useAudioRecorder.d.ts +1 -0
- package/build/useAudioRecorder.d.ts.map +1 -1
- package/build/useAudioRecorder.js +20 -1
- package/build/useAudioRecorder.js.map +1 -1
- package/build/utils/BlobFix.d.ts.map +1 -1
- package/build/utils/BlobFix.js +2 -2
- package/build/utils/BlobFix.js.map +1 -1
- package/build/workers/InlineFeaturesExtractor.web.d.ts +1 -1
- package/build/workers/InlineFeaturesExtractor.web.d.ts.map +1 -1
- package/build/workers/InlineFeaturesExtractor.web.js +27 -26
- package/build/workers/InlineFeaturesExtractor.web.js.map +1 -1
- package/build/workers/inlineAudioWebWorker.web.d.ts +1 -1
- package/build/workers/inlineAudioWebWorker.web.d.ts.map +1 -1
- package/build/workers/inlineAudioWebWorker.web.js +25 -1
- package/build/workers/inlineAudioWebWorker.web.js.map +1 -1
- package/ios/AudioDeviceManager.swift +654 -0
- package/ios/AudioStreamManager.swift +964 -760
- package/ios/ExpoAudioStreamModule.swift +174 -19
- package/ios/Features.swift +1 -1
- package/ios/ISSUE_IOS.md +45 -0
- package/ios/Logger.swift +13 -1
- package/ios/RecordingSettings.swift +12 -0
- package/package.json +2 -2
- package/src/AudioAnalysis/AudioAnalysis.types.ts +2 -2
- package/src/AudioDeviceManager.ts +571 -0
- package/src/AudioRecorder.provider.tsx +3 -0
- package/src/ExpoAudioStream.types.ts +97 -1
- package/src/ExpoAudioStream.web.ts +513 -63
- package/src/ExpoAudioStreamModule.ts +23 -0
- package/src/WebRecorder.web.ts +346 -81
- package/src/hooks/useAudioDevices.ts +180 -0
- package/src/index.ts +6 -0
- package/src/types/crc-32.d.ts +6 -6
- package/src/useAudioRecorder.tsx +27 -1
- package/src/utils/BlobFix.ts +6 -4
- package/src/workers/InlineFeaturesExtractor.web.tsx +27 -26
- package/src/workers/inlineAudioWebWorker.web.tsx +25 -1
|
@@ -15,6 +15,7 @@ import android.media.MediaCodec
|
|
|
15
15
|
import java.io.FileInputStream
|
|
16
16
|
import java.io.RandomAccessFile
|
|
17
17
|
import java.util.zip.CRC32
|
|
18
|
+
import net.siteed.audiostream.LogUtils
|
|
18
19
|
|
|
19
20
|
data class DecodingConfig(
|
|
20
21
|
val targetSampleRate: Int? = null, // Optional target sample rate
|
|
@@ -34,6 +35,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
34
35
|
const val DCT_SQRT_DIVISOR = 2.0
|
|
35
36
|
private const val N_FFT = 1024
|
|
36
37
|
private const val N_CHROMA = 12
|
|
38
|
+
private const val CLASS_NAME = "AudioProcessor" // Add class name constant for logging
|
|
37
39
|
|
|
38
40
|
private val uniqueIdCounter = AtomicLong(0L) // Keep as companion object property to maintain during pause/resume cycles
|
|
39
41
|
|
|
@@ -50,11 +52,11 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
50
52
|
private fun loadAudioFile(filePath: String): AudioData? {
|
|
51
53
|
try {
|
|
52
54
|
val fileUri = filePath.removePrefix("file://")
|
|
53
|
-
|
|
55
|
+
LogUtils.d(CLASS_NAME, "Processing WAV file: $fileUri")
|
|
54
56
|
|
|
55
57
|
val file = File(fileUri).takeIf { it.exists() } ?: File(filesDir, File(fileUri).name).takeIf { it.exists() }
|
|
56
58
|
?: run {
|
|
57
|
-
|
|
59
|
+
LogUtils.e(CLASS_NAME, "File not found: $fileUri")
|
|
58
60
|
return null
|
|
59
61
|
}
|
|
60
62
|
|
|
@@ -64,14 +66,14 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
64
66
|
// Read RIFF header
|
|
65
67
|
val riffHeader = ByteArray(4).apply { raf.readFully(this) }
|
|
66
68
|
if (String(riffHeader) != "RIFF") {
|
|
67
|
-
|
|
69
|
+
LogUtils.e(CLASS_NAME, "Invalid RIFF header")
|
|
68
70
|
return null
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
// Read WAVE header
|
|
72
74
|
val waveHeader = ByteArray(4).apply { raf.readFully(this) }
|
|
73
75
|
if (String(waveHeader) != "WAVE") {
|
|
74
|
-
|
|
76
|
+
LogUtils.e(CLASS_NAME, "Invalid WAVE header")
|
|
75
77
|
return null
|
|
76
78
|
}
|
|
77
79
|
|
|
@@ -89,12 +91,12 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
89
91
|
val chunkSizeBytes = ByteArray(4).apply { raf.readFully(this) }
|
|
90
92
|
val chunkSize = ByteBuffer.wrap(chunkSizeBytes).order(ByteOrder.LITTLE_ENDIAN).int.toLong() and 0xFFFFFFFFL
|
|
91
93
|
|
|
92
|
-
|
|
94
|
+
LogUtils.d(CLASS_NAME, "Found chunk: $chunkId ($chunkSize bytes)")
|
|
93
95
|
|
|
94
96
|
when (chunkId) {
|
|
95
97
|
"fmt " -> {
|
|
96
98
|
if (chunkSize < 16) {
|
|
97
|
-
|
|
99
|
+
LogUtils.e(CLASS_NAME, "Invalid fmt chunk size")
|
|
98
100
|
return null
|
|
99
101
|
}
|
|
100
102
|
|
|
@@ -109,11 +111,11 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
109
111
|
val blockAlign = formatBuffer.short
|
|
110
112
|
bitDepth = formatBuffer.short.toInt() and 0xFFFF
|
|
111
113
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
+
LogUtils.d(CLASS_NAME, "Raw format data: ${formatData.joinToString(", ")}")
|
|
115
|
+
LogUtils.d(CLASS_NAME, "Format chunk: audioFormat=$audioFormat, channels=$channels, sampleRate=$sampleRate, bitDepth=$bitDepth, byteRate=$byteRate, blockAlign=$blockAlign")
|
|
114
116
|
|
|
115
117
|
if (bitDepth !in listOf(8, 16, 32)) {
|
|
116
|
-
|
|
118
|
+
LogUtils.e(CLASS_NAME, "Invalid bit depth: $bitDepth")
|
|
117
119
|
return null
|
|
118
120
|
}
|
|
119
121
|
|
|
@@ -141,17 +143,17 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
141
143
|
}
|
|
142
144
|
|
|
143
145
|
if (!fmtChunkFound || !dataChunkFound) {
|
|
144
|
-
|
|
146
|
+
LogUtils.e(CLASS_NAME, "Missing essential chunks (fmt=$fmtChunkFound, data=$dataChunkFound)")
|
|
145
147
|
return null
|
|
146
148
|
}
|
|
147
149
|
|
|
148
150
|
// Calculate actual data size if it seems wrong
|
|
149
151
|
if (dataSize <= 0 || dataSize > fileSize - dataOffset) {
|
|
150
152
|
dataSize = fileSize - dataOffset
|
|
151
|
-
|
|
153
|
+
LogUtils.d(CLASS_NAME, "Adjusted data size to: $dataSize")
|
|
152
154
|
}
|
|
153
155
|
|
|
154
|
-
|
|
156
|
+
LogUtils.d(CLASS_NAME, "Reading PCM data: offset=$dataOffset, size=$dataSize")
|
|
155
157
|
|
|
156
158
|
val wavData = ByteArray(dataSize.toInt())
|
|
157
159
|
raf.seek(dataOffset)
|
|
@@ -163,7 +165,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
163
165
|
val numFrames = wavData.size / bytesPerFrame
|
|
164
166
|
val durationMs = (numFrames * 1000L) / sampleRate
|
|
165
167
|
|
|
166
|
-
|
|
168
|
+
LogUtils.d(CLASS_NAME, "WAV duration calculation: size=${wavData.size}, bytesPerFrame=$bytesPerFrame, numFrames=$numFrames, sampleRate=$sampleRate, duration=${durationMs}ms")
|
|
167
169
|
|
|
168
170
|
return AudioData(
|
|
169
171
|
data = wavData,
|
|
@@ -173,7 +175,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
173
175
|
durationMs = durationMs
|
|
174
176
|
)
|
|
175
177
|
} catch (e: Exception) {
|
|
176
|
-
|
|
178
|
+
LogUtils.e(CLASS_NAME, "Failed to load WAV file: ${e.message}", e)
|
|
177
179
|
return null
|
|
178
180
|
}
|
|
179
181
|
}
|
|
@@ -186,7 +188,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
186
188
|
*/
|
|
187
189
|
fun processAudioData(data: ByteArray, config: RecordingConfig): AudioAnalysisData {
|
|
188
190
|
if (data.isEmpty()) {
|
|
189
|
-
|
|
191
|
+
LogUtils.e(CLASS_NAME, "Received empty audio data")
|
|
190
192
|
return AudioAnalysisData(
|
|
191
193
|
segmentDurationMs = config.segmentDurationMs,
|
|
192
194
|
durationMs = 0,
|
|
@@ -216,12 +218,12 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
216
218
|
val samplesPerSegment = ((config.segmentDurationMs / 1000.0) * sampleRate).toInt()
|
|
217
219
|
val totalPoints = ceil(totalSamples.toDouble() / samplesPerSegment).toInt()
|
|
218
220
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
+
LogUtils.d(CLASS_NAME, "Extracting waveform totalSize=${data.size} with $totalSamples samples --> $totalPoints points")
|
|
222
|
+
LogUtils.d(CLASS_NAME, "segmentDuration: ${config.segmentDurationMs}ms, samplesPerSegment: $samplesPerSegment")
|
|
221
223
|
|
|
222
224
|
// Remove expectedPoints calculation since it used pointsPerSecond
|
|
223
225
|
val samplesPerPoint = ceil(channelData.size / totalPoints.toDouble()).toInt()
|
|
224
|
-
|
|
226
|
+
LogUtils.d(CLASS_NAME, "Extracting waveform with samplesPerPoints=$samplesPerPoint")
|
|
225
227
|
|
|
226
228
|
val dataPoints = mutableListOf<DataPoint>()
|
|
227
229
|
var minAmplitude = Float.MAX_VALUE
|
|
@@ -372,21 +374,21 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
372
374
|
val mfcc = try {
|
|
373
375
|
if (featureOptions["mfcc"] == true) computeMFCC(segmentData, sampleRate) else emptyList()
|
|
374
376
|
} catch (e: Exception) {
|
|
375
|
-
|
|
377
|
+
LogUtils.e(CLASS_NAME, "Failed to extract MFCC: ${e.message}", e)
|
|
376
378
|
emptyList()
|
|
377
379
|
}
|
|
378
380
|
|
|
379
381
|
val melSpectrogram = try {
|
|
380
382
|
if (featureOptions["melSpectrogram"] == true) computeMelSpectrogram(segmentData, sampleRate) else emptyList()
|
|
381
383
|
} catch (e: Exception) {
|
|
382
|
-
|
|
384
|
+
LogUtils.e(CLASS_NAME, "Failed to compute mel spectrogram: ${e.message}", e)
|
|
383
385
|
emptyList()
|
|
384
386
|
}
|
|
385
387
|
|
|
386
388
|
val chroma = try {
|
|
387
389
|
if (featureOptions["chromagram"] == true) computeChroma(segmentData, sampleRate) else emptyList()
|
|
388
390
|
} catch (e: Exception) {
|
|
389
|
-
|
|
391
|
+
LogUtils.e(CLASS_NAME, "Failed to compute chroma: ${e.message}", e)
|
|
390
392
|
emptyList()
|
|
391
393
|
}
|
|
392
394
|
|
|
@@ -402,28 +404,28 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
402
404
|
val tempo = try {
|
|
403
405
|
if (featureOptions["tempo"] == true) extractTempo(segmentData, sampleRate) else 0f
|
|
404
406
|
} catch (e: Exception) {
|
|
405
|
-
|
|
407
|
+
LogUtils.e(CLASS_NAME, "Failed to extract tempo: ${e.message}", e)
|
|
406
408
|
0f
|
|
407
409
|
}
|
|
408
410
|
|
|
409
411
|
val hnr = try {
|
|
410
412
|
if (featureOptions["hnr"] == true) extractHNR(segmentData) else 0f
|
|
411
413
|
} catch (e: Exception) {
|
|
412
|
-
|
|
414
|
+
LogUtils.e(CLASS_NAME, "Failed to extract HNR: ${e.message}", e)
|
|
413
415
|
0f
|
|
414
416
|
}
|
|
415
417
|
|
|
416
418
|
val spectralContrast = try {
|
|
417
419
|
if (featureOptions["spectralContrast"] == true) computeSpectralContrast(segmentData, sampleRate) else emptyList()
|
|
418
420
|
} catch (e: Exception) {
|
|
419
|
-
|
|
421
|
+
LogUtils.e(CLASS_NAME, "Failed to compute spectral contrast: ${e.message}", e)
|
|
420
422
|
emptyList()
|
|
421
423
|
}
|
|
422
424
|
|
|
423
425
|
val tonnetz = try {
|
|
424
426
|
if (featureOptions["tonnetz"] == true) computeTonnetz(segmentData, sampleRate) else emptyList()
|
|
425
427
|
} catch (e: Exception) {
|
|
426
|
-
|
|
428
|
+
LogUtils.e(CLASS_NAME, "Failed to compute tonnetz: ${e.message}", e)
|
|
427
429
|
emptyList()
|
|
428
430
|
}
|
|
429
431
|
|
|
@@ -661,7 +663,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
661
663
|
)
|
|
662
664
|
|
|
663
665
|
if (melFilters.any { it.size != powerSpectrum.size }) {
|
|
664
|
-
|
|
666
|
+
LogUtils.e(CLASS_NAME, "Mel filter size (${melFilters[0].size}) does not match power spectrum size (${powerSpectrum.size})")
|
|
665
667
|
return emptyList()
|
|
666
668
|
}
|
|
667
669
|
|
|
@@ -812,14 +814,14 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
812
814
|
val cleanUri = fileUri.removePrefix("file://")
|
|
813
815
|
val file = File(cleanUri).takeIf { it.exists() } ?: File(filesDir, File(cleanUri).name).takeIf { it.exists() }
|
|
814
816
|
?: run {
|
|
815
|
-
|
|
817
|
+
LogUtils.e(CLASS_NAME, "File not found in any location: $cleanUri")
|
|
816
818
|
return null
|
|
817
819
|
}
|
|
818
820
|
|
|
819
821
|
// First try MediaExtractor
|
|
820
822
|
val extractor = MediaExtractor()
|
|
821
823
|
try {
|
|
822
|
-
|
|
824
|
+
LogUtils.d(CLASS_NAME, "Attempting MediaExtractor with path: ${file.absolutePath}")
|
|
823
825
|
extractor.setDataSource(file.absolutePath)
|
|
824
826
|
|
|
825
827
|
// Find the first audio track
|
|
@@ -838,10 +840,10 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
838
840
|
} catch (e: Exception) {
|
|
839
841
|
(format.getString(MediaFormat.KEY_DURATION) ?: "-1").toLong()
|
|
840
842
|
}
|
|
841
|
-
|
|
843
|
+
LogUtils.d(CLASS_NAME, "Raw duration from format: ${totalDurationUs}us")
|
|
842
844
|
|
|
843
845
|
val totalDurationMs = totalDurationUs / 1000
|
|
844
|
-
|
|
846
|
+
LogUtils.d(CLASS_NAME, "Final duration: ${totalDurationMs}ms")
|
|
845
847
|
|
|
846
848
|
// Process using MediaExtractor
|
|
847
849
|
val pcmData = decodeAudioToPCM(extractor, format)
|
|
@@ -867,14 +869,14 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
867
869
|
)
|
|
868
870
|
}
|
|
869
871
|
} catch (e: Exception) {
|
|
870
|
-
|
|
872
|
+
LogUtils.d(CLASS_NAME, "MediaExtractor failed, attempting WAV parser: ${e.message}")
|
|
871
873
|
} finally {
|
|
872
874
|
extractor.release()
|
|
873
875
|
}
|
|
874
876
|
|
|
875
877
|
// If MediaExtractor failed and file is WAV, try WAV parser
|
|
876
878
|
if (file.name.lowercase().endsWith(".wav")) {
|
|
877
|
-
|
|
879
|
+
LogUtils.d(CLASS_NAME, "Falling back to WAV parser")
|
|
878
880
|
return loadAudioFile(file.absolutePath)?.let { wavData ->
|
|
879
881
|
if (decodingConfig != null) {
|
|
880
882
|
val processedData = processAudio(
|
|
@@ -898,7 +900,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
898
900
|
}
|
|
899
901
|
}
|
|
900
902
|
|
|
901
|
-
|
|
903
|
+
LogUtils.e(CLASS_NAME, "Failed to process audio file with both MediaExtractor and WAV parser")
|
|
902
904
|
return null
|
|
903
905
|
}
|
|
904
906
|
|
|
@@ -1081,11 +1083,11 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
1081
1083
|
raf.readFully(bytes)
|
|
1082
1084
|
}
|
|
1083
1085
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
+
LogUtils.d(CLASS_NAME, "WAV Header Bytes: ${bytes.joinToString(", ") { String.format("%02X", it) }}")
|
|
1087
|
+
LogUtils.d(CLASS_NAME, "ASCII: ${bytes.map { it.toInt().toChar() }.joinToString("")}")
|
|
1086
1088
|
|
|
1087
1089
|
val buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN)
|
|
1088
|
-
|
|
1090
|
+
LogUtils.d(CLASS_NAME, """
|
|
1089
1091
|
RIFF header: ${String(bytes, 0, 4)}
|
|
1090
1092
|
File size: ${buffer.getInt(4)}
|
|
1091
1093
|
WAVE header: ${String(bytes, 8, 4)}
|
|
@@ -1099,7 +1101,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
1099
1101
|
Bits per sample: ${buffer.getShort(34)}
|
|
1100
1102
|
""".trimIndent())
|
|
1101
1103
|
} catch (e: Exception) {
|
|
1102
|
-
|
|
1104
|
+
LogUtils.e(CLASS_NAME, "Failed to debug WAV header: ${e.message}", e)
|
|
1103
1105
|
}
|
|
1104
1106
|
}
|
|
1105
1107
|
|
|
@@ -1112,7 +1114,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
1112
1114
|
): AudioAnalysisData {
|
|
1113
1115
|
val totalDurationMs = audioData.durationMs
|
|
1114
1116
|
|
|
1115
|
-
|
|
1117
|
+
LogUtils.d(CLASS_NAME, "Total audio duration: ${totalDurationMs}ms")
|
|
1116
1118
|
|
|
1117
1119
|
// Validate time range
|
|
1118
1120
|
if (startTimeMs != null) {
|
|
@@ -1123,7 +1125,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
1123
1125
|
if (endTimeMs != null) {
|
|
1124
1126
|
require(endTimeMs >= 0) { "endTime must be non-negative, got: $endTimeMs" }
|
|
1125
1127
|
if (endTimeMs > totalDurationMs) {
|
|
1126
|
-
|
|
1128
|
+
LogUtils.w(CLASS_NAME, "endTime ($endTimeMs) is beyond audio duration ($totalDurationMs), clamping to duration")
|
|
1127
1129
|
}
|
|
1128
1130
|
if (startTimeMs != null) {
|
|
1129
1131
|
require(startTimeMs < endTimeMs) { "startTime ($startTimeMs) must be less than endTime ($endTimeMs)" }
|
|
@@ -1135,7 +1137,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
1135
1137
|
val effectiveEndMs = (endTimeMs ?: totalDurationMs).coerceAtMost(totalDurationMs)
|
|
1136
1138
|
val durationMs = effectiveEndMs - effectiveStartMs
|
|
1137
1139
|
|
|
1138
|
-
|
|
1140
|
+
LogUtils.d(CLASS_NAME, "Preview range: ${effectiveStartMs}ms to ${effectiveEndMs}ms (${durationMs}ms)")
|
|
1139
1141
|
|
|
1140
1142
|
// Calculate sample range
|
|
1141
1143
|
val startSampleIndex = ((effectiveStartMs * audioData.sampleRate) / 1000).toInt()
|
|
@@ -1199,7 +1201,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
1199
1201
|
samples = segmentData.size
|
|
1200
1202
|
))
|
|
1201
1203
|
} catch (e: Exception) {
|
|
1202
|
-
|
|
1204
|
+
LogUtils.e(CLASS_NAME, "Error processing segment $i: ${e.message}")
|
|
1203
1205
|
throw IllegalStateException("Failed to process audio segment: ${e.message}", e)
|
|
1204
1206
|
}
|
|
1205
1207
|
}
|
|
@@ -1260,17 +1262,17 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
1260
1262
|
|
|
1261
1263
|
// If it's a WAV file (by extension and header verification)
|
|
1262
1264
|
return if (isWavByExtension && headerSize != null) {
|
|
1263
|
-
|
|
1265
|
+
LogUtils.d(CLASS_NAME, "Loading WAV range with header size: $headerSize bytes")
|
|
1264
1266
|
loadWavRange(fileUri, startTimeMs, endTimeMs, effectiveConfig, headerSize)
|
|
1265
1267
|
} else {
|
|
1266
1268
|
if (isWavByExtension) {
|
|
1267
|
-
|
|
1269
|
+
LogUtils.w(CLASS_NAME, "File has .wav extension but invalid header, falling back to compressed loader")
|
|
1268
1270
|
}
|
|
1269
|
-
|
|
1271
|
+
LogUtils.d(CLASS_NAME, "Loading compressed audio range")
|
|
1270
1272
|
loadCompressedAudioRange(fileUri, startTimeMs, endTimeMs, effectiveConfig)
|
|
1271
1273
|
}
|
|
1272
1274
|
} catch (e: Exception) {
|
|
1273
|
-
|
|
1275
|
+
LogUtils.e(CLASS_NAME, "Failed to load audio range: ${e.message}", e)
|
|
1274
1276
|
return null
|
|
1275
1277
|
}
|
|
1276
1278
|
}
|
|
@@ -1297,7 +1299,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
1297
1299
|
val startByte = headerSize + startByteOffset
|
|
1298
1300
|
val endByte = headerSize + endByteOffset
|
|
1299
1301
|
|
|
1300
|
-
|
|
1302
|
+
LogUtils.d(CLASS_NAME, """
|
|
1301
1303
|
Loading WAV range:
|
|
1302
1304
|
- headerSize: $headerSize
|
|
1303
1305
|
- startByte: $startByte
|
|
@@ -1320,7 +1322,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
1320
1322
|
config.targetBitDepth
|
|
1321
1323
|
)
|
|
1322
1324
|
effectiveBitDepth = config.targetBitDepth
|
|
1323
|
-
|
|
1325
|
+
LogUtils.d(CLASS_NAME, "Converted bit depth from ${format.bitDepth} to ${config.targetBitDepth}")
|
|
1324
1326
|
}
|
|
1325
1327
|
|
|
1326
1328
|
return AudioData(
|
|
@@ -1331,7 +1333,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
1331
1333
|
durationMs = endTimeMs - startTimeMs
|
|
1332
1334
|
)
|
|
1333
1335
|
} catch (e: Exception) {
|
|
1334
|
-
|
|
1336
|
+
LogUtils.e(CLASS_NAME, "Failed to load WAV range: ${e.message}", e)
|
|
1335
1337
|
return null
|
|
1336
1338
|
}
|
|
1337
1339
|
}
|
|
@@ -1357,10 +1359,10 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
1357
1359
|
} catch (e: Exception) {
|
|
1358
1360
|
(format.getString(MediaFormat.KEY_DURATION) ?: "-1").toLong()
|
|
1359
1361
|
}
|
|
1360
|
-
|
|
1362
|
+
LogUtils.d(CLASS_NAME, "Raw duration from format: ${totalDurationUs}us")
|
|
1361
1363
|
|
|
1362
1364
|
val totalDurationMs = totalDurationUs / 1000
|
|
1363
|
-
|
|
1365
|
+
LogUtils.d(CLASS_NAME, "Final duration: ${totalDurationMs}ms")
|
|
1364
1366
|
|
|
1365
1367
|
// Calculate valid time range
|
|
1366
1368
|
val validStartMs = startTimeMs.coerceIn(0, totalDurationMs) ?: 0
|
|
@@ -1385,7 +1387,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
1385
1387
|
val samplesPerSecond = targetSampleRate * targetChannels
|
|
1386
1388
|
val totalBytes = (effectiveDurationMs * samplesPerSecond * bytesPerSample) / 1000
|
|
1387
1389
|
|
|
1388
|
-
|
|
1390
|
+
LogUtils.d(CLASS_NAME, """
|
|
1389
1391
|
Loading audio range:
|
|
1390
1392
|
- start: ${validStartMs}ms
|
|
1391
1393
|
- end: ${validEndMs}ms
|
|
@@ -1454,10 +1456,10 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
1454
1456
|
bitDepth = targetBitDepth,
|
|
1455
1457
|
durationMs = endTimeMs - startTimeMs // Use the actual time range
|
|
1456
1458
|
).also {
|
|
1457
|
-
|
|
1459
|
+
LogUtils.d(CLASS_NAME, "Loaded compressed audio with duration: ${effectiveDurationMs}ms")
|
|
1458
1460
|
}
|
|
1459
1461
|
} catch (e: Exception) {
|
|
1460
|
-
|
|
1462
|
+
LogUtils.e(CLASS_NAME, "Failed to load compressed audio range: ${e.message}", e)
|
|
1461
1463
|
return null
|
|
1462
1464
|
} finally {
|
|
1463
1465
|
decoder?.stop()
|
|
@@ -1489,7 +1491,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
1489
1491
|
|
|
1490
1492
|
val durationMs = (endTimeMs - startTimeMs).toInt()
|
|
1491
1493
|
|
|
1492
|
-
|
|
1494
|
+
LogUtils.d(CLASS_NAME, """
|
|
1493
1495
|
Trimming audio:
|
|
1494
1496
|
- start: ${startTimeMs}ms
|
|
1495
1497
|
- end: ${endTimeMs}ms
|
|
@@ -1546,7 +1548,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
1546
1548
|
bitDepth = audioData.bitDepth
|
|
1547
1549
|
)
|
|
1548
1550
|
} catch (e: Exception) {
|
|
1549
|
-
|
|
1551
|
+
LogUtils.e(CLASS_NAME, "Failed to trim audio: ${e.message}", e)
|
|
1550
1552
|
return null
|
|
1551
1553
|
}
|
|
1552
1554
|
}
|
|
@@ -1910,7 +1912,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
1910
1912
|
try {
|
|
1911
1913
|
fft.realForward(padded)
|
|
1912
1914
|
} catch (e: Exception) {
|
|
1913
|
-
|
|
1915
|
+
LogUtils.e(CLASS_NAME, "FFT forward transform failed: ${e.message}")
|
|
1914
1916
|
return 0.0f
|
|
1915
1917
|
}
|
|
1916
1918
|
|
|
@@ -1929,7 +1931,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
1929
1931
|
powerSpectrum[fftLength - i] = powerSpectrum[i] // Mirror for inverse FFT
|
|
1930
1932
|
}
|
|
1931
1933
|
} catch (e: Exception) {
|
|
1932
|
-
|
|
1934
|
+
LogUtils.e(CLASS_NAME, "Power spectrum computation failed: ${e.message}")
|
|
1933
1935
|
return 0.0f
|
|
1934
1936
|
}
|
|
1935
1937
|
|
|
@@ -1938,7 +1940,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
1938
1940
|
try {
|
|
1939
1941
|
fft.realInverse(powerSpectrum, autocorrelation)
|
|
1940
1942
|
} catch (e: Exception) {
|
|
1941
|
-
|
|
1943
|
+
LogUtils.e(CLASS_NAME, "FFT inverse transform failed: ${e.message}")
|
|
1942
1944
|
return 0.0f
|
|
1943
1945
|
}
|
|
1944
1946
|
|
|
@@ -2011,7 +2013,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
2011
2013
|
val cleanUri = fileUri.removePrefix("file://")
|
|
2012
2014
|
val file = File(cleanUri).takeIf { it.exists() } ?: File(filesDir, File(cleanUri).name).takeIf { it.exists() }
|
|
2013
2015
|
?: run {
|
|
2014
|
-
|
|
2016
|
+
LogUtils.e(CLASS_NAME, "File not found: $cleanUri")
|
|
2015
2017
|
return null
|
|
2016
2018
|
}
|
|
2017
2019
|
|
|
@@ -2025,7 +2027,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
2025
2027
|
bitDepth = 16 // Most compressed formats decode to 16-bit PCM
|
|
2026
2028
|
)
|
|
2027
2029
|
} catch (e: Exception) {
|
|
2028
|
-
|
|
2030
|
+
LogUtils.e(CLASS_NAME, "Failed to get audio format: ${e.message}", e)
|
|
2029
2031
|
return null
|
|
2030
2032
|
} finally {
|
|
2031
2033
|
extractor.release()
|
|
@@ -2064,7 +2066,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
2064
2066
|
val cleanUri = fileUri.removePrefix("file://")
|
|
2065
2067
|
val file = File(cleanUri).takeIf { it.exists() } ?: File(filesDir, File(cleanUri).name).takeIf { it.exists() }
|
|
2066
2068
|
?: run {
|
|
2067
|
-
|
|
2069
|
+
LogUtils.e(CLASS_NAME, "File not found: $cleanUri")
|
|
2068
2070
|
return null
|
|
2069
2071
|
}
|
|
2070
2072
|
|
|
@@ -2074,13 +2076,13 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
2074
2076
|
|
|
2075
2077
|
// Read RIFF header
|
|
2076
2078
|
if (inputStream.read(buffer) != 12) {
|
|
2077
|
-
|
|
2079
|
+
LogUtils.e(CLASS_NAME, "Failed to read RIFF header")
|
|
2078
2080
|
return null
|
|
2079
2081
|
}
|
|
2080
2082
|
|
|
2081
2083
|
// Verify RIFF header
|
|
2082
2084
|
if (String(buffer, 0, 4) != "RIFF" || String(buffer, 8, 4) != "WAVE") {
|
|
2083
|
-
|
|
2085
|
+
LogUtils.e(CLASS_NAME, "Invalid WAV file format")
|
|
2084
2086
|
return null
|
|
2085
2087
|
}
|
|
2086
2088
|
|
|
@@ -2090,7 +2092,7 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
2090
2092
|
// Read chunks until we find the data chunk
|
|
2091
2093
|
while (true) {
|
|
2092
2094
|
if (inputStream.read(buffer, 0, 8) != 8) {
|
|
2093
|
-
|
|
2095
|
+
LogUtils.e(CLASS_NAME, "Unexpected end of file while reading chunks")
|
|
2094
2096
|
break
|
|
2095
2097
|
}
|
|
2096
2098
|
|
|
@@ -2100,11 +2102,11 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
2100
2102
|
(buffer[4].toInt() and 0xFF)
|
|
2101
2103
|
|
|
2102
2104
|
val chunkId = String(buffer, 0, 4)
|
|
2103
|
-
|
|
2105
|
+
LogUtils.d(CLASS_NAME, "Found chunk: $chunkId, size: $chunkSize")
|
|
2104
2106
|
|
|
2105
2107
|
if (chunkId == "data") {
|
|
2106
2108
|
headerSize += 8 // Add chunk header size
|
|
2107
|
-
|
|
2109
|
+
LogUtils.d(CLASS_NAME, "Found data chunk at offset: $headerSize")
|
|
2108
2110
|
break
|
|
2109
2111
|
}
|
|
2110
2112
|
|
|
@@ -2113,11 +2115,11 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
2113
2115
|
}
|
|
2114
2116
|
|
|
2115
2117
|
inputStream.close()
|
|
2116
|
-
|
|
2118
|
+
LogUtils.d(CLASS_NAME, "Total WAV header size: $headerSize bytes")
|
|
2117
2119
|
return headerSize
|
|
2118
2120
|
|
|
2119
2121
|
} catch (e: Exception) {
|
|
2120
|
-
|
|
2122
|
+
LogUtils.e(CLASS_NAME, "Error calculating WAV header size: ${e.message}", e)
|
|
2121
2123
|
return null
|
|
2122
2124
|
}
|
|
2123
2125
|
}
|
|
@@ -2215,20 +2217,20 @@ class AudioProcessor(private val filesDir: File) {
|
|
|
2215
2217
|
durationMs = endTimeMs - startTimeMs
|
|
2216
2218
|
)
|
|
2217
2219
|
} catch (e: Exception) {
|
|
2218
|
-
|
|
2220
|
+
LogUtils.e(CLASS_NAME, "Failed to decode audio range: ${e.message}", e)
|
|
2219
2221
|
return null
|
|
2220
2222
|
} finally {
|
|
2221
2223
|
try {
|
|
2222
2224
|
decoder?.stop()
|
|
2223
2225
|
decoder?.release()
|
|
2224
2226
|
} catch (e: Exception) {
|
|
2225
|
-
|
|
2227
|
+
LogUtils.w(CLASS_NAME, "Error releasing decoder: ${e.message}")
|
|
2226
2228
|
}
|
|
2227
2229
|
|
|
2228
2230
|
try {
|
|
2229
2231
|
extractor.release()
|
|
2230
2232
|
} catch (e: Exception) {
|
|
2231
|
-
|
|
2233
|
+
LogUtils.w(CLASS_NAME, "Error releasing extractor: ${e.message}")
|
|
2232
2234
|
}
|
|
2233
2235
|
}
|
|
2234
2236
|
}
|