// Jitter buffer wrapper API // Locking must be done at the application level to ensure // that two threads can't be in jitter buffer methods. // timestamp is a counter incremented once per "tick" public void JitterBufferPut(byte[] frame, int startIndex, uint byteCount, uint timestamp) { if (jitterBuffer == null) { throw new Exception("JitterBufferPut: jitterBuffer is null!"); } lock (jitterBufferLockable) { JitterBufferPacket p = new JitterBufferPacket(); unsafe { fixed(byte *frameBytes = &frame[startIndex]) { p.data = frameBytes; p.len = byteCount; p.timestamp = timestamp; p.span = (uint)frameSize; jitter_buffer_put(jitterBuffer, &p); } } } }
/// <summary> /// Get one packet from the jitter buffer /// </summary> /// <param name="packet"></param> /// <param name="desired_span"></param> /// <param name="start_offset"></param> /// <returns></returns> public int Get(ref JitterBufferPacket packet, int desired_span, out int start_offset) { if (desired_span <= 0) { throw new ArgumentOutOfRangeException("desired_span"); } int i; long j; short opt; start_offset = 0; /* Syncing on the first call */ if (reset_state) { bool found = false; /* Find the oldest packet */ long oldest = 0; for (i = 0; i < MAX_BUFFER_SIZE; i++) { if (packets[i].data != null && (!found || packets[i].timestamp < oldest)) { oldest = packets[i].timestamp; found = true; } } if (found) { reset_state = false; pointer_timestamp = oldest; next_stop = oldest; } else { packet.timestamp = 0; packet.span = interp_requested; return(JITTER_BUFFER_MISSING); } } last_returned_timestamp = pointer_timestamp; if (interp_requested != 0) { packet.timestamp = pointer_timestamp; packet.span = interp_requested; /* Increment the pointer because it got decremented in the delay update */ pointer_timestamp += interp_requested; packet.len = 0; /*fprintf (stderr, "Deferred interpolate\n");*/ interp_requested = 0; buffered = packet.span - desired_span; return(JITTER_BUFFER_INSERTION); } /* Searching for the packet that fits best */ /* Search the buffer for a packet with the right timestamp and spanning the whole current chunk */ for (i = 0; i < MAX_BUFFER_SIZE; i++) { if (packets[i].data != null && packets[i].timestamp == pointer_timestamp && (packets[i].timestamp + packets[i].span) >= (pointer_timestamp + desired_span)) { break; } } /* If no match, try for an "older" packet that still spans (fully) the current chunk */ if (i == MAX_BUFFER_SIZE) { for (i = 0; i < MAX_BUFFER_SIZE; i++) { if (packets[i].data != null && packets[i].timestamp <= pointer_timestamp && (packets[i].timestamp + packets[i].span) >= (pointer_timestamp + desired_span)) { break; } } } /* If still no match, try for an "older" packet that spans part of the current chunk */ if (i == MAX_BUFFER_SIZE) { for (i = 0; i < MAX_BUFFER_SIZE; i++) { if (packets[i].data != null && packets[i].timestamp <= pointer_timestamp && (packets[i].timestamp + packets[i].span) > pointer_timestamp) { break; } } } /* If still no match, try for earliest packet possible */ if (i == MAX_BUFFER_SIZE) { bool found = false; long best_time = 0; long best_span = 0; int besti = 0; for (i = 0; i < MAX_BUFFER_SIZE; i++) { /* check if packet starts within current chunk */ if (packets[i].data != null && packets[i].timestamp < (pointer_timestamp + desired_span) && (packets[i].timestamp >= pointer_timestamp)) { if (!found || packets[i].timestamp < best_time || (packets[i].timestamp == best_time && packets[i].span > best_span)) { best_time = packets[i].timestamp; best_span = packets[i].span; besti = i; found = true; } } } if (found) { i = besti; /*fprintf (stderr, "incomplete: %d %d %d %d\n", packets[i].timestamp, pointer_timestamp, chunk_size, packets[i].span);*/ } } /* If we find something */ if (i != MAX_BUFFER_SIZE) { int offset; /* We (obviously) haven't lost this packet */ lost_count = 0; /* In this case, 0 isn't as a valid timestamp */ if (arrival[i] != 0) { UpdateTimings(((int)packets[i].timestamp) - ((int)arrival[i]) - buffer_margin); } /* Copy packet */ if (DestroyBufferCallback != null) { packet.data = packets[i].data; packet.len = packets[i].len; } else { if (packets[i].len > packet.len) { Debug.WriteLine("JitterBuffer.Get(): packet too large to fit. Size is", packets[i].len); } else { packet.len = packets[i].len; } for (j = 0; j < packet.len; j++) { packet.data[j] = packets[i].data[j]; } /* Remove packet */ FreeBuffer(packets[i].data); } packets[i].data = null; /* Set timestamp and span (if requested) */ offset = (int)packets[i].timestamp - (int)pointer_timestamp; if (start_offset != 0) { start_offset = offset; } else if (offset != 0) { Debug.WriteLine("JitterBuffer.Get(): discarding non-zero start_offset", offset); } packet.timestamp = packets[i].timestamp; last_returned_timestamp = packet.timestamp; packet.span = packets[i].span; packet.sequence = packets[i].sequence; packet.user_data = packets[i].user_data; packet.len = packets[i].len; /* Point to the end of the current packet */ pointer_timestamp = packets[i].timestamp + packets[i].span; buffered = packet.span - desired_span; if (start_offset != 0) { buffered += start_offset; } return(JITTER_BUFFER_OK); } /* If we haven't found anything worth returning */ /*fprintf (stderr, "not found\n");*/ lost_count++; /*fprintf (stderr, "m");*/ /*fprintf (stderr, "lost_count = %d\n", lost_count);*/ opt = ComputeOptDelay(); /* Should we force an increase in the buffer or just do normal interpolation? */ if (opt < 0) { /* Need to increase buffering */ /* Shift histogram to compensate */ ShiftTimings((short)-opt); packet.timestamp = pointer_timestamp; packet.span = -opt; /* Don't move the pointer_timestamp forward */ packet.len = 0; buffered = packet.span - desired_span; return(JITTER_BUFFER_INSERTION); /*pointer_timestamp -= delay_step;*/ /*fprintf (stderr, "Forced to interpolate\n");*/ } else { /* Normal packet loss */ packet.timestamp = pointer_timestamp; desired_span = RoundDown(desired_span, concealment_size); packet.span = desired_span; pointer_timestamp += desired_span; packet.len = 0; buffered = packet.span - desired_span; return(JITTER_BUFFER_MISSING); /*fprintf (stderr, "Normal loss\n");*/ } }
/// <summary> /// Put one packet into the jitter buffer /// </summary> /// <param name="packet"></param> public void Put(JitterBufferPacket packet) { int i, j; bool late; /*fprintf (stderr, "put packet %d %d\n", timestamp, span);*/ // Cleanup buffer (remove old packets that weren't played) if (!reset_state) { for (i = 0; i < MAX_BUFFER_SIZE; i++) { /* Make sure we don't discard a "just-late" packet in case we want to play it next (if we interpolate). */ if (packets[i].data != null && (packets[i].timestamp + packets[i].span) <= pointer_timestamp) { /*fprintf (stderr, "cleaned (not played)\n");*/ if (DestroyBufferCallback != null) { DestroyBufferCallback(packets[i].data); } else { FreeBuffer(packets[i].data); } packets[i].data = null; } } } /*fprintf(stderr, "arrival: %d %d %d\n", packet.timestamp, next_stop, pointer_timestamp);*/ /* Check if packet is late (could still be useful though) */ if (!reset_state && packet.timestamp < next_stop) { UpdateTimings(((int)packet.timestamp) - ((int)next_stop) - buffer_margin); late = true; } else { late = false; } /* For some reason, the consumer has failed the last 20 fetches. Make sure this packet is * used to resync. */ if (lost_count > 20) { Reset(); } /* Only insert the packet if it's not hopelessly late (i.e. totally useless) */ if (reset_state || (packet.timestamp + packet.span + delay_step) >= pointer_timestamp) { /*Find an empty slot in the buffer*/ for (i = 0; i < MAX_BUFFER_SIZE; i++) { if (packets[i].data == null) { break; } } /*No place left in the buffer, need to make room for it by discarding the oldest packet */ if (i == MAX_BUFFER_SIZE) { long earliest = packets[0].timestamp; i = 0; for (j = 1; j < MAX_BUFFER_SIZE; j++) { if (packets[i].data == null || packets[j].timestamp < earliest) { earliest = packets[j].timestamp; i = j; } } if (DestroyBufferCallback != null) { DestroyBufferCallback(packets[i].data); } else { FreeBuffer(packets[i].data); } packets[i].data = null; /*fprintf (stderr, "Buffer is full, discarding earliest frame %d (currently at %d)\n", timestamp, pointer_timestamp);*/ } /* Copy packet in buffer */ if (DestroyBufferCallback != null) { packets[i].data = packet.data; } else { packets[i].data = AllocBuffer(packet.len); for (j = 0; j < packet.len; j++) { packets[i].data[j] = packet.data[j]; } } packets[i].timestamp = packet.timestamp; packets[i].span = packet.span; packets[i].len = packet.len; packets[i].sequence = packet.sequence; packets[i].user_data = packet.user_data; if (reset_state || late) { arrival[i] = 0; } else { arrival[i] = next_stop; } } }
// Returns the length of the _encoded_ frame in bytes public void JitterBufferGet(short[] decodedFrame, uint timestamp, ref int startOffset) { int i; int ret; int activity = 0; if (jitterBuffer == null) { throw new Exception("JitterBufferPut: jitterBuffer is null!"); } lock (jitterBufferLockable) { if (validJitterBits) { // Try decoding last received packet ret = DecoderDecodeBits(decodedFrame); if (ret == 0) { jitter_buffer_tick(jitterBuffer); return; } else { validJitterBits = false; } } JitterBufferPacket packet = new JitterBufferPacket(); packet.span = (uint)frameSize; packet.timestamp = timestamp; // The encoded buffer must be fixed, because // jitter_buffer_get refers to it through packet unsafe { fixed(byte *pData = &encodedJitterFrame[0]) { fixed(int *pStartOffset = &startOffset) { packet.data = pData; packet.span = (uint)frameSize; packet.len = 2048; ret = jitter_buffer_get(jitterBuffer, &packet, frameSize, pStartOffset); } } } encodedJitterFrameErrorCode = ret; if (ret != (int)JitterBufferRetCode.JITTER_BUFFER_OK) { // No packet found: Packet is late or lost DecoderDecodeNullBits(decodedFrame); } else { encodedJitterFrameLength = (int)packet.len; DecoderReadFrom(encodedJitterFrame, encodedJitterFrameLength); /* Decode packet */ ret = DecoderDecodeBits(decodedFrame); if (ret == 0) { validJitterBits = true; } else { /* Error while decoding */ for (i = 0; i < frameSize; i++) { decodedFrame[i] = 0; } } } GetOneCodecSetting(false, SpeexCtlCode.SPEEX_GET_ACTIVITY, ref activity); if (activity < 30) { jitter_buffer_update_delay(jitterBuffer, &packet, null); } jitter_buffer_tick(jitterBuffer); } }
public static extern int jitter_buffer_update_delay(IntPtr jitter, ref JitterBufferPacket packet, [Out] out int start_offset);
public static extern int jitter_buffer_get_another(IntPtr jitter, ref JitterBufferPacket packet);
public static extern int jitter_buffer_get(IntPtr jitter, ref JitterBufferPacket packet, int desired_span, [Out] out int start_offset);
public static extern void jitter_buffer_put(IntPtr jitter, ref JitterBufferPacket packet);