Part 1
[Talkie](http://atastypixel.com/products/talkie) is my newest product, the result of a collaboration with a good designer friend, Tim Churchward, who did the user interface.
Talkie is a little different from many of the other walkie talkie applications on the App Store (aside from the fact that much of it was written by me from our motorhome in [Tunisia](http://michael.tyson.id.au/2010/01/26/down-time-in-hammamet/)!), and I thought I’d write a little about some of the tech underpinning the app, and some of the choices we made. Along the way it may get a little tutorial-esque.
- This first part will introduce our initial motivations, and will talk about basic broadcast communications — the broadcast communications part may be very familiar to some, in which case it may be worth skipping to the next instalment.
- In the [second part](http://atastypixel.com/the-making-of-talkie-broadcasting/), I’ll continue the theme of networking, and will talk about what I ended up with for Talkie’s network code after addressing a couple of things, including switching to multicast.
- Finally, I’ll talk audio, dual platform development, and anything else I think of along the way (Actually, I’m aching to talk about one particular upcoming feature that had me jumping up and down when I first thought of it, but for now, mum’s the word on that one.)## Inspiration
Right from the start, we wanted a product that brought back the fun walkie talkie experience we remember from our childhoods. I’m talking colourful plastic, whip antennas and hiding in tree-houses. This was mostly Tim’s domain, so I shall leave it to him to discuss how he found that in the user interface.
It also meant stepping back from traditional voice chat, with manual call initialisation and termination and simple one-to-one calls. We wanted to mimic a radio, so that meant broadcasting as soon as you hit “Talk” — whereupon anyone in the neighbourhood would hear you.
Basically, we wanted to get as close to the real thing as was practical. This included the addition of a prominent ‘morse code’ button, of course, as well as a ‘squelch’ control for ‘fine tuning’, which simulated the static of bad reception.
Going dual-platform
Soon after I started developing Talkie, I realised I wanted a version for the Mac too.
Having Talkie both on the iPhone and on the Mac made sense, as we envisioned that a fairly common usage pattern would involve communication between a desktop and a handheld — say, someone wandering a campus with the iPhone in their pocket, staying in touch with friends at their desks.
We wanted to offer good value with Talkie, which is why we made Talkie for Mac available for free, when it’s used with Talkie for iPhone.
One of the very convenient things about developing for both iPhone and Mac is that the platforms are so similar, porting code is usually effortless. So, going dual-platform was an easy decision.
Finding common ground
The result of all of this was the need to develop or find a communication protocol and codec that would work across the iPhone and the Mac, over both Bluetooth for iPhone-iPhone communication, and Wifi, for communication between iPhones and iPhones, and iPhones and Macs.
Version 3.0 of the iPhone SDK introduced the GameKit framework and as part of it, GKVoiceChatService
, which provides two person voice chat straight out of the box. Immediately it was clear that it wouldn’t serve our purposes — two person is not broadcast. I was also keen to provide a Mac version of Talkie, and given that GameKit is iPhone-only, it was time to go off the beaten track.
There are a variety of voice communication protocols out there, with varying licences and varying complexity and feature sets. I could’ve spent a week or two researching the options and evaluating them against our need for broadcast functionality, figuring out compilation and linking on the iPhone, resolving any compatibility or performance issues that arose, etc.
Or, I could spend a day putting together a few big building blocks provided by Apple (and the underlying Unix system) and have an easily tweakable working solution that precisely meets our needs and provides for future features.
It may not have been the most scientific approach, but the core of Talkie’s functionality was up and running on our home wi-fi network within about four hours, and it was working well.
Getting from a to b (and c, d, e, and f)
For those unfamiliar with the ins and outs of TCP/IP (the most common communication protocol — or rather, set of protocols — between computers, and the fundamental building block of the Internet), communication between computers can be connection-oriented, or connectionless.
Connection-oriented (a.k.a. ‘reliable’) communications are most common: They’re used when you open up a website, connect to iChat, or check your email. This is the TCP part of TCP/IP, and includes niceties like built-in guaranteed delivery via retransmission (providing your cat hasn’t eaten your network cable, of course) and packet ordering so you receive the messages in the right order. This only works between two computers, though – you and the server.
Connectionless (or ‘unreliable’) communications are much more open — it’s basically just a spray of messages: The data isn’t carefully ushered along, it’s just spat out and left to fend for itself. This is UDP, and is commonly used for time-sensitive applications like audio communication, network gaming, etc., where once a packet arrives late, it’s useless: No point mucking about re-sending lost data that’s going to be past its use-by date by the time it arrives. The other thing about UDP is that, because there’s no co-operation required from the destination (to acknowledge receipt, etc.), it lends itself to one-to-many communications — broadcast.
Just what we’re after.
So, we use connectionless communications (UDP) to send messages containing the audio, and anyone who receives them unpackages and plays the audio within.
The basic mechanics of this are fairly simple, once you get past the arguably cryptic C syntax.
Here’s how it works.
Sending
On the transmission end, on startup, you create a socket using the socket
call:
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> ... int sock_fd = socket(AF_INET, SOCK_DGRAM, 0); if ( sock_fd < 0 ) { // Error occurred } |
The socket, identified by sock_fd
, will be created using the IPv4 domain (AF_INET
), a ‘datagram’ type (SOCK_DFRAM
: that’s connectionless communication — if we wanted connection-oriented, we could put in SOCK_STREAM
here instead), using protocol 0, which is IP.
Then, we enable broadcasting:
int flag = 1; int result = setsockopt(sock_fd, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(flag)); if ( result < 0 ) { // Error occurred } |
That is, we set the SO_BROADCAST
option at the SOL_SOCKET
level to 1 (via the flag
variable, which we pass in as a pointer), thereby requesting permission from the operating system kernel to broadcast.
Now we have the carrier pigeon coop at our disposal, we can start dispatching pigeons. First, we fill out the address:
#define kPortNumber 1234 ... struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_BROADCAST; addr.sin_port = htons(kPortNumber); |
(Note, htons
converts kPortNumber
into a format that can be understood by any computer, regardless of the way it represents numbers internally.)
Now, send:
NSData *dataToSend; result = sendto(sock_fd, [data bytes], [data length], 0, (struct sockaddr*)&addr, sizeof(addr)); if ( result < 0 ) { // Error occurred } |
Receiving
That takes care of outgoing messages. To receive them, we want another socket:
int sock_fd = socket(AF_INET, SOCK_DGRAM, 0); if ( sock_fd < 0 ) { // Error occurred } |
Now we specify where we want to receive messages from, by ‘binding’ the socket:
struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(kPortNumber); result = bind(sock_fd, (struct sockaddr*)&addr, sizeof(addr)); if ( result < 0 ) { // Error occurred } |
And we pass a buffer to recvfrom
to fill up with tasty morsels of data:
#define kBufferSize 1024 ... char buffer[kBufferSize]; int addr_len = sizeof(addr); int bytes_received = recvfrom(sock_fd, buffer, kBufferSize, 0, (struck sockaddr*)&addr, &addr_len); if ( bytes_received < 0 ) { // Error occurred } |
Voila, we have bytes_received
bytes sitting in buffer
for us to do something with. Note also that addr
now also contains the address of the sender. Now we can loop and continue to receive data as it comes in, passing the data off to some other part of the application to deal with.
We can put it all together into a simple test app: broadcast_sample.c
Compile it by opening up Terminal, and typing make broadcast_sample
(or cc -o broadcast_sample broadcast_sample.c
, if you like), then run it with ./broadcast_sample "Message to send"
, or just ./broadcast_sample
to listen.
$ ./broadcast_sample Listening... Hello world!
$ ./broadcast_sample 'Hello world!' "Hello world!" transmitted.
Coming next
This will work happily between computers with just one network interface. But, if you have more than one (say, wireless, and an Ethernet connection too), you’ll notice that it will only communicate through one of the interfaces. That’s because just the default interface is used. You have to explicitly attend to each interface, to broadcast out of each one, and listen on each one.
In the [next part of this series](http://atastypixel.com/the-making-of-talkie-broadcasting/), I’ll write about how I addressed that, and about multicast, which is used in things like Bonjour (MDNS). I’ll also write about designing packet formats.
Thanks for reading!
Thank you! This is quite helpful. I’m interested in how you would handle larger data (more than your 1024 bytes) since when I try this with larger data (~50k) I get a EMSGSIZE (40) error in the errno global after my call to sendto.
I’d also love to see your follow up post about handling other interfaces.
Please feel free to contact me directly.
Cheers,
Levi
Hi Levi,
Glad it’s helpful! I’m working on the next article now.
I’d suggest breaking your 50k blocks up into smaller chunks – UDP has a maximum size of 65536 bytes, but that’s the specified maximum and some implementations (including, potentially, routers) may have a smaller max size. Plus, the bigger your chunks, the more you have to lose if you drop a packet.
You can include your own packet ordering provisions if you need them to arrive in the same order – i’ll be talking a little about that in the next part.
Thanks Michael.
I have broken my data up into smaller packets and have that working for the most part. I appreciate your article and look forward to the next one.
Cheers,
Levi
Hi. Great article. One question. Have you ever had any issues with iOS devices not receiving broadcast packets? I am trying to talk windowsiOS. The ios can send broadcast which the windows machine receives, but if windows instead sends the broadcast, ios doesn’t receive it. just a local wifi network. I read someone on the net had the same problem and switched to multicast instead of broadcast, but it seems like you have ios ios working via broadcast? Thanks James.
Turns out there was some “bind” template coming from somewhere which was compiling but silently failing (coz I was cocky and didn’t bother checking the bind had succeeded since I didn’t see how it could fail).
using ::bind() fixed my problem.