private SampleChunkInfo GetMinJitterTimeReference(uint sampleRate)
        {
            DateTime        startTime         = this.sampleInfo[0].Timestamp;
            uint            startSampleTick   = this.sampleInfo[0].SampleTick;
            double          minJitter         = 100;
            SampleChunkInfo bestTuple         = null;
            uint            firstSyncSourceId = this.sampleInfo[0].SyncSourceID;

            foreach (SampleChunkInfo tuple in this.sampleInfo)
            {
                if (tuple.SyncSourceID != firstSyncSourceId)
                {
                    break;
                }
                double seconds     = tuple.Timestamp.Subtract(startTime).TotalSeconds;
                uint   sampleTicks = tuple.SampleTick - startSampleTick;
                //avoid using the first chunk
                if (sampleTicks > 0 && seconds * sampleRate - sampleTicks < minJitter)
                {
                    minJitter = seconds * sampleRate - sampleTicks;
                    bestTuple = tuple;
                }
            }
            return(bestTuple);
        }
            public override int Read(byte[] buffer, int offset, int count)
            {
                int bytesRead;

                lock (this.pcm16Stream) {
                    lock (this.audioStream.tempFileStream) {
                        while (this.writePosition < this.readPosition + count && this.sampleChunkIndex < this.audioStream.sampleInfo.Count)
                        {
                            this.pcm16Stream.Position = writePosition;
                            SampleChunkInfo sampleInfo = this.audioStream.sampleInfo[this.sampleChunkIndex];
                            if (this.currentSyncSourceID == null)
                            {
                                this.currentSyncSourceID = sampleInfo.SyncSourceID;
                            }
                            else if (this.currentSyncSourceID.Value != sampleInfo.SyncSourceID)
                            {
                                //new source
                                this.currentSyncSourceID = sampleInfo.SyncSourceID;
                                this.firstSampleTick     = sampleInfo.SampleTick - this.writePosition / 2;
                                if (this.firstSampleTick < 0)
                                {
                                    SharedUtils.Logger.Log("Changing VoIP first sample tick from " + this.firstSampleTick + " to 0", SharedUtils.Logger.EventLogEntryType.Warning);
                                    this.firstSampleTick = 0;
                                }
                            }

                            this.audioStream.tempFileStream.Position = sampleInfo.TempFsPosition;
                            byte[] inputBuffer    = new byte[sampleInfo.DataLength];
                            int    inputBytesRead = this.audioStream.tempFileStream.Read(inputBuffer, 0, inputBuffer.Length);
                            if (this.insertSilenceOnMissingSamples)
                            {
                                while (sampleInfo.SampleTick > firstSampleTick + this.writePosition / 2)
                                {
                                    byte[] firstSample = Utils.ByteConverter.ToByteArray((ushort)this.decompressionTable[inputBuffer[0]], true);
                                    this.pcm16Stream.Write(firstSample, 0, 2);//write first value of the input stream
                                    this.writePosition += 2;
                                }
                            }
                            //byte[] pcm16 = new byte[inputBytesRead * 2];
                            for (int i = 0; i < inputBytesRead; i++)
                            {
                                //byte[] b2 = Utils.ByteConverter.ToByteArray((ushort)decompressionTable[inputBuffer[i]], true);
                                //pcm16[2 * i] = b2[0];
                                //pcm16[2 * i + 1] = b2[1];
                                this.pcm16Stream.Write(Utils.ByteConverter.ToByteArray((ushort)decompressionTable[inputBuffer[i]], true), 0, 2);
                                this.writePosition += 2;
                            }
                            //this.pcm16Stream.Write(pcm16, 0, pcm16.Length);
                            //this.writePosition += pcm16.Length;

                            this.sampleChunkIndex++;
                        }
                    }
                    this.pcm16Stream.Position = this.readPosition;
                    bytesRead          = this.pcm16Stream.Read(buffer, offset, count);
                    this.readPosition += bytesRead;
                }
                return(bytesRead);
            }
        public FileTransfer.FileStreamAssembler MergeAsStereoWavAssembler(AudioStream other)
        {
            uint sampleRate = 8000;

            FileTransfer.WavFileAssembler mergedAssembler = new FileTransfer.WavFileAssembler("MergedAudioStreams-" + FiveTuple.GetHashCode() + ".wav", this.fileStreamAssemblerList, this.FiveTuple, FileTransfer.FileStreamTypes.RTP, this.initialFrameNumber, this.StartTime, sampleRate);
            //figure out if sample rates match with sampleTicks etc.
            //double thisSampleRateSkew = this.GetSampleTicksPerSecond() / sampleRate;
            //double otherSampleRateSkew = other.GetSampleTicksPerSecond() / sampleRate;

            //if (thisSampleRateSkew > 0.9 && thisSampleRateSkew < 1.11 && otherSampleRateSkew > 0.9 && otherSampleRateSkew < 1.11) {
            //figure out the correct start time and lock that to a sampleTick
            double nanosecondHundredsPerSample = 10000000.0 / sampleRate;//8000Hz => 1250

            SampleChunkInfo thisTimeReference        = this.GetMinJitterTimeReference(sampleRate);
            TimeSpan        thisTicksReferenceOffset = new TimeSpan((long)(nanosecondHundredsPerSample * ((int)thisTimeReference.SampleTick - this.sampleInfo[0].SampleTick)));
            DateTime        thisFirstSampleTimestamp = thisTimeReference.Timestamp.Subtract(thisTicksReferenceOffset);

            SampleChunkInfo otherTimeReference        = other.GetMinJitterTimeReference(sampleRate);
            TimeSpan        otherTicksReferenceOffset = new TimeSpan((long)(nanosecondHundredsPerSample * ((int)otherTimeReference.SampleTick - other.sampleInfo[0].SampleTick)));
            DateTime        otherFirstSampleTimestamp = otherTimeReference.Timestamp.Subtract(otherTicksReferenceOffset);

            long thisSampleTicksOffset, otherSampleTicksOffset;

            if (thisFirstSampleTimestamp < otherFirstSampleTimestamp)
            {
                thisSampleTicksOffset  = this.sampleInfo[0].SampleTick;
                otherSampleTicksOffset = otherTimeReference.SampleTick - (long)(otherTimeReference.Timestamp.Subtract(thisFirstSampleTimestamp).Ticks / nanosecondHundredsPerSample);
            }
            else
            {
                thisSampleTicksOffset  = thisTimeReference.SampleTick - (long)(thisTimeReference.Timestamp.Subtract(otherFirstSampleTimestamp).Ticks / nanosecondHundredsPerSample);
                otherSampleTicksOffset = other.sampleInfo[0].SampleTick;
            }

            var thisLastTuple  = this.sampleInfo[this.sampleInfo.Count - 1];
            var otherLastTuple = other.sampleInfo[other.sampleInfo.Count - 1];

            //uint metaDataLength = 0;
            mergedAssembler.TryActivate();



            //nSamples might be incorrect here
            Pcm16BitSampleStream thisStream  = new Pcm16BitSampleStream(this, thisSampleTicksOffset, true);
            Pcm16BitSampleStream otherStream = new Pcm16BitSampleStream(other, otherSampleTicksOffset, true);

            //uint nSamples = (uint)Math.Max(thisLastTuple.SampleTick + thisLastTuple.DataLength - thisSampleTicksOffset, otherLastTuple.SampleTick + otherLastTuple.DataLength - otherSampleTicksOffset);
            uint nSamples = mergedAssembler.CountSamplesInStreams(thisStream, otherStream);

            //reset positions
            thisStream.Position  = 0;
            otherStream.Position = 0;

            mergedAssembler.WriteSampleStreamToFile(nSamples, thisStream, otherStream);

            //mergedAssembler.FinishAssembling();
            return(mergedAssembler);
        }