diff --git a/setup_intercom.sh b/setup_intercom.sh index 8f6ccbc..1b44b4e 100755 --- a/setup_intercom.sh +++ b/setup_intercom.sh @@ -217,16 +217,13 @@ install_talkkonnect_with_config() { fi echo "[2/4] Installing audio dependencies..." - sudo apt install -y libopenal-dev libopus-dev alsa-utils portaudio19-dev git + sudo apt install -y libopenal-dev libopus-dev libopus0 libopusfile-dev alsa-utils portaudio19-dev git build-essential pkg-config echo "[3/4] Cloning and building talkkonnect..." - local tk_src="/tmp/talkkonnect-src" - rm -rf "$tk_src" - git clone https://github.com/talkkonnect/talkkonnect.git "$tk_src" echo "Building (this takes 5-10 minutes)..." - # Build as kiosk user with proper environment + # Build as kiosk user with proper environment (using their home directory to avoid permission issues) sudo -u "$KIOSK_USER" bash -c " export PATH=/usr/local/go/bin:\$PATH export HOME=/home/$KIOSK_USER @@ -235,13 +232,287 @@ install_talkkonnect_with_config() { mkdir -p /home/$KIOSK_USER/go/bin - cd '$tk_src' || exit 1 + # Clone to kiosk user's home directory (avoids /tmp permission issues) + cd /home/$KIOSK_USER || exit 1 - echo 'Compiling...' - go build -v -o /home/$KIOSK_USER/go/bin/talkkonnect . 2>&1 | tail -20 + if [[ -d talkkonnect ]]; then + echo 'Removing existing talkkonnect directory...' + rm -rf talkkonnect + fi - if [[ -f /home/$KIOSK_USER/go/bin/talkkonnect ]]; then - chmod +x /home/$KIOSK_USER/go/bin/talkkonnect + git clone https://github.com/talkkonnect/talkkonnect.git + cd talkkonnect || exit 1 + + echo 'Creating vendored dependencies...' + go mod vendor + + # Apply critical x86_64 Opus patch + echo 'Applying x86_64 Opus compatibility patch...' + if [[ -f vendor/github.com/talkkonnect/gopus/opus_nonshared.go ]]; then + cp vendor/github.com/talkkonnect/gopus/opus_nonshared.go \ + vendor/github.com/talkkonnect/gopus/opus_nonshared.go.backup + + cat > vendor/github.com/talkkonnect/gopus/opus_nonshared.go << 'EOFOPUS' +// +build amd64,cgo 386,cgo + +package gopus + +// #cgo pkg-config: opus +// #cgo LDFLAGS: -lm +// +// #include +// #include +// #include +// +// enum { +// gopus_ok = OPUS_OK, +// gopus_bad_arg = OPUS_BAD_ARG, +// gopus_small_buffer = OPUS_BUFFER_TOO_SMALL, +// gopus_internal = OPUS_INTERNAL_ERROR, +// gopus_invalid_packet = OPUS_INVALID_PACKET, +// gopus_unimplemented = OPUS_UNIMPLEMENTED, +// gopus_invalid_state = OPUS_INVALID_STATE, +// gopus_alloc_fail = OPUS_ALLOC_FAIL, +// }; +// +// enum { +// gopus_application_voip = OPUS_APPLICATION_VOIP, +// gopus_application_audio = OPUS_APPLICATION_AUDIO, +// gopus_restricted_lowdelay = OPUS_APPLICATION_RESTRICTED_LOWDELAY, +// gopus_bitrate_max = OPUS_BITRATE_MAX, +// }; +// +// void gopus_setvbr(OpusEncoder *encoder, int vbr) { +// opus_encoder_ctl(encoder, OPUS_SET_VBR(vbr)); +// } +// +// void gopus_setbitrate(OpusEncoder *encoder, int bitrate) { +// opus_encoder_ctl(encoder, OPUS_SET_BITRATE(bitrate)); +// } +// +// opus_int32 gopus_bitrate(OpusEncoder *encoder) { +// opus_int32 bitrate; +// opus_encoder_ctl(encoder, OPUS_GET_BITRATE(&bitrate)); +// return bitrate; +// } +// +// void gopus_setapplication(OpusEncoder *encoder, int application) { +// opus_encoder_ctl(encoder, OPUS_SET_APPLICATION(application)); +// } +// +// opus_int32 gopus_application(OpusEncoder *encoder) { +// opus_int32 application; +// opus_encoder_ctl(encoder, OPUS_GET_APPLICATION(&application)); +// return application; +// } +// +// void gopus_encoder_resetstate(OpusEncoder *encoder) { +// opus_encoder_ctl(encoder, OPUS_RESET_STATE); +// } +// +// void gopus_decoder_resetstate(OpusDecoder *decoder) { +// opus_decoder_ctl(decoder, OPUS_RESET_STATE); +// } +import \"C\" + +import ( + \"errors\" + \"unsafe\" +) + +type Application int + +const ( + Voip Application = C.gopus_application_voip + Audio Application = C.gopus_application_audio + RestrictedLowDelay Application = C.gopus_restricted_lowdelay +) + +const ( + BitrateMaximum = C.gopus_bitrate_max +) + +type Encoder struct { + data []byte + cEncoder *C.struct_OpusEncoder +} + +func NewEncoder(sampleRate, channels int, application Application) (*Encoder, error) { + encoder := &Encoder{} + encoder.data = make([]byte, int(C.opus_encoder_get_size(C.int(channels)))) + encoder.cEncoder = (*C.struct_OpusEncoder)(unsafe.Pointer(&encoder.data[0])) + + ret := C.opus_encoder_init(encoder.cEncoder, C.opus_int32(sampleRate), C.int(channels), C.int(application)) + if err := getErr(ret); err != nil { + return nil, err + } + return encoder, nil +} + +func (e *Encoder) Encode(pcm []int16, frameSize, maxDataBytes int) ([]byte, error) { + pcmPtr := (*C.opus_int16)(unsafe.Pointer(&pcm[0])) + + data := make([]byte, maxDataBytes) + dataPtr := (*C.uchar)(unsafe.Pointer(&data[0])) + + encodedC := C.opus_encode(e.cEncoder, pcmPtr, C.int(frameSize), dataPtr, C.opus_int32(len(data))) + encoded := int(encodedC) + + if encoded < 0 { + return nil, getErr(C.int(encodedC)) + } + return data[0:encoded], nil +} + +func (e *Encoder) SetVbr(vbr bool) { + var cVbr C.int + if vbr { + cVbr = 1 + } else { + cVbr = 0 + } + C.gopus_setvbr(e.cEncoder, cVbr) +} + +func (e *Encoder) SetBitrate(bitrate int) { + C.gopus_setbitrate(e.cEncoder, C.int(bitrate)) +} + +func (e *Encoder) Bitrate() int { + return int(C.gopus_bitrate(e.cEncoder)) +} + +func (e *Encoder) SetApplication(application Application) { + C.gopus_setapplication(e.cEncoder, C.int(application)) +} + +func (e *Encoder) Application() Application { + return Application(C.gopus_application(e.cEncoder)) +} + +func (e *Encoder) ResetState() { + C.gopus_encoder_resetstate(e.cEncoder) +} + +type Decoder struct { + data []byte + cDecoder *C.struct_OpusDecoder + channels int +} + +func NewDecoder(sampleRate, channels int) (*Decoder, error) { + decoder := &Decoder{} + decoder.data = make([]byte, int(C.opus_decoder_get_size(C.int(channels)))) + decoder.cDecoder = (*C.struct_OpusDecoder)(unsafe.Pointer(&decoder.data[0])) + + ret := C.opus_decoder_init(decoder.cDecoder, C.opus_int32(sampleRate), C.int(channels)) + if err := getErr(ret); err != nil { + return nil, err + } + decoder.channels = channels + + return decoder, nil +} + +func (d *Decoder) Decode(data []byte, frameSize int, fec bool) ([]int16, error) { + var dataPtr *C.uchar + if len(data) > 0 { + dataPtr = (*C.uchar)(unsafe.Pointer(&data[0])) + } + dataLen := C.opus_int32(len(data)) + + output := make([]int16, d.channels*frameSize) + outputPtr := (*C.opus_int16)(unsafe.Pointer(&output[0])) + + var cFec C.int + if fec { + cFec = 1 + } else { + cFec = 0 + } + + cRet := C.opus_decode(d.cDecoder, dataPtr, dataLen, outputPtr, C.int(frameSize), cFec) + ret := int(cRet) + + if ret < 0 { + return nil, getErr(cRet) + } + return output[:ret*d.channels], nil +} + +func (d *Decoder) ResetState() { + C.gopus_decoder_resetstate(d.cDecoder) +} + +func GetSamplesPerFrame(data []byte, samplingRate int) (int, error) { + dataPtr := (*C.uchar)(unsafe.Pointer(&data[0])) + cSamplingRate := C.opus_int32(samplingRate) + cRet := C.opus_packet_get_samples_per_frame(dataPtr, cSamplingRate) + return int(cRet), nil +} + +func CountFrames(data []byte) (int, error) { + dataPtr := (*C.uchar)(unsafe.Pointer(&data[0])) + cLen := C.opus_int32(len(data)) + + cRet := C.opus_packet_get_nb_frames(dataPtr, cLen) + if err := getErr(cRet); err != nil { + return 0, err + } + return int(cRet), nil +} + +var ( + ErrBadArgument = errors.New(\"bad argument\") + ErrSmallBuffer = errors.New(\"buffer is too small\") + ErrInternal = errors.New(\"internal error\") + ErrInvalidPacket = errors.New(\"invalid packet\") + ErrUnimplemented = errors.New(\"unimplemented\") + ErrInvalidState = errors.New(\"invalid state\") + ErrAllocFail = errors.New(\"allocation failed\") + ErrUnknown = errors.New(\"unknown error\") +) + +func getErr(code C.int) error { + switch code { + case C.gopus_ok: + return nil + case C.gopus_bad_arg: + return ErrBadArgument + case C.gopus_small_buffer: + return ErrSmallBuffer + case C.gopus_internal: + return ErrInternal + case C.gopus_invalid_packet: + return ErrInvalidPacket + case C.gopus_unimplemented: + return ErrUnimplemented + case C.gopus_invalid_state: + return ErrInvalidState + case C.gopus_alloc_fail: + return ErrAllocFail + default: + return ErrUnknown + } +} +EOFOPUS + echo 'Opus patch applied' + else + echo 'Warning: gopus not found in vendor directory' + fi + + # Set CGO flags for Opus + export CGO_CFLAGS=\"\$(pkg-config --cflags opus)\" + export CGO_LDFLAGS=\"\$(pkg-config --libs opus) -lm\" + + # Build in the cmd/talkkonnect directory with vendored dependencies + cd cmd/talkkonnect || exit 1 + + echo 'Compiling with vendored dependencies...' + go build -mod=vendor -v -o /home/$KIOSK_USER/talkkonnect-binary . 2>&1 | tail -20 + + if [[ -f /home/$KIOSK_USER/talkkonnect-binary ]]; then + chmod +x /home/$KIOSK_USER/talkkonnect-binary echo 'Build successful' exit 0 else @@ -251,7 +522,6 @@ install_talkkonnect_with_config() { " local build_result=$? - rm -rf "$tk_src" if [[ $build_result -ne 0 ]]; then log_error "Build failed" @@ -264,70 +534,108 @@ install_talkkonnect_with_config() { return 1 fi - # Verify binary - if [[ ! -x "/home/$KIOSK_USER/go/bin/talkkonnect" ]]; then + # Verify and install binary + if [[ ! -x "/home/$KIOSK_USER/talkkonnect-binary" ]]; then log_error "Binary not executable after build" pause return 1 fi + echo "Installing binary..." + sudo cp /home/$KIOSK_USER/talkkonnect-binary /home/$KIOSK_USER/go/bin/talkkonnect + sudo chmod +x /home/$KIOSK_USER/go/bin/talkkonnect + sudo chown "$KIOSK_USER:$KIOSK_USER" /home/$KIOSK_USER/go/bin/talkkonnect + rm -f /home/$KIOSK_USER/talkkonnect-binary + log_success "talkkonnect built successfully" echo "[4/4] Creating configuration..." + # Create config directory for logs + sudo -u "$KIOSK_USER" mkdir -p /home/$KIOSK_USER/.config/talkkonnect + sudo -u "$KIOSK_USER" tee /home/$KIOSK_USER/talkkonnect.xml > /dev/null < - + + + + + + + + + + + + + + - - true - Primary - $server_addr + + $server_addr:$server_port $tk_user $tk_pass true - $server_port + false + $tk_channel + + + + + + + + - - - default - 3 - false - true - - - - - false - rpi - - - - + + + + + + + + + + + + + + + + + + + + TKXML