Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IOStreamObserver and confirmed IOStreamRecorder. #1409

Merged
merged 1 commit into from
Apr 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions HaishinKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@
BC959F1229717EDB0067BA97 /* PreferenceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC959F1129717EDB0067BA97 /* PreferenceViewController.swift */; };
BC9CFA9323BDE8B700917EEF /* IOStreamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC9CFA9223BDE8B700917EEF /* IOStreamView.swift */; };
BC9F9C7826F8C16600B01ED0 /* Choreographer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC9F9C7726F8C16600B01ED0 /* Choreographer.swift */; };
BCA7C24F2A91AA0500882D85 /* IORecorderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCA7C24E2A91AA0500882D85 /* IORecorderTests.swift */; };
BCA7C24F2A91AA0500882D85 /* IOStreamRecorderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCA7C24E2A91AA0500882D85 /* IOStreamRecorderTests.swift */; };
BCAD0C18263ED67F00ADFB80 /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = BCAD0C16263ED67F00ADFB80 /* [email protected] */; };
BCAD0C19263ED67F00ADFB80 /* SampleVideo_360x240_5mb@m4v in Resources */ = {isa = PBXBuildFile; fileRef = BCAD0C17263ED67F00ADFB80 /* SampleVideo_360x240_5mb@m4v */; };
BCB976DF26107B5600C9A649 /* TSField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB976DE26107B5600C9A649 /* TSField.swift */; };
Expand Down Expand Up @@ -264,6 +264,7 @@
BCD63ADD26FDF34C0084842D /* HaishinKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 2945CBBD1B4BE66000104112 /* HaishinKit.framework */; platformFilter = ios; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
BCD63AE126FDF3500084842D /* Logboard.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = BC34DFD125EBB12C005F975A /* Logboard.xcframework */; };
BCD63AE226FDF3500084842D /* Logboard.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BC34DFD125EBB12C005F975A /* Logboard.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
BCD8702B2BC266CD009E495B /* IOStreamObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCD8702A2BC266CD009E495B /* IOStreamObserver.swift */; };
BCD91C0D2A700FF50033F9E1 /* IOAudioRingBufferTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCD91C0C2A700FF50033F9E1 /* IOAudioRingBufferTests.swift */; };
BCE0E33D2AD369550082C16F /* NetStreamSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE0E33B2AD369410082C16F /* NetStreamSwitcher.swift */; };
BCFB355524FA27EA00DC5108 /* PlaybackViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCFB355324FA275600DC5108 /* PlaybackViewController.swift */; };
Expand Down Expand Up @@ -645,7 +646,7 @@
BC959F1129717EDB0067BA97 /* PreferenceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceViewController.swift; sourceTree = "<group>"; };
BC9CFA9223BDE8B700917EEF /* IOStreamView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOStreamView.swift; sourceTree = "<group>"; };
BC9F9C7726F8C16600B01ED0 /* Choreographer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Choreographer.swift; sourceTree = "<group>"; };
BCA7C24E2A91AA0500882D85 /* IORecorderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IORecorderTests.swift; sourceTree = "<group>"; };
BCA7C24E2A91AA0500882D85 /* IOStreamRecorderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOStreamRecorderTests.swift; sourceTree = "<group>"; };
BCAD0C16263ED67F00ADFB80 /* [email protected] */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "[email protected]"; sourceTree = "<group>"; };
BCAD0C17263ED67F00ADFB80 /* SampleVideo_360x240_5mb@m4v */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "SampleVideo_360x240_5mb@m4v"; sourceTree = "<group>"; };
BCB976DE26107B5600C9A649 /* TSField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSField.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -676,6 +677,7 @@
BCD63AB626FDF1250084842D /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
BCD63AB826FDF12A0084842D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
BCD63ABB26FDF12A0084842D /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
BCD8702A2BC266CD009E495B /* IOStreamObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOStreamObserver.swift; sourceTree = "<group>"; };
BCD91C0C2A700FF50033F9E1 /* IOAudioRingBufferTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOAudioRingBufferTests.swift; sourceTree = "<group>"; };
BCE0E33B2AD369410082C16F /* NetStreamSwitcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetStreamSwitcher.swift; sourceTree = "<group>"; };
BCFB355324FA275600DC5108 /* PlaybackViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackViewController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1092,6 +1094,7 @@
BC4078C32AD5CC7E00BBB4FA /* IOMuxer.swift */,
29AF3FCE1D7C744C00E41212 /* IOStream.swift */,
BC6692F22AC2F717009EC058 /* IOStreamBitRateStrategyConvertible.swift */,
BCD8702A2BC266CD009E495B /* IOStreamObserver.swift */,
2976A47D1D48C5C700B53EF2 /* IOStreamRecorder.swift */,
BC9CFA9223BDE8B700917EEF /* IOStreamView.swift */,
BCC4F4142AD6FC1100954EF5 /* IOTellyUnit.swift */,
Expand Down Expand Up @@ -1208,7 +1211,7 @@
BC3802182AB6AD79001AE399 /* IOAudioResamplerTests.swift */,
BCD91C0C2A700FF50033F9E1 /* IOAudioRingBufferTests.swift */,
BC0BF4F429866FDE00D72CB4 /* IOMixerTests.swift */,
BCA7C24E2A91AA0500882D85 /* IORecorderTests.swift */,
BCA7C24E2A91AA0500882D85 /* IOStreamRecorderTests.swift */,
);
path = IO;
sourceTree = "<group>";
Expand Down Expand Up @@ -1835,6 +1838,7 @@
BC1DC4A429F4F74F00E928ED /* AVCaptureSession+Extension.swift in Sources */,
29EA87D81E79A0090043A5F8 /* URL+Extension.swift in Sources */,
BC9F9C7826F8C16600B01ED0 /* Choreographer.swift in Sources */,
BCD8702B2BC266CD009E495B /* IOStreamObserver.swift in Sources */,
BC93792F2ADD76BE001097DB /* AVAudioCompressedBuffer+Extension.swift in Sources */,
29B876BC1CD70B3900FC07DA /* ByteArray.swift in Sources */,
29B876831CD70AE800FC07DA /* AudioSpecificConfig.swift in Sources */,
Expand Down Expand Up @@ -1904,7 +1908,7 @@
BC3802192AB6AD79001AE399 /* IOAudioResamplerTests.swift in Sources */,
BC1DC5042A02894D00E928ED /* FLVVideoFourCCTests.swift in Sources */,
BC1DC5122A04E46E00E928ED /* HEVCDecoderConfigurationRecordTests.swift in Sources */,
BCA7C24F2A91AA0500882D85 /* IORecorderTests.swift in Sources */,
BCA7C24F2A91AA0500882D85 /* IOStreamRecorderTests.swift in Sources */,
BCD91C0D2A700FF50033F9E1 /* IOAudioRingBufferTests.swift in Sources */,
2976077F20A89FBB00DCF24F /* RTMPMessageTests.swift in Sources */,
BC7C56C729A7701F00C41A9B /* ESSpecificDataTests.swift in Sources */,
Expand Down
18 changes: 0 additions & 18 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,6 @@
"revision" : "6a7cbf54553936103084ed72cfb6d6f836758229",
"version" : "2.4.1"
}
},
{
"identity" : "swift-docc-plugin",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-docc-plugin",
"state" : {
"revision" : "26ac5758409154cc448d7ab82389c520fa8a8247",
"version" : "1.3.0"
}
},
{
"identity" : "swift-docc-symbolkit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-docc-symbolkit",
"state" : {
"revision" : "b45d1f2ed151d057b54504d653e0da5552844e34",
"version" : "1.0.0"
}
}
],
"version" : 2
Expand Down
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ stream.attachCamera(front, channel: 0) { videoUnit, error in

### 🔊 [AudioCodecSettings](https://shogo4405.github.io/HaishinKit.swift/Structs/AudioCodecSettings.html)
When you specify the sampling rate, it will perform resampling. Additionally, in the case of multiple channels, downsampling can be applied.
```
```swift
stream.audioSettings = AudioCodecSettings(
bitRate: Int = 64 * 1000,
sampleRate: Float64 = 0,
Expand All @@ -293,7 +293,7 @@ stream.audioSettings = AudioCodecSettings(
```

### 🎥 [VideoCodecSettings](https://shogo4405.github.io/HaishinKit.swift/Structs/VideoCodecSettings.html)
```
```swift
stream.videoSettings = VideoCodecSettings(
videoSize: .init(width: 854, height: 480),
profileLevel: kVTProfileLevel_H264_Baseline_3_1 as String,
Expand All @@ -307,9 +307,12 @@ stream.videoSettings = VideoCodecSettings(
```

### ⏺️ Recording
```
```swift
// Specifies the recording settings. 0" means the same of input.
stream.startRecording(self, settings: [
var recorder = IOStreamRecorder()
stream.addObserver(recorder)

recorder.outputSettings = [
AVMediaType.audio: [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 0,
Expand All @@ -328,7 +331,10 @@ stream.startRecording(self, settings: [
]
*/
]
])
]

recorder.startRunning()

```

## 📜 License
Expand Down
3 changes: 0 additions & 3 deletions Sources/IO/IOMixer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ final class IOMixer {

private(set) var isRunning: Atomic<Bool> = .init(false)

private(set) lazy var recorder = IOStreamRecorder()

private(set) lazy var audioIO = {
var audioIO = IOAudioUnit()
audioIO.mixer = self
Expand Down Expand Up @@ -187,7 +185,6 @@ extension IOMixer: IOAudioUnitDelegate {

func audioUnit(_ audioUnit: IOAudioUnit, didOutput audioBuffer: AVAudioPCMBuffer, when: AVAudioTime) {
delegate?.mixer(self, didOutput: audioBuffer, when: when)
recorder.append(audioBuffer, when: when)
}
}

Expand Down
54 changes: 27 additions & 27 deletions Sources/IO/IOStream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ import UIKit

/// The interface an IOStream uses to inform its delegate.
public protocol IOStreamDelegate: AnyObject {
/// Tells the receiver to an audio packet incoming.
func stream(_ stream: IOStream, didOutput audio: AVAudioBuffer, when: AVAudioTime)
/// Tells the receiver to a video incoming.
func stream(_ stream: IOStream, didOutput video: CMSampleBuffer)
/// Tells the receiver to video error occured.
func stream(_ stream: IOStream, videoErrorOccurred error: IOVideoUnitError)
/// Tells the receiver to audio error occured.
Expand Down Expand Up @@ -45,19 +41,19 @@ open class IOStream: NSObject {
return lhs.rawValue == rhs.rawValue
}

/// NetStream has been created.
/// IOStream has been created.
case initialized
/// NetStream waiting for new method.
/// IOStream waiting for new method.
case open
/// NetStream play() has been called.
/// IOStream play() has been called.
case play
/// NetStream play and server was accepted as playing
/// IOStream play and server was accepted as playing
case playing
/// NetStream publish() has been called
/// IOStream publish() has been called
case publish
/// NetStream publish and server accpted as publising.
/// IOStream publish and server accpted as publising.
case publishing(muxer: any IOMuxer)
/// NetStream close() has been called.
/// IOStream close() has been called.
case closed

var rawValue: UInt8 {
Expand Down Expand Up @@ -139,7 +135,7 @@ open class IOStream: NSObject {
#if os(iOS) || os(tvOS)
/// Specifies the AVCaptureMultiCamSession enabled.
/// Warning: If there is a possibility of using multiple cameras, please set it to true initially.
@available(tvOS 17.0, iOS 13.0, *)
@available(tvOS 17.0, *)
public var isMultiCamSessionEnabled: Bool {
get {
return mixer.session.isMultiCamSessionEnabled
Expand Down Expand Up @@ -236,11 +232,6 @@ open class IOStream: NSObject {
return mixer.audioIO.inputFormat
}

/// The isRecording value that indicates whether the recorder is recording.
public var isRecording: Bool {
return mixer.recorder.isRunning.value
}

/// Specifies the controls sound.
public var soundTransform: SoundTransform {
get {
Expand Down Expand Up @@ -305,6 +296,8 @@ open class IOStream: NSObject {
return telly
}()

private var observers: [any IOStreamObserver] = []

/// Creates a NetStream object.
override public init() {
super.init()
Expand All @@ -314,6 +307,10 @@ open class IOStream: NSObject {
#endif
}

deinit {
observers.removeAll()
}

/// Attaches the camera object.
@available(tvOS 17.0, *)
public func attachCamera(_ device: AVCaptureDevice?, channel: UInt8 = 0, configuration: IOVideoCaptureConfigurationBlock? = nil) {
Expand Down Expand Up @@ -388,16 +385,19 @@ open class IOStream: NSObject {
}
}

/// Starts recording.
public func startRecording(_ delegate: any IOStreamRecorderDelegate, settings: [AVMediaType: [String: Any]] = IOStreamRecorder.defaultOutputSettings) {
mixer.recorder.delegate = delegate
mixer.recorder.outputSettings = settings
mixer.recorder.startRunning()
/// Adds an observer.
public func addObserver(_ observer: any IOStreamObserver) {
guard observers.firstIndex(where: { $0 === observer }) == nil else {
return
}
observers.append(observer)
}

/// Stop recording.
public func stopRecording() {
mixer.recorder.stopRunning()
/// Removes an observer.
public func removeObserver(_ observer: any IOStreamObserver) {
if let index = observers.firstIndex(where: { $0 === observer }) {
observers.remove(at: index)
}
}

/// A handler that receives stream readyState will update.
Expand Down Expand Up @@ -450,11 +450,11 @@ open class IOStream: NSObject {
extension IOStream: IOMixerDelegate {
// MARK: IOMixerDelegate
func mixer(_ mixer: IOMixer, didOutput video: CMSampleBuffer) {
delegate?.stream(self, didOutput: video)
observers.forEach { $0.stream(self, didOutput: video) }
}

func mixer(_ mixer: IOMixer, didOutput audio: AVAudioPCMBuffer, when: AVAudioTime) {
delegate?.stream(self, didOutput: audio, when: when)
observers.forEach { $0.stream(self, didOutput: audio, when: when) }
}

func mixer(_ mixer: IOMixer, audioErrorOccurred error: IOAudioUnitError) {
Expand Down
11 changes: 11 additions & 0 deletions Sources/IO/IOStreamObserver.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import AVFoundation
import CoreMedia
import Foundation

/// A delegate protocol your app implements to receive capture stream output events.
public protocol IOStreamObserver: AnyObject {
/// Tells the receiver to an audio packet incoming.
func stream(_ stream: IOStream, didOutput audio: AVAudioBuffer, when: AVAudioTime)
/// Tells the receiver to a video incoming.
func stream(_ stream: IOStream, didOutput video: CMSampleBuffer)
}
Loading
Loading