CC3200 Two-Way Audio with Android

Texas Instrument’s CC3200 is a single-chip microcontroller unit with built-in Wi-Fi connectivity, created for the Internet of Things. It can run the FreeRTOS operating system and provides a hardware encryption engine. Sounds interesting? It did to us! So we created a demo that is capable of streaming two-way audio between the CC3200 and an Android App using our uNabto framework. Why do you need Nabto for this? Because it solves all the router and firewall hassle for you: all you need to connect to the device is a unique Device ID!

cc3200-audio-board

What you need

Just hook up the two boards as described in the Audio BoosterPack User Guide provided by TI.

How the Nabto platform works

How does the Nabto platform work exactly? The drawing below gives a brief overview. Your CC3200 represents the Device running the uNabto server. As soon as it connects to the internet it identifies itself at the Nabto Basestation, using its unique ID. If a Client (i.e. the Android App in this demo) wants to connect to the CC3200, a connect request with the ID is sent to the Basestation, and a direct connection to the device is established.

nabto-platform-basics

Get more information on the Nabto/AppMyProduct platform and the Client/Device SDKs at appmyproduct.com.

The uNabto Platform Adapter:

The uNabto Platform Adapter is a small component that abstracts the Native Platforms network and time functionality. The Platform Adapter is part of the uNabto server.

The uNabto server is divided into two layers:

  • The uNabto framework (uNabto SDK)
  • The uNabto Platform Adapter abstraction layer between the framework and the Native Platform

unabto-platform-adapter

To run the uNabto server on the CC3200, we only need to implement the uNabto Platform Adapter (for details see the TEN023 Nabto device SDK guide). The CC3200 adapter is divided into the following files:

  • unabto_config.h: Basic uNabto configuration
  • unabto_platform_types.h: Define all necessary uNabto types
  • unabto_platform.h: Platform specific ad-hoc functions
  • unabto_adapter_network.c: Init, close, read and write functionality for network data
  • unabto_adapter_time.c: Time functions
  • unabto_adapter_dns.c: DNS resolving
  • unabto_adapter_random.c: Random generator
  • unabto_adapter_crypto.c: CC3200 hardware encryption

If you are interested in how the platform adapter is implemented in detail, check the adapter files on GitHub.

The Device Application

Info: The following describes the audio demo application (‘audio’ branch). A more generic and straightforward example of using streaming to echo data is maintained in the ‘master’ branch of our CC3200 GitHub repository.

The uNabto server is running in its own task implemented in unabto_task.c. After waiting for the network connection being established in another task, the uNabto server is initialized with basic settings as well as the unique Device ID and pre-shared encryption key from portal.appmyproduct.com.  Then, the server continuously handles incoming network events and checks for available recorded audio to send to the client.

void UNabto(void* pvParameters) {
    // device id and key from portal.appmyproduct.com
    const char* nabtoId = "<DEVICE ID>";
    const char* presharedKey = "<KEY>";

    // Initialize uNabto
    nabto_main_setup* nms = unabto_init_context();
    nms->ipAddress = g_uiIpAddress;
    nms->id = nabtoId;
    nms->secureAttach = 1;
    nms->secureData = 1;
    nms->cryptoSuite = CRYPT_W_AES_CBC_HMAC_SHA256;

    const char* p;
    unsigned char* up;
    for (p = presharedKey, up = nms->presharedKey; *p; p += 2, ++up) {
        *up = hctoi(p[0]) * 16 + hctoi(p[1]);  // hex string to byte array
    }

    while ((!IS_CONNECTED(g_ulStatus)) || (!IS_IP_ACQUIRED(g_ulStatus))) {
        osi_Sleep(500);
    }

    srand(xTaskGetTickCount());

    stream_audio_init();
    unabto_init();

    while (true) {
        wait_event();
        stream_audio_write();
    }
}

The actual audio streaming code is in stream_audio.c. The unabto_stream_event() function handles all incoming streaming events. Once a Nabto stream connection is established, the device is waiting for a stream command. In this case, there is only the command “audio”. If an unknown command is received, the device returns “-” and closes the stream. If “audio” is received from the client an acknowledgment is returned (“+”) and the actual audio streaming is started. Incoming audio data from the client is processed in lines 125-137:

    if (my_audio_stream.state == STREAM_STATE_STREAMING) {
		const uint8_t* buf;
		unabto_stream_hint hint;
		size_t readLength = unabto_stream_read(stream, &buf, &hint);
		if (readLength > 0) {
			adpcm_decode(pPlayBuffer, buf, readLength);
			if (!unabto_stream_ack(stream, buf, readLength, &hint)) {
				my_audio_stream.state = STREAM_STATE_CLOSING;
			}
		} else {
			if (hint != UNABTO_STREAM_HINT_OK) {
				my_audio_stream.state = STREAM_STATE_CLOSING;
			}
		}
}

The data is read from the stream, decoded, and written to the circular play buffer. Processed data has to be acknowledged with unabto_stream_ack(). For the audio encoding ADPCM is used (for implementation details see adpcm_audio.c). It compresses the two 16-bit stereo samples to one single byte. At 16000 samples per second, this results in a transfer bitrate of 16 kbit/s which is no problem for the CC3200, even for encrypted remote connections. If you want to have a more advanced codec like for example Speex, just replace the encoding and decoding functions.

As mentioned before, the uNabto task continuously checks for available recorded audio by calling the stream_audio_write() function. If new encoded audio is available and a stream is established, the data is sent.

void stream_audio_write() {
	if (my_audio_stream.state != STREAM_STATE_STREAMING) {
		return;
	}
	size_t encodedLen = adpcm_encode(pRecordBuffer, encodedBuf, sizeof(encodedBuf));
	if (encodedLen == 0) {
		return;
	} else {
		UNABTO_ASSERT(encodedLen == sizeof(encodedBuf));
	}

	unabto_stream_hint hint;
	size_t writeLength =
			unabto_stream_write(my_audio_stream.stream, encodedBuf, sizeof(encodedBuf), &hint);

	if (writeLength <= 0 && hint != UNABTO_STREAM_HINT_OK) {
		my_audio_stream.state = STREAM_STATE_CLOSING;
	}

	UpdateReadPtr(pRecordBuffer, writeLength * ENCODING_RATIO);
}

Using the CC3200 Code

You can get the whole CC3200 code including the described platform adapter and the audio streaming device application from the audio branch of our public CC3200 GitHub repository. Simply follow the instructions in the README to set everything up.

The Android Client

The Android audio streaming client is also published on GitHub. It uses our android client SDK available on JCenter (for source code see GitHub repository). It is included in the build.gradle with one single line:

compile ‘com.nabto.android:nabto-api:1.0.1’
}

The main App logic is implemented in MainActiviy.java. Once the unique Device ID is entered in the UI and the OPEN AUDIO STREAM button is pressed, a thread is started to establish the stream connection and send the “audio” command. Then, a recording + sending and a receiving + playing thread are started until the connection breaks or the stream is closed by the user.

android-audio-client

To run the App on your Android device, follow the README instructions in the repository. If you enter the Device ID, you should be able to establish a stream connection and hear the microphone (and line-in) input of the opposite device.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s