Skip to content
Open

Av #4

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
75f48e1
Separated debug logging to scopes
May 22, 2017
b4a5c10
Completed audio recording in AAC format and playback to speakers
May 23, 2017
b27a179
AAC buffer serialization/deserialization
May 24, 2017
f6e2abe
Video encoding and serializing to H264 completed
May 24, 2017
6966e3d
Video completed on Mac
May 25, 2017
c2ad70e
AV refactoring to prepare for networking
May 26, 2017
a23ce44
Audio/Video looping throw the server
May 26, 2017
829b4b8
fixed iOS compilation
May 29, 2017
03c654c
iOS video capture and preview
May 29, 2017
f49d675
Small project reorganization and classes naming
May 29, 2017
07154c2
Now interclient communication works
May 29, 2017
2238036
Project layout. Video dimension.
May 30, 2017
93beddc
Video session completed
May 31, 2017
27ef934
h264: switched auto level profile (external camera problem)
Jun 1, 2017
b9ef8ad
Audio sessions completed
Jun 1, 2017
806f3fb
Some settings for mac app
Jun 2, 2017
97f762b
Fixed UI for iPhone
Jun 4, 2017
cd49b97
Integrated Crashlitics
Jun 4, 2017
da14276
Hardcoded low res video capture for iOS
Jun 4, 2017
3dbf9ef
iOS 10 support
Jun 5, 2017
423f1a6
Fixed server stops responding after adding contact.
Jun 5, 2017
eaaad75
Video rotation on iOS
Jun 5, 2017
96f8fc1
Stability, AV session ID, place for audio/video sync
Jun 6, 2017
898d621
Integrated Crashlitics for OSX
Jun 6, 2017
13445ac
Added group ID for audio/video session
Jun 7, 2017
cae0344
Audio/Video sync implemented
Jun 7, 2017
b44f7ef
fixed problem with very slow video compression.
Jun 9, 2017
5877907
Various IO fixes
Jun 12, 2017
d18773a
Now AV works fast and smooth; playback utils added; AV sync disabled
Jun 19, 2017
5133ee0
Custom audio/video packets time info.
Jun 20, 2017
db102ba
AV Sync tuning. Small fixes. Video appearance
Jun 20, 2017
00e8f49
IO sync fix + Logs to file
Jun 21, 2017
0806d44
Call common functionlaity + osx ui
Jun 23, 2017
7e1bc92
Small logging improvements
Jun 23, 2017
9ca780a
Small fixes
Jun 26, 2017
c9ac364
Diagrams: call functionality
Jun 26, 2017
88b55cf
fixed crash
Jun 27, 2017
c5cc15f
Call functionality (both iOS and Mac)
Jun 28, 2017
f439b39
[server] safe access to shared collection
Jun 28, 2017
b14840b
Fixed disconnecting bug
Jun 28, 2017
01931eb
safe access to shared collections
Jun 28, 2017
4c7cc34
Audio decoder restart if error occured
Jun 28, 2017
304ca28
“call” diagram
Jun 28, 2017
e49a608
video quality (dimension) varying dependant on connection
Jul 3, 2017
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
372 changes: 372 additions & 0 deletions apple/Common/AV.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,372 @@

import AVFoundation

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Queue
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

func assert_audio_capture_queue() {
assert(DispatchQueue.OnQueue(AV.shared.audioCaptureQueue))
}

func assert_video_capture_queue() {
assert(DispatchQueue.OnQueue(AV.shared.videoCaptureQueue))
}

func assert_av_output_queue() {
assert(DispatchQueue.OnQueue(AV.shared.avOutputQueue))
}

func dispatch_sync_av_output(_ block: FuncVV) {
if DispatchQueue.OnQueue(AV.shared.avOutputQueue) {
block()
}
else {
AV.shared.avOutputQueue.sync { block() }
}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// AV
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class AV {

static let shared = AV()
static let defaultAudioFormatID = kAudioFormatMPEG4AAC
static let defaultAudioInterval = 0.1

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// IO
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

let audioCaptureQueue = DispatchQueue.CreateCheckable("chat.AudioCaptureQueue")
let videoCaptureQueue = DispatchQueue.CreateCheckable("chat.VideoCaptureQueue")
let avOutputQueue = DispatchQueue.CreateCheckable("chat.AVOutputQueue")

init() {
defaultVideoDimension = defaultVideoInputDevice?.activeFormat.dimensions
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Input
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

var defaultVideoInputDevice: AVCaptureDevice? {
return AVCaptureDevice.chatVideoDevice()
}

var defaultVideoDimension: CMVideoDimensions?

var defaultVideoInputFormat: AVCaptureDeviceFormat? {
get {
guard let dimensions = defaultVideoDimension else { return nil }
return defaultVideoInputDevice?.inputFormat(width: dimensions.width)
}
}

var defaultAudioInputFormat: AudioStreamBasicDescription? {
guard let inputFormat = AVAudioEngine().inputNode?.inputFormat(forBus: AudioBus.input) else { return nil }

return AudioStreamBasicDescription.CreateVBR(AV.defaultAudioFormatID,
inputFormat.sampleRate/*8000*/,
1/*inputFormat.channelCount*/)
}

private func _defaultNetworkVideoInput(_ id: IOID,
_ context: IOInputContext,
_ rotated: Bool,
_ info: inout NetworkVideoSessionInfo?,
_ session: inout AVCaptureSession.Accessor?,
_ x: inout [VideoSessionProtocol]) {
guard let device = AVCaptureDevice.chatVideoDevice() else { return }
guard var outFormat = defaultVideoOutputFormat else { return }
guard let inpFormat = defaultVideoInputFormat else { return }

if rotated {
outFormat.rotate()
}

let serializer =
NetworkH264Serializer(
NetworkOutputVideo(id, context.qos, context.balancer))

let sessionEncoder =
VideoEncoderSessionH264(
inpFormat.dimensions,
outFormat,
serializer)

let videoInput =
VideoInput(
device,
AV.shared.videoCaptureQueue,
inpFormat,
sessionEncoder)

let videoInputQoS
= VideoInputQoS(inpFormat,
VideoSessionBroadcast([videoInput, sessionEncoder]))

let qos =
IOQoSDispatcher(
videoCaptureQueue,
IOQoSBroadcast([videoInputQoS, serializer]))

context.qos.add(qos)
info = NetworkVideoSessionInfo(id, factory(outFormat))
session = videoInput.sessionAccessor
x.append(VideoSessionBroadcast([sessionEncoder, videoInput]))
}

func defaultNetworkVideoInput(_ id: IOID,
_ context: IOInputContext,
_ rotated: Bool,
_ info: inout NetworkVideoSessionInfo?,
_ session: inout AVCaptureSession.Accessor?) -> VideoSessionProtocol? {
var x = [VideoSessionProtocol]()

_defaultNetworkVideoInput(id, context, rotated, &info, &session, &x)
return broadcast(x)
}

func defaultNetworkVideoInput(_ id: IOID,
_ context: IOInputContext,
_ preview: AVCaptureVideoPreviewLayer,
_ info: inout NetworkVideoSessionInfo?) -> VideoSessionProtocol? {
var x = [VideoSessionProtocol]()
var y: AVCaptureSession.Accessor?

_defaultNetworkVideoInput(id, context, false, &info, &y, &x)

if y != nil {
x.append(VideoPreview(preview, y!))
}

return broadcast(x)
}

private func _defaultNetworkAudioInput(_ id: IOID,
_ context: IOInputContext,
_ x: inout [IOSessionProtocol],
_ formatOut: inout AudioFormat.Factory?) {

guard let format = defaultAudioInputFormat else { return }

let serializer =
NetworkAudioSerializer(
NetworkOutputAudio(id, context.qos, context.balancer))

let input =
AudioInput(
format,
AV.defaultAudioInterval,
serializer)

x.append(
IOSessionSyncDispatcher(
audioCaptureQueue,
input))

let qos =
IOQoSDispatcher(
audioCaptureQueue,
serializer)

context.qos.add(qos)
formatOut = input.format
}

func defaultNetworkAudioInput(_ id: IOID,
_ context: IOInputContext,
_ info: inout NetworkAudioSessionInfo?) -> IOSessionProtocol? {
var x = [IOSessionProtocol]()
var format: AudioFormat.Factory?

_defaultNetworkAudioInput(id, context, &x, &format)

if format != nil {
info = NetworkAudioSessionInfo(id, format!)
}
return broadcast(x)
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Output
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

var defaultVideoOutputFormat: VideoFormat? {
get {
guard let format = defaultVideoInputFormat else { return nil }
return VideoFormat(format)
}
}

func defaultNetworkVideoOutput(_ id: IOID,
_ context: IOOutputContext,
_ output: VideoOutputProtocol,
_ session: IOSessionProtocol? = nil) -> IOOutputContext {

let time =
VideoTimeSerializer(NetworkDeserializer.timeIndex)

let balancedOutput =
IOBalancedDataSession(
NetworkH264Deserializer(
// VideoDecoderH264(
output))

let sheduler =
IOSheduler(
IOKind.Video,
balancedOutput)

let balancer =
IODataAdapter4Balancer(
time,
context.balancer!,
sheduler)

let resultSession =
IODataSession(
IOTimebaseReset(
context.timebase!,
time,
balancer))

let result =
IODataAsyncDispatcher(
avOutputQueue,
resultSession)

return IOOutputContext(id,
broadcast([sheduler, resultSession, balancedOutput, session])!,
result,
context)
}

func defaultNetworkVideoOutput(_ id: IOID,
_ context: IOOutputContext,
_ layer: AVSampleBufferDisplayLayer,
_ session: IOSessionProtocol? = nil) -> IOOutputContext {
let output = VideoOutput(layer)

return AV.shared.defaultNetworkVideoOutput(id,
context,
output,
output)
}

func defaultNetworkAudioOutput(_ id: IOID,
_ format: AudioFormat,
_ context: IOOutputContext,
_ session: IOSessionProtocol? = nil) -> IOOutputContext {
let time =
AudioTimeUpdater(NetworkDeserializer.timeIndex)

let output =
AudioOutput(
factory(format),
avOutputQueue)

let decoder =
AudioDecoder(
factory(format),
output.format,
output)

let balancedOutput =
IOBalancedDataSession(
IOBalanceSubdataSkip(
NetworkAudioDeserializer(
decoder)))

let sheduler =
IOSheduler(
IOKind.Audio,
balancedOutput)

let balancer =
IODataAdapter4Balancer(
time,
context.balancer!,
sheduler)

let resultSession =
IODataSession(
IOTimebaseReset(
context.timebase!,
time,
balancer))

let result =
IODataAsyncDispatcher(
avOutputQueue,
resultSession)

return IOOutputContext(id,
broadcast([sheduler, resultSession, output, balancedOutput, decoder, session])!,
result,
context)
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Playback
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

var defaultAudioInputUncompressedFormat: AudioStreamBasicDescription? {
guard let inputFormat = AVAudioEngine().inputNode?.inputFormat(forBus: AudioBus.input) else { return nil }
var format = inputFormat.streamDescription.pointee

format.mChannelsPerFrame = 1
return format
}

func audioUncompressedPlayback() throws -> IOSessionProtocol? {
guard let format = defaultAudioInputUncompressedFormat else { return nil }

let input =
AudioInput(
format,
AV.defaultAudioInterval)

let output =
AudioOutput(
input.format,
AV.shared.audioCaptureQueue)

input.output =
NetworkAudioSerializer(
NetworkAudioDeserializer(
output))

return broadcast([output, input])
}

func audioCompressedPlayback() throws -> IOSessionProtocol? {
guard let format = defaultAudioInputFormat else { return nil }

let input =
AudioInput(
format,
AV.defaultAudioInterval)

let output =
AudioOutput(
input.format,
AV.shared.audioCaptureQueue)

let decoder =
AudioDecoder(
input.format,
output.format,
output)

input.output =
NetworkAudioSerializer(
NetworkAudioDeserializer(
decoder))

return broadcast([input, output, decoder])
}
}
Loading