Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ There exists a protocol definition (below), a Go library, and Asterisk
application and channel interfaces.

**NOTE:** [@florentchauveau](https://github.com/florentchauveau) has contributed [DTMF support](https://github.com/asterisk/asterisk/pull/1138) to the AudioSocket protocol. The patch has been merged into `master` and will be included in Asterisk versions 20.X, 21.X, and 22.X.
**NOTE:** [@SvenKube](https://github.com/SvenKube) has contributed [support for higher sample rates](https://github.com/asterisk/asterisk/pull/1492) to the AudioSocket protocol. The patch has been merged into `master` and will be included in Asterisk versions 20.X, 21.X, 22.X, and 23.X.

## Protocol definition

Expand All @@ -26,6 +27,14 @@ indication, for instance, is `0x00 0x00 0x00`.
- `0x01` - Payload will contain the UUID (16-byte binary representation) for the audio stream
- `0x03` - Payload is 1 byte (ascii) DTMF (dual-tone multi-frequency) digit
- `0x10` - Payload is signed linear, 16-bit, 8kHz, mono PCM (little-endian)
- `0x11` - Payload is signed linear, 16-bit, 12kHz, mono PCM (little-endian)
- `0x12` - Payload is signed linear, 16-bit, 16kHz, mono PCM (little-endian)
- `0x13` - Payload is signed linear, 16-bit, 24kHz, mono PCM (little-endian)
- `0x14` - Payload is signed linear, 16-bit, 32kHz, mono PCM (little-endian)
- `0x15` - Payload is signed linear, 16-bit, 44.1kHz, mono PCM (little-endian)
- `0x16` - Payload is signed linear, 16-bit, 48kHz, mono PCM (little-endian)
- `0x17` - Payload is signed linear, 16-bit, 96kHz, mono PCM (little-endian)
- `0x18` - Payload is signed linear, 16-bit, 192kHz, mono PCM (little-endian)
- `0xff` - An error has occurred; payload is the (optional)
application-specific error code. Asterisk-generated error codes are listed
below.
Expand Down
128 changes: 127 additions & 1 deletion audiosocket.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,33 @@ const (
// KindDTMF indicates the message contains DTMF data
KindDTMF = 0x03

// KindSlin indicates the message contains signed-linear audio data
// KindSlin indicates that the message contains 16-bit, 8 kbit/s signed-linear audio data
KindSlin = 0x10

// KindSlin indicates that the message contains 16-bit, 12 kbit/s signed-linear audio data
KindSlin12 = 0x11

// KindSlin indicates that the message contains 16-bit, 16 kbit/s signed-linear audio data
KindSlin16 = 0x12

// KindSlin indicates that the message contains 16-bit, 24 kbit/s signed-linear audio data
KindSlin24 = 0x13

// KindSlin indicates that the message contains 16-bit, 32 kbit/s signed-linear audio data
KindSlin32 = 0x14

// KindSlin indicates that the message contains 16-bit, 44.1 kbit/s signed-linear audio data
KindSlin44 = 0x15

// KindSlin indicates that the message contains 16-bit, 48 kbit/s signed-linear audio data
KindSlin48 = 0x16

// KindSlin indicates that the message contains 16-bit, 96 kbit/s signed-linear audio data
KindSlin96 = 0x17

// KindSlin indicates that the message contains 16-bit, 192 kbit/s signed-linear audio data
KindSlin192 = 0x18

// KindError indicates the message contains an error code
KindError = 0xff
)
Expand All @@ -54,6 +78,94 @@ const (
ErrUnknown = 0xff
)

// AudioFormat defines codec-specific parameters for audio transmission
type AudioFormat struct {
Kind Kind
ChunkSize int
}

var (
// FormatSlin represents 8kHz signed linear audio format
FormatSlin = AudioFormat{
Kind: KindSlin,
ChunkSize: 320, // 8000Hz * 20ms * 2 bytes
}

// FormatSlin12 represents 12kHz signed linear audio format
FormatSlin12 = AudioFormat{
Kind: KindSlin12,
ChunkSize: 480, // 12000Hz * 20ms * 2 bytes
}

// FormatSlin16 represents 16kHz signed linear audio format
FormatSlin16 = AudioFormat{
Kind: KindSlin16,
ChunkSize: 640, // 16000Hz * 20ms * 2 bytes
}

// FormatSlin24 represents 24kHz signed linear audio format
FormatSlin24 = AudioFormat{
Kind: KindSlin24,
ChunkSize: 960, // 24000Hz * 20ms * 2 bytes
}

// FormatSlin32 represents 32kHz signed linear audio format
FormatSlin32 = AudioFormat{
Kind: KindSlin32,
ChunkSize: 1280, // 32000Hz * 20ms * 2 bytes
}

// FormatSlin44 represents 44kHz signed linear audio format
FormatSlin44 = AudioFormat{
Kind: KindSlin44,
ChunkSize: 1764, // 44100Hz * 20ms * 2 bytes
}

// FormatSlin48 represents 48kHz signed linear audio format
FormatSlin48 = AudioFormat{
Kind: KindSlin48,
ChunkSize: 1920, // 48000Hz * 20ms * 2 bytes
}

// FormatSlin96 represents 96kHz signed linear audio format
FormatSlin96 = AudioFormat{
Kind: KindSlin96,
ChunkSize: 3840, // 96000Hz * 20ms * 2 bytes
}

// FormatSlin192 represents 192kHz signed linear audio format
FormatSlin192 = AudioFormat{
Kind: KindSlin192,
ChunkSize: 7680, // 192000Hz * 20ms * 2 bytes
}
)

// AudioFormat returns the AudioFormat for this Kind
func (k Kind) AudioFormat() (AudioFormat, error) {
switch k {
case KindSlin:
return FormatSlin, nil
case KindSlin12:
return FormatSlin12, nil
case KindSlin16:
return FormatSlin16, nil
case KindSlin24:
return FormatSlin24, nil
case KindSlin32:
return FormatSlin32, nil
case KindSlin44:
return FormatSlin44, nil
case KindSlin48:
return FormatSlin48, nil
case KindSlin96:
return FormatSlin96, nil
case KindSlin192:
return FormatSlin192, nil
default:
return AudioFormat{}, fmt.Errorf("unsupported audio format: %d", k)
}
}

// ContentLength returns the length of the payload of the message
func (m Message) ContentLength() uint16 {
if len(m) < 3 {
Expand Down Expand Up @@ -171,3 +283,17 @@ func SlinMessage(in []byte) Message {
out = append(out, in...)
return out
}

// AudioMessage creates a new Message from audio data with the specified kind
// If the input is larger than 65535 bytes, this function will panic.
func AudioMessage(in []byte, kind Kind) Message {
if len(in) > 65535 {
panic("audiosocket: message too large")
}

out := make([]byte, 3, 3+len(in))
out[0] = byte(kind)
binary.BigEndian.PutUint16(out[1:], uint16(len(in)))
out = append(out, in...)
return out
}
19 changes: 12 additions & 7 deletions chunk.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,27 @@ const DefaultSlinChunkSize = 320 // 8000Hz * 20ms * 2 bytes

// SendSlinChunks takes signed linear data and sends it over an AudioSocket connection in chunks of the given size.
func SendSlinChunks(w io.Writer, chunkSize int, input []byte) error {
var chunks int
return SendAudioChunks(
w, AudioFormat{
Kind: KindSlin,
ChunkSize: chunkSize,
}, input)
}

if chunkSize < 1 {
chunkSize = DefaultSlinChunkSize
}
// SendAudioChunks takes audio data and sends it over an AudioSocket connection using the specified format
func SendAudioChunks(w io.Writer, format AudioFormat, input []byte) error {
var chunks int

t := time.NewTicker(20 * time.Millisecond)
defer t.Stop()

for i := 0; i < len(input); {
<-t.C
chunkLen := chunkSize
if i+chunkSize > len(input) {
chunkLen := format.ChunkSize
if i+format.ChunkSize > len(input) {
chunkLen = len(input) - i
}
if _, err := w.Write(SlinMessage(input[i : i+chunkLen])); err != nil {
if _, err := w.Write(AudioMessage(input[i:i+chunkLen], format.Kind)); err != nil {
return fmt.Errorf("failed to write chunk to AudioSocket: %w", err)
}
chunks++
Expand Down