Skip to content

Commit 685cd80

Browse files
✨ Update recorder normalization and updated default values for sample rate and bit rate. (#99)
Co-authored-by: ujas-m-simformsolutions <[email protected]>
1 parent 2beaee5 commit 685cd80

File tree

10 files changed

+89
-61
lines changed

10 files changed

+89
-61
lines changed

CHANGELOG.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
## 1.0.0 (Unreleased)
1+
## 1.0.0
22

33
- Reworked waveforms from audio file
4-
- Breaking: removed `readingComplete` PlayerState and `visualizerHeight`. With this, added `extractWaveforms` function to extract waveforms.
4+
- **Breaking**: removed `readingComplete` PlayerState and `visualizerHeight`. With this, added `extractWaveforms` function to extract waveforms.
55
- Added `WaveformType` enum for selecting longer or shorter type of waveform.
66
- Added `onCurrentExtractedWaveformData` and `onExtractionProgress` to monitor progress and currently extracted waveform data.
77
- improved drawing of waveforms.
@@ -10,7 +10,10 @@
1010
- Fixed [#101](https://github.com/SimformSolutionsPvtLtd/audio_waveforms/issues/101) - Fixed setting volume for android throws error
1111
- Fixed zero duration would cause waveforms not to expand
1212
- Fixed `postFrameCallback` error for flutter version below 3.0.0
13+
- **BREAKING**: Replaced `normalizationFactor` with `scaleFactor`and with this fixed [#43](https://github.com/SimformSolutionsPvtLtd/audio_waveforms/issues/43)
1314
- Updated default values for bitRate and sampleRate
15+
- Encoders, sample rate and bit rate can now Directly be set from `record` function.
16+
- Updated example app
1417

1518
## 0.1.5+1
1619

android/src/main/kotlin/com/simform/audio_waveforms/AudioRecorder.kt

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,14 @@ import io.flutter.plugin.common.MethodChannel
1212
import io.flutter.plugin.common.PluginRegistry
1313
import java.io.IOException
1414
import java.lang.IllegalStateException
15-
import kotlin.math.log10
1615

1716
private const val LOG_TAG = "AudioWaveforms"
1817
private const val RECORD_AUDIO_REQUEST_CODE = 1001
1918

2019
class AudioRecorder : PluginRegistry.RequestPermissionsResultListener {
2120
private var permissions = arrayOf(Manifest.permission.RECORD_AUDIO)
2221
fun getDecibel(result: MethodChannel.Result, recorder: MediaRecorder?) {
23-
val db = 20 * log10((recorder?.maxAmplitude?.toDouble() ?: 0.0 / 32768.0))
24-
if (db == Double.NEGATIVE_INFINITY) {
25-
Log.d(LOG_TAG, "Microphone might be turned off")
26-
} else {
27-
result.success(db)
28-
}
22+
result.success(recorder?.maxAmplitude?.toDouble() ?: 0.0)
2923
}
3024

3125
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)

example/lib/main.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ class _HomeState extends State<Home> with WidgetsBindingObserver {
222222
debugPrint(path);
223223
}
224224
} else {
225-
await recorderController.record(path);
225+
await recorderController.record(path: path);
226226
}
227227
setState(() {
228228
isRecording = !isRecording;

ios/Classes/AudioRecorder.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ public class AudioRecorder: NSObject, AVAudioRecorderDelegate{
99

1010
public func startRecording(_ result: @escaping FlutterResult,_ path: String?,_ encoder : Int?,_ sampleRate : Int?,_ bitRate : Int?,_ fileNameFormat: String){
1111
let settings = [
12-
AVEncoderBitRateKey: bitRate ?? 64000,
12+
AVEncoderBitRateKey: bitRate ?? 48000,
1313
AVFormatIDKey: getEncoder(encoder ?? 0),
14-
AVSampleRateKey: sampleRate ?? 16000,
14+
AVSampleRateKey: sampleRate ?? 44100,
1515
AVNumberOfChannelsKey: 1,
1616
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
1717
]
@@ -62,8 +62,9 @@ public class AudioRecorder: NSObject, AVAudioRecorderDelegate{
6262

6363
public func getDecibel(_ result: @escaping FlutterResult) {
6464
audioRecorder?.updateMeters()
65-
let amp = audioRecorder?.averagePower(forChannel: 0) ?? 0.0
66-
result(amp)
65+
let amp = audioRecorder?.peakPower(forChannel: 0) ?? 0.0
66+
let linear = pow(10, amp / 20);
67+
result(linear)
6768
}
6869

6970
public func checkHasPermission(_ result: @escaping FlutterResult){

lib/src/audio_waveforms.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ class _AudioWaveformsState extends State<AudioWaveforms> {
113113
widget.recorderController.setScrolledPositionDuration,
114114
shouldCalculateScrolledPosition:
115115
widget.shouldCalculateScrolledPosition,
116+
scaleFactor: widget.waveStyle.scaleFactor,
116117
),
117118
),
118119
),

lib/src/base/utils.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,13 @@ extension PlayerStateExtension on PlayerState {
176176

177177
bool get isPaused => this == PlayerState.paused;
178178
}
179+
180+
extension RecorderStateExtension on RecorderState {
181+
bool get isRecording => this == RecorderState.recording;
182+
183+
bool get isInitialized => this == RecorderState.initialized;
184+
185+
bool get isPaused => this == RecorderState.paused;
186+
187+
bool get isStopped => this == RecorderState.stopped;
188+
}

lib/src/base/wave_style.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ class WaveStyle {
8282
/// ```dart
8383
final Shader? gradient;
8484

85-
/// This is applied to each wave while generating.
86-
/// Use this to [scale] the waves. Defaults to 1.0.
85+
/// Default normalised amplitude/power we have are between 0.0 and 1.0.
86+
/// So scale them, [scaleFactor] can be used. Defaults to 20.0.
8787
final double scaleFactor;
8888

8989
/// A model class to provide style to the waveforms.
@@ -112,7 +112,7 @@ class WaveStyle {
112112
this.durationTextPadding = 20.0,
113113
this.durationLinesColor = Colors.blueAccent,
114114
this.gradient,
115-
this.scaleFactor = 1.0,
115+
this.scaleFactor = 20.0,
116116
}) : assert(waveThickness < spacing,
117117
"waveThickness can't be greater than spacing");
118118
}

lib/src/controllers/recorder_controller.dart

Lines changed: 58 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'dart:async';
22
import 'dart:io' show Platform;
3+
import 'dart:math' show max;
34

45
import 'package:flutter/material.dart';
56

@@ -10,23 +11,26 @@ class RecorderController extends ChangeNotifier {
1011
final List<double> _waveData = [];
1112

1213
/// At which rate waveform needs to be updated
13-
late Duration updateFrequency = const Duration(milliseconds: 100);
14+
Duration updateFrequency = const Duration(milliseconds: 100);
1415

15-
late AndroidEncoder androidEncoder = AndroidEncoder.aac;
16+
AndroidEncoder androidEncoder = AndroidEncoder.aac;
1617

17-
late AndroidOutputFormat androidOutputFormat = AndroidOutputFormat.mpeg4;
18+
AndroidOutputFormat androidOutputFormat = AndroidOutputFormat.mpeg4;
1819

19-
late IosEncoder iosEncoder = IosEncoder.kAudioFormatMPEG4AAC;
20+
IosEncoder iosEncoder = IosEncoder.kAudioFormatMPEG4AAC;
2021

21-
late int sampleRate = 44100;
22+
int sampleRate = 44100;
2223

23-
late int bitRate = 48000;
24+
int bitRate = 48000;
2425

25-
///Db we get from native is too high so in Android it the value is subtracted
26-
///and in IOS value added
27-
late double normalizationFactor = Platform.isAndroid ? 60 : 40;
26+
/// Current maximum peak power for ios and peak amplitude android.
27+
double _maxPeak = Platform.isIOS ? 1 : 32786.0;
2828

29-
///Current list of decibels(different values for each platform)
29+
/// Current list of scaled waves. For IOS, this list contains normalised
30+
/// peak power and for Android, this list contains normalised peak
31+
/// amplitude.
32+
///
33+
/// Values are between 0.0 to 1.0.
3034
List<double> get waveData => _waveData;
3135

3236
RecorderState _recorderState = RecorderState.stopped;
@@ -92,14 +96,27 @@ class RecorderController extends ChangeNotifier {
9296
///
9397
/// 2. Stopped -: If a recorder is stopped from previous recording and again
9498
/// this function is called then it will re-initialise the recorder.
95-
Future<void> record([String? path]) async {
96-
if (_recorderState != RecorderState.recording) {
99+
Future<void> record({
100+
String? path,
101+
AndroidEncoder? androidEncoder,
102+
AndroidOutputFormat? androidOutputFormat,
103+
IosEncoder? iosEncoder,
104+
int? sampleRate,
105+
int? bitRate,
106+
}) async {
107+
if (!_recorderState.isRecording) {
97108
await checkPermission();
98109
if (_hasPermission) {
99-
if (Platform.isAndroid && _recorderState == RecorderState.stopped) {
100-
await _initRecorder(path);
110+
if (Platform.isAndroid && _recorderState.isStopped) {
111+
await _initRecorder(
112+
path: path,
113+
androidEncoder: androidEncoder,
114+
androidOutputFormat: androidOutputFormat,
115+
sampleRate: sampleRate,
116+
bitRate: bitRate,
117+
);
101118
}
102-
if (_recorderState == RecorderState.paused) {
119+
if (_recorderState.isPaused) {
103120
_isRecording = await AudioWaveformsInterface.instance.resume();
104121
if (_isRecording) {
105122
_startTimer();
@@ -113,12 +130,13 @@ class RecorderController extends ChangeNotifier {
113130
if (Platform.isIOS) {
114131
_recorderState = RecorderState.initialized;
115132
}
116-
if (_recorderState == RecorderState.initialized) {
133+
if (_recorderState.isInitialized) {
117134
_isRecording = await AudioWaveformsInterface.instance.record(
118-
audioFormat:
119-
Platform.isIOS ? iosEncoder.index : androidEncoder.index,
120-
sampleRate: sampleRate,
121-
bitRate: bitRate,
135+
audioFormat: Platform.isIOS
136+
? iosEncoder?.index ?? this.iosEncoder.index
137+
: androidEncoder?.index ?? this.androidEncoder.index,
138+
sampleRate: sampleRate ?? this.sampleRate,
139+
bitRate: bitRate ?? this.bitRate,
122140
path: path,
123141
);
124142
if (_isRecording) {
@@ -137,13 +155,20 @@ class RecorderController extends ChangeNotifier {
137155
}
138156

139157
/// Initialises recorder for android platform.
140-
Future<void> _initRecorder(String? path) async {
158+
Future<void> _initRecorder({
159+
String? path,
160+
AndroidEncoder? androidEncoder,
161+
AndroidOutputFormat? androidOutputFormat,
162+
int? sampleRate,
163+
int? bitRate,
164+
}) async {
141165
final initialized = await AudioWaveformsInterface.instance.initRecorder(
142166
path: path,
143-
encoder: androidEncoder.index,
144-
outputFormat: androidOutputFormat.index,
145-
sampleRate: sampleRate,
146-
bitRate: bitRate,
167+
encoder: androidEncoder?.index ?? this.androidEncoder.index,
168+
outputFormat:
169+
androidOutputFormat?.index ?? this.androidOutputFormat.index,
170+
sampleRate: sampleRate ?? this.sampleRate,
171+
bitRate: bitRate ?? this.bitRate,
147172
);
148173
if (initialized) {
149174
_recorderState = RecorderState.initialized;
@@ -172,7 +197,7 @@ class RecorderController extends ChangeNotifier {
172197

173198
/// Pauses the current recording. Call [record] to resume recording.
174199
Future<void> pause() async {
175-
if (_recorderState == RecorderState.recording) {
200+
if (_recorderState.isRecording) {
176201
_isRecording = (await AudioWaveformsInterface.instance.pause()) ?? true;
177202
if (_isRecording) {
178203
throw "Failed to pause recording";
@@ -195,8 +220,7 @@ class RecorderController extends ChangeNotifier {
195220
/// manually else it will start showing waveforms from same place where it
196221
/// left of for previous recording.
197222
Future<String?> stop([bool callReset = true]) async {
198-
if (_recorderState == RecorderState.recording ||
199-
_recorderState == RecorderState.paused) {
223+
if (_recorderState.isRecording || _recorderState.isPaused) {
200224
final path = await AudioWaveformsInterface.instance.stop();
201225

202226
if (path != null) {
@@ -251,19 +275,12 @@ class RecorderController extends ChangeNotifier {
251275
);
252276
}
253277

254-
/// Normalises the decibel
255-
void _normalise(double db) {
256-
if (Platform.isAndroid) {
257-
waveData.add(db - normalizationFactor);
258-
} else {
259-
if (db == 0.0) {
260-
waveData.add(0);
261-
} else if (db + normalizationFactor < 1) {
262-
waveData.add(0);
263-
} else {
264-
waveData.add(db + normalizationFactor);
265-
}
266-
}
278+
/// Normalises the peak power for ios and peak amplitude for android
279+
void _normalise(double peak) {
280+
final absDb = peak.abs();
281+
_maxPeak = max(absDb, _maxPeak);
282+
final scaledWave = (absDb / _maxPeak);
283+
_waveData.add(scaledWave);
267284
notifyListeners();
268285
}
269286

lib/src/painters/recorder_wave_painter.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class RecorderWavePainter extends CustomPainter {
4848
final VoidCallback revertClearlabelCall;
4949
final Function(int) setCurrentPositionDuration;
5050
final bool shouldCalculateScrolledPosition;
51+
final double scaleFactor;
5152

5253
RecorderWavePainter({
5354
required this.waveData,
@@ -80,6 +81,7 @@ class RecorderWavePainter extends CustomPainter {
8081
required this.revertClearlabelCall,
8182
required this.setCurrentPositionDuration,
8283
required this.shouldCalculateScrolledPosition,
84+
required this.scaleFactor,
8385
}) : _wavePaint = Paint()
8486
..color = waveColor
8587
..strokeWidth = waveThickness
@@ -201,7 +203,7 @@ class RecorderWavePainter extends CustomPainter {
201203
dragOffset.dx +
202204
(spacing * i) -
203205
initialPosition,
204-
-waveData[i] + size.height - bottomPadding),
206+
-(waveData[i] * scaleFactor) + size.height - bottomPadding),
205207
_wavePaint);
206208
}
207209

@@ -218,7 +220,7 @@ class RecorderWavePainter extends CustomPainter {
218220
dragOffset.dx +
219221
(spacing * i) -
220222
initialPosition,
221-
waveData[i] + size.height - bottomPadding),
223+
(waveData[i] * scaleFactor) + size.height - bottomPadding),
222224
_wavePaint);
223225
}
224226

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: audio_waveforms
22
description: A Flutter package that allow you to generate waveform while recording audio or from audio file.
3-
version: 0.1.6
3+
version: 1.0.0
44
homepage: https://github.com/SimformSolutionsPvtLtd/audio_waveforms
55
issue_tracker: https://github.com/SimformSolutionsPvtLtd/audio_waveforms/issues
66

0 commit comments

Comments
 (0)