diff --git a/src/JT1078.Flv/Audio/AdpcmCodec.cs b/src/JT1078.Flv/Audio/AdpcmCodec.cs
new file mode 100644
index 0000000..cc325d9
--- /dev/null
+++ b/src/JT1078.Flv/Audio/AdpcmCodec.cs
@@ -0,0 +1,265 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Flv.Audio
+{
+ public class State
+ {
+ ///
+ /// 上一个采样数据,当index为0是该值应该为未压缩的原数据
+ ///
+ public short Valprev { get; set; }
+
+ ///
+ /// 保留数据(未使用)
+ ///
+ public byte Reserved { get; set; }
+
+ ///
+ /// 上一个block最后一个index,第一个block的index=0
+ ///
+ public byte Index { get; set; }
+ }
+ public class AdpcmCodec
+ {
+ static readonly int[] indexTable = {
+ -1, -1, -1, -1, 2, 4, 6, 8,
+ -1, -1, -1, -1, 2, 4, 6, 8
+ };
+
+ static readonly int[] stepsizeTable = {
+ 7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
+ 19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
+ 50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
+ 130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
+ 337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
+ 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
+ 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
+ 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
+ 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
+ };
+ public static byte[] ToAdpcm(short[] indata, State state)
+ {
+ int val; /* Current input sample value */
+ int sign; /* Current adpcm sign bit */
+ int delta; /* Current adpcm output value */
+ int diff; /* Difference between val and valprev */
+ int step; /* Stepsize */
+ int valpred; /* Predicted output value */
+ int vpdiff; /* Current change to valpred */
+ int index; /* Current step change index */
+ int outputbuffer = 0; /* place to keep previous 4-bit value */
+ int bufferstep; /* toggle between outputbuffer/output */
+
+ List outp = new List();
+ short[] inp = indata;
+ var len = indata.Length;
+ valpred = state.Valprev;
+ index = state.Index;
+ step = stepsizeTable[index];
+
+ bufferstep = 1;
+
+ int k = 0;
+ for (int i = 0; len > 0; len--, i++)
+ {
+ val = inp[i];
+
+ /* Step 1 - compute difference with previous value */
+ diff = val - valpred;
+ sign = (diff < 0) ? 8 : 0;
+ if (sign != 0) diff = (-diff);
+
+ /* Step 2 - Divide and clamp */
+ /* Note:
+ ** This code *approximately* computes:
+ ** delta = diff*4/step;
+ ** vpdiff = (delta+0.5)*step/4;
+ ** but in shift step bits are dropped. The net result of this is
+ ** that even if you have fast mul/div hardware you cannot put it to
+ ** good use since the fixup would be too expensive.
+ */
+ delta = 0;
+ vpdiff = (step >> 3);
+
+ if (diff >= step)
+ {
+ delta = 4;
+ diff -= step;
+ vpdiff += step;
+ }
+ step >>= 1;
+ if (diff >= step)
+ {
+ delta |= 2;
+ diff -= step;
+ vpdiff += step;
+ }
+ step >>= 1;
+ if (diff >= step)
+ {
+ delta |= 1;
+ vpdiff += step;
+ }
+
+ /* Step 3 - Update previous value */
+ if (sign != 0)
+ valpred -= vpdiff;
+ else
+ valpred += vpdiff;
+
+ /* Step 4 - Clamp previous value to 16 bits */
+ if (valpred > 32767)
+ valpred = 32767;
+ else if (valpred < -32768)
+ valpred = -32768;
+
+ /* Step 5 - Assemble value, update index and step values */
+ delta |= sign;
+
+ index += indexTable[delta];
+ if (index < 0) index = 0;
+ if (index > 88) index = 88;
+ step = stepsizeTable[index];
+
+ /* Step 6 - Output value */
+ if (bufferstep != 0)
+ {
+ outputbuffer = (delta << 4) & 0xf0;
+ }
+ else
+ {
+ outp.Add((byte)((delta & 0x0f) | outputbuffer));
+ }
+ bufferstep = bufferstep == 0 ? 1 : 0;
+ }
+
+ /* Output last step, if needed */
+ if (bufferstep == 0)
+ outp.Add((byte)outputbuffer);
+
+ state.Valprev = (short)valpred;
+ state.Index = (byte)index;
+ return outp.ToArray();
+ }
+
+ ///
+ /// 将adpcm转为pcm
+ ///
+ ///
+ ///
+ ///
+ public byte[] ToPcm(byte[] data, State state)
+ {
+ // signed char *inp; /* Input buffer pointer */
+ // short *outp; /* output buffer pointer */
+ int sign; /* Current adpcm sign bit */
+ int delta; /* Current adpcm output value */
+ int step; /* Stepsize */
+ int valpred = state.Valprev; /* Predicted value */
+ int vpdiff; /* Current change to valpred */
+ byte index = state.Index; /* Current step change index */
+ int inputbuffer = 0; /* place to keep next 4-bit value */
+ bool bufferstep = false; /* toggle between inputbuffer/input */
+
+ step = stepsizeTable[index];
+
+ var outdata = new List();
+ var len = data.Length * 2;
+ for (int i = 0; len > 0; len--)
+ {
+ /* Step 1 - get the delta value */
+ if (bufferstep)
+ {
+ delta = inputbuffer & 0xf;
+ }
+ else
+ {
+ inputbuffer = data[i++];
+ delta = (inputbuffer >> 4) & 0xf;
+ }
+ bufferstep = !bufferstep;
+
+ /* Step 2 - Find new index value (for later) */
+ index += (byte)indexTable[delta];
+ if (index < 0) index = 0;
+ if (index > 88) index = 88;
+
+ /* Step 3 - Separate sign and magnitude */
+ sign = delta & 8;
+ delta &= 7;
+
+ /* Step 4 - Compute difference and new predicted value */
+ /*
+ ** Computes 'vpdiff = (delta+0.5)*step/4', but see comment
+ ** in adpcm_coder.
+ */
+ vpdiff = step >> 3;
+ if ((delta & 4) > 0) vpdiff += step;
+ if ((delta & 2) > 0) vpdiff += step >> 1;
+ if ((delta & 1) > 0) vpdiff += step >> 2;
+
+ if (sign != 0)
+ valpred -= vpdiff;
+ else
+ valpred += vpdiff;
+
+ /* Step 5 - clamp output value */
+ if (valpred > 32767)
+ valpred = 32767;
+ else if (valpred < -32768)
+ valpred = -32768;
+
+ /* Step 6 - Update step value */
+ step = stepsizeTable[index];
+
+ /* Step 7 - Output value */
+ outdata.AddRange(BitConverter.GetBytes((short)valpred));
+ }
+ state.Valprev = (short)valpred;
+ state.Index = index;
+ return outdata.ToArray();
+ }
+ }
+
+ public static class AdpcmDecoderExtension
+ {
+ ///
+ /// 添加wav头
+ /// 仅用于测试pcm是否转成成功,因此没考虑性能,因为播放器可播——#
+ ///
+ /// pcm数据
+ /// 采样率
+ /// 位深
+ ///
+ public static byte[] ToWav(this byte[] input, uint frequency, byte bitDepth = 16)
+ {
+ byte[] output = new byte[input.Length + 44];
+ Array.Copy(Encoding.ASCII.GetBytes("RIFF"), 0, output, 0, 4);
+ WriteUint(4, (uint)output.Length - 8, output);
+ Array.Copy(Encoding.ASCII.GetBytes("WAVE"), 0, output, 8, 4);
+ Array.Copy(Encoding.ASCII.GetBytes("fmt "), 0, output, 12, 4);
+ WriteUint(16, 16, output); //Header size
+ output[20] = 1; //PCM
+ output[22] = 1; //1 channel
+ WriteUint(24, frequency, output); //Sample Rate
+ WriteUint(28, (uint)(frequency * (bitDepth / 8)), output); //Bytes per second
+ output[32] = (byte)(bitDepth >> 3); //Bytes per sample
+ output[34] = bitDepth; //Bits per sample
+ Array.Copy(Encoding.ASCII.GetBytes("data"), 0, output, 36, 4);
+ WriteUint(40, (uint)output.Length, output); //Date size
+ Array.Copy(input, 0, output, 44, input.Length);
+ return output;
+ }
+
+ private static void WriteUint(uint offset, uint value, byte[] destination)
+ {
+ for (int i = 0; i < 4; i++)
+ {
+ destination[offset + i] = (byte)(value & 0xFF);
+ value >>= 8;
+ }
+ }
+ }
+}
diff --git a/src/JT1078.Flv/Audio/FaacEncoder.cs b/src/JT1078.Flv/Audio/FaacEncoder.cs
new file mode 100644
index 0000000..50a58ef
--- /dev/null
+++ b/src/JT1078.Flv/Audio/FaacEncoder.cs
@@ -0,0 +1,195 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+using System.Runtime.InteropServices;
+using JT1078.Flv.Extensions;
+
+namespace JT1078.Flv.Audio
+{
+ public class FaacEncoder : IDisposable
+ {
+ private IntPtr faacEncHandle = IntPtr.Zero;
+ private readonly int inputSamples;
+ private readonly int maxOutput;
+ public readonly int frameSize;
+ private List frameCache = new List();
+ public FaacEncoder(int sampleRate, int channels, int sampleBit)
+ {
+ var inputSampleBytes = new byte[4];
+ var maxOutputBytes = new byte[4];
+
+ faacEncHandle = FaacEncOpen(sampleRate, channels, inputSampleBytes, maxOutputBytes);
+ inputSamples = BitConverter.ToInt32(inputSampleBytes, 0);
+ maxOutput = BitConverter.ToInt32(maxOutputBytes, 0);
+ frameSize = inputSamples * channels * sampleBit / 8;
+
+ var ptr = FaacEncGetCurrentConfiguration(faacEncHandle);
+ var configuration = InteropExtensions.IntPtrToStruct(ptr);
+ configuration.inputFormat = 1;
+ configuration.outputFormat = 0;
+ configuration.useTns = 0;
+ configuration.useLfe = 0;
+ configuration.aacObjectType = 2;
+ configuration.shortctl = 0;
+ configuration.quantqual = 100;
+ configuration.bandWidth = 0;
+ configuration.bitRate = 0;
+ InteropExtensions.IntPtrSetValue(ptr, configuration);
+
+ if (FaacEncSetConfiguration(faacEncHandle, ptr) < 0) throw new Exception("set faac configuration failed!");
+ }
+
+ public byte[] Encode(byte[] bytes)
+ {
+ frameCache.AddRange(bytes);
+ if (frameCache.Count() < frameSize)//faac必须达到一帧数据后才能正常编码
+ return new byte[0];
+ var outputBytes = new byte[maxOutput];
+ var len = FaacEncEncode(faacEncHandle, frameCache.Take(frameSize).ToArray(), inputSamples, outputBytes, maxOutput);
+ frameCache = frameCache.Skip(frameSize).ToList();
+ if (len <= 0)
+ return new byte[0];
+ return outputBytes.Take(len).ToArray();
+ }
+
+ public void Dispose()
+ {
+ if (faacEncHandle != IntPtr.Zero)
+ {
+ FaacEncClose(faacEncHandle);
+ faacEncHandle = IntPtr.Zero;
+ }
+ }
+
+
+
+ const string DLLFile = @"Libs/libfaac.dll";
+
+ [DllImport(DLLFile, EntryPoint = "faacEncGetVersion", CallingConvention = CallingConvention.StdCall)]
+ //int FAACAPI faacEncGetVersion(char **faac_id_string, char **faac_copyright_string);
+ private extern static int FaacEncGetVersion(ref IntPtr faac_id_string, ref IntPtr faac_copyright_string);
+
+
+
+ [DllImport(DLLFile, EntryPoint = "faacEncGetCurrentConfiguration", CallingConvention = CallingConvention.StdCall)]
+ //faacEncConfigurationPtr FAACAPI faacEncGetCurrentConfiguration(faacEncHandle hEncoder);
+ private extern static IntPtr FaacEncGetCurrentConfiguration(IntPtr hEncoder);
+
+
+ [DllImport(DLLFile, EntryPoint = "faacEncSetConfiguration", CallingConvention = CallingConvention.StdCall)]
+ //int FAACAPI faacEncSetConfiguration(faacEncHandle hEncoder,faacEncConfigurationPtr config);
+ private extern static int FaacEncSetConfiguration(IntPtr hEncoder, IntPtr config);
+
+ [DllImport(DLLFile, EntryPoint = "faacEncOpen", CallingConvention = CallingConvention.StdCall)]
+ //faacEncHandle FAACAPI faacEncOpen(unsigned long sampleRate, unsigned int numChannels, unsigned long *inputSamples, unsigned long *maxOutputBytes);
+ private extern static IntPtr FaacEncOpen(int sampleRate, int numChannels, byte[] inputSamples, byte[] maxOutputBytes);
+
+
+ [DllImport(DLLFile, EntryPoint = "faacEncGetDecoderSpecificInfo", CallingConvention = CallingConvention.StdCall)]
+ //int FAACAPI faacEncGetDecoderSpecificInfo(faacEncHandle hEncoder, unsigned char **ppBuffer,unsigned long *pSizeOfDecoderSpecificInfo);
+ private extern static IntPtr FaacEncGetDecoderSpecificInfo(IntPtr hEncoder, ref IntPtr ppBuffer, ref int pSizeOfDecoderSpecificInfo);
+
+
+ [DllImport(DLLFile, EntryPoint = "faacEncEncode", CallingConvention = CallingConvention.StdCall)]
+ //int FAACAPI faacEncEncode(faacEncHandle hEncoder, int32_t * inputBuffer, unsigned int samplesInput, unsigned char *outputBuffer, unsigned int bufferSize);
+ private extern static int FaacEncEncode(IntPtr hEncoder, IntPtr inputBuffer, int samplesInput, IntPtr outputBuffer, int bufferSize);
+ [DllImport(DLLFile, EntryPoint = "faacEncEncode", CallingConvention = CallingConvention.StdCall)]
+ private extern static int FaacEncEncode(IntPtr hEncoder, byte[] inputBuffer, int samplesInput, byte[] outputBuffer, int bufferSize);
+
+ [DllImport(DLLFile, EntryPoint= "faacEncClose", CallingConvention = CallingConvention.StdCall)]
+ //int FAACAPI faacEncClose(faacEncHandle hEncoder);
+ private extern static IntPtr FaacEncClose(IntPtr hEncoder);
+
+ #region 配置结构
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ private struct FaacEncConfiguration
+ {
+ /* config version */
+
+ public int version;
+
+ /* library version */
+ public IntPtr name;
+
+ /* copyright string */
+ public IntPtr copyright;
+
+ /* MPEG version, 2 or 4 */
+ public uint mpegVersion;
+
+ /* AAC object type
+ * #define MAIN 1
+ #define LOW 2
+ #define SSR 3
+ #define LTP 4
+ * */
+
+ public uint aacObjectType;
+
+ /* Allow mid/side coding */
+ public uint allowMidside;
+
+ /* Use one of the channels as LFE channel */
+ public uint useLfe;
+
+ /* Use Temporal Noise Shaping */
+ public uint useTns;
+
+ /* bitrate / channel of AAC file */
+ public uint bitRate;
+
+ /* AAC file frequency bandwidth */
+ public uint bandWidth;
+
+ /* Quantizer quality */
+ public uint quantqual;
+
+ /* Bitstream output format (0 = Raw; 1 = ADTS) */
+ public int outputFormat;
+
+ /* psychoacoustic model list */
+ public IntPtr psymodellist;
+
+ /* selected index in psymodellist */
+ public int psymodelidx;
+
+ /*
+ PCM Sample Input Format
+ 0 FAAC_INPUT_NULL invalid, signifies a misconfigured config
+ 1 FAAC_INPUT_16BIT native endian 16bit
+ 2 FAAC_INPUT_24BIT native endian 24bit in 24 bits (not implemented)
+ 3 FAAC_INPUT_32BIT native endian 24bit in 32 bits (DEFAULT)
+ 4 FAAC_INPUT_FLOAT 32bit floating point
+ */
+ public int inputFormat;
+
+ /* block type enforcing (SHORTCTL_NORMAL/SHORTCTL_NOSHORT/SHORTCTL_NOLONG) */
+ // #define FAAC_INPUT_NULL 0
+ //#define FAAC_INPUT_16BIT 1
+ //#define FAAC_INPUT_24BIT 2
+ //#define FAAC_INPUT_32BIT 3
+ //#define FAAC_INPUT_FLOAT 4
+
+ //#define SHORTCTL_NORMAL 0
+ //#define SHORTCTL_NOSHORT 1
+ //#define SHORTCTL_NOLONG 2
+ public int shortctl;
+
+ /*
+ Channel Remapping
+
+ Default 0, 1, 2, 3 ... 63 (64 is MAX_CHANNELS in coder.h)
+
+ WAVE 4.0 2, 0, 1, 3
+ WAVE 5.0 2, 0, 1, 3, 4
+ WAVE 5.1 2, 0, 1, 4, 5, 3
+ AIFF 5.1 2, 0, 3, 1, 4, 5
+ */
+ [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I4, SizeConst = 64)]
+ public int[] channel_map;
+
+ }
+ #endregion
+ }
+}
diff --git a/src/JT1078.Flv/Audio/G711ACodec.cs b/src/JT1078.Flv/Audio/G711ACodec.cs
new file mode 100644
index 0000000..8344c83
--- /dev/null
+++ b/src/JT1078.Flv/Audio/G711ACodec.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Flv.Audio
+{
+ public class G711ACodec
+ {
+ private readonly int SIGN_BIT = 0x80;
+ private readonly int QUANT_MASK = 0xf;
+ private readonly int SEG_SHIFT = 4;
+ private readonly int SEG_MASK = 0x70;
+ private readonly short[] seg_end = { 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF };
+
+ static short Search(short val, short[] table, short size)
+ {
+ for (short i = 0; i < size; i++)
+ {
+ if (val <= table[i])
+ {
+ return i;
+ }
+ }
+ return size;
+ }
+
+ byte LinearToAlaw(short pcm_val)
+ {
+ short mask;
+ short seg;
+ char aval;
+ if (pcm_val >= 0)
+ {
+ mask = 0xD5;
+ }
+ else
+ {
+ mask = 0x55;
+ pcm_val = (short)(-pcm_val - 1);
+ if (pcm_val < 0)
+ {
+ pcm_val = 32767;
+ }
+ }
+
+ //Convert the scaled magnitude to segment number.
+ seg = Search(pcm_val, seg_end, 8);
+
+ //Combine the sign, segment, and quantization bits.
+ if (seg >= 8)
+ {
+ //out of range, return maximum value.
+ return (byte)(0x7F ^ mask);
+ }
+ else
+ {
+ aval = (char)(seg << SEG_SHIFT);
+ if (seg < 2) aval |= (char)((pcm_val >> 4) & QUANT_MASK);
+ else aval |= (char)((pcm_val >> (seg + 3)) & QUANT_MASK);
+ return (byte)(aval ^ mask);
+ }
+ }
+
+
+ short AlawToLinear(byte value)
+ {
+ short t;
+ short seg;
+
+ value ^= 0x55;
+
+ t = (short)((value & QUANT_MASK) << 4);
+ seg = (short)((value & SEG_MASK) >> SEG_SHIFT);
+ switch (seg)
+ {
+ case 0:
+ t += 8;
+ break;
+ case 1:
+ t += 0x108;
+ break;
+ default:
+ t += 0x108;
+ t <<= seg - 1;
+ break;
+ }
+ return (value & SIGN_BIT) != 0 ? t : (short)-t;
+ }
+
+ ///
+ /// 转至PCM
+ ///
+ ///
+ ///
+ public byte[] ToPcm(byte[] g711data)
+ {
+ byte[] pcmdata = new byte[g711data.Length * 2];
+ for (int i = 0, offset = 0; i < g711data.Length; i++)
+ {
+ short value = AlawToLinear(g711data[i]);
+ pcmdata[offset++] = (byte)((value >> 8) & 0xff);
+ pcmdata[offset++] = (byte)(value & 0xff);
+ }
+ return pcmdata;
+ }
+
+ ///
+ /// 转至G711
+ ///
+ ///
+ ///
+ public byte[] ToG711(byte[] pcmdata)
+ {
+ byte[] g711data = new byte[pcmdata.Length / 2];
+ for (int i = 0, k = 0; i < pcmdata.Length; i += 2, k++)
+ {
+ short v = (short)((pcmdata[i + 1] << 8) | (pcmdata[i]));
+ g711data[k] = LinearToAlaw(v);
+ }
+ return g711data;
+ }
+ }
+}
diff --git a/src/JT1078.Flv/Audio/G711UCodec.cs b/src/JT1078.Flv/Audio/G711UCodec.cs
new file mode 100644
index 0000000..641999c
--- /dev/null
+++ b/src/JT1078.Flv/Audio/G711UCodec.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Flv.Audio
+{
+ public class G711UCodec
+ {
+ /* 16384 entries per table (16 bit) */
+ readonly byte[] linearToUlawTable = new byte[65536];
+
+ /* 16384 entries per table (8 bit) */
+ readonly short[] ulawToLinearTable = new short[256];
+ readonly int SIGN_BIT = 0x80;
+ readonly int QUANT_MASK = 0x0f;
+ readonly int SEG_SHIFT = 0x04;
+ readonly int SEG_MASK = 0x70;
+ readonly int BIAS = 0x84;
+ readonly int CLIP = 8159;
+ readonly short[] seg_uend = { 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF };
+
+ public G711UCodec()
+ {
+ // 初始化ulaw表
+ for (int i = 0; i < 256; i++) ulawToLinearTable[i] = Ulaw2linear((byte)i);
+ // 初始化ulaw2linear表
+ for (int i = 0; i < 65535; i++) linearToUlawTable[i] = Linear2ulaw((short)i);
+ }
+
+ short Ulaw2linear(byte ulawValue)
+ {
+ ulawValue = (byte)(~ulawValue);
+ short t = (short)(((ulawValue & QUANT_MASK) << 3) + BIAS);
+ t <<= (ulawValue & SEG_MASK) >> SEG_SHIFT;
+
+ return ((ulawValue & SIGN_BIT) > 0 ? (short)(BIAS - t) : (short)(t - BIAS));
+ }
+
+ byte Linear2ulaw(short pcmValue)
+ {
+ short mask;
+ short seg;
+ byte uval;
+
+ pcmValue = (short)(pcmValue >> 2);
+ if (pcmValue < 0)
+ {
+ pcmValue = (short)(-pcmValue);
+ mask = 0x7f;
+ }
+ else
+ {
+ mask = 0xff;
+ }
+
+ if (pcmValue > CLIP) pcmValue = (short)CLIP;
+ pcmValue += (short)(BIAS >> 2);
+
+ seg = Search(pcmValue, seg_uend, 8);
+
+ if (seg >= 8)
+ {
+ return (byte)(0x7f ^ mask);
+ }
+ else
+ {
+ uval = (byte)((seg << 4) | ((pcmValue >> (seg + 1)) & 0xF));
+ return (byte)(uval ^ mask);
+ }
+ }
+
+ short Search(short val, short[] table, short size)
+ {
+ for (short i = 0; i < size; i++)
+ {
+ if (val <= table[i]) return i;
+ }
+ return size;
+ }
+
+ byte[] UlawToPcm16(byte[] samples)
+ {
+ var pcmSamples = new byte[samples.Length * 2];
+ for (int i = 0, k = 0; i < samples.Length; i++)
+ {
+ short s = ulawToLinearTable[samples[i] & 0xff];
+ pcmSamples[k++] = (byte)(s & 0xff);
+ pcmSamples[k++] = (byte)((s >> 8) & 0xff);
+ }
+ return pcmSamples;
+ }
+
+ private byte[] Pcm16ToUlaw(byte[] pcmSamples)
+ {
+ short[] dst = new short[pcmSamples.Length / 2];
+ byte[] ulawSamples = new byte[pcmSamples.Length / 2];
+ for (int i = 0, k = 0; i < pcmSamples.Length;)
+ {
+ dst[k++] = (short)((pcmSamples[i++] & 0xff) | ((pcmSamples[i++] & 0xff) << 8));
+ }
+ for (int i = 0, k = 0; i < dst.Length; i++)
+ {
+ ulawSamples[k++] = Linear2ulaw(dst[i]);
+ }
+ return ulawSamples;
+ }
+
+ public byte[] ToPcm(byte[] data) => UlawToPcm16(data);
+
+ public byte[] ToG711(byte[] data) => Pcm16ToUlaw(data);
+ }
+}
diff --git a/src/JT1078.Flv/Enums/AACPacketType.cs b/src/JT1078.Flv/Enums/AACPacketType.cs
new file mode 100644
index 0000000..a4e9cfb
--- /dev/null
+++ b/src/JT1078.Flv/Enums/AACPacketType.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Flv.Enums
+{
+ ///
+ /// Aac tag-body数据包类型
+ ///
+ public enum AACPacketType
+ {
+ ///
+ /// 音频序列配置
+ ///
+ AudioSpecificConfig = 0,
+ ///
+ /// 音频帧
+ ///
+ AudioFrame = 1
+ }
+}
diff --git a/src/JT1078.Flv/Enums/AudioFormat.cs b/src/JT1078.Flv/Enums/AudioFormat.cs
new file mode 100644
index 0000000..3fe87d0
--- /dev/null
+++ b/src/JT1078.Flv/Enums/AudioFormat.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Flv.Enums
+{
+ ///
+ /// 音频格式
+ ///
+ public enum AudioFormat
+ {
+ ///
+ /// Linear PCM, platform endian
+ ///
+ Pcm_Platform = 0,
+ ///
+ /// ADPCM
+ ///
+ ADPCM = 1,
+ ///
+ /// MP3
+ ///
+ MP3,
+ ///
+ /// Linear PCM, little endian
+ ///
+ Pcm_Little = 3,
+ ///
+ /// 16-kHz mono
+ ///
+ Nellymoser_16Khz = 4,
+ ///
+ /// 8-kHz mono
+ ///
+ Nellymoser_8Khz = 5,
+ ///
+ /// Nellymoser
+ ///
+ Nellymoser = 6,
+ ///
+ /// A-law logarithmic PCM
+ ///
+ G711_A_law = 7,
+ ///
+ /// mu-law logarithmic PCM
+ ///
+ G711_mu_law = 8,
+ ///
+ /// AAC
+ ///
+ AAC = 10,
+ ///
+ /// Speex
+ ///
+ Speex = 11,
+ ///
+ /// MP3 8-Khz
+ ///
+ MP3_8Khz = 14
+ }
+}
diff --git a/src/JT1078.Flv/Enums/ChannelType.cs b/src/JT1078.Flv/Enums/ChannelType.cs
new file mode 100644
index 0000000..b7cf0f9
--- /dev/null
+++ b/src/JT1078.Flv/Enums/ChannelType.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Flv.Enums
+{
+ ///
+ /// 声道类型
+ ///
+ public enum ChannelType
+ {
+ ///
+ /// 单声道
+ ///
+ Mono,
+ ///
+ /// 立体声
+ ///
+ Stereo
+ }
+}
diff --git a/src/JT1078.Flv/Enums/SampleBit.cs b/src/JT1078.Flv/Enums/SampleBit.cs
new file mode 100644
index 0000000..e788781
--- /dev/null
+++ b/src/JT1078.Flv/Enums/SampleBit.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Flv.Enums
+{
+ ///
+ /// 采样位深
+ ///
+ public enum SampleBit
+ {
+ ///
+ /// 8位
+ ///
+ Bit_8 = 0,
+ ///
+ /// 16位
+ ///
+ Bit_16 = 1
+ }
+}
diff --git a/src/JT1078.Flv/Extensions/InteropExtensions.cs b/src/JT1078.Flv/Extensions/InteropExtensions.cs
new file mode 100644
index 0000000..72a7e52
--- /dev/null
+++ b/src/JT1078.Flv/Extensions/InteropExtensions.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Runtime.InteropServices;
+
+namespace JT1078.Flv.Extensions
+{
+ public static class InteropExtensions
+ {
+ public static T BytesToStruct(byte[] bytes, int startIndex, int length)
+ {
+ T local;
+ T local2;
+ if (bytes == null)
+ {
+ local2 = default;
+ local = local2;
+ }
+ else if (bytes.Length <= 0)
+ {
+ local2 = default;
+ local = local2;
+ }
+ else
+ {
+ IntPtr destination = Marshal.AllocHGlobal(length);
+ try
+ {
+ Marshal.Copy(bytes, startIndex, destination, length);
+ local = (T)Marshal.PtrToStructure(destination, typeof(T));
+ }
+ catch (Exception exception)
+ {
+ throw new Exception("Error in BytesToStruct ! " + exception.Message);
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(destination);
+ }
+ }
+ return local;
+ }
+
+ public static void IntPtrSetValue(IntPtr intptr, object structObj)
+ {
+ IntPtrSetValue(intptr, StructToBytes(structObj));
+ }
+
+ public static void IntPtrSetValue(IntPtr intptr, byte[] bytes)
+ {
+ Marshal.Copy(bytes, 0, intptr, bytes.Length);
+ }
+
+ public static T IntPtrToStruct(IntPtr intptr)
+ {
+ int index = 0;
+ return IntPtrToStruct(intptr, index, Marshal.SizeOf(typeof(T)));
+ }
+
+ public static T IntPtrToStruct(IntPtr intptr, int index, int length)
+ {
+ byte[] destination = new byte[length];
+ Marshal.Copy(intptr, destination, index, length);
+ return BytesToStruct(destination, 0, destination.Length);
+ }
+
+ public static byte[] StructToBytes(object structObj)
+ {
+ int size = Marshal.SizeOf(structObj);
+ return StructToBytes(structObj, size);
+ }
+
+ public static byte[] StructToBytes(object structObj, int size)
+ {
+ byte[] buffer2;
+ IntPtr ptr = Marshal.AllocHGlobal(size);
+ try
+ {
+ Marshal.StructureToPtr(structObj, ptr, false);
+ byte[] destination = new byte[size];
+ Marshal.Copy(ptr, destination, 0, size);
+ buffer2 = destination;
+ }
+ catch (Exception exception)
+ {
+ throw new Exception("Error in StructToBytes ! " + exception.Message);
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(ptr);
+ }
+ return buffer2;
+ }
+ }
+}
diff --git a/src/JT1078.Flv/FlvEncoder.cs b/src/JT1078.Flv/FlvEncoder.cs
index 1548f4f..1a69f3a 100644
--- a/src/JT1078.Flv/FlvEncoder.cs
+++ b/src/JT1078.Flv/FlvEncoder.cs
@@ -27,7 +27,7 @@ namespace JT1078.Flv
private static readonly H264Decoder H264Decoder;
private static readonly ConcurrentDictionary VideoSPSDict;
private static readonly ConcurrentDictionary FlvFrameInfoDict;
- internal static readonly ConcurrentDictionary FirstFlvFrameCache;
+ internal static readonly ConcurrentDictionary FirstFlvFrameCache;
private readonly ILogger logger;
static FlvEncoder()
{
@@ -117,11 +117,11 @@ namespace JT1078.Flv
}
//cache PreviousTagSize
FlvFrameInfoDict.TryAdd(key, new FlvFrameInfo
- {
- PreviousTagSize = firstFlvKeyFrame.PreviousTagSize,
- Interval = (uint)(pps.Timestamp - sps.Timestamp),
- Timestamp = pps.Timestamp,
- }
+ {
+ PreviousTagSize = firstFlvKeyFrame.PreviousTagSize,
+ Interval = (uint)(pps.Timestamp - sps.Timestamp),
+ Timestamp = pps.Timestamp,
+ }
);
FirstFlvFrameCache.TryAdd(key, (firstFlvKeyFrame.PreviousTagSize, firstFlvKeyFrame.Buffer, false));
VideoSPSDict.TryAdd(key, spsInfo);
@@ -203,7 +203,7 @@ namespace JT1078.Flv
/// 由于获取的SIM卡可能为000000000000,所以如果有替换JT1078Package.GetKey()的值
/// 默认65535
///
- public byte[] CreateFlvFrame(JT1078Package package,string key=null,int minimumLength = 65535)
+ public byte[] CreateFlvFrame(JT1078Package package, string key = null, int minimumLength = 65535)
{
var nalus = H264Decoder.ParseNALU(package);
if (nalus == null || nalus.Count <= 0) return default;
@@ -215,7 +215,7 @@ namespace JT1078.Flv
/// 设备号+通道号(1111111_1)
/// 当前接收到的flv数据
///
- public byte[] GetFirstFlvFrame(string key,byte[] currentBufferFlvFrame)
+ public byte[] GetFirstFlvFrame(string key, byte[] currentBufferFlvFrame)
{
if (FirstFlvFrameCache.TryGetValue(key, out var firstBuffer))
{
@@ -248,7 +248,7 @@ namespace JT1078.Flv
{
FlvArrayPool.Return(buffer);
}
- }
+ }
return default;
}
@@ -428,10 +428,20 @@ namespace JT1078.Flv
/// 1078当前时间戳
///
public ulong Timestamp { get; set; }
+
+ ///
+ /// 1078当前音频时间戳
+ ///
+ public ulong AudioTimestamp { get; set; }
///
/// 与flv上一帧相减的时间间隔
///
public uint Interval { get; set; }
+
+ ///
+ /// 上一帧音频的时间间隔
+ ///
+ public uint AudioInterval { get; set; }
///
/// 1078数据类型
///
diff --git a/src/JT1078.Flv/FlvTags.cs b/src/JT1078.Flv/FlvTags.cs
index 13376e1..620621c 100644
--- a/src/JT1078.Flv/FlvTags.cs
+++ b/src/JT1078.Flv/FlvTags.cs
@@ -27,6 +27,10 @@ namespace JT1078.Flv
///
public VideoTags VideoTagsData { get; set; }
///
+ /// 音频数据
+ ///
+ public AudioTags AudioTagsData { get; set; }
+ ///
/// 根据tag类型
///
public Amf3 DataTagsData { get; set; }
diff --git a/src/JT1078.Flv/JT1078.Flv.csproj b/src/JT1078.Flv/JT1078.Flv.csproj
index 2c5805c..871a9bd 100644
--- a/src/JT1078.Flv/JT1078.Flv.csproj
+++ b/src/JT1078.Flv/JT1078.Flv.csproj
@@ -44,4 +44,9 @@
+
+
+ Always
+
+
diff --git a/src/JT1078.Flv/JT1078.Flv.xml b/src/JT1078.Flv/JT1078.Flv.xml
index 420425a..ad37679 100644
--- a/src/JT1078.Flv/JT1078.Flv.xml
+++ b/src/JT1078.Flv/JT1078.Flv.xml
@@ -4,11 +4,153 @@
JT1078.Flv
+
+
+ 上一个采样数据,当index为0是该值应该为未压缩的原数据
+
+
+
+
+ 保留数据(未使用)
+
+
+
+
+ 上一个block最后一个index,第一个block的index=0
+
+
+
+
+ 将adpcm转为pcm
+
+
+
+
+
+
+
+ 添加wav头
+ 仅用于测试pcm是否转成成功,因此没考虑性能,因为播放器可播——#
+
+ pcm数据
+ 采样率
+ 位深
+
+
+
+
+ 转至PCM
+
+
+
+
+
+
+ 转至G711
+
+
+
+
+
+
+ Aac tag-body数据包类型
+
+
+
+
+ 音频序列配置
+
+
+
+
+ 音频帧
+
+
+
+
+ 音频格式
+
+
+
+
+ Linear PCM, platform endian
+
+
+
+
+ ADPCM
+
+
+
+
+ MP3
+
+
+
+
+ Linear PCM, little endian
+
+
+
+
+ 16-kHz mono
+
+
+
+
+ 8-kHz mono
+
+
+
+
+ Nellymoser
+
+
+
+
+ A-law logarithmic PCM
+
+
+
+
+ mu-law logarithmic PCM
+
+
+
+
+ AAC
+
+
+
+
+ Speex
+
+
+
+
+ MP3 8-Khz
+
+
+
+
+ 声道类型
+
+
+
+
+ 单声道
+
+
+
+
+ 立体声
+
+
00010000
@@ -34,6 +176,21 @@
01010000
+
+
+ 采样位深
+
+
+
+
+ 8位
+
+
+
+
+ 16位
+
+
@@ -94,11 +251,21 @@
1078当前时间戳
+
+
+ 1078当前音频时间戳
+
+
与flv上一帧相减的时间间隔
+
+
+ 上一帧音频的时间间隔
+
+
1078数据类型
@@ -127,11 +294,21 @@
根据tag类型
+
+
+ 音频数据
+
+
根据tag类型
+
+
+ 元数据
+
+
AMF3数据类型
@@ -147,6 +324,48 @@
+
+
+ 其实有很多,这里就固定为立体声
+
+
+
+
+ 音频类型
+ 其实有很多,这里就列几个,如有需要再加
+
+
+
+
+ 采样率
+ AAC固定为3
+ 0 = 5.5-kHz
+ 1 = 11-kHz
+ 2 = 22-kHz
+ 3 = 44-kHz
+
+
+
+
+ 采样位深
+
+
+
+
+ 声道
+ AAC永远是1
+
+
+
+
+ 音频格式
+
+
+
+
+ 元数据
+
+
diff --git a/src/JT1078.Flv/Libs/libfaac.dll b/src/JT1078.Flv/Libs/libfaac.dll
new file mode 100644
index 0000000..c9f2e45
Binary files /dev/null and b/src/JT1078.Flv/Libs/libfaac.dll differ
diff --git a/src/JT1078.Flv/Metadata/AacPacke.cs b/src/JT1078.Flv/Metadata/AacPacke.cs
new file mode 100644
index 0000000..0c8fcb7
--- /dev/null
+++ b/src/JT1078.Flv/Metadata/AacPacke.cs
@@ -0,0 +1,32 @@
+using JT1078.Flv.Enums;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Flv.Metadata
+{
+ public class AacPacke
+ {
+ public AacPacke(AACPacketType packetType, byte[] data = null)
+ {
+ AACPacketType = packetType;
+ if (packetType == AACPacketType.AudioSpecificConfig)
+ {
+ Data.Add(0);
+ Data.AddRange(new AudioSpecificConfig().ToArray());
+ }
+ else
+ {
+ Data.Add(1);
+ Data.AddRange(data ?? throw new NullReferenceException("data cannot be null"));
+ }
+ }
+ public AACPacketType AACPacketType { get; private set; }
+
+ List Data { get; set; } = new List();
+ ///
+ /// 元数据
+ ///
+ public byte[] RawData => Data.ToArray();
+ }
+}
diff --git a/src/JT1078.Flv/Metadata/AudioSpecificConfig.cs b/src/JT1078.Flv/Metadata/AudioSpecificConfig.cs
new file mode 100644
index 0000000..4218b39
--- /dev/null
+++ b/src/JT1078.Flv/Metadata/AudioSpecificConfig.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Flv.Metadata
+{
+ public class AudioSpecificConfig
+ {
+ public AudioObjectType AudioType { get; set; } = AudioObjectType.AAC_LC;
+ public SamplingFrequency SamplingFrequencyIndex { get; set; } = SamplingFrequency.Index_8000;
+ ///
+ /// 其实有很多,这里就固定为立体声
+ ///
+ public int ChannelConfiguration { get; set; } = 1;
+
+ public byte[] ToArray()
+ {
+ var value = Convert.ToInt16($"{Convert.ToString((int)AudioType, 2).PadLeft(5, '0')}{Convert.ToString((int)SamplingFrequencyIndex, 2).PadLeft(4, '0')}{Convert.ToString(ChannelConfiguration, 2).PadLeft(4, '0')}000", 2);
+ return new byte[] { (byte)(value >> 8), (byte)value, 0x56, 0xe5, 0x00 };
+ }
+ ///
+ /// 音频类型
+ /// 其实有很多,这里就列几个,如有需要再加
+ ///
+ public enum AudioObjectType
+ {
+ AAC_MAIN = 1,
+ AAC_LC = 2,
+ AAC_SSR = 3,
+ AAC_LTP = 4,
+ SBR = 5,
+ AAC_SCALABLE
+ }
+ public enum SamplingFrequency
+ {
+ Index_96000 = 0x00,
+ Index_88200 = 0x01,
+ Index_64000 = 0x02,
+ Index_48000 = 0x03,
+ Index_44100 = 0x04,
+ Index_32000 = 0x05,
+ Index_24000 = 0x06,
+ Index_22050 = 0x07,
+ Index_16000 = 0x08,
+ Index_12000 = 0x09,
+ Index_11025 = 0x0a,
+ Index_8000 = 0x0b,
+ Index_7350 = 0x0c,
+ ESCAPE = 0x0f
+ }
+ }
+}
diff --git a/src/JT1078.Flv/Metadata/AudioTags.cs b/src/JT1078.Flv/Metadata/AudioTags.cs
new file mode 100644
index 0000000..e0cbf10
--- /dev/null
+++ b/src/JT1078.Flv/Metadata/AudioTags.cs
@@ -0,0 +1,53 @@
+using JT1078.Flv.Enums;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace JT1078.Flv.Metadata
+{
+ public class AudioTags
+ {
+ public AudioTags(AACPacketType packetType = AACPacketType.AudioSpecificConfig, byte[] aacFrameData = null)
+ {
+ AacPacke = new AacPacke(packetType, aacFrameData);
+ }
+ ///
+ /// 采样率
+ /// AAC固定为3
+ /// 0 = 5.5-kHz
+ /// 1 = 11-kHz
+ /// 2 = 22-kHz
+ /// 3 = 44-kHz
+ ///
+ public int SampleRate => 3;
+ ///
+ /// 采样位深
+ ///
+ public SampleBit SampleBit { get; set; } = SampleBit.Bit_16;
+ ///
+ /// 声道
+ /// AAC永远是1
+ ///
+ public ChannelType Channel => ChannelType.Stereo;
+ ///
+ /// 音频格式
+ ///
+ public AudioFormat SoundType => AudioFormat.AAC;
+
+ ///
+ /// 元数据
+ ///
+ private AacPacke AacPacke { get; set; }
+
+ public byte[] ToArray()
+ {
+ var value = $"{Convert.ToString((int)SoundType, 2).PadLeft(4, '0')}{Convert.ToString(SampleRate, 2).PadLeft(2, '0')}{(int)SampleBit}{(int)Channel}";
+ var data = new List
+ {
+ Convert.ToByte(value, 2)
+ };
+ data.AddRange(AacPacke.RawData);
+ return data.ToArray();
+ }
+ }
+}