Files
go-sip/internal/audio/mp3.go
T

66 lines
2.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}