esp32S3+ES8388+LEDC+PYTHON PC客户端4

ES8388播放音乐

之前创建项目使用的例程,其实是完整可以播放的项目了。需要额外增加SD卡。

数据流

播放数据流,采用的是三级流水线的方式:
1.fatfs读取SD音频数据放入ringbuf1中
2.codec获取ringbuf1中的音频数据,将数据解码放入ringbuf2中
3.i2s获取ringbuf2中的音频数据,发送到ES8388中
以上三个流水线均作为element,放入到pipeline(管道)中管理,包括启动、暂停、停止、监听事件

代码

下面代码理解,如有错误请见谅。

日志等级
static const char *TAG = "PLAY_SDCARD_MUSIC";
esp_log_level_set("*", ESP_LOG_WARN);//*代表整个项目的日志等级均需在>=WARN才能输出
//代表使用TAG字符串输出的日志等级为>=INFO.看起来有点矛盾,我并没有做测试
esp_log_level_set(TAG, ESP_LOG_INFO);

//INFO日志输出.LOGI表示INFO,LOGW表示warn,LOGE表示error
//TAG自定义字符串"PLAY_SDCARD_MUSIC"
//输出格式为: PLAY_SDCARD_MUSIC:[ 1 ] Mount sdcar
ESP_LOGI(TAG, "[ 1 ] Mount sdcard");
主体代码
		//管道与element的创建
    audio_pipeline_handle_t pipeline;
    audio_element_handle_t fatfs_stream_reader, i2s_stream_writer, music_decoder;
		
		//初始化外设管理
		//DEFAULT_ESP_PERIPH_SET_CONFIG 展开为
		// 任务栈大小、任务优先级、CPU0、无外部栈
		//{ .task_stack = (4*1024), .task_prio = (5), .task_core = (0), .extern_stack = 0, }
		//periph_cfg 可以使用periph_cfg.task_core = 1,将任务移动到CPU1(应用态)
    esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG();
    //初始化外设集与外设链表
    //用于监听外设
    esp_periph_set_handle_t set = esp_periph_set_init(&periph_cfg);

    //初始化SD以及SD卡挂载,挂载为文件系统.根目录一般设为/sdcard
    //可以使用fopen,fclose,fwrite,fread.对文件进行操作.例如fopen("/sdcard/filename.map3",rb)
    //放入到set列表,启动监听
    audio_board_sdcard_init(set, SD_MODE_1_LINE);//单线模式.与我们之前SD卡IO口设置有关
	 
	 //编解码器初始化
	 audio_board_handle_t board_handle = audio_board_init();
	 //启动解码器
   audio_hal_ctrl_codec(board_handle->audio_hal, AUDIO_HAL_CODEC_MODE_DECODE, AUDIO_HAL_CTRL_START);

	//创建管道
	audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
  	pipeline = audio_pipeline_init(&pipeline_cfg);	

	//创建fatfs数据流,从SD卡读取数据.也可以通过fatfs_cfg.task_core = 1放到CPU1
	//优先级4.确保自己创建的task优先级不要超过4.或者提高此优先级
	fatfs_stream_cfg_t fatfs_cfg = FATFS_STREAM_CFG_DEFAULT();
	fatfs_cfg.type = AUDIO_STREAM_READER;
	fatfs_stream_reader = fatfs_stream_init(&fatfs_cfg);	

	//创建I2S数据流.也可以通过i2s_cfg.task_core = 1放到CPU1
	//优先级23,确保音频不卡顿
	i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
  	i2s_cfg.type = AUDIO_STREAM_WRITER;
  	i2s_stream_writer = i2s_stream_init(&i2s_cfg);

	//解码数据流.也可以通过mp3_cfg.task_core = 1放入到CPU1
	//优先级5
	mp3_decoder_cfg_t mp3_cfg = DEFAULT_MP3_DECODER_CONFIG();
  music_decoder = mp3_decoder_init(&mp3_cfg);

	//3个element加入到管道列表
	audio_pipeline_register(pipeline, fatfs_stream_reader, "file");
  audio_pipeline_register(pipeline, music_decoder, "dec");
  audio_pipeline_register(pipeline, i2s_stream_writer, "i2s");

	//名称,与注册时一样
	const char *link_tag[3] = {"file", "dec", "i2s"};
	//执行顺序[sdcard]-->fatfs_stream-->music_decoder-->i2s_stream-->[codec_chip]
	audio_pipeline_link(pipeline, &link_tag[0], 3);
	
	//打开map3文件.以自己的map3名称为主
	audio_element_set_uri(fatfs_stream_reader, "/sdcard/test.mp3");

	//设置事件队列大小,回调函数
	audio_event_iface_cfg_t evt_cfg = AUDIO_EVENT_IFACE_DEFAULT_CFG();
 	audio_event_iface_handle_t evt = audio_event_iface_init(&evt_cfg);

	//设置监听
	audio_pipeline_set_listener(pipeline, evt);
	
	//监听SD外设与其他外设.这里只用到SD卡
	audio_event_iface_set_listener(esp_periph_set_get_event_iface(set), evt);

	//其他3个element的任务.通过事件通讯
	audio_pipeline_run(pipeline);

	//事件获取.建议扔到task任务执行
	while (1) {
        audio_event_iface_msg_t msg;
        //阻塞执行,直到有事件.所以不用使用vTaskDelay延迟函数
        esp_err_t ret = audio_event_iface_listen(evt, &msg, portMAX_DELAY);
        if (ret != ESP_OK) {
            continue;
        }

				//解码器AEL_MSG_CMD_REPORT_MUSIC_INFO事件.
				//设置I2S 采集波特率sample_rates、位宽、通道数
        if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && msg.source == (void *) music_decoder
            && msg.cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO) {
            audio_element_info_t music_info = {0};
            audio_element_getinfo(music_decoder, &music_info);
            audio_element_setinfo(i2s_stream_writer, &music_info);
            i2s_stream_set_clk(i2s_stream_writer, music_info.sample_rates, music_info.bits, music_info.channels);
            continue;
        }
			
			//fatfs数据流结束
			if (msg.source == (void *)fatfs_stream_reader && msg.cmd == AEL_MSG_CMD_REPORT_STATUS && (((int)msg.data == 							AEL_STATUS_STATE_STOPPED) || ((int)msg.data == AEL_STATUS_STATE_FINISHED)))
        {
            continue;
        }
        
        //解码器数据流结束
        if (msg.source == (void *)music_decoder && msg.cmd == AEL_MSG_CMD_REPORT_STATUS && (((int)msg.data == AEL_STATUS_STATE_STOPPED) || ((int)msg.data == AEL_STATUS_STATE_FINISHED)))
        {
            continue;
        }
        //I2S传输结束.结束流程一般为FATFS、decoder、i2s
        if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && msg.source == (void *) i2s_stream_writer
            && msg.cmd == AEL_MSG_CMD_REPORT_STATUS
            && (((int)msg.data == AEL_STATUS_STATE_STOPPED) || ((int)msg.data == AEL_STATUS_STATE_FINISHED))) {
            //如果该监听放在task里面.这里可以通知外部,可以播放下一首歌
            //播放下一首歌前调用
            //audio_pipeline_wait_for_stop(pipeline);
            //audio_pipeline_reset_ringbuffer(pipeline);
            //audio_pipeline_reset_elements(pipeline);
            //audio_element_set_uri(fatfs_stream_reader, "path");
            //audio_pipeline_run(pipeline);
            break;
        }
    }

		//结束播放.直接贴源码
    audio_pipeline_stop(pipeline);
    audio_pipeline_wait_for_stop(pipeline);
    audio_pipeline_terminate(pipeline);

    audio_pipeline_unregister(pipeline, fatfs_stream_reader);
    audio_pipeline_unregister(pipeline, i2s_stream_writer);
    audio_pipeline_unregister(pipeline, music_decoder);

    /* Terminal the pipeline before removing the listener */
    audio_pipeline_remove_listener(pipeline);

    /* Stop all periph before removing the listener */
    esp_periph_set_stop_all(set);
    audio_event_iface_remove_listener(esp_periph_set_get_event_iface(set), evt);

    /* Make sure audio_pipeline_remove_listener & audio_event_iface_remove_listener are called before destroying event_iface */
    audio_event_iface_destroy(evt);

    /* Release all resources */
    audio_pipeline_deinit(pipeline);
    audio_element_deinit(fatfs_stream_reader);
    audio_element_deinit(i2s_stream_writer);
    audio_element_deinit(music_decoder);
    esp_periph_set_destroy(set);
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