diff --git a/tts_engine.py b/tts_engine.py index bb5eb46..27b6c4d 100644 --- a/tts_engine.py +++ b/tts_engine.py @@ -41,6 +41,7 @@ import struct import subprocess import wave import logging +from bitarray import bitarray logger = logging.getLogger(__name__) @@ -71,6 +72,46 @@ DV3K_RATEP_DMR = bytes([ DV3K_PRODID_REQ = bytes([0x61, 0x00, 0x01, 0x00, 0x30]) +AMBE_SILENCE = bytes([0xAC, 0xAA, 0x40, 0x20, 0x00, 0x44, 0x40, 0x80, 0x80]) + + +def _interleave_ambe_to_dmr(frames_data): + ''' + Convert raw AMBE+2 frames (9 bytes/72 bits each, from DV3000) to DMR burst format. + + DMR voice bursts carry 3 AMBE frames per burst (216 bits = 2 x 108 bits). + The 3 frames are interleaved: burst_payload[i*3 + j] = frame[j].bit[i] + where i = 0..71 (bit index within frame), j = 0..2 (frame index). + + This matches ETSI TS 102 361-1 voice burst payload format. + The output is compatible with readAMBE.readSingleFile() which splits + the data into 108-bit pairs for mk_voice.pkt_gen(). + ''' + result = bitarray(endian='big') + + for idx in range(0, len(frames_data), 3): + f0 = frames_data[idx] if idx < len(frames_data) else AMBE_SILENCE + f1 = frames_data[idx + 1] if idx + 1 < len(frames_data) else AMBE_SILENCE + f2 = frames_data[idx + 2] if idx + 2 < len(frames_data) else AMBE_SILENCE + + b0 = bitarray(endian='big') + b0.frombytes(f0) + b1 = bitarray(endian='big') + b1.frombytes(f1) + b2 = bitarray(endian='big') + b2.frombytes(f2) + + burst = bitarray(216, endian='big') + burst.setall(False) + for i in range(72): + burst[i * 3 + 0] = b0[i] + burst[i * 3 + 1] = b1[i] + burst[i * 3 + 2] = b2[i] + + result.extend(burst) + + return result.tobytes() + def _get_tts_lang(announcement_language): if announcement_language in _LANG_MAP: @@ -278,9 +319,14 @@ def _encode_ambe_ambeserver(wav_path, ambe_path, host, port): return False try: + interleaved = _interleave_ambe_to_dmr(_ambe_frames) with open(ambe_path, 'wb') as f: - for frame in _ambe_frames: - f.write(frame) + f.write(interleaved) + _bursts = len(_ambe_frames) // 3 + if len(_ambe_frames) % 3: + _bursts += 1 + logger.info('(TTS-AMBESERVER) Entrelazado DMR: %d frames -> %d bursts (%d bytes)', + len(_ambe_frames), _bursts, len(interleaved)) except Exception as e: logger.error('(TTS-AMBESERVER) Error escribiendo archivo AMBE: %s', e) return False