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
|
||||
|
||||
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user