From 57018fa67ab6448909195b6c627173c30ccaf09d Mon Sep 17 00:00:00 2001
From: yedajiang44 <602830483@qq.com>
Date: Fri, 3 Jan 2020 09:30:45 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=9F=B3=E9=A2=91=E7=BC=96?=
=?UTF-8?q?=E8=A7=A3=E7=A0=81=E4=BB=A5=E5=8F=8Aflv=E9=9F=B3=E9=A2=91tag?=
=?UTF-8?q?=E5=B0=81=E8=A3=85?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/JT1078.Flv/Audio/AdpcmCodec.cs | 265 ++++++++++++++++++
src/JT1078.Flv/Audio/FaacEncoder.cs | 195 +++++++++++++
src/JT1078.Flv/Audio/G711ACodec.cs | 123 ++++++++
src/JT1078.Flv/Audio/G711UCodec.cs | 112 ++++++++
src/JT1078.Flv/Enums/AACPacketType.cs | 21 ++
src/JT1078.Flv/Enums/AudioFormat.cs | 61 ++++
src/JT1078.Flv/Enums/ChannelType.cs | 21 ++
src/JT1078.Flv/Enums/SampleBit.cs | 21 ++
.../Extensions/InteropExtensions.cs | 95 +++++++
src/JT1078.Flv/FlvEncoder.cs | 28 +-
src/JT1078.Flv/FlvTags.cs | 4 +
src/JT1078.Flv/JT1078.Flv.csproj | 5 +
src/JT1078.Flv/JT1078.Flv.xml | 219 +++++++++++++++
src/JT1078.Flv/Libs/libfaac.dll | Bin 0 -> 71168 bytes
src/JT1078.Flv/Metadata/AacPacke.cs | 32 +++
.../Metadata/AudioSpecificConfig.cs | 52 ++++
src/JT1078.Flv/Metadata/AudioTags.cs | 53 ++++
17 files changed, 1298 insertions(+), 9 deletions(-)
create mode 100644 src/JT1078.Flv/Audio/AdpcmCodec.cs
create mode 100644 src/JT1078.Flv/Audio/FaacEncoder.cs
create mode 100644 src/JT1078.Flv/Audio/G711ACodec.cs
create mode 100644 src/JT1078.Flv/Audio/G711UCodec.cs
create mode 100644 src/JT1078.Flv/Enums/AACPacketType.cs
create mode 100644 src/JT1078.Flv/Enums/AudioFormat.cs
create mode 100644 src/JT1078.Flv/Enums/ChannelType.cs
create mode 100644 src/JT1078.Flv/Enums/SampleBit.cs
create mode 100644 src/JT1078.Flv/Extensions/InteropExtensions.cs
create mode 100644 src/JT1078.Flv/Libs/libfaac.dll
create mode 100644 src/JT1078.Flv/Metadata/AacPacke.cs
create mode 100644 src/JT1078.Flv/Metadata/AudioSpecificConfig.cs
create mode 100644 src/JT1078.Flv/Metadata/AudioTags.cs
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 0000000000000000000000000000000000000000..c9f2e4588ca41667749854ddeafd69df9fbf9262
GIT binary patch
literal 71168
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 )JyE-%3eJz#^9s5Hn?9U
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 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();
+ }
+ }
+}