音视频 SDP 添加码率

音视频 SDP 添加码率

最终目标

音频设置码率
  a=rtcp-mux
  a=rtpmap:111 opus/48000/2
  a=rtcp-fb:111 transport-cc
- a=fmtp:111 minptime=10;useinbandfec=1
+ a=fmtp:111 minptime=10;useinbandfec=1;maxaveragebitrate=64000
  a=rtpmap:63 red/48000/2
  a=fmtp:63 111/111
视频设置码率
  a=rtpmap:102 H264/90000
  a=rtcp-fb:102 goog-remb
  a=rtcp-fb:102 transport-cc
  a=rtcp-fb:102 ccm fir
  a=rtcp-fb:102 nack
  a=rtcp-fb:102 nack pli
-  a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
+  a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f;x-google-max-bitrate=800;x-google-min-bitrate=180;x-google-start-bitrate=560
  a=rtpmap:121 rtx/90000
  a=fmtp:121 apt=102
  a=rtpmap:127 H264/90000
  a=rtcp-fb:127 goog-remb
  a=rtcp-fb:127 transport-cc
  a=rtcp-fb:127 ccm fir
  a=rtcp-fb:127 nack

代码实现

import { ITrackBitrate } from '../../interfaces'
import { ISdpSemantics, SdpSemantics } from './ASdpStrategy'

/**
 * 以字符串的形式 硬处理 SDP 信息
 * 对于 plan-b | unified-plan 数据的处理 方式是相同的。
 * TODO: 暂时对于 plan-b 中多个 ssrc 的信息没有做特殊处理
 * 将 SDP 以 m=[video|audio] 进行拆分,
 * 从而获取 SDPHeader | videoStreams | audioStreams 并对其内容进行处理
 */
export default class SDPFactory {
  private SDPHeader: string = ''
  private videoStreams: string[] = []
  private audioStreams: string[] = []

  constructor (public readonly SDP:string, public readonly type: ISdpSemantics) {
    // 获取 Mid 的位置
    const streamPositions = this.getStreamIndex()
    this.spliteStreams(streamPositions)
  }

  /**
   * 它从 SDP 中删除空行和尾随空格
   * @param {string} sdp - 要处理的 SDP 字符串。
   * @returns 删除了换行符的 sdp 字符串。
   */
  static trimBlankLine (sdp: string):string {
    // 移除空行以及行尾的空格
    return sdp.replace(/\n\r\s*\r\n/g, '\r\n').replace(/\s+\r\n/g, '\r\n')
  }

  /**
   * 通过 m=video | m=audio 将 SDP 进行分隔
   * 它返回一个索引数组,其中在 SDP 中找到子字符串“m=video”或“m=audio”
   * 它返回 SDP 中 execResult.index 的索引数组。
   * @returns 一组数字。
   */
  private getStreamIndex ():number[] {
    const mid = RegExp('m=(?:video|audio)', 'img')
    const midPositions = new Set([0])
    let execResult
    while ((execResult = mid.exec(this.SDP)) !== null) {
      midPositions.add(execResult.index)
    }
    midPositions.add(this.SDP.length)
    return [...midPositions]
  }

  /**
   * 它将 SDP 拆分为标头和流
   * @param {number[]} streams - 表示每个流的开始和结束的数字数组。
   */
  private spliteStreams (streams:number[]) {
    // streams = [0, 30, 40, 50, 80, 100]
    this.SDPHeader = this.SDP.substring(streams[0], streams[1])
    for (let mid = 1; mid < streams.length; mid++) {
      const stream = this.SDP.substring(streams[mid], streams[mid + 1])
      // 移除不需要发布资源的 ssrc
      const sendonlyStream = this.clearInactiveOrRecvonly(stream)
      if (sendonlyStream) {
        if (/^\bm=video\b/.test(stream)) {
          this.videoStreams.push(stream)
        } else {
          this.audioStreams.push(stream)
        }
      }
    }
  }

  /**
   * 如果当前资源方向 RTCRtpTransceiverDirection 是 recvonly | inactive
   * 表明当前不需要发送 RTP 数据所以将 SDP 中删除所有以 `a=ssrc` 或 `a=msid` 开头的行
   * @param {string} stream - string - 要修改的 SDP 字符串
   * @returns 正在返回流。
   */
  clearInactiveOrRecvonly (stream: string) {
    const recvonlyORinactive = /\ba=(recvonly|inactive)\b/.test(stream)
    if (recvonlyORinactive) {
      return stream.replace(/\r\na=(ssrc|msid)[^\r\n]+/ig, '')
    }
    return stream
  }

