首页 > 其他分享 >JUCE - 音频

JUCE - 音频

时间:2024-10-07 17:34:03浏览次数:1  
标签:AudioFormatReaderSource juce 对象 JUCE 音频 transportSource 按钮

官方文档:https://juce.com/learn/tutorials/

1、构建音频播放器

本教程介绍如何打开和播放声音文件。其中包括一些在 JUCE 中处理声音文件的重要类。

级别:中级

平台: Windows、macOS、Linux

类: AudioFormatManagerAudioFormatReaderAudioFormatReaderSourceAudioTransportSourceFileChooserChangeListenerFileFileChooser

1.1、入门

在此处下载本教程的演示项目:PIP | ZIP。解压缩项目并在 Projucer 中打开第一个头文件。

1.2、演示项目

演示项目提供了一个三按钮界面,用于控制声音文件的播放。这三个按钮分别是:

  • 一个按钮向用户显示文件选择器,供他们选择声音文件。
  • 播放声音的按钮。
  • 一个按钮来停止声音。

界面如下图所示:

1.3、有用的课程

1.3.1、AudioSource 类

虽然我们可以在音频应用程序模板的getNextAudioBlock()函数中逐个生成音频样本,但有一些内置工具可用于生成和处理音频。这些工具允许我们将高级构建块链接在一起以形成强大的音频应用程序,而无需在我们的应用程序代码中处理每个音频样本(JUCE 代表我们执行此操作)。这些构建块基于AudioSource。事实上,如果您已经遵循了基于AudioAppComponent类的任何教程(例如,教程:构建白噪声生成器),那么您已经在使用AudioSource类了。AudioAppComponent类本身继承自AudioSource类,重要的是,它包含一个AudioSourcePlayer对象,该对象在AudioAppComponent和音频硬件设备之间传输音频。我们可以直接在getNextAudioBlock()函数中生成音频样本,但我们可以将多个AudioSource对象链接在一起以形成一系列流程。我们在本教程中使用此功能。

1.3.2、音频格式

JUCE 提供了许多用于读取和写入多种格式的声音文件的工具。在本教程中,我们将使用其中的几个,特别是使用以下类:

1.4、整合

现在,我们将把这些类与合适的用户界面类组合在一起,以制作我们的声音文件播放应用程序。此时,考虑播放音频文件的各个阶段(或传输状态)很有用。加载音频文件后,我们可以考虑以下四种可能的状态:

  • Stopped:音频播放已停止并准备开始。
  • Starting:音频播放尚未开始,但已被告知开始。
  • Playing:音频正在播放。
  • Stopping:音频正在播放,但播放已被告知停止,此后它将返回停止状态。

为了表示这些状态,我们在MainContentComponent类中创建一个enum

    enum {
        Stopped, // 已停止
        Starting, // 正在开始
        Playing, // 播放中
        Stopping // 停止中
    };

1.4.1、初始化接口

在我们MainContentComponent类的构造函数中,我们配置了三个按钮:

MainComponent::MainComponent():
    state(TransportState::Stopped)
{
    setSize (800, 600);

    // Some platforms require permissions to open input channels so request that here
    if (juce::RuntimePermissions::isRequired (juce::RuntimePermissions::recordAudio)
        && ! juce::RuntimePermissions::isGranted (juce::RuntimePermissions::recordAudio)) {
        juce::RuntimePermissions::request (juce::RuntimePermissions::recordAudio,
                                           [&] (bool granted) { setAudioChannels (granted ? 2 : 0, 2); });
    }
    else {
        // Specify the number of input and output channels that we want to open
        setAudioChannels (2, 2);
    }

    addAndMakeVisible(&openButton);
    openButton.setButtonText("Open...");
    openButton.onClick = [this]() {openButtonClicked(); };
    addAndMakeVisible(&playButton);
    playButton.setButtonText("Play");
    playButton.onClick = [this]() {playButtonClicked(); };
    playButton.setColour(juce::TextButton::buttonColourId, juce::Colours::green);
    playButton.setEnabled(false);
    addAndMakeVisible(&stopButton);
    stopButton.setButtonText("Stop");
    stopButton.onClick = [this]() {stopButtonClicked(); };
    stopButton.setColour(juce::TextButton::buttonColourId, juce::Colours::red);
    stopButton.setEnabled(false);
}

