From 4018cec7ff0b5a5fc81506101717f965d56feb7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kajetan=20Rachwa=C5=82?= Date: Thu, 30 Jan 2025 16:37:45 +0100 Subject: [PATCH] test: add sound device connector tests --- .../communication/sound_device/connector.py | 14 +++++- .../sounds_device/test_connector.py | 45 +++++++++++++----- tests/resources/sine_wave.wav | Bin 0 -> 176444 bytes 3 files changed, 44 insertions(+), 15 deletions(-) create mode 100644 tests/resources/sine_wave.wav diff --git a/src/rai/rai/communication/sound_device/connector.py b/src/rai/rai/communication/sound_device/connector.py index f502dc36f..90fb652da 100644 --- a/src/rai/rai/communication/sound_device/connector.py +++ b/src/rai/rai/communication/sound_device/connector.py @@ -13,8 +13,12 @@ # limitations under the License. +import base64 +import io from typing import Callable, Literal, Optional, Sequence, Tuple +from scipy.io import wavfile + try: import sounddevice as sd except ImportError as e: @@ -90,7 +94,10 @@ def send_message(self, message: SoundDeviceMessage, target: str, **kwargs) -> No ) else: if message.audios is not None: - self.devices[target].write(message.audios[0]) + wav_bytes = base64.b64decode(message.audios[0]) + wav_buffer = io.BytesIO(wav_bytes) + _, audio_data = wavfile.read(wav_buffer) + self.devices[target].write(audio_data) else: raise SoundDeviceError("Failed to provice audios in message to play") @@ -118,7 +125,10 @@ def service_call( ) ret = SoundDeviceMessage(payload) else: - self.devices[target].write(message.payload.audio, blocking=True) + if message.audios is not None: + self.devices[target].write(message.audios[0], blocking=True) + else: + raise SoundDeviceError("Failed to provice audios in message to play") ret = SoundDeviceMessage() return ret diff --git a/tests/communication/sounds_device/test_connector.py b/tests/communication/sounds_device/test_connector.py index b503c600d..27e573c68 100644 --- a/tests/communication/sounds_device/test_connector.py +++ b/tests/communication/sounds_device/test_connector.py @@ -11,9 +11,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import base64 from unittest.mock import MagicMock, patch -import numpy as np import pytest import sounddevice @@ -29,7 +29,7 @@ def mock_sound_device_api(): with patch("rai.communication.sound_device.api.SoundDeviceAPI") as mock: mock_instance = MagicMock() - mock_instance.play = MagicMock() + mock_instance.write = MagicMock() mock_instance.rec = MagicMock() mock_instance.stop = MagicMock() mock.return_value = mock_instance @@ -58,21 +58,40 @@ def connector(mock_sound_device_api): ) targets = [("speaker", config)] sources = [("microphone", config)] - return SoundDeviceConnector(targets, sources) + ret = SoundDeviceConnector(targets, sources) + ret.devices["speaker"] = mock_sound_device_api + ret.devices["microphone"] = mock_sound_device_api + return ret -def test_send_message_play_audio(connector, mock_sound_device_api): + +@pytest.fixture +def base64_audio(): + # load audio file + audio_file = "tests/resources/sine_wave.wav" + with open(audio_file, "rb") as wav_file: + wav_bytes = wav_file.read() + base64_string = base64.b64encode(wav_bytes).decode( + "utf-8" + ) # Encode and convert to string + return base64_string + + +def test_send_message_play_audio(connector, mock_sound_device_api, base64_audio): message = SoundDeviceMessage( - payload=HRIPayload(text="", audios=[np.array([1, 2, 3])]) + payload=HRIPayload( + text="", + audios=[base64_audio], + ) ) connector.send_message(message, "speaker") - connector.devices["speaker"].assert_called_once_with(b"test_audio") + connector.devices["speaker"].write.assert_called_once() def test_send_message_stop_audio(connector, mock_sound_device_api): message = SoundDeviceMessage(stop=True) connector.send_message(message, "speaker") - connector.devices["speaker"].assert_called_once() + connector.devices["speaker"].stop.assert_called_once() def test_send_message_read_error(connector): @@ -84,19 +103,19 @@ def test_send_message_read_error(connector): connector.send_message(message, "speaker") -def test_service_call_play_audio(connector, mock_sound_device_api): - message = SoundDeviceMessage(payload=HRIPayload(text="", audios=["test_audio"])) +def test_service_call_play_audio(connector, mock_sound_device_api, base64_audio): + message = SoundDeviceMessage(payload=HRIPayload(text="", audios=[base64_audio])) result = connector.service_call(message, "speaker") - mock_sound_device_api.play.assert_called_once_with(b"test_audio", blocking=True) + mock_sound_device_api.write.assert_called_once() assert isinstance(result, SoundDeviceMessage) -def test_service_call_read_audio(connector, mock_sound_device_api): - mock_sound_device_api.record.return_value = b"recorded_audio" +def test_service_call_read_audio(connector, mock_sound_device_api, base64_audio): + mock_sound_device_api.record.return_value = base64_audio message = SoundDeviceMessage(read=True, duration=2.0) result = connector.service_call(message, "microphone") mock_sound_device_api.record.assert_called_once_with(2.0, blocking=True) - assert result.payload.audios == [b"recorded_audio"] + assert result.audios == [base64_audio] def test_service_call_stop_error(connector): diff --git a/tests/resources/sine_wave.wav b/tests/resources/sine_wave.wav new file mode 100644 index 0000000000000000000000000000000000000000..36544e1e6f451b5cded14af91b0ffb623ea51557 GIT binary patch literal 176444 zcmeI*_g7Q*{|9iw-g|^S!-fzNAoE_e7PMMdTPKe8Ff_0?*!*G2hxW67V_;xtY+ycW)oAyR(+Rg2Pq`02-~#JK zXja74Xd&kbcPNg*FBj| zgy~qf1Hyf8dG@-6JFl?cZk=L&z&OQV-(1?`AWW07A>7me@{hGhLjt%lQN_$=uW9fu2NjezLcZPi!IO;{!sE=d1jSS z?e2!D<_GQDx+Hx;gUnHvH*PcTMocpYt7+R>$6s8NJx09u`m2KsL+v8^qwd87a}UH_ zO4u!6icX22N$wM?$Xs%g>?7}xj^uoTFX>#)RToyA6z4UFWes=@G{Vx;cFIBRa@IY>`;nhb(4vqJ!{3dvin$p(#Pdin7yK(s5T{5| z2p;)2IXe5iB>Ch);;@7iD}_!1RstjLVXQ~Y`l!9(>qGp4{__)i|KQ&2;^Y`>JI``~ zX_DdF)AJ`5j;tEk*PGVa(l)m_tN#5O$EwjX(~^0G8R{i@0Xb3Gdlh=wAJPYO9kr6O zo&6*P^*6PP-XgsvvsPp(@2FnqI;wAKmKVpDB~>2R*48CAjkjj#j`k$$7Y)xJpFfqz zSYR^WBEiPnLGS!0bCp-4@1npf!B4^-M($=?#D2z0i~o_oQP?KpNJzq#{E6%#r^tMA z9r=jJl3Wz~iw+BJC7g)kasP^8MEgZJhc*PA@wf8X>+#xk(9y*%)GEd-#wdCw>J58T zH28ks(XM~m9a~N{xYg!Y{Zjr%Nqym*g3`Pna&Bc8Dc+Lnr8TrE{TaoaeX}B{Zz+-v zl`fV2C10V8R1vvn@(neQi_Vu`si3Rf>QWnnTFW|q>OR!JWhi-U-Q;?MjmGQES6GYe zot-P)PJ6ogo((8x&4f-z6h|N6bZ~>?qxsWNG*AYtRzMx<>G^) z27wKq5&s`|AtyaLFCr)ON7mwiN}oi}8*W8Tqjt8|ZszXBZU!!sZexB!@%`(%uXnVz zEN|4-o~vG0v8wdTqOyYId}gktil@9O7syW>I=$=?x9U(>*allf3oSE zsl1&9c0~;(wdKau%WI_#%UdSfsjlz)jt}l1-TNkG=77^pX0z zvlfNEAHj)kh}ppXHSXVpn*y;YU7Rm@No*#I$Z4{Z{Ejpv*@U-5Et)4h!ao|H#H;1Z zVW&hM30o6vA9&A~>9w1g;ap`uU_E1QXkx&ao}3vo9cJo%(mRiVD6-%-ca}MNHtM?T0OCrk` zR;AXA)*owTwKsSE-Ftf=b>#fSh3Rh$zcD>)dBAp+Bg?hc{gC$?zqLWvLmq}-idqm; z92*+9FdpjEW3*3MmQP z<)`yXa=+k`^ABAMYNX)VudC>`ZOzY5JtztVUJ&q)c9HT)1EDo;RQ} z%3iD}l3kTvp*7UIlqF?OB~o(g1bs}JDdQ-bl^WGR?jm)$=4$bgvMZHpZB%_m(~>r` z&hnlY`iH}5>_}J|HyL{%hKkaL=Y?DgO7PG0_V(E7ddo4( zHs7+uw8XGzS~F2H(l9X5%j(+F{<@jlkW=$j)raMul-wyaE%+(#)0|z|zbOLbWl|+Q zLT#a(DI+SDN~c!PbEG?EY2uk#5_Uy-J?y23`gwXUJ@U~8mqw7ayQ9?Bfc zoXj$KWt?gL*!o-h9nNfKmFG6!@&Nzf#ISjh4(ul!U*78Yb$pianaEt?LyQoqWZUeU zRZi|Al|-E+Q@m32Qc$0ui95%&;d~T*HsY($6|5=$Gd_BcIc^7?ezbdP^^e&rqpX>i zZ?Z-egW5jBZb`@GmYIf&wUTP53a3({=w<;czb&UW+gf=*?j@U{{pjPA-|PtqpiWXj zw6Qc$c1-T7?8qL-;pYERu%sxclvS~;`cAD=1OJA+s7eb3+r0w_Yr%ey*S@-wQ*euQo%+MC2o=A5_`zX*^}N%ULf@ZlVC`G z7kLVo@fXKC@X|R9HYbu7W*aOGc+Xev>CRl~e9Zoab-MX|>rSH-|Q=HlF!-d*LO;GuCjWk^TOmQ&# z^PJ!ETnhdyJY2H3{CZVQ&4PxK=ELoxF8jXGfzFZkiH_+`!%ov?%Ocx99gn!idZ@h@ z`9BD%4e1Uqj5-rz$xV*?A|Y8|D%vl8D7j56CzZ2zp`N@?dXP(rM9GZku<&2LGX4gS z7yA>tDzYssC-`)rkKcD*dM4Lplfz+~uPstdE;7zfogcqEoTjIG^g2P?ttR*SEbW(- zpO&2{&en+4t+_H)r7}eERQ84RC{0uIDKpB7T1u5rH|U$vD%nbfP4=`ZIPa9&xv;cY zT2@`@QS)WJVe`GVjh!*QP6I|G)8jK!3`0ZHDT^MPLWeY$WcO*W6Mk)h!68I=WK=i% zNNhFFFkz7YNEjmCCfQCzkx$6s*|YwNB*~M+Imr@nl`vEwN$`x5#|mP;jQTeGL`Y&# zyWdvtYQpVx7l z3R+L;mi4gp-oviru2W2gyNRoXg-yS`!ud2a%Il?XP~eW>)UeZ$%h+9<4|re4pXDzW zmWzTUJi?T`Np{WNg&Oih@-IRuxh>|3E(x9{{1CT{TNL9TJuiYEYRY=-&-3}gL+9%2 zG|z6O)dsVTM(bzRz1c9jeekQkM_ujh;+8)eh}waw{BljnOyP!tzPx8Se`dET-j~lv zJ7_z4Kjk@lLPDuaR6Ok^og+(=6G|5qEBBN9mV(rxk4tw}+^+7c-O@PG@~C4^_u~G9 zq3E%w$!LQZW43vawXJ=J(;sf@JbQdL1^mg+BYJ`i5ze;fZ1Z-TRl{axhGVaI|K0@c3rylykK&ZZ82Hc=KF6E-7iDr%fJoT%U0 zb4%CUn$(n2w^JKe$ts&ud|qR$ev*4#m7z3JT$62-Zm0jE#FWYGyS18XqJE>FNylY} z75wbzoOOBss#g@cm6(?YRc)&&s9)JU(e|SA>)u@hn?}}6Bu{TJ+-SPSa-nUwhTGm<|Sa?;<%d^a3W^Yt9%I-*ipev~5l=bXcpGW;meL?S)K9cz;3Y0HYmATRC zmzq7rYsz+1KG2%fpJ@tg)9QZhIj=uByl;Hp)PBYRlf4#OY~FG3cF{7w^0M&T8~7r) zG)xtFi5(n!o|hH>l)qOvDk3HC5U%81vVZnBOGUm<-XZ>w92MJ%whB%rY>#7dzl~{# znhNg^$qd@y-{hU-an)7n*kLE;oZQe~^RVhv`Na~d zFt8vq?_|!Y?AHpeyjfaAPg9>zuCs3zoBDxTK=Y&!E{nv&rjGdpnXpm}r!Th-O$M$oajhPvqgl|T`Br7n?FH#?Ujbp?k z3L%(3L0y2C& zJ$Ja>cgnSEu+o`z8R=%)-{?jM1}*y8-P=2|T0$G2)&8$Kp(3tyL(z+Zx%o3WgV}z{ zv+{76r8Ja2Lj}&BbYJQyWtk-P7!W39(#Rs{v_+O&;t=GX8%Pw#FfUCCOj6rD|#ZX zl+eVdWZCSU-a%d=Cx{@zPV%paE!@iA93RP}IR5PSB3Fm;f^`8$e0x3LW}bBZ-Cki` zWL{!iY*08^I955-)o;@?PnX&{+<2(YPupG5P&!let|mYK%iOK1)5>~ziY!sOlD<#z zX755L>LY4^qG^rPL!PGiU-rhFD|w^p%Y|!7mX?23^}NQn;n(J6?RH(wy*UG~MqW;2 zO}{dHW%}Imp6yA;<*sHP_q+rBj|XLj}&Q!q(yjd@b7_%e*bxSxo>p&#^G0+7Z$Hf{$XTJ zy&Qi%T%hmi@$TH*CU2Ts-=Mu)nOb(YxJ|Q0J(H_d^(ci3T6S7`mR3;sxKQZ950+IW|Wv{EGV11 z4*Oj=?xkL9{r(GNgm{KqMXA{vWB=oI#@F({7rKepN;VQK@^^A@_LER#BKZw*U9v&k zC6ow0NQjGTh+Q4?bJWZ52O&Fy?EEiz54ek5cRGG!`;+B8(|d;L(`gg;N1hI-d&fJ4 z?bn;F8q#Z$t0d(MOTH@9sZZvu$XS#9y~0thlD?vQsZEr_>|Gd7{YtH)7fX-GI^>s? zdsOFhtMijJ_Qm?r=?bnkwa&8XUh8UIV2`1`Z>VFeeNt!8W!zz2V=c44>AaR{So`p67pm66;UjCB%Uw2D^MjokK4iR zii`!zS-FBy}E}DH~baCeVn~S421|Rp8cR6-!Z7FZqQtMXT zSFSJdDLPzWnXk^t&K_56k=w`yX*c>1$?0CD|x8GvOU~Kvg< zI@s31whp#+u&sk_9c=4hTL;@Z*w(?e4z_i$t%GeHZ0jKB1UVi774NF)X4gKiyk>!4c)-8$&jLAMUNbCy*1!3FHKF0y&vOnNl{? z`;?YSqaR3hvX2#k*{(Tr^B^aX6UYhV1abm7ft)~2ASaL$$O+^GasoMloIp+>CyCy*1!3FHKF0y%-4Ku#bhkQ2xW zrywVg6UYhV1abm7 zft)~2ASaL$$O+^GasoMloIp+>Cyj1Z}08#zsulec*rTD>{f*sjy3MVS z6UYhV1abm7ft)~2ASaL$$O+^GasoMloIp+>Cy*1!i5X=@Eu~7R8}vCy*1!3FHKF0y%-4Ku#bhah0sH zImPES#_A`z*HsxxBgHk@M(K7MasoMloIp+>Cy*1!3FHKF0y%-4Ku#bhkQ2xWCy*1!3FHKF0y%-4 zKu#bhkQ2y>gYd3sLhMBJ5a-Aia)zuTQ%DWbDS@0oP9P_c6UYhV1abm7ft)~2ASaL$ z$O+^GasoMloIp+#)EDOlZ9iILtXtgJ@a&_t;3Cw6UYhV1abm7ft)~2ASaL$ z$O+^GasoMloIp+>Cy*1!i3Rlzl}}xyFG_P{Zz;xAaR{Cy*1!3FHKF0y%-4Ku#bhkQ2zsRZ*ANjA$iJ zl8xjHSxX)wD~SCy*1!3FKsPyaO+t!(ekFd11D}(t!7T<(}@$mCle8$O+^GasoMloIp+>Cy*1! z3FHKF0y%-4Ku#bhkQ2zsiQ;UHSlyZ{Q&lQM6i;PeNRQGqHJ^fCma(tBWfyYoHv}P-`aCa*W8-alvB4; z3ps(DKu#bhkQ2xWtHeL?S)K9cz;3Y0HYmATRC zmzq7rYsw%ekQ2xW