예제 #1
0
    async def receive(self, trackId: str, kind: MediaKind,
                      rtpParameters: RtpParameters) -> HandlerReceiveResult:
        options = HandlerReceiveOptions(trackId=trackId,
                                        kind=kind,
                                        rtpParameters=rtpParameters)
        self._assertRecvDirection()
        logging.debug(
            f'receive() [trackId:{options.trackId}, kind:{options.kind}]')
        localId = options.rtpParameters.mid if options.rtpParameters.mid != None else str(
            len(self._mapMidTransceiver))
        self.remoteSdp.receive(mid=localId,
                               kind=options.kind,
                               offerRtpParameters=options.rtpParameters,
                               streamId=options.rtpParameters.rtcp.cname,
                               trackId=options.trackId)
        offer: RTCSessionDescription = RTCSessionDescription(
            type='offer', sdp=self.remoteSdp.getSdp())
        logging.debug(
            f'receive() | calling pc.setRemoteDescription() [offer:{offer}]')
        await self.pc.setRemoteDescription(offer)
        answer: RTCSessionDescription = await self.pc.createAnswer()
        localSdpDict = sdp_transform.parse(answer.sdp)
        answerMediaDict = [
            m for m in localSdpDict.get('media')
            if str(m.get('mid')) == localId
        ][0]
        # May need to modify codec parameters in the answer based on codec
        # parameters in the offer.
        applyCodecParameters(offerRtpParameters=options.rtpParameters,
                             answerMediaDict=answerMediaDict)
        answer = RTCSessionDescription(type='answer',
                                       sdp=sdp_transform.write(localSdpDict))
        if not self._transportReady:
            await self._setupTransport(localDtlsRole='client',
                                       localSdpDict=localSdpDict)
        logging.debug(
            f'receive() | calling pc.setLocalDescription() [answer:{answer}]')
        await self.pc.setLocalDescription(answer)
        transceivers = [
            t for t in self.pc.getTransceivers() if t.mid == localId
        ]
        if not transceivers:
            raise Exception('new RTCRtpTransceiver not found')
        # Store in the map.
        transceiver = transceivers[0]
        self._mapMidTransceiver[localId] = transceiver

        return HandlerReceiveResult(localId=localId,
                                    track=transceiver.receiver.track,
                                    rtpReceiver=transceiver.receiver)
예제 #2
0
 async def _setupTransport(self,
                           localDtlsRole: DtlsRole,
                           localSdpDict: dict = {}):
     if localSdpDict == {}:
         localSdpDict = sdp_transform.parse(self.pc.localDescription.sdp)
     # Get our local DTLS parameters.
     dtlsParameters: DtlsParameters = extractDtlsParameters(localSdpDict)
     # Set our DTLS role.
     dtlsParameters.role = localDtlsRole
     # Update the remote DTLS role in the SDP.
     self.remoteSdp.updateDtlsRole('server' if localDtlsRole ==
                                   'client' else 'client')
     # Need to tell the remote transport about our parameters.
     await self.emit_for_results('@connect', dtlsParameters)
     self._transportReady = True
예제 #3
0
    async def getNativeRtpCapabilities(self) -> RtpCapabilities:
        logging.debug('getNativeRtpCapabilities()')

        pc = RTCPeerConnection()
        for track in self._tracks:
            pc.addTrack(track)
        pc.addTransceiver('audio')
        pc.addTransceiver('video')

        offer: RTCSessionDescription = await pc.createOffer()
        await pc.close()

        sdpDict: dict = sdp_transform.parse(offer.sdp)
        nativeRtpCapabilities: RtpCapabilities = common_utils.extractRtpCapabilities(
            sdpDict)

        return nativeRtpCapabilities
