Skip to content

Commit c6294cc

Browse files
RSDK-12154 RSDK-12155 Audio component wrappers (#436)
* [WORKFLOW] Updating protos from viamrobotics/api, commit: 0e3309b0ccd42ed0e81262f749f0016f0244436d * audio * clean up protos * clean up protos * clean * format * clean up * clean up * format * test * fix play tests * get props test * use uint8List for audio data * protos * only alias common * add request_id * protos * test * revert * pr comments --------- Co-authored-by: viambot <[email protected]>
1 parent 212a4b8 commit c6294cc

File tree

12 files changed

+837
-0
lines changed

12 files changed

+837
-0
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import 'package:fixnum/fixnum.dart';
2+
3+
import '../../gen/common/v1/common.pb.dart';
4+
import '../../gen/component/audioin/v1/audioin.pb.dart';
5+
import '../../resource/base.dart';
6+
import '../../robot/client.dart';
7+
8+
export '../../media/audio.dart';
9+
10+
/// AudioIn represents a device that can capture audio.
11+
abstract class AudioIn extends Resource {
12+
static const Subtype subtype = Subtype(resourceNamespaceRDK, resourceTypeComponent, 'audio_in');
13+
14+
/// Stream audio from this device
15+
Stream<GetAudioResponse> getAudio({
16+
required String codec,
17+
double? durationSeconds,
18+
Int64? previousTimestampNanoseconds,
19+
Map<String, dynamic>? extra,
20+
});
21+
22+
/// Get the audio properties of this audio in device
23+
Future<GetPropertiesResponse> getProperties({Map<String, dynamic>? extra});
24+
25+
/// Get the [ResourceName] for this [AudioIn] with the given [name]
26+
static ResourceName getResourceName(String name) {
27+
return AudioIn.subtype.getResourceName(name);
28+
}
29+
30+
/// Get the [AudioIn] named [name] from the provided robot.
31+
static AudioIn fromRobot(RobotClient robot, String name) {
32+
return robot.getResource(AudioIn.getResourceName(name));
33+
}
34+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import 'package:fixnum/fixnum.dart';
2+
import 'package:grpc/grpc_connection_interface.dart';
3+
import 'package:uuid/uuid.dart';
4+
5+
import '../../gen/common/v1/common.pb.dart';
6+
import '../../gen/component/audioin/v1/audioin.pbgrpc.dart';
7+
import '../../gen/google/protobuf/struct.pb.dart';
8+
import '../../resource/base.dart';
9+
import '../../utils.dart';
10+
import 'audio_in.dart';
11+
12+
/// gRPC client for the [AudioIn] component.
13+
class AudioInClient extends AudioIn implements ResourceRPCClient {
14+
@override
15+
String name;
16+
17+
@override
18+
ClientChannelBase channel;
19+
20+
@override
21+
AudioInServiceClient get client => AudioInServiceClient(channel);
22+
23+
AudioInClient(this.name, this.channel);
24+
25+
@override
26+
Stream<GetAudioResponse> getAudio({
27+
required String codec,
28+
double? durationSeconds,
29+
Int64? previousTimestampNanoseconds,
30+
Map<String, dynamic>? extra,
31+
}) {
32+
final uuid = Uuid();
33+
final request = GetAudioRequest()
34+
..name = name
35+
..codec = codec
36+
..requestId = uuid.v4();
37+
if (durationSeconds != null) {
38+
request.durationSeconds = durationSeconds;
39+
}
40+
if (previousTimestampNanoseconds != null) {
41+
request.previousTimestampNanoseconds = previousTimestampNanoseconds;
42+
}
43+
if (extra != null) {
44+
request.extra = extra.toStruct();
45+
}
46+
return client.getAudio(request);
47+
}
48+
49+
@override
50+
Future<GetPropertiesResponse> getProperties({Map<String, dynamic>? extra}) async {
51+
final request = GetPropertiesRequest()
52+
..name = name
53+
..extra = extra?.toStruct() ?? Struct();
54+
return await client.getProperties(request);
55+
}
56+
57+
@override
58+
Future<Map<String, dynamic>> doCommand(Map<String, dynamic> command) async {
59+
final request = DoCommandRequest()
60+
..name = name
61+
..command = command.toStruct();
62+
final response = await client.doCommand(request);
63+
return response.result.toMap();
64+
}
65+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import 'package:grpc/grpc.dart';
2+
3+
import '../../gen/common/v1/common.pb.dart';
4+
import '../../gen/component/audioin/v1/audioin.pbgrpc.dart';
5+
import '../../resource/manager.dart';
6+
import '../../utils.dart';
7+
import 'audio_in.dart';
8+
9+
/// gRPC Service for an [AudioIn]
10+
class AudioInService extends AudioInServiceBase {
11+
final ResourceManager _manager;
12+
13+
AudioInService(this._manager);
14+
15+
AudioIn _fromManager(String name) {
16+
try {
17+
return _manager.getResource(AudioIn.getResourceName(name));
18+
} catch (e) {
19+
throw (GrpcError.notFound(e.toString()));
20+
}
21+
}
22+
23+
@override
24+
Stream<GetAudioResponse> getAudio(ServiceCall call, GetAudioRequest request) {
25+
final audioIn = _fromManager(request.name);
26+
final requestId = request.hasRequestId() ? request.requestId : '';
27+
28+
return audioIn
29+
.getAudio(
30+
codec: request.codec,
31+
durationSeconds: request.hasDurationSeconds() ? request.durationSeconds : null,
32+
previousTimestampNanoseconds: request.hasPreviousTimestampNanoseconds() ? request.previousTimestampNanoseconds : null,
33+
extra: request.hasExtra() ? request.extra.toMap() : null,
34+
)
35+
.map((response) {
36+
if (requestId.isNotEmpty) {
37+
response.requestId = requestId;
38+
}
39+
return response;
40+
});
41+
}
42+
43+
@override
44+
Future<GetPropertiesResponse> getProperties(ServiceCall call, GetPropertiesRequest request) {
45+
final audioIn = _fromManager(request.name);
46+
return audioIn.getProperties(extra: request.extra.toMap());
47+
}
48+
49+
@override
50+
Future<DoCommandResponse> doCommand(ServiceCall call, DoCommandRequest request) async {
51+
final audioIn = _fromManager(request.name);
52+
final result = await audioIn.doCommand(request.command.toMap());
53+
return DoCommandResponse()..result = result.toStruct();
54+
}
55+
56+
@override
57+
Future<GetGeometriesResponse> getGeometries(ServiceCall call, GetGeometriesRequest request) {
58+
throw UnimplementedError();
59+
}
60+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import 'dart:typed_data';
2+
3+
import '../../gen/common/v1/common.pb.dart';
4+
import '../../gen/component/audioout/v1/audioout.pb.dart';
5+
import '../../resource/base.dart';
6+
import '../../robot/client.dart';
7+
8+
export '../../media/audio.dart';
9+
10+
/// AudioOut represents a device that can play audio.
11+
abstract class AudioOut extends Resource {
12+
static const Subtype subtype = Subtype(resourceNamespaceRDK, resourceTypeComponent, 'audio_out');
13+
14+
/// Play audio data on this audio output device
15+
Future<PlayResponse> play({
16+
required Uint8List audioData,
17+
required AudioInfo audioInfo,
18+
Map<String, dynamic>? extra,
19+
});
20+
21+
/// Get the audio properties of this audio output device
22+
Future<GetPropertiesResponse> getProperties({Map<String, dynamic>? extra});
23+
24+
/// Get the [ResourceName] for this [AudioOut] with the given [name]
25+
static ResourceName getResourceName(String name) {
26+
return AudioOut.subtype.getResourceName(name);
27+
}
28+
29+
/// Get the [AudioOut] named [name] from the provided robot.
30+
static AudioOut fromRobot(RobotClient robot, String name) {
31+
return robot.getResource(AudioOut.getResourceName(name));
32+
}
33+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import 'dart:typed_data';
2+
3+
import 'package:grpc/grpc_connection_interface.dart';
4+
5+
import '../../gen/common/v1/common.pb.dart';
6+
import '../../gen/component/audioout/v1/audioout.pbgrpc.dart';
7+
import '../../gen/google/protobuf/struct.pb.dart';
8+
import '../../resource/base.dart';
9+
import '../../utils.dart';
10+
import 'audio_out.dart';
11+
12+
/// gRPC client for the [AudioOut] component.
13+
class AudioOutClient extends AudioOut implements ResourceRPCClient {
14+
@override
15+
String name;
16+
17+
@override
18+
ClientChannelBase channel;
19+
20+
@override
21+
AudioOutServiceClient get client => AudioOutServiceClient(channel);
22+
23+
AudioOutClient(this.name, this.channel);
24+
25+
@override
26+
Future<PlayResponse> play({
27+
required Uint8List audioData,
28+
required AudioInfo audioInfo,
29+
Map<String, dynamic>? extra,
30+
}) async {
31+
final request = PlayRequest()
32+
..name = name
33+
..audioData = audioData
34+
..audioInfo = audioInfo
35+
..extra = extra?.toStruct() ?? Struct();
36+
return await client.play(request);
37+
}
38+
39+
@override
40+
Future<GetPropertiesResponse> getProperties({Map<String, dynamic>? extra}) async {
41+
final request = GetPropertiesRequest()
42+
..name = name
43+
..extra = extra?.toStruct() ?? Struct();
44+
return await client.getProperties(request);
45+
}
46+
47+
@override
48+
Future<Map<String, dynamic>> doCommand(Map<String, dynamic> command) async {
49+
final request = DoCommandRequest()
50+
..name = name
51+
..command = command.toStruct();
52+
final response = await client.doCommand(request);
53+
return response.result.toMap();
54+
}
55+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import 'dart:typed_data';
2+
3+
import 'package:grpc/grpc.dart';
4+
5+
import '../../gen/common/v1/common.pb.dart';
6+
import '../../gen/component/audioout/v1/audioout.pbgrpc.dart';
7+
import '../../resource/manager.dart';
8+
import '../../utils.dart';
9+
import 'audio_out.dart';
10+
11+
/// gRPC Service for an [AudioOut]
12+
class AudioOutService extends AudioOutServiceBase {
13+
final ResourceManager _manager;
14+
15+
AudioOutService(this._manager);
16+
17+
AudioOut _fromManager(String name) {
18+
try {
19+
return _manager.getResource(AudioOut.getResourceName(name));
20+
} catch (e) {
21+
throw (GrpcError.notFound(e.toString()));
22+
}
23+
}
24+
25+
@override
26+
Future<PlayResponse> play(ServiceCall call, PlayRequest request) {
27+
final audioOut = _fromManager(request.name);
28+
final audioData = request.audioData;
29+
return audioOut.play(
30+
audioData: audioData is Uint8List ? audioData : Uint8List.fromList(audioData),
31+
audioInfo: request.audioInfo,
32+
extra: request.hasExtra() ? request.extra.toMap() : null,
33+
);
34+
}
35+
36+
@override
37+
Future<GetPropertiesResponse> getProperties(ServiceCall call, GetPropertiesRequest request) {
38+
final audioOut = _fromManager(request.name);
39+
return audioOut.getProperties(extra: request.extra.toMap());
40+
}
41+
42+
@override
43+
Future<DoCommandResponse> doCommand(ServiceCall call, DoCommandRequest request) async {
44+
final audioOut = _fromManager(request.name);
45+
final result = await audioOut.doCommand(request.command.toMap());
46+
return DoCommandResponse()..result = result.toStruct();
47+
}
48+
49+
@override
50+
Future<GetGeometriesResponse> getGeometries(ServiceCall call, GetGeometriesRequest request) {
51+
throw UnimplementedError();
52+
}
53+
}

lib/src/media/audio.dart

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/// Common audio utilities and constants shared across audio components.
2+
library;
3+
4+
/// Common audio codec constants
5+
///
6+
/// These constants represent standard audio codec formats supported
7+
/// by Viam audio components (AudioIn and AudioOut).
8+
///
9+
/// Example usage:
10+
/// ```dart
11+
/// audioIn.getAudio(
12+
/// codec: AudioCodec.mp3,
13+
/// durationSeconds: 5.0,
14+
/// );
15+
/// ```
16+
class AudioCodec {
17+
const AudioCodec._();
18+
19+
static const String mp3 = 'mp3';
20+
static const String pcm16 = 'pcm16';
21+
static const String pcm32 = 'pcm32';
22+
static const String pcm32Float = 'pcm32float';
23+
static const String aac = 'aac';
24+
static const String opus = 'opus';
25+
static const String flac = 'flac';
26+
}

lib/src/resource/registry.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import 'package:grpc/grpc_connection_interface.dart';
22

33
import '../components/arm/arm.dart';
44
import '../components/arm/client.dart';
5+
import '../components/audio_in/audio_in.dart';
6+
import '../components/audio_in/client.dart';
7+
import '../components/audio_out/audio_out.dart';
8+
import '../components/audio_out/client.dart';
59
import '../components/base/base.dart';
610
import '../components/base/client.dart';
711
import '../components/board/board.dart';
@@ -63,6 +67,8 @@ class Registry {
6367
Registry._() {
6468
// Register built-in types
6569
registerSubtype(ResourceRegistration(Arm.subtype, (name, channel) => ArmClient(name, channel)));
70+
registerSubtype(ResourceRegistration(AudioIn.subtype, (name, channel) => AudioInClient(name, channel)));
71+
registerSubtype(ResourceRegistration(AudioOut.subtype, (name, channel) => AudioOutClient(name, channel)));
6672
registerSubtype(ResourceRegistration(Board.subtype, (name, channel) => BoardClient(name, channel)));
6773
registerSubtype(ResourceRegistration(Base.subtype, (name, channel) => BaseClient(name, channel)));
6874
registerSubtype(ResourceRegistration(Button.subtype, (name, channel) => ButtonClient(name, channel)));

lib/viam_sdk.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ export 'src/app/robot.dart';
1111
/// Components
1212
export 'src/components/arm/arm.dart';
1313
export 'src/components/arm/client.dart';
14+
export 'src/media/audio.dart';
15+
export 'src/components/audio_in/audio_in.dart';
16+
export 'src/components/audio_in/client.dart';
17+
export 'src/components/audio_out/audio_out.dart';
18+
export 'src/components/audio_out/client.dart';
1419
export 'src/components/base/base.dart';
1520
export 'src/components/base/client.dart';
1621
export 'src/components/board/board.dart';

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ dependencies:
2121
async: ^2.11.0
2222
bonsoir: ^5.1.8
2323
bson: ^5.0.5
24+
uuid: ^4.0.0
2425
flutter_platform_widgets: ^7.0.1
2526

2627
dev_dependencies:

0 commit comments

Comments
 (0)