Skip to content

Commit 3ef1d9b

Browse files
authored
Merge pull request #689 from cph-cachet/audiostreamer-dev
Audiostreamer dev: custom sample rate
2 parents 58d5c23 + 7581d1d commit 3ef1d9b

24 files changed

Lines changed: 270 additions & 143 deletions

File tree

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,59 @@
1+
## 2.3.0
2+
3+
- implemented custom sample rate functionality
4+
- [PR#521](https://github.com/cph-cachet/flutter-plugins/pull/521)
5+
- [PR#522](https://github.com/cph-cachet/flutter-plugins/pull/522)
6+
17
## 2.2.0+1
8+
29
- updated example app podfile to correctly include permission for iOS
310
- updated README to include podfile permission
411

512
## 2.2.0
13+
614
- upgrade of `permission_handler: ^10.0.0`
715
- Upgraded to Dart 2.17 and Flutter 3.0
816

917
## 2.1.0
18+
1019
- upgrade of `permission_handler: ^9.2.0`
1120
- [PR#503](https://github.com/cph-cachet/flutter-plugins/pull/503)
1221
- [PR#504](https://github.com/cph-cachet/flutter-plugins/pull/504)
1322

1423
## 2.0.3
24+
1525
- [PR#371](https://github.com/cph-cachet/flutter-plugins/pull/371)
1626

1727
## 2.0.2
28+
1829
- [PR#364](https://github.com/cph-cachet/flutter-plugins/pull/364)
1930
- upgrade to `permission_handler: ^8.1.0`
20-
31+
2132
## 2.0.0
33+
2234
- Null safety migration
2335

2436
## 1.3.0
37+
2538
- Fixed an issue where using another media player/recorder would cause the plugin to go into an error state on iOS (see https://github.com/cph-cachet/flutter-plugins/issues/86)
2639

2740
## 1.2.0
41+
2842
- Fixed an issue where the AVAudioRecorder would crash on iOS (see https://github.com/cph-cachet/flutter-plugins/issues/91)
2943

3044
## 1.1.6
45+
3146
- Upgrade to `permission_handler` v. 5.
3247

3348
## 1.1.5
49+
3450
- Added a getter for the sample rate field.
3551

3652
## 1.1.0
53+
3754
- Able to stream audio data on Android as well
3855
- The plugin will now record as soon as the permission dialog ends.
3956

4057
## 1.0.0
58+
4159
- Able to stream audio data on iOS.

packages/audio_streamer/README.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# Audio Streamer
22

3-
Streaming of PCM audio from Android and iOS with a sample rate of 44,100 Hz
3+
Streaming of PCM audio from Android and iOS with a customizable sample rate.
44

55
## Permissions
6+
67
On **Android** add the audio recording permission to `AndroidManifest.xml`:
78

89
```xml
@@ -11,8 +12,8 @@ On **Android** add the audio recording permission to `AndroidManifest.xml`:
1112

1213
On **iOS** enable the following:
1314

14-
* Capabilities > Background Modes > _Audio, AirPlay and Picture in Picture_
15-
* In the Runner Xcode project edit the `Info.plist` file. Add an entry for _'Privacy - Microphone Usage Description'_
15+
- Capabilities > Background Modes > _Audio, AirPlay and Picture in Picture_
16+
- In the Runner Xcode project edit the `Info.plist` file. Add an entry for _'Privacy - Microphone Usage Description'_
1617

1718
When editing the `Info.plist` file manually, the entries needed are:
1819

@@ -25,7 +26,7 @@ When editing the `Info.plist` file manually, the entries needed are:
2526
</array>
2627
```
2728

28-
* Edit the `Podfile` to include the permission for the microphone:
29+
- Edit the `Podfile` to include the permission for the microphone:
2930

3031
```ruby
3132
post_install do |installer|
@@ -43,13 +44,15 @@ end
4344
```
4445

4546
## Example
47+
4648
See the file `example/lib/main.dart` for a fully fledged example app using the plugin.
49+
Note that on iOS the sample rate will not necessarily change, as there is only the option to set a preferred one.
4750

4851
```dart
4952
AudioStreamer _streamer = AudioStreamer();
5053
bool _isRecording = false;
5154
List<double> _audio = [];
52-
55+
5356
void onAudio(List<double> buffer) {
5457
_audio.addAll(buffer);
5558
double secondsRecorded = _audio.length.toDouble() / _streamer.sampleRate.toDouble();
@@ -58,7 +61,8 @@ See the file `example/lib/main.dart` for a fully fledged example app using the p
5861
5962
void start() async {
6063
try {
61-
_streamer.start(onAudio);
64+
//_streamer.start(onAudio, handleError, sampleRate: 16000); //uses custom sample rate
65+
_streamer.start(onAudio, handleError); //uses default sample rate of 44100 Hz
6266
setState(() {
6367
_isRecording = true;
6468
});

packages/audio_streamer/android/src/main/kotlin/plugins/cachet/audio_streamer/AudioStreamerPlugin.kt

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,45 @@ import io.flutter.embedding.engine.plugins.activity.ActivityAware
1515
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
1616
import io.flutter.plugin.common.EventChannel
1717
import io.flutter.plugin.common.EventChannel.EventSink
18+
import io.flutter.plugin.common.MethodChannel
1819
import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener
1920
import java.util.*
2021

2122
/** AudioStreamerPlugin */
2223
class AudioStreamerPlugin : FlutterPlugin, RequestPermissionsResultListener, EventChannel.StreamHandler, ActivityAware {
2324

24-
/// Constants
25+
// / Constants
2526
private val eventChannelName = "audio_streamer.eventChannel"
26-
private val sampleRate = 44100
27-
private var bufferSize = 6400 * 2; /// Magical number!
27+
28+
// / Method channel for returning the sample rate.
29+
private val methodChannelName = "audio_streamer.methodChannel"
30+
private var sampleRate = 44100 // standard value to initialize
31+
private var bufferSize = 6400 * 2; // / Magical number!
2832
private val maxAmplitude = 32767 // same as 2^15
2933
private val logTag = "AudioStreamerPlugin"
3034

31-
/// Variables (i.e. will change value)
35+
// / Variables (i.e. will change value)
3236
private var eventSink: EventSink? = null
3337
private var recording = false
3438

3539
private var currentActivity: Activity? = null
3640

41+
private lateinit var audioRecord: AudioRecord;
42+
3743
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
3844
val messenger = flutterPluginBinding.binaryMessenger
3945
val eventChannel = EventChannel(messenger, eventChannelName)
4046
eventChannel.setStreamHandler(this)
47+
val methodChannel = MethodChannel(messenger, methodChannelName)
48+
methodChannel.setMethodCallHandler {
49+
call, result ->
50+
if (call.method == "getSampleRate") {
51+
// Sample rate never changes, so return the given sample rate.
52+
result.success(audioRecord?.getSampleRate())
53+
} else {
54+
result.notImplemented()
55+
}
56+
}
4157
}
4258

4359
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
@@ -63,11 +79,16 @@ class AudioStreamerPlugin : FlutterPlugin, RequestPermissionsResultListener, Eve
6379
}
6480

6581
/**
66-
* Called from Flutter, starts the stream.
82+
* Called from Flutter, starts the stream and updates the sample rate using the argument.
6783
*/
6884
override fun onListen(arguments: Any?, events: EventSink?) {
6985
this.eventSink = events
7086
recording = true
87+
sampleRate = (arguments as Map<*, *>)["sampleRate"] as Int
88+
if (sampleRate < 4000 || sampleRate > 48000) {
89+
events!!.error("SampleRateError", "A sample rate of " + sampleRate + "Hz is not supported by Android.", null)
90+
return
91+
}
7192
streamMicData()
7293
}
7394

@@ -92,42 +113,45 @@ class AudioStreamerPlugin : FlutterPlugin, RequestPermissionsResultListener, Eve
92113
/**
93114
* Starts recording and streaming audio data from the mic.
94115
* Uses a buffer array of size 512. Whenever buffer is full, the content is sent to Flutter.
95-
*
116+
* Sets the sample rate as defined by [sampleRate].
96117
*
97118
* Source:
98119
* https://www.newventuresoftware.com/blog/record-play-and-visualize-raw-audio-data-in-android
99120
*/
100121
private fun streamMicData() {
101-
Thread(Runnable {
102-
Process.setThreadPriority(Process.THREAD_PRIORITY_AUDIO)
103-
val audioBuffer = ShortArray(bufferSize / 2)
104-
val record = AudioRecord(
122+
Thread(
123+
Runnable {
124+
Process.setThreadPriority(Process.THREAD_PRIORITY_AUDIO)
125+
val audioBuffer = ShortArray(bufferSize / 2)
126+
audioRecord = AudioRecord(
105127
MediaRecorder.AudioSource.DEFAULT,
106128
sampleRate,
107129
AudioFormat.CHANNEL_IN_MONO,
108130
AudioFormat.ENCODING_PCM_16BIT,
109-
bufferSize)
110-
if (record.state != AudioRecord.STATE_INITIALIZED) {
111-
Log.e(logTag, "Audio Record can't initialize!")
112-
return@Runnable
113-
}
114-
/** Start recording loop */
115-
record.startRecording()
116-
while (recording) {
117-
/** Read data into buffer */
118-
record.read(audioBuffer, 0, audioBuffer.size)
119-
Handler(Looper.getMainLooper()).post {
120-
/// Convert to list in order to send via EventChannel.
121-
val audioBufferList = ArrayList<Double>()
122-
for (impulse in audioBuffer) {
123-
val normalizedImpulse = impulse.toDouble() / maxAmplitude.toDouble()
124-
audioBufferList.add(normalizedImpulse)
131+
bufferSize,
132+
)
133+
if (audioRecord.state != AudioRecord.STATE_INITIALIZED) {
134+
Log.e(logTag, "Audio Record can't initialize!")
135+
return@Runnable
136+
}
137+
/** Start recording loop */
138+
audioRecord.startRecording()
139+
while (recording) {
140+
/** Read data into buffer */
141+
audioRecord.read(audioBuffer, 0, audioBuffer.size)
142+
Handler(Looper.getMainLooper()).post {
143+
// / Convert to list in order to send via EventChannel.
144+
val audioBufferList = ArrayList<Double>()
145+
for (impulse in audioBuffer) {
146+
val normalizedImpulse = impulse.toDouble() / maxAmplitude.toDouble()
147+
audioBufferList.add(normalizedImpulse)
148+
}
149+
eventSink!!.success(audioBufferList)
125150
}
126-
eventSink!!.success(audioBufferList)
127151
}
128-
}
129-
record.stop()
130-
record.release()
131-
}).start()
152+
audioRecord.stop()
153+
audioRecord.release()
154+
},
155+
).start()
132156
}
133157
}

packages/audio_streamer/example/ios/Flutter/.last_build_id

Lines changed: 0 additions & 1 deletion
This file was deleted.

packages/audio_streamer/example/lib/main.dart

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ class _MyAppState extends State<MyApp> {
2323
super.initState();
2424
}
2525

26-
void onAudio(List<double> buffer) {
26+
void onAudio(List<double> buffer) async {
2727
_audio.addAll(buffer);
28-
double secondsRecorded =
29-
_audio.length.toDouble() / AudioStreamer.sampleRate.toDouble();
28+
var sampleRate = await AudioStreamer.currSampleRate;
29+
double secondsRecorded = _audio.length.toDouble() / sampleRate;
3030
print('Max amp: ${buffer.reduce(max)}');
3131
print('Min amp: ${buffer.reduce(min)}');
3232
print('$secondsRecorded seconds recorded.');
@@ -43,7 +43,9 @@ class _MyAppState extends State<MyApp> {
4343

4444
void start() async {
4545
try {
46-
_streamer.start(onAudio, handleError);
46+
//_streamer.start(onAudio, handleError, sampleRate: 16000); //uses custom sample rate
47+
_streamer.start(
48+
onAudio, handleError); //uses default sample rate of 44100 Hz
4749
setState(() {
4850
_isRecording = true;
4951
});

0 commit comments

Comments
 (0)