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:
+357
-49
@@ -217,16 +217,13 @@ install_talkkonnect_with_config() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[2/4] Installing audio dependencies..."
|
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..."
|
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)..."
|
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 "
|
sudo -u "$KIOSK_USER" bash -c "
|
||||||
export PATH=/usr/local/go/bin:\$PATH
|
export PATH=/usr/local/go/bin:\$PATH
|
||||||
export HOME=/home/$KIOSK_USER
|
export HOME=/home/$KIOSK_USER
|
||||||
@@ -235,13 +232,287 @@ install_talkkonnect_with_config() {
|
|||||||
|
|
||||||
mkdir -p /home/$KIOSK_USER/go/bin
|
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...'
|
if [[ -d talkkonnect ]]; then
|
||||||
go build -v -o /home/$KIOSK_USER/go/bin/talkkonnect . 2>&1 | tail -20
|
echo 'Removing existing talkkonnect directory...'
|
||||||
|
rm -rf talkkonnect
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ -f /home/$KIOSK_USER/go/bin/talkkonnect ]]; then
|
git clone https://github.com/talkkonnect/talkkonnect.git
|
||||||
chmod +x /home/$KIOSK_USER/go/bin/talkkonnect
|
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'
|
echo 'Build successful'
|
||||||
exit 0
|
exit 0
|
||||||
else
|
else
|
||||||
@@ -251,7 +522,6 @@ install_talkkonnect_with_config() {
|
|||||||
"
|
"
|
||||||
|
|
||||||
local build_result=$?
|
local build_result=$?
|
||||||
rm -rf "$tk_src"
|
|
||||||
|
|
||||||
if [[ $build_result -ne 0 ]]; then
|
if [[ $build_result -ne 0 ]]; then
|
||||||
log_error "Build failed"
|
log_error "Build failed"
|
||||||
@@ -264,70 +534,108 @@ install_talkkonnect_with_config() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Verify binary
|
# Verify and install binary
|
||||||
if [[ ! -x "/home/$KIOSK_USER/go/bin/talkkonnect" ]]; then
|
if [[ ! -x "/home/$KIOSK_USER/talkkonnect-binary" ]]; then
|
||||||
log_error "Binary not executable after build"
|
log_error "Binary not executable after build"
|
||||||
pause
|
pause
|
||||||
return 1
|
return 1
|
||||||
fi
|
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"
|
log_success "talkkonnect built successfully"
|
||||||
|
|
||||||
echo "[4/4] Creating configuration..."
|
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
|
sudo -u "$KIOSK_USER" tee /home/$KIOSK_USER/talkkonnect.xml > /dev/null <<TKXML
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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>
|
<accounts>
|
||||||
<account>
|
<account name="default" default="true">
|
||||||
<default>true</default>
|
<serverandport>$server_addr:$server_port</serverandport>
|
||||||
<name>Primary</name>
|
|
||||||
<server>$server_addr</server>
|
|
||||||
<username>$tk_user</username>
|
<username>$tk_user</username>
|
||||||
<password>$tk_pass</password>
|
<password>$tk_pass</password>
|
||||||
<insecure>true</insecure>
|
<insecure>true</insecure>
|
||||||
<port>$server_port</port>
|
<register>false</register>
|
||||||
|
<certificate></certificate>
|
||||||
<channel>$tk_channel</channel>
|
<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>
|
</account>
|
||||||
</accounts>
|
</accounts>
|
||||||
|
|
||||||
<software>
|
<beacon enabled="false"/>
|
||||||
<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>
|
|
||||||
|
|
||||||
<audio>
|
<audio>
|
||||||
<input>
|
<input>
|
||||||
<enabled>true</enabled>
|
<settings enabled="true"
|
||||||
<device>default</device>
|
device="default"
|
||||||
<samplerate>48000</samplerate>
|
samplerate="48000"
|
||||||
<channels>1</channels>
|
channels="1"
|
||||||
|
codec="opus"
|
||||||
|
framespersecond="50"/>
|
||||||
</input>
|
</input>
|
||||||
<output>
|
<output>
|
||||||
<enabled>true</enabled>
|
<settings enabled="true"
|
||||||
<device>default</device>
|
device="default"
|
||||||
<samplerate>48000</samplerate>
|
samplerate="48000"
|
||||||
<channels>2</channels>
|
channels="1"/>
|
||||||
<volume>100</volume>
|
|
||||||
</output>
|
</output>
|
||||||
</audio>
|
</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>
|
</document>
|
||||||
TKXML
|
TKXML
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user