@@ -0,0 +1,265 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace JT1078.Flv.Audio | |||||
{ | |||||
public class State | |||||
{ | |||||
/// <summary> | |||||
/// 上一个采样数据,当index为0是该值应该为未压缩的原数据 | |||||
/// </summary> | |||||
public short Valprev { get; set; } | |||||
/// <summary> | |||||
/// 保留数据(未使用) | |||||
/// </summary> | |||||
public byte Reserved { get; set; } | |||||
/// <summary> | |||||
/// 上一个block最后一个index,第一个block的index=0 | |||||
/// </summary> | |||||
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<byte> outp = new List<byte>(); | |||||
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(); | |||||
} | |||||
/// <summary> | |||||
/// 将adpcm转为pcm | |||||
/// </summary> | |||||
/// <see cref="https://github.com/ctuning/ctuning-programs/blob/master/program/cbench-telecom-adpcm-d/adpcm.c"/> | |||||
/// <param name="data"></param> | |||||
/// <returns></returns> | |||||
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<byte>(); | |||||
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 | |||||
{ | |||||
/// <summary> | |||||
/// 添加wav头 | |||||
/// 仅用于测试pcm是否转成成功,因此没考虑性能,因为播放器可播——# | |||||
/// </summary> | |||||
/// <param name="input">pcm数据</param> | |||||
/// <param name="frequency">采样率</param> | |||||
/// <param name="bitDepth">位深</param> | |||||
/// <returns></returns> | |||||
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; | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -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<byte> frameCache = new List<byte>(); | |||||
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<FaacEncConfiguration>(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 | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
/// <summary> | |||||
/// 转至PCM | |||||
/// </summary> | |||||
/// <param name="g711data"></param> | |||||
/// <returns></returns> | |||||
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; | |||||
} | |||||
/// <summary> | |||||
/// 转至G711 | |||||
/// </summary> | |||||
/// <param name="pcmdata"></param> | |||||
/// <returns></returns> | |||||
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; | |||||
} | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} |
@@ -0,0 +1,21 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace JT1078.Flv.Enums | |||||
{ | |||||
/// <summary> | |||||
/// Aac tag-body数据包类型 | |||||
/// </summary> | |||||
public enum AACPacketType | |||||
{ | |||||
/// <summary> | |||||
/// 音频序列配置 | |||||
/// </summary> | |||||
AudioSpecificConfig = 0, | |||||
/// <summary> | |||||
/// 音频帧 | |||||
/// </summary> | |||||
AudioFrame = 1 | |||||
} | |||||
} |
@@ -0,0 +1,61 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace JT1078.Flv.Enums | |||||
{ | |||||
/// <summary> | |||||
/// 音频格式 | |||||
/// </summary> | |||||
public enum AudioFormat | |||||
{ | |||||
/// <summary> | |||||
/// Linear PCM, platform endian | |||||
/// </summary> | |||||
Pcm_Platform = 0, | |||||
/// <summary> | |||||
/// ADPCM | |||||
/// </summary> | |||||
ADPCM = 1, | |||||
/// <summary> | |||||
/// MP3 | |||||
/// </summary> | |||||
MP3, | |||||
/// <summary> | |||||
/// Linear PCM, little endian | |||||
/// </summary> | |||||
Pcm_Little = 3, | |||||
/// <summary> | |||||
/// 16-kHz mono | |||||
/// </summary> | |||||
Nellymoser_16Khz = 4, | |||||
/// <summary> | |||||
/// 8-kHz mono | |||||
/// </summary> | |||||
Nellymoser_8Khz = 5, | |||||
/// <summary> | |||||
/// Nellymoser | |||||
/// </summary> | |||||
Nellymoser = 6, | |||||
/// <summary> | |||||
/// A-law logarithmic PCM | |||||
/// </summary> | |||||
G711_A_law = 7, | |||||
/// <summary> | |||||
/// mu-law logarithmic PCM | |||||
/// </summary> | |||||
G711_mu_law = 8, | |||||
/// <summary> | |||||
/// AAC | |||||
/// </summary> | |||||
AAC = 10, | |||||
/// <summary> | |||||
/// Speex | |||||
/// </summary> | |||||
Speex = 11, | |||||
/// <summary> | |||||
/// MP3 8-Khz | |||||
/// </summary> | |||||
MP3_8Khz = 14 | |||||
} | |||||
} |
@@ -0,0 +1,21 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace JT1078.Flv.Enums | |||||
{ | |||||
/// <summary> | |||||
/// 声道类型 | |||||
/// </summary> | |||||
public enum ChannelType | |||||
{ | |||||
/// <summary> | |||||
/// 单声道 | |||||
/// </summary> | |||||
Mono, | |||||
/// <summary> | |||||
/// 立体声 | |||||
/// </summary> | |||||
Stereo | |||||
} | |||||
} |
@@ -0,0 +1,21 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace JT1078.Flv.Enums | |||||
{ | |||||
/// <summary> | |||||
/// 采样位深 | |||||
/// </summary> | |||||
public enum SampleBit | |||||
{ | |||||
/// <summary> | |||||
/// 8位 | |||||
/// </summary> | |||||
Bit_8 = 0, | |||||
/// <summary> | |||||
/// 16位 | |||||
/// </summary> | |||||
Bit_16 = 1 | |||||
} | |||||
} |
@@ -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<T>(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<T>(IntPtr intptr) | |||||
{ | |||||
int index = 0; | |||||
return IntPtrToStruct<T>(intptr, index, Marshal.SizeOf(typeof(T))); | |||||
} | |||||
public static T IntPtrToStruct<T>(IntPtr intptr, int index, int length) | |||||
{ | |||||
byte[] destination = new byte[length]; | |||||
Marshal.Copy(intptr, destination, index, length); | |||||
return BytesToStruct<T>(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; | |||||
} | |||||
} | |||||
} |
@@ -27,7 +27,7 @@ namespace JT1078.Flv | |||||
private static readonly H264Decoder H264Decoder; | private static readonly H264Decoder H264Decoder; | ||||
private static readonly ConcurrentDictionary<string, SPSInfo> VideoSPSDict; | private static readonly ConcurrentDictionary<string, SPSInfo> VideoSPSDict; | ||||
private static readonly ConcurrentDictionary<string, FlvFrameInfo> FlvFrameInfoDict; | private static readonly ConcurrentDictionary<string, FlvFrameInfo> FlvFrameInfoDict; | ||||
internal static readonly ConcurrentDictionary<string, (uint PreviousTagSize,byte[] Buffer,bool Changed)> FirstFlvFrameCache; | |||||
internal static readonly ConcurrentDictionary<string, (uint PreviousTagSize, byte[] Buffer, bool Changed)> FirstFlvFrameCache; | |||||
private readonly ILogger logger; | private readonly ILogger logger; | ||||
static FlvEncoder() | static FlvEncoder() | ||||
{ | { | ||||
@@ -117,11 +117,11 @@ namespace JT1078.Flv | |||||
} | } | ||||
//cache PreviousTagSize | //cache PreviousTagSize | ||||
FlvFrameInfoDict.TryAdd(key, new FlvFrameInfo | 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)); | FirstFlvFrameCache.TryAdd(key, (firstFlvKeyFrame.PreviousTagSize, firstFlvKeyFrame.Buffer, false)); | ||||
VideoSPSDict.TryAdd(key, spsInfo); | VideoSPSDict.TryAdd(key, spsInfo); | ||||
@@ -203,7 +203,7 @@ namespace JT1078.Flv | |||||
/// <param name="key">由于获取的SIM卡可能为000000000000,所以如果有替换JT1078Package.GetKey()的值</param> | /// <param name="key">由于获取的SIM卡可能为000000000000,所以如果有替换JT1078Package.GetKey()的值</param> | ||||
/// <param name="minimumLength">默认65535</param> | /// <param name="minimumLength">默认65535</param> | ||||
/// <returns></returns> | /// <returns></returns> | ||||
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); | var nalus = H264Decoder.ParseNALU(package); | ||||
if (nalus == null || nalus.Count <= 0) return default; | if (nalus == null || nalus.Count <= 0) return default; | ||||
@@ -215,7 +215,7 @@ namespace JT1078.Flv | |||||
/// <param name="key">设备号+通道号(1111111_1)</param> | /// <param name="key">设备号+通道号(1111111_1)</param> | ||||
/// <param name="currentBufferFlvFrame">当前接收到的flv数据</param> | /// <param name="currentBufferFlvFrame">当前接收到的flv数据</param> | ||||
/// <returns></returns> | /// <returns></returns> | ||||
public byte[] GetFirstFlvFrame(string key,byte[] currentBufferFlvFrame) | |||||
public byte[] GetFirstFlvFrame(string key, byte[] currentBufferFlvFrame) | |||||
{ | { | ||||
if (FirstFlvFrameCache.TryGetValue(key, out var firstBuffer)) | if (FirstFlvFrameCache.TryGetValue(key, out var firstBuffer)) | ||||
{ | { | ||||
@@ -248,7 +248,7 @@ namespace JT1078.Flv | |||||
{ | { | ||||
FlvArrayPool.Return(buffer); | FlvArrayPool.Return(buffer); | ||||
} | } | ||||
} | |||||
} | |||||
return default; | return default; | ||||
} | } | ||||
@@ -428,10 +428,20 @@ namespace JT1078.Flv | |||||
/// 1078当前时间戳 | /// 1078当前时间戳 | ||||
/// </summary> | /// </summary> | ||||
public ulong Timestamp { get; set; } | public ulong Timestamp { get; set; } | ||||
/// <summary> | |||||
/// 1078当前音频时间戳 | |||||
/// </summary> | |||||
public ulong AudioTimestamp { get; set; } | |||||
/// <summary> | /// <summary> | ||||
/// 与flv上一帧相减的时间间隔 | /// 与flv上一帧相减的时间间隔 | ||||
/// </summary> | /// </summary> | ||||
public uint Interval { get; set; } | public uint Interval { get; set; } | ||||
/// <summary> | |||||
/// 上一帧音频的时间间隔 | |||||
/// </summary> | |||||
public uint AudioInterval { get; set; } | |||||
/// <summary> | /// <summary> | ||||
/// 1078数据类型 | /// 1078数据类型 | ||||
/// </summary> | /// </summary> | ||||
@@ -27,6 +27,10 @@ namespace JT1078.Flv | |||||
/// </summary> | /// </summary> | ||||
public VideoTags VideoTagsData { get; set; } | public VideoTags VideoTagsData { get; set; } | ||||
/// <summary> | /// <summary> | ||||
/// 音频数据 | |||||
/// </summary> | |||||
public AudioTags AudioTagsData { get; set; } | |||||
/// <summary> | |||||
/// 根据tag类型 | /// 根据tag类型 | ||||
/// </summary> | /// </summary> | ||||
public Amf3 DataTagsData { get; set; } | public Amf3 DataTagsData { get; set; } | ||||
@@ -44,4 +44,9 @@ | |||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.0.0" /> | <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.0.0" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | |||||
<None Update="Libs\libfaac.dll"> | |||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||||
</None> | |||||
</ItemGroup> | |||||
</Project> | </Project> |
@@ -4,11 +4,153 @@ | |||||
<name>JT1078.Flv</name> | <name>JT1078.Flv</name> | ||||
</assembly> | </assembly> | ||||
<members> | <members> | ||||
<member name="P:JT1078.Flv.Audio.State.Valprev"> | |||||
<summary> | |||||
上一个采样数据,当index为0是该值应该为未压缩的原数据 | |||||
</summary> | |||||
</member> | |||||
<member name="P:JT1078.Flv.Audio.State.Reserved"> | |||||
<summary> | |||||
保留数据(未使用) | |||||
</summary> | |||||
</member> | |||||
<member name="P:JT1078.Flv.Audio.State.Index"> | |||||
<summary> | |||||
上一个block最后一个index,第一个block的index=0 | |||||
</summary> | |||||
</member> | |||||
<member name="M:JT1078.Flv.Audio.AdpcmCodec.ToPcm(System.Byte[],JT1078.Flv.Audio.State)"> | |||||
<summary> | |||||
将adpcm转为pcm | |||||
</summary> | |||||
<see cref="!:https://github.com/ctuning/ctuning-programs/blob/master/program/cbench-telecom-adpcm-d/adpcm.c"/> | |||||
<param name="data"></param> | |||||
<returns></returns> | |||||
</member> | |||||
<member name="M:JT1078.Flv.Audio.AdpcmDecoderExtension.ToWav(System.Byte[],System.UInt32,System.Byte)"> | |||||
<summary> | |||||
添加wav头 | |||||
仅用于测试pcm是否转成成功,因此没考虑性能,因为播放器可播——# | |||||
</summary> | |||||
<param name="input">pcm数据</param> | |||||
<param name="frequency">采样率</param> | |||||
<param name="bitDepth">位深</param> | |||||
<returns></returns> | |||||
</member> | |||||
<member name="M:JT1078.Flv.Audio.G711ACodec.ToPcm(System.Byte[])"> | |||||
<summary> | |||||
转至PCM | |||||
</summary> | |||||
<param name="g711data"></param> | |||||
<returns></returns> | |||||
</member> | |||||
<member name="M:JT1078.Flv.Audio.G711ACodec.ToG711(System.Byte[])"> | |||||
<summary> | |||||
转至G711 | |||||
</summary> | |||||
<param name="pcmdata"></param> | |||||
<returns></returns> | |||||
</member> | |||||
<member name="T:JT1078.Flv.FlvBufferWriter"> | <member name="T:JT1078.Flv.FlvBufferWriter"> | ||||
<summary> | <summary> | ||||
<see cref="!:System.Buffers.Writer"/> | <see cref="!:System.Buffers.Writer"/> | ||||
</summary> | </summary> | ||||
</member> | </member> | ||||
<member name="T:JT1078.Flv.Enums.AACPacketType"> | |||||
<summary> | |||||
Aac tag-body数据包类型 | |||||
</summary> | |||||
</member> | |||||
<member name="F:JT1078.Flv.Enums.AACPacketType.AudioSpecificConfig"> | |||||
<summary> | |||||
音频序列配置 | |||||
</summary> | |||||
</member> | |||||
<member name="F:JT1078.Flv.Enums.AACPacketType.AudioFrame"> | |||||
<summary> | |||||
音频帧 | |||||
</summary> | |||||
</member> | |||||
<member name="T:JT1078.Flv.Enums.AudioFormat"> | |||||
<summary> | |||||
音频格式 | |||||
</summary> | |||||
</member> | |||||
<member name="F:JT1078.Flv.Enums.AudioFormat.Pcm_Platform"> | |||||
<summary> | |||||
Linear PCM, platform endian | |||||
</summary> | |||||
</member> | |||||
<member name="F:JT1078.Flv.Enums.AudioFormat.ADPCM"> | |||||
<summary> | |||||
ADPCM | |||||
</summary> | |||||
</member> | |||||
<member name="F:JT1078.Flv.Enums.AudioFormat.MP3"> | |||||
<summary> | |||||
MP3 | |||||
</summary> | |||||
</member> | |||||
<member name="F:JT1078.Flv.Enums.AudioFormat.Pcm_Little"> | |||||
<summary> | |||||
Linear PCM, little endian | |||||
</summary> | |||||
</member> | |||||
<member name="F:JT1078.Flv.Enums.AudioFormat.Nellymoser_16Khz"> | |||||
<summary> | |||||
16-kHz mono | |||||
</summary> | |||||
</member> | |||||
<member name="F:JT1078.Flv.Enums.AudioFormat.Nellymoser_8Khz"> | |||||
<summary> | |||||
8-kHz mono | |||||
</summary> | |||||
</member> | |||||
<member name="F:JT1078.Flv.Enums.AudioFormat.Nellymoser"> | |||||
<summary> | |||||
Nellymoser | |||||
</summary> | |||||
</member> | |||||
<member name="F:JT1078.Flv.Enums.AudioFormat.G711_A_law"> | |||||
<summary> | |||||
A-law logarithmic PCM | |||||
</summary> | |||||
</member> | |||||
<member name="F:JT1078.Flv.Enums.AudioFormat.G711_mu_law"> | |||||
<summary> | |||||
mu-law logarithmic PCM | |||||
</summary> | |||||
</member> | |||||
<member name="F:JT1078.Flv.Enums.AudioFormat.AAC"> | |||||
<summary> | |||||
AAC | |||||
</summary> | |||||
</member> | |||||
<member name="F:JT1078.Flv.Enums.AudioFormat.Speex"> | |||||
<summary> | |||||
Speex | |||||
</summary> | |||||
</member> | |||||
<member name="F:JT1078.Flv.Enums.AudioFormat.MP3_8Khz"> | |||||
<summary> | |||||
MP3 8-Khz | |||||
</summary> | |||||
</member> | |||||
<member name="T:JT1078.Flv.Enums.ChannelType"> | |||||
<summary> | |||||
声道类型 | |||||
</summary> | |||||
</member> | |||||
<member name="F:JT1078.Flv.Enums.ChannelType.Mono"> | |||||
<summary> | |||||
单声道 | |||||
</summary> | |||||
</member> | |||||
<member name="F:JT1078.Flv.Enums.ChannelType.Stereo"> | |||||
<summary> | |||||
立体声 | |||||
</summary> | |||||
</member> | |||||
<member name="F:JT1078.Flv.Enums.FrameType.KeyFrame"> | <member name="F:JT1078.Flv.Enums.FrameType.KeyFrame"> | ||||
<summary> | <summary> | ||||
00010000 | 00010000 | ||||
@@ -34,6 +176,21 @@ | |||||
01010000 | 01010000 | ||||
</summary> | </summary> | ||||
</member> | </member> | ||||
<member name="T:JT1078.Flv.Enums.SampleBit"> | |||||
<summary> | |||||
采样位深 | |||||
</summary> | |||||
</member> | |||||
<member name="F:JT1078.Flv.Enums.SampleBit.Bit_8"> | |||||
<summary> | |||||
8位 | |||||
</summary> | |||||
</member> | |||||
<member name="F:JT1078.Flv.Enums.SampleBit.Bit_16"> | |||||
<summary> | |||||
16位 | |||||
</summary> | |||||
</member> | |||||
<member name="T:JT1078.Flv.Extensions.HexExtensions"> | <member name="T:JT1078.Flv.Extensions.HexExtensions"> | ||||
<summary> | <summary> | ||||
@@ -94,11 +251,21 @@ | |||||
1078当前时间戳 | 1078当前时间戳 | ||||
</summary> | </summary> | ||||
</member> | </member> | ||||
<member name="P:JT1078.Flv.FlvFrameInfo.AudioTimestamp"> | |||||
<summary> | |||||
1078当前音频时间戳 | |||||
</summary> | |||||
</member> | |||||
<member name="P:JT1078.Flv.FlvFrameInfo.Interval"> | <member name="P:JT1078.Flv.FlvFrameInfo.Interval"> | ||||
<summary> | <summary> | ||||
与flv上一帧相减的时间间隔 | 与flv上一帧相减的时间间隔 | ||||
</summary> | </summary> | ||||
</member> | </member> | ||||
<member name="P:JT1078.Flv.FlvFrameInfo.AudioInterval"> | |||||
<summary> | |||||
上一帧音频的时间间隔 | |||||
</summary> | |||||
</member> | |||||
<member name="P:JT1078.Flv.FlvFrameInfo.LastDataType"> | <member name="P:JT1078.Flv.FlvFrameInfo.LastDataType"> | ||||
<summary> | <summary> | ||||
1078数据类型 | 1078数据类型 | ||||
@@ -127,11 +294,21 @@ | |||||
根据tag类型 | 根据tag类型 | ||||
</summary> | </summary> | ||||
</member> | </member> | ||||
<member name="P:JT1078.Flv.FlvTags.AudioTagsData"> | |||||
<summary> | |||||
音频数据 | |||||
</summary> | |||||
</member> | |||||
<member name="P:JT1078.Flv.FlvTags.DataTagsData"> | <member name="P:JT1078.Flv.FlvTags.DataTagsData"> | ||||
<summary> | <summary> | ||||
根据tag类型 | 根据tag类型 | ||||
</summary> | </summary> | ||||
</member> | </member> | ||||
<member name="P:JT1078.Flv.Metadata.AacPacke.RawData"> | |||||
<summary> | |||||
元数据 | |||||
</summary> | |||||
</member> | |||||
<member name="P:JT1078.Flv.Metadata.Amf3.DataType"> | <member name="P:JT1078.Flv.Metadata.Amf3.DataType"> | ||||
<summary> | <summary> | ||||
AMF3数据类型 | AMF3数据类型 | ||||
@@ -147,6 +324,48 @@ | |||||
<see cref="!:typeof(JT1078.Flv.Enums.CodecId.AvcVideoPacke)"/> | <see cref="!:typeof(JT1078.Flv.Enums.CodecId.AvcVideoPacke)"/> | ||||
</summary> | </summary> | ||||
</member> | </member> | ||||
<member name="P:JT1078.Flv.Metadata.AudioSpecificConfig.ChannelConfiguration"> | |||||
<summary> | |||||
其实有很多,这里就固定为立体声 | |||||
</summary> | |||||
</member> | |||||
<member name="T:JT1078.Flv.Metadata.AudioSpecificConfig.AudioObjectType"> | |||||
<summary> | |||||
音频类型 | |||||
其实有很多,这里就列几个,如有需要再加 | |||||
</summary> | |||||
</member> | |||||
<member name="P:JT1078.Flv.Metadata.AudioTags.SampleRate"> | |||||
<summary> | |||||
采样率 | |||||
AAC固定为3 | |||||
0 = 5.5-kHz | |||||
1 = 11-kHz | |||||
2 = 22-kHz | |||||
3 = 44-kHz | |||||
</summary> | |||||
</member> | |||||
<member name="P:JT1078.Flv.Metadata.AudioTags.SampleBit"> | |||||
<summary> | |||||
采样位深 | |||||
</summary> | |||||
</member> | |||||
<member name="P:JT1078.Flv.Metadata.AudioTags.Channel"> | |||||
<summary> | |||||
声道 | |||||
AAC永远是1 | |||||
</summary> | |||||
</member> | |||||
<member name="P:JT1078.Flv.Metadata.AudioTags.SoundType"> | |||||
<summary> | |||||
音频格式 | |||||
</summary> | |||||
</member> | |||||
<member name="P:JT1078.Flv.Metadata.AudioTags.AacPacke"> | |||||
<summary> | |||||
元数据 | |||||
</summary> | |||||
</member> | |||||
<!-- Badly formed XML comment ignored for member "T:JT1078.Flv.Metadata.AVCDecoderConfigurationRecord" --> | <!-- Badly formed XML comment ignored for member "T:JT1078.Flv.Metadata.AVCDecoderConfigurationRecord" --> | ||||
<member name="P:JT1078.Flv.Metadata.IAmf3Metadata.FieldNameLength"> | <member name="P:JT1078.Flv.Metadata.IAmf3Metadata.FieldNameLength"> | ||||
<summary> | <summary> | ||||
@@ -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<byte> Data { get; set; } = new List<byte>(); | |||||
/// <summary> | |||||
/// 元数据 | |||||
/// </summary> | |||||
public byte[] RawData => Data.ToArray(); | |||||
} | |||||
} |
@@ -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; | |||||
/// <summary> | |||||
/// 其实有很多,这里就固定为立体声 | |||||
/// </summary> | |||||
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 }; | |||||
} | |||||
/// <summary> | |||||
/// 音频类型 | |||||
/// 其实有很多,这里就列几个,如有需要再加 | |||||
/// </summary> | |||||
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 | |||||
} | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
/// <summary> | |||||
/// 采样率 | |||||
/// AAC固定为3 | |||||
/// 0 = 5.5-kHz | |||||
/// 1 = 11-kHz | |||||
/// 2 = 22-kHz | |||||
/// 3 = 44-kHz | |||||
/// </summary> | |||||
public int SampleRate => 3; | |||||
/// <summary> | |||||
/// 采样位深 | |||||
/// </summary> | |||||
public SampleBit SampleBit { get; set; } = SampleBit.Bit_16; | |||||
/// <summary> | |||||
/// 声道 | |||||
/// AAC永远是1 | |||||
/// </summary> | |||||
public ChannelType Channel => ChannelType.Stereo; | |||||
/// <summary> | |||||
/// 音频格式 | |||||
/// </summary> | |||||
public AudioFormat SoundType => AudioFormat.AAC; | |||||
/// <summary> | |||||
/// 元数据 | |||||
/// </summary> | |||||
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<byte> | |||||
{ | |||||
Convert.ToByte(value, 2) | |||||
}; | |||||
data.AddRange(AacPacke.RawData); | |||||
return data.ToArray(); | |||||
} | |||||
} | |||||
} |