@@ -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 ConcurrentDictionary<string, SPSInfo> VideoSPSDict; | |||
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; | |||
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 | |||
/// <param name="key">由于获取的SIM卡可能为000000000000,所以如果有替换JT1078Package.GetKey()的值</param> | |||
/// <param name="minimumLength">默认65535</param> | |||
/// <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); | |||
if (nalus == null || nalus.Count <= 0) return default; | |||
@@ -215,7 +215,7 @@ namespace JT1078.Flv | |||
/// <param name="key">设备号+通道号(1111111_1)</param> | |||
/// <param name="currentBufferFlvFrame">当前接收到的flv数据</param> | |||
/// <returns></returns> | |||
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当前时间戳 | |||
/// </summary> | |||
public ulong Timestamp { get; set; } | |||
/// <summary> | |||
/// 1078当前音频时间戳 | |||
/// </summary> | |||
public ulong AudioTimestamp { get; set; } | |||
/// <summary> | |||
/// 与flv上一帧相减的时间间隔 | |||
/// </summary> | |||
public uint Interval { get; set; } | |||
/// <summary> | |||
/// 上一帧音频的时间间隔 | |||
/// </summary> | |||
public uint AudioInterval { get; set; } | |||
/// <summary> | |||
/// 1078数据类型 | |||
/// </summary> | |||
@@ -27,6 +27,10 @@ namespace JT1078.Flv | |||
/// </summary> | |||
public VideoTags VideoTagsData { get; set; } | |||
/// <summary> | |||
/// 音频数据 | |||
/// </summary> | |||
public AudioTags AudioTagsData { get; set; } | |||
/// <summary> | |||
/// 根据tag类型 | |||
/// </summary> | |||
public Amf3 DataTagsData { get; set; } | |||
@@ -44,4 +44,9 @@ | |||
<ItemGroup> | |||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.0.0" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<None Update="Libs\libfaac.dll"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
</ItemGroup> | |||
</Project> |
@@ -4,11 +4,153 @@ | |||
<name>JT1078.Flv</name> | |||
</assembly> | |||
<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"> | |||
<summary> | |||
<see cref="!:System.Buffers.Writer"/> | |||
</summary> | |||
</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"> | |||
<summary> | |||
00010000 | |||
@@ -34,6 +176,21 @@ | |||
01010000 | |||
</summary> | |||
</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"> | |||
<summary> | |||
@@ -94,11 +251,21 @@ | |||
1078当前时间戳 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.FlvFrameInfo.AudioTimestamp"> | |||
<summary> | |||
1078当前音频时间戳 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.FlvFrameInfo.Interval"> | |||
<summary> | |||
与flv上一帧相减的时间间隔 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.FlvFrameInfo.AudioInterval"> | |||
<summary> | |||
上一帧音频的时间间隔 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.FlvFrameInfo.LastDataType"> | |||
<summary> | |||
1078数据类型 | |||
@@ -127,11 +294,21 @@ | |||
根据tag类型 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.FlvTags.AudioTagsData"> | |||
<summary> | |||
音频数据 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.FlvTags.DataTagsData"> | |||
<summary> | |||
根据tag类型 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.Metadata.AacPacke.RawData"> | |||
<summary> | |||
元数据 | |||
</summary> | |||
</member> | |||
<member name="P:JT1078.Flv.Metadata.Amf3.DataType"> | |||
<summary> | |||
AMF3数据类型 | |||
@@ -147,6 +324,48 @@ | |||
<see cref="!:typeof(JT1078.Flv.Enums.CodecId.AvcVideoPacke)"/> | |||
</summary> | |||
</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" --> | |||
<member name="P:JT1078.Flv.Metadata.IAmf3Metadata.FieldNameLength"> | |||
<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(); | |||
} | |||
} | |||
} |