Skip to content

Reuse/Idle Pipeline When it is finished (AUD-6795) #28

@Haerteleric

Description

@Haerteleric

Checklist

  • Checked the issue tracker for similar issues to ensure this is not a duplicate.
  • Provided a clear description of your suggestion.
  • Included any relevant context or examples.

Issue or Suggestion Description

Description:
Hi, I’m using ESP-GMF to develop an interactive game. My goal is to stitch together a story from multiple MP3 files.

The challenge is that each file has a different runtime, so they may need to start/stop while another file is still playing. I also need to reuse pipelines to play different files once the current one is done (e.g., in a conversation).

Here’s a diagram illustrating my approach:

Image

My current GMF architecture:

Image
  • Each track and the mixer have their own GMF-Task assigned.
  • They are connected via a ring buffer at the moment.

Problems encountered:

  1. If I start the GMF-Task and one of the track pipelines is not started or has no input URI, there is no sound output at all.
  2. If one track pipeline finishes and I try to restart it with a different URI, I get a Task Timeout when attempting to restart the pipeline.

Question:

What is the correct way to:

  • Keep pipelines functional even if some don’t have a URI yet?
  • Properly restart a finished pipeline with a new URI without hitting a timeout?

This is my current Soucecode (Work in Progress, Skratch State)

#include "game_audio.h"
#include "utils/pinMap.h"
#include "sdkconfig.h"

#include "esp_gmf_io_codec_dev.h"
#include "esp_gmf_port.h"
#include "esp_gmf_pool.h"
#include "esp_gmf_pool.h"
#include "esp_gmf_err.h"


const char *TAG = "GAME_AUDIO";

#ifndef AUDIO_MANAGER_DEBUG_INFO
    #define AUDIO_MANAGER_DEBUG_INFO(...)
#endif

#ifndef AUDIO_MANAGER_DEBUG_ERROR
    #define AUDIO_MANAGER_DEBUG_ERROR(...)
#endif

