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(); + } + } +}