@ariaflowagents/livekit-plugin-transport-twilio 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +221 -0
- package/dist/audio_input.d.ts +42 -0
- package/dist/audio_input.d.ts.map +1 -0
- package/dist/audio_input.js +117 -0
- package/dist/audio_input.js.map +1 -0
- package/dist/audio_output.d.ts +47 -0
- package/dist/audio_output.d.ts.map +1 -0
- package/dist/audio_output.js +162 -0
- package/dist/audio_output.js.map +1 -0
- package/dist/codec/g711.d.ts +36 -0
- package/dist/codec/g711.d.ts.map +1 -0
- package/dist/codec/g711.js +137 -0
- package/dist/codec/g711.js.map +1 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +42 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +125 -0
- package/dist/server.js.map +1 -0
- package/dist/text_output.d.ts +32 -0
- package/dist/text_output.d.ts.map +1 -0
- package/dist/text_output.js +72 -0
- package/dist/text_output.js.map +1 -0
- package/dist/transport_adapter.d.ts +95 -0
- package/dist/transport_adapter.d.ts.map +1 -0
- package/dist/transport_adapter.js +174 -0
- package/dist/transport_adapter.js.map +1 -0
- package/dist/twilio_protocol.d.ts +130 -0
- package/dist/twilio_protocol.d.ts.map +1 -0
- package/dist/twilio_protocol.js +74 -0
- package/dist/twilio_protocol.js.map +1 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# @ariaflow/livekit-plugin-transport-twilio
|
|
2
|
+
|
|
3
|
+
Twilio Media Streams transport adapter for AriaFlow voice agents. Works with Node.js, Bun, Deno, and any WebSocket-enabled runtime. Cloudflare Workers support is currently unstable.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ⚠️ **Cloudflare Workers Compatible** (Unstable) - No Node.js dependencies
|
|
8
|
+
- ✅ **Twilio Media Streams Support** - Native integration with Twilio's API
|
|
9
|
+
- ✅ **Automatic Codec Conversion** - G.711 μ-law ↔ PCM with resampling (8kHz ↔ 24kHz)
|
|
10
|
+
- ✅ **AriaFlow Native** - Works with `createAriaFlowSession` and AriaFlow Runtime
|
|
11
|
+
- ✅ **Session Management** - Full lifecycle management with SessionManager
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Using npm
|
|
17
|
+
npm install @ariaflow/livekit-plugin @ariaflow/livekit-plugin-transport-twilio
|
|
18
|
+
|
|
19
|
+
# Using bun
|
|
20
|
+
bun add @ariaflow/livekit-plugin @ariaflow/livekit-plugin-transport-twilio
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### Cloudflare Workers (Unstable)
|
|
26
|
+
|
|
27
|
+
> ⚠️ **Note:** The Cloudflare Workers integration is currently unstable and not officially supported. Use at your own risk.
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
// wrangler.toml:
|
|
31
|
+
// name = "ariaflow-twilio-agent"
|
|
32
|
+
// compatibility_date = "2024-01-01"
|
|
33
|
+
// compatibility_flags = ["nodejs_compat"]
|
|
34
|
+
|
|
35
|
+
import { createTwilioWorker } from '@ariaflow/livekit-plugin-transport-twilio/cloudflare';
|
|
36
|
+
import { createAriaFlowSession } from '@ariaflow/livekit-plugin';
|
|
37
|
+
import { GeminiLiveSTT, GeminiLiveTTS } from '@ariaflow/livekit-plugin/gemini';
|
|
38
|
+
|
|
39
|
+
const agentConfig = {
|
|
40
|
+
agents: [{ id: 'assistant', prompt: 'You are helpful.' }],
|
|
41
|
+
defaultAgentId: 'assistant',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default createTwilioWorker({
|
|
45
|
+
agent: () => createAriaFlowSession({
|
|
46
|
+
runtime: agentConfig,
|
|
47
|
+
stt: new GeminiLiveSTT(),
|
|
48
|
+
tts: new GeminiLiveTTS(),
|
|
49
|
+
greeting: 'Hello! How can I help?',
|
|
50
|
+
}),
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Node.js / Deno / Any WebSocket
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { WebSocketServer } from 'ws';
|
|
58
|
+
import { TwilioTransportAdapter } from '@ariaflow/livekit-plugin-transport-twilio';
|
|
59
|
+
import { createAriaFlowSession } from '@ariaflow/livekit-plugin';
|
|
60
|
+
import { SessionManager } from '@ariaflow/livekit-plugin';
|
|
61
|
+
import { GeminiLiveSTT, GeminiLiveTTS } from '@ariaflow/livekit-plugin/gemini';
|
|
62
|
+
|
|
63
|
+
const wss = new WebSocketServer({ port: 8080 });
|
|
64
|
+
const sessionManager = new SessionManager();
|
|
65
|
+
|
|
66
|
+
const agentConfig = {
|
|
67
|
+
agents: [{ id: 'assistant', prompt: 'You are helpful.' }],
|
|
68
|
+
defaultAgentId: 'assistant',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
wss.on('connection', (ws) => {
|
|
72
|
+
const transport = new TwilioTransportAdapter({
|
|
73
|
+
send: (msg) => ws.send(msg),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
ws.on('message', (data) => {
|
|
77
|
+
transport.handleMessage(data.toString());
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
ws.on('close', async () => {
|
|
81
|
+
await transport.close();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Wait for 'connected' event before starting session
|
|
85
|
+
ws.once('message', async (data) => {
|
|
86
|
+
const event = JSON.parse(data.toString());
|
|
87
|
+
if (event.event === 'connected') {
|
|
88
|
+
const { agent, sessionOptions } = createAriaFlowSession({
|
|
89
|
+
runtime: agentConfig,
|
|
90
|
+
stt: new GeminiLiveSTT(),
|
|
91
|
+
tts: new GeminiLiveTTS(),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
await sessionManager.startSession(transport, agent, sessionOptions);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## TwiML Setup
|
|
101
|
+
|
|
102
|
+
Create a TwiML bin in your Twilio console:
|
|
103
|
+
|
|
104
|
+
```xml
|
|
105
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
106
|
+
<Response>
|
|
107
|
+
<Say>Connecting to AI assistant...</Say>
|
|
108
|
+
<Connect>
|
|
109
|
+
<Stream url="wss://your-worker.workers.dev/voice/stream" />
|
|
110
|
+
</Connect>
|
|
111
|
+
</Response>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Audio Specifications
|
|
115
|
+
|
|
116
|
+
| Direction | Twilio Format | Internal Format | Sample Rate |
|
|
117
|
+
|-----------|---------------|-----------------|-------------|
|
|
118
|
+
| Inbound | G.711 μ-law | PCM S16 LE | 8kHz → 24kHz |
|
|
119
|
+
| Outbound | PCM S16 LE | G.711 μ-law | 24kHz → 8kHz |
|
|
120
|
+
|
|
121
|
+
The transport handles all codec conversion and resampling automatically.
|
|
122
|
+
|
|
123
|
+
## API Reference
|
|
124
|
+
|
|
125
|
+
### `TwilioTransportAdapter`
|
|
126
|
+
|
|
127
|
+
Main transport adapter class.
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
const transport = new TwilioTransportAdapter({
|
|
131
|
+
id: 'optional-custom-id',
|
|
132
|
+
send: (message: string) => {
|
|
133
|
+
// Send message to WebSocket
|
|
134
|
+
ws.send(message);
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Properties:**
|
|
140
|
+
- `id: string` - Unique identifier for this transport
|
|
141
|
+
- `audioInput: TwilioAudioInput` - Audio input stream
|
|
142
|
+
- `audioOutput: TwilioAudioOutput` - Audio output stream
|
|
143
|
+
- `textOutput: TwilioTextOutput` - Text output stream
|
|
144
|
+
- `config: TransportAdapterConfig` - Configuration object
|
|
145
|
+
- `isOpen: boolean` - Whether the transport is active
|
|
146
|
+
|
|
147
|
+
**Methods:**
|
|
148
|
+
- `handleMessage(message: string)` - Process incoming Twilio message
|
|
149
|
+
- `clearAudio()` - Clear audio buffer
|
|
150
|
+
- `close()` - Close the transport
|
|
151
|
+
|
|
152
|
+
### `createTwilioWorker(options)` (Unstable)
|
|
153
|
+
|
|
154
|
+
> ⚠️ **Note:** This Cloudflare Workers integration helper is currently unstable and not officially supported.
|
|
155
|
+
|
|
156
|
+
Cloudflare Workers integration helper.
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
export default createTwilioWorker({
|
|
160
|
+
agent: () => createAriaFlowSession({...}),
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Options:**
|
|
165
|
+
- `agent: () => Agent | { agent: Agent; sessionOptions: any }` - Agent factory
|
|
166
|
+
- `stt?: any` - STT instance (for raw LiveKit pattern)
|
|
167
|
+
- `llm?: any` - LLM instance
|
|
168
|
+
- `tts?: any` - TTS instance
|
|
169
|
+
- `vad?: any` - VAD instance
|
|
170
|
+
- `turnDetection?: any` - Turn detection config
|
|
171
|
+
|
|
172
|
+
## Environment Compatibility
|
|
173
|
+
|
|
174
|
+
| Runtime | WebSocket Support | Status |
|
|
175
|
+
|---------|------------------|--------|
|
|
176
|
+
| Cloudflare Workers | ✅ Native | ⚠️ Unstable |
|
|
177
|
+
| Node.js | ✅ via `ws` package | ✅ Supported |
|
|
178
|
+
| Deno | ✅ Native | ✅ Supported |
|
|
179
|
+
| Bun | ✅ Native | ✅ Supported |
|
|
180
|
+
| Browser | ✅ Native | ✅ Supported (development only) |
|
|
181
|
+
|
|
182
|
+
## Limitations
|
|
183
|
+
|
|
184
|
+
1. **Twilio Sample Rate**: Twilio uses 8kHz G.711 μ-law. The transport resamples to 24kHz for AriaFlow, but this adds slight latency.
|
|
185
|
+
2. **Codec**: Only G.711 μ-law is supported (Twilio's standard).
|
|
186
|
+
3. **Latency**: Expect 500-1000ms additional latency compared to direct WebRTC.
|
|
187
|
+
4. **Full Duplex**: Twilio Media Streams is full-duplex, but the implementation treats inbound/outbound as separate streams.
|
|
188
|
+
|
|
189
|
+
## Troubleshooting
|
|
190
|
+
|
|
191
|
+
### Audio cutting out
|
|
192
|
+
- Check your WebSocket connection is stable
|
|
193
|
+
- Verify `send` callback is working correctly
|
|
194
|
+
- Check for errors in the console
|
|
195
|
+
|
|
196
|
+
### Poor audio quality
|
|
197
|
+
- The 8kHz sample rate is lower than typical 24kHz
|
|
198
|
+
- This is a Twilio limitation, not the transport
|
|
199
|
+
- Consider using Twilio's programmable voice SDK for higher quality
|
|
200
|
+
|
|
201
|
+
### Connection issues
|
|
202
|
+
- Ensure your WebSocket URL is publicly accessible
|
|
203
|
+
- Check Twilio can reach your server (no firewall blocking)
|
|
204
|
+
- Verify CORS headers if using browser-based testing
|
|
205
|
+
|
|
206
|
+
## Examples
|
|
207
|
+
|
|
208
|
+
See `examples/` directory for:
|
|
209
|
+
- `cloudflare_worker.ts` - Complete Cloudflare Worker example (⚠️ Unstable)
|
|
210
|
+
- `wrangler.toml` - Deployment configuration
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
Apache-2.0
|
|
215
|
+
|
|
216
|
+
## Related Packages
|
|
217
|
+
|
|
218
|
+
- `@ariaflow/livekit-plugin` - Core AriaFlow LiveKit integration
|
|
219
|
+
- `@ariaflow/livekit-plugin/gemini` - Gemini STT/TTS plugins
|
|
220
|
+
- `@ariaflow/livekit-plugin-transport-ws` - WebSocket transport
|
|
221
|
+
- `@ariaflow/livekit-plugin-transport-http` - HTTP/SSE transport
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audio input for Twilio Media Streams.
|
|
3
|
+
*
|
|
4
|
+
* Receives mu-law encoded audio at 8kHz from Twilio, decodes to PCM,
|
|
5
|
+
* resamples to 24kHz via a streaming Sox sinc resampler, and provides
|
|
6
|
+
* AudioFrame objects to the STT pipeline.
|
|
7
|
+
*
|
|
8
|
+
* The resampler is allocated once and reused across all media events
|
|
9
|
+
* for the lifetime of this input (local Rust FFI, no LiveKit server).
|
|
10
|
+
*/
|
|
11
|
+
import { AudioInput } from '@ariaflow/livekit-plugin';
|
|
12
|
+
import type { TwilioMediaEvent } from './twilio_protocol.js';
|
|
13
|
+
/**
|
|
14
|
+
* Receives audio from Twilio Media Streams and provides it as a
|
|
15
|
+
* ReadableStream<AudioFrame> for the STT pipeline.
|
|
16
|
+
*
|
|
17
|
+
* Flow:
|
|
18
|
+
* 1. Twilio sends mu-law audio at 8kHz (base64 encoded)
|
|
19
|
+
* 2. Decode mu-law to PCM Int16
|
|
20
|
+
* 3. Wrap in AudioFrame at 8kHz
|
|
21
|
+
* 4. Push through streaming resampler -> 24kHz AudioFrames
|
|
22
|
+
* 5. Write frames to STT stream
|
|
23
|
+
*/
|
|
24
|
+
export declare class TwilioAudioInput extends AudioInput {
|
|
25
|
+
private currentStreamId;
|
|
26
|
+
private streamWriter;
|
|
27
|
+
private closed;
|
|
28
|
+
private resampler;
|
|
29
|
+
/**
|
|
30
|
+
* Process a media event from Twilio.
|
|
31
|
+
*
|
|
32
|
+
* @param event - Twilio media event with base64 mu-law payload
|
|
33
|
+
*/
|
|
34
|
+
handleMediaEvent(event: TwilioMediaEvent): void;
|
|
35
|
+
private startNewStream;
|
|
36
|
+
/**
|
|
37
|
+
* End the current audio stream (flush remaining audio from resampler).
|
|
38
|
+
*/
|
|
39
|
+
endCurrentStream(): void;
|
|
40
|
+
close(): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=audio_input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio_input.d.ts","sourceRoot":"","sources":["../src/audio_input.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,UAAU,EAAc,MAAM,0BAA0B,CAAC;AAIlE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAK7D;;;;;;;;;;GAUG;AACH,qBAAa,gBAAiB,SAAQ,UAAU;IAC9C,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,YAAY,CAAwD;IAC5E,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,SAAS,CAA2D;IAE5E;;;;OAIG;IACH,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAgD/C,OAAO,CAAC,cAAc;IAMtB;;OAEG;IACH,gBAAgB,IAAI,IAAI;IAuBlB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAK7B"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audio input for Twilio Media Streams.
|
|
3
|
+
*
|
|
4
|
+
* Receives mu-law encoded audio at 8kHz from Twilio, decodes to PCM,
|
|
5
|
+
* resamples to 24kHz via a streaming Sox sinc resampler, and provides
|
|
6
|
+
* AudioFrame objects to the STT pipeline.
|
|
7
|
+
*
|
|
8
|
+
* The resampler is allocated once and reused across all media events
|
|
9
|
+
* for the lifetime of this input (local Rust FFI, no LiveKit server).
|
|
10
|
+
*/
|
|
11
|
+
import { AudioInput, AudioFrame } from '@ariaflow/livekit-plugin';
|
|
12
|
+
import { createResampler } from '@ariaflow/livekit-plugin/utils/resample';
|
|
13
|
+
import { TransformStream } from 'stream/web';
|
|
14
|
+
import { mulawDecodeArray } from '@ariaflow/livekit-plugin/codec/g711';
|
|
15
|
+
const TWILIO_SAMPLE_RATE = 8000;
|
|
16
|
+
const TARGET_SAMPLE_RATE = 24000;
|
|
17
|
+
/**
|
|
18
|
+
* Receives audio from Twilio Media Streams and provides it as a
|
|
19
|
+
* ReadableStream<AudioFrame> for the STT pipeline.
|
|
20
|
+
*
|
|
21
|
+
* Flow:
|
|
22
|
+
* 1. Twilio sends mu-law audio at 8kHz (base64 encoded)
|
|
23
|
+
* 2. Decode mu-law to PCM Int16
|
|
24
|
+
* 3. Wrap in AudioFrame at 8kHz
|
|
25
|
+
* 4. Push through streaming resampler -> 24kHz AudioFrames
|
|
26
|
+
* 5. Write frames to STT stream
|
|
27
|
+
*/
|
|
28
|
+
export class TwilioAudioInput extends AudioInput {
|
|
29
|
+
currentStreamId = null;
|
|
30
|
+
streamWriter = null;
|
|
31
|
+
closed = false;
|
|
32
|
+
resampler = createResampler(TWILIO_SAMPLE_RATE, TARGET_SAMPLE_RATE);
|
|
33
|
+
/**
|
|
34
|
+
* Process a media event from Twilio.
|
|
35
|
+
*
|
|
36
|
+
* @param event - Twilio media event with base64 mu-law payload
|
|
37
|
+
*/
|
|
38
|
+
handleMediaEvent(event) {
|
|
39
|
+
if (this.closed)
|
|
40
|
+
return;
|
|
41
|
+
const payload = event.media.payload;
|
|
42
|
+
if (!payload)
|
|
43
|
+
return;
|
|
44
|
+
try {
|
|
45
|
+
// Decode base64 -> mu-law bytes
|
|
46
|
+
const mulawData = Uint8Array.from(atob(payload), (c) => c.charCodeAt(0));
|
|
47
|
+
// Decode mu-law to PCM at 8kHz
|
|
48
|
+
const pcm8kHz = mulawDecodeArray(mulawData);
|
|
49
|
+
// Wrap in AudioFrame at the input rate
|
|
50
|
+
const inputFrame = new AudioFrame(pcm8kHz, TWILIO_SAMPLE_RATE, 1, pcm8kHz.length);
|
|
51
|
+
// Resample 8kHz -> 24kHz via streaming resampler
|
|
52
|
+
const outputFrames = this.resampler.push(inputFrame);
|
|
53
|
+
if (outputFrames.length === 0)
|
|
54
|
+
return;
|
|
55
|
+
// Ensure we have an active stream
|
|
56
|
+
if (!this.currentStreamId) {
|
|
57
|
+
this.startNewStream();
|
|
58
|
+
}
|
|
59
|
+
// Write resampled frames to STT stream
|
|
60
|
+
for (const frame of outputFrames) {
|
|
61
|
+
if (this.streamWriter) {
|
|
62
|
+
this.streamWriter.write(frame).catch((err) => {
|
|
63
|
+
if (!this.closed) {
|
|
64
|
+
console.error('[TwilioAudioInput] Error writing frame:', {
|
|
65
|
+
error: err instanceof Error ? err.message : String(err),
|
|
66
|
+
streamId: this.currentStreamId,
|
|
67
|
+
timestamp: new Date().toISOString(),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
console.error('[TwilioAudioInput] Error processing media event:', {
|
|
76
|
+
error: error instanceof Error ? error.message : String(error),
|
|
77
|
+
payloadLength: payload?.length || 0,
|
|
78
|
+
timestamp: new Date().toISOString(),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
startNewStream() {
|
|
83
|
+
const { readable, writable } = new TransformStream();
|
|
84
|
+
this.streamWriter = writable.getWriter();
|
|
85
|
+
this.currentStreamId = this.multiStream.addInputStream(readable);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* End the current audio stream (flush remaining audio from resampler).
|
|
89
|
+
*/
|
|
90
|
+
endCurrentStream() {
|
|
91
|
+
// Flush resampler to emit any remaining buffered samples
|
|
92
|
+
if (this.streamWriter) {
|
|
93
|
+
for (const frame of this.resampler.flush()) {
|
|
94
|
+
this.streamWriter.write(frame).catch(() => { });
|
|
95
|
+
}
|
|
96
|
+
this.streamWriter.close().catch((err) => {
|
|
97
|
+
if (!this.closed) {
|
|
98
|
+
console.error('[TwilioAudioInput] Error closing stream writer:', {
|
|
99
|
+
error: err instanceof Error ? err.message : String(err),
|
|
100
|
+
streamId: this.currentStreamId,
|
|
101
|
+
timestamp: new Date().toISOString(),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
this.streamWriter = null;
|
|
106
|
+
}
|
|
107
|
+
this.currentStreamId = null;
|
|
108
|
+
// Re-create the resampler for the next stream (fresh state)
|
|
109
|
+
this.resampler = createResampler(TWILIO_SAMPLE_RATE, TARGET_SAMPLE_RATE);
|
|
110
|
+
}
|
|
111
|
+
async close() {
|
|
112
|
+
this.closed = true;
|
|
113
|
+
this.endCurrentStream();
|
|
114
|
+
await super.close();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=audio_input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio_input.js","sourceRoot":"","sources":["../src/audio_input.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audio output for Twilio Media Streams.
|
|
3
|
+
*
|
|
4
|
+
* Receives AudioFrame objects at 24kHz from the TTS pipeline,
|
|
5
|
+
* resamples to 8kHz via a streaming Sox sinc resampler, encodes
|
|
6
|
+
* to mu-law, and sends to Twilio via WebSocket.
|
|
7
|
+
*
|
|
8
|
+
* The resampler is allocated once and reused across all frames
|
|
9
|
+
* for the lifetime of this output (local Rust FFI, no LiveKit server).
|
|
10
|
+
*/
|
|
11
|
+
import { AudioOutput, AudioFrame } from '@ariaflow/livekit-plugin';
|
|
12
|
+
/**
|
|
13
|
+
* Sends audio frames from the TTS pipeline to Twilio Media Streams.
|
|
14
|
+
*
|
|
15
|
+
* Flow:
|
|
16
|
+
* 1. TTS pipeline sends AudioFrame at 24kHz
|
|
17
|
+
* 2. Push through streaming resampler -> 8kHz AudioFrames
|
|
18
|
+
* 3. Encode each frame to mu-law
|
|
19
|
+
* 4. Base64 encode
|
|
20
|
+
* 5. Send as Twilio media event
|
|
21
|
+
*/
|
|
22
|
+
export declare class TwilioAudioOutput extends AudioOutput {
|
|
23
|
+
private sendQueue;
|
|
24
|
+
private sending;
|
|
25
|
+
private segmentSamplesSent;
|
|
26
|
+
private flushed;
|
|
27
|
+
private closed;
|
|
28
|
+
private resampler;
|
|
29
|
+
private sendCallback;
|
|
30
|
+
constructor();
|
|
31
|
+
/**
|
|
32
|
+
* Set the callback for sending messages to Twilio.
|
|
33
|
+
*
|
|
34
|
+
* @param callback - Function that sends JSON string to WebSocket
|
|
35
|
+
*/
|
|
36
|
+
setSendCallback(callback: (message: string) => void): void;
|
|
37
|
+
captureFrame(frame: AudioFrame): Promise<void>;
|
|
38
|
+
private processSendQueue;
|
|
39
|
+
/**
|
|
40
|
+
* Encode and send a single 8kHz audio frame to Twilio.
|
|
41
|
+
*/
|
|
42
|
+
private sendFrame;
|
|
43
|
+
flush(): void;
|
|
44
|
+
clearBuffer(): void;
|
|
45
|
+
close(): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=audio_output.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio_output.d.ts","sourceRoot":"","sources":["../src/audio_output.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAOnE;;;;;;;;;GASG;AACH,qBAAa,iBAAkB,SAAQ,WAAW;IAChD,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,kBAAkB,CAAa;IACvC,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,SAAS,CAAwD;IAGzE,OAAO,CAAC,YAAY,CAAuC;;IAM3D;;;;OAIG;IACH,eAAe,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAIpD,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAcpD,OAAO,CAAC,gBAAgB;IAyCxB;;OAEG;IACH,OAAO,CAAC,SAAS;IAoBjB,KAAK,IAAI,IAAI;IA0Bb,WAAW,IAAI,IAAI;IA6Bb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAI7B"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audio output for Twilio Media Streams.
|
|
3
|
+
*
|
|
4
|
+
* Receives AudioFrame objects at 24kHz from the TTS pipeline,
|
|
5
|
+
* resamples to 8kHz via a streaming Sox sinc resampler, encodes
|
|
6
|
+
* to mu-law, and sends to Twilio via WebSocket.
|
|
7
|
+
*
|
|
8
|
+
* The resampler is allocated once and reused across all frames
|
|
9
|
+
* for the lifetime of this output (local Rust FFI, no LiveKit server).
|
|
10
|
+
*/
|
|
11
|
+
import { AudioOutput } from '@ariaflow/livekit-plugin';
|
|
12
|
+
import { createResampler } from '@ariaflow/livekit-plugin/utils/resample';
|
|
13
|
+
import { mulawEncodeArray } from '@ariaflow/livekit-plugin/codec/g711';
|
|
14
|
+
const TTS_SAMPLE_RATE = 24000;
|
|
15
|
+
const TARGET_SAMPLE_RATE = 8000;
|
|
16
|
+
/**
|
|
17
|
+
* Sends audio frames from the TTS pipeline to Twilio Media Streams.
|
|
18
|
+
*
|
|
19
|
+
* Flow:
|
|
20
|
+
* 1. TTS pipeline sends AudioFrame at 24kHz
|
|
21
|
+
* 2. Push through streaming resampler -> 8kHz AudioFrames
|
|
22
|
+
* 3. Encode each frame to mu-law
|
|
23
|
+
* 4. Base64 encode
|
|
24
|
+
* 5. Send as Twilio media event
|
|
25
|
+
*/
|
|
26
|
+
export class TwilioAudioOutput extends AudioOutput {
|
|
27
|
+
sendQueue = [];
|
|
28
|
+
sending = false;
|
|
29
|
+
segmentSamplesSent = 0;
|
|
30
|
+
flushed = false;
|
|
31
|
+
closed = false;
|
|
32
|
+
resampler = createResampler(TTS_SAMPLE_RATE, TARGET_SAMPLE_RATE);
|
|
33
|
+
// Callback to send messages to Twilio
|
|
34
|
+
sendCallback = () => { };
|
|
35
|
+
constructor() {
|
|
36
|
+
super(TARGET_SAMPLE_RATE);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Set the callback for sending messages to Twilio.
|
|
40
|
+
*
|
|
41
|
+
* @param callback - Function that sends JSON string to WebSocket
|
|
42
|
+
*/
|
|
43
|
+
setSendCallback(callback) {
|
|
44
|
+
this.sendCallback = callback;
|
|
45
|
+
}
|
|
46
|
+
async captureFrame(frame) {
|
|
47
|
+
await super.captureFrame(frame);
|
|
48
|
+
if (this.closed)
|
|
49
|
+
return;
|
|
50
|
+
// Push 24kHz frame through the streaming resampler -> 8kHz frames
|
|
51
|
+
for (const outFrame of this.resampler.push(frame)) {
|
|
52
|
+
this.sendQueue.push(outFrame);
|
|
53
|
+
}
|
|
54
|
+
if (!this.sending) {
|
|
55
|
+
this.processSendQueue();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
processSendQueue() {
|
|
59
|
+
if (this.sending || this.closed)
|
|
60
|
+
return;
|
|
61
|
+
this.sending = true;
|
|
62
|
+
while (this.sendQueue.length > 0) {
|
|
63
|
+
const frame = this.sendQueue.shift();
|
|
64
|
+
if (this.segmentSamplesSent === 0) {
|
|
65
|
+
this.onPlaybackStarted(Date.now());
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
this.sendFrame(frame);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.error('[TwilioAudioOutput] Error sending frame:', {
|
|
72
|
+
error: error instanceof Error ? error.message : String(error),
|
|
73
|
+
samplesPerChannel: frame.samplesPerChannel,
|
|
74
|
+
timestamp: new Date().toISOString(),
|
|
75
|
+
});
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
this.segmentSamplesSent += frame.samplesPerChannel;
|
|
79
|
+
}
|
|
80
|
+
this.sending = false;
|
|
81
|
+
if (this.flushed && this.sendQueue.length === 0) {
|
|
82
|
+
const sampleRate = this.sampleRate || TARGET_SAMPLE_RATE;
|
|
83
|
+
const playbackDuration = this.segmentSamplesSent / sampleRate;
|
|
84
|
+
this.onPlaybackFinished({
|
|
85
|
+
playbackPosition: playbackDuration,
|
|
86
|
+
interrupted: false,
|
|
87
|
+
});
|
|
88
|
+
this.segmentSamplesSent = 0;
|
|
89
|
+
this.flushed = false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Encode and send a single 8kHz audio frame to Twilio.
|
|
94
|
+
*/
|
|
95
|
+
sendFrame(frame) {
|
|
96
|
+
// Encode to mu-law
|
|
97
|
+
const mulawData = mulawEncodeArray(new Int16Array(frame.data));
|
|
98
|
+
// Base64 encode
|
|
99
|
+
const base64 = Buffer.from(mulawData).toString('base64');
|
|
100
|
+
// Create Twilio media event
|
|
101
|
+
const message = JSON.stringify({
|
|
102
|
+
event: 'media',
|
|
103
|
+
streamSid: '',
|
|
104
|
+
sequenceNumber: `${Date.now()}`,
|
|
105
|
+
media: {
|
|
106
|
+
payload: base64,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
this.sendCallback(message);
|
|
110
|
+
}
|
|
111
|
+
flush() {
|
|
112
|
+
super.flush();
|
|
113
|
+
// Drain any buffered samples from the resampler
|
|
114
|
+
for (const outFrame of this.resampler.flush()) {
|
|
115
|
+
this.sendQueue.push(outFrame);
|
|
116
|
+
}
|
|
117
|
+
this.flushed = true;
|
|
118
|
+
if (this.sendQueue.length === 0) {
|
|
119
|
+
const sampleRate = this.sampleRate || TARGET_SAMPLE_RATE;
|
|
120
|
+
const playbackDuration = this.segmentSamplesSent / sampleRate;
|
|
121
|
+
this.onPlaybackFinished({
|
|
122
|
+
playbackPosition: playbackDuration,
|
|
123
|
+
interrupted: false,
|
|
124
|
+
});
|
|
125
|
+
this.segmentSamplesSent = 0;
|
|
126
|
+
this.flushed = false;
|
|
127
|
+
}
|
|
128
|
+
else if (!this.sending) {
|
|
129
|
+
this.processSendQueue();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
clearBuffer() {
|
|
133
|
+
const sampleRate = this.sampleRate || TARGET_SAMPLE_RATE;
|
|
134
|
+
const playbackDuration = this.segmentSamplesSent / sampleRate;
|
|
135
|
+
this.sendQueue = [];
|
|
136
|
+
this.onPlaybackFinished({
|
|
137
|
+
playbackPosition: playbackDuration,
|
|
138
|
+
interrupted: true,
|
|
139
|
+
});
|
|
140
|
+
this.segmentSamplesSent = 0;
|
|
141
|
+
this.flushed = false;
|
|
142
|
+
// Send clear message to Twilio
|
|
143
|
+
try {
|
|
144
|
+
this.sendCallback(JSON.stringify({
|
|
145
|
+
event: 'clear',
|
|
146
|
+
streamSid: '',
|
|
147
|
+
sequenceNumber: `${Date.now()}`,
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
console.error('[TwilioAudioOutput] Error sending clear message:', {
|
|
152
|
+
error: error instanceof Error ? error.message : String(error),
|
|
153
|
+
timestamp: new Date().toISOString(),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async close() {
|
|
158
|
+
this.closed = true;
|
|
159
|
+
this.sendQueue = [];
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=audio_output.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio_output.js","sourceRoot":"","sources":["../src/audio_output.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,WAAW,EAAc,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AAEvE,MAAM,eAAe,GAAG,KAAK,CAAC;AAC9B,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEhC;;;;;;;;;GASG;AACH,MAAM,OAAO,iBAAkB,SAAQ,WAAW;IACxC,SAAS,GAAiB,EAAE,CAAC;IAC7B,OAAO,GAAY,KAAK,CAAC;IACzB,kBAAkB,GAAW,CAAC,CAAC;IAC/B,OAAO,GAAY,KAAK,CAAC;IACzB,MAAM,GAAY,KAAK,CAAC;IACxB,SAAS,GAAG,eAAe,CAAC,eAAe,EAAE,kBAAkB,CAAC,CAAC;IAEzE,sCAAsC;IAC9B,YAAY,GAA8B,GAAG,EAAE,GAAE,CAAC,CAAC;IAE3D;QACE,KAAK,CAAC,kBAAkB,CAAC,CAAC;IAC5B,CAAC;IAED;;;;OAIG;IACH,eAAe,CAAC,QAAmC;QACjD,IAAI,CAAC,YAAY,GAAG,QAAQ,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,KAAiB;QAClC,MAAM,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QAChC,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QAExB,kEAAkE;QAClE,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAClD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAChC,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;IAEO,gBAAgB;QACtB,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QAEpB,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACjC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAG,CAAC;YAEtC,IAAI,IAAI,CAAC,kBAAkB,KAAK,CAAC,EAAE,CAAC;gBAClC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;YACrC,CAAC;YAED,IAAI,CAAC;gBACH,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACxB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,0CAA0C,EAAE;oBACxD,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;oBAC7D,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;oBAC1C,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;iBACpC,CAAC,CAAC;gBACH,MAAM;YACR,CAAC;YAED,IAAI,CAAC,kBAAkB,IAAI,KAAK,CAAC,iBAAiB,CAAC;QACrD,CAAC;QAED,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QAErB,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAChD,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,kBAAkB,CAAC;YACzD,MAAM,gBAAgB,GAAG,IAAI,CAAC,kBAAkB,GAAG,UAAU,CAAC;YAE9D,IAAI,CAAC,kBAAkB,CAAC;gBACtB,gBAAgB,EAAE,gBAAgB;gBAClC,WAAW,EAAE,KAAK;aACnB,CAAC,CAAC;YAEH,IAAI,CAAC,kBAAkB,GAAG,CAAC,CAAC;YAC5B,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACvB,CAAC;IACH,CAAC;IAED;;OAEG;IACK,SAAS,CAAC,KAAiB;QACjC,mBAAmB;QACnB,MAAM,SAAS,GAAG,gBAAgB,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QAE/D,gBAAgB;QAChB,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAEzD,4BAA4B;QAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC;YAC7B,KAAK,EAAE,OAAO;YACd,SAAS,EAAE,EAAE;YACb,cAAc,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE;YAC/B,KAAK,EAAE;gBACL,OAAO,EAAE,MAAM;aAChB;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;IAED,KAAK;QACH,KAAK,CAAC,KAAK,EAAE,CAAC;QAEd,gDAAgD;QAChD,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC;YAC9C,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAChC,CAAC;QAED,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QAEpB,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAChC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,kBAAkB,CAAC;YACzD,MAAM,gBAAgB,GAAG,IAAI,CAAC,kBAAkB,GAAG,UAAU,CAAC;YAE9D,IAAI,CAAC,kBAAkB,CAAC;gBACtB,gBAAgB,EAAE,gBAAgB;gBAClC,WAAW,EAAE,KAAK;aACnB,CAAC,CAAC;YAEH,IAAI,CAAC,kBAAkB,GAAG,CAAC,CAAC;YAC5B,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACvB,CAAC;aAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YACzB,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,WAAW;QACT,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,kBAAkB,CAAC;QACzD,MAAM,gBAAgB,GAAG,IAAI,CAAC,kBAAkB,GAAG,UAAU,CAAC;QAE9D,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC;QAEpB,IAAI,CAAC,kBAAkB,CAAC;YACtB,gBAAgB,EAAE,gBAAgB;YAClC,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;QAEH,IAAI,CAAC,kBAAkB,GAAG,CAAC,CAAC;QAC5B,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QAErB,+BAA+B;QAC/B,IAAI,CAAC;YACH,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC;gBAC/B,KAAK,EAAE,OAAO;gBACd,SAAS,EAAE,EAAE;gBACb,cAAc,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE;aAChC,CAAC,CAAC,CAAC;QACN,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,kDAAkD,EAAE;gBAChE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;gBAC7D,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aACpC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC;IACtB,CAAC;CACF"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G.711 μ-law codec implementation for Twilio Media Streams.
|
|
3
|
+
*
|
|
4
|
+
* Twilio uses G.711 μ-law (mu-law) encoding at 8kHz.
|
|
5
|
+
* This codec provides bidirectional conversion between PCM Int16
|
|
6
|
+
* and μ-law encoded Uint8.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Encode a PCM Int16 sample to μ-law Uint8.
|
|
10
|
+
*
|
|
11
|
+
* @param sample - 16-bit PCM sample (-32768 to 32767)
|
|
12
|
+
* @returns μ-law encoded byte (0-255)
|
|
13
|
+
*/
|
|
14
|
+
export declare function mulawEncode(sample: number): number;
|
|
15
|
+
/**
|
|
16
|
+
* Decode a μ-law Uint8 to PCM Int16.
|
|
17
|
+
*
|
|
18
|
+
* @param mulaw - μ-law encoded byte (0-255)
|
|
19
|
+
* @returns 16-bit PCM sample
|
|
20
|
+
*/
|
|
21
|
+
export declare function mulawDecode(mulaw: number): number;
|
|
22
|
+
/**
|
|
23
|
+
* Encode an array of PCM samples to μ-law.
|
|
24
|
+
*
|
|
25
|
+
* @param pcm - Int16Array of PCM samples
|
|
26
|
+
* @returns Uint8Array of μ-law encoded bytes
|
|
27
|
+
*/
|
|
28
|
+
export declare function mulawEncodeArray(pcm: Int16Array): Uint8Array;
|
|
29
|
+
/**
|
|
30
|
+
* Decode an array of μ-law bytes to PCM.
|
|
31
|
+
*
|
|
32
|
+
* @param mulaw - Uint8Array of μ-law encoded bytes
|
|
33
|
+
* @returns Int16Array of PCM samples
|
|
34
|
+
*/
|
|
35
|
+
export declare function mulawDecodeArray(mulaw: Uint8Array): Int16Array;
|
|
36
|
+
//# sourceMappingURL=g711.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"g711.d.ts","sourceRoot":"","sources":["../../src/codec/g711.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAmEH;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA2BlD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,UAAU,CAM5D;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAM9D"}
|