Skip to content

Commit

Permalink
apps: add Auracast USB sample with LC3 codec
Browse files Browse the repository at this point in the history
This adds sample that acts as USB sound device. Audio signal is coded
using LC3 and broadacasted using Auracast package.
  • Loading branch information
KKopyscinski committed Dec 5, 2023
1 parent bbba284 commit 0a75ddf
Show file tree
Hide file tree
Showing 9 changed files with 1,848 additions and 0 deletions.
445 changes: 445 additions & 0 deletions apps/auracast_usb/include/tusb_config.h

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions apps/auracast_usb/include/usb_audio.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

#ifndef H_USB_AUDIO_
#define H_USB_AUDIO_

#include <stdint.h>

typedef void (* usb_audio_sample_rate_cb_t)(uint32_t);

/* Set default sample rate, should only be used before USB is initialized */
void usb_desc_sample_rate_set(uint32_t sample_rate);

/* Set callback to receive sample rate set by USB host */
void usb_audio_sample_rate_cb_set(usb_audio_sample_rate_cb_t cb);

/* Get current sample rate */
uint32_t usb_audio_sample_rate_get(void);

#endif /* H_USB_AUDIO_ */
45 changes: 45 additions & 0 deletions apps/auracast_usb/pkg.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#

pkg.name: apps/auracast_usb
pkg.type: app
pkg.description: Auracast sample application.

pkg.author: "Krzysztof Kopyściński"
pkg.email: "krzysztof.kopyscinski@codecoup.pl"
pkg.homepage: "http://mynewt.apache.org/"
pkg.keywords:

pkg.deps:
- "@apache-mynewt-core/sys/config"
- nimble/host
- nimble/host/util
- nimble/host/services/gap
- nimble/host/store/config
- "@apache-mynewt-core/kernel/os"
- "@apache-mynewt-core/sys/console"
- "@apache-mynewt-core/sys/log"
- "@apache-mynewt-core/sys/stats"
- "@apache-mynewt-core/sys/sysinit"
- "@apache-mynewt-core/sys/id"
- "@apache-mynewt-core/hw/usb/tinyusb"
- "@apache-mynewt-nimble/nimble/host/services/auracast"
- "@apache-mynewt-nimble/ext/liblc3"

pkg.init:
audio_usb_init: 402
44 changes: 44 additions & 0 deletions apps/auracast_usb/src/app_priv.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

#ifndef H_APP_PRIV_
#define H_APP_PRIV_

#include <syscfg/syscfg.h>

#ifndef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif

#define AUDIO_CHANNELS MYNEWT_VAL(AURACAST_CHAN_NUM)
#define AUDIO_SAMPLE_SIZE sizeof(int16_t)

#define LC3_FRAME_DURATION (MYNEWT_VAL(LC3_FRAME_DURATION))
#define LC3_SAMPLING_FREQ (MYNEWT_VAL(LC3_SAMPLING_FREQ))
#define LC3_BITRATE (MYNEWT_VAL(LC3_BITRATE))
#define LC3_FPDT (LC3_SAMPLING_FREQ * LC3_FRAME_DURATION / 1000000)
#define BIG_NUM_BIS (MIN(AUDIO_CHANNELS, MYNEWT_VAL(BIG_NUM_BIS)))

struct chan {
void *encoder;
uint16_t handle;
};

extern struct chan chans[AUDIO_CHANNELS];
#endif /* H_APP_PRIV_ */
236 changes: 236 additions & 0 deletions apps/auracast_usb/src/audio_usb.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

#include <assert.h>
#include <string.h>
#include <stdint.h>
#include <syscfg/syscfg.h>
#include <hal/hal_gpio.h>
#include <bsp/bsp.h>

#include <os/os.h>
#include <common/tusb_fifo.h>
#include <class/audio/audio_device.h>
#include <usb_audio.h>

#include <lc3.h>
#include <nrf_clock.h>

#include <nimble/hci_common.h>
#include <nimble/transport.h>
#include "host/ble_gap.h"

#include "app_priv.h"

#if 1
#define TP_PIN_ENCODE LED_1
#define TP_PIN_DATA_CB LED_2
#define TP_PIN_DATA_IN LED_3
#define TP_PIN_DATA_ISR 0
#else
#define TP_PIN_ENCODE 0
#define TP_PIN_DATA_CB 0
#define TP_PIN_DATA_IN 0
#define TP_PIN_DATA_ISR 0
#endif