  /**
   * 它采用 SDP 标头和音频和视频流并将它们连接到一个字符串中
   * @returns SDP 标头和音频和视频流的字符串。
   */
  public stringify ():string {
    const sdpSource:string[] = [this.SDPHeader]
    const sdp:string[] = [...this.audioStreams, ...this.videoStreams]
    sdp.forEach(item => {
      const matchMid = item.match(/\ba=mid:(\d+)\b/)
      if (matchMid && matchMid[1]) {
        sdpSource[+matchMid[1] + 1] = item
      }
    })
    // TODO: 重置数据
    // this.SDPHeader = ''
    // this.videoStreams = []
    // this.audioStreams = []
    // 对空行进行处理
    const streamsJoin = sdpSource.join('')
    return SDPFactory.trimBlankLine(streamsJoin)
  }

  /**
   * 它获取音频流并将它们映射到一个新数组,其中每个项目都是调用 setAudioItemBitrate 函数的结果
   * @param {ITrackBitrate} bitrate - 以 kbps 为单位的比特率。
   */
  public setAudiosBitrate (bitrate: ITrackBitrate) {
    this.audioStreams = this.audioStreams.map(sdpAudioBlock => {
      return this.setAudioItemBitrate(sdpAudioBlock, bitrate)
    })
  }

  /**
   * 它设置特定 MID 的音频比特率。
   * @param {ITrackBitrate} bitrate - 音频流的比特率,以 kbps 为单位。
   * @param {string} streamId - 音频流的媒体流 ID。
   */
  public setAudioBitrateWithStreamId (bitrate: ITrackBitrate, streamId: string) {
    this.audioStreams = this.audioStreams.map(sdpAudioBlock => {
      const testStreamId = new RegExp(`\\bmsid:${streamId}\\b`, 'ig')
      // 判断 msid 是否存在
      if (testStreamId.test(sdpAudioBlock)) {
        return this.setAudioItemBitrate(sdpAudioBlock, bitrate)
      }
      return sdpAudioBlock
    })
  }

  /**
   * 它接受一个 SDP 音频块和一个比特率,并返回比特率设置为给定值的 SDP 音频块
   * @param {string} sdpAudioBlock - SDP 的音频块。
   * @param {ITrackBitrate} bitrate - 以 kbps 为单位的比特率。
   * @returns 正在返回 sdpAudioBlock 并将比特率设置为传入的比特率。
   */
  private setAudioItemBitrate (sdpAudioBlock: string, bitrate: ITrackBitrate): string {
    const audioBitrate = [
      '$1',
      `maxaveragebitrate=${bitrate.max}`
    ]
    return sdpAudioBlock.replace(/(\buseinbandfec=[^\r\n]+)/ig, audioBitrate.join(';'))
  }

  /**
   * 对视频元素设置 SDP 相关信息
   * 针对 SDP 中所有的 video 元素添加码率设置
   * @param bitrate
   */
  public setVideosBitrate (bitrate: ITrackBitrate) {
    // 找到 profile-level-id 行在后面添加  ;x-google--bitrate ;x-google--bitrate=
    this.videoStreams = this.videoStreams.map(sdpVideoBlock => {
      return this.setVideoItemBitrate(sdpVideoBlock, bitrate)
    })
  }

  /**
   * 它设置特定中间的视频比特率, 针对 SDP 中给定 mid 的 video 元素添加码率设置。
   * @param {ITrackBitrate} bitrate - 您要设置的比特率。
   * @param {string} streamId - 媒体流 ID。
   */
  public setVideoBitrateWithStreamId (bitrate: ITrackBitrate, streamId: string) {
    this.videoStreams = this.videoStreams.map(sdpVideoBlock => {
      const testStreamId = new RegExp(`\\bmsid:${streamId}\\b`, 'ig')
      if (testStreamId.test(sdpVideoBlock)) {
        return this.setVideoItemBitrate(sdpVideoBlock, bitrate)
      }
      return sdpVideoBlock
    })
  }

  /**
   * 它设置视频项目的比特率。
   * @param {string} sdpVideoBlock - SDP 的视频块。
   * @param {ITrackBitrate} bitrate - 包含最小、最大和起始比特率值的比特率对象。
   * @returns 正在返回 sdpVideoBlock。
   */
  private setVideoItemBitrate (sdpVideoBlock: string, bitrate: ITrackBitrate):string {
    const videoBitrate = [
      '$1',
      `x-google-max-bitrate=${bitrate.max}`,
      `x-google-min-bitrate=${bitrate.min}`,
      `x-google-start-bitrate=${bitrate.start || bitrate.max * 0.7}`
    ]
    return sdpVideoBlock.replace(/(\bprofile-level-id=[^\r\n]+)/ig, videoBitrate.join(';'))
  }
}

通过 chrome://webrtc-internals/ 验证所添加的码率

Stats graphs for RTCOutboundRTPVideoStream_319339320 (outbound-rtp
在这里插入图片描述

注意发布多道流时,只有最后一个AS:码率有效,会以它为最高码率

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

从未、淡定

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值