66 lines
2.4 KiB
Go
66 lines
2.4 KiB
Go
package audio
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/binary"
|
||
"errors"
|
||
"io"
|
||
|
||
"github.com/hajimehoshi/go-mp3"
|
||
)
|
||
|
||
// 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, errors.New("MP3 stream could not be decoded")
|
||
}
|
||
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
|
||
}
|