Created
May 26, 2020 11:52
-
-
Save aalekz/ca3a22953a519112fbe2edaf3eaf9aa8 to your computer and use it in GitHub Desktop.
OpusHandler encode/decode sample
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// OpusExample.swift | |
// | |
import Foundation | |
import os | |
final class OpusHandler { | |
// swiftlint:disable:next type_name | |
private typealias opus_encoder = OpaquePointer | |
private typealias opus_decoder = OpaquePointer | |
private var encoder: opus_encoder // encoder to convert pcm to opus | |
private var decoder: opus_decoder // decoder to convert oput to pcm | |
private let pcmRate: Int32 = 48000 // pcm sample rate | |
private let pcmChannels: Int32 = 1 // pcm channels | |
private let pcmBytesPerFrame: Int32 = 2 // bytes per frame in the pcm audio | |
private var pcmData: Data = Data() // decoded pcm data | |
private let frameSize: Int32 = 960 // frame size | |
private let maxFrameSize: Int32 = 3840 // maximum size of an opus frame | |
private let opusRate: Int32 = 48000 // desired sample rate of the opus audio | |
init() throws { | |
// status to catch errors when creating encoder | |
var status = Int32(0) | |
encoder = opus_encoder_create(opusRate, pcmChannels, OPUS_APPLICATION_VOIP, &status) | |
guard let error = OpusError(rawValue: status) else { | |
os_log("Failed to create encoder, error: %@", log: .opus, type: .error, String(cString: opus_strerror(status))) | |
throw OpusError.internalError | |
} | |
guard error == .okay else { throw error } | |
// status to catch errors when creating decoder | |
status = Int32(0) | |
decoder = opus_decoder_create(opusRate, pcmChannels, &status) | |
guard let error2 = OpusError(rawValue: status) else { | |
os_log("Failed to create decoder, error: %@", log: .opus, type: .error, String(cString: opus_strerror(status))) | |
throw OpusError.internalError | |
} | |
guard error2 == .okay else { throw error } | |
} | |
deinit { | |
opus_encoder_destroy(encoder) | |
opus_decoder_destroy(decoder) | |
} | |
func encode(pcm: Data) throws -> Data { | |
try pcm.withUnsafeBytes { bytes in | |
let buffer: UnsafePointer<Float> = bytes.baseAddress!.assumingMemoryBound(to: Float.self) | |
return try encode(pcm: buffer, count: pcm.count) | |
} | |
} | |
private func encode(pcm: UnsafePointer<Float>, count: Int) throws -> Data { | |
// construct audio buffers | |
var pcm = UnsafeMutablePointer<Float>(mutating: pcm) | |
var opus = [UInt8](repeating: 0, count: Int(maxFrameSize)) | |
var out = [Float](repeating: 0, count: Int(maxFrameSize)) | |
var count = count | |
// number of total encoded bytes | |
var totalBytesEncoded = 0 | |
var opusData = Data() | |
// encode complete frames | |
while count >= Int(frameSize) * Int(pcmBytesPerFrame) { | |
// encode an opus frame | |
let encodedBytes = opus_encode_float(encoder, pcm, frameSize, &opus, maxFrameSize) | |
if encodedBytes < 0 { | |
os_log("Encoding error: %@", log: .opus, type: .error, String(cString: opus_strerror(encodedBytes))) | |
throw OpusError.internalError | |
} else { | |
os_log("%@ bytes encoded", log: .opus, type: .error, String(describing: encodedBytes)) | |
} | |
opusData.append(contentsOf: opus) | |
// decode opus frame | |
let decodedFrames = opus_decode_float(decoder, opus, encodedBytes, &out, maxFrameSize, 0) | |
if decodedFrames < 0 { | |
os_log("Decoding error: %@", log: .opus, type: .error, String(cString: opus_strerror(decodedFrames))) | |
throw OpusError.internalError | |
} else { | |
os_log("%@ frames decoded", log: .opus, type: .error, String(describing: decodedFrames)) | |
} | |
let decodedOpusData = Data(buffer: UnsafeBufferPointer(start: out, count: out.count)) | |
pcmData.append(decodedOpusData) | |
// advance pcm buffer | |
let bytesEncoded = Int(frameSize) * Int(pcmBytesPerFrame) | |
pcm = pcm.advanced(by: bytesEncoded / MemoryLayout<Float32>.stride) | |
count -= bytesEncoded | |
totalBytesEncoded += bytesEncoded | |
} | |
return pcmData | |
} | |
func decode(opusData: Data) throws -> Data { | |
return try opusData.withUnsafeBytes { bytes in | |
let buffer: UnsafePointer<UInt8> = bytes.baseAddress!.assumingMemoryBound(to: UInt8.self) | |
return try decode(opusData: buffer, count: opusData.count) | |
} | |
} | |
private func decode(opusData: UnsafePointer<UInt8>, count: Int) throws -> Data { | |
// construct audio buffers | |
var opus = UnsafeMutablePointer<UInt8>(mutating: opusData) | |
var out = [Float](repeating: 0, count: Int(maxFrameSize)) | |
var count = count | |
// number of total decoded bytes | |
var totalBytesDecoded = 0 | |
// decode complete frames | |
while count >= Int(frameSize) { | |
let decodedFrames = opus_decode_float(decoder, opus, frameSize, &out, maxFrameSize, 0) | |
if decodedFrames < 0 { | |
os_log("Decoding error: %@", log: .opus, type: .error, String(cString: opus_strerror(decodedFrames))) | |
throw OpusError.internalError | |
} else { | |
os_log("Decoding successful: %@ samples decoded", log: .opus, type: .error, String(describing: decodedFrames)) | |
} | |
// advance opus buffer | |
let bytesDecoded = Int(frameSize) | |
opus = opus.advanced(by: bytesDecoded / MemoryLayout<UInt8>.stride) | |
count -= bytesDecoded | |
totalBytesDecoded += bytesDecoded | |
} | |
return Data(bytes: &opus, count: totalBytesDecoded) | |
} | |
} | |
extension OSLog { | |
private static var subsystem = Bundle.main.bundleIdentifier! | |
static let opus = OSLog(subsystem: subsystem, category: "Opus") | |
} | |
enum OpusError: Error { | |
case okay | |
case badArgument | |
case bufferTooSmall | |
case internalError | |
case invalidPacket | |
case unimplemented | |
case invalidState | |
case allocationFailure | |
var rawValue: Int32 { | |
switch self { | |
case .okay: return OPUS_OK | |
case .badArgument: return OPUS_BAD_ARG | |
case .bufferTooSmall: return OPUS_BUFFER_TOO_SMALL | |
case .internalError: return OPUS_INTERNAL_ERROR | |
case .invalidPacket: return OPUS_INVALID_PACKET | |
case .unimplemented: return OPUS_UNIMPLEMENTED | |
case .invalidState: return OPUS_INVALID_STATE | |
case .allocationFailure: return OPUS_ALLOC_FAIL | |
} | |
} | |
init?(rawValue: Int32) { | |
switch rawValue { | |
case OPUS_OK: self = .okay | |
case OPUS_BAD_ARG: self = .badArgument | |
case OPUS_BUFFER_TOO_SMALL: self = .bufferTooSmall | |
case OPUS_INTERNAL_ERROR: self = .internalError | |
case OPUS_INVALID_PACKET: self = .invalidPacket | |
case OPUS_UNIMPLEMENTED: self = .unimplemented | |
case OPUS_INVALID_STATE: self = .invalidState | |
case OPUS_ALLOC_FAIL: self = .allocationFailure | |
default: return nil | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment