After several years of perfect Skype functioning, I began to have problems with choppy/robotized voice. The connection technical information reported a huge packet loss downstream, but I did not pay much attention at first, because those statistics are not that reliable (for me, it always showed high loss rates even when voice was perfect.)
A funny thing was, my wife has been using Skype without complaining (and believe me, she *would* complain at the smallest problem :) while I could not even make a Skype-to-Skype call. WTF? The first suspect became my own computer, because my wife uses her own MacBook, while I have an iMac. My SkypeOut calls were being affected too.
Sometimes, that MacBook was not even configured with incoming ports (which tend to make Skype work better).
Last month, aided by those observations, I discovered that Skype calls went much better when techincal data showed a DIRECT_TCP mode for incoming data, which means that TCP is being used to carry voice. I guess DIRECT_TCP is more a firewall-piercing alternate protocol than a better option for voice, but in my case it solved the voice issues. Problem is, Skype does not have a configuration flag to force this mode.
At the past weekend, I found that, if I blocked UDP packets on Mac, thereby forcing DIRECT_TCP mode, the Skype calls to "test123" (call testing service) went flawlessly, while they were almost impossible to understand while using UDP. I know iptables very well, but did not know BSD ipfw command until that time. The magic command line is:
sudo ipfw add deny udp from any to any 43245 in
while "43245" is the port, which you can configure in Skype "Advanced" tab. It is important to know the port, otherwise you'd have to block all UDP incoming connections, probably breaking other protocols.
Today I recomissioned an old machine here to aid in my work with BlueZ (Linux Bluetooth stack), and then I took the opportunity to test Skype for Linux on it. I still thought that it could be some Mac-related problem. But Linux version showed the same robotized/chopped voice problem. An iptables-based block on UDP packets solved the issue. Some days ago, Skype launched a full-featured Symbian version. I tried that too on my N85; exactly the same problem with voice! (Fring worked ok over the same Wifi network.)
So, Mac was not causing the problem. The remaining suspects: ADSL modem, Wi-Fi, ADSL protocol (1 or 2), and the ISP (Oi/Velox). Everyone except the ISP was found innocent by a direct PPPoE connection using another modem and wired Ethernet.
But still, I had the burden of proof. I swore that this thing would not pass unexplained today, so it was time to make some network tests. I built a simple Python program (listed at the end of this post) which exchanged UDP packets between my PC and a remote server.
That's what I found: indeed the ISP is throttling downstream UDP packets. It throttles both the number of packets and the total bandwidth. Anything above 10-15 packets per second or 20kbps (that's bits per second, not bytes, mind you) makes some packets disappear, so the total packet count/bandwidth received by PC does not go over the above limits.
I had captured some Skype UDP packets and used their payloads as a bait, but it turns out that payload does not make a difference, you can just fill the UDP packet with spaces or whatever, and it is throttled anyway.
The throttling seems not to try to add jitter or change the order of packets; it simply discards them in a monotonic way. If you send 20 packets per second, it generally discards one of two. If you send 30, it discards one in three and so on.
Very small packets (< 35 bytes) seem not to trigger the throttling. Unfortunately, Skype UDP voice packets are in the 50-100 byte range.
Using few large packets is not enough to get along with a lot of bandwidth; 1250-byte packets trigger throttling at as few as 5 packets per second. It seems that minimum Skype bandwidth is around 40kbps for voice, so both bandwidth and packet rhythm are prone to trigger the throttle.
SkypeOut uses G.729 compression, which uses less bandwidth than pure Skype calls. That's what my wife uses most, so this may be an additional explanation why she hasn't had many problems, while I had issues all the time.
I don't know if this throttling was meant to hurt Skype, or it targets the P2P file-sharing protocols, which use UDP for diffusion. The absolute limits (40kbps, packets > 35 bytes) suggest that the throttling was indeed configured to hurt Skype. In the other hand, Skype network is a by-product of Kazaa, a file sharing P2P network, so it's expected that Skype has the same "cross-section".
Around 21:00 local time, something interesting happened. The throttling seemed to ease up. I could pass around 50kbps at 100 pkts/sec, but not 80kbps at the same rate. I guess that Skype can adapt itself in this scenario after a minute or two, by using a lighter codec, as happened so many times when I tested it with friends.
I guess that, for now, the only real solution is to block UDP entirely, or use Fring, which has a Skype gateway.
The Python program below was used for the tests. Maybe someone else with Skype issues would like to make additional tests.
#!/usr/bin/env python
# bits per second
bps = 30 * 8 * 50
# packets per second
pps = 30
Bps = bps / 8
psize = Bps / pps
print "Packet size", psize
d = {}
d["incoming"] = [psize, ""]
d["outgoing"] = [psize, ""]
for k in d.keys():
d[k][1] = "X" * d[k][0]
d[k] = d[k][1]
import sys, socket, time, select, math
assert(sys.argv[1] == "cli" or sys.argv[1] == "srv")
is_server = sys.argv[1] == "srv"
cli = socket.gethostbyname("local_machine")
srv = socket.gethostbyname("remote_machine")
ops = 1000
port = 54325
rhythm = 1.0 / pps
buffer = {}
lastseq = -1
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('', port))
distances = []
client_feedback = 0
total = duplicates = outoforder = 0
seq = 1
next_packet = time.time()
while ops >= 0:
t0 = time.time()
timeout = max(0, next_packet - t0)
rd, wr, ex = select.select([s], [], [], timeout)
t1 = time.time()
while t1 >= next_packet:
# timeout has passed
next_packet += rhythm
if is_server:
# server: send packet
if client_feedback:
# but only if client has sent something, too
client_feedback -= 1
msg = d["incoming"]
msg += "%9d" % seq
seq += 1
s.sendto(msg, (cli, port))
else:
seq = 1
else:
# client: send packet
ops -= 1
s.sendto(d["outgoing"], (srv, port))
if rd:
# packet has arrived
msg, addr = s.recvfrom(8192)
if not is_server and addr[0] != srv:
# stray packet, ignore
continue
client_feedback = 200
total += 1
if is_server:
# server just checks contents
assert(msg == d["outgoing"])
else:
# client checks, extracts seq and calculates time-distance
assert(msg[0:len(d["incoming"])] == d["incoming"])
assert(len(msg) == len(d["incoming"]) + 9)
thisseq = int(msg[-9:])
print thisseq
if buffer.has_key(thisseq):
print "\tRepetida"
duplicates += 1
if buffer.has_key(thisseq - 1):
distances.append(t1 - buffer[thisseq - 1])
if thisseq != (lastseq + 1):
if buffer:
print "\tOut of order or jumped seq"
outoforder += 1
buffer[thisseq] = t1
lastseq = thisseq
if sys.argv[1] == "cli":
average = sum(distances) / len(distances)
deviation = 0.0
for t in distances:
deviation += (t - average) ** 2
deviation = math.sqrt(deviation / len(distances))
print "Avg distance (ms): ", average * 1000
print "Drift: ", (rhythm - average) * 1000
print "Jitter: ", deviation * 1000
print "Duplicates", duplicates
print "Out-of-order, jumped seq", outoforder
print "Total", total