2024年4月23日 星期二

linux 上使用 libuvc 開啟 usb camera

先安裝 libx11, libuvc, libusb 程式庫:

    sudo  apt   install   libx11-dev  libuvc-dev   libusb-1.0-0-dev

//  原始程式 xwin.cpp
#include "libuvc/libuvc.h"
#include <locale.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xresource.h>
#include <X11/cursorfont.h>
#include <signal.h>
class XwinClient {
    private:
        const char *title = "usbCamera";
        int         x, y, width, height;
        Display     *display= nullptr;
        XImage         *bg = nullptr;
        Window      winX;
        GC          gCtx;
        Colormap    cmap;
        XColor      fgColor;
    public:
        XColor     greyColor, blackColor, whiteColor ,redColor, greenColor, blueColor, yellowColor;
    ~XwinClient(){
        if (bg) XDestroyImage(bg);
        if (display) {
            XFreeColormap(display, cmap);
            XFreeGC(display, gCtx);
            XDestroyWindow(display, winX);
            XCloseDisplay(display);   
        }
    }
    XwinClient(int w = 800, int h = 600) {
        width = w;
        height = h;
        display = XOpenDisplay(getenv("DISPLAY"));
        winX = XCreateSimpleWindow(display,
            XDefaultRootWindow(display), 0, 0, w, h, 0,
            WhitePixel(display, 0),
            BlackPixel(display, 0)
        );
        gCtx = XCreateGC(display, winX, 0, 0);
        cmap = DefaultColormap(display, 0);        
        auto setColorMap = [this] (XColor &c, const char *str){
            XParseColor(display, cmap, str, &c);
            XAllocColor(display, cmap, &c);
        };
        setColorMap(whiteColor , "#FFFFFF");
        setColorMap(blackColor , "#000000");
        setColorMap(redColor   , "#FF0000");
        setColorMap(greenColor , "#00FF00");
        setColorMap(blueColor  , "#0000FF");
        setColorMap(greyColor  , "#7f7f7f");
        setColorMap(yellowColor, "#FFFF00");
        fgColor = redColor;
        XSetForeground(display, gCtx, fgColor.pixel);
        XSetStandardProperties(display, winX, title,
            "Icon", None, nullptr, 0, nullptr
        );
        XSelectInput(display, winX,
            StructureNotifyMask |
            ExposureMask         |
            ButtonPressMask     |
            KeyPressMask        |
            PointerMotionMask
        );
        XMapWindow(display, winX);
        XEvent         evt;
        while (true) {
            usleep(1000);
            XEvent evt;
            XNextEvent(display, &evt);
            if(evt.type == Expose && evt.xexpose.count == 0) break;
        }
        XFlush(display);
        XClearWindow(display, winX);
        XSync(display, false);
    }

    void setImageBGR(void *rgb888, int sw, int sh, int sch) {
        while (true) {
            if (bg) XDestroyImage(bg);
            bg = nullptr;
            XWindowAttributes info;
            XGetWindowAttributes(display, winX, &info);
            Visual *v  = info.visual;
            width  = info.width;
            height = info.height;
            int depth  = info.depth;
            int dline= 4 * width;
            void *fb_bg = malloc(dline * height);
            if (fb_bg == nullptr) break;
            bg = XCreateImage(display, v, depth, ZPixmap, 0, (char *)fb_bg, width, height, 32, 0);
            if (bg == nullptr) {
                free(fb_bg);
                break;
            }
            uint8_t *dst = (uint8_t *)fb_bg;
               uint8_t *src = (uint8_t *)rgb888;
            float xs = (float)sw / width ;
            float ys = (float)sh / height;
            int  sline = sch   * sw;
            int  pSize = sline * sh;
            int  zch = (sch > 4) ? 4 : sch;
            int   vk = 0;
            float fy = 0;
            for(int y = 0; y < height && vk < pSize; y ++) {
              int ld = 0;
              int ls = 0;
              float fx = 0;
              for (int x = 0; x < width && ls < sline; x ++) {
                dst[ld] = src[ls];
                for(int z = 1; z < zch; z ++) {
                  dst[ld + z] = src[ls + z];
                }
                ld += 4;
                fx += xs;
                ls = sch * (int)fx;
              }
              dst += dline;
              fy += ys;
              vk = sline * (int)fy;
              src = (uint8_t *)rgb888 + vk;
            }
            XPutImage(display, winX, gCtx, bg, 0, 0, 0, 0, width, height);
            break;
        }
        XSync(display, false);
    }
};

