Create audio pipeline with output to file and Bluetooth

jeeper1974
Posts: 11
Joined: Thu Aug 13, 2020 5:47 am

Create audio pipeline with output to file and Bluetooth

Postby jeeper1974 » Wed Sep 09, 2020 6:02 am

I am trying to setup a audio pipeline that will take input from the microphone(i2s) on a LyraT board and send it to some Bluetooth headphones along with converting it to a WAV and saving it to a file on the SD card.
I can make a i2s-WAV-sdcard pipeline work successfully.
And I can make a i2s-Bluetooth headphones pipeline work successfully.
But when I create a i2s-Bluetooth-WAV-sdcard pipeline only the Bluetooth works some of the times and the file never saves.
Any suggestions on why the pipeline does not allow two writers (Bluetooth and sdcard) to be linked to one audio source?

Code: Select all

audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
pipeline = audio_pipeline_init(&pipeline_cfg);
mem_assert(pipeline);

i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
i2s_cfg.type = AUDIO_STREAM_READER;
i2s_stream_reader = i2s_stream_init(&i2s_cfg);

wav_encoder_cfg_t wav_cfg = DEFAULT_WAV_ENCODER_CONFIG();
wav_encoder = wav_encoder_init(&wav_cfg);

fatfs_stream_cfg_t fatfs_cfg = FATFS_STREAM_CFG_DEFAULT();
fatfs_cfg.type = AUDIO_STREAM_WRITER;
fatfs_stream_writer = fatfs_stream_init(&fatfs_cfg);

a2dp_stream_config_t a2dp_config = {
		   .type = AUDIO_STREAM_WRITER,
		   .user_callback = {0},
	   };
bt_stream_writer = a2dp_stream_init(&a2dp_config);

audio_pipeline_register(pipeline, i2s_stream_reader, "i2s");
audio_pipeline_register(pipeline, wav_encoder, "wav");
audio_pipeline_register(pipeline, fatfs_stream_writer, "file");
audio_pipeline_register(pipeline, bt_stream_writer, "bt");

const char *link_tag[4] = {"i2s", "bt", "wav", "file"  };
audio_pipeline_link(pipeline, &link_tag[0], 4);

audio_element_set_uri(fatfs_stream_writer, "/sdcard/rec1.wav");

audio_pipeline_run(pipeline);

...


jeeper1974
Posts: 11
Joined: Thu Aug 13, 2020 5:47 am

Re: Create audio pipeline with output to file and Bluetooth

Postby jeeper1974 » Thu Sep 10, 2020 2:35 pm

For others that may have a similar issue and want to output audio to multiple elements, I found a solution that works. You cannot use the ESP-ADF pipeline. You need to go to the layer below the pipeline function and connect the elements with ringbuffers. This allows one of the elements to use "audio_element_set_multi_output_ringbuf(audio_element_handle_t el, ringbuf_handle_t rb, int index)" which will use the input ringbuffer for multiple other elements.

Code: Select all

    audio_element_handle_t fatfs_stream_writer, i2s_stream_reader, wav_encoder, bt_stream_writer;
    ringbuf_handle_t ringbuf01, ringbuf02, ringbuf11;

    esp_log_level_set("*", ESP_LOG_WARN);
    esp_log_level_set(TAG, ESP_LOG_INFO);

    ESP_LOGI(TAG, "[ 1 ] Mount sdcard");
    // Initialize peripherals management
    esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG();
    esp_periph_set_handle_t set = esp_periph_set_init(&periph_cfg);

    // Initialize SD Card peripheral
    audio_board_sdcard_init(set);

    ESP_LOGI(TAG, "[ 2 ] Start codec chip");

    audio_board_handle_t board_handle = audio_board_init();
    codec_begin(); //This will start up the codec
    int player_volume;
    audio_hal_get_volume(board_handle->audio_hal, &player_volume);

    ESP_LOGI(TAG, "[3.0] Create audio pipeline for recording");

    ESP_LOGI(TAG, "[3.2] Create i2s stream to read audio data from codec chip");
    i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
    i2s_cfg.type = AUDIO_STREAM_READER;
    i2s_cfg.multi_out_num = 1;
    i2s_cfg.task_core = 1;
	#if defined CONFIG_ESP_LYRAT_MINI_V1_1_BOARD
		i2s_cfg.i2s_port = (i2s_port_t)1;
	#endif
    i2s_stream_reader = i2s_stream_init(&i2s_cfg);

    ESP_LOGI(TAG, "[3.3] Create wav encoder to encode wav format");
    wav_encoder_cfg_t wav_cfg = DEFAULT_WAV_ENCODER_CONFIG();
    wav_encoder = wav_encoder_init(&wav_cfg);

    ESP_LOGI(TAG, "[3.1] Create fatfs stream to write data to sdcard");
	fatfs_stream_cfg_t fatfs_cfg = FATFS_STREAM_CFG_DEFAULT();
	fatfs_cfg.type = AUDIO_STREAM_WRITER;
	fatfs_stream_writer = fatfs_stream_init(&fatfs_cfg);

	audio_element_info_t info = AUDIO_ELEMENT_INFO_DEFAULT();
	audio_element_getinfo(i2s_stream_reader, &info);
	audio_element_setinfo(fatfs_stream_writer, &info);

	ESP_LOGI(TAG, "[3.3] Create a ringbuffer and insert it between i2s_stream_reader and wav_encoder");
	ringbuf01 = rb_create(RING_BUFFER_SIZE, 1);
	audio_element_set_output_ringbuf(i2s_stream_reader, ringbuf01);
	audio_element_set_input_ringbuf(wav_encoder, ringbuf01);

	ESP_LOGI(TAG, "[3.4] Create a ringbuffer and insert it between wav_encoder and wav_fatfs_stream_writer");
	ringbuf02 = rb_create(RING_BUFFER_SIZE, 1);
	audio_element_set_output_ringbuf(wav_encoder, ringbuf02);
	audio_element_set_input_ringbuf(fatfs_stream_writer, ringbuf02);

	ESP_LOGI(TAG, "[3.4.1] Set up  uri (file as fatfs_stream, wav as wav encoder)");
	audio_element_set_uri(fatfs_stream_writer, "/sdcard/rec1.wav");

	ESP_LOGI(TAG, "[3.5] Create Bluetooth with i2s element input ");
	a2dp_stream_config_t a2dp_config = {
		   .type = AUDIO_STREAM_WRITER,
		   .user_callback = {0},
	   };
	bt_stream_writer = a2dp_stream_init(&a2dp_config);
	// bt_stream_writer = bluetooth_service_create_stream();

	ringbuf11 = rb_create(RING_BUFFER_SIZE, 1);
	audio_element_set_multi_output_ringbuf(i2s_stream_reader,ringbuf11,0);
	audio_element_set_input_ringbuf(bt_stream_writer,ringbuf11);

	ESP_LOGI(TAG, "[5.0] Set callback function for audio_elements");
	/**
	* Event handler used here is quite generic and simple one.
	* It just reports state changes of different audio_elements
	* Note that, it is not mandatory to set event callbacks.
	* One may remove entire step [5.0]. This example could still run.
	*/
	audio_element_set_event_callback(wav_encoder, audio_element_event_handler, NULL);
	audio_element_set_event_callback(i2s_stream_reader, audio_element_event_handler, NULL);
	audio_element_set_event_callback(fatfs_stream_writer, audio_element_event_handler, NULL);
	audio_element_set_event_callback(bt_stream_writer, audio_element_event_handler, NULL);


	ESP_LOGI(TAG, "[ 6.0 ] Set up  event listener");
	audio_event_iface_cfg_t evt_cfg = AUDIO_EVENT_IFACE_DEFAULT_CFG();
	audio_event_iface_handle_t evt = audio_event_iface_init(&evt_cfg);
	esp_periph_handle_t bt_periph;

	ESP_LOGI(TAG, "[6.1] Create bt peripheral");
	bt_periph = bt_create_periph();

	ESP_LOGI(TAG, "[6.2] Start bt peripheral");
	esp_periph_start(set, bt_periph);

	ESP_LOGI(TAG, "[7.0] Listening event from peripherals");
	audio_event_iface_set_listener(esp_periph_set_get_event_iface(set), evt);

	ESP_LOGI(TAG, "[8.0] Start audio elements");
	audio_element_run(i2s_stream_reader);
	audio_element_run(wav_encoder);
	audio_element_run(fatfs_stream_writer);
	audio_element_run(bt_stream_writer);

	//RUN starts in a paused state. RESUME starts the element going
	audio_element_resume(i2s_stream_reader, 0, 0);
	audio_element_resume(wav_encoder, 0, 0);
	audio_element_resume(fatfs_stream_writer, 0, 0);
	audio_element_resume(bt_stream_writer, 0, 0);

    ESP_LOGI(TAG, "[ 6 ] Listen for all pipeline events, record for %d Seconds", RECORD_TIME_SECONDS);
    int second_recorded = 0;
    while (1) {
        audio_event_iface_msg_t msg;
        int button_debounce = 3; //Seconds before the button can be pressed again to stop the recording
        int button_time = 0;
        if (audio_event_iface_listen(evt, &msg, 1000 / portTICK_RATE_MS) != ESP_OK) {
            second_recorded ++;
            ESP_LOGI(TAG, "[ * ] Recording ... %d", second_recorded);
            /*Button pressed again to stop recording*/

//            if ((get_button(8) || get_button(9)) && button_time >= button_debounce){
//            	ESP_LOGI(TAG, "Button Pressed to Stop");
//            	break;
//		   }
            //button_time++;
            if (second_recorded >= RECORD_TIME_SECONDS) {
                break;
            }
            continue;
        }

//        if ((msg.source_type == PERIPH_ID_TOUCH || msg.source_type == PERIPH_ID_BUTTON || msg.source_type == PERIPH_ID_ADC_BTN)
//                    && (msg.cmd == PERIPH_TOUCH_TAP || msg.cmd == PERIPH_BUTTON_PRESSED || msg.cmd == PERIPH_ADC_BUTTON_PRESSED)) {

		
			if (msg.source_type == PERIPH_ID_BLUETOOTH && msg.source == (void *)bt_periph) {
				if ((msg.cmd == PERIPH_BLUETOOTH_DISCONNECTED) || (msg.cmd == PERIPH_BLUETOOTH_AUDIO_SUSPENDED)) {
					ESP_LOGW(TAG, "[ * ] Bluetooth disconnected or suspended");
				   // periph_bt_stop(bt_periph);
					break;
				}
			}
		
        /* Stop when the last pipeline element (i2s_stream_reader in this case) receives stop event */
        if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && msg.source == (void *) i2s_stream_reader
            && msg.cmd == AEL_MSG_CMD_REPORT_STATUS
            && (((int)msg.data == AEL_STATUS_STATE_STOPPED) || ((int)msg.data == AEL_STATUS_STATE_FINISHED))) {
            ESP_LOGW(TAG, "[ * ] Stop event received");
            break;
        }
    }
    ESP_LOGI(TAG, "[ 7 ] Stop audio_pipeline");

    /* Stop all periph before removing the listener */
    esp_periph_set_stop_all(set);

    /* Release all resources */
       audio_element_deinit(i2s_stream_reader);
       audio_element_deinit(wav_encoder);
       audio_element_deinit(fatfs_stream_writer);
       audio_element_deinit(bt_stream_writer);
       esp_periph_set_destroy(set);
       rb_destroy(ringbuf01);
       rb_destroy(ringbuf02);
       rb_destroy(ringbuf11);

}
    
    


bosleymusic
Posts: 4
Joined: Mon Aug 24, 2020 9:45 am

Re: Create audio pipeline with output to file and Bluetooth

Postby bosleymusic » Thu Sep 10, 2020 6:13 pm

You replied earlier about this problem I was having so thank you for the sharing the post. I was also going to tell you there is an example for a raw split - they take an http stream and play it back while writing to a fatfs - you might want to try that example project out to help debug your issues here.

Fireman
Posts: 2
Joined: Sat Jan 23, 2021 6:52 am

Re: Create audio pipeline with output to file and Bluetooth

Postby Fireman » Sat Jan 23, 2021 7:21 am

Hi,

I'm looking for an example that sends audio to bluetooth headphone.
Can you give me the code how to make a i2s-Bluetooth headphones pipeline work successfully.
I've searched the internet but did not find an example to do so.
thanks


ehabulhaq
Posts: 7
Joined: Thu Apr 01, 2021 10:48 pm

Re: Create audio pipeline with output to file and Bluetooth

Postby ehabulhaq » Wed Apr 14, 2021 4:16 am

@jeeper1974

Thank you very much for your earlier post of using underlying audio element. it helped me solve my problem in my project.

I developed a wireless audio streaming service for ESP-MDF. I wanted to play music on the root speaker and also send the stream to other nodes in the mesh network. I was only able to achieve it by using multi output buffer method. my reader is a tcp stream reader that gets data from a tcp server.

then I arranged my stream like this TCP_STREAM_READER->DECODER->I2S

AND

TCP_STREAM_READER -> MESH_WRITER

this gives me mesh audio stream control as well. otherwise mesh would burst data so fast and nodes couldn't throttle down ( unless I created an audio service over mesh). But this works for now with no issues.
  1. audio_element_err_t audio_element_output(audio_element_handle_t el, char *buffer, int write_size)
  2. {
  3.     int output_len = 0;
  4.     if (el->write_type == IO_TYPE_CB) {
  5.         if (el->out.write_cb.cb && write_size) {
  6.             output_len = el->out.write_cb.cb(el, buffer, write_size, el->output_wait_time,
  7.                                              el->out.write_cb.ctx);
  8.         }
  9.     } else if (el->write_type == IO_TYPE_RB) {
  10.         //if (el->out.output_rb && write_size) {
  11.  
  12.             if ( el->multi_out.max_rb_num > 0)
  13.             {
  14.                 output_len = audio_element_multi_output( el, buffer, write_size, el->output_wait_time);
  15.             }
  16.             else
  17.             {
  18.                 output_len = rb_write(el->out.output_rb, buffer, write_size, el->output_wait_time);
  19.             }
  20.            
  21.            
  22.             if ((rb_bytes_filled(el->out.output_rb) > el->out_buf_size_expect) || (output_len < 0)) {
  23.                 xEventGroupSetBits(el->state_event, BUFFER_REACH_LEVEL_BIT);
  24.             }
  25.         //}
  26.     }

Who is online

Users browsing this forum: No registered users and 29 guests