init: 自 zomaintain/backend/rdplib 平移独立成库; module path 从上游 github.com/nakagami/grdp 改为 git.zeroonesoft.cn/golib/rdplib

This commit is contained in:
w11
2026-09-19 18:55:43 +08:00
commit 1096fbce9d
94 changed files with 36790 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
// Package aac provides AAC-to-PCM decoding for RDPSND audio streams.
package aac
import "git.zeroonesoft.cn/golib/rdplib/plugin/rdpsnd"
// Decoder decodes MPEG-4 AAC packets into signed 16-bit little-endian PCM.
type Decoder interface {
// Decode decodes one raw AAC packet. Returns nil, nil for empty input.
Decode(data []byte) ([]byte, error)
// Close releases all resources held by the decoder.
Close()
}
// New creates a Decoder for the given RDPSND AudioFormat.
// Returns an error on platforms where AAC decoding is not supported.
func New(format rdpsnd.AudioFormat) (Decoder, error) {
return newDecoder(format)
}
+210
View File
@@ -0,0 +1,210 @@
//go:build darwin && cgo
package aac
/*
#cgo LDFLAGS: -framework AudioToolbox
#cgo nocallback grdp_aac_converter_new
#cgo nocallback grdp_aac_decode
#cgo nocallback AudioConverterDispose
#cgo noescape grdp_aac_converter_new
#cgo noescape grdp_aac_decode
#include <AudioToolbox/AudioToolbox.h>
#include <stdlib.h>
typedef struct {
const uint8_t *data;
uint32_t size;
int consumed;
} grdp_aac_input_t;
// AudioConverter input callback — called by AudioToolbox to pull one AAC
// packet per invocation. Returns noErr on the first call, then -1 (no data)
// on subsequent calls to signal end-of-input for the current decode round.
static OSStatus grdp_aac_input_proc(
AudioConverterRef inConverter,
UInt32 *ioNumberDataPackets,
AudioBufferList *ioData,
AudioStreamPacketDescription **outDataPacketDescription,
void *inUserData)
{
grdp_aac_input_t *state = (grdp_aac_input_t *)inUserData;
if (state->consumed || state->size == 0) {
*ioNumberDataPackets = 0;
ioData->mBuffers[0].mData = NULL;
ioData->mBuffers[0].mDataByteSize = 0;
return -1;
}
*ioNumberDataPackets = 1;
ioData->mBuffers[0].mData = (void *)state->data;
ioData->mBuffers[0].mDataByteSize = state->size;
if (outDataPacketDescription != NULL) {
static AudioStreamPacketDescription desc;
desc.mStartOffset = 0;
desc.mDataByteSize = state->size;
desc.mVariableFramesInPacket = 0;
*outDataPacketDescription = &desc;
}
state->consumed = 1;
return noErr;
}
// grdp_aac_converter_new creates an AudioConverter that decodes MPEG-4 AAC at
// the given sample rate / channel count. asc/ascLen is the AudioSpecificConfig
// stored in the WAVEFORMATEX ExtraData field; it may be NULL/0 for ADTS input.
static AudioConverterRef grdp_aac_converter_new(
double sampleRate,
uint32_t channels,
const uint8_t *asc,
uint32_t ascLen,
OSStatus *outErr)
{
AudioStreamBasicDescription inFmt = {
.mSampleRate = sampleRate,
.mFormatID = kAudioFormatMPEG4AAC,
.mChannelsPerFrame = channels,
};
AudioStreamBasicDescription outFmt = {
.mSampleRate = sampleRate,
.mFormatID = kAudioFormatLinearPCM,
.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger |
kLinearPCMFormatFlagIsPacked,
.mBitsPerChannel = 16,
.mChannelsPerFrame = channels,
.mBytesPerFrame = (uint32_t)(2 * channels),
.mFramesPerPacket = 1,
.mBytesPerPacket = (uint32_t)(2 * channels),
};
AudioConverterRef conv = NULL;
OSStatus err = AudioConverterNew(&inFmt, &outFmt, &conv);
if (err != noErr) { *outErr = err; return NULL; }
if (ascLen > 0 && asc != NULL) {
err = AudioConverterSetProperty(
conv,
kAudioConverterDecompressionMagicCookie,
ascLen, asc);
if (err != noErr) {
AudioConverterDispose(conv);
*outErr = err;
return NULL;
}
}
*outErr = noErr;
return conv;
}
// grdp_aac_decode decodes one AAC packet (inData/inSize) into signed 16-bit
// little-endian PCM stored in outBuf. *outBytesWritten is set to the number
// of bytes actually written. Returns noErr on success.
static OSStatus grdp_aac_decode(
AudioConverterRef conv,
const uint8_t *inData,
uint32_t inSize,
uint8_t *outBuf,
uint32_t outBufSize,
uint32_t *outBytesWritten)
{
grdp_aac_input_t state = { inData, inSize, 0 };
AudioBufferList outList = {
.mNumberBuffers = 1,
.mBuffers[0] = {
.mNumberChannels = 0,
.mDataByteSize = outBufSize,
.mData = outBuf,
},
};
// AAC-LC produces 1024 samples/channel per frame; AAC-HE produces 2048.
// 4096 is large enough for both at up to 2 channels.
UInt32 numFrames = 4096;
OSStatus err = AudioConverterFillComplexBuffer(
conv, grdp_aac_input_proc, &state, &numFrames, &outList, NULL);
// -1 from the input callback signals "no more data"; that is not an error.
if (err != noErr && err != -1) {
*outBytesWritten = 0;
return err;
}
*outBytesWritten = outList.mBuffers[0].mDataByteSize;
return noErr;
}
*/
import "C"
import (
"fmt"
"log/slog"
"runtime"
"unsafe"
"git.zeroonesoft.cn/golib/rdplib/plugin/rdpsnd"
)
// darwinDecoder decodes MPEG-4 AAC audio into signed 16-bit PCM using
// macOS AudioToolbox (hardware-accelerated on Apple Silicon / Intel iGPU).
type darwinDecoder struct {
conv C.AudioConverterRef
channels int
outBuf []byte // reusable output scratch buffer
}
func newDecoder(format rdpsnd.AudioFormat) (Decoder, error) {
var oscErr C.OSStatus
var ascPtr *C.uint8_t
ascLen := C.uint32_t(0)
if len(format.ExtraData) > 0 {
ascPtr = (*C.uint8_t)(unsafe.Pointer(&format.ExtraData[0]))
ascLen = C.uint32_t(len(format.ExtraData))
}
conv := C.grdp_aac_converter_new(
C.double(format.SamplesPerSec),
C.uint32_t(format.Channels),
ascPtr, ascLen,
&oscErr)
if len(format.ExtraData) > 0 {
runtime.KeepAlive(format.ExtraData)
}
if conv == nil {
return nil, fmt.Errorf("AudioToolbox: AudioConverterNew failed (err=%d)", int32(oscErr))
}
slog.Info("AAC decoder created",
"rate", format.SamplesPerSec,
"channels", format.Channels,
"ascLen", len(format.ExtraData))
// 4096 samples × channels × 2 bytes/sample — large enough for AAC-HE at 2 ch.
outBufSize := 4096 * int(format.Channels) * 2
return &darwinDecoder{conv: conv, channels: int(format.Channels), outBuf: make([]byte, outBufSize)}, nil
}
// Decode decodes one raw AAC packet into signed 16-bit little-endian PCM.
// Returns nil, nil when data is empty.
func (d *darwinDecoder) Decode(data []byte) ([]byte, error) {
if len(data) == 0 {
return nil, nil
}
var written C.uint32_t
err := C.grdp_aac_decode(
d.conv,
(*C.uint8_t)(unsafe.Pointer(&data[0])),
C.uint32_t(len(data)),
(*C.uint8_t)(unsafe.Pointer(&d.outBuf[0])),
C.uint32_t(len(d.outBuf)),
&written)
runtime.KeepAlive(data)
runtime.KeepAlive(d.outBuf)
if err != 0 {
return nil, fmt.Errorf("AudioToolbox: decode error %d", int32(err))
}
if written == 0 {
return nil, nil
}
out := make([]byte, int(written))
copy(out, d.outBuf[:written])
return out, nil
}
// Close releases the AudioConverter.
func (d *darwinDecoder) Close() {
if d.conv != nil {
C.AudioConverterDispose(d.conv)
d.conv = nil
}
}
+22
View File
@@ -0,0 +1,22 @@
//go:build !darwin || !cgo
package aac
import (
"errors"
"git.zeroonesoft.cn/golib/rdplib/plugin/rdpsnd"
)
// stubDecoder is used on platforms without AudioToolbox support.
type stubDecoder struct{}
func newDecoder(_ rdpsnd.AudioFormat) (Decoder, error) {
return nil, errors.New("AAC decoding not supported on this platform")
}
func (d *stubDecoder) Decode(_ []byte) ([]byte, error) {
return nil, errors.New("AAC decoding not supported on this platform")
}
func (d *stubDecoder) Close() {}