掘金 后端 ( ) • 2024-04-23 10:42

软件开发中遇到需要播放音频文件时,可以使用操作系统提供的API也可以依赖于第三方库,通常第三方库多因其简单易用的接口而成为首选。miniaudio便是游戏的音频开源库之一。

miniaudio 是一个轻量级的音频播放、采集、播放+采集的库,专注于提供简单易用的 API 和跨平台的音频播放功能。它具有以下特点:

  • 轻量级: miniaudio 是一个小巧的库,header-only,不依赖于其他外部库,易于集成到各种项目中。

  • 跨平台: 支持 Windows、macOS、Linux 等多个主流操作系统,并提供了对于多种平台的兼容性。

  • 简单易用: miniaudio 提供了简洁的 API,无需复杂的配置,即使对音频编程不熟悉的开发者也能够快速上手。

下载和安装

下载链接见(https://github.com/mackron/miniaudio /tree/0.11.21),

miniaudio作为header-only的开源库,只需将miniaudio.h头文件集成到项目中即可。在使用时,需要在包含头文件前定义宏MINIAUDIO_IMPLEMENTATION,形如:

#define MINIAUDIO_IMPLEMENTATION
#include"miniaudio/miniaudio.h"

使用

miniaudio分为上层(High Low)接口和底层(Low Level)接口两种,高层接口做了封装,使用起来更加方便,对于开发者来讲更像一个黑盒子。=反而底层接口,开发者可以获得操作音频原始数据的机会。

接下来将分别使用上层接口、底层接口来播放本地文件以及录制声音。

#define MINIAUDIO_IMPLEMENTATION
#include"miniaudio/miniaudio.h"
//上层接口播放本地文件
void using_high_level_playback()
{
    ma_result result;
    ma_engine engine;
    result = ma_engine_init(NULL, &engine);
    if (result != MA_SUCCESS) {
        return ;
    }


    //"d://22.mp3"本地文件,需替换为自己的文件
    ma_engine_play_sound(&engine, "d://22.mp3", NULL);
    printf("Press Enter to quit...");
    getchar();

    ma_engine_uninit(&engine);
}



//底层接口播放本地文件
void data_callback_playback(ma_device*pDevice, void*pOutput,
        constvoid*pInput, ma_uint32frameCount)
{
    ma_decoder* pDecoder = (ma_decoder*)pDevice->pUserData;
    if (pDecoder == NULL) {
        return;
    }

    ma_decoder_read_pcm_frames(pDecoder, pOutput, frameCount, NULL);
    (void)pInput;
}



void using_low_level_playback()
{
    ma_result result;
    ma_decoder decoder;
    ma_device_config deviceConfig;
    ma_device device;


    result = ma_decoder_init_file("d://22.mp3", NULL, &decoder);
    if (result != MA_SUCCESS) {
        return;
    }


    deviceConfig = ma_device_config_init(ma_device_type_playback);
    deviceConfig.playback.format = decoder.outputFormat;
    deviceConfig.playback.channels = decoder.outputChannels;
    deviceConfig.sampleRate = decoder.outputSampleRate;
    deviceConfig.dataCallback = data_callback_playback;
    deviceConfig.pUserData = &decoder;


    if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS) {
        printf("Failed to open playback device.\n");
        ma_decoder_uninit(&decoder);
        return ;
    }


    if (ma_device_start(&device) != MA_SUCCESS) {
        printf("Failed to start playback device.\n");
        ma_device_uninit(&device);
        ma_decoder_uninit(&decoder);
        return ;
    }


    printf("Press Enter to quit...");
    getchar();


    ma_device_uninit(&device);
    ma_decoder_uninit(&decoder);
}




//采集声音
void data_callback_capture(ma_device*pDevice, void*pOutput,
            constvoid*pInput, ma_uint32frameCount)
{
    ma_encoder* pEncoder = (ma_encoder*)pDevice->pUserData;
    MA_ASSERT(pEncoder != NULL);
    ma_encoder_write_pcm_frames(pEncoder, pInput, frameCount, NULL);
    (void)pOutput;
}


int using_capture()
{
    ma_result result;
    ma_encoder_config encoderConfig;
    ma_encoder encoder;
    ma_device_config deviceConfig;
    ma_device device;



    encoderConfig = ma_encoder_config_init(ma_encoding_format_wav, ma_format_f32, 2, 48000);


    if (ma_encoder_init_file("d://capture.wav", &encoderConfig, &encoder) != MA_SUCCESS) {
        printf("Failed to initialize output file.\n");
        return -1;
    }


    deviceConfig = ma_device_config_init(ma_device_type_duplex);
    deviceConfig.capture.format = encoder.config.format;
    deviceConfig.capture.channels = encoder.config.channels;
    deviceConfig.sampleRate = encoder.config.sampleRate;
    deviceConfig.dataCallback = data_callback_capture;
    deviceConfig.pUserData = &encoder;


    result = ma_device_init(NULL, &deviceConfig, &device);
    if (result != MA_SUCCESS) {
        printf("Failed to initialize capture device.\n");
        return -2;
    }


    result = ma_device_start(&device);
    if (result != MA_SUCCESS) {
        ma_device_uninit(&device);
        printf("Failed to start device.\n");
        return -3;
    }


    printf("Press Enter to stop recording...\n");
    getchar();


    ma_device_uninit(&device);
    ma_encoder_uninit(&encoder);


    return0;
}

对比播放本地文件部分代码,扎心的发现,上层接口使用极其少量的代码实现了和底层接口相同的功能。但是底层接口,使得我们获得了在回调函数data_callback操作pcm数据的可能,当然若没有修改pcm的需求时,可以直接使用上层的接口。

总结

miniaudio作为一个header-only且MIT协议的开源库,极大地方便了在项目中的集成。同时,miniaudio支持播放、采集、采集同时播放的功能,可视为音频开源库的首选。

本文使用 文章同步助手 同步