如何解码 iOS 中的 self-delimited 作品
How to decode self-delimited opus in iOS
我能够使用 AVFoundation 录制和播放作品。问题是我得到了一个自定义的 opus 音频文件,如下所示:
| header 1 (1 byte) | opus data 1 (1~255 bytes) | header 2 (1 byte) | opus data 2 (1~255 bytes) | ... | ... |
每个header表示opus数据的大小即如果header1是200(Int)那么opus数据1是200字节
所以,我正在提取作品数据并将其附加到数据缓冲区,如下所示:
guard let url = Bundle.main.url(forResource: "test_16kbps", withExtension: "opus") else { return }
do {
let fileData = try Data(contentsOf: url)
while index < fileData.count {
headerData = fileData[index...(index + HEADER_SIZE)]
let opusBytesFromHeader = Int([UInt8](headerData)[0])
start = index + HEADER_SIZE
end = start + opusBytesFromHeader
opusData = fileData[start..<end]
opusAudioData.append(opusData)
index += (HEADER_SIZE + opusBytesFromHeader)
}
} catch let err {
print(err)
}
然后我尝试使用 AVAudioPlayer 播放如下
// ... ... ...
playData(audioData: opusAudioData)
// ... ... ...
func playData(audioData: Data){
var avAudioPlayer: AVAudioPlayer?
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
avAudioPlayer = try AVAudioPlayer.init(data: audioData)
if let player = avAudioPlayer {
player.play()
} else {
print("failed to create player from data")
}
} catch let error {
print(error.localizedDescription)
}
}
给出错误:The operation couldn’t be completed. (OSStatus error 1954115647.)
我也试过 MobileVLCKit pod 如下:
if let opusFilePath = createNewDirPath() { // creates "foo.opus"
do {
try opusAudioData.write(to: opusFilePath)
let opusMedia = VLCMedia(url: opusFilePath)
vlcPlayer.media = opusMedia
vlcPlayer.play()
} catch let err {
print(err)
}
}
给出以下错误:
2020-01-05 14:03:41.421270+0900 AppPlay[8695:4077367] creating player instance using shared library
[mp3 @ 0x10881e600] Failed to read frame size: Could not seek to 40126.
TagLib: Ogg::File::packet() -- Could not find the requested packet.
TagLib: Opus::File::read() -- invalid Opus identification header
Android 团队能够使用 libopus (JNI) 和 AudioTrack 进行游戏。因此,我尝试使用 OpusKit pod 解码作品数据,如下所示:
if let decoded = OpusKit.shared.decodeData(opusData) {
decodedOpusData.append(decoded)
} else {
print("failed to decode")
}
然后尝试使用 AVAudioPlayer 播放 decodedOpusData
但给出相同的错误:The operation couldn’t be completed. (OSStatus error 1954115647.)
。
我不知道如何播放该音频文件(我是 opus 的新手)。
关于作品数据
Sample: 16000 | Frame size: 320 (16 * 20 ms) | Channel: 1
Per your other comment/question, if you can get decoded PCM samples, you can add a RIFF/WAV using bytes similar to this C const below (taken from WAV_HEADER_TEMPLATE)
// Header for a 48 kHz, stereo, 32-bit float WAV.
// http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
static const unsigned char WAV_HEADER_TEMPLATE[44]={
'R','I','F','F',
0xFF,0xFF,0xFF,0x7F, // file size
'W','A','V','E',
'f','m','t',' ', // Chunk ID
0x10,0x00,0x00,0x00, // Chunk Size - length of format above
0x03,0x00, // Format Code: 1 is PCM, 3 is IEEE float
0x02,0x00, // Number of Channels (e.g. 2)
0x80,0xBB,0x00,0x00, // Samples per Second, Sample Rate (e.g. 48000)
0x00,0xDC,0x05,0x00, // Bytes per second, byte rate = sample rate * bits per sample * channels / 8
0x08,0x00, // Bytes per Sample Frame, block align = bits per sample * channels / 8
0x20,0x00, // bits per sample (16 for PCM, 32 for float)
'd','a','t','a',
0xFF,0xFF,0xFF,0x7F // size of data section
};
this C function也建议阅读
0x01
应该用于指定格式代码 的PCM
- “数据大小”将是解码音频的总字节大小
数据.
- “文件大小”= 44 +“数据大小”
(完整的解决方案:Libopus Kit)Swift iOS 的代码(Swift 5, iOS 13, Xcode 11.3)
guard let url = Bundle.main.url(forResource: "foo", withExtension: "opus") else { return }
print("url: \(url)")
do {
let fileData = try Data(contentsOf: url)
let extractedPCM = AudioUtil.extractPCM(from: fileData, for: OpusAudioSetting(/*defaults*/))
let wavHeader = AudioUtil.createWavHeader(pcmInfo: PCMInfo(/*defaults*/), pcmDataSizeInBytes: Int32(extractedPCM.count))
//print("wavHeader: \([UInt8](wavHeader))")
let wavAudioData = AudioUtil.generateWav(header: wavHeader, pcmData: extractedPCM)
playData(audioData: wavAudioData)
} catch let error {
print(error)
}
AudioUtil
import Foundation
import OpusKit
public class AudioUtil {
private init(){}
public static func createWavHeader(pcmInfo: PCMInfo, pcmDataSizeInBytes dataSize: Int32) -> Data {
let WAV_HEADER_SIZE:Int32 = 44
let fileSize:Int32 = dataSize + WAV_HEADER_SIZE
let sampleRate:Int32 = 16000
let subChunkSize:Int32 = 16
let format:Int16 = 1
let channels:Int16 = 1
let bitsPerSample:Int16 = 16
let byteRate:Int32 = sampleRate * Int32(channels * bitsPerSample / 8)
let blockAlign: Int16 = (bitsPerSample * channels) / 8
let header = NSMutableData()
header.append([UInt8]("RIFF".utf8), length: 4)
header.append(byteArray(from: fileSize), length: 4)
//WAVE
header.append([UInt8]("WAVE".utf8), length: 4)
//FMT
header.append([UInt8]("fmt ".utf8), length: 4)
header.append(byteArray(from: subChunkSize), length: 4)
header.append(byteArray(from: format), length: 2)
header.append(byteArray(from: channels), length: 2)
header.append(byteArray(from: sampleRate), length: 4)
header.append(byteArray(from: byteRate), length: 4)
header.append(byteArray(from: blockAlign), length: 2)
header.append(byteArray(from: bitsPerSample), length: 2)
header.append([UInt8]("data".utf8), length: 4)
header.append(byteArray(from: dataSize), length: 4)
return header as Data
}
public static func extractPCM(from audioData: Data, for setting: OpusAudioSetting) -> Data {
OpusKit.shared.initialize(
sampleRate: setting.sampleRate,
numberOfChannels: setting.channels,
packetSize: setting.packetSize,
encodeBlockSize: setting.encodeBlockSize)
let decodedPCMData = extractAndDecodeAudioData(from: audioData)
return decodedPCMData
}
public static func extractAndDecodeAudioData(from fileData: Data, headerSize: Int = 1) -> Data {
// can not share this implementation
}
private static func byteArray<T>(from value: T) -> [UInt8] where T: FixedWidthInteger {
// .littleEndian is required
return withUnsafeBytes(of: value.littleEndian) { Array([=12=]) }
}
public static func generateWav(header wavHeader: Data, pcmData: Data) -> Data {
var wavData = Data()
wavData.append(wavHeader)
wavData.append(pcmData)
return wavData
}
}
public struct OpusAudioSetting {
var sampleRate: opus_int32 = 16000
var channels: opus_int32 = 1
var packetSize: opus_int32 = 320
var encodeBlockSize: opus_int32 = 320
}
public struct PCMInfo {
var sampleRate:Int32 = 16000
var channels:Int16 = 1
var bitsPerSample:Int16 = 16
}
我能够使用 AVFoundation 录制和播放作品。问题是我得到了一个自定义的 opus 音频文件,如下所示:
| header 1 (1 byte) | opus data 1 (1~255 bytes) | header 2 (1 byte) | opus data 2 (1~255 bytes) | ... | ... |
每个header表示opus数据的大小即如果header1是200(Int)那么opus数据1是200字节
所以,我正在提取作品数据并将其附加到数据缓冲区,如下所示:
guard let url = Bundle.main.url(forResource: "test_16kbps", withExtension: "opus") else { return }
do {
let fileData = try Data(contentsOf: url)
while index < fileData.count {
headerData = fileData[index...(index + HEADER_SIZE)]
let opusBytesFromHeader = Int([UInt8](headerData)[0])
start = index + HEADER_SIZE
end = start + opusBytesFromHeader
opusData = fileData[start..<end]
opusAudioData.append(opusData)
index += (HEADER_SIZE + opusBytesFromHeader)
}
} catch let err {
print(err)
}
然后我尝试使用 AVAudioPlayer 播放如下
// ... ... ...
playData(audioData: opusAudioData)
// ... ... ...
func playData(audioData: Data){
var avAudioPlayer: AVAudioPlayer?
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
avAudioPlayer = try AVAudioPlayer.init(data: audioData)
if let player = avAudioPlayer {
player.play()
} else {
print("failed to create player from data")
}
} catch let error {
print(error.localizedDescription)
}
}
给出错误:The operation couldn’t be completed. (OSStatus error 1954115647.)
我也试过 MobileVLCKit pod 如下:
if let opusFilePath = createNewDirPath() { // creates "foo.opus"
do {
try opusAudioData.write(to: opusFilePath)
let opusMedia = VLCMedia(url: opusFilePath)
vlcPlayer.media = opusMedia
vlcPlayer.play()
} catch let err {
print(err)
}
}
给出以下错误:
2020-01-05 14:03:41.421270+0900 AppPlay[8695:4077367] creating player instance using shared library
[mp3 @ 0x10881e600] Failed to read frame size: Could not seek to 40126.
TagLib: Ogg::File::packet() -- Could not find the requested packet.
TagLib: Opus::File::read() -- invalid Opus identification header
Android 团队能够使用 libopus (JNI) 和 AudioTrack 进行游戏。因此,我尝试使用 OpusKit pod 解码作品数据,如下所示:
if let decoded = OpusKit.shared.decodeData(opusData) {
decodedOpusData.append(decoded)
} else {
print("failed to decode")
}
然后尝试使用 AVAudioPlayer 播放 decodedOpusData
但给出相同的错误:The operation couldn’t be completed. (OSStatus error 1954115647.)
。
我不知道如何播放该音频文件(我是 opus 的新手)。
关于作品数据
Sample: 16000 | Frame size: 320 (16 * 20 ms) | Channel: 1
Per your other comment/question, if you can get decoded PCM samples, you can add a RIFF/WAV using bytes similar to this C const below (taken from WAV_HEADER_TEMPLATE)
// Header for a 48 kHz, stereo, 32-bit float WAV.
// http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
static const unsigned char WAV_HEADER_TEMPLATE[44]={
'R','I','F','F',
0xFF,0xFF,0xFF,0x7F, // file size
'W','A','V','E',
'f','m','t',' ', // Chunk ID
0x10,0x00,0x00,0x00, // Chunk Size - length of format above
0x03,0x00, // Format Code: 1 is PCM, 3 is IEEE float
0x02,0x00, // Number of Channels (e.g. 2)
0x80,0xBB,0x00,0x00, // Samples per Second, Sample Rate (e.g. 48000)
0x00,0xDC,0x05,0x00, // Bytes per second, byte rate = sample rate * bits per sample * channels / 8
0x08,0x00, // Bytes per Sample Frame, block align = bits per sample * channels / 8
0x20,0x00, // bits per sample (16 for PCM, 32 for float)
'd','a','t','a',
0xFF,0xFF,0xFF,0x7F // size of data section
};
this C function也建议阅读
0x01
应该用于指定格式代码 的PCM
- “数据大小”将是解码音频的总字节大小 数据.
- “文件大小”= 44 +“数据大小”
(完整的解决方案:Libopus Kit)Swift iOS 的代码(Swift 5, iOS 13, Xcode 11.3)
guard let url = Bundle.main.url(forResource: "foo", withExtension: "opus") else { return }
print("url: \(url)")
do {
let fileData = try Data(contentsOf: url)
let extractedPCM = AudioUtil.extractPCM(from: fileData, for: OpusAudioSetting(/*defaults*/))
let wavHeader = AudioUtil.createWavHeader(pcmInfo: PCMInfo(/*defaults*/), pcmDataSizeInBytes: Int32(extractedPCM.count))
//print("wavHeader: \([UInt8](wavHeader))")
let wavAudioData = AudioUtil.generateWav(header: wavHeader, pcmData: extractedPCM)
playData(audioData: wavAudioData)
} catch let error {
print(error)
}
AudioUtil
import Foundation
import OpusKit
public class AudioUtil {
private init(){}
public static func createWavHeader(pcmInfo: PCMInfo, pcmDataSizeInBytes dataSize: Int32) -> Data {
let WAV_HEADER_SIZE:Int32 = 44
let fileSize:Int32 = dataSize + WAV_HEADER_SIZE
let sampleRate:Int32 = 16000
let subChunkSize:Int32 = 16
let format:Int16 = 1
let channels:Int16 = 1
let bitsPerSample:Int16 = 16
let byteRate:Int32 = sampleRate * Int32(channels * bitsPerSample / 8)
let blockAlign: Int16 = (bitsPerSample * channels) / 8
let header = NSMutableData()
header.append([UInt8]("RIFF".utf8), length: 4)
header.append(byteArray(from: fileSize), length: 4)
//WAVE
header.append([UInt8]("WAVE".utf8), length: 4)
//FMT
header.append([UInt8]("fmt ".utf8), length: 4)
header.append(byteArray(from: subChunkSize), length: 4)
header.append(byteArray(from: format), length: 2)
header.append(byteArray(from: channels), length: 2)
header.append(byteArray(from: sampleRate), length: 4)
header.append(byteArray(from: byteRate), length: 4)
header.append(byteArray(from: blockAlign), length: 2)
header.append(byteArray(from: bitsPerSample), length: 2)
header.append([UInt8]("data".utf8), length: 4)
header.append(byteArray(from: dataSize), length: 4)
return header as Data
}
public static func extractPCM(from audioData: Data, for setting: OpusAudioSetting) -> Data {
OpusKit.shared.initialize(
sampleRate: setting.sampleRate,
numberOfChannels: setting.channels,
packetSize: setting.packetSize,
encodeBlockSize: setting.encodeBlockSize)
let decodedPCMData = extractAndDecodeAudioData(from: audioData)
return decodedPCMData
}
public static func extractAndDecodeAudioData(from fileData: Data, headerSize: Int = 1) -> Data {
// can not share this implementation
}
private static func byteArray<T>(from value: T) -> [UInt8] where T: FixedWidthInteger {
// .littleEndian is required
return withUnsafeBytes(of: value.littleEndian) { Array([=12=]) }
}
public static func generateWav(header wavHeader: Data, pcmData: Data) -> Data {
var wavData = Data()
wavData.append(wavHeader)
wavData.append(pcmData)
return wavData
}
}
public struct OpusAudioSetting {
var sampleRate: opus_int32 = 16000
var channels: opus_int32 = 1
var packetSize: opus_int32 = 320
var encodeBlockSize: opus_int32 = 320
}
public struct PCMInfo {
var sampleRate:Int32 = 16000
var channels:Int16 = 1
var bitsPerSample:Int16 = 16
}