package audio import ( "bytes" "encoding/binary" "errors" "fmt" "io" "github.com/hujm2023/go-mp3/v2" ) // DecodeMP3ToPCM16Mono decodes MP3 to signed little-endian mono PCM16 at the // requested rate. The decoder emits stereo PCM16 for every MP3 frame, so this // function downmixes before resampling. maxOutputBytes bounds the final buffer. func DecodeMP3ToPCM16Mono(encoded []byte, targetSampleRate, maxOutputBytes int) ([]byte, int, error) { if len(encoded) == 0 || maxOutputBytes <= 0 { return nil, 0, errors.New("MP3 input or output limit is invalid") } if targetSampleRate < 8000 || targetSampleRate > 48000 { return nil, 0, errors.New("MP3 target sample rate is outside 8–48 kHz") } if len(encoded) > maxOutputBytes { return nil, 0, errors.New("encoded MP3 exceeds the approved audio bound") } decoder, err := mp3.NewDecoder(bytes.NewReader(encoded)) if err != nil { return nil, 0, fmt.Errorf("MP3 stream could not be decoded: %w", err) } sourceSampleRate := decoder.SampleRate() if sourceSampleRate < 8000 || sourceSampleRate > 48000 { return nil, 0, errors.New("MP3 source sample rate is outside 8–48 kHz") } maxDecodedBytes := int64(maxOutputBytes) * 2 * int64(sourceSampleRate) / int64(targetSampleRate) if maxDecodedBytes < 4 { return nil, 0, errors.New("MP3 output limit is too small for a PCM16 sample") } decoded, err := io.ReadAll(io.LimitReader(decoder, maxDecodedBytes+1)) if err != nil { return nil, 0, errors.New("MP3 decode failed while reading audio") } if int64(len(decoded)) > maxDecodedBytes { return nil, 0, errors.New("decoded MP3 exceeds the approved PCM16 bound") } if len(decoded) == 0 || len(decoded)%4 != 0 { return nil, 0, errors.New("MP3 decoder returned incomplete stereo PCM16") } mono := make([]byte, len(decoded)/2) for source, target := 0, 0; source < len(decoded); source, target = source+4, target+2 { left := int32(int16(binary.LittleEndian.Uint16(decoded[source : source+2]))) right := int32(int16(binary.LittleEndian.Uint16(decoded[source+2 : source+4]))) binary.LittleEndian.PutUint16(mono[target:target+2], uint16(int16((left+right)/2))) } pcm16 := ResamplePCM16(mono, sourceSampleRate, targetSampleRate) if len(pcm16) == 0 || len(pcm16)%2 != 0 { return nil, 0, errors.New("MP3 conversion produced no complete PCM16 samples") } if len(pcm16) > maxOutputBytes { return nil, 0, errors.New("converted MP3 exceeds the approved PCM16 bound") } return pcm16, sourceSampleRate, nil }