XwinClient x11 = XwinClient(800, 600);
 
void screenUpdate(uvc_frame_t *frame, void *user) {
    static bool active = false;
    int length = 2 * frame->width * frame->height;
    if (active || frame->frame_format != UVC_FRAME_FORMAT_YUYV || length != frame->data_bytes) return;
    active = true;
    auto int8bits = [](float v) { return  (v < 0) ? 0 : (v > 255) ? 0xff : (uint8_t)v; };
    uint8_t *yuyv = (uint8_t *)(frame->data);
    uint8_t *dest = (uint8_t *)user;
    for (int i = 0; i < length; i += 4) {
        int y0  = *yuyv;
        int u   = yuyv[1];
        int y1  = yuyv[2];
        int v   = yuyv[3];
        dest[2] = int8bits(y0 + 1.13983 * (v - 128));// R -> b0
        dest[1] = int8bits(y0 - 0.39465 * (u - 128) - 0.5806 * (v - 128));//G -> g0
        dest[0] = int8bits(y0 + 2.03211 * (u - 128));// B -> r0
        dest[5] = int8bits(y1 + 1.13983 * (v - 128));// R -> b1
        dest[4] = int8bits(y1 - 0.39465 * (u - 128) - 0.5806 * (v - 128));//G -> g1
        dest[3] = int8bits(y1 + 2.03211 * (u - 128));// B -> r1
        yuyv += 4;
        dest += 6;
    }
    x11.setImageBGR(user, frame->width , frame->height, 3);
    active = false;
}

int main(int argc, char **argv) {
      printf("press Ctrl+C to exit.\n");
    signal(SIGINT, [](int sig) { exit(1); });
      uvc_context_t *ctx;
      printf("uvc init ...\n");
      if (uvc_init(&ctx, NULL) == UVC_SUCCESS) {
        uvc_device_t *dev;
        printf("find device ...\n");
        if (uvc_find_device(ctx, &dev, 0, 0, NULL) == UVC_SUCCESS) {
          uvc_device_handle_t *handle;
              printf("open device ...\n");
              if (uvc_open(dev, &handle) == UVC_SUCCESS) { 
                const uvc_format_desc_t *descriptor = uvc_get_format_descs(handle); 
                const uvc_frame_desc_t *hFrame = descriptor->frame_descs;
                int fps = 10000000 / hFrame->dwDefaultFrameInterval;
                  uvc_stream_ctrl_t controller;
                  uvc_frame_format fmt = UVC_FRAME_FORMAT_YUYV;
                  uvc_error_t result = uvc_get_stream_ctrl_format_size(handle, &controller, fmt, hFrame->wWidth, hFrame->wHeight, 10);       
                  if (result == UVC_SUCCESS) {
                      uint8_t *image = new uint8_t[hFrame->wWidth * hFrame->wHeight * 3];
                      uvc_start_streaming(handle, &controller, screenUpdate, image, 0);
                    sleep(10);
                        uvc_stop_streaming(handle);
                    delete [] image;
                  }
                    uvc_close(handle);
              }
            uvc_unref_device(dev);
        }
        uvc_exit(ctx);
      }
}

編譯並執行, 因為開啟 /dev/video 需要權限, 暫時用 sudo 來執行:

     g++  xwin.cpp  -lX11  -luvc  -lusb-1.0  &&  sudo ./a.out


後記:使用原始 libuvc 及 libusb 程式庫編譯
   1. 先到官網下載 libusb 及 libuvc 原始程式庫
         https://github.com/libuvc/libuvc/tags
         https://github.com/libusb/libusb/releases
   2. 在專案根目錄內, 編輯一個檔案: CMakeLists.txt
   3. 在專案根目錄內, 另建三個目錄: lib/    src/    build/ 
   4. 將上述原始程式庫解壓縮, 放到專案 lib/ 目錄內
   5. 在專案根目錄 src/ 目錄內, 再建一個目錄命名為  libuvc/, 只要放入 libuvc_config.h 檔案
   6. 建一個空檔案命名為   libuvc_config.h 放到  src/libuvc/ 目錄內:  src/libuvc/libuvc_config.h
   7. 編輯主程式放到專案 src/ 目錄內:  src/xwin.cpp
   8. 切換到專案目錄下 build/  執行 cmake .. 產生 Makefile
   9. 編譯時, 在專案 build/ 目錄內執行 make 就會產生可執行檔 xwin, 暫時可用 root 權限或用 sudo  ./xwin 來執行
# 檔案 CMakeLists.txt 內容
cmake_minimum_required(VERSION 3.16)
project(xwin)
set(topDir ${PROJECT_SOURCE_DIR})
set(uvcDir ${topDir}/lib/libuvc-0.0.7)
set(usbDir ${topDir}/lib/libusb-1.0.27)
set(uvcSource 
  ${uvcDir}/src/device.c
  ${uvcDir}/src/init.c
  ${uvcDir}/src/stream.c
)
set(usbSource  
  ${usbDir}/libusb/core.c
  ${usbDir}/libusb/descriptor.c
  ${usbDir}/libusb/hotplug.c
  ${usbDir}/libusb/io.c
  ${usbDir}/libusb/strerror.c
  ${usbDir}/libusb/sync.c
  ${usbDir}/libusb/os/events_posix.c
  ${usbDir}/libusb/os/threads_posix.c
  ${usbDir}/libusb/os/linux_usbfs.c
  ${usbDir}/libusb/os/linux_netlink.c
)
include_directories(
    $(topDir)/src
    ${uvcDir}/include
    ${usbDir}/android
    ${usbDir}/libusb
    ${usbDir}/libusb/os
)
add_library(uvcdriver
    ${uvcSource}
    ${usbSource}
)
add_executable(xwin
    ${topDir}/src/xwin.cpp
)
target_link_libraries(xwin  
    X11
    uvcdriver
)

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 

2024年4月10日 星期三

簡單的 CMakeLists.txt 用來編譯並連結程式庫

