hundreds-dawn-90573
07/16/2025, 6:03 AMsdp_offer and we can create a sdp answer and send them a request to accept the call.
I just wanted to know if it will be possible to create webrtc connection that can accept the sdp_offer from WhatsApp and stream audio back n forth b/w caller and agent (Livekit agent)
I don’t have much idea about webrtc, so just asking if anyone else has done something like this previously.
Thanks.refined-appointment-81829
07/17/2025, 4:45 PMhundreds-dawn-90573
07/17/2025, 5:17 PMrefined-appointment-81829
07/17/2025, 5:19 PMhundreds-dawn-90573
07/17/2025, 5:19 PMrefined-appointment-81829
07/18/2025, 3:42 PMhundreds-dawn-90573
07/18/2025, 3:59 PMasync def stream(self) -> AsyncIterable[tuple[rtc.AudioFrame, float]]:
if self._track is None:
raise ValueError("track is not set")
while True:
av_frame: av.AudioFrame = await self._track.recv()
frame = av_frame.to_ndarray() # this produces interleaved channels audio so [l1, r1, l2, r2, ...]
<http://logger.info|logger.info>("av frame data: channels: %s", av_frame.layout.nb_channels)
<http://logger.info|logger.info>("frame: %s", frame)
<http://logger.info|logger.info>("frame data: len(frame) %s, len(frame[0]): %s", len(frame), len(frame[0]))
# scale frame values to int16 and do a type casting
frame = (frame * self.SCALE_FACTOR).astype(np.int16)
frame = frame.flatten()
duration = len(frame) / self._media_info.audio_sample_rate # 48kHz
samples_per_channel = int(len(frame) / av_frame.layout.nb_channels)
rtc_frame = rtc.AudioFrame(
data=frame.tobytes(),
sample_rate=self._media_info.audio_sample_rate,
num_channels=av_frame.layout.nb_channels,
samples_per_channel=samples_per_channel,
)
<http://logger.info|logger.info>("rtc_frame: %s", rtc_frame)
<http://logger.info|logger.info>("sample rate: %s", rtc_frame.sample_rate)
<http://logger.info|logger.info>("rtc frame channels: %s", rtc_frame.num_channels)
<http://logger.info|logger.info>("rtc frame samples per channel: %s", rtc_frame.samples_per_channel)
yield (
rtc_frame,
duration,
)
this stream is then consumed by an audio source.
async def _push_audio_frames(self, stream: AsyncIterable[tuple[rtc.AudioFrame, float]]):
assert self.audio_source is not None
async for frame, timestamp in stream:
<http://logger.info|logger.info>("livekit capturng frame: %s", frame)
await self.audio_source.capture_frame(frame)
await asyncio.sleep(0)
def start_streaming(self):
asyncio.create_task(self._push_audio_frames(self.streamer.stream()))
None of this produces any error but agent also does not gets anything.
this the last log that agent worker shows
{
"participant": "whatsapp_917889685075",
"source": "SOURCE_MICROPHONE",
"pid": 32073,
"job_id": "AJ_LCoWm4qTGzEx"
}
I am though getting audio from whatsapp and am saving that in .wav file as well.hundreds-dawn-90573
07/18/2025, 4:08 PMloud-dawn-97520
07/18/2025, 5:47 PMhundreds-dawn-90573
07/18/2025, 6:20 PMstereo (2 channels) but agent worker is receiving only mono single channel.hundreds-dawn-90573
07/18/2025, 6:24 PMhundreds-dawn-90573
07/18/2025, 6:25 PM<http://av_frame.to|av_frame.to>_ndarray()?hundreds-dawn-90573
07/18/2025, 8:21 PMasync def entrypoint(ctx: JobContext):
logger.debug("Entrypint called with context: %s", ctx)
# Add room event debugging
@ctx.room.on("track_published")
def on_track_published(publication, participant):
<http://logger.info|logger.info>(f"🎵 Track published: {publication.name} by {participant.identity}")
@ctx.room.on("track_subscribed")
def on_track_subscribed(track, publication, participant):
<http://logger.info|logger.info>(f"🎵 Track subscribed: {publication.name} by {participant.identity}, kind: {track.kind}")
if track.kind == rtc.TrackKind.KIND_AUDIO:
# Add audio track inspection
audio_track = track
<http://logger.info|logger.info>("Audio track details:")
<http://logger.info|logger.info>(f" - Track SID: {audio_track.sid}")
<http://logger.info|logger.info>(f" - Source: {publication.source}")
# Check if the track is actually receiving data
asyncio.create_task(monitor_audio_track(audio_track))
...
async def monitor_audio_track(audio_track):
"""Monitor if audio track is receiving data"""
frame_count = 0
try:
async for frame in rtc.AudioStream(audio_track):
frame_count += 1
if frame_count % 50 == 0: # Log every 50 frames
<http://logger.info|logger.info>(
f"Received audio frame {frame_count}: {len(frame.frame.data)} bytes, sample_rate: {frame.frame.sample_rate}"
)
<http://logger.info|logger.info>(
f"Frame format - Sample rate: {frame.frame.sample_rate}, Channels: {frame.frame.num_channels}, Samples per channel: {frame.frame.samples_per_channel}"
)
audio_data = np.frombuffer(frame.frame.data.tobytes(), dtype=np.int16)
<http://logger.info|logger.info>(f"Audio data shape: {len(audio_data)} samples, first 10 values: {audio_data[:10]}")
except Exception as e:
logger.error(f"Error monitoring audio track: {e}")
AND this is how I am doing conversions.
async def stream(self) -> AsyncIterable[tuple[rtc.AudioFrame, float]]:
if self._track is None:
raise ValueError("track is not set")
while True:
av_frame: av.AudioFrame = await self._track.recv()
# <http://logger.info|logger.info>("av frame data: channels: %s", av_frame.layout.nb_channels)
# <http://logger.info|logger.info>("frame: %s", frame)
# <http://logger.info|logger.info>("frame data: len(frame) %s, len(frame[0]): %s", len(frame), len(frame[0]))
# scale frame values to int16 and do a type casting
resampled_frames = self._resampler.resample(av_frame)
for mono_frame in resampled_frames:
self._webrtc_frame_count += 1
if self._webrtc_frame_count % 50 == 0:
<http://logger.info|logger.info>(
f"[WEBRTC->BRIDGE] Frame {self._webrtc_frame_count}: {len(mono_frame.to_ndarray().flatten())}, stereo: {len(av_frame.to_ndarray().flatten())}, samples, sample_rate: {mono_frame.sample_rate}, channels: {mono_frame.layout.nb_channels}"
)
<http://logger.info|logger.info>("mono-av frame data: %s", mono_frame.to_ndarray().flatten()[:10])
mono_frame = mono_frame.to_ndarray()
mono_frame = (mono_frame * self.SCALE_FACTOR).astype(np.int16)
mono_frame = mono_frame.flatten()
duration = len(mono_frame) / self._media_info.audio_sample_rate # 48kHz
samples_per_channel = int(len(mono_frame))
rtc_frame = rtc.AudioFrame(
data=mono_frame.tobytes(),
sample_rate=self._media_info.audio_sample_rate,
num_channels=1,
samples_per_channel=samples_per_channel,
)
# <http://logger.info|logger.info>("rtc_frame: %s", rtc_frame)
# <http://logger.info|logger.info>("sample rate: %s", rtc_frame.sample_rate)
# <http://logger.info|logger.info>("rtc frame channels: %s", rtc_frame.num_channels)
# <http://logger.info|logger.info>("rtc frame samples per channel: %s", rtc_frame.samples_per_channel)
yield (
rtc_frame,
duration,
)
kinda stuck on this one.most-vr-18651
07/28/2025, 7:23 AM