Merge pull request #36 from outis1one/claude/integrate-talkkonnect-install-016rfpEaspgs7mfn2XHEVj7W

Fix talkkonnect x86_64 build failure with Opus patch
This commit is contained in:
outis1one
2025-11-22 19:26:11 -05:00
committed by GitHub
+357 -49
View File
@@ -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 <stdio.h>
// #include <stdlib.h>
// #include <opus.h>
//
// 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 <<TKXML
<?xml version="1.0" encoding="UTF-8"?>
<document type="talkkonnect.org/talkkonnect">
<document type="talkkonnect/xml">
<global>
<software>
<settings outputdevice="default"
logfilenameandpath="/home/$KIOSK_USER/.config/talkkonnect/talkkonnect.log"
logging="both"
daemonize="false"
cancelconnect="false"
simplexwithvox="false"
nextserverindex="0"/>
<autoprovisioning enabled="false"/>
</software>
<hardware>
<settings targetboard="pc"
voiceactivitytimersecs="200"/>
<io>
<pins enabled="false"/>
</io>
</hardware>
</global>
<accounts>
<account>
<default>true</default>
<name>Primary</name>
<server>$server_addr</server>
<account name="default" default="true">
<serverandport>$server_addr:$server_port</serverandport>
<username>$tk_user</username>
<password>$tk_pass</password>
<insecure>true</insecure>
<port>$server_port</port>
<register>false</register>
<certificate></certificate>
<channel>$tk_channel</channel>
<ident></ident>
<tokens enabled="false"></tokens>
<voicetargets enabled="false">
<id id="1" iscurrent="false" name="default">
<channels></channels>
<users></users>
</id>
</voicetargets>
</account>
</accounts>
<software>
<settings>
<outputdevice>default</outputdevice>
<loglevel>3</loglevel>
<cancellingnoisegate>false</cancellingnoisegate>
<simplex>true</simplex>
</settings>
</software>
<hardware>
<ledstrip><enabled>false</enabled></ledstrip>
<targetboard>rpi</targetboard>
<buttons>
<button>
<enabled>true</enabled>
<name>Transmit</name>
<pin>spacebar</pin>
<action>transmit</action>
</button>
</buttons>
</hardware>
<beacon enabled="false"/>
<audio>
<input>
<enabled>true</enabled>
<device>default</device>
<samplerate>48000</samplerate>
<channels>1</channels>
<settings enabled="true"
device="default"
samplerate="48000"
channels="1"
codec="opus"
framespersecond="50"/>
</input>
<output>
<enabled>true</enabled>
<device>default</device>
<samplerate>48000</samplerate>
<channels>2</channels>
<volume>100</volume>
<settings enabled="true"
device="default"
samplerate="48000"
channels="1"/>
</output>
</audio>
<ptt enabled="false">
<usbkeyboard enabled="false"/>
</ptt>
<voiceactivity enabled="true">
<settings threshold="0.3"
holdtimems="1000"
holdtimeoutms="2000"/>
</voiceactivity>
<sounds enabled="false"/>
<txtts enabled="false"/>
<smtp enabled="false"/>
<api enabled="false"/>
<printxml enabled="false"/>
</document>
TKXML