diff --git a/README.md b/README.md index d9df191..802f9da 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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. diff --git a/audiosocket.go b/audiosocket.go index 9ed8e67..bd4b04c 100644 --- a/audiosocket.go +++ b/audiosocket.go @@ -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 ) @@ -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 { @@ -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 +} diff --git a/chunk.go b/chunk.go index 61b0771..d8d5f81 100644 --- a/chunk.go +++ b/chunk.go @@ -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++