【C++】miniaudio:音频播放的轻量封装

miniaudio 是一个轻量级的音频库,以单个头文件的形式提供,方便在 C++ 项目中集成。它提供了简单易用的 API 来处理音频播放。本教程将详细介绍如何引入 miniaudio,并通过面向对象的封装方式创建一个 AudioPlayer 类,使音频播放功能更易于使用。我们还将提供一个完整的示例程序,展示如何加载、播放和控制音频。


下载和引入 miniaudio

首先,你需要从 miniaudio 的 GitHub 仓库 下载 miniaudio.h 文件。将该头文件放入你的项目目录中,并在需要使用音频播放功能的文件中引入它。

在代码中引入 miniaudio.h 的方式如下:

#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"

注意事项

  • MINIAUDIO_IMPLEMENTATION:必须在 #include "miniaudio.h" 之前定义。这个宏确保 miniaudio 的实现代码被包含到你的项目中。如果不定义该宏,仅包含头文件将不会有实际的功能实现。

完成这一步后,你就可以开始使用 miniaudio 的功能了。接下来,我们将通过封装一个 AudioPlayer 类来简化音频播放的操作。


封装 AudioPlayer

为了更方便地管理音频播放,我们将 miniaudio 的核心功能封装到一个 AudioPlayer 类中。这个类提供了加载音频文件、播放、暂停、恢复、停止以及音量控制等常用功能。

类定义

以下是完整的 AudioPlayer 类代码,建议将其保存为 AudioPlayer.hpp

#pragma once

#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"

#include <string>
#include <iostream>

class AudioPlayer
{
public:
    AudioPlayer() : m_initialized(false), m_volume(1.0f), m_isPaused(false) {}

    ~AudioPlayer() {
        stop(); // 析构时释放资源
    }

    // 加载音频文件,自动释放旧资源
    bool load(const std::string& filePath, bool loop = true)
    {
        // 自动释放旧资源
        if (m_initialized) {
            stop();
        }

        // 初始化解码器
        ma_result result = ma_decoder_init_file(filePath.c_str(), NULL, &m_decoder);
        if (result != MA_SUCCESS) {
            std::cerr << "Failed to initialize decoder for file: " << filePath << std::endl;
            m_initialized = false;
            return false;
        }

        // 循环设置
        ma_data_source_set_looping(&m_decoder, loop ? MA_TRUE : MA_FALSE);

        // 设备配置
        m_deviceConfig = ma_device_config_init(ma_device_type_playback);
        m_deviceConfig.playback.format   = m_decoder.outputFormat;
        m_deviceConfig.playback.channels = m_decoder.outputChannels;
        m_deviceConfig.sampleRate        = m_decoder.outputSampleRate;
        m_deviceConfig.dataCallback      = data_callback;
        m_deviceConfig.pUserData         = &m_decoder;

        // 初始化设备
        if (ma_device_init(NULL, &m_deviceConfig, &m_device) != MA_SUCCESS) {
            std::cerr << "Failed to initialize playback device." << std::endl;
            ma_decoder_uninit(&m_decoder);
            m_initialized = false;
            return false;
        }

        // 设置音量
        ma_device_set_master_volume(&m_device, m_volume);

        m_initialized = true;
        m_isPaused = false;
        return true;
    }

    // 开始播放
    bool play()
    {
        if (!m_initialized) {
            std::cerr << "Device is not initialized.\n";
            return false;
        }

        if (ma_device_start(&m_device) != MA_SUCCESS) {
            std::cerr << "Failed to start playback device.\n";
            return false;
        }

        m_isPaused = false;
        return true;
    }

    // 停止播放并释放资源
    void stop()
    {
        if (m_initialized) {
            ma_device_uninit(&m_device);
            ma_decoder_uninit(&m_decoder);
            m_initialized = false;
            m_isPaused = false;
        }
    }

    // 暂停播放
    void pause()
    {
        if (m_initialized && !m_isPaused) {
            ma_device_stop(&m_device);
            m_isPaused = true;
        }
    }

    // 恢复播放
    void resume()
    {
        if (m_initialized && m_isPaused) {
            ma_device_start(&m_device);
            m_isPaused = false;
        }
    }

    // 设置音量(0.0 ~ 1.0)
    void setVolume(float volume)
    {
        if (!m_initialized) return;
        m_volume = (volume < 0.0f) ? 0.0f : (volume > 1.0f) ? 1.0f : volume; // 限制范围
        ma_device_set_master_volume(&m_device, m_volume);
    }

    // 获取音量
    float getVolume() const { return m_volume; }

    bool isPlaying() const { return m_initialized && !m_isPaused; }
    bool isPaused() const { return m_initialized && m_isPaused; }

private:
    static void data_callback(ma_device* pDevice, void* pOutput, const void* /*pInput*/, ma_uint32 frameCount)
    {
        ma_decoder* pDecoder = (ma_decoder*)pDevice->pUserData;
        if (pDecoder == nullptr) {
            return;
        }
        ma_data_source_read_pcm_frames(pDecoder, pOutput, frameCount, nullptr);
    }

private:
    ma_decoder m_decoder;
    ma_device_config m_deviceConfig;
    ma_device m_device;
    bool m_initialized;
    bool m_isPaused;
    float m_volume;
};

功能说明

以下是 AudioPlayer 类中各个成员函数的详细说明:

  • 构造函数和析构函数

    • 构造函数初始化成员变量:m_initializedfalse(表示未初始化)、m_volume 为 1.0(默认最大音量)、m_isPausedfalse(未暂停)。
    • 析构函数调用 stop(),确保在对象销毁时释放所有资源。
  • load(const std::string& filePath, bool loop = true)

    • 加载指定的音频文件(如 MP3、WAV 等)。
    • 如果已有资源,会先释放旧资源。
    • 参数 loop 控制是否循环播放,默认值为 true
    • 返回 true 表示加载成功,否则返回 false 并输出错误信息。
  • play()

    • 开始播放已加载的音频。
    • 如果设备未初始化或启动失败,返回 false 并输出错误信息。
  • stop()

    • 停止播放并释放解码器和设备资源。
    • 可在任何时候安全调用,即使未初始化也不会出错。
  • pause()

    • 暂停播放,只有在已初始化且未暂停时生效。
  • resume()

    • 恢复暂停的播放,只有在已初始化且已暂停时生效。
  • setVolume(float volume)

    • 设置音量,范围为 0.0(静音)到 1.0(最大音量)。
    • 超出范围的值会被限制在 0.0 到 1.0 之间。
  • getVolume()

    • 返回当前音量值。
  • isPlaying()isPaused()

    • 检查当前播放状态,分别返回是否正在播放或暂停。
  • data_callback

    • 静态回调函数,由 miniaudio 调用,用于从解码器读取音频数据并输出到播放设备。

使用示例

以下是一个使用 AudioPlayer 类的完整示例程序,展示如何加载音频文件、循环播放,并通过命令行控制音量和播放状态。

将以下代码保存为 main.cpp

#include "AudioPlayer.hpp"
#include <iostream>

int main() {
    AudioPlayer player;

    // 加载并播放
    if (!player.load("./input.mp3", true)) {
        std::cerr << "Failed to load audio file." << std::endl;
        return 1;
    }
    player.play();

    std::cout << "Playing input.mp3 in loop." << std::endl;
    std::cout << "Commands: [+] Volume Up | [-] Volume Down | [p] Pause | [r] Resume | [q] Quit" << std::endl;

    char cmd;
    while (std::cin >> cmd) {
        if (cmd == '+') {
            float newVolume = player.getVolume() + 0.1f;
            player.setVolume(newVolume);
            std::cout << "Volume: " << player.getVolume() << std::endl;
        } else if (cmd == '-') {
            float newVolume = player.getVolume() - 0.1f;
            player.setVolume(newVolume);
            std::cout << "Volume: " << player.getVolume() << std::endl;
        } else if (cmd == 'p') {
            player.pause();
            std::cout << "Paused." << std::endl;
        } else if (cmd == 'r') {
            player.resume();
            std::cout << "Resumed." << std::endl;
        } else if (cmd == 'q') {
            break;
        }
    }

    player.stop(); // 最终释放资源
    return 0;
}

运行步骤

  1. 准备工作
  • 确保 miniaudio.hAudioPlayer.hpp 已正确引入项目。
  • 将一个音频文件(例如 input.mp3)放置在程序运行目录下。
  1. 编译和运行
  • 使用支持 C++ 的编译器(例如 g++)编译程序:

    g++ main.cpp -o audio_player
    
  • 运行程序:

    ./audio_player
    
  1. 控制播放
  • 输入以下命令:
    • +:增加音量(每次增加 0.1)。
    • -:减小音量(每次减小 0.1)。
    • p:暂停播放。
    • r:恢复播放。
    • q:退出程序。

程序启动后,音频将循环播放,并通过命令行显示当前状态和音量。


注意事项

  • 资源管理

    • AudioPlayer 类在加载新音频文件时会自动释放旧资源,避免内存泄漏。
    • 析构函数会确保所有资源在对象销毁时被正确释放。
  • 音量范围

    • 音量值应在 0.0 到 1.0 之间,超出范围的值会被自动限制。
  • 错误处理

    • 在加载文件或初始化设备失败时,程序会输出错误信息并返回 false,便于调试。
  • 线程安全

    • 本示例未考虑多线程环境。如果需要在多线程中使用 AudioPlayer,需自行添加线程同步机制。

通过本教程,你可以在 C++ 项目中快速集成音频播放功能,并使用封装好的 AudioPlayer 类轻松控制播放行为。无论是简单的音频播放需求,还是更复杂的应用场景,这一轻量封装都能提供良好的支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值