Skip to content

Round trip decode/encode  #75

@braheezy

Description

@braheezy

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
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions