Fix talkkonnect x86_64 build failure with Opus patch
Critical fixes to resolve "[ERROR] Binary not executable after build": 1. **Opus x86_64 Compatibility Patch** - Added gopus library patch to use system libopus for x86_64 - The embedded Opus source in gopus is ARM-optimized and incomplete for x86_64 - Now uses pkg-config to link against system libopus library 2. **Vendored Dependencies** - Added `go mod vendor` to create vendored dependencies - Build now uses `-mod=vendor` flag to ensure patched gopus is used 3. **Enhanced Build Process** - Set CGO_CFLAGS and CGO_LDFLAGS for proper Opus compilation - Build in cmd/talkkonnect directory (correct location) - Added build-essential and pkg-config to dependencies 4. **Improved XML Configuration** - Updated to modern talkkonnect/xml document type - Added critical voicetargets section to prevent crashes - Added proper global settings structure - Created .config/talkkonnect directory for logs 5. **Additional Dependencies** - Added libopus0, libopusfile-dev for complete Opus support - Added build-essential and pkg-config for compilation This integrates the working approach from talkkonnect_complete_install.sh into the setup_intercom.sh script.
This commit is contained in:
+336
-39
@@ -217,7 +217,7 @@ 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"
|
||||
@@ -237,8 +237,273 @@ install_talkkonnect_with_config() {
|
||||
|
||||
cd '$tk_src' || exit 1
|
||||
|
||||
echo 'Compiling...'
|
||||
go build -v -o /home/$KIOSK_USER/go/bin/talkkonnect . 2>&1 | tail -20
|
||||
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/go/bin/talkkonnect . 2>&1 | tail -20
|
||||
|
||||
if [[ -f /home/$KIOSK_USER/go/bin/talkkonnect ]]; then
|
||||
chmod +x /home/$KIOSK_USER/go/bin/talkkonnect
|
||||
@@ -275,59 +540,91 @@ install_talkkonnect_with_config() {
|
||||
|
||||
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