#define GAME_AUDIO_EXPECT(func, expect, retVar) \
    do { \
        retVar = func; \
        if (retVar != expect) { \
            AUDIO_MANAGER_DEBUG_ERROR("'%s' failed at %s:%d", #func, __FILE__, __LINE__); \
            goto _exit; \
        } \
    } while (0)

//*-----------------------------GMF Audio IO Init Functions------------------------*/
#include "esp_gmf_io.h"
#include "esp_gmf_io_file.h"
#include "esp_gmf_io_codec_dev.h"
static esp_gmf_err_t initGmfIo_fileReader(esp_gmf_pool_handle_t pool)
{
    ESP_GMF_NULL_CHECK(TAG, pool, return ESP_GMF_ERR_INVALID_ARG);
    esp_gmf_err_t ret = ESP_GMF_ERR_OK;
    file_io_cfg_t fs_cfg = FILE_IO_CFG_DEFAULT();
    esp_gmf_io_handle_t hd = NULL;
    fs_cfg.dir = ESP_GMF_IO_DIR_READER;
    ret = esp_gmf_io_file_init(&fs_cfg, &hd);
    ESP_GMF_RET_ON_ERROR(TAG, ret, return ret, "Failed to init file io");
    ret = esp_gmf_pool_register_io(pool, hd, "fileReader");
    ESP_GMF_RET_ON_ERROR(TAG, ret, {esp_gmf_io_deinit(hd); return ret;}, "Failed to register file io");
    return ret;
}

static esp_gmf_err_t initGmfIo_codecWriter(esp_gmf_pool_handle_t pool)
{
    ESP_GMF_NULL_CHECK(TAG, pool, return ESP_GMF_ERR_INVALID_ARG);

    esp_gmf_err_t ret = ESP_GMF_ERR_OK;
    esp_gmf_io_handle_t dev = NULL;
    codec_dev_io_cfg_t tx_codec_dev_cfg = ESP_GMF_IO_CODEC_DEV_CFG_DEFAULT();
    tx_codec_dev_cfg.dir = ESP_GMF_IO_DIR_WRITER;
    tx_codec_dev_cfg.dev = NULL;
    ret = esp_gmf_io_codec_dev_init(&tx_codec_dev_cfg, &dev);
    ESP_GMF_RET_ON_ERROR(TAG, ret, return ret, "Failed to init codec dev io");
    ret = esp_gmf_pool_register_io(pool, dev, "codecWriter");
    ESP_GMF_RET_ON_ERROR(TAG, ret, {esp_gmf_io_deinit(dev); return ret;}, "Failed to register codec dev io");
    return ret;
}

//*--------------------------GMF Audio Decoder Init Functions----------------------*/
#include "esp_audio_dec_default.h"
#include "esp_audio_enc_default.h"
#include "esp_audio_simple_dec_default.h"
#include "esp_gmf_audio_dec.h"

static esp_gmf_err_t initGmfElement_mp3Decoder(esp_gmf_pool_handle_t pool)
{
    ESP_GMF_NULL_CHECK(TAG, pool, return ESP_GMF_ERR_INVALID_ARG);
    esp_gmf_err_t ret = ESP_GMF_ERR_OK;

    ret = (esp_gmf_err_t) esp_mp3_dec_register();
    ESP_GMF_RET_ON_ERROR(TAG, ret, return ret, "Failed to register mp3 decoder");
    
    esp_audio_simple_dec_cfg_t dec_cfg = DEFAULT_ESP_GMF_AUDIO_DEC_CONFIG();    
    esp_gmf_element_handle_t hd = NULL;
    dec_cfg.dec_type = ESP_AUDIO_SIMPLE_DEC_TYPE_MP3;

    ret = esp_gmf_audio_dec_init(&dec_cfg, &hd);
    ESP_GMF_RET_ON_ERROR(TAG, ret, return ret, "Failed to init audio dec");
    ret = esp_gmf_pool_register_element(pool, hd, "mp3Decoder");
    ESP_GMF_RET_ON_ERROR(TAG, ret, {esp_gmf_element_deinit(hd); return ret;}, "Failed to register element in pool");
    return ret;
}

//*--------------------------GMF Audio Elements Init Functions---------------------*/
#include "esp_gmf_mixer.h"
#include "esp_gmf_rate_cvt.h"
#include "esp_gmf_bit_cvt.h"
#include "esp_gmf_ch_cvt.h"
static esp_gmf_err_t initGmfElement_mixer(esp_gmf_pool_handle_t pool, uint8_t numAudioTracks)
{
    ESP_GMF_NULL_CHECK(TAG, pool, return ESP_GMF_ERR_INVALID_ARG);
    esp_gmf_err_t ret = ESP_GMF_ERR_OK;
    esp_gmf_element_handle_t hd = NULL;

    // Configure Track Info for Mixer
    esp_ae_mixer_info_t *mixerInputConfig = (esp_ae_mixer_info_t *)malloc(sizeof(esp_ae_mixer_info_t) * numAudioTracks);
    if (mixerInputConfig == NULL) {
        return ESP_GMF_ERR_MEMORY_LACK;
    }
    for(int i = 0; i < numAudioTracks; i++) {
        mixerInputConfig[i].weight1 = 1.0f; // Default weight for each track
        mixerInputConfig[i].weight2 = 1.0f; // Default weight for each track
        mixerInputConfig[i].transit_time = 100; // Default transition time of 100 ms
    }

    // Configure Mixer
    esp_ae_mixer_cfg_t mixer_cfg = 
    {
        .channel = 1,
        .sample_rate = 44100,
        .bits_per_sample = 16,
        .src_num = numAudioTracks,
        .src_info = mixerInputConfig
    };

    // Initialize Mixer Element
    ret = esp_gmf_mixer_init(&mixer_cfg, &hd);
    free(mixerInputConfig); // Free the mixer input config after use
    ESP_GMF_RET_ON_ERROR(TAG, ret, return ret, "Failed to init audio mixer");

    // Register Mixer Element
    ret = esp_gmf_pool_register_element(pool, hd, "mixer");
    ESP_GMF_RET_ON_ERROR(TAG, ret, {esp_gmf_element_deinit(hd); return ret;}, "Failed to register element in pool");
    return ret;
}

static esp_gmf_err_t initGmfElement_sampleRateConverter(esp_gmf_pool_handle_t pool)
{
    ESP_GMF_NULL_CHECK(TAG, pool, return ESP_GMF_ERR_INVALID_ARG);
    esp_gmf_err_t ret = ESP_GMF_ERR_OK;
    esp_gmf_element_handle_t hd = NULL;
    esp_ae_rate_cvt_cfg_t es_rate_cvt_cfg = DEFAULT_ESP_GMF_RATE_CVT_CONFIG();

    es_rate_cvt_cfg.dest_rate = 44100;
    es_rate_cvt_cfg.complexity = 2; // CPU vs Quality tradeoff (1=fast/low, 3=slow/high)
    es_rate_cvt_cfg.perf_type = ESP_AE_RATE_CVT_PERF_TYPE_SPEED;

    ret = esp_gmf_rate_cvt_init(&es_rate_cvt_cfg, &hd);
    ESP_GMF_RET_ON_ERROR(TAG, ret, return ret, "Failed to init sampleRateConverter");
    ret = esp_gmf_pool_register_element(pool, hd, "sampleRateConverter");
    ESP_GMF_RET_ON_ERROR(TAG, ret, {esp_gmf_element_deinit(hd); return ret;}, "Failed to register sampleRateConverter element in pool");
    return ret;
}

static esp_gmf_err_t initGmfElement_bitDepthConverter(esp_gmf_pool_handle_t pool)
{
    ESP_GMF_NULL_CHECK(TAG, pool, return ESP_GMF_ERR_INVALID_ARG);
    esp_gmf_err_t ret = ESP_GMF_ERR_OK;
    esp_gmf_element_handle_t hd = NULL;
    esp_ae_bit_cvt_cfg_t es_bit_cvt_cfg = DEFAULT_ESP_GMF_BIT_CVT_CONFIG();

    es_bit_cvt_cfg.dest_bits = 16;

    ret = esp_gmf_bit_cvt_init(&es_bit_cvt_cfg, &hd);
    ESP_GMF_RET_ON_ERROR(TAG, ret, return ret, "Failed to init audio bit depth converter");
    ret = esp_gmf_pool_register_element(pool, hd, "bitDepthConverter");
    ESP_GMF_RET_ON_ERROR(TAG, ret, {esp_gmf_element_deinit(hd); return ret;}, "Failed to register audio bit depth converter element in pool");
    return ret;
}

static esp_gmf_err_t initGmfElement_channelConverter(esp_gmf_pool_handle_t pool)
{
    //There are 2 chCvt needed:
    // 1. Stereo to Mono for the input tracks (always mono output)
    // 2. Mono to Stereo for the final output to the DAC (always stereo output

    ESP_GMF_NULL_CHECK(TAG, pool, return ESP_GMF_ERR_INVALID_ARG);
    esp_gmf_err_t ret = ESP_GMF_ERR_OK;
    esp_gmf_element_handle_t handleIn = NULL;

    // 1. Stereo to Mono for the input tracks (always mono output)
    esp_ae_ch_cvt_cfg_t es_ch_cvt_cfg = DEFAULT_ESP_GMF_CH_CVT_CONFIG();
    es_ch_cvt_cfg.dest_ch = 1; // Always mono

    ret = esp_gmf_ch_cvt_init(&es_ch_cvt_cfg, &handleIn);
    ESP_GMF_RET_ON_ERROR(TAG, ret, return ret, "Failed to init channelConverter_in");
    ret = esp_gmf_pool_register_element(pool, handleIn, "channelConverter_in");
    ESP_GMF_RET_ON_ERROR(TAG, ret, {esp_gmf_element_deinit(handleIn); return ret;}, "Failed to register channelConverter_in element in pool");

    // 2. Mono to Stereo for the final output to the DAC (always stereo output)
    esp_gmf_element_handle_t handleOut = NULL; // Reset handle for new element
    es_ch_cvt_cfg.dest_ch = 2; // Always stereo

    ret = esp_gmf_ch_cvt_init(&es_ch_cvt_cfg, &handleOut);
    ESP_GMF_RET_ON_ERROR(TAG, ret, return ret, "Failed to init channelConverter_out");
    ret = esp_gmf_pool_register_element(pool, handleOut, "channelConverter_out");
ESP_GMF_RET_ON_ERROR(TAG, ret, {esp_gmf_element_deinit(handleOut); return ret;}, "Failed to register channelConverter_out element in pool");

    return ret;
}

//*-------------------------GMF Audio Track Pipeline Functions-----------------------*//
#include "esp_gmf_pipeline.h"
#include "esp_gmf_task.h"
#include "esp_gmf_data_bus.h"
#include "esp_gmf_new_databus.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"

typedef struct game_audio_track_s{
    esp_gmf_task_handle_t task;
    esp_gmf_pipeline_handle_t pipeline;
    esp_gmf_db_handle_t db;
} game_audio_track_t;

esp_gmf_err_t trackEvent_callback(esp_gmf_event_pkt_t *pkt, void *ctx)
{
    return ESP_GMF_ERR_OK;
}

static esp_gmf_err_t initGmfPipeline_track(esp_gmf_pool_handle_t pool, game_audio_track_t *track, int trackIndex)
{
        ESP_GMF_NULL_CHECK(TAG, pool, return ESP_GMF_ERR_INVALID_ARG);
        esp_gmf_err_t ret = ESP_GMF_ERR_OK;

        // Create unique task name for each track
        char trackTaskName[8] = "track_0"; // Base name for track tasks
        trackTaskName[6] = '0' + trackIndex; // Set the track number in the task name

        //Create Ring Buffer for each track
        if(esp_gmf_db_new_ringbuf(4, 1024, &track->db) < 0) {
            return ESP_GMF_ERR_MEMORY_LACK; // GMF Data Bus creation failed
        }

        // Initialize each track's pipeline
        const char *elements[] = {"mp3Decoder", "channelConverter_in", "bitDepthConverter", "sampleRateConverter"};
        const uint8_t num_elements = sizeof(elements) / sizeof(elements[0]);
        if(esp_gmf_pool_new_pipeline(pool, "fileReader", elements, num_elements, NULL, &track->pipeline) != ESP_GMF_ERR_OK) {
            AUDIO_MANAGER_DEBUG_ERROR("GMF Pool new pipeline failed for track %d", trackIndex);
            ret = ESP_GMF_ERR_FAIL;
            goto exit; // GMF Pool new pipeline failed
        }

        esp_gmf_task_cfg_t task_cfg = {
            .thread = {
                .stack = 4096, // Stack size for the track task
                .prio = 6, // Priority of the track task
                .core = 0, // Run on core 0
                .stack_in_ext = 1 // Stack is in external memory (PSRAM)
            },
            .name = trackTaskName, // Name of the track task
            .ctx = NULL, // No user context needed
            .cb = NULL // No callback function needed
        };

        if(esp_gmf_task_init(&task_cfg, &track->task) != ESP_GMF_ERR_OK) {
            AUDIO_MANAGER_DEBUG_ERROR("GMF Task initialization failed for track %d", trackIndex);
            ret = ESP_GMF_ERR_FAIL;
            goto exit; // GMF Task initialization failed
        }

        if(esp_gmf_pipeline_bind_task(track->pipeline, track->task) != ESP_GMF_ERR_OK) {
            AUDIO_MANAGER_DEBUG_ERROR("GMF Pipeline bind task failed for track %d", trackIndex);
            ret = ESP_GMF_ERR_FAIL;
            goto exit; // GMF Pipeline bind task failed
        }

        if(esp_gmf_pipeline_loading_jobs(track->pipeline) != ESP_GMF_ERR_OK) {
            AUDIO_MANAGER_DEBUG_ERROR("GMF Pipeline loading jobs failed for track %d", trackIndex);
            ret = ESP_GMF_ERR_FAIL;
            goto exit; // GMF Pipeline loading jobs failed
        }


exit:
    if(ret != ESP_GMF_ERR_OK) {
        if(track->task) {
            esp_gmf_task_deinit(track->task);
            track->task = NULL;
        }
        if(track->pipeline) {
            esp_gmf_pipeline_stop(track->pipeline);
            track->pipeline = NULL;
        }
        if(track->db) {
            esp_gmf_db_deinit(track->db);
            track->db = NULL;
        }
    }
    return ret;
}

typedef struct game_audio_mixer_s{
    esp_gmf_task_handle_t task;
    esp_gmf_pipeline_handle_t pipeline;
} game_audio_mixer_t;

static esp_gmf_err_t initGmfPipeline_mixer(esp_gmf_pool_handle_t pool, game_audio_mixer_t *mixer, game_audio_track_t *tracks, int numTracks)
{
    ESP_GMF_NULL_CHECK(TAG, pool, return ESP_GMF_ERR_INVALID_ARG);
    esp_gmf_err_t ret = ESP_GMF_ERR_OK;

    // Create Mixer Pipeline
    const char *elements[] = {"mixer", "channelConverter_out"};
    const uint8_t numElements = sizeof(elements) / sizeof(elements[0]);

    if(esp_gmf_pool_new_pipeline(pool, NULL, elements, numElements, "codecWriter", &mixer->pipeline) != ESP_GMF_ERR_OK) {
        AUDIO_MANAGER_DEBUG_ERROR("GMF Pool new pipeline failed on mixer");
        return ESP_GMF_ERR_FAIL; // GMF Pool new pipeline failed
    }

    // Connect each track's output to the mixer input
    for(int i = 0; i < numTracks; i++) {
        if(tracks[i].pipeline == NULL) {
            AUDIO_MANAGER_DEBUG_ERROR("Track %d pipeline is NULL", i);
            ret = ESP_GMF_ERR_FAIL;
            goto exit; // Track pipeline is NULL
        }

        esp_gmf_port_handle_t out_port = 
            NEW_ESP_GMF_PORT_OUT_BYTE(
                esp_gmf_db_acquire_write, 
                esp_gmf_db_release_write, 
                esp_gmf_db_deinit, 
                tracks[i].db,
                4096, 300
            )
        ;
        esp_gmf_port_handle_t in_port = 
            NEW_ESP_GMF_PORT_IN_BYTE(
                esp_gmf_db_acquire_read, 
                esp_gmf_db_release_read, 
                esp_gmf_db_deinit, 
                tracks[i].db,
                4096, 300
            )
        ;

        if(esp_gmf_pipeline_connect_pipe(
            tracks[i].pipeline, "sampleRateConverter", out_port,
            mixer->pipeline, "mixer", in_port) != ESP_GMF_ERR_OK) {
            AUDIO_MANAGER_DEBUG_ERROR("GMF Pipeline connect pipe failed for track %d", i);
            ret = ESP_GMF_ERR_FAIL;
            goto exit; // GMF Pipeline connect pipe failed
        }
    }


    esp_gmf_task_cfg_t task_cfg = {
        .thread = {
            .stack = 4096, // Stack size for the track task
            .prio = 5, // Priority of the track task
            .core = 0, // Run on core 0
            .stack_in_ext = 1 // Stack is in external memory (PSRAM)
        },
        .name = "mixer", // Name of the track task
        .ctx = NULL, // No user context needed
        .cb = NULL // No callback function needed
    };

    if(esp_gmf_task_init(&task_cfg, &mixer->task) != ESP_GMF_ERR_OK) {
        AUDIO_MANAGER_DEBUG_ERROR("GMF Task initialization failed for mixer");
        ret = ESP_GMF_ERR_FAIL;
        goto exit; // GMF Task initialization failed
    }

    if(esp_gmf_pipeline_bind_task(mixer->pipeline, mixer->task) != ESP_GMF_ERR_OK) {
        AUDIO_MANAGER_DEBUG_ERROR("GMF Pipeline bind task failed for mixer");
        ret = ESP_GMF_ERR_FAIL;
        goto exit; // GMF Pipeline bind task failed
    }

    if(esp_gmf_pipeline_loading_jobs(mixer->pipeline) != ESP_GMF_ERR_OK) {
        AUDIO_MANAGER_DEBUG_ERROR("GMF Pipeline loading jobs failed for mixer");
        ret = ESP_GMF_ERR_FAIL;
        goto exit; // GMF Pipeline loading jobs failed
    }
exit:
    if(ret != ESP_GMF_ERR_OK) {
        if(mixer->task) {
            esp_gmf_task_deinit(mixer->task);
            mixer->task = NULL;
        }
        if(mixer->pipeline) {
            esp_gmf_pipeline_stop(mixer->pipeline);
            mixer->pipeline = NULL;
        }
    }
    return ret;
}


//*-------------------------ESP Codec Device Functions-----------------------*//
#include "tlv320dac3100.h"

static esp_codec_dev_handle_t s_codecDev = NULL;
esp_codec_dev_handle_t codecDev_init(void)
{
    if(s_codecDev != NULL) {
        return s_codecDev; // Already initialized
    }
    //Init I2C
    i2c_master_bus_config_t i2c_cfg = {
        .sda_io_num = RFID_I2C_SDA_PIN,
        .scl_io_num = RFID_I2C_SCL_PIN,
        .clk_source = I2C_CLK_SRC_DEFAULT,
        .i2c_port = I2C_NUM_0,
        .flags.enable_internal_pullup = 1,
        .glitch_ignore_cnt = 7,
    };
    i2c_master_bus_handle_t i2cHandle = NULL;
    ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_cfg, &i2cHandle));
    
    tlv320dac3100_codec_cfg_t codec_cfg = {
        .ctrl_if = tlv320dac3100_codec_ctrl_if_new(i2cHandle,i2c_cfg.i2c_port),
        .enableHeadphoneDetection = true,
        .monitoringPollIntervalMs = 300, //ms

        
        .pll = {
            .enable = true,
            .clkIn = 0b01, // BCLK

            //BCLK = Fin = 44.1kHz * 2 * 16 = 1.4112MHz

            .p = 1,
            .r = 8,
            .j = 8,
            .d = 0,

            //PLL Fout = (Fin * R * (J + D/16384)) / P) = (1.4112MHz * 8 * (8 + 0/16384)) / 1) = 90.3168MHz
            //(I)   512 KHz <= Fin/P <= 20 MHz
            //(II)  80MHz <= Fout <= 110MHz 
            //(III) 4 <= R*J <= 259
            //(see condition (7) TLV320DAC3100 datasheet (page 55))
        },

        .clk =
        {
            .clkIn = 0b11, // PLL_CLK
            .nDacDivider = 4, // DAC_CLK = 90.3168MHz / 4 = 22.5792MHz
            .mDacDivider = 4, // DAC_MOD_CLK = 22.5792MHz / 4 = 5.6448MHz
            .dOsr = 128, // Fs = 5.6448MHz / 128 = 44.1kHz

            // (I)      DAC_CLK <=  48 MHz
            // (II)     DAC_MOD_CLK <= 6.758 MHz
            // (III)    Fs == 44.1kHz
            // (IV)     2.8 MHz < (DOSR * DAC_fS) < 6.2 MHz
            // (V)      (MDAC * DOSR / 32) ≥ RC_FILTER (max 12)
        },

        .timer = 
        {
            .useMClk = false   
        },
    };

    esp_codec_dev_cfg_t codec_dev_cfg = {
        .dev_type = ESP_CODEC_DEV_TYPE_OUT,
    };
    codec_dev_cfg.data_if = tlv320dac3100_codec_data_if_new(
        AUDIO_AMP_I2S_MCLK_PIN, 
        AUDIO_AMP_I2S_BCLK_PIN, 
        AUDIO_AMP_I2S_WS_PIN,
        AUDIO_AMP_I2S_DOUT_PIN
    );
    codec_dev_cfg.codec_if = tlv320dac3100_codec_if_new(&codec_cfg);

    s_codecDev = esp_codec_dev_new(&codec_dev_cfg);
    return s_codecDev;
}



