feat(ios): refactor plugin and add DSCP, stats, and codec support
This commit is contained in:
parent
057020864a
commit
a02b76c360
2 changed files with 200 additions and 155 deletions
|
|
@ -45,7 +45,6 @@ class LinphoneBridge {
|
|||
let videoStatus = AVCaptureDevice.authorizationStatus(for: .video)
|
||||
|
||||
if audioStatus != .authorized || videoStatus != .authorized {
|
||||
// Request permissions
|
||||
AVCaptureDevice.requestAccess(for: .audio) { _ in }
|
||||
AVCaptureDevice.requestAccess(for: .video) { _ in }
|
||||
return false
|
||||
|
|
@ -58,14 +57,11 @@ class LinphoneBridge {
|
|||
do {
|
||||
let factory = Factory.Instance
|
||||
core = try factory.createCore(configPath: nil, factoryConfigPath: nil, systemContext: nil)
|
||||
core.addDelegate(delegate: self)
|
||||
|
||||
core.ipv6Enabled = false
|
||||
core.config?.setInt(section: "net", key: "ipv6", value: 0)
|
||||
try? core.config?.sync()
|
||||
core.agcEnabled = false
|
||||
|
||||
// Add core listener
|
||||
core.addDelegate(delegate: self)
|
||||
|
||||
// Enable video
|
||||
core.videoCaptureEnabled = true
|
||||
core.videoDisplayEnabled = true
|
||||
|
|
@ -84,7 +80,7 @@ class LinphoneBridge {
|
|||
core.downloadBandwidth = 1500
|
||||
|
||||
// Configure audio codecs
|
||||
let preferredAudio = ["opus", "pcmu", "pcma"]
|
||||
let preferredAudio = ["g729"]
|
||||
for pt in core.audioPayloadTypes {
|
||||
let mime = pt.mimeType.lowercased()
|
||||
let enabled = preferredAudio.contains(mime)
|
||||
|
|
@ -102,9 +98,9 @@ class LinphoneBridge {
|
|||
}
|
||||
|
||||
// Set bitrates
|
||||
core.getPayloadType(type: "opus", rate: -1, channels: 0)?.normalBitrate = 32
|
||||
core.getPayloadType(type: "h264", rate: -1, channels: 0)?.normalBitrate = 600
|
||||
core.getPayloadType(type: "vp8", rate: -1, channels: 0)?.normalBitrate = 600
|
||||
// core.getPayloadType(type: "opus", rate: -1, channels: 0)?.normalBitrate = 32
|
||||
// core.getPayloadType(type: "h264", rate: -1, channels: 0)?.normalBitrate = 600
|
||||
// core.getPayloadType(type: "vp8", rate: -1, channels: 0)?.normalBitrate = 600
|
||||
|
||||
// Video settings
|
||||
let preferredVidDef = try factory.createVideoDefinition(width: 720, height: 1280)
|
||||
|
|
@ -156,29 +152,23 @@ class LinphoneBridge {
|
|||
throw NSError(domain: "LinphoneBridge", code: 1, userInfo: [NSLocalizedDescriptionKey: "Not registered"])
|
||||
}
|
||||
|
||||
do {
|
||||
let factory = Factory.Instance
|
||||
guard let remoteAddress = try? factory.createAddress(addr: callTo) else {
|
||||
throw NSError(domain: "LinphoneBridge", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to create remote address"])
|
||||
}
|
||||
|
||||
guard let callParams = try? core.createCallParams(call: nil) else {
|
||||
throw NSError(domain: "LinphoneBridge", code: 3, userInfo: [NSLocalizedDescriptionKey: "Failed to create call params"])
|
||||
}
|
||||
|
||||
callParams.videoEnabled = isVideoEnabled
|
||||
callParams.videoDirection = isVideoEnabled ? .SendRecv : .Inactive
|
||||
|
||||
currentCall = try core.inviteAddressWithParams(addr: remoteAddress, params: callParams)
|
||||
} catch {
|
||||
throw error
|
||||
let factory = Factory.Instance
|
||||
guard let remoteAddress = try? factory.createAddress(addr: callTo) else {
|
||||
throw NSError(domain: "LinphoneBridge", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to create remote address"])
|
||||
}
|
||||
|
||||
guard let callParams = try? core.createCallParams(call: nil) else {
|
||||
throw NSError(domain: "LinphoneBridge", code: 3, userInfo: [NSLocalizedDescriptionKey: "Failed to create call params"])
|
||||
}
|
||||
|
||||
callParams.videoEnabled = isVideoEnabled
|
||||
callParams.videoDirection = isVideoEnabled ? .SendRecv : .Inactive
|
||||
|
||||
currentCall = try core.inviteAddressWithParams(addr: remoteAddress, params: callParams)
|
||||
}
|
||||
|
||||
func answerCall() -> Bool {
|
||||
guard let call = currentCall else {
|
||||
return false
|
||||
}
|
||||
guard let call = currentCall else { return false }
|
||||
|
||||
do {
|
||||
let callParams = try core.createCallParams(call: call)
|
||||
|
|
@ -203,9 +193,7 @@ class LinphoneBridge {
|
|||
}
|
||||
|
||||
func toggleVideo() -> Bool {
|
||||
guard let call = currentCall else {
|
||||
return false
|
||||
}
|
||||
guard let call = currentCall else { return false }
|
||||
|
||||
do {
|
||||
let params = try core.createCallParams(call: call)
|
||||
|
|
@ -238,25 +226,12 @@ class LinphoneBridge {
|
|||
}
|
||||
|
||||
func callType() -> CallType {
|
||||
guard let params = currentCall?.currentParams else {
|
||||
return .unknown
|
||||
}
|
||||
|
||||
if params.videoEnabled {
|
||||
return .video
|
||||
} else {
|
||||
return .audio
|
||||
}
|
||||
guard let params = currentCall?.currentParams else { return .unknown }
|
||||
return params.videoEnabled ? .video : .audio
|
||||
}
|
||||
|
||||
func sendDtmf(tone: String) -> Bool {
|
||||
guard let call = currentCall else {
|
||||
return false
|
||||
}
|
||||
|
||||
guard !tone.isEmpty else {
|
||||
return false
|
||||
}
|
||||
guard let call = currentCall, !tone.isEmpty else { return false }
|
||||
|
||||
let dtmfChar = tone.first!
|
||||
do {
|
||||
|
|
@ -273,6 +248,8 @@ class LinphoneBridge {
|
|||
onCallStateChanged(callState.rawValue)
|
||||
}
|
||||
|
||||
// MARK: - Gain
|
||||
|
||||
func setMicGain(level: Float) {
|
||||
core.micGainDb = level
|
||||
}
|
||||
|
|
@ -288,6 +265,111 @@ class LinphoneBridge {
|
|||
func getPlaybackGain() -> Float {
|
||||
return core.playbackGainDb
|
||||
}
|
||||
|
||||
// MARK: - DSCP
|
||||
|
||||
func setDscp(sipDscp: Int?, audioDscp: Int?, videoDscp: Int?) {
|
||||
if let sip = sipDscp { core.sipDscp = sip }
|
||||
if let audio = audioDscp { core.audioDscp = audio }
|
||||
if let video = videoDscp { core.videoDscp = video }
|
||||
}
|
||||
|
||||
func getDscp() -> [String: Any] {
|
||||
return [
|
||||
"sipDscp": core.sipDscp,
|
||||
"audioDscp": core.audioDscp,
|
||||
"videoDscp": core.videoDscp,
|
||||
]
|
||||
}
|
||||
|
||||
// MARK: - Call Stats
|
||||
|
||||
func getCurrentCallStats() -> [String: Any]? {
|
||||
guard let call = core.currentCall,
|
||||
let stats = call.getStats(type: .Audio) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return [
|
||||
"recordTs": Int64(Date().timeIntervalSince1970 * 1000),
|
||||
"remoteAddress": call.remoteAddress?.asString() ?? "",
|
||||
"currentQuality": call.currentQuality,
|
||||
"downloadBandwidth": stats.downloadBandwidth,
|
||||
"estimatedDownloadBandwidth": stats.estimatedDownloadBandwidth,
|
||||
"fecCumulativeLostPacketsNumber": stats.fecCumulativeLostPacketsNumber,
|
||||
"fecDownloadBandwidth": stats.fecDownloadBandwidth,
|
||||
"fecRepairedPacketsNumber": stats.fecRepairedPacketsNumber,
|
||||
"fecUploadBandwidth": stats.fecUploadBandwidth,
|
||||
"iceState": stats.iceState.rawValue,
|
||||
"jitterBufferSizeMs": stats.jitterBufferSizeMs,
|
||||
"latePacketsCumulativeNumber": stats.latePacketsCumulativeNumber,
|
||||
"localLateRate": stats.localLateRate,
|
||||
"localLossRate": stats.localLossRate,
|
||||
"receiverInterarrivalJitter": stats.receiverInterarrivalJitter,
|
||||
"receiverLossRate": stats.receiverLossRate,
|
||||
"roundTripDelay": stats.roundTripDelay,
|
||||
"rtcpDownloadBandwidth": stats.rtcpDownloadBandwidth,
|
||||
"rtcpUploadBandwidth": stats.rtcpUploadBandwidth,
|
||||
"rtpCumPacketLoss": stats.rtpCumPacketLoss,
|
||||
"rtpDiscarded": stats.rtpDiscarded,
|
||||
"rtpHwRecv": stats.rtpHwRecv,
|
||||
"rtpPacketRecv": stats.rtpPacketRecv,
|
||||
"rtpPacketSent": stats.rtpPacketSent,
|
||||
"rtpRecv": stats.rtpRecv,
|
||||
"rtpSent": stats.rtpSent,
|
||||
"senderInterarrivalJitter": stats.senderInterarrivalJitter,
|
||||
"senderLossRate": stats.senderLossRate,
|
||||
"srtpSource": stats.srtpSource.rawValue,
|
||||
"srtpSuite": stats.srtpSuite.rawValue,
|
||||
"uploadBandwidth": stats.uploadBandwidth,
|
||||
"upnpState": stats.upnpState.rawValue,
|
||||
"zrtpAuthTagAlgo": stats.zrtpAuthTagAlgo,
|
||||
"zrtpCipherAlgo": stats.zrtpCipherAlgo,
|
||||
"zrtpHashAlgo": stats.zrtpHashAlgo,
|
||||
"zrtpKeyAgreementAlgo": stats.zrtpKeyAgreementAlgo,
|
||||
"zrtpSasAlgo": stats.zrtpSasAlgo,
|
||||
"isZrtpKeyAgreementAlgoPostQuantum": stats.isZrtpKeyAgreementAlgoPostQuantum,
|
||||
]
|
||||
}
|
||||
|
||||
// MARK: - Codec management
|
||||
|
||||
func getAvailableAudioCodecs() -> [[String: Any]] {
|
||||
return core.audioPayloadTypes.map { pt in
|
||||
[
|
||||
"mimeType": pt.mimeType.lowercased(),
|
||||
"clockRate": pt.clockRate,
|
||||
"enabled": pt.enabled(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables the codec matching (mime, clockRate) and disables all others.
|
||||
/// Returns true if the target codec was found and enabled successfully.
|
||||
func setAudioCodec(mime: String, clockRate: Int) -> Bool {
|
||||
var found = false
|
||||
var ok = false
|
||||
|
||||
for pt in core.audioPayloadTypes {
|
||||
let ptMime = pt.mimeType.lowercased()
|
||||
let ptClockRate = pt.clockRate
|
||||
let enable = ptMime == mime && ptClockRate == clockRate
|
||||
if enable {
|
||||
ok = pt.enable(enabled: true) == 0
|
||||
found = true
|
||||
print("\(LinphoneBridge.TAG) setAudioCodec: preferred codec found (\(ptMime) @ \(ptClockRate)) switch: \(ok)")
|
||||
} else {
|
||||
let switchedOk = pt.enable(enabled: false) == 0
|
||||
print("\(LinphoneBridge.TAG) setAudioCodec: \(ptMime) @ \(ptClockRate) switch: \(switchedOk)")
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
print("\(LinphoneBridge.TAG) setAudioCodec: could not find codec with params \(mime) @ \(clockRate)")
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - CoreDelegate
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue