You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

451 lines
17 KiB

// SPDX-License-Identifier: AGPL-3.0-only
/**
* Digital Voice Modem - Audio Bridge
* AGPLv3 Open Source. Use is subject to license terms.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* @package DVM / Audio Bridge
* @license AGPLv3 License (https://opensource.org/licenses/AGPL-3.0)
*
* Copyright (C) 2023 Bryan Biedenkapp, N2PLL
*
*/
using System.Runtime.InteropServices;
namespace dvmconsole
{
/// <summary>
/// Implements P/Invoke to callback into external AMBE encoder/decoder library.
/// </summary>
/// <remarks>This is used to interface to a external library that talks to a DVSI USB-3000.</remarks>
public class AmbeVocoder
{
private const int MBE_SAMPLES_LENGTH = 160;
private const short NO_BIT_STEAL = 0;
private const ushort ECMODE_NOISE_SUPPRESS = 0x40;
private const ushort ECMODE_AGC = 0x2000;
private const int DECSTATE_SIZE = 2048;
private byte[] decoderState;
private const int ENCSTATE_SIZE = 6144;
private byte[] encoderState;
/// <summary>
///
/// </summary>
public enum AmbeMode : short
{
FULL_RATE = 0x00,
HALF_RATE = 0x01,
NOT_VALID = 0x03
} // public enum AmbeMode
private AmbeMode mode;
private int frameLengthInBytes;
private int frameLengthInBits;
private ushort dcmode;
private ushort ecmode;
private MBEInterleaver interleaver;
/*
** Properties
*/
/// <summary>
/// Gets the currently operating decoder mode.
/// </summary>
public AmbeMode DecoderMode
{
get
{
unsafe
{
fixed (byte* state = decoderState)
return (AmbeMode)ambe_get_dec_mode((IntPtr)state);
}
}
}
/// <summary>
/// Gets the currently operating encoder mode.
/// </summary>
public AmbeMode EncoderMode
{
get
{
unsafe
{
fixed (byte* state = encoderState)
return (AmbeMode)ambe_get_enc_mode((IntPtr)state);
}
}
}
/*
** Methods
*/
/// <summary>
/// Initializes a new instance of the <see cref="AmbeVocoder"/> class.
/// </summary>
/// <param name="fullRate"></param>
public AmbeVocoder(bool fullRate = true)
{
if (fullRate)
{
this.mode = AmbeMode.FULL_RATE;
this.interleaver = new MBEInterleaver(MBE_MODE.IMBE_88BIT);
this.frameLengthInBits = 88;
this.frameLengthInBytes = 11;
}
else
{
this.mode = AmbeMode.HALF_RATE;
this.interleaver = new MBEInterleaver(MBE_MODE.DMR_AMBE);
this.frameLengthInBits = 49;
this.frameLengthInBytes = 7;
}
this.decoderState = new byte[DECSTATE_SIZE];
this.dcmode = 0;
this.encoderState = new byte[ENCSTATE_SIZE];
this.ecmode = ECMODE_NOISE_SUPPRESS | ECMODE_AGC;
unsafe
{
// initialize the AMBE decoder state
fixed (byte* state = decoderState)
ambe_init_dec((IntPtr)state, (short)mode);
// initialize the AMBE encoder state
fixed (byte* state = encoderState)
ambe_init_enc((IntPtr)state, (short)mode, 1);
}
}
/// <summary>
/// Helper to unpack the codeword bytes into codeword bits for use with the AMBE decoder.
/// </summary>
/// <remarks><see cref="short"/> output bits array.</remarks>
/// <param name="codewordBits">Codeword bits.</param>
/// <param name="codeword">Codeword bytes.</param>
/// <param name="lengthBytes">Length of codeword in bytes.</param>
/// <param name="lengthBits">Length of codeword in bits.</param>
private void unpackBytesToBits(out short[] codewordBits, byte[] codeword, int lengthBytes, int lengthBits)
{
codewordBits = new short[this.frameLengthInBits * 2];
int processed = 0, bitPtr = 0, bytePtr = 0;
for (int i = 0; i < lengthBytes; i++)
{
for (int j = 7; -1 < j; j--)
{
if (processed < lengthBits)
{
codewordBits[bitPtr] = (short)((codeword[bytePtr] >> ((byte)j & 0x1F)) & 1);
bitPtr++;
}
processed++;
}
bytePtr++;
}
}
/// <summary>
/// Helper to unpack the codeword bytes into codeword bits for use with the AMBE decoder.
/// </summary>
/// <remarks><see cref="byte"/> output bits array.</remarks>
/// <param name="codewordBits">Codeword bits.</param>
/// <param name="codeword">Codeword bytes.</param>
/// <param name="lengthBytes">Length of codeword in bytes.</param>
/// <param name="lengthBits">Length of codeword in bits.</param>
private void unpackBytesToBits(out byte[] codewordBits, byte[] codeword, int lengthBytes, int lengthBits)
{
codewordBits = new byte[this.frameLengthInBits * 2];
int processed = 0, bitPtr = 0, bytePtr = 0;
for (int i = 0; i < lengthBytes; i++)
{
for (int j = 7; -1 < j; j--)
{
if (processed < lengthBits)
{
codewordBits[bitPtr] = (byte)((codeword[bytePtr] >> ((byte)j & 0x1F)) & 1);
bitPtr++;
}
processed++;
}
bytePtr++;
}
}
/// <summary>
/// Decodes the given MBE codewords to PCM samples using the decoder mode.
/// </summary>
/// <param name="codeword"></param>
/// <param name="samples"></param>
public int decode(byte[] codeword, out short[] samples)
{
samples = new short[MBE_SAMPLES_LENGTH];
if (codeword == null)
throw new NullReferenceException("codeword");
// is this a DMR codeword?
if (codeword.Length > frameLengthInBytes && mode == AmbeMode.HALF_RATE &&
codeword.Length == 9)
{
// use the managed vocoder to retrieve the un-ECC'ed and uninterleaved AMBE bits
byte[] bits = new byte[49];
interleaver.Decode(codeword, bits);
// repack bits into 7-byte array
packBitsToBytes(bits, out codeword, frameLengthInBytes, frameLengthInBits);
}
if (codeword.Length > frameLengthInBytes)
throw new ArgumentOutOfRangeException($"Codeword length is > {frameLengthInBytes}");
if (codeword.Length < frameLengthInBytes)
throw new ArgumentOutOfRangeException($"Codeword length is < {frameLengthInBytes}");
// unpack codeword from bytes to bits for use with external library
short[] codewordBits = null;
unpackBytesToBits(out codewordBits, codeword, frameLengthInBytes, frameLengthInBits);
short[] n0 = new short[MBE_SAMPLES_LENGTH / 2];
short[] n1 = new short[MBE_SAMPLES_LENGTH / 2];
// perform P/Invoke callback and pointer pinning and callback into external library
unsafe
{
fixed (short* c = codewordBits)
fixed (byte* state = decoderState)
{
IntPtr codewordPtr = (IntPtr)c;
// sample segment 1
GCHandle pinnedN0 = GCHandle.Alloc(n0, GCHandleType.Pinned);
IntPtr n0Ptr = pinnedN0.AddrOfPinnedObject();
ambe_voice_dec(n0Ptr, MBE_SAMPLES_LENGTH / 2, codewordPtr, NO_BIT_STEAL, dcmode, 0, (IntPtr)state);
pinnedN0.Free();
// sample segment 2
GCHandle pinnedN1 = GCHandle.Alloc(n1, GCHandleType.Pinned);
IntPtr n1Ptr = pinnedN1.AddrOfPinnedObject();
ambe_voice_dec(n1Ptr, MBE_SAMPLES_LENGTH / 2, codewordPtr, NO_BIT_STEAL, dcmode, 1, (IntPtr)state);
pinnedN1.Free();
}
}
// combine sample segments into contiguous samples
for (int i = 0; i < MBE_SAMPLES_LENGTH / 2; i++)
samples[i] = n0[i];
for (int i = 0; i < MBE_SAMPLES_LENGTH / 2; i++)
samples[i + (MBE_SAMPLES_LENGTH / 2)] = n1[i];
return 0;
}
/// <summary>
/// Calls ambe_init_dec() in the external DLL.
/// </summary>
/// <param name="state">Buffer containing the decoder state to initialize.</param>
/// <param name="mode">AMBE mode; FULL (0) or HALF (1).</param>
[DllImport("AMBE.DLL", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern void ambe_init_dec([Out] IntPtr state, [In] short mode);
/// <summary>
/// Calls ambe_get_dec_mode() in the external DLL.
/// </summary>
/// <param name="state">Buffer containing the decoder state to initialize.</param>
[DllImport("AMBE.DLL", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern short ambe_get_dec_mode([In] IntPtr state);
/// <summary>
/// Calls ambe_voice_dec() in the external DLL.
/// </summary>
/// <param name="samples"></param>
/// <param name="sampleLength"></param>
/// <param name="codeword"></param>
/// <param name="bitSteal"></param>
/// <param name="cmode"></param>
/// <param name="n"></param>
/// <param name="state">Buffer containing the decoder state.</param>
/// <returns></returns>
[DllImport("AMBE.DLL", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern uint ambe_voice_dec([Out] IntPtr samples, [In] short sampleLength, [In] IntPtr codeword, [In] short bitSteal, [In] ushort cmode, [In] short n, [In] IntPtr state);
/// <summary>
/// Helper to pack the codeword bits into codeword bytes for use with the AMBE encoder.
/// </summary>
/// <remarks><see cref="short"/> input bits array.</remarks>
/// <param name="codewordBits">Codeword bits.</param>
/// <param name="codeword">Codeword bytes.</param>
/// <param name="lengthBytes">Length of codeword in bytes.</param>
/// <param name="lengthBits">Length of codeword in bits.</param>
private void packBitsToBytes(short[] codewordBits, out byte[] codeword, int lengthBytes, int lengthBits)
{
codeword = new byte[lengthBytes];
int processed = 0, bitPtr = 0, bytePtr = 0;
for (int i = 0; i < lengthBytes; i++)
{
codeword[i] = 0;
for (int j = 7; -1 < j; j--)
{
if (processed < lengthBits)
{
codeword[bytePtr] = (byte)(codeword[bytePtr] | (byte)((codewordBits[bitPtr] & 1) << ((byte)j & 0x1F)));
bitPtr++;
}
processed++;
}
bytePtr++;
}
}
/// <summary>
/// Helper to pack the codeword bits into codeword bytes for use with the AMBE encoder.
/// </summary>
/// <remarks><see cref="byte"/> input bits array.</remarks>
/// <param name="codewordBits">Codeword bits.</param>
/// <param name="codeword">Codeword bytes.</param>
/// <param name="lengthBytes">Length of codeword in bytes.</param>
/// <param name="lengthBits">Length of codeword in bits.</param>
private void packBitsToBytes(byte[] codewordBits, out byte[] codeword, int lengthBytes, int lengthBits)
{
codeword = new byte[lengthBytes];
int processed = 0, bitPtr = 0, bytePtr = 0;
for (int i = 0; i < lengthBytes; i++)
{
codeword[i] = 0;
for (int j = 7; -1 < j; j--)
{
if (processed < lengthBits)
{
codeword[bytePtr] = (byte)(codeword[bytePtr] | (byte)((codewordBits[bitPtr] & 1) << ((byte)j & 0x1F)));
bitPtr++;
}
processed++;
}
bytePtr++;
}
}
/// <summary>
/// Encodes the given PCM samples using the encoder mode to MBE codewords.
/// </summary>
/// <param name="samples"></param>
/// <param name="codeword"></param>
/// <param name="encodeDMR"></param>
public void encode(short[] samples, out byte[] codeword, bool encodeDMR = false)
{
codeword = new byte[this.frameLengthInBytes];
if (samples == null)
throw new NullReferenceException("samples");
if (samples.Length > MBE_SAMPLES_LENGTH)
throw new ArgumentOutOfRangeException($"Samples length is > {MBE_SAMPLES_LENGTH}");
if (samples.Length < MBE_SAMPLES_LENGTH)
throw new ArgumentOutOfRangeException($"Samples length is < {MBE_SAMPLES_LENGTH}");
short[] codewordBits = new short[this.frameLengthInBits * 2];
// split samples into 2 segments
short[] n0 = new short[MBE_SAMPLES_LENGTH / 2];
for (int i = 0; i < MBE_SAMPLES_LENGTH / 2; i++)
n0[i] = samples[i];
short[] n1 = new short[MBE_SAMPLES_LENGTH / 2];
for (int i = 0; i < MBE_SAMPLES_LENGTH / 2; i++)
n1[i] = samples[i + (MBE_SAMPLES_LENGTH / 2)];
// perform P/Invoke callback and pointer pinning and callback into external library
unsafe
{
fixed (short* c = codewordBits)
fixed (byte* state = encoderState)
{
IntPtr codewordPtr = (IntPtr)c;
// sample segment 1
GCHandle pinnedN0 = GCHandle.Alloc(n0, GCHandleType.Pinned);
IntPtr n0Ptr = pinnedN0.AddrOfPinnedObject();
ambe_voice_enc(codewordPtr, NO_BIT_STEAL, n0Ptr, MBE_SAMPLES_LENGTH / 2, ecmode, 0, 8192, (IntPtr)state);
pinnedN0.Free();
// sample segment 2
GCHandle pinnedN1 = GCHandle.Alloc(n1, GCHandleType.Pinned);
IntPtr n1Ptr = pinnedN1.AddrOfPinnedObject();
ambe_voice_enc(codewordPtr, NO_BIT_STEAL, n1Ptr, MBE_SAMPLES_LENGTH / 2, ecmode, 1, 8192, (IntPtr)state);
pinnedN1.Free();
}
}
// is this to be a DMR codeword?
if (mode == AmbeMode.HALF_RATE && encodeDMR)
{
byte[] bits = new byte[49];
for (int i = 0; i < 49; i++)
bits[i] = (byte)codewordBits[i];
// use the managed vocoder to create the ECC'ed and interleaved AMBE bits
interleaver.Encode(bits, codeword);
}
else
{
// pack codeword from bits to bytes for use with external library
packBitsToBytes(codewordBits, out codeword, frameLengthInBytes, frameLengthInBits);
}
}
/// <summary>
/// Calls ambe_init_enc() in the external DLL.
/// </summary>
/// <param name="state">Buffer containing the encoder state to initialize.</param>
/// <param name="mode">AMBE mode; FULL (0) or HALF (1).</param>
/// <param name="initialize">Flag to initialize encoder state fully, 1 to initialize, 0 to not.</param>
[DllImport("AMBE.DLL", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern void ambe_init_enc([Out] IntPtr state, [In] short mode, [In] short initialize);
/// <summary>
/// Calls ambe_get_enc_mode() in the external DLL.
/// </summary>
/// <param name="state">Buffer containing the encoder state to initialize.</param>
[DllImport("AMBE.DLL", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern short ambe_get_enc_mode([In] IntPtr state);
/// <summary>
/// Calls ambe_voice_enc() in the external DLL.
/// </summary>
/// <param name="codeword"></param>
/// <param name="bitSteal"></param>
/// <param name="samples"></param>
/// <param name="sampleLength"></param>
/// <param name="cmode"></param>
/// <param name="n"></param>
/// <param name="unk"></param>
/// <param name="state">Buffer containing the encoder state.</param>
/// <returns></returns>
[DllImport("AMBE.DLL", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern uint ambe_voice_enc([Out] IntPtr codeword, [In] short bitSteal, [In] IntPtr samples, [In] short sampleLength, [In] ushort cmode, [In] short n, [In] short unk, [In] IntPtr state);
} // public class AmbeVocoder
}

Powered by TurnKey Linux.