使用 go lang 获取 caf 音频文件的持续时间
get duration of caf audio file with go lang
我想使用 go 获取 .caf 音频文件的持续时间。我找到了一些解码器,但它们的 Duration() 方法只有 return 0,其中的注释可能暗示了计算持续时间的方法,有人知道这些注释是否合法吗?如果是,我该如何计算持续时间?如果没有简单的解决方案,我会接受 "it's not possible" 作为答案。
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。在这些文档中,您会发现以下两个重要内容:
"The duration of the audio in the file is [the number of valid frames] divided by the sample rate specified in the file’s Audio Description chunk."
好的,很酷,但是有多少个有效帧?有两种可能的方式可以知道:
- 如果 CAF 有一个数据包 table,它必须包括有效帧的数量。完美。
- 唯一允许没有数据包的 CAF table 是那些具有恒定数据包大小的 CAF:
"Note that, as long as the format has a constant number of frames per packet, you can calculate the duration of each packet by dividing the mSampleRate [frames per second] value by the mFramesPerPacket value."
这告诉您每个数据包的持续时间,但由于数据包的大小是恒定的,因此数据包的数量只是 audioDataSize / bytesPerPacket
。后一个值包含在音频描述中。前者通常直接嵌入到文件中,但允许 -1
将音频数据作为最后一个块,在这种情况下,其大小为 totalFileSize - startOfAudioData
分解成这样:
- 如果有数据包 Table 块,请使用它和音频描述:
seconds = validFrames / sampleRate
- 否则,数据包必须具有恒定大小:
framesPerByte = framesPerPacket / bytesPerPacket
seconds = framesPerByte * audioDataSize
您的库会读取音频描述块,但我认为它不会读取数据包 Table。此外,如果块为 -1,我不确定它会计算音频数据大小。也许是 both/either,在这种情况下,您可以使用上面的信息。
如果没有,您可以自己解析文件,特别是如果您只关心持续时间。该文件以短 header 开头,然后拆分为 "chunks"(也称为 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)
}
我想使用 go 获取 .caf 音频文件的持续时间。我找到了一些解码器,但它们的 Duration() 方法只有 return 0,其中的注释可能暗示了计算持续时间的方法,有人知道这些注释是否合法吗?如果是,我该如何计算持续时间?如果没有简单的解决方案,我会接受 "it's not possible" 作为答案。
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。在这些文档中,您会发现以下两个重要内容:
"The duration of the audio in the file is [the number of valid frames] divided by the sample rate specified in the file’s Audio Description chunk."
好的,很酷,但是有多少个有效帧?有两种可能的方式可以知道:
- 如果 CAF 有一个数据包 table,它必须包括有效帧的数量。完美。
- 唯一允许没有数据包的 CAF table 是那些具有恒定数据包大小的 CAF:
"Note that, as long as the format has a constant number of frames per packet, you can calculate the duration of each packet by dividing the mSampleRate [frames per second] value by the mFramesPerPacket value."
这告诉您每个数据包的持续时间,但由于数据包的大小是恒定的,因此数据包的数量只是 audioDataSize / bytesPerPacket
。后一个值包含在音频描述中。前者通常直接嵌入到文件中,但允许 -1
将音频数据作为最后一个块,在这种情况下,其大小为 totalFileSize - startOfAudioData
分解成这样:
- 如果有数据包 Table 块,请使用它和音频描述:
seconds = validFrames / sampleRate
- 否则,数据包必须具有恒定大小:
framesPerByte = framesPerPacket / bytesPerPacket
seconds = framesPerByte * audioDataSize
您的库会读取音频描述块,但我认为它不会读取数据包 Table。此外,如果块为 -1,我不确定它会计算音频数据大小。也许是 both/either,在这种情况下,您可以使用上面的信息。
如果没有,您可以自己解析文件,特别是如果您只关心持续时间。该文件以短 header 开头,然后拆分为 "chunks"(也称为 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)
}