特别注意,我们最初禁用了播放停止按钮。加载有效文件后,播放按钮就会启用。我们可以在这里看到,我们为这三个按钮的Button::onClick辅​​助对象分配了一个 lambda 函数(请参阅教程:监听器和广播器)。我们还在构造函数的初始化列表中初始化了传输状态。

1.4.2、其他初始化

除了三个TextButton对象之外,我们的MainContentComponent类还有另外四个成员:

    juce::AudioFormatManager formatManager; // 音频格式管理
    std::unique_ptr<juce::AudioFormatReaderSource> readerSource;
    juce::AudioTransportSource transportSource;
    TransportState state;

这里我们看到前面提到的AudioFormatManagerAudioFormatReaderSourceAudioTransportSource类。

MainContentComponent构造函数中,我们需要初始化AudioFormatManager对象来注册标准格式列表:

    // 注册标准格式列表
    formatManager.registerBasicFormats();

至少这将使AudioFormatManager对象能够为 WAV 和 AIFF 格式创建读取器。其他格式可能可用,具体取决于平台和 Projucer 项目中模块中启用的选项,juce_audio_formats如以下屏幕截图所示:

MainContentComponent构造函数中,我们还将我们的MainContentComponent对象作为侦听器添加到AudioTransportSource对象中,以便我们可以响应其状态的变化(例如,当它停止时):

// 继承 ChangeListener 类
class MainComponent  : public juce::AudioAppComponent, public juce::ChangeListener    

// 在构造函数中添加如下代码
transportSource.addChangeListener(this);

注意:在这种情况下,函数addChangeListener()名称是这样的,而不是像JUCE 中的许多其他监听器类那样简单地使用addListener()

1.4.3、响应 AudioTransportSource 更改

当传输中的变化被报告时,changeListenerCallback()将被调用。这将在消息线程上异步调用:

void MainComponent::changeListenerCallback(juce::ChangeBroadcaster* source) {
    if (source == &transportSource) {
        if (transportSource.isPlaying()) {
            changeState(TransportState::Playing);
        } else {
            changeState(TransportState::Stopped);
        }
    }
}

您可以看到这只是调用一个changeState()成员函数。

1.4.4、改变状态

传输状态的改变被局限在这个单一的changeState()函数中。这有助于将此功能的所有逻辑集中在一个地方。此函数更新state成员并触发在新状态下需要对其他对象进行的任何更改。

void MainComponent::changeState(TransportState newState) {
    if (state != newState) {
        state = newState;
        switch (state) {
        case Stopped:
            stopButton.setEnabled(false);
            playButton.setEnabled(true);
            transportSource.setPosition(0.0);
            break;
        case Starting:
            playButton.setEnabled(false);
            transportSource.start();
        case Playing:
            stopButton.setEnabled(true);
            break;
        case Stopping:
            transportSource.stop();
            break;
        }
    }
}
  • 当传输返回到停止状态时,它会禁用“停止”按钮,启用“播放”按钮,并将传输位置重置回文件的开头。
  • 用户点击播放按钮会触发“开始”状态,这会告诉AudioTransportSource对象开始播放。此时我们也禁用了播放按钮。
  • AudioTransportSource对象通过函数报告变化后,会触发播放状态。这里我们启用了停止按钮。changeListenerCallback()
  • 停止状态是由用户点击停止按钮触发的,因此我们告诉AudioTransportSource对象停止。

1.4.5、处理音频

此演示项目中的音频处理非常简单:我们只需将通过AudioAppComponent类传递的AudioSourceChannelInfo结构传递给AudioTransportSource对象即可:

void MainComponent::getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill)
{
    if (readerSource.get() == nullptr) {
        bufferToFill.clearActiveBufferRegion();
        return;
    }
    transportSource.getNextAudioBlock(bufferToFill);
}

请注意,我们首先检查是否存在有效的AudioFormatReaderSource对象,如果没有,则简单地将输出归零(使用方便的AudioSourceChannelInfo ::clearActiveBufferRegion()函数)。AudioFormatReaderSource成员存储在 std::unique_ptr 对象中,因为我们需要根据用户的操作动态创建这些对象。它还允许我们检查是否存在nullptr无效对象。

我们还需要记住将prepareToPlay()回调传递给我们正在使用的任何其他AudioSource对象:

void MainComponent::prepareToPlay (int samplesPerBlockExpected, double sampleRate)
{
    // This function will be called when the audio device is started, or when
    // its settings (i.e. sample rate, block size, etc) are changed.

    // You can use this function to initialise any resources you might need,
    // but be careful - it will be called on the audio thread, not the GUI thread.

    // For more details, see the help for AudioProcessor::prepareToPlay()
    transportSource.prepareToPlay(samplesPerBlockExpected, sampleRate);
}

还有releaseResources()回调:

void MainComponent::releaseResources()
{
    // This will be called when the audio device stops, or when it is being
    // restarted due to a setting change.

    // For more details, see the help for AudioProcessor::releaseResources()
    transportSource.releaseResources();
}

1.4.6、打开文件

要打开文件,我们会弹出一个FileChooser对象来响应单击“打开...”按钮:

void MainComponent::openButtonClicked() {
    // 创建带有简短消息的FileChooser对象并允许用户选择.wav文件
    chooser = std::make_unique<juce::FileChooser>("Select a Wave file to play...",
        juce::File{},
        "*.wav");
    auto chooserFlags = juce::FileBrowserComponent::openMode
        | juce::FileBrowserComponent::canSelectFiles;

    // 弹出FileChooser对象
    chooser->launchAsync(chooserFlags, [this](const juce::FileChooser& fc) {
        auto file = fc.getResult();

        // 如果文件不为空(用户实际选择了一个文件)
        if (file != juce::File{}) {
            // 尝试为特定文件创建读取器,如果失败返回nullptr(比如:该文件不是AudioFormatManager对象可以处理的音频格式)
            auto* reader = formatManager.createReaderFor(file);

            if (reader != nullptr) {
                // 使用reader创建一个新的AudioFormatReaderSource对象,第二个参数为true,表示我们希望AudioFormatReaderSource对象管理AudioFormatReader对象并在不再需要时将其删除。我们将AudioFormatReaderSource对象存储在临时的std::unique_ptr对象中,以避免在后续打开文件的命令中过早删除之前分配的AudioFormatReaderSource。
                auto newSource = std::make_unique<juce::AudioFormatReaderSource>(reader, true);
                // 将AudioFormatReaderSource对象连接到我们getNextAudioBlock()函数中使用的AudioTransportSource对象。如果文件的采样率与硬件采样率不匹配,我们会将其作为第四个参数传入,该参数是从AudioFormatReader获得的。AudioTransportSource将处理任何必要的采样率转换。
                transportSource.setSource(newSource.get(), 0, nullptr, reader->sampleRate);
                // 启用播放按钮,以便用户可以点击它
                playButton.setEnabled(true);
                // 由于AudioTransportSource现在应该使用我们新分配的AudioFormatReaderSource对象,因此我们可以安全地将AudioFormatReaderSource对象存储在我们的成员中。为此,我们必须使用std::unique_ptr::release()从局部变量newSource转移所有权
                readerSource.reset(newSource.release());
            }
        }
    });
}

