5

2 つのモノラル ALSA デバイスから読み取り、1 つのステレオ ALSA デバイスに書き込むプログラムを作成しています。

3 つのスレッドとピンポン バッファーを使用してそれらを管理します。2 つの読み取りスレッドと 1 つの書き込みスレッド。それらの構成は次のとおりです。

// Capture ALSA device 
alsaBufferSize = 16384;
alsaCaptureChunkSize = 4096;
bitsPerSample = 16;
samplingFrequency = 24000;
numOfChannels = 1;
block = true;
accessType = SND_PCM_ACCESS_RW_INTERLEAVED;

// Playback device (only list params that are different from above)
alsaBufferSize = 16384 * 2;
numOfChannels = 2;
accessType = SND_PCM_ACCESS_RW_NON_INTERLEAVED;

2 つの読み取りスレッドは、ping バッファーを書き込み、次に pong バッファーを書き込みます。書き込みスレッドは、2 つのバッファーのいずれかの準備が整うのを待ち、ロックし、読み取り、ロック解除します。

しかし、このプログラムを実行すると xrun が表示され、復元できません。

ALSA lib pcm.c:7316:(snd_pcm_recover) underrun occurred
ALSA lib pcm.c:7319:(snd_pcm_recover) cannot recovery from underrun, prepare failed: Broken pipe

以下は、ALSA 再生デバイスに書き込むための私のコードです。

bool CALSAWriter::writen(uint8_t**  a_pOutputBuffer, uint32_t a_rFrames)
{

    bool ret = false;

    // 1. write audio chunk from ALSA
    const snd_pcm_sframes_t alsaCaptureChunkSize = static_cast<snd_pcm_sframes_t>(a_rFrames); //(m_pALSACfg->alsaCaptureChunkSize);

    const snd_pcm_sframes_t writenFrames = snd_pcm_writen(m_pALSAHandle, (void**)a_pOutputBuffer, alsaCaptureChunkSize); 

    if (0 < writenFrames)
    {// write succeeded

        ret = true;

    }
    else
    {// write failed
        logPrint("CALSAWriter WRITE FAILED for  writen farmes = %d ", writenFrames);
        ret = false;
        const int alsaReadError = static_cast<int>(writenFrames);// alsa error is of int type

        if (ALSA_OK == snd_pcm_recover(m_pALSAHandle, alsaReadError, 0))
        {// recovery succeeded
            a_rFrames = 0;// only recovery was done, no write at all was done
        }
        else
        {    
            logPrint("CALSAWriter: failed to recover from ALSA write error: %s (%i)", snd_strerror(alsaReadError), alsaReadError);
            ret = false;
        }
    }

    // 2. check current buffer load
    snd_pcm_sframes_t framesInBuffer = 0;
    snd_pcm_sframes_t delayedFrames = 0;

    snd_pcm_avail_delay(m_pALSAHandle, &framesInBuffer, &delayedFrames);

    // round to nearest int, cast is safe, buffer size is no bigger than uint32_t
    const int32_t ONE_HUNDRED_PERCENTS = 100;
    const uint32_t bufferLoadInPercents = ONE_HUNDRED_PERCENTS *
            static_cast<int32_t>(framesInBuffer) / static_cast<int32_t>(m_pALSACfg->alsaBufferSize);

    logPrint("write: ALSA buffer percentage: %u, delayed frames: %d",  bufferLoadInPercents, delayedFrames);

    return ret;
}

その他の診断情報:

02:53:00.465047  log info V 1 [write: ALSA buffer percentage: 75, delayed frames: 4096]
02:53:00.635758  log info V 1 [write: ALSA buffer percentage: 74, delayed frames: 4160]
02:53:00.805714  log info V 1 [write: ALSA buffer percentage: 74, delayed frames: 4152]
02:53:00.976781  log info V 1 [write: ALSA buffer percentage: 74, delayed frames: 4144]
02:53:01.147948  log info V 1 [write: ALSA buffer percentage: 0, delayed frames: 0]
02:53:01.317113  log error V 1 [CALSAWriter WRITE FAILED for  writen farmes = -32 ]
02:53:01.317795  log error V 1 [CALSAWriter: failed to recover from ALSA write error: Broken pipe (-32)]
4

1 に答える 1

5

解決策を見つけるのに約3日かかりました。@CLありがとうございます。「writen の呼び出しが遅すぎる」のヒント。

問題:

  • スレッドの切り替え時間は一定ではありません。

解決:

  • 初めて「writen」を呼び出す前に、空のバッファを挿入します。このバッファの時間の長さは、マルチスレッドの切り替えを避けるために任意の値にすることができます。私は150msに設定しました。
  • または、私がこれを行うことができない間、スレッドの優先度を高く設定することもできます。ALSA: Ways to prevent underrun for speaker を参照してください。

問題の診断:

事実は次のとおりです。

  • "readi" は 171 ミリ秒ごとに返されます (4096/24000 = 0.171)。スレッド セット バッファを準備完了として読み取ります。
  • バッファの準備ができると、書き込みスレッドで「writen」が呼び出されます。バッファは ALSA 再生デバイスにコピーされます。そして、バッファのこの部分を再生するには、再生デバイスで 171ms かかります。
  • 再生デバイスがすべてのバッファの再生を終了した場合、新しいバッファは書き込まれません。「アンダーラン」が発生。

ここでの実際のシナリオ:

  • 0msで、「readi」が開始されます。171msで「readi」が終了します
  • 172ms (スレッド切り替えの場合は 1ms) で、「writen」が開始されます。新しいバッファが書き込まれない場合、343msで「アンダーラン」が発生します。
  • 171ms、「readi」が再び開始されます。342 ミリ秒で「 readi」が終了します。
  • このとき、スレッドの切り替えに 2ms かかります。344ms で"writen" が始まる前に、343msで "underrun" が発生しました

CPU 負荷が高い場合、「スレッドの切り替え」にかかる時間は保証されません。そのため、最初の書き込みで空のバッファーを挿入できます。シナリオを次のように変換します。

  • 0msで、「readi」が開始されます。171msで「readi」が終了します
  • 172 ミリ秒(スレッド切り替えの場合は1 ミリ秒) で、「writen」は 150 ミリ秒の長さのバッファーで開始します。新しいバッファが書き込まれなければ、493msで「アンダーラン」が発生します。
  • 171ms、「readi」が再び開始されます。342 ミリ秒で「 readi」が終了します。
  • このとき、スレッドの切り替えには50msかかります。writen は392msで開始され、アンダーランはまったく発生しません。
于 2014-11-06T02:33:55.313 に答える