Documentation
¶
Overview ¶
Package voice provides the voice-decoding plumbing that sits between the trunking engine and the audio output / recording layer.
The package layout:
- vocoder.go a Vocoder interface + thread-safe Registry. Default build registers NullVocoder (silence), the pure-Go IMBE decoder from internal/voice/imbe, and the pure-Go AMBE+2 decoder from internal/voice/ambe2.
- wav.go 16-bit PCM mono WAV writer with length-fields patched on Close (so the file is valid even if the daemon dies).
- recorder.go subscribes to CallStart / CallEnd events from the trunking engine, opens a per-call WAV file (and an optional raw-frame sidecar) under a configurable directory tree, and exposes WritePCM / WriteRawFrame for the demod pipeline to push samples into.
IMBE patents have expired; AMBE+2 carries active patents in some jurisdictions and re-implementing it in pure Go does not change that posture. Operators in licence-restrictive jurisdictions should evaluate before deploying. See docs/vocoders.md for the full picture.
Index ¶
- Variables
- func DecodeStream(in io.Reader, vocoderName string, out io.WriteSeeker) (int, error)
- func DecodeStreamWithVocoder(in io.Reader, v Vocoder, out io.WriteSeeker) (int, error)
- func DefaultVocoderForProtocol() map[string]string
- func NormalizeWAVFile(path string, cfg NormalizeConfig) error
- func ReadWAVSamples(path string) (samples []int16, sampleRate uint32, err error)
- type DecodedPCMCallSink
- type DecodedPCMSink
- type ErrorAware
- type NormalizeConfig
- type NullVocoder
- type RawFrameCallSink
- type RawFrameSink
- type Recorder
- func (r *Recorder) Close() error
- func (r *Recorder) HasSession(deviceSerial string) bool
- func (r *Recorder) RecordingEnabled() bool
- func (r *Recorder) Run(ctx context.Context) error
- func (r *Recorder) SessionCount() int
- func (r *Recorder) SetDecodedPCMSink(s DecodedPCMSink)
- func (r *Recorder) SetRawFrameSink(s RawFrameSink)
- func (r *Recorder) SetRecordingEnabled(enabled bool)
- func (r *Recorder) SetVoiceEnhance(cfg mbe.EnhancerConfig)
- func (r *Recorder) VoiceEnhance() mbe.EnhancerConfig
- func (r *Recorder) WritePCM(deviceSerial string, samples []int16) error
- func (r *Recorder) WriteRawFrame(deviceSerial string, frame []byte) error
- func (r *Recorder) WriteRawFrameForCall(deviceSerial string, callID uint64, frame []byte, correctedBits int) error
- func (r *Recorder) WriteRawFrameWithErrors(deviceSerial string, frame []byte, correctedBits int) error
- type RecorderOptions
- type Registry
- type StatProvider
- type Vocoder
- type VocoderFactory
- type VoiceEnhanceable
- type VoiceStats
- type WavWriter
Constants ¶
This section is empty.
Variables ¶
var DefaultRegistry = NewRegistry()
DefaultRegistry is process-global; init() in subpackages registers here.
var ErrNoVocoder = errors.New("voice: no vocoder registered for that name")
ErrNoVocoder is returned by recorders when a CallStart references a vocoder name that isn't registered in the build.
var ErrPartialFrame = errors.New("voice: input ended mid-frame")
ErrPartialFrame is returned by DecodeStream when the input ends in the middle of a vocoder frame — the trailing bytes don't make up a complete frame for the chosen vocoder. Callers can inspect the byte count returned alongside the error to decide whether the partial trailer is recoverable (typically: it isn't).
Functions ¶
func DecodeStream ¶
DecodeStream reads vocoder frames from in, decodes each via the named vocoder from DefaultRegistry, and writes 8 kHz / 16-bit / mono PCM as a WAV stream to out. Returns the number of frames decoded successfully.
out must be an io.WriteSeeker so the WAV header length fields can be patched on close (file handles satisfy this; in-memory callers can wrap a bytes.Buffer with a seeker shim).
Frame size is determined by the chosen vocoder via FrameSize(). Input must be an exact multiple of that frame size; trailing bytes are reported via ErrPartialFrame after the leading complete frames have been written.
On a per-frame Decode error, DecodeStream stops and returns the number of frames decoded so far + the error. The WAV is closed (length fields patched) before returning so callers get a playable file even on partial decode.
func DecodeStreamWithVocoder ¶
DecodeStreamWithVocoder is the lower-level entry point: callers supply a constructed Vocoder (so they can pin reproducibility via NewWithSeed or tune AGC via NewWithConfig before handing it off). Behaviour matches DecodeStream — see that function's doc for the contract.
The Vocoder is not Reset before use; the caller controls initial state. The caller is responsible for closing v.
func DefaultVocoderForProtocol ¶
DefaultVocoderForProtocol returns the Protocol → vocoder-name mapping NewRecorder uses when RecorderOptions.VocoderForProtocol is nil. The keys match the strings the radio decoders set on Grant.Protocol; the values match factory names registered into voice.DefaultRegistry by the imbe / ambe2 package init()s.
Callers wanting to override one entry should start with a copy of this map (DefaultVocoderForProtocol() returns a fresh map per call) and mutate from there — RecorderOptions.VocoderForProtocol is taken as-is, no merging.
func NormalizeWAVFile ¶ added in v0.4.0
func NormalizeWAVFile(path string, cfg NormalizeConfig) error
NormalizeWAVFile applies loudness normalization to the WAV at path, filling in default parameters for any zero fields in cfg. It is the exported entry point for offline tools (e.g. `gophertrunk decode -normalize`); the recorder uses the internal path with defaults already applied at construction.
func ReadWAVSamples ¶ added in v0.4.0
ReadWAVSamples reads a 16-bit PCM mono WAV file written by WavWriter (or any canonical PCM WAV) and returns its samples and sample rate. It walks the RIFF chunks so it tolerates extra chunks before "data", but only accepts 16-bit mono PCM — the recorder's output contract.
Types ¶
type DecodedPCMCallSink ¶ added in v0.4.9
type DecodedPCMCallSink interface {
WritePCMForCall(deviceSerial string, callID uint64, samples []int16) error
}
DecodedPCMCallSink is the call-aware extension of DecodedPCMSink: it carries the CallID the decoded PCM belongs to so the live fan-out can fence a stale frame from a previous call on a reused voice-tap serial — the same cross-call audio-bleed guard sessionForWrite applies to the WAV. Wideband voice taps reuse a fixed pool of serials, so without this the live stream labels (and leaks) an old call's draining audio with the next call's talkgroup. A sink implementing this is preferred over plain WritePCM in WriteRawFrame's fan-out; sinks that don't implement it still get WritePCM.
type DecodedPCMSink ¶ added in v0.3.9
DecodedPCMSink receives PCM the recorder decodes from digital vocoder frames, so live consumers (web stream, host player, tone-out) hear digital calls — not just analog ones. The composer's digital chains emit only raw vocoder frames, which fan out solely to the recorder (the lone decoder); without this tap that decoded audio never reaches the WritePCM-only live sinks. Mirrors composer.PCMSink's WritePCM but is declared here to avoid a voice → composer import cycle.
type ErrorAware ¶ added in v0.3.9
type ErrorAware interface {
SetFrameErrors(correctedBits int)
}
ErrorAware is implemented by vocoders that can use the channel FEC corrected-bit count for the upcoming frame to drive adaptive smoothing (currently the pure-Go IMBE decoder). The recorder calls SetFrameErrors with the per-frame corrected-bit count immediately before Decode when the caller supplies it (P25 Phase 1); a vocoder that doesn't implement this simply ignores the channel error rate.
type NormalizeConfig ¶ added in v0.4.0
type NormalizeConfig struct {
// Enabled turns normalization on. When false the recorder never
// rewrites WAVs (the default).
Enabled bool
// TargetLUFS is the integrated-loudness target (e.g. -16).
TargetLUFS float64
// TruePeakDBTP is the true-peak ceiling in dBTP (e.g. -1.5).
TruePeakDBTP float64
// MaxBoostDB caps how much gain may be applied in either direction,
// so a near-silent call doesn't amplify hiss up to the target.
MaxBoostDB float64
}
NormalizeConfig configures per-call EBU R128 / BS.1770 loudness normalization of a finished recording. It mirrors ffmpeg's two-pass linear "loudnorm": measure the whole call's integrated loudness, apply a single linear gain toward TargetLUFS, then back the gain off if the true peak would exceed TruePeakDBTP. Pure gain — no compression — so the within-call dynamics the vocoder AGC produced are preserved.
type NullVocoder ¶
type NullVocoder struct {
// contains filtered or unexported fields
}
NullVocoder produces silence. It's the default when no IMBE / AMBE+2 decoder is available, and it is always safe to use because it doesn't touch any patented algorithm.
func NewNullVocoder ¶
func NewNullVocoder(frameSize int) *NullVocoder
NewNullVocoder returns a silent vocoder with the supplied frame size (in bytes). Output is 8 kHz / 20 ms / 160 samples per frame regardless.
func (*NullVocoder) Close ¶
func (n *NullVocoder) Close() error
func (*NullVocoder) FrameSize ¶
func (n *NullVocoder) FrameSize() int
func (*NullVocoder) Name ¶
func (n *NullVocoder) Name() string
func (*NullVocoder) Reset ¶
func (n *NullVocoder) Reset()
type RawFrameCallSink ¶ added in v0.4.9
type RawFrameCallSink interface {
WriteRawFrameForCall(deviceSerial string, callID uint64, vocoder string, frame []byte) error
}
RawFrameCallSink is the call-aware extension of RawFrameSink: it carries the CallID the raw frame belongs to so the live fan-out applies the same cross-call fence the decoded tap does when a voice-tap serial is reused. A sink implementing it is preferred over plain WriteRawFrame in the raw-tap fan-out; sinks that don't still get WriteRawFrame.
type RawFrameSink ¶ added in v0.4.9
RawFrameSink receives the verbatim, un-decoded vocoder frames (IMBE / AMBE+2) the recorder also lays down as its .raw sidecar, so a live consumer — the gRPC audio publisher's include_raw subscribers — can stream the raw bytes without the recorder remaining the only place they exist. It carries the vocoder name that produced the session so the wire frame can label its codec, and unlike the decoded tap it fires even for protocols with no in-process decoder (ProVoice, encrypted): the raw bytes exist even when decoded PCM does not.
type Recorder ¶
type Recorder struct {
// contains filtered or unexported fields
}
Recorder writes per-call audio + raw-frame files. It subscribes to events.KindCallStart and events.KindCallEnd from the trunking engine, opens a WAV (and optional raw-frame sidecar) for each new call, and closes them on call end. The demod-pipeline composer pushes PCM samples in via WritePCM (analog protocols) and raw vocoder frames in via WriteRawFrame (digital protocols), keyed by device serial.
Layout under OutDir (Trunk-Recorder-style):
<OutDir>/<system>/<talkgroup-or-decimal-id>/<UTC-RFC3339>_freq<Hz>_src<src>.wav <OutDir>/<system>/<talkgroup-or-decimal-id>/<UTC-RFC3339>_freq<Hz>_src<src>.raw
(The _freq<Hz> tag carries the RF voice-channel frequency; it is omitted when the grant frequency is unknown. A _ts<slot> tag is appended for slotted protocols. When per-transmission recording is enabled, each over rolls to a new file with a fresh timestamp.)
The raw sidecar is appended once per WriteRawFrame call. It is intentionally a flat concatenation of frames so users can BYO decoder (external libmbe, DVSI hardware, etc.) without parsing surrounding metadata.
Per-call vocoder: when Grant.Protocol matches an entry in the configured VocoderForProtocol map, the recorder instantiates a fresh Vocoder from voice.DefaultRegistry on CallStart and decodes each WriteRawFrame call through it, writing the resulting PCM into the WAV. This makes captures of P25 / DMR / NXDN voice produce playable WAVs alongside the optional raw sidecar — out-of-band decode via `gophertrunk decode` remains available for operators who want bit-exact mbelib / DSD-FME output.
EDACS ProVoice grants (Grant.ProVoice == true) always force a `.raw` sidecar even when WriteRaw is false. The ProVoice vocoder is patent + trade-secret encumbered so we cannot ship a built-in decoder; the sidecar lets researchers feed frames into an external decoder.
func NewRecorder ¶
func NewRecorder(opts RecorderOptions) (*Recorder, error)
NewRecorder validates options and returns a recorder ready to Run. Like the engine, the recorder subscribes to the bus at construction so that CallStart events published before Run starts are not lost.
func (*Recorder) Close ¶
Close releases the bus subscription, waits for Run (if running) to exit, then closes any outstanding sessions. Safe to call multiple times; second and later calls are no-ops.
func (*Recorder) HasSession ¶
HasSession reports whether a session exists for deviceSerial.
func (*Recorder) RecordingEnabled ¶
RecordingEnabled reports the current gate state.
func (*Recorder) SessionCount ¶
SessionCount returns the number of currently-open recording sessions. Useful in tests; takes the internal lock so it is race-free.
func (*Recorder) SetDecodedPCMSink ¶ added in v0.3.9
func (r *Recorder) SetDecodedPCMSink(s DecodedPCMSink)
SetDecodedPCMSink wires the live-audio tap that receives PCM decoded from digital vocoder frames. Call once during daemon construction before Run/any calls start; it is not safe to change concurrently with WriteRawFrame.
func (*Recorder) SetRawFrameSink ¶ added in v0.4.9
func (r *Recorder) SetRawFrameSink(s RawFrameSink)
SetRawFrameSink wires the live raw-frame tap that receives the verbatim un-decoded vocoder frames (the gRPC audio publisher). Call once during daemon construction before Run/any calls start; it is not safe to change concurrently with WriteRawFrame.
func (*Recorder) SetRecordingEnabled ¶
SetRecordingEnabled toggles the recorder's runtime "create new sessions" gate. When enabled is false, subsequent CallStart events do NOT open .wav / .raw files; in-flight sessions are left alone so the head of a mid-call disable isn't lost on disk. Default (after NewRecorder) is enabled = true.
func (*Recorder) SetVoiceEnhance ¶ added in v0.5.3
func (r *Recorder) SetVoiceEnhance(cfg mbe.EnhancerConfig)
SetVoiceEnhance swaps the voice enhancement config at runtime. The change applies to the NEXT call (each call builds a fresh vocoder that reads VoiceEnhance); in-flight calls keep the config they started with. This backs the live "enhance on/off" toggle in PATCH /api/v1/settings.
func (*Recorder) VoiceEnhance ¶ added in v0.5.3
func (r *Recorder) VoiceEnhance() mbe.EnhancerConfig
VoiceEnhance returns the current voice enhancement config (defaults already backfilled when enabled). Read under the lock so a concurrent SetVoiceEnhance never tears the struct.
func (*Recorder) WritePCM ¶
WritePCM appends 16-bit PCM samples for the named device serial. If no session is open for that device the samples are dropped (the demod pipeline can race ahead of the CallStart event).
func (*Recorder) WriteRawFrame ¶
WriteRawFrame consumes a raw vocoder frame for the named device serial. Two outputs are produced when applicable:
- The .raw sidecar (when one was opened — see handleStart). The frame bytes are appended verbatim so external decoders can consume the file with no surrounding metadata.
- The .wav (when a vocoder was instantiated for the call's Grant.Protocol). The frame is decoded into PCM and the samples are appended to the WAV. A per-frame Decode error is logged and the frame is dropped from PCM but still written to the sidecar.
Frames for a session without either output (no sidecar, no vocoder) are dropped silently.
func (*Recorder) WriteRawFrameForCall ¶ added in v0.4.8
func (r *Recorder) WriteRawFrameForCall(deviceSerial string, callID uint64, frame []byte, correctedBits int) error
WriteRawFrameForCall is WriteRawFrameWithErrors plus the call identity (Grant.CallID) the frame was decoded for. A frame whose callID doesn't match the open session is dropped, fencing the cross-call audio-bleed window when a voice-tap serial is reused for the next call before the previous chain has fully drained. Digital voice chains that know their call's CallID call this in preference to WriteRawFrameWithErrors.
func (*Recorder) WriteRawFrameWithErrors ¶ added in v0.3.9
func (r *Recorder) WriteRawFrameWithErrors(deviceSerial string, frame []byte, correctedBits int) error
WriteRawFrameWithErrors is WriteRawFrame plus the channel FEC corrected-bit count for the frame. When the session's vocoder implements voice.ErrorAware (the pure-Go IMBE decoder), the count is handed to it before Decode so adaptive smoothing can estimate the channel error rate. Callers without an error count use WriteRawFrame.
type RecorderOptions ¶
type RecorderOptions struct {
Bus *events.Bus
Log *slog.Logger
OutDir string
SampleRate uint32 // 8000 typical
WriteRaw bool // emit a .raw sidecar alongside each .wav
// SkipEncrypted, when true, makes the recorder refuse to write files
// for calls flagged encrypted. A grant that already signals encryption
// never opens a session; a call whose encryption is only discovered
// mid-stream (P25 Phase 1 LDU2 Encryption Sync, or a Phase 2 compressed
// grant resolved in-call) has its in-progress WAV/raw files closed and
// deleted, and no CallComplete is published so downstream upload feeds
// never see the partial.
SkipEncrypted bool
// Normalize configures optional per-call EBU R128 / BS.1770 loudness
// normalization. When Enabled, a finished WAV is measured and rewritten
// in place to TargetLUFS (true-peak limited) before CallComplete is
// published, so every downstream consumer (web playback, MP3 encode,
// uploads) reads the normalized audio. Off by default.
Normalize NormalizeConfig
// Enhance configures the optional, opt-in "sound-good" voice
// enhancement chain (band-limit + warmth shelf + louder AGC +
// optional compression) applied to decoded digital voice. When
// Enabled it shapes both the recorded WAV and the live decoded-PCM
// fan-out, since both come from the recorder's single per-call
// vocoder. Disabled by default (faithful, byte-identical output).
Enhance mbe.EnhancerConfig
// VocoderForProtocol maps a Grant.Protocol value to a vocoder
// registry name used to decode raw frames into PCM that's
// written to the call's WAV. nil means "use the package
// defaults" (DefaultVocoderForProtocol). Pass an explicit empty
// (non-nil) map to disable auto-decode entirely; the .raw
// sidecar then becomes the only path for digital voice.
//
// Protocols not in the map produce no decoded audio — typically
// analog protocols (motorola, edacs, ltr, mpt1327) where the
// composer's FM chain feeds WritePCM directly, and ProVoice
// where no in-binary decoder is available.
VocoderForProtocol map[string]string
// DisplayLoc is the timezone the recording-filename timestamp renders
// in (display.timezone, via config.DisplayConfig.Location()). nil falls
// back to time.UTC (the prior behaviour). Threaded so WAV filenames
// match the local wall-clock the rest of the UI/logs already show,
// instead of always UTC.
DisplayLoc *time.Location
}
RecorderOptions configure a new Recorder.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry holds the set of vocoders the running daemon has linked in. Drivers register from init(); callers fetch by name from config.
func NewRegistry ¶
func NewRegistry() *Registry
func (*Registry) Register ¶
func (r *Registry) Register(name string, f VocoderFactory)
Register adds (or replaces) a factory by name.
type StatProvider ¶ added in v0.3.9
type StatProvider interface {
VoiceStats() (VoiceStats, bool)
ResetStats()
}
StatProvider is implemented by vocoders that can report a per-call VoiceStats summary (currently the pure-Go IMBE decoder). The recorder and the offline `gophertrunk decode` tool type-assert to this interface so a vocoder that doesn't track stats simply produces no summary.
VoiceStats returns the accumulated stats and ok == false when no frames have been decoded yet. ResetStats clears the accumulator (the recorder calls it at the start of each call / segment).
type Vocoder ¶
type Vocoder interface {
Name() string
FrameSize() int // input bytes per frame
Decode(frame []byte) ([]int16, error)
Reset()
Close() error
}
Vocoder is implemented by every voice decoder GopherTrunk can use: pure-Go IMBE, pure-Go AMBE+2, the future DVSI hardware backend, and the NullVocoder used when no decoder is available.
Decode consumes one compressed frame and returns 16-bit PCM samples at 8 kHz mono (one frame is 20 ms = 160 samples for IMBE/AMBE+2). Decoders that need internal state across frames (most vocoders do) keep it on the implementation; they are NOT safe for concurrent calls on the same instance.
type VocoderFactory ¶
VocoderFactory builds a fresh vocoder instance per call. We allocate one per call so vocoders with internal state don't bleed between calls.
type VoiceEnhanceable ¶ added in v0.5.3
type VoiceEnhanceable interface {
SetVoiceEnhancer(cfg mbe.EnhancerConfig)
}
VoiceEnhanceable is the optional interface a software vocoder implements to accept the opt-in output enhancement chain (recordings.enhance). The recorder type-asserts the vocoder it builds for each call and, when the assertion succeeds, installs the current enhancement config so the chain applies to BOTH the recorded WAV and the live decoded-PCM fan-out (they share this one decode). Vocoders that don't implement it (e.g. a future hardware backend) simply decode faithfully. The imbe and ambe2 decoders implement it.
type VoiceStats ¶ added in v0.3.9
type VoiceStats struct {
// Frame-class counts over the call.
Frames int // total frames passed to Decode
Voiced int // frames with a majority of voiced harmonics
Unvoiced int // good frames with a majority of unvoiced harmonics
Silent int // explicit silence-window frames
IdleMuted int // idle-carrier-tone frames muted to silence
Bad int // bad frames that emitted silence (cache miss / budget out)
Repeated int // bad frames served from the last-good repeat cache
// Pitch / spectral envelope, averaged over good non-silent frames.
MeanF0Hz float64 // mean fundamental frequency (Hz)
MinF0Hz float64 // min fundamental seen
MaxF0Hz float64 // max fundamental seen
MeanL float64 // mean harmonic count
MeanVoicedFrac float64 // mean fraction of harmonics that were voiced
// AGC behaviour over the call.
MeanAGCGain float64 // mean per-frame applied gain
MinAGCGain float64 // min applied gain (loudest input frames)
MaxAGCGain float64 // max applied gain (quietest input frames)
// Fundamental-frequency parameter (b_0) range over all frames. b_0 is
// the raw codebook index the IMBE header carries; the idle-carrier
// corner is b_0 ≤ 7. When a call decodes to all-idle (Voiced+Unvoiced
// == 0), these disambiguate a genuine low-modulation dead-key (b_0
// varying within [0,7]) from empty/zero frames reaching the vocoder —
// a mistuned tap or extraction bug (b_0 pinned at 0). MaxB0 == 0 with
// frames > 0 means every frame's b_0 was 0.
MinB0 int // smallest b_0 seen across all frames (−1 when no frames)
MaxB0 int // largest b_0 seen across all frames (−1 when no frames)
// FirstFrameHex is the first raw 11-byte vocoder frame as hex, captured
// for diagnostics so an all-idle capture can be inspected off-line.
FirstFrameHex string
// Output amplitude health.
MaxPreClipPeak float64 // largest pre-clip sample magnitude (int16 units)
OutputRMS float64 // RMS of the int16 output across the call
CrestFactor float64 // post-AGC peak / RMS (natural speech ~8–15)
ClipSamples int // output samples hard-limited at the rail
TotalSamples int // total output samples
}
VoiceStats is a compact, per-call summary of a vocoder's decode + synthesis behaviour. It exists for diagnostics: a field tester reporting "robotic" or over-loud audio can be triaged from these numbers without per-frame log spam. The decode-quality line in the composer covers the FEC layer (uncorrectable LDUs, corrected bit errors); VoiceStats covers the *audio* layer the FEC counters can't see — pitch, harmonic count, AGC gain, and (critically) whether the output is clipping or has been compressed flat.
Loudness/dynamics health is read from CrestFactor (peak/RMS): natural speech sits around 8–15, and a value near 3–4 with ClipPct > 0 is the signature of an over-hot AGC slamming every frame into the int16 rail.
All fields are aggregated across one call (one recording session / one offline decode run). Zero-frame stats are reported as Frames == 0 and should be skipped by callers.
func (VoiceStats) ClipPct ¶ added in v0.3.9
func (s VoiceStats) ClipPct() float64
ClipPct is the percentage of output samples that hit the limiter. A non-trivial value (> ~0.5%) means the vocoder gain is too hot.
type WavWriter ¶
type WavWriter struct {
// contains filtered or unexported fields
}
WavWriter writes a 16-bit PCM mono WAV file. Length fields in the RIFF and data chunks are patched in Close() so that a daemon crash leaves a readable (if length-zero) file behind rather than something most media players reject.
Construction takes any io.WriteSeeker (so tests can use bytes.Buffer wrapped in an in-memory seeker). The dedicated NewFile helper opens a regular file on disk.
func NewWavFile ¶
NewWavFile opens path for write (creating or truncating) and returns a WavWriter that closes the file on Close().
func NewWavWriter ¶
func NewWavWriter(w io.WriteSeeker, sampleRate uint32) (*WavWriter, error)
NewWavWriter wraps an io.WriteSeeker and emits the WAV header. The sample-rate parameter is the PCM rate in Hz (8000 is typical for digital-radio voice).
func (*WavWriter) Close ¶
Close patches the length fields and closes the underlying file (if the writer owns one).
func (*WavWriter) DataBytes ¶ added in v0.1.9
DataBytes returns the number of PCM payload bytes written so far (excluding the 44-byte header). Stays readable after Close so the recorder can tell whether a call captured any audio.
func (*WavWriter) WriteSamples ¶
WriteSamples appends 16-bit PCM samples (little-endian).
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package ambe2 is the in-progress pure-Go AMBE+2 2400 bps voice decoder used by P25 Phase 2, DMR (Tier II / III), and NXDN voice frames.
|
Package ambe2 is the in-progress pure-Go AMBE+2 2400 bps voice decoder used by P25 Phase 2, DMR (Tier II / III), and NXDN voice frames. |
|
Package calibrate compares an in-tree Vocoder's PCM output against a reference WAV (typically produced by DSD-FME or OP25) from the same raw vocoder-frame source.
|
Package calibrate compares an in-tree Vocoder's PCM output against a reference WAV (typically produced by DSD-FME or OP25) from the same raw vocoder-frame source. |
|
Package composer bridges the trunking engine's CallStart events to the per-call demod chain that turns IQ samples on a freshly-tuned Voice device into 16-bit PCM the recorder can write.
|
Package composer bridges the trunking engine's CallStart events to the per-call demod chain that turns IQ samples on a freshly-tuned Voice device into 16-bit PCM the recorder can write. |
|
Package cryptocap is the bridge from GopherTrunk's live decoders to the offline cryptolab toolkit.
|
Package cryptocap is the bridge from GopherTrunk's live decoders to the offline cryptolab toolkit. |
|
Package dvsi implements the DVSI USB-3000 / AMBE-3003 hardware vocoder backend.
|
Package dvsi implements the DVSI USB-3000 / AMBE-3003 hardware vocoder backend. |
|
Package imbe is the pure-Go IMBE 4400 bps voice decoder used by P25 Phase 1 LDU1 / LDU2 frames.
|
Package imbe is the pure-Go IMBE 4400 bps voice decoder used by P25 Phase 1 LDU1 / LDU2 frames. |
|
Package mbe is the shared Multi-Band Excitation synthesis core used by GopherTrunk's IMBE 4400 (P25 Phase 1) and AMBE+2 2400 (P25 Phase 2 / DMR / NXDN) decoders.
|
Package mbe is the shared Multi-Band Excitation synthesis core used by GopherTrunk's IMBE 4400 (P25 Phase 1) and AMBE+2 2400 (P25 Phase 2 / DMR / NXDN) decoders. |
|
Package mp3 provides a pure-Go MP3 encoder used to compress completed call audio before it is streamed to broadcast aggregators (Broadcastify Calls, RdioScanner, OpenMHz, Icecast).
|
Package mp3 provides a pure-Go MP3 encoder used to compress completed call audio before it is streamed to broadcast aggregators (Broadcastify Calls, RdioScanner, OpenMHz, Icecast). |
|
Package player is the live-audio sink that turns int16 PCM coming out of the per-call composer / conventional scanner into sound out of the host's speakers.
|
Package player is the live-audio sink that turns int16 PCM coming out of the per-call composer / conventional scanner into sound out of the host's speakers. |
|
Package toneout detects fire/EMS paging tones — Two-Tone Sequential (Motorola Quick Call II), single-tone, and DTMF — over the PCM stream produced by the voice composer, and emits events.KindToneAlert when a configured profile matches.
|
Package toneout detects fire/EMS paging tones — Two-Tone Sequential (Motorola Quick Call II), single-tone, and DTMF — over the PCM stream produced by the voice composer, and emits events.KindToneAlert when a configured profile matches. |