我想使用go获取.caf音频文件的持续时间。我找到了一些解码器,但是它们的Duration()方法返回的0只是带有注释,可能暗示了计算持续时间的方式,是否知道这些注释是否合法,如果知道,我如何计算持续时间?如果没有简单的解决方案,我将接受“不可能”作为答案。

func (d *Decoder) Duration() time.Duration {
    //duration := time.Duration((float64(p.Size) / float64(p.AvgBytesPerSec)) * float64(time.Second))
    //duration := time.Duration(float64(p.NumSampleFrames) / float64(p.SampleRate) * float64(time.Second))

    return 0
}

一个实现示例,尽管我很高兴使用易于安装的任何实现:https://github.com/mattetti/audio/blob/master/caf/decoder.go

最佳答案

您链接的文件中的文档注释直接被添加为from Apple's spec。在这些文档中,您会发现以下两点重要信息:



好的,很酷,但是有多少个有效帧?有两种可能的方法来知道:

  • 如果CAF有一个数据包表,则它必须包括有效帧数。完美的。
  • 唯一不具有数据包表的CAF是具有恒定数据包大小的CAF:



  • 这告诉您每个数据包的持续时间,但是由于数据包的大小是恒定的,因此数据包的数量仅为audioDataSize / bytesPerPacket。后一个值包含在“音频说明”中。前者通常直接嵌入到文件中,但允许将其作为-1,最后一个音频数据块,在这种情况下,其大小为totalFileSize - startOfAudioData
    它像这样分解:
  • 如果有数据包表块,请使用它和音频说明:seconds = validFrames / sampleRate
  • 否则,数据包必须具有恒定的大小:
  • framesPerByte = framesPerPacket / bytesPerPacket
  • seconds = framesPerByte * audioDataSize

  • 您拥有的库读取了Audio Description块,但我不认为它读取了Packet Table。另外,如果块为-1,我不确定它会计算音频数据大小。也许两者兼有,在这种情况下,您可以使用上面的信息。

    如果没有,您可以自己解析文件,特别是如果您只关心持续时间。该文件以短标题开头,然后被拆分为“块”(又名TLV)。
    这是一个示例实现,您可以将其用作起点或修改链接的库:

    
    func readCAF() {
        buf := []byte{
            // file header
            'c', 'a', 'f', 'f', // file type
            0x0, 0x1, 0x0, 0x0, // file version, flags
    
            // audio description
            'd', 'e', 's', 'c', // chunk type
            0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x20, // CAFAudioFormat size
    
            0x40, 0xe5, 0x88, 0x80,
            0x00, 0x00, 0x00, 0x00, // sample rate
            'l', 'p', 'c', 'm', // fmt id
            0x0, 0x0, 0x0, 0x0, // fmt flags
            0x0, 0x0, 0x0, 0x1, // bytes per packet
            0x0, 0x0, 0x0, 0x1, // frames per packet
            0x0, 0x0, 0x0, 0x2, // channels per frame
            0x0, 0x0, 0x0, 0x3, // bits per channel
    
            // audio data
            'd', 'a', 't', 'a', // chunk type
            0xff, 0xff, 0xff, 0xff,
            0xff, 0xff, 0xff, 0xff, // size of data section (-1 = til EOF)
    
            // actual audio packets (in theory, anyway)
            0x0,
            0x0,
            0x0,
            0x0,
            0x0,
            0x0,
        }
    
        fileSize := len(buf)
        br := bufio.NewReader(bytes.NewBuffer(buf))
    
        type cafHdr struct {
            Typ     [4]byte
            Version uint16
            _       uint16
        }
    
        type chunkHdr struct {
            Typ [4]byte
            Sz  int64
        }
    
        type audioDescription struct {
            FramesPerSec     float64
            FmtId            uint32
            FmtFlags         uint32
            BytesPerPacket   uint32
            FramesPerPacket  uint32
            ChannelsPerFrame uint32
            BitsPerChannel   uint32
        }
    
        type packetTable struct {
            NPackets, NValidFrames, NPrimingFr, NRemainingFr int64
        }
    
        const FileHeaderSz = 8
        const ChunkHeaderSz = 12
        const AudioDescSz = 32
        const PacketHdrSz = 24
    
        fileHdr := cafHdr{}
        if err := binary.Read(br, binary.BigEndian, &fileHdr); err != nil {
            panic(err)
        }
        if fileHdr.Typ != [4]byte{'c', 'a', 'f', 'f'} || fileHdr.Version != 1 {
            panic("unknown file format")
        }
        remaining := int64(fileSize) - FileHeaderSz
    
        audioDesc := audioDescription{}
        packetTab := packetTable{}
        var audioDataSz int64
    
    readChunks:
        for {
            hdr := chunkHdr{}
            if err := binary.Read(br, binary.BigEndian, &hdr); err != nil {
                panic(err)
            }
            remaining -= ChunkHeaderSz
    
            switch hdr.Typ {
            case [4]byte{'d', 'e', 's', 'c'}: // audio description
                if err := binary.Read(br, binary.BigEndian, &audioDesc); err != nil {
                    panic(err)
                }
                hdr.Sz -= AudioDescSz
                remaining -= AudioDescSz
    
            case [4]byte{'p', 'a', 'k', 't'}: // packet table
                if err := binary.Read(br, binary.BigEndian, &packetTab); err != nil {
                    panic(err)
                }
                hdr.Sz -= PacketHdrSz
                remaining -= PacketHdrSz
    
            case [4]byte{'d', 'a', 't', 'a'}: // audio data
                if hdr.Sz > 0 {
                    audioDataSz = hdr.Sz
                } else if hdr.Sz == -1 {
                    // if needed, read to EOF to determine byte size
                    audioDataSz = remaining
                    break readChunks
                }
            }
    
            if hdr.Sz < 0 {
                panic("invalid header size")
            }
            remaining -= hdr.Sz
    
            // Skip to the next chunk. On 32 bit machines, Sz can overflow,
            // so you should check for that (or use Seek if you're reading a file).
            if n, err := br.Discard(int(hdr.Sz)); err != nil {
                if err == io.EOF && int64(n) == hdr.Sz {
                    break
                }
                panic(err)
            }
        }
    
        var seconds float64
    
        // If the data included a packet table, the frames determines duration.
        if packetTab.NValidFrames > 0 {
            seconds = float64(packetTab.NValidFrames) / audioDesc.FramesPerSec
        } else {
            // If there no packet table, it must have a constant packet size.
            if audioDesc.BytesPerPacket == 0 || audioDesc.FramesPerPacket == 0 {
                panic("bad data")
            }
            framesPerByte := float64(audioDesc.FramesPerPacket) / float64(audioDesc.BytesPerPacket)
            seconds = framesPerByte * float64(audioDataSz)
        }
    
        fmt.Printf("seconds: %f\n", seconds)
    }
    
    

    10-08 02:15