Dieser Artikel ist eine Fortsetzung des vorherigen Discord Bot mit Aufnahmefunktion beginnend mit Python: (4) Abspielen von Musikdateien.
Versuchen Sie in diesem Artikel als vorbereitenden Schritt zum Starten der Implementierung der Sprachaufzeichnung, die Sprachkommunikations-API mithilfe der Discord-API zu verwenden, um zu verstehen, wie die Kommunikation ausgeführt wird.
Wir planen insgesamt 7 Artikel zu schreiben und haben bis zu 5 Artikel fertig geschrieben.
Wie Sie sehen können, indem Sie "Anfänger" aus dem Tag entfernen, ist der folgende Vorgang etwas mühsam und betrifft hauptsächlich den Teil der unteren Ebene.
Ein schematisches Diagramm der Kommunikation beim Senden und Empfangen von Sprache mit Discord ist unten dargestellt.
Da es sich um ein schematisches Diagramm handelt, wird eine ausführliche Erklärung weggelassen, aber ich denke, es wäre gut, wenn Sie verstehen könnten, dass die Verbindung über verschiedene Prozesse hergestellt wird.
Dieser Ablauf wird ohne Verwendung von discord.py implementiert, und das Senden und Empfangen von Discord-Audio wird detailliert behandelt.
Alle nachfolgenden Informationen sind offizielle Referenzen (Voice Connection Gateway, Normal Gateway. Es wird basierend auf docs / topic / gateway # gateways)) beschrieben.
Discord Gateway verfügt über ein Gateway, das Informationen über normale Gateway-Sprache sendet und empfängt. Um die Endpunkt-URL für die Verbindung mit dem Voice-Gateway zu erhalten, authentifizieren Sie sich zuerst beim normalen Gateway und stellen Sie dann eine Verbindung zum Voice-Gateway her. Informationen werden gesendet.
Erstellen Sie zunächst ein Skript, um die WebSocket-Verbindung in Python zu testen.
op10 Hello
import json
import asyncio
import aiohttp
from pprint import pprint
class Gateway:
def __init__(self, loop=None):
if loop is None:
loop = asyncio.get_event_loop()
self.endpoint = 'wss://gateway.discord.gg/?v=6&encoding=json'
loop.create_task(self.receive_data())
async def receive_data(self):
async with aiohttp.ClientSession() as session:
socket = await session.ws_connect(self.endpoint)
while True:
packet = await socket.receive()
if packet.type in (aiohttp.WSMsgType.CLOSED,
aiohttp.WSMsgType.CLOSING,
aiohttp.WSMsgType.CLOSE,
aiohttp.WSMsgType.ERROR):
print(packet)
print('==Verbindungsende==')
break
pprint(json.loads(packet.data))
if __name__ == "__main__":
loop = asyncio.get_event_loop()
ws = Gateway(loop)
loop.run_forever()
Wenn dies ausgeführt wird, wird die Co-Routine "receive_data" zum Herstellen einer Verbindung mit dem Gateway und zum Anzeigen der vom Gateway empfangenen Daten nacheinander von der Funktion "create_task" im Gateway aufgerufen und beginnt mit der Verarbeitung. Wenn dies ausgeführt wird, werden die folgenden Daten vom Discord Gateway gesendet.
{'d': {'_trace': ['["gateway-prd-main-xwmj",{"micros":0.0}]'],
'heartbeat_interval': 41250},
'op': 10,
's': None,
't': None}
Die von Discord gesendeten Daten verwenden "d" und "op" und selten "t", um die Daten darzustellen. op
speichert den Datentyp und d
speichert den Dateninformationskörper. t
ist im Grunde None
, aber wenn Sie detailliertere Informationen übermitteln müssen, ist es eine Zeichenfolge, die die Details dieser Informationen übermittelt.
op1 Heartbeat
Hier ist "op = 10". Dies ist eine Antwort namens "Hallo". Dies sind die Daten, die gesendet werden, wenn Sie zum ersten Mal eine Verbindung herstellen, wie der Name schon sagt. Die wichtigen Daten in Hello sind heartbeat_interval
. Hier ist es "41250", das einfache Daten mit dem Namen "Heartbeat" senden muss, um Ihnen mitzuteilen, dass das Gateway noch jede angegebene Millisekunde (41,25 Sekunden) verbunden ist. Es gibt. Erstellen Sie eine Klasse, die "threading.Thread" als Hilfsklasse erbt, die diese Heartbeat-Verarbeitung ausführt. Durch Schreiben des gewünschten Prozesses in die Funktion "Ausführen" und Aufrufen der Startfunktion von der Instanz aus wird der Prozess in einem anderen Thread ausgeführt.
import json
import asyncio
import aiohttp
import threading
from pprint import pprint
class HeartbeatHandler(threading.Thread):
def __init__(self, ws, interval):
self.ws = ws
self.interval = interval
self.stop_ev = threading.Event()
super().__init__()
def run(self):
self.send()
while not self.stop_ev.wait(self.interval):
self.send()
def send(self):
data = self.get_payload()
asyncio.run_coroutine_threadsafe(
self.ws.socket.send_json(data),
self.ws.loop
)
print('==Senden==')
print(data)
def stop(self):
self.stop_ev.set()
def get_payload(self):
raise NotImplementedError
class GatewayHeartbeat(HeartbeatHandler):
def __init__(self, ws, interval):
super().__init__(ws, interval)
def get_payload(self):
return {'op': 1, 'd': None}
class Gateway:
def __init__(self, loop=None):
if loop is None:
self.loop = asyncio.get_event_loop()
else:
self.loop = loop
self.endpoint = 'wss://gateway.discord.gg/?v=6&encoding=json'
self.loop.create_task(self.receive_data())
async def receive_data(self):
async with aiohttp.ClientSession() as session:
self.socket = await session.ws_connect(self.endpoint)
while True:
packet = await self.socket.receive()
if packet.type in (aiohttp.WSMsgType.CLOSED,
aiohttp.WSMsgType.CLOSING,
aiohttp.WSMsgType.CLOSE,
aiohttp.WSMsgType.ERROR):
print(packet)
print('==Verbindungsende==')
break
print('==Erhalten==')
pprint(json.loads(packet.data))
await self.handle_message(json.loads(packet.data))
if hasattr(self, 'heartbeat'):
self.heartbeat.stop()
async def handle_message(self, msg):
op = msg.get('op')
d = msg.get('d')
t = msg.get('t')
if op == 10:
self.heartbeat = GatewayHeartbeat(
self, d['heartbeat_interval'] / 1000
)
self.heartbeat.start()
return
if __name__ == "__main__":
loop = asyncio.get_event_loop()
ws = Gateway(loop)
loop.run_forever()
Es heißt run_coroutine_threadsafe
** Wie auch immer! !! Da es eine ** Funktion gibt, verwenden Sie sie. Wenn dies ausgeführt wird, wird der Kommunikationszustand untereinander alle 40 Sekunden ausgegeben.
==Erhalten==
{'d': {'_trace': ['["gateway-prd-main-w7j9",{"micros":0.0}]'],
'heartbeat_interval': 41250},
'op': 10,
's': None,
't': None}
==Senden==
{'op': 1, 'd': None}
==Erhalten==
{'d': None, 'op': 11, 's': None, 't': None}
==Senden==
{'op': 1, 'd': None}
==Erhalten==
{'d': None, 'op': 11, 's': None, 't': None}
...
Andernfalls wird die Verbindung nach Ablauf von 40 Sekunden vom Discord Gateway getrennt. Wenn Sie jedoch nur Heartbeat ausführen, erhalten Sie möglicherweise eine Anfrage zur erneuten Verbindung von der Discord-Seite. Derzeit werden wir hier keine Verarbeitung wie die erneute Verbindung durchführen.
op2 Identify
Als Nächstes müssen Sie ein Bot-Token senden, um das Gateway über die Verbindungsinformationen zu informieren. Diese Informationen werden von op2 gesendet, aber zusätzlich zum Bot-Token werden einfache Eigenschaften zu den Eigenschaften in der Nutzlast hinzugefügt. Wenn Sie einen großen Bot betreiben und "Sharding" ausführen, ist zusätzliche Verarbeitung erforderlich. In diesem Fall wird die Verarbeitung jedoch unter der Annahme durchgeführt, dass ein kleiner Bot kein Sharding verwendet.
class Gateway:
def __init__(self, loop=None):
if loop is None:
self.loop = asyncio.get_event_loop()
else:
self.loop = loop
self.endpoint = 'wss://gateway.discord.gg/?v=6&encoding=json'
self.loop.create_task(self.receive_data())
self.identified = asyncio.Event()
async def receive_data(self):
async with aiohttp.ClientSession() as session:
self.socket = await session.ws_connect(self.endpoint)
while True:
packet = await self.socket.receive()
if packet.type in (aiohttp.WSMsgType.CLOSED,
aiohttp.WSMsgType.CLOSING,
aiohttp.WSMsgType.CLOSE,
aiohttp.WSMsgType.ERROR):
print('==Verbindungsende==')
print(packet)
break
print('==Erhalten==')
pprint(json.loads(packet.data))
await self.handle_message(json.loads(packet.data))
if hasattr(self, 'heartbeat'):
self.heartbeat.stop()
async def identify(self):
payload = {
'op': 2,
'd': {
'token': 'BOT_TOKEN',
'properties': {
'$os': 'linux',
'$browser': 'python',
'$device': 'python',
},
'v': 3
}
}
print('==Senden==')
print(payload)
await self.socket.send_json(payload)
self.identified.set()
async def handle_message(self, msg):
op = msg.get('op')
d = msg.get('d')
t = msg.get('t')
if op == 10:
self.heartbeat = GatewayHeartbeat(
self, d['heartbeat_interval'] / 1000
)
self.heartbeat.start()
await self.identify()
return
Die Authentifizierung erfolgt durch Senden des Bot-Tokens. Die Bot-Informationen und die Informationen des Servers, auf dem der Bot installiert ist, werden empfangen. Es ist leicht zu übersehen, da verschiedene Informationen gesendet werden. Wenn jedoch die Informationen "t = READY" mit "op = 0" gesendet werden, bedeutet dies, dass Sie bereit sind, über Gateway miteinander zu kommunizieren. Außerdem wird die "session_id" im "d" für die Sprachverbindung verwendet. Speichern Sie sie daher.
{'d': {
...
'session_id': 'f0d7bba081bc0df51e43c1eef8092adcb',
...
},
'op': 0,
's': 1,
't': 'READY'}
op4 Gateway Voice State Update
Um die Informationen für die Verbindung mit dem Voice-Gateway zu erhalten, muss die Verbindung mit "op = 4" an das normale Gateway gesendet werden.
Mit op = 4
werden die ID des Servers und des Audiokanals sowie der eigene Stummschaltungsstatus angegeben und an das Gateway gesendet. Dadurch erhalten Sie die Endpunkt-URL des vom Server verwendeten Voice-Gateways.
class Gateway:
...
async def voice_state_update(self):
payload = {
'op': 4,
'd': {
'guild_id': '705...',
'channel_id': '706...',
"self_mute": False, #Ob stumm geschaltet werden soll
"self_deaf": False, #Gibt an, ob der Lautsprecher stummgeschaltet werden soll
}
}
print('==Senden==')
print(payload)
await self.socket.send_json(payload)
async def handle_message(self, msg):
op = msg.get('op')
d = msg.get('d')
t = msg.get('t')
if op == 10:
self.heartbeat = GatewayHeartbeat(
self, d['heartbeat_interval'] / 1000
)
self.heartbeat.start()
await self.identify()
return
if op == 0:
if t == 'READY':
self.session_id = d['session_id']
await self.voice_state_update()
Wenn dies ausgeführt wird, wird der Bot mit dem Audiokanal verbunden und die folgenden zwei Daten werden empfangen.
==Erhalten==
{'d': {'channel_id': '705...',
'deaf': False,
'guild_id': '706...',
'member': ...,
'mute': False,
'self_deaf': False,
'self_mute': False,
'self_video': False,
'session_id': 'f0d7bba081bc0df51e43c1eef8092adcb',
'suppress': False,
'user_id': '743...'},
'op': 0,
's': 3,
't': 'VOICE_STATE_UPDATE'}
==Erhalten==
{'d': {'endpoint': 'japan396.discord.media:80',
'guild_id': '705...',
'token': '0123456789abcdef'},
'op': 0,
's': 4,
't': 'VOICE_SERVER_UPDATE'}
Der "Endpunkt" von "VOICE_SERVER_UPDATE" unten ist der Endpunkt des Sprachgateways, und "Token" wird als Authentifizierungstoken verwendet.
op3 Heartbeat
Ab hier beginnt die Kommunikation mit dem Voice Gateway.
Erstellen Sie eine neue Klasse für die WebSocket-Kommunikation mit dem zuvor erhaltenen Endpunkt.
class Gateway:
...
async def handle_message(self, msg):
op = msg.get('op')
d = msg.get('d')
t = msg.get('t')
if op == 10:
self.heartbeat = GatewayHeartbeat(
self, d['heartbeat_interval'] / 1000
)
self.heartbeat.start()
await self.identify()
return
if op == 0:
if t == 'READY':
self.session_id = d['session_id']
await self.voice_state_update()
if t == 'VOICE_SERVER_UPDATE':
self.voice_endpoint = d['endpoint']
self.token = d['token']
self.voice_gw = VoiceGateway(self, self.loop)
class VoiceGateway:
def __init__(self, gateway, loop=None):
self.gateway = gateway
if loop is None:
self.loop = asyncio.get_event_loop()
else:
self.loop = loop
self.endpoint = f'wss://{gateway.voice_endpoint.replace(":80", "")}/?v=4'
self.loop.create_task(self.receive_data())
self.identified = asyncio.Event()
async def receive_data(self):
async with aiohttp.ClientSession() as session:
self.socket = await session.ws_connect(self.endpoint)
while True:
packet = await self.socket.receive()
if packet.type in (aiohttp.WSMsgType.CLOSED,
aiohttp.WSMsgType.CLOSING,
aiohttp.WSMsgType.CLOSE,
aiohttp.WSMsgType.ERROR):
print('**Verbindungsende**')
print(packet)
break
print('**Erhalten**')
pprint(json.loads(packet.data))
await self.handle_message(json.loads(packet.data))
async def handle_message(self, msg):
pass
Wenn Sie dies tun und es funktioniert, werden das Heartbeat-Intervall sowie das erste Gateway zurückgegeben.
==Erhalten==
{'d': ...
'op': 0,
's': 5,
't': 'VOICE_SERVER_UPDATE'}
**Erhalten**
{'d': {'heartbeat_interval': 13750.25, 'v': 4}, 'op': 8}
Um die Verbindung aufrechtzuerhalten, senden Sie diesmal Heartbeat mit "op = 3". Als Daten wird ein Zeitstempel angegeben.
import json
import asyncio
import aiohttp
import threading
import time # <-hinzufügen
from pprint import pprint
class VoiceGatewayHeartbeat(HeartbeatHandler):
def __init__(self, ws, interval):
super().__init__(ws, interval)
def get_payload(self):
#Zeit in Millisekunden'd'Einstellen
return {'op': 3, 'd': time.time_ns()//1000}
class VoiceGateway:
...
async def handle_message(self, msg):
op = msg.get('op')
d = msg.get('d')
t = msg.get('t')
if op == 8:
self.heartbeat = VoiceGatewayHeartbeat(
self, d['heartbeat_interval'] / 1000
)
return
Da dieser Heartbeat nach der Bot-Authentifizierung ausgeführt werden muss, wird die Kommunikation nicht gestartet und bleibt unverändert.
op0 Identify
Verwenden Sie zur Authentifizierung beim Voice Gateway die Server-ID, die Bot-Benutzer-ID, die Sitzungs-ID und das Token als Nutzdaten und senden Sie sie mit op = 0.
class VoiceGateway:
...
async def identify(self):
payload = {
'op': 0,
'd': {
'token': self.gateway.token,
'user_id': '743853432007557210',
'server_id': '705052322761277540',
'session_id': self.gateway.session_id,
}
}
print('**Senden**')
print(payload)
await self.socket.send_json(payload)
async def handle_message(self, msg):
op = msg.get('op')
d = msg.get('d')
t = msg.get('t')
if op == 8:
await self.identify()
self.heartbeat = VoiceGatewayHeartbeat(
self, d['heartbeat_interval'] / 1000
)
self.heartbeat.start()
return
Bei erfolgreicher Authentifizierung erhalten Sie "op2 Ready". "Ip" und "Port" entsprechen der Adresse zum Abrufen von Sprachinformationen, "Modi" entsprechen der von Discord unterstützten Sprachverschlüsselungsmethode und "ssrc" entspricht der Kennung.
**Erhalten**
{'d': {'heartbeat_interval': 13750.25, 'v': 4}, 'op': 8}
**Senden**
{'op': 0, 'd': {'token': '871d40956f7cf34a', 'user_id': '743853432007557210', 'server_id': '705052322761277540', 'session_id': 'c412a670dbed864b559a25009459f15a'}}
==Senden==
{'op': 3, 'd': 1598314493140616}
**Erhalten**
{'d': {'experiments': ['bwe_conservative_link_estimate',
'bwe_remote_locus_client'],
'ip': '123.123.123.123',
'modes': ['aead_aes256_gcm',
'xsalsa20_poly1305_lite',
'xsalsa20_poly1305_suffix',
'xsalsa20_poly1305'],
'port': 50004,
'ssrc': 364117},
'op': 2}
**Erhalten**
{'d': 1598314493140616, 'op': 6}
==Senden==
{'op': 3, 'd': 1598314506891112}
**Erhalten**
{'d': 1598314506891112, 'op': 6}
Ich stelle eine UDP-Verbindung zu der in der vorherigen Kommunikation erhaltenen IP her und erhalte Sprachdaten, aber diese IP ist durch NAT verschleiert. -connections # ip-Discovery), daher müssen Sie die Adresse und den Port abrufen, die für die Öffentlichkeit zugänglich sind. Um es zu erhalten, senden Sie das folgende UDP-Paket an den Server von "ip", "port".
Feld | Erläuterung | Größe |
---|---|---|
Art | 0x1 | 2 Bytes |
Länge | 70 | 2 Bytes |
SSRC | Ganzzahl ohne Vorzeichen | 4 Bytes |
IP Adresse | ASCII-Code(Der Überschuss ist0x0 (Nullzeichen)Pack. 0 beim Senden) |
64 Bytes |
Hafen | Ganzzahl ohne Vorzeichen(0 beim Senden) | 2 Bytes |
Wenn dies gesendet wird, wird dasselbe 74-Byte-Paket gesendet, das Daten in der IP und im Port enthält, sodass die IP- und Portinformationen von diesem Paket erhalten werden.
import json
import asyncio
import aiohttp
import threading
import time
import socket # <-hinzufügen
import struct # <-hinzufügen
from pprint import pprint
class VoiceGateway:
...
async def ip_discovering(self):
self.udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.udp.setblocking(False)
packet = bytearray(74)
packet[:2] = struct.pack('>H', 1)
packet[2:4] = struct.pack('>H', 70)
packet[4:8] = struct.pack('>I', self.ssrc)
self.udp.sendto(bytes(packet), (self.ip, self.port))
data = await self.loop.sock_recv(self.udp, 2048)
self.external_ip, self.external_port = struct.unpack_from(
'>64sH', data, 8
)
self.external_ip = self.external_ip.decode(encoding='ascii').rstrip('\x00')
print(self.external_ip, self.external_port)
async def handle_message(self, msg):
op = msg.get('op')
d = msg.get('d')
t = msg.get('t')
if op == 8:
await self.identify()
self.heartbeat = VoiceGatewayHeartbeat(
self, d['heartbeat_interval'] / 1000
)
self.heartbeat.start()
return
if op == 2:
self.ip = d['ip']
self.port = d['port']
self.modes = d['modes']
self.ssrc = d['ssrc']
await self.ip_discovering()
Struct
(Standardpaket) wird zum Erstellen von UDP-Paketdaten verwendet. Wenn dies ausgeführt wird, wird ein UDP-Paket unter Verwendung der Ereignisschleife empfangen und die IP und der Port werden an die Konsole ausgegeben.
**Erhalten**
{'d': ...,
'op': 2}
201.158.201.158 54345
Der Grund für diesen langwierigen Prozess besteht darin, den Schlüssel zum Entschlüsseln der verschlüsselten Stimme zu erhalten. Durch Senden der externen IP und des in diesem Prozess erhaltenen Ports an das Voice Gateway können Sie den Schlüssel zur Entschlüsselung als Antwort erhalten. Was als libsodium bezeichnet wird, wird für die Sprachverschlüsselung verwendet. Im Fall von Python kann die Verschlüsselung und Entschlüsselung mit libsodium durch Hinzufügen des PyNaCl-Pakets durchgeführt werden.
op1 Select Protocol
Machen Sie es möglich, den Schlüssel für die Verwendung mit libsodium zu erhalten. Für "Modus" in der Nutzlast von op1 ist es notwendig, eine der Verschlüsselungsmethoden aus den zuvor in "op2" erhaltenen Modi auszuwählen, aber hier werden wir konsequent "xsalsa20_poly1305" verwenden. .. Wenn Sie "op1" senden, wird "op4 Session Description" als Antwort gesendet. In dieser Nutzlast befindet sich ein Schlüssel zur Entschlüsselung. Nehmen Sie ihn daher heraus.
class VoiceGateway:
...
async def select_protocol(self):
payload = {
'op': 1,
'd': {
'protocol': 'udp',
'data': {
'address': self.external_ip,
'port': self.external_port,
'mode': 'xsalsa20_poly1305'
}
}
}
print('**Senden**')
print(payload)
await self.socket.send_json(payload)
async def receive_audio_packet(self):
while True:
data = await self.loop.sock_recv(self.udp, 2048)
print('**Sprachempfang**')
print(data)
async def handle_message(self, msg):
op = msg.get('op')
d = msg.get('d')
t = msg.get('t')
if op == 8:
await self.identify()
self.heartbeat = VoiceGatewayHeartbeat(
self, d['heartbeat_interval'] / 1000
)
self.heartbeat.start()
return
if op == 2:
self.ip = d['ip']
self.port = d['port']
self.modes = d['modes']
self.ssrc = d['ssrc']
await self.ip_discovering()
await self.select_protocol()
if op == 4:
self.secret_key = d['secret_key']
self.loop.create_task(self.receive_audio_packet())
Nach dem Empfang von "op4" werden Sprachdaten an den UDP-Socket gesendet, sodass create_task ausgeführt wird, um die Aufgabe des Empfangs von Sprachdaten zu starten.
**Senden**
{'op': 1, 'd': {'protocol': 'udp', 'data': {'address': '106.73.199.128', 'port': 42057, 'mode': 'xsalsa20_poly1305'}}}
**Erhalten**
{'d': {'audio_codec': 'opus',
...
'mode': 'xsalsa20_poly1305',
'secret_key': [244,
157,
...
214],
'video_codec': None},
'op': 4}
**Sprachempfang**
b'\x81\xc9\x00\x07\x00\x07\xdd(\x9fI\xb9\xd6\x00G\xce\xa2\xa4\x85M[\xed\xd3\x0fu\x15\x89|\xa6W\x1e\xc3U\x06\xc8\xd5S\x8fJ\x08\xfcx\xff\xe9\x83k\xca\xa9\xec'
**Sprachempfang**
b'\x81\xc9\x00\x07\x00\x07\xdd(\x00\x9c^\x83\x90\xc5V\xafX\xff\x14\x97\xf5\xf1/\xad\x15\x89|\xa6W\x1e\xc3U\x06\xc8\xd5S\x8fJ\x08\xfcx\xff\xe9\x83k\xcb\xa9\x02'
**Sprachempfang**
b'\x81\xc9\x00\x07\x00\x07\xdd(j\x88B\\O\xd0\rs`\xc1_\x92\xc6\xe6\xe7=\x15\x89|\xa6W\x1e\xc3U\x06\xc8\xd5S\x8fJ\x08\xfcx\xff\xe9\x83k\xc8\xa9\xfd'
**Sprachempfang**
b'\x81\xc9\x00\x07\x00\x07\xdd(\x05\x02\xf56\x8a\x13\x9e\xc2\xb6\x8c,\xe6r5\x0e\n\x15\x89|\xa6W\x1e\xc3U\x06\xc8\xd5S\x8fJ\x08\xfcx\xff\xe9\x83k\xc9\xa9\x14'
Die Protokolle zum Senden und Empfangen von Discord-Audio sind RTP und RTCP. Jedes Paket, in dem Sprachdaten gespeichert sind, ist ein RTP-Paket, das jeweils 20 ms lang Sprachdaten sendet, und ein RTCP-Paket sendet zusätzliche Informationen zu diesen Sprachdaten.
Konzentrieren Sie sich zur Unterscheidung zwischen RTP und RTCP auf den Wert des zweiten Bytes des Pakets. Gemäß der Definition des Protokolls ist das zweite Byte von RTCP Bereich 200 bis 204, sodass es dort identifiziert werden kann.
Um die Länge des RTP-Headers zu berechnen, achten Sie auf X = "1. Byte 4. Bit" und CC = "1. Byte 5-8.". Ich werde nicht die Rolle jedes Bits erklären, aber
Wenn $ X = 0 $
Wenn $ X = 1 $
Es kann wie folgt berechnet werden. len (EX_header) ist ein Wert, der die zusätzliche Headerlänge angibt, die dem Wert von 2 Bytes aus dem Byte "14 + 4 × CC" entspricht.
Einzelheiten finden Sie in der Tabelle auf Wikipedia.
Dieses Mal gibt es kein Problem, wenn Sie nur "Zeitstempel" erhalten können, dh die Sprachübertragungszeit im RTP-Header, also [API-Referenz](https://discord.com/developers/docs/topics/voice-connections#encrypting-and -sending-voice-voice-paketstruktur) und extrahiere die 4.-8. Bytes.
Jetzt können Sie die Audiodaten vorerst abrufen.
Aus diesen Informationen ist es möglich, die Nutzlast und den Header vom RTP-Paket zu trennen, es gibt jedoch immer noch einige Probleme.
--Die gesendeten Daten müssen verschlüsselt sein
Im ersteren Fall haben Sie den Schlüssel bereits, sodass Sie ihn basierend darauf entschlüsseln können. Letzteres erfordert eine etwas komplizierte Verarbeitung und stellt eine C-Bibliothek namens "libopus" von Python zur Verfügung. Wenn Sie die Dekodierungsfunktion aufrufen, können sie als normale Wav-Daten gespeichert werden.
Wenn Sie diese beiden löschen können, können Sie die Audiodaten speichern. Das nächste Mal werde ich die vorhandene discord.py erweitern, basierend auf den Erkenntnissen aus dieser unteren Ebene, wie die Daten abgerufen und die Audiodaten gespeichert werden.
Recommended Posts