PROCESSOR-SDK-AM62X: McASP capture failure on second usage

Part Number: PROCESSOR-SDK-AM62X

Tool/software:

I have experienced the issue described in this post

https://e2e.ti.com/support/processors-group/processors/f/processors-forum/1217223/processor-sdk-am62x-audio-capture-dma-issue-repeated-capture-commands-fail

In that post it is mentioned that TI plans to fix it in a subsequent SDK release. We are currently on version 08.06.00.42 of the SDK. Before we go to the effort of switching to a newer SDK we would like to know if this was fixed. I read through release notes but I did not see explicit mention of it.

  • I am answering my own question. The issue can be fixed even on the 5.10.X kernel used in the 08.06.00.42 SDK. This patch resolved the issue for me

    https://github.com/varigit/ti-linux-kernel/commit/85a4b70c0bbddff31c7a075cf985008f30955f1f

    Here's a simple c program that uses mmap to record 32 bit audio (change to 16 bit if you are using an EVK). It can be run multiple times

    // mmap-record.c - Records audio using ALSA mmap and writes to S32_LE WAV file
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    #include <string.h>
    #include <alsa/asoundlib.h>
    
    #define SAMPLE_RATE 48000
    #define CHANNELS 2
    #define FORMAT SND_PCM_FORMAT_S32_LE
    #define BUFFER_TIME 500000   // us
    #define PERIOD_TIME 100000   // us
    #define DURATION 5           // seconds
    #define FILENAME "record_mmap.wav"
    #define PCM_DEVICE "hw:0,0"
    
    #define WAV_HEADER_SIZE 44
    
    #define kAudioFrameSamples 256  // This is the audio driver callback rate.  Should not be too huge as it consumes coherent kernel memory.
    #define kNumAlsaBuffers 4
    snd_pcm_uframes_t periodSize = kAudioFrameSamples;
    snd_pcm_uframes_t bufferSize = kAudioFrameSamples * kNumAlsaBuffers; // adjusted in initPcmHandle
    
    
    void write_wav_header(FILE *f, int data_bytes) {
        int32_t sample_rate = SAMPLE_RATE;
        int16_t bits_per_sample = 32;
        int16_t channels = CHANNELS;
        int32_t byte_rate = sample_rate * channels * bits_per_sample / 8;
        int16_t block_align = channels * bits_per_sample / 8;
        int32_t chunk_size = 36 + data_bytes;
    
        uint8_t header[WAV_HEADER_SIZE] = {
            'R','I','F','F',
            chunk_size & 0xFF, (chunk_size >> 8) & 0xFF, (chunk_size >> 16) & 0xFF, (chunk_size >> 24) & 0xFF,
            'W','A','V','E','f','m','t',' ',
            16,0,0,0, // Subchunk1Size
            1,0, // PCM
            channels,0,
            sample_rate & 0xFF, (sample_rate >> 8) & 0xFF, (sample_rate >> 16) & 0xFF, (sample_rate >> 24) & 0xFF,
            byte_rate & 0xFF, (byte_rate >> 8) & 0xFF, (byte_rate >> 16) & 0xFF, (byte_rate >> 24) & 0xFF,
            block_align,0,
            bits_per_sample,0,
            'd','a','t','a',
            data_bytes & 0xFF, (data_bytes >> 8) & 0xFF, (data_bytes >> 16) & 0xFF, (data_bytes >> 24) & 0xFF
        };
        fwrite(header, 1, WAV_HEADER_SIZE, f);
    }
    
    const char *snd_pcm_state_name(snd_pcm_state_t state)
    {
      switch (state)
      {
        case SND_PCM_STATE_OPEN:
          return "SND_PCM_STATE_OPEN";
        case SND_PCM_STATE_SETUP:
          return "SND_PCM_STATE_SETUP";
        case SND_PCM_STATE_PREPARED:
          return "SND_PCM_STATE_PREPARED";
        case SND_PCM_STATE_RUNNING:
          return "SND_PCM_STATE_RUNNING";
        case SND_PCM_STATE_XRUN:
          return "SND_PCM_STATE_XRUN";
        case SND_PCM_STATE_DRAINING:
          return "SND_PCM_STATE_DRAINING";
        case SND_PCM_STATE_PAUSED:
          return "SND_PCM_STATE_PAUSED";
        case SND_PCM_STATE_SUSPENDED:
          return "SND_PCM_STATE_SUSPENDED";
        case SND_PCM_STATE_DISCONNECTED:
          return "SND_PCM_STATE_DISCONNECTED";
        default:
          return "Unknown state";
      }
    }
    
    const char *snd_pcm_stream_name_zz(snd_pcm_t *pcmHandle)
    {
      snd_pcm_stream_t stream = snd_pcm_stream(pcmHandle);
      switch (stream)
      {
        case SND_PCM_STREAM_PLAYBACK:
          return "SND_PCM_STREAM_PLAYBACK";
        case SND_PCM_STREAM_CAPTURE:
          return "SND_PCM_STREAM_CAPTURE";
        default:
          return "Unknown stream";
      }
    }
    
    
    int handlePcmOverOrUnderRun(snd_pcm_t *pcmHandle, int dbgCauseSource)
    {
      // handle overrun or underrun.
      snd_pcm_drop(pcmHandle);
      snd_pcm_recover(pcmHandle, -EPIPE, 0);
      snd_pcm_prepare(pcmHandle);
      usleep(3000);
      snd_pcm_state_t state = snd_pcm_state(pcmHandle);
      printf("Stream %s: state = %s after recovery, source = %d\n",
          snd_pcm_stream_name_zz(pcmHandle), snd_pcm_state_name(state), dbgCauseSource);
    
      state = snd_pcm_state(pcmHandle);
      printf("Stream %s: state = %s after start, source = %d\n",
          snd_pcm_stream_name_zz(pcmHandle), snd_pcm_state_name(state), dbgCauseSource);
    
      usleep(3000);
      return 0;
    }
    
    void hw_init(snd_pcm_t *handle)
    {
      snd_pcm_hw_params_t *hwparams;
        int err;
    
        snd_pcm_hw_params_malloc(&hwparams);
        snd_pcm_hw_params_any(handle, hwparams);
        snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED);
        snd_pcm_hw_params_set_format(handle, hwparams, FORMAT);
        snd_pcm_hw_params_set_rate(handle, hwparams, SAMPLE_RATE, 0);
        snd_pcm_hw_params_set_channels(handle, hwparams, CHANNELS);
        err        = snd_pcm_hw_params_set_period_size_near(handle, hwparams, &periodSize, 0);
        if (err < 0) printf("Error %d with snd_pcm_hw_params_set_period_size_near\n", err);
        bufferSize = periodSize * kNumAlsaBuffers;  // e.g., 4 audio frames in the entire alsa buffer
        err        = snd_pcm_hw_params_set_buffer_size_near(handle, hwparams, &bufferSize);
        if (err < 0) printf("Error %d with snd_pcm_hw_params_set_buffer_size_near\n", err);
        printf("Using period (audio frame) size: %lu and buffer size %lu\n", periodSize, bufferSize);
        snd_pcm_hw_params(handle, hwparams);    
        snd_pcm_hw_params_free(hwparams);
    }
    
    void sw_init(snd_pcm_t *handle,const int  audio_frame_size)
    {
        snd_pcm_sw_params_t *swparams;
        snd_pcm_sw_params_malloc(&swparams);
        snd_pcm_sw_params_current(handle, swparams);
        snd_pcm_sw_params_set_avail_min(handle, swparams, audio_frame_size);
        snd_pcm_sw_params_set_start_threshold(handle, swparams, 0);
        snd_pcm_sw_params_set_stop_threshold(handle, swparams, 65536);
        snd_pcm_sw_params(handle, swparams);
        snd_pcm_sw_params_free(swparams);
    
    }
    
    
    void do_capture_samples(snd_pcm_t *handle,const int  audio_frame_size, FILE *fout)
    {
      int dir, err;
      int total_frames = SAMPLE_RATE * DURATION;
      int frame_size = CHANNELS * sizeof(int32_t);
      int recorded_frames = 0;
      int attempts = 0;
      const snd_pcm_channel_area_t *areas;
      snd_pcm_uframes_t offset, frames;
    
      while (recorded_frames < total_frames) 
      {
        frames = audio_frame_size;
        err = snd_pcm_mmap_begin(handle, &areas, &offset, &frames);
        if (err < 0) {
            fprintf(stderr, "mmap_begin error: %s\n", snd_strerror(err));
            break;
        }
    
        snd_pcm_state_t state = snd_pcm_state(handle);
        if (frames == 0)
        {
          err = snd_pcm_mmap_commit(handle, offset, frames);
          // printf ("commit 0 frames err = %d, state =  %s)\n", err,
          //           snd_pcm_state_name(state));
          if (attempts > 500)
          {
            printf ("Not getting audio frames\n");
            attempts = 0;
          }
          if (attempts > 0)
            ++attempts;
          if (state == SND_PCM_STATE_PREPARED)
          {
            printf("Starting pcm capture stream after %d attempts\n", attempts);
            snd_pcm_start(handle);
          }
          else if (state == SND_PCM_STATE_XRUN) 
          {
            printf("XRUN detected, attempting recovery\n");
            handlePcmOverOrUnderRun(handle, 1);
            continue;
          }
          usleep(4000);
          continue;
        }
    
        if ((recorded_frames & 0x3FF) == 0)
          printf ("%d frames\n", recorded_frames);
        if (frames > audio_frame_size)
          frames = audio_frame_size;
    
        if (frames < audio_frame_size) 
        {
          printf ("Short: %d\n", frames);
        }
        // else
        {
    
          int32_t *samples = (int32_t *)((uint8_t *)areas[0].addr + (offset * (areas[0].step / 8)));
          size_t bytes = frames * frame_size;
          fwrite(samples, 1, bytes, fout);
          recorded_frames += frames;
    
          err = snd_pcm_mmap_commit(handle, offset, frames);
          if (err < 0) {
              fprintf(stderr, "mmap_commit error: %s\n", snd_strerror(err));
              break;
          }
          usleep(5000);
        }
        attempts = -1;
      }
    }
    
    int main()
    {
        snd_pcm_t *handle;
        const snd_pcm_channel_area_t *areas;
        snd_pcm_uframes_t offset, frames;
        int dir, err;
        const int audio_frame_size = 256;
    
        int frame_size = CHANNELS * sizeof(int32_t);
        int total_frames = SAMPLE_RATE * DURATION;
        int recorded_frames = 0;
    
        err = snd_pcm_open(&handle, PCM_DEVICE, SND_PCM_STREAM_CAPTURE, 0);
            if (err < 0) {
            fprintf(stderr, "Error opening PCM device: %s\n", snd_strerror(err));
            return 1;
        }
    
        hw_init(handle);
        sw_init(handle, audio_frame_size);
    
        FILE *fout = fopen(FILENAME, "wb");
        if (!fout) {
            perror("fopen");
            return 1;
        }
        write_wav_header(fout, total_frames * frame_size);
    
        snd_pcm_prepare(handle);
        do_capture_samples(handle, audio_frame_size, fout);
    
        snd_pcm_pause(handle, 1);
        fclose(fout);
    
        snd_pcm_pause(handle, 1);
        snd_pcm_close(handle);
        printf("Wrote %s\n", FILENAME);
        return 0;
    }
    

    Compile by sourcing the SDK's environment-setup script and then running

     ${CC} mcasp-alsa-record-mmap-main.c -lasound -lm -o mcasp-alsa-mmap-record

  • Hi,

    Yes the force teardown on RX channel was added to fix the consecutive records to work properly. 

    But the latest software version (10.0 onwards ) has improved version and cleaner way to have audio functionatility. 

    Please use SDK version 10.0 or beyond to verify.

    Best Regards,

    Suren

  • Thank you Suren. I will check the latest SDK to see if this resolves the problem. However our team wants to avoid switching the kernel and SDK version as a solution right now because it is too big of a change to make for where we are in the product release cycle. So if there are other related patches to the ti kernel you can recommend we look at, that would be appreciated.

  • Hi Chris,

    https://git.ti.com/cgit/ti-linux-kernel/ti-linux-kernel/commit/?h=ti-linux-6.6.y&id=e0a0ce8c2684c13fab0e65be767d036dfa592ee1

    https://git.ti.com/cgit/ti-linux-kernel/ti-linux-kernel/commit/?h=ti-linux-6.6.y&id=343c3e2d9d0b49905aac24f9795cb2de5ef0cdaf

    https://git.ti.com/cgit/ti-linux-kernel/ti-linux-kernel/commit/?h=ti-linux-6.6.y&id=f08ebde41b3053f02de99e994a2b49dfcb494085

    These above three commits are of importance for audio functionality to work properly on AM62x.

    See if you can validate with the latest ( these are already integrated in latest SDK) or have them back ported if migrating to latest SDK is a challenge.

    Hope these help.

    Best Regards,

    Suren

  • Hi Suren

    I have applied these patches and they are working stably on our hardware, and this resolves a warning message I had previously been seeing when only using the "force teardown" patch. 

    Thanks!

    Chris

  • I am glad those patches helped. Please don't hesitate to reach out for any further support. Closing this thread.

    Best Regards,

    Suren