From f1074c6a922b920dd9d8cafdea081234737dbb46 Mon Sep 17 00:00:00 2001 From: shogo4405 Date: Mon, 14 Oct 2024 15:02:13 +0900 Subject: [PATCH] refs #1603 --- Sources/Codec/VideoCodec.swift | 2 +- Sources/RTMP/RTMPMuxer.swift | 76 ++++++++++++++++++-------------- Sources/RTMP/RTMPTimestamp.swift | 26 ++++++----- 3 files changed, 59 insertions(+), 45 deletions(-) diff --git a/Sources/Codec/VideoCodec.swift b/Sources/Codec/VideoCodec.swift index 05dc7abe0..d64575c17 100644 --- a/Sources/Codec/VideoCodec.swift +++ b/Sources/Codec/VideoCodec.swift @@ -178,7 +178,7 @@ final class VideoCodec { guard kVideoCodec_defaultFrameInterval < frameInterval else { return true } - return frameInterval < presentationTimeStamp.seconds - self.presentationTimeStamp.seconds + return frameInterval <= presentationTimeStamp.seconds - self.presentationTimeStamp.seconds } #if os(iOS) || os(tvOS) || os(visionOS) diff --git a/Sources/RTMP/RTMPMuxer.swift b/Sources/RTMP/RTMPMuxer.swift index 1de36c3ad..0c20b49d4 100644 --- a/Sources/RTMP/RTMPMuxer.swift +++ b/Sources/RTMP/RTMPMuxer.swift @@ -153,46 +153,54 @@ extension RTMPMuxer: IOMuxer { guard let stream, let audioBuffer = audioBuffer as? AVAudioCompressedBuffer else { return } - let timedelta = audioTimestamp.update(when) - var buffer = Data([RTMPMuxer.aac, FLVAACPacketType.raw.rawValue]) - buffer.append(audioBuffer.data.assumingMemoryBound(to: UInt8.self), count: Int(audioBuffer.byteLength)) - stream.doOutput( - .one, - chunkStreamId: FLVTagType.audio.streamId, - message: RTMPAudioMessage(streamId: 0, timestamp: timedelta, payload: buffer) - ) + do { + let timedelta = try audioTimestamp.update(when) + var buffer = Data([RTMPMuxer.aac, FLVAACPacketType.raw.rawValue]) + buffer.append(audioBuffer.data.assumingMemoryBound(to: UInt8.self), count: Int(audioBuffer.byteLength)) + stream.doOutput( + .one, + chunkStreamId: FLVTagType.audio.streamId, + message: RTMPAudioMessage(streamId: 0, timestamp: timedelta, payload: buffer) + ) + } catch { + logger.info(error) + } } func append(_ sampleBuffer: CMSampleBuffer) { guard let stream, let data = try? sampleBuffer.dataBuffer?.dataBytes() else { return } - let keyframe = !sampleBuffer.isNotSync - let decodeTimeStamp = sampleBuffer.decodeTimeStamp.isValid ? sampleBuffer.decodeTimeStamp : sampleBuffer.presentationTimeStamp - let compositionTime = videoTimestamp.getCompositionTime(sampleBuffer) - let timedelta = videoTimestamp.update(decodeTimeStamp) - stream.frameCount += 1 - switch sampleBuffer.formatDescription?.mediaSubType { - case .h264?: - var buffer = Data([((keyframe ? FLVFrameType.key.rawValue : FLVFrameType.inter.rawValue) << 4) | FLVVideoCodec.avc.rawValue, FLVAVCPacketType.nal.rawValue]) - buffer.append(contentsOf: compositionTime.bigEndian.data[1..<4]) - buffer.append(data) - stream.doOutput( - .one, - chunkStreamId: FLVTagType.video.streamId, - message: RTMPVideoMessage(streamId: 0, timestamp: timedelta, payload: buffer) - ) - case .hevc?: - var buffer = Data([0b10000000 | ((keyframe ? FLVFrameType.key.rawValue : FLVFrameType.inter.rawValue) << 4) | FLVVideoPacketType.codedFrames.rawValue, 0x68, 0x76, 0x63, 0x31]) - buffer.append(contentsOf: compositionTime.bigEndian.data[1..<4]) - buffer.append(data) - stream.doOutput( - .one, - chunkStreamId: FLVTagType.video.streamId, - message: RTMPVideoMessage(streamId: 0, timestamp: timedelta, payload: buffer) - ) - default: - break + do { + let keyframe = !sampleBuffer.isNotSync + let decodeTimeStamp = sampleBuffer.decodeTimeStamp.isValid ? sampleBuffer.decodeTimeStamp : sampleBuffer.presentationTimeStamp + let compositionTime = videoTimestamp.getCompositionTime(sampleBuffer) + let timedelta = try videoTimestamp.update(decodeTimeStamp) + stream.frameCount += 1 + switch sampleBuffer.formatDescription?.mediaSubType { + case .h264?: + var buffer = Data([((keyframe ? FLVFrameType.key.rawValue : FLVFrameType.inter.rawValue) << 4) | FLVVideoCodec.avc.rawValue, FLVAVCPacketType.nal.rawValue]) + buffer.append(contentsOf: compositionTime.bigEndian.data[1..<4]) + buffer.append(data) + stream.doOutput( + .one, + chunkStreamId: FLVTagType.video.streamId, + message: RTMPVideoMessage(streamId: 0, timestamp: timedelta, payload: buffer) + ) + case .hevc?: + var buffer = Data([0b10000000 | ((keyframe ? FLVFrameType.key.rawValue : FLVFrameType.inter.rawValue) << 4) | FLVVideoPacketType.codedFrames.rawValue, 0x68, 0x76, 0x63, 0x31]) + buffer.append(contentsOf: compositionTime.bigEndian.data[1..<4]) + buffer.append(data) + stream.doOutput( + .one, + chunkStreamId: FLVTagType.video.streamId, + message: RTMPVideoMessage(streamId: 0, timestamp: timedelta, payload: buffer) + ) + default: + break + } + } catch { + logger.info(error) } } } diff --git a/Sources/RTMP/RTMPTimestamp.swift b/Sources/RTMP/RTMPTimestamp.swift index 55e143547..daac9f67f 100644 --- a/Sources/RTMP/RTMPTimestamp.swift +++ b/Sources/RTMP/RTMPTimestamp.swift @@ -10,25 +10,31 @@ private let kRTMPTimestamp_defaultTimeInterval: TimeInterval = 0 private let kRTMPTimestamp_compositiionTimeOffset = CMTime(value: 3, timescale: 30) struct RTMPTimestamp { + enum Error: Swift.Error { + case invalidSequence + } + private var startedAt = kRTMPTimestamp_defaultTimeInterval private var updatedAt = kRTMPTimestamp_defaultTimeInterval private var timedeltaFraction: TimeInterval = kRTMPTimestamp_defaultTimeInterval - mutating func update(_ value: T) -> UInt32 { + mutating func update(_ value: T) throws -> UInt32 { + guard updatedAt < value.seconds else { + throw Error.invalidSequence + } if startedAt == 0 { startedAt = value.seconds updatedAt = value.seconds return 0 - } else { - var timedelta = (value.seconds - updatedAt) * 1000 - timedeltaFraction += timedelta.truncatingRemainder(dividingBy: 1) - if 1 <= timedeltaFraction { - timedeltaFraction -= 1 - timedelta += 1 - } - updatedAt = value.seconds - return UInt32(timedelta) } + var timedelta = (value.seconds - updatedAt) * 1000 + timedeltaFraction += timedelta.truncatingRemainder(dividingBy: 1) + if 1 <= timedeltaFraction { + timedeltaFraction -= 1 + timedelta += 1 + } + updatedAt = value.seconds + return UInt32(timedelta) } mutating func update(_ message: RTMPMessage, chunkType: RTMPChunkType) {