I'm working on joining a webrtc call from python using aiortc. I have some example code for how to do this with a video, but I would like to create an audio-only connection, where I can send and receive audio.

Also, not in the example, but I have an SDP offer from another client, which looks like this:

v=0
o=root 769564264 769564264 IN IP4 172.18.159.184
s=Twilio Media Gateway
c=IN IP4 34.203.250.171
t=0 0
a=group:BUNDLE audio
a=ice-lite
m=audio 16534 RTP/SAVPF 107 0 101
a=rtpmap:0 PCMU/8000
a=rtpmap:107 opus/48000/2
a=fmtp:107 useinbandfec=1
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-16
a=ptime:20
a=maxptime:20
a=ice-ufrag:154a85290c61d78e684637130e453bf7
a=ice-pwd:404fd5403a5b0c4536ff83f347665171
a=candidate:H22cbfaab 1 UDP 2130706431 34.203.250.171 16534 typ host
a=candidate:H22cbfaab 2 UDP 2130706430 34.203.250.171 16535 typ host
a=end-of-candidates
a=connection:new
a=setup:actpass
a=fingerprint:sha-256 4B:6D:8D:DA:BE:EF:F0:C3:77:B3:99:40:D2:B0:A4:7B:00:C4:84:6E:A0:F8:64:54:4B:79:CA:A5:8D:F3:35:BB
a=mid:audio
a=msid:25fbec1b-0f35-4a1c-9e96-106b996fec19 a4396926-2bc0-4da4-b826-37552e81b07b
a=ssrc:281098860 msid:25fbec1b-0f35-4a1c-9e96-106b996fec19 a4396926-2bc0-4da4-b826-37552e81b07b
a=ssrc:281098860 mslabel:25fbec1b-0f35-4a1c-9e96-106b996fec19
a=ssrc:281098860 label:a4396926-2bc0-4da4-b826-37552e81b07b
a=rtcp-mux
a=ssrc:281098860 cname:c753bf95-8f31-4cf0-a712-552987dbc47c
a=sendrecv

python code example:

import argparse
import asyncio
import json
import logging
import os
import platform
import ssl

from aiohttp import web

from aiortc import RTCPeerConnection, RTCSessionDescription
from aiortc.contrib.media import MediaPlayer, MediaRelay
from aiortc.rtcrtpsender import RTCRtpSender

ROOT = os.path.dirname(\_\_file\_\_)
relay = None
webcam = None
def create\_local\_tracks(play\_from, decode):
 global relay, webcam

 if play\_from:
 player = MediaPlayer(play\_from, decode=decode)
 return player.audio, player.video
 else:
 options = {"framerate": "30", "video\_size": "640x480"}
 if relay is None:
 if platform.system() == "Darwin":
 webcam = MediaPlayer(
 "default:none", format="avfoundation", options=options
 )
 elif platform.system() == "Windows":
 webcam = MediaPlayer(
 "video=Integrated Camera", format="dshow", options=options
 )
 else:
 webcam = MediaPlayer("/dev/video0", format="v4l2", options=options)
 relay = MediaRelay()
 return None, relay.subscribe(webcam.video)
def force\_codec(pc, sender, forced\_codec):
 kind = forced\_codec.split("/")[0]
 codecs = RTCRtpSender.getCapabilities(kind).codecs
 transceiver = next(t for t in pc.getTransceivers() if t.sender == sender)
 transceiver.setCodecPreferences(
 [codec for codec in codecs if codec.mimeType == forced\_codec]
 )
async def index(request):
 content = open(os.path.join(ROOT, "index.html"), "r").read()
 return web.Response(content\_type="text/html", text=content)
async def javascript(request):
 content = open(os.path.join(ROOT, "client.js"), "r").read()
 return web.Response(content\_type="application/javascript", text=content)
async def offer(request):
 params = await request.json()
 offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"])

 pc = RTCPeerConnection()
 pcs.add(pc)

 @pc.on("connectionstatechange")
 async def on\_connectionstatechange():
 print("Connection state is %s" % pc.connectionState)
 if pc.connectionState == "failed":
 await pc.close()
 pcs.discard(pc)

 # open media source
 audio, video = create\_local\_tracks(
 args.play\_from, decode=not args.play\_without\_decoding
 )

 if audio:
 audio\_sender = pc.addTrack(audio)
 if args.audio\_codec:
 force\_codec(pc, audio\_sender, args.audio\_codec)
 elif args.play\_without\_decoding:
 raise Exception("You must specify the audio codec using --audio-codec")

 if video:
 video\_sender = pc.addTrack(video)
 if args.video\_codec:
 force\_codec(pc, video\_sender, args.video\_codec)
 elif args.play\_without\_decoding:
 raise Exception("You must specify the video codec using --video-codec")

 await pc.setRemoteDescription(offer)

 answer = await pc.createAnswer()
 await pc.setLocalDescription(answer)

 return web.Response(
 content\_type="application/json",
 text=json.dumps(
 {"sdp": pc.localDescription.sdp, "type": pc.localDescription.type}
 ),
 )
pcs = set()
async def on\_shutdown(app):
 # close peer connections
 coros = [pc.close() for pc in pcs]
 await asyncio.gather(\*coros)
 pcs.clear()
if \_\_name\_\_ == "\_\_main\_\_":
 parser = argparse.ArgumentParser(description="WebRTC webcam demo")
 parser.add\_argument("--cert-file", help="SSL certificate file (for HTTPS)")
 parser.add\_argument("--key-file", help="SSL key file (for HTTPS)")
 parser.add\_argument("--play-from", help="Read the media from a file and sent it."),
 parser.add\_argument(
 "--play-without-decoding",
 help=(
 "Read the media without decoding it (experimental). "
 "For now it only works with an MPEGTS container with only H.264 video."
 ),
 action="store\_true",
 )
 parser.add\_argument(
 "--host", default="0.0.0.0", help="Host for HTTP server (default: 0.0.0.0)"
 )
 parser.add\_argument(
 "--port", type=int, default=8080, help="Port for HTTP server (default: 8080)"
 )
 parser.add\_argument("--verbose", "-v", action="count")
 parser.add\_argument(
 "--audio-codec", help="Force a specific audio codec (e.g. audio/opus)"
 )
 parser.add\_argument(
 "--video-codec", help="Force a specific video codec (e.g. video/H264)"
 )

 args = parser.parse\_args()

 if args.verbose:
 logging.basicConfig(level=logging.DEBUG)
 else:
 logging.basicConfig(level=logging.INFO)

 if args.cert\_file:
 ssl\_context = ssl.SSLContext()
 ssl\_context.load\_cert\_chain(args.cert\_file, args.key\_file)
 else:
 ssl\_context = None

 app = web.Application()
 app.on\_shutdown.append(on\_shutdown)
 app.router.add\_get("/", index)
 app.router.add\_get("/client.js", javascript)
 app.router.add\_post("/offer", offer)
 web.run\_app(app, host=args.host, port=args.port, ssl\_context=ssl\_context)