#define TP_INIT(_pin) (TP_PIN_ ## _pin ? \
hal_gpio_init_out(TP_PIN_ ## _pin, 0) : (void)0)
#define TP_1(_pin) (TP_PIN_ ## _pin ? \
hal_gpio_write(TP_PIN_ ## _pin, 1) : (void)0)
#define TP_0(_pin) (TP_PIN_ ## _pin ? \
hal_gpio_write(TP_PIN_ ## _pin, 0) : (void)0)

static uint8_t g_usb_enabled;

static void usb_data_func(struct os_event *ev);

struct chan chans[AUDIO_CHANNELS];

static struct os_event usb_data_ev = {
.ev_cb = usb_data_func,
};

static uint32_t frame_bytes_lc3;
static uint16_t big_sdu;

static int16_t samples[96000 / 100 * 2 * 2];
static uint8_t samples_out[96000 / 100 * 2];
static int samples_idx = 0;

static uint32_t pkt_counter = 0;

static void
usb_data_func(struct os_event *ev)
{
int ch_idx;
unsigned int num_bytes;
unsigned int num_samples;
unsigned int num_frames;
unsigned int frames_count;
int read;
int skip;

if (!g_usb_enabled) {
tud_audio_clear_ep_out_ff();
return;
}

TP_1(DATA_CB);

while ((num_bytes = tud_audio_available()) > 0) {
num_samples = num_bytes / AUDIO_SAMPLE_SIZE;
num_frames = num_samples / MYNEWT_VAL(AURACAST_CHAN_NUM);
num_bytes = num_frames * AUDIO_SAMPLE_SIZE * MYNEWT_VAL(AURACAST_CHAN_NUM);

assert(samples_idx + num_samples < ARRAY_SIZE(samples));

TP_1(DATA_IN);
read = tud_audio_read(&samples[samples_idx], num_bytes);
TP_0(DATA_IN);

assert(read == num_bytes);
assert(samples[ARRAY_SIZE(samples) - 1] = 0xaaaa);

samples_idx += num_samples;
assert((samples_idx & 0x80000000) == 0);
frames_count = samples_idx / MYNEWT_VAL(AURACAST_CHAN_NUM);

if (frames_count >= LC3_FPDT) {
pkt_counter++;
skip = 0;

for (ch_idx = 0; ch_idx < MYNEWT_VAL(AURACAST_CHAN_NUM); ch_idx++) {
if (chans[ch_idx].handle == 0) {
skip = 1;
continue;
}
}

if (!skip) {
memset(samples_out, 0, sizeof(samples_out));
TP_1(ENCODE);
lc3_encode(chans[0].encoder, LC3_PCM_FORMAT_S16,
samples + 0, AUDIO_CHANNELS,
(int)frame_bytes_lc3, samples_out);
TP_0(ENCODE);

if (AUDIO_CHANNELS == 2) {
ble_iso_tx(chans[0].handle, samples_out, big_sdu);

TP_1(ENCODE);
memset(samples_out, 0, sizeof(samples_out));
lc3_encode(chans[1].encoder, LC3_PCM_FORMAT_S16,
samples + 1, AUDIO_CHANNELS,
(int)frame_bytes_lc3, samples_out);
TP_0(ENCODE);
ble_iso_tx(chans[1].handle, samples_out, big_sdu);
} else {
ble_iso_tx(chans[0].handle, samples_out, big_sdu);
if (BIG_NUM_BIS == 1) {
memset(samples_out, 0, sizeof(samples_out));
lc3_encode(chans[1].encoder, LC3_PCM_FORMAT_S16,
samples + 1, AUDIO_CHANNELS,
(int)frame_bytes_lc3, samples_out);
}
ble_iso_tx(chans[0].handle, samples_out, big_sdu);
}

}

if (frames_count > LC3_FPDT) {
int old_samples_idx = samples_idx;
samples_idx -= LC3_FPDT * AUDIO_CHANNELS;
memmove(samples, &samples[old_samples_idx],
(old_samples_idx - samples_idx) * AUDIO_SAMPLE_SIZE);
} else {
samples_idx = 0;
}
}
}

TP_0(DATA_CB);
}

bool
tud_audio_rx_done_post_read_cb(uint8_t rhport, uint16_t n_bytes_received,
uint8_t func_id, uint8_t ep_out,
uint8_t cur_alt_setting)
{
(void)rhport;
(void)n_bytes_received;
(void)func_id;
(void)ep_out;
(void)cur_alt_setting;

TP_1(DATA_ISR);

if (!usb_data_ev.ev_queued) {
os_eventq_put(os_eventq_dflt_get(), &usb_data_ev);
}

TP_0(DATA_ISR);

return true;
}

void
audio_usb_init(void)
{
/* Need to reference those explicitly, so they are always pulled by linker
* instead of weak symbols in tinyusb.
*/
(void)tud_audio_rx_done_post_read_cb;

TP_INIT(ENCODE);
TP_INIT(DATA_CB);
TP_INIT(DATA_IN);
TP_INIT(DATA_ISR);

usb_desc_sample_rate_set(LC3_SAMPLING_FREQ);

assert(LC3_FPDT == lc3_frame_samples(LC3_FRAME_DURATION,
LC3_SAMPLING_FREQ));

memset(samples, 0xaa, sizeof(samples));

unsigned esize = lc3_encoder_size(LC3_FRAME_DURATION,
LC3_SAMPLING_FREQ);
for (int i = 0; i < AUDIO_CHANNELS; i++) {
chans[i].encoder = calloc(1, esize);
lc3_setup_encoder(LC3_FRAME_DURATION, LC3_SAMPLING_FREQ,
0, chans[i].encoder);
}

#ifdef NRF53_SERIES
nrf_clock_hfclk_div_set(NRF_CLOCK, NRF_CLOCK_HFCLK_DIV_1);
#endif

g_usb_enabled = 1;

frame_bytes_lc3 = lc3_frame_bytes(LC3_FRAME_DURATION, LC3_BITRATE);
big_sdu = frame_bytes_lc3 *
(1 + ((AUDIO_CHANNELS == 2) && (BIG_NUM_BIS == 1)));
}

void
audio_usb_enable(uint8_t enabled)
{
g_usb_enabled = enabled;
}
Loading

0 comments on commit 0a75ddf

Please sign in to comment.