2024年4月17日 星期三

利用 smart pointer 播放 *.wav 與 *.aac 檔案的整合程式

// 主程式: play.cpp
#include <memory>
#include <stdio.h>
#include <sys/mman.h>
#include <fdk-aac/aacdecoder_lib.h>
#include "alsa/asoundlib.h"
struct PCM16 {
    int16_t *data;
    int length;
    int channel;
    int fs;
};
const PCM16 zeroPCM16 = {.data = (int16_t *)0, .length = 0, .channel = 0, .fs = 0};
const uint32_t fsHz[16] = {96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350, 0, 0, 0};
const uint32_t multiChannel[16] = {0, 1, 2, 3, 4, 5 + 1, 7 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0};
struct ChunkWAV { // sizeof(ChunkWAV) = 8
    char id[4];
    int32_t length;
    char *textID() {
        static char str[5];
        memcpy(str, id, 4);
        return str;
    }
    bool is_id(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 StructWAV { // sizeof(StructWAV) = 36
    ChunkWAV title;
    char wave[4];
    ChunkWAV fmt;
    int16_t  pcmType   , channels;
    int32_t  sampleRate, byteRate;
    int16_t  frameBytes, bitsData;
    bool is_wav() {
        if (! title.is_id("RIFF")) return false;
        static const char *waveID = "WAVE";
        for (int i = 0; i < 4; i ++) { if (wave[i] != waveID[i]) return false; }
        return fmt.is_id("fmt");
    }
};

struct WavMMAP {
    void *mmapFile = MAP_FAILED; // to management mmap file
    int32_t length = 0;// mmap file length
    int32_t position = 0; // trace file position
    int32_t audioStart = 0; // start position of audio data
    bool syncFound = false;
    int fs = 8000;
    int channel = 2;
    int frameBytes = 4;
    virtual ~WavMMAP() { if (mmapFile != MAP_FAILED) munmap(mmapFile, length); }// auto unmap
    virtual PCM16 decode() { return wavDecode(); }
    PCM16 wavDecode() {
        if (position < length) {
            auto src = (unsigned char *)mmapFile + position;// byte pointer
            const uint32_t maxSteps = length - position;// availabe bytes in file
            int tempSteps = 1024 * frameBytes;// number of byte to return
            if (tempSteps > maxSteps) tempSteps = maxSteps;
            position += tempSteps;
            return PCM16 {
                .data =  (int16_t *)src,
                .length = tempSteps / frameBytes,
                .channel = channel,
                .fs = fs
            };
        }
        return zeroPCM16;
    }
    WavMMAP(){ }// basic constructor
    WavMMAP(const char *filename) {
        if (mmapFile != MAP_FAILED) munmap(mmapFile, length);
        int fd = open(filename, O_RDONLY);
        if (fd < 0) {
            printf("%s => not found.\n", filename);
        } else {
            int fileLength = lseek(fd, 0l, SEEK_END);
            mmapFile = mmap(0, fileLength, PROT_READ, MAP_PRIVATE, fd, 0);
            close(fd);
            length = fileLength;

            auto header = (StructWAV *)mmapFile;
            if (header->is_wav()) { // wav file
                fs = header->sampleRate;
                channel   = header->channels;
                frameBytes = header->frameBytes;
                audioStart = sizeof(StructWAV);
                int offset = sizeof(ChunkWAV);
                while (audioStart < length) {
                    auto *chunkList = (ChunkWAV *)((unsigned char *)mmapFile + audioStart);
                    if (chunkList->is_id("data")) {
                        audioStart += offset;
                        break;
                    }
                    audioStart += offset + chunkList->length;
                }
            } else {
                auto uid = (unsigned char *)mmapFile;
                if (uid[0] == 0xff && (uid[1] >> 4) == 0xf) {
                    syncFound = true;
                    if (uid[1] == 0xf1 || uid[1] == 0xf9) {// aac file
                        fs = fsHz[(uid[2] & 0x3e) >> 2];
                        channel = multiChannel[((uid[2] & 1) << 2) | (uid[3] >> 6)];
                        frameBytes = channel * 2; // 16bits
                    }
                }
            }
        }
        position = audioStart;
    }
};

struct FdkAAC: WavMMAP {
    int16_t pcmdist[1152 * 5];
    HANDLE_AACDECODER decoder = aacDecoder_Open(TT_MP4_ADTS, 1);
    PCM16 decode( ) override {
        if (! syncFound) {// fall back to use super.wavDecoder()
            return wavDecode();
        }
        if (position < length) {
            unsigned char *src[] = { (unsigned char *)mmapFile + position };
            const uint32_t maxSteps = length - position;
            uint32_t tempSteps = maxSteps;
            aacDecoder_Fill(decoder, src, &maxSteps, &tempSteps);
            int result = (int)aacDecoder_DecodeFrame(decoder, pcmdist, sizeof(pcmdist), 0);
            position += maxSteps - tempSteps;
            if (result == AAC_DEC_OK) {                
                CStreamInfo *info = aacDecoder_GetStreamInfo(decoder);                        
                return PCM16 {
                    .data = pcmdist,
                    .length = info->frameSize,
                    .channel = info->numChannels,
                    .fs = info->sampleRate
                };
            }
        }
        return zeroPCM16;
    }
    ~FdkAAC() { aacDecoder_Close(decoder); }
    FdkAAC(const char *filename): WavMMAP(filename) { }
};
typedef std::unique_ptr<WavMMAP> MyDecoder;

int main(int argc, char const *argv[]) {
    const char* filename[2] = {"test.aac", "test.wav"};
    int len = sizeof(filename) / sizeof(filename[0]);
    for (int i = 0; i < len; i++) {
        auto play = MyDecoder(new FdkAAC(filename[i]));
        auto frame = play->decode();
          if (frame.length == 0) break;
        snd_pcm_t *handle;
        if (snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0) == 0) { // hw:0,0 , default
            if (snd_pcm_set_params(handle, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, frame.channel, frame.fs, 1, frame.fs / 4) == 0) {     
                do {
                     int err = snd_pcm_writei(handle, frame.data, frame.length);
                     if (err < 0) {// alsa try to recover
                         if (snd_pcm_recover(handle, err, 0) < 0) {
                            printf("alsa can't recover\n");
                            break;
                        }
                     }
                    frame = play->decode();
                } while (frame.length > 0);
            }
            snd_pcm_close(handle);
        }
    }
    return 0;
}

編譯並執行:

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

沒有留言:

張貼留言

使用 pcie 轉接器連接 nvme SSD

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