|
|
|
@ -651,122 +651,125 @@ namespace dvmconsole
|
|
|
|
{
|
|
|
|
{
|
|
|
|
try
|
|
|
|
try
|
|
|
|
{
|
|
|
|
{
|
|
|
|
var channel = selectedChannelsManager.PrimaryChannel;
|
|
|
|
ChannelBox primaryChannel = selectedChannelsManager.PrimaryChannel;
|
|
|
|
if (channel == null)
|
|
|
|
List<ChannelBox> channelsToProcess = primaryChannel != null
|
|
|
|
|
|
|
|
? new List<ChannelBox> { primaryChannel }
|
|
|
|
|
|
|
|
: selectedChannelsManager.GetSelectedChannels().ToList();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
foreach (ChannelBox channel in channelsToProcess)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (channel.SystemName == PLAYBACKSYS || channel.ChannelName == PLAYBACKCHNAME || channel.DstId == PLAYBACKTG)
|
|
|
|
if (channel.SystemName == PLAYBACKSYS || channel.ChannelName == PLAYBACKCHNAME || channel.DstId == PLAYBACKTG)
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
|
|
|
|
Codeplug.System system = Codeplug.GetSystemForChannel(channel.ChannelName);
|
|
|
|
if (system == null)
|
|
|
|
if (system == null)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
Log.WriteLine($"{channel.ChannelName} refers to an {INVALID_SYSTEM} {channel.SystemName}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}.");
|
|
|
|
Log.WriteLine($"{channel.ChannelName} refers to an {INVALID_SYSTEM} {channel.SystemName}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}.");
|
|
|
|
channel.IsSelected = false;
|
|
|
|
channel.IsSelected = false;
|
|
|
|
selectedChannelsManager.RemoveSelectedChannel(channel);
|
|
|
|
selectedChannelsManager.RemoveSelectedChannel(channel);
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
|
|
|
|
Codeplug.Channel cpgChannel = Codeplug.GetChannelByName(channel.ChannelName);
|
|
|
|
if (cpgChannel == null)
|
|
|
|
if (cpgChannel == null)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
Log.WriteLine($"{channel.ChannelName} refers to an {INVALID_CODEPLUG_CHANNEL}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}.");
|
|
|
|
Log.WriteLine($"{channel.ChannelName} refers to an {INVALID_CODEPLUG_CHANNEL}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}.");
|
|
|
|
channel.IsSelected = false;
|
|
|
|
channel.IsSelected = false;
|
|
|
|
selectedChannelsManager.RemoveSelectedChannel(channel);
|
|
|
|
selectedChannelsManager.RemoveSelectedChannel(channel);
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
PeerSystem fne = fneSystemManager.GetFneSystem(system.Name);
|
|
|
|
PeerSystem fne = fneSystemManager.GetFneSystem(system.Name);
|
|
|
|
if (fne == null)
|
|
|
|
if (fne == null)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
Log.WriteLine($"{channel.ChannelName} has a {ERR_INVALID_FNE_REF}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}.");
|
|
|
|
Log.WriteLine($"{channel.ChannelName} has a {ERR_INVALID_FNE_REF}. {ERR_INVALID_CODEPLUG}. {ERR_SKIPPING_AUDIO}.");
|
|
|
|
channel.IsSelected = false;
|
|
|
|
channel.IsSelected = false;
|
|
|
|
selectedChannelsManager.RemoveSelectedChannel(channel);
|
|
|
|
selectedChannelsManager.RemoveSelectedChannel(channel);
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
if (channel.PageState || (forHold && channel.HoldState) || primaryChannel != null)
|
|
|
|
if (channel.PageState || (forHold && channel.HoldState))
|
|
|
|
{
|
|
|
|
{
|
|
|
|
byte[] pcmData;
|
|
|
|
byte[] pcmData;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Task.Run(async () => {
|
|
|
|
Task.Run(async () =>
|
|
|
|
using (var waveReader = new WaveFileReader(filePath))
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if (waveReader.WaveFormat.Encoding != WaveFormatEncoding.Pcm ||
|
|
|
|
using (var waveReader = new WaveFileReader(filePath))
|
|
|
|
waveReader.WaveFormat.SampleRate != 8000 ||
|
|
|
|
|
|
|
|
waveReader.WaveFormat.BitsPerSample != 16 ||
|
|
|
|
|
|
|
|
waveReader.WaveFormat.Channels != 1)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
MessageBox.Show("The alert tone must be PCM 16-bit, Mono, 8000Hz format.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
|
if (waveReader.WaveFormat.Encoding != WaveFormatEncoding.Pcm ||
|
|
|
|
return;
|
|
|
|
waveReader.WaveFormat.SampleRate != 8000 ||
|
|
|
|
}
|
|
|
|
waveReader.WaveFormat.BitsPerSample != 16 ||
|
|
|
|
|
|
|
|
waveReader.WaveFormat.Channels != 1)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
MessageBox.Show("The alert tone must be PCM 16-bit, Mono, 8000Hz format.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
using (MemoryStream ms = new MemoryStream())
|
|
|
|
using (MemoryStream ms = new MemoryStream())
|
|
|
|
{
|
|
|
|
{
|
|
|
|
waveReader.CopyTo(ms);
|
|
|
|
waveReader.CopyTo(ms);
|
|
|
|
pcmData = ms.ToArray();
|
|
|
|
pcmData = ms.ToArray();
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int chunkSize = 1600;
|
|
|
|
int chunkSize = 1600;
|
|
|
|
int totalChunks = (pcmData.Length + chunkSize - 1) / chunkSize;
|
|
|
|
int totalChunks = (pcmData.Length + chunkSize - 1) / chunkSize;
|
|
|
|
|
|
|
|
|
|
|
|
if (pcmData.Length % chunkSize != 0)
|
|
|
|
if (pcmData.Length % chunkSize != 0)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
byte[] paddedData = new byte[totalChunks * chunkSize];
|
|
|
|
byte[] paddedData = new byte[totalChunks * chunkSize];
|
|
|
|
Buffer.BlockCopy(pcmData, 0, paddedData, 0, pcmData.Length);
|
|
|
|
Buffer.BlockCopy(pcmData, 0, paddedData, 0, pcmData.Length);
|
|
|
|
pcmData = paddedData;
|
|
|
|
pcmData = paddedData;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Task.Run(() =>
|
|
|
|
Task.Run(() =>
|
|
|
|
{
|
|
|
|
{
|
|
|
|
audioManager.AddTalkgroupStream(cpgChannel.Tgid, pcmData);
|
|
|
|
audioManager.AddTalkgroupStream(cpgChannel.Tgid, pcmData);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
DateTime startTime = DateTime.UtcNow;
|
|
|
|
DateTime startTime = DateTime.UtcNow;
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < totalChunks; i++)
|
|
|
|
for (int i = 0; i < totalChunks; i++)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
int offset = i * chunkSize;
|
|
|
|
int offset = i * chunkSize;
|
|
|
|
byte[] chunk = new byte[chunkSize];
|
|
|
|
byte[] chunk = new byte[chunkSize];
|
|
|
|
Buffer.BlockCopy(pcmData, offset, chunk, 0, chunkSize);
|
|
|
|
Buffer.BlockCopy(pcmData, offset, chunk, 0, chunkSize);
|
|
|
|
|
|
|
|
|
|
|
|
channel.chunkedPCM = AudioConverter.SplitToChunks(chunk);
|
|
|
|
channel.chunkedPCM = AudioConverter.SplitToChunks(chunk);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (byte[] audioChunk in channel.chunkedPCM)
|
|
|
|
foreach (byte[] audioChunk in channel.chunkedPCM)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (audioChunk.Length == PCM_SAMPLES_LENGTH)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if (cpgChannel.GetChannelMode() == Codeplug.ChannelMode.P25)
|
|
|
|
if (audioChunk.Length == PCM_SAMPLES_LENGTH)
|
|
|
|
P25EncodeAudioFrame(audioChunk, fne, channel, cpgChannel, system);
|
|
|
|
{
|
|
|
|
else if (cpgChannel.GetChannelMode() == Codeplug.ChannelMode.DMR)
|
|
|
|
if (cpgChannel.GetChannelMode() == Codeplug.ChannelMode.P25)
|
|
|
|
DMREncodeAudioFrame(audioChunk, fne, channel, cpgChannel, system);
|
|
|
|
P25EncodeAudioFrame(audioChunk, fne, channel, cpgChannel, system);
|
|
|
|
|
|
|
|
else if (cpgChannel.GetChannelMode() == Codeplug.ChannelMode.DMR)
|
|
|
|
|
|
|
|
DMREncodeAudioFrame(audioChunk, fne, channel, cpgChannel, system);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DateTime nextPacketTime = startTime.AddMilliseconds((i + 1) * 100);
|
|
|
|
DateTime nextPacketTime = startTime.AddMilliseconds((i + 1) * 100);
|
|
|
|
TimeSpan waitTime = nextPacketTime - DateTime.UtcNow;
|
|
|
|
TimeSpan waitTime = nextPacketTime - DateTime.UtcNow;
|
|
|
|
|
|
|
|
|
|
|
|
if (waitTime.TotalMilliseconds > 0)
|
|
|
|
if (waitTime.TotalMilliseconds > 0)
|
|
|
|
await Task.Delay(waitTime);
|
|
|
|
await Task.Delay(waitTime);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
double totalDurationMs = ((double)pcmData.Length / 16000) + 250;
|
|
|
|
double totalDurationMs = ((double)pcmData.Length / 16000) + 250;
|
|
|
|
await Task.Delay((int)totalDurationMs + 3000);
|
|
|
|
await Task.Delay((int)totalDurationMs + 3000);
|
|
|
|
|
|
|
|
|
|
|
|
fne.SendP25TDU(uint.Parse(system.Rid), uint.Parse(cpgChannel.Tgid), false);
|
|
|
|
fne.SendP25TDU(uint.Parse(system.Rid), uint.Parse(cpgChannel.Tgid), false);
|
|
|
|
|
|
|
|
|
|
|
|
Dispatcher.Invoke(() =>
|
|
|
|
Dispatcher.Invoke(() =>
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if (forHold)
|
|
|
|
if (forHold)
|
|
|
|
channel.PttButton.Background = ChannelBox.GRAY_GRADIENT;
|
|
|
|
channel.PttButton.Background = ChannelBox.GRAY_GRADIENT;
|
|
|
|
else
|
|
|
|
else
|
|
|
|
channel.PageState = false;
|
|
|
|
channel.PageState = false;
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
catch (Exception ex)
|
|
|
|
|