@@ -1,5 +1,4 @@ | |||
using JT1078.Flv.Audio; | |||
using System; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
using System.IO; | |||
@@ -26,28 +25,29 @@ namespace JT1078.Flv.Test.Audio | |||
[Fact(DisplayName = "pcm编码aac")] | |||
public void Test1() | |||
{ | |||
//todo:音频暂时先放下 | |||
ReadOnlySpan<byte> fileData = File.ReadAllBytes(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"Audio/Files/testpacket.pcm")); | |||
//注意 这里为了可以判断音频是否可用,因此使用adts,当网络传输的时候不应该使用adts | |||
var faac = new FaacEncoder_x64(8000, 1, 16, true); | |||
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Audio\Files\testpacket.aac"); | |||
if (File.Exists(path)) File.Delete(path); | |||
output.WriteLine(path); | |||
var offset = 0; | |||
var step = faac.frameSize; | |||
var totalBytes = 0; | |||
var stopwatch = new Stopwatch(); | |||
while (offset + step < fileData.Length) | |||
{ | |||
stopwatch.Start(); | |||
var aacBuff = faac.Encode(fileData.Slice(offset, step).ToArray()); | |||
stopwatch.Stop(); | |||
if (aacBuff.Any()) | |||
aacBuff.AppendBytesToFile(path); | |||
offset += step; | |||
totalBytes += aacBuff.Length; | |||
} | |||
faac.Dispose(); | |||
output.WriteLine($"已编码字节数:{offset},剩余未编码字节数:{fileData.Length - offset},编码后字节数:{totalBytes},耗时:{stopwatch.Elapsed.Milliseconds}毫秒"); | |||
//var faac = new FaacEncoder_x64(8000, 1, 16, true); | |||
//var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Audio\Files\testpacket.aac"); | |||
//if (File.Exists(path)) File.Delete(path); | |||
//output.WriteLine(path); | |||
//var offset = 0; | |||
//var step = faac.frameSize; | |||
//var totalBytes = 0; | |||
//var stopwatch = new Stopwatch(); | |||
//while (offset + step < fileData.Length) | |||
//{ | |||
// stopwatch.Start(); | |||
// var aacBuff = faac.Encode(fileData.Slice(offset, step).ToArray()); | |||
// stopwatch.Stop(); | |||
// if (aacBuff.Any()) | |||
// aacBuff.AppendBytesToFile(path); | |||
// offset += step; | |||
// totalBytes += aacBuff.Length; | |||
//} | |||
//faac.Dispose(); | |||
//output.WriteLine($"已编码字节数:{offset},剩余未编码字节数:{fileData.Length - offset},编码后字节数:{totalBytes},耗时:{stopwatch.Elapsed.Milliseconds}毫秒"); | |||
} | |||
} | |||
static class Ex | |||
@@ -1,123 +0,0 @@ | |||
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 & 0xff); | |||
pcmdata[offset++] = (byte)((value >> 8) & 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; | |||
} | |||
} | |||
} |
@@ -4,12 +4,12 @@ using JT1078.Flv.Metadata; | |||
using JT1078.Protocol.Enums; | |||
using JT1078.Protocol.H264; | |||
using JT1078.Protocol.MessagePack; | |||
using JT1078.Flv.Audio; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Runtime.CompilerServices; | |||
using JT1078.Protocol; | |||
using JT1078.Protocol.Audio; | |||
[assembly: InternalsVisibleTo("JT1078.Flv.Test")] | |||
namespace JT1078.Flv | |||
@@ -35,20 +35,15 @@ namespace JT1078.Flv | |||
/// 3、<see cref="EncoderVideoTag"/>第二个参数传true | |||
/// 4、<see cref="EncoderAudioTag"/>第二个参数传true | |||
/// </summary> | |||
public class FlvEncoder : IDisposable | |||
public class FlvEncoder | |||
{ | |||
readonly IFaacEncoder faacEncoder; | |||
readonly H264Decoder h264Decoder = new H264Decoder(); | |||
public FlvEncoder(int sampleRate = 8000, int channels = 1, int sampleBit = 16, bool adts = false) | |||
readonly H264Decoder h264Decoder; | |||
readonly AudioCodecFactory audioCodecFactory; | |||
//public FlvEncoder(int sampleRate = 8000, int channels = 1, int sampleBit = 16, bool adts = false) | |||
public FlvEncoder() | |||
{ | |||
try | |||
{ | |||
faacEncoder = new FaacEncoder_x86(sampleRate, channels, sampleBit, adts); | |||
} | |||
catch | |||
{ | |||
faacEncoder = new FaacEncoder_x64(sampleRate, channels, sampleBit, adts); | |||
} | |||
audioCodecFactory = new AudioCodecFactory(); | |||
h264Decoder = new H264Decoder(); | |||
} | |||
/// <summary> | |||
@@ -255,6 +250,7 @@ namespace JT1078.Flv | |||
/// <param name="package"></param> | |||
/// <param name="needAacHeader">是否需要首帧音频</param> | |||
/// <returns></returns> | |||
[Obsolete("音频暂时去掉")] | |||
public byte[] EncoderAudioTag(JT1078Package package, bool needAacHeader = false) | |||
{ | |||
if (package.Label3.DataType != JT1078DataType.音频帧) throw new Exception("Incorrect parameter, package must be audio frame"); | |||
@@ -263,26 +259,7 @@ namespace JT1078.Flv | |||
{ | |||
flvMessagePackWriter.WriteArray(EncoderFirstAudioTag(package.Timestamp)); | |||
} | |||
byte[] aacFrameData = null; | |||
switch (package.Label2.PT) | |||
{ | |||
case JT1078AVType.ADPCM: | |||
ReadOnlySpan<byte> adpcm = package.Bodies; | |||
// 海思芯片编码的音频需要移除海思头,可能还有其他的海思头 | |||
if (adpcm.StartsWith(new byte[] { 0x00, 0x01, 0x52, 0x00 })) adpcm = adpcm.Slice(4); | |||
aacFrameData = faacEncoder.Encode(new AdpcmCodec().ToPcm(adpcm.Slice(4).ToArray(), new State() | |||
{ | |||
Valprev = (short)((adpcm[1] << 8) | adpcm[0]), | |||
Index = adpcm[2], | |||
Reserved = adpcm[3] | |||
})); break; | |||
case JT1078AVType.G711A: | |||
aacFrameData = faacEncoder.Encode(new G711ACodec().ToPcm(package.Bodies)); | |||
break; | |||
case JT1078AVType.AACLC: | |||
aacFrameData = package.Bodies; | |||
break; | |||
} | |||
byte[] aacFrameData = audioCodecFactory.Encode(package.Label2.PT, package.Bodies); | |||
if (aacFrameData != null && aacFrameData.Any())//编码成功,此时为一帧aac音频数据 | |||
{ | |||
// Data Tag Frame | |||
@@ -372,10 +349,5 @@ namespace JT1078.Flv | |||
FlvArrayPool.Return(buffer); | |||
} | |||
} | |||
public void Dispose() | |||
{ | |||
faacEncoder.Dispose(); | |||
} | |||
} | |||
} |
@@ -14,7 +14,7 @@ | |||
<licenseUrl>https://github.com/SmallChi/JT1078/blob/master/LICENSE</licenseUrl> | |||
<license>https://github.com/SmallChi/JT1078/blob/master/LICENSE</license> | |||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild> | |||
<Version>1.0.0-preview7</Version> | |||
<Version>1.0.0-preview8</Version> | |||
<SignAssembly>false</SignAssembly> | |||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> | |||
<PackageLicenseFile>LICENSE</PackageLicenseFile> | |||
@@ -44,12 +44,4 @@ | |||
<ItemGroup> | |||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.7" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<None Update="nativelibs\x64\libfaac.dll"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
<None Update="nativelibs\x86\libfaac.dll"> | |||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||
</None> | |||
</ItemGroup> | |||
</Project> |
@@ -4,53 +4,6 @@ | |||
<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"/> | |||
@@ -210,7 +163,7 @@ | |||
Flv编码器 | |||
一个客户端对应一个实例 | |||
<para> | |||
当实例不适用时,尽量手动调用下<see cref="M:JT1078.Flv.FlvEncoder.Dispose"/> | |||
当实例不适用时,尽量手动调用下<see cref="!:Dispose"/> | |||
</para> | |||
手动编码 | |||
@@ -114,7 +114,8 @@ | |||
<summary> | |||
创建M3U8文件 | |||
</summary> | |||
<param name="curTsFileInfo"></param> | |||
<param name="curTsFileInfo">当前ts文件信息</param> | |||
<param name="tsFileInfoQueue">ts文件信息队列</param> | |||
</member> | |||
<member name="M:JT1078.Hls.M3U8FileManage.CreateTsFileInfo(System.String)"> | |||
<summary> | |||
@@ -123,11 +124,12 @@ | |||
<param name="key"></param> | |||
<returns></returns> | |||
</member> | |||
<member name="M:JT1078.Hls.M3U8FileManage.CreateTsFile(System.String,System.Byte[])"> | |||
<member name="M:JT1078.Hls.M3U8FileManage.CreateTsFile(System.String,System.String,System.Byte[])"> | |||
<summary> | |||
创建TS文件 | |||
</summary> | |||
<param name="fileName">ts文件路径</param> | |||
<param name="key">终端号_通道号(用作目录)</param> | |||
<param name="data">文件内容</param> | |||
</member> | |||
<member name="M:JT1078.Hls.M3U8FileManage.Clear(System.String,System.Int32)"> | |||
@@ -2,26 +2,9 @@ | |||
using System.Collections.Generic; | |||
using System.Text; | |||
namespace JT1078.Flv.Audio | |||
namespace JT1078.Protocol.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 | |||
public class AdpcmCodec: IAudioCodec | |||
{ | |||
static readonly int[] indexTable = { | |||
-1, -1, -1, -1, 2, 4, 6, 8, | |||
@@ -39,119 +22,122 @@ namespace JT1078.Flv.Audio | |||
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]; | |||
//public static byte[] ToAdpcm(short[] indata, AdpcmState 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 */ | |||
bufferstep = 1; | |||
// List<byte> outp = new List<byte>(); | |||
// short[] inp = indata; | |||
// var len = indata.Length; | |||
// valpred = state.Valprev; | |||
// index = state.Index; | |||
// step = stepsizeTable[index]; | |||
int k = 0; | |||
for (int i = 0; len > 0; len--, i++) | |||
{ | |||
val = inp[i]; | |||
// bufferstep = 1; | |||
/* Step 1 - compute difference with previous value */ | |||
diff = val - valpred; | |||
sign = (diff < 0) ? 8 : 0; | |||
if (sign != 0) diff = (-diff); | |||
// int k = 0; | |||
// for (int i = 0; len > 0; len--, i++) | |||
// { | |||
// val = inp[i]; | |||
/* 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); | |||
// /* Step 1 - compute difference with previous value */ | |||
// diff = val - valpred; | |||
// sign = (diff < 0) ? 8 : 0; | |||
// if (sign != 0) diff = (-diff); | |||
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 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); | |||
/* Step 3 - Update previous value */ | |||
if (sign != 0) | |||
valpred -= vpdiff; | |||
else | |||
valpred += vpdiff; | |||
// 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 4 - Clamp previous value to 16 bits */ | |||
if (valpred > 32767) | |||
valpred = 32767; | |||
else if (valpred < -32768) | |||
valpred = -32768; | |||
// /* Step 3 - Update previous value */ | |||
// if (sign != 0) | |||
// valpred -= vpdiff; | |||
// else | |||
// valpred += vpdiff; | |||
/* Step 5 - Assemble value, update index and step values */ | |||
delta |= sign; | |||
// /* Step 4 - Clamp previous value to 16 bits */ | |||
// if (valpred > 32767) | |||
// valpred = 32767; | |||
// else if (valpred < -32768) | |||
// valpred = -32768; | |||
index += indexTable[delta]; | |||
if (index < 0) index = 0; | |||
if (index > 88) index = 88; | |||
step = stepsizeTable[index]; | |||
// /* Step 5 - Assemble value, update index and step values */ | |||
// delta |= sign; | |||
/* Step 6 - Output value */ | |||
if (bufferstep != 0) | |||
{ | |||
outputbuffer = (delta << 4) & 0xf0; | |||
} | |||
else | |||
{ | |||
outp.Add((byte)((delta & 0x0f) | outputbuffer)); | |||
} | |||
bufferstep = bufferstep == 0 ? 1 : 0; | |||
} | |||
// index += indexTable[delta]; | |||
// if (index < 0) index = 0; | |||
// if (index > 88) index = 88; | |||
// step = stepsizeTable[index]; | |||
/* Output last step, if needed */ | |||
if (bufferstep == 0) | |||
outp.Add((byte)outputbuffer); | |||
// /* Step 6 - Output value */ | |||
// if (bufferstep != 0) | |||
// { | |||
// outputbuffer = (delta << 4) & 0xf0; | |||
// } | |||
// else | |||
// { | |||
// outp.Add((byte)((delta & 0x0f) | outputbuffer)); | |||
// } | |||
// bufferstep = bufferstep == 0 ? 1 : 0; | |||
// } | |||
state.Valprev = (short)valpred; | |||
state.Index = (byte)index; | |||
return outp.ToArray(); | |||
} | |||
// /* 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> | |||
/// <param name="audio"></param> | |||
/// <param name="audioAttachData"></param> | |||
/// <returns></returns> | |||
public byte[] ToPcm(byte[] data, State state) | |||
public byte[] ToPcm(byte[] audio, IAudioAttachData audioAttachData) | |||
{ | |||
AdpcmState state = (AdpcmState)audioAttachData; | |||
// signed char *inp; /* Input buffer pointer */ | |||
// short *outp; /* output buffer pointer */ | |||
int sign; /* Current adpcm sign bit */ | |||
@@ -166,7 +152,7 @@ namespace JT1078.Flv.Audio | |||
step = stepsizeTable[index]; | |||
var outdata = new List<byte>(); | |||
var len = data.Length * 2; | |||
var len = audio.Length * 2; | |||
for (int i = 0; len > 0; len--) | |||
{ | |||
/* Step 1 - get the delta value */ | |||
@@ -176,7 +162,7 @@ namespace JT1078.Flv.Audio | |||
} | |||
else | |||
{ | |||
inputbuffer = data[i++]; | |||
inputbuffer = audio[i++]; | |||
delta = (inputbuffer >> 4) & 0xf; | |||
} | |||
bufferstep = !bufferstep; | |||
@@ -222,7 +208,23 @@ namespace JT1078.Flv.Audio | |||
return outdata.ToArray(); | |||
} | |||
} | |||
public class AdpcmState : IAudioAttachData | |||
{ | |||
/// <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 static class AdpcmDecoderExtension | |||
{ | |||
/// <summary> |
@@ -0,0 +1,47 @@ | |||
using JT1078.Protocol.Enums; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
namespace JT1078.Protocol.Audio | |||
{ | |||
public class AudioCodecFactory | |||
{ | |||
private readonly AdpcmCodec adpcmCodec = new AdpcmCodec(); | |||
private readonly G711ACodec g711ACodec = new G711ACodec(); | |||
private readonly G711UCodec g711UCodec = new G711UCodec(); | |||
//海思芯片编码的音频需要移除海思头,可能还有其他的海思头 | |||
private static byte[] HI = new byte[] { 0x00, 0x01, 0x52, 0x00 }; | |||
public byte[] Encode(JT1078AVType aVType,byte[]bodies) | |||
{ | |||
byte[] pcm = null; | |||
switch (aVType) | |||
{ | |||
case JT1078AVType.ADPCM: | |||
ReadOnlySpan<byte> adpcm = bodies; | |||
if (adpcm.StartsWith(HI)) adpcm = adpcm.Slice(4); | |||
pcm = adpcmCodec.ToPcm(adpcm.Slice(4).ToArray(), new AdpcmState() | |||
{ | |||
Valprev = (short)((adpcm[1] << 8) | adpcm[0]), | |||
Index = adpcm[2], | |||
Reserved = adpcm[3] | |||
}); | |||
//todo:编码mp3 | |||
return pcm; | |||
case JT1078AVType.G711A: | |||
pcm=g711ACodec.ToPcm(bodies, null); | |||
//todo:编码mp3 | |||
return pcm; | |||
case JT1078AVType.AACLC: | |||
//直接AAC出去 | |||
return bodies; | |||
case JT1078AVType.MP3: | |||
//直接MP3出去 | |||
return bodies; | |||
default: | |||
return bodies; | |||
} | |||
} | |||
} | |||
} |
@@ -3,9 +3,8 @@ using System.Collections.Generic; | |||
using System.Text; | |||
using System.Linq; | |||
using System.Runtime.InteropServices; | |||
using JT1078.Flv.Extensions; | |||
namespace JT1078.Flv.Audio | |||
namespace JT1078.Protocol.Audio | |||
{ | |||
public interface IFaacEncoder:IDisposable | |||
{ |
@@ -0,0 +1,114 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
namespace JT1078.Protocol.Audio | |||
{ | |||
public class G711ACodec: IAudioCodec | |||
{ | |||
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 }; | |||
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; | |||
} | |||
public byte[] ToPcm(byte[] audio, IAudioAttachData audioAttachData) | |||
{ | |||
byte[] pcmdata = new byte[audio.Length * 2]; | |||
for (int i = 0, offset = 0; i < audio.Length; i++) | |||
{ | |||
short value = AlawToLinear(audio[i]); | |||
pcmdata[offset++] = (byte)(value & 0xff); | |||
pcmdata[offset++] = (byte)((value >> 8) & 0xff); | |||
} | |||
return pcmdata; | |||
} | |||
//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); | |||
// } | |||
//} | |||
///// <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; | |||
//} | |||
} | |||
} |
@@ -2,13 +2,12 @@ | |||
using System.Collections.Generic; | |||
using System.Text; | |||
namespace JT1078.Flv.Audio | |||
namespace JT1078.Protocol.Audio | |||
{ | |||
public class G711UCodec | |||
public class G711UCodec: IAudioCodec | |||
{ | |||
/* 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; | |||
@@ -90,23 +89,26 @@ namespace JT1078.Flv.Audio | |||
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; | |||
} | |||
//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); | |||
public byte[] ToG711(byte[] data) => Pcm16ToUlaw(data); | |||
public byte[] ToPcm(byte[] audio, IAudioAttachData audioAttachData) | |||
{ | |||
return UlawToPcm16(audio); | |||
} | |||
} | |||
} |
@@ -0,0 +1,10 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
namespace JT1078.Protocol.Audio | |||
{ | |||
public interface IAudioAttachData | |||
{ | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
namespace JT1078.Protocol.Audio | |||
{ | |||
public interface IAudioCodec | |||
{ | |||
byte[] ToPcm(byte[] audio, IAudioAttachData audioAttachData); | |||
} | |||
} |
@@ -14,7 +14,7 @@ | |||
<licenseUrl>https://github.com/SmallChi/JT1078/blob/master/LICENSE</licenseUrl> | |||
<license>https://github.com/SmallChi/JT1078/blob/master/LICENSE</license> | |||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild> | |||
<Version>1.0.3</Version> | |||
<Version>1.0.4-preview1</Version> | |||
<SignAssembly>false</SignAssembly> | |||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> | |||
<PackageLicenseFile>LICENSE</PackageLicenseFile> | |||
@@ -24,6 +24,11 @@ | |||
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' "> | |||
<PackageReference Include="System.Memory" Version="4.5.4" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<Compile Remove="Audio\FaacEncoder.cs" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<None Include="..\..\LICENSE"> | |||