基于合宙AIR780EG(csdk开发)实现外挂flash中的音频播放功能

本文基于合宙开发csdk,参考example_sfud和dowmload_play_mp3实现片外flash中音频播放功能

相关头文件和变量声明

#include "common_api.h"
#include "luat_rtos.h"
#include "luat_audio_play_ec618.h"
#include "luat_i2s_ec618.h"
#include "luat_gpio.h"
#include "luat_debug.h"
#include "luat_mobile.h"
#include "luat_network_adapter.h"
#include "networkmgr.h"
#include "luat_http.h"
#include "luat_spi.h"
#include "sfud.h"
#include "luat_fs.h"
#include "bsp_custom.h"
#include "luat_mem.h"
#include "lfs.h"
#include "usr_main.h"

//AIR780E+TM8211开发板配置
#define CODEC_PWR_PIN HAL_GPIO_12
#define CODEC_PWR_PIN_ALT_FUN 4
#define PA_PWR_PIN HAL_GPIO_10
#define PA_PWR_PIN_ALT_FUN 0
#define LED2_PIN HAL_GPIO_24
#define LED2_PIN_ALT_FUN 0
#define LED3_PIN HAL_GPIO_23
#define LED3_PIN_ALT_FUN 0
#define LED4_PIN HAL_GPIO_27
#define LED4_PIN_ALT_FUN 0
#define CHARGE_EN_PIN HAL_GPIO_10
#define SPI_FLASH_VCC_PIN (HAL_GPIO_26)

#define FATFS_READ_BUFF_LEN (2*1024)
#define MP3_BUFFER_LEN_MIN  (1 * 1024)  //MP3数据必须大于10KB才开始解码,需要根据实际情况确定
#define MP3_BUFFER_LEN_LOW  (30 * 1024)
#define MP3_BUFFER_LEN_HIGH (40 * 1024)
#define MP3_FRAME_LEN (4 * 1152)
#define MP3_MAX_CODED_FRAME_SIZE 1792
static Buffer_Struct g_s_mp3_buffer = {0};
static Buffer_Struct g_s_pcm_buffer = {0};
static uint8_t g_s_mp3_wait_start;
static uint32_t g_s_mp3_downloading;
static uint8_t g_s_mp3_pause;
static HANDLE g_s_delay_timer;
static uint32_t g_s_mp3_data_tx;    //发送到audio线程的数据
static uint32_t g_s_mp3_data_rx;    //audio接收到的数据,差值就是缓存的数据
static uint8_t g_s_mp3_error;
static uint8_t g_s_mp3_wait_start;
static uint8_t g_s_mp3_pause;
static uint8_t g_s_mp3_data_mode;
static uint16_t g_s_mp3_dummy;
static void *g_s_mp3_decoder;   //不用的时候可以free掉,本demo由于是循环播放,没有free
static uint8_t audio_is_pause;
luat_rtos_task_handle audio_task_handle;
luat_rtos_task_handle ex_audio_read_handle;
luat_rtos_queue_t audio_play_queue;
luat_rtos_semaphore_t single_play_semaphore_handle;

enum
{
    FATFS_GET_MP3_HEAD =0,
    FATFS_GET_DATA,
    FATFS_GET_DATA_DONE,
    FATFS_FILE_UNEXIST,
    FATFS_FILE_EMPTY,
    FATFS_FILE_OPEN_FILE,
    FATFS_FAILED,
};

音频相关硬件初始化和主任务开启

void usr_audio_play_init(void)
{
    luat_gpio_cfg_t gpio_cfg;
    luat_gpio_set_default_cfg(&gpio_cfg);

    gpio_cfg.pin = LED2_PIN;
    gpio_cfg.pull = LUAT_GPIO_DEFAULT;
    luat_gpio_open(&gpio_cfg);
    gpio_cfg.pin = LED3_PIN;
    luat_gpio_open(&gpio_cfg);
    gpio_cfg.pin = LED4_PIN;
    // luat_gpio_open(&gpio_cfg);
    // gpio_cfg.pin = CHARGE_EN_PIN;
    // luat_gpio_open(&gpio_cfg);
    gpio_cfg.pin = PA_PWR_PIN;
    luat_gpio_open(&gpio_cfg);
    gpio_cfg.pin = CODEC_PWR_PIN;
    luat_gpio_open(&gpio_cfg);
    gpio_cfg.alt_fun = CODEC_PWR_PIN_ALT_FUN;
    luat_gpio_open(&gpio_cfg);

    gpio_cfg.pin = SPI_FLASH_VCC_PIN;
    gpio_cfg.alt_fun = 0;
    gpio_cfg.mode = LUAT_GPIO_OUTPUT;
    gpio_cfg.output_level = 1;
    luat_gpio_open(&gpio_cfg);

    luat_rtos_task_create(&audio_task_handle, 1024 * 40, 20, "audio_play_task", audio_play_task, NULL, NULL);
}
static void audio_play_task(void *param)
{
    size_t total, used, max_used;

    luat_rtos_timer_create(&g_s_delay_timer);

    luat_audio_play_global_init_with_task_priority(audio_event_cb, audio_data_cb, NULL, NULL, NULL, 90);

    luat_i2s_base_setup(0, I2S_MODE_MSB, I2S_FRAME_SIZE_16_16);

    luat_rtos_queue_create(&audio_play_queue, 10, sizeof(AUDIO_PLAY_PARAM_T));

    luat_rtos_task_create(&ex_audio_read_handle, 2048*2, 20, "ex_audio_read_task", ex_audio_read_task, NULL, 64);
    
    uint32_t start, i;
    uint8_t is_error;
    uint8_t mp3_head_len = 0;
    uint32_t all,now_free_block,min_free_block;
    luat_event_t event;
    while (1)
    {
        luat_meminfo_sys(&all, &now_free_block, &min_free_block);
        LUAT_DEBUG_PRINT("meminfo %d,%d,%d",all,now_free_block,min_free_block);
        g_s_mp3_data_mode = 0;
        g_s_mp3_error = 0;
        g_s_mp3_data_tx = 0;
        g_s_mp3_data_rx = 0;
        g_s_mp3_wait_start = 0;
        is_error = 0;
        
        luat_rtos_event_recv(audio_task_handle, 0, &event, NULL, LUAT_WAIT_FOREVER);
        LUAT_DEBUG_PRINT("event.id %d",event.id);
        switch(event.id)
        {
        case FATFS_GET_MP3_HEAD:
            LUAT_DEBUG_PRINT("FATFS_MP3_HEAD");
            break;
        case FATFS_GET_DATA:
            LUAT_DEBUG_PRINT("FATFS_GET_DATA");
            break;
        case FATFS_GET_DATA_DONE:
            LUAT_DEBUG_PRINT("FATFS_GET_DATA_DONE");
            break;
        case FATFS_FILE_UNEXIST:
            LUAT_DEBUG_PRINT("FATFS_FILE_UNEXIST");
            break;
        case FATFS_FAILED:
            LUAT_DEBUG_PRINT("FATFS_FAILED");
            break;
        } 
    }
}

音频文件读取和mp3解码

static void ex_audio_read_task(void *param)
{
    AUDIO_PLAY_PARAM_T event =  {0};
    uint8_t tmp[FATFS_READ_BUFF_LEN];
    while(1)
    {
        luat_rtos_semaphore_take(single_play_semaphore_handle, LUAT_WAIT_FOREVER);
        if(!luat_rtos_queue_recv(audio_play_queue, (AUDIO_PLAY_PARAM_T *)&event, NULL, LUAT_WAIT_FOREVER))
        {
            usr_log("type %d fileName %s",event.type, event.fileName);
            if (!luat_fs_fexist(event.fileName))
            {
                LUAT_DEBUG_PRINT("file unexist %s", event.fileName);
                luat_rtos_event_send(audio_task_handle, FATFS_FILE_UNEXIST, 0, 0, 0, 0);
                luat_rtos_semaphore_release(single_play_semaphore_handle);
                continue;
            }
            size_t file_size = luat_fs_fsize(event.fileName);
            if(!file_size)
            {
                LUAT_DEBUG_PRINT("file size is empty %s", event.fileName);
                luat_rtos_event_send(audio_task_handle, FATFS_FILE_UNEXIST, 0, 0, 0, 0);
                luat_rtos_semaphore_release(single_play_semaphore_handle);
                continue;
            }
            FILE *fp = luat_fs_fopen(event.fileName, "r");
            if (!fp) {
                LUAT_DEBUG_PRINT("file open failed %s", event.fileName);
                luat_rtos_event_send(audio_task_handle, FATFS_FILE_OPEN_FILE, 0, 0, 0, 0);
                luat_rtos_semaphore_release(single_play_semaphore_handle);
                continue;
            }
            size_t ret = 0;
            uint8_t get_mp3_head = 0;
            do
            {
                if(!get_mp3_head)
                {
                    ret = luat_fs_fread(tmp, 12, 1, fp);
                    if (ret <= 0) {
                        // LUAT_DEBUG_PRINT("file read done");
                        luat_rtos_event_send(audio_task_handle, FATFS_GET_DATA_DONE, 0, 0, 0, 0);
                    }else{
                        // usr_log("111 %x %x %x %x %x %x %x %x %x %x %x %x\r\n", tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8], tmp[9], tmp[10], tmp[11]);
                        if (!memcmp(tmp, "ID3", 3) || (tmp[0] == 0xff))
                        {
                            uint32_t start = 0;
                            if (tmp[0] != 0xff)
                            {
                                //跳过无用的数据
                                for(int i = 0; i < 4; i++)
                                {
                                    start <<= 7;
                                    start |= tmp[6 + i] & 0x7f;
                                }
                            }
                            uint8_t re = luat_fs_fseek(fp, start, SEEK_SET);
                            // LUAT_DEBUG_PRINT("re %d",re);
                            // LUAT_DEBUG_PRINT("是MP3文件");
                            g_s_mp3_wait_start = 1;
                            luat_rtos_event_send(audio_task_handle, FATFS_GET_MP3_HEAD, 0, 0, 0, 0);
                        }
                        else
                        {
                            LUAT_DEBUG_PRINT("不是MP3文件,退出");
                            luat_rtos_semaphore_release(lte_semaphore_handle);
                            break;
                        }
                        get_mp3_head = 1;
                    } 
                }else{
                    while(audio_is_pause)
                    {
                        usr_delay_ms(10);
                    }
                    luat_audio_play_set_user_lock(0, 1);
                    ret = luat_fs_fread(tmp, FATFS_READ_BUFF_LEN, 1, fp);
                    if (ret <= 0) {
                        // LUAT_DEBUG_PRINT("file read done");
                        luat_rtos_event_send(audio_task_handle, FATFS_GET_DATA_DONE, 0, 0, 0, 0);
                        g_s_mp3_downloading = 0;
                        luat_audio_play_set_user_lock(0, 0);
                        
                    }else{
                        // usr_log("222 %x %x %x %x %x %x %x %x %x %x %x %x\r\n", tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], tmp[6], tmp[7], tmp[8], tmp[9], tmp[10], tmp[11]);
                        luat_rtos_event_send(audio_task_handle, FATFS_GET_DATA, tmp, FATFS_READ_BUFF_LEN, 0, 0);
                        g_s_mp3_downloading = 1;
                        g_s_mp3_data_tx += 1024;
                        soc_call_function_in_audio(run_mp3_decode, tmp, FATFS_READ_BUFF_LEN, LUAT_WAIT_FOREVER);
                        if ((g_s_mp3_data_tx - g_s_mp3_data_rx + g_s_mp3_buffer.Pos) >= MP3_BUFFER_LEN_HIGH)
                        {
                            if (!audio_is_pause)
                            {
                                audio_is_pause = 1;
                            }
                        }
                        
                    }
                }
            } while (ret>0);
            luat_fs_fclose(fp);  
        }
    }
}

1.接收语音播放指令
2.读取文件前12个字节内容,判断是否是mp3文件
3.根据前12个字节内容的第7-10个字节,获取文件头信息大小,根据该信息将文件指针偏移到音频数据位置
4.分段读取音频数据,直至读完
5.当文件还没有读取完毕时,使用luat_audio_play_set_user_lock(0, 1)保证播放不会因数据没有及时写入而导致中断;当文件读取完毕后,使用luat_audio_play_set_user_lock(0, 0),当没有播放数据时,正确退出
6.缓冲区数据量大于MP3_BUFFER_LEN_HIGH时,暂停文件读取

mp3数据解码和写入相关api

void app_pa_on(uint32_t arg)
{
    luat_gpio_set(PA_PWR_PIN, 1);
}

用于硬件静音开启/关闭

void run_mp3_add_blank(uint8_t *data, uint32_t len)
{
    int16_t *dummy_data = malloc(8192);
    for(int i = 0; i < 4096; i++)
    {
        dummy_data[i] = g_s_mp3_dummy;
    }
    luat_audio_play_write_raw(0, dummy_data, 8192);
    luat_audio_play_write_raw(0, dummy_data, 8192);
    free(dummy_data);
}

写入空白块

void run_mp3_decode(uint8_t *data, uint32_t len)
{
    LUAT_DEBUG_PRINT("len %d", len);
    if (data)
    {
        OS_BufferWrite(&g_s_mp3_buffer, data, len);
        g_s_mp3_data_rx += len;
        if (g_s_mp3_wait_start)
        {
            LUAT_DEBUG_PRINT("g_s_mp3_buffer.Pos %d", g_s_mp3_buffer.Pos);
            if (g_s_mp3_buffer.Pos >= MP3_BUFFER_LEN_MIN)
            {
                g_s_mp3_wait_start = 0;
                run_mp3_play(1);
            }
            return;
        }
        // if (g_s_mp3_pause)
        // {
        //  if ((g_s_mp3_buffer.Pos >= MP3_BUFFER_LEN_MIN) || !g_s_mp3_downloading)
        //  {
        //      g_s_mp3_pause = 0;
        //      run_mp3_play(0);
        //  }
        //  return;
        // }
    }
    run_mp3_play(0);
    if (g_s_mp3_buffer.Pos < MP3_BUFFER_LEN_LOW)
    {
        if (audio_is_pause)
        {
            audio_is_pause = 0;
        }
    }
    Audio_StreamStruct *stream = (Audio_StreamStruct *)luat_audio_play_get_stream(0);
    if (g_s_mp3_downloading && (llist_num(&stream->DataHead) <= 1))
    {
        LUAT_DEBUG_PRINT("no data %d, %d", g_s_mp3_downloading, llist_num(&stream->DataHead));
        run_mp3_add_blank(NULL, 0);
        return;
    }
}

1.第一次数据量大于MP3_BUFFER_LEN_MIN时进行解码,仅作用一次
2.进行数据写入
3.当缓冲区数据量小于MP3_BUFFER_LEN_LOW时,开启文件读取

void audio_event_cb(uint32_t event, void *param)
{
    switch(event)
    {
    case LUAT_MULTIMEDIA_CB_AUDIO_NEED_DATA:
        soc_call_function_in_audio(run_mp3_decode, NULL, 0, LUAT_WAIT_FOREVER);
        break;
    case LUAT_MULTIMEDIA_CB_AUDIO_DONE:
        // if (g_s_mp3_downloading)
        // {
        //  LUAT_DEBUG_PRINT("pause");
        //  g_s_mp3_pause = 1;
        //  soc_call_function_in_audio(run_mp3_add_blank, NULL, 0, LUAT_WAIT_FOREVER);
        //  return;
        // }
        OS_DeInitBuffer(&g_s_mp3_buffer);
        luat_audio_play_stop_raw(0);
        luat_rtos_timer_stop(g_s_delay_timer);
        luat_gpio_set(PA_PWR_PIN, 0);
        luat_gpio_set(CODEC_PWR_PIN, 0);
        luat_rtos_semaphore_release(single_play_semaphore_handle);
        break;
    }
}
void audio_data_cb(uint8_t *data, uint32_t len, uint8_t bits, uint8_t channels)
{
}
int run_mp3_play(uint8_t is_start)
{
    Audio_StreamStruct *stream = (Audio_StreamStruct *)luat_audio_play_get_stream(0);
    uint8_t num_channels = 1;
    uint32_t sample_rate = 0;
    int result, len;
    if (!g_s_mp3_decoder)
    {
        g_s_mp3_decoder = mp3_decoder_create();
    }
    if (is_start)
    {
        mp3_decoder_init(g_s_mp3_decoder);
        // LUAT_DEBUG_PRINT("%x %x %x %x %x %x",g_s_mp3_buffer.Data[0],g_s_mp3_buffer.Data[1],g_s_mp3_buffer.Data[2],g_s_mp3_buffer.Data[3],g_s_mp3_buffer.Pos);
        result = mp3_decoder_get_info(g_s_mp3_decoder, g_s_mp3_buffer.Data, g_s_mp3_buffer.Pos, &sample_rate, &num_channels);
        if (result)
        {
            mp3_decoder_init(g_s_mp3_decoder);
            LUAT_DEBUG_PRINT("mp3 %d,%d", sample_rate, num_channels);
            len = (num_channels * sample_rate >> 2) + MP3_FRAME_LEN * 2;
            OS_ReInitBuffer(&g_s_pcm_buffer, len);
        }
        else
        {
            LUAT_DEBUG_PRINT("mp3 decode fail!");
            g_s_mp3_error = 1;
            return -1;
        }
    }
    uint32_t pos = 0;
    uint32_t out_len, hz, used;
    if (!g_s_mp3_buffer.Pos)
    {
        return -1;
    }
    while ((llist_num(&stream->DataHead) < 3) && (g_s_mp3_buffer.Pos > (MP3_MAX_CODED_FRAME_SIZE * g_s_mp3_downloading + 1)) )
    {
        while (( g_s_pcm_buffer.Pos < (g_s_pcm_buffer.MaxLen - MP3_FRAME_LEN * 2) ) && (g_s_mp3_buffer.Pos > (MP3_MAX_CODED_FRAME_SIZE * g_s_mp3_downloading + 1)))
        {
            pos = 0;
            do
            {
                result = mp3_decoder_get_data(g_s_mp3_decoder, g_s_mp3_buffer.Data + pos, g_s_mp3_buffer.Pos - pos,
                        (int16_t *)&g_s_pcm_buffer.Data[g_s_pcm_buffer.Pos], &out_len, &hz, &used);
                luat_wdt_feed();
                if (result > 0)
                {
                    g_s_pcm_buffer.Pos += out_len;
                    //记录下最后一个采样数据,用于pause时加入空白音
                    memcpy(&g_s_mp3_dummy, g_s_pcm_buffer.Data[g_s_pcm_buffer.Pos - 2], 2);
                }
                pos += used;

                if (!result || (g_s_pcm_buffer.Pos >= (g_s_pcm_buffer.MaxLen - MP3_FRAME_LEN * 2)))
                {
                    break;
                }
            } while ( ((g_s_mp3_buffer.Pos - pos) >= (MP3_MAX_CODED_FRAME_SIZE * g_s_mp3_downloading + 1)));
            OS_BufferRemove(&g_s_mp3_buffer, pos);
        }
        if (is_start)
        {
            luat_audio_play_start_raw(0, AUSTREAM_FORMAT_PCM, num_channels, sample_rate, 16, 1);
            //打开外部DAC,由于要配合PA的启动,需要播放一段空白音
            luat_gpio_set(CODEC_PWR_PIN, 1);
            luat_audio_play_write_blank_raw(0, 6, 1);
            is_start = 0;
            luat_rtos_timer_start(g_s_delay_timer, 200, 0, app_pa_on, NULL);
        }
        luat_audio_play_write_raw(0, g_s_pcm_buffer.Data, g_s_pcm_buffer.Pos);
        g_s_pcm_buffer.Pos = 0;
    }
}

业务层函数,具体播放哪个文件

void usr_audio_play_mp3_file(char *fileName, AUDIO_TYPE_E type)
{
    AUDIO_PLAY_PARAM_T param = {
        .fileName = fileName,
        .type = type,
    };
    usr_audio_play(param);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值