static esp_gmf_pool_handle_t s_pool = NULL;
static game_audio_mixer_t s_mixer = {0};
static game_audio_track_t *s_tracks = NULL;
static int s_numAudioTracks = 0;
static bool s_mixerRunning = false;

int game_audio_init(int numAudioTracks)
{
    esp_gmf_err_t ret = ESP_GMF_ERR_OK;

    esp_gmf_pool_init(&s_pool);

    GAME_AUDIO_EXPECT(initGmfElement_bitDepthConverter(s_pool), ESP_GMF_ERR_OK, ret);
    GAME_AUDIO_EXPECT(initGmfElement_channelConverter(s_pool), ESP_GMF_ERR_OK, ret);
    GAME_AUDIO_EXPECT(initGmfElement_sampleRateConverter(s_pool), ESP_GMF_ERR_OK, ret);
    GAME_AUDIO_EXPECT(initGmfElement_mp3Decoder(s_pool), ESP_GMF_ERR_OK, ret);
    GAME_AUDIO_EXPECT(initGmfElement_mixer(s_pool, numAudioTracks), ESP_GMF_ERR_OK, ret);
    GAME_AUDIO_EXPECT(initGmfIo_fileReader(s_pool), ESP_GMF_ERR_OK, ret);
    GAME_AUDIO_EXPECT(initGmfIo_codecWriter(s_pool), ESP_GMF_ERR_OK, ret);

    s_tracks = (game_audio_track_t *)malloc(sizeof(game_audio_track_t) * numAudioTracks);
    if(s_tracks == NULL) {
        ret = ESP_GMF_ERR_MEMORY_LACK;
        goto _exit;
    }
    s_numAudioTracks = numAudioTracks;

    for(int i = 0; i < numAudioTracks; i++) {
        memset(&s_tracks[i], 0, sizeof(game_audio_track_t));
        GAME_AUDIO_EXPECT(initGmfPipeline_track(s_pool, &s_tracks[i], i), ESP_GMF_ERR_OK, ret);
    }

    GAME_AUDIO_EXPECT(initGmfPipeline_mixer(s_pool, &s_mixer, s_tracks, numAudioTracks), ESP_GMF_ERR_OK, ret);


    codecDev_init();
    esp_codec_dev_sample_info_t fs = {
        .bits_per_sample = 16,
        .sample_rate = 44100,
        .channel = 2,
    };
    esp_codec_dev_open(s_codecDev, &fs);
    esp_codec_dev_set_out_vol(s_codecDev, 100);
    GAME_AUDIO_EXPECT(esp_gmf_io_codec_dev_set_dev(ESP_GMF_PIPELINE_GET_OUT_INSTANCE(s_mixer.pipeline), s_codecDev), ESP_GMF_ERR_OK, ret);

    // esp_gmf_pipeline_run(s_mixer.pipeline);
    // for (size_t i = 0; i < numAudioTracks; i++)
    // {
    //     esp_gmf_pipeline_run(s_tracks[i].pipeline);
    // }
    

    return ESP_GMF_ERR_OK;

_exit:
    if(ret != ESP_GMF_ERR_OK) {
        game_audio_deinit();
    }
    return ret;
}

