получить продолжительность аудиофайла caf с помощью go lang

Я хочу получить продолжительность аудиофайлов .caf с помощью go. Я нашел несколько декодеров, но их методы 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


person robert king    schedule 08.05.2020    source источник
comment
вот реализация: github.com/mattetti/audio/blob/master/ caf/decoder.go   -  person robert king    schedule 11.05.2020
comment
Мы не можем догадаться, почему эти строки закомментированы, но пробовали ли вы их и сравнивали результат с реальной продолжительностью?   -  person Marc    schedule 13.05.2020


Ответы (1)


Комментарии документа в этом файле, на который вы ссылаетесь, берутся напрямую из спецификации Apple. В этих документах вы найдете две важные вещи:

«Продолжительность звука в файле равна [количество допустимых кадров], деленной на частоту дискретизации, указанную в фрагменте описания звука файла».

ОК, круто, но сколько допустимых кадров? Есть два возможных способа узнать:

  • Если CAF имеет таблицу пакетов, она должна включать количество допустимых кадров. Идеальный.
  • Единственными CAF, которым разрешено НЕ иметь таблицу пакетов, являются те, у которых размеры пакетов неизменны:

«Обратите внимание, что, поскольку формат имеет постоянное количество кадров в пакете, вы можете рассчитать продолжительность каждого пакета, разделив значение mSampleRate [кадров в секунду] на значение mFramesPerPacket».

Это говорит вам о продолжительности на пакет, но поскольку пакеты имеют постоянный размер, количество пакетов равно audioDataSize / bytesPerPacket. Последнее значение включено в описание аудио. Первый часто встраивается непосредственно в файл, но допускается -1 с аудиоданными в качестве последнего фрагмента, и в этом случае его размер составляет totalFileSize - startOfAudioData.

Разбивается так:

  • Если есть фрагмент таблицы пакетов, используйте его и аудиоописание: seconds = validFrames / sampleRate
  • Otherwise, packets must have constant size:
    • 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)
}

person Saites    schedule 16.05.2020