Advanced protocol MediaMixerOutput.

This commit is contained in:
shogo4405
2024-09-10 21:53:25 +09:00
parent 136bd6d151
commit 0b7c99829a
8 changed files with 76 additions and 52 deletions
+15 -8
View File
@@ -7,6 +7,8 @@ import libsrt
/// An actor that provides the interface to control a one-way channel over a SRTConnection.
public actor SRTStream {
@Published public private(set) var readyState: HKStreamReadyState = .idle
public private(set) var videoTrackId: UInt8? = UInt8.max
public private(set) var audioTrackId: UInt8? = UInt8.max
private var name: String?
private var action: (() async -> Void)?
private var outputs: [any HKStreamOutput] = []
@@ -191,6 +193,17 @@ extension SRTStream: HKStream {
}
}
public func selectTrack(_ id: UInt8?, mediaType: CMFormatDescription.MediaType) {
switch mediaType {
case .audio:
audioTrackId = id
case .video:
videoTrackId = id
default:
break
}
}
public func dispatch(_ event: NetworkMonitorEvent) async {
await bitrateStorategy?.adjustBitrate(event, stream: self)
}
@@ -198,17 +211,11 @@ extension SRTStream: HKStream {
extension SRTStream: MediaMixerOutput {
// MARK: MediaMixerOutput
nonisolated public func mixer(_ mixer: MediaMixer, track: UInt8, didOutput sampleBuffer: CMSampleBuffer) {
guard track == UInt8.max else {
return
}
nonisolated public func mixer(_ mixer: MediaMixer, didOutput sampleBuffer: CMSampleBuffer) {
Task { await append(sampleBuffer) }
}
nonisolated public func mixer(_ mixer: MediaMixer, track: UInt8, didOutput buffer: AVAudioPCMBuffer, when: AVAudioTime) {
guard track == UInt8.max else {
return
}
nonisolated public func mixer(_ mixer: MediaMixer, didOutput buffer: AVAudioPCMBuffer, when: AVAudioTime) {
Task { await append(buffer, when: when) }
}
}
+3
View File
@@ -46,6 +46,9 @@ public protocol HKStream: Actor, MediaMixerOutput {
/// Removes an output observer.
func removeOutput(_ observer: some HKStreamOutput)
/// Selects track id for streaming.
func selectTrack(_ id: UInt8?, mediaType: CMFormatDescription.MediaType)
/// Dispatch a network monitor event.
func dispatch(_ event: NetworkMonitorEvent) async
}
+15 -14
View File
@@ -67,6 +67,8 @@ public actor HKStreamRecorder {
public private(set) var isRecording = false
/// The the movie fragment interval in sec.
public private(set) var movieFragmentInterval: Double?
public private(set) var videoTrackId: UInt8? = UInt8.max
public private(set) var audioTrackId: UInt8? = UInt8.max
private var isReadyForStartWriting: Bool {
guard let writer = writer else {
return false
@@ -74,7 +76,6 @@ public actor HKStreamRecorder {
return settings.count == writer.inputs.count
}
private var writer: AVAssetWriter?
private var trackId: UInt8 = 0
private var continuation: AsyncStream<Error>.Continuation?
private var writerInputs: [AVMediaType: AVAssetWriterInput] = [:]
private var audioPresentationTime: CMTime = .zero
@@ -95,14 +96,6 @@ public actor HKStreamRecorder {
public init() {
}
/// Sets the video track id for recording.
public func setTrackId(_ trackId: UInt8) throws {
guard isRecording else {
throw Error.invalidState
}
self.trackId = trackId
}
/// Sets the movie fragment interval in sec.
///
/// This value allows the file to be written continuously, so the file will remain even if the app crashes or is forcefully terminated. A value of 10 seconds or more is recommended.
@@ -162,6 +155,17 @@ public actor HKStreamRecorder {
return writer.outputURL
}
public func selectTrack(_ id: UInt8?, mediaType: CMFormatDescription.MediaType) {
switch mediaType {
case .audio:
audioTrackId = id
case .video:
videoTrackId = id
default:
break
}
}
private func append(_ sampleBuffer: CMSampleBuffer) {
guard isRecording else {
return
@@ -273,16 +277,13 @@ extension HKStreamRecorder: HKStreamOutput {
extension HKStreamRecorder: MediaMixerOutput {
// MARK: MediaMixerOutput
nonisolated public func mixer(_ mixer: MediaMixer, track: UInt8, didOutput sampleBuffer: CMSampleBuffer) {
nonisolated public func mixer(_ mixer: MediaMixer, didOutput sampleBuffer: CMSampleBuffer) {
Task {
guard await trackId == track else {
return
}
await append(sampleBuffer)
}
}
nonisolated public func mixer(_ mixer: MediaMixer, track: UInt8, didOutput buffer: AVAudioPCMBuffer, when: AVAudioTime) {
nonisolated public func mixer(_ mixer: MediaMixer, didOutput buffer: AVAudioPCMBuffer, when: AVAudioTime) {
guard let sampleBuffer = buffer.makeSampleBuffer(when) else {
return
}
+10 -5
View File
@@ -314,19 +314,22 @@ extension MediaMixer: AsyncRunner {
Task { @ScreenActor in
screen.append(inputs.0, buffer: inputs.1)
}
for output in outputs where await output.videoTrackId == inputs.0 {
output.mixer(self, didOutput: inputs.1)
}
}
}
Task {
for await video in videoIO.output where isRunning {
for output in outputs {
output.mixer(self, track: UInt8.max, didOutput: video)
for output in outputs where await output.videoTrackId == UInt8.max {
output.mixer(self, didOutput: video)
}
}
}
Task {
for await audio in audioIO.output where isRunning {
for output in outputs {
output.mixer(self, track: UInt8.max, didOutput: audio.0, when: audio.1)
for output in outputs where await output.audioTrackId == UInt8.max {
output.mixer(self, didOutput: audio.0, when: audio.1)
}
}
}
@@ -337,7 +340,9 @@ extension MediaMixer: AsyncRunner {
guard let buffer = screen.makeSampleBuffer() else {
continue
}
await outputs.forEach { $0.mixer(self, track: UInt8.max, didOutput: buffer) }
for output in await self.outputs where await output.videoTrackId == UInt8.max {
output.mixer(self, didOutput: buffer)
}
}
}
#if os(iOS) || os(tvOS) || os(visionOS)
+6 -2
View File
@@ -2,8 +2,12 @@ import AVFoundation
/// A delegate protocol implements to receive stream output events.
public protocol MediaMixerOutput: AnyObject, Sendable {
/// Tells the receiver to a video track id.
var videoTrackId: UInt8? { get async }
/// Tells the receiver to an audio track id.
var audioTrackId: UInt8? { get async }
/// Tells the receiver to a video buffer incoming.
func mixer(_ mixer: MediaMixer, track: UInt8, didOutput sampleBuffer: CMSampleBuffer)
func mixer(_ mixer: MediaMixer, didOutput sampleBuffer: CMSampleBuffer)
/// Tells the receiver to an audio buffer incoming.
func mixer(_ mixer: MediaMixer, track: UInt8, didOutput buffer: AVAudioPCMBuffer, when: AVAudioTime)
func mixer(_ mixer: MediaMixer, didOutput buffer: AVAudioPCMBuffer, when: AVAudioTime)
}
+16 -8
View File
@@ -191,6 +191,9 @@ public actor RTMPStream {
/// The stream's name used for FMLE-compatible sequences.
public private(set) var fcPublishName: String?
public private(set) var videoTrackId: UInt8? = UInt8.max
public private(set) var audioTrackId: UInt8? = UInt8.max
private var isPaused = false
private var startedAt = Date() {
didSet {
@@ -802,6 +805,17 @@ extension RTMPStream: HKStream {
self.bitrateStorategy = bitrateStorategy
}
public func selectTrack(_ id: UInt8?, mediaType: CMFormatDescription.MediaType) {
switch mediaType {
case .audio:
audioTrackId = id
case .video:
videoTrackId = id
default:
break
}
}
public func dispatch(_ event: NetworkMonitorEvent) async {
await bitrateStorategy?.adjustBitrate(event, stream: self)
currentFPS = frameCount
@@ -812,17 +826,11 @@ extension RTMPStream: HKStream {
extension RTMPStream: MediaMixerOutput {
// MARK: MediaMixerOutput
nonisolated public func mixer(_ mixer: MediaMixer, track: UInt8, didOutput sampleBuffer: CMSampleBuffer) {
guard track == UInt8.max else {
return
}
nonisolated public func mixer(_ mixer: MediaMixer, didOutput sampleBuffer: CMSampleBuffer) {
Task { await append(sampleBuffer) }
}
nonisolated public func mixer(_ mixer: MediaMixer, track: UInt8, didOutput buffer: AVAudioPCMBuffer, when: AVAudioTime) {
guard track == UInt8.max else {
return
}
nonisolated public func mixer(_ mixer: MediaMixer, didOutput buffer: AVAudioPCMBuffer, when: AVAudioTime) {
Task { await append(buffer, when: when) }
}
}
+4 -6
View File
@@ -9,7 +9,8 @@ public class MTHKView: MTKView {
public var videoGravity: AVLayerVideoGravity = .resizeAspect
/// Specifies how the video is displayed with in track.
public var track = UInt8.max
public var videoTrackId: UInt8? = UInt8.max
public var audioTrackId: UInt8?
private var displayImage: CIImage?
private let colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB()
@@ -120,14 +121,11 @@ public class MTHKView: MTKView {
extension MTHKView: MediaMixerOutput {
// MARK: MediaMixerOutput
nonisolated public func mixer(_ mixer: MediaMixer, track: UInt8, didOutput buffer: AVAudioPCMBuffer, when: AVAudioTime) {
nonisolated public func mixer(_ mixer: MediaMixer, didOutput buffer: AVAudioPCMBuffer, when: AVAudioTime) {
}
nonisolated public func mixer(_ mixer: MediaMixer, track: UInt8, didOutput sampleBuffer: CMSampleBuffer) {
nonisolated public func mixer(_ mixer: MediaMixer, didOutput sampleBuffer: CMSampleBuffer) {
Task { @MainActor in
guard self.track == track else {
return
}
displayImage = try? sampleBuffer.imageBuffer?.makeCIImage()
#if os(macOS)
self.needsDisplay = true
+7 -9
View File
@@ -13,14 +13,14 @@ public class PiPHKView: UIView {
AVSampleBufferDisplayLayer.self
}
/// Specifies how the video is displayed with in track.
public var track = UInt8.max
/// The views Core Animation layer used for rendering.
override public var layer: AVSampleBufferDisplayLayer {
super.layer as! AVSampleBufferDisplayLayer
}
public var videoTrackId: UInt8? = UInt8.max
public var audioTrackId: UInt8?
/// A value that specifies how the video is displayed within a player layers bounds.
public var videoGravity: AVLayerVideoGravity = .resizeAspect {
didSet {
@@ -67,7 +67,8 @@ public class PiPHKView: NSView {
}
/// Specifies how the video is displayed with in track.
public var track = UInt8.max
public var videoTrackId: UInt8? = UInt8.max
public var audioTrackId: UInt8?
/// Initializes and returns a newly allocated view object with the specified frame rectangle.
override public init(frame: CGRect) {
@@ -96,14 +97,11 @@ public class PiPHKView: NSView {
extension PiPHKView: MediaMixerOutput {
// MARK: MediaMixerOutput
nonisolated public func mixer(_ mixer: MediaMixer, track: UInt8, didOutput buffer: AVAudioPCMBuffer, when: AVAudioTime) {
nonisolated public func mixer(_ mixer: MediaMixer, didOutput buffer: AVAudioPCMBuffer, when: AVAudioTime) {
}
nonisolated public func mixer(_ mixer: MediaMixer, track: UInt8, didOutput sampleBuffer: CMSampleBuffer) {
nonisolated public func mixer(_ mixer: MediaMixer, didOutput sampleBuffer: CMSampleBuffer) {
Task { @MainActor in
guard self.track == track else {
return
}
#if os(macOS)
(layer as? AVSampleBufferDisplayLayer)?.enqueue(sampleBuffer)
self.needsDisplay = true