Skip to content

Commit a6e40b5

Browse files
committed
make info optional, include constants for codecs
1 parent 72bb79f commit a6e40b5

File tree

10 files changed

+95
-16
lines changed

10 files changed

+95
-16
lines changed

examples/server/v1/components.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ def __init__(self, name: str):
251251

252252
async def play(self,
253253
data: bytes,
254-
info: AudioInfo,
254+
info: Optional[AudioInfo] = None,
255255
*,
256256
extra: Optional[Dict[str, Any]] = None,
257257
timeout: Optional[float] = None,
@@ -262,8 +262,11 @@ async def play(self,
262262

263263
# Simulate playing audio
264264
self.is_playing = True
265-
print(f"Playing audio: {len(data)} bytes, codec={info.codec}, "
266-
f"sample_rate={info.sample_rate_hz}, channels={info.num_channels}")
265+
if info:
266+
print(f"Playing audio: {len(data)} bytes, codec={info.codec}, "
267+
f"sample_rate={info.sample_rate_hz}, channels={info.num_channels}")
268+
else:
269+
print(f"Playing audio: {len(data)} bytes (no audio info provided)")
267270

268271
await asyncio.sleep(0.1)
269272

src/viam/audio_codecs.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
2+
class AudioCodec:
3+
"""Common audio codec identifiers.
4+
5+
These constants represent commonly supported audio codecs
6+
for audioin and audioout components.
7+
8+
Example::
9+
10+
from viam.components.codecs import AudioCodec
11+
from viam.proto.common import AudioInfo
12+
13+
audio_info = AudioInfo(
14+
codec=AudioCodec.PCM16,
15+
sample_rate_hz=44100,
16+
num_channels=2
17+
)
18+
"""
19+
20+
PCM16 = "pcm16"
21+
PCM32 = "pcm32"
22+
PCM32_FLOAT = "pcm32_float"
23+
MP3 = "mp3"
24+
AAC = "aac"
25+
OPUS = "opus"
26+
FLAC = "flac"

src/viam/components/audio_in/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from viam.resource.registry import Registry, ResourceRegistration
22

3+
from viam.proto.common import AudioInfo
4+
from viam.audio_codecs import AudioCodec
35
from .audio_in import AudioIn
46
from .client import AudioInClient
57
from .service import AudioInRPCService
@@ -9,6 +11,8 @@
911
__all__ = [
1012
"AudioIn",
1113
"AudioResponse",
14+
"AudioInfo",
15+
"AudioCodec",
1216
]
1317

1418
Registry.register_api(

src/viam/components/audio_in/audio_in.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,13 @@ async def get_audio(self, codec: str,
4242
Get a stream of audio from the device
4343
4444
::
45-
4645
my_audio_in = AudioIn.from_robot(robot=machine, name="my_audio_in")
4746
48-
stream = await my_audio_in.get_audio()
47+
stream = await my_audio_in.get_audio(
48+
codec=AudioCodec.PCM16,
49+
duration_seconds=10.0,
50+
previous_timestamp_ns=0
51+
)
4952
5053
Args:
5154
codec (str): The desired codec of the returned audio data
@@ -63,9 +66,7 @@ async def get_properties(self, *, timeout: Optional[float] = None, **kwargs) ->
6366
Get the audio device's properties
6467
6568
::
66-
6769
my_audio_in = AudioIn.from_robot(robot=machine, name="my_audio_in")
68-
6970
properties = await my_audio_in.get_properties()
7071
7172
Returns:

src/viam/components/audio_out/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
from viam.resource.registry import Registry, ResourceRegistration
22

33
from viam.proto.common import AudioInfo
4+
from viam.audio_codecs import AudioCodec
45
from .audio_out import AudioOut
56
from .client import AudioOutClient
67
from .service import AudioOutRPCService
78

89
__all__ = [
910
"AudioOut",
10-
"AudioInfo"
11+
"AudioInfo",
12+
"AudioCodec",
1113
]
1214

1315
Registry.register_api(

src/viam/components/audio_out/audio_out.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class AudioOut(ComponentBase):
3131
@abc.abstractmethod
3232
async def play(self,
3333
data: bytes,
34-
info: AudioInfo,
34+
info: Optional[AudioInfo] = None,
3535
*,
3636
extra: Optional[Dict[str, Any]] = None,
3737
timeout: Optional[float] = None,
@@ -40,15 +40,18 @@ async def play(self,
4040
Play the given audio data.
4141
4242
::
43-
4443
my_audio_out = AudioOut.from_robot(robot=machine, name="my_audio_out")
4544
46-
audio_info = AudioInfo(codec="pcm16", sample_rate=44100, num_channels=2)
45+
# With audio info
46+
audio_info = AudioInfo(codec=AudioCodec.PCM16, sample_rate_hz=44100, num_channels=2)
4747
await my_audio_out.play(audio_data, audio_info)
4848
49+
# Without audio info (when codec encodes information within audio_data)
50+
await my_audio_out.play(audio_data)
51+
4952
Args:
5053
data: audio bytes to play
51-
info: information about the audio data such as codec, sample rate, and channel count
54+
info: (optional) information about the audio data such as codec, sample rate, and channel count
5255
"""
5356

5457
@abc.abstractmethod
@@ -61,7 +64,6 @@ async def get_properties(self,
6164
Get the audio output device's properties.
6265
6366
::
64-
6567
my_audio_out = AudioOut.from_robot(robot=machine, name="my_audio_out")
6668
properties = await my_audio_out.get_properties()
6769

src/viam/components/audio_out/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def __init__(self, name: str, channel: Channel) -> None:
3232

3333
async def play(self,
3434
data: bytes,
35-
info: AudioInfo,
35+
info: Optional[AudioInfo] = None,
3636
*,
3737
extra: Optional[Dict[str, Any]] = None,
3838
timeout: Optional[float] = None,

src/viam/components/audio_out/service.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ async def Play(self, stream: Stream[PlayRequest, PlayResponse]) -> None:
3030
name = request.name
3131
audio_out = self.get_resource(name)
3232
extra = struct_to_dict(request.extra)
33-
await audio_out.play(request.audio_data, request.audio_info, extra=extra)
33+
# Check if audio_info was provided in the request
34+
audio_info = request.audio_info if request.HasField("audio_info") else None
35+
await audio_out.play(request.audio_data, audio_info, extra=extra)
3436
await stream.send_message(PlayResponse())
3537

3638
async def GetProperties(self, stream: Stream[GetPropertiesRequest, GetPropertiesResponse]) -> None:

tests/mocks/components.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1144,7 +1144,7 @@ def __init__(self, name: str):
11441144
self.last_audio_data = None
11451145
self.last_audio_info = None
11461146

1147-
async def play(self, data: bytes, info, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None, **kwargs) -> None:
1147+
async def play(self, data: bytes, info: Optional[AudioInfo] = None, *, extra: Optional[Dict[str, Any]] = None, timeout: Optional[float] = None, **kwargs) -> None:
11481148
self.play_called = True
11491149
self.last_audio_data = data
11501150
self.last_audio_info = info

tests/test_audio_out.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ async def test_play(self, audio_out: MockAudioOut):
3737
assert audio_out.last_audio_data == audio_data
3838
assert audio_out.last_audio_info == audio_info
3939

40+
@pytest.mark.asyncio
41+
async def test_play_without_audio_info(self, audio_out: MockAudioOut):
42+
audio_data = b"test_audio_data"
43+
await audio_out.play(audio_data)
44+
assert audio_out.play_called
45+
assert audio_out.last_audio_data == audio_data
46+
assert audio_out.last_audio_info is None
47+
4048
@pytest.mark.asyncio
4149
async def test_get_properties(self, audio_out: MockAudioOut):
4250
properties = await audio_out.get_properties()
@@ -72,6 +80,25 @@ async def test_play(self, audio_out: MockAudioOut, service: AudioOutRPCService):
7280
assert audio_out.last_audio_data == audio_data
7381
assert audio_out.last_audio_info == audio_info
7482

83+
@pytest.mark.asyncio
84+
async def test_play_without_audio_info(self, audio_out: MockAudioOut, service: AudioOutRPCService):
85+
audio_data = b"test_audio_data"
86+
87+
async with ChannelFor([service]) as channel:
88+
client = AudioOutServiceStub(channel)
89+
request = PlayRequest(
90+
name=audio_out.name,
91+
audio_data=audio_data,
92+
audio_info=None,
93+
extra=dict_to_struct({}),
94+
)
95+
96+
await client.Play(request)
97+
98+
assert audio_out.play_called
99+
assert audio_out.last_audio_data == audio_data
100+
assert audio_out.last_audio_info is None
101+
75102
@pytest.mark.asyncio
76103
async def test_get_properties(self, audio_out: MockAudioOut, service: AudioOutRPCService):
77104
async with ChannelFor([service]) as channel:
@@ -122,6 +149,18 @@ async def test_play(self, audio_out: MockAudioOut, service: AudioOutRPCService):
122149
assert audio_out.last_audio_data == audio_data
123150
assert audio_out.last_audio_info == audio_info
124151

152+
@pytest.mark.asyncio
153+
async def test_play_without_audio_info(self, audio_out: MockAudioOut, service: AudioOutRPCService):
154+
async with ChannelFor([service]) as channel:
155+
client = AudioOutClient(audio_out.name, channel)
156+
audio_data = b"test_audio_data"
157+
158+
await client.play(audio_data)
159+
160+
assert audio_out.play_called
161+
assert audio_out.last_audio_data == audio_data
162+
assert audio_out.last_audio_info is None
163+
125164
@pytest.mark.asyncio
126165
async def test_get_properties(self, audio_out: MockAudioOut,service: AudioOutRPCService):
127166
async with ChannelFor([service]) as channel:

0 commit comments

Comments
 (0)