【山大会议】处理媒体流发布请求

处理请求体

func handlePublishRequest(request map[string]interface{}) error {
	var meetingId, userId int
	var meetingIdF, userIdF float64
	var jsep map[string]interface{}
	var sdp string
	var ok bool

	if userIdF, ok = request["userId"].(float64); !ok {
		return errors.New("parameter \"userId\" must be integer")
	}
	userId = int(userIdF)

	if meetingIdF, ok = request["meetingId"].(float64); !ok {
		return errors.New("parameter \"meetingId\" must be integer")
	}
	meetingId = int(meetingIdF)

	if jsep, ok = request["jsep"].(map[string]interface{}); !ok {
		return errors.New("parameter \"jsep\" must be map[string]interface{}")
	}

	if sdp, ok = jsep["sdp"].(string); !ok {
		return errors.New("parameter \"sdp\" must be string")
	}

	meeting := meetingList.GetMeetingById(meetingId)
	if meeting == nil {
		return errors.New("meeting is not found")
	}

	user := meeting.GetUserById(userId)
	if user == nil {
		return errors.New("user is not found")
	}

	offer := webrtc.SessionDescription{
		Type: webrtc.SDPTypeOffer,
		SDP:  sdp,
	}

	meeting.AddPublisherById(userId)

	answer, err := meeting.AnswerPublisher(userId, offer)
	if err != nil {
		fmt.Println(err)
		return errors.New("fail to answer publisher")
	}

	response := make(map[string]interface{})
	response["meetingId"] = meetingId
	response["pubId"] = userId
	response["jsep"] = answer
	meeting.Broadcast(ResponsePublishSuccess, response)

	return nil
}

当 publish 请求到来时,先解析请求体,获取userIdmeetingId和 SDP 信息。

在会议列表中查询meetingId,检测会议号的有效性。如果该会议确实存在,再检查该会议中是否有用户 id 为userId的用户。

SDP 用于在数据传输时两端都能够理解彼此的数据,在这里利用客户端的 SDP 生成对应的服务端 SDP。

最后,将该用户发布媒体流的消息发给会议内的所有用户,便于他们订阅。

响应发布者

func (m *Meeting) AnswerPublisher(id int, offer webrtc.SessionDescription) (
	webrtc.SessionDescription, error) {

	p := m.GetPublisherById(id)

	return p.AnswerPublisher(offer)
}

func (p *WebRTCPeer) AnswerPublisher(offer webrtc.SessionDescription) (
	answer webrtc.SessionDescription, err error) {

	return engine.CreateMediaStreamReceiver(offer, &p.PC, 
		&p.VideoTrack, &p.AudioTrack, p.stop, p.pli)
}

在这里将消息层层传递,传到引擎层。

接收媒体流

func (engine WebRTCEngine) CreateMediaStreamReceiver(offer webrtc.SessionDescription,
	pc **webrtc.PeerConnection, videoTrack, audioTrack **webrtc.Track,
	stop chan int, pli chan int) (answer webrtc.SessionDescription, err error) {

	*pc, err = engine.api.NewPeerConnection(engine.config)
	if err != nil {
		return webrtc.SessionDescription{}, err
	}

	_, err = (*pc).AddTransceiverFromKind(webrtc.RTPCodecTypeVideo)
	if err != nil {
		return webrtc.SessionDescription{}, err
	}

	_, err = (*pc).AddTransceiverFromKind(webrtc.RTPCodecTypeAudio)
	if err != nil {
		return webrtc.SessionDescription{}, err
	}

	(*pc).OnTrack(func(track *webrtc.Track, receiver *webrtc.RTPReceiver) {
		switch track.PayloadType() {
		case webrtc.DefaultPayloadTypeVP8:
			*videoTrack, err = (*pc).NewTrack(track.PayloadType(), 
			track.SSRC(), "video", track.Label())

			go func() {
				for {
					select {
					case <-pli:
						_ = (*pc).WriteRTCP([]rtcp.Packet{
						&rtcp.PictureLossIndication{MediaSSRC: track.SSRC()}})
					case <-stop:
						return
					}
				}
			}()
			
			builder := samplebuilder.New(7*5, &codecs.VP8Packet{})

			for {
				select {
				case <-stop:
					return
				default:
					r, err := track.ReadRTP()
					if err != nil {
						if err == io.EOF {
							return
						}
						global.Logger.Error(err.Error())
					}

					builder.Push(r)

					for s := builder.Pop(); s != nil; s = builder.Pop() {
						if err = (*videoTrack).WriteSample(*s); 
						err != nil && err != io.ErrClosedPipe {
							global.Logger.Error(err.Error())
						}
					}
				}
			}
		case webrtc.DefaultPayloadTypeOpus:
			*audioTrack, err = (*pc).NewTrack(track.PayloadType(), 
			track.SSRC(), "audio", track.Label())

			rtpBuf := make([]byte, 1400)
			for {
				select {
				case <-stop:
					return
				default:
					i, err := track.Read(rtpBuf)
					if err == nil {
						_, _ = (*audioTrack).Write(rtpBuf[:i])
					} else {
						global.Logger.Error(err.Error())
					}
				}
			}
		default:
			global.Logger.Error("This codec is not supported by the system")
		}
	})

	err = (*pc).SetRemoteDescription(offer)
	if err != nil {
		return webrtc.SessionDescription{}, err
	}

	answer, err = (*pc).CreateAnswer(nil)
	if err != nil {
		return webrtc.SessionDescription{}, err
	}

	err = (*pc).SetLocalDescription(answer)
	if err != nil {
		return webrtc.SessionDescription{}, err
	}

	return answer, nil

先创建一个PeerConnection对象,并在其中添加视频 RTC 收发器和音频 RTC 收发器。

设置一个事件处理函数,当远程 track 从远端 peer 到达时调用。在这个函数中,它会根据 track 的类型进行不同的解析方式。

然后设置远端 peer 和本地 peer 的 SDP 信息。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值