aiortc를 사용하여 파이썬에서 webrtc 호출에 참여하는 작업을 하고 있습니다. 비디오로이 작업을 수행하는 방법에 대한 몇 가지 예제 코드가 있지만 오디오를 보내고받을 수있는 오디오 전용 연결을 만들고 싶습니다.또한 예제에는 없지만 다른 클라이언트의 SDP 오퍼가 있는데 다음과 같습니다:v=0o=루트 769564264 769564264 IN IP4 172.18.159.184s=트윌리오 미디어 게이트웨이c=IN IP4 34.203.250.171t=0 0a=group:BUNDLE 오디오a=ICE-LITEm=audio 16534 RTP/SAVPF 107 0 101a=rtpmap:0 PCMU/8000A=RTP맵:107 OPUS/48000/2A=FMTP:107 USEINBANDFEC=1A=RPMAP:101 전화-이벤트/8000A=FMTP:101 0-16a=ptime:20a=최대시간:20a=ice-ufrag:154a85290c61d78e684637130e453bf7a=ice-pwd:404fd5403a5b0c4536ff83f347665171a=후보:H22cbfaab 1 UDP 2130706431 34.203.250.171 16534 typ hosta=candidate:H22cbfaab 2 UDP 2130706430 34.203.250.171 16535 typ hosta=후보 끝a=연결:신규a=설정:액트패스a=fingerprint:sha-256 4B:6D:8D:DA:BE:EF:F0:C3:77:B3:99:40:D2:B0:A4:7B:00:C4:84:6E:A0:F8:64:54:4B:79:CA:A5:8D:F3:35:BBa=mid:오디오a=msid:25fbec1b-0f35-4a1c-9e96-106b996fec19 a4396926-2bc0-4da4-b826-37552e81b07ba=ssrc:281098860 msid:25fbec1b-0f35-4a1c-9e96-106b996fec19 a4396926-2bc0-4da4-b826-37552e81b07ba=ssrc:281098860 mslabel:25fbec1b-0f35-4a1c-9e96-106b996fec19a=ssrc:281098860 label:a4396926-2bc0-4da4-b826-37552e81b07ba=rtcp-muxa=ssrc:281098860 cname:c753bf95-8f31-4cf0-a712-552987dbc47ca=sendrecv파이썬 코드 예제:import argparseasyncio 임포트import jsonimport loggingimport os플랫폼 가져오기import sslaiohttp에서 웹 가져오기aiortc에서 RTCPeerConnection, RTCSessionDescription을 가져옵니다.aiortc.contrib.media에서 MediaPlayer, MediaRelay를 가져옵니다.aiortc.rtcrtpsender에서 RTCRtpSender를 가져옵니다.ROOT = os.path.dirname(\_\_file\_\_)릴레이 = 없음웹캠 = 없음def create\_local\_tracks(play\_from, decode): 글로벌 릴레이, 웹캠 if play\_from: player = MediaPlayer(play\_from, decode=디코드) return player.audio, player.video else: options = {"프레임 속도": "30", "video\_size": "640x480"} 릴레이가 None인 경우: platform.system() == "다윈": 웹캠 = MediaPlayer( "기본값:없음", 형식="avfoundation", 옵션=옵션 ) platform.system() == "Windows": 웹캠 = MediaPlayer( "비디오=통합 카메라", 형식="dshow", 옵션=옵션) ) else: 웹캠 = MediaPlayer("/dev/video0", format="v4l2", options=옵션) 릴레이 = MediaRelay() 반환 없음, 릴레이.구독(웹캠.비디오)def force\_codec(pc, 발신자, forced\_codec): kind = forced\_codec.split("/")[0] 코덱 = RTCRtpSender.getCapabilities(kind).codecs 트랜시버 = next(for t in pc.getTransceivers() if t.sender == sender) 트랜시버.setCodecPreferences( [코덱의 코덱에 대한 코덱 codec.mimeType == forced\_codec]) )비동기 def index(request): content = open(os.path.join(ROOT, "index.html"), "r").read() return web.Response(content\_type="text/html", text=content)비동기 정의 자바스크립트(요청): content = open(os.path.join(ROOT, "client.js"), "r").read() return web.Response(content\_type="application/javascript", text=content)비동기 def offer(request): params = await request.json() offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"]) pc = RTCPeerConnection() pcs.add(pc) pc.on("연결 상태 변경") 비동기 def on\_connectionstatechange(): print("연결 상태는 %s입니다" % pc.connectionState) if pc.connectionState == "실패": await pc.close() pcs.discard(pc) # 미디어 소스 열기 오디오, 비디오 = create\_local\_tracks( args.play\_from, decode=not args.play\_without\_decoding ) 만약 오디오: audio\_sender = pc.addTrack(audio) if args.audio\_codec: force\_codec(pc, audio\_sender, args.audio\_codec) elif args.play\_without\_decoding: raise Exception("--audio-codec을 사용하여 오디오 코덱을 지정해야 합니다") if video: video\_sender = pc.addTrack(video) if args.video\_codec: force\_codec(pc, video\_sender, args.video\_codec) elif args.play\_without\_decoding: raise Exception("--video-codec을 사용하여 비디오 코덱을 지정해야 합니다") await pc.setRemoteDescription(offer) answer = await pc.createAnswer() await pc.setLocalDescription(answer) 반환 웹 응답( content\_type="application/json", text=json.dumps( {"sdp": pc.localDescription.sdp, "type": pc.localDescription.type} ), )pcs = set()비동기 def on\_shutdown(app): # 피어 연결 닫기 coros = [pc.close() for pc in pcs] await asyncio.gather(\*coros) pcs.clear()if \_\_name\_\_ == "\_\_main\_\_": parser = argparse.ArgumentParser(description="WebRTC 웹캠 데모") parser.add\_argument("--cert-file", help="SSL 인증서 파일(HTTPS용)") parser.add\_argument("--key-file", help="SSL 키 파일(HTTPS용)") parser.add\_argument("--play-from", help="파일에서 미디어를 읽고 전송합니다."), parser.add\_argument( "--play-without-decoding", help=( "디코딩하지 않고 미디어를 읽습니다(실험적). " "현재로서는 H.264 비디오만 있는 MPEGTS 컨테이너에서만 작동합니다." ), action="store\_true", ) parser.add\_argument( "--host", d