專案 mp3play 的目錄結構:
mp3play/
    ./CMakeList.txt
    ./lib/
        ./*.c
    ./src/
        ./main.cpp    
    ./include/
        ./*.h

# 檔案 CMakeLists.txt 內容
cmake_minimum_required(VERSION 3.16)
project(mp3play)
set(mp3Lib  "${PROJECT_SOURCE_DIR}/lib")
set(decoder "${PROJECT_SOURCE_DIR}/src")
include_directories($(decoder)  ${PROJECT_SOURCE_DIR}/include)
file(GLOB_RECURSE  cLib  ${mp3Lib}/*.c)
add_library (myLib ${cLib})
add_executable(mp3play
    ${decoder}/main.cpp
)
target_link_libraries(mp3play  myLib  -lasound)

要編譯時, 先執行以下命令產生 Makefile 檔案
    cd  mp3play &&  make build  &&  cd build  &&  cmake  ..
接著再執行 make 就能產生執行擋了
    cd  mp3play/build   &&  make   &&  ./mp3play

// 主程式: mp3play/src/main.cpp
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
#include "alsa/asoundlib.h"
#include "mp3dec.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 char *path_to_close = "./close!";
auto stepDecoder = [](const void *path = nullptr, int repeate = 0) {
    static void *mmapFile = MAP_FAILED; // to management mmap file
    static HMP3Decoder decoder = nullptr;
    static int32_t length = 0;// mmap file length
    static int32_t position = 0; // trace file position
    static int32_t remainRepeate = 0;
    if (path == path_to_close) { // to close decoder
        if (decoder) {
            MP3FreeDecoder(decoder);
            decoder = nullptr;
            length = 0;
            position = 0;
            remainRepeate = 0;
        }
    } else {
        if (path) {
            const char *filename = (const char *)path;
            int fd = open(filename, O_RDONLY);
            if (mmapFile != MAP_FAILED) munmap(mmapFile, length);
            if (fd < 0) {
                printf("%s => not found.\n", filename);
            } else {
                int fileLength = lseek(fd, 0l, SEEK_END);
                if (fileLength >= 512) {
                    mmapFile = mmap(0, fileLength, PROT_READ, MAP_PRIVATE, fd, 0);
                    if (mmapFile == MAP_FAILED)  {
                        printf("%s mmap fail.\n", filename);
                    } else {
                        if (decoder) MP3FreeDecoder(decoder);
                        decoder = MP3InitDecoder();
                        printf("%s file length = %d\n", filename, fileLength);
                        length = fileLength;// mmap sucess
                        position = 0;
                        remainRepeate = repeate;                   
                    }
                }
                close(fd);// After the mmap() call, fd can be closed immediately.
            }
        }
        if (remainRepeate > 0 && (position >= length)) {
            printf("End of file, position = %d, wrap arond to repeate again. remainRepeate = %d\n", position, remainRepeate);
            remainRepeate --;
            position = 0;
        }
        if (position < length) {
            unsigned char *src = (unsigned char *)mmapFile + position;
            static int16_t pcmdist[1152 * 2];
            const uint32_t maxSteps = length - position;
            int tempSteps = maxSteps;// tempSteps will be updated by decoder
            for (int i = 0; i < maxSteps; i ++) {
                if (*src == 0xff && (src[1] >> 4) == 0xf) break;// find sync word
                tempSteps --; // size shrink
                src ++;  // seek one by one
            }
             int result = MP3Decode(decoder, &src, &tempSteps, pcmdist, 0);
            position += maxSteps - tempSteps; // go ahead, and back off by tempSteps
            printf("maxSteps = %8d, tempSteps = %8d, position = %8d, steps = %8d, err = %4x:\t\t\n",
                maxSteps,
                tempSteps,
                position,
                maxSteps - tempSteps,
                (unsigned)result
            );            
            if (result == ERR_MP3_NONE) {
                MP3FrameInfo frameinfo;
                MP3GetLastFrameInfo(decoder, &frameinfo);
                int nChans = frameinfo.nChans;
                return PCM16 {
                    .data = pcmdist,
                    .length = (int)frameinfo.outputSamps / nChans,
                    .channel = nChans,
                    .fs = frameinfo.samprate
                };
            }
        }
    }
    return zeroPCM16;
};
int main(int argc, char const *argv[]) {    
    PCM16 frame = stepDecoder((argc > 1) ? argv[1] : "test.mp3");
     if (frame.length==0) return 0;
    snd_pcm_t *handle;
    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, frame.channel, frame.fs, 1, frame.fs / 4) == 0) {    
            do {
                 int err = snd_pcm_writei(handle, frame.data, frame.length);
                 if (err < 0) {// try to recover
                     if (snd_pcm_recover(handle, err, 0) < 0) {
                        printf("alsa can't recover\n");
                        break;
                    }
                 }
                frame = stepDecoder();
            } while (frame.length);
        }
        snd_pcm_close(handle);
    }   
    stepDecoder(path_to_close);
    return 0;
}

2024年4月7日 星期日

linux 使用 ALSA lib 播放 aac 檔

先安裝開發程式庫

        sudo   apt   install   libfdk-acc-dev   libasound-dev

下載一些測試 aac 檔案: https://espressif-docs.readthedocs-hosted.com/projects/esp-adf/en/latest/design-guide/audio-samples.html

// 主程式: aacplay.cpp
#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 char *path_to_close = "./close!";
auto stepDecoder = [](const void *path = nullptr, int repeate = 0) {
    static void *mmapFile = MAP_FAILED; // to management mmap file
    static HANDLE_AACDECODER decoder = nullptr;
    static int32_t length = 0;// mmap file length
    static int32_t position = 0; // trace file position
    static int32_t remainRepeate = 0;
    if (path == path_to_close) { // to close decoder
        if (decoder) {
            aacDecoder_Close(decoder);
            decoder = nullptr;
            length = 0;
            position = 0;
            remainRepeate = 0;
        }
    } else {
        if (path) {// use non-null path to initialize mmap
            const char *filename = (const char *)path;
            int fd = open(filename, O_RDONLY);
            if (mmapFile != MAP_FAILED) munmap(mmapFile, length);
            if (fd < 0) {
                printf("%s => not found.\n", filename);
            } else {
                int fileLength = lseek(fd, 0l, SEEK_END);
                if (fileLength >= 512) {
                    mmapFile = mmap(0, fileLength, PROT_READ, MAP_PRIVATE, fd, 0);
                    if (mmapFile == MAP_FAILED)  {
                        printf("%s mmap fail.\n", filename);
                    } else {
                        if (decoder) aacDecoder_Close(decoder);
                        decoder = aacDecoder_Open(TT_MP4_ADTS, 1);
                        printf("%s file length = %d\n", filename, fileLength);
                        length = fileLength;// mmap sucess
                        position = 0;
                        remainRepeate = repeate;
                    }
                }
                close(fd);// After the mmap() call, fd can be closed immediately.
            }
        }
        if (remainRepeate > 0 && (position >= length)) { // when repeate enable
            printf("End of file, position = %d, wrap arond to repeate again. remainRepeate = %d\n", position, remainRepeate);
            remainRepeate --;
            position = 0;
        }
        if (position < length) {
            unsigned char *src[] = { (unsigned char *)mmapFile + position };
            static int16_t pcmdist[1152 * 5];// distinct PCM16 buffer for 5 channels
            const uint32_t maxSteps = length - position;
            uint32_t tempSteps = maxSteps;// tempSteps will be updated by decoder
            aacDecoder_Fill(decoder, src, &maxSteps, &tempSteps);
            int result = (int)aacDecoder_DecodeFrame(decoder, pcmdist, sizeof(pcmdist), 0);
            position += maxSteps - tempSteps; // go ahead, and back off by tempSteps
            printf("maxSteps = %8d, tempSteps = %8d, position = %8d, steps = %8d, err = %4x:\t\t\n",
                maxSteps,
                tempSteps,
                position,
                maxSteps - tempSteps,
                (unsigned)result
            );          
            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;
};

int main(int argc, char const *argv[]) {    
    PCM16 frame = stepDecoder((argc > 1) ? argv[1] : "test.aac");
     if (frame.length==0) return 0;
    snd_pcm_t *handle;
    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, frame.channel, frame.fs, 1, frame.fs / 4) == 0) {    
            do {
                 int err = snd_pcm_writei(handle, frame.data, frame.length);
                 if (err < 0) {// try to recover
                     if (snd_pcm_recover(handle, err, 0) < 0) {
                        printf("alsa can't recover\n");
                        break;
                    }
                 }
                frame = stepDecoder();
            } while (frame.length);
        }
        snd_pcm_close(handle);
    }   
    stepDecoder(path_to_close);
    return 0;
}

編譯並執行:

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

2024年4月6日 星期六

linux 使用 ALSA lib 播放 mp3 檔

先上官網下載 minimp3 原始檔: https://github.com/lieff/minimp3

只需將檔案 minimp3.h 複製到專案目錄.  再編輯 mp3 播放主程式, 同樣透過 ALSA library 來播放:
// mp3play.cpp
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include "alsa/asoundlib.h"
#define MINIMP3_IMPLEMENTATION
#include "minimp3.h"

int main(int argc, char *argv[]) {
    const char *filename = (argc > 1) ? argv[1] : "test.mp3";
    int fd = open(filename, O_RDONLY);
    if (fd < 0) {
        printf("%s => not found\n", filename);
        return -1;
    }
    int fileLength = lseek(fd, 0, SEEK_END);
    unsigned char *fileMMAP = (unsigned char *) mmap(0, fileLength, PROT_READ, MAP_PRIVATE, fd, 0);
    int16_t speaker[MINIMP3_MAX_SAMPLES_PER_FRAME];

    mp3dec_frame_info_t info;
    mp3dec_t mp3decoder;
    mp3dec_init(&mp3decoder);
    int uframes = mp3dec_decode_frame(&mp3decoder, fileMMAP, fileLength, speaker, &info);// 1st frame to get channels && sampleRate
    if (uframes > 0) {
        printf("play uframes = %d, offset =%d, channels=%d, sampleRate=%d, frame_bytes=%d\n", uframes, info.frame_offset, info.channels, info.hz, info.frame_bytes);
        unsigned int channels = info.channels;
        unsigned int sampleRate = info.hz;
        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, channels, sampleRate, 1, sampleRate / 4) == 0) {
                do {
                     const int err = snd_pcm_writei(handle, speaker, uframes);
                     if (err < 0) {
                         if (snd_pcm_recover(handle, err, 0) < 0) break;// alsa try to recover
                     }
                    fileMMAP += info.frame_bytes;// mp3 file position goes ahead
                    uframes = mp3dec_decode_frame(&mp3decoder, fileMMAP, uframes, speaker, &info);// is 1152 enough?
                } while (uframes > 0);
            }
            snd_pcm_close(handle);
        }
    }
    close(fd);
    return 0;
}
編譯並執行

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

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;
}

簡單 c 程式碼, 根據五行八卦相生相剋推斷吉凶

#include "stdio.h" // 五行: //               木2 //      水1           火3 //         金0     土4 // // 先天八卦 vs 五行 //                    ...