nrf5340 audio - gateway playback - bad sound quality

Hi,

I am trying to get playback on gateway device so it can act as an additional speaker. I configured hw codec and tried to redirect data directly from USB to hw codec, but something is not working as expected.

I got the gateway to play sound received over USB from PC. However sound quality is quite bad. I was wondering if maybe hw codec configuration is invalid, but I think I applied the same configuration as in Headset mode.This makes me think there is something wrong in the way I copy data from USB handler to audio_datapath fifo. I wonder if maybe bitrate is different for USB stream vs what is expected by hw codec ?

Here is modified USB handler:

static uint16_t audio_data[960];
static uint16_t buf_count = 0;

static void data_received(const struct device *dev, struct net_buf *buffer, size_t size)
{
	int ret;
	void *data_in;

	if (fifo_rx == NULL) {
		/* Throwing away data */
		net_buf_unref(buffer);
		return;
	}

	if (!buffer || !size) {
		/* This should never happen */
		ERR_CHK(-EINVAL);
	}

	/* Receive data from USB */
	if (size != USB_FRAME_SIZE_STEREO) {
		LOG_WRN("Wrong length: %d", size);
		net_buf_unref(buffer);
		return;
	}

	// Added code
	LOG_INF("Size %d, net len %d", size, buffer->len);

	static uint32_t recv_frame_ts_us;
	if (buf_count == 0)
	{
		recv_frame_ts_us = audio_sync_timer_curr_time_get();
	}

	memcpy(&audio_data[96 * buf_count], buffer->data, 192);
	buf_count++;
	if (buf_count >= 10)
	{
		LOG_INF("Sending buffer %d", buf_count * 96 * 2);
		audio_datapath_add(audio_data, recv_frame_ts_us);
		buf_count = 0;
	}
	// Added code end

	ret = data_fifo_pointer_first_vacant_get(fifo_rx, &data_in, K_NO_WAIT);

	/* RX FIFO can fill up due to retransmissions or disconnect */
	if (ret == -ENOMEM) {
		void *temp;
		size_t temp_size;

		LOG_WRN("USB RX overrun");

		ret = data_fifo_pointer_last_filled_get(fifo_rx, &temp, &temp_size, K_NO_WAIT);
		ERR_CHK(ret);

		data_fifo_block_free(fifo_rx, &temp);

		ret = data_fifo_pointer_first_vacant_get(fifo_rx, &data_in, K_NO_WAIT);
	}

	ERR_CHK_MSG(ret, "RX failed to get block");

	memcpy(data_in, buffer->data, size);

	ret = data_fifo_block_lock(fifo_rx, &data_in, size);
	ERR_CHK_MSG(ret, "Failed to lock block");

	net_buf_unref(buffer);
}

Here is new audio_datapath_add function that I am calling - it is doing the same as audio_datapath_stream_out but without decoding part that is not needed here:

void audio_datapath_add(uint16_t * p_data, uint32_t recv_frame_ts_us)
{
	/*** Add audio data to FIFO buffer ***/
	int32_t num_blks_in_fifo = ctrl_blk.out.prod_blk_idx - ctrl_blk.out.cons_blk_idx;

	if ((num_blks_in_fifo + NUM_BLKS_IN_FRAME) > FIFO_NUM_BLKS) {
		LOG_WRN("Output audio stream overrun - Discarding audio frame");

		/* Discard frame to allow consumer to catch up */
		return;
	}

	uint32_t out_blk_idx = ctrl_blk.out.prod_blk_idx;

	LOG_INF("EXPECTED %d * %d, offset = %d", NUM_BLKS_IN_FRAME, BLK_STEREO_SIZE_OCTETS, BLK_STEREO_NUM_SAMPS);
	for (uint32_t i = 0; i < NUM_BLKS_IN_FRAME; i++) {
		memcpy(&ctrl_blk.out.fifo[out_blk_idx * BLK_STEREO_NUM_SAMPS],
		       &p_data[i * BLK_STEREO_NUM_SAMPS],
		       BLK_STEREO_SIZE_OCTETS);

		/* Record producer block start reference */
		ctrl_blk.out.prod_blk_ts[out_blk_idx] = recv_frame_ts_us + (i * BLK_PERIOD_US);

		out_blk_idx = NEXT_IDX(out_blk_idx);
	}

	ctrl_blk.out.prod_blk_idx = out_blk_idx;
}


Last is blk_complete function. I believe I only changed conditions to make gateway send data over i2s:

static void audio_datapath_i2s_blk_complete(uint32_t frame_start_ts, uint32_t *rx_buf_released,
					    uint32_t const *tx_buf_released)
{
	int ret;
	static bool underrun_condition;

	alt_buffer_free(tx_buf_released);

	/*** Presentation delay measurement ***/
	ctrl_blk.current_pres_dly_us =
		frame_start_ts - ctrl_blk.out.prod_blk_ts[ctrl_blk.out.cons_blk_idx];

	/********** I2S TX **********/
	static uint8_t *tx_buf;

	if (true){ // IS_ENABLED(CONFIG_STREAM_BIDIRECTIONAL) || (config_audio_dev_var == HEADSET)) {
		if (tx_buf_released != NULL) {
			/* Double buffered index */
			uint32_t next_out_blk_idx = NEXT_IDX(ctrl_blk.out.cons_blk_idx);

			if (next_out_blk_idx != ctrl_blk.out.prod_blk_idx) {
				/* Only increment if not in underrun condition */
				ctrl_blk.out.cons_blk_idx = next_out_blk_idx;
				if (underrun_condition) {
					underrun_condition = false;
					LOG_WRN("Data received, total underruns: %d",
						ctrl_blk.out.total_blk_underruns);
				}

				tx_buf = (uint8_t *)&ctrl_blk.out
						 .fifo[next_out_blk_idx * BLK_MONO_SIZE_OCTETS];

			} else {
				if (stream_state_get() == STATE_STREAMING) {
					underrun_condition = true;
					ctrl_blk.out.total_blk_underruns++;

					if ((ctrl_blk.out.total_blk_underruns %
					     UNDERRUN_LOG_INTERVAL_BLKS) == 0) {
						LOG_WRN("In I2S TX underrun condition, total: %d",
							ctrl_blk.out.total_blk_underruns);
					}
				}

				/*
				 * No data available in out.fifo
				 * use alternative buffers
				 */
				ret = alt_buffer_get((void **)&tx_buf);
				ERR_CHK(ret);

				memset(tx_buf, 0, BLK_STEREO_SIZE_OCTETS);
			}

			if (tone_active) {
				tone_mix(tx_buf);
			}
		}
	}

	/********** I2S RX **********/
	static uint32_t *rx_buf;
	static int prev_ret;

	if (false) {//IS_ENABLED(CONFIG_STREAM_BIDIRECTIONAL) || (config_audio_dev_var == GATEWAY)) {
		/* Lock last filled buffer into message queue */
		if (rx_buf_released != NULL) {
			ret = data_fifo_block_lock(ctrl_blk.in.fifo, (void **)&rx_buf_released,
						   BLOCK_SIZE_BYTES);

			ERR_CHK_MSG(ret, "Unable to lock block RX");
		}

		/* Get new empty buffer to send to I2S HW */
		ret = data_fifo_pointer_first_vacant_get(ctrl_blk.in.fifo, (void **)&rx_buf,
							 K_NO_WAIT);
		if (ret == 0 && prev_ret == -ENOMEM) {
			LOG_WRN("I2S RX continuing stream");
			prev_ret = ret;
		}

		/* If RX FIFO is filled up */
		if (ret == -ENOMEM) {
			void *data;
			size_t size;

			if (ret != prev_ret) {
				LOG_WRN("I2S RX overrun. Single msg");
				prev_ret = ret;
			}

			ret = data_fifo_pointer_last_filled_get(ctrl_blk.in.fifo, &data, &size,
								K_NO_WAIT);
			ERR_CHK(ret);

			data_fifo_block_free(ctrl_blk.in.fifo, &data);

			ret = data_fifo_pointer_first_vacant_get(ctrl_blk.in.fifo, (void **)&rx_buf,
								 K_NO_WAIT);
		}

		ERR_CHK_MSG(ret, "RX failed to get block");
	}

	/*** Data exchange ***/
	audio_i2s_set_next_buf(tx_buf, rx_buf);

	/*** Drift compensation ***/
	audio_datapath_drift_compensation(frame_start_ts);
}

I wonder what am I doing wrong. Also interested in your opinion, if I selected correct "anchors" to redirect data from USB to output. Can it be done simpler/ more elegant way?

Best regards.
Michal

Parents Reply Children
  • Hi Elfving,

    the example is set up in a way where Gateway is connected to the sound source and it send this sound over LE Audio to Headset where you can listen to the sound on headphones. I want to make this possible also on the Gateway sound. As the first step I just want to be able to listen to the same sound as on the Headset, in the next step I will try synchronize this as well, as in the end product these will be speakers, not headphones so sychronization is very important. Hopefully it make sense. I am really stuck right now, so I will apreciate any help.

    Best regards,
    Michal

  • Hello Michael, and sorry about the wait. Things can be a but hectic here during the summer.

    What OS are you using on this computer providing the audio stream? If you are using MAC then you can try if this recently merged fix helps.

    The sample rate is hardcoded in zephyr/subsys/usb/device/class/audio/usb_audio_internal.h, `.tSamFreq = {0x80, 0xBB, 0x00}` is 48kHz. This is simply 24-bit little-endian value.

    Regards,

    Elfving

Related