代码之家  ›  专栏  ›  技术社区  ›  MD TAREQ HASSAN

在iOS中播放自定义.opus音频文件

  •  0
  • MD TAREQ HASSAN  · 技术社区  · 5 年前

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

    每个头表示opus数据的大小,即,如果头1是200(Int),那么opus数据1是200字节

    因此,我提取opus数据并附加到数据缓冲区,如下所示:

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

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

    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
    

    安卓团队能够使用libopus(JNI)和AudioTrack进行游戏。所以,我试着用 小品 pod如下:

    if let decoded = OpusKit.shared.decodeData(opusData) {
        decodedOpusData.append(decoded)
    } else {
        print("failed to decode")
    }
    

    然后试着玩 decodedOpusData .

    我不知道怎么播放那个音频文件(我是opus的新手)。

    关于opus数据

    样本:16000 |帧大小:320(16*20ms)|通道:1

    0 回复  |  直到 5 年前
        1
  •  1
  •   MD TAREQ HASSAN    4 年前

    Per your other comment/question ,如果您可以得到解码的PCM样本,您可以添加一个RIFF/WAV,使用的字节类似于下面的C常量(取自 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
    • “数据大小”是解码音频的总字节大小

    (完整解决方案: Libopus Kit ) iOS的Swift代码 ( )

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

    音频工具

    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
    }