int game_audio_deinit()
{
    if(s_tracks) {
        for(int i = 0; i < s_numAudioTracks; i++) {
            if(s_tracks[i].task) {
                esp_gmf_task_deinit(s_tracks[i].task);
                s_tracks[i].task = NULL;
            }
            if(s_tracks[i].pipeline) {
                esp_gmf_pipeline_stop(s_tracks[i].pipeline);
                s_tracks[i].pipeline = NULL;
            }
            if(s_tracks[i].db) {
                esp_gmf_db_deinit(s_tracks[i].db);
                s_tracks[i].db = NULL;
            }
        }
        free(s_tracks);
        s_tracks = NULL;
    }
    if(s_mixer.task) {
        esp_gmf_task_deinit(s_mixer.task);
        s_mixer.task = NULL;
    }
    if(s_mixer.pipeline) {
        esp_gmf_pipeline_stop(s_mixer.pipeline);
        esp_gmf_pipeline_destroy(s_mixer.pipeline);
        s_mixer.pipeline = NULL;
    }

    esp_gmf_pool_deinit(s_pool);


    return ESP_GMF_ERR_OK;
}

int game_audio_playTrack(int trackIndex, const char *filePath)
{
    ESP_LOGI(TAG, "Playing track %d: %s", trackIndex, filePath);
    esp_gmf_pipeline_reset(s_tracks[trackIndex].pipeline);
    esp_gmf_pipeline_set_in_uri(s_tracks[trackIndex].pipeline, filePath);
    esp_gmf_pipeline_run(s_tracks[trackIndex].pipeline);

    if(!s_mixerRunning) {
        esp_gmf_pipeline_run(s_mixer.pipeline);
        s_mixerRunning = true;
    }

    return ESP_GMF_ERR_OK;
}

used like this:

    game_audio_init(2);
    game_audio_playTrack(1, "/sdcard/B.mp3"); //(B.mp3 is 29.8 secs)
    game_audio_playTrack(0, "/sdcard/A.mp3"); //(A.mp3 is 14.5 secs)
    vTaskDelay(pdMS_TO_TICKS(15000)); // Wait for a 15 second (A.mp3 is 14.5 secs)
    game_audio_playTrack(0, "/sdcard/A.mp3"); //Restart just to test
    vTaskDelay(pdMS_TO_TICKS(15000)); // Let Audio Play

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions