@@ -1,5 +1,4 @@ | |||||
using JT1078.Flv.Audio; | |||||
using System; | |||||
using System; | |||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Diagnostics; | using System.Diagnostics; | ||||
using System.IO; | using System.IO; | ||||
@@ -26,28 +25,29 @@ namespace JT1078.Flv.Test.Audio | |||||
[Fact(DisplayName = "pcm编码aac")] | [Fact(DisplayName = "pcm编码aac")] | ||||
public void Test1() | public void Test1() | ||||
{ | { | ||||
//todo:音频暂时先放下 | |||||
ReadOnlySpan<byte> fileData = File.ReadAllBytes(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"Audio/Files/testpacket.pcm")); | ReadOnlySpan<byte> fileData = File.ReadAllBytes(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"Audio/Files/testpacket.pcm")); | ||||
//注意 这里为了可以判断音频是否可用,因此使用adts,当网络传输的时候不应该使用adts | //注意 这里为了可以判断音频是否可用,因此使用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 | 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.Enums; | ||||
using JT1078.Protocol.H264; | using JT1078.Protocol.H264; | ||||
using JT1078.Protocol.MessagePack; | using JT1078.Protocol.MessagePack; | ||||
using JT1078.Flv.Audio; | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Linq; | using System.Linq; | ||||
using System.Runtime.CompilerServices; | using System.Runtime.CompilerServices; | ||||
using JT1078.Protocol; | using JT1078.Protocol; | ||||
using JT1078.Protocol.Audio; | |||||
[assembly: InternalsVisibleTo("JT1078.Flv.Test")] | [assembly: InternalsVisibleTo("JT1078.Flv.Test")] | ||||
namespace JT1078.Flv | namespace JT1078.Flv | ||||
@@ -35,20 +35,15 @@ namespace JT1078.Flv | |||||
/// 3、<see cref="EncoderVideoTag"/>第二个参数传true | /// 3、<see cref="EncoderVideoTag"/>第二个参数传true | ||||
/// 4、<see cref="EncoderAudioTag"/>第二个参数传true | /// 4、<see cref="EncoderAudioTag"/>第二个参数传true | ||||
/// </summary> | /// </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> | /// <summary> | ||||
@@ -255,6 +250,7 @@ namespace JT1078.Flv | |||||
/// <param name="package"></param> | /// <param name="package"></param> | ||||
/// <param name="needAacHeader">是否需要首帧音频</param> | /// <param name="needAacHeader">是否需要首帧音频</param> | ||||
/// <returns></returns> | /// <returns></returns> | ||||
[Obsolete("音频暂时去掉")] | |||||
public byte[] EncoderAudioTag(JT1078Package package, bool needAacHeader = false) | public byte[] EncoderAudioTag(JT1078Package package, bool needAacHeader = false) | ||||
{ | { | ||||
if (package.Label3.DataType != JT1078DataType.音频帧) throw new Exception("Incorrect parameter, package must be audio frame"); | 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)); | 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音频数据 | if (aacFrameData != null && aacFrameData.Any())//编码成功,此时为一帧aac音频数据 | ||||
{ | { | ||||
// Data Tag Frame | // Data Tag Frame | ||||
@@ -372,10 +349,5 @@ namespace JT1078.Flv | |||||
FlvArrayPool.Return(buffer); | FlvArrayPool.Return(buffer); | ||||
} | } | ||||
} | } | ||||
public void Dispose() | |||||
{ | |||||
faacEncoder.Dispose(); | |||||
} | |||||
} | } | ||||
} | } |
@@ -14,7 +14,7 @@ | |||||
<licenseUrl>https://github.com/SmallChi/JT1078/blob/master/LICENSE</licenseUrl> | <licenseUrl>https://github.com/SmallChi/JT1078/blob/master/LICENSE</licenseUrl> | ||||
<license>https://github.com/SmallChi/JT1078/blob/master/LICENSE</license> | <license>https://github.com/SmallChi/JT1078/blob/master/LICENSE</license> | ||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild> | <GeneratePackageOnBuild>false</GeneratePackageOnBuild> | ||||
<Version>1.0.0-preview7</Version> | |||||
<Version>1.0.0-preview8</Version> | |||||
<SignAssembly>false</SignAssembly> | <SignAssembly>false</SignAssembly> | ||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> | <PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> | ||||
<PackageLicenseFile>LICENSE</PackageLicenseFile> | <PackageLicenseFile>LICENSE</PackageLicenseFile> | ||||
@@ -44,12 +44,4 @@ | |||||
<ItemGroup> | <ItemGroup> | ||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.7" /> | <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.7" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | |||||
<None Update="nativelibs\x64\libfaac.dll"> | |||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||||
</None> | |||||
<None Update="nativelibs\x86\libfaac.dll"> | |||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> | |||||
</None> | |||||
</ItemGroup> | |||||
</Project> | </Project> |
@@ -4,53 +4,6 @@ | |||||
<name>JT1078.Flv</name> | <name>JT1078.Flv</name> | ||||
</assembly> | </assembly> | ||||
<members> | <members> | ||||
<member name="P:JT1078.Flv.Audio.State.Valprev"> | |||||
<summary> | |||||
上一个采样数据,当index为0是该值应该为未压缩的原数据 | |||||
</summary> | |||||
</member> | |||||
<member name="P:JT1078.Flv.Audio.State.Reserved"> | |||||
<summary> | |||||
保留数据(未使用) | |||||
</summary> | |||||
</member> | |||||
<member name="P:JT1078.Flv.Audio.State.Index"> | |||||
<summary> | |||||
上一个block最后一个index,第一个block的index=0 | |||||
</summary> | |||||
</member> | |||||
<member name="M:JT1078.Flv.Audio.AdpcmCodec.ToPcm(System.Byte[],JT1078.Flv.Audio.State)"> | |||||
<summary> | |||||
将adpcm转为pcm | |||||
</summary> | |||||
<see cref="!:https://github.com/ctuning/ctuning-programs/blob/master/program/cbench-telecom-adpcm-d/adpcm.c"/> | |||||
<param name="data"></param> | |||||
<returns></returns> | |||||
</member> | |||||
<member name="M:JT1078.Flv.Audio.AdpcmDecoderExtension.ToWav(System.Byte[],System.UInt32,System.Byte)"> | |||||
<summary> | |||||
添加wav头 | |||||
仅用于测试pcm是否转成成功,因此没考虑性能,因为播放器可播——# | |||||
</summary> | |||||
<param name="input">pcm数据</param> | |||||
<param name="frequency">采样率</param> | |||||
<param name="bitDepth">位深</param> | |||||
<returns></returns> | |||||
</member> | |||||
<member name="M:JT1078.Flv.Audio.G711ACodec.ToPcm(System.Byte[])"> | |||||
<summary> | |||||
转至PCM | |||||
</summary> | |||||
<param name="g711data"></param> | |||||
<returns></returns> | |||||
</member> | |||||
<member name="M:JT1078.Flv.Audio.G711ACodec.ToG711(System.Byte[])"> | |||||
<summary> | |||||
转至G711 | |||||
</summary> | |||||
<param name="pcmdata"></param> | |||||
<returns></returns> | |||||
</member> | |||||
<member name="T:JT1078.Flv.FlvBufferWriter"> | <member name="T:JT1078.Flv.FlvBufferWriter"> | ||||
<summary> | <summary> | ||||
<see cref="!:System.Buffers.Writer"/> | <see cref="!:System.Buffers.Writer"/> | ||||
@@ -210,7 +163,7 @@ | |||||
Flv编码器 | Flv编码器 | ||||
一个客户端对应一个实例 | 一个客户端对应一个实例 | ||||
<para> | <para> | ||||
当实例不适用时,尽量手动调用下<see cref="M:JT1078.Flv.FlvEncoder.Dispose"/> | |||||
当实例不适用时,尽量手动调用下<see cref="!:Dispose"/> | |||||
</para> | </para> | ||||
手动编码 | 手动编码 | ||||
@@ -114,7 +114,8 @@ | |||||
<summary> | <summary> | ||||
创建M3U8文件 | 创建M3U8文件 | ||||
</summary> | </summary> | ||||
<param name="curTsFileInfo"></param> | |||||
<param name="curTsFileInfo">当前ts文件信息</param> | |||||
<param name="tsFileInfoQueue">ts文件信息队列</param> | |||||
</member> | </member> | ||||
<member name="M:JT1078.Hls.M3U8FileManage.CreateTsFileInfo(System.String)"> | <member name="M:JT1078.Hls.M3U8FileManage.CreateTsFileInfo(System.String)"> | ||||
<summary> | <summary> | ||||
@@ -123,11 +124,12 @@ | |||||
<param name="key"></param> | <param name="key"></param> | ||||
<returns></returns> | <returns></returns> | ||||
</member> | </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> | <summary> | ||||
创建TS文件 | 创建TS文件 | ||||
</summary> | </summary> | ||||
<param name="fileName">ts文件路径</param> | <param name="fileName">ts文件路径</param> | ||||
<param name="key">终端号_通道号(用作目录)</param> | |||||
<param name="data">文件内容</param> | <param name="data">文件内容</param> | ||||
</member> | </member> | ||||
<member name="M:JT1078.Hls.M3U8FileManage.Clear(System.String,System.Int32)"> | <member name="M:JT1078.Hls.M3U8FileManage.Clear(System.String,System.Int32)"> | ||||
@@ -2,26 +2,9 @@ | |||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Text; | 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 = { | static readonly int[] indexTable = { | ||||
-1, -1, -1, -1, 2, 4, 6, 8, | -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, | 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, | ||||
15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 | 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> | /// <summary> | ||||
/// 将adpcm转为pcm | /// 将adpcm转为pcm | ||||
/// </summary> | /// </summary> | ||||
/// <see cref="https://github.com/ctuning/ctuning-programs/blob/master/program/cbench-telecom-adpcm-d/adpcm.c"/> | /// <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> | /// <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 */ | // signed char *inp; /* Input buffer pointer */ | ||||
// short *outp; /* output buffer pointer */ | // short *outp; /* output buffer pointer */ | ||||
int sign; /* Current adpcm sign bit */ | int sign; /* Current adpcm sign bit */ | ||||
@@ -166,7 +152,7 @@ namespace JT1078.Flv.Audio | |||||
step = stepsizeTable[index]; | step = stepsizeTable[index]; | ||||
var outdata = new List<byte>(); | var outdata = new List<byte>(); | ||||
var len = data.Length * 2; | |||||
var len = audio.Length * 2; | |||||
for (int i = 0; len > 0; len--) | for (int i = 0; len > 0; len--) | ||||
{ | { | ||||
/* Step 1 - get the delta value */ | /* Step 1 - get the delta value */ | ||||
@@ -176,7 +162,7 @@ namespace JT1078.Flv.Audio | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
inputbuffer = data[i++]; | |||||
inputbuffer = audio[i++]; | |||||
delta = (inputbuffer >> 4) & 0xf; | delta = (inputbuffer >> 4) & 0xf; | ||||
} | } | ||||
bufferstep = !bufferstep; | bufferstep = !bufferstep; | ||||
@@ -222,7 +208,23 @@ namespace JT1078.Flv.Audio | |||||
return outdata.ToArray(); | 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 | public static class AdpcmDecoderExtension | ||||
{ | { | ||||
/// <summary> | /// <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.Text; | ||||
using System.Linq; | using System.Linq; | ||||
using System.Runtime.InteropServices; | using System.Runtime.InteropServices; | ||||
using JT1078.Flv.Extensions; | |||||
namespace JT1078.Flv.Audio | |||||
namespace JT1078.Protocol.Audio | |||||
{ | { | ||||
public interface IFaacEncoder:IDisposable | 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.Collections.Generic; | ||||
using System.Text; | using System.Text; | ||||
namespace JT1078.Flv.Audio | |||||
namespace JT1078.Protocol.Audio | |||||
{ | { | ||||
public class G711UCodec | |||||
public class G711UCodec: IAudioCodec | |||||
{ | { | ||||
/* 16384 entries per table (16 bit) */ | /* 16384 entries per table (16 bit) */ | ||||
readonly byte[] linearToUlawTable = new byte[65536]; | readonly byte[] linearToUlawTable = new byte[65536]; | ||||
/* 16384 entries per table (8 bit) */ | /* 16384 entries per table (8 bit) */ | ||||
readonly short[] ulawToLinearTable = new short[256]; | readonly short[] ulawToLinearTable = new short[256]; | ||||
readonly int SIGN_BIT = 0x80; | readonly int SIGN_BIT = 0x80; | ||||
@@ -90,23 +89,26 @@ namespace JT1078.Flv.Audio | |||||
return pcmSamples; | 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> | <licenseUrl>https://github.com/SmallChi/JT1078/blob/master/LICENSE</licenseUrl> | ||||
<license>https://github.com/SmallChi/JT1078/blob/master/LICENSE</license> | <license>https://github.com/SmallChi/JT1078/blob/master/LICENSE</license> | ||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild> | <GeneratePackageOnBuild>false</GeneratePackageOnBuild> | ||||
<Version>1.0.3</Version> | |||||
<Version>1.0.4-preview1</Version> | |||||
<SignAssembly>false</SignAssembly> | <SignAssembly>false</SignAssembly> | ||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> | <PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> | ||||
<PackageLicenseFile>LICENSE</PackageLicenseFile> | <PackageLicenseFile>LICENSE</PackageLicenseFile> | ||||
@@ -24,6 +24,11 @@ | |||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' "> | <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' "> | ||||
<PackageReference Include="System.Memory" Version="4.5.4" /> | <PackageReference Include="System.Memory" Version="4.5.4" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | |||||
<Compile Remove="Audio\FaacEncoder.cs" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | <ItemGroup> | ||||
<None Include="..\..\LICENSE"> | <None Include="..\..\LICENSE"> | ||||