예제 #4
0
    async def receiveDataChannel(
            self,
            sctpStreamParameters: SctpStreamParameters,
            label: Optional[str] = None,
            protocol: Optional[str] = None) -> HandlerReceiveDataChannelResult:
        options = HandlerReceiveDataChannelOptions(
            sctpStreamParameters=sctpStreamParameters,
            label=label,
            protocol=protocol)
        self._assertRecvDirection()
        logging.debug(
            f'[receiveDataChannel() [options:{options.sctpStreamParameters}]]')
        dataChannel = self.pc.createDataChannel(
            label=options.label,
            maxPacketLifeTime=options.sctpStreamParameters.maxPacketLifeTime,
            maxRetransmits=options.sctpStreamParameters.maxRetransmits,
            ordered=options.sctpStreamParameters.ordered,
            protocol=options.protocol,
            negotiated=True,
            id=options.sctpStreamParameters.streamId)

        # If this is the first DataChannel we need to create the SDP offer with
        # m=application section.
        if not self._hasDataChannelMediaSection:
            self.remoteSdp.receiveSctpAssociation()
            offer: RTCSessionDescription = RTCSessionDescription(
                type='offer', sdp=self.remoteSdp.getSdp())
            logging.debug(
                f'receiveDataChannel() | calling pc.setRemoteDescription() [offer:{offer}]'
            )
            await self.pc.setRemoteDescription(offer)
            answer = await self.pc.createAnswer()
            if not self._transportReady:
                localSdpDict = sdp_transform.parse(answer.sdp)
                await self._setupTransport(localDtlsRole='client',
                                           localSdpDict=localSdpDict)
            logging.debug(
                f'receiveDataChannel() | calling pc.setRemoteDescription() [answer:{answer}]'
            )
            await self.pc.setLocalDescription(answer)
            self._hasDataChannelMediaSection = True
        return HandlerReceiveDataChannelResult(dataChannel=dataChannel)
예제 #5
0
    async def sendDataChannel(
            self,
            streamId: Optional[int] = None,
            ordered: Optional[bool] = True,
            maxPacketLifeTime: Optional[int] = None,
            maxRetransmits: Optional[int] = None,
            priority: Optional[Literal['very-low', 'low', 'medium',
                                       'high']] = None,
            label: Optional[str] = None,
            protocol: Optional[str] = None) -> HandlerSendDataChannelResult:
        if streamId == None:
            streamId = self._nextSendSctpStreamId
        options = SctpStreamParameters(streamId=streamId,
                                       ordered=ordered,
                                       maxPacketLifeTime=maxPacketLifeTime,
                                       maxRetransmits=maxRetransmits,
                                       priority=priority,
                                       label=label,
                                       protocol=protocol)
        self._assertSendDirection()
        logging.debug('sendDataChannel()')
        dataChannel = self.pc.createDataChannel(
            label=options.label,
            maxPacketLifeTime=options.maxPacketLifeTime,
            ordered=options.ordered,
            protocol=options.protocol,
            negotiated=True,
            id=self._nextSendSctpStreamId)
        # Increase next id.
        self._nextSendSctpStreamId = (self._nextSendSctpStreamId +
                                      1) % SCTP_NUM_STREAMS.get('MIS', 1)
        # If this is the first DataChannel we need to create the SDP answer with
        # m=application section.
        if not self._hasDataChannelMediaSection:
            offer: RTCSessionDescription = await self.pc.createOffer()
            localSdpDict = sdp_transform.parse(offer.sdp)
            offerMediaDicts = [
                m for m in localSdpDict.get('media')
                if m.get('type') == 'application'
            ]
            if not offerMediaDicts:
                raise Exception('No datachannel')
            offerMediaDict = offerMediaDicts[0]

            if not self._transportReady:
                await self._setupTransport(localDtlsRole='server',
                                           localSdpDict=localSdpDict)

            logging.debug(
                f'sendDataChannel() | calling pc.setLocalDescription() [offer:{offer}]'
            )
            await self.pc.setLocalDescription(offer)
            self.remoteSdp.sendSctpAssociation(offerMediaDict=offerMediaDict)
            answer: RTCSessionDescription = RTCSessionDescription(
                type='answer', sdp=self.remoteSdp.getSdp())

            logging.debug(
                f'sendDataChannel() | calling pc.setRemoteDescription() [answer:{answer}]'
            )
            await self.pc.setRemoteDescription(answer)
            self._hasDataChannelMediaSection = True

        return HandlerSendDataChannelResult(dataChannel=dataChannel,
                                            sctpStreamParameters=options)
예제 #6
0
    async def send(
            self,
            track: MediaStreamTrack,
            encodings: List[RtpEncodingParameters] = [],
            codecOptions: Optional[ProducerCodecOptions] = None,
            codec: Optional[RtpCodecCapability] = None) -> HandlerSendResult:
        options = HandlerSendOptions(track=track,
                                     encodings=encodings,
                                     codecOptions=codecOptions,
                                     codec=codec)
        self._assertSendDirection()
        logging.debug(
            f'send() [kind:{options.track.kind}, track.id:{options.track.id}]')
        if options.encodings:
            for idx in range(len(options.encodings)):
                options.encodings[idx].rid = f'r{idx}'

        sendingRtpParameters: RtpParameters = self._sendingRtpParametersByKind[
            options.track.kind].copy(deep=True)
        sendingRtpParameters.codecs = reduceCodecs(sendingRtpParameters.codecs,
                                                   options.codec)

        sendingRemoteRtpParameters: RtpParameters = self._sendingRemoteRtpParametersByKind[
            options.track.kind].copy(deep=True)
        sendingRemoteRtpParameters.codecs = reduceCodecs(
            sendingRemoteRtpParameters.codecs, options.codec)

        mediaSectionIdx = self.remoteSdp.getNextMediaSectionIdx()
        transceiver = self.pc.addTransceiver(options.track,
                                             direction='sendonly')

        offer: RTCSessionDescription = await self.pc.createOffer()
        offerMediaDict: dict
        localSdpDict = sdp_transform.parse(offer.sdp)
        if not self._transportReady:
            await self._setupTransport(localDtlsRole='server',
                                       localSdpDict=localSdpDict)
        # Special case for VP9 with SVC.
        hackVp9Svc = False
        if options.encodings:
            layers = smParse(options.encodings[0].scalabilityMode if options.
                             encodings[0].scalabilityMode else '')
        else:
            layers = smParse('')
        if len(
                options.encodings
        ) == 1 and layers.spatialLayers > 1 and sendingRtpParameters.codecs[
                0].mimeType.lower() == 'video/vp9':
            logging.debug('send() | enabling legacy simulcast for VP9 SVC')
            hackVp9Svc = True
            localSdpDict = sdp_transform.parse
            offerMediaDict = localSdpDict['media'][mediaSectionIdx.idx]
            addLegacySimulcast(offerMediaDict=offerMediaDict,
                               numStreams=layers.spatialLayers)
            offer = RTCSessionDescription(
                type='offer', sdp=sdp_transform.write(localSdpDict))

        logging.debug(
            f'send() | calling pc.setLocalDescription() [offer:{offer}]')

        await self.pc.setLocalDescription(offer)
        # We can now get the transceiver.mid.
        localId = transceiver.mid
        # Set MID.
        sendingRtpParameters.mid = localId
        localSdpDict = sdp_transform.parse(self.pc.localDescription.sdp)

        offerMediaDict = localSdpDict['media'][mediaSectionIdx.idx]

        logging.debug(
            f"send() | get offerMediaDict {offerMediaDict} \n from localSdpDict {localSdpDict['media']} index {mediaSectionIdx.idx}"
        )
        # Set RTCP CNAME.
        if sendingRtpParameters.rtcp == None:
            sendingRtpParameters.rtcp = RtcpParameters()
        sendingRtpParameters.rtcp.cname = common_utils.getCname(offerMediaDict)
        # Set RTP encodings by parsing the SDP offer if no encodings are given.
        if not options.encodings:
            sendingRtpParameters.encodings = getRtpEncodings(offerMediaDict)
        # Set RTP encodings by parsing the SDP offer and complete them with given
        # one if just a single encoding has been given.
        elif len(options.encodings) == 1:
            newEncodings = getRtpEncodings(offerMediaDict)
            if newEncodings and options.encodings[0]:
                firstEncodingDict: dict = newEncodings[0].dict()
                optionsEncodingDict: dict = options.encodings[0].dict()
                firstEncodingDict.update(optionsEncodingDict)
                newEncodings[0] = RtpEncodingParameters(**firstEncodingDict)
                if hackVp9Svc:
                    newEncodings = [newEncodings[0]]
            sendingRtpParameters.encodings = newEncodings
        # Otherwise if more than 1 encoding are given use them verbatim.
        else:
            sendingRtpParameters.encodings = options.encodings
        # If VP8 or H264 and there is effective simulcast, add scalabilityMode to
        # each encoding.
        if len(sendingRtpParameters.encodings) > 1 and (
                sendingRtpParameters.codecs[0].mimeType.lower() == 'video/vp8'
                or sendingRtpParameters.codecs[0].mimeType.lower()
                == 'video/h264'):
            for encoding in sendingRtpParameters.encodings:
                encoding.scalabilityMode = 'S1T3'
        self.remoteSdp.send(offerMediaDict=offerMediaDict,
                            reuseMid=mediaSectionIdx.reuseMid,
                            offerRtpParameters=sendingRtpParameters,
                            answerRtpParameters=sendingRemoteRtpParameters,
                            codecOptions=options.codecOptions,
                            extmapAllowMixed=True)
        answer: RTCSessionDescription = RTCSessionDescription(
            type='answer', sdp=self.remoteSdp.getSdp())
        logging.debug(
            f'send() | calling pc.setRemoteDescription() [answer:{answer}]')
        await self.pc.setRemoteDescription(answer)
        # Store in the map.
        self._mapMidTransceiver[localId] = transceiver
        return HandlerSendResult(localId=localId,
                                 rtpParameters=sendingRtpParameters,
                                 rtpSender=transceiver.sender)