2024年4月5日 星期五

linux 使用 ALSA lib 播放 wav 檔

簡單的播放器
// File: play.cpp
#include "alsa/asoundlib.h"
struct ChunkHEAD {
    char id[4];
    int32_t length;
    char *stringID() {
        static char str[5];
        memcpy(str, id, 4);
        return str;
    }
    bool id_is(const char *name) {
        if (*name == 0) return false;
        for (int i = 0; i < 4; i ++) {
            if (name[i] == 0) break; // end of String
            if (name[i] != id[i]) return false;
        }
        return true;
    }
};
struct PCMtype {
    int16_t  pcmType, channels;
    int32_t  sampleRate, byteRate;
    int16_t  frameBytes, bitsData;
};
struct WAVstruct {
    ChunkHEAD title;
    char wave[4];
    ChunkHEAD fmt;
    PCMtype pcm;
    bool is_wav() {
        if (! title.id_is("RIFF")) return false;
        static const char waveID[5] = "WAVE";
        for (int i = 0; i < 4; i ++) { if (wave[i] != waveID[i]) return false; }
        return fmt.id_is("fmt");
    }
};
struct PCM_2CH {// interleave L/R
  int16_t l;
  int16_t r;
};
 
int main(void) {     
    FILE *fin= fopen("sample.wav", "rb");
    if (fin == nullptr) return -1;
    unsigned int sampleRate = 8000;
    unsigned int channels = 2;
      int frameBytes = sizeof(PCM_2CH);
    int frameSamples = 0;
    int bufSize = 1024;
    WAVstruct header;
    const int n = fread(&header, 1, sizeof(WAVstruct), fin);
    if (n > 0 && header.is_wav()) {
        sampleRate = header.pcm.sampleRate;
        channels   = header.pcm.channels;
        frameBytes = header.pcm.frameBytes;
        ChunkHEAD chunkList;
        do {
            if (fread(&chunkList, 1, sizeof(ChunkHEAD), fin) <= 0) break;
            if (chunkList.id_is("data")) break;
            fseek(fin, chunkList.length, SEEK_CUR);// ignore unknown id
        } while (! feof(fin));
        frameSamples = chunkList.length / frameBytes;
    }
    snd_pcm_t *handle;
      char speaker[bufSize * frameBytes];
    if (snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0) == 0) {
         if (snd_pcm_set_params(handle, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, channels, sampleRate, 1, sampleRate / 4) == 0) {
            while(! feof(fin)) {                   
                const int uframes = fread(speaker, frameBytes, bufSize, fin);
                if (uframes <= 0) break;
                const int err = snd_pcm_writei(handle, speaker, uframes);
                if (err < 0) snd_pcm_recover(handle, err, 0);// try to recover
            }
        }
        snd_pcm_close(handle);
    }
    fclose(fin);
    return 0;
}
編譯並執行:

     g++  play.cpp  -lasound  &&  ./a.out

後記: 改用 mmap 方式播放 wav 檔, 修改主程式
int main(int argc, char **argv) {     
    FILE *fin= fopen(argc > 1 ? argv[1] : "sample.wav", "rb");
    if (fin == nullptr) return -1;
    unsigned int sampleRate = 44100;
    unsigned int channels = 2;  
    WAVstruct header;
    const int n = fread(&header, 1, sizeof(WAVstruct), fin);
    if (n > 0 && header.is_wav()) {
        sampleRate = header.pcm.sampleRate;
        channels   = header.pcm.channels;  
        ChunkHEAD chunkList;
        do {
            if (fread(&chunkList, 1, sizeof(ChunkHEAD), fin) <= 0) break;
            if (chunkList.is_id("data")) break;
            chunkList.length);
            fseek(fin, chunkList.length, SEEK_CUR);// ignore unknown id
        } while (! feof(fin));
    }
    snd_pcm_t *handle;
    if (snd_pcm_open(&handle, "hw:1,0", SND_PCM_STREAM_PLAYBACK, 0) == 0) { // hw:1,0 , default
        if (snd_pcm_set_params(handle, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_MMAP_INTERLEAVED, channels, sampleRate, 1, sampleRate / 4) == 0) {
            const snd_pcm_channel_area_t* areas;
            snd_pcm_uframes_t offset;
            snd_pcm_uframes_t uframes = 1024;// size to request, it will be updated by DMA
            snd_pcm_mmap_begin(handle, &areas, &offset, &uframes);// DMA 需要的 addr, offset, uframes
            const int first = areas->first / 8;
            const int uBytes = areas->step / 8;
            int8_t *data = (int8_t *)areas->addr + first;
            printf("begin offset = %ld, uframes =%ld, first = %d, uBytes = %d bytes\n", offset,  uframes, first , uBytes);
            memset(data + offset * uBytes, 0, uBytes * uframes);// fill zero data
            snd_pcm_mmap_commit(handle , offset , uframes);
            snd_pcm_start(handle); // 開始播放
            int count = 0;
            while (!feof(fin))  {
                snd_pcm_wait(handle, -1);
                uframes = 1024;
                snd_pcm_mmap_begin(handle, &areas, &offset, &uframes);
                uframes = fread(data + offset * uBytes, uBytes, uframes, fin);
                if (uframes <= 0) break;
                snd_pcm_mmap_commit(handle, offset, uframes);
            }
        }        
        snd_pcm_close(handle);
    }
    fclose(fin);
    return 0;
}

沒有留言:

張貼留言

使用 pcie 轉接器連接 nvme SSD

之前 AM4 主機板使用 pcie ssd, 但主機板故障了沒辦法上網, 只好翻出以前買的 FM2 舊主機板, 想辦法讓老主機復活, 但舊主機板沒有 nvme 的界面, 因此上網買了 pcie 轉接器用來連接 nvme ssd, 遺憾的是 grub2 bootloader 無法識...