Browse Source

Merge pull request #1 from yedajiang44/master

添加音频编解码以及flv音频tag封装
tags/v1.1.0
SmallChi(Koike) 5 years ago
committed by GitHub
parent
commit
98e75e68f5
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1298 additions and 9 deletions
  1. +265
    -0
      src/JT1078.Flv/Audio/AdpcmCodec.cs
  2. +195
    -0
      src/JT1078.Flv/Audio/FaacEncoder.cs
  3. +123
    -0
      src/JT1078.Flv/Audio/G711ACodec.cs
  4. +112
    -0
      src/JT1078.Flv/Audio/G711UCodec.cs
  5. +21
    -0
      src/JT1078.Flv/Enums/AACPacketType.cs
  6. +61
    -0
      src/JT1078.Flv/Enums/AudioFormat.cs
  7. +21
    -0
      src/JT1078.Flv/Enums/ChannelType.cs
  8. +21
    -0
      src/JT1078.Flv/Enums/SampleBit.cs
  9. +95
    -0
      src/JT1078.Flv/Extensions/InteropExtensions.cs
  10. +19
    -9
      src/JT1078.Flv/FlvEncoder.cs
  11. +4
    -0
      src/JT1078.Flv/FlvTags.cs
  12. +5
    -0
      src/JT1078.Flv/JT1078.Flv.csproj
  13. +219
    -0
      src/JT1078.Flv/JT1078.Flv.xml
  14. BIN
      src/JT1078.Flv/Libs/libfaac.dll
  15. +32
    -0
      src/JT1078.Flv/Metadata/AacPacke.cs
  16. +52
    -0
      src/JT1078.Flv/Metadata/AudioSpecificConfig.cs
  17. +53
    -0
      src/JT1078.Flv/Metadata/AudioTags.cs

+ 265
- 0
src/JT1078.Flv/Audio/AdpcmCodec.cs View File

@@ -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;
}
}
}
}

+ 195
- 0
src/JT1078.Flv/Audio/FaacEncoder.cs View File

@@ -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
}
}

+ 123
- 0
src/JT1078.Flv/Audio/G711ACodec.cs View File

@@ -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;
}
}
}

+ 112
- 0
src/JT1078.Flv/Audio/G711UCodec.cs View File

@@ -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);
}
}

+ 21
- 0
src/JT1078.Flv/Enums/AACPacketType.cs View File

@@ -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
}
}

+ 61
- 0
src/JT1078.Flv/Enums/AudioFormat.cs View File

@@ -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
}
}

+ 21
- 0
src/JT1078.Flv/Enums/ChannelType.cs View File

@@ -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
}
}

+ 21
- 0
src/JT1078.Flv/Enums/SampleBit.cs View File

@@ -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
}
}

+ 95
- 0
src/JT1078.Flv/Extensions/InteropExtensions.cs View File

@@ -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;
}
}
}

+ 19
- 9
src/JT1078.Flv/FlvEncoder.cs View File

@@ -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>


+ 4
- 0
src/JT1078.Flv/FlvTags.cs View File

@@ -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; }


+ 5
- 0
src/JT1078.Flv/JT1078.Flv.csproj View File

@@ -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>

+ 219
- 0
src/JT1078.Flv/JT1078.Flv.xml View File

@@ -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>


BIN
src/JT1078.Flv/Libs/libfaac.dll View File


+ 32
- 0
src/JT1078.Flv/Metadata/AacPacke.cs View File

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

+ 52
- 0
src/JT1078.Flv/Metadata/AudioSpecificConfig.cs View File

@@ -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
}
}
}

+ 53
- 0
src/JT1078.Flv/Metadata/AudioTags.cs View File

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

Loading…
Cancel
Save