如何在iOS中解码自定界的作品 [英] How to decode self-delimited opus in iOS

查看:109
本文介绍了如何在iOS中解码自定界的作品的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我能够使用AVFoundation录制和播放作品.问题是我得到了一个自定义作品音频文件,如下所示:

I was able to record and play opus using AVFoundation. The problem is I got a custom opus audio file as follows:

| header 1 (1 byte) | opus data 1 (1~255 bytes) | header 2 (1 byte) | opus data 2 (1~255 bytes) | ... | ... |

每个标头都指示op​​us数据的大小,即如果标头1为200(Int),则opus数据1为200字节

Each header indicates size of the opus data i.e. if header 1 is 200 (Int) then opus data 1 is 200 bytes

因此,我正在提取opus数据并将其追加到数据缓冲区,如下所示:

So, I am extracting opus data and appending to Data buffer as following:

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播放

Then I tried to play using AVAudioPlayer as following

// ... ... ...

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 窗格,如下所示:

I also tried MobileVLCKit pod as following:

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解码Opus数据,如下所示:

Android team was able to play using libopus (JNI) and AudioTrack. So, I tried to decode opus data using OpusKit pod as following:

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.).

And then tried to play decodedOpusData using AVAudioPlayer but gives same error: The operation couldn’t be completed. (OSStatus error 1954115647.).

我不知道该如何播放该音频文件(我是Opus的新手).

I don't know what to do to play that audio file (I am new to opus).

关于作品数据

样品:16000 |画面大小:320(16 * 20 ms)|频道:1

Sample: 16000 | Frame size: 320 (16 * 20 ms) | Channel: 1

推荐答案

WAV_HEADER_TEMPLATE )

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
 };

此C函数建议阅读.

  • 0x01应该用于为格式代码指定PCM
  • 数据大小"将是解码音频的总字节大小 数据.
  • 文件大小" = 44 +数据大小"
  • 0x01 should be used to specify PCM for format code
  • "size of data" would be the total byte size of your decoded audio data.
  • "file size" = 44 + "size of data"

iOS的Swift代码( 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($0) }
    }

    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
}

这篇关于如何在iOS中解码自定界的作品的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