Я постараюсь быть максимально кратким, но я сделал довольно много, прежде чем дошел до публикации вопроса на SO...
TLDR: у меня есть звук, который я получаю от Google TTS API . У меня есть двунаправленный поток от Twilio, на который я хотел бы отправить свое tts-аудио через мультимедийное сообщение Twilio, чтобы Twilio воспроизводил его для вызывающего абонента. Однако все, что я слышу как звонящий, это громкий статический визг. Итак, мои вопросы:
ffmpeg
и sox
или с Audacity в отношении приемлемого звука?Некоторые ответы на вероятные вопросы:
Twilio указывает, что аудио, отправляемое на него, должно быть audio/x-mulaw
-кодировано с частотой дискретизации 8000 Гц. Вы уверены, что то, что вы отправляете, соответствует этому требованию? Да. Когда я делаю запрос TTS в Google, я указываю, что хочу, чтобы звук был закодирован в мулау с частотой 8000 Гц. Google отправляет звук обратно в виде байтов в кодировке base64; когда я декодирую возвращенную строку base64 и сохраняю ее в файл на диске, и ffprobe
, и soxi
подтверждают, что она действительно закодирована в mulaw и с частотой дискретизации 8000 Гц.
Когда Google кодирует звук TTS как mulaw, он прикрепляет к результату заголовок wav
. Twilio говорит, что отправляемые ему медиафайлы не должны содержать заголовков. Вы уверены, что отправляете в Twilio только необработанные аудиобайты? Да. Когда я получаю результат от Google, я сначала декодирую строку base64, затем вырезаю первые 44 байта (размер заголовка wav
), а base64 кодирую только оставшиеся байты для отправки в Twilio. Я знаю, что байты, которые я обрезал, являются правильными, потому что я записал их в файл на диске, а затем импортировал их в Audacity как mulaw, необработанные аудиоданные с частотой 8000 Гц, и Audacity отлично воспроизводит звук.
Правильно ли вы кодируете base64 свой mulaw/8000 байт? Я полагаю, что это может быть вопрос без ответа (с моей стороны), но я так думаю. Если я пишу строку base64 (закодированную «стандартным» движком), я отправляю Twilio в файл test.enc
на диске, затем запускаю base64 test.enc -d > unk.dat
, я могу импортировать unk.dat
в Audacity как необработанные данные mulaw/8000, и он воспроизводится. В своем приложении я пробовал весь спектр распространенных движков base64: стандартный, стандартный без подкладки, безопасный для URL, безопасный для URL без подкладки. Ни один из них не дает хороших результатов.
Правильно ли вы форматируете мультимедийное сообщение для Twilio? Да. По крайней мере, сообщение, которое я отправляю, выглядит как пример здесь.
Является ли мультимедийное сообщение, которое вы отправляете в Twilio, текстовым сообщением WS? Да.
Я помню, как где-то в Интернете я видел, как кто-то предлагал отправить сообщение «Отметить» после сообщения «Медиа» в Twilio. Вы пробовали это для хихиканья? Да.
Какой-то уродливый код на Rust:
// Rust code that does and returns the equivalent of the steps at
// https://cloud.google.com/text-to-speech/docs/create-audio-text-command-line#synthesize_audio_from_text
let audio_config = AudioConfig {
audio_encoding: Some(AudioConfigAudioEncoding::MULAW),
sample_rate_hertz: Some(8_000),
..Default::default()
};
// Other things for Google TTS API...
let payload = synthesize_response.audio_content.unwrap();
println!("{payload}");
// `payload` is the base64-encoded mulaw/8000 bytes plus a `wav` header
// Base64-decode `payload`
let mut enc = Cursor::new(payload);
let mut decoder = read::DecoderReader::new(&mut enc, &engine::general_purpose::STANDARD);
let mut body = Vec::new();
decoder.read_to_end(&mut body).unwrap();
// `body` is now raw u8's; if written to a file on disk, `ffprobe` and `soxi` recognize it as
// mulaw/8000 audio; `play` can play it.
// Trim `wav` header from Google's response
let trimmed = &body[44..];
// `trimmed` is headerless mulaw/8000 audio; if written to a disk, it can be imported into
// Audacity as mulaw-encoded, 8000Hz audio and played.
// base64-encode the trimmed raw audio
let re_encoded: String = engine::general_purpose::STANDARD.encode(trimmed);
// Construct a Media message to send to Twilio.
let outbound_media_meta = OutboundMediaMeta {
payload: re_encoded,
};
let outbound_media = TwilioOutbound::Media {
media: outbound_media_meta,
stream_sid: stream_sid.clone(),
};
let json = serde_json::to_string(&outbound_media).unwrap();
println!("{json}");
// We can verify that the json content is of the right format for a Media message to be
// consumed by Twilio (not obvious here is that the `"event": "media"` tag is present).
let message = Message::Text(json);
sender.send(message).await.unwrap();
Инженер технической поддержки Twilio здесь. Из моего опыта работы с двунаправленными билетами Media Stream я заметил, что заголовки WAV, которые Google, Amazon, Microsoft и т. д. отправляют обратно, имеют размер не 44 байта, а 58 байтов. Кажется, что стандарт WAV допускает дополнительные метаданные, которые увеличивают размер.
В качестве теста вы можете преобразовать ответ base64 от Google и сохранить файл в виде файла WAV на своем компьютере. Обратите внимание на размер файла. Затем откройте файл в Audacity и в Audacity, экспортируйте аудио с заголовком RAW (header-less)
и сохраните его как другой файл на своем компьютере. Если вы затем проверите размер файла и сравните его с исходным файлом, вы сможете определить размер заголовка.
Если вы все еще сталкиваетесь с той же проблемой после удаления 58 байтов, включите Отслеживание голоса в своей учетной записи, сделайте несколько новых тестовых звонков, а затем отправьте запрос в службу поддержки через консоль, и мы будем рады проверить SID звонка.
@wayeast Я рад слышать, что проблема решена. Нам не терпится увидеть, что вы строите!
Спасибо @Daniel O. Я смог воспроизвести ваш тест, как описано, и он показал, что аудиобайты без заголовка были на 58 байт меньше, чем
wav
, возвращенный Google. Ранее я взломал обходной путь, запросив кодировку linear16 у Google, а затем используя небольшую процедуру, скопированную из SO, чтобы преобразовать ее в mulaw. Этот хак сработал, но казался более неприятным, чем просто использование mulaw, закодированного Google. Разблокирован, правильный путь. Спасибо.