Skip to content

Commit 334da1b

Browse files
committed
no need to remove audio in
1 parent 4951cfa commit 334da1b

File tree

5 files changed

+439
-0
lines changed

5 files changed

+439
-0
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from viam.media.audio import AudioCodec
2+
from viam.proto.common import AudioInfo
3+
from viam.resource.registry import Registry, ResourceRegistration
4+
5+
from .audio_in import AudioIn
6+
from .client import AudioInClient
7+
from .service import AudioInRPCService
8+
9+
AudioResponse = AudioIn.AudioResponse
10+
11+
__all__ = [
12+
"AudioIn",
13+
"AudioResponse",
14+
"AudioInfo",
15+
"AudioCodec",
16+
]
17+
18+
Registry.register_api(
19+
ResourceRegistration(
20+
AudioIn,
21+
AudioInRPCService,
22+
lambda name, channel: AudioInClient(name, channel),
23+
)
24+
)
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import abc
2+
import sys
3+
from typing import Final, Optional
4+
5+
from viam.proto.common import GetPropertiesResponse
6+
from viam.proto.component.audioin import GetAudioResponse
7+
from viam.resource.types import API, RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT
8+
from viam.streams import Stream
9+
10+
from ..component_base import ComponentBase
11+
12+
if sys.version_info >= (3, 10):
13+
from typing import TypeAlias
14+
else:
15+
from typing_extensions import TypeAlias
16+
17+
18+
class AudioIn(ComponentBase):
19+
"""AudioIn represents a component that can capture audio.
20+
21+
This acts as an abstract base class for any drivers representing specific
22+
audio input implementations. This cannot be used on its own. If the ``__init__()`` function is
23+
overridden, it must call the ``super().__init__()`` function.
24+
"""
25+
26+
API: Final = API( # pyright: ignore [reportIncompatibleVariableOverride]
27+
RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_COMPONENT, "audio_in"
28+
)
29+
30+
Properties: "TypeAlias" = GetPropertiesResponse
31+
AudioResponse: "TypeAlias" = GetAudioResponse
32+
AudioStream = Stream[AudioResponse]
33+
34+
@abc.abstractmethod
35+
async def get_audio(
36+
self, codec: str, duration_seconds: float, previous_timestamp_ns: int, *, timeout: Optional[float] = None, **kwargs
37+
) -> AudioStream:
38+
"""
39+
Get a stream of audio from the device
40+
41+
::
42+
43+
my_audio_in = AudioIn.from_robot(robot=machine, name="my_audio_in")
44+
45+
stream = await my_audio_in.get_audio(
46+
codec=AudioCodec.PCM16,
47+
duration_seconds=10.0,
48+
previous_timestamp_ns=0
49+
)
50+
51+
Args:
52+
codec (str): The desired codec of the returned audio data
53+
duration_seconds (float): duration of the stream. 0 = indefinite stream
54+
previous_timestamp_ns (int): starting timestamp in nanoseconds for recording continuity.
55+
Set to 0 to begin recording from the current time.
56+
Returns:
57+
AudioStream: stream of audio chunks.
58+
...
59+
"""
60+
61+
@abc.abstractmethod
62+
async def get_properties(self, *, timeout: Optional[float] = None, **kwargs) -> Properties:
63+
"""
64+
Get the audio device's properties
65+
66+
::
67+
68+
my_audio_in = AudioIn.from_robot(robot=machine, name="my_audio_in")
69+
properties = await my_audio_in.get_properties()
70+
71+
Returns:
72+
Properties: The properties of the audio in device.
73+
...
74+
"""
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import uuid
2+
from typing import Any, Dict, List, Mapping, Optional
3+
4+
from grpclib.client import Channel
5+
from grpclib.client import Stream as ClientStream
6+
7+
from viam.proto.common import DoCommandRequest, DoCommandResponse, Geometry, GetPropertiesRequest
8+
from viam.proto.component.audioin import AudioInServiceStub, GetAudioRequest, GetAudioResponse
9+
from viam.resource.rpc_client_base import ReconfigurableResourceRPCClientBase
10+
from viam.streams import StreamWithIterator
11+
from viam.utils import ValueTypes, dict_to_struct, get_geometries, struct_to_dict
12+
13+
from .audio_in import AudioIn
14+
15+
16+
class AudioInClient(AudioIn, ReconfigurableResourceRPCClientBase):
17+
def __init__(self, name: str, channel: Channel) -> None:
18+
self.channel = channel
19+
self.client = AudioInServiceStub(channel)
20+
super().__init__(name)
21+
22+
async def get_audio(
23+
self,
24+
codec: str,
25+
duration_seconds: float,
26+
previous_timestamp_ns: int,
27+
*,
28+
extra: Optional[Dict[str, Any]] = None,
29+
**kwargs,
30+
):
31+
request = GetAudioRequest(
32+
name=self.name,
33+
codec=codec,
34+
duration_seconds=duration_seconds,
35+
previous_timestamp_nanoseconds=previous_timestamp_ns,
36+
request_id=str(uuid.uuid4()),
37+
extra=dict_to_struct(extra),
38+
)
39+
40+
async def read():
41+
md = kwargs.get("metadata", self.Metadata()).proto
42+
audio_stream: ClientStream[GetAudioRequest, GetAudioResponse]
43+
async with self.client.GetAudio.open(metadata=md) as audio_stream:
44+
try:
45+
await audio_stream.send_message(request, end=True)
46+
async for response in audio_stream:
47+
yield response
48+
except Exception as e:
49+
raise (e)
50+
51+
return StreamWithIterator(read())
52+
53+
async def get_properties(
54+
self,
55+
*,
56+
timeout: Optional[float] = None,
57+
**kwargs,
58+
) -> AudioIn.Properties:
59+
md = kwargs.get("metadata", self.Metadata()).proto
60+
return await self.client.GetProperties(GetPropertiesRequest(name=self.name), timeout=timeout, metadata=md)
61+
62+
async def do_command(
63+
self,
64+
command: Mapping[str, ValueTypes],
65+
*,
66+
timeout: Optional[float] = None,
67+
**kwargs,
68+
) -> Mapping[str, ValueTypes]:
69+
md = kwargs.get("metadata", self.Metadata()).proto
70+
request = DoCommandRequest(name=self.name, command=dict_to_struct(command))
71+
response: DoCommandResponse = await self.client.DoCommand(request, timeout=timeout, metadata=md)
72+
return struct_to_dict(response.result)
73+
74+
async def get_geometries(self, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None, **kwargs) -> List[Geometry]:
75+
md = kwargs.get("metadata", self.Metadata())
76+
return await get_geometries(self.client, self.name, extra, timeout, md)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from grpclib.server import Stream
2+
from h2.exceptions import StreamClosedError
3+
4+
from viam.logging import getLogger
5+
from viam.proto.common import (
6+
DoCommandRequest,
7+
DoCommandResponse,
8+
GetGeometriesRequest,
9+
GetGeometriesResponse,
10+
GetPropertiesRequest,
11+
GetPropertiesResponse,
12+
)
13+
from viam.proto.component.audioin import AudioInServiceBase, GetAudioRequest, GetAudioResponse
14+
from viam.resource.rpc_service_base import ResourceRPCServiceBase
15+
from viam.utils import dict_to_struct, struct_to_dict
16+
17+
from .audio_in import AudioIn
18+
19+
LOGGER = getLogger(__name__)
20+
21+
22+
class AudioInRPCService(AudioInServiceBase, ResourceRPCServiceBase[AudioIn]):
23+
"""
24+
gRPC Service for a generic audio in.
25+
"""
26+
27+
RESOURCE_TYPE = AudioIn
28+
29+
async def GetAudio(self, stream: Stream[GetAudioRequest, GetAudioResponse]) -> None:
30+
request = await stream.recv_message()
31+
assert request is not None
32+
name = request.name
33+
audio_in = self.get_resource(name)
34+
audio_stream = await audio_in.get_audio(
35+
codec=request.codec,
36+
duration_seconds=request.duration_seconds,
37+
previous_timestamp_ns=request.previous_timestamp_nanoseconds,
38+
metadata=stream.metadata,
39+
)
40+
async for response in audio_stream:
41+
try:
42+
response.request_id = request.request_id
43+
await stream.send_message(response)
44+
except StreamClosedError:
45+
return
46+
except Exception as e:
47+
LOGGER.error(e)
48+
return
49+
50+
async def GetProperties(self, stream: Stream[GetPropertiesRequest, GetPropertiesResponse]) -> None:
51+
request = await stream.recv_message()
52+
assert request is not None
53+
name = request.name
54+
audio_in = self.get_resource(name)
55+
timeout = stream.deadline.time_remaining() if stream.deadline else None
56+
properties = await audio_in.get_properties(
57+
timeout=timeout,
58+
metadata=stream.metadata,
59+
)
60+
await stream.send_message(properties)
61+
62+
async def DoCommand(self, stream: Stream[DoCommandRequest, DoCommandResponse]) -> None:
63+
request = await stream.recv_message()
64+
assert request is not None
65+
name = request.name
66+
audio_in = self.get_resource(name)
67+
timeout = stream.deadline.time_remaining() if stream.deadline else None
68+
result = await audio_in.do_command(
69+
command=struct_to_dict(request.command),
70+
timeout=timeout,
71+
metadata=stream.metadata,
72+
)
73+
response = DoCommandResponse(result=dict_to_struct(result))
74+
await stream.send_message(response)
75+
76+
async def GetGeometries(self, stream: Stream[GetGeometriesRequest, GetGeometriesResponse]) -> None:
77+
request = await stream.recv_message()
78+
assert request is not None
79+
arm = self.get_resource(request.name)
80+
timeout = stream.deadline.time_remaining() if stream.deadline else None
81+
geometries = await arm.get_geometries(extra=struct_to_dict(request.extra), timeout=timeout)
82+
response = GetGeometriesResponse(geometries=geometries)
83+
await stream.send_message(response)

0 commit comments

Comments
 (0)