55 lines
1.6 KiB
Go
55 lines
1.6 KiB
Go
//go:build windows
|
||
|
||
package audio
|
||
|
||
import (
|
||
"os"
|
||
|
||
"github.com/braheezy/shine-mp3/pkg/mp3"
|
||
)
|
||
|
||
// mp3Rate is the encode sample rate. The capture pipeline is 16 kHz, but the
|
||
// Shine encoder emits broken "free-format" frames at MPEG-2 rates (16/22/24
|
||
// kHz) that most players reject. Encoding at an MPEG-1 rate (we upsample ×2 to
|
||
// 32 kHz) produces standard, universally-playable MP3s.
|
||
const mp3Rate = sampleRate * 2 // 32000
|
||
|
||
// writeMP3 encodes 16 kHz mono 16-bit PCM to a standard MP3 file using the
|
||
// pure-Go Shine encoder (no CGO). Two quirks are worked around:
|
||
// - 16 kHz (MPEG-2) yields broken free-format frames → upsample ×2 to 32 kHz.
|
||
// - Shine's Write only encodes half the samples for MONO input (its loop
|
||
// advances by samples_per_pass*2). Feeding STEREO interleaved data (the
|
||
// encoder reads samples_per_pass*channels per pass) encodes everything, so
|
||
// we duplicate mono → L=R stereo.
|
||
func writeMP3(path string, pcm []byte) error {
|
||
f, err := os.Create(path)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer f.Close()
|
||
mono32 := upsample2(bytesToInt16(pcm)) // 16 kHz → 32 kHz mono
|
||
stereo := make([]int16, len(mono32)*2) // L=R interleaved
|
||
for i, v := range mono32 {
|
||
stereo[2*i], stereo[2*i+1] = v, v
|
||
}
|
||
enc := mp3.NewEncoder(mp3Rate, 2)
|
||
return enc.Write(f, stereo)
|
||
}
|
||
|
||
// upsample2 doubles the sample rate with linear interpolation (16 kHz → 32 kHz).
|
||
func upsample2(in []int16) []int16 {
|
||
if len(in) == 0 {
|
||
return in
|
||
}
|
||
out := make([]int16, len(in)*2)
|
||
for i := range in {
|
||
out[2*i] = in[i]
|
||
if i+1 < len(in) {
|
||
out[2*i+1] = int16((int32(in[i]) + int32(in[i+1])) / 2)
|
||
} else {
|
||
out[2*i+1] = in[i]
|
||
}
|
||
}
|
||
return out
|
||
}
|