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

adding g711 codec #20

Merged
merged 5 commits into from
May 25, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1540"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "RootEncoderTests"
BuildableName = "RootEncoderTests"
BlueprintName = "RootEncoderTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "044138302501A02300732969"
BuildableName = "app.app"
BlueprintName = "app"
ReferencedContainer = "container:../app.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "044138302501A02300732969"
BuildableName = "app.app"
BlueprintName = "app"
ReferencedContainer = "container:../app.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
101 changes: 84 additions & 17 deletions RootEncoder/Sources/RootEncoder/encoder/audio/AudioEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,29 @@ public class AudioEncoder {
private var initTs: Int64 = 0
private let thread = DispatchQueue(label: "AudioEncoder")
private let syncQueue = SynchronizedQueue<AVAudioPCMBuffer>(label: "AudioEncodeQueue", size: 60)
public var codec = AudioCodec.AAC
private var codec = AudioCodec.AAC

public init(callback: GetAacData) {
self.callback = callback
}

public func setCodec(codec: AudioCodec) {
self.codec = codec
}

public func prepareAudio(inputFormat: AVAudioFormat, sampleRate: Double, channels: UInt32, bitrate: Int) -> Bool {
guard let outputFormat = getAACFormat(sampleRate: sampleRate, channels: channels) else {
if (codec == AudioCodec.G711 && (sampleRate != 8000 || channels != 1)) {
print("G711 only support samplerate 8000 and mono channel")
return false
}
let format: AVAudioFormat? = if codec == AudioCodec.AAC {
getAACFormat(sampleRate: sampleRate, channels: channels)
} else if codec == AudioCodec.G711 {
getG711AFormat(sampleRate: sampleRate, channels: channels)
} else {
nil
}
guard let outputFormat = format else {
return false
}
guard let converter = AVAudioConverter(from: inputFormat, to: outputFormat) else {
Expand Down Expand Up @@ -52,30 +67,51 @@ public class AudioEncoder {
let b = self.syncQueue.dequeue()
if let b = b {
var error: NSError? = nil
guard let aacBuffer = self.convertToAAC(buffer: b, error: &error) else {
continue
}
if error != nil {
print("Encode error: \(error.debugDescription)")
} else {
let data = Array<UInt8>(UnsafeBufferPointer<UInt8>(start: aacBuffer.data.assumingMemoryBound(to: UInt8.self), count: Int(aacBuffer.byteLength)))
guard let packetDescriptions = aacBuffer.packetDescriptions else {
if self.codec == AudioCodec.AAC {
guard let aacBuffer = self.convertToAAC(buffer: b, error: &error) else {
continue
}
if error != nil {
print("Encode error: \(error.debugDescription)")
} else {
let data = Array<UInt8>(UnsafeBufferPointer<UInt8>(start: aacBuffer.data.assumingMemoryBound(to: UInt8.self), count: Int(aacBuffer.byteLength)))
guard let packetDescriptions = aacBuffer.packetDescriptions else {
continue
}
for i in 0..<aacBuffer.packetCount {
let info = packetDescriptions[Int(i)]
var mBuffer = Array<UInt8>(repeating: 0, count: Int(info.mDataByteSize))
mBuffer[0...mBuffer.count - 1] = data[Int(info.mStartOffset)...Int(info.mStartOffset) + Int(info.mDataByteSize - 1)]
let end = Date().millisecondsSince1970
let elapsedNanoSeconds = (end - self.initTs) * 1000

var frame = Frame()
frame.buffer = mBuffer
frame.length = UInt32(mBuffer.count)
frame.timeStamp = UInt64(elapsedNanoSeconds)
self.callback?.getAacData(frame: frame)
}
}
} else if self.codec == AudioCodec.G711 {
guard let g711Buffer = self.convertToG711(buffer: b, error: &error) else {
continue
}
for i in 0..<aacBuffer.packetCount {
let info = packetDescriptions[Int(i)]
var mBuffer = Array<UInt8>(repeating: 0, count: Int(info.mDataByteSize))
mBuffer[0...mBuffer.count - 1] = data[Int(info.mStartOffset)...Int(info.mStartOffset) + Int(info.mDataByteSize - 1)]
if error != nil {
print("Encode error: \(error.debugDescription)")
} else {
let data = g711Buffer.audioBufferToBytes()
let end = Date().millisecondsSince1970
let elapsedNanoSeconds = (end - self.initTs) * 1000

var frame = Frame()
frame.buffer = mBuffer
frame.length = UInt32(mBuffer.count)
frame.buffer = data
frame.length = UInt32(data.count)
frame.timeStamp = UInt64(elapsedNanoSeconds)
self.callback?.getAacData(frame: frame)

}
}

}
}
}
Expand All @@ -97,6 +133,16 @@ public class AudioEncoder {
return outBuffer
}

private func convertToG711(buffer: AVAudioPCMBuffer, error: inout NSError?) -> AVAudioPCMBuffer? {
guard let outputFormat = outputFormat else {
return nil
}
let outBuffer = AVAudioPCMBuffer(pcmFormat: outputFormat, frameCapacity: AVAudioFrameCount(outputFormat.sampleRate) * buffer.frameLength / AVAudioFrameCount(buffer.format.sampleRate))!
outBuffer.frameLength = outBuffer.frameCapacity
self.convert(sourceBuffer: buffer, destinationBuffer: outBuffer, error: &error)
return outBuffer
}

private func convert(sourceBuffer: AVAudioPCMBuffer, destinationBuffer: AVAudioBuffer, error: NSErrorPointer) {
if (running) {
sourceBuffer.frameLength = sourceBuffer.frameCapacity
Expand All @@ -118,7 +164,28 @@ public class AudioEncoder {
}

private func getAACFormat(sampleRate: Double, channels: UInt32) -> AVAudioFormat? {
var description = AudioStreamBasicDescription(mSampleRate: sampleRate, mFormatID: kAudioFormatMPEG4AAC, mFormatFlags: 0, mBytesPerPacket: 0, mFramesPerPacket: 0, mBytesPerFrame: 0, mChannelsPerFrame: channels, mBitsPerChannel: 0, mReserved: 0)
var description = AudioStreamBasicDescription(mSampleRate: sampleRate,
mFormatID: kAudioFormatMPEG4AAC,
mFormatFlags: 0,
mBytesPerPacket: 0,
mFramesPerPacket: 0,
mBytesPerFrame: 0,
mChannelsPerFrame: channels,
mBitsPerChannel: 0,
mReserved: 0)
return AVAudioFormat(streamDescription: &description)
}

private func getG711AFormat(sampleRate: Double, channels: UInt32) -> AVAudioFormat? {
var description = AudioStreamBasicDescription(mSampleRate: sampleRate,
mFormatID: kAudioFormatALaw,
mFormatFlags: AudioFormatFlags(kAudioFormatALaw),
mBytesPerPacket: 1,
mFramesPerPacket: 1,
mBytesPerFrame: 1,
mChannelsPerFrame: channels,
mBitsPerChannel: 8,
mReserved: 0)
return AVAudioFormat(streamDescription: &description)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import AVFoundation

public class MicrophoneManager {

private let thread = DispatchQueue.global()
private let thread = DispatchQueue(label: "MicrophoneManager")
private let audioEngine = AVAudioEngine()
private var inputNode: AVAudioInputNode?
private var inputFormat: AVAudioFormat?
Expand Down
13 changes: 13 additions & 0 deletions RootEncoder/Sources/RootEncoder/encoder/utils/EncoderUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ public extension AVAudioPCMBuffer {
)
return sampleBuffer
}

func audioBufferToBytes() -> [UInt8] {
let srcLeft = self.audioBufferList.pointee.mBuffers.mData!
let bytesPerFrame = self.format.streamDescription.pointee.mBytesPerFrame
let numBytes = Int(bytesPerFrame * self.frameLength)
var audioByteArray = [UInt8](repeating: 0, count: numBytes)
srcLeft.withMemoryRebound(to: UInt8.self, capacity: numBytes) { srcByteData in
audioByteArray.withUnsafeMutableBufferPointer {
$0.baseAddress!.initialize(from: srcByteData, count: numBytes)
}
}
return audioByteArray
}
}

extension AVAudioTime {
Expand Down
10 changes: 7 additions & 3 deletions RootEncoder/Sources/RootEncoder/encoder/video/VideoEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ public class VideoEncoder {
codecType: codec.value, encoderSpecification: nil, imageBufferAttributes: nil,
compressedDataAllocator: nil, outputCallback: videoCallback, refcon: Unmanaged.passUnretained(self).toOpaque(),
compressionSessionOut: &session)
print("using codec \(codec)")
self.resolution = resolution
self.fps = fps
self.bitrate = bitrate
Expand Down Expand Up @@ -100,8 +99,13 @@ public class VideoEncoder {
}
}

public func setCodec(codec: CodecUtil) {
self.codec = codec
public func setCodec(codec: VideoCodec) {
switch codec {
case .H264:
self.codec = CodecUtil.H264
case .H265:
self.codec = CodecUtil.H265
}
}

public func forceKeyFrame() {
Expand Down
12 changes: 2 additions & 10 deletions RootEncoder/Sources/RootEncoder/library/base/CameraBase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -164,21 +164,13 @@ public class CameraBase: GetMicrophoneData, GetCameraData, GetAacData, GetH264Da
public func setVideoCodec(codec: VideoCodec) {
setVideoCodecImp(codec: codec)
recordController.setVideoCodec(codec: codec)
let type = switch codec {
case .H264:
CodecUtil.H264
case .H265:
CodecUtil.H265
@unknown default:
CodecUtil.H264
}
videoEncoder.setCodec(codec: type)
videoEncoder.setCodec(codec: codec)
}

public func setAudioCodec(codec: AudioCodec) {
setAudioCodecImp(codec: codec)
recordController.setAudioCodec(codec: codec)
audioEncoder.codec = codec
audioEncoder.setCodec(codec: codec)
}

public func setVideoCodecImp(codec: VideoCodec) {}
Expand Down
12 changes: 2 additions & 10 deletions RootEncoder/Sources/RootEncoder/library/base/DisplayBase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,21 +148,13 @@ public class DisplayBase: GetMicrophoneData, GetCameraData, GetAacData, GetH264D
public func setVideoCodec(codec: VideoCodec) {
setVideoCodecImp(codec: codec)
recordController.setVideoCodec(codec: codec)
let type = switch codec {
case .H264:
CodecUtil.H264
case .H265:
CodecUtil.H265
@unknown default:
CodecUtil.H264
}
videoEncoder.setCodec(codec: type)
videoEncoder.setCodec(codec: codec)
}

public func setAudioCodec(codec: AudioCodec) {
setAudioCodecImp(codec: codec)
recordController.setAudioCodec(codec: codec)
audioEncoder.codec = codec
audioEncoder.setCodec(codec: codec)
}

public func setVideoCodecImp(codec: VideoCodec) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@ public class DisplayRtmp: DisplayBase {
}

public func setAuth(user: String, password: String) {
//client.setAuth(user: user, password: password)
}

public func setCodec(codec: CodecUtil) {
videoEncoder.setCodec(codec: codec)
client.setAuth(user: user, password: password)
}

public func reTry(delay: Int, reason: String, backUrl: String? = nil) -> Bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ public class DisplayRtsp: DisplayBase {
client.setAuth(user: user, password: password)
}

public func setCodec(codec: CodecUtil) {
videoEncoder.setCodec(codec: codec)
}

public func reTry(delay: Int, reason: String, backUrl: String? = nil) -> Bool {
let result = client.shouldRetry(reason: reason)
if (result) {
Expand Down
4 changes: 0 additions & 4 deletions RootEncoder/Sources/RootEncoder/library/rtsp/RtspCamera.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@ public class RtspCamera: CameraBase {
client.setAuth(user: user, password: password)
}

public func setCodec(codec: CodecUtil) {
videoEncoder.setCodec(codec: codec)
}

public func reTry(delay: Int, reason: String, backUrl: String? = nil) -> Bool {
let result = client.shouldRetry(reason: reason)
if (result) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,6 @@ public class RecordController {
AVVideoCodecType.h264
case VideoCodec.H265:
AVVideoCodecType.hevc
default:
AVVideoCodecType.h264 //TODO throw error
}
}

Expand All @@ -157,8 +155,6 @@ public class RecordController {
kAudioFormatMPEG4AAC
case AudioCodec.G711:
kAudioFormatALaw
default:
kAudioFormatLinearPCM // TODO throw error
}
}

Expand Down
4 changes: 2 additions & 2 deletions RootEncoder/Sources/RootEncoder/rtmp/rtmp/RtmpClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public class RtmpClient {
}
if (!self.isStreaming || isRetry) {
self.isStreaming = true
thread = Task {
thread = Task(priority: .high) {
guard let url = url else {
self.connectChecker.onConnectionFailed(reason: "Endpoint malformed, should be: rtmp://ip:port/appname/streamname")
return
Expand Down Expand Up @@ -292,7 +292,7 @@ public class RtmpClient {
connectChecker.onAuthError()
}
default:
connectChecker.onConnectionFailed(reason: description)
connectChecker.onConnectionFailed(reason: description)
}
case "onStatus":
let code = ((command.data[3] as! AmfObject).getProperty(name: "code") as! AmfString).value
Expand Down