-
Notifications
You must be signed in to change notification settings - Fork 52
Description
Hi! I have a small test program that decodes a FLAC to a slice of samples, then encodes that back to a FLAC. I initially wrote it on a Fedora machine and the output FLAC was playing and sounded fine. I brought the code to a M3 Mac and the output file won't open in QuickTime Player. I installed VLC and that was able to open the file and play it (sounds great!). I've been able to reproduce this with different FLAC files.
I eventually want goqoa to support converting QOA files to FLAC and I'll be working with a slice of samples there.
Not trusting my code, I tried your wav2flac program and it panics on a random WAV I downloaded from pixabay:
frame number: 13085
constant method
constant method
panic: runtime error: index out of range [1] with length 1
goroutine 1 [running]:
github.com/mewkiz/flac/frame.(*Frame).Hash(0x1400007ad80, {0x10213fa20, 0x14000110000})
/Users/braheezy/go/pkg/mod/github.com/mewkiz/flac@v1.0.12/frame/frame.go:159 +0x20c
github.com/mewkiz/flac.(*Encoder).WriteFrame(0x1400010e050, 0x1400007ad80)
/Users/braheezy/go/pkg/mod/github.com/mewkiz/flac@v1.0.12/encode_frame.go:59 +0x53c
main.wav2flac({0x16ddd749d, 0xa}, 0x1)Despite the panic, it produces a FLAC file that VLC can open and play.
This smells like a metadata issue that the strict QuickTime Player fails to deal with but the robust VLC can handle. The actual audio is good, so most of the encoding went smoothly.
Here's my test program if it helps. I can share the audio files but they are just random off the internet, so I assume any will do.
package main
import (
"fmt"
"io"
"log"
"os"
"github.com/mewkiz/flac"
"github.com/mewkiz/flac/frame"
"github.com/mewkiz/flac/meta"
)
func main() {
// inputPath := "../Island Zone.flac"
// outputPath := "island-zone-output.flac"
inputPath := "center.flac"
outputPath := "center-output.flac"
metadata, pcmData := decodeFLAC(inputPath)
fmt.Printf("Metadata: %+v\n", metadata)
/// Create an encoder
outFile, err := os.Create(outputPath)
if err != nil {
log.Fatalf("Failed to create output file: %v", err)
}
defer outFile.Close()
enc, err := flac.NewEncoder(outFile, &meta.StreamInfo{
SampleRate: uint32(metadata.SampleRate),
NChannels: uint8(metadata.NChannels),
BitsPerSample: uint8(metadata.BitsPerSample),
BlockSizeMin: 16,
BlockSizeMax: 4096,
})
if err != nil {
log.Fatalf("Failed to initialize FLAC encoder: %v", err)
}
defer enc.Close()
if err := encodePCMToFLAC(enc, pcmData, metadata); err != nil {
log.Fatalf("Failed to encode PCM to FLAC: %v", err)
}
log.Println("FLAC file written successfully!")
}
func encodePCMToFLAC(enc *flac.Encoder, pcm []int32, metadata *meta.StreamInfo) error {
const nsamplesPerChannel = 4096
nchannels := int(metadata.NChannels)
totalSamples := len(pcm) / nchannels
subframes := make([]*frame.Subframe, nchannels)
for i := range subframes {
subframes[i] = &frame.Subframe{
Samples: make([]int32, nsamplesPerChannel),
}
}
for i := 0; i < totalSamples; i += nsamplesPerChannel {
end := i + nsamplesPerChannel
if end > totalSamples {
end = totalSamples
}
actualBlockSize := end - i
for _, subframe := range subframes {
subHdr := frame.SubHeader{
Pred: frame.PredVerbatim,
Order: 0,
Wasted: 0,
}
subframe.SubHeader = subHdr
subframe.NSamples = actualBlockSize
subframe.Samples = subframe.Samples[:actualBlockSize]
}
// Map PCM data into subframes
for sampleIdx := 0; sampleIdx < actualBlockSize*nchannels; sampleIdx++ {
ch := sampleIdx % nchannels
frameIndex := sampleIdx / nchannels
subframes[ch].Samples[frameIndex] = pcm[i*nchannels+sampleIdx]
}
// Optimize for Constant Prediction
for _, subframe := range subframes {
sample := subframe.Samples[0]
constant := true
for _, s := range subframe.Samples[1:] {
if s != sample {
constant = false
break
}
}
if constant {
subframe.SubHeader.Pred = frame.PredConstant
}
}
// Construct FLAC Frame
channels, err := getChannels(nchannels)
if err != nil {
return err
}
frameData := &frame.Frame{
Header: frame.Header{
HasFixedBlockSize: true,
BlockSize: uint16(actualBlockSize),
SampleRate: uint32(metadata.SampleRate),
Channels: channels,
BitsPerSample: uint8(metadata.BitsPerSample),
},
Subframes: subframes,
}
// Write FLAC Frame
if err := enc.WriteFrame(frameData); err != nil {
return err
}
}
return nil
}
func decodeFLAC(path string) (*meta.StreamInfo, []int32) {
flacStream, err := flac.Open(path)
if err != nil {
log.Fatalf("Error opening FLAC file: %v", err)
}
defer flacStream.Close()
var decodedData []int32
i := 0
for {
// Decode FLAC frame
flacFrame, err := flacStream.ParseNext()
if err != nil {
if err == io.EOF {
break
}
log.Fatalf("Error parsing FLAC frame: %v", err)
}
if i == 0 {
fmt.Printf("Frame: %+v\n", flacFrame)
fmt.Printf("Subframe: %+v\n", flacFrame.Subframes[0].SubHeader)
i++
}
// Collect audio samples
for i := 0; i < flacFrame.Subframes[0].NSamples; i++ {
for _, subframe := range flacFrame.Subframes {
sample := subframe.Samples[i]
decodedData = append(decodedData, sample)
}
}
}
return flacStream.Info, decodedData
}