注意:将新分配的AudioFormatReaderSource对象存储在临时 std::unique_ptr 对象中还有一个额外的好处,那就是可以避免异常。在函数调用AudioTransportSource::setSource()期间可能会抛出异常,在这种情况下,std::unique_ptr 对象将删除不再需要的AudioFormatReaderSource对象。如果此时使用原始指针来存储AudioFormatReaderSource对象,那么可能会发生内存泄漏,因为如果抛出异常,指针将处于悬空状态。

1.4.7、播放和停止文件

由于我们已经设置了实际播放文件的代码,因此我们只需使用适当的参数调用我们的changeState()函数即可播放文件。单击“播放”按钮时,我们将执行以下操作:

void MainComponent::playButtonClicked() {
    changeState(TransportState::Starting);
}

当单击“停止”按钮时,停止文件同样简单:

练习:创建FileChooser对象时更改第三个 ( filePatternsAllowed) 参数,以允许应用程序也加载 AIFF 文件。文件模式可以用分号分隔,因此这应该允许此格式的两个常见文件扩展名"*.wav;*.aif;*.aiff"

1.4.8、添加暂停功能

现在,我们将逐步介绍如何为应用程序添加暂停功能。在这里,我们将使播放按钮在文件播放时变为暂停按钮(而不是仅仅禁用它)。我们还将使停止按钮在声音文件暂停时变为返回零按钮。

首先,我们需要向TransportState枚举中添加两个状态PausingPaused:

    enum TransportState {
        Stopped,
        Starting,
        Playing,
        Pausing,
        Paused,
        Stopping
    };  

我们的changeState()函数需要处理这两个新状态,并且其他状态的代码也需要更新:

void MainComponent::changeState(TransportState newState) {
    if (state != newState) {
        state = newState;
        switch (state) {
        case Stopped:
            playButton.setButtonText("Play");
            stopButton.setButtonText("Stop");
            stopButton.setEnabled(false);
            // playButton.setEnabled(true);
            transportSource.setPosition(0.0);
            break;
        case Starting:
            // playButton.setEnabled(false);
            transportSource.start();
        case Playing:
            playButton.setButtonText("Pause");
            stopButton.setButtonText("Stop");
            stopButton.setEnabled(true);
            break;
        case Pausing:
            transportSource.stop();
            break;
        case Paused:
            playButton.setButtonText("Resume");
            stopButton.setButtonText("Return to Zero");
            break;
        case Stopping:
            transportSource.stop();
            break;
        }
    }
}

我们适当地启用和禁用按钮,并在每个状态下正确更新按钮文本。

请注意,当要求在暂停状态下暂停时,我们实际上会停止传输。在changeListenerCallback()函数中,我们需要根据是否发出暂停或停止请求来更改逻辑以移动到正确的状态:

void MainComponent::changeListenerCallback(juce::ChangeBroadcaster* source) {
    if (source == &transportSource) {
        if (transportSource.isPlaying()) {
            changeState(TransportState::Playing);
        } else if ((state == Stopping) || (state == Playing)) {
            changeState(TransportState::Stopped);
        } else if (state == Pausing) {
            changeState(Paused);
        }
    }
}

我们需要更改单击播放按钮时的代码:

void MainComponent::playButtonClicked() {
    if ((state == Stopped) || (state == Paused))
        changeState(Starting);
    else if (state == Playing)
        changeState(Pausing);
}

当单击“停止”按钮时:

void MainComponent::stopButtonClicked() {
    if (state == Paused)
        changeState(Stopped);
    else
        changeState(Stopping);
}

就是这样:您现在应该能够构建并运行该应用程序。

练习:将Label对象添加到显示AudioTransportSource对象当前时间位置的界面。您可以使用AudioTransportSource::getCurrentPosition()函数获取此位置。您还需要让该MainContentComponent类继承自Timer类,并在timerCallback()函数中执行定期更新以更新标签。您甚至可以使用RelativeTime类将原始时间(以秒为单位)转换为更有用的格式(以分钟、秒和毫秒为单位)。

 

 

 

 

 

 

1

 

标签:AudioFormatReaderSource,juce,对象,JUCE,音频,transportSource,按钮
From: https://www.cnblogs.com/aoe1231/p/18450352

相关文章

  • JUCE - 入门
    注:本文档由JUCE官方教程翻译而来。JUCE官网:https://juce.com1、开始使用Projucer本教程将向您展示如何安装JUCE以及如何使用Projucer创建新的跨平台JUCE项目。您还将学习如何将项目导出到IDE(例如Xcode或VisualStudio)以开发、运行和调试您的JUCE应用程序。级别:初......
  • 音频采样率转换的研究与代码实现
    音频采样率转换本文原始版本发布于https://www.52pojie.cn/thread-1959816-1-1.html,此处进行了适当的精简,同时更新了一下代码(最新代码以GitHub仓库为准)。前言两年前,我研究了WASAPI播放音频的方法,详见https://www.cnblogs.com/PeaZomboss/p/17035785.html,挖了个坑,就是重采......
  • 免费在线音频转字幕网站 All In One
    免费在线音频转字幕网站AllInOne利用AI将语音转成文本/使用AI为视频添加字幕freeonlineSpeechtoTextwebsites每天三次免费https://turboscribe.ai/zh-CN/dashboarddemos(......
  • 用python写一段脚本:将旧的视频文件中的音频替换成新的,并保存成新的视频文件
    代码:importsubprocessdefreplace_audio_with_ffmpeg(video_path,audio_path,output_path):#构建ffmpeg命令command=['ffmpeg','-i',video_path,#输入视频文件'-an',#禁用输入视频的音频'-i&......
  • 音频格式转换软件 Coolutils Total Audio Converter 序列号
    CoolutilsTotalAudioConverter,音频转换器,可以将音频文件转换为WAV,MP3,OGG,WMA,APE,FLAC,MP4,AAC,MPC等音频格式。支持将CD转换无损格式,CUE分割APE和FLAC文件、自动化批量转换。该版本已内置序列号,可以使用全部功能。软件截图:使用说明:1、将压缩文件解压到固定......
  • WPF中播放音频文件
    SoundPlayer第一种方式,就是使用SoundPlayer。优点:平台自带,使用非常简单。缺点:只支持WAV音频格式,不支持MP3格式。示例代码:SoundPlayerplayer=newSoundPlayer("BLOW.WAV");player.Play();NAudio.NET平台,音频相关的开发,经常会用到NAudio这个库。优点:用起来相对也比较简......
  • 《音频转换之旅:探索 YouTube 到 WAV 的奇妙世界》
    #《音频转换之旅:探索YouTube到WAV的奇妙世界》今天呀,我们就来好好看看一个专门能把YouTube视频转成WAV格式的在线工具——https://www.youtubetowav.cc/zh-cn/  ##一、工具介绍这个叫https://www.youtubetowav.cc/zh-cn/的网页......
  • python处理英语发音音频的项目
    项目需求是这样的:想要获取单词的发音音频,词性,解释,音标,拆分单音标,根据发音拆分音节。实现思路:1、如果是批量处理的话,提前准备好需要处理的单词词库(全是要处理的单词)。2、通过API(你们懂得)可以获取到单词的  词性,解释,发音音频,保存到数据库中,测试单词:conversation3、处......
  • 鸿蒙媒体开发系列11——音频/录制流状态
    如果你也对鸿蒙开发感兴趣,加入“Harmony自习室”吧!扫描下方名片,关注公众号,公众号更新更快,同时也有更多学习资料和技术讨论群。对于录制音频类的应用,我们需要关注该应用的音频流的状态以做出相应的操作,比如监听到状态为结束时,及时提示用户录制已结束。......
  • 「漏洞复现」誉龙视音频综合管理平台 RelMedia/FindById SQL注入漏洞
    0x01 免责声明请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,作者不为此承担任何责任。工具来自网络,安全性自测,如有侵权请联系删除。本次测试仅供学习使用,如若非法他用,与平台和本文作者无关,需......