Author : MD TAREQ HASSAN | Updated : 2020/09/29
Inspired by
https://github.com/robertveringa89/OpusKit
Pod dependency
pod 'opus-ios', :git => 'https://github.com/chrisballinger/Opus-iOS.git'
object-c bridge header
https://github.com/robertveringa89/OpusKit/blob/master/OpusKit/OpusKit.h
OpuslibKit
OpuslibKit.swift
import Foundation
import AVFoundation
import opus
//
// Swift bridge for libopus
//
public class OpuslibKit {
//
// Singleton
//
private init() {}
public static let shared = OpuslibKit()
// MARK: - Properties
private var decoder: OpaquePointer!
private var encoder: OpaquePointer!
private var sampleRate: opus_int32 = OpusSetting.default.sampleRate
private var channelCount: Int32 = OpusSetting.default.channelCount
private var frameSize: opus_int32 = OpusSetting.default.frameSize
private var maxDataBytes: opus_int32 = OpusSetting.default.encoderBufferSize
private static let DECODER_BUFFER_SIZE = 2048
// MARK: - INIT
public func initialize(with opusSetting: OpusSetting) {
self.sampleRate = opusSetting.sampleRate
self.channelCount = opusSetting.channelCount
self.frameSize = opusSetting.frameSize
self.maxDataBytes = opusSetting.encoderBufferSize
// Status
var status = Int32(0)
// Decoder
decoder = opus_decoder_create(sampleRate, channelCount, &status)
if (status != OPUS_OK) {
print("OpuslibKit - could not create decoder: \(opusErrorMessage(errorCode: status))")
}
// Encoder
encoder = opus_encoder_create(sampleRate, channelCount, OPUS_APPLICATION_VOIP, &status)
if (status != OPUS_OK) {
print("OpuslibKit - could not create encoder: \(opusErrorMessage(errorCode: status))")
}
}
// MARK: - Decode
public func decode(fromBytes: [UInt8]) -> Data? {
let encodedBytes = fromBytes
var decodedBytes = [opus_int16](repeating: 0, count: OpuslibKit.DECODER_BUFFER_SIZE)
let decodedPacketCountInBytes = opus_decode(self.decoder,
encodedBytes,
(opus_int32)(encodedBytes.count),
&decodedBytes,
self.frameSize,
0)
print("decodedPacketCountInBytes: \(decodedPacketCountInBytes)")
let decodedData: NSMutableData = NSMutableData()
if decodedPacketCountInBytes > 0 {
let decodedDataSize = Int(decodedPacketCountInBytes) * MemoryLayout<opus_int16>.size
decodedData.append(decodedBytes, length: decodedDataSize)
} else {
let code = decodedPacketCountInBytes
Logger.logIt("Libopus Decoding Error - Something went wrong while decoding data: \(opusErrorMessage(errorCode: code))")
}
return decodedData as Data
}
public func decode(fromData: Data) -> Data? {
let encodedBytes = [UInt8](fromData)
return decode(fromBytes: encodedBytes)
}
// MARK: - Encode
public func encode(pcmData: Data) -> Data? {
let pcmBytes = pcmData.toArray(type: opus_int16.self)
var encodedOpus = [UInt8]()
let encodedPacketCountInBytes = opus_encode(self.encoder,
pcmBytes,
self.frameSize,
&encodedOpus,
self.maxDataBytes)
print("encodedPacketCountInBytes: \(encodedPacketCountInBytes)")
if encodedPacketCountInBytes > 0 {
var encodedData = Data()
encodedData.append(encodedOpus, count: Int(encodedPacketCountInBytes))
return encodedData
} else {
let code = encodedPacketCountInBytes
Logger.logIt("Opuslib Encoding Error - Something went wrong while encoding data: \(opusErrorMessage(errorCode: code))")
return nil
}
}
// MARK: - Error message
private func opusErrorMessage(errorCode: Int32) -> String {
switch (errorCode) {
case OPUS_BAD_ARG:
return "One or more invalid/out of range arguments."
case OPUS_BUFFER_TOO_SMALL:
return "The mode struct passed is invalid."
case OPUS_INTERNAL_ERROR:
return "The compressed data passed is corrupted."
case OPUS_INVALID_PACKET:
return "Invalid/unsupported request number."
case OPUS_INVALID_STATE:
return "An encoder or decoder structure is invalid or already freed."
case OPUS_UNIMPLEMENTED:
return "Invalid/unsupported request number."
case OPUS_ALLOC_FAIL:
return "Memory allocation has failed."
default:
return "Unknown error."
}
}
}
OpusSetting
OpusSetting.swift
/**
Opus encoder and decoder setting
This class will be used to initialize before encoding/decoding
*/
public class OpusSetting {
private static let OPUS_ENCODER_BUFFER_SIZE: opus_int32 = 1275 // ref: https://stackoverflow.com/a/55707654/4802664
private static let DIVIDER_1K: opus_int32 = 1000
public static let FRAME_DURATION_DEFAULT: opus_int32 = 20 //ms
public static let SAMPLE_RATE_DEFAULT: opus_int32 = 16_000
public static let CHANNEL_COUNT_DEFAULT: opus_int32 = 1
private static let FRAME_SIZE_DEFAULT: opus_int32 = FRAME_DURATION_DEFAULT * (SAMPLE_RATE_DEFAULT / DIVIDER_1K) * CHANNEL_COUNT_DEFAULT
//
// Default singleton
//
public static let `default` = OpusSetting(sampleRate: SAMPLE_RATE_DEFAULT,
channelCount: CHANNEL_COUNT_DEFAULT)
//
// Frame capture duration
//
public var frameDuration: opus_int32 = FRAME_DURATION_DEFAULT {
willSet {
updateFrameSize()
}
}
//
// Sample rate
//
public var sampleRate: opus_int32 = SAMPLE_RATE_DEFAULT {
willSet {
updateFrameSize()
}
}
//
// Channel count
//
public var channelCount: opus_int32 = CHANNEL_COUNT_DEFAULT {
willSet {
updateFrameSize()
}
}
//
// Set frame size based on other properties
//
public private(set) var frameSize: opus_int32 = FRAME_SIZE_DEFAULT
private func updateFrameSize(){
frameSize = frameDuration * (sampleRate / OpusSetting.DIVIDER_1K) * channelCount
}
//
// Designed initilizer
//
public init(sampleRate: opus_int32, channelCount: opus_int32){
self.sampleRate = sampleRate
self.channelCount = channelCount
self.frameSize = self.frameDuration * (self.sampleRate / OpusSetting.DIVIDER_1K) * self.channelCount
}
//
// Buffer size of encoder
//
public var encoderBufferSize: opus_int32 = OPUS_ENCODER_BUFFER_SIZE
//
// Convinience initilizer
//
public convenience init(sampleRate: opus_int32, channelCount: opus_int32, bufferSize: opus_int32){
self.init(sampleRate: sampleRate, channelCount: channelCount)
self.encoderBufferSize = bufferSize
}
}
Extension of Data class
DataExtension.swift
//
// Extension of Data class
//
extension Data {
func toArray<T>(type: T.Type) -> [T] where T: ExpressibleByIntegerLiteral {
var array = Array<T>(repeating: 0, count: self.count/MemoryLayout<T>.stride)
_ = array.withUnsafeMutableBytes { copyBytes(to: $0) }
return array
}
init<T>(from value: T) {
self = Swift.withUnsafeBytes(of: value) { Data($0) }
}
func to<T>(type: T.Type) -> T? where T: ExpressibleByIntegerLiteral {
var value: T = 0
guard count >= MemoryLayout.size(ofValue: value) else { return nil }
_ = Swift.withUnsafeMutableBytes(of: &value, { copyBytes(to: $0)} )